Report timestamp: 2020-04-28 00:08:04


Reference Phabricator Ticket: T248308


Notebook: Optimizing WDQS Response Time From SPARQL Queries Analysis

Feedback should be send to goran.milovanovic_ext@wikimedia.de.

Summary

WMDE together with php.cc is trying to find a way to optimize the Wikidata Query Service. This Report provides (a) and Exploratory Data Analysis (EDA) approach to WDQS response times (Part A) and (2) describes a Machine Learning (ML) approach (Part B) to analyze the SPARQL queries that were received by the endpoint in the first three weeks of April 2020, with the goal of developing a model able to predict whether a query would be critical for WDQS/Blazegraph or not. The motivation for the ML approach to the problem is the following one: by singling out the most important features that are present in the SPARQL queries under analysis we hope to be able to understand better what types of queries typically cause problems.

This, Part A, of the Report, provides the Exploratory Data Analytics(EDA) on the WDQS query response times. We analyze the query response times across a range of variables obtained directly from the event.wdqs_external_sparql_query schema in the WMF Data Lake.

In the following sections we describe (1) the characteristics of the sample of SPARQL queries used in this study, (2) the overview of the number of queries run per (a) day of week, (b) hour of day, (c) WMF Datacenter/Host, (d) HTTP method of request, (e) server HTTP response code, and (f) the desired output format, (3) the mean and median WDQS query processing times across the mentioned (a) - (f) variables, and (4) the distributions of WDQS processing times across WMF Datacenter/Hosts and output format.

Summary

  • The eqiad data center is receiving tons of queries in comparison to codfw.
  • The XML output format seems to take much more to process in comparison to JSON and text/plain (except for we really have only few observations of text/plain in the sample).
  • The distributions of the WDQS processing time across the crucial variables (WMF Datacenter/Host, Output format) are highly skewed towards short processing times - so we really need to focus on the outliers seriously.

0. Sample of SPARQL queries observed at the WDQS SPARQL endpoint

The SPARQL queries that hit the endpoint are registered in the event.wdqs_external_sparql_query table in the WMF Data Lake (our Hadoop Big Data storage). A bucketing approach to sample data from this Hive table was taken, sampling approximately 1% of queries that were observed in the period between 2020/04/01 and 2020/04/21. The sample is approximately uniformly distributed across days and hours of the mentioned period of time. The sample size is slightly higher than 1M SPARQL queries - including duplicates.

The following fields were derived from the event.wdqs_external_sparql_query table for each collected SPARQL query:

  • ds_sparql - the SPARQL query
  • ds_dt - the query timestamp, later parsed into year, month, day, hour, and minute
  • ds_status_code - the server http status response for this query
  • ds_query_time - how many seconds did it took for the WDQS to process the query
  • ds_datacenter_host - a combination of the (a) WMF data center and (b) a back-end host in the respective data center
  • ds_format - content type (e.g. JSON, text/tsv, xml, etc.)
  • ds_method - HTTP method (e.g. GET, POST)
  • ds_n_conc_queries - for each SPARQL query, the number of SPARQL whose processing started in (1) the same minute, on (2) the same data center, and on (3) the same back-end host.

The following R script was used to obtain a sample of SPARQL queries from event.wdqs_external_sparql_query.

#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WDQS_Event_S1_ETL.R v 0.0.1
### --- Script: WDQS_Event_S1_ETL.R, v. Beta 0.1
### --- Description: WDQS_Event_S1_ETL.R collects a sample of 
### --- SPARQL queries from event.wdqs_external_sparql_query in the WMF Data Lake.
### --- Author: Goran S. Milovanovic, Data Scientist, WMDE
### --- Developed under the contract between Goran Milovanovic PR Data Kolektiv
### --- and WMDE.
### --- Contact: goran.milovanovic_ext@wikimedia.de
### ---------------------------------------------------------------------------
### --- RUN FROM: stat1005
### --- Date: 2020/04/22
### ---------------------------------------------------------------------------

### ---------------------------------------------------------------------------
### --- LICENSE:
### ---------------------------------------------------------------------------
### --- GPL v2
### --- This file is part of the Wikidata SPARQL Endpoint Analytics Project
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is free software: 
### --- you can redistribute it and/or modify
### --- it under the terms of the GNU General Public License as published by
### --- the Free Software Foundation, either version 2 of the License, or
### --- (at your option) any later version.
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is distributed 
### --- in the hope that it will be useful,
### --- but WITHOUT ANY WARRANTY; without even the implied warranty of
### --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### --- GNU General Public License for more details.
### ---
### --- You should have received a copy of the GNU General Public License
### --- along with Wikidata SPARQL Endpoint Analytics Project.
### --- If not, see <http://www.gnu.org/licenses/>.
### ---------------------------------------------------------------------------

### ---------------------------------------------------------------------------
### --- Section 1. Collect raw data set event.wdqs_external_sparql_query
### ---------------------------------------------------------------------------

### --- setup
library(data.table)
library(tidyverse)

### --- directories
### --- Read WDCM paramereters
# - fPath: where the scripts is run from?
fPath <- as.character(commandArgs(trailingOnly = F)[4])
fPath <- gsub("--file=", "", fPath, fixed = T)
fPath <- unlist(strsplit(fPath, split = "/", fixed = T))
fPath <- paste(
  paste(fPath[1:length(fPath) - 1], collapse = "/"),
  "/",
  sep = "")
fPath <- '/home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics/'
dataDir <- paste0(fPath, '_event/_data/')
analyticsDir <- paste0(fPath, '_event/_analytics/')

### --- grab a sample of SPARQL queries event.wdqs_external_sparql_query
### --- w. HiveQL from Beeline
# - query filename
queryFile <- "wdqs_CollectData.hql"
# - Kerberos Init
kerberosPrefix <- 'sudo -u analytics-privatedata kerberos-run-command analytics-privatedata '
# - dateRange
startDate <- '2020/04/01'
endDate <- '2020/04/21'
dateRange <- seq(as.Date(startDate), as.Date(endDate), by = "day")
# - query over dateRange
for (i in 1:length(dateRange)) {
  print(paste0("Started: ", dateRange[i]))
  # - year, month, date
  year = substr(dateRange[i], 1, 4)
  month = substr(dateRange[i], 6, 7)
  if (substr(month, 1, 1) == '0') {month = substr(month, 2, 2)}
  day = substr(dateRange[i], 9, 10)
  if (substr(day, 1, 1) == '0') {day = substr(day, 2, 2)}
  # - compose query
  hiveQLquery <- paste0("SELECT meta.dt as dt, 
                          regexp_replace(
                            regexp_replace(
                              regexp_replace(query, '\\n|\\r\\n|\\r|\\n\\r', ' __NEWLINE__ '),
                            '\\t', ' '),
                          '\\000', ' ') as sparql, 
                          format, meta.domain as domain, 
                          http.request_headers['X-BIGDATA-MAX-QUERY-MILLIS'] as max_query_millis, http.method as method, 
                          http.status_code as status_code, 
                          backend_host, datacenter, query_time 
                          FROM event.wdqs_external_sparql_query TABLESAMPLE (BUCKET 1 OUT OF 100 ON rand()) w
                          WHERE isnotnull(http.status_code) 
                          AND (meta.domain = 'query.wikidata.org') 
                          AND year=", year,
                        " AND month=", month,
                        " AND day=", day, ';')
  filename <- "wdqs_data_sample_TEST_"
  filename <- paste0(filename, dateRange[i], ".tsv")
  # - write hql
  write(hiveQLquery, paste0(fPath, queryFile))
  # - to Report
  print("Fetching sparql_testData from wmf.webrequest now.")
  # - Run query
  query <- system(command = paste(kerberosPrefix, 
                                  '/usr/local/bin/beeline --incremental=true --silent -f "',
                                  paste0(fPath, queryFile),
                                  '" > ', dataDir, filename,
                                  sep = ""),
                  wait = TRUE)
  print(paste0("Completed: ", dateRange[i]))
  print("--------------------------------------------")
}
# - to Report
print("DONE w. ETL from Hadoop: wmf.webrequest.")

1. Exploratory Data Analytics

All of the following analytics were derived from the datasets produced by the following R script.

#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WDQS_Event_S5_ReportingDatasets.R, v. 0.0.1
### --- Script: WDQS_Event_S5_ReportingDatasets.R, v. 0.0.1
### --- Description: WDQS_Event_S5_ReportingDatasets.R is used
### --- only to produce the reporting datasets for the 
### --- Wikidata SPARQL Endpoint Analytics Project.
### --- All data are obtained from the event.wdqs_external_sparql_query 
### --- table in the WMF Data Lake.
### --- Author: Goran S. Milovanovic, Data Scientist, WMDE
### --- Developed under the contract between Goran Milovanovic PR Data Kolektiv
### --- and WMDE.
### --- Contact: goran.milovanovic_ext@wikimedia.de
### ---------------------------------------------------------------------------
### --- RUN FROM: stat1005
### --- Date: 2020/04/27
### ---------------------------------------------------------------------------

### ---------------------------------------------------------------------------
### --- LICENSE:
### ---------------------------------------------------------------------------
### --- GPL v2
### --- This file is part of the Wikidata SPARQL Endpoint Analytics Project
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is free software: 
### --- you can redistribute it and/or modify
### --- it under the terms of the GNU General Public License as published by
### --- the Free Software Foundation, either version 2 of the License, or
### --- (at your option) any later version.
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is distributed 
### --- in the hope that it will be useful,
### --- but WITHOUT ANY WARRANTY; without even the implied warranty of
### --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### --- GNU General Public License for more details.
### ---
### --- You should have received a copy of the GNU General Public License
### --- along with Wikidata SPARQL Endpoint Analytics Project.
### --- If not, see <http://www.gnu.org/licenses/>.
### ---------------------------------------------------------------------------

### --- setup
library(data.table)
library(stringr)
library(dplyr)
library(tidyr)
library(httr)
library(pROC)
library(xgboost)
library(caret)
library(fastDummies)

### --- directories
### --- Read WDCM paramereters
# - fPath: where the scripts is run from?
fPath <- as.character(commandArgs(trailingOnly = FALSE)[4])
fPath <- gsub("--file=", "", fPath, fixed = T)
fPath <- unlist(strsplit(fPath, split = "/", fixed = T))
fPath <- paste(
  paste(fPath[1:length(fPath) - 1], collapse = "/"),
  "/",
  sep = "")

fPath <- '/home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics/'
# - NOTE: Hetzner local dir
#  - fPath <- '/home/goran/WMDE/WD_SPARQL_Endpoint_Analytics/'
dataDir <- paste0(fPath, '_event/_data/')
analyticsDir <- paste0(fPath, '_event/_analytics/')
reportingDir <- paste0(fPath, '_event/_reporting/')

### ---------------------------------------------------------------------------
### --- Section 1. Produce reporting dataset
### ---------------------------------------------------------------------------

### --- feature frequencies: vocabulary
filename <- 'queries_vocabulary.csv'
vocabulary <- fread(paste0(analyticsDir, filename))
vocabulary$V1 <- NULL

### --- dataset
filename <- 'wdqs_data_sample_2020-04-22.csv'
dataSet <- fread(paste0(analyticsDir, filename))
dataSet$V1 <- NULL
# - derive: n_conc_queries per datacenter x back_endhost
dataSet$datacenter_host <- paste(dataSet$datacenter,
                                 dataSet$backend_host, 
                                 sep = "_")
dataSet$datacenter <- NULL
dataSet$backend_host <- NULL
# - the number of queries started in the same minute
# - as the target query
# - per datacenter x back_endhost
dataSet$datacenter_host_ymdhm <- paste(dataSet$datacenter_host,
                                       dataSet$ymdhm, 
                                       sep = "_")
dataSet$ymdhm <- NULL
n_conc_queries <- dataSet %>% 
  dplyr::select(datacenter_host_ymdhm) %>% 
  dplyr::group_by(datacenter_host_ymdhm) %>% 
  dplyr::summarise(n_conc_queries = n())
dataSet <- dataSet %>% 
  dplyr::left_join(n_conc_queries, by = "datacenter_host_ymdhm")
dataSet$datacenter_host_ymdhm <- NULL
rm(n_conc_queries)
# - derive: mild and extreme outliers on query_time
# - query_time >= q3 + 3*iq 
# - q3 = 75 percentile, q1 = 25 percentile
q3 <- quantile(dataSet$query_time, .75)
q1 <- quantile(dataSet$query_time, .25)
# - iq = q3 - q1; Interquartile range
iq <- unname(q3 - q1)
upper_outer_fence <- q3 + 3*iq
upper_inner_fence <- q3 + 1.5*iq
dataSet$query_time_extreme <- 
  ifelse(dataSet$query_time >= upper_outer_fence, 1, 0)
dataSet$query_time_mild <- 
  ifelse(dataSet$query_time >= upper_inner_fence, 1, 0)
# - derive multiclass DV: 0 - mild, mild - extreme, extreme query_time
dataSet$query_time_class <- 0
dataSet$query_time_class[dataSet$query_time_extreme == 0 & dataSet$query_time_mild == 1] <- 1
dataSet$query_time_class[dataSet$query_time_extreme == 1] <- 2
# - derive: below and above median query_time
dataSet$query_time_median <- ifelse(dataSet$query_time >= median(dataSet$query_time), 1, 0)

### --- vocabulary: top_f most frequently used features
top_f <- 300
ob_features <- c('nchar', '__vars__', '__vars_usage__', '__literal__', 
                 '__vars_label__', '__vars_label_usage__', '__iri_ref__')
vocabulary <- vocabulary$feature[1:top_f]
vocabulary <- unique(c(vocabulary, ob_features))

### --- join vocabulary to dataSet
queries_processed <- fread(paste0(analyticsDir, 'queries_processed.csv'))
queries_processed$V1 <- NULL
dim(queries_processed)
queries_processed <- queries_processed %>% 
  dplyr::filter(feature %in% vocabulary)
