library(tidyverse)
library(visreg)
library(broom)
library(lubridate)

Introduction: Time Series - Basic Concepts

In this workshop, we will introduce a new concept in statistics - time series. Time series are observations or measurements that are indexed according to time regularly. Examples of time series are the number of newly confirmed COVID-19 cases per day in NZ, the global sales of iPhone per month, the quarterly unemployment rate in NZ, and the daily closing value of the NZX50.

Give a few examples on time series.

It is easy to notice that all the time series data are collected with a natural temporal ordering. In an abstract sense, we record a time series like \(y_1,y_2,...,y_T\), or just \(y_t,t=1,...,T\) where \(t=1,...,T\) is the time index.

Why does that make things different?

What happened today may affect tomorrow’s world but nothing could change the outcomes of yesterday. The time series has a natural temporal ordering which implies a unidirectional causal relationship. The structure of time series implied by this ordering distinguishes it from other types of data that are commonly analysed.

In addition to the time index, it is important to notice that data points in a time series are collected regularly over time. In the beginning of this workshop, we highlight the words, ‘per day’, ‘per month’, ‘quarterly’, and ‘daily’. These words define the frequency or periodicity of time series within one year, i.e. the number of observations within a fixed time period. The frequency is 12 for monthly time series and 365 for daily time series within one year.

List a few more adjectives/adverbs describing the frequency.

Answer: weekly, hourly, annually …

What is the frequency of the wind speed in Wellington per hour within one day?

Answer: 24

What is the frequency of the daily precipitation in Auckland within one week?

Answer: 7

The above concepts will be pretty easy to understand by exploring some real time series. So we will perform an exploratory data analysis on NZ housing data collected by the Reserve Bank of New Zealand(RBNZ) in Exercise 1. Interestingly, time can still be modelled by our linear model easily and the fitted linear model can be further used to make time series predictions. In Exercise 2 and 3, we will learn how to analyse time series via linear models.

You first need to install RBNZ and janitor using the Packages menu in the bottom right. Just click the install button and type in RBNZ, then install. Or you can install the package with R command install.packages(). RStudio may have prompted you about installing them when you loaded this file.

Exercise 1: Exploring NZ House Price

