This R Notebook is the complement to my blog post What Percent of the Top-Voted Comments in Reddit Threads Were Also 1st Comment?.

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! :)

1 Setup

Setup the R packages.

source("Rstart.R")

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.

2 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()
)
df %>% head()

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)")

3 Analyze by Subreddits

3.1 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()

3.2 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)

3.2.1 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)

3.2.2 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)

4 Plotly

Do Plotly separately since it requires certain parameters for it to be implemented at minimaxir.com. (The final interactive plot is not used in the post since it doesn’t add much)

theme_color <- "#f7f8fa"
plotly_theme <- function() {
                theme(plot.background=element_rect(fill=theme_color), 
                  panel.background=element_rect(fill=theme_color),
                  panel.border=element_rect(color=theme_color),
                  strip.background=element_rect(fill=theme_color))
}
plot <- ggplot(df_first_comment, 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() +
            plotly_theme() +
            theme(plot.title = element_text(size=7)) +
            labs(title = "What Percentage of the Top Comments in Reddit Threads Were Also 1st Comment?",
                 subtitle = "Proportion of Score Rankings for First Comment",
                 x = "Score Rank of First Comment in Thread (Lower Number is Higher Score)",
                 y = "Proportion of all First Comments at Score Rank")
plot <- plot %>%
        ggplotly() %>% 
        saveWidget("reddit-first-1d.html", selfcontained=F, libdir="plotly")

5 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==