Survival Analysis

In this lab, we perform survival analyses on three separate data sets. In Section 11.8.1 we analyze the BrainCancer data that was first described in Section 11.3. In Section 11.8.2, we examine the Publication data from Section 11.5.4. Finally, Section 11.8.3 explores a simulated call center data set.

Brain Cancer Data

We begin with the BrainCancer data set, which is part of the ISLR2 package.

library(ISLR2)

The rows index the 88 patients, while the columns contain the 8 predictors.

names(BrainCancer)
[1] "sex"       "diagnosis" "loc"       "ki"        "gtv"       "stereo"   
[7] "status"    "time"     

We first briefly examine the data.

attach(BrainCancer)
table(sex)
sex
Female   Male 
    45     43 
table(diagnosis)
diagnosis
Meningioma  LG glioma  HG glioma      Other 
        42          9         22         14 
table(status)
status
 0  1 
53 35 

Before beginning an analysis, it is important to know how the status variable has been coded. Most software, including R, uses the convention that status = 1 indicates an uncensored observation, and status = 0 indicates a censored observation. But some scientists might use the opposite coding. For the BrainCancer data set 35 patients died before the end of the study.

To begin the analysis, we re-create the Kaplan-Meier survival curve shown in Figure 11.2, using the survfit() function within the R survival library. Here time corresponds to \(y_i\), the time to the \(i\)th event (either censoring or death).

library(survival)
package 㤼㸱survival㤼㸲 was built under R version 3.6.3
fit_surv<-survfit(Surv(time,status)~1)
plot(fit_surv, xlab="Months",ylab="Estimated Probability of Survival")

Next we create Kaplan-Meier survival curves that are stratified by sex, in order to reproduce Figure 11.3.

fit_sex <- survfit (Surv(time,status) ~ sex)
plot (fit_sex , xlab = "Months",
ylab = "Estimated Probability of Survival", col = c(2,4))
legend("bottomleft", levels (sex), col = c(2,4), lty = 1)

As discussed in Section 11.4, we can perform a log-rank test to compare the survival of males to females, using the survdiff() function.

logrank_test <- survdiff(Surv(time, status) ~ sex)
logrank_test
Call:
survdiff(formula = Surv(time, status) ~ sex)

            N Observed Expected (O-E)^2/E (O-E)^2/V
sex=Female 45       15     18.5     0.676      1.44
sex=Male   43       20     16.5     0.761      1.44

 Chisq= 1.4  on 1 degrees of freedom, p= 0.2 

The resulting \(p\)-value is 0.23, indicating no evidence of a difference in survival between the two sexes.

Next, we fit Cox proportional hazards models using the coxph() function. To begin, we consider a model that uses sex as the only predictor.

fit_cox <- coxph(Surv(time, status) ~ sex)
summary(fit_cox)
Call:
coxph(formula = Surv(time, status) ~ sex)

  n= 88, number of events= 35 

          coef exp(coef) se(coef)     z Pr(>|z|)
sexMale 0.4077    1.5033   0.3420 1.192    0.233

        exp(coef) exp(-coef) lower .95 upper .95
sexMale     1.503     0.6652     0.769     2.939

Concordance= 0.565  (se = 0.045 )
Likelihood ratio test= 1.44  on 1 df,   p=0.2
Wald test            = 1.42  on 1 df,   p=0.2
Score (logrank) test = 1.44  on 1 df,   p=0.2

Note that the values of the likelihood ratio, Wald, and score tests have been rounded. It is possible to display additional digits.

summary(fit_cox)$logtest[1]
    test 
1.438822 
summary(fit_cox)$waldtest[1]
test 
1.42 
summary(fit_cox)$sctest[1]
    test 
1.440495 

Regardless of which test we use, we see that there is no clear evidence for a difference in survival between males and females.

logrank_test$chisq
[1] 1.440495

As we learned in this chapter, the score test from the Cox model is exactly equal to the log rank test statistic!

Now we fit a model that makes use of additional predictors.

fit_all <- coxph(Surv(time, status) ~ sex + diagnosis + loc + ki + gtv + stereo)
fit_all
Call:
coxph(formula = Surv(time, status) ~ sex + diagnosis + loc + 
    ki + gtv + stereo)

                       coef exp(coef) se(coef)      z        p
sexMale             0.18375   1.20171  0.36036  0.510  0.61012
diagnosisLG glioma  0.91502   2.49683  0.63816  1.434  0.15161
diagnosisHG glioma  2.15457   8.62414  0.45052  4.782 1.73e-06
diagnosisOther      0.88570   2.42467  0.65787  1.346  0.17821
locSupratentorial   0.44119   1.55456  0.70367  0.627  0.53066
ki                 -0.05496   0.94653  0.01831 -3.001  0.00269
gtv                 0.03429   1.03489  0.02233  1.536  0.12466
stereoSRT           0.17778   1.19456  0.60158  0.296  0.76760

Likelihood ratio test=41.37  on 8 df, p=1.776e-06
n= 87, number of events= 35 
   (1 observation deleted due to missingness)

The diagnosis variable has been coded so that the baseline corresponds to meningioma. The results indicate that the risk associated with HG glioma is more than eight times (i.e. \(e^{2.15} = 8.62\)) the risk associated with meningioma. In other words, after adjusting for the other predictors, patients with HG glioma have much worse survival compared to those with meningioma. In addition, larger values of the Karnofsky index, ki, are associated with lower risk, i.e. longer survival.

Finally, we plot survival curves for each diagnosis category, adjusting for the other predictors. To make these plots, we set the values of the other predictors equal to the mean for quantitative variables, and the modal value for factors. We first create a data frame with four rows, one for each level of diagnosis. The survfit() function will produce a curve for each of the rows in this data frame, and one call to plot() will display them all in the same plot.

modaldata <- data.frame(
diagnosis = levels(diagnosis),
sex = rep("Female", 4),
loc = rep("Supratentorial", 4),
ki = rep(mean(ki), 4),
gtv = rep(mean(gtv), 4),
stereo = rep("SRT", 4)
)

survplots <- survfit(fit_all, newdata = modaldata)
plot (survplots , xlab = "Months", ylab = "Survival Probability", col = 2:5)
legend ("bottomleft", levels (diagnosis), col = 2:5, lty = 1)

Publication Data

