Report timestamp: 2020-04-29 13:03:08


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. 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.

In the following sections we describe (1) the sampling approach taken to collect a relevant set of SPARQL queries observed at the (WDQS) endpoint in three weeks of March 2020, (2) the feature engineering approach taken to analyze the composition of queries and extract meaningful “atomic elements” of SPARQL that will be used in an attempt to predict the WDQS response time for a query, (3) the definition of a criterion for selecting a critical query for WDQS (i.e. the one that makes too much use of the service’s resources), and (4) the ML (optimization) approach, relying on XGBoost, taken to build a predictive model for WDQS server response times starting from the extracted SPARQL feature.

Goal

  • An optimization system for WDQS of any form will need to decide if some particular SPARQL query needs optimization or not,
  • and thus it is wise to figure out a criterion that sorts out the queries in respect to whether they should receive any special treatment or not.
  • Our approach is to build a ML model with a goal to predict if the time taken by WDQS to process a particular query is excessively long or typical,
  • based on a set of features extracted from the SPARQL code of the queries themselves, and eventualy
  • recommend to use that set of features to parse the incoming query before WDQS processing and decide if it needs optimization or not basing on the features that the query encompasses.

Criterion

  • In the event.wdqs_external_sparql_query table (lives in the WMF Data Lake, Hadoop) we find the query_time variable representing the WDQS processing time in seconds for each query observed at the endpoint;
  • We partition all observed queries in two groups: 1 - queries that took excessively long processing times, defined as mild outliers: query_time >= Q3+1.5×IQR, where Q3 represents the 3rd quartile (75%) of the WDQS response time distribution while IQR represents the inter-quartile range, IQR = Q3-Q1; 0 - queries that took “typical” processing times - everything with the processing time below what we have just defined as mild outliers.

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.

NOTE. It is immediately obvious that not all fields from the event.wdqs_external_sparql_query table are candidates for a predictive model of the described kind: ds_status_code, the server HTTP response code, is not know before the query is actually processed. We keep this variable in the dataset having in mind that it might serve as a potential criterion in similar prediction tasks someday.

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/')
reportingDir <- paste0(fPath, '_event/_reporting/')

### --- 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. Data Cleaning and Feature Engineering

1.1 Data Cleaning

The following R script checks for data consistency The following chunk of code also parses the dt (timestamp) field and extracts year, month, day, hour, and minute when the query was run. Also, and additional variable is produced from a simple concatenation of year, month, day, hour, and minute: ymdmh. This variable will be subsequently used to calculate the number of queries that were run in a particular minute observed in the sample, per WMF data center (eqiad or codefw) and back-end host in the data center. This is a potential “environmental” variable (i.e. taken from the WDQS/Blazegraph enviornment) in predictive modeling of server response time. It can characterize the query processing context by estimating how many queries were run against Blazegraph concurrently in the same minute, on the same server, when a particular query under of interest was run. Any future WDQS optimization system relying on this variable would have to rely on Blazegraph somehow broadcasting this variable to the predictive modeling API.

NOTE. In this step we also eliminate all queries that have resulted in a http_status of 4**: client side errors. Obviously, incorrectly stated SPARQL queries are of no use to us in any attempt to optimize the WDQS processing time. So if a query fails an initial syntax analysis on the endpoint it does not enter our analysis.

#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WDQS_Event_S2_CleanUp_Inspect.R, v. 0.0.1
### --- Script: WDQS_Event_S2_CleanUp_Inspect.R, v. 0.0.1
### --- Description: WDQS_Event_S2_CleanUp_Inspect.R performs 
### --- feature engineering of the SPARQL queries data 
### --- collected w. WDQS_Event_S1_ETL.R 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/03/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/>.
### ---------------------------------------------------------------------------

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

### --- 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/'
dataDir <- paste0(fPath, '_event/_data/')
analyticsDir <- paste0(fPath, '_event/_analytics/')
reportingDir <- paste0(fPath, '_event/_reporting/')

### ---------------------------------------------------------------------------
### --- Section 1. Load, clean, and decode the SPARQL queries data set
### ---------------------------------------------------------------------------

### --- Sys.setlocale()
Sys.setlocale('LC_ALL','C')

### --- load data
lF <- list.files(dataDir)
lF <- lF[grepl("^wdqs_data_sample", lF)]
dataSet <- lapply(paste0(dataDir, lF), fread, header = T, quote="")
dataSet <- rbindlist(dataSet)
dim(dataSet)

### --- inspect
colnames(dataSet)
str(dataSet)

### --- clean data: sparql
dataSet$sparql <- gsub("__NEWLINE__", "\\\\n", dataSet$sparql)

### --- clean data: max_query_millis
sumNA <- sum(is.na(dataSet$max_query_millis))
sumNA
table(dataSet$max_query_millis)
# - all dataSet$max_query_millis values are: 60000; dropping
dataSet$max_query_millis <- NULL

### --- clean data: status_code
sumNA <- sum(is.na(dataSet$status_code))
sumNA
table(dataSet$status_code)
# - remove status_code = 4*
w400 <- which(grepl('^4', dataSet$status_code))
length(w400)
dim(dataSet)
if (length(w400) > 0) {
  dataSet <- dataSet[-w400, ]
}
dim(dataSet)

### --- clean data: format
sumNA <- sum(is.na(dataSet$format))
sumNA
table(dataSet$format)
# - fix: lower case and upper case
dataSet$format <- tolower(dataSet$format)
table(dataSet$format)

### --- clean data: domain
sumNA <- sum(is.na(dataSet$domain))
sumNA
table(dataSet$domain)
# - all dataSet$domain values are: query.wikidata.org; dropping
dataSet$domain <- NULL

### --- clean data: method
sumNA <- sum(is.na(dataSet$method))
sumNA
table(dataSet$method)

### --- clean data: backend_host
sumNA <- sum(is.na(dataSet$backend_host))
sumNA
table(dataSet$backend_host)

### --- clean data: datacenter
sumNA <- sum(is.na(dataSet$datacenter))
sumNA
table(dataSet$datacenter)

### --- clean data: query_time
sumNA <- sum(is.na(dataSet$query_time))
sumNA
summary(dataSet$query_time)

### --- validate dt (timestamp)
sumNA <- sum(is.na(dataSet$dt))
sumNA
w <- which(dataSet$dt == "-")
length(w)
w <- which(!(grepl("\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\.\\d+Z", 
                   dataSet$dt)))
length(w)
# - nothing crucial:
dataSet$dt[w] 
### --- extract year, month, day, hour, and minute from dt (timestamp)
dataSet$day <- substr(dataSet$dt, 9, 10)
dataSet$hour <- substr(dataSet$dt, 12, 13)
dataSet$minute <- substr(dataSet$dt, 15, 16)
dataSet$ymdhm <- paste(dataSet$day,
                       dataSet$hour,
                       dataSet$minute,
                       sep = "-")
### --- remove dataSet$dt
dataSet$dt <- NULL

### --- inspect: format and query_time
dataSet %>% 
  dplyr::select(format, query_time) %>% 
  dplyr::group_by(format) %>% 
  dplyr::summarise(mean = mean(query_time, na.rm = T), 
                   median = median(query_time, na.rm = T), 
                   min = min(query_time, na.rm = T), 
                   max = max(query_time, na.rm = T),
                   n = n()) %>% 
  dplyr::arrange(desc(median))

### --- inspect: method and query_time
dataSet %>% 
  dplyr::select(method, query_time) %>% 
  dplyr::group_by(method) %>% 
  dplyr::summarise(mean = mean(query_time, na.rm = T), 
                   median = median(query_time, na.rm = T), 
                   min = min(query_time, na.rm = T), 
                   max = max(query_time, na.rm = T), 
                   n = n()) %>% 
  dplyr::arrange(desc(median))

### --- inspect: status_code and query_time
dataSet %>% 
  dplyr::select(status_code, query_time) %>% 
  dplyr::group_by(status_code) %>% 
  dplyr::summarise(mean = mean(query_time, na.rm = T), 
                   median = median(query_time, na.rm = T), 
                   min = min(query_time, na.rm = T), 
                   max = max(query_time, na.rm = T), 
                   n = n()) %>% 
  dplyr::arrange(desc(median))

### --- inspect: backend_host and query_time
dataSet %>% 
  dplyr::select(backend_host, query_time) %>% 
  dplyr::group_by(backend_host) %>% 
  dplyr::summarise(mean = mean(query_time, na.rm = T), 
                   median = median(query_time, na.rm = T), 
                   min = min(query_time, na.rm = T), 
                   max = max(query_time, na.rm = T), 
                   n = n()) %>% 
  dplyr::arrange(desc(median))

### --- inspect: datacenter and query_time
dataSet %>% 
  dplyr::select(datacenter, query_time) %>% 
  dplyr::group_by(datacenter) %>% 
  dplyr::summarise(mean = mean(query_time, na.rm = T), 
                   median = median(query_time, na.rm = T), 
                   min = min(query_time, na.rm = T), 
                   max = max(query_time, na.rm = T), 
                   n = n()) %>% 
  dplyr::arrange(desc(median))

### --- inspect: day and query_time
dataSet %>% 
  dplyr::select(day, query_time) %>% 
  dplyr::group_by(day) %>% 
  dplyr::summarise(mean = mean(query_time, na.rm = T), 
                   median = median(query_time, na.rm = T), 
                   min = min(query_time, na.rm = T), 
                   max = max(query_time, na.rm = T), 
                   n = n()) %>% 
  dplyr::arrange(desc(median))

### --- inspect: hour and query_time
dataSet %>% 
  dplyr::select(hour, query_time) %>% 
  dplyr::group_by(hour) %>% 
  dplyr::summarise(mean = mean(query_time, na.rm = T), 
                   median = median(query_time, na.rm = T), 
                   min = min(query_time, na.rm = T), 
                   max = max(query_time, na.rm = T), 
                   n = n()) %>% 
  dplyr::arrange(desc(median))

### --- add ID, uniqueQueryID
dataSet$id <- 1:dim(dataSet)[1]
uniqueQueries <- unique(dataSet$sparql)
uniqueQueries <- data.frame(sparql = uniqueQueries, 
                            uniqueSparqlId = 1:length(uniqueQueries), 
                            stringsAsFactors = F)
dim(uniqueQueries)
dataSet <- dataSet %>% 
  dplyr::left_join(uniqueQueries, 
                   by = "sparql")
rm(uniqueQueries)

### --- rearrange, store clean data set
colnames(dataSet)
dataSet <- dataSet %>% 
  dplyr::select('id', 'uniqueSparqlId', 'day', 'hour', 'minute', 'ymdhm', 
                       'sparql', 'format', 'method', 'status_code', 
                       'backend_host', 'datacenter', 'query_time')
# - final check:
w <- which(complete.cases(dataSet))
length(w)
dim(dataSet)

### --- store to analyticsDir
filename <- paste0(analyticsDir, 
                   'wdqs_data_sample_', 
                   substr(as.character(Sys.time()), 1, 10), 
                   '.csv')
write.csv(dataSet, filename)

1.2 Feaure Engineering

The SPARQL queries in our sample, present in the sparql field of the dataset retrieved from the event.wdqs_external_sparql_query table, need to be parsed in order to extract a set of meaningful features to use in predictive modeling of server response time. As in the case of Natural Language Processing, we need to differentiate between three possible levels of analysis here:

  • syntax: the composition of SPARQL keywords, operators, variables, and functions result in a specific description of the data that need to be obtained, and their analysis on behalf of Blazegraph determines the query execution plan (via Abstract Syntax Tree, AST, derived from query analysis); determines query validity and query execution plan;
  • semantics: the exact mapping of query syntax to particular variables that need to be instantiated in order to obtain the desired result from WDQS; theoretically, it determines truth relations; in practice, we can say that it precisely specifies the expected query result;
  • pragmatics: given the SPARQL language conventions are satisfied, any query migth instantiate various variable names, use (more or less efficiently) various syntactic constructions to obtain the same effect, include code comments, etc.: the way users use the SPARQL query language to communicate their goals to the WDQS.

Our approach to feature engineering is to parse each SPARQL query with a set of regular expressions in order to (a) single out the “atomic elements” of the SPARQL language (keywords, functions, IRI mentions, variables, literals, operators, Wikibase parameters, etc.), (b) compute a set of abstract variables that can be used to describe the query, e.g.: the number of different variables instantiated (i.e. how many unique variables there are in a query), the total number of variables mentioned (including repeated uses of the same variable), the number of IRI references in a query, how many variables also request labels, and similar.

With this appproach, which is essentialy the same as what is typically done in Natural Language Processing, query syntax and semantics are in effect fused. A question could be posed: why keep specific variable names, or comments, when it is clear that the later are not even processed while variable names are conventional and WDQS users are free to use them as long as they satisfy the rules of the SPARQL syntax? These aspects of query analysis are important from the viewpoint of query pragmatics and carry useful information indeed. Of course the variable names are conventional, but imagine a community of librarians using WDQS who learn SPARQL from each other and thus frequently use variable names like ?author and/or ?authorLabel? If such “typical” variable names are frequently used in similar queries, they can act as informative features in the prediction of query response time indeed. Comments: many WDQS bots and/or human users might use a “signature” comment (and indeed some do) of some form, for example. If such bots run their queries against WDQS frequently, the comments in their code might also act as predictive feaures for the query perfomance.

NOTE. The current regex analysis of SPARQL queries is approximate and, most of time, correct in a sense of being able to extract intuitively meaningful atoms of SPARQL. It is approximate in a sense that the regular expressions use are derived by the author of this report by directly analyzing the typical SPARQL queries with only limited attention payed to the formal definition of SPARQL (e.g. its Backus–Naur forms, or the formal regex definitions of its various syntactic constituents). The feature engineering procedure used in our current analyses can certainly receive further improvment.

Beyond extracting SPARQL “atoms”, the following features are derived as frequencies (counts) for each query:

  • nchar - the length of the query in characters
  • __vars__ - how many variables are used?
  • __vars_usage__ - how many variable mentions there are in total?
  • __vars_label__ - how many variables request labels also?
  • __vars_label_usage__ - how many times are variable labels mentioned in a query?
  • __IRI_REF__ - how many IRI references are used in the query?
  • __LITERAL__ - how many SPARQL literals are used in the query?

So the final description of any SPARQL query, following these feature engineering steps, is a vector of counts for each extracted feature, including: (a) tokens (e.g. each IRI or SPARQL literal mentioned, for example), as well as (b) types (e.g. how many __IRI_REFs__ there are in a query in total).

NOTE 1. Approximating syntax. Obviously, this feature engineering approach does not analyze the structural relations between the language constituents: its syntax. However, some of the extracted features clearly “correlate” with the query syntactic complexity. For example, the count of { tokens, or the count of SELECT keywords, used in a query, clearly speaks about the query depth (i.e. how many sub-queries do we have, if any). So the syntactic information is present in our feature engineering more or less up to a degree dictated by how much useful information is carried by a mention of a particular SPARQL keyword, or function, or operator, etc; the number of variables used, the query “depth”, and similar. More useful syntactic information can be obtained from Blazegraph by running a query in the EXPLAIN mode (similar to what one would in SQL), see: https://www.mediawiki.org/wiki/Wikidata_Query_Service/User_Manual#Explain_Query. However, even if featurus obtained from the query execution plan in this way might prove to be useful, it is unclear at this point whether the SPARQL query execution plan can be obtained from Blazegraph without actually running the query.

NOTE 2. The following script performs feature engineering followint the above outlined principles. It also computes the n_conc_queries variable: the number of queries whose processing started concurrently in the same minute, same WMF data center, on the same back-end host. The queries_vocabulary.csv file contains a list of all extracted features sorted in a descending order of their usage frequency across all SPARQL queries in the sample. The queries_coverage.csv dataset, on the other hand, lists features alongside the number of queries in the sample that make use of them, and also expresses feature coverage in percents. Additional operations are performed on column (feature) names in order to comfort them to XGBoost standards. The top_f variable controls how many of the most frequently used features will be selected for the data set that is passed on to the ML phase with XGBoost.

#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WD_WDQS_Analytics_FeatureEngineering.R, v. Beta 0.1
### --- Script: WD_WDQS_Analytics_FeatureEngineering.R, v. Beta 0.1
### --- Description: WD_WDQS_Analytics_FeatureEngineering.R performs 
### --- feature engineering of the SPARQL queries data 
### --- collected w. WD_WDQS_Analytics_CollectData.R from the 
### --- wmf.webrequest 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: 
### --- /home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics
### --- on stat1005
### --- Date: 2020/03/20
### ---------------------------------------------------
#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WDQS_Event_S3_FeatureEngineering.R, v. 0.0.1
### --- Script: WDQS_Event_S3_FeatureEngineering.R, v. Beta 0.1
### --- Description: WDQS_Event_S3_FeatureEngineering.R performs 
### --- feature engineering of the SPARQL queries data 
### --- collected w. WDQS_Event_S1_ETL.R 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: on 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/>.
### ---------------------------------------------------------------------------

### --- setup
library(data.table)
library(stringr)
library(dplyr)
library(tidyr)
library(snowfall)

### --- 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/'
dataDir <- paste0(fPath, '_event/_data/')
analyticsDir <- paste0(fPath, '_event/_analytics/')
reportingDir <- paste0(fPath, '_event/_reporting/')

### ---------------------------------------------------------------------------
### --- Section 1. Parse SPARQL queries
### ---------------------------------------------------------------------------

### --- load queries
filename <- 'wdqs_data_sample_2020-04-22.csv'
dataSet <- fread(paste0(analyticsDir, filename))
dataSet$V1 <- NULL
# - keep only unique queries
w <- which(!duplicated(dataSet$uniqueSparqlId))
if (length(w) > 0) {
  dataSet <- dataSet[w]
}
dim(dataSet)

### --- prepare queries for parsing
queries <- dataSet$sparql
uniqueQueryId <- dataSet$uniqueSparqlId
rm(dataSet); gc()

### --- function: parse_SPARQL()
parse_SPARQL <- function(tq) {
  
  # - get nchar()
  tq_nchar <- nchar(tq)
  
  # - split by "\n"
  tq <- unlist(lapply(tq, function(x) {
    strsplit(x, split = "\\\\n")
  }))
  
  # - trim trailing space at the beginning and end, if any
  tq <- str_trim(tq, side = "both")
  
  # - remove empty lines if any
  wEmpty <- which(tq == "")
  if (length(wEmpty) > 0) {
    tq <- tq[-wEmpty] 
  }
  
  # - collect IRI references
  tq_iri <- unlist(str_extract_all(tq, '<([[:alnum:]]|[[:punct:]])+>'))
  
  # - match IRI references
  tq <- gsub('<([[:alnum:]]|[[:punct:]])+>', " __IRI_REF__ ", tq)
  
  # - process comments
  tq_comments <- unlist(str_extract_all(tq, "#.+$"))
  tq <- gsub("#.+$", "", tq)
  wEmpty <- which(tq == "")
  if (length(wEmpty) > 0) {
    tq <- tq[-wEmpty] 
  }
  
  # - space to the left and right of 
  # - brackets, operators, and punctuation
  tq <- gsub("\\{", " \\{ ", tq)
  tq <- gsub("\\}", " \\} ", tq)
  tq <- gsub("\\)", " \\) ", tq)
  tq <- gsub("\\(", " \\( ", tq)
  tq <- gsub("\\]", " \\] ", tq)
  tq <- gsub("\\[", " \\[ ", tq)
  tq <- gsub("([^<]|[^>]|[^!])=", " = ", tq)
  tq <- gsub("!=", " != ", tq)
  tq <- gsub(">=", " >= ", tq)
  tq <- gsub("<=", " <= ", tq)
  tq <- gsub("!([^=])", " ! ", tq)
  tq <- gsub(">([^=])", " > ", tq)
  tq <- gsub("<([^=])", " < ", tq)
  tq <- gsub("\\^", " \\^ ", tq)
  tq <- gsub("\\+", " \\+ ", tq)
  tq <- gsub("-", " - ", tq)
  tq <- gsub("\\*", " \\* ", tq)
  tq <- gsub("/", " / ", tq)
  tq <- gsub("\\|\\|", " \\|\\| ", tq)
  tq <- gsub("([^\\|])\\|([^\\|])", " \\| ", tq)
  tq <- gsub("&&", " && ", tq)
  tq <- gsub(",", " , ", tq)
  tq <- gsub("\\.", " \\. ", tq)
  tq <- gsub(";", " ; ", tq)
  tq <- gsub("\\s+", " ", tq)
  
  # - match literals
  tq <- gsub('(\'|")+([^\'"])+(\'|")+', " __LITERAL__ ", tq)
  tq <- gsub('\\b[[:digit:]]+\\b', " __LITERAL__ ", tq)
  
  # - "word tokenization": split by "\s"
  tq <- unlist(lapply(tq, function(x) {
    strsplit(x, split = "\\s")
  }))
  
  # - remove: '"', ".", ","
  tq <- gsub('("|\\.|,)+', '', tq)
  
  # - remove: "'"
  tq <- gsub("'+", '', tq)
  
  # - count unique variables
  num_vars <- length(unique(tq[grepl('^\\?|^\\$', tq)]))
  
  # - count variable use
  vars_usage <- length(tq[grepl('^\\?|^\\$', tq)])
  
  # - count unique label variables
  num_vars_label <- length(unique(tq[grepl('[^:]label$', tq, ignore.case = T)]))
  
  # - count label variable use
  label_vars_usage <- length(tq[grepl('[^:]label$', tq, ignore.case = T)])
  
  # - add variables use and unique variable count to tq
  tq <- c(tq, 
          rep('__vars__', num_vars), 
          rep('__vars_usage__', vars_usage), 
          rep('__vars_label__', num_vars_label),
          rep('__vars_label_usage__', label_vars_usage))
  
  # - tolower()
  tq <- tolower(tq)
  
  # - clean from: "]", "}", ";", ")" if present anywhere
  tq <- gsub("\\]|\\}|\\)|;", "", tq)
  
  # - remove empty elements from tq if any:
  wEmpty <- which(grepl("^\\s+$|^$", tq))
  if (length(wEmpty) > 0) {tq <- tq[-wEmpty]}
  
  # - enter comments
  if (length(tq_comments) > 0) {
    tq <- c(tq, tq_comments)
    }
  
  # - enter tq_iri
  if (length(tq_iri) > 0) {
    tq <- c(tq, tq_iri)
    }
  
  # - tq feature frequency as data.frame
  tq <- as.data.frame(table(tq))
  
  # - enter nchar(tq) as feature:
  tq <- rbind(tq, 
              data.frame(tq = 'nchar',
                         Freq = tq_nchar))
  # - ouput: tq
  return(tq)
}

### --- Parse SPARQL queries in parallel
sfInit(parallel = T, cpus = 36)
t1 <- Sys.time()
sfExport("queries")
sfExport("parse_SPARQL")
sfLibrary(stringr)
# - process
queries_processed <- sfClusterApplyLB(queries, parse_SPARQL)
# - stop cluster
sfStop()
print(paste0("Parsing took: ", 
             round(difftime(Sys.time(), t1, units = "min"), 2)))

### --- collect results
names(queries_processed) <- uniqueQueryId
queries_processed <- rbindlist(queries_processed, 
                               idcol = T, 
                               fill = T, 
                               use.names = T)
colnames(queries_processed) <- 
  c('uniqueSparqlId', 'feature', 'freq')
write.csv(queries_processed, paste0(analyticsDir, "queries_processed.csv"))

### --- produce vocabulary: feature frequency
vocabulary <- queries_processed %>%
  dplyr::select(feature, freq) %>% 
  dplyr::group_by(feature) %>% 
  dplyr::summarise(freq = sum(freq)) %>% 
  dplyr::arrange(desc(freq))
write.csv(vocabulary, paste0(analyticsDir, "queries_vocabulary.csv"))
dim(vocabulary)
head(data.frame(vocabulary), 400)

### --- in how many queries is a particular feature used?
query_features <- queries_processed %>% 
  dplyr::select(feature) %>% 
  dplyr::group_by(feature) %>% 
  dplyr::summarise(num_queries = n()) %>% 
  dplyr::arrange(desc(num_queries))
query_features$percent_queries <- 
  query_features$num_queries/length(unique(queries_processed$uniqueSparqlId))
write.csv(query_features, paste0(analyticsDir, "queries_coverage.csv"))
head(data.frame(query_features), 400)

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

### ---------------------------------------------------------------------------
### --- Section 2. Prepare Model Datasets
### ---------------------------------------------------------------------------

### --- 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
# - remove features that will not be used in the ML phase
dataSet$sparql <- NULL
dataSet$minute <- 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)

### --- comfort column names to {xgboost}
modelFrameColumns <- colnames(dataSet)
# - define replacements
mchar_regex <- c('\\.', '\\\\', '\\|', '\\(', '\\)',
                 '\\[', '\\]', '\\{', '\\}', '\\^',
                 '\\$', '\\*', '\\+', '\\?')
mchar_regex_replace <- c('_dot_', '_backslash_', '_vbar_', '_left_bracket_', '_right_bracket_', 
                         '_left_sq_bracket_', '_right_sq_bracket_', '_left_curly_', '_right_curly_', '_caret_', 
                         '_dollar_', '_asterisk_', '_plus_', '_question_mark_')
modelFrameColumns <- unname(sapply(modelFrameColumns, function(x) {
  for (i in 1:length(mchar_regex)) {
    if (grepl(mchar_regex[i], x)) {
      x <- gsub(mchar_regex[i], mchar_regex_replace[i], x)
    }
  }
  return(x)
}))
sparql_ops <- c('#', '@', ':', ';', '/', '>', '<', '=', '<=', '>=')
sparql_ops_replace <- c('_hash_', '_at_', '_colon_', '_semi_colon_', '_slash_', '_greater_than_', '_less_than_', 
                        '_equals_', '_less_than_or_equal_', '_greater_than_or_equal_')
modelFrameColumns <- unname(sapply(modelFrameColumns, function(x) {
  for (i in 1:length(sparql_ops)) {
    if (grepl(sparql_ops[i], x)) {
      x <- gsub(sparql_ops[i], sparql_ops_replace[i], x)
    }
  }
  return(x)
}))
modelFrameColumns <- paste0("f_", modelFrameColumns)
# - produce and store modelFrameFeatureCode
modelFrameFeatureCode <- data.frame(feature = colnames(dataSet), 
                                    xgboost_feature = modelFrameColumns, 
                                    stringsAsFactors = F)
filename <- paste0('modelFrameFeatureCode', '_', top_f, '.csv')
write.csv(modelFrameFeatureCode,
          paste0(analyticsDir, filename))
# - store dataSet w. {xgboost} column names
colnames(dataSet) <- modelFrameFeatureCode$xgboost_feature
filename <- paste0('modelFrame', '_', top_f, '.csv')
write.csv(dataSet,
          paste0(analyticsDir, filename))

Again, The goal is to derive a description of SPARQL queries via feature vectors. For example:

