1 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
) 

2 Preparing for the Simulation

2.1 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

2.2 Full Factorial Setup

In the chunk below, we create a nested objected titled sim_setup, which is a tibble containing three variables:

  1. exp_num a unique id for each combination of num_classes, num_observations, first_class_perc and pred_approach.
  2. 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).
  3. 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'
)

2.3 Custom Functions

2.3.1 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)
}

2.3.2 Big Simulation Function for Vectorized/Functional Programming

The sim_function() is a custom vectorized function that generates:

  1. the simulated observations, which are used to mimic true (aka reference) values used to measure the classification performance
  2. 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:

  1. overall accuracy,
  2. sensitivity for the reference class class_1,
  3. specificity for the reference class class_1,
  4. the geometric mean of sensitivity and specificity,
  5. precision for the reference class class_1,
  6. recall for the reference class class_1, and
  7. 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)
}

3 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'
)

4 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.


5 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)


  1. Email: | Phone: +1-513-529-4185 | Website: Miami University Official↩︎

  2. Email: | Phone: +1-937-229-2405 | Website: University of Dayton Official↩︎

  3. Email: | Phone: +1-513-529-4823 | Website: Miami University Official↩︎

  4. Email: | Website: Saint Louis University Official↩︎

LS0tDQp0aXRsZTogIlRoZSBWYXJpYWJpbGl0eSBpbiBDb21tb25seSBVc2VkIENsYXNzaWZpY2F0aW9uIE1ldHJpY3Mgd2l0aCBDbGFzcyBJbWJhbGFuY2UiDQphdXRob3I6DQogIC0gbmFtZTogIkZhZGVsIE0uIE1lZ2FoZWQgXltFbWFpbDogZm1lZ2FoZWRAbWlhbWlvaC5lZHUgfCBQaG9uZTogKzEtNTEzLTUyOS00MTg1IHwgV2Vic2l0ZTogPGEgaHJlZj1cImh0dHBzOi8vbWlhbWlvaC5lZHUvZnNiL2RpcmVjdG9yeS8/dXA9L2RpcmVjdG9yeS9tZWdhaGVmbVwiPk1pYW1pIFVuaXZlcnNpdHkgT2ZmaWNpYWw8L2E+XSINCiAgICBhZmZpbGlhdGlvbjogRmFybWVyIFNjaG9vbCBvZiBCdXNpbmVzcywgTWlhbWkgVW5pdmVyc2l0eQ0KICAtIG5hbWU6ICJZaW5nLUp1IChUZXNzYSkgQ2hlbiBeW0VtYWlsOiB5Y2hlbjRAdWRheXRvbi5lZHUgfCBQaG9uZTogKzEtOTM3LTIyOS0yNDA1IHwgV2Vic2l0ZTogPGEgaHJlZj1cImh0dHA6Ly9zaXRlcy51ZGF5dG9uLmVkdS95Y2hlbjRcIj5Vbml2ZXJzaXR5IG9mIERheXRvbiBPZmZpY2lhbDwvYT5dIg0KICAgIGFmZmlsaWF0aW9uOiBEZXBhcnRtZW50IG9mIE1hdGhlbWF0aWNzLCBVbml2ZXJzaXR5IG9mIERheXRvbg0KICAtIG5hbWU6ICJBbGxpc29uIEpvbmVzLUZhcm1lciBeW0VtYWlsOiBmYXJtZXJsMkBtaWFtaW9oLmVkdSB8IFBob25lOiArMS01MTMtNTI5LTQ4MjMgfCBXZWJzaXRlOiA8YSBocmVmPVwiaHR0cHM6Ly9taWFtaW9oLmVkdS9mc2IvZGlyZWN0b3J5Lz91cD0vZGlyZWN0b3J5L2Zhcm1lcmwyXCI+TWlhbWkgVW5pdmVyc2l0eSBPZmZpY2lhbDwvYT5dIg0KICAgIGFmZmlsaWF0aW9uOiBGYXJtZXIgU2Nob29sIG9mIEJ1c2luZXNzLCBNaWFtaSBVbml2ZXJzaXR5DQogIC0gbmFtZTogIlN0ZXZlIFJpZ2RvbiBeW0VtYWlsOiBzdGV2ZS5yaWdkb25Ac2x1LmVkdSB8IFdlYnNpdGU6IDxhIGhyZWY9XCJodHRwczovL3d3dy5zbHUuZWR1L3B1YmxpYy1oZWFsdGgtc29jaWFsLWp1c3RpY2UvZmFjdWx0eS9yaWdkb24tc3RldmVuLnBocFwiPlNhaW50IExvdWlzIFVuaXZlcnNpdHkgT2ZmaWNpYWw8L2E+XSINCiAgICBhZmZpbGlhdGlvbjogQ29sbGVnZSBvZiAgUHVibGljIEhlYWx0aCBhbmQgU29jaWFsIEp1c3RpY2UsIFNhaW50IExvdWlzIFVuaXZlcnNpdHkNCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBjdXN0b20uY3NzDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIG51bWJlcl9zZWN0aW9uczogVFJVRQ0KICAgIHBhZ2VkX2RmOiBUUlVFDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgdGhlbWU6IHJlYWRhYmxlDQogIGluY2x1ZGVzOg0KICAgIGluX2hlYWRlcjogc3RydWN0dXJlLnRleA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICBlY2hvID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBkZXYgPSBjKCdwbmcnLCAncGRmJywgJ3RpZmYnKSwNCiAgICAgICAgICAgICAgICAgICAgICBmaWcucmV0aW5hID0gMiwNCiAgICAgICAgICAgICAgICAgICAgICBvdXQud2lkdGggPSAnMTAwJScsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFzcCA9IDAuNykNCg0Kb3B0aW9ucyhxd3JhcHMyX21hcmt1cCA9ICJtYXJrZG93biIpDQoNCiMgU2V0dGluZyBwcm9wZXJ0aWVzIGZvciB0aGUgZGVmYXVsdCB0aGVtZV9idygpIGJlaGF2aW9yIGZvciBhbGwgcGxvdHMNCmlmKHJlcXVpcmUoZ2dwbG90MikgPT0gRkFMU0UpIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KbGlicmFyeShnZ3Bsb3QyKSA7IHRoZW1lX3NldCh0aGVtZV9idyhiYXNlX3NpemUgPSAxMSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAndG9wJykpIA0KDQojIFNldHRpbmcgZGVmYXVsdCBjb2xvciBwYWxldHRlcyB0byBSQ29sb3JCcmV3ZXIgUGFsZXR0ZXMNCmlmKHJlcXVpcmUoUkNvbG9yQnJld2VyKSA9PSBGQUxTRSkgaW5zdGFsbC5wYWNrYWdlcygiUkNvbG9yQnJld2VyIikNCnNjYWxlX2NvbG91cl9kaXNjcmV0ZSA9IHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlBhaXJlZCIpDQoNCiMgU2V0dGluZyB0aGUgcmFuZG9tIHNlZWQgYW5kIGNodW5rIGRlcGVuZGVuY2llcw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlLmV4dHJhID0gc2V0LnNlZWQoMjAyMyksDQogICAgICAgICAgICAgICAgICAgICAgYXV0b2RlcCA9IFRSVUUpIA0Ka25pdHI6OmRlcF9hdXRvKCkNCmBgYA0KDQojIFIgU2V0dXAgYW5kIFJlcXVpcmVkIFBhY2thZ2VzDQpJbiB0aGlzIHByb2plY3QsIHRoZSBvcGVuLXNvdXJjZSBSIHByb2dyYW1taW5nIGxhbmd1YWdlIGlzIHVzZWQgdG8gaGVscCB1bmRlcnN0YW5kIHRoZSB2YXJpYWJpbGl0eSBpbiBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzIHdpdGggY2xhc3MgaW1iYWxhbmNlLCBzYW1wbGUgc2l6ZSwgYW5kIGRpZmZlcmVudCBudW1iZXIgb2YgY2xhc3Nlcy4gUiBpcyBtYWludGFpbmVkIGJ5IGFuIGludGVybmF0aW9uYWwgdGVhbSBvZiBkZXZlbG9wZXJzIHdobyBtYWtlIHRoZSBsYW5ndWFnZSBhdmFpbGFibGUgYXQgW1RoZSBDb21wcmVoZW5zaXZlIFIgQXJjaGl2ZSBOZXR3b3JrXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pLiBSZWFkZXJzIGludGVyZXN0ZWQgaW4gcmV1c2luZyBvdXIgY29kZSBhbmQgcmVwcm9kdWNpbmcgb3VyIHJlc3VsdHMgc2hvdWxkIGhhdmUgUiBpbnN0YWxsZWQgbG9jYWxseSBvbiB0aGVpciBtYWNoaW5lcy4gUiBjYW4gYmUgaW5zdGFsbGVkIG9uIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBvcGVyYXRpbmcgc3lzdGVtcyAoc2VlIFtXaW5kb3dzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9iaW4vd2luZG93cy8pLCBbTWFjXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9iaW4vbWFjb3N4LyksIGFuZCBbTGludXhdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2Jpbi9saW51eC8pIGZvciB0aGUgaW5zdGFsbGF0aW9uIGluc3RydWN0aW9ucyBmb3IgdGhlc2Ugc3lzdGVtcykuIFdlIGFsc28gcmVjb21tZW5kIHVzaW5nIHRoZSBSU3R1ZGlvIGludGVyZmFjZSBmb3IgUi4gVGhlIHJlYWRlciBjYW4gW2Rvd25sb2FkIFJTdHVkaW9dKGh0dHA6Ly93d3cucnN0dWRpby5jb20vaWRlKSBmb3IgZnJlZSBieSBmb2xsb3dpbmcgdGhlIGluc3RydWN0aW9ucyBhdCB0aGUgbGluay4gRm9yIG5vbi1SIHVzZXJzLCB3ZSByZWNvbW1lbmQgdGhlIFtIYW5kcy1vbiBQcm9ncmFtbWluZyB3aXRoIFJdKGh0dHBzOi8vcnN0dWRpby1lZHVjYXRpb24uZ2l0aHViLmlvL2hvcHIvcGFja2FnZXMuaHRtbCkgZm9yIGEgYnJpZWYgb3ZlcnZpZXcgb2YgdGhlIHNvZnR3YXJlJ3MgZnVuY3Rpb25hbGl0eS4gSGVyZWFmdGVyLCB3ZSBhc3N1bWUgdGhhdCB0aGUgcmVhZGVyIGhhcyBhbiBpbnRyb2R1Y3RvcnkgdW5kZXJzdGFuZGluZyBvZiB0aGUgUiBwcm9ncmFtbWluZyBsYW5ndWFnZS4NCg0KSW4gdGhlIGNvZGUgY2h1bmsgYmVsb3csIHdlIGxvYWQgdGhlIHBhY2thZ2VzIHVzZWQgdG8gc3VwcG9ydCBvdXIgYW5hbHlzaXMuIE5vdGUgdGhhdCB0aGUgY29kZSBvZiB0aGlzIGFuZCBhbnkgb2YgdGhlIGNvZGUgY2h1bmtzIGNhbiBiZSBoaWRkZW4gYnkgY2xpY2tpbmcgb24gdGhlICdIaWRlJyBidXR0b24gdG8gZmFjaWxpdGF0ZSB0aGUgbmF2aWdhdGlvbi4gKipUaGUgcmVhZGVyIGNhbiBoaWRlIGFsbCBjb2RlIGFuZC9vciBkb3dubG9hZCB0aGUgUm1kIGZpbGUgYXNzb2NpYXRlZCB3aXRoIHRoaXMgZG9jdW1lbnQgYnkgY2xpY2tpbmcgb24gdGhlIENvZGUgYnV0dG9uIG9uIHRoZSB0b3AgcmlnaHQgY29ybmVyIG9mIHRoaXMgZG9jdW1lbnQuKiogT3VyIGlucHV0IGFuZCBvdXRwdXQgZmlsZXMgY2FuIGFsc28gYmUgYWNjZXNzZWQvIGRvd25sb2FkZWQgZnJvbSBbZm1lZ2FoZWQvbWV0cmljX2ludGVycHJldGF0aW9uXShodHRwczovL2dpdGh1Yi5jb20vZm1lZ2FoZWQvbWV0cmljX2ludGVycHJldGF0aW9uKS4NCg0KDQpgYGB7ciBwYWNrYWdlc30NCiMgaW5zdGFsbGluZyB0aGUgcGFjbWFuIChQQUNrYWdlIE1BTmFnZXIpIHBhY2thZ2UgaWYgbm90IGluc3RhbGxlZCANCmlmKHJlcXVpcmUocGFjbWFuKT09RkFMU0UpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQoNCiMgbG9hZCAoYW5kIGlmIG5vdCBpbnN0YWxsZWQsIGluc3RhbGwpIHRoZSBuZWVkZWQgcGFja2FnZXMNCnBhY21hbjo6cF9sb2FkKA0KICB0aWR5dmVyc2UsICMgZm9yIGRhdGEgbWFuaXB1bGF0aW9uDQogIGNhcmV0LCAjIGZvciBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG1ldHJpY3MNCiAgcGxvdGx5LCAjIGZvciBpbnRlcmFjdGl2ZSBwbG90cw0KICBEVCAjIGZvciBuaWNlbHkgZm9ybWF0dGVkIHRhYmxlcw0KKSANCg0KYGBgDQoNCi0tLQ0KDQojIFByZXBhcmluZyBmb3IgdGhlIFNpbXVsYXRpb24NCg0KDQojIyBTaW11bGF0aW9uIFBhcmFtZXRlcnMNCg0KYGBge3Igc2ltX3BhcmFtYXRlcnN9DQoNCnNpbV9udW0gPSAxOjEwXjQgIyBzZXR0aW5nIHRoZSBudW1iZXIgb2Ygc2ltdWxhdGlvbnMgcGVyIGV4cCBydW4gdG8gMTAsMDAwDQoNCm51bV9jbGFzc2VzID0gYygyLCAzLCA1LCAxMCkgIyBudW1iZXIgb2YgY2xhc3Nlcw0KDQpudW1fb2JzZXJ2YXRpb25zID0gYygxMDAsIDEwXjMsIDEwXjQsIDEwXjUpICMgbnVtYmVyIG9mIG9ic2VydmF0aW9ucw0KDQpmaXJzdF9jbGFzc19wZXJjID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMiwgKDEvMyksIDAuNSkgIyBpbWJhbGFuY2UNCg0KcHJlZF9hcHByb2FjaCA9IGMoJ3VuaWZvcm0nLCAncHJvcG9ydGlvbmFsJywgJ21vc3RfZnJlcXVlbnQnKQ0KDQpxdWFudF9vZl9pbnRlcmVzdCA9IDAuOTkgIyBxdWFudGlsZSBvZiBpbnRlcmVzdA0KYGBgDQoNCg0KIyMgRnVsbCBGYWN0b3JpYWwgU2V0dXANCg0KSW4gdGhlIGNodW5rIGJlbG93LCB3ZSBjcmVhdGUgYSBuZXN0ZWQgb2JqZWN0ZWQgdGl0bGVkIGBzaW1fc2V0dXBgLCB3aGljaCBpcyBhIHRpYmJsZSBjb250YWluaW5nIHRocmVlIHZhcmlhYmxlczogIA0KDQogIDEuIGBleHBfbnVtYCBhIHVuaXF1ZSBpZCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbnVtX2NsYXNzZXNgLCBgbnVtX29ic2VydmF0aW9uc2AsIGBmaXJzdF9jbGFzc19wZXJjYCBhbmQgYHByZWRfYXBwcm9hY2hgLiAgDQogIDIuIGBzaW1fbnVtYCwgd2hpY2ggaGFzIHZhbHVlcyBmcm9tIDEgdG8gYHIgc2NhbGVzOjpjb21tYSggbWF4KHNpbV9udW0pIClgLCByZXBlYXRpbmcgZm9yIGVhY2ggSUQgKGkuZS4sIGVhY2ggdmFsdWUgZm9yYGV4cF9udW1gIGhhcyBgc2ltX251bWAgZnJvbSAgMSB0byBgciBzY2FsZXM6OmNvbW1hKCBtYXgoc2ltX251bSkgKSBgKS4gIA0KICAzLiBgZGF0YWAsIHdoaWNoIGlzIGxpc3QgY29sdW1uLCB3aGVyZSBlYWNoIGNlbGwvcm93IGNvbnRhaW5zIGEgdGliYmxlIG9mIDEgcm93IGFuZCB0aGUgZm91ciB2YXJpYWJsZXMgYG51bV9jbGFzc2VzYCwgYG51bV9vYnNlcnZhdGlvbnNgLCBgZmlyc3RfY2xhc3NfcGVyY2AgYW5kIGBwcmVkX2FwcHJvYWNoYC4gICANCg0KTm90ZSB0aGF0IHRoZSBgZGF0YWAgY29sdW1uIGlzIHRoZSBpbnB1dCB1c2VkIGluIG91ciBjdXN0b20gYHNpbV9mdW4oKWAsIHdoaWNoIGlzIHNob3duL2NyZWF0ZWQgaW4gYSBzdWJzZXF1ZW50IHN1YnNlY3Rpb24uIA0KDQpgYGB7ciBmdWxsX2ZhY3RvcmlhbF9zZXR1cCwgZXZhbD1GQUxTRX0NCiMgdGhlIGZ1bGwgZmFjdG9yaWFsIHNldHVwDQpzaW1fc2V0dXAgPSANCiAgIyBjcmVhdGluZyBhIHRpYmJsZSBvZiBhbGwgY29tYmluYXRpb25zIG9mIHRoZSBmaXZlIGV4cGVyaW1lbnRhbCBjb25kaXRpb25zDQogIGV4cGFuZF9ncmlkKCBzaW1fbnVtLCBudW1fY2xhc3NlcywgbnVtX29ic2VydmF0aW9ucywgZmlyc3RfY2xhc3NfcGVyYywNCiAgICAgICAgICAgICAgIHByZWRfYXBwcm9hY2gpIHw+DQogICMgdXNpbmcgZ3JvdXBfYnkgdG8gY3JlYXRlIGlkcyB3aGljaCBhcmUgTk9UIGRlcGVuZGVudCBvbiB0aGUgc2ltX251bQ0KICBncm91cF9ieShudW1fY2xhc3NlcywgbnVtX29ic2VydmF0aW9ucywgZmlyc3RfY2xhc3NfcGVyYywgcHJlZF9hcHByb2FjaCkgfD4NCiAgIyB1c2luZyB0aGUgZHBseXI6OmN1cl9ncm91cF9pZCgpIHRvIGNyZWF0ZSB0aGUgSURzDQogIG11dGF0ZSggZXhwX251bSA9IGN1cl9ncm91cF9pZCgpICkgfD4NCiAgIyB1bmdyb3VwaW5nIHNpbmNlIGl0IHdhcyBvbmx5IHBlcmZvcm1lZCB0byBjcmVhdGUgdW5pcXVlIGlkcw0KICB1bmdyb3VwKCkgfD4NCiAgIyBtb3ZpbmcgdGhlIGV4cF9udW0gdG8gdGhlIGJlZ2lubmluZw0KICByZWxvY2F0ZSggZXhwX251bSApIHw+DQogICMgY3JlYXRpbmcgbGlzdCBjb2x1bW5zIGZvciBhbGwgdGhlIGNvbHVtbnMgd2l0aCBleGNlcHQgb2YgSUQtdHlwZSBjb2x1bW5zDQogIG5lc3QoZGF0YSA9IC1jKGV4cF9udW0sIHNpbV9udW0pICkgfD4NCiAgIyBzb3J0IHRoZSBleHBlcmltZW50YWwgZGF0YSBieSBleHBfbnVtDQogIGFycmFuZ2UoZXhwX251bSkNCg0KDQojIHNhdmluZyB0aGUgcmVzdWx0cyBhcyBhbiByZHMgZmlsZSB1bmRlciB0aGUgcmVzdWx0cyBmb2xkZXINCndyaXRlX3JkcygNCiAgeCA9IHNpbV9zZXR1cCwgZmlsZSA9ICcuLi9yZXN1bHRzL3NpbV9zZXR1cC5yZHMnDQopDQpgYGANCg0KIyMgQ3VzdG9tIEZ1bmN0aW9ucw0KDQojIyMgZy1tZWFuIENhbGN1bGF0aW9uDQoNClRoZSBgZ19tZWFuKClgIGZ1bmN0aW9uIGJlbG93IGlzIHVzZWQgdG8gY29tcHV0ZSB0aGUgZ2VvbXRlcmljIG1lYW4gb2YgYW55IHR3byB2YXJpYWJsZXMuDQoNCmBgYHtyIGdfbWVhbl9jb21wfQ0KZ19tZWFuID0gZnVuY3Rpb24oeCwgeSl7DQogIA0KICByZXN1bHQgPSBzcXJ0KHgqeSkNCiAgDQogIHJldHVybihyZXN1bHQpDQp9DQoNCmBgYA0KDQoNCiMjIyBCaWcgU2ltdWxhdGlvbiBGdW5jdGlvbiBmb3IgVmVjdG9yaXplZC9GdW5jdGlvbmFsIFByb2dyYW1taW5nDQoNClRoZSBgc2ltX2Z1bmN0aW9uKClgIGlzIGEgY3VzdG9tIHZlY3Rvcml6ZWQgZnVuY3Rpb24gdGhhdCBnZW5lcmF0ZXM6ICAgDQoNCiAgYS4gdGhlIHNpbXVsYXRlZCBvYnNlcnZhdGlvbnMsIHdoaWNoIGFyZSB1c2VkIHRvIG1pbWljIGB0cnVlYCAoYWthIHJlZmVyZW5jZSkgdmFsdWVzIHVzZWQgdG8gbWVhc3VyZSB0aGUgY2xhc3NpZmljYXRpb24gcGVyZm9ybWFuY2UgIA0KICBiLiBzaW11bGF0ZWQgdmFsdWVzIGZvciB0aGUgcHJlZGljdGlvbnMgIA0KDQpUaGUgdHdvIHZlY3RvcnMgb2Ygb2JzZXJ2YXRpb25zIGFuZCBwcmVkaWN0aW9ucyBhcmUgYmFzZWQgb24gdGhlIHZhbHVlcyBpbnB1dHRlZCBpbiB0aGUgYFNpbXVsYXRpb24gUGFyYW1hdGVyc2Agc2VjdGlvbiAoaS5lLiwgYG51bV9jbGFzc2VzYCwgYG51bV9vYnNlcnZhdGlvbnNgLCBgZmlyc3RfY2xhc3NfcGVyY2AgYW5kIGBwcmVkX2FwcHJvYWNoYCkuIA0KDQpUaGUgZnVuY3Rpb24gcmV0dXJucyBzZXZlbiBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzIG9mIGludGVyZXN0OiAgIA0KDQogIDEuIG92ZXJhbGwgYWNjdXJhY3ksICAgDQogIDIuIHNlbnNpdGl2aXR5IGZvciB0aGUgcmVmZXJlbmNlIGNsYXNzIGBjbGFzc18xYCwgICANCiAgMy4gc3BlY2lmaWNpdHkgZm9yIHRoZSByZWZlcmVuY2UgY2xhc3MgYGNsYXNzXzFgLCAgDQogIDQuIHRoZSBnZW9tZXRyaWMgbWVhbiBvZiBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHksICAgDQogIDUuIHByZWNpc2lvbiBmb3IgdGhlIHJlZmVyZW5jZSBjbGFzcyBgY2xhc3NfMWAsICAgDQogIDYuIHJlY2FsbCBmb3IgdGhlIHJlZmVyZW5jZSBjbGFzcyBgY2xhc3NfMWAsIGFuZCAgICAgDQogIDcuIGZfbWVhc3VyZSBmb3IgdGhlIHJlZmVyZW5jZSBjbGFzcyBgY2xhc3NfMWAuDQoNCmBgYHtyIGJpZ19zaW1fZnVufQ0Kc2ltX2Z1bmN0aW9uID0gZnVuY3Rpb24oZGF0YSA9IE5VTEwsIGJhc2VfY2xhc3NfbmFtZSA9ICdjbGFzc18nKXsNCiAgDQogICMgZXh0cmFjdGluZyB0aGUgcmVsZXZhbnQgZmVhdHVyZXMgZnJvbSB0aGUgZGF0YSANCiAgbnVtYmVyX2NsYXNzZXMgPSBkYXRhJG51bV9jbGFzc2VzDQogIG51bWJlcl9vYnNlcnZhdGlvbnMgPSBkYXRhJG51bV9vYnNlcnZhdGlvbnMNCiAgZmlyc3RfY2xhc3NfcGVyY2VudGFnZSA9IGRhdGEkZmlyc3RfY2xhc3NfcGVyYw0KICBwcmVkaWN0aW9uX2FwcHJvYWNoID0gZGF0YSRwcmVkX2FwcHJvYWNoDQoNCg0KICAjIGdlbmVyYXRpbmcgdGhlIHJlZmVyZW5jZSBkYXRhDQogIHggPSBjKHBhc3RlMChiYXNlX2NsYXNzX25hbWUsIDE6bnVtYmVyX2NsYXNzZXMpKQ0KICByZWYgPSBjKHJlcCh4WzFdLCBudW1iZXJfb2JzZXJ2YXRpb25zKmZpcnN0X2NsYXNzX3BlcmNlbnRhZ2UpLA0KICAgICAgICAgIHJlcCh4Wy0xXSwgZWFjaCA9IG51bWJlcl9vYnNlcnZhdGlvbnMqKDEtZmlyc3RfY2xhc3NfcGVyY2VudGFnZSkvKG51bWJlcl9jbGFzc2VzLTEpKSkNCiAgDQogIGlmIChsZW5ndGgocmVmKSE9bnVtYmVyX2NsYXNzZXMpIHJlZiA9IGMocmVmLCByZXAoeFtudW1iZXJfY2xhc3Nlc10sIChudW1iZXJfb2JzZXJ2YXRpb25zLWxlbmd0aChyZWYpKSkpDQogIA0KICByZWYgPSBzYW1wbGUocmVmLCBudW1iZXJfb2JzZXJ2YXRpb25zLCByZXBsYWNlPUYpIHw+IGZhY3RvcihsZXZlbHMgPSBwYXN0ZTAoYmFzZV9jbGFzc19uYW1lLCAxOm51bWJlcl9jbGFzc2VzKSApDQogIA0KICAjIGdlbmVyYXRpbmcgdGhlIHByZWRpY3Rpb25zDQogIGlmKHByZWRpY3Rpb25fYXBwcm9hY2ggPT0gJ3VuaWZvcm0nKXsNCiAgICBwcmVkaWN0aW9ucyA9IHNhbXBsZSgNCiAgICAgIHggPSBjKHBhc3RlMChiYXNlX2NsYXNzX25hbWUsIDE6bnVtYmVyX2NsYXNzZXMpKSwNCiAgICAgIHNpemUgPSBudW1iZXJfb2JzZXJ2YXRpb25zLA0KICAgICAgcmVwbGFjZSA9IFQsDQogICAgICBwcm9iID0gcmVwKDEvbnVtYmVyX2NsYXNzZXMsIG51bWJlcl9jbGFzc2VzKSANCiAgICApIHw+IGZhY3RvcihsZXZlbHMgPSBwYXN0ZTAoYmFzZV9jbGFzc19uYW1lLCAxOm51bWJlcl9jbGFzc2VzKSkNCiAgfWVsc2UgaWYocHJlZGljdGlvbl9hcHByb2FjaCA9PSAncHJvcG9ydGlvbmFsJyl7DQogICAgcHJlZGljdGlvbnMgPSBzYW1wbGUoDQogICAgICB4ID0gYyhwYXN0ZTAoYmFzZV9jbGFzc19uYW1lLCAxOm51bWJlcl9jbGFzc2VzKSksDQogICAgICBzaXplID0gbnVtYmVyX29ic2VydmF0aW9ucywNCiAgICAgIHJlcGxhY2UgPSBULA0KICAgICAgcHJvYiA9ICh0YWJsZShyZWYpL2xlbmd0aChyZWYpKQ0KICAgICkgfD4gZmFjdG9yKGxldmVscyA9IHBhc3RlMChiYXNlX2NsYXNzX25hbWUsIDE6bnVtYmVyX2NsYXNzZXMpKQ0KICB9IGVsc2V7DQogICAgcHJvYiA9ICh0YWJsZShyZWYpL2xlbmd0aChyZWYpKQ0KICAgIHByZWRpY3Rpb25zID0gcmVwKA0KICAgICAgcGFzdGUwKGJhc2VfY2xhc3NfbmFtZSwgd2hpY2gubWF4KHByb2IpKSwNCiAgICAgIHRpbWVzID0gbnVtYmVyX29ic2VydmF0aW9ucw0KICAgICkgfD4gZmFjdG9yKGxldmVscyA9IHBhc3RlMChiYXNlX2NsYXNzX25hbWUsIDE6bnVtYmVyX2NsYXNzZXMpICkNCiAgfQ0KICANCiAgY29uZl9tYXRyaXggPSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KGRhdGEgPSBwcmVkaWN0aW9ucywgcmVmZXJlbmNlID0gcmVmKQ0KICANCiAgIyBjb21wdXRpbmcgdGhlIGFjYyBtZXRyaWNzIG9mIGludGVyZXN0DQogIGlmIChudW1iZXJfY2xhc3NlcyA9PSAyKXsNCiAgICBhY2NfbWV0cmljcyA9IHRpYmJsZSgNCiAgICAgIGFjY3VyYWN5ID0gY29uZl9tYXRyaXgkb3ZlcmFsbFsnQWNjdXJhY3knXSwNCiAgICAgIHNlbnNpdGl2aXR5ID0gY29uZl9tYXRyaXgkYnlDbGFzc1siU2Vuc2l0aXZpdHkiXSwNCiAgICAgIHNwZWNpZmljaXR5ID0gY29uZl9tYXRyaXgkYnlDbGFzc1siU3BlY2lmaWNpdHkiXSwNCiAgICAgIGdfbWVhbiA9IGdfbWVhbih4ID0gc2Vuc2l0aXZpdHksIHkgPSBzcGVjaWZpY2l0eSksDQogICAgICBwcmVjaXNpb24gPSBjb25mX21hdHJpeCRieUNsYXNzWyJQcmVjaXNpb24iXSwNCiAgICAgIHJlY2FsbCA9IGNvbmZfbWF0cml4JGJ5Q2xhc3NbIlJlY2FsbCJdLA0KICAgICAgZl9tZWFzdXJlID0gY29uZl9tYXRyaXgkYnlDbGFzc1siRjEiXQ0KICAgICkNCiAgfWVsc2V7DQogIGFjY19tZXRyaWNzID0gdGliYmxlKA0KICAgIGFjY3VyYWN5ID0gY29uZl9tYXRyaXgkb3ZlcmFsbFsnQWNjdXJhY3knXSwNCiAgICBzZW5zaXRpdml0eSA9IGNvbmZfbWF0cml4JGJ5Q2xhc3NbcGFzdGUwKCJDbGFzczogIiwgYmFzZV9jbGFzc19uYW1lLCAxKSwgIlNlbnNpdGl2aXR5Il0sDQogICAgc3BlY2lmaWNpdHkgPSBjb25mX21hdHJpeCRieUNsYXNzW3Bhc3RlMCgiQ2xhc3M6ICIsIGJhc2VfY2xhc3NfbmFtZSwgMSksICJTcGVjaWZpY2l0eSJdLA0KICAgIGdfbWVhbiA9IGdfbWVhbih4ID0gc2Vuc2l0aXZpdHksIHkgPSBzcGVjaWZpY2l0eSksDQogICAgcHJlY2lzaW9uID0gY29uZl9tYXRyaXgkYnlDbGFzc1twYXN0ZTAoIkNsYXNzOiAiLCBiYXNlX2NsYXNzX25hbWUsIDEpLCAiUHJlY2lzaW9uIl0sDQogICAgcmVjYWxsID0gY29uZl9tYXRyaXgkYnlDbGFzc1twYXN0ZTAoIkNsYXNzOiAiLCBiYXNlX2NsYXNzX25hbWUsIDEpLCAiUmVjYWxsIl0sDQogICAgZl9tZWFzdXJlID0gY29uZl9tYXRyaXgkYnlDbGFzc1twYXN0ZTAoIkNsYXNzOiAiLCBiYXNlX2NsYXNzX25hbWUsIDEpLCAiRjEiXQ0KICApDQogIH0NCiAgcmV0dXJuKGFjY19tZXRyaWNzKQ0KfQ0KYGBgDQoNCg0KLS0tDQoNCiMgUnVubmluZyB0aGUgRXhwZXJpbWVudA0KDQpXaXRoIG91ciBgc2ltX2Z1bmAsIHdlIGNhbiB1c2UgYSBmdW5jdGlvbmFsIHByb2dyYW1taW5nIGFwcHJvYWNoIHRvIGNvbXB1dGUgdGhlIGZpdmUgY2xhc3NpZmljYXRpb24gbWV0cmljcyBvZiBpbnRlcmVzdCBmb3IgZWFjaCBzaW11bGF0aW9uIHJ1bi4gVGhlIHJlc3VsdHMgd2lsbCBiZSBzdG9yZWQgaW4gYW4gb2JqZWN0IHRpdGxlZCBgZXhwZXJpbWVudGFsX3Jlc3VsdHNgLCB3aGljaCBjb250YWlucyB0aGUgMyBvcmlnaW5hbCBjb2x1bW5zIGZyb20gYHNpbV9zZXR1cGAgYW5kIHRoZSBuZXcgbGlzdCBjb2x1bW4gdGl0bGVkIGBhY2NfbWV0cmljc2AuDQoNCmBgYHtyIGV4cF9ydW4sIGV2YWw9RkFMU0V9DQpleHBlcmltZW50YWxfcmVzdWx0cyA9IA0KICBzaW1fc2V0dXAgfD4jIHNldHVwIGZyb20gYSBwcmV2aW91cyBjaHVuaw0KICAjIGNyZWF0aW5nIGEgbmV3IGxpc3QgY29sdW1uIHRpdGxlZCBhY2NfbWV0cmljcywgd2hlcmUgdGhlIGZpdmUgY2xhc3NpZmljYXRpb24NCiAgIyBtZXRyaWNzIG9mIGNob2ljZSBhcmUgc3RvcmVkIGZvciBlYWNoIHNpbXVsYXRpb24gcnVuDQogIA0KICAjIFRoZSBtYXAgZnVuY3Rpb24gZnJvbSBwdXJyciAocGFydCBvZiB0aWR5dmVyc2UpIGFsbG93cyB1cyB0byBwZXJmb3JtIHJvdy13aXNlIA0KICAjIGNvbXB1dGF0aW9ucyAoaS5lLiwgZm9yIGVhY2ggY29tYmluYXRpb24gb2Ygc2ltdWxhdGlvbiBwYXJhbWV0ZXJzKQ0KICAjIG9uIHRoZSBgZGF0YWAgbGlzdCBjb2x1bW4sIHdoZXJlIHdlIHVzZSB0aGUgdmFyaWFibGVzIHdpdGhpbiBpdCB0bw0KICAjIGdlbmVyYXRlIHRoZSByYW5kb20gdmFsdWVzIGFuZCBjb21wdXRlIHRoZSBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzLg0KICBtdXRhdGUoDQogICAgYWNjX21ldHJpY3MgPSBtYXAoLnggPSBkYXRhLCAuZiA9IHNpbV9mdW5jdGlvbiwgYmFzZV9jbGFzc19uYW1lID0gJ2NsYXNzXycpDQogICkNCg0KIyBzYXZpbmcgdGhlIGV4cGVyaW1lbnRhbCByZXN1bHRzDQp3cml0ZV9yZHMoDQogIHggPSBleHBlcmltZW50YWxfcmVzdWx0cywgZmlsZSA9ICcuLi9yZXN1bHRzL2V4cGVyaW1lbnRhbF9yZXN1bHRzLnJkcycNCikNCg0KYGBgDQoNCi0tLQ0KDQojIFN1bW1hcml6aW5nIHRoZSBFeHBlcmltZW50YWwgUmVzdWx0cw0KDQpCZWxvdywgd2UgY29tcHV0ZSB0aGUgbWVhbnMsIHN0YW5kYXJkIGRldmlhdGlvbnMgYW5kIHF1YW50aWxlcyBmb3IgZWFjaCBvZiB0aGUgbWV0cmljcywgZ3JvdXBlZCBieSB0aGUgSUQgKGkuZS4sIGBleHBfbnVtYCkuIFdlIHNhdmUgdGhlIHN1bW1hcnkgcmVzdWx0cyBpbnRvIFJEUyBhbmQgQ1NWIGZvcm1hdHMgKHRvIGZhY2lsaXRhdGUgdGhlaXIgY29uc3VtcHRpb24gYnkgYm90aCBSIGFuZCBub24tUiB1c2VycykuIEluIGFkZGl0aW9uLCB3ZSBwcmludCBhIHRhYmxlIG9mIHRoZSBvYnRhaW5lZCByZXN1bHRzIGJlbG93Lg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmV4cGVyaW1lbnRhbF9yZXN1bHRzIDwtIHJlYWRSRFMoIkc6Ly5zaG9ydGN1dC10YXJnZXRzLWJ5LWlkLzEwMXJ3ckZkYlB5Zk1yZGNKUmxQNnFsVE9JeDlzWFRlXy9zZW5zaXRpdml0eS9yZXN1bHRzL2V4cGVyaW1lbnRhbF9yZXN1bHRzXzA2MDUucmRzIikNCmBgYA0KDQpgYGB7ciBleHBfc3VtbWFyeX0NCnN1bW1hcnlfcmVzdWx0cyA9IA0KICBleHBlcmltZW50YWxfcmVzdWx0cyB8Pg0KICAjIHVubmVzdGluZyB0aGUgYWNjX21ldHJpY3MgKGkuZS4sIGdlbmVyYXRpbmcgY29sdW1ucyBmb3IgZWFjaCBvZiB0aGUgbWV0cmljcykNCiAgdW5uZXN0KCBhY2NfbWV0cmljcyApIHw+DQogICMgZ3JvdXBpbmcgYnkgZXhwX251bSANCiAgIyAoYW5kIGRhdGEgd2hpY2ggaXMgcmVkdW5kYW50IGJ1dCB0byBrZWVwIGl0IGluIHRoZSBzdW1tYXJ5IHRhYmxlKQ0KICBncm91cF9ieShleHBfbnVtLCBkYXRhKSB8Pg0KICAjIHN1bW1hcmllcyBvZiBtZWFucywgc2RzIGFuZCBxdWFudGlsZSBvZiBpbnRlcmVzdCBmb3IgZWFjaCBtZXRyaWMgYnkgZXhwX251bQ0KICBzdW1tYXJpc2UoDQogICAgYWNyb3NzKGFjY3VyYWN5OmZfbWVhc3VyZSwgIA0KICAgICAgICAgICBsaXN0KG1lYW4gPSB+IG1lYW4oLngsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICAgICAgc2QgPSB+IHNkKC54LCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgICAgIHF1YW50aWxlID0gfiBxdWFudGlsZSgueCwgcHJvYnMgPSBxdWFudF9vZl9pbnRlcmVzdCwgbmEucm0gPSBUUlVFKSApLCANCiAgICAgICAgICAgLm5hbWVzID0gInsuZm59X3suY29sfSIgKQ0KICApIHw+DQogICMgZXhwYW5kaW5nIHRoZSBkYXRhIGludG8gdGhlIGRpZmZlcmVudCBleHBlcmltZW50YWwgY29uZGl0aW9ucyBmb3IgaW50ZXJwcmV0YXRpb24NCiAgdW5uZXN0KGRhdGEpDQoNCiMgc3RvcmluZyB0aGUgcmVzdWx0cyBhcyByZHMNCndyaXRlX3JkcygNCiAgeCA9IHN1bW1hcnlfcmVzdWx0cywgZmlsZSA9ICcuLi9yZXN1bHRzL3N1bW1hcnlfcmVzdWx0cy5yZHMnDQopDQoNCiMgd2UgYWxzbywgc3RvcmUgdGhlIHJlc3VsdHMgYXMgQ1NWDQp3cml0ZV9jc3YoDQogIHggPSBzdW1tYXJ5X3Jlc3VsdHMsIGZpbGUgPSAnLi4vcmVzdWx0cy9zdW1tYXJ5X3Jlc3VsdHMuY3N2Jw0KKQ0KDQojIHByaW50aW5nIHRoZSByZXN1bHRzDQpEVDo6ZGF0YXRhYmxlKA0KICBzdW1tYXJ5X3Jlc3VsdHMgfD5tdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCByb3VuZCwgMykpIHw+DQogICAgIyBtb3ZpbmcgYW5kIHJlbmFtaW5nIGNvbHVtbnMgcHVyZWx5IGZvciBvdXRwdXQgcHJlc2VudGF0aW9uIHB1cnBvc2VzDQogICAgcmVsb2NhdGUoZXhwX251bSwgbnVtX2NsYXNzZXMsIGZpcnN0X2NsYXNzX3BlcmMsIHByZWRfYXBwcm9hY2gsIG51bV9vYnNlcnZhdGlvbnMpIHw+DQogICAgcmVuYW1lKG5fb2JzID0gbnVtX29ic2VydmF0aW9ucywgDQogICAgICAgICAgIHJlZl9wcm9iID0gZmlyc3RfY2xhc3NfcGVyYywNCiAgICAgICAgICAgcHJlZCA9IHByZWRfYXBwcm9hY2gsIA0KICAgICAgICAgICBtZWFuX2FjYyA9IG1lYW5fYWNjdXJhY3ksIHNkX2FjYyA9IHNkX2FjY3VyYWN5LA0KICAgICAgICAgICBtZWFuX3NlbnMgPSBtZWFuX3NlbnNpdGl2aXR5LCBzZF9zZW5zID0gc2Rfc2Vuc2l0aXZpdHksIA0KICAgICAgICAgICBtZWFuX3NwZWMgPSBtZWFuX3NwZWNpZmljaXR5LCBzZF9zcGVjID0gc2Rfc3BlY2lmaWNpdHksDQogICAgICAgICAgIG1lYW5fcHJlYyA9IG1lYW5fcHJlY2lzaW9uLCBzZF9wcmVjID0gc2RfcHJlY2lzaW9uLA0KICAgICAgICAgICBtZWFuX2ZfbWVhcyA9IG1lYW5fZl9tZWFzdXJlLCBzZF9mX21lYXMgPSBzZF9mX21lYXN1cmUpLCANCiAgZmlsdGVyID0gJ3RvcCcsDQogIGV4dGVuc2lvbnMgPSBjKCdCdXR0b25zJywgJ0ZpeGVkQ29sdW1ucycpLCANCiAgcm93bmFtZXMgPSBGQUxTRSwNCiAgb3B0aW9ucyA9IGxpc3QoDQogIGluaXRDb21wbGV0ZSA9IEpTKCJmdW5jdGlvbihzZXR0aW5ncywganNvbikgeyQodGhpcy5hcGkoKS50YWJsZSgpLmNvbnRhaW5lcigpKS5jc3Moeydmb250LXNpemUnIDogJzEwcHQnfSk7fSIpLCANCiAgICBkb20gPSAnQmZydGlwJywNCiAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JyksDQogICAgcGFnZUxlbmd0aCA9IDIwLA0KICAgIHNjcm9sbFggPSBUUlVFLA0KICAgIGZpeGVkQ29sdW1ucyA9IGxpc3QobGVmdENvbHVtbnMgPSA0KQ0KICApIA0KKQ0KDQpgYGANCg0KVGhlIHJlc3VsdHMgY2FuIGJlIGRvd25sb2FkZWQgZnJvbSBbc3VtbWFyeV9yZXN1bHRzLmNzdl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2ZtZWdhaGVkL21ldHJpY19pbnRlcnByZXRhdGlvbi9tYWluL3Jlc3VsdHMvc3VtbWFyeV9yZXN1bHRzLmNzdikuDQoNCg0KLS0tDQoNCiMgQXBwIGZvciBDaXRpemVuIERhdGEgU2NpZW50aXN0cw0KDQpXZSBoYXZlIGNyZWF0ZWQgYSBbZmxleGRhc2hib2FyZF0oaHR0cHM6Ly9wa2dzLnJzdHVkaW8uY29tL2ZsZXhkYXNoYm9hcmQvaW5kZXguaHRtbCktYmFzZWQgd2ViIGFwcGxpY2F0aW9uLCB3aGljaCBhbGxvd3MgdXNlcnMgdG8gaW5wdXQgdGhlIHNpbXVsYXRpb24gcGFyYW1ldGVycyBvZiBpbnRlcmVzdCBhbmQgb3V0cHV0IHRoZSBjb3JyZXNwb25kaW5nIHNpbXVsYXRpb24gc3VtbWFyeSBhbmQgaGlzdG9ncmFtcyBvZiBvdXIgZml2ZSBjbGFzc2lmaWNhdGlvbiBtZXRyaWNzIG9mIGludGVyZXN0LiBUaGUgYXBwbGljYXRpb24gY2FuIGJlIGFjY2Vzc2VkIFtoZXJlXShodHRwOi8vcnN0dWRpby5mc2IubWlhbWlvaC5lZHU6MzgzOC9tZWdhaGVmbS9tZXRyaWNfaW50ZXJwcmV0YXRpb24vKSBhbmQgdGhlIGNvZGUgdXNlZCB0byBjcmVhdGUgdGhlIGFwcGxpY2F0aW9uIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9mbWVnYWhlZC9tZXRyaWNfaW50ZXJwcmV0YXRpb24vdHJlZS9tYWluL2FwcCkuDQoNCg0KLS0tDQoNCiMgQXBwZW5kaXggey19DQoNCkluIHRoaXMgYXBwZW5kaXgsIHdlIHByaW50IGFsbCB0aGUgUiBwYWNrYWdlcyB1c2VkIGluIG91ciBhbmFseXNpcyBhbmQgdGhlaXIgdmVyc2lvbnMgdG8gYXNzaXN0IHdpdGggcmVwcm9kdWNpbmcgb3VyIHJlc3VsdHMvYW5hbHlzaXMuDQoNCmBgYHtyIHNlc3Npb25JbmZvfQ0KcGFjbWFuOjpwX2xvYWQocGFuZGVyKQ0KcGFuZGVyOjpwYW5kZXIoc2Vzc2lvbkluZm8oKSwgY29tcGFjdCA9IFRSVUUpICMgcHJpbnRpbmcgdGhlIHNlc3Npb24gaW5mbw0KYGBgDQo=