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:

  1. Hour of day, day of week, temperature
  2. Pickup station, day of week, temperature
  3. Variables in models 1 and 2 + precipitation
  4. Variables in model 3 + time lags
  5. 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