{% raw %} Estimating the number of COVID-19 cases from population isolation level

Preface

All variables and functions have the prefix pandiso in the name.

System

The system variables and functions handle the current directory value, the manuscript directory and the image format. A function is offered to write the files in the right location and the preprint directory (images and macros) is modified only if pandiso.update.preprint is assigned to TRUE.

library(testit) # assert
# Control verbosity
VERBOSE = FALSE
# SYSTEM variables and functions
## Directory where the manuscript (preprint) is located
pandiso.preprint.dir <- "preprint"

## File extension for plots
pandiso.plot.ext <- ".png"

pandiso.file.path <- function(fn) {
  # getwd() returns the current working directory.
  fp <- file.path(getwd(), fn);
  return(fp)
}

# Assign TRUE to update files in the preprint directory
pandiso.update.preprint <- FALSE

pandiso.write.plot <- function(file_prefix, plt) {
  assert("1st parameter is file prefix string", 
         typeof(file_prefix) == "character")
  
  fn <- file.path(pandiso.preprint.dir, 
                  paste(file_prefix, pandiso.plot.ext, sep = ""))
  fp <- pandiso.file.path(fn)
  
  if (pandiso.update.preprint == FALSE) {
    if (VERBOSE) {
      print(paste("* Didn't write", fp))
      print("* Assign 'TRUE' to variable 'pandiso.update.preprint.plots' to write it.")
    
    }
    return(pandiso.update.preprint)
  }
  
  ggsave(fp)
  if (VERBOSE) {
    print(paste("* Wrote", fp))
  }
  return(pandiso.update.preprint)
}

Macros

The values used in the manuscript are assigned to \(\TeX\) macros in a file in the preprint directory.

pandiso.write.TeX_macros <- function() {
  if (pandiso.update.preprint == FALSE)
    return(pandiso.update.preprint)
  
  fp <- file.path(getwd(), pandiso.preprint.dir, "macros.tex")
  tex <- "% This file is automatically generated by R. %\n"
  tex <- paste(tex, "\\usepackage{tikz}\n", sep = "")
  tex <- paste(tex,"\\def\\pandisofitK{", 
               format(pandiso.fit.K, big.mark = ",", digits = 7, nsmall = 0), 
               "}%\n", sep = "")
  tex <- paste(tex,"\\def\\pandisofitm{", 
               format(pandiso.fit.m, big.mark = ",", digits=6, nsmall = 0),
               "}%", sep = "")
  tex <- paste(tex, "\n\\pgfkeys{%\n", sep = "")
  tex <- paste(tex,"\t/pandiso/count/time/steps/.initial=", 
               format(pandiso.count.time.steps, digits=3, nsmall = 0),
               ",%\n", sep = "")
  tex <- paste(tex, "\t/pandiso/date/first/.initial=", pandiso.date.first, ",%\n", sep = "")
  tex <- paste(tex,"\t/pandiso/date/last/.initial=", pandiso.date.last, ",%\n", sep = "")
  tex <- paste(tex,"\t/pandiso/estimation/pearson/.initial=", 
               format(pandiso.estimation.pearson, digits = 1, nsmall = 3), 
               ",%\n", sep = "")
  tex <- paste(tex,"\t/pandiso/fit/pearson/.initial=", 
               format(pandiso.fit.pearson, digits = 1, nsmall = 3), 
               ",%\n", sep = "")
  tex <- paste(tex,"\t/pandiso/fit/r/.initial=", 
               format(pandiso.fit.r, digits = 1, nsmall = 3), 
               ",%\n", sep = "")
  tex <- paste(tex,"\t/pandiso/overall/level/mean/.initial=", 
               format(pandiso.overall.level.mean, digits = 1, nsmall = 3), 
               ",%\n", sep = "")
  tex <- paste(tex,"\t/pandiso/overall/level/stdev/.initial=", 
               format(pandiso.overall.level.stdev, digits = 1, nsmall = 3), 
               ",%\n", sep = "")  
 tex <- paste(tex,"\t/pandiso/time/step/.initial=", 
               format(pandiso.time.step, digits = 1, nsmall = 0), 
               ",%\n", sep = "")  
  tex <- paste(tex, "}%", sep = "")
  writeLines(tex, fp)
  return(pandiso.update.preprint)
}

Introduction

The logistic function is used to estimate the number of individuals infected by a virus in a time \(t+1\) using the average isolation level in a population at a time \(t\). Each time step is composed by a date range in days where the isolation level is supposed to take effect over the virus spread.

Logistic function

library(ggplot2)
library(latex2exp)
# Smooth the line plotted
require(splines)
Carregando pacotes exigidos: splines
# Differential equations solver
library(deSolve)
df.logistic <- function (t, y, parms) {
  list(parms["r"] * y * (1-y/parms["K"]))
}
t_final <- 295
times <- seq(from=1, to=t_final, by=1)
y_initial <- c(y=1)
parms <- c(r=0.04, K=350)
out <- ode(func=df.logistic, y=y_initial, 
           times=times, parms=parms)
# Normalize (standardize) the data

for (i in 1:2) {
  out[,i] <- out[,i] / max(out[,i])
}
# Create the data frame from logistic output
df <- data.frame(t=out[,1], y=out[,2])
# Plot
xy <- data.frame(predict(interpSpline(df$t, df$y)))
p <- ggplot(xy, aes(x, y)) + geom_line() +
     labs(title = "Logistic function", 
          x = "t", y = "n\n") +
    geom_label(
      aes(label="midpoint", 
          x=.57,
          y=.5,
          label.size = 0.35),
      color = "black",
      label.size = NA
  ) +
  annotate("point", x = .5, y = .5, colour = "black") +
  theme_light()
Warning: Ignoring unknown aesthetics: label.size
p


pandiso.write.plot("logistic", p)
[1] FALSE
pandiso.logistic.plot <- p
rm(df, df.logistic, i, out, p, parms, times, t_final, xy, y_initial)

Stage 0 - Data Organization

The data are processed in periods of time, we use days as unit for time and avoid the use of fractional values to simplify the code and calculations. Another justification for the discrete values for time is that we are handling approximations for the conditions studied.

The value of pandiso.time.step (see Processing) was adopted after a set of tests were performed to check which value causes a better correlation in the curves for the model and estimated data. This high correlation means that the isolation level in that time step affects the number of cases in the next time step.

COVID-19 cases

The COVID-19 cases are gathered from “São Paulo Plan” organized by the government of São Paulo State. Brazil to concentrate efforts to handle the virus spread in the state. The file containing the cases, and other data, is downloaded from Github repository for the initiative.

fn <- "dados_covid_sp.csv"
# Download the data from original source
pandiso.cases.file.path <- pandiso.file.path(fn);
fp <- pandiso.cases.file.path
if (file.exists(fp) == FALSE) {
  download.file(paste("https://raw.githubusercontent.com/seade-R/dados-covid-sp/master/data/", fn, sep = ""), fp)
}
# Read CSV file organizing the data according to columns
df <- read.csv(fp, header = TRUE, sep = ";");
# Sum the cases for all cities by date
dfc <- aggregate(casos_novos~datahora, FUN=sum, data=df)
# Rename the data frame columns
names(dfc)[names(dfc) == "datahora"] <- "date"
names(dfc)[names(dfc) == "casos_novos"] <- "cases"
# Convert date string to date Class
dfc$date <- as.Date(dfc$date)
rm(fp, fn)

Population isolation level

The isolation level for all cities in the São Paulo State are stored in an Excel file downloaded from ``São Paulo Government Isolation Monitoring’’ site.

The file must be downloaded manually after clicking in the “DADOS” button, then click “Baixar” icon, choose “Tabela de referência cruzada”, “Excel” format and click “Baixar” button. We choose the Excel format because its content could be parsed with reliability. The isolation dates are synchronized cases data, only the format is different (“DD/MM/YY”), but this is fixed by changing it to “YYYY-MM-DD”.

