Introduction

This is an extensive exploratory tutorial on the Big Mart Sales challenge. It’s a regression practice problem wherein we have to predict sales product-wise and store-wise.This tutorial is intended for the beginners who want to learn how to solve a regression problem in R. We will follow the following table of content.


  1. Problem Statement
  2. Hypothesis Generation
  3. Data Structure and Content
  4. Exploratory Data Analysis
    • Univariate Analysis
    • Bivariate Analysis
  5. Missing Value Treatment
  6. Feature Engineering
  7. Encoding Categorical Variables
    • Label Encoding
    • One Hot Encoding
  8. PreProcessing Data
  9. Modeling
    • Linear Regression
    • Lasso Regression
    • Ridge Regression
    • RandomForest
    • XGBoost
  10. End Notes


Problem Statement

The data scientists at BigMart have collected 2013 sales data for 1559 products across 10 stores in different cities. Also, certain attributes of each product and store have been defined. The aim is to build a predictive model and find out the sales of each product at a particular store.

Using this model, BigMart will try to understand the properties of products and stores which play a key role in increasing sales.

Hypothesis Generation

This is a very important stage in any machine learning process. It basically involves brainstorming and coming up with as many ideas as possible about what could affect the target variable. It helps us in exploring the data at hand more efficiently and effectively. Hypothesis Generation should be done before seeing the data or else we will end up with biased hypotheses. Following are some of the hypotheses based on the problem statement.

  1. Sales are higher during weekends.
  2. Higher sales during morning and late evening.
  3. Higher sales during end of the year.
  4. Store size affects the sales.
  5. Location of the store affects the sales.
  6. Items with more shelf space sell more.


You can come up with more hypotheses of your own, the more the better. Let’s begin exploring the dataset and try to find interesting patterns.

We’ll first load the required packages.

library(data.table)
library(dplyr)
library(ggplot2)
library(caret)
library(corrplot)
library(e1071)
library(xgboost)
library(cowplot)

We use data.table’s fread() to speed up reading in the datasets.

train = fread("Train_UWu5bXk.csv")
test = fread("Test_u94Q5KV.csv")
submission = fread("SampleSubmission_TmnO39y.csv")

Data Structure and Content

It’s good to quicky check the dimensions of our data.

dim(train);dim(test)
[1] 8523   12
[1] 5681   11

train dataset has 8523 rows and 12 features and test has 5681 rows and 11 columns. train has 1 extra column which is the target variable. We will predict this target variable for the test dataset later in this tutorial.

Feature names of train and test datasets

names(train)
 [1] "Item_Identifier"           "Item_Weight"               "Item_Fat_Content"          "Item_Visibility"          
 [5] "Item_Type"                 "Item_MRP"                  "Outlet_Identifier"         "Outlet_Establishment_Year"
 [9] "Outlet_Size"               "Outlet_Location_Type"      "Outlet_Type"               "Item_Outlet_Sales"        
names(test)
 [1] "Item_Identifier"           "Item_Weight"               "Item_Fat_Content"          "Item_Visibility"          
 [5] "Item_Type"                 "Item_MRP"                  "Outlet_Identifier"         "Outlet_Establishment_Year"
 [9] "Outlet_Size"               "Outlet_Location_Type"      "Outlet_Type"              

Item_Outlet_Sales is present in train but not in test dataset because this is the target variable that we have to predict.

Structure of train and test datasets

str(train)
Classes ‘data.table’ and 'data.frame':  8523 obs. of  12 variables:
 $ Item_Identifier          : chr  "FDA15" "DRC01" "FDN15" "FDX07" ...
 $ Item_Weight              : num  9.3 5.92 17.5 19.2 8.93 ...
 $ Item_Fat_Content         : chr  "Low Fat" "Regular" "Low Fat" "Regular" ...
 $ Item_Visibility          : num  0.016 0.0193 0.0168 0 0 ...
 $ Item_Type                : chr  "Dairy" "Soft Drinks" "Meat" "Fruits and Vegetables" ...
 $ Item_MRP                 : num  249.8 48.3 141.6 182.1 53.9 ...
 $ Outlet_Identifier        : chr  "OUT049" "OUT018" "OUT049" "OUT010" ...
 $ Outlet_Establishment_Year: int  1999 2009 1999 1998 1987 2009 1987 1985 2002 2007 ...
 $ Outlet_Size              : chr  "Medium" "Medium" "Medium" "" ...
 $ Outlet_Location_Type     : chr  "Tier 1" "Tier 3" "Tier 1" "Tier 3" ...
 $ Outlet_Type              : chr  "Supermarket Type1" "Supermarket Type2" "Supermarket Type1" "Grocery Store" ...
 $ Item_Outlet_Sales        : num  3735 443 2097 732 995 ...
 - attr(*, ".internal.selfref")=<externalptr> 
str(test)
Classes ‘data.table’ and 'data.frame':  5681 obs. of  11 variables:
 $ Item_Identifier          : chr  "FDW58" "FDW14" "NCN55" "FDQ58" ...
 $ Item_Weight              : num  20.75 8.3 14.6 7.32 NA ...
 $ Item_Fat_Content         : chr  "Low Fat" "reg" "Low Fat" "Low Fat" ...
 $ Item_Visibility          : num  0.00756 0.03843 0.09957 0.01539 0.1186 ...
 $ Item_Type                : chr  "Snack Foods" "Dairy" "Others" "Snack Foods" ...
 $ Item_MRP                 : num  107.9 87.3 241.8 155 234.2 ...
 $ Outlet_Identifier        : chr  "OUT049" "OUT017" "OUT010" "OUT017" ...
 $ Outlet_Establishment_Year: int  1999 2007 1998 2007 1985 1997 2009 1985 2002 2007 ...
 $ Outlet_Size              : chr  "Medium" "" "" "" ...
 $ Outlet_Location_Type     : chr  "Tier 1" "Tier 2" "Tier 3" "Tier 2" ...
 $ Outlet_Type              : chr  "Supermarket Type1" "Supermarket Type1" "Grocery Store" "Supermarket Type1" ...
 - attr(*, ".internal.selfref")=<externalptr> 

To explore data in any data science competetion, it is advisable to append test data to the train data. So, we will combine both train and test to carry out data visualization, feature engineering, one-hot encoding, and label encoding. Later we would split this combined data back to train and test datasets.

test[,Item_Outlet_Sales := NA]
combi = rbind(train, test) # combining train and test datasets
dim(combi)
[1] 14204    12


Exploratory Data Analysis

Univariate Analysis

We will start off by ploting and exploring all the individual variables to gain some insights. In this tutorial ggplot2 package has been used to generate all the plots.


Target Variable

Since our target variable is continuous, we can visualise it by plotting its histogram.

ggplot(train) + geom_histogram(aes(train$Item_Outlet_Sales), binwidth = 100, fill = "darkgreen") +
  xlab("Item_Outlet_Sales")

As you can see, it is a right skewd variable and would need some data transformation to treat its skewness. We will come back to it later.


Independent Variables (numeric variables)

Now let’s check the numeric independent variables. We’ll again use the histograms for visualizations because that will help us in visualizing the distribution of individual variables.

p1 = ggplot(combi) + geom_histogram(aes(Item_Weight), binwidth = 0.5, fill = "blue")
p2 = ggplot(combi) + geom_histogram(aes(Item_Visibility), binwidth = 0.005, fill = "blue")
p3 = ggplot(combi) + geom_histogram(aes(Item_MRP), binwidth = 1, fill = "blue")
plot_grid(p1, p2, p3, nrow = 1) # plot_grid() from cowplot package
Removed 2439 rows containing non-finite values (stat_bin).

As you can see, there is no clear pattern in Item_Weight and Item_MRP. However, Item_Visibility is right-skewed and should be transformed to curb its skewness.


Independent Variables (categorical variables)

Now we’ll try to explore and gain some insights from the categorical variables. A categorical variable or feature can have only a finite set of values. Let’s first plot Item_Fat_Content.

ggplot(combi %>% group_by(Item_Fat_Content) %>% summarise(Count = n())) + 
  geom_bar(aes(Item_Fat_Content, Count), stat = "identity", fill = "coral1")


In the figure above, ‘LF’, ‘low fat’, and ‘Low Fat’ are the same category and can be combined into one. Similarly we can be done for ‘reg’ and ‘Regular’ into one. After making these corrections we’ll plot the same figure again.

combi$Item_Fat_Content[combi$Item_Fat_Content == "LF"] = "Low Fat"
combi$Item_Fat_Content[combi$Item_Fat_Content == "low fat"] = "Low Fat"
combi$Item_Fat_Content[combi$Item_Fat_Content == "reg"] = "Regular"
ggplot(combi %>% group_by(Item_Fat_Content) %>% summarise(Count = n())) + 
  geom_bar(aes(Item_Fat_Content, Count), stat = "identity", fill = "coral1")


Now let’s check the other categorical variables.

p4 = ggplot(combi %>% group_by(Item_Type) %>% summarise(Count = n())) + 
  geom_bar(aes(Item_Type, Count), stat = "identity", fill = "coral1") +
  xlab("") +
  geom_label(aes(Item_Type, Count, label = Count), vjust = 0.5) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  ggtitle("Item_Type")
p5 = ggplot(combi %>% group_by(Outlet_Identifier) %>% summarise(Count = n())) + 
  geom_bar(aes(Outlet_Identifier, Count), stat = "identity", fill = "coral1") +
  geom_label(aes(Outlet_Identifier, Count, label = Count), vjust = 0.5) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
p6 = ggplot(combi %>% group_by(Outlet_Size) %>% summarise(Count = n())) + 
  geom_bar(aes(Outlet_Size, Count), stat = "identity", fill = "coral1") +
  geom_label(aes(Outlet_Size, Count, label = Count), vjust = 0.5) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
second_row = plot_grid(p5, p6, nrow = 1)
plot_grid(p4, second_row, ncol = 1)


In Outlet_Size’s plot, for 4016 observations, Outlet_Size is blank or missing.We will check for this in the bivariate analysis to substitute the missing values in the Outlet_Size.

p7 = ggplot(combi %>% group_by(Outlet_Establishment_Year) %>% summarise(Count = n())) + 
  geom_bar(aes(factor(Outlet_Establishment_Year), Count), stat = "identity", fill = "coral1") +
  geom_label(aes(factor(Outlet_Establishment_Year), Count, label = Count), vjust = 0.5) +
  xlab("Outlet_Establishment_Year") +
  theme(axis.text.x = element_text(size = 8.5))
p8 = ggplot(combi %>% group_by(Outlet_Type) %>% summarise(Count = n())) + 
  geom_bar(aes(Outlet_Type, Count), stat = "identity", fill = "coral1") +
  geom_label(aes(factor(Outlet_Type), Count, label = Count), vjust = 0.5) +
  theme(axis.text.x = element_text(size = 8.5))
plot_grid(p7, p8, ncol = 2)

There are lesser number of observations in the data for the outlets established in the year 1998 as compared to the other years. Supermarket Type 1 seems to be the most popular category of Outlet_Type.


Bivariate Analysis

After looking at every feature individually, let’s now explore them again with respect to the target variable. Here we will make use of scatter plots for continuous or numeric variables and violin plots for the categorical variables.

train = combi[1:nrow(train)]
p9 = ggplot(train) + geom_point(aes(Item_Weight, Item_Outlet_Sales), colour = "violet", alpha = 0.3) +
     theme(axis.title = element_text(size = 8.5))
p10 = ggplot(train) + geom_point(aes(Item_Visibility, Item_Outlet_Sales), colour = "violet", alpha = 0.3) +
      theme(axis.title = element_text(size = 8.5))
p11 = ggplot(train) + geom_point(aes(Item_MRP, Item_Outlet_Sales), colour = "violet", alpha = 0.3) +
      theme(axis.title = element_text(size = 8.5))
second_row_2 = plot_grid(p10, p11, ncol = 2)
plot_grid(p9, second_row_2, nrow = 2)
Removed 1463 rows containing missing values (geom_point).

Item_Outlet_Sales is spread well across the entire range of the Item_Weight without any obvious pattern. In the Item_Visibility vs Item_Outlet_Sales, there is a string of points at Item_Visibility = 0.0 which seems strange as item visibility cannot be completely zero. We will take note of this issue and deal with it in the later stages.

In the third plot of Item_MRP vs Item_Outlet_Sales, we can clearly see 4 segments of prices that can be used in feature engineering to create a new variable.

Now we’ll visualise the categorical variables with respect to Item_Outlet_Sales. We will try to check the distribution of the target variable across all the categories of each of the categorical variable. We can do this using boxplots but instead we’ll use the violin plots as they show the full distribution of the data. The horizontal width of a violin plot at a particular level indicates the concentration of data at that level.

p12 = ggplot(train) + geom_violin(aes(Item_Type, Item_Outlet_Sales), fill = "magenta") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        axis.text = element_text(size = 6),
        axis.title = element_text(size = 8.5))
p13 = ggplot(train) + geom_violin(aes(Item_Fat_Content, Item_Outlet_Sales), fill = "magenta") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        axis.text = element_text(size = 8),
        axis.title = element_text(size = 8.5))
p14 = ggplot(train) + geom_violin(aes(Outlet_Identifier, Item_Outlet_Sales), fill = "magenta") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        axis.text = element_text(size = 8),
        axis.title = element_text(size = 8.5))
second_row_3 = plot_grid(p13, p14, ncol = 2)
plot_grid(p12, second_row_3, ncol = 1)

Distribution of Item_Outlet_Sales across the categories of Item_Type is not very distinct and same is the case with Item_Fat_Content. However, the distribution for OUT010 and OUT019 categories of Outlet_Identifier are quite similar and very much different from the rest of the categories of Outlet_Identifier.

In the univariate analysis, we came to know about the empty values in Outlet_Size variable. Let’s check the distribution of the target variable across Outlet_Size.

ggplot(train) + geom_violin(aes(Outlet_Size, Item_Outlet_Sales), fill = "magenta")

The distribution of ‘Small’ Outlet_Size is almost identical to the distribution of the blank category (first vioin) of Outlet_Size. So, we can substitute the blanks in Outlet_Size with ‘Small’. Please note that this is not the only way to impute missing values, but for the time being we will go ahead and impute the missing values with ‘Small’.

Let’s examine the remaining variables.

p15 = ggplot(train) + geom_violin(aes(Outlet_Location_Type, Item_Outlet_Sales), fill = "magenta")
p16 = ggplot(train) + geom_violin(aes(Outlet_Type, Item_Outlet_Sales), fill = "magenta")
plot_grid(p15, p16, ncol = 1)

Tier 1 and Tier 3 locations of Outlet_Location_Type look similar. In the Outlet_Type, Grocery Store has most of its data points around the lower sales values as compared to the other categories. These are the kind of insights that we can extract by visualizing our data. Hence, data visualization should be an important part of any kind data analysis.


Missing Value Treatment

Missing data can have a severe impact on building predictive models because the missing values might be contain some vital information which could help in making better predictions. So, it becomes imperative to carry out missing data imputation. There are different methods to treat missing values based on the problem and the data. Some of the common techniques are as follows:

  1. Deletion of rows: In train dataset, observations having missing values in any variable are deleted. The downside of this method is the loss of information and drop in prediction power of model.

  2. Mean/Median/Mode Imputation: In case of continuous variable, missing values can be replaced with mean or median of all known values of that variable. For categorical variables, we can use mode of the given values to replace the missing values.

  3. Building Prediction Model: We can even make a predictive model to impute missing data in a variable. Here we will treat the variable having missing data as the target variable and the other variables as predictors. We will divide our data into 2 datasets—one without any missing value for that variable and the other with missing values for that variable. The former set would be used as training set to build the predictive model and it would then be applied to the latter set to predict the missing values.

