Outline of my 2022 SiRAcon 2022 presentation, “Making R work for you (with automation!)”.

Questions/TODO

library(siracon2022)
library(readr)
library(purrr)
library(dplyr)
library(tidyr)
library(lubridate)
library(ggplot2)
library(scales)
library(vistime)
library(jbplot)

Framework

Use the DORA Research Program to frame the story of how I learned R and software engineering by implementing the DORA DevOps technical practices:

https://www.devops-research.com/research.html

Create a timeline of my journey using vistime or timevis.

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
    • Build Package
  • Continuous Testing
    • R CMD check
  • Version Control
    • .Rprofile

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
    • R-CMD-check
    • lintr

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

  • Deployment Automation
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)

  • stage_release(): Open a GitHub pull request for a new release from NEWS.md

  • merge_release(): Merge a staged pull request and create a new GitHub release

  • Deployment Automation

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.

  1. Introduction: background and motivation, use Event group as the talk overview. Exclude SiRAcon 2020 from future timelines. “R Development Timeline”.
  2. Version Control: put everything (except artifacts) into version control for reproducibility and history.
  3. Trunk-based Development: linear development avoids code conflicts.
  4. Shift Left on Security: maintenance first ensures you get it done.
  5. Continuous Integration: build and test on each commit to catch mistakes early.
  6. Deployment Automation: automate your development workflow to spend more time writing.
  7. Code Maintainability: consistent and clean code is easier to understand.
  8. 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.
  9. 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.
  10. 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:

  • renv
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==