The Publication data presented in Section 11.5.4 can be found in the ISLR2 library. We first reproduce Figure 11.5 by plotting the Kaplan-Meier curves stratified on the posres variable, which records whether the study had a positive or negative result.

fit_posres <- survfit (Surv(time, status) ∼ posres , data = Publication)
plot(fit_posres , xlab = "Months",ylab = "Probability of Not Being Published ", col = 3:4)
legend ("topright", c("Negative Result", "Positive Result") , col = 3:4, lty = 1)

As discussed previously, the \(p\)-values from fitting Cox’s proportional hazards model to the posres variable are quite large, providing no evidence of a difference in time-to-publication between studies with positive versus negative results.

fit_pub <- coxph(Surv(time, status) ~ posres , data = Publication)
fit_pub
Call:
coxph(formula = Surv(time, status) ~ posres, data = Publication)

         coef exp(coef) se(coef)     z    p
posres 0.1481    1.1596   0.1616 0.916 0.36

Likelihood ratio test=0.83  on 1 df, p=0.3611
n= 244, number of events= 156 

As expected, the log-rank test provides an identical conclusion.

logrank_test <- survdiff(Surv(time, status) ~ posres , data =  Publication)
logrank_test
Call:
survdiff(formula = Surv(time, status) ~ posres, data = Publication)

           N Observed Expected (O-E)^2/E (O-E)^2/V
posres=0 146       87     92.6     0.341     0.844
posres=1  98       69     63.4     0.498     0.844

 Chisq= 0.8  on 1 degrees of freedom, p= 0.4 

However, the results change dramatically when we include other predictors in the model. Here we have excluded the funding mechanism variable.

fit_pub2 <- coxph(Surv(time, status) ~ . - mech , data = Publication)
fit_pub2
Call:
coxph(formula = Surv(time, status) ~ . - mech, data = Publication)

               coef  exp(coef)   se(coef)      z       p
posres    5.708e-01  1.770e+00  1.760e-01  3.244 0.00118
multi    -4.086e-02  9.600e-01  2.512e-01 -0.163 0.87079
clinend   5.462e-01  1.727e+00  2.620e-01  2.085 0.03710
sampsize  4.678e-06  1.000e+00  1.472e-05  0.318 0.75070
budget    4.385e-03  1.004e+00  2.465e-03  1.779 0.07518
impact    5.832e-02  1.060e+00  6.676e-03  8.735 < 2e-16

Likelihood ratio test=149.2  on 6 df, p=< 2.2e-16
n= 244, number of events= 156 

We see that there are a number of statistically significant variables, including whether the trial focused on a clinical endpoint, the impact of the study, and whether the study had positive or negative results.

Call Center Data

In this section, we will simulate survival data using the sim.survdata() function, which is part of the coxed library. Our simulated data will represent the observed wait times (in seconds) for 2,000 customers who have phoned a call center. In this context, censoring occurs if a customer hangs up before his or her call is answered.

There are three covariates: Operators (the number of call center operators available at the time of the call, which can range from 5 to 15), Center (either A, B, or C), and Time of day (Morning, Afternoon, or Evening). We generate data for these covariates so that all possibilities are equally likely: for instance, morning, afternoon and evening calls are equally likely, and any number of operators from 5 to 15 is equally likely.

set.seed (4)
N <- 2000
Operators <- sample(5:15, N, replace = T)
Center <- sample(c("A", "B", "C"), N, replace = T)
Time <- sample(c("Morning", "Afternoon", "Evening"), N, replace = T)
X <- model.matrix( ~ Operators + Center + Time)[, -1]

It is worthwhile to take a peek at the design matrix X, so that we can be sure that we understand how the variables have been coded.

X[1:5,]
  Operators CenterB CenterC TimeEvening TimeMorning
1        12       1       0           0           1
2        15       0       0           0           0
3         7       0       1           1           0
4         7       0       0           0           0
5        11       0       1           0           1

Next, we specify the coefficients and the hazard function.

true_beta <- c(0.04, -0.3, 0, 0.2, -0.2)
h_fn <- function(x) return(0.00001 * x)

Here, we have set the coefficient associated with Operators to equal 0.04; in other words, each additional operator leads to a \(e^{0.04} = 1.041\)-fold increase in the “risk” that the call will be answered, given the Center and Time covariates. This makes sense: the greater the number of operators at hand, the shorter the wait time! The coefficient associated with Center = B is −0.3, and Center = A is treated as the baseline. This means that the risk of a call being answered at Center B is 0.74 times the risk that it will be answered at Center A; in other words, the wait times are a bit longer at Center B.

We are now ready to generate data under the Cox proportional hazards model. The sim.survdata() function allows us to specify the maximum possible failure time, which in this case corresponds to the longest possible wait time for a customer; we set this to equal 1,000 seconds.

library (coxed)
package 㤼㸱coxed㤼㸲 was built under R version 3.6.3Loading required package: rms
package 㤼㸱rms㤼㸲 was built under R version 3.6.3Loading required package: Hmisc
package 㤼㸱Hmisc㤼㸲 was built under R version 3.6.3Loading required package: lattice
Loading required package: Formula
Loading required package: ggplot2
package 㤼㸱ggplot2㤼㸲 was built under R version 3.6.3Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     

Attaching package: 㤼㸱Hmisc㤼㸲

The following objects are masked from 㤼㸱package:base㤼㸲:

    format.pval, units

Loading required package: SparseM
package 㤼㸱SparseM㤼㸲 was built under R version 3.6.2
Attaching package: 㤼㸱SparseM㤼㸲

The following object is masked from 㤼㸱package:base㤼㸲:

    backsolve

Loading required package: mgcv
Loading required package: nlme
This is mgcv 1.8-28. For overview type 'help("mgcv-package")'.
queuing <- sim.survdata(N = N, T = 1000, X = X, beta = true_beta, hazard.fun = h_fn)
9 additional observations right-censored because the user-supplied hazard function
                                  is nonzero at the latest timepoint. To avoid these extra censored observations, increase T
names(queuing)
[1] "data"             "xdata"            "baseline"        
[4] "xb"               "exp.xb"           "betas"           
[7] "ind.survive"      "marg.effect"      "marg.effect.data"