Before we start the formal statistical analysis on any real time series, it is essential to load the data first and then turn the data to a suitable format ready for any further analysis.

  1. The following R code chunk retrieves the quarterly time series on housing from the website of the Reserve Bank of New Zealand (RBNZ) (https://www.rbnz.govt.nz/statistics).
housing <- read_csv("https://www.massey.ac.nz/~jcmarsha/161122/data/housing.csv")
## Rows: 125 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl  (4): number, index, m, real_m
## dttm (1): date
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
housing
## # A tibble: 125 × 5
##    date                number index      m real_m
##    <dttm>               <dbl> <dbl>  <dbl>  <dbl>
##  1 1990-03-31 00:00:00  22856   477 123000   1790
##  2 1990-06-30 00:00:00  22685   480 125000   2043
##  3 1990-09-30 00:00:00  20175   483 126000   1873
##  4 1990-12-31 00:00:00  17476   482 126000   1985
##  5 1991-03-31 00:00:00  17331   479 124000   1567
##  6 1991-06-30 00:00:00  17898   470 123000   1626
##  7 1991-09-30 00:00:00  17774   468 125000   1541
##  8 1991-12-31 00:00:00  16785   466 125000   1684
##  9 1992-03-31 00:00:00  18656   468 125000   1498
## 10 1992-06-30 00:00:00  18561   467 127000   1701
## # … with 115 more rows

housing data contains one column for time information (date) and four columns for different housing information, i.e. total value of housing stock m, residential investment real_m, house price index (HPI) index, and the number of house sales number.

Specify the frequency of housing data.

Answer: 4

  1. Let’s make a scatter-line plot of house price index over time. You may customise it a bit to get a better appearance.
housing |> ggplot(aes(x=date,y=index)) + geom_point() + geom_line() +
  xlab('Time') + ylab('House Price Index') + ggtitle('Will Kiwi afford the house price?')

The warning message Removed 1 rows containing missing values (geom_point). in your HTML reminds you that there is an NA value in our data. Let’s remove this NA value from the tibble via drop_na() as

housing <- housing |> drop_na()

We can see clearly over the last three decades HPI was increasing in most years. There are a few exceptions, i.e., around 1998, 2008, 2011. What happened in these years?

Answer: the Asian Financial crisis in 1997, the global financial crisis of 2007–2008, and the 2011 Christchurch earthquake.

Another feature of this time series is that HPI seems increase with different speeds over different decades. In 1990s, HPI tends to be flatten or just increase moderately. But the first two decades in 21 century look quite crazy. The affordability of NZ house has become a core social issue. Generally speaking, HPI shows an increasing trend over the past thirty years.

What will happen in the next decade with the shock of COVID-19? Will the house price in NZ become more affordable in the future? To answer these questions, it is important for us to extract the long term trend from this time series and make predictions on the future HPI.

  1. Make scatter-line plot for the rest three variables in housing. Briefly summarise their features.

Answer:

housing |> ggplot(aes(x=date,y=number)) + geom_point() + geom_line() +
  xlab('Time') + ylab('Number of House Sales') + ggtitle('How many houses are sold in the past years?')

No clear trend or seasonality can be observed here. But the pattern can be linked to HPI. When the house price is increasing, the number of house sales tends to be high. However, when the house price is fatten, the number of house sales tends to be low. One can also expect the internal correlation between successive numbers given the frequent short term movements in this time series (increasing or decreasing within several successive quarters).

housing |> ggplot(aes(x=date,y=m)) + geom_point() + geom_line() +
  xlab('Time') + ylab('Total Value of Housing Stock') + ggtitle('Is housing market always prospering?')

Almost same features as we can see from HPI. Just notice that the total value of housing stock can be roughly calculated by the average house price times the total number of houses. The newly built houses make the depression caused by the Asian Financial crisis in 1997 and the 2011 Christchurch earthquake less obvious.

housing |> ggplot(aes(x=date,y=real_m)) + geom_point() + geom_line() +
  xlab('Time') + ylab('Residential Investment') + ggtitle('
Residential Investment Opportunity!')

The residential investment has a similar general pattern as the number of house sales as it contributes a certain portion to the whole housing market (another important contributor is the first home buyer.) However, the details of residential investment are different from the number of house sales. We see many zigzag segments in this time series especially in the first two decades. This may reflect the self-regulatory rationality of the investors who may not follow the trend of an over-heated housing market. OK, I have to say that maybe the housing market in our country becomes a little crazy in the recent years. The last sentence is purely a personal comment.

Exercise 2: Capturing the Trend of House Price

The time index \(t=1,2,3,...,T\) implied by the natural temporal ordering is always increasing. A very simple idea is to consider the pairs \((1,y_1)\), \((2,y_2)\),…,\((T,y_T)\) just like the pairs of observation \((x_i,y_i),i=1,2,...,n\) in the linear model.

If the underlying trend of a time series shows either increasing or decreasing pattern consistently, it is natural to link it with the time index of each observation. Why?

Answer: The correlation coefficient will confirm your intuition.

Moreover, if the underlying trend looks linear, a simple linear model like y~t becomes a perfect candidate to discover the underlying trend in a time series.

Anyway, let’s try it on HPI! Before fitting our linear model, it is necessary to expand our tibble by adding the time indices \(1,2,3,...\) as follows.

housing.ts <- housing |> mutate(time=1:n())
housing.ts
## # A tibble: 125 × 6
##    date                number index      m real_m  time
##    <dttm>               <dbl> <dbl>  <dbl>  <dbl> <int>
##  1 1990-03-31 00:00:00  22856   477 123000   1790     1
##  2 1990-06-30 00:00:00  22685   480 125000   2043     2
##  3 1990-09-30 00:00:00  20175   483 126000   1873     3
##  4 1990-12-31 00:00:00  17476   482 126000   1985     4
##  5 1991-03-31 00:00:00  17331   479 124000   1567     5
##  6 1991-06-30 00:00:00  17898   470 123000   1626     6
##  7 1991-09-30 00:00:00  17774   468 125000   1541     7
##  8 1991-12-31 00:00:00  16785   466 125000   1684     8
##  9 1992-03-31 00:00:00  18656   468 125000   1498     9
## 10 1992-06-30 00:00:00  18561   467 127000   1701    10
## # … with 115 more rows

Why we don’t use date immediately?

Answer: Actually, you can use date. I prefer the time index as it provides a consistent way to analyse any time series. A tricky issue of using date is that the intercept may not be interpretable. For the time index, it is easy to understand that the intercept is the observation at time 0, i.e. the day/month/quarter right before the first one in our time series. For date, interpreting the intercept will force us to go back to the birth of Jesus.

  1. Now we can fit a simple linear regression model as follows
hpi.lm <- lm(index~time,data=housing.ts)
summary(hpi.lm)
## 
## Call:
## lm(formula = index ~ time, data = housing.ts)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -290.66 -190.74   -6.63  144.37  854.40 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  90.8132    38.5253   2.357     0.02 *  
## time         18.9983     0.5306  35.803   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 214.1 on 123 degrees of freedom
## Multiple R-squared:  0.9124, Adjusted R-squared:  0.9117 
## F-statistic:  1282 on 1 and 123 DF,  p-value: < 2.2e-16

Comment on the R summary of this model.

Answer: Very high \(R^2\). Both coefficients are highly significant. Looks good!

We can further visualise the fitted linear model as

visreg(hpi.lm, gg=TRUE) + xlab('Time Index') + ylab('House Price Index')

Comment on the goodness of fit of the linear trend model based on the visualisation.

Answer: Even with a very high \(R^2\), the plot make me feel worried. A straight line can not capture the complicated cycles in the house market.

Perform a standard residual diagnostics for your fitted linear model by following the steps in Lab C3.

plot(hpi.lm)

Answer: The residuals diagnostics confirm our concerns on the visualisation. None of the residuals plots looks good. Strange patterns in all plots suggest that there is a lot of information hidden in the residuals.

Optional Challenge: Make a log transformation on HPI and fit another linear model. Compare the R summary with the model without transformation. Perform the standard residual diagnostics on this model.

hpi.lm.log <- lm(log(index)~time,data=housing.ts)
summary(hpi.lm.log)
## 
## Call:
## lm(formula = log(index) ~ time, data = housing.ts)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.193098 -0.064345  0.002189  0.056390  0.215112 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 6.0198407  0.0165470  363.80   <2e-16 ***
## time        0.0155930  0.0002279   68.42   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.09195 on 123 degrees of freedom
## Multiple R-squared:  0.9744, Adjusted R-squared:  0.9742 
## F-statistic:  4681 on 1 and 123 DF,  p-value: < 2.2e-16
visreg(hpi.lm.log,trans = exp,partial=TRUE)

plot(hpi.lm.log)

Answer: \(R^2\) gets even higher! The visualisation looks slightly better. The residuals plots are still worrying except for the Q-Q plot.

  1. A group of talented school students studied HPI before they learned the log transformation. They noticed that HPI was increasing slowly at the beginning but increasing faster later. In other words, the increase in HPI occurs at an increasing rate for each successive time period. The students suggested that we may use a quadratic function instead of the linear function to model the trend as follows. \[ mean(y_t)=a+bt+ct^2. \] But the students didn’t know how to find the coefficients \(a,b,c\). Can we help?

The answer is YES! Interestingly, this can be done via lm() easily as follows.

hpi.qm <- lm(index~time+I(time^2), data=housing.ts)
summary(hpi.qm)
## 
## Call:
## lm(formula = index ~ time + I(time^2), data = housing.ts)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -204.37  -74.09  -11.28   65.60  471.59 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 4.925e+02  3.306e+01  14.898   <2e-16 ***
## time        2.311e-02  1.211e+00   0.019    0.985    
## I(time^2)   1.506e-01  9.312e-03  16.173   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 121.2 on 122 degrees of freedom
## Multiple R-squared:  0.9722, Adjusted R-squared:  0.9717 
## F-statistic:  2129 on 2 and 122 DF,  p-value: < 2.2e-16

It is important to use I(time^2), not time^2 if you want to fit a quadratic trend to a time series.

We can find an additional row in Coefficients: as I(time^2). There is the estimate for \(c\) as you may expected. The interpretation of rest is just like what we have done in Lab C2. One interesting thing is that the estimate for \(b\) becomes insignificant!

Compare the R summary with that in Step 1. Discuss your findings.

Answer: \(R^2\) gets higher just like the transformed model. The quadratic term is highly significant but the linear term becomes insignificant.

Visualising this model can be done with visreg() in the usual way, but let’s use the broom library instead to do it here:

augment(hpi.qm) |> ggplot(aes(x = time ,y=index)) + geom_point() + geom_line() +
        geom_line(aes(y=.fitted),col='red') + xlab('Time Index') + ylab('House Price Index')

You may add the confidence band or prediction band by following Exercise 1 in Lab C3.

Sometimes people prefer making a plot in the calendar time rather than in the time index. This can be done easily by using broom as follows. Notice that we add the original data set housing.ts into augment() and change x = time to x = date in ggplot(). This is very handy in many cases especially you are fitting a linear model with transformation.

augment(hpi.qm,housing.ts) |> ggplot(aes(x = date ,y=index)) + geom_point() + geom_line() +
        geom_line(aes(y=.fitted),col='red') + xlab('Time') + ylab('House Price Index')

Comment on the goodness of fit of the quadratic trend model based on the visualisation.

Answer: It is slightly better than the linear model. But the general pattern in HPI time series still deviate from the curve frequently.

  1. Now we have two candidate models from Step 1 and 2 for HPI time series. A natural question arises: Which model is better?

Both R summary and visualisation can be used. The residual analysis is another tool to find the better model. While these tools are useful, we have a more delicate way to compare the above two models performance. Particularly, the linear model can be regarded as a special case of the quadratic model. If we set \(c=0\), the quadratic model reduces to the linear model. If two models can be linked in such manner, we call these two models are nested. The simple model is usually called a reduced model (linear one in this case) and the complicated model is usually called an extended model (quadratic one in this case).

An ANOVA (ANalysis Of VAriance) test can be used to compare two nested candidate models as

anova(hpi.lm, hpi.qm)
## Analysis of Variance Table
## 
## Model 1: index ~ time
## Model 2: index ~ time + I(time^2)
##   Res.Df     RSS Df Sum of Sq      F    Pr(>F)    
## 1    123 5636696                                  
## 2    122 1792830  1   3843866 261.57 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

anova() tests if the additional parameter(s) \(c\) in the quadratic model is zero or not, i.e. the null hypothesis is \(c=0\) and the p-value is reported by Pr(>F). What does the output of anova() suggest? If the P-value is small we can conclude that that Model 2 - the quadratic trend model is better.

It is easy to find the improvement in goodness of fit from the quadratic model is significant for HPI time series. In fact, with the additional coefficient \(c\), the quadratic trend model (and any extended model) is guaranteed to get closer to the real time series than the linear trend model(and any reduced model).

A pure quadratic model as \(y=a+cx^2\) is also nested with the quadratic model \(y=a+bx+cx^2\). Why? Use ANOVA to tell which model is better.

Answer: Make sure that you still have I() in your pure quadratic model. anova() tells us that the linear term can be excluded for a more concise model.

hpi.pqm <- lm(index~I(time^2), data=housing.ts)
anova(hpi.pqm, hpi.qm)
## Analysis of Variance Table
## 
## Model 1: index ~ I(time^2)
## Model 2: index ~ time + I(time^2)
##   Res.Df     RSS Df Sum of Sq     F Pr(>F)
## 1    123 1792835                          
## 2    122 1792830  1    5.3509 4e-04 0.9848

However, the quadratic trend model is more complicated than the linear trend model and the pure quadratic model as it has one more coefficient. If the improvement is just marginal, we may prefer the linear model or the pure quadratic model as they are so concise. The ANOVA test examines if the extended model (quadratic trend model ) explains sufficient variability in \(y\) compared to the reduced model (linear trend model) at a price of adding one more parameter. We will revisit this topic in Lab C6.

  1. Predict HPI for Q3 and Q4 of 2020 with both linear trend model and quadratic trend model with 95% prediction intervals. Comment on your predictions.

Answer: One has to figure out that the time indices for Q3 and Q4 of 2020 are 123 and 124.

newtime <- data.frame(time=c(123,124))
predict(hpi.lm,newdata=newtime)
##        1        2 
## 2427.600 2446.598
predict(hpi.qm,newdata=newtime)
##        1        2 
## 2773.671 2810.891

The linear trend model is certainly underestimating the HPI. The quadratic one may do a better job. However, both two models are unable to quantify the uncertainties in a right way due the highly ill-posed residuals.

Exercise 3: Addressing Seasonalities in Consumption

Besides the trend extracted by our linear models, another important feature of time series is the seasonality or seasonal effects. Many things exhibit seasonal patterns over different time periods (e.g. months or quarters) of a year. For example:

Enumerate a few more time series with a seasonal pattern.

Answer: electricity production per month/week/day, sales of ice creams, etc.

Clearly, seasonality is closely related to the frequency and their linkages can be expected just by reading the words ‘monthly’, ‘weekly’, or ‘quarterly’. In fact, in a time series the seasonal pattern for a particular quarter or month is exactly repeated by the frequency of the time series.

Obviously, it is necessary to include the seasonal effects in time series modelling in most cases. Otherwise, the prediction becomes rather superficial as it only accounts for the long-term trend.

How can we make use of the seasonal effects? We will start by exploring the data set M2 Consumption from RBNZ.

  1. First of all, we load consumption data from the RBNZ website - see (https://www.rbnz.govt.nz/statistics/m2) for more information on this time series. We then subset the data, filtering out the 2020 data and before. We’ll reserve 2020 and 2021 for prediction.
consumption <- read_csv("https://www.massey.ac.nz/~jcmarsha/161122/data/consumption.csv")
## Rows: 104 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl  (1): x_m
## dttm (1): date
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
retail.trade.sales <- consumption |>
  filter(date < ymd('2020-01-01'))
retail.trade.sales |>
  ggplot(aes(x=date,y=x_m)) + geom_point() + geom_line() + 
  xlab('Time') + ylab('Retail Trade Sales')

Specify the frequency of this time series.

Answer: 4

We can clearly see an increasing trend over these years in general. The regular fluctuations of sales within one year suggest strong seasonal effects. Why? One shall also notice that the seasonal variability is increasing over time.

  1. First of all, we can extract a linear trend from this time series as follows
rts.ts <- retail.trade.sales |> mutate(time=1:n())
rts.lm <- lm(x_m~time,data=rts.ts)
summary(rts.lm)
## 
## Call:
## lm(formula = x_m ~ time, data = rts.ts)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1533.6  -848.5  -184.9   664.4  3535.4 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 7347.250    221.589   33.16   <2e-16 ***
## time         166.483      3.887   42.84   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1088 on 96 degrees of freedom
## Multiple R-squared:  0.9503, Adjusted R-squared:  0.9498 
## F-statistic:  1835 on 1 and 96 DF,  p-value: < 2.2e-16

The R output reads pretty good with all coefficient significant and a very high \(R^2=0.9457\)!

Let’s visualise the model as follows:

visreg(rts.lm, gg=TRUE) + xlab('Time Index') + ylab('Retail Trade Sales')

Oops. A simple line won’t be able to capture the seasonality.

  1. Let’s perform a standard residual diagnostics on the fitted model.
plot(rts.lm)

Except for Q-Q plot, rest diagnostic plots look really really weird! Add a few detailed comments on each plot.

In addition to the above standard diagnostic plots, the residuals versus time plot is frequently used in the residual diagnostics of time series modelling as

augment(rts.lm) |>
  ggplot(aes(x=time,y=.resid)) + geom_point() + geom_line() +
  ggtitle('Residuals versus Time Index') + xlab('Time Index')

The pattern in the residuals versus time plot seems containing the information on the seasonality! Such pattern actually suggests that the residuals violate the i.i.d. condition, i.e. the independently and identically distributed condition. We can conjecture that the residuals in different seasons is coming from different distributions and they are correlated in the time order. How can we check the internal correlation in residuals?

Answer: This one is not straightforward. To discover the internal correlation in a time series, we need a bit more background knowledge. Nevertheless, we may conjecture that there is a one-step correlation in a time series, say \(x_1\) is correlated with \(x_2\), \(x_2\) is correlated with \(x_3\),…,\(x_{t-1}\) is correlated with \(x_t\). Like \((x_i,y_i)\) is paired for computing the correlation coefficient, we have the pairs \((x_1,x_2)\), \((x_2,x_3)\),…,\(x_{t-1},x_t\) and we can compute the correlation between \(\{x_t\}\) and \(\{x_{t+1}\}\). Such a correlation coefficient within a time series is called autocorrelation. We can further examine the higher order (multi-step) autocorrelation. These issues will be explored in the last two lectures.

set.seed(2020)
tibble(e=rnorm(100)) |> summarise(acf1=cor(e[-1],e[-length(e)]))
## # A tibble: 1 × 1
##     acf1
##    <dbl>
## 1 0.0988

We further make a box plot to compare the residuals over different quarters. Here we add the data set rts.ts to augment() to make sure that we have the access to the calendar time and we can get the corresponding quarter by quarter() from lubridate().

augment(rts.lm, rts.ts) |> mutate(quarter=quarter(date)) |>
  ggplot(aes(y=.resid,x=quarter)) + geom_boxplot(aes(group=quarter))

Each box in the above boxplot characterises the residuals, i.e. the deviations from the trend line, at a specific quarter. The residuals here are a combination of true random errors and seasonal effects.

How can we extract the information on seasonality? A simple idea is to estimate the seasonal effect at a particular quarter by the mean of residuals at this quarter. This will add a constant shift to the trend line for each quarter. The rest deviations will be regarded as the final residuals.

Try to compute the residuals mean of each quarter by summarise().

Answer:

augment(rts.lm, rts.ts) |> mutate(quarter=quarter(date)) |> group_by(quarter) |> summarise(resid.mean=mean(.resid))
## # A tibble: 4 × 2
##   quarter resid.mean
##     <int>      <dbl>
## 1       1      -242.
## 2       2      -580.
## 3       3      -534.
## 4       4      1323.
  1. The above procedures seems work well but tedious. A surprising fact is that we can complete these jobs with one line by lm() as follows. The trick is that we must turn quarter into a factor (categorical, qualitative) variable via factor() and include it in the regression formula in lm().
rts.ts.q <- rts.ts |> mutate(quarter=factor(quarter(date))) 
rts.lm.q <- lm(x_m~time+quarter,data=rts.ts.q)
summary(rts.lm.q)
## 
## Call:
## lm(formula = x_m ~ time + quarter, data = rts.ts.q)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1075.9  -636.0  -136.9   492.4  2224.5 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 7117.490    203.721  34.937  < 2e-16 ***
## time         166.239      2.706  61.433  < 2e-16 ***
## quarter2    -338.197    218.743  -1.546    0.125    
## quarter3    -291.888    216.528  -1.348    0.181    
## quarter4    1564.593    216.545   7.225 1.36e-10 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 757.7 on 93 degrees of freedom
## Multiple R-squared:  0.9767, Adjusted R-squared:  0.9757 
## F-statistic: 972.8 on 4 and 93 DF,  p-value: < 2.2e-16

We have obtained a few more rows in Coefficients:, including quarter2,quarter3,quarter4 with Estimate, Std. Error, etc.

Looks good! Oh, wait, where is quarter1?

In fact, the seasonal effect at the first quarter is included in (Intercept)\(=True.Intercept + Seasonal.Effect.of.Q1\)! More importantly, Estimates for the rest three quarters is not the seasonal effects at corresponding quarters. They are instead the difference between \(Seasonal.Effect.of.Q1\) and \(Seasonal.Effect.of.Q2.Q3.and .Q4\)

Therefore, we can write the follow equation for our fitted linear model as \[ mean(Retail.Trade.Sales)=7240.8 + 162.6 \times Time + D_Q, Q=1,2,3,4. \] where \(D_1=0\), \(D_2=-330.0\),\(D_3=-278.0\) and \(D_4=1521.8\).

More details on this strange issue after adding factor variables in the linear model will be revealed in the coming Lectures C5 and C6, and corresponding Labs.

Visualise our model with both linear trend and seasonal effects:

augment(rts.lm.q) |> ggplot(aes(x = time ,y=x_m)) + geom_point() + geom_line(alpha=0.3) +
  geom_line(aes(y=.fitted),col='red') + xlab('Time Index') + ylab('Retail Trade Sales')

Now the fitted curve looks much more reasonable! Do you think there is any room for further improvement?

Answer: Yes. We can see that the seasonal variations are increasing over time. A log transform can help stabilise the increasing variability.

Perform a time series residual analysis on your fitted model. Remember the residuals versus time plot.

Answer:

plot(rts.lm.q)

augment(rts.lm.q) |> ggplot(aes(x=time,y=.resid)) + geom_point()

Not really good! Still have some obvious patterns in the residuals. None of the plots pass the diagonostics. Need some better models to address it.

Try to visualise the model with visreg(). What do you find from the plots of visreg().

Answer:

visreg(rts.lm.q)

visreg() splits the effect of trend and seasonality to two separate graphs.

  1. We can predict the sales in 2020 as follows
newtime <- data.frame(time=nrow(rts.ts)+(1:4),quarter=factor(1:4))
augment(rts.lm.q,newdata=newtime)
## # A tibble: 4 × 3
##    time quarter .fitted
##   <int> <fct>     <dbl>
## 1    99 1        23575.
## 2   100 2        23403.
## 3   101 3        23616.
## 4   102 4        25638.

By adding newdata=newtime in augment(), we can get the prediction immediately just like predict() and the results are organised well in a tibble.

Compute the mean square errors of your prediction in 2020.

rts.2020 <- consumption |>
  filter(date >= ymd('2020-01-01') & date <= ymd('2021-01-01') ) |> 
  cbind(augment(rts.lm.q,newdata=newtime)) 
rts.2020
##         date   x_m time quarter  .fitted
## 1 2020-03-31 24794   99       1 23575.17
## 2 2020-06-30 20052  100       2 23403.21
## 3 2020-09-30 25545  101       3 23615.76
## 4 2020-12-31 28505  102       4 25638.48
rts.2020 |> summarise(mse=mean((x_m-.fitted)^2))
##       mse
## 1 6163773

The predictions are not really good as the sales have been underestimated consistently.

What will happen if we use the fitted model to predict the sales in 2021?

Answer: We won’t get good predictions given the shock of COVID-19

Optional Challenge: Will log transform improve your model fit? Try it!

Answer:

rts.lm.q.log <- lm(log(x_m)~time+quarter,data=rts.ts.q)
summary(rts.lm.q.log)
## 
## Call:
## lm(formula = log(x_m) ~ time + quarter, data = rts.ts.q)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.076174 -0.026552 -0.008532  0.015924  0.112484 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  9.0461066  0.0112518 803.968  < 2e-16 ***
## time         0.0110025  0.0001495  73.616  < 2e-16 ***
## quarter2    -0.0207053  0.0120815  -1.714   0.0899 .  
## quarter3    -0.0200914  0.0119592  -1.680   0.0963 .  
## quarter4     0.0951143  0.0119601   7.953 4.26e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.04185 on 93 degrees of freedom
## Multiple R-squared:  0.9836, Adjusted R-squared:  0.9829 
## F-statistic:  1391 on 4 and 93 DF,  p-value: < 2.2e-16
augment(rts.lm.q.log,newdata=newtime) |> mutate(.pred=exp(.fitted))
## # A tibble: 4 × 4
##    time quarter .fitted  .pred
##   <int> <fct>     <dbl>  <dbl>
## 1    99 1          10.1 25219.
## 2   100 2          10.1 24976.
## 3   101 3          10.1 25267.
## 4   102 4          10.3 28666.

Predictions are much better! You can check residuals and visualisation accordingly.

LS0tCnRpdGxlOiAiV29ya3Nob3AgQzA0OiBUaW1lIFNlcmllcyBSZWdyZXNzaW9uIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogCiAgICB0b2M6IHllcwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh2aXNyZWcpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkobHVicmlkYXRlKQpgYGAKCiMjIEludHJvZHVjdGlvbjogVGltZSBTZXJpZXMgLSBCYXNpYyBDb25jZXB0cwoKSW4gdGhpcyB3b3Jrc2hvcCwgd2Ugd2lsbCBpbnRyb2R1Y2UgYSBuZXcgY29uY2VwdCBpbiBzdGF0aXN0aWNzIC0gKip0aW1lIHNlcmllcyoqLiBUaW1lIHNlcmllcyBhcmUgb2JzZXJ2YXRpb25zIG9yIG1lYXN1cmVtZW50cyB0aGF0IGFyZSBpbmRleGVkIGFjY29yZGluZyB0byB0aW1lICoqcmVndWxhcmx5KiouIEV4YW1wbGVzIG9mIHRpbWUgc2VyaWVzIGFyZSB0aGUgbnVtYmVyIG9mIG5ld2x5IGNvbmZpcm1lZCBDT1ZJRC0xOSBjYXNlcyAqKnBlciBkYXkqKiBpbiBOWiwgdGhlIGdsb2JhbCBzYWxlcyBvZiBpUGhvbmUgKipwZXIgbW9udGgqKiwgdGhlICoqcXVhcnRlcmx5KiogdW5lbXBsb3ltZW50IHJhdGUgaW4gTlosIGFuZCB0aGUgKipkYWlseSoqIGNsb3NpbmcgdmFsdWUgb2YgdGhlIE5aWDUwLgoKKkdpdmUgYSBmZXcgZXhhbXBsZXMgb24gdGltZSBzZXJpZXMuKgoKSXQgaXMgZWFzeSB0byBub3RpY2UgdGhhdCBhbGwgdGhlIHRpbWUgc2VyaWVzIGRhdGEgYXJlIGNvbGxlY3RlZCB3aXRoIGEgbmF0dXJhbCB0ZW1wb3JhbCBvcmRlcmluZy4gSW4gYW4gYWJzdHJhY3Qgc2Vuc2UsIHdlIHJlY29yZCBhIHRpbWUgc2VyaWVzIGxpa2UgJHlfMSx5XzIsLi4uLHlfVCQsIG9yIGp1c3QgJHlfdCx0PTEsLi4uLFQkIHdoZXJlICR0PTEsLi4uLFQkIGlzIHRoZSAqKnRpbWUgaW5kZXgqKi4gCgpXaHkgZG9lcyB0aGF0IG1ha2UgdGhpbmdzIGRpZmZlcmVudD8KCldoYXQgaGFwcGVuZWQgdG9kYXkgbWF5IGFmZmVjdCB0b21vcnJvdydzIHdvcmxkIGJ1dCBub3RoaW5nIGNvdWxkIGNoYW5nZSB0aGUgb3V0Y29tZXMgb2YgeWVzdGVyZGF5LiBUaGUgdGltZSBzZXJpZXMgaGFzIGEgbmF0dXJhbCB0ZW1wb3JhbCBvcmRlcmluZyB3aGljaCBpbXBsaWVzIGEgKip1bmlkaXJlY3Rpb25hbCoqIGNhdXNhbCByZWxhdGlvbnNoaXAuIFRoZSBzdHJ1Y3R1cmUgb2YgdGltZSBzZXJpZXMgaW1wbGllZCBieSB0aGlzIG9yZGVyaW5nIGRpc3Rpbmd1aXNoZXMgaXQgZnJvbSBvdGhlciB0eXBlcyBvZiBkYXRhIHRoYXQgYXJlIGNvbW1vbmx5IGFuYWx5c2VkLiAKCkluIGFkZGl0aW9uIHRvIHRoZSB0aW1lIGluZGV4LCBpdCBpcyBpbXBvcnRhbnQgdG8gbm90aWNlIHRoYXQgZGF0YSBwb2ludHMgaW4gYSB0aW1lIHNlcmllcyBhcmUgY29sbGVjdGVkICoqcmVndWxhcmx5Kiogb3ZlciB0aW1lLiBJbiB0aGUgYmVnaW5uaW5nIG9mIHRoaXMgd29ya3Nob3AsIHdlIGhpZ2hsaWdodCB0aGUgd29yZHMsICdwZXIgZGF5JywgJ3BlciBtb250aCcsICdxdWFydGVybHknLCBhbmQgJ2RhaWx5Jy4gVGhlc2Ugd29yZHMgZGVmaW5lIHRoZSAqKmZyZXF1ZW5jeSoqIG9yICoqcGVyaW9kaWNpdHkqKiBvZiB0aW1lIHNlcmllcyAqKndpdGhpbiBvbmUgeWVhcioqLCBpLmUuIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIHdpdGhpbiBhIGZpeGVkIHRpbWUgcGVyaW9kLiBUaGUgZnJlcXVlbmN5IGlzIDEyIGZvciBtb250aGx5IHRpbWUgc2VyaWVzIGFuZCAzNjUgZm9yIGRhaWx5IHRpbWUgc2VyaWVzIHdpdGhpbiBvbmUgeWVhci4KCipMaXN0IGEgZmV3IG1vcmUgYWRqZWN0aXZlcy9hZHZlcmJzIGRlc2NyaWJpbmcgdGhlIGZyZXF1ZW5jeS4qCgoqKkFuc3dlcioqOiB3ZWVrbHksIGhvdXJseSwgYW5udWFsbHkgLi4uIAoKKldoYXQgaXMgdGhlIGZyZXF1ZW5jeSBvZiB0aGUgd2luZCBzcGVlZCBpbiBXZWxsaW5ndG9uIHBlciBob3VyIHdpdGhpbiBvbmUgZGF5PyoKCioqQW5zd2VyKio6IDI0CgoKKldoYXQgaXMgdGhlIGZyZXF1ZW5jeSBvZiB0aGUgZGFpbHkgcHJlY2lwaXRhdGlvbiBpbiBBdWNrbGFuZCB3aXRoaW4gb25lIHdlZWs/KgoKKipBbnN3ZXIqKjogNwoKVGhlIGFib3ZlIGNvbmNlcHRzIHdpbGwgYmUgcHJldHR5IGVhc3kgdG8gdW5kZXJzdGFuZCBieSBleHBsb3Jpbmcgc29tZSByZWFsIHRpbWUgc2VyaWVzLiBTbyB3ZSB3aWxsIHBlcmZvcm0gYW4gZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyBvbiBOWiBob3VzaW5nIGRhdGEgY29sbGVjdGVkIGJ5IHRoZSBSZXNlcnZlIEJhbmsgb2YgTmV3IFplYWxhbmQoUkJOWikgaW4gRXhlcmNpc2UgMS4gSW50ZXJlc3RpbmdseSwgdGltZSBjYW4gc3RpbGwgYmUgbW9kZWxsZWQgYnkgb3VyIGxpbmVhciBtb2RlbCBlYXNpbHkgYW5kIHRoZSBmaXR0ZWQgbGluZWFyIG1vZGVsIGNhbiBiZSBmdXJ0aGVyIHVzZWQgdG8gbWFrZSB0aW1lIHNlcmllcyBwcmVkaWN0aW9ucy4gSW4gRXhlcmNpc2UgMiBhbmQgMywgd2Ugd2lsbCBsZWFybiBob3cgdG8gYW5hbHlzZSB0aW1lIHNlcmllcyB2aWEgbGluZWFyIG1vZGVscy4KCllvdSBmaXJzdCBuZWVkIHRvIGluc3RhbGwgYFJCTlpgIGFuZCBgamFuaXRvcmAgdXNpbmcgdGhlIGBQYWNrYWdlc2AgbWVudSBpbiB0aGUgYm90dG9tIHJpZ2h0LiBKdXN0IGNsaWNrIHRoZSBpbnN0YWxsIGJ1dHRvbiBhbmQgdHlwZSBpbiBgUkJOWmAsIHRoZW4gaW5zdGFsbC4gT3IgeW91IGNhbiBpbnN0YWxsIHRoZSBwYWNrYWdlIHdpdGggUiBjb21tYW5kIGBpbnN0YWxsLnBhY2thZ2VzKClgLiBSU3R1ZGlvIG1heSBoYXZlIHByb21wdGVkIHlvdSBhYm91dCBpbnN0YWxsaW5nIHRoZW0gd2hlbiB5b3UgbG9hZGVkIHRoaXMgZmlsZS4KCiMjIEV4ZXJjaXNlIDE6IEV4cGxvcmluZyBOWiBIb3VzZSBQcmljZQoKQmVmb3JlIHdlIHN0YXJ0IHRoZSBmb3JtYWwgc3RhdGlzdGljYWwgYW5hbHlzaXMgb24gYW55IHJlYWwgdGltZSBzZXJpZXMsIGl0IGlzIGVzc2VudGlhbCB0byBsb2FkIHRoZSBkYXRhIGZpcnN0IGFuZCB0aGVuIHR1cm4gdGhlIGRhdGEgdG8gYSBzdWl0YWJsZSBmb3JtYXQgcmVhZHkgZm9yIGFueSBmdXJ0aGVyIGFuYWx5c2lzLiAKCjEuIFRoZSBmb2xsb3dpbmcgUiBjb2RlIGNodW5rIHJldHJpZXZlcyB0aGUgcXVhcnRlcmx5IHRpbWUgc2VyaWVzIG9uIGhvdXNpbmcgZnJvbSB0aGUgd2Vic2l0ZSBvZiB0aGUgUmVzZXJ2ZSBCYW5rIG9mIE5ldyBaZWFsYW5kIChSQk5aKSAoaHR0cHM6Ly93d3cucmJuei5nb3Z0Lm56L3N0YXRpc3RpY3MpLgoKYGBge3J9CmhvdXNpbmcgPC0gcmVhZF9jc3YoImh0dHBzOi8vd3d3Lm1hc3NleS5hYy5uei9+amNtYXJzaGEvMTYxMTIyL2RhdGEvaG91c2luZy5jc3YiKQpob3VzaW5nCmBgYAoKYGhvdXNpbmdgIGRhdGEgY29udGFpbnMgb25lIGNvbHVtbiBmb3IgdGltZSBpbmZvcm1hdGlvbiAoYGRhdGVgKSBhbmQgZm91ciBjb2x1bW5zIGZvciBkaWZmZXJlbnQgaG91c2luZyBpbmZvcm1hdGlvbiwgaS5lLiB0b3RhbCB2YWx1ZSBvZiBob3VzaW5nIHN0b2NrIGBtYCwgcmVzaWRlbnRpYWwgaW52ZXN0bWVudCBgcmVhbF9tYCwgaG91c2UgcHJpY2UgaW5kZXggKEhQSSkgYGluZGV4YCwgYW5kIHRoZSBudW1iZXIgb2YgaG91c2Ugc2FsZXMgYG51bWJlcmAuCgoqU3BlY2lmeSB0aGUgZnJlcXVlbmN5IG9mIGBob3VzaW5nYCBkYXRhLioKCioqQW5zd2VyKio6IDQKCjIuIExldCdzIG1ha2UgYSBzY2F0dGVyLWxpbmUgcGxvdCBvZiBob3VzZSBwcmljZSBpbmRleCBvdmVyIHRpbWUuIFlvdSBtYXkgY3VzdG9taXNlIGl0IGEgYml0IHRvIGdldCBhIGJldHRlciBhcHBlYXJhbmNlLiAKCmBgYHtyfQpob3VzaW5nIHw+IGdncGxvdChhZXMoeD1kYXRlLHk9aW5kZXgpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGluZSgpICsKICB4bGFiKCdUaW1lJykgKyB5bGFiKCdIb3VzZSBQcmljZSBJbmRleCcpICsgZ2d0aXRsZSgnV2lsbCBLaXdpIGFmZm9yZCB0aGUgaG91c2UgcHJpY2U/JykKYGBgCgoKVGhlIHdhcm5pbmcgbWVzc2FnZSBgUmVtb3ZlZCAxIHJvd3MgY29udGFpbmluZyBtaXNzaW5nIHZhbHVlcyAoZ2VvbV9wb2ludCkuYCBpbiB5b3VyIEhUTUwgcmVtaW5kcyB5b3UgdGhhdCB0aGVyZSBpcyBhbiBgTkFgIHZhbHVlIGluIG91ciBkYXRhLiBMZXQncyByZW1vdmUgdGhpcyBOQSB2YWx1ZSBmcm9tIHRoZSB0aWJibGUgdmlhIGBkcm9wX25hKClgIGFzCmBgYHtyfQpob3VzaW5nIDwtIGhvdXNpbmcgfD4gZHJvcF9uYSgpCmBgYAoKV2UgY2FuIHNlZSBjbGVhcmx5IG92ZXIgdGhlIGxhc3QgdGhyZWUgZGVjYWRlcyBIUEkgd2FzIGluY3JlYXNpbmcgaW4gbW9zdCB5ZWFycy4gVGhlcmUgYXJlIGEgZmV3IGV4Y2VwdGlvbnMsIGkuZS4sIGFyb3VuZCAxOTk4LCAyMDA4LCAyMDExLiAqV2hhdCBoYXBwZW5lZCBpbiB0aGVzZSB5ZWFycz8qCgoqKkFuc3dlcioqOiAgdGhlIEFzaWFuIEZpbmFuY2lhbCBjcmlzaXMgaW4gMTk5NywgdGhlIGdsb2JhbCBmaW5hbmNpYWwgY3Jpc2lzIG9mIDIwMDfigJMyMDA4LCBhbmQgdGhlIDIwMTEgQ2hyaXN0Y2h1cmNoIGVhcnRocXVha2UuCgpBbm90aGVyIGZlYXR1cmUgb2YgdGhpcyB0aW1lIHNlcmllcyBpcyB0aGF0IEhQSSBzZWVtcyBpbmNyZWFzZSB3aXRoIGRpZmZlcmVudCBzcGVlZHMgb3ZlciBkaWZmZXJlbnQgZGVjYWRlcy4gSW4gMTk5MHMsIEhQSSB0ZW5kcyB0byBiZSBmbGF0dGVuIG9yIGp1c3QgaW5jcmVhc2UgbW9kZXJhdGVseS4gQnV0IHRoZSBmaXJzdCB0d28gZGVjYWRlcyBpbiAyMSBjZW50dXJ5IGxvb2sgcXVpdGUgY3JhenkuIFRoZSBhZmZvcmRhYmlsaXR5IG9mIE5aIGhvdXNlIGhhcyBiZWNvbWUgYSBjb3JlIHNvY2lhbCBpc3N1ZS4gR2VuZXJhbGx5IHNwZWFraW5nLCBIUEkgc2hvd3MgYW4gaW5jcmVhc2luZyAqKnRyZW5kKiogb3ZlciB0aGUgcGFzdCB0aGlydHkgeWVhcnMuIAoKV2hhdCB3aWxsIGhhcHBlbiBpbiB0aGUgbmV4dCBkZWNhZGUgd2l0aCB0aGUgc2hvY2sgb2YgQ09WSUQtMTk/IFdpbGwgdGhlIGhvdXNlIHByaWNlIGluIE5aIGJlY29tZSBtb3JlIGFmZm9yZGFibGUgaW4gdGhlIGZ1dHVyZT8gVG8gYW5zd2VyIHRoZXNlIHF1ZXN0aW9ucywgaXQgaXMgaW1wb3J0YW50IGZvciB1cyB0byBleHRyYWN0IHRoZSBsb25nIHRlcm0gKip0cmVuZCoqIGZyb20gdGhpcyB0aW1lIHNlcmllcyBhbmQgbWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgZnV0dXJlIEhQSS4gCgozLiAqTWFrZSBzY2F0dGVyLWxpbmUgcGxvdCBmb3IgdGhlIHJlc3QgdGhyZWUgdmFyaWFibGVzIGluIGBob3VzaW5nYC4gQnJpZWZseSBzdW1tYXJpc2UgdGhlaXIgZmVhdHVyZXMuKgoKKipBbnN3ZXIqKjoKCmBgYHtyfQpob3VzaW5nIHw+IGdncGxvdChhZXMoeD1kYXRlLHk9bnVtYmVyKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoKSArCiAgeGxhYignVGltZScpICsgeWxhYignTnVtYmVyIG9mIEhvdXNlIFNhbGVzJykgKyBnZ3RpdGxlKCdIb3cgbWFueSBob3VzZXMgYXJlIHNvbGQgaW4gdGhlIHBhc3QgeWVhcnM/JykKYGBgCgpObyBjbGVhciB0cmVuZCBvciBzZWFzb25hbGl0eSBjYW4gYmUgb2JzZXJ2ZWQgaGVyZS4gQnV0IHRoZSBwYXR0ZXJuIGNhbiBiZSBsaW5rZWQgdG8gSFBJLiBXaGVuIHRoZSBob3VzZSBwcmljZSBpcyBpbmNyZWFzaW5nLCB0aGUgbnVtYmVyIG9mIGhvdXNlIHNhbGVzIHRlbmRzIHRvIGJlIGhpZ2guIEhvd2V2ZXIsIHdoZW4gdGhlIGhvdXNlIHByaWNlIGlzIGZhdHRlbiwgdGhlIG51bWJlciBvZiBob3VzZSBzYWxlcyB0ZW5kcyB0byBiZSBsb3cuIE9uZSBjYW4gYWxzbyBleHBlY3QgdGhlIGludGVybmFsIGNvcnJlbGF0aW9uIGJldHdlZW4gc3VjY2Vzc2l2ZSBudW1iZXJzIGdpdmVuIHRoZSBmcmVxdWVudCBzaG9ydCB0ZXJtIG1vdmVtZW50cyBpbiB0aGlzIHRpbWUgc2VyaWVzIChpbmNyZWFzaW5nIG9yIGRlY3JlYXNpbmcgd2l0aGluIHNldmVyYWwgc3VjY2Vzc2l2ZSBxdWFydGVycykuCgpgYGB7cn0KaG91c2luZyB8PiBnZ3Bsb3QoYWVzKHg9ZGF0ZSx5PW0pKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGluZSgpICsKICB4bGFiKCdUaW1lJykgKyB5bGFiKCdUb3RhbCBWYWx1ZSBvZiBIb3VzaW5nIFN0b2NrJykgKyBnZ3RpdGxlKCdJcyBob3VzaW5nIG1hcmtldCBhbHdheXMgcHJvc3BlcmluZz8nKQpgYGAKCkFsbW9zdCBzYW1lIGZlYXR1cmVzIGFzIHdlIGNhbiBzZWUgZnJvbSBIUEkuIEp1c3Qgbm90aWNlIHRoYXQgdGhlIHRvdGFsIHZhbHVlIG9mIGhvdXNpbmcgc3RvY2sgY2FuIGJlIHJvdWdobHkgY2FsY3VsYXRlZCBieSB0aGUgYXZlcmFnZSBob3VzZSBwcmljZSB0aW1lcyB0aGUgdG90YWwgbnVtYmVyIG9mIGhvdXNlcy4gVGhlIG5ld2x5IGJ1aWx0IGhvdXNlcyBtYWtlIHRoZSBkZXByZXNzaW9uIGNhdXNlZCBieSB0aGUgQXNpYW4gRmluYW5jaWFsIGNyaXNpcyBpbiAxOTk3IGFuZCB0aGUgMjAxMSBDaHJpc3RjaHVyY2ggZWFydGhxdWFrZSBsZXNzIG9idmlvdXMuIAoKYGBge3J9CmhvdXNpbmcgfD4gZ2dwbG90KGFlcyh4PWRhdGUseT1yZWFsX20pKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGluZSgpICsKICB4bGFiKCdUaW1lJykgKyB5bGFiKCdSZXNpZGVudGlhbCBJbnZlc3RtZW50JykgKyBnZ3RpdGxlKCcKUmVzaWRlbnRpYWwgSW52ZXN0bWVudCBPcHBvcnR1bml0eSEnKQpgYGAKClRoZSByZXNpZGVudGlhbCBpbnZlc3RtZW50IGhhcyBhIHNpbWlsYXIgZ2VuZXJhbCBwYXR0ZXJuIGFzIHRoZSBudW1iZXIgb2YgaG91c2Ugc2FsZXMgYXMgaXQgY29udHJpYnV0ZXMgYSBjZXJ0YWluIHBvcnRpb24gdG8gdGhlIHdob2xlIGhvdXNpbmcgbWFya2V0IChhbm90aGVyIGltcG9ydGFudCBjb250cmlidXRvciBpcyB0aGUgZmlyc3QgaG9tZSBidXllci4pIEhvd2V2ZXIsIHRoZSBkZXRhaWxzIG9mIHJlc2lkZW50aWFsIGludmVzdG1lbnQgYXJlIGRpZmZlcmVudCBmcm9tIHRoZSBudW1iZXIgb2YgaG91c2Ugc2FsZXMuIFdlIHNlZSBtYW55IHppZ3phZyBzZWdtZW50cyBpbiB0aGlzIHRpbWUgc2VyaWVzIGVzcGVjaWFsbHkgaW4gdGhlIGZpcnN0IHR3byBkZWNhZGVzLiBUaGlzIG1heSByZWZsZWN0IHRoZSBzZWxmLXJlZ3VsYXRvcnkgcmF0aW9uYWxpdHkgb2YgdGhlIGludmVzdG9ycyB3aG8gbWF5IG5vdCBmb2xsb3cgdGhlIHRyZW5kIG9mIGFuIG92ZXItaGVhdGVkIGhvdXNpbmcgbWFya2V0LiAqT0ssIEkgaGF2ZSB0byBzYXkgdGhhdCBtYXliZSB0aGUgaG91c2luZyBtYXJrZXQgaW4gb3VyIGNvdW50cnkgYmVjb21lcyBhIGxpdHRsZSBjcmF6eSBpbiB0aGUgcmVjZW50IHllYXJzLiogKipUaGUgbGFzdCBzZW50ZW5jZSBpcyBwdXJlbHkgYSBwZXJzb25hbCBjb21tZW50LioqICAgCgoKIyMgRXhlcmNpc2UgMjogQ2FwdHVyaW5nIHRoZSBUcmVuZCBvZiBIb3VzZSBQcmljZQoKVGhlIHRpbWUgaW5kZXggJHQ9MSwyLDMsLi4uLFQkIGltcGxpZWQgYnkgdGhlIG5hdHVyYWwgdGVtcG9yYWwgb3JkZXJpbmcgaXMgYWx3YXlzIGluY3JlYXNpbmcuIEEgdmVyeSBzaW1wbGUgaWRlYSBpcyB0byBjb25zaWRlciB0aGUgcGFpcnMgJCgxLHlfMSkkLCAkKDIseV8yKSQsLi4uLCQoVCx5X1QpJCBqdXN0IGxpa2UgdGhlIHBhaXJzIG9mIG9ic2VydmF0aW9uICQoeF9pLHlfaSksaT0xLDIsLi4uLG4kIGluIHRoZSBsaW5lYXIgbW9kZWwuIAoKSWYgdGhlIHVuZGVybHlpbmcgdHJlbmQgb2YgYSB0aW1lIHNlcmllcyBzaG93cyBlaXRoZXIgaW5jcmVhc2luZyBvciBkZWNyZWFzaW5nIHBhdHRlcm4gY29uc2lzdGVudGx5LCBpdCBpcyBuYXR1cmFsIHRvIGxpbmsgaXQgd2l0aCB0aGUgdGltZSBpbmRleCBvZiBlYWNoIG9ic2VydmF0aW9uLiAqV2h5PyogCgoqKkFuc3dlcioqOiBUaGUgY29ycmVsYXRpb24gY29lZmZpY2llbnQgd2lsbCBjb25maXJtIHlvdXIgaW50dWl0aW9uLiAgCgpNb3Jlb3ZlciwgaWYgdGhlIHVuZGVybHlpbmcgdHJlbmQgbG9va3MgbGluZWFyLCBhIHNpbXBsZSBsaW5lYXIgbW9kZWwgbGlrZSBgeX50YCBiZWNvbWVzIGEgcGVyZmVjdCBjYW5kaWRhdGUgdG8gZGlzY292ZXIgdGhlIHVuZGVybHlpbmcgdHJlbmQgaW4gYSB0aW1lIHNlcmllcy4KCkFueXdheSwgbGV0J3MgdHJ5IGl0IG9uIEhQSSEgQmVmb3JlIGZpdHRpbmcgb3VyIGxpbmVhciBtb2RlbCwgaXQgaXMgbmVjZXNzYXJ5IHRvIGV4cGFuZCBvdXIgdGliYmxlIGJ5IGFkZGluZyB0aGUgdGltZSBpbmRpY2VzICQxLDIsMywuLi4kIGFzIGZvbGxvd3MuCgpgYGB7cn0KaG91c2luZy50cyA8LSBob3VzaW5nIHw+IG11dGF0ZSh0aW1lPTE6bigpKQpob3VzaW5nLnRzCmBgYAoKKldoeSB3ZSBkb24ndCB1c2UgYGRhdGVgIGltbWVkaWF0ZWx5PyoKCioqQW5zd2VyKio6IEFjdHVhbGx5LCB5b3UgY2FuIHVzZSBgZGF0ZWAuIEkgcHJlZmVyIHRoZSB0aW1lIGluZGV4IGFzIGl0IHByb3ZpZGVzIGEgY29uc2lzdGVudCB3YXkgdG8gYW5hbHlzZSBhbnkgdGltZSBzZXJpZXMuIEEgdHJpY2t5IGlzc3VlIG9mIHVzaW5nIGBkYXRlYCBpcyB0aGF0IHRoZSBpbnRlcmNlcHQgbWF5IG5vdCBiZSBpbnRlcnByZXRhYmxlLiBGb3IgdGhlIHRpbWUgaW5kZXgsIGl0IGlzIGVhc3kgdG8gdW5kZXJzdGFuZCB0aGF0IHRoZSBpbnRlcmNlcHQgaXMgdGhlIG9ic2VydmF0aW9uIGF0IHRpbWUgMCwgaS5lLiB0aGUgZGF5L21vbnRoL3F1YXJ0ZXIgcmlnaHQgYmVmb3JlIHRoZSBmaXJzdCBvbmUgaW4gb3VyIHRpbWUgc2VyaWVzLiBGb3IgYGRhdGVgLCBpbnRlcnByZXRpbmcgdGhlIGludGVyY2VwdCB3aWxsIGZvcmNlIHVzIHRvIGdvIGJhY2sgdG8gdGhlIGJpcnRoIG9mIEplc3VzLiAKCjEuIE5vdyB3ZSBjYW4gZml0IGEgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIGFzIGZvbGxvd3MKYGBge3J9CmhwaS5sbSA8LSBsbShpbmRleH50aW1lLGRhdGE9aG91c2luZy50cykKc3VtbWFyeShocGkubG0pCmBgYAoKKkNvbW1lbnQgb24gdGhlIFIgc3VtbWFyeSBvZiB0aGlzIG1vZGVsLioKCioqQW5zd2VyKio6ICBWZXJ5IGhpZ2ggJFJeMiQuIEJvdGggY29lZmZpY2llbnRzIGFyZSBoaWdobHkgc2lnbmlmaWNhbnQuIExvb2tzIGdvb2QhCgpXZSBjYW4gZnVydGhlciB2aXN1YWxpc2UgdGhlIGZpdHRlZCBsaW5lYXIgbW9kZWwgYXMgCmBgYHtyfQp2aXNyZWcoaHBpLmxtLCBnZz1UUlVFKSArIHhsYWIoJ1RpbWUgSW5kZXgnKSArIHlsYWIoJ0hvdXNlIFByaWNlIEluZGV4JykKYGBgCgoqQ29tbWVudCBvbiB0aGUgZ29vZG5lc3Mgb2YgZml0IG9mIHRoZSBsaW5lYXIgdHJlbmQgbW9kZWwgYmFzZWQgb24gdGhlIHZpc3VhbGlzYXRpb24uKgoKKipBbnN3ZXIqKjogRXZlbiB3aXRoIGEgdmVyeSBoaWdoICRSXjIkLCB0aGUgcGxvdCBtYWtlIG1lIGZlZWwgd29ycmllZC4gQSBzdHJhaWdodCBsaW5lIGNhbiBub3QgY2FwdHVyZSB0aGUgY29tcGxpY2F0ZWQgY3ljbGVzIGluIHRoZSBob3VzZSBtYXJrZXQuCgoqUGVyZm9ybSBhIHN0YW5kYXJkIHJlc2lkdWFsIGRpYWdub3N0aWNzIGZvciB5b3VyIGZpdHRlZCBsaW5lYXIgbW9kZWwgYnkgZm9sbG93aW5nIHRoZSBzdGVwcyBpbiBMYWIgQzMuKgoKYGBge3J9CnBsb3QoaHBpLmxtKQpgYGAKCioqQW5zd2VyKio6IFRoZSByZXNpZHVhbHMgZGlhZ25vc3RpY3MgY29uZmlybSBvdXIgY29uY2VybnMgb24gdGhlIHZpc3VhbGlzYXRpb24uIE5vbmUgb2YgdGhlIHJlc2lkdWFscyBwbG90cyBsb29rcyBnb29kLiBTdHJhbmdlIHBhdHRlcm5zIGluIGFsbCBwbG90cyBzdWdnZXN0IHRoYXQgdGhlcmUgaXMgYSBsb3Qgb2YgaW5mb3JtYXRpb24gaGlkZGVuIGluIHRoZSByZXNpZHVhbHMuIAoKKipPcHRpb25hbCBDaGFsbGVuZ2U6IE1ha2UgYSBsb2cgdHJhbnNmb3JtYXRpb24gb24gSFBJIGFuZCBmaXQgYW5vdGhlciBsaW5lYXIgbW9kZWwuIENvbXBhcmUgdGhlIFIgc3VtbWFyeSB3aXRoIHRoZSBtb2RlbCB3aXRob3V0IHRyYW5zZm9ybWF0aW9uLiBQZXJmb3JtIHRoZSBzdGFuZGFyZCByZXNpZHVhbCBkaWFnbm9zdGljcyBvbiB0aGlzIG1vZGVsLioqIAoKYGBge3J9CmhwaS5sbS5sb2cgPC0gbG0obG9nKGluZGV4KX50aW1lLGRhdGE9aG91c2luZy50cykKc3VtbWFyeShocGkubG0ubG9nKQp2aXNyZWcoaHBpLmxtLmxvZyx0cmFucyA9IGV4cCxwYXJ0aWFsPVRSVUUpCnBsb3QoaHBpLmxtLmxvZykKYGBgCgoqKkFuc3dlcioqOiAkUl4yJCBnZXRzIGV2ZW4gaGlnaGVyISBUaGUgdmlzdWFsaXNhdGlvbiBsb29rcyBzbGlnaHRseSBiZXR0ZXIuIFRoZSByZXNpZHVhbHMgcGxvdHMgYXJlIHN0aWxsIHdvcnJ5aW5nIGV4Y2VwdCBmb3IgdGhlIFEtUSBwbG90LiAKCjIuIEEgZ3JvdXAgb2YgdGFsZW50ZWQgc2Nob29sIHN0dWRlbnRzIHN0dWRpZWQgSFBJIGJlZm9yZSB0aGV5IGxlYXJuZWQgdGhlIGxvZyB0cmFuc2Zvcm1hdGlvbi4gVGhleSBub3RpY2VkIHRoYXQgSFBJIHdhcyBpbmNyZWFzaW5nIHNsb3dseSBhdCB0aGUgYmVnaW5uaW5nIGJ1dCBpbmNyZWFzaW5nIGZhc3RlciBsYXRlci4gSW4gb3RoZXIgd29yZHMsIHRoZSBpbmNyZWFzZSBpbiBIUEkgb2NjdXJzIGF0IGFuIGluY3JlYXNpbmcgcmF0ZSBmb3IgZWFjaCBzdWNjZXNzaXZlIHRpbWUgcGVyaW9kLiBUaGUgc3R1ZGVudHMgc3VnZ2VzdGVkIHRoYXQgd2UgbWF5IHVzZSBhICoqcXVhZHJhdGljKiogZnVuY3Rpb24gaW5zdGVhZCBvZiB0aGUgbGluZWFyIGZ1bmN0aW9uIHRvIG1vZGVsIHRoZSB0cmVuZCBhcyBmb2xsb3dzLgpcWwptZWFuKHlfdCk9YStidCtjdF4yLgpcXQpCdXQgdGhlIHN0dWRlbnRzIGRpZG4ndCBrbm93IGhvdyB0byBmaW5kIHRoZSBjb2VmZmljaWVudHMgJGEsYixjJC4gQ2FuIHdlIGhlbHA/CgpUaGUgYW5zd2VyIGlzIFlFUyEgSW50ZXJlc3RpbmdseSwgdGhpcyBjYW4gYmUgZG9uZSB2aWEgYGxtKClgIGVhc2lseSBhcyBmb2xsb3dzLgoKYGBge3J9CmhwaS5xbSA8LSBsbShpbmRleH50aW1lK0kodGltZV4yKSwgZGF0YT1ob3VzaW5nLnRzKQpzdW1tYXJ5KGhwaS5xbSkKYGBgCgoqKkl0IGlzIGltcG9ydGFudCB0byB1c2UgYEkodGltZV4yKWAsIG5vdCBgdGltZV4yYCBpZiB5b3Ugd2FudCB0byBmaXQgYSBxdWFkcmF0aWMgdHJlbmQgdG8gYSB0aW1lIHNlcmllcy4qKgoKV2UgY2FuIGZpbmQgYW4gYWRkaXRpb25hbCByb3cgaW4gYENvZWZmaWNpZW50czpgIGFzIGBJKHRpbWVeMilgLiBUaGVyZSBpcyB0aGUgZXN0aW1hdGUgZm9yICRjJCBhcyB5b3UgbWF5IGV4cGVjdGVkLiBUaGUgaW50ZXJwcmV0YXRpb24gb2YgcmVzdCBpcyBqdXN0IGxpa2Ugd2hhdCB3ZSBoYXZlIGRvbmUgaW4gTGFiIEMyLiBPbmUgaW50ZXJlc3RpbmcgdGhpbmcgaXMgdGhhdCB0aGUgZXN0aW1hdGUgZm9yICRiJCBiZWNvbWVzIGluc2lnbmlmaWNhbnQhIAoKKkNvbXBhcmUgdGhlIFIgc3VtbWFyeSB3aXRoIHRoYXQgaW4gU3RlcCAxLiBEaXNjdXNzIHlvdXIgZmluZGluZ3MuKgoKKipBbnN3ZXIqKjogJFJeMiQgZ2V0cyBoaWdoZXIganVzdCBsaWtlIHRoZSB0cmFuc2Zvcm1lZCBtb2RlbC4gVGhlIHF1YWRyYXRpYyB0ZXJtIGlzIGhpZ2hseSBzaWduaWZpY2FudCBidXQgdGhlIGxpbmVhciB0ZXJtIGJlY29tZXMgaW5zaWduaWZpY2FudC4gCgpWaXN1YWxpc2luZyB0aGlzIG1vZGVsIGNhbiBiZSBkb25lIHdpdGggYHZpc3JlZygpYCBpbiB0aGUgdXN1YWwgd2F5LCBidXQgbGV0J3MgdXNlIHRoZSBgYnJvb21gIGxpYnJhcnkgaW5zdGVhZCB0byBkbyBpdCBoZXJlOgoKYGBge3J9CmF1Z21lbnQoaHBpLnFtKSB8PiBnZ3Bsb3QoYWVzKHggPSB0aW1lICx5PWluZGV4KSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoKSArCiAgICAgICAgZ2VvbV9saW5lKGFlcyh5PS5maXR0ZWQpLGNvbD0ncmVkJykgKyB4bGFiKCdUaW1lIEluZGV4JykgKyB5bGFiKCdIb3VzZSBQcmljZSBJbmRleCcpCmBgYAoKWW91IG1heSBhZGQgdGhlIGNvbmZpZGVuY2UgYmFuZCBvciBwcmVkaWN0aW9uIGJhbmQgYnkgZm9sbG93aW5nIEV4ZXJjaXNlIDEgaW4gTGFiIEMzLiAKClNvbWV0aW1lcyBwZW9wbGUgcHJlZmVyIG1ha2luZyBhIHBsb3QgaW4gdGhlIGNhbGVuZGFyIHRpbWUgcmF0aGVyIHRoYW4gaW4gdGhlIHRpbWUgaW5kZXguIFRoaXMgY2FuIGJlIGRvbmUgZWFzaWx5IGJ5IHVzaW5nIGBicm9vbWAgYXMgZm9sbG93cy4gTm90aWNlIHRoYXQgd2UgYWRkIHRoZSBvcmlnaW5hbCBkYXRhIHNldCBgaG91c2luZy50c2AgaW50byBgYXVnbWVudCgpYCBhbmQgY2hhbmdlIGB4ID0gdGltZWAgdG8gYHggPSBkYXRlYCBpbiBgZ2dwbG90KClgLiBUaGlzIGlzIHZlcnkgaGFuZHkgaW4gbWFueSBjYXNlcyBlc3BlY2lhbGx5IHlvdSBhcmUgZml0dGluZyBhIGxpbmVhciBtb2RlbCB3aXRoIHRyYW5zZm9ybWF0aW9uLiAKCmBgYHtyfQphdWdtZW50KGhwaS5xbSxob3VzaW5nLnRzKSB8PiBnZ3Bsb3QoYWVzKHggPSBkYXRlICx5PWluZGV4KSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoKSArCiAgICAgICAgZ2VvbV9saW5lKGFlcyh5PS5maXR0ZWQpLGNvbD0ncmVkJykgKyB4bGFiKCdUaW1lJykgKyB5bGFiKCdIb3VzZSBQcmljZSBJbmRleCcpCmBgYAoKKkNvbW1lbnQgb24gdGhlIGdvb2RuZXNzIG9mIGZpdCBvZiB0aGUgcXVhZHJhdGljIHRyZW5kIG1vZGVsIGJhc2VkIG9uIHRoZSB2aXN1YWxpc2F0aW9uLioKCioqQW5zd2VyKio6IEl0IGlzIHNsaWdodGx5IGJldHRlciB0aGFuIHRoZSBsaW5lYXIgbW9kZWwuIEJ1dCB0aGUgZ2VuZXJhbCBwYXR0ZXJuIGluIEhQSSB0aW1lIHNlcmllcyBzdGlsbCBkZXZpYXRlIGZyb20gdGhlIGN1cnZlIGZyZXF1ZW50bHkuIAoKMy4gTm93IHdlIGhhdmUgdHdvIGNhbmRpZGF0ZSBtb2RlbHMgZnJvbSBTdGVwIDEgYW5kIDIgZm9yIEhQSSB0aW1lIHNlcmllcy4gCkEgbmF0dXJhbCBxdWVzdGlvbiBhcmlzZXM6IFdoaWNoIG1vZGVsIGlzIGJldHRlcj8gCgpCb3RoIFIgc3VtbWFyeSBhbmQgdmlzdWFsaXNhdGlvbiBjYW4gYmUgdXNlZC4gVGhlIHJlc2lkdWFsIGFuYWx5c2lzIGlzIGFub3RoZXIgdG9vbCB0byBmaW5kIHRoZSBiZXR0ZXIgbW9kZWwuIFdoaWxlIHRoZXNlIHRvb2xzIGFyZSB1c2VmdWwsIHdlIGhhdmUgYSBtb3JlIGRlbGljYXRlIHdheSB0byBjb21wYXJlIHRoZSBhYm92ZSB0d28gbW9kZWxzIHBlcmZvcm1hbmNlLiBQYXJ0aWN1bGFybHksIHRoZSBsaW5lYXIgbW9kZWwgY2FuIGJlIHJlZ2FyZGVkIGFzIGEgc3BlY2lhbCBjYXNlIG9mIHRoZSBxdWFkcmF0aWMgbW9kZWwuIElmIHdlIHNldCAkYz0wJCwgdGhlIHF1YWRyYXRpYyBtb2RlbCByZWR1Y2VzIHRvIHRoZSBsaW5lYXIgbW9kZWwuIElmIHR3byBtb2RlbHMgY2FuIGJlIGxpbmtlZCBpbiBzdWNoIG1hbm5lciwgd2UgY2FsbCB0aGVzZSB0d28gbW9kZWxzIGFyZSAqKm5lc3RlZCoqLiBUaGUgc2ltcGxlIG1vZGVsIGlzIHVzdWFsbHkgY2FsbGVkIGEgKipyZWR1Y2VkIG1vZGVsKiogKGxpbmVhciBvbmUgaW4gdGhpcyBjYXNlKSBhbmQgdGhlIGNvbXBsaWNhdGVkIG1vZGVsIGlzIHVzdWFsbHkgY2FsbGVkIGFuICoqZXh0ZW5kZWQgbW9kZWwqKiAocXVhZHJhdGljIG9uZSBpbiB0aGlzIGNhc2UpLgoKQW4gKipBTk9WQSAoQU5hbHlzaXMgT2YgVkFyaWFuY2UpIHRlc3QqKiBjYW4gYmUgdXNlZCB0byBjb21wYXJlIHR3byBuZXN0ZWQgY2FuZGlkYXRlIG1vZGVscyBhcwpgYGB7cn0KYW5vdmEoaHBpLmxtLCBocGkucW0pCmBgYAoKYGFub3ZhKClgIHRlc3RzIGlmIHRoZSBhZGRpdGlvbmFsIHBhcmFtZXRlcihzKSAkYyQgaW4gdGhlIHF1YWRyYXRpYyBtb2RlbCBpcyB6ZXJvIG9yIG5vdCwgaS5lLiB0aGUgbnVsbCBoeXBvdGhlc2lzIGlzICRjPTAkIGFuZCB0aGUgcC12YWx1ZSBpcyByZXBvcnRlZCBieSBgUHIoPkYpYC4gKldoYXQgZG9lcyB0aGUgb3V0cHV0IG9mIGBhbm92YSgpYCBzdWdnZXN0PyogSWYgdGhlIFAtdmFsdWUgaXMgc21hbGwgd2UgY2FuIGNvbmNsdWRlIHRoYXQgdGhhdCBgTW9kZWwgMmAgLSB0aGUgcXVhZHJhdGljIHRyZW5kIG1vZGVsIGlzIGJldHRlci4gCgpJdCBpcyBlYXN5IHRvIGZpbmQgdGhlIGltcHJvdmVtZW50IGluIGdvb2RuZXNzIG9mIGZpdCBmcm9tIHRoZSBxdWFkcmF0aWMgbW9kZWwgaXMgc2lnbmlmaWNhbnQgZm9yIEhQSSB0aW1lIHNlcmllcy4gSW4gZmFjdCwgd2l0aCB0aGUgYWRkaXRpb25hbCBjb2VmZmljaWVudCAkYyQsIHRoZSBxdWFkcmF0aWMgdHJlbmQgbW9kZWwgKGFuZCBhbnkgZXh0ZW5kZWQgbW9kZWwpIGlzIGd1YXJhbnRlZWQgdG8gZ2V0IGNsb3NlciB0byB0aGUgcmVhbCB0aW1lIHNlcmllcyB0aGFuIHRoZSBsaW5lYXIgdHJlbmQgbW9kZWwoYW5kIGFueSByZWR1Y2VkIG1vZGVsKS4gCgoqQSBwdXJlIHF1YWRyYXRpYyBtb2RlbCBhcyAkeT1hK2N4XjIkIGlzIGFsc28gbmVzdGVkIHdpdGggdGhlIHF1YWRyYXRpYyBtb2RlbCAkeT1hK2J4K2N4XjIkLiBXaHk/IFVzZSBBTk9WQSB0byB0ZWxsIHdoaWNoIG1vZGVsIGlzIGJldHRlci4qCgoqKkFuc3dlcioqOiAqTWFrZSBzdXJlIHRoYXQgeW91IHN0aWxsIGhhdmUgYEkoKWAgaW4geW91ciBwdXJlIHF1YWRyYXRpYyBtb2RlbC4qIGBhbm92YSgpYCB0ZWxscyB1cyB0aGF0IHRoZSBsaW5lYXIgdGVybSBjYW4gYmUgZXhjbHVkZWQgZm9yIGEgbW9yZSBjb25jaXNlIG1vZGVsLiAKCmBgYHtyfQpocGkucHFtIDwtIGxtKGluZGV4fkkodGltZV4yKSwgZGF0YT1ob3VzaW5nLnRzKQphbm92YShocGkucHFtLCBocGkucW0pCmBgYAoKCkhvd2V2ZXIsIHRoZSBxdWFkcmF0aWMgdHJlbmQgbW9kZWwgaXMgbW9yZSBjb21wbGljYXRlZCB0aGFuIHRoZSBsaW5lYXIgdHJlbmQgbW9kZWwgYW5kIHRoZSBwdXJlIHF1YWRyYXRpYyBtb2RlbCBhcyBpdCBoYXMgb25lIG1vcmUgY29lZmZpY2llbnQuIElmIHRoZSBpbXByb3ZlbWVudCBpcyBqdXN0IG1hcmdpbmFsLCB3ZSBtYXkgcHJlZmVyIHRoZSBsaW5lYXIgbW9kZWwgb3IgdGhlIHB1cmUgcXVhZHJhdGljIG1vZGVsIGFzIHRoZXkgYXJlIHNvIGNvbmNpc2UuIFRoZSBBTk9WQSB0ZXN0IGV4YW1pbmVzIGlmIHRoZSBleHRlbmRlZCBtb2RlbCAocXVhZHJhdGljIHRyZW5kIG1vZGVsICkgZXhwbGFpbnMgc3VmZmljaWVudCB2YXJpYWJpbGl0eSBpbiAkeSQgY29tcGFyZWQgdG8gdGhlIHJlZHVjZWQgbW9kZWwgKGxpbmVhciB0cmVuZCBtb2RlbCkgYXQgYSBwcmljZSBvZiBhZGRpbmcgb25lIG1vcmUgcGFyYW1ldGVyLiBXZSB3aWxsIHJldmlzaXQgdGhpcyB0b3BpYyBpbiBMYWIgQzYuCgo0LiAqUHJlZGljdCBIUEkgZm9yIFEzIGFuZCBRNCBvZiAyMDIwIHdpdGggYm90aCBsaW5lYXIgdHJlbmQgbW9kZWwgYW5kIHF1YWRyYXRpYyB0cmVuZCBtb2RlbCB3aXRoIDk1JSBwcmVkaWN0aW9uIGludGVydmFscy4gQ29tbWVudCBvbiB5b3VyIHByZWRpY3Rpb25zLioKCioqQW5zd2VyKio6IE9uZSBoYXMgdG8gZmlndXJlIG91dCB0aGF0IHRoZSB0aW1lIGluZGljZXMgZm9yIFEzIGFuZCBRNCBvZiAyMDIwIGFyZSAxMjMgYW5kIDEyNC4gCgpgYGB7cn0KbmV3dGltZSA8LSBkYXRhLmZyYW1lKHRpbWU9YygxMjMsMTI0KSkKcHJlZGljdChocGkubG0sbmV3ZGF0YT1uZXd0aW1lKQpwcmVkaWN0KGhwaS5xbSxuZXdkYXRhPW5ld3RpbWUpCmBgYAoKVGhlIGxpbmVhciB0cmVuZCBtb2RlbCBpcyBjZXJ0YWlubHkgdW5kZXJlc3RpbWF0aW5nIHRoZSBIUEkuIFRoZSBxdWFkcmF0aWMgb25lIG1heSBkbyBhIGJldHRlciBqb2IuIEhvd2V2ZXIsIGJvdGggdHdvIG1vZGVscyBhcmUgdW5hYmxlIHRvIHF1YW50aWZ5IHRoZSB1bmNlcnRhaW50aWVzIGluIGEgcmlnaHQgd2F5IGR1ZSB0aGUgaGlnaGx5IGlsbC1wb3NlZCByZXNpZHVhbHMuCgoKIyMgRXhlcmNpc2UgMzogQWRkcmVzc2luZyBTZWFzb25hbGl0aWVzIGluIENvbnN1bXB0aW9uCgpCZXNpZGVzIHRoZSB0cmVuZCBleHRyYWN0ZWQgYnkgb3VyIGxpbmVhciBtb2RlbHMsIGFub3RoZXIgaW1wb3J0YW50IGZlYXR1cmUgb2YgdGltZSBzZXJpZXMgaXMgdGhlICoqc2Vhc29uYWxpdHkqKiBvciAqKnNlYXNvbmFsIGVmZmVjdHMqKi4gTWFueSB0aGluZ3MgZXhoaWJpdCBzZWFzb25hbCBwYXR0ZXJucyBvdmVyIGRpZmZlcmVudCB0aW1lIHBlcmlvZHMgKGUuZy4gbW9udGhzIG9yIHF1YXJ0ZXJzKSBvZiBhIHllYXIuIEZvciBleGFtcGxlOgogICAgCiAgLSBTYWxlcyBvZiBza2kgZXF1aXBtZW50IGFyZSB2ZXJ5IGhpZ2ggZHVyaW5nIHdpbnRlciBhbmQgYWxtb3N0IG5vbmV4aXN0ZW50IGR1cmluZyBvdGhlciBzZWFzb25zLgoKICAtIFNhbGVzIGF0IGRlcGFydG1lbnQgc3RvcmVzIGFyb3VuZCBDaHJpc3RtYXMgaG9saWRheXMgYXJlIG11Y2ggaGlnaGVyIHRoYW4gZHVyaW5nIG90aGVyIHRpbWVzIG9mIHRoZSB5ZWFyLgoKICAtIFBvd2VyIGJpbGxzIGFyZSBleHBlY3RlZCB0byBiZSBoaWdoZXIgZHVyaW5nIHdpbnRlciB0aGFuIGluIG90aGVyIHNlYXNvbnMuIAoKKkVudW1lcmF0ZSBhIGZldyBtb3JlIHRpbWUgc2VyaWVzIHdpdGggYSBzZWFzb25hbCBwYXR0ZXJuLioKCioqQW5zd2VyKio6IGVsZWN0cmljaXR5IHByb2R1Y3Rpb24gcGVyIG1vbnRoL3dlZWsvZGF5LCBzYWxlcyBvZiBpY2UgY3JlYW1zLCBldGMuCgoKQ2xlYXJseSwgc2Vhc29uYWxpdHkgaXMgY2xvc2VseSByZWxhdGVkIHRvIHRoZSBmcmVxdWVuY3kgYW5kIHRoZWlyIGxpbmthZ2VzIGNhbiBiZSBleHBlY3RlZCBqdXN0IGJ5IHJlYWRpbmcgdGhlIHdvcmRzICdtb250aGx5JywgJ3dlZWtseScsIG9yICdxdWFydGVybHknLiBJbiBmYWN0LCBpbiBhIHRpbWUgc2VyaWVzIHRoZSBzZWFzb25hbCBwYXR0ZXJuIGZvciBhIHBhcnRpY3VsYXIgcXVhcnRlciBvciBtb250aCBpcyBleGFjdGx5IHJlcGVhdGVkIGJ5IHRoZSBmcmVxdWVuY3kgb2YgdGhlIHRpbWUgc2VyaWVzLiAKCk9idmlvdXNseSwgaXQgaXMgbmVjZXNzYXJ5IHRvIGluY2x1ZGUgdGhlIHNlYXNvbmFsIGVmZmVjdHMgaW4gdGltZSBzZXJpZXMgbW9kZWxsaW5nIGluIG1vc3QgY2FzZXMuIE90aGVyd2lzZSwgdGhlIHByZWRpY3Rpb24gYmVjb21lcyByYXRoZXIgc3VwZXJmaWNpYWwgYXMgaXQgb25seSBhY2NvdW50cyBmb3IgdGhlIGxvbmctdGVybSB0cmVuZC4gCgpIb3cgY2FuIHdlIG1ha2UgdXNlIG9mIHRoZSBzZWFzb25hbCBlZmZlY3RzPyBXZSB3aWxsIHN0YXJ0IGJ5IGV4cGxvcmluZyB0aGUgZGF0YSBzZXQgYE0yIENvbnN1bXB0aW9uYCBmcm9tIFJCTlouCgoxLiBGaXJzdCBvZiBhbGwsIHdlIGxvYWQgYGNvbnN1bXB0aW9uYCBkYXRhIGZyb20gdGhlIFJCTlogd2Vic2l0ZSAtIHNlZSAoaHR0cHM6Ly93d3cucmJuei5nb3Z0Lm56L3N0YXRpc3RpY3MvbTIpIGZvciBtb3JlIGluZm9ybWF0aW9uIG9uIHRoaXMgdGltZSBzZXJpZXMuIFdlIHRoZW4gc3Vic2V0IHRoZSBkYXRhLCBmaWx0ZXJpbmcgb3V0IHRoZSAyMDIwIGRhdGEgYW5kIGJlZm9yZS4gV2UnbGwgcmVzZXJ2ZSAyMDIwIGFuZCAyMDIxIGZvciBwcmVkaWN0aW9uLgoKYGBge3J9CmNvbnN1bXB0aW9uIDwtIHJlYWRfY3N2KCJodHRwczovL3d3dy5tYXNzZXkuYWMubnovfmpjbWFyc2hhLzE2MTEyMi9kYXRhL2NvbnN1bXB0aW9uLmNzdiIpCnJldGFpbC50cmFkZS5zYWxlcyA8LSBjb25zdW1wdGlvbiB8PgogIGZpbHRlcihkYXRlIDwgeW1kKCcyMDIwLTAxLTAxJykpCnJldGFpbC50cmFkZS5zYWxlcyB8PgogIGdncGxvdChhZXMoeD1kYXRlLHk9eF9tKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoKSArIAogIHhsYWIoJ1RpbWUnKSArIHlsYWIoJ1JldGFpbCBUcmFkZSBTYWxlcycpCmBgYAoKKlNwZWNpZnkgdGhlIGZyZXF1ZW5jeSBvZiB0aGlzIHRpbWUgc2VyaWVzLioKCioqQW5zd2VyKio6IDQKCldlIGNhbiBjbGVhcmx5IHNlZSBhbiBpbmNyZWFzaW5nIHRyZW5kIG92ZXIgdGhlc2UgeWVhcnMgaW4gZ2VuZXJhbC4gVGhlIHJlZ3VsYXIgZmx1Y3R1YXRpb25zIG9mIHNhbGVzIHdpdGhpbiBvbmUgeWVhciBzdWdnZXN0IHN0cm9uZyBzZWFzb25hbCBlZmZlY3RzLiAqV2h5PyogT25lIHNoYWxsIGFsc28gbm90aWNlIHRoYXQgdGhlIHNlYXNvbmFsIHZhcmlhYmlsaXR5IGlzIGluY3JlYXNpbmcgb3ZlciB0aW1lLiAgCgoyLiBGaXJzdCBvZiBhbGwsIHdlIGNhbiBleHRyYWN0IGEgbGluZWFyIHRyZW5kIGZyb20gdGhpcyB0aW1lIHNlcmllcyBhcyBmb2xsb3dzCgpgYGB7cn0KcnRzLnRzIDwtIHJldGFpbC50cmFkZS5zYWxlcyB8PiBtdXRhdGUodGltZT0xOm4oKSkKcnRzLmxtIDwtIGxtKHhfbX50aW1lLGRhdGE9cnRzLnRzKQpzdW1tYXJ5KHJ0cy5sbSkKYGBgCgpUaGUgUiBvdXRwdXQgcmVhZHMgcHJldHR5IGdvb2Qgd2l0aCBhbGwgY29lZmZpY2llbnQgc2lnbmlmaWNhbnQgYW5kIGEgdmVyeSBoaWdoICRSXjI9MC45NDU3JCEgCgpMZXQncyB2aXN1YWxpc2UgdGhlIG1vZGVsIGFzIGZvbGxvd3M6CmBgYHtyfQp2aXNyZWcocnRzLmxtLCBnZz1UUlVFKSArIHhsYWIoJ1RpbWUgSW5kZXgnKSArIHlsYWIoJ1JldGFpbCBUcmFkZSBTYWxlcycpCmBgYAoKT29wcy4gQSBzaW1wbGUgbGluZSB3b24ndCBiZSBhYmxlIHRvIGNhcHR1cmUgdGhlIHNlYXNvbmFsaXR5LgoKMy4gTGV0J3MgcGVyZm9ybSBhIHN0YW5kYXJkIHJlc2lkdWFsIGRpYWdub3N0aWNzIG9uIHRoZSBmaXR0ZWQgbW9kZWwuCgpgYGB7cn0KcGxvdChydHMubG0pCmBgYAoKRXhjZXB0IGZvciBRLVEgcGxvdCwgcmVzdCBkaWFnbm9zdGljIHBsb3RzIGxvb2sgcmVhbGx5IHJlYWxseSB3ZWlyZCEgKkFkZCBhIGZldyBkZXRhaWxlZCBjb21tZW50cyBvbiBlYWNoIHBsb3QuKgoKSW4gYWRkaXRpb24gdG8gdGhlIGFib3ZlIHN0YW5kYXJkIGRpYWdub3N0aWMgcGxvdHMsIHRoZSAqKnJlc2lkdWFscyB2ZXJzdXMgdGltZSoqIHBsb3QgaXMgZnJlcXVlbnRseSB1c2VkIGluIHRoZSByZXNpZHVhbCBkaWFnbm9zdGljcyBvZiB0aW1lIHNlcmllcyBtb2RlbGxpbmcgYXMKCmBgYHtyfQphdWdtZW50KHJ0cy5sbSkgfD4KICBnZ3Bsb3QoYWVzKHg9dGltZSx5PS5yZXNpZCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKCkgKwogIGdndGl0bGUoJ1Jlc2lkdWFscyB2ZXJzdXMgVGltZSBJbmRleCcpICsgeGxhYignVGltZSBJbmRleCcpCmBgYAoKVGhlIHBhdHRlcm4gaW4gdGhlICoqcmVzaWR1YWxzIHZlcnN1cyB0aW1lKiogcGxvdCBzZWVtcyBjb250YWluaW5nIHRoZSBpbmZvcm1hdGlvbiBvbiB0aGUgc2Vhc29uYWxpdHkhIFN1Y2ggcGF0dGVybiBhY3R1YWxseSBzdWdnZXN0cyB0aGF0IHRoZSByZXNpZHVhbHMgdmlvbGF0ZSB0aGUgaS5pLmQuIGNvbmRpdGlvbiwgaS5lLiB0aGUgaW5kZXBlbmRlbnRseSBhbmQgaWRlbnRpY2FsbHkgZGlzdHJpYnV0ZWQgY29uZGl0aW9uLiBXZSBjYW4gY29uamVjdHVyZSB0aGF0IHRoZSByZXNpZHVhbHMgaW4gZGlmZmVyZW50IHNlYXNvbnMgaXMgY29taW5nIGZyb20gZGlmZmVyZW50IGRpc3RyaWJ1dGlvbnMgYW5kIHRoZXkgYXJlIGNvcnJlbGF0ZWQgaW4gdGhlIHRpbWUgb3JkZXIuICpIb3cgY2FuIHdlIGNoZWNrIHRoZSBpbnRlcm5hbCBjb3JyZWxhdGlvbiBpbiByZXNpZHVhbHM/KgoKKipBbnN3ZXIqKjogVGhpcyBvbmUgaXMgbm90IHN0cmFpZ2h0Zm9yd2FyZC4gVG8gZGlzY292ZXIgdGhlIGludGVybmFsIGNvcnJlbGF0aW9uIGluIGEgdGltZSBzZXJpZXMsIHdlIG5lZWQgYSBiaXQgbW9yZSBiYWNrZ3JvdW5kIGtub3dsZWRnZS4gTmV2ZXJ0aGVsZXNzLCB3ZSBtYXkgY29uamVjdHVyZSB0aGF0IHRoZXJlIGlzIGEgb25lLXN0ZXAgY29ycmVsYXRpb24gaW4gYSB0aW1lIHNlcmllcywgc2F5ICR4XzEkIGlzIGNvcnJlbGF0ZWQgd2l0aCAkeF8yJCwgICR4XzIkIGlzIGNvcnJlbGF0ZWQgd2l0aCAkeF8zJCwuLi4sJHhfe3QtMX0kIGlzIGNvcnJlbGF0ZWQgd2l0aCAkeF90JC4gTGlrZSAkKHhfaSx5X2kpJCBpcyBwYWlyZWQgZm9yIGNvbXB1dGluZyB0aGUgY29ycmVsYXRpb24gY29lZmZpY2llbnQsIHdlIGhhdmUgdGhlIHBhaXJzICQoeF8xLHhfMikkLCAkKHhfMix4XzMpJCwuLi4sJHhfe3QtMX0seF90JCBhbmQgd2UgY2FuIGNvbXB1dGUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gJFx7eF90XH0kIGFuZCAkXHt4X3t0KzF9XH0kLiBTdWNoIGEgY29ycmVsYXRpb24gY29lZmZpY2llbnQgd2l0aGluIGEgdGltZSBzZXJpZXMgaXMgY2FsbGVkICoqYXV0b2NvcnJlbGF0aW9uKiouIFdlIGNhbiBmdXJ0aGVyIGV4YW1pbmUgdGhlIGhpZ2hlciBvcmRlciAobXVsdGktc3RlcCkgYXV0b2NvcnJlbGF0aW9uLiBUaGVzZSBpc3N1ZXMgd2lsbCBiZSBleHBsb3JlZCBpbiB0aGUgbGFzdCB0d28gbGVjdHVyZXMuCgpgYGB7cn0Kc2V0LnNlZWQoMjAyMCkKdGliYmxlKGU9cm5vcm0oMTAwKSkgfD4gc3VtbWFyaXNlKGFjZjE9Y29yKGVbLTFdLGVbLWxlbmd0aChlKV0pKQpgYGAKCldlIGZ1cnRoZXIgbWFrZSBhIGJveCBwbG90IHRvIGNvbXBhcmUgdGhlIHJlc2lkdWFscyBvdmVyIGRpZmZlcmVudCBxdWFydGVycy4KSGVyZSB3ZSBhZGQgdGhlIGRhdGEgc2V0IGBydHMudHNgIHRvIGBhdWdtZW50KClgIHRvIG1ha2Ugc3VyZSB0aGF0IHdlIGhhdmUgdGhlIGFjY2VzcyB0byB0aGUgY2FsZW5kYXIgdGltZSBhbmQgd2UgY2FuIGdldCB0aGUgY29ycmVzcG9uZGluZyBxdWFydGVyIGJ5IGBxdWFydGVyKClgIGZyb20gYGx1YnJpZGF0ZSgpYC4gCgpgYGB7cn0KYXVnbWVudChydHMubG0sIHJ0cy50cykgfD4gbXV0YXRlKHF1YXJ0ZXI9cXVhcnRlcihkYXRlKSkgfD4KICBnZ3Bsb3QoYWVzKHk9LnJlc2lkLHg9cXVhcnRlcikpICsgZ2VvbV9ib3hwbG90KGFlcyhncm91cD1xdWFydGVyKSkKYGBgCgpFYWNoIGJveCBpbiB0aGUgYWJvdmUgYm94cGxvdCBjaGFyYWN0ZXJpc2VzIHRoZSByZXNpZHVhbHMsIGkuZS4gdGhlIGRldmlhdGlvbnMgZnJvbSB0aGUgdHJlbmQgbGluZSwgYXQgYSBzcGVjaWZpYyBxdWFydGVyLiBUaGUgcmVzaWR1YWxzIGhlcmUgYXJlIGEgY29tYmluYXRpb24gb2YgdHJ1ZSByYW5kb20gZXJyb3JzIGFuZCBzZWFzb25hbCBlZmZlY3RzLiAKCkhvdyBjYW4gd2UgZXh0cmFjdCB0aGUgaW5mb3JtYXRpb24gb24gc2Vhc29uYWxpdHk/IEEgc2ltcGxlIGlkZWEgaXMgdG8gZXN0aW1hdGUgdGhlIHNlYXNvbmFsIGVmZmVjdCBhdCBhIHBhcnRpY3VsYXIgcXVhcnRlciBieSB0aGUgbWVhbiBvZiByZXNpZHVhbHMgYXQgdGhpcyBxdWFydGVyLiBUaGlzIHdpbGwgYWRkIGEgY29uc3RhbnQgc2hpZnQgdG8gdGhlIHRyZW5kIGxpbmUgZm9yIGVhY2ggcXVhcnRlci4gVGhlIHJlc3QgZGV2aWF0aW9ucyB3aWxsIGJlIHJlZ2FyZGVkIGFzIHRoZSBmaW5hbCByZXNpZHVhbHMuIAoKKlRyeSB0byBjb21wdXRlIHRoZSByZXNpZHVhbHMgbWVhbiBvZiBlYWNoIHF1YXJ0ZXIgYnkgYHN1bW1hcmlzZSgpLmAqCgoKKipBbnN3ZXIqKjoKYGBge3J9CmF1Z21lbnQocnRzLmxtLCBydHMudHMpIHw+IG11dGF0ZShxdWFydGVyPXF1YXJ0ZXIoZGF0ZSkpIHw+IGdyb3VwX2J5KHF1YXJ0ZXIpIHw+IHN1bW1hcmlzZShyZXNpZC5tZWFuPW1lYW4oLnJlc2lkKSkKYGBgCgoKNC4gVGhlIGFib3ZlIHByb2NlZHVyZXMgc2VlbXMgd29yayB3ZWxsIGJ1dCB0ZWRpb3VzLiBBIHN1cnByaXNpbmcgZmFjdCBpcyB0aGF0IHdlIGNhbiBjb21wbGV0ZSB0aGVzZSBqb2JzIHdpdGggb25lIGxpbmUgYnkgYGxtKClgIGFzIGZvbGxvd3MuIFRoZSB0cmljayBpcyB0aGF0IHdlIG11c3QgdHVybiBgcXVhcnRlcmAgaW50byBhIGZhY3RvciAoY2F0ZWdvcmljYWwsIHF1YWxpdGF0aXZlKSB2YXJpYWJsZSB2aWEgYGZhY3RvcigpYCBhbmQgaW5jbHVkZSBpdCBpbiB0aGUgcmVncmVzc2lvbiBmb3JtdWxhIGluIGBsbSgpYC4KCmBgYHtyfQpydHMudHMucSA8LSBydHMudHMgfD4gbXV0YXRlKHF1YXJ0ZXI9ZmFjdG9yKHF1YXJ0ZXIoZGF0ZSkpKSAKcnRzLmxtLnEgPC0gbG0oeF9tfnRpbWUrcXVhcnRlcixkYXRhPXJ0cy50cy5xKQpzdW1tYXJ5KHJ0cy5sbS5xKQpgYGAKCldlIGhhdmUgb2J0YWluZWQgYSBmZXcgbW9yZSByb3dzIGluIGBDb2VmZmljaWVudHM6YCwgaW5jbHVkaW5nIGBxdWFydGVyMmAsYHF1YXJ0ZXIzYCxgcXVhcnRlcjRgIHdpdGggYEVzdGltYXRlYCwgYFN0ZC4gRXJyb3JgLCBldGMuIAoKTG9va3MgZ29vZCEgT2gsIHdhaXQsIHdoZXJlIGlzIGBxdWFydGVyMWA/CgpJbiBmYWN0LCB0aGUgc2Vhc29uYWwgZWZmZWN0IGF0IHRoZSBmaXJzdCBxdWFydGVyIGlzIGluY2x1ZGVkIGluIGAoSW50ZXJjZXB0KWAkPVRydWUuSW50ZXJjZXB0ICsgU2Vhc29uYWwuRWZmZWN0Lm9mLlExJCEgTW9yZSBpbXBvcnRhbnRseSwgYEVzdGltYXRlYHMgZm9yIHRoZSByZXN0IHRocmVlIHF1YXJ0ZXJzIGlzIG5vdCB0aGUgc2Vhc29uYWwgZWZmZWN0cyBhdCBjb3JyZXNwb25kaW5nIHF1YXJ0ZXJzLiBUaGV5IGFyZSBpbnN0ZWFkIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gJFNlYXNvbmFsLkVmZmVjdC5vZi5RMSQgYW5kICRTZWFzb25hbC5FZmZlY3Qub2YuUTIuUTMuYW5kIC5RNCQKClRoZXJlZm9yZSwgd2UgY2FuIHdyaXRlIHRoZSBmb2xsb3cgZXF1YXRpb24gZm9yIG91ciBmaXR0ZWQgbGluZWFyIG1vZGVsIGFzClxbCm1lYW4oUmV0YWlsLlRyYWRlLlNhbGVzKT03MjQwLjggKyAxNjIuNiBcdGltZXMgVGltZSArIERfUSwgUT0xLDIsMyw0LgpcXQp3aGVyZSAkRF8xPTAkLCAkRF8yPS0zMzAuMCQsJERfMz0tMjc4LjAkIGFuZCAkRF80PTE1MjEuOCQuCgpNb3JlIGRldGFpbHMgb24gdGhpcyBzdHJhbmdlIGlzc3VlIGFmdGVyIGFkZGluZyBmYWN0b3IgdmFyaWFibGVzIGluIHRoZSBsaW5lYXIgbW9kZWwgd2lsbCBiZSByZXZlYWxlZCBpbiB0aGUgY29taW5nIExlY3R1cmVzIEM1IGFuZCBDNiwgYW5kIGNvcnJlc3BvbmRpbmcgTGFicy4KClZpc3VhbGlzZSBvdXIgbW9kZWwgd2l0aCBib3RoIGxpbmVhciB0cmVuZCBhbmQgc2Vhc29uYWwgZWZmZWN0czoKYGBge3J9CmF1Z21lbnQocnRzLmxtLnEpIHw+IGdncGxvdChhZXMoeCA9IHRpbWUgLHk9eF9tKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoYWxwaGE9MC4zKSArCiAgZ2VvbV9saW5lKGFlcyh5PS5maXR0ZWQpLGNvbD0ncmVkJykgKyB4bGFiKCdUaW1lIEluZGV4JykgKyB5bGFiKCdSZXRhaWwgVHJhZGUgU2FsZXMnKQpgYGAKCk5vdyB0aGUgZml0dGVkIGN1cnZlIGxvb2tzIG11Y2ggbW9yZSByZWFzb25hYmxlISAqRG8geW91IHRoaW5rIHRoZXJlIGlzIGFueSByb29tIGZvciBmdXJ0aGVyIGltcHJvdmVtZW50PyoKCioqQW5zd2VyKio6IFllcy4gV2UgY2FuIHNlZSB0aGF0IHRoZSBzZWFzb25hbCB2YXJpYXRpb25zIGFyZSBpbmNyZWFzaW5nIG92ZXIgdGltZS4gQSBsb2cgdHJhbnNmb3JtIGNhbiBoZWxwIHN0YWJpbGlzZSB0aGUgaW5jcmVhc2luZyB2YXJpYWJpbGl0eS4KCipQZXJmb3JtIGEgdGltZSBzZXJpZXMgcmVzaWR1YWwgYW5hbHlzaXMgb24geW91ciBmaXR0ZWQgbW9kZWwuIFJlbWVtYmVyIHRoZSByZXNpZHVhbHMgdmVyc3VzIHRpbWUgcGxvdC4qCgoqKkFuc3dlcioqOiAKYGBge3J9CnBsb3QocnRzLmxtLnEpCmF1Z21lbnQocnRzLmxtLnEpIHw+IGdncGxvdChhZXMoeD10aW1lLHk9LnJlc2lkKSkgKyBnZW9tX3BvaW50KCkKYGBgCk5vdCByZWFsbHkgZ29vZCEgU3RpbGwgaGF2ZSBzb21lIG9idmlvdXMgcGF0dGVybnMgaW4gdGhlIHJlc2lkdWFscy4gTm9uZSBvZiB0aGUgcGxvdHMgcGFzcyB0aGUgZGlhZ29ub3N0aWNzLiBOZWVkIHNvbWUgYmV0dGVyIG1vZGVscyB0byBhZGRyZXNzIGl0LgoKKlRyeSB0byB2aXN1YWxpc2UgdGhlIG1vZGVsIHdpdGggYHZpc3JlZygpYC4gV2hhdCBkbyB5b3UgZmluZCBmcm9tIHRoZSBwbG90cyBvZiBgdmlzcmVnKClgLioKCioqQW5zd2VyKio6IApgYGB7cn0KdmlzcmVnKHJ0cy5sbS5xKQpgYGAKCmB2aXNyZWcoKWAgc3BsaXRzIHRoZSBlZmZlY3Qgb2YgdHJlbmQgYW5kIHNlYXNvbmFsaXR5IHRvIHR3byBzZXBhcmF0ZSBncmFwaHMuCgo1LiBXZSBjYW4gcHJlZGljdCB0aGUgc2FsZXMgaW4gMjAyMCBhcyBmb2xsb3dzCmBgYHtyfQpuZXd0aW1lIDwtIGRhdGEuZnJhbWUodGltZT1ucm93KHJ0cy50cykrKDE6NCkscXVhcnRlcj1mYWN0b3IoMTo0KSkKYXVnbWVudChydHMubG0ucSxuZXdkYXRhPW5ld3RpbWUpCmBgYApCeSBhZGRpbmcgYG5ld2RhdGE9bmV3dGltZWAgaW4gYGF1Z21lbnQoKWAsIHdlIGNhbiBnZXQgdGhlIHByZWRpY3Rpb24gaW1tZWRpYXRlbHkganVzdCBsaWtlIGBwcmVkaWN0KClgIGFuZCB0aGUgcmVzdWx0cyBhcmUgb3JnYW5pc2VkIHdlbGwgaW4gYSB0aWJibGUuIAoKKkNvbXB1dGUgdGhlIG1lYW4gc3F1YXJlIGVycm9ycyBvZiB5b3VyIHByZWRpY3Rpb24gaW4gMjAyMC4qCgpgYGB7cn0KcnRzLjIwMjAgPC0gY29uc3VtcHRpb24gfD4KICBmaWx0ZXIoZGF0ZSA+PSB5bWQoJzIwMjAtMDEtMDEnKSAmIGRhdGUgPD0geW1kKCcyMDIxLTAxLTAxJykgKSB8PiAKICBjYmluZChhdWdtZW50KHJ0cy5sbS5xLG5ld2RhdGE9bmV3dGltZSkpIApydHMuMjAyMApydHMuMjAyMCB8PiBzdW1tYXJpc2UobXNlPW1lYW4oKHhfbS0uZml0dGVkKV4yKSkKYGBgClRoZSBwcmVkaWN0aW9ucyBhcmUgbm90IHJlYWxseSBnb29kIGFzIHRoZSBzYWxlcyBoYXZlIGJlZW4gdW5kZXJlc3RpbWF0ZWQgY29uc2lzdGVudGx5LiAKCipXaGF0IHdpbGwgaGFwcGVuIGlmIHdlIHVzZSB0aGUgZml0dGVkIG1vZGVsIHRvIHByZWRpY3QgdGhlIHNhbGVzIGluIDIwMjE/KgoKKipBbnN3ZXIqKjogV2Ugd29uJ3QgZ2V0IGdvb2QgcHJlZGljdGlvbnMgZ2l2ZW4gdGhlIHNob2NrIG9mIENPVklELTE5CgoqKk9wdGlvbmFsIENoYWxsZW5nZTogV2lsbCBsb2cgdHJhbnNmb3JtIGltcHJvdmUgeW91ciBtb2RlbCBmaXQ/IFRyeSBpdCEqKgoKKipBbnN3ZXIqKjoKYGBge3J9CnJ0cy5sbS5xLmxvZyA8LSBsbShsb2coeF9tKX50aW1lK3F1YXJ0ZXIsZGF0YT1ydHMudHMucSkKc3VtbWFyeShydHMubG0ucS5sb2cpCmF1Z21lbnQocnRzLmxtLnEubG9nLG5ld2RhdGE9bmV3dGltZSkgfD4gbXV0YXRlKC5wcmVkPWV4cCguZml0dGVkKSkKYGBgCgpQcmVkaWN0aW9ucyBhcmUgbXVjaCBiZXR0ZXIhIFlvdSBjYW4gY2hlY2sgcmVzaWR1YWxzIGFuZCB2aXN1YWxpc2F0aW9uIGFjY29yZGluZ2x5LiA=