Agenda
Announcements
- Ch 17 programming notebook not assigned, but highly recommended
- several steps take some non-trivial configuration that I don’t want to require per se
MDSR Ch 17: Towards big data
- 3 V’s of big data
- “big data is when your workflow breaks” –Randy Pruim
- how big is “big” is relative
- pencil/paper workflow: 30 rows & 3 columns is “big”
- TI 83 workflow: larger than 99 rows (or columns) is “big”
- MS Excel workflow: larger than any of the following constraints is “big”
- 1,048,576 rows (\(2^20\))
- 16,384 columns (\(2^14\))
- 255 characters in a column (\(2^8 - 1\))
- Laptop running R: large with respect to available memory
Biggest of the big… (for perspective)
- the Large Hadron Collider in Geneva generates 25 petabytes of data per year
- one petabyte is a million gigabytes
- all the workflows are broken
- they actually only save 0.001% of the data generated
- Others just for fun (based on 2018 Forbes article)
- Google processes 40,000 searches per second
- 300 million photos uploaded to Facebook per day
- The Weather Channel receives 18,055,556 forecast requests per minute
What happens when data start getting big (in R)
- The data may not load into memory
- Analyzing the data may take a (really) long time
- Visualizations get messy
Memory limits in R
- If you’re running 32-bit R on any OS, it’ll be 2 or 3 GB
- note: 2GB of memory used by R is not the same as 2GB on disk
- Overhead for R to keep track of your data
- Memory used for analysis, etc.
- Probably not more than about 500MB on disk?
- If you’re running 64-bit R on a 64-bit OS, the upper limit should be infinite (but it’s not)
- Package
bigmemory
- “Manage massive matrices with shared memory and memory-mapped files”
- use in parallel environments can provide substantial speed and memory efficiencies
- uses a pointer to a C++ data structure
- (con) matrix data structure requires homogeneity
- Package
ff
- “Memory-Efficient Storage of Large Data on Disk and Fast Access Functions”
- provides data structures that are stored on disk but behave (almost) as if they were in RAM
- uses a pointer to a flat binary file stored on disk, and it can be shared across different sessions
- permits heterogeneous data structures
Strategies when data get big
- Make the data smaller
- Get a bigger computer
- Access the data differently
- Split up the dataset for analysis
If things are just slow…
Recall the teller simulation:
any_active <- function(df) {
# return TRUE if someone has not finished
return(max(df$endtime) == Inf)
}
next_customer <- function(df) {
# returns the next customer in line
res <- filter(df, endtime == Inf) %>%
arrange(arrival)
return(head(res, 1))
}
update_customer <- function(df, cust_num, end_time) {
# sets the end time of a specific customer
return(mutate(df, endtime = ifelse(custnum == cust_num, end_time, endtime)))
}
teller_sim <- function(n = 1/2, m = 3/2, hours = 6) {
# simulation of bank where there is just one teller
# n: expected number of customers per minute
# m: expected length of transaction is m minutes
# hours: bank open for this many hours
customers <- rpois(hours * 60, lambda = n)
arrival <- numeric(sum(customers))
position <- 1
for (i in 1:length(customers)) {
numcust <- customers[i]
if (numcust != 0) {
arrival[position:(position + numcust - 1)] <- rep(i, numcust)
position <- position + numcust
}
}
duration <- rexp(length(arrival), rate = 1/m) # E[X]=m
df <- data.frame(arrival, duration, custnum = 1:length(duration),
endtime = Inf, stringsAsFactors = FALSE)
endtime <- 0 # set up beginning of simulation
while (any_active(df)) { # anyone left to serve
next_one <- next_customer(df)
now <- ifelse(next_one$arrival >= endtime, next_one$arrival, endtime)
endtime <- now + next_one$duration
df <- update_customer(df, next_one$custnum, endtime)
}
df <- mutate(df, totaltime = endtime - arrival)
return(favstats(~ totaltime, data = df))
}
Profiling the teller simulation
- let’s see where the time goes…
- not bad in this case, but you could discover interesting things… like
- maybe some modeling function defaults to bootstrap confidence intervals that you don’t care about with 1000 iterations per model
- you did something for every line of your huge data frame and then combine results using
c()
or rbind()
rather than assigning to a preallocated vector or matrix
Rprof("TellerSimProfile")
teller_sim()
Rprof(NULL)
head(summaryRprof("TellerSimProfile")$by.self, 20)
STILL slow? Try biglm
biglm
package has an efficient alternative to the lm
function
- it can even fit generalized linear models (regression & logistic regression) with data frames that are larger than memory
require(biglm)
n <- 20000
p <- 500
d <- as.data.frame(matrix(rnorm(n * (p + 1)), ncol = (p + 1)))
expl_vars <- paste(paste0("V", 2:(p+1)), collapse = " + ")
my_formula <- as.formula(paste("V1 ~ ", expl_vars))
# profile `lm` vs `biglm`
system.time(lm(my_formula, data = d))
user system elapsed
5.779 0.251 6.131
system.time(biglm(my_formula, data = d))
user system elapsed
3.777 0.182 4.002
Next step: parallel processing
- Parallel processing is basically farming out subtasks to independent processors, then merging results
- effectively just allocates more RAM for the problem
my_cores <- detectCores()
my_cores
[1] 4
- embarassingly parallel computing
- I need to repeat the same task many times
- order of implementation doesn’t matter
- I have 4 total cores, but you should always save one for your operating system
- e.g., comparison for several iterations of teller simulation
k <- 5
# without parallel processing
system.time(lapply(1:k, teller_sim))
user system elapsed
12.102 0.258 12.565
# parallelize with 3 cores
system.time(mclapply(1:k, teller_sim, mc.cores = my_cores - 1))
user system elapsed
11.942 0.933 12.079
MapReduce (parallelization that’s not embarassing?)
- programming paradigm for parallel computing
- two phase algorithm
- map–farm out parallizeable task to many machines
- reduce–combine results
- tricky part: you have to define the
map
function and the reduce
function
- needs software implementation
Hadoop & Spark
- Hadoop was first to really tackle MapReduce
- Hadoop MapReduce has been superseded by Spark,
- tools that emerged as the “ecosystem” around it are still popular (HDFS)
- “legacy” projects might still use Hadoop MapReduce
- Apache Spark is considered superior for a few reasons
- had the benefit of implementing lessons learned from Hadoop
- keep the good–HDFS (Hadoop Distributed File System) for disk storage
- improve the weaknesses–prioritize RAM rather than disk storage whenever possible
Interface with Spark
- Spark provides provides an interface for programming entire clusters
- a computer cluster is a set of connected computers that work together as a single system
- the
sparklyr
package in R makes it easy to
- install a local Spark cluster (from within R)
- connect to a local or remote cluster
require(sparklyr)
# spark_install() # only once per machine
Interface with Spark
# modify master to connect to a remote Spark cluster
sc <- spark_connect(master = "local")
* Using Spark: 2.4.0
class(sc)
[1] "spark_connection" "spark_shell_connection" "DBIConnection"
babynames_tbl <-
sc %>%
copy_to(babynames::babynames, "babynames", overwrite = TRUE)
class(babynames_tbl)
[1] "tbl_spark" "tbl_sql" "tbl_lazy" "tbl"
Counting Matthews
babynames_tbl %>%
filter(name == "Matthew") %>%
group_by(year) %>%
summarise(N = n(),
total_births = sum(n)) %>%
arrange(desc(total_births)) %>%
head()
Missing values are always removed in SQL.
Use `SUM(x, na.rm = TRUE)` to silence this warning
This warning is displayed only once per session.
From dplyr
to SQL
- whenever
dplyr
meets an object with class tbl_sql
(like babynames_tbl
), dplyr
automatically translates the R pipeline into SQL
- SQL (structured query language) is a widely used language for querying relational databases, among other purposes
- Queries in SQL start with the
SELECT
keyword and consist several clauses which must to be written in order; basically
SELECT
(required)–like select()
in dplyr
(and possibly combined with mutate()
)
FROM
(required)–like table before the first %>%
in dplyr
JOIN
–like join()
verbs in dplyr
WHERE
–like filter()
verb in dplyr
GROUP BY
–like group_by()
verb in dplyr
HAVING
–like using a second filter()
in dplyr
after the rows have already been
ORDER BY
–like arrange()
verb in dplyr
LIMIT
–sort of like head()
but more versatile
- For example, let’s revisit our previous
dplyr
query
q <-
babynames_tbl %>%
filter(name == "Matthew") %>%
group_by(year) %>%
summarise(N = n(),
total_births = sum(n)) %>%
arrange(desc(total_births)) %>%
head()
q
show_query(q)
<SQL>
SELECT `year`, count(*) AS `N`, SUM(`n`) AS `total_births`
FROM `babynames`
WHERE (`name` = "Matthew")
GROUP BY `year`
ORDER BY `total_births` DESC
LIMIT 6
Querying the Spark cluster
- Spark is a parallelized technology designed to supersede SQL, but it’s still useful to know SQL in order to use Spark
- here, we’ll query the Spark cluster using the connection we’ve defined
sc
with the SQL statement equivalent to our dplyr
wrangling
require(DBI)
dbGetQuery(conn = sc, statement = "
SELECT year, sum(1) as N, sum(n) as total_births
FROM babynames
WHERE name == 'Matthew'
GROUP BY year
ORDER BY total_births desc
LIMIT 6
")
Modeling with Spark
whately_2015
has some weather data from Massachusetts (in the macleish
package)
- Spark has a machine learning library which includes many of the supervised/unsupervised learning tools we’ve discussed this semester
- let’s use Spark to fit a multiple regression model as an example
require(macleish)
weather_tbl <- copy_to(sc, whately_2015, overwrite = TRUE)
weather_tbl %>%
sparklyr::ml_linear_regression(rainfall ~ temperature + pressure + rel_humidity) %>%
summary()
Deviance Residuals:
Min 1Q Median 3Q Max
-0.041290 -0.021761 -0.011632 -0.000576 15.968356
Coefficients:
(Intercept) temperature pressure rel_humidity
0.7177542 0.0004089 -0.0007545 0.0004377
R-Squared: 0.004824
Root Mean Squared Error: 0.1982
Alternatives to SQL (Google BigQuery)
require(bigrquery)
project_id <- "stat-380-class-demo" # Beckman's google cloud project ID
sql <- "SELECT word, count(distinct corpus) as numPlays, sum(word_count) as N
FROM [publicdata:samples.shakespeare]
GROUP BY word
ORDER BY N desc
LIMIT 10
"
query_exec(query = sql, project = project_id)
Waiting for authentication in browser...
Press Esc/Ctrl + C to abort
Authentication complete.
Running job -: 1s:
Running job \: 2s:
Running job |: 2s:
Running job /: 3s:
Running job -: 3s:
Running job \: 3s:
Running job |: 4s:
Running job /: 4s:
Running job -: 5s:
Running job \: 5s:
Running job |: 5s:
Running job /: 5s:
10.0 megabytes processed
Rcpp
RCPP::cppFunction()
allows you to write C++ functions in R
Rcpp::sourceCpp()
loads a C++ file from disk in the same way you use source() to load a file of R code.
- Rcpp will compile the C++ code and construct an R function that connects to the compiled C++ function
- more here: http://adv-r.had.co.nz/Rcpp.html
require(Rcpp)
# write a simple function in C++
cppFunction('int addemup(int x, int y, int z) {
int sum = x + y + z;
return sum;
}')
# R recognizes `addemup` like any other function
addemup
function (x, y, z)
.Call(<pointer: 0x1176de8f0>, x, y, z)
addemup(2, 4, 6)
[1] 12
Stan
- Bayesian statistical inference with MCMC sampling
- Stan model within the code chunk is compiled into a “stanmodel” object
- result assigned to a variable with the name given by the
output.var
option
parameters {
real y[2];
}
model {
y[1] ~ normal(0, 1);
y[2] ~ double_exponential(0, 2);
}
Loading required package: sp
library(rstan)
Loading required package: StanHeaders
rstan (Version 2.19.2, GitRev: 2e1f913d3ca3)
For execution on a local, multicore CPU with excess RAM we recommend calling
options(mc.cores = parallel::detectCores()).
To avoid recompilation of unchanged Stan programs, we recommend calling
rstan_options(auto_write = TRUE)
Attaching package: ‘rstan’
The following object is masked from ‘package:tidyr’:
extract
fit <- sampling(ex1, cores = 3)
starting worker pid=25541 on localhost:11849 at 12:27:28.837
starting worker pid=25555 on localhost:11849 at 12:27:29.119
starting worker pid=25569 on localhost:11849 at 12:27:29.379
SAMPLING FOR MODEL 'stan-5a7c20ea0b45' NOW (CHAIN 1).
Chain 1:
Chain 1: Gradient evaluation took 1.1e-05 seconds
Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.11 seconds.
Chain 1: Adjust your expectations accordingly!
Chain 1:
Chain 1:
Chain 1: Iteration: 1 / 2000 [ 0%] (Warmup)
Chain 1: Iteration: 200 / 2000 [ 10%] (Warmup)
Chain 1: Iteration: 400 / 2000 [ 20%] (Warmup)
Chain 1: Iteration: 600 / 2000 [ 30%] (Warmup)
Chain 1: Iteration: 800 / 2000 [ 40%] (Warmup)
Chain 1: Iteration: 1000 / 2000 [ 50%] (Warmup)
Chain 1: Iteration: 1001 / 2000 [ 50%] (Sampling)
Chain 1: Iteration: 1200 / 2000 [ 60%] (Sampling)
Chain 1: Iteration: 1400 / 2000 [ 70%] (Sampling)
Chain 1: Iteration: 1600 / 2000 [ 80%] (Sampling)
Chain 1: Iteration: 1800 / 2000 [ 90%] (Sampling)
SAMPLING FOR MODEL 'stan-5a7c20ea0b45' NOW (CHAIN 2).
Chain 1: Iteration: 2000 / 2000 [100%] (Sampling)
Chain 1:
Chain 1: Elapsed Time: 0.032539 seconds (Warm-up)
Chain 1: 0.031726 seconds (Sampling)
Chain 1: 0.064265 seconds (Total)
Chain 1:
Chain 2:
Chain 2: Gradient evaluation took 1.1e-05 seconds
Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.11 seconds.
Chain 2: Adjust your expectations accordingly!
Chain 2:
Chain 2:
Chain 2: Iteration: 1 / 2000 [ 0%] (Warmup)
Chain 2: Iteration: 200 / 2000 [ 10%] (Warmup)
Chain 2: Iteration: 400 / 2000 [ 20%] (Warmup)
Chain 2: Iteration: 600 / 2000 [ 30%] (Warmup)
Chain 2: Iteration: 800 / 2000 [ 40%] (Warmup)
Chain 2: Iteration: 1000 / 2000 [ 50%] (Warmup)
Chain 2: Iteration: 1001 / 2000 [ 50%] (Sampling)
Chain 2: Iteration: 1200 / 2000 [ 60%] (Sampling)
Chain
SAMPLING FOR MODEL 'stan-5a7c20ea0b45' NOW (CHAIN 3).
2: Iteration: 1400 / 2000 [ 70%] (Sampling)
Chain 3:
Chain 3: Gradient evaluation took 9e-06 seconds
Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0.09 seconds.
Chain 3: Adjust your expectations accordingly!
Chain 3:
Chain 3:
Chain 3: Iteration: 1 / 2000 [ 0%] (Warmup)
Chain 2: Iteration: 1600 / 2000 [ 80%] (Sampling)
Chain 2: Iteration: 1800 / 2000 [ 90%] (Sampling)
Chain 2: Iteration: 2000 / 2000 [100%] (Sampling)
Chain 2:
Chain 2: Elapsed Time: 0.033218 seconds (Warm-up)
Chain 2: 0.029568 seconds (Sampling)
Chain 2: 0.062786 seconds (Total)
Chain 2:
Chain 3: Iteration: 200 / 2000 [ 10%] (Warmup)
Chain 3: Iteration: 400 / 2000 [ 20%] (Warmup)
Chain 3: Iteration: 600 / 2000 [ 30%] (Warmup)
Chain 3: Iteration: 800 / 2000 [ 40%] (Warmup)
Chain 3: Iteration: 1000 / 2000 [ 50%] (Warmup)
Chain 3: Iteration: 1001 / 2000 [ 50%] (Sampling)
Chain 3: Iteration: 1200 / 2000 [ 60%] (Sampling)
Chain 3: Iteration: 1400 / 2000 [ 70%] (Sampling)
Chain 3: Iteration: 1600 / 2000 [ 80%] (Sampling)
Chain 3: Iteration: 1800 / 2000 [ 90%] (Sampling)
Chain 3: Iteration: 2000 / 2000 [100%] (Sampling)
Chain 3:
Chain 3: Elapsed Time: 0.033801 seconds (Warm-up)
Chain 3: 0.032489 seconds (Sampling)
Chain 3: 0.06629 seconds (Total)
Chain 3:
SAMPLING FOR MODEL 'stan-5a7c20ea0b45' NOW (CHAIN 4).
Chain 4:
Chain 4: Gradient evaluation took 6e-06 seconds
Chain 4: 1000 transitions using 10 leapfrog steps per transition would take 0.06 seconds.
Chain 4: Adjust your expectations accordingly!
Chain 4:
Chain 4:
Chain 4: Iteration: 1 / 2000 [ 0%] (Warmup)
Chain 4: Iteration: 200 / 2000 [ 10%] (Warmup)
Chain 4: Iteration: 400 / 2000 [ 20%] (Warmup)
Chain 4: Iteration: 600 / 2000 [ 30%] (Warmup)
Chain 4: Iteration: 800 / 2000 [ 40%] (Warmup)
Chain 4: Iteration: 1000 / 2000 [ 50%] (Warmup)
Chain 4: Iteration: 1001 / 2000 [ 50%] (Sampling)
Chain 4: Iteration: 1200 / 2000 [ 60%] (Sampling)
Chain 4: Iteration: 1400 / 2000 [ 70%] (Sampling)
Chain 4: Iteration: 1600 / 2000 [ 80%] (Sampling)
Chain 4: Iteration: 1800 / 2000 [ 90%] (Sampling)
Chain 4: Iteration: 2000 / 2000 [100%] (Sampling)
Chain 4:
Chain 4: Elapsed Time: 0.019408 seconds (Warm-up)
Chain 4: 0.016486 seconds (Sampling)
Chain 4: 0.035894 seconds (Total)
Chain 4:
print(fit)
Inference for Stan model: stan-5a7c20ea0b45.
4 chains, each with iter=2000; warmup=1000; thin=1;
post-warmup draws per chain=1000, total post-warmup draws=4000.
mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat
y[1] -0.02 0.02 1.02 -2.01 -0.72 -0.01 0.67 2.01 1897 1
y[2] 0.10 0.06 2.79 -5.72 -1.28 0.06 1.47 6.03 2006 1
lp__ -1.50 0.03 1.22 -4.61 -2.06 -1.22 -0.62 -0.10 1329 1
Samples were drawn using NUTS(diag_e) at Wed Oct 2 12:27:32 2019.
For each parameter, n_eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor on split chains (at
convergence, Rhat=1).
# posterior
stan_plot(fit, point_est = "mean", show_density = TRUE, fill_color = "dodgerblue")
ci_level: 0.8 (80% intervals)
outer_level: 0.95 (95% intervals)

# trace
stan_trace(fit) +
scale_color_manual(values = c("red", "blue", "green", "black"))
Scale for 'colour' is already present. Adding another scale for 'colour',
which will replace the existing scale.

Python in R Markdown
https://rstudio.github.io/reticulate/#python-in-r-markdown
reticulate
package includes a Python engine for R Markdown:
- Run Python chunks in a single Python session embedded within your R session (shared variables/state between Python chunks)
- Printing of Python output, including graphical output from
matplotlib
.
- Access to objects created within Python chunks from R using the
py
object (e.g. py$x
would access an x
variable created within Python from R).
- Access to objects created within R chunks from Python using the
r
object (e.g. r.x
would access to x
variable created within R from Python)
- Built in conversion for many Python object types, including
NumPy
arrays and Pandas
data frames.
reticulate
& R Notebooks
reticulate
package does make it pretty easy to go back and forth between R & Python objects
- it “just works” when you compile RMarkdown
- the R Notebook isn’t yet ironed out in the current production release (v1.1)
- preview will throw errors, even when the code is correct
- R Notebook is no better than preview
- This will be fixed in RStudio v1.2 (install preview here…)
- you should install it (after the semester)
- I generally don’t recommend major updates during the semester if you can avoid it
Create object in R code chunk
require(reticulate)
dat <- c(180, 215, 210, 210, 188, 176, 209, 200)
Manipulate r.dat
in Python code chunk
198.5
Access py$avg
object in R code chunk
py$avg
[1] 198.5
LS0tCnRpdGxlOiAiRXh0ZW5kaW5nIFIgdG8gYWNjb21tb2RhdGUgJ2JpZyBkYXRhJyBhbmQgaW50ZXJmYWNlIHdpdGggb3RoZXIgc29mdHdhcmUgdGVjaG5vbG9naWVzIgpzdWJ0aXRsZTogIk1EU1IgQ2ggMTctLVRvd2FyZHMgQmlnIERhdGEiCm91dHB1dDogCiAgc2xpZHlfcHJlc2VudGF0aW9uOiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdCAgCi0tLQoKCmBgYHtyIEZyb250IE1hdHRlciwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQojIGNsZWFuIHVwIFIgZW52aXJvbm1lbnQKcm0obGlzdCA9IGxzKCkpCgojIGdsb2JhbCBvcHRpb25zCmtuaXRyOjpvcHRzX2NodW5rJHNldChldmFsPVRSVUUsIGluY2x1ZGU9VFJVRSkKb3B0aW9ucyhkaWdpdHM9NCkKCiMgbG9hZCBhbGwgcGFja2FnZXMgaGVyZQpsaWJyYXJ5KG1kc3IpCmxpYnJhcnkodGlkeXZlcnNlKQoKbGlicmFyeShiaWdsbSkKbGlicmFyeShiaWdycXVlcnkpCmxpYnJhcnkoREJJKQpsaWJyYXJ5KG1hY2xlaXNoKQpsaWJyYXJ5KHBhcmFsbGVsKQpsaWJyYXJ5KFJjcHApCmxpYnJhcnkocmV0aWN1bGF0ZSkKbGlicmFyeShyc3RhbikKbGlicmFyeShzcGFya2x5cikKCgojIGlucHV0cyBzdW1tYXJ5CgpgYGAKCgojIEFnZW5kYQoKCiMjIyMgQW5ub3VuY2VtZW50cwoKLSBDaCAxNyBwcm9ncmFtbWluZyBub3RlYm9vayBub3QgYXNzaWduZWQsIGJ1dCAqaGlnaGx5KiByZWNvbW1lbmRlZAotIHNldmVyYWwgc3RlcHMgdGFrZSBzb21lIG5vbi10cml2aWFsIGNvbmZpZ3VyYXRpb24gdGhhdCBJIGRvbid0IHdhbnQgdG8gcmVxdWlyZSBwZXIgc2UKCgojIE1EU1IgQ2ggMTc6IFRvd2FyZHMgYmlnIGRhdGEKCi0gMyBWJ3Mgb2YgYmlnIGRhdGEKICAgIC0gVm9sdW1lCiAgICAtIFZhcmlldHkKICAgIC0gVmVsb2NpdHkKLSAiYmlnIGRhdGEgaXMgd2hlbiB5b3VyIHdvcmtmbG93IGJyZWFrcyIgLS1SYW5keSBQcnVpbQotIGhvdyBiaWcgaXMgImJpZyIgaXMgcmVsYXRpdmUKICAgIC0gcGVuY2lsL3BhcGVyIHdvcmtmbG93OiAzMCByb3dzICYgMyBjb2x1bW5zIGlzICJiaWciCiAgICAtIFRJIDgzIHdvcmtmbG93OiBsYXJnZXIgdGhhbiA5OSByb3dzIChvciBjb2x1bW5zKSBpcyAiYmlnIgogICAgLSBNUyBFeGNlbCB3b3JrZmxvdzogbGFyZ2VyIHRoYW4gYW55IG9mIHRoZSBmb2xsb3dpbmcgY29uc3RyYWludHMgaXMgImJpZyIKICAgICAgICAtIDEsMDQ4LDU3NiByb3dzICgkMl4yMCQpCiAgICAgICAgLSAxNiwzODQgY29sdW1ucyAoJDJeMTQkKQogICAgICAgIC0gMjU1IGNoYXJhY3RlcnMgaW4gYSBjb2x1bW4gKCQyXjggLSAxJCkKICAgIC0gTGFwdG9wIHJ1bm5pbmcgUjogbGFyZ2Ugd2l0aCByZXNwZWN0IHRvIGF2YWlsYWJsZSBtZW1vcnkKCgojIEJpZ2dlc3Qgb2YgdGhlIGJpZy4uLiAoZm9yIHBlcnNwZWN0aXZlKQoKLSB0aGUgTGFyZ2UgSGFkcm9uIENvbGxpZGVyIGluIEdlbmV2YSBnZW5lcmF0ZXMgMjUgcGV0YWJ5dGVzIG9mIGRhdGEgcGVyIHllYXIKICAgIC0gb25lIHBldGFieXRlIGlzIGEgbWlsbGlvbiBnaWdhYnl0ZXMKICAgIC0gYWxsIHRoZSB3b3JrZmxvd3MgYXJlIGJyb2tlbgogICAgLSB0aGV5IGFjdHVhbGx5IG9ubHkgc2F2ZSAwLjAwMSUgb2YgdGhlIGRhdGEgZ2VuZXJhdGVkCi0gT3RoZXJzIGp1c3QgZm9yIGZ1biAoW2Jhc2VkIG9uIDIwMTggRm9yYmVzIGFydGljbGVdKGh0dHBzOi8vd3d3LmZvcmJlcy5jb20vc2l0ZXMvYmVybmFyZG1hcnIvMjAxOC8wNS8yMS9ob3ctbXVjaC1kYXRhLWRvLXdlLWNyZWF0ZS1ldmVyeS1kYXktdGhlLW1pbmQtYmxvd2luZy1zdGF0cy1ldmVyeW9uZS1zaG91bGQtcmVhZC8jN2VmNTA4Yzc2MGJhKSkKICAgIC0gR29vZ2xlIHByb2Nlc3NlcyA0MCwwMDAgc2VhcmNoZXMgKnBlciBzZWNvbmQqCiAgICAtIDMwMCBtaWxsaW9uIHBob3RvcyB1cGxvYWRlZCB0byBGYWNlYm9vayBwZXIgZGF5CiAgICAtIFRoZSBXZWF0aGVyIENoYW5uZWwgcmVjZWl2ZXMgMTgsMDU1LDU1NiBmb3JlY2FzdCByZXF1ZXN0cyAqcGVyIG1pbnV0ZSoKCiFbaHR0cHM6Ly93d3cuaWZsc2NpZW5jZS5jb20vdGVjaG5vbG9neS9ob3ctbXVjaC1kYXRhLWRvZXMtdGhlLXdvcmxkLWdlbmVyYXRlLWV2ZXJ5LW1pbnV0ZS9dKDIwMThiaWdEYXRhLnBuZyl7d2lkdGg9OTUlfQoKCgojIFdoYXQgaGFwcGVucyB3aGVuIGRhdGEgc3RhcnQgZ2V0dGluZyBiaWcgKGluIFIpCgotIFRoZSBkYXRhIG1heSBub3QgbG9hZCBpbnRvIG1lbW9yeQotIEFuYWx5emluZyB0aGUgZGF0YSBtYXkgdGFrZSBhIChyZWFsbHkpIGxvbmcgdGltZQotIFZpc3VhbGl6YXRpb25zIGdldCBtZXNzeQoKIyBTaWRlYmFyIGFib3V0IFIgdnMgU0FTIGZvciBsYXJnZSBkYXRhIHNldHMKCi0gU0FTIGFsbG9jYXRlcyBtZW1vcnkgZHluYW1pY2FsbHkgdG8ga2VlcCBkYXRhIG9uIGRpc2sgKGJ5IGRlZmF1bHQpCi0gUiBsb2FkcyBhbGwgZGF0YSBpbnRvIG1lbW9yeSAoYnkgZGVmYXVsdCkKLSBib3R0b20gbGluZTogCiAgICAtIGJ5IGRlZmF1bHQsIFNBUyBoYW5kbGVzIHZlcnkgbGFyZ2UgZGF0YXNldHMgYmV0dGVyLi4uIGl0ICJqdXN0IHdvcmtzIgogICAgLSBidXQgZG9uJ3QgdXNlIHRoZSBSIGRlZmF1bHQhCgojIE1lbW9yeSBsaW1pdHMgaW4gUgoKLSBJZiB5b3UncmUgcnVubmluZyAzMi1iaXQgUiBvbiBhbnkgT1MsIGl0J2xsIGJlIDIgb3IgMyBHQgogICAgLSBub3RlOiAyR0Igb2YgbWVtb3J5IHVzZWQgYnkgUiBpcyBub3QgdGhlIHNhbWUgYXMgMkdCIG9uIGRpc2sKICAgIC0gT3ZlcmhlYWQgZm9yIFIgdG8ga2VlcCB0cmFjayBvZiB5b3VyIGRhdGEKICAgIC0gTWVtb3J5IHVzZWQgZm9yIGFuYWx5c2lzLCBldGMuCiAgICAtIFByb2JhYmx5IG5vdCBtb3JlIHRoYW4gYWJvdXQgNTAwTUIgb24gZGlzaz8KLSBJZiB5b3UncmUgcnVubmluZyA2NC1iaXQgUiBvbiBhIDY0LWJpdCBPUywgdGhlIHVwcGVyIGxpbWl0IHNob3VsZCBiZSBpbmZpbml0ZSAoYnV0IGl0J3Mgbm90KQogICAgLSBQYWNrYWdlIGBiaWdtZW1vcnlgCiAgICAgICAgLSAiTWFuYWdlIG1hc3NpdmUgbWF0cmljZXMgd2l0aCBzaGFyZWQgbWVtb3J5IGFuZCBtZW1vcnktbWFwcGVkIGZpbGVzIgogICAgICAgIC0gdXNlIGluIHBhcmFsbGVsIGVudmlyb25tZW50cyBjYW4gcHJvdmlkZSBzdWJzdGFudGlhbCBzcGVlZCBhbmQgbWVtb3J5IGVmZmljaWVuY2llcwogICAgICAgIC0gdXNlcyBhIHBvaW50ZXIgdG8gYSBDKysgZGF0YSBzdHJ1Y3R1cmUKICAgICAgICAtIChjb24pIG1hdHJpeCBkYXRhIHN0cnVjdHVyZSByZXF1aXJlcyBob21vZ2VuZWl0eSAKICAgIC0gUGFja2FnZSBgZmZgCiAgICAgICAgLSAiTWVtb3J5LUVmZmljaWVudCBTdG9yYWdlIG9mIExhcmdlIERhdGEgb24gRGlzayBhbmQgRmFzdCBBY2Nlc3MgRnVuY3Rpb25zIgogICAgICAgIC0gcHJvdmlkZXMgZGF0YSBzdHJ1Y3R1cmVzIHRoYXQgYXJlIHN0b3JlZCBvbiBkaXNrIGJ1dCBiZWhhdmUgKGFsbW9zdCkgYXMgaWYgdGhleSB3ZXJlIGluIFJBTQogICAgICAgIC0gdXNlcyBhIHBvaW50ZXIgdG8gYSBmbGF0IGJpbmFyeSBmaWxlIHN0b3JlZCBvbiBkaXNrLCBhbmQgaXQgY2FuIGJlIHNoYXJlZCBhY3Jvc3MgZGlmZmVyZW50IHNlc3Npb25zCiAgICAgICAgLSBwZXJtaXRzIGhldGVyb2dlbmVvdXMgZGF0YSBzdHJ1Y3R1cmVzIAoKCiMgIFN0cmF0ZWdpZXMgd2hlbiBkYXRhIGdldCBiaWcKCi0gTWFrZSB0aGUgZGF0YSBzbWFsbGVyIAotIEdldCBhIGJpZ2dlciBjb21wdXRlciAKLSBBY2Nlc3MgdGhlIGRhdGEgZGlmZmVyZW50bHkKLSBTcGxpdCB1cCB0aGUgZGF0YXNldCBmb3IgYW5hbHlzaXMKCgojIElmIHRoaW5ncyBhcmUganVzdCBzbG93Li4uCgotIHRpbWUgeW91ciBjb2RlCiAgICAtIG9uZS1saW5lcjogCiAgICBgYGAKICAgICAgICBzeXN0ZW0udGltZSg8Y2FsbD4pCiAgICBgYGAKICAgIC0gYWx0ZXJuYXRpdmU6IAogICAgYGBgCiAgICAgICAgc3RhcnRfdGltZSA8LSBwcm9jLnRpbWUoKQogICAgICAgIDxjYWxsPgogICAgICAgIHByb2MudGltZSgpIOKAkyBzdGFydF90aW1lCiAgICBgYGAKLSBwcm9maWxlIHlvdXIgY29kZSB0byBmaW5kIG91dCB3aGljaCBzcGVjaWZpYyBvcGVyYXRpb25zIGFyZSBzbG93aW5nIHlvdSBkb3duCi0gc21hbGwgY2hhbmdlcyAoZS5nLiwgdmVjdG9yaXplIHRvIGF2b2lkIGEgbG9vcCkgY2FuIGhhdmUgaHVnZSBiZW5lZml0CgoKIyMjIyBSZWNhbGwgdGhlIHRlbGxlciBzaW11bGF0aW9uOiAKCmBgYHtyfQoKYW55X2FjdGl2ZSA8LSBmdW5jdGlvbihkZikgewogICMgcmV0dXJuIFRSVUUgaWYgc29tZW9uZSBoYXMgbm90IGZpbmlzaGVkCiAgcmV0dXJuKG1heChkZiRlbmR0aW1lKSA9PSBJbmYpCn0KCm5leHRfY3VzdG9tZXIgPC0gZnVuY3Rpb24oZGYpIHsKICAjIHJldHVybnMgdGhlIG5leHQgY3VzdG9tZXIgaW4gbGluZQogIHJlcyA8LSBmaWx0ZXIoZGYsIGVuZHRpbWUgPT0gSW5mKSAlPiUKICAgIGFycmFuZ2UoYXJyaXZhbCkKICByZXR1cm4oaGVhZChyZXMsIDEpKQp9Cgp1cGRhdGVfY3VzdG9tZXIgPC0gZnVuY3Rpb24oZGYsIGN1c3RfbnVtLCBlbmRfdGltZSkgewogICMgc2V0cyB0aGUgZW5kIHRpbWUgb2YgYSBzcGVjaWZpYyBjdXN0b21lcgogIHJldHVybihtdXRhdGUoZGYsIGVuZHRpbWUgPSBpZmVsc2UoY3VzdG51bSA9PSBjdXN0X251bSwgZW5kX3RpbWUsIGVuZHRpbWUpKSkKfQoKCnRlbGxlcl9zaW0gPC0gZnVuY3Rpb24obiA9IDEvMiwgbSA9IDMvMiwgaG91cnMgPSA2KSB7CiAgIyBzaW11bGF0aW9uIG9mIGJhbmsgd2hlcmUgdGhlcmUgaXMganVzdCBvbmUgdGVsbGVyCiAgIyBuOiBleHBlY3RlZCBudW1iZXIgb2YgY3VzdG9tZXJzIHBlciBtaW51dGUKICAjIG06IGV4cGVjdGVkIGxlbmd0aCBvZiB0cmFuc2FjdGlvbiBpcyBtIG1pbnV0ZXMKICAjIGhvdXJzOiBiYW5rIG9wZW4gZm9yIHRoaXMgbWFueSBob3VycwogIAogIGN1c3RvbWVycyA8LSBycG9pcyhob3VycyAqIDYwLCBsYW1iZGEgPSBuKQogIGFycml2YWwgPC0gbnVtZXJpYyhzdW0oY3VzdG9tZXJzKSkKICBwb3NpdGlvbiA8LSAxCiAgZm9yIChpIGluIDE6bGVuZ3RoKGN1c3RvbWVycykpIHsKICAgIG51bWN1c3QgPC0gY3VzdG9tZXJzW2ldCiAgICBpZiAobnVtY3VzdCAhPSAwKSB7CiAgICAgIGFycml2YWxbcG9zaXRpb246KHBvc2l0aW9uICsgbnVtY3VzdCAtIDEpXSA8LSByZXAoaSwgbnVtY3VzdCkKICAgICAgcG9zaXRpb24gPC0gcG9zaXRpb24gKyBudW1jdXN0CiAgICB9CiAgfQogIGR1cmF0aW9uIDwtIHJleHAobGVuZ3RoKGFycml2YWwpLCByYXRlID0gMS9tKSAgIyBFW1hdPW0KICBkZiA8LSBkYXRhLmZyYW1lKGFycml2YWwsIGR1cmF0aW9uLCBjdXN0bnVtID0gMTpsZW5ndGgoZHVyYXRpb24pLCAKICAgICAgICAgICAgICAgICAgIGVuZHRpbWUgPSBJbmYsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKICAKICBlbmR0aW1lIDwtIDAgIyBzZXQgdXAgYmVnaW5uaW5nIG9mIHNpbXVsYXRpb24KICB3aGlsZSAoYW55X2FjdGl2ZShkZikpIHsgIyBhbnlvbmUgbGVmdCB0byBzZXJ2ZQogICAgbmV4dF9vbmUgPC0gbmV4dF9jdXN0b21lcihkZikKICAgIG5vdyA8LSBpZmVsc2UobmV4dF9vbmUkYXJyaXZhbCA+PSBlbmR0aW1lLCBuZXh0X29uZSRhcnJpdmFsLCBlbmR0aW1lKQogICAgZW5kdGltZSA8LSBub3cgKyBuZXh0X29uZSRkdXJhdGlvbgogICAgZGYgPC0gdXBkYXRlX2N1c3RvbWVyKGRmLCBuZXh0X29uZSRjdXN0bnVtLCBlbmR0aW1lKQogIH0KICBkZiA8LSBtdXRhdGUoZGYsIHRvdGFsdGltZSA9IGVuZHRpbWUgLSBhcnJpdmFsKQogIHJldHVybihmYXZzdGF0cyh+IHRvdGFsdGltZSwgZGF0YSA9IGRmKSkKfQoKYGBgCgoKIyBQcm9maWxpbmcgdGhlIHRlbGxlciBzaW11bGF0aW9uIAoKLSBsZXQncyBzZWUgd2hlcmUgdGhlIHRpbWUgZ29lcy4uLgotIG5vdCBiYWQgaW4gdGhpcyBjYXNlLCBidXQgeW91IGNvdWxkIGRpc2NvdmVyIGludGVyZXN0aW5nIHRoaW5ncy4uLiBsaWtlCiAgICAtIG1heWJlIHNvbWUgbW9kZWxpbmcgZnVuY3Rpb24gZGVmYXVsdHMgdG8gYm9vdHN0cmFwIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIHRoYXQgeW91IGRvbuKAmXQgY2FyZSBhYm91dCB3aXRoIDEwMDAgaXRlcmF0aW9ucyBwZXIgbW9kZWwKICAgIC0geW91IGRpZCBzb21ldGhpbmcgZm9yIGV2ZXJ5IGxpbmUgb2YgeW91ciBodWdlIGRhdGEgZnJhbWUgYW5kIHRoZW4gY29tYmluZSByZXN1bHRzIHVzaW5nIGBjKClgIG9yIGByYmluZCgpYCByYXRoZXIgdGhhbiBhc3NpZ25pbmcgdG8gYSBwcmVhbGxvY2F0ZWQgdmVjdG9yIG9yIG1hdHJpeAoKCmBgYHtyfQpScHJvZigiVGVsbGVyU2ltUHJvZmlsZSIpCnRlbGxlcl9zaW0oKQpScHJvZihOVUxMKQpoZWFkKHN1bW1hcnlScHJvZigiVGVsbGVyU2ltUHJvZmlsZSIpJGJ5LnNlbGYsIDIwKQpgYGAKCgojIFNUSUxMIHNsb3c/IFRyeSBgYmlnbG1gCgotIGBiaWdsbWAgcGFja2FnZSBoYXMgYW4gZWZmaWNpZW50IGFsdGVybmF0aXZlIHRvIHRoZSBgbG1gIGZ1bmN0aW9uCi0gaXQgY2FuIGV2ZW4gZml0IGdlbmVyYWxpemVkIGxpbmVhciBtb2RlbHMgKHJlZ3Jlc3Npb24gJiBsb2dpc3RpYyByZWdyZXNzaW9uKSB3aXRoIGRhdGEgZnJhbWVzIHRoYXQgYXJlIGxhcmdlciB0aGFuIG1lbW9yeQoKYGBge3J9CnJlcXVpcmUoYmlnbG0pCgpuIDwtIDIwMDAwCnAgPC0gNTAwCmQgPC0gYXMuZGF0YS5mcmFtZShtYXRyaXgocm5vcm0obiAqIChwICsgMSkpLCBuY29sID0gKHAgKyAxKSkpCmV4cGxfdmFycyA8LSBwYXN0ZShwYXN0ZTAoIlYiLCAyOihwKzEpKSwgY29sbGFwc2UgPSAiICsgIikKbXlfZm9ybXVsYSA8LSBhcy5mb3JtdWxhKHBhc3RlKCJWMSB+ICIsIGV4cGxfdmFycykpCgojIHByb2ZpbGUgYGxtYCB2cyBgYmlnbG1gCnN5c3RlbS50aW1lKGxtKG15X2Zvcm11bGEsIGRhdGEgPSBkKSkKCnN5c3RlbS50aW1lKGJpZ2xtKG15X2Zvcm11bGEsIGRhdGEgPSBkKSkKYGBgCgoKIyBOZXh0IHN0ZXA6IHBhcmFsbGVsIHByb2Nlc3NpbmcKCi0gKipQYXJhbGxlbCBwcm9jZXNzaW5nKiogaXMgYmFzaWNhbGx5IGZhcm1pbmcgb3V0IHN1YnRhc2tzIHRvIGluZGVwZW5kZW50IHByb2Nlc3NvcnMsIHRoZW4gbWVyZ2luZyByZXN1bHRzCi0gZWZmZWN0aXZlbHkganVzdCBhbGxvY2F0ZXMgbW9yZSBSQU0gZm9yIHRoZSBwcm9ibGVtCgpgYGB7cn0KbXlfY29yZXMgPC0gZGV0ZWN0Q29yZXMoKQpteV9jb3JlcwpgYGAKCgotICoqZW1iYXJhc3NpbmdseSBwYXJhbGxlbCBjb21wdXRpbmcqKgogICAgLSBJIG5lZWQgdG8gcmVwZWF0IHRoZSBzYW1lIHRhc2sgbWFueSB0aW1lcwogICAgLSBvcmRlciBvZiBpbXBsZW1lbnRhdGlvbiBkb2Vzbid0IG1hdHRlcgotIEkgaGF2ZSA0IHRvdGFsIGNvcmVzLCBidXQgeW91IHNob3VsZCBhbHdheXMgc2F2ZSBvbmUgZm9yIHlvdXIgb3BlcmF0aW5nIHN5c3RlbQotIGUuZy4sIGNvbXBhcmlzb24gZm9yIHNldmVyYWwgaXRlcmF0aW9ucyBvZiB0ZWxsZXIgc2ltdWxhdGlvbgoKYGBge3J9CmsgPC0gNQoKIyB3aXRob3V0IHBhcmFsbGVsIHByb2Nlc3NpbmcKc3lzdGVtLnRpbWUobGFwcGx5KDE6aywgdGVsbGVyX3NpbSkpCgojIHBhcmFsbGVsaXplIHdpdGggMyBjb3JlcwpzeXN0ZW0udGltZShtY2xhcHBseSgxOmssIHRlbGxlcl9zaW0sIG1jLmNvcmVzID0gbXlfY29yZXMgLSAxKSkKYGBgCgoKIyBNYXBSZWR1Y2UgKHBhcmFsbGVsaXphdGlvbiB0aGF0J3Mgbm90IGVtYmFyYXNzaW5nPykKCi0gcHJvZ3JhbW1pbmcgcGFyYWRpZ20gZm9yIHBhcmFsbGVsIGNvbXB1dGluZwogICAgLSB0d28gcGhhc2UgYWxnb3JpdGhtCiAgICAtICoqbWFwKiotLWZhcm0gb3V0IHBhcmFsbGl6ZWFibGUgdGFzayB0byBtYW55IG1hY2hpbmVzCiAgICAtICoqcmVkdWNlKiotLWNvbWJpbmUgcmVzdWx0cwotIHRyaWNreSBwYXJ0OiAqKnlvdSoqIGhhdmUgdG8gZGVmaW5lIHRoZSBgbWFwYCBmdW5jdGlvbiBhbmQgdGhlIGByZWR1Y2VgIGZ1bmN0aW9uCi0gbmVlZHMgc29mdHdhcmUgaW1wbGVtZW50YXRpb24KICAgIC0gSGFkb29wCiAgICAtIFNwYXJrCgojIEhhZG9vcCAmIFNwYXJrCgotIEhhZG9vcCB3YXMgZmlyc3QgdG8gcmVhbGx5IHRhY2tsZSBNYXBSZWR1Y2UgCi0gSGFkb29wIE1hcFJlZHVjZSBoYXMgYmVlbiBzdXBlcnNlZGVkIGJ5IFNwYXJrLCAKICAgIC0gdG9vbHMgdGhhdCBlbWVyZ2VkIGFzIHRoZSAiZWNvc3lzdGVtIiBhcm91bmQgaXQgYXJlIHN0aWxsIHBvcHVsYXIgKEhERlMpCiAgICAtICJsZWdhY3kiIHByb2plY3RzIG1pZ2h0IHN0aWxsIHVzZSBIYWRvb3AgTWFwUmVkdWNlCi0gQXBhY2hlIFNwYXJrIGlzIGNvbnNpZGVyZWQgc3VwZXJpb3IgZm9yIGEgZmV3IHJlYXNvbnMKICAgIC0gaGFkIHRoZSBiZW5lZml0IG9mIGltcGxlbWVudGluZyBsZXNzb25zIGxlYXJuZWQgZnJvbSBIYWRvb3AKICAgIC0ga2VlcCB0aGUgZ29vZC0tSERGUyAoSGFkb29wIERpc3RyaWJ1dGVkIEZpbGUgU3lzdGVtKSBmb3IgZGlzayBzdG9yYWdlCiAgICAtIGltcHJvdmUgdGhlIHdlYWtuZXNzZXMtLXByaW9yaXRpemUgUkFNIHJhdGhlciB0aGFuIGRpc2sgc3RvcmFnZSB3aGVuZXZlciBwb3NzaWJsZQoKCiMgSW50ZXJmYWNlIHdpdGggU3BhcmsKCi0gU3BhcmsgcHJvdmlkZXMgcHJvdmlkZXMgYW4gaW50ZXJmYWNlIGZvciBwcm9ncmFtbWluZyBlbnRpcmUgY2x1c3RlcnMKLSBhICoqY29tcHV0ZXIgY2x1c3RlcioqIGlzIGEgc2V0IG9mIGNvbm5lY3RlZCBjb21wdXRlcnMgdGhhdCB3b3JrIHRvZ2V0aGVyIGFzIGEgc2luZ2xlIHN5c3RlbQotIHRoZSBgc3BhcmtseXJgIHBhY2thZ2UgaW4gUiBtYWtlcyBpdCBlYXN5IHRvIAogICAgLSBpbnN0YWxsIGEgbG9jYWwgU3BhcmsgY2x1c3RlciAoZnJvbSB3aXRoaW4gUikKICAgIC0gY29ubmVjdCB0byBhIGxvY2FsIG9yIHJlbW90ZSBjbHVzdGVyCgpgYGB7ciBldmFsPUZBTFNFfQpyZXF1aXJlKHNwYXJrbHlyKQojIHNwYXJrX2luc3RhbGwoKSAgICMgb25seSBvbmNlIHBlciBtYWNoaW5lCmBgYAoKCiMgSW50ZXJmYWNlIHdpdGggU3BhcmsKCgpgYGB7cn0KIyBtb2RpZnkgbWFzdGVyIHRvIGNvbm5lY3QgdG8gYSByZW1vdGUgU3BhcmsgY2x1c3RlcgpzYyA8LSBzcGFya19jb25uZWN0KG1hc3RlciA9ICJsb2NhbCIpCmNsYXNzKHNjKQpgYGAKCgpgYGB7cn0KYmFieW5hbWVzX3RibCA8LSAKICBzYyAlPiUKICBjb3B5X3RvKGJhYnluYW1lczo6YmFieW5hbWVzLCAiYmFieW5hbWVzIiwgb3ZlcndyaXRlID0gVFJVRSkKCmNsYXNzKGJhYnluYW1lc190YmwpCmBgYAoKCiMgQ291bnRpbmcgTWF0dGhld3MKCmBgYHtyfQpiYWJ5bmFtZXNfdGJsICU+JQogIGZpbHRlcihuYW1lID09ICJNYXR0aGV3IikgJT4lCiAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgc3VtbWFyaXNlKE4gPSBuKCksIAogICAgICAgICAgICB0b3RhbF9iaXJ0aHMgPSBzdW0obikpICU+JQogIGFycmFuZ2UoZGVzYyh0b3RhbF9iaXJ0aHMpKSAlPiUKICBoZWFkKCkKYGBgCgoKIyBGcm9tIGBkcGx5cmAgdG8gU1FMCgotIHdoZW5ldmVyIGBkcGx5cmAgbWVldHMgYW4gb2JqZWN0IHdpdGggY2xhc3MgYHRibF9zcWxgIChsaWtlIGBiYWJ5bmFtZXNfdGJsYCksIGBkcGx5cmAgKiphdXRvbWF0aWNhbGx5KiogdHJhbnNsYXRlcyB0aGUgUiBwaXBlbGluZSBpbnRvICoqU1FMKioKLSAqKlNRTCoqIChzdHJ1Y3R1cmVkIHF1ZXJ5IGxhbmd1YWdlKSBpcyBhIHdpZGVseSB1c2VkIGxhbmd1YWdlIGZvciBxdWVyeWluZyByZWxhdGlvbmFsIGRhdGFiYXNlcywgYW1vbmcgb3RoZXIgcHVycG9zZXMKLSBRdWVyaWVzIGluIFNRTCBzdGFydCB3aXRoIHRoZSBgU0VMRUNUYCBrZXl3b3JkIGFuZCBjb25zaXN0IHNldmVyYWwgY2xhdXNlcyB3aGljaCAqKm11c3QgdG8gYmUgd3JpdHRlbiBpbiBvcmRlcioqOyBiYXNpY2FsbHkKICAgIC0gYFNFTEVDVGAgKCoqcmVxdWlyZWQqKiktLWxpa2UgYHNlbGVjdCgpYCBpbiBgZHBseXJgIChhbmQgcG9zc2libHkgY29tYmluZWQgd2l0aCBgbXV0YXRlKClgKQogICAgLSBgRlJPTWAgKCoqcmVxdWlyZWQqKiktLWxpa2UgdGFibGUgYmVmb3JlIHRoZSBmaXJzdCBgJT4lYCBpbiBgZHBseXJgCiAgICAtIGBKT0lOYC0tbGlrZSBgam9pbigpYCB2ZXJicyBpbiBgZHBseXJgCiAgICAtIGBXSEVSRWAtLWxpa2UgYGZpbHRlcigpYCB2ZXJiIGluIGBkcGx5cmAKICAgIC0gYEdST1VQIEJZYC0tbGlrZSBgZ3JvdXBfYnkoKWAgdmVyYiBpbiBgZHBseXJgCiAgICAtIGBIQVZJTkdgLS1saWtlIHVzaW5nIGEgc2Vjb25kIGBmaWx0ZXIoKWAgaW4gYGRwbHlyYCBhZnRlciB0aGUgcm93cyBoYXZlIGFscmVhZHkgYmVlbiAKICAgIC0gYE9SREVSIEJZYC0tbGlrZSBgYXJyYW5nZSgpYCB2ZXJiIGluIGBkcGx5cmAgCiAgICAtIGBMSU1JVGAtLXNvcnQgb2YgbGlrZSBgaGVhZCgpYCBidXQgbW9yZSB2ZXJzYXRpbGUKLSBGb3IgZXhhbXBsZSwgbGV0J3MgcmV2aXNpdCBvdXIgcHJldmlvdXMgYGRwbHlyYCBxdWVyeQoKYGBge3J9CnEgPC0gCiAgYmFieW5hbWVzX3RibCAlPiUKICBmaWx0ZXIobmFtZSA9PSAiTWF0dGhldyIpICU+JQogIGdyb3VwX2J5KHllYXIpICU+JQogIHN1bW1hcmlzZShOID0gbigpLCAKICAgICAgICAgICAgdG90YWxfYmlydGhzID0gc3VtKG4pKSAlPiUKICBhcnJhbmdlKGRlc2ModG90YWxfYmlydGhzKSkgJT4lCiAgaGVhZCgpCgpxCmBgYAoKYGBge3J9CnNob3dfcXVlcnkocSkKYGBgCgojIFF1ZXJ5aW5nIHRoZSBTcGFyayBjbHVzdGVyCgotIFNwYXJrIGlzIGEgcGFyYWxsZWxpemVkIHRlY2hub2xvZ3kgZGVzaWduZWQgdG8gc3VwZXJzZWRlIFNRTCwgYnV0IGl0J3Mgc3RpbGwgdXNlZnVsIHRvIGtub3cgU1FMIGluIG9yZGVyIHRvIHVzZSBTcGFyawotIGhlcmUsIHdlJ2xsIHF1ZXJ5IHRoZSBTcGFyayBjbHVzdGVyIHVzaW5nIHRoZSBjb25uZWN0aW9uIHdlJ3ZlIGRlZmluZWQgYHNjYCB3aXRoIHRoZSBTUUwgc3RhdGVtZW50IGVxdWl2YWxlbnQgdG8gb3VyIGBkcGx5cmAgd3JhbmdsaW5nCgpgYGB7cn0KcmVxdWlyZShEQkkpCgpkYkdldFF1ZXJ5KGNvbm4gPSBzYywgc3RhdGVtZW50ID0gIgogICAgICAgICAgICAgICAgICAgICAgICAgU0VMRUNUIHllYXIsIHN1bSgxKSBhcyBOLCBzdW0obikgYXMgdG90YWxfYmlydGhzCiAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIGJhYnluYW1lcwogICAgICAgICAgICAgICAgICAgICAgICAgV0hFUkUgbmFtZSA9PSAnTWF0dGhldycKICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHllYXIKICAgICAgICAgICAgICAgICAgICAgICAgIE9SREVSIEJZIHRvdGFsX2JpcnRocyBkZXNjCiAgICAgICAgICAgICAgICAgICAgICAgICBMSU1JVCA2CiAgICAgICAgICAgICAgICAgICAgICAgICAiKQpgYGAKCgojIE1vZGVsaW5nIHdpdGggU3BhcmsKCi0gYHdoYXRlbHlfMjAxNWAgaGFzIHNvbWUgd2VhdGhlciBkYXRhIGZyb20gTWFzc2FjaHVzZXR0cyAoaW4gdGhlIGBtYWNsZWlzaGAgcGFja2FnZSkKLSBTcGFyayBoYXMgYSBtYWNoaW5lIGxlYXJuaW5nIGxpYnJhcnkgd2hpY2ggaW5jbHVkZXMgbWFueSBvZiB0aGUgc3VwZXJ2aXNlZC91bnN1cGVydmlzZWQgbGVhcm5pbmcgdG9vbHMgd2UndmUgZGlzY3Vzc2VkIHRoaXMgc2VtZXN0ZXIKLSBsZXQncyB1c2UgU3BhcmsgdG8gZml0IGEgbXVsdGlwbGUgcmVncmVzc2lvbiBtb2RlbCBhcyBhbiBleGFtcGxlCgpgYGB7cn0KcmVxdWlyZShtYWNsZWlzaCkKCndlYXRoZXJfdGJsIDwtIGNvcHlfdG8oc2MsIHdoYXRlbHlfMjAxNSwgb3ZlcndyaXRlID0gVFJVRSkKCndlYXRoZXJfdGJsICU+JQogIHNwYXJrbHlyOjptbF9saW5lYXJfcmVncmVzc2lvbihyYWluZmFsbCB+IHRlbXBlcmF0dXJlICsgcHJlc3N1cmUgKyByZWxfaHVtaWRpdHkpICU+JQogIHN1bW1hcnkoKQoKYGBgCgoKCgojIEFsdGVybmF0aXZlcyB0byBTUUwgKEdvb2dsZSBCaWdRdWVyeSkKCgpgYGB7cn0KcmVxdWlyZShiaWdycXVlcnkpCgpwcm9qZWN0X2lkIDwtICJzdGF0LTM4MC1jbGFzcy1kZW1vIiAgICMgQmVja21hbidzIGdvb2dsZSBjbG91ZCBwcm9qZWN0IElECgpzcWwgPC0gIlNFTEVDVCB3b3JkLCBjb3VudChkaXN0aW5jdCBjb3JwdXMpIGFzIG51bVBsYXlzLCBzdW0od29yZF9jb3VudCkgYXMgTgogICAgICAgIEZST00gW3B1YmxpY2RhdGE6c2FtcGxlcy5zaGFrZXNwZWFyZV0KICAgICAgICBHUk9VUCBCWSB3b3JkCiAgICAgICAgT1JERVIgQlkgTiBkZXNjCiAgICAgICAgTElNSVQgMTAKICAgICAgICAiCnF1ZXJ5X2V4ZWMocXVlcnkgPSBzcWwsIHByb2plY3QgPSBwcm9qZWN0X2lkKQoKYGBgCgoKIyBgUmNwcGAKCi0gYFJDUFA6OmNwcEZ1bmN0aW9uKClgIGFsbG93cyB5b3UgdG8gd3JpdGUgQysrIGZ1bmN0aW9ucyBpbiBSCi0gYFJjcHA6OnNvdXJjZUNwcCgpYCBsb2FkcyBhIEMrKyBmaWxlIGZyb20gZGlzayBpbiB0aGUgc2FtZSB3YXkgeW91IHVzZSBzb3VyY2UoKSB0byBsb2FkIGEgZmlsZSBvZiBSIGNvZGUuIAotIFJjcHAgd2lsbCBjb21waWxlIHRoZSBDKysgY29kZSBhbmQgY29uc3RydWN0IGFuIFIgZnVuY3Rpb24gdGhhdCBjb25uZWN0cyB0byB0aGUgY29tcGlsZWQgQysrIGZ1bmN0aW9uCi0gbW9yZSBoZXJlOiA8aHR0cDovL2Fkdi1yLmhhZC5jby5uei9SY3BwLmh0bWw+CgoKYGBge3J9CnJlcXVpcmUoUmNwcCkKCiMgd3JpdGUgYSBzaW1wbGUgZnVuY3Rpb24gaW4gQysrCgpjcHBGdW5jdGlvbignaW50IGFkZGVtdXAoaW50IHgsIGludCB5LCBpbnQgeikgewogIGludCBzdW0gPSB4ICsgeSArIHo7CiAgcmV0dXJuIHN1bTsKfScpCgoKIyBSIHJlY29nbml6ZXMgYGFkZGVtdXBgIGxpa2UgYW55IG90aGVyIGZ1bmN0aW9uCmFkZGVtdXAKYGBgCgpgYGB7cn0KYWRkZW11cCgyLCA0LCA2KQpgYGAKCgoKIyBTdGFuCgotIEJheWVzaWFuIHN0YXRpc3RpY2FsIGluZmVyZW5jZSB3aXRoIE1DTUMgc2FtcGxpbmcKLSBTdGFuIG1vZGVsIHdpdGhpbiB0aGUgY29kZSBjaHVuayBpcyBjb21waWxlZCBpbnRvIGEgInN0YW5tb2RlbCIgb2JqZWN0IAotIHJlc3VsdCBhc3NpZ25lZCB0byBhIHZhcmlhYmxlIHdpdGggdGhlIG5hbWUgZ2l2ZW4gYnkgdGhlIGBvdXRwdXQudmFyYCBvcHRpb24KCgpgYGB7c3Rhbiwgb3V0cHV0LnZhcj0iZXgxIn0KcGFyYW1ldGVycyB7CiAgcmVhbCB5WzJdOwp9Cm1vZGVsIHsKICB5WzFdIH4gbm9ybWFsKDAsIDEpOwogIHlbMl0gfiBkb3VibGVfZXhwb25lbnRpYWwoMCwgMik7Cn0KYGBgCgoKYGBge3J9CmxpYnJhcnkocnN0YW4pCmZpdCA8LSBzYW1wbGluZyhleDEsIGNvcmVzID0gMykKcHJpbnQoZml0KQpgYGAKCmBgYHtyfQojIHBvc3RlcmlvcgpzdGFuX3Bsb3QoZml0LCBwb2ludF9lc3QgPSAibWVhbiIsIHNob3dfZGVuc2l0eSA9IFRSVUUsIGZpbGxfY29sb3IgPSAiZG9kZ2VyYmx1ZSIpCgojIHRyYWNlCnN0YW5fdHJhY2UoZml0KSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoInJlZCIsICJibHVlIiwgImdyZWVuIiwgImJsYWNrIikpCgpgYGAKCgojIFB5dGhvbiBpbiBSIE1hcmtkb3duCgo8aHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9yZXRpY3VsYXRlLyNweXRob24taW4tci1tYXJrZG93bj4KCi0gYHJldGljdWxhdGVgIHBhY2thZ2UgaW5jbHVkZXMgYSBQeXRob24gZW5naW5lIGZvciBSIE1hcmtkb3duOgogICAgLSBSdW4gUHl0aG9uIGNodW5rcyBpbiBhIHNpbmdsZSBQeXRob24gc2Vzc2lvbiBlbWJlZGRlZCB3aXRoaW4geW91ciBSIHNlc3Npb24gKHNoYXJlZCB2YXJpYWJsZXMvc3RhdGUgYmV0d2VlbiBQeXRob24gY2h1bmtzKQogICAgLSBQcmludGluZyBvZiBQeXRob24gb3V0cHV0LCBpbmNsdWRpbmcgZ3JhcGhpY2FsIG91dHB1dCBmcm9tIGBtYXRwbG90bGliYC4KICAgIC0gQWNjZXNzIHRvIG9iamVjdHMgY3JlYXRlZCB3aXRoaW4gUHl0aG9uIGNodW5rcyBmcm9tIFIgdXNpbmcgdGhlIGBweWAgb2JqZWN0IChlLmcuIGBweSR4YCB3b3VsZCBhY2Nlc3MgYW4gYHhgIHZhcmlhYmxlIGNyZWF0ZWQgd2l0aGluIFB5dGhvbiBmcm9tIFIpLgogICAgLSBBY2Nlc3MgdG8gb2JqZWN0cyBjcmVhdGVkIHdpdGhpbiBSIGNodW5rcyBmcm9tIFB5dGhvbiB1c2luZyB0aGUgYHJgIG9iamVjdCAoZS5nLiBgci54YCB3b3VsZCBhY2Nlc3MgdG8gYHhgIHZhcmlhYmxlIGNyZWF0ZWQgd2l0aGluIFIgZnJvbSBQeXRob24pCi0gQnVpbHQgaW4gY29udmVyc2lvbiBmb3IgbWFueSBQeXRob24gb2JqZWN0IHR5cGVzLCBpbmNsdWRpbmcgYE51bVB5YCBhcnJheXMgYW5kIGBQYW5kYXNgIGRhdGEgZnJhbWVzLgoKCiMgYHJldGljdWxhdGVgICYgUiBOb3RlYm9va3MKCi0gYHJldGljdWxhdGVgIHBhY2thZ2UgZG9lcyBtYWtlIGl0IHByZXR0eSBlYXN5IHRvIGdvIGJhY2sgYW5kIGZvcnRoIGJldHdlZW4gUiAmIFB5dGhvbiBvYmplY3RzCi0gaXQgImp1c3Qgd29ya3MiIHdoZW4geW91IGNvbXBpbGUgUk1hcmtkb3duIAotIHRoZSBSIE5vdGVib29rIGlzbid0IHlldCBpcm9uZWQgb3V0IGluIHRoZSBjdXJyZW50IHByb2R1Y3Rpb24gcmVsZWFzZSAodjEuMSkKICAgIC0gcHJldmlldyB3aWxsIHRocm93IGVycm9ycywgZXZlbiB3aGVuIHRoZSBjb2RlIGlzIGNvcnJlY3QKICAgIC0gUiBOb3RlYm9vayBpcyBubyBiZXR0ZXIgdGhhbiBwcmV2aWV3CiAgICAtIFRoaXMgd2lsbCBiZSBmaXhlZCBpbiBSU3R1ZGlvIHYxLjIgWyhpbnN0YWxsIHByZXZpZXcgaGVyZS4uLildKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvcHJldmlldy8pICAKICAgICAgICAtIHlvdSBzaG91bGQgaW5zdGFsbCBpdCAoYWZ0ZXIgdGhlIHNlbWVzdGVyKQogICAgICAgIC0gSSBnZW5lcmFsbHkgZG9uJ3QgcmVjb21tZW5kIG1ham9yIHVwZGF0ZXMgZHVyaW5nIHRoZSBzZW1lc3RlciBpZiB5b3UgY2FuIGF2b2lkIGl0CgoKIyMjIyBDcmVhdGUgb2JqZWN0IGluIFIgY29kZSBjaHVuawoKYGBge3J9CnJlcXVpcmUocmV0aWN1bGF0ZSkKZGF0IDwtIGMoMTgwLCAyMTUsIDIxMCwgMjEwLCAxODgsIDE3NiwgMjA5LCAyMDApCmBgYAoKCiMjIyMgTWFuaXB1bGF0ZSBgci5kYXRgIGluIFB5dGhvbiBjb2RlIGNodW5rCgpgYGB7cHl0aG9ufQojIEltcG9ydCB0aGUgbnVtcHkgcGFja2FnZQppbXBvcnQgbnVtcHkKCiMgQ3JlYXRlIGEgTnVtcHkgYXJyYXkgZnJvbSBkYXRhOiBucF9kYXRhCm5wX2RhdGEgPSBudW1weS5hcnJheShyLmRhdCkKCiMgUHJpbnQgb3V0IG1lYW4gb2YgbnBfZGF0YQphdmcgPSBudW1weS5tZWFuKG5wX2RhdGEpCgpwcmludChhdmcpCmBgYAoKIyMjIyBBY2Nlc3MgYHB5JGF2Z2Agb2JqZWN0IGluIFIgY29kZSBjaHVuawoKYGBge3J9CnB5JGF2ZwpgYGAKCgoKCgoKCg==