iri_ref:11, literal:1, vars:5, vars_label:1, vars_label_usage:1, vars_usage:20, ?item:15, ?type:1, ?typelabel:1, ?url:2, ?wikipedia:1, {:14, http://standartconta.com.tr/:1, http://standartconta.com.tr:1, http://www.standartconta.com.tr/:2, http://www.standartconta.com.tr:1, http://www.www.standartconta.com.tr/:1, https://standartconta.com.tr/:1, https://standartconta.com.tr:1, https://www.standartconta.com.tr/:1, https://www.standartconta.com.tr:1, https://www.www.standartconta.com.tr/:1, bd:serviceparam:1, distinct:1, optional:1, schema:about:1, select:1, service:1, union:10, wdt:p31:1, wdt:p856:12, where:1, wikibase:label:1, wikibase:language:1, nchar:903

In this example of a query feature vector we see that the query had used 11 IRI references (__iri_ref__:11), one literal (__literal__:1), five variables (__vars__:5) that were mentioned 20 times altogether (__vars_usage__:20), used wikibase:label once (wikibase:label:1), the UNION keyword 10 times (union:10), that the length of the query in characters is nchar:903, etc.

2. Machine Learning w. XGBoost: can we predict mild outliers on WDQS response time from SPARQL query features?

Thus far,

  • all SPARQL queries are represented as feature vectors, and
  • a criterion variable has been defined (mild outliers on WDQS server response times).

Our goal is to select a predictive model that will use the feauture vectors to predict whether the respective queries’ response times are critical (i.e. are they outliers on query_time) or not. At the same time, the model will be performing feature selection (i.e. XGBoost will not necessarily use all of the provided features to build a sequence of decision trees for binary classification) and informing us about the value of each feature in the prediction. This will allows us to select a small set of SPARQL feauters that can be used to characterize the queries as resulting in typical vs. extreme WDQS response times. And such small feature vocabulary is exactly what we are looking for in order to be able to develop a WDQS optimization engine in the future: a system that would fast can any given SPARQL query and decide wheter it needs to be handled as a special, potentially difficult case to process - or should the processing proceed as usual.

We have used XGBoost via the R {xgboost} package to train many predictive models that attempt to categorize SPARQL queries as critical or not in accordance with the above stated criterion. XGBoost - short for Extreme Gradient Boosting - is a ML method to estimate an ensemble of mathematical models - decision trees in our case - that jointly work to predict the response variable. Its description is really way out of scope of this Report. This technique is as known to be notoriously good in extracting almost every and the last piece of useful information from the training data (feauture vectors describing the SPARQL queries in this case). The XGBoost models involve a large number of parameters that must be fine-tuned in order for it to derive a plausible predictive model for the task at hand. We have experimented with various parameter settings and data sets encompassing a variable number of extracted SPARQL features to reach the following conclusions:

  • the number of features initially used to build a model does not significantly change the accuracy, true positive rate, and false positive rate (both to be explained soon) with which the model can tell the critical SPARQL queries from typical ones; this is most probably entirely due to the fact that XGBoost performs feature selection along the way of its training; this is also very important because it means that we can use only a small fraction of the most frequently used SPARQL features to train a predictive model - and the model with less features will make a predictive run faster than the one encompassing a large number of features;
  • mostly, the performance of the predictive model is not seriously affected by choosing various combinations of parameter settings; we have performed many cross-validations, always additional splitting our 1M sample of SPARQL queries into train and test data (approx. 50% of the sample each), in order to reach this conclusion;
  • the model performance is good, but far from perfect.
#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WDQS_Event_S4A_XGBoostOptimization_Downsampling.R, v. 0.0.1
### --- Script: WDQS_Event_S4A_XGBoostOptimization_Downsampling.R, v. 0.0.1
### --- Description: WDQS_Event_S4A_XGBoostOptimization_Downsampling.R performs 
### --- XGBoost optimization of SPARQL queries WDQS response times 
### --- collected w. WDQS_Event_S1_ETL.R from the 
### --- event.wdqs_external_sparql_query table in the WMF Data Lake.
### --- NOTE. Approach to Class Imbalances: downsampling.
### --- 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.1 Train selected model
### ---------------------------------------------------------------------------

### --- grab dataset
filename <- 'modelFrame_300.csv'
### --- load model dataset
modelFrame <- fread(paste0(analyticsDir, filename))
modelFrame$V1 <- NULL
print(paste0("The dimesion is: ", paste(dim(modelFrame), collapse = ", ")))

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

### --- process model dataset
# - split SPARQL features
ds_features <- colnames(modelFrame)[which(grepl("^f_ds", colnames(modelFrame)))]
modelFrame_ds <- modelFrame[, ..ds_features]
modelFrame_f <- modelFrame %>% 
  dplyr::select(-ds_features)
rm(modelFrame); gc()
# - transform NA to zero in modelFrame_f
# - (for all non ds_ variables:)
modelFrame_f[is.na(modelFrame_f)] <- 0
# - pick dimension of SPARQL features only:
top_f <- dim(modelFrame_f)[2]
# - recompose modelFrame
modelFrame <- cbind(modelFrame_ds, modelFrame_f)
rm(modelFrame_ds); rm(modelFrame_f); gc()

### --- Prima Facie Correlation between:
# - modelFrame$f_ds_n_conc_queries
# - modelFrame$f_ds_query_time
corrFrame <- modelFrame %>% 
  dplyr::select(f_ds_datacenter_host, 
                f_ds_n_conc_queries, 
                f_ds_query_time)
corrFrame <- corrFrame %>% 
  dplyr::group_by(f_ds_datacenter_host) %>% 
  dplyr::summarise(med_n_conc_queries = median(f_ds_n_conc_queries), 
                   med_query_time = median(f_ds_query_time))
cor.test(corrFrame$med_n_conc_queries, 
         corrFrame$med_query_time, 
         method = "pearson")
cor.test(corrFrame$med_n_conc_queries, 
         corrFrame$med_query_time, 
         method = "spearman")

### --- Select only ML features
# - to split train and test: f_ds_ttSplit
modelFrame$f_ds_ttSplit <- round(runif(dim(modelFrame)[1], 0, 1))
# - remove IDs and keep track of f_ds_ttSplit
modelFrame_IDs <- modelFrame %>% 
  dplyr::select(f_ds_id, 
                f_ds_uniqueSparqlId,
                f_ds_ttSplit)
filename <- paste0('modelFrame_IDs_', top_f, ".csv")
write.csv(modelFrame_IDs, 
          paste0(analyticsDir, filename))
rm(modelFrame_IDs); gc()
modelFrame <- modelFrame %>% 
  dplyr::select(-f_ds_id,
                -f_ds_uniqueSparqlId)

### --- Select criterion
### --- Drop all other potential criteria
### --- Potential criteria are:
# - f_ds_query_time_median
# - f_ds_query_time
# - f_ds_query_time_mild
# - f_ds_query_time_extreme
# - f_ds_query_time_class
# - f_ds_status_code
### --- criterion: f_ds_query_time_mild
table(modelFrame$f_ds_query_time_mild)
modelFrame <- modelFrame %>% 
  dplyr::select(-f_ds_query_time, 
                -f_ds_query_time_median,
                -f_ds_query_time_extreme, 
                -f_ds_query_time_class, 
                -f_ds_status_code)

### --- prepare XGBoost structures
# - modelFrame as designMatrix
modelFrame$f_ds_hour <- as.character(modelFrame$f_ds_hour)
modelFrame <- fastDummies::dummy_cols(modelFrame)
# - remove dummified:
modelFrame$f_ds_hour <- NULL
modelFrame$f_ds_day <- NULL
modelFrame$f_ds_format <- NULL
modelFrame$f_ds_method <- NULL
modelFrame$f_ds_datacenter_host <- NULL

# - split: train vs test
trainFrame <- modelFrame[modelFrame$f_ds_ttSplit == 0, ]
testFrame <- modelFrame[modelFrame$f_ds_ttSplit == 1, ]
rm(modelFrame); gc()

### --- Downsampling
table(trainFrame$f_ds_query_time_mild)
positiveSampleSize =  sum(trainFrame$f_ds_query_time_mild == 1)
negativeSample <- trainFrame[trainFrame$f_ds_query_time_mild == 0]
dim(negativeSample)
positiveSample <- trainFrame[trainFrame$f_ds_query_time_mild == 1]
dim(positiveSample)
rm(trainFrame)
negativeSampleChoice <- sample(1:dim(negativeSample)[1], 
                               size = positiveSampleSize, 
                               replace = FALSE, 
                               prob = NULL)
negativeSample <- negativeSample[negativeSampleChoice, ]
dim(negativeSample)
trainFrame <- rbind(negativeSample, 
                    positiveSample)
rm(positiveSample); rm(negativeSample); gc()

# - design matrices 
response_time_train <- trainFrame$f_ds_query_time_mild
trainFrame$f_ds_query_time_mild <- NULL
trainFrame$f_ds_ttSplit <- NULL
trainFrame <- as.matrix(trainFrame)
response_time_test <- testFrame$f_ds_query_time_mild
testFrame$f_ds_query_time_mild <- NULL
testFrame$f_ds_ttSplit <- NULL
testFrame <- as.matrix(testFrame)

# - to {xgboost} DMatrix
trainFrame <- xgb.DMatrix(trainFrame, label = response_time_train)
print(paste0("Dimesion of trainFrame: ", 
             paste(dim(trainFrame), collapse = ", ", sep = "")))
print("testFrame as xgb.DMatrix now.")
testFrame <- xgb.DMatrix(testFrame, label = response_time_test)
print(paste0("Dimesion of testFrame: ", 
             paste(dim(testFrame), collapse = ", ", sep = "")))
# - store testFrame to save RAM on stat100*
xgb.DMatrix.save(testFrame, paste0(analyticsDir, "temp_testFrame.dmatrix"))
rm(testFrame); gc()

### --- train XGBoost on train model dataset
print("Preparation complete. Model w. XGBoost now.")
print("-----------------------------------------------------------------------")
print("Set CV tree parameters:")
### --- grab model params
eta <- .5
gamma <- 10
max_depth <- 6
subsample <- .5
colsample_bytree <- .5
nthread <- 36
nrounds <- 30000
t1 <- Sys.time()
### --- FIRE!
res_boost <- xgb.train(
  data = trainFrame,
  watchlist = list(validation1 = trainFrame),
  params = list(booster = "gbtree",
                nthread = nthread,
                eta = eta,
                gamma = gamma,
                max_depth = max_depth, 
                subsample = subsample, 
                colsample_bytree = colsample_bytree,
                objective = "binary:logistic"),
  nrounds = nrounds,
  verbose = 1,
  print_every_n = 100,
  eval_metric = "logloss",
  early_stopping_rounds = 1000,
  maximize = NULL,
  save_period = NULL,
  save_name = NULL,
  xgb_model = NULL
)
print(paste0("Training ends: ", Sys.time()))
training_time <- difftime(Sys.time(), t1, units = "mins")
print(paste0("XGBoost CV took: ", training_time))

# - grab feature importance
importanceFrame <- xgb.importance(feature_names = colnames(trainFrame),
                                  model = res_boost)
# - remove trainFrame
rm(trainFrame); gc()

# - predictions on the testFrame:
testFrame <- xgb.DMatrix(paste0(analyticsDir, "temp_testFrame.dmatrix"))
training_prediction <- predict(res_boost, testFrame, 
                               ntreelimit = res_boost$best_ntreelimit)
# - remove testFrame
rm(testFrame);gc()

# - ROC Analysis
d_t <- seq(.01, .99, .01)
rocFrame <- data.frame(threshold = numeric(length(d_t)), 
                       acc = numeric(length(d_t)),
                       tp_rate = numeric(length(d_t)),
                       fn_rate = numeric(length(d_t)), 
                       fp_rate = numeric(length(d_t)), 
                       tn_rate = numeric(length(d_t)))
for (i in 1:length(d_t)) {
  dec_threshold <- d_t[i]
  acc <- mean(as.numeric(training_prediction > dec_threshold) == response_time_test)
  hitRate <- sum((as.numeric(training_prediction > dec_threshold) == 1) & (response_time_test == 1))
  hitRate <- hitRate/sum(response_time_test == 1)
  missRate <- sum((as.numeric(training_prediction > dec_threshold) == 0) & (response_time_test == 1))
  missRate <- missRate/sum(response_time_test == 1)
  falseAlarmRate <- sum((as.numeric(training_prediction > dec_threshold) == 1) & (response_time_test == 0))
  falseAlarmRate <- falseAlarmRate/sum(response_time_test == 0)
  correctRejectionRate <- sum((as.numeric(training_prediction > dec_threshold) == 0) & (response_time_test == 0))
  correctRejectionRate <- correctRejectionRate/sum(response_time_test == 0)
  rocFrame[i, ] <- c(dec_threshold, 
                     round(acc, 3), 
                     round(hitRate, 3), 
                     round(missRate, 3), 
                     round(falseAlarmRate, 3), 
                     round(correctRejectionRate, 3)
  )
}
rocFrame$diff_hit_fa <- rocFrame$tp_rate - rocFrame$fp_rate
wmax_diff <- which.max(rocFrame$diff_hit_fa)
rocFrame[wmax_diff, ]
write.csv(rocFrame, 
          paste0(analyticsDir, "ROC_model_qtMild_eta001_", top_f, ".csv"))

# - feature importance
modelFrameCodeFile <- list.files(analyticsDir)
modelFrameCodeFile <- modelFrameCodeFile[grepl("modelFrameFeatureCode", modelFrameCodeFile)]
modelFrameCodeFile <- modelFrameCodeFile[grepl(top_f, modelFrameCodeFile)]
modelFrameCode <- read.csv(paste0(analyticsDir, modelFrameCodeFile), 
                           check.names = F,
                           header = T,
                           row.names = 1,
                           stringsAsFactors = F)
importanceFrame <- importanceFrame %>% 
  dplyr::left_join(modelFrameCode, 
                   by = c('Feature' = 'xgboost_feature'))
rm(modelFrameCode)
importanceFrame$feature[which(is.na(importanceFrame$feature))] <- 
  importanceFrame$Feature[which(is.na(importanceFrame$feature))]
importanceFrame <- importanceFrame[, c(5, 1, 2, 3, 4)]
colnames(importanceFrame)[2] <- 'xgboost_feature'
importanceFrame <- dplyr::arrange(importanceFrame, 
                                  desc(Gain))
write.csv(importanceFrame, 
          paste0(analyticsDir, "importance_qtMild_eta001_", top_f, ".csv"))

In order to explain the typical modelling result with XGBoost, we need to introduce some terminology first.

Let’s assume each of the SPARQL queries is coded as 1 if the respective WDQS response time for that query was critically high or 0 if it had fallen in a range of typical processing times. For a sample of SPARQL queries than we have two binary vectors over {0, 1} describing: (1) the observed status of the query (again: 1 - critical, 0 - typical), and (2) the predicted status of the query: what the trained predictive model “thinks” the query would be by looking at the features that describe it.

For example:

sampleFrame <- data.frame(query = paste0("Query_", 1:10),
                          observed = round(runif(10, 0, 1)),
                          predicted = round(runif(10, 0, 1)))
sampleFrame

For each of the 10 queries in the table we know if they really where critical (1) or not (0) and we see that from the observed column, and if the model predicts they would be critical (1) or not (0) from their features: the predicted column. For example, in the first row we have a query that was critical indeed and correctly predicted as critical: that is something we call a Hit (or a True Positive) in ML. In the second row we observe a False Alarm (or a False Positive): the query was not really critical (it has 0 on observed) but the model has incorrectly categorized it as if it would be (1 on predicted). In the eigth row we find a situation known as a Correct Rejection: the query was not critical (0 on observed) and the model figured out it wouldn’t be critical indeed by looking into its features (0 on predicted). Finally, the fourth important situation can be found in the sixth row, where the query was critical (1 on observed) but not recognized as such by the model (0 on predicted), a case known as Miss in the ML terminology. More on this terminology: https://en.wikipedia.org/wiki/Confusion_matrix.

In the table above, we can compute the model accuracy by looking at how many times do the observe and predicted column coincide:

Table 1. Confusion matrix example.

acc <- sum(sampleFrame$observed == sampleFrame$predicted)/dim(sampleFrame)[1]
print(paste0("Model accuracy = ", round(acc, 2), "."))
[1] "Model accuracy = 0.2."

When we look at the Hit rate we see that:

hits <- sum(sampleFrame$observed == 1 & sampleFrame$predicted == 1)/sum(sampleFrame$observed == 1)
print(paste0("Hit rate = ", round(hits, 2), "."))
[1] "Hit rate = 0.33."

Finally, the False alarm rate of the model is:

FAs <- sum(sampleFrame$observed == 0 & sampleFrame$predicted == 1)/sum(sampleFrame$observed == 1)
print(paste0("FA rate = ", round(FAs, 2), "."))
[1] "FA rate = 0.67."

So, the model would recognize a critical query correctly 71% of time, while 30% of time it would incorrectly predict a non-critical query to be critical. Imagine if we use this model to chose when to start and when not to start some optimization procedure in WDQS - be it as simple as diverging different queries (critical vs. non-critical) to separate processing resources. From the viewpoint of any system optimization, the decision is needed: can we live with these two values, or not? The False Alarm suggests that 30% of time the model would suggest the optimization routine to start in a situation when it should not start and thus spare valuable system resources. Again, 100% - 71% (Hit rate) = 29% of time the model would miss to trigger the optimization routine for a critical query that could engage too many resources.

A good predictive model is the one which attains (a) a high Hit rate (i.e. observed critical and predicted critical), and (b) a low False Alarm rate (i.e. observed non-critical but predicted critical). In other words, we do not want our model to miss critical queries and thus not trigger some optimization procedure to help WDQS better use its resources, but we also do not want our model to trigger any optimization procedures on a query that would not result in a critically high usage of resources.

Our current results:

  • the results vary with the number of features initially passed into optimization and (of course) the specific values of model parameters, but they do not vary significantly:
  • model accuracy is around 85%;
  • typical Hit rate (or True Positive Rate) is around 72%, and
  • typical False alarm rate (or False Positive Rate) is about 13%.

Now we can see how misleading is model accuracy alone. In effect, these results imply the following:

  • imagine, again, that we develop a system to optimize the WDQS by somehow splitting its resources into those that process queries with expected typical processing times from those with expected critical processing times (e.g. we have two data centers, eqiad and codefw, of which the later seems to be barely used to support the WDQS; we can think about the problem as if we were posing a question if a query is too heavy to be processed in the overloaded eqiad and thus should be sent to codefw, or not);
  • our current modelling efforts let us know that we can get the number of True Positives to be about 72% at the expense of incorrectly categorizing approx. 13% of typical queries as if they were critical;
  • that means the system would correctly run 72% of the heavy queries on critical processing resourcess, while it would erroneously run 13% of the queries on critical processing resources (i.e. 13% of queries that should be processed with the resources for typical queries would incorrectly trigger the critical processing resources);
  • or, to rephrase: the model would think a critical query is non-critical 28% of a time, and decide to pass such a query to a normal, typical WDQS processing, while at the same time it would recognize a non-critical query as non-critical 87% of time and than it would not trigger any optimization action.

4. Selected features

4.1 The most important features for categorization: typical vs. extreme processing time

The XGBoost model has used 312 features to model query_time. NOTE. Some of the initial features, like day of week, actually become several features in the process of dummy coding (e.g. for day of week it would be: Monday, Tuesday, Wednesday, etc, each being a binary 0/1 feature, e.g. 1 for Monday if it was Monday and 0 otherwise).

The following table presents all the features used to predict if the query will reach a critical or typical processing time. The table is sorted by Gain* which is the measure of feature's importance in the categorization of queries. Remember thatds_n_conc_queries` is the number of queries concurrently started in the same minute, in the same datacenter and on the same back-end host.

Feature Importance Table

library(ggplot2)
library(tidyverse)
library(DT)
featureFrame <- read.csv('importance_qtMild_eta001_300.csv', 
                         header = T,
                         check.names = F,
                         row.names = 1,
                         stringsAsFactors = F)
featureFrame <- featureFrame %>% 
  dplyr::select(feature, Gain)
featureFrame$Gain <- round(featureFrame$Gain, 5)
DT::datatable(featureFrame, 
              options = list(
                           paging = T,
                           pageLength =  50 
                           ))

Just from a glimpse at the feature importance table we can see that it is important:

  • when the query was run (many features of the form f_ds_day_*, representing day or week, and f_ds_hour_*, representing hour of day, are selected as important; also, the above mentioned ds_n_conc_queries which is a proxy measure of a datacenter/host load in a given minute)
  • the overall complexity of the query measured by nchar (the length of the query in characters) and __vars_usage__ (the total number of tokens representing variables found in the SPARQL code);
  • where the query was run, e.g. f_ds_datacenter_host_eqiad_wdqs1005, f_ds_datacenter_host_eqiad_wdqs1006, f_ds_datacenter_host_eqiad_wdqs1004, f_ds_datacenter_host_eqiad_wdqs1007, f_ds_datacenter_host_codfw_wdqs2002, etc;
  • the usage of labels: the __vars_label_usage__ - a count of tokens representing labels for a particular variable, e.g. ?authorLabel and similar;
  • pragmatics: if a query is about movies, e.g. the ?imdbid feature (except for if WDQS users are pulling our leg by using a variable named ?imdbid while actually retrieving data on cats - the risks of analyzing the pragmatics of a language);
  • the “structural depth” of the query: the { feature, how many open curly braces do we find in a query (and knowing that each must be matched by the respective } symbol we have eliminated the later early in the feature engineering phase);
  • etc.

4.2 The direction of influence of the most important features

Following the model and variable selection, we have estimated a Binomial Logistic Regression model with the selected features and on the same dataset. We have used the Ridge regression approach to regularization. The obtained coefficients are linear:

  • The feature Gain in the XGBoost decision tree model represents the informativeness of a particular feature in a sequence of decision trees that perform binary classification (extreme vs. typical query processing times); however,
  • the size and the sign (positive or negative) of a feature linear coefficient obtained from the Binary Logistic Regression model represent how much and in what direction, respectively, does the presence of the feature in a query influence if the query will be processed in extreme or typical time.

The following charts show the linear coefficients of the 20 features with most positive and most negative coefficients:

pFrame <- read.csv('Regression_Coeffs_300.csv', 
                   header = T, 
                   check.names = F,
                   row.names = 1,
                   stringsAsFactors = F)
# - log-odds to odds
pFrame$Weight <- exp(pFrame$Weight)
pFrame_top <- head(pFrame, 30)
pFrame_bottom <- tail(pFrame, 30)
pFrame <- rbind(pFrame_top, pFrame_bottom)
pFrame$pos_neg <- c(rep('positive', 30), rep('negative', 30))
pFrame$xgboost_feature <- NULL
pFrame$Feature <- factor(pFrame$Feature, 
                         levels = pFrame$Feature[order(pFrame$Weight)])
ggplot(data = pFrame, 
       aes(x = Feature,
           y = Weight)) + 
  geom_line(group = 1) + 
  geom_point(size = 2.5) + geom_point(size = 2, color = "white") +
  facet_wrap(~pos_neg, scales='free_x') + 
  theme_bw() + 
  theme(axis.text.x = element_text(size = 9, angle = 90, hjust=0.95, vjust=0.2))

The following table presents all model features alongside their respective linear coefficients

pFrame$pos_neg <- NULL
pFrame$Weight <- round(pFrame$Weight, 2)
DT::datatable(pFrame, 
              options = list(
                           paging = T,
                           pageLength =  50 
                           ))

5. Directions for future work

  • The sampling approach appears to be robust;
  • The feature engineering procedures are approximate: there is place for further improvement in that respect;
  • The ML results were discussed and it is uncertain if further significant improvements are possible, but at least the following should be considered:
    • maybe a different model altogether (e.g. Convolutional Deep Learning) should be tested.

Goran S. Milovanović

Wikimedia Deutschland, Data Scientist DataKolektiv, Owner

contact:

LS0tCnRpdGxlOiBXRFFTIEVuZHBvaW50IEFuYWx5dGljcyAtIE9wdGltaXppbmcgU2VydmVyIFJlc3BvbnNlIFRpbWUgLSBQYXJ0IEIgKE1MKQphdXRob3I6Ci0gbmFtZTogR29yYW4gUy4gTWlsb3Zhbm92acSHCiAgYWZmaWxpYXRpb246IFdpa2ltZWRpYSBEZXV0c2NobGFuZCwgRGF0YSBTY2llbnRpc3QsIERhdGFLb2xla3RpdiwgT3duZXIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0b2NfZGVwdGg6IDUKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNQotLS0KKioqCgoqKlJlcG9ydCB0aW1lc3RhbXA6KiogYHIge1N5cy50aW1lKCl9YAoKKioqCgoqKlJlZmVyZW5jZSBQaGFicmljYXRvciBUaWNrZXQ6KiogW1QyNDgzMDhdKGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjQ4MzA4KQoKKioqCiMjIyBOb3RlYm9vazogT3B0aW1pemluZyBXRFFTIFJlc3BvbnNlIFRpbWUgRnJvbSBTUEFSUUwgUXVlcmllcyBBbmFseXNpcwoqKkZlZWRiYWNrKiogc2hvdWxkIGJlIHNlbmQgdG8gYGdvcmFuLm1pbG92YW5vdmljX2V4dEB3aWtpbWVkaWEuZGVgLiAKCiMjICoqU3VtbWFyeSoqIAoKW1dNREVdKGh0dHBzOi8vd3d3Lndpa2ltZWRpYS5kZS8pIHRvZ2V0aGVyIHdpdGggW3BocC5jY10oaHR0cHM6Ly90aGVwaHAuY2Mvd2VsY29tZSkgaXMgdHJ5aW5nIHRvIGZpbmQgYSB3YXkgdG8gb3B0aW1pemUgdGhlIFtXaWtpZGF0YSBRdWVyeSBTZXJ2aWNlXShodHRwczovL3d3dy5tZWRpYXdpa2kub3JnL3dpa2kvV2lraWRhdGFfUXVlcnlfU2VydmljZSkuIFRoaXMgUmVwb3J0IHByb3ZpZGVzIChhKSBhbmQgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyAoRURBKSBhcHByb2FjaCB0byBXRFFTIHJlc3BvbnNlIHRpbWVzIChQYXJ0IEEpIGFuZCAoMikgZGVzY3JpYmVzIGEgTWFjaGluZSBMZWFybmluZyAoTUwpIGFwcHJvYWNoIChQYXJ0IEIpIHRvIGFuYWx5emUgdGhlIFNQQVJRTCBxdWVyaWVzIHRoYXQgd2VyZSByZWNlaXZlZCBieSB0aGUgZW5kcG9pbnQgaW4gdGhlIGZpcnN0IHRocmVlIHdlZWtzIG9mIEFwcmlsIDIwMjAsIHdpdGggdGhlIGdvYWwgb2YgZGV2ZWxvcGluZyBhIG1vZGVsIGFibGUgdG8gcHJlZGljdCB3aGV0aGVyIGEgcXVlcnkgd291bGQgYmUgKipjcml0aWNhbCoqIGZvciBXRFFTL0JsYXplZ3JhcGggb3Igbm90LiBCeSBzaW5nbGluZyBvdXQgdGhlIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzIHRoYXQgYXJlIHByZXNlbnQgaW4gdGhlIFNQQVJRTCBxdWVyaWVzIHVuZGVyIGFuYWx5c2lzIHdlIGhvcGUgdG8gYmUgYWJsZSB0byB1bmRlcnN0YW5kIGJldHRlciB3aGF0IHR5cGVzIG9mIHF1ZXJpZXMgdHlwaWNhbGx5IGNhdXNlIHByb2JsZW1zLgoKSW4gdGhlIGZvbGxvd2luZyBzZWN0aW9ucyB3ZSBkZXNjcmliZSAqKigxKSoqIHRoZSAqc2FtcGxpbmcgYXBwcm9hY2gqIHRha2VuIHRvIGNvbGxlY3QgYSByZWxldmFudCBzZXQgb2YgU1BBUlFMIHF1ZXJpZXMgb2JzZXJ2ZWQgYXQgdGhlIChXRFFTKSBlbmRwb2ludCBpbiB0aHJlZSB3ZWVrcyBvZiBNYXJjaCAyMDIwLCAqKigyKSoqIHRoZSAqZmVhdHVyZSBlbmdpbmVlcmluZyogYXBwcm9hY2ggdGFrZW4gdG8gYW5hbHl6ZSB0aGUgY29tcG9zaXRpb24gb2YgcXVlcmllcyBhbmQgZXh0cmFjdCBtZWFuaW5nZnVsICJhdG9taWMgZWxlbWVudHMiIG9mIFNQQVJRTCB0aGF0IHdpbGwgYmUgdXNlZCBpbiBhbiBhdHRlbXB0IHRvIHByZWRpY3QgdGhlIFdEUVMgcmVzcG9uc2UgdGltZSBmb3IgYSBxdWVyeSwgKiooMykqKiB0aGUgZGVmaW5pdGlvbiBvZiBhIGNyaXRlcmlvbiBmb3Igc2VsZWN0aW5nIGEgKipjcml0aWNhbCoqIHF1ZXJ5IGZvciBXRFFTIChpLmUuIHRoZSBvbmUgdGhhdCBtYWtlcyB0b28gbXVjaCB1c2Ugb2YgdGhlIHNlcnZpY2UncyByZXNvdXJjZXMpLCBhbmQgKiooNCkqKiB0aGUgTUwgKG9wdGltaXphdGlvbikgYXBwcm9hY2gsIHJlbHlpbmcgb24gW1hHQm9vc3RdKGh0dHBzOi8veGdib29zdC5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvKSwgdGFrZW4gdG8gYnVpbGQgYSBwcmVkaWN0aXZlIG1vZGVsIGZvciBXRFFTIHNlcnZlciByZXNwb25zZSB0aW1lcyBzdGFydGluZyBmcm9tIHRoZSBleHRyYWN0ZWQgU1BBUlFMIGZlYXR1cmUuCgojIyAqKkdvYWwqKiAKCi0gQW4gb3B0aW1pemF0aW9uIHN5c3RlbSBmb3IgV0RRUyBvZiBhbnkgZm9ybSB3aWxsIG5lZWQgdG8gZGVjaWRlIGlmIHNvbWUgcGFydGljdWxhciBTUEFSUUwgcXVlcnkgbmVlZHMgb3B0aW1pemF0aW9uIG9yIG5vdCwKLSBhbmQgdGh1cyBpdCBpcyB3aXNlIHRvIGZpZ3VyZSBvdXQgYSBjcml0ZXJpb24gdGhhdCBzb3J0cyBvdXQgdGhlIHF1ZXJpZXMgaW4gcmVzcGVjdCB0byB3aGV0aGVyIHRoZXkgc2hvdWxkIHJlY2VpdmUgYW55IHNwZWNpYWwgdHJlYXRtZW50IG9yIG5vdC4KLSBPdXIgYXBwcm9hY2ggaXMgdG8gYnVpbGQgYSBNTCBtb2RlbCB3aXRoIGEgZ29hbCB0byBwcmVkaWN0IGlmIHRoZSB0aW1lIHRha2VuIGJ5IFdEUVMgdG8gcHJvY2VzcyBhIHBhcnRpY3VsYXIgcXVlcnkgaXMgZXhjZXNzaXZlbHkgbG9uZyBvciB0eXBpY2FsLAotIGJhc2VkIG9uIGEgc2V0IG9mIGZlYXR1cmVzIGV4dHJhY3RlZCBmcm9tIHRoZSBTUEFSUUwgY29kZSBvZiB0aGUgcXVlcmllcyB0aGVtc2VsdmVzLCBhbmQgZXZlbnR1YWx5IAotIHJlY29tbWVuZCB0byB1c2UgdGhhdCBzZXQgb2YgZmVhdHVyZXMgdG8gcGFyc2UgdGhlIGluY29taW5nIHF1ZXJ5IGJlZm9yZSBXRFFTIHByb2Nlc3NpbmcgYW5kIGRlY2lkZSBpZiBpdCBuZWVkcyBvcHRpbWl6YXRpb24gb3Igbm90IGJhc2luZyBvbiB0aGUgZmVhdHVyZXMgdGhhdCB0aGUgcXVlcnkgZW5jb21wYXNzZXMuCgojIyAqKkNyaXRlcmlvbioqCgotIEluIHRoZSBbYGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5YF0oaHR0cHM6Ly9nZXJyaXQud2lraW1lZGlhLm9yZy9yL3BsdWdpbnMvZ2l0aWxlcy9tZWRpYXdpa2kvZXZlbnQtc2NoZW1hcy8rL21hc3Rlci9qc29uc2NoZW1hL3NwYXJxbC9xdWVyeS8xLjAuMC55YW1sKSB0YWJsZSAobGl2ZXMgaW4gdGhlIFdNRiBEYXRhIExha2UsIEhhZG9vcCkgd2UgZmluZCB0aGUgYHF1ZXJ5X3RpbWVgIHZhcmlhYmxlIHJlcHJlc2VudGluZyB0aGUgV0RRUyBwcm9jZXNzaW5nIHRpbWUgaW4gc2Vjb25kcyBmb3IgZWFjaCBxdWVyeSBvYnNlcnZlZCBhdCB0aGUgZW5kcG9pbnQ7Ci0gV2UgcGFydGl0aW9uIGFsbCBvYnNlcnZlZCBxdWVyaWVzIGluIHR3byBncm91cHM6IGAxYCAtIHF1ZXJpZXMgdGhhdCB0b29rIGV4Y2Vzc2l2ZWx5IGxvbmcgcHJvY2Vzc2luZyB0aW1lcywgZGVmaW5lZCBhcyAqbWlsZCBvdXRsaWVycyo6IGBxdWVyeV90aW1lID49IFEzKzEuNcOXSVFSYCwgd2hlcmUgYFEzYCByZXByZXNlbnRzIHRoZSAzcmQgcXVhcnRpbGUgKGA3NSVgKSBvZiB0aGUgV0RRUyByZXNwb25zZSB0aW1lIGRpc3RyaWJ1dGlvbiB3aGlsZSBgSVFSYCByZXByZXNlbnRzIHRoZSBpbnRlci1xdWFydGlsZSByYW5nZSwgYElRUiA9IFEzLVExYDsgYDBgIC0gcXVlcmllcyB0aGF0IHRvb2sgInR5cGljYWwiIHByb2Nlc3NpbmcgdGltZXMgLSBldmVyeXRoaW5nIHdpdGggdGhlIHByb2Nlc3NpbmcgdGltZSBiZWxvdyB3aGF0IHdlIGhhdmUganVzdCBkZWZpbmVkIGFzIG1pbGQgb3V0bGllcnMuCgoKKioqCgojIyAwLiBTYW1wbGUgb2YgU1BBUlFMIHF1ZXJpZXMgb2JzZXJ2ZWQgYXQgdGhlIFdEUVMgU1BBUlFMIGVuZHBvaW50CgpUaGUgU1BBUlFMIHF1ZXJpZXMgdGhhdCBoaXQgdGhlIGVuZHBvaW50IGFyZSByZWdpc3RlcmVkIGluIHRoZSBbYGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5YF0oaHR0cHM6Ly9nZXJyaXQud2lraW1lZGlhLm9yZy9yL3BsdWdpbnMvZ2l0aWxlcy9tZWRpYXdpa2kvZXZlbnQtc2NoZW1hcy8rL21hc3Rlci9qc29uc2NoZW1hL3NwYXJxbC9xdWVyeS8xLjAuMC55YW1sKSB0YWJsZSBpbiB0aGUgV01GIERhdGEgTGFrZSAob3VyIEhhZG9vcCBCaWcgRGF0YSBzdG9yYWdlKS4gQSBbYnVja2V0aW5nIGFwcHJvYWNoXShodHRwczovL2N3aWtpLmFwYWNoZS5vcmcvY29uZmx1ZW5jZS9kaXNwbGF5L0hpdmUvTGFuZ3VhZ2VNYW51YWwrU2FtcGxpbmcpIHRvIHNhbXBsZSBkYXRhIGZyb20gdGhpcyBIaXZlIHRhYmxlIHdhcyB0YWtlbiwgc2FtcGxpbmcgYXBwcm94aW1hdGVseSBgMSVgIG9mIHF1ZXJpZXMgdGhhdCB3ZXJlIG9ic2VydmVkIGluIHRoZSBwZXJpb2QgYmV0d2VlbiBgMjAyMC8wNC8wMWAgYW5kIGAyMDIwLzA0LzIxYC4gVGhlIHNhbXBsZSBpcyBhcHByb3hpbWF0ZWx5IHVuaWZvcm1seSBkaXN0cmlidXRlZCBhY3Jvc3MgZGF5cyBhbmQgaG91cnMgb2YgdGhlIG1lbnRpb25lZCBwZXJpb2Qgb2YgdGltZS4gVGhlIHNhbXBsZSBzaXplIGlzIHNsaWdodGx5IGhpZ2hlciB0aGFuIGAxTWAgU1BBUlFMIHF1ZXJpZXMgLSBpbmNsdWRpbmcgZHVwbGljYXRlcy4KClRoZSBmb2xsb3dpbmcgZmllbGRzIHdlcmUgZGVyaXZlZCBmcm9tIHRoZSBgZXZlbnQud2Rxc19leHRlcm5hbF9zcGFycWxfcXVlcnlgIHRhYmxlIGZvciBlYWNoIGNvbGxlY3RlZCBTUEFSUUwgcXVlcnk6CgotIGBkc19zcGFycWxgIC0gdGhlIFNQQVJRTCBxdWVyeQotIGBkc19kdGAgLSB0aGUgcXVlcnkgdGltZXN0YW1wLCBsYXRlciBwYXJzZWQgaW50byBgeWVhcmAsIGBtb250aGAsIGBkYXlgLCBgaG91cmAsIGFuZCBgbWludXRlYAotIGBkc19zdGF0dXNfY29kZWAgLSB0aGUgc2VydmVyIGh0dHAgc3RhdHVzIHJlc3BvbnNlIGZvciB0aGlzIHF1ZXJ5Ci0gYGRzX3F1ZXJ5X3RpbWVgIC0gaG93IG1hbnkgc2Vjb25kcyBkaWQgaXQgdG9vayBmb3IgdGhlIFdEUVMgdG8gcHJvY2VzcyB0aGUgcXVlcnkKLSBgZHNfZGF0YWNlbnRlcl9ob3N0YCAtIGEgY29tYmluYXRpb24gb2YgdGhlIChhKSBXTUYgZGF0YSBjZW50ZXIgYW5kIChiKSBhIGJhY2stZW5kIGhvc3QgaW4gdGhlIHJlc3BlY3RpdmUgZGF0YSBjZW50ZXIKLSBgZHNfZm9ybWF0YCAtIGNvbnRlbnQgdHlwZSAoZS5nLiBKU09OLCB0ZXh0L3RzdiwgeG1sLCBldGMuKQotIGBkc19tZXRob2RgIC0gSFRUUCBtZXRob2QgKGUuZy4gR0VULCBQT1NUKQotIGBkc19uX2NvbmNfcXVlcmllc2AgLSBmb3IgZWFjaCBTUEFSUUwgcXVlcnksIHRoZSBudW1iZXIgb2YgU1BBUlFMIHdob3NlIHByb2Nlc3Npbmcgc3RhcnRlZCBpbiAoMSkgdGhlIHNhbWUgbWludXRlLCBvbiAoMikgdGhlIHNhbWUgZGF0YSBjZW50ZXIsIGFuZCBvbiAoMykgdGhlIHNhbWUgYmFjay1lbmQgaG9zdC4KCioqTk9URS4qKiBJdCBpcyBpbW1lZGlhdGVseSBvYnZpb3VzIHRoYXQgbm90IGFsbCBmaWVsZHMgZnJvbSB0aGUgYGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5YCB0YWJsZSBhcmUgY2FuZGlkYXRlcyBmb3IgYSBwcmVkaWN0aXZlIG1vZGVsIG9mIHRoZSBkZXNjcmliZWQga2luZDogYGRzX3N0YXR1c19jb2RlYCwgdGhlIHNlcnZlciBIVFRQIHJlc3BvbnNlIGNvZGUsIGlzIG5vdCBrbm93IGJlZm9yZSB0aGUgcXVlcnkgaXMgYWN0dWFsbHkgcHJvY2Vzc2VkLiBXZSBrZWVwIHRoaXMgdmFyaWFibGUgaW4gdGhlIGRhdGFzZXQgaGF2aW5nIGluIG1pbmQgdGhhdCBpdCBtaWdodCBzZXJ2ZSBhcyBhIHBvdGVudGlhbCBjcml0ZXJpb24gaW4gc2ltaWxhciBwcmVkaWN0aW9uIHRhc2tzIHNvbWVkYXkuCgpUaGUgZm9sbG93aW5nIFIgc2NyaXB0IHdhcyB1c2VkIHRvIG9idGFpbiBhIHNhbXBsZSBvZiBTUEFSUUwgcXVlcmllcyBmcm9tIGBldmVudC53ZHFzX2V4dGVybmFsX3NwYXJxbF9xdWVyeWAuCgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBGLCBtZXNzYWdlID0gRn0KCiMhL3Vzci9iaW4vZW52IFJzY3JpcHQKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBXRFFTX0V2ZW50X1MxX0VUTC5SIHYgMC4wLjEKIyMjIC0tLSBTY3JpcHQ6IFdEUVNfRXZlbnRfUzFfRVRMLlIsIHYuIEJldGEgMC4xCiMjIyAtLS0gRGVzY3JpcHRpb246IFdEUVNfRXZlbnRfUzFfRVRMLlIgY29sbGVjdHMgYSBzYW1wbGUgb2YgCiMjIyAtLS0gU1BBUlFMIHF1ZXJpZXMgZnJvbSBldmVudC53ZHFzX2V4dGVybmFsX3NwYXJxbF9xdWVyeSBpbiB0aGUgV01GIERhdGEgTGFrZS4KIyMjIC0tLSBBdXRob3I6IEdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERQojIyMgLS0tIERldmVsb3BlZCB1bmRlciB0aGUgY29udHJhY3QgYmV0d2VlbiBHb3JhbiBNaWxvdmFub3ZpYyBQUiBEYXRhIEtvbGVrdGl2CiMjIyAtLS0gYW5kIFdNREUuCiMjIyAtLS0gQ29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gUlVOIEZST006IHN0YXQxMDA1CiMjIyAtLS0gRGF0ZTogMjAyMC8wNC8yMgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gTElDRU5TRToKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIEdQTCB2MgojIyMgLS0tIFRoaXMgZmlsZSBpcyBwYXJ0IG9mIHRoZSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QKIyMjIC0tLQojIyMgLS0tIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdCBpcyBmcmVlIHNvZnR3YXJlOiAKIyMjIC0tLSB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3IgbW9kaWZ5CiMjIyAtLS0gaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkKIyMjIC0tLSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAyIG9mIHRoZSBMaWNlbnNlLCBvcgojIyMgLS0tIChhdCB5b3VyIG9wdGlvbikgYW55IGxhdGVyIHZlcnNpb24uCiMjIyAtLS0KIyMjIC0tLSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QgaXMgZGlzdHJpYnV0ZWQgCiMjIyAtLS0gaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwKIyMjIC0tLSBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZgojIyMgLS0tIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUKIyMjIC0tLSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgojIyMgLS0tCiMjIyAtLS0gWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UKIyMjIC0tLSBhbG9uZyB3aXRoIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdC4KIyMjIC0tLSBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTZWN0aW9uIDEuIENvbGxlY3QgcmF3IGRhdGEgc2V0IGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5CiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gc2V0dXAKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCiMjIyAtLS0gZGlyZWN0b3JpZXMKIyMjIC0tLSBSZWFkIFdEQ00gcGFyYW1lcmV0ZXJzCiMgLSBmUGF0aDogd2hlcmUgdGhlIHNjcmlwdHMgaXMgcnVuIGZyb20/CmZQYXRoIDwtIGFzLmNoYXJhY3Rlcihjb21tYW5kQXJncyh0cmFpbGluZ09ubHkgPSBGKVs0XSkKZlBhdGggPC0gZ3N1YigiLS1maWxlPSIsICIiLCBmUGF0aCwgZml4ZWQgPSBUKQpmUGF0aCA8LSB1bmxpc3Qoc3Ryc3BsaXQoZlBhdGgsIHNwbGl0ID0gIi8iLCBmaXhlZCA9IFQpKQpmUGF0aCA8LSBwYXN0ZSgKICBwYXN0ZShmUGF0aFsxOmxlbmd0aChmUGF0aCkgLSAxXSwgY29sbGFwc2UgPSAiLyIpLAogICIvIiwKICBzZXAgPSAiIikKZlBhdGggPC0gJy9ob21lL2dvcmFuc20vQW5hbHl0aWNzL1dpa2lkYXRhL1dEX21pc2MvV0RfU1BBUlFMX0VuZHBvaW50X0FuYWx5dGljcy8nCmRhdGFEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2V2ZW50L19kYXRhLycpCmFuYWx5dGljc0RpciA8LSBwYXN0ZTAoZlBhdGgsICdfZXZlbnQvX2FuYWx5dGljcy8nKQpyZXBvcnRpbmdEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2V2ZW50L19yZXBvcnRpbmcvJykKCiMjIyAtLS0gZ3JhYiBhIHNhbXBsZSBvZiBTUEFSUUwgcXVlcmllcyBldmVudC53ZHFzX2V4dGVybmFsX3NwYXJxbF9xdWVyeQojIyMgLS0tIHcuIEhpdmVRTCBmcm9tIEJlZWxpbmUKIyAtIHF1ZXJ5IGZpbGVuYW1lCnF1ZXJ5RmlsZSA8LSAid2Rxc19Db2xsZWN0RGF0YS5ocWwiCiMgLSBLZXJiZXJvcyBJbml0CmtlcmJlcm9zUHJlZml4IDwtICdzdWRvIC11IGFuYWx5dGljcy1wcml2YXRlZGF0YSBrZXJiZXJvcy1ydW4tY29tbWFuZCBhbmFseXRpY3MtcHJpdmF0ZWRhdGEgJwojIC0gZGF0ZVJhbmdlCnN0YXJ0RGF0ZSA8LSAnMjAyMC8wNC8wMScKZW5kRGF0ZSA8LSAnMjAyMC8wNC8yMScKZGF0ZVJhbmdlIDwtIHNlcShhcy5EYXRlKHN0YXJ0RGF0ZSksIGFzLkRhdGUoZW5kRGF0ZSksIGJ5ID0gImRheSIpCiMgLSBxdWVyeSBvdmVyIGRhdGVSYW5nZQpmb3IgKGkgaW4gMTpsZW5ndGgoZGF0ZVJhbmdlKSkgewogIHByaW50KHBhc3RlMCgiU3RhcnRlZDogIiwgZGF0ZVJhbmdlW2ldKSkKICAjIC0geWVhciwgbW9udGgsIGRhdGUKICB5ZWFyID0gc3Vic3RyKGRhdGVSYW5nZVtpXSwgMSwgNCkKICBtb250aCA9IHN1YnN0cihkYXRlUmFuZ2VbaV0sIDYsIDcpCiAgaWYgKHN1YnN0cihtb250aCwgMSwgMSkgPT0gJzAnKSB7bW9udGggPSBzdWJzdHIobW9udGgsIDIsIDIpfQogIGRheSA9IHN1YnN0cihkYXRlUmFuZ2VbaV0sIDksIDEwKQogIGlmIChzdWJzdHIoZGF5LCAxLCAxKSA9PSAnMCcpIHtkYXkgPSBzdWJzdHIoZGF5LCAyLCAyKX0KICAjIC0gY29tcG9zZSBxdWVyeQogIGhpdmVRTHF1ZXJ5IDwtIHBhc3RlMCgiU0VMRUNUIG1ldGEuZHQgYXMgZHQsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2V4cF9yZXBsYWNlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnZXhwX3JlcGxhY2UoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2V4cF9yZXBsYWNlKHF1ZXJ5LCAnXFxufFxcclxcbnxcXHJ8XFxuXFxyJywgJyBfX05FV0xJTkVfXyAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdcXHQnLCAnICcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICdcXDAwMCcsICcgJykgYXMgc3BhcnFsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQsIG1ldGEuZG9tYWluIGFzIGRvbWFpbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgaHR0cC5yZXF1ZXN0X2hlYWRlcnNbJ1gtQklHREFUQS1NQVgtUVVFUlktTUlMTElTJ10gYXMgbWF4X3F1ZXJ5X21pbGxpcywgaHR0cC5tZXRob2QgYXMgbWV0aG9kLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBodHRwLnN0YXR1c19jb2RlIGFzIHN0YXR1c19jb2RlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZW5kX2hvc3QsIGRhdGFjZW50ZXIsIHF1ZXJ5X3RpbWUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBldmVudC53ZHFzX2V4dGVybmFsX3NwYXJxbF9xdWVyeSBUQUJMRVNBTVBMRSAoQlVDS0VUIDEgT1VUIE9GIDEwMCBPTiByYW5kKCkpIHcKICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVSRSBpc25vdG51bGwoaHR0cC5zdGF0dXNfY29kZSkgCiAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIChtZXRhLmRvbWFpbiA9ICdxdWVyeS53aWtpZGF0YS5vcmcnKSAKICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgeWVhcj0iLCB5ZWFyLAogICAgICAgICAgICAgICAgICAgICAgICAiIEFORCBtb250aD0iLCBtb250aCwKICAgICAgICAgICAgICAgICAgICAgICAgIiBBTkQgZGF5PSIsIGRheSwgJzsnKQogIGZpbGVuYW1lIDwtICJ3ZHFzX2RhdGFfc2FtcGxlX1RFU1RfIgogIGZpbGVuYW1lIDwtIHBhc3RlMChmaWxlbmFtZSwgZGF0ZVJhbmdlW2ldLCAiLnRzdiIpCiAgIyAtIHdyaXRlIGhxbAogIHdyaXRlKGhpdmVRTHF1ZXJ5LCBwYXN0ZTAoZlBhdGgsIHF1ZXJ5RmlsZSkpCiAgIyAtIHRvIFJlcG9ydAogIHByaW50KCJGZXRjaGluZyBzcGFycWxfdGVzdERhdGEgZnJvbSB3bWYud2VicmVxdWVzdCBub3cuIikKICAjIC0gUnVuIHF1ZXJ5CiAgcXVlcnkgPC0gc3lzdGVtKGNvbW1hbmQgPSBwYXN0ZShrZXJiZXJvc1ByZWZpeCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnL3Vzci9sb2NhbC9iaW4vYmVlbGluZSAtLWluY3JlbWVudGFsPXRydWUgLS1zaWxlbnQgLWYgIicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoZlBhdGgsIHF1ZXJ5RmlsZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnIiA+ICcsIGRhdGFEaXIsIGZpbGVuYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiIpLAogICAgICAgICAgICAgICAgICB3YWl0ID0gVFJVRSkKICBwcmludChwYXN0ZTAoIkNvbXBsZXRlZDogIiwgZGF0ZVJhbmdlW2ldKSkKICBwcmludCgiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iKQp9CiMgLSB0byBSZXBvcnQKcHJpbnQoIkRPTkUgdy4gRVRMIGZyb20gSGFkb29wOiB3bWYud2VicmVxdWVzdC4iKQpgYGAKCgojIyAxLiBEYXRhIENsZWFuaW5nIGFuZCBGZWF0dXJlIEVuZ2luZWVyaW5nIAoKIyMjIDEuMSBEYXRhIENsZWFuaW5nCgpUaGUgZm9sbG93aW5nIFIgc2NyaXB0IGNoZWNrcyBmb3IgZGF0YSBjb25zaXN0ZW5jeSBUaGUgZm9sbG93aW5nIGNodW5rIG9mIGNvZGUgYWxzbyBwYXJzZXMgdGhlIGBkdGAgKHRpbWVzdGFtcCkgZmllbGQgYW5kIGV4dHJhY3RzIGB5ZWFyYCwgYG1vbnRoYCwgYGRheWAsIGBob3VyYCwgYW5kIGBtaW51dGVgIHdoZW4gdGhlIHF1ZXJ5IHdhcyBydW4uIEFsc28sIGFuZCBhZGRpdGlvbmFsIHZhcmlhYmxlIGlzIHByb2R1Y2VkIGZyb20gYSBzaW1wbGUgY29uY2F0ZW5hdGlvbiBvZiBgeWVhcmAsIGBtb250aGAsIGBkYXlgLCBgaG91cmAsIGFuZCBgbWludXRlYDogYHltZG1oYC4gVGhpcyB2YXJpYWJsZSB3aWxsIGJlIHN1YnNlcXVlbnRseSB1c2VkIHRvIGNhbGN1bGF0ZSB0aGUgbnVtYmVyIG9mIHF1ZXJpZXMgdGhhdCB3ZXJlIHJ1biBpbiBhIHBhcnRpY3VsYXIgbWludXRlIG9ic2VydmVkIGluIHRoZSBzYW1wbGUsIHBlciBXTUYgZGF0YSBjZW50ZXIgKGBlcWlhZGAgb3IgYGNvZGVmd2ApIGFuZCBiYWNrLWVuZCBob3N0IGluIHRoZSBkYXRhIGNlbnRlci4gVGhpcyBpcyBhIHBvdGVudGlhbCAiZW52aXJvbm1lbnRhbCIgdmFyaWFibGUgKGkuZS4gdGFrZW4gZnJvbSB0aGUgV0RRUy9CbGF6ZWdyYXBoIGVudmlvcm5tZW50KSBpbiBwcmVkaWN0aXZlIG1vZGVsaW5nIG9mIHNlcnZlciByZXNwb25zZSB0aW1lLiBJdCBjYW4gY2hhcmFjdGVyaXplIHRoZSBxdWVyeSBwcm9jZXNzaW5nIGNvbnRleHQgYnkgZXN0aW1hdGluZyBob3cgbWFueSBxdWVyaWVzIHdlcmUgcnVuIGFnYWluc3QgQmxhemVncmFwaCBjb25jdXJyZW50bHkgaW4gdGhlIHNhbWUgbWludXRlLCBvbiB0aGUgc2FtZSBzZXJ2ZXIsIHdoZW4gYSBwYXJ0aWN1bGFyIHF1ZXJ5IHVuZGVyIG9mIGludGVyZXN0IHdhcyBydW4uIEFueSBmdXR1cmUgV0RRUyBvcHRpbWl6YXRpb24gc3lzdGVtIHJlbHlpbmcgb24gdGhpcyB2YXJpYWJsZSB3b3VsZCBoYXZlIHRvIHJlbHkgb24gQmxhemVncmFwaCBzb21laG93IGJyb2FkY2FzdGluZyB0aGlzIHZhcmlhYmxlIHRvIHRoZSBwcmVkaWN0aXZlIG1vZGVsaW5nIEFQSS4KCioqTk9URS4qKiBJbiB0aGlzIHN0ZXAgd2UgYWxzbyBlbGltaW5hdGUgYWxsIHF1ZXJpZXMgdGhhdCBoYXZlIHJlc3VsdGVkIGluIGEgYGh0dHBfc3RhdHVzYCBvZiBgNCoqYDogY2xpZW50IHNpZGUgZXJyb3JzLiBPYnZpb3VzbHksIGluY29ycmVjdGx5IHN0YXRlZCBTUEFSUUwgcXVlcmllcyBhcmUgb2Ygbm8gdXNlIHRvIHVzIGluIGFueSBhdHRlbXB0IHRvIG9wdGltaXplIHRoZSBXRFFTIHByb2Nlc3NpbmcgdGltZS4gU28gaWYgYSBxdWVyeSBmYWlscyBhbiBpbml0aWFsIHN5bnRheCBhbmFseXNpcyBvbiB0aGUgZW5kcG9pbnQgaXQgZG9lcyBub3QgZW50ZXIgb3VyIGFuYWx5c2lzLgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBGLCBtZXNzYWdlID0gRn0KCiMhL3Vzci9iaW4vZW52IFJzY3JpcHQKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBXRFFTX0V2ZW50X1MyX0NsZWFuVXBfSW5zcGVjdC5SLCB2LiAwLjAuMQojIyMgLS0tIFNjcmlwdDogV0RRU19FdmVudF9TMl9DbGVhblVwX0luc3BlY3QuUiwgdi4gMC4wLjEKIyMjIC0tLSBEZXNjcmlwdGlvbjogV0RRU19FdmVudF9TMl9DbGVhblVwX0luc3BlY3QuUiBwZXJmb3JtcyAKIyMjIC0tLSBmZWF0dXJlIGVuZ2luZWVyaW5nIG9mIHRoZSBTUEFSUUwgcXVlcmllcyBkYXRhIAojIyMgLS0tIGNvbGxlY3RlZCB3LiBXRFFTX0V2ZW50X1MxX0VUTC5SIGZyb20gdGhlIAojIyMgLS0tIGV2ZW50LndkcXNfZXh0ZXJuYWxfc3BhcnFsX3F1ZXJ5IHRhYmxlIGluIHRoZSBXTUYgRGF0YSBMYWtlLgojIyMgLS0tIEF1dGhvcjogR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgU2NpZW50aXN0LCBXTURFCiMjIyAtLS0gRGV2ZWxvcGVkIHVuZGVyIHRoZSBjb250cmFjdCBiZXR3ZWVuIEdvcmFuIE1pbG92YW5vdmljIFBSIERhdGEgS29sZWt0aXYKIyMjIC0tLSBhbmQgV01ERS4KIyMjIC0tLSBDb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBSVU4gRlJPTTogc3RhdDEwMDUKIyMjIC0tLSBEYXRlOiAyMDIwLzAzLzIyCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBMSUNFTlNFOgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gR1BMIHYyCiMjIyAtLS0gVGhpcyBmaWxlIGlzIHBhcnQgb2YgdGhlIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdAojIyMgLS0tCiMjIyAtLS0gV2lraWRhdGEgU1BBUlFMIEVuZHBvaW50IEFuYWx5dGljcyBQcm9qZWN0IGlzIGZyZWUgc29mdHdhcmU6IAojIyMgLS0tIHlvdSBjYW4gcmVkaXN0cmlidXRlIGl0IGFuZC9vciBtb2RpZnkKIyMjIC0tLSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieQojIyMgLS0tIHRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb24sIGVpdGhlciB2ZXJzaW9uIDIgb2YgdGhlIExpY2Vuc2UsIG9yCiMjIyAtLS0gKGF0IHlvdXIgb3B0aW9uKSBhbnkgbGF0ZXIgdmVyc2lvbi4KIyMjIC0tLQojIyMgLS0tIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdCBpcyBkaXN0cmlidXRlZCAKIyMjIC0tLSBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLAojIyMgLS0tIGJ1dCBXSVRIT1VUIEFOWSBXQVJSQU5UWTsgd2l0aG91dCBldmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mCiMjIyAtLS0gTUVSQ0hBTlRBQklMSVRZIG9yIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZQojIyMgLS0tIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuCiMjIyAtLS0KIyMjIC0tLSBZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZQojIyMgLS0tIGFsb25nIHdpdGggV2lraWRhdGEgU1BBUlFMIEVuZHBvaW50IEFuYWx5dGljcyBQcm9qZWN0LgojIyMgLS0tIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4KIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLSBzZXR1cApsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHVybHRvb2xzKQoKIyMjIC0tLSBkaXJlY3RvcmllcwojIyMgLS0tIFJlYWQgV0RDTSBwYXJhbWVyZXRlcnMKIyAtIGZQYXRoOiB3aGVyZSB0aGUgc2NyaXB0cyBpcyBydW4gZnJvbT8KZlBhdGggPC0gYXMuY2hhcmFjdGVyKGNvbW1hbmRBcmdzKHRyYWlsaW5nT25seSA9IEZBTFNFKVs0XSkKZlBhdGggPC0gZ3N1YigiLS1maWxlPSIsICIiLCBmUGF0aCwgZml4ZWQgPSBUKQpmUGF0aCA8LSB1bmxpc3Qoc3Ryc3BsaXQoZlBhdGgsIHNwbGl0ID0gIi8iLCBmaXhlZCA9IFQpKQpmUGF0aCA8LSBwYXN0ZSgKICBwYXN0ZShmUGF0aFsxOmxlbmd0aChmUGF0aCkgLSAxXSwgY29sbGFwc2UgPSAiLyIpLAogICIvIiwKICBzZXAgPSAiIikKZlBhdGggPC0gJy9ob21lL2dvcmFuc20vQW5hbHl0aWNzL1dpa2lkYXRhL1dEX21pc2MvV0RfU1BBUlFMX0VuZHBvaW50X0FuYWx5dGljcy8nCmRhdGFEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2V2ZW50L19kYXRhLycpCmFuYWx5dGljc0RpciA8LSBwYXN0ZTAoZlBhdGgsICdfZXZlbnQvX2FuYWx5dGljcy8nKQpyZXBvcnRpbmdEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2V2ZW50L19yZXBvcnRpbmcvJykKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTZWN0aW9uIDEuIExvYWQsIGNsZWFuLCBhbmQgZGVjb2RlIHRoZSBTUEFSUUwgcXVlcmllcyBkYXRhIHNldAojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tIFN5cy5zZXRsb2NhbGUoKQpTeXMuc2V0bG9jYWxlKCdMQ19BTEwnLCdDJykKCiMjIyAtLS0gbG9hZCBkYXRhCmxGIDwtIGxpc3QuZmlsZXMoZGF0YURpcikKbEYgPC0gbEZbZ3JlcGwoIl53ZHFzX2RhdGFfc2FtcGxlIiwgbEYpXQpkYXRhU2V0IDwtIGxhcHBseShwYXN0ZTAoZGF0YURpciwgbEYpLCBmcmVhZCwgaGVhZGVyID0gVCwgcXVvdGU9IiIpCmRhdGFTZXQgPC0gcmJpbmRsaXN0KGRhdGFTZXQpCmRpbShkYXRhU2V0KQoKIyMjIC0tLSBpbnNwZWN0CmNvbG5hbWVzKGRhdGFTZXQpCnN0cihkYXRhU2V0KQoKIyMjIC0tLSBjbGVhbiBkYXRhOiBzcGFycWwKZGF0YVNldCRzcGFycWwgPC0gZ3N1YigiX19ORVdMSU5FX18iLCAiXFxcXG4iLCBkYXRhU2V0JHNwYXJxbCkKCiMjIyAtLS0gY2xlYW4gZGF0YTogbWF4X3F1ZXJ5X21pbGxpcwpzdW1OQSA8LSBzdW0oaXMubmEoZGF0YVNldCRtYXhfcXVlcnlfbWlsbGlzKSkKc3VtTkEKdGFibGUoZGF0YVNldCRtYXhfcXVlcnlfbWlsbGlzKQojIC0gYWxsIGRhdGFTZXQkbWF4X3F1ZXJ5X21pbGxpcyB2YWx1ZXMgYXJlOiA2MDAwMDsgZHJvcHBpbmcKZGF0YVNldCRtYXhfcXVlcnlfbWlsbGlzIDwtIE5VTEwKCiMjIyAtLS0gY2xlYW4gZGF0YTogc3RhdHVzX2NvZGUKc3VtTkEgPC0gc3VtKGlzLm5hKGRhdGFTZXQkc3RhdHVzX2NvZGUpKQpzdW1OQQp0YWJsZShkYXRhU2V0JHN0YXR1c19jb2RlKQojIC0gcmVtb3ZlIHN0YXR1c19jb2RlID0gNCoKdzQwMCA8LSB3aGljaChncmVwbCgnXjQnLCBkYXRhU2V0JHN0YXR1c19jb2RlKSkKbGVuZ3RoKHc0MDApCmRpbShkYXRhU2V0KQppZiAobGVuZ3RoKHc0MDApID4gMCkgewogIGRhdGFTZXQgPC0gZGF0YVNldFstdzQwMCwgXQp9CmRpbShkYXRhU2V0KQoKIyMjIC0tLSBjbGVhbiBkYXRhOiBmb3JtYXQKc3VtTkEgPC0gc3VtKGlzLm5hKGRhdGFTZXQkZm9ybWF0KSkKc3VtTkEKdGFibGUoZGF0YVNldCRmb3JtYXQpCiMgLSBmaXg6IGxvd2VyIGNhc2UgYW5kIHVwcGVyIGNhc2UKZGF0YVNldCRmb3JtYXQgPC0gdG9sb3dlcihkYXRhU2V0JGZvcm1hdCkKdGFibGUoZGF0YVNldCRmb3JtYXQpCgojIyMgLS0tIGNsZWFuIGRhdGE6IGRvbWFpbgpzdW1OQSA8LSBzdW0oaXMubmEoZGF0YVNldCRkb21haW4pKQpzdW1OQQp0YWJsZShkYXRhU2V0JGRvbWFpbikKIyAtIGFsbCBkYXRhU2V0JGRvbWFpbiB2YWx1ZXMgYXJlOiBxdWVyeS53aWtpZGF0YS5vcmc7IGRyb3BwaW5nCmRhdGFTZXQkZG9tYWluIDwtIE5VTEwKCiMjIyAtLS0gY2xlYW4gZGF0YTogbWV0aG9kCnN1bU5BIDwtIHN1bShpcy5uYShkYXRhU2V0JG1ldGhvZCkpCnN1bU5BCnRhYmxlKGRhdGFTZXQkbWV0aG9kKQoKIyMjIC0tLSBjbGVhbiBkYXRhOiBiYWNrZW5kX2hvc3QKc3VtTkEgPC0gc3VtKGlzLm5hKGRhdGFTZXQkYmFja2VuZF9ob3N0KSkKc3VtTkEKdGFibGUoZGF0YVNldCRiYWNrZW5kX2hvc3QpCgojIyMgLS0tIGNsZWFuIGRhdGE6IGRhdGFjZW50ZXIKc3VtTkEgPC0gc3VtKGlzLm5hKGRhdGFTZXQkZGF0YWNlbnRlcikpCnN1bU5BCnRhYmxlKGRhdGFTZXQkZGF0YWNlbnRlcikKCiMjIyAtLS0gY2xlYW4gZGF0YTogcXVlcnlfdGltZQpzdW1OQSA8LSBzdW0oaXMubmEoZGF0YVNldCRxdWVyeV90aW1lKSkKc3VtTkEKc3VtbWFyeShkYXRhU2V0JHF1ZXJ5X3RpbWUpCgojIyMgLS0tIHZhbGlkYXRlIGR0ICh0aW1lc3RhbXApCnN1bU5BIDwtIHN1bShpcy5uYShkYXRhU2V0JGR0KSkKc3VtTkEKdyA8LSB3aGljaChkYXRhU2V0JGR0ID09ICItIikKbGVuZ3RoKHcpCncgPC0gd2hpY2goIShncmVwbCgiXFxkXFxkXFxkXFxkLVxcZFxcZC1cXGRcXGRUXFxkXFxkOlxcZFxcZDpcXGRcXGRcXC5cXGQrWiIsIAogICAgICAgICAgICAgICAgICAgZGF0YVNldCRkdCkpKQpsZW5ndGgodykKIyAtIG5vdGhpbmcgY3J1Y2lhbDoKZGF0YVNldCRkdFt3XSAKIyMjIC0tLSBleHRyYWN0IHllYXIsIG1vbnRoLCBkYXksIGhvdXIsIGFuZCBtaW51dGUgZnJvbSBkdCAodGltZXN0YW1wKQpkYXRhU2V0JGRheSA8LSBzdWJzdHIoZGF0YVNldCRkdCwgOSwgMTApCmRhdGFTZXQkaG91ciA8LSBzdWJzdHIoZGF0YVNldCRkdCwgMTIsIDEzKQpkYXRhU2V0JG1pbnV0ZSA8LSBzdWJzdHIoZGF0YVNldCRkdCwgMTUsIDE2KQpkYXRhU2V0JHltZGhtIDwtIHBhc3RlKGRhdGFTZXQkZGF5LAogICAgICAgICAgICAgICAgICAgICAgIGRhdGFTZXQkaG91ciwKICAgICAgICAgICAgICAgICAgICAgICBkYXRhU2V0JG1pbnV0ZSwKICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiLSIpCiMjIyAtLS0gcmVtb3ZlIGRhdGFTZXQkZHQKZGF0YVNldCRkdCA8LSBOVUxMCgojIyMgLS0tIGluc3BlY3Q6IGZvcm1hdCBhbmQgcXVlcnlfdGltZQpkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KGZvcm1hdCwgcXVlcnlfdGltZSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShmb3JtYXQpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKG1lYW4gPSBtZWFuKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWVkaWFuID0gbWVkaWFuKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWluID0gbWluKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICBuID0gbigpKSAlPiUgCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhtZWRpYW4pKQoKIyMjIC0tLSBpbnNwZWN0OiBtZXRob2QgYW5kIHF1ZXJ5X3RpbWUKZGF0YVNldCAlPiUgCiAgZHBseXI6OnNlbGVjdChtZXRob2QsIHF1ZXJ5X3RpbWUpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkobWV0aG9kKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuID0gbWVhbihxdWVyeV90aW1lLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbiA9IG1lZGlhbihxdWVyeV90aW1lLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1pbiA9IG1pbihxdWVyeV90aW1lLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1heCA9IG1heChxdWVyeV90aW1lLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG4gPSBuKCkpICU+JSAKICBkcGx5cjo6YXJyYW5nZShkZXNjKG1lZGlhbikpCgojIyMgLS0tIGluc3BlY3Q6IHN0YXR1c19jb2RlIGFuZCBxdWVyeV90aW1lCmRhdGFTZXQgJT4lIAogIGRwbHlyOjpzZWxlY3Qoc3RhdHVzX2NvZGUsIHF1ZXJ5X3RpbWUpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoc3RhdHVzX2NvZGUpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKG1lYW4gPSBtZWFuKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWVkaWFuID0gbWVkaWFuKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWluID0gbWluKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbiA9IG4oKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MobWVkaWFuKSkKCiMjIyAtLS0gaW5zcGVjdDogYmFja2VuZF9ob3N0IGFuZCBxdWVyeV90aW1lCmRhdGFTZXQgJT4lIAogIGRwbHlyOjpzZWxlY3QoYmFja2VuZF9ob3N0LCBxdWVyeV90aW1lKSAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KGJhY2tlbmRfaG9zdCkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UobWVhbiA9IG1lYW4ocXVlcnlfdGltZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtZWRpYW4gPSBtZWRpYW4ocXVlcnlfdGltZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtaW4gPSBtaW4ocXVlcnlfdGltZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtYXggPSBtYXgocXVlcnlfdGltZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBuID0gbigpKSAlPiUgCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhtZWRpYW4pKQoKIyMjIC0tLSBpbnNwZWN0OiBkYXRhY2VudGVyIGFuZCBxdWVyeV90aW1lCmRhdGFTZXQgJT4lIAogIGRwbHlyOjpzZWxlY3QoZGF0YWNlbnRlciwgcXVlcnlfdGltZSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShkYXRhY2VudGVyKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuID0gbWVhbihxdWVyeV90aW1lLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbiA9IG1lZGlhbihxdWVyeV90aW1lLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1pbiA9IG1pbihxdWVyeV90aW1lLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1heCA9IG1heChxdWVyeV90aW1lLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG4gPSBuKCkpICU+JSAKICBkcGx5cjo6YXJyYW5nZShkZXNjKG1lZGlhbikpCgojIyMgLS0tIGluc3BlY3Q6IGRheSBhbmQgcXVlcnlfdGltZQpkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KGRheSwgcXVlcnlfdGltZSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShkYXkpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKG1lYW4gPSBtZWFuKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWVkaWFuID0gbWVkaWFuKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWluID0gbWluKHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KHF1ZXJ5X3RpbWUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbiA9IG4oKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MobWVkaWFuKSkKCiMjIyAtLS0gaW5zcGVjdDogaG91ciBhbmQgcXVlcnlfdGltZQpkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KGhvdXIsIHF1ZXJ5X3RpbWUpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoaG91cikgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UobWVhbiA9IG1lYW4ocXVlcnlfdGltZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtZWRpYW4gPSBtZWRpYW4ocXVlcnlfdGltZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtaW4gPSBtaW4ocXVlcnlfdGltZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtYXggPSBtYXgocXVlcnlfdGltZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBuID0gbigpKSAlPiUgCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhtZWRpYW4pKQoKIyMjIC0tLSBhZGQgSUQsIHVuaXF1ZVF1ZXJ5SUQKZGF0YVNldCRpZCA8LSAxOmRpbShkYXRhU2V0KVsxXQp1bmlxdWVRdWVyaWVzIDwtIHVuaXF1ZShkYXRhU2V0JHNwYXJxbCkKdW5pcXVlUXVlcmllcyA8LSBkYXRhLmZyYW1lKHNwYXJxbCA9IHVuaXF1ZVF1ZXJpZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5pcXVlU3BhcnFsSWQgPSAxOmxlbmd0aCh1bmlxdWVRdWVyaWVzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZGltKHVuaXF1ZVF1ZXJpZXMpCmRhdGFTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmxlZnRfam9pbih1bmlxdWVRdWVyaWVzLCAKICAgICAgICAgICAgICAgICAgIGJ5ID0gInNwYXJxbCIpCnJtKHVuaXF1ZVF1ZXJpZXMpCgojIyMgLS0tIHJlYXJyYW5nZSwgc3RvcmUgY2xlYW4gZGF0YSBzZXQKY29sbmFtZXMoZGF0YVNldCkKZGF0YVNldCA8LSBkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KCdpZCcsICd1bmlxdWVTcGFycWxJZCcsICdkYXknLCAnaG91cicsICdtaW51dGUnLCAneW1kaG0nLCAKICAgICAgICAgICAgICAgICAgICAgICAnc3BhcnFsJywgJ2Zvcm1hdCcsICdtZXRob2QnLCAnc3RhdHVzX2NvZGUnLCAKICAgICAgICAgICAgICAgICAgICAgICAnYmFja2VuZF9ob3N0JywgJ2RhdGFjZW50ZXInLCAncXVlcnlfdGltZScpCiMgLSBmaW5hbCBjaGVjazoKdyA8LSB3aGljaChjb21wbGV0ZS5jYXNlcyhkYXRhU2V0KSkKbGVuZ3RoKHcpCmRpbShkYXRhU2V0KQoKIyMjIC0tLSBzdG9yZSB0byBhbmFseXRpY3NEaXIKZmlsZW5hbWUgPC0gcGFzdGUwKGFuYWx5dGljc0RpciwgCiAgICAgICAgICAgICAgICAgICAnd2Rxc19kYXRhX3NhbXBsZV8nLCAKICAgICAgICAgICAgICAgICAgIHN1YnN0cihhcy5jaGFyYWN0ZXIoU3lzLnRpbWUoKSksIDEsIDEwKSwgCiAgICAgICAgICAgICAgICAgICAnLmNzdicpCndyaXRlLmNzdihkYXRhU2V0LCBmaWxlbmFtZSkKYGBgCgojIyMgMS4yIEZlYXVyZSBFbmdpbmVlcmluZwoKVGhlIFNQQVJRTCBxdWVyaWVzIGluIG91ciBzYW1wbGUsIHByZXNlbnQgaW4gdGhlIGBzcGFycWxgIGZpZWxkIG9mIHRoZSBkYXRhc2V0IHJldHJpZXZlZCBmcm9tIHRoZSBgZXZlbnQud2Rxc19leHRlcm5hbF9zcGFycWxfcXVlcnlgIHRhYmxlLCBuZWVkIHRvIGJlIHBhcnNlZCBpbiBvcmRlciB0byBleHRyYWN0IGEgc2V0IG9mIG1lYW5pbmdmdWwgZmVhdHVyZXMgdG8gdXNlIGluIHByZWRpY3RpdmUgbW9kZWxpbmcgb2Ygc2VydmVyIHJlc3BvbnNlIHRpbWUuIEFzIGluIHRoZSBjYXNlIG9mIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZywgd2UgbmVlZCB0byBkaWZmZXJlbnRpYXRlIGJldHdlZW4gdGhyZWUgcG9zc2libGUgbGV2ZWxzIG9mIGFuYWx5c2lzIGhlcmU6CgotICoqc3ludGF4Kio6IHRoZSBjb21wb3NpdGlvbiBvZiBTUEFSUUwga2V5d29yZHMsIG9wZXJhdG9ycywgdmFyaWFibGVzLCBhbmQgZnVuY3Rpb25zIHJlc3VsdCBpbiBhIHNwZWNpZmljIGRlc2NyaXB0aW9uIG9mIHRoZSBkYXRhIHRoYXQgbmVlZCB0byBiZSBvYnRhaW5lZCwgYW5kIHRoZWlyIGFuYWx5c2lzIG9uIGJlaGFsZiBvZiBCbGF6ZWdyYXBoIGRldGVybWluZXMgdGhlIHF1ZXJ5IGV4ZWN1dGlvbiBwbGFuICh2aWEgQWJzdHJhY3QgU3ludGF4IFRyZWUsIEFTVCwgZGVyaXZlZCBmcm9tIHF1ZXJ5IGFuYWx5c2lzKTsgZGV0ZXJtaW5lcyBxdWVyeSB2YWxpZGl0eSBhbmQgcXVlcnkgZXhlY3V0aW9uIHBsYW47Ci0gKipzZW1hbnRpY3MqKjogdGhlIGV4YWN0IG1hcHBpbmcgb2YgcXVlcnkgc3ludGF4IHRvIHBhcnRpY3VsYXIgdmFyaWFibGVzIHRoYXQgbmVlZCB0byBiZSBpbnN0YW50aWF0ZWQgaW4gb3JkZXIgdG8gb2J0YWluIHRoZSBkZXNpcmVkIHJlc3VsdCBmcm9tIFdEUVM7IHRoZW9yZXRpY2FsbHksIGl0IGRldGVybWluZXMgdHJ1dGggcmVsYXRpb25zOyBpbiBwcmFjdGljZSwgd2UgY2FuIHNheSB0aGF0IGl0IHByZWNpc2VseSBzcGVjaWZpZXMgdGhlIGV4cGVjdGVkIHF1ZXJ5IHJlc3VsdDsgCi0gKipwcmFnbWF0aWNzKio6IGdpdmVuIHRoZSBTUEFSUUwgbGFuZ3VhZ2UgY29udmVudGlvbnMgYXJlIHNhdGlzZmllZCwgYW55IHF1ZXJ5IG1pZ3RoIGluc3RhbnRpYXRlIHZhcmlvdXMgdmFyaWFibGUgbmFtZXMsIHVzZSAobW9yZSBvciBsZXNzIGVmZmljaWVudGx5KSB2YXJpb3VzIHN5bnRhY3RpYyBjb25zdHJ1Y3Rpb25zIHRvIG9idGFpbiB0aGUgc2FtZSBlZmZlY3QsIGluY2x1ZGUgY29kZSBjb21tZW50cywgZXRjLjogdGhlIHdheSB1c2VycyAqdXNlKiB0aGUgU1BBUlFMIHF1ZXJ5IGxhbmd1YWdlIHRvIGNvbW11bmljYXRlIHRoZWlyIGdvYWxzIHRvIHRoZSBXRFFTLgoKT3VyIGFwcHJvYWNoIHRvIGZlYXR1cmUgZW5naW5lZXJpbmcgaXMgdG8gcGFyc2UgZWFjaCBTUEFSUUwgcXVlcnkgd2l0aCBhIHNldCBvZiByZWd1bGFyIGV4cHJlc3Npb25zIGluIG9yZGVyIHRvIChhKSBzaW5nbGUgb3V0IHRoZSAiYXRvbWljIGVsZW1lbnRzIiBvZiB0aGUgU1BBUlFMIGxhbmd1YWdlIChrZXl3b3JkcywgZnVuY3Rpb25zLCBJUkkgbWVudGlvbnMsIHZhcmlhYmxlcywgbGl0ZXJhbHMsIG9wZXJhdG9ycywgV2lraWJhc2UgcGFyYW1ldGVycywgZXRjLiksIChiKSBjb21wdXRlIGEgc2V0IG9mIGFic3RyYWN0IHZhcmlhYmxlcyB0aGF0IGNhbiBiZSB1c2VkIHRvIGRlc2NyaWJlIHRoZSBxdWVyeSwgZS5nLjogdGhlIG51bWJlciBvZiBkaWZmZXJlbnQgdmFyaWFibGVzIGluc3RhbnRpYXRlZCAoaS5lLiBob3cgbWFueSB1bmlxdWUgdmFyaWFibGVzIHRoZXJlIGFyZSBpbiBhIHF1ZXJ5KSwgdGhlIHRvdGFsIG51bWJlciBvZiB2YXJpYWJsZXMgbWVudGlvbmVkIChpbmNsdWRpbmcgcmVwZWF0ZWQgdXNlcyBvZiB0aGUgc2FtZSB2YXJpYWJsZSksIHRoZSBudW1iZXIgb2YgSVJJIHJlZmVyZW5jZXMgaW4gYSBxdWVyeSwgaG93IG1hbnkgdmFyaWFibGVzIGFsc28gcmVxdWVzdCBsYWJlbHMsIGFuZCBzaW1pbGFyLgoKV2l0aCB0aGlzIGFwcHByb2FjaCwgd2hpY2ggaXMgZXNzZW50aWFseSB0aGUgc2FtZSBhcyB3aGF0IGlzIHR5cGljYWxseSBkb25lIGluIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZywgcXVlcnkgc3ludGF4IGFuZCBzZW1hbnRpY3MgYXJlIGluIGVmZmVjdCBmdXNlZC4gQSBxdWVzdGlvbiBjb3VsZCBiZSBwb3NlZDogd2h5IGtlZXAgc3BlY2lmaWMgdmFyaWFibGUgbmFtZXMsIG9yIGNvbW1lbnRzLCB3aGVuIGl0IGlzIGNsZWFyIHRoYXQgdGhlIGxhdGVyIGFyZSBub3QgZXZlbiBwcm9jZXNzZWQgd2hpbGUgdmFyaWFibGUgbmFtZXMgYXJlIGNvbnZlbnRpb25hbCBhbmQgV0RRUyB1c2VycyBhcmUgZnJlZSB0byB1c2UgdGhlbSBhcyBsb25nIGFzIHRoZXkgc2F0aXNmeSB0aGUgcnVsZXMgb2YgdGhlIFNQQVJRTCBzeW50YXg/IFRoZXNlIGFzcGVjdHMgb2YgcXVlcnkgYW5hbHlzaXMgYXJlIGltcG9ydGFudCBmcm9tIHRoZSB2aWV3cG9pbnQgb2YgcXVlcnkgKnByYWdtYXRpY3MqIGFuZCBjYXJyeSB1c2VmdWwgaW5mb3JtYXRpb24gaW5kZWVkLiBPZiBjb3Vyc2UgdGhlIHZhcmlhYmxlIG5hbWVzIGFyZSBjb252ZW50aW9uYWwsIGJ1dCBpbWFnaW5lIGEgY29tbXVuaXR5IG9mIGxpYnJhcmlhbnMgdXNpbmcgV0RRUyB3aG8gbGVhcm4gU1BBUlFMIGZyb20gZWFjaCBvdGhlciBhbmQgdGh1cyBmcmVxdWVudGx5IHVzZSB2YXJpYWJsZSBuYW1lcyBsaWtlIGA/YXV0aG9yYCBhbmQvb3IgYD9hdXRob3JMYWJlbGA/IElmIHN1Y2ggInR5cGljYWwiIHZhcmlhYmxlIG5hbWVzIGFyZSBmcmVxdWVudGx5IHVzZWQgaW4gc2ltaWxhciBxdWVyaWVzLCB0aGV5IGNhbiBhY3QgYXMgaW5mb3JtYXRpdmUgZmVhdHVyZXMgaW4gdGhlIHByZWRpY3Rpb24gb2YgcXVlcnkgcmVzcG9uc2UgdGltZSBpbmRlZWQuIENvbW1lbnRzOiBtYW55IFdEUVMgYm90cyBhbmQvb3IgaHVtYW4gdXNlcnMgbWlnaHQgdXNlIGEgInNpZ25hdHVyZSIgY29tbWVudCAoYW5kIGluZGVlZCBzb21lIGRvKSBvZiBzb21lIGZvcm0sIGZvciBleGFtcGxlLiBJZiBzdWNoIGJvdHMgcnVuIHRoZWlyIHF1ZXJpZXMgYWdhaW5zdCBXRFFTIGZyZXF1ZW50bHksIHRoZSBjb21tZW50cyBpbiB0aGVpciBjb2RlIG1pZ2h0IGFsc28gYWN0IGFzIHByZWRpY3RpdmUgZmVhdXJlcyBmb3IgdGhlIHF1ZXJ5IHBlcmZvbWFuY2UuIAoKKipOT1RFLioqIFRoZSAqKmN1cnJlbnQgcmVnZXggYW5hbHlzaXMgb2YgU1BBUlFMIHF1ZXJpZXMgaXMgYXBwcm94aW1hdGUqKiBhbmQsIG1vc3Qgb2YgdGltZSwgY29ycmVjdCBpbiBhIHNlbnNlIG9mIGJlaW5nIGFibGUgdG8gZXh0cmFjdCBpbnR1aXRpdmVseSBtZWFuaW5nZnVsIGF0b21zIG9mIFNQQVJRTC4gSXQgaXMgYXBwcm94aW1hdGUgaW4gYSBzZW5zZSB0aGF0IHRoZSByZWd1bGFyIGV4cHJlc3Npb25zIHVzZSBhcmUgZGVyaXZlZCBieSB0aGUgYXV0aG9yIG9mIHRoaXMgcmVwb3J0IGJ5IGRpcmVjdGx5IGFuYWx5emluZyB0aGUgdHlwaWNhbCBTUEFSUUwgcXVlcmllcyB3aXRoIG9ubHkgbGltaXRlZCBhdHRlbnRpb24gcGF5ZWQgdG8gdGhlIGZvcm1hbCBkZWZpbml0aW9uIG9mIFNQQVJRTCAoZS5nLiBpdHMgQmFja3Vz4oCTTmF1ciBmb3Jtcywgb3IgdGhlIGZvcm1hbCByZWdleCBkZWZpbml0aW9ucyBvZiBpdHMgdmFyaW91cyBzeW50YWN0aWMgY29uc3RpdHVlbnRzKS4gVGhlIGZlYXR1cmUgZW5naW5lZXJpbmcgcHJvY2VkdXJlIHVzZWQgaW4gb3VyIGN1cnJlbnQgYW5hbHlzZXMgKipjYW4gY2VydGFpbmx5IHJlY2VpdmUgZnVydGhlciBpbXByb3ZtZW50KiouIAoKQmV5b25kIGV4dHJhY3RpbmcgU1BBUlFMICJhdG9tcyIsIHRoZSBmb2xsb3dpbmcgZmVhdHVyZXMgYXJlIGRlcml2ZWQgYXMgZnJlcXVlbmNpZXMgKGNvdW50cykgZm9yIGVhY2ggcXVlcnk6CgotIGBuY2hhcmAgLSB0aGUgbGVuZ3RoIG9mIHRoZSBxdWVyeSBpbiBjaGFyYWN0ZXJzCi0gYF9fdmFyc19fYCAtIGhvdyBtYW55IHZhcmlhYmxlcyBhcmUgdXNlZD8KLSBgX192YXJzX3VzYWdlX19gIC0gaG93IG1hbnkgdmFyaWFibGUgbWVudGlvbnMgdGhlcmUgYXJlIGluIHRvdGFsPyAKLSBgX192YXJzX2xhYmVsX19gIC0gaG93IG1hbnkgdmFyaWFibGVzIHJlcXVlc3QgbGFiZWxzIGFsc28/Ci0gYF9fdmFyc19sYWJlbF91c2FnZV9fYCAtIGhvdyBtYW55IHRpbWVzIGFyZSB2YXJpYWJsZSBsYWJlbHMgbWVudGlvbmVkIGluIGEgcXVlcnk/Ci0gYF9fSVJJX1JFRl9fYCAtIGhvdyBtYW55IElSSSByZWZlcmVuY2VzIGFyZSB1c2VkIGluIHRoZSBxdWVyeT8KLSBgX19MSVRFUkFMX19gIC0gaG93IG1hbnkgU1BBUlFMIGxpdGVyYWxzIGFyZSB1c2VkIGluIHRoZSBxdWVyeT8KClNvIHRoZSBmaW5hbCBkZXNjcmlwdGlvbiBvZiBhbnkgU1BBUlFMIHF1ZXJ5LCBmb2xsb3dpbmcgdGhlc2UgZmVhdHVyZSBlbmdpbmVlcmluZyBzdGVwcywgaXMgYSB2ZWN0b3Igb2YgY291bnRzIGZvciBlYWNoIGV4dHJhY3RlZCBmZWF0dXJlLCBpbmNsdWRpbmc6IChhKSB0b2tlbnMgKGUuZy4gZWFjaCBJUkkgb3IgU1BBUlFMIGxpdGVyYWwgbWVudGlvbmVkLCBmb3IgZXhhbXBsZSksIGFzIHdlbGwgYXMgKGIpIHR5cGVzIChlLmcuIGhvdyBtYW55IGBfX0lSSV9SRUZzX19gIHRoZXJlIGFyZSBpbiBhIHF1ZXJ5IGluIHRvdGFsKS4KCioqTk9URSAxLioqIEFwcHJveGltYXRpbmcgc3ludGF4LiBPYnZpb3VzbHksIHRoaXMgZmVhdHVyZSBlbmdpbmVlcmluZyBhcHByb2FjaCBkb2VzIG5vdCBhbmFseXplIHRoZSBzdHJ1Y3R1cmFsIHJlbGF0aW9ucyBiZXR3ZWVuIHRoZSBsYW5ndWFnZSBjb25zdGl0dWVudHM6IGl0cyBzeW50YXguIEhvd2V2ZXIsIHNvbWUgb2YgdGhlIGV4dHJhY3RlZCBmZWF0dXJlcyBjbGVhcmx5ICJjb3JyZWxhdGUiIHdpdGggdGhlIHF1ZXJ5IHN5bnRhY3RpYyBjb21wbGV4aXR5LiBGb3IgZXhhbXBsZSwgdGhlIGNvdW50IG9mIGB7YCB0b2tlbnMsIG9yIHRoZSBjb3VudCBvZiBgU0VMRUNUYCBrZXl3b3JkcywgdXNlZCBpbiBhIHF1ZXJ5LCBjbGVhcmx5IHNwZWFrcyBhYm91dCB0aGUgcXVlcnkgYGRlcHRoYCAoaS5lLiBob3cgbWFueSBzdWItcXVlcmllcyBkbyB3ZSBoYXZlLCBpZiBhbnkpLiBTbyB0aGUgc3ludGFjdGljIGluZm9ybWF0aW9uIGlzIHByZXNlbnQgaW4gb3VyIGZlYXR1cmUgZW5naW5lZXJpbmcgbW9yZSBvciBsZXNzIHVwIHRvIGEgZGVncmVlIGRpY3RhdGVkIGJ5IGhvdyBtdWNoIHVzZWZ1bCBpbmZvcm1hdGlvbiBpcyBjYXJyaWVkIGJ5IGEgbWVudGlvbiBvZiBhIHBhcnRpY3VsYXIgU1BBUlFMIGtleXdvcmQsIG9yIGZ1bmN0aW9uLCBvciBvcGVyYXRvciwgZXRjOyB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlcyB1c2VkLCB0aGUgcXVlcnkgImRlcHRoIiwgYW5kIHNpbWlsYXIuIE1vcmUgdXNlZnVsIHN5bnRhY3RpYyBpbmZvcm1hdGlvbiBjYW4gYmUgb2J0YWluZWQgZnJvbSBCbGF6ZWdyYXBoIGJ5IHJ1bm5pbmcgYSBxdWVyeSBpbiB0aGUgRVhQTEFJTiBtb2RlIChzaW1pbGFyIHRvIHdoYXQgb25lIHdvdWxkIGluIFNRTCksIHNlZTogaHR0cHM6Ly93d3cubWVkaWF3aWtpLm9yZy93aWtpL1dpa2lkYXRhX1F1ZXJ5X1NlcnZpY2UvVXNlcl9NYW51YWwjRXhwbGFpbl9RdWVyeS4gSG93ZXZlciwgZXZlbiBpZiBmZWF0dXJ1cyBvYnRhaW5lZCBmcm9tIHRoZSBxdWVyeSBleGVjdXRpb24gcGxhbiBpbiB0aGlzIHdheSBtaWdodCBwcm92ZSB0byBiZSB1c2VmdWwsIGl0IGlzIHVuY2xlYXIgYXQgdGhpcyBwb2ludCB3aGV0aGVyIHRoZSBTUEFSUUwgcXVlcnkgZXhlY3V0aW9uIHBsYW4gY2FuIGJlIG9idGFpbmVkIGZyb20gQmxhemVncmFwaCAqd2l0aG91dCogYWN0dWFsbHkgcnVubmluZyB0aGUgcXVlcnkuCgoqKk5PVEUgMi4qKiBUaGUgZm9sbG93aW5nIHNjcmlwdCBwZXJmb3JtcyBmZWF0dXJlIGVuZ2luZWVyaW5nIGZvbGxvd2ludCB0aGUgYWJvdmUgb3V0bGluZWQgcHJpbmNpcGxlcy4gSXQgYWxzbyBjb21wdXRlcyB0aGUgYG5fY29uY19xdWVyaWVzYCB2YXJpYWJsZTogdGhlIG51bWJlciBvZiBxdWVyaWVzIHdob3NlIHByb2Nlc3Npbmcgc3RhcnRlZCBjb25jdXJyZW50bHkgaW4gdGhlIHNhbWUgbWludXRlLCBzYW1lIFdNRiBkYXRhIGNlbnRlciwgb24gdGhlIHNhbWUgYmFjay1lbmQgaG9zdC4gVGhlIGBxdWVyaWVzX3ZvY2FidWxhcnkuY3N2YCBmaWxlIGNvbnRhaW5zIGEgbGlzdCBvZiBhbGwgZXh0cmFjdGVkIGZlYXR1cmVzIHNvcnRlZCBpbiBhIGRlc2NlbmRpbmcgb3JkZXIgb2YgdGhlaXIgdXNhZ2UgZnJlcXVlbmN5IGFjcm9zcyBhbGwgU1BBUlFMIHF1ZXJpZXMgaW4gdGhlIHNhbXBsZS4gVGhlIGBxdWVyaWVzX2NvdmVyYWdlLmNzdmAgZGF0YXNldCwgb24gdGhlIG90aGVyIGhhbmQsIGxpc3RzIGZlYXR1cmVzIGFsb25nc2lkZSB0aGUgbnVtYmVyIG9mIHF1ZXJpZXMgaW4gdGhlIHNhbXBsZSB0aGF0IG1ha2UgdXNlIG9mIHRoZW0sIGFuZCBhbHNvIGV4cHJlc3NlcyBmZWF0dXJlIGNvdmVyYWdlIGluIHBlcmNlbnRzLiBBZGRpdGlvbmFsIG9wZXJhdGlvbnMgYXJlIHBlcmZvcm1lZCBvbiBjb2x1bW4gKGZlYXR1cmUpIG5hbWVzIGluIG9yZGVyIHRvIGNvbWZvcnQgdGhlbSB0byBYR0Jvb3N0IHN0YW5kYXJkcy4gVGhlIGB0b3BfZmAgdmFyaWFibGUgY29udHJvbHMgaG93IG1hbnkgb2YgdGhlIG1vc3QgZnJlcXVlbnRseSB1c2VkIGZlYXR1cmVzIHdpbGwgYmUgc2VsZWN0ZWQgZm9yIHRoZSBkYXRhIHNldCB0aGF0IGlzIHBhc3NlZCBvbiB0byB0aGUgTUwgcGhhc2Ugd2l0aCBYR0Jvb3N0LiAgCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IEYsIG1lc3NhZ2UgPSBGfQoKIyEvdXNyL2Jpbi9lbnYgUnNjcmlwdAoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFdEX1dEUVNfQW5hbHl0aWNzX0ZlYXR1cmVFbmdpbmVlcmluZy5SLCB2LiBCZXRhIDAuMQojIyMgLS0tIFNjcmlwdDogV0RfV0RRU19BbmFseXRpY3NfRmVhdHVyZUVuZ2luZWVyaW5nLlIsIHYuIEJldGEgMC4xCiMjIyAtLS0gRGVzY3JpcHRpb246IFdEX1dEUVNfQW5hbHl0aWNzX0ZlYXR1cmVFbmdpbmVlcmluZy5SIHBlcmZvcm1zIAojIyMgLS0tIGZlYXR1cmUgZW5naW5lZXJpbmcgb2YgdGhlIFNQQVJRTCBxdWVyaWVzIGRhdGEgCiMjIyAtLS0gY29sbGVjdGVkIHcuIFdEX1dEUVNfQW5hbHl0aWNzX0NvbGxlY3REYXRhLlIgZnJvbSB0aGUgCiMjIyAtLS0gd21mLndlYnJlcXVlc3QgdGFibGUgaW4gdGhlIFdNRiBEYXRhIExha2UuCiMjIyAtLS0gQXV0aG9yOiBHb3JhbiBTLiBNaWxvdmFub3ZpYywgRGF0YSBTY2llbnRpc3QsIFdNREUKIyMjIC0tLSBEZXZlbG9wZWQgdW5kZXIgdGhlIGNvbnRyYWN0IGJldHdlZW4gR29yYW4gTWlsb3Zhbm92aWMgUFIgRGF0YSBLb2xla3RpdgojIyMgLS0tIGFuZCBXTURFLgojIyMgLS0tIENvbnRhY3Q6IGdvcmFuLm1pbG92YW5vdmljX2V4dEB3aWtpbWVkaWEuZGUKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFJVTiBGUk9NOiAKIyMjIC0tLSAvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9XaWtpZGF0YS9XRF9taXNjL1dEX1NQQVJRTF9FbmRwb2ludF9BbmFseXRpY3MKIyMjIC0tLSBvbiBzdGF0MTAwNQojIyMgLS0tIERhdGU6IDIwMjAvMDMvMjAKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIS91c3IvYmluL2VudiBSc2NyaXB0CgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gV0RRU19FdmVudF9TM19GZWF0dXJlRW5naW5lZXJpbmcuUiwgdi4gMC4wLjEKIyMjIC0tLSBTY3JpcHQ6IFdEUVNfRXZlbnRfUzNfRmVhdHVyZUVuZ2luZWVyaW5nLlIsIHYuIEJldGEgMC4xCiMjIyAtLS0gRGVzY3JpcHRpb246IFdEUVNfRXZlbnRfUzNfRmVhdHVyZUVuZ2luZWVyaW5nLlIgcGVyZm9ybXMgCiMjIyAtLS0gZmVhdHVyZSBlbmdpbmVlcmluZyBvZiB0aGUgU1BBUlFMIHF1ZXJpZXMgZGF0YSAKIyMjIC0tLSBjb2xsZWN0ZWQgdy4gV0RRU19FdmVudF9TMV9FVEwuUiBmcm9tIHRoZSAKIyMjIC0tLSBldmVudC53ZHFzX2V4dGVybmFsX3NwYXJxbF9xdWVyeSB0YWJsZSBpbiB0aGUgV01GIERhdGEgTGFrZS4KIyMjIC0tLSBBdXRob3I6IEdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERQojIyMgLS0tIERldmVsb3BlZCB1bmRlciB0aGUgY29udHJhY3QgYmV0d2VlbiBHb3JhbiBNaWxvdmFub3ZpYyBQUiBEYXRhIEtvbGVrdGl2CiMjIyAtLS0gYW5kIFdNREUuCiMjIyAtLS0gQ29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gUlVOIEZST006IG9uIHN0YXQxMDA1CiMjIyAtLS0gRGF0ZTogMjAyMC8wNC8yMgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gTElDRU5TRToKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIEdQTCB2MgojIyMgLS0tIFRoaXMgZmlsZSBpcyBwYXJ0IG9mIHRoZSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QKIyMjIC0tLQojIyMgLS0tIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdCBpcyBmcmVlIHNvZnR3YXJlOiAKIyMjIC0tLSB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3IgbW9kaWZ5CiMjIyAtLS0gaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkKIyMjIC0tLSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAyIG9mIHRoZSBMaWNlbnNlLCBvcgojIyMgLS0tIChhdCB5b3VyIG9wdGlvbikgYW55IGxhdGVyIHZlcnNpb24uCiMjIyAtLS0KIyMjIC0tLSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QgaXMgZGlzdHJpYnV0ZWQgCiMjIyAtLS0gaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwKIyMjIC0tLSBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZgojIyMgLS0tIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUKIyMjIC0tLSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgojIyMgLS0tCiMjIyAtLS0gWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UKIyMjIC0tLSBhbG9uZyB3aXRoIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdC4KIyMjIC0tLSBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gc2V0dXAKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoc25vd2ZhbGwpCgojIyMgLS0tIGRpcmVjdG9yaWVzCiMjIyAtLS0gUmVhZCBXRENNIHBhcmFtZXJldGVycwojIC0gZlBhdGg6IHdoZXJlIHRoZSBzY3JpcHRzIGlzIHJ1biBmcm9tPwpmUGF0aCA8LSBhcy5jaGFyYWN0ZXIoY29tbWFuZEFyZ3ModHJhaWxpbmdPbmx5ID0gRkFMU0UpWzRdKQpmUGF0aCA8LSBnc3ViKCItLWZpbGU9IiwgIiIsIGZQYXRoLCBmaXhlZCA9IFQpCmZQYXRoIDwtIHVubGlzdChzdHJzcGxpdChmUGF0aCwgc3BsaXQgPSAiLyIsIGZpeGVkID0gVCkpCmZQYXRoIDwtIHBhc3RlKAogIHBhc3RlKGZQYXRoWzE6bGVuZ3RoKGZQYXRoKSAtIDFdLCBjb2xsYXBzZSA9ICIvIiksCiAgIi8iLAogIHNlcCA9ICIiKQpmUGF0aCA8LSAnL2hvbWUvZ29yYW5zbS9BbmFseXRpY3MvV2lraWRhdGEvV0RfbWlzYy9XRF9TUEFSUUxfRW5kcG9pbnRfQW5hbHl0aWNzLycKZGF0YURpciA8LSBwYXN0ZTAoZlBhdGgsICdfZXZlbnQvX2RhdGEvJykKYW5hbHl0aWNzRGlyIDwtIHBhc3RlMChmUGF0aCwgJ19ldmVudC9fYW5hbHl0aWNzLycpCnJlcG9ydGluZ0RpciA8LSBwYXN0ZTAoZlBhdGgsICdfZXZlbnQvX3JlcG9ydGluZy8nKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFNlY3Rpb24gMS4gUGFyc2UgU1BBUlFMIHF1ZXJpZXMKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLSBsb2FkIHF1ZXJpZXMKZmlsZW5hbWUgPC0gJ3dkcXNfZGF0YV9zYW1wbGVfMjAyMC0wNC0yMi5jc3YnCmRhdGFTZXQgPC0gZnJlYWQocGFzdGUwKGFuYWx5dGljc0RpciwgZmlsZW5hbWUpKQpkYXRhU2V0JFYxIDwtIE5VTEwKIyAtIGtlZXAgb25seSB1bmlxdWUgcXVlcmllcwp3IDwtIHdoaWNoKCFkdXBsaWNhdGVkKGRhdGFTZXQkdW5pcXVlU3BhcnFsSWQpKQppZiAobGVuZ3RoKHcpID4gMCkgewogIGRhdGFTZXQgPC0gZGF0YVNldFt3XQp9CmRpbShkYXRhU2V0KQoKIyMjIC0tLSBwcmVwYXJlIHF1ZXJpZXMgZm9yIHBhcnNpbmcKcXVlcmllcyA8LSBkYXRhU2V0JHNwYXJxbAp1bmlxdWVRdWVyeUlkIDwtIGRhdGFTZXQkdW5pcXVlU3BhcnFsSWQKcm0oZGF0YVNldCk7IGdjKCkKCiMjIyAtLS0gZnVuY3Rpb246IHBhcnNlX1NQQVJRTCgpCnBhcnNlX1NQQVJRTCA8LSBmdW5jdGlvbih0cSkgewogIAogICMgLSBnZXQgbmNoYXIoKQogIHRxX25jaGFyIDwtIG5jaGFyKHRxKQogIAogICMgLSBzcGxpdCBieSAiXG4iCiAgdHEgPC0gdW5saXN0KGxhcHBseSh0cSwgZnVuY3Rpb24oeCkgewogICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiXFxcXG4iKQogIH0pKQogIAogICMgLSB0cmltIHRyYWlsaW5nIHNwYWNlIGF0IHRoZSBiZWdpbm5pbmcgYW5kIGVuZCwgaWYgYW55CiAgdHEgPC0gc3RyX3RyaW0odHEsIHNpZGUgPSAiYm90aCIpCiAgCiAgIyAtIHJlbW92ZSBlbXB0eSBsaW5lcyBpZiBhbnkKICB3RW1wdHkgPC0gd2hpY2godHEgPT0gIiIpCiAgaWYgKGxlbmd0aCh3RW1wdHkpID4gMCkgewogICAgdHEgPC0gdHFbLXdFbXB0eV0gCiAgfQogIAogICMgLSBjb2xsZWN0IElSSSByZWZlcmVuY2VzCiAgdHFfaXJpIDwtIHVubGlzdChzdHJfZXh0cmFjdF9hbGwodHEsICc8KFtbOmFsbnVtOl1dfFtbOnB1bmN0Ol1dKSs+JykpCiAgCiAgIyAtIG1hdGNoIElSSSByZWZlcmVuY2VzCiAgdHEgPC0gZ3N1YignPChbWzphbG51bTpdXXxbWzpwdW5jdDpdXSkrPicsICIgX19JUklfUkVGX18gIiwgdHEpCiAgCiAgIyAtIHByb2Nlc3MgY29tbWVudHMKICB0cV9jb21tZW50cyA8LSB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKHRxLCAiIy4rJCIpKQogIHRxIDwtIGdzdWIoIiMuKyQiLCAiIiwgdHEpCiAgd0VtcHR5IDwtIHdoaWNoKHRxID09ICIiKQogIGlmIChsZW5ndGgod0VtcHR5KSA+IDApIHsKICAgIHRxIDwtIHRxWy13RW1wdHldIAogIH0KICAKICAjIC0gc3BhY2UgdG8gdGhlIGxlZnQgYW5kIHJpZ2h0IG9mIAogICMgLSBicmFja2V0cywgb3BlcmF0b3JzLCBhbmQgcHVuY3R1YXRpb24KICB0cSA8LSBnc3ViKCJcXHsiLCAiIFxceyAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXH0iLCAiIFxcfSAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXCkiLCAiIFxcKSAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXCgiLCAiIFxcKCAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXF0iLCAiIFxcXSAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXFsiLCAiIFxcWyAiLCB0cSkKICB0cSA8LSBnc3ViKCIoW148XXxbXj5dfFteIV0pPSIsICIgPSAiLCB0cSkKICB0cSA8LSBnc3ViKCIhPSIsICIgIT0gIiwgdHEpCiAgdHEgPC0gZ3N1YigiPj0iLCAiID49ICIsIHRxKQogIHRxIDwtIGdzdWIoIjw9IiwgIiA8PSAiLCB0cSkKICB0cSA8LSBnc3ViKCIhKFtePV0pIiwgIiAhICIsIHRxKQogIHRxIDwtIGdzdWIoIj4oW149XSkiLCAiID4gIiwgdHEpCiAgdHEgPC0gZ3N1YigiPChbXj1dKSIsICIgPCAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXF4iLCAiIFxcXiAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXCsiLCAiIFxcKyAiLCB0cSkKICB0cSA8LSBnc3ViKCItIiwgIiAtICIsIHRxKQogIHRxIDwtIGdzdWIoIlxcKiIsICIgXFwqICIsIHRxKQogIHRxIDwtIGdzdWIoIi8iLCAiIC8gIiwgdHEpCiAgdHEgPC0gZ3N1YigiXFx8XFx8IiwgIiBcXHxcXHwgIiwgdHEpCiAgdHEgPC0gZ3N1YigiKFteXFx8XSlcXHwoW15cXHxdKSIsICIgXFx8ICIsIHRxKQogIHRxIDwtIGdzdWIoIiYmIiwgIiAmJiAiLCB0cSkKICB0cSA8LSBnc3ViKCIsIiwgIiAsICIsIHRxKQogIHRxIDwtIGdzdWIoIlxcLiIsICIgXFwuICIsIHRxKQogIHRxIDwtIGdzdWIoIjsiLCAiIDsgIiwgdHEpCiAgdHEgPC0gZ3N1YigiXFxzKyIsICIgIiwgdHEpCiAgCiAgIyAtIG1hdGNoIGxpdGVyYWxzCiAgdHEgPC0gZ3N1YignKFwnfCIpKyhbXlwnIl0pKyhcJ3wiKSsnLCAiIF9fTElURVJBTF9fICIsIHRxKQogIHRxIDwtIGdzdWIoJ1xcYltbOmRpZ2l0Ol1dK1xcYicsICIgX19MSVRFUkFMX18gIiwgdHEpCiAgCiAgIyAtICJ3b3JkIHRva2VuaXphdGlvbiI6IHNwbGl0IGJ5ICJccyIKICB0cSA8LSB1bmxpc3QobGFwcGx5KHRxLCBmdW5jdGlvbih4KSB7CiAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICJcXHMiKQogIH0pKQogIAogICMgLSByZW1vdmU6ICciJywgIi4iLCAiLCIKICB0cSA8LSBnc3ViKCcoInxcXC58LCkrJywgJycsIHRxKQogIAogICMgLSByZW1vdmU6ICInIgogIHRxIDwtIGdzdWIoIicrIiwgJycsIHRxKQogIAogICMgLSBjb3VudCB1bmlxdWUgdmFyaWFibGVzCiAgbnVtX3ZhcnMgPC0gbGVuZ3RoKHVuaXF1ZSh0cVtncmVwbCgnXlxcP3xeXFwkJywgdHEpXSkpCiAgCiAgIyAtIGNvdW50IHZhcmlhYmxlIHVzZQogIHZhcnNfdXNhZ2UgPC0gbGVuZ3RoKHRxW2dyZXBsKCdeXFw/fF5cXCQnLCB0cSldKQogIAogICMgLSBjb3VudCB1bmlxdWUgbGFiZWwgdmFyaWFibGVzCiAgbnVtX3ZhcnNfbGFiZWwgPC0gbGVuZ3RoKHVuaXF1ZSh0cVtncmVwbCgnW146XWxhYmVsJCcsIHRxLCBpZ25vcmUuY2FzZSA9IFQpXSkpCiAgCiAgIyAtIGNvdW50IGxhYmVsIHZhcmlhYmxlIHVzZQogIGxhYmVsX3ZhcnNfdXNhZ2UgPC0gbGVuZ3RoKHRxW2dyZXBsKCdbXjpdbGFiZWwkJywgdHEsIGlnbm9yZS5jYXNlID0gVCldKQogIAogICMgLSBhZGQgdmFyaWFibGVzIHVzZSBhbmQgdW5pcXVlIHZhcmlhYmxlIGNvdW50IHRvIHRxCiAgdHEgPC0gYyh0cSwgCiAgICAgICAgICByZXAoJ19fdmFyc19fJywgbnVtX3ZhcnMpLCAKICAgICAgICAgIHJlcCgnX192YXJzX3VzYWdlX18nLCB2YXJzX3VzYWdlKSwgCiAgICAgICAgICByZXAoJ19fdmFyc19sYWJlbF9fJywgbnVtX3ZhcnNfbGFiZWwpLAogICAgICAgICAgcmVwKCdfX3ZhcnNfbGFiZWxfdXNhZ2VfXycsIGxhYmVsX3ZhcnNfdXNhZ2UpKQogIAogICMgLSB0b2xvd2VyKCkKICB0cSA8LSB0b2xvd2VyKHRxKQogIAogICMgLSBjbGVhbiBmcm9tOiAiXSIsICJ9IiwgIjsiLCAiKSIgaWYgcHJlc2VudCBhbnl3aGVyZQogIHRxIDwtIGdzdWIoIlxcXXxcXH18XFwpfDsiLCAiIiwgdHEpCiAgCiAgIyAtIHJlbW92ZSBlbXB0eSBlbGVtZW50cyBmcm9tIHRxIGlmIGFueToKICB3RW1wdHkgPC0gd2hpY2goZ3JlcGwoIl5cXHMrJHxeJCIsIHRxKSkKICBpZiAobGVuZ3RoKHdFbXB0eSkgPiAwKSB7dHEgPC0gdHFbLXdFbXB0eV19CiAgCiAgIyAtIGVudGVyIGNvbW1lbnRzCiAgaWYgKGxlbmd0aCh0cV9jb21tZW50cykgPiAwKSB7CiAgICB0cSA8LSBjKHRxLCB0cV9jb21tZW50cykKICAgIH0KICAKICAjIC0gZW50ZXIgdHFfaXJpCiAgaWYgKGxlbmd0aCh0cV9pcmkpID4gMCkgewogICAgdHEgPC0gYyh0cSwgdHFfaXJpKQogICAgfQogIAogICMgLSB0cSBmZWF0dXJlIGZyZXF1ZW5jeSBhcyBkYXRhLmZyYW1lCiAgdHEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZSh0cSkpCiAgCiAgIyAtIGVudGVyIG5jaGFyKHRxKSBhcyBmZWF0dXJlOgogIHRxIDwtIHJiaW5kKHRxLCAKICAgICAgICAgICAgICBkYXRhLmZyYW1lKHRxID0gJ25jaGFyJywKICAgICAgICAgICAgICAgICAgICAgICAgIEZyZXEgPSB0cV9uY2hhcikpCiAgIyAtIG91cHV0OiB0cQogIHJldHVybih0cSkKfQoKIyMjIC0tLSBQYXJzZSBTUEFSUUwgcXVlcmllcyBpbiBwYXJhbGxlbApzZkluaXQocGFyYWxsZWwgPSBULCBjcHVzID0gMzYpCnQxIDwtIFN5cy50aW1lKCkKc2ZFeHBvcnQoInF1ZXJpZXMiKQpzZkV4cG9ydCgicGFyc2VfU1BBUlFMIikKc2ZMaWJyYXJ5KHN0cmluZ3IpCiMgLSBwcm9jZXNzCnF1ZXJpZXNfcHJvY2Vzc2VkIDwtIHNmQ2x1c3RlckFwcGx5TEIocXVlcmllcywgcGFyc2VfU1BBUlFMKQojIC0gc3RvcCBjbHVzdGVyCnNmU3RvcCgpCnByaW50KHBhc3RlMCgiUGFyc2luZyB0b29rOiAiLCAKICAgICAgICAgICAgIHJvdW5kKGRpZmZ0aW1lKFN5cy50aW1lKCksIHQxLCB1bml0cyA9ICJtaW4iKSwgMikpKQoKIyMjIC0tLSBjb2xsZWN0IHJlc3VsdHMKbmFtZXMocXVlcmllc19wcm9jZXNzZWQpIDwtIHVuaXF1ZVF1ZXJ5SWQKcXVlcmllc19wcm9jZXNzZWQgPC0gcmJpbmRsaXN0KHF1ZXJpZXNfcHJvY2Vzc2VkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkY29sID0gVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2UubmFtZXMgPSBUKQpjb2xuYW1lcyhxdWVyaWVzX3Byb2Nlc3NlZCkgPC0gCiAgYygndW5pcXVlU3BhcnFsSWQnLCAnZmVhdHVyZScsICdmcmVxJykKd3JpdGUuY3N2KHF1ZXJpZXNfcHJvY2Vzc2VkLCBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAicXVlcmllc19wcm9jZXNzZWQuY3N2IikpCgojIyMgLS0tIHByb2R1Y2Ugdm9jYWJ1bGFyeTogZmVhdHVyZSBmcmVxdWVuY3kKdm9jYWJ1bGFyeSA8LSBxdWVyaWVzX3Byb2Nlc3NlZCAlPiUKICBkcGx5cjo6c2VsZWN0KGZlYXR1cmUsIGZyZXEpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoZmVhdHVyZSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UoZnJlcSA9IHN1bShmcmVxKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MoZnJlcSkpCndyaXRlLmNzdih2b2NhYnVsYXJ5LCBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAicXVlcmllc192b2NhYnVsYXJ5LmNzdiIpKQpkaW0odm9jYWJ1bGFyeSkKaGVhZChkYXRhLmZyYW1lKHZvY2FidWxhcnkpLCA0MDApCgojIyMgLS0tIGluIGhvdyBtYW55IHF1ZXJpZXMgaXMgYSBwYXJ0aWN1bGFyIGZlYXR1cmUgdXNlZD8KcXVlcnlfZmVhdHVyZXMgPC0gcXVlcmllc19wcm9jZXNzZWQgJT4lIAogIGRwbHlyOjpzZWxlY3QoZmVhdHVyZSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShmZWF0dXJlKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShudW1fcXVlcmllcyA9IG4oKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MobnVtX3F1ZXJpZXMpKQpxdWVyeV9mZWF0dXJlcyRwZXJjZW50X3F1ZXJpZXMgPC0gCiAgcXVlcnlfZmVhdHVyZXMkbnVtX3F1ZXJpZXMvbGVuZ3RoKHVuaXF1ZShxdWVyaWVzX3Byb2Nlc3NlZCR1bmlxdWVTcGFycWxJZCkpCndyaXRlLmNzdihxdWVyeV9mZWF0dXJlcywgcGFzdGUwKGFuYWx5dGljc0RpciwgInF1ZXJpZXNfY292ZXJhZ2UuY3N2IikpCmhlYWQoZGF0YS5mcmFtZShxdWVyeV9mZWF0dXJlcyksIDQwMCkKCiMgLSBjbGVhcgpybShsaXN0ID0gc2V0ZGlmZihscygpLCBjKCdhbmFseXRpY3NEaXInLCAnZGF0YURpcicsICdmUGF0aCcpKSkKZ2MoKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFNlY3Rpb24gMi4gUHJlcGFyZSBNb2RlbCBEYXRhc2V0cwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tIGZlYXR1cmUgZnJlcXVlbmNpZXM6IHZvY2FidWxhcnkKZmlsZW5hbWUgPC0gJ3F1ZXJpZXNfdm9jYWJ1bGFyeS5jc3YnCnZvY2FidWxhcnkgPC0gZnJlYWQocGFzdGUwKGFuYWx5dGljc0RpciwgZmlsZW5hbWUpKQp2b2NhYnVsYXJ5JFYxIDwtIE5VTEwKCiMjIyAtLS0gZGF0YXNldApmaWxlbmFtZSA8LSAnd2Rxc19kYXRhX3NhbXBsZV8yMDIwLTA0LTIyLmNzdicKZGF0YVNldCA8LSBmcmVhZChwYXN0ZTAoYW5hbHl0aWNzRGlyLCBmaWxlbmFtZSkpCmRhdGFTZXQkVjEgPC0gTlVMTAojIC0gcmVtb3ZlIGZlYXR1cmVzIHRoYXQgd2lsbCBub3QgYmUgdXNlZCBpbiB0aGUgTUwgcGhhc2UKZGF0YVNldCRzcGFycWwgPC0gTlVMTApkYXRhU2V0JG1pbnV0ZSA8LSBOVUxMCiMgLSBkZXJpdmU6IG5fY29uY19xdWVyaWVzIHBlciBkYXRhY2VudGVyIHggYmFja19lbmRob3N0CmRhdGFTZXQkZGF0YWNlbnRlcl9ob3N0IDwtIHBhc3RlKGRhdGFTZXQkZGF0YWNlbnRlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YVNldCRiYWNrZW5kX2hvc3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXyIpCmRhdGFTZXQkZGF0YWNlbnRlciA8LSBOVUxMCmRhdGFTZXQkYmFja2VuZF9ob3N0IDwtIE5VTEwKIyAtIHRoZSBudW1iZXIgb2YgcXVlcmllcyBzdGFydGVkIGluIHRoZSBzYW1lIG1pbnV0ZQojIC0gYXMgdGhlIHRhcmdldCBxdWVyeQojIC0gcGVyIGRhdGFjZW50ZXIgeCBiYWNrX2VuZGhvc3QKZGF0YVNldCRkYXRhY2VudGVyX2hvc3RfeW1kaG0gPC0gcGFzdGUoZGF0YVNldCRkYXRhY2VudGVyX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFTZXQkeW1kaG0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXyIpCmRhdGFTZXQkeW1kaG0gPC0gTlVMTApuX2NvbmNfcXVlcmllcyA8LSBkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KGRhdGFjZW50ZXJfaG9zdF95bWRobSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShkYXRhY2VudGVyX2hvc3RfeW1kaG0pICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKG5fY29uY19xdWVyaWVzID0gbigpKQpkYXRhU2V0IDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpsZWZ0X2pvaW4obl9jb25jX3F1ZXJpZXMsIGJ5ID0gImRhdGFjZW50ZXJfaG9zdF95bWRobSIpCmRhdGFTZXQkZGF0YWNlbnRlcl9ob3N0X3ltZGhtIDwtIE5VTEwKcm0obl9jb25jX3F1ZXJpZXMpCiMgLSBkZXJpdmU6IG1pbGQgYW5kIGV4dHJlbWUgb3V0bGllcnMgb24gcXVlcnlfdGltZQojIC0gcXVlcnlfdGltZSA+PSBxMyArIDMqaXEgCiMgLSBxMyA9IDc1IHBlcmNlbnRpbGUsIHExID0gMjUgcGVyY2VudGlsZQpxMyA8LSBxdWFudGlsZShkYXRhU2V0JHF1ZXJ5X3RpbWUsIC43NSkKcTEgPC0gcXVhbnRpbGUoZGF0YVNldCRxdWVyeV90aW1lLCAuMjUpCiMgLSBpcSA9IHEzIC0gcTE7IEludGVycXVhcnRpbGUgcmFuZ2UKaXEgPC0gdW5uYW1lKHEzIC0gcTEpCnVwcGVyX291dGVyX2ZlbmNlIDwtIHEzICsgMyppcQp1cHBlcl9pbm5lcl9mZW5jZSA8LSBxMyArIDEuNSppcQpkYXRhU2V0JHF1ZXJ5X3RpbWVfZXh0cmVtZSA8LSAKICBpZmVsc2UoZGF0YVNldCRxdWVyeV90aW1lID49IHVwcGVyX291dGVyX2ZlbmNlLCAxLCAwKQpkYXRhU2V0JHF1ZXJ5X3RpbWVfbWlsZCA8LSAKICBpZmVsc2UoZGF0YVNldCRxdWVyeV90aW1lID49IHVwcGVyX2lubmVyX2ZlbmNlLCAxLCAwKQojIC0gZGVyaXZlIG11bHRpY2xhc3MgRFY6IDAgLSBtaWxkLCBtaWxkIC0gZXh0cmVtZSwgZXh0cmVtZSBxdWVyeV90aW1lCmRhdGFTZXQkcXVlcnlfdGltZV9jbGFzcyA8LSAwCmRhdGFTZXQkcXVlcnlfdGltZV9jbGFzc1tkYXRhU2V0JHF1ZXJ5X3RpbWVfZXh0cmVtZSA9PSAwICYgZGF0YVNldCRxdWVyeV90aW1lX21pbGQgPT0gMV0gPC0gMQpkYXRhU2V0JHF1ZXJ5X3RpbWVfY2xhc3NbZGF0YVNldCRxdWVyeV90aW1lX2V4dHJlbWUgPT0gMV0gPC0gMgojIC0gZGVyaXZlOiBiZWxvdyBhbmQgYWJvdmUgbWVkaWFuIHF1ZXJ5X3RpbWUKZGF0YVNldCRxdWVyeV90aW1lX21lZGlhbiA8LSBpZmVsc2UoZGF0YVNldCRxdWVyeV90aW1lID49IG1lZGlhbihkYXRhU2V0JHF1ZXJ5X3RpbWUpLCAxLCAwKQoKIyMjIC0tLSB2b2NhYnVsYXJ5OiB0b3BfZiBtb3N0IGZyZXF1ZW50bHkgdXNlZCBmZWF0dXJlcwp0b3BfZiA8LSAzMDAKb2JfZmVhdHVyZXMgPC0gYygnbmNoYXInLCAnX192YXJzX18nLCAnX192YXJzX3VzYWdlX18nLCAnX19saXRlcmFsX18nLCAKICAgICAgICAgICAgICAgICAnX192YXJzX2xhYmVsX18nLCAnX192YXJzX2xhYmVsX3VzYWdlX18nLCAnX19pcmlfcmVmX18nKQp2b2NhYnVsYXJ5IDwtIHZvY2FidWxhcnkkZmVhdHVyZVsxOnRvcF9mXQp2b2NhYnVsYXJ5IDwtIHVuaXF1ZShjKHZvY2FidWxhcnksIG9iX2ZlYXR1cmVzKSkKCiMjIyAtLS0gam9pbiB2b2NhYnVsYXJ5IHRvIGRhdGFTZXQKcXVlcmllc19wcm9jZXNzZWQgPC0gZnJlYWQocGFzdGUwKGFuYWx5dGljc0RpciwgJ3F1ZXJpZXNfcHJvY2Vzc2VkLmNzdicpKQpxdWVyaWVzX3Byb2Nlc3NlZCRWMSA8LSBOVUxMCmRpbShxdWVyaWVzX3Byb2Nlc3NlZCkKcXVlcmllc19wcm9jZXNzZWQgPC0gcXVlcmllc19wcm9jZXNzZWQgJT4lIAogIGRwbHlyOjpmaWx0ZXIoZmVhdHVyZSAlaW4lIHZvY2FidWxhcnkpCnJtKHZvY2FidWxhcnkpCmRpbShxdWVyaWVzX3Byb2Nlc3NlZCkKZmVhdHVyZUZyYW1lIDwtIGRhdGEudGFibGU6OmRjYXN0KGFzLmRhdGEudGFibGUocXVlcmllc19wcm9jZXNzZWQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5pcXVlU3BhcnFsSWQgfiBmZWF0dXJlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUudmFyID0gImZyZXEiKQpybShxdWVyaWVzX3Byb2Nlc3NlZCk7IGdjKCkKIyAtIG1hcmsgZmVhdHVyZXMgaW4gZGF0YVNldDogZHMKY29sbmFtZXMoZGF0YVNldCkgPC0gcGFzdGUwKCJkc18iLCBjb2xuYW1lcyhkYXRhU2V0KSkKIyAtIGpvaW46CmRhdGFTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmxlZnRfam9pbihmZWF0dXJlRnJhbWUsIAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCdkc191bmlxdWVTcGFycWxJZCcgPSAndW5pcXVlU3BhcnFsSWQnKSkKcm0oZmVhdHVyZUZyYW1lKTsgZ2MoKQpjb2xuYW1lcyhkYXRhU2V0KQoKIyMjIC0tLSBjb21mb3J0IGNvbHVtbiBuYW1lcyB0byB7eGdib29zdH0KbW9kZWxGcmFtZUNvbHVtbnMgPC0gY29sbmFtZXMoZGF0YVNldCkKIyAtIGRlZmluZSByZXBsYWNlbWVudHMKbWNoYXJfcmVnZXggPC0gYygnXFwuJywgJ1xcXFwnLCAnXFx8JywgJ1xcKCcsICdcXCknLAogICAgICAgICAgICAgICAgICdcXFsnLCAnXFxdJywgJ1xceycsICdcXH0nLCAnXFxeJywKICAgICAgICAgICAgICAgICAnXFwkJywgJ1xcKicsICdcXCsnLCAnXFw/JykKbWNoYXJfcmVnZXhfcmVwbGFjZSA8LSBjKCdfZG90XycsICdfYmFja3NsYXNoXycsICdfdmJhcl8nLCAnX2xlZnRfYnJhY2tldF8nLCAnX3JpZ2h0X2JyYWNrZXRfJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAnX2xlZnRfc3FfYnJhY2tldF8nLCAnX3JpZ2h0X3NxX2JyYWNrZXRfJywgJ19sZWZ0X2N1cmx5XycsICdfcmlnaHRfY3VybHlfJywgJ19jYXJldF8nLCAKICAgICAgICAgICAgICAgICAgICAgICAgICdfZG9sbGFyXycsICdfYXN0ZXJpc2tfJywgJ19wbHVzXycsICdfcXVlc3Rpb25fbWFya18nKQptb2RlbEZyYW1lQ29sdW1ucyA8LSB1bm5hbWUoc2FwcGx5KG1vZGVsRnJhbWVDb2x1bW5zLCBmdW5jdGlvbih4KSB7CiAgZm9yIChpIGluIDE6bGVuZ3RoKG1jaGFyX3JlZ2V4KSkgewogICAgaWYgKGdyZXBsKG1jaGFyX3JlZ2V4W2ldLCB4KSkgewogICAgICB4IDwtIGdzdWIobWNoYXJfcmVnZXhbaV0sIG1jaGFyX3JlZ2V4X3JlcGxhY2VbaV0sIHgpCiAgICB9CiAgfQogIHJldHVybih4KQp9KSkKc3BhcnFsX29wcyA8LSBjKCcjJywgJ0AnLCAnOicsICc7JywgJy8nLCAnPicsICc8JywgJz0nLCAnPD0nLCAnPj0nKQpzcGFycWxfb3BzX3JlcGxhY2UgPC0gYygnX2hhc2hfJywgJ19hdF8nLCAnX2NvbG9uXycsICdfc2VtaV9jb2xvbl8nLCAnX3NsYXNoXycsICdfZ3JlYXRlcl90aGFuXycsICdfbGVzc190aGFuXycsIAogICAgICAgICAgICAgICAgICAgICAgICAnX2VxdWFsc18nLCAnX2xlc3NfdGhhbl9vcl9lcXVhbF8nLCAnX2dyZWF0ZXJfdGhhbl9vcl9lcXVhbF8nKQptb2RlbEZyYW1lQ29sdW1ucyA8LSB1bm5hbWUoc2FwcGx5KG1vZGVsRnJhbWVDb2x1bW5zLCBmdW5jdGlvbih4KSB7CiAgZm9yIChpIGluIDE6bGVuZ3RoKHNwYXJxbF9vcHMpKSB7CiAgICBpZiAoZ3JlcGwoc3BhcnFsX29wc1tpXSwgeCkpIHsKICAgICAgeCA8LSBnc3ViKHNwYXJxbF9vcHNbaV0sIHNwYXJxbF9vcHNfcmVwbGFjZVtpXSwgeCkKICAgIH0KICB9CiAgcmV0dXJuKHgpCn0pKQptb2RlbEZyYW1lQ29sdW1ucyA8LSBwYXN0ZTAoImZfIiwgbW9kZWxGcmFtZUNvbHVtbnMpCiMgLSBwcm9kdWNlIGFuZCBzdG9yZSBtb2RlbEZyYW1lRmVhdHVyZUNvZGUKbW9kZWxGcmFtZUZlYXR1cmVDb2RlIDwtIGRhdGEuZnJhbWUoZmVhdHVyZSA9IGNvbG5hbWVzKGRhdGFTZXQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeGdib29zdF9mZWF0dXJlID0gbW9kZWxGcmFtZUNvbHVtbnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZmlsZW5hbWUgPC0gcGFzdGUwKCdtb2RlbEZyYW1lRmVhdHVyZUNvZGUnLCAnXycsIHRvcF9mLCAnLmNzdicpCndyaXRlLmNzdihtb2RlbEZyYW1lRmVhdHVyZUNvZGUsCiAgICAgICAgICBwYXN0ZTAoYW5hbHl0aWNzRGlyLCBmaWxlbmFtZSkpCiMgLSBzdG9yZSBkYXRhU2V0IHcuIHt4Z2Jvb3N0fSBjb2x1bW4gbmFtZXMKY29sbmFtZXMoZGF0YVNldCkgPC0gbW9kZWxGcmFtZUZlYXR1cmVDb2RlJHhnYm9vc3RfZmVhdHVyZQpmaWxlbmFtZSA8LSBwYXN0ZTAoJ21vZGVsRnJhbWUnLCAnXycsIHRvcF9mLCAnLmNzdicpCndyaXRlLmNzdihkYXRhU2V0LAogICAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgZmlsZW5hbWUpKQpgYGAKCkFnYWluLCBUaGUgZ29hbCBpcyB0byBkZXJpdmUgYSBkZXNjcmlwdGlvbiBvZiBTUEFSUUwgcXVlcmllcyB2aWEgZmVhdHVyZSB2ZWN0b3JzLiBGb3IgZXhhbXBsZToKCj4gX19pcmlfcmVmX186MTEsIF9fbGl0ZXJhbF9fOjEsIF9fdmFyc19fOjUsIF9fdmFyc19sYWJlbF9fOjEsIF9fdmFyc19sYWJlbF91c2FnZV9fOjEsIF9fdmFyc191c2FnZV9fOjIwLCA/aXRlbToxNSwgP3R5cGU6MSwgP3R5cGVsYWJlbDoxLCA/dXJsOjIsID93aWtpcGVkaWE6MSwgezoxNCwgPGh0dHA6Ly9zdGFuZGFydGNvbnRhLmNvbS50ci8+OjEsIDxodHRwOi8vc3RhbmRhcnRjb250YS5jb20udHI+OjEsIDxodHRwOi8vd3d3LnN0YW5kYXJ0Y29udGEuY29tLnRyLz46MiwgPGh0dHA6Ly93d3cuc3RhbmRhcnRjb250YS5jb20udHI+OjEsIDxodHRwOi8vd3d3Lnd3dy5zdGFuZGFydGNvbnRhLmNvbS50ci8+OjEsIDxodHRwczovL3N0YW5kYXJ0Y29udGEuY29tLnRyLz46MSwgPGh0dHBzOi8vc3RhbmRhcnRjb250YS5jb20udHI+OjEsIDxodHRwczovL3d3dy5zdGFuZGFydGNvbnRhLmNvbS50ci8+OjEsIDxodHRwczovL3d3dy5zdGFuZGFydGNvbnRhLmNvbS50cj46MSwgPGh0dHBzOi8vd3d3Lnd3dy5zdGFuZGFydGNvbnRhLmNvbS50ci8+OjEsIGJkOnNlcnZpY2VwYXJhbToxLCBkaXN0aW5jdDoxLCBvcHRpb25hbDoxLCBzY2hlbWE6YWJvdXQ6MSwgc2VsZWN0OjEsIHNlcnZpY2U6MSwgdW5pb246MTAsIHdkdDpwMzE6MSwgd2R0OnA4NTY6MTIsIHdoZXJlOjEsIHdpa2liYXNlOmxhYmVsOjEsIHdpa2liYXNlOmxhbmd1YWdlOjEsIG5jaGFyOjkwMwoKSW4gdGhpcyBleGFtcGxlIG9mIGEgcXVlcnkgZmVhdHVyZSB2ZWN0b3Igd2Ugc2VlIHRoYXQgdGhlIHF1ZXJ5IGhhZCB1c2VkIDExIElSSSByZWZlcmVuY2VzIChgX19pcmlfcmVmX186MTFgKSwgb25lIGxpdGVyYWwgKGBfX2xpdGVyYWxfXzoxYCksIGZpdmUgdmFyaWFibGVzIChgX192YXJzX186NWApIHRoYXQgd2VyZSBtZW50aW9uZWQgMjAgdGltZXMgYWx0b2dldGhlciAoYF9fdmFyc191c2FnZV9fOjIwYCksIHVzZWQgYHdpa2liYXNlOmxhYmVsYCBvbmNlIChgd2lraWJhc2U6bGFiZWw6MWApLCB0aGUgYFVOSU9OYCBrZXl3b3JkIDEwIHRpbWVzIChgdW5pb246MTBgKSwgdGhhdCB0aGUgbGVuZ3RoIG9mIHRoZSBxdWVyeSBpbiBjaGFyYWN0ZXJzIGlzIGBuY2hhcjo5MDNgLCBldGMuCgojIyAyLiBNYWNoaW5lIExlYXJuaW5nIHcuIFhHQm9vc3Q6IGNhbiB3ZSBwcmVkaWN0IG1pbGQgb3V0bGllcnMgb24gV0RRUyByZXNwb25zZSB0aW1lIGZyb20gU1BBUlFMIHF1ZXJ5IGZlYXR1cmVzPwoKVGh1cyBmYXIsCgotIGFsbCBTUEFSUUwgcXVlcmllcyBhcmUgcmVwcmVzZW50ZWQgYXMgZmVhdHVyZSB2ZWN0b3JzLCBhbmQKLSBhIGNyaXRlcmlvbiB2YXJpYWJsZSBoYXMgYmVlbiBkZWZpbmVkIChtaWxkIG91dGxpZXJzIG9uIFdEUVMgc2VydmVyIHJlc3BvbnNlIHRpbWVzKS4KCk91ciBnb2FsIGlzIHRvIHNlbGVjdCBhIHByZWRpY3RpdmUgbW9kZWwgdGhhdCB3aWxsIHVzZSB0aGUgZmVhdXR1cmUgdmVjdG9ycyB0byBwcmVkaWN0IHdoZXRoZXIgdGhlIHJlc3BlY3RpdmUgcXVlcmllcycgcmVzcG9uc2UgdGltZXMgYXJlIGNyaXRpY2FsIChpLmUuIGFyZSB0aGV5IG91dGxpZXJzIG9uIGBxdWVyeV90aW1lYCkgb3Igbm90LiBBdCB0aGUgc2FtZSB0aW1lLCB0aGUgbW9kZWwgd2lsbCBiZSBwZXJmb3JtaW5nIGZlYXR1cmUgc2VsZWN0aW9uIChpLmUuIFhHQm9vc3Qgd2lsbCBub3QgbmVjZXNzYXJpbHkgdXNlIGFsbCBvZiB0aGUgcHJvdmlkZWQgZmVhdHVyZXMgdG8gYnVpbGQgYSBzZXF1ZW5jZSBvZiBkZWNpc2lvbiB0cmVlcyBmb3IgYmluYXJ5IGNsYXNzaWZpY2F0aW9uKSBhbmQgaW5mb3JtaW5nIHVzIGFib3V0IHRoZSB2YWx1ZSBvZiBlYWNoIGZlYXR1cmUgaW4gdGhlIHByZWRpY3Rpb24uIFRoaXMgd2lsbCBhbGxvd3MgdXMgdG8gc2VsZWN0IGEgc21hbGwgc2V0IG9mIFNQQVJRTCBmZWF1dGVycyB0aGF0IGNhbiBiZSB1c2VkIHRvIGNoYXJhY3Rlcml6ZSB0aGUgcXVlcmllcyBhcyByZXN1bHRpbmcgaW4gdHlwaWNhbCB2cy4gZXh0cmVtZSBXRFFTIHJlc3BvbnNlIHRpbWVzLiBBbmQgc3VjaCBzbWFsbCBmZWF0dXJlIHZvY2FidWxhcnkgaXMgZXhhY3RseSB3aGF0IHdlIGFyZSBsb29raW5nIGZvciBpbiBvcmRlciB0byBiZSBhYmxlIHRvIGRldmVsb3AgYSBXRFFTIG9wdGltaXphdGlvbiBlbmdpbmUgaW4gdGhlIGZ1dHVyZTogYSBzeXN0ZW0gdGhhdCB3b3VsZCBmYXN0IGNhbiBhbnkgZ2l2ZW4gU1BBUlFMIHF1ZXJ5IGFuZCBkZWNpZGUgd2hldGVyIGl0IG5lZWRzIHRvIGJlIGhhbmRsZWQgYXMgYSBzcGVjaWFsLCBwb3RlbnRpYWxseSBkaWZmaWN1bHQgY2FzZSB0byBwcm9jZXNzIC0gb3Igc2hvdWxkIHRoZSBwcm9jZXNzaW5nIHByb2NlZWQgYXMgdXN1YWwuCgpXZSBoYXZlIHVzZWQgW1hHQm9vc3RdKGh0dHBzOi8veGdib29zdC5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvaW5kZXguaHRtbCkgdmlhIHRoZSBSIFt7eGdib29zdH1dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy94Z2Jvb3N0L2luZGV4Lmh0bWwpIHBhY2thZ2UgdG8gdHJhaW4gbWFueSBwcmVkaWN0aXZlIG1vZGVscyB0aGF0IGF0dGVtcHQgdG8gY2F0ZWdvcml6ZSBTUEFSUUwgcXVlcmllcyBhcyBjcml0aWNhbCBvciBub3QgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBhYm92ZSBzdGF0ZWQgY3JpdGVyaW9uLiBYR0Jvb3N0IC0gc2hvcnQgZm9yIFtFeHRyZW1lIEdyYWRpZW50IEJvb3N0aW5nXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9HcmFkaWVudF9ib29zdGluZykgLSBpcyBhIE1MIG1ldGhvZCB0byBlc3RpbWF0ZSBhbiBlbnNlbWJsZSBvZiBtYXRoZW1hdGljYWwgbW9kZWxzIC0gZGVjaXNpb24gdHJlZXMgaW4gb3VyIGNhc2UgLSB0aGF0IGpvaW50bHkgd29yayB0byBwcmVkaWN0IHRoZSByZXNwb25zZSB2YXJpYWJsZS4gSXRzIGRlc2NyaXB0aW9uIGlzIHJlYWxseSB3YXkgb3V0IG9mIHNjb3BlIG9mIHRoaXMgUmVwb3J0LiBUaGlzIHRlY2huaXF1ZSBpcyBhcyBrbm93biB0byBiZSBub3RvcmlvdXNseSBnb29kIGluIGV4dHJhY3RpbmcgYWxtb3N0IGV2ZXJ5IGFuZCB0aGUgbGFzdCBwaWVjZSBvZiB1c2VmdWwgaW5mb3JtYXRpb24gZnJvbSB0aGUgdHJhaW5pbmcgZGF0YSAoZmVhdXR1cmUgdmVjdG9ycyBkZXNjcmliaW5nIHRoZSBTUEFSUUwgcXVlcmllcyBpbiB0aGlzIGNhc2UpLiBUaGUgWEdCb29zdCBtb2RlbHMgaW52b2x2ZSBhIFtsYXJnZSBudW1iZXIgb2YgcGFyYW1ldGVyc10oaHR0cHM6Ly94Z2Jvb3N0LnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC9wYXJhbWV0ZXIuaHRtbCkgdGhhdCBtdXN0IGJlIGZpbmUtdHVuZWQgaW4gb3JkZXIgZm9yIGl0IHRvIGRlcml2ZSBhIHBsYXVzaWJsZSBwcmVkaWN0aXZlIG1vZGVsIGZvciB0aGUgdGFzayBhdCBoYW5kLiBXZSBoYXZlIGV4cGVyaW1lbnRlZCB3aXRoIHZhcmlvdXMgcGFyYW1ldGVyIHNldHRpbmdzIGFuZCBkYXRhIHNldHMgZW5jb21wYXNzaW5nIGEgdmFyaWFibGUgbnVtYmVyIG9mIGV4dHJhY3RlZCBTUEFSUUwgZmVhdHVyZXMgdG8gcmVhY2ggdGhlIGZvbGxvd2luZyBjb25jbHVzaW9uczoKCi0gdGhlIG51bWJlciBvZiBmZWF0dXJlcyBpbml0aWFsbHkgdXNlZCB0byBidWlsZCBhIG1vZGVsIGRvZXMgbm90IHNpZ25pZmljYW50bHkgY2hhbmdlIHRoZSAqYWNjdXJhY3kqLCAqdHJ1ZSBwb3NpdGl2ZSByYXRlKiwgYW5kICpmYWxzZSBwb3NpdGl2ZSByYXRlKiAoYm90aCB0byBiZSBleHBsYWluZWQgc29vbikgd2l0aCB3aGljaCB0aGUgbW9kZWwgY2FuIHRlbGwgdGhlIGNyaXRpY2FsIFNQQVJRTCBxdWVyaWVzIGZyb20gdHlwaWNhbCBvbmVzOyB0aGlzIGlzIG1vc3QgcHJvYmFibHkgZW50aXJlbHkgZHVlIHRvIHRoZSBmYWN0IHRoYXQgWEdCb29zdCBwZXJmb3JtcyBmZWF0dXJlIHNlbGVjdGlvbiBhbG9uZyB0aGUgd2F5IG9mIGl0cyB0cmFpbmluZzsgdGhpcyBpcyBhbHNvICp2ZXJ5IGltcG9ydGFudCogYmVjYXVzZSBpdCBtZWFucyB0aGF0IHdlIGNhbiB1c2Ugb25seSBhIHNtYWxsIGZyYWN0aW9uIG9mIHRoZSBtb3N0IGZyZXF1ZW50bHkgdXNlZCBTUEFSUUwgZmVhdHVyZXMgdG8gdHJhaW4gYSBwcmVkaWN0aXZlIG1vZGVsIC0gYW5kIHRoZSBtb2RlbCB3aXRoIGxlc3MgZmVhdHVyZXMgd2lsbCBtYWtlIGEgcHJlZGljdGl2ZSBydW4gZmFzdGVyIHRoYW4gdGhlIG9uZSBlbmNvbXBhc3NpbmcgYSBsYXJnZSBudW1iZXIgb2YgZmVhdHVyZXM7IAotIG1vc3RseSwgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBwcmVkaWN0aXZlIG1vZGVsIGlzIG5vdCBzZXJpb3VzbHkgYWZmZWN0ZWQgYnkgY2hvb3NpbmcgdmFyaW91cyBjb21iaW5hdGlvbnMgb2YgcGFyYW1ldGVyIHNldHRpbmdzOyB3ZSBoYXZlIHBlcmZvcm1lZCBtYW55IGNyb3NzLXZhbGlkYXRpb25zLCBhbHdheXMgYWRkaXRpb25hbCBzcGxpdHRpbmcgb3VyIGAxTWAgc2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIGludG8gdHJhaW4gYW5kIHRlc3QgZGF0YSAoYXBwcm94LiA1MCUgb2YgdGhlIHNhbXBsZSBlYWNoKSwgaW4gb3JkZXIgdG8gcmVhY2ggdGhpcyBjb25jbHVzaW9uOwotIHRoZSBtb2RlbCBwZXJmb3JtYW5jZSBpcyBnb29kLCBidXQgZmFyIGZyb20gcGVyZmVjdC4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gRiwgbWVzc2FnZSA9IEZ9CgojIS91c3IvYmluL2VudiBSc2NyaXB0CgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gV0RRU19FdmVudF9TNEFfWEdCb29zdE9wdGltaXphdGlvbl9Eb3duc2FtcGxpbmcuUiwgdi4gMC4wLjEKIyMjIC0tLSBTY3JpcHQ6IFdEUVNfRXZlbnRfUzRBX1hHQm9vc3RPcHRpbWl6YXRpb25fRG93bnNhbXBsaW5nLlIsIHYuIDAuMC4xCiMjIyAtLS0gRGVzY3JpcHRpb246IFdEUVNfRXZlbnRfUzRBX1hHQm9vc3RPcHRpbWl6YXRpb25fRG93bnNhbXBsaW5nLlIgcGVyZm9ybXMgCiMjIyAtLS0gWEdCb29zdCBvcHRpbWl6YXRpb24gb2YgU1BBUlFMIHF1ZXJpZXMgV0RRUyByZXNwb25zZSB0aW1lcyAKIyMjIC0tLSBjb2xsZWN0ZWQgdy4gV0RRU19FdmVudF9TMV9FVEwuUiBmcm9tIHRoZSAKIyMjIC0tLSBldmVudC53ZHFzX2V4dGVybmFsX3NwYXJxbF9xdWVyeSB0YWJsZSBpbiB0aGUgV01GIERhdGEgTGFrZS4KIyMjIC0tLSBOT1RFLiBBcHByb2FjaCB0byBDbGFzcyBJbWJhbGFuY2VzOiBkb3duc2FtcGxpbmcuCiMjIyAtLS0gQXV0aG9yOiBHb3JhbiBTLiBNaWxvdmFub3ZpYywgRGF0YSBTY2llbnRpc3QsIFdNREUKIyMjIC0tLSBEZXZlbG9wZWQgdW5kZXIgdGhlIGNvbnRyYWN0IGJldHdlZW4gR29yYW4gTWlsb3Zhbm92aWMgUFIgRGF0YSBLb2xla3RpdgojIyMgLS0tIGFuZCBXTURFLgojIyMgLS0tIENvbnRhY3Q6IGdvcmFuLm1pbG92YW5vdmljX2V4dEB3aWtpbWVkaWEuZGUKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFJVTiBGUk9NOiBzdGF0MTAwNQojIyMgLS0tIERhdGU6IDIwMjAvMDQvMjcKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIExJQ0VOU0U6CiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBHUEwgdjIKIyMjIC0tLSBUaGlzIGZpbGUgaXMgcGFydCBvZiB0aGUgV2lraWRhdGEgU1BBUlFMIEVuZHBvaW50IEFuYWx5dGljcyBQcm9qZWN0CiMjIyAtLS0KIyMjIC0tLSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QgaXMgZnJlZSBzb2Z0d2FyZTogCiMjIyAtLS0geW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeQojIyMgLS0tIGl0IHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYXMgcHVibGlzaGVkIGJ5CiMjIyAtLS0gdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgZWl0aGVyIHZlcnNpb24gMiBvZiB0aGUgTGljZW5zZSwgb3IKIyMjIC0tLSAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLgojIyMgLS0tCiMjIyAtLS0gV2lraWRhdGEgU1BBUlFMIEVuZHBvaW50IEFuYWx5dGljcyBQcm9qZWN0IGlzIGRpc3RyaWJ1dGVkIAojIyMgLS0tIGluIHRoZSBob3BlIHRoYXQgaXQgd2lsbCBiZSB1c2VmdWwsCiMjIyAtLS0gYnV0IFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRob3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YKIyMjIC0tLSBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlCiMjIyAtLS0gR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZm9yIG1vcmUgZGV0YWlscy4KIyMjIC0tLQojIyMgLS0tIFlvdSBzaG91bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlCiMjIyAtLS0gYWxvbmcgd2l0aCBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QuCiMjIyAtLS0gSWYgbm90LCBzZWUgPGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy8+LgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tIHNldHVwCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGh0dHIpCmxpYnJhcnkocFJPQykKbGlicmFyeSh4Z2Jvb3N0KQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGZhc3REdW1taWVzKQoKIyMjIC0tLSBkaXJlY3RvcmllcwojIyMgLS0tIFJlYWQgV0RDTSBwYXJhbWVyZXRlcnMKIyAtIGZQYXRoOiB3aGVyZSB0aGUgc2NyaXB0cyBpcyBydW4gZnJvbT8KZlBhdGggPC0gYXMuY2hhcmFjdGVyKGNvbW1hbmRBcmdzKHRyYWlsaW5nT25seSA9IEZBTFNFKVs0XSkKZlBhdGggPC0gZ3N1YigiLS1maWxlPSIsICIiLCBmUGF0aCwgZml4ZWQgPSBUKQpmUGF0aCA8LSB1bmxpc3Qoc3Ryc3BsaXQoZlBhdGgsIHNwbGl0ID0gIi8iLCBmaXhlZCA9IFQpKQpmUGF0aCA8LSBwYXN0ZSgKICBwYXN0ZShmUGF0aFsxOmxlbmd0aChmUGF0aCkgLSAxXSwgY29sbGFwc2UgPSAiLyIpLAogICIvIiwKICBzZXAgPSAiIikKCmZQYXRoIDwtICcvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9XaWtpZGF0YS9XRF9taXNjL1dEX1NQQVJRTF9FbmRwb2ludF9BbmFseXRpY3MvJwojIC0gTk9URTogSGV0em5lciBsb2NhbCBkaXIKIyAgLSBmUGF0aCA8LSAnL2hvbWUvZ29yYW4vV01ERS9XRF9TUEFSUUxfRW5kcG9pbnRfQW5hbHl0aWNzLycKZGF0YURpciA8LSBwYXN0ZTAoZlBhdGgsICdfZXZlbnQvX2RhdGEvJykKYW5hbHl0aWNzRGlyIDwtIHBhc3RlMChmUGF0aCwgJ19ldmVudC9fYW5hbHl0aWNzLycpCnJlcG9ydGluZ0RpciA8LSBwYXN0ZTAoZlBhdGgsICdfZXZlbnQvX3JlcG9ydGluZy8nKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFNlY3Rpb24gMS4xIFRyYWluIHNlbGVjdGVkIG1vZGVsCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gZ3JhYiBkYXRhc2V0CmZpbGVuYW1lIDwtICdtb2RlbEZyYW1lXzMwMC5jc3YnCiMjIyAtLS0gbG9hZCBtb2RlbCBkYXRhc2V0Cm1vZGVsRnJhbWUgPC0gZnJlYWQocGFzdGUwKGFuYWx5dGljc0RpciwgZmlsZW5hbWUpKQptb2RlbEZyYW1lJFYxIDwtIE5VTEwKcHJpbnQocGFzdGUwKCJUaGUgZGltZXNpb24gaXM6ICIsIHBhc3RlKGRpbShtb2RlbEZyYW1lKSwgY29sbGFwc2UgPSAiLCAiKSkpCgojIyMgLS0tIHJlY29kZSBmX2RzX2RheSB0byBkYXkgb2Ygd2VlawpzYW1wbGVEYXlzIDwtIGlmZWxzZShuY2hhcihtb2RlbEZyYW1lJGZfZHNfZGF5KSA9PSAxLCAKICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCcwJywgbW9kZWxGcmFtZSRmX2RzX2RheSksCiAgICAgICAgICAgICAgICAgICAgIG1vZGVsRnJhbWUkZl9kc19kYXkpCm1vZGVsRnJhbWUkZl9kc19kYXkgPC0gcGFzdGUwKCIyMDIwLTA0LSIsIHNhbXBsZURheXMpCm1vZGVsRnJhbWUkZl9kc19kYXkgPC0gd2Vla2RheXMoYXMuRGF0ZShtb2RlbEZyYW1lJGZfZHNfZGF5KSkKCiMjIyAtLS0gcHJvY2VzcyBtb2RlbCBkYXRhc2V0CiMgLSBzcGxpdCBTUEFSUUwgZmVhdHVyZXMKZHNfZmVhdHVyZXMgPC0gY29sbmFtZXMobW9kZWxGcmFtZSlbd2hpY2goZ3JlcGwoIl5mX2RzIiwgY29sbmFtZXMobW9kZWxGcmFtZSkpKV0KbW9kZWxGcmFtZV9kcyA8LSBtb2RlbEZyYW1lWywgLi5kc19mZWF0dXJlc10KbW9kZWxGcmFtZV9mIDwtIG1vZGVsRnJhbWUgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWRzX2ZlYXR1cmVzKQpybShtb2RlbEZyYW1lKTsgZ2MoKQojIC0gdHJhbnNmb3JtIE5BIHRvIHplcm8gaW4gbW9kZWxGcmFtZV9mCiMgLSAoZm9yIGFsbCBub24gZHNfIHZhcmlhYmxlczopCm1vZGVsRnJhbWVfZltpcy5uYShtb2RlbEZyYW1lX2YpXSA8LSAwCiMgLSBwaWNrIGRpbWVuc2lvbiBvZiBTUEFSUUwgZmVhdHVyZXMgb25seToKdG9wX2YgPC0gZGltKG1vZGVsRnJhbWVfZilbMl0KIyAtIHJlY29tcG9zZSBtb2RlbEZyYW1lCm1vZGVsRnJhbWUgPC0gY2JpbmQobW9kZWxGcmFtZV9kcywgbW9kZWxGcmFtZV9mKQpybShtb2RlbEZyYW1lX2RzKTsgcm0obW9kZWxGcmFtZV9mKTsgZ2MoKQoKIyMjIC0tLSBQcmltYSBGYWNpZSBDb3JyZWxhdGlvbiBiZXR3ZWVuOgojIC0gbW9kZWxGcmFtZSRmX2RzX25fY29uY19xdWVyaWVzCiMgLSBtb2RlbEZyYW1lJGZfZHNfcXVlcnlfdGltZQpjb3JyRnJhbWUgPC0gbW9kZWxGcmFtZSAlPiUgCiAgZHBseXI6OnNlbGVjdChmX2RzX2RhdGFjZW50ZXJfaG9zdCwgCiAgICAgICAgICAgICAgICBmX2RzX25fY29uY19xdWVyaWVzLCAKICAgICAgICAgICAgICAgIGZfZHNfcXVlcnlfdGltZSkKY29yckZyYW1lIDwtIGNvcnJGcmFtZSAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KGZfZHNfZGF0YWNlbnRlcl9ob3N0KSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWRfbl9jb25jX3F1ZXJpZXMgPSBtZWRpYW4oZl9kc19uX2NvbmNfcXVlcmllcyksIAogICAgICAgICAgICAgICAgICAgbWVkX3F1ZXJ5X3RpbWUgPSBtZWRpYW4oZl9kc19xdWVyeV90aW1lKSkKY29yLnRlc3QoY29yckZyYW1lJG1lZF9uX2NvbmNfcXVlcmllcywgCiAgICAgICAgIGNvcnJGcmFtZSRtZWRfcXVlcnlfdGltZSwgCiAgICAgICAgIG1ldGhvZCA9ICJwZWFyc29uIikKY29yLnRlc3QoY29yckZyYW1lJG1lZF9uX2NvbmNfcXVlcmllcywgCiAgICAgICAgIGNvcnJGcmFtZSRtZWRfcXVlcnlfdGltZSwgCiAgICAgICAgIG1ldGhvZCA9ICJzcGVhcm1hbiIpCgojIyMgLS0tIFNlbGVjdCBvbmx5IE1MIGZlYXR1cmVzCiMgLSB0byBzcGxpdCB0cmFpbiBhbmQgdGVzdDogZl9kc190dFNwbGl0Cm1vZGVsRnJhbWUkZl9kc190dFNwbGl0IDwtIHJvdW5kKHJ1bmlmKGRpbShtb2RlbEZyYW1lKVsxXSwgMCwgMSkpCiMgLSByZW1vdmUgSURzIGFuZCBrZWVwIHRyYWNrIG9mIGZfZHNfdHRTcGxpdAptb2RlbEZyYW1lX0lEcyA8LSBtb2RlbEZyYW1lICU+JSAKICBkcGx5cjo6c2VsZWN0KGZfZHNfaWQsIAogICAgICAgICAgICAgICAgZl9kc191bmlxdWVTcGFycWxJZCwKICAgICAgICAgICAgICAgIGZfZHNfdHRTcGxpdCkKZmlsZW5hbWUgPC0gcGFzdGUwKCdtb2RlbEZyYW1lX0lEc18nLCB0b3BfZiwgIi5jc3YiKQp3cml0ZS5jc3YobW9kZWxGcmFtZV9JRHMsIAogICAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgZmlsZW5hbWUpKQpybShtb2RlbEZyYW1lX0lEcyk7IGdjKCkKbW9kZWxGcmFtZSA8LSBtb2RlbEZyYW1lICU+JSAKICBkcGx5cjo6c2VsZWN0KC1mX2RzX2lkLAogICAgICAgICAgICAgICAgLWZfZHNfdW5pcXVlU3BhcnFsSWQpCgojIyMgLS0tIFNlbGVjdCBjcml0ZXJpb24KIyMjIC0tLSBEcm9wIGFsbCBvdGhlciBwb3RlbnRpYWwgY3JpdGVyaWEKIyMjIC0tLSBQb3RlbnRpYWwgY3JpdGVyaWEgYXJlOgojIC0gZl9kc19xdWVyeV90aW1lX21lZGlhbgojIC0gZl9kc19xdWVyeV90aW1lCiMgLSBmX2RzX3F1ZXJ5X3RpbWVfbWlsZAojIC0gZl9kc19xdWVyeV90aW1lX2V4dHJlbWUKIyAtIGZfZHNfcXVlcnlfdGltZV9jbGFzcwojIC0gZl9kc19zdGF0dXNfY29kZQojIyMgLS0tIGNyaXRlcmlvbjogZl9kc19xdWVyeV90aW1lX21pbGQKdGFibGUobW9kZWxGcmFtZSRmX2RzX3F1ZXJ5X3RpbWVfbWlsZCkKbW9kZWxGcmFtZSA8LSBtb2RlbEZyYW1lICU+JSAKICBkcGx5cjo6c2VsZWN0KC1mX2RzX3F1ZXJ5X3RpbWUsIAogICAgICAgICAgICAgICAgLWZfZHNfcXVlcnlfdGltZV9tZWRpYW4sCiAgICAgICAgICAgICAgICAtZl9kc19xdWVyeV90aW1lX2V4dHJlbWUsIAogICAgICAgICAgICAgICAgLWZfZHNfcXVlcnlfdGltZV9jbGFzcywgCiAgICAgICAgICAgICAgICAtZl9kc19zdGF0dXNfY29kZSkKCiMjIyAtLS0gcHJlcGFyZSBYR0Jvb3N0IHN0cnVjdHVyZXMKIyAtIG1vZGVsRnJhbWUgYXMgZGVzaWduTWF0cml4Cm1vZGVsRnJhbWUkZl9kc19ob3VyIDwtIGFzLmNoYXJhY3Rlcihtb2RlbEZyYW1lJGZfZHNfaG91cikKbW9kZWxGcmFtZSA8LSBmYXN0RHVtbWllczo6ZHVtbXlfY29scyhtb2RlbEZyYW1lKQojIC0gcmVtb3ZlIGR1bW1pZmllZDoKbW9kZWxGcmFtZSRmX2RzX2hvdXIgPC0gTlVMTAptb2RlbEZyYW1lJGZfZHNfZGF5IDwtIE5VTEwKbW9kZWxGcmFtZSRmX2RzX2Zvcm1hdCA8LSBOVUxMCm1vZGVsRnJhbWUkZl9kc19tZXRob2QgPC0gTlVMTAptb2RlbEZyYW1lJGZfZHNfZGF0YWNlbnRlcl9ob3N0IDwtIE5VTEwKCiMgLSBzcGxpdDogdHJhaW4gdnMgdGVzdAp0cmFpbkZyYW1lIDwtIG1vZGVsRnJhbWVbbW9kZWxGcmFtZSRmX2RzX3R0U3BsaXQgPT0gMCwgXQp0ZXN0RnJhbWUgPC0gbW9kZWxGcmFtZVttb2RlbEZyYW1lJGZfZHNfdHRTcGxpdCA9PSAxLCBdCnJtKG1vZGVsRnJhbWUpOyBnYygpCgojIyMgLS0tIERvd25zYW1wbGluZwp0YWJsZSh0cmFpbkZyYW1lJGZfZHNfcXVlcnlfdGltZV9taWxkKQpwb3NpdGl2ZVNhbXBsZVNpemUgPSAgc3VtKHRyYWluRnJhbWUkZl9kc19xdWVyeV90aW1lX21pbGQgPT0gMSkKbmVnYXRpdmVTYW1wbGUgPC0gdHJhaW5GcmFtZVt0cmFpbkZyYW1lJGZfZHNfcXVlcnlfdGltZV9taWxkID09IDBdCmRpbShuZWdhdGl2ZVNhbXBsZSkKcG9zaXRpdmVTYW1wbGUgPC0gdHJhaW5GcmFtZVt0cmFpbkZyYW1lJGZfZHNfcXVlcnlfdGltZV9taWxkID09IDFdCmRpbShwb3NpdGl2ZVNhbXBsZSkKcm0odHJhaW5GcmFtZSkKbmVnYXRpdmVTYW1wbGVDaG9pY2UgPC0gc2FtcGxlKDE6ZGltKG5lZ2F0aXZlU2FtcGxlKVsxXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gcG9zaXRpdmVTYW1wbGVTaXplLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2UgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9iID0gTlVMTCkKbmVnYXRpdmVTYW1wbGUgPC0gbmVnYXRpdmVTYW1wbGVbbmVnYXRpdmVTYW1wbGVDaG9pY2UsIF0KZGltKG5lZ2F0aXZlU2FtcGxlKQp0cmFpbkZyYW1lIDwtIHJiaW5kKG5lZ2F0aXZlU2FtcGxlLCAKICAgICAgICAgICAgICAgICAgICBwb3NpdGl2ZVNhbXBsZSkKcm0ocG9zaXRpdmVTYW1wbGUpOyBybShuZWdhdGl2ZVNhbXBsZSk7IGdjKCkKCiMgLSBkZXNpZ24gbWF0cmljZXMgCnJlc3BvbnNlX3RpbWVfdHJhaW4gPC0gdHJhaW5GcmFtZSRmX2RzX3F1ZXJ5X3RpbWVfbWlsZAp0cmFpbkZyYW1lJGZfZHNfcXVlcnlfdGltZV9taWxkIDwtIE5VTEwKdHJhaW5GcmFtZSRmX2RzX3R0U3BsaXQgPC0gTlVMTAp0cmFpbkZyYW1lIDwtIGFzLm1hdHJpeCh0cmFpbkZyYW1lKQpyZXNwb25zZV90aW1lX3Rlc3QgPC0gdGVzdEZyYW1lJGZfZHNfcXVlcnlfdGltZV9taWxkCnRlc3RGcmFtZSRmX2RzX3F1ZXJ5X3RpbWVfbWlsZCA8LSBOVUxMCnRlc3RGcmFtZSRmX2RzX3R0U3BsaXQgPC0gTlVMTAp0ZXN0RnJhbWUgPC0gYXMubWF0cml4KHRlc3RGcmFtZSkKCiMgLSB0byB7eGdib29zdH0gRE1hdHJpeAp0cmFpbkZyYW1lIDwtIHhnYi5ETWF0cml4KHRyYWluRnJhbWUsIGxhYmVsID0gcmVzcG9uc2VfdGltZV90cmFpbikKcHJpbnQocGFzdGUwKCJEaW1lc2lvbiBvZiB0cmFpbkZyYW1lOiAiLCAKICAgICAgICAgICAgIHBhc3RlKGRpbSh0cmFpbkZyYW1lKSwgY29sbGFwc2UgPSAiLCAiLCBzZXAgPSAiIikpKQpwcmludCgidGVzdEZyYW1lIGFzIHhnYi5ETWF0cml4IG5vdy4iKQp0ZXN0RnJhbWUgPC0geGdiLkRNYXRyaXgodGVzdEZyYW1lLCBsYWJlbCA9IHJlc3BvbnNlX3RpbWVfdGVzdCkKcHJpbnQocGFzdGUwKCJEaW1lc2lvbiBvZiB0ZXN0RnJhbWU6ICIsIAogICAgICAgICAgICAgcGFzdGUoZGltKHRlc3RGcmFtZSksIGNvbGxhcHNlID0gIiwgIiwgc2VwID0gIiIpKSkKIyAtIHN0b3JlIHRlc3RGcmFtZSB0byBzYXZlIFJBTSBvbiBzdGF0MTAwKgp4Z2IuRE1hdHJpeC5zYXZlKHRlc3RGcmFtZSwgcGFzdGUwKGFuYWx5dGljc0RpciwgInRlbXBfdGVzdEZyYW1lLmRtYXRyaXgiKSkKcm0odGVzdEZyYW1lKTsgZ2MoKQoKIyMjIC0tLSB0cmFpbiBYR0Jvb3N0IG9uIHRyYWluIG1vZGVsIGRhdGFzZXQKcHJpbnQoIlByZXBhcmF0aW9uIGNvbXBsZXRlLiBNb2RlbCB3LiBYR0Jvb3N0IG5vdy4iKQpwcmludCgiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iKQpwcmludCgiU2V0IENWIHRyZWUgcGFyYW1ldGVyczoiKQojIyMgLS0tIGdyYWIgbW9kZWwgcGFyYW1zCmV0YSA8LSAuNQpnYW1tYSA8LSAxMAptYXhfZGVwdGggPC0gNgpzdWJzYW1wbGUgPC0gLjUKY29sc2FtcGxlX2J5dHJlZSA8LSAuNQpudGhyZWFkIDwtIDM2Cm5yb3VuZHMgPC0gMzAwMDAKdDEgPC0gU3lzLnRpbWUoKQojIyMgLS0tIEZJUkUhCnJlc19ib29zdCA8LSB4Z2IudHJhaW4oCiAgZGF0YSA9IHRyYWluRnJhbWUsCiAgd2F0Y2hsaXN0ID0gbGlzdCh2YWxpZGF0aW9uMSA9IHRyYWluRnJhbWUpLAogIHBhcmFtcyA9IGxpc3QoYm9vc3RlciA9ICJnYnRyZWUiLAogICAgICAgICAgICAgICAgbnRocmVhZCA9IG50aHJlYWQsCiAgICAgICAgICAgICAgICBldGEgPSBldGEsCiAgICAgICAgICAgICAgICBnYW1tYSA9IGdhbW1hLAogICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gbWF4X2RlcHRoLCAKICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IHN1YnNhbXBsZSwgCiAgICAgICAgICAgICAgICBjb2xzYW1wbGVfYnl0cmVlID0gY29sc2FtcGxlX2J5dHJlZSwKICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJiaW5hcnk6bG9naXN0aWMiKSwKICBucm91bmRzID0gbnJvdW5kcywKICB2ZXJib3NlID0gMSwKICBwcmludF9ldmVyeV9uID0gMTAwLAogIGV2YWxfbWV0cmljID0gImxvZ2xvc3MiLAogIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwMDAsCiAgbWF4aW1pemUgPSBOVUxMLAogIHNhdmVfcGVyaW9kID0gTlVMTCwKICBzYXZlX25hbWUgPSBOVUxMLAogIHhnYl9tb2RlbCA9IE5VTEwKKQpwcmludChwYXN0ZTAoIlRyYWluaW5nIGVuZHM6ICIsIFN5cy50aW1lKCkpKQp0cmFpbmluZ190aW1lIDwtIGRpZmZ0aW1lKFN5cy50aW1lKCksIHQxLCB1bml0cyA9ICJtaW5zIikKcHJpbnQocGFzdGUwKCJYR0Jvb3N0IENWIHRvb2s6ICIsIHRyYWluaW5nX3RpbWUpKQoKIyAtIGdyYWIgZmVhdHVyZSBpbXBvcnRhbmNlCmltcG9ydGFuY2VGcmFtZSA8LSB4Z2IuaW1wb3J0YW5jZShmZWF0dXJlX25hbWVzID0gY29sbmFtZXModHJhaW5GcmFtZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IHJlc19ib29zdCkKIyAtIHJlbW92ZSB0cmFpbkZyYW1lCnJtKHRyYWluRnJhbWUpOyBnYygpCgojIC0gcHJlZGljdGlvbnMgb24gdGhlIHRlc3RGcmFtZToKdGVzdEZyYW1lIDwtIHhnYi5ETWF0cml4KHBhc3RlMChhbmFseXRpY3NEaXIsICJ0ZW1wX3Rlc3RGcmFtZS5kbWF0cml4IikpCnRyYWluaW5nX3ByZWRpY3Rpb24gPC0gcHJlZGljdChyZXNfYm9vc3QsIHRlc3RGcmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZWxpbWl0ID0gcmVzX2Jvb3N0JGJlc3RfbnRyZWVsaW1pdCkKIyAtIHJlbW92ZSB0ZXN0RnJhbWUKcm0odGVzdEZyYW1lKTtnYygpCgojIC0gUk9DIEFuYWx5c2lzCmRfdCA8LSBzZXEoLjAxLCAuOTksIC4wMSkKcm9jRnJhbWUgPC0gZGF0YS5mcmFtZSh0aHJlc2hvbGQgPSBudW1lcmljKGxlbmd0aChkX3QpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgYWNjID0gbnVtZXJpYyhsZW5ndGgoZF90KSksCiAgICAgICAgICAgICAgICAgICAgICAgdHBfcmF0ZSA9IG51bWVyaWMobGVuZ3RoKGRfdCkpLAogICAgICAgICAgICAgICAgICAgICAgIGZuX3JhdGUgPSBudW1lcmljKGxlbmd0aChkX3QpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgZnBfcmF0ZSA9IG51bWVyaWMobGVuZ3RoKGRfdCkpLCAKICAgICAgICAgICAgICAgICAgICAgICB0bl9yYXRlID0gbnVtZXJpYyhsZW5ndGgoZF90KSkpCmZvciAoaSBpbiAxOmxlbmd0aChkX3QpKSB7CiAgZGVjX3RocmVzaG9sZCA8LSBkX3RbaV0KICBhY2MgPC0gbWVhbihhcy5udW1lcmljKHRyYWluaW5nX3ByZWRpY3Rpb24gPiBkZWNfdGhyZXNob2xkKSA9PSByZXNwb25zZV90aW1lX3Rlc3QpCiAgaGl0UmF0ZSA8LSBzdW0oKGFzLm51bWVyaWModHJhaW5pbmdfcHJlZGljdGlvbiA+IGRlY190aHJlc2hvbGQpID09IDEpICYgKHJlc3BvbnNlX3RpbWVfdGVzdCA9PSAxKSkKICBoaXRSYXRlIDwtIGhpdFJhdGUvc3VtKHJlc3BvbnNlX3RpbWVfdGVzdCA9PSAxKQogIG1pc3NSYXRlIDwtIHN1bSgoYXMubnVtZXJpYyh0cmFpbmluZ19wcmVkaWN0aW9uID4gZGVjX3RocmVzaG9sZCkgPT0gMCkgJiAocmVzcG9uc2VfdGltZV90ZXN0ID09IDEpKQogIG1pc3NSYXRlIDwtIG1pc3NSYXRlL3N1bShyZXNwb25zZV90aW1lX3Rlc3QgPT0gMSkKICBmYWxzZUFsYXJtUmF0ZSA8LSBzdW0oKGFzLm51bWVyaWModHJhaW5pbmdfcHJlZGljdGlvbiA+IGRlY190aHJlc2hvbGQpID09IDEpICYgKHJlc3BvbnNlX3RpbWVfdGVzdCA9PSAwKSkKICBmYWxzZUFsYXJtUmF0ZSA8LSBmYWxzZUFsYXJtUmF0ZS9zdW0ocmVzcG9uc2VfdGltZV90ZXN0ID09IDApCiAgY29ycmVjdFJlamVjdGlvblJhdGUgPC0gc3VtKChhcy5udW1lcmljKHRyYWluaW5nX3ByZWRpY3Rpb24gPiBkZWNfdGhyZXNob2xkKSA9PSAwKSAmIChyZXNwb25zZV90aW1lX3Rlc3QgPT0gMCkpCiAgY29ycmVjdFJlamVjdGlvblJhdGUgPC0gY29ycmVjdFJlamVjdGlvblJhdGUvc3VtKHJlc3BvbnNlX3RpbWVfdGVzdCA9PSAwKQogIHJvY0ZyYW1lW2ksIF0gPC0gYyhkZWNfdGhyZXNob2xkLCAKICAgICAgICAgICAgICAgICAgICAgcm91bmQoYWNjLCAzKSwgCiAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGhpdFJhdGUsIDMpLCAKICAgICAgICAgICAgICAgICAgICAgcm91bmQobWlzc1JhdGUsIDMpLCAKICAgICAgICAgICAgICAgICAgICAgcm91bmQoZmFsc2VBbGFybVJhdGUsIDMpLCAKICAgICAgICAgICAgICAgICAgICAgcm91bmQoY29ycmVjdFJlamVjdGlvblJhdGUsIDMpCiAgKQp9CnJvY0ZyYW1lJGRpZmZfaGl0X2ZhIDwtIHJvY0ZyYW1lJHRwX3JhdGUgLSByb2NGcmFtZSRmcF9yYXRlCndtYXhfZGlmZiA8LSB3aGljaC5tYXgocm9jRnJhbWUkZGlmZl9oaXRfZmEpCnJvY0ZyYW1lW3dtYXhfZGlmZiwgXQp3cml0ZS5jc3Yocm9jRnJhbWUsIAogICAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgIlJPQ19tb2RlbF9xdE1pbGRfZXRhMDAxXyIsIHRvcF9mLCAiLmNzdiIpKQoKIyAtIGZlYXR1cmUgaW1wb3J0YW5jZQptb2RlbEZyYW1lQ29kZUZpbGUgPC0gbGlzdC5maWxlcyhhbmFseXRpY3NEaXIpCm1vZGVsRnJhbWVDb2RlRmlsZSA8LSBtb2RlbEZyYW1lQ29kZUZpbGVbZ3JlcGwoIm1vZGVsRnJhbWVGZWF0dXJlQ29kZSIsIG1vZGVsRnJhbWVDb2RlRmlsZSldCm1vZGVsRnJhbWVDb2RlRmlsZSA8LSBtb2RlbEZyYW1lQ29kZUZpbGVbZ3JlcGwodG9wX2YsIG1vZGVsRnJhbWVDb2RlRmlsZSldCm1vZGVsRnJhbWVDb2RlIDwtIHJlYWQuY3N2KHBhc3RlMChhbmFseXRpY3NEaXIsIG1vZGVsRnJhbWVDb2RlRmlsZSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQppbXBvcnRhbmNlRnJhbWUgPC0gaW1wb3J0YW5jZUZyYW1lICU+JSAKICBkcGx5cjo6bGVmdF9qb2luKG1vZGVsRnJhbWVDb2RlLCAKICAgICAgICAgICAgICAgICAgIGJ5ID0gYygnRmVhdHVyZScgPSAneGdib29zdF9mZWF0dXJlJykpCnJtKG1vZGVsRnJhbWVDb2RlKQppbXBvcnRhbmNlRnJhbWUkZmVhdHVyZVt3aGljaChpcy5uYShpbXBvcnRhbmNlRnJhbWUkZmVhdHVyZSkpXSA8LSAKICBpbXBvcnRhbmNlRnJhbWUkRmVhdHVyZVt3aGljaChpcy5uYShpbXBvcnRhbmNlRnJhbWUkZmVhdHVyZSkpXQppbXBvcnRhbmNlRnJhbWUgPC0gaW1wb3J0YW5jZUZyYW1lWywgYyg1LCAxLCAyLCAzLCA0KV0KY29sbmFtZXMoaW1wb3J0YW5jZUZyYW1lKVsyXSA8LSAneGdib29zdF9mZWF0dXJlJwppbXBvcnRhbmNlRnJhbWUgPC0gZHBseXI6OmFycmFuZ2UoaW1wb3J0YW5jZUZyYW1lLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2MoR2FpbikpCndyaXRlLmNzdihpbXBvcnRhbmNlRnJhbWUsIAogICAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgImltcG9ydGFuY2VfcXRNaWxkX2V0YTAwMV8iLCB0b3BfZiwgIi5jc3YiKSkKYGBgCgpJbiBvcmRlciB0byBleHBsYWluIHRoZSB0eXBpY2FsIG1vZGVsbGluZyByZXN1bHQgd2l0aCBYR0Jvb3N0LCB3ZSBuZWVkIHRvIGludHJvZHVjZSBzb21lIHRlcm1pbm9sb2d5IGZpcnN0LgoKTGV0J3MgYXNzdW1lIGVhY2ggb2YgdGhlIFNQQVJRTCBxdWVyaWVzIGlzIGNvZGVkIGFzIGAxYCBpZiB0aGUgcmVzcGVjdGl2ZSBXRFFTIHJlc3BvbnNlIHRpbWUgZm9yIHRoYXQgcXVlcnkgd2FzIGNyaXRpY2FsbHkgaGlnaCBvciBgMGAgaWYgaXQgaGFkIGZhbGxlbiBpbiBhIHJhbmdlIG9mIHR5cGljYWwgcHJvY2Vzc2luZyB0aW1lcy4gRm9yIGEgc2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIHRoYW4gd2UgaGF2ZSB0d28gYmluYXJ5IHZlY3RvcnMgb3ZlciB7MCwgMX0gZGVzY3JpYmluZzogKDEpIHRoZSBvYnNlcnZlZCBzdGF0dXMgb2YgdGhlIHF1ZXJ5IChhZ2FpbjogMSAtIGNyaXRpY2FsLCAwIC0gdHlwaWNhbCksIGFuZCAoMikgdGhlIHByZWRpY3RlZCBzdGF0dXMgb2YgdGhlIHF1ZXJ5OiB3aGF0IHRoZSB0cmFpbmVkIHByZWRpY3RpdmUgbW9kZWwgInRoaW5rcyIgdGhlIHF1ZXJ5IHdvdWxkIGJlIGJ5IGxvb2tpbmcgYXQgdGhlIGZlYXR1cmVzIHRoYXQgZGVzY3JpYmUgaXQuCgpGb3IgZXhhbXBsZToKCmBgYHtyIGVjaG8gPSBUfQpzYW1wbGVGcmFtZSA8LSBkYXRhLmZyYW1lKHF1ZXJ5ID0gcGFzdGUwKCJRdWVyeV8iLCAxOjEwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBvYnNlcnZlZCA9IHJvdW5kKHJ1bmlmKDEwLCAwLCAxKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdGVkID0gcm91bmQocnVuaWYoMTAsIDAsIDEpKSkKc2FtcGxlRnJhbWUKYGBgCgpGb3IgZWFjaCBvZiB0aGUgMTAgcXVlcmllcyBpbiB0aGUgdGFibGUgd2Uga25vdyBpZiB0aGV5IHJlYWxseSB3aGVyZSBjcml0aWNhbCAoMSkgb3Igbm90ICgwKSBhbmQgd2Ugc2VlIHRoYXQgZnJvbSB0aGUgYG9ic2VydmVkYCBjb2x1bW4sIGFuZCBpZiB0aGUgbW9kZWwgcHJlZGljdHMgdGhleSB3b3VsZCBiZSBjcml0aWNhbCAoMSkgb3Igbm90ICgwKSBmcm9tIHRoZWlyIGZlYXR1cmVzOiB0aGUgYHByZWRpY3RlZGAgY29sdW1uLiBGb3IgZXhhbXBsZSwgaW4gdGhlIGZpcnN0IHJvdyB3ZSBoYXZlIGEgcXVlcnkgdGhhdCB3YXMgY3JpdGljYWwgaW5kZWVkIGFuZCBjb3JyZWN0bHkgcHJlZGljdGVkIGFzIGNyaXRpY2FsOiB0aGF0IGlzIHNvbWV0aGluZyB3ZSBjYWxsIGEgSGl0IChvciBhIFRydWUgUG9zaXRpdmUpIGluIE1MLiBJbiB0aGUgc2Vjb25kIHJvdyB3ZSBvYnNlcnZlIGEgRmFsc2UgQWxhcm0gKG9yIGEgRmFsc2UgUG9zaXRpdmUpOiB0aGUgcXVlcnkgd2FzIG5vdCByZWFsbHkgY3JpdGljYWwgKGl0IGhhcyAwIG9uIGBvYnNlcnZlZGApIGJ1dCB0aGUgbW9kZWwgaGFzIGluY29ycmVjdGx5IGNhdGVnb3JpemVkIGl0IGFzIGlmIGl0IHdvdWxkIGJlICgxIG9uIGBwcmVkaWN0ZWRgKS4gSW4gdGhlIGVpZ3RoIHJvdyB3ZSBmaW5kIGEgc2l0dWF0aW9uIGtub3duIGFzIGEgQ29ycmVjdCBSZWplY3Rpb246IHRoZSBxdWVyeSB3YXMgbm90IGNyaXRpY2FsICgwIG9uIGBvYnNlcnZlZGApIGFuZCB0aGUgbW9kZWwgZmlndXJlZCBvdXQgaXQgd291bGRuJ3QgYmUgY3JpdGljYWwgaW5kZWVkIGJ5IGxvb2tpbmcgaW50byBpdHMgZmVhdHVyZXMgKDAgb24gYHByZWRpY3RlZGApLiBGaW5hbGx5LCB0aGUgZm91cnRoIGltcG9ydGFudCBzaXR1YXRpb24gY2FuIGJlIGZvdW5kIGluIHRoZSBzaXh0aCByb3csIHdoZXJlIHRoZSBxdWVyeSB3YXMgY3JpdGljYWwgKDEgb24gYG9ic2VydmVkYCkgYnV0IG5vdCByZWNvZ25pemVkIGFzIHN1Y2ggYnkgdGhlIG1vZGVsICgwIG9uIGBwcmVkaWN0ZWRgKSwgYSBjYXNlIGtub3duIGFzIE1pc3MgaW4gdGhlIE1MIHRlcm1pbm9sb2d5LiBNb3JlIG9uIHRoaXMgdGVybWlub2xvZ3k6IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NvbmZ1c2lvbl9tYXRyaXguCgpJbiB0aGUgdGFibGUgYWJvdmUsIHdlIGNhbiBjb21wdXRlIHRoZSBtb2RlbCBhY2N1cmFjeSBieSBsb29raW5nIGF0IGhvdyBtYW55IHRpbWVzIGRvIHRoZSBgb2JzZXJ2ZWAgYW5kIGBwcmVkaWN0ZWRgIGNvbHVtbiBjb2luY2lkZToKCioqVGFibGUgMS4qKiBDb25mdXNpb24gbWF0cml4IGV4YW1wbGUuCgpgYGB7ciBlY2hvID0gVH0KYWNjIDwtIHN1bShzYW1wbGVGcmFtZSRvYnNlcnZlZCA9PSBzYW1wbGVGcmFtZSRwcmVkaWN0ZWQpL2RpbShzYW1wbGVGcmFtZSlbMV0KcHJpbnQocGFzdGUwKCJNb2RlbCBhY2N1cmFjeSA9ICIsIHJvdW5kKGFjYywgMiksICIuIikpCmBgYAoKV2hlbiB3ZSBsb29rIGF0IHRoZSBIaXQgcmF0ZSB3ZSBzZWUgdGhhdDoKCmBgYHtyIGVjaG8gPSBUfQpoaXRzIDwtIHN1bShzYW1wbGVGcmFtZSRvYnNlcnZlZCA9PSAxICYgc2FtcGxlRnJhbWUkcHJlZGljdGVkID09IDEpL3N1bShzYW1wbGVGcmFtZSRvYnNlcnZlZCA9PSAxKQpwcmludChwYXN0ZTAoIkhpdCByYXRlID0gIiwgcm91bmQoaGl0cywgMiksICIuIikpCmBgYAoKRmluYWxseSwgdGhlIEZhbHNlIGFsYXJtIHJhdGUgb2YgdGhlIG1vZGVsIGlzOgoKYGBge3IgZWNobyA9IFR9CkZBcyA8LSBzdW0oc2FtcGxlRnJhbWUkb2JzZXJ2ZWQgPT0gMCAmIHNhbXBsZUZyYW1lJHByZWRpY3RlZCA9PSAxKS9zdW0oc2FtcGxlRnJhbWUkb2JzZXJ2ZWQgPT0gMSkKcHJpbnQocGFzdGUwKCJGQSByYXRlID0gIiwgcm91bmQoRkFzLCAyKSwgIi4iKSkKYGBgCgpTbywgdGhlIG1vZGVsIHdvdWxkIHJlY29nbml6ZSBhIGNyaXRpY2FsIHF1ZXJ5IGNvcnJlY3RseSA3MSUgb2YgdGltZSwgd2hpbGUgMzAlIG9mIHRpbWUgaXQgd291bGQgaW5jb3JyZWN0bHkgcHJlZGljdCBhIG5vbi1jcml0aWNhbCBxdWVyeSB0byBiZSBjcml0aWNhbC4gSW1hZ2luZSBpZiB3ZSB1c2UgdGhpcyBtb2RlbCB0byBjaG9zZSB3aGVuIHRvIHN0YXJ0IGFuZCB3aGVuIG5vdCB0byBzdGFydCBzb21lIG9wdGltaXphdGlvbiBwcm9jZWR1cmUgaW4gV0RRUyAtIGJlIGl0IGFzIHNpbXBsZSBhcyBkaXZlcmdpbmcgZGlmZmVyZW50IHF1ZXJpZXMgKGNyaXRpY2FsIHZzLiBub24tY3JpdGljYWwpIHRvIHNlcGFyYXRlIHByb2Nlc3NpbmcgcmVzb3VyY2VzLiBGcm9tIHRoZSB2aWV3cG9pbnQgb2YgYW55IHN5c3RlbSBvcHRpbWl6YXRpb24sIHRoZSBkZWNpc2lvbiBpcyBuZWVkZWQ6IGNhbiB3ZSBsaXZlIHdpdGggdGhlc2UgdHdvIHZhbHVlcywgb3Igbm90PyBUaGUgRmFsc2UgQWxhcm0gc3VnZ2VzdHMgdGhhdCAzMCUgb2YgdGltZSB0aGUgbW9kZWwgd291bGQgc3VnZ2VzdCB0aGUgb3B0aW1pemF0aW9uIHJvdXRpbmUgdG8gc3RhcnQgaW4gYSBzaXR1YXRpb24gd2hlbiBpdCBzaG91bGQgbm90IHN0YXJ0IGFuZCB0aHVzIHNwYXJlIHZhbHVhYmxlIHN5c3RlbSByZXNvdXJjZXMuIEFnYWluLCAxMDAlIC0gNzElIChIaXQgcmF0ZSkgPSAyOSUgb2YgdGltZSB0aGUgbW9kZWwgd291bGQgbWlzcyB0byB0cmlnZ2VyIHRoZSBvcHRpbWl6YXRpb24gcm91dGluZSBmb3IgYSBjcml0aWNhbCBxdWVyeSB0aGF0IGNvdWxkIGVuZ2FnZSB0b28gbWFueSByZXNvdXJjZXMuCgpBIGdvb2QgcHJlZGljdGl2ZSBtb2RlbCBpcyB0aGUgb25lIHdoaWNoIGF0dGFpbnMgKGEpIGEgaGlnaCBIaXQgcmF0ZSAoaS5lLiBvYnNlcnZlZCBjcml0aWNhbCBhbmQgcHJlZGljdGVkIGNyaXRpY2FsKSwgYW5kIChiKSBhIGxvdyBGYWxzZSBBbGFybSByYXRlIChpLmUuIG9ic2VydmVkIG5vbi1jcml0aWNhbCBidXQgcHJlZGljdGVkIGNyaXRpY2FsKS4gSW4gb3RoZXIgd29yZHMsIHdlIGRvIG5vdCB3YW50IG91ciBtb2RlbCB0byBtaXNzIGNyaXRpY2FsIHF1ZXJpZXMgYW5kIHRodXMgbm90IHRyaWdnZXIgc29tZSBvcHRpbWl6YXRpb24gcHJvY2VkdXJlIHRvIGhlbHAgV0RRUyBiZXR0ZXIgdXNlIGl0cyByZXNvdXJjZXMsIGJ1dCB3ZSBhbHNvIGRvIG5vdCB3YW50IG91ciBtb2RlbCB0byB0cmlnZ2VyIGFueSBvcHRpbWl6YXRpb24gcHJvY2VkdXJlcyBvbiBhIHF1ZXJ5IHRoYXQgd291bGQgbm90IHJlc3VsdCBpbiBhIGNyaXRpY2FsbHkgaGlnaCB1c2FnZSBvZiByZXNvdXJjZXMuCgoqKk91ciBjdXJyZW50IHJlc3VsdHMqKjoKCi0gdGhlIHJlc3VsdHMgdmFyeSB3aXRoIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgaW5pdGlhbGx5IHBhc3NlZCBpbnRvIG9wdGltaXphdGlvbiBhbmQgKG9mIGNvdXJzZSkgdGhlIHNwZWNpZmljIHZhbHVlcyBvZiBtb2RlbCBwYXJhbWV0ZXJzLCBidXQgdGhleSBkbyBub3QgdmFyeSBzaWduaWZpY2FudGx5OgotIG1vZGVsICoqYWNjdXJhY3kqKiBpcyBhcm91bmQgKio4NSUqKjsKLSB0eXBpY2FsICoqSGl0IHJhdGUqKiAob3IgVHJ1ZSBQb3NpdGl2ZSBSYXRlKSBpcyBhcm91bmQgKio3MiUqKiwgYW5kCi0gdHlwaWNhbCAqKkZhbHNlIGFsYXJtIHJhdGUqKiAob3IgRmFsc2UgUG9zaXRpdmUgUmF0ZSkgaXMgYWJvdXQgKioxMyUqKi4KCk5vdyB3ZSBjYW4gc2VlIGhvdyBtaXNsZWFkaW5nIGlzIG1vZGVsIGFjY3VyYWN5IGFsb25lLiBJbiBlZmZlY3QsIHRoZXNlIHJlc3VsdHMgaW1wbHkgdGhlIGZvbGxvd2luZzoKCi0gaW1hZ2luZSwgYWdhaW4sIHRoYXQgd2UgZGV2ZWxvcCBhIHN5c3RlbSB0byBvcHRpbWl6ZSB0aGUgV0RRUyBieSBzb21laG93IHNwbGl0dGluZyBpdHMgcmVzb3VyY2VzIGludG8gdGhvc2UgdGhhdCBwcm9jZXNzIHF1ZXJpZXMgd2l0aCBleHBlY3RlZCB0eXBpY2FsIHByb2Nlc3NpbmcgdGltZXMgZnJvbSB0aG9zZSB3aXRoIGV4cGVjdGVkIGNyaXRpY2FsIHByb2Nlc3NpbmcgdGltZXMgKGUuZy4gd2UgaGF2ZSB0d28gZGF0YSBjZW50ZXJzLCBgZXFpYWRgIGFuZCBgY29kZWZ3YCwgb2Ygd2hpY2ggdGhlIGxhdGVyIHNlZW1zIHRvIGJlIGJhcmVseSB1c2VkIHRvIHN1cHBvcnQgdGhlIFdEUVM7IHdlIGNhbiB0aGluayBhYm91dCB0aGUgcHJvYmxlbSBhcyBpZiB3ZSB3ZXJlIHBvc2luZyBhIHF1ZXN0aW9uIGlmIGEgcXVlcnkgaXMgdG9vIGhlYXZ5IHRvIGJlIHByb2Nlc3NlZCBpbiB0aGUgb3ZlcmxvYWRlZCBgZXFpYWRgIGFuZCB0aHVzIHNob3VsZCBiZSBzZW50IHRvIGBjb2RlZndgLCBvciBub3QpOwotIG91ciBjdXJyZW50IG1vZGVsbGluZyBlZmZvcnRzIGxldCB1cyBrbm93IHRoYXQgd2UgY2FuIGdldCB0aGUgbnVtYmVyIG9mIFRydWUgUG9zaXRpdmVzIHRvIGJlIGFib3V0IDcyJSBhdCB0aGUgZXhwZW5zZSBvZiBpbmNvcnJlY3RseSBjYXRlZ29yaXppbmcgYXBwcm94LiAxMyUgb2YgdHlwaWNhbCBxdWVyaWVzIGFzIGlmIHRoZXkgd2VyZSBjcml0aWNhbDsKLSB0aGF0IG1lYW5zIHRoZSBzeXN0ZW0gd291bGQgY29ycmVjdGx5IHJ1biA3MiUgb2YgdGhlIGhlYXZ5IHF1ZXJpZXMgb24gY3JpdGljYWwgcHJvY2Vzc2luZyByZXNvdXJjZXNzLCB3aGlsZSBpdCB3b3VsZCBlcnJvbmVvdXNseSBydW4gMTMlIG9mIHRoZSBxdWVyaWVzIG9uIGNyaXRpY2FsIHByb2Nlc3NpbmcgcmVzb3VyY2VzIChpLmUuIDEzJSBvZiBxdWVyaWVzIHRoYXQgc2hvdWxkIGJlIHByb2Nlc3NlZCB3aXRoIHRoZSByZXNvdXJjZXMgZm9yIHR5cGljYWwgcXVlcmllcyB3b3VsZCBpbmNvcnJlY3RseSB0cmlnZ2VyIHRoZSBjcml0aWNhbCBwcm9jZXNzaW5nIHJlc291cmNlcyk7Ci0gb3IsIHRvIHJlcGhyYXNlOiB0aGUgbW9kZWwgd291bGQgdGhpbmsgYSBjcml0aWNhbCBxdWVyeSBpcyBub24tY3JpdGljYWwgMjglIG9mIGEgdGltZSwgYW5kIGRlY2lkZSB0byBwYXNzIHN1Y2ggYSBxdWVyeSB0byBhIG5vcm1hbCwgdHlwaWNhbCBXRFFTIHByb2Nlc3NpbmcsIHdoaWxlIGF0IHRoZSBzYW1lIHRpbWUgaXQgd291bGQgcmVjb2duaXplIGEgbm9uLWNyaXRpY2FsIHF1ZXJ5IGFzIG5vbi1jcml0aWNhbCA4NyUgb2YgdGltZSBhbmQgdGhhbiBpdCB3b3VsZCBub3QgdHJpZ2dlciBhbnkgb3B0aW1pemF0aW9uIGFjdGlvbi4gCgojIyA0LiBTZWxlY3RlZCBmZWF0dXJlcwoKIyMjIDQuMSBUaGUgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZXMgZm9yIGNhdGVnb3JpemF0aW9uOiB0eXBpY2FsIHZzLiBleHRyZW1lIHByb2Nlc3NpbmcgdGltZQoKVGhlIFhHQm9vc3QgbW9kZWwgaGFzIHVzZWQgMzEyIGZlYXR1cmVzIHRvIG1vZGVsIGBxdWVyeV90aW1lYC4gCioqTk9URS4qKiBTb21lIG9mIHRoZSBpbml0aWFsIGZlYXR1cmVzLCBsaWtlIGBkYXkgb2Ygd2Vla2AsIGFjdHVhbGx5IGJlY29tZSBzZXZlcmFsIGZlYXR1cmVzIGluIHRoZSBwcm9jZXNzIG9mIFtkdW1teSBjb2RpbmddKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NhdGVnb3JpY2FsX3ZhcmlhYmxlI0R1bW15X2NvZGluZykgKGUuZy4gZm9yIGBkYXkgb2Ygd2Vla2AgaXQgd291bGQgYmU6IGBNb25kYXlgLCBgVHVlc2RheWAsIGBXZWRuZXNkYXlgLCBldGMsIGVhY2ggYmVpbmcgYSBiaW5hcnkgYDAvMWAgZmVhdHVyZSwgZS5nLiBgMWAgZm9yIGBNb25kYXlgIGlmIGl0IHdhcyBNb25kYXkgYW5kIGAwYCBvdGhlcndpc2UpLgoKVGhlIGZvbGxvd2luZyB0YWJsZSBwcmVzZW50cyBhbGwgdGhlIGZlYXR1cmVzIHVzZWQgdG8gcHJlZGljdCBpZiB0aGUgcXVlcnkgd2lsbCByZWFjaCBhIGNyaXRpY2FsIG9yIHR5cGljYWwgcHJvY2Vzc2luZyB0aW1lLiBUaGUgdGFibGUgaXMgc29ydGVkIGJ5ICpHYWluKmAqIHdoaWNoIGlzIHRoZSBtZWFzdXJlIG9mIGZlYXR1cmUncyBpbXBvcnRhbmNlIGluIHRoZSBjYXRlZ29yaXphdGlvbiBvZiBxdWVyaWVzLiBSZW1lbWJlciB0aGF0IGBkc19uX2NvbmNfcXVlcmllc2AgaXMgdGhlIG51bWJlciBvZiBxdWVyaWVzIGNvbmN1cnJlbnRseSBzdGFydGVkIGluIHRoZSBzYW1lIG1pbnV0ZSwgKmluIHRoZSBzYW1lIGRhdGFjZW50ZXIgYW5kIG9uIHRoZSBzYW1lIGJhY2stZW5kIGhvc3QqLgoKKipGZWF0dXJlIEltcG9ydGFuY2UgVGFibGUqKgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEVCkKZmVhdHVyZUZyYW1lIDwtIHJlYWQuY3N2KCdpbXBvcnRhbmNlX3F0TWlsZF9ldGEwMDFfMzAwLmNzdicsIAogICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZmVhdHVyZUZyYW1lIDwtIGZlYXR1cmVGcmFtZSAlPiUgCiAgZHBseXI6OnNlbGVjdChmZWF0dXJlLCBHYWluKQpmZWF0dXJlRnJhbWUkR2FpbiA8LSByb3VuZChmZWF0dXJlRnJhbWUkR2FpbiwgNSkKRFQ6OmRhdGF0YWJsZShmZWF0dXJlRnJhbWUsIAogICAgICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KAogICAgICAgICAgICAgICAgICAgICAgICAgICBwYWdpbmcgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICBwYWdlTGVuZ3RoID0gIDUwIAogICAgICAgICAgICAgICAgICAgICAgICAgICApKQpgYGAKCkp1c3QgZnJvbSBhIGdsaW1wc2UgYXQgdGhlIGZlYXR1cmUgaW1wb3J0YW5jZSB0YWJsZSB3ZSBjYW4gc2VlIHRoYXQgaXQgaXMgaW1wb3J0YW50OgoKLSAqd2hlbiogdGhlIHF1ZXJ5IHdhcyBydW4gKG1hbnkgZmVhdHVyZXMgb2YgdGhlIGZvcm0gYGZfZHNfZGF5XypgLCByZXByZXNlbnRpbmcgZGF5IG9yIHdlZWssIGFuZCBgZl9kc19ob3VyXypgLCByZXByZXNlbnRpbmcgaG91ciBvZiBkYXksIGFyZSBzZWxlY3RlZCBhcyBpbXBvcnRhbnQ7IGFsc28sIHRoZSBhYm92ZSBtZW50aW9uZWQgYGRzX25fY29uY19xdWVyaWVzYCB3aGljaCBpcyBhIHByb3h5IG1lYXN1cmUgb2YgYSBkYXRhY2VudGVyL2hvc3QgbG9hZCBpbiBhIGdpdmVuIG1pbnV0ZSkKLSB0aGUgb3ZlcmFsbCBjb21wbGV4aXR5IG9mIHRoZSBxdWVyeSBtZWFzdXJlZCBieSBgbmNoYXJgICh0aGUgbGVuZ3RoIG9mIHRoZSBxdWVyeSBpbiBjaGFyYWN0ZXJzKSBhbmQgYF9fdmFyc191c2FnZV9fYCAodGhlIHRvdGFsIG51bWJlciBvZiB0b2tlbnMgcmVwcmVzZW50aW5nIHZhcmlhYmxlcyBmb3VuZCBpbiB0aGUgU1BBUlFMIGNvZGUpOwotICp3aGVyZSogdGhlIHF1ZXJ5IHdhcyBydW4sIGUuZy4gYGZfZHNfZGF0YWNlbnRlcl9ob3N0X2VxaWFkX3dkcXMxMDA1YCwgYGZfZHNfZGF0YWNlbnRlcl9ob3N0X2VxaWFkX3dkcXMxMDA2YCwgYGZfZHNfZGF0YWNlbnRlcl9ob3N0X2VxaWFkX3dkcXMxMDA0YCwgZl9kc19kYXRhY2VudGVyX2hvc3RfZXFpYWRfd2RxczEwMDdgLCBmX2RzX2RhdGFjZW50ZXJfaG9zdF9jb2Rmd193ZHFzMjAwMmAsIGV0YzsKLSB0aGUgdXNhZ2Ugb2YgbGFiZWxzOiB0aGUgYF9fdmFyc19sYWJlbF91c2FnZV9fYCAtIGEgY291bnQgb2YgdG9rZW5zIHJlcHJlc2VudGluZyBsYWJlbHMgZm9yIGEgcGFydGljdWxhciB2YXJpYWJsZSwgZS5nLiBgP2F1dGhvckxhYmVsYCBhbmQgc2ltaWxhcjsKLSBwcmFnbWF0aWNzOiBpZiBhIHF1ZXJ5IGlzIGFib3V0IG1vdmllcywgZS5nLiB0aGUgYD9pbWRiaWRgIGZlYXR1cmUgKGV4Y2VwdCBmb3IgaWYgV0RRUyB1c2VycyBhcmUgcHVsbGluZyBvdXIgbGVnIGJ5IHVzaW5nIGEgdmFyaWFibGUgbmFtZWQgYD9pbWRiaWRgIHdoaWxlIGFjdHVhbGx5IHJldHJpZXZpbmcgZGF0YSBvbiBjYXRzIC0gdGhlIHJpc2tzIG9mIGFuYWx5emluZyB0aGUgcHJhZ21hdGljcyBvZiBhIGxhbmd1YWdlKTsKLSB0aGUgInN0cnVjdHVyYWwgZGVwdGgiIG9mIHRoZSBxdWVyeTogdGhlIGB7YCBmZWF0dXJlLCBob3cgbWFueSBvcGVuIGN1cmx5IGJyYWNlcyBkbyB3ZSBmaW5kIGluIGEgcXVlcnkgKGFuZCBrbm93aW5nIHRoYXQgZWFjaCBtdXN0IGJlIG1hdGNoZWQgYnkgdGhlIHJlc3BlY3RpdmUgYH1gIHN5bWJvbCB3ZSBoYXZlIGVsaW1pbmF0ZWQgdGhlIGxhdGVyIGVhcmx5IGluIHRoZSBmZWF0dXJlIGVuZ2luZWVyaW5nIHBoYXNlKTsKLSBldGMuCgojIyMgNC4yIFRoZSBkaXJlY3Rpb24gb2YgaW5mbHVlbmNlIG9mIHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlcwoKRm9sbG93aW5nIHRoZSBtb2RlbCBhbmQgdmFyaWFibGUgc2VsZWN0aW9uLCB3ZSBoYXZlIGVzdGltYXRlZCBhIFtCaW5vbWlhbCBMb2dpc3RpYyBSZWdyZXNzaW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Mb2dpc3RpY19yZWdyZXNzaW9uKSBtb2RlbCB3aXRoIHRoZSBzZWxlY3RlZCBmZWF0dXJlcyBhbmQgb24gdGhlIHNhbWUgZGF0YXNldC4gV2UgaGF2ZSB1c2VkIHRoZSBSaWRnZSByZWdyZXNzaW9uIGFwcHJvYWNoIHRvIHJlZ3VsYXJpemF0aW9uLiBUaGUgb2J0YWluZWQgY29lZmZpY2llbnRzIGFyZSBsaW5lYXI6CgotIFRoZSBmZWF0dXJlICpHYWluKiBpbiB0aGUgWEdCb29zdCBkZWNpc2lvbiB0cmVlIG1vZGVsIHJlcHJlc2VudHMgdGhlIGluZm9ybWF0aXZlbmVzcyBvZiBhIHBhcnRpY3VsYXIgZmVhdHVyZSBpbiBhIHNlcXVlbmNlIG9mIGRlY2lzaW9uIHRyZWVzIHRoYXQgcGVyZm9ybSBiaW5hcnkgY2xhc3NpZmljYXRpb24gKGV4dHJlbWUgdnMuIHR5cGljYWwgcXVlcnkgcHJvY2Vzc2luZyB0aW1lcyk7IGhvd2V2ZXIsIAotIHRoZSBzaXplIGFuZCB0aGUgc2lnbiAocG9zaXRpdmUgb3IgbmVnYXRpdmUpIG9mIGEgZmVhdHVyZSBsaW5lYXIgY29lZmZpY2llbnQgb2J0YWluZWQgZnJvbSB0aGUgQmluYXJ5IExvZ2lzdGljIFJlZ3Jlc3Npb24gbW9kZWwgcmVwcmVzZW50IGhvdyBtdWNoIGFuZCBpbiB3aGF0IGRpcmVjdGlvbiwgcmVzcGVjdGl2ZWx5LCBkb2VzIHRoZSBwcmVzZW5jZSBvZiB0aGUgZmVhdHVyZSBpbiBhIHF1ZXJ5IGluZmx1ZW5jZSBpZiB0aGUgcXVlcnkgd2lsbCBiZSBwcm9jZXNzZWQgaW4gZXh0cmVtZSBvciB0eXBpY2FsIHRpbWUuIAoKVGhlIGZvbGxvd2luZyBjaGFydHMgc2hvdyB0aGUgbGluZWFyIGNvZWZmaWNpZW50cyBvZiB0aGUgMjAgZmVhdHVyZXMgd2l0aCBtb3N0IHBvc2l0aXZlIGFuZCBtb3N0IG5lZ2F0aXZlIGNvZWZmaWNpZW50czoKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgbWVzc2FnZSA9IEYsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gOH0KcEZyYW1lIDwtIHJlYWQuY3N2KCdSZWdyZXNzaW9uX0NvZWZmc18zMDAuY3N2JywgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKIyAtIGxvZy1vZGRzIHRvIG9kZHMKcEZyYW1lJFdlaWdodCA8LSBleHAocEZyYW1lJFdlaWdodCkKcEZyYW1lX3RvcCA8LSBoZWFkKHBGcmFtZSwgMzApCnBGcmFtZV9ib3R0b20gPC0gdGFpbChwRnJhbWUsIDMwKQpwRnJhbWUgPC0gcmJpbmQocEZyYW1lX3RvcCwgcEZyYW1lX2JvdHRvbSkKcEZyYW1lJHBvc19uZWcgPC0gYyhyZXAoJ3Bvc2l0aXZlJywgMzApLCByZXAoJ25lZ2F0aXZlJywgMzApKQpwRnJhbWUkeGdib29zdF9mZWF0dXJlIDwtIE5VTEwKcEZyYW1lJEZlYXR1cmUgPC0gZmFjdG9yKHBGcmFtZSRGZWF0dXJlLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHBGcmFtZSRGZWF0dXJlW29yZGVyKHBGcmFtZSRXZWlnaHQpXSkKZ2dwbG90KGRhdGEgPSBwRnJhbWUsIAogICAgICAgYWVzKHggPSBGZWF0dXJlLAogICAgICAgICAgIHkgPSBXZWlnaHQpKSArIAogIGdlb21fbGluZShncm91cCA9IDEpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMi41KSArIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gIndoaXRlIikgKwogIGZhY2V0X3dyYXAofnBvc19uZWcsIHNjYWxlcz0nZnJlZV94JykgKyAKICB0aGVtZV9idygpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGFuZ2xlID0gOTAsIGhqdXN0PTAuOTUsIHZqdXN0PTAuMikpCmBgYAoKVGhlIGZvbGxvd2luZyB0YWJsZSBwcmVzZW50cyBhbGwgbW9kZWwgZmVhdHVyZXMgYWxvbmdzaWRlIHRoZWlyIHJlc3BlY3RpdmUgbGluZWFyIGNvZWZmaWNpZW50cwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCBtZXNzYWdlID0gRn0KcEZyYW1lJHBvc19uZWcgPC0gTlVMTApwRnJhbWUkV2VpZ2h0IDwtIHJvdW5kKHBGcmFtZSRXZWlnaHQsIDIpCkRUOjpkYXRhdGFibGUocEZyYW1lLCAKICAgICAgICAgICAgICBvcHRpb25zID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFnaW5nID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFnZUxlbmd0aCA9ICA1MCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkKYGBgCgojIyA1LiBEaXJlY3Rpb25zIGZvciBmdXR1cmUgd29yawoKLSBUaGUgKipzYW1wbGluZyBhcHByb2FjaCoqIGFwcGVhcnMgdG8gYmUgcm9idXN0OwotIFRoZSAqKmZlYXR1cmUgZW5naW5lZXJpbmcqKiBwcm9jZWR1cmVzIGFyZSBhcHByb3hpbWF0ZTogdGhlcmUgaXMgcGxhY2UgZm9yIGZ1cnRoZXIgaW1wcm92ZW1lbnQgaW4gdGhhdCByZXNwZWN0OwotIFRoZSAqKk1MKiogcmVzdWx0cyB3ZXJlIGRpc2N1c3NlZCBhbmQgaXQgaXMgdW5jZXJ0YWluIGlmIGZ1cnRoZXIgc2lnbmlmaWNhbnQgaW1wcm92ZW1lbnRzIGFyZSBwb3NzaWJsZSwgYnV0IGF0IGxlYXN0IHRoZSBmb2xsb3dpbmcgc2hvdWxkIGJlIGNvbnNpZGVyZWQ6CiAgICAtIG1heWJlIGEgZGlmZmVyZW50IG1vZGVsIGFsdG9nZXRoZXIgKGUuZy4gQ29udm9sdXRpb25hbCBEZWVwIExlYXJuaW5nKSBzaG91bGQgYmUgdGVzdGVkLgoKKioqCkdvcmFuIFMuIE1pbG92YW5vdmnEhwoKV2lraW1lZGlhIERldXRzY2hsYW5kLCBEYXRhIFNjaWVudGlzdApEYXRhS29sZWt0aXYsIE93bmVyCgoyMDIwLgoKY29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZQoKIVtdKF9pbWcvREtfTG9nb18xMDAucG5nKQohW10oX2ltZy9XaWtpZGF0YS1sb2dvLWVuLnBuZykKIVtdKF9pbWcvV2lraW1lZGlhX0RldXRzY2hsYW5kX0xvZ29fc21hbGwucG5nKQoKCgoKCg==