We now can upload our data.

ebeer <- read_csv("ebeer.csv")

Set the seed so that random draws are the same for everyone

set.seed(19312)

Exploratory Data Analysis

How many observations, how many variables? Show the first 10 observations

head(ebeer) %>% select(-acctnum) %>% 
  kbl() %>%
  kable_styling()
gender R F M firstpur age_class single student mailing respmail
1 30 10 35.7 50 3 1 1 1 0
0 16 1 149.0 16 0 0 1 1 0
0 12 1 123.0 12 0 1 0 1 0
0 6 2 147.0 8 0 1 1 1 0
1 6 3 96.0 18 0 0 1 1 1
0 12 2 150.5 14 0 1 1 1 0

Table: Summary Statistics

st(ebeer[-1], summ=c('mean(x)', 'sd(x)', 'min(x)', 'max(x)'))
Summary Statistics
Variable Mean Sd Min Max
gender 0.3 0.458 0 1
R 13.2 8.117 2 36
F 3.9 3.471 1 12
M 92 75.449 15 314
firstpur 26.5 18.216 2 99
age_class 0.9 1.103 0 8
single 0.3 0.461 0 1
student 0.5 0.499 0 1
mailing 0.5 0.5 0 1
respmail 0.1 0.33 0 1

Monetary Value

Let’s plot the distribution of the monetary amount spent by our customers (M) . This is the monetary value.

ebeer %>% 
  ggplot(aes(x = M)) + 
  geom_histogram(bins = 100, colour = "black", fill = "#00A087B2") +
  xlab("Monetary amount") + ylab("Frequency") +
  theme_bw()

How does average amount spent (M) and its standard deviation vary by whether customers responded to the mailing?

ebeer %>% 
  group_by(respmail) %>% 
  summarise(N = n(), Mean = round(mean(M),2), Std.Dev = round(sd(M),2)) %>% 
  kbl() %>%
  kable_styling()
respmail N Mean Std.Dev
0 4336 98.7 77.0
1 616 42.2 26.9
NA 5012 92.3 75.8

Uncertainty

Classical Uncertainty: Normal Distribution

Let’s estimate the distribution of the monetary amount, assuming normal distribution:

xbar <- mean(ebeer$M) 
xbse <-  sqrt(var(ebeer$M)/nrow(ebeer))

We can also plot the sampling distribution of the sample mean, assuming Normal Distribution:

norm.density <- as.data.frame(cbind(xx, m=dnorm(xx, xbar, xbse))) 

norm.density %>% 
  ggplot(aes(xx, m)) +
  geom_line(color = "#00A087B2") +
  geom_vline(aes(xintercept=xbar), color="black", linetype="dashed") +
  xlab("Average Monetary Value") + ylab("Density") +
  theme_bw()

Classical Uncertainty: Nonparametric Bootstrap

Another way to compute the distribution is using the bootstrap.

# nonparametric bootstrap
B <- 10000 # number of bootstrap samples
mub <- c() # where we are going to collect the mean
set.seed(19312) # setting seed right before sampling
for (b in 1:B){
  samp_b = sample.int(nrow(ebeer), replace=TRUE) # sample with replacement
  mub <- c(mub, mean(ebeer$M[samp_b])) # store the mean of the sample 
}

Let’s build a Comparison Table:

Distribution Comparison
Mean Std. Dev. Confidence Intervals (95%)
Normally Distributed 91.993 0.756 [90.512; 93.475]
Nonparametric Bootstrap 91.989 0.756 [90.507; 93.471]

The final Plot with the distribution of the bootstrapped mean:

as.data.frame(mub) %>% 
  ggplot(aes(mub)) + 
  geom_histogram(aes(y=..density..), bins = 100, colour = "black", fill = "#00A087B2") +
  geom_density(color="#3C5488B2", size=1) +
  geom_vline(aes(xintercept=mean(mub)), color="black", linetype="dashed", size=1) +
  xlab("Average Monetary Value") + ylab("Density") +
  theme_bw()

We can compare both distributions:

as.data.frame(mub) %>% 
  ggplot(aes(mub)) + 
  geom_density(color="#3C5488B2") +
  geom_line(data = norm.density, aes(xx, m) , color = "#00A087B2") +
  geom_vline(aes(xintercept=mean(mub)), color="blue", linetype="dashed") +
  geom_vline(aes(xintercept=xbar), color="black", linetype="dashed") +
  xlab("Average Monetary Value") + ylab("Density") +
  theme_bw()

Bayesian Uncertainty

A. Binary Example

Let’s consider the class example of an emailing campaign and we want to know the uncertainty of the response rates \(p\). One way to measure uncertainty of the possible values of the response rate is to assume that \(p\) comes from a beta distribution. The mean of the beta distribution is \[E[p|a,b]=\frac{a}{a+b}\]

Prior

## The mean response rate is 0.027

In our case, we have some prior that the response rate is 0.027, so that we can plot the prior distribution.

xx=seq(0,1,length=1000)

plot(xx, y=dbeta(xx, shape1=prior_a, shape2 = prior_b), 
     type="l", col="black", xlab="Response Rate", ylab="Prior Density")
abline(v=prior_a/(prior_a+prior_b))

Posterior

Bayes rule tells you how you should update your beliefs after you see some data.

\[ \textrm{posterior} \propto \textrm{likelihood} \times \textrm{prior} \]

Now, we already assume some prior and now we run the experiment and we see our data results. With this, we can update our prior belief by calculating the posterior. Our observed experiment had the following characteristics:

n = 5000 # number in test sample
s = 175 # number of responses | (s) Number of Successes vs. (n-s) Number of Failures
c = 1.5 # cost per mailing
m = 50 # profit if respond

We can calculate the probability that the posterior is above the breakeven point: \[ p >\frac{c}{\pi} \equiv \frac{1.5}{50} = 0.03 \]

So that we are interested in: \[ \Pr(p<0.03) \] We estimate the probability by drawing from a beta distribution with above parameters.

B = 10000 # number draws from distribution

prior_a = 1
prior_b = 36
post_a= prior_a + s
post_b = prior_b + n - s

xx=seq(0,.07,length=1000)

plot(xx, y <- dbeta(xx, shape1=post_a, shape2 = post_b), 
     type="l", col="black", xlab="Response Rate", ylab="Posterior Density")