You can try the following code to quickly find missing values in a dataset.

colSums(is.na(combi))
          Item_Identifier               Item_Weight          Item_Fat_Content           Item_Visibility 
                        0                      2439                         0                         0 
                Item_Type                  Item_MRP         Outlet_Identifier Outlet_Establishment_Year 
                        0                         0                         0                         0 
              Outlet_Size      Outlet_Location_Type               Outlet_Type         Item_Outlet_Sales 
                        0                         0                         0                      5681 

As you can see above, we have missing values in Item_Weight and Item_Outlet_Sales. Missing data in Item_Outlet_Sales can be ignored since they belong to the test dataset. We’ll now impute Item_Weight with mean weight based on the Item_Identifier variabe.

missing_index = which(is.na(combi$Item_Weight))
for(i in missing_index){
  
  item = combi$Item_Identifier[i]
  combi$Item_Weight[i] = mean(combi$Item_Weight[combi$Item_Identifier == item], na.rm = T)
  
}

Replacing 0’s in Item_Visibility variable

zero_index = which(combi$Item_Visibility == 0)
for(i in zero_index){
  
  item = combi$Item_Identifier[i]
  combi$Item_Visibility[i] = mean(combi$Item_Visibility[combi$Item_Identifier == item], na.rm = T)
  
}

Feature Engineering

Most of the times the given features in a dataset are not enough to give satisfactory predictions. In such cases, we have to create new features which might help in improving the model’s performance. Let’s try to create some new features for our dataset.

We can have a look at the Item_Type variable and classify the categories into perishable and non_perishable as per our understanding and make it into a new feature.

# create a new feature 'Item_Type_new' 
perishable = c("Breads", "Breakfast", "Dairy", "Fruits and Vegetables", "Meat", "Seafood")
non_perishable = c("Baking Goods", "Canned", "Frozen Foods", "Hard Drinks", "Health and Hygiene",
                   "Household", "Soft Drinks")
combi[,Item_Type_new := ifelse(Item_Type %in% perishable, "perishable",
                               ifelse(Item_Type %in% non_perishable, "non_perishable", "not_sure"))]

Let’s compare Item_Type with the first 2 charactes of Item_Identifier, i.e., ‘DR’, ‘FD’, and ‘NC’. These identifiers most probably stand drinks, food, and non-consumable.

table(combi$Item_Type, substr(combi$Item_Identifier, 1, 2))
                       
                          DR   FD   NC
  Baking Goods             0 1086    0
  Breads                   0  416    0
  Breakfast                0  186    0
  Canned                   0 1084    0
  Dairy                  229  907    0
  Frozen Foods             0 1426    0
  Fruits and Vegetables    0 2013    0
  Hard Drinks            362    0    0
  Health and Hygiene       0    0  858
  Household                0    0 1548
  Meat                     0  736    0
  Others                   0    0  280
  Seafood                  0   89    0
  Snack Foods              0 1989    0
  Soft Drinks            726    0    0
  Starchy Foods            0  269    0

Based on the above table we can create a new feature. Let’s call it Item_category.

combi[,Item_category := substr(combi$Item_Identifier, 1, 2)]

We will also change the values of Item_Fat_Content whereever Item_category is ‘NC’ because non-consumable items cannot have any fat content. We will also create a couple of more features — Outlet_Years (years of operation) and price_per_unit_wt (price per unit weight).

combi$Item_Fat_Content[combi$Item_category == "NC"] = "Non-Edible"
# years of operation of outlets
combi[,Outlet_Years := 2013 - Outlet_Establishment_Year]
combi$Outlet_Establishment_Year = as.factor(combi$Outlet_Establishment_Year)
# Price per unit weight
combi[,price_per_unit_wt := Item_MRP/Item_Weight]

Earlier in the Item_MRP vs Item_Outlet_Sales plot, we saw Item_MRP was spread across in 4 chunks. We can use k Means clustering to create 4 groups using Item_MRP variable. K Means clustering requires prior knowledge of K i.e. no. of clusters we want to divide our data into. Here we will go ahead with K=4. Let’s try it out.

Item_MRP_clusters = kmeans(combi$Item_MRP, centers = 4)
table(Item_MRP_clusters$cluster) # display no. of observations in each cluster

   1    2    3    4 
2556 4317 4931 2400 

Let’s use these clusters as a new independent feature in our data.

combi$Item_MRP_clusters = as.factor(Item_MRP_clusters$cluster)

Encoding Categorical Variables

In this stage, we will convert our categorical variables into numerical ones. We will use 2 techniques — Label Encoding and One Hot Encoding.

  1. Label encoding simply means converting each category in a variable to a number. It is more suitable for ordinal variables — categorical variables with some order.

  2. In One hot encoding, each category of a categorical variable is converted into a new bunary column (1/0).

We will use both the encoding techniques.

Label encoding for the categorical variables

We will label encode Outlet_Size and Outlet_Location_Type as these are ordinal variables.

combi[,Outlet_Size_num := ifelse(Outlet_Size == "Small", 0,
                                 ifelse(Outlet_Size == "Medium", 1, 2))]
combi[,Outlet_Location_Type_num := ifelse(Outlet_Location_Type == "Tier 3", 0,
                                          ifelse(Outlet_Location_Type == "Tier 2", 1, 2))]
# removing categorical variables after label encoding
combi[, c("Outlet_Size", "Outlet_Location_Type") := NULL]


One hot encoding for the categorical variable

ohe = dummyVars("~.", data = combi[,-c("Item_Identifier", "Outlet_Establishment_Year", "Item_Type")], fullRank = T)
ohe_df = data.table(predict(ohe, combi[,-c("Item_Identifier", "Outlet_Establishment_Year", "Item_Type")]))
combi = cbind(combi[,"Item_Identifier"], ohe_df)

PreProcessing Data

Before feeding our data into any model, it is a good practice to preprocess the data. We will do preprocessing on both independent variables and target variable

Checking Skewness

Skewness in variables is undesirable for predictive modeling. Some machine learning methods assume normally distributed data and a skewed variable can be transformed by taking its log, square root, or cube root so as to make the distribution of the skewed variable as close to normal distribution as possible.

skewness(combi$Item_Visibility); skewness(combi$price_per_unit_wt)
[1] 1.254046
[1] 1.304551
combi[,Item_Visibility := log(Item_Visibility + 1)] # log + 1 to avoid division by zero
combi[,price_per_unit_wt := log(price_per_unit_wt + 1)]

Scaling numeric predictors

Let’s scale and center the numeric variables to make them have a mean of zero, standard deviation of one and scale of 0 to 1. Scaling and centering is required for linear regression models.

num_vars = which(sapply(combi, is.numeric)) # index of numeric features
num_vars_names = names(num_vars)
combi_numeric = combi[,setdiff(num_vars_names, "Item_Outlet_Sales"), with = F]
prep_num = preProcess(combi_numeric, method=c("center", "scale"))
combi_numeric_norm = predict(prep_num, combi_numeric)
combi[,setdiff(num_vars_names, "Item_Outlet_Sales") := NULL] # removing numeric independent variables
combi = cbind(combi, combi_numeric_norm)
train = combi[1:nrow(train)]
test = combi[(nrow(train) + 1):nrow(combi)]
test[,Item_Outlet_Sales := NULL] # removing Item_Outlet_Sales as it contains only NA

Correlated Variables

Let’s examine the correlated features of train dataset. Correlation varies from -1 to 1.

  1. negative correlation: < 0 and >= -1
  2. positive correlation: > 0 and <= 1
  3. no correlation: 0

It is not desirable to have correlated features if we are using linear regressions.

cor_train = cor(train[,-c("Item_Identifier")])
corrplot(cor_train, method = "pie", type = "lower", tl.cex = 0.9)

Variables price_per_unit_wt and Item_Weight are highly correlated as the former one was created from the latter. Similarly price_per_unit_wt and Item_MRP are highly correlated for the same reason.

Modeling

Finally we have arrived at most interesting stage of the whole process — predictive modeling. We will start off with the simpler models and gradually move on to more sophisticated models. We will build the models using…

We will use 5-fold cross validation in all the models we are going to build. Basically cross vaidation gives an idea how well a model generalizes to unseen data.


Linear Regression

Linear regression is the simplest and most widely used statistical technique for predictive modeling. Given below is the linear regression equation:

where X1, X2,…,Xn are the independent variables, Y is the target variable and all thetas are the coefficients. Magnitude of a coefficient wrt to the other coefficients determines the importance of the corresponding independent variable.

For a good linear regression model, the data should satisfy a few assumptions which can be found in this article in detail. One of these assumptions is that of absence of multicollinearity, i.e, the independent variables should be correlated. However, as per the correlation plot above, we have a few highly correlated independent variables in our data. This issue of multicollinearity can be dealt with regularization.

For the time being, let’s build our linear regression model with all the variables.

set.seed(1234)
my_control = trainControl(method="cv", number=5)
linear_reg_mod = train(x = train[,-c("Item_Identifier", "Item_Outlet_Sales")], y = train$Item_Outlet_Sales,
                       method='glmnet', trControl= my_control)
print("5- fold cross validation scores:")
[1] "5- fold cross validation scores:"
print(round(linear_reg_mod$resample$RMSE, 2))
[1] 1159.97 1121.63 1089.28 1115.11 1162.17

Making predictions on the test dataset

#submission$Item_Outlet_Sales = (predict(linear_reg_mod, test[,-c("Item_Identifier")]))^2 
submission$Item_Outlet_Sales = predict(linear_reg_mod, test[,-c("Item_Identifier")])
write.csv(submission, "Linear_Reg_submit_2_21_Apr_18.csv", row.names = F)

Leaderboard score: 1202.26


Regularised regression models can handle the correlated independent variables well and helps in overcoming overfitting. Ridge penalty shrinks the coefficients of correlated predictors towards each other while the Lasso tends to pick one of a pair of correlated features and discard the other. The tuning parameter lambda controls the strength of the penalty.

Lasso Regression

set.seed(1235)
my_control = trainControl(method="cv", number=5)
Grid = expand.grid(alpha = 1, lambda = seq(0.001,0.1,by = 0.0002))

lasso_linear_reg_mod = train(x = train[, -c("Item_Identifier", "Item_Outlet_Sales")], y = train$Item_Outlet_Sales,
                       method='glmnet', trControl= my_control, tuneGrid = Grid)

Leaderboard score: 1202.26


Ridge Regression

set.seed(1236)
my_control = trainControl(method="cv", number=5)
Grid = expand.grid(alpha = 0, lambda = seq(0.001,0.1,by = 0.0002))

ridge_linear_reg_mod = train(x = train[, -c("Item_Identifier", "Item_Outlet_Sales")], y = train$Item_Outlet_Sales,
                       method='glmnet', trControl= my_control, tuneGrid = Grid)

Leaderboard score: 1219.08


RandomForest

RandomForest is a tree based bootstrapping algorithm wherein a certain no. of weak learners (decision trees) are combined to make a powerful prediction model. For every indivisual learner, a random sample of rows and a few randomly chosen variables are used to build a decision tree model. Final prediction can be a function of all the predictions made by the individual learners. In case of regression problem, the final prediction can be mean of all the predictions. For detailed explanation visit this article.

We will now build a RandomForest model with 400 trees. The other tuning parameters used here are mtry — no. of predictor variables randomly sampled at each split, and min.node.size — minimum size of terminal nodes (setting this number large causes smaller trees and reduces overfitting).

set.seed(1237)
my_control = trainControl(method="cv", number=5)

tgrid = expand.grid(
  .mtry = c(3:10),
  .splitrule = "variance",
  .min.node.size = c(10,15,20)
)

rf_mod = train(x = train[, -c("Item_Identifier", "Item_Outlet_Sales")], 
               y = train$Item_Outlet_Sales,
               method='ranger', 
               trControl= my_control, 
               tuneGrid = tgrid,
               num.trees = 400,
               importance = "permutation")
plot(rf_mod)

As per the plot shown above, the best score is achieved at mtry = 5 and min.node.size = 20.

Leaderboard score: 1157.25


XGBoost Model

XGBoost is a fast and efficient algorithm and has been used to by the winners of many data science competetions. It’s a boosting algorithm and you may refer this article to know more about boosting.XGBoost works only with numeric variables and we have already done that. There are many tuning parameters in XGBoost which can be broadly classified into General Parameters, Booster Parameters and Task Parameters.

  • General parameters refers to which booster we are using to do boosting. The commonly used are tree or linear model
  • Booster parameters depends on which booster you have chosen
  • Learning Task parameters that decides on the learning scenario, for example, regression tasks may use different parameters with ranking tasks.

Let’s have a look at the parameters that we are going to use in our model.

  1. eta: It is also known as the learning rate or the shrinkage factor. It actually shrinks the feature weights to make the boosting process more conservative. The range is 0 to 1. Low eta value means model is more robust to overfitting.
  2. gamma: The range is 0 to ∞. Larger the gamma more conservative the algorithm is.
  3. max_depth: We can specify maximum depth of a tree using this parameter.
  4. subsample: It is the proportion of rows that the model will randomly select to grow trees.
  5. colsample_bytree: It is the ratio of variables randomly chosen for build each tree in the model.
param_list = list(
        
        objective = "reg:linear",
        eta=0.01,
        gamma = 1,
        max_depth=6,
        subsample=0.8,
        colsample_bytree=0.5
        )
dtrain = xgb.DMatrix(data = as.matrix(train[,-c("Item_Identifier", "Item_Outlet_Sales")]), label= train$Item_Outlet_Sales)
dtest = xgb.DMatrix(data = as.matrix(test[,-c("Item_Identifier")]))
set.seed(112)
xgbcv = xgb.cv(params = param_list, 
               data = dtrain, 
               nrounds = 1000, 
               nfold = 5, 
               print_every_n = 10, 
               early_stopping_rounds = 30, 
               maximize = F)
[1] train-rmse:2746.726611+7.335351 test-rmse:2746.741455+30.997620 
Multiple eval metrics are present. Will use test_rmse for early stopping.
Will train until test_rmse hasn't improved in 30 rounds.

