This notebook conducts and demonstrates the results of the Random-effects meta-analysis performed on each healthcare system’s locally run covariate adjusted Kaplan-Meier survival curves.

Of note, this analysis is performed only on adult patients due to the pediatric cohort’s low incidence of both poor health outcomes and neurological diagnoses during COVID-19 hospitalization.

library(tidyverse)
library(metafor)
library(meta)
library(egg)
library(DT)
library(ggpubr)
source("R/forester_custom.R")
source("R/analyze_survival.R")

Import Data from each Healthcare system

Each participating healthcare system ran the analysis locally on patient level data using our customized R package. The output of each local analysis consisted of only the summary results (rather than patient level data). The model summary results from each healthcare system were then aggregated and are imported and then combined using the following code:

# read in files from results folder 
# this folder contains all of the local healthcare system level analyses
rdas <- list.files(
  path = "results",
  pattern = ".rda",
  full.names = TRUE
)
for (rda in rdas) {
  load(rda)
}

rm(rdas, rda)

# create a list of participating healthcare systems from our study tracking spreadsheet
site_google_url <- "https://docs.google.com/spreadsheets/d/1epcYNd_0jCUMktOHf8mz5v651zy1JALD6PgzobrGWDY/edit?usp=sharing"

# load site parameters
site_params <- googlesheets4::read_sheet(site_google_url, sheet = 1)
site_avails <- googlesheets4::read_sheet(site_google_url, sheet = 2)

# filter the list of sites who ran the analysis
sorted_sites <- site_avails %>%
  filter(!is.na(date_v4_received)) %>%
  pull(siteid) %>%
  paste("results", sep = "_")

# list sites without race
sites_wo_race <- site_params %>%
  filter(!include_race) %>%
  pull(siteid)

# combine all rda files with 'results' in name
results <- mget(ls(pattern = "results"))

## read in pt counts
pt_counts_df <- read.csv('tables/site_pt_counts.csv')

Pre-processing

Define outcome parameters

outcomes <-
  c(
    "time_first_discharge_reg_elix",
    "deceased_reg_elix"
  )

Identify sites to include in the analysis

In this analysis, we only included a healthcare system if they had >= 3 adult patients with a neurological diagnosis.

## adults
sites_adult <- pt_counts_df %>% 
  filter(population == "Adult") %>% 
  mutate(neuro_sum = n_var_Central + n_var_Peripheral) %>% 
  filter(!neuro_sum < 3) %>% 
  distinct(site) %>% 
  mutate(site = gsub("_results", "", site))

sites_adult_results <- sites_adult %>% 
  mutate(site = paste0(site, "_results")) %>% 
  as.vector()

adult_results <- results[grepl(paste(sites_adult_results$site, collapse = "|"), names(results))]

Random-Effects Meta-Analysis

Get Cox model results

This notebook specifies the parameters to load in the Cox-PH results using a censor_cutoff=‘90’ (days since initial hospitalization) and the comorb_method=‘ind’ which adjusts for patient comorbidity burden by treating each individual comorbidity of the Elixhauser Comorbidity Index as an individual model covariate (binary variable indicating whether or not the patient previously was diagnosed with the respective comorbidity).

cox_results_adults <- list()

for (outcome_i in outcomes) {
  cox_results_adults[[outcome_i]] <-
    adult_results %>%
    lapply(get_cox_row, population = "adults", comorb_method = "ind", censor_cutoff = "90", outcome = outcome_i) %>%
    bind_rows() %>%
    mutate(outcome = outcome_i)
}

Get adjusted KM results - Adults

Similarly, we will will load in the KM results while specifying the censor_cutoff=‘90’ and comorb_method=‘ind’.

Format survival results

res_dat_surv <- bind_rows(km_results_adults) %>%
  add_count(site, strata, outcome, name = "n_timepoints") %>%
  # add rows to fill in missing time points (this ensures all sites will have up to the censor date)
  complete(time, site, nesting(strata, outcome)) %>%
  group_by(site, strata, outcome) %>%
  arrange(time) %>% 
  # carry down the survival and standard errors to fill in missing time periods
  fill(surv, .direction = "down") %>%
  fill(std.err.sqrt, n_timepoints, .direction = "down") %>%
  fill(std.err, n_timepoints, .direction = "down") %>%
  ungroup() %>% 
  mutate(
    strata = toupper(strata),
    strata = factor(strata,
                    levels=c("NONE", "CNS", "PNS"),
                    labels=c("NNC", "CNS", "PNS"))
    )

Set survival plot params

Here we will specify several parameters to aid in our construction of the survival figure

# calculate CIs
zval <- qnorm(1- (1-0.95)/2, 0, 1)
standard_error <- function(x) sd(x, na.rm = TRUE) / sqrt(length(x)) # Create own function

# set plot colors
group.colors <- c(NNC = "#BBBBBC", PNS = "slateblue", CNS ="tomato")

Weight each site

Here we will weight each site by the inverse of its variance calculated from the CNS and PNS meta-analysis models. We will take the average of the CNS and PNS weights

load("survival_model/adults_deceased_reg_elix_ind_90_CNS.rda")
load("survival_model/adults_deceased_reg_elix_ind_90_PNS.rda")

load("survival_model/adults_time_first_discharge_reg_elix_ind_90_CNS.rda")
load("survival_model/adults_time_first_discharge_reg_elix_ind_90_PNS.rda")

# rename weight column
cns_weights <- adults_deceased_reg_elix_ind_90_CNS %>% 
  rbind(., adults_time_first_discharge_reg_elix_ind_90_CNS) %>% 
  rename("cns_weight" = Weight.random) %>% 
  select(-strata)

pns_weights <- adults_deceased_reg_elix_ind_90_PNS %>% 
  rbind(., adults_time_first_discharge_reg_elix_ind_90_PNS) %>% 
  rename("pns_weight" = Weight.random) %>% 
  select(-strata) 

## average the weights
avg_weights <- cns_weights %>% 
  left_join(., pns_weights, by = c("analysis", "studlab")) %>% 
  mutate(cns_weight = as.numeric(cns_weight),
         pns_weight = as.numeric(pns_weight),
         avg_weight = rowMeans(cbind(cns_weight, pns_weight), na.rm = TRUE)) %>% 
  rename("outcome" = analysis,
         "site" = studlab)

meta_surv_df <- res_dat_surv %>%
  left_join(., avg_weights, by = c("site", "outcome")) %>%
  ungroup() %>%  
  as.data.frame() %>%  
  mutate(
    outcome = factor(outcome, levels = outcomes) %>%
      fct_recode(
        "Mortality" = "deceased_reg_elix",
        "Discharge" = "time_first_discharge_reg_elix"
      )) %>% 
  ungroup()

Generate Survival Curves

Random effects meta analysis function

# chuan's code

## y is the vector of beta coefficients; 
## s is the vector of SE of beta coefficients;
## wt is the weight (e.g, number of events, make sure it does not change over time)

# Meg added the rule to handle cases when v.between is a negative number based on the following reference which explains: 
# https://bookdown.org/MathiasHarrer/Doing_Meta_Analysis_in_R/heterogeneity.html
# the value of I2 cannot be lower than 0%, so if Q happens to be smaller than K-1, we simply use 0 instead of a negative value.
metaFUN.randomeffect=function(y, s, wt){
  mm=c(y%*%wt/sum(wt))
  df=length(y)-1
  C=sum(wt)-sum((wt)^2)/sum(wt)
  Q=wt%*%(y-mm)^2
  k = length(y)
  if (Q < k-1) {
  v.between=0
} else {
  v.between=c((Q-df)/C); v.between # variance between (tau^2)
}
  ww=1/(s^2+v.between) # random-effects weight
  ss=sqrt(1/sum(ww)) # sum of squares 
  ## MH addition based on link above and discussion of T^2 approximating X^2 distribution## 
  #ci_low = qchisq(0.025, df=df)
  #ci_high = qchisq(0.975, df=df)
  data.frame(pool_mean=mm, pool_se=ss) #pool_high = ci_high, tau2=v.between, ww=ww)
}

