library(readr)
library(ggplot2)
library(dplyr)
library(scales)
library(reshape2)
library(broom)
library(TSA)
library(forecast)
nasdaq <- nasdaq[-1,]
nasdaq <- nasdaq %>% mutate(date=as.Date(nasdaq$date)) %>%
dplyr::select(-volume)
nasdaq_long <- nasdaq %>% melt(id.vars="date")
nasdaq <- nasdaq %>% mutate(average=(close+open+high+low)/4)
ggplot(nasdaq_long) +
geom_point(aes(x=date, y=value, group=variable, color=variable), alpha=0.7) +
geom_segment(data=nasdaq, aes(x=date, xend=date, y=low, yend=high), alpha=0.3) +
geom_line(data=nasdaq, aes(x=date, y=average), lwd=1, col="darkred") +
ylab("NASDAQ") +
ggtitle("Nasdaq Stock Market Index (27-FEB-2019 TO 28-MAY-2019)") +
theme_minimal() +
scale_colour_brewer(palette="Reds")
If you’re partial to watching the news on television, the NASDAQ stock market index is a natural sight. The y-axis in the plot above represents a “snapshot” stock market performance and the x-axis is time. The data presented in the plot are a “time series” because they are repeated measurements taken over a time period. The data above were downloaded from the Nasdaq website and cover a three month period from the end of February to the end of May. Over this period, the index peaked in early March.
If you prefer personalizing your periodicals on Twitter over television news, you are familiar with the concept of “trending”. While no time series plots are visible to Twitter users, data scientists use time series trends to determine which hashtags are the most popular according to your account preferences.
By default, Twitter accounts show personalized trends on the homepage. That is, the trends you see take into account your location and who you follow to show you what is important to you. (Which has some dangerous aftermath, but this thought experiment will be left as exercise to the reader.)
The figure to the right is a screenshot of US trends from the 28th of May. The US Twitter community celebrated the life of Harambe three years after his passing and the #AnimalKingdom. In fourth place after National Hamburger Day, Justice Clarence Thomas was trending for his “bizarre attack on birth control” associating abortions with racial eugenics.
Reflecting Twitter’s fast-paced stream, the countrywide trends changed completely by the next day.
Why time series?
Stock market averages are tracked and predicted by time series analysis to keep investors well-informed. Twitter users keep up-to-date with their cyberworld using trending hashtags detected by time series algorithms. Population figures, monthly airline passengers, and temperature can also be represented by time series data to equip the city for an influx of new citizens, to use airline resources efficiently, and to reveal effects of global warming.
We use time series analysis to describe and predict the relationship between our observations through time.
Statistically speaking
Time series is a branch of statistics that deals with temporal data and is also the name for those data. We analyze time series using time series. (That sounds rather unhelpful!) The Nasdaq data we visualized above is an example of a time series plot.
A time series (type of data) can be denoted as \(\{x_t\} = \{x_1, x_2, ..., x_t\}\) where the \(x\)’s are observations and the indices represent their temporal order.
A time series model of these data is a joint distribution for the random variables \(\{X_t\} = \{X_1, X_2, ..., X_t\}\) that would give rise to our data \(\{x_t\}\).
The classical decomposition model rewrites the time series model \(\{X_t\}\) in three components: trend (\(m_t\)), seasonality (\(s_t\)), and random noise residuals (\(Y_t\)). We can write the model as an addition between the three components as follows:
\[
X_t = m_t + s_t + Y_t.
\]
The model assumes that the components have an additive relationship (a log transformation must be taken when the relationship is multiplicative). Our goal is to subtract trend and seasonality from our data and examine the noise component \(Y_t = X_t - m_t - s_t\). Upon retrieval of our noise data \(y_t\), we want to test whether it is stationary or not. Stationarity of a time series is a requirement of the residuals of a time series for modeling.
The strict stationarity of a time series implies that for all integers \(h\) (lag), \(\{X_t\}\) and \(\{X_{t+h}\}=\{X_{1+h}, X_{2+h},...,X_{n+h}\}\) are distributed the same. Weak stationarity implies
\(\mu_X(t)\) is independent of \(t\) and
\(\gamma_X(t+h,t)\) is independent of \(t\) for each \(h\).
The independence of means and covariances with given lag \(h\) from time enable us (the modelers) to create mathematical descriptions and predict new values using time series.
How to properly time travel
The classical decomposition model requires us to separate our trend and seasonality components from random noise. Recall the Nasdaq data from earlier. For a clean look, I only kept the average index per day in the following time series plot.
ggplot(data=nasdaq, aes(x=date, y=average)) +
geom_line(lwd=1, col="pink3") +
geom_point(col="darkred") +
ylab("NASDAQ") +
ggtitle("Nasdaq Stock Market Index (27-FEB-2019 TO 28-MAY-2019)") +
theme_minimal() +
scale_colour_brewer(palette="Reds")
In statistics, we canonically learn to model trends with linear regression. That is, we take \(y=mx+b\) and make a line of best fit. Using ordinary least squares, we can fit coefficients using lm()
from base R
. How to fit a sinusoidal (curvy/up and down pattern/sine-wave-like) seasonality component is less taught, but we can use harmonic regression to do this. Methods to separate trend/seasonality that are particular to and popular in time series are differencing and using moving averages.
With so many methods available to estimate trend and seasonality, I was immediately confused on which route was most correct. My reflex was to ask one of my favorite advisors, Dr. Alex Aue. Here’s part of our email thread. He advised me that there is no general rule on how to choose which method to remove trend and seasonality. After estimating trend and seasonality using any method, an assessment of the random noise residuals will show which method works the best.
The following is a rudimentary attempt to fit a time series model. I will not be heavily checking assumptions. The point of the next few subsections is to demonstrate simple examples.
The trend component
One way to estimate the trend component is linear regression. In R
, we can use the lm()
function. See the block of code by clicking the “Code” button.
nasdaq <- nasdaq %>% mutate(nm_date=as.numeric(date))
straight <- lm(average ~ nm_date, data=nasdaq)
parabola <- lm(average ~ nm_date + I(nm_date^2), data=nasdaq)
cubic <- lm(average ~ nm_date + I(nm_date^2) + I(nm_date^3), data=nasdaq)
The plot below shows all three trend lines atop the original Nasdaq data.
fitted_df <- data.frame(cbind(straight=straight$fitted.values, parabola=parabola$fitted.values, cubic=cubic$fitted.values))
nasdaq_fits <- cbind(nasdaq, fitted_df)
nasdaq_fits <- nasdaq_fits %>% dplyr::select(date, nm_date, average, straight, parabola, cubic)
ggplot(data=nasdaq_fits, aes(x=date, y=average)) +
geom_line(col="darkgrey") +
geom_point(col="grey2") +
geom_line(aes(x=date, y=straight), lwd=1, col="red4") +
geom_line(aes(x=date, y=parabola), alpha=0.3, lwd=2, col="darkred") +
geom_line(aes(x=date, y=cubic), col="red3") +
ylab("NASDAQ") +
ggtitle("Nasdaq Stock Market Index (27-FEB-2019 TO 28-MAY-2019)") +
theme_minimal() +
scale_colour_brewer(palette="Reds")
The straight line is improper for these data. I should not have fitted a straight line to these data in the first place, but some relentless souls will still do it and call it a day. To educate away from this doctrine, notice how the parabolic and cubic regression lines (plotted almost atop each other) fit the data’s shape much better.
To model the trend of this time series, we will choose the parabola. The following output show the coefficients and associated p-values.
tidy(parabola)
The following is the formula for our trend line based off the previous output.
\[
m_t = 337666.6 -37.5t + 0.001t^2
\]
We can now perform the subtraction of the fitted points from our actual data.
\[
X_t - m_t = s_t + Y_t
\]
On a plot, our detrended data look like this.
nasdaq_fits <- nasdaq_fits %>%
select(date, nm_date, average, parabola) %>%
mutate(detrended=average-parabola)
ggplot(data=nasdaq_fits, aes(x=date, y=detrended)) +
geom_line(lwd=1, col="pink3") +
geom_point(col="darkred") +
ylab("NASDAQ") +
ggtitle("Nasdaq Stock Market Index (27-FEB-2019 TO 28-MAY-2019)") +
theme_minimal() +
scale_colour_brewer(palette="Reds")
The data have been transformed to oscillate about the horizontal line about the data’s mean (\(\approx 0\)).
ggplot(data=nasdaq_fits, aes(y=detrended)) +
geom_boxplot(col="black", fill="pink3") +
ylab("NASDAQ") +
xlab("") +
ggtitle("Nasdaq Stock Market Index (27-FEB-2019 TO 28-MAY-2019)") +
theme_minimal()
Two outlier index measures took place at the start of May.
The seasonality component
In order to fit the seasonality component, we will use harmonic()
from library(TSA)
. Using this library took quite a bit of work to convert from dataframe analyses into time series (data format for library(TSA)
).
full_dates <- seq(min(nasdaq_fits$date), max(nasdaq_fits$date), by="day")
ix_order <- sapply(1:length(full_dates), function(x) {
if (any(full_dates[x]==nasdaq_fits$date)) {
which(full_dates[x] == nasdaq_fits$date)
} else {
NA
}
})
nasdaq_mat <- cbind(full_dates, detrended=nasdaq_fits$detrended[ix_order])
nasdaq_ts <- ts(data=nasdaq_mat[,2],
start = c(2019,
as.numeric(format(full_dates[1], "%j"))),
frequency = 365)
When we let there be 2 harmonics fit, the fitted values look like this. These waves do not well describe the seasonality.
harm_val <- harmonic(nasdaq_ts, m=2)
harm_fit <- lm(nasdaq_ts~harm_val)
nasdaq_fits$fit_harm <- harm_fit$fitted.values
nasdaq_fits <- nasdaq_fits %>% mutate(deseasoned=detrended-fit_harm)
ggplot(data=nasdaq_fits, aes(x=date, y=fit_harm)) +
geom_point(data=nasdaq_fits, aes(x=date, y=detrended), col="darkgrey") +
geom_line(data=nasdaq_fits, aes(x=date, y=detrended), col="grey") +
ylab("NASDAQ") +
geom_line(lwd=1, col="pink3") +
geom_point(col="darkred") +
ggtitle("Nasdaq Stock Market Index (27-FEB-2019 TO 28-MAY-2019)") +
theme_minimal() +
scale_colour_brewer(palette="Reds")
When we let there be 3 harmonics fit, the fitted values look like this. This harmonic regression better fits our data.
harm_val <- harmonic(nasdaq_ts, m=3)
harm_fit <- lm(nasdaq_ts~harm_val)
nasdaq_fits$fit_harm <- harm_fit$fitted.values
nasdaq_fits <- nasdaq_fits %>% mutate(deseasoned=detrended-fit_harm)
ggplot(data=nasdaq_fits, aes(x=date, y=fit_harm)) +
geom_point(data=nasdaq_fits, aes(x=date, y=detrended), col="darkgrey") +
geom_line(data=nasdaq_fits, aes(x=date, y=detrended), col="grey") +
ylab("NASDAQ") +
geom_line(lwd=1, col="pink3") +
geom_point(col="darkred") +
ggtitle("Nasdaq Stock Market Index (27-FEB-2019 TO 28-MAY-2019)") +
theme_minimal() +
scale_colour_brewer(palette="Reds")
Noise
ggplot(data=nasdaq_fits, aes(x=date, y=deseasoned)) +
geom_line(lwd=1, col="pink3") +
geom_point(col="darkred") +
ggtitle("Nasdaq Stock Market Index (27-FEB-2019 TO 28-MAY-2019)") +
theme_minimal() +
scale_colour_brewer(palette="Reds")
Test for stationarity
To test if this white noise is stationary, we will use the Ljung-Box test. The lag I will input will be min(2*d, n/5)
. I will let my period be one week, therefore our period \(d=7\), and \(n=\) 63.
\(H_0\): The model does not show a lack of fit. The model’s does not show autocorrelation. (The model’s a-okay.)
\(H_1:\) The model shows a lack of fit.
d <- 7
n <- nrow(nasdaq_fits)
box_lag <- min(2*d, n/5)
Box.test(nasdaq_fits$deseasoned, lag=box_lag, type="Ljung-Box")
Box-Ljung test
data: nasdaq_fits$deseasoned
X-squared = 48.296, df = 12.6, p-value = 4.353e-06
The p-value above is super small, so we cannot reject the null hypothesis above. Our model’s not doing so well.
Diagnostic plots (ACF/PACF)
nasdaq_acf <- acf(nasdaq_fits$deseasoned, plot=FALSE)
acf_values <- as.numeric(unlist(nasdaq_acf[[1]]))
nasdaq_pacf <- pacf(nasdaq_fits$deseasoned, plot=FALSE)
pacf_values <- as.numeric(unlist(nasdaq_pacf[[1]]))
diagnostic_df <- data.frame(cbind(lags=1:length(acf_values), acf=acf_values, pacf=pacf_values))
ci <- 0.95
bound <- qnorm((1 + ci)/2)/sqrt(n)
The following is an autocorrelation function (ACF) plot. There are significant spikes at the frist and second lag. The 17th lag is also significant.
ggplot(diagnostic_df, aes(x=lags, y=acf)) +
geom_hline(yintercept=c(bound, -bound), col="pink2", lty=2) +
geom_hline(yintercept=0, col="pink3") +
geom_segment(aes(x=lags, xend=lags, y=0, yend=acf), lwd=2, col="darkred") +
theme_minimal()
The following is a partial autocorrelation function (PACF) plot. The PACF plot shows a decay toward 0.
ggplot(diagnostic_df, aes(x=lags, y=pacf)) +
geom_hline(yintercept=c(bound, -bound), col="pink2", lty=2) +
geom_hline(yintercept=0, col="pink3") +
geom_segment(aes(x=lags, xend=lags, y=0, yend=pacf), lwd=2, col="darkred") +
theme_minimal()
These plots show that there is still strong dependence structures between the observations between the present observation and the two observations before. Therefore, our model is not capturing all of the dependence between time and the Nasdaq index.
So, what?
We need to restrategize. In this blog, we covered classical decomposition in a way that you can jump into time series by knowing linear regression and understanding the gist harmonic regression. In blogs to come, I will go over more methods like differencing, moving averages, and ARIMA models. These will require some more statistical theory to understand, so continue to bare with me!
If you have questions or corrections, please do route them over to my email.
Contributions
Thank you to my first reader, Asem!
References
Brockwell, P. J., Davis, R. A., & Fienberg, S. E. (1991). Time Series: Theory and Methods: Theory and Methods. Springer Science & Business Media.
Hom, Elaine J. “What is NASDAQ?”. (2012.) [URL]
Hyndman, R.J., & Athanasopoulos, G. (2018) Forecasting: principles and practice, 2nd edition, OTexts: Melbourne, Australia. OTexts.com/fpp2. Accessed on 30 May 2019.
Lotan, Gilad. “#FreddieGray – is not trending on Twitter?” (2015.) [URL]
Millheiser, Ian. “Justice Thomas launches an utterly bizarre attack on birth control”. (2019.) [URL]
Needle, Sarah. “How Does Twitter Decide What Is Trending?”. (2016.) [URL]
Rich, Kyle T. “Stationarity Testing”. (2017). [URL]
Shumway, R. H., & Stoffer, D. S. (2017). Time series analysis and its applications: with R examples. Springer.
Simpson, Gavin. StackOverflow answer. (2015). [URL]
Stirling, Doug. “CAST”. [URL]
Data Sources
Nasdaq Historical Quote Download. [URL]
nasdaq %>% arrange(date)
LS0tCnRpdGxlOiAiSW50cm8gdG8gVGltZSBTZXJpZXMgW1RTMV0iCmF1dGhvcjogRWRpZSBFc3Blam8KZGF0ZTogMjggTWF5IDIwMTkKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBmaWdfY2FwdGlvbjogeWVzCiAgICB0aGVtZTogc3BhY2VsYWIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSkKYGBgCgpgYGB7ciBsaWJyYXJpZXMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkcGx5cikKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkoVFNBKQpsaWJyYXJ5KGZvcmVjYXN0KQpgYGAKCmBgYHtyIGRhdGEtbG9hZCwgaW5jbHVkZT1GQUxTRX0KbmFzZGFxIDwtIHJlYWRfY3N2KCIuLi8uLi9kYXRhL25hc2RhcS1oaXN0b3J5LTIuY3N2IikKYGBgCgpgYGB7ciBuYXNkYXEtcGxvdH0KbmFzZGFxIDwtIG5hc2RhcVstMSxdCm5hc2RhcSA8LSBuYXNkYXEgJT4lIG11dGF0ZShkYXRlPWFzLkRhdGUobmFzZGFxJGRhdGUpKSAlPiUKICBkcGx5cjo6c2VsZWN0KC12b2x1bWUpCgpuYXNkYXFfbG9uZyA8LSBuYXNkYXEgJT4lIG1lbHQoaWQudmFycz0iZGF0ZSIpCm5hc2RhcSA8LSBuYXNkYXEgJT4lIG11dGF0ZShhdmVyYWdlPShjbG9zZStvcGVuK2hpZ2grbG93KS80KQoKZ2dwbG90KG5hc2RhcV9sb25nKSArCiAgZ2VvbV9wb2ludChhZXMoeD1kYXRlLCB5PXZhbHVlLCBncm91cD12YXJpYWJsZSwgY29sb3I9dmFyaWFibGUpLCBhbHBoYT0wLjcpICsKICBnZW9tX3NlZ21lbnQoZGF0YT1uYXNkYXEsIGFlcyh4PWRhdGUsIHhlbmQ9ZGF0ZSwgeT1sb3csIHllbmQ9aGlnaCksIGFscGhhPTAuMykgKyAKICBnZW9tX2xpbmUoZGF0YT1uYXNkYXEsIGFlcyh4PWRhdGUsIHk9YXZlcmFnZSksIGx3ZD0xLCBjb2w9ImRhcmtyZWQiKSArCiAgeWxhYigiTkFTREFRIikgKwogIGdndGl0bGUoIk5hc2RhcSBTdG9jayBNYXJrZXQgSW5kZXggKDI3LUZFQi0yMDE5IFRPIDI4LU1BWS0yMDE5KSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iUmVkcyIpCmBgYAoKPGltZyBzcmM9ImltYWdlcy91cy10cmVuZGluZy5wbmciIHdpZHRoPTIwMCBhbHQ9IlVTIHRyZW5kcyBvbiBUd2l0dGVyLiIgYWxpZ249InJpZ2h0Ij4KCklmIHlvdSdyZSBwYXJ0aWFsIHRvIHdhdGNoaW5nIHRoZSBuZXdzIG9uIHRlbGV2aXNpb24sIHRoZSBOQVNEQVEgc3RvY2sgbWFya2V0IGluZGV4IGlzIGEgbmF0dXJhbCBzaWdodC4gVGhlIHktYXhpcyBpbiB0aGUgcGxvdCBhYm92ZSByZXByZXNlbnRzIGEgInNuYXBzaG90IiBzdG9jayBtYXJrZXQgcGVyZm9ybWFuY2UgYW5kIHRoZSB4LWF4aXMgaXMgdGltZS4gVGhlIGRhdGEgcHJlc2VudGVkIGluIHRoZSBwbG90IGFyZSBhICJ0aW1lIHNlcmllcyIgYmVjYXVzZSB0aGV5IGFyZSByZXBlYXRlZCBtZWFzdXJlbWVudHMgdGFrZW4gb3ZlciBhIHRpbWUgcGVyaW9kLiBUaGUgZGF0YSBhYm92ZSB3ZXJlIGRvd25sb2FkZWQgZnJvbSB0aGUgTmFzZGFxIHdlYnNpdGUgYW5kIGNvdmVyIGEgdGhyZWUgbW9udGggcGVyaW9kIGZyb20gdGhlIGVuZCBvZiBGZWJydWFyeSB0byB0aGUgZW5kIG9mIE1heS4gT3ZlciB0aGlzIHBlcmlvZCwgdGhlIGluZGV4IHBlYWtlZCBpbiBlYXJseSBNYXJjaC4KCklmIHlvdSBwcmVmZXIgcGVyc29uYWxpemluZyB5b3VyIHBlcmlvZGljYWxzIG9uIFR3aXR0ZXIgb3ZlciB0ZWxldmlzaW9uIG5ld3MsIHlvdSBhcmUgZmFtaWxpYXIgd2l0aCB0aGUgY29uY2VwdCBvZiAidHJlbmRpbmciLiBXaGlsZSBubyB0aW1lIHNlcmllcyBwbG90cyBhcmUgdmlzaWJsZSB0byBUd2l0dGVyIHVzZXJzLCBkYXRhIHNjaWVudGlzdHMgdXNlIHRpbWUgc2VyaWVzIHRyZW5kcyB0byBkZXRlcm1pbmUgd2hpY2ggaGFzaHRhZ3MgYXJlIHRoZSBtb3N0IHBvcHVsYXIgYWNjb3JkaW5nIHRvIHlvdXIgYWNjb3VudCBwcmVmZXJlbmNlcy4gCgpCeSBkZWZhdWx0LCBUd2l0dGVyIGFjY291bnRzIHNob3cgcGVyc29uYWxpemVkIHRyZW5kcyBvbiB0aGUgaG9tZXBhZ2UuIFRoYXQgaXMsIHRoZSB0cmVuZHMgeW91IHNlZSB0YWtlIGludG8gYWNjb3VudCB5b3VyIGxvY2F0aW9uIGFuZCB3aG8geW91IGZvbGxvdyB0byBzaG93IHlvdSB3aGF0IGlzIGltcG9ydGFudCB0byB5b3UuIChXaGljaCBoYXMgc29tZSBkYW5nZXJvdXMgYWZ0ZXJtYXRoLCBidXQgdGhpcyB0aG91Z2h0IGV4cGVyaW1lbnQgd2lsbCBiZSBsZWZ0IGFzIGV4ZXJjaXNlIHRvIHRoZSByZWFkZXIuKSAKClRoZSBmaWd1cmUgdG8gdGhlIHJpZ2h0IGlzIGEgc2NyZWVuc2hvdCBvZiBVUyB0cmVuZHMgZnJvbSB0aGUgMjh0aCBvZiBNYXkuIFRoZSBVUyBUd2l0dGVyIGNvbW11bml0eSBjZWxlYnJhdGVkIHRoZSBsaWZlIG9mIEhhcmFtYmUgdGhyZWUgeWVhcnMgYWZ0ZXIgaGlzIHBhc3NpbmcgYW5kIHRoZSAjQW5pbWFsS2luZ2RvbS4gSW4gZm91cnRoIHBsYWNlIGFmdGVyIE5hdGlvbmFsIEhhbWJ1cmdlciBEYXksIEp1c3RpY2UgQ2xhcmVuY2UgVGhvbWFzIHdhcyB0cmVuZGluZyBmb3IgaGlzICJiaXphcnJlIGF0dGFjayBvbiBiaXJ0aCBjb250cm9sIiBhc3NvY2lhdGluZyBhYm9ydGlvbnMgd2l0aCByYWNpYWwgZXVnZW5pY3MuCgpSZWZsZWN0aW5nIFR3aXR0ZXIncyBmYXN0LXBhY2VkIHN0cmVhbSwgdGhlIGNvdW50cnl3aWRlIHRyZW5kcyBjaGFuZ2VkIGNvbXBsZXRlbHkgYnkgdGhlIG5leHQgZGF5LgoKIyBXaHkgdGltZSBzZXJpZXM/ClN0b2NrIG1hcmtldCBhdmVyYWdlcyBhcmUgdHJhY2tlZCBhbmQgcHJlZGljdGVkIGJ5IHRpbWUgc2VyaWVzIGFuYWx5c2lzIHRvIGtlZXAgaW52ZXN0b3JzIHdlbGwtaW5mb3JtZWQuIFR3aXR0ZXIgdXNlcnMga2VlcCB1cC10by1kYXRlIHdpdGggdGhlaXIgY3liZXJ3b3JsZCB1c2luZyB0cmVuZGluZyBoYXNodGFncyBkZXRlY3RlZCBieSB0aW1lIHNlcmllcyBhbGdvcml0aG1zLiBQb3B1bGF0aW9uIGZpZ3VyZXMsIG1vbnRobHkgYWlybGluZSBwYXNzZW5nZXJzLCBhbmQgdGVtcGVyYXR1cmUgY2FuIGFsc28gYmUgcmVwcmVzZW50ZWQgYnkgdGltZSBzZXJpZXMgZGF0YSB0byBlcXVpcCB0aGUgY2l0eSBmb3IgYW4gaW5mbHV4IG9mIG5ldyBjaXRpemVucywgdG8gdXNlIGFpcmxpbmUgcmVzb3VyY2VzIGVmZmljaWVudGx5LCBhbmQgdG8gcmV2ZWFsIGVmZmVjdHMgb2YgZ2xvYmFsIHdhcm1pbmcuCgpXZSB1c2UgKip0aW1lIHNlcmllcyBhbmFseXNpcyoqIHRvIGRlc2NyaWJlIGFuZCBwcmVkaWN0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBvdXIgb2JzZXJ2YXRpb25zIHRocm91Z2ggdGltZS4KCiMgU3RhdGlzdGljYWxseSBzcGVha2luZwoqKlRpbWUgc2VyaWVzKiogaXMgYSBicmFuY2ggb2Ygc3RhdGlzdGljcyB0aGF0IGRlYWxzIHdpdGggdGVtcG9yYWwgZGF0YSBhbmQgaXMgYWxzbyB0aGUgbmFtZSBmb3IgdGhvc2UgZGF0YS4gV2UgYW5hbHl6ZSB0aW1lIHNlcmllcyB1c2luZyB0aW1lIHNlcmllcy4gKFRoYXQgc291bmRzIHJhdGhlciB1bmhlbHBmdWwhKSBUaGUgTmFzZGFxIGRhdGEgd2UgdmlzdWFsaXplZCBhYm92ZSBpcyBhbiBleGFtcGxlIG9mIGEgKip0aW1lIHNlcmllcyBwbG90KiouCgpBICoqdGltZSBzZXJpZXMqKiAoKnR5cGUgb2YgZGF0YSopIGNhbiBiZSBkZW5vdGVkIGFzICRce3hfdFx9ID0gXHt4XzEsIHhfMiwgLi4uLCB4X3RcfSQgd2hlcmUgdGhlICR4JCdzIGFyZSBvYnNlcnZhdGlvbnMgYW5kIHRoZSBpbmRpY2VzIHJlcHJlc2VudCB0aGVpciB0ZW1wb3JhbCBvcmRlci4KCkEgKip0aW1lIHNlcmllcyBtb2RlbCoqIG9mIHRoZXNlIGRhdGEgaXMgYSBqb2ludCBkaXN0cmlidXRpb24gZm9yIHRoZSByYW5kb20gdmFyaWFibGVzICRce1hfdFx9ID0gXHtYXzEsIFhfMiwgLi4uLCBYX3RcfSQgdGhhdCB3b3VsZCBnaXZlIHJpc2UgdG8gb3VyIGRhdGEgJFx7eF90XH0kLgoKVGhlICoqY2xhc3NpY2FsIGRlY29tcG9zaXRpb24gbW9kZWwqKiByZXdyaXRlcyB0aGUgdGltZSBzZXJpZXMgbW9kZWwgJFx7WF90XH0kIGluIHRocmVlIGNvbXBvbmVudHM6IHRyZW5kICgkbV90JCksIHNlYXNvbmFsaXR5ICgkc190JCksIGFuZCByYW5kb20gbm9pc2UgcmVzaWR1YWxzICgkWV90JCkuIFdlIGNhbiB3cml0ZSB0aGUgbW9kZWwgYXMgYW4gYWRkaXRpb24gYmV0d2VlbiB0aGUgdGhyZWUgY29tcG9uZW50cyBhcyBmb2xsb3dzOgoKJCQKWF90ID0gbV90ICsgc190ICsgWV90LgokJAoKVGhlIG1vZGVsIGFzc3VtZXMgdGhhdCB0aGUgY29tcG9uZW50cyBoYXZlIGFuIGFkZGl0aXZlIHJlbGF0aW9uc2hpcCAoYSBsb2cgdHJhbnNmb3JtYXRpb24gbXVzdCBiZSB0YWtlbiB3aGVuIHRoZSByZWxhdGlvbnNoaXAgaXMgbXVsdGlwbGljYXRpdmUpLiBPdXIgZ29hbCBpcyB0byBzdWJ0cmFjdCB0cmVuZCBhbmQgc2Vhc29uYWxpdHkgZnJvbSBvdXIgZGF0YSBhbmQgZXhhbWluZSB0aGUgbm9pc2UgY29tcG9uZW50ICRZX3QgPSBYX3QgLSBtX3QgLSBzX3QkLiBVcG9uIHJldHJpZXZhbCBvZiBvdXIgbm9pc2UgZGF0YSAkeV90JCwgd2Ugd2FudCB0byB0ZXN0IHdoZXRoZXIgaXQgaXMgKipzdGF0aW9uYXJ5Kiogb3Igbm90LiBTdGF0aW9uYXJpdHkgb2YgYSB0aW1lIHNlcmllcyBpcyBhIHJlcXVpcmVtZW50IG9mIHRoZSByZXNpZHVhbHMgb2YgYSB0aW1lIHNlcmllcyBmb3IgbW9kZWxpbmcuIAoKVGhlICoqc3RyaWN0IHN0YXRpb25hcml0eSoqIG9mIGEgdGltZSBzZXJpZXMgaW1wbGllcyB0aGF0IGZvciBhbGwgaW50ZWdlcnMgJGgkICgqKmxhZyoqKSwgJFx7WF90XH0kIGFuZCAkXHtYX3t0K2h9XH09XHtYX3sxK2h9LCBYX3syK2h9LC4uLixYX3tuK2h9XH0kIGFyZSBkaXN0cmlidXRlZCB0aGUgc2FtZS4gKipXZWFrIHN0YXRpb25hcml0eSoqIGltcGxpZXMKCigxKSAkXG11X1godCkkIGlzIGluZGVwZW5kZW50IG9mICR0JCBhbmQgIAoKKDIpICRcZ2FtbWFfWCh0K2gsdCkkIGlzIGluZGVwZW5kZW50IG9mICR0JCBmb3IgZWFjaCAkaCQuCgpUaGUgaW5kZXBlbmRlbmNlIG9mIG1lYW5zIGFuZCBjb3ZhcmlhbmNlcyB3aXRoIGdpdmVuIGxhZyAkaCQgZnJvbSB0aW1lIGVuYWJsZSB1cyAodGhlIG1vZGVsZXJzKSB0byBjcmVhdGUgbWF0aGVtYXRpY2FsIGRlc2NyaXB0aW9ucyBhbmQgcHJlZGljdCBuZXcgdmFsdWVzIHVzaW5nIHRpbWUgc2VyaWVzLgoKCiMgSG93IHRvIHByb3Blcmx5IHRpbWUgdHJhdmVsClRoZSBjbGFzc2ljYWwgZGVjb21wb3NpdGlvbiBtb2RlbCByZXF1aXJlcyB1cyB0byBzZXBhcmF0ZSBvdXIgdHJlbmQgYW5kIHNlYXNvbmFsaXR5IGNvbXBvbmVudHMgZnJvbSByYW5kb20gbm9pc2UuIFJlY2FsbCB0aGUgTmFzZGFxIGRhdGEgZnJvbSBlYXJsaWVyLiBGb3IgYSBjbGVhbiBsb29rLCBJIG9ubHkga2VwdCB0aGUgYXZlcmFnZSBpbmRleCBwZXIgZGF5IGluIHRoZSBmb2xsb3dpbmcgdGltZSBzZXJpZXMgcGxvdC4KCmBgYHtyIG5hc2RhcS1wbG90LTJ9CmdncGxvdChkYXRhPW5hc2RhcSwgYWVzKHg9ZGF0ZSwgeT1hdmVyYWdlKSkgKwogIGdlb21fbGluZShsd2Q9MSwgY29sPSJwaW5rMyIpICsKICBnZW9tX3BvaW50KGNvbD0iZGFya3JlZCIpICsKICB5bGFiKCJOQVNEQVEiKSArCiAgZ2d0aXRsZSgiTmFzZGFxIFN0b2NrIE1hcmtldCBJbmRleCAoMjctRkVCLTIwMTkgVE8gMjgtTUFZLTIwMTkpIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgc2NhbGVfY29sb3VyX2JyZXdlcihwYWxldHRlPSJSZWRzIikKYGBgCgpJbiBzdGF0aXN0aWNzLCB3ZSBjYW5vbmljYWxseSBsZWFybiB0byBtb2RlbCB0cmVuZHMgd2l0aCAqKmxpbmVhciByZWdyZXNzaW9uKiouIFRoYXQgaXMsIHdlIHRha2UgJHk9bXgrYiQgYW5kIG1ha2UgYSBsaW5lIG9mIGJlc3QgZml0LiBVc2luZyBvcmRpbmFyeSBsZWFzdCBzcXVhcmVzLCB3ZSBjYW4gZml0IGNvZWZmaWNpZW50cyB1c2luZyBgbG0oKWAgZnJvbSBiYXNlIGBSYC4gSG93IHRvIGZpdCBhIHNpbnVzb2lkYWwgKGN1cnZ5L3VwIGFuZCBkb3duIHBhdHRlcm4vc2luZS13YXZlLWxpa2UpIHNlYXNvbmFsaXR5IGNvbXBvbmVudCBpcyBsZXNzIHRhdWdodCwgYnV0IHdlIGNhbiB1c2UgKipoYXJtb25pYyByZWdyZXNzaW9uKiogdG8gZG8gdGhpcy4gTWV0aG9kcyB0byBzZXBhcmF0ZSB0cmVuZC9zZWFzb25hbGl0eSB0aGF0IGFyZSBwYXJ0aWN1bGFyIHRvIGFuZCBwb3B1bGFyIGluIHRpbWUgc2VyaWVzIGFyZSAqKmRpZmZlcmVuY2luZyoqIGFuZCB1c2luZyAqKm1vdmluZyBhdmVyYWdlcyoqLgoKV2l0aCBzbyBtYW55IG1ldGhvZHMgYXZhaWxhYmxlIHRvIGVzdGltYXRlIHRyZW5kIGFuZCBzZWFzb25hbGl0eSwgSSB3YXMgaW1tZWRpYXRlbHkgY29uZnVzZWQgb24gd2hpY2ggcm91dGUgd2FzIG1vc3QgY29ycmVjdC4gTXkgcmVmbGV4IHdhcyB0byBhc2sgb25lIG9mIG15IGZhdm9yaXRlIGFkdmlzb3JzLCA8YSBocmVmPSJodHRwczovL3N0YXRpc3RpY3MudWNkYXZpcy5lZHUvcGVvcGxlL2FsZXhhbmRlci1hdWUiPkRyLiBBbGV4IEF1ZTwvYT4uIEhlcmUncyBwYXJ0IG9mIG91ciBlbWFpbCB0aHJlYWQuIEhlIGFkdmlzZWQgbWUgdGhhdCAqKnRoZXJlIGlzIG5vIGdlbmVyYWwgcnVsZSBvbiBob3cgdG8gY2hvb3NlIHdoaWNoIG1ldGhvZCB0byByZW1vdmUgdHJlbmQgYW5kIHNlYXNvbmFsaXR5KiouIEFmdGVyIGVzdGltYXRpbmcgdHJlbmQgYW5kIHNlYXNvbmFsaXR5IHVzaW5nIGFueSBtZXRob2QsIGFuIGFzc2Vzc21lbnQgb2YgdGhlIHJhbmRvbSBub2lzZSByZXNpZHVhbHMgd2lsbCBzaG93IHdoaWNoIG1ldGhvZCB3b3JrcyB0aGUgYmVzdC4KClRoZSBmb2xsb3dpbmcgaXMgYSBydWRpbWVudGFyeSBhdHRlbXB0IHRvIGZpdCBhIHRpbWUgc2VyaWVzIG1vZGVsLiBJIHdpbGwgbm90IGJlIGhlYXZpbHkgY2hlY2tpbmcgYXNzdW1wdGlvbnMuIFRoZSBwb2ludCBvZiB0aGUgbmV4dCBmZXcgc3Vic2VjdGlvbnMgaXMgdG8gZGVtb25zdHJhdGUgc2ltcGxlIGV4YW1wbGVzLgoKIyMgVGhlIHRyZW5kIGNvbXBvbmVudApPbmUgd2F5IHRvIGVzdGltYXRlIHRoZSB0cmVuZCBjb21wb25lbnQgaXMgKipsaW5lYXIgcmVncmVzc2lvbioqLiBJbiBgUmAsIHdlIGNhbiB1c2UgdGhlIGBsbSgpYCBmdW5jdGlvbi4gU2VlIHRoZSBibG9jayBvZiBjb2RlIGJ5IGNsaWNraW5nIHRoZSAiQ29kZSIgYnV0dG9uLgoKYGBge3IsIGVjaG89VFJVRX0KbmFzZGFxIDwtIG5hc2RhcSAlPiUgbXV0YXRlKG5tX2RhdGU9YXMubnVtZXJpYyhkYXRlKSkKc3RyYWlnaHQgPC0gbG0oYXZlcmFnZSB+IG5tX2RhdGUsIGRhdGE9bmFzZGFxKQpwYXJhYm9sYSA8LSBsbShhdmVyYWdlIH4gbm1fZGF0ZSArIEkobm1fZGF0ZV4yKSwgZGF0YT1uYXNkYXEpCmN1YmljICAgIDwtIGxtKGF2ZXJhZ2UgfiBubV9kYXRlICsgSShubV9kYXRlXjIpICsgSShubV9kYXRlXjMpLCBkYXRhPW5hc2RhcSkKYGBgCgpUaGUgcGxvdCBiZWxvdyBzaG93cyBhbGwgdGhyZWUgdHJlbmQgbGluZXMgYXRvcCB0aGUgb3JpZ2luYWwgTmFzZGFxIGRhdGEuIAoKYGBge3IgbmFzZGFxLXBsb3QtM30KZml0dGVkX2RmICAgPC0gZGF0YS5mcmFtZShjYmluZChzdHJhaWdodD1zdHJhaWdodCRmaXR0ZWQudmFsdWVzLCBwYXJhYm9sYT1wYXJhYm9sYSRmaXR0ZWQudmFsdWVzLCBjdWJpYz1jdWJpYyRmaXR0ZWQudmFsdWVzKSkKbmFzZGFxX2ZpdHMgPC0gY2JpbmQobmFzZGFxLCBmaXR0ZWRfZGYpCm5hc2RhcV9maXRzIDwtIG5hc2RhcV9maXRzICU+JSBkcGx5cjo6c2VsZWN0KGRhdGUsIG5tX2RhdGUsIGF2ZXJhZ2UsIHN0cmFpZ2h0LCBwYXJhYm9sYSwgY3ViaWMpCgpnZ3Bsb3QoZGF0YT1uYXNkYXFfZml0cywgYWVzKHg9ZGF0ZSwgeT1hdmVyYWdlKSkgKwogIGdlb21fbGluZShjb2w9ImRhcmtncmV5IikgKwogIGdlb21fcG9pbnQoY29sPSJncmV5MiIpICsKICBnZW9tX2xpbmUoYWVzKHg9ZGF0ZSwgeT1zdHJhaWdodCksIGx3ZD0xLCBjb2w9InJlZDQiKSArCiAgZ2VvbV9saW5lKGFlcyh4PWRhdGUsIHk9cGFyYWJvbGEpLCBhbHBoYT0wLjMsIGx3ZD0yLCBjb2w9ImRhcmtyZWQiKSArCiAgZ2VvbV9saW5lKGFlcyh4PWRhdGUsIHk9Y3ViaWMpLCBjb2w9InJlZDMiKSArCiAgeWxhYigiTkFTREFRIikgKwogIGdndGl0bGUoIk5hc2RhcSBTdG9jayBNYXJrZXQgSW5kZXggKDI3LUZFQi0yMDE5IFRPIDI4LU1BWS0yMDE5KSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iUmVkcyIpCmBgYAoKKipUaGUgc3RyYWlnaHQgbGluZSBpcyBpbXByb3BlciBmb3IgdGhlc2UgZGF0YS4qKiBJIHNob3VsZCBub3QgaGF2ZSBmaXR0ZWQgYSBzdHJhaWdodCBsaW5lIHRvIHRoZXNlIGRhdGEgaW4gdGhlIGZpcnN0IHBsYWNlLCBidXQgc29tZSByZWxlbnRsZXNzIHNvdWxzIHdpbGwgc3RpbGwgZG8gaXQgYW5kIGNhbGwgaXQgYSBkYXkuIFRvIGVkdWNhdGUgYXdheSBmcm9tIHRoaXMgZG9jdHJpbmUsIG5vdGljZSBob3cgdGhlIHBhcmFib2xpYyBhbmQgY3ViaWMgcmVncmVzc2lvbiBsaW5lcyAocGxvdHRlZCBhbG1vc3QgYXRvcCBlYWNoIG90aGVyKSBmaXQgdGhlIGRhdGEncyBzaGFwZSBtdWNoIGJldHRlci4KCioqVG8gbW9kZWwgdGhlIHRyZW5kIG9mIHRoaXMgdGltZSBzZXJpZXMsIHdlIHdpbGwgY2hvb3NlIHRoZSBwYXJhYm9sYS4qKiBUaGUgZm9sbG93aW5nIG91dHB1dCBzaG93IHRoZSBjb2VmZmljaWVudHMgYW5kIGFzc29jaWF0ZWQgcC12YWx1ZXMuCgpgYGB7cn0KdGlkeShwYXJhYm9sYSkKYGBgCgpUaGUgZm9sbG93aW5nIGlzIHRoZSBmb3JtdWxhIGZvciBvdXIgdHJlbmQgbGluZSBiYXNlZCBvZmYgdGhlIHByZXZpb3VzIG91dHB1dC4KCiQkCm1fdCA9IDMzNzY2Ni42IC0zNy41dCArIDAuMDAxdF4yCiQkCgpXZSBjYW4gbm93IHBlcmZvcm0gdGhlIHN1YnRyYWN0aW9uIG9mIHRoZSBmaXR0ZWQgcG9pbnRzIGZyb20gb3VyIGFjdHVhbCBkYXRhLgoKJCQKWF90IC0gbV90ID0gc190ICsgWV90CiQkCgpPbiBhIHBsb3QsIG91ciBkZXRyZW5kZWQgZGF0YSBsb29rIGxpa2UgdGhpcy4KCmBgYHtyIG5hc2RhcS1wbG90LTR9Cm5hc2RhcV9maXRzIDwtIG5hc2RhcV9maXRzICU+JSAKICBzZWxlY3QoZGF0ZSwgbm1fZGF0ZSwgYXZlcmFnZSwgcGFyYWJvbGEpICU+JQogIG11dGF0ZShkZXRyZW5kZWQ9YXZlcmFnZS1wYXJhYm9sYSkKCmdncGxvdChkYXRhPW5hc2RhcV9maXRzLCBhZXMoeD1kYXRlLCB5PWRldHJlbmRlZCkpICsKICBnZW9tX2xpbmUobHdkPTEsIGNvbD0icGluazMiKSArCiAgZ2VvbV9wb2ludChjb2w9ImRhcmtyZWQiKSArCiAgeWxhYigiTkFTREFRIikgKwogIGdndGl0bGUoIk5hc2RhcSBTdG9jayBNYXJrZXQgSW5kZXggKDI3LUZFQi0yMDE5IFRPIDI4LU1BWS0yMDE5KSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iUmVkcyIpCmBgYAoKVGhlIGRhdGEgaGF2ZSBiZWVuIHRyYW5zZm9ybWVkIHRvIG9zY2lsbGF0ZSBhYm91dCB0aGUgaG9yaXpvbnRhbCBsaW5lIGFib3V0IHRoZSBkYXRhJ3MgbWVhbiAoJFxhcHByb3ggMCQpLgoKYGBge3IgYm94cGxvdH0KZ2dwbG90KGRhdGE9bmFzZGFxX2ZpdHMsIGFlcyh5PWRldHJlbmRlZCkpICsKICBnZW9tX2JveHBsb3QoY29sPSJibGFjayIsIGZpbGw9InBpbmszIikgKwogIHlsYWIoIk5BU0RBUSIpICsKICB4bGFiKCIiKSArCiAgZ2d0aXRsZSgiTmFzZGFxIFN0b2NrIE1hcmtldCBJbmRleCAoMjctRkVCLTIwMTkgVE8gMjgtTUFZLTIwMTkpIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKClR3byBvdXRsaWVyIGluZGV4IG1lYXN1cmVzIHRvb2sgcGxhY2UgYXQgdGhlIHN0YXJ0IG9mIE1heS4KCiMjIFRoZSBzZWFzb25hbGl0eSBjb21wb25lbnQKCkluIG9yZGVyIHRvIGZpdCB0aGUgc2Vhc29uYWxpdHkgY29tcG9uZW50LCB3ZSB3aWxsIHVzZSBgaGFybW9uaWMoKWAgZnJvbSBgbGlicmFyeShUU0EpYC4gVXNpbmcgdGhpcyBsaWJyYXJ5IHRvb2sgcXVpdGUgYSBiaXQgb2Ygd29yayB0byBjb252ZXJ0IGZyb20gZGF0YWZyYW1lIGFuYWx5c2VzIGludG8gdGltZSBzZXJpZXMgKGRhdGEgZm9ybWF0IGZvciBgbGlicmFyeShUU0EpYCkuCgpgYGB7ciBoYXJtb25pYy1yZWdyZXNzaW9ufQpmdWxsX2RhdGVzIDwtIHNlcShtaW4obmFzZGFxX2ZpdHMkZGF0ZSksIG1heChuYXNkYXFfZml0cyRkYXRlKSwgYnk9ImRheSIpCgppeF9vcmRlciA8LSBzYXBwbHkoMTpsZW5ndGgoZnVsbF9kYXRlcyksIGZ1bmN0aW9uKHgpIHsKICBpZiAoYW55KGZ1bGxfZGF0ZXNbeF09PW5hc2RhcV9maXRzJGRhdGUpKSB7CiAgICB3aGljaChmdWxsX2RhdGVzW3hdID09IG5hc2RhcV9maXRzJGRhdGUpCn0gZWxzZSB7CiAgICBOQQogIH0KfSkgCgpuYXNkYXFfbWF0IDwtIGNiaW5kKGZ1bGxfZGF0ZXMsIGRldHJlbmRlZD1uYXNkYXFfZml0cyRkZXRyZW5kZWRbaXhfb3JkZXJdKQoKbmFzZGFxX3RzICA8LSB0cyhkYXRhPW5hc2RhcV9tYXRbLDJdLAogICAgICAgICAgICAgICAgc3RhcnQgPSBjKDIwMTksIAogICAgICAgICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoZm9ybWF0KGZ1bGxfZGF0ZXNbMV0sICIlaiIpKSksCiAgICAgICAgICAgICAgICBmcmVxdWVuY3kgPSAzNjUpCmBgYAoKV2hlbiB3ZSBsZXQgdGhlcmUgYmUgMiBoYXJtb25pY3MgZml0LCB0aGUgZml0dGVkIHZhbHVlcyBsb29rIGxpa2UgdGhpcy4gVGhlc2Ugd2F2ZXMgZG8gbm90IHdlbGwgZGVzY3JpYmUgdGhlIHNlYXNvbmFsaXR5LgoKYGBge3IgbmFzZGFxLXBsb3QtNX0KaGFybV92YWwgPC0gaGFybW9uaWMobmFzZGFxX3RzLCBtPTIpCmhhcm1fZml0IDwtIGxtKG5hc2RhcV90c35oYXJtX3ZhbCkKCm5hc2RhcV9maXRzJGZpdF9oYXJtIDwtIGhhcm1fZml0JGZpdHRlZC52YWx1ZXMKbmFzZGFxX2ZpdHMgPC0gbmFzZGFxX2ZpdHMgJT4lIG11dGF0ZShkZXNlYXNvbmVkPWRldHJlbmRlZC1maXRfaGFybSkKCmdncGxvdChkYXRhPW5hc2RhcV9maXRzLCBhZXMoeD1kYXRlLCB5PWZpdF9oYXJtKSkgKwogIGdlb21fcG9pbnQoZGF0YT1uYXNkYXFfZml0cywgYWVzKHg9ZGF0ZSwgeT1kZXRyZW5kZWQpLCBjb2w9ImRhcmtncmV5IikgKwogIGdlb21fbGluZShkYXRhPW5hc2RhcV9maXRzLCBhZXMoeD1kYXRlLCB5PWRldHJlbmRlZCksIGNvbD0iZ3JleSIpICsKICB5bGFiKCJOQVNEQVEiKSArCiAgZ2VvbV9saW5lKGx3ZD0xLCBjb2w9InBpbmszIikgKwogIGdlb21fcG9pbnQoY29sPSJkYXJrcmVkIikgKwogIGdndGl0bGUoIk5hc2RhcSBTdG9jayBNYXJrZXQgSW5kZXggKDI3LUZFQi0yMDE5IFRPIDI4LU1BWS0yMDE5KSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iUmVkcyIpCmBgYAoKV2hlbiB3ZSBsZXQgdGhlcmUgYmUgMyBoYXJtb25pY3MgZml0LCB0aGUgZml0dGVkIHZhbHVlcyBsb29rIGxpa2UgdGhpcy4gVGhpcyBoYXJtb25pYyByZWdyZXNzaW9uIGJldHRlciBmaXRzIG91ciBkYXRhLgoKCmBgYHtyIG5hc2RhcS1wbG90LTZ9Cmhhcm1fdmFsIDwtIGhhcm1vbmljKG5hc2RhcV90cywgbT0zKQpoYXJtX2ZpdCA8LSBsbShuYXNkYXFfdHN+aGFybV92YWwpCgpuYXNkYXFfZml0cyRmaXRfaGFybSA8LSBoYXJtX2ZpdCRmaXR0ZWQudmFsdWVzCm5hc2RhcV9maXRzIDwtIG5hc2RhcV9maXRzICU+JSBtdXRhdGUoZGVzZWFzb25lZD1kZXRyZW5kZWQtZml0X2hhcm0pCgpnZ3Bsb3QoZGF0YT1uYXNkYXFfZml0cywgYWVzKHg9ZGF0ZSwgeT1maXRfaGFybSkpICsKICBnZW9tX3BvaW50KGRhdGE9bmFzZGFxX2ZpdHMsIGFlcyh4PWRhdGUsIHk9ZGV0cmVuZGVkKSwgY29sPSJkYXJrZ3JleSIpICsKICBnZW9tX2xpbmUoZGF0YT1uYXNkYXFfZml0cywgYWVzKHg9ZGF0ZSwgeT1kZXRyZW5kZWQpLCBjb2w9ImdyZXkiKSArCiAgeWxhYigiTkFTREFRIikgKwogIGdlb21fbGluZShsd2Q9MSwgY29sPSJwaW5rMyIpICsKICBnZW9tX3BvaW50KGNvbD0iZGFya3JlZCIpICsKICBnZ3RpdGxlKCJOYXNkYXEgU3RvY2sgTWFya2V0IEluZGV4ICgyNy1GRUItMjAxOSBUTyAyOC1NQVktMjAxOSkiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGU9IlJlZHMiKQpgYGAKCiMjIE5vaXNlCgoKYGBge3J9CmdncGxvdChkYXRhPW5hc2RhcV9maXRzLCBhZXMoeD1kYXRlLCB5PWRlc2Vhc29uZWQpKSArCiAgZ2VvbV9saW5lKGx3ZD0xLCBjb2w9InBpbmszIikgKwogIGdlb21fcG9pbnQoY29sPSJkYXJrcmVkIikgKwogIGdndGl0bGUoIk5hc2RhcSBTdG9jayBNYXJrZXQgSW5kZXggKDI3LUZFQi0yMDE5IFRPIDI4LU1BWS0yMDE5KSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iUmVkcyIpCmBgYAoKIyMjIFRlc3QgZm9yIHN0YXRpb25hcml0eQoKVG8gdGVzdCBpZiB0aGlzIHdoaXRlIG5vaXNlIGlzIHN0YXRpb25hcnksIHdlIHdpbGwgdXNlIHRoZSAqKkxqdW5nLUJveCB0ZXN0KiouIFRoZSBsYWcgSSB3aWxsIGlucHV0IHdpbGwgYmUgYG1pbigyKmQsIG4vNSlgLiBJIHdpbGwgbGV0IG15IHBlcmlvZCBiZSBvbmUgd2VlaywgdGhlcmVmb3JlIG91ciBwZXJpb2QgJGQ9NyQsIGFuZCAkbj0kIGByIG5yb3cobmFzZGFxX2ZpdHMpYC4KCiRIXzAkOiBUaGUgbW9kZWwgZG9lcyBub3Qgc2hvdyBhIGxhY2sgb2YgZml0LiBUaGUgbW9kZWwncyBkb2VzIG5vdCBzaG93IGF1dG9jb3JyZWxhdGlvbi4gKFRoZSBtb2RlbCdzIGEtb2theS4pCgokSF8xOiQgVGhlIG1vZGVsIHNob3dzIGEgbGFjayBvZiBmaXQuCgpgYGB7cn0KZCA8LSA3Cm4gPC0gbnJvdyhuYXNkYXFfZml0cykKCmJveF9sYWcgPC0gbWluKDIqZCwgbi81KQpCb3gudGVzdChuYXNkYXFfZml0cyRkZXNlYXNvbmVkLCBsYWc9Ym94X2xhZywgdHlwZT0iTGp1bmctQm94IikKYGBgCgpUaGUgcC12YWx1ZSBhYm92ZSBpcyBzdXBlciBzbWFsbCwgc28gd2UgY2Fubm90IHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzIGFib3ZlLiBPdXIgbW9kZWwncyBub3QgZG9pbmcgc28gd2VsbC4KCiMjIyBEaWFnbm9zdGljIHBsb3RzIChBQ0YvUEFDRikKCgpgYGB7ciBhY2YtcGFjZn0KbmFzZGFxX2FjZiA8LSBhY2YobmFzZGFxX2ZpdHMkZGVzZWFzb25lZCwgcGxvdD1GQUxTRSkKYWNmX3ZhbHVlcyA8LSBhcy5udW1lcmljKHVubGlzdChuYXNkYXFfYWNmW1sxXV0pKQoKbmFzZGFxX3BhY2YgPC0gcGFjZihuYXNkYXFfZml0cyRkZXNlYXNvbmVkLCBwbG90PUZBTFNFKQpwYWNmX3ZhbHVlcyA8LSBhcy5udW1lcmljKHVubGlzdChuYXNkYXFfcGFjZltbMV1dKSkKCmRpYWdub3N0aWNfZGYgPC0gZGF0YS5mcmFtZShjYmluZChsYWdzPTE6bGVuZ3RoKGFjZl92YWx1ZXMpLCBhY2Y9YWNmX3ZhbHVlcywgcGFjZj1wYWNmX3ZhbHVlcykpCgpjaSAgICA8LSAwLjk1CmJvdW5kIDwtIHFub3JtKCgxICsgY2kpLzIpL3NxcnQobikKYGBgCgpUaGUgZm9sbG93aW5nIGlzIGFuICoqYXV0b2NvcnJlbGF0aW9uIGZ1bmN0aW9uIChBQ0YpKiogcGxvdC4gVGhlcmUgYXJlIHNpZ25pZmljYW50IHNwaWtlcyBhdCB0aGUgZnJpc3QgYW5kIHNlY29uZCBsYWcuIFRoZSAxN3RoIGxhZyBpcyBhbHNvIHNpZ25pZmljYW50LgoKYGBge3IgYWNmLXBsb3R9CmdncGxvdChkaWFnbm9zdGljX2RmLCBhZXMoeD1sYWdzLCB5PWFjZikpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9Yyhib3VuZCwgLWJvdW5kKSwgY29sPSJwaW5rMiIsIGx0eT0yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAsIGNvbD0icGluazMiKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4PWxhZ3MsIHhlbmQ9bGFncywgeT0wLCB5ZW5kPWFjZiksIGx3ZD0yLCBjb2w9ImRhcmtyZWQiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKClRoZSBmb2xsb3dpbmcgaXMgYSAqKnBhcnRpYWwgYXV0b2NvcnJlbGF0aW9uIGZ1bmN0aW9uIChQQUNGKSoqIHBsb3QuIFRoZSBQQUNGIHBsb3Qgc2hvd3MgYSBkZWNheSB0b3dhcmQgMC4KCmBgYHtyIHBhY2YtcGxvdH0KZ2dwbG90KGRpYWdub3N0aWNfZGYsIGFlcyh4PWxhZ3MsIHk9cGFjZikpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9Yyhib3VuZCwgLWJvdW5kKSwgY29sPSJwaW5rMiIsIGx0eT0yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAsIGNvbD0icGluazMiKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4PWxhZ3MsIHhlbmQ9bGFncywgeT0wLCB5ZW5kPXBhY2YpLCBsd2Q9MiwgY29sPSJkYXJrcmVkIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKClRoZXNlIHBsb3RzIHNob3cgdGhhdCB0aGVyZSBpcyBzdGlsbCBzdHJvbmcgZGVwZW5kZW5jZSBzdHJ1Y3R1cmVzIGJldHdlZW4gdGhlIG9ic2VydmF0aW9ucyBiZXR3ZWVuIHRoZSBwcmVzZW50IG9ic2VydmF0aW9uIGFuZCB0aGUgdHdvIG9ic2VydmF0aW9ucyBiZWZvcmUuIFRoZXJlZm9yZSwgb3VyIG1vZGVsIGlzIG5vdCBjYXB0dXJpbmcgYWxsIG9mIHRoZSBkZXBlbmRlbmNlIGJldHdlZW4gdGltZSBhbmQgdGhlIE5hc2RhcSBpbmRleC4KCiMjIFNvLCB3aGF0PwpXZSBuZWVkIHRvIHJlc3RyYXRlZ2l6ZS4gSW4gdGhpcyBibG9nLCB3ZSBjb3ZlcmVkIGNsYXNzaWNhbCBkZWNvbXBvc2l0aW9uIGluIGEgd2F5IHRoYXQgeW91IGNhbiBqdW1wIGludG8gdGltZSBzZXJpZXMgYnkga25vd2luZyBsaW5lYXIgcmVncmVzc2lvbiBhbmQgdW5kZXJzdGFuZGluZyB0aGUgZ2lzdCBoYXJtb25pYyByZWdyZXNzaW9uLiAqKkluIGJsb2dzIHRvIGNvbWUsIEkgd2lsbCBnbyBvdmVyIG1vcmUgbWV0aG9kcyBsaWtlIGRpZmZlcmVuY2luZywgIG1vdmluZyBhdmVyYWdlcywgYW5kIEFSSU1BIG1vZGVscy4qKiBUaGVzZSB3aWxsIHJlcXVpcmUgc29tZSBtb3JlIHN0YXRpc3RpY2FsIHRoZW9yeSB0byB1bmRlcnN0YW5kLCBzbyBjb250aW51ZSB0byBiYXJlIHdpdGggbWUhCgpJZiB5b3UgaGF2ZSBxdWVzdGlvbnMgb3IgY29ycmVjdGlvbnMsIHBsZWFzZSBkbyByb3V0ZSB0aGVtIG92ZXIgdG8gbXkgZW1haWwuCgoKIyBDb250cmlidXRpb25zClRoYW5rIHlvdSB0byBteSBmaXJzdCByZWFkZXIsIDxhIGhyZWY9Imh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9pbi9hc2VtYmVya2FsaWV2YS8iPkFzZW08L2E+IQoKIyBSZWZlcmVuY2VzCkJyb2Nrd2VsbCwgUC4gSi4sIERhdmlzLCBSLiBBLiwgJiBGaWVuYmVyZywgUy4gRS4gKDE5OTEpLiBUaW1lIFNlcmllczogVGhlb3J5IGFuZCBNZXRob2RzOiBUaGVvcnkgYW5kIE1ldGhvZHMuIFNwcmluZ2VyIFNjaWVuY2UgJiBCdXNpbmVzcyBNZWRpYS4KCkhvbSwgRWxhaW5lIEouICJXaGF0IGlzIE5BU0RBUT8iLiAoMjAxMi4pIFs8YSBocmVmPSJodHRwczovL3d3dy5idXNpbmVzc25ld3NkYWlseS5jb20vMzQwMy1uYXNkYXEuaHRtbCI+VVJMPC9hPl0KCkh5bmRtYW4sIFIuSi4sICYgQXRoYW5hc29wb3Vsb3MsIEcuICgyMDE4KSBGb3JlY2FzdGluZzogcHJpbmNpcGxlcyBhbmQgcHJhY3RpY2UsIDJuZCBlZGl0aW9uLCBPVGV4dHM6IE1lbGJvdXJuZSwgQXVzdHJhbGlhLiBPVGV4dHMuY29tL2ZwcDIuIEFjY2Vzc2VkIG9uIDMwIE1heSAyMDE5LgoKTG90YW4sIEdpbGFkLiAiI0ZyZWRkaWVHcmF5IC0tIGlzIG5vdCB0cmVuZGluZyBvbiBUd2l0dGVyPyIgKDIwMTUuKSBbPGEgaHJlZj0iaHR0cHM6Ly9tZWRpdW0uY29tL2ktZGF0YS9mcmVkZGllZ3JheS1pcy1ub3QtdHJlbmRpbmctb24tdHdpdHRlci05ZTQ1NTA2MDdhMzkiPlVSTDwvYT5dCgpNaWxsaGVpc2VyLCBJYW4uICJKdXN0aWNlIFRob21hcyBsYXVuY2hlcyBhbiB1dHRlcmx5IGJpemFycmUgYXR0YWNrIG9uIGJpcnRoIGNvbnRyb2wiLiAoMjAxOS4pCls8YSBocmVmPSJodHRwczovL3RoaW5rcHJvZ3Jlc3Mub3JnL2p1c3RpY2UtdGhvbWFzLWxhdW5jaGVzLWFuLXV0dGVybHktYml6YXJyZS1hdHRhY2stb24tYmlydGgtY29udHJvbC0xOTU5MjFiYjc3ODEvIj5VUkw8L2E+XQoKTmVlZGxlLCBTYXJhaC4gIkhvdyBEb2VzIFR3aXR0ZXIgRGVjaWRlIFdoYXQgSXMgVHJlbmRpbmc/Ii4gKDIwMTYuKSBbPGEgaHJlZj0iaHR0cHM6Ly9yZXRoaW5rbWVkaWEub3JnL2Jsb2cvaG93LWRvZXMtdHdpdHRlci1kZWNpZGUtd2hhdC10cmVuZGluZyI+VVJMPC9hPl0KClJpY2gsIEt5bGUgVC4gIlN0YXRpb25hcml0eSBUZXN0aW5nIi4gKDIwMTcpLiBbPGEgaHJlZj0iaHR0cHM6Ly9ycHVicy5jb20vcmljaGt0LzI2OTc5NyI+VVJMPC9hPl0KClNodW13YXksIFIuIEguLCAmIFN0b2ZmZXIsIEQuIFMuICgyMDE3KS4gVGltZSBzZXJpZXMgYW5hbHlzaXMgYW5kIGl0cyBhcHBsaWNhdGlvbnM6IHdpdGggUiBleGFtcGxlcy4gU3ByaW5nZXIuCgpTaW1wc29uLCBHYXZpbi4gU3RhY2tPdmVyZmxvdyBhbnN3ZXIuICgyMDE1KS4gWzxhIGhyZWY9Imh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzMzMTI4ODY1L3N0YXJ0aW5nLWEtZGFpbHktdGltZS1zZXJpZXMtaW4tciI+VVJMPC9hPl0KClN0aXJsaW5nLCBEb3VnLiAiQ0FTVCIuIFs8YSBocmVmPSJodHRwOi8vd3d3LWlzdC5tYXNzZXkuYWMubnovZHN0aXJsaW4vQ0FTVC9DQVNUL0htdWx0aXBsaWNhdGl2ZS9tdWx0aXBsaWNhdGl2ZTEuaHRtbCI+VVJMPC9hPl0KCgoKIyBEYXRhIFNvdXJjZXMKTmFzZGFxIEhpc3RvcmljYWwgUXVvdGUgRG93bmxvYWQuIFs8YSBocmVmPSJodHRwczovL3d3dy5uYXNkYXEuY29tL3N5bWJvbC9jc3YvaGlzdG9yaWNhbCI+VVJMPC9hPl0KCmBgYHtyfQpuYXNkYXEgJT4lIGFycmFuZ2UoZGF0ZSkKYGBgCg==