Introduction

Bike share system provides shared bike services for short travelling purposes. One may borrow a bike at a bike-share station, and return it at another station within the same system close to the destination. The system may be operated by a private company or a public municipality, usually with a fee.

“Re-balancing”, in terms of a bike-share system, refers to the process of reallocating available bikes at a given time according to the demand. Failure to meet the demand is undesirable for both the users and the bike share companies – the former loses a means of transportation and the latter loses potential revenue. Therefore it is important to have a sense of how many people will pick up a bike at a certain time and station – in order to redistribute excessive bikes at other locations to stations that actually have high demand. Usually, re-balancing will take place manually by small trucks to move the bikes around.

The analysis below will examine the bike demand in Boston, MA in August and September 2019. “Bluebikes” is a private company that operates the bike share system in the city. It employs 4-5 rebalancing vans, each with a payload of 20-25 bikes, to redistribute bicycles 24 hours a day, 7 days a week. A regression model is developed based on weather and one-hour to one-week time lag to predict the number of trips that occur at a particular time and station. Since the company is redistributing bikes every hour, a one-hour time lag is appropriate, because that will allow van drivers to know an hour beforehand on the demand of bikes at different locations. Other time lags are added to strengthen the model.

Import Bike Trip Data

Data from August and September 2019 is used, because the temperature in Boston is pretty pleasant during that time of the year. The public data is downloaded from Bluebikes website.

dat <- rbind(read.csv("201908boston.csv"),read.csv("201909boston.csv"))

dat2 <- dat %>%
  mutate(interval60 = floor_date(ymd_hms(starttime), unit = "hour"),
         interval15 = floor_date(ymd_hms(starttime), unit = "15 mins"),
         week = week(interval60),
         dotw = wday(interval60, label=TRUE))
BostonCensus <- 
  get_acs(geography = "tract", 
          variables = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"), 
          year = 2019, 
          state = "MA", 
          geometry = TRUE, 
          county=c("Suffolk"),
          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)

BostonTracts <- 
  BostonCensus %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  select(GEOID, geometry) %>% 
  st_sf
dat_census <- st_join(dat2 %>% 
          filter(is.na(start.station.longitude) == FALSE &
                   is.na(start.station.latitude) == FALSE &
                   is.na(end.station.latitude) == FALSE &
                   is.na(end.station.longitude) == FALSE) %>%
          st_as_sf(., coords = c("start.station.longitude", "start.station.latitude"), crs = "EPSG:4269"),
        BostonTracts%>%
          st_transform(crs="EPSG:4269"),
        join=st_intersects,
              left = TRUE) %>%
   rename(Origin.Tract = GEOID) %>%
  mutate(start.station.longitude = unlist(map(geometry, 1)),
         start.station.latitude = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  select(-geometry)%>%
  st_as_sf(., coords = c("end.station.longitude", "end.station.latitude"), crs = "EPSG:4269") %>%
  st_join(., BostonTracts %>%
            st_transform(crs="EPSG:4269"),
          join=st_intersects,
          left = TRUE) %>%
  rename(Destination.Tract = GEOID)  %>%
  mutate(end.station.longitude = unlist(map(geometry, 1)),
         end.station.latitude = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  select(-geometry)

Weather Data

We take the weather records measured by Logan Airport in Boston (code name is “BOS”) by the function “riem_measures”. We can assume that the weather measured by the airport can be applied to the city, because they are all in the same region.

weather.Panel <- 
  riem_measures(station = "BOS", date_start = "2019-08-01", date_end = "2019-09-30") %>%
  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))
grid.arrange(
  ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line() + 
  labs(title="Percipitation", x="Hour", y="Perecipitation") + plotTheme(),
  ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line() + 
    labs(title="Wind Speed", x="Hour", y="Wind Speed") + plotTheme(),
  ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line() + 
    labs(title="Temperature", x="Hour", y="Temperature") + plotTheme(),
  top="Fig. 1 Weather Data - Boston Logan - August and September, 2019")

Fig.1 displays the weather conditions on an hourly basis from August 1, 2019 to September 30, 2019.

Examine the Relationship between Bike Demand and Time/Space

We can now analyse the data by generating number of trips and time on an hourly basis.

ggplot(dat_census %>%
         group_by(interval60) %>%
         tally())+
  geom_line(aes(x = interval60, y = n))+
  labs(title="Bike share trips per hr. Boston,\n August and September, 2019",
       x="Date", 
       y="Number of trips", caption = "Fig 2")+
  plotTheme()

We may detect a pattern in Fig 2. The trips are occurring at a consistent trend weekly. For every ten spikes, there are two dips. The two dips occur during the weekends, and the ten spikes occur during the weekday with two rush hours: one in the morning and one in the evening. Note that there is a sharp decrease on Sept. 2. That was the labor day in 2019.

Rush Hours

From Fig. 2, we can see that some hours have a consistent high demand. It is worthwhile to categorize time into ranges based on these rush hours and see if they play a big role in telling the number of trips.

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 Number of Hourly Trips Per Station. Boston, \nAugust and Sepetember, 2019",
       x="Number of trips", 
       y="Frequency", caption = "Fig 3")+
  facet_wrap(~time_of_day)+
  plotTheme()

The more spread-out AM and PM rush plots in Fig 3. suggest that the number of trips increases as we hit these rush hours.

Day of the week

Is there a difference between weekend and weekday? Intuitively, we would like to assume that more bikes are used during weekday as more people commute to work and less during weekends.

ggplot(dat_census %>% mutate(hour = hour(starttime)))+
     geom_freqpoly(aes(hour, color = dotw), binwidth = 1)+
  labs(title="Bike share trips in Boston, by day of the week, August and September, 2019",
       x="Hour", 
       y="Trip Counts", caption = "Fig 4")+
     plotTheme()

ggplot(dat_census %>% 
         mutate(hour = hour(starttime),
                weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday")))+
     geom_freqpoly(aes(hour, color = weekend), binwidth = 1)+
  labs(title="Bike share trips in Boston - weekend vs weekday\n, August and September, 2019",
       x="Hour", 
       y="Trip Counts", caption = "Fig 5")+
     plotTheme()

Fig 4 plots the number of trips by day of the week. Monday to Friday have very close trend, while Saturday and Sunday, as expected, have lower number of trips.

Fig 5 plots categorize number of trips into weekday and weekend by hour. We can again see that there are two spikes at rush hours during weekdays. Weekends generally have much lower bike usage, and bikes are mostly used in the afternoon to the evening (12 PM - 18 PM).

Station Usage Per hour

We can also examine the station usage by hour. This allows us to see if some stations are just idle at certain hours.

ggplot(dat_census %>%
         group_by(interval60, start.station.name) %>%
         tally())+
  geom_histogram(aes(n), binwidth = 5)+
  labs(title="Bike share trips per hr by station",subtitle="Boston, August and September, 2019",
       x="Trip Counts", 
       y="Number of Stations", caption = "Fig 6")+
  plotTheme()

We can actually see from Fig 6. that many stations are unused at some hours. We can also see that some particular stations have high demands – more than 20 times within an hour.

Bikeshare per Hour by Station

To visualize if there is a location bias for bike share, a map is created to show number of trips taken at each station across different time.

DAT_MAp <-               
dat_census %>% 
            mutate(hour = hour(starttime),
                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, start.station.latitude, start.station.longitude, weekend, time_of_day) %>%
              tally() %>%
  st_as_sf(coords = c("start.station.longitude", "start.station.latitude"), crs = "EPSG:4269") 

DAT_MAp <-
  st_intersection(DAT_MAp, BostonCensus)
## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries
ggplot()+
  geom_sf(data = BostonTracts %>%
          st_transform(crs= 4326))+
  geom_sf(data = DAT_MAp,
            aes(color = n))+
  scale_color_viridis(discrete = FALSE, option = "D",direction = -1)+
  facet_grid(weekend ~ time_of_day)+
  labs(title="Bike share trips per hr by station. Boston\n, August and September, 2019", color = "Trip Count", caption="Fig 7")+
  mapTheme()

It is clear from Fig 7 that, no matter the time, ride demand seems to cluster over the center around the Back Bay area, represented by darker green.

In short, we can see that ride demand is very time and space dependent. All the figures above show re-balancing should be more frequent during weekdays than weekends, because demand variation during weekends is small. The demand spikes during AM and PM rush hours, so probably more van drivers need to be hired during that time to shift bikes more efficiently.

Ride Animation

We can show the difference of ride demand across time and space by animating the data. For the sake of limited computing capacity, we will only animate the demand on the first day of August based on a 15 minutes interval.

week31 <-
  filter(dat2, week == 31 & dotw == "Mon")

week31.panel <-
  expand.grid(
    interval15 = unique(week31$interval15),
    start.station.name = unique(dat2$start.station.name))
station <- read.csv("current_bluebikes_stations.csv") %>% 
  st_as_sf(coords = c("Longitude", "Latitude"),crs = "EPSG:4269")

station$Name <-
  as.character(station$Name) 

station <-
  st_intersection(station,BostonCensus)
## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries
ride.animation.data <-
    mutate(week31, Trip_Counter = 1) %>%
    right_join(week31.panel) %>% 
    group_by(interval15, start.station.name) %>%
    summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>% 
    ungroup() %>% 
    left_join(station, by=c("start.station.name" = "Name")) %>%
    st_sf() %>%
    mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
                             Trip_Count > 0 & Trip_Count <= 3 ~ "1-3 trips",
                             Trip_Count > 3 & Trip_Count <= 6 ~ "4-6 trips",
                             Trip_Count > 6 & Trip_Count <= 10 ~ "7-10 trips",
                             Trip_Count > 10 ~ "11+ trips")) %>%
    mutate(Trips  = fct_relevel(Trips, "0 trips","1-3 trips","4-6 trips",
                                       "7-10 trips","10+ trips"))
## Warning: Unknown levels in `f`: 10+ trips
rideshare_animation <-
  ggplot() +
    geom_point(data = st_centroid(ride.animation.data), aes(size = Trip_Count,geometry=geometry),stat="sf_coordinates") +
    scale_color_manual(values = palette5) +
    labs(title = "Bikeshare pickup for one day in August 2019",
         subtitle = "15 minute intervals: {current_frame}",
         size = "Trip Count") +
    transition_manual(interval15)+
  geom_sf(data=BostonCensus,fill="transparent",color = "gray")+
  mapTheme()

animate(rideshare_animation, duration=20, renderer = gifski_renderer())
## nframes and fps adjusted to match transition

Develop and Run Regression Models for Demand Prediction

Create Space-Time Panel

A study panel is created to ensure that every unique station is included and we will convert “NA”s into zero, so that they can be included in the regression. We will then join the panel with Boston census data, to ensure only the stations within Boston are analysed. This new panel is called the ride panel.

Due to computer’s limited capacity, only the data from first five weeks in August and September are selected for ride panel.

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, start.station.longitude, start.station.latitude )%>%
              distinct() %>%
              group_by(start.station.id) %>%
              slice(1))
ride.panel <- 
  dat_census %>%
  mutate(Trip_Counter = 1) %>%
  right_join(study.panel) %>% 
  group_by(interval60, start.station.id, start.station.name, Origin.Tract, start.station.longitude, start.station.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, BostonCensus %>%
              as.data.frame() %>%
              select(-geometry), by = c("Origin.Tract" = "GEOID"))

Create time lags

Time lag helps us to determine the relationship between the current trip number and the previous trip number at a given time. Intuitively, the shorter the time frame, the stronger the correlation. For example, the demands before and after one hour have a stronger relationship than that of four hours. Also, the demand today at a given time should be similar to the demand at this time tomorrow. The demand on a Monday should be reflective of the demand on next Monday.

The ride panel is created with time lags of 1 hour, 2 hours, 3 hours, 4 hours, 12 hours, 1 day (24 hours), and 1 week. The demand correlation is evaluated on each lag by r-squared.

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),
         lag1week = dplyr::lag(Trip_Count,168),
         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 ~ "PlustTwoDays",
                                 dplyr::lag(holiday, 3) == 1 ~ "PlustThreeDays",
                                 dplyr::lead(holiday, 1) == 1 ~ "MinusOneDay",
                                 dplyr::lead(holiday, 2) == 1 ~ "MinusTwoDays",
                                 dplyr::lead(holiday, 3) == 1 ~ "MinusThreeDays"),
         holidayLag = replace_na(holidayLag, 0))
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","lag1week")))%>%
    group_by(Variable) %>%  
    summarize(correlation = round(cor(Value, Trip_Count),2))%>%
  kable(caption = "Table 1. Bikeride Demand Correlation based on Time Lag Measured by R-square")%>%kable_styling()
Table 1. Bikeride Demand Correlation based on Time Lag Measured by R-square
Variable correlation
lagHour 0.80
lag2Hours 0.49
lag3Hours 0.24
lag4Hours 0.08
lag12Hours -0.42
lag1day 0.82
lag1week 0.87

Table 1 validates our hypothesis. We see that 1 hour lag, 1 day lag, and 1 week lag have the strongest correlation between demands. The week lag is the strongest, meaning the weekly demand for bike rides is very similar.

Models

The ride panel is further separated into a training set and a testing set. We wil train the model on the first three weeks, and test it on the last two weeks. We don’t randomly generate training and testing sets, because they are very time dependent.

The data for all five weeks will be combined to a new panel. This panel is going to be used for cross-validation.

ride.Train <- filter(ride.panel, week == 31 | week == 32 | week == 33)
ride.Test <- filter(ride.panel, week == 34 | week == 35)
ride.panel5week <- rbind(ride.Train,ride.Test)
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 + lag1week, 
     data=ride.Train)

Four models are developed here. Reg1 considers hour, day of the week, temperature as factors of ride share demand. Reg2 adds a location factor (station.station.name). Reg3 adds precipitation as another factor on to Reg2.Reg4 has everything from Reg3, but also including lag time factors. We will examine how each regression perform based on errors.

Predict for test data

We will predict the data based on the test set. Here, we will nest the data in to matrix. Nesting allows us to run models over two week at once, instead of separating each week out and run the regression individually.

Four regressions are run over the two weeks in the testing set.

ride.Test.weekNest <- 
  ride.Test %>%
  nest(-week) 
## Warning: All elements of `...` must be named.
## Did you want `data = c(interval60, start.station.id, start.station.name, Origin.Tract, 
##     start.station.longitude, start.station.latitude, Trip_Count, 
##     Temperature, Precipitation, Wind_Speed, dotw, Total_Pop, 
##     Med_Inc, White_Pop, Travel_Time, Means_of_Transport, Total_Public_Trans, 
##     Med_Age, Percent_White, Mean_Commute_Time, Percent_Taking_Public_Trans, 
##     lagHour, lag2Hours, lag3Hours, lag4Hours, lag12Hours, lag1day, 
##     lag1week, holiday, day, holidayLag)`?
model_pred <- function(dat, fit){
   pred <- predict(fit, newdata = dat)}