library(readxl)
fn <- "Dados.xlsx"
pandiso.isolation.file.path <- pandiso.file.path(fn)
fp <- pandiso.isolation.file.path
df <- read_excel(fp)
New names:
* `` -> `..1`
* `` -> `..2`
* `` -> `..3`
* `` -> `..4`
* `` -> `..6`
* … and 583 more
# The isolation level data start at 5th column and the
# first row is the date, the rest are isolation level 
# values.
shift <- 4 # 5th column because indices in R start at 1
dates <- vector("character", ncol(df) - shift)
values <- vector("numeric", ncol(df) - shift)
for (j in (shift+1):ncol(df)) {
  # process dates
  tmp <- toString(df[1,j])
  tmp <- unlist(strsplit(tmp, "/"))
  # convert "DD/MM/YY" to "YYYY-MM-DD"
  tmp <- paste(paste("20", tmp[[3]], sep=""), tmp[[2]], tmp[[1]], sep="-")
  dates[j-shift] <- as.character(tmp)
  # process isolation level values
  tmps <- as.numeric(unlist(as.list(df[2:nrow(df),j])))
  # mark non-empty cells, in some cells the value is missing
  good <- complete.cases(tmps)
  values[j-shift] <- mean(tmps[good])
}
dfl <- data.frame(date=as.Date(dates), level=values)
rm(fn, fp, good, j, shift, tmp, tmps, values)

Processing

The time step in days which the data points are grouped must be assigned to pandiso.time.step variable.

The sum of cases in a time step (period) are accumulated and the last date of the period is associated with the cumulative value. The same reasoning for isolation level, with the exception that the levels are averaged in the period.

The isolation level data frame dfl is used to calculate the number of steps pandiso.count.time.steps because there is a delay in the data update of COVID-19 cases and isolation levels. For these reason, the number of steps is based on the variable that has the chance to have less values.

# Time step in days to be used along the R code.
pandiso.time.step <- 5  # days

# Cases are accumulated along the time
pandiso.cases.are.accumulated <- TRUE
is_cumul <- pandiso.cases.are.accumulated
# Number of elements in the data frame
pandiso.count.time.steps <- floor(length(dfl[,1]) /  pandiso.time.step)
n <- pandiso.count.time.steps
cases_dt <- vector("numeric", n)
levels_dt <- vector("numeric", n)
dates_dt <- vector("character", n)
# Find the intersection of dates to avoid mismatch.
dates <- intersect(as.character(dfc[,"date"]), as.character(dfl[,"date"]))
for (i in 1:n) {
  # Time step [inf, sup]
  # time lower bound
  inf <- pandiso.time.step * (i-1) + 1
  # time upper bound
  sup <- pandiso.time.step * i
  
  # Date interval is marked by the upper bound
  dates_dt[i] <- dates[sup]
  # The levels are averaged
  levels_dt[i] <- mean(dfl[dfl$date >= dates[inf] & dfl$date <= dates[sup],]$level)
  
  if (is_cumul == TRUE) {
    inf <- 1
  }
  # The cases are summed since from the first case if IS.CUMULATIVE is TRUE
  cases_dt[i] <-  sum(dfc[dfc$date >= dates[inf] & dfc$date <= dates[sup],]$cases)
}
dfdt = data.frame(date=dates_dt, empirical=cases_dt, level=levels_dt)
# Add time steps to data frame
times <- seq(from=1, to=length(dfdt$level), by=1)
dfdt$time <- times
rm(times)
# Total number of dates, remembering the dates considered
# as marks are in steps of pandiso.time.step value.
pandiso.count.total.dates <- sup

# Clean up some variables not needed anymore
rm(cases_dt, dates, dates_dt, i, inf, is_cumul, levels_dt, n, sup)

Stage 1 - Fitting

The logistic function in the exponential form is used to fit the observed number of COVID-19 cases. The parameters from fitting are used in the estimation phase:

\[\begin{equation} \tag{1} f(t)= {K \over 1 + ({ K-m \over m }) e^{−rt}} \end{equation}\]

where:

\(t\) - a list of values representing the time steps;

\(m\) - the sigmoid’s midpoint;

\(K\) - the maximum value for f(t);

\(r\) - the the logistic growth rate.

library(ggplot2)
library(minpack.lm)
temp <- data.frame(y = dfdt$empirical, x = dfdt$time)
logistic.model <- nlsLM(y ~ (K/ (1 + ((K-m)/m)*exp(- r * x))), 
                        data = temp, 
                        start = list(K = 1, m=1, r = 0.1))
summary(logistic.model)

Formula: y ~ (K/(1 + ((K - m)/m) * exp(-r * x)))

Parameters:
        Estimate     Std. Error  t value   Pr(>|t|)
K 5.48279896e+06 1.30152417e+05 42.12599 < 2.22e-16
m 1.25672527e+05 8.60939742e+03 14.59713 < 2.22e-16
r 4.53826212e-02 1.18091155e-03 38.43016 < 2.22e-16

Residual standard error: 119530.153 on 114 degrees of freedom

Number of iterations to convergence: 39 
Achieved convergence tolerance: 1.49011612e-08
fit <- predict(logistic.model, list(x = temp$x))
dfdt$fit <- fit
rm(fit, temp)

# Plot
p <- ggplot(dfdt) + geom_point(aes(time, empirical)) +
     geom_line(aes(time, fit), size = 1, col = "gray50") +
     labs(title = paste("Fitting of empirical data using", pandiso.time.step, "days as time step"),
          x = "time", y= "accumulated cases") +
    scale_x_continuous(breaks=seq(0, max(dfdt$time), 20)) +
    theme_light()
p

pandiso.write.plot("fit", p)
[1] FALSE
pandiso.fit.plot <- p

# Test the correlation of the empirical data and the model
c <- cor.test(dfdt$empirical, dfdt$fit, method=c("pearson"))
c

    Pearson's product-moment correlation

data:  dfdt$empirical and dfdt$fit
t = 134.5868854, df = 115, p-value < 2.220446e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.995442351266 0.997810390759
sample estimates:
           cor 
0.996840630566 
pandiso.fit.pearson <- c$estimate
rm(c, p)

Stage 2 - Estimating

The estimation is performed after the calculation of \(\lambda\) using the equation 2:

\[\begin{equation} \tag{2} \lambda = r_{fit}\, \overline{i\,}, \end{equation}\]

where:

\(r_{fit}\) - the logistic growth rate obtained from fitting;

\(\overline{i\,}\) - the mean of all isolation level values.

The \(\lambda\) is used to estimate the number of cases \(N(t)\) using the logistic function in the exponential form, substituting the parameters by the fitted ones with the exception of the growth rate from the previous time step that now is considered as a dependent variable correlated with the isolation level of that time step and it’s calculated by

\[\begin{equation} \tag{3} r(t-1) = {\lambda\over i(t-1)}, \end{equation}\]

and the logistic function calculates the current number of cases based on the previous time step isolation level using:

\[\begin{equation} \tag{4} N(t)= {K \over 1 + ({ K-N_m \over N_m }) e^{−r(t-1)\, (t)}} \end{equation}\]

where:

\(K\) - maximum value reachable by \(N\);

\(N_m\) - number of cases in the curve’s midpoint;

\(r(t-1)\) - growth rate from the previous period;

\(t\) - time and \(t\in \{2..t_{final}\}\).

library(ggplot2)
library(patchwork)
library(reshape2)
# Estimation of cases using fitted parameters and 
# the mean of isolation level at each time step.
exp.logistic <- function(t, r_t, K, m) {
    y <- K / (1 + ((K-m)/m)*exp(-r_t * t))
    y
}

# Calculate the constant alpha
pandiso.fit.r <- coef(logistic.model)["r"]
pandiso.overall.level.mean <- mean(dfdt$level)
pandiso.overall.level.stdev <- sd(dfdt$level)
pandiso.lambda <- pandiso.fit.r * pandiso.overall.level.mean

pandiso.fit.K <- coef(logistic.model)["K"]
pandiso.fit.m <- coef(logistic.model)["m"]
# Estimate the number of cases N(t+dt)
estimated = vector("numeric", pandiso.count.time.steps)
# Estimate the first case manually, but not the rest.
estimated[1] = 1
for (i in 2:pandiso.count.time.steps) {
  r_t <- pandiso.lambda/dfdt$level[i]
  # This is t+dt compared with empirical time
  estimated[i] <- exp.logistic(i, r_t, pandiso.fit.K, pandiso.fit.m)
}
dfdt$estimated <- estimated
rm(estimated)

# Subplot in 2 rows, same x
## Subplot 1 - time x empirical+estimated
### Melt the desired data to plot
dfm <- melt(data.frame(time = dfdt$time, empirical = dfdt$empirical, 
                       estimated = dfdt$estimated), id = "time")