rm(vocabulary)
dim(queries_processed)
featureFrame <- data.table::dcast(as.data.table(queries_processed),
                                  uniqueSparqlId ~ feature,
                                  value.var = "freq")
rm(queries_processed); gc()
# - mark features in dataSet: ds
colnames(dataSet) <- paste0("ds_", colnames(dataSet))
# - join:
dataSet <- dataSet %>% 
  dplyr::left_join(featureFrame, 
                   by = c('ds_uniqueSparqlId' = 'uniqueSparqlId'))
rm(featureFrame); gc()
colnames(dataSet)

# - store reporting data.frame: reportingFrame.csv
filename <- paste0('reportingFrame', '_', top_f, '.csv')
write.csv(dataSet,
          paste0(reportingDir, filename))

# - clear
rm(list = setdiff(ls(), c('reportingDir', 'analyticsDir', 'dataDir', 'fPath')))
gc()

### ---------------------------------------------------------------------------
### --- Section 2. Exploratory Data Analysis
### ---------------------------------------------------------------------------

### --- load reporting data.frame
top_f <- 300
filename <- paste0('reportingFrame', '_', top_f, '.csv')
reportFrame <- fread(paste0(reportingDir, filename))
reportFrame$V1 <- NULL

### --- reportFrame$recode ds_day to day of week
sampleDays <- ifelse(nchar(reportFrame$ds_day) == 1, 
                     paste0('0', reportFrame$ds_day),
                     reportFrame$ds_day)
reportFrame$ds_day <- paste0("2020-04-", sampleDays)
reportFrame$ds_day <- weekdays(as.Date(reportFrame$ds_day))
rm(sampleDays)

### --- Sample characteristics
# - external (non-SPARQL parsing based) fatures in the dataset
ds_features <- 
  colnames(reportFrame)[which(grepl("^ds_", colnames(reportFrame)))]

# - days
sampleDays <- as.data.frame(table(reportFrame$ds_day))
colnames(sampleDays) <- c('Day', 'Num.Queries')
sampleDays$Percent <- round(
  sampleDays$Num.Queries/sum(sampleDays$Num.Queries)*100, 2)
sampleDays <-dplyr::arrange(sampleDays, desc(Percent))
write.csv(sampleDays, paste0(reportingDir, 'sampleChar_Days.csv'))
# - hours
sampleHours <- as.data.frame(table(reportFrame$ds_hour))
colnames(sampleHours) <- c('Hour', 'Num.Queries')
sampleHours$Percent <- round(
  sampleHours$Num.Queries/sum(sampleHours$Num.Queries)*100, 2)
sampleHours <-dplyr::arrange(sampleHours, desc(Percent))
write.csv(sampleHours, paste0(reportingDir, 'sampleChar_Hours.csv'))
# - HTTP method
sampleMethod <- as.data.frame(table(reportFrame$ds_method))
colnames(sampleMethod) <- c('HTTP.method', 'Num.Queries')
sampleMethod$Percent <- round(
  sampleMethod$Num.Queries/sum(sampleMethod$Num.Queries)*100, 2)
sampleMethod <-dplyr::arrange(sampleMethod, desc(Percent))
write.csv(sampleMethod, paste0(reportingDir, 'sampleChar_HTTPmethod.csv'))
# - datacenter_host
sample_datacenter_host <- as.data.frame(table(reportFrame$ds_datacenter_host))
colnames(sample_datacenter_host) <- c('Datacenter.Host', 'Num.Queries')
sample_datacenter_host$Percent <- round(
  sample_datacenter_host$Num.Queries/sum(sample_datacenter_host$Num.Queries)*100, 2)
sample_datacenter_host <-dplyr::arrange(sample_datacenter_host, desc(Percent))
write.csv(sample_datacenter_host, paste0(reportingDir, 'sampleChar_DatacenterHost.csv'))
# - Status Code
sample_StatusCode <- as.data.frame(table(reportFrame$ds_status_code))
colnames(sample_StatusCode) <- c('Status', 'Num.Queries')
sample_StatusCode$Percent <- round(
  sample_StatusCode$Num.Queries/sum(sample_StatusCode$Num.Queries)*100, 2)
sample_StatusCode <-dplyr::arrange(sample_StatusCode, desc(Percent))
write.csv(sample_StatusCode, paste0(reportingDir, 'sampleChar_StatusCode.csv'))
# - Format
sample_Format <- as.data.frame(table(reportFrame$ds_format))
colnames(sample_Format) <- c('Format', 'Num.Queries')
sample_Format$Percent <- round(
  sample_Format$Num.Queries/sum(sample_Format$Num.Queries)*100, 2)
sample_Format <-dplyr::arrange(sample_Format, desc(Percent))
write.csv(sample_Format, paste0(reportingDir, 'sample_Format.csv'))
# - Distribution of the number of queries 
# - concurrently started in the same minute
reportFrame$ds_day_hour_minute <- paste(reportFrame$ds_day,
                                        reportFrame$ds_hour,
                                        reportFrame$ds_minute,
                                        sep = "-")
sample_datacenter_host_qrqueries <- reportFrame %>% 
  dplyr::select(ds_datacenter_host, ds_day_hour_minute) %>% 
  dplyr::group_by(ds_datacenter_host, ds_day_hour_minute) %>% 
  dplyr::summarise(query_count = n()) %>%
  dplyr::select(ds_datacenter_host, query_count) %>% 
  dplyr::group_by(ds_datacenter_host) %>% 
  dplyr::summarise(mean_conc_queries = mean(query_count), 
                   median_conc_queries = median(query_count), 
                   stdev_conc_queries = sd(query_count)) %>%
  dplyr::arrange(desc(mean_conc_queries))
write.csv(sample_datacenter_host_qrqueries, 
          paste0(reportingDir, 'sample_datacenter_host_qrqueries.csv'))
reportFrame$ds_day_hour_minute <- NULL

### --- Produce repeated queries dataset
repeatedQueries <- reportFrame %>% 
  dplyr::select(ds_uniqueSparqlId, 
                ds_sparql, 
                ds_query_time)
colnames(repeatedQueries)
repeatedQueries <- repeatedQueries %>% 
  dplyr::group_by(ds_uniqueSparqlId, ds_sparql) %>% 
  dplyr::summarise(num = n(),
                   mean_query_time = mean(ds_query_time), 
                   median_query_time = median(ds_query_time),
                   stdev_query_time = sd(ds_query_time))
repeatedQueries$Percent <- round(
  repeatedQueries$num/sum(repeatedQueries$num)*100, 6)
repeatedQueries <- dplyr::arrange(repeatedQueries, desc(num))
write.csv(repeatedQueries, 
          paste0(reportingDir, 'repeatedQueries.csv'))
rm(repeatedQueries); gc()

### --- Cross-Tabulation: ds_* features x query_time
# - ds_format
crossTabs_format <- reportFrame %>% 
  dplyr::select(ds_format, ds_query_time) %>% 
  dplyr::group_by(ds_format) %>% 
  dplyr::summarise(mean_query_time = mean(ds_query_time), 
                   median_query_time = median(ds_query_time), 
                   stdev_query_time = sd(ds_query_time)) %>% 
  dplyr::arrange(desc(mean_query_time))
write.csv(crossTabs_format, 
          paste0(reportingDir, 'crossTabs_format.csv'))
# - ds_day
crossTabs_day <- reportFrame %>% 
  dplyr::select(ds_day, ds_query_time) %>% 
  dplyr::group_by(ds_day) %>% 
  dplyr::summarise(mean_query_time = mean(ds_query_time), 
                   median_query_time = median(ds_query_time), 
                   stdev_query_time = sd(ds_query_time)) %>% 
  dplyr::arrange(desc(mean_query_time))
write.csv(crossTabs_day, 
          paste0(reportingDir, 'crossTabs_day.csv'))
# - ds_method
crossTabs_method <- reportFrame %>% 
  dplyr::select(ds_method, ds_query_time) %>% 
  dplyr::group_by(ds_method) %>% 
  dplyr::summarise(mean_query_time = mean(ds_query_time), 
                   median_query_time = median(ds_query_time), 
                   stdev_query_time = sd(ds_query_time)) %>% 
  dplyr::arrange(desc(mean_query_time))
write.csv(crossTabs_method, 
          paste0(reportingDir, 'crossTabs_method.csv'))
# - ds_datacenter_host
crossTabs_datacenter_host <- reportFrame %>% 
  dplyr::select(ds_datacenter_host, ds_query_time) %>% 
  dplyr::group_by(ds_datacenter_host) %>% 
  dplyr::summarise(mean_query_time = mean(ds_query_time), 
                   median_query_time = median(ds_query_time), 
                   stdev_query_time = sd(ds_query_time)) %>% 
  dplyr::arrange(desc(mean_query_time))
write.csv(crossTabs_datacenter_host, 
          paste0(reportingDir, 'crossTabs_datacenter_host.csv'))

# - ds_status_code
crossTabs_status_code <- reportFrame %>% 
  dplyr::select(ds_status_code, ds_query_time) %>% 
  dplyr::group_by(ds_status_code) %>% 
  dplyr::summarise(mean_query_time = mean(ds_query_time), 
                   median_query_time = median(ds_query_time), 
                   stdev_query_time = sd(ds_query_time)) %>% 
  dplyr::arrange(desc(mean_query_time))
write.csv(crossTabs_status_code, 
          paste0(reportingDir, 'crossTabs_status_code.csv'))

1.1 Sample Description

library(ggplot2)
library(ggrepel)
library(scales)

First we provide descriptive statistics for the most important variables in the data set.

1.1.1 The distribution of queries per day of week

pFrame <- read.csv('sampleChar_Days.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$Day <- factor(pFrame$Day,
                     levels = c('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'))
ggplot(data = pFrame, 
       aes(x = Day, 
           y = Num.Queries, 
           label = paste0(Percent, "%"))) + 
  geom_bar(width = .15, color = 'darkred', fill = 'red', stat = 'identity') + 
  geom_text_repel(size = 3) + 
  theme_bw()

1.1.2 The distribution of queries per hour of day

pFrame <- read.csv('sampleChar_Hours.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$Hour <- factor(pFrame$Hour,
                     levels = as.character(0:23))
ggplot(data = pFrame, 
       aes(x = Hour, 
           y = Num.Queries, 
           label = paste0(Percent, "%"))) + 
  geom_bar(width = .15, color = 'darkred', fill = 'red', stat = 'identity') + 
  geom_text_repel(size = 3) + 
  theme_bw()

1.1.3A The distribution of queries per WMF Datacenter/Host

pFrame <- read.csv('sampleChar_DatacenterHost.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$Datacenter.Host <- factor(pFrame$Datacenter.Host,
                     levels = unique(pFrame$Datacenter.Host))