week_predictions <- 
  ride.Test.weekNest %>% 
    mutate(ATime_FE = map(.x = data, fit = reg1, .f = model_pred),
           BSpace_FE = map(.x = data, fit = reg2, .f = model_pred),
           CTime_Space_FE = map(.x = data, fit = reg3, .f = model_pred),
           DTime_Space_FE_timeLags = map(.x = data, fit = reg4, .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: 8 x 8
##    week data      Regression    Prediction  Observed  Absolute_Error   MAE sd_AE
##   <dbl> <list>    <chr>         <list>      <list>    <list>         <dbl> <dbl>
## 1    34 <tibble ~ ATime_FE      <dbl [35,4~ <dbl [35~ <dbl [35,448]> 1.57   2.14
## 2    35 <tibble ~ ATime_FE      <dbl [35,4~ <dbl [35~ <dbl [35,448]> 1.50   1.93
## 3    34 <tibble ~ BSpace_FE     <dbl [35,4~ <dbl [35~ <dbl [35,448]> 1.36   1.95
## 4    35 <tibble ~ BSpace_FE     <dbl [35,4~ <dbl [35~ <dbl [35,448]> 1.27   1.84
## 5    34 <tibble ~ CTime_Space_~ <dbl [35,4~ <dbl [35~ <dbl [35,448]> 1.34   1.90
## 6    35 <tibble ~ CTime_Space_~ <dbl [35,4~ <dbl [35~ <dbl [35,448]> 1.48   2.41
## 7    34 <tibble ~ DTime_Space_~ <dbl [35,4~ <dbl [35~ <dbl [35,448]> 0.983  1.51
## 8    35 <tibble ~ DTime_Space_~ <dbl [35,4~ <dbl [35~ <dbl [35,448]> 1.00   1.45

The returned “tibble” (an other form of table) shows the mean absolute error for each regression. We can see the regression that consider time, space, weather, and time lags (Reg4) has the lowest mean absolute error. Others regression have errors at a relatively high level, meaning that time lag really made a difference here.

Examine Error Metrics for Accuracy

Fig 8 visualizes the comparison of errors across the four models.

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", caption = "Fig 8") +
  plotTheme()

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 bike share time series", subtitle = "Boston; A test set of 2 weeks",  x = "Hour", y= "Station Trips", caption = "Fig 9") +
      plotTheme()

Again, apparently, Reg 4 with time lags has the lowest mean absolute error (MAE) below 1 for both weeks. Fig 9 shows the predicted trip count lay over observed trip count. Reg 4 with time lags fit the best.

Error clustering

Do these errors cluster like demand based on location? We will take our best model of fit (Reg 4) to map out errors in Boston.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start.station.id = map(data, pull, start.station.id), 
           start.station.latitude = map(data, pull, start.station.latitude), 
           start.station.longitude = map(data, pull, start.station.longitude)) %>%
    select(interval60, start.station.id, start.station.longitude, start.station.latitude, Observed, Prediction, Regression) %>%
    unnest() %>%
  filter(Regression == "DTime_Space_FE_timeLags") %>%
  group_by(start.station.id, start.station.longitude, start.station.latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
  geom_sf(data = BostonCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = start.station.longitude, y = start.station.latitude, color = MAE), 
             fill = "transparent", alpha = 0.4)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  labs(title="Mean Abs Error, Test Set, Model 4", caption = "Fig 10")+
  mapTheme()

Indeed, the model is less accurate at the centeral bay area according to Fig 10. This means our model may predict less accurately around that area. However, most of MAEs are not too high and do not exceed 3, which is still acceptable.

Space-Time Error Evaluation

We are going to dissect our prediction a little more. Here we are determining wether the errors cluster based on time.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start.station.id = map(data, pull, start.station.id), 
           start.station.latitude = map(data, pull, start.station.latitude), 
           start.station.longitude = map(data, pull, start.station.longitude),
           dotw = map(data, pull, dotw)) %>%
    select(interval60, start.station.id, start.station.longitude, 
           start.station.latitude, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "DTime_Space_FE_timeLags")%>%
  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", caption ="Fig 11")+
  plotTheme()

Black lines in Fig 11 show perfect predictions. The redline shows the linear result based on reg4. The red lines are below the black lines, meaning we are under-predicting in general. The error varies more largely during the weekdays and at AM rush and PM rush. This is understandable because the actual trips also vary during these hours and days, whereas the trip pattern during weekends and non-rush hours are fairly consistent.

Detailed Time-Space Error Map

Fig 10 shows the aggregated result for MAE across all times. Here, we are going to look at MAE at different time period and see if errors are still location biased during different time.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start.station.id = map(data, pull, start.station.id), 
           start.station.latitude = map(data, pull, start.station.latitude), 
           start.station.longitude = map(data, pull, start.station.longitude),
           dotw = map(data, pull, dotw)) %>%
    select(interval60, start.station.id, start.station.longitude, 
           start.station.latitude, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "DTime_Space_FE_timeLags")%>%
  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, start.station.longitude, start.station.latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  ggplot(.)+
  geom_sf(data = BostonCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = start.station.longitude, y = start.station.latitude, color = MAE), 
             fill = "transparent", size = 0.5, alpha = 0.4)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  facet_grid(weekend~time_of_day)+
  labs(title="Mean Absolute Errors, Test Set", caption = "Fig 12")+
  mapTheme()

Fig 12 does show that errors cluster at the central bay area regardless of time. The model predicts the worst at PM rush hours at the central bay area. It is good that we don’t see much blue on the map, indicating a higher MAE. Most MAEs are in the range of 0 to 3. Most of them are under-predicted. This observation could remind the company that during rush hours, it is probably wise to add 3 bikes on top of what the model predicts during rush hours at high demand area when re-balancing the bikes.

Cross-validation: K-Fold

We just see some error clustering based on time and space. So can our model generalize well? This section runs two k-fold validation: one on station location and one on time across all five weeks we used for training and testing.

ride.panel5week <-
  ride.panel5week %>%
  mutate(hour=hour(interval60))

reg.vars <-
  c("hour", "dotw", "Temperature","Precipitation", "lagHour","lag2Hours","lag3Hours","lag12Hours","lag1day","lag1week")

ride.panel5week <-
  ride.panel5week %>%
  st_as_sf(coords = c("start.station.longitude", "start.station.latitude"))


reg.cv <- crossValidate(
  dataset = ride.panel5week,
  id = "start.station.name",
  dependentVariable = "Trip_Count",
  indVariables = reg.vars) %>%
    dplyr::select(start.station.name = start.station.name, Trip_Count, Prediction)

reg.cv.time <- crossValidate(
  dataset = ride.panel5week,
  id = "dotw",
  dependentVariable = "Trip_Count",
  indVariables = reg.vars) %>%
    dplyr::select("Day_of_the_Week" = dotw, Trip_Count, Prediction)

reg.summary.space <-
  mutate(reg.cv, Error = Prediction - Trip_Count,
                             Regression = "Random k-fold CV: Space (Station)")

reg.summary.time <-
  reg.cv.time %>%
  mutate(reg.cv.time, Error = Prediction - Trip_Count,
                             Regression = "Random k-fold CV: Time (Day of the   Week)")
error.fold.space <- 
  reg.summary.space %>%
    group_by(start.station.name) %>% 
    summarize(Mean_Error = mean(Prediction - Trip_Count, na.rm = T),
              MAE = mean(abs(Mean_Error), na.rm = T),
              SD_MAE = mean(abs(Mean_Error), na.rm = T)) %>%
  ungroup()

plot1<-
  error.fold.space %>%
  ggplot(aes(MAE)) + 
    geom_histogram(bins = 30, colour="black", fill = "#FDE725FF") +
    geom_vline(xintercept = 0) + scale_x_continuous(breaks = seq(0, 8, by = 1)) + 
    labs(title="Distribution of MAE", subtitle = "k-fold cross validation: Station Location",
         x="Mean Absolute Error", y="Count") +
    plotTheme()

error.fold.time <- 
  reg.summary.time %>%
    group_by(Day_of_the_Week) %>% 
    summarize(Mean_Error = mean(Prediction - Trip_Count, na.rm = T),
              MAE = mean(abs(Mean_Error), na.rm = T),
              SD_MAE = mean(abs(Mean_Error), na.rm = T)) %>%
  ungroup()

plot2 <-
error.fold.time %>%
  ggplot(aes(MAE)) + 
    geom_histogram(bins = 30, colour="black", fill = "#FDE725FF") +
    geom_vline(xintercept = 0) + scale_x_continuous(breaks = seq(0, 0.2, by = 0.01)) + 
    labs(title="Distribution of MAE", subtitle = "k-fold cross validation: Day of the Week",
         x="Mean Absolute Error", y="Count")+
  plotTheme()

grid.arrange(plot1,plot2,top="Fig 13. K-Fold of Bikeshare Trip Counts by Station and Day of the Week")

st_drop_geometry(error.fold.space) %>%
   summarize(Mean_MAE = round(mean(MAE), 2),
              SD_MAE = round(sd(MAE), 2)) %>%
  kable(caption = "Table 2. K-fold Error by Station Location - Mean Absolute Error (MAE) and Standard Deviation of MAE") %>%
    kable_styling()
Table 2. K-fold Error by Station Location - Mean Absolute Error (MAE) and Standard Deviation of MAE
Mean_MAE SD_MAE
0.83 1.71
st_drop_geometry(error.fold.time) %>%
  summarize(Mean_MAE = round(mean(MAE), 2),
              SD_MAE = round(sd(MAE), 2)) %>%
  kable(caption = "Table 3. K-fold Error by Day of the Week - Mean Absolute Error (MAE) and Standard Deviation of MAE") %>%
    kable_styling()
Table 3. K-fold Error by Day of the Week - Mean Absolute Error (MAE) and Standard Deviation of MAE
Mean_MAE SD_MAE
0.05 0.05

Fig 12 plots the distribution of MAE based on time and space. Most of the MAEs on station location are concentrated around 1, followed by 0. This shows that our model generalizes fine over station location, mostly only off by 1 trip count.

It generalizes exceptionally for time. Fig 13 shows that the MAEs for k-fold based on time are well below 0, altering between 0.01 and 0.15.

Table 2 and 3 show the mean of MAE for k-fold on space and time respectively. Again, Table 3 suggests that our model generalizes superbly on time - with only 0.05 mean MAE and 0.05 MAE standard deviation. It also does not generalize too badly based on geography. The MAE is controlled under 1, and the standard deviation is under 2, meaning the MAE does not vary too much.

Conclusion

In this analysis, we found out that the model of the best fit is the one that incorporates location, weather, and time lags. After training, testing, and cross-validating our model, the model seems to predict effectively (with mean absolute error below five) and generalizes well across time and space. Based on the results, the model is recommended for the re-distributing plan for predicting the demand.

Notably that the model generalizes much better on time than location. This means that it is slightly unreliable on predicting the demand when it comes to location , especially during PM rush hours. The errors show that we are generally under-predicting, it is wise, thus, to redistribute 3 (the maximum absolute error) more bikes at high demand region (i.e. the central bay area) on top of the predicted demand during these hours to compensate for the errors.

Note that this analysis does not include any social-economical factors or neighborhood effect into account. For example, the demand could be related to the income level and employment rate of the neighborhood where a station locates. The model may be strengthened by considering these social-economical factors, which could essentially reduce error clustering over geography.

Also note this regression model only tells the demand. In order to implement the re-balancing plan, a real-time monitoring system should also be set up and sends signals at certain hour, when it senses that there is a lack of bikes at this particular station.

