This notebook is licensed under the MIT License. If you use the code or data visualization designs contained within this notebook, it would be greatly appreciated if proper attribution is given back to this notebook and/or myself. Thanks! :)
Setup
Setup the R packages.
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
Registering fonts with R
Attaching package: ‘scales’
The following objects are masked from ‘package:readr’:
col_factor, col_numeric
library(viridis)
library(plotly)
Attaching package: ‘plotly’
The following object is masked _by_ ‘.GlobalEnv’:
subplot
The following object is masked from ‘package:ggplot2’:
last_plot
The following object is masked from ‘package:stats’:
filter
The following object is masked from ‘package:graphics’:
layout
library(bigrquery)
library(htmlwidgets)
sessionInfo()
R version 3.3.0 (2016-05-03)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X 10.12.1 (unknown)
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
attached base packages:
[1] grid stats graphics grDevices utils datasets methods base
other attached packages:
[1] htmlwidgets_0.7 bigrquery_0.3.0 plotly_4.5.2 viridis_0.3.4
[5] stringr_1.1.0 digest_0.6.10 RColorBrewer_1.1-2 scales_0.4.0
[9] extrafont_0.17 ggplot2_2.1.0 dplyr_0.5.0 readr_1.0.0
loaded via a namespace (and not attached):
[1] Rcpp_0.12.7 formatR_1.4 plyr_1.8.4 base64enc_0.1-3
[5] tools_3.3.0 jsonlite_1.1 evaluate_0.10 tibble_1.2
[9] gtable_0.2.0 viridisLite_0.1.3 DBI_0.5-1 yaml_2.1.13
[13] gridExtra_2.2.1 Rttf2pt1_1.3.4 httr_1.2.1 knitr_1.14
[17] R6_2.2.0 rmarkdown_1.1 purrr_0.2.2 tidyr_0.6.0
[21] extrafontdb_1.0 magrittr_1.5 htmltools_0.3.5 assertthat_0.1
[25] colorspace_1.2-7 stringi_1.1.2 lazyeval_0.2.0 munsell_0.4.3
This project uses data from BigQuery. To get the data for the first charts, run this query:
SELECT created_rank, score_rank,
COUNT(*) as num_comments
FROM
(
SELECT
subreddit,
ROW_NUMBER() OVER (PARTITION BY link_id ORDER BY score DESC) AS score_rank,
ROW_NUMBER() OVER (PARTITION BY link_id ORDER BY created_utc ASC) AS created_rank,
COUNT(*) OVER (PARTITION BY link_id) AS num_toplevelcomments_in_thread
FROM [fh-bigquery:reddit_comments.all_starting_201501]
WHERE link_id = parent_id
)
WHERE score_rank <= 100 AND created_rank <= 100 AND num_toplevelcomments_in_thread >= 30
GROUP BY created_rank, score_rank
ORDER BY created_rank, score_rank
ROW_NUMBER()
must be used instead of RANK()
to avoid biasing ties in score.
COUNT() OVER (PARTITION BY link_id)
returns the number of toplevel comments in the thread where that comment is located; the outerlevel filter then filters the output on that.
WHERE link_id = parent_id
corresponds to top-level comments
This outputs a 10,000 row file.
Exploratory Analysis
df <- read_csv("reddit_012015_all.csv")
Parsed with column specification:
cols(
created_rank = col_integer(),
score_rank = col_integer(),
num_comments = col_integer()
)
The total number of comments analyzed is n = 86,561,476.
Plot a basic 2x2 heat map. A log10()
filter is likely necessary to compress the values.
plot <- ggplot(df, aes(x=created_rank, y=score_rank, fill=log10(num_comments))) +
geom_raster(interpolate = TRUE) +
fte_theme() +
scale_fill_viridis()
max_save(plot, "reddit-first", "Reddit")
Optimize axes/parameters and add a contour to visualize groupings. (use non-log filter for posterity)
plot <- ggplot(df, aes(x=created_rank, y=score_rank, fill=num_comments, z=num_comments)) +
geom_raster(interpolate = TRUE) +
geom_contour(color = "white", alpha = 0.5, bins = 5) +
scale_x_continuous(breaks = c(1,seq(10,100,by=10))) +
scale_y_continuous(breaks = c(1,seq(10,100,by=10))) +
fte_theme() +
theme(legend.title = element_text(size=7, family="Open Sans Condensed Bold"), legend.position="top", legend.direction="horizontal", legend.key.width=unit(1.25, "cm"), legend.key.height=unit(0.25, "cm"), legend.margin=unit(0,"cm"), panel.margin=element_blank()) +
scale_fill_viridis(name="# of\nComments", labels=comma, breaks=pretty_breaks(6)) +
labs(title = "Heatmap between Time Comment Made on Reddit and Score Rank",
x = "# Top-Level Comment (Lower Number is Posted Earlier)",
y = "Comment Score Ranking (Lower Number is Higher Score)")
max_save(plot, "reddit-first-2", "Reddit (Jan 2015 - Sep 2016)")
Remake the plot using a log10
scale.
plot <- ggplot(df, aes(x=created_rank, y=score_rank, fill=num_comments, z=log10(num_comments))) +
geom_raster(interpolate = TRUE) +
geom_contour(color = "white", alpha = 0.5, bins = 5) +
scale_x_continuous(breaks = c(1,seq(10,100,by=10))) +
scale_y_continuous(breaks = c(1,seq(10,100,by=10))) +
fte_theme() +
theme(legend.title = element_text(size=7, family="Open Sans Condensed Bold"), legend.position="top", legend.direction="horizontal", legend.key.width=unit(1.25, "cm"), legend.key.height=unit(0.25, "cm"), legend.margin=unit(0,"cm"), panel.margin=element_blank()) +
scale_fill_viridis(name="# of\nComments", labels=comma, breaks=10^(1:5), trans="log10") +
labs(title = "Heatmap between Time Comment Made on Reddit and Score Rank",
x = "# Top-Level Comment (Lower Number is Posted Earlier)",
y = "Comment Score Ranking (Lower Number is Higher Score)")
max_save(plot, "reddit-first-2a", "Reddit (Jan 2015 - Sep 2016)")
Filter on only the first comments to create 1D slice of the proportions.
df_first_comment <- df %>% filter(created_rank == 1) %>%
mutate(norm = num_comments/sum(num_comments))
df_first_comment %>% select(score_rank, norm) %>% head()
The aggregate accounts for n = 1,636,298 first comments.
The total proportion of the top 5 ranks is 0.46, and the top 10 ranks is 0.62.
plot <- ggplot(df_first_comment, aes(x=score_rank, y=norm)) +
geom_bar(stat = "identity", fill = "#2980b9") +
scale_x_continuous(breaks = c(1,seq(10,100,by=10))) +
scale_y_continuous(labels = percent, breaks=pretty_breaks(6)) +
fte_theme() +
theme(plot.title = element_text(size=6)) +
labs(title = "What Percentage of the 1st Comments in Reddit Threads Were Also the Top-Voted Comment?",
x = "Score Rank of First Comment in Thread (Lower Number is Higher Score)",
y = "Proportion of all First Comments at Score Rank")
max_save(plot, "reddit-first-3", "Reddit (Jan 2015 - Sep 2016)")
Invert the dataframe to arrange by Top Comment (this should be symmetric, per the 2D map).
df_top_comment <- df %>% filter(score_rank == 1) %>%
mutate(norm = num_comments/sum(num_comments))
df_top_comment %>% head()
The aggregate accounts for n = 1,679,007 first comments.
The total proportion of the top 5 ranks is 0.56, and the top 10 ranks is 0.77.
plot <- ggplot(df_top_comment, aes(x=created_rank, y=norm)) +
geom_bar(stat = "identity", fill = "#c0392b") +
scale_x_continuous(breaks = c(1,seq(10,100,by=10))) +
scale_y_continuous(labels = percent, breaks=pretty_breaks(6)) +
fte_theme() +
theme(plot.title = element_text(size=6)) +
labs(title = "What Percentage of the Top-Voted Comments in Reddit Threads Were Also the 1st Comment?",
x = "# Comment Which Resulted in Top-Voted Comment (Lower Number is Posted Earlier)",
y = "Proportion of all Top-Voted Comments at Comment Posting #")
max_save(plot, "reddit-first-4", "Reddit (Jan 2015 - Sep 2016)")
Analyze by Subreddits
Get Subreddit Data from BigQuery
Create same charts as above for Top 100 Subreddits by unique active commenters. Must use R to get data since too many rows returned.
project_id <- <FILL IN> # DO NOT SHARE!
This requires a subquery tweak on the first query to filter on top 100 subreddits, and include in output:
SELECT subreddit, created_rank, score_rank,
COUNT(*) as num_comments
FROM
(
SELECT
subreddit,
ROW_NUMBER() OVER (PARTITION BY link_id ORDER BY score DESC) AS score_rank,
ROW_NUMBER() OVER (PARTITION BY link_id ORDER BY created_utc ASC) AS created_rank,
COUNT(*) OVER (PARTITION BY link_id) AS num_toplevelcomments_in_thread
FROM [fh-bigquery:reddit_comments.all_starting_201501]
WHERE link_id = parent_id AND subreddit IN
(SELECT subreddit FROM (SELECT subreddit, COUNT(DISTINCT author) as unique_commenters,
FROM [fh-bigquery:reddit_comments.all_starting_201501]
GROUP BY subreddit
ORDER BY unique_commenters DESC
LIMIT 100)
)
)
WHERE score_rank <= 100 AND created_rank <= 100 AND num_toplevelcomments_in_thread >= 30
GROUP BY subreddit, created_rank, score_rank
ORDER BY subreddit, created_rank, score_rank
Query with R and save output for later.
query <- "SELECT subreddit, created_rank, score_rank,
COUNT(*) as num_comments
FROM
(
SELECT
subreddit,
ROW_NUMBER() OVER (PARTITION BY link_id ORDER BY score DESC) AS score_rank,
ROW_NUMBER() OVER (PARTITION BY link_id ORDER BY created_utc ASC) AS created_rank,
COUNT(*) OVER (PARTITION BY link_id) AS num_toplevelcomments_in_thread
FROM [fh-bigquery:reddit_comments.all_starting_201501]
WHERE link_id = parent_id AND subreddit IN
(SELECT subreddit FROM (SELECT subreddit, COUNT(DISTINCT author) as unique_commenters,
FROM [fh-bigquery:reddit_comments.all_starting_201501]
GROUP BY subreddit
ORDER BY unique_commenters DESC
LIMIT 100)
)
)
WHERE score_rank <= 100 AND created_rank <= 100 AND num_toplevelcomments_in_thread >= 30
GROUP BY subreddit, created_rank, score_rank
ORDER BY subreddit, created_rank, score_rank"
df_subreddits <- tbl_df(query_exec(query, project=project_id, max_pages=Inf))
df_subreddits %>% head()
write.csv(df_subreddits, "reddit_012015_by_subreddit.csv", row.names=F)
Read from cached output.
df_subreddits <- read_csv("reddit_012015_by_subreddit.csv")
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_integer(),
subreddit = col_character(),
created_rank = col_integer(),
score_rank = col_integer(),
num_comments = col_integer()
)
The returned file has 933,517 rows and takes 21.4 Mb of memory in R.
Remake the df_first_comment
data frame with the subreddit data as well:
df_first_comment_subreddit <- df_subreddits %>% filter(created_rank == 1) %>%
group_by(subreddit) %>%
mutate(norm = num_comments/sum(num_comments))
df_first_comment_subreddit %>% head()
Visualize Subreddit Data
Create the 1D Map and the 2D Map for each subreddit using variants code above. First, extract a list of the top 100 subreddits used above.
subreddits <- df_subreddits %>% select(subreddit) %>% unique() %>% unlist()
subreddits %>% head()
subreddit1 subreddit2 subreddit3 subreddit4 subreddit5
"4chan" "AdviceAnimals" "Android" "Art" "AskMen"
subreddit6
"AskReddit"
Create a wrapper function, which given a subreddit, produces the chart for that subreddit. (assumes img-1d
and img-2d
directories exist)
1D Charts for Top 100 Subreddits
subreddit_1d <- function(p_subreddit) {
plot <- ggplot(df_first_comment_subreddit %>% filter(subreddit == p_subreddit), aes(x=score_rank, y=norm)) +
geom_bar(stat = "identity") +
scale_x_continuous(breaks = c(1,seq(10,100,by=10))) +
scale_y_continuous(labels = percent, breaks=pretty_breaks(6)) +
fte_theme() +
labs(title = sprintf("Distribution of First Comment Rankings for /r/%s", p_subreddit),
x = "Score Rank of First Comment in Thread (Lower Number is Higher Score)",
y = "Proportion of all First Comments at Score Rank")
max_save(plot, sprintf("img-1d/%s-1d", p_subreddit), "Reddit (Jan 2015 - Sep 2016)")
}
Run for each of the 100 subreddits.
temp <- lapply(subreddits, subreddit_1d)
2D Charts for Top 100 Subreddits
subreddit_2d <- function(p_subreddit) {
plot <- ggplot(df_subreddits %>% filter(subreddit == p_subreddit), aes(x=created_rank, y=score_rank, fill=num_comments, z=log10(num_comments))) +
geom_raster(interpolate = FALSE) +
geom_contour(color = "white", alpha = 0.5, bins = 3, size = 0.25) +
scale_x_continuous(breaks = c(1,seq(10,100,by=10))) +
scale_y_continuous(breaks = c(1,seq(10,100,by=10))) +
fte_theme() +
theme(plot.title = element_text(size=7)) +
theme(legend.title = element_text(size=7, family="Open Sans Condensed Bold"), legend.position="top", legend.direction="horizontal", legend.key.width=unit(1.25, "cm"), legend.key.height=unit(0.25, "cm"), legend.margin=unit(0,"cm"), panel.margin=element_blank()) +
scale_fill_viridis(name="# of\nComments", labels=comma, breaks=10^(0:4), trans="log10") +
labs(title = sprintf("Heatmap between Time Comment Made and Score Rank for /r/%s", p_subreddit),
x = "# Top-Level Comment (Lower Number is Posted Earlier)",
y = "Comment Score Ranking (Lower Number is Higher Score)")
max_save(plot, sprintf("img-2d/%s-2d", p_subreddit), "Reddit (Jan 2015 - Sep 2016)")
}
temp <- lapply(subreddits, subreddit_2d)
LICENSE
The MIT License (MIT)
Copyright (c) 2016 Max Woolf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LS0tCnRpdGxlOiAiV2hhdCBQZXJjZW50IG9mIHRoZSBUb3AtVm90ZWQgQ29tbWVudHMgaW4gUmVkZGl0IFRocmVhZHMgV2VyZSBBbHNvIDFzdCBDb21tZW50PyIKYXV0aG9yOiAiTWF4IFdvb2xmIChAbWluaW1heGlyKSIKZGF0ZTogIk5vdmVtYmVyIDd0aCwgMjAxNiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBtYXRoamF4OiBudWxsCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IHNwYWNlbGFiCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgpUaGlzIFIgTm90ZWJvb2sgaXMgdGhlIGNvbXBsZW1lbnQgdG8gbXkgYmxvZyBwb3N0IFtXaGF0IFBlcmNlbnQgb2YgdGhlIFRvcC1Wb3RlZCBDb21tZW50cyBpbiBSZWRkaXQgVGhyZWFkcyBXZXJlIEFsc28gMXN0IENvbW1lbnQ/XShodHRwOi8vbWluaW1heGlyLmNvbS8yMDE2LzExL2ZpcnN0LWNvbW1lbnQvKS4KClRoaXMgbm90ZWJvb2sgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBJZiB5b3UgdXNlIHRoZSBjb2RlIG9yIGRhdGEgdmlzdWFsaXphdGlvbiBkZXNpZ25zIGNvbnRhaW5lZCB3aXRoaW4gdGhpcyBub3RlYm9vaywgaXQgd291bGQgYmUgZ3JlYXRseSBhcHByZWNpYXRlZCBpZiBwcm9wZXIgYXR0cmlidXRpb24gaXMgZ2l2ZW4gYmFjayB0byB0aGlzIG5vdGVib29rIGFuZC9vciBteXNlbGYuIFRoYW5rcyEgOikKCiMgU2V0dXAKClNldHVwIHRoZSBSIHBhY2thZ2VzLgoKYGBge3J9CnNvdXJjZSgiUnN0YXJ0LlIiKQoKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShiaWdycXVlcnkpCmxpYnJhcnkoaHRtbHdpZGdldHMpCgpzZXNzaW9uSW5mbygpCmBgYAoKVGhpcyBwcm9qZWN0IHVzZXMgZGF0YSBmcm9tIFtCaWdRdWVyeV0oaHR0cHM6Ly9jbG91ZC5nb29nbGUuY29tL2JpZ3F1ZXJ5LykuIFRvIGdldCB0aGUgZGF0YSBmb3IgdGhlIGZpcnN0IGNoYXJ0cywgcnVuIHRoaXMgcXVlcnk6CgpgYGBzcWwKU0VMRUNUIGNyZWF0ZWRfcmFuaywgc2NvcmVfcmFuaywKQ09VTlQoKikgYXMgbnVtX2NvbW1lbnRzCkZST00KKApTRUxFQ1QgCnN1YnJlZGRpdCwKUk9XX05VTUJFUigpIE9WRVIgKFBBUlRJVElPTiBCWSBsaW5rX2lkIE9SREVSIEJZIHNjb3JlIERFU0MpIEFTIHNjb3JlX3JhbmssIApST1dfTlVNQkVSKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGxpbmtfaWQgT1JERVIgQlkgY3JlYXRlZF91dGMgQVNDKSBBUyBjcmVhdGVkX3JhbmssCkNPVU5UKCopIE9WRVIgKFBBUlRJVElPTiBCWSBsaW5rX2lkKSBBUyBudW1fdG9wbGV2ZWxjb21tZW50c19pbl90aHJlYWQKRlJPTSBbZmgtYmlncXVlcnk6cmVkZGl0X2NvbW1lbnRzLmFsbF9zdGFydGluZ18yMDE1MDFdCldIRVJFIGxpbmtfaWQgPSBwYXJlbnRfaWQKKQpXSEVSRSBzY29yZV9yYW5rIDw9IDEwMCBBTkQgY3JlYXRlZF9yYW5rIDw9IDEwMCBBTkQgbnVtX3RvcGxldmVsY29tbWVudHNfaW5fdGhyZWFkID49IDMwCkdST1VQIEJZIGNyZWF0ZWRfcmFuaywgc2NvcmVfcmFuawpPUkRFUiBCWSBjcmVhdGVkX3JhbmssIHNjb3JlX3JhbmsKYGBgCgoqIGBST1dfTlVNQkVSKClgIG11c3QgYmUgdXNlZCBpbnN0ZWFkIG9mIGBSQU5LKClgIHRvIGF2b2lkIGJpYXNpbmcgdGllcyBpbiBzY29yZS4KKiBgQ09VTlQoKSBPVkVSIChQQVJUSVRJT04gQlkgbGlua19pZClgIHJldHVybnMgdGhlIG51bWJlciBvZiB0b3BsZXZlbCBjb21tZW50cyBpbiB0aGUgdGhyZWFkIHdoZXJlIHRoYXQgY29tbWVudCBpcyBsb2NhdGVkOyB0aGUgb3V0ZXJsZXZlbCBmaWx0ZXIgdGhlbiBmaWx0ZXJzIHRoZSBvdXRwdXQgb24gdGhhdC4KKiBgV0hFUkUgbGlua19pZCA9IHBhcmVudF9pZGAgY29ycmVzcG9uZHMgdG8gdG9wLWxldmVsIGNvbW1lbnRzCgpUaGlzIG91dHB1dHMgYSAxMCwwMDAgcm93IGZpbGUuCgojIEV4cGxvcmF0b3J5IEFuYWx5c2lzCgpgYGB7cn0KZGYgPC0gcmVhZF9jc3YoInJlZGRpdF8wMTIwMTVfYWxsLmNzdiIpCgpkZiAlPiUgaGVhZCgpCmBgYAoKVGhlIHRvdGFsIG51bWJlciBvZiBjb21tZW50cyBhbmFseXplZCBpcyAqKm4gPSBgciBkZiAlPiUgc2VsZWN0KG51bV9jb21tZW50cykgJT4lIHN1bSgpICU+JSBmb3JtYXQoYmlnLm1hcms9IiwiKWAqKi4KClBsb3QgYSBiYXNpYyAyeDIgaGVhdCBtYXAuIEEgYGxvZzEwKClgIGZpbHRlciBpcyBsaWtlbHkgbmVjZXNzYXJ5IHRvIGNvbXByZXNzIHRoZSB2YWx1ZXMuCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGYsIGFlcyh4PWNyZWF0ZWRfcmFuaywgeT1zY29yZV9yYW5rLCBmaWxsPWxvZzEwKG51bV9jb21tZW50cykpKSArCiAgICAgICAgICAgIGdlb21fcmFzdGVyKGludGVycG9sYXRlID0gVFJVRSkgKwogICAgICAgICAgICBmdGVfdGhlbWUoKSArCiAgICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpcygpCgptYXhfc2F2ZShwbG90LCAicmVkZGl0LWZpcnN0IiwgIlJlZGRpdCIpCmBgYAoKIVtdKHJlZGRpdC1maXJzdC5wbmcpCgpPcHRpbWl6ZSBheGVzL3BhcmFtZXRlcnMgYW5kIGFkZCBhIGNvbnRvdXIgdG8gdmlzdWFsaXplIGdyb3VwaW5ncy4gKHVzZSBub24tbG9nIGZpbHRlciBmb3IgcG9zdGVyaXR5KQoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmLCBhZXMoeD1jcmVhdGVkX3JhbmssIHk9c2NvcmVfcmFuaywgZmlsbD1udW1fY29tbWVudHMsIHo9bnVtX2NvbW1lbnRzKSkgKwogICAgICAgICAgICBnZW9tX3Jhc3RlcihpbnRlcnBvbGF0ZSA9IFRSVUUpICsKICAgICAgICAgICAgZ2VvbV9jb250b3VyKGNvbG9yID0gIndoaXRlIiwgYWxwaGEgPSAwLjUsIGJpbnMgPSA1KSArCiAgICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDEsc2VxKDEwLDEwMCxieT0xMCkpKSArCiAgICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKDEsc2VxKDEwLDEwMCxieT0xMCkpKSArCiAgICAgICAgICAgIGZ0ZV90aGVtZSgpICsKICAgICAgICAgICAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9NywgZmFtaWx5PSJPcGVuIFNhbnMgQ29uZGVuc2VkIEJvbGQiKSwgbGVnZW5kLnBvc2l0aW9uPSJ0b3AiLCBsZWdlbmQuZGlyZWN0aW9uPSJob3Jpem9udGFsIiwgbGVnZW5kLmtleS53aWR0aD11bml0KDEuMjUsICJjbSIpLCBsZWdlbmQua2V5LmhlaWdodD11bml0KDAuMjUsICJjbSIpLCBsZWdlbmQubWFyZ2luPXVuaXQoMCwiY20iKSwgcGFuZWwubWFyZ2luPWVsZW1lbnRfYmxhbmsoKSkgKwogICAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZT0iIyBvZlxuQ29tbWVudHMiLCBsYWJlbHM9Y29tbWEsIGJyZWFrcz1wcmV0dHlfYnJlYWtzKDYpKSArCiAgICAgICAgICAgIGxhYnModGl0bGUgPSAiSGVhdG1hcCBiZXR3ZWVuIFRpbWUgQ29tbWVudCBNYWRlIG9uIFJlZGRpdCBhbmQgU2NvcmUgUmFuayIsCiAgICAgICAgICAgICAgICAgeCA9ICIjIFRvcC1MZXZlbCBDb21tZW50IChMb3dlciBOdW1iZXIgaXMgUG9zdGVkIEVhcmxpZXIpIiwKICAgICAgICAgICAgICAgICB5ID0gIkNvbW1lbnQgU2NvcmUgUmFua2luZyAoTG93ZXIgTnVtYmVyIGlzIEhpZ2hlciBTY29yZSkiKQoKbWF4X3NhdmUocGxvdCwgInJlZGRpdC1maXJzdC0yIiwgIlJlZGRpdCAoSmFuIDIwMTUgLSBTZXAgMjAxNikiKQpgYGAKCiFbXShyZWRkaXQtZmlyc3QtMi5wbmcpCgpSZW1ha2UgdGhlIHBsb3QgdXNpbmcgYSBgbG9nMTBgIHNjYWxlLgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmLCBhZXMoeD1jcmVhdGVkX3JhbmssIHk9c2NvcmVfcmFuaywgZmlsbD1udW1fY29tbWVudHMsIHo9bG9nMTAobnVtX2NvbW1lbnRzKSkpICsKICAgICAgICAgICAgZ2VvbV9yYXN0ZXIoaW50ZXJwb2xhdGUgPSBUUlVFKSArCiAgICAgICAgICAgIGdlb21fY29udG91cihjb2xvciA9ICJ3aGl0ZSIsIGFscGhhID0gMC41LCBiaW5zID0gNSkgKwogICAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxLHNlcSgxMCwxMDAsYnk9MTApKSkgKwogICAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxLHNlcSgxMCwxMDAsYnk9MTApKSkgKwogICAgICAgICAgICBmdGVfdGhlbWUoKSArCiAgICAgICAgICAgIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTcsIGZhbWlseT0iT3BlbiBTYW5zIENvbmRlbnNlZCBCb2xkIiksIGxlZ2VuZC5wb3NpdGlvbj0idG9wIiwgbGVnZW5kLmRpcmVjdGlvbj0iaG9yaXpvbnRhbCIsIGxlZ2VuZC5rZXkud2lkdGg9dW5pdCgxLjI1LCAiY20iKSwgbGVnZW5kLmtleS5oZWlnaHQ9dW5pdCgwLjI1LCAiY20iKSwgbGVnZW5kLm1hcmdpbj11bml0KDAsImNtIiksIHBhbmVsLm1hcmdpbj1lbGVtZW50X2JsYW5rKCkpICsKICAgICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWU9IiMgb2ZcbkNvbW1lbnRzIiwgbGFiZWxzPWNvbW1hLCBicmVha3M9MTBeKDE6NSksIHRyYW5zPSJsb2cxMCIpICsKICAgICAgICAgICAgbGFicyh0aXRsZSA9ICJIZWF0bWFwIGJldHdlZW4gVGltZSBDb21tZW50IE1hZGUgb24gUmVkZGl0IGFuZCBTY29yZSBSYW5rIiwKICAgICAgICAgICAgICAgICB4ID0gIiMgVG9wLUxldmVsIENvbW1lbnQgKExvd2VyIE51bWJlciBpcyBQb3N0ZWQgRWFybGllcikiLAogICAgICAgICAgICAgICAgIHkgPSAiQ29tbWVudCBTY29yZSBSYW5raW5nIChMb3dlciBOdW1iZXIgaXMgSGlnaGVyIFNjb3JlKSIpCgptYXhfc2F2ZShwbG90LCAicmVkZGl0LWZpcnN0LTJhIiwgIlJlZGRpdCAoSmFuIDIwMTUgLSBTZXAgMjAxNikiKQpgYGAKCiFbXShyZWRkaXQtZmlyc3QtMmEucG5nKQoKRmlsdGVyIG9uIG9ubHkgdGhlIGZpcnN0IGNvbW1lbnRzIHRvIGNyZWF0ZSAxRCBzbGljZSBvZiB0aGUgcHJvcG9ydGlvbnMuCgpgYGB7cn0KZGZfZmlyc3RfY29tbWVudCA8LSBkZiAlPiUgZmlsdGVyKGNyZWF0ZWRfcmFuayA9PSAxKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG5vcm0gPSBudW1fY29tbWVudHMvc3VtKG51bV9jb21tZW50cykpCgpkZl9maXJzdF9jb21tZW50ICU+JSBzZWxlY3Qoc2NvcmVfcmFuaywgbm9ybSkgJT4lIGhlYWQoKQpgYGAKClRoZSBhZ2dyZWdhdGUgYWNjb3VudHMgZm9yICpuID0gYHIgZGZfZmlyc3RfY29tbWVudCAlPiUgc2VsZWN0KG51bV9jb21tZW50cykgJT4lIHN1bSgpICU+JSBmb3JtYXQoYmlnLm1hcmsgPSAiLCIpYCogZmlyc3QgY29tbWVudHMuCgpUaGUgdG90YWwgcHJvcG9ydGlvbiBvZiB0aGUgdG9wIDUgcmFua3MgaXMgYHIgZGZfZmlyc3RfY29tbWVudCAlPiUgc2VsZWN0KG5vcm0pICU+JSBoZWFkKDUpICU+JSBzdW0oKSAlPiUgcm91bmQoMilgLCBhbmQgdGhlIHRvcCAxMCByYW5rcyBpcyBgciBkZl9maXJzdF9jb21tZW50ICU+JSBzZWxlY3Qobm9ybSkgJT4lIGhlYWQoMTApICU+JSBzdW0oKSAlPiUgcm91bmQoMilgLgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX2ZpcnN0X2NvbW1lbnQsIGFlcyh4PXNjb3JlX3JhbmssIHk9bm9ybSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAiIzI5ODBiOSIpICsKICAgICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMSxzZXEoMTAsMTAwLGJ5PTEwKSkpICsKICAgICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQsIGJyZWFrcz1wcmV0dHlfYnJlYWtzKDYpKSArCiAgICAgICAgICAgIGZ0ZV90aGVtZSgpICsKICAgICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTYpKSArCiAgICAgICAgICAgIGxhYnModGl0bGUgPSAiV2hhdCBQZXJjZW50YWdlIG9mIHRoZSAxc3QgQ29tbWVudHMgaW4gUmVkZGl0IFRocmVhZHMgV2VyZSBBbHNvIHRoZSBUb3AtVm90ZWQgQ29tbWVudD8iLAogICAgICAgICAgICAgICAgIHggPSAiU2NvcmUgUmFuayBvZiBGaXJzdCBDb21tZW50IGluIFRocmVhZCAoTG93ZXIgTnVtYmVyIGlzIEhpZ2hlciBTY29yZSkiLAogICAgICAgICAgICAgICAgIHkgPSAiUHJvcG9ydGlvbiBvZiBhbGwgRmlyc3QgQ29tbWVudHMgYXQgU2NvcmUgUmFuayIpCgptYXhfc2F2ZShwbG90LCAicmVkZGl0LWZpcnN0LTMiLCAiUmVkZGl0IChKYW4gMjAxNSAtIFNlcCAyMDE2KSIpCmBgYAohW10ocmVkZGl0LWZpcnN0LTMucG5nKQoKSW52ZXJ0IHRoZSBkYXRhZnJhbWUgdG8gYXJyYW5nZSBieSBUb3AgQ29tbWVudCAodGhpcyBzaG91bGQgYmUgc3ltbWV0cmljLCBwZXIgdGhlIDJEIG1hcCkuCgpgYGB7cn0KZGZfdG9wX2NvbW1lbnQgPC0gZGYgJT4lIGZpbHRlcihzY29yZV9yYW5rID09IDEpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUobm9ybSA9IG51bV9jb21tZW50cy9zdW0obnVtX2NvbW1lbnRzKSkKCmRmX3RvcF9jb21tZW50ICU+JSBoZWFkKCkKYGBgCgpUaGUgYWdncmVnYXRlIGFjY291bnRzIGZvciAqbiA9IGByIGRmX3RvcF9jb21tZW50ICU+JSBzZWxlY3QobnVtX2NvbW1lbnRzKSAlPiUgc3VtKCkgJT4lIGZvcm1hdChiaWcubWFyayA9ICIsIilgKiBmaXJzdCBjb21tZW50cy4KClRoZSB0b3RhbCBwcm9wb3J0aW9uIG9mIHRoZSB0b3AgNSByYW5rcyBpcyBgciBkZl90b3BfY29tbWVudCAlPiUgc2VsZWN0KG5vcm0pICU+JSBoZWFkKDUpICU+JSBzdW0oKSAlPiUgcm91bmQoMilgLCBhbmQgdGhlIHRvcCAxMCByYW5rcyBpcyBgciBkZl90b3BfY29tbWVudCAlPiUgc2VsZWN0KG5vcm0pICU+JSBoZWFkKDEwKSAlPiUgc3VtKCkgJT4lIHJvdW5kKDIpYC4KCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl90b3BfY29tbWVudCwgYWVzKHg9Y3JlYXRlZF9yYW5rLCB5PW5vcm0pKSArCiAgICAgICAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gIiNjMDM5MmIiKSArCiAgICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDEsc2VxKDEwLDEwMCxieT0xMCkpKSArCiAgICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50LCBicmVha3M9cHJldHR5X2JyZWFrcyg2KSkgKwogICAgICAgICAgICBmdGVfdGhlbWUoKSArCiAgICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT02KSkgKwogICAgICAgICAgICBsYWJzKHRpdGxlID0gIldoYXQgUGVyY2VudGFnZSBvZiB0aGUgVG9wLVZvdGVkIENvbW1lbnRzIGluIFJlZGRpdCBUaHJlYWRzIFdlcmUgQWxzbyB0aGUgMXN0IENvbW1lbnQ/IiwKICAgICAgICAgICAgICAgICB4ID0gIiMgQ29tbWVudCBXaGljaCBSZXN1bHRlZCBpbiBUb3AtVm90ZWQgQ29tbWVudCAoTG93ZXIgTnVtYmVyIGlzIFBvc3RlZCBFYXJsaWVyKSIsCiAgICAgICAgICAgICAgICAgeSA9ICJQcm9wb3J0aW9uIG9mIGFsbCBUb3AtVm90ZWQgQ29tbWVudHMgYXQgQ29tbWVudCBQb3N0aW5nICMiKQoKbWF4X3NhdmUocGxvdCwgInJlZGRpdC1maXJzdC00IiwgIlJlZGRpdCAoSmFuIDIwMTUgLSBTZXAgMjAxNikiKQpgYGAKIVtdKHJlZGRpdC1maXJzdC00LnBuZykKCiMgQW5hbHl6ZSBieSBTdWJyZWRkaXRzCiMjIEdldCBTdWJyZWRkaXQgRGF0YSBmcm9tIEJpZ1F1ZXJ5CgpDcmVhdGUgc2FtZSBjaGFydHMgYXMgYWJvdmUgZm9yIFRvcCAxMDAgU3VicmVkZGl0cyBieSB1bmlxdWUgYWN0aXZlIGNvbW1lbnRlcnMuIE11c3QgdXNlIFIgdG8gZ2V0IGRhdGEgc2luY2UgdG9vIG1hbnkgcm93cyByZXR1cm5lZC4gCgpgYGB7ciwgZXZhbD1GQUxTRX0KcHJvamVjdF9pZCA8LSA8RklMTCBJTj4gICAjIERPIE5PVCBTSEFSRSEKYGBgCgpUaGlzIHJlcXVpcmVzIGEgc3VicXVlcnkgdHdlYWsgb24gdGhlIGZpcnN0IHF1ZXJ5IHRvIGZpbHRlciBvbiB0b3AgMTAwIHN1YnJlZGRpdHMsIGFuZCBpbmNsdWRlIGluIG91dHB1dDoKCmBgYHNxbApTRUxFQ1Qgc3VicmVkZGl0LCBjcmVhdGVkX3JhbmssIHNjb3JlX3JhbmssCkNPVU5UKCopIGFzIG51bV9jb21tZW50cwpGUk9NCigKU0VMRUNUIApzdWJyZWRkaXQsClJPV19OVU1CRVIoKSBPVkVSIChQQVJUSVRJT04gQlkgbGlua19pZCBPUkRFUiBCWSBzY29yZSBERVNDKSBBUyBzY29yZV9yYW5rLCAKUk9XX05VTUJFUigpIE9WRVIgKFBBUlRJVElPTiBCWSBsaW5rX2lkIE9SREVSIEJZIGNyZWF0ZWRfdXRjIEFTQykgQVMgY3JlYXRlZF9yYW5rLApDT1VOVCgqKSBPVkVSIChQQVJUSVRJT04gQlkgbGlua19pZCkgQVMgbnVtX3RvcGxldmVsY29tbWVudHNfaW5fdGhyZWFkCkZST00gW2ZoLWJpZ3F1ZXJ5OnJlZGRpdF9jb21tZW50cy5hbGxfc3RhcnRpbmdfMjAxNTAxXQpXSEVSRSBsaW5rX2lkID0gcGFyZW50X2lkIEFORCBzdWJyZWRkaXQgSU4KICAgIChTRUxFQ1Qgc3VicmVkZGl0IEZST00gKFNFTEVDVCBzdWJyZWRkaXQsIENPVU5UKERJU1RJTkNUIGF1dGhvcikgYXMgdW5pcXVlX2NvbW1lbnRlcnMsCiAgICAgICAgRlJPTSBbZmgtYmlncXVlcnk6cmVkZGl0X2NvbW1lbnRzLmFsbF9zdGFydGluZ18yMDE1MDFdCiAgICAgICAgR1JPVVAgQlkgc3VicmVkZGl0CiAgICAgICAgT1JERVIgQlkgdW5pcXVlX2NvbW1lbnRlcnMgREVTQwogICAgICAgIExJTUlUIDEwMCkKICAgICkKKQpXSEVSRSBzY29yZV9yYW5rIDw9IDEwMCBBTkQgY3JlYXRlZF9yYW5rIDw9IDEwMCBBTkQgbnVtX3RvcGxldmVsY29tbWVudHNfaW5fdGhyZWFkID49IDMwCkdST1VQIEJZIHN1YnJlZGRpdCwgY3JlYXRlZF9yYW5rLCBzY29yZV9yYW5rCk9SREVSIEJZIHN1YnJlZGRpdCwgY3JlYXRlZF9yYW5rLCBzY29yZV9yYW5rCmBgYAoKUXVlcnkgd2l0aCBSIGFuZCBzYXZlIG91dHB1dCBmb3IgbGF0ZXIuCgpgYGB7ciwgZXZhbD1GQUxTRX0KcXVlcnkgPC0gIlNFTEVDVCBzdWJyZWRkaXQsIGNyZWF0ZWRfcmFuaywgc2NvcmVfcmFuaywKQ09VTlQoKikgYXMgbnVtX2NvbW1lbnRzCkZST00KKApTRUxFQ1QgCnN1YnJlZGRpdCwKUk9XX05VTUJFUigpIE9WRVIgKFBBUlRJVElPTiBCWSBsaW5rX2lkIE9SREVSIEJZIHNjb3JlIERFU0MpIEFTIHNjb3JlX3JhbmssIApST1dfTlVNQkVSKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGxpbmtfaWQgT1JERVIgQlkgY3JlYXRlZF91dGMgQVNDKSBBUyBjcmVhdGVkX3JhbmssCkNPVU5UKCopIE9WRVIgKFBBUlRJVElPTiBCWSBsaW5rX2lkKSBBUyBudW1fdG9wbGV2ZWxjb21tZW50c19pbl90aHJlYWQKRlJPTSBbZmgtYmlncXVlcnk6cmVkZGl0X2NvbW1lbnRzLmFsbF9zdGFydGluZ18yMDE1MDFdCldIRVJFIGxpbmtfaWQgPSBwYXJlbnRfaWQgQU5EIHN1YnJlZGRpdCBJTgogICAgKFNFTEVDVCBzdWJyZWRkaXQgRlJPTSAoU0VMRUNUIHN1YnJlZGRpdCwgQ09VTlQoRElTVElOQ1QgYXV0aG9yKSBhcyB1bmlxdWVfY29tbWVudGVycywKICAgICAgICBGUk9NIFtmaC1iaWdxdWVyeTpyZWRkaXRfY29tbWVudHMuYWxsX3N0YXJ0aW5nXzIwMTUwMV0KICAgICAgICBHUk9VUCBCWSBzdWJyZWRkaXQKICAgICAgICBPUkRFUiBCWSB1bmlxdWVfY29tbWVudGVycyBERVNDCiAgICAgICAgTElNSVQgMTAwKQogICAgKQopCldIRVJFIHNjb3JlX3JhbmsgPD0gMTAwIEFORCBjcmVhdGVkX3JhbmsgPD0gMTAwIEFORCBudW1fdG9wbGV2ZWxjb21tZW50c19pbl90aHJlYWQgPj0gMzAKR1JPVVAgQlkgc3VicmVkZGl0LCBjcmVhdGVkX3JhbmssIHNjb3JlX3JhbmsKT1JERVIgQlkgc3VicmVkZGl0LCBjcmVhdGVkX3JhbmssIHNjb3JlX3JhbmsiCgpkZl9zdWJyZWRkaXRzIDwtIHRibF9kZihxdWVyeV9leGVjKHF1ZXJ5LCBwcm9qZWN0PXByb2plY3RfaWQsIG1heF9wYWdlcz1JbmYpKQpkZl9zdWJyZWRkaXRzICU+JSBoZWFkKCkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0Kd3JpdGUuY3N2KGRmX3N1YnJlZGRpdHMsICJyZWRkaXRfMDEyMDE1X2J5X3N1YnJlZGRpdC5jc3YiLCByb3cubmFtZXM9RikKYGBgCgpSZWFkIGZyb20gY2FjaGVkIG91dHB1dC4KCmBgYHtyfQpkZl9zdWJyZWRkaXRzIDwtIHJlYWRfY3N2KCJyZWRkaXRfMDEyMDE1X2J5X3N1YnJlZGRpdC5jc3YiKQpgYGAKClRoZSByZXR1cm5lZCBmaWxlIGhhcyAqKmByIGRmX3N1YnJlZGRpdHMgJT4lIG5yb3coKSAlPiUgZm9ybWF0KGJpZy5tYXJrPScsJylgKiogcm93cyBhbmQgdGFrZXMgKipgciBkZl9zdWJyZWRkaXRzICU+JSBvYmplY3Quc2l6ZSgpICU+JSBmb3JtYXQodW5pdHMgPSAnTUInKWAqKiBvZiBtZW1vcnkgaW4gUi4KClJlbWFrZSB0aGUgYGRmX2ZpcnN0X2NvbW1lbnRgIGRhdGEgZnJhbWUgd2l0aCB0aGUgc3VicmVkZGl0IGRhdGEgYXMgd2VsbDoKCmBgYHtyfQpkZl9maXJzdF9jb21tZW50X3N1YnJlZGRpdCA8LSBkZl9zdWJyZWRkaXRzICU+JSBmaWx0ZXIoY3JlYXRlZF9yYW5rID09IDEpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieShzdWJyZWRkaXQpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUobm9ybSA9IG51bV9jb21tZW50cy9zdW0obnVtX2NvbW1lbnRzKSkKCmRmX2ZpcnN0X2NvbW1lbnRfc3VicmVkZGl0ICU+JSBoZWFkKCkKYGBgCgoKIyMgVmlzdWFsaXplIFN1YnJlZGRpdCBEYXRhCgpDcmVhdGUgdGhlIDFEIE1hcCBhbmQgdGhlIDJEIE1hcCBmb3IgZWFjaCBzdWJyZWRkaXQgdXNpbmcgdmFyaWFudHMgY29kZSBhYm92ZS4gRmlyc3QsIGV4dHJhY3QgYSBsaXN0IG9mIHRoZSB0b3AgMTAwIHN1YnJlZGRpdHMgdXNlZCBhYm92ZS4KCmBgYHtyfQpzdWJyZWRkaXRzIDwtIGRmX3N1YnJlZGRpdHMgJT4lIHNlbGVjdChzdWJyZWRkaXQpICU+JSB1bmlxdWUoKSAlPiUgdW5saXN0KCkKc3VicmVkZGl0cyAlPiUgaGVhZCgpCmBgYAoKQ3JlYXRlIGEgd3JhcHBlciBmdW5jdGlvbiwgd2hpY2ggZ2l2ZW4gYSBzdWJyZWRkaXQsIHByb2R1Y2VzIHRoZSBjaGFydCBmb3IgdGhhdCBzdWJyZWRkaXQuIChhc3N1bWVzIGBpbWctMWRgIGFuZCBgaW1nLTJkYCBkaXJlY3RvcmllcyBleGlzdCkKCiMjIyAxRCBDaGFydHMgZm9yIFRvcCAxMDAgU3VicmVkZGl0cwoKYGBge3J9CnN1YnJlZGRpdF8xZCA8LSBmdW5jdGlvbihwX3N1YnJlZGRpdCkgewpwbG90IDwtIGdncGxvdChkZl9maXJzdF9jb21tZW50X3N1YnJlZGRpdCAlPiUgZmlsdGVyKHN1YnJlZGRpdCA9PSBwX3N1YnJlZGRpdCksIGFlcyh4PXNjb3JlX3JhbmssIHk9bm9ybSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICAgICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMSxzZXEoMTAsMTAwLGJ5PTEwKSkpICsKICAgICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQsIGJyZWFrcz1wcmV0dHlfYnJlYWtzKDYpKSArCiAgICAgICAgICAgIGZ0ZV90aGVtZSgpICsKICAgICAgICAgICAgbGFicyh0aXRsZSA9IHNwcmludGYoIkRpc3RyaWJ1dGlvbiBvZiBGaXJzdCBDb21tZW50IFJhbmtpbmdzIGZvciAvci8lcyIsIHBfc3VicmVkZGl0KSwKICAgICAgICAgICAgICAgICB4ID0gIlNjb3JlIFJhbmsgb2YgRmlyc3QgQ29tbWVudCBpbiBUaHJlYWQgKExvd2VyIE51bWJlciBpcyBIaWdoZXIgU2NvcmUpIiwKICAgICAgICAgICAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgYWxsIEZpcnN0IENvbW1lbnRzIGF0IFNjb3JlIFJhbmsiKQoKbWF4X3NhdmUocGxvdCwgc3ByaW50ZigiaW1nLTFkLyVzLTFkIiwgcF9zdWJyZWRkaXQpLCAiUmVkZGl0IChKYW4gMjAxNSAtIFNlcCAyMDE2KSIpCn0KYGBgCgpSdW4gZm9yIGVhY2ggb2YgdGhlIDEwMCBzdWJyZWRkaXRzLgoKYGBge3J9CnRlbXAgPC0gbGFwcGx5KHN1YnJlZGRpdHMsIHN1YnJlZGRpdF8xZCkKYGBgCgoKIVtdKGltZy0xZC9tZV9pcmwtMWQucG5nKQoKIyMjIDJEIENoYXJ0cyBmb3IgVG9wIDEwMCBTdWJyZWRkaXRzCgpgYGB7cn0Kc3VicmVkZGl0XzJkIDwtIGZ1bmN0aW9uKHBfc3VicmVkZGl0KSB7CnBsb3QgPC0gZ2dwbG90KGRmX3N1YnJlZGRpdHMgJT4lIGZpbHRlcihzdWJyZWRkaXQgPT0gcF9zdWJyZWRkaXQpLCBhZXMoeD1jcmVhdGVkX3JhbmssIHk9c2NvcmVfcmFuaywgZmlsbD1udW1fY29tbWVudHMsIHo9bG9nMTAobnVtX2NvbW1lbnRzKSkpICsKICAgICAgICAgICAgZ2VvbV9yYXN0ZXIoaW50ZXJwb2xhdGUgPSBGQUxTRSkgKwogICAgICAgICAgICBnZW9tX2NvbnRvdXIoY29sb3IgPSAid2hpdGUiLCBhbHBoYSA9IDAuNSwgYmlucyA9IDMsIHNpemUgPSAwLjI1KSArCiAgICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDEsc2VxKDEwLDEwMCxieT0xMCkpKSArCiAgICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKDEsc2VxKDEwLDEwMCxieT0xMCkpKSArCiAgICAgICAgICAgIGZ0ZV90aGVtZSgpICsKICAgICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTcpKSArCiAgICAgICAgICAgIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTcsIGZhbWlseT0iT3BlbiBTYW5zIENvbmRlbnNlZCBCb2xkIiksIGxlZ2VuZC5wb3NpdGlvbj0idG9wIiwgbGVnZW5kLmRpcmVjdGlvbj0iaG9yaXpvbnRhbCIsIGxlZ2VuZC5rZXkud2lkdGg9dW5pdCgxLjI1LCAiY20iKSwgbGVnZW5kLmtleS5oZWlnaHQ9dW5pdCgwLjI1LCAiY20iKSwgbGVnZW5kLm1hcmdpbj11bml0KDAsImNtIiksIHBhbmVsLm1hcmdpbj1lbGVtZW50X2JsYW5rKCkpICsKICAgICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWU9IiMgb2ZcbkNvbW1lbnRzIiwgbGFiZWxzPWNvbW1hLCBicmVha3M9MTBeKDA6NCksIHRyYW5zPSJsb2cxMCIpICsKICAgICAgICAgICAgbGFicyh0aXRsZSA9IHNwcmludGYoIkhlYXRtYXAgYmV0d2VlbiBUaW1lIENvbW1lbnQgTWFkZSBhbmQgU2NvcmUgUmFuayBmb3IgL3IvJXMiLCBwX3N1YnJlZGRpdCksCiAgICAgICAgICAgICAgICAgeCA9ICIjIFRvcC1MZXZlbCBDb21tZW50IChMb3dlciBOdW1iZXIgaXMgUG9zdGVkIEVhcmxpZXIpIiwKICAgICAgICAgICAgICAgICB5ID0gIkNvbW1lbnQgU2NvcmUgUmFua2luZyAoTG93ZXIgTnVtYmVyIGlzIEhpZ2hlciBTY29yZSkiKQoKbWF4X3NhdmUocGxvdCwgc3ByaW50ZigiaW1nLTJkLyVzLTJkIiwgcF9zdWJyZWRkaXQpLCAiUmVkZGl0IChKYW4gMjAxNSAtIFNlcCAyMDE2KSIpCn0KYGBgCgpgYGB7cn0KdGVtcCA8LSBsYXBwbHkoc3VicmVkZGl0cywgc3VicmVkZGl0XzJkKQpgYGAKCiFbXShpbWctMmQvbWVfaXJsLTJkLnBuZykKCiMgUGxvdGx5CgpEbyBQbG90bHkgc2VwYXJhdGVseSBzaW5jZSBpdCByZXF1aXJlcyBjZXJ0YWluIHBhcmFtZXRlcnMgZm9yIGl0IHRvIGJlIGltcGxlbWVudGVkIGF0IG1pbmltYXhpci5jb20uICooVGhlIGZpbmFsIGludGVyYWN0aXZlIHBsb3QgaXMgbm90IHVzZWQgaW4gdGhlIHBvc3Qgc2luY2UgaXQgZG9lc24ndCBhZGQgbXVjaCkqCgpgYGB7cn0KdGhlbWVfY29sb3IgPC0gIiNmN2Y4ZmEiCgpwbG90bHlfdGhlbWUgPC0gZnVuY3Rpb24oKSB7CiAgICAgICAgICAgICAgICB0aGVtZShwbG90LmJhY2tncm91bmQ9ZWxlbWVudF9yZWN0KGZpbGw9dGhlbWVfY29sb3IpLCAKICAgICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X3JlY3QoZmlsbD10aGVtZV9jb2xvciksCiAgICAgICAgICAgICAgICAgIHBhbmVsLmJvcmRlcj1lbGVtZW50X3JlY3QoY29sb3I9dGhlbWVfY29sb3IpLAogICAgICAgICAgICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kPWVsZW1lbnRfcmVjdChmaWxsPXRoZW1lX2NvbG9yKSkKfQpgYGAKCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfZmlyc3RfY29tbWVudCwgYWVzKHg9c2NvcmVfcmFuaywgeT1ub3JtKSkgKwogICAgICAgICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogICAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxLHNlcSgxMCwxMDAsYnk9MTApKSkgKwogICAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCwgYnJlYWtzPXByZXR0eV9icmVha3MoNikpICsKICAgICAgICAgICAgZnRlX3RoZW1lKCkgKwogICAgICAgICAgICBwbG90bHlfdGhlbWUoKSArCiAgICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT03KSkgKwogICAgICAgICAgICBsYWJzKHRpdGxlID0gIldoYXQgUGVyY2VudGFnZSBvZiB0aGUgVG9wIENvbW1lbnRzIGluIFJlZGRpdCBUaHJlYWRzIFdlcmUgQWxzbyAxc3QgQ29tbWVudD8iLAogICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIlByb3BvcnRpb24gb2YgU2NvcmUgUmFua2luZ3MgZm9yIEZpcnN0IENvbW1lbnQiLAogICAgICAgICAgICAgICAgIHggPSAiU2NvcmUgUmFuayBvZiBGaXJzdCBDb21tZW50IGluIFRocmVhZCAoTG93ZXIgTnVtYmVyIGlzIEhpZ2hlciBTY29yZSkiLAogICAgICAgICAgICAgICAgIHkgPSAiUHJvcG9ydGlvbiBvZiBhbGwgRmlyc3QgQ29tbWVudHMgYXQgU2NvcmUgUmFuayIpCgoKcGxvdCA8LSBwbG90ICU+JQogICAgICAgIGdncGxvdGx5KCkgJT4lIAogICAgICAgIHNhdmVXaWRnZXQoInJlZGRpdC1maXJzdC0xZC5odG1sIiwgc2VsZmNvbnRhaW5lZD1GLCBsaWJkaXI9InBsb3RseSIpCmBgYAoKCiMgTElDRU5TRQoKVGhlIE1JVCBMaWNlbnNlIChNSVQpCgpDb3B5cmlnaHQgKGMpIDIwMTYgTWF4IFdvb2xmCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMgdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbCBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IgSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSIExJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLg==