Train Random Forest and Search Optimal Parameters using h2o package

Random Forest is one of the most popular and most powerful machine learning algorithms. It is a type of ensemble machine learning algorithm called Bootstrap Aggregation or bagging.

Random forests are built on the same fundamental principles as decision trees and bagging (check out this tutorial if you need a refresher on these techniques). Bagging trees introduces a random component in to the tree building process that reduces the variance of a single tree’s prediction and improves predictive performance. However, the trees in bagging are not completely independent of each other since all the original predictors are considered at every split of every tree. Rather, trees from different bootstrap samples typically have similar structure to each other (especially at the top of the tree) due to underlying relationships.

However, Random Forests have important parameters which cannot be directly estimated from the data. Searching optimal parameters that maximizes model performance is called the process of tuning parameter.

There are different approaches to searching for the best parameters. A general approach that can be applied to almost any model is to define a set of candidate values, generate reliable estimates of model utility across the candidates values, then choose the optimal settings. A flowchart of this process is shown as follows:

More specifically, once a candidate set of parameter values has been selected, then we must obtain trustworthy estimates of model performance. The performance on the hold-out samples is then aggregated into a performance profile which is then used to determine the final tuning parameters. We then build a final model with all of the training data using the selected tuning parameters. The training data would then be resampled and evaluated many times for each tuning parameter value. These results would then be aggregated to find the optimal value of parameter.

Currently, there are over 20 random forest packages in R and in this post I will present the method of training and turning parameters for Random Forest by using h2o package. This package is a powerful and efficient java-based interface that provides parallel distributed algorithms. Moreover, h2o allows for different optimal search paths in our grid search. This allows us to be more efficient in tuning our models.

#=================================
#       Data Pre-processing
#=================================

# Load some packages for data manipulation: 
library(tidyverse)
library(magrittr)

# Clear workspace: 
rm(list = ls())

# Import data: 
hmeq <- read.csv("http://www.creditriskanalytics.net/uploads/1/9/5/1/19511601/hmeq.csv")

# Function replaces NA by mean: 
replace_by_mean <- function(x) {
  x[is.na(x)] <- mean(x, na.rm = TRUE)
  return(x)
}

# A function imputes NA observations for categorical variables: 

replace_na_categorical <- function(x) {
  x %>% 
    table() %>% 
    as.data.frame() %>% 
    arrange(-Freq) ->> my_df
  
  n_obs <- sum(my_df$Freq)
  pop <- my_df$. %>% as.character()
  set.seed(29)
  x[is.na(x)] <- sample(pop, sum(is.na(x)), replace = TRUE, prob = my_df$Freq)
  return(x)
}

# Use the two functions: 
df <- hmeq %>% 
  mutate_if(is.factor, as.character) %>% 
  mutate(REASON = case_when(REASON == "" ~ NA_character_, TRUE ~ REASON), 
         JOB = case_when(JOB == "" ~ NA_character_, TRUE ~ JOB), 
         BAD = case_when(BAD == 1 ~ "BAD", TRUE ~ "GOOD")) %>%
  mutate_if(is_character, as.factor) %>% 
  mutate_if(is.numeric, replace_by_mean) %>% 
  mutate_if(is.factor, replace_na_categorical)

# Split our data: 
set.seed(1)

df_train <- df %>% 
  group_by(BAD) %>% 
  sample_frac(0.7) %>% 
  ungroup() # Use 70% data set for training model. 
 
df_test <- dplyr::setdiff(df, df_train) # Use 30% data set for validation. 

# Activate h2o package for using: 
library(h2o)
h2o.init(nthreads = 20, max_mem_size = "16g")
## 
## H2O is not running yet, starting it now...
## 
## Note:  In case of errors look at the following log files:
##     /tmp/RtmpyvFm1p/h2o_chidung_started_from_r.out
##     /tmp/RtmpyvFm1p/h2o_chidung_started_from_r.err
## 
## 
## Starting H2O JVM and connecting: .. Connection successful!
## 
## R is connected to the H2O cluster: 
##     H2O cluster uptime:         2 seconds 46 milliseconds 
##     H2O cluster timezone:       Asia/Ho_Chi_Minh 
##     H2O data parsing timezone:  UTC 
##     H2O cluster version:        3.20.0.8 
##     H2O cluster version age:    2 months and 20 days  
##     H2O cluster name:           H2O_started_from_R_chidung_meh345 
##     H2O cluster total nodes:    1 
##     H2O cluster total memory:   16.00 GB 
##     H2O cluster total cores:    32 
##     H2O cluster allowed cores:  20 
##     H2O cluster healthy:        TRUE 
##     H2O Connection ip:          localhost 
##     H2O Connection port:        54321 
##     H2O Connection proxy:       NA 
##     H2O Internal Security:      FALSE 
##     H2O API Extensions:         XGBoost, Algos, AutoML, Core V3, Core V4 
##     R Version:                  R version 3.5.1 (2018-07-02)
h2o.no_progress()

# Convert to h2o Frame and identify inputs and output: 
test <- as.h2o(df_test)
train <- as.h2o(df_train)
y <- "BAD"
x <- setdiff(names(train), y)

Default Random Forest