The “observed” data is stored in queuing$data, with y corresponding to the event time and failed an indicator of whether the call was answered (failed = T) or the customer hung up before the call was answered (failed = F). We see that almost 90% of calls were answered.

head(queuing$data)
mean(queuing$data$failed)
[1] 0.89

We now plot Kaplan-Meier survival curves. First, we stratify by Center.

#par(mfrow = c(1, 2))
fit_Center <- survfit(Surv(y, failed) ~ Center , data = queuing$data)
plot(fit_Center , xlab = "Seconds", ylab = "Probability of Still Being on Hold", col = c(2, 4, 5))
legend("topright", c("Call Center A", "Call Center B", "Call Center C"), col = c(2, 4, 5), lty = 1)

Next, we stratify by Time.

fit_Time <- survfit(Surv(y, failed) ~ Time, data = queuing$data)
plot(fit_Time , xlab = "Seconds", ylab = "Probability of Still Being on Hold",
col = c(2, 4, 5))
legend("topright", c("Morning", "Afternoon", "Evening"), col = c(5, 2, 4), lty = 1)

It seems that calls at Call Center B take longer to be answered than calls at Centers A and C. Similarly, it appears that wait times are longest in the morning and shortest in the evening hours. We can use a log-rank test to determine whether these differences are statistically significant.

survdiff(Surv(y, failed) ~ Center, data = queuing$data)
Call:
survdiff(formula = Surv(y, failed) ~ Center, data = queuing$data)

           N Observed Expected (O-E)^2/E (O-E)^2/V
Center=A 683      603      579     0.971      1.45
Center=B 667      600      701    14.641     24.64
Center=C 650      577      499    12.062     17.05

 Chisq= 28.3  on 2 degrees of freedom, p= 7e-07 
survdiff(Surv(y, failed) ~ Time, data = queuing$data)
Call:
survdiff(formula = Surv(y, failed) ~ Time, data = queuing$data)

                 N Observed Expected (O-E)^2/E (O-E)^2/V
Time=Afternoon 688      616      619    0.0135     0.021
Time=Evening   653      582      468   27.6353    38.353
Time=Morning   659      582      693   17.7381    29.893

 Chisq= 46.8  on 2 degrees of freedom, p= 7e-11 

We find that differences between centers are highly significant, as are differences between times of day.

Finally, we fit Cox’s proportional hazards model to the data.

fit_queuing <- coxph(Surv(y, failed) ∼ ., data = queuing$data)
fit_queuing
Call:
coxph(formula = Surv(y, failed) ~ ., data = queuing$data)

                coef exp(coef) se(coef)      z        p
Operators    0.04174   1.04263  0.00759  5.500  3.8e-08
CenterB     -0.21879   0.80349  0.05793 -3.777 0.000159
CenterC      0.07930   1.08253  0.05850  1.356 0.175256
TimeEvening  0.20904   1.23249  0.05820  3.592 0.000328
TimeMorning -0.17352   0.84070  0.05811 -2.986 0.002828

Likelihood ratio test=102.8  on 5 df, p=< 2.2e-16
n= 2000, number of events= 1780 

