# Rmarkdown stuff
library(rmarkdown, quietly = TRUE)
library(knitr, quietly = TRUE)
library(htmltools, quietly = TRUE)
library(styler, quietly = TRUE)
library(xaringanExtra, quietly = TRUE)
# Dataset
library(ISLR2, quietly = TRUE)
# Session info and package reporting
library(report, quietly = TRUE)
# Data wrangling
library(dplyr, quietly = TRUE)
library(magrittr, quietly = TRUE)
# Create interactive tables
library(reactable, quietly = TRUE)
# For plotting
library(ggplot2, quietly = TRUE)
# From github
library(parttree, quietly = TRUE)
library(ConfusionTableR, quietly = TRUE)
# For Decision Classification Trees
library(rpart, quietly = TRUE)
library(rpart.plot, quietly = TRUE)
# For Bagging and Random Forest
library(randomForest, quietly = TRUE)
# For Boosting
library(Ckmeans.1d.dp, quietly = TRUE)
library(xgboost, quietly = TRUE)
library(DiagrammeR, quietly = TRUE)
library(DiagrammeRsvg, quietly = TRUE)
library(rsvg, quietly = TRUE)
# For BART
library(dbarts, quietly = TRUE)
# From github
library(embarcadero, quietly = TRUE)
# For variable importance
library(vip, quietly = TRUE)
# For parallel processing for tuning
library(foreach, quietly = TRUE)
library(doParallel, quietly = TRUE)
# For applying tidymodels
library(rsample, quietly = TRUE)
library(recipes, quietly = TRUE)
library(parsnip, quietly = TRUE)
library(dials, quietly = TRUE)
library(tune, quietly = TRUE)
library(workflows, quietly = TRUE)
library(yardstick, quietly = TRUE)
summary(report::report(sessionInfo()))
The analysis was done using the R Statistical language (v4.2.0; R
Core Team, 2022) on Windows 10 x64, using the packages randomForest
(v4.7.1), vip (v0.3.2), ggplot2 (v3.3.5), rmarkdown (v2.14), report
(v0.5.1), sp (v1.4.7), ROCR (v1.0.11), Ckmeans.1d.dp (v4.3.4), dbarts
(v0.9.22), tidyverse (v1.3.1), knitr (v1.39), ConfusionTableR (v1.0.4),
data.table (v1.14.2), DiagrammeR (v1.0.9), DiagrammeRsvg (v0.1), dials
(v0.1.1), dismo (v1.3.5), doParallel (v1.0.17), dplyr (v1.0.9),
embarcadero (v1.2.0.1003), forcats (v0.5.1), foreach (v1.5.2), ggpubr
(v0.4.0), htmltools (v0.5.2), ISLR2 (v1.3.1), iterators (v1.0.14),
magrittr (v2.0.3), matrixStats (v0.62.0), Metrics (v0.1.4), parsnip
(v0.2.1), parttree (v0.0.1.9000), patchwork (v1.1.1), purrr (v0.3.4),
raster (v3.5.15), reactable (v0.2.3), readr (v2.1.2), recipes (v0.2.0),
rpart (v4.1.16), rpart.plot (v3.1.0), rsample (v0.1.1), rsvg (v2.3.1),
scales (v1.2.0), stringr (v1.4.0), styler (v1.7.0), tibble (v3.1.6),
tidyr (v1.2.0), tune (v0.2.0), workflows (v0.2.6), xaringanExtra
(v0.5.5), xgboost (v1.6.0.1) and yardstick (v0.0.9).
Fitting Classification Trees
In the Carseats
data set from the ISLR2
package, Sales
of
child car seats is a continuous variable, and so we begin by recording
it as a binary variable called High
, which takes on a value
of Yes if the Sales variable exceeds 8, and takes on a value of No
otherwise.
Carseats <- dplyr::as_tibble(ISLR2::Carseats) %>%
dplyr::mutate(
High = factor(dplyr::if_else(.data[["Sales"]] <= 8, "No", "Yes"))
) %>%
dplyr::select(-dplyr::all_of(c("Sales")))
Carseats %>%
reactable::reactable(
defaultPageSize = 5,
filterable = TRUE
)
Create the resample object
First, we split the samples into a training set and a test set. From
the training set, we create a 10-fold cross-validation data set from the
training set.
This is done with rsample::initial_split
,
rsample::training
,
rsample::testing
and rsample::vfold_cv
.
Carseats_split <- rsample::initial_split(Carseats)
Carseats_train <- rsample::training(Carseats_split)
Carseats_test <- rsample::testing(Carseats_split)
Carseats_fold <- rsample::vfold_cv(Carseats_train, v = 10)
Create the preprocessor
We create a recipe with recipes::recipe
.
No other preprocessing step is done.
class_tree_recipe <-
recipes::recipe(formula = High ~ ., data = Carseats_train)
Specify the model
Use parsnip::decision_tree
,
parsnip::set_mode
and parsnip::set_engine
to create the model.
The cost complexity is tuned using tune::tune
An engine specific parameter model = TRUE
is set for
rpart
to prevent this warning message from coming up later
when rpart.plot::rpart.plot
is used later.
#> Warning: Cannot retrieve the data used to build the model (so cannot determine roundint and is.binary for the variables).
#> To silence this warning:
#> Call rpart.plot with roundint=FALSE,
#> or rebuild the rpart model with model=TRUE.
class_tree_spec <- parsnip::decision_tree(
tree_depth = 4,
cost_complexity = tune::tune()
) %>%
parsnip::set_mode("classification") %>%
parsnip::set_engine("rpart", model = TRUE)
class_tree_spec %>%
parsnip::translate()
> Decision Tree Model Specification (classification)
>
> Main Arguments:
> cost_complexity = tune::tune()
> tree_depth = 4
>
> Engine-Specific Arguments:
> model = TRUE
>
> Computational engine: rpart
>
> Model fit template:
> rpart::rpart(formula = missing_arg(), data = missing_arg(), weights = missing_arg(),
> cp = tune::tune(), maxdepth = 4, model = TRUE)
Create the workflow
workflows::workflow
,
workflows::add_recipe
and workflows::add_model
are used.
class_tree_workflow <- workflows::workflow() %>%
workflows::add_recipe(class_tree_recipe) %>%
workflows::add_model(class_tree_spec)
class_tree_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: decision_tree()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
> Decision Tree Model Specification (classification)
>
> Main Arguments:
> cost_complexity = tune::tune()
> tree_depth = 4
>
> Engine-Specific Arguments:
> model = TRUE
>
> Computational engine: rpart
Create the cost complexity grid
A cost complexity grid of \(10\)
numbers from \(0.001\) (\(10^{-3}\)) to \(10\) (\(10^1\)) is created.
Regular grid is created using dials::grid_regular
,
dials::cost_complexity
and scales::log10_trans
# Create a range from 10
cost_complexity_grid <-
dials::grid_regular(
x = dials::cost_complexity(
range = c(-3, 1),
trans = scales::log10_trans()
),
levels = 10
)
cost_complexity_grid %>%
reactable::reactable(defaultPageSize = 5)
Classification tree model fitting on cross validated data
Now we have everything we need and we can fit all the models on the
cross validated data with tune::tune_grid
.
Note that this process may take some time.
We use yardstick::metric_set
,
to choose a set of metrics to used to evaluate the model. In this
example, yardstick::accuracy
,
yardstick::roc_auc
,
yardstick::sensitivity
and yardstick::specificity
are used.
doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
object = class_tree_workflow,
resamples = Carseats_fold,
grid = cost_complexity_grid,
metrics = yardstick::metric_set(
yardstick::accuracy,
yardstick::roc_auc,
yardstick::sensitivity,
yardstick::specificity
)
)
tune_res
> # Tuning results
> # 10-fold cross-validation
> # A tibble: 10 × 4
> splits id .metrics .notes
> <list> <chr> <list> <list>
> 1 <split [270/30]> Fold01 <tibble> <tibble [0 × 3]>
> 2 <split [270/30]> Fold02 <tibble> <tibble [0 × 3]>
> 3 <split [270/30]> Fold03 <tibble> <tibble [0 × 3]>
> 4 <split [270/30]> Fold04 <tibble> <tibble [0 × 3]>
> 5 <split [270/30]> Fold05 <tibble> <tibble [0 × 3]>
> 6 <split [270/30]> Fold06 <tibble> <tibble [0 × 3]>
> 7 <split [270/30]> Fold07 <tibble> <tibble [0 × 3]>
> 8 <split [270/30]> Fold08 <tibble> <tibble [0 × 3]>
> 9 <split [270/30]> Fold09 <tibble> <tibble [0 × 3]>
> 10 <split [270/30]> Fold10 <tibble> <tibble [0 × 3]>
Here we see that the amount of cost complexity affects the
performance metrics differently using tune::autoplot
.
Do note that using a different seed will give a different plot
# Note that a different seed will give different plots
tune::autoplot(tune_res)
We can also see the raw metrics that created this chart by calling tune::collect_metrics()
.
tune::collect_metrics(tune_res) %>%
reactable::reactable(defaultPageSize = 5)
Here is the ggplot
way should tune::autoplot
fails
tune_res %>%
tune::collect_metrics() %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["cost_complexity"]],
y = .data[["mean"]],
colour = .data[[".metric"]]
)) +
ggplot2::geom_errorbar(
mapping = ggplot2::aes(
ymin = .data[["mean"]] - .data[["std_err"]],
ymax = .data[["mean"]] + .data[["std_err"]]
),
alpha = 0.5
) +
ggplot2::geom_line(size = 1.5) +
ggplot2::facet_wrap(
facets = ggplot2::vars(.data[[".metric"]]),
scales = "free",
nrow = 2
) +
ggplot2::scale_x_log10() +
ggplot2::theme(legend.position = "none")
Use tune::show_best
to see the top few values for a given metric.
The “best” values can be selected using tune::select_best
,
this function requires you to specify a metric that it should select
against. The cost complexity value is 0.001 for metric
accuracy
since it gives the highest value. Do note that
using a different seed will give a different best cost complexity
value.
top_cost_complexity <- tune::show_best(tune_res, metric = c("accuracy"), n = 5)
top_cost_complexity %>%
reactable::reactable(defaultPageSize = 5)
best_cost_complexity <- tune::select_best(tune_res, metric = "accuracy")
best_cost_complexity
> # A tibble: 1 × 2
> cost_complexity .config
> <dbl> <chr>
> 1 0.0215 Preprocessor1_Model04
Classification tree model with optimised cost complexity value
We create the classification tree workflow with the best cost
complexity score using tune::finalize_workflow
.
class_tree_final <- tune::finalize_workflow(
x = class_tree_workflow,
parameters = best_cost_complexity
)
class_tree_final
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: decision_tree()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
> Decision Tree Model Specification (classification)
>
> Main Arguments:
> cost_complexity = 0.0215443469003188
> tree_depth = 4
>
> Engine-Specific Arguments:
> model = TRUE
>
> Computational engine: rpart
We now train the classification tree model with the training data
using parsnip::fit
class_tree_final_fit <- parsnip::fit(object = class_tree_final, data = Carseats_train)
We can see the tree in greater detail using tune::extract_fit_engine
class_tree_final_fit %>%
tune::extract_fit_engine()
> n= 300
>
> node), split, n, loss, yval, (yprob)
> * denotes terminal node
>
> 1) root 300 120 No (0.6000000 0.4000000)
> 2) ShelveLoc=Bad,Medium 242 73 No (0.6983471 0.3016529)
> 4) Price>=92.5 213 51 No (0.7605634 0.2394366)
> 8) Advertising< 13.5 175 30 No (0.8285714 0.1714286) *
> 9) Advertising>=13.5 38 17 Yes (0.4473684 0.5526316)
> 18) Age>=55.5 17 4 No (0.7647059 0.2352941) *
> 19) Age< 55.5 21 4 Yes (0.1904762 0.8095238) *
> 5) Price< 92.5 29 7 Yes (0.2413793 0.7586207) *
> 3) ShelveLoc=Good 58 11 Yes (0.1896552 0.8103448)
> 6) Price>=136.5 9 3 No (0.6666667 0.3333333) *
> 7) Price< 136.5 49 5 Yes (0.1020408 0.8979592) *
We can visualise the above better with
rpart.plot::rpart.plot
class_tree_final_fit %>%
tune::extract_fit_engine() %>%
rpart.plot::rpart.plot()
Variable Importance
What are the most important variables in this tree for predicting
Sales
?
Using the vip
package and workflows::extract_fit_parsnip
,
we have
vip_table <- class_tree_final_fit %>%
workflows::extract_fit_parsnip() %>%
vip::vi()
vip_table %>%
reactable::reactable(defaultPageSize = 5)
class_tree_final_fit %>%
workflows::extract_fit_parsnip() %>%
vip::vip(
geom = "col",
aesthetics = list(fill = "midnightblue", alpha = 0.8)
) +
ggplot2::scale_y_continuous(expand = c(0, 0))
We can use parttree
to understand why these two parameters work so well on the training
set.
However this package only works with two continuous predictors. As
ShelveLoc
is a categorical variable, there is a need to
“convert” them to continuous by making “Bad” as -1, “Medium” as 0 and
“Good” as 1.
Carseats_train_modfified <- Carseats_train %>%
dplyr::mutate(
ShelveLoc = dplyr::case_when(
.data[["ShelveLoc"]] == "Bad" ~ "-1",
.data[["ShelveLoc"]] == "Medium" ~ "0",
.data[["ShelveLoc"]] == "Good" ~ "1"
)
) %>%
dplyr::mutate(
ShelveLoc = as.numeric(.data[["ShelveLoc"]])
)
partial_tree <- parsnip::decision_tree(
tree_depth = 30,
cost_complexity = best_cost_complexity$cost_complexity
) %>%
parsnip::set_mode("classification") %>%
parsnip::set_engine("rpart", model = TRUE) %>%
parsnip::fit(High ~ Price + ShelveLoc,
data = Carseats_train_modfified
) %>%
tune::extract_fit_engine()
Carseats_train_modfified %>%
ggplot2::ggplot(
mapping = ggplot2::aes(
x = .data[["ShelveLoc"]],
y = .data[["Price"]]
)
) +
parttree::geom_parttree(
data = partial_tree,
mapping = ggplot2::aes(fill = .data[["High"]]),
alpha = 0.2
) +
ggplot2::geom_jitter(
alpha = 0.7,
width = 0.05,
height = 0.2,
mapping = ggplot2::aes(color = .data[["High"]])
)
Classification tree model on test data
Finally, let’s turn to the testing data. For classification models, a
.pred_class
, column and class probability columns named
.pred_{level} are added when parsnip::augment
is used.
test_results <- parsnip::augment(
x = class_tree_final_fit,
new_data = Carseats_test
)
test_results %>%
reactable::reactable(defaultPageSize = 5)
We can view the confusion matrix using yardstick::conf_mat
test_results %>%
yardstick::conf_mat(
truth = .data[["High"]],
estimate = .data[[".pred_class"]]
) %>%
ggplot2::autoplot(type = "heatmap")
test_results %>%
yardstick::conf_mat(truth = .data[["High"]], estimate = .data[[".pred_class"]]) %>%
summary() %>%
reactable::reactable(defaultPageSize = 5)
or by ConfusionTableR::binary_visualiseR
ConfusionTableR::binary_visualiseR(
train_labels = test_results[["High"]],
truth_labels = test_results[[".pred_class"]],
class_label1 = "Yes",
class_label2 = "No",
quadrant_col1 = "#28ACB4",
quadrant_col2 = "#4397D2",
custom_title = "High Confusion Matrix",
text_col = "black"
)
We can view the ROC curve using yardstick::roc_curve
roc_plot_data <- test_results %>%
yardstick::roc_curve(
truth = test_results[["High"]],
.data[[".pred_No"]]
)
roc_plot_data %>%
reactable::reactable(defaultPageSize = 5)
roc_plot_data %>%
ggplot2::autoplot()
Here is a ggplot2
version.
roc_plot_data %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = 1 - .data[["specificity"]],
y = .data[["sensitivity"]]
)) +
ggplot2::geom_line(size = 1.5, color = "midnightblue") +
ggplot2::geom_abline(
lty = 2, alpha = 0.5,
color = "gray50",
size = 1.2
) +
ggplot2::coord_equal()
To view the metrics, we can also use tune::last_fit
and tune::collect_metrics
.
metrics_results <- tune::last_fit(
object = class_tree_final_fit,
split = Carseats_split
) %>%
tune::collect_metrics()
metrics_results %>%
reactable::reactable(defaultPageSize = 5)
Fitting Regression Trees
In the Boston
data set from the ISLR2
package, we want to predict
medv
, the median value of owner-occupied homes in
$1000’s.
Boston <- dplyr::as_tibble(ISLR2::Boston)
Boston %>%
reactable::reactable(
defaultPageSize = 5,
filterable = TRUE
)
Create the resample object
First, we split the samples into a training set and a test set. From
the training set, we create a 10-fold cross-validation data set from the
training set.
This is done with rsample::initial_split
,
rsample::training
,
rsample::testing
and rsample::vfold_cv
.
Boston_split <- rsample::initial_split(Boston)
Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)
Boston_fold <- rsample::vfold_cv(Boston_train, v = 10)
Create the preprocessor
We create a recipe with recipes::recipe
.
No other preprocessing step is done.
reg_tree_recipe <-
recipes::recipe(formula = medv ~ ., data = Boston_train)
Specify the model
Use parsnip::decision_tree
,
parsnip::set_mode
and parsnip::set_engine
to create the model.
The cost complexity is tuned using tune::tune
An engine specific parameter model = TRUE
is set for
rpart
to prevent this warning message from coming up later
when rpart.plot::rpart.plot
is used later.
#> Warning: Cannot retrieve the data used to build the model (so cannot determine roundint and is.binary for the variables).
#> To silence this warning:
#> Call rpart.plot with roundint=FALSE,
#> or rebuild the rpart model with model=TRUE.
reg_tree_spec <- parsnip::decision_tree(
# tree_depth = 4,
cost_complexity = tune::tune()
) %>%
parsnip::set_mode("regression") %>%
parsnip::set_engine("rpart", model = TRUE)
reg_tree_spec %>%
parsnip::translate()
> Decision Tree Model Specification (regression)
>
> Main Arguments:
> cost_complexity = tune::tune()
>
> Engine-Specific Arguments:
> model = TRUE
>
> Computational engine: rpart
>
> Model fit template:
> rpart::rpart(formula = missing_arg(), data = missing_arg(), weights = missing_arg(),
> cp = tune::tune(), model = TRUE)
Create the workflow
workflows::workflow
,
workflows::add_recipe
and workflows::add_model
are used.
reg_tree_workflow <- workflows::workflow() %>%
workflows::add_recipe(reg_tree_recipe) %>%
workflows::add_model(reg_tree_spec)
reg_tree_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: decision_tree()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
> Decision Tree Model Specification (regression)
>
> Main Arguments:
> cost_complexity = tune::tune()
>
> Engine-Specific Arguments:
> model = TRUE
>
> Computational engine: rpart
Create the cost complexity grid
A cost complexity grid of \(10\)
numbers from \(0.001\) (\(10^{-4}\)) to \(10\) (\(10^1\)) is created.
Regular grid is created using dials::grid_regular
,
dials::cost_complexity
and scales::log10_trans
# Create a range from 10
cost_complexity_grid <-
dials::grid_regular(
x = dials::cost_complexity(
range = c(-4, 1),
trans = scales::log10_trans()
),
levels = 10
)
cost_complexity_grid %>%
reactable::reactable(defaultPageSize = 5)
Regression tree model fitting on cross validated data
Now we have everything we need and we can fit all the models on the
cross validated data with tune::tune_grid
.
Note that this process may take some time.
We use yardstick::metric_set
,
to choose a set of metrics to used to evaluate the model. In this
example, yardstick::rmse
and yardstick::rsq
are used.
doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
object = reg_tree_workflow,
resamples = Boston_fold,
grid = cost_complexity_grid,
# metrics = yardstick::metric_set(yardstick::rmse,
# yardstick::rsq)
)
tune_res
> # Tuning results
> # 10-fold cross-validation
> # A tibble: 10 × 4
> splits id .metrics .notes
> <list> <chr> <list> <list>
> 1 <split [341/38]> Fold01 <tibble> <tibble [1 × 3]>
> 2 <split [341/38]> Fold02 <tibble> <tibble [1 × 3]>
> 3 <split [341/38]> Fold03 <tibble> <tibble [1 × 3]>
> 4 <split [341/38]> Fold04 <tibble> <tibble [1 × 3]>
> 5 <split [341/38]> Fold05 <tibble> <tibble [1 × 3]>
> 6 <split [341/38]> Fold06 <tibble> <tibble [1 × 3]>
> 7 <split [341/38]> Fold07 <tibble> <tibble [1 × 3]>
> 8 <split [341/38]> Fold08 <tibble> <tibble [1 × 3]>
> 9 <split [341/38]> Fold09 <tibble> <tibble [1 × 3]>
> 10 <split [342/37]> Fold10 <tibble> <tibble [1 × 3]>
>
> There were issues with some computations:
>
> - Warning(s) x10: A correlation computation is required, bu...
>
> Use `collect_notes(object)` for more information.
Here we see that the amount of cost complexity affects the
performance metrics differently using tune::autoplot
.
Do note that using a different seed will give a different plot
# Note that a different seed will give different plots
tune::autoplot(tune_res)
We can also see the raw metrics that created this chart by calling tune::collect_metrics()
.
tune::collect_metrics(tune_res) %>%
reactable::reactable(defaultPageSize = 5)
Here is the ggplot
way should tune::autoplot
fails
tune_res %>%
tune::collect_metrics() %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["cost_complexity"]],
y = .data[["mean"]],
colour = .data[[".metric"]]
)) +
ggplot2::geom_errorbar(
mapping = ggplot2::aes(
ymin = .data[["mean"]] - .data[["std_err"]],
ymax = .data[["mean"]] + .data[["std_err"]]
),
alpha = 0.5
) +
ggplot2::geom_line(size = 1.5) +
ggplot2::facet_wrap(
facets = ggplot2::vars(.data[[".metric"]]),
scales = "free",
nrow = 2
) +
ggplot2::scale_x_log10() +
ggplot2::theme(legend.position = "none")
> Warning: Removed 3 row(s) containing missing values
> (geom_path).
Use tune::show_best
to see the top few values for a given metric.
The “best” values can be selected using tune::select_best
,
this function requires you to specify a metric that it should select
against. The cost complexity value is 0.00129 for metric
rmse
since it gives the lowest value. Do note that using a
different seed will give a different best cost complexity value.
top_cost_complexity <- tune::show_best(tune_res, metric = c("rmse"), n = 5)
top_cost_complexity %>%
reactable::reactable(defaultPageSize = 5)
best_cost_complexity <- tune::select_best(tune_res, metric = "rmse")
best_cost_complexity
> # A tibble: 1 × 2
> cost_complexity .config
> <dbl> <chr>
> 1 0.00129 Preprocessor1_Model03
Regression tree model with optimised cost complexity value
We create the regression tree workflow with the best cost complexity
score using tune::finalize_workflow
.
reg_tree_final <- tune::finalize_workflow(
x = reg_tree_workflow,
parameters = best_cost_complexity
)
reg_tree_final
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: decision_tree()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
> Decision Tree Model Specification (regression)
>
> Main Arguments:
> cost_complexity = 0.00129154966501488
>
> Engine-Specific Arguments:
> model = TRUE
>
> Computational engine: rpart
We now train the regression tree model with the training data using
parsnip::fit
reg_tree_final_fit <- parsnip::fit(object = reg_tree_final, data = Boston_train)
We can see the tree in greater detail using tune::extract_fit_engine
reg_tree_final_fit %>%
tune::extract_fit_engine()
> n= 379
>
> node), split, n, deviance, yval
> * denotes terminal node
>
> 1) root 379 32622.95000 22.548020
> 2) rm< 6.941 320 13602.31000 19.862810
> 4) lstat>=14.395 129 2582.10900 14.515500
> 8) nox>=0.607 80 984.73390 12.358750
> 16) lstat>=19.34 47 388.63320 10.359570
> 32) tax>=551.5 40 243.94980 9.677500
> 64) crim>=24.59775 7 26.27714 7.157143 *
> 65) crim< 24.59775 33 163.77520 10.212120 *
> 33) tax< 551.5 7 19.73714 14.257140 *
> 17) lstat< 19.34 33 140.71880 15.206060
> 34) crim>=6.99237 13 34.98769 13.630770 *
> 35) crim< 6.99237 20 52.50200 16.230000 *
> 9) nox< 0.607 49 617.69390 18.036730
> 18) crim>=0.381565 25 313.20000 16.200000
> 36) ptratio>=20.6 8 16.23875 14.162500 *
> 37) ptratio< 20.6 17 248.12120 17.158820 *
> 19) crim< 0.381565 24 132.30000 19.950000
> 38) age>=72.8 15 51.49333 18.766670 *
> 39) age< 72.8 9 24.79556 21.922220 *
> 5) lstat< 14.395 191 4840.36400 23.474350
> 10) rm< 6.543 151 2861.39900 22.211920
> 20) dis>=1.68515 144 1179.59700 21.820830
> 40) rm< 6.062 56 306.22860 20.285710 *
> 41) rm>=6.062 88 657.41950 22.797730
> 82) lstat>=9.98 35 98.32686 21.025710 *
> 83) lstat< 9.98 53 376.61550 23.967920
> 166) crim< 0.048715 17 100.97530 22.629410 *
> 167) crim>=0.048715 36 230.80000 24.600000
> 334) tax>=280.5 27 43.50667 23.911110 *
> 335) tax< 280.5 9 136.04000 26.666670 *
> 21) dis< 1.68515 7 1206.69700 30.257140 *
> 11) rm>=6.543 40 829.85600 28.240000
> 22) lstat>=4.44 33 274.06180 27.154550
> 44) dis>=3.9683 19 131.76530 26.015790 *
> 45) dis< 3.9683 14 84.22000 28.700000 *
> 23) lstat< 4.44 7 333.61710 33.357140 *
> 3) rm>=6.941 59 4199.10200 37.111860
> 6) rm< 7.437 35 1012.41000 32.082860
> 12) nox>=0.4885 14 673.46930 28.892860 *
> 13) nox< 0.4885 21 101.49810 34.209520 *
> 7) rm>=7.437 24 1010.62000 44.445830
> 14) ptratio>=15.4 12 585.07670 40.716670 *
> 15) ptratio< 15.4 12 91.78250 48.175000 *
We can visualise the above better with
rpart.plot::rpart.plot
reg_tree_final_fit %>%
tune::extract_fit_engine() %>%
rpart.plot::rpart.plot()
Variable Importance
What are the most important variables in this tree for predicting
medv
?
Using the vip
package and workflows::extract_fit_parsnip
,
we have
vip_table <- reg_tree_final_fit %>%
workflows::extract_fit_parsnip() %>%
vip::vi()
vip_table %>%
reactable::reactable(defaultPageSize = 5)
reg_tree_final_fit %>%
workflows::extract_fit_parsnip() %>%
vip::vip(
geom = "col",
aesthetics = list(fill = "midnightblue", alpha = 0.8)
) +
ggplot2::scale_y_continuous(expand = c(0, 0))
We can use parttree
to understand why these two parameters work so well on the training
set.
partial_tree <- parsnip::decision_tree(
cost_complexity = best_cost_complexity$cost_complexity
) %>%
parsnip::set_mode("regression") %>%
parsnip::set_engine("rpart", model = TRUE) %>%
parsnip::fit(medv ~ rm + lstat,
data = Boston_train
) %>%
tune::extract_fit_engine()
Boston_train %>%
ggplot2::ggplot(
mapping = ggplot2::aes(
x = .data[["rm"]],
y = .data[["lstat"]]
)
) +
parttree::geom_parttree(
data = partial_tree,
mapping = ggplot2::aes(fill = .data[["medv"]]),
alpha = 0.2
) +
ggplot2::geom_jitter(
alpha = 0.7,
width = 0.05,
height = 0.2,
mapping = ggplot2::aes(color = .data[["medv"]])
) +
ggplot2::scale_colour_viridis_c(aesthetics = c("color", "fill"))
Regression tree model on test data
Finally, let’s turn to the testing data. For regression models, a
.pred
, column is added when parsnip::augment
is used.
test_results <- parsnip::augment(
x = reg_tree_final_fit,
new_data = Boston_test
)
test_results %>%
reactable::reactable(defaultPageSize = 5)
We check how well the .pred
column matches the
medv
using yardstick::rmse
.
test_results %>%
yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
reactable::reactable(defaultPageSize = 5)
Alternatively, we can use tune::last_fit
and tune::collect_metrics
.
test_rs <- tune::last_fit(
object = reg_tree_final_fit,
split = Boston_split
)
test_rs %>%
tune::collect_metrics() %>%
reactable::reactable(defaultPageSize = 5)
Use tune::collect_predictions
,
to see only the actual and predicted values of the test data.
test_rs %>%
tune::collect_predictions() %>%
reactable::reactable(defaultPageSize = 5)
Let us take a closer look at the predicted and actual response as a
scatter plot.
test_rs %>%
tune::collect_predictions() %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["medv"]],
y = .data[[".pred"]]
)) +
ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
ggplot2::coord_fixed()
Bagging
Here we apply bagging to the Boston
data, using the
randomForest
package in R
Create the resample object
First, we split the samples into a training set and a test set. From
the training set, we create a 10-fold cross-validation data set from the
training set.
This is done with rsample::initial_split
,
rsample::training
and rsample::testing
Boston_split <- rsample::initial_split(Boston)
Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)
Create the preprocessor
We create a recipe with recipes::recipe
.
No other preprocessing step is done.
bagging_recipe <-
recipes::recipe(formula = medv ~ ., data = Boston_train)
Specify the model
Use parsnip::rand_forest
,
parsnip::set_mode
and parsnip::set_engine
to create the model.
mtry
is the number of predictors that will be randomly
sampled at each split when creating the tree models. For bagging, that
number is the number of columns in the predictor matrix denoted by parsnip::.cols
importance
set to TRUE ensures the importance of
predictors are assessed.
bagging_spec <- parsnip::rand_forest(mtry = .cols()) %>%
parsnip::set_engine("randomForest", importance = TRUE) %>%
parsnip::set_mode("regression")
bagging_spec %>%
parsnip::translate()
> Random Forest Model Specification (regression)
>
> Main Arguments:
> mtry = .cols()
>
> Engine-Specific Arguments:
> importance = TRUE
>
> Computational engine: randomForest
>
> Model fit template:
> randomForest::randomForest(x = missing_arg(), y = missing_arg(),
> mtry = min_cols(~.cols(), x), importance = TRUE)
Create the workflow
workflows::workflow
,
workflows::add_recipe
and workflows::add_model
are used.
bagging_workflow <- workflows::workflow() %>%
workflows::add_recipe(bagging_recipe) %>%
workflows::add_model(bagging_spec)
bagging_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: rand_forest()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
> Random Forest Model Specification (regression)
>
> Main Arguments:
> mtry = .cols()
>
> Engine-Specific Arguments:
> importance = TRUE
>
> Computational engine: randomForest
Bagging model on training data
bagging_fit <- parsnip::fit(
object = bagging_workflow,
data = Boston_train
)
Variable Importance
What are the most important variables in this tree for predicting
medv
?
Using the vip
package and workflows::extract_fit_parsnip
,
we have
vip_table <- bagging_fit %>%
workflows::extract_fit_parsnip() %>%
vip::vi()
vip_table %>%
reactable::reactable(defaultPageSize = 5)
bagging_fit %>%
workflows::extract_fit_parsnip() %>%
vip::vip(
geom = "col",
aesthetics = list(fill = "midnightblue", alpha = 0.8)
) +
ggplot2::scale_y_continuous(expand = c(0, 0))
Bagging model on test data
Finally, let’s turn to the testing data. For regression models, a
.pred
, column is added when parsnip::augment
is used.
test_results <- parsnip::augment(
x = bagging_fit,
new_data = Boston_test
)
test_results %>%
reactable::reactable(defaultPageSize = 5)
We check how well the .pred
column matches the
medv
using yardstick::rmse
.
test_results %>%
yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
reactable::reactable(defaultPageSize = 5)
Let us take a closer look at the predicted and actual response as a
scatter plot.
test_results %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["medv"]],
y = .data[[".pred"]]
)) +
ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
ggplot2::coord_fixed()
Random Forest
Here we apply random forest to the Boston
data, using
the randomForest
package in R
Create the rsample object
First, we split the samples into a training set and a test set. From
the training set, we create a 10-fold cross-validation data set from the
training set.
This is done with rsample::initial_split
,
rsample::training
and rsample::testing
Boston_split <- rsample::initial_split(Boston)
Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)
Create the preprocessor
We create a recipe with recipes::recipe
.
No other preprocessing step is done.
rf_recipe <-
recipes::recipe(formula = medv ~ ., data = Boston_train)
Specify the model
Use parsnip::rand_forest
,
parsnip::set_mode
and parsnip::set_engine
to create the model.
mtry
is the number of predictors that will be randomly
sampled at each split when creating the tree models. For random forest,
the randomForest::randomForest
use \(\frac{p}{3}\) variables when building a
random forest of regression trees and \(\sqrt{p}\) variables when building a random
forest of classification trees.
importance
set to TRUE ensures the importance of
predictors are assessed.
rf_spec <- parsnip::rand_forest() %>%
parsnip::set_engine("randomForest", importance = TRUE) %>%
parsnip::set_mode("regression")
rf_spec %>%
parsnip::translate()
> Random Forest Model Specification (regression)
>
> Engine-Specific Arguments:
> importance = TRUE
>
> Computational engine: randomForest
>
> Model fit template:
> randomForest::randomForest(x = missing_arg(), y = missing_arg(),
> importance = TRUE)
Create the workflow
workflows::workflow
,
workflows::add_recipe
and workflows::add_model
are used.
rf_workflow <- workflows::workflow() %>%
workflows::add_recipe(rf_recipe) %>%
workflows::add_model(rf_spec)
rf_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: rand_forest()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
> Random Forest Model Specification (regression)
>
> Engine-Specific Arguments:
> importance = TRUE
>
> Computational engine: randomForest
Random forest on training data
rf_fit <- parsnip::fit(
object = rf_workflow,
data = Boston_train
)
Variable Importance
What are the most important variables in this tree for predicting
medv
?
Using the vip
package and workflows::extract_fit_parsnip
,
we have
vip_table <- rf_fit %>%
workflows::extract_fit_parsnip() %>%
vip::vi()
vip_table %>%
reactable::reactable(defaultPageSize = 5)
rf_fit %>%
workflows::extract_fit_parsnip() %>%
vip::vip(
geom = "col",
aesthetics = list(fill = "midnightblue", alpha = 0.8)
) +
ggplot2::scale_y_continuous(expand = c(0, 0))
The randomForest
package also has functions like
randomForest::importance
to view the importance of each
variable as well. The key is to use workflows::extract_fit_engine
The first is based upon the mean decrease of accuracy in predictions
on the out of bag samples when a given variable is permuted.
The second is a measure of the total decrease in node impurity that
results from splits over that variable, averaged over all trees.
importance_table <- rf_fit %>%
workflows::extract_fit_engine() %>%
randomForest::importance()
importance_table %>%
reactable::reactable(defaultPageSize = 5)
Plots of these importance measures can be produced using the
randomForest::varImpPlot
function.
rf_fit %>%
workflows::extract_fit_engine() %>%
randomForest::varImpPlot()
Random forest on test data
Finally, let’s turn to the testing data. For regression models, a
.pred
, column is added when parsnip::augment
is used.
test_results <- parsnip::augment(
x = rf_fit,
new_data = Boston_test
)
test_results %>%
reactable::reactable(defaultPageSize = 5)
We check how well the .pred
column matches the
medv
using yardstick::rmse
.
test_results %>%
yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
reactable::reactable(defaultPageSize = 5)
Let us take a closer look at the predicted and actual response as a
scatter plot.
test_results %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["medv"]],
y = .data[[".pred"]]
)) +
ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
ggplot2::coord_fixed()
Boosting
Here we apply bagging to the Boston
data. The book uses
the R package gbm
to do the boosting. Unfortunately,
gbm
is not one of the list boosted tree models in the
current parsnip
list.
The process of building it from scratch can be found in this github issue
post
For simplicity, a different R package is used to create a bagging
tree model. In this example, the default parsnip engine for boosted tree
is the xgboost
R package.
Create the resample object
First, we split the samples into a training set and a test set. From
the training set, we create a 10-fold cross-validation data set from the
training set.
This is done with rsample::initial_split
,
rsample::training
,
rsample::testing
and rsample::vfold_cv
.
Boston_split <- rsample::initial_split(Boston)
Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)
Boston_fold <- rsample::vfold_cv(Boston_train, v = 10)
Create the preprocessor
We create a recipe with recipes::recipe
.
No other preprocessing step is done.
boost_recipe <-
recipes::recipe(formula = medv ~ ., data = Boston_train)
Specify the model
Use parsnip::boost_tree
,
parsnip::set_mode
and parsnip::set_engine
to create the model.
Recall in the book that we can tune the number of trees
trees
, the shrinkage parameter learn_rate
and
the number of splits/depth tree_depth
.
These can be tuned using tune::tune
boost_spec <- parsnip::boost_tree(
trees = tune::tune(),
tree_depth = tune::tune(),
learn_rate = tune::tune()
) %>%
parsnip::set_mode("regression") %>%
parsnip::set_engine("xgboost")
boost_spec %>%
parsnip::translate()
> Boosted Tree Model Specification (regression)
>
> Main Arguments:
> trees = tune::tune()
> tree_depth = tune::tune()
> learn_rate = tune::tune()
>
> Computational engine: xgboost
>
> Model fit template:
> parsnip::xgb_train(x = missing_arg(), y = missing_arg(), nrounds = tune::tune(),
> max_depth = tune::tune(), eta = tune::tune(), nthread = 1,
> verbose = 0)
Create the workflow
workflows::workflow
,
workflows::add_recipe
and workflows::add_model
are used.
boost_workflow <- workflows::workflow() %>%
workflows::add_recipe(boost_recipe) %>%
workflows::add_model(boost_spec)
boost_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: boost_tree()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
> Boosted Tree Model Specification (regression)
>
> Main Arguments:
> trees = tune::tune()
> tree_depth = tune::tune()
> learn_rate = tune::tune()
>
> Computational engine: xgboost
Create the boosting tree grid
Let’s use a space-filling design (non-regular grid) so that we can
cover the hyperparameter space as well as possible. We do this using dials::grid_latin_hypercube
We use the default values for dials::trees
,
dials::tree_depth
and dials::learn_rate
boost_grid <-
dials::grid_latin_hypercube(
dials::trees(range = c(1L, 2000L)),
dials::tree_depth(range = c(1L, 15L)),
dials::learn_rate(
range = c(-10, -1),
trans = scales::log10_trans()
),
size = 10
)
boost_grid %>%
reactable::reactable(defaultPageSize = 5)
You may refer to the Tidyverse
blog or Tengku
Hanis blog for more details about how the different grids work.
Boosting tree model fitting on cross validated data
Now we have everything we need and we can fit all the models on the
cross validated data with tune::tune_grid
.
Note that this process may take some time.
We use yardstick::metric_set
,
to choose a set of metrics to used to evaluate the model. In this
example, yardstick::rmse
and yardstick::rsq
are used.
doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
object = boost_workflow,
resamples = Boston_fold,
grid = boost_grid,
metrics = yardstick::metric_set(
yardstick::rmse,
yardstick::rsq
)
)
tune_res
> # Tuning results
> # 10-fold cross-validation
> # A tibble: 10 × 4
> splits id .metrics .notes
> <list> <chr> <list> <list>
> 1 <split [341/38]> Fold01 <tibble> <tibble [1 × 3]>
> 2 <split [341/38]> Fold02 <tibble> <tibble [1 × 3]>
> 3 <split [341/38]> Fold03 <tibble> <tibble [1 × 3]>
> 4 <split [341/38]> Fold04 <tibble> <tibble [1 × 3]>
> 5 <split [341/38]> Fold05 <tibble> <tibble [1 × 3]>
> 6 <split [341/38]> Fold06 <tibble> <tibble [1 × 3]>
> 7 <split [341/38]> Fold07 <tibble> <tibble [1 × 3]>
> 8 <split [341/38]> Fold08 <tibble> <tibble [1 × 3]>
> 9 <split [341/38]> Fold09 <tibble> <tibble [1 × 3]>
> 10 <split [342/37]> Fold10 <tibble> <tibble [1 × 3]>
>
> There were issues with some computations:
>
> - Warning(s) x10: A correlation computation is required, bu...
>
> Use `collect_notes(object)` for more information.
Here we see that the different amount of trees,
learning/shrinkage/rate and tree depth affects the performance metrics
differently using tune::autoplot
.
Do note that using a different seed will give a different plot
# Note that a different seed will give different plots
tune::autoplot(tune_res)
We can also see the raw metrics that created this chart by calling tune::collect_metrics()
.
tune::collect_metrics(tune_res) %>%
reactable::reactable(defaultPageSize = 5)
Here is the ggplot
way should tune::autoplot
fails
for_plotting <- tune_res %>%
tune::collect_metrics() %>%
dplyr::select(dplyr::all_of(c(
"mean", ".metric",
"trees", "tree_depth", "learn_rate"
))) %>%
tidyr::pivot_longer(
cols = dplyr::all_of(c("trees", "tree_depth", "learn_rate")),
values_to = "value",
names_to = "parameter"
)
for_plotting %>%
reactable::reactable(defaultPageSize = 5)
for_plotting %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["value"]],
y = .data[["mean"]],
colour = .data[["parameter"]]
)) +
ggplot2::geom_point(alpha = 0.8, show.legend = FALSE) +
ggplot2::facet_grid(
cols = ggplot2::vars(.data[["parameter"]]),
rows = ggplot2::vars(.data[[".metric"]]),
scales = "free"
) +
ggplot2::labs(x = NULL, y = NULL)
> Warning: Removed 3 rows containing missing values
> (geom_point).
Use tune::show_best
to see the top few values for a given metric.
The “best” values can be selected using tune::select_best
.
Do note that using a different seed will give a different optimised
value.
top_boost_grid <- tune::show_best(tune_res, metric = c("rmse"), n = 5)
top_boost_grid %>%
reactable::reactable(defaultPageSize = 5)
best_boost_grid <- tune::select_best(tune_res, metric = "rmse")
best_boost_grid
> # A tibble: 1 × 4
> trees tree_depth learn_rate .config
> <int> <int> <dbl> <chr>
> 1 492 5 0.00997 Preprocessor1_Model03
Boosting tree model with optimised grid
We create the boosting tree workflow with the best grid tune::finalize_workflow
.
boost_final <- tune::finalize_workflow(
x = boost_workflow,
parameters = best_boost_grid
)
boost_final
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: boost_tree()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
> Boosted Tree Model Specification (regression)
>
> Main Arguments:
> trees = 492
> tree_depth = 5
> learn_rate = 0.00996976561192888
>
> Computational engine: xgboost
We now train the boosting tree model with the training data using parsnip::fit
boost_final_fit <- parsnip::fit(object = boost_final, data = Boston_train)
We can see the tree in greater detail using tune::extract_fit_engine
xgb_model <- boost_final_fit %>%
tune::extract_fit_engine()
xgb_model
> ##### xgb.Booster
> raw: 893.9 Kb
> call:
> xgboost::xgb.train(params = list(eta = 0.00996976561192888, max_depth = 5L,
> gamma = 0, colsample_bytree = 1, colsample_bynode = 1, min_child_weight = 1,
> subsample = 1, objective = "reg:squarederror"), data = x$data,
> nrounds = 492L, watchlist = x$watchlist, verbose = 0, nthread = 1)
> params (as set within xgb.train):
> eta = "0.00996976561192888", max_depth = "5", gamma = "0", colsample_bytree = "1", colsample_bynode = "1", min_child_weight = "1", subsample = "1", objective = "reg:squarederror", nthread = "1", validate_parameters = "TRUE"
> xgb.attributes:
> niter
> callbacks:
> cb.evaluation.log()
> # of features: 12
> niter: 492
> nfeatures : 12
> evaluation_log:
> iter training_rmse
> 1 24.024099
> 2 23.796185
> ---
> 491 1.172467
> 492 1.170336
We can visualise one of the trees with
xgboost::xgb.plot.tree
# See the first tree
gr <- xgboost::xgb.plot.tree(
model = xgb_model, trees = 0,
render = FALSE
)
DiagrammeR::export_graph(
graph = gr,
file_name = "docs/xgboost_single_tree.png",
file_type = "PNG"
)
knitr::include_graphics("docs/xgboost_single_tree.png")
We can visualise a summary of all the trees with
xgboost::xgb.plot.multi.trees
gr <- xgboost::xgb.plot.multi.trees(
model = xgb_model,
features_keep = 3,
render = FALSE
)
DiagrammeR::export_graph(
graph = gr,
file_name = "docs/xgboost_multiple_tree.png",
file_type = "PNG"
)
knitr::include_graphics("docs/xgboost_multiple_tree.png")
Variable Importance
What are the most important variables in this tree for predicting
medv
?
We can extract the important features from the boosted tree model
with xgboost::xgb.importance
Details on what Gain
, Cover
and
Frequency
can be found in this blog
post
importance_matrix <- xgboost::xgb.importance(model = xgb_model)
importance_matrix %>%
reactable::reactable(defaultPageSize = 5)
The xgboost::xgb.ggplot.importance
uses the
Gain
variable importance measurement by default to
calculate variable importance.
xgboost::xgb.ggplot.importance(importance_matrix,
rel_to_first = FALSE,
xlab = "Relative importance"
)
Boosting tree model on test data
Finally, let’s turn to the testing data. For regression models, a
.pred
, column is added when parsnip::augment
is used.
test_results <- parsnip::augment(
x = boost_final_fit,
new_data = Boston_test
)
test_results %>%
reactable::reactable(defaultPageSize = 5)
We check how well the .pred
column matches the
medv
using yardstick::rmse
.
test_results %>%
yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
reactable::reactable(defaultPageSize = 5)
Let us take a closer look at the predicted and actual response as a
scatter plot.
test_results %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["medv"]],
y = .data[[".pred"]]
)) +
ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
ggplot2::coord_fixed()
Baysesian Additive Regression Tree (BART)
Here we apply bagging to the Boston
data. The book uses
the R package BART
to do the boosting. Unfortunately,
BART
is not one of the list bart models in the current parsnip
list.
For simplicity, a different R package is used to create a BART model.
In this example, the default parsnip engine for bosted tree is the
dbarts
R package.
Create the resample object
First, we split the samples into a training set and a test set. From
the training set, we create a 10-fold cross-validation data set from the
training set.
This is done with rsample::initial_split
,
rsample::training
,
rsample::testing
and rsample::vfold_cv
.
Boston_split <- rsample::initial_split(Boston)
Boston_train <- rsample::training(Boston_split)
Boston_test <- rsample::testing(Boston_split)
Boston_fold <- rsample::vfold_cv(Boston_train, v = 10)
Create the preprocessor
We create a recipe with recipes::recipe
.
No other preprocessing step is done.
bart_recipe <-
recipes::recipe(formula = medv ~ ., data = Boston_train)
Specify the model
Use parsnip::bart
,
parsnip::set_mode
and parsnip::set_engine
to create the model.
ndpost
is the number of MCMC interations.
nskip
is the number of burn-in iterations.
bart_spec <- parsnip::bart(
trees = tune::tune()
) %>%
parsnip::set_mode("regression") %>%
parsnip::set_engine("dbarts",
nskip = 100,
ndpost = 500
)
bart_spec %>%
parsnip::translate()
>
> Call:
> NULL
Create the workflow
workflows::workflow
,
workflows::add_recipe
and workflows::add_model
are used.
bart_workflow <- workflows::workflow() %>%
workflows::add_recipe(bart_recipe) %>%
workflows::add_model(bart_spec)
bart_workflow
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: bart()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
>
> Call:
> NULL
Create the BART grid
Let’s use a space-filling design (non-regular grid) so that we can
cover the hyperparameter space as well as possible. We do this using dials::grid_regular
We use the default values for dials::trees
,
dials::tree_depth
and dials::learn_rate
bart_grid <-
dials::grid_regular(
x = dials::trees(range = c(1L, 10L)),
levels = 10
)
bart_grid %>%
reactable::reactable(defaultPageSize = 5)
BART model fitting on cross validated data
Now we have everything we need and we can fit all the models on the
cross validated data with tune::tune_grid
.
Note that this process may take some time.
We use yardstick::metric_set
,
to choose a set of metrics to used to evaluate the model. In this
example, yardstick::rmse
and yardstick::rsq
are used.
doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
object = bart_workflow,
resamples = Boston_fold,
grid = bart_grid,
metrics = yardstick::metric_set(
yardstick::rmse,
yardstick::rsq
)
)
tune_res
> # Tuning results
> # 10-fold cross-validation
> # A tibble: 10 × 4
> splits id .metrics .notes
> <list> <chr> <list> <list>
> 1 <split [341/38]> Fold01 <tibble> <tibble [0 × 3]>
> 2 <split [341/38]> Fold02 <tibble> <tibble [0 × 3]>
> 3 <split [341/38]> Fold03 <tibble> <tibble [0 × 3]>
> 4 <split [341/38]> Fold04 <tibble> <tibble [0 × 3]>
> 5 <split [341/38]> Fold05 <tibble> <tibble [0 × 3]>
> 6 <split [341/38]> Fold06 <tibble> <tibble [0 × 3]>
> 7 <split [341/38]> Fold07 <tibble> <tibble [0 × 3]>
> 8 <split [341/38]> Fold08 <tibble> <tibble [0 × 3]>
> 9 <split [341/38]> Fold09 <tibble> <tibble [0 × 3]>
> 10 <split [342/37]> Fold10 <tibble> <tibble [0 × 3]>
Here we see that the different amount of trees affects the
performance metrics differently using tune::autoplot
.
Do note that using a different seed will give a different plot
# Note that a different seed will give different plots
tune::autoplot(tune_res)
We can also see the raw metrics that created this chart by calling tune::collect_metrics()
.
tune::collect_metrics(tune_res) %>%
reactable::reactable(defaultPageSize = 5)
Here is the ggplot
way should tune::autoplot
fails
tune_res %>%
tune::collect_metrics() %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["trees"]],
y = .data[["mean"]],
colour = .data[[".metric"]]
)) +
ggplot2::geom_errorbar(
mapping = ggplot2::aes(
ymin = .data[["mean"]] - .data[["std_err"]],
ymax = .data[["mean"]] + .data[["std_err"]]
),
alpha = 0.5
) +
ggplot2::geom_line(size = 1.5) +
ggplot2::facet_wrap(
facets = ggplot2::vars(.data[[".metric"]]),
scales = "free",
nrow = 2
) +
ggplot2::scale_x_log10() +
ggplot2::theme(legend.position = "none")
Use tune::show_best
to see the top few values for a given metric.
The “best” values can be selected using tune::select_best
.
Do note that using a different seed will give a different optimised
value.
top_number_of_trees <- tune::show_best(tune_res, metric = c("rmse"), n = 5)
top_number_of_trees %>%
reactable::reactable(defaultPageSize = 5)
best_number_of_trees <- tune::select_best(tune_res, metric = "rmse")
best_number_of_trees
> # A tibble: 1 × 2
> trees .config
> <int> <chr>
> 1 5 Preprocessor1_Model05
BART model with optimised grid
We create the BART workflow with the best grid tune::finalize_workflow
.
bart_final <- tune::finalize_workflow(
x = bart_workflow,
parameters = best_number_of_trees
)
bart_final
> ══ Workflow ════════════════════════════════════════
> Preprocessor: Recipe
> Model: bart()
>
> ── Preprocessor ────────────────────────────────────
> 0 Recipe Steps
>
> ── Model ───────────────────────────────────────────
>
> Call:
> NULL
We now train the boosting tree model with the training data using parsnip::fit
bart_final_fit <- parsnip::fit(object = bart_final, data = Boston_train)
We can see the tree in greater detail using tune::extract_fit_engine
For example, to see the 2nd tree on the third iteration
bart_model <- bart_final_fit %>%
tune::extract_fit_engine()
bart_model$fit$plotTree(sampleNum = 3, treeNum = 2)
Variable Importance
What are the most important variables in this tree for predicting
medv
?
We can extract the important features from the bart tree model with
embarcadero::varimp
embarcadero::varimp(bart_model, plots = TRUE) %>%
reactable::reactable(defaultPageSize = 5)
BART model on test data
Finally, let’s turn to the testing data. For regression models, a
.pred
, column is added when parsnip::augment
is used.
test_results <- parsnip::augment(
x = bart_final_fit,
new_data = Boston_test
)
test_results %>%
reactable::reactable(defaultPageSize = 5)
We check how well the .pred
column matches the
medv
using yardstick::rmse
.
test_results %>%
yardstick::rmse(truth = .data[["medv"]], estimate = .data[[".pred"]]) %>%
reactable::reactable(defaultPageSize = 5)
Let us take a closer look at the predicted and actual response as a
scatter plot.
test_results %>%
ggplot2::ggplot(mapping = ggplot2::aes(
x = .data[["medv"]],
y = .data[[".pred"]]
)) +
ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
ggplot2::coord_fixed()
Blog References
Emil Hvitfeldt’s ISLR
tidymodels Labs
Julia Silge’s blog titled
“Predict which #TidyTuesday Scooby Doo monsters are REAL with a tuned
decision tree model”
Julia Silge’s blog titled
“Tune and interpret decision trees for #TidyTuesday wind
turbines”
Julia Silge’s blog
titled “Tune XGBoost with tidymodels and #TidyTuesday beach
volleyball”
R-bloggers’ blog
titled “Explaining Predictions: Boosted Trees Post-hoc Analysis
(Xgboost)”
Package References
get_citation <- function(package_name) {
transform_name <- package_name %>%
citation() %>%
format(style = "text")
return(transform_name[1])
}
packages <- c(
"base", "randomForest", "vip", "ggplot2",
"rmarkdown", "report", "sp", "ROCR",
"dbarts", "tidyverse", "knitr", "ConfusionTableR",
"data.table", "DiagrammeR", "DiagrammeRsvg", "dials",
"dismo", "doParallel", "dplyr", "embarcadero",
"forcats", "foreach", "ggpubr", "htmltools",
"ISLR2", "iterators", "magrittr", "matrixStats",
"Metrics", "parsnip", "parttree", "patchwork",
"purrr", "raster", "reactable", "readr",
"recipes", "rpart", "rpart.plot", "rsample",
"rsvg", "scales", "stringr", "styler",
"tibble", "tidyr", "tune", "workflows",
"xaringanExtra", "xgboost", "yardstick",
"Ckmeans.1d.dp"
)
table <- tibble::tibble(Packages = packages)
table %>%
dplyr::mutate(
transform_name = purrr::map_chr(
.data[["Packages"]],
get_citation
)
) %>%
dplyr::pull(.data[["transform_name"]]) %>%
report::as.report_parameters()
- R Core Team (2022). R: A Language and Environment for
Statistical Computing. R Foundation for Statistical Computing,
Vienna, Austria. https://www.R-project.org/.
- Liaw A, Wiener M (2002). “Classification and Regression by
randomForest.” R News, 2(3), 18-22. https://CRAN.R-project.org/doc/Rnews/.
- Greenwell BM, Boehmke BC (2020). “Variable Importance Plots—An
Introduction to the vip Package.” The R Journal,
12(1), 343-366. https://doi.org/10.32614/RJ-2020-013.
- Wickham H (2016). ggplot2: Elegant Graphics for Data
Analysis. Springer-Verlag New York. ISBN 978-3-319-24277-4, https://ggplot2.tidyverse.org.
- Allaire J, Xie Y, McPherson J, Luraschi J, Ushey K, Atkins A,
Wickham H, Cheng J, Chang W, Iannone R (2022). rmarkdown: Dynamic
Documents for R. R package version 2.14, https://github.com/rstudio/rmarkdown.
- Makowski D, Ben-Shachar M, Patil I, Lüdecke D (2021). “Automated
Results Reporting as a Practical Tool to Improve Reproducibility and
Methodological Best Practices Adoption.” CRAN. https://github.com/easystats/report.
- Pebesma EJ, Bivand RS (2005). “Classes and methods for spatial data
in R.” R News, 5(2), 9-13. https://CRAN.R-project.org/doc/Rnews/.
- Sing T, Sander O, Beerenwinkel N, Lengauer T (2005). “ROCR:
visualizing classifier performance in R.” Bioinformatics,
21(20), 7881. http://rocr.bioinf.mpi-sb.mpg.de.
- Dorie V (2022). “dbarts: Discrete Bayesian Additive Regression Trees
Sampler.” R package version 0.9-22, https://CRAN.R-project.org/package=dbarts.
- Wickham H, Averick M, Bryan J, Chang W, McGowan LD, François R,
Grolemund G, Hayes A, Henry L, Hester J, Kuhn M, Pedersen TL, Miller E,
Bache SM, Müller K, Ooms J, Robinson D, Seidel DP, Spinu V, Takahashi K,
Vaughan D, Wilke C, Woo K, Yutani H (2019). “Welcome to the tidyverse.”
Journal of Open Source Software, 4(43), 1686. doi:10.21105/joss.01686
https://doi.org/10.21105/joss.01686.
- Xie Y (2022). knitr: A General-Purpose Package for Dynamic
Report Generation in R. R package version 1.39, https://yihui.org/knitr/.
- Hutson G (2021). ConfusionTableR: Confusion Matrix Toolset.
R package version 1.0.4, https://CRAN.R-project.org/package=ConfusionTableR.
- Dowle M, Srinivasan A (2021). data.table: Extension of
data.frame
. R package version 1.14.2, https://CRAN.R-project.org/package=data.table.
- Iannone R (2022). DiagrammeR: Graph/Network Visualization.
R package version 1.0.9, https://CRAN.R-project.org/package=DiagrammeR.
- Iannone R (2016). DiagrammeRsvg: Export DiagrammeR Graphviz
Graphs as SVG. R package version 0.1, https://CRAN.R-project.org/package=DiagrammeRsvg.
- Kuhn M, Frick H (2022). dials: Tools for Creating Tuning
Parameter Values. R package version 0.1.1, https://CRAN.R-project.org/package=dials.
- Hijmans RJ, Phillips S, Leathwick J, Elith J (2021). dismo:
Species Distribution Modeling. R package version 1.3-5, https://CRAN.R-project.org/package=dismo.
- Corporation M, Weston S (2022). doParallel: Foreach Parallel
Adaptor for the ‘parallel’ Package. R package version 1.0.17, https://CRAN.R-project.org/package=doParallel.
- Wickham H, François R, Henry L, Müller K (2022). dplyr: A
Grammar of Data Manipulation. R package version 1.0.9, https://CRAN.R-project.org/package=dplyr.
- Carlson C (2022). embarcadero: Species distribution models with
BART. R package version 1.2.0.1003.
- Wickham H (2021). forcats: Tools for Working with Categorical
Variables (Factors). R package version 0.5.1, https://CRAN.R-project.org/package=forcats.
- Microsoft, Weston S (2022). foreach: Provides Foreach Looping
Construct. R package version 1.5.2, https://CRAN.R-project.org/package=foreach.
- Kassambara A (2020). ggpubr: ‘ggplot2’ Based Publication Ready
Plots. R package version 0.4.0, https://CRAN.R-project.org/package=ggpubr.
- Cheng J, Sievert C, Schloerke B, Chang W, Xie Y, Allen J (2021).
htmltools: Tools for HTML. R package version 0.5.2, https://CRAN.R-project.org/package=htmltools.
- James G, Witten D, Hastie T, Tibshirani R (2022). ISLR2:
Introduction to Statistical Learning, Second Edition. R package
version 1.3-1, https://CRAN.R-project.org/package=ISLR2.
- Analytics R, Weston S (2022). iterators: Provides Iterator
Construct. R package version 1.0.14, https://CRAN.R-project.org/package=iterators.
- Bache S, Wickham H (2022). magrittr: A Forward-Pipe Operator for
R. R package version 2.0.3, https://CRAN.R-project.org/package=magrittr.
- Bengtsson H (2022). matrixStats: Functions that Apply to Rows
and Columns of Matrices (and to Vectors). R package version 0.62.0,
https://CRAN.R-project.org/package=matrixStats.
- Hamner B, Frasco M (2018). Metrics: Evaluation Metrics for
Machine Learning. R package version 0.1.4, https://CRAN.R-project.org/package=Metrics.
- Kuhn M, Vaughan D (2022). parsnip: A Common API to Modeling and
Analysis Functions. R package version 0.2.1, https://CRAN.R-project.org/package=parsnip.
- McDermott G (2022). parttree: Visualise simple decision tree
partitions. https://github.com/grantmcdermott/parttree, http://grantmcdermott.com/parttree.
- Pedersen T (2020). patchwork: The Composer of Plots. R
package version 1.1.1, https://CRAN.R-project.org/package=patchwork.
- Henry L, Wickham H (2020). purrr: Functional Programming
Tools. R package version 0.3.4, https://CRAN.R-project.org/package=purrr.
- Hijmans R (2022). raster: Geographic Data Analysis and
Modeling. R package version 3.5-15, https://CRAN.R-project.org/package=raster.
- Lin G (2020). reactable: Interactive Data Tables Based on ‘React
Table’. R package version 0.2.3, https://CRAN.R-project.org/package=reactable.
- Wickham H, Hester J, Bryan J (2022). readr: Read Rectangular
Text Data. R package version 2.1.2, https://CRAN.R-project.org/package=readr.
- Kuhn M, Wickham H (2022). recipes: Preprocessing and Feature
Engineering Steps for Modeling. R package version 0.2.0, https://CRAN.R-project.org/package=recipes.
- Therneau T, Atkinson B (2022). rpart: Recursive Partitioning and
Regression Trees. R package version 4.1.16, https://CRAN.R-project.org/package=rpart.
- Milborrow S (2021). rpart.plot: Plot ‘rpart’ Models: An Enhanced
Version of ‘plot.rpart’. R package version 3.1.0, https://CRAN.R-project.org/package=rpart.plot.
- Silge J, Chow F, Kuhn M, Wickham H (2021). rsample: General
Resampling Infrastructure. R package version 0.1.1, https://CRAN.R-project.org/package=rsample.
- Ooms J (2022). rsvg: Render SVG Images into PDF, PNG,
(Encapsulated) PostScript, or Bitmap Arrays. R package version
2.3.1, https://CRAN.R-project.org/package=rsvg.
- Wickham H, Seidel D (2022). scales: Scale Functions for
Visualization. R package version 1.2.0, https://CRAN.R-project.org/package=scales.
- Wickham H (2019). stringr: Simple, Consistent Wrappers for
Common String Operations. R package version 1.4.0, https://CRAN.R-project.org/package=stringr.
- Müller K, Walthert L (2022). styler: Non-Invasive Pretty
Printing of R Code. R package version 1.7.0, https://CRAN.R-project.org/package=styler.
- Müller K, Wickham H (2021). tibble: Simple Data Frames. R
package version 3.1.6, https://CRAN.R-project.org/package=tibble.
- Wickham H, Girlich M (2022). tidyr: Tidy Messy Data. R
package version 1.2.0, https://CRAN.R-project.org/package=tidyr.
- Kuhn M (2022). tune: Tidy Tuning Tools. R package version
0.2.0, https://CRAN.R-project.org/package=tune.
- Vaughan D (2022). workflows: Modeling Workflows. R package
version 0.2.6, https://CRAN.R-project.org/package=workflows.
- Aden-Buie G, Warkentin M (2022). xaringanExtra: Extras And
Extensions for Xaringan Slides. R package version 0.5.5, https://github.com/gadenbuie/xaringanExtra.
- Chen T, He T, Benesty M, Khotilovich V, Tang Y, Cho H, Chen K,
Mitchell R, Cano I, Zhou T, Li M, Xie J, Lin M, Geng Y, Li Y, Yuan J
(2022). xgboost: Extreme Gradient Boosting. R package version
1.6.0.1, https://CRAN.R-project.org/package=xgboost.
- Kuhn M, Vaughan D (2021). yardstick: Tidy Characterizations of
Model Performance. R package version 0.0.9, https://CRAN.R-project.org/package=yardstick.
- Wang H, Song M (2011). “Ckmeans.1d.dp: Optimal \(k\)-means clustering in one dimension by
dynamic programming.” The R Journal, 3(2), 29-33. doi:10.32614/RJ-2011-015
https://doi.org/10.32614/RJ-2011-015.
LS0tCnRpdGxlOiAnKipDaGFwdGVyIDggTGFiKionCmF1dGhvcjogIkplcmVteSBTZWx2YSIKc3VidGl0bGU6IFRoaXMgZG9jdW1lbnQgd2FzIHByZXBhcmVkIG9uIGByIGZvcm1hdChTeXMuRGF0ZSgpKWAuCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IGNlcnVsZWFuCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgc2VsZl9jb250YWluZWQ6IGZhbHNlCiAgICBsaWJfZGlyOiAiZG9jcy9ybWFya2Rvd25fbGlicyIKICBybWFya2Rvd246Omh0bWxfdmlnbmV0dGU6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAyCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCjwhLS0gCiEhISEgSU1QT1JUQU5UOiBydW4gYHNvdXJjZSgidXRpbHMvcmVuZGVyLlIiKWAgdG8gcHVibGlzaCBpbnN0ZWFkIG9mIGNsaWNraW5nIG9uICdLbml0JwojIFNlZSBodHRwczovL3lpaHVpLm9yZy9rbml0ci9vcHRpb25zLwotLT4KCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPVRSVUUsIGluY2x1ZGU9RkFMU0V9CiMgU2V0IHVwIHRoZSBlbnZpcm9ubWVudAoKIyBPcHRpb25zIHJlbGF0aXZlIHRvIGZpZ3VyZSBzaXplCiMgMS42MTggaXMgdGhlIGdvbGRlbiByYXRpbwpmaWdoZWlnaHQgPC0gNApmaWd3aWR0aCA8LSA0ICogMS42MTggCgojIEdlbmVyYWwgb3B0aW9ucwpvcHRpb25zKGtuaXRyLmthYmxlLk5BID0gIiIsCiAgICAgICAgbnNtYWxsID0gMywKICAgICAgICB0aWR5dmVyc2UucXVpZXQgPSBUUlVFCiAgICAgICAgKQpob29rX291dHB1dCA8LSBrbml0cjo6a25pdF9ob29rcyRnZXQoJ291dHB1dCcpCgprbml0cjo6a25pdF9ob29rcyRzZXQoCiAgb3V0cHV0ID0gZnVuY3Rpb24oeCwgb3B0aW9ucykgewogICAgaWYgKCFpcy5udWxsKG9wdGlvbnMkbWF4LmhlaWdodCkpIHsKICAgICAgb3B0aW9ucyRhdHRyLm91dHB1dCA8LSBjKG9wdGlvbnMkYXR0ci5vdXRwdXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCdzdHlsZT0ibWF4LWhlaWdodDogJXM7IicsIG9wdGlvbnMkbWF4LmhlaWdodCkpCiAgICB9CiAgICBob29rX291dHB1dCh4LCBvcHRpb25zKQogICAgfQogICkKCiMgQ2h1bmsgb3B0aW9ucyAoc2VlIGh0dHBzOi8veWlodWkub3JnL2tuaXRyL29wdGlvbnMvI2NodW5rX29wdGlvbnMpCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjb21tZW50ID0gIj4iLCAgIyBUaGUgcHJlZml4IHRvIGJlIGFkZGVkIGJlZm9yZSBlYWNoIGxpbmUgb2YgdGhlIHRleHQgb3V0cHV0LgogIGRwaSA9IDYwMCwKICBmaWcucGF0aCA9ICJkb2NzL2ZpZ3VyZXMvIiwKICBmaWcuaGVpZ2h0ID0gZmlnaGVpZ2h0LAogIGZpZy53aWR0aCA9IGZpZ3dpZHRoLAogIGZpZy5hbGlnbiA9ICJjZW50ZXIiLAogICMgU2VlIGh0dHBzOi8vY29tbXVuaXR5LnJzdHVkaW8uY29tL3QvY2VudGVyaW5nLWltYWdlcy1pbi1ibG9nZG93bi1wb3N0LzIwOTYyCiAgIyB0byBsZWFybiBob3cgdG8gY2VudGVyIGltYWdlcwogICMgU2VlIGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi1jb29rYm9vay9vcHRzLXRpZHkuaHRtbAogICMgU2VlIGh0dHBzOi8vd3d3LnpvdGVyby5vcmcvc3R5bGVzIGZvciBjaXRhdGlvbiBzdHlsZSByZXNwb3NpdG9yeQogIHRpZHk9J3N0eWxlcicsCiAgdGlkeS5vcHRzPWxpc3Qoc3RyaWN0PVRSVUUpCikKCmh0bWx0b29sczo6dGFnTGlzdCgKICB4YXJpbmdhbkV4dHJhOjp1c2VfY2xpcGJvYXJkKAogICAgYnV0dG9uX3RleHQgPSAiPGkgY2xhc3M9XCJmYSBmYS1jbGlwYm9hcmRcIj48L2k+IENvcHkgQ29kZSIsCiAgICBzdWNjZXNzX3RleHQgPSAiPGkgY2xhc3M9XCJmYSBmYS1jaGVja1wiIHN0eWxlPVwiY29sb3I6ICM5MEJFNkRcIj48L2k+IENvcGllZCEiLAogICksCiAgcm1hcmtkb3duOjpodG1sX2RlcGVuZGVuY3lfZm9udF9hd2Vzb21lKCkKKQpgYGAKCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2FzaXMnLCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30KIyBSbWFya2Rvd24gc3R1ZmYKbGlicmFyeShybWFya2Rvd24sIHF1aWV0bHk9VFJVRSkKbGlicmFyeShrbml0ciwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KGh0bWx0b29scywgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KHN0eWxlciwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KHhhcmluZ2FuRXh0cmEsIHF1aWV0bHk9VFJVRSkKCiMgRGF0YXNldApsaWJyYXJ5KElTTFIyLCBxdWlldGx5PVRSVUUpCgojIFNlc3Npb24gaW5mbyBhbmQgcGFja2FnZSByZXBvcnRpbmcKbGlicmFyeShyZXBvcnQsIHF1aWV0bHk9VFJVRSkKCiMgRGF0YSB3cmFuZ2xpbmcKCmxpYnJhcnkoZHBseXIsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShtYWdyaXR0ciwgcXVpZXRseT1UUlVFKQoKIyBDcmVhdGUgaW50ZXJhY3RpdmUgdGFibGVzCmxpYnJhcnkocmVhY3RhYmxlLCBxdWlldGx5PVRSVUUpCgojIEZvciBwbG90dGluZwoKbGlicmFyeShnZ3Bsb3QyLCBxdWlldGx5PVRSVUUpCiMgRnJvbSBnaXRodWIKbGlicmFyeShwYXJ0dHJlZSwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KENvbmZ1c2lvblRhYmxlUiwgcXVpZXRseSA9IFRSVUUpCgojIEZvciBEZWNpc2lvbiBDbGFzc2lmaWNhdGlvbiBUcmVlcwpsaWJyYXJ5KHJwYXJ0LCBxdWlldGx5PVRSVUUpCmxpYnJhcnkocnBhcnQucGxvdCwgcXVpZXRseT1UUlVFKQoKIyBGb3IgQmFnZ2luZyBhbmQgUmFuZG9tIEZvcmVzdApsaWJyYXJ5KHJhbmRvbUZvcmVzdCwgcXVpZXRseT1UUlVFKQoKIyBGb3IgQm9vc3RpbmcKbGlicmFyeShDa21lYW5zLjFkLmRwLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkoeGdib29zdCwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KERpYWdyYW1tZVIsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShEaWFncmFtbWVSc3ZnLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkocnN2ZywgcXVpZXRseT1UUlVFKQoKIyBGb3IgQkFSVApsaWJyYXJ5KGRiYXJ0cywgcXVpZXRseT1UUlVFKQojIEZyb20gZ2l0aHViCmxpYnJhcnkoZW1iYXJjYWRlcm8sIHF1aWV0bHk9VFJVRSkKCiMgRm9yIHZhcmlhYmxlIGltcG9ydGFuY2UKbGlicmFyeSh2aXAsIHF1aWV0bHk9VFJVRSkKCiMgRm9yIHBhcmFsbGVsIHByb2Nlc3NpbmcgZm9yIHR1bmluZwpsaWJyYXJ5KGZvcmVhY2gsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShkb1BhcmFsbGVsLCBxdWlldGx5PVRSVUUpCgojIEZvciBhcHBseWluZyB0aWR5bW9kZWxzCgpsaWJyYXJ5KHJzYW1wbGUsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShyZWNpcGVzLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkocGFyc25pcCwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KGRpYWxzLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkodHVuZSwgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KHdvcmtmbG93cywgcXVpZXRseT1UUlVFKQpsaWJyYXJ5KHlhcmRzdGljaywgcXVpZXRseT1UUlVFKQoKc3VtbWFyeShyZXBvcnQ6OnJlcG9ydChzZXNzaW9uSW5mbygpKSkKYGBgCgojIEZpdHRpbmcgQ2xhc3NpZmljYXRpb24gVHJlZXMKCmBgYHtyLCBlY2hvPUZBTFNFfQpzZXQuc2VlZCgxMjM0KQpgYGAKCkluIHRoZSBbYENhcnNlYXRzYF0oaHR0cHM6Ly9yZHJyLmlvL2NyYW4vSVNMUi9tYW4vQ2Fyc2VhdHMuaHRtbCkgZGF0YSBzZXQgZnJvbSB0aGUgYElTTFIyYCBwYWNrYWdlLCBgU2FsZXNgIG9mIGNoaWxkIGNhciBzZWF0cyBpcyBhIGNvbnRpbnVvdXMgdmFyaWFibGUsIGFuZCBzbyB3ZSBiZWdpbiBieSByZWNvcmRpbmcgaXQgYXMgYSBiaW5hcnkgdmFyaWFibGUgY2FsbGVkIGBIaWdoYCwgd2hpY2ggdGFrZXMgb24gYSB2YWx1ZSBvZiBZZXMgaWYgdGhlIFNhbGVzIHZhcmlhYmxlIGV4Y2VlZHMgOCwgYW5kIHRha2VzIG9uIGEgdmFsdWUgb2YgTm8gb3RoZXJ3aXNlLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpDYXJzZWF0cyA8LSBkcGx5cjo6YXNfdGliYmxlKElTTFIyOjpDYXJzZWF0cykgJT4lCiAgZHBseXI6Om11dGF0ZSgKICAgIEhpZ2ggPSBmYWN0b3IoZHBseXI6OmlmX2Vsc2UoLmRhdGFbWyJTYWxlcyJdXSA8PSA4LCAiTm8iLCAiWWVzIikpCiAgICApICU+JQogIGRwbHlyOjpzZWxlY3QoLWRwbHlyOjphbGxfb2YoYygiU2FsZXMiKSkpCgpDYXJzZWF0cyAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1LAogICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcmFibGUgPSBUUlVFKQpgYGAKCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0CgpGaXJzdCwgd2Ugc3BsaXQgdGhlIHNhbXBsZXMgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0IHNldC4gRnJvbSB0aGUgdHJhaW5pbmcgc2V0LCB3ZSBjcmVhdGUgYSAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gZGF0YSBzZXQgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LgoKVGhpcyBpcyBkb25lIHdpdGggW2Byc2FtcGxlOjppbml0aWFsX3NwbGl0YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRyYWluaW5nYF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkKYW5kCltgcnNhbXBsZTo6dmZvbGRfY3ZgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Zmb2xkX2N2Lmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpDYXJzZWF0c19zcGxpdCA8LSByc2FtcGxlOjppbml0aWFsX3NwbGl0KENhcnNlYXRzKQoKQ2Fyc2VhdHNfdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQ2Fyc2VhdHNfc3BsaXQpCkNhcnNlYXRzX3Rlc3QgPC0gcnNhbXBsZTo6dGVzdGluZyhDYXJzZWF0c19zcGxpdCkKCkNhcnNlYXRzX2ZvbGQgPC0gcnNhbXBsZTo6dmZvbGRfY3YoQ2Fyc2VhdHNfdHJhaW4sIHYgPSAxMCkKCgpgYGAKCiMjIENyZWF0ZSB0aGUgcHJlcHJvY2Vzc29yCgpXZSBjcmVhdGUgYSByZWNpcGUgd2l0aCBbYHJlY2lwZXM6OnJlY2lwZWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcmVjaXBlLmh0bWwpLgpObyBvdGhlciBwcmVwcm9jZXNzaW5nIHN0ZXAgaXMgZG9uZS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKY2xhc3NfdHJlZV9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBIaWdoIH4gLiwgZGF0YSA9IENhcnNlYXRzX3RyYWluKQoKYGBgCgojIyBTcGVjaWZ5IHRoZSBtb2RlbAoKVXNlCltgcGFyc25pcDo6ZGVjaXNpb25fdHJlZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZGVjaXNpb25fdHJlZS5odG1sKSwKW2BwYXJzbmlwOjpzZXRfbW9kZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2FyZ3MuaHRtbCkKYW5kCltgcGFyc25pcDo6c2V0X2VuZ2luZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2VuZ2luZS5odG1sKQp0byBjcmVhdGUgdGhlIG1vZGVsLgoKVGhlIGNvc3QgY29tcGxleGl0eSBpcyB0dW5lZCB1c2luZwpbYHR1bmU6OnR1bmVgXShodHRwczovL2hhcmRoYXQudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmUuaHRtbCkKCkFuIGVuZ2luZSBzcGVjaWZpYyBwYXJhbWV0ZXIgYG1vZGVsID0gVFJVRWAgaXMgc2V0IGZvciBgcnBhcnRgIHRvIHByZXZlbnQgdGhpcyB3YXJuaW5nIG1lc3NhZ2UgZnJvbSBjb21pbmcgdXAgbGF0ZXIgd2hlbiBgcnBhcnQucGxvdDo6cnBhcnQucGxvdGAgaXMgdXNlZCBsYXRlci4KCmBgYHtyfQojPiBXYXJuaW5nOiBDYW5ub3QgcmV0cmlldmUgdGhlIGRhdGEgdXNlZCB0byBidWlsZCB0aGUgbW9kZWwgKHNvIGNhbm5vdCBkZXRlcm1pbmUgcm91bmRpbnQgYW5kIGlzLmJpbmFyeSBmb3IgdGhlIHZhcmlhYmxlcykuCiM+IFRvIHNpbGVuY2UgdGhpcyB3YXJuaW5nOgojPiAgICAgQ2FsbCBycGFydC5wbG90IHdpdGggcm91bmRpbnQ9RkFMU0UsCiM+ICAgICBvciByZWJ1aWxkIHRoZSBycGFydCBtb2RlbCB3aXRoIG1vZGVsPVRSVUUuCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpjbGFzc190cmVlX3NwZWMgPC0gcGFyc25pcDo6ZGVjaXNpb25fdHJlZSgKICB0cmVlX2RlcHRoID0gNCwKICBjb3N0X2NvbXBsZXhpdHkgPSB0dW5lOjp0dW5lKCkpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpICU+JSAKICBwYXJzbmlwOjpzZXRfZW5naW5lKCJycGFydCIsIG1vZGVsID0gVFJVRSkKCmNsYXNzX3RyZWVfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKY2xhc3NfdHJlZV93b3JrZmxvdyA8LSAgd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUoY2xhc3NfdHJlZV9yZWNpcGUpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9tb2RlbChjbGFzc190cmVlX3NwZWMpCgpjbGFzc190cmVlX3dvcmtmbG93IApgYGAKCiMjIENyZWF0ZSB0aGUgY29zdCBjb21wbGV4aXR5IGdyaWQKCkEgY29zdCBjb21wbGV4aXR5IGdyaWQgb2YgJDEwJCBudW1iZXJzIGZyb20gJDAuMDAxJCAoJDEwXnstM30kKSB0bwokMTAkICgkMTBeMSQpIGlzIGNyZWF0ZWQuCgpSZWd1bGFyIGdyaWQgaXMgY3JlYXRlZCB1c2luZwpbYGRpYWxzOjpncmlkX3JlZ3VsYXJgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ncmlkX3JlZ3VsYXIuaHRtbCksCltgZGlhbHM6OmNvc3RfY29tcGxleGl0eWBdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3RyZWVzLmh0bWwpCmFuZApbYHNjYWxlczo6bG9nMTBfdHJhbnNgXShodHRwczovL3NjYWxlcy5yLWxpYi5vcmcvcmVmZXJlbmNlL2xvZ190cmFucy5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgojIENyZWF0ZSBhIHJhbmdlIGZyb20gMTAKY29zdF9jb21wbGV4aXR5X2dyaWQgPC0gCiAgZGlhbHM6OmdyaWRfcmVndWxhcigKICAgIHggPSBkaWFsczo6Y29zdF9jb21wbGV4aXR5KHJhbmdlID0gYygtMywgMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFucyA9IHNjYWxlczo6bG9nMTBfdHJhbnMoKSksCiAgICBsZXZlbHMgPSAxMAogICkKCmNvc3RfY29tcGxleGl0eV9ncmlkICAlPiUgCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgojIyBDbGFzc2lmaWNhdGlvbiB0cmVlIG1vZGVsIGZpdHRpbmcgb24gY3Jvc3MgdmFsaWRhdGVkIGRhdGEKCk5vdyB3ZSBoYXZlIGV2ZXJ5dGhpbmcgd2UgbmVlZCBhbmQgd2UgY2FuIGZpdCBhbGwgdGhlIG1vZGVscyBvbiB0aGUKY3Jvc3MgdmFsaWRhdGVkIGRhdGEgd2l0aApbYHR1bmU6OnR1bmVfZ3JpZGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZV9ncmlkLmh0bWwpLgpOb3RlIHRoYXQgdGhpcyBwcm9jZXNzIG1heSB0YWtlIHNvbWUgdGltZS4KCldlIHVzZSBbYHlhcmRzdGljazo6bWV0cmljX3NldGBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9tZXRyaWNfc2V0Lmh0bWwpLCB0byBjaG9vc2UgYSBzZXQgb2YgbWV0cmljcyB0byB1c2VkIHRvIGV2YWx1YXRlIHRoZSBtb2RlbC4gSW4gdGhpcyBleGFtcGxlLCBbYHlhcmRzdGljazo6YWNjdXJhY3lgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWNjdXJhY3kuaHRtbCksCltgeWFyZHN0aWNrOjpyb2NfYXVjYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3JvY19jdXJ2ZS5odG1sKSwKW2B5YXJkc3RpY2s6OnNlbnNpdGl2aXR5YF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NlbnMuaHRtbCkgYW5kIFtgeWFyZHN0aWNrOjpzcGVjaWZpY2l0eWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zcGVjLmh0bWwpIGFyZSB1c2VkLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbCgpCmZvcmVhY2g6OmdldERvUGFyV29ya2VycygpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KdHVuZV9yZXMgPC0gdHVuZTo6dHVuZV9ncmlkKAogIG9iamVjdCA9IGNsYXNzX3RyZWVfd29ya2Zsb3csCiAgcmVzYW1wbGVzID0gQ2Fyc2VhdHNfZm9sZCwgCiAgZ3JpZCA9IGNvc3RfY29tcGxleGl0eV9ncmlkLAogIG1ldHJpY3MgPSB5YXJkc3RpY2s6Om1ldHJpY19zZXQoeWFyZHN0aWNrOjphY2N1cmFjeSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlhcmRzdGljazo6cm9jX2F1YywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlhcmRzdGljazo6c2Vuc2l0aXZpdHksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWFyZHN0aWNrOjpzcGVjaWZpY2l0eSkKKQoKdHVuZV9yZXMKYGBgCgpIZXJlIHdlIHNlZSB0aGF0IHRoZSBhbW91bnQgb2YgY29zdCBjb21wbGV4aXR5IGFmZmVjdHMgdGhlIHBlcmZvcm1hbmNlCm1ldHJpY3MgZGlmZmVyZW50bHkgdXNpbmcKW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpLgpEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYSBkaWZmZXJlbnQgcGxvdAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CiMgTm90ZSB0aGF0IGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGRpZmZlcmVudCBwbG90cwp0dW5lOjphdXRvcGxvdCh0dW5lX3JlcykKYGBgCgpXZSBjYW4gYWxzbyBzZWUgdGhlIHJhdyBtZXRyaWNzIHRoYXQgY3JlYXRlZCB0aGlzIGNoYXJ0IGJ5IGNhbGxpbmcKW2B0dW5lOjpjb2xsZWN0X21ldHJpY3MoKWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9Cgp0dW5lOjpjb2xsZWN0X21ldHJpY3ModHVuZV9yZXMpICU+JSAKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKCkhlcmUgaXMgdGhlIGBnZ3Bsb3RgIHdheSBzaG91bGQKW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpCmZhaWxzCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdHVuZV9yZXMgJT4lCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1siY29zdF9jb21wbGV4aXR5Il1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIm1lYW4iXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gLmRhdGFbWyIubWV0cmljIl1dKSkgKwogIGdncGxvdDI6Omdlb21fZXJyb3JiYXIobWFwcGluZyA9IGdncGxvdDI6OmFlcyh5bWluID0gLmRhdGFbWyJtZWFuIl1dIC0gLmRhdGFbWyJzdGRfZXJyIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5bWF4ID0gLmRhdGFbWyJtZWFuIl1dICsgLmRhdGFbWyJzdGRfZXJyIl1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC41KSArCiAgZ2dwbG90Mjo6Z2VvbV9saW5lKHNpemUgPSAxLjUpICsKICBnZ3Bsb3QyOjpmYWNldF93cmFwKGZhY2V0cyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyIubWV0cmljIl1dKSwgCiAgICAgICAgICAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIsIAogICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IDIpICsKICBnZ3Bsb3QyOjpzY2FsZV94X2xvZzEwKCkgKwogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpVc2UKW2B0dW5lOjpzaG93X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKQp0byBzZWUgdGhlIHRvcCBmZXcgdmFsdWVzIGZvciBhIGdpdmVuIG1ldHJpYy4KClRoZSAiYmVzdCIgdmFsdWVzIGNhbiBiZSBzZWxlY3RlZCB1c2luZwpbYHR1bmU6OnNlbGVjdF9iZXN0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCksCnRoaXMgZnVuY3Rpb24gcmVxdWlyZXMgeW91IHRvIHNwZWNpZnkgYSBtZXRyaWMgdGhhdCBpdCBzaG91bGQgc2VsZWN0CmFnYWluc3QuIFRoZSBjb3N0IGNvbXBsZXhpdHkgdmFsdWUgaXMgMC4wMDEgZm9yIG1ldHJpYyBgYWNjdXJhY3lgIHNpbmNlIGl0CmdpdmVzIHRoZSBoaWdoZXN0IHZhbHVlLiBEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYQpkaWZmZXJlbnQgYmVzdCBjb3N0IGNvbXBsZXhpdHkgdmFsdWUuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdG9wX2Nvc3RfY29tcGxleGl0eSA8LSB0dW5lOjpzaG93X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9IGMoImFjY3VyYWN5IiksIG4gPSA1KQp0b3BfY29zdF9jb21wbGV4aXR5ICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCgpiZXN0X2Nvc3RfY29tcGxleGl0eSA8LSB0dW5lOjpzZWxlY3RfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gImFjY3VyYWN5IikKYmVzdF9jb3N0X2NvbXBsZXhpdHkKCmBgYAoKIyMgQ2xhc3NpZmljYXRpb24gdHJlZSBtb2RlbCB3aXRoIG9wdGltaXNlZCBjb3N0IGNvbXBsZXhpdHkgdmFsdWUKCldlIGNyZWF0ZSB0aGUgY2xhc3NpZmljYXRpb24gdHJlZSB3b3JrZmxvdyB3aXRoIHRoZSBiZXN0IGNvc3QgY29tcGxleGl0eSBzY29yZQp1c2luZwpbYHR1bmU6OmZpbmFsaXplX3dvcmtmbG93YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9maW5hbGl6ZV9tb2RlbC5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CgpjbGFzc190cmVlX2ZpbmFsIDwtIHR1bmU6OmZpbmFsaXplX3dvcmtmbG93KAogIHggPSBjbGFzc190cmVlX3dvcmtmbG93LCAKICBwYXJhbWV0ZXJzID0gYmVzdF9jb3N0X2NvbXBsZXhpdHkpCgpjbGFzc190cmVlX2ZpbmFsCmBgYAoKV2Ugbm93IHRyYWluIHRoZSBjbGFzc2lmaWNhdGlvbiB0cmVlIG1vZGVsIHdpdGggdGhlIHRyYWluaW5nIGRhdGEgdXNpbmcgW2BwYXJzbmlwOjpmaXRgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2ZpdC5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpjbGFzc190cmVlX2ZpbmFsX2ZpdCA8LSBwYXJzbmlwOjpmaXQob2JqZWN0ID0gY2xhc3NfdHJlZV9maW5hbCwgZGF0YSA9IENhcnNlYXRzX3RyYWluKQpgYGAKCldlIGNhbiBzZWUgdGhlIHRyZWUgaW4gZ3JlYXRlciBkZXRhaWwgdXNpbmcKW2B0dW5lOjpleHRyYWN0X2ZpdF9lbmdpbmVgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2V4dHJhY3QtdHVuZS5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KY2xhc3NfdHJlZV9maW5hbF9maXQgJT4lCiAgdHVuZTo6ZXh0cmFjdF9maXRfZW5naW5lKCkKYGBgCgpXZSBjYW4gdmlzdWFsaXNlIHRoZSBhYm92ZSBiZXR0ZXIgd2l0aCBgcnBhcnQucGxvdDo6cnBhcnQucGxvdGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CmNsYXNzX3RyZWVfZmluYWxfZml0ICU+JQogIHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZSgpICU+JQogIHJwYXJ0LnBsb3Q6OnJwYXJ0LnBsb3QoKQpgYGAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCldoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgaW4gdGhpcyB0cmVlIGZvciBwcmVkaWN0aW5nIGBTYWxlc2A/CgpVc2luZyB0aGUgYHZpcGAgcGFja2FnZSBhbmQKW2B3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXBgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZXh0cmFjdC13b3JrZmxvdy5odG1sKSwgd2UgaGF2ZQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp2aXBfdGFibGUgPC0gY2xhc3NfdHJlZV9maW5hbF9maXQgJT4lCiAgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lCiAgdmlwOjp2aSgpIAoKdmlwX3RhYmxlICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmNsYXNzX3RyZWVfZmluYWxfZml0ICU+JQogIHdvcmtmbG93czo6ZXh0cmFjdF9maXRfcGFyc25pcCgpICU+JQogIHZpcDo6dmlwKGdlb20gPSAiY29sIiwgCiAgICAgICAgICAgYWVzdGhldGljcyA9IGxpc3QoZmlsbCA9ICJtaWRuaWdodGJsdWUiLCBhbHBoYSA9IDAuOCkpICsKICBnZ3Bsb3QyOjpzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkKYGBgCgpXZSBjYW4gdXNlIFtgcGFydHRyZWVgXShodHRwczovL2dpdGh1Yi5jb20vZ3JhbnRtY2Rlcm1vdHQvcGFydHRyZWUpIHRvIHVuZGVyc3RhbmQgd2h5IHRoZXNlIHR3byBwYXJhbWV0ZXJzIHdvcmsgc28gd2VsbCBvbiB0aGUgdHJhaW5pbmcgc2V0LgoKSG93ZXZlciB0aGlzIHBhY2thZ2Ugb25seSB3b3JrcyB3aXRoIHR3byBjb250aW51b3VzIHByZWRpY3RvcnMuIEFzIGBTaGVsdmVMb2NgIGlzIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUsIHRoZXJlIGlzIGEgbmVlZCB0byAiY29udmVydCIgdGhlbSB0byBjb250aW51b3VzIGJ5IG1ha2luZyAiQmFkIiBhcyAtMSwgIk1lZGl1bSIgYXMgMCBhbmQgIkdvb2QiIGFzIDEuIAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpDYXJzZWF0c190cmFpbl9tb2RmaWZpZWQgPC0gQ2Fyc2VhdHNfdHJhaW4gJT4lIAogIGRwbHlyOjptdXRhdGUoCiAgICBTaGVsdmVMb2MgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgICAuZGF0YVtbIlNoZWx2ZUxvYyJdXSA9PSAiQmFkIiB+ICItMSIsCiAgICAgIC5kYXRhW1siU2hlbHZlTG9jIl1dID09ICJNZWRpdW0iIH4gIjAiLAogICAgICAuZGF0YVtbIlNoZWx2ZUxvYyJdXSA9PSJHb29kIiB+ICIxIgogICAgKSkgJT4lIAogIGRwbHlyOjptdXRhdGUoCiAgICBTaGVsdmVMb2MgPSBhcy5udW1lcmljKC5kYXRhW1siU2hlbHZlTG9jIl1dKQogICkKCnBhcnRpYWxfdHJlZSA8LSBwYXJzbmlwOjpkZWNpc2lvbl90cmVlKAogIHRyZWVfZGVwdGggPSAzMCwKICBjb3N0X2NvbXBsZXhpdHkgPSBiZXN0X2Nvc3RfY29tcGxleGl0eSRjb3N0X2NvbXBsZXhpdHkpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpICU+JSAKICBwYXJzbmlwOjpzZXRfZW5naW5lKCJycGFydCIsIG1vZGVsID0gVFJVRSkgJT4lCiAgcGFyc25pcDo6Zml0KEhpZ2ggfiBQcmljZSArIFNoZWx2ZUxvYywgCiAgICAgICAgICAgICAgIGRhdGEgPSBDYXJzZWF0c190cmFpbl9tb2RmaWZpZWQpICU+JQogIHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZSgpCgpDYXJzZWF0c190cmFpbl9tb2RmaWZpZWQgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KAogICAgbWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJTaGVsdmVMb2MiXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIlByaWNlIl1dKSkgKwogIHBhcnR0cmVlOjpnZW9tX3BhcnR0cmVlKAogICAgZGF0YSA9IHBhcnRpYWxfdHJlZSwgCiAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKGZpbGwgPSAuZGF0YVtbIkhpZ2giXV0pLCAKICAgIGFscGhhID0gMC4yKSArCiAgZ2dwbG90Mjo6Z2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjcsIAogICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMC4wNSwgCiAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0ID0gMC4yLCAKICAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKGNvbG9yID0gLmRhdGFbWyJIaWdoIl1dKSkKYGBgCgojIyBDbGFzc2lmaWNhdGlvbiB0cmVlIG1vZGVsIG9uIHRlc3QgZGF0YQoKRmluYWxseSwgbGV04oCZcyB0dXJuIHRvIHRoZSB0ZXN0aW5nIGRhdGEuCkZvciBjbGFzc2lmaWNhdGlvbiBtb2RlbHMsIGEKYC5wcmVkX2NsYXNzYCwgY29sdW1uIGFuZCBjbGFzcyBwcm9iYWJpbGl0eSBjb2x1bW5zIG5hbWVkIC5wcmVkX3tsZXZlbH0gYXJlIGFkZGVkIHdoZW4KW2BwYXJzbmlwOjphdWdtZW50YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdWdtZW50Lmh0bWwpCmlzIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnRlc3RfcmVzdWx0cyA8LSBwYXJzbmlwOjphdWdtZW50KHggPSBjbGFzc190cmVlX2ZpbmFsX2ZpdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19kYXRhID0gQ2Fyc2VhdHNfdGVzdCkKICAKdGVzdF9yZXN1bHRzICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCgpgYGAKCldlIGNhbiB2aWV3IHRoZSBjb25mdXNpb24gbWF0cml4IHVzaW5nIFtgeWFyZHN0aWNrOjpjb25mX21hdGBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb25mX21hdC5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgJT4lCiAgeWFyZHN0aWNrOjpjb25mX21hdCh0cnV0aCA9IC5kYXRhW1siSGlnaCJdXSwgCiAgICAgICAgICAgICAgICAgICAgICBlc3RpbWF0ZSA9IC5kYXRhW1siLnByZWRfY2xhc3MiXV0pICU+JQogIGdncGxvdDI6OmF1dG9wbG90KHR5cGUgPSAiaGVhdG1hcCIpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgJT4lCiAgeWFyZHN0aWNrOjpjb25mX21hdCh0cnV0aCA9IC5kYXRhW1siSGlnaCJdXSwgZXN0aW1hdGUgPSAuZGF0YVtbIi5wcmVkX2NsYXNzIl1dKSAlPiUKICBzdW1tYXJ5KCkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpvciBieSBgQ29uZnVzaW9uVGFibGVSOjpiaW5hcnlfdmlzdWFsaXNlUmAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBmaWcuaGVpZ2h0ID0gOH0KQ29uZnVzaW9uVGFibGVSOjpiaW5hcnlfdmlzdWFsaXNlUih0cmFpbl9sYWJlbHMgPSB0ZXN0X3Jlc3VsdHNbWyJIaWdoIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydXRoX2xhYmVscyA9IHRlc3RfcmVzdWx0c1tbIi5wcmVkX2NsYXNzIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzX2xhYmVsMSA9ICJZZXMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzc19sYWJlbDIgPSAiTm8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YWRyYW50X2NvbDEgPSAiIzI4QUNCNCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YWRyYW50X2NvbDIgPSAiIzQzOTdEMiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1c3RvbV90aXRsZSA9ICJIaWdoIENvbmZ1c2lvbiBNYXRyaXgiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0X2NvbD0gImJsYWNrIikKCgpgYGAKCldlIGNhbiB2aWV3IHRoZSBST0MgY3VydmUgdXNpbmcgW2B5YXJkc3RpY2s6OnJvY19jdXJ2ZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yb2NfY3VydmUuaHRtbCkgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnJvY19wbG90X2RhdGEgPC0gdGVzdF9yZXN1bHRzICU+JQogIHlhcmRzdGljazo6cm9jX2N1cnZlKHRydXRoID0gdGVzdF9yZXN1bHRzW1siSGlnaCJdXSwKICAgICAgICAgICAgICAgICAgICAgICAuZGF0YVtbIi5wcmVkX05vIl1dKQoKcm9jX3Bsb3RfZGF0YSAlPiUgCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnJvY19wbG90X2RhdGEgJT4lIAogIGdncGxvdDI6OmF1dG9wbG90KCkKCmBgYAoKSGVyZSBpcyBhIGBnZ3Bsb3QyYCB2ZXJzaW9uLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgpyb2NfcGxvdF9kYXRhICU+JSAKICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gMSAtIC5kYXRhW1sic3BlY2lmaWNpdHkiXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbInNlbnNpdGl2aXR5Il1dKSkgKwogIGdncGxvdDI6Omdlb21fbGluZShzaXplID0gMS41LCBjb2xvciA9ICJtaWRuaWdodGJsdWUiKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoCiAgICBsdHkgPSAyLCBhbHBoYSA9IDAuNSwKICAgIGNvbG9yID0gImdyYXk1MCIsCiAgICBzaXplID0gMS4yCiAgKSArCiAgZ2dwbG90Mjo6Y29vcmRfZXF1YWwoKQoKYGBgCgoKVG8gdmlldyB0aGUgbWV0cmljcywgd2UgY2FuIGFsc28gdXNlCltgdHVuZTo6bGFzdF9maXRgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2xhc3RfZml0Lmh0bWwpCmFuZApbYHR1bmU6OmNvbGxlY3RfbWV0cmljc2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4KCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCm1ldHJpY3NfcmVzdWx0cyA8LSB0dW5lOjpsYXN0X2ZpdChvYmplY3QgPSBjbGFzc190cmVlX2ZpbmFsX2ZpdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9IENhcnNlYXRzX3NwbGl0KSAlPiUKICB0dW5lOjpjb2xsZWN0X21ldHJpY3MoKQogIAptZXRyaWNzX3Jlc3VsdHMgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKICAKYGBgCgojIEZpdHRpbmcgUmVncmVzc2lvbiBUcmVlcwoKYGBge3IsIGVjaG89RkFMU0V9CnNldC5zZWVkKDEyMzQpCmBgYAoKSW4gdGhlIFtgQm9zdG9uYF0oaHR0cHM6Ly9yZHJyLmlvL2NyYW4vSVNMUjIvbWFuL0Jvc3Rvbi5odG1sKSBkYXRhIHNldCBmcm9tIHRoZSBgSVNMUjJgIHBhY2thZ2UsIHdlIHdhbnQgdG8gcHJlZGljdCBgbWVkdmAsIHRoZSBtZWRpYW4gdmFsdWUgb2Ygb3duZXItb2NjdXBpZWQgaG9tZXMgaW4gJDEwMDAncy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKQm9zdG9uIDwtIGRwbHlyOjphc190aWJibGUoSVNMUjI6OkJvc3RvbikKCkJvc3RvbiAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1LAogICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcmFibGUgPSBUUlVFKQpgYGAKCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0CgpGaXJzdCwgd2Ugc3BsaXQgdGhlIHNhbXBsZXMgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0IHNldC4gRnJvbSB0aGUgdHJhaW5pbmcgc2V0LCB3ZSBjcmVhdGUgYSAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gZGF0YSBzZXQgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LgoKVGhpcyBpcyBkb25lIHdpdGggW2Byc2FtcGxlOjppbml0aWFsX3NwbGl0YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRyYWluaW5nYF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkKYW5kCltgcnNhbXBsZTo6dmZvbGRfY3ZgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Zmb2xkX2N2Lmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpCb3N0b25fc3BsaXQgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChCb3N0b24pCgpCb3N0b25fdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQm9zdG9uX3NwbGl0KQpCb3N0b25fdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEJvc3Rvbl9zcGxpdCkKCkJvc3Rvbl9mb2xkIDwtIHJzYW1wbGU6OnZmb2xkX2N2KEJvc3Rvbl90cmFpbiwgdiA9IDEwKQoKCmBgYAoKIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3IKCldlIGNyZWF0ZSBhIHJlY2lwZSB3aXRoIFtgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkuCk5vIG90aGVyIHByZXByb2Nlc3Npbmcgc3RlcCBpcyBkb25lLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpyZWdfdHJlZV9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBtZWR2IH4gLiwgZGF0YSA9IEJvc3Rvbl90cmFpbikKCmBgYAoKIyMgU3BlY2lmeSB0aGUgbW9kZWwKClVzZQpbYHBhcnNuaXA6OmRlY2lzaW9uX3RyZWVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2RlY2lzaW9uX3RyZWUuaHRtbCksCltgcGFyc25pcDo6c2V0X21vZGVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9hcmdzLmh0bWwpCmFuZApbYHBhcnNuaXA6OnNldF9lbmdpbmVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9lbmdpbmUuaHRtbCkKdG8gY3JlYXRlIHRoZSBtb2RlbC4KClRoZSBjb3N0IGNvbXBsZXhpdHkgaXMgdHVuZWQgdXNpbmcKW2B0dW5lOjp0dW5lYF0oaHR0cHM6Ly9oYXJkaGF0LnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90dW5lLmh0bWwpCgpBbiBlbmdpbmUgc3BlY2lmaWMgcGFyYW1ldGVyIGBtb2RlbCA9IFRSVUVgIGlzIHNldCBmb3IgYHJwYXJ0YCB0byBwcmV2ZW50IHRoaXMgd2FybmluZyBtZXNzYWdlIGZyb20gY29taW5nIHVwIGxhdGVyIHdoZW4gYHJwYXJ0LnBsb3Q6OnJwYXJ0LnBsb3RgIGlzIHVzZWQgbGF0ZXIuCgpgYGB7cn0KIz4gV2FybmluZzogQ2Fubm90IHJldHJpZXZlIHRoZSBkYXRhIHVzZWQgdG8gYnVpbGQgdGhlIG1vZGVsIChzbyBjYW5ub3QgZGV0ZXJtaW5lIHJvdW5kaW50IGFuZCBpcy5iaW5hcnkgZm9yIHRoZSB2YXJpYWJsZXMpLgojPiBUbyBzaWxlbmNlIHRoaXMgd2FybmluZzoKIz4gICAgIENhbGwgcnBhcnQucGxvdCB3aXRoIHJvdW5kaW50PUZBTFNFLAojPiAgICAgb3IgcmVidWlsZCB0aGUgcnBhcnQgbW9kZWwgd2l0aCBtb2RlbD1UUlVFLgpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcmVnX3RyZWVfc3BlYyA8LSBwYXJzbmlwOjpkZWNpc2lvbl90cmVlKAogICN0cmVlX2RlcHRoID0gNCwKICBjb3N0X2NvbXBsZXhpdHkgPSB0dW5lOjp0dW5lKCkpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lIAogIHBhcnNuaXA6OnNldF9lbmdpbmUoInJwYXJ0IiwgbW9kZWwgPSBUUlVFKQoKcmVnX3RyZWVfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKcmVnX3RyZWVfd29ya2Zsb3cgPC0gIHdvcmtmbG93czo6d29ya2Zsb3coKSAlPiUgCiAgd29ya2Zsb3dzOjphZGRfcmVjaXBlKHJlZ190cmVlX3JlY2lwZSkgJT4lIAogIHdvcmtmbG93czo6YWRkX21vZGVsKHJlZ190cmVlX3NwZWMpCgpyZWdfdHJlZV93b3JrZmxvdyAKYGBgCgojIyBDcmVhdGUgdGhlIGNvc3QgY29tcGxleGl0eSBncmlkCgpBIGNvc3QgY29tcGxleGl0eSBncmlkIG9mICQxMCQgbnVtYmVycyBmcm9tICQwLjAwMSQgKCQxMF57LTR9JCkgdG8KJDEwJCAoJDEwXjEkKSBpcyBjcmVhdGVkLgoKUmVndWxhciBncmlkIGlzIGNyZWF0ZWQgdXNpbmcKW2BkaWFsczo6Z3JpZF9yZWd1bGFyYF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZ3JpZF9yZWd1bGFyLmh0bWwpLApbYGRpYWxzOjpjb3N0X2NvbXBsZXhpdHlgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90cmVlcy5odG1sKQphbmQKW2BzY2FsZXM6OmxvZzEwX3RyYW5zYF0oaHR0cHM6Ly9zY2FsZXMuci1saWIub3JnL3JlZmVyZW5jZS9sb2dfdHJhbnMuaHRtbCkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKIyBDcmVhdGUgYSByYW5nZSBmcm9tIDEwCmNvc3RfY29tcGxleGl0eV9ncmlkIDwtIAogIGRpYWxzOjpncmlkX3JlZ3VsYXIoCiAgICB4ID0gZGlhbHM6OmNvc3RfY29tcGxleGl0eShyYW5nZSA9IGMoLTQsIDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnMgPSBzY2FsZXM6OmxvZzEwX3RyYW5zKCkpLAogICAgbGV2ZWxzID0gMTAKICApCgpjb3N0X2NvbXBsZXhpdHlfZ3JpZCAgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKIyMgUmVncmVzc2lvbiB0cmVlIG1vZGVsIGZpdHRpbmcgb24gY3Jvc3MgdmFsaWRhdGVkIGRhdGEKCk5vdyB3ZSBoYXZlIGV2ZXJ5dGhpbmcgd2UgbmVlZCBhbmQgd2UgY2FuIGZpdCBhbGwgdGhlIG1vZGVscyBvbiB0aGUKY3Jvc3MgdmFsaWRhdGVkIGRhdGEgd2l0aApbYHR1bmU6OnR1bmVfZ3JpZGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZV9ncmlkLmh0bWwpLgpOb3RlIHRoYXQgdGhpcyBwcm9jZXNzIG1heSB0YWtlIHNvbWUgdGltZS4KCldlIHVzZSBbYHlhcmRzdGljazo6bWV0cmljX3NldGBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9tZXRyaWNfc2V0Lmh0bWwpLCB0byBjaG9vc2UgYSBzZXQgb2YgbWV0cmljcyB0byB1c2VkIHRvIGV2YWx1YXRlIHRoZSBtb2RlbC4gSW4gdGhpcyBleGFtcGxlLCBbYHlhcmRzdGljazo6cm1zZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ybXNlLmh0bWwpIGFuZApbYHlhcmRzdGljazo6cnNxYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3JzcS5odG1sKSBhcmUgdXNlZC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpkb1BhcmFsbGVsOjpyZWdpc3RlckRvUGFyYWxsZWwoKQpmb3JlYWNoOjpnZXREb1BhcldvcmtlcnMoKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CnR1bmVfcmVzIDwtIHR1bmU6OnR1bmVfZ3JpZCgKICBvYmplY3QgPSByZWdfdHJlZV93b3JrZmxvdywKICByZXNhbXBsZXMgPSBCb3N0b25fZm9sZCwgCiAgZ3JpZCA9IGNvc3RfY29tcGxleGl0eV9ncmlkLAogICNtZXRyaWNzID0geWFyZHN0aWNrOjptZXRyaWNfc2V0KHlhcmRzdGljazo6cm1zZSwKICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5YXJkc3RpY2s6OnJzcSkKKQoKdHVuZV9yZXMKYGBgCgpIZXJlIHdlIHNlZSB0aGF0IHRoZSBhbW91bnQgb2YgY29zdCBjb21wbGV4aXR5IGFmZmVjdHMgdGhlIHBlcmZvcm1hbmNlCm1ldHJpY3MgZGlmZmVyZW50bHkgdXNpbmcKW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpLgpEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYSBkaWZmZXJlbnQgcGxvdAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CiMgTm90ZSB0aGF0IGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGRpZmZlcmVudCBwbG90cwp0dW5lOjphdXRvcGxvdCh0dW5lX3JlcykKYGBgCgpXZSBjYW4gYWxzbyBzZWUgdGhlIHJhdyBtZXRyaWNzIHRoYXQgY3JlYXRlZCB0aGlzIGNoYXJ0IGJ5IGNhbGxpbmcKW2B0dW5lOjpjb2xsZWN0X21ldHJpY3MoKWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9Cgp0dW5lOjpjb2xsZWN0X21ldHJpY3ModHVuZV9yZXMpICU+JSAKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKCkhlcmUgaXMgdGhlIGBnZ3Bsb3RgIHdheSBzaG91bGQKW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpCmZhaWxzCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdHVuZV9yZXMgJT4lCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1siY29zdF9jb21wbGV4aXR5Il1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIm1lYW4iXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gLmRhdGFbWyIubWV0cmljIl1dKSkgKwogIGdncGxvdDI6Omdlb21fZXJyb3JiYXIobWFwcGluZyA9IGdncGxvdDI6OmFlcyh5bWluID0gLmRhdGFbWyJtZWFuIl1dIC0gLmRhdGFbWyJzdGRfZXJyIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5bWF4ID0gLmRhdGFbWyJtZWFuIl1dICsgLmRhdGFbWyJzdGRfZXJyIl1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC41KSArCiAgZ2dwbG90Mjo6Z2VvbV9saW5lKHNpemUgPSAxLjUpICsKICBnZ3Bsb3QyOjpmYWNldF93cmFwKGZhY2V0cyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyIubWV0cmljIl1dKSwgCiAgICAgICAgICAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIsIAogICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IDIpICsKICBnZ3Bsb3QyOjpzY2FsZV94X2xvZzEwKCkgKwogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpVc2UKW2B0dW5lOjpzaG93X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKQp0byBzZWUgdGhlIHRvcCBmZXcgdmFsdWVzIGZvciBhIGdpdmVuIG1ldHJpYy4KClRoZSAiYmVzdCIgdmFsdWVzIGNhbiBiZSBzZWxlY3RlZCB1c2luZwpbYHR1bmU6OnNlbGVjdF9iZXN0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCksCnRoaXMgZnVuY3Rpb24gcmVxdWlyZXMgeW91IHRvIHNwZWNpZnkgYSBtZXRyaWMgdGhhdCBpdCBzaG91bGQgc2VsZWN0CmFnYWluc3QuIFRoZSBjb3N0IGNvbXBsZXhpdHkgdmFsdWUgaXMgMC4wMDEyOSBmb3IgbWV0cmljIGBybXNlYCBzaW5jZSBpdApnaXZlcyB0aGUgbG93ZXN0IHZhbHVlLiBEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYQpkaWZmZXJlbnQgYmVzdCBjb3N0IGNvbXBsZXhpdHkgdmFsdWUuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdG9wX2Nvc3RfY29tcGxleGl0eSA8LSB0dW5lOjpzaG93X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9IGMoInJtc2UiKSwgbiA9IDUpCnRvcF9jb3N0X2NvbXBsZXhpdHkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKCmJlc3RfY29zdF9jb21wbGV4aXR5IDwtIHR1bmU6OnNlbGVjdF9iZXN0KHR1bmVfcmVzLCBtZXRyaWMgPSAicm1zZSIpCmJlc3RfY29zdF9jb21wbGV4aXR5CgpgYGAKCiMjIFJlZ3Jlc3Npb24gdHJlZSBtb2RlbCB3aXRoIG9wdGltaXNlZCBjb3N0IGNvbXBsZXhpdHkgdmFsdWUKCldlIGNyZWF0ZSB0aGUgcmVncmVzc2lvbiB0cmVlIHdvcmtmbG93IHdpdGggdGhlIGJlc3QgY29zdCBjb21wbGV4aXR5IHNjb3JlCnVzaW5nCltgdHVuZTo6ZmluYWxpemVfd29ya2Zsb3dgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2ZpbmFsaXplX21vZGVsLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KCnJlZ190cmVlX2ZpbmFsIDwtIHR1bmU6OmZpbmFsaXplX3dvcmtmbG93KAogIHggPSByZWdfdHJlZV93b3JrZmxvdywgCiAgcGFyYW1ldGVycyA9IGJlc3RfY29zdF9jb21wbGV4aXR5KQoKcmVnX3RyZWVfZmluYWwKYGBgCgpXZSBub3cgdHJhaW4gdGhlIHJlZ3Jlc3Npb24gdHJlZSBtb2RlbCB3aXRoIHRoZSB0cmFpbmluZyBkYXRhIHVzaW5nIFtgcGFyc25pcDo6Zml0YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9maXQuaHRtbCkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcmVnX3RyZWVfZmluYWxfZml0IDwtIHBhcnNuaXA6OmZpdChvYmplY3QgPSByZWdfdHJlZV9maW5hbCwgZGF0YSA9IEJvc3Rvbl90cmFpbikKYGBgCgpXZSBjYW4gc2VlIHRoZSB0cmVlIGluIGdyZWF0ZXIgZGV0YWlsIHVzaW5nCltgdHVuZTo6ZXh0cmFjdF9maXRfZW5naW5lYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXR1bmUuaHRtbCkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CnJlZ190cmVlX2ZpbmFsX2ZpdCAlPiUKICB0dW5lOjpleHRyYWN0X2ZpdF9lbmdpbmUoKQpgYGAKCldlIGNhbiB2aXN1YWxpc2UgdGhlIGFib3ZlIGJldHRlciB3aXRoIGBycGFydC5wbG90OjpycGFydC5wbG90YAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KcmVnX3RyZWVfZmluYWxfZml0ICU+JQogIHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZSgpICU+JQogIHJwYXJ0LnBsb3Q6OnJwYXJ0LnBsb3QoKQpgYGAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCldoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgaW4gdGhpcyB0cmVlIGZvciBwcmVkaWN0aW5nIGBtZWR2YD8KClVzaW5nIHRoZSBgdmlwYCBwYWNrYWdlIGFuZApbYHdvcmtmbG93czo6ZXh0cmFjdF9maXRfcGFyc25pcGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXdvcmtmbG93Lmh0bWwpLCB3ZSBoYXZlCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnZpcF90YWJsZSA8LSByZWdfdHJlZV9maW5hbF9maXQgJT4lCiAgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lCiAgdmlwOjp2aSgpIAoKdmlwX3RhYmxlICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnJlZ190cmVlX2ZpbmFsX2ZpdCAlPiUKICB3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUKICB2aXA6OnZpcChnZW9tID0gImNvbCIsIAogICAgICAgICAgIGFlc3RoZXRpY3MgPSBsaXN0KGZpbGwgPSAibWlkbmlnaHRibHVlIiwgYWxwaGEgPSAwLjgpKSArCiAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpCmBgYAoKV2UgY2FuIHVzZSBbYHBhcnR0cmVlYF0oaHR0cHM6Ly9naXRodWIuY29tL2dyYW50bWNkZXJtb3R0L3BhcnR0cmVlKSB0byB1bmRlcnN0YW5kIHdoeSB0aGVzZSB0d28gcGFyYW1ldGVycyB3b3JrIHNvIHdlbGwgb24gdGhlIHRyYWluaW5nIHNldC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcGFydGlhbF90cmVlIDwtIHBhcnNuaXA6OmRlY2lzaW9uX3RyZWUoCiAgY29zdF9jb21wbGV4aXR5ID0gYmVzdF9jb3N0X2NvbXBsZXhpdHkkY29zdF9jb21wbGV4aXR5KSAlPiUKICBwYXJzbmlwOjpzZXRfbW9kZSgicmVncmVzc2lvbiIpICU+JSAKICBwYXJzbmlwOjpzZXRfZW5naW5lKCJycGFydCIsIG1vZGVsID0gVFJVRSkgJT4lCiAgcGFyc25pcDo6Zml0KG1lZHYgfiBybSArIGxzdGF0LCAKICAgICAgICAgICAgICAgZGF0YSA9IEJvc3Rvbl90cmFpbikgJT4lCiAgdHVuZTo6ZXh0cmFjdF9maXRfZW5naW5lKCkKCkJvc3Rvbl90cmFpbiAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QoCiAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbInJtIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyJsc3RhdCJdXSkpICsKICBwYXJ0dHJlZTo6Z2VvbV9wYXJ0dHJlZSgKICAgIGRhdGEgPSBwYXJ0aWFsX3RyZWUsIAogICAgbWFwcGluZyA9IGdncGxvdDI6OmFlcyhmaWxsID0gLmRhdGFbWyJtZWR2Il1dKSwgCiAgICBhbHBoYSA9IDAuMikgKwogIGdncGxvdDI6Omdlb21faml0dGVyKGFscGhhID0gMC43LAogICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMC4wNSwKICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSAwLjIsCiAgICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGdncGxvdDI6OmFlcyhjb2xvciA9IC5kYXRhW1sibWVkdiJdXSkpICsKICBnZ3Bsb3QyOjpzY2FsZV9jb2xvdXJfdmlyaWRpc19jKGFlc3RoZXRpY3MgPSBjKCJjb2xvciIsICJmaWxsIikpCmBgYAoKIyMgUmVncmVzc2lvbiB0cmVlIG1vZGVsIG9uIHRlc3QgZGF0YQoKRmluYWxseSwgbGV04oCZcyB0dXJuIHRvIHRoZSB0ZXN0aW5nIGRhdGEuCkZvciByZWdyZXNzaW9uIG1vZGVscywgYQpgLnByZWRgLCBjb2x1bW4gaXMgYWRkZWQgd2hlbgpbYHBhcnNuaXA6OmF1Z21lbnRgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2F1Z21lbnQuaHRtbCkKaXMgdXNlZC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKdGVzdF9yZXN1bHRzIDwtIHBhcnNuaXA6OmF1Z21lbnQoeCA9IHJlZ190cmVlX2ZpbmFsX2ZpdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19kYXRhID0gQm9zdG9uX3Rlc3QpCiAgCnRlc3RfcmVzdWx0cyAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQoKYGBgCgpXZSBjaGVjayBob3cgd2VsbCB0aGUgYC5wcmVkYCBjb2x1bW4gbWF0Y2hlcyB0aGUgYG1lZHZgIHVzaW5nCltgeWFyZHN0aWNrOjpybXNlYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Jtc2UuaHRtbCkuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KdGVzdF9yZXN1bHRzICU+JQogIHlhcmRzdGljazo6cm1zZSh0cnV0aCA9IC5kYXRhW1sibWVkdiJdXSwgZXN0aW1hdGUgPSAuZGF0YVtbIi5wcmVkIl1dKSAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKCkFsdGVybmF0aXZlbHksIHdlIGNhbiB1c2UKW2B0dW5lOjpsYXN0X2ZpdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbGFzdF9maXQuaHRtbCkKYW5kCltgdHVuZTo6Y29sbGVjdF9tZXRyaWNzYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3JzIDwtIHR1bmU6Omxhc3RfZml0KG9iamVjdCA9IHJlZ190cmVlX2ZpbmFsX2ZpdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXQgPSBCb3N0b25fc3BsaXQpCiAgCnRlc3RfcnMgJT4lCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKICAKYGBgCgpVc2UgW2B0dW5lOjpjb2xsZWN0X3ByZWRpY3Rpb25zYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLCB0byBzZWUgb25seSB0aGUgYWN0dWFsIGFuZCBwcmVkaWN0ZWQgdmFsdWVzIG9mIHRoZSB0ZXN0IGRhdGEuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KdGVzdF9ycyAlPiUKICB0dW5lOjpjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3JzICU+JQogIHR1bmU6OmNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJtZWR2Il1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIi5wcmVkIl1dCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICApICsKICBnZ3Bsb3QyOjpnZW9tX2FibGluZShzbG9wZSA9IDEsIGx0eSA9IDIsIGNvbG9yID0gImdyYXk1MCIsIGFscGhhID0gMC41KSArCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludChhbHBoYSA9IDAuNiwgY29sb3IgPSAibWlkbmlnaHRibHVlIikgKwogIGdncGxvdDI6OmNvb3JkX2ZpeGVkKCkKYGBgCgojIEJhZ2dpbmcKCkhlcmUgd2UgYXBwbHkgYmFnZ2luZyB0byB0aGUgYEJvc3RvbmAgZGF0YSwgdXNpbmcgdGhlIGByYW5kb21Gb3Jlc3RgIHBhY2thZ2UgaW4gUgoKYGBge3IsIGVjaG89RkFMU0V9CnNldC5zZWVkKDEpCmBgYAoKIyMgQ3JlYXRlIHRoZSByZXNhbXBsZSBvYmplY3QKCkZpcnN0LCB3ZSBzcGxpdCB0aGUgc2FtcGxlcyBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0LiBGcm9tIHRoZSB0cmFpbmluZyBzZXQsIHdlIGNyZWF0ZSBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBkYXRhIHNldCBmcm9tIHRoZSB0cmFpbmluZyBzZXQuCgpUaGlzIGlzIGRvbmUgd2l0aCBbYHJzYW1wbGU6OmluaXRpYWxfc3BsaXRgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCksCltgcnNhbXBsZTo6dHJhaW5pbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkgYW5kCltgcnNhbXBsZTo6dGVzdGluZ2BdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpCb3N0b25fc3BsaXQgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChCb3N0b24pCgpCb3N0b25fdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQm9zdG9uX3NwbGl0KQpCb3N0b25fdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEJvc3Rvbl9zcGxpdCkKCmBgYAoKIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3IKCldlIGNyZWF0ZSBhIHJlY2lwZSB3aXRoIFtgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkuCk5vIG90aGVyIHByZXByb2Nlc3Npbmcgc3RlcCBpcyBkb25lLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpiYWdnaW5nX3JlY2lwZSA8LSAKICByZWNpcGVzOjpyZWNpcGUoZm9ybXVsYSA9IG1lZHYgfiAuLCBkYXRhID0gQm9zdG9uX3RyYWluKQoKYGBgCgojIyBTcGVjaWZ5IHRoZSBtb2RlbAoKVXNlCltgcGFyc25pcDo6cmFuZF9mb3Jlc3RgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3JhbmRfZm9yZXN0Lmh0bWwpLApbYHBhcnNuaXA6OnNldF9tb2RlYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zZXRfYXJncy5odG1sKQphbmQKW2BwYXJzbmlwOjpzZXRfZW5naW5lYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zZXRfZW5naW5lLmh0bWwpCnRvIGNyZWF0ZSB0aGUgbW9kZWwuCgpgbXRyeWAgaXMgdGhlIG51bWJlciBvZiBwcmVkaWN0b3JzIHRoYXQgd2lsbCBiZSByYW5kb21seSBzYW1wbGVkIGF0IGVhY2ggc3BsaXQgd2hlbiBjcmVhdGluZyB0aGUgdHJlZSBtb2RlbHMuIEZvciBiYWdnaW5nLCB0aGF0IG51bWJlciBpcyB0aGUgbnVtYmVyIG9mIGNvbHVtbnMgaW4gdGhlIHByZWRpY3RvciBtYXRyaXggZGVub3RlZCBieSBbYHBhcnNuaXA6Oi5jb2xzYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9kZXNjcmlwdG9ycy5odG1sKQoKYGltcG9ydGFuY2VgIHNldCB0byBUUlVFIGVuc3VyZXMgdGhlIGltcG9ydGFuY2Ugb2YgcHJlZGljdG9ycyBhcmUgYXNzZXNzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJhZ2dpbmdfc3BlYyA8LSBwYXJzbmlwOjpyYW5kX2ZvcmVzdChtdHJ5ID0gLmNvbHMoKSkgJT4lCiAgcGFyc25pcDo6c2V0X2VuZ2luZSgicmFuZG9tRm9yZXN0IiwgaW1wb3J0YW5jZSA9IFRSVUUpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikKCmJhZ2dpbmdfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKYmFnZ2luZ193b3JrZmxvdyA8LSAgd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUoYmFnZ2luZ19yZWNpcGUpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9tb2RlbChiYWdnaW5nX3NwZWMpCgpiYWdnaW5nX3dvcmtmbG93IApgYGAKCiMjIEJhZ2dpbmcgbW9kZWwgb24gdHJhaW5pbmcgZGF0YQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpiYWdnaW5nX2ZpdCA8LSBwYXJzbmlwOjpmaXQob2JqZWN0ID0gYmFnZ2luZ193b3JrZmxvdywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gQm9zdG9uX3RyYWluKQpgYGAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCldoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgaW4gdGhpcyB0cmVlIGZvciBwcmVkaWN0aW5nIGBtZWR2YD8KClVzaW5nIHRoZSBgdmlwYCBwYWNrYWdlIGFuZApbYHdvcmtmbG93czo6ZXh0cmFjdF9maXRfcGFyc25pcGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXdvcmtmbG93Lmh0bWwpLCB3ZSBoYXZlCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnZpcF90YWJsZSA8LSBiYWdnaW5nX2ZpdCAlPiUKICB3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUKICB2aXA6OnZpKCkgCgp2aXBfdGFibGUgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KYmFnZ2luZ19maXQgJT4lCiAgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lCiAgdmlwOjp2aXAoZ2VvbSA9ICJjb2wiLCAKICAgICAgICAgICBhZXN0aGV0aWNzID0gbGlzdChmaWxsID0gIm1pZG5pZ2h0Ymx1ZSIsIGFscGhhID0gMC44KSkgKwogIGdncGxvdDI6OnNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKQpgYGAKCiMjIEJhZ2dpbmcgbW9kZWwgb24gdGVzdCBkYXRhCgpGaW5hbGx5LCBsZXTigJlzIHR1cm4gdG8gdGhlIHRlc3RpbmcgZGF0YS4KRm9yIHJlZ3Jlc3Npb24gbW9kZWxzLCBhCmAucHJlZGAsIGNvbHVtbiBpcyBhZGRlZCB3aGVuCltgcGFyc25pcDo6YXVnbWVudGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXVnbWVudC5odG1sKQppcyB1c2VkLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgPC0gcGFyc25pcDo6YXVnbWVudCh4ID0gYmFnZ2luZ19maXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfZGF0YSA9IEJvc3Rvbl90ZXN0KQogIAp0ZXN0X3Jlc3VsdHMgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKCmBgYAoKV2UgY2hlY2sgaG93IHdlbGwgdGhlIGAucHJlZGAgY29sdW1uIG1hdGNoZXMgdGhlIGBtZWR2YCB1c2luZwpbYHlhcmRzdGljazo6cm1zZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ybXNlLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnRlc3RfcmVzdWx0cyAlPiUKICB5YXJkc3RpY2s6OnJtc2UodHJ1dGggPSAuZGF0YVtbIm1lZHYiXV0sIGVzdGltYXRlID0gLmRhdGFbWyIucHJlZCJdXSkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3Jlc3VsdHMgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1sibWVkdiJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyIucHJlZCJdXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBsdHkgPSAyLCBjb2xvciA9ICJncmF5NTAiLCBhbHBoYSA9IDAuNSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gIm1pZG5pZ2h0Ymx1ZSIpICsKICBnZ3Bsb3QyOjpjb29yZF9maXhlZCgpCmBgYAoKIyBSYW5kb20gRm9yZXN0CgpIZXJlIHdlIGFwcGx5IHJhbmRvbSBmb3Jlc3QgdG8gdGhlIGBCb3N0b25gIGRhdGEsIHVzaW5nIHRoZSBgcmFuZG9tRm9yZXN0YCBwYWNrYWdlIGluIFIKCmBgYHtyLCBlY2hvPUZBTFNFfQpzZXQuc2VlZCgxKQpgYGAKCiMjIENyZWF0ZSB0aGUgcnNhbXBsZSBvYmplY3QKCkZpcnN0LCB3ZSBzcGxpdCB0aGUgc2FtcGxlcyBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0LiBGcm9tIHRoZSB0cmFpbmluZyBzZXQsIHdlIGNyZWF0ZSBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBkYXRhIHNldCBmcm9tIHRoZSB0cmFpbmluZyBzZXQuCgpUaGlzIGlzIGRvbmUgd2l0aCBbYHJzYW1wbGU6OmluaXRpYWxfc3BsaXRgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCksCltgcnNhbXBsZTo6dHJhaW5pbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkgYW5kCltgcnNhbXBsZTo6dGVzdGluZ2BdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpCb3N0b25fc3BsaXQgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChCb3N0b24pCgpCb3N0b25fdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQm9zdG9uX3NwbGl0KQpCb3N0b25fdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEJvc3Rvbl9zcGxpdCkKCmBgYAoKIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3IKCldlIGNyZWF0ZSBhIHJlY2lwZSB3aXRoIFtgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkuCk5vIG90aGVyIHByZXByb2Nlc3Npbmcgc3RlcCBpcyBkb25lLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpyZl9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBtZWR2IH4gLiwgZGF0YSA9IEJvc3Rvbl90cmFpbikKCmBgYAoKIyMgU3BlY2lmeSB0aGUgbW9kZWwKClVzZQpbYHBhcnNuaXA6OnJhbmRfZm9yZXN0YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yYW5kX2ZvcmVzdC5odG1sKSwKW2BwYXJzbmlwOjpzZXRfbW9kZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2FyZ3MuaHRtbCkKYW5kCltgcGFyc25pcDo6c2V0X2VuZ2luZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2VuZ2luZS5odG1sKQp0byBjcmVhdGUgdGhlIG1vZGVsLgoKYG10cnlgIGlzIHRoZSBudW1iZXIgb2YgcHJlZGljdG9ycyB0aGF0IHdpbGwgYmUgcmFuZG9tbHkgc2FtcGxlZCBhdCBlYWNoIHNwbGl0IHdoZW4gY3JlYXRpbmcgdGhlIHRyZWUgbW9kZWxzLiBGb3IgcmFuZG9tIGZvcmVzdCwgdGhlIGByYW5kb21Gb3Jlc3Q6OnJhbmRvbUZvcmVzdGAgdXNlICRcZnJhY3twfXszfSQgdmFyaWFibGVzIHdoZW4gYnVpbGRpbmcgYSByYW5kb20gZm9yZXN0IG9mIHJlZ3Jlc3Npb24gdHJlZXMgYW5kICRcc3FydHtwfSQgdmFyaWFibGVzIHdoZW4gYnVpbGRpbmcgYSByYW5kb20gZm9yZXN0IG9mIGNsYXNzaWZpY2F0aW9uIHRyZWVzLgoKYGltcG9ydGFuY2VgIHNldCB0byBUUlVFIGVuc3VyZXMgdGhlIGltcG9ydGFuY2Ugb2YgcHJlZGljdG9ycyBhcmUgYXNzZXNzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnJmX3NwZWMgPC0gcGFyc25pcDo6cmFuZF9mb3Jlc3QoKSAlPiUKICBwYXJzbmlwOjpzZXRfZW5naW5lKCJyYW5kb21Gb3Jlc3QiLCBpbXBvcnRhbmNlID0gVFJVRSkgJT4lCiAgcGFyc25pcDo6c2V0X21vZGUoInJlZ3Jlc3Npb24iKQoKcmZfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKcmZfd29ya2Zsb3cgPC0gIHdvcmtmbG93czo6d29ya2Zsb3coKSAlPiUgCiAgd29ya2Zsb3dzOjphZGRfcmVjaXBlKHJmX3JlY2lwZSkgJT4lIAogIHdvcmtmbG93czo6YWRkX21vZGVsKHJmX3NwZWMpCgpyZl93b3JrZmxvdyAKYGBgCgojIyBSYW5kb20gZm9yZXN0IG9uIHRyYWluaW5nIGRhdGEKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcmZfZml0IDwtIHBhcnNuaXA6OmZpdChvYmplY3QgPSByZl93b3JrZmxvdywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gQm9zdG9uX3RyYWluKQpgYGAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCldoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgaW4gdGhpcyB0cmVlIGZvciBwcmVkaWN0aW5nIGBtZWR2YD8KClVzaW5nIHRoZSBgdmlwYCBwYWNrYWdlIGFuZApbYHdvcmtmbG93czo6ZXh0cmFjdF9maXRfcGFyc25pcGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXdvcmtmbG93Lmh0bWwpLCB3ZSBoYXZlCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnZpcF90YWJsZSA8LSByZl9maXQgJT4lCiAgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lCiAgdmlwOjp2aSgpIAoKdmlwX3RhYmxlICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnJmX2ZpdCAlPiUKICB3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUKICB2aXA6OnZpcChnZW9tID0gImNvbCIsIAogICAgICAgICAgIGFlc3RoZXRpY3MgPSBsaXN0KGZpbGwgPSAibWlkbmlnaHRibHVlIiwgYWxwaGEgPSAwLjgpKSArCiAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpCmBgYAoKVGhlIGByYW5kb21Gb3Jlc3RgIHBhY2thZ2UgYWxzbyBoYXMgZnVuY3Rpb25zIGxpa2UgYHJhbmRvbUZvcmVzdDo6aW1wb3J0YW5jZWAgdG8gdmlldyB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIHZhcmlhYmxlIGFzIHdlbGwuIFRoZSBrZXkgaXMgdG8gdXNlIFtgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9lbmdpbmVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZXh0cmFjdC13b3JrZmxvdy5odG1sKQoKVGhlIGZpcnN0IGlzIGJhc2VkIHVwb24gdGhlIG1lYW4gZGVjcmVhc2Ugb2YgYWNjdXJhY3kgaW4gcHJlZGljdGlvbnMgb24gdGhlIG91dCBvZiBiYWcgc2FtcGxlcyB3aGVuIGEgZ2l2ZW4gdmFyaWFibGUgaXMgcGVybXV0ZWQuIAoKVGhlIHNlY29uZCBpcyBhIG1lYXN1cmUgb2YgdGhlIHRvdGFsIGRlY3JlYXNlIGluIG5vZGUgaW1wdXJpdHkgdGhhdCByZXN1bHRzIGZyb20gc3BsaXRzIG92ZXIgdGhhdCB2YXJpYWJsZSwgYXZlcmFnZWQgb3ZlciBhbGwgdHJlZXMuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmltcG9ydGFuY2VfdGFibGUgPC0gcmZfZml0ICU+JQogIHdvcmtmbG93czo6ZXh0cmFjdF9maXRfZW5naW5lKCkgJT4lCiAgcmFuZG9tRm9yZXN0OjppbXBvcnRhbmNlKCkKICAKaW1wb3J0YW5jZV90YWJsZSAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKClBsb3RzIG9mIHRoZXNlIGltcG9ydGFuY2UgbWVhc3VyZXMgY2FuIGJlIHByb2R1Y2VkIHVzaW5nIHRoZSBgcmFuZG9tRm9yZXN0Ojp2YXJJbXBQbG90YCBmdW5jdGlvbi4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKcmZfZml0ICU+JQogIHdvcmtmbG93czo6ZXh0cmFjdF9maXRfZW5naW5lKCkgJT4lIAogIHJhbmRvbUZvcmVzdDo6dmFySW1wUGxvdCgpCgpgYGAKCiMjIFJhbmRvbSBmb3Jlc3Qgb24gdGVzdCBkYXRhCgpGaW5hbGx5LCBsZXTigJlzIHR1cm4gdG8gdGhlIHRlc3RpbmcgZGF0YS4KRm9yIHJlZ3Jlc3Npb24gbW9kZWxzLCBhCmAucHJlZGAsIGNvbHVtbiBpcyBhZGRlZCB3aGVuCltgcGFyc25pcDo6YXVnbWVudGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXVnbWVudC5odG1sKQppcyB1c2VkLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgPC0gcGFyc25pcDo6YXVnbWVudCh4ID0gcmZfZml0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEgPSBCb3N0b25fdGVzdCkKICAKdGVzdF9yZXN1bHRzICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCgpgYGAKCldlIGNoZWNrIGhvdyB3ZWxsIHRoZSBgLnByZWRgIGNvbHVtbiBtYXRjaGVzIHRoZSBgbWVkdmAgdXNpbmcKW2B5YXJkc3RpY2s6OnJtc2VgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvcm1zZS5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3Jlc3VsdHMgJT4lCiAgeWFyZHN0aWNrOjpybXNlKHRydXRoID0gLmRhdGFbWyJtZWR2Il1dLCBlc3RpbWF0ZSA9IC5kYXRhW1siLnByZWQiXV0pICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKTGV0IHVzIHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgcHJlZGljdGVkIGFuZCBhY3R1YWwgcmVzcG9uc2UgYXMgYSBzY2F0dGVyIHBsb3QuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KdGVzdF9yZXN1bHRzICU+JQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbIm1lZHYiXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1siLnByZWQiXV0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICkgKwogIGdncGxvdDI6Omdlb21fYWJsaW5lKHNsb3BlID0gMSwgbHR5ID0gMiwgY29sb3IgPSAiZ3JheTUwIiwgYWxwaGEgPSAwLjUpICsKICBnZ3Bsb3QyOjpnZW9tX3BvaW50KGFscGhhID0gMC42LCBjb2xvciA9ICJtaWRuaWdodGJsdWUiKSArCiAgZ2dwbG90Mjo6Y29vcmRfZml4ZWQoKQpgYGAKCiMgQm9vc3RpbmcKCkhlcmUgd2UgYXBwbHkgYmFnZ2luZyB0byB0aGUgYEJvc3RvbmAgZGF0YS4gVGhlIGJvb2sgdXNlcyB0aGUgUiBwYWNrYWdlIGBnYm1gIHRvIGRvIHRoZSBib29zdGluZy4gVW5mb3J0dW5hdGVseSwgYGdibWAgaXMgbm90IG9uZSBvZiB0aGUgbGlzdCBib29zdGVkIHRyZWUgbW9kZWxzIGluIHRoZSBjdXJyZW50IFtwYXJzbmlwIGxpc3RdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYm9vc3RfdHJlZS5odG1sKS4KClRoZSBwcm9jZXNzIG9mIGJ1aWxkaW5nIGl0IGZyb20gc2NyYXRjaCBjYW4gYmUgZm91bmQgaW4gdGhpcyBbZ2l0aHViIGlzc3VlIHBvc3RdKGh0dHBzOi8vZ2l0aHViLmNvbS90aWR5bW9kZWxzL3BhcnNuaXAvaXNzdWVzLzIyMCkKCkZvciBzaW1wbGljaXR5LCBhIGRpZmZlcmVudCBSIHBhY2thZ2UgaXMgdXNlZCB0byBjcmVhdGUgYSBiYWdnaW5nIHRyZWUgbW9kZWwuIEluIHRoaXMgZXhhbXBsZSwgdGhlIGRlZmF1bHQgcGFyc25pcCBlbmdpbmUgZm9yIGJvb3N0ZWQgdHJlZSBpcyB0aGUgYHhnYm9vc3RgIFIgcGFja2FnZS4KCmBgYHtyLCBlY2hvPUZBTFNFfQpzZXQuc2VlZCgxKQpgYGAKCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0CgpGaXJzdCwgd2Ugc3BsaXQgdGhlIHNhbXBsZXMgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0IHNldC4gRnJvbSB0aGUgdHJhaW5pbmcgc2V0LCB3ZSBjcmVhdGUgYSAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gZGF0YSBzZXQgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LgoKVGhpcyBpcyBkb25lIHdpdGggW2Byc2FtcGxlOjppbml0aWFsX3NwbGl0YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRyYWluaW5nYF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLApbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkKYW5kCltgcnNhbXBsZTo6dmZvbGRfY3ZgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Zmb2xkX2N2Lmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgpCb3N0b25fc3BsaXQgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChCb3N0b24pCgpCb3N0b25fdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoQm9zdG9uX3NwbGl0KQpCb3N0b25fdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEJvc3Rvbl9zcGxpdCkKCkJvc3Rvbl9mb2xkIDwtIHJzYW1wbGU6OnZmb2xkX2N2KEJvc3Rvbl90cmFpbiwgdiA9IDEwKQoKCmBgYAoKIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3IKCldlIGNyZWF0ZSBhIHJlY2lwZSB3aXRoIFtgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkuCk5vIG90aGVyIHByZXByb2Nlc3Npbmcgc3RlcCBpcyBkb25lLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgpib29zdF9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBtZWR2IH4gLiwgZGF0YSA9IEJvc3Rvbl90cmFpbikKCmBgYAoKIyMgU3BlY2lmeSB0aGUgbW9kZWwKClVzZQpbYHBhcnNuaXA6OmJvb3N0X3RyZWVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2Jvb3N0X3RyZWUuaHRtbCksCltgcGFyc25pcDo6c2V0X21vZGVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9hcmdzLmh0bWwpCmFuZApbYHBhcnNuaXA6OnNldF9lbmdpbmVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9lbmdpbmUuaHRtbCkKdG8gY3JlYXRlIHRoZSBtb2RlbC4KClJlY2FsbCBpbiB0aGUgYm9vayB0aGF0IHdlIGNhbiB0dW5lIHRoZSBudW1iZXIgb2YgdHJlZXMgYHRyZWVzYCwgdGhlIHNocmlua2FnZSBwYXJhbWV0ZXIgYGxlYXJuX3JhdGVgIGFuZCB0aGUgbnVtYmVyIG9mIHNwbGl0cy9kZXB0aCBgdHJlZV9kZXB0aGAuCgpUaGVzZSBjYW4gYmUgdHVuZWQgdXNpbmcgW2B0dW5lOjp0dW5lYF0oaHR0cHM6Ly9oYXJkaGF0LnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90dW5lLmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJvb3N0X3NwZWMgPC0gcGFyc25pcDo6Ym9vc3RfdHJlZSgKICB0cmVlcyA9IHR1bmU6OnR1bmUoKSwKICB0cmVlX2RlcHRoID0gdHVuZTo6dHVuZSgpLAogIGxlYXJuX3JhdGUgPSB0dW5lOjp0dW5lKCkKICApICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lIAogIHBhcnNuaXA6OnNldF9lbmdpbmUoInhnYm9vc3QiKQoKYm9vc3Rfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKYm9vc3Rfd29ya2Zsb3cgPC0gIHdvcmtmbG93czo6d29ya2Zsb3coKSAlPiUgCiAgd29ya2Zsb3dzOjphZGRfcmVjaXBlKGJvb3N0X3JlY2lwZSkgJT4lIAogIHdvcmtmbG93czo6YWRkX21vZGVsKGJvb3N0X3NwZWMpCgpib29zdF93b3JrZmxvdyAKYGBgCgojIyBDcmVhdGUgdGhlIGJvb3N0aW5nIHRyZWUgZ3JpZAoKTGV04oCZcyB1c2UgYSBzcGFjZS1maWxsaW5nIGRlc2lnbiAobm9uLXJlZ3VsYXIgZ3JpZCkgc28gdGhhdCB3ZSBjYW4gY292ZXIgdGhlIGh5cGVycGFyYW1ldGVyIHNwYWNlIGFzIHdlbGwgYXMgcG9zc2libGUuIFdlIGRvIHRoaXMgdXNpbmcKW2BkaWFsczo6Z3JpZF9sYXRpbl9oeXBlcmN1YmVgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ncmlkX21heF9lbnRyb3B5Lmh0bWwpCgpXZSB1c2UgdGhlIGRlZmF1bHQgdmFsdWVzIGZvciBbYGRpYWxzOjp0cmVlc2BdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3RyZWVzLmh0bWwpLCBbYGRpYWxzOjp0cmVlX2RlcHRoYF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHJlZXMuaHRtbCkgYW5kCltgZGlhbHM6OmxlYXJuX3JhdGVgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9sZWFybl9yYXRlLmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJvb3N0X2dyaWQgPC0gCiAgZGlhbHM6OmdyaWRfbGF0aW5faHlwZXJjdWJlKAogICAgZGlhbHM6OnRyZWVzKHJhbmdlID0gYygxTCwgMjAwMEwpKSwKICAgIGRpYWxzOjp0cmVlX2RlcHRoKHJhbmdlID0gYygxTCwgMTVMKSksCiAgICBkaWFsczo6bGVhcm5fcmF0ZShyYW5nZSA9IGMoLTEwLCAtMSksIAogICAgICAgICAgICAgICAgICAgICAgdHJhbnMgPSBzY2FsZXM6OmxvZzEwX3RyYW5zKCkpLAogICAgc2l6ZSA9IDEwCiAgKQoKYm9vc3RfZ3JpZCAgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKWW91IG1heSByZWZlciB0byB0aGUgW1RpZHl2ZXJzZSBibG9nXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL2Jsb2cvMjAxOS8xMC9kaWFscy0wLTAtMy8pIG9yIFtUZW5na3UgSGFuaXMgYmxvZ10oaHR0cHM6Ly90ZW5na3VoYW5pcy5uZXRsaWZ5LmFwcC9wb3N0L2h5cGVycGFyYW1ldGVyLXR1bmluZy1pbi10aWR5bW9kZWxzLykgZm9yIG1vcmUgZGV0YWlscyBhYm91dCBob3cgdGhlIGRpZmZlcmVudCBncmlkcyB3b3JrLgoKIyMgQm9vc3RpbmcgdHJlZSBtb2RlbCBmaXR0aW5nIG9uIGNyb3NzIHZhbGlkYXRlZCBkYXRhCgpOb3cgd2UgaGF2ZSBldmVyeXRoaW5nIHdlIG5lZWQgYW5kIHdlIGNhbiBmaXQgYWxsIHRoZSBtb2RlbHMgb24gdGhlCmNyb3NzIHZhbGlkYXRlZCBkYXRhIHdpdGgKW2B0dW5lOjp0dW5lX2dyaWRgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmVfZ3JpZC5odG1sKS4KTm90ZSB0aGF0IHRoaXMgcHJvY2VzcyBtYXkgdGFrZSBzb21lIHRpbWUuCgpXZSB1c2UgW2B5YXJkc3RpY2s6Om1ldHJpY19zZXRgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbWV0cmljX3NldC5odG1sKSwgdG8gY2hvb3NlIGEgc2V0IG9mIG1ldHJpY3MgdG8gdXNlZCB0byBldmFsdWF0ZSB0aGUgbW9kZWwuIEluIHRoaXMgZXhhbXBsZSwgW2B5YXJkc3RpY2s6OnJtc2VgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvcm1zZS5odG1sKSBhbmQKW2B5YXJkc3RpY2s6OnJzcWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yc3EuaHRtbCkgYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkKZm9yZWFjaDo6Z2V0RG9QYXJXb3JrZXJzKCkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQp0dW5lX3JlcyA8LSB0dW5lOjp0dW5lX2dyaWQoCiAgb2JqZWN0ID0gYm9vc3Rfd29ya2Zsb3csCiAgcmVzYW1wbGVzID0gQm9zdG9uX2ZvbGQsIAogIGdyaWQgPSBib29zdF9ncmlkLAogIG1ldHJpY3MgPSB5YXJkc3RpY2s6Om1ldHJpY19zZXQoeWFyZHN0aWNrOjpybXNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWFyZHN0aWNrOjpyc3EpCikKCnR1bmVfcmVzCmBgYAoKSGVyZSB3ZSBzZWUgdGhhdCB0aGUgZGlmZmVyZW50IGFtb3VudCBvZiB0cmVlcywgbGVhcm5pbmcvc2hyaW5rYWdlL3JhdGUgYW5kIHRyZWUgZGVwdGggYWZmZWN0cyB0aGUgcGVyZm9ybWFuY2UKbWV0cmljcyBkaWZmZXJlbnRseSB1c2luZwpbYHR1bmU6OmF1dG9wbG90YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdXRvcGxvdC50dW5lX3Jlc3VsdHMuaHRtbCkuCkRvIG5vdGUgdGhhdCB1c2luZyBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBhIGRpZmZlcmVudCBwbG90CgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyBOb3RlIHRoYXQgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgZGlmZmVyZW50IHBsb3RzCnR1bmU6OmF1dG9wbG90KHR1bmVfcmVzKQpgYGAKCldlIGNhbiBhbHNvIHNlZSB0aGUgcmF3IG1ldHJpY3MgdGhhdCBjcmVhdGVkIHRoaXMgY2hhcnQgYnkgY2FsbGluZwpbYHR1bmU6OmNvbGxlY3RfbWV0cmljcygpYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KCnR1bmU6OmNvbGxlY3RfbWV0cmljcyh0dW5lX3JlcykgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKSGVyZSBpcyB0aGUgYGdncGxvdGAgd2F5IHNob3VsZApbYHR1bmU6OmF1dG9wbG90YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdXRvcGxvdC50dW5lX3Jlc3VsdHMuaHRtbCkKZmFpbHMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpmb3JfcGxvdHRpbmcgPC0gdHVuZV9yZXMgJT4lCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgZHBseXI6OnNlbGVjdChkcGx5cjo6YWxsX29mKGMoIm1lYW4iLCAiLm1ldHJpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRyZWVzIiwidHJlZV9kZXB0aCIsImxlYXJuX3JhdGUiKSkpICU+JSAKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgY29scyA9IGRwbHlyOjphbGxfb2YoYygidHJlZXMiLCJ0cmVlX2RlcHRoIiwibGVhcm5fcmF0ZSIpKSwKICAgIHZhbHVlc190byA9ICJ2YWx1ZSIsCiAgICBuYW1lc190byA9ICJwYXJhbWV0ZXIiKQoKZm9yX3Bsb3R0aW5nICU+JSAKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKZm9yX3Bsb3R0aW5nICU+JQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbInZhbHVlIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIm1lYW4iXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gLmRhdGFbWyJwYXJhbWV0ZXIiXV0pKSArCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludChhbHBoYSA9IDAuOCwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdncGxvdDI6OmZhY2V0X2dyaWQoY29scyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyJwYXJhbWV0ZXIiXV0pLAogICAgICAgICAgICAgICAgICAgICAgcm93cyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyIubWV0cmljIl1dKSwKICAgICAgICAgICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIikgKwogIGdncGxvdDI6OmxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMKQpgYGAKClVzZQpbYHR1bmU6OnNob3dfYmVzdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2hvd19iZXN0Lmh0bWwpCnRvIHNlZSB0aGUgdG9wIGZldyB2YWx1ZXMgZm9yIGEgZ2l2ZW4gbWV0cmljLgoKVGhlICJiZXN0IiB2YWx1ZXMgY2FuIGJlIHNlbGVjdGVkIHVzaW5nCltgdHVuZTo6c2VsZWN0X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKS4KCkRvIG5vdGUgdGhhdCB1c2luZyBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBhIGRpZmZlcmVudCBvcHRpbWlzZWQgdmFsdWUuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKdG9wX2Jvb3N0X2dyaWQgPC0gdHVuZTo6c2hvd19iZXN0KHR1bmVfcmVzLCBtZXRyaWMgPSBjKCJybXNlIiksIG4gPSA1KQp0b3BfYm9vc3RfZ3JpZCAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQoKYmVzdF9ib29zdF9ncmlkIDwtIHR1bmU6OnNlbGVjdF9iZXN0KHR1bmVfcmVzLCBtZXRyaWMgPSAicm1zZSIpCmJlc3RfYm9vc3RfZ3JpZAoKYGBgCgojIyBCb29zdGluZyB0cmVlIG1vZGVsIHdpdGggb3B0aW1pc2VkIGdyaWQKCldlIGNyZWF0ZSB0aGUgYm9vc3RpbmcgdHJlZSB3b3JrZmxvdyB3aXRoIHRoZSBiZXN0IGdyaWQKW2B0dW5lOjpmaW5hbGl6ZV93b3JrZmxvd2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZmluYWxpemVfbW9kZWwuaHRtbCkuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKYm9vc3RfZmluYWwgPC0gdHVuZTo6ZmluYWxpemVfd29ya2Zsb3coCiAgeCA9IGJvb3N0X3dvcmtmbG93LCAKICBwYXJhbWV0ZXJzID0gYmVzdF9ib29zdF9ncmlkKQoKYm9vc3RfZmluYWwKYGBgCgpXZSBub3cgdHJhaW4gdGhlIGJvb3N0aW5nIHRyZWUgbW9kZWwgd2l0aCB0aGUgdHJhaW5pbmcgZGF0YSB1c2luZyBbYHBhcnNuaXA6OmZpdGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZml0Lmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJvb3N0X2ZpbmFsX2ZpdCA8LSBwYXJzbmlwOjpmaXQob2JqZWN0ID0gYm9vc3RfZmluYWwsIGRhdGEgPSBCb3N0b25fdHJhaW4pCmBgYAoKV2UgY2FuIHNlZSB0aGUgdHJlZSBpbiBncmVhdGVyIGRldGFpbCB1c2luZwpbYHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZXh0cmFjdC10dW5lLmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQp4Z2JfbW9kZWwgPC0gYm9vc3RfZmluYWxfZml0ICU+JQogIHR1bmU6OmV4dHJhY3RfZml0X2VuZ2luZSgpCgp4Z2JfbW9kZWwKYGBgCgpXZSBjYW4gdmlzdWFsaXNlIG9uZSBvZiB0aGUgdHJlZXMgd2l0aCBgeGdib29zdDo6eGdiLnBsb3QudHJlZWAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CiMgU2VlIHRoZSBmaXJzdCB0cmVlCmdyIDwtIHhnYm9vc3Q6OnhnYi5wbG90LnRyZWUobW9kZWwgPSB4Z2JfbW9kZWwsIHRyZWVzID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZW5kZXIgPSBGQUxTRSkKCkRpYWdyYW1tZVI6OmV4cG9ydF9ncmFwaChncmFwaCA9IGdyLAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsZV9uYW1lID0gImRvY3MveGdib29zdF9zaW5nbGVfdHJlZS5wbmciLAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsZV90eXBlID0gIlBORyIpCgpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiZG9jcy94Z2Jvb3N0X3NpbmdsZV90cmVlLnBuZyIpCmBgYAoKV2UgY2FuIHZpc3VhbGlzZSBhIHN1bW1hcnkgb2YgYWxsIHRoZSB0cmVlcyB3aXRoIGB4Z2Jvb3N0Ojp4Z2IucGxvdC5tdWx0aS50cmVlc2AKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9CmdyIDwtIHhnYm9vc3Q6OnhnYi5wbG90Lm11bHRpLnRyZWVzKG1vZGVsID0geGdiX21vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlc19rZWVwID0gMywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVuZGVyID0gRkFMU0UpCgpEaWFncmFtbWVSOjpleHBvcnRfZ3JhcGgoZ3JhcGggPSBnciwKICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVfbmFtZSA9ICJkb2NzL3hnYm9vc3RfbXVsdGlwbGVfdHJlZS5wbmciLAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsZV90eXBlID0gIlBORyIpCgpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiZG9jcy94Z2Jvb3N0X211bHRpcGxlX3RyZWUucG5nIikKYGBgCgojIyBWYXJpYWJsZSBJbXBvcnRhbmNlCgpXaGF0IGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGluIHRoaXMgdHJlZSBmb3IgcHJlZGljdGluZyBgbWVkdmA/CgpXZSBjYW4gZXh0cmFjdCB0aGUgaW1wb3J0YW50IGZlYXR1cmVzIGZyb20gdGhlIGJvb3N0ZWQgdHJlZSBtb2RlbCB3aXRoIGB4Z2Jvb3N0Ojp4Z2IuaW1wb3J0YW5jZWAKCkRldGFpbHMgb24gd2hhdCBgR2FpbmAsIGBDb3ZlcmAgYW5kIGBGcmVxdWVuY3lgIGNhbiBiZSBmb3VuZCBpbiB0aGlzIFtibG9nIHBvc3RdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tLzIwMTkvMTAvZXhwbGFpbmluZy1wcmVkaWN0aW9ucy1ib29zdGVkLXRyZWVzLXBvc3QtaG9jLWFuYWx5c2lzLXhnYm9vc3QvKQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CgppbXBvcnRhbmNlX21hdHJpeCA8LSB4Z2Jvb3N0Ojp4Z2IuaW1wb3J0YW5jZShtb2RlbCA9IHhnYl9tb2RlbCkKCmltcG9ydGFuY2VfbWF0cml4ICU+JSAKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQpgYGAKClRoZSBgeGdib29zdDo6eGdiLmdncGxvdC5pbXBvcnRhbmNlYCB1c2VzIHRoZSBgR2FpbmAgdmFyaWFibGUgaW1wb3J0YW5jZSBtZWFzdXJlbWVudCBieSBkZWZhdWx0IHRvIGNhbGN1bGF0ZSB2YXJpYWJsZSBpbXBvcnRhbmNlLiAKCmBgYHtyfQoKeGdib29zdDo6eGdiLmdncGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbF90b19maXJzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeGxhYiA9ICJSZWxhdGl2ZSBpbXBvcnRhbmNlIikKCmBgYAoKIyMgQm9vc3RpbmcgdHJlZSBtb2RlbCBvbiB0ZXN0IGRhdGEKCkZpbmFsbHksIGxldOKAmXMgdHVybiB0byB0aGUgdGVzdGluZyBkYXRhLgpGb3IgcmVncmVzc2lvbiBtb2RlbHMsIGEKYC5wcmVkYCwgY29sdW1uIGlzIGFkZGVkIHdoZW4KW2BwYXJzbmlwOjphdWdtZW50YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdWdtZW50Lmh0bWwpCmlzIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCnRlc3RfcmVzdWx0cyA8LSBwYXJzbmlwOjphdWdtZW50KHggPSBib29zdF9maW5hbF9maXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfZGF0YSA9IEJvc3Rvbl90ZXN0KQogIAp0ZXN0X3Jlc3VsdHMgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKCmBgYAoKV2UgY2hlY2sgaG93IHdlbGwgdGhlIGAucHJlZGAgY29sdW1uIG1hdGNoZXMgdGhlIGBtZWR2YCB1c2luZwpbYHlhcmRzdGljazo6cm1zZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ybXNlLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnRlc3RfcmVzdWx0cyAlPiUKICB5YXJkc3RpY2s6OnJtc2UodHJ1dGggPSAuZGF0YVtbIm1lZHYiXV0sIGVzdGltYXRlID0gLmRhdGFbWyIucHJlZCJdXSkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3Jlc3VsdHMgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1sibWVkdiJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyIucHJlZCJdXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBsdHkgPSAyLCBjb2xvciA9ICJncmF5NTAiLCBhbHBoYSA9IDAuNSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gIm1pZG5pZ2h0Ymx1ZSIpICsKICBnZ3Bsb3QyOjpjb29yZF9maXhlZCgpCmBgYAoKIyBCYXlzZXNpYW4gQWRkaXRpdmUgUmVncmVzc2lvbiBUcmVlIChCQVJUKQoKSGVyZSB3ZSBhcHBseSBiYWdnaW5nIHRvIHRoZSBgQm9zdG9uYCBkYXRhLiBUaGUgYm9vayB1c2VzIHRoZSBSIHBhY2thZ2UgYEJBUlRgIHRvIGRvIHRoZSBib29zdGluZy4gVW5mb3J0dW5hdGVseSwgYEJBUlRgIGlzIG5vdCBvbmUgb2YgdGhlIGxpc3QgYmFydCBtb2RlbHMgaW4gdGhlIGN1cnJlbnQgW3BhcnNuaXAgbGlzdF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9iYXJ0Lmh0bWwpLgoKRm9yIHNpbXBsaWNpdHksIGEgZGlmZmVyZW50IFIgcGFja2FnZSBpcyB1c2VkIHRvIGNyZWF0ZSBhIEJBUlQgbW9kZWwuIEluIHRoaXMgZXhhbXBsZSwgdGhlIGRlZmF1bHQgcGFyc25pcCBlbmdpbmUgZm9yIGJvc3RlZCB0cmVlIGlzIHRoZSBgZGJhcnRzYCBSIHBhY2thZ2UuCgpgYGB7ciwgZWNobz1GQUxTRX0Kc2V0LnNlZWQoMSkKYGBgCgojIyBDcmVhdGUgdGhlIHJlc2FtcGxlIG9iamVjdAoKRmlyc3QsIHdlIHNwbGl0IHRoZSBzYW1wbGVzIGludG8gYSB0cmFpbmluZyBzZXQgYW5kIGEgdGVzdCBzZXQuIEZyb20gdGhlIHRyYWluaW5nIHNldCwgd2UgY3JlYXRlIGEgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIGRhdGEgc2V0IGZyb20gdGhlIHRyYWluaW5nIHNldC4KClRoaXMgaXMgZG9uZSB3aXRoIFtgcnNhbXBsZTo6aW5pdGlhbF9zcGxpdGBdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKSwKW2Byc2FtcGxlOjp0cmFpbmluZ2BdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKSwKW2Byc2FtcGxlOjp0ZXN0aW5nYF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpCmFuZApbYHJzYW1wbGU6OnZmb2xkX2N2YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS92Zm9sZF9jdi5odG1sKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKQm9zdG9uX3NwbGl0IDwtIHJzYW1wbGU6OmluaXRpYWxfc3BsaXQoQm9zdG9uKQoKQm9zdG9uX3RyYWluIDwtIHJzYW1wbGU6OnRyYWluaW5nKEJvc3Rvbl9zcGxpdCkKQm9zdG9uX3Rlc3QgPC0gcnNhbXBsZTo6dGVzdGluZyhCb3N0b25fc3BsaXQpCgpCb3N0b25fZm9sZCA8LSByc2FtcGxlOjp2Zm9sZF9jdihCb3N0b25fdHJhaW4sIHYgPSAxMCkKCgpgYGAKCiMjIENyZWF0ZSB0aGUgcHJlcHJvY2Vzc29yCgpXZSBjcmVhdGUgYSByZWNpcGUgd2l0aCBbYHJlY2lwZXM6OnJlY2lwZWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcmVjaXBlLmh0bWwpLgpObyBvdGhlciBwcmVwcm9jZXNzaW5nIHN0ZXAgaXMgZG9uZS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKYmFydF9yZWNpcGUgPC0gCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBtZWR2IH4gLiwgZGF0YSA9IEJvc3Rvbl90cmFpbikKCmBgYAoKIyMgU3BlY2lmeSB0aGUgbW9kZWwKClVzZQpbYHBhcnNuaXA6OmJhcnRgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2JhcnQuaHRtbCksCltgcGFyc25pcDo6c2V0X21vZGVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9hcmdzLmh0bWwpCmFuZApbYHBhcnNuaXA6OnNldF9lbmdpbmVgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3NldF9lbmdpbmUuaHRtbCkKdG8gY3JlYXRlIHRoZSBtb2RlbC4KCmBuZHBvc3RgIGlzIHRoZSBudW1iZXIgb2YgTUNNQyBpbnRlcmF0aW9ucy4KYG5za2lwYCBpcyB0aGUgbnVtYmVyIG9mIGJ1cm4taW4gaXRlcmF0aW9ucy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKYmFydF9zcGVjIDwtIHBhcnNuaXA6OmJhcnQoCiAgdHJlZXMgPSB0dW5lOjp0dW5lKCkpICU+JQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lIAogIHBhcnNuaXA6OnNldF9lbmdpbmUoImRiYXJ0cyIsCiAgICAgICAgICAgICAgICAgICAgICBuc2tpcCA9IDEwMCwKICAgICAgICAgICAgICAgICAgICAgIG5kcG9zdCA9IDUwMCkKCmJhcnRfc3BlYyAlPiUKICBwYXJzbmlwOjp0cmFuc2xhdGUoKQpgYGAKCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cKCltgd29ya2Zsb3dzOjp3b3JrZmxvd2BdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS93b3JrZmxvdy5odG1sKSwKW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQphbmQKW2B3b3JrZmxvd3M6OmFkZF9tb2RlbGBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfbW9kZWwuaHRtbCkKYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQoKYmFydF93b3JrZmxvdyA8LSAgd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUoYmFydF9yZWNpcGUpICU+JSAKICB3b3JrZmxvd3M6OmFkZF9tb2RlbChiYXJ0X3NwZWMpCgpiYXJ0X3dvcmtmbG93IApgYGAKCiMjIENyZWF0ZSB0aGUgQkFSVCBncmlkCgpMZXTigJlzIHVzZSBhIHNwYWNlLWZpbGxpbmcgZGVzaWduIChub24tcmVndWxhciBncmlkKSBzbyB0aGF0IHdlIGNhbiBjb3ZlciB0aGUgaHlwZXJwYXJhbWV0ZXIgc3BhY2UgYXMgd2VsbCBhcyBwb3NzaWJsZS4gV2UgZG8gdGhpcyB1c2luZwpbYGRpYWxzOjpncmlkX3JlZ3VsYXJgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ncmlkX3JlZ3VsYXIuaHRtbCkKCldlIHVzZSB0aGUgZGVmYXVsdCB2YWx1ZXMgZm9yIFtgZGlhbHM6OnRyZWVzYF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHJlZXMuaHRtbCksIFtgZGlhbHM6OnRyZWVfZGVwdGhgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90cmVlcy5odG1sKSBhbmQKW2BkaWFsczo6bGVhcm5fcmF0ZWBdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2xlYXJuX3JhdGUuaHRtbCkKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKYmFydF9ncmlkIDwtIAogIGRpYWxzOjpncmlkX3JlZ3VsYXIoCiAgICB4ID0gZGlhbHM6OnRyZWVzKHJhbmdlID0gYygxTCwgMTBMKSksCiAgICBsZXZlbHMgPSAxMAogICkKCmJhcnRfZ3JpZCAgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKIyMgQkFSVCBtb2RlbCBmaXR0aW5nIG9uIGNyb3NzIHZhbGlkYXRlZCBkYXRhCgpOb3cgd2UgaGF2ZSBldmVyeXRoaW5nIHdlIG5lZWQgYW5kIHdlIGNhbiBmaXQgYWxsIHRoZSBtb2RlbHMgb24gdGhlCmNyb3NzIHZhbGlkYXRlZCBkYXRhIHdpdGgKW2B0dW5lOjp0dW5lX2dyaWRgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmVfZ3JpZC5odG1sKS4KTm90ZSB0aGF0IHRoaXMgcHJvY2VzcyBtYXkgdGFrZSBzb21lIHRpbWUuCgpXZSB1c2UgW2B5YXJkc3RpY2s6Om1ldHJpY19zZXRgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbWV0cmljX3NldC5odG1sKSwgdG8gY2hvb3NlIGEgc2V0IG9mIG1ldHJpY3MgdG8gdXNlZCB0byBldmFsdWF0ZSB0aGUgbW9kZWwuIEluIHRoaXMgZXhhbXBsZSwgW2B5YXJkc3RpY2s6OnJtc2VgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvcm1zZS5odG1sKSBhbmQKW2B5YXJkc3RpY2s6OnJzcWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yc3EuaHRtbCkgYXJlIHVzZWQuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkKZm9yZWFjaDo6Z2V0RG9QYXJXb3JrZXJzKCkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQp0dW5lX3JlcyA8LSB0dW5lOjp0dW5lX2dyaWQoCiAgb2JqZWN0ID0gYmFydF93b3JrZmxvdywKICByZXNhbXBsZXMgPSBCb3N0b25fZm9sZCwgCiAgZ3JpZCA9IGJhcnRfZ3JpZCwKICBtZXRyaWNzID0geWFyZHN0aWNrOjptZXRyaWNfc2V0KHlhcmRzdGljazo6cm1zZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlhcmRzdGljazo6cnNxKQopCgp0dW5lX3JlcwpgYGAKCkhlcmUgd2Ugc2VlIHRoYXQgdGhlIGRpZmZlcmVudCBhbW91bnQgb2YgdHJlZXMgYWZmZWN0cyB0aGUgcGVyZm9ybWFuY2UKbWV0cmljcyBkaWZmZXJlbnRseSB1c2luZwpbYHR1bmU6OmF1dG9wbG90YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdXRvcGxvdC50dW5lX3Jlc3VsdHMuaHRtbCkuCkRvIG5vdGUgdGhhdCB1c2luZyBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBhIGRpZmZlcmVudCBwbG90CgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyBOb3RlIHRoYXQgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgZGlmZmVyZW50IHBsb3RzCnR1bmU6OmF1dG9wbG90KHR1bmVfcmVzKQpgYGAKCldlIGNhbiBhbHNvIHNlZSB0aGUgcmF3IG1ldHJpY3MgdGhhdCBjcmVhdGVkIHRoaXMgY2hhcnQgYnkgY2FsbGluZwpbYHR1bmU6OmNvbGxlY3RfbWV0cmljcygpYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KCnR1bmU6OmNvbGxlY3RfbWV0cmljcyh0dW5lX3JlcykgJT4lIAogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCmBgYAoKSGVyZSBpcyB0aGUgYGdncGxvdGAgd2F5IHNob3VsZApbYHR1bmU6OmF1dG9wbG90YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdXRvcGxvdC50dW5lX3Jlc3VsdHMuaHRtbCkKZmFpbHMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9Cgp0dW5lX3JlcyAlPiUKICB0dW5lOjpjb2xsZWN0X21ldHJpY3MoKSAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJ0cmVlcyJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyJtZWFuIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG91ciA9IC5kYXRhW1siLm1ldHJpYyJdXSkpICsKICBnZ3Bsb3QyOjpnZW9tX2Vycm9yYmFyKG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeW1pbiA9IC5kYXRhW1sibWVhbiJdXSAtIC5kYXRhW1sic3RkX2VyciJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeW1heCA9IC5kYXRhW1sibWVhbiJdXSArIC5kYXRhW1sic3RkX2VyciJdXSksCiAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKwogIGdncGxvdDI6Omdlb21fbGluZShzaXplID0gMS41KSArCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcChmYWNldHMgPSBnZ3Bsb3QyOjp2YXJzKC5kYXRhW1siLm1ldHJpYyJdXSksIAogICAgICAgICAgICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLCAKICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSAyKSArCiAgZ2dwbG90Mjo6c2NhbGVfeF9sb2cxMCgpICsKICBnZ3Bsb3QyOjp0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKVXNlCltgdHVuZTo6c2hvd19iZXN0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCkKdG8gc2VlIHRoZSB0b3AgZmV3IHZhbHVlcyBmb3IgYSBnaXZlbiBtZXRyaWMuCgpUaGUgImJlc3QiIHZhbHVlcyBjYW4gYmUgc2VsZWN0ZWQgdXNpbmcKW2B0dW5lOjpzZWxlY3RfYmVzdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2hvd19iZXN0Lmh0bWwpLgoKRG8gbm90ZSB0aGF0IHVzaW5nIGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGEgZGlmZmVyZW50IG9wdGltaXNlZCB2YWx1ZS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9Cgp0b3BfbnVtYmVyX29mX3RyZWVzIDwtIHR1bmU6OnNob3dfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gYygicm1zZSIpLCBuID0gNSkKdG9wX251bWJlcl9vZl90cmVlcyAlPiUKICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQoKYmVzdF9udW1iZXJfb2ZfdHJlZXMgPC0gdHVuZTo6c2VsZWN0X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9ICJybXNlIikKYmVzdF9udW1iZXJfb2ZfdHJlZXMKCmBgYAoKIyMgQkFSVCBtb2RlbCB3aXRoIG9wdGltaXNlZCBncmlkCgpXZSBjcmVhdGUgdGhlIEJBUlQgd29ya2Zsb3cgd2l0aCB0aGUgYmVzdCBncmlkCltgdHVuZTo6ZmluYWxpemVfd29ya2Zsb3dgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2ZpbmFsaXplX21vZGVsLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KCmJhcnRfZmluYWwgPC0gdHVuZTo6ZmluYWxpemVfd29ya2Zsb3coCiAgeCA9IGJhcnRfd29ya2Zsb3csIAogIHBhcmFtZXRlcnMgPSBiZXN0X251bWJlcl9vZl90cmVlcykKCmJhcnRfZmluYWwKYGBgCgpXZSBub3cgdHJhaW4gdGhlIGJvb3N0aW5nIHRyZWUgbW9kZWwgd2l0aCB0aGUgdHJhaW5pbmcgZGF0YSB1c2luZyBbYHBhcnNuaXA6OmZpdGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZml0Lmh0bWwpCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCmJhcnRfZmluYWxfZml0IDwtIHBhcnNuaXA6OmZpdChvYmplY3QgPSBiYXJ0X2ZpbmFsLCBkYXRhID0gQm9zdG9uX3RyYWluKQpgYGAKCldlIGNhbiBzZWUgdGhlIHRyZWUgaW4gZ3JlYXRlciBkZXRhaWwgdXNpbmcKW2B0dW5lOjpleHRyYWN0X2ZpdF9lbmdpbmVgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2V4dHJhY3QtdHVuZS5odG1sKQoKRm9yIGV4YW1wbGUsIHRvIHNlZSB0aGUgMm5kIHRyZWUgb24gdGhlIHRoaXJkIGl0ZXJhdGlvbgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30KYmFydF9tb2RlbCA8LSBiYXJ0X2ZpbmFsX2ZpdCAlPiUKICB0dW5lOjpleHRyYWN0X2ZpdF9lbmdpbmUoKQoKYmFydF9tb2RlbCRmaXQkcGxvdFRyZWUoc2FtcGxlTnVtID0gMywgdHJlZU51bSA9IDIpCmBgYAoKIyMgVmFyaWFibGUgSW1wb3J0YW5jZQoKV2hhdCBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyBpbiB0aGlzIHRyZWUgZm9yIHByZWRpY3RpbmcgYG1lZHZgPwoKV2UgY2FuIGV4dHJhY3QgdGhlIGltcG9ydGFudCBmZWF0dXJlcyBmcm9tIHRoZSBiYXJ0IHRyZWUgbW9kZWwgd2l0aCBgZW1iYXJjYWRlcm86OnZhcmltcGAKCmBgYHtyfQplbWJhcmNhZGVybzo6dmFyaW1wKGJhcnRfbW9kZWwsIHBsb3RzPVRSVUUpICU+JQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpCgpgYGAKCiMjIEJBUlQgbW9kZWwgb24gdGVzdCBkYXRhCgpGaW5hbGx5LCBsZXTigJlzIHR1cm4gdG8gdGhlIHRlc3RpbmcgZGF0YS4KRm9yIHJlZ3Jlc3Npb24gbW9kZWxzLCBhCmAucHJlZGAsIGNvbHVtbiBpcyBhZGRlZCB3aGVuCltgcGFyc25pcDo6YXVnbWVudGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXVnbWVudC5odG1sKQppcyB1c2VkLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9Cgp0ZXN0X3Jlc3VsdHMgPC0gcGFyc25pcDo6YXVnbWVudCh4ID0gYmFydF9maW5hbF9maXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfZGF0YSA9IEJvc3Rvbl90ZXN0KQogIAp0ZXN0X3Jlc3VsdHMgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKCmBgYAoKV2UgY2hlY2sgaG93IHdlbGwgdGhlIGAucHJlZGAgY29sdW1uIG1hdGNoZXMgdGhlIGBtZWR2YCB1c2luZwpbYHlhcmRzdGljazo6cm1zZWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ybXNlLmh0bWwpLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnRlc3RfcmVzdWx0cyAlPiUKICB5YXJkc3RpY2s6OnJtc2UodHJ1dGggPSAuZGF0YVtbIm1lZHYiXV0sIGVzdGltYXRlID0gLmRhdGFbWyIucHJlZCJdXSkgJT4lCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkKYGBgCgpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQp0ZXN0X3Jlc3VsdHMgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1sibWVkdiJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyIucHJlZCJdXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBsdHkgPSAyLCBjb2xvciA9ICJncmF5NTAiLCBhbHBoYSA9IDAuNSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gIm1pZG5pZ2h0Ymx1ZSIpICsKICBnZ3Bsb3QyOjpjb29yZF9maXhlZCgpCmBgYAoKIyBSbWFya2Rvd24gVGVtcGxhdGUKClRoaXMgUm1hcmtkb3duIHRlbXBsYXRlIGlzIGNyZWF0ZWQgYnkgdGhlIFtSZWFsaXR5IEJlbmRpbmcKTGFiXShodHRwczovL3JlYWxpdHliZW5kaW5nLmdpdGh1Yi5pby8pLiBUaGUgdGVtcGxhdGUgY2FuIGJlIGRvd25sb2FkCmZyb20gdGhlIGxhYidzCltnaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9SZWFsaXR5QmVuZGluZy9UZW1wbGF0ZVJlc3VsdHMpIHJlcG9zaXRvcnkuCkZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBtb3RpdmF0aW9uIGJlaGluZCBjcmVhdGluZyB0aGlzIHRlbXBsYXRlLApjaGVjayBvdXQgW0RyLiBEb21pbmlxdWUgTWFrb3dza2kncyBibG9nCnBvc3RdKGh0dHBzOi8vZG9taW5pcXVlbWFrb3dza2kuZ2l0aHViLmlvL3Bvc3QvMjAyMS0wMi0xMC10ZW1wbGF0ZV9yZXN1bHRzLykKCgojIEJsb2cgUmVmZXJlbmNlcwoKLSAgIEVtaWwgSHZpdGZlbGR0J3MgW0lTTFIgdGlkeW1vZGVscwogICAgTGFic10oaHR0cHM6Ly9lbWlsaHZpdGZlbGR0LmdpdGh1Yi5pby9JU0xSLXRpZHltb2RlbHMtbGFicy90cmVlLWJhc2VkLW1ldGhvZHMuaHRtbCkKCi0gICBKdWxpYSBTaWxnZSdzCiAgICBbYmxvZ10oaHR0cHM6Ly9qdWxpYXNpbGdlLmNvbS9ibG9nL3Njb29ieS1kb28uaHRtbCkgdGl0bGVkCiAgICAiUHJlZGljdCB3aGljaCAjVGlkeVR1ZXNkYXkgU2Nvb2J5IERvbyBtb25zdGVycyBhcmUgUkVBTCB3aXRoIGEgdHVuZWQgZGVjaXNpb24gdHJlZSBtb2RlbCIKCi0gICBKdWxpYSBTaWxnZSdzIFtibG9nXShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvd2luZC10dXJiaW5lLmh0bWwpIHRpdGxlZAogICAgIlR1bmUgYW5kIGludGVycHJldCBkZWNpc2lvbiB0cmVlcyBmb3IgI1RpZHlUdWVzZGF5IHdpbmQgdHVyYmluZXMiCiAgICAKLSAgIEp1bGlhIFNpbGdlJ3MgW2Jsb2ddKGh0dHBzOi8vanVsaWFzaWxnZS5jb20vYmxvZy94Z2Jvb3N0LXR1bmUtdm9sbGV5YmFsbC5odG1sKSB0aXRsZWQKICAgICJUdW5lIFhHQm9vc3Qgd2l0aCB0aWR5bW9kZWxzIGFuZCAjVGlkeVR1ZXNkYXkgYmVhY2ggdm9sbGV5YmFsbCIKICAgIAotICAgUi1ibG9nZ2VycycgW2Jsb2ddKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tLzIwMTkvMTAvZXhwbGFpbmluZy1wcmVkaWN0aW9ucy1ib29zdGVkLXRyZWVzLXBvc3QtaG9jLWFuYWx5c2lzLXhnYm9vc3QvKSB0aXRsZWQgIkV4cGxhaW5pbmcgUHJlZGljdGlvbnM6IEJvb3N0ZWQgVHJlZXMgUG9zdC1ob2MgQW5hbHlzaXMgKFhnYm9vc3QpIgoKIyBQYWNrYWdlIFJlZmVyZW5jZXMKCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9VFJVRSwgcmVzdWx0cz0nYXNpcyd9CmdldF9jaXRhdGlvbiA8LSBmdW5jdGlvbihwYWNrYWdlX25hbWUpIHsKICB0cmFuc2Zvcm1fbmFtZSA8LSBwYWNrYWdlX25hbWUgJT4lIAogICAgY2l0YXRpb24oKSAlPiUgCiAgICBmb3JtYXQoc3R5bGU9InRleHQiKQogIHJldHVybih0cmFuc2Zvcm1fbmFtZVsxXSkKfSAKCnBhY2thZ2VzIDwtIGMoImJhc2UiLCJyYW5kb21Gb3Jlc3QiLCAidmlwIiwgImdncGxvdDIiLAogICAgICAgICAgICAgICJybWFya2Rvd24iLCAicmVwb3J0IiwgInNwIiwgIlJPQ1IiLAogICAgICAgICAgICAgICJkYmFydHMiLCAidGlkeXZlcnNlIiwgImtuaXRyIiwgIkNvbmZ1c2lvblRhYmxlUiIsCiAgICAgICAgICAgICAgImRhdGEudGFibGUiLCAiRGlhZ3JhbW1lUiIsICJEaWFncmFtbWVSc3ZnIiwgImRpYWxzIiwKICAgICAgICAgICAgICAiZGlzbW8iLCAiZG9QYXJhbGxlbCIsICJkcGx5ciIsICJlbWJhcmNhZGVybyIsCiAgICAgICAgICAgICAgImZvcmNhdHMiLCAiZm9yZWFjaCIsICJnZ3B1YnIiLCAiaHRtbHRvb2xzIiwKICAgICAgICAgICAgICAiSVNMUjIiLCAiaXRlcmF0b3JzIiwgIm1hZ3JpdHRyIiwgIm1hdHJpeFN0YXRzIiwKICAgICAgICAgICAgICAiTWV0cmljcyIsICJwYXJzbmlwIiwgInBhcnR0cmVlIiwgInBhdGNod29yayIsCiAgICAgICAgICAgICAgInB1cnJyIiwgInJhc3RlciIsICJyZWFjdGFibGUiLCAicmVhZHIiLAogICAgICAgICAgICAgICJyZWNpcGVzIiwgInJwYXJ0IiwgInJwYXJ0LnBsb3QiLCAicnNhbXBsZSIsCiAgICAgICAgICAgICAgInJzdmciLCAic2NhbGVzIiwgInN0cmluZ3IiLCAic3R5bGVyIiwKICAgICAgICAgICAgICAidGliYmxlIiwgInRpZHlyIiwgInR1bmUiLCAid29ya2Zsb3dzIiwKICAgICAgICAgICAgICAieGFyaW5nYW5FeHRyYSIsICJ4Z2Jvb3N0IiwgInlhcmRzdGljayIsCiAgICAgICAgICAgICAgIkNrbWVhbnMuMWQuZHAiKQoKdGFibGUgPC0gdGliYmxlOjp0aWJibGUoUGFja2FnZXMgPSBwYWNrYWdlcykKCnRhYmxlICU+JQogIGRwbHlyOjptdXRhdGUoCiAgICB0cmFuc2Zvcm1fbmFtZSA9IHB1cnJyOjptYXBfY2hyKC5kYXRhW1siUGFja2FnZXMiXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdldF9jaXRhdGlvbikKICApICU+JSAKICBkcGx5cjo6cHVsbCguZGF0YVtbInRyYW5zZm9ybV9uYW1lIl1dKSAlPiUgCiAgcmVwb3J0Ojphcy5yZXBvcnRfcGFyYW1ldGVycygpCgpgYGAK