p1 <- ggplot(data = dfm, aes(x = time, y = value, color = variable)) + 
      geom_point(pch = 19, size = 1) +
      scale_color_manual(labels = unique(dfm$variable), values = c("black", "grey60")) + 
      labs(x = "", y = "accumulated cases", color = "", tag = "a)") +
      theme(axis.title.x = element_blank(), axis.text.x = element_blank(),
            legend.position = c(0.1, 0.675),  
            legend.background = element_rect(fill = "white", color = "black"),
            legend.title=element_blank()) +
      scale_x_continuous(breaks=seq(0, max(dfdt$time), 20)) +
      theme_light()

## Subplot 2 - time x mean isolation level
p2 <- ggplot(dfdt, aes(time, level)) +
      geom_point(pch = 19, size = 1.125,  col = "gray40") +
      labs(x = "time", y = "mean isolation level", tag = "b)") + 
      scale_x_continuous(breaks=seq(0, max(dfdt$time), 20)) +
      theme_light()
     
pandiso.estimation.plot <- (p1/p2)
pandiso.estimation.plot


pandiso.write.plot("estimation", pandiso.estimation.plot)
[1] FALSE
# Test the correlation of the empirical and estimated data.
c <- cor.test(dfdt$empirical, dfdt$estimated, method=c("pearson"))
c

    Pearson's product-moment correlation

data:  dfdt$empirical and dfdt$estimated
t = 113.7503146, df = 115, p-value < 2.220446e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.993633524552 0.996939943612
sample estimates:
           cor 
0.995585521284 
pandiso.estimation.pearson <- c$estimate

pandiso.date.first <- dfdt[1, "date"]
pandiso.date.last <- dfdt[length(dfdt$date), "date"]
rm(c, i, r_t)