The Mean Posterior Response Rate:

## The mean response rate is 0.035

95% Confidence Interval:

prior_ci <- round(qbeta(c(0.025, 0.975), shape1=prior_a, shape2 = prior_a),3)
posterior_ci <- round(qbeta(c(0.025, 0.975), shape1=post_a, shape2 = post_b),3)
## Prior Response Rate 95% CI: 0.025 0.975
## Posterior Response Rate 95% CI: 0.03 0.04

Probability of Passing Breakeven Point:

set.seed(19312)
post_draws <- rbeta(B,post_a,post_b) #random deviates
## The Probability that the response rate is above the breakeven point is 0.023

Prior vs Posterior:

plot(xx, y=dbeta(xx, shape1=post_a, shape2 = post_b), 
     type="l", col="black", xlab="Response Rate", ylab="Posterior Density")
lines(xx, y=dbeta(xx, shape1=prior_a, shape2 = prior_b), type="l", col="gray")
abline(v=brk)
legend("topright", col=c("black", "gray"), legend=c("posterior", "prior"), bty="n", lty=1)
set.seed(19312)
prob = sum(post_draws<brk)/B
text(x = .02,y= 100, paste("P(p < .03) = ", round(prob,3) ))

So we know that the probability that our response rate falls below our breakeven point is 0.023, which is highly unlikely.

B. Continuous Example

If the dependent variable is continuous, then we use a normal prior. For example, I would like to know what the mean time-on-site is for the A group and the B group from an A/B test. In particular, we want to study whether those differences are statistically significant or not in order to assess the impact of our experiment.

So, let’s say we have an A/B Test to study Time spend on site (minutes), depending on the design of a company’s homepage. Here our dependent variable is continuous, so we will assume a normal distribution.

Prior

Before I saw this data, I knew nothing about how long people might spend on this website. They might stay for 5 seconds or they might stay for 5 hours. Formally, I can describe my prior beliefs with a prior distribution: \[\textrm{mean time-on-site for group} \sim N(0, 100^2)\]
Here is the picture:

y <- dnorm(-300:300, mean=50, sd=100)
round(qnorm(c(0.025, 0.975), mean=0, sd=100),2)
## [1] -196  196

Posterior

Then Bayes rule tells us that the posterior distribution for mean

time-on-site for each group should be:

\[ \textrm{mean time-on-site (m)} \sim \mathcal{N}\left(\mu, \sigma^2\right) \]where

\[ \sigma = \left(\frac{1}{\sigma_{0}^2} + \frac{n}{s^2}\right)^{-1} \]

and

\[ \mu = \sigma^2 \left(\frac{\mu_0}{\sigma_{0}^2} + \frac{n \bar{y}}{s^2}\right) \]

n_A <- sum(test_data$group=="A") # obs for A
n_B <- sum(test_data$group=="B") # obs for B
s <- sd(test_data$time_on_site) # standard deviation of data (approx)

# Posterior standard deviation follows this formula
post_sd_A <- (1/100^2 + n_A/s^2)^-(1/2)
post_sd_B <- (1/100^2 + n_B/s^2)^-(1/2)

# sample mean
ybar_A <- mean(test_data[test_data$group=="A", "time_on_site"])
ybar_B <- mean(test_data[test_data$group=="B", "time_on_site"])

# Posterior mean is just the mean for each group, 
post_mean_A <- post_sd_A^2*(0/100^2 + n_A *ybar_A / s^2)
post_mean_B <- post_sd_B^2*(0/100^2 + n_B *ybar_B / s^2)

We can plot each posterior distribution to compare:

xx=seq(5,6,length=1000) 

plot(x=xx, y=dnorm(xx, mean=post_mean_A, sd=post_sd_A), 
     type="l", col="blue", xlab="mean time-on-site (m)", ylab="posterior density")
lines(x=xx, y=dnorm(xx, mean=post_mean_B, sd=post_sd_B), col="red")
lines(x=xx, y=dnorm(xx, mean=0, sd=100), col="gray")
legend("topright", col=c("blue", "red", "gray"), legend=c("posterior for A", "posterior for B", "prior"), bty="n", lty=1)

Once we have the distribution for the difference in the mean time-on-site, we can compute the probability that the mean of B is greater than the mean of A:

post_mean_diff <- post_mean_B - post_mean_A
post_sd_diff <- sqrt(post_sd_B^2 + post_sd_A^2)
prob=1-pnorm(0, mean=post_mean_diff, sd=post_sd_diff)

plot(x=(-50:60)/100, y=dnorm((-50:60)/100, mean=post_mean_diff, sd=post_sd_diff), 
     type="l", col="black", 
     xlab="difference in mean time-on-site (m)", ylab="posterior density")
abline(v=0)
text(-0.25, 2.9, "A has higher mean time-on-site")
text(0.35, 2.9, "B has higher mean time-on-site")
text(x = .4,y= 1.9, paste("P(m_A < m_B) = ", round(prob,3) ))

Test & Roll

Finally, when doing Test & Roll Experiments, we want to know how big should the test sample be relative to the rollout sample. This is a maximization problem.

Profit-Maximizing Sample Size

the profit maximizing sample size is:
\[n_1 = n_2 = \sqrt{\frac{N}{4}\left( \frac{s}{\sigma} \right)^2 + \left( \frac{3}{4} \left( \frac{s}{\sigma} \right)^2 \right)^2 } - \frac{3}{4} \left(\frac{s}{\sigma} \right)^2\] This new sample size formula is derived in Feit and Berman (2019) Marketing Science.

Computing the sample size in R

We will work with our conversion rates example. Our data will have the following characteristics:

N <- 100000 # available population
mu <- 0.68  # average conversion rate across previous treatments
sigma <- 0.03 # range of expected conversation rates across previous treatments
s <- sqrt(mu*(1-mu)) # binomial approximation, because is binary

We will use a binomial distribution for binary data. We can calculate the optimal test size:

## Optimal Test Sample Size: 2284
LS0tDQp0aXRsZTogIlR1dG9yaWFsIDE6IFRlc3QgJiBSb2xsIg0KZGF0ZTogIjIwMjMtMDEtMjkiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCnJtKGxpc3QgPSBscygpKQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodnRhYmxlKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbGlicmFyeShyZWFkcikNCmBgYA0KDQpXZSBub3cgY2FuIHVwbG9hZCBvdXIgZGF0YS4NCg0KYGBge3IgbG9hZGluZyBkYXRhLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KZWJlZXIgPC0gcmVhZF9jc3YoImViZWVyLmNzdiIpDQpgYGANCg0KU2V0IHRoZSBzZWVkIHNvIHRoYXQgcmFuZG9tIGRyYXdzIGFyZSB0aGUgc2FtZSBmb3IgZXZlcnlvbmUNCg0KYGBge3J9DQpzZXQuc2VlZCgxOTMxMikNCmBgYA0KDQojIyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzDQoNCkhvdyBtYW55IG9ic2VydmF0aW9ucywgaG93IG1hbnkgdmFyaWFibGVzPyBTaG93IHRoZSBmaXJzdCAxMCBvYnNlcnZhdGlvbnMNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpoZWFkKGViZWVyKSAlPiUgc2VsZWN0KC1hY2N0bnVtKSAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQoqKlRhYmxlOiBTdW1tYXJ5IFN0YXRpc3RpY3MqKg0KDQpgYGB7cn0NCnN0KGViZWVyWy0xXSwgc3VtbT1jKCdtZWFuKHgpJywgJ3NkKHgpJywgJ21pbih4KScsICdtYXgoeCknKSkNCmBgYA0KDQojIyMgTW9uZXRhcnkgVmFsdWUNCg0KTGV0J3MgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBtb25ldGFyeSBhbW91bnQgc3BlbnQgYnkgb3VyIGN1c3RvbWVycyAoKipNKiopIC4gVGhpcyBpcyB0aGUgbW9uZXRhcnkgdmFsdWUuDQoNCmBgYHtyfQ0KZWJlZXIgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBNKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMCwgY29sb3VyID0gImJsYWNrIiwgZmlsbCA9ICIjMDBBMDg3QjIiKSArDQogIHhsYWIoIk1vbmV0YXJ5IGFtb3VudCIpICsgeWxhYigiRnJlcXVlbmN5IikgKw0KICB0aGVtZV9idygpDQpgYGANCg0KSG93IGRvZXMgYXZlcmFnZSBhbW91bnQgc3BlbnQgKCoqTSoqKSBhbmQgaXRzIHN0YW5kYXJkIGRldmlhdGlvbiB2YXJ5IGJ5IHdoZXRoZXIgY3VzdG9tZXJzIHJlc3BvbmRlZCB0byB0aGUgbWFpbGluZz8NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQplYmVlciAlPiUgDQogIGdyb3VwX2J5KHJlc3BtYWlsKSAlPiUgDQogIHN1bW1hcmlzZShOID0gbigpLCBNZWFuID0gcm91bmQobWVhbihNKSwyKSwgU3RkLkRldiA9IHJvdW5kKHNkKE0pLDIpKSAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQojIyBVbmNlcnRhaW50eQ0KDQojIyMgQ2xhc3NpY2FsIFVuY2VydGFpbnR5OiBOb3JtYWwgRGlzdHJpYnV0aW9uDQoNCkxldCdzIGVzdGltYXRlIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlICptb25ldGFyeSBhbW91bnQqLCBhc3N1bWluZyBub3JtYWwgZGlzdHJpYnV0aW9uOg0KDQpgYGB7cn0NCnhiYXIgPC0gbWVhbihlYmVlciRNKSANCnhic2UgPC0gIHNxcnQodmFyKGViZWVyJE0pL25yb3coZWJlZXIpKQ0KYGBgDQoNCldlIGNhbiBhbHNvIHBsb3QgdGhlIHNhbXBsaW5nIGRpc3RyaWJ1dGlvbiBvZiB0aGUgKipzYW1wbGUgbWVhbioqLCBhc3N1bWluZyBOb3JtYWwgRGlzdHJpYnV0aW9uOg0KDQpgYGB7ciBsYWIgcGxvdDEsIGluY2x1ZGU9RkFMU0V9DQp4eCA8LSBzZXEoODksOTUsbGVuZ3RoPTEwMDApDQoNCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpwbG90KHh4LCBkbm9ybSh4eCwgeGJhciwgeGJzZSksDQogICAgIG1haW49IkRpc3RyaWJ1dGlvbiBvZiBTYW1wbGUgTWVhbiIsIHR5cGU9ImwiLCANCiAgICAgY29sPSJyb3lhbGJsdWUiLCBsd2Q9MS41LA0KICAgICB4bGFiPSJhdmVyYWdlIG1vbmV0YXJ5IHZhbHVlIiwgeWxhYj0iZGVuc2l0eSIpDQpgYGANCg0KYGBge3J9DQpub3JtLmRlbnNpdHkgPC0gYXMuZGF0YS5mcmFtZShjYmluZCh4eCwgbT1kbm9ybSh4eCwgeGJhciwgeGJzZSkpKSANCg0Kbm9ybS5kZW5zaXR5ICU+JSANCiAgZ2dwbG90KGFlcyh4eCwgbSkpICsNCiAgZ2VvbV9saW5lKGNvbG9yID0gIiMwMEEwODdCMiIpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD14YmFyKSwgY29sb3I9ImJsYWNrIiwgbGluZXR5cGU9ImRhc2hlZCIpICsNCiAgeGxhYigiQXZlcmFnZSBNb25ldGFyeSBWYWx1ZSIpICsgeWxhYigiRGVuc2l0eSIpICsNCiAgdGhlbWVfYncoKQ0KDQpgYGANCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQojIExldCdzIGFsc28gY3JlYXRlIG91ciA5NSUgQ29uZmlkZW5jZSBJbnRlcnZhbA0KcW5vcm0oYygwLjAyNSwgMC45NzUpLCBtZWFuPXhiYXIsIHNkPXhic2UpDQpgYGANCg0KIyMjIENsYXNzaWNhbCBVbmNlcnRhaW50eTogTm9ucGFyYW1ldHJpYyBCb290c3RyYXANCg0KQW5vdGhlciB3YXkgdG8gY29tcHV0ZSB0aGUgZGlzdHJpYnV0aW9uIGlzIHVzaW5nIHRoZSAqKmJvb3RzdHJhcCoqLg0KDQpgYGB7ciBib290c3RyYXAsIGNhY2hlPVRSVUV9DQojIG5vbnBhcmFtZXRyaWMgYm9vdHN0cmFwDQpCIDwtIDEwMDAwICMgbnVtYmVyIG9mIGJvb3RzdHJhcCBzYW1wbGVzDQptdWIgPC0gYygpICMgd2hlcmUgd2UgYXJlIGdvaW5nIHRvIGNvbGxlY3QgdGhlIG1lYW4NCnNldC5zZWVkKDE5MzEyKSAjIHNldHRpbmcgc2VlZCByaWdodCBiZWZvcmUgc2FtcGxpbmcNCmZvciAoYiBpbiAxOkIpew0KICBzYW1wX2IgPSBzYW1wbGUuaW50KG5yb3coZWJlZXIpLCByZXBsYWNlPVRSVUUpICMgc2FtcGxlIHdpdGggcmVwbGFjZW1lbnQNCiAgbXViIDwtIGMobXViLCBtZWFuKGViZWVyJE1bc2FtcF9iXSkpICMgc3RvcmUgdGhlIG1lYW4gb2YgdGhlIHNhbXBsZSANCn0NCg0KYGBgDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQ0KbmxvdyA8LSByb3VuZChxbm9ybShjKDAuMDI1KSwgbWVhbj14YmFyLCBzZD14YnNlKSwzKQ0KbmhpZ2ggPC0gcm91bmQocW5vcm0oYygwLjk3NSksIG1lYW49eGJhciwgc2Q9eGJzZSksMykNCmJsb3cgPC0gcm91bmQocW5vcm0oYygwLjAyNSksIG1lYW49bWVhbihtdWIpLCBzZD1zZChtdWIpKSwzKQ0KYmhpZ2ggPC0gcm91bmQocW5vcm0oYygwLjk3NSksIG1lYW49bWVhbihtdWIpLCBzZD1zZChtdWIpKSwzKQ0KYGBgDQoNCkxldCdzIGJ1aWxkIGEgQ29tcGFyaXNvbiBUYWJsZToNCg0KfCAgICAgICAgICAgICAgICAgICAgICAgICB8IE1lYW4gICAgICAgICAgICAgICAgICAgfCBTdGQuIERldi4gICAgICAgICAgICB8IENvbmZpZGVuY2UgSW50ZXJ2YWxzICg5NSUpIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgTm9ybWFsbHkgRGlzdHJpYnV0ZWQgICAgfCBgciByb3VuZCh4YmFyLDMpYCAgICAgIHwgYHIgcm91bmQoeGJzZSwzKWAgICAgfCBbYHIgbmxvd2A7IGByIG5oaWdoYF0gICAgICB8DQp8IE5vbnBhcmFtZXRyaWMgQm9vdHN0cmFwIHwgYHIgcm91bmQobWVhbihtdWIpLDMpYCB8IGByIHJvdW5kKHNkKG11YiksMylgIHwgW2ByIGJsb3dgOyBgciBiaGlnaGBdICAgICAgfA0KDQo6IERpc3RyaWJ1dGlvbiBDb21wYXJpc29uDQoNClRoZSBmaW5hbCBQbG90IHdpdGggdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgYm9vdHN0cmFwcGVkIG1lYW46DQoNCmBgYHtyfQ0KYXMuZGF0YS5mcmFtZShtdWIpICU+JSANCiAgZ2dwbG90KGFlcyhtdWIpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeT0uLmRlbnNpdHkuLiksIGJpbnMgPSAxMDAsIGNvbG91ciA9ICJibGFjayIsIGZpbGwgPSAiIzAwQTA4N0IyIikgKw0KICBnZW9tX2RlbnNpdHkoY29sb3I9IiMzQzU0ODhCMiIsIHNpemU9MSkgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW1lYW4obXViKSksIGNvbG9yPSJibGFjayIsIGxpbmV0eXBlPSJkYXNoZWQiLCBzaXplPTEpICsNCiAgeGxhYigiQXZlcmFnZSBNb25ldGFyeSBWYWx1ZSIpICsgeWxhYigiRGVuc2l0eSIpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCmBgYHtyIGxhYiBwbG90LCBpbmNsdWRlPUZBTFNFfQ0KIyBXZSBjYW4gY29tcGFyZSBib3RoIGRpc3RyaWJ1dGlvbnM6DQpwYXIobWFpPWMoLjgsLjgsLjIsLjIpKQ0KaGlzdChtdWIsIA0KICAgICBtYWluPSJEaXN0cmlidXRpb24gb2YgYm9vdHN0cmFwIG1lYW4iLCANCiAgICAgeGxhYj0iYXZlcmFnZSBtb25ldGFyeSB2YWx1ZSIsIA0KICAgICBicmVha3MgPSAxMDAsDQogICAgIGNvbD04LCBib3JkZXI9ImdyZXk5MCIsIGZyZXE9RkFMU0UpDQpsaW5lcyh4eCwgZG5vcm0oeHgsIHhiYXIsIHhic2UpLCBjb2w9InJveWFsYmx1ZSIsIGx3ZD0xLjUpDQoNCmBgYA0KDQpXZSBjYW4gY29tcGFyZSBib3RoIGRpc3RyaWJ1dGlvbnM6DQoNCmBgYHtyfQ0KYXMuZGF0YS5mcmFtZShtdWIpICU+JSANCiAgZ2dwbG90KGFlcyhtdWIpKSArIA0KICBnZW9tX2RlbnNpdHkoY29sb3I9IiMzQzU0ODhCMiIpICsNCiAgZ2VvbV9saW5lKGRhdGEgPSBub3JtLmRlbnNpdHksIGFlcyh4eCwgbSkgLCBjb2xvciA9ICIjMDBBMDg3QjIiKSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihtdWIpKSwgY29sb3I9ImJsdWUiLCBsaW5ldHlwZT0iZGFzaGVkIikgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PXhiYXIpLCBjb2xvcj0iYmxhY2siLCBsaW5ldHlwZT0iZGFzaGVkIikgKw0KICB4bGFiKCJBdmVyYWdlIE1vbmV0YXJ5IFZhbHVlIikgKyB5bGFiKCJEZW5zaXR5IikgKw0KICB0aGVtZV9idygpDQpgYGANCg0KIyMjIEJheWVzaWFuIFVuY2VydGFpbnR5DQoNCiMjIyMgQS4gQmluYXJ5IEV4YW1wbGUNCg0KTGV0J3MgY29uc2lkZXIgdGhlIGNsYXNzIGV4YW1wbGUgb2YgYW4gZW1haWxpbmcgY2FtcGFpZ24gYW5kIHdlIHdhbnQgdG8ga25vdyB0aGUgdW5jZXJ0YWludHkgb2YgdGhlICoqKnJlc3BvbnNlIHJhdGVzKioqICRwJC4gT25lIHdheSB0byBtZWFzdXJlIHVuY2VydGFpbnR5IG9mIHRoZSBwb3NzaWJsZSB2YWx1ZXMgb2YgdGhlIHJlc3BvbnNlIHJhdGUgaXMgdG8gYXNzdW1lIHRoYXQgJHAkIGNvbWVzIGZyb20gYSAqKmJldGEgZGlzdHJpYnV0aW9uKiouIFRoZSBtZWFuIG9mIHRoZSBiZXRhIGRpc3RyaWJ1dGlvbiBpcyAkJEVbcHxhLGJdPVxmcmFje2F9e2ErYn0kJA0KDQojIyMjIFByaW9yDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQ0KcHJpb3JfYSA9IDENCnByaW9yX2IgPSAzNg0KDQojIE1lYW4NCnBfaGF0ID0gcHJpb3JfYS8ocHJpb3JfYStwcmlvcl9iKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KY2F0KCJUaGUgbWVhbiByZXNwb25zZSByYXRlIGlzIiwgcm91bmQocF9oYXQsMyksICJcbiIpDQpgYGANCg0KSW4gb3VyIGNhc2UsIHdlIGhhdmUgc29tZSBwcmlvciB0aGF0IHRoZSByZXNwb25zZSByYXRlIGlzIGByIHBfaGF0YCwgc28gdGhhdCB3ZSBjYW4gcGxvdCB0aGUgcHJpb3IgZGlzdHJpYnV0aW9uLg0KDQpgYGB7cn0NCnh4PXNlcSgwLDEsbGVuZ3RoPTEwMDApDQoNCnBsb3QoeHgsIHk9ZGJldGEoeHgsIHNoYXBlMT1wcmlvcl9hLCBzaGFwZTIgPSBwcmlvcl9iKSwgDQogICAgIHR5cGU9ImwiLCBjb2w9ImJsYWNrIiwgeGxhYj0iUmVzcG9uc2UgUmF0ZSIsIHlsYWI9IlByaW9yIERlbnNpdHkiKQ0KYWJsaW5lKHY9cHJpb3JfYS8ocHJpb3JfYStwcmlvcl9iKSkNCmBgYA0KDQojIyMjIFBvc3Rlcmlvcg0KDQpCYXllcyBydWxlIHRlbGxzIHlvdSBob3cgeW91IHNob3VsZCB1cGRhdGUgeW91ciBiZWxpZWZzIGFmdGVyIHlvdSBzZWUgc29tZSBkYXRhLg0KDQokJCBcdGV4dHJte3Bvc3Rlcmlvcn0gXHByb3B0byBcdGV4dHJte2xpa2VsaWhvb2R9IFx0aW1lcyBcdGV4dHJte3ByaW9yfSAkJA0KDQpOb3csIHdlIGFscmVhZHkgYXNzdW1lIHNvbWUgcHJpb3IgYW5kIG5vdyB3ZSBydW4gdGhlIGV4cGVyaW1lbnQgYW5kIHdlIHNlZSBvdXIgZGF0YSByZXN1bHRzLiBXaXRoIHRoaXMsIHdlIGNhbiB1cGRhdGUgb3VyIHByaW9yIGJlbGllZiBieSBjYWxjdWxhdGluZyB0aGUgcG9zdGVyaW9yLiBPdXIgb2JzZXJ2ZWQgZXhwZXJpbWVudCBoYWQgdGhlIGZvbGxvd2luZyBjaGFyYWN0ZXJpc3RpY3M6DQoNCmBgYHtyfQ0KbiA9IDUwMDAgIyBudW1iZXIgaW4gdGVzdCBzYW1wbGUNCnMgPSAxNzUgIyBudW1iZXIgb2YgcmVzcG9uc2VzIHwgKHMpIE51bWJlciBvZiBTdWNjZXNzZXMgdnMuIChuLXMpIE51bWJlciBvZiBGYWlsdXJlcw0KYyA9IDEuNSAjIGNvc3QgcGVyIG1haWxpbmcNCm0gPSA1MCAjIHByb2ZpdCBpZiByZXNwb25kDQpgYGANCg0KV2UgY2FuIGNhbGN1bGF0ZSB0aGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgcG9zdGVyaW9yIGlzICphYm92ZSB0aGUgYnJlYWtldmVuIHBvaW50KjogJCQgcCA+XGZyYWN7Y317XHBpfSBcZXF1aXYgXGZyYWN7MS41fXs1MH0gPSAwLjAzICQkDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQ0KYnJrID0gYy9tDQpgYGANCg0KU28gdGhhdCB3ZSBhcmUgaW50ZXJlc3RlZCBpbjogJCQgXFByKHA8MC4wMykgJCQgV2UgZXN0aW1hdGUgdGhlIHByb2JhYmlsaXR5IGJ5IGRyYXdpbmcgZnJvbSBhIGJldGEgZGlzdHJpYnV0aW9uIHdpdGggYWJvdmUgcGFyYW1ldGVycy4NCg0KYGBge3J9DQpCID0gMTAwMDAgIyBudW1iZXIgZHJhd3MgZnJvbSBkaXN0cmlidXRpb24NCg0KcHJpb3JfYSA9IDENCnByaW9yX2IgPSAzNg0KcG9zdF9hPSBwcmlvcl9hICsgcw0KcG9zdF9iID0gcHJpb3JfYiArIG4gLSBzDQoNCnh4PXNlcSgwLC4wNyxsZW5ndGg9MTAwMCkNCg0KcGxvdCh4eCwgeSA8LSBkYmV0YSh4eCwgc2hhcGUxPXBvc3RfYSwgc2hhcGUyID0gcG9zdF9iKSwgDQogICAgIHR5cGU9ImwiLCBjb2w9ImJsYWNrIiwgeGxhYj0iUmVzcG9uc2UgUmF0ZSIsIHlsYWI9IlBvc3RlcmlvciBEZW5zaXR5IikNCmBgYA0KDQpUaGUgKipNZWFuIFBvc3RlcmlvciBSZXNwb25zZSBSYXRlKio6DQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQ0KcF9oYXQxIDwtIHBvc3RfYS8ocG9zdF9hICsgcG9zdF9iKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KY2F0KCJUaGUgbWVhbiByZXNwb25zZSByYXRlIGlzIiwgcm91bmQocF9oYXQxLDMpLCAiXG4iKQ0KYGBgDQoNCioqOTUlIENvbmZpZGVuY2UgSW50ZXJ2YWwqKjoNCg0KYGBge3J9DQpwcmlvcl9jaSA8LSByb3VuZChxYmV0YShjKDAuMDI1LCAwLjk3NSksIHNoYXBlMT1wcmlvcl9hLCBzaGFwZTIgPSBwcmlvcl9hKSwzKQ0KcG9zdGVyaW9yX2NpIDwtIHJvdW5kKHFiZXRhKGMoMC4wMjUsIDAuOTc1KSwgc2hhcGUxPXBvc3RfYSwgc2hhcGUyID0gcG9zdF9iKSwzKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KY2F0KCJQcmlvciBSZXNwb25zZSBSYXRlIDk1JSBDSToiLCBwcmlvcl9jaSwgIlxuIikNCmNhdCgiUG9zdGVyaW9yIFJlc3BvbnNlIFJhdGUgOTUlIENJOiIsIHBvc3Rlcmlvcl9jaSkNCmBgYA0KDQpQcm9iYWJpbGl0eSBvZiBQYXNzaW5nIEJyZWFrZXZlbiBQb2ludDoNCg0KYGBge3J9DQpzZXQuc2VlZCgxOTMxMikNCnBvc3RfZHJhd3MgPC0gcmJldGEoQixwb3N0X2EscG9zdF9iKSAjcmFuZG9tIGRldmlhdGVzDQoNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmNhdCgiVGhlIFByb2JhYmlsaXR5IHRoYXQgdGhlIHJlc3BvbnNlIHJhdGUgaXMgYWJvdmUgdGhlIGJyZWFrZXZlbiBwb2ludCBpcyIsIHJvdW5kKHN1bShwb3N0X2RyYXdzPGJyaykvQiwzKSkNCmBgYA0KDQpQcmlvciB2cyBQb3N0ZXJpb3I6DQoNCmBgYHtyfQ0KcGxvdCh4eCwgeT1kYmV0YSh4eCwgc2hhcGUxPXBvc3RfYSwgc2hhcGUyID0gcG9zdF9iKSwgDQogICAgIHR5cGU9ImwiLCBjb2w9ImJsYWNrIiwgeGxhYj0iUmVzcG9uc2UgUmF0ZSIsIHlsYWI9IlBvc3RlcmlvciBEZW5zaXR5IikNCmxpbmVzKHh4LCB5PWRiZXRhKHh4LCBzaGFwZTE9cHJpb3JfYSwgc2hhcGUyID0gcHJpb3JfYiksIHR5cGU9ImwiLCBjb2w9ImdyYXkiKQ0KYWJsaW5lKHY9YnJrKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGNvbD1jKCJibGFjayIsICJncmF5IiksIGxlZ2VuZD1jKCJwb3N0ZXJpb3IiLCAicHJpb3IiKSwgYnR5PSJuIiwgbHR5PTEpDQpzZXQuc2VlZCgxOTMxMikNCnByb2IgPSBzdW0ocG9zdF9kcmF3czxicmspL0INCnRleHQoeCA9IC4wMix5PSAxMDAsIHBhc3RlKCJQKHAgPCAuMDMpID0gIiwgcm91bmQocHJvYiwzKSApKQ0KDQpgYGANCg0KU28gd2Uga25vdyB0aGF0IHRoZSBwcm9iYWJpbGl0eSB0aGF0IG91ciByZXNwb25zZSByYXRlIGZhbGxzIGJlbG93IG91ciBicmVha2V2ZW4gcG9pbnQgaXMgYHIgcm91bmQocHJvYiwzKWAsIHdoaWNoIGlzIGhpZ2hseSB1bmxpa2VseS4NCg0KIyMjIyBCLiBDb250aW51b3VzIEV4YW1wbGUNCg0KSWYgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSBpcyBjb250aW51b3VzLCB0aGVuIHdlIHVzZSBhIG5vcm1hbCBwcmlvci4gRm9yIGV4YW1wbGUsIEkgd291bGQgbGlrZSB0byBrbm93IHdoYXQgdGhlIG1lYW4gdGltZS1vbi1zaXRlIGlzIGZvciB0aGUgQSBncm91cCBhbmQgdGhlIEIgZ3JvdXAgZnJvbSBhbiBBL0IgdGVzdC4gSW4gcGFydGljdWxhciwgd2Ugd2FudCB0byBzdHVkeSB3aGV0aGVyIHRob3NlIGRpZmZlcmVuY2VzIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IG9yIG5vdCBpbiBvcmRlciB0byBhc3Nlc3MgdGhlIGltcGFjdCBvZiBvdXIgZXhwZXJpbWVudC4NCg0KU28sIGxldCdzIHNheSB3ZSBoYXZlIGFuIEEvQiBUZXN0IHRvIHN0dWR5IFRpbWUgc3BlbmQgb24gc2l0ZSAobWludXRlcyksIGRlcGVuZGluZyBvbiB0aGUgZGVzaWduIG9mIGEgY29tcGFueSdzIGhvbWVwYWdlLiBIZXJlIG91ciBkZXBlbmRlbnQgdmFyaWFibGUgaXMgY29udGludW91cywgc28gd2Ugd2lsbCBhc3N1bWUgYSAqKm5vcm1hbCBkaXN0cmlidXRpb24qKi4NCg0KIyMjIyBQcmlvcg0KDQpCZWZvcmUgSSBzYXcgdGhpcyBkYXRhLCBJIGtuZXcgbm90aGluZyBhYm91dCBob3cgbG9uZyBwZW9wbGUgbWlnaHQgc3BlbmQgb24gdGhpcyB3ZWJzaXRlLiBUaGV5IG1pZ2h0IHN0YXkgZm9yIDUgc2Vjb25kcyBvciB0aGV5IG1pZ2h0IHN0YXkgZm9yIDUgaG91cnMuIEZvcm1hbGx5LCBJIGNhbiBkZXNjcmliZSBteSBwcmlvciBiZWxpZWZzIHdpdGggYSAqcHJpb3IgZGlzdHJpYnV0aW9uKjogJCRcdGV4dHJte21lYW4gdGltZS1vbi1zaXRlIGZvciBncm91cH0gXHNpbSBOKDAsIDEwMF4yKSQkXA0KSGVyZSBpcyB0aGUgcGljdHVyZToNCg0KYGBge3J9DQp5IDwtIGRub3JtKC0zMDA6MzAwLCBtZWFuPTUwLCBzZD0xMDApDQpyb3VuZChxbm9ybShjKDAuMDI1LCAwLjk3NSksIG1lYW49MCwgc2Q9MTAwKSwyKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KIyBQbG90IHRoZSBwcmlvcg0KcGxvdCh4PS0zMDA6MzAwLCB5PWRub3JtKC0zMDA6MzAwLCBtZWFuPTAsIHNkPTEwMCksIA0KICAgICB0eXBlPSJsIiwgY29sPSJibHVlIiwgeGxhYj0ibWVhbiB0aW1lLW9uLXNpdGUgKG0pIiwgeWxhYj0icHJpb3IgZGVuc2l0eSIpDQpgYGANCg0KIyMjIyBQb3N0ZXJpb3INCg0KVGhlbiBCYXllcyBydWxlIHRlbGxzIHVzIHRoYXQgdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb24gZm9yIG1lYW4NCg0KdGltZS1vbi1zaXRlIGZvciBlYWNoIGdyb3VwIHNob3VsZCBiZToNCg0KJCQNClx0ZXh0cm17bWVhbiB0aW1lLW9uLXNpdGUgKG0pfSBcc2ltIFxtYXRoY2Fse059XGxlZnQoXG11LCBcc2lnbWFeMlxyaWdodCkgDQokJHdoZXJlDQoNCiQkDQogXHNpZ21hID0gXGxlZnQoXGZyYWN7MX17XHNpZ21hX3swfV4yfSArIFxmcmFje259e3NeMn1ccmlnaHQpXnstMX0NCiQkDQoNCmFuZA0KDQokJA0KXG11ID0gXHNpZ21hXjIgXGxlZnQoXGZyYWN7XG11XzB9e1xzaWdtYV97MH1eMn0gKyBcZnJhY3tuIFxiYXJ7eX19e3NeMn1ccmlnaHQpDQokJA0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCnNldC5zZWVkKDE5MzEyKQ0KZ3JvdXAgPC0gYyhyZXAoIkEiLCA1MDApLCByZXAoIkIiLCA1MDApKSANCnRpbWVfb25fc2l0ZSA8LSBjKHJub3JtKDUwMCwgbWVhbj01LjIsIHNkPTIpLCBybm9ybSg1MDAsIG1lYW49NS40LCBzZD0yLjIpKQ0KdGVzdF9kYXRhIDwtIGRhdGEuZnJhbWUoZ3JvdXAsIHRpbWVfb25fc2l0ZSkNCnJtKGdyb3VwLCB0aW1lX29uX3NpdGUpDQpoZWFkKHRlc3RfZGF0YSkNCmBgYA0KDQpgYGB7cn0NCm5fQSA8LSBzdW0odGVzdF9kYXRhJGdyb3VwPT0iQSIpICMgb2JzIGZvciBBDQpuX0IgPC0gc3VtKHRlc3RfZGF0YSRncm91cD09IkIiKSAjIG9icyBmb3IgQg0KcyA8LSBzZCh0ZXN0X2RhdGEkdGltZV9vbl9zaXRlKSAjIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBkYXRhIChhcHByb3gpDQoNCiMgUG9zdGVyaW9yIHN0YW5kYXJkIGRldmlhdGlvbiBmb2xsb3dzIHRoaXMgZm9ybXVsYQ0KcG9zdF9zZF9BIDwtICgxLzEwMF4yICsgbl9BL3NeMileLSgxLzIpDQpwb3N0X3NkX0IgPC0gKDEvMTAwXjIgKyBuX0Ivc14yKV4tKDEvMikNCg0KIyBzYW1wbGUgbWVhbg0KeWJhcl9BIDwtIG1lYW4odGVzdF9kYXRhW3Rlc3RfZGF0YSRncm91cD09IkEiLCAidGltZV9vbl9zaXRlIl0pDQp5YmFyX0IgPC0gbWVhbih0ZXN0X2RhdGFbdGVzdF9kYXRhJGdyb3VwPT0iQiIsICJ0aW1lX29uX3NpdGUiXSkNCg0KIyBQb3N0ZXJpb3IgbWVhbiBpcyBqdXN0IHRoZSBtZWFuIGZvciBlYWNoIGdyb3VwLCANCnBvc3RfbWVhbl9BIDwtIHBvc3Rfc2RfQV4yKigwLzEwMF4yICsgbl9BICp5YmFyX0EgLyBzXjIpDQpwb3N0X21lYW5fQiA8LSBwb3N0X3NkX0JeMiooMC8xMDBeMiArIG5fQiAqeWJhcl9CIC8gc14yKQ0KYGBgDQoNCldlIGNhbiBwbG90IGVhY2ggcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbiB0byBjb21wYXJlOg0KDQpgYGB7cn0NCnh4PXNlcSg1LDYsbGVuZ3RoPTEwMDApIA0KDQpwbG90KHg9eHgsIHk9ZG5vcm0oeHgsIG1lYW49cG9zdF9tZWFuX0EsIHNkPXBvc3Rfc2RfQSksIA0KICAgICB0eXBlPSJsIiwgY29sPSJibHVlIiwgeGxhYj0ibWVhbiB0aW1lLW9uLXNpdGUgKG0pIiwgeWxhYj0icG9zdGVyaW9yIGRlbnNpdHkiKQ0KbGluZXMoeD14eCwgeT1kbm9ybSh4eCwgbWVhbj1wb3N0X21lYW5fQiwgc2Q9cG9zdF9zZF9CKSwgY29sPSJyZWQiKQ0KbGluZXMoeD14eCwgeT1kbm9ybSh4eCwgbWVhbj0wLCBzZD0xMDApLCBjb2w9ImdyYXkiKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGNvbD1jKCJibHVlIiwgInJlZCIsICJncmF5IiksIGxlZ2VuZD1jKCJwb3N0ZXJpb3IgZm9yIEEiLCAicG9zdGVyaW9yIGZvciBCIiwgInByaW9yIiksIGJ0eT0ibiIsIGx0eT0xKQ0KYGBgDQoNCk9uY2Ugd2UgaGF2ZSB0aGUgZGlzdHJpYnV0aW9uIGZvciB0aGUgZGlmZmVyZW5jZSBpbiB0aGUgbWVhbiB0aW1lLW9uLXNpdGUsIHdlIGNhbiBjb21wdXRlIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBtZWFuIG9mIEIgaXMgZ3JlYXRlciB0aGFuIHRoZSBtZWFuIG9mIEE6DQoNCmBgYHtyfQ0KcG9zdF9tZWFuX2RpZmYgPC0gcG9zdF9tZWFuX0IgLSBwb3N0X21lYW5fQQ0KcG9zdF9zZF9kaWZmIDwtIHNxcnQocG9zdF9zZF9CXjIgKyBwb3N0X3NkX0FeMikNCnByb2I9MS1wbm9ybSgwLCBtZWFuPXBvc3RfbWVhbl9kaWZmLCBzZD1wb3N0X3NkX2RpZmYpDQoNCnBsb3QoeD0oLTUwOjYwKS8xMDAsIHk9ZG5vcm0oKC01MDo2MCkvMTAwLCBtZWFuPXBvc3RfbWVhbl9kaWZmLCBzZD1wb3N0X3NkX2RpZmYpLCANCiAgICAgdHlwZT0ibCIsIGNvbD0iYmxhY2siLCANCiAgICAgeGxhYj0iZGlmZmVyZW5jZSBpbiBtZWFuIHRpbWUtb24tc2l0ZSAobSkiLCB5bGFiPSJwb3N0ZXJpb3IgZGVuc2l0eSIpDQphYmxpbmUodj0wKQ0KdGV4dCgtMC4yNSwgMi45LCAiQSBoYXMgaGlnaGVyIG1lYW4gdGltZS1vbi1zaXRlIikNCnRleHQoMC4zNSwgMi45LCAiQiBoYXMgaGlnaGVyIG1lYW4gdGltZS1vbi1zaXRlIikNCnRleHQoeCA9IC40LHk9IDEuOSwgcGFzdGUoIlAobV9BIDwgbV9CKSA9ICIsIHJvdW5kKHByb2IsMykgKSkNCmBgYA0KDQojIyBUZXN0ICYgUm9sbA0KDQpGaW5hbGx5LCB3aGVuIGRvaW5nICpUZXN0ICYgUm9sbCBFeHBlcmltZW50cyosIHdlIHdhbnQgdG8ga25vdyBob3cgYmlnIHNob3VsZCB0aGUgdGVzdCBzYW1wbGUgYmUgcmVsYXRpdmUgdG8gdGhlIHJvbGxvdXQgc2FtcGxlLiBUaGlzIGlzIGEgbWF4aW1pemF0aW9uIHByb2JsZW0uDQoNCiMjIyBQcm9maXQtTWF4aW1pemluZyBTYW1wbGUgU2l6ZQ0KDQp0aGUgcHJvZml0IG1heGltaXppbmcgc2FtcGxlIHNpemUgaXM6XA0KJCRuXzEgPSBuXzIgPSBcc3FydHtcZnJhY3tOfXs0fVxsZWZ0KCBcZnJhY3tzfXtcc2lnbWF9IFxyaWdodCleMiArIFxsZWZ0KCBcZnJhY3szfXs0fSBcbGVmdCggXGZyYWN7c317XHNpZ21hfSBccmlnaHQpXjIgIFxyaWdodCleMiB9IC0gIFxmcmFjezN9ezR9IFxsZWZ0KFxmcmFje3N9e1xzaWdtYX0gXHJpZ2h0KV4yJCQgVGhpcyBuZXcgc2FtcGxlIHNpemUgZm9ybXVsYSBpcyBkZXJpdmVkIGluIFtGZWl0IGFuZCBCZXJtYW4gKDIwMTkpICpNYXJrZXRpbmcgU2NpZW5jZSpdKGh0dHBzOi8vcGFwZXJzLnNzcm4uY29tL3NvbDMvcGFwZXJzLmNmbT9hYnN0cmFjdF9pZD0zMjc0ODc1KS4NCg0KIyMjIyBDb21wdXRpbmcgdGhlIHNhbXBsZSBzaXplIGluIFINCg0KV2Ugd2lsbCB3b3JrIHdpdGggb3VyIGNvbnZlcnNpb24gcmF0ZXMgZXhhbXBsZS4gT3VyIGRhdGEgd2lsbCBoYXZlIHRoZSBmb2xsb3dpbmcgY2hhcmFjdGVyaXN0aWNzOg0KDQpgYGB7cn0NCk4gPC0gMTAwMDAwICMgYXZhaWxhYmxlIHBvcHVsYXRpb24NCm11IDwtIDAuNjggICMgYXZlcmFnZSBjb252ZXJzaW9uIHJhdGUgYWNyb3NzIHByZXZpb3VzIHRyZWF0bWVudHMNCnNpZ21hIDwtIDAuMDMgIyByYW5nZSBvZiBleHBlY3RlZCBjb252ZXJzYXRpb24gcmF0ZXMgYWNyb3NzIHByZXZpb3VzIHRyZWF0bWVudHMNCnMgPC0gc3FydChtdSooMS1tdSkpICMgYmlub21pYWwgYXBwcm94aW1hdGlvbiwgYmVjYXVzZSBpcyBiaW5hcnkNCmBgYA0KDQpXZSB3aWxsIHVzZSBhIGJpbm9taWFsIGRpc3RyaWJ1dGlvbiBmb3IgYmluYXJ5IGRhdGEuIFdlIGNhbiBjYWxjdWxhdGUgdGhlIG9wdGltYWwgKip0ZXN0IHNpemUqKjoNCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIk9wdGltYWwgVGVzdCBTYW1wbGUgU2l6ZToiLCByb3VuZChzcXJ0KE4vNCoocy9zaWdtYSleMiArICgzLzQqKHMvc2lnbWEpXjIpXjIpIC0gMy80KihzL3NpZ21hKV4yLDApICkNCmBgYA0K