R Setup and Required
Packages
In this project, the open-source R programming language is used to
help understand the variability in classification metrics with class
imbalance, sample size, and different number of classes. R is maintained
by an international team of developers who make the language available
at The Comprehensive R Archive
Network. Readers interested in reusing our code and reproducing our
results should have R installed locally on their machines. R can be
installed on a number of different operating systems (see Windows, Mac, and Linux for the
installation instructions for these systems). We also recommend using
the RStudio interface for R. The reader can download RStudio for free by
following the instructions at the link. For non-R users, we recommend
the Hands-on
Programming with R for a brief overview of the software’s
functionality. Hereafter, we assume that the reader has an introductory
understanding of the R programming language.
In the code chunk below, we load the packages used to support our
analysis. Note that the code of this and any of the code chunks can be
hidden by clicking on the ‘Hide’ button to facilitate the navigation.
The reader can hide all code and/or download the Rmd file
associated with this document by clicking on the Code button on the top
right corner of this document. Our input and output files can
also be accessed/ downloaded from fmegahed/metric_interpretation.
# installing the pacman (PACkage MANager) package if not installed
if(require(pacman)==FALSE) install.packages("pacman")
# load (and if not installed, install) the needed packages
pacman::p_load(
tidyverse, # for data manipulation
caret, # for predictive performance metrics
plotly, # for interactive plots
DT # for nicely formatted tables
)
Preparing for the
Simulation
Simulation
Parameters
sim_num = 1:10^4 # setting the number of simulations per exp run to 10,000
num_classes = c(2, 3, 5, 10) # number of classes
num_observations = c(100, 10^3, 10^4, 10^5) # number of observations
first_class_perc = c(0.01, 0.05, 0.1, 0.2, (1/3), 0.5) # imbalance
pred_approach = c('uniform', 'proportional', 'most_frequent')
quant_of_interest = 0.99 # quantile of interest
Full Factorial
Setup
In the chunk below, we create a nested objected titled
sim_setup
, which is a tibble containing three
variables:
exp_num
a unique id for each combination of
num_classes
, num_observations
,
first_class_perc
and pred_approach
.
sim_num
, which has values from 1 to 10,000, repeating
for each ID (i.e., each value forexp_num
has
sim_num
from 1 to 10,000).
data
, which is list column, where each cell/row
contains a tibble of 1 row and the four variables
num_classes
, num_observations
,
first_class_perc
and pred_approach
.
Note that the data
column is the input used in our
custom sim_fun()
, which is shown/created in a subsequent
subsection.
# the full factorial setup
sim_setup =
# creating a tibble of all combinations of the five experimental conditions
expand_grid( sim_num, num_classes, num_observations, first_class_perc,
pred_approach) |>
# using group_by to create ids which are NOT dependent on the sim_num
group_by(num_classes, num_observations, first_class_perc, pred_approach) |>
# using the dplyr::cur_group_id() to create the IDs
mutate( exp_num = cur_group_id() ) |>
# ungrouping since it was only performed to create unique ids
ungroup() |>
# moving the exp_num to the beginning
relocate( exp_num ) |>
# creating list columns for all the columns with except of ID-type columns
nest(data = -c(exp_num, sim_num) ) |>
# sort the experimental data by exp_num
arrange(exp_num)
# saving the results as an rds file under the results folder
write_rds(
x = sim_setup, file = '../results/sim_setup.rds'
)
Custom Functions
g-mean
Calculation
The g_mean()
function below is used to compute the
geomteric mean of any two variables.
g_mean = function(x, y){
result = sqrt(x*y)
return(result)
}
Big Simulation
Function for Vectorized/Functional Programming
The sim_function()
is a custom vectorized function that
generates:
- the simulated observations, which are used to mimic
true
(aka reference) values used to measure the
classification performance
- simulated values for the predictions
The two vectors of observations and predictions are based on the
values inputted in the Simulation Paramaters
section (i.e.,
num_classes
, num_observations
,
first_class_perc
and pred_approach
).
The function returns seven classification metrics of interest:
- overall accuracy,
- sensitivity for the reference class
class_1
,
- specificity for the reference class
class_1
,
- the geometric mean of sensitivity and specificity,
- precision for the reference class
class_1
,
- recall for the reference class
class_1
, and
- f_measure for the reference class
class_1
.
sim_function = function(data = NULL, base_class_name = 'class_'){
# extracting the relevant features from the data
number_classes = data$num_classes
number_observations = data$num_observations
first_class_percentage = data$first_class_perc
prediction_approach = data$pred_approach
# generating the reference data
x = c(paste0(base_class_name, 1:number_classes))
ref = c(rep(x[1], number_observations*first_class_percentage),
rep(x[-1], each = number_observations*(1-first_class_percentage)/(number_classes-1)))
if (length(ref)!=number_classes) ref = c(ref, rep(x[number_classes], (number_observations-length(ref))))
ref = sample(ref, number_observations, replace=F) |> factor(levels = paste0(base_class_name, 1:number_classes) )
# generating the predictions
if(prediction_approach == 'uniform'){
predictions = sample(
x = c(paste0(base_class_name, 1:number_classes)),
size = number_observations,
replace = T,
prob = rep(1/number_classes, number_classes)
) |> factor(levels = paste0(base_class_name, 1:number_classes))
}else if(prediction_approach == 'proportional'){
predictions = sample(
x = c(paste0(base_class_name, 1:number_classes)),
size = number_observations,
replace = T,
prob = (table(ref)/length(ref))
) |> factor(levels = paste0(base_class_name, 1:number_classes))
} else{
prob = (table(ref)/length(ref))
predictions = rep(
paste0(base_class_name, which.max(prob)),
times = number_observations
) |> factor(levels = paste0(base_class_name, 1:number_classes) )
}
conf_matrix = caret::confusionMatrix(data = predictions, reference = ref)
# computing the acc metrics of interest
if (number_classes == 2){
acc_metrics = tibble(
accuracy = conf_matrix$overall['Accuracy'],
sensitivity = conf_matrix$byClass["Sensitivity"],
specificity = conf_matrix$byClass["Specificity"],
g_mean = g_mean(x = sensitivity, y = specificity),
precision = conf_matrix$byClass["Precision"],
recall = conf_matrix$byClass["Recall"],
f_measure = conf_matrix$byClass["F1"]
)
}else{
acc_metrics = tibble(
accuracy = conf_matrix$overall['Accuracy'],
sensitivity = conf_matrix$byClass[paste0("Class: ", base_class_name, 1), "Sensitivity"],
specificity = conf_matrix$byClass[paste0("Class: ", base_class_name, 1), "Specificity"],
g_mean = g_mean(x = sensitivity, y = specificity),
precision = conf_matrix$byClass[paste0("Class: ", base_class_name, 1), "Precision"],
recall = conf_matrix$byClass[paste0("Class: ", base_class_name, 1), "Recall"],
f_measure = conf_matrix$byClass[paste0("Class: ", base_class_name, 1), "F1"]
)
}
return(acc_metrics)
}
Running the
Experiment
With our sim_fun
, we can use a functional programming
approach to compute the five classification metrics of interest for each
simulation run. The results will be stored in an object titled
experimental_results
, which contains the 3 original columns
from sim_setup
and the new list column titled
acc_metrics
.
experimental_results =
sim_setup |># setup from a previous chunk
# creating a new list column titled acc_metrics, where the five classification
# metrics of choice are stored for each simulation run
# The map function from purrr (part of tidyverse) allows us to perform row-wise
# computations (i.e., for each combination of simulation parameters)
# on the `data` list column, where we use the variables within it to
# generate the random values and compute the classification metrics.
mutate(
acc_metrics = map(.x = data, .f = sim_function, base_class_name = 'class_')
)
# saving the experimental results
write_rds(
x = experimental_results, file = '../results/experimental_results.rds'
)
Summarizing the
Experimental Results
Below, we compute the means, standard deviations and quantiles for
each of the metrics, grouped by the ID (i.e., exp_num
). We
save the summary results into RDS and CSV formats (to facilitate their
consumption by both R and non-R users). In addition, we print a table of
the obtained results below.
summary_results =
experimental_results |>
# unnesting the acc_metrics (i.e., generating columns for each of the metrics)
unnest( acc_metrics ) |>
# grouping by exp_num
# (and data which is redundant but to keep it in the summary table)
group_by(exp_num, data) |>
# summaries of means, sds and quantile of interest for each metric by exp_num
summarise(
across(accuracy:f_measure,
list(mean = ~ mean(.x, na.rm = TRUE),
sd = ~ sd(.x, na.rm = TRUE),
quantile = ~ quantile(.x, probs = quant_of_interest, na.rm = TRUE) ),
.names = "{.fn}_{.col}" )
) |>
# expanding the data into the different experimental conditions for interpretation
unnest(data)
# storing the results as rds
write_rds(
x = summary_results, file = '../results/summary_results.rds'
)
# we also, store the results as CSV
write_csv(
x = summary_results, file = '../results/summary_results.csv'
)
# printing the results
DT::datatable(
summary_results |>mutate(across(where(is.numeric), round, 3)) |>
# moving and renaming columns purely for output presentation purposes
relocate(exp_num, num_classes, first_class_perc, pred_approach, num_observations) |>
rename(n_obs = num_observations,
ref_prob = first_class_perc,
pred = pred_approach,
mean_acc = mean_accuracy, sd_acc = sd_accuracy,
mean_sens = mean_sensitivity, sd_sens = sd_sensitivity,
mean_spec = mean_specificity, sd_spec = sd_specificity,
mean_prec = mean_precision, sd_prec = sd_precision,
mean_f_meas = mean_f_measure, sd_f_meas = sd_f_measure),
filter = 'top',
extensions = c('Buttons', 'FixedColumns'),
rownames = FALSE,
options = list(
initComplete = JS("function(settings, json) {$(this.api().table().container()).css({'font-size' : '10pt'});}"),
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
pageLength = 20,
scrollX = TRUE,
fixedColumns = list(leftColumns = 4)
)
)
The results can be downloaded from summary_results.csv.
App for Citizen Data
Scientists
We have created a flexdashboard-based
web application, which allows users to input the simulation parameters
of interest and output the corresponding simulation summary and
histograms of our five classification metrics of interest. The
application can be accessed here
and the code used to create the application can be downloaded from here.
Appendix
In this appendix, we print all the R packages used in our analysis
and their versions to assist with reproducing our results/analysis.
pacman::p_load(pander)
pander::pander(sessionInfo(), compact = TRUE) # printing the session info
R version 4.2.2 (2022-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
locale: LC_COLLATE=English_United
States.utf8, LC_CTYPE=English_United States.utf8,
LC_MONETARY=English_United States.utf8, LC_NUMERIC=C
and LC_TIME=English_United States.utf8
attached base packages: stats,
graphics, grDevices, utils,
datasets, methods and base
other attached packages: pander(v.0.6.5),
DT(v.0.27), plotly(v.4.10.1),
caret(v.6.0-93), lattice(v.0.20-45),
lubridate(v.1.9.2), forcats(v.1.0.0),
stringr(v.1.5.0), dplyr(v.1.1.0),
purrr(v.1.0.1), readr(v.2.1.4),
tidyr(v.1.3.0), tibble(v.3.2.0),
tidyverse(v.2.0.0), pacman(v.0.5.1),
RColorBrewer(v.1.1-3) and ggplot2(v.3.4.1)
loaded via a namespace (and not attached):
nlme(v.3.1-162), bit64(v.4.0.5),
httr(v.1.4.5), tools(v.4.2.2),
bslib(v.0.4.2), utf8(v.1.2.3), R6(v.2.5.1),
rpart(v.4.1.19), lazyeval(v.0.2.2),
colorspace(v.2.1-0), nnet(v.7.3-18),
withr(v.2.5.0), tidyselect(v.1.2.0),
bit(v.4.0.5), compiler(v.4.2.2),
cli(v.3.6.0), sass(v.0.4.5), scales(v.1.2.1),
proxy(v.0.4-27), digest(v.0.6.31),
rmarkdown(v.2.20), pkgconfig(v.2.0.3),
htmltools(v.0.5.4), parallelly(v.1.34.0),
fastmap(v.1.1.1), htmlwidgets(v.1.6.1),
rlang(v.1.0.6), rstudioapi(v.0.14),
jquerylib(v.0.1.4), generics(v.0.1.3),
jsonlite(v.1.8.4), crosstalk(v.1.2.0),
vroom(v.1.6.1), ModelMetrics(v.1.2.2.2),
magrittr(v.2.0.3), Matrix(v.1.5-3),
Rcpp(v.1.0.10), munsell(v.0.5.0),
fansi(v.1.0.4), lifecycle(v.1.0.3),
stringi(v.1.7.12), pROC(v.1.18.0),
yaml(v.2.3.7), MASS(v.7.3-58.3),
plyr(v.1.8.8), recipes(v.1.0.5),
grid(v.4.2.2), parallel(v.4.2.2),
listenv(v.0.9.0), crayon(v.1.5.2),
splines(v.4.2.2), hms(v.1.1.2),
knitr(v.1.42), pillar(v.1.8.1),
future.apply(v.1.10.0), reshape2(v.1.4.4),
codetools(v.0.2-19), stats4(v.4.2.2),
glue(v.1.6.2), evaluate(v.0.20),
data.table(v.1.14.8), vctrs(v.0.5.2),
tzdb(v.0.3.0), foreach(v.1.5.2),
gtable(v.0.3.1), future(v.1.32.0),
cachem(v.1.0.7), xfun(v.0.37),
gower(v.1.0.1), prodlim(v.2019.11.13),
e1071(v.1.7-13), class(v.7.3-21),
survival(v.3.5-3), viridisLite(v.0.4.1),
timeDate(v.4022.108), iterators(v.1.0.14),
hardhat(v.1.2.0), lava(v.1.7.2.1),
timechange(v.0.2.0), globals(v.0.16.2),
ellipsis(v.0.3.2) and ipred(v.0.9-14)
LS0tDQp0aXRsZTogIlRoZSBWYXJpYWJpbGl0eSBpbiBDb21tb25seSBVc2VkIENsYXNzaWZpY2F0aW9uIE1ldHJpY3Mgd2l0aCBDbGFzcyBJbWJhbGFuY2UiDQphdXRob3I6DQogIC0gbmFtZTogIkZhZGVsIE0uIE1lZ2FoZWQgXltFbWFpbDogZm1lZ2FoZWRAbWlhbWlvaC5lZHUgfCBQaG9uZTogKzEtNTEzLTUyOS00MTg1IHwgV2Vic2l0ZTogPGEgaHJlZj1cImh0dHBzOi8vbWlhbWlvaC5lZHUvZnNiL2RpcmVjdG9yeS8/dXA9L2RpcmVjdG9yeS9tZWdhaGVmbVwiPk1pYW1pIFVuaXZlcnNpdHkgT2ZmaWNpYWw8L2E+XSINCiAgICBhZmZpbGlhdGlvbjogRmFybWVyIFNjaG9vbCBvZiBCdXNpbmVzcywgTWlhbWkgVW5pdmVyc2l0eQ0KICAtIG5hbWU6ICJZaW5nLUp1IChUZXNzYSkgQ2hlbiBeW0VtYWlsOiB5Y2hlbjRAdWRheXRvbi5lZHUgfCBQaG9uZTogKzEtOTM3LTIyOS0yNDA1IHwgV2Vic2l0ZTogPGEgaHJlZj1cImh0dHA6Ly9zaXRlcy51ZGF5dG9uLmVkdS95Y2hlbjRcIj5Vbml2ZXJzaXR5IG9mIERheXRvbiBPZmZpY2lhbDwvYT5dIg0KICAgIGFmZmlsaWF0aW9uOiBEZXBhcnRtZW50IG9mIE1hdGhlbWF0aWNzLCBVbml2ZXJzaXR5IG9mIERheXRvbg0KICAtIG5hbWU6ICJBbGxpc29uIEpvbmVzLUZhcm1lciBeW0VtYWlsOiBmYXJtZXJsMkBtaWFtaW9oLmVkdSB8IFBob25lOiArMS01MTMtNTI5LTQ4MjMgfCBXZWJzaXRlOiA8YSBocmVmPVwiaHR0cHM6Ly9taWFtaW9oLmVkdS9mc2IvZGlyZWN0b3J5Lz91cD0vZGlyZWN0b3J5L2Zhcm1lcmwyXCI+TWlhbWkgVW5pdmVyc2l0eSBPZmZpY2lhbDwvYT5dIg0KICAgIGFmZmlsaWF0aW9uOiBGYXJtZXIgU2Nob29sIG9mIEJ1c2luZXNzLCBNaWFtaSBVbml2ZXJzaXR5DQogIC0gbmFtZTogIlN0ZXZlIFJpZ2RvbiBeW0VtYWlsOiBzdGV2ZS5yaWdkb25Ac2x1LmVkdSB8IFdlYnNpdGU6IDxhIGhyZWY9XCJodHRwczovL3d3dy5zbHUuZWR1L3B1YmxpYy1oZWFsdGgtc29jaWFsLWp1c3RpY2UvZmFjdWx0eS9yaWdkb24tc3RldmVuLnBocFwiPlNhaW50IExvdWlzIFVuaXZlcnNpdHkgT2ZmaWNpYWw8L2E+XSINCiAgICBhZmZpbGlhdGlvbjogQ29sbGVnZSBvZiAgUHVibGljIEhlYWx0aCBhbmQgU29jaWFsIEp1c3RpY2UsIFNhaW50IExvdWlzIFVuaXZlcnNpdHkNCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBjdXN0b20uY3NzDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIG51bWJlcl9zZWN0aW9uczogVFJVRQ0KICAgIHBhZ2VkX2RmOiBUUlVFDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgdGhlbWU6IHJlYWRhYmxlDQogIGluY2x1ZGVzOg0KICAgIGluX2hlYWRlcjogc3RydWN0dXJlLnRleA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICBlY2hvID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBkZXYgPSBjKCdwbmcnLCAncGRmJywgJ3RpZmYnKSwNCiAgICAgICAgICAgICAgICAgICAgICBmaWcucmV0aW5hID0gMiwNCiAgICAgICAgICAgICAgICAgICAgICBvdXQud2lkdGggPSAnMTAwJScsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFzcCA9IDAuNykNCg0Kb3B0aW9ucyhxd3JhcHMyX21hcmt1cCA9ICJtYXJrZG93biIpDQoNCiMgU2V0dGluZyBwcm9wZXJ0aWVzIGZvciB0aGUgZGVmYXVsdCB0aGVtZV9idygpIGJlaGF2aW9yIGZvciBhbGwgcGxvdHMNCmlmKHJlcXVpcmUoZ2dwbG90MikgPT0gRkFMU0UpIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KbGlicmFyeShnZ3Bsb3QyKSA7IHRoZW1lX3NldCh0aGVtZV9idyhiYXNlX3NpemUgPSAxMSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAndG9wJykpIA0KDQojIFNldHRpbmcgZGVmYXVsdCBjb2xvciBwYWxldHRlcyB0byBSQ29sb3JCcmV3ZXIgUGFsZXR0ZXMNCmlmKHJlcXVpcmUoUkNvbG9yQnJld2VyKSA9PSBGQUxTRSkgaW5zdGFsbC5wYWNrYWdlcygiUkNvbG9yQnJld2VyIikNCnNjYWxlX2NvbG91cl9kaXNjcmV0ZSA9IHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlBhaXJlZCIpDQoNCiMgU2V0dGluZyB0aGUgcmFuZG9tIHNlZWQgYW5kIGNodW5rIGRlcGVuZGVuY2llcw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlLmV4dHJhID0gc2V0LnNlZWQoMjAyMyksDQogICAgICAgICAgICAgICAgICAgICAgYXV0b2RlcCA9IFRSVUUpIA0Ka25pdHI6OmRlcF9hdXRvKCkNCmBgYA0KDQojIFIgU2V0dXAgYW5kIFJlcXVpcmVkIFBhY2thZ2VzDQpJbiB0aGlzIHByb2plY3QsIHRoZSBvcGVuLXNvdXJjZSBSIHByb2dyYW1taW5nIGxhbmd1YWdlIGlzIHVzZWQgdG8gaGVscCB1bmRlcnN0YW5kIHRoZSB2YXJpYWJpbGl0eSBpbiBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzIHdpdGggY2xhc3MgaW1iYWxhbmNlLCBzYW1wbGUgc2l6ZSwgYW5kIGRpZmZlcmVudCBudW1iZXIgb2YgY2xhc3Nlcy4gUiBpcyBtYWludGFpbmVkIGJ5IGFuIGludGVybmF0aW9uYWwgdGVhbSBvZiBkZXZlbG9wZXJzIHdobyBtYWtlIHRoZSBsYW5ndWFnZSBhdmFpbGFibGUgYXQgW1RoZSBDb21wcmVoZW5zaXZlIFIgQXJjaGl2ZSBOZXR3b3JrXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pLiBSZWFkZXJzIGludGVyZXN0ZWQgaW4gcmV1c2luZyBvdXIgY29kZSBhbmQgcmVwcm9kdWNpbmcgb3VyIHJlc3VsdHMgc2hvdWxkIGhhdmUgUiBpbnN0YWxsZWQgbG9jYWxseSBvbiB0aGVpciBtYWNoaW5lcy4gUiBjYW4gYmUgaW5zdGFsbGVkIG9uIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBvcGVyYXRpbmcgc3lzdGVtcyAoc2VlIFtXaW5kb3dzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9iaW4vd2luZG93cy8pLCBbTWFjXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9iaW4vbWFjb3N4LyksIGFuZCBbTGludXhdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2Jpbi9saW51eC8pIGZvciB0aGUgaW5zdGFsbGF0aW9uIGluc3RydWN0aW9ucyBmb3IgdGhlc2Ugc3lzdGVtcykuIFdlIGFsc28gcmVjb21tZW5kIHVzaW5nIHRoZSBSU3R1ZGlvIGludGVyZmFjZSBmb3IgUi4gVGhlIHJlYWRlciBjYW4gW2Rvd25sb2FkIFJTdHVkaW9dKGh0dHA6Ly93d3cucnN0dWRpby5jb20vaWRlKSBmb3IgZnJlZSBieSBmb2xsb3dpbmcgdGhlIGluc3RydWN0aW9ucyBhdCB0aGUgbGluay4gRm9yIG5vbi1SIHVzZXJzLCB3ZSByZWNvbW1lbmQgdGhlIFtIYW5kcy1vbiBQcm9ncmFtbWluZyB3aXRoIFJdKGh0dHBzOi8vcnN0dWRpby1lZHVjYXRpb24uZ2l0aHViLmlvL2hvcHIvcGFja2FnZXMuaHRtbCkgZm9yIGEgYnJpZWYgb3ZlcnZpZXcgb2YgdGhlIHNvZnR3YXJlJ3MgZnVuY3Rpb25hbGl0eS4gSGVyZWFmdGVyLCB3ZSBhc3N1bWUgdGhhdCB0aGUgcmVhZGVyIGhhcyBhbiBpbnRyb2R1Y3RvcnkgdW5kZXJzdGFuZGluZyBvZiB0aGUgUiBwcm9ncmFtbWluZyBsYW5ndWFnZS4NCg0KSW4gdGhlIGNvZGUgY2h1bmsgYmVsb3csIHdlIGxvYWQgdGhlIHBhY2thZ2VzIHVzZWQgdG8gc3VwcG9ydCBvdXIgYW5hbHlzaXMuIE5vdGUgdGhhdCB0aGUgY29kZSBvZiB0aGlzIGFuZCBhbnkgb2YgdGhlIGNvZGUgY2h1bmtzIGNhbiBiZSBoaWRkZW4gYnkgY2xpY2tpbmcgb24gdGhlICdIaWRlJyBidXR0b24gdG8gZmFjaWxpdGF0ZSB0aGUgbmF2aWdhdGlvbi4gKipUaGUgcmVhZGVyIGNhbiBoaWRlIGFsbCBjb2RlIGFuZC9vciBkb3dubG9hZCB0aGUgUm1kIGZpbGUgYXNzb2NpYXRlZCB3aXRoIHRoaXMgZG9jdW1lbnQgYnkgY2xpY2tpbmcgb24gdGhlIENvZGUgYnV0dG9uIG9uIHRoZSB0b3AgcmlnaHQgY29ybmVyIG9mIHRoaXMgZG9jdW1lbnQuKiogT3VyIGlucHV0IGFuZCBvdXRwdXQgZmlsZXMgY2FuIGFsc28gYmUgYWNjZXNzZWQvIGRvd25sb2FkZWQgZnJvbSBbZm1lZ2FoZWQvbWV0cmljX2ludGVycHJldGF0aW9uXShodHRwczovL2dpdGh1Yi5jb20vZm1lZ2FoZWQvbWV0cmljX2ludGVycHJldGF0aW9uKS4NCg0KDQpgYGB7ciBwYWNrYWdlc30NCiMgaW5zdGFsbGluZyB0aGUgcGFjbWFuIChQQUNrYWdlIE1BTmFnZXIpIHBhY2thZ2UgaWYgbm90IGluc3RhbGxlZCANCmlmKHJlcXVpcmUocGFjbWFuKT09RkFMU0UpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQoNCiMgbG9hZCAoYW5kIGlmIG5vdCBpbnN0YWxsZWQsIGluc3RhbGwpIHRoZSBuZWVkZWQgcGFja2FnZXMNCnBhY21hbjo6cF9sb2FkKA0KICB0aWR5dmVyc2UsICMgZm9yIGRhdGEgbWFuaXB1bGF0aW9uDQogIGNhcmV0LCAjIGZvciBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG1ldHJpY3MNCiAgcGxvdGx5LCAjIGZvciBpbnRlcmFjdGl2ZSBwbG90cw0KICBEVCAjIGZvciBuaWNlbHkgZm9ybWF0dGVkIHRhYmxlcw0KKSANCg0KYGBgDQoNCi0tLQ0KDQojIFByZXBhcmluZyBmb3IgdGhlIFNpbXVsYXRpb24NCg0KDQojIyBTaW11bGF0aW9uIFBhcmFtZXRlcnMNCg0KYGBge3Igc2ltX3BhcmFtYXRlcnN9DQoNCnNpbV9udW0gPSAxOjEwXjQgIyBzZXR0aW5nIHRoZSBudW1iZXIgb2Ygc2ltdWxhdGlvbnMgcGVyIGV4cCBydW4gdG8gMTAsMDAwDQoNCm51bV9jbGFzc2VzID0gYygyLCAzLCA1LCAxMCkgIyBudW1iZXIgb2YgY2xhc3Nlcw0KDQpudW1fb2JzZXJ2YXRpb25zID0gYygxMDAsIDEwXjMsIDEwXjQsIDEwXjUpICMgbnVtYmVyIG9mIG9ic2VydmF0aW9ucw0KDQpmaXJzdF9jbGFzc19wZXJjID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMiwgKDEvMyksIDAuNSkgIyBpbWJhbGFuY2UNCg0KcHJlZF9hcHByb2FjaCA9IGMoJ3VuaWZvcm0nLCAncHJvcG9ydGlvbmFsJywgJ21vc3RfZnJlcXVlbnQnKQ0KDQpxdWFudF9vZl9pbnRlcmVzdCA9IDAuOTkgIyBxdWFudGlsZSBvZiBpbnRlcmVzdA0KYGBgDQoNCg0KIyMgRnVsbCBGYWN0b3JpYWwgU2V0dXANCg0KSW4gdGhlIGNodW5rIGJlbG93LCB3ZSBjcmVhdGUgYSBuZXN0ZWQgb2JqZWN0ZWQgdGl0bGVkIGBzaW1fc2V0dXBgLCB3aGljaCBpcyBhIHRpYmJsZSBjb250YWluaW5nIHRocmVlIHZhcmlhYmxlczogIA0KDQogIDEuIGBleHBfbnVtYCBhIHVuaXF1ZSBpZCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbnVtX2NsYXNzZXNgLCBgbnVtX29ic2VydmF0aW9uc2AsIGBmaXJzdF9jbGFzc19wZXJjYCBhbmQgYHByZWRfYXBwcm9hY2hgLiAgDQogIDIuIGBzaW1fbnVtYCwgd2hpY2ggaGFzIHZhbHVlcyBmcm9tIDEgdG8gYHIgc2NhbGVzOjpjb21tYSggbWF4KHNpbV9udW0pIClgLCByZXBlYXRpbmcgZm9yIGVhY2ggSUQgKGkuZS4sIGVhY2ggdmFsdWUgZm9yYGV4cF9udW1gIGhhcyBgc2ltX251bWAgZnJvbSAgMSB0byBgciBzY2FsZXM6OmNvbW1hKCBtYXgoc2ltX251bSkgKSBgKS4gIA0KICAzLiBgZGF0YWAsIHdoaWNoIGlzIGxpc3QgY29sdW1uLCB3aGVyZSBlYWNoIGNlbGwvcm93IGNvbnRhaW5zIGEgdGliYmxlIG9mIDEgcm93IGFuZCB0aGUgZm91ciB2YXJpYWJsZXMgYG51bV9jbGFzc2VzYCwgYG51bV9vYnNlcnZhdGlvbnNgLCBgZmlyc3RfY2xhc3NfcGVyY2AgYW5kIGBwcmVkX2FwcHJvYWNoYC4gICANCg0KTm90ZSB0aGF0IHRoZSBgZGF0YWAgY29sdW1uIGlzIHRoZSBpbnB1dCB1c2VkIGluIG91ciBjdXN0b20gYHNpbV9mdW4oKWAsIHdoaWNoIGlzIHNob3duL2NyZWF0ZWQgaW4gYSBzdWJzZXF1ZW50IHN1YnNlY3Rpb24uIA0KDQpgYGB7ciBmdWxsX2ZhY3RvcmlhbF9zZXR1cCwgZXZhbD1GQUxTRX0NCiMgdGhlIGZ1bGwgZmFjdG9yaWFsIHNldHVwDQpzaW1fc2V0dXAgPSANCiAgIyBjcmVhdGluZyBhIHRpYmJsZSBvZiBhbGwgY29tYmluYXRpb25zIG9mIHRoZSBmaXZlIGV4cGVyaW1lbnRhbCBjb25kaXRpb25zDQogIGV4cGFuZF9ncmlkKCBzaW1fbnVtLCBudW1fY2xhc3NlcywgbnVtX29ic2VydmF0aW9ucywgZmlyc3RfY2xhc3NfcGVyYywNCiAgICAgICAgICAgICAgIHByZWRfYXBwcm9hY2gpIHw+DQogICMgdXNpbmcgZ3JvdXBfYnkgdG8gY3JlYXRlIGlkcyB3aGljaCBhcmUgTk9UIGRlcGVuZGVudCBvbiB0aGUgc2ltX251bQ0KICBncm91cF9ieShudW1fY2xhc3NlcywgbnVtX29ic2VydmF0aW9ucywgZmlyc3RfY2xhc3NfcGVyYywgcHJlZF9hcHByb2FjaCkgfD4NCiAgIyB1c2luZyB0aGUgZHBseXI6OmN1cl9ncm91cF9pZCgpIHRvIGNyZWF0ZSB0aGUgSURzDQogIG11dGF0ZSggZXhwX251bSA9IGN1cl9ncm91cF9pZCgpICkgfD4NCiAgIyB1bmdyb3VwaW5nIHNpbmNlIGl0IHdhcyBvbmx5IHBlcmZvcm1lZCB0byBjcmVhdGUgdW5pcXVlIGlkcw0KICB1bmdyb3VwKCkgfD4NCiAgIyBtb3ZpbmcgdGhlIGV4cF9udW0gdG8gdGhlIGJlZ2lubmluZw0KICByZWxvY2F0ZSggZXhwX251bSApIHw+DQogICMgY3JlYXRpbmcgbGlzdCBjb2x1bW5zIGZvciBhbGwgdGhlIGNvbHVtbnMgd2l0aCBleGNlcHQgb2YgSUQtdHlwZSBjb2x1bW5zDQogIG5lc3QoZGF0YSA9IC1jKGV4cF9udW0sIHNpbV9udW0pICkgfD4NCiAgIyBzb3J0IHRoZSBleHBlcmltZW50YWwgZGF0YSBieSBleHBfbnVtDQogIGFycmFuZ2UoZXhwX251bSkNCg0KDQojIHNhdmluZyB0aGUgcmVzdWx0cyBhcyBhbiByZHMgZmlsZSB1bmRlciB0aGUgcmVzdWx0cyBmb2xkZXINCndyaXRlX3JkcygNCiAgeCA9IHNpbV9zZXR1cCwgZmlsZSA9ICcuLi9yZXN1bHRzL3NpbV9zZXR1cC5yZHMnDQopDQpgYGANCg0KIyMgQ3VzdG9tIEZ1bmN0aW9ucw0KDQojIyMgZy1tZWFuIENhbGN1bGF0aW9uDQoNClRoZSBgZ19tZWFuKClgIGZ1bmN0aW9uIGJlbG93IGlzIHVzZWQgdG8gY29tcHV0ZSB0aGUgZ2VvbXRlcmljIG1lYW4gb2YgYW55IHR3byB2YXJpYWJsZXMuDQoNCmBgYHtyIGdfbWVhbl9jb21wfQ0KZ19tZWFuID0gZnVuY3Rpb24oeCwgeSl7DQogIA0KICByZXN1bHQgPSBzcXJ0KHgqeSkNCiAgDQogIHJldHVybihyZXN1bHQpDQp9DQoNCmBgYA0KDQoNCiMjIyBCaWcgU2ltdWxhdGlvbiBGdW5jdGlvbiBmb3IgVmVjdG9yaXplZC9GdW5jdGlvbmFsIFByb2dyYW1taW5nDQoNClRoZSBgc2ltX2Z1bmN0aW9uKClgIGlzIGEgY3VzdG9tIHZlY3Rvcml6ZWQgZnVuY3Rpb24gdGhhdCBnZW5lcmF0ZXM6ICAgDQoNCiAgYS4gdGhlIHNpbXVsYXRlZCBvYnNlcnZhdGlvbnMsIHdoaWNoIGFyZSB1c2VkIHRvIG1pbWljIGB0cnVlYCAoYWthIHJlZmVyZW5jZSkgdmFsdWVzIHVzZWQgdG8gbWVhc3VyZSB0aGUgY2xhc3NpZmljYXRpb24gcGVyZm9ybWFuY2UgIA0KICBiLiBzaW11bGF0ZWQgdmFsdWVzIGZvciB0aGUgcHJlZGljdGlvbnMgIA0KDQpUaGUgdHdvIHZlY3RvcnMgb2Ygb2JzZXJ2YXRpb25zIGFuZCBwcmVkaWN0aW9ucyBhcmUgYmFzZWQgb24gdGhlIHZhbHVlcyBpbnB1dHRlZCBpbiB0aGUgYFNpbXVsYXRpb24gUGFyYW1hdGVyc2Agc2VjdGlvbiAoaS5lLiwgYG51bV9jbGFzc2VzYCwgYG51bV9vYnNlcnZhdGlvbnNgLCBgZmlyc3RfY2xhc3NfcGVyY2AgYW5kIGBwcmVkX2FwcHJvYWNoYCkuIA0KDQpUaGUgZnVuY3Rpb24gcmV0dXJucyBzZXZlbiBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzIG9mIGludGVyZXN0OiAgIA0KDQogIDEuIG92ZXJhbGwgYWNjdXJhY3ksICAgDQogIDIuIHNlbnNpdGl2aXR5IGZvciB0aGUgcmVmZXJlbmNlIGNsYXNzIGBjbGFzc18xYCwgICANCiAgMy4gc3BlY2lmaWNpdHkgZm9yIHRoZSByZWZlcmVuY2UgY2xhc3MgYGNsYXNzXzFgLCAgDQogIDQuIHRoZSBnZW9tZXRyaWMgbWVhbiBvZiBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHksICAgDQogIDUuIHByZWNpc2lvbiBmb3IgdGhlIHJlZmVyZW5jZSBjbGFzcyBgY2xhc3NfMWAsICAgDQogIDYuIHJlY2FsbCBmb3IgdGhlIHJlZmVyZW5jZSBjbGFzcyBgY2xhc3NfMWAsIGFuZCAgICAgDQogIDcuIGZfbWVhc3VyZSBmb3IgdGhlIHJlZmVyZW5jZSBjbGFzcyBgY2xhc3NfMWAuDQoNCmBgYHtyIGJpZ19zaW1fZnVufQ0Kc2ltX2Z1bmN0aW9uID0gZnVuY3Rpb24oZGF0YSA9IE5VTEwsIGJhc2VfY2xhc3NfbmFtZSA9ICdjbGFzc18nKXsNCiAgDQogICMgZXh0cmFjdGluZyB0aGUgcmVsZXZhbnQgZmVhdHVyZXMgZnJvbSB0aGUgZGF0YSANCiAgbnVtYmVyX2NsYXNzZXMgPSBkYXRhJG51bV9jbGFzc2VzDQogIG51bWJlcl9vYnNlcnZhdGlvbnMgPSBkYXRhJG51bV9vYnNlcnZhdGlvbnMNCiAgZmlyc3RfY2xhc3NfcGVyY2VudGFnZSA9IGRhdGEkZmlyc3RfY2xhc3NfcGVyYw0KICBwcmVkaWN0aW9uX2FwcHJvYWNoID0gZGF0YSRwcmVkX2FwcHJvYWNoDQoNCg0KICAjIGdlbmVyYXRpbmcgdGhlIHJlZmVyZW5jZSBkYXRhDQogIHggPSBjKHBhc3RlMChiYXNlX2NsYXNzX25hbWUsIDE6bnVtYmVyX2NsYXNzZXMpKQ0KICByZWYgPSBjKHJlcCh4WzFdLCBudW1iZXJfb2JzZXJ2YXRpb25zKmZpcnN0X2NsYXNzX3BlcmNlbnRhZ2UpLA0KICAgICAgICAgIHJlcCh4Wy0xXSwgZWFjaCA9IG51bWJlcl9vYnNlcnZhdGlvbnMqKDEtZmlyc3RfY2xhc3NfcGVyY2VudGFnZSkvKG51bWJlcl9jbGFzc2VzLTEpKSkNCiAgDQogIGlmIChsZW5ndGgocmVmKSE9bnVtYmVyX2NsYXNzZXMpIHJlZiA9IGMocmVmLCByZXAoeFtudW1iZXJfY2xhc3Nlc10sIChudW1iZXJfb2JzZXJ2YXRpb25zLWxlbmd0aChyZWYpKSkpDQogIA0KICByZWYgPSBzYW1wbGUocmVmLCBudW1iZXJfb2JzZXJ2YXRpb25zLCByZXBsYWNlPUYpIHw+IGZhY3RvcihsZXZlbHMgPSBwYXN0ZTAoYmFzZV9jbGFzc19uYW1lLCAxOm51bWJlcl9jbGFzc2VzKSApDQogIA0KICAjIGdlbmVyYXRpbmcgdGhlIHByZWRpY3Rpb25zDQogIGlmKHByZWRpY3Rpb25fYXBwcm9hY2ggPT0gJ3VuaWZvcm0nKXsNCiAgICBwcmVkaWN0aW9ucyA9IHNhbXBsZSgNCiAgICAgIHggPSBjKHBhc3RlMChiYXNlX2NsYXNzX25hbWUsIDE6bnVtYmVyX2NsYXNzZXMpKSwNCiAgICAgIHNpemUgPSBudW1iZXJfb2JzZXJ2YXRpb25zLA0KICAgICAgcmVwbGFjZSA9IFQsDQogICAgICBwcm9iID0gcmVwKDEvbnVtYmVyX2NsYXNzZXMsIG51bWJlcl9jbGFzc2VzKSANCiAgICApIHw+IGZhY3RvcihsZXZlbHMgPSBwYXN0ZTAoYmFzZV9jbGFzc19uYW1lLCAxOm51bWJlcl9jbGFzc2VzKSkNCiAgfWVsc2UgaWYocHJlZGljdGlvbl9hcHByb2FjaCA9PSAncHJvcG9ydGlvbmFsJyl7DQogICAgcHJlZGljdGlvbnMgPSBzYW1wbGUoDQogICAgICB4ID0gYyhwYXN0ZTAoYmFzZV9jbGFzc19uYW1lLCAxOm51bWJlcl9jbGFzc2VzKSksDQogICAgICBzaXplID0gbnVtYmVyX29ic2VydmF0aW9ucywNCiAgICAgIHJlcGxhY2UgPSBULA0KICAgICAgcHJvYiA9ICh0YWJsZShyZWYpL2xlbmd0aChyZWYpKQ0KICAgICkgfD4gZmFjdG9yKGxldmVscyA9IHBhc3RlMChiYXNlX2NsYXNzX25hbWUsIDE6bnVtYmVyX2NsYXNzZXMpKQ0KICB9IGVsc2V7DQogICAgcHJvYiA9ICh0YWJsZShyZWYpL2xlbmd0aChyZWYpKQ0KICAgIHByZWRpY3Rpb25zID0gcmVwKA0KICAgICAgcGFzdGUwKGJhc2VfY2xhc3NfbmFtZSwgd2hpY2gubWF4KHByb2IpKSwNCiAgICAgIHRpbWVzID0gbnVtYmVyX29ic2VydmF0aW9ucw0KICAgICkgfD4gZmFjdG9yKGxldmVscyA9IHBhc3RlMChiYXNlX2NsYXNzX25hbWUsIDE6bnVtYmVyX2NsYXNzZXMpICkNCiAgfQ0KICANCiAgY29uZl9tYXRyaXggPSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KGRhdGEgPSBwcmVkaWN0aW9ucywgcmVmZXJlbmNlID0gcmVmKQ0KICANCiAgIyBjb21wdXRpbmcgdGhlIGFjYyBtZXRyaWNzIG9mIGludGVyZXN0DQogIGlmIChudW1iZXJfY2xhc3NlcyA9PSAyKXsNCiAgICBhY2NfbWV0cmljcyA9IHRpYmJsZSgNCiAgICAgIGFjY3VyYWN5ID0gY29uZl9tYXRyaXgkb3ZlcmFsbFsnQWNjdXJhY3knXSwNCiAgICAgIHNlbnNpdGl2aXR5ID0gY29uZl9tYXRyaXgkYnlDbGFzc1siU2Vuc2l0aXZpdHkiXSwNCiAgICAgIHNwZWNpZmljaXR5ID0gY29uZl9tYXRyaXgkYnlDbGFzc1siU3BlY2lmaWNpdHkiXSwNCiAgICAgIGdfbWVhbiA9IGdfbWVhbih4ID0gc2Vuc2l0aXZpdHksIHkgPSBzcGVjaWZpY2l0eSksDQogICAgICBwcmVjaXNpb24gPSBjb25mX21hdHJpeCRieUNsYXNzWyJQcmVjaXNpb24iXSwNCiAgICAgIHJlY2FsbCA9IGNvbmZfbWF0cml4JGJ5Q2xhc3NbIlJlY2FsbCJdLA0KICAgICAgZl9tZWFzdXJlID0gY29uZl9tYXRyaXgkYnlDbGFzc1siRjEiXQ0KICAgICkNCiAgfWVsc2V7DQogIGFjY19tZXRyaWNzID0gdGliYmxlKA0KICAgIGFjY3VyYWN5ID0gY29uZl9tYXRyaXgkb3ZlcmFsbFsnQWNjdXJhY3knXSwNCiAgICBzZW5zaXRpdml0eSA9IGNvbmZfbWF0cml4JGJ5Q2xhc3NbcGFzdGUwKCJDbGFzczogIiwgYmFzZV9jbGFzc19uYW1lLCAxKSwgIlNlbnNpdGl2aXR5Il0sDQogICAgc3BlY2lmaWNpdHkgPSBjb25mX21hdHJpeCRieUNsYXNzW3Bhc3RlMCgiQ2xhc3M6ICIsIGJhc2VfY2xhc3NfbmFtZSwgMSksICJTcGVjaWZpY2l0eSJdLA0KICAgIGdfbWVhbiA9IGdfbWVhbih4ID0gc2Vuc2l0aXZpdHksIHkgPSBzcGVjaWZpY2l0eSksDQogICAgcHJlY2lzaW9uID0gY29uZl9tYXRyaXgkYnlDbGFzc1twYXN0ZTAoIkNsYXNzOiAiLCBiYXNlX2NsYXNzX25hbWUsIDEpLCAiUHJlY2lzaW9uIl0sDQogICAgcmVjYWxsID0gY29uZl9tYXRyaXgkYnlDbGFzc1twYXN0ZTAoIkNsYXNzOiAiLCBiYXNlX2NsYXNzX25hbWUsIDEpLCAiUmVjYWxsIl0sDQogICAgZl9tZWFzdXJlID0gY29uZl9tYXRyaXgkYnlDbGFzc1twYXN0ZTAoIkNsYXNzOiAiLCBiYXNlX2NsYXNzX25hbWUsIDEpLCAiRjEiXQ0KICApDQogIH0NCiAgcmV0dXJuKGFjY19tZXRyaWNzKQ0KfQ0KYGBgDQoNCg0KLS0tDQoNCiMgUnVubmluZyB0aGUgRXhwZXJpbWVudA0KDQpXaXRoIG91ciBgc2ltX2Z1bmAsIHdlIGNhbiB1c2UgYSBmdW5jdGlvbmFsIHByb2dyYW1taW5nIGFwcHJvYWNoIHRvIGNvbXB1dGUgdGhlIGZpdmUgY2xhc3NpZmljYXRpb24gbWV0cmljcyBvZiBpbnRlcmVzdCBmb3IgZWFjaCBzaW11bGF0aW9uIHJ1bi4gVGhlIHJlc3VsdHMgd2lsbCBiZSBzdG9yZWQgaW4gYW4gb2JqZWN0IHRpdGxlZCBgZXhwZXJpbWVudGFsX3Jlc3VsdHNgLCB3aGljaCBjb250YWlucyB0aGUgMyBvcmlnaW5hbCBjb2x1bW5zIGZyb20gYHNpbV9zZXR1cGAgYW5kIHRoZSBuZXcgbGlzdCBjb2x1bW4gdGl0bGVkIGBhY2NfbWV0cmljc2AuDQoNCmBgYHtyIGV4cF9ydW4sIGV2YWw9RkFMU0V9DQpleHBlcmltZW50YWxfcmVzdWx0cyA9IA0KICBzaW1fc2V0dXAgfD4jIHNldHVwIGZyb20gYSBwcmV2aW91cyBjaHVuaw0KICAjIGNyZWF0aW5nIGEgbmV3IGxpc3QgY29sdW1uIHRpdGxlZCBhY2NfbWV0cmljcywgd2hlcmUgdGhlIGZpdmUgY2xhc3NpZmljYXRpb24NCiAgIyBtZXRyaWNzIG9mIGNob2ljZSBhcmUgc3RvcmVkIGZvciBlYWNoIHNpbXVsYXRpb24gcnVuDQogIA0KICAjIFRoZSBtYXAgZnVuY3Rpb24gZnJvbSBwdXJyciAocGFydCBvZiB0aWR5dmVyc2UpIGFsbG93cyB1cyB0byBwZXJmb3JtIHJvdy13aXNlIA0KICAjIGNvbXB1dGF0aW9ucyAoaS5lLiwgZm9yIGVhY2ggY29tYmluYXRpb24gb2Ygc2ltdWxhdGlvbiBwYXJhbWV0ZXJzKQ0KICAjIG9uIHRoZSBgZGF0YWAgbGlzdCBjb2x1bW4sIHdoZXJlIHdlIHVzZSB0aGUgdmFyaWFibGVzIHdpdGhpbiBpdCB0bw0KICAjIGdlbmVyYXRlIHRoZSByYW5kb20gdmFsdWVzIGFuZCBjb21wdXRlIHRoZSBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzLg0KICBtdXRhdGUoDQogICAgYWNjX21ldHJpY3MgPSBtYXAoLnggPSBkYXRhLCAuZiA9IHNpbV9mdW5jdGlvbiwgYmFzZV9jbGFzc19uYW1lID0gJ2NsYXNzXycpDQogICkNCg0KIyBzYXZpbmcgdGhlIGV4cGVyaW1lbnRhbCByZXN1bHRzDQp3cml0ZV9yZHMoDQogIHggPSBleHBlcmltZW50YWxfcmVzdWx0cywgZmlsZSA9ICcuLi9yZXN1bHRzL2V4cGVyaW1lbnRhbF9yZXN1bHRzLnJkcycNCikNCg0KYGBgDQoNCi0tLQ0KDQojIFN1bW1hcml6aW5nIHRoZSBFeHBlcmltZW50YWwgUmVzdWx0cw0KDQpCZWxvdywgd2UgY29tcHV0ZSB0aGUgbWVhbnMsIHN0YW5kYXJkIGRldmlhdGlvbnMgYW5kIHF1YW50aWxlcyBmb3IgZWFjaCBvZiB0aGUgbWV0cmljcywgZ3JvdXBlZCBieSB0aGUgSUQgKGkuZS4sIGBleHBfbnVtYCkuIFdlIHNhdmUgdGhlIHN1bW1hcnkgcmVzdWx0cyBpbnRvIFJEUyBhbmQgQ1NWIGZvcm1hdHMgKHRvIGZhY2lsaXRhdGUgdGhlaXIgY29uc3VtcHRpb24gYnkgYm90aCBSIGFuZCBub24tUiB1c2VycykuIEluIGFkZGl0aW9uLCB3ZSBwcmludCBhIHRhYmxlIG9mIHRoZSBvYnRhaW5lZCByZXN1bHRzIGJlbG93Lg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmV4cGVyaW1lbnRhbF9yZXN1bHRzIDwtIHJlYWRSRFMoIkc6Ly5zaG9ydGN1dC10YXJnZXRzLWJ5LWlkLzEwMXJ3ckZkYlB5Zk1yZGNKUmxQNnFsVE9JeDlzWFRlXy9zZW5zaXRpdml0eS9yZXN1bHRzL2V4cGVyaW1lbnRhbF9yZXN1bHRzXzA2MDUucmRzIikNCmBgYA0KDQpgYGB7ciBleHBfc3VtbWFyeX0NCnN1bW1hcnlfcmVzdWx0cyA9IA0KICBleHBlcmltZW50YWxfcmVzdWx0cyB8Pg0KICAjIHVubmVzdGluZyB0aGUgYWNjX21ldHJpY3MgKGkuZS4sIGdlbmVyYXRpbmcgY29sdW1ucyBmb3IgZWFjaCBvZiB0aGUgbWV0cmljcykNCiAgdW5uZXN0KCBhY2NfbWV0cmljcyApIHw+DQogICMgZ3JvdXBpbmcgYnkgZXhwX251bSANCiAgIyAoYW5kIGRhdGEgd2hpY2ggaXMgcmVkdW5kYW50IGJ1dCB0byBrZWVwIGl0IGluIHRoZSBzdW1tYXJ5IHRhYmxlKQ0KICBncm91cF9ieShleHBfbnVtLCBkYXRhKSB8Pg0KICAjIHN1bW1hcmllcyBvZiBtZWFucywgc2RzIGFuZCBxdWFudGlsZSBvZiBpbnRlcmVzdCBmb3IgZWFjaCBtZXRyaWMgYnkgZXhwX251bQ0KICBzdW1tYXJpc2UoDQogICAgYWNyb3NzKGFjY3VyYWN5OmZfbWVhc3VyZSwgIA0KICAgICAgICAgICBsaXN0KG1lYW4gPSB+IG1lYW4oLngsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICAgICAgc2QgPSB+IHNkKC54LCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgICAgIHF1YW50aWxlID0gfiBxdWFudGlsZSgueCwgcHJvYnMgPSBxdWFudF9vZl9pbnRlcmVzdCwgbmEucm0gPSBUUlVFKSApLCANCiAgICAgICAgICAgLm5hbWVzID0gInsuZm59X3suY29sfSIgKQ0KICApIHw+DQogICMgZXhwYW5kaW5nIHRoZSBkYXRhIGludG8gdGhlIGRpZmZlcmVudCBleHBlcmltZW50YWwgY29uZGl0aW9ucyBmb3IgaW50ZXJwcmV0YXRpb24NCiAgdW5uZXN0KGRhdGEpDQoNCiMgc3RvcmluZyB0aGUgcmVzdWx0cyBhcyByZHMNCndyaXRlX3JkcygNCiAgeCA9IHN1bW1hcnlfcmVzdWx0cywgZmlsZSA9ICcuLi9yZXN1bHRzL3N1bW1hcnlfcmVzdWx0cy5yZHMnDQopDQoNCiMgd2UgYWxzbywgc3RvcmUgdGhlIHJlc3VsdHMgYXMgQ1NWDQp3cml0ZV9jc3YoDQogIHggPSBzdW1tYXJ5X3Jlc3VsdHMsIGZpbGUgPSAnLi4vcmVzdWx0cy9zdW1tYXJ5X3Jlc3VsdHMuY3N2Jw0KKQ0KDQojIHByaW50aW5nIHRoZSByZXN1bHRzDQpEVDo6ZGF0YXRhYmxlKA0KICBzdW1tYXJ5X3Jlc3VsdHMgfD5tdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCByb3VuZCwgMykpIHw+DQogICAgIyBtb3ZpbmcgYW5kIHJlbmFtaW5nIGNvbHVtbnMgcHVyZWx5IGZvciBvdXRwdXQgcHJlc2VudGF0aW9uIHB1cnBvc2VzDQogICAgcmVsb2NhdGUoZXhwX251bSwgbnVtX2NsYXNzZXMsIGZpcnN0X2NsYXNzX3BlcmMsIHByZWRfYXBwcm9hY2gsIG51bV9vYnNlcnZhdGlvbnMpIHw+DQogICAgcmVuYW1lKG5fb2JzID0gbnVtX29ic2VydmF0aW9ucywgDQogICAgICAgICAgIHJlZl9wcm9iID0gZmlyc3RfY2xhc3NfcGVyYywNCiAgICAgICAgICAgcHJlZCA9IHByZWRfYXBwcm9hY2gsIA0KICAgICAgICAgICBtZWFuX2FjYyA9IG1lYW5fYWNjdXJhY3ksIHNkX2FjYyA9IHNkX2FjY3VyYWN5LA0KICAgICAgICAgICBtZWFuX3NlbnMgPSBtZWFuX3NlbnNpdGl2aXR5LCBzZF9zZW5zID0gc2Rfc2Vuc2l0aXZpdHksIA0KICAgICAgICAgICBtZWFuX3NwZWMgPSBtZWFuX3NwZWNpZmljaXR5LCBzZF9zcGVjID0gc2Rfc3BlY2lmaWNpdHksDQogICAgICAgICAgIG1lYW5fcHJlYyA9IG1lYW5fcHJlY2lzaW9uLCBzZF9wcmVjID0gc2RfcHJlY2lzaW9uLA0KICAgICAgICAgICBtZWFuX2ZfbWVhcyA9IG1lYW5fZl9tZWFzdXJlLCBzZF9mX21lYXMgPSBzZF9mX21lYXN1cmUpLCANCiAgZmlsdGVyID0gJ3RvcCcsDQogIGV4dGVuc2lvbnMgPSBjKCdCdXR0b25zJywgJ0ZpeGVkQ29sdW1ucycpLCANCiAgcm93bmFtZXMgPSBGQUxTRSwNCiAgb3B0aW9ucyA9IGxpc3QoDQogIGluaXRDb21wbGV0ZSA9IEpTKCJmdW5jdGlvbihzZXR0aW5ncywganNvbikgeyQodGhpcy5hcGkoKS50YWJsZSgpLmNvbnRhaW5lcigpKS5jc3Moeydmb250LXNpemUnIDogJzEwcHQnfSk7fSIpLCANCiAgICBkb20gPSAnQmZydGlwJywNCiAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JyksDQogICAgcGFnZUxlbmd0aCA9IDIwLA0KICAgIHNjcm9sbFggPSBUUlVFLA0KICAgIGZpeGVkQ29sdW1ucyA9IGxpc3QobGVmdENvbHVtbnMgPSA0KQ0KICApIA0KKQ0KDQpgYGANCg0KVGhlIHJlc3VsdHMgY2FuIGJlIGRvd25sb2FkZWQgZnJvbSBbc3VtbWFyeV9yZXN1bHRzLmNzdl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2ZtZWdhaGVkL21ldHJpY19pbnRlcnByZXRhdGlvbi9tYWluL3Jlc3VsdHMvc3VtbWFyeV9yZXN1bHRzLmNzdikuDQoNCg0KLS0tDQoNCiMgQXBwIGZvciBDaXRpemVuIERhdGEgU2NpZW50aXN0cw0KDQpXZSBoYXZlIGNyZWF0ZWQgYSBbZmxleGRhc2hib2FyZF0oaHR0cHM6Ly9wa2dzLnJzdHVkaW8uY29tL2ZsZXhkYXNoYm9hcmQvaW5kZXguaHRtbCktYmFzZWQgd2ViIGFwcGxpY2F0aW9uLCB3aGljaCBhbGxvd3MgdXNlcnMgdG8gaW5wdXQgdGhlIHNpbXVsYXRpb24gcGFyYW1ldGVycyBvZiBpbnRlcmVzdCBhbmQgb3V0cHV0IHRoZSBjb3JyZXNwb25kaW5nIHNpbXVsYXRpb24gc3VtbWFyeSBhbmQgaGlzdG9ncmFtcyBvZiBvdXIgZml2ZSBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzIG9mIGludGVyZXN0LiBUaGUgYXBwbGljYXRpb24gY2FuIGJlIGFjY2Vzc2VkIFtoZXJlXShodHRwOi8vcnN0dWRpby5mc2IubWlhbWlvaC5lZHU6MzgzOC9tZWdhaGVmbS9tZXRyaWNfaW50ZXJwcmV0YXRpb24vKSBhbmQgdGhlIGNvZGUgdXNlZCB0byBjcmVhdGUgdGhlIGFwcGxpY2F0aW9uIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9mbWVnYWhlZC9tZXRyaWNfaW50ZXJwcmV0YXRpb24vdHJlZS9tYWluL2FwcCkuDQoNCg0KLS0tDQoNCiMgQXBwZW5kaXggey19DQoNCkluIHRoaXMgYXBwZW5kaXgsIHdlIHByaW50IGFsbCB0aGUgUiBwYWNrYWdlcyB1c2VkIGluIG91ciBhbmFseXNpcyBhbmQgdGhlaXIgdmVyc2lvbnMgdG8gYXNzaXN0IHdpdGggcmVwcm9kdWNpbmcgb3VyIHJlc3VsdHMvYW5hbHlzaXMuDQoNCmBgYHtyIHNlc3Npb25JbmZvfQ0KcGFjbWFuOjpwX2xvYWQocGFuZGVyKQ0KcGFuZGVyOjpwYW5kZXIoc2Vzc2lvbkluZm8oKSwgY29tcGFjdCA9IFRSVUUpICMgcHJpbnRpbmcgdGhlIHNlc3Npb24gaW5mbw0KYGBgDQo=