ggplot(data = pFrame, 
       aes(x = Datacenter.Host, 
           y = Num.Queries, 
           label = paste0(Percent, "%"))) + 
  geom_bar(width = .15, color = 'darkgreen', fill = 'green', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  theme_bw() + 
  theme(axis.text.x =  element_text(angle = 90))

1.1.3B The mean and median number of queries concurrently started in the same minute, per WMF Datacenter/Host

pFrame <- read.csv('sample_datacenter_host_qrqueries.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$Datacenter.Host <- factor(pFrame$ds_datacenter_host,
                     levels = unique(pFrame$ds_datacenter_host))
ggplot(data = pFrame, 
       aes(x = ds_datacenter_host, 
           y = mean_conc_queries, 
           label = paste0("Med: ", median_conc_queries))) + 
  geom_bar(width = .15, color = 'darkgreen', fill = 'green', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  xlab('WMF Datacenter/Host') + ylab('Mean Response Time (secs)') +
  theme_bw() + 
  theme(axis.text.x =  element_text(angle = 90))

1.1.4 The distribution of queries per HTTP method of request

pFrame <- read.csv('sampleChar_HTTPmethod.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$HTTP.method <- factor(pFrame$HTTP.method,
                     levels = unique(pFrame$HTTP.method))
ggplot(data = pFrame, 
       aes(x = HTTP.method, 
           y = Num.Queries, 
           label = paste0(Percent, "%"))) + 
  geom_bar(width = .15, color = 'darkblue', fill = 'blue', stat = 'identity') + 
  geom_text_repel(size = 3) + 
  theme_bw()

1.1.5 The distribution of queries per server response code

pFrame <- read.csv('sampleChar_StatusCode.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$Status<- as.character(pFrame$Status)
ggplot(data = pFrame, 
       aes(x = Status, 
           y = Num.Queries, 
           label = paste0(Percent, "%"))) +
  scale_y_continuous(labels = comma) + 
  geom_bar(width = .15, color = 'darkorange', fill = 'orange', stat = 'identity') + 
  geom_text_repel(size = 3) + 
  theme_bw()

1.1.6 The distribution of queries per output format

pFrame <- read.csv('sample_Format.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$Format<- factor(pFrame$Format, 
                       levels = unique(pFrame$Format))
ggplot(data = pFrame, 
       aes(x = Format, 
           y = Num.Queries, 
           label = paste0(Percent, "%"))) + 
  geom_bar(width = .15, color = 'darkviolet', fill = 'violet', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  theme_bw()

1.2 WDQS processing times

1.2.1 Day of week vs. WDQS response time

pFrame <- read.csv('crossTabs_day.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$ds_day <- factor(pFrame$ds_day,
                        levels = c('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'))
ggplot(data = pFrame, 
       aes(x = ds_day, 
           y = mean_query_time, 
           label = paste0("Med: ", median_query_time))) + 
  geom_bar(width = .15, color = 'darkred', fill = 'red', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  xlab('Day') + ylab('Mean Query Time (secs)') +
  theme_bw()

1.2.2 Hour of day vs. WDQS response time

pFrame <- read.csv('crossTabs_hour.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$ds_hour <- factor(pFrame$ds_hour,
                     levels = as.character(0:23))
ggplot(data = pFrame, 
       aes(x = ds_hour, 
           y = mean_query_time, 
           label = paste0("Med: ", median_query_time))) + 
  geom_bar(width = .15, color = 'darkred', fill = 'red', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  xlab('Hour') + ylab('Mean Query Time (secs)') +
  theme_bw()

1.2.3 WMF Datacenter/Host vs. WDQS response time

pFrame <- read.csv('crossTabs_datacenter_host.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$ds_datacenter_host <- factor(pFrame$ds_datacenter_host,
                                 levels = unique(pFrame$ds_datacenter_host))
ggplot(data = pFrame, 
       aes(x = ds_datacenter_host, 
           y = mean_query_time, 
           label = paste0("Med: ", median_query_time))) + 
  geom_bar(width = .15, color = 'darkgreen', fill = 'green', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  xlab('WMF/Datacenter/Host') + ylab('Mean Query Time (secs)') +
  theme_bw() + 
  theme(axis.text.x =  element_text(angle = 90))

1.2.4 HTTP method of request vs. WDQS response time

pFrame <- read.csv('crossTabs_method.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$ds_method <- factor(pFrame$ds_method,
                           levels = unique(pFrame$ds_method))
ggplot(data = pFrame, 
       aes(x = ds_method, 
           y = mean_query_time, 
           label = paste0("Med: ", median_query_time))) + 
  geom_bar(width = .15, color = 'darkblue', fill = 'blue', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  xlab('HTTP method of request') + ylab('Mean Query Time (secs)') +
  theme_bw() + 
  theme(axis.text.x =  element_text(angle = 90))

1.2.5 Server response code vs. WDQS response time

pFrame <- read.csv('crossTabs_status_code.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$ds_status_code <- factor(pFrame$ds_status_code,
                                levels = unique(pFrame$ds_status_code))
ggplot(data = pFrame, 
       aes(x = ds_status_code, 
           y = mean_query_time, 
           label = paste0("Med: ", median_query_time))) + 
  geom_bar(width = .15, color = 'darkorange', fill = 'orange', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  xlab('Status Code') + ylab('Mean Query Time (secs)') +
  theme_bw() + 
  theme(axis.text.x =  element_text(angle = 90))

1.2.6 Output format vs. WDQS response time

pFrame <- read.csv('crossTabs_format.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
pFrame$ds_format <- factor(pFrame$ds_format,
                           levels = unique(pFrame$ds_format))
ggplot(data = pFrame, 
       aes(x = ds_format, 
           y = mean_query_time, 
           label = paste0("Med: ", median_query_time))) + 
  geom_bar(width = .15, color = 'darkviolet', fill = 'violet', stat = 'identity') + 
  scale_y_continuous(labels = comma) + 
  geom_text_repel(size = 3) + 
  xlab('Status Code') + ylab('Mean Query Time (secs)') +
  theme_bw() + 
  theme(axis.text.x =  element_text(angle = 90))

1.3 The distribution of WDQS processing times

1.3.1 The distribution of WDQS processing times across WMF Datacenter/Hosts

NOTE. Please mind that we use a logarithmic scale for the WDQS query processing times. The distributions are highly skewed on their natural scales.

pFrame <- read.csv('qtDistribution_DatacenterHost.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
ggplot(data = pFrame, 
       aes(x = log(ds_query_time))) +
  geom_density(size = .25, color = "darkgreen", fill="green", alpha = .2) +
  facet_wrap(~ds_datacenter_host) +
  xlab('log(WDQS processing time)') + ylab('Density') + 
  ylim(c(0, 1)) + 
  theme_bw()+ 
  theme(axis.text.x =  element_text(angle = 90))

1.3.2 The distribution of WDQS processing times across output formats

NOTE. The text/plain output format has two few observations.

pFrame <- read.csv('qtDistribution_Format.csv', 
                   header = T, 
                   row.names = 1, 
                   check.names = F,
                   stringsAsFactors = F)
ggplot(data = pFrame, 
       aes(x = log(ds_query_time))) +
  geom_density(size = .25, color = "darkviolet", fill="violet", alpha = .2) +
  facet_wrap(~ds_format) +
  xlab('log(WDQS processing time)') + ylab('Density') + 
  ylim(c(0, 1)) + 
  theme_bw()+ 
  theme(axis.text.x =  element_text(angle = 90))


Goran S. Milovanović

Wikimedia Deutschland, Data Scientist DataKolektiv, Owner

contact:

LS0tCnRpdGxlOiBXRFFTIEVuZHBvaW50IEFuYWx5dGljcyAtIE9wdGltaXppbmcgU2VydmVyIFJlc3BvbnNlIFRpbWUgLSBQYXJ0IEEgKEVEQSkKYXV0aG9yOgotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhwogIGFmZmlsaWF0aW9uOiBXaWtpbWVkaWEgRGV1dHNjaGxhbmQsIERhdGEgU2NpZW50aXN0LCBEYXRhS29sZWt0aXYsIE93bmVyCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDUKLS0tCioqKgoKKipSZXBvcnQgdGltZXN0YW1wOioqIGByIHtTeXMudGltZSgpfWAKCioqKgoKKipSZWZlcmVuY2UgUGhhYnJpY2F0b3IgVGlja2V0OioqIFtUMjQ4MzA4XShodHRwczovL3BoYWJyaWNhdG9yLndpa2ltZWRpYS5vcmcvVDI0ODMwOCkKCioqKgojIyMgTm90ZWJvb2s6IE9wdGltaXppbmcgV0RRUyBSZXNwb25zZSBUaW1lIEZyb20gU1BBUlFMIFF1ZXJpZXMgQW5hbHlzaXMKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlYC4gCgojIyAqKlN1bW1hcnkqKiAKCltXTURFXShodHRwczovL3d3dy53aWtpbWVkaWEuZGUvKSB0b2dldGhlciB3aXRoIFtwaHAuY2NdKGh0dHBzOi8vdGhlcGhwLmNjL3dlbGNvbWUpIGlzIHRyeWluZyB0byBmaW5kIGEgd2F5IHRvIG9wdGltaXplIHRoZSBbV2lraWRhdGEgUXVlcnkgU2VydmljZV0oaHR0cHM6Ly93d3cubWVkaWF3aWtpLm9yZy93aWtpL1dpa2lkYXRhX1F1ZXJ5X1NlcnZpY2UpLiBUaGlzIFJlcG9ydCBwcm92aWRlcyAoYSkgYW5kIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMgKEVEQSkgYXBwcm9hY2ggdG8gV0RRUyByZXNwb25zZSB0aW1lcyAoUGFydCBBKSBhbmQgKDIpIGRlc2NyaWJlcyBhIE1hY2hpbmUgTGVhcm5pbmcgKE1MKSBhcHByb2FjaCAoUGFydCBCKSB0byBhbmFseXplIHRoZSBTUEFSUUwgcXVlcmllcyB0aGF0IHdlcmUgcmVjZWl2ZWQgYnkgdGhlIGVuZHBvaW50IGluIHRoZSBmaXJzdCB0aHJlZSB3ZWVrcyBvZiBBcHJpbCAyMDIwLCB3aXRoIHRoZSBnb2FsIG9mIGRldmVsb3BpbmcgYSBtb2RlbCBhYmxlIHRvIHByZWRpY3Qgd2hldGhlciBhIHF1ZXJ5IHdvdWxkIGJlICoqY3JpdGljYWwqKiBmb3IgV0RRUy9CbGF6ZWdyYXBoIG9yIG5vdC4gVGhlIG1vdGl2YXRpb24gZm9yIHRoZSBNTCBhcHByb2FjaCB0byB0aGUgcHJvYmxlbSBpcyB0aGUgZm9sbG93aW5nIG9uZTogYnkgc2luZ2xpbmcgb3V0IHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlcyB0aGF0IGFyZSBwcmVzZW50IGluIHRoZSBTUEFSUUwgcXVlcmllcyB1bmRlciBhbmFseXNpcyB3ZSBob3BlIHRvIGJlIGFibGUgdG8gdW5kZXJzdGFuZCBiZXR0ZXIgd2hhdCB0eXBlcyBvZiBxdWVyaWVzIHR5cGljYWxseSBjYXVzZSBwcm9ibGVtcy4KCioqVGhpcywgUGFydCBBLCBvZiB0aGUgUmVwb3J0LCBwcm92aWRlcyB0aGUgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXRpY3MoRURBKSBvbiB0aGUgV0RRUyBxdWVyeSByZXNwb25zZSB0aW1lcy4gV2UgYW5hbHl6ZSB0aGUgcXVlcnkgcmVzcG9uc2UgdGltZXMgYWNyb3NzIGEgcmFuZ2Ugb2YgdmFyaWFibGVzIG9idGFpbmVkIGRpcmVjdGx5IGZyb20gdGhlIFtgZXZlbnQud2Rxc19leHRlcm5hbF9zcGFycWxfcXVlcnlgXShodHRwczovL2dlcnJpdC53aWtpbWVkaWEub3JnL3IvcGx1Z2lucy9naXRpbGVzL21lZGlhd2lraS9ldmVudC1zY2hlbWFzLysvbWFzdGVyL2pzb25zY2hlbWEvc3BhcnFsL3F1ZXJ5LzEuMC4wLnlhbWwpIHNjaGVtYSBpbiB0aGUgW1dNRiBEYXRhIExha2VdKGh0dHBzOi8vd2lraXRlY2gud2lraW1lZGlhLm9yZy93aWtpL0FuYWx5dGljcy9EYXRhX0xha2UpLioqCgpJbiB0aGUgZm9sbG93aW5nIHNlY3Rpb25zIHdlIGRlc2NyaWJlICoqKDEpKiogdGhlIGNoYXJhY3RlcmlzdGljcyBvZiB0aGUgc2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIHVzZWQgaW4gdGhpcyBzdHVkeSwgKiooMikqKiB0aGUgb3ZlcnZpZXcgb2YgdGhlIG51bWJlciBvZiBxdWVyaWVzIHJ1biBwZXIgKGEpIGRheSBvZiB3ZWVrLCAoYikgaG91ciBvZiBkYXksIChjKSBXTUYgRGF0YWNlbnRlci9Ib3N0LCAoZCkgSFRUUCBtZXRob2Qgb2YgcmVxdWVzdCwgKGUpIHNlcnZlciBIVFRQIHJlc3BvbnNlIGNvZGUsIGFuZCAoZikgdGhlIGRlc2lyZWQgb3V0cHV0IGZvcm1hdCwgKiooMykqKiB0aGUgbWVhbiBhbmQgbWVkaWFuIFdEUVMgcXVlcnkgcHJvY2Vzc2luZyB0aW1lcyBhY3Jvc3MgdGhlIG1lbnRpb25lZCAoYSkgLSAoZikgdmFyaWFibGVzLCBhbmQgKiooNCkqKiB0aGUgZGlzdHJpYnV0aW9ucyBvZiBXRFFTIHByb2Nlc3NpbmcgdGltZXMgYWNyb3NzIFdNRiBEYXRhY2VudGVyL0hvc3RzIGFuZCBvdXRwdXQgZm9ybWF0LgoKKipTdW1tYXJ5KiogCgoqIFRoZSBgZXFpYWRgIGRhdGEgY2VudGVyIGlzIHJlY2VpdmluZyB0b25zIG9mIHF1ZXJpZXMgaW4gY29tcGFyaXNvbiB0byBgY29kZndgLgoqIFRoZSBgWE1MYCBvdXRwdXQgZm9ybWF0IHNlZW1zIHRvIHRha2UgbXVjaCBtb3JlIHRvIHByb2Nlc3MgaW4gY29tcGFyaXNvbiB0byBgSlNPTmAgYW5kIGB0ZXh0L3BsYWluYCAoZXhjZXB0IGZvciB3ZSByZWFsbHkgaGF2ZSBvbmx5IGZldyBvYnNlcnZhdGlvbnMgb2YgYHRleHQvcGxhaW5gIGluIHRoZSBzYW1wbGUpLgoqIFRoZSBkaXN0cmlidXRpb25zIG9mIHRoZSBXRFFTIHByb2Nlc3NpbmcgdGltZSBhY3Jvc3MgdGhlIGNydWNpYWwgdmFyaWFibGVzIChXTUYgRGF0YWNlbnRlci9Ib3N0LCBPdXRwdXQgZm9ybWF0KSBhcmUgaGlnaGx5IHNrZXdlZCB0b3dhcmRzIHNob3J0IHByb2Nlc3NpbmcgdGltZXMgLSBzbyB3ZSByZWFsbHkgbmVlZCB0byBmb2N1cyBvbiB0aGUgb3V0bGllcnMgc2VyaW91c2x5LgoKCioqKgoKIyMgMC4gU2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIG9ic2VydmVkIGF0IHRoZSBXRFFTIFNQQVJRTCBlbmRwb2ludAoKVGhlIFNQQVJRTCBxdWVyaWVzIHRoYXQgaGl0IHRoZSBlbmRwb2ludCBhcmUgcmVnaXN0ZXJlZCBpbiB0aGUgYGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5YCB0YWJsZSBpbiB0aGUgV01GIERhdGEgTGFrZSAob3VyIEhhZG9vcCBCaWcgRGF0YSBzdG9yYWdlKS4gQSBbYnVja2V0aW5nIGFwcHJvYWNoXShodHRwczovL2N3aWtpLmFwYWNoZS5vcmcvY29uZmx1ZW5jZS9kaXNwbGF5L0hpdmUvTGFuZ3VhZ2VNYW51YWwrU2FtcGxpbmcpIHRvIHNhbXBsZSBkYXRhIGZyb20gdGhpcyBIaXZlIHRhYmxlIHdhcyB0YWtlbiwgc2FtcGxpbmcgYXBwcm94aW1hdGVseSBgMSVgIG9mIHF1ZXJpZXMgdGhhdCB3ZXJlIG9ic2VydmVkIGluIHRoZSBwZXJpb2QgYmV0d2VlbiBgMjAyMC8wNC8wMWAgYW5kIGAyMDIwLzA0LzIxYC4gVGhlIHNhbXBsZSBpcyBhcHByb3hpbWF0ZWx5IHVuaWZvcm1seSBkaXN0cmlidXRlZCBhY3Jvc3MgZGF5cyBhbmQgaG91cnMgb2YgdGhlIG1lbnRpb25lZCBwZXJpb2Qgb2YgdGltZS4gVGhlIHNhbXBsZSBzaXplIGlzIHNsaWdodGx5IGhpZ2hlciB0aGFuIGAxTWAgU1BBUlFMIHF1ZXJpZXMgLSBpbmNsdWRpbmcgZHVwbGljYXRlcy4KClRoZSBmb2xsb3dpbmcgZmllbGRzIHdlcmUgZGVyaXZlZCBmcm9tIHRoZSBgZXZlbnQud2Rxc19leHRlcm5hbF9zcGFycWxfcXVlcnlgIHRhYmxlIGZvciBlYWNoIGNvbGxlY3RlZCBTUEFSUUwgcXVlcnk6CgotIGBkc19zcGFycWxgIC0gdGhlIFNQQVJRTCBxdWVyeQotIGBkc19kdGAgLSB0aGUgcXVlcnkgdGltZXN0YW1wLCBsYXRlciBwYXJzZWQgaW50byBgeWVhcmAsIGBtb250aGAsIGBkYXlgLCBgaG91cmAsIGFuZCBgbWludXRlYAotIGBkc19zdGF0dXNfY29kZWAgLSB0aGUgc2VydmVyIGh0dHAgc3RhdHVzIHJlc3BvbnNlIGZvciB0aGlzIHF1ZXJ5Ci0gYGRzX3F1ZXJ5X3RpbWVgIC0gaG93IG1hbnkgc2Vjb25kcyBkaWQgaXQgdG9vayBmb3IgdGhlIFdEUVMgdG8gcHJvY2VzcyB0aGUgcXVlcnkKLSBgZHNfZGF0YWNlbnRlcl9ob3N0YCAtIGEgY29tYmluYXRpb24gb2YgdGhlIChhKSBXTUYgZGF0YSBjZW50ZXIgYW5kIChiKSBhIGJhY2stZW5kIGhvc3QgaW4gdGhlIHJlc3BlY3RpdmUgZGF0YSBjZW50ZXIKLSBgZHNfZm9ybWF0YCAtIGNvbnRlbnQgdHlwZSAoZS5nLiBKU09OLCB0ZXh0L3RzdiwgeG1sLCBldGMuKQotIGBkc19tZXRob2RgIC0gSFRUUCBtZXRob2QgKGUuZy4gR0VULCBQT1NUKQotIGBkc19uX2NvbmNfcXVlcmllc2AgLSBmb3IgZWFjaCBTUEFSUUwgcXVlcnksIHRoZSBudW1iZXIgb2YgU1BBUlFMIHdob3NlIHByb2Nlc3Npbmcgc3RhcnRlZCBpbiAoMSkgdGhlIHNhbWUgbWludXRlLCBvbiAoMikgdGhlIHNhbWUgZGF0YSBjZW50ZXIsIGFuZCBvbiAoMykgdGhlIHNhbWUgYmFjay1lbmQgaG9zdC4KClRoZSBmb2xsb3dpbmcgUiBzY3JpcHQgd2FzIHVzZWQgdG8gb2J0YWluIGEgc2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIGZyb20gYGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5YC4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gRiwgbWVzc2FnZSA9IEZ9CiMhL3Vzci9iaW4vZW52IFJzY3JpcHQKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBXRFFTX0V2ZW50X1MxX0VUTC5SIHYgMC4wLjEKIyMjIC0tLSBTY3JpcHQ6IFdEUVNfRXZlbnRfUzFfRVRMLlIsIHYuIEJldGEgMC4xCiMjIyAtLS0gRGVzY3JpcHRpb246IFdEUVNfRXZlbnRfUzFfRVRMLlIgY29sbGVjdHMgYSBzYW1wbGUgb2YgCiMjIyAtLS0gU1BBUlFMIHF1ZXJpZXMgZnJvbSBldmVudC53ZHFzX2V4dGVybmFsX3NwYXJxbF9xdWVyeSBpbiB0aGUgV01GIERhdGEgTGFrZS4KIyMjIC0tLSBBdXRob3I6IEdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERQojIyMgLS0tIERldmVsb3BlZCB1bmRlciB0aGUgY29udHJhY3QgYmV0d2VlbiBHb3JhbiBNaWxvdmFub3ZpYyBQUiBEYXRhIEtvbGVrdGl2CiMjIyAtLS0gYW5kIFdNREUuCiMjIyAtLS0gQ29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gUlVOIEZST006IHN0YXQxMDA1CiMjIyAtLS0gRGF0ZTogMjAyMC8wNC8yMgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gTElDRU5TRToKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIEdQTCB2MgojIyMgLS0tIFRoaXMgZmlsZSBpcyBwYXJ0IG9mIHRoZSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QKIyMjIC0tLQojIyMgLS0tIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdCBpcyBmcmVlIHNvZnR3YXJlOiAKIyMjIC0tLSB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3IgbW9kaWZ5CiMjIyAtLS0gaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkKIyMjIC0tLSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAyIG9mIHRoZSBMaWNlbnNlLCBvcgojIyMgLS0tIChhdCB5b3VyIG9wdGlvbikgYW55IGxhdGVyIHZlcnNpb24uCiMjIyAtLS0KIyMjIC0tLSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QgaXMgZGlzdHJpYnV0ZWQgCiMjIyAtLS0gaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwKIyMjIC0tLSBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZgojIyMgLS0tIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUKIyMjIC0tLSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgojIyMgLS0tCiMjIyAtLS0gWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UKIyMjIC0tLSBhbG9uZyB3aXRoIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdC4KIyMjIC0tLSBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTZWN0aW9uIDEuIENvbGxlY3QgcmF3IGRhdGEgc2V0IGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5CiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gc2V0dXAKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCiMjIyAtLS0gZGlyZWN0b3JpZXMKIyMjIC0tLSBSZWFkIFdEQ00gcGFyYW1lcmV0ZXJzCiMgLSBmUGF0aDogd2hlcmUgdGhlIHNjcmlwdHMgaXMgcnVuIGZyb20/CmZQYXRoIDwtIGFzLmNoYXJhY3Rlcihjb21tYW5kQXJncyh0cmFpbGluZ09ubHkgPSBGKVs0XSkKZlBhdGggPC0gZ3N1YigiLS1maWxlPSIsICIiLCBmUGF0aCwgZml4ZWQgPSBUKQpmUGF0aCA8LSB1bmxpc3Qoc3Ryc3BsaXQoZlBhdGgsIHNwbGl0ID0gIi8iLCBmaXhlZCA9IFQpKQpmUGF0aCA8LSBwYXN0ZSgKICBwYXN0ZShmUGF0aFsxOmxlbmd0aChmUGF0aCkgLSAxXSwgY29sbGFwc2UgPSAiLyIpLAogICIvIiwKICBzZXAgPSAiIikKZlBhdGggPC0gJy9ob21lL2dvcmFuc20vQW5hbHl0aWNzL1dpa2lkYXRhL1dEX21pc2MvV0RfU1BBUlFMX0VuZHBvaW50X0FuYWx5dGljcy8nCmRhdGFEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2V2ZW50L19kYXRhLycpCmFuYWx5dGljc0RpciA8LSBwYXN0ZTAoZlBhdGgsICdfZXZlbnQvX2FuYWx5dGljcy8nKQoKIyMjIC0tLSBncmFiIGEgc2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5CiMjIyAtLS0gdy4gSGl2ZVFMIGZyb20gQmVlbGluZQojIC0gcXVlcnkgZmlsZW5hbWUKcXVlcnlGaWxlIDwtICJ3ZHFzX0NvbGxlY3REYXRhLmhxbCIKIyAtIEtlcmJlcm9zIEluaXQKa2VyYmVyb3NQcmVmaXggPC0gJ3N1ZG8gLXUgYW5hbHl0aWNzLXByaXZhdGVkYXRhIGtlcmJlcm9zLXJ1bi1jb21tYW5kIGFuYWx5dGljcy1wcml2YXRlZGF0YSAnCiMgLSBkYXRlUmFuZ2UKc3RhcnREYXRlIDwtICcyMDIwLzA0LzAxJwplbmREYXRlIDwtICcyMDIwLzA0LzIxJwpkYXRlUmFuZ2UgPC0gc2VxKGFzLkRhdGUoc3RhcnREYXRlKSwgYXMuRGF0ZShlbmREYXRlKSwgYnkgPSAiZGF5IikKIyAtIHF1ZXJ5IG92ZXIgZGF0ZVJhbmdlCmZvciAoaSBpbiAxOmxlbmd0aChkYXRlUmFuZ2UpKSB7CiAgcHJpbnQocGFzdGUwKCJTdGFydGVkOiAiLCBkYXRlUmFuZ2VbaV0pKQogICMgLSB5ZWFyLCBtb250aCwgZGF0ZQogIHllYXIgPSBzdWJzdHIoZGF0ZVJhbmdlW2ldLCAxLCA0KQogIG1vbnRoID0gc3Vic3RyKGRhdGVSYW5nZVtpXSwgNiwgNykKICBpZiAoc3Vic3RyKG1vbnRoLCAxLCAxKSA9PSAnMCcpIHttb250aCA9IHN1YnN0cihtb250aCwgMiwgMil9CiAgZGF5ID0gc3Vic3RyKGRhdGVSYW5nZVtpXSwgOSwgMTApCiAgaWYgKHN1YnN0cihkYXksIDEsIDEpID09ICcwJykge2RheSA9IHN1YnN0cihkYXksIDIsIDIpfQogICMgLSBjb21wb3NlIHF1ZXJ5CiAgaGl2ZVFMcXVlcnkgPC0gcGFzdGUwKCJTRUxFQ1QgbWV0YS5kdCBhcyBkdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnZXhwX3JlcGxhY2UoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdleHBfcmVwbGFjZSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnZXhwX3JlcGxhY2UocXVlcnksICdcXG58XFxyXFxufFxccnxcXG5cXHInLCAnIF9fTkVXTElORV9fICcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1xcdCcsICcgJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgJ1xcMDAwJywgJyAnKSBhcyBzcGFycWwsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1hdCwgbWV0YS5kb21haW4gYXMgZG9tYWluLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBodHRwLnJlcXVlc3RfaGVhZGVyc1snWC1CSUdEQVRBLU1BWC1RVUVSWS1NSUxMSVMnXSBhcyBtYXhfcXVlcnlfbWlsbGlzLCBodHRwLm1ldGhvZCBhcyBtZXRob2QsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGh0dHAuc3RhdHVzX2NvZGUgYXMgc3RhdHVzX2NvZGUsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tlbmRfaG9zdCwgZGF0YWNlbnRlciwgcXVlcnlfdGltZSAKICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5IFRBQkxFU0FNUExFIChCVUNLRVQgMSBPVVQgT0YgMTAwIE9OIHJhbmQoKSkgdwogICAgICAgICAgICAgICAgICAgICAgICAgIFdIRVJFIGlzbm90bnVsbChodHRwLnN0YXR1c19jb2RlKSAKICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgKG1ldGEuZG9tYWluID0gJ3F1ZXJ5Lndpa2lkYXRhLm9yZycpIAogICAgICAgICAgICAgICAgICAgICAgICAgIEFORCB5ZWFyPSIsIHllYXIsCiAgICAgICAgICAgICAgICAgICAgICAgICIgQU5EIG1vbnRoPSIsIG1vbnRoLAogICAgICAgICAgICAgICAgICAgICAgICAiIEFORCBkYXk9IiwgZGF5LCAnOycpCiAgZmlsZW5hbWUgPC0gIndkcXNfZGF0YV9zYW1wbGVfVEVTVF8iCiAgZmlsZW5hbWUgPC0gcGFzdGUwKGZpbGVuYW1lLCBkYXRlUmFuZ2VbaV0sICIudHN2IikKICAjIC0gd3JpdGUgaHFsCiAgd3JpdGUoaGl2ZVFMcXVlcnksIHBhc3RlMChmUGF0aCwgcXVlcnlGaWxlKSkKICAjIC0gdG8gUmVwb3J0CiAgcHJpbnQoIkZldGNoaW5nIHNwYXJxbF90ZXN0RGF0YSBmcm9tIHdtZi53ZWJyZXF1ZXN0IG5vdy4iKQogICMgLSBSdW4gcXVlcnkKICBxdWVyeSA8LSBzeXN0ZW0oY29tbWFuZCA9IHBhc3RlKGtlcmJlcm9zUHJlZml4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcvdXNyL2xvY2FsL2Jpbi9iZWVsaW5lIC0taW5jcmVtZW50YWw9dHJ1ZSAtLXNpbGVudCAtZiAiJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMChmUGF0aCwgcXVlcnlGaWxlKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICciID4gJywgZGF0YURpciwgZmlsZW5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIiksCiAgICAgICAgICAgICAgICAgIHdhaXQgPSBUUlVFKQogIHByaW50KHBhc3RlMCgiQ29tcGxldGVkOiAiLCBkYXRlUmFuZ2VbaV0pKQogIHByaW50KCItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSIpCn0KIyAtIHRvIFJlcG9ydApwcmludCgiRE9ORSB3LiBFVEwgZnJvbSBIYWRvb3A6IHdtZi53ZWJyZXF1ZXN0LiIpCmBgYAoKCiMjIDEuIEV4cGxvcmF0b3J5IERhdGEgQW5hbHl0aWNzIAoKQWxsIG9mIHRoZSBmb2xsb3dpbmcgYW5hbHl0aWNzIHdlcmUgZGVyaXZlZCBmcm9tIHRoZSBkYXRhc2V0cyBwcm9kdWNlZCBieSB0aGUgZm9sbG93aW5nIFIgc2NyaXB0LgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBGLCBtZXNzYWdlID0gRn0KCiMhL3Vzci9iaW4vZW52IFJzY3JpcHQKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBXRFFTX0V2ZW50X1M1X1JlcG9ydGluZ0RhdGFzZXRzLlIsIHYuIDAuMC4xCiMjIyAtLS0gU2NyaXB0OiBXRFFTX0V2ZW50X1M1X1JlcG9ydGluZ0RhdGFzZXRzLlIsIHYuIDAuMC4xCiMjIyAtLS0gRGVzY3JpcHRpb246IFdEUVNfRXZlbnRfUzVfUmVwb3J0aW5nRGF0YXNldHMuUiBpcyB1c2VkCiMjIyAtLS0gb25seSB0byBwcm9kdWNlIHRoZSByZXBvcnRpbmcgZGF0YXNldHMgZm9yIHRoZSAKIyMjIC0tLSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QuCiMjIyAtLS0gQWxsIGRhdGEgYXJlIG9idGFpbmVkIGZyb20gdGhlIGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5IAojIyMgLS0tIHRhYmxlIGluIHRoZSBXTUYgRGF0YSBMYWtlLgojIyMgLS0tIEF1dGhvcjogR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgU2NpZW50aXN0LCBXTURFCiMjIyAtLS0gRGV2ZWxvcGVkIHVuZGVyIHRoZSBjb250cmFjdCBiZXR3ZWVuIEdvcmFuIE1pbG92YW5vdmljIFBSIERhdGEgS29sZWt0aXYKIyMjIC0tLSBhbmQgV01ERS4KIyMjIC0tLSBDb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBSVU4gRlJPTTogc3RhdDEwMDUKIyMjIC0tLSBEYXRlOiAyMDIwLzA0LzI3CiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBMSUNFTlNFOgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gR1BMIHYyCiMjIyAtLS0gVGhpcyBmaWxlIGlzIHBhcnQgb2YgdGhlIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdAojIyMgLS0tCiMjIyAtLS0gV2lraWRhdGEgU1BBUlFMIEVuZHBvaW50IEFuYWx5dGljcyBQcm9qZWN0IGlzIGZyZWUgc29mdHdhcmU6IAojIyMgLS0tIHlvdSBjYW4gcmVkaXN0cmlidXRlIGl0IGFuZC9vciBtb2RpZnkKIyMjIC0tLSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieQojIyMgLS0tIHRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb24sIGVpdGhlciB2ZXJzaW9uIDIgb2YgdGhlIExpY2Vuc2UsIG9yCiMjIyAtLS0gKGF0IHlvdXIgb3B0aW9uKSBhbnkgbGF0ZXIgdmVyc2lvbi4KIyMjIC0tLQojIyMgLS0tIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdCBpcyBkaXN0cmlidXRlZCAKIyMjIC0tLSBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLAojIyMgLS0tIGJ1dCBXSVRIT1VUIEFOWSBXQVJSQU5UWTsgd2l0aG91dCBldmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mCiMjIyAtLS0gTUVSQ0hBTlRBQklMSVRZIG9yIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZQojIyMgLS0tIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuCiMjIyAtLS0KIyMjIC0tLSBZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZQojIyMgLS0tIGFsb25nIHdpdGggV2lraWRhdGEgU1BBUlFMIEVuZHBvaW50IEFuYWx5dGljcyBQcm9qZWN0LgojIyMgLS0tIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4KIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLSBzZXR1cApsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShodHRyKQpsaWJyYXJ5KHBST0MpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShjYXJldCkKbGlicmFyeShmYXN0RHVtbWllcykKCiMjIyAtLS0gZGlyZWN0b3JpZXMKIyMjIC0tLSBSZWFkIFdEQ00gcGFyYW1lcmV0ZXJzCiMgLSBmUGF0aDogd2hlcmUgdGhlIHNjcmlwdHMgaXMgcnVuIGZyb20/CmZQYXRoIDwtIGFzLmNoYXJhY3Rlcihjb21tYW5kQXJncyh0cmFpbGluZ09ubHkgPSBGQUxTRSlbNF0pCmZQYXRoIDwtIGdzdWIoIi0tZmlsZT0iLCAiIiwgZlBhdGgsIGZpeGVkID0gVCkKZlBhdGggPC0gdW5saXN0KHN0cnNwbGl0KGZQYXRoLCBzcGxpdCA9ICIvIiwgZml4ZWQgPSBUKSkKZlBhdGggPC0gcGFzdGUoCiAgcGFzdGUoZlBhdGhbMTpsZW5ndGgoZlBhdGgpIC0gMV0sIGNvbGxhcHNlID0gIi8iKSwKICAiLyIsCiAgc2VwID0gIiIpCgpmUGF0aCA8LSAnL2hvbWUvZ29yYW5zbS9BbmFseXRpY3MvV2lraWRhdGEvV0RfbWlzYy9XRF9TUEFSUUxfRW5kcG9pbnRfQW5hbHl0aWNzLycKIyAtIE5PVEU6IEhldHpuZXIgbG9jYWwgZGlyCiMgIC0gZlBhdGggPC0gJy9ob21lL2dvcmFuL1dNREUvV0RfU1BBUlFMX0VuZHBvaW50X0FuYWx5dGljcy8nCmRhdGFEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2V2ZW50L19kYXRhLycpCmFuYWx5dGljc0RpciA8LSBwYXN0ZTAoZlBhdGgsICdfZXZlbnQvX2FuYWx5dGljcy8nKQpyZXBvcnRpbmdEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2V2ZW50L19yZXBvcnRpbmcvJykKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTZWN0aW9uIDEuIFByb2R1Y2UgcmVwb3J0aW5nIGRhdGFzZXQKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLSBmZWF0dXJlIGZyZXF1ZW5jaWVzOiB2b2NhYnVsYXJ5CmZpbGVuYW1lIDwtICdxdWVyaWVzX3ZvY2FidWxhcnkuY3N2Jwp2b2NhYnVsYXJ5IDwtIGZyZWFkKHBhc3RlMChhbmFseXRpY3NEaXIsIGZpbGVuYW1lKSkKdm9jYWJ1bGFyeSRWMSA8LSBOVUxMCgojIyMgLS0tIGRhdGFzZXQKZmlsZW5hbWUgPC0gJ3dkcXNfZGF0YV9zYW1wbGVfMjAyMC0wNC0yMi5jc3YnCmRhdGFTZXQgPC0gZnJlYWQocGFzdGUwKGFuYWx5dGljc0RpciwgZmlsZW5hbWUpKQpkYXRhU2V0JFYxIDwtIE5VTEwKIyAtIGRlcml2ZTogbl9jb25jX3F1ZXJpZXMgcGVyIGRhdGFjZW50ZXIgeCBiYWNrX2VuZGhvc3QKZGF0YVNldCRkYXRhY2VudGVyX2hvc3QgPC0gcGFzdGUoZGF0YVNldCRkYXRhY2VudGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhU2V0JGJhY2tlbmRfaG9zdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICJfIikKZGF0YVNldCRkYXRhY2VudGVyIDwtIE5VTEwKZGF0YVNldCRiYWNrZW5kX2hvc3QgPC0gTlVMTAojIC0gdGhlIG51bWJlciBvZiBxdWVyaWVzIHN0YXJ0ZWQgaW4gdGhlIHNhbWUgbWludXRlCiMgLSBhcyB0aGUgdGFyZ2V0IHF1ZXJ5CiMgLSBwZXIgZGF0YWNlbnRlciB4IGJhY2tfZW5kaG9zdApkYXRhU2V0JGRhdGFjZW50ZXJfaG9zdF95bWRobSA8LSBwYXN0ZShkYXRhU2V0JGRhdGFjZW50ZXJfaG9zdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YVNldCR5bWRobSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICJfIikKZGF0YVNldCR5bWRobSA8LSBOVUxMCm5fY29uY19xdWVyaWVzIDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpzZWxlY3QoZGF0YWNlbnRlcl9ob3N0X3ltZGhtKSAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KGRhdGFjZW50ZXJfaG9zdF95bWRobSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2Uobl9jb25jX3F1ZXJpZXMgPSBuKCkpCmRhdGFTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmxlZnRfam9pbihuX2NvbmNfcXVlcmllcywgYnkgPSAiZGF0YWNlbnRlcl9ob3N0X3ltZGhtIikKZGF0YVNldCRkYXRhY2VudGVyX2hvc3RfeW1kaG0gPC0gTlVMTApybShuX2NvbmNfcXVlcmllcykKIyAtIGRlcml2ZTogbWlsZCBhbmQgZXh0cmVtZSBvdXRsaWVycyBvbiBxdWVyeV90aW1lCiMgLSBxdWVyeV90aW1lID49IHEzICsgMyppcSAKIyAtIHEzID0gNzUgcGVyY2VudGlsZSwgcTEgPSAyNSBwZXJjZW50aWxlCnEzIDwtIHF1YW50aWxlKGRhdGFTZXQkcXVlcnlfdGltZSwgLjc1KQpxMSA8LSBxdWFudGlsZShkYXRhU2V0JHF1ZXJ5X3RpbWUsIC4yNSkKIyAtIGlxID0gcTMgLSBxMTsgSW50ZXJxdWFydGlsZSByYW5nZQppcSA8LSB1bm5hbWUocTMgLSBxMSkKdXBwZXJfb3V0ZXJfZmVuY2UgPC0gcTMgKyAzKmlxCnVwcGVyX2lubmVyX2ZlbmNlIDwtIHEzICsgMS41KmlxCmRhdGFTZXQkcXVlcnlfdGltZV9leHRyZW1lIDwtIAogIGlmZWxzZShkYXRhU2V0JHF1ZXJ5X3RpbWUgPj0gdXBwZXJfb3V0ZXJfZmVuY2UsIDEsIDApCmRhdGFTZXQkcXVlcnlfdGltZV9taWxkIDwtIAogIGlmZWxzZShkYXRhU2V0JHF1ZXJ5X3RpbWUgPj0gdXBwZXJfaW5uZXJfZmVuY2UsIDEsIDApCiMgLSBkZXJpdmUgbXVsdGljbGFzcyBEVjogMCAtIG1pbGQsIG1pbGQgLSBleHRyZW1lLCBleHRyZW1lIHF1ZXJ5X3RpbWUKZGF0YVNldCRxdWVyeV90aW1lX2NsYXNzIDwtIDAKZGF0YVNldCRxdWVyeV90aW1lX2NsYXNzW2RhdGFTZXQkcXVlcnlfdGltZV9leHRyZW1lID09IDAgJiBkYXRhU2V0JHF1ZXJ5X3RpbWVfbWlsZCA9PSAxXSA8LSAxCmRhdGFTZXQkcXVlcnlfdGltZV9jbGFzc1tkYXRhU2V0JHF1ZXJ5X3RpbWVfZXh0cmVtZSA9PSAxXSA8LSAyCiMgLSBkZXJpdmU6IGJlbG93IGFuZCBhYm92ZSBtZWRpYW4gcXVlcnlfdGltZQpkYXRhU2V0JHF1ZXJ5X3RpbWVfbWVkaWFuIDwtIGlmZWxzZShkYXRhU2V0JHF1ZXJ5X3RpbWUgPj0gbWVkaWFuKGRhdGFTZXQkcXVlcnlfdGltZSksIDEsIDApCgojIyMgLS0tIHZvY2FidWxhcnk6IHRvcF9mIG1vc3QgZnJlcXVlbnRseSB1c2VkIGZlYXR1cmVzCnRvcF9mIDwtIDMwMApvYl9mZWF0dXJlcyA8LSBjKCduY2hhcicsICdfX3ZhcnNfXycsICdfX3ZhcnNfdXNhZ2VfXycsICdfX2xpdGVyYWxfXycsIAogICAgICAgICAgICAgICAgICdfX3ZhcnNfbGFiZWxfXycsICdfX3ZhcnNfbGFiZWxfdXNhZ2VfXycsICdfX2lyaV9yZWZfXycpCnZvY2FidWxhcnkgPC0gdm9jYWJ1bGFyeSRmZWF0dXJlWzE6dG9wX2ZdCnZvY2FidWxhcnkgPC0gdW5pcXVlKGModm9jYWJ1bGFyeSwgb2JfZmVhdHVyZXMpKQoKIyMjIC0tLSBqb2luIHZvY2FidWxhcnkgdG8gZGF0YVNldApxdWVyaWVzX3Byb2Nlc3NlZCA8LSBmcmVhZChwYXN0ZTAoYW5hbHl0aWNzRGlyLCAncXVlcmllc19wcm9jZXNzZWQuY3N2JykpCnF1ZXJpZXNfcHJvY2Vzc2VkJFYxIDwtIE5VTEwKZGltKHF1ZXJpZXNfcHJvY2Vzc2VkKQpxdWVyaWVzX3Byb2Nlc3NlZCA8LSBxdWVyaWVzX3Byb2Nlc3NlZCAlPiUgCiAgZHBseXI6OmZpbHRlcihmZWF0dXJlICVpbiUgdm9jYWJ1bGFyeSkKcm0odm9jYWJ1bGFyeSkKZGltKHF1ZXJpZXNfcHJvY2Vzc2VkKQpmZWF0dXJlRnJhbWUgPC0gZGF0YS50YWJsZTo6ZGNhc3QoYXMuZGF0YS50YWJsZShxdWVyaWVzX3Byb2Nlc3NlZCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bmlxdWVTcGFycWxJZCB+IGZlYXR1cmUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZS52YXIgPSAiZnJlcSIpCnJtKHF1ZXJpZXNfcHJvY2Vzc2VkKTsgZ2MoKQojIC0gbWFyayBmZWF0dXJlcyBpbiBkYXRhU2V0OiBkcwpjb2xuYW1lcyhkYXRhU2V0KSA8LSBwYXN0ZTAoImRzXyIsIGNvbG5hbWVzKGRhdGFTZXQpKQojIC0gam9pbjoKZGF0YVNldCA8LSBkYXRhU2V0ICU+JSAKICBkcGx5cjo6bGVmdF9qb2luKGZlYXR1cmVGcmFtZSwgCiAgICAgICAgICAgICAgICAgICBieSA9IGMoJ2RzX3VuaXF1ZVNwYXJxbElkJyA9ICd1bmlxdWVTcGFycWxJZCcpKQpybShmZWF0dXJlRnJhbWUpOyBnYygpCmNvbG5hbWVzKGRhdGFTZXQpCgojIC0gc3RvcmUgcmVwb3J0aW5nIGRhdGEuZnJhbWU6IHJlcG9ydGluZ0ZyYW1lLmNzdgpmaWxlbmFtZSA8LSBwYXN0ZTAoJ3JlcG9ydGluZ0ZyYW1lJywgJ18nLCB0b3BfZiwgJy5jc3YnKQp3cml0ZS5jc3YoZGF0YVNldCwKICAgICAgICAgIHBhc3RlMChyZXBvcnRpbmdEaXIsIGZpbGVuYW1lKSkKCiMgLSBjbGVhcgpybShsaXN0ID0gc2V0ZGlmZihscygpLCBjKCdyZXBvcnRpbmdEaXInLCAnYW5hbHl0aWNzRGlyJywgJ2RhdGFEaXInLCAnZlBhdGgnKSkpCmdjKCkKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTZWN0aW9uIDIuIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLSBsb2FkIHJlcG9ydGluZyBkYXRhLmZyYW1lCnRvcF9mIDwtIDMwMApmaWxlbmFtZSA8LSBwYXN0ZTAoJ3JlcG9ydGluZ0ZyYW1lJywgJ18nLCB0b3BfZiwgJy5jc3YnKQpyZXBvcnRGcmFtZSA8LSBmcmVhZChwYXN0ZTAocmVwb3J0aW5nRGlyLCBmaWxlbmFtZSkpCnJlcG9ydEZyYW1lJFYxIDwtIE5VTEwKCiMjIyAtLS0gcmVwb3J0RnJhbWUkcmVjb2RlIGRzX2RheSB0byBkYXkgb2Ygd2VlawpzYW1wbGVEYXlzIDwtIGlmZWxzZShuY2hhcihyZXBvcnRGcmFtZSRkc19kYXkpID09IDEsIAogICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoJzAnLCByZXBvcnRGcmFtZSRkc19kYXkpLAogICAgICAgICAgICAgICAgICAgICByZXBvcnRGcmFtZSRkc19kYXkpCnJlcG9ydEZyYW1lJGRzX2RheSA8LSBwYXN0ZTAoIjIwMjAtMDQtIiwgc2FtcGxlRGF5cykKcmVwb3J0RnJhbWUkZHNfZGF5IDwtIHdlZWtkYXlzKGFzLkRhdGUocmVwb3J0RnJhbWUkZHNfZGF5KSkKcm0oc2FtcGxlRGF5cykKCiMjIyAtLS0gU2FtcGxlIGNoYXJhY3RlcmlzdGljcwojIC0gZXh0ZXJuYWwgKG5vbi1TUEFSUUwgcGFyc2luZyBiYXNlZCkgZmF0dXJlcyBpbiB0aGUgZGF0YXNldApkc19mZWF0dXJlcyA8LSAKICBjb2xuYW1lcyhyZXBvcnRGcmFtZSlbd2hpY2goZ3JlcGwoIl5kc18iLCBjb2xuYW1lcyhyZXBvcnRGcmFtZSkpKV0KCiMgLSBkYXlzCnNhbXBsZURheXMgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShyZXBvcnRGcmFtZSRkc19kYXkpKQpjb2xuYW1lcyhzYW1wbGVEYXlzKSA8LSBjKCdEYXknLCAnTnVtLlF1ZXJpZXMnKQpzYW1wbGVEYXlzJFBlcmNlbnQgPC0gcm91bmQoCiAgc2FtcGxlRGF5cyROdW0uUXVlcmllcy9zdW0oc2FtcGxlRGF5cyROdW0uUXVlcmllcykqMTAwLCAyKQpzYW1wbGVEYXlzIDwtZHBseXI6OmFycmFuZ2Uoc2FtcGxlRGF5cywgZGVzYyhQZXJjZW50KSkKd3JpdGUuY3N2KHNhbXBsZURheXMsIHBhc3RlMChyZXBvcnRpbmdEaXIsICdzYW1wbGVDaGFyX0RheXMuY3N2JykpCiMgLSBob3VycwpzYW1wbGVIb3VycyA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKHJlcG9ydEZyYW1lJGRzX2hvdXIpKQpjb2xuYW1lcyhzYW1wbGVIb3VycykgPC0gYygnSG91cicsICdOdW0uUXVlcmllcycpCnNhbXBsZUhvdXJzJFBlcmNlbnQgPC0gcm91bmQoCiAgc2FtcGxlSG91cnMkTnVtLlF1ZXJpZXMvc3VtKHNhbXBsZUhvdXJzJE51bS5RdWVyaWVzKSoxMDAsIDIpCnNhbXBsZUhvdXJzIDwtZHBseXI6OmFycmFuZ2Uoc2FtcGxlSG91cnMsIGRlc2MoUGVyY2VudCkpCndyaXRlLmNzdihzYW1wbGVIb3VycywgcGFzdGUwKHJlcG9ydGluZ0RpciwgJ3NhbXBsZUNoYXJfSG91cnMuY3N2JykpCiMgLSBIVFRQIG1ldGhvZApzYW1wbGVNZXRob2QgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShyZXBvcnRGcmFtZSRkc19tZXRob2QpKQpjb2xuYW1lcyhzYW1wbGVNZXRob2QpIDwtIGMoJ0hUVFAubWV0aG9kJywgJ051bS5RdWVyaWVzJykKc2FtcGxlTWV0aG9kJFBlcmNlbnQgPC0gcm91bmQoCiAgc2FtcGxlTWV0aG9kJE51bS5RdWVyaWVzL3N1bShzYW1wbGVNZXRob2QkTnVtLlF1ZXJpZXMpKjEwMCwgMikKc2FtcGxlTWV0aG9kIDwtZHBseXI6OmFycmFuZ2Uoc2FtcGxlTWV0aG9kLCBkZXNjKFBlcmNlbnQpKQp3cml0ZS5jc3Yoc2FtcGxlTWV0aG9kLCBwYXN0ZTAocmVwb3J0aW5nRGlyLCAnc2FtcGxlQ2hhcl9IVFRQbWV0aG9kLmNzdicpKQojIC0gZGF0YWNlbnRlcl9ob3N0CnNhbXBsZV9kYXRhY2VudGVyX2hvc3QgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShyZXBvcnRGcmFtZSRkc19kYXRhY2VudGVyX2hvc3QpKQpjb2xuYW1lcyhzYW1wbGVfZGF0YWNlbnRlcl9ob3N0KSA8LSBjKCdEYXRhY2VudGVyLkhvc3QnLCAnTnVtLlF1ZXJpZXMnKQpzYW1wbGVfZGF0YWNlbnRlcl9ob3N0JFBlcmNlbnQgPC0gcm91bmQoCiAgc2FtcGxlX2RhdGFjZW50ZXJfaG9zdCROdW0uUXVlcmllcy9zdW0oc2FtcGxlX2RhdGFjZW50ZXJfaG9zdCROdW0uUXVlcmllcykqMTAwLCAyKQpzYW1wbGVfZGF0YWNlbnRlcl9ob3N0IDwtZHBseXI6OmFycmFuZ2Uoc2FtcGxlX2RhdGFjZW50ZXJfaG9zdCwgZGVzYyhQZXJjZW50KSkKd3JpdGUuY3N2KHNhbXBsZV9kYXRhY2VudGVyX2hvc3QsIHBhc3RlMChyZXBvcnRpbmdEaXIsICdzYW1wbGVDaGFyX0RhdGFjZW50ZXJIb3N0LmNzdicpKQojIC0gU3RhdHVzIENvZGUKc2FtcGxlX1N0YXR1c0NvZGUgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShyZXBvcnRGcmFtZSRkc19zdGF0dXNfY29kZSkpCmNvbG5hbWVzKHNhbXBsZV9TdGF0dXNDb2RlKSA8LSBjKCdTdGF0dXMnLCAnTnVtLlF1ZXJpZXMnKQpzYW1wbGVfU3RhdHVzQ29kZSRQZXJjZW50IDwtIHJvdW5kKAogIHNhbXBsZV9TdGF0dXNDb2RlJE51bS5RdWVyaWVzL3N1bShzYW1wbGVfU3RhdHVzQ29kZSROdW0uUXVlcmllcykqMTAwLCAyKQpzYW1wbGVfU3RhdHVzQ29kZSA8LWRwbHlyOjphcnJhbmdlKHNhbXBsZV9TdGF0dXNDb2RlLCBkZXNjKFBlcmNlbnQpKQp3cml0ZS5jc3Yoc2FtcGxlX1N0YXR1c0NvZGUsIHBhc3RlMChyZXBvcnRpbmdEaXIsICdzYW1wbGVDaGFyX1N0YXR1c0NvZGUuY3N2JykpCiMgLSBGb3JtYXQKc2FtcGxlX0Zvcm1hdCA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKHJlcG9ydEZyYW1lJGRzX2Zvcm1hdCkpCmNvbG5hbWVzKHNhbXBsZV9Gb3JtYXQpIDwtIGMoJ0Zvcm1hdCcsICdOdW0uUXVlcmllcycpCnNhbXBsZV9Gb3JtYXQkUGVyY2VudCA8LSByb3VuZCgKICBzYW1wbGVfRm9ybWF0JE51bS5RdWVyaWVzL3N1bShzYW1wbGVfRm9ybWF0JE51bS5RdWVyaWVzKSoxMDAsIDIpCnNhbXBsZV9Gb3JtYXQgPC1kcGx5cjo6YXJyYW5nZShzYW1wbGVfRm9ybWF0LCBkZXNjKFBlcmNlbnQpKQp3cml0ZS5jc3Yoc2FtcGxlX0Zvcm1hdCwgcGFzdGUwKHJlcG9ydGluZ0RpciwgJ3NhbXBsZV9Gb3JtYXQuY3N2JykpCiMgLSBEaXN0cmlidXRpb24gb2YgdGhlIG51bWJlciBvZiBxdWVyaWVzIAojIC0gY29uY3VycmVudGx5IHN0YXJ0ZWQgaW4gdGhlIHNhbWUgbWludXRlCnJlcG9ydEZyYW1lJGRzX2RheV9ob3VyX21pbnV0ZSA8LSBwYXN0ZShyZXBvcnRGcmFtZSRkc19kYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBvcnRGcmFtZSRkc19ob3VyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwb3J0RnJhbWUkZHNfbWludXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIi0iKQpzYW1wbGVfZGF0YWNlbnRlcl9ob3N0X3FycXVlcmllcyA8LSByZXBvcnRGcmFtZSAlPiUgCiAgZHBseXI6OnNlbGVjdChkc19kYXRhY2VudGVyX2hvc3QsIGRzX2RheV9ob3VyX21pbnV0ZSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShkc19kYXRhY2VudGVyX2hvc3QsIGRzX2RheV9ob3VyX21pbnV0ZSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UocXVlcnlfY291bnQgPSBuKCkpICU+JQogIGRwbHlyOjpzZWxlY3QoZHNfZGF0YWNlbnRlcl9ob3N0LCBxdWVyeV9jb3VudCkgJT4lIAogIGRwbHlyOjpncm91cF9ieShkc19kYXRhY2VudGVyX2hvc3QpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKG1lYW5fY29uY19xdWVyaWVzID0gbWVhbihxdWVyeV9jb3VudCksIAogICAgICAgICAgICAgICAgICAgbWVkaWFuX2NvbmNfcXVlcmllcyA9IG1lZGlhbihxdWVyeV9jb3VudCksIAogICAgICAgICAgICAgICAgICAgc3RkZXZfY29uY19xdWVyaWVzID0gc2QocXVlcnlfY291bnQpKSAlPiUKICBkcGx5cjo6YXJyYW5nZShkZXNjKG1lYW5fY29uY19xdWVyaWVzKSkKd3JpdGUuY3N2KHNhbXBsZV9kYXRhY2VudGVyX2hvc3RfcXJxdWVyaWVzLCAKICAgICAgICAgIHBhc3RlMChyZXBvcnRpbmdEaXIsICdzYW1wbGVfZGF0YWNlbnRlcl9ob3N0X3FycXVlcmllcy5jc3YnKSkKcmVwb3J0RnJhbWUkZHNfZGF5X2hvdXJfbWludXRlIDwtIE5VTEwKCiMjIyAtLS0gUHJvZHVjZSByZXBlYXRlZCBxdWVyaWVzIGRhdGFzZXQKcmVwZWF0ZWRRdWVyaWVzIDwtIHJlcG9ydEZyYW1lICU+JSAKICBkcGx5cjo6c2VsZWN0KGRzX3VuaXF1ZVNwYXJxbElkLCAKICAgICAgICAgICAgICAgIGRzX3NwYXJxbCwgCiAgICAgICAgICAgICAgICBkc19xdWVyeV90aW1lKQpjb2xuYW1lcyhyZXBlYXRlZFF1ZXJpZXMpCnJlcGVhdGVkUXVlcmllcyA8LSByZXBlYXRlZFF1ZXJpZXMgJT4lIAogIGRwbHlyOjpncm91cF9ieShkc191bmlxdWVTcGFycWxJZCwgZHNfc3BhcnFsKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShudW0gPSBuKCksCiAgICAgICAgICAgICAgICAgICBtZWFuX3F1ZXJ5X3RpbWUgPSBtZWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbl9xdWVyeV90aW1lID0gbWVkaWFuKGRzX3F1ZXJ5X3RpbWUpLAogICAgICAgICAgICAgICAgICAgc3RkZXZfcXVlcnlfdGltZSA9IHNkKGRzX3F1ZXJ5X3RpbWUpKQpyZXBlYXRlZFF1ZXJpZXMkUGVyY2VudCA8LSByb3VuZCgKICByZXBlYXRlZFF1ZXJpZXMkbnVtL3N1bShyZXBlYXRlZFF1ZXJpZXMkbnVtKSoxMDAsIDYpCnJlcGVhdGVkUXVlcmllcyA8LSBkcGx5cjo6YXJyYW5nZShyZXBlYXRlZFF1ZXJpZXMsIGRlc2MobnVtKSkKd3JpdGUuY3N2KHJlcGVhdGVkUXVlcmllcywgCiAgICAgICAgICBwYXN0ZTAocmVwb3J0aW5nRGlyLCAncmVwZWF0ZWRRdWVyaWVzLmNzdicpKQpybShyZXBlYXRlZFF1ZXJpZXMpOyBnYygpCgojIyMgLS0tIENyb3NzLVRhYnVsYXRpb246IGRzXyogZmVhdHVyZXMgeCBxdWVyeV90aW1lCiMgLSBkc19mb3JtYXQKY3Jvc3NUYWJzX2Zvcm1hdCA8LSByZXBvcnRGcmFtZSAlPiUgCiAgZHBseXI6OnNlbGVjdChkc19mb3JtYXQsIGRzX3F1ZXJ5X3RpbWUpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoZHNfZm9ybWF0KSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuX3F1ZXJ5X3RpbWUgPSBtZWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbl9xdWVyeV90aW1lID0gbWVkaWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIHN0ZGV2X3F1ZXJ5X3RpbWUgPSBzZChkc19xdWVyeV90aW1lKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MobWVhbl9xdWVyeV90aW1lKSkKd3JpdGUuY3N2KGNyb3NzVGFic19mb3JtYXQsIAogICAgICAgICAgcGFzdGUwKHJlcG9ydGluZ0RpciwgJ2Nyb3NzVGFic19mb3JtYXQuY3N2JykpCiMgLSBkc19kYXkKY3Jvc3NUYWJzX2RheSA8LSByZXBvcnRGcmFtZSAlPiUgCiAgZHBseXI6OnNlbGVjdChkc19kYXksIGRzX3F1ZXJ5X3RpbWUpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoZHNfZGF5KSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuX3F1ZXJ5X3RpbWUgPSBtZWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbl9xdWVyeV90aW1lID0gbWVkaWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIHN0ZGV2X3F1ZXJ5X3RpbWUgPSBzZChkc19xdWVyeV90aW1lKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MobWVhbl9xdWVyeV90aW1lKSkKd3JpdGUuY3N2KGNyb3NzVGFic19kYXksIAogICAgICAgICAgcGFzdGUwKHJlcG9ydGluZ0RpciwgJ2Nyb3NzVGFic19kYXkuY3N2JykpCiMgLSBkc19tZXRob2QKY3Jvc3NUYWJzX21ldGhvZCA8LSByZXBvcnRGcmFtZSAlPiUgCiAgZHBseXI6OnNlbGVjdChkc19tZXRob2QsIGRzX3F1ZXJ5X3RpbWUpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoZHNfbWV0aG9kKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuX3F1ZXJ5X3RpbWUgPSBtZWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbl9xdWVyeV90aW1lID0gbWVkaWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIHN0ZGV2X3F1ZXJ5X3RpbWUgPSBzZChkc19xdWVyeV90aW1lKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MobWVhbl9xdWVyeV90aW1lKSkKd3JpdGUuY3N2KGNyb3NzVGFic19tZXRob2QsIAogICAgICAgICAgcGFzdGUwKHJlcG9ydGluZ0RpciwgJ2Nyb3NzVGFic19tZXRob2QuY3N2JykpCiMgLSBkc19kYXRhY2VudGVyX2hvc3QKY3Jvc3NUYWJzX2RhdGFjZW50ZXJfaG9zdCA8LSByZXBvcnRGcmFtZSAlPiUgCiAgZHBseXI6OnNlbGVjdChkc19kYXRhY2VudGVyX2hvc3QsIGRzX3F1ZXJ5X3RpbWUpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoZHNfZGF0YWNlbnRlcl9ob3N0KSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuX3F1ZXJ5X3RpbWUgPSBtZWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbl9xdWVyeV90aW1lID0gbWVkaWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIHN0ZGV2X3F1ZXJ5X3RpbWUgPSBzZChkc19xdWVyeV90aW1lKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MobWVhbl9xdWVyeV90aW1lKSkKd3JpdGUuY3N2KGNyb3NzVGFic19kYXRhY2VudGVyX2hvc3QsIAogICAgICAgICAgcGFzdGUwKHJlcG9ydGluZ0RpciwgJ2Nyb3NzVGFic19kYXRhY2VudGVyX2hvc3QuY3N2JykpCgojIC0gZHNfc3RhdHVzX2NvZGUKY3Jvc3NUYWJzX3N0YXR1c19jb2RlIDwtIHJlcG9ydEZyYW1lICU+JSAKICBkcGx5cjo6c2VsZWN0KGRzX3N0YXR1c19jb2RlLCBkc19xdWVyeV90aW1lKSAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KGRzX3N0YXR1c19jb2RlKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuX3F1ZXJ5X3RpbWUgPSBtZWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbl9xdWVyeV90aW1lID0gbWVkaWFuKGRzX3F1ZXJ5X3RpbWUpLCAKICAgICAgICAgICAgICAgICAgIHN0ZGV2X3F1ZXJ5X3RpbWUgPSBzZChkc19xdWVyeV90aW1lKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MobWVhbl9xdWVyeV90aW1lKSkKd3JpdGUuY3N2KGNyb3NzVGFic19zdGF0dXNfY29kZSwgCiAgICAgICAgICBwYXN0ZTAocmVwb3J0aW5nRGlyLCAnY3Jvc3NUYWJzX3N0YXR1c19jb2RlLmNzdicpKQpgYGAKCiMjIyAxLjEgU2FtcGxlIERlc2NyaXB0aW9uCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIG1lc3NhZ2UgPSBGfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzY2FsZXMpCmBgYAoKRmlyc3Qgd2UgcHJvdmlkZSBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzIGZvciB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGluIHRoZSBkYXRhIHNldC4KCiMjIyMgMS4xLjEgVGhlIGRpc3RyaWJ1dGlvbiBvZiBxdWVyaWVzIHBlciBkYXkgb2Ygd2VlawoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lIDwtIHJlYWQuY3N2KCdzYW1wbGVDaGFyX0RheXMuY3N2JywgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsIAogICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnBGcmFtZSREYXkgPC0gZmFjdG9yKHBGcmFtZSREYXksCiAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoJ01vbmRheScsICdUdWVzZGF5JywgJ1dlZG5lc2RheScsICdUaHVyc2RheScsICdGcmlkYXknLCAnU2F0dXJkYXknLCAnU3VuZGF5JykpCmdncGxvdChkYXRhID0gcEZyYW1lLCAKICAgICAgIGFlcyh4ID0gRGF5LCAKICAgICAgICAgICB5ID0gTnVtLlF1ZXJpZXMsIAogICAgICAgICAgIGxhYmVsID0gcGFzdGUwKFBlcmNlbnQsICIlIikpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjE1LCBjb2xvciA9ICdkYXJrcmVkJywgZmlsbCA9ICdyZWQnLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMpICsgCiAgdGhlbWVfYncoKQpgYGAKCiMjIyMgMS4xLjIgVGhlIGRpc3RyaWJ1dGlvbiBvZiBxdWVyaWVzIHBlciBob3VyIG9mIGRheQoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lIDwtIHJlYWQuY3N2KCdzYW1wbGVDaGFyX0hvdXJzLmNzdicsIAogICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgCiAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLCAKICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpwRnJhbWUkSG91ciA8LSBmYWN0b3IocEZyYW1lJEhvdXIsCiAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjIzKSkKZ2dwbG90KGRhdGEgPSBwRnJhbWUsIAogICAgICAgYWVzKHggPSBIb3VyLCAKICAgICAgICAgICB5ID0gTnVtLlF1ZXJpZXMsIAogICAgICAgICAgIGxhYmVsID0gcGFzdGUwKFBlcmNlbnQsICIlIikpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjE1LCBjb2xvciA9ICdkYXJrcmVkJywgZmlsbCA9ICdyZWQnLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMpICsgCiAgdGhlbWVfYncoKQpgYGAKCiMjIyMgMS4xLjNBIFRoZSBkaXN0cmlidXRpb24gb2YgcXVlcmllcyBwZXIgV01GIERhdGFjZW50ZXIvSG9zdAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lIDwtIHJlYWQuY3N2KCdzYW1wbGVDaGFyX0RhdGFjZW50ZXJIb3N0LmNzdicsIAogICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgCiAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLCAKICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpwRnJhbWUkRGF0YWNlbnRlci5Ib3N0IDwtIGZhY3RvcihwRnJhbWUkRGF0YWNlbnRlci5Ib3N0LAogICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSB1bmlxdWUocEZyYW1lJERhdGFjZW50ZXIuSG9zdCkpCmdncGxvdChkYXRhID0gcEZyYW1lLCAKICAgICAgIGFlcyh4ID0gRGF0YWNlbnRlci5Ib3N0LCAKICAgICAgICAgICB5ID0gTnVtLlF1ZXJpZXMsIAogICAgICAgICAgIGxhYmVsID0gcGFzdGUwKFBlcmNlbnQsICIlIikpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjE1LCBjb2xvciA9ICdkYXJrZ3JlZW4nLCBmaWxsID0gJ2dyZWVuJywgc3RhdCA9ICdpZGVudGl0eScpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMykgKyAKICB0aGVtZV9idygpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSAgZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpgYGAKCiMjIyMgMS4xLjNCIFRoZSBtZWFuIGFuZCBtZWRpYW4gbnVtYmVyIG9mIHF1ZXJpZXMgY29uY3VycmVudGx5IHN0YXJ0ZWQgaW4gdGhlIHNhbWUgbWludXRlLCBwZXIgV01GIERhdGFjZW50ZXIvSG9zdAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lIDwtIHJlYWQuY3N2KCdzYW1wbGVfZGF0YWNlbnRlcl9ob3N0X3FycXVlcmllcy5jc3YnLCAKICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsIAogICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwgCiAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKcEZyYW1lJERhdGFjZW50ZXIuSG9zdCA8LSBmYWN0b3IocEZyYW1lJGRzX2RhdGFjZW50ZXJfaG9zdCwKICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gdW5pcXVlKHBGcmFtZSRkc19kYXRhY2VudGVyX2hvc3QpKQpnZ3Bsb3QoZGF0YSA9IHBGcmFtZSwgCiAgICAgICBhZXMoeCA9IGRzX2RhdGFjZW50ZXJfaG9zdCwgCiAgICAgICAgICAgeSA9IG1lYW5fY29uY19xdWVyaWVzLCAKICAgICAgICAgICBsYWJlbCA9IHBhc3RlMCgiTWVkOiAiLCBtZWRpYW5fY29uY19xdWVyaWVzKSkpICsgCiAgZ2VvbV9iYXIod2lkdGggPSAuMTUsIGNvbG9yID0gJ2RhcmtncmVlbicsIGZpbGwgPSAnZ3JlZW4nLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzKSArIAogIHhsYWIoJ1dNRiBEYXRhY2VudGVyL0hvc3QnKSArIHlsYWIoJ01lYW4gUmVzcG9uc2UgVGltZSAoc2VjcyknKSArCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gIGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkKYGBgCgojIyMjIDEuMS40IFRoZSBkaXN0cmlidXRpb24gb2YgcXVlcmllcyBwZXIgSFRUUCBtZXRob2Qgb2YgcmVxdWVzdAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lIDwtIHJlYWQuY3N2KCdzYW1wbGVDaGFyX0hUVFBtZXRob2QuY3N2JywgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsIAogICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnBGcmFtZSRIVFRQLm1ldGhvZCA8LSBmYWN0b3IocEZyYW1lJEhUVFAubWV0aG9kLAogICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSB1bmlxdWUocEZyYW1lJEhUVFAubWV0aG9kKSkKZ2dwbG90KGRhdGEgPSBwRnJhbWUsIAogICAgICAgYWVzKHggPSBIVFRQLm1ldGhvZCwgCiAgICAgICAgICAgeSA9IE51bS5RdWVyaWVzLCAKICAgICAgICAgICBsYWJlbCA9IHBhc3RlMChQZXJjZW50LCAiJSIpKSkgKyAKICBnZW9tX2Jhcih3aWR0aCA9IC4xNSwgY29sb3IgPSAnZGFya2JsdWUnLCBmaWxsID0gJ2JsdWUnLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMpICsgCiAgdGhlbWVfYncoKQpgYGAKCiMjIyMgMS4xLjUgVGhlIGRpc3RyaWJ1dGlvbiBvZiBxdWVyaWVzIHBlciBzZXJ2ZXIgcmVzcG9uc2UgY29kZQoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lIDwtIHJlYWQuY3N2KCdzYW1wbGVDaGFyX1N0YXR1c0NvZGUuY3N2JywgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsIAogICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnBGcmFtZSRTdGF0dXM8LSBhcy5jaGFyYWN0ZXIocEZyYW1lJFN0YXR1cykKZ2dwbG90KGRhdGEgPSBwRnJhbWUsIAogICAgICAgYWVzKHggPSBTdGF0dXMsIAogICAgICAgICAgIHkgPSBOdW0uUXVlcmllcywgCiAgICAgICAgICAgbGFiZWwgPSBwYXN0ZTAoUGVyY2VudCwgIiUiKSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgZ2VvbV9iYXIod2lkdGggPSAuMTUsIGNvbG9yID0gJ2RhcmtvcmFuZ2UnLCBmaWxsID0gJ29yYW5nZScsIHN0YXQgPSAnaWRlbnRpdHknKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMykgKyAKICB0aGVtZV9idygpCmBgYAoKIyMjIyAxLjEuNiBUaGUgZGlzdHJpYnV0aW9uIG9mIHF1ZXJpZXMgcGVyIG91dHB1dCBmb3JtYXQKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgbWVzc2FnZSA9IEZ9CnBGcmFtZSA8LSByZWFkLmNzdignc2FtcGxlX0Zvcm1hdC5jc3YnLCAKICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsIAogICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwgCiAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKcEZyYW1lJEZvcm1hdDwtIGZhY3RvcihwRnJhbWUkRm9ybWF0LCAKICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSB1bmlxdWUocEZyYW1lJEZvcm1hdCkpCmdncGxvdChkYXRhID0gcEZyYW1lLCAKICAgICAgIGFlcyh4ID0gRm9ybWF0LCAKICAgICAgICAgICB5ID0gTnVtLlF1ZXJpZXMsIAogICAgICAgICAgIGxhYmVsID0gcGFzdGUwKFBlcmNlbnQsICIlIikpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjE1LCBjb2xvciA9ICdkYXJrdmlvbGV0JywgZmlsbCA9ICd2aW9sZXQnLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzKSArIAogIHRoZW1lX2J3KCkKYGBgCgojIyMgMS4yIFdEUVMgcHJvY2Vzc2luZyB0aW1lcwoKIyMjIyAxLjIuMSBEYXkgb2Ygd2VlayB2cy4gV0RRUyByZXNwb25zZSB0aW1lCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIG1lc3NhZ2UgPSBGfQpwRnJhbWUgPC0gcmVhZC5jc3YoJ2Nyb3NzVGFic19kYXkuY3N2JywgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsIAogICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnBGcmFtZSRkc19kYXkgPC0gZmFjdG9yKHBGcmFtZSRkc19kYXksCiAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoJ01vbmRheScsICdUdWVzZGF5JywgJ1dlZG5lc2RheScsICdUaHVyc2RheScsICdGcmlkYXknLCAnU2F0dXJkYXknLCAnU3VuZGF5JykpCmdncGxvdChkYXRhID0gcEZyYW1lLCAKICAgICAgIGFlcyh4ID0gZHNfZGF5LCAKICAgICAgICAgICB5ID0gbWVhbl9xdWVyeV90aW1lLCAKICAgICAgICAgICBsYWJlbCA9IHBhc3RlMCgiTWVkOiAiLCBtZWRpYW5fcXVlcnlfdGltZSkpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjE1LCBjb2xvciA9ICdkYXJrcmVkJywgZmlsbCA9ICdyZWQnLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzKSArIAogIHhsYWIoJ0RheScpICsgeWxhYignTWVhbiBRdWVyeSBUaW1lIChzZWNzKScpICsKICB0aGVtZV9idygpCmBgYAoKIyMjIyAxLjIuMiBIb3VyIG9mIGRheSB2cy4gV0RRUyByZXNwb25zZSB0aW1lCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIG1lc3NhZ2UgPSBGfQpwRnJhbWUgPC0gcmVhZC5jc3YoJ2Nyb3NzVGFic19ob3VyLmNzdicsIAogICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgCiAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLCAKICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpwRnJhbWUkZHNfaG91ciA8LSBmYWN0b3IocEZyYW1lJGRzX2hvdXIsCiAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjIzKSkKZ2dwbG90KGRhdGEgPSBwRnJhbWUsIAogICAgICAgYWVzKHggPSBkc19ob3VyLCAKICAgICAgICAgICB5ID0gbWVhbl9xdWVyeV90aW1lLCAKICAgICAgICAgICBsYWJlbCA9IHBhc3RlMCgiTWVkOiAiLCBtZWRpYW5fcXVlcnlfdGltZSkpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjE1LCBjb2xvciA9ICdkYXJrcmVkJywgZmlsbCA9ICdyZWQnLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzKSArIAogIHhsYWIoJ0hvdXInKSArIHlsYWIoJ01lYW4gUXVlcnkgVGltZSAoc2VjcyknKSArCiAgdGhlbWVfYncoKQpgYGAKCiMjIyMgMS4yLjMgV01GIERhdGFjZW50ZXIvSG9zdCB2cy4gV0RRUyByZXNwb25zZSB0aW1lCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIG1lc3NhZ2UgPSBGfQpwRnJhbWUgPC0gcmVhZC5jc3YoJ2Nyb3NzVGFic19kYXRhY2VudGVyX2hvc3QuY3N2JywgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsIAogICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnBGcmFtZSRkc19kYXRhY2VudGVyX2hvc3QgPC0gZmFjdG9yKHBGcmFtZSRkc19kYXRhY2VudGVyX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHVuaXF1ZShwRnJhbWUkZHNfZGF0YWNlbnRlcl9ob3N0KSkKZ2dwbG90KGRhdGEgPSBwRnJhbWUsIAogICAgICAgYWVzKHggPSBkc19kYXRhY2VudGVyX2hvc3QsIAogICAgICAgICAgIHkgPSBtZWFuX3F1ZXJ5X3RpbWUsIAogICAgICAgICAgIGxhYmVsID0gcGFzdGUwKCJNZWQ6ICIsIG1lZGlhbl9xdWVyeV90aW1lKSkpICsgCiAgZ2VvbV9iYXIod2lkdGggPSAuMTUsIGNvbG9yID0gJ2RhcmtncmVlbicsIGZpbGwgPSAnZ3JlZW4nLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzKSArIAogIHhsYWIoJ1dNRi9EYXRhY2VudGVyL0hvc3QnKSArIHlsYWIoJ01lYW4gUXVlcnkgVGltZSAoc2VjcyknKSArCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gIGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkKYGBgCgojIyMjIDEuMi40IEhUVFAgbWV0aG9kIG9mIHJlcXVlc3QgdnMuIFdEUVMgcmVzcG9uc2UgdGltZQoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lIDwtIHJlYWQuY3N2KCdjcm9zc1RhYnNfbWV0aG9kLmNzdicsIAogICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgCiAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLCAKICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpwRnJhbWUkZHNfbWV0aG9kIDwtIGZhY3RvcihwRnJhbWUkZHNfbWV0aG9kLAogICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSB1bmlxdWUocEZyYW1lJGRzX21ldGhvZCkpCmdncGxvdChkYXRhID0gcEZyYW1lLCAKICAgICAgIGFlcyh4ID0gZHNfbWV0aG9kLCAKICAgICAgICAgICB5ID0gbWVhbl9xdWVyeV90aW1lLCAKICAgICAgICAgICBsYWJlbCA9IHBhc3RlMCgiTWVkOiAiLCBtZWRpYW5fcXVlcnlfdGltZSkpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjE1LCBjb2xvciA9ICdkYXJrYmx1ZScsIGZpbGwgPSAnYmx1ZScsIHN0YXQgPSAnaWRlbnRpdHknKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKyAKICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMpICsgCiAgeGxhYignSFRUUCBtZXRob2Qgb2YgcmVxdWVzdCcpICsgeWxhYignTWVhbiBRdWVyeSBUaW1lIChzZWNzKScpICsKICB0aGVtZV9idygpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSAgZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpgYGAKCiMjIyMgMS4yLjUgU2VydmVyIHJlc3BvbnNlIGNvZGUgdnMuIFdEUVMgcmVzcG9uc2UgdGltZQoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lIDwtIHJlYWQuY3N2KCdjcm9zc1RhYnNfc3RhdHVzX2NvZGUuY3N2JywgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsIAogICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnBGcmFtZSRkc19zdGF0dXNfY29kZSA8LSBmYWN0b3IocEZyYW1lJGRzX3N0YXR1c19jb2RlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHVuaXF1ZShwRnJhbWUkZHNfc3RhdHVzX2NvZGUpKQpnZ3Bsb3QoZGF0YSA9IHBGcmFtZSwgCiAgICAgICBhZXMoeCA9IGRzX3N0YXR1c19jb2RlLCAKICAgICAgICAgICB5ID0gbWVhbl9xdWVyeV90aW1lLCAKICAgICAgICAgICBsYWJlbCA9IHBhc3RlMCgiTWVkOiAiLCBtZWRpYW5fcXVlcnlfdGltZSkpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjE1LCBjb2xvciA9ICdkYXJrb3JhbmdlJywgZmlsbCA9ICdvcmFuZ2UnLCBzdGF0ID0gJ2lkZW50aXR5JykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzKSArIAogIHhsYWIoJ1N0YXR1cyBDb2RlJykgKyB5bGFiKCdNZWFuIFF1ZXJ5IFRpbWUgKHNlY3MpJykgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9ICBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpCmBgYAoKIyMjIyAxLjIuNiBPdXRwdXQgZm9ybWF0IHZzLiBXRFFTIHJlc3BvbnNlIHRpbWUKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgbWVzc2FnZSA9IEZ9CnBGcmFtZSA8LSByZWFkLmNzdignY3Jvc3NUYWJzX2Zvcm1hdC5jc3YnLCAKICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsIAogICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwgCiAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKcEZyYW1lJGRzX2Zvcm1hdCA8LSBmYWN0b3IocEZyYW1lJGRzX2Zvcm1hdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gdW5pcXVlKHBGcmFtZSRkc19mb3JtYXQpKQpnZ3Bsb3QoZGF0YSA9IHBGcmFtZSwgCiAgICAgICBhZXMoeCA9IGRzX2Zvcm1hdCwgCiAgICAgICAgICAgeSA9IG1lYW5fcXVlcnlfdGltZSwgCiAgICAgICAgICAgbGFiZWwgPSBwYXN0ZTAoIk1lZDogIiwgbWVkaWFuX3F1ZXJ5X3RpbWUpKSkgKyAKICBnZW9tX2Jhcih3aWR0aCA9IC4xNSwgY29sb3IgPSAnZGFya3Zpb2xldCcsIGZpbGwgPSAndmlvbGV0Jywgc3RhdCA9ICdpZGVudGl0eScpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMykgKyAKICB4bGFiKCdTdGF0dXMgQ29kZScpICsgeWxhYignTWVhbiBRdWVyeSBUaW1lIChzZWNzKScpICsKICB0aGVtZV9idygpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSAgZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpgYGAKCiMjIyAxLjMgVGhlIGRpc3RyaWJ1dGlvbiBvZiBXRFFTIHByb2Nlc3NpbmcgdGltZXMKCiMjIyMgMS4zLjEgVGhlIGRpc3RyaWJ1dGlvbiBvZiBXRFFTIHByb2Nlc3NpbmcgdGltZXMgYWNyb3NzIFdNRiBEYXRhY2VudGVyL0hvc3RzCgoqKk5PVEUuKiogUGxlYXNlIG1pbmQgdGhhdCB3ZSB1c2UgYSBsb2dhcml0aG1pYyBzY2FsZSBmb3IgdGhlIFdEUVMgcXVlcnkgcHJvY2Vzc2luZyB0aW1lcy4gVGhlIGRpc3RyaWJ1dGlvbnMgYXJlICpoaWdobHkgc2tld2VkKiBvbiB0aGVpciBuYXR1cmFsIHNjYWxlcy4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgbWVzc2FnZSA9IEZ9CnBGcmFtZSA8LSByZWFkLmNzdigncXREaXN0cmlidXRpb25fRGF0YWNlbnRlckhvc3QuY3N2JywgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsIAogICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmdncGxvdChkYXRhID0gcEZyYW1lLCAKICAgICAgIGFlcyh4ID0gbG9nKGRzX3F1ZXJ5X3RpbWUpKSkgKwogIGdlb21fZGVuc2l0eShzaXplID0gLjI1LCBjb2xvciA9ICJkYXJrZ3JlZW4iLCBmaWxsPSJncmVlbiIsIGFscGhhID0gLjIpICsKICBmYWNldF93cmFwKH5kc19kYXRhY2VudGVyX2hvc3QpICsKICB4bGFiKCdsb2coV0RRUyBwcm9jZXNzaW5nIHRpbWUpJykgKyB5bGFiKCdEZW5zaXR5JykgKyAKICB5bGltKGMoMCwgMSkpICsgCiAgdGhlbWVfYncoKSsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSAgZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpgYGAKCiMjIyMgMS4zLjIgVGhlIGRpc3RyaWJ1dGlvbiBvZiBXRFFTIHByb2Nlc3NpbmcgdGltZXMgYWNyb3NzIG91dHB1dCBmb3JtYXRzCgoqKk5PVEUuKiogVGhlIGB0ZXh0L3BsYWluYCBvdXRwdXQgZm9ybWF0IGhhcyB0d28gZmV3IG9ic2VydmF0aW9ucy4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgbWVzc2FnZSA9IEZ9CnBGcmFtZSA8LSByZWFkLmNzdigncXREaXN0cmlidXRpb25fRm9ybWF0LmNzdicsIAogICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgCiAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLCAKICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpnZ3Bsb3QoZGF0YSA9IHBGcmFtZSwgCiAgICAgICBhZXMoeCA9IGxvZyhkc19xdWVyeV90aW1lKSkpICsKICBnZW9tX2RlbnNpdHkoc2l6ZSA9IC4yNSwgY29sb3IgPSAiZGFya3Zpb2xldCIsIGZpbGw9InZpb2xldCIsIGFscGhhID0gLjIpICsKICBmYWNldF93cmFwKH5kc19mb3JtYXQpICsKICB4bGFiKCdsb2coV0RRUyBwcm9jZXNzaW5nIHRpbWUpJykgKyB5bGFiKCdEZW5zaXR5JykgKyAKICB5bGltKGMoMCwgMSkpICsgCiAgdGhlbWVfYncoKSsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSAgZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpgYGAKCioqKgpHb3JhbiBTLiBNaWxvdmFub3ZpxIcKCldpa2ltZWRpYSBEZXV0c2NobGFuZCwgRGF0YSBTY2llbnRpc3QKRGF0YUtvbGVrdGl2LCBPd25lcgoKMjAyMC4KCmNvbnRhY3Q6IGdvcmFuLm1pbG92YW5vdmljX2V4dEB3aWtpbWVkaWEuZGUKCiFbXShfaW1nL0RLX0xvZ29fMTAwLnBuZykKIVtdKF9pbWcvV2lraWRhdGEtbG9nby1lbi5wbmcpCiFbXShfaW1nL1dpa2ltZWRpYV9EZXV0c2NobGFuZF9Mb2dvX3NtYWxsLnBuZykKCgoKCgo=