Calculate Confidence Intervals

meta_surv_ci_df <- meta_surv_df %>%
  group_by(strata, outcome, time) %>%
  mutate(
    der_std.err = std.err*surv, # average std.err of the cumulative hazard * average surv probability
    m_surv = metaFUN.randomeffect(surv, der_std.err, avg_weight)$pool_mean,
    m_se = metaFUN.randomeffect(surv, der_std.err, avg_weight)$pool_se,
    ci_l = m_surv - m_se*zval,
    ci_u = m_surv + m_se*zval) %>% 
  ungroup() %>% 
  distinct(outcome, strata, time, m_surv, m_se, ci_l, ci_u)

Analyze Survival Curves

surv_curves <- meta_surv_ci_df %>%
  ggplot(aes(
    x = time, y = m_surv,
    color = strata,
    fill = strata,
  )) +
  geom_line(aes()) +
  geom_ribbon(aes(ymin = ci_l, ymax = ci_u, fill = strata), alpha = 0.2, linetype = 0, show.legend = FALSE) + 
  labs(color = NULL, y = NULL) +
  scale_colour_manual(values = group.colors) + 
  scale_fill_manual(values = group.colors) + 
  facet_wrap(~outcome, ncol = 3) +
coord_cartesian(clip = "off", ylim = c(0, 1), expand = FALSE) +
  xlab("Time (days)") + 
  ylab("Event Probability") +
  scale_x_continuous(limits = c(0, 90), breaks = c(0, 30, 60, 90)) +
  theme_classic() + 
  labs(color = "Neurological Status",
       fill = "") + 
  theme(legend.position="top",
        legend.title = element_text(face = "bold"),
        legend.text = element_text(size = 10, face = "bold"),
        strip.text = element_text(color = "black", size = 12, face = "bold"),
        panel.spacing = unit(60, "pt"),
        axis.title.x = element_text(size = 13, face = "bold"),
        axis.title.y = element_text(size = 13, face = "bold"),
        axis.text.y = element_text(size = 10),
        axis.text.x = element_text(color = "black", size = 10 )); surv_curves

ggsave("figures/survival_curves.png", surv_curves, height = 5, width = 6)

Evaluate Time to Event

breaks_list <- list("Discharge" = c(0, 6, 8, 11, 30, 60, 90),
                    "Mortality" = c(0, 12, 24, 30, 60, 90))

# determine median time where at least 50% patients were discharged
median_data_discharge <- meta_surv_ci_df %>%
  group_by(strata, outcome) %>%
  filter(outcome == "Discharge") %>%
  summarize(median_time = min(time[m_surv <= 0.5])) %>%
  mutate(yend = 0.5,
         y = 0.5)
#determine median time where at least 10% patients died
median_data_mortality <- meta_surv_ci_df %>%
  group_by(strata, outcome) %>%
  filter(outcome == "Mortality") %>%
  summarize(median_time = min(time[m_surv <= 0.9])) %>%
  mutate(yend = .9,
         y = 0.9)

median_data <- rbind(median_data_discharge, median_data_mortality) %>%
  filter(!is.infinite(median_time))

# plot individual survival plots
discharge_plot <- meta_surv_ci_df %>%
  filter(outcome == 'Discharge') %>%
  ggplot(aes(
    x = time, y = m_surv,
    color = strata,
    fill = strata,
  )) +
  geom_line() +
  geom_ribbon(aes(ymin = ci_l, ymax = ci_u, fill = strata), alpha = 0.2, linetype = 0, show.legend = FALSE) +
  labs(color = NULL, y = NULL) +
  scale_colour_manual(values = group.colors) +
  scale_fill_manual(values = group.colors) +
  coord_cartesian(clip = "off", ylim = c(0, 1), expand = FALSE) +
  xlab("Time (days)") +
  ylab("Event Probability") +
  scale_x_continuous(limits = c(0, 90), breaks = breaks_list$Discharge) + #breaks = breaks_fun) +
  theme_classic() +
  labs(color = "Neurological Status",
       fill = "") +
  ggtitle("Discharge") +
  theme(plot.title = element_text(face = "bold", hjust=0.5, size = 16),
        legend.position="top",
        legend.title = element_text(face = "bold"),
        legend.text = element_text(size = 8, face = "bold"),
        strip.text = element_text(color = "black", size = 12, face = "bold"),
        panel.spacing = unit(60, "pt"),
        axis.title.x = element_text(size = 13, face = "bold"),
        axis.title.y = element_text(size = 13, face = "bold"),
        axis.text.y = element_text(size = 10),
                axis.text.x = element_text(
          color = c("black","#BBBBBC", "slateblue", "tomato", "black", "black", "black"), 
          size = 10, 
          face = c("bold", "plain", "plain", "plain", "bold", "bold", "bold"))
        ) +
  geom_segment(
    aes(x = median_time, xend = median_time, y = 0, yend = yend, color = strata),
    data = median_data %>% filter(outcome == "Discharge"),
    linetype = "dashed",
    size = 0.5
  ) +
  geom_segment(
    aes(x = median_time, xend = 0, y = y, yend = yend, color = strata),
    data = median_data %>% filter(outcome == "Discharge"),
    linetype = "dashed",
    size = 0.5
  )

mortality_plot <- meta_surv_ci_df %>%
  filter(outcome == 'Mortality') %>%
  ggplot(aes(
    x = time, y = m_surv,
    color = strata,
    fill = strata,
  )) +
  geom_line() +
  geom_ribbon(aes(ymin = ci_l, ymax = ci_u, fill = strata), alpha = 0.2, linetype = 0, show.legend = FALSE) +
  labs(color = NULL, y = NULL) +
  scale_colour_manual(values = group.colors) +
  scale_fill_manual(values = group.colors) +
  coord_cartesian(clip = "off", ylim = c(0, 1), expand = FALSE) +
  xlab("Time (days)") +
  ylab("Event Probability") +
  scale_x_continuous(limits = c(0, 90), breaks = breaks_list$Mortality) + #breaks = breaks_fun) +
  theme_classic() +
  labs(color = "Neurological Status",
       fill = "") +
  ggtitle("Mortality") +
  theme(plot.title = element_text(face = "bold", hjust=0.5, size = 16),
        legend.position="none",
        legend.title = element_text(face = "bold"),
        legend.text = element_text(size = 10, face = "bold"),
        strip.text = element_text(color = "black", size = 12, face = "bold"),
        panel.spacing = unit(60, "pt"),
        axis.title.x = element_text(size = 13, face = "bold"),
        axis.title.y = element_text(size = 13, face = "bold"),
        axis.text.y = element_text(size = 10),
        axis.text.x = element_text(
          color = c("black", "tomato", "#BBBBBC", "black", "black", "black"), 
          size = 10, 
          face = c("bold", "plain", "plain", "bold", "bold", "bold"))
        ) +
  geom_segment(
    aes(x = median_time, xend = median_time, y = 0, yend = yend, color = strata),
    data = median_data %>% filter(outcome == "Mortality"),
    linetype = "dashed",
    size = 0.5
  ) +
  geom_segment(
    aes(x = median_time, xend = 0, y = y, yend = yend, color = strata),
    data = median_data %>% filter(outcome == "Mortality"),
    linetype = "dashed",
    size = 0.5
  )

# combine plots into one figure
survival_curves_time <- ggarrange(discharge_plot, mortality_plot, ncol=2, nrow=1, common.legend = TRUE, legend="top") %>%
  ggexport(filename = "figures/survival_curves_time.pdf")


ggsave("figures/survival_curves_times.png", ggarrange(discharge_plot, mortality_plot, ncol=2, nrow=1, common.legend = TRUE, legend="top"), height = 4, width = 8)

ggarrange(discharge_plot, mortality_plot, ncol=2, nrow=1, common.legend = TRUE, legend="top")

Risk Table

risk_table <- list()

