Introduction
As the nation’s capital, Washington D.C. stands as a vibrant and
densely populated metropolitan hub. To address the city’s need for
efficient transportation in such limited space, SmartBike DC, now known
as Capital Bikeshare, was established with an initial deployment of 10
stations and 120 bicycles. As of now, this bikeshare network extends
throughout the DC metro area, catering to over 3 million riders
annually. The system connects residents to workplaces, recreational
areas, Metro stations, and various points of interest across the region
to fill the gaps where alternative transportation may not be
available.
A significant challenge faced by bikeshare systems is the optimal
redistribution of bikes within the network. Ensuring the availability of
at least one bike and one open slot at every station is crucial, as a
station without bikes or bike parking inconveniences riders, compelling
them to seek alternative transportation. In this initiative, we focus on
developing a predictive model for Capital Bikeshare in the District of
Columbia. The model aims to facilitate bike redistribution by
forecasting demand in 15-minute intervals for the upcoming week.
Departing from traditional truck-based resupply methods, which are
resource-intensive, we propose a novel approach—offering dynamic
incentives to riders for redistributing bikes across the network.
Our analysis is restricted to the District of Columbia because of
computational limitations on model cross validation.
library(tidyverse)
library(sf)
library(lubridate)
library(tigris)
library(tidycensus)
library(viridis)
library(riem)
library(gridExtra)
library(knitr)
library(kableExtra)
library(RSocrata)
library(ggplot2)
library(FNN)
library(modelr)
library(gganimate)
library(gifski)
root.dir = "https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/DATA/"
source("https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/functions.r")
palette5 <- c("#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c")
palette4 <- c("#D2FBD4","#92BCAB","#527D82","#123F5A")
palette2 <- c("#6baed6","#08519c")
Import Data
Our model development process involves utilizing a bike-trip dataset
from Capital Bikeshare. We extract information on all bikeshare trips, including the names and coordinates of pickup
and dropoff locations for each trip.
data <- read.csv("C:/Users/Owner/Documents/RStudio/PublicPolicyAnalytics/BikeShare/202105-capitalbikeshare-tripdata.csv")
data2 <- data %>%
mutate(interval60 = floor_date(ymd_hms(started_at), unit = "hour"),
interval15 = floor_date(ymd_hms(started_at), unit = "15 mins"),
week = week(interval60),
dotw = wday(interval60, label=TRUE))
dcCensus <-
get_acs(geography = "tract",
variables = c("B01003_001", "B19013_001",
"B02001_002", "B08013_001",
"B08012_001", "B08301_001",
"B08301_010", "B01002_001"),
year = 2021,
state = "DC",
geometry = TRUE,
output = "wide") %>%
rename(Total_Pop = B01003_001E,
Med_Inc = B19013_001E,
Med_Age = B01002_001E,
White_Pop = B02001_002E,
Travel_Time = B08013_001E,
Num_Commuters = B08012_001E,
Means_of_Transport = B08301_001E,
Total_Public_Trans = B08301_010E) %>%
select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
Means_of_Transport, Total_Public_Trans,
Med_Age,
GEOID, geometry) %>%
mutate(Percent_White = White_Pop / Total_Pop,
Mean_Commute_Time = Travel_Time / Total_Public_Trans,
Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)
dcTracts <-
dcCensus %>%
as.data.frame() %>%
distinct(GEOID, .keep_all = TRUE) %>%
select(GEOID, geometry) %>%
st_sf
dat_census <- st_join(data2 %>%
filter(is.na(start_lat) == FALSE &
is.na(start_lng) == FALSE &
is.na(end_lat) == FALSE &
is.na(end_lng) == FALSE) %>%
st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326),
dcTracts %>%
st_transform(crs=4326),
join=st_intersects,
left = TRUE) %>%
rename(Origin.Tract = GEOID) %>%
mutate(from_longitude = unlist(map(geometry, 1)),
from_latitude = unlist(map(geometry, 2)))%>%
as.data.frame() %>%
select(-geometry)%>%
st_as_sf(., coords = c("end_lng", "end_lat"), crs = 4326) %>%
st_join(., dcTracts %>%
st_transform(crs=4326),
join=st_intersects,
left = TRUE) %>%
rename(Destination.Tract = GEOID) %>%
mutate(to_longitude = unlist(map(geometry, 1)),
to_latitude = unlist(map(geometry, 2)))%>%
filter(is.na(Origin.Tract) == FALSE,
is.na(Destination.Tract) == FALSE) %>%
as.data.frame() %>%
select(-geometry)
Engineering Feaures, Weather Data
Since climate conditions are important to whether a commuter decides
to travel by bicycle or not, we import 2021 data from the weather
station at Reagan National Airport to create temperature and
precipitation variables for our model. We visualize these data below. We
see a cyclical pattern in temperature and can expect bike ridership to
follow with these dips because of the time of day associated, however we
will also have to compare these to precipitation and temperatures
dropping below a certain amount, and how that effects ridership.
weather.Panel <-
riem_measures(station = "DCA", date_start = "2021-05-01", date_end = "2021-05-31") %>%
dplyr::select(valid, tmpf, p01i, sknt)%>%
replace(is.na(.), 0) %>%
mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
mutate(week = week(interval60),
dotw = wday(interval60, label=TRUE)) %>%
group_by(interval60) %>%
summarize(Temperature = max(tmpf),
Precipitation = sum(p01i),
Wind_Speed = max(sknt)) %>%
mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))
#glimpse(weather.Panel)
grid.arrange(
ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line() +
labs(title="Precipitation", x="Hour", y="Precipitation") + plotTheme(),
ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line() +
labs(title="Temperature", x="Hour", y="Temperature") + plotTheme(),
top="Weather Data - DC Reagan National Airport (DCA) - May 2021")

Import Metro Data
Proximity to Metro stations can indicate bikeshare demand. Therefore,
we extracted DC Metro stations and calculate the distance from each
bikeshare station to its nearest Metro station.
#length(unique(dat_census$interval60)) * length(unique(dat_census$start_station_id))
study.panel <-
expand.grid(interval60=unique(dat_census$interval60),
start_station_id = unique(dat_census$start_station_id)) %>%
left_join(., dat_census %>%
select(start_station_id, start_station_name, Origin.Tract, from_longitude, from_latitude)%>%
distinct() %>%
group_by(start_station_id) %>%
slice(1))
#nrow(study.panel)
ride.panel <-
dat_census %>%
mutate(Trip_Counter = 1) %>%
right_join(study.panel) %>%
group_by(interval60, start_station_id, start_station_name, Origin.Tract, from_longitude, from_latitude) %>%
summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
left_join(weather.Panel) %>%
ungroup() %>%
filter(is.na(start_station_id) == FALSE) %>%
mutate(week = week(interval60),
dotw = wday(interval60, label = TRUE)) %>%
filter(is.na(Origin.Tract) == FALSE)
ride.panel <-
left_join(ride.panel, dcCensus %>%
as.data.frame() %>%
select(-geometry), by = c("Origin.Tract" = "GEOID"))
metroStops <- st_read("https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Transportation_Rail_Bus_WebMercator/MapServer/52/query?outFields=*&where=1%3D1&f=geojson") %>%
dplyr::select(NAME, LINE) %>%
st_transform(crs=4326)
ride.panel <-
ride.panel %>%
mutate(X = from_longitude, Y = from_latitude )%>%
st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant")
ride.panel <-
ride.panel %>%
mutate(
station.dist = nn_function(st_coordinates(ride.panel), st_coordinates(metroStops), 1)) %>%
mutate(station.cat = case_when(
station.dist > 0 & station.dist <= 500 ~ "1",
station.dist > 500 & station.dist <= 1000 ~ "3",
station.dist > 1000 ~ "3"))
st_drop_geometry(ride.panel)
Data Exploration
We begin by examining the time and frequency components of our
data.
First, we look at the overall time pattern where there is clearly a
daily cyclical spikes and less activity on weekends. Notice that the
weekend near the 28th of May (Memorial Day) doesn’t have the same dip in
activity.
ggplot(dat_census %>%
group_by(interval60) %>%
tally())+
geom_line(aes(x = interval60, y = n))+
labs(title="Bike share trips per hr., Washington, DC, May 2021",
x="Date",
y="Number of trips")+
plotTheme()

