Time Series Data

A lot of data is collected over time:

The objectives can be

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:

Data might be missing

Time series can also be recorded at irregular times:

Variables recorded over time can be numerical or categorical.

Time Series Objects

There are 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:

Class Package Features
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)
riverTS
## 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 getOption("max.print") -- omitted 116 entries ]

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     2  9.49
## 3     3 10.2

Change to more convenient names:

riverDF <- rename(riverDF, month = index, flow = value)
head(riverDF, 3)
## # A tibble: 3 × 2
##   month  flow
##   <dbl> <dbl>
## 1     1  8.95
## 2     2  9.49
## 3     3 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:

p + coord_fixed(ratio = 4)

Lattice supports several aspect ratio strategies:

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_width(month,
                          center = 21.5,
                          width = 43)) %>%
    ggplot(aes(month, 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 = 4)
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.70 314.43 -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.71 -1 -9.99 -0.99
## 4 1958  6 1958.455 317.24 315.14 -1 -9.99 -0.99
## 5 1958  7 1958.537 315.86 315.18 -1 -9.99 -0.99
## 6 1958  8 1958.622 314.93 316.18 -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)
head(co2stl$time.series)
##            seasonal    trend  remainder
## Mar 1958  1.1151386 314.9331 -0.3482842
## Apr 1958  2.2812033 315.0057  0.1630790
## May 1958  2.7700266 315.0783 -0.3383163
## Jun 1958  2.2252316 315.1509 -0.1360934
## Jul 1958  0.8621872 315.2178 -0.2199841
## Aug 1958 -1.0841494 315.2847  0.7294176

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\n\n\n\nValue`
##   <chr>        <chr>               
## 1 Dec 31, 2022 0.88%               
## 2 Sep 30, 2022 1.94%               
## 3 Jun 30, 2022 1.80%               
## 4 Mar 31, 2022 3.68%               
## 5 Dec 31, 2021 5.72%               
## 6 Sep 30, 2021 4.96%

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 2022-12-31  0.88
## 2 2022-09-30  1.94
## 3 2022-06-30  1.8 
## 4 2022-03-31  3.68
## 5 2021-12-31  5.72
## 6 2021-09-30  4.96

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.

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

ggplot(nf2, aes(x = wd, y = mw, fill = n)) +
    geom_tile(color = "white") +
    facet_wrap(~ month(date, TRUE)) +
    scale_fill_gradient(low = "white") +
    ylab("") + xlab("Week of Month") +
    theme(panel.grid.major = element_blank(),
          axis.text.x =
              element_text(angle = 45,
                           hjust = 1),
          panel.border = element_blank())

Reading

Chapters Visualizing time series and other functions of an independent variable and Visualizing trends in Fundamentals of Data Visualization.

Exercises

  1. 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?

    1. autoplot(unemp_ts, aspect = 200)
    2. autoplot(unemp_ts) + coord_fixed(ratio = 0.0007)
    3. autoplot(unemp_ts) + coord_fixed(asp = 0.0007)
    4. autoplot(unemp_ts) + coord_fixed(70)
LS0tCnRpdGxlOiAiVGltZSBTZXJpZXMgUGxvdHMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0ic3RhdDQ1ODAuY3NzIiB0eXBlPSJ0ZXh0L2NzcyIgLz4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4gLnJlbWFyay1jb2RlIHsgZm9udC1zaXplOiA4NSU7IH0gPC9zdHlsZT4KYGBge3Igc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Kc291cmNlKGhlcmU6OmhlcmUoInNldHVwLlIiKSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGNvbGxhcHNlID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aCA9IDYsIGZpZy5hbGlnbiA9ICJjZW50ZXIiKQoKb3B0aW9ucyhodG1sdG9vbHMuZGlyLnZlcnNpb24gPSBGQUxTRSkKCnNldC5zZWVkKDEyMzQ1KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobGF0dGljZSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KHBhdGNod29yaykKdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSArCiAgICAgICAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiZ3JleTMwIiwgZmlsbCA9IE5BKSkpCmBgYAoKCiMjIFRpbWUgU2VyaWVzIERhdGEKCkEgbG90IG9mIGRhdGEgaXMgY29sbGVjdGVkIG92ZXIgdGltZToKCi0gZWNvbm9taWMgcGVyZm9ybWFjZSBtZWFzdXJlczsKCi0gd2VhdGhlciwgY2xpbWF0ZSBkYXRhOwoKLSBtZWRpY2FsIGluZm9ybWF0aW9uLgoKVGhlIG9iamVjdGl2ZXMgY2FuIGJlCgotIHVuZGVyc3RhbmRpbmcgdGhlIHBhc3QgLS0gaG93IHdlIGdvdCB3aGVyZSB3ZSBhcmU7CgotIHVuZGVyc3RhbmRpbmcgcGF0dGVybnMgaW4gdmFyaWF0aW9uOwoKLSBmb3JlY2FzdGluZyB3aGF0IHdpbGwgaGFwcGVuIG5leHQuCgpFdmVuIGlmIHRpbWUgaXMgbm90IG9mIHByaW1hcnkgaW50ZXJlc3QsIGRhdGEgYXJlIG9mdGVuIGNvbGxlY3RlZCB3aXRoCmEgdGltZSBzdGFtcC4KCkV4YW1pbmluZyB0aGUgYmVoYXZpb3Igb3ZlciB0aW1lIGNhbiByZXZlYWwgaW50ZXJlc3RpbmcgZmVhdHVyZXMgdGhhdAptYXkgaGludCBhdCBjb25mb3VuZGluZyB2YXJpYWJsZXM6CgotIHRlbXBlcmF0dXJlIHZhcnlpbmcgd2l0aCB0aW1lIG9mIGRheTsKCi0gaHVtaWRpdHkgdmFyeWluZyBvdmVyIHRoZSB5ZWFyLgoKVGltZSBzZXJpZXMgZGF0YSBjYW4gYmUgY29sbGVjdGVkIGF0IHJlZ3VsYXJseSBzcGFjZWQgaW50ZXJ2YWxzOgoKLSB5ZWFybHksIGUuZy4gdGhlIGBnYXBtaW5kZXJgIGRhdGEKCi0gbW9udGhseSwgZS5nLiB0aGUgYHJpdmVyYCBkYXRhCgotIGRhaWx5LCBlLmcuIGhpZ2ggdGVtcGVyYXR1cmVzLgoKRGF0YSBtaWdodCBiZSBtaXNzaW5nCgotIGlycmVndWxhcmx5LCBlLmcuIGZhdWx0eSBpbnN0cnVtZW50LCBob2xpZGF5czsKCi0gcmVndWxhcmx5LCBlLmcuIHdlZWtlbmRzLgoKVGltZSBzZXJpZXMgY2FuIGFsc28gYmUgcmVjb3JkZWQgYXQgaXJyZWd1bGFyIHRpbWVzOgoKLSBibG9vZCBwcmVzc3VyZSBhdCBkb2N0b3IgdmlzaXRzOwoKVmFyaWFibGVzIHJlY29yZGVkIG92ZXIgdGltZSBjYW4gYmUgbnVtZXJpY2FsIG9yIGNhdGVnb3JpY2FsLgoKCiMjIFRpbWUgU2VyaWVzIE9iamVjdHMKClRoZXJlIGFyZSBhIG51bWJlciBvZiBzcGVjaWFsaXplZCBvYmplY3QgY2xhc3NlcyBmb3IgZGVhbGluZyB3aXRoIHRpbWUKc2VyaWVzLgoKQSBudW1iZXIgb2YgcGFja2FnZXMgcHJvdmlkZSBgcGxvdGAgb3IgYGF1dG9wbG90YCBtZXRob2RzIGFuZCBvdGhlcgp1dGlsaXRpZXMgZm9yIHRoZXNlIG9iamVjdHMuCgpTb21lIG9mIHRoZSBtb3N0IHVzZWZ1bDoKCnwgQ2xhc3MgICAgICB8ICBQYWNrYWdlICAgfCBGZWF0dXJlcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8CnxgdHNgLCBgbXRzYHwgYGJhc2VgICAgICB8IHJlZ3VsYXIgc2VyaWVzOyBtZXRob2RzIGZvciBgcGxvdGAgYW5kIGBsaW5lc2AuICAgfAp8ICAgICAgICAgICB8IGBmb3JlY2FzdGAgfCBtZXRob2RzIGZvciBgYXV0b3Bsb3RgIGFuZCBgYXV0b2xheWVyYC4gICAgICAgICAgfAp8YHpvb2AgICAgICB8ICBgem9vYCAgICAgfCBpcnJlZ3VsYXIgYW5kIHJlZ3VsYXIgc2VyaWVzOyBgYXV0b3Bsb3RgIHN1cHBvcnQuIHwKfCAgICAgICAgICAgfCBgYnJvb21gICAgIHxgdGlkeWAgZm9yIGNvbnZlcnRpbmcgdG8gdGlkeSBkYXRhIGZyYW1lcy4gICAgICAgICB8CgoKIyMgYGF1dG9wbG90YCBhbmQgYGF1dG9sYXllcmAKCk1hbnkgZGF0YSBzdHJ1Y3R1cmVzIHByb3ZpZGUgbWV0aG9kcyBmb3IgYGF1dG9wbG90YCBhbmQgYGF1dG9sYXllcmAuCgpGcm9tIHRoZSBgYXV0b3Bsb3RgIGhlbHAgcGFnZToKCj4gYGF1dG9wbG90YCB1c2VzIGBnZ3Bsb3QyYCB0byBkcmF3IGEgcGFydGljdWxhciBwbG90IGZvciBhbiBvYmplY3Qgb2YKPiBhIHBhcnRpY3VsYXIgY2xhc3MgaW4gYSBzaW5nbGUgY29tbWFuZC4gVGhpcyBkZWZpbmVzIHRoZSBTMyBnZW5lcmljCj4gdGhhdCBvdGhlciBjbGFzc2VzIGFuZCBwYWNrYWdlcyBjYW4gZXh0ZW5kLgoKRnJvbSB0aGUgYGF1dG9sYXllcmAgaGVscCBwYWdlOgoKPiBgYXV0b2xheWVyYCB1c2VzIGBnZ3Bsb3QyYCB0byBkcmF3IGEgcGFydGljdWxhciBsYXllciBmb3IgYW4gb2JqZWN0Cj4gb2YgYSBwYXJ0aWN1bGFyIGNsYXNzIGluIGEgc2luZ2xlIGNvbW1hbmQuIFRoaXMgZGVmaW5lcyB0aGUgUzMKPiBnZW5lcmljIHRoYXQgb3RoZXIgY2xhc3NlcyBhbmQgcGFja2FnZXMgY2FuIGV4dGVuZC4KCgojIyBFeGFtcGxlOiBSaXZlciBEYXRhCgpDcmVhdGluZyBhIGB0c2Agb2JqZWN0IGZvciB0aGUgYHJpdmVyYCBkYXRhOgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kb3AgPC0gb3B0aW9ucyhtYXgucHJpbnQgPSAxMikKYGBgCmBgYHtyfQpyaXZlciA8LSBzY2FuKCJodHRwczovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL3JpdmVyLmRhdCIpCnJpdmVyVFMgPC0gdHMocml2ZXIpCnJpdmVyVFMKYGBgCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9Cm9wdGlvbnMob3ApCmBgYAoKVXNpbmcgYHRpZHlgIGZyb20gcGFja2FnZSBgYnJvb21gIHRvIGNyZWF0ZSBhIGRhdGEgZnJhbWUgZnJvbSB0aGUgYHRzYCBvYmplY3Q6CgpgYGB7cn0Kcml2ZXJERiA8LSBicm9vbTo6dGlkeShyaXZlclRTKQpoZWFkKHJpdmVyREYsIDMpCmBgYAoKQ2hhbmdlIHRvIG1vcmUgY29udmVuaWVudCBuYW1lczoKCmBgYHtyfQpyaXZlckRGIDwtIHJlbmFtZShyaXZlckRGLCBtb250aCA9IGluZGV4LCBmbG93ID0gdmFsdWUpCmhlYWQocml2ZXJERiwgMykKYGBgCgoKIyMgQmFzaWMgUGxvdHMgZm9yIGEgU2luZ2xlIE51bWVyaWMgVGltZSBTZXJpZXMKCk51bWVyaWMgdGltZSBzZXJpZXMgYXJlIHVzdWFsbHkgcGxvdHRlZCBhcyBhIF9saW5lIGNoYXJ0Xy4KClRoaXMgaXMgd2hhdCB0aGUgYGF1dG9wbG90YCBtZXRob2QgZm9yIGB0c2Agb2JqZWN0cyBwcm92aWRlZCBieQpwYWNrYWdlIGBmb3JlY2FzdGAgZG9lcy4KCkZvciB0aGUgcml2ZXIgZmxvdyBkYXRhOgoKYGBge3Igcml2ZXItdHMtYXV0b3Bsb3QsIGVjaG8gPSBGQUxTRX0KbGlicmFyeShmb3JlY2FzdCkKcCA8LSBhdXRvcGxvdChyaXZlclRTKQpwCmBgYAoKYGBge3Igcml2ZXItdHMtYXV0b3Bsb3QsIGV2YWwgPSBGQUxTRX0KYGBgCgpVc2luZyBzeW1ib2xzIGluIGFkZGl0aW9uIHRvIGxpbmVzIGlzIHNvbWV0aW1lcyB1c2VmdWw6CgpgYGB7ciByaXZlci10cy1hdXRvcGxvdC1zeW1zLCBlY2hvID0gRkFMU0V9CnAgKyBnZW9tX3BvaW50KCkKYGBgCgpgYGB7ciByaXZlci10cy1hdXRvcGxvdC1zeW1zLCBldmFsID0gRkFMU0V9CmBgYAoKCiMjIEFzcGVjdCBSYXRpbwoKQXNwZWN0IHJhdGlvIGhhcyBhIHNpZ25pZmljYW50IGVmZmVjdCBvbiBob3cgd2VsbCB3ZSBwZXJjZWl2ZSBzbG9wZQpkaWZmZXJlbmNlcy4KCi0gQmFua2luZyB0byA0NSBkZWdyZWVzOiBjaG9vc2UgYW4gYXNwZWN0IHJhdGlvIHNvIHRoZSBtYWduaXR1ZGUgb2YKICB0aGUgaW1wb3J0YW50IHNsb3BlcyBpcyBjbG9zZSB0byA0NSBkZWdyZWVzLgoKLSBUaGlzIHVzdWFsbHkgcmVxdWlyZXMgdGhlIGhvcml6b250YWwgYXhpcyB0byBiZSBsb25nZXIgdGhhbiB0aGUKICB2ZXJ0aWNhbCBheGlzLgoKQ29udHJvbGxpbmcgdGhlIGFzcGVjdCByYXRpbzoKCi0gRm9yIGBiYXNlYCBncmFwaGljcyB1c2UgdGhlIGBhc3BgIGFyZ3VtZW50IHRvIGBwbG90YC4KCi0gYGxhdHRpY2VgIHVzZXMgdGhlIGBhc3BlY3RgIGFyZ3VtZW50LgoKLSBGb3IgYGdncGxvdGAgdXNlIHRoZSBgcmF0aW9gIGFyZ3VtZW50IHRvIGBjb29yZF9maXhlZGAuCgpgYGB7ciwgZmlnLmhlaWdodCA9IDMsIGZpZy53aWR0aCA9IDEwfQpwICsgY29vcmRfZml4ZWQocmF0aW8gPSA0KQpgYGAKCkxhdHRpY2Ugc3VwcG9ydHMgc2V2ZXJhbCBhc3BlY3QgcmF0aW8gc3RyYXRlZ2llczoKCi0gImZpbGwiIHRvIGF2b2lkIHVudXNlZCB3aGl0ZSBzcGFjZTsKCi0gInh5IiB0byB1c2UgYSA0NSBkZWdyZWUgYmFua2luZyBydWxlOwoKLSAiaXNvIiBmb3IgaXNvbWV0cmljIHNjYWxlcywgZS5nLiBkaXN0YW5jZXMuCgpTb21lIHdvcmsgbWF5IGJlIG5lZWRlZCB0byBhdm9pZCBleGNlc3Mgd2hpdGUgc3BhY2UgYXJvdW5kIGZpZ3VyZXMuCgpVc2luZyBtdWx0aXBsZSBwYW5lbHMgY2FuIGhlbHAuCgpJbiBhbiBpbnRlcmFjdGl2ZSBzZXR0aW5nLCB6b29tIGFuZCBwYW4gc3VwcG9ydCBjYW4gaGVscC4KCk11bHRpcGxlIHBhbmVsczoKCmBgYHtyIHJpdmVyLXRzLXBhbmVscywgZWNobyA9IEZBTFNFfQptdXRhdGUocml2ZXJERiwKICAgICAgIHBlcmlvZCA9IGN1dF93aWR0aChtb250aCwKICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXIgPSAyMS41LAogICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gNDMpKSAlPiUKICAgIGdncGxvdChhZXMobW9udGgsIGZsb3cpKSArCiAgICBnZW9tX2xpbmUoKSArCiAgICBmYWNldF93cmFwKH4gcGVyaW9kLAogICAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZV94IiwKICAgICAgICAgICAgICAgbmNvbCA9IDEpCmBgYAoKYGBge3Igcml2ZXItdHMtcGFuZWxzLCBldmFsID0gRkFMU0V9CmBgYAoKQWRkaW5nIHpvb20gYW5kIHBhbiBzdXBwb3J0IHVzaW5nIFtgcGxvdGx5YF0oaHR0cHM6Ly9wbG90bHkuY29tL3IvKToKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CmxpYnJhcnkocGxvdGx5KQpgYGAKYGBge3IsICwgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDEwfQpsaWJyYXJ5KHBsb3RseSkKcHAgPC0gcCArIGdlb21fbGluZSgpICsgY29vcmRfZml4ZWQocmF0aW8gPSA0KQpnZ3Bsb3RseShwcCkKYGBgCgpUaGVyZSBpcyBub3QgYWx3YXlzIGEgc2luZ2xlIGJlc3QgYXNwZWN0IHJhdGlvLgoKVGhlIGBjbzJgIGRhdGEgc2V0IGluIHRoZSBgZGF0YXNldHNgIHBhY2thZ2UgY29udGFpbnMgbW9udGhseQpjb25jZW50cmF0aW9ucyBvZiBDTzIgZm9yIHRoZSB5ZWFycyAxOTU5IHRvIDE5OTcgcmVjb3JkZWQgYXQgdGhlIE1hdW5hCkxvYSBvYnNlcnZhdG9yeS4KClRoZSBgY28yYCBkYXRhIGlzIHN0b3JlZCBhcyBhbiBvYmplY3Qgb2YgY2xhc3MgYHRzYDoKYGBge3J9CnN0cihjbzIpCmBgYAoKYHBsb3RgIGFuZCBgeHlwbG90YCBoYXZlIG1ldGhvZHMgZm9yIGB0c2Agb2JqZWN0cyB0aGF0IHNpbXBsaWZ5IHRpbWUKc2VyaWVzIHBsb3R0aW5nLgoKVHdvIGFzcGVjdCByYXRpb3M6CgpgYGB7ciAsIGZpZy5oZWlnaHQgPSA0LjUsIGZpZy53aWR0aCA9IDUuNX0KeHlwbG90KGNvMikKYGBgCgpgYGB7ciwgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDEwfQp4eXBsb3QoY28yLCBhc3BlY3QgPSAieHkiKQpgYGAKSXMgdGhlcmUgYSBzbG93ZG93biBpbiB0aGUgaW5jcmVhc2U/CgpBIGdyYXBoIHdpdGggbW9yZSByZWNlbnQgZGF0YSBpcwpbYXZhaWxhYmxlXShodHRwczovL2dtbC5ub2FhLmdvdi9jY2dnL3RyZW5kcy8pLgoKVGhlIGZ1bGwgZGF0YSBpcwpbYXZhaWxhYmxlXShodHRwczovL2dtbC5ub2FhLmdvdi93ZWJkYXRhL2NjZ2cvdHJlbmRzL2NvMi9jbzJfYW5ubWVhbl9tbG8udHh0KQphcyB3ZWxsLgoKYGF1dG9wbG90YCBkb2VzIG5vdCB5ZXQgcHJvdmlkZSBhbiBlYXN5IHdheSB0byBiYW5rIHRvIDQ1IGRlZ3JlZXM7IHlvdQpuZWVkIHRvIGV4cGVyaW1lbnQgb3IgY2FsY3VsYXRlIGFuIGFwcHJvcHJpYXRlIGFzcGVjdCByYXRpbyB5b3Vyc2VsZi4KClRoZSBkZWZhdWx0IGBhdXRvcGxvdGA6CgpgYGB7ciBjbzItYXV0b3Bsb3QtZGVmYXVsdCwgZWNobyA9IEZBTFNFfQpsaWJyYXJ5KGZvcmVjYXN0KQphdXRvcGxvdChjbzIpCmBgYAoKYGBge3IgY28yLWF1dG9wbG90LWRlZmF1bHQsIGV2YWwgPSBGQUxTRX0KYGBgCgpBc3BlY3QgcmF0aW8gYWRqdXN0ZWQgd2l0aCBgY29vcmRfZml4ZWRgOgoKYGBge3IsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMH0KcjQ1IDwtIG1lZGlhbihkaWZmKHRpbWUoY28yKSkpIC8gbWVkaWFuKGFicyhkaWZmKGNvMikpKQphdXRvcGxvdChjbzIpICsgY29vcmRfZml4ZWQocmF0aW8gPSByNDUpCmBgYCAKCgojIyBVcGRhdGVkIE1hdW5hIExvYSBDTzIgRGF0YQoKRG93bmxvYWRpbmcgYW5kIHJlYWRpbmcgaW4gdGhlIGRhdGE6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGx1YnJpZGF0ZSkKdXJsIDwtICJodHRwczovL2dtbC5ub2FhLmdvdi93ZWJkYXRhL2NjZ2cvdHJlbmRzL2NvMi9jbzJfbW1fbWxvLnR4dCIKaWYgKCEgZmlsZS5leGlzdHMoImNvMm5ldy5kYXQiKSB8fAogICAgbm93KCkgPiBmaWxlLm10aW1lKCJjbzJuZXcuZGF0IikgKyB3ZWVrcyg0KSkKICAgIGRvd25sb2FkLmZpbGUodXJsLCAiY28ybmV3LmRhdCIpCmNvMm5ldyA8LSByZWFkLnRhYmxlKCJjbzJuZXcuZGF0IikKYGBgCgpDbGVhbiB1cCB0aGUgdmFyaWFibGUgbmFtZXM6CmBgYHtyfQpoZWFkKGNvMm5ldykKbmFtZXMoY28ybmV3KSA8LSBjKCJ5ZWFyIiwgIm1vbnRoIiwgImRlY2ltYWxfZGF0ZSIsICJhdmVyYWdlIiwKICAgICAgICAgICAgICAgICAgICJkZXNlYXNvbiIsICJuZGF5cyIsICJzdGRkIiwgInVtbSIpCmBgYAoKQW4gaW5pdGlhbCBsb29rOgoKYGBge3J9CmdncGxvdChjbzJuZXcsIGFlcyhkZWNpbWFsX2RhdGUsIGF2ZXJhZ2UpKSArIGdlb21fbGluZSgpCmBgYAoKTWlzc2luZyB2YWx1ZXMgYXJlIGVuY29kZWQgYXMgYC05Ljk5YCBvciBgLTk5Ljk5YC4KCklmIHRoZXJlIGFyZSBhbnkgc3VjaCB2YWx1ZXMgdGhleSBuZWVkIHRvIGJlIHJlcGxhY2VkIHdpdGggUidzIG1pc3NpbmcKdmFsdWUgcmVwcmVzZW50YXRpb246CgpgYGB7cn0KZmlsdGVyKGNvMm5ldywgYXZlcmFnZSA8IC05KQpjbzJuZXcgPC0gbXV0YXRlKGNvMm5ldywgYXZlcmFnZSA9IGlmZWxzZShhdmVyYWdlIDwgLTksIE5BLCBhdmVyYWdlKSkKYGBgCgpUaGlzIHByb2R1Y2VzIGEgcmVhc29uYWJsZSBwbG90OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoY28ybmV3LCBhZXMoZGVjaW1hbF9kYXRlLCBhdmVyYWdlKSkgKwogICAgZ2VvbV9saW5lKCkKYGBgCgpUaGUgcGxvdCB3b3VsZCBzaG93IGdhcHMgZm9yIHRoZSBtaXNzaW5nIHZhbHVlcy4KCk1pc3NpbmcgdmFsdWVzIGNvdWxkIGFsc28gYmUgaW50ZXJwb2xhdGVkIHdpdGggYG5hLmludGVycGAgZnJvbQpwYWNrYWdlIGBmb3JlY2FzdGAuCgpDb252ZXJ0aW5nIHRoZSBuZXcgZGF0YSB0byBhIGB0c2Agb2JqZWN0IG1ha2VzIGFkZGluZyB0aGUgb3JpZ2luYWwKZGF0YSBhIGxpdHRsZSBlYXNpZXI6CgpgYGB7cn0KY28ybmV3VFMgPC0gdHMoY28ybmV3JGF2ZXJhZ2UsIHN0YXJ0ID0gYygxOTU4LCAzKSwgZnJlcXVlbmN5ID0gMTIpCmF1dG9wbG90KGNvMm5ld1RTKSArIGF1dG9sYXllcihjbzIpCmBgYAoKCiMjIFNlYXNvbmFsIERlY29tcG9zaXRpb24gb2YgVGltZSBTZXJpZXMgYnkgTG9lc3MgKFNUTCkKCkZvciB0aW1lIHNlcmllcyB3aXRoIGEgc3Ryb25nIHNlYXNvbmFsIGNvbXBvbmVudCBpdCBjYW4gYmUgdXNlZnVsIHRvCmxvb2sgYXQgYSBfU2Vhc29uYWwgRGVjb21wb3NpdGlvbiBvZiBUaW1lIFNlcmllcyBieSBMb2Vzc18sIG9yCihfU1RMXykuCgpUaGUgbWV0aG9kb2xvZ3kgd2FzIHN1Z2dlc3RlZCBieSBDbGV2YWxhbmQgYW5kIGNvd29ya2Vycy4KClRoZSBgc3RsYCBmdW5jdGlvbiBpbiB0aGUgYGJhc2VgIHBhY2thZ2UgY29tcHV0ZXMgc3VjaCBhCmRlY29tcG9zaXRpb24uCgpJdCByZXF1aXJlcyBhIHNlcmllcyB3aXRob3V0IG1pc3NpbmcgdmFsdWVzLgoKYGBge3J9CmNvMm5ld1RTX25vX05BIDwtIHRzKGNvMm5ldyRhdmVyYWdlLCBzdGFydCA9IGMoMTk1OCwgMyksIGZyZXF1ZW5jeSA9IDEyKQpjbzJzdGwgPC0gc3RsKGNvMm5ld1RTX25vX05BLCBzLndpbmRvdyA9IDIxKQpoZWFkKGNvMnN0bCR0aW1lLnNlcmllcykKYGBgCgpQYWNrYWdlIGBmb3JlY2FzdGAgcHJvdmlkZXMgYW4gYGF1dG9wbG90YCBtZXRob2QgZm9yIGBzdGxgIG9iamVjdHMgdGhhdApzaG93cyB0aGUgb3JpZ2luYWwgZGF0YSBhbmQgdGhlIHRocmVlIGNvbXBvbmVudHMgaW4gc2VwYXJhdGUgcGFuZWxzOgoKYGBge3IgY28yLXN0bC1wbG90LCBldmFsID0gRkFMU0V9CmF1dG9wbG90KGNvMnN0bCkKYGBgCgpgYGB7ciBjbzItc3RsLXBsb3QsIGVjaG8gPSBGQUxTRSwgZmlnLmhlaWdodCA9IDd9CmBgYAoKCiMjIFNvbWUgQWx0ZXJuYXRpdmUgVmlzdWFsaXphdGlvbnMKCkJhciBjaGFydHMgYXJlIHNvbWV0aW1lcyB1c2VkIHdoZW4gY29tcGFyaXNvbiB0byBhIGJhc2UgbGluZSBpcwppbXBvcnRhbnQ7IGZvciBleGFtcGxlIGZvciBbaWxsdXN0cmF0aW5nIGNsaW1hdGUKY2hhbmdlXShgciBJTUcoIkNhR19HbG9iYWxUZW1wQW5vbV8xLmpwZyIpYCkuICA8IS0tIG9yaWdpbmFsIGF0Cmh0dHBzOi8vd3d3LmNsaW1hdGUuZ292L3NpdGVzL2RlZmF1bHQvZmlsZXMvQ2FHX0dsb2JhbFRlbXBBbm9tXzEuanBnCkRhdGEgaXMgYXZhaWxhYmxlIGZyb20KaHR0cHM6Ly93d3cuY2xpbWF0ZS5nb3YvbWFwcy1kYXRhL2RhdGFzZXQvZ2xvYmFsLXRlbXBlcmF0dXJlLWFub21hbGllcy1ncmFwaGluZy10b29sCi0tPgoKQ2FsZW5kYXIgcGxvdHMsIG9yIGNhbGVuZGFyIGhlYXRtYXBzLCBhcmUgc29tZXRpbWVzIHVzZWZ1bCBmb3IKZGlzcGxheWluZyBkYWlseSBkYXRhIHdoZXJlIGRheSBvZiB0aGUgd2Vlay93ZWVrZW5kIGVmZmVjdHMgbWlnaHQgYmUKaW1wb3J0YW50LiAgT25lIGV4YW1wbGUgaXMgYXZhaWxhYmxlIFtoZXJlXShgciBJTUcoImNhbGVuZGFySGVhdG1hcC5wbmciKWApLgoKQSBbYmxvZyBwb3N0XShodHRwczovL2RvbWluaWtrb2NoLmdpdGh1Yi5pby9DYWxlbmRhci1IZWF0bWFwLykgc2hvd3MKaG93IHRvIGJ1aWxkIGEgY2FsZW5kYXIgcGxvdCB3aXRoIGBnZ3Bsb3RgLgoKCiMjIEdEUCBHcm93dGggRGF0YQoKQmFyIGNoYXJ0cyBhcmUgc29tZXRpbWVzIHVzZWQgZm9yIGVjb25vbWljIHRpbWUgc2VyaWVzLCBpbiBwYXJ0aWN1bGFyCmdyb3d0aCByYXRlIGRhdGEuCgpTb21lIEdEUCBncm93dGggcmF0ZSBkYXRhIGlzIGF2YWlsYWJsZSBvbiB0aGlzClt3ZWIgcGFnZV0oaHR0cHM6Ly93d3cubXVsdHBsLmNvbS91cy1yZWFsLWdkcC1ncm93dGgtcmF0ZS90YWJsZS9ieS1xdWFydGVyKToKClJlYWQgdGhlIGRhdGEgZnJvbSB0aGUgd2ViIHBhZ2U6CgpgYGB7ciwgY2FjaGUgPSBUUlVFfQpsaWJyYXJ5KHJ2ZXN0KQpnZHB1cmwgPC0gImh0dHA6Ly93d3cubXVsdHBsLmNvbS91cy1yZWFsLWdkcC1ncm93dGgtcmF0ZS90YWJsZS9ieS1xdWFydGVyIgpnZHBwYWdlIDwtIHJlYWRfaHRtbChnZHB1cmwpCmdkcHRibCA8LSBodG1sX3RhYmxlKGdkcHBhZ2UpW1sxXV0KaGVhZChnZHB0YmwpCmBgYAoKQ2xlYW4gdGhlIGRhdGE6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbmFtZXMoZ2RwdGJsKSA8LSBjKCJEYXRlIiwgIlZhbHVlIikKZ2RwdGJsIDwtIG11dGF0ZShnZHB0YmwsCiAgICAgICAgICAgICAgICAgVmFsdWUgPSBhcy5udW1lcmljKHN1YigiJSIsICIiLCBWYWx1ZSkpLAogICAgICAgICAgICAgICAgIERhdGUgPSBtZHkoRGF0ZSkpCmhlYWQoZ2RwdGJsKQpgYGAKCkRhdGEgcHJpb3IgdG8gMTk0NyBpcyBhbm51YWwsIHNvIGRyb3AgZWFybGllciBkYXRhOgoKYGBge3J9CmdkcHRibCA8LSBmaWx0ZXIoZ2RwdGJsLAogICAgICAgICAgICAgICAgIERhdGUgPj0gbWFrZV9kYXRlKDE5NDgsIDEsIDEpKQpgYGAKClJlYXJyYW5nZSB0byBwdXQgYERhdGVgIGluIGluY3JlYXNpbmcgb3JkZXI6CgpgYGB7cn0KZ2RwdGJsIDwtIGFycmFuZ2UoZ2RwdGJsLCBEYXRlKQpgYGAKCkNyZWF0ZSBhbmQgcGxvdCBhIHRpbWUgc2VyaWVzOgoKYGBge3IgZ2RwLXBsb3QsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGZvcmVjYXN0KQpnZHB0cyA8LSB0cyhnZHB0YmwkVmFsdWUsCiAgICAgICAgICAgIHN0YXJ0ID0gMTk0OCwgZnJlcXVlbmN5ID0gNCkKYXV0b3Bsb3QoZ2RwdHMpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMikKYGBgCgpgYGB7ciBnZHAtcGxvdCwgZWNobyA9IEZBTFNFfQpgYGAKCkEgYmFyIGNoYXJ0IHdpdGggY29sb3IgdXNlZCB0byBkaXN0aW5ndWlzaCBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUKZ3Jvd3RoIGNhbiBiZSBjcmVhdGVkIGZyb20gdGhlIG9yaWdpbmFsIHRhYmxlIGBnZHB0YmxgOgoKYGBge3IgZ2RwLWJhciwgZXZhbCA9IEZBTFNFLCBkcGkgPSAzMDB9CmxpYnJhcnkoZ2dwbG90MikKbXV0YXRlKGdkcHRibCwKICAgICAgIGdyb3d0aCA9IGlmZWxzZShWYWx1ZSA+PSAwLAogICAgICAgICAgICAgICAgICAgICAgICJwb3MiLAogICAgICAgICAgICAgICAgICAgICAgICJuZWciKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBEYXRlLAogICAgICAgICAgICAgICB5ID0gVmFsdWUsCiAgICAgICAgICAgICAgIGZpbGwgPSBncm93dGgpKSArCiAgICBnZW9tX2NvbCgpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKHBvcyA9ICJibHVlIiwgbmVnID0gInJlZCIpKQpgYGAKCmBgYHtyIGdkcC1iYXIsIGVjaG8gPSBGQUxTRX0KYGBgCgpUaGUgYmFyIGNoYXJ0IGNhbiBhbHNvIGJlIGNyZWF0ZWQgZnJvbSB0aGUgdGltZSBzZXJpZXMgb2JqZWN0OgoKYGBge3IgZ2RwLWJhci10cywgZXZhbCA9IEZBTFNFLCBkcGkgPSAzMDB9CmRhdGEuZnJhbWUocmF0ZSA9IGFzLm51bWVyaWMoZ2RwdHMpLAogICAgICAgICAgIHRpbWUgPSBhcy5udW1lcmljKHRpbWUoZ2RwdHMpKSkgJT4lCiAgICBtdXRhdGUoZ3Jvd3RoID0gaWZlbHNlKHJhdGUgPj0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgInBvcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJuZWciKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHRpbWUsIHJhdGUsIGZpbGwgPSBncm93dGgpKSArCiAgICBnZW9tX2NvbCgpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKHBvcyA9ICJibHVlIiwgbmVnID0gInJlZCIpKQpgYGAKCmBgYHtyIGdkcC1iYXItdHMsIGVjaG8gPSBGQUxTRX0KYGBgCgoKIyMgQWlybGluZSBGbGlnaHRzIEZyb20gTllDCgpUaGUgYG55Y2ZsaWdodHMxM2AgZGF0YSBzZXQgY29udGFpbnMgZGF0YSBvbiBhbGwgZmxpZ2h0IGxlYXZpbmcgTmV3CllvcmsgQ2l0eSBpbiAyMDEzLgoKU2ltaWxhciBkYXRhIGlzIGF2YWlsYWJsZSBmb3Igb3RoZXIgY2l0aWVzIGFuZCB5ZWFycy4KCldlIGNhbiBsb29rIGF0IHRoZSBudW1iZXIgb2YgZGVwYXJ0dXJlcyBmb3IgZWFjaCBkYXk6CgpgYGB7cn0KbGlicmFyeShueWNmbGlnaHRzMTMpCmxpYnJhcnkoZ2dwbG90MikKbmYgPC0gY291bnQoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkKYGBgCgpJdCB3aWxsIGJlIHVzZWZ1bCB0byBoYXZlIGEgY29tYmluZWQgYGRhdGVgIGZpZWxkIGluIG91ciBkYXRhIGZyYW1lLgoKVGhlIGBsdWJyaWRhdGVgIGZ1bmN0aW9uIGBtYWtlX2RhdGVgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIGZvciB0aGlzOgoKYGBge3J9CmxpYnJhcnkobHVicmlkYXRlKQpuZiA8LSBtdXRhdGUobmYsIGRhdGUgPSBtYWtlX2RhdGUoeWVhciwgbW9udGgsIGRheSkpCmBgYAoKQSBsaW5lIHBsb3Qgb2YgdGhlIG51bWJlciBvZiBmbGlnaHRzOgoKYGBge3IgZmxpZ2h0cy0xLCBlY2hvID0gRkFMU0V9CmdncGxvdChuZiwgYWVzKGRhdGUsIG4pKSArCiAgICBnZW9tX2xpbmUoKQpgYGAKYGBge3IgZmxpZ2h0cy0xLCBldmFsID0gRkFMU0V9CmBgYAoKV2Vla2RheSB2YXJpYXRpb24gaXMgY2xlYXJseSB2aXNpYmxlLgoKQnJlYWtpbmcgb3V0IHRoZSBkYXlzIG9mIHRoZSB3ZWVrIGludG8gc2VwYXJhdGUgZmFjZXRzIG1heSBoZWxwOgoKYGBge3IgZmxpZ2h0cy0yLCBlY2hvID0gRkFMU0V9Cm5uZiA8LQogICAgbXV0YXRlKG5mLCB3ZCA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSkKCm5uZl90aG0gPC0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0KICAgICAgICAgICAgICBlbGVtZW50X3RleHQoYW5nbGUgPSAtNDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMCksCiAgICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbihyID0gMzApKQoKZ2dwbG90KG5uZiwgYWVzKGRhdGUsIG4pKSArCiAgICBnZW9tX2xpbmUoKSArCiAgICBmYWNldF93cmFwKH4gd2QpICsKICAgIG5uZl90aG0KYGBgCgpgYGB7ciBmbGlnaHRzLTIsIGV2YWwgPSBGQUxTRX0KYGBgCgpKdWx5IDQgaW4gMjAxMzoKCmBgYHtyfQphcy5jaGFyYWN0ZXIod2RheSh5bWQoIjIwMTMtMDctMDQiKSwKICAgICAgICAgICAgICAgICAgbGFiZWwgPSBUUlVFKSkKYGBgCgpJZGVudGlmeWluZyB3aGVuIHNvbWUgbWFqb3IgaG9saWRheXMgb2NjdXIgbWF5IGFsc28gYmUgdXNlZnVsOgoKYGBge3IgZmxpZ2h0cy0zLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcuNX0KaG9saWRheXMgPC0KICAgIGRhdGEuZnJhbWUoSG9saWRheSA9IGMoIjR0aCBvZiBKdWx5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRoYW5rc2dpdmluZyIpLAogICAgICAgICAgICAgICBkYXRlID0geW1kKGMoIjIwMTMtMDctMDQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIjIwMTMtMTEtMjgiKSkpCmhsYXllciA8LQogICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IGRhdGUsCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IEhvbGlkYXkpLAogICAgICAgICAgICAgICBsaW5ldHlwZSA9IDIsCiAgICAgICAgICAgICAgIGxpbmV3aWR0aCA9IDEsCiAgICAgICAgICAgICAgIGRhdGEgPSBob2xpZGF5cykKZ2dwbG90KG5mLCBhZXMoZGF0ZSwgbikpICsKICAgIGhsYXllciArCiAgICBnZW9tX2xpbmUoKQpgYGAKYGBge3IgZmxpZ2h0cy0zLCBldmFsID0gRkFMU0V9CmBgYAoKSG9saWRheXMgaW4gdGhlIGZhY2V0ZWQgcGxvdDoKCmBgYHtyIGZsaWdodHMtaG9saWRheS1mYWNldC0xLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcuNX0KZ2dwbG90KG5uZiwgYWVzKGRhdGUsIG4pKSArCiAgICBobGF5ZXIgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZmFjZXRfd3JhcCh+IHdkKSArCiAgICBubmZfdGhtCmBgYAoKYGBge3IgZmxpZ2h0cy1ob2xpZGF5LWZhY2V0LTEsIGV2YWwgPSBGQUxTRX0KYGBgCgpUaGlzIG1hcmtzIHRoZSBob2xpZGF5IHdlZWtzLgoKSG9saWRheXMgaW4gdGhlIGZhY2V0ZWQgcGxvdDoKCmBgYHtyIGZsaWdodHMtaG9saWRheS1mYWNldC0yLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcuNX0KaGxheWVyMiA8LQogICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IGRhdGUsCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IEhvbGlkYXkpLAogICAgICAgICAgICAgICBsaW5ld2lkdGggPSAxLAogICAgICAgICAgICAgICBkYXRhID0gbXV0YXRlKGhvbGlkYXlzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdkID0gd2RheShkYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFKSkpCmdncGxvdChubmYsIGFlcyhkYXRlLCBuKSkgKwogICAgaGxheWVyMiArCiAgICBnZW9tX2xpbmUoKSArCiAgICBmYWNldF93cmFwKH4gd2QpICsKICAgIG5uZl90aG0KYGBgCgpUaGlzIHNob3dzIG9ubHkgdGhlIGhvbGlkYXkgZGF5cy4KCmBgYHtyIGZsaWdodHMtaG9saWRheS1mYWNldC0yLCBldmFsID0gRkFMU0V9CmBgYAoKSG9saWRheXMgaW4gdGhlIGZhY2V0ZWQgcGxvdDoKCmBgYHtyIGZsaWdodHMtaG9saWRheS1mYWNldC0zLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcuNX0KZ2dwbG90KG5uZiwgYWVzKGRhdGUsIG4pKSArCiAgICBobGF5ZXIgKwogICAgaGxheWVyMiArCiAgICBnZW9tX2xpbmUoKSArCiAgICBmYWNldF93cmFwKH4gd2QpICsKICAgIG5uZl90aG0KYGBgClNob3cgaG9saWRheXMgYXMgc29saWQgbGluZXMsIGRheXMgaW4gaG9saWRheSB3ZWVrcyBhcyBkYXNoZWQgbGluZXMuCgpgYGB7ciBmbGlnaHRzLWhvbGlkYXktZmFjZXQtMywgZXZhbCA9IEZBTFNFfQpgYGAKCkEgc2ltcGxlIGNhbGVuZGFyIHBsb3Q6CgpgYGB7ciBmbGlnaHRzLWNhbC0wLCBlY2hvID0gRkFMU0V9Cm1vbnRod2VlayA8LSBmdW5jdGlvbihkLCB3KQogICAgY2VpbGluZygoZCAtIHcpIC8gNykgKyAxCgpuZiA8LSBtdXRhdGUobmYsCiAgICAgICAgICAgICB3ZCA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSwKICAgICAgICAgICAgIHdkID0gZmN0X3Jldih3ZCksCiAgICAgICAgICAgICBtdyA9IG1vbnRod2VlayhkYXksIHdkYXkoZGF0ZSkpKQoKcCA8LSBnZ3Bsb3QobmYsIGFlcyh4ID0gYXMuY2hhcmFjdGVyKG13KSwKICAgICAgICAgICAgICAgICAgICB5ID0gd2QsCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IG4pKSArCiAgICBnZW9tX3RpbGUoY29sb3IgPSAid2hpdGUiKSArCiAgICBmYWNldF93cmFwKH4gbW9udGgoZGF0ZSwgbGFiZWwgPSBUUlVFKSkKcApgYGAKVGhpcyB1c2VzIGBnZW9tX3RpbGVgIGFuZCBmYWNldGluZy4KCmBgYHtyIGZsaWdodHMtY2FsLTAsIGV2YWwgPSBGQUxTRX0KYGBgCgpTb21lIHRoZW1lIGFuZCBjb2xvciBhZGp1c3RtZW50czoKCmBgYHtyIGZsaWdodHMtY2FsLTEsIGVjaG8gPSBGQUxTRX0KcCA8LSBwICsKICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIndoaXRlIikgKwogICAgeWxhYigiIikgKyB4bGFiKCJXZWVrIG9mIE1vbnRoIikgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkKcApgYGAKYGBge3IgZmxpZ2h0cy1jYWwtMSwgZXZhbCA9IEZBTFNFfQpgYGAKClRoaXMgbGF5b3V0IHdpdGggZGF5cyBvbiB0aGUgdmVydGljYWwgYXhpcyB3b3JrcyB3ZWxsIHdpdGggYSBzaW5nbGUgcm93LAp3aGljaCBjb3VsZCBiZSBleHRlbmRlZCB0byBhIG11bHRpLXllYXIgcGxvdDoKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMiwgZmlnLndpZHRoID0gMTB9CnAgKyBmYWNldF93cmFwKH4gbW9udGgoZGF0ZSwgVFJVRSksIG5yb3cgPSAxKQpgYGAKCkEgbW9yZSBzdGFuZGFyZCBsYXlvdXQ6CgpgYGB7ciBmbGlnaHRzLWNhbC0zLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDh9Cm5mMiA8LQogICAgbXV0YXRlKG5mLAogICAgICAgICAgIHdkID0gd2RheShkYXRlLCBsYWJlbCA9IFRSVUUpLAogICAgICAgICAgIG13ID0gZmFjdG9yKG1vbnRod2VlayhkYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdkYXkoZGF0ZSkpKSwKICAgICAgICAgICBtdyA9IGZjdF9yZXYobXcpKQoKZ2dwbG90KG5mMiwgYWVzKHggPSB3ZCwgeSA9IG13LCBmaWxsID0gbikpICsKICAgIGdlb21fdGlsZShjb2xvciA9ICJ3aGl0ZSIpICsKICAgIGZhY2V0X3dyYXAofiBtb250aChkYXRlLCBUUlVFKSkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiKSArCiAgICB5bGFiKCIiKSArIHhsYWIoIldlZWsgb2YgTW9udGgiKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPQogICAgICAgICAgICAgIGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LAogICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpLAogICAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQpgYGAKYGBge3IgZmxpZ2h0cy1jYWwtMywgZXZhbCA9IEZBTFNFLCBmaWcud2lkdGggPSA4fQpgYGAKCgojIyBSZWFkaW5nCgpDaGFwdGVycyBbX1Zpc3VhbGl6aW5nIHRpbWUgc2VyaWVzIGFuZCBvdGhlciBmdW5jdGlvbnMgb2YgYW4KICBpbmRlcGVuZGVudAogIHZhcmlhYmxlX10oaHR0cHM6Ly9jbGF1c3dpbGtlLmNvbS9kYXRhdml6L3RpbWUtc2VyaWVzLmh0bWwpIGFuZAogIFtfVmlzdWFsaXppbmcKICB0cmVuZHNfXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovdmlzdWFsaXppbmctdHJlbmRzLmh0bWwpIGluCiAgW19GdW5kYW1lbnRhbHMgb2YgRGF0YQogIFZpc3VhbGl6YXRpb25fXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovKS4KCgojIyBFeGVyY2lzZXMKCjEuIEEgdXNlZnVsIGZlYXR1cmUgb2YgdGhlIGBsYXR0aWNlYCBwYWNrYWdlIGlzIHRoZSBhYmlsaXR5IHRvIGVhc2lseQogICAgcmVxdWVzdCBhbiBhc3BlY3QgcmF0aW8gdGhhdCBpcyBfYmFua2VkIHRvIDQ1IGRlZ3JlZXNfIGJ5CiAgICBzcGVjaWZ5aW5nIGBhc3BlY3QgPSAieHkiYCBpbiB0aGUgYXJndW1lbnRzIHRvIGBsYXR0aWNlYCBwbG90dGluZwogICAgZnVuY3Rpb25zLiBGb3IgZXhhbXBsZSwgVGhpcyBjb2RlIHByb2R1Y2VzIGEgcmVhc29uYWJsZSBhc3BlY3QKICAgIHJhdGlvIGZvciBhIHBsb3Qgb2YgYW4gdW5lbXBsb3ltZW50IHRpbWUgc2VyaWVzOgoKICAgIGBgYHtyfQogICAgbGlicmFyeShnZ3Bsb3QyKQogICAgbGlicmFyeShmb3JlY2FzdCkKICAgIGxpYnJhcnkobGF0dGljZSkKICAgIHVuZW1wX3RzIDwtIHRzKGVjb25vbWljcyR1bmVtcGxveSwgc3RhcnQgPSBjKDE5NjcsIDcpLCBmcmVxdWVuY3kgPSAxMikKICAgIHh5cGxvdCh1bmVtcF90cywgYXNwZWN0ID0gInh5IikKICAgIGBgYAoKICAgIFdoaWNoIG9mIHRoZSBmb2xsb3dpbmcgcGxvdHMgbW9zdCBjbG9zZWx5IG1hdGNoZXMgdGhlIGFzcGVjdCByYXRpbwogICAgaW4gdGhlIGBsYXR0aWNlYCBwbG90PwoKICAgIGEuIGBhdXRvcGxvdCh1bmVtcF90cywgYXNwZWN0ID0gMjAwKWAKICAgIGIuIGBhdXRvcGxvdCh1bmVtcF90cykgKyBjb29yZF9maXhlZChyYXRpbyA9IDAuMDAwNylgCiAgICBjLiBgYXV0b3Bsb3QodW5lbXBfdHMpICsgY29vcmRfZml4ZWQoYXNwID0gMC4wMDA3KWAKICAgIGQuIGBhdXRvcGxvdCh1bmVtcF90cykgKyBjb29yZF9maXhlZCg3MClgCg==