Time Series Data
A lot of data is collected over time:
The objectives can be
understanding the past – how we got where we are;
understanding patterns in variation;
forecasting what will happen next.
Even if time is not of primary interest, data are often collected
with a time stamp.
Examining the behavior over time can reveal interesting features that
may hint at confounding variables:
Time series data can be collected at regularly spaced intervals:
yearly, e.g. the gapminder data
monthly, e.g. the river data
daily, e.g. high temperatures.
Data might be missing
irregularly, e.g. faulty instrument, holidays;
regularly, e.g. weekends.
Time series can also be recorded at irregular times:
blood pressure at doctor visits;
Variables recorded over time can be numerical or categorical.
Time Series Objects
Time series data can be represented as regular data frames with a
time variable.
There are also a number of specialized object classes for dealing
with time series.
A number of packages provide plot() or
autoplot() methods and other utilities for these
objects.
Some of the most useful:
ts, mts
base
regular series; methods for plot() and
lines().
forecast
methods for autoplot() and
autolayer().
zoo
zoo
irregular and regular series; autoplot() support.
broom
tidy() for converting to tidy data frames.
autoplot() and autolayer()
Many data structures provide methods for autoplot() and
autolayer().
From the autoplot() help page:
autoplot uses ggplot2 to draw a particular
plot for an object of a particular class in a single command. This
defines the S3 generic that other classes and packages can extend.
From the autolayer() help page:
autolayer uses ggplot2 to draw a particular
layer for an object of a particular class in a single command. This
defines the S3 generic that other classes and packages can extend.
Example: River Data
Creating a ts object for the river
data:
river <- scan("https://www.stat.uiowa.edu/~luke/data/river.dat")
(riverTS <- ts(river))
## Time Series:
## Start = 1
## End = 128
## Frequency = 1
## [1] 8.95361 9.49409 10.19430 10.95660 11.07770 10.98170 10.48970 10.27540
## [9] 10.16620 9.23484 8.56537 8.51064
## [ reached 'max' / getOption("max.print") -- omitted 116 entries ]
Or, since the data were collected monthly:
(riverTS <- ts(river, frequency = 12))
## Jan Feb Mar Apr May Jun Jul Aug
## 1 8.95361 9.49409 10.19430 10.95660 11.07770 10.98170 10.48970 10.27540
## Sep Oct Nov Dec
## 1 10.16620 9.23484 8.56537 8.51064
## [ reached 'max' / getOption("max.print") -- omitted 10 rows ]
Using tidy from package broom to create a
data frame from the ts object:
riverDF <- broom::tidy(riverTS)
head(riverDF, 3)
## # A tibble: 3 × 2
## index value
## <dbl> <dbl>
## 1 1 8.95
## 2 1.08 9.49
## 3 1.17 10.2
Change to more convenient names:
riverDF <- rename(riverDF, time = index, flow = value)
head(riverDF, 3)
## # A tibble: 3 × 2
## time flow
## <dbl> <dbl>
## 1 1 8.95
## 2 1.08 9.49
## 3 1.17 10.2
Basic Plots for a Single Numeric Time Series
Numeric time series are usually plotted as a line chart .
This is what the autoplot() method for ts
objects provided by package forecast does.
For the river flow data:
library(forecast)
p <- autoplot(riverTS)
p
Using symbols in addition to lines is sometimes useful:
p + geom_point()
Aspect Ratio
Aspect ratio has a significant effect on how well we perceive slope
differences.
Controlling the aspect ratio:
For base graphics use the asp argument
to plot().
lattice uses the aspect
argument.
For ggplot use the ratio argument to
coord_fixed().
p + coord_fixed(ratio = 1 / 3)
Lattice supports several aspect ratio strategies :
"fill" to avoid unused white space;
"xy" to use a 45 degree banking rule;
"iso" for isometric scales, e.g. distances.
Some work may be needed to avoid excess white space around
figures.
Using multiple panels can help.
In an interactive setting, zoom and pan support can help.
Multiple panels:
mutate(riverDF,
period = cut(time, breaks = 3)) |>
ggplot(aes(time, flow)) +
geom_line() +
facet_wrap(~ period,
scales = "free_x",
ncol = 1)
Adding zoom and pan support using plotly :
library(plotly)
pp <- p + geom_line() + coord_fixed(ratio = 1 / 3)
ggplotly(pp)
There is not always a single best aspect ratio.
The co2 data set in the datasets package
contains monthly concentrations of CO2 for the years 1959 to 1997
recorded at the Mauna Loa observatory.
The co2 data is stored as an object of class
ts:
str(co2)
## Time-Series [1:468] from 1959 to 1998: 315 316 316 318 318 ...
plot() and xyplot() have methods for
ts objects that simplify time series plotting.
Two aspect ratios:
xyplot(co2)
xyplot(co2, aspect = "xy")
Is there a slowdown in the increase?
A graph with more recent data is available .
The full data is available
as well.
autoplot() does not yet provide an easy way to bank to
45 degrees; you need to experiment or calculate an appropriate aspect
ratio yourself.
The default autoplot:
library(forecast)
autoplot(co2)
Aspect ratio adjusted with coord_fixed():
r45 <- median(diff(time(co2))) / median(abs(diff(co2)))
autoplot(co2) + coord_fixed(ratio = r45)
Updated Mauna Loa CO2 Data
Downloading and reading in the data:
library(lubridate)
url <- "https://gml.noaa.gov/webdata/ccgg/trends/co2/co2_mm_mlo.txt"
if (! file.exists("co2new.dat") ||
now() > file.mtime("co2new.dat") + weeks(4))
download.file(url, "co2new.dat")
co2new <- read.table("co2new.dat")
Clean up the variable names:
head(co2new)
## V1 V2 V3 V4 V5 V6 V7 V8
## 1 1958 3 1958.203 315.71 314.44 -1 -9.99 -0.99
## 2 1958 4 1958.288 317.45 315.16 -1 -9.99 -0.99
## 3 1958 5 1958.370 317.51 314.69 -1 -9.99 -0.99
## 4 1958 6 1958.455 317.27 315.15 -1 -9.99 -0.99
## 5 1958 7 1958.537 315.87 315.20 -1 -9.99 -0.99
## 6 1958 8 1958.622 314.93 316.21 -1 -9.99 -0.99
names(co2new) <- c("year", "month", "decimal_date", "average",
"deseason", "ndays", "stdd", "umm")
An initial look:
ggplot(co2new, aes(decimal_date, average)) + geom_line()
Missing values are encoded as -9.99 or
-99.99.
If there are any such values they need to be replaced with R’s
missing value representation:
filter(co2new, average < -9)
## [1] year month decimal_date average deseason
## [6] ndays stdd umm
## <0 rows> (or 0-length row.names)
co2new <- mutate(co2new, average = ifelse(average < -9, NA, average))
This produces a reasonable plot:
ggplot(co2new, aes(decimal_date, average)) +
geom_line()
The plot would show gaps for the missing values.
Missing values could also be interpolated with na.interp
from package forecast.
Converting the new data to a ts object makes adding the
original data a little easier:
co2newTS <- ts(co2new$average, start = c(1958, 3), frequency = 12)
autoplot(co2newTS) + autolayer(co2)
Seasonal Decomposition of Time Series by Loess (STL)
For time series with a strong seasonal component it can be useful to
look at a Seasonal Decomposition of Time Series by Loess , or
(STL ).
The methodology was suggested by Clevaland and coworkers.
The stl() function in the base package
computes such a decomposition.
It requires a series without missing values.
co2newTS_no_NA <- ts(co2new$average, start = c(1958, 3), frequency = 12)
co2stl <- stl(co2newTS_no_NA, s.window = 21)
class(co2stl)
## [1] "stl"
head(co2stl$time.series)
## seasonal trend remainder
## Mar 1958 1.1159067 314.9426 -0.3484682
## Apr 1958 2.2797228 315.0143 0.1559363
## May 1958 2.7672728 315.0861 -0.3433932
## Jun 1958 2.2272653 315.1579 -0.1151651
## Jul 1958 0.8611775 315.2241 -0.2153045
## Aug 1958 -1.0848270 315.2904 0.7244728
Package forecast provides an autoplot()
method for stl objects that shows the original data and the
three components in separate panels:
autoplot(co2stl)
Some Alternative Visualizations
Bar charts are sometimes used when comparison to a base line is
important; for example for illustrating climate change .
Calendar plots, or calendar heatmaps, are sometimes useful for
displaying daily data where day of the week/weekend effects might be
important. One example is available here .
A blog
post shows how to build a calendar plot with
ggplot.
GDP Growth Data
Bar charts are sometimes used for economic time series, in particular
growth rate data.
Some GDP growth rate data is available on this web
page :
Read the data from the web page:
library(rvest)
gdpurl <- "http://www.multpl.com/us-real-gdp-growth-rate/table/by-quarter"
gdppage <- read_html(gdpurl)
gdptbl <- html_table(gdppage)[[1]]
head(gdptbl)
## # A tibble: 6 × 2
## Date Value
## <chr> <chr>
## 1 Dec 31, 2025 2.23%
## 2 Sep 30, 2025 2.34%
## 3 Jun 30, 2025 2.08%
## 4 Mar 31, 2025 2.02%
## 5 Dec 31, 2024 2.40%
## 6 Sep 30, 2024 2.79%
Clean the data:
library(dplyr)
library(lubridate)
names(gdptbl) <- c("Date", "Value")
gdptbl <- mutate(gdptbl,
Value = as.numeric(sub("%", "", Value)),
Date = mdy(Date))
head(gdptbl)
## # A tibble: 6 × 2
## Date Value
## <date> <dbl>
## 1 2025-12-31 2.23
## 2 2025-09-30 2.34
## 3 2025-06-30 2.08
## 4 2025-03-31 2.02
## 5 2024-12-31 2.4
## 6 2024-09-30 2.79
Data prior to 1947 is annual, so drop earlier data:
gdptbl <- filter(gdptbl,
Date >= make_date(1948, 1, 1))
Rearrange to put Date in increasing order:
gdptbl <- arrange(gdptbl, Date)
Create and plot a time series:
library(ggplot2)
library(forecast)
gdpts <- ts(gdptbl$Value,
start = 1948, frequency = 4)
autoplot(gdpts) +
geom_hline(yintercept = 0, linetype = 2)
A bar chart with color used to distinguish positive and negative
growth can be created from the original table gdptbl:
library(ggplot2)
mutate(gdptbl,
growth = ifelse(Value >= 0,
"pos",
"neg")) |>
ggplot(aes(x = Date,
y = Value,
fill = growth)) +
geom_col() +
theme(legend.position = "top") +
scale_fill_manual(
values = c(pos = "blue", neg = "red"))
The bar chart can also be created from the time series object:
data.frame(rate = as.numeric(gdpts),
time = as.numeric(time(gdpts))) |>
mutate(growth = ifelse(rate >= 0,
"pos",
"neg")) |>
ggplot(aes(time, rate, fill = growth)) +
geom_col() +
theme(legend.position = "top") +
scale_fill_manual(
values = c(pos = "blue", neg = "red"))
Airline Flights From NYC
The nycflights13 data set contains data on all flight
leaving New York City in 2013.
Similar data is available for other cities and years, e.g. from the
anyflights
package .
We can look at the number of departures for each day:
library(nycflights13)
library(ggplot2)
nf <- count(flights, year, month, day)
It will be useful to have a combined date field in our
data frame.
The lubridate function make_date() can be
used for this:
library(lubridate)
nf <- mutate(nf, date = make_date(year, month, day))
A line plot of the number of flights:
ggplot(nf, aes(date, n)) +
geom_line()
Weekday variation is clearly visible.
Breaking out the days of the week into separate facets may help:
nnf <-
mutate(nf, wd = wday(date, label = TRUE))
nnf_thm <-
theme(axis.text.x =
element_text(angle = -45,
hjust = 0),
plot.margin = margin(r = 30))
ggplot(nnf, aes(date, n)) +
geom_line() +
facet_wrap(~ wd) +
nnf_thm
July 4 in 2013:
as.character(wday(ymd("2013-07-04"),
label = TRUE))
## [1] "Thu"
Identifying when some major holidays occur may also be useful:
holidays <-
data.frame(Holiday = c("4th of July",
"Thanksgiving"),
date = ymd(c("2013-07-04",
"2013-11-28")))
hlayer <-
geom_vline(aes(xintercept = date,
color = Holiday),
linetype = 2,
linewidth = 1,
data = holidays)
ggplot(nf, aes(date, n)) +
hlayer +
geom_line()
Holidays in the faceted plot:
ggplot(nnf, aes(date, n)) +
hlayer +
geom_line() +
facet_wrap(~ wd) +
nnf_thm
This marks the holiday weeks.
Holidays in the faceted plot:
This shows only the holiday days.
hlayer2 <-
geom_vline(aes(xintercept = date,
color = Holiday),
linewidth = 1,
data = mutate(holidays,
wd = wday(date,
TRUE)))
ggplot(nnf, aes(date, n)) +
hlayer2 +
geom_line() +
facet_wrap(~ wd) +
nnf_thm
Holidays in the faceted plot:
Show holidays as solid lines, days in holiday weeks as dashed lines.
ggplot(nnf, aes(date, n)) +
hlayer +
hlayer2 +
geom_line() +
facet_wrap(~ wd) +
nnf_thm
A simple calendar plot:
This uses geom_tile() and faceting.
monthweek <- function(d, w)
ceiling((d - w) / 7) + 1
nf <- mutate(nf,
wd = wday(date, label = TRUE),
wd = fct_rev(wd),
mw = monthweek(day, wday(date)))
p <- ggplot(nf, aes(x = as.character(mw),
y = wd,
fill = n)) +
geom_tile(color = "white") +
facet_wrap(~ month(date, label = TRUE))
p
Some theme and color adjustments:
p <- p +
scale_fill_gradient(low = "white") +
ylab("") + xlab("Week of Month") +
theme(panel.grid.major = element_blank(),
panel.border = element_blank())
p
This layout with days on the vertical axis works well with a single
row, which could be extended to a multi-year plot:
p + facet_wrap(~ month(date, TRUE), nrow = 1)
A more standard layout:
nf2 <-
mutate(nf,
wd = wday(date, label = TRUE),
mw = factor(monthweek(day,
wday(date))),
mw = fct_rev(mw))
wdlabs <-substr(levels(nf2$wd), 1, 1)
ggplot(nf2, aes(x = wd, y = mw, fill = n)) +
geom_tile(color = "white") +
facet_wrap(~ month(date, TRUE)) +
scale_fill_gradient(low = "white") +
xlab("") + ylab("Week of Month") +
scale_x_discrete(labels = wdlabs) +
theme(panel.grid.major = element_blank(),
panel.border = element_blank())
Exercises
A useful feature of the lattice package is the
ability to easily request an aspect ratio that is banked to 45
degrees by specifying aspect = "xy" in the arguments
to lattice plotting functions. For example, This code
produces a reasonable aspect ratio for a plot of an unemployment time
series:
library(ggplot2)
library(forecast)
library(lattice)
unemp_ts <- ts(economics$unemploy, start = c(1967, 7), frequency = 12)
xyplot(unemp_ts, aspect = "xy")
Which of the following plots most closely matches the aspect ratio in
the lattice plot?
autoplot(unemp_ts, aspect = 200)
autoplot(unemp_ts) + coord_fixed(ratio = 0.0007)
autoplot(unemp_ts) + coord_fixed(asp = 0.0007)
autoplot(unemp_ts) + coord_fixed(70)
LS0tCnRpdGxlOiAiVGltZSBTZXJpZXMgUGxvdHMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0ic3RhdDQ1ODAuY3NzIiB0eXBlPSJ0ZXh0L2NzcyIgLz4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4gLnJlbWFyay1jb2RlIHsgZm9udC1zaXplOiA4NSU7IH0gPC9zdHlsZT4KYGBge3Igc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Kc291cmNlKGhlcmU6OmhlcmUoInNldHVwLlIiKSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGNvbGxhcHNlID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aCA9IDYsIGZpZy5hbGlnbiA9ICJjZW50ZXIiKQoKb3B0aW9ucyhodG1sdG9vbHMuZGlyLnZlcnNpb24gPSBGQUxTRSkKCnNldC5zZWVkKDEyMzQ1KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobGF0dGljZSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KHBhdGNod29yaykKdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSArCiAgICAgICAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiZ3JleTMwIiwgZmlsbCA9IE5BKSkpCmBgYAoKCiMjIFRpbWUgU2VyaWVzIERhdGEKCkEgbG90IG9mIGRhdGEgaXMgY29sbGVjdGVkIG92ZXIgdGltZToKCi0gZWNvbm9taWMgcGVyZm9ybWFjZSBtZWFzdXJlczsKCi0gd2VhdGhlciwgY2xpbWF0ZSBkYXRhOwoKLSBtZWRpY2FsIGluZm9ybWF0aW9uLgoKVGhlIG9iamVjdGl2ZXMgY2FuIGJlCgotIHVuZGVyc3RhbmRpbmcgdGhlIHBhc3QgJm5kYXNoOyBob3cgd2UgZ290IHdoZXJlIHdlIGFyZTsKCi0gdW5kZXJzdGFuZGluZyBwYXR0ZXJucyBpbiB2YXJpYXRpb247CgotIGZvcmVjYXN0aW5nIHdoYXQgd2lsbCBoYXBwZW4gbmV4dC4KCkV2ZW4gaWYgdGltZSBpcyBub3Qgb2YgcHJpbWFyeSBpbnRlcmVzdCwgZGF0YSBhcmUgb2Z0ZW4gY29sbGVjdGVkIHdpdGgKYSB0aW1lIHN0YW1wLgoKRXhhbWluaW5nIHRoZSBiZWhhdmlvciBvdmVyIHRpbWUgY2FuIHJldmVhbCBpbnRlcmVzdGluZyBmZWF0dXJlcyB0aGF0Cm1heSBoaW50IGF0IGNvbmZvdW5kaW5nIHZhcmlhYmxlczoKCi0gdGVtcGVyYXR1cmUgdmFyeWluZyB3aXRoIHRpbWUgb2YgZGF5OwoKLSBodW1pZGl0eSB2YXJ5aW5nIG92ZXIgdGhlIHllYXIuCgpUaW1lIHNlcmllcyBkYXRhIGNhbiBiZSBjb2xsZWN0ZWQgYXQgcmVndWxhcmx5IHNwYWNlZCBpbnRlcnZhbHM6CgotIHllYXJseSwgZS5nLiB0aGUgYGdhcG1pbmRlcmAgZGF0YQoKLSBtb250aGx5LCBlLmcuIHRoZSBgcml2ZXJgIGRhdGEKCi0gZGFpbHksIGUuZy4gaGlnaCB0ZW1wZXJhdHVyZXMuCgpEYXRhIG1pZ2h0IGJlIG1pc3NpbmcKCi0gaXJyZWd1bGFybHksIGUuZy4gZmF1bHR5IGluc3RydW1lbnQsIGhvbGlkYXlzOwoKLSByZWd1bGFybHksIGUuZy4gd2Vla2VuZHMuCgpUaW1lIHNlcmllcyBjYW4gYWxzbyBiZSByZWNvcmRlZCBhdCBpcnJlZ3VsYXIgdGltZXM6CgotIGJsb29kIHByZXNzdXJlIGF0IGRvY3RvciB2aXNpdHM7CgpWYXJpYWJsZXMgcmVjb3JkZWQgb3ZlciB0aW1lIGNhbiBiZSBudW1lcmljYWwgb3IgY2F0ZWdvcmljYWwuCgoKIyMgVGltZSBTZXJpZXMgT2JqZWN0cwoKVGltZSBzZXJpZXMgZGF0YSBjYW4gYmUgcmVwcmVzZW50ZWQgYXMgcmVndWxhciBkYXRhIGZyYW1lcyB3aXRoIGEgdGltZQp2YXJpYWJsZS4KClRoZXJlIGFyZSBhbHNvIGEgbnVtYmVyIG9mIHNwZWNpYWxpemVkIG9iamVjdCBjbGFzc2VzIGZvciBkZWFsaW5nIHdpdGggdGltZQpzZXJpZXMuCgpBIG51bWJlciBvZiBwYWNrYWdlcyBwcm92aWRlIGBwbG90KClgIG9yIGBhdXRvcGxvdCgpYCBtZXRob2RzIGFuZCBvdGhlcgp1dGlsaXRpZXMgZm9yIHRoZXNlIG9iamVjdHMuCgpTb21lIG9mIHRoZSBtb3N0IHVzZWZ1bDoKCnwgQ2xhc3MgICAgICB8ICBQYWNrYWdlICAgfCBGZWF0dXJlcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8CnxgdHNgLCBgbXRzYHwgYGJhc2VgICAgICB8IHJlZ3VsYXIgc2VyaWVzOyBtZXRob2RzIGZvciBgcGxvdCgpYCBhbmQgYGxpbmVzKClgLiAgIHwKfCAgICAgICAgICAgfCBgZm9yZWNhc3RgIHwgbWV0aG9kcyBmb3IgYGF1dG9wbG90KClgIGFuZCBgYXV0b2xheWVyKClgLiAgICAgICAgICB8Cnxgem9vYCAgICAgIHwgIGB6b29gICAgICB8IGlycmVndWxhciBhbmQgcmVndWxhciBzZXJpZXM7IGBhdXRvcGxvdCgpYCBzdXBwb3J0LiB8CnwgICAgICAgICAgIHwgYGJyb29tYCAgICB8YHRpZHkoKWAgZm9yIGNvbnZlcnRpbmcgdG8gdGlkeSBkYXRhIGZyYW1lcy4gICAgICAgICB8CgoKIyMgYGF1dG9wbG90KClgIGFuZCBgYXV0b2xheWVyKClgCgpNYW55IGRhdGEgc3RydWN0dXJlcyBwcm92aWRlIG1ldGhvZHMgZm9yIGBhdXRvcGxvdCgpYCBhbmQgYGF1dG9sYXllcigpYC4KCkZyb20gdGhlIGBhdXRvcGxvdCgpYCBoZWxwIHBhZ2U6Cgo+IGBhdXRvcGxvdGAgdXNlcyBgZ2dwbG90MmAgdG8gZHJhdyBhIHBhcnRpY3VsYXIgcGxvdCBmb3IgYW4gb2JqZWN0IG9mCj4gYSBwYXJ0aWN1bGFyIGNsYXNzIGluIGEgc2luZ2xlIGNvbW1hbmQuIFRoaXMgZGVmaW5lcyB0aGUgUzMgZ2VuZXJpYwo+IHRoYXQgb3RoZXIgY2xhc3NlcyBhbmQgcGFja2FnZXMgY2FuIGV4dGVuZC4KCkZyb20gdGhlIGBhdXRvbGF5ZXIoKWAgaGVscCBwYWdlOgoKPiBgYXV0b2xheWVyYCB1c2VzIGBnZ3Bsb3QyYCB0byBkcmF3IGEgcGFydGljdWxhciBsYXllciBmb3IgYW4gb2JqZWN0Cj4gb2YgYSBwYXJ0aWN1bGFyIGNsYXNzIGluIGEgc2luZ2xlIGNvbW1hbmQuIFRoaXMgZGVmaW5lcyB0aGUgUzMKPiBnZW5lcmljIHRoYXQgb3RoZXIgY2xhc3NlcyBhbmQgcGFja2FnZXMgY2FuIGV4dGVuZC4KCgojIyBFeGFtcGxlOiBSaXZlciBEYXRhCgpDcmVhdGluZyBhIGB0c2Agb2JqZWN0IGZvciB0aGUgYHJpdmVyYCBkYXRhOgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kb3AgPC0gb3B0aW9ucyhtYXgucHJpbnQgPSAxMikKYGBgCmBgYHtyfQpyaXZlciA8LSBzY2FuKCJodHRwczovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL3JpdmVyLmRhdCIpCihyaXZlclRTIDwtIHRzKHJpdmVyKSkKYGBgCgpPciwgc2luY2UgdGhlIGRhdGEgd2VyZSBjb2xsZWN0ZWQgbW9udGhseToKCmBgYHtyfQoocml2ZXJUUyA8LSB0cyhyaXZlciwgZnJlcXVlbmN5ID0gMTIpKQpgYGAKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kb3B0aW9ucyhvcCkKYGBgCgpVc2luZyBgdGlkeWAgZnJvbSBwYWNrYWdlIGBicm9vbWAgdG8gY3JlYXRlIGEgZGF0YSBmcmFtZSBmcm9tIHRoZSBgdHNgIG9iamVjdDoKCmBgYHtyfQpyaXZlckRGIDwtIGJyb29tOjp0aWR5KHJpdmVyVFMpCmhlYWQocml2ZXJERiwgMykKYGBgCgpDaGFuZ2UgdG8gbW9yZSBjb252ZW5pZW50IG5hbWVzOgoKYGBge3J9CnJpdmVyREYgPC0gcmVuYW1lKHJpdmVyREYsIHRpbWUgPSBpbmRleCwgZmxvdyA9IHZhbHVlKQpoZWFkKHJpdmVyREYsIDMpCmBgYAoKCiMjIEJhc2ljIFBsb3RzIGZvciBhIFNpbmdsZSBOdW1lcmljIFRpbWUgU2VyaWVzCgpOdW1lcmljIHRpbWUgc2VyaWVzIGFyZSB1c3VhbGx5IHBsb3R0ZWQgYXMgYSBfbGluZSBjaGFydF8uCgpUaGlzIGlzIHdoYXQgdGhlIGBhdXRvcGxvdCgpYCBtZXRob2QgZm9yIGB0c2Agb2JqZWN0cyBwcm92aWRlZCBieQpwYWNrYWdlIGBmb3JlY2FzdGAgZG9lcy4KCkZvciB0aGUgcml2ZXIgZmxvdyBkYXRhOgoKYGBge3Igcml2ZXItdHMtYXV0b3Bsb3QsIGVjaG8gPSBGQUxTRX0KbGlicmFyeShmb3JlY2FzdCkKcCA8LSBhdXRvcGxvdChyaXZlclRTKQpwCmBgYAoKYGBge3Igcml2ZXItdHMtYXV0b3Bsb3QsIGV2YWwgPSBGQUxTRX0KYGBgCgpVc2luZyBzeW1ib2xzIGluIGFkZGl0aW9uIHRvIGxpbmVzIGlzIHNvbWV0aW1lcyB1c2VmdWw6CgpgYGB7ciByaXZlci10cy1hdXRvcGxvdC1zeW1zLCBlY2hvID0gRkFMU0V9CnAgKyBnZW9tX3BvaW50KCkKYGBgCgpgYGB7ciByaXZlci10cy1hdXRvcGxvdC1zeW1zLCBldmFsID0gRkFMU0V9CmBgYAoKCiMjIEFzcGVjdCBSYXRpbwoKQXNwZWN0IHJhdGlvIGhhcyBhIHNpZ25pZmljYW50IGVmZmVjdCBvbiBob3cgd2VsbCB3ZSBwZXJjZWl2ZSBzbG9wZQpkaWZmZXJlbmNlcy4KCi0gQmFua2luZyB0byA0NSBkZWdyZWVzOiBjaG9vc2UgYW4gYXNwZWN0IHJhdGlvIHNvIHRoZSBtYWduaXR1ZGUgb2YKICB0aGUgaW1wb3J0YW50IHNsb3BlcyBpcyBjbG9zZSB0byA0NSBkZWdyZWVzLgoKLSBUaGlzIHVzdWFsbHkgcmVxdWlyZXMgdGhlIGhvcml6b250YWwgYXhpcyB0byBiZSBsb25nZXIgdGhhbiB0aGUKICB2ZXJ0aWNhbCBheGlzLgoKQ29udHJvbGxpbmcgdGhlIGFzcGVjdCByYXRpbzoKCi0gRm9yIGBiYXNlYCBncmFwaGljcyB1c2UgdGhlIGBhc3BgIGFyZ3VtZW50IHRvIGBwbG90KClgLgoKLSBgbGF0dGljZWAgdXNlcyB0aGUgYGFzcGVjdGAgYXJndW1lbnQuCgotIEZvciBgZ2dwbG90YCB1c2UgdGhlIGByYXRpb2AgYXJndW1lbnQgdG8gYGNvb3JkX2ZpeGVkKClgLgoKYGBge3IsIGZpZy5oZWlnaHQgPSAzLCBmaWcud2lkdGggPSAxMH0KcCArIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSAvIDMpCmBgYAoKTGF0dGljZSBzdXBwb3J0cyBzZXZlcmFsIGFzcGVjdCByYXRpbyBfc3RyYXRlZ2llc186CgotIGAiZmlsbCJgIHRvIGF2b2lkIHVudXNlZCB3aGl0ZSBzcGFjZTsKCi0gYCJ4eSJgIHRvIHVzZSBhIDQ1IGRlZ3JlZSBiYW5raW5nIHJ1bGU7CgotIGAiaXNvImAgZm9yIGlzb21ldHJpYyBzY2FsZXMsIGUuZy4gZGlzdGFuY2VzLgoKU29tZSB3b3JrIG1heSBiZSBuZWVkZWQgdG8gYXZvaWQgZXhjZXNzIHdoaXRlIHNwYWNlIGFyb3VuZCBmaWd1cmVzLgoKVXNpbmcgbXVsdGlwbGUgcGFuZWxzIGNhbiBoZWxwLgoKSW4gYW4gaW50ZXJhY3RpdmUgc2V0dGluZywgem9vbSBhbmQgcGFuIHN1cHBvcnQgY2FuIGhlbHAuCgpNdWx0aXBsZSBwYW5lbHM6CgpgYGB7ciByaXZlci10cy1wYW5lbHMsIGVjaG8gPSBGQUxTRX0KbXV0YXRlKHJpdmVyREYsCiAgICAgICBwZXJpb2QgPSBjdXQodGltZSwgYnJlYWtzID0gMykpIHw+CiAgICBnZ3Bsb3QoYWVzKHRpbWUsIGZsb3cpKSArCiAgICBnZW9tX2xpbmUoKSArCiAgICBmYWNldF93cmFwKH4gcGVyaW9kLAogICAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZV94IiwKICAgICAgICAgICAgICAgbmNvbCA9IDEpCmBgYAoKYGBge3Igcml2ZXItdHMtcGFuZWxzLCBldmFsID0gRkFMU0V9CmBgYAoKQWRkaW5nIHpvb20gYW5kIHBhbiBzdXBwb3J0IHVzaW5nIFtgcGxvdGx5YF0oaHR0cHM6Ly9wbG90bHkuY29tL3IvKToKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CmxpYnJhcnkocGxvdGx5KQpgYGAKYGBge3IsICwgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDEwfQpsaWJyYXJ5KHBsb3RseSkKcHAgPC0gcCArIGdlb21fbGluZSgpICsgY29vcmRfZml4ZWQocmF0aW8gPSAxIC8gMykKZ2dwbG90bHkocHApCmBgYAoKVGhlcmUgaXMgbm90IGFsd2F5cyBhIHNpbmdsZSBiZXN0IGFzcGVjdCByYXRpby4KClRoZSBgY28yYCBkYXRhIHNldCBpbiB0aGUgYGRhdGFzZXRzYCBwYWNrYWdlIGNvbnRhaW5zIG1vbnRobHkKY29uY2VudHJhdGlvbnMgb2YgQ08yIGZvciB0aGUgeWVhcnMgMTk1OSB0byAxOTk3IHJlY29yZGVkIGF0IHRoZSBNYXVuYQpMb2Egb2JzZXJ2YXRvcnkuCgpUaGUgYGNvMmAgZGF0YSBpcyBzdG9yZWQgYXMgYW4gb2JqZWN0IG9mIGNsYXNzIGB0c2A6CmBgYHtyfQpzdHIoY28yKQpgYGAKCmBwbG90KClgIGFuZCBgeHlwbG90KClgIGhhdmUgbWV0aG9kcyBmb3IgYHRzYCBvYmplY3RzIHRoYXQgc2ltcGxpZnkgdGltZQpzZXJpZXMgcGxvdHRpbmcuCgpUd28gYXNwZWN0IHJhdGlvczoKCmBgYHtyICwgZmlnLmhlaWdodCA9IDQuNSwgZmlnLndpZHRoID0gNS41fQp4eXBsb3QoY28yKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gMTB9Cnh5cGxvdChjbzIsIGFzcGVjdCA9ICJ4eSIpCmBgYApJcyB0aGVyZSBhIHNsb3dkb3duIGluIHRoZSBpbmNyZWFzZT8KCkEgZ3JhcGggd2l0aCBtb3JlIHJlY2VudCBkYXRhIGlzClthdmFpbGFibGVdKGh0dHBzOi8vZ21sLm5vYWEuZ292L2NjZ2cvdHJlbmRzLykuCgpUaGUgZnVsbCBkYXRhIGlzClthdmFpbGFibGVdKGh0dHBzOi8vZ21sLm5vYWEuZ292L3dlYmRhdGEvY2NnZy90cmVuZHMvY28yL2NvMl9hbm5tZWFuX21sby50eHQpCmFzIHdlbGwuCgpgYXV0b3Bsb3QoKWAgZG9lcyBub3QgeWV0IHByb3ZpZGUgYW4gZWFzeSB3YXkgdG8gYmFuayB0byA0NSBkZWdyZWVzOyB5b3UKbmVlZCB0byBleHBlcmltZW50IG9yIGNhbGN1bGF0ZSBhbiBhcHByb3ByaWF0ZSBhc3BlY3QgcmF0aW8geW91cnNlbGYuCgpUaGUgZGVmYXVsdCBgYXV0b3Bsb3RgOgoKYGBge3IgY28yLWF1dG9wbG90LWRlZmF1bHQsIGVjaG8gPSBGQUxTRX0KbGlicmFyeShmb3JlY2FzdCkKYXV0b3Bsb3QoY28yKQpgYGAKCmBgYHtyIGNvMi1hdXRvcGxvdC1kZWZhdWx0LCBldmFsID0gRkFMU0V9CmBgYAoKQXNwZWN0IHJhdGlvIGFkanVzdGVkIHdpdGggYGNvb3JkX2ZpeGVkKClgOgoKYGBge3IsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMH0KcjQ1IDwtIG1lZGlhbihkaWZmKHRpbWUoY28yKSkpIC8gbWVkaWFuKGFicyhkaWZmKGNvMikpKQphdXRvcGxvdChjbzIpICsgY29vcmRfZml4ZWQocmF0aW8gPSByNDUpCmBgYCAKCgojIyBVcGRhdGVkIE1hdW5hIExvYSBDTzIgRGF0YQoKRG93bmxvYWRpbmcgYW5kIHJlYWRpbmcgaW4gdGhlIGRhdGE6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGx1YnJpZGF0ZSkKdXJsIDwtICJodHRwczovL2dtbC5ub2FhLmdvdi93ZWJkYXRhL2NjZ2cvdHJlbmRzL2NvMi9jbzJfbW1fbWxvLnR4dCIKaWYgKCEgZmlsZS5leGlzdHMoImNvMm5ldy5kYXQiKSB8fAogICAgbm93KCkgPiBmaWxlLm10aW1lKCJjbzJuZXcuZGF0IikgKyB3ZWVrcyg0KSkKICAgIGRvd25sb2FkLmZpbGUodXJsLCAiY28ybmV3LmRhdCIpCmNvMm5ldyA8LSByZWFkLnRhYmxlKCJjbzJuZXcuZGF0IikKYGBgCgpDbGVhbiB1cCB0aGUgdmFyaWFibGUgbmFtZXM6CmBgYHtyfQpoZWFkKGNvMm5ldykKbmFtZXMoY28ybmV3KSA8LSBjKCJ5ZWFyIiwgIm1vbnRoIiwgImRlY2ltYWxfZGF0ZSIsICJhdmVyYWdlIiwKICAgICAgICAgICAgICAgICAgICJkZXNlYXNvbiIsICJuZGF5cyIsICJzdGRkIiwgInVtbSIpCmBgYAoKQW4gaW5pdGlhbCBsb29rOgoKYGBge3J9CmdncGxvdChjbzJuZXcsIGFlcyhkZWNpbWFsX2RhdGUsIGF2ZXJhZ2UpKSArIGdlb21fbGluZSgpCmBgYAoKTWlzc2luZyB2YWx1ZXMgYXJlIGVuY29kZWQgYXMgYC05Ljk5YCBvciBgLTk5Ljk5YC4KCklmIHRoZXJlIGFyZSBhbnkgc3VjaCB2YWx1ZXMgdGhleSBuZWVkIHRvIGJlIHJlcGxhY2VkIHdpdGggUidzIG1pc3NpbmcKdmFsdWUgcmVwcmVzZW50YXRpb246CgpgYGB7cn0KZmlsdGVyKGNvMm5ldywgYXZlcmFnZSA8IC05KQpjbzJuZXcgPC0gbXV0YXRlKGNvMm5ldywgYXZlcmFnZSA9IGlmZWxzZShhdmVyYWdlIDwgLTksIE5BLCBhdmVyYWdlKSkKYGBgCgpUaGlzIHByb2R1Y2VzIGEgcmVhc29uYWJsZSBwbG90OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoY28ybmV3LCBhZXMoZGVjaW1hbF9kYXRlLCBhdmVyYWdlKSkgKwogICAgZ2VvbV9saW5lKCkKYGBgCgpUaGUgcGxvdCB3b3VsZCBzaG93IGdhcHMgZm9yIHRoZSBtaXNzaW5nIHZhbHVlcy4KCk1pc3NpbmcgdmFsdWVzIGNvdWxkIGFsc28gYmUgaW50ZXJwb2xhdGVkIHdpdGggYG5hLmludGVycGAgZnJvbQpwYWNrYWdlIGBmb3JlY2FzdGAuCgpDb252ZXJ0aW5nIHRoZSBuZXcgZGF0YSB0byBhIGB0c2Agb2JqZWN0IG1ha2VzIGFkZGluZyB0aGUgb3JpZ2luYWwKZGF0YSBhIGxpdHRsZSBlYXNpZXI6CgpgYGB7cn0KY28ybmV3VFMgPC0gdHMoY28ybmV3JGF2ZXJhZ2UsIHN0YXJ0ID0gYygxOTU4LCAzKSwgZnJlcXVlbmN5ID0gMTIpCmF1dG9wbG90KGNvMm5ld1RTKSArIGF1dG9sYXllcihjbzIpCmBgYAoKCiMjIFNlYXNvbmFsIERlY29tcG9zaXRpb24gb2YgVGltZSBTZXJpZXMgYnkgTG9lc3MgKFNUTCkKCkZvciB0aW1lIHNlcmllcyB3aXRoIGEgc3Ryb25nIHNlYXNvbmFsIGNvbXBvbmVudCBpdCBjYW4gYmUgdXNlZnVsIHRvCmxvb2sgYXQgYSBfU2Vhc29uYWwgRGVjb21wb3NpdGlvbiBvZiBUaW1lIFNlcmllcyBieSBMb2Vzc18sIG9yCihfU1RMXykuCgpUaGUgbWV0aG9kb2xvZ3kgd2FzIHN1Z2dlc3RlZCBieSBDbGV2YWxhbmQgYW5kIGNvd29ya2Vycy4KClRoZSBgc3RsKClgIGZ1bmN0aW9uIGluIHRoZSBgYmFzZWAgcGFja2FnZSBjb21wdXRlcyBzdWNoIGEKZGVjb21wb3NpdGlvbi4KCkl0IHJlcXVpcmVzIGEgc2VyaWVzIHdpdGhvdXQgbWlzc2luZyB2YWx1ZXMuCgpgYGB7cn0KY28ybmV3VFNfbm9fTkEgPC0gdHMoY28ybmV3JGF2ZXJhZ2UsIHN0YXJ0ID0gYygxOTU4LCAzKSwgZnJlcXVlbmN5ID0gMTIpCmNvMnN0bCA8LSBzdGwoY28ybmV3VFNfbm9fTkEsIHMud2luZG93ID0gMjEpCmNsYXNzKGNvMnN0bCkKaGVhZChjbzJzdGwkdGltZS5zZXJpZXMpCmBgYAoKUGFja2FnZSBgZm9yZWNhc3RgIHByb3ZpZGVzIGFuIGBhdXRvcGxvdCgpYCBtZXRob2QgZm9yIGBzdGxgIG9iamVjdHMgdGhhdApzaG93cyB0aGUgb3JpZ2luYWwgZGF0YSBhbmQgdGhlIHRocmVlIGNvbXBvbmVudHMgaW4gc2VwYXJhdGUgcGFuZWxzOgoKYGBge3IgY28yLXN0bC1wbG90LCBldmFsID0gRkFMU0V9CmF1dG9wbG90KGNvMnN0bCkKYGBgCgpgYGB7ciBjbzItc3RsLXBsb3QsIGVjaG8gPSBGQUxTRSwgZmlnLmhlaWdodCA9IDd9CmBgYAoKCiMjIFNvbWUgQWx0ZXJuYXRpdmUgVmlzdWFsaXphdGlvbnMKCkJhciBjaGFydHMgYXJlIHNvbWV0aW1lcyB1c2VkIHdoZW4gY29tcGFyaXNvbiB0byBhIGJhc2UgbGluZSBpcwppbXBvcnRhbnQ7IGZvciBleGFtcGxlIGZvciBbaWxsdXN0cmF0aW5nIGNsaW1hdGUKY2hhbmdlXShgciBJTUcoIkNhR19HbG9iYWxUZW1wQW5vbV8xLmpwZyIpYCkuICA8IS0tIG9yaWdpbmFsIGF0Cmh0dHBzOi8vd3d3LmNsaW1hdGUuZ292L3NpdGVzL2RlZmF1bHQvZmlsZXMvQ2FHX0dsb2JhbFRlbXBBbm9tXzEuanBnCkRhdGEgaXMgYXZhaWxhYmxlIGZyb20KaHR0cHM6Ly93d3cuY2xpbWF0ZS5nb3YvbWFwcy1kYXRhL2RhdGFzZXQvZ2xvYmFsLXRlbXBlcmF0dXJlLWFub21hbGllcy1ncmFwaGluZy10b29sCi0tPgoKQ2FsZW5kYXIgcGxvdHMsIG9yIGNhbGVuZGFyIGhlYXRtYXBzLCBhcmUgc29tZXRpbWVzIHVzZWZ1bCBmb3IKZGlzcGxheWluZyBkYWlseSBkYXRhIHdoZXJlIGRheSBvZiB0aGUgd2Vlay93ZWVrZW5kIGVmZmVjdHMgbWlnaHQgYmUKaW1wb3J0YW50LiAgT25lIGV4YW1wbGUgaXMgYXZhaWxhYmxlIFtoZXJlXShgciBJTUcoImNhbGVuZGFySGVhdG1hcC5wbmciKWApLgoKQSBbYmxvZyBwb3N0XShodHRwczovL2RvbWluaWtrb2NoLmdpdGh1Yi5pby9DYWxlbmRhci1IZWF0bWFwLykgc2hvd3MKaG93IHRvIGJ1aWxkIGEgY2FsZW5kYXIgcGxvdCB3aXRoIGBnZ3Bsb3RgLgoKCiMjIEdEUCBHcm93dGggRGF0YQoKQmFyIGNoYXJ0cyBhcmUgc29tZXRpbWVzIHVzZWQgZm9yIGVjb25vbWljIHRpbWUgc2VyaWVzLCBpbiBwYXJ0aWN1bGFyCmdyb3d0aCByYXRlIGRhdGEuCgpTb21lIEdEUCBncm93dGggcmF0ZSBkYXRhIGlzIGF2YWlsYWJsZSBvbiB0aGlzClt3ZWIgcGFnZV0oaHR0cHM6Ly93d3cubXVsdHBsLmNvbS91cy1yZWFsLWdkcC1ncm93dGgtcmF0ZS90YWJsZS9ieS1xdWFydGVyKToKClJlYWQgdGhlIGRhdGEgZnJvbSB0aGUgd2ViIHBhZ2U6CgpgYGB7ciwgY2FjaGUgPSBUUlVFfQpsaWJyYXJ5KHJ2ZXN0KQpnZHB1cmwgPC0gImh0dHA6Ly93d3cubXVsdHBsLmNvbS91cy1yZWFsLWdkcC1ncm93dGgtcmF0ZS90YWJsZS9ieS1xdWFydGVyIgpnZHBwYWdlIDwtIHJlYWRfaHRtbChnZHB1cmwpCmdkcHRibCA8LSBodG1sX3RhYmxlKGdkcHBhZ2UpW1sxXV0KaGVhZChnZHB0YmwpCmBgYAoKQ2xlYW4gdGhlIGRhdGE6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbmFtZXMoZ2RwdGJsKSA8LSBjKCJEYXRlIiwgIlZhbHVlIikKZ2RwdGJsIDwtIG11dGF0ZShnZHB0YmwsCiAgICAgICAgICAgICAgICAgVmFsdWUgPSBhcy5udW1lcmljKHN1YigiJSIsICIiLCBWYWx1ZSkpLAogICAgICAgICAgICAgICAgIERhdGUgPSBtZHkoRGF0ZSkpCmhlYWQoZ2RwdGJsKQpgYGAKCkRhdGEgcHJpb3IgdG8gMTk0NyBpcyBhbm51YWwsIHNvIGRyb3AgZWFybGllciBkYXRhOgoKYGBge3J9CmdkcHRibCA8LSBmaWx0ZXIoZ2RwdGJsLAogICAgICAgICAgICAgICAgIERhdGUgPj0gbWFrZV9kYXRlKDE5NDgsIDEsIDEpKQpgYGAKClJlYXJyYW5nZSB0byBwdXQgYERhdGVgIGluIGluY3JlYXNpbmcgb3JkZXI6CgpgYGB7cn0KZ2RwdGJsIDwtIGFycmFuZ2UoZ2RwdGJsLCBEYXRlKQpgYGAKCkNyZWF0ZSBhbmQgcGxvdCBhIHRpbWUgc2VyaWVzOgoKYGBge3IgZ2RwLXBsb3QsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGZvcmVjYXN0KQpnZHB0cyA8LSB0cyhnZHB0YmwkVmFsdWUsCiAgICAgICAgICAgIHN0YXJ0ID0gMTk0OCwgZnJlcXVlbmN5ID0gNCkKYXV0b3Bsb3QoZ2RwdHMpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMikKYGBgCgpgYGB7ciBnZHAtcGxvdCwgZWNobyA9IEZBTFNFfQpgYGAKCkEgYmFyIGNoYXJ0IHdpdGggY29sb3IgdXNlZCB0byBkaXN0aW5ndWlzaCBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUKZ3Jvd3RoIGNhbiBiZSBjcmVhdGVkIGZyb20gdGhlIG9yaWdpbmFsIHRhYmxlIGBnZHB0YmxgOgoKYGBge3IgZ2RwLWJhciwgZXZhbCA9IEZBTFNFLCBkZXYgPSAic3ZnIn0KbGlicmFyeShnZ3Bsb3QyKQptdXRhdGUoZ2RwdGJsLAogICAgICAgZ3Jvd3RoID0gaWZlbHNlKFZhbHVlID49IDAsCiAgICAgICAgICAgICAgICAgICAgICAgInBvcyIsCiAgICAgICAgICAgICAgICAgICAgICAgIm5lZyIpKSB8PgogICAgZ2dwbG90KGFlcyh4ID0gRGF0ZSwKICAgICAgICAgICAgICAgeSA9IFZhbHVlLAogICAgICAgICAgICAgICBmaWxsID0gZ3Jvd3RoKSkgKwogICAgZ2VvbV9jb2woKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwoCiAgICAgICAgdmFsdWVzID0gYyhwb3MgPSAiYmx1ZSIsIG5lZyA9ICJyZWQiKSkKYGBgCgpgYGB7ciBnZHAtYmFyLCBlY2hvID0gRkFMU0V9CmBgYAoKVGhlIGJhciBjaGFydCBjYW4gYWxzbyBiZSBjcmVhdGVkIGZyb20gdGhlIHRpbWUgc2VyaWVzIG9iamVjdDoKCmBgYHtyIGdkcC1iYXItdHMsIGV2YWwgPSBGQUxTRSwgZGV2ID0gInN2ZyJ9CmRhdGEuZnJhbWUocmF0ZSA9IGFzLm51bWVyaWMoZ2RwdHMpLAogICAgICAgICAgIHRpbWUgPSBhcy5udW1lcmljKHRpbWUoZ2RwdHMpKSkgfD4KICAgIG11dGF0ZShncm93dGggPSBpZmVsc2UocmF0ZSA+PSAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAicG9zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5lZyIpKSB8PgogICAgZ2dwbG90KGFlcyh0aW1lLCByYXRlLCBmaWxsID0gZ3Jvd3RoKSkgKwogICAgZ2VvbV9jb2woKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwoCiAgICAgICAgdmFsdWVzID0gYyhwb3MgPSAiYmx1ZSIsIG5lZyA9ICJyZWQiKSkKYGBgCgpgYGB7ciBnZHAtYmFyLXRzLCBlY2hvID0gRkFMU0V9CmBgYAoKCiMjIEFpcmxpbmUgRmxpZ2h0cyBGcm9tIE5ZQwoKVGhlIGBueWNmbGlnaHRzMTNgIGRhdGEgc2V0IGNvbnRhaW5zIGRhdGEgb24gYWxsIGZsaWdodCBsZWF2aW5nIE5ldwpZb3JrIENpdHkgaW4gMjAxMy4KClNpbWlsYXIgZGF0YSBpcyBhdmFpbGFibGUgZm9yIG90aGVyIGNpdGllcyBhbmQgeWVhcnMsIGUuZy4gZnJvbSB0aGUKW2BhbnlmbGlnaHRzYCBwYWNrYWdlXShodHRwczovL3NpbW9ucGNvdWNoLmdpdGh1Yi5pby9hbnlmbGlnaHRzLykuCgpXZSBjYW4gbG9vayBhdCB0aGUgbnVtYmVyIG9mIGRlcGFydHVyZXMgZm9yIGVhY2ggZGF5OgoKYGBge3J9CmxpYnJhcnkobnljZmxpZ2h0czEzKQpsaWJyYXJ5KGdncGxvdDIpCm5mIDwtIGNvdW50KGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpCmBgYAoKSXQgd2lsbCBiZSB1c2VmdWwgdG8gaGF2ZSBhIGNvbWJpbmVkIGBkYXRlYCBmaWVsZCBpbiBvdXIgZGF0YSBmcmFtZS4KClRoZSBgbHVicmlkYXRlYCBmdW5jdGlvbiBgbWFrZV9kYXRlKClgIGNhbiBiZSB1c2VkIGZvciB0aGlzOgoKYGBge3J9CmxpYnJhcnkobHVicmlkYXRlKQpuZiA8LSBtdXRhdGUobmYsIGRhdGUgPSBtYWtlX2RhdGUoeWVhciwgbW9udGgsIGRheSkpCmBgYAoKQSBsaW5lIHBsb3Qgb2YgdGhlIG51bWJlciBvZiBmbGlnaHRzOgoKYGBge3IgZmxpZ2h0cy0xLCBlY2hvID0gRkFMU0V9CmdncGxvdChuZiwgYWVzKGRhdGUsIG4pKSArCiAgICBnZW9tX2xpbmUoKQpgYGAKYGBge3IgZmxpZ2h0cy0xLCBldmFsID0gRkFMU0V9CmBgYAoKV2Vla2RheSB2YXJpYXRpb24gaXMgY2xlYXJseSB2aXNpYmxlLgoKQnJlYWtpbmcgb3V0IHRoZSBkYXlzIG9mIHRoZSB3ZWVrIGludG8gc2VwYXJhdGUgZmFjZXRzIG1heSBoZWxwOgoKYGBge3IgZmxpZ2h0cy0yLCBlY2hvID0gRkFMU0V9Cm5uZiA8LQogICAgbXV0YXRlKG5mLCB3ZCA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSkKCm5uZl90aG0gPC0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0KICAgICAgICAgICAgICBlbGVtZW50X3RleHQoYW5nbGUgPSAtNDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMCksCiAgICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbihyID0gMzApKQoKZ2dwbG90KG5uZiwgYWVzKGRhdGUsIG4pKSArCiAgICBnZW9tX2xpbmUoKSArCiAgICBmYWNldF93cmFwKH4gd2QpICsKICAgIG5uZl90aG0KYGBgCgpgYGB7ciBmbGlnaHRzLTIsIGV2YWwgPSBGQUxTRX0KYGBgCgpKdWx5IDQgaW4gMjAxMzoKCmBgYHtyfQphcy5jaGFyYWN0ZXIod2RheSh5bWQoIjIwMTMtMDctMDQiKSwKICAgICAgICAgICAgICAgICAgbGFiZWwgPSBUUlVFKSkKYGBgCgpJZGVudGlmeWluZyB3aGVuIHNvbWUgbWFqb3IgaG9saWRheXMgb2NjdXIgbWF5IGFsc28gYmUgdXNlZnVsOgoKYGBge3IgZmxpZ2h0cy0zLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcuNX0KaG9saWRheXMgPC0KICAgIGRhdGEuZnJhbWUoSG9saWRheSA9IGMoIjR0aCBvZiBKdWx5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRoYW5rc2dpdmluZyIpLAogICAgICAgICAgICAgICBkYXRlID0geW1kKGMoIjIwMTMtMDctMDQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIjIwMTMtMTEtMjgiKSkpCmhsYXllciA8LQogICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IGRhdGUsCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IEhvbGlkYXkpLAogICAgICAgICAgICAgICBsaW5ldHlwZSA9IDIsCiAgICAgICAgICAgICAgIGxpbmV3aWR0aCA9IDEsCiAgICAgICAgICAgICAgIGRhdGEgPSBob2xpZGF5cykKZ2dwbG90KG5mLCBhZXMoZGF0ZSwgbikpICsKICAgIGhsYXllciArCiAgICBnZW9tX2xpbmUoKQpgYGAKYGBge3IgZmxpZ2h0cy0zLCBldmFsID0gRkFMU0V9CmBgYAoKSG9saWRheXMgaW4gdGhlIGZhY2V0ZWQgcGxvdDoKCmBgYHtyIGZsaWdodHMtaG9saWRheS1mYWNldC0xLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcuNX0KZ2dwbG90KG5uZiwgYWVzKGRhdGUsIG4pKSArCiAgICBobGF5ZXIgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZmFjZXRfd3JhcCh+IHdkKSArCiAgICBubmZfdGhtCmBgYAoKYGBge3IgZmxpZ2h0cy1ob2xpZGF5LWZhY2V0LTEsIGV2YWwgPSBGQUxTRX0KYGBgCgpUaGlzIG1hcmtzIHRoZSBob2xpZGF5IHdlZWtzLgoKSG9saWRheXMgaW4gdGhlIGZhY2V0ZWQgcGxvdDoKCmBgYHtyIGZsaWdodHMtaG9saWRheS1mYWNldC0yLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcuNX0KaGxheWVyMiA8LQogICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IGRhdGUsCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IEhvbGlkYXkpLAogICAgICAgICAgICAgICBsaW5ld2lkdGggPSAxLAogICAgICAgICAgICAgICBkYXRhID0gbXV0YXRlKGhvbGlkYXlzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdkID0gd2RheShkYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFKSkpCmdncGxvdChubmYsIGFlcyhkYXRlLCBuKSkgKwogICAgaGxheWVyMiArCiAgICBnZW9tX2xpbmUoKSArCiAgICBmYWNldF93cmFwKH4gd2QpICsKICAgIG5uZl90aG0KYGBgCgpUaGlzIHNob3dzIG9ubHkgdGhlIGhvbGlkYXkgZGF5cy4KCmBgYHtyIGZsaWdodHMtaG9saWRheS1mYWNldC0yLCBldmFsID0gRkFMU0V9CmBgYAoKSG9saWRheXMgaW4gdGhlIGZhY2V0ZWQgcGxvdDoKCmBgYHtyIGZsaWdodHMtaG9saWRheS1mYWNldC0zLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcuNX0KZ2dwbG90KG5uZiwgYWVzKGRhdGUsIG4pKSArCiAgICBobGF5ZXIgKwogICAgaGxheWVyMiArCiAgICBnZW9tX2xpbmUoKSArCiAgICBmYWNldF93cmFwKH4gd2QpICsKICAgIG5uZl90aG0KYGBgClNob3cgaG9saWRheXMgYXMgc29saWQgbGluZXMsIGRheXMgaW4gaG9saWRheSB3ZWVrcyBhcyBkYXNoZWQgbGluZXMuCgpgYGB7ciBmbGlnaHRzLWhvbGlkYXktZmFjZXQtMywgZXZhbCA9IEZBTFNFfQpgYGAKCkEgc2ltcGxlIGNhbGVuZGFyIHBsb3Q6CgpgYGB7ciBmbGlnaHRzLWNhbC0wLCBlY2hvID0gRkFMU0V9Cm1vbnRod2VlayA8LSBmdW5jdGlvbihkLCB3KQogICAgY2VpbGluZygoZCAtIHcpIC8gNykgKyAxCgpuZiA8LSBtdXRhdGUobmYsCiAgICAgICAgICAgICB3ZCA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSwKICAgICAgICAgICAgIHdkID0gZmN0X3Jldih3ZCksCiAgICAgICAgICAgICBtdyA9IG1vbnRod2VlayhkYXksIHdkYXkoZGF0ZSkpKQoKcCA8LSBnZ3Bsb3QobmYsIGFlcyh4ID0gYXMuY2hhcmFjdGVyKG13KSwKICAgICAgICAgICAgICAgICAgICB5ID0gd2QsCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IG4pKSArCiAgICBnZW9tX3RpbGUoY29sb3IgPSAid2hpdGUiKSArCiAgICBmYWNldF93cmFwKH4gbW9udGgoZGF0ZSwgbGFiZWwgPSBUUlVFKSkKcApgYGAKVGhpcyB1c2VzIGBnZW9tX3RpbGUoKWAgYW5kIGZhY2V0aW5nLgoKYGBge3IgZmxpZ2h0cy1jYWwtMCwgZXZhbCA9IEZBTFNFfQpgYGAKClNvbWUgdGhlbWUgYW5kIGNvbG9yIGFkanVzdG1lbnRzOgoKYGBge3IgZmxpZ2h0cy1jYWwtMSwgZWNobyA9IEZBTFNFfQpwIDwtIHAgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiKSArCiAgICB5bGFiKCIiKSArIHhsYWIoIldlZWsgb2YgTW9udGgiKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQpwCmBgYApgYGB7ciBmbGlnaHRzLWNhbC0xLCBldmFsID0gRkFMU0V9CmBgYAoKVGhpcyBsYXlvdXQgd2l0aCBkYXlzIG9uIHRoZSB2ZXJ0aWNhbCBheGlzIHdvcmtzIHdlbGwgd2l0aCBhIHNpbmdsZSByb3csCndoaWNoIGNvdWxkIGJlIGV4dGVuZGVkIHRvIGEgbXVsdGkteWVhciBwbG90OgoKYGBge3IsIGZpZy5oZWlnaHQgPSAyLCBmaWcud2lkdGggPSAxMH0KcCArIGZhY2V0X3dyYXAofiBtb250aChkYXRlLCBUUlVFKSwgbnJvdyA9IDEpCmBgYAoKQSBtb3JlIHN0YW5kYXJkIGxheW91dDoKCmBgYHtyIGZsaWdodHMtY2FsLTMsIGVjaG8gPSBGQUxTRSwgZmlnLndpZHRoID0gOH0KbmYyIDwtCiAgICBtdXRhdGUobmYsCiAgICAgICAgICAgd2QgPSB3ZGF5KGRhdGUsIGxhYmVsID0gVFJVRSksCiAgICAgICAgICAgbXcgPSBmYWN0b3IobW9udGh3ZWVrKGRheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2RheShkYXRlKSkpLAogICAgICAgICAgIG13ID0gZmN0X3JldihtdykpCgp3ZGxhYnMgPC1zdWJzdHIobGV2ZWxzKG5mMiR3ZCksIDEsIDEpCgpnZ3Bsb3QobmYyLCBhZXMoeCA9IHdkLCB5ID0gbXcsIGZpbGwgPSBuKSkgKwogICAgZ2VvbV90aWxlKGNvbG9yID0gIndoaXRlIikgKwogICAgZmFjZXRfd3JhcCh+IG1vbnRoKGRhdGUsIFRSVUUpKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ3aGl0ZSIpICsKICAgIHhsYWIoIiIpICsgeWxhYigiV2VlayBvZiBNb250aCIpICsKICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gd2RsYWJzKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQpgYGAKYGBge3IgZmxpZ2h0cy1jYWwtMywgZXZhbCA9IEZBTFNFLCBmaWcud2lkdGggPSA4fQpgYGAKCgojIyBSZWFkaW5nCgpDaGFwdGVycyBbX1Zpc3VhbGl6aW5nIHRpbWUgc2VyaWVzIGFuZCBvdGhlciBmdW5jdGlvbnMgb2YgYW4KICBpbmRlcGVuZGVudAogIHZhcmlhYmxlX10oaHR0cHM6Ly9jbGF1c3dpbGtlLmNvbS9kYXRhdml6L3RpbWUtc2VyaWVzLmh0bWwpIGFuZAogIFtfVmlzdWFsaXppbmcKICB0cmVuZHNfXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovdmlzdWFsaXppbmctdHJlbmRzLmh0bWwpIGluCiAgW19GdW5kYW1lbnRhbHMgb2YgRGF0YQogIFZpc3VhbGl6YXRpb25fXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovKS4KCgojIyBFeGVyY2lzZXMKCjEuIEEgdXNlZnVsIGZlYXR1cmUgb2YgdGhlIGBsYXR0aWNlYCBwYWNrYWdlIGlzIHRoZSBhYmlsaXR5IHRvIGVhc2lseQogICAgcmVxdWVzdCBhbiBhc3BlY3QgcmF0aW8gdGhhdCBpcyBfYmFua2VkIHRvIDQ1IGRlZ3JlZXNfIGJ5CiAgICBzcGVjaWZ5aW5nIGBhc3BlY3QgPSAieHkiYCBpbiB0aGUgYXJndW1lbnRzIHRvIGBsYXR0aWNlYCBwbG90dGluZwogICAgZnVuY3Rpb25zLiBGb3IgZXhhbXBsZSwgVGhpcyBjb2RlIHByb2R1Y2VzIGEgcmVhc29uYWJsZSBhc3BlY3QKICAgIHJhdGlvIGZvciBhIHBsb3Qgb2YgYW4gdW5lbXBsb3ltZW50IHRpbWUgc2VyaWVzOgoKICAgIGBgYHtyfQogICAgbGlicmFyeShnZ3Bsb3QyKQogICAgbGlicmFyeShmb3JlY2FzdCkKICAgIGxpYnJhcnkobGF0dGljZSkKICAgIHVuZW1wX3RzIDwtIHRzKGVjb25vbWljcyR1bmVtcGxveSwgc3RhcnQgPSBjKDE5NjcsIDcpLCBmcmVxdWVuY3kgPSAxMikKICAgIHh5cGxvdCh1bmVtcF90cywgYXNwZWN0ID0gInh5IikKICAgIGBgYAoKICAgIFdoaWNoIG9mIHRoZSBmb2xsb3dpbmcgcGxvdHMgbW9zdCBjbG9zZWx5IG1hdGNoZXMgdGhlIGFzcGVjdCByYXRpbwogICAgaW4gdGhlIGBsYXR0aWNlYCBwbG90PwoKICAgIGEuIGBhdXRvcGxvdCh1bmVtcF90cywgYXNwZWN0ID0gMjAwKWAKICAgIGIuIGBhdXRvcGxvdCh1bmVtcF90cykgKyBjb29yZF9maXhlZChyYXRpbyA9IDAuMDAwNylgCiAgICBjLiBgYXV0b3Bsb3QodW5lbXBfdHMpICsgY29vcmRfZml4ZWQoYXNwID0gMC4wMDA3KWAKICAgIGQuIGBhdXRvcGxvdCh1bmVtcF90cykgKyBjb29yZF9maXhlZCg3MClgCg==