# Finish writing the values of some values to TeX variables 
# to be used in the preprint.
pandiso.write.TeX_macros()
[1] FALSE
LS0tCnRpdGxlOiAiRXN0aW1hdGluZyB0aGUgbnVtYmVyIG9mIENPVklELTE5IGNhc2VzIGZyb20gcG9wdWxhdGlvbiBpc29sYXRpb24gbGV2ZWwiCmF1dGhvcjogIkFkcmlhbm8gSi4gSG9sYW5kYSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBQcmVmYWNlCgpBbGwgdmFyaWFibGVzIGFuZCBmdW5jdGlvbnMgaGF2ZSB0aGUgcHJlZml4IGBwYW5kaXNvYCBpbiB0aGUgbmFtZS4gCgojIFN5c3RlbQoKVGhlIHN5c3RlbSB2YXJpYWJsZXMgYW5kIGZ1bmN0aW9ucyBoYW5kbGUgdGhlIGN1cnJlbnQgZGlyZWN0b3J5IAp2YWx1ZSwgdGhlIG1hbnVzY3JpcHQgZGlyZWN0b3J5IGFuZCB0aGUgaW1hZ2UgZm9ybWF0LiBBIGZ1bmN0aW9uIAppcyBvZmZlcmVkIHRvIHdyaXRlIHRoZSBmaWxlcyBpbiB0aGUgcmlnaHQgbG9jYXRpb24gYW5kIHRoZSBwcmVwcmludApkaXJlY3RvcnkgKGltYWdlcyBhbmQgbWFjcm9zKSBpcyBtb2RpZmllZCBvbmx5IAppZiBgcGFuZGlzby51cGRhdGUucHJlcHJpbnRgIGlzIGFzc2lnbmVkIHRvIGBUUlVFYC4KCgpgYGB7cn0KbGlicmFyeSh0ZXN0aXQpICMgYXNzZXJ0CiMgQ29udHJvbCB2ZXJib3NpdHkKVkVSQk9TRSA9IEZBTFNFCiMgU1lTVEVNIHZhcmlhYmxlcyBhbmQgZnVuY3Rpb25zCiMjIERpcmVjdG9yeSB3aGVyZSB0aGUgbWFudXNjcmlwdCAocHJlcHJpbnQpIGlzIGxvY2F0ZWQKcGFuZGlzby5wcmVwcmludC5kaXIgPC0gInByZXByaW50IgoKIyMgRmlsZSBleHRlbnNpb24gZm9yIHBsb3RzCnBhbmRpc28ucGxvdC5leHQgPC0gIi5wbmciCgpwYW5kaXNvLmZpbGUucGF0aCA8LSBmdW5jdGlvbihmbikgewogICMgZ2V0d2QoKSByZXR1cm5zIHRoZSBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5LgogIGZwIDwtIGZpbGUucGF0aChnZXR3ZCgpLCBmbik7CiAgcmV0dXJuKGZwKQp9CgojIEFzc2lnbiBUUlVFIHRvIHVwZGF0ZSBmaWxlcyBpbiB0aGUgcHJlcHJpbnQgZGlyZWN0b3J5CnBhbmRpc28udXBkYXRlLnByZXByaW50IDwtIEZBTFNFCgpwYW5kaXNvLndyaXRlLnBsb3QgPC0gZnVuY3Rpb24oZmlsZV9wcmVmaXgsIHBsdCkgewogIGFzc2VydCgiMXN0IHBhcmFtZXRlciBpcyBmaWxlIHByZWZpeCBzdHJpbmciLCAKICAgICAgICAgdHlwZW9mKGZpbGVfcHJlZml4KSA9PSAiY2hhcmFjdGVyIikKICAKICBmbiA8LSBmaWxlLnBhdGgocGFuZGlzby5wcmVwcmludC5kaXIsIAogICAgICAgICAgICAgICAgICBwYXN0ZShmaWxlX3ByZWZpeCwgcGFuZGlzby5wbG90LmV4dCwgc2VwID0gIiIpKQogIGZwIDwtIHBhbmRpc28uZmlsZS5wYXRoKGZuKQogIAogIGlmIChwYW5kaXNvLnVwZGF0ZS5wcmVwcmludCA9PSBGQUxTRSkgewogICAgaWYgKFZFUkJPU0UpIHsKICAgICAgcHJpbnQocGFzdGUoIiogRGlkbid0IHdyaXRlIiwgZnApKQogICAgICBwcmludCgiKiBBc3NpZ24gJ1RSVUUnIHRvIHZhcmlhYmxlICdwYW5kaXNvLnVwZGF0ZS5wcmVwcmludC5wbG90cycgdG8gd3JpdGUgaXQuIikKICAgIAogICAgfQogICAgcmV0dXJuKHBhbmRpc28udXBkYXRlLnByZXByaW50KQogIH0KICAKICBnZ3NhdmUoZnApCiAgaWYgKFZFUkJPU0UpIHsKICAgIHByaW50KHBhc3RlKCIqIFdyb3RlIiwgZnApKQogIH0KICByZXR1cm4ocGFuZGlzby51cGRhdGUucHJlcHJpbnQpCn0KYGBgCgojIyBNYWNyb3MKClRoZSB2YWx1ZXMgdXNlZCBpbiB0aGUgbWFudXNjcmlwdCBhcmUgYXNzaWduZWQgdG8gJFxUZVgkIG1hY3JvcyBpbiBhIGZpbGUgCmluIHRoZSBgcHJlcHJpbnRgIGRpcmVjdG9yeS4gCgpgYGB7cn0KcGFuZGlzby53cml0ZS5UZVhfbWFjcm9zIDwtIGZ1bmN0aW9uKCkgewogIGlmIChwYW5kaXNvLnVwZGF0ZS5wcmVwcmludCA9PSBGQUxTRSkKICAgIHJldHVybihwYW5kaXNvLnVwZGF0ZS5wcmVwcmludCkKICAKICBmcCA8LSBmaWxlLnBhdGgoZ2V0d2QoKSwgcGFuZGlzby5wcmVwcmludC5kaXIsICJtYWNyb3MudGV4IikKICB0ZXggPC0gIiUgVGhpcyBmaWxlIGlzIGF1dG9tYXRpY2FsbHkgZ2VuZXJhdGVkIGJ5IFIuICVcbiIKICB0ZXggPC0gcGFzdGUodGV4LCAiXFx1c2VwYWNrYWdle3Rpa3p9XG4iLCBzZXAgPSAiIikKICB0ZXggPC0gcGFzdGUodGV4LCJcXGRlZlxccGFuZGlzb2ZpdEt7IiwgCiAgICAgICAgICAgICAgIGZvcm1hdChwYW5kaXNvLmZpdC5LLCBiaWcubWFyayA9ICIsIiwgZGlnaXRzID0gNywgbnNtYWxsID0gMCksIAogICAgICAgICAgICAgICAifSVcbiIsIHNlcCA9ICIiKQogIHRleCA8LSBwYXN0ZSh0ZXgsIlxcZGVmXFxwYW5kaXNvZml0bXsiLCAKICAgICAgICAgICAgICAgZm9ybWF0KHBhbmRpc28uZml0Lm0sIGJpZy5tYXJrID0gIiwiLCBkaWdpdHM9NiwgbnNtYWxsID0gMCksCiAgICAgICAgICAgICAgICJ9JSIsIHNlcCA9ICIiKQogIHRleCA8LSBwYXN0ZSh0ZXgsICJcblxccGdma2V5c3slXG4iLCBzZXAgPSAiIikKICB0ZXggPC0gcGFzdGUodGV4LCJcdC9wYW5kaXNvL2NvdW50L3RpbWUvc3RlcHMvLmluaXRpYWw9IiwgCiAgICAgICAgICAgICAgIGZvcm1hdChwYW5kaXNvLmNvdW50LnRpbWUuc3RlcHMsIGRpZ2l0cz0zLCBuc21hbGwgPSAwKSwKICAgICAgICAgICAgICAgIiwlXG4iLCBzZXAgPSAiIikKICB0ZXggPC0gcGFzdGUodGV4LCAiXHQvcGFuZGlzby9kYXRlL2ZpcnN0Ly5pbml0aWFsPSIsIHBhbmRpc28uZGF0ZS5maXJzdCwgIiwlXG4iLCBzZXAgPSAiIikKICB0ZXggPC0gcGFzdGUodGV4LCJcdC9wYW5kaXNvL2RhdGUvbGFzdC8uaW5pdGlhbD0iLCBwYW5kaXNvLmRhdGUubGFzdCwgIiwlXG4iLCBzZXAgPSAiIikKICB0ZXggPC0gcGFzdGUodGV4LCJcdC9wYW5kaXNvL2VzdGltYXRpb24vcGVhcnNvbi8uaW5pdGlhbD0iLCAKICAgICAgICAgICAgICAgZm9ybWF0KHBhbmRpc28uZXN0aW1hdGlvbi5wZWFyc29uLCBkaWdpdHMgPSAxLCBuc21hbGwgPSAzKSwgCiAgICAgICAgICAgICAgICIsJVxuIiwgc2VwID0gIiIpCiAgdGV4IDwtIHBhc3RlKHRleCwiXHQvcGFuZGlzby9maXQvcGVhcnNvbi8uaW5pdGlhbD0iLCAKICAgICAgICAgICAgICAgZm9ybWF0KHBhbmRpc28uZml0LnBlYXJzb24sIGRpZ2l0cyA9IDEsIG5zbWFsbCA9IDMpLCAKICAgICAgICAgICAgICAgIiwlXG4iLCBzZXAgPSAiIikKICB0ZXggPC0gcGFzdGUodGV4LCJcdC9wYW5kaXNvL2ZpdC9yLy5pbml0aWFsPSIsIAogICAgICAgICAgICAgICBmb3JtYXQocGFuZGlzby5maXQuciwgZGlnaXRzID0gMSwgbnNtYWxsID0gMyksIAogICAgICAgICAgICAgICAiLCVcbiIsIHNlcCA9ICIiKQogIHRleCA8LSBwYXN0ZSh0ZXgsIlx0L3BhbmRpc28vb3ZlcmFsbC9sZXZlbC9tZWFuLy5pbml0aWFsPSIsIAogICAgICAgICAgICAgICBmb3JtYXQocGFuZGlzby5vdmVyYWxsLmxldmVsLm1lYW4sIGRpZ2l0cyA9IDEsIG5zbWFsbCA9IDMpLCAKICAgICAgICAgICAgICAgIiwlXG4iLCBzZXAgPSAiIikKICB0ZXggPC0gcGFzdGUodGV4LCJcdC9wYW5kaXNvL292ZXJhbGwvbGV2ZWwvc3RkZXYvLmluaXRpYWw9IiwgCiAgICAgICAgICAgICAgIGZvcm1hdChwYW5kaXNvLm92ZXJhbGwubGV2ZWwuc3RkZXYsIGRpZ2l0cyA9IDEsIG5zbWFsbCA9IDMpLCAKICAgICAgICAgICAgICAgIiwlXG4iLCBzZXAgPSAiIikgIAogdGV4IDwtIHBhc3RlKHRleCwiXHQvcGFuZGlzby90aW1lL3N0ZXAvLmluaXRpYWw9IiwgCiAgICAgICAgICAgICAgIGZvcm1hdChwYW5kaXNvLnRpbWUuc3RlcCwgZGlnaXRzID0gMSwgbnNtYWxsID0gMCksIAogICAgICAgICAgICAgICAiLCVcbiIsIHNlcCA9ICIiKSAgCiAgdGV4IDwtIHBhc3RlKHRleCwgIn0lIiwgc2VwID0gIiIpCiAgd3JpdGVMaW5lcyh0ZXgsIGZwKQogIHJldHVybihwYW5kaXNvLnVwZGF0ZS5wcmVwcmludCkKfQpgYGAKCiMgSW50cm9kdWN0aW9uCgpUaGUgbG9naXN0aWMgZnVuY3Rpb24gaXMgdXNlZCB0byBlc3RpbWF0ZSB0aGUgbnVtYmVyIApvZiBpbmRpdmlkdWFscyBpbmZlY3RlZCBieSBhIHZpcnVzIGluIGEgdGltZSAkdCsxJAp1c2luZyB0aGUgYXZlcmFnZSBpc29sYXRpb24gbGV2ZWwgaW4gYSBwb3B1bGF0aW9uIGF0IGEgdGltZSAkdCQuIApFYWNoIHRpbWUgc3RlcCBpcyBjb21wb3NlZCBieSBhIGRhdGUgcmFuZ2UgaW4gZGF5cyAKd2hlcmUgdGhlIGlzb2xhdGlvbiBsZXZlbCBpcyBzdXBwb3NlZCB0byB0YWtlIGVmZmVjdCBvdmVyIHRoZSAKdmlydXMgc3ByZWFkLgoKIyBMb2dpc3RpYyBmdW5jdGlvbgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShsYXRleDJleHApCiMgU21vb3RoIHRoZSBsaW5lIHBsb3R0ZWQKcmVxdWlyZShzcGxpbmVzKQojIERpZmZlcmVudGlhbCBlcXVhdGlvbnMgc29sdmVyCmxpYnJhcnkoZGVTb2x2ZSkKZGYubG9naXN0aWMgPC0gZnVuY3Rpb24gKHQsIHksIHBhcm1zKSB7CiAgbGlzdChwYXJtc1siciJdICogeSAqICgxLXkvcGFybXNbIksiXSkpCn0KdF9maW5hbCA8LSAyOTUKdGltZXMgPC0gc2VxKGZyb209MSwgdG89dF9maW5hbCwgYnk9MSkKeV9pbml0aWFsIDwtIGMoeT0xKQpwYXJtcyA8LSBjKHI9MC4wNCwgSz0zNTApCm91dCA8LSBvZGUoZnVuYz1kZi5sb2dpc3RpYywgeT15X2luaXRpYWwsIAogICAgICAgICAgIHRpbWVzPXRpbWVzLCBwYXJtcz1wYXJtcykKIyBOb3JtYWxpemUgKHN0YW5kYXJkaXplKSB0aGUgZGF0YQoKZm9yIChpIGluIDE6MikgewogIG91dFssaV0gPC0gb3V0WyxpXSAvIG1heChvdXRbLGldKQp9CiMgQ3JlYXRlIHRoZSBkYXRhIGZyYW1lIGZyb20gbG9naXN0aWMgb3V0cHV0CmRmIDwtIGRhdGEuZnJhbWUodD1vdXRbLDFdLCB5PW91dFssMl0pCiMgUGxvdAp4eSA8LSBkYXRhLmZyYW1lKHByZWRpY3QoaW50ZXJwU3BsaW5lKGRmJHQsIGRmJHkpKSkKcCA8LSBnZ3Bsb3QoeHksIGFlcyh4LCB5KSkgKyBnZW9tX2xpbmUoKSArCiAgICAgbGFicyh0aXRsZSA9ICJMb2dpc3RpYyBmdW5jdGlvbiIsIAogICAgICAgICAgeCA9ICJ0IiwgeSA9ICJuXG4iKSArCiAgICBnZW9tX2xhYmVsKAogICAgICBhZXMobGFiZWw9Im1pZHBvaW50IiwgCiAgICAgICAgICB4PS41NywKICAgICAgICAgIHk9LjUsCiAgICAgICAgICBsYWJlbC5zaXplID0gMC4zNSksCiAgICAgIGNvbG9yID0gImJsYWNrIiwKICAgICAgbGFiZWwuc2l6ZSA9IE5BCiAgKSArCiAgYW5ub3RhdGUoInBvaW50IiwgeCA9IC41LCB5ID0gLjUsIGNvbG91ciA9ICJibGFjayIpICsKICB0aGVtZV9saWdodCgpCnAKCnBhbmRpc28ud3JpdGUucGxvdCgibG9naXN0aWMiLCBwKQpwYW5kaXNvLmxvZ2lzdGljLnBsb3QgPC0gcApybShkZiwgZGYubG9naXN0aWMsIGksIG91dCwgcCwgcGFybXMsIHRpbWVzLCB0X2ZpbmFsLCB4eSwgeV9pbml0aWFsKQpgYGAKIyBTdGFnZSAwIC0gRGF0YSBPcmdhbml6YXRpb24KClRoZSBkYXRhIGFyZSBwcm9jZXNzZWQgaW4gcGVyaW9kcyBvZiB0aW1lLCB3ZSB1c2UgZGF5cyBhcyAKdW5pdCBmb3IgdGltZSBhbmQgYXZvaWQgdGhlIHVzZSBvZiBmcmFjdGlvbmFsIHZhbHVlcyB0byAKc2ltcGxpZnkgdGhlIGNvZGUgYW5kIGNhbGN1bGF0aW9ucy4gQW5vdGhlciBqdXN0aWZpY2F0aW9uIApmb3IgdGhlIGRpc2NyZXRlIHZhbHVlcyBmb3IgdGltZSBpcyB0aGF0IHdlIGFyZSBoYW5kbGluZyAKYXBwcm94aW1hdGlvbnMgZm9yIHRoZSBjb25kaXRpb25zIHN0dWRpZWQuCgpUaGUgdmFsdWUgb2YgYHBhbmRpc28udGltZS5zdGVwYCAoc2VlIFtQcm9jZXNzaW5nXSkKd2FzIGFkb3B0ZWQgYWZ0ZXIgYSBzZXQgb2YgdGVzdHMgCndlcmUgcGVyZm9ybWVkIHRvIGNoZWNrIHdoaWNoIHZhbHVlIGNhdXNlcyAKIGEgYmV0dGVyIGNvcnJlbGF0aW9uIGluIHRoZSBjdXJ2ZXMgCiBmb3IgdGhlIG1vZGVsIGFuZCBlc3RpbWF0ZWQgZGF0YS4gCiBUaGlzIGhpZ2ggY29ycmVsYXRpb24gbWVhbnMgdGhhdCAKIHRoZSBpc29sYXRpb24gbGV2ZWwgaW4gdGhhdCB0aW1lIHN0ZXAgCiBhZmZlY3RzIHRoZSBudW1iZXIgb2YgY2FzZXMgaW4gdGhlIAogbmV4dCB0aW1lIHN0ZXAuCgoKIyMgQ09WSUQtMTkgY2FzZXMKClRoZSBDT1ZJRC0xOSBjYXNlcyBhcmUgZ2F0aGVyZWQgZnJvbSAKWyJTw6NvIFBhdWxvIFBsYW4iXShodHRwczovL3d3dy5zYW9wYXVsby5zcC5nb3YuYnIvcGxhbm9zcC8pIAogb3JnYW5pemVkIGJ5IHRoZSBnb3Zlcm5tZW50IG9mIFPDo28gUGF1bG8gU3RhdGUuIEJyYXppbCAKIHRvIGNvbmNlbnRyYXRlIGVmZm9ydHMgdG8gaGFuZGxlIHRoZSB2aXJ1cyBzcHJlYWQgaW4gdGhlIAogc3RhdGUuIFRoZSBmaWxlIGNvbnRhaW5pbmcgdGhlIGNhc2VzLCBhbmQgb3RoZXIgZGF0YSwgCiBpcyBkb3dubG9hZGVkIGZyb20gCiBbR2l0aHViIHJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9zZWFkZS1SL2RhZG9zLWNvdmlkLXNwKQogZm9yIHRoZSBpbml0aWF0aXZlLgoKYGBge3J9CmZuIDwtICJkYWRvc19jb3ZpZF9zcC5jc3YiCiMgRG93bmxvYWQgdGhlIGRhdGEgZnJvbSBvcmlnaW5hbCBzb3VyY2UKcGFuZGlzby5jYXNlcy5maWxlLnBhdGggPC0gcGFuZGlzby5maWxlLnBhdGgoZm4pOwpmcCA8LSBwYW5kaXNvLmNhc2VzLmZpbGUucGF0aAppZiAoZmlsZS5leGlzdHMoZnApID09IEZBTFNFKSB7CiAgZG93bmxvYWQuZmlsZShwYXN0ZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NlYWRlLVIvZGFkb3MtY292aWQtc3AvbWFzdGVyL2RhdGEvIiwgZm4sIHNlcCA9ICIiKSwgZnApCn0KIyBSZWFkIENTViBmaWxlIG9yZ2FuaXppbmcgdGhlIGRhdGEgYWNjb3JkaW5nIHRvIGNvbHVtbnMKZGYgPC0gcmVhZC5jc3YoZnAsIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7Iik7CiMgU3VtIHRoZSBjYXNlcyBmb3IgYWxsIGNpdGllcyBieSBkYXRlCmRmYyA8LSBhZ2dyZWdhdGUoY2Fzb3Nfbm92b3N+ZGF0YWhvcmEsIEZVTj1zdW0sIGRhdGE9ZGYpCiMgUmVuYW1lIHRoZSBkYXRhIGZyYW1lIGNvbHVtbnMKbmFtZXMoZGZjKVtuYW1lcyhkZmMpID09ICJkYXRhaG9yYSJdIDwtICJkYXRlIgpuYW1lcyhkZmMpW25hbWVzKGRmYykgPT0gImNhc29zX25vdm9zIl0gPC0gImNhc2VzIgojIENvbnZlcnQgZGF0ZSBzdHJpbmcgdG8gZGF0ZSBDbGFzcwpkZmMkZGF0ZSA8LSBhcy5EYXRlKGRmYyRkYXRlKQpybShmcCwgZm4pCmBgYAoKIyMgUG9wdWxhdGlvbiBpc29sYXRpb24gbGV2ZWwKClRoZSBpc29sYXRpb24gbGV2ZWwgZm9yIGFsbCBjaXRpZXMgaW4gdGhlIFPDo28gUGF1bG8gU3RhdGUgYXJlIApzdG9yZWQgaW4gYW4gRXhjZWwgZmlsZSBkb3dubG9hZGVkIGZyb20gCltgYFPDo28gUGF1bG8gR292ZXJubWVudCBJc29sYXRpb24gTW9uaXRvcmluZycnIHNpdGVdKGh0dHBzOi8vd3d3LnNhb3BhdWxvLnNwLmdvdi5ici9jb3JvbmF2aXJ1cy9pc29sYW1lbnRvLykuCgpUaGUgZmlsZSBtdXN0IGJlIGRvd25sb2FkZWQgbWFudWFsbHkgYWZ0ZXIgY2xpY2tpbmcgaW4gdGhlICJEQURPUyIgYnV0dG9uLCAKdGhlbiBjbGljayAiQmFpeGFyIiBpY29uLCBjaG9vc2UgIlRhYmVsYSBkZSByZWZlcsOqbmNpYSBjcnV6YWRhIiwgIkV4Y2VsIiBmb3JtYXQKICBhbmQgY2xpY2sgIkJhaXhhciIgYnV0dG9uLiAKIFdlIGNob29zZSB0aGUgRXhjZWwgZm9ybWF0IGJlY2F1c2UgaXRzIGNvbnRlbnQgY291bGQgYmUgcGFyc2VkIHdpdGggCiByZWxpYWJpbGl0eS4gVGhlIGlzb2xhdGlvbiBkYXRlcyBhcmUgc3luY2hyb25pemVkIGNhc2VzIGRhdGEsIG9ubHkgdGhlIAogZm9ybWF0IGlzIGRpZmZlcmVudCAoIkREL01NL1lZIiksIGJ1dCB0aGlzIGlzIGZpeGVkIGJ5IGNoYW5naW5nIGl0IHRvCiAiWVlZWS1NTS1ERCIuCgpgYGB7cn0KbGlicmFyeShyZWFkeGwpCmZuIDwtICJEYWRvcy54bHN4IgpwYW5kaXNvLmlzb2xhdGlvbi5maWxlLnBhdGggPC0gcGFuZGlzby5maWxlLnBhdGgoZm4pCmZwIDwtIHBhbmRpc28uaXNvbGF0aW9uLmZpbGUucGF0aApkZiA8LSByZWFkX2V4Y2VsKGZwKQojIFRoZSBpc29sYXRpb24gbGV2ZWwgZGF0YSBzdGFydCBhdCA1dGggY29sdW1uIGFuZCB0aGUKIyBmaXJzdCByb3cgaXMgdGhlIGRhdGUsIHRoZSByZXN0IGFyZSBpc29sYXRpb24gbGV2ZWwgCiMgdmFsdWVzLgpzaGlmdCA8LSA0ICMgNXRoIGNvbHVtbiBiZWNhdXNlIGluZGljZXMgaW4gUiBzdGFydCBhdCAxCmRhdGVzIDwtIHZlY3RvcigiY2hhcmFjdGVyIiwgbmNvbChkZikgLSBzaGlmdCkKdmFsdWVzIDwtIHZlY3RvcigibnVtZXJpYyIsIG5jb2woZGYpIC0gc2hpZnQpCmZvciAoaiBpbiAoc2hpZnQrMSk6bmNvbChkZikpIHsKICAjIHByb2Nlc3MgZGF0ZXMKICB0bXAgPC0gdG9TdHJpbmcoZGZbMSxqXSkKICB0bXAgPC0gdW5saXN0KHN0cnNwbGl0KHRtcCwgIi8iKSkKICAjIGNvbnZlcnQgIkREL01NL1lZIiB0byAiWVlZWS1NTS1ERCIKICB0bXAgPC0gcGFzdGUocGFzdGUoIjIwIiwgdG1wW1szXV0sIHNlcD0iIiksIHRtcFtbMl1dLCB0bXBbWzFdXSwgc2VwPSItIikKICBkYXRlc1tqLXNoaWZ0XSA8LSBhcy5jaGFyYWN0ZXIodG1wKQogICMgcHJvY2VzcyBpc29sYXRpb24gbGV2ZWwgdmFsdWVzCiAgdG1wcyA8LSBhcy5udW1lcmljKHVubGlzdChhcy5saXN0KGRmWzI6bnJvdyhkZiksal0pKSkKICAjIG1hcmsgbm9uLWVtcHR5IGNlbGxzLCBpbiBzb21lIGNlbGxzIHRoZSB2YWx1ZSBpcyBtaXNzaW5nCiAgZ29vZCA8LSBjb21wbGV0ZS5jYXNlcyh0bXBzKQogIHZhbHVlc1tqLXNoaWZ0XSA8LSBtZWFuKHRtcHNbZ29vZF0pCn0KZGZsIDwtIGRhdGEuZnJhbWUoZGF0ZT1hcy5EYXRlKGRhdGVzKSwgbGV2ZWw9dmFsdWVzKQpybShmbiwgZnAsIGdvb2QsIGosIHNoaWZ0LCB0bXAsIHRtcHMsIHZhbHVlcykKYGBgCgojIyBQcm9jZXNzaW5nIHsjcHJvY2Vzc2luZ30KClRoZSB0aW1lIHN0ZXAgaW4gZGF5cyB3aGljaCB0aGUgZGF0YSBwb2ludHMgYXJlIGdyb3VwZWQgCm11c3QgYmUgYXNzaWduZWQgdG8gYHBhbmRpc28udGltZS5zdGVwYCB2YXJpYWJsZS4KClRoZSBzdW0gb2YgY2FzZXMgaW4gYSB0aW1lIHN0ZXAgKHBlcmlvZCkgYXJlIGFjY3VtdWxhdGVkIAphbmQgdGhlIGxhc3QgZGF0ZSBvZiB0aGUgcGVyaW9kIGlzIGFzc29jaWF0ZWQgd2l0aCB0aGUKY3VtdWxhdGl2ZSB2YWx1ZS4gVGhlIHNhbWUgcmVhc29uaW5nIGZvciBpc29sYXRpb24gbGV2ZWwsIAp3aXRoIHRoZSBleGNlcHRpb24gdGhhdCB0aGUgbGV2ZWxzIGFyZSBhdmVyYWdlZCBpbiB0aGUgcGVyaW9kLgoKVGhlIGlzb2xhdGlvbiBsZXZlbCBkYXRhIGZyYW1lIGBkZmxgIGlzIHVzZWQgdG8gY2FsY3VsYXRlCnRoZSBudW1iZXIgb2Ygc3RlcHMgYHBhbmRpc28uY291bnQudGltZS5zdGVwc2AgCmJlY2F1c2UgdGhlcmUgaXMgYSBkZWxheSBpbiB0aGUgZGF0YSB1cGRhdGUgb2YgQ09WSUQtMTkgY2FzZXMgCmFuZCBpc29sYXRpb24gbGV2ZWxzLiBGb3IgdGhlc2UgcmVhc29uLCB0aGUgbnVtYmVyIG9mIHN0ZXBzIGlzIGJhc2VkIApvbiB0aGUgdmFyaWFibGUgdGhhdCBoYXMgdGhlIGNoYW5jZSB0byBoYXZlIGxlc3MgdmFsdWVzLgoKYGBge3J9CiMgVGltZSBzdGVwIGluIGRheXMgdG8gYmUgdXNlZCBhbG9uZyB0aGUgUiBjb2RlLgpwYW5kaXNvLnRpbWUuc3RlcCA8LSA1ICAjIGRheXMKCiMgQ2FzZXMgYXJlIGFjY3VtdWxhdGVkIGFsb25nIHRoZSB0aW1lCnBhbmRpc28uY2FzZXMuYXJlLmFjY3VtdWxhdGVkIDwtIFRSVUUKaXNfY3VtdWwgPC0gcGFuZGlzby5jYXNlcy5hcmUuYWNjdW11bGF0ZWQKIyBOdW1iZXIgb2YgZWxlbWVudHMgaW4gdGhlIGRhdGEgZnJhbWUKcGFuZGlzby5jb3VudC50aW1lLnN0ZXBzIDwtIGZsb29yKGxlbmd0aChkZmxbLDFdKSAvICBwYW5kaXNvLnRpbWUuc3RlcCkKbiA8LSBwYW5kaXNvLmNvdW50LnRpbWUuc3RlcHMKY2FzZXNfZHQgPC0gdmVjdG9yKCJudW1lcmljIiwgbikKbGV2ZWxzX2R0IDwtIHZlY3RvcigibnVtZXJpYyIsIG4pCmRhdGVzX2R0IDwtIHZlY3RvcigiY2hhcmFjdGVyIiwgbikKIyBGaW5kIHRoZSBpbnRlcnNlY3Rpb24gb2YgZGF0ZXMgdG8gYXZvaWQgbWlzbWF0Y2guCmRhdGVzIDwtIGludGVyc2VjdChhcy5jaGFyYWN0ZXIoZGZjWywiZGF0ZSJdKSwgYXMuY2hhcmFjdGVyKGRmbFssImRhdGUiXSkpCmZvciAoaSBpbiAxOm4pIHsKICAjIFRpbWUgc3RlcCBbaW5mLCBzdXBdCiAgIyB0aW1lIGxvd2VyIGJvdW5kCiAgaW5mIDwtIHBhbmRpc28udGltZS5zdGVwICogKGktMSkgKyAxCiAgIyB0aW1lIHVwcGVyIGJvdW5kCiAgc3VwIDwtIHBhbmRpc28udGltZS5zdGVwICogaQogIAogICMgRGF0ZSBpbnRlcnZhbCBpcyBtYXJrZWQgYnkgdGhlIHVwcGVyIGJvdW5kCiAgZGF0ZXNfZHRbaV0gPC0gZGF0ZXNbc3VwXQogICMgVGhlIGxldmVscyBhcmUgYXZlcmFnZWQKICBsZXZlbHNfZHRbaV0gPC0gbWVhbihkZmxbZGZsJGRhdGUgPj0gZGF0ZXNbaW5mXSAmIGRmbCRkYXRlIDw9IGRhdGVzW3N1cF0sXSRsZXZlbCkKICAKICBpZiAoaXNfY3VtdWwgPT0gVFJVRSkgewogICAgaW5mIDwtIDEKICB9CiAgIyBUaGUgY2FzZXMgYXJlIHN1bW1lZCBzaW5jZSBmcm9tIHRoZSBmaXJzdCBjYXNlIGlmIElTLkNVTVVMQVRJVkUgaXMgVFJVRQogIGNhc2VzX2R0W2ldIDwtICBzdW0oZGZjW2RmYyRkYXRlID49IGRhdGVzW2luZl0gJiBkZmMkZGF0ZSA8PSBkYXRlc1tzdXBdLF0kY2FzZXMpCn0KZGZkdCA9IGRhdGEuZnJhbWUoZGF0ZT1kYXRlc19kdCwgZW1waXJpY2FsPWNhc2VzX2R0LCBsZXZlbD1sZXZlbHNfZHQpCiMgQWRkIHRpbWUgc3RlcHMgdG8gZGF0YSBmcmFtZQp0aW1lcyA8LSBzZXEoZnJvbT0xLCB0bz1sZW5ndGgoZGZkdCRsZXZlbCksIGJ5PTEpCmRmZHQkdGltZSA8LSB0aW1lcwpybSh0aW1lcykKIyBUb3RhbCBudW1iZXIgb2YgZGF0ZXMsIHJlbWVtYmVyaW5nIHRoZSBkYXRlcyBjb25zaWRlcmVkCiMgYXMgbWFya3MgYXJlIGluIHN0ZXBzIG9mIHBhbmRpc28udGltZS5zdGVwIHZhbHVlLgpwYW5kaXNvLmNvdW50LnRvdGFsLmRhdGVzIDwtIHN1cAoKIyBDbGVhbiB1cCBzb21lIHZhcmlhYmxlcyBub3QgbmVlZGVkIGFueW1vcmUKcm0oY2FzZXNfZHQsIGRhdGVzLCBkYXRlc19kdCwgaSwgaW5mLCBpc19jdW11bCwgbGV2ZWxzX2R0LCBuLCBzdXApCmBgYAoKIyBTdGFnZSAxIC0gRml0dGluZwoKVGhlIGxvZ2lzdGljIGZ1bmN0aW9uIGluIHRoZSBleHBvbmVudGlhbCBmb3JtIGlzIHVzZWQgdG8gZml0IHRoZSAKb2JzZXJ2ZWQgbnVtYmVyIG9mIENPVklELTE5IGNhc2VzLiBUaGUgcGFyYW1ldGVycyBmcm9tIGZpdHRpbmcgCmFyZSB1c2VkIGluIHRoZSBlc3RpbWF0aW9uIHBoYXNlOgoKXGJlZ2lue2VxdWF0aW9ufQpcdGFnezF9CmYodCk9IHtLIFxvdmVyIDEgKyAoeyBLLW0gXG92ZXIgbSB9KSBlXnviiJJydH19ClxlbmR7ZXF1YXRpb259CgoKXG5vaW5kZW50IHdoZXJlOgoKJHQkIC0gYSBsaXN0IG9mIHZhbHVlcyByZXByZXNlbnRpbmcgdGhlIHRpbWUgc3RlcHM7CgokbSQgLSB0aGUgc2lnbW9pZCdzIG1pZHBvaW50OwoKJEskIC0gdGhlIG1heGltdW0gdmFsdWUgZm9yIGYodCk7CgokciQgLSB0aGUgdGhlIGxvZ2lzdGljIGdyb3d0aCByYXRlLgoKCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobWlucGFjay5sbSkKdGVtcCA8LSBkYXRhLmZyYW1lKHkgPSBkZmR0JGVtcGlyaWNhbCwgeCA9IGRmZHQkdGltZSkKbG9naXN0aWMubW9kZWwgPC0gbmxzTE0oeSB+IChLLyAoMSArICgoSy1tKS9tKSpleHAoLSByICogeCkpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0ZW1wLCAKICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnQgPSBsaXN0KEsgPSAxLCBtPTEsIHIgPSAwLjEpKQpzdW1tYXJ5KGxvZ2lzdGljLm1vZGVsKQpmaXQgPC0gcHJlZGljdChsb2dpc3RpYy5tb2RlbCwgbGlzdCh4ID0gdGVtcCR4KSkKZGZkdCRmaXQgPC0gZml0CnJtKGZpdCwgdGVtcCkKCiMgUGxvdApwIDwtIGdncGxvdChkZmR0KSArIGdlb21fcG9pbnQoYWVzKHRpbWUsIGVtcGlyaWNhbCkpICsKICAgICBnZW9tX2xpbmUoYWVzKHRpbWUsIGZpdCksIHNpemUgPSAxLCBjb2wgPSAiZ3JheTUwIikgKwogICAgIGxhYnModGl0bGUgPSBwYXN0ZSgiRml0dGluZyBvZiBlbXBpcmljYWwgZGF0YSB1c2luZyIsIHBhbmRpc28udGltZS5zdGVwLCAiZGF5cyBhcyB0aW1lIHN0ZXAiKSwKICAgICAgICAgIHggPSAidGltZSIsIHk9ICJhY2N1bXVsYXRlZCBjYXNlcyIpICsKICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDAsIG1heChkZmR0JHRpbWUpLCAyMCkpICsKICAgIHRoZW1lX2xpZ2h0KCkKcApwYW5kaXNvLndyaXRlLnBsb3QoImZpdCIsIHApCnBhbmRpc28uZml0LnBsb3QgPC0gcAoKIyBUZXN0IHRoZSBjb3JyZWxhdGlvbiBvZiB0aGUgZW1waXJpY2FsIGRhdGEgYW5kIHRoZSBtb2RlbApjIDwtIGNvci50ZXN0KGRmZHQkZW1waXJpY2FsLCBkZmR0JGZpdCwgbWV0aG9kPWMoInBlYXJzb24iKSkKYwpwYW5kaXNvLmZpdC5wZWFyc29uIDwtIGMkZXN0aW1hdGUKcm0oYywgcCkKYGBgCiMgU3RhZ2UgMiAtIEVzdGltYXRpbmcKClRoZSBlc3RpbWF0aW9uIGlzIHBlcmZvcm1lZCBhZnRlciB0aGUgY2FsY3VsYXRpb24gb2YgJFxsYW1iZGEkIHVzaW5nIHRoZSBlcXVhdGlvbiAyOgoKXGJlZ2lue2VxdWF0aW9ufQpcdGFnezJ9CiBcbGFtYmRhID0gcl97Zml0fVwsIFxvdmVybGluZXtpXCx9LAogXGVuZHtlcXVhdGlvbn0KIApcbm9pbmRlbnQgd2hlcmU6IAoKJHJfe2ZpdH0kIC0gdGhlIGxvZ2lzdGljIGdyb3d0aCByYXRlIG9idGFpbmVkIGZyb20gCmZpdHRpbmc7CgokXG92ZXJsaW5le2lcLH0kIC0gdGhlIG1lYW4gb2YgYWxsIGlzb2xhdGlvbiAKbGV2ZWwgdmFsdWVzLgoKVGhlICRcbGFtYmRhJCBpcyB1c2VkIHRvIGVzdGltYXRlIHRoZSBudW1iZXIgb2YgY2FzZXMgCiROKHQpJCB1c2luZyB0aGUgbG9naXN0aWMgZnVuY3Rpb24gaW4gdGhlIGV4cG9uZW50aWFsIGZvcm0sIApzdWJzdGl0dXRpbmcgdGhlIHBhcmFtZXRlcnMgYnkgdGhlIGZpdHRlZCBvbmVzIHdpdGggdGhlIGV4Y2VwdGlvbiAKb2YgdGhlIGdyb3d0aCByYXRlIGZyb20gdGhlIHByZXZpb3VzIHRpbWUgc3RlcCB0aGF0IG5vdyAKaXMgY29uc2lkZXJlZCBhcyBhIGRlcGVuZGVudCB2YXJpYWJsZSBjb3JyZWxhdGVkIHdpdGggdGhlIAppc29sYXRpb24gbGV2ZWwgb2YgdGhhdCB0aW1lIHN0ZXAgYW5kIGl0J3MgY2FsY3VsYXRlZCBieQoKXGJlZ2lue2VxdWF0aW9ufQpcdGFnezN9CnIodC0xKSA9IHtcbGFtYmRhXG92ZXIgaSh0LTEpfSwKXGVuZHtlcXVhdGlvbn0KCmFuZCB0aGUgbG9naXN0aWMgZnVuY3Rpb24gY2FsY3VsYXRlcyB0aGUgY3VycmVudCBudW1iZXIgb2YgY2FzZXMgYmFzZWQgCm9uIHRoZSBwcmV2aW91cyB0aW1lIHN0ZXAgaXNvbGF0aW9uIGxldmVsIHVzaW5nOgoKXGJlZ2lue2VxdWF0aW9ufQpcdGFnezR9Ck4odCk9IHtLIFxvdmVyIDEgKyAoeyBLLU5fbSBcb3ZlciBOX20gfSkgZV574oiScih0LTEpXCwgKHQpfX0KXGVuZHtlcXVhdGlvbn0KClxub2luZGVudCB3aGVyZToKCiRLJCAtIG1heGltdW0gdmFsdWUgcmVhY2hhYmxlIGJ5ICROJDsKCiROX20kIC0gbnVtYmVyIG9mIGNhc2VzIGluIHRoZSBjdXJ2ZSdzIG1pZHBvaW50OwoKJHIodC0xKSQgLSBncm93dGggcmF0ZSBmcm9tIHRoZSBwcmV2aW91cyBwZXJpb2Q7CgokdCQgLSB0aW1lIGFuZCAkdFxpbiBcezIuLnRfe2ZpbmFsfVx9JC4KCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShyZXNoYXBlMikKIyBFc3RpbWF0aW9uIG9mIGNhc2VzIHVzaW5nIGZpdHRlZCBwYXJhbWV0ZXJzIGFuZCAKIyB0aGUgbWVhbiBvZiBpc29sYXRpb24gbGV2ZWwgYXQgZWFjaCB0aW1lIHN0ZXAuCmV4cC5sb2dpc3RpYyA8LSBmdW5jdGlvbih0LCByX3QsIEssIG0pIHsKICAgIHkgPC0gSyAvICgxICsgKChLLW0pL20pKmV4cCgtcl90ICogdCkpCiAgICB5Cn0KCiMgQ2FsY3VsYXRlIHRoZSBjb25zdGFudCBhbHBoYQpwYW5kaXNvLmZpdC5yIDwtIGNvZWYobG9naXN0aWMubW9kZWwpWyJyIl0KcGFuZGlzby5vdmVyYWxsLmxldmVsLm1lYW4gPC0gbWVhbihkZmR0JGxldmVsKQpwYW5kaXNvLm92ZXJhbGwubGV2ZWwuc3RkZXYgPC0gc2QoZGZkdCRsZXZlbCkKcGFuZGlzby5sYW1iZGEgPC0gcGFuZGlzby5maXQuciAqIHBhbmRpc28ub3ZlcmFsbC5sZXZlbC5tZWFuCgpwYW5kaXNvLmZpdC5LIDwtIGNvZWYobG9naXN0aWMubW9kZWwpWyJLIl0KcGFuZGlzby5maXQubSA8LSBjb2VmKGxvZ2lzdGljLm1vZGVsKVsibSJdCiMgRXN0aW1hdGUgdGhlIG51bWJlciBvZiBjYXNlcyBOKHQrZHQpCmVzdGltYXRlZCA9IHZlY3RvcigibnVtZXJpYyIsIHBhbmRpc28uY291bnQudGltZS5zdGVwcykKIyBFc3RpbWF0ZSB0aGUgZmlyc3QgY2FzZSBtYW51YWxseSwgYnV0IG5vdCB0aGUgcmVzdC4KZXN0aW1hdGVkWzFdID0gMQpmb3IgKGkgaW4gMjpwYW5kaXNvLmNvdW50LnRpbWUuc3RlcHMpIHsKICByX3QgPC0gcGFuZGlzby5sYW1iZGEvZGZkdCRsZXZlbFtpXQogICMgVGhpcyBpcyB0K2R0IGNvbXBhcmVkIHdpdGggZW1waXJpY2FsIHRpbWUKICBlc3RpbWF0ZWRbaV0gPC0gZXhwLmxvZ2lzdGljKGksIHJfdCwgcGFuZGlzby5maXQuSywgcGFuZGlzby5maXQubSkKfQpkZmR0JGVzdGltYXRlZCA8LSBlc3RpbWF0ZWQKcm0oZXN0aW1hdGVkKQoKIyBTdWJwbG90IGluIDIgcm93cywgc2FtZSB4CiMjIFN1YnBsb3QgMSAtIHRpbWUgeCBlbXBpcmljYWwrZXN0aW1hdGVkCiMjIyBNZWx0IHRoZSBkZXNpcmVkIGRhdGEgdG8gcGxvdApkZm0gPC0gbWVsdChkYXRhLmZyYW1lKHRpbWUgPSBkZmR0JHRpbWUsIGVtcGlyaWNhbCA9IGRmZHQkZW1waXJpY2FsLCAKICAgICAgICAgICAgICAgICAgICAgICBlc3RpbWF0ZWQgPSBkZmR0JGVzdGltYXRlZCksIGlkID0gInRpbWUiKQpwMSA8LSBnZ3Bsb3QoZGF0YSA9IGRmbSwgYWVzKHggPSB0aW1lLCB5ID0gdmFsdWUsIGNvbG9yID0gdmFyaWFibGUpKSArIAogICAgICBnZW9tX3BvaW50KHBjaCA9IDE5LCBzaXplID0gMSkgKwogICAgICBzY2FsZV9jb2xvcl9tYW51YWwobGFiZWxzID0gdW5pcXVlKGRmbSR2YXJpYWJsZSksIHZhbHVlcyA9IGMoImJsYWNrIiwgImdyZXk2MCIpKSArIAogICAgICBsYWJzKHggPSAiIiwgeSA9ICJhY2N1bXVsYXRlZCBjYXNlcyIsIGNvbG9yID0gIiIsIHRhZyA9ICJhKSIpICsKICAgICAgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gYygwLjEsIDAuNjc1KSwgIAogICAgICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSkgKwogICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCBtYXgoZGZkdCR0aW1lKSwgMjApKSArCiAgICAgIHRoZW1lX2xpZ2h0KCkKCiMjIFN1YnBsb3QgMiAtIHRpbWUgeCBtZWFuIGlzb2xhdGlvbiBsZXZlbApwMiA8LSBnZ3Bsb3QoZGZkdCwgYWVzKHRpbWUsIGxldmVsKSkgKwogICAgICBnZW9tX3BvaW50KHBjaCA9IDE5LCBzaXplID0gMS4xMjUsICBjb2wgPSAiZ3JheTQwIikgKwogICAgICBsYWJzKHggPSAidGltZSIsIHkgPSAibWVhbiBpc29sYXRpb24gbGV2ZWwiLCB0YWcgPSAiYikiKSArIAogICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCBtYXgoZGZkdCR0aW1lKSwgMjApKSArCiAgICAgIHRoZW1lX2xpZ2h0KCkKICAgICAKcGFuZGlzby5lc3RpbWF0aW9uLnBsb3QgPC0gKHAxL3AyKQpwYW5kaXNvLmVzdGltYXRpb24ucGxvdAoKcGFuZGlzby53cml0ZS5wbG90KCJlc3RpbWF0aW9uIiwgcGFuZGlzby5lc3RpbWF0aW9uLnBsb3QpCgojIFRlc3QgdGhlIGNvcnJlbGF0aW9uIG9mIHRoZSBlbXBpcmljYWwgYW5kIGVzdGltYXRlZCBkYXRhLgpjIDwtIGNvci50ZXN0KGRmZHQkZW1waXJpY2FsLCBkZmR0JGVzdGltYXRlZCwgbWV0aG9kPWMoInBlYXJzb24iKSkKYwpwYW5kaXNvLmVzdGltYXRpb24ucGVhcnNvbiA8LSBjJGVzdGltYXRlCgpwYW5kaXNvLmRhdGUuZmlyc3QgPC0gZGZkdFsxLCAiZGF0ZSJdCnBhbmRpc28uZGF0ZS5sYXN0IDwtIGRmZHRbbGVuZ3RoKGRmZHQkZGF0ZSksICJkYXRlIl0Kcm0oYywgaSwgcl90KQoKIyBGaW5pc2ggd3JpdGluZyB0aGUgdmFsdWVzIG9mIHNvbWUgdmFsdWVzIHRvIFRlWCB2YXJpYWJsZXMgCiMgdG8gYmUgdXNlZCBpbiB0aGUgcHJlcHJpbnQuCnBhbmRpc28ud3JpdGUuVGVYX21hY3JvcygpCmBgYAo=
{% endraw %}