Outline of my 2022 SiRAcon 2022 presentation, “Making R work for you
(with automation!)”.
Outline
Use GitHub data to show how the DORA metrics changed over time as I
developed and used rdev.
git log
Idea: use gert::git_log()
tables across all my public
and personal (private) R repositories over time to create an annotated
timeline and visualization of my work, and implementation of the DORA
technical practices.
Import git logs from my repositories:
# gitlogs is now included in siracon2022 for reproducibility, see data-raw/gitlogs and ?gitlogs
gitlogs_tz <- tz(gitlogs$time)
Filter logs by repository, adding cutoff dates when active
development ended for timeline visualization. Drop commits past April 30
to remove partial months.
filtered_gitlogs <- gitlogs |>
# set filter to midnight after last relevant commit, use same timezone as gitlogs
filter(!(repo == "rstudio-training" & time > ymd_h("2020-12-28 0", tz = gitlogs_tz))) |>
filter(!(repo == "software-resilience" & time > ymd_h("2021-02-22 0", tz = gitlogs_tz))) |>
filter(!(repo == "rtraining" & time > ymd_h("2021-10-08 0", tz = gitlogs_tz))) |>
filter(!(repo == "workshop7" & time > ymd_h("2021-12-07 0", tz = gitlogs_tz))) |>
filter(!(repo == "jbplot" & time > ymd_h("2022-02-07 0", tz = gitlogs_tz))) |>
# while this is now redundant (here and elsewhere), keeping it for clarity
filter(time < ymd_h("2022-05-01 0", tz = gitlogs_tz)) |>
# oldest first
mutate(repo = factor(repo, levels = c(
"rstudio-training", "software-resilience", "rtraining", "rdev", "workshop7", "jbplot",
"siracon2022"
)))
Plot monthly commits by repository.
filtered_gitlogs |>
mutate(time = floor_date(time, unit = "month")) |>
group_by(time, repo) |>
summarize(commits = n(), .groups = "drop") |>
ggplot(aes(x = time, y = commits, color = repo)) +
geom_point() +
geom_line() +
labs(title = "Monthly commits by repository") +
labs(x = "", y = "", color = "repository") +
theme_quo()
ggsave("rendered/monthly-commits-repo.png", width = 16 * 0.6, height = 9 * 0.6, bg = "white")
High resolution
plot
Timeline
Create a timeline using groups showing the history of the
repositories:
- training: rstudio-training, rtraining
- development: rdev, jbplot
- notebooks: software-resilience, workshop7, siracon2022
repo_timeline <- filtered_gitlogs |>
select(repo, time) |>
mutate(time = floor_date(time, unit = "day")) |>
group_by(repo) |>
summarize(start = min(time), end = max(time)) |>
arrange(start) |>
mutate(group = case_when(
grepl("training", repo, fixed = TRUE) ~ "training",
repo %in% c("rdev", "jbplot") ~ "development",
TRUE ~ "notebooks"
)) |>
mutate(color = hue_pal()(7)[row_number()])
repo_timeline
# TODO: gg_vistime doesn't render well when using scale_color_viridis_d()
# issue: https://github.com/shosaco/vistime/issues/30
gg_vistime(repo_timeline, col.event = "repo", title = "R Development Timeline") +
theme_quo()
ggsave("rendered/repo-timeline.png", width = 16 * 0.6, height = 9 * 0.6, bg = "white")
High resolution plot
Key events
Plot key events on a timevis()
timeline. Full page version.
key_events <- read_csv("data/key-events.csv", col_types = cols(
id = col_integer(),
start = col_date(format = ""),
end = col_date(format = ""),
content = col_character(),
group = col_integer(),
group_content = col_character(),
intro = col_logical(),
milestone = col_logical()
))
dora_groups <- key_events |>
select(id = group, content = group_content) |>
unique() |>
arrange(id)
key_events |>
render_timevis(groups = dora_groups, file = "rendered/key-events.html", showZoom = TRUE)
2020-09-08: Starting out, rstudio-training, renv
- Version Control
- Stored project files, notebook (Rmd and html) in private git
repository
- Use renv to store package dependencies in source control
- Trunk-based Development
- Direct commits to master (not recommended)
- Shifting Left on Security
- Start development with
renv::update()
2020-09-11: Published “Working
with R”
2020-09-30: (Aside) First bug discovered, https://github.com/rstudio/renv/issues/547 !
2020-10-06: setup-r
script
- Version Control
- Automate setup of local R development environment
2020-12-02: Adoption of styler and lintr
- Code Maintainability
- Consistent formatting (styler)
- Consistent code (lintr)
- Continuous Testing
- Static code analysis (lintr)
2020-12-27: Migration to rtraining package
- Continuous Integration
- Continuous Testing
- Version Control
2020-12-29: build-site script
- Deployment Automation
- build-site: shell script to publish notebooks using
rmarkdown::render_site()
- MVP for publishing notebooks using GitHub Pages
2020-12-30: First release: rtraining 0.0.1
2020-12-30: GitHub Actions
- Continuous Integration, Continuous Testing
2020-12-30: lint_all()
- Continuous Integration, Continuous Testing
- lint all files locally
- first testthat tests
- roxygen2 documentation
2020-12-30: style_all()
- Continuous Integration, Code Maintainability
- run styler on all files locally
2020-12-31: Switch GitHub Actions to lint_all()
- Continuous Integration, Continuous Testing
- match GitHub and local CI checks
2021-01-01: ci()
, check_renv()
- Continuous Integration, Continuous Testing
- run all CI checks locally
- eliminate toil
- match GitHub and local CI checks
2021-01-01: Migration to rdev package
- Code Maintainability
- Moved most functions to new rdev package
- Consistent tools across projects
2021-01-02: Multi-platform R CMD check
- Continuous Integration, Continuous Testing
- ensure package works on Windows and macOS
2021-01-03: First version of build_analysis_site()
- Deployment Automation
- Automatically build GitHub Pages site with functions, notebooks
- Still a shell script
- Beginning of standard deployment and release pattern:
- bump version
- write code
- update NEWS.md
- “GitHub Release”
- build_site
2021-01-09: Analysis Package Layout
- Code Maintainability
- Consistent package layout across projects
- Supported future automation for creating packages
2021-01-12: Native R version of
build_analysis_site()
2021-01-16: Migrated build_analysis_site()
from
rtraining to rdev
- Code Maintainability
- Cross-platform support
- Moves all automation to R Console
- Deployment Automation
- Automated builds across all projects
2021-09-29: Formal R Analysis Package Layout, Documented release
process
- Code Maintainability
- Consistent package layout across projects
- Supported future automation for creating packages
- Deployment Automation
- Supported future automation for creating releases
2021-12-04: Documented package creation process
- Code Maintainability
- Consistent package layout across projects
- Supported future automation for creating packages
2021-12-23: theme_quo()
: a personalized theme to
visually identify my ggplots.
2022-01-01: Automate package configuration with
use_analysis_package()
- Code Maintainability
- Consistent package layout across projects
2022-01-10: Create package automation (rdev 0.7.0)
create_github_repo()
: Create new GitHub repository
following rdev conventions in the active user’s account and create a
basic package
use_rdev_package()
: Add rdev templates and settings
within the active package. Normally invoked when first setting up a
package.
Added build_rdev_site()
, a wrapper for
pkgdown::build_site()
optimized for rdev workflow that
updates README.md
and performs a clean build using
pkgdown
Added ‘Analysis Notebook’ R markdown template for RStudio (File
> New File > Rmarkdown > From Template)
Migrated ggplot2 themes/styles (theme_quo()
,
viridis_quo()
) to new package,
jabenninghoff/jbplot
Code Maintainability
- Cross-platform support
- Moves all automation to R Console
Deployment Automation
- Automated builds across all projects
2022-01-10: Automate notebook listings in README
library(rdev)
library(fs)
library(dplyr)
library(purrr)
notebooks <- dir_ls("analysis", glob = "*.Rmd") |>
map_dfr(rmd_metadata) |>
mutate(bullet = paste0("- [", title, "](", url, ") (", date, "): ", description)) |>
pull(bullet)
writeLines(notebooks)
2022-01-17: Release automation (rdev 0.8.0)
2022-01-19: More workflow automation
- Added
new_branch()
: Create a new feature branch, and
(optionally) bump the version in DESCRIPTION
2022-01-21 - 2022-02-06: adding test coverage
- Continuous Testing
- Biggest challenge yet
- Significantly improved code quality
- “Unit” testing
- Just test
- Test program flow
- Don’t test other people’s code
- Mock external functions
- Fix bugs by writing a test
- Code coverage, and code coverage metrics
- Test Driven Development
- Tests Give You Confidence (to Refactor)
(Show plot of increasing code coverage from codecov.io)
2022-01-24: write_eval() is a really bad idea:
#' Write and evaluate an expression
#'
#' `write_eval(string)` is a simple wrapper that prints `string` to the console using
#' [`writeLines()`][base::writeLines], then executes the expression using [`parse()`][base::parse]
#' and [`eval()`][base::eval].
#'
#' @param string An expression to be printed to the console and evaluated
#'
#' @return The return value from the evaluated expression
#'
#' @examples
#' write_eval("pi")
#'
#' write_eval("exp(1)")
#' @export
write_eval <- function(string) {
if (!is.character(string)) stop("not a character vector")
if (string == "") stop("nothing to evaluate")
writeLines(string)
eval(parse(text = string))
}
2022-01-30: Manual test script for new package setup
- Continuous Testing
- Manual tests evolve into partially or fully automated tests
2022-02-02: Added local_temppkg()
test helper
function
- Continuous Testing
- Test helpers - testing test helpers helps!
2022-02-06: rdev 1.0.0 !
- Release automation: Stage and create GitHub releases, including
GitHub pages
- Continuous Integration: Local continuous integration checks and
dependency management
- Package Setup: Package setup tasks, typically performed once
2022-02-06 - Today: Continuous Improvement
- Improve CI workflow to catch mistakes
- Spell checks
- Branch protection automation
- Options
- Dependency management
- Product health
Releases
Get releases from GitHub using
siracon2022::gh_releases()
:
# cache results
if (!exists("releases")) {
repos <- c("rtraining", "rdev", "workshop7", "jbplot", "siracon2022")
repos <- setNames(repos, repos)
releases <- map_dfr(repos, gh_releases, "jabenninghoff", .id = "repo") |>
arrange(time)
}
Filter releases past April 30 to remove partial months.
filtered_releases <- releases |>
mutate(time = with_tz(time, tzone = gitlogs_tz)) |>
filter(time < ymd_h("2022-05-01 0", tz = gitlogs_tz))
Plot releases over time: total GitHub releases per period (for all
repositories) to show changes in release frequency. The dotted line
marks the implementation of release automation.
monthly_releases <- filtered_releases |>
mutate(time = floor_date(time, unit = "month")) |>
group_by(time) |>
summarize(releases = n(), .groups = "drop") |>
add_row(time = ymd("2020-11-01"), releases = 0) |>
add_row(time = ymd("2020-10-01"), releases = 0) |>
add_row(time = ymd("2020-09-01"), releases = 0) |>
arrange(time)
monthly_releases |>
ggplot(aes(x = time, y = releases)) +
geom_point() +
geom_line() +
geom_vline(xintercept = ymd_h("2020-12-01 0", tz = gitlogs_tz), linetype = "dotted") +
geom_vline(xintercept = ymd_h("2022-01-01 0", tz = gitlogs_tz), linetype = "dotted") +
coord_cartesian(ylim = c(0, NA)) +
labs(title = "Monthly GitHub releases") +
labs(x = "", y = "") +
theme_quo()
ggsave("rendered/monthly-releases.png", width = 16 * 0.6, height = 9 * 0.6, bg = "white")
High resolution plot
However, the number of releases per month might just represent how
much work is being done, and looks similar to the plot of all commits by
month:
gitlogs |>
filter(time < ymd_h("2022-05-01 0", tz = gitlogs_tz)) |>
mutate(time = floor_date(time, unit = "month")) |>
group_by(time) |>
summarize(commits = n(), .groups = "drop") |>
arrange(time) |>
ggplot(aes(x = time, y = commits)) +
geom_point() +
geom_line() +
geom_vline(xintercept = ymd_h("2020-12-01 0", tz = gitlogs_tz), linetype = "dotted") +
geom_vline(xintercept = ymd_h("2022-01-01 0", tz = gitlogs_tz), linetype = "dotted") +
coord_cartesian(ylim = c(0, NA)) +
labs(title = "Monthly git commits") +
labs(x = "", y = "") +
theme_quo()
ggsave("rendered/monthly-commits.png", width = 16 * 0.6, height = 9 * 0.6, bg = "white")
High resolution plot
Also plot releases per commit, which will fall between 0 and 1. The
dotted lines mark adoption of GitHub and implementation of release
automation.
gitlogs |>
filter(time < ymd_h("2022-05-01 0", tz = gitlogs_tz)) |>
mutate(time = floor_date(time, unit = "month")) |>
group_by(time) |>
summarize(commits = n()) |>
full_join(monthly_releases, by = "time") |>
replace_na(list(commits = 0, releases = 0)) |>
mutate(rpc = releases / commits) |>
ggplot(aes(x = time, y = rpc)) +
geom_point() +
geom_line() +
geom_vline(xintercept = ymd_h("2020-12-01 0", tz = gitlogs_tz), linetype = "dotted") +
geom_vline(xintercept = ymd_h("2022-01-01 0", tz = gitlogs_tz), linetype = "dotted") +
labs(title = "Monthly GitHub releases per commit") +
labs(x = "", y = "") +
theme_quo()
ggsave("rendered/releases-per-commit.png", width = 16 * 0.6, height = 9 * 0.6, bg = "white")
High resolution
plot
Story
Use the timeline and plots to tell the story of continuous
improvement. Each section filters on group 1 and the other focus area.
Integrate themes into story.
- Introduction: background and motivation, use Event group as the talk
overview. Exclude SiRAcon 2020 from future timelines. “R Development
Timeline”.
- Version Control: put everything (except artifacts) into version
control for reproducibility and history.
- Trunk-based Development: linear development avoids code
conflicts.
- Shift Left on Security: maintenance first ensures you get it
done.
- Continuous Integration: build and test on each commit to catch
mistakes early.
- Deployment Automation: automate your development workflow to spend
more time writing.
- Code Maintainability: consistent and clean code is easier to
understand.
- Continuous Testing: (the biggest challenge) formally specifying what
you are building and how it is supposed to work defends against
the dangers of hidden assumptions.
- Results: “Monthly commits by repository”, “Monthly GitHub releases”,
“GitHub releases per commit”. Improvement on technical practices also
means less rework, less deployment pain, less burnout, and greater job
satisfaction.
- Closing: complete key events timeline.
Full rdev package list:
desc
devtools
fs
gert
gh
lintr
markdown
miniUI
pkgdown
purrr
rcmdcheck
remotes
renv
rlang
rmarkdown
styler
tibble
usethis
withr
xml2
yaml
covr
DT
knitr
mockery
spelling
stringi
testthat
Introduction
Background and motivation. Full page
version.
key_events |>
filter(group == 1) |>
render_timevis(groups = filter(dora_groups, id == 1), file = "rendered/intro.html")
Version Control
Put everything (except artifacts) into version control for
reproducibility and history. Full page version.
Use of Homebrew, and
brew bundle
.
Packages:
renv
: dependency management
key_events |>
filter(!intro) |>
filter(milestone | group == 2) |>
render_timevis(groups = filter(dora_groups, id %in% c(1, 2)), "rendered/version-control.html")
Trunk-based Development
Linear development avoids code conflicts. Full page version.
key_events |>
filter(!intro) |>
filter(milestone | group == 3 | id == 44) |>
render_timevis(groups = filter(dora_groups, id %in% c(1, 3)), file = "rendered/trunk-based.html")
Shift Left on Security
Maintenance first ensures you get it done. Full page version.
Reference last year’s talk, recording available in member’s
section.
Packages:
key_events |>
filter(!intro) |>
filter(milestone | group == 4) |>
render_timevis(groups = filter(dora_groups, id %in% c(1, 4)), file = "rendered/shift-left.html")
Continuous Integration
Build and test on each commit to catch mistakes early. Full page version.
Packages:
key_events |>
filter(!intro) |>
filter(milestone | group == 5) |>
render_timevis(groups = filter(dora_groups, id %in% c(1, 5)), file = "rendered/ci.html")
Deployment Automation
Automate your development workflow to spend more time writing. Full page version.
Reducing toil. Forming habits, which become repeated tasks, which
become automation. If it’s automated, it gets done.
Packages:
pkgdown
, rmarkdown
:
build_analysis_site()
gert
, gh
: git, GitHub automation
devtools
key_events |>
filter(!intro) |>
filter(milestone | group == 6 | id == 32) |>
render_timevis(groups = filter(dora_groups, id %in% c(1, 6)), file = "rendered/deployments.html")
Code Maintainability
Consistent and clean code is easier to understand. Full page version.
Functional programming (purrr) vs procedural programming.
Functional programming is harder to learn, but safer.
R dialects: base R is for functions, tidyverse R is for
notebooks.
“Clean” code: code should be written for future humans, including
you!
Packages:
styler
usethis
gh
desc
devtools
purrr
key_events |>
filter(!intro) |>
filter(milestone | group == 7 | id == 32) |>
render_timevis(groups = filter(dora_groups, id %in% c(1, 7)), file = "rendered/code-maint.html")
Continuous Testing
The biggest challenge: formally specifying what you are building and
how it is supposed to work defends against the dangers of
hidden assumptions. Full page
version.
Packages:
lintr
: static code analysis
rcmdcheck
testthat
devtools
covr
mockery
withr
rlang
spelling
Future Testing
Mutation Testing: Wikipedia
R packages:
Papers:
Formal Methods:
key_events |>
filter(!intro) |>
filter(milestone | group == 8 | id == 32) |>
render_timevis(groups = filter(dora_groups, id %in% c(1, 8)), file = "rendered/testing.html")
End of (out)line.
LS0tCnRpdGxlOiBTaVJBY29uIFByZXNlbnRhdGlvbiBPdXRsaW5lCmF1dGhvcjogSm9obiBCZW5uaW5naG9mZgpkYXRlOiAnMjAyMi0wNC0yNCcKZGF0ZS1tb2RpZmllZDogJzIwMjMtMTEtMDgnCmNhdGVnb3JpZXM6IFtdCm9yZGVyOiB+Cm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6CiAgICAgIHZlcnNpb246IDUKICAgICAgcHJlc2V0OiBib290c3RyYXAKICAgIGNzczogYXNzZXRzL2V4dHJhLmNzcwogICAgcGFuZG9jX2FyZ3M6IC0tc2hpZnQtaGVhZGluZy1sZXZlbC1ieT0xCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IG5vCiAgICAgIHNtb290aF9zY3JvbGw6IG5vCi0tLQoKT3V0bGluZSBvZiBteSAyMDIyIFNpUkFjb24gMjAyMiBwcmVzZW50YXRpb24sICJNYWtpbmcgUiB3b3JrIGZvciB5b3UgKHdpdGggYXV0b21hdGlvbiEpIi4KCiMgUXVlc3Rpb25zL1RPRE8KCi0gWyBdIFF1ZXN0aW9ucy9UT0RPIGxpc3QgaGVyZQoKYGBge3Igc2V0dXAsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KHNpcmFjb24yMDIyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHNjYWxlcykKbGlicmFyeSh2aXN0aW1lKQpsaWJyYXJ5KGpicGxvdCkKYGBgCgojIEZyYW1ld29yawoKVXNlIHRoZSBbRE9SQSBSZXNlYXJjaCBQcm9ncmFtXShhc3NldHMvZG9yYV9yZXNlYXJjaF9wcm9ncmFtLnBkZikgdG8gZnJhbWUgdGhlIHN0b3J5IG9mIGhvdyBJCmxlYXJuZWQgUiBhbmQgc29mdHdhcmUgZW5naW5lZXJpbmcgYnkgaW1wbGVtZW50aW5nIHRoZSBET1JBIERldk9wcyB0ZWNobmljYWwgcHJhY3RpY2VzOgoKLSBbVmVyc2lvbiBDb250cm9sXShodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vYXJjaGl0ZWN0dXJlL2Rldm9wcy9kZXZvcHMtdGVjaC12ZXJzaW9uLWNvbnRyb2wpCi0gW1RydW5rLWJhc2VkIERldmVsb3BtZW50XShodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vYXJjaGl0ZWN0dXJlL2Rldm9wcy9kZXZvcHMtdGVjaC10cnVuay1iYXNlZC1kZXZlbG9wbWVudCkKLSBbU2hpZnRpbmcgTGVmdCBvbiBTZWN1cml0eV0oaHR0cHM6Ly9jbG91ZC5nb29nbGUuY29tL2FyY2hpdGVjdHVyZS9kZXZvcHMvZGV2b3BzLXRlY2gtc2hpZnRpbmctbGVmdC1vbi1zZWN1cml0eSkKLSBbQ29udGludW91cyBJbnRlZ3JhdGlvbl0oaHR0cHM6Ly9jbG91ZC5nb29nbGUuY29tL2FyY2hpdGVjdHVyZS9kZXZvcHMvZGV2b3BzLXRlY2gtY29udGludW91cy1pbnRlZ3JhdGlvbikKLSBbQ29udGludW91cyBUZXN0aW5nXShodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vYXJjaGl0ZWN0dXJlL2Rldm9wcy9kZXZvcHMtdGVjaC10ZXN0LWF1dG9tYXRpb24pCi0gW0RlcGxveW1lbnQgQXV0b21hdGlvbl0oaHR0cHM6Ly9jbG91ZC5nb29nbGUuY29tL2FyY2hpdGVjdHVyZS9kZXZvcHMvZGV2b3BzLXRlY2gtZGVwbG95bWVudC1hdXRvbWF0aW9uKQotIFtDb2RlIE1haW50YWluYWJpbGl0eV0oaHR0cHM6Ly9jbG91ZC5nb29nbGUuY29tL2FyY2hpdGVjdHVyZS9kZXZvcHMvZGV2b3BzLXRlY2gtY29kZS1tYWludGFpbmFiaWxpdHkpCiAgKGFsc28gY292ZXIgImNsZWFuIiBjb2RlKQoKPGh0dHBzOi8vd3d3LmRldm9wcy1yZXNlYXJjaC5jb20vcmVzZWFyY2guaHRtbD4KCkNyZWF0ZSBhIHRpbWVsaW5lIG9mIG15IGpvdXJuZXkgdXNpbmcgW3Zpc3RpbWVdKGh0dHBzOi8vc2hvc2Fjby5naXRodWIuaW8vdmlzdGltZS8pIG9yClt0aW1ldmlzXShodHRwczovL2RhYXR0YWxpLmNvbS9zaGlueS90aW1ldmlzLWRlbW8vKS4KCiMgT3V0bGluZQoKVXNlIEdpdEh1YiBkYXRhIHRvIHNob3cgaG93IHRoZSBET1JBIG1ldHJpY3MgY2hhbmdlZCBvdmVyIHRpbWUgYXMgSSBkZXZlbG9wZWQgYW5kIHVzZWQKW3JkZXZdKGh0dHBzOi8vamFiZW5uaW5naG9mZi5naXRodWIuaW8vcmRldi8pLgoKIyMgZ2l0IGxvZwoKSWRlYTogdXNlIGBnZXJ0OjpnaXRfbG9nKClgIHRhYmxlcyBhY3Jvc3MgYWxsIG15IHB1YmxpYyBhbmQgcGVyc29uYWwgKHByaXZhdGUpIFIgcmVwb3NpdG9yaWVzIG92ZXIKdGltZSB0byBjcmVhdGUgYW4gYW5ub3RhdGVkIHRpbWVsaW5lIGFuZCB2aXN1YWxpemF0aW9uIG9mIG15IHdvcmssIGFuZCBpbXBsZW1lbnRhdGlvbiBvZiB0aGUgRE9SQQp0ZWNobmljYWwgcHJhY3RpY2VzLgoKSW1wb3J0IGdpdCBsb2dzIGZyb20gbXkgcmVwb3NpdG9yaWVzOgoKYGBge3IgZ2l0bG9nc190en0KIyBnaXRsb2dzIGlzIG5vdyBpbmNsdWRlZCBpbiBzaXJhY29uMjAyMiBmb3IgcmVwcm9kdWNpYmlsaXR5LCBzZWUgZGF0YS1yYXcvZ2l0bG9ncyBhbmQgP2dpdGxvZ3MKCmdpdGxvZ3NfdHogPC0gdHooZ2l0bG9ncyR0aW1lKQpgYGAKCkZpbHRlciBsb2dzIGJ5IHJlcG9zaXRvcnksIGFkZGluZyBjdXRvZmYgZGF0ZXMgd2hlbiBhY3RpdmUgZGV2ZWxvcG1lbnQgZW5kZWQgZm9yIHRpbWVsaW5lCnZpc3VhbGl6YXRpb24uIERyb3AgY29tbWl0cyBwYXN0IEFwcmlsIDMwIHRvIHJlbW92ZSBwYXJ0aWFsIG1vbnRocy4KCmBgYHtyIGZpbHRlcmVkX2dpdGxvZ3N9CmZpbHRlcmVkX2dpdGxvZ3MgPC0gZ2l0bG9ncyB8PgogICMgc2V0IGZpbHRlciB0byBtaWRuaWdodCBhZnRlciBsYXN0IHJlbGV2YW50IGNvbW1pdCwgdXNlIHNhbWUgdGltZXpvbmUgYXMgZ2l0bG9ncwogIGZpbHRlcighKHJlcG8gPT0gInJzdHVkaW8tdHJhaW5pbmciICYgdGltZSA+IHltZF9oKCIyMDIwLTEyLTI4IDAiLCB0eiA9IGdpdGxvZ3NfdHopKSkgfD4KICBmaWx0ZXIoIShyZXBvID09ICJzb2Z0d2FyZS1yZXNpbGllbmNlIiAmIHRpbWUgPiB5bWRfaCgiMjAyMS0wMi0yMiAwIiwgdHogPSBnaXRsb2dzX3R6KSkpIHw+CiAgZmlsdGVyKCEocmVwbyA9PSAicnRyYWluaW5nIiAmIHRpbWUgPiB5bWRfaCgiMjAyMS0xMC0wOCAwIiwgdHogPSBnaXRsb2dzX3R6KSkpIHw+CiAgZmlsdGVyKCEocmVwbyA9PSAid29ya3Nob3A3IiAmIHRpbWUgPiB5bWRfaCgiMjAyMS0xMi0wNyAwIiwgdHogPSBnaXRsb2dzX3R6KSkpIHw+CiAgZmlsdGVyKCEocmVwbyA9PSAiamJwbG90IiAmIHRpbWUgPiB5bWRfaCgiMjAyMi0wMi0wNyAwIiwgdHogPSBnaXRsb2dzX3R6KSkpIHw+CiAgIyB3aGlsZSB0aGlzIGlzIG5vdyByZWR1bmRhbnQgKGhlcmUgYW5kIGVsc2V3aGVyZSksIGtlZXBpbmcgaXQgZm9yIGNsYXJpdHkKICBmaWx0ZXIodGltZSA8IHltZF9oKCIyMDIyLTA1LTAxIDAiLCB0eiA9IGdpdGxvZ3NfdHopKSB8PgogICMgb2xkZXN0IGZpcnN0CiAgbXV0YXRlKHJlcG8gPSBmYWN0b3IocmVwbywgbGV2ZWxzID0gYygKICAgICJyc3R1ZGlvLXRyYWluaW5nIiwgInNvZnR3YXJlLXJlc2lsaWVuY2UiLCAicnRyYWluaW5nIiwgInJkZXYiLCAid29ya3Nob3A3IiwgImpicGxvdCIsCiAgICAic2lyYWNvbjIwMjIiCiAgKSkpCmBgYAoKUGxvdCBtb250aGx5IGNvbW1pdHMgYnkgcmVwb3NpdG9yeS4KCmBgYHtyIG1vbnRobHlfY29tbWl0c19yZXBvfQpmaWx0ZXJlZF9naXRsb2dzIHw+CiAgbXV0YXRlKHRpbWUgPSBmbG9vcl9kYXRlKHRpbWUsIHVuaXQgPSAibW9udGgiKSkgfD4KICBncm91cF9ieSh0aW1lLCByZXBvKSB8PgogIHN1bW1hcml6ZShjb21taXRzID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKSB8PgogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBjb21taXRzLCBjb2xvciA9IHJlcG8pKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IGNvbW1pdHMgYnkgcmVwb3NpdG9yeSIpICsKICBsYWJzKHggPSAiIiwgeSA9ICIiLCBjb2xvciA9ICJyZXBvc2l0b3J5IikgKwogIHRoZW1lX3F1bygpCgpnZ3NhdmUoInJlbmRlcmVkL21vbnRobHktY29tbWl0cy1yZXBvLnBuZyIsIHdpZHRoID0gMTYgKiAwLjYsIGhlaWdodCA9IDkgKiAwLjYsIGJnID0gIndoaXRlIikKYGBgCgpbSGlnaCByZXNvbHV0aW9uIHBsb3RdKHJlbmRlcmVkL21vbnRobHktY29tbWl0cy1yZXBvLnBuZykKCiMjIFRpbWVsaW5lCgpDcmVhdGUgYSB0aW1lbGluZSB1c2luZyBncm91cHMgc2hvd2luZyB0aGUgaGlzdG9yeSBvZiB0aGUgcmVwb3NpdG9yaWVzOgoKLSB0cmFpbmluZzogcnN0dWRpby10cmFpbmluZywgcnRyYWluaW5nCi0gZGV2ZWxvcG1lbnQ6IHJkZXYsIGpicGxvdAotIG5vdGVib29rczogc29mdHdhcmUtcmVzaWxpZW5jZSwgd29ya3Nob3A3LCBzaXJhY29uMjAyMgoKYGBge3IgcmVwb190aW1lbGluZX0KcmVwb190aW1lbGluZSA8LSBmaWx0ZXJlZF9naXRsb2dzIHw+CiAgc2VsZWN0KHJlcG8sIHRpbWUpIHw+CiAgbXV0YXRlKHRpbWUgPSBmbG9vcl9kYXRlKHRpbWUsIHVuaXQgPSAiZGF5IikpIHw+CiAgZ3JvdXBfYnkocmVwbykgfD4KICBzdW1tYXJpemUoc3RhcnQgPSBtaW4odGltZSksIGVuZCA9IG1heCh0aW1lKSkgfD4KICBhcnJhbmdlKHN0YXJ0KSB8PgogIG11dGF0ZShncm91cCA9IGNhc2Vfd2hlbigKICAgIGdyZXBsKCJ0cmFpbmluZyIsIHJlcG8sIGZpeGVkID0gVFJVRSkgfiAidHJhaW5pbmciLAogICAgcmVwbyAlaW4lIGMoInJkZXYiLCAiamJwbG90IikgfiAiZGV2ZWxvcG1lbnQiLAogICAgVFJVRSB+ICJub3RlYm9va3MiCiAgKSkgfD4KICBtdXRhdGUoY29sb3IgPSBodWVfcGFsKCkoNylbcm93X251bWJlcigpXSkKCnJlcG9fdGltZWxpbmUKIyBUT0RPOiBnZ192aXN0aW1lIGRvZXNuJ3QgcmVuZGVyIHdlbGwgd2hlbiB1c2luZyBzY2FsZV9jb2xvcl92aXJpZGlzX2QoKQojICAgaXNzdWU6IGh0dHBzOi8vZ2l0aHViLmNvbS9zaG9zYWNvL3Zpc3RpbWUvaXNzdWVzLzMwCmdnX3Zpc3RpbWUocmVwb190aW1lbGluZSwgY29sLmV2ZW50ID0gInJlcG8iLCB0aXRsZSA9ICJSIERldmVsb3BtZW50IFRpbWVsaW5lIikgKwogIHRoZW1lX3F1bygpCgpnZ3NhdmUoInJlbmRlcmVkL3JlcG8tdGltZWxpbmUucG5nIiwgd2lkdGggPSAxNiAqIDAuNiwgaGVpZ2h0ID0gOSAqIDAuNiwgYmcgPSAid2hpdGUiKQpgYGAKCltIaWdoIHJlc29sdXRpb24gcGxvdF0ocmVuZGVyZWQvcmVwby10aW1lbGluZS5wbmcpCgojIyBLZXkgZXZlbnRzCgpQbG90IGtleSBldmVudHMgb24gYSBgdGltZXZpcygpYCB0aW1lbGluZS4gW0Z1bGwgcGFnZSB2ZXJzaW9uXShyZW5kZXJlZC9rZXktZXZlbnRzLmh0bWwpLgoKYGBge3Iga2V5X2V2ZW50cywgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxOH0Ka2V5X2V2ZW50cyA8LSByZWFkX2NzdigiZGF0YS9rZXktZXZlbnRzLmNzdiIsIGNvbF90eXBlcyA9IGNvbHMoCiAgaWQgPSBjb2xfaW50ZWdlcigpLAogIHN0YXJ0ID0gY29sX2RhdGUoZm9ybWF0ID0gIiIpLAogIGVuZCA9IGNvbF9kYXRlKGZvcm1hdCA9ICIiKSwKICBjb250ZW50ID0gY29sX2NoYXJhY3RlcigpLAogIGdyb3VwID0gY29sX2ludGVnZXIoKSwKICBncm91cF9jb250ZW50ID0gY29sX2NoYXJhY3RlcigpLAogIGludHJvID0gY29sX2xvZ2ljYWwoKSwKICBtaWxlc3RvbmUgPSBjb2xfbG9naWNhbCgpCikpCgpkb3JhX2dyb3VwcyA8LSBrZXlfZXZlbnRzIHw+CiAgc2VsZWN0KGlkID0gZ3JvdXAsIGNvbnRlbnQgPSBncm91cF9jb250ZW50KSB8PgogIHVuaXF1ZSgpIHw+CiAgYXJyYW5nZShpZCkKCmtleV9ldmVudHMgfD4KICByZW5kZXJfdGltZXZpcyhncm91cHMgPSBkb3JhX2dyb3VwcywgZmlsZSA9ICJyZW5kZXJlZC9rZXktZXZlbnRzLmh0bWwiLCBzaG93Wm9vbSA9IFRSVUUpCmBgYAoKMjAyMC0wOS0wODogU3RhcnRpbmcgb3V0LCByc3R1ZGlvLXRyYWluaW5nLCByZW52CgotIFZlcnNpb24gQ29udHJvbAogIC0gU3RvcmVkIHByb2plY3QgZmlsZXMsIG5vdGVib29rIChSbWQgYW5kIGh0bWwpIGluIHByaXZhdGUgZ2l0IHJlcG9zaXRvcnkKICAtIFVzZSByZW52IHRvIHN0b3JlIHBhY2thZ2UgZGVwZW5kZW5jaWVzIGluIHNvdXJjZSBjb250cm9sCi0gVHJ1bmstYmFzZWQgRGV2ZWxvcG1lbnQKICAtIERpcmVjdCBjb21taXRzIHRvIG1hc3RlciAobm90IHJlY29tbWVuZGVkKQotIFNoaWZ0aW5nIExlZnQgb24gU2VjdXJpdHkKICAtIFN0YXJ0IGRldmVsb3BtZW50IHdpdGggYHJlbnY6OnVwZGF0ZSgpYAoKMjAyMC0wOS0xMTogUHVibGlzaGVkIFsiV29ya2luZyB3aXRoIFIiXShodHRwczovL3d3dy5pbmZvcm1hdGlvbi1zYWZldHkub3JnLzIwMjAvMDkvMTEvd29ya2luZy13aXRoLXIvKQoKMjAyMC0wOS0zMDogKEFzaWRlKSBGaXJzdCBidWcgZGlzY292ZXJlZCwgaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vcmVudi9pc3N1ZXMvNTQ3ICEKCjIwMjAtMTAtMDY6IGBzZXR1cC1yYCBzY3JpcHQKCi0gVmVyc2lvbiBDb250cm9sCiAgLSBBdXRvbWF0ZSBzZXR1cCBvZiBsb2NhbCBSIGRldmVsb3BtZW50IGVudmlyb25tZW50CgoyMDIwLTEyLTAyOiBBZG9wdGlvbiBvZiBzdHlsZXIgYW5kIGxpbnRyCgotIENvZGUgTWFpbnRhaW5hYmlsaXR5CiAgLSBDb25zaXN0ZW50IGZvcm1hdHRpbmcgKHN0eWxlcikKICAtIENvbnNpc3RlbnQgY29kZSAobGludHIpCi0gQ29udGludW91cyBUZXN0aW5nCiAgLSBTdGF0aWMgY29kZSBhbmFseXNpcyAobGludHIpCgoyMDIwLTEyLTI3OiBNaWdyYXRpb24gdG8gcnRyYWluaW5nIHBhY2thZ2UKCi0gQ29udGludW91cyBJbnRlZ3JhdGlvbgogIC0gQnVpbGQgUGFja2FnZQotIENvbnRpbnVvdXMgVGVzdGluZwogIC0gUiBDTUQgY2hlY2sKLSBWZXJzaW9uIENvbnRyb2wKICAtIC5ScHJvZmlsZQoKMjAyMC0xMi0yOTogYnVpbGQtc2l0ZSBzY3JpcHQKCi0gRGVwbG95bWVudCBBdXRvbWF0aW9uCiAgLSBidWlsZC1zaXRlOiBzaGVsbCBzY3JpcHQgdG8gcHVibGlzaCBub3RlYm9va3MgdXNpbmcgYHJtYXJrZG93bjo6cmVuZGVyX3NpdGUoKWAKICAtIE1WUCBmb3IgcHVibGlzaGluZyBub3RlYm9va3MgdXNpbmcgR2l0SHViIFBhZ2VzCgoyMDIwLTEyLTMwOiBGaXJzdCByZWxlYXNlOiBydHJhaW5pbmcgMC4wLjEKCjIwMjAtMTItMzA6IEdpdEh1YiBBY3Rpb25zCgotIENvbnRpbnVvdXMgSW50ZWdyYXRpb24sIENvbnRpbnVvdXMgVGVzdGluZwogIC0gUi1DTUQtY2hlY2sKICAtIGxpbnRyCgoyMDIwLTEyLTMwOiBgbGludF9hbGwoKWAKCi0gQ29udGludW91cyBJbnRlZ3JhdGlvbiwgQ29udGludW91cyBUZXN0aW5nCiAgLSBsaW50IGFsbCBmaWxlcyBsb2NhbGx5CiAgLSBmaXJzdCB0ZXN0dGhhdCB0ZXN0cwogIC0gcm94eWdlbjIgZG9jdW1lbnRhdGlvbgoKMjAyMC0xMi0zMDogYHN0eWxlX2FsbCgpYAoKLSBDb250aW51b3VzIEludGVncmF0aW9uLCBDb2RlIE1haW50YWluYWJpbGl0eQogIC0gcnVuIHN0eWxlciBvbiBhbGwgZmlsZXMgbG9jYWxseQoKMjAyMC0xMi0zMTogU3dpdGNoIEdpdEh1YiBBY3Rpb25zIHRvIGBsaW50X2FsbCgpYAoKLSBDb250aW51b3VzIEludGVncmF0aW9uLCBDb250aW51b3VzIFRlc3RpbmcKICAtIG1hdGNoIEdpdEh1YiBhbmQgbG9jYWwgQ0kgY2hlY2tzCgoyMDIxLTAxLTAxOiBgY2koKWAsIGBjaGVja19yZW52KClgCgotIENvbnRpbnVvdXMgSW50ZWdyYXRpb24sIENvbnRpbnVvdXMgVGVzdGluZwogIC0gcnVuIGFsbCBDSSBjaGVja3MgbG9jYWxseQogIC0gZWxpbWluYXRlIHRvaWwKICAtIG1hdGNoIEdpdEh1YiBhbmQgbG9jYWwgQ0kgY2hlY2tzCgoyMDIxLTAxLTAxOiBNaWdyYXRpb24gdG8gcmRldiBwYWNrYWdlCgotIENvZGUgTWFpbnRhaW5hYmlsaXR5CiAgLSBNb3ZlZCBtb3N0IGZ1bmN0aW9ucyB0byBuZXcgcmRldiBwYWNrYWdlCiAgLSBDb25zaXN0ZW50IHRvb2xzIGFjcm9zcyBwcm9qZWN0cwoKMjAyMS0wMS0wMjogTXVsdGktcGxhdGZvcm0gUiBDTUQgY2hlY2sKCi0gQ29udGludW91cyBJbnRlZ3JhdGlvbiwgQ29udGludW91cyBUZXN0aW5nCiAgLSBlbnN1cmUgcGFja2FnZSB3b3JrcyBvbiBXaW5kb3dzIGFuZCBtYWNPUwoKMjAyMS0wMS0wMzogRmlyc3QgdmVyc2lvbiBvZiBgYnVpbGRfYW5hbHlzaXNfc2l0ZSgpYAoKLSBEZXBsb3ltZW50IEF1dG9tYXRpb24KICAtIEF1dG9tYXRpY2FsbHkgYnVpbGQgR2l0SHViIFBhZ2VzIHNpdGUgd2l0aCBmdW5jdGlvbnMsIG5vdGVib29rcwogIC0gU3RpbGwgYSBzaGVsbCBzY3JpcHQKICAtIEJlZ2lubmluZyBvZiBzdGFuZGFyZCBkZXBsb3ltZW50IGFuZCByZWxlYXNlIHBhdHRlcm46CiAgICAtIGJ1bXAgdmVyc2lvbgogICAgLSB3cml0ZSBjb2RlCiAgICAtIHVwZGF0ZSBORVdTLm1kCiAgICAtICJHaXRIdWIgUmVsZWFzZSIKICAgIC0gYnVpbGRfc2l0ZQoKMjAyMS0wMS0wOTogQW5hbHlzaXMgUGFja2FnZSBMYXlvdXQKCi0gQ29kZSBNYWludGFpbmFiaWxpdHkKICAtIENvbnNpc3RlbnQgcGFja2FnZSBsYXlvdXQgYWNyb3NzIHByb2plY3RzCiAgLSBTdXBwb3J0ZWQgZnV0dXJlIGF1dG9tYXRpb24gZm9yIGNyZWF0aW5nIHBhY2thZ2VzCgoyMDIxLTAxLTEyOiBOYXRpdmUgUiB2ZXJzaW9uIG9mIGBidWlsZF9hbmFseXNpc19zaXRlKClgCgoyMDIxLTAxLTE2OiBNaWdyYXRlZCBgYnVpbGRfYW5hbHlzaXNfc2l0ZSgpYCBmcm9tIHJ0cmFpbmluZyB0byByZGV2CgotIENvZGUgTWFpbnRhaW5hYmlsaXR5CiAgLSBDcm9zcy1wbGF0Zm9ybSBzdXBwb3J0CiAgLSBNb3ZlcyBhbGwgYXV0b21hdGlvbiB0byBSIENvbnNvbGUKLSBEZXBsb3ltZW50IEF1dG9tYXRpb24KICAtIEF1dG9tYXRlZCBidWlsZHMgYWNyb3NzIGFsbCBwcm9qZWN0cwoKMjAyMS0wOS0yOTogRm9ybWFsIFIgQW5hbHlzaXMgUGFja2FnZSBMYXlvdXQsIERvY3VtZW50ZWQgcmVsZWFzZSBwcm9jZXNzCgotIENvZGUgTWFpbnRhaW5hYmlsaXR5CiAgLSBDb25zaXN0ZW50IHBhY2thZ2UgbGF5b3V0IGFjcm9zcyBwcm9qZWN0cwogIC0gU3VwcG9ydGVkIGZ1dHVyZSBhdXRvbWF0aW9uIGZvciBjcmVhdGluZyBwYWNrYWdlcwotIERlcGxveW1lbnQgQXV0b21hdGlvbgogIC0gU3VwcG9ydGVkIGZ1dHVyZSBhdXRvbWF0aW9uIGZvciBjcmVhdGluZyByZWxlYXNlcwoKMjAyMS0xMi0wNDogRG9jdW1lbnRlZCBwYWNrYWdlIGNyZWF0aW9uIHByb2Nlc3MKCi0gQ29kZSBNYWludGFpbmFiaWxpdHkKICAtIENvbnNpc3RlbnQgcGFja2FnZSBsYXlvdXQgYWNyb3NzIHByb2plY3RzCiAgLSBTdXBwb3J0ZWQgZnV0dXJlIGF1dG9tYXRpb24gZm9yIGNyZWF0aW5nIHBhY2thZ2VzCgoyMDIxLTEyLTIzOiBgdGhlbWVfcXVvKClgOiBhIHBlcnNvbmFsaXplZCB0aGVtZSB0byB2aXN1YWxseSBpZGVudGlmeSBteSBnZ3Bsb3RzLgoKMjAyMi0wMS0wMTogQXV0b21hdGUgcGFja2FnZSBjb25maWd1cmF0aW9uIHdpdGggYHVzZV9hbmFseXNpc19wYWNrYWdlKClgCgotIENvZGUgTWFpbnRhaW5hYmlsaXR5CiAgLSBDb25zaXN0ZW50IHBhY2thZ2UgbGF5b3V0IGFjcm9zcyBwcm9qZWN0cwoKMjAyMi0wMS0xMDogQ3JlYXRlIHBhY2thZ2UgYXV0b21hdGlvbiAocmRldiAwLjcuMCkKCi0gYGNyZWF0ZV9naXRodWJfcmVwbygpYDogQ3JlYXRlIG5ldyBHaXRIdWIgcmVwb3NpdG9yeSBmb2xsb3dpbmcgcmRldiBjb252ZW50aW9ucyBpbiB0aGUgYWN0aXZlCiAgdXNlcidzIGFjY291bnQgYW5kIGNyZWF0ZSBhIGJhc2ljIHBhY2thZ2UKLSBgdXNlX3JkZXZfcGFja2FnZSgpYDogQWRkIHJkZXYgdGVtcGxhdGVzIGFuZCBzZXR0aW5ncyB3aXRoaW4gdGhlIGFjdGl2ZSBwYWNrYWdlLiBOb3JtYWxseSBpbnZva2VkCiAgd2hlbiBmaXJzdCBzZXR0aW5nIHVwIGEgcGFja2FnZS4KLSBBZGRlZCBgYnVpbGRfcmRldl9zaXRlKClgLCBhIHdyYXBwZXIgZm9yIGBwa2dkb3duOjpidWlsZF9zaXRlKClgIG9wdGltaXplZCBmb3IgcmRldiB3b3JrZmxvdyB0aGF0CiAgdXBkYXRlcyBgUkVBRE1FLm1kYCBhbmQgcGVyZm9ybXMgYSBjbGVhbiBidWlsZCB1c2luZyBwa2dkb3duCi0gQWRkZWQgJ0FuYWx5c2lzIE5vdGVib29rJyBSIG1hcmtkb3duIHRlbXBsYXRlIGZvciBSU3R1ZGlvIChGaWxlID4gTmV3IEZpbGUgPiBSbWFya2Rvd24gPiBGcm9tCiAgVGVtcGxhdGUpCi0gTWlncmF0ZWQgZ2dwbG90MiB0aGVtZXMvc3R5bGVzIChgdGhlbWVfcXVvKClgLCBgdmlyaWRpc19xdW8oKWApIHRvIG5ldyBwYWNrYWdlLAogIGBqYWJlbm5pbmdob2ZmL2picGxvdGAKCi0gQ29kZSBNYWludGFpbmFiaWxpdHkKICAtIENyb3NzLXBsYXRmb3JtIHN1cHBvcnQKICAtIE1vdmVzIGFsbCBhdXRvbWF0aW9uIHRvIFIgQ29uc29sZQotIERlcGxveW1lbnQgQXV0b21hdGlvbgogIC0gQXV0b21hdGVkIGJ1aWxkcyBhY3Jvc3MgYWxsIHByb2plY3RzCgoyMDIyLTAxLTEwOiBBdXRvbWF0ZSBub3RlYm9vayBsaXN0aW5ncyBpbiBSRUFETUUKCi0gRGVwbG95bWVudCBBdXRvbWF0aW9uCgpgYGByCmxpYnJhcnkocmRldikKbGlicmFyeShmcykKbGlicmFyeShkcGx5cikKbGlicmFyeShwdXJycikKCm5vdGVib29rcyA8LSBkaXJfbHMoImFuYWx5c2lzIiwgZ2xvYiA9ICIqLlJtZCIpIHw+CiAgbWFwX2RmcihybWRfbWV0YWRhdGEpIHw+CiAgbXV0YXRlKGJ1bGxldCA9IHBhc3RlMCgiLSBbIiwgdGl0bGUsICJdKCIsIHVybCwgIikgKCIsIGRhdGUsICIpOiAiLCBkZXNjcmlwdGlvbikpIHw+CiAgcHVsbChidWxsZXQpCgp3cml0ZUxpbmVzKG5vdGVib29rcykKYGBgCgoyMDIyLTAxLTE3OiBSZWxlYXNlIGF1dG9tYXRpb24gKHJkZXYgMC44LjApCgotIGBzdGFnZV9yZWxlYXNlKClgOiBPcGVuIGEgR2l0SHViIHB1bGwgcmVxdWVzdCBmb3IgYSBuZXcgcmVsZWFzZSBmcm9tIE5FV1MubWQKLSBgbWVyZ2VfcmVsZWFzZSgpYDogTWVyZ2UgYSBzdGFnZWQgcHVsbCByZXF1ZXN0IGFuZCBjcmVhdGUgYSBuZXcgR2l0SHViIHJlbGVhc2UKCi0gRGVwbG95bWVudCBBdXRvbWF0aW9uCgoyMDIyLTAxLTE5OiBNb3JlIHdvcmtmbG93IGF1dG9tYXRpb24KCi0gQWRkZWQgYG5ld19icmFuY2goKWA6IENyZWF0ZSBhIG5ldyBmZWF0dXJlIGJyYW5jaCwgYW5kIChvcHRpb25hbGx5KSBidW1wIHRoZSB2ZXJzaW9uIGluCiAgREVTQ1JJUFRJT04KCjIwMjItMDEtMjEgLSAyMDIyLTAyLTA2OiBhZGRpbmcgdGVzdCBjb3ZlcmFnZQoKLSBDb250aW51b3VzIFRlc3RpbmcKICAtIEJpZ2dlc3QgY2hhbGxlbmdlIHlldAogIC0gU2lnbmlmaWNhbnRseSBpbXByb3ZlZCBjb2RlIHF1YWxpdHkKICAtICJVbml0IiB0ZXN0aW5nCiAgLSBKdXN0IHRlc3QKICAtIFRlc3QgcHJvZ3JhbSBmbG93CiAgLSBEb24ndCB0ZXN0IG90aGVyIHBlb3BsZSdzIGNvZGUKICAtIE1vY2sgZXh0ZXJuYWwgZnVuY3Rpb25zCiAgLSBGaXggYnVncyBieSB3cml0aW5nIGEgdGVzdAogIC0gQ29kZSBjb3ZlcmFnZSwgYW5kIGNvZGUgY292ZXJhZ2UgbWV0cmljcwogIC0gVGVzdCBEcml2ZW4gRGV2ZWxvcG1lbnQKICAtIFRlc3RzIEdpdmUgWW91IENvbmZpZGVuY2UgKHRvIFJlZmFjdG9yKQoKKFNob3cgcGxvdCBvZiBpbmNyZWFzaW5nIGNvZGUgY292ZXJhZ2UgZnJvbSBjb2RlY292LmlvKQoKMjAyMi0wMS0yNDogd3JpdGVfZXZhbCgpIGlzIGEgcmVhbGx5IGJhZCBpZGVhOgoKYGBgcgojJyBXcml0ZSBhbmQgZXZhbHVhdGUgYW4gZXhwcmVzc2lvbgojJwojJyBgd3JpdGVfZXZhbChzdHJpbmcpYCBpcyBhIHNpbXBsZSB3cmFwcGVyIHRoYXQgcHJpbnRzIGBzdHJpbmdgIHRvIHRoZSBjb25zb2xlIHVzaW5nCiMnICAgW2B3cml0ZUxpbmVzKClgXVtiYXNlOjp3cml0ZUxpbmVzXSwgdGhlbiBleGVjdXRlcyB0aGUgZXhwcmVzc2lvbiB1c2luZyBbYHBhcnNlKClgXVtiYXNlOjpwYXJzZV0KIycgICBhbmQgW2BldmFsKClgXVtiYXNlOjpldmFsXS4KIycKIycgQHBhcmFtIHN0cmluZyBBbiBleHByZXNzaW9uIHRvIGJlIHByaW50ZWQgdG8gdGhlIGNvbnNvbGUgYW5kIGV2YWx1YXRlZAojJwojJyBAcmV0dXJuIFRoZSByZXR1cm4gdmFsdWUgZnJvbSB0aGUgZXZhbHVhdGVkIGV4cHJlc3Npb24KIycKIycgQGV4YW1wbGVzCiMnIHdyaXRlX2V2YWwoInBpIikKIycKIycgd3JpdGVfZXZhbCgiZXhwKDEpIikKIycgQGV4cG9ydAp3cml0ZV9ldmFsIDwtIGZ1bmN0aW9uKHN0cmluZykgewogIGlmICghaXMuY2hhcmFjdGVyKHN0cmluZykpIHN0b3AoIm5vdCBhIGNoYXJhY3RlciB2ZWN0b3IiKQogIGlmIChzdHJpbmcgPT0gIiIpIHN0b3AoIm5vdGhpbmcgdG8gZXZhbHVhdGUiKQogIHdyaXRlTGluZXMoc3RyaW5nKQogIGV2YWwocGFyc2UodGV4dCA9IHN0cmluZykpCn0KYGBgCgoyMDIyLTAxLTMwOiBNYW51YWwgdGVzdCBzY3JpcHQgZm9yIG5ldyBwYWNrYWdlIHNldHVwCgotIENvbnRpbnVvdXMgVGVzdGluZwogIC0gTWFudWFsIHRlc3RzIGV2b2x2ZSBpbnRvIHBhcnRpYWxseSBvciBmdWxseSBhdXRvbWF0ZWQgdGVzdHMKCjIwMjItMDItMDI6IEFkZGVkIGBsb2NhbF90ZW1wcGtnKClgIHRlc3QgaGVscGVyIGZ1bmN0aW9uCgotIENvbnRpbnVvdXMgVGVzdGluZwogIC0gVGVzdCBoZWxwZXJzIC0gdGVzdGluZyB0ZXN0IGhlbHBlcnMgaGVscHMhCgoyMDIyLTAyLTA2OiByZGV2IDEuMC4wICEKCi0gUmVsZWFzZSBhdXRvbWF0aW9uOiBTdGFnZSBhbmQgY3JlYXRlIEdpdEh1YiByZWxlYXNlcywgaW5jbHVkaW5nIEdpdEh1YiBwYWdlcwotIENvbnRpbnVvdXMgSW50ZWdyYXRpb246IExvY2FsIGNvbnRpbnVvdXMgaW50ZWdyYXRpb24gY2hlY2tzIGFuZCBkZXBlbmRlbmN5IG1hbmFnZW1lbnQKLSBQYWNrYWdlIFNldHVwOiBQYWNrYWdlIHNldHVwIHRhc2tzLCB0eXBpY2FsbHkgcGVyZm9ybWVkIG9uY2UKCjIwMjItMDItMDYgLSBUb2RheTogQ29udGludW91cyBJbXByb3ZlbWVudAoKLSBJbXByb3ZlIENJIHdvcmtmbG93IHRvIGNhdGNoIG1pc3Rha2VzCi0gU3BlbGwgY2hlY2tzCi0gQnJhbmNoIHByb3RlY3Rpb24gYXV0b21hdGlvbgotIE9wdGlvbnMKLSBEZXBlbmRlbmN5IG1hbmFnZW1lbnQKLSBQcm9kdWN0IGhlYWx0aAoKIyMgUmVsZWFzZXMKCkdldCByZWxlYXNlcyBmcm9tIEdpdEh1YiB1c2luZyBgc2lyYWNvbjIwMjI6OmdoX3JlbGVhc2VzKClgOgoKYGBge3IgcmVsZWFzZXN9CiMgY2FjaGUgcmVzdWx0cwppZiAoIWV4aXN0cygicmVsZWFzZXMiKSkgewogIHJlcG9zIDwtIGMoInJ0cmFpbmluZyIsICJyZGV2IiwgIndvcmtzaG9wNyIsICJqYnBsb3QiLCAic2lyYWNvbjIwMjIiKQogIHJlcG9zIDwtIHNldE5hbWVzKHJlcG9zLCByZXBvcykKICByZWxlYXNlcyA8LSBtYXBfZGZyKHJlcG9zLCBnaF9yZWxlYXNlcywgImphYmVubmluZ2hvZmYiLCAuaWQgPSAicmVwbyIpIHw+CiAgICBhcnJhbmdlKHRpbWUpCn0KYGBgCgpGaWx0ZXIgcmVsZWFzZXMgcGFzdCBBcHJpbCAzMCB0byByZW1vdmUgcGFydGlhbCBtb250aHMuCgpgYGB7ciBmaWx0ZXJlZF9yZWxlYXNlc30KZmlsdGVyZWRfcmVsZWFzZXMgPC0gcmVsZWFzZXMgfD4KICBtdXRhdGUodGltZSA9IHdpdGhfdHoodGltZSwgdHpvbmUgPSBnaXRsb2dzX3R6KSkgfD4KICBmaWx0ZXIodGltZSA8IHltZF9oKCIyMDIyLTA1LTAxIDAiLCB0eiA9IGdpdGxvZ3NfdHopKQpgYGAKClBsb3QgcmVsZWFzZXMgb3ZlciB0aW1lOiB0b3RhbCBHaXRIdWIgcmVsZWFzZXMgcGVyIHBlcmlvZCAoZm9yIGFsbCByZXBvc2l0b3JpZXMpIHRvIHNob3cgY2hhbmdlcyBpbgpyZWxlYXNlIGZyZXF1ZW5jeS4gVGhlIGRvdHRlZCBsaW5lIG1hcmtzIHRoZSBpbXBsZW1lbnRhdGlvbiBvZiByZWxlYXNlIGF1dG9tYXRpb24uCgpgYGB7ciBtb250aGx5X3JlbGVhc2VzfQptb250aGx5X3JlbGVhc2VzIDwtIGZpbHRlcmVkX3JlbGVhc2VzIHw+CiAgbXV0YXRlKHRpbWUgPSBmbG9vcl9kYXRlKHRpbWUsIHVuaXQgPSAibW9udGgiKSkgfD4KICBncm91cF9ieSh0aW1lKSB8PgogIHN1bW1hcml6ZShyZWxlYXNlcyA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikgfD4KICBhZGRfcm93KHRpbWUgPSB5bWQoIjIwMjAtMTEtMDEiKSwgcmVsZWFzZXMgPSAwKSB8PgogIGFkZF9yb3codGltZSA9IHltZCgiMjAyMC0xMC0wMSIpLCByZWxlYXNlcyA9IDApIHw+CiAgYWRkX3Jvdyh0aW1lID0geW1kKCIyMDIwLTA5LTAxIiksIHJlbGVhc2VzID0gMCkgfD4KICBhcnJhbmdlKHRpbWUpCgptb250aGx5X3JlbGVhc2VzIHw+CiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IHJlbGVhc2VzKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IHltZF9oKCIyMDIwLTEyLTAxIDAiLCB0eiA9IGdpdGxvZ3NfdHopLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0geW1kX2goIjIwMjItMDEtMDEgMCIsIHR6ID0gZ2l0bG9nc190eiksIGxpbmV0eXBlID0gImRvdHRlZCIpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwgTkEpKSArCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IEdpdEh1YiByZWxlYXNlcyIpICsKICBsYWJzKHggPSAiIiwgeSA9ICIiKSArCiAgdGhlbWVfcXVvKCkKCmdnc2F2ZSgicmVuZGVyZWQvbW9udGhseS1yZWxlYXNlcy5wbmciLCB3aWR0aCA9IDE2ICogMC42LCBoZWlnaHQgPSA5ICogMC42LCBiZyA9ICJ3aGl0ZSIpCmBgYAoKW0hpZ2ggcmVzb2x1dGlvbiBwbG90XShyZW5kZXJlZC9tb250aGx5LXJlbGVhc2VzLnBuZykKCkhvd2V2ZXIsIHRoZSBudW1iZXIgb2YgcmVsZWFzZXMgcGVyIG1vbnRoIG1pZ2h0IGp1c3QgcmVwcmVzZW50IGhvdyBtdWNoIHdvcmsgaXMgYmVpbmcgZG9uZSwgYW5kCmxvb2tzIHNpbWlsYXIgdG8gdGhlIHBsb3Qgb2YgYWxsIGNvbW1pdHMgYnkgbW9udGg6CgpgYGB7ciBtb250aGx5X2NvbW1pdHN9CmdpdGxvZ3MgfD4KICBmaWx0ZXIodGltZSA8IHltZF9oKCIyMDIyLTA1LTAxIDAiLCB0eiA9IGdpdGxvZ3NfdHopKSB8PgogIG11dGF0ZSh0aW1lID0gZmxvb3JfZGF0ZSh0aW1lLCB1bml0ID0gIm1vbnRoIikpIHw+CiAgZ3JvdXBfYnkodGltZSkgfD4KICBzdW1tYXJpemUoY29tbWl0cyA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikgfD4KICBhcnJhbmdlKHRpbWUpIHw+CiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IGNvbW1pdHMpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0geW1kX2goIjIwMjAtMTItMDEgMCIsIHR6ID0gZ2l0bG9nc190eiksIGxpbmV0eXBlID0gImRvdHRlZCIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSB5bWRfaCgiMjAyMi0wMS0wMSAwIiwgdHogPSBnaXRsb2dzX3R6KSwgbGluZXR5cGUgPSAiZG90dGVkIikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCBOQSkpICsKICBsYWJzKHRpdGxlID0gIk1vbnRobHkgZ2l0IGNvbW1pdHMiKSArCiAgbGFicyh4ID0gIiIsIHkgPSAiIikgKwogIHRoZW1lX3F1bygpCgpnZ3NhdmUoInJlbmRlcmVkL21vbnRobHktY29tbWl0cy5wbmciLCB3aWR0aCA9IDE2ICogMC42LCBoZWlnaHQgPSA5ICogMC42LCBiZyA9ICJ3aGl0ZSIpCmBgYAoKW0hpZ2ggcmVzb2x1dGlvbiBwbG90XShyZW5kZXJlZC9tb250aGx5LWNvbW1pdHMucG5nKQoKQWxzbyBwbG90IHJlbGVhc2VzIHBlciBjb21taXQsIHdoaWNoIHdpbGwgZmFsbCBiZXR3ZWVuIDAgYW5kIDEuIFRoZSBkb3R0ZWQgbGluZXMgbWFyayBhZG9wdGlvbiBvZgpHaXRIdWIgYW5kIGltcGxlbWVudGF0aW9uIG9mIHJlbGVhc2UgYXV0b21hdGlvbi4KCmBgYHtyIHJlbGVhc2VzX3Blcl9jb21taXR9CmdpdGxvZ3MgfD4KICBmaWx0ZXIodGltZSA8IHltZF9oKCIyMDIyLTA1LTAxIDAiLCB0eiA9IGdpdGxvZ3NfdHopKSB8PgogIG11dGF0ZSh0aW1lID0gZmxvb3JfZGF0ZSh0aW1lLCB1bml0ID0gIm1vbnRoIikpIHw+CiAgZ3JvdXBfYnkodGltZSkgfD4KICBzdW1tYXJpemUoY29tbWl0cyA9IG4oKSkgfD4KICBmdWxsX2pvaW4obW9udGhseV9yZWxlYXNlcywgYnkgPSAidGltZSIpIHw+CiAgcmVwbGFjZV9uYShsaXN0KGNvbW1pdHMgPSAwLCByZWxlYXNlcyA9IDApKSB8PgogIG11dGF0ZShycGMgPSByZWxlYXNlcyAvIGNvbW1pdHMpIHw+CiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IHJwYykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSB5bWRfaCgiMjAyMC0xMi0wMSAwIiwgdHogPSBnaXRsb2dzX3R6KSwgbGluZXR5cGUgPSAiZG90dGVkIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IHltZF9oKCIyMDIyLTAxLTAxIDAiLCB0eiA9IGdpdGxvZ3NfdHopLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IEdpdEh1YiByZWxlYXNlcyBwZXIgY29tbWl0IikgKwogIGxhYnMoeCA9ICIiLCB5ID0gIiIpICsKICB0aGVtZV9xdW8oKQoKZ2dzYXZlKCJyZW5kZXJlZC9yZWxlYXNlcy1wZXItY29tbWl0LnBuZyIsIHdpZHRoID0gMTYgKiAwLjYsIGhlaWdodCA9IDkgKiAwLjYsIGJnID0gIndoaXRlIikKYGBgCgpbSGlnaCByZXNvbHV0aW9uIHBsb3RdKHJlbmRlcmVkL3JlbGVhc2VzLXBlci1jb21taXQucG5nKQoKIyBTdG9yeQoKVXNlIHRoZSB0aW1lbGluZSBhbmQgcGxvdHMgdG8gdGVsbCB0aGUgc3Rvcnkgb2YgY29udGludW91cyBpbXByb3ZlbWVudC4gRWFjaCBzZWN0aW9uIGZpbHRlcnMgb24KZ3JvdXAgMSBhbmQgdGhlIG90aGVyIGZvY3VzIGFyZWEuIEludGVncmF0ZSB0aGVtZXMgaW50byBzdG9yeS4KCjEuIEludHJvZHVjdGlvbjogYmFja2dyb3VuZCBhbmQgbW90aXZhdGlvbiwgdXNlIEV2ZW50IGdyb3VwIGFzIHRoZSB0YWxrIG92ZXJ2aWV3LiBFeGNsdWRlIFNpUkFjb24KICAgMjAyMCBmcm9tIGZ1dHVyZSB0aW1lbGluZXMuICJSIERldmVsb3BtZW50IFRpbWVsaW5lIi4KMS4gVmVyc2lvbiBDb250cm9sOiBwdXQgZXZlcnl0aGluZyAoZXhjZXB0IGFydGlmYWN0cykgaW50byB2ZXJzaW9uIGNvbnRyb2wgZm9yIHJlcHJvZHVjaWJpbGl0eSBhbmQKICAgaGlzdG9yeS4KMS4gVHJ1bmstYmFzZWQgRGV2ZWxvcG1lbnQ6IGxpbmVhciBkZXZlbG9wbWVudCBhdm9pZHMgY29kZSBjb25mbGljdHMuCjEuIFNoaWZ0IExlZnQgb24gU2VjdXJpdHk6IG1haW50ZW5hbmNlIGZpcnN0IGVuc3VyZXMgeW91IGdldCBpdCBkb25lLgoxLiBDb250aW51b3VzIEludGVncmF0aW9uOiBidWlsZCBhbmQgdGVzdCBvbiBlYWNoIGNvbW1pdCB0byBjYXRjaCBtaXN0YWtlcyBlYXJseS4KMS4gRGVwbG95bWVudCBBdXRvbWF0aW9uOiBhdXRvbWF0ZSB5b3VyIGRldmVsb3BtZW50IHdvcmtmbG93IHRvIHNwZW5kIG1vcmUgdGltZSB3cml0aW5nLgoxLiBDb2RlIE1haW50YWluYWJpbGl0eTogY29uc2lzdGVudCBhbmQgY2xlYW4gY29kZSBpcyBlYXNpZXIgdG8gdW5kZXJzdGFuZC4KMS4gQ29udGludW91cyBUZXN0aW5nOiAodGhlIGJpZ2dlc3QgY2hhbGxlbmdlKSBmb3JtYWxseSBzcGVjaWZ5aW5nIHdoYXQgeW91IGFyZSBidWlsZGluZyBhbmQgaG93IGl0CiAgIGlzICpzdXBwb3NlZCogdG8gd29yayBkZWZlbmRzIGFnYWluc3QgdGhlIGRhbmdlcnMgb2YgaGlkZGVuIGFzc3VtcHRpb25zLgoxLiBSZXN1bHRzOiAiTW9udGhseSBjb21taXRzIGJ5IHJlcG9zaXRvcnkiLCAiTW9udGhseSBHaXRIdWIgcmVsZWFzZXMiLCAiR2l0SHViIHJlbGVhc2VzIHBlcgogICBjb21taXQiLiBJbXByb3ZlbWVudCBvbiB0ZWNobmljYWwgcHJhY3RpY2VzIGFsc28gbWVhbnMgbGVzcyByZXdvcmssIGxlc3MgZGVwbG95bWVudCBwYWluLCBsZXNzCiAgIGJ1cm5vdXQsIGFuZCBncmVhdGVyIGpvYiBzYXRpc2ZhY3Rpb24uCjEuIENsb3Npbmc6IGNvbXBsZXRlIGtleSBldmVudHMgdGltZWxpbmUuCgpGdWxsIHJkZXYgcGFja2FnZSBsaXN0OgoKLSBgZGVzY2AKLSBgZGV2dG9vbHNgCi0gYGZzYAotIGBnZXJ0YAotIGBnaGAKLSBgbGludHJgCi0gYG1hcmtkb3duYAotIGBtaW5pVUlgCi0gYHBrZ2Rvd25gCi0gYHB1cnJyYAotIGByY21kY2hlY2tgCi0gYHJlbW90ZXNgCi0gYHJlbnZgCi0gYHJsYW5nYAotIGBybWFya2Rvd25gCi0gYHN0eWxlcmAKLSBgdGliYmxlYAotIGB1c2V0aGlzYAotIGB3aXRocmAKLSBgeG1sMmAKLSBgeWFtbGAKLSBgY292cmAKLSBgRFRgCi0gYGtuaXRyYAotIGBtb2NrZXJ5YAotIGBzcGVsbGluZ2AKLSBgc3RyaW5naWAKLSBgdGVzdHRoYXRgCgojIyBJbnRyb2R1Y3Rpb24KCkJhY2tncm91bmQgYW5kIG1vdGl2YXRpb24uIFtGdWxsIHBhZ2UgdmVyc2lvbl0ocmVuZGVyZWQvaW50cm8uaHRtbCkuCgpgYGB7ciBpbnRybywgZmlnLndpZHRoID0gOC42LCBmaWcuaGVpZ2h0ID0gNH0Ka2V5X2V2ZW50cyB8PgogIGZpbHRlcihncm91cCA9PSAxKSB8PgogIHJlbmRlcl90aW1ldmlzKGdyb3VwcyA9IGZpbHRlcihkb3JhX2dyb3VwcywgaWQgPT0gMSksIGZpbGUgPSAicmVuZGVyZWQvaW50cm8uaHRtbCIpCmBgYAoKIyMgVmVyc2lvbiBDb250cm9sCgpQdXQgZXZlcnl0aGluZyAoZXhjZXB0IGFydGlmYWN0cykgaW50byB2ZXJzaW9uIGNvbnRyb2wgZm9yIHJlcHJvZHVjaWJpbGl0eSBhbmQgaGlzdG9yeS4KW0Z1bGwgcGFnZSB2ZXJzaW9uXShyZW5kZXJlZC92ZXJzaW9uLWNvbnRyb2wuaHRtbCkuCgoqVXNlIG9mIFtIb21lYnJld10oaHR0cHM6Ly9icmV3LnNoKSwgYW5kIGBicmV3IGJ1bmRsZWAuKgoKUGFja2FnZXM6CgotIGByZW52YDogZGVwZW5kZW5jeSBtYW5hZ2VtZW50CgpgYGB7ciB2ZXJzaW9uX2NvbnRyb2wsIGZpZy53aWR0aCA9IDguNiwgZmlnLmhlaWdodCA9IDN9CmtleV9ldmVudHMgfD4KICBmaWx0ZXIoIWludHJvKSB8PgogIGZpbHRlcihtaWxlc3RvbmUgfCBncm91cCA9PSAyKSB8PgogIHJlbmRlcl90aW1ldmlzKGdyb3VwcyA9IGZpbHRlcihkb3JhX2dyb3VwcywgaWQgJWluJSBjKDEsIDIpKSwgInJlbmRlcmVkL3ZlcnNpb24tY29udHJvbC5odG1sIikKYGBgCgojIyBUcnVuay1iYXNlZCBEZXZlbG9wbWVudAoKTGluZWFyIGRldmVsb3BtZW50IGF2b2lkcyBjb2RlIGNvbmZsaWN0cy4gW0Z1bGwgcGFnZSB2ZXJzaW9uXShyZW5kZXJlZC90cnVuay1iYXNlZC5odG1sKS4KCmBgYHtyIHRydW5rX2Jhc2VkLCBmaWcud2lkdGggPSA4LjYsIGZpZy5oZWlnaHQgPSAyLjV9CmtleV9ldmVudHMgfD4KICBmaWx0ZXIoIWludHJvKSB8PgogIGZpbHRlcihtaWxlc3RvbmUgfCBncm91cCA9PSAzIHwgaWQgPT0gNDQpIHw+CiAgcmVuZGVyX3RpbWV2aXMoZ3JvdXBzID0gZmlsdGVyKGRvcmFfZ3JvdXBzLCBpZCAlaW4lIGMoMSwgMykpLCBmaWxlID0gInJlbmRlcmVkL3RydW5rLWJhc2VkLmh0bWwiKQpgYGAKCiMjIFNoaWZ0IExlZnQgb24gU2VjdXJpdHkKCk1haW50ZW5hbmNlIGZpcnN0IGVuc3VyZXMgeW91IGdldCBpdCBkb25lLiBbRnVsbCBwYWdlIHZlcnNpb25dKHJlbmRlcmVkL3NoaWZ0LWxlZnQuaHRtbCkuCgoqUmVmZXJlbmNlIGxhc3QgeWVhcidzIHRhbGssIHJlY29yZGluZyBhdmFpbGFibGUgaW4gbWVtYmVyJ3Mgc2VjdGlvbi4qCgpQYWNrYWdlczoKCi0gYHJlbnZgCgpgYGB7ciBzaGlmdF9sZWZ0LCBmaWcud2lkdGggPSA4LjYsIGZpZy5oZWlnaHQgPSAyLjV9CmtleV9ldmVudHMgfD4KICBmaWx0ZXIoIWludHJvKSB8PgogIGZpbHRlcihtaWxlc3RvbmUgfCBncm91cCA9PSA0KSB8PgogIHJlbmRlcl90aW1ldmlzKGdyb3VwcyA9IGZpbHRlcihkb3JhX2dyb3VwcywgaWQgJWluJSBjKDEsIDQpKSwgZmlsZSA9ICJyZW5kZXJlZC9zaGlmdC1sZWZ0Lmh0bWwiKQpgYGAKCiMjIENvbnRpbnVvdXMgSW50ZWdyYXRpb24KCkJ1aWxkIGFuZCB0ZXN0IG9uIGVhY2ggY29tbWl0IHRvIGNhdGNoIG1pc3Rha2VzIGVhcmx5LiBbRnVsbCBwYWdlIHZlcnNpb25dKHJlbmRlcmVkL2NpLmh0bWwpLgoKUGFja2FnZXM6CgotIGBkZXZ0b29sc2AKLSBgdXNldGhpc2AKLSBbci1saWIvYWN0aW9uc10oaHR0cHM6Ly9naXRodWIuY29tL3ItbGliL2FjdGlvbnMpCgpgYGB7ciBjaSwgZmlnLndpZHRoID0gOC42LCBmaWcuaGVpZ2h0ID0gMy41fQprZXlfZXZlbnRzIHw+CiAgZmlsdGVyKCFpbnRybykgfD4KICBmaWx0ZXIobWlsZXN0b25lIHwgZ3JvdXAgPT0gNSkgfD4KICByZW5kZXJfdGltZXZpcyhncm91cHMgPSBmaWx0ZXIoZG9yYV9ncm91cHMsIGlkICVpbiUgYygxLCA1KSksIGZpbGUgPSAicmVuZGVyZWQvY2kuaHRtbCIpCmBgYAoKIyMgRGVwbG95bWVudCBBdXRvbWF0aW9uCgpBdXRvbWF0ZSB5b3VyIGRldmVsb3BtZW50IHdvcmtmbG93IHRvIHNwZW5kIG1vcmUgdGltZSB3cml0aW5nLgpbRnVsbCBwYWdlIHZlcnNpb25dKHJlbmRlcmVkL2RlcGxveW1lbnRzLmh0bWwpLgoKKlJlZHVjaW5nIHRvaWwuIEZvcm1pbmcgaGFiaXRzLCB3aGljaCBiZWNvbWUgcmVwZWF0ZWQgdGFza3MsIHdoaWNoIGJlY29tZSBhdXRvbWF0aW9uLiBJZiBpdCdzCmF1dG9tYXRlZCwgaXQgZ2V0cyBkb25lLioKClBhY2thZ2VzOgoKLSBgcGtnZG93bmAsIGBybWFya2Rvd25gOiBgYnVpbGRfYW5hbHlzaXNfc2l0ZSgpYAotIGBnZXJ0YCwgYGdoYDogZ2l0LCBHaXRIdWIgYXV0b21hdGlvbgotIGBkZXZ0b29sc2AKCmBgYHtyIGRlcGxveW1lbnRzLCBmaWcud2lkdGggPSA4LjYsIGZpZy5oZWlnaHQgPSA0LjV9CmtleV9ldmVudHMgfD4KICBmaWx0ZXIoIWludHJvKSB8PgogIGZpbHRlcihtaWxlc3RvbmUgfCBncm91cCA9PSA2IHwgaWQgPT0gMzIpIHw+CiAgcmVuZGVyX3RpbWV2aXMoZ3JvdXBzID0gZmlsdGVyKGRvcmFfZ3JvdXBzLCBpZCAlaW4lIGMoMSwgNikpLCBmaWxlID0gInJlbmRlcmVkL2RlcGxveW1lbnRzLmh0bWwiKQpgYGAKCiMjIENvZGUgTWFpbnRhaW5hYmlsaXR5CgpDb25zaXN0ZW50IGFuZCBjbGVhbiBjb2RlIGlzIGVhc2llciB0byB1bmRlcnN0YW5kLiBbRnVsbCBwYWdlIHZlcnNpb25dKHJlbmRlcmVkL2NvZGUtbWFpbnQuaHRtbCkuCgoqRnVuY3Rpb25hbCBwcm9ncmFtbWluZyAocHVycnIpIHZzIHByb2NlZHVyYWwgcHJvZ3JhbW1pbmcuIEZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgaXMgaGFyZGVyIHRvCmxlYXJuLCBidXQgc2FmZXIuKgoKKlIgZGlhbGVjdHM6IGJhc2UgUiBpcyBmb3IgZnVuY3Rpb25zLCB0aWR5dmVyc2UgUiBpcyBmb3Igbm90ZWJvb2tzLioKCioiQ2xlYW4iIGNvZGU6IGNvZGUgc2hvdWxkIGJlIHdyaXR0ZW4gZm9yIGZ1dHVyZSBodW1hbnMsIGluY2x1ZGluZyB5b3UhKgoKUGFja2FnZXM6CgotIGBzdHlsZXJgCi0gYHVzZXRoaXNgCi0gYGdoYAotIGBkZXNjYAotIGBkZXZ0b29sc2AKLSBgcHVycnJgCgpgYGB7ciBjb2RlX21haW50LCBmaWcud2lkdGggPSA4LjYsIGZpZy5oZWlnaHQgPSA1fQprZXlfZXZlbnRzIHw+CiAgZmlsdGVyKCFpbnRybykgfD4KICBmaWx0ZXIobWlsZXN0b25lIHwgZ3JvdXAgPT0gNyB8IGlkID09IDMyKSB8PgogIHJlbmRlcl90aW1ldmlzKGdyb3VwcyA9IGZpbHRlcihkb3JhX2dyb3VwcywgaWQgJWluJSBjKDEsIDcpKSwgZmlsZSA9ICJyZW5kZXJlZC9jb2RlLW1haW50Lmh0bWwiKQpgYGAKCiMjIENvbnRpbnVvdXMgVGVzdGluZwoKVGhlIGJpZ2dlc3QgY2hhbGxlbmdlOiBmb3JtYWxseSBzcGVjaWZ5aW5nIHdoYXQgeW91IGFyZSBidWlsZGluZyBhbmQgaG93IGl0IGlzICpzdXBwb3NlZCogdG8gd29yawpkZWZlbmRzIGFnYWluc3QgdGhlIGRhbmdlcnMgb2YgaGlkZGVuIGFzc3VtcHRpb25zLiBbRnVsbCBwYWdlIHZlcnNpb25dKHJlbmRlcmVkL3Rlc3RpbmcuaHRtbCkuCgpQYWNrYWdlczoKCi0gYGxpbnRyYDogc3RhdGljIGNvZGUgYW5hbHlzaXMKLSBgcmNtZGNoZWNrYAotIGB0ZXN0dGhhdGAKLSBgZGV2dG9vbHNgCi0gYGNvdnJgCi0gYG1vY2tlcnlgCi0gYHdpdGhyYAotIGBybGFuZ2AKLSBgc3BlbGxpbmdgCgojIyMgRnV0dXJlIFRlc3RpbmcKCk11dGF0aW9uIFRlc3Rpbmc6IFtXaWtpcGVkaWFdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL011dGF0aW9uX3Rlc3RpbmcpCgpSIHBhY2thZ2VzOgoKLSBbbXV0YW50XShodHRwczovL2dpdGh1Yi5jb20vc2Nrb3R0L211dGFudCkKLSBbYXV0b3Rlc3RdKGh0dHBzOi8vZ2l0aHViLmNvbS9yb3BlbnNjaS1yZXZpZXctdG9vbHMvYXV0b3Rlc3QvKQoKUGFwZXJzOgoKLSBbRG9lcyBtdXRhdGlvbiB0ZXN0aW5nIGltcHJvdmUgdGVzdGluZyBwcmFjdGljZXM/XShodHRwczovL2hvbWVzLmNzLndhc2hpbmd0b24uZWR1L35yanVzdC9wdWJsL211dGF0aW9uX3Rlc3RpbmdfcHJhY3RpY2VzX2ljc2VfMjAyMS5wZGYpCi0gW1ByYWN0aWNhbCBNdXRhdGlvbiBUZXN0aW5nIGF0IFNjYWxlXShodHRwczovL2hvbWVzLmNzLndhc2hpbmd0b24uZWR1L35yanVzdC9wdWJsL3ByYWN0aWNhbF9tdXRhdGlvbl90ZXN0aW5nX3RyXzIwMjEucGRmKQoKRm9ybWFsIE1ldGhvZHM6CgotIFtQbGFubmluZyB3aXRoIGZsYXJlXShodHRwczovL2luY3JlbWVudC5jb20vcGxhbm5pbmcvZm9ybWFsLXNwZWNpZmljYXRpb25zLWFuZC1wbGFubmluZy8pCi0gW0hpbGxlbCBXYXluZV0oaHR0cHM6Ly9oaWxsZWx3YXluZS5jb20pCi0gW0xlYXJuIFRMQStdKGh0dHBzOi8vbGVhcm50bGEuY29tLykKLSBbQWxsb3kgRG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9hbGxveS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvKQoKYGBge3IgdGVzdGluZywgZmlnLndpZHRoID0gOC42LCBmaWcuaGVpZ2h0ID0gMy41fQprZXlfZXZlbnRzIHw+CiAgZmlsdGVyKCFpbnRybykgfD4KICBmaWx0ZXIobWlsZXN0b25lIHwgZ3JvdXAgPT0gOCB8IGlkID09IDMyKSB8PgogIHJlbmRlcl90aW1ldmlzKGdyb3VwcyA9IGZpbHRlcihkb3JhX2dyb3VwcywgaWQgJWluJSBjKDEsIDgpKSwgZmlsZSA9ICJyZW5kZXJlZC90ZXN0aW5nLmh0bWwiKQpgYGAKCkVuZCBvZiAob3V0KWxpbmUuCg==