for (outcome_i in outcomes) {
  risk_table[[outcome_i]] <-
    adult_results %>% 
    lapply(get_risk_table, population = "adults", comorb_method = "ind", censor_cutoff = "90", outcome = outcome_i) %>%
    bind_rows() %>%
    mutate(outcome = outcome_i) 
}


# tidy table
risk_table_tidy <- bind_rows(risk_table) %>% 
  mutate(
    outcome = factor(outcome, levels = outcomes) %>%
      fct_recode(
        "Mortality" = "deceased_reg_elix",
        "Discharge" = "time_first_discharge_reg_elix"
      ),
    strata = factor(strata) %>% 
      fct_recode(
        "NNC" = "neuro_post=None",
        "CNS" = "neuro_post=Central",
        "PNS" = "neuro_post=Peripheral"
      )
    
  ) %>% 
  # in some cases, sites did not have patients past 60 days -- thus the cumulative at T=90 is off - i need to carry forward the previous sum.event and n.risk?
  complete(time, site, nesting(strata, outcome)) %>%
  arrange(outcome, site, strata)  %>% 
  fill(n.risk, cum.n.event, .direction = "down") %>% 
  group_by(outcome, strata, time) %>% 
  mutate(sum.n.risk = sum(n.risk, na.rm = TRUE), 
         sum.event = sum(cum.n.event, na.rm = TRUE),
         sum.censor = sum(cum.n.censor, na.rm = TRUE)) %>% 
  ungroup() %>% 
  distinct(outcome, time, strata, sum.n.risk, sum.event, sum.censor)