The \(p\)-values for Center = B, Time = Evening. and Time = Morning. are very small. It is also clear that the hazard — that is, the instantaneous risk that a call will be answered — increases with the number of operators. Since we generated the data ourselves, we know that the true coefficients for Operators, Center = B, Center = C, Time = Evening, and Time = Morning are 0.04, −0.3, 0, 0.2, and −0.2, respectively. The coefficient estimates resulting from the Cox model are fairly accurate.

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KLS0tDQojIFN1cnZpdmFsIEFuYWx5c2lzICANCkluIHRoaXMgbGFiLCB3ZSBwZXJmb3JtIHN1cnZpdmFsIGFuYWx5c2VzIG9uIHRocmVlIHNlcGFyYXRlIGRhdGEgc2V0cy4gSW4gU2VjdGlvbiAxMS44LjEgd2UgYW5hbHl6ZSB0aGUgW0JyYWluQ2FuY2VyXShodHRwczovL3JkcnIuaW8vY3Jhbi9JU0xSMi9tYW4vQnJhaW5DYW5jZXIuaHRtbCkgZGF0YSB0aGF0IHdhcyBmaXJzdCBkZXNjcmliZWQgaW4gU2VjdGlvbiAxMS4zLiBJbiBTZWN0aW9uIDExLjguMiwgd2UgZXhhbWluZSB0aGUgW1B1YmxpY2F0aW9uXShodHRwczovL3JkcnIuaW8vZ2l0aHViL2xpZnMtdG9vbHMvcm16VGFiLW0vbWFuL1B1YmxpY2F0aW9uLmh0bWwpIGRhdGEgZnJvbSBTZWN0aW9uIDExLjUuNC4gRmluYWxseSwgU2VjdGlvbiAxMS44LjMgZXhwbG9yZXMgYSBzaW11bGF0ZWQgY2FsbCBjZW50ZXIgZGF0YSBzZXQuDQoNCiMjIEJyYWluIENhbmNlciBEYXRhICANCldlIGJlZ2luIHdpdGggdGhlIGBCcmFpbkNhbmNlcmAgZGF0YSBzZXQsIHdoaWNoIGlzIHBhcnQgb2YgdGhlIGBJU0xSMmAgcGFja2FnZS4NCg0KYGBge3J9DQpsaWJyYXJ5KElTTFIyKQ0KYGBgDQpUaGUgcm93cyBpbmRleCB0aGUgODggcGF0aWVudHMsIHdoaWxlIHRoZSBjb2x1bW5zIGNvbnRhaW4gdGhlIDggcHJlZGljdG9ycy4NCg0KYGBge3J9DQpuYW1lcyhCcmFpbkNhbmNlcikNCmBgYA0KDQpXZSBmaXJzdCBicmllZmx5IGV4YW1pbmUgdGhlIGRhdGEuDQoNCmBgYHtyfQ0KYXR0YWNoKEJyYWluQ2FuY2VyKQ0KdGFibGUoc2V4KQ0KYGBgDQoNCmBgYHtyfQ0KdGFibGUoZGlhZ25vc2lzKQ0KYGBgDQoNCmBgYHtyfQ0KdGFibGUoc3RhdHVzKQ0KYGBgDQoNCkJlZm9yZSBiZWdpbm5pbmcgYW4gYW5hbHlzaXMsIGl0IGlzIGltcG9ydGFudCB0byBrbm93IGhvdyB0aGUgYHN0YXR1c2AgdmFyaWFibGUgaGFzIGJlZW4gY29kZWQuIE1vc3Qgc29mdHdhcmUsIGluY2x1ZGluZyBgUmAsIHVzZXMgdGhlIGNvbnZlbnRpb24gdGhhdCBgc3RhdHVzID0gMWAgaW5kaWNhdGVzIGFuIHVuY2Vuc29yZWQgb2JzZXJ2YXRpb24sIGFuZCBgc3RhdHVzID0gMGAgaW5kaWNhdGVzIGEgY2Vuc29yZWQgb2JzZXJ2YXRpb24uIEJ1dCBzb21lIHNjaWVudGlzdHMgbWlnaHQgdXNlIHRoZSBvcHBvc2l0ZSBjb2RpbmcuIEZvciB0aGUgYEJyYWluQ2FuY2VyYCBkYXRhIHNldCAzNSBwYXRpZW50cyBkaWVkIGJlZm9yZSB0aGUgZW5kIG9mIHRoZSBzdHVkeS4NCg0KVG8gYmVnaW4gdGhlIGFuYWx5c2lzLCB3ZSByZS1jcmVhdGUgdGhlIEthcGxhbi1NZWllciBzdXJ2aXZhbCBjdXJ2ZSBzaG93biBpbiBGaWd1cmUgMTEuMiwgdXNpbmcgdGhlIGBzdXJ2Zml0KClgIGZ1bmN0aW9uIHdpdGhpbiB0aGUgUiBzdXJ2aXZhbCBsaWJyYXJ5LiBIZXJlIHRpbWUgY29ycmVzcG9uZHMgdG8gJHlfaSQsIHRoZSB0aW1lIHRvIHRoZSAkaSR0aCBldmVudCAoZWl0aGVyIGNlbnNvcmluZyBvciBkZWF0aCkuDQoNCmBgYHtyfQ0KbGlicmFyeShzdXJ2aXZhbCkNCmZpdF9zdXJ2PC1zdXJ2Zml0KFN1cnYodGltZSxzdGF0dXMpfjEpDQpwbG90KGZpdF9zdXJ2LCB4bGFiPSJNb250aHMiLHlsYWI9IkVzdGltYXRlZCBQcm9iYWJpbGl0eSBvZiBTdXJ2aXZhbCIpDQpgYGANCg0KTmV4dCB3ZSBjcmVhdGUgS2FwbGFuLU1laWVyIHN1cnZpdmFsIGN1cnZlcyB0aGF0IGFyZSBzdHJhdGlmaWVkIGJ5IGBzZXhgLCBpbiBvcmRlciB0byByZXByb2R1Y2UgRmlndXJlIDExLjMuDQoNCmBgYHtyfQ0KZml0X3NleCA8LSBzdXJ2Zml0IChTdXJ2KHRpbWUsc3RhdHVzKSB+IHNleCkNCnBsb3QgKGZpdF9zZXggLCB4bGFiID0gIk1vbnRocyIsDQp5bGFiID0gIkVzdGltYXRlZCBQcm9iYWJpbGl0eSBvZiBTdXJ2aXZhbCIsIGNvbCA9IGMoMiw0KSkNCmxlZ2VuZCgiYm90dG9tbGVmdCIsIGxldmVscyAoc2V4KSwgY29sID0gYygyLDQpLCBsdHkgPSAxKQ0KYGBgDQoNCkFzIGRpc2N1c3NlZCBpbiBTZWN0aW9uIDExLjQsIHdlIGNhbiBwZXJmb3JtIGEgbG9nLXJhbmsgdGVzdCB0byBjb21wYXJlIHRoZSBzdXJ2aXZhbCBvZiBtYWxlcyB0byBmZW1hbGVzLCB1c2luZyB0aGUgYHN1cnZkaWZmKClgIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCmxvZ3JhbmtfdGVzdCA8LSBzdXJ2ZGlmZihTdXJ2KHRpbWUsIHN0YXR1cykgfiBzZXgpDQpsb2dyYW5rX3Rlc3QNCmBgYA0KDQpUaGUgcmVzdWx0aW5nICRwJC12YWx1ZSBpcyAwLjIzLCBpbmRpY2F0aW5nIG5vIGV2aWRlbmNlIG9mIGEgZGlmZmVyZW5jZSBpbiBzdXJ2aXZhbCBiZXR3ZWVuIHRoZSB0d28gc2V4ZXMuDQoNCk5leHQsIHdlIGZpdCBDb3ggcHJvcG9ydGlvbmFsIGhhemFyZHMgbW9kZWxzIHVzaW5nIHRoZSBgY294cGgoKWAgZnVuY3Rpb24uIFRvIGJlZ2luLCB3ZSBjb25zaWRlciBhIG1vZGVsIHRoYXQgdXNlcyBzZXggYXMgdGhlIG9ubHkgcHJlZGljdG9yLg0KDQpgYGB7cn0NCmZpdF9jb3ggPC0gY294cGgoU3Vydih0aW1lLCBzdGF0dXMpIH4gc2V4KQ0Kc3VtbWFyeShmaXRfY294KQ0KYGBgDQoNCk5vdGUgdGhhdCB0aGUgdmFsdWVzIG9mIHRoZSBsaWtlbGlob29kIHJhdGlvLCBXYWxkLCBhbmQgc2NvcmUgdGVzdHMgaGF2ZSBiZWVuIHJvdW5kZWQuIEl0IGlzIHBvc3NpYmxlIHRvIGRpc3BsYXkgYWRkaXRpb25hbCBkaWdpdHMuDQoNCmBgYHtyfQ0Kc3VtbWFyeShmaXRfY294KSRsb2d0ZXN0WzFdDQpzdW1tYXJ5KGZpdF9jb3gpJHdhbGR0ZXN0WzFdDQpzdW1tYXJ5KGZpdF9jb3gpJHNjdGVzdFsxXQ0KYGBgDQoNClJlZ2FyZGxlc3Mgb2Ygd2hpY2ggdGVzdCB3ZSB1c2UsIHdlIHNlZSB0aGF0IHRoZXJlIGlzIG5vIGNsZWFyIGV2aWRlbmNlIGZvciBhIGRpZmZlcmVuY2UgaW4gc3Vydml2YWwgYmV0d2VlbiBtYWxlcyBhbmQgZmVtYWxlcy4NCg0KYGBge3J9DQpsb2dyYW5rX3Rlc3QkY2hpc3ENCmBgYA0KDQoNCkFzIHdlIGxlYXJuZWQgaW4gdGhpcyBjaGFwdGVyLCB0aGUgc2NvcmUgdGVzdCBmcm9tIHRoZSBDb3ggbW9kZWwgaXMgZXhhY3RseQ0KZXF1YWwgdG8gdGhlIGxvZyByYW5rIHRlc3Qgc3RhdGlzdGljIQ0KDQpOb3cgd2UgZml0IGEgbW9kZWwgdGhhdCBtYWtlcyB1c2Ugb2YgYWRkaXRpb25hbCBwcmVkaWN0b3JzLg0KDQoNCmBgYHtyfQ0KZml0X2FsbCA8LSBjb3hwaChTdXJ2KHRpbWUsIHN0YXR1cykgfiBzZXggKyBkaWFnbm9zaXMgKyBsb2MgKyBraSArIGd0diArIHN0ZXJlbykNCmZpdF9hbGwNCmBgYA0KDQpUaGUgZGlhZ25vc2lzIHZhcmlhYmxlIGhhcyBiZWVuIGNvZGVkIHNvIHRoYXQgdGhlIGJhc2VsaW5lIGNvcnJlc3BvbmRzIHRvIG1lbmluZ2lvbWEuIFRoZSByZXN1bHRzIGluZGljYXRlIHRoYXQgdGhlIHJpc2sgYXNzb2NpYXRlZCB3aXRoICpIRyogZ2xpb21hIGlzIG1vcmUgdGhhbiBlaWdodCB0aW1lcyAoaS5lLiAkZV57Mi4xNX0gPSA4LjYyJCkgdGhlIHJpc2sgYXNzb2NpYXRlZCB3aXRoIG1lbmluZ2lvbWEuIEluIG90aGVyIHdvcmRzLCBhZnRlciBhZGp1c3RpbmcgZm9yIHRoZSBvdGhlciBwcmVkaWN0b3JzLCBwYXRpZW50cyB3aXRoICpIRyogZ2xpb21hIGhhdmUgbXVjaCB3b3JzZSBzdXJ2aXZhbCBjb21wYXJlZCB0byB0aG9zZSB3aXRoIG1lbmluZ2lvbWEuIEluIGFkZGl0aW9uLCBsYXJnZXIgdmFsdWVzIG9mIHRoZSBLYXJub2Zza3kgaW5kZXgsIGBraWAsIGFyZSBhc3NvY2lhdGVkIHdpdGggbG93ZXIgcmlzaywgaS5lLiBsb25nZXIgc3Vydml2YWwuDQoNCkZpbmFsbHksIHdlIHBsb3Qgc3Vydml2YWwgY3VydmVzIGZvciBlYWNoIGRpYWdub3NpcyBjYXRlZ29yeSwgYWRqdXN0aW5nIGZvciB0aGUgb3RoZXIgcHJlZGljdG9ycy4gVG8gbWFrZSB0aGVzZSBwbG90cywgd2Ugc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIG90aGVyIHByZWRpY3RvcnMgZXF1YWwgdG8gdGhlIG1lYW4gZm9yIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMsIGFuZCB0aGUgbW9kYWwgdmFsdWUgZm9yIGZhY3RvcnMuIFdlIGZpcnN0IGNyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCBmb3VyIHJvd3MsIG9uZSBmb3IgZWFjaCBsZXZlbCBvZiBkaWFnbm9zaXMuIFRoZSBgc3VydmZpdCgpYCBmdW5jdGlvbiB3aWxsIHByb2R1Y2UgYSBjdXJ2ZSBmb3IgZWFjaCBvZiB0aGUgcm93cyBpbiB0aGlzIGRhdGEgZnJhbWUsIGFuZCBvbmUgY2FsbCB0byBgcGxvdCgpYCB3aWxsIGRpc3BsYXkgdGhlbSBhbGwgaW4gdGhlIHNhbWUgcGxvdC4NCg0KYGBge3J9DQptb2RhbGRhdGEgPC0gZGF0YS5mcmFtZSgNCmRpYWdub3NpcyA9IGxldmVscyhkaWFnbm9zaXMpLA0Kc2V4ID0gcmVwKCJGZW1hbGUiLCA0KSwNCmxvYyA9IHJlcCgiU3VwcmF0ZW50b3JpYWwiLCA0KSwNCmtpID0gcmVwKG1lYW4oa2kpLCA0KSwNCmd0diA9IHJlcChtZWFuKGd0diksIDQpLA0Kc3RlcmVvID0gcmVwKCJTUlQiLCA0KQ0KKQ0KDQpzdXJ2cGxvdHMgPC0gc3VydmZpdChmaXRfYWxsLCBuZXdkYXRhID0gbW9kYWxkYXRhKQ0KcGxvdCAoc3VydnBsb3RzICwgeGxhYiA9ICJNb250aHMiLCB5bGFiID0gIlN1cnZpdmFsIFByb2JhYmlsaXR5IiwgY29sID0gMjo1KQ0KbGVnZW5kICgiYm90dG9tbGVmdCIsIGxldmVscyAoZGlhZ25vc2lzKSwgY29sID0gMjo1LCBsdHkgPSAxKQ0KYGBgDQoNCiMjIFB1YmxpY2F0aW9uIERhdGEgIA0KVGhlIGBQdWJsaWNhdGlvbmAgZGF0YSBwcmVzZW50ZWQgaW4gU2VjdGlvbiAxMS41LjQgY2FuIGJlIGZvdW5kIGluIHRoZSBgSVNMUjJgIGxpYnJhcnkuIFdlIGZpcnN0IHJlcHJvZHVjZSBGaWd1cmUgMTEuNSBieSBwbG90dGluZyB0aGUgS2FwbGFuLU1laWVyIGN1cnZlcyBzdHJhdGlmaWVkIG9uIHRoZSBgcG9zcmVzYCB2YXJpYWJsZSwgd2hpY2ggcmVjb3JkcyB3aGV0aGVyIHRoZSBzdHVkeSBoYWQgYSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSByZXN1bHQuDQoNCmBgYHtyfQ0KZml0X3Bvc3JlcyA8LSBzdXJ2Zml0IChTdXJ2KHRpbWUsIHN0YXR1cykg4oi8IHBvc3JlcyAsIGRhdGEgPSBQdWJsaWNhdGlvbikNCnBsb3QoZml0X3Bvc3JlcyAsIHhsYWIgPSAiTW9udGhzIix5bGFiID0gIlByb2JhYmlsaXR5IG9mIE5vdCBCZWluZyBQdWJsaXNoZWQgIiwgY29sID0gMzo0KQ0KbGVnZW5kICgidG9wcmlnaHQiLCBjKCJOZWdhdGl2ZSBSZXN1bHQiLCAiUG9zaXRpdmUgUmVzdWx0IikgLCBjb2wgPSAzOjQsIGx0eSA9IDEpDQpgYGANCg0KQXMgZGlzY3Vzc2VkIHByZXZpb3VzbHksIHRoZSAkcCQtdmFsdWVzIGZyb20gZml0dGluZyBDb3jigJlzIHByb3BvcnRpb25hbCBoYXphcmRzIG1vZGVsIHRvIHRoZSBgcG9zcmVzYCB2YXJpYWJsZSBhcmUgcXVpdGUgbGFyZ2UsIHByb3ZpZGluZyBubyBldmlkZW5jZSBvZiBhIGRpZmZlcmVuY2UgaW4gdGltZS10by1wdWJsaWNhdGlvbiBiZXR3ZWVuIHN0dWRpZXMgd2l0aCBwb3NpdGl2ZSB2ZXJzdXMgbmVnYXRpdmUgcmVzdWx0cy4NCg0KYGBge3J9DQpmaXRfcHViIDwtIGNveHBoKFN1cnYodGltZSwgc3RhdHVzKSB+IHBvc3JlcyAsIGRhdGEgPSBQdWJsaWNhdGlvbikNCmZpdF9wdWINCmBgYA0KDQpBcyBleHBlY3RlZCwgdGhlIGxvZy1yYW5rIHRlc3QgcHJvdmlkZXMgYW4gaWRlbnRpY2FsIGNvbmNsdXNpb24uDQoNCmBgYHtyfQ0KbG9ncmFua190ZXN0IDwtIHN1cnZkaWZmKFN1cnYodGltZSwgc3RhdHVzKSB+IHBvc3JlcyAsIGRhdGEgPSAgUHVibGljYXRpb24pDQpsb2dyYW5rX3Rlc3QNCmBgYA0KDQoNCkhvd2V2ZXIsIHRoZSByZXN1bHRzIGNoYW5nZSBkcmFtYXRpY2FsbHkgd2hlbiB3ZSBpbmNsdWRlIG90aGVyIHByZWRpY3RvcnMgaW4gdGhlIG1vZGVsLiBIZXJlIHdlIGhhdmUgZXhjbHVkZWQgdGhlIGZ1bmRpbmcgbWVjaGFuaXNtIHZhcmlhYmxlLg0KDQpgYGB7cn0NCmZpdF9wdWIyIDwtIGNveHBoKFN1cnYodGltZSwgc3RhdHVzKSB+IC4gLSBtZWNoICwgZGF0YSA9IFB1YmxpY2F0aW9uKQ0KZml0X3B1YjINCmBgYA0KDQpXZSBzZWUgdGhhdCB0aGVyZSBhcmUgYSBudW1iZXIgb2Ygc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCB2YXJpYWJsZXMsIGluY2x1ZGluZyB3aGV0aGVyIHRoZSB0cmlhbCBmb2N1c2VkIG9uIGEgY2xpbmljYWwgZW5kcG9pbnQsIHRoZSBpbXBhY3Qgb2YgdGhlIHN0dWR5LCBhbmQgd2hldGhlciB0aGUgc3R1ZHkgaGFkIHBvc2l0aXZlIG9yIG5lZ2F0aXZlIHJlc3VsdHMuDQoNCiMjICBDYWxsIENlbnRlciBEYXRhICANCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBzaW11bGF0ZSBzdXJ2aXZhbCBkYXRhIHVzaW5nIHRoZSBgc2ltLnN1cnZkYXRhKClgIGZ1bmN0aW9uLCB3aGljaCBpcyBwYXJ0IG9mIHRoZSBgY294ZWRgIGxpYnJhcnkuIE91ciBzaW11bGF0ZWQgZGF0YSB3aWxsIHJlcHJlc2VudCB0aGUgb2JzZXJ2ZWQgd2FpdCB0aW1lcyAoaW4gc2Vjb25kcykgZm9yIDIsMDAwIGN1c3RvbWVycyB3aG8gaGF2ZSBwaG9uZWQgYSBjYWxsIGNlbnRlci4gSW4gdGhpcyBjb250ZXh0LCBjZW5zb3Jpbmcgb2NjdXJzIGlmIGEgY3VzdG9tZXIgaGFuZ3MgdXAgYmVmb3JlIGhpcyBvciBoZXIgY2FsbCBpcyBhbnN3ZXJlZC4gIA0KDQpUaGVyZSBhcmUgdGhyZWUgY292YXJpYXRlczogYE9wZXJhdG9yc2AgKHRoZSBudW1iZXIgb2YgY2FsbCBjZW50ZXIgb3BlcmF0b3JzIGF2YWlsYWJsZSBhdCB0aGUgdGltZSBvZiB0aGUgY2FsbCwgd2hpY2ggY2FuIHJhbmdlIGZyb20gNSB0byAxNSksIGBDZW50ZXJgIChlaXRoZXIgQSwgQiwgb3IgQyksIGFuZCBgVGltZWAgb2YgZGF5IChNb3JuaW5nLCBBZnRlcm5vb24sIG9yIEV2ZW5pbmcpLiBXZSBnZW5lcmF0ZSBkYXRhIGZvciB0aGVzZSBjb3ZhcmlhdGVzIHNvIHRoYXQgYWxsIHBvc3NpYmlsaXRpZXMgYXJlIGVxdWFsbHkgbGlrZWx5OiBmb3IgaW5zdGFuY2UsIG1vcm5pbmcsIGFmdGVybm9vbiBhbmQgZXZlbmluZyBjYWxscyBhcmUgZXF1YWxseSBsaWtlbHksIGFuZCBhbnkgbnVtYmVyIG9mIG9wZXJhdG9ycyBmcm9tIDUgdG8gMTUgaXMgZXF1YWxseSBsaWtlbHkuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQgKDQpDQpOIDwtIDIwMDANCk9wZXJhdG9ycyA8LSBzYW1wbGUoNToxNSwgTiwgcmVwbGFjZSA9IFQpDQpDZW50ZXIgPC0gc2FtcGxlKGMoIkEiLCAiQiIsICJDIiksIE4sIHJlcGxhY2UgPSBUKQ0KVGltZSA8LSBzYW1wbGUoYygiTW9ybmluZyIsICJBZnRlcm5vb24iLCAiRXZlbmluZyIpLCBOLCByZXBsYWNlID0gVCkNClggPC0gbW9kZWwubWF0cml4KCB+IE9wZXJhdG9ycyArIENlbnRlciArIFRpbWUpWywgLTFdDQpgYGANCg0KSXQgaXMgd29ydGh3aGlsZSB0byB0YWtlIGEgcGVlayBhdCB0aGUgZGVzaWduIG1hdHJpeCBgWGAsIHNvIHRoYXQgd2UgY2FuIGJlIHN1cmUgdGhhdCB3ZSB1bmRlcnN0YW5kIGhvdyB0aGUgdmFyaWFibGVzIGhhdmUgYmVlbiBjb2RlZC4NCg0KYGBge3J9DQpYWzE6NSxdDQpgYGANCg0KTmV4dCwgd2Ugc3BlY2lmeSB0aGUgY29lZmZpY2llbnRzIGFuZCB0aGUgaGF6YXJkIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCnRydWVfYmV0YSA8LSBjKDAuMDQsIC0wLjMsIDAsIDAuMiwgLTAuMikNCmhfZm4gPC0gZnVuY3Rpb24oeCkgcmV0dXJuKDAuMDAwMDEgKiB4KQ0KYGBgDQoNCkhlcmUsIHdlIGhhdmUgc2V0IHRoZSBjb2VmZmljaWVudCBhc3NvY2lhdGVkIHdpdGggYE9wZXJhdG9yc2AgdG8gZXF1YWwgMC4wNDsgaW4NCm90aGVyIHdvcmRzLCBlYWNoIGFkZGl0aW9uYWwgb3BlcmF0b3IgbGVhZHMgdG8gYSAkZV57MC4wNH0gPSAxLjA0MSQtZm9sZCBpbmNyZWFzZSBpbiB0aGUg4oCccmlza+KAnSB0aGF0IHRoZSBjYWxsIHdpbGwgYmUgYW5zd2VyZWQsIGdpdmVuIHRoZSBgQ2VudGVyYCBhbmQgYFRpbWVgIGNvdmFyaWF0ZXMuIFRoaXMgbWFrZXMgc2Vuc2U6IHRoZSBncmVhdGVyIHRoZSBudW1iZXIgb2Ygb3BlcmF0b3JzIGF0IGhhbmQsIHRoZSBzaG9ydGVyIHRoZSB3YWl0IHRpbWUhIFRoZSBjb2VmZmljaWVudCBhc3NvY2lhdGVkIHdpdGggYENlbnRlciA9IEJgIGlzIOKIkjAuMywgYW5kIGBDZW50ZXIgPSBBYCBpcyB0cmVhdGVkIGFzIHRoZSBiYXNlbGluZS4gVGhpcyBtZWFucyB0aGF0IHRoZSByaXNrIG9mIGEgY2FsbCBiZWluZyBhbnN3ZXJlZCBhdCBDZW50ZXIgQiBpcyAwLjc0IHRpbWVzIHRoZSByaXNrIHRoYXQgaXQgd2lsbCBiZSBhbnN3ZXJlZCBhdCBDZW50ZXIgQTsgaW4gb3RoZXIgd29yZHMsIHRoZSB3YWl0IHRpbWVzIGFyZSBhIGJpdCBsb25nZXIgYXQgQ2VudGVyIEIuDQoNCldlIGFyZSBub3cgcmVhZHkgdG8gZ2VuZXJhdGUgZGF0YSB1bmRlciB0aGUgQ294IHByb3BvcnRpb25hbCBoYXphcmRzIG1vZGVsLiBUaGUgYHNpbS5zdXJ2ZGF0YSgpYCBmdW5jdGlvbiBhbGxvd3MgdXMgdG8gc3BlY2lmeSB0aGUgbWF4aW11bSBwb3NzaWJsZSBmYWlsdXJlIHRpbWUsIHdoaWNoIGluIHRoaXMgY2FzZSBjb3JyZXNwb25kcyB0byB0aGUgbG9uZ2VzdCBwb3NzaWJsZSB3YWl0IHRpbWUgZm9yIGEgY3VzdG9tZXI7IHdlIHNldCB0aGlzIHRvIGVxdWFsIDEsMDAwIHNlY29uZHMuDQoNCmBgYHtyfQ0KbGlicmFyeSAoY294ZWQpDQpxdWV1aW5nIDwtIHNpbS5zdXJ2ZGF0YShOID0gTiwgVCA9IDEwMDAsIFggPSBYLCBiZXRhID0gdHJ1ZV9iZXRhLCBoYXphcmQuZnVuID0gaF9mbikNCm5hbWVzKHF1ZXVpbmcpDQpgYGANCg0KVGhlIOKAnG9ic2VydmVk4oCdIGRhdGEgaXMgc3RvcmVkIGluIGBxdWV1aW5nJGRhdGFgLCB3aXRoIGB5YCBjb3JyZXNwb25kaW5nIHRvIHRoZSBldmVudCB0aW1lIGFuZCBmYWlsZWQgYW4gaW5kaWNhdG9yIG9mIHdoZXRoZXIgdGhlIGNhbGwgd2FzIGFuc3dlcmVkIChgZmFpbGVkID0gVGApIG9yIHRoZSBjdXN0b21lciBodW5nIHVwIGJlZm9yZSB0aGUgY2FsbCB3YXMgYW5zd2VyZWQgKGBmYWlsZWQgPSBGYCkuIFdlIHNlZSB0aGF0IGFsbW9zdCA5MCUgb2YgY2FsbHMgd2VyZSBhbnN3ZXJlZC4NCg0KYGBge3J9DQpoZWFkKHF1ZXVpbmckZGF0YSkNCmBgYA0KDQpgYGB7cn0NCm1lYW4ocXVldWluZyRkYXRhJGZhaWxlZCkNCmBgYA0KV2Ugbm93IHBsb3QgS2FwbGFuLU1laWVyIHN1cnZpdmFsIGN1cnZlcy4gRmlyc3QsIHdlIHN0cmF0aWZ5IGJ5IGBDZW50ZXJgLg0KDQpgYGB7cn0NCiNwYXIobWZyb3cgPSBjKDEsIDIpKQ0KZml0X0NlbnRlciA8LSBzdXJ2Zml0KFN1cnYoeSwgZmFpbGVkKSB+IENlbnRlciAsIGRhdGEgPSBxdWV1aW5nJGRhdGEpDQpwbG90KGZpdF9DZW50ZXIgLCB4bGFiID0gIlNlY29uZHMiLCB5bGFiID0gIlByb2JhYmlsaXR5IG9mIFN0aWxsIEJlaW5nIG9uIEhvbGQiLCBjb2wgPSBjKDIsIDQsIDUpKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGMoIkNhbGwgQ2VudGVyIEEiLCAiQ2FsbCBDZW50ZXIgQiIsICJDYWxsIENlbnRlciBDIiksIGNvbCA9IGMoMiwgNCwgNSksIGx0eSA9IDEpDQpgYGANCg0KTmV4dCwgd2Ugc3RyYXRpZnkgYnkgYFRpbWVgLg0KDQpgYGB7cn0NCmZpdF9UaW1lIDwtIHN1cnZmaXQoU3Vydih5LCBmYWlsZWQpIH4gVGltZSwgZGF0YSA9IHF1ZXVpbmckZGF0YSkNCnBsb3QoZml0X1RpbWUgLCB4bGFiID0gIlNlY29uZHMiLCB5bGFiID0gIlByb2JhYmlsaXR5IG9mIFN0aWxsIEJlaW5nIG9uIEhvbGQiLA0KY29sID0gYygyLCA0LCA1KSkNCmxlZ2VuZCgidG9wcmlnaHQiLCBjKCJNb3JuaW5nIiwgIkFmdGVybm9vbiIsICJFdmVuaW5nIiksIGNvbCA9IGMoNSwgMiwgNCksIGx0eSA9IDEpDQpgYGANCg0KSXQgc2VlbXMgdGhhdCBjYWxscyBhdCBDYWxsIENlbnRlciBCIHRha2UgbG9uZ2VyIHRvIGJlIGFuc3dlcmVkIHRoYW4gY2FsbHMgYXQgQ2VudGVycyBBIGFuZCBDLiBTaW1pbGFybHksIGl0IGFwcGVhcnMgdGhhdCB3YWl0IHRpbWVzIGFyZSBsb25nZXN0IGluIHRoZSBtb3JuaW5nIGFuZCBzaG9ydGVzdCBpbiB0aGUgZXZlbmluZyBob3Vycy4gV2UgY2FuIHVzZSBhIGxvZy1yYW5rIHRlc3QgdG8gZGV0ZXJtaW5lIHdoZXRoZXIgdGhlc2UgZGlmZmVyZW5jZXMgYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuDQoNCmBgYHtyfQ0Kc3VydmRpZmYoU3Vydih5LCBmYWlsZWQpIH4gQ2VudGVyLCBkYXRhID0gcXVldWluZyRkYXRhKQ0Kc3VydmRpZmYoU3Vydih5LCBmYWlsZWQpIH4gVGltZSwgZGF0YSA9IHF1ZXVpbmckZGF0YSkNCmBgYA0KDQpXZSBmaW5kIHRoYXQgZGlmZmVyZW5jZXMgYmV0d2VlbiBjZW50ZXJzIGFyZSBoaWdobHkgc2lnbmlmaWNhbnQsIGFzIGFyZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRpbWVzIG9mIGRheS4NCg0KRmluYWxseSwgd2UgZml0IENveOKAmXMgcHJvcG9ydGlvbmFsIGhhemFyZHMgbW9kZWwgdG8gdGhlIGRhdGEuDQoNCmBgYHtyfQ0KZml0X3F1ZXVpbmcgPC0gY294cGgoU3Vydih5LCBmYWlsZWQpIOKIvCAuLCBkYXRhID0gcXVldWluZyRkYXRhKQ0KZml0X3F1ZXVpbmcNCmBgYA0KDQpUaGUgJHAkLXZhbHVlcyBmb3IgYENlbnRlciA9IEJgLCBgVGltZSA9IEV2ZW5pbmdgLiBhbmQgYFRpbWUgPSBNb3JuaW5nYC4gYXJlIHZlcnkgc21hbGwuIEl0IGlzIGFsc28gY2xlYXIgdGhhdCB0aGUgaGF6YXJkIOKAlCB0aGF0IGlzLCB0aGUgaW5zdGFudGFuZW91cyByaXNrIHRoYXQgYSBjYWxsIHdpbGwgYmUgYW5zd2VyZWQg4oCUIGluY3JlYXNlcyB3aXRoIHRoZSBudW1iZXIgb2Ygb3BlcmF0b3JzLiBTaW5jZSB3ZSBnZW5lcmF0ZWQgdGhlIGRhdGEgb3Vyc2VsdmVzLCB3ZSBrbm93IHRoYXQgdGhlIHRydWUgY29lZmZpY2llbnRzIGZvciBgT3BlcmF0b3JzYCwgYENlbnRlciA9IEJgLCBgQ2VudGVyID0gQ2AsIGBUaW1lID0gRXZlbmluZ2AsIGFuZCBgVGltZSA9IE1vcm5pbmdgIGFyZSAwLjA0LCDiiJIwLjMsIDAsIDAuMiwgYW5kIOKIkjAuMiwgcmVzcGVjdGl2ZWx5LiBUaGUgY29lZmZpY2llbnQgZXN0aW1hdGVzIHJlc3VsdGluZyBmcm9tIHRoZSBDb3ggbW9kZWwgYXJlIGZhaXJseSBhY2N1cmF0ZS4NCg==