R codes bellows will be used for training a “Default” Random Forest (for further information: http://docs.h2o.ai/h2o/latest-stable/h2o-r/docs/reference/h2o.randomForest.html):

# Train default Random Forest: 
default_rf <- h2o.randomForest(x = x, y = y, 
                               training_frame = train, 
                               stopping_rounds = 5, 
                               stopping_tolerance = 0.001, 
                               stopping_metric = "AUC", 
                               seed = 29, 
                               balance_classes = FALSE, 
                               nfolds = 10)

# Function for collecting cross-validation results: 

results_cross_validation <- function(h2o_model) {
  h2o_model@model$cross_validation_metrics_summary %>% 
    as.data.frame() %>% 
    select(-mean, -sd) %>% 
    t() %>% 
    as.data.frame() %>% 
    mutate_all(as.character) %>% 
    mutate_all(as.numeric) %>% 
    select(Accuracy = accuracy, 
           AUC = auc, 
           Precision = precision, 
           Specificity = specificity, 
           Recall = recall, 
           Logloss = logloss) %>% 
    return()
  }

# Use function: 
results_cross_validation(default_rf) -> ket_qua_default

# Model Performance by Graph: 
theme_set(theme_minimal())

plot_results <- function(df_results) {
  df_results %>% 
  gather(Metrics, Values) %>% 
  ggplot(aes(Metrics, Values, fill = Metrics, color = Metrics)) +
  geom_boxplot(alpha = 0.3, show.legend = FALSE) + 
  theme(plot.margin = unit(c(1, 1, 1, 1), "cm")) +    
  scale_y_continuous(labels = scales::percent) + 
  facet_wrap(~ Metrics, scales = "free") + 
  labs(title = "Model Performance by Some Criteria Selected", y = NULL)
  }

plot_results(ket_qua_default) +
  labs(subtitle = "Model: Random Forest (h2o package)")

Use model for prediction:

# Model performance based on test data: 
pred_class <- h2o.predict(default_rf, test) %>% as.data.frame() %>% pull(predict)
library(caret)
confusionMatrix(pred_class, df_test$BAD, positive = "BAD")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  BAD GOOD
##       BAD   287   52
##       GOOD   70 1379
##                                          
##                Accuracy : 0.9318         
##                  95% CI : (0.9191, 0.943)
##     No Information Rate : 0.8003         
##     P-Value [Acc > NIR] : <2e-16         
##                                          
##                   Kappa : 0.7824         
##  Mcnemar's Test P-Value : 0.1238         
##                                          
##             Sensitivity : 0.8039         
##             Specificity : 0.9637         
##          Pos Pred Value : 0.8466         
##          Neg Pred Value : 0.9517         
##              Prevalence : 0.1997         
##          Detection Rate : 0.1605         
##    Detection Prevalence : 0.1896         
##       Balanced Accuracy : 0.8838         
##                                          
##        'Positive' Class : BAD            
## 
# ROC curve and AUC: 
library(pROC) 

# Function calculates AUC: 
auc_for_test <- function(model_selected) {
  actual <- df_test$BAD
  pred_prob <- h2o.predict(model_selected, test) %>% as.data.frame() %>% pull(BAD)
  return(roc(actual, pred_prob))
}

# Use this function: 
my_auc <- auc_for_test(default_rf)
my_auc$auc
## Area under the curve: 0.9682
# Graph ROC and AUC: 

sen_spec_df <- data_frame(TPR = my_auc$sensitivities, FPR = 1 - my_auc$specificities)

sen_spec_df %>% 
  ggplot(aes(x = FPR, ymin = 0, ymax = TPR))+
  geom_polygon(aes(y = TPR), fill = "red", alpha = 0.3)+
  geom_path(aes(y = TPR), col = "firebrick", size = 1.2) +
  geom_abline(intercept = 0, slope = 1, color = "gray37", size = 1, linetype = "dashed") + 
  theme_bw() +
  coord_equal() +
  labs(x = "FPR (1 - Specificity)", 
       y = "TPR (Sensitivity)", 
       title = "Model Performance for RF Classifier based on Test Data", 
       subtitle = paste0("AUC Value: ", my_auc$auc %>% round(2)))

# Function for calculating CM: 

my_cm_com_rf <- function(thre) {
  du_bao_prob <- h2o.predict(default_rf, test) %>% as.data.frame() %>% pull(BAD)
  du_bao <- case_when(du_bao_prob >= thre ~ "BAD", 
                      du_bao_prob < thre ~ "GOOD") %>% as.factor()
  cm <- confusionMatrix(du_bao, df_test$BAD, positive = "BAD")
  return(cm)
  
}

Accuracy rate when predict bad applications by threshold selected:

# Set a range of threshold for classification: 
my_threshold <- c(0.10, 0.15, 0.35, 0.5)
results_list_rf <- lapply(my_threshold, my_cm_com_rf)

# Function for presenting prediction power by class:  

vis_detection_rate_rf <- function(x) {
  
  results_list_rf[[x]]$table %>% as.data.frame() -> m
  rate <- round(100*m$Freq[1] / sum(m$Freq[c(1, 2)]), 2)
  acc <- round(100*sum(m$Freq[c(1, 4)]) / sum(m$Freq), 2)
  acc <- paste0(acc, "%")
  
  m %>% 
    ggplot(aes(Reference, Freq, fill = Prediction)) +
    geom_col(position = "fill") + 
    scale_fill_manual(values = c("#e41a1c", "#377eb8"), name = "") + 
    theme(panel.grid.minor.y = element_blank()) + 
    theme(panel.grid.minor.x = element_blank()) + 
    scale_y_continuous(labels = scales::percent) + 
    labs(x = NULL, y = NULL, 
         title = paste0("Detecting Default Cases when Threshold = ", my_threshold[x]), 
         subtitle = paste0("Detecting Rate for Default Cases: ", rate, "%", ", ", "Accuracy: ", acc))
  }

# Use this function: 
gridExtra::grid.arrange(vis_detection_rate_rf(1), 
                        vis_detection_rate_rf(2), 
                        vis_detection_rate_rf(3), 
                        vis_detection_rate_rf(4))

Some Conclusions

Random forests provide a very powerful out-of-the-box algorithm that often has great predictive accuracy. Because of their more simplistic tuning nature and the fact that they require very little, if any, feature pre-processing they are often one of the first go-to algorithms when facing a predictive modeling problem.

In this post you discovered the importance of tuning well-performing machine learning Random Forest in order to get the best performance from them. You worked through an example of tuning the Random Forest algorithm in R and discovered two ways of turning parameter by using h20 package.

LS0tCnRpdGxlOiAiVHJhaW5pbmcgYW5kIFR1cm5pbmcgUGFyYW1ldGVycyBmb3IgUmFuZG9tIEZvcmVzdCBVc2luZyBoMm8gUGFja2FnZSIgCnN1YnRpdGxlOiAiUiBmb3IgUGxlYXN1cmUiCmF1dGhvcjogIk5ndXllbiBDaGkgRHVuZyIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6IAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgIyBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiAiZmxhdGx5IgogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKLS0tCgpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQpgYGAKCgojIFRyYWluIFJhbmRvbSBGb3Jlc3QgYW5kIFNlYXJjaCBPcHRpbWFsIFBhcmFtZXRlcnMgdXNpbmcgaDJvIHBhY2thZ2UKClJhbmRvbSBGb3Jlc3QgaXMgb25lIG9mIHRoZSBtb3N0IHBvcHVsYXIgYW5kIG1vc3QgcG93ZXJmdWwgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zLiBJdCBpcyBhIHR5cGUgb2YgZW5zZW1ibGUgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0gY2FsbGVkIEJvb3RzdHJhcCBBZ2dyZWdhdGlvbiBvciBiYWdnaW5nLiAKClJhbmRvbSBmb3Jlc3RzIGFyZSBidWlsdCBvbiB0aGUgc2FtZSBmdW5kYW1lbnRhbCBwcmluY2lwbGVzIGFzIGRlY2lzaW9uIHRyZWVzIGFuZCBiYWdnaW5nIChjaGVjayBvdXQgdGhpcyB0dXRvcmlhbCBpZiB5b3UgbmVlZCBhIHJlZnJlc2hlciBvbiB0aGVzZSB0ZWNobmlxdWVzKS4gQmFnZ2luZyB0cmVlcyBpbnRyb2R1Y2VzIGEgcmFuZG9tIGNvbXBvbmVudCBpbiB0byB0aGUgdHJlZSBidWlsZGluZyBwcm9jZXNzIHRoYXQgcmVkdWNlcyB0aGUgdmFyaWFuY2Ugb2YgYSBzaW5nbGUgdHJlZeKAmXMgcHJlZGljdGlvbiBhbmQgaW1wcm92ZXMgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZS4gSG93ZXZlciwgdGhlIHRyZWVzIGluIGJhZ2dpbmcgYXJlIG5vdCBjb21wbGV0ZWx5IGluZGVwZW5kZW50IG9mIGVhY2ggb3RoZXIgc2luY2UgYWxsIHRoZSBvcmlnaW5hbCBwcmVkaWN0b3JzIGFyZSBjb25zaWRlcmVkIGF0IGV2ZXJ5IHNwbGl0IG9mIGV2ZXJ5IHRyZWUuIFJhdGhlciwgdHJlZXMgZnJvbSBkaWZmZXJlbnQgYm9vdHN0cmFwIHNhbXBsZXMgdHlwaWNhbGx5IGhhdmUgc2ltaWxhciBzdHJ1Y3R1cmUgdG8gZWFjaCBvdGhlciAoZXNwZWNpYWxseSBhdCB0aGUgdG9wIG9mIHRoZSB0cmVlKSBkdWUgdG8gdW5kZXJseWluZyByZWxhdGlvbnNoaXBzLiAKCkhvd2V2ZXIsIFJhbmRvbSBGb3Jlc3RzIGhhdmUgaW1wb3J0YW50IHBhcmFtZXRlcnMgd2hpY2ggY2Fubm90IGJlIGRpcmVjdGx5IGVzdGltYXRlZCBmcm9tIHRoZSBkYXRhLiBTZWFyY2hpbmcgb3B0aW1hbCBwYXJhbWV0ZXJzIHRoYXQgbWF4aW1pemVzIG1vZGVsIHBlcmZvcm1hbmNlIGlzIGNhbGxlZCB0aGUgcHJvY2VzcyBvZiAqKnR1bmluZyBwYXJhbWV0ZXIqKi4gCgpUaGVyZSBhcmUgZGlmZmVyZW50IGFwcHJvYWNoZXMgdG8gc2VhcmNoaW5nIGZvciB0aGUgYmVzdCBwYXJhbWV0ZXJzLiBBIGdlbmVyYWwgYXBwcm9hY2ggdGhhdCBjYW4gYmUgYXBwbGllZCB0byBhbG1vc3QgYW55IG1vZGVsIGlzIHRvIGRlZmluZSBhIHNldCBvZiBjYW5kaWRhdGUgdmFsdWVzLCBnZW5lcmF0ZSByZWxpYWJsZSBlc3RpbWF0ZXMgb2YgbW9kZWwgdXRpbGl0eSBhY3Jvc3MgdGhlIGNhbmRpZGF0ZXMgdmFsdWVzLCB0aGVuIGNob29zZSB0aGUgb3B0aW1hbCBzZXR0aW5ncy4gQSBmbG93Y2hhcnQgb2YgdGhpcyBwcm9jZXNzIGlzIHNob3duIGFzIGZvbGxvd3M6IAoKIVtdKC9ob21lL2NoaWR1bmcvUGljdHVyZXMvdHVybmluZy5wbmcpCgpNb3JlIHNwZWNpZmljYWxseSwgb25jZSBhIGNhbmRpZGF0ZSBzZXQgb2YgcGFyYW1ldGVyIHZhbHVlcyBoYXMgYmVlbiBzZWxlY3RlZCwgdGhlbiB3ZSBtdXN0IG9idGFpbiB0cnVzdHdvcnRoeSBlc3RpbWF0ZXMgb2YgbW9kZWwgcGVyZm9ybWFuY2UuIFRoZSBwZXJmb3JtYW5jZSBvbiB0aGUgaG9sZC1vdXQgc2FtcGxlcyBpcyB0aGVuIGFnZ3JlZ2F0ZWQgaW50byBhIHBlcmZvcm1hbmNlIHByb2ZpbGUgd2hpY2ggaXMgdGhlbiB1c2VkIHRvIGRldGVybWluZSB0aGUgZmluYWwgdHVuaW5nIHBhcmFtZXRlcnMuIFdlIHRoZW4gYnVpbGQgYSBmaW5hbCBtb2RlbCB3aXRoIGFsbCBvZiB0aGUgdHJhaW5pbmcgZGF0YSB1c2luZyB0aGUgc2VsZWN0ZWQgdHVuaW5nIHBhcmFtZXRlcnMuIFRoZSB0cmFpbmluZyBkYXRhIHdvdWxkIHRoZW4gYmUgcmVzYW1wbGVkIGFuZCBldmFsdWF0ZWQgbWFueSB0aW1lcyBmb3IgZWFjaCB0dW5pbmcgcGFyYW1ldGVyIHZhbHVlLiBUaGVzZSByZXN1bHRzIHdvdWxkIHRoZW4gYmUgYWdncmVnYXRlZCB0byBmaW5kIHRoZSBvcHRpbWFsIHZhbHVlIG9mIHBhcmFtZXRlci4gCgpDdXJyZW50bHksIHRoZXJlIGFyZSBvdmVyIDIwIHJhbmRvbSBmb3Jlc3QgcGFja2FnZXMgaW4gUiBhbmQgaW4gdGhpcyBwb3N0IEkgd2lsbCBwcmVzZW50IHRoZSBtZXRob2Qgb2YgdHJhaW5pbmcgYW5kIHR1cm5pbmcgcGFyYW1ldGVycyBmb3IgUmFuZG9tIEZvcmVzdCBieSB1c2luZyAqKmgybyoqIHBhY2thZ2UuIFRoaXMgcGFja2FnZSBpcyBhIHBvd2VyZnVsIGFuZCBlZmZpY2llbnQgamF2YS1iYXNlZCBpbnRlcmZhY2UgdGhhdCBwcm92aWRlcyBwYXJhbGxlbCBkaXN0cmlidXRlZCBhbGdvcml0aG1zLiBNb3Jlb3ZlciwgaDJvIGFsbG93cyBmb3IgZGlmZmVyZW50IG9wdGltYWwgc2VhcmNoIHBhdGhzIGluIG91ciBncmlkIHNlYXJjaC4gVGhpcyBhbGxvd3MgdXMgdG8gYmUgbW9yZSBlZmZpY2llbnQgaW4gdHVuaW5nIG91ciBtb2RlbHMuCgoKYGBge3J9CiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyAgICAgICBEYXRhIFByZS1wcm9jZXNzaW5nCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiMgTG9hZCBzb21lIHBhY2thZ2VzIGZvciBkYXRhIG1hbmlwdWxhdGlvbjogCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KG1hZ3JpdHRyKQoKIyBDbGVhciB3b3Jrc3BhY2U6IApybShsaXN0ID0gbHMoKSkKCiMgSW1wb3J0IGRhdGE6IApobWVxIDwtIHJlYWQuY3N2KCJodHRwOi8vd3d3LmNyZWRpdHJpc2thbmFseXRpY3MubmV0L3VwbG9hZHMvMS85LzUvMS8xOTUxMTYwMS9obWVxLmNzdiIpCgojIEZ1bmN0aW9uIHJlcGxhY2VzIE5BIGJ5IG1lYW46IApyZXBsYWNlX2J5X21lYW4gPC0gZnVuY3Rpb24oeCkgewogIHhbaXMubmEoeCldIDwtIG1lYW4oeCwgbmEucm0gPSBUUlVFKQogIHJldHVybih4KQp9CgojIEEgZnVuY3Rpb24gaW1wdXRlcyBOQSBvYnNlcnZhdGlvbnMgZm9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlczogCgpyZXBsYWNlX25hX2NhdGVnb3JpY2FsIDwtIGZ1bmN0aW9uKHgpIHsKICB4ICU+JSAKICAgIHRhYmxlKCkgJT4lIAogICAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICAgIGFycmFuZ2UoLUZyZXEpIC0+PiBteV9kZgogIAogIG5fb2JzIDwtIHN1bShteV9kZiRGcmVxKQogIHBvcCA8LSBteV9kZiQuICU+JSBhcy5jaGFyYWN0ZXIoKQogIHNldC5zZWVkKDI5KQogIHhbaXMubmEoeCldIDwtIHNhbXBsZShwb3AsIHN1bShpcy5uYSh4KSksIHJlcGxhY2UgPSBUUlVFLCBwcm9iID0gbXlfZGYkRnJlcSkKICByZXR1cm4oeCkKfQoKIyBVc2UgdGhlIHR3byBmdW5jdGlvbnM6IApkZiA8LSBobWVxICU+JSAKICBtdXRhdGVfaWYoaXMuZmFjdG9yLCBhcy5jaGFyYWN0ZXIpICU+JSAKICBtdXRhdGUoUkVBU09OID0gY2FzZV93aGVuKFJFQVNPTiA9PSAiIiB+IE5BX2NoYXJhY3Rlcl8sIFRSVUUgfiBSRUFTT04pLCAKICAgICAgICAgSk9CID0gY2FzZV93aGVuKEpPQiA9PSAiIiB+IE5BX2NoYXJhY3Rlcl8sIFRSVUUgfiBKT0IpLCAKICAgICAgICAgQkFEID0gY2FzZV93aGVuKEJBRCA9PSAxIH4gIkJBRCIsIFRSVUUgfiAiR09PRCIpKSAlPiUKICBtdXRhdGVfaWYoaXNfY2hhcmFjdGVyLCBhcy5mYWN0b3IpICU+JSAKICBtdXRhdGVfaWYoaXMubnVtZXJpYywgcmVwbGFjZV9ieV9tZWFuKSAlPiUgCiAgbXV0YXRlX2lmKGlzLmZhY3RvciwgcmVwbGFjZV9uYV9jYXRlZ29yaWNhbCkKCiMgU3BsaXQgb3VyIGRhdGE6IApzZXQuc2VlZCgxKQoKZGZfdHJhaW4gPC0gZGYgJT4lIAogIGdyb3VwX2J5KEJBRCkgJT4lIAogIHNhbXBsZV9mcmFjKDAuNykgJT4lIAogIHVuZ3JvdXAoKSAjIFVzZSA3MCUgZGF0YSBzZXQgZm9yIHRyYWluaW5nIG1vZGVsLiAKIApkZl90ZXN0IDwtIGRwbHlyOjpzZXRkaWZmKGRmLCBkZl90cmFpbikgIyBVc2UgMzAlIGRhdGEgc2V0IGZvciB2YWxpZGF0aW9uLiAKCiMgQWN0aXZhdGUgaDJvIHBhY2thZ2UgZm9yIHVzaW5nOiAKbGlicmFyeShoMm8pCmgyby5pbml0KG50aHJlYWRzID0gMjAsIG1heF9tZW1fc2l6ZSA9ICIxNmciKQpoMm8ubm9fcHJvZ3Jlc3MoKQoKIyBDb252ZXJ0IHRvIGgybyBGcmFtZSBhbmQgaWRlbnRpZnkgaW5wdXRzIGFuZCBvdXRwdXQ6IAp0ZXN0IDwtIGFzLmgybyhkZl90ZXN0KQp0cmFpbiA8LSBhcy5oMm8oZGZfdHJhaW4pCnkgPC0gIkJBRCIKeCA8LSBzZXRkaWZmKG5hbWVzKHRyYWluKSwgeSkKCmBgYAoKCiMgRGVmYXVsdCBSYW5kb20gRm9yZXN0CgpSIGNvZGVzIGJlbGxvd3Mgd2lsbCBiZSB1c2VkIGZvciB0cmFpbmluZyBhICJEZWZhdWx0IiBSYW5kb20gRm9yZXN0IChmb3IgZnVydGhlciBpbmZvcm1hdGlvbjogaHR0cDovL2RvY3MuaDJvLmFpL2gyby9sYXRlc3Qtc3RhYmxlL2gyby1yL2RvY3MvcmVmZXJlbmNlL2gyby5yYW5kb21Gb3Jlc3QuaHRtbCk6IAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgVHJhaW4gZGVmYXVsdCBSYW5kb20gRm9yZXN0OiAKZGVmYXVsdF9yZiA8LSBoMm8ucmFuZG9tRm9yZXN0KHggPSB4LCB5ID0geSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfdG9sZXJhbmNlID0gMC4wMDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIkFVQyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDI5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhbGFuY2VfY2xhc3NlcyA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IDEwKQoKIyBGdW5jdGlvbiBmb3IgY29sbGVjdGluZyBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHM6IAoKcmVzdWx0c19jcm9zc192YWxpZGF0aW9uIDwtIGZ1bmN0aW9uKGgyb19tb2RlbCkgewogIGgyb19tb2RlbEBtb2RlbCRjcm9zc192YWxpZGF0aW9uX21ldHJpY3Nfc3VtbWFyeSAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgc2VsZWN0KC1tZWFuLCAtc2QpICU+JSAKICAgIHQoKSAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgbXV0YXRlX2FsbChhcy5jaGFyYWN0ZXIpICU+JSAKICAgIG11dGF0ZV9hbGwoYXMubnVtZXJpYykgJT4lIAogICAgc2VsZWN0KEFjY3VyYWN5ID0gYWNjdXJhY3ksIAogICAgICAgICAgIEFVQyA9IGF1YywgCiAgICAgICAgICAgUHJlY2lzaW9uID0gcHJlY2lzaW9uLCAKICAgICAgICAgICBTcGVjaWZpY2l0eSA9IHNwZWNpZmljaXR5LCAKICAgICAgICAgICBSZWNhbGwgPSByZWNhbGwsIAogICAgICAgICAgIExvZ2xvc3MgPSBsb2dsb3NzKSAlPiUgCiAgICByZXR1cm4oKQogIH0KCiMgVXNlIGZ1bmN0aW9uOiAKcmVzdWx0c19jcm9zc192YWxpZGF0aW9uKGRlZmF1bHRfcmYpIC0+IGtldF9xdWFfZGVmYXVsdAoKIyBNb2RlbCBQZXJmb3JtYW5jZSBieSBHcmFwaDogCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpCgpwbG90X3Jlc3VsdHMgPC0gZnVuY3Rpb24oZGZfcmVzdWx0cykgewogIGRmX3Jlc3VsdHMgJT4lIAogIGdhdGhlcihNZXRyaWNzLCBWYWx1ZXMpICU+JSAKICBnZ3Bsb3QoYWVzKE1ldHJpY3MsIFZhbHVlcywgZmlsbCA9IE1ldHJpY3MsIGNvbG9yID0gTWV0cmljcykpICsKICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwLjMsIHNob3cubGVnZW5kID0gRkFMU0UpICsgCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMSwgMSwgMSwgMSksICJjbSIpKSArICAgIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgCiAgZmFjZXRfd3JhcCh+IE1ldHJpY3MsIHNjYWxlcyA9ICJmcmVlIikgKyAKICBsYWJzKHRpdGxlID0gIk1vZGVsIFBlcmZvcm1hbmNlIGJ5IFNvbWUgQ3JpdGVyaWEgU2VsZWN0ZWQiLCB5ID0gTlVMTCkKICB9CgpwbG90X3Jlc3VsdHMoa2V0X3F1YV9kZWZhdWx0KSArCiAgbGFicyhzdWJ0aXRsZSA9ICJNb2RlbDogUmFuZG9tIEZvcmVzdCAoaDJvIHBhY2thZ2UpIikKCmBgYAoKClVzZSBtb2RlbCBmb3IgcHJlZGljdGlvbjogCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCiMgTW9kZWwgcGVyZm9ybWFuY2UgYmFzZWQgb24gdGVzdCBkYXRhOiAKcHJlZF9jbGFzcyA8LSBoMm8ucHJlZGljdChkZWZhdWx0X3JmLCB0ZXN0KSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBwdWxsKHByZWRpY3QpCmxpYnJhcnkoY2FyZXQpCmNvbmZ1c2lvbk1hdHJpeChwcmVkX2NsYXNzLCBkZl90ZXN0JEJBRCwgcG9zaXRpdmUgPSAiQkFEIikKCiMgUk9DIGN1cnZlIGFuZCBBVUM6IApsaWJyYXJ5KHBST0MpIAoKIyBGdW5jdGlvbiBjYWxjdWxhdGVzIEFVQzogCmF1Y19mb3JfdGVzdCA8LSBmdW5jdGlvbihtb2RlbF9zZWxlY3RlZCkgewogIGFjdHVhbCA8LSBkZl90ZXN0JEJBRAogIHByZWRfcHJvYiA8LSBoMm8ucHJlZGljdChtb2RlbF9zZWxlY3RlZCwgdGVzdCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcHVsbChCQUQpCiAgcmV0dXJuKHJvYyhhY3R1YWwsIHByZWRfcHJvYikpCn0KCiMgVXNlIHRoaXMgZnVuY3Rpb246IApteV9hdWMgPC0gYXVjX2Zvcl90ZXN0KGRlZmF1bHRfcmYpCm15X2F1YyRhdWMKCiMgR3JhcGggUk9DIGFuZCBBVUM6IAoKc2VuX3NwZWNfZGYgPC0gZGF0YV9mcmFtZShUUFIgPSBteV9hdWMkc2Vuc2l0aXZpdGllcywgRlBSID0gMSAtIG15X2F1YyRzcGVjaWZpY2l0aWVzKQoKc2VuX3NwZWNfZGYgJT4lIAogIGdncGxvdChhZXMoeCA9IEZQUiwgeW1pbiA9IDAsIHltYXggPSBUUFIpKSsKICBnZW9tX3BvbHlnb24oYWVzKHkgPSBUUFIpLCBmaWxsID0gInJlZCIsIGFscGhhID0gMC4zKSsKICBnZW9tX3BhdGgoYWVzKHkgPSBUUFIpLCBjb2wgPSAiZmlyZWJyaWNrIiwgc2l6ZSA9IDEuMikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAiZ3JheTM3Iiwgc2l6ZSA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIpICsgCiAgdGhlbWVfYncoKSArCiAgY29vcmRfZXF1YWwoKSArCiAgbGFicyh4ID0gIkZQUiAoMSAtIFNwZWNpZmljaXR5KSIsIAogICAgICAgeSA9ICJUUFIgKFNlbnNpdGl2aXR5KSIsIAogICAgICAgdGl0bGUgPSAiTW9kZWwgUGVyZm9ybWFuY2UgZm9yIFJGIENsYXNzaWZpZXIgYmFzZWQgb24gVGVzdCBEYXRhIiwgCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlMCgiQVVDIFZhbHVlOiAiLCBteV9hdWMkYXVjICU+JSByb3VuZCgyKSkpCgojIEZ1bmN0aW9uIGZvciBjYWxjdWxhdGluZyBDTTogCgpteV9jbV9jb21fcmYgPC0gZnVuY3Rpb24odGhyZSkgewogIGR1X2Jhb19wcm9iIDwtIGgyby5wcmVkaWN0KGRlZmF1bHRfcmYsIHRlc3QpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHB1bGwoQkFEKQogIGR1X2JhbyA8LSBjYXNlX3doZW4oZHVfYmFvX3Byb2IgPj0gdGhyZSB+ICJCQUQiLCAKICAgICAgICAgICAgICAgICAgICAgIGR1X2Jhb19wcm9iIDwgdGhyZSB+ICJHT09EIikgJT4lIGFzLmZhY3RvcigpCiAgY20gPC0gY29uZnVzaW9uTWF0cml4KGR1X2JhbywgZGZfdGVzdCRCQUQsIHBvc2l0aXZlID0gIkJBRCIpCiAgcmV0dXJuKGNtKQogIAp9CgpgYGAKCkFjY3VyYWN5IHJhdGUgd2hlbiBwcmVkaWN0IGJhZCBhcHBsaWNhdGlvbnMgYnkgdGhyZXNob2xkIHNlbGVjdGVkOiAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBmaWcuZnVsbHdpZHRoID0gVFJVRSwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTJ9CiMgU2V0IGEgcmFuZ2Ugb2YgdGhyZXNob2xkIGZvciBjbGFzc2lmaWNhdGlvbjogCm15X3RocmVzaG9sZCA8LSBjKDAuMTAsIDAuMTUsIDAuMzUsIDAuNSkKcmVzdWx0c19saXN0X3JmIDwtIGxhcHBseShteV90aHJlc2hvbGQsIG15X2NtX2NvbV9yZikKCiMgRnVuY3Rpb24gZm9yIHByZXNlbnRpbmcgcHJlZGljdGlvbiBwb3dlciBieSBjbGFzczogIAoKdmlzX2RldGVjdGlvbl9yYXRlX3JmIDwtIGZ1bmN0aW9uKHgpIHsKICAKICByZXN1bHRzX2xpc3RfcmZbW3hdXSR0YWJsZSAlPiUgYXMuZGF0YS5mcmFtZSgpIC0+IG0KICByYXRlIDwtIHJvdW5kKDEwMCptJEZyZXFbMV0gLyBzdW0obSRGcmVxW2MoMSwgMildKSwgMikKICBhY2MgPC0gcm91bmQoMTAwKnN1bShtJEZyZXFbYygxLCA0KV0pIC8gc3VtKG0kRnJlcSksIDIpCiAgYWNjIDwtIHBhc3RlMChhY2MsICIlIikKICAKICBtICU+JSAKICAgIGdncGxvdChhZXMoUmVmZXJlbmNlLCBGcmVxLCBmaWxsID0gUHJlZGljdGlvbikpICsKICAgIGdlb21fY29sKHBvc2l0aW9uID0gImZpbGwiKSArIAogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2U0MWExYyIsICIjMzc3ZWI4IiksIG5hbWUgPSAiIikgKyAKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgCiAgICBsYWJzKHggPSBOVUxMLCB5ID0gTlVMTCwgCiAgICAgICAgIHRpdGxlID0gcGFzdGUwKCJEZXRlY3RpbmcgRGVmYXVsdCBDYXNlcyB3aGVuIFRocmVzaG9sZCA9ICIsIG15X3RocmVzaG9sZFt4XSksIAogICAgICAgICBzdWJ0aXRsZSA9IHBhc3RlMCgiRGV0ZWN0aW5nIFJhdGUgZm9yIERlZmF1bHQgQ2FzZXM6ICIsIHJhdGUsICIlIiwgIiwgIiwgIkFjY3VyYWN5OiAiLCBhY2MpKQogIH0KCiMgVXNlIHRoaXMgZnVuY3Rpb246IApncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSh2aXNfZGV0ZWN0aW9uX3JhdGVfcmYoMSksIAogICAgICAgICAgICAgICAgICAgICAgICB2aXNfZGV0ZWN0aW9uX3JhdGVfcmYoMiksIAogICAgICAgICAgICAgICAgICAgICAgICB2aXNfZGV0ZWN0aW9uX3JhdGVfcmYoMyksIAogICAgICAgICAgICAgICAgICAgICAgICB2aXNfZGV0ZWN0aW9uX3JhdGVfcmYoNCkpCgpgYGAKCgojIEZ1bGwgQ2FydGVzaWFuIEdyaWQgU2VhcmNoCgpXaGVuIFVzaW5nIGEgZnVsbCBjYXJ0ZXNpYW4gZ3JpZCBzZWFyY2gsIHdlIHdpbGwgZXhhbWluZSBldmVyeSBjb21iaW5hdGlvbiBvZiBoeXBlcnBhcmFtZXRlciBzZXR0aW5ncyB0aGF0IHdlIHNwZWNpZnkgaW4gKipoeXBlcl9ncmlkLmgybyoqIG9iamVjdDoKCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZmlnLmZ1bGx3aWR0aCA9IFRSVUUsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEyfQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIEZ1bGwgQ2FydGVzaWFuIEdyaWQgU2VhcmNoCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiMgU2V0IGh5cGVycGFyYW1ldGVyIGdyaWQ6IAoKaHlwZXJfZ3JpZC5oMm8gPC0gbGlzdChudHJlZXMgPSBzZXEoNTAsIDUwMCwgYnkgPSA1MCksCiAgICAgICAgICAgICAgICAgICAgICAgbXRyaWVzID0gc2VxKDMsIDUsIGJ5ID0gMSksCiAgICAgICAgICAgICAgICAgICAgICAgIyBtYXhfZGVwdGggPSBzZXEoMTAsIDMwLCBieSA9IDEwKSwKICAgICAgICAgICAgICAgICAgICAgICAjIG1pbl9yb3dzID0gc2VxKDEsIDMsIGJ5ID0gMSksCiAgICAgICAgICAgICAgICAgICAgICAgIyBuYmlucyA9IHNlcSgyMCwgMzAsIGJ5ID0gMTApLAogICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9yYXRlID0gYygwLjU1LCAwLjYzMiwgMC43NSkpCgojIFRoZSBudW1iZXIgb2YgbW9kZWxzIGlzIDkwOiAKc2FwcGx5KGh5cGVyX2dyaWQuaDJvLCBsZW5ndGgpICU+JSBwcm9kKCkKCiMgVHJhaW4gNjAwMCBSYW5kb20gRm9yZXN0IE1vZGVsczogCnN5c3RlbS50aW1lKGdyaWRfY2FydGVzaWFuIDwtIGgyby5ncmlkKGFsZ29yaXRobSA9ICJyYW5kb21Gb3Jlc3QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmlkX2lkID0gInJmX2dyaWQxIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IHgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0geSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAyOSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IDEwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIkFVQyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoeXBlcl9wYXJhbXMgPSBoeXBlcl9ncmlkLmgybywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VhcmNoX2NyaXRlcmlhID0gbGlzdChzdHJhdGVneSA9ICJDYXJ0ZXNpYW4iKSkpCgojIENvbGxlY3QgdGhlIHJlc3VsdHMgYW5kIHNvcnQgYnkgb3VyIG1vZGVsIHBlcmZvcm1hbmNlIG1ldHJpYyBvZiBjaG9pY2U6IApncmlkX3BlcmYgPC0gaDJvLmdldEdyaWQoZ3JpZF9pZCA9ICJyZl9ncmlkMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgc29ydF9ieSA9ICJhdWMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGRlY3JlYXNpbmcgPSBGQUxTRSkKCiMgQmVzdCBtb2RlbCBjaG9zZW4gYnkgdmFsaWRhdGlvbiBlcnJvcjogCmJlc3RfbW9kZWwgPC0gaDJvLmdldE1vZGVsKGdyaWRfcGVyZkBtb2RlbF9pZHNbWzFdXSkKCiMgVXNlIGJlc3QgbW9kZWwgZm9yIG1ha2luZyBwcmVkaWN0aW9uczogCmNvbmZ1c2lvbk1hdHJpeChoMm8ucHJlZGljdChiZXN0X21vZGVsLCB0ZXN0KSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBwdWxsKHByZWRpY3QpLCAKICAgICAgICAgICAgICAgIGRmX3Rlc3QkQkFELCBwb3NpdGl2ZSA9ICJCQUQiKQoKIyBDb21wYXJlOiAKY29uZnVzaW9uTWF0cml4KGgyby5wcmVkaWN0KGRlZmF1bHRfcmYsIHRlc3QpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHB1bGwocHJlZGljdCksIAogICAgICAgICAgICAgICAgZGZfdGVzdCRCQUQsIHBvc2l0aXZlID0gIkJBRCIpCgojIEJ5IEdyYXBoOiAKbXlfY21fY29tX3JmX2Jlc3QgPC0gZnVuY3Rpb24odGhyZSkgewogIGR1X2Jhb19wcm9iIDwtIGgyby5wcmVkaWN0KGJlc3RfbW9kZWwsIHRlc3QpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHB1bGwoQkFEKQogIGR1X2JhbyA8LSBjYXNlX3doZW4oZHVfYmFvX3Byb2IgPj0gdGhyZSB+ICJCQUQiLCAKICAgICAgICAgICAgICAgICAgICAgIGR1X2Jhb19wcm9iIDwgdGhyZSB+ICJHT09EIikgJT4lIGFzLmZhY3RvcigpCiAgY20gPC0gY29uZnVzaW9uTWF0cml4KGR1X2JhbywgZGZfdGVzdCRCQUQsIHBvc2l0aXZlID0gIkJBRCIpCiAgcmV0dXJuKGNtKQogIAp9CgoKcmVzdWx0c19saXN0X3JmX2Jlc3QgPC0gbGFwcGx5KG15X3RocmVzaG9sZCwgbXlfY21fY29tX3JmX2Jlc3QpCgojIEZ1bmN0aW9uIGZvciBwcmVzZW50aW5nIHByZWRpY3Rpb24gcG93ZXIgYnkgY2xhc3M6ICAKCnZpc19kZXRlY3Rpb25fcmF0ZV9yZl9iZXN0IDwtIGZ1bmN0aW9uKHgpIHsKICAKICByZXN1bHRzX2xpc3RfcmZfYmVzdFtbeF1dJHRhYmxlICU+JSBhcy5kYXRhLmZyYW1lKCkgLT4gbQogIHJhdGUgPC0gcm91bmQoMTAwKm0kRnJlcVsxXSAvIHN1bShtJEZyZXFbYygxLCAyKV0pLCAyKQogIGFjYyA8LSByb3VuZCgxMDAqc3VtKG0kRnJlcVtjKDEsIDQpXSkgLyBzdW0obSRGcmVxKSwgMikKICBhY2MgPC0gcGFzdGUwKGFjYywgIiUiKQogIAogIG0gJT4lIAogICAgZ2dwbG90KGFlcyhSZWZlcmVuY2UsIEZyZXEsIGZpbGwgPSBQcmVkaWN0aW9uKSkgKwogICAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIpICsgCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjZTQxYTFjIiwgIiMzNzdlYjgiKSwgbmFtZSA9ICIiKSArIAogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKSArIAogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArIAogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKyAKICAgIGxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMLCAKICAgICAgICAgdGl0bGUgPSBwYXN0ZTAoIkRldGVjdGluZyBEZWZhdWx0IENhc2VzIHdoZW4gVGhyZXNob2xkID0gIiwgbXlfdGhyZXNob2xkW3hdKSwgCiAgICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKCJEZXRlY3RpbmcgUmF0ZSBmb3IgRGVmYXVsdCBDYXNlczogIiwgcmF0ZSwgIiUiLCAiLCAiLCAiQWNjdXJhY3k6ICIsIGFjYykpCiAgfQoKIyBVc2UgdGhpcyBmdW5jdGlvbjogCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHZpc19kZXRlY3Rpb25fcmF0ZV9yZl9iZXN0KDEpLCAKICAgICAgICAgICAgICAgICAgICAgICAgdmlzX2RldGVjdGlvbl9yYXRlX3JmX2Jlc3QoMiksIAogICAgICAgICAgICAgICAgICAgICAgICB2aXNfZGV0ZWN0aW9uX3JhdGVfcmZfYmVzdCgzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHZpc19kZXRlY3Rpb25fcmF0ZV9yZl9iZXN0KDQpKQoKCmBgYAoKIyBSYW5kb20gRGlzY3JldGUgR3JpZCBTZWFyY2gKCkJlY2F1c2Ugb2YgdGhlIGNvbWJpbmF0b3JpYWwgZXhwbG9zaW9uLCBlYWNoIGFkZGl0aW9uYWwgaHlwZXJwYXJhbWV0ZXIgdGhhdCBnZXRzIGFkZGVkIHRvIG91ciBncmlkIHNlYXJjaCBoYXMgYSBodWdlIGVmZmVjdCBvbiB0aGUgdGltZSB0byBjb21wbGV0ZS4gQ29uc2VxdWVudGx5LCBoMm8gcHJvdmlkZXMgYW4gYWRkaXRpb25hbCBncmlkIHNlYXJjaCBwYXRoIGNhbGxlZCAqKlJhbmRvbURpc2NyZXRlKiosIHdoaWNoIHdpbGwganVtcCBmcm9tIG9uZSByYW5kb20gY29tYmluYXRpb24gdG8gYW5vdGhlciBhbmQgc3RvcCBvbmNlIGEgY2VydGFpbiBsZXZlbCBvZiBpbXByb3ZlbWVudCBoYXMgYmVlbiBtYWRlLCBjZXJ0YWluIGFtb3VudCBvZiB0aW1lIGhhcyBiZWVuIGV4Y2VlZGVkLCBvciBhIGNlcnRhaW4gYW1vdW50IG9mIG1vZGVscyBoYXZlIGJlZW4gcmFuIChvciBhIGNvbWJpbmF0aW9uIG9mIHRoZXNlIGhhdmUgYmVlbiBtZXQpLiBBbHRob3VnaCB1c2luZyBhIHJhbmRvbSBkaXNjcmV0ZSBzZWFyY2ggcGF0aCB3aWxsIGxpa2VseSBub3QgZmluZCB0aGUgb3B0aW1hbCBtb2RlbCwgaXQgdHlwaWNhbGx5IGRvZXMgYSBnb29kIGpvYiBvZiBmaW5kaW5nIGEgdmVyeSBnb29kIG1vZGVsLgoKRm9yIGV4YW1wbGUsIHRoZSBmb2xsb3dpbmcgY29kZSBzZWFyY2hlcyBhIGxhcmdlIGdyaWQgc2VhcmNoIG9mIDkwIGh5cGVycGFyYW1ldGVyIGNvbWJpbmF0aW9ucy4gV2UgY3JlYXRlIGEgcmFuZG9tIGdyaWQgc2VhcmNoIHRoYXQgd2lsbCBzdG9wIGlmOiAKCjEuIE5vbmUgb2YgdGhlIGxhc3QgMTAgbW9kZWxzIGhhdmUgbWFuYWdlZCB0byBoYXZlIGEgMC41JSBpbXByb3ZlbWVudCBpbiBBVUMgY29tcGFyZWQgdG8gdGhlIGJlc3QgbW9kZWwgYmVmb3JlIHRoYXQuIAoKMi4gV2UgY29udGludWUgdG8gZmluZCBpbXByb3ZlbWVudHMgdGhlbiBJIGN1dCB0aGUgZ3JpZCBzZWFyY2ggb2ZmIGFmdGVyIDYwMCBzZWNvbmRzICgzMCBtaW51dGVzKS4gCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEyfQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIFJhbmRvbSBEaXNjcmV0ZSBHcmlkIFNlYXJjaAojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgojIFNldCByYW5kb20gZ3JpZCBzZWFyY2ggY3JpdGVyaWE6IApzZWFyY2hfY3JpdGVyaWFfMiA8LSBsaXN0KHN0cmF0ZWd5ID0gIlJhbmRvbURpc2NyZXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAiQVVDIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ190b2xlcmFuY2UgPSAwLjAwNSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19yb3VuZHMgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfcnVudGltZV9zZWNzID0gMzAqNjApCgoKIyBUdXJuIHBhcmFtZXRlcnMgZm9yIFJGOiAKc3lzdGVtLnRpbWUocmFuZG9tX2dyaWQgPC0gaDJvLmdyaWQoYWxnb3JpdGhtID0gInJhbmRvbUZvcmVzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyaWRfaWQgPSAicmZfZ3JpZDIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4ID0geCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSB5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDI5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmZvbGRzID0gMTAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoeXBlcl9wYXJhbXMgPSBoeXBlcl9ncmlkLmgybywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VhcmNoX2NyaXRlcmlhID0gc2VhcmNoX2NyaXRlcmlhXzIpKQoKIyBDb2xsZWN0IHRoZSByZXN1bHRzIGFuZCBzb3J0IGJ5IG91ciBtb2RlbHM6IApncmlkX3BlcmYyIDwtIGgyby5nZXRHcmlkKGdyaWRfaWQgPSAicmZfZ3JpZDIiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBzb3J0X2J5ID0gIkFVQyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGRlY3JlYXNpbmcgPSBGQUxTRSkKCiMgQmVzdCBSRjogCmJlc3RfbW9kZWwyIDwtIGgyby5nZXRNb2RlbChncmlkX3BlcmYyQG1vZGVsX2lkc1tbMV1dKQoKIyBVc2UgdGhpcyBiZXN0IG1vZGVsIGZvciBwcmVkaWN0aW9uIHdpdGggc29tZSB0aHJlc2hvbGRzIHNlbGVjdGVkOiAKCm15X2NtX2NvbV9yZl9iZXN0MiA8LSBmdW5jdGlvbih0aHJlKSB7CiAgZHVfYmFvX3Byb2IgPC0gaDJvLnByZWRpY3QoYmVzdF9tb2RlbDIsIHRlc3QpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHB1bGwoQkFEKQogIGR1X2JhbyA8LSBjYXNlX3doZW4oZHVfYmFvX3Byb2IgPj0gdGhyZSB+ICJCQUQiLCAKICAgICAgICAgICAgICAgICAgICAgIGR1X2Jhb19wcm9iIDwgdGhyZSB+ICJHT09EIikgJT4lIGFzLmZhY3RvcigpCiAgY20gPC0gY29uZnVzaW9uTWF0cml4KGR1X2JhbywgZGZfdGVzdCRCQUQsIHBvc2l0aXZlID0gIkJBRCIpCiAgcmV0dXJuKGNtKQogIAp9CgoKcmVzdWx0c19saXN0X3JmX2Jlc3QyIDwtIGxhcHBseShteV90aHJlc2hvbGQsIG15X2NtX2NvbV9yZl9iZXN0MikKCiMgRnVuY3Rpb24gZm9yIHByZXNlbnRpbmcgcHJlZGljdGlvbiBwb3dlciBieSBjbGFzczogIAoKdmlzX2RldGVjdGlvbl9yYXRlX3JmX2Jlc3QyIDwtIGZ1bmN0aW9uKHgpIHsKICAKICByZXN1bHRzX2xpc3RfcmZfYmVzdDJbW3hdXSR0YWJsZSAlPiUgYXMuZGF0YS5mcmFtZSgpIC0+IG0KICByYXRlIDwtIHJvdW5kKDEwMCptJEZyZXFbMV0gLyBzdW0obSRGcmVxW2MoMSwgMildKSwgMikKICBhY2MgPC0gcm91bmQoMTAwKnN1bShtJEZyZXFbYygxLCA0KV0pIC8gc3VtKG0kRnJlcSksIDIpCiAgYWNjIDwtIHBhc3RlMChhY2MsICIlIikKICAKICBtICU+JSAKICAgIGdncGxvdChhZXMoUmVmZXJlbmNlLCBGcmVxLCBmaWxsID0gUHJlZGljdGlvbikpICsKICAgIGdlb21fY29sKHBvc2l0aW9uID0gImZpbGwiKSArIAogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2U0MWExYyIsICIjMzc3ZWI4IiksIG5hbWUgPSAiIikgKyAKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgCiAgICBsYWJzKHggPSBOVUxMLCB5ID0gTlVMTCwgCiAgICAgICAgIHRpdGxlID0gcGFzdGUwKCJEZXRlY3RpbmcgRGVmYXVsdCBDYXNlcyB3aGVuIFRocmVzaG9sZCA9ICIsIG15X3RocmVzaG9sZFt4XSksIAogICAgICAgICBzdWJ0aXRsZSA9IHBhc3RlMCgiRGV0ZWN0aW5nIFJhdGUgZm9yIERlZmF1bHQgQ2FzZXM6ICIsIHJhdGUsICIlIiwgIiwgIiwgIkFjY3VyYWN5OiAiLCBhY2MpKQogIH0KCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSh2aXNfZGV0ZWN0aW9uX3JhdGVfcmZfYmVzdDIoMSksIAogICAgICAgICAgICAgICAgICAgICAgICB2aXNfZGV0ZWN0aW9uX3JhdGVfcmZfYmVzdDIoMiksIAogICAgICAgICAgICAgICAgICAgICAgICB2aXNfZGV0ZWN0aW9uX3JhdGVfcmZfYmVzdDIoMyksIAogICAgICAgICAgICAgICAgICAgICAgICB2aXNfZGV0ZWN0aW9uX3JhdGVfcmZfYmVzdDIoNCkpCgoKCmBgYAoKIyBTb21lIENvbmNsdXNpb25zIAoKUmFuZG9tIGZvcmVzdHMgcHJvdmlkZSBhIHZlcnkgcG93ZXJmdWwgb3V0LW9mLXRoZS1ib3ggYWxnb3JpdGhtIHRoYXQgb2Z0ZW4gaGFzIGdyZWF0IHByZWRpY3RpdmUgYWNjdXJhY3kuIEJlY2F1c2Ugb2YgdGhlaXIgbW9yZSBzaW1wbGlzdGljIHR1bmluZyBuYXR1cmUgYW5kIHRoZSBmYWN0IHRoYXQgdGhleSByZXF1aXJlIHZlcnkgbGl0dGxlLCBpZiBhbnksIGZlYXR1cmUgcHJlLXByb2Nlc3NpbmcgdGhleSBhcmUgb2Z0ZW4gb25lIG9mIHRoZSBmaXJzdCBnby10byBhbGdvcml0aG1zIHdoZW4gZmFjaW5nIGEgcHJlZGljdGl2ZSBtb2RlbGluZyBwcm9ibGVtLgoKSW4gdGhpcyBwb3N0IHlvdSBkaXNjb3ZlcmVkIHRoZSBpbXBvcnRhbmNlIG9mIHR1bmluZyB3ZWxsLXBlcmZvcm1pbmcgbWFjaGluZSBsZWFybmluZyBSYW5kb20gRm9yZXN0IGluIG9yZGVyIHRvIGdldCB0aGUgYmVzdCBwZXJmb3JtYW5jZSBmcm9tIHRoZW0uIFlvdSB3b3JrZWQgdGhyb3VnaCBhbiBleGFtcGxlIG9mIHR1bmluZyB0aGUgUmFuZG9tIEZvcmVzdCBhbGdvcml0aG0gaW4gUiBhbmQgZGlzY292ZXJlZCB0d28gd2F5cyBvZiB0dXJuaW5nIHBhcmFtZXRlciBieSB1c2luZyBoMjAgcGFja2FnZS4gCg==