write.csv(risk_table_tidy, "tables/risk_table_90_ind.csv", row.names = FALSE)
datatable(risk_table_tidy)
LS0tDQp0aXRsZTogIk1ldGEtQW5hbHlzaXM6IEtNIFN1cnZpdmFsIEN1cnZlcyINCmRhdGU6ICIwNC8xMS8yMDIzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICB0aGVtZTogc3BhY2VsYWINCi0tLQ0KDQpUaGlzIG5vdGVib29rIGNvbmR1Y3RzIGFuZCBkZW1vbnN0cmF0ZXMgdGhlIHJlc3VsdHMgb2YgdGhlIFJhbmRvbS1lZmZlY3RzIG1ldGEtYW5hbHlzaXMgcGVyZm9ybWVkIG9uIGVhY2ggaGVhbHRoY2FyZSBzeXN0ZW0ncyBsb2NhbGx5IHJ1biBjb3ZhcmlhdGUgYWRqdXN0ZWQgS2FwbGFuLU1laWVyIHN1cnZpdmFsIGN1cnZlcy4NCg0KT2Ygbm90ZSwgdGhpcyBhbmFseXNpcyBpcyBwZXJmb3JtZWQgb25seSBvbiBhZHVsdCBwYXRpZW50cyBkdWUgdG8gdGhlIHBlZGlhdHJpYyBjb2hvcnQncyBsb3cgaW5jaWRlbmNlIG9mIGJvdGggcG9vciBoZWFsdGggb3V0Y29tZXMgYW5kIG5ldXJvbG9naWNhbCBkaWFnbm9zZXMgZHVyaW5nIENPVklELTE5IGhvc3BpdGFsaXphdGlvbi4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtZXRhZm9yKQ0KbGlicmFyeShtZXRhKQ0KbGlicmFyeShlZ2cpDQpsaWJyYXJ5KERUKQ0KbGlicmFyeShnZ3B1YnIpDQpzb3VyY2UoIlIvZm9yZXN0ZXJfY3VzdG9tLlIiKQ0Kc291cmNlKCJSL2FuYWx5emVfc3Vydml2YWwuUiIpDQpgYGANCg0KIyAqKkltcG9ydCBEYXRhIGZyb20gZWFjaCBIZWFsdGhjYXJlIHN5c3RlbSoqDQoNCkVhY2ggcGFydGljaXBhdGluZyBoZWFsdGhjYXJlIHN5c3RlbSByYW4gdGhlIGFuYWx5c2lzIGxvY2FsbHkgb24gcGF0aWVudCBsZXZlbCBkYXRhIHVzaW5nIG91ciBbY3VzdG9taXplZCBSIHBhY2thZ2VdKGh0dHBzOi8vZ2l0aHViLmNvbS9jb3ZpZGNsaW5pY2FsL1BoYXNlMi4xTmV1cm9SUGFja2FnZSkuIFRoZSBvdXRwdXQgb2YgZWFjaCBsb2NhbCBhbmFseXNpcyBjb25zaXN0ZWQgb2Ygb25seSB0aGUgc3VtbWFyeSByZXN1bHRzIChyYXRoZXIgdGhhbiBwYXRpZW50IGxldmVsIGRhdGEpLiBUaGUgbW9kZWwgc3VtbWFyeSByZXN1bHRzIGZyb20gZWFjaCBoZWFsdGhjYXJlIHN5c3RlbSB3ZXJlIHRoZW4gYWdncmVnYXRlZCBhbmQgYXJlIGltcG9ydGVkIGFuZCB0aGVuIGNvbWJpbmVkIHVzaW5nIHRoZSBmb2xsb3dpbmcgY29kZToNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgcmVhZCBpbiBmaWxlcyBmcm9tIHJlc3VsdHMgZm9sZGVyIA0KIyB0aGlzIGZvbGRlciBjb250YWlucyBhbGwgb2YgdGhlIGxvY2FsIGhlYWx0aGNhcmUgc3lzdGVtIGxldmVsIGFuYWx5c2VzDQpyZGFzIDwtIGxpc3QuZmlsZXMoDQogIHBhdGggPSAicmVzdWx0cyIsDQogIHBhdHRlcm4gPSAiLnJkYSIsDQogIGZ1bGwubmFtZXMgPSBUUlVFDQopDQpmb3IgKHJkYSBpbiByZGFzKSB7DQogIGxvYWQocmRhKQ0KfQ0KDQpybShyZGFzLCByZGEpDQoNCiMgY3JlYXRlIGEgbGlzdCBvZiBwYXJ0aWNpcGF0aW5nIGhlYWx0aGNhcmUgc3lzdGVtcyBmcm9tIG91ciBzdHVkeSB0cmFja2luZyBzcHJlYWRzaGVldA0Kc2l0ZV9nb29nbGVfdXJsIDwtICJodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9zcHJlYWRzaGVldHMvZC8xZXBjWU5kXzBqQ1VNa3RPSGY4bXo1djY1MXp5MUpBTEQ2UGd6b2JyR1dEWS9lZGl0P3VzcD1zaGFyaW5nIg0KDQojIGxvYWQgc2l0ZSBwYXJhbWV0ZXJzDQpzaXRlX3BhcmFtcyA8LSBnb29nbGVzaGVldHM0OjpyZWFkX3NoZWV0KHNpdGVfZ29vZ2xlX3VybCwgc2hlZXQgPSAxKQ0Kc2l0ZV9hdmFpbHMgPC0gZ29vZ2xlc2hlZXRzNDo6cmVhZF9zaGVldChzaXRlX2dvb2dsZV91cmwsIHNoZWV0ID0gMikNCg0KIyBmaWx0ZXIgdGhlIGxpc3Qgb2Ygc2l0ZXMgd2hvIHJhbiB0aGUgYW5hbHlzaXMNCnNvcnRlZF9zaXRlcyA8LSBzaXRlX2F2YWlscyAlPiUNCiAgZmlsdGVyKCFpcy5uYShkYXRlX3Y0X3JlY2VpdmVkKSkgJT4lDQogIHB1bGwoc2l0ZWlkKSAlPiUNCiAgcGFzdGUoInJlc3VsdHMiLCBzZXAgPSAiXyIpDQoNCiMgbGlzdCBzaXRlcyB3aXRob3V0IHJhY2UNCnNpdGVzX3dvX3JhY2UgPC0gc2l0ZV9wYXJhbXMgJT4lDQogIGZpbHRlcighaW5jbHVkZV9yYWNlKSAlPiUNCiAgcHVsbChzaXRlaWQpDQoNCiMgY29tYmluZSBhbGwgcmRhIGZpbGVzIHdpdGggJ3Jlc3VsdHMnIGluIG5hbWUNCnJlc3VsdHMgPC0gbWdldChscyhwYXR0ZXJuID0gInJlc3VsdHMiKSkNCg0KIyMgcmVhZCBpbiBwdCBjb3VudHMNCnB0X2NvdW50c19kZiA8LSByZWFkLmNzdigndGFibGVzL3NpdGVfcHRfY291bnRzLmNzdicpDQpgYGANCg0KIyAqKlByZS1wcm9jZXNzaW5nKioNCg0KIyMgRGVmaW5lIG91dGNvbWUgcGFyYW1ldGVycw0KDQpgYGB7cn0NCm91dGNvbWVzIDwtDQogIGMoDQogICAgInRpbWVfZmlyc3RfZGlzY2hhcmdlX3JlZ19lbGl4IiwNCiAgICAiZGVjZWFzZWRfcmVnX2VsaXgiDQogICkNCmBgYA0KDQojIyBJZGVudGlmeSBzaXRlcyB0byBpbmNsdWRlIGluIHRoZSBhbmFseXNpcw0KDQpJbiB0aGlzIGFuYWx5c2lzLCB3ZSBvbmx5IGluY2x1ZGVkIGEgaGVhbHRoY2FyZSBzeXN0ZW0gaWYgdGhleSBoYWQgXD49IDMgYWR1bHQgcGF0aWVudHMgd2l0aCBhIG5ldXJvbG9naWNhbCBkaWFnbm9zaXMuDQoNCmBgYHtyfQ0KIyMgYWR1bHRzDQpzaXRlc19hZHVsdCA8LSBwdF9jb3VudHNfZGYgJT4lIA0KICBmaWx0ZXIocG9wdWxhdGlvbiA9PSAiQWR1bHQiKSAlPiUgDQogIG11dGF0ZShuZXVyb19zdW0gPSBuX3Zhcl9DZW50cmFsICsgbl92YXJfUGVyaXBoZXJhbCkgJT4lIA0KICBmaWx0ZXIoIW5ldXJvX3N1bSA8IDMpICU+JSANCiAgZGlzdGluY3Qoc2l0ZSkgJT4lIA0KICBtdXRhdGUoc2l0ZSA9IGdzdWIoIl9yZXN1bHRzIiwgIiIsIHNpdGUpKQ0KDQpzaXRlc19hZHVsdF9yZXN1bHRzIDwtIHNpdGVzX2FkdWx0ICU+JSANCiAgbXV0YXRlKHNpdGUgPSBwYXN0ZTAoc2l0ZSwgIl9yZXN1bHRzIikpICU+JSANCiAgYXMudmVjdG9yKCkNCg0KYWR1bHRfcmVzdWx0cyA8LSByZXN1bHRzW2dyZXBsKHBhc3RlKHNpdGVzX2FkdWx0X3Jlc3VsdHMkc2l0ZSwgY29sbGFwc2UgPSAifCIpLCBuYW1lcyhyZXN1bHRzKSldDQpgYGANCg0KIyAqKlJhbmRvbS1FZmZlY3RzIE1ldGEtQW5hbHlzaXMqKg0KDQojIyBHZXQgQ294IG1vZGVsIHJlc3VsdHMgDQoNClRoaXMgbm90ZWJvb2sgc3BlY2lmaWVzIHRoZSBwYXJhbWV0ZXJzIHRvIGxvYWQgaW4gdGhlIENveC1QSCByZXN1bHRzIHVzaW5nIGEgYGNlbnNvcl9jdXRvZmZgPSc5MCcgKGRheXMgc2luY2UgaW5pdGlhbCBob3NwaXRhbGl6YXRpb24pIGFuZCB0aGUgYGNvbW9yYl9tZXRob2RgPSdpbmQnIHdoaWNoIGFkanVzdHMgZm9yIHBhdGllbnQgY29tb3JiaWRpdHkgYnVyZGVuIGJ5IHRyZWF0aW5nIGVhY2ggaW5kaXZpZHVhbCBjb21vcmJpZGl0eSBvZiB0aGUgRWxpeGhhdXNlciBDb21vcmJpZGl0eSBJbmRleCBhcyBhbiBpbmRpdmlkdWFsIG1vZGVsIGNvdmFyaWF0ZSAoYmluYXJ5IHZhcmlhYmxlIGluZGljYXRpbmcgd2hldGhlciBvciBub3QgdGhlIHBhdGllbnQgcHJldmlvdXNseSB3YXMgZGlhZ25vc2VkIHdpdGggdGhlIHJlc3BlY3RpdmUgY29tb3JiaWRpdHkpLg0KDQpgYGB7cn0NCmNveF9yZXN1bHRzX2FkdWx0cyA8LSBsaXN0KCkNCg0KZm9yIChvdXRjb21lX2kgaW4gb3V0Y29tZXMpIHsNCiAgY294X3Jlc3VsdHNfYWR1bHRzW1tvdXRjb21lX2ldXSA8LQ0KICAgIGFkdWx0X3Jlc3VsdHMgJT4lDQogICAgbGFwcGx5KGdldF9jb3hfcm93LCBwb3B1bGF0aW9uID0gImFkdWx0cyIsIGNvbW9yYl9tZXRob2QgPSAiaW5kIiwgY2Vuc29yX2N1dG9mZiA9ICI5MCIsIG91dGNvbWUgPSBvdXRjb21lX2kpICU+JQ0KICAgIGJpbmRfcm93cygpICU+JQ0KICAgIG11dGF0ZShvdXRjb21lID0gb3V0Y29tZV9pKQ0KfQ0KYGBgDQoNCiMjIEdldCBhZGp1c3RlZCBLTSByZXN1bHRzIC0gQWR1bHRzDQoNClNpbWlsYXJseSwgd2Ugd2lsbCB3aWxsIGxvYWQgaW4gdGhlIEtNIHJlc3VsdHMgd2hpbGUgc3BlY2lmeWluZyB0aGUgYGNlbnNvcl9jdXRvZmZgPSc5MCcgYW5kIGBjb21vcmJfbWV0aG9kYD0naW5kJy4NCg0KYGBge3IgaW5jbHVkZT1GQUxTRX0NCmttX3Jlc3VsdHNfYWR1bHRzIDwtIGxpc3QoKQ0KDQpmb3IgKG91dGNvbWVfaSBpbiBvdXRjb21lcykgew0KICBrbV9yZXN1bHRzX2FkdWx0c1tbb3V0Y29tZV9pXV0gPC0NCiAgICBhZHVsdF9yZXN1bHRzICU+JSANCiAgICBsYXBwbHkoZ2V0X2ttX3JvdywgcG9wdWxhdGlvbiA9ICJhZHVsdHMiLCBjb21vcmJfbWV0aG9kID0gImluZCIsIGNlbnNvcl9jdXRvZmYgPSAiOTAiLCBvdXRjb21lID0gb3V0Y29tZV9pKSAlPiUNCiAgICBiaW5kX3Jvd3MoKSAlPiUNCiAgICBtdXRhdGUob3V0Y29tZSA9IG91dGNvbWVfaSkNCn0NCmBgYA0KDQojIyBGb3JtYXQgc3Vydml2YWwgcmVzdWx0cw0KDQpgYGB7cn0NCnJlc19kYXRfc3VydiA8LSBiaW5kX3Jvd3Moa21fcmVzdWx0c19hZHVsdHMpICU+JQ0KICBhZGRfY291bnQoc2l0ZSwgc3RyYXRhLCBvdXRjb21lLCBuYW1lID0gIm5fdGltZXBvaW50cyIpICU+JQ0KICAjIGFkZCByb3dzIHRvIGZpbGwgaW4gbWlzc2luZyB0aW1lIHBvaW50cyAodGhpcyBlbnN1cmVzIGFsbCBzaXRlcyB3aWxsIGhhdmUgdXAgdG8gdGhlIGNlbnNvciBkYXRlKQ0KICBjb21wbGV0ZSh0aW1lLCBzaXRlLCBuZXN0aW5nKHN0cmF0YSwgb3V0Y29tZSkpICU+JQ0KICBncm91cF9ieShzaXRlLCBzdHJhdGEsIG91dGNvbWUpICU+JQ0KICBhcnJhbmdlKHRpbWUpICU+JSANCiAgIyBjYXJyeSBkb3duIHRoZSBzdXJ2aXZhbCBhbmQgc3RhbmRhcmQgZXJyb3JzIHRvIGZpbGwgaW4gbWlzc2luZyB0aW1lIHBlcmlvZHMNCiAgZmlsbChzdXJ2LCAuZGlyZWN0aW9uID0gImRvd24iKSAlPiUNCiAgZmlsbChzdGQuZXJyLnNxcnQsIG5fdGltZXBvaW50cywgLmRpcmVjdGlvbiA9ICJkb3duIikgJT4lDQogIGZpbGwoc3RkLmVyciwgbl90aW1lcG9pbnRzLCAuZGlyZWN0aW9uID0gImRvd24iKSAlPiUNCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKA0KICAgIHN0cmF0YSA9IHRvdXBwZXIoc3RyYXRhKSwNCiAgICBzdHJhdGEgPSBmYWN0b3Ioc3RyYXRhLA0KICAgICAgICAgICAgICAgICAgICBsZXZlbHM9YygiTk9ORSIsICJDTlMiLCAiUE5TIiksDQogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJOTkMiLCAiQ05TIiwgIlBOUyIpKQ0KICAgICkNCmBgYA0KDQojIyBTZXQgc3Vydml2YWwgcGxvdCBwYXJhbXMNCg0KSGVyZSB3ZSB3aWxsIHNwZWNpZnkgc2V2ZXJhbCBwYXJhbWV0ZXJzIHRvIGFpZCBpbiBvdXIgY29uc3RydWN0aW9uIG9mIHRoZSBzdXJ2aXZhbCBmaWd1cmUNCg0KYGBge3J9DQojIGNhbGN1bGF0ZSBDSXMNCnp2YWwgPC0gcW5vcm0oMS0gKDEtMC45NSkvMiwgMCwgMSkNCnN0YW5kYXJkX2Vycm9yIDwtIGZ1bmN0aW9uKHgpIHNkKHgsIG5hLnJtID0gVFJVRSkgLyBzcXJ0KGxlbmd0aCh4KSkgIyBDcmVhdGUgb3duIGZ1bmN0aW9uDQoNCiMgc2V0IHBsb3QgY29sb3JzDQpncm91cC5jb2xvcnMgPC0gYyhOTkMgPSAiI0JCQkJCQyIsIFBOUyA9ICJzbGF0ZWJsdWUiLCBDTlMgPSJ0b21hdG8iKQ0KYGBgDQoNCiMjIFdlaWdodCBlYWNoIHNpdGUgDQoNCkhlcmUgd2Ugd2lsbCB3ZWlnaHQgZWFjaCBzaXRlIGJ5IHRoZSBpbnZlcnNlIG9mIGl0cyB2YXJpYW5jZSBjYWxjdWxhdGVkIGZyb20gdGhlIENOUyBhbmQgUE5TIG1ldGEtYW5hbHlzaXMgbW9kZWxzLiBXZSB3aWxsIHRha2UgdGhlIGF2ZXJhZ2Ugb2YgdGhlIENOUyBhbmQgUE5TIHdlaWdodHMNCg0KYGBge3J9DQpsb2FkKCJzdXJ2aXZhbF9tb2RlbC9hZHVsdHNfZGVjZWFzZWRfcmVnX2VsaXhfaW5kXzkwX0NOUy5yZGEiKQ0KbG9hZCgic3Vydml2YWxfbW9kZWwvYWR1bHRzX2RlY2Vhc2VkX3JlZ19lbGl4X2luZF85MF9QTlMucmRhIikNCg0KbG9hZCgic3Vydml2YWxfbW9kZWwvYWR1bHRzX3RpbWVfZmlyc3RfZGlzY2hhcmdlX3JlZ19lbGl4X2luZF85MF9DTlMucmRhIikNCmxvYWQoInN1cnZpdmFsX21vZGVsL2FkdWx0c190aW1lX2ZpcnN0X2Rpc2NoYXJnZV9yZWdfZWxpeF9pbmRfOTBfUE5TLnJkYSIpDQoNCiMgcmVuYW1lIHdlaWdodCBjb2x1bW4NCmNuc193ZWlnaHRzIDwtIGFkdWx0c19kZWNlYXNlZF9yZWdfZWxpeF9pbmRfOTBfQ05TICU+JSANCiAgcmJpbmQoLiwgYWR1bHRzX3RpbWVfZmlyc3RfZGlzY2hhcmdlX3JlZ19lbGl4X2luZF85MF9DTlMpICU+JSANCiAgcmVuYW1lKCJjbnNfd2VpZ2h0IiA9IFdlaWdodC5yYW5kb20pICU+JSANCiAgc2VsZWN0KC1zdHJhdGEpDQoNCnBuc193ZWlnaHRzIDwtIGFkdWx0c19kZWNlYXNlZF9yZWdfZWxpeF9pbmRfOTBfUE5TICU+JSANCiAgcmJpbmQoLiwgYWR1bHRzX3RpbWVfZmlyc3RfZGlzY2hhcmdlX3JlZ19lbGl4X2luZF85MF9QTlMpICU+JSANCiAgcmVuYW1lKCJwbnNfd2VpZ2h0IiA9IFdlaWdodC5yYW5kb20pICU+JSANCiAgc2VsZWN0KC1zdHJhdGEpIA0KDQojIyBhdmVyYWdlIHRoZSB3ZWlnaHRzDQphdmdfd2VpZ2h0cyA8LSBjbnNfd2VpZ2h0cyAlPiUgDQogIGxlZnRfam9pbiguLCBwbnNfd2VpZ2h0cywgYnkgPSBjKCJhbmFseXNpcyIsICJzdHVkbGFiIikpICU+JSANCiAgbXV0YXRlKGNuc193ZWlnaHQgPSBhcy5udW1lcmljKGNuc193ZWlnaHQpLA0KICAgICAgICAgcG5zX3dlaWdodCA9IGFzLm51bWVyaWMocG5zX3dlaWdodCksDQogICAgICAgICBhdmdfd2VpZ2h0ID0gcm93TWVhbnMoY2JpbmQoY25zX3dlaWdodCwgcG5zX3dlaWdodCksIG5hLnJtID0gVFJVRSkpICU+JSANCiAgcmVuYW1lKCJvdXRjb21lIiA9IGFuYWx5c2lzLA0KICAgICAgICAgInNpdGUiID0gc3R1ZGxhYikNCg0KbWV0YV9zdXJ2X2RmIDwtIHJlc19kYXRfc3VydiAlPiUNCiAgbGVmdF9qb2luKC4sIGF2Z193ZWlnaHRzLCBieSA9IGMoInNpdGUiLCAib3V0Y29tZSIpKSAlPiUNCiAgdW5ncm91cCgpICU+JSAgDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgIA0KICBtdXRhdGUoDQogICAgb3V0Y29tZSA9IGZhY3RvcihvdXRjb21lLCBsZXZlbHMgPSBvdXRjb21lcykgJT4lDQogICAgICBmY3RfcmVjb2RlKA0KICAgICAgICAiTW9ydGFsaXR5IiA9ICJkZWNlYXNlZF9yZWdfZWxpeCIsDQogICAgICAgICJEaXNjaGFyZ2UiID0gInRpbWVfZmlyc3RfZGlzY2hhcmdlX3JlZ19lbGl4Ig0KICAgICAgKSkgJT4lIA0KICB1bmdyb3VwKCkNCmBgYA0KDQojICoqR2VuZXJhdGUgU3Vydml2YWwgQ3VydmVzKioNCg0KKipSYW5kb20gZWZmZWN0cyBtZXRhIGFuYWx5c2lzIGZ1bmN0aW9uKioNCg0KYGBge3J9DQojIGNodWFuJ3MgY29kZQ0KDQojIyB5IGlzIHRoZSB2ZWN0b3Igb2YgYmV0YSBjb2VmZmljaWVudHM7IA0KIyMgcyBpcyB0aGUgdmVjdG9yIG9mIFNFIG9mIGJldGEgY29lZmZpY2llbnRzOw0KIyMgd3QgaXMgdGhlIHdlaWdodCAoZS5nLCBudW1iZXIgb2YgZXZlbnRzLCBtYWtlIHN1cmUgaXQgZG9lcyBub3QgY2hhbmdlIG92ZXIgdGltZSkNCg0KIyBNZWcgYWRkZWQgdGhlIHJ1bGUgdG8gaGFuZGxlIGNhc2VzIHdoZW4gdi5iZXR3ZWVuIGlzIGEgbmVnYXRpdmUgbnVtYmVyIGJhc2VkIG9uIHRoZSBmb2xsb3dpbmcgcmVmZXJlbmNlIHdoaWNoIGV4cGxhaW5zOiANCiMgaHR0cHM6Ly9ib29rZG93bi5vcmcvTWF0aGlhc0hhcnJlci9Eb2luZ19NZXRhX0FuYWx5c2lzX2luX1IvaGV0ZXJvZ2VuZWl0eS5odG1sDQojIHRoZSB2YWx1ZSBvZiBJMiBjYW5ub3QgYmUgbG93ZXIgdGhhbiAwJSwgc28gaWYgUSBoYXBwZW5zIHRvIGJlIHNtYWxsZXIgdGhhbiBLLTEsIHdlIHNpbXBseSB1c2UgMCBpbnN0ZWFkIG9mIGEgbmVnYXRpdmUgdmFsdWUuDQptZXRhRlVOLnJhbmRvbWVmZmVjdD1mdW5jdGlvbih5LCBzLCB3dCl7DQogIG1tPWMoeSUqJXd0L3N1bSh3dCkpDQogIGRmPWxlbmd0aCh5KS0xDQogIEM9c3VtKHd0KS1zdW0oKHd0KV4yKS9zdW0od3QpDQogIFE9d3QlKiUoeS1tbSleMg0KICBrID0gbGVuZ3RoKHkpDQogIGlmIChRIDwgay0xKSB7DQogIHYuYmV0d2Vlbj0wDQp9IGVsc2Ugew0KICB2LmJldHdlZW49YygoUS1kZikvQyk7IHYuYmV0d2VlbiAjIHZhcmlhbmNlIGJldHdlZW4gKHRhdV4yKQ0KfQ0KICB3dz0xLyhzXjIrdi5iZXR3ZWVuKSAjIHJhbmRvbS1lZmZlY3RzIHdlaWdodA0KICBzcz1zcXJ0KDEvc3VtKHd3KSkgIyBzdW0gb2Ygc3F1YXJlcyANCiAgIyMgTUggYWRkaXRpb24gYmFzZWQgb24gbGluayBhYm92ZSBhbmQgZGlzY3Vzc2lvbiBvZiBUXjIgYXBwcm94aW1hdGluZyBYXjIgZGlzdHJpYnV0aW9uIyMgDQogICNjaV9sb3cgPSBxY2hpc3EoMC4wMjUsIGRmPWRmKQ0KICAjY2lfaGlnaCA9IHFjaGlzcSgwLjk3NSwgZGY9ZGYpDQogIGRhdGEuZnJhbWUocG9vbF9tZWFuPW1tLCBwb29sX3NlPXNzKSAjcG9vbF9oaWdoID0gY2lfaGlnaCwgdGF1Mj12LmJldHdlZW4sIHd3PXd3KQ0KfQ0KYGBgDQoNCiMjIENhbGN1bGF0ZSBDb25maWRlbmNlIEludGVydmFscw0KDQpgYGB7cn0NCm1ldGFfc3Vydl9jaV9kZiA8LSBtZXRhX3N1cnZfZGYgJT4lDQogIGdyb3VwX2J5KHN0cmF0YSwgb3V0Y29tZSwgdGltZSkgJT4lDQogIG11dGF0ZSgNCiAgICBkZXJfc3RkLmVyciA9IHN0ZC5lcnIqc3VydiwgIyBhdmVyYWdlIHN0ZC5lcnIgb2YgdGhlIGN1bXVsYXRpdmUgaGF6YXJkICogYXZlcmFnZSBzdXJ2IHByb2JhYmlsaXR5DQogICAgbV9zdXJ2ID0gbWV0YUZVTi5yYW5kb21lZmZlY3Qoc3VydiwgZGVyX3N0ZC5lcnIsIGF2Z193ZWlnaHQpJHBvb2xfbWVhbiwNCiAgICBtX3NlID0gbWV0YUZVTi5yYW5kb21lZmZlY3Qoc3VydiwgZGVyX3N0ZC5lcnIsIGF2Z193ZWlnaHQpJHBvb2xfc2UsDQogICAgY2lfbCA9IG1fc3VydiAtIG1fc2UqenZhbCwNCiAgICBjaV91ID0gbV9zdXJ2ICsgbV9zZSp6dmFsKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIGRpc3RpbmN0KG91dGNvbWUsIHN0cmF0YSwgdGltZSwgbV9zdXJ2LCBtX3NlLCBjaV9sLCBjaV91KQ0KYGBgDQoNCiMjIEFuYWx5emUgU3Vydml2YWwgQ3VydmVzDQoNCmBgYHtyfQ0Kc3Vydl9jdXJ2ZXMgPC0gbWV0YV9zdXJ2X2NpX2RmICU+JQ0KICBnZ3Bsb3QoYWVzKA0KICAgIHggPSB0aW1lLCB5ID0gbV9zdXJ2LA0KICAgIGNvbG9yID0gc3RyYXRhLA0KICAgIGZpbGwgPSBzdHJhdGEsDQogICkpICsNCiAgZ2VvbV9saW5lKGFlcygpKSArDQogIGdlb21fcmliYm9uKGFlcyh5bWluID0gY2lfbCwgeW1heCA9IGNpX3UsIGZpbGwgPSBzdHJhdGEpLCBhbHBoYSA9IDAuMiwgbGluZXR5cGUgPSAwLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIA0KICBsYWJzKGNvbG9yID0gTlVMTCwgeSA9IE5VTEwpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBncm91cC5jb2xvcnMpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGdyb3VwLmNvbG9ycykgKyANCiAgZmFjZXRfd3JhcCh+b3V0Y29tZSwgbmNvbCA9IDMpICsNCmNvb3JkX2NhcnRlc2lhbihjbGlwID0gIm9mZiIsIHlsaW0gPSBjKDAsIDEpLCBleHBhbmQgPSBGQUxTRSkgKw0KICB4bGFiKCJUaW1lIChkYXlzKSIpICsgDQogIHlsYWIoIkV2ZW50IFByb2JhYmlsaXR5IikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA5MCksIGJyZWFrcyA9IGMoMCwgMzAsIDYwLCA5MCkpICsNCiAgdGhlbWVfY2xhc3NpYygpICsgDQogIGxhYnMoY29sb3IgPSAiTmV1cm9sb2dpY2FsIFN0YXR1cyIsDQogICAgICAgZmlsbCA9ICIiKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIsDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJibGFjayIsIHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLnNwYWNpbmcgPSB1bml0KDYwLCAicHQiKSwNCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMywgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJibGFjayIsIHNpemUgPSAxMCApKTsgc3Vydl9jdXJ2ZXMNCg0KZ2dzYXZlKCJmaWd1cmVzL3N1cnZpdmFsX2N1cnZlcy5wbmciLCBzdXJ2X2N1cnZlcywgaGVpZ2h0ID0gNSwgd2lkdGggPSA2KQ0KYGBgDQoNCiMjIEV2YWx1YXRlIFRpbWUgdG8gRXZlbnQNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmJyZWFrc19saXN0IDwtIGxpc3QoIkRpc2NoYXJnZSIgPSBjKDAsIDYsIDgsIDExLCAzMCwgNjAsIDkwKSwNCiAgICAgICAgICAgICAgICAgICAgIk1vcnRhbGl0eSIgPSBjKDAsIDEyLCAyNCwgMzAsIDYwLCA5MCkpDQoNCiMgZGV0ZXJtaW5lIG1lZGlhbiB0aW1lIHdoZXJlIGF0IGxlYXN0IDUwJSBwYXRpZW50cyB3ZXJlIGRpc2NoYXJnZWQNCm1lZGlhbl9kYXRhX2Rpc2NoYXJnZSA8LSBtZXRhX3N1cnZfY2lfZGYgJT4lDQogIGdyb3VwX2J5KHN0cmF0YSwgb3V0Y29tZSkgJT4lDQogIGZpbHRlcihvdXRjb21lID09ICJEaXNjaGFyZ2UiKSAlPiUNCiAgc3VtbWFyaXplKG1lZGlhbl90aW1lID0gbWluKHRpbWVbbV9zdXJ2IDw9IDAuNV0pKSAlPiUNCiAgbXV0YXRlKHllbmQgPSAwLjUsDQogICAgICAgICB5ID0gMC41KQ0KI2RldGVybWluZSBtZWRpYW4gdGltZSB3aGVyZSBhdCBsZWFzdCAxMCUgcGF0aWVudHMgZGllZA0KbWVkaWFuX2RhdGFfbW9ydGFsaXR5IDwtIG1ldGFfc3Vydl9jaV9kZiAlPiUNCiAgZ3JvdXBfYnkoc3RyYXRhLCBvdXRjb21lKSAlPiUNCiAgZmlsdGVyKG91dGNvbWUgPT0gIk1vcnRhbGl0eSIpICU+JQ0KICBzdW1tYXJpemUobWVkaWFuX3RpbWUgPSBtaW4odGltZVttX3N1cnYgPD0gMC45XSkpICU+JQ0KICBtdXRhdGUoeWVuZCA9IC45LA0KICAgICAgICAgeSA9IDAuOSkNCg0KbWVkaWFuX2RhdGEgPC0gcmJpbmQobWVkaWFuX2RhdGFfZGlzY2hhcmdlLCBtZWRpYW5fZGF0YV9tb3J0YWxpdHkpICU+JQ0KICBmaWx0ZXIoIWlzLmluZmluaXRlKG1lZGlhbl90aW1lKSkNCg0KIyBwbG90IGluZGl2aWR1YWwgc3Vydml2YWwgcGxvdHMNCmRpc2NoYXJnZV9wbG90IDwtIG1ldGFfc3Vydl9jaV9kZiAlPiUNCiAgZmlsdGVyKG91dGNvbWUgPT0gJ0Rpc2NoYXJnZScpICU+JQ0KICBnZ3Bsb3QoYWVzKA0KICAgIHggPSB0aW1lLCB5ID0gbV9zdXJ2LA0KICAgIGNvbG9yID0gc3RyYXRhLA0KICAgIGZpbGwgPSBzdHJhdGEsDQogICkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IGNpX2wsIHltYXggPSBjaV91LCBmaWxsID0gc3RyYXRhKSwgYWxwaGEgPSAwLjIsIGxpbmV0eXBlID0gMCwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBsYWJzKGNvbG9yID0gTlVMTCwgeSA9IE5VTEwpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBncm91cC5jb2xvcnMpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZ3JvdXAuY29sb3JzKSArDQogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gIm9mZiIsIHlsaW0gPSBjKDAsIDEpLCBleHBhbmQgPSBGQUxTRSkgKw0KICB4bGFiKCJUaW1lIChkYXlzKSIpICsNCiAgeWxhYigiRXZlbnQgUHJvYmFiaWxpdHkiKSArDQogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDkwKSwgYnJlYWtzID0gYnJlYWtzX2xpc3QkRGlzY2hhcmdlKSArICNicmVha3MgPSBicmVha3NfZnVuKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIGxhYnMoY29sb3IgPSAiTmV1cm9sb2dpY2FsIFN0YXR1cyIsDQogICAgICAgZmlsbCA9ICIiKSArDQogIGdndGl0bGUoIkRpc2NoYXJnZSIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdD0wLjUsIHNpemUgPSAxNiksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbj0idG9wIiwNCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiYmxhY2siLCBzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5zcGFjaW5nID0gdW5pdCg2MCwgInB0IiksDQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEzLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgNCiAgICAgICAgICBjb2xvciA9IGMoImJsYWNrIiwiI0JCQkJCQyIsICJzbGF0ZWJsdWUiLCAidG9tYXRvIiwgImJsYWNrIiwgImJsYWNrIiwgImJsYWNrIiksIA0KICAgICAgICAgIHNpemUgPSAxMCwgDQogICAgICAgICAgZmFjZSA9IGMoImJvbGQiLCAicGxhaW4iLCAicGxhaW4iLCAicGxhaW4iLCAiYm9sZCIsICJib2xkIiwgImJvbGQiKSkNCiAgICAgICAgKSArDQogIGdlb21fc2VnbWVudCgNCiAgICBhZXMoeCA9IG1lZGlhbl90aW1lLCB4ZW5kID0gbWVkaWFuX3RpbWUsIHkgPSAwLCB5ZW5kID0geWVuZCwgY29sb3IgPSBzdHJhdGEpLA0KICAgIGRhdGEgPSBtZWRpYW5fZGF0YSAlPiUgZmlsdGVyKG91dGNvbWUgPT0gIkRpc2NoYXJnZSIpLA0KICAgIGxpbmV0eXBlID0gImRhc2hlZCIsDQogICAgc2l6ZSA9IDAuNQ0KICApICsNCiAgZ2VvbV9zZWdtZW50KA0KICAgIGFlcyh4ID0gbWVkaWFuX3RpbWUsIHhlbmQgPSAwLCB5ID0geSwgeWVuZCA9IHllbmQsIGNvbG9yID0gc3RyYXRhKSwNCiAgICBkYXRhID0gbWVkaWFuX2RhdGEgJT4lIGZpbHRlcihvdXRjb21lID09ICJEaXNjaGFyZ2UiKSwNCiAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLA0KICAgIHNpemUgPSAwLjUNCiAgKQ0KDQptb3J0YWxpdHlfcGxvdCA8LSBtZXRhX3N1cnZfY2lfZGYgJT4lDQogIGZpbHRlcihvdXRjb21lID09ICdNb3J0YWxpdHknKSAlPiUNCiAgZ2dwbG90KGFlcygNCiAgICB4ID0gdGltZSwgeSA9IG1fc3VydiwNCiAgICBjb2xvciA9IHN0cmF0YSwNCiAgICBmaWxsID0gc3RyYXRhLA0KICApKSArDQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBjaV9sLCB5bWF4ID0gY2lfdSwgZmlsbCA9IHN0cmF0YSksIGFscGhhID0gMC4yLCBsaW5ldHlwZSA9IDAsIHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgbGFicyhjb2xvciA9IE5VTEwsIHkgPSBOVUxMKSArDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gZ3JvdXAuY29sb3JzKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGdyb3VwLmNvbG9ycykgKw0KICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICJvZmYiLCB5bGltID0gYygwLCAxKSwgZXhwYW5kID0gRkFMU0UpICsNCiAgeGxhYigiVGltZSAoZGF5cykiKSArDQogIHlsYWIoIkV2ZW50IFByb2JhYmlsaXR5IikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA5MCksIGJyZWFrcyA9IGJyZWFrc19saXN0JE1vcnRhbGl0eSkgKyAjYnJlYWtzID0gYnJlYWtzX2Z1bikgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICBsYWJzKGNvbG9yID0gIk5ldXJvbG9naWNhbCBTdGF0dXMiLA0KICAgICAgIGZpbGwgPSAiIikgKw0KICBnZ3RpdGxlKCJNb3J0YWxpdHkiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3Q9MC41LCBzaXplID0gMTYpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb249Im5vbmUiLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiYmxhY2siLCBzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5zcGFjaW5nID0gdW5pdCg2MCwgInB0IiksDQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEzLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoDQogICAgICAgICAgY29sb3IgPSBjKCJibGFjayIsICJ0b21hdG8iLCAiI0JCQkJCQyIsICJibGFjayIsICJibGFjayIsICJibGFjayIpLCANCiAgICAgICAgICBzaXplID0gMTAsIA0KICAgICAgICAgIGZhY2UgPSBjKCJib2xkIiwgInBsYWluIiwgInBsYWluIiwgImJvbGQiLCAiYm9sZCIsICJib2xkIikpDQogICAgICAgICkgKw0KICBnZW9tX3NlZ21lbnQoDQogICAgYWVzKHggPSBtZWRpYW5fdGltZSwgeGVuZCA9IG1lZGlhbl90aW1lLCB5ID0gMCwgeWVuZCA9IHllbmQsIGNvbG9yID0gc3RyYXRhKSwNCiAgICBkYXRhID0gbWVkaWFuX2RhdGEgJT4lIGZpbHRlcihvdXRjb21lID09ICJNb3J0YWxpdHkiKSwNCiAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLA0KICAgIHNpemUgPSAwLjUNCiAgKSArDQogIGdlb21fc2VnbWVudCgNCiAgICBhZXMoeCA9IG1lZGlhbl90aW1lLCB4ZW5kID0gMCwgeSA9IHksIHllbmQgPSB5ZW5kLCBjb2xvciA9IHN0cmF0YSksDQogICAgZGF0YSA9IG1lZGlhbl9kYXRhICU+JSBmaWx0ZXIob3V0Y29tZSA9PSAiTW9ydGFsaXR5IiksDQogICAgbGluZXR5cGUgPSAiZGFzaGVkIiwNCiAgICBzaXplID0gMC41DQogICkNCg0KIyBjb21iaW5lIHBsb3RzIGludG8gb25lIGZpZ3VyZQ0Kc3Vydml2YWxfY3VydmVzX3RpbWUgPC0gZ2dhcnJhbmdlKGRpc2NoYXJnZV9wbG90LCBtb3J0YWxpdHlfcGxvdCwgbmNvbD0yLCBucm93PTEsIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBsZWdlbmQ9InRvcCIpICU+JQ0KICBnZ2V4cG9ydChmaWxlbmFtZSA9ICJmaWd1cmVzL3N1cnZpdmFsX2N1cnZlc190aW1lLnBkZiIpDQoNCg0KZ2dzYXZlKCJmaWd1cmVzL3N1cnZpdmFsX2N1cnZlc190aW1lcy5wbmciLCBnZ2FycmFuZ2UoZGlzY2hhcmdlX3Bsb3QsIG1vcnRhbGl0eV9wbG90LCBuY29sPTIsIG5yb3c9MSwgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIGxlZ2VuZD0idG9wIiksIGhlaWdodCA9IDQsIHdpZHRoID0gOCkNCg0KZ2dhcnJhbmdlKGRpc2NoYXJnZV9wbG90LCBtb3J0YWxpdHlfcGxvdCwgbmNvbD0yLCBucm93PTEsIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBsZWdlbmQ9InRvcCIpDQoNCmBgYA0KDQoNCiMjIFJpc2sgVGFibGUNCg0KYGBge3J9DQoNCnJpc2tfdGFibGUgPC0gbGlzdCgpDQoNCmZvciAob3V0Y29tZV9pIGluIG91dGNvbWVzKSB7DQogIHJpc2tfdGFibGVbW291dGNvbWVfaV1dIDwtDQogICAgYWR1bHRfcmVzdWx0cyAlPiUgDQogICAgbGFwcGx5KGdldF9yaXNrX3RhYmxlLCBwb3B1bGF0aW9uID0gImFkdWx0cyIsIGNvbW9yYl9tZXRob2QgPSAiaW5kIiwgY2Vuc29yX2N1dG9mZiA9ICI5MCIsIG91dGNvbWUgPSBvdXRjb21lX2kpICU+JQ0KICAgIGJpbmRfcm93cygpICU+JQ0KICAgIG11dGF0ZShvdXRjb21lID0gb3V0Y29tZV9pKSANCn0NCg0KDQojIHRpZHkgdGFibGUNCnJpc2tfdGFibGVfdGlkeSA8LSBiaW5kX3Jvd3Mocmlza190YWJsZSkgJT4lIA0KICBtdXRhdGUoDQogICAgb3V0Y29tZSA9IGZhY3RvcihvdXRjb21lLCBsZXZlbHMgPSBvdXRjb21lcykgJT4lDQogICAgICBmY3RfcmVjb2RlKA0KICAgICAgICAiTW9ydGFsaXR5IiA9ICJkZWNlYXNlZF9yZWdfZWxpeCIsDQogICAgICAgICJEaXNjaGFyZ2UiID0gInRpbWVfZmlyc3RfZGlzY2hhcmdlX3JlZ19lbGl4Ig0KICAgICAgKSwNCiAgICBzdHJhdGEgPSBmYWN0b3Ioc3RyYXRhKSAlPiUgDQogICAgICBmY3RfcmVjb2RlKA0KICAgICAgICAiTk5DIiA9ICJuZXVyb19wb3N0PU5vbmUiLA0KICAgICAgICAiQ05TIiA9ICJuZXVyb19wb3N0PUNlbnRyYWwiLA0KICAgICAgICAiUE5TIiA9ICJuZXVyb19wb3N0PVBlcmlwaGVyYWwiDQogICAgICApDQogICAgDQogICkgJT4lIA0KICAjIGluIHNvbWUgY2FzZXMsIHNpdGVzIGRpZCBub3QgaGF2ZSBwYXRpZW50cyBwYXN0IDYwIGRheXMgLS0gdGh1cyB0aGUgY3VtdWxhdGl2ZSBhdCBUPTkwIGlzIG9mZiAtIGkgbmVlZCB0byBjYXJyeSBmb3J3YXJkIHRoZSBwcmV2aW91cyBzdW0uZXZlbnQgYW5kIG4ucmlzaz8NCiAgY29tcGxldGUodGltZSwgc2l0ZSwgbmVzdGluZyhzdHJhdGEsIG91dGNvbWUpKSAlPiUNCiAgYXJyYW5nZShvdXRjb21lLCBzaXRlLCBzdHJhdGEpICAlPiUgDQogIGZpbGwobi5yaXNrLCBjdW0ubi5ldmVudCwgLmRpcmVjdGlvbiA9ICJkb3duIikgJT4lIA0KICBncm91cF9ieShvdXRjb21lLCBzdHJhdGEsIHRpbWUpICU+JSANCiAgbXV0YXRlKHN1bS5uLnJpc2sgPSBzdW0obi5yaXNrLCBuYS5ybSA9IFRSVUUpLCANCiAgICAgICAgIHN1bS5ldmVudCA9IHN1bShjdW0ubi5ldmVudCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgIHN1bS5jZW5zb3IgPSBzdW0oY3VtLm4uY2Vuc29yLCBuYS5ybSA9IFRSVUUpKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIGRpc3RpbmN0KG91dGNvbWUsIHRpbWUsIHN0cmF0YSwgc3VtLm4ucmlzaywgc3VtLmV2ZW50LCBzdW0uY2Vuc29yKQ0KDQoNCg0Kd3JpdGUuY3N2KHJpc2tfdGFibGVfdGlkeSwgInRhYmxlcy9yaXNrX3RhYmxlXzkwX2luZC5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQpgYGB7cn0NCmRhdGF0YWJsZShyaXNrX3RhYmxlX3RpZHkpDQpgYGANCg0K