[11]    train-rmse:2537.407764+5.897640 test-rmse:2539.186767+31.033561 
[21]    train-rmse:2349.479150+4.506506 test-rmse:2353.149951+32.307349 
[31]    train-rmse:2182.710596+4.820427 test-rmse:2188.130664+31.642597 
[41]    train-rmse:2033.751758+3.185745 test-rmse:2040.717822+31.669930 
[51]    train-rmse:1901.117578+2.685327 test-rmse:1910.519629+30.535673 
[61]    train-rmse:1784.638452+2.785332 test-rmse:1796.658545+28.845284 
[71]    train-rmse:1680.817847+2.442780 test-rmse:1695.664795+28.498337 
[81]    train-rmse:1590.710376+2.355847 test-rmse:1608.133936+28.083114 
[91]    train-rmse:1511.284009+1.938146 test-rmse:1531.907446+27.753792 
[101]   train-rmse:1441.667432+1.669035 test-rmse:1465.339282+29.325773 
[111]   train-rmse:1381.855786+2.373448 test-rmse:1408.609692+27.946078 
[121]   train-rmse:1329.842700+2.424946 test-rmse:1359.539600+27.418424 
[131]   train-rmse:1283.422485+2.876224 test-rmse:1316.416382+26.402673 
[141]   train-rmse:1243.852637+2.540695 test-rmse:1280.471533+25.467234 
[151]   train-rmse:1210.363989+2.557588 test-rmse:1250.352270+25.341492 
[161]   train-rmse:1180.807373+2.830153 test-rmse:1224.257568+24.374810 
[171]   train-rmse:1155.128735+2.460500 test-rmse:1202.033203+23.914640 
[181]   train-rmse:1133.309326+2.638564 test-rmse:1183.441016+23.106944 
[191]   train-rmse:1114.457251+2.588460 test-rmse:1167.850073+22.394809 
[201]   train-rmse:1098.201440+3.044061 test-rmse:1154.797119+21.841495 
[211]   train-rmse:1083.706030+3.528924 test-rmse:1143.217773+21.357599 
[221]   train-rmse:1071.321094+3.570172 test-rmse:1134.130224+20.882285 
[231]   train-rmse:1060.626660+3.701624 test-rmse:1126.470825+20.581249 
[241]   train-rmse:1051.289917+3.347872 test-rmse:1119.966406+20.567948 
[251]   train-rmse:1043.086231+3.365346 test-rmse:1114.744653+20.188325 
[261]   train-rmse:1035.549756+3.017828 test-rmse:1110.146753+19.898095 
[271]   train-rmse:1028.656812+3.034549 test-rmse:1106.056055+19.461418 
[281]   train-rmse:1022.382519+3.010057 test-rmse:1102.918799+19.266198 
[291]   train-rmse:1016.968725+2.859337 test-rmse:1100.386548+19.148728 
[301]   train-rmse:1011.780688+2.829672 test-rmse:1098.218945+18.923820 
[311]   train-rmse:1007.186755+2.809837 test-rmse:1096.416211+18.866274 
[321]   train-rmse:1002.984875+2.724723 test-rmse:1095.036182+18.856279 
[331]   train-rmse:999.086707+2.610513  test-rmse:1093.826953+18.811409 
[341]   train-rmse:995.230347+2.489401  test-rmse:1092.823755+18.730222 
[351]   train-rmse:991.635303+2.562694  test-rmse:1091.962793+18.797019 
[361]   train-rmse:988.453491+2.598685  test-rmse:1091.367261+18.735331 
[371]   train-rmse:985.262891+2.528210  test-rmse:1090.897730+18.676589 
[381]   train-rmse:982.168066+2.491125  test-rmse:1090.334131+18.727500 
[391]   train-rmse:979.388269+2.496498  test-rmse:1090.044995+18.728524 
[401]   train-rmse:976.569849+2.411764  test-rmse:1089.951758+18.669657 
[411]   train-rmse:973.715760+2.416732  test-rmse:1089.936035+18.802668 
[421]   train-rmse:971.002002+2.453059  test-rmse:1089.889087+18.826629 
[431]   train-rmse:968.387219+2.438929  test-rmse:1089.820801+18.791873 
[441]   train-rmse:965.961414+2.681547  test-rmse:1089.849414+18.777218 
[451]   train-rmse:963.600769+2.733766  test-rmse:1089.912305+18.756355 
Stopping. Best iteration:
[430]   train-rmse:968.672278+2.411026  test-rmse:1089.806958+18.821152
xgb_model = xgb.train(data = dtrain, params = param_list, nrounds = 430)

Leaderboard score: 1154.70


Variable Importance

var_imp = xgb.importance(feature_names = setdiff(names(train), c("Item_Identifier", "Item_Outlet_Sales")), 
                         model = xgb_model)
xgb.plot.importance(var_imp)

As expected Item_MRP is the most important variable in predicting the target variable. New features created by us, like price_per_unit_wt, Outlet_Years, Item_MRP_Clusters, are also among the top most important variables. This is why feature engineering plays such a crucial role in predictive modeling.


End Notes

After trying and testing 5 different algorithms, the best score on the public leaderboard is achieved by XGBoost (1154.70), followed by RandomForest (1157.25). I hope this tutorial was helpful for you in understanding how a machine learning competetion is approached and what are the steps one should go through to build a robust model. So, do replicate this analysis and let us know if you face any issues.