Here, we examine the distributions of trips per station at different
times of the day. They show strong right skews, meaning that the
majority of stations have smaller pickup numbers and a minority of
stations experience a disproportionately higher volume of pickups. There
are stronger skews for AM and overnight ridership, suggesting that
higher pickup volumes occur during mid-day and the PM rush hour.
dat_census %>%
mutate(time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
group_by(interval60, start_station_name, time_of_day) %>%
tally()%>%
group_by(start_station_name, time_of_day)%>%
summarize(mean_trips = mean(n))%>%
ggplot()+
geom_histogram(aes(mean_trips), binwidth = 1)+
labs(title="Mean # of Hourly Trips Per Station, Washington DC, May 2021",
x="Number of trips",
y="Frequency")+
facet_wrap(~time_of_day)+
plotTheme()

We also examine ridership by day of the week and weekend vs. weekday.
There appears to be large differences between days of the week,
especially between weekends and weekdays. Notably, weekends have their
spike consistently around late morning to midday (hours 11-16), while
weekdays have an consistent spike after 5pm (hours 17-18). Because of
the overall variation across days of the week, we will employ it as a
variable in our model.
ggplot(dat_census %>% mutate(hour = hour(started_at)))+
geom_freqpoly(aes(hour, color = dotw), binwidth = 1)+
labs(title="Bike share trips in DC, by day of the week, May 2021",
x="Hour",
y="Trip Counts")+
plotTheme()

ggplot(dat_census %>%
mutate(hour = hour(started_at),
weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday")))+
geom_freqpoly(aes(hour, color = weekend), binwidth = 1)+
labs(title="Bike share trips in DC - weekend vs weekday, May 2021",
x="Hour",
y="Trip Counts")+
plotTheme()
The map series below shows which bike stations are used more often
throughout the day of the week and time of day. Majority of these areas
are yellow representing little ridership, but when relating back to the
spikes on the weekends and weekdays, we see where these spikes are
occurring. Mid-day spikes on the weekday show red dots closer to the
central part of DC while the weekends presents slightly more activity on
the southern side throughout the day.
ggplot()+
geom_sf(data = dcTracts %>%
st_transform(crs=4326),
color = "gray80",
fill = "gray50")+
geom_point(data = dat_census %>%
mutate(hour = hour(started_at),
weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
group_by(start_station_id, from_latitude, from_longitude, weekend, time_of_day) %>%
tally(),
aes(x=from_longitude, y = from_latitude, color = n),
fill = "transparent", alpha = 0.7, size = 0.8)+
scale_colour_viridis(direction = -1, end = 0.95,
discrete = FALSE, option = "B")+
ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
facet_grid(weekend ~ time_of_day)+
labs(title="Bike share trips per hr by station, Washington, DC, May 2021",)+
mapTheme()

Generate time lags
Creating time lag variables provides additional insights into
rideshare demand by capturing variations in usage hours before and
during a given day. Analyzing ride usage in hours before and after can
forecast future activity at rideshare stations. Additionally, it’s
important to factor in holiday effects that disrupt expected demand
patterns on weekends or weekdays, treating them as outliers. For May, we
can use Memorial Day on May 28 as a three-day weekend and examine the
temporal proximity to the holiday. The correlations in these time lags
are notably strong, with a Pearson’s R of 0.92 for the lagHour,
indicating a nearly 1:1 correlation. This high correlation serves as
practical evidence linking ridership usage and holidays.
It aligns with the intuitive understanding that current demand should
resemble tomorrow’s and an hour from now, but expectations equally vary
twelve hours into the future in terms of demand, where we see a value of
-0.59, showing usage 12 hours after a measurement will have an opposite
effect.
ride.panel <-
ride.panel %>%
arrange(start_station_id, interval60) %>%
mutate(lagHour = dplyr::lag(Trip_Count,1),
lag2Hours = dplyr::lag(Trip_Count,2),
lag3Hours = dplyr::lag(Trip_Count,3),
lag4Hours = dplyr::lag(Trip_Count,4),
lag12Hours = dplyr::lag(Trip_Count,12),
lag1day = dplyr::lag(Trip_Count,24),
holiday = ifelse(yday(interval60) == 148,1,0)) %>%
mutate(day = yday(interval60)) %>%
mutate(holidayLag = case_when(dplyr::lag(holiday, 1) == 1 ~ "PlusOneDay",
dplyr::lag(holiday, 2) == 1 ~ "PlusTwoDays",
dplyr::lag(holiday, 3) == 1 ~ "PlusThreeDays",
dplyr::lead(holiday, 1) == 1 ~ "MinusOneDay",
dplyr::lead(holiday, 2) == 1 ~ "MinusTwoDays",
dplyr::lead(holiday, 3) == 1 ~ "MinusThreeDays"),
holidayLag = ifelse(is.na(holidayLag) == TRUE, 0, holidayLag))
as.data.frame(ride.panel) %>%
group_by(interval60) %>%
summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
gather(Variable, Value, -interval60, -Trip_Count) %>%
mutate(Variable = factor(Variable, levels=c("lagHour","lag2Hours","lag3Hours","lag4Hours",
"lag12Hours","lag1day")))%>%
group_by(Variable) %>%
summarize(correlation = round(cor(Value, Trip_Count),2))
## # A tibble: 6 × 2
## Variable correlation
## <fct> <dbl>
## 1 lagHour 0.92
## 2 lag2Hours 0.78
## 3 lag3Hours 0.6
## 4 lag4Hours 0.42
## 5 lag12Hours -0.59
## 6 lag1day 0.78
Animated map
We select bikeshare trips on May 18th from the dataset to visualize
in the following animated map. We chose this date as a relatively
representative date on which there was no precipitation and moderate
temperatures (a high of 78 F). Majority of bike trips throughout the day
remain in the center of DC with frequent lone points on the edges of the
map.
week20 <- dat_census %>%
filter(week == 20 & dotw == "Tue")
week20.panel <-
expand.grid(
interval15 = unique(week20$interval15),
station = unique(dat_census$start_station_id))
ride.animation.data <-
mutate(week20, Trip_Counter = 1) %>%
select(interval15, start_station_id, from_longitude, from_latitude, Trip_Counter) %>%
group_by(interval15, from_longitude, from_latitude, start_station_id) %>%
summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
ungroup() %>%
mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
Trip_Count > 0 & Trip_Count <= 1 ~ "1 trip",
Trip_Count > 1 & Trip_Count <= 2 ~ "2 trips",
Trip_Count > 2 & Trip_Count <= 5 ~ "3-5 trips",
Trip_Count > 5 & Trip_Count <= 10 ~ "6-10 trips",
Trip_Count > 10 ~ ">10 trips")) %>%
mutate(Trips = fct_relevel(Trips, "0 trips","1 trip","2 trips",
"3-5 trips","6-10 trips",">10 trips"))
rideshare_animation <-
ggplot()+
geom_sf(data = dcTracts %>%
st_transform(crs=4326), color = 'gray80', fill = "gray50")+
geom_point(data = ride.animation.data,
aes(x = from_longitude, y = from_latitude, color = Trips),
size = 1.5,
alpha = 1.5) +
scale_colour_manual(values = palette5) +
labs(title = "Bikeshare trips on May 18th, 2021, Washington, DC",
subtitle = "Time: {current_frame}") +
transition_manual(interval15) +
mapTheme()
animate(rideshare_animation, duration=45, renderer = gifski_renderer())

Modeling
In order to train and test our prediction model, we first split our
data into a training and a test set. Then, we build five linear models
using our training data to predict trip counts. The models’ dependent
variables are as follows:
- Hour of day, day of week, temperature
- Pickup station, day of week, temperature
- Variables in models 1 and 2 + precipitation
- Variables in model 3 + time lags
- Variables in model 4 + distance from Metro station
ride.Train <- filter(ride.panel, week >= 20)
ride.Test <- filter(ride.panel, week < 20)
reg1 <-
lm(Trip_Count ~ hour(interval60) + dotw + Temperature, data=ride.Train)
reg2 <-
lm(Trip_Count ~ start_station_name + dotw + Temperature, data=ride.Train)
reg3 <-
lm(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Precipitation,
data=ride.Train)
reg4 <-
lm(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Precipitation +
lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day,
data=ride.Train)
reg5 <-
lm(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Precipitation +
lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + station.dist,
data=ride.Train)
Predict for test data
Next, we use our models to predict trip counts on the test data.
ride.Test.weekNest <-
ride.Test %>%
nest(-week)
model_pred <- function(dat, fit){
pred <- predict(fit, newdata = dat)}
week_predictions <-
ride.Test.weekNest %>%
mutate(Model1_TimeOnly = map(.x = data, fit = reg1, .f = model_pred),
Model2_SpaceOnly = map(.x = data, fit = reg2, .f = model_pred),
Model3_Time_Space = map(.x = data, fit = reg3, .f = model_pred),
Model4_TimeSpaceLag = map(.x = data, fit = reg4, .f = model_pred),
Model5_All = map(.x = data, fit = reg5, .f = model_pred)) %>%
gather(Regression, Prediction, -data, -week) %>%
mutate(Observed = map(data, pull, Trip_Count),
Absolute_Error = map2(Observed, Prediction, ~ abs(.x - .y)),
MAE = map_dbl(Absolute_Error, mean, na.rm = TRUE),
sd_AE = map_dbl(Absolute_Error, sd, na.rm = TRUE))
week_predictions
## # A tibble: 10 × 8
## week data Regression Prediction Observed Absolute_Error MAE sd_AE
## <dbl> <list> <chr> <list> <list> <list> <dbl> <dbl>
## 1 18 <sf> Model1_TimeOnly <dbl> <dbl> <dbl [47,232]> 0.684 0.978
## 2 19 <sf> Model1_TimeOnly <dbl> <dbl> <dbl [55,104]> 0.612 0.929
## 3 18 <sf> Model2_SpaceOnly <dbl> <dbl> <dbl [47,232]> 0.636 0.892
## 4 19 <sf> Model2_SpaceOnly <dbl> <dbl> <dbl [55,104]> 0.608 0.826
## 5 18 <sf> Model3_Time_Spac… <dbl> <dbl> <dbl [47,232]> 0.643 0.871
## 6 19 <sf> Model3_Time_Spac… <dbl> <dbl> <dbl [55,104]> 0.606 0.809
## 7 18 <sf> Model4_Time_Spac… <dbl> <dbl> <dbl [47,232]> 0.532 0.811
## 8 19 <sf> Model4_Time_Spac… <dbl> <dbl> <dbl [55,104]> 0.509 0.769
## 9 18 <sf> Model5_All <dbl> <dbl> <dbl [47,232]> 0.532 0.811
## 10 19 <sf> Model5_All <dbl> <dbl> <dbl [55,104]> 0.509 0.769
Examining Model Errors
Overall, our models are accurate to less than an average of one ride
per hour. The best performing models are those that incorporate time
lags, with a negligible improvement from Model 4 to Model 5.
week_predictions %>%
dplyr::select(week, Regression, MAE) %>%
gather(Variable, MAE, -Regression, -week) %>%
ggplot(aes(week, MAE)) +
geom_bar(aes(fill = Regression), position = "dodge", stat="identity") +
scale_fill_manual(values = palette5) +
labs(title = "Mean Absolute Errors by model specification and week") +
plotTheme()

After plotting predicted vs. observed pickups, we observe that the
correspondence between predicted and observed values is strongest in
models 4 and 5, which is consistent with the previous observations. Each
model does somewhat of a decent job predicting the spike seen at the end
off work-hours on weekdays, but what makes models 4 and 5 best suited is
because they are able to measure that spike. The predicted outcomes for
1,2, and 3 reflect similarly to our observations on weekends, where the
peak was gradual, longer, and more consistent.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id)) %>%
dplyr::select(interval60, start_station_id, Observed, Prediction, Regression) %>%
unnest() %>%
gather(Variable, Value, -Regression, -interval60, -start_station_id) %>%
group_by(Regression, Variable, interval60) %>%
summarize(Value = sum(Value)) %>%
ggplot(aes(interval60, Value, colour=Variable)) +
geom_line(size = 1.1) +
facet_wrap(~Regression, ncol=1) +
labs(title = "Predicted/Observed bikeshare pickup time series", subtitle = "Washington DC; A test set of 2 weeks", x = "Hour", y= "Station Trips") +
plotTheme()

Further examining Model 5, our best suited, we observe Mean Absolute
Error (MAE) values when mapped across stations. Notably, errors are more
pronounced for stations in close proximity to downtown DC and the
National Mall. Our next step involves a thorough examination of
space-time errors to gain further insights. Like all the other models,
it underperforms perdictions where the spikes post-workday occur, but
can accurately predict other times of the day. The MAE would have been
exacerbated if using any other model.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude)) %>%
select(interval60, start_station_id, from_longitude, from_latitude, Observed, Prediction, Regression) %>%
unnest() %>%
filter(Regression == "Model5_All") %>%
group_by(start_station_id, from_longitude, from_latitude) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
geom_sf(data = dcCensus, color = "gray80", fill = "gray50")+
geom_point(aes(x = from_longitude, y = from_latitude, color = MAE),
fill = "transparent", alpha = 0.7, size = 0.8)+
scale_colour_viridis(direction = -1,
discrete = FALSE, option = "D")+
ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
labs(title="Mean Abs Error, Test Set, Model 5")+
mapTheme()

Space-Time Error Evaluation
When contrasting predicted vs observed the pattern goes beyond under
performing the spike but also overpredictions in the overnight. Looking
at the graph, we see that predicting trips have a heavy lean towards the
left side, indicating that there is predictions of more trips happening
overnight when the observed outcomes are smaller. The model is not
capable of explaining the day-night patterns of people that are
reflected in bikeshare activity.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude),
dotw = map(data, pull, dotw)) %>%
select(interval60, start_station_id, from_longitude,
from_latitude, Observed, Prediction, Regression,
dotw) %>%
unnest() %>%
filter(Regression == "Model5_All")%>%
mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
ggplot()+
geom_point(aes(x= Observed, y = Prediction))+
geom_smooth(aes(x= Observed, y= Prediction), method = "lm", se = FALSE, color = "red")+
geom_abline(slope = 1, intercept = 0)+
facet_grid(time_of_day~weekend)+
labs(title="Observed vs Predicted",
x="Observed trips",
y="Predicted trips")+
plotTheme()

We also need to look at these errors of predictions versus
observations spatially, the spikes that the model missed are marked as
the higher MAE values in the National Mall area during midday and PM
rushes. We then look at the overnight where it also has higher MAE in
that same center, where it is predicting small but consistent ride usage
throughout the night. The model tends to take a more conservative
approach throughout the cyclical day, under performing business and over
performing downtime. The model cannot explain these discrepancies alone,
and there may be factors not related to bikeshare specifically that can
show correlations in its predictions. For this analysis we will look
deeper into the AM rush and the attributes errors in terms of
socio-economic factors.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude),
dotw = map(data, pull, dotw) ) %>%
select(interval60, start_station_id, from_longitude,
from_latitude, Observed, Prediction, Regression,
dotw) %>%
unnest() %>%
filter(Regression == "Model5_All")%>%
mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
group_by(start_station_id, weekend, time_of_day, from_longitude, from_latitude) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
geom_sf(data = dcCensus, color = "white", fill = "gray80")+
geom_point(aes(x = from_longitude, y = from_latitude, color = MAE),
fill = "transparent", size = 0.5, alpha = 0.8)+
scale_colour_viridis(direction = -1,
discrete = FALSE, option = "B")+
ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
facet_grid(weekend~time_of_day)+
labs(title="Mean Absolute Errors, Test Set")+
mapTheme()

We looked at three factors, median income, public transportation use,
and percentage of white people. Public transportation does not appear to
have a given effect on the predicted values but the white population
& median income show a steady incline where there are larger errors
when there is more white people or wealthier areas. Thus, our model
predicts higher rideshare use in areas with whiter populations or higher
income.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude),
dotw = map(data, pull, dotw),
Percent_Taking_Public_Trans = map(data, pull, Percent_Taking_Public_Trans),
Median_Income = map(data, pull, Med_Inc),
Percent_White = map(data, pull, Percent_White)) %>%
select(interval60, start_station_id, from_longitude,
from_latitude, Observed, Prediction, Regression,
dotw, Percent_Taking_Public_Trans, Median_Income, Percent_White) %>%
unnest() %>%
filter(Regression == "Model5_All")%>%
mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
filter(time_of_day == "AM Rush") %>%
group_by(start_station_id, Percent_Taking_Public_Trans, Median_Income, Percent_White) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
gather(-start_station_id, -MAE, key = "variable", value = "value")%>%
ggplot(.)+
#geom_sf(data = dcCensus, color = "grey", fill = "transparent")+
geom_point(aes(x = value, y = MAE), alpha = 0.4)+
geom_smooth(aes(x = value, y = MAE), method = "lm", se= FALSE)+
facet_wrap(~variable, scales = "free")+
labs(title="AM Rush errors as a function of socio-economic variables",
y="Mean Absolute Error (Trips)")+
plotTheme()

Cross Validation
To assess the goodness of our model, we use k-fold cross-validation,
normally we would run 100 folds for a proper evaluation. However, due to
computational constraints, we had to settle for 10 folds. The histogram
illustrates the average errors across each fold, providing a summary of
our cross-validation results. The distribution exhibits an approximate
normality, with a central tendency around 1.05. This value represents a
relatively low level of error, underscoring the precision of our
model.
set.seed(8)
cv2<- crossv_kfold(ride.Train, 10)
models <- map(cv2$train,~ lm(Trip_Count ~ start_station_name + hour(interval60)
+ dotw + Temperature
+ Precipitation + lagHour + lag2Hours +lag3Hours
+lag12Hours + lag1day + station.dist,
data=ride.Train))
errs <- map2_dbl(models, cv2$test, rmse)
ggplot()+
geom_histogram(aes(errs),bins=8)+
labs(title="Histogram of k-fold CV errors",
y="Frequency",
x="Mean fold error")+
plotTheme()