LS0tDQp0aXRsZTogIlJlYmFsYW5jaW5nIEJpa2Ugc2hhcmUgaW4gQm9zdG9uIg0Ka25pdDogKGZ1bmN0aW9uKGlucHV0X2ZpbGUsIGVuY29kaW5nKSB7DQogIG91dF9kaXIgPC0gJ2RvY3MnOw0KICBybWFya2Rvd246OnJlbmRlcihpbnB1dF9maWxlLA0KIGVuY29kaW5nPWVuY29kaW5nLA0KIG91dHB1dF9maWxlPWZpbGUucGF0aChkaXJuYW1lKGlucHV0X2ZpbGUpLCBvdXRfZGlyLCAnaW5kZXguaHRtbCcpKX0pDQphdXRob3I6ICJZaWhvbmcgSHUiDQpkYXRlOiAiMTEvMTUvMjAyMSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgcmVzdWx0cyA9IEZBTFNFLCBXYXJuaW5nID0gRkFMU0UsIE1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFKQ0KYGBgDQoNCiMgSW50cm9kdWN0aW9uDQoNCkJpa2Ugc2hhcmUgc3lzdGVtIHByb3ZpZGVzIHNoYXJlZCBiaWtlIHNlcnZpY2VzIGZvciBzaG9ydCB0cmF2ZWxsaW5nIHB1cnBvc2VzLiBPbmUgbWF5IGJvcnJvdyBhIGJpa2UgYXQgYSBiaWtlLXNoYXJlIHN0YXRpb24sIGFuZCByZXR1cm4gaXQgYXQgYW5vdGhlciBzdGF0aW9uIHdpdGhpbiB0aGUgc2FtZSBzeXN0ZW0gY2xvc2UgdG8gdGhlIGRlc3RpbmF0aW9uLiBUaGUgc3lzdGVtIG1heSBiZSBvcGVyYXRlZCBieSBhIHByaXZhdGUgY29tcGFueSBvciBhIHB1YmxpYyBtdW5pY2lwYWxpdHksIHVzdWFsbHkgd2l0aCBhIGZlZS4NCg0KIlJlLWJhbGFuY2luZyIsIGluIHRlcm1zIG9mIGEgYmlrZS1zaGFyZSBzeXN0ZW0sIHJlZmVycyB0byB0aGUgcHJvY2VzcyBvZiByZWFsbG9jYXRpbmcgYXZhaWxhYmxlIGJpa2VzIGF0IGEgZ2l2ZW4gdGltZSBhY2NvcmRpbmcgdG8gdGhlIGRlbWFuZC4gRmFpbHVyZSB0byBtZWV0IHRoZSBkZW1hbmQgaXMgdW5kZXNpcmFibGUgZm9yIGJvdGggdGhlIHVzZXJzIGFuZCB0aGUgYmlrZSBzaGFyZSBjb21wYW5pZXMgLS0gdGhlIGZvcm1lciBsb3NlcyBhIG1lYW5zIG9mIHRyYW5zcG9ydGF0aW9uIGFuZCB0aGUgbGF0dGVyIGxvc2VzIHBvdGVudGlhbCByZXZlbnVlLiBUaGVyZWZvcmUgaXQgaXMgaW1wb3J0YW50IHRvIGhhdmUgYSBzZW5zZSBvZiBob3cgbWFueSBwZW9wbGUgd2lsbCBwaWNrIHVwIGEgYmlrZSBhdCBhIGNlcnRhaW4gdGltZSBhbmQgc3RhdGlvbiAtLSBpbiBvcmRlciB0byByZWRpc3RyaWJ1dGUgZXhjZXNzaXZlIGJpa2VzIGF0IG90aGVyIGxvY2F0aW9ucyB0byBzdGF0aW9ucyB0aGF0IGFjdHVhbGx5IGhhdmUgaGlnaCBkZW1hbmQuIFVzdWFsbHksIHJlLWJhbGFuY2luZyB3aWxsIHRha2UgcGxhY2UgbWFudWFsbHkgYnkgc21hbGwgdHJ1Y2tzIHRvIG1vdmUgdGhlIGJpa2VzIGFyb3VuZC4NCg0KDQoNClRoZSBhbmFseXNpcyBiZWxvdyB3aWxsIGV4YW1pbmUgdGhlIGJpa2UgZGVtYW5kIGluIEJvc3RvbiwgTUEgaW4gQXVndXN0IGFuZCBTZXB0ZW1iZXIgMjAxOS4gIkJsdWViaWtlcyIgaXMgYSBwcml2YXRlIGNvbXBhbnkgdGhhdCBvcGVyYXRlcyB0aGUgYmlrZSBzaGFyZSBzeXN0ZW0gaW4gdGhlIGNpdHkuIEl0IGVtcGxveXMgNC01IHJlYmFsYW5jaW5nIHZhbnMsIGVhY2ggd2l0aCBhIHBheWxvYWQgb2YgMjAtMjUgYmlrZXMsIHRvIHJlZGlzdHJpYnV0ZSBiaWN5Y2xlcyAyNCBob3VycyBhIGRheSwgNyBkYXlzIGEgd2Vlay4gQSByZWdyZXNzaW9uIG1vZGVsIGlzIGRldmVsb3BlZCBiYXNlZCBvbiB3ZWF0aGVyIGFuZCBvbmUtaG91ciB0byBvbmUtd2VlayB0aW1lIGxhZyB0byBwcmVkaWN0IHRoZSBudW1iZXIgb2YgdHJpcHMgdGhhdCBvY2N1ciBhdCBhIHBhcnRpY3VsYXIgdGltZSBhbmQgc3RhdGlvbi4gU2luY2UgdGhlIGNvbXBhbnkgaXMgcmVkaXN0cmlidXRpbmcgYmlrZXMgZXZlcnkgaG91ciwgYSBvbmUtaG91ciB0aW1lIGxhZyBpcyBhcHByb3ByaWF0ZSwgYmVjYXVzZSB0aGF0IHdpbGwgYWxsb3fCoHZhbiBkcml2ZXJzIHRvIGtub3cgYW4gaG91ciBiZWZvcmVoYW5kIG9uIHRoZSBkZW1hbmQgb2YgYmlrZXMgYXQgZGlmZmVyZW50IGxvY2F0aW9ucy4gT3RoZXIgdGltZSBsYWdzIGFyZSBhZGRlZCB0byBzdHJlbmd0aGVuIHRoZSBtb2RlbC4NCg0KYGBge3Igc2V0dXBfMTMsIGNhY2hlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGUgPSBGQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzZikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeSh0aWdyaXMpDQpsaWJyYXJ5KHRpZHljZW5zdXMpDQpsaWJyYXJ5KHZpcmlkaXMpDQpsaWJyYXJ5KHJpZW0pDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KFJTb2NyYXRhKQ0KbGlicmFyeShnZ2FuaW1hdGUpDQpsaWJyYXJ5KGdpZnNraSkNCmxpYnJhcnkoY2FyZXQpDQoNCnBsb3RUaGVtZSA8LSB0aGVtZSgNCiAgcGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLA0KICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCksDQogIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksDQogIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICMgU2V0IHRoZSBlbnRpcmUgY2hhcnQgcmVnaW9uIHRvIGJsYW5rDQogIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICBwbG90LmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICAjcGFuZWwuYm9yZGVyPWVsZW1lbnRfcmVjdChjb2xvdXI9IiNGMEYwRjAiKSwNCiAgIyBGb3JtYXQgdGhlIGdyaWQNCiAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2xpbmUoY29sb3VyPSIjRDBEMEQwIixzaXplPS4yKSwNCiAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCkpDQoNCm1hcFRoZW1lIDwtIHRoZW1lKHBsb3QudGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTEyKSwNCiAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwNCiAgICAgICAgICAgICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksDQogICAgICAgICAgICAgICAgICBheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuYm9yZGVyPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG91ciA9ICd0cmFuc3BhcmVudCcpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gInZlcnRpY2FsIiwgDQogICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLA0KICAgICAgICAgICAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMSwgMSwgMSwgMSwgJ2NtJyksDQogICAgICAgICAgICAgICAgICBsZWdlbmQua2V5LmhlaWdodCA9IHVuaXQoMSwgImNtIiksIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDAuMiwgImNtIikpDQoNCnJvb3QuZGlyID0gImh0dHBzOi8vZ2l0aHViLmNvbS9CYW1ib28tRm9yZXN0LVJhaW4vUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy90cmVlL21hc3Rlci9EQVRBIg0Kc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQmFtYm9vLUZvcmVzdC1SYWluL1B1YmxpYy1Qb2xpY3ktQW5hbHl0aWNzLUxhbmRpbmcvbWFzdGVyL2Z1bmN0aW9ucy5yIikNCg0KcGFsZXR0ZTUgPC0gYygiI2VmZjNmZiIsIiNiZGQ3ZTciLCIjNmJhZWQ2IiwiIzMxODJiZCIsIiMwODUxOWMiKQ0KcGFsZXR0ZTQgPC0gYygiI0QyRkJENCIsIiM5MkJDQUIiLCIjNTI3RDgyIiwiIzEyM0Y1QSIpDQpwYWxldHRlMiA8LSBjKCIjNmJhZWQ2IiwiIzA4NTE5YyIpDQpgYGANCg0KYGBge3IgaW5zdGFsbF9jZW5zdXNfQVBJX2tleSwgd2FybmluZyA9IEZBTFNFLCBpbmNsdWRlPUZBTFNFLCBldmFsID0gVFJVRX0NCiMgSW5zdGFsbCBDZW5zdXMgQVBJIEtleQ0KY2Vuc3VzX2FwaV9rZXkoImU3OWYzNzA2YjZkNjEyNDk5NjhjNmNlODg3OTRmNmY1NTZlNWJmM2QiLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYGBgDQoNCiMjIEltcG9ydCBCaWtlIFRyaXAgRGF0YSANCg0KRGF0YSBmcm9tIEF1Z3VzdCBhbmQgU2VwdGVtYmVyIDIwMTkgaXMgdXNlZCwgYmVjYXVzZSB0aGUgdGVtcGVyYXR1cmUgaW4gQm9zdG9uIGlzIHByZXR0eSBwbGVhc2FudCBkdXJpbmcgdGhhdCB0aW1lIG9mIHRoZSB5ZWFyLiBUaGUgcHVibGljIGRhdGEgaXMgZG93bmxvYWRlZCBmcm9tIEJsdWViaWtlcyB3ZWJzaXRlLg0KDQpgYGB7ciBJbnB1dCBEYXRhIGFuZCBjcmVhdGUgdGltZSBiaW5zfQ0KZGF0IDwtIHJiaW5kKHJlYWQuY3N2KCIyMDE5MDhib3N0b24uY3N2IikscmVhZC5jc3YoIjIwMTkwOWJvc3Rvbi5jc3YiKSkNCg0KZGF0MiA8LSBkYXQgJT4lDQogIG11dGF0ZShpbnRlcnZhbDYwID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0dGltZSksIHVuaXQgPSAiaG91ciIpLA0KICAgICAgICAgaW50ZXJ2YWwxNSA9IGZsb29yX2RhdGUoeW1kX2htcyhzdGFydHRpbWUpLCB1bml0ID0gIjE1IG1pbnMiKSwNCiAgICAgICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLA0KICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWw9VFJVRSkpDQpgYGANCg0KYGBge3IgZ2V0X2NlbnN1cywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30NCkJvc3RvbkNlbnN1cyA8LSANCiAgZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCANCiAgICAgICAgICB2YXJpYWJsZXMgPSBjKCJCMDEwMDNfMDAxIiwgIkIxOTAxM18wMDEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDAyIiwgIkIwODAxM18wMDEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIkIwODAxMl8wMDEiLCAiQjA4MzAxXzAwMSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIkIwODMwMV8wMTAiLCAiQjAxMDAyXzAwMSIpLCANCiAgICAgICAgICB5ZWFyID0gMjAxOSwgDQogICAgICAgICAgc3RhdGUgPSAiTUEiLCANCiAgICAgICAgICBnZW9tZXRyeSA9IFRSVUUsIA0KICAgICAgICAgIGNvdW50eT1jKCJTdWZmb2xrIiksDQogICAgICAgICAgb3V0cHV0ID0gIndpZGUiKSAlPiUNCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwNCiAgICAgICAgIE1lZF9JbmMgPSBCMTkwMTNfMDAxRSwNCiAgICAgICAgIE1lZF9BZ2UgPSBCMDEwMDJfMDAxRSwNCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLA0KICAgICAgICAgVHJhdmVsX1RpbWUgPSBCMDgwMTNfMDAxRSwNCiAgICAgICAgIE51bV9Db21tdXRlcnMgPSBCMDgwMTJfMDAxRSwNCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLA0KICAgICAgICAgVG90YWxfUHVibGljX1RyYW5zID0gQjA4MzAxXzAxMEUpICU+JQ0KICBzZWxlY3QoVG90YWxfUG9wLCBNZWRfSW5jLCBXaGl0ZV9Qb3AsIFRyYXZlbF9UaW1lLA0KICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsDQogICAgICAgICBNZWRfQWdlLA0KICAgICAgICAgR0VPSUQsIGdlb21ldHJ5KSAlPiUNCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsDQogICAgICAgICBNZWFuX0NvbW11dGVfVGltZSA9IFRyYXZlbF9UaW1lIC8gVG90YWxfUHVibGljX1RyYW5zLA0KICAgICAgICAgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zID0gVG90YWxfUHVibGljX1RyYW5zIC8gTWVhbnNfb2ZfVHJhbnNwb3J0KQ0KDQpCb3N0b25UcmFjdHMgPC0gDQogIEJvc3RvbkNlbnN1cyAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBkaXN0aW5jdChHRU9JRCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lDQogIHNlbGVjdChHRU9JRCwgZ2VvbWV0cnkpICU+JSANCiAgc3Rfc2YNCg0KYGBgDQoNCmBgYHtyIGFkZF9jZW5zdXNfdHJhY3RzICwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpkYXRfY2Vuc3VzIDwtIHN0X2pvaW4oZGF0MiAlPiUgDQogICAgICAgICAgZmlsdGVyKGlzLm5hKHN0YXJ0LnN0YXRpb24ubG9uZ2l0dWRlKSA9PSBGQUxTRSAmDQogICAgICAgICAgICAgICAgICAgaXMubmEoc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSkgPT0gRkFMU0UgJg0KICAgICAgICAgICAgICAgICAgIGlzLm5hKGVuZC5zdGF0aW9uLmxhdGl0dWRlKSA9PSBGQUxTRSAmDQogICAgICAgICAgICAgICAgICAgaXMubmEoZW5kLnN0YXRpb24ubG9uZ2l0dWRlKSA9PSBGQUxTRSkgJT4lDQogICAgICAgICAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygic3RhcnQuc3RhdGlvbi5sb25naXR1ZGUiLCAic3RhcnQuc3RhdGlvbi5sYXRpdHVkZSIpLCBjcnMgPSAiRVBTRzo0MjY5IiksDQogICAgICAgIEJvc3RvblRyYWN0cyU+JQ0KICAgICAgICAgIHN0X3RyYW5zZm9ybShjcnM9IkVQU0c6NDI2OSIpLA0KICAgICAgICBqb2luPXN0X2ludGVyc2VjdHMsDQogICAgICAgICAgICAgIGxlZnQgPSBUUlVFKSAlPiUNCiAgIHJlbmFtZShPcmlnaW4uVHJhY3QgPSBHRU9JRCkgJT4lDQogIG11dGF0ZShzdGFydC5zdGF0aW9uLmxvbmdpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwNCiAgICAgICAgIHN0YXJ0LnN0YXRpb24ubGF0aXR1ZGUgPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAyKSkpJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgc2VsZWN0KC1nZW9tZXRyeSklPiUNCiAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygiZW5kLnN0YXRpb24ubG9uZ2l0dWRlIiwgImVuZC5zdGF0aW9uLmxhdGl0dWRlIiksIGNycyA9ICJFUFNHOjQyNjkiKSAlPiUNCiAgc3Rfam9pbiguLCBCb3N0b25UcmFjdHMgJT4lDQogICAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPSJFUFNHOjQyNjkiKSwNCiAgICAgICAgICBqb2luPXN0X2ludGVyc2VjdHMsDQogICAgICAgICAgbGVmdCA9IFRSVUUpICU+JQ0KICByZW5hbWUoRGVzdGluYXRpb24uVHJhY3QgPSBHRU9JRCkgICU+JQ0KICBtdXRhdGUoZW5kLnN0YXRpb24ubG9uZ2l0dWRlID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLA0KICAgICAgICAgZW5kLnN0YXRpb24ubGF0aXR1ZGUgPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAyKSkpJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgc2VsZWN0KC1nZW9tZXRyeSkNCmBgYA0KDQojIyBXZWF0aGVyIERhdGENCg0KV2UgdGFrZSB0aGUgd2VhdGhlciByZWNvcmRzIG1lYXN1cmVkIGJ5IExvZ2FuIEFpcnBvcnQgaW4gQm9zdG9uIChjb2RlIG5hbWUgaXMgIkJPUyIpIGJ5IHRoZSBmdW5jdGlvbiAicmllbV9tZWFzdXJlcyIuIFdlIGNhbiBhc3N1bWUgdGhhdCB0aGUgd2VhdGhlciBtZWFzdXJlZCBieSB0aGUgYWlycG9ydCBjYW4gYmUgYXBwbGllZCB0byB0aGUgY2l0eSwgYmVjYXVzZSB0aGV5IGFyZSBhbGwgaW4gdGhlIHNhbWUgcmVnaW9uLiANCg0KYGBge3IgaW1wb3J0X3dlYXRoZXIsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFIH0NCndlYXRoZXIuUGFuZWwgPC0gDQogIHJpZW1fbWVhc3VyZXMoc3RhdGlvbiA9ICJCT1MiLCBkYXRlX3N0YXJ0ID0gIjIwMTktMDgtMDEiLCBkYXRlX2VuZCA9ICIyMDE5LTA5LTMwIikgJT4lDQogIGRwbHlyOjpzZWxlY3QodmFsaWQsIHRtcGYsIHAwMWksIHNrbnQpJT4lDQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQ0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0geW1kX2goc3Vic3RyKHZhbGlkLDEsMTMpKSkgJT4lDQogICAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApLA0KICAgICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lDQogICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lDQogICAgc3VtbWFyaXplKFRlbXBlcmF0dXJlID0gbWF4KHRtcGYpLA0KICAgICAgICAgICAgICBQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpLA0KICAgICAgICAgICAgICBXaW5kX1NwZWVkID0gbWF4KHNrbnQpKSAlPiUNCiAgICBtdXRhdGUoVGVtcGVyYXR1cmUgPSBpZmVsc2UoVGVtcGVyYXR1cmUgPT0gMCwgNDIsIFRlbXBlcmF0dXJlKSkNCmBgYA0KDQpgYGB7ciBwbG90X3dlYXRoZXIsIGNhdGNoZSA9IFRSVUV9DQpncmlkLmFycmFuZ2UoDQogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxQcmVjaXBpdGF0aW9uKSkgKyBnZW9tX2xpbmUoKSArIA0KICBsYWJzKHRpdGxlPSJQZXJjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlBlcmVjaXBpdGF0aW9uIikgKyBwbG90VGhlbWUoKSwNCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFdpbmRfU3BlZWQpKSArIGdlb21fbGluZSgpICsgDQogICAgbGFicyh0aXRsZT0iV2luZCBTcGVlZCIsIHg9IkhvdXIiLCB5PSJXaW5kIFNwZWVkIikgKyBwbG90VGhlbWUoKSwNCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFRlbXBlcmF0dXJlKSkgKyBnZW9tX2xpbmUoKSArIA0KICAgIGxhYnModGl0bGU9IlRlbXBlcmF0dXJlIiwgeD0iSG91ciIsIHk9IlRlbXBlcmF0dXJlIikgKyBwbG90VGhlbWUoKSwNCiAgdG9wPSJGaWcuIDEgV2VhdGhlciBEYXRhIC0gQm9zdG9uIExvZ2FuIC0gQXVndXN0IGFuZCBTZXB0ZW1iZXIsIDIwMTkiKQ0KYGBgDQoNCkZpZy4xIGRpc3BsYXlzIHRoZSB3ZWF0aGVyIGNvbmRpdGlvbnMgb24gYW4gaG91cmx5IGJhc2lzIGZyb20gQXVndXN0IDEsIDIwMTkgdG8gU2VwdGVtYmVyIDMwLCAyMDE5LiANCg0KIyBFeGFtaW5lIHRoZSBSZWxhdGlvbnNoaXAgYmV0d2VlbiBCaWtlIERlbWFuZCBhbmQgVGltZS9TcGFjZQ0KDQpXZSBjYW4gbm93IGFuYWx5c2UgdGhlIGRhdGEgYnkgZ2VuZXJhdGluZyBudW1iZXIgb2YgdHJpcHMgYW5kIHRpbWUgb24gYW4gaG91cmx5IGJhc2lzLiANCg0KYGBge3IgdHJpcF90aW1lc2VyaWVzIH0NCmdncGxvdChkYXRfY2Vuc3VzICU+JQ0KICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lDQogICAgICAgICB0YWxseSgpKSsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gaW50ZXJ2YWw2MCwgeSA9IG4pKSsNCiAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBwZXIgaHIuIEJvc3RvbixcbiBBdWd1c3QgYW5kIFNlcHRlbWJlciwgMjAxOSIsDQogICAgICAgeD0iRGF0ZSIsIA0KICAgICAgIHk9Ik51bWJlciBvZiB0cmlwcyIsIGNhcHRpb24gPSAiRmlnIDIiKSsNCiAgcGxvdFRoZW1lKCkNCmBgYA0KDQpXZSBtYXkgZGV0ZWN0IGEgcGF0dGVybiBpbiBGaWcgMi4gVGhlIHRyaXBzIGFyZSBvY2N1cnJpbmcgYXQgYSBjb25zaXN0ZW50IHRyZW5kIHdlZWtseS4gRm9yIGV2ZXJ5IHRlbiBzcGlrZXMsIHRoZXJlIGFyZSB0d28gZGlwcy4gVGhlIHR3byBkaXBzIG9jY3VyIGR1cmluZyB0aGUgd2Vla2VuZHMsIGFuZCB0aGUgdGVuIHNwaWtlcyBvY2N1ciBkdXJpbmcgdGhlIHdlZWtkYXkgd2l0aCB0d28gcnVzaCBob3Vyczogb25lIGluIHRoZSBtb3JuaW5nIGFuZCBvbmUgaW4gdGhlIGV2ZW5pbmcuIE5vdGUgdGhhdCB0aGVyZSBpcyBhIHNoYXJwIGRlY3JlYXNlIG9uIFNlcHQuIDIuIFRoYXQgd2FzIHRoZSBsYWJvciBkYXkgaW4gMjAxOS4gDQoNCiMjIFJ1c2ggSG91cnMNCg0KRnJvbSBGaWcuIDIsIHdlIGNhbiBzZWUgdGhhdCBzb21lIGhvdXJzIGhhdmUgYSBjb25zaXN0ZW50IGhpZ2ggZGVtYW5kLiBJdCBpcyB3b3J0aHdoaWxlIHRvIGNhdGVnb3JpemUgdGltZSBpbnRvIHJhbmdlcyBiYXNlZCBvbiB0aGVzZSBydXNoIGhvdXJzIGFuZCBzZWUgaWYgdGhleSBwbGF5IGEgYmlnIHJvbGUgaW4gdGVsbGluZyB0aGUgbnVtYmVyIG9mIHRyaXBzLg0KDQpgYGB7ciBtZWFuX3RyaXBzX2hpc3QsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFIH0NCmRhdF9jZW5zdXMgJT4lDQogICAgICAgIG11dGF0ZSh0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUNCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0LnN0YXRpb24ubmFtZSwgdGltZV9vZl9kYXkpICU+JQ0KICAgICAgICAgdGFsbHkoKSU+JQ0KICBncm91cF9ieShzdGFydC5zdGF0aW9uLm5hbWUsIHRpbWVfb2ZfZGF5KSU+JQ0KICBzdW1tYXJpemUobWVhbl90cmlwcyA9IG1lYW4obikpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMobWVhbl90cmlwcyksIGJpbndpZHRoID0gMSkrDQogIGxhYnModGl0bGU9Ik1lYW4gTnVtYmVyIG9mIEhvdXJseSBUcmlwcyBQZXIgU3RhdGlvbi4gQm9zdG9uLCBcbkF1Z3VzdCBhbmQgU2VwZXRlbWJlciwgMjAxOSIsDQogICAgICAgeD0iTnVtYmVyIG9mIHRyaXBzIiwgDQogICAgICAgeT0iRnJlcXVlbmN5IiwgY2FwdGlvbiA9ICJGaWcgMyIpKw0KICBmYWNldF93cmFwKH50aW1lX29mX2RheSkrDQogIHBsb3RUaGVtZSgpDQpgYGANCg0KVGhlIG1vcmUgc3ByZWFkLW91dCBBTSBhbmQgUE0gcnVzaCBwbG90cyBpbiBGaWcgMy4gc3VnZ2VzdCB0aGF0IHRoZSBudW1iZXIgb2YgdHJpcHMgaW5jcmVhc2VzIGFzIHdlIGhpdCB0aGVzZSBydXNoIGhvdXJzLiANCg0KIyMgRGF5IG9mIHRoZSB3ZWVrDQoNCklzIHRoZXJlIGEgZGlmZmVyZW5jZSBiZXR3ZWVuIHdlZWtlbmQgYW5kIHdlZWtkYXk/IEludHVpdGl2ZWx5LCB3ZSB3b3VsZCBsaWtlIHRvIGFzc3VtZSB0aGF0IG1vcmUgYmlrZXMgYXJlIHVzZWQgZHVyaW5nIHdlZWtkYXkgYXMgbW9yZSBwZW9wbGUgY29tbXV0ZSB0byB3b3JrIGFuZCBsZXNzIGR1cmluZyB3ZWVrZW5kcy4gDQoNCmBgYHtyIHRyaXBzX2hvdXJfZG90dyB9DQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUgbXV0YXRlKGhvdXIgPSBob3VyKHN0YXJ0dGltZSkpKSsNCiAgICAgZ2VvbV9mcmVxcG9seShhZXMoaG91ciwgY29sb3IgPSBkb3R3KSwgYmlud2lkdGggPSAxKSsNCiAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBpbiBCb3N0b24sIGJ5IGRheSBvZiB0aGUgd2VlaywgQXVndXN0IGFuZCBTZXB0ZW1iZXIsIDIwMTkiLA0KICAgICAgIHg9IkhvdXIiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIsIGNhcHRpb24gPSAiRmlnIDQiKSsNCiAgICAgcGxvdFRoZW1lKCkNCg0KDQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUgDQogICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIoc3RhcnR0aW1lKSwNCiAgICAgICAgICAgICAgICB3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSkpKw0KICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IHdlZWtlbmQpLCBiaW53aWR0aCA9IDEpKw0KICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIGluIEJvc3RvbiAtIHdlZWtlbmQgdnMgd2Vla2RheVxuLCBBdWd1c3QgYW5kIFNlcHRlbWJlciwgMjAxOSIsDQogICAgICAgeD0iSG91ciIsIA0KICAgICAgIHk9IlRyaXAgQ291bnRzIiwgY2FwdGlvbiA9ICJGaWcgNSIpKw0KICAgICBwbG90VGhlbWUoKQ0KYGBgDQoNCkZpZyA0IHBsb3RzIHRoZSBudW1iZXIgb2YgdHJpcHMgYnkgZGF5IG9mIHRoZSB3ZWVrLiBNb25kYXkgdG8gRnJpZGF5IGhhdmUgdmVyeSBjbG9zZSB0cmVuZCwgd2hpbGUgU2F0dXJkYXkgYW5kIFN1bmRheSwgYXMgZXhwZWN0ZWQsIGhhdmUgbG93ZXIgbnVtYmVyIG9mIHRyaXBzLg0KDQpGaWcgNSBwbG90cyBjYXRlZ29yaXplIG51bWJlciBvZiB0cmlwcyBpbnRvIHdlZWtkYXkgYW5kIHdlZWtlbmQgYnkgaG91ci4gV2UgY2FuIGFnYWluIHNlZSB0aGF0IHRoZXJlIGFyZSB0d28gc3Bpa2VzIGF0IHJ1c2ggaG91cnMgZHVyaW5nIHdlZWtkYXlzLiBXZWVrZW5kcyBnZW5lcmFsbHkgaGF2ZSBtdWNoIGxvd2VyIGJpa2UgdXNhZ2UsIGFuZCBiaWtlcyBhcmUgbW9zdGx5IHVzZWQgaW4gdGhlIGFmdGVybm9vbiB0byB0aGUgZXZlbmluZyAoMTIgUE0gLSAxOCBQTSkuIA0KDQojIyBTdGF0aW9uIFVzYWdlIFBlciBob3VyDQoNCldlIGNhbiBhbHNvIGV4YW1pbmUgdGhlIHN0YXRpb24gdXNhZ2UgYnkgaG91ci4gVGhpcyBhbGxvd3MgdXMgdG8gc2VlIGlmIHNvbWUgc3RhdGlvbnMgYXJlIGp1c3QgaWRsZSBhdCBjZXJ0YWluIGhvdXJzLiANCg0KYGBge3IgdHJpcHNfc3RhdGlvbl9kb3R3IH0NCmdncGxvdChkYXRfY2Vuc3VzICU+JQ0KICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnQuc3RhdGlvbi5uYW1lKSAlPiUNCiAgICAgICAgIHRhbGx5KCkpKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMobiksIGJpbndpZHRoID0gNSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyIGJ5IHN0YXRpb24iLHN1YnRpdGxlPSJCb3N0b24sIEF1Z3VzdCBhbmQgU2VwdGVtYmVyLCAyMDE5IiwNCiAgICAgICB4PSJUcmlwIENvdW50cyIsIA0KICAgICAgIHk9Ik51bWJlciBvZiBTdGF0aW9ucyIsIGNhcHRpb24gPSAiRmlnIDYiKSsNCiAgcGxvdFRoZW1lKCkNCmBgYA0KDQpXZSBjYW4gYWN0dWFsbHkgc2VlIGZyb20gRmlnIDYuIHRoYXQgbWFueSBzdGF0aW9ucyBhcmUgdW51c2VkIGF0IHNvbWUgaG91cnMuIFdlIGNhbiBhbHNvIHNlZSB0aGF0IHNvbWUgcGFydGljdWxhciBzdGF0aW9ucyBoYXZlIGhpZ2ggZGVtYW5kcyAtLSBtb3JlIHRoYW4gMjAgdGltZXMgd2l0aGluIGFuIGhvdXIuDQoNCiMjIEJpa2VzaGFyZSBwZXIgSG91ciBieSBTdGF0aW9uDQoNClRvIHZpc3VhbGl6ZSBpZiB0aGVyZSBpcyBhIGxvY2F0aW9uIGJpYXMgZm9yIGJpa2Ugc2hhcmUsIGEgbWFwIGlzIGNyZWF0ZWQgdG8gc2hvdyBudW1iZXIgb2YgdHJpcHMgdGFrZW4gYXQgZWFjaCBzdGF0aW9uIGFjcm9zcyBkaWZmZXJlbnQgdGltZS4gDQoNCmBgYHtyIG9yaWdpbl9tYXAgfQ0KREFUX01BcCA8LSAgICAgICAgICAgICAgIA0KZGF0X2NlbnN1cyAlPiUgDQogICAgICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIoc3RhcnR0aW1lKSwNCiAgICAgICAgICAgICAgICB3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwNCiAgICAgICAgICAgICAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUNCiAgZ3JvdXBfYnkoc3RhcnQuc3RhdGlvbi5pZCwgc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSwgc3RhcnQuc3RhdGlvbi5sb25naXR1ZGUsIHdlZWtlbmQsIHRpbWVfb2ZfZGF5KSAlPiUNCiAgICAgICAgICAgICAgdGFsbHkoKSAlPiUNCiAgc3RfYXNfc2YoY29vcmRzID0gYygic3RhcnQuc3RhdGlvbi5sb25naXR1ZGUiLCAic3RhcnQuc3RhdGlvbi5sYXRpdHVkZSIpLCBjcnMgPSAiRVBTRzo0MjY5IikgDQoNCkRBVF9NQXAgPC0NCiAgc3RfaW50ZXJzZWN0aW9uKERBVF9NQXAsIEJvc3RvbkNlbnN1cykNCiAgDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gQm9zdG9uVHJhY3RzICU+JQ0KICAgICAgICAgIHN0X3RyYW5zZm9ybShjcnM9IDQzMjYpKSsNCiAgZ2VvbV9zZihkYXRhID0gREFUX01BcCwNCiAgICAgICAgICAgIGFlcyhjb2xvciA9IG4pKSsNCiAgc2NhbGVfY29sb3JfdmlyaWRpcyhkaXNjcmV0ZSA9IEZBTFNFLCBvcHRpb24gPSAiRCIsZGlyZWN0aW9uID0gLTEpKw0KICBmYWNldF9ncmlkKHdlZWtlbmQgfiB0aW1lX29mX2RheSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyIGJ5IHN0YXRpb24uIEJvc3RvblxuLCBBdWd1c3QgYW5kIFNlcHRlbWJlciwgMjAxOSIsIGNvbG9yID0gIlRyaXAgQ291bnQiLCBjYXB0aW9uPSJGaWcgNyIpKw0KICBtYXBUaGVtZSgpDQogIA0KYGBgDQoNCkl0IGlzIGNsZWFyIGZyb20gRmlnIDcgdGhhdCwgbm8gbWF0dGVyIHRoZSB0aW1lLCByaWRlIGRlbWFuZCBzZWVtcyB0byBjbHVzdGVyIG92ZXIgdGhlIGNlbnRlciBhcm91bmQgdGhlIEJhY2sgQmF5IGFyZWEsIHJlcHJlc2VudGVkIGJ5IGRhcmtlciBncmVlbi4gDQoNCkluIHNob3J0LCB3ZSBjYW4gc2VlIHRoYXQgcmlkZSBkZW1hbmQgaXMgdmVyeSB0aW1lIGFuZCBzcGFjZSBkZXBlbmRlbnQuIEFsbCB0aGUgZmlndXJlcyBhYm92ZSBzaG93IHJlLWJhbGFuY2luZyBzaG91bGQgYmUgbW9yZSBmcmVxdWVudCBkdXJpbmcgd2Vla2RheXMgdGhhbiB3ZWVrZW5kcywgYmVjYXVzZSBkZW1hbmQgdmFyaWF0aW9uIGR1cmluZyB3ZWVrZW5kcyBpcyBzbWFsbC4gVGhlIGRlbWFuZCBzcGlrZXMgZHVyaW5nIEFNIGFuZCBQTSBydXNoIGhvdXJzLCBzbyBwcm9iYWJseSBtb3JlIHZhbiBkcml2ZXJzIG5lZWQgdG8gYmUgaGlyZWQgZHVyaW5nIHRoYXQgdGltZSB0byBzaGlmdCBiaWtlcyBtb3JlIGVmZmljaWVudGx5LiANCg0KIyMgUmlkZSBBbmltYXRpb24NCg0KV2UgY2FuIHNob3cgdGhlIGRpZmZlcmVuY2Ugb2YgcmlkZSBkZW1hbmQgYWNyb3NzIHRpbWUgYW5kIHNwYWNlIGJ5IGFuaW1hdGluZyB0aGUgZGF0YS4gRm9yIHRoZSBzYWtlIG9mIGxpbWl0ZWQgY29tcHV0aW5nIGNhcGFjaXR5LCB3ZSB3aWxsIG9ubHkgYW5pbWF0ZSB0aGUgZGVtYW5kIG9uIHRoZSBmaXJzdCBkYXkgb2YgQXVndXN0IGJhc2VkIG9uIGEgMTUgbWludXRlcyBpbnRlcnZhbC4gDQoNCmBgYHtyIEFkZGluZyAxIHdlZWsgUGFuZWwgQW5pbWF0ZWQgTWFwfQ0Kd2VlazMxIDwtDQogIGZpbHRlcihkYXQyLCB3ZWVrID09IDMxICYgZG90dyA9PSAiTW9uIikNCg0Kd2VlazMxLnBhbmVsIDwtDQogIGV4cGFuZC5ncmlkKA0KICAgIGludGVydmFsMTUgPSB1bmlxdWUod2VlazMxJGludGVydmFsMTUpLA0KICAgIHN0YXJ0LnN0YXRpb24ubmFtZSA9IHVuaXF1ZShkYXQyJHN0YXJ0LnN0YXRpb24ubmFtZSkpDQoNCmBgYA0KDQpgYGB7ciByaWRlIGFuaW1hdGlvbiBkYXRhLCBtZXNzYWdlID0gRkFMU0V9DQpzdGF0aW9uIDwtIHJlYWQuY3N2KCJjdXJyZW50X2JsdWViaWtlc19zdGF0aW9ucy5jc3YiKSAlPiUgDQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIkxvbmdpdHVkZSIsICJMYXRpdHVkZSIpLGNycyA9ICJFUFNHOjQyNjkiKQ0KDQpzdGF0aW9uJE5hbWUgPC0NCiAgYXMuY2hhcmFjdGVyKHN0YXRpb24kTmFtZSkgDQoNCnN0YXRpb24gPC0NCiAgc3RfaW50ZXJzZWN0aW9uKHN0YXRpb24sQm9zdG9uQ2Vuc3VzKQ0KDQpyaWRlLmFuaW1hdGlvbi5kYXRhIDwtDQogICAgbXV0YXRlKHdlZWszMSwgVHJpcF9Db3VudGVyID0gMSkgJT4lDQogICAgcmlnaHRfam9pbih3ZWVrMzEucGFuZWwpICU+JSANCiAgICBncm91cF9ieShpbnRlcnZhbDE1LCBzdGFydC5zdGF0aW9uLm5hbWUpICU+JQ0KICAgIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gc3VtKFRyaXBfQ291bnRlciwgbmEucm09VCkpICU+JSANCiAgICB1bmdyb3VwKCkgJT4lIA0KICAgIGxlZnRfam9pbihzdGF0aW9uLCBieT1jKCJzdGFydC5zdGF0aW9uLm5hbWUiID0gIk5hbWUiKSkgJT4lDQogICAgc3Rfc2YoKSAlPiUNCiAgICBtdXRhdGUoVHJpcHMgPSBjYXNlX3doZW4oVHJpcF9Db3VudCA9PSAwIH4gIjAgdHJpcHMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMCAmIFRyaXBfQ291bnQgPD0gMyB+ICIxLTMgdHJpcHMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMyAmIFRyaXBfQ291bnQgPD0gNiB+ICI0LTYgdHJpcHMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gNiAmIFRyaXBfQ291bnQgPD0gMTAgfiAiNy0xMCB0cmlwcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRyaXBfQ291bnQgPiAxMCB+ICIxMSsgdHJpcHMiKSkgJT4lDQogICAgbXV0YXRlKFRyaXBzICA9IGZjdF9yZWxldmVsKFRyaXBzLCAiMCB0cmlwcyIsIjEtMyB0cmlwcyIsIjQtNiB0cmlwcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiNy0xMCB0cmlwcyIsIjEwKyB0cmlwcyIpKQ0KDQpgYGANCg0KYGBge3IgUmlkZXNoYXJlIGFuaW1hdGlvbiwgd2FybmluZyA9IEZBTFNFLCByZXN1bHRzID0gVFJVRX0NCg0KcmlkZXNoYXJlX2FuaW1hdGlvbiA8LQ0KICBnZ3Bsb3QoKSArDQogICAgZ2VvbV9wb2ludChkYXRhID0gc3RfY2VudHJvaWQocmlkZS5hbmltYXRpb24uZGF0YSksIGFlcyhzaXplID0gVHJpcF9Db3VudCxnZW9tZXRyeT1nZW9tZXRyeSksc3RhdD0ic2ZfY29vcmRpbmF0ZXMiKSArDQogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU1KSArDQogICAgbGFicyh0aXRsZSA9ICJCaWtlc2hhcmUgcGlja3VwIGZvciBvbmUgZGF5IGluIEF1Z3VzdCAyMDE5IiwNCiAgICAgICAgIHN1YnRpdGxlID0gIjE1IG1pbnV0ZSBpbnRlcnZhbHM6IHtjdXJyZW50X2ZyYW1lfSIsDQogICAgICAgICBzaXplID0gIlRyaXAgQ291bnQiKSArDQogICAgdHJhbnNpdGlvbl9tYW51YWwoaW50ZXJ2YWwxNSkrDQogIGdlb21fc2YoZGF0YT1Cb3N0b25DZW5zdXMsZmlsbD0idHJhbnNwYXJlbnQiLGNvbG9yID0gImdyYXkiKSsNCiAgbWFwVGhlbWUoKQ0KDQphbmltYXRlKHJpZGVzaGFyZV9hbmltYXRpb24sIGR1cmF0aW9uPTIwLCByZW5kZXJlciA9IGdpZnNraV9yZW5kZXJlcigpKQ0KYGBgDQoNCiMgRGV2ZWxvcCBhbmQgUnVuIFJlZ3Jlc3Npb24gTW9kZWxzIGZvciBEZW1hbmQgUHJlZGljdGlvbg0KDQojIyBDcmVhdGUgU3BhY2UtVGltZSBQYW5lbA0KDQpBIHN0dWR5IHBhbmVsIGlzIGNyZWF0ZWQgdG8gZW5zdXJlIHRoYXQgZXZlcnkgdW5pcXVlIHN0YXRpb24gaXMgaW5jbHVkZWQgYW5kIHdlIHdpbGwgY29udmVydCAiTkEicyBpbnRvIHplcm8sIHNvIHRoYXQgdGhleSBjYW4gYmUgaW5jbHVkZWQgaW4gdGhlIHJlZ3Jlc3Npb24uIFdlIHdpbGwgdGhlbiBqb2luIHRoZSBwYW5lbCB3aXRoIEJvc3RvbiBjZW5zdXMgZGF0YSwgdG8gZW5zdXJlIG9ubHkgdGhlIHN0YXRpb25zIHdpdGhpbiBCb3N0b24gYXJlIGFuYWx5c2VkLiBUaGlzIG5ldyBwYW5lbCBpcyBjYWxsZWQgdGhlIHJpZGUgcGFuZWwuIA0KDQpEdWUgdG8gY29tcHV0ZXIncyBsaW1pdGVkIGNhcGFjaXR5LCBvbmx5IHRoZSBkYXRhIGZyb20gZmlyc3QgZml2ZSB3ZWVrcyBpbiBBdWd1c3QgYW5kIFNlcHRlbWJlciBhcmUgc2VsZWN0ZWQgZm9yIHJpZGUgcGFuZWwuIA0KDQpgYGB7ciBTdHVkeSBQYW5lbCwgY2xhc3Muc291cmNlID0gJ2ZvbGQtc2hvdycsIG1lc3NhZ2UgPSBGQUxTRX0NCnN0dWR5LnBhbmVsIDwtIA0KICBleHBhbmQuZ3JpZChpbnRlcnZhbDYwPXVuaXF1ZShkYXRfY2Vuc3VzJGludGVydmFsNjApLCANCiAgICAgICAgICAgICAgc3RhcnQuc3RhdGlvbi5pZCA9IHVuaXF1ZShkYXRfY2Vuc3VzJHN0YXJ0LnN0YXRpb24uaWQpKSAlPiUNCiAgbGVmdF9qb2luKC4sIGRhdF9jZW5zdXMgJT4lDQogICAgICAgICAgICAgIHNlbGVjdChzdGFydC5zdGF0aW9uLmlkLCBzdGFydC5zdGF0aW9uLm5hbWUsIE9yaWdpbi5UcmFjdCwgc3RhcnQuc3RhdGlvbi5sb25naXR1ZGUsIHN0YXJ0LnN0YXRpb24ubGF0aXR1ZGUgKSU+JQ0KICAgICAgICAgICAgICBkaXN0aW5jdCgpICU+JQ0KICAgICAgICAgICAgICBncm91cF9ieShzdGFydC5zdGF0aW9uLmlkKSAlPiUNCiAgICAgICAgICAgICAgc2xpY2UoMSkpDQpgYGANCg0KDQpgYGB7ciBjcmVhdGVfcGFuZWwgLCBtZXNzYWdlID0gRkFMU0UsIGNsYXNzLnNvdXJjZSA9ICdmb2xkLXNob3cnfQ0KcmlkZS5wYW5lbCA8LSANCiAgZGF0X2NlbnN1cyAlPiUNCiAgbXV0YXRlKFRyaXBfQ291bnRlciA9IDEpICU+JQ0KICByaWdodF9qb2luKHN0dWR5LnBhbmVsKSAlPiUgDQogIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0LnN0YXRpb24uaWQsIHN0YXJ0LnN0YXRpb24ubmFtZSwgT3JpZ2luLlRyYWN0LCBzdGFydC5zdGF0aW9uLmxvbmdpdHVkZSwgc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSkgJT4lDQogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gc3VtKFRyaXBfQ291bnRlciwgbmEucm09VCkpICU+JQ0KICBsZWZ0X2pvaW4od2VhdGhlci5QYW5lbCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgZmlsdGVyKGlzLm5hKHN0YXJ0LnN0YXRpb24uaWQpID09IEZBTFNFKSAlPiUNCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApLA0KICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGZpbHRlcihpcy5uYShPcmlnaW4uVHJhY3QpID09IEZBTFNFKQ0KYGBgDQoNCg0KYGBge3IgY2Vuc3VzX2FuZF9wYW5lbCAsIG1lc3NhZ2UgPSBGQUxTRSwgY2xhc3Muc291cmNlID0gJ2ZvbGQtc2hvdyd9DQpyaWRlLnBhbmVsIDwtIA0KICBsZWZ0X2pvaW4ocmlkZS5wYW5lbCwgQm9zdG9uQ2Vuc3VzICU+JQ0KICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtZ2VvbWV0cnkpLCBieSA9IGMoIk9yaWdpbi5UcmFjdCIgPSAiR0VPSUQiKSkNCmBgYA0KDQojIyBDcmVhdGUgdGltZSBsYWdzDQoNClRpbWUgbGFnIGhlbHBzIHVzIHRvIGRldGVybWluZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGN1cnJlbnQgdHJpcCBudW1iZXIgYW5kIHRoZSBwcmV2aW91cyB0cmlwIG51bWJlciBhdCBhIGdpdmVuIHRpbWUuIEludHVpdGl2ZWx5LCB0aGUgc2hvcnRlciB0aGUgdGltZSBmcmFtZSwgdGhlIHN0cm9uZ2VyIHRoZSBjb3JyZWxhdGlvbi4gRm9yIGV4YW1wbGUsIHRoZSBkZW1hbmRzIGJlZm9yZSBhbmQgYWZ0ZXIgb25lIGhvdXIgaGF2ZSBhIHN0cm9uZ2VyIHJlbGF0aW9uc2hpcCB0aGFuIHRoYXQgb2YgZm91ciBob3Vycy4gQWxzbywgdGhlIGRlbWFuZCB0b2RheSBhdCBhIGdpdmVuIHRpbWUgc2hvdWxkIGJlIHNpbWlsYXIgdG8gdGhlIGRlbWFuZCBhdCB0aGlzIHRpbWUgdG9tb3Jyb3cuIFRoZSBkZW1hbmQgb24gYSAgTW9uZGF5IHNob3VsZCBiZSByZWZsZWN0aXZlIG9mIHRoZSBkZW1hbmQgb24gbmV4dCBNb25kYXkuIA0KDQpUaGUgcmlkZSBwYW5lbCBpcyBjcmVhdGVkIHdpdGggdGltZSBsYWdzIG9mIDEgaG91ciwgMiBob3VycywgMyBob3VycywgNCBob3VycywgMTIgaG91cnMsIDEgZGF5ICgyNCBob3VycyksIGFuZCAxIHdlZWsuIFRoZSBkZW1hbmQgY29ycmVsYXRpb24gaXMgZXZhbHVhdGVkIG9uIGVhY2ggbGFnIGJ5IHItc3F1YXJlZC4gDQoNCmBgYHtyIHRpbWVfbGFncyAsIG1lc3NhZ2UgPSBGQUxTRX0NCnJpZGUucGFuZWwgPC0gDQogIHJpZGUucGFuZWwgJT4lIA0KICBhcnJhbmdlKHN0YXJ0LnN0YXRpb24uaWQsIGludGVydmFsNjApICU+JSANCiAgbXV0YXRlKGxhZ0hvdXIgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMSksDQogICAgICAgICBsYWcySG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMiksDQogICAgICAgICBsYWczSG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMyksDQogICAgICAgICBsYWc0SG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsNCksDQogICAgICAgICBsYWcxMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEyKSwNCiAgICAgICAgIGxhZzFkYXkgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMjQpLA0KICAgICAgICAgbGFnMXdlZWsgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMTY4KSwNCiAgICAgICAgIGhvbGlkYXkgPSBpZmVsc2UoeWRheShpbnRlcnZhbDYwKSA9PSAxNDgsMSwwKSkgJT4lDQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkgJT4lDQogICBtdXRhdGUoaG9saWRheUxhZyA9IGNhc2Vfd2hlbihkcGx5cjo6bGFnKGhvbGlkYXksIDEpID09IDEgfiAiUGx1c09uZURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGFnKGhvbGlkYXksIDIpID09IDEgfiAiUGx1c3RUd29EYXlzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsYWcoaG9saWRheSwgMykgPT0gMSB+ICJQbHVzdFRocmVlRGF5cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAxKSA9PSAxIH4gIk1pbnVzT25lRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDIpID09IDEgfiAiTWludXNUd29EYXlzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDMpID09IDEgfiAiTWludXNUaHJlZURheXMiKSwNCiAgICAgICAgIGhvbGlkYXlMYWcgPSByZXBsYWNlX25hKGhvbGlkYXlMYWcsIDApKQ0KDQpgYGANCg0KYGBge3IgZXZhbHVhdGVfbGFncyAsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdhc2lzJ30NCmFzLmRhdGEuZnJhbWUocmlkZS5wYW5lbCkgJT4lDQogICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIA0KICAgIHN1bW1hcmlzZV9hdCh2YXJzKHN0YXJ0c193aXRoKCJsYWciKSwgIlRyaXBfQ291bnQiKSwgbWVhbiwgbmEucm0gPSBUUlVFKSAlPiUNCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtaW50ZXJ2YWw2MCwgLVRyaXBfQ291bnQpICU+JQ0KICAgIG11dGF0ZShWYXJpYWJsZSA9IGZhY3RvcihWYXJpYWJsZSwgbGV2ZWxzPWMoImxhZ0hvdXIiLCJsYWcySG91cnMiLCJsYWczSG91cnMiLCJsYWc0SG91cnMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxhZzEySG91cnMiLCJsYWcxZGF5IiwibGFnMXdlZWsiKSkpJT4lDQogICAgZ3JvdXBfYnkoVmFyaWFibGUpICU+JSAgDQogICAgc3VtbWFyaXplKGNvcnJlbGF0aW9uID0gcm91bmQoY29yKFZhbHVlLCBUcmlwX0NvdW50KSwyKSklPiUNCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSAxLiBCaWtlcmlkZSBEZW1hbmQgQ29ycmVsYXRpb24gYmFzZWQgb24gVGltZSBMYWcgTWVhc3VyZWQgYnkgUi1zcXVhcmUiKSU+JWthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNClRhYmxlIDEgdmFsaWRhdGVzIG91ciBoeXBvdGhlc2lzLiBXZSBzZWUgdGhhdCAxIGhvdXIgbGFnLCAxIGRheSBsYWcsIGFuZCAxIHdlZWsgbGFnIGhhdmUgdGhlIHN0cm9uZ2VzdCBjb3JyZWxhdGlvbiBiZXR3ZWVuIGRlbWFuZHMuIFRoZSB3ZWVrIGxhZyBpcyB0aGUgc3Ryb25nZXN0LCBtZWFuaW5nIHRoZSB3ZWVrbHkgZGVtYW5kIGZvciBiaWtlIHJpZGVzIGlzIHZlcnkgc2ltaWxhci4NCg0KDQojIyBNb2RlbHMNCg0KVGhlIHJpZGUgcGFuZWwgaXMgZnVydGhlciBzZXBhcmF0ZWQgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0aW5nIHNldC4gV2Ugd2lsIHRyYWluIHRoZSBtb2RlbCBvbiB0aGUgZmlyc3QgdGhyZWUgd2Vla3MsIGFuZCB0ZXN0IGl0IG9uIHRoZSBsYXN0IHR3byB3ZWVrcy4gV2UgZG9uJ3QgcmFuZG9tbHkgZ2VuZXJhdGUgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cywgYmVjYXVzZSB0aGV5IGFyZSB2ZXJ5IHRpbWUgZGVwZW5kZW50LiANCg0KVGhlIGRhdGEgZm9yIGFsbCBmaXZlIHdlZWtzIHdpbGwgYmUgY29tYmluZWQgdG8gYSBuZXcgcGFuZWwuIFRoaXMgcGFuZWwgaXMgZ29pbmcgdG8gYmUgdXNlZCBmb3IgY3Jvc3MtdmFsaWRhdGlvbi4gDQoNCmBgYHtyIHRyYWluX3Rlc3QgfQ0KcmlkZS5UcmFpbiA8LSBmaWx0ZXIocmlkZS5wYW5lbCwgd2VlayA9PSAzMSB8IHdlZWsgPT0gMzIgfCB3ZWVrID09IDMzKQ0KcmlkZS5UZXN0IDwtIGZpbHRlcihyaWRlLnBhbmVsLCB3ZWVrID09IDM0IHwgd2VlayA9PSAzNSkNCnJpZGUucGFuZWw1d2VlayA8LSByYmluZChyaWRlLlRyYWluLHJpZGUuVGVzdCkNCmBgYA0KDQpgYGB7ciBmb3VyIG1vZGVscywgY2xhc3Muc291cmNlID0gJ2ZvbGQtc2hvdyd9DQpyZWcxIDwtIA0KICAgIGxtKFRyaXBfQ291bnQgfiAgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSwgIGRhdGE9cmlkZS5UcmFpbikNCg0KcmVnMiA8LSANCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydC5zdGF0aW9uLm5hbWUgKyBkb3R3ICsgVGVtcGVyYXR1cmUsICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzMgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnQuc3RhdGlvbi5uYW1lICsgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24sIA0KICAgICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzQgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnQuc3RhdGlvbi5uYW1lICsgIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsgbGFnSG91ciArIGxhZzJIb3VycyArbGFnM0hvdXJzICsgbGFnMTJIb3VycyArIGxhZzFkYXkgKyBsYWcxd2VlaywgDQogICAgIGRhdGE9cmlkZS5UcmFpbikNCmBgYA0KDQpGb3VyIG1vZGVscyBhcmUgZGV2ZWxvcGVkIGhlcmUuIFJlZzEgY29uc2lkZXJzIGhvdXIsIGRheSBvZiB0aGUgd2VlaywgdGVtcGVyYXR1cmUgYXMgZmFjdG9ycyBvZiByaWRlIHNoYXJlIGRlbWFuZC4gUmVnMiBhZGRzIGEgbG9jYXRpb24gZmFjdG9yIChzdGF0aW9uLnN0YXRpb24ubmFtZSkuIFJlZzMgYWRkcyBwcmVjaXBpdGF0aW9uIGFzIGFub3RoZXIgZmFjdG9yIG9uIHRvIFJlZzIuUmVnNCBoYXMgZXZlcnl0aGluZyBmcm9tIFJlZzMsIGJ1dCBhbHNvIGluY2x1ZGluZyBsYWcgdGltZSBmYWN0b3JzLiBXZSB3aWxsIGV4YW1pbmUgaG93IGVhY2ggcmVncmVzc2lvbiBwZXJmb3JtIGJhc2VkIG9uIGVycm9ycy4NCg0KIyMgUHJlZGljdCBmb3IgdGVzdCBkYXRhDQoNCldlIHdpbGwgcHJlZGljdCB0aGUgZGF0YSBiYXNlZCBvbiB0aGUgdGVzdCBzZXQuIEhlcmUsIHdlIHdpbGwgbmVzdCB0aGUgZGF0YSBpbiB0byBtYXRyaXguIE5lc3RpbmcgYWxsb3dzIHVzIHRvIHJ1biBtb2RlbHMgb3ZlciB0d28gd2VlayBhdCBvbmNlLCBpbnN0ZWFkIG9mIHNlcGFyYXRpbmcgZWFjaCB3ZWVrIG91dCBhbmQgcnVuIHRoZSByZWdyZXNzaW9uIGluZGl2aWR1YWxseS4gDQoNCkZvdXIgcmVncmVzc2lvbnMgYXJlIHJ1biBvdmVyIHRoZSB0d28gd2Vla3MgaW4gdGhlIHRlc3Rpbmcgc2V0LiANCg0KYGBge3IgbmVzdF9kYXRhLCBtZXNzYWdlID0gRkFMU0V9DQpyaWRlLlRlc3Qud2Vla05lc3QgPC0gDQogIHJpZGUuVGVzdCAlPiUNCiAgbmVzdCgtd2VlaykgDQpgYGANCg0KYGBge3IgcHJlZGljdF9mdW5jdGlvbiB9DQptb2RlbF9wcmVkIDwtIGZ1bmN0aW9uKGRhdCwgZml0KXsNCiAgIHByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXQpfQ0KYGBgDQoNCmBgYHtyIGRvX3ByZWRpY2l0b25zLCByZXN1bHRzID0gVFJVRX0NCndlZWtfcHJlZGljdGlvbnMgPC0gDQogIHJpZGUuVGVzdC53ZWVrTmVzdCAlPiUgDQogICAgbXV0YXRlKEFUaW1lX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMSwgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICAgICAgICAgQlNwYWNlX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMiwgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICAgICAgICAgQ1RpbWVfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWczLCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgICAgICAgICBEVGltZV9TcGFjZV9GRV90aW1lTGFncyA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzQsIC5mID0gbW9kZWxfcHJlZCkpJT4lICAgICBnYXRoZXIoUmVncmVzc2lvbiwgUHJlZGljdGlvbiwgLWRhdGEsIC13ZWVrKSAlPiUNCiAgICBtdXRhdGUoT2JzZXJ2ZWQgPSBtYXAoZGF0YSwgcHVsbCwgVHJpcF9Db3VudCksDQogICAgICAgICAgIEFic29sdXRlX0Vycm9yID0gbWFwMihPYnNlcnZlZCwgUHJlZGljdGlvbiwgIH4gYWJzKC54IC0gLnkpKSwNCiAgICAgICAgICAgTUFFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgbWVhbiwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgc2RfQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBzZCwgbmEucm0gPSBUUlVFKSkNCg0Kd2Vla19wcmVkaWN0aW9ucw0KYGBgDQoNClRoZSByZXR1cm5lZCAidGliYmxlIiAoYW4gb3RoZXIgZm9ybSBvZiB0YWJsZSkgc2hvd3MgdGhlIG1lYW4gYWJzb2x1dGUgZXJyb3IgZm9yIGVhY2ggcmVncmVzc2lvbi4gV2UgY2FuIHNlZSB0aGUgcmVncmVzc2lvbiB0aGF0IGNvbnNpZGVyIHRpbWUsIHNwYWNlLCB3ZWF0aGVyLCBhbmQgdGltZSBsYWdzIChSZWc0KSBoYXMgdGhlIGxvd2VzdCBtZWFuIGFic29sdXRlIGVycm9yLiBPdGhlcnMgcmVncmVzc2lvbiBoYXZlIGVycm9ycyBhdCBhIHJlbGF0aXZlbHkgaGlnaCBsZXZlbCwgbWVhbmluZyB0aGF0IHRpbWUgbGFnIHJlYWxseSBtYWRlIGEgZGlmZmVyZW5jZSBoZXJlLg0KDQojIyBFeGFtaW5lIEVycm9yIE1ldHJpY3MgZm9yIEFjY3VyYWN5DQoNCkZpZyA4IHZpc3VhbGl6ZXMgdGhlIGNvbXBhcmlzb24gb2YgZXJyb3JzIGFjcm9zcyB0aGUgZm91ciBtb2RlbHMuIA0KDQpgYGB7ciBwbG90X2Vycm9yc19ieV9tb2RlbCB9DQp3ZWVrX3ByZWRpY3Rpb25zICU+JQ0KICBkcGx5cjo6c2VsZWN0KHdlZWssIFJlZ3Jlc3Npb24sIE1BRSkgJT4lDQogIGdhdGhlcihWYXJpYWJsZSwgTUFFLCAtUmVncmVzc2lvbiwgLXdlZWspICU+JQ0KICBnZ3Bsb3QoYWVzKHdlZWssIE1BRSkpICsgDQogICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBSZWdyZXNzaW9uKSwgcG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0PSJpZGVudGl0eSIpICsNCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlNSkgKw0KICAgIGxhYnModGl0bGUgPSAiTWVhbiBBYnNvbHV0ZSBFcnJvcnMgYnkgbW9kZWwgc3BlY2lmaWNhdGlvbiBhbmQgd2VlayIsIGNhcHRpb24gPSAiRmlnIDgiKSArDQogIHBsb3RUaGVtZSgpDQpgYGANCg0KYGBge3IgZXJyb3JfdnNfYWN0dWFsX3RpbWVzZXJpZXMgLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBzdGFydC5zdGF0aW9uLmlkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0LnN0YXRpb24uaWQpKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KGludGVydmFsNjAsIHN0YXJ0LnN0YXRpb24uaWQsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uKSAlPiUNCiAgICB1bm5lc3QoKSAlPiUNCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtUmVncmVzc2lvbiwgLWludGVydmFsNjAsIC1zdGFydC5zdGF0aW9uLmlkKSAlPiUNCiAgICBncm91cF9ieShSZWdyZXNzaW9uLCBWYXJpYWJsZSwgaW50ZXJ2YWw2MCkgJT4lDQogICAgc3VtbWFyaXplKFZhbHVlID0gc3VtKFZhbHVlKSkgJT4lDQogICAgZ2dwbG90KGFlcyhpbnRlcnZhbDYwLCBWYWx1ZSwgY29sb3VyPVZhcmlhYmxlKSkgKyANCiAgICAgIGdlb21fbGluZShzaXplID0gMS4xKSArIA0KICAgICAgZmFjZXRfd3JhcCh+UmVncmVzc2lvbiwgbmNvbD0xKSArDQogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBiaWtlIHNoYXJlIHRpbWUgc2VyaWVzIiwgc3VidGl0bGUgPSAiQm9zdG9uOyBBIHRlc3Qgc2V0IG9mIDIgd2Vla3MiLCAgeCA9ICJIb3VyIiwgeT0gIlN0YXRpb24gVHJpcHMiLCBjYXB0aW9uID0gIkZpZyA5IikgKw0KICAgICAgcGxvdFRoZW1lKCkNCmBgYA0KDQpBZ2FpbiwgYXBwYXJlbnRseSwgUmVnIDQgd2l0aCB0aW1lIGxhZ3MgaGFzIHRoZSBsb3dlc3QgbWVhbiBhYnNvbHV0ZSBlcnJvciAoTUFFKSBiZWxvdyAxIGZvciBib3RoIHdlZWtzLiBGaWcgOSBzaG93cyB0aGUgcHJlZGljdGVkIHRyaXAgY291bnQgbGF5IG92ZXIgb2JzZXJ2ZWQgdHJpcCBjb3VudC4gUmVnIDQgd2l0aCB0aW1lIGxhZ3MgZml0IHRoZSBiZXN0LiANCg0KIyMgRXJyb3IgY2x1c3RlcmluZw0KDQpEbyB0aGVzZSBlcnJvcnMgY2x1c3RlciBsaWtlIGRlbWFuZCBiYXNlZCBvbiBsb2NhdGlvbj8gV2Ugd2lsbCB0YWtlIG91ciBiZXN0IG1vZGVsIG9mIGZpdCAoUmVnIDQpIHRvIG1hcCBvdXQgZXJyb3JzIGluIEJvc3Rvbi4gDQoNCmBgYHtyIGVycm9yc19ieV9zdGF0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSB9DQp3ZWVrX3ByZWRpY3Rpb25zICU+JSANCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwNCiAgICAgICAgICAgc3RhcnQuc3RhdGlvbi5pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydC5zdGF0aW9uLmlkKSwgDQogICAgICAgICAgIHN0YXJ0LnN0YXRpb24ubGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSksIA0KICAgICAgICAgICBzdGFydC5zdGF0aW9uLmxvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBzdGFydC5zdGF0aW9uLmxvbmdpdHVkZSkpICU+JQ0KICAgIHNlbGVjdChpbnRlcnZhbDYwLCBzdGFydC5zdGF0aW9uLmlkLCBzdGFydC5zdGF0aW9uLmxvbmdpdHVkZSwgc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRFRpbWVfU3BhY2VfRkVfdGltZUxhZ3MiKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnQuc3RhdGlvbi5pZCwgc3RhcnQuc3RhdGlvbi5sb25naXR1ZGUsIHN0YXJ0LnN0YXRpb24ubGF0aXR1ZGUpICU+JQ0KICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lDQpnZ3Bsb3QoLikrDQogIGdlb21fc2YoZGF0YSA9IEJvc3RvbkNlbnN1cywgY29sb3IgPSAiZ3JleSIsIGZpbGwgPSAidHJhbnNwYXJlbnQiKSsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0LnN0YXRpb24ubG9uZ2l0dWRlLCB5ID0gc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSwgY29sb3IgPSBNQUUpLCANCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IiwgYWxwaGEgPSAwLjQpKw0KICBzY2FsZV9jb2xvdXJfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwNCiAgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiKSsNCiAgbGFicyh0aXRsZT0iTWVhbiBBYnMgRXJyb3IsIFRlc3QgU2V0LCBNb2RlbCA0IiwgY2FwdGlvbiA9ICJGaWcgMTAiKSsNCiAgbWFwVGhlbWUoKQ0KYGBgDQoNCkluZGVlZCwgdGhlIG1vZGVsIGlzIGxlc3MgYWNjdXJhdGUgYXQgdGhlIGNlbnRlcmFsIGJheSBhcmVhIGFjY29yZGluZyB0byBGaWcgMTAuIFRoaXMgbWVhbnMgb3VyIG1vZGVsIG1heSBwcmVkaWN0IGxlc3MgYWNjdXJhdGVseSBhcm91bmQgdGhhdCBhcmVhLiBIb3dldmVyLCBtb3N0IG9mIE1BRXMgYXJlIG5vdCB0b28gaGlnaCBhbmQgZG8gbm90IGV4Y2VlZCAzLCB3aGljaCBpcyBzdGlsbCBhY2NlcHRhYmxlLiANCg0KIyMgU3BhY2UtVGltZSBFcnJvciBFdmFsdWF0aW9uDQoNCldlIGFyZSBnb2luZyB0byBkaXNzZWN0IG91ciBwcmVkaWN0aW9uIGEgbGl0dGxlIG1vcmUuIEhlcmUgd2UgYXJlIGRldGVybWluaW5nIHdldGhlciB0aGUgZXJyb3JzIGNsdXN0ZXIgYmFzZWQgb24gdGltZS4gDQoNCmBgYHtyIG9ic19wcmVkX2FsbCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZT1UUlVFfQ0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgDQogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksDQogICAgICAgICAgIHN0YXJ0LnN0YXRpb24uaWQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnQuc3RhdGlvbi5pZCksIA0KICAgICAgICAgICBzdGFydC5zdGF0aW9uLmxhdGl0dWRlID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0LnN0YXRpb24ubGF0aXR1ZGUpLCANCiAgICAgICAgICAgc3RhcnQuc3RhdGlvbi5sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnQuc3RhdGlvbi5sb25naXR1ZGUpLA0KICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnQuc3RhdGlvbi5pZCwgc3RhcnQuc3RhdGlvbi5sb25naXR1ZGUsIA0KICAgICAgICAgICBzdGFydC5zdGF0aW9uLmxhdGl0dWRlLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbiwNCiAgICAgICAgICAgZG90dykgJT4lDQogICAgdW5uZXN0KCkgJT4lDQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJEVGltZV9TcGFjZV9GRV90aW1lTGFncyIpJT4lDQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwNCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChhZXMoeD0gT2JzZXJ2ZWQsIHkgPSBQcmVkaWN0aW9uKSkrDQogICAgZ2VvbV9zbW9vdGgoYWVzKHg9IE9ic2VydmVkLCB5PSBQcmVkaWN0aW9uKSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3IgPSAicmVkIikrDQogICAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKSsNCiAgZmFjZXRfZ3JpZCh0aW1lX29mX2RheX53ZWVrZW5kKSsNCiAgbGFicyh0aXRsZT0iT2JzZXJ2ZWQgdnMgUHJlZGljdGVkIiwNCiAgICAgICB4PSJPYnNlcnZlZCB0cmlwcyIsIA0KICAgICAgIHk9IlByZWRpY3RlZCB0cmlwcyIsIGNhcHRpb24gPSJGaWcgMTEiKSsNCiAgcGxvdFRoZW1lKCkNCmBgYA0KDQpCbGFjayBsaW5lcyBpbiBGaWcgMTEgc2hvdyBwZXJmZWN0IHByZWRpY3Rpb25zLiBUaGUgcmVkbGluZSBzaG93cyB0aGUgbGluZWFyIHJlc3VsdCBiYXNlZCBvbiByZWc0LiBUaGUgcmVkIGxpbmVzIGFyZSBiZWxvdyB0aGUgYmxhY2sgbGluZXMsIG1lYW5pbmcgd2UgYXJlIHVuZGVyLXByZWRpY3RpbmcgaW4gZ2VuZXJhbC4gVGhlIGVycm9yIHZhcmllcyBtb3JlIGxhcmdlbHkgZHVyaW5nIHRoZSB3ZWVrZGF5cyBhbmQgYXQgQU0gcnVzaCBhbmQgUE0gcnVzaC4gVGhpcyBpcyB1bmRlcnN0YW5kYWJsZSBiZWNhdXNlIHRoZSBhY3R1YWwgdHJpcHMgYWxzbyB2YXJ5IGR1cmluZyB0aGVzZSBob3VycyBhbmQgZGF5cywgd2hlcmVhcyB0aGUgdHJpcCBwYXR0ZXJuIGR1cmluZyB3ZWVrZW5kcyBhbmQgbm9uLXJ1c2ggaG91cnMgYXJlIGZhaXJseSBjb25zaXN0ZW50LiANCg0KIyMgRGV0YWlsZWQgVGltZS1TcGFjZSBFcnJvciBNYXANCg0KRmlnIDEwIHNob3dzIHRoZSBhZ2dyZWdhdGVkIHJlc3VsdCBmb3IgTUFFIGFjcm9zcyBhbGwgdGltZXMuIEhlcmUsIHdlIGFyZSBnb2luZyB0byBsb29rIGF0IE1BRSBhdCBkaWZmZXJlbnQgdGltZSBwZXJpb2QgYW5kIHNlZSBpZiBlcnJvcnMgYXJlIHN0aWxsIGxvY2F0aW9uIGJpYXNlZCBkdXJpbmcgZGlmZmVyZW50IHRpbWUuDQoNCmBgYHtyIHN0YXRpb25fc3VtbWFyeSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZSA9IEZBTFNFIH0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBzdGFydC5zdGF0aW9uLmlkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0LnN0YXRpb24uaWQpLCANCiAgICAgICAgICAgc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBzdGFydC5zdGF0aW9uLmxhdGl0dWRlKSwgDQogICAgICAgICAgIHN0YXJ0LnN0YXRpb24ubG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0LnN0YXRpb24ubG9uZ2l0dWRlKSwNCiAgICAgICAgICAgZG90dyA9IG1hcChkYXRhLCBwdWxsLCBkb3R3KSkgJT4lDQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0LnN0YXRpb24uaWQsIHN0YXJ0LnN0YXRpb24ubG9uZ2l0dWRlLCANCiAgICAgICAgICAgc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sDQogICAgICAgICAgIGRvdHcpICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRFRpbWVfU3BhY2VfRkVfdGltZUxhZ3MiKSU+JQ0KICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksDQogICAgICAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSkgJT4lDQogIGdyb3VwX2J5KHN0YXJ0LnN0YXRpb24uaWQsIHdlZWtlbmQsIHRpbWVfb2ZfZGF5LCBzdGFydC5zdGF0aW9uLmxvbmdpdHVkZSwgc3RhcnQuc3RhdGlvbi5sYXRpdHVkZSkgJT4lDQogIHN1bW1hcml6ZShNQUUgPSBtZWFuKGFicyhPYnNlcnZlZC1QcmVkaWN0aW9uKSwgbmEucm0gPSBUUlVFKSklPiUNCiAgZ2dwbG90KC4pKw0KICBnZW9tX3NmKGRhdGEgPSBCb3N0b25DZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGdlb21fcG9pbnQoYWVzKHggPSBzdGFydC5zdGF0aW9uLmxvbmdpdHVkZSwgeSA9IHN0YXJ0LnN0YXRpb24ubGF0aXR1ZGUsIGNvbG9yID0gTUFFKSwgDQogICAgICAgICAgICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIsIHNpemUgPSAwLjUsIGFscGhhID0gMC40KSsNCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXMoZGlyZWN0aW9uID0gLTEsDQogIGRpc2NyZXRlID0gRkFMU0UsIG9wdGlvbiA9ICJEIikrDQogIGZhY2V0X2dyaWQod2Vla2VuZH50aW1lX29mX2RheSkrDQogIGxhYnModGl0bGU9Ik1lYW4gQWJzb2x1dGUgRXJyb3JzLCBUZXN0IFNldCIsIGNhcHRpb24gPSAiRmlnIDEyIikrDQogIG1hcFRoZW1lKCkNCmBgYA0KDQpGaWcgMTIgZG9lcyBzaG93IHRoYXQgZXJyb3JzIGNsdXN0ZXIgYXQgdGhlIGNlbnRyYWwgYmF5IGFyZWEgcmVnYXJkbGVzcyBvZiB0aW1lLiBUaGUgbW9kZWwgcHJlZGljdHMgdGhlIHdvcnN0IGF0IFBNIHJ1c2ggaG91cnMgYXQgdGhlIGNlbnRyYWwgYmF5IGFyZWEuIEl0IGlzIGdvb2QgdGhhdCB3ZSBkb24ndCBzZWUgbXVjaCBibHVlIG9uIHRoZSBtYXAsIGluZGljYXRpbmcgYSBoaWdoZXIgTUFFLiBNb3N0IE1BRXMgYXJlIGluIHRoZSByYW5nZSBvZiAwIHRvIDMuIE1vc3Qgb2YgdGhlbSBhcmUgdW5kZXItcHJlZGljdGVkLiBUaGlzIG9ic2VydmF0aW9uIGNvdWxkIHJlbWluZCB0aGUgY29tcGFueSB0aGF0IGR1cmluZyBydXNoIGhvdXJzLCBpdCBpcyBwcm9iYWJseSB3aXNlIHRvIGFkZCAzIGJpa2VzIG9uIHRvcCBvZiB3aGF0IHRoZSBtb2RlbCBwcmVkaWN0cyBkdXJpbmcgcnVzaCBob3VycyBhdCBoaWdoIGRlbWFuZCBhcmVhIHdoZW4gcmUtYmFsYW5jaW5nIHRoZSBiaWtlcy4gDQoNCiMgQ3Jvc3MtdmFsaWRhdGlvbjogSy1Gb2xkDQoNCldlIGp1c3Qgc2VlIHNvbWUgZXJyb3IgY2x1c3RlcmluZyBiYXNlZCBvbiB0aW1lIGFuZCBzcGFjZS4gU28gY2FuIG91ciBtb2RlbCBnZW5lcmFsaXplIHdlbGw/IFRoaXMgc2VjdGlvbiBydW5zIHR3byBrLWZvbGQgdmFsaWRhdGlvbjogb25lIG9uIHN0YXRpb24gbG9jYXRpb24gYW5kIG9uZSBvbiB0aW1lIGFjcm9zcyBhbGwgZml2ZSB3ZWVrcyB3ZSB1c2VkIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZy4gDQoNCmBgYHtyIEstRm9sZCwgbWVzc2FnZSA9IEZBTFNFfQ0KDQpyaWRlLnBhbmVsNXdlZWsgPC0NCiAgcmlkZS5wYW5lbDV3ZWVrICU+JQ0KICBtdXRhdGUoaG91cj1ob3VyKGludGVydmFsNjApKQ0KDQpyZWcudmFycyA8LQ0KICBjKCJob3VyIiwgImRvdHciLCAiVGVtcGVyYXR1cmUiLCJQcmVjaXBpdGF0aW9uIiwgImxhZ0hvdXIiLCJsYWcySG91cnMiLCJsYWczSG91cnMiLCJsYWcxMkhvdXJzIiwibGFnMWRheSIsImxhZzF3ZWVrIikNCg0KcmlkZS5wYW5lbDV3ZWVrIDwtDQogIHJpZGUucGFuZWw1d2VlayAlPiUNCiAgc3RfYXNfc2YoY29vcmRzID0gYygic3RhcnQuc3RhdGlvbi5sb25naXR1ZGUiLCAic3RhcnQuc3RhdGlvbi5sYXRpdHVkZSIpKQ0KDQoNCnJlZy5jdiA8LSBjcm9zc1ZhbGlkYXRlKA0KICBkYXRhc2V0ID0gcmlkZS5wYW5lbDV3ZWVrLA0KICBpZCA9ICJzdGFydC5zdGF0aW9uLm5hbWUiLA0KICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJUcmlwX0NvdW50IiwNCiAgaW5kVmFyaWFibGVzID0gcmVnLnZhcnMpICU+JQ0KICAgIGRwbHlyOjpzZWxlY3Qoc3RhcnQuc3RhdGlvbi5uYW1lID0gc3RhcnQuc3RhdGlvbi5uYW1lLCBUcmlwX0NvdW50LCBQcmVkaWN0aW9uKQ0KDQpyZWcuY3YudGltZSA8LSBjcm9zc1ZhbGlkYXRlKA0KICBkYXRhc2V0ID0gcmlkZS5wYW5lbDV3ZWVrLA0KICBpZCA9ICJkb3R3IiwNCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiVHJpcF9Db3VudCIsDQogIGluZFZhcmlhYmxlcyA9IHJlZy52YXJzKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KCJEYXlfb2ZfdGhlX1dlZWsiID0gZG90dywgVHJpcF9Db3VudCwgUHJlZGljdGlvbikNCg0KcmVnLnN1bW1hcnkuc3BhY2UgPC0NCiAgbXV0YXRlKHJlZy5jdiwgRXJyb3IgPSBQcmVkaWN0aW9uIC0gVHJpcF9Db3VudCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVncmVzc2lvbiA9ICJSYW5kb20gay1mb2xkIENWOiBTcGFjZSAoU3RhdGlvbikiKQ0KDQpyZWcuc3VtbWFyeS50aW1lIDwtDQogIHJlZy5jdi50aW1lICU+JQ0KICBtdXRhdGUocmVnLmN2LnRpbWUsIEVycm9yID0gUHJlZGljdGlvbiAtIFRyaXBfQ291bnQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZ3Jlc3Npb24gPSAiUmFuZG9tIGstZm9sZCBDVjogVGltZSAoRGF5IG9mIHRoZSAgIFdlZWspIikNCg0KYGBgDQoNCmBgYHtyIE1lYW4gTUFFLCByZXN1bHRzID0gJ2FzaXMnfQ0KZXJyb3IuZm9sZC5zcGFjZSA8LSANCiAgcmVnLnN1bW1hcnkuc3BhY2UgJT4lDQogICAgZ3JvdXBfYnkoc3RhcnQuc3RhdGlvbi5uYW1lKSAlPiUgDQogICAgc3VtbWFyaXplKE1lYW5fRXJyb3IgPSBtZWFuKFByZWRpY3Rpb24gLSBUcmlwX0NvdW50LCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgICBNQUUgPSBtZWFuKGFicyhNZWFuX0Vycm9yKSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgICAgU0RfTUFFID0gbWVhbihhYnMoTWVhbl9FcnJvciksIG5hLnJtID0gVCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KcGxvdDE8LQ0KICBlcnJvci5mb2xkLnNwYWNlICU+JQ0KICBnZ3Bsb3QoYWVzKE1BRSkpICsgDQogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBjb2xvdXI9ImJsYWNrIiwgZmlsbCA9ICIjRkRFNzI1RkYiKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCkgKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDgsIGJ5ID0gMSkpICsgDQogICAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIE1BRSIsIHN1YnRpdGxlID0gImstZm9sZCBjcm9zcyB2YWxpZGF0aW9uOiBTdGF0aW9uIExvY2F0aW9uIiwNCiAgICAgICAgIHg9Ik1lYW4gQWJzb2x1dGUgRXJyb3IiLCB5PSJDb3VudCIpICsNCiAgICBwbG90VGhlbWUoKQ0KDQplcnJvci5mb2xkLnRpbWUgPC0gDQogIHJlZy5zdW1tYXJ5LnRpbWUgJT4lDQogICAgZ3JvdXBfYnkoRGF5X29mX3RoZV9XZWVrKSAlPiUgDQogICAgc3VtbWFyaXplKE1lYW5fRXJyb3IgPSBtZWFuKFByZWRpY3Rpb24gLSBUcmlwX0NvdW50LCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgICBNQUUgPSBtZWFuKGFicyhNZWFuX0Vycm9yKSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgICAgU0RfTUFFID0gbWVhbihhYnMoTWVhbl9FcnJvciksIG5hLnJtID0gVCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KcGxvdDIgPC0NCmVycm9yLmZvbGQudGltZSAlPiUNCiAgZ2dwbG90KGFlcyhNQUUpKSArIA0KICAgIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCwgY29sb3VyPSJibGFjayIsIGZpbGwgPSAiI0ZERTcyNUZGIikgKw0KICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDApICsgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAwLjIsIGJ5ID0gMC4wMSkpICsgDQogICAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIE1BRSIsIHN1YnRpdGxlID0gImstZm9sZCBjcm9zcyB2YWxpZGF0aW9uOiBEYXkgb2YgdGhlIFdlZWsiLA0KICAgICAgICAgeD0iTWVhbiBBYnNvbHV0ZSBFcnJvciIsIHk9IkNvdW50IikrDQogIHBsb3RUaGVtZSgpDQoNCmdyaWQuYXJyYW5nZShwbG90MSxwbG90Mix0b3A9IkZpZyAxMy4gSy1Gb2xkIG9mIEJpa2VzaGFyZSBUcmlwIENvdW50cyBieSBTdGF0aW9uIGFuZCBEYXkgb2YgdGhlIFdlZWsiKQ0KDQpzdF9kcm9wX2dlb21ldHJ5KGVycm9yLmZvbGQuc3BhY2UpICU+JQ0KICAgc3VtbWFyaXplKE1lYW5fTUFFID0gcm91bmQobWVhbihNQUUpLCAyKSwNCiAgICAgICAgICAgICAgU0RfTUFFID0gcm91bmQoc2QoTUFFKSwgMikpICU+JQ0KICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDIuIEstZm9sZCBFcnJvciBieSBTdGF0aW9uIExvY2F0aW9uIC0gTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSBhbmQgU3RhbmRhcmQgRGV2aWF0aW9uIG9mIE1BRSIpICU+JQ0KICAgIGthYmxlX3N0eWxpbmcoKQ0KDQpzdF9kcm9wX2dlb21ldHJ5KGVycm9yLmZvbGQudGltZSkgJT4lDQogIHN1bW1hcml6ZShNZWFuX01BRSA9IHJvdW5kKG1lYW4oTUFFKSwgMiksDQogICAgICAgICAgICAgIFNEX01BRSA9IHJvdW5kKHNkKE1BRSksIDIpKSAlPiUNCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSAzLiBLLWZvbGQgRXJyb3IgYnkgRGF5IG9mIHRoZSBXZWVrIC0gTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSBhbmQgU3RhbmRhcmQgRGV2aWF0aW9uIG9mIE1BRSIpICU+JQ0KICAgIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCkZpZyAxMiBwbG90cyB0aGUgZGlzdHJpYnV0aW9uIG9mIE1BRSBiYXNlZCBvbiB0aW1lIGFuZCBzcGFjZS4gTW9zdCBvZiB0aGUgTUFFcyBvbiBzdGF0aW9uIGxvY2F0aW9uIGFyZSBjb25jZW50cmF0ZWQgYXJvdW5kIDEsIGZvbGxvd2VkIGJ5IDAuIFRoaXMgc2hvd3MgdGhhdCBvdXIgbW9kZWwgZ2VuZXJhbGl6ZXMgZmluZSBvdmVyIHN0YXRpb24gbG9jYXRpb24sIG1vc3RseSBvbmx5IG9mZiBieSAxIHRyaXAgY291bnQuDQoNCkl0IGdlbmVyYWxpemVzIGV4Y2VwdGlvbmFsbHkgZm9yIHRpbWUuIEZpZyAxMyBzaG93cyB0aGF0IHRoZSBNQUVzIGZvciBrLWZvbGQgYmFzZWQgb24gdGltZSBhcmUgd2VsbCBiZWxvdyAwLCBhbHRlcmluZyBiZXR3ZWVuIDAuMDEgYW5kIDAuMTUuIA0KDQpUYWJsZSAyIGFuZCAzIHNob3cgdGhlIG1lYW4gb2YgTUFFIGZvciBrLWZvbGQgb24gc3BhY2UgYW5kIHRpbWUgcmVzcGVjdGl2ZWx5LiBBZ2FpbiwgVGFibGUgMyBzdWdnZXN0cyB0aGF0IG91ciBtb2RlbCBnZW5lcmFsaXplcyBzdXBlcmJseSBvbiB0aW1lIC0gd2l0aCBvbmx5IDAuMDUgbWVhbiBNQUUgYW5kIDAuMDUgTUFFIHN0YW5kYXJkIGRldmlhdGlvbi4gSXQgYWxzbyBkb2VzIG5vdCBnZW5lcmFsaXplIHRvbyBiYWRseSBiYXNlZCBvbiBnZW9ncmFwaHkuIFRoZSBNQUUgaXMgY29udHJvbGxlZCB1bmRlciAxLCBhbmQgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBpcyB1bmRlciAyLCBtZWFuaW5nIHRoZSBNQUUgZG9lcyBub3QgdmFyeSB0b28gbXVjaC4gDQoNCiMgQ29uY2x1c2lvbg0KDQpJbiB0aGlzIGFuYWx5c2lzLCB3ZSBmb3VuZCBvdXQgdGhhdCB0aGUgbW9kZWwgb2YgdGhlIGJlc3QgZml0IGlzIHRoZSBvbmUgdGhhdCBpbmNvcnBvcmF0ZXMgbG9jYXRpb24sIHdlYXRoZXIsIGFuZCB0aW1lIGxhZ3MuIEFmdGVyIHRyYWluaW5nLCB0ZXN0aW5nLCBhbmQgY3Jvc3MtdmFsaWRhdGluZyBvdXIgbW9kZWwsIHRoZSBtb2RlbCBzZWVtcyB0byBwcmVkaWN0IGVmZmVjdGl2ZWx5ICh3aXRoIG1lYW4gYWJzb2x1dGUgZXJyb3IgYmVsb3cgZml2ZSkgYW5kIGdlbmVyYWxpemVzIHdlbGwgYWNyb3NzIHRpbWUgYW5kIHNwYWNlLiBCYXNlZCBvbiB0aGUgcmVzdWx0cywgdGhlIG1vZGVsIGlzIHJlY29tbWVuZGVkIGZvciB0aGUgcmUtZGlzdHJpYnV0aW5nIHBsYW4gZm9yIHByZWRpY3RpbmcgdGhlIGRlbWFuZC4gDQoNCk5vdGFibHkgdGhhdCB0aGUgbW9kZWwgZ2VuZXJhbGl6ZXMgbXVjaCBiZXR0ZXIgb24gdGltZSB0aGFuIGxvY2F0aW9uLiBUaGlzIG1lYW5zIHRoYXQgaXQgaXMgc2xpZ2h0bHkgdW5yZWxpYWJsZSBvbiBwcmVkaWN0aW5nIHRoZSBkZW1hbmQgd2hlbiBpdCBjb21lcyB0byBsb2NhdGlvbiAsIGVzcGVjaWFsbHkgZHVyaW5nIFBNIHJ1c2ggaG91cnMuIFRoZSBlcnJvcnMgc2hvdyB0aGF0IHdlIGFyZSBnZW5lcmFsbHkgdW5kZXItcHJlZGljdGluZywgaXQgaXMgd2lzZSwgdGh1cywgdG8gcmVkaXN0cmlidXRlIDMgKHRoZSBtYXhpbXVtIGFic29sdXRlIGVycm9yKSBtb3JlIGJpa2VzIGF0IGhpZ2ggZGVtYW5kIHJlZ2lvbiAoaS5lLiB0aGUgY2VudHJhbCBiYXkgYXJlYSkgb24gdG9wIG9mIHRoZSBwcmVkaWN0ZWQgZGVtYW5kIGR1cmluZyB0aGVzZSBob3VycyB0byBjb21wZW5zYXRlIGZvciB0aGUgZXJyb3JzLiANCg0KTm90ZSB0aGF0IHRoaXMgYW5hbHlzaXMgZG9lcyBub3QgaW5jbHVkZSBhbnkgc29jaWFsLWVjb25vbWljYWwgZmFjdG9ycyBvciBuZWlnaGJvcmhvb2QgZWZmZWN0IGludG8gYWNjb3VudC4gRm9yIGV4YW1wbGUsIHRoZSBkZW1hbmQgY291bGQgYmUgcmVsYXRlZCB0byB0aGUgaW5jb21lIGxldmVsIGFuZCBlbXBsb3ltZW50IHJhdGUgb2YgdGhlIG5laWdoYm9yaG9vZCB3aGVyZSBhIHN0YXRpb24gbG9jYXRlcy4gVGhlIG1vZGVsIG1heSBiZSBzdHJlbmd0aGVuZWQgYnkgY29uc2lkZXJpbmcgdGhlc2Ugc29jaWFsLWVjb25vbWljYWwgZmFjdG9ycywgd2hpY2ggY291bGQgZXNzZW50aWFsbHkgcmVkdWNlIGVycm9yIGNsdXN0ZXJpbmcgb3ZlciBnZW9ncmFwaHkuIA0KDQpBbHNvIG5vdGUgdGhpcyByZWdyZXNzaW9uIG1vZGVsIG9ubHkgdGVsbHMgdGhlIGRlbWFuZC4gSW4gb3JkZXIgdG8gaW1wbGVtZW50IHRoZSByZS1iYWxhbmNpbmcgcGxhbiwgYSByZWFsLXRpbWUgbW9uaXRvcmluZyBzeXN0ZW0gc2hvdWxkIGFsc28gYmUgc2V0IHVwIGFuZCBzZW5kcyBzaWduYWxzIGF0IGNlcnRhaW4gaG91ciwgd2hlbiBpdCBzZW5zZXMgdGhhdCB0aGVyZSBpcyBhIGxhY2sgb2YgYmlrZXMgYXQgdGhpcyBwYXJ0aWN1bGFyIHN0YXRpb24uIA0K