LS0tDQp0aXRsZTogIkJpZyBNYXJ0IFNhbGVzOiBQcmFjdGljZSBQcm9ibGVtIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KPGJyPg0KDQojIyBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBpcyBhbiBleHRlbnNpdmUgZXhwbG9yYXRvcnkgdHV0b3JpYWwgb24gdGhlIEJpZyBNYXJ0IFNhbGVzIGNoYWxsZW5nZS4gSXQncyBhIHJlZ3Jlc3Npb24gcHJhY3RpY2UgcHJvYmxlbSB3aGVyZWluIHdlIGhhdmUgdG8gcHJlZGljdCBzYWxlcyBwcm9kdWN0LXdpc2UgYW5kIHN0b3JlLXdpc2UuVGhpcyB0dXRvcmlhbCBpcyBpbnRlbmRlZCBmb3IgdGhlIGJlZ2lubmVycyB3aG8gd2FudCB0byBsZWFybiBob3cgdG8gc29sdmUgYSByZWdyZXNzaW9uIHByb2JsZW0gaW4gUi4gV2Ugd2lsbCBmb2xsb3cgdGhlIGZvbGxvd2luZyB0YWJsZSBvZiBjb250ZW50Lg0KDQo8YnI+DQoNCjEuIFByb2JsZW0gU3RhdGVtZW50DQoyLiBIeXBvdGhlc2lzIEdlbmVyYXRpb24NCjMuIERhdGEgU3RydWN0dXJlIGFuZCBDb250ZW50DQo0LiBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzDQogICAgKyBVbml2YXJpYXRlIEFuYWx5c2lzDQogICAgKyBCaXZhcmlhdGUgQW5hbHlzaXMNCjUuIE1pc3NpbmcgVmFsdWUgVHJlYXRtZW50DQo2LiBGZWF0dXJlIEVuZ2luZWVyaW5nDQo3LiBFbmNvZGluZyBDYXRlZ29yaWNhbCBWYXJpYWJsZXMNCiAgICArIExhYmVsIEVuY29kaW5nDQogICAgKyBPbmUgSG90IEVuY29kaW5nDQo4LiBQcmVQcm9jZXNzaW5nIERhdGENCjkuIE1vZGVsaW5nDQogICAgKyBMaW5lYXIgUmVncmVzc2lvbg0KICAgICsgTGFzc28gUmVncmVzc2lvbg0KICAgICsgUmlkZ2UgUmVncmVzc2lvbg0KICAgICsgUmFuZG9tRm9yZXN0DQogICAgKyBYR0Jvb3N0DQoxMC4gRW5kIE5vdGVzDQogICAgDQoNCjxicj4NCg0KIyMgUHJvYmxlbSBTdGF0ZW1lbnQNCg0KVGhlIGRhdGEgc2NpZW50aXN0cyBhdCBCaWdNYXJ0IGhhdmUgY29sbGVjdGVkIDIwMTMgc2FsZXMgZGF0YSBmb3IgMTU1OSBwcm9kdWN0cyBhY3Jvc3MgMTAgc3RvcmVzIGluIGRpZmZlcmVudCBjaXRpZXMuIEFsc28sIGNlcnRhaW4gYXR0cmlidXRlcyBvZiBlYWNoIHByb2R1Y3QgYW5kIHN0b3JlIGhhdmUgYmVlbiBkZWZpbmVkLiBUaGUgYWltIGlzIHRvIGJ1aWxkIGEgcHJlZGljdGl2ZSBtb2RlbCBhbmQgZmluZCBvdXQgdGhlIHNhbGVzIG9mIGVhY2ggcHJvZHVjdCBhdCBhIHBhcnRpY3VsYXIgc3RvcmUuDQoNClVzaW5nIHRoaXMgbW9kZWwsIEJpZ01hcnQgd2lsbCB0cnkgdG8gdW5kZXJzdGFuZCB0aGUgcHJvcGVydGllcyBvZiBwcm9kdWN0cyBhbmQgc3RvcmVzIHdoaWNoIHBsYXkgYSBrZXkgcm9sZSBpbiBpbmNyZWFzaW5nIHNhbGVzLg0KDQojIyBIeXBvdGhlc2lzIEdlbmVyYXRpb24NCg0KVGhpcyBpcyBhIHZlcnkgaW1wb3J0YW50IHN0YWdlIGluIGFueSBtYWNoaW5lIGxlYXJuaW5nIHByb2Nlc3MuIEl0IGJhc2ljYWxseSBpbnZvbHZlcyBicmFpbnN0b3JtaW5nIGFuZCBjb21pbmcgdXAgd2l0aCBhcyBtYW55IGlkZWFzIGFzIHBvc3NpYmxlIGFib3V0IHdoYXQgY291bGQgYWZmZWN0IHRoZSB0YXJnZXQgdmFyaWFibGUuIEl0IGhlbHBzIHVzIGluIGV4cGxvcmluZyB0aGUgZGF0YSBhdCBoYW5kIG1vcmUgZWZmaWNpZW50bHkgYW5kIGVmZmVjdGl2ZWx5LiBIeXBvdGhlc2lzIEdlbmVyYXRpb24gc2hvdWxkIGJlIGRvbmUgYmVmb3JlIHNlZWluZyB0aGUgZGF0YSBvciBlbHNlIHdlIHdpbGwgZW5kIHVwIHdpdGggYmlhc2VkIGh5cG90aGVzZXMuIEZvbGxvd2luZyBhcmUgc29tZSBvZiB0aGUgaHlwb3RoZXNlcyBiYXNlZCBvbiB0aGUgcHJvYmxlbSBzdGF0ZW1lbnQuIA0KDQoxLiBTYWxlcyBhcmUgaGlnaGVyIGR1cmluZyB3ZWVrZW5kcy4NCjIuIEhpZ2hlciBzYWxlcyBkdXJpbmcgbW9ybmluZyBhbmQgbGF0ZSBldmVuaW5nLg0KMy4gSGlnaGVyIHNhbGVzIGR1cmluZyBlbmQgb2YgdGhlIHllYXIuDQo0LiBTdG9yZSBzaXplIGFmZmVjdHMgdGhlIHNhbGVzLg0KNS4gTG9jYXRpb24gb2YgdGhlIHN0b3JlIGFmZmVjdHMgdGhlIHNhbGVzLg0KNi4gSXRlbXMgd2l0aCBtb3JlIHNoZWxmIHNwYWNlIHNlbGwgbW9yZS4NCg0KPGJyPg0KWW91IGNhbiBjb21lIHVwIHdpdGggbW9yZSBoeXBvdGhlc2VzIG9mIHlvdXIgb3duLCB0aGUgbW9yZSB0aGUgYmV0dGVyLiBMZXQncyBiZWdpbiBleHBsb3JpbmcgdGhlIGRhdGFzZXQgYW5kIHRyeSB0byBmaW5kIGludGVyZXN0aW5nIHBhdHRlcm5zLiANCg0KV2UnbGwgZmlyc3QgbG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMuDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoZTEwNzEpDQpsaWJyYXJ5KHhnYm9vc3QpDQpsaWJyYXJ5KGNvd3Bsb3QpDQpgYGANCg0KV2UgdXNlIGRhdGEudGFibGUncyBmcmVhZCgpIHRvIHNwZWVkIHVwIHJlYWRpbmcgaW4gdGhlIGRhdGFzZXRzLg0KYGBge3J9DQp0cmFpbiA9IGZyZWFkKCJUcmFpbl9VV3U1YlhrLmNzdiIpDQp0ZXN0ID0gZnJlYWQoIlRlc3RfdTk0UTVLVi5jc3YiKQ0Kc3VibWlzc2lvbiA9IGZyZWFkKCJTYW1wbGVTdWJtaXNzaW9uX1Rtbk8zOXkuY3N2IikNCmBgYA0KDQojIyBEYXRhIFN0cnVjdHVyZSBhbmQgQ29udGVudA0KDQpJdCdzIGdvb2QgdG8gcXVpY2t5IGNoZWNrIHRoZSBkaW1lbnNpb25zIG9mIG91ciBkYXRhLg0KYGBge3J9DQpkaW0odHJhaW4pO2RpbSh0ZXN0KQ0KYGBgDQp0cmFpbiBkYXRhc2V0IGhhcyA4NTIzIHJvd3MgYW5kIDEyIGZlYXR1cmVzIGFuZCB0ZXN0IGhhcyA1NjgxIHJvd3MgYW5kIDExIGNvbHVtbnMuIHRyYWluIGhhcyAxIGV4dHJhIGNvbHVtbiB3aGljaCBpcyB0aGUgdGFyZ2V0IHZhcmlhYmxlLiBXZSB3aWxsIHByZWRpY3QgdGhpcyB0YXJnZXQgdmFyaWFibGUgZm9yIHRoZSB0ZXN0IGRhdGFzZXQgbGF0ZXIgaW4gdGhpcyB0dXRvcmlhbC4NCg0KRmVhdHVyZSBuYW1lcyBvZiB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cw0KYGBge3J9DQpuYW1lcyh0cmFpbikNCmBgYA0KYGBge3J9DQpuYW1lcyh0ZXN0KQ0KYGBgDQpJdGVtX091dGxldF9TYWxlcyBpcyBwcmVzZW50IGluIHRyYWluIGJ1dCBub3QgaW4gdGVzdCBkYXRhc2V0IGJlY2F1c2UgdGhpcyBpcyB0aGUgdGFyZ2V0IHZhcmlhYmxlIHRoYXQgd2UgaGF2ZSB0byBwcmVkaWN0Lg0KDQpTdHJ1Y3R1cmUgb2YgdHJhaW4gYW5kIHRlc3QgZGF0YXNldHMNCmBgYHtyfQ0Kc3RyKHRyYWluKQ0KYGBgDQpgYGB7cn0NCnN0cih0ZXN0KQ0KYGBgDQoNClRvIGV4cGxvcmUgZGF0YSBpbiBhbnkgZGF0YSBzY2llbmNlIGNvbXBldGV0aW9uLCBpdCBpcyBhZHZpc2FibGUgdG8gYXBwZW5kIHRlc3QgZGF0YSB0byB0aGUgdHJhaW4gZGF0YS4gU28sIHdlIHdpbGwgY29tYmluZSBib3RoIHRyYWluIGFuZCB0ZXN0IHRvIGNhcnJ5IG91dCBkYXRhIHZpc3VhbGl6YXRpb24sIGZlYXR1cmUgZW5naW5lZXJpbmcsIG9uZS1ob3QgZW5jb2RpbmcsIGFuZCBsYWJlbCBlbmNvZGluZy4gTGF0ZXIgd2Ugd291bGQgc3BsaXQgdGhpcyBjb21iaW5lZCBkYXRhIGJhY2sgdG8gdHJhaW4gYW5kIHRlc3QgZGF0YXNldHMuDQpgYGB7cn0NCnRlc3RbLEl0ZW1fT3V0bGV0X1NhbGVzIDo9IE5BXQ0KDQpjb21iaSA9IHJiaW5kKHRyYWluLCB0ZXN0KSAjIGNvbWJpbmluZyB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cw0KZGltKGNvbWJpKQ0KYGBgDQoNCjxicj4NCg0KIyMgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcw0KIyMjIFVuaXZhcmlhdGUgQW5hbHlzaXMNCldlIHdpbGwgc3RhcnQgb2ZmIGJ5IHBsb3RpbmcgYW5kIGV4cGxvcmluZyBhbGwgdGhlIGluZGl2aWR1YWwgdmFyaWFibGVzIHRvIGdhaW4gc29tZSBpbnNpZ2h0cy4gSW4gdGhpcyB0dXRvcmlhbCBnZ3Bsb3QyIHBhY2thZ2UgaGFzIGJlZW4gdXNlZCB0byBnZW5lcmF0ZSBhbGwgdGhlIHBsb3RzLg0KDQo8YnI+DQoNCiMjIyMgVGFyZ2V0IFZhcmlhYmxlDQoNClNpbmNlIG91ciB0YXJnZXQgdmFyaWFibGUgaXMgY29udGludW91cywgd2UgY2FuIHZpc3VhbGlzZSBpdCBieSBwbG90dGluZyBpdHMgaGlzdG9ncmFtLg0KYGBge3J9DQpnZ3Bsb3QodHJhaW4pICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHRyYWluJEl0ZW1fT3V0bGV0X1NhbGVzKSwgYmlud2lkdGggPSAxMDAsIGZpbGwgPSAiZGFya2dyZWVuIikgKw0KICB4bGFiKCJJdGVtX091dGxldF9TYWxlcyIpDQpgYGANCg0KDQpBcyB5b3UgY2FuIHNlZSwgaXQgaXMgYSByaWdodCBza2V3ZCB2YXJpYWJsZSBhbmQgd291bGQgbmVlZCBzb21lIGRhdGEgdHJhbnNmb3JtYXRpb24gdG8gdHJlYXQgaXRzIHNrZXduZXNzLiBXZSB3aWxsIGNvbWUgYmFjayB0byBpdCBsYXRlci4NCg0KPGJyPg0KDQojIyMjIEluZGVwZW5kZW50IFZhcmlhYmxlcyAobnVtZXJpYyB2YXJpYWJsZXMpDQoNCk5vdyBsZXQncyBjaGVjayB0aGUgbnVtZXJpYyBpbmRlcGVuZGVudCB2YXJpYWJsZXMuIFdlJ2xsIGFnYWluIHVzZSB0aGUgaGlzdG9ncmFtcyBmb3IgdmlzdWFsaXphdGlvbnMgYmVjYXVzZSB0aGF0IHdpbGwgaGVscCB1cyBpbiB2aXN1YWxpemluZyB0aGUgZGlzdHJpYnV0aW9uIG9mIGluZGl2aWR1YWwgdmFyaWFibGVzLg0KDQpgYGB7ciwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9OX0NCnAxID0gZ2dwbG90KGNvbWJpKSArIGdlb21faGlzdG9ncmFtKGFlcyhJdGVtX1dlaWdodCksIGJpbndpZHRoID0gMC41LCBmaWxsID0gImJsdWUiKQ0KcDIgPSBnZ3Bsb3QoY29tYmkpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKEl0ZW1fVmlzaWJpbGl0eSksIGJpbndpZHRoID0gMC4wMDUsIGZpbGwgPSAiYmx1ZSIpDQpwMyA9IGdncGxvdChjb21iaSkgKyBnZW9tX2hpc3RvZ3JhbShhZXMoSXRlbV9NUlApLCBiaW53aWR0aCA9IDEsIGZpbGwgPSAiYmx1ZSIpDQoNCnBsb3RfZ3JpZChwMSwgcDIsIHAzLCBucm93ID0gMSkgIyBwbG90X2dyaWQoKSBmcm9tIGNvd3Bsb3QgcGFja2FnZQ0KYGBgDQoNCkFzIHlvdSBjYW4gc2VlLCB0aGVyZSBpcyBubyBjbGVhciBwYXR0ZXJuIGluIEl0ZW1fV2VpZ2h0IGFuZCBJdGVtX01SUC4gSG93ZXZlciwgSXRlbV9WaXNpYmlsaXR5IGlzIHJpZ2h0LXNrZXdlZCBhbmQgc2hvdWxkIGJlIHRyYW5zZm9ybWVkIHRvIGN1cmIgaXRzIHNrZXduZXNzLiAgIA0KDQo8YnI+DQoNCiMjIyMgSW5kZXBlbmRlbnQgVmFyaWFibGVzIChjYXRlZ29yaWNhbCB2YXJpYWJsZXMpDQoNCk5vdyB3ZSdsbCB0cnkgdG8gZXhwbG9yZSBhbmQgZ2FpbiBzb21lIGluc2lnaHRzIGZyb20gdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gQSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBvciBmZWF0dXJlIGNhbiBoYXZlIG9ubHkgYSBmaW5pdGUgc2V0IG9mIHZhbHVlcy4gTGV0J3MgZmlyc3QgcGxvdCBJdGVtX0ZhdF9Db250ZW50Lg0KDQpgYGB7cn0NCmdncGxvdChjb21iaSAlPiUgZ3JvdXBfYnkoSXRlbV9GYXRfQ29udGVudCkgJT4lIHN1bW1hcmlzZShDb3VudCA9IG4oKSkpICsgDQogIGdlb21fYmFyKGFlcyhJdGVtX0ZhdF9Db250ZW50LCBDb3VudCksIHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImNvcmFsMSIpDQpgYGANCg0KPGJyPg0KDQpJbiB0aGUgZmlndXJlIGFib3ZlLCAnTEYnLCAnbG93IGZhdCcsIGFuZCAnTG93IEZhdCcgYXJlIHRoZSBzYW1lIGNhdGVnb3J5IGFuZCBjYW4gYmUgY29tYmluZWQgaW50byBvbmUuIFNpbWlsYXJseSB3ZSBjYW4gYmUgZG9uZSBmb3IgJ3JlZycgYW5kICdSZWd1bGFyJyBpbnRvIG9uZS4gQWZ0ZXIgbWFraW5nIHRoZXNlIGNvcnJlY3Rpb25zIHdlJ2xsIHBsb3QgdGhlIHNhbWUgZmlndXJlIGFnYWluLg0KDQpgYGB7cn0NCmNvbWJpJEl0ZW1fRmF0X0NvbnRlbnRbY29tYmkkSXRlbV9GYXRfQ29udGVudCA9PSAiTEYiXSA9ICJMb3cgRmF0Ig0KY29tYmkkSXRlbV9GYXRfQ29udGVudFtjb21iaSRJdGVtX0ZhdF9Db250ZW50ID09ICJsb3cgZmF0Il0gPSAiTG93IEZhdCINCmNvbWJpJEl0ZW1fRmF0X0NvbnRlbnRbY29tYmkkSXRlbV9GYXRfQ29udGVudCA9PSAicmVnIl0gPSAiUmVndWxhciINCg0KZ2dwbG90KGNvbWJpICU+JSBncm91cF9ieShJdGVtX0ZhdF9Db250ZW50KSAlPiUgc3VtbWFyaXNlKENvdW50ID0gbigpKSkgKyANCiAgZ2VvbV9iYXIoYWVzKEl0ZW1fRmF0X0NvbnRlbnQsIENvdW50KSwgc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAiY29yYWwxIikNCmBgYA0KDQo8YnI+DQoNCk5vdyBsZXQncyBjaGVjayB0aGUgb3RoZXIgY2F0ZWdvcmljYWwgdmFyaWFibGVzLg0KDQpgYGB7ciwgZmlnLmhlaWdodD05fQ0KcDQgPSBnZ3Bsb3QoY29tYmkgJT4lIGdyb3VwX2J5KEl0ZW1fVHlwZSkgJT4lIHN1bW1hcmlzZShDb3VudCA9IG4oKSkpICsgDQogIGdlb21fYmFyKGFlcyhJdGVtX1R5cGUsIENvdW50KSwgc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAiY29yYWwxIikgKw0KICB4bGFiKCIiKSArDQogIGdlb21fbGFiZWwoYWVzKEl0ZW1fVHlwZSwgQ291bnQsIGxhYmVsID0gQ291bnQpLCB2anVzdCA9IDAuNSkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsNCiAgZ2d0aXRsZSgiSXRlbV9UeXBlIikNCg0KcDUgPSBnZ3Bsb3QoY29tYmkgJT4lIGdyb3VwX2J5KE91dGxldF9JZGVudGlmaWVyKSAlPiUgc3VtbWFyaXNlKENvdW50ID0gbigpKSkgKyANCiAgZ2VvbV9iYXIoYWVzKE91dGxldF9JZGVudGlmaWVyLCBDb3VudCksIHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImNvcmFsMSIpICsNCiAgZ2VvbV9sYWJlbChhZXMoT3V0bGV0X0lkZW50aWZpZXIsIENvdW50LCBsYWJlbCA9IENvdW50KSwgdmp1c3QgPSAwLjUpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCg0KcDYgPSBnZ3Bsb3QoY29tYmkgJT4lIGdyb3VwX2J5KE91dGxldF9TaXplKSAlPiUgc3VtbWFyaXNlKENvdW50ID0gbigpKSkgKyANCiAgZ2VvbV9iYXIoYWVzKE91dGxldF9TaXplLCBDb3VudCksIHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImNvcmFsMSIpICsNCiAgZ2VvbV9sYWJlbChhZXMoT3V0bGV0X1NpemUsIENvdW50LCBsYWJlbCA9IENvdW50KSwgdmp1c3QgPSAwLjUpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCg0Kc2Vjb25kX3JvdyA9IHBsb3RfZ3JpZChwNSwgcDYsIG5yb3cgPSAxKQ0KDQpwbG90X2dyaWQocDQsIHNlY29uZF9yb3csIG5jb2wgPSAxKQ0KYGBgDQoNCjxicj4NCg0KSW4gT3V0bGV0X1NpemUncyBwbG90LCBmb3IgNDAxNiBvYnNlcnZhdGlvbnMsIE91dGxldF9TaXplIGlzIGJsYW5rIG9yIG1pc3NpbmcuV2Ugd2lsbCBjaGVjayBmb3IgdGhpcyBpbiB0aGUgYml2YXJpYXRlIGFuYWx5c2lzIHRvIHN1YnN0aXR1dGUgdGhlIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBPdXRsZXRfU2l6ZS4NCg0KYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTExfQ0KcDcgPSBnZ3Bsb3QoY29tYmkgJT4lIGdyb3VwX2J5KE91dGxldF9Fc3RhYmxpc2htZW50X1llYXIpICU+JSBzdW1tYXJpc2UoQ291bnQgPSBuKCkpKSArIA0KICBnZW9tX2JhcihhZXMoZmFjdG9yKE91dGxldF9Fc3RhYmxpc2htZW50X1llYXIpLCBDb3VudCksIHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImNvcmFsMSIpICsNCiAgZ2VvbV9sYWJlbChhZXMoZmFjdG9yKE91dGxldF9Fc3RhYmxpc2htZW50X1llYXIpLCBDb3VudCwgbGFiZWwgPSBDb3VudCksIHZqdXN0ID0gMC41KSArDQogIHhsYWIoIk91dGxldF9Fc3RhYmxpc2htZW50X1llYXIiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4LjUpKQ0KDQpwOCA9IGdncGxvdChjb21iaSAlPiUgZ3JvdXBfYnkoT3V0bGV0X1R5cGUpICU+JSBzdW1tYXJpc2UoQ291bnQgPSBuKCkpKSArIA0KICBnZW9tX2JhcihhZXMoT3V0bGV0X1R5cGUsIENvdW50KSwgc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAiY29yYWwxIikgKw0KICBnZW9tX2xhYmVsKGFlcyhmYWN0b3IoT3V0bGV0X1R5cGUpLCBDb3VudCwgbGFiZWwgPSBDb3VudCksIHZqdXN0ID0gMC41KSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4LjUpKQ0KDQpwbG90X2dyaWQocDcsIHA4LCBuY29sID0gMikNCmBgYA0KDQpUaGVyZSBhcmUgbGVzc2VyIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gdGhlIGRhdGEgZm9yIHRoZSBvdXRsZXRzIGVzdGFibGlzaGVkIGluIHRoZSB5ZWFyIDE5OTggYXMgY29tcGFyZWQgdG8gdGhlIG90aGVyIHllYXJzLiBTdXBlcm1hcmtldCBUeXBlIDEgc2VlbXMgdG8gYmUgdGhlIG1vc3QgcG9wdWxhciBjYXRlZ29yeSBvZiBPdXRsZXRfVHlwZS4NCg0KPGJyPg0KDQojIyMgQml2YXJpYXRlIEFuYWx5c2lzDQoNCkFmdGVyIGxvb2tpbmcgYXQgZXZlcnkgZmVhdHVyZSBpbmRpdmlkdWFsbHksIGxldCdzIG5vdyBleHBsb3JlIHRoZW0gYWdhaW4gd2l0aCByZXNwZWN0IHRvIHRoZSB0YXJnZXQgdmFyaWFibGUuIEhlcmUgd2Ugd2lsbCBtYWtlIHVzZSBvZiBzY2F0dGVyIHBsb3RzIGZvciBjb250aW51b3VzIG9yIG51bWVyaWMgdmFyaWFibGVzIGFuZCB2aW9saW4gcGxvdHMgZm9yIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0KdHJhaW4gPSBjb21iaVsxOm5yb3codHJhaW4pXQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aCA9IDl9DQpwOSA9IGdncGxvdCh0cmFpbikgKyBnZW9tX3BvaW50KGFlcyhJdGVtX1dlaWdodCwgSXRlbV9PdXRsZXRfU2FsZXMpLCBjb2xvdXIgPSAidmlvbGV0IiwgYWxwaGEgPSAwLjMpICsNCiAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOC41KSkNCg0KcDEwID0gZ2dwbG90KHRyYWluKSArIGdlb21fcG9pbnQoYWVzKEl0ZW1fVmlzaWJpbGl0eSwgSXRlbV9PdXRsZXRfU2FsZXMpLCBjb2xvdXIgPSAidmlvbGV0IiwgYWxwaGEgPSAwLjMpICsNCiAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDguNSkpDQoNCnAxMSA9IGdncGxvdCh0cmFpbikgKyBnZW9tX3BvaW50KGFlcyhJdGVtX01SUCwgSXRlbV9PdXRsZXRfU2FsZXMpLCBjb2xvdXIgPSAidmlvbGV0IiwgYWxwaGEgPSAwLjMpICsNCiAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDguNSkpDQoNCnNlY29uZF9yb3dfMiA9IHBsb3RfZ3JpZChwMTAsIHAxMSwgbmNvbCA9IDIpDQoNCnBsb3RfZ3JpZChwOSwgc2Vjb25kX3Jvd18yLCBucm93ID0gMikNCmBgYA0KSXRlbV9PdXRsZXRfU2FsZXMgaXMgc3ByZWFkIHdlbGwgYWNyb3NzIHRoZSBlbnRpcmUgcmFuZ2Ugb2YgdGhlIEl0ZW1fV2VpZ2h0IHdpdGhvdXQgYW55IG9idmlvdXMgcGF0dGVybi4gSW4gdGhlIEl0ZW1fVmlzaWJpbGl0eSB2cyBJdGVtX091dGxldF9TYWxlcywgdGhlcmUgaXMgYSBzdHJpbmcgb2YgcG9pbnRzIGF0IEl0ZW1fVmlzaWJpbGl0eSA9IDAuMCB3aGljaCBzZWVtcyBzdHJhbmdlIGFzIGl0ZW0gdmlzaWJpbGl0eSBjYW5ub3QgYmUgY29tcGxldGVseSB6ZXJvLiBXZSB3aWxsIHRha2Ugbm90ZSBvZiB0aGlzIGlzc3VlIGFuZCBkZWFsIHdpdGggaXQgaW4gdGhlIGxhdGVyIHN0YWdlcy4gDQoNCkluIHRoZSB0aGlyZCBwbG90IG9mIEl0ZW1fTVJQIHZzIEl0ZW1fT3V0bGV0X1NhbGVzLCB3ZSBjYW4gY2xlYXJseSBzZWUgNCBzZWdtZW50cyBvZiBwcmljZXMgdGhhdCBjYW4gYmUgdXNlZCBpbiBmZWF0dXJlIGVuZ2luZWVyaW5nIHRvIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZS4gDQoNCk5vdyB3ZSdsbCB2aXN1YWxpc2UgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB3aXRoIHJlc3BlY3QgdG8gSXRlbV9PdXRsZXRfU2FsZXMuIFdlIHdpbGwgdHJ5IHRvIGNoZWNrIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHRhcmdldCB2YXJpYWJsZSBhY3Jvc3MgYWxsIHRoZSBjYXRlZ29yaWVzIG9mIGVhY2ggb2YgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlLiBXZSBjYW4gZG8gdGhpcyB1c2luZyBib3hwbG90cyBidXQgaW5zdGVhZCB3ZSdsbCB1c2UgdGhlIHZpb2xpbiBwbG90cyBhcyB0aGV5IHNob3cgdGhlIGZ1bGwgZGlzdHJpYnV0aW9uIG9mIHRoZSBkYXRhLiBUaGUgaG9yaXpvbnRhbCB3aWR0aCBvZiBhIHZpb2xpbiBwbG90IGF0IGEgcGFydGljdWxhciBsZXZlbCBpbmRpY2F0ZXMgdGhlIGNvbmNlbnRyYXRpb24gb2YgZGF0YSBhdCB0aGF0IGxldmVsLg0KDQpgYGB7ciwgZmlnLndpZHRoPTl9DQpwMTIgPSBnZ3Bsb3QodHJhaW4pICsgZ2VvbV92aW9saW4oYWVzKEl0ZW1fVHlwZSwgSXRlbV9PdXRsZXRfU2FsZXMpLCBmaWxsID0gIm1hZ2VudGEiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksDQogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDguNSkpDQoNCnAxMyA9IGdncGxvdCh0cmFpbikgKyBnZW9tX3Zpb2xpbihhZXMoSXRlbV9GYXRfQ29udGVudCwgSXRlbV9PdXRsZXRfU2FsZXMpLCBmaWxsID0gIm1hZ2VudGEiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksDQogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDguNSkpDQoNCnAxNCA9IGdncGxvdCh0cmFpbikgKyBnZW9tX3Zpb2xpbihhZXMoT3V0bGV0X0lkZW50aWZpZXIsIEl0ZW1fT3V0bGV0X1NhbGVzKSwgZmlsbCA9ICJtYWdlbnRhIikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpLA0KICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA4LjUpKQ0KDQpzZWNvbmRfcm93XzMgPSBwbG90X2dyaWQocDEzLCBwMTQsIG5jb2wgPSAyKQ0KDQpwbG90X2dyaWQocDEyLCBzZWNvbmRfcm93XzMsIG5jb2wgPSAxKQ0KYGBgDQoNCkRpc3RyaWJ1dGlvbiBvZiBJdGVtX091dGxldF9TYWxlcyBhY3Jvc3MgdGhlIGNhdGVnb3JpZXMgb2YgSXRlbV9UeXBlIGlzIG5vdCB2ZXJ5IGRpc3RpbmN0IGFuZCBzYW1lIGlzIHRoZSBjYXNlIHdpdGggSXRlbV9GYXRfQ29udGVudC4gSG93ZXZlciwgdGhlIGRpc3RyaWJ1dGlvbiBmb3IgT1VUMDEwIGFuZCBPVVQwMTkgY2F0ZWdvcmllcyBvZiBPdXRsZXRfSWRlbnRpZmllciBhcmUgcXVpdGUgc2ltaWxhciBhbmQgdmVyeSBtdWNoIGRpZmZlcmVudCBmcm9tIHRoZSByZXN0IG9mIHRoZSBjYXRlZ29yaWVzIG9mIE91dGxldF9JZGVudGlmaWVyLg0KDQpJbiB0aGUgdW5pdmFyaWF0ZSBhbmFseXNpcywgd2UgY2FtZSB0byBrbm93IGFib3V0IHRoZSBlbXB0eSB2YWx1ZXMgaW4gT3V0bGV0X1NpemUgdmFyaWFibGUuIExldCdzIGNoZWNrIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHRhcmdldCB2YXJpYWJsZSBhY3Jvc3MgT3V0bGV0X1NpemUuDQoNCmBgYHtyLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00fQ0KZ2dwbG90KHRyYWluKSArIGdlb21fdmlvbGluKGFlcyhPdXRsZXRfU2l6ZSwgSXRlbV9PdXRsZXRfU2FsZXMpLCBmaWxsID0gIm1hZ2VudGEiKQ0KYGBgDQoNClRoZSBkaXN0cmlidXRpb24gb2YgJ1NtYWxsJyBPdXRsZXRfU2l6ZSBpcyBhbG1vc3QgaWRlbnRpY2FsIHRvIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGJsYW5rIGNhdGVnb3J5IChmaXJzdCB2aW9pbikgb2YgT3V0bGV0X1NpemUuIFNvLCB3ZSBjYW4gc3Vic3RpdHV0ZSB0aGUgYmxhbmtzIGluIE91dGxldF9TaXplIHdpdGggJ1NtYWxsJy4gUGxlYXNlIG5vdGUgdGhhdCB0aGlzIGlzIG5vdCB0aGUgb25seSB3YXkgdG8gaW1wdXRlIG1pc3NpbmcgdmFsdWVzLCBidXQgZm9yIHRoZSB0aW1lIGJlaW5nIHdlIHdpbGwgZ28gYWhlYWQgYW5kIGltcHV0ZSB0aGUgbWlzc2luZyB2YWx1ZXMgd2l0aCAnU21hbGwnLiANCg0KTGV0J3MgZXhhbWluZSB0aGUgcmVtYWluaW5nIHZhcmlhYmxlcy4NCg0KYGBge3J9DQpwMTUgPSBnZ3Bsb3QodHJhaW4pICsgZ2VvbV92aW9saW4oYWVzKE91dGxldF9Mb2NhdGlvbl9UeXBlLCBJdGVtX091dGxldF9TYWxlcyksIGZpbGwgPSAibWFnZW50YSIpDQoNCnAxNiA9IGdncGxvdCh0cmFpbikgKyBnZW9tX3Zpb2xpbihhZXMoT3V0bGV0X1R5cGUsIEl0ZW1fT3V0bGV0X1NhbGVzKSwgZmlsbCA9ICJtYWdlbnRhIikNCg0KcGxvdF9ncmlkKHAxNSwgcDE2LCBuY29sID0gMSkNCmBgYA0KDQpUaWVyIDEgYW5kIFRpZXIgMyBsb2NhdGlvbnMgb2YgT3V0bGV0X0xvY2F0aW9uX1R5cGUgbG9vayBzaW1pbGFyLiBJbiB0aGUgT3V0bGV0X1R5cGUsIEdyb2NlcnkgU3RvcmUgaGFzIG1vc3Qgb2YgaXRzIGRhdGEgcG9pbnRzIGFyb3VuZCB0aGUgbG93ZXIgc2FsZXMgdmFsdWVzIGFzIGNvbXBhcmVkIHRvIHRoZSBvdGhlciBjYXRlZ29yaWVzLiBUaGVzZSBhcmUgdGhlIGtpbmQgb2YgaW5zaWdodHMgdGhhdCB3ZSBjYW4gZXh0cmFjdCBieSB2aXN1YWxpemluZyBvdXIgZGF0YS4gSGVuY2UsIGRhdGEgdmlzdWFsaXphdGlvbiBzaG91bGQgYmUgYW4gaW1wb3J0YW50IHBhcnQgb2YgYW55IGtpbmQgZGF0YSBhbmFseXNpcy4NCg0KPGJyPg0KDQojIyBNaXNzaW5nIFZhbHVlIFRyZWF0bWVudA0KDQpNaXNzaW5nIGRhdGEgY2FuIGhhdmUgYSBzZXZlcmUgaW1wYWN0IG9uIGJ1aWxkaW5nIHByZWRpY3RpdmUgbW9kZWxzIGJlY2F1c2UgdGhlIG1pc3NpbmcgdmFsdWVzIG1pZ2h0IGJlIGNvbnRhaW4gc29tZSB2aXRhbCBpbmZvcm1hdGlvbiB3aGljaCBjb3VsZCBoZWxwIGluIG1ha2luZyBiZXR0ZXIgcHJlZGljdGlvbnMuIFNvLCBpdCBiZWNvbWVzIGltcGVyYXRpdmUgdG8gY2Fycnkgb3V0IG1pc3NpbmcgZGF0YSBpbXB1dGF0aW9uLg0KVGhlcmUgYXJlIGRpZmZlcmVudCBtZXRob2RzIHRvIHRyZWF0IG1pc3NpbmcgdmFsdWVzIGJhc2VkIG9uIHRoZSBwcm9ibGVtIGFuZCB0aGUgZGF0YS4gU29tZSBvZiB0aGUgY29tbW9uIHRlY2huaXF1ZXMgYXJlIGFzIGZvbGxvd3M6DQoNCjEuIF9fRGVsZXRpb24gb2Ygcm93c19fOiBJbiB0cmFpbiBkYXRhc2V0LCBvYnNlcnZhdGlvbnMgaGF2aW5nIG1pc3NpbmcgdmFsdWVzIGluIGFueSB2YXJpYWJsZSBhcmUgZGVsZXRlZC4gVGhlIGRvd25zaWRlIG9mIHRoaXMgbWV0aG9kIGlzIHRoZSBsb3NzIG9mIGluZm9ybWF0aW9uIGFuZCBkcm9wIGluIHByZWRpY3Rpb24gcG93ZXIgb2YgbW9kZWwuDQoNCjIuIF9fTWVhbi9NZWRpYW4vTW9kZSBJbXB1dGF0aW9uX186IEluIGNhc2Ugb2YgY29udGludW91cyB2YXJpYWJsZSwgbWlzc2luZyB2YWx1ZXMgY2FuIGJlIHJlcGxhY2VkIHdpdGggbWVhbiBvciBtZWRpYW4gb2YgYWxsIGtub3duIHZhbHVlcyBvZiB0aGF0IHZhcmlhYmxlLiBGb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCB3ZSBjYW4gdXNlIG1vZGUgb2YgdGhlIGdpdmVuIHZhbHVlcyB0byByZXBsYWNlIHRoZSBtaXNzaW5nIHZhbHVlcy4gDQoNCjMuIF9fQnVpbGRpbmcgUHJlZGljdGlvbiBNb2RlbF9fOiBXZSBjYW4gZXZlbiBtYWtlIGEgcHJlZGljdGl2ZSBtb2RlbCB0byBpbXB1dGUgbWlzc2luZyBkYXRhIGluIGEgdmFyaWFibGUuIEhlcmUgd2Ugd2lsbCB0cmVhdCB0aGUgdmFyaWFibGUgaGF2aW5nIG1pc3NpbmcgZGF0YSBhcyB0aGUgdGFyZ2V0IHZhcmlhYmxlIGFuZCB0aGUgb3RoZXIgdmFyaWFibGVzIGFzIHByZWRpY3RvcnMuIFdlIHdpbGwgZGl2aWRlIG91ciBkYXRhIGludG8gMiBkYXRhc2V0c+KAlG9uZSB3aXRob3V0IGFueSBtaXNzaW5nIHZhbHVlIGZvciB0aGF0IHZhcmlhYmxlIGFuZCB0aGUgb3RoZXIgd2l0aCBtaXNzaW5nIHZhbHVlcyBmb3IgdGhhdCB2YXJpYWJsZS4gVGhlIGZvcm1lciBzZXQgd291bGQgYmUgdXNlZCBhcyB0cmFpbmluZyBzZXQgdG8gYnVpbGQgdGhlIHByZWRpY3RpdmUgbW9kZWwgYW5kIGl0IHdvdWxkIHRoZW4gYmUgYXBwbGllZCB0byB0aGUgbGF0dGVyIHNldCB0byBwcmVkaWN0IHRoZSBtaXNzaW5nIHZhbHVlcy4NCg0KWW91IGNhbiB0cnkgdGhlIGZvbGxvd2luZyBjb2RlIHRvIHF1aWNrbHkgZmluZCBtaXNzaW5nIHZhbHVlcyBpbiBhIGRhdGFzZXQuDQoNCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShjb21iaSkpDQpgYGANCg0KQXMgeW91IGNhbiBzZWUgYWJvdmUsIHdlIGhhdmUgbWlzc2luZyB2YWx1ZXMgaW4gSXRlbV9XZWlnaHQgYW5kIEl0ZW1fT3V0bGV0X1NhbGVzLiBNaXNzaW5nIGRhdGEgaW4gSXRlbV9PdXRsZXRfU2FsZXMgY2FuIGJlIGlnbm9yZWQgc2luY2UgdGhleSBiZWxvbmcgdG8gdGhlIHRlc3QgZGF0YXNldC4gV2UnbGwgbm93IGltcHV0ZSBJdGVtX1dlaWdodCB3aXRoIG1lYW4gd2VpZ2h0IGJhc2VkIG9uIHRoZSBJdGVtX0lkZW50aWZpZXIgdmFyaWFiZS4NCg0KYGBge3J9DQptaXNzaW5nX2luZGV4ID0gd2hpY2goaXMubmEoY29tYmkkSXRlbV9XZWlnaHQpKQ0KZm9yKGkgaW4gbWlzc2luZ19pbmRleCl7DQogIA0KICBpdGVtID0gY29tYmkkSXRlbV9JZGVudGlmaWVyW2ldDQogIGNvbWJpJEl0ZW1fV2VpZ2h0W2ldID0gbWVhbihjb21iaSRJdGVtX1dlaWdodFtjb21iaSRJdGVtX0lkZW50aWZpZXIgPT0gaXRlbV0sIG5hLnJtID0gVCkNCiAgDQp9DQpgYGANCg0KIyMjIyBSZXBsYWNpbmcgMCdzIGluIEl0ZW1fVmlzaWJpbGl0eSB2YXJpYWJsZSANCg0KYGBge3J9DQp6ZXJvX2luZGV4ID0gd2hpY2goY29tYmkkSXRlbV9WaXNpYmlsaXR5ID09IDApDQpmb3IoaSBpbiB6ZXJvX2luZGV4KXsNCiAgDQogIGl0ZW0gPSBjb21iaSRJdGVtX0lkZW50aWZpZXJbaV0NCiAgY29tYmkkSXRlbV9WaXNpYmlsaXR5W2ldID0gbWVhbihjb21iaSRJdGVtX1Zpc2liaWxpdHlbY29tYmkkSXRlbV9JZGVudGlmaWVyID09IGl0ZW1dLCBuYS5ybSA9IFQpDQogIA0KfQ0KYGBgDQoNCiMjIEZlYXR1cmUgRW5naW5lZXJpbmcNCg0KTW9zdCBvZiB0aGUgdGltZXMgdGhlIGdpdmVuIGZlYXR1cmVzIGluIGEgZGF0YXNldCBhcmUgbm90IGVub3VnaCB0byBnaXZlIHNhdGlzZmFjdG9yeSBwcmVkaWN0aW9ucy4gSW4gc3VjaCBjYXNlcywgd2UgaGF2ZSB0byBjcmVhdGUgbmV3IGZlYXR1cmVzIHdoaWNoIG1pZ2h0IGhlbHAgaW4gaW1wcm92aW5nIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlLiBMZXQncyB0cnkgdG8gY3JlYXRlIHNvbWUgbmV3IGZlYXR1cmVzIGZvciBvdXIgZGF0YXNldC4gDQoNCldlIGNhbiBoYXZlIGEgbG9vayBhdCB0aGUgSXRlbV9UeXBlIHZhcmlhYmxlIGFuZCBjbGFzc2lmeSB0aGUgY2F0ZWdvcmllcyBpbnRvIHBlcmlzaGFibGUgYW5kIG5vbl9wZXJpc2hhYmxlIGFzIHBlciBvdXIgdW5kZXJzdGFuZGluZyBhbmQgbWFrZSBpdCBpbnRvIGEgbmV3IGZlYXR1cmUuDQoNCmBgYHtyfQ0KIyBjcmVhdGUgYSBuZXcgZmVhdHVyZSAnSXRlbV9UeXBlX25ldycgDQpwZXJpc2hhYmxlID0gYygiQnJlYWRzIiwgIkJyZWFrZmFzdCIsICJEYWlyeSIsICJGcnVpdHMgYW5kIFZlZ2V0YWJsZXMiLCAiTWVhdCIsICJTZWFmb29kIikNCm5vbl9wZXJpc2hhYmxlID0gYygiQmFraW5nIEdvb2RzIiwgIkNhbm5lZCIsICJGcm96ZW4gRm9vZHMiLCAiSGFyZCBEcmlua3MiLCAiSGVhbHRoIGFuZCBIeWdpZW5lIiwNCiAgICAgICAgICAgICAgICAgICAiSG91c2Vob2xkIiwgIlNvZnQgRHJpbmtzIikNCg0KY29tYmlbLEl0ZW1fVHlwZV9uZXcgOj0gaWZlbHNlKEl0ZW1fVHlwZSAlaW4lIHBlcmlzaGFibGUsICJwZXJpc2hhYmxlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoSXRlbV9UeXBlICVpbiUgbm9uX3BlcmlzaGFibGUsICJub25fcGVyaXNoYWJsZSIsICJub3Rfc3VyZSIpKV0NCmBgYA0KDQpMZXQncyBjb21wYXJlIEl0ZW1fVHlwZSB3aXRoIHRoZSBmaXJzdCAgMiBjaGFyYWN0ZXMgb2YgSXRlbV9JZGVudGlmaWVyLCBpLmUuLCAnRFInLCAnRkQnLCBhbmQgJ05DJy4gVGhlc2UgaWRlbnRpZmllcnMgbW9zdCBwcm9iYWJseSBzdGFuZCBkcmlua3MsIGZvb2QsIGFuZCBub24tY29uc3VtYWJsZS4gDQoNCmBgYHtyfQ0KdGFibGUoY29tYmkkSXRlbV9UeXBlLCBzdWJzdHIoY29tYmkkSXRlbV9JZGVudGlmaWVyLCAxLCAyKSkNCmBgYA0KDQpCYXNlZCBvbiB0aGUgYWJvdmUgdGFibGUgd2UgY2FuIGNyZWF0ZSBhIG5ldyBmZWF0dXJlLiBMZXQncyBjYWxsIGl0IEl0ZW1fY2F0ZWdvcnkuIA0KYGBge3J9DQpjb21iaVssSXRlbV9jYXRlZ29yeSA6PSBzdWJzdHIoY29tYmkkSXRlbV9JZGVudGlmaWVyLCAxLCAyKV0NCmBgYA0KDQpXZSB3aWxsIGFsc28gY2hhbmdlIHRoZSB2YWx1ZXMgb2YgSXRlbV9GYXRfQ29udGVudCB3aGVyZWV2ZXIgSXRlbV9jYXRlZ29yeSBpcyAnTkMnIGJlY2F1c2Ugbm9uLWNvbnN1bWFibGUgaXRlbXMgY2Fubm90IGhhdmUgYW55IGZhdCBjb250ZW50LiBXZSB3aWxsIGFsc28gY3JlYXRlIGEgY291cGxlIG9mIG1vcmUgZmVhdHVyZXMg4oCUIE91dGxldF9ZZWFycyAoeWVhcnMgb2Ygb3BlcmF0aW9uKSBhbmQgcHJpY2VfcGVyX3VuaXRfd3QgKHByaWNlIHBlciB1bml0IHdlaWdodCkuDQoNCmBgYHtyfQ0KY29tYmkkSXRlbV9GYXRfQ29udGVudFtjb21iaSRJdGVtX2NhdGVnb3J5ID09ICJOQyJdID0gIk5vbi1FZGlibGUiDQoNCiMgeWVhcnMgb2Ygb3BlcmF0aW9uIG9mIG91dGxldHMNCmNvbWJpWyxPdXRsZXRfWWVhcnMgOj0gMjAxMyAtIE91dGxldF9Fc3RhYmxpc2htZW50X1llYXJdDQpjb21iaSRPdXRsZXRfRXN0YWJsaXNobWVudF9ZZWFyID0gYXMuZmFjdG9yKGNvbWJpJE91dGxldF9Fc3RhYmxpc2htZW50X1llYXIpDQoNCiMgUHJpY2UgcGVyIHVuaXQgd2VpZ2h0DQpjb21iaVsscHJpY2VfcGVyX3VuaXRfd3QgOj0gSXRlbV9NUlAvSXRlbV9XZWlnaHRdDQpgYGANCg0KRWFybGllciBpbiB0aGUgSXRlbV9NUlAgdnMgSXRlbV9PdXRsZXRfU2FsZXMgcGxvdCwgd2Ugc2F3IEl0ZW1fTVJQIHdhcyBzcHJlYWQgYWNyb3NzIGluIDQgY2h1bmtzLiBXZSBjYW4gdXNlIGsgTWVhbnMgY2x1c3RlcmluZyB0byBjcmVhdGUgNCBncm91cHMgdXNpbmcgSXRlbV9NUlAgdmFyaWFibGUuIEsgTWVhbnMgY2x1c3RlcmluZyByZXF1aXJlcyBwcmlvciBrbm93bGVkZ2Ugb2YgSyBpLmUuIG5vLiBvZiBjbHVzdGVycyB3ZSB3YW50IHRvIGRpdmlkZSBvdXIgZGF0YSBpbnRvLiBIZXJlIHdlIHdpbGwgZ28gYWhlYWQgd2l0aCBLPTQuIExldCdzIHRyeSBpdCBvdXQuDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD01LCBlY2hvPUZ9DQpnZ3Bsb3QodHJhaW4pICsgZ2VvbV9wb2ludChhZXMoSXRlbV9NUlAsIEl0ZW1fT3V0bGV0X1NhbGVzKSwgY29sb3VyID0gInZpb2xldCIsIGFscGhhID0gMC4zKQ0KYGBgDQoNCmBgYHtyfQ0KSXRlbV9NUlBfY2x1c3RlcnMgPSBrbWVhbnMoY29tYmkkSXRlbV9NUlAsIGNlbnRlcnMgPSA0KQ0KDQp0YWJsZShJdGVtX01SUF9jbHVzdGVycyRjbHVzdGVyKSAjIGRpc3BsYXkgbm8uIG9mIG9ic2VydmF0aW9ucyBpbiBlYWNoIGNsdXN0ZXINCmBgYA0KDQpMZXQncyB1c2UgdGhlc2UgY2x1c3RlcnMgYXMgYSBuZXcgaW5kZXBlbmRlbnQgZmVhdHVyZSBpbiBvdXIgZGF0YS4NCg0KYGBge3J9DQpjb21iaSRJdGVtX01SUF9jbHVzdGVycyA9IGFzLmZhY3RvcihJdGVtX01SUF9jbHVzdGVycyRjbHVzdGVyKQ0KYGBgDQojIyBFbmNvZGluZyBDYXRlZ29yaWNhbCBWYXJpYWJsZXMNCg0KSW4gdGhpcyBzdGFnZSwgd2Ugd2lsbCBjb252ZXJ0IG91ciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW50byBudW1lcmljYWwgb25lcy4gV2Ugd2lsbCB1c2UgMiB0ZWNobmlxdWVzIOKAlCBMYWJlbCBFbmNvZGluZyBhbmQgT25lIEhvdCBFbmNvZGluZy4NCg0KMS4gX19MYWJlbCBlbmNvZGluZ19fIHNpbXBseSBtZWFucyBjb252ZXJ0aW5nIGVhY2ggY2F0ZWdvcnkgaW4gYSB2YXJpYWJsZSB0byBhIG51bWJlci4gSXQgaXMgbW9yZSBzdWl0YWJsZSBmb3Igb3JkaW5hbCB2YXJpYWJsZXMg4oCUIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB3aXRoIHNvbWUgb3JkZXIuIA0KDQoyLiBJbiBfX09uZSBob3QgZW5jb2RpbmdfXywgZWFjaCBjYXRlZ29yeSBvZiBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGlzIGNvbnZlcnRlZCBpbnRvIGEgbmV3IGJ1bmFyeSBjb2x1bW4gKDEvMCkuIA0KDQpXZSB3aWxsIHVzZSBib3RoIHRoZSBlbmNvZGluZyB0ZWNobmlxdWVzLg0KDQojIyMgTGFiZWwgZW5jb2RpbmcgZm9yIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMNCg0KV2Ugd2lsbCBsYWJlbCBlbmNvZGUgT3V0bGV0X1NpemUgYW5kIE91dGxldF9Mb2NhdGlvbl9UeXBlIGFzIHRoZXNlIGFyZSBvcmRpbmFsIHZhcmlhYmxlcy4NCg0KYGBge3J9DQpjb21iaVssT3V0bGV0X1NpemVfbnVtIDo9IGlmZWxzZShPdXRsZXRfU2l6ZSA9PSAiU21hbGwiLCAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKE91dGxldF9TaXplID09ICJNZWRpdW0iLCAxLCAyKSldDQoNCmNvbWJpWyxPdXRsZXRfTG9jYXRpb25fVHlwZV9udW0gOj0gaWZlbHNlKE91dGxldF9Mb2NhdGlvbl9UeXBlID09ICJUaWVyIDMiLCAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKE91dGxldF9Mb2NhdGlvbl9UeXBlID09ICJUaWVyIDIiLCAxLCAyKSldDQoNCiMgcmVtb3ZpbmcgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFmdGVyIGxhYmVsIGVuY29kaW5nDQpjb21iaVssIGMoIk91dGxldF9TaXplIiwgIk91dGxldF9Mb2NhdGlvbl9UeXBlIikgOj0gTlVMTF0NCmBgYA0KDQo8YnI+DQoNCiMjIyBPbmUgaG90IGVuY29kaW5nIGZvciB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUNCg0KYGBge3J9DQpvaGUgPSBkdW1teVZhcnMoIn4uIiwgZGF0YSA9IGNvbWJpWywtYygiSXRlbV9JZGVudGlmaWVyIiwgIk91dGxldF9Fc3RhYmxpc2htZW50X1llYXIiLCAiSXRlbV9UeXBlIildLCBmdWxsUmFuayA9IFQpDQpvaGVfZGYgPSBkYXRhLnRhYmxlKHByZWRpY3Qob2hlLCBjb21iaVssLWMoIkl0ZW1fSWRlbnRpZmllciIsICJPdXRsZXRfRXN0YWJsaXNobWVudF9ZZWFyIiwgIkl0ZW1fVHlwZSIpXSkpDQoNCmNvbWJpID0gY2JpbmQoY29tYmlbLCJJdGVtX0lkZW50aWZpZXIiXSwgb2hlX2RmKQ0KYGBgDQoNCiMjIFByZVByb2Nlc3NpbmcgRGF0YQ0KDQpCZWZvcmUgZmVlZGluZyBvdXIgZGF0YSBpbnRvIGFueSBtb2RlbCwgaXQgaXMgYSBnb29kIHByYWN0aWNlIHRvIHByZXByb2Nlc3MgdGhlIGRhdGEuIFdlIHdpbGwgZG8gcHJlcHJvY2Vzc2luZyBvbiBib3RoIGluZGVwZW5kZW50IHZhcmlhYmxlcyBhbmQgdGFyZ2V0IHZhcmlhYmxlDQoNCiMjIyMgQ2hlY2tpbmcgU2tld25lc3MNCg0KU2tld25lc3MgaW4gdmFyaWFibGVzIGlzIHVuZGVzaXJhYmxlIGZvciBwcmVkaWN0aXZlIG1vZGVsaW5nLiBTb21lIG1hY2hpbmUgbGVhcm5pbmcgbWV0aG9kcyBhc3N1bWUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgZGF0YSBhbmQgYSBza2V3ZWQgdmFyaWFibGUgY2FuIGJlIHRyYW5zZm9ybWVkIGJ5IHRha2luZyBpdHMgbG9nLCBzcXVhcmUgcm9vdCwgb3IgY3ViZSByb290IHNvIGFzIHRvIG1ha2UgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgc2tld2VkIHZhcmlhYmxlIGFzIGNsb3NlIHRvIG5vcm1hbCBkaXN0cmlidXRpb24gYXMgcG9zc2libGUuIA0KDQpgYGB7cn0NCnNrZXduZXNzKGNvbWJpJEl0ZW1fVmlzaWJpbGl0eSk7IHNrZXduZXNzKGNvbWJpJHByaWNlX3Blcl91bml0X3d0KQ0KYGBgDQpgYGB7cn0NCmNvbWJpWyxJdGVtX1Zpc2liaWxpdHkgOj0gbG9nKEl0ZW1fVmlzaWJpbGl0eSArIDEpXSAjIGxvZyArIDEgdG8gYXZvaWQgZGl2aXNpb24gYnkgemVybw0KY29tYmlbLHByaWNlX3Blcl91bml0X3d0IDo9IGxvZyhwcmljZV9wZXJfdW5pdF93dCArIDEpXQ0KYGBgDQoNCiMjIyMgU2NhbGluZyBudW1lcmljIHByZWRpY3RvcnMNCg0KTGV0J3Mgc2NhbGUgYW5kIGNlbnRlciB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgdG8gbWFrZSB0aGVtIGhhdmUgYSBtZWFuIG9mIHplcm8sIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBvbmUgYW5kIHNjYWxlIG9mIDAgdG8gMS4gU2NhbGluZyBhbmQgY2VudGVyaW5nIGlzIHJlcXVpcmVkIGZvciBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMuIA0KDQpgYGB7cn0NCm51bV92YXJzID0gd2hpY2goc2FwcGx5KGNvbWJpLCBpcy5udW1lcmljKSkgIyBpbmRleCBvZiBudW1lcmljIGZlYXR1cmVzDQpudW1fdmFyc19uYW1lcyA9IG5hbWVzKG51bV92YXJzKQ0KDQpjb21iaV9udW1lcmljID0gY29tYmlbLHNldGRpZmYobnVtX3ZhcnNfbmFtZXMsICJJdGVtX091dGxldF9TYWxlcyIpLCB3aXRoID0gRl0NCg0KcHJlcF9udW0gPSBwcmVQcm9jZXNzKGNvbWJpX251bWVyaWMsIG1ldGhvZD1jKCJjZW50ZXIiLCAic2NhbGUiKSkNCmNvbWJpX251bWVyaWNfbm9ybSA9IHByZWRpY3QocHJlcF9udW0sIGNvbWJpX251bWVyaWMpDQpgYGANCg0KYGBge3J9DQpjb21iaVssc2V0ZGlmZihudW1fdmFyc19uYW1lcywgIkl0ZW1fT3V0bGV0X1NhbGVzIikgOj0gTlVMTF0gIyByZW1vdmluZyBudW1lcmljIGluZGVwZW5kZW50IHZhcmlhYmxlcw0KY29tYmkgPSBjYmluZChjb21iaSwgY29tYmlfbnVtZXJpY19ub3JtKQ0KYGBgDQoNCmBgYHtyfQ0KdHJhaW4gPSBjb21iaVsxOm5yb3codHJhaW4pXQ0KdGVzdCA9IGNvbWJpWyhucm93KHRyYWluKSArIDEpOm5yb3coY29tYmkpXQ0KdGVzdFssSXRlbV9PdXRsZXRfU2FsZXMgOj0gTlVMTF0gIyByZW1vdmluZyBJdGVtX091dGxldF9TYWxlcyBhcyBpdCBjb250YWlucyBvbmx5IE5BDQpgYGANCg0KIyMjIyBDb3JyZWxhdGVkIFZhcmlhYmxlcw0KDQpMZXQncyBleGFtaW5lIHRoZSBjb3JyZWxhdGVkIGZlYXR1cmVzIG9mIHRyYWluIGRhdGFzZXQuIENvcnJlbGF0aW9uIHZhcmllcyBmcm9tIC0xIHRvIDEuIA0KDQoxLiBuZWdhdGl2ZSBjb3JyZWxhdGlvbjogPCAwIGFuZCA+PSAtMQ0KMi4gcG9zaXRpdmUgY29ycmVsYXRpb246ID4gMCBhbmQgPD0gMQ0KMy4gbm8gY29ycmVsYXRpb246IDANCg0KSXQgaXMgbm90IGRlc2lyYWJsZSB0byBoYXZlIGNvcnJlbGF0ZWQgZmVhdHVyZXMgaWYgd2UgYXJlIHVzaW5nIGxpbmVhciByZWdyZXNzaW9ucy4gDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PSAxMSwgZmlnLndpZHRoPTEwfQ0KY29yX3RyYWluID0gY29yKHRyYWluWywtYygiSXRlbV9JZGVudGlmaWVyIildKQ0KDQpjb3JycGxvdChjb3JfdHJhaW4sIG1ldGhvZCA9ICJwaWUiLCB0eXBlID0gImxvd2VyIiwgdGwuY2V4ID0gMC45KQ0KYGBgDQpWYXJpYWJsZXMgcHJpY2VfcGVyX3VuaXRfd3QgYW5kIEl0ZW1fV2VpZ2h0IGFyZSBoaWdobHkgY29ycmVsYXRlZCBhcyB0aGUgZm9ybWVyIG9uZSB3YXMgY3JlYXRlZCBmcm9tIHRoZSBsYXR0ZXIuIFNpbWlsYXJseSBwcmljZV9wZXJfdW5pdF93dCBhbmQgSXRlbV9NUlAgYXJlIGhpZ2hseSBjb3JyZWxhdGVkIGZvciB0aGUgc2FtZSByZWFzb24uIA0KDQoNCiMjIE1vZGVsaW5nDQoNCkZpbmFsbHkgd2UgaGF2ZSBhcnJpdmVkIGF0IG1vc3QgaW50ZXJlc3Rpbmcgc3RhZ2Ugb2YgdGhlIHdob2xlIHByb2Nlc3Mg4oCUIHByZWRpY3RpdmUgbW9kZWxpbmcuIFdlIHdpbGwgc3RhcnQgb2ZmIHdpdGggdGhlIHNpbXBsZXIgbW9kZWxzIGFuZCBncmFkdWFsbHkgbW92ZSBvbiB0byBtb3JlIHNvcGhpc3RpY2F0ZWQgbW9kZWxzLiBXZSB3aWxsIGJ1aWxkIHRoZSBtb2RlbHMgdXNpbmcuLi4NCg0KKiBMaW5lYXIgUmVncmVzc2lvbg0KKiBMYXNzbyBSZWdyZXNzaW9uDQoqIFJpZGdlIFJlZ3Jlc3Npb24NCiogUmFuZG9tRm9yZXN0DQoqIFhHQm9vc3QNCg0KV2Ugd2lsbCB1c2UgX181LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbl9fIGluIGFsbCB0aGUgbW9kZWxzIHdlIGFyZSBnb2luZyB0byBidWlsZC4gQmFzaWNhbGx5IGNyb3NzIHZhaWRhdGlvbiBnaXZlcyBhbiBpZGVhIGhvdyB3ZWxsIGEgbW9kZWwgZ2VuZXJhbGl6ZXMgdG8gdW5zZWVuIGRhdGEuIA0KDQo8YnI+DQoNCiMjIyBMaW5lYXIgUmVncmVzc2lvbg0KDQpMaW5lYXIgcmVncmVzc2lvbiBpcyB0aGUgc2ltcGxlc3QgYW5kIG1vc3Qgd2lkZWx5IHVzZWQgc3RhdGlzdGljYWwgdGVjaG5pcXVlIGZvciBwcmVkaWN0aXZlIG1vZGVsaW5nLiBHaXZlbiBiZWxvdyBpcyB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gZXF1YXRpb246DQoNCiFbXShMUl9lcXVhdGlvbi5wbmcpDQoNCndoZXJlIFgxLCBYMiwuLi4sWG4gYXJlIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMsIFkgaXMgdGhlIHRhcmdldCB2YXJpYWJsZSBhbmQgYWxsIHRoZXRhcyBhcmUgdGhlIGNvZWZmaWNpZW50cy4gTWFnbml0dWRlIG9mIGEgY29lZmZpY2llbnQgd3J0IHRvIHRoZSBvdGhlciBjb2VmZmljaWVudHMgZGV0ZXJtaW5lcyB0aGUgaW1wb3J0YW5jZSBvZiB0aGUgY29ycmVzcG9uZGluZyBpbmRlcGVuZGVudCB2YXJpYWJsZS4gDQoNCkZvciBhIGdvb2QgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwsIHRoZSBkYXRhIHNob3VsZCBzYXRpc2Z5IGEgZmV3IGFzc3VtcHRpb25zIHdoaWNoIGNhbiBiZSBmb3VuZCBpbiB0aGlzIFthcnRpY2xlXShodHRwczovL3d3dy5hbmFseXRpY3N2aWRoeWEuY29tL2Jsb2cvMjAxNi8wNy9kZWVwZXItcmVncmVzc2lvbi1hbmFseXNpcy1hc3N1bXB0aW9ucy1wbG90cy1zb2x1dGlvbnMvKSBpbiBkZXRhaWwuIE9uZSBvZiB0aGVzZSBhc3N1bXB0aW9ucyBpcyB0aGF0IG9mIGFic2VuY2Ugb2YgbXVsdGljb2xsaW5lYXJpdHksIGkuZSwgdGhlIGluZGVwZW5kZW50IHZhcmlhYmxlcyBzaG91bGQgYmUgY29ycmVsYXRlZC4gSG93ZXZlciwgYXMgcGVyIHRoZSBjb3JyZWxhdGlvbiBwbG90IGFib3ZlLCB3ZSBoYXZlIGEgZmV3IGhpZ2hseSBjb3JyZWxhdGVkIGluZGVwZW5kZW50IHZhcmlhYmxlcyBpbiBvdXIgZGF0YS4gVGhpcyBpc3N1ZSBvZiBtdWx0aWNvbGxpbmVhcml0eSBjYW4gYmUgZGVhbHQgd2l0aCByZWd1bGFyaXphdGlvbi4gDQoNCkZvciB0aGUgdGltZSBiZWluZywgbGV0J3MgYnVpbGQgb3VyIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHdpdGggYWxsIHRoZSB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNCkNCm15X2NvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj01KQ0KDQpsaW5lYXJfcmVnX21vZCA9IHRyYWluKHggPSB0cmFpblssLWMoIkl0ZW1fSWRlbnRpZmllciIsICJJdGVtX091dGxldF9TYWxlcyIpXSwgeSA9IHRyYWluJEl0ZW1fT3V0bGV0X1NhbGVzLA0KICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9J2dsbW5ldCcsIHRyQ29udHJvbD0gbXlfY29udHJvbCkNCg0KcHJpbnQoIjUtIGZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBzY29yZXM6IikNCnByaW50KHJvdW5kKGxpbmVhcl9yZWdfbW9kJHJlc2FtcGxlJFJNU0UsIDIpKQ0KYGBgDQoNCk1ha2luZyBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhc2V0DQpgYGB7cn0NCiNzdWJtaXNzaW9uJEl0ZW1fT3V0bGV0X1NhbGVzID0gKHByZWRpY3QobGluZWFyX3JlZ19tb2QsIHRlc3RbLC1jKCJJdGVtX0lkZW50aWZpZXIiKV0pKV4yIA0Kc3VibWlzc2lvbiRJdGVtX091dGxldF9TYWxlcyA9IHByZWRpY3QobGluZWFyX3JlZ19tb2QsIHRlc3RbLC1jKCJJdGVtX0lkZW50aWZpZXIiKV0pDQp3cml0ZS5jc3Yoc3VibWlzc2lvbiwgIkxpbmVhcl9SZWdfc3VibWl0XzJfMjFfQXByXzE4LmNzdiIsIHJvdy5uYW1lcyA9IEYpDQpgYGANCg0KX19MZWFkZXJib2FyZCBzY29yZTogMTIwMi4yNl9fDQoNCjxicj4NCg0KUmVndWxhcmlzZWQgcmVncmVzc2lvbiBtb2RlbHMgY2FuIGhhbmRsZSB0aGUgY29ycmVsYXRlZCBpbmRlcGVuZGVudCB2YXJpYWJsZXMgd2VsbCBhbmQgaGVscHMgaW4gb3ZlcmNvbWluZyBvdmVyZml0dGluZy4gX19SaWRnZV9fIHBlbmFsdHkgc2hyaW5rcyB0aGUgY29lZmZpY2llbnRzIG9mIGNvcnJlbGF0ZWQgcHJlZGljdG9ycyB0b3dhcmRzIGVhY2ggb3RoZXIgd2hpbGUgdGhlIF9fTGFzc29fXyB0ZW5kcyB0byBwaWNrIG9uZSBvZiBhIHBhaXIgb2YgY29ycmVsYXRlZCBmZWF0dXJlcyBhbmQgZGlzY2FyZCB0aGUgb3RoZXIuIFRoZSB0dW5pbmcgcGFyYW1ldGVyIF9fbGFtYmRhX18gY29udHJvbHMgdGhlIHN0cmVuZ3RoIG9mIHRoZSBwZW5hbHR5Lg0KDQojIyMgTGFzc28gUmVncmVzc2lvbg0KYGBge3J9DQpzZXQuc2VlZCgxMjM1KQ0KbXlfY29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTUpDQpHcmlkID0gZXhwYW5kLmdyaWQoYWxwaGEgPSAxLCBsYW1iZGEgPSBzZXEoMC4wMDEsMC4xLGJ5ID0gMC4wMDAyKSkNCg0KbGFzc29fbGluZWFyX3JlZ19tb2QgPSB0cmFpbih4ID0gdHJhaW5bLCAtYygiSXRlbV9JZGVudGlmaWVyIiwgIkl0ZW1fT3V0bGV0X1NhbGVzIildLCB5ID0gdHJhaW4kSXRlbV9PdXRsZXRfU2FsZXMsDQogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0nZ2xtbmV0JywgdHJDb250cm9sPSBteV9jb250cm9sLCB0dW5lR3JpZCA9IEdyaWQpDQpgYGANCg0KYGBge3IsIGVjaG89Rn0NCiNzdWJtaXNzaW9uJEl0ZW1fT3V0bGV0X1NhbGVzID0gKHByZWRpY3QobGFzc29fbGluZWFyX3JlZ19tb2QsIHRlc3RbLCAtYygiSXRlbV9JZGVudGlmaWVyIildKSleMiANCnN1Ym1pc3Npb24kSXRlbV9PdXRsZXRfU2FsZXMgPSBwcmVkaWN0KGxhc3NvX2xpbmVhcl9yZWdfbW9kLCB0ZXN0WywgLWMoIkl0ZW1fSWRlbnRpZmllciIpXSkNCndyaXRlLmNzdihzdWJtaXNzaW9uLCAiTGFzc29fTGluZWFyX1JlZ19zdWJtaXRfMl8yMV9BcHJfMTguY3N2Iiwgcm93Lm5hbWVzID0gRikNCmBgYA0KX19MZWFkZXJib2FyZCBzY29yZTogMTIwMi4yNl9fDQoNCjxicj4NCg0KIyMjIFJpZGdlIFJlZ3Jlc3Npb24NCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNikNCm15X2NvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj01KQ0KR3JpZCA9IGV4cGFuZC5ncmlkKGFscGhhID0gMCwgbGFtYmRhID0gc2VxKDAuMDAxLDAuMSxieSA9IDAuMDAwMikpDQoNCnJpZGdlX2xpbmVhcl9yZWdfbW9kID0gdHJhaW4oeCA9IHRyYWluWywgLWMoIkl0ZW1fSWRlbnRpZmllciIsICJJdGVtX091dGxldF9TYWxlcyIpXSwgeSA9IHRyYWluJEl0ZW1fT3V0bGV0X1NhbGVzLA0KICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9J2dsbW5ldCcsIHRyQ29udHJvbD0gbXlfY29udHJvbCwgdHVuZUdyaWQgPSBHcmlkKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZ9DQpzdWJtaXNzaW9uJEl0ZW1fT3V0bGV0X1NhbGVzID0gKHByZWRpY3QocmlkZ2VfbGluZWFyX3JlZ19tb2QsIHRlc3RbLCAtYygiSXRlbV9JZGVudGlmaWVyIildKSleMiANCndyaXRlLmNzdihzdWJtaXNzaW9uLCAiUmlkZ2VfTGluZWFyX1JlZ19zdWJtaXRfMV8yMV9BcHJfMTguY3N2Iiwgcm93Lm5hbWVzID0gRikNCmBgYA0KX19MZWFkZXJib2FyZCBzY29yZTogMTIxOS4wOF9fDQoNCjxicj4NCg0KIyMjIFJhbmRvbUZvcmVzdA0KDQpSYW5kb21Gb3Jlc3QgaXMgYSB0cmVlIGJhc2VkIGJvb3RzdHJhcHBpbmcgYWxnb3JpdGhtIHdoZXJlaW4gYSBjZXJ0YWluIG5vLiBvZiB3ZWFrIGxlYXJuZXJzIChkZWNpc2lvbiB0cmVlcykgYXJlIGNvbWJpbmVkIHRvIG1ha2UgYSBwb3dlcmZ1bCBwcmVkaWN0aW9uIG1vZGVsLiBGb3IgZXZlcnkgaW5kaXZpc3VhbCBsZWFybmVyLCBhIHJhbmRvbSBzYW1wbGUgb2Ygcm93cyBhbmQgYSBmZXcgcmFuZG9tbHkgY2hvc2VuIHZhcmlhYmxlcyBhcmUgdXNlZCB0byBidWlsZCBhIGRlY2lzaW9uIHRyZWUgbW9kZWwuIEZpbmFsIHByZWRpY3Rpb24gY2FuIGJlIGEgZnVuY3Rpb24gb2YgYWxsIHRoZSBwcmVkaWN0aW9ucyBtYWRlIGJ5IHRoZSBpbmRpdmlkdWFsIGxlYXJuZXJzLiBJbiBjYXNlIG9mIHJlZ3Jlc3Npb24gcHJvYmxlbSwgdGhlIGZpbmFsIHByZWRpY3Rpb24gY2FuIGJlIG1lYW4gb2YgYWxsIHRoZSBwcmVkaWN0aW9ucy4gRm9yIGRldGFpbGVkIGV4cGxhbmF0aW9uIHZpc2l0IHRoaXMgW2FydGljbGVdKGh0dHBzOi8vd3d3LmFuYWx5dGljc3ZpZGh5YS5jb20vYmxvZy8yMDE2LzA0L2NvbXBsZXRlLXR1dG9yaWFsLXRyZWUtYmFzZWQtbW9kZWxpbmctc2NyYXRjaC1pbi1weXRob24vKS4NCg0KV2Ugd2lsbCBub3cgYnVpbGQgYSBSYW5kb21Gb3Jlc3QgbW9kZWwgd2l0aCA0MDAgdHJlZXMuIFRoZSAgb3RoZXIgdHVuaW5nIHBhcmFtZXRlcnMgdXNlZCBoZXJlIGFyZSBtdHJ5IOKAlCBuby4gb2YgcHJlZGljdG9yIHZhcmlhYmxlcyByYW5kb21seSBzYW1wbGVkIGF0IGVhY2ggc3BsaXQsIGFuZCBtaW4ubm9kZS5zaXplIOKAlCBtaW5pbXVtIHNpemUgb2YgdGVybWluYWwgbm9kZXMgKHNldHRpbmcgdGhpcyBudW1iZXIgbGFyZ2UgY2F1c2VzIHNtYWxsZXIgdHJlZXMgYW5kIHJlZHVjZXMgb3ZlcmZpdHRpbmcpLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzcpDQpteV9jb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBudW1iZXI9NSkNCg0KdGdyaWQgPSBleHBhbmQuZ3JpZCgNCiAgLm10cnkgPSBjKDM6MTApLA0KICAuc3BsaXRydWxlID0gInZhcmlhbmNlIiwNCiAgLm1pbi5ub2RlLnNpemUgPSBjKDEwLDE1LDIwKQ0KKQ0KDQpyZl9tb2QgPSB0cmFpbih4ID0gdHJhaW5bLCAtYygiSXRlbV9JZGVudGlmaWVyIiwgIkl0ZW1fT3V0bGV0X1NhbGVzIildLCANCiAgICAgICAgICAgICAgIHkgPSB0cmFpbiRJdGVtX091dGxldF9TYWxlcywNCiAgICAgICAgICAgICAgIG1ldGhvZD0ncmFuZ2VyJywgDQogICAgICAgICAgICAgICB0ckNvbnRyb2w9IG15X2NvbnRyb2wsIA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSB0Z3JpZCwNCiAgICAgICAgICAgICAgIG51bS50cmVlcyA9IDQwMCwNCiAgICAgICAgICAgICAgIGltcG9ydGFuY2UgPSAicGVybXV0YXRpb24iKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChyZl9tb2QpDQpgYGANCg0KQXMgcGVyIHRoZSBwbG90IHNob3duIGFib3ZlLCB0aGUgYmVzdCBzY29yZSBpcyBhY2hpZXZlZCBhdCBtdHJ5ID0gNSBhbmQgbWluLm5vZGUuc2l6ZSA9IDIwLg0KDQpgYGB7ciwgZWNobz1GfQ0Kc3VibWlzc2lvbiRJdGVtX091dGxldF9TYWxlcyA9IHByZWRpY3QocmZfbW9kLCB0ZXN0WywgLWMoIkl0ZW1fSWRlbnRpZmllciIpXSkgDQp3cml0ZS5jc3Yoc3VibWlzc2lvbiwgIlJhbmRvbUZvcmVzdF9zdWJtaXRfMl8yMV9BcHJfMTguY3N2Iiwgcm93Lm5hbWVzID0gRikNCmBgYA0KDQpfX0xlYWRlcmJvYXJkIHNjb3JlOiAxMTU3LjI1X18NCg0KPGJyPg0KDQojIyMgWEdCb29zdCBNb2RlbA0KDQpYR0Jvb3N0IGlzIGEgZmFzdCBhbmQgZWZmaWNpZW50IGFsZ29yaXRobSBhbmQgaGFzIGJlZW4gdXNlZCB0byBieSB0aGUgd2lubmVycyBvZiBtYW55IGRhdGEgc2NpZW5jZSBjb21wZXRldGlvbnMuIEl0J3MgYSBib29zdGluZyBhbGdvcml0aG0gYW5kIHlvdSBtYXkgcmVmZXIgdGhpcyBbYXJ0aWNsZV0oaHR0cHM6Ly93d3cuYW5hbHl0aWNzdmlkaHlhLmNvbS9ibG9nLzIwMTUvMTEvcXVpY2staW50cm9kdWN0aW9uLWJvb3N0aW5nLWFsZ29yaXRobXMtbWFjaGluZS1sZWFybmluZy8pIHRvIGtub3cgbW9yZSBhYm91dCBib29zdGluZy5YR0Jvb3N0IHdvcmtzIG9ubHkgd2l0aCBudW1lcmljIHZhcmlhYmxlcyBhbmQgd2UgaGF2ZSBhbHJlYWR5IGRvbmUgdGhhdC4gVGhlcmUgYXJlIG1hbnkgdHVuaW5nIHBhcmFtZXRlcnMgaW4gWEdCb29zdCB3aGljaCBjYW4gYmUgYnJvYWRseSBjbGFzc2lmaWVkIGludG8gR2VuZXJhbCBQYXJhbWV0ZXJzLCBCb29zdGVyIFBhcmFtZXRlcnMgYW5kIFRhc2sgUGFyYW1ldGVycy4NCg0KKiBHZW5lcmFsIHBhcmFtZXRlcnMgcmVmZXJzIHRvIHdoaWNoIGJvb3N0ZXIgd2UgYXJlIHVzaW5nIHRvIGRvIGJvb3N0aW5nLiBUaGUgY29tbW9ubHkgdXNlZCBhcmUgdHJlZSBvciBsaW5lYXIgbW9kZWwNCiogQm9vc3RlciBwYXJhbWV0ZXJzIGRlcGVuZHMgb24gd2hpY2ggYm9vc3RlciB5b3UgaGF2ZSBjaG9zZW4NCiogTGVhcm5pbmcgVGFzayBwYXJhbWV0ZXJzIHRoYXQgZGVjaWRlcyBvbiB0aGUgbGVhcm5pbmcgc2NlbmFyaW8sIGZvciBleGFtcGxlLCByZWdyZXNzaW9uIHRhc2tzIG1heSB1c2UgZGlmZmVyZW50IHBhcmFtZXRlcnMgd2l0aCByYW5raW5nIHRhc2tzLg0KDQpMZXQncyBoYXZlIGEgbG9vayBhdCB0aGUgcGFyYW1ldGVycyB0aGF0IHdlIGFyZSBnb2luZyB0byB1c2UgaW4gb3VyIG1vZGVsLg0KDQoxLiBfX2V0YV9fOiBJdCBpcyBhbHNvIGtub3duIGFzIHRoZSBsZWFybmluZyByYXRlIG9yIHRoZSBzaHJpbmthZ2UgZmFjdG9yLiBJdCBhY3R1YWxseSBzaHJpbmtzIHRoZSBmZWF0dXJlIHdlaWdodHMgdG8gbWFrZSB0aGUgYm9vc3RpbmcgcHJvY2VzcyBtb3JlIGNvbnNlcnZhdGl2ZS4gVGhlIHJhbmdlIGlzIDAgdG8gMS4gTG93IGV0YSB2YWx1ZSBtZWFucyBtb2RlbCBpcyBtb3JlIHJvYnVzdCB0byBvdmVyZml0dGluZy4NCjIuIF9fZ2FtbWFfXzogVGhlIHJhbmdlIGlzIDAgdG8g4oieLiBMYXJnZXIgdGhlIGdhbW1hIG1vcmUgY29uc2VydmF0aXZlIHRoZSBhbGdvcml0aG0gaXMuDQozLiBfX21heF9kZXB0aF9fOiBXZSBjYW4gc3BlY2lmeSBtYXhpbXVtIGRlcHRoIG9mIGEgdHJlZSB1c2luZyB0aGlzIHBhcmFtZXRlci4NCjQuIF9fc3Vic2FtcGxlX186IEl0IGlzIHRoZSBwcm9wb3J0aW9uIG9mIHJvd3MgdGhhdCB0aGUgbW9kZWwgd2lsbCByYW5kb21seSBzZWxlY3QgdG8gZ3JvdyB0cmVlcy4NCjUuIF9fY29sc2FtcGxlX2J5dHJlZV9fOiBJdCBpcyB0aGUgcmF0aW8gb2YgdmFyaWFibGVzIHJhbmRvbWx5IGNob3NlbiBmb3IgYnVpbGQgZWFjaCB0cmVlIGluIHRoZSBtb2RlbC4NCg0KYGBge3J9DQpwYXJhbV9saXN0ID0gbGlzdCgNCiAgICAgICAgDQogICAgICAgIG9iamVjdGl2ZSA9ICJyZWc6bGluZWFyIiwNCiAgICAgICAgZXRhPTAuMDEsDQogICAgICAgIGdhbW1hID0gMSwNCiAgICAgICAgbWF4X2RlcHRoPTYsDQogICAgICAgIHN1YnNhbXBsZT0wLjgsDQogICAgICAgIGNvbHNhbXBsZV9ieXRyZWU9MC41DQogICAgICAgICkNCmBgYA0KDQpgYGB7cn0NCmR0cmFpbiA9IHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgodHJhaW5bLC1jKCJJdGVtX0lkZW50aWZpZXIiLCAiSXRlbV9PdXRsZXRfU2FsZXMiKV0pLCBsYWJlbD0gdHJhaW4kSXRlbV9PdXRsZXRfU2FsZXMpDQpkdGVzdCA9IHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgodGVzdFssLWMoIkl0ZW1fSWRlbnRpZmllciIpXSkpDQpgYGANCg0KYGBge3J9DQpzZXQuc2VlZCgxMTIpDQp4Z2JjdiA9IHhnYi5jdihwYXJhbXMgPSBwYXJhbV9saXN0LCANCiAgICAgICAgICAgICAgIGRhdGEgPSBkdHJhaW4sIA0KICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAsIA0KICAgICAgICAgICAgICAgbmZvbGQgPSA1LCANCiAgICAgICAgICAgICAgIHByaW50X2V2ZXJ5X24gPSAxMCwgDQogICAgICAgICAgICAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAzMCwgDQogICAgICAgICAgICAgICBtYXhpbWl6ZSA9IEYpDQpgYGANCmBgYHtyfQ0KeGdiX21vZGVsID0geGdiLnRyYWluKGRhdGEgPSBkdHJhaW4sIHBhcmFtcyA9IHBhcmFtX2xpc3QsIG5yb3VuZHMgPSA0MzApDQpgYGANCl9fTGVhZGVyYm9hcmQgc2NvcmU6IDExNTQuNzBfXw0KDQo8YnI+DQoNCiMjIyBWYXJpYWJsZSBJbXBvcnRhbmNlDQoNCmBgYHtyfQ0KdmFyX2ltcCA9IHhnYi5pbXBvcnRhbmNlKGZlYXR1cmVfbmFtZXMgPSBzZXRkaWZmKG5hbWVzKHRyYWluKSwgYygiSXRlbV9JZGVudGlmaWVyIiwgIkl0ZW1fT3V0bGV0X1NhbGVzIikpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IHhnYl9tb2RlbCkNCnhnYi5wbG90LmltcG9ydGFuY2UodmFyX2ltcCkNCmBgYA0KDQpBcyBleHBlY3RlZCBJdGVtX01SUCBpcyB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUgaW4gcHJlZGljdGluZyB0aGUgdGFyZ2V0IHZhcmlhYmxlLiBOZXcgZmVhdHVyZXMgY3JlYXRlZCBieSB1cywgbGlrZSBwcmljZV9wZXJfdW5pdF93dCwgT3V0bGV0X1llYXJzLCBJdGVtX01SUF9DbHVzdGVycywgYXJlIGFsc28gYW1vbmcgdGhlIHRvcCBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMuIFRoaXMgaXMgd2h5IGZlYXR1cmUgZW5naW5lZXJpbmcgcGxheXMgc3VjaCBhIGNydWNpYWwgcm9sZSBpbiBwcmVkaWN0aXZlIG1vZGVsaW5nLiANCg0KYGBge3IsIGVjaG89Rn0NCnN1Ym1pc3Npb24kSXRlbV9PdXRsZXRfU2FsZXMgPSBwcmVkaWN0KHhnYl9tb2RlbCwgZHRlc3QpIA0Kd3JpdGUuY3N2KHN1Ym1pc3Npb24sICJYR0Jfc3VibWl0XzRfMjFfQXByXzE4LmNzdiIsIHJvdy5uYW1lcyA9IEYpDQpgYGANCg0KPGJyPg0KDQojIyBFbmQgTm90ZXMNCg0KQWZ0ZXIgdHJ5aW5nIGFuZCB0ZXN0aW5nIDUgZGlmZmVyZW50IGFsZ29yaXRobXMsIHRoZSBiZXN0IHNjb3JlIG9uIHRoZSBwdWJsaWMgbGVhZGVyYm9hcmQgaXMgYWNoaWV2ZWQgYnkgWEdCb29zdCAoMTE1NC43MCksIGZvbGxvd2VkIGJ5IFJhbmRvbUZvcmVzdCAoMTE1Ny4yNSkuIEkgaG9wZSB0aGlzIHR1dG9yaWFsIHdhcyBoZWxwZnVsIGZvciB5b3UgaW4gdW5kZXJzdGFuZGluZyBob3cgYSBtYWNoaW5lIGxlYXJuaW5nIGNvbXBldGV0aW9uIGlzIGFwcHJvYWNoZWQgYW5kIHdoYXQgYXJlIHRoZSBzdGVwcyBvbmUgc2hvdWxkIGdvIHRocm91Z2ggdG8gYnVpbGQgYSByb2J1c3QgbW9kZWwuIFNvLCBkbyByZXBsaWNhdGUgdGhpcyBhbmFseXNpcyBhbmQgbGV0IHVzIGtub3cgaWYgeW91IGZhY2UgYW55IGlzc3Vlcy4=