remove(models, cv2)
Conclusion
We developed a model forecasting bike demand across DC, factoring in
both time and location. Our goal is to keep every bike station stocked
with at least one bike and have 1 bike slot open at all times, ensuring
riders always find what they need. After analyzing the various factors
and observed patterns, we estimate the bikes riders will pick up,
enabling Capital Bikeshare to proactively plan for station and bike
availability.
In conjunction with data on bike station capacity, this model is
capable to be implemented in a dynamic incentive system that
redistributes bikes across stations by offering lower costs (or even
offsets to future rides) to riders in exchange for a different drop-off
location. The incentive should vary with distance to original
destination, number of bikes already at the target drop-off station as
well as predicted bike demand at that station. Capital Bikeshare can
experiment with the incentive structure to optimize for bike
availability across stations.
LS0tDQp0aXRsZTogIkJpa2UgU2hhcmUgTW9uaXRvcmluZyBpbiBXYXNoaW5ndG9uIEQuQyINCmF1dGhvcjogIlRyZXZvciBLYXB1dmFyaSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogImhpZGUiDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCiMgSW50cm9kdWN0aW9uDQoNCkFzIHRoZSBuYXRpb24ncyBjYXBpdGFsLCBXYXNoaW5ndG9uIEQuQy4gc3RhbmRzIGFzIGEgdmlicmFudCBhbmQgZGVuc2VseSBwb3B1bGF0ZWQgbWV0cm9wb2xpdGFuIGh1Yi4gVG8gYWRkcmVzcyB0aGUgY2l0eSdzIG5lZWQgZm9yIGVmZmljaWVudCB0cmFuc3BvcnRhdGlvbiBpbiBzdWNoIGxpbWl0ZWQgc3BhY2UsIFNtYXJ0QmlrZSBEQywgbm93IGtub3duIGFzIENhcGl0YWwgQmlrZXNoYXJlLCB3YXMgZXN0YWJsaXNoZWQgd2l0aCBhbiBpbml0aWFsIGRlcGxveW1lbnQgb2YgMTAgc3RhdGlvbnMgYW5kIDEyMCBiaWN5Y2xlcy4gQXMgb2Ygbm93LCB0aGlzIGJpa2VzaGFyZSBuZXR3b3JrIGV4dGVuZHMgdGhyb3VnaG91dCB0aGUgREMgbWV0cm8gYXJlYSwgY2F0ZXJpbmcgdG8gb3ZlciAzIG1pbGxpb24gcmlkZXJzIGFubnVhbGx5LiBUaGUgc3lzdGVtIGNvbm5lY3RzIHJlc2lkZW50cyB0byB3b3JrcGxhY2VzLCByZWNyZWF0aW9uYWwgYXJlYXMsIE1ldHJvIHN0YXRpb25zLCBhbmQgdmFyaW91cyBwb2ludHMgb2YgaW50ZXJlc3QgYWNyb3NzIHRoZSByZWdpb24gdG8gZmlsbCB0aGUgZ2FwcyB3aGVyZSBhbHRlcm5hdGl2ZSB0cmFuc3BvcnRhdGlvbiBtYXkgbm90IGJlIGF2YWlsYWJsZS4NCg0KQSBzaWduaWZpY2FudCBjaGFsbGVuZ2UgZmFjZWQgYnkgYmlrZXNoYXJlIHN5c3RlbXMgaXMgdGhlIG9wdGltYWwgcmVkaXN0cmlidXRpb24gb2YgYmlrZXMgd2l0aGluIHRoZSBuZXR3b3JrLiBFbnN1cmluZyB0aGUgYXZhaWxhYmlsaXR5IG9mIGF0IGxlYXN0IG9uZSBiaWtlIGFuZCBvbmUgb3BlbiBzbG90IGF0IGV2ZXJ5IHN0YXRpb24gaXMgY3J1Y2lhbCwgYXMgYSBzdGF0aW9uIHdpdGhvdXQgYmlrZXMgb3IgYmlrZSBwYXJraW5nIGluY29udmVuaWVuY2VzIHJpZGVycywgY29tcGVsbGluZyB0aGVtIHRvIHNlZWsgYWx0ZXJuYXRpdmUgdHJhbnNwb3J0YXRpb24uIEluIHRoaXMgaW5pdGlhdGl2ZSwgd2UgZm9jdXMgb24gZGV2ZWxvcGluZyBhIHByZWRpY3RpdmUgbW9kZWwgZm9yIENhcGl0YWwgQmlrZXNoYXJlIGluIHRoZSBEaXN0cmljdCBvZiBDb2x1bWJpYS4gVGhlIG1vZGVsIGFpbXMgdG8gZmFjaWxpdGF0ZSBiaWtlIHJlZGlzdHJpYnV0aW9uIGJ5IGZvcmVjYXN0aW5nIGRlbWFuZCBpbiAxNS1taW51dGUgaW50ZXJ2YWxzIGZvciB0aGUgdXBjb21pbmcgd2Vlay4gRGVwYXJ0aW5nIGZyb20gdHJhZGl0aW9uYWwgdHJ1Y2stYmFzZWQgcmVzdXBwbHkgbWV0aG9kcywgd2hpY2ggICAgICAgICAgICAgICAgYXJlIHJlc291cmNlLWludGVuc2l2ZSwgd2UgcHJvcG9zZSBhIG5vdmVsIGFwcHJvYWNo4oCUb2ZmZXJpbmcgZHluYW1pYyBpbmNlbnRpdmVzIHRvIHJpZGVycyBmb3IgcmVkaXN0cmlidXRpbmcgYmlrZXMgYWNyb3NzIHRoZSBuZXR3b3JrLg0KDQoNCk91ciBhbmFseXNpcyBpcyByZXN0cmljdGVkIHRvIHRoZSBEaXN0cmljdCBvZiBDb2x1bWJpYSBiZWNhdXNlIG9mIGNvbXB1dGF0aW9uYWwgbGltaXRhdGlvbnMgb24gbW9kZWwgY3Jvc3MgdmFsaWRhdGlvbi4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQoNCmBgYA0KDQpgYGB7ciBzZXR1cF8xMywgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KHRpZ3JpcykNCmxpYnJhcnkodGlkeWNlbnN1cykNCmxpYnJhcnkodmlyaWRpcykNCmxpYnJhcnkocmllbSkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkoUlNvY3JhdGEpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KEZOTikNCmxpYnJhcnkobW9kZWxyKQ0KbGlicmFyeShnZ2FuaW1hdGUpDQpsaWJyYXJ5KGdpZnNraSkNCg0Kcm9vdC5kaXIgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3VyYmFuU3BhdGlhbC9QdWJsaWMtUG9saWN5LUFuYWx5dGljcy1MYW5kaW5nL21hc3Rlci9EQVRBLyINCnNvdXJjZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3VyYmFuU3BhdGlhbC9QdWJsaWMtUG9saWN5LUFuYWx5dGljcy1MYW5kaW5nL21hc3Rlci9mdW5jdGlvbnMuciIpDQoNCnBhbGV0dGU1IDwtIGMoIiNlZmYzZmYiLCIjYmRkN2U3IiwiIzZiYWVkNiIsIiMzMTgyYmQiLCIjMDg1MTljIikNCnBhbGV0dGU0IDwtIGMoIiNEMkZCRDQiLCIjOTJCQ0FCIiwiIzUyN0Q4MiIsIiMxMjNGNUEiKQ0KcGFsZXR0ZTIgPC0gYygiIzZiYWVkNiIsIiMwODUxOWMiKQ0KYGBgDQoNCg0KYGBge3IgaW5zdGFsbF9jZW5zdXNfQVBJX2tleSwgd2FybmluZyA9IEZBTFNFLCBpbmNsdWRlPUZBTFNFLCBldmFsID0gVFJVRX0NCiMgSW5zdGFsbCBDZW5zdXMgQVBJIEtleQ0KdGlkeWNlbnN1czo6Y2Vuc3VzX2FwaV9rZXkoIjgxMWUxZjZmOGQxZjI5OWNiNzVhMGYwYzA3ZTAxYWFmZDgwMWZhNzkiLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYGBgDQoNCiMjIEltcG9ydCBEYXRhDQoNCk91ciBtb2RlbCBkZXZlbG9wbWVudCBwcm9jZXNzIGludm9sdmVzIHV0aWxpemluZyBhIDUtd2VlayBkYXRhc2V0IGZyb20gQ2FwaXRhbCBCaWtlc2hhcmUuIFdlIGV4dHJhY3QgaW5mb3JtYXRpb24gb24gYWxsIGJpa2VzaGFyZSB0cmlwcyB0aGF0IG9jY3VycmVkIGluIE1heSAyMDIxLCBpbmNsdWRpbmcgdGhlIG5hbWVzIGFuZCBjb29yZGluYXRlcyBvZiBwaWNrdXAgYW5kIGRyb3BvZmYgbG9jYXRpb25zIGZvciBlYWNoIHRyaXAuIA0KDQpgYGB7ciByZWFkX2RhdGF9DQpkYXRhIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy9Pd25lci9Eb2N1bWVudHMvUlN0dWRpby9QdWJsaWNQb2xpY3lBbmFseXRpY3MvQmlrZVNoYXJlLzIwMjEwNS1jYXBpdGFsYmlrZXNoYXJlLXRyaXBkYXRhLmNzdiIpDQpgYGANCg0KYGBge3IgdGltZV9iaW5zLCBtZXNzYWdlPUYsIHJlc3VsdHM9J2hpZGUnIH0NCmRhdGEyIDwtIGRhdGEgJT4lDQogIG11dGF0ZShpbnRlcnZhbDYwID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0ZWRfYXQpLCB1bml0ID0gImhvdXIiKSwNCiAgICAgICAgIGludGVydmFsMTUgPSBmbG9vcl9kYXRlKHltZF9obXMoc3RhcnRlZF9hdCksIHVuaXQgPSAiMTUgbWlucyIpLA0KICAgICAgICAgd2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkNCg0KZGNDZW5zdXMgPC0gDQogIGdldF9hY3MoZ2VvZ3JhcGh5ID0gInRyYWN0IiwgDQogICAgICAgICAgdmFyaWFibGVzID0gYygiQjAxMDAzXzAwMSIsICJCMTkwMTNfMDAxIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAiQjAyMDAxXzAwMiIsICJCMDgwMTNfMDAxIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDgwMTJfMDAxIiwgIkIwODMwMV8wMDEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDgzMDFfMDEwIiwgIkIwMTAwMl8wMDEiKSwgDQogICAgICAgICAgeWVhciA9IDIwMjEsIA0KICAgICAgICAgIHN0YXRlID0gIkRDIiwNCiAgICAgICAgICBnZW9tZXRyeSA9IFRSVUUsDQogICAgICAgICAgb3V0cHV0ID0gIndpZGUiKSAlPiUNCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwNCiAgICAgICAgIE1lZF9JbmMgPSBCMTkwMTNfMDAxRSwNCiAgICAgICAgIE1lZF9BZ2UgPSBCMDEwMDJfMDAxRSwNCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLA0KICAgICAgICAgVHJhdmVsX1RpbWUgPSBCMDgwMTNfMDAxRSwNCiAgICAgICAgIE51bV9Db21tdXRlcnMgPSBCMDgwMTJfMDAxRSwNCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLA0KICAgICAgICAgVG90YWxfUHVibGljX1RyYW5zID0gQjA4MzAxXzAxMEUpICU+JQ0KICBzZWxlY3QoVG90YWxfUG9wLCBNZWRfSW5jLCBXaGl0ZV9Qb3AsIFRyYXZlbF9UaW1lLA0KICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsDQogICAgICAgICBNZWRfQWdlLA0KICAgICAgICAgR0VPSUQsIGdlb21ldHJ5KSAlPiUNCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsDQogICAgICAgICBNZWFuX0NvbW11dGVfVGltZSA9IFRyYXZlbF9UaW1lIC8gVG90YWxfUHVibGljX1RyYW5zLA0KICAgICAgICAgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zID0gVG90YWxfUHVibGljX1RyYW5zIC8gTWVhbnNfb2ZfVHJhbnNwb3J0KQ0KDQpkY1RyYWN0cyA8LSANCiAgZGNDZW5zdXMgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZGlzdGluY3QoR0VPSUQsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQ0KICBzZWxlY3QoR0VPSUQsIGdlb21ldHJ5KSAlPiUgDQogIHN0X3NmDQoNCmRhdF9jZW5zdXMgPC0gc3Rfam9pbihkYXRhMiAlPiUgDQogICAgICAgICAgZmlsdGVyKGlzLm5hKHN0YXJ0X2xhdCkgPT0gRkFMU0UgJg0KICAgICAgICAgICAgICAgICAgIGlzLm5hKHN0YXJ0X2xuZykgPT0gRkFMU0UgJg0KICAgICAgICAgICAgICAgICAgIGlzLm5hKGVuZF9sYXQpID09IEZBTFNFICYNCiAgICAgICAgICAgICAgICAgICBpcy5uYShlbmRfbG5nKSA9PSBGQUxTRSkgJT4lDQogICAgICAgICAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygic3RhcnRfbG5nIiwgInN0YXJ0X2xhdCIpLCBjcnMgPSA0MzI2KSwNCiAgICAgICAgZGNUcmFjdHMgJT4lDQogICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSwNCiAgICAgICAgam9pbj1zdF9pbnRlcnNlY3RzLA0KICAgICAgICAgICAgICBsZWZ0ID0gVFJVRSkgJT4lDQogIHJlbmFtZShPcmlnaW4uVHJhY3QgPSBHRU9JRCkgJT4lDQogIG11dGF0ZShmcm9tX2xvbmdpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwNCiAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAyKSkpJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgc2VsZWN0KC1nZW9tZXRyeSklPiUNCiAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygiZW5kX2xuZyIsICJlbmRfbGF0IiksIGNycyA9IDQzMjYpICU+JQ0KICBzdF9qb2luKC4sIGRjVHJhY3RzICU+JQ0KICAgICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSwNCiAgICAgICAgICBqb2luPXN0X2ludGVyc2VjdHMsDQogICAgICAgICAgbGVmdCA9IFRSVUUpICU+JQ0KICByZW5hbWUoRGVzdGluYXRpb24uVHJhY3QgPSBHRU9JRCkgICU+JQ0KICBtdXRhdGUodG9fbG9uZ2l0dWRlID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLA0KICAgICAgICAgdG9fbGF0aXR1ZGUgPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAyKSkpJT4lDQogIGZpbHRlcihpcy5uYShPcmlnaW4uVHJhY3QpID09IEZBTFNFLA0KICAgICAgICAgaXMubmEoRGVzdGluYXRpb24uVHJhY3QpID09IEZBTFNFKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBzZWxlY3QoLWdlb21ldHJ5KQ0KYGBgDQoNCiMjIEVuZ2luZWVyaW5nIEZlYXVyZXMsIFdlYXRoZXIgRGF0YQ0KDQpTaW5jZSBjbGltYXRlIGNvbmRpdGlvbnMgYXJlIGltcG9ydGFudCB0byB3aGV0aGVyIGEgY29tbXV0ZXIgZGVjaWRlcyB0byB0cmF2ZWwgYnkgYmljeWNsZSBvciBub3QsIHdlIGltcG9ydCBNYXkgMjAyMSBkYXRhIGZyb20gdGhlIHdlYXRoZXIgc3RhdGlvbiBhdCBSZWFnYW4gTmF0aW9uYWwgQWlycG9ydCB0byBjcmVhdGUgdGVtcGVyYXR1cmUgYW5kIHByZWNpcGl0YXRpb24gdmFyaWFibGVzIGZvciBvdXIgbW9kZWwuIFdlIHZpc3VhbGl6ZSB0aGVzZSBkYXRhIGJlbG93LiBXZSBzZWUgYSBjeWNsaWNhbCBwYXR0ZXJuIGluIHRlbXBlcmF0dXJlIGFuZCBjYW4gZXhwZWN0IGJpa2UgcmlkZXJzaGlwIHRvIGZvbGxvdyB3aXRoIHRoZXNlIGRpcHMgYmVjYXVzZSBvZiB0aGUgdGltZSBvZiBkYXkgYXNzb2NpYXRlZCwgaG93ZXZlciB3ZSB3aWxsIGFsc28gaGF2ZSB0byBjb21wYXJlIHRoZXNlIHRvIHByZWNpcGl0YXRpb24gYW5kIHRlbXBlcmF0dXJlcyBkcm9wcGluZyBiZWxvdyBhIGNlcnRhaW4gYW1vdW50LCBhbmQgaG93IHRoYXQgZWZmZWN0cyByaWRlcnNoaXAuIA0KDQpgYGB7ciBpbXBvcnRfd2VhdGhlciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UgfQ0Kd2VhdGhlci5QYW5lbCA8LSANCiAgcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIkRDQSIsIGRhdGVfc3RhcnQgPSAiMjAyMS0wNS0wMSIsIGRhdGVfZW5kID0gIjIwMjEtMDUtMzEiKSAlPiUNCiAgZHBseXI6OnNlbGVjdCh2YWxpZCwgdG1wZiwgcDAxaSwgc2tudCklPiUNCiAgcmVwbGFjZShpcy5uYSguKSwgMCkgJT4lDQogICAgbXV0YXRlKGludGVydmFsNjAgPSB5bWRfaChzdWJzdHIodmFsaWQsMSwxMykpKSAlPiUNCiAgICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgICAgICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsPVRSVUUpKSAlPiUNCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUNCiAgICBzdW1tYXJpemUoVGVtcGVyYXR1cmUgPSBtYXgodG1wZiksDQogICAgICAgICAgICAgIFByZWNpcGl0YXRpb24gPSBzdW0ocDAxaSksDQogICAgICAgICAgICAgIFdpbmRfU3BlZWQgPSBtYXgoc2tudCkpICU+JQ0KICAgIG11dGF0ZShUZW1wZXJhdHVyZSA9IGlmZWxzZShUZW1wZXJhdHVyZSA9PSAwLCA0MiwgVGVtcGVyYXR1cmUpKQ0KDQojZ2xpbXBzZSh3ZWF0aGVyLlBhbmVsKQ0KDQpncmlkLmFycmFuZ2UoDQogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxQcmVjaXBpdGF0aW9uKSkgKyBnZW9tX2xpbmUoKSArIA0KICBsYWJzKHRpdGxlPSJQcmVjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlByZWNpcGl0YXRpb24iKSArIHBsb3RUaGVtZSgpLA0KICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsVGVtcGVyYXR1cmUpKSArIGdlb21fbGluZSgpICsgDQogICAgbGFicyh0aXRsZT0iVGVtcGVyYXR1cmUiLCB4PSJIb3VyIiwgeT0iVGVtcGVyYXR1cmUiKSArIHBsb3RUaGVtZSgpLA0KICB0b3A9IldlYXRoZXIgRGF0YSAtIERDIFJlYWdhbiBOYXRpb25hbCBBaXJwb3J0IChEQ0EpIC0gTWF5IDIwMjEiKQ0KYGBgDQoNCiMjIEltcG9ydCBNZXRybyBEYXRhDQoNClByb3hpbWl0eSB0byBNZXRybyBzdGF0aW9ucyBjYW4gaW5kaWNhdGUgYmlrZXNoYXJlIGRlbWFuZC4gVGhlcmVmb3JlLCB3ZSBleHRyYWN0ZWQgREMgTWV0cm8gc3RhdGlvbnMgYW5kIGNhbGN1bGF0ZSB0aGUgZGlzdGFuY2UgZnJvbSBlYWNoIGJpa2VzaGFyZSBzdGF0aW9uIHRvIGl0cyBuZWFyZXN0IE1ldHJvIHN0YXRpb24uDQoNCmBgYHtyIGNyZWF0ZSBzcGFjZSB0aW1lIHBhbmVsLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCiNsZW5ndGgodW5pcXVlKGRhdF9jZW5zdXMkaW50ZXJ2YWw2MCkpICogbGVuZ3RoKHVuaXF1ZShkYXRfY2Vuc3VzJHN0YXJ0X3N0YXRpb25faWQpKQ0KDQpzdHVkeS5wYW5lbCA8LSANCiAgZXhwYW5kLmdyaWQoaW50ZXJ2YWw2MD11bmlxdWUoZGF0X2NlbnN1cyRpbnRlcnZhbDYwKSwgDQogICAgICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSB1bmlxdWUoZGF0X2NlbnN1cyRzdGFydF9zdGF0aW9uX2lkKSkgJT4lDQogIGxlZnRfam9pbiguLCBkYXRfY2Vuc3VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3Qoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfc3RhdGlvbl9uYW1lLCBPcmlnaW4uVHJhY3QsIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlKSU+JQ0KICAgICAgICAgICAgICBkaXN0aW5jdCgpICU+JQ0KICAgICAgICAgICAgICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkKSAlPiUNCiAgICAgICAgICAgICAgc2xpY2UoMSkpDQoNCiNucm93KHN0dWR5LnBhbmVsKSAgICAgIA0KDQpyaWRlLnBhbmVsIDwtIA0KICBkYXRfY2Vuc3VzICU+JQ0KICBtdXRhdGUoVHJpcF9Db3VudGVyID0gMSkgJT4lDQogIHJpZ2h0X2pvaW4oc3R1ZHkucGFuZWwpICU+JSANCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfc3RhdGlvbl9uYW1lLCBPcmlnaW4uVHJhY3QsIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlKSAlPiUNCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBzdW0oVHJpcF9Db3VudGVyLCBuYS5ybT1UKSkgJT4lDQogIGxlZnRfam9pbih3ZWF0aGVyLlBhbmVsKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBmaWx0ZXIoaXMubmEoc3RhcnRfc3RhdGlvbl9pZCkgPT0gRkFMU0UpICU+JQ0KICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUpKSAlPiUNCiAgZmlsdGVyKGlzLm5hKE9yaWdpbi5UcmFjdCkgPT0gRkFMU0UpDQoNCnJpZGUucGFuZWwgPC0gDQogIGxlZnRfam9pbihyaWRlLnBhbmVsLCBkY0NlbnN1cyAlPiUNCiAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWdlb21ldHJ5KSwgYnkgPSBjKCJPcmlnaW4uVHJhY3QiID0gIkdFT0lEIikpDQpgYGANCg0KYGBge3IgbWV0cm9zdG9wcywgcmVzdWx0cz0naGlkZSd9DQptZXRyb1N0b3BzIDwtIHN0X3JlYWQoImh0dHBzOi8vbWFwczIuZGNnaXMuZGMuZ292L2RjZ2lzL3Jlc3Qvc2VydmljZXMvRENHSVNfREFUQS9UcmFuc3BvcnRhdGlvbl9SYWlsX0J1c19XZWJNZXJjYXRvci9NYXBTZXJ2ZXIvNTIvcXVlcnk/b3V0RmllbGRzPSomd2hlcmU9MSUzRDEmZj1nZW9qc29uIikgJT4lIA0KICAgIGRwbHlyOjpzZWxlY3QoTkFNRSwgTElORSkgJT4lDQogIHN0X3RyYW5zZm9ybShjcnM9NDMyNikNCg0KcmlkZS5wYW5lbCA8LQ0KICByaWRlLnBhbmVsICU+JQ0KICBtdXRhdGUoWCA9IGZyb21fbG9uZ2l0dWRlLCBZID0gZnJvbV9sYXRpdHVkZSApJT4lDQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKQ0KDQpyaWRlLnBhbmVsIDwtDQogIHJpZGUucGFuZWwgJT4lDQogIG11dGF0ZSgNCiAgICBzdGF0aW9uLmRpc3QgPSBubl9mdW5jdGlvbihzdF9jb29yZGluYXRlcyhyaWRlLnBhbmVsKSwgc3RfY29vcmRpbmF0ZXMobWV0cm9TdG9wcyksIDEpKSAlPiUNCiAgbXV0YXRlKHN0YXRpb24uY2F0ID0gY2FzZV93aGVuKA0KICAgIHN0YXRpb24uZGlzdCA+IDAgJiBzdGF0aW9uLmRpc3QgPD0gNTAwIH4gIjEiLA0KICAgIHN0YXRpb24uZGlzdCA+IDUwMCAmIHN0YXRpb24uZGlzdCA8PSAxMDAwIH4gIjMiLA0KICAgIHN0YXRpb24uZGlzdCA+IDEwMDAgfiAiMyIpKQ0KDQpzdF9kcm9wX2dlb21ldHJ5KHJpZGUucGFuZWwpDQoNCmBgYA0KDQojIyBEYXRhIEV4cGxvcmF0aW9uDQoNCldlIGJlZ2luIGJ5IGV4YW1pbmluZyB0aGUgdGltZSBhbmQgZnJlcXVlbmN5IGNvbXBvbmVudHMgb2Ygb3VyIGRhdGEuIA0KDQpGaXJzdCwgd2UgbG9vayBhdCB0aGUgb3ZlcmFsbCB0aW1lIHBhdHRlcm4gd2hlcmUgdGhlcmUgaXMgY2xlYXJseSBhIGRhaWx5IGN5Y2xpY2FsIHNwaWtlcyBhbmQgbGVzcyBhY3Rpdml0eSBvbiB3ZWVrZW5kcy4gTm90aWNlIHRoYXQgdGhlIHdlZWtlbmQgbmVhciB0aGUgMjh0aCBvZiBNYXkgKE1lbW9yaWFsIERheSkgZG9lc24ndCBoYXZlIHRoZSBzYW1lIGRpcCBpbiBhY3Rpdml0eS4NCg0KYGBge3IgdHJpcF90aW1lc2VyaWVzIH0NCmdncGxvdChkYXRfY2Vuc3VzICU+JQ0KICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lDQogICAgICAgICB0YWxseSgpKSsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gaW50ZXJ2YWw2MCwgeSA9IG4pKSsNCiAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBwZXIgaHIuLCBXYXNoaW5ndG9uLCBEQywgTWF5IDIwMjEiLA0KICAgICAgIHg9IkRhdGUiLCANCiAgICAgICB5PSJOdW1iZXIgb2YgdHJpcHMiKSsNCiAgcGxvdFRoZW1lKCkNCmBgYA0KDQpIZXJlLCB3ZSBleGFtaW5lIHRoZSBkaXN0cmlidXRpb25zIG9mIHRyaXBzIHBlciBzdGF0aW9uIGF0IGRpZmZlcmVudCB0aW1lcyBvZiB0aGUgZGF5LiBUaGV5IHNob3cgc3Ryb25nIHJpZ2h0IHNrZXdzLCBtZWFuaW5nIHRoYXQgdGhlIG1ham9yaXR5IG9mIHN0YXRpb25zIGhhdmUgc21hbGxlciBwaWNrdXAgbnVtYmVycyBhbmQgYSBtaW5vcml0eSBvZiBzdGF0aW9ucyBleHBlcmllbmNlIGEgZGlzcHJvcG9ydGlvbmF0ZWx5IGhpZ2hlciB2b2x1bWUgb2YgcGlja3Vwcy4gVGhlcmUgYXJlIHN0cm9uZ2VyIHNrZXdzIGZvciBBTSBhbmQgb3Zlcm5pZ2h0IHJpZGVyc2hpcCwgc3VnZ2VzdGluZyB0aGF0IGhpZ2hlciBwaWNrdXAgdm9sdW1lcyBvY2N1ciBkdXJpbmcgbWlkLWRheSBhbmQgdGhlIFBNIHJ1c2ggaG91ci4NCg0KYGBge3IgbWVhbl90cmlwc19oaXN0LCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSB9DQpkYXRfY2Vuc3VzICU+JQ0KICAgICAgICBtdXRhdGUodGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpJT4lDQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX25hbWUsIHRpbWVfb2ZfZGF5KSAlPiUNCiAgICAgICAgIHRhbGx5KCklPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9uYW1lLCB0aW1lX29mX2RheSklPiUNCiAgc3VtbWFyaXplKG1lYW5fdHJpcHMgPSBtZWFuKG4pKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG1lYW5fdHJpcHMpLCBiaW53aWR0aCA9IDEpKw0KICBsYWJzKHRpdGxlPSJNZWFuICMgb2YgSG91cmx5IFRyaXBzIFBlciBTdGF0aW9uLCBXYXNoaW5ndG9uIERDLCBNYXkgMjAyMSIsDQogICAgICAgeD0iTnVtYmVyIG9mIHRyaXBzIiwgDQogICAgICAgeT0iRnJlcXVlbmN5IikrDQogIGZhY2V0X3dyYXAofnRpbWVfb2ZfZGF5KSsNCiAgcGxvdFRoZW1lKCkNCmBgYA0KDQpXZSBhbHNvIGV4YW1pbmUgcmlkZXJzaGlwIGJ5IGRheSBvZiB0aGUgd2VlayBhbmQgd2Vla2VuZCB2cy4gd2Vla2RheS4gVGhlcmUgYXBwZWFycyB0byBiZSBsYXJnZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGRheXMgb2YgdGhlIHdlZWssIGVzcGVjaWFsbHkgYmV0d2VlbiB3ZWVrZW5kcyBhbmQgd2Vla2RheXMuIE5vdGFibHksIHdlZWtlbmRzIGhhdmUgdGhlaXIgc3Bpa2UgY29uc2lzdGVudGx5IGFyb3VuZCBsYXRlIG1vcm5pbmcgdG8gbWlkZGF5IChob3VycyAxMS0xNiksIHdoaWxlIHdlZWtkYXlzIGhhdmUgYW4gY29uc2lzdGVudCBzcGlrZSBhZnRlciA1cG0gKGhvdXJzIDE3LTE4KS4gQmVjYXVzZSBvZiB0aGUgb3ZlcmFsbCB2YXJpYXRpb24gYWNyb3NzIGRheXMgb2YgdGhlIHdlZWssIHdlIHdpbGwgZW1wbG95IGl0IGFzIGEgdmFyaWFibGUgaW4gb3VyIG1vZGVsLg0KDQpgYGB7ciB0cmlwc19ob3VyX2RvdHd9DQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUgbXV0YXRlKGhvdXIgPSBob3VyKHN0YXJ0ZWRfYXQpKSkrDQogICAgIGdlb21fZnJlcXBvbHkoYWVzKGhvdXIsIGNvbG9yID0gZG90dyksIGJpbndpZHRoID0gMSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgaW4gREMsIGJ5IGRheSBvZiB0aGUgd2VlaywgTWF5IDIwMjEiLA0KICAgICAgIHg9IkhvdXIiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIpKw0KICAgICBwbG90VGhlbWUoKQ0KDQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUgDQogICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIoc3RhcnRlZF9hdCksDQogICAgICAgICAgICAgICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IikpKSsNCiAgICAgZ2VvbV9mcmVxcG9seShhZXMoaG91ciwgY29sb3IgPSB3ZWVrZW5kKSwgYmlud2lkdGggPSAxKSsNCiAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBpbiBEQyAtIHdlZWtlbmQgdnMgd2Vla2RheSwgTWF5IDIwMjEiLA0KICAgICAgIHg9IkhvdXIiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIpKw0KICAgICBwbG90VGhlbWUoKQ0KYGBgDQpUaGUgbWFwIHNlcmllcyBiZWxvdyBzaG93cyB3aGljaCBiaWtlIHN0YXRpb25zIGFyZSB1c2VkIG1vcmUgb2Z0ZW4gdGhyb3VnaG91dCB0aGUgZGF5IG9mIHRoZSB3ZWVrIGFuZCB0aW1lIG9mIGRheS4gTWFqb3JpdHkgb2YgdGhlc2UgYXJlYXMgYXJlIHllbGxvdyByZXByZXNlbnRpbmcgbGl0dGxlIHJpZGVyc2hpcCwgYnV0IHdoZW4gcmVsYXRpbmcgYmFjayB0byB0aGUgc3Bpa2VzIG9uIHRoZSB3ZWVrZW5kcyBhbmQgd2Vla2RheXMsIHdlIHNlZSB3aGVyZSB0aGVzZSBzcGlrZXMgYXJlIG9jY3VycmluZy4gTWlkLWRheSBzcGlrZXMgb24gdGhlIHdlZWtkYXkgc2hvdyByZWQgZG90cyBjbG9zZXIgdG8gdGhlIGNlbnRyYWwgcGFydCBvZiBEQyB3aGlsZSB0aGUgd2Vla2VuZHMgcHJlc2VudHMgc2xpZ2h0bHkgbW9yZSBhY3Rpdml0eSBvbiB0aGUgc291dGhlcm4gc2lkZSB0aHJvdWdob3V0IHRoZSBkYXkuIA0KDQpgYGB7ciBvcmlnaW5fbWFwLCBmaWcuaGVpZ2h0PTd9DQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gZGNUcmFjdHMgJT4lDQogICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSwNCiAgICAgICAgICBjb2xvciA9ICJncmF5ODAiLA0KICAgICAgICAgIGZpbGwgPSAiZ3JheTUwIikrDQogIGdlb21fcG9pbnQoZGF0YSA9IGRhdF9jZW5zdXMgJT4lIA0KICAgICAgICAgICAgbXV0YXRlKGhvdXIgPSBob3VyKHN0YXJ0ZWRfYXQpLA0KICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLA0KICAgICAgICAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSU+JQ0KICAgICAgICAgICAgICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkLCBmcm9tX2xhdGl0dWRlLCBmcm9tX2xvbmdpdHVkZSwgd2Vla2VuZCwgdGltZV9vZl9kYXkpICU+JQ0KICAgICAgICAgICAgICB0YWxseSgpLA0KICAgICAgICAgICAgYWVzKHg9ZnJvbV9sb25naXR1ZGUsIHkgPSBmcm9tX2xhdGl0dWRlLCBjb2xvciA9IG4pLCANCiAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBhbHBoYSA9IDAuNywgc2l6ZSA9IDAuOCkrDQogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLCBlbmQgPSAwLjk1LA0KICAgICAgICAgICAgICAgICAgICAgICBkaXNjcmV0ZSA9IEZBTFNFLCBvcHRpb24gPSAiQiIpKw0KICB5bGltKG1pbihkYXRfY2Vuc3VzJGZyb21fbGF0aXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSkrDQogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKw0KICBmYWNldF9ncmlkKHdlZWtlbmQgfiB0aW1lX29mX2RheSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyIGJ5IHN0YXRpb24sIFdhc2hpbmd0b24sIERDLCBNYXkgMjAyMSIsKSsNCiAgbWFwVGhlbWUoKQ0KYGBgDQoNCg0KIyMgR2VuZXJhdGUgdGltZSBsYWdzDQoNCkNyZWF0aW5nIHRpbWUgbGFnIHZhcmlhYmxlcyBwcm92aWRlcyBhZGRpdGlvbmFsIGluc2lnaHRzIGludG8gcmlkZXNoYXJlIGRlbWFuZCBieSBjYXB0dXJpbmcgdmFyaWF0aW9ucyBpbiB1c2FnZSBob3VycyBiZWZvcmUgYW5kIGR1cmluZyBhIGdpdmVuIGRheS4gQW5hbHl6aW5nIHJpZGUgdXNhZ2UgaW4gaG91cnMgYmVmb3JlIGFuZCBhZnRlciBjYW4gZm9yZWNhc3QgZnV0dXJlIGFjdGl2aXR5IGF0IHJpZGVzaGFyZSBzdGF0aW9ucy4gQWRkaXRpb25hbGx5LCBpdCdzIGltcG9ydGFudCB0byBmYWN0b3IgaW4gaG9saWRheSBlZmZlY3RzIHRoYXQgZGlzcnVwdCBleHBlY3RlZCBkZW1hbmQgcGF0dGVybnMgb24gd2Vla2VuZHMgb3Igd2Vla2RheXMsIHRyZWF0aW5nIHRoZW0gYXMgb3V0bGllcnMuIEZvciBNYXksIHdlIGNhbiB1c2UgTWVtb3JpYWwgRGF5IG9uIE1heSAyOCBhcyBhIHRocmVlLWRheSB3ZWVrZW5kIGFuZCBleGFtaW5lIHRoZSB0ZW1wb3JhbCBwcm94aW1pdHkgdG8gdGhlIGhvbGlkYXkuIFRoZSBjb3JyZWxhdGlvbnMgaW4gdGhlc2UgdGltZSBsYWdzIGFyZSBub3RhYmx5IHN0cm9uZywgd2l0aCBhIFBlYXJzb24ncyBSIG9mIDAuOTIgZm9yIHRoZSBsYWdIb3VyLCBpbmRpY2F0aW5nIGEgbmVhcmx5IDE6MSBjb3JyZWxhdGlvbi4gVGhpcyBoaWdoIGNvcnJlbGF0aW9uIHNlcnZlcyBhcyBwcmFjdGljYWwgZXZpZGVuY2UgbGlua2luZyByaWRlcnNoaXAgdXNhZ2UgYW5kIGhvbGlkYXlzLiANCg0KSXQgYWxpZ25zIHdpdGggdGhlIGludHVpdGl2ZSB1bmRlcnN0YW5kaW5nIHRoYXQgY3VycmVudCBkZW1hbmQgc2hvdWxkIHJlc2VtYmxlIHRvbW9ycm93J3MgYW5kIGFuIGhvdXIgZnJvbSBub3csIGJ1dCBleHBlY3RhdGlvbnMgZXF1YWxseSB2YXJ5IHR3ZWx2ZSBob3VycyBpbnRvIHRoZSBmdXR1cmUgaW4gdGVybXMgb2YgZGVtYW5kLCB3aGVyZSB3ZSBzZWUgYSB2YWx1ZSBvZiAtMC41OSwgc2hvd2luZyB1c2FnZSAxMiBob3VycyBhZnRlciBhIG1lYXN1cmVtZW50IHdpbGwgaGF2ZSBhbiBvcHBvc2l0ZSBlZmZlY3QuIA0KDQoNCmBgYHtyIHRpbWVfbGFncyAsIG1lc3NhZ2UgPSBGQUxTRX0NCnJpZGUucGFuZWwgPC0gDQogIHJpZGUucGFuZWwgJT4lIA0KICBhcnJhbmdlKHN0YXJ0X3N0YXRpb25faWQsIGludGVydmFsNjApICU+JSANCiAgbXV0YXRlKGxhZ0hvdXIgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMSksDQogICAgICAgICBsYWcySG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMiksDQogICAgICAgICBsYWczSG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMyksDQogICAgICAgICBsYWc0SG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsNCksDQogICAgICAgICBsYWcxMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEyKSwNCiAgICAgICAgIGxhZzFkYXkgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMjQpLA0KICAgICAgICAgaG9saWRheSA9IGlmZWxzZSh5ZGF5KGludGVydmFsNjApID09IDE0OCwxLDApKSAlPiUNCiAgIG11dGF0ZShkYXkgPSB5ZGF5KGludGVydmFsNjApKSAlPiUNCiAgIG11dGF0ZShob2xpZGF5TGFnID0gY2FzZV93aGVuKGRwbHlyOjpsYWcoaG9saWRheSwgMSkgPT0gMSB+ICJQbHVzT25lRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsYWcoaG9saWRheSwgMikgPT0gMSB+ICJQbHVzVHdvRGF5cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGFnKGhvbGlkYXksIDMpID09IDEgfiAiUGx1c1RocmVlRGF5cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAxKSA9PSAxIH4gIk1pbnVzT25lRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDIpID09IDEgfiAiTWludXNUd29EYXlzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDMpID09IDEgfiAiTWludXNUaHJlZURheXMiKSwNCiAgICAgICAgIGhvbGlkYXlMYWcgPSBpZmVsc2UoaXMubmEoaG9saWRheUxhZykgPT0gVFJVRSwgMCwgaG9saWRheUxhZykpDQoNCmBgYA0KDQpgYGB7ciBldmFsdWF0ZV9sYWdzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQphcy5kYXRhLmZyYW1lKHJpZGUucGFuZWwpICU+JQ0KICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JSANCiAgICBzdW1tYXJpc2VfYXQodmFycyhzdGFydHNfd2l0aCgibGFnIiksICJUcmlwX0NvdW50IiksIG1lYW4sIG5hLnJtID0gVFJVRSkgJT4lDQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWludGVydmFsNjAsIC1UcmlwX0NvdW50KSAlPiUNCiAgICBtdXRhdGUoVmFyaWFibGUgPSBmYWN0b3IoVmFyaWFibGUsIGxldmVscz1jKCJsYWdIb3VyIiwibGFnMkhvdXJzIiwibGFnM0hvdXJzIiwibGFnNEhvdXJzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsYWcxMkhvdXJzIiwibGFnMWRheSIpKSklPiUNCiAgICBncm91cF9ieShWYXJpYWJsZSkgJT4lICANCiAgICBzdW1tYXJpemUoY29ycmVsYXRpb24gPSByb3VuZChjb3IoVmFsdWUsIFRyaXBfQ291bnQpLDIpKQ0KYGBgDQoNCiMjIEFuaW1hdGVkIG1hcA0KDQpXZSBzZWxlY3QgYmlrZXNoYXJlIHRyaXBzIG9uIE1heSAxOHRoIGZyb20gdGhlIGRhdGFzZXQgdG8gdmlzdWFsaXplIGluIHRoZSBmb2xsb3dpbmcgYW5pbWF0ZWQgbWFwLiBXZSBjaG9zZSB0aGlzIGRhdGUgYXMgYSByZWxhdGl2ZWx5IHJlcHJlc2VudGF0aXZlIGRhdGUgb24gd2hpY2ggdGhlcmUgd2FzIG5vIHByZWNpcGl0YXRpb24gYW5kIG1vZGVyYXRlIHRlbXBlcmF0dXJlcyAoYSBoaWdoIG9mIDc4IEYpLiBNYWpvcml0eSBvZiBiaWtlIHRyaXBzIHRocm91Z2hvdXQgdGhlIGRheSByZW1haW4gaW4gdGhlIGNlbnRlciBvZiBEQyB3aXRoIGZyZXF1ZW50IGxvbmUgcG9pbnRzIG9uIHRoZSBlZGdlcyBvZiB0aGUgbWFwLiANCg0KYGBge3IgYW5pbWF0ZWQgbWFwLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCndlZWsyMCA8LSBkYXRfY2Vuc3VzICU+JQ0KICBmaWx0ZXIod2VlayA9PSAyMCAmIGRvdHcgPT0gIlR1ZSIpDQoNCndlZWsyMC5wYW5lbCA8LQ0KICBleHBhbmQuZ3JpZCgNCiAgICBpbnRlcnZhbDE1ID0gdW5pcXVlKHdlZWsyMCRpbnRlcnZhbDE1KSwNCiAgICBzdGF0aW9uID0gdW5pcXVlKGRhdF9jZW5zdXMkc3RhcnRfc3RhdGlvbl9pZCkpDQoNCnJpZGUuYW5pbWF0aW9uLmRhdGEgPC0NCiAgbXV0YXRlKHdlZWsyMCwgVHJpcF9Db3VudGVyID0gMSkgJT4lDQogIHNlbGVjdChpbnRlcnZhbDE1LCBzdGFydF9zdGF0aW9uX2lkLCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSwgVHJpcF9Db3VudGVyKSAlPiUNCiAgZ3JvdXBfYnkoaW50ZXJ2YWwxNSwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUsIHN0YXJ0X3N0YXRpb25faWQpICU+JQ0KICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtPVQpKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIG11dGF0ZShUcmlwcyA9IGNhc2Vfd2hlbihUcmlwX0NvdW50ID09IDAgfiAiMCB0cmlwcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMCAmIFRyaXBfQ291bnQgPD0gMSB+ICIxIHRyaXAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDEgJiBUcmlwX0NvdW50IDw9IDIgfiAiMiB0cmlwcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMiAmIFRyaXBfQ291bnQgPD0gNSB+ICIzLTUgdHJpcHMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDUgJiBUcmlwX0NvdW50IDw9IDEwIH4gIjYtMTAgdHJpcHMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDEwIH4gIj4xMCB0cmlwcyIpKSAlPiUNCiAgbXV0YXRlKFRyaXBzICA9IGZjdF9yZWxldmVsKFRyaXBzLCAiMCB0cmlwcyIsIjEgdHJpcCIsIjIgdHJpcHMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjMtNSB0cmlwcyIsIjYtMTAgdHJpcHMiLCI+MTAgdHJpcHMiKSkNCg0KcmlkZXNoYXJlX2FuaW1hdGlvbiA8LQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gZGNUcmFjdHMgJT4lDQogICAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBjb2xvciA9ICdncmF5ODAnLCBmaWxsID0gImdyYXk1MCIpKw0KICBnZW9tX3BvaW50KGRhdGEgPSByaWRlLmFuaW1hdGlvbi5kYXRhLCANCiAgICAgICAgICAgICBhZXMoeCA9IGZyb21fbG9uZ2l0dWRlLCB5ID0gZnJvbV9sYXRpdHVkZSwgY29sb3IgPSBUcmlwcyksIA0KICAgICAgICAgICAgIHNpemUgPSAxLjUsIA0KICAgICAgICAgICAgIGFscGhhID0gMS41KSArDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsNCiAgbGFicyh0aXRsZSA9ICJCaWtlc2hhcmUgdHJpcHMgb24gTWF5IDE4dGgsIDIwMjEsIFdhc2hpbmd0b24sIERDIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJUaW1lOiB7Y3VycmVudF9mcmFtZX0iKSArDQogIHRyYW5zaXRpb25fbWFudWFsKGludGVydmFsMTUpICsNCiAgbWFwVGhlbWUoKQ0KDQphbmltYXRlKHJpZGVzaGFyZV9hbmltYXRpb24sIGR1cmF0aW9uPTQ1LCByZW5kZXJlciA9IGdpZnNraV9yZW5kZXJlcigpKQ0KYGBgDQoNCiMgTW9kZWxpbmcNCg0KSW4gb3JkZXIgdG8gdHJhaW4gYW5kIHRlc3Qgb3VyIHByZWRpY3Rpb24gbW9kZWwsIHdlIGZpcnN0IHNwbGl0IG91ciBkYXRhIGludG8gYSB0cmFpbmluZyBhbmQgYSB0ZXN0IHNldC4gVGhlbiwgd2UgYnVpbGQgZml2ZSBsaW5lYXIgbW9kZWxzIHVzaW5nIG91ciB0cmFpbmluZyBkYXRhIHRvIHByZWRpY3QgdHJpcCBjb3VudHMuIFRoZSBtb2RlbHMnIGRlcGVuZGVudCB2YXJpYWJsZXMgYXJlIGFzIGZvbGxvd3M6DQoNCjEuIEhvdXIgb2YgZGF5LCBkYXkgb2Ygd2VlaywgdGVtcGVyYXR1cmUNCjIuIFBpY2t1cCBzdGF0aW9uLCBkYXkgb2Ygd2VlaywgdGVtcGVyYXR1cmUNCjMuIFZhcmlhYmxlcyBpbiBtb2RlbHMgMSBhbmQgMiArIHByZWNpcGl0YXRpb24NCjQuIFZhcmlhYmxlcyBpbiBtb2RlbCAzICsgdGltZSBsYWdzDQo1LiBWYXJpYWJsZXMgaW4gbW9kZWwgNCArIGRpc3RhbmNlIGZyb20gTWV0cm8gc3RhdGlvbg0KDQoNCmBgYHtyIHRyYWluX3Rlc3QgLSByZWdyZXNzaW9uc30NCnJpZGUuVHJhaW4gPC0gZmlsdGVyKHJpZGUucGFuZWwsIHdlZWsgPj0gMjApDQpyaWRlLlRlc3QgPC0gZmlsdGVyKHJpZGUucGFuZWwsIHdlZWsgPCAyMCkNCg0KcmVnMSA8LSANCiAgbG0oVHJpcF9Db3VudCB+ICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlLCAgZGF0YT1yaWRlLlRyYWluKQ0KDQpyZWcyIDwtIA0KICBsbShUcmlwX0NvdW50IH4gIHN0YXJ0X3N0YXRpb25fbmFtZSArIGRvdHcgKyBUZW1wZXJhdHVyZSwgIGRhdGE9cmlkZS5UcmFpbikNCg0KcmVnMyA8LSANCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiwgDQogICAgIGRhdGE9cmlkZS5UcmFpbikNCg0KcmVnNCA8LSANCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyAgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24gKw0KICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyArIGxhZzEySG91cnMgKyBsYWcxZGF5LCANCiAgICAgZGF0YT1yaWRlLlRyYWluKQ0KDQpyZWc1IDwtIA0KICBsbShUcmlwX0NvdW50IH4gIHN0YXJ0X3N0YXRpb25fbmFtZSArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsNCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgK2xhZzEySG91cnMgKyBsYWcxZGF5ICsgc3RhdGlvbi5kaXN0LCANCiAgICAgZGF0YT1yaWRlLlRyYWluKQ0KYGBgDQoNCiMjIDQuMi4gUHJlZGljdCBmb3IgdGVzdCBkYXRhDQoNCk5leHQsIHdlIHVzZSBvdXIgbW9kZWxzIHRvIHByZWRpY3QgdHJpcCBjb3VudHMgb24gdGhlIHRlc3QgZGF0YS4gDQoNCmBgYHtyIG5lc3QgZGF0YSAtIGdlbmVyYXRlIHByZWRpY3Rpb25zLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCnJpZGUuVGVzdC53ZWVrTmVzdCA8LSANCiAgcmlkZS5UZXN0ICU+JQ0KICBuZXN0KC13ZWVrKSANCg0KbW9kZWxfcHJlZCA8LSBmdW5jdGlvbihkYXQsIGZpdCl7DQogICBwcmVkIDwtIHByZWRpY3QoZml0LCBuZXdkYXRhID0gZGF0KX0NCg0Kd2Vla19wcmVkaWN0aW9ucyA8LSANCiAgcmlkZS5UZXN0LndlZWtOZXN0ICU+JSANCiAgICBtdXRhdGUoTW9kZWwxX1RpbWVfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcxLCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgICAgICAgICBNb2RlbDJfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcyLCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgICAgICAgICBNb2RlbDNfVGltZV9TcGFjZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzMsIC5mID0gbW9kZWxfcHJlZCksDQogICAgICAgICAgIE1vZGVsNF9UaW1lX1NwYWNlX0ZFX3RpbWVMYWdzID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNCwgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICAgICAgICAgTW9kZWw1X0Z1bGwgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWc1LCAuZiA9IG1vZGVsX3ByZWQpKSAlPiUgDQogICAgZ2F0aGVyKFJlZ3Jlc3Npb24sIFByZWRpY3Rpb24sIC1kYXRhLCAtd2VlaykgJT4lDQogICAgbXV0YXRlKE9ic2VydmVkID0gbWFwKGRhdGEsIHB1bGwsIFRyaXBfQ291bnQpLA0KICAgICAgICAgICBBYnNvbHV0ZV9FcnJvciA9IG1hcDIoT2JzZXJ2ZWQsIFByZWRpY3Rpb24sICB+IGFicygueCAtIC55KSksDQogICAgICAgICAgIE1BRSA9IG1hcF9kYmwoQWJzb2x1dGVfRXJyb3IsIG1lYW4sIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgIHNkX0FFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgc2QsIG5hLnJtID0gVFJVRSkpDQoNCndlZWtfcHJlZGljdGlvbnMNCmBgYA0KDQojIyBFeGFtaW5pbmcgTW9kZWwgRXJyb3JzDQoNCk92ZXJhbGwsIG91ciBtb2RlbHMgYXJlIGFjY3VyYXRlIHRvIGxlc3MgdGhhbiBhbiBhdmVyYWdlIG9mIG9uZSByaWRlIHBlciBob3VyLiBUaGUgYmVzdCBwZXJmb3JtaW5nIG1vZGVscyBhcmUgdGhvc2UgdGhhdCBpbmNvcnBvcmF0ZSB0aW1lIGxhZ3MsIHdpdGggYSBuZWdsaWdpYmxlIGltcHJvdmVtZW50IGZyb20gTW9kZWwgNCB0byBNb2RlbCA1LiANCg0KYGBge3IgcGxvdF9lcnJvcnNfYnlfbW9kZWwgfQ0Kd2Vla19wcmVkaWN0aW9ucyAlPiUNCiAgZHBseXI6OnNlbGVjdCh3ZWVrLCBSZWdyZXNzaW9uLCBNQUUpICU+JQ0KICBnYXRoZXIoVmFyaWFibGUsIE1BRSwgLVJlZ3Jlc3Npb24sIC13ZWVrKSAlPiUNCiAgZ2dwbG90KGFlcyh3ZWVrLCBNQUUpKSArIA0KICAgIGdlb21fYmFyKGFlcyhmaWxsID0gUmVncmVzc2lvbiksIHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdD0iaWRlbnRpdHkiKSArDQogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsNCiAgICBsYWJzKHRpdGxlID0gIk1lYW4gQWJzb2x1dGUgRXJyb3JzIGJ5IG1vZGVsIHNwZWNpZmljYXRpb24gYW5kIHdlZWsiKSArDQogIHBsb3RUaGVtZSgpDQpgYGANCg0KQWZ0ZXIgcGxvdHRpbmcgcHJlZGljdGVkIHZzLiBvYnNlcnZlZCBwaWNrdXBzLCB3ZSBvYnNlcnZlIHRoYXQgdGhlIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gcHJlZGljdGVkIGFuZCBvYnNlcnZlZCB2YWx1ZXMgaXMgc3Ryb25nZXN0IGluIG1vZGVscyA0IGFuZCA1LCB3aGljaCBpcyBjb25zaXN0ZW50IHdpdGggdGhlIHByZXZpb3VzIG9ic2VydmF0aW9ucy4gRWFjaCBtb2RlbCBkb2VzIHNvbWV3aGF0IG9mIGEgZGVjZW50IGpvYiBwcmVkaWN0aW5nIHRoZSBzcGlrZSBzZWVuIGF0IHRoZSBlbmQgb2ZmIHdvcmstaG91cnMgb24gd2Vla2RheXMsIGJ1dCB3aGF0IG1ha2VzIG1vZGVscyA0IGFuZCA1IGJlc3Qgc3VpdGVkIGlzIGJlY2F1c2UgdGhleSBhcmUgYWJsZSB0byBtZWFzdXJlIHRoYXQgc3Bpa2UuIFRoZSBwcmVkaWN0ZWQgb3V0Y29tZXMgZm9yIDEsMiwgYW5kIDMgcmVmbGVjdCBzaW1pbGFybHkgdG8gb3VyIG9ic2VydmF0aW9ucyBvbiB3ZWVrZW5kcywgd2hlcmUgdGhlIHBlYWsgd2FzIGdyYWR1YWwsIGxvbmdlciwgYW5kIG1vcmUgY29uc2lzdGVudC4gDQoNCmBgYHtyIGVycm9yX3ZzX2FjdHVhbF90aW1lc2VyaWVzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGZpZy5oZWlnaHQ9OH0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb25faWQpKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uKSAlPiUNCiAgICB1bm5lc3QoKSAlPiUNCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtUmVncmVzc2lvbiwgLWludGVydmFsNjAsIC1zdGFydF9zdGF0aW9uX2lkKSAlPiUNCiAgICBncm91cF9ieShSZWdyZXNzaW9uLCBWYXJpYWJsZSwgaW50ZXJ2YWw2MCkgJT4lDQogICAgc3VtbWFyaXplKFZhbHVlID0gc3VtKFZhbHVlKSkgJT4lDQogICAgZ2dwbG90KGFlcyhpbnRlcnZhbDYwLCBWYWx1ZSwgY29sb3VyPVZhcmlhYmxlKSkgKyANCiAgICAgIGdlb21fbGluZShzaXplID0gMS4xKSArIA0KICAgICAgZmFjZXRfd3JhcCh+UmVncmVzc2lvbiwgbmNvbD0xKSArDQogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBiaWtlc2hhcmUgcGlja3VwIHRpbWUgc2VyaWVzIiwgc3VidGl0bGUgPSAiV2FzaGluZ3RvbiBEQzsgQSB0ZXN0IHNldCBvZiAyIHdlZWtzIiwgIHggPSAiSG91ciIsIHk9ICJTdGF0aW9uIFRyaXBzIikgKw0KICAgICAgcGxvdFRoZW1lKCkNCmBgYA0KDQpGdXJ0aGVyIGV4YW1pbmluZyBNb2RlbCA1LCBvdXIgYmVzdCBzdWl0ZWQsIHdlIG9ic2VydmUgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSB2YWx1ZXMgd2hlbiBtYXBwZWQgYWNyb3NzIHN0YXRpb25zLiBOb3RhYmx5LCBlcnJvcnMgYXJlIG1vcmUgcHJvbm91bmNlZCBmb3Igc3RhdGlvbnMgaW4gY2xvc2UgcHJveGltaXR5IHRvIGRvd250b3duIERDIGFuZCB0aGUgTmF0aW9uYWwgTWFsbC4gT3VyIG5leHQgc3RlcCBpbnZvbHZlcyBhIHRob3JvdWdoIGV4YW1pbmF0aW9uIG9mIHNwYWNlLXRpbWUgZXJyb3JzIHRvIGdhaW4gZnVydGhlciBpbnNpZ2h0cy4gTGlrZSBhbGwgdGhlIG90aGVyIG1vZGVscywgaXQgdW5kZXJwZXJmb3JtcyBwZXJkaWN0aW9ucyB3aGVyZSB0aGUgc3Bpa2VzIHBvc3Qtd29ya2RheSBvY2N1ciwgYnV0IGNhbiBhY2N1cmF0ZWx5IHByZWRpY3Qgb3RoZXIgdGltZXMgb2YgdGhlIGRheS4gVGhlIE1BRSB3b3VsZCBoYXZlIGJlZW4gZXhhY2VyYmF0ZWQgaWYgdXNpbmcgYW55IG90aGVyIG1vZGVsLg0KDQpgYGB7ciBlcnJvcnNfYnlfc3RhdGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQ0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgDQogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksDQogICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbl9pZCksIA0KICAgICAgICAgICBmcm9tX2xhdGl0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbGF0aXR1ZGUpLCANCiAgICAgICAgICAgZnJvbV9sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sb25naXR1ZGUpKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uKSAlPiUNCiAgICB1bm5lc3QoKSAlPiUNCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIk1vZGVsNV9GdWxsIikgJT4lDQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25faWQsIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlKSAlPiUNCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQ0KZ2dwbG90KC4pKw0KICBnZW9tX3NmKGRhdGEgPSBkY0NlbnN1cywgY29sb3IgPSAiZ3JheTgwIiwgZmlsbCA9ICJncmF5NTAiKSsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IGZyb21fbG9uZ2l0dWRlLCB5ID0gZnJvbV9sYXRpdHVkZSwgY29sb3IgPSBNQUUpLCANCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IiwgYWxwaGEgPSAwLjcsIHNpemUgPSAwLjgpKw0KICBzY2FsZV9jb2xvdXJfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwNCiAgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiKSsNCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpKw0KICB4bGltKG1pbihkYXRfY2Vuc3VzJGZyb21fbG9uZ2l0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpKSsNCiAgbGFicyh0aXRsZT0iTWVhbiBBYnMgRXJyb3IsIFRlc3QgU2V0LCBNb2RlbCA1IikrDQogIG1hcFRoZW1lKCkNCmBgYA0KDQojIyMgU3BhY2UtVGltZSBFcnJvciBFdmFsdWF0aW9uDQoNCldoZW4gY29udHJhc3RpbmcgcHJlZGljdGVkIHZzIG9ic2VydmVkIHRoZSBwYXR0ZXJuIGdvZXMgYmV5b25kIHVuZGVyIHBlcmZvcm1pbmcgdGhlIHNwaWtlIGJ1dCBhbHNvIG92ZXJwcmVkaWN0aW9ucyBpbiB0aGUgb3Zlcm5pZ2h0LiBMb29raW5nIGF0IHRoZSBncmFwaCwgd2Ugc2VlIHRoYXQgcHJlZGljdGluZyB0cmlwcyBoYXZlIGEgaGVhdnkgbGVhbiB0b3dhcmRzIHRoZSBsZWZ0IHNpZGUsIGluZGljYXRpbmcgdGhhdCB0aGVyZSBpcyBwcmVkaWN0aW9ucyBvZiBtb3JlIHRyaXBzICBoYXBwZW5pbmcgb3Zlcm5pZ2h0IHdoZW4gdGhlIG9ic2VydmVkIG91dGNvbWVzIGFyZSBzbWFsbGVyLiBUaGUgbW9kZWwgaXMgbm90IGNhcGFibGUgb2YgZXhwbGFpbmluZyB0aGUgZGF5LW5pZ2h0IHBhdHRlcm5zIG9mIHBlb3BsZSB0aGF0IGFyZSByZWZsZWN0ZWQgaW4gYmlrZXNoYXJlIGFjdGl2aXR5LiANCg0KDQpgYGB7ciBvYnNfcHJlZF9hbGwsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGU9VFJVRX0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb25faWQpLCANCiAgICAgICAgICAgZnJvbV9sYXRpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xhdGl0dWRlKSwgDQogICAgICAgICAgIGZyb21fbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbG9uZ2l0dWRlKSwNCiAgICAgICAgICAgZG90dyA9IG1hcChkYXRhLCBwdWxsLCBkb3R3KSkgJT4lDQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIGZyb21fbG9uZ2l0dWRlLCANCiAgICAgICAgICAgZnJvbV9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sDQogICAgICAgICAgIGRvdHcpICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiTW9kZWw1X0Z1bGwiKSU+JQ0KICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksDQogICAgICAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fcG9pbnQoYWVzKHg9IE9ic2VydmVkLCB5ID0gUHJlZGljdGlvbikpKw0KICAgIGdlb21fc21vb3RoKGFlcyh4PSBPYnNlcnZlZCwgeT0gUHJlZGljdGlvbiksIG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGNvbG9yID0gInJlZCIpKw0KICAgIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCkrDQogIGZhY2V0X2dyaWQodGltZV9vZl9kYXl+d2Vla2VuZCkrDQogIGxhYnModGl0bGU9Ik9ic2VydmVkIHZzIFByZWRpY3RlZCIsDQogICAgICAgeD0iT2JzZXJ2ZWQgdHJpcHMiLCANCiAgICAgICB5PSJQcmVkaWN0ZWQgdHJpcHMiKSsNCiAgcGxvdFRoZW1lKCkNCmBgYA0KDQpXZSBhbHNvIG5lZWQgdG8gbG9vayBhdCB0aGVzZSBlcnJvcnMgb2YgcHJlZGljdGlvbnMgdmVyc3VzIG9ic2VydmF0aW9ucyBzcGF0aWFsbHksIHRoZSBzcGlrZXMgdGhhdCB0aGUgbW9kZWwgbWlzc2VkIGFyZSBtYXJrZWQgYXMgdGhlIGhpZ2hlciBNQUUgdmFsdWVzIGluIHRoZSBOYXRpb25hbCBNYWxsIGFyZWEgZHVyaW5nIG1pZGRheSBhbmQgUE0gcnVzaGVzLiBXZSB0aGVuIGxvb2sgYXQgdGhlIG92ZXJuaWdodCB3aGVyZSBpdCBhbHNvIGhhcyBoaWdoZXIgTUFFIGluIHRoYXQgc2FtZSBjZW50ZXIsIHdoZXJlIGl0IGlzIHByZWRpY3Rpbmcgc21hbGwgYnV0IGNvbnNpc3RlbnQgcmlkZSB1c2FnZSB0aHJvdWdob3V0IHRoZSBuaWdodC4gVGhlIG1vZGVsIHRlbmRzIHRvIHRha2UgYSBtb3JlIGNvbnNlcnZhdGl2ZSBhcHByb2FjaCB0aHJvdWdob3V0IHRoZSBjeWNsaWNhbCBkYXksIHVuZGVyIHBlcmZvcm1pbmcgYnVzaW5lc3MgYW5kIG92ZXIgcGVyZm9ybWluZyBkb3dudGltZS4gVGhlIG1vZGVsIGNhbm5vdCBleHBsYWluIHRoZXNlIGRpc2NyZXBhbmNpZXMgYWxvbmUsIGFuZCB0aGVyZSBtYXkgYmUgZmFjdG9ycyBub3QgcmVsYXRlZCB0byBiaWtlc2hhcmUgc3BlY2lmaWNhbGx5IHRoYXQgIGNhbiBzaG93IGNvcnJlbGF0aW9ucyBpbiBpdHMgcHJlZGljdGlvbnMuIEZvciB0aGlzIGFuYWx5c2lzIHdlIHdpbGwgbG9vayBkZWVwZXIgaW50byB0aGUgQU0gcnVzaCBhbmQgdGhlIGF0dHJpYnV0ZXMgZXJyb3JzIGluIHRlcm1zIG9mIHNvY2lvLWVjb25vbWljIGZhY3RvcnMuIA0KDQpgYGB7ciBzdGF0aW9uX3N1bW1hcnksIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLmhlaWdodD03IH0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb25faWQpLCANCiAgICAgICAgICAgZnJvbV9sYXRpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xhdGl0dWRlKSwgDQogICAgICAgICAgIGZyb21fbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbG9uZ2l0dWRlKSwNCiAgICAgICAgICAgZG90dyA9IG1hcChkYXRhLCBwdWxsLCBkb3R3KSApICU+JQ0KICAgIHNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX2lkLCBmcm9tX2xvbmdpdHVkZSwgDQogICAgICAgICAgIGZyb21fbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLA0KICAgICAgICAgICBkb3R3KSAlPiUNCiAgICB1bm5lc3QoKSAlPiUNCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIk1vZGVsNV9GdWxsIiklPiUNCiAgbXV0YXRlKHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLA0KICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQ0KICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkLCB3ZWVrZW5kLCB0aW1lX29mX2RheSwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUpICU+JQ0KICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lDQogIGdncGxvdCguKSsNCiAgZ2VvbV9zZihkYXRhID0gZGNDZW5zdXMsIGNvbG9yID0gIndoaXRlIiwgZmlsbCA9ICJncmF5ODAiKSsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IGZyb21fbG9uZ2l0dWRlLCB5ID0gZnJvbV9sYXRpdHVkZSwgY29sb3IgPSBNQUUpLCANCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50Iiwgc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjgpKw0KICBzY2FsZV9jb2xvdXJfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwNCiAgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkIiKSsNCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpKw0KICB4bGltKG1pbihkYXRfY2Vuc3VzJGZyb21fbG9uZ2l0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpKSsNCiAgZmFjZXRfZ3JpZCh3ZWVrZW5kfnRpbWVfb2ZfZGF5KSsNCiAgbGFicyh0aXRsZT0iTWVhbiBBYnNvbHV0ZSBFcnJvcnMsIFRlc3QgU2V0IikrDQogIG1hcFRoZW1lKCkNCiAgDQpgYGANCg0KV2UgbG9va2VkIGF0IHRocmVlIGZhY3RvcnMsIG1lZGlhbiBpbmNvbWUsIHB1YmxpYyB0cmFuc3BvcnRhdGlvbiB1c2UsIGFuZCBwZXJjZW50YWdlIG9mIHdoaXRlIHBlb3BsZS4gUHVibGljIHRyYW5zcG9ydGF0aW9uIGRvZXMgbm90IGFwcGVhciB0byBoYXZlIGEgZ2l2ZW4gZWZmZWN0IG9uIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGJ1dCB0aGUgd2hpdGUgcG9wdWxhdGlvbiAmIG1lZGlhbiBpbmNvbWUgc2hvdyBhIHN0ZWFkeSBpbmNsaW5lIHdoZXJlIHRoZXJlIGFyZSBsYXJnZXIgZXJyb3JzIHdoZW4gdGhlcmUgaXMgbW9yZSB3aGl0ZSBwZW9wbGUgb3Igd2VhbHRoaWVyIGFyZWFzLiBUaHVzLCBvdXIgbW9kZWwgcHJlZGljdHMgaGlnaGVyIHJpZGVzaGFyZSB1c2UgaW4gYXJlYXMgd2l0aCB3aGl0ZXIgcG9wdWxhdGlvbnMgb3IgaGlnaGVyIGluY29tZS4gICAgIA0KDQpgYGB7ciBzdGF0aW9uX3N1bW1hcnkyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aD0xMCB9DQp3ZWVrX3ByZWRpY3Rpb25zICU+JSANCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwNCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uX2lkKSwgDQogICAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sYXRpdHVkZSksIA0KICAgICAgICAgICBmcm9tX2xvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xvbmdpdHVkZSksDQogICAgICAgICAgIGRvdHcgPSBtYXAoZGF0YSwgcHVsbCwgZG90dyksDQogICAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMpLA0KICAgICAgICAgICBNZWRpYW5fSW5jb21lID0gbWFwKGRhdGEsIHB1bGwsIE1lZF9JbmMpLA0KICAgICAgICAgICBQZXJjZW50X1doaXRlID0gbWFwKGRhdGEsIHB1bGwsIFBlcmNlbnRfV2hpdGUpKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIA0KICAgICAgICAgICBmcm9tX2xhdGl0dWRlLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbiwNCiAgICAgICAgICAgZG90dywgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zLCBNZWRpYW5fSW5jb21lLCBQZXJjZW50X1doaXRlKSAlPiUNCiAgICB1bm5lc3QoKSAlPiUNCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIk1vZGVsNV9GdWxsIiklPiUNCiAgbXV0YXRlKHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLA0KICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQ0KICBmaWx0ZXIodGltZV9vZl9kYXkgPT0gIkFNIFJ1c2giKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zLCBNZWRpYW5fSW5jb21lLCBQZXJjZW50X1doaXRlKSAlPiUNCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQ0KICBnYXRoZXIoLXN0YXJ0X3N0YXRpb25faWQsIC1NQUUsIGtleSA9ICJ2YXJpYWJsZSIsIHZhbHVlID0gInZhbHVlIiklPiUNCiAgZ2dwbG90KC4pKw0KICAjZ2VvbV9zZihkYXRhID0gZGNDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGdlb21fcG9pbnQoYWVzKHggPSB2YWx1ZSwgeSA9IE1BRSksIGFscGhhID0gMC40KSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHggPSB2YWx1ZSwgeSA9IE1BRSksIG1ldGhvZCA9ICJsbSIsIHNlPSBGQUxTRSkrDQogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpKw0KICBsYWJzKHRpdGxlPSJBTSBSdXNoIGVycm9ycyBhcyBhIGZ1bmN0aW9uIG9mIHNvY2lvLWVjb25vbWljIHZhcmlhYmxlcyIsDQogICAgICAgeT0iTWVhbiBBYnNvbHV0ZSBFcnJvciAoVHJpcHMpIikrDQogIHBsb3RUaGVtZSgpDQogIA0KYGBgDQoNCg0KDQojIyBDcm9zcyBWYWxpZGF0aW9uDQoNClRvIGFzc2VzcyB0aGUgZ29vZG5lc3Mgb2Ygb3VyIG1vZGVsLCB3ZSB1c2Ugay1mb2xkIGNyb3NzLXZhbGlkYXRpb24sIG5vcm1hbGx5IHdlIHdvdWxkIHJ1biAxMDAgZm9sZHMgZm9yIGEgcHJvcGVyIGV2YWx1YXRpb24uIEhvd2V2ZXIsIGR1ZSB0byBjb21wdXRhdGlvbmFsIGNvbnN0cmFpbnRzLCB3ZSBoYWQgdG8gc2V0dGxlIGZvciAxMCBmb2xkcy4gVGhlIGhpc3RvZ3JhbSBpbGx1c3RyYXRlcyB0aGUgYXZlcmFnZSBlcnJvcnMgYWNyb3NzIGVhY2ggZm9sZCwgcHJvdmlkaW5nIGEgc3VtbWFyeSBvZiBvdXIgY3Jvc3MtdmFsaWRhdGlvbiByZXN1bHRzLiBUaGUgZGlzdHJpYnV0aW9uIGV4aGliaXRzIGFuIGFwcHJveGltYXRlIG5vcm1hbGl0eSwgd2l0aCBhIGNlbnRyYWwgdGVuZGVuY3kgYXJvdW5kIDEuMDUuIFRoaXMgdmFsdWUgcmVwcmVzZW50cyBhIHJlbGF0aXZlbHkgbG93IGxldmVsIG9mIGVycm9yLCB1bmRlcnNjb3JpbmcgdGhlIHByZWNpc2lvbiBvZiBvdXIgbW9kZWwuDQoNCmBgYHtyIGN2LCBjYWNoZT1UfQ0Kc2V0LnNlZWQoOCkNCg0KY3YyPC0gY3Jvc3N2X2tmb2xkKHJpZGUuVHJhaW4sIDEwKQ0KDQptb2RlbHMgPC0gbWFwKGN2MiR0cmFpbix+IGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbl9uYW1lICsgaG91cihpbnRlcnZhbDYwKSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKyBkb3R3ICsgVGVtcGVyYXR1cmUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICsgUHJlY2lwaXRhdGlvbiArIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgK2xhZzEySG91cnMgKyBsYWcxZGF5ICsgc3RhdGlvbi5kaXN0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPXJpZGUuVHJhaW4pKQ0KDQplcnJzIDwtIG1hcDJfZGJsKG1vZGVscywgY3YyJHRlc3QsIHJtc2UpDQoNCmdncGxvdCgpKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoZXJycyksYmlucz04KSsNCiAgbGFicyh0aXRsZT0iSGlzdG9ncmFtIG9mIGstZm9sZCBDViBlcnJvcnMiLA0KICAgICAgIHk9IkZyZXF1ZW5jeSIsDQogICAgICAgeD0iTWVhbiBmb2xkIGVycm9yIikrDQogIHBsb3RUaGVtZSgpDQoNCnJlbW92ZShtb2RlbHMsIGN2MikNCmBgYA0KDQojIyBDb25jbHVzaW9uDQoNCg0KV2UgZGV2ZWxvcGVkIGEgbW9kZWwgZm9yZWNhc3RpbmcgYmlrZSBkZW1hbmQgYWNyb3NzIERDLCBmYWN0b3JpbmcgaW4gYm90aCB0aW1lIGFuZCBsb2NhdGlvbi4gT3VyIGdvYWwgaXMgdG8ga2VlcCBldmVyeSBiaWtlIHN0YXRpb24gc3RvY2tlZCB3aXRoIGF0IGxlYXN0IG9uZSBiaWtlIGFuZCBoYXZlIDEgYmlrZSBzbG90IG9wZW4gYXQgYWxsIHRpbWVzLCBlbnN1cmluZyByaWRlcnMgYWx3YXlzIGZpbmQgd2hhdCB0aGV5IG5lZWQuIEFmdGVyIGFuYWx5emluZyB0aGUgdmFyaW91cyBmYWN0b3JzIGFuZCBvYnNlcnZlZCBwYXR0ZXJucywgd2UgZXN0aW1hdGUgdGhlIGJpa2VzIHJpZGVycyB3aWxsIHBpY2sgdXAsIGVuYWJsaW5nIENhcGl0YWwgQmlrZXNoYXJlIHRvIHByb2FjdGl2ZWx5IHBsYW4gZm9yIHN0YXRpb24gYW5kIGJpa2UgYXZhaWxhYmlsaXR5LiANCg0KSW4gY29uanVuY3Rpb24gd2l0aCBkYXRhIG9uIGJpa2Ugc3RhdGlvbiBjYXBhY2l0eSwgdGhpcyBtb2RlbCBpcyBjYXBhYmxlIHRvIGJlIGltcGxlbWVudGVkIGluIGEgZHluYW1pYyBpbmNlbnRpdmUgc3lzdGVtIHRoYXQgcmVkaXN0cmlidXRlcyBiaWtlcyBhY3Jvc3Mgc3RhdGlvbnMgYnkgb2ZmZXJpbmcgbG93ZXIgY29zdHMgKG9yIGV2ZW4gb2Zmc2V0cyB0byBmdXR1cmUgcmlkZXMpIHRvIHJpZGVycyBpbiBleGNoYW5nZSBmb3IgYSBkaWZmZXJlbnQgZHJvcC1vZmYgbG9jYXRpb24uIFRoZSBpbmNlbnRpdmUgc2hvdWxkIHZhcnkgd2l0aCBkaXN0YW5jZSB0byBvcmlnaW5hbCBkZXN0aW5hdGlvbiwgbnVtYmVyIG9mIGJpa2VzIGFscmVhZHkgYXQgdGhlIHRhcmdldCBkcm9wLW9mZiBzdGF0aW9uIGFzIHdlbGwgYXMgcHJlZGljdGVkIGJpa2UgZGVtYW5kIGF0IHRoYXQgc3RhdGlvbi4gQ2FwaXRhbCBCaWtlc2hhcmUgY2FuIGV4cGVyaW1lbnQgd2l0aCB0aGUgaW5jZW50aXZlIHN0cnVjdHVyZSB0byBvcHRpbWl6ZSBmb3IgYmlrZSBhdmFpbGFiaWxpdHkgYWNyb3NzIHN0YXRpb25zLiAgDQoNCg0K