I. Introduction

This is a comprehensive Exploratory Data Analysis for 300 millions of for-hire vehicle (Uber, Lyft, Via) trips originating in New York City from 2018-01-01 to 2018-12-31, and we focuse on trip counts and duration in New York competition with tidy R, ggplot2, and plotly.

The goal of this challenge is to process large data sets and to understand the duration of FHV in NYC based on features: trip location, pick-up, drop-off time, and weather effect. Also, we are interested in the difference betweeen three companies such as market shares, targeted customers, and business strategy. First of all, we analysis and visualize the original data, engineer new features, aggregate time-series variables to understand the data and pattern. Second, we compare three companies (Uber, Lyft, Via) over various time frame on trip amount and duration to analyze the market share and business strategy. Lastly, we add external NYC weather data to study how the weather impact on the trip duration and order requests in order to understand users behavior.

II. Description of the data source

The data were collected and provided to the NYC Taxi and Limousine Commission (TLC) by technology providers authorized under the Taxicab & Livery Passenger Enhancement Programs (https://www1.nyc.gov/site/tlc/about/tlc-trip-record-data.page).

The For-Hire Vehicle (“FHV”) trip records since 2009 until present including fields capturing the dispatching base license number and the pick-up date, time, and taxi zone location ID. We are focusing on the time period from 2018-01-01 to 2018-12-31, so the data comes in the shape of 200+ million observations, and each row contains one trip infomation.

The base license number is matching with different vehicle companies, so that we will join the base-number file to define the vehicle types, and we only focus on Uber, Lyft, Via at this point.

The NYC Taxi Zones map provided by TLC and published to NYC Open Data(https://data.cityofnewyork.us/Transportation/NYC-Taxi-Zones/d3c5-ddgc). This map shows the NYC taxi zones corresponding to the pick up zomes and drop off zones, or location IDs, included in the FHV trip records. The taxi zones are roughly based on NYC Department of City Planning’s Neighborhood Tabulation Areas (NTAs) and are meant to approximate neighborhoods.

The NYC Weather data is provided by National Centers For Environmental Information (https://www.ncdc.noaa.gov/data-access). NCEI is the world’s largest provider of weather and climate data. Land-based, marine, model, radar, weather balloon, satellite, and paleoclimatic are just a few of the types of datasets available. The weather data we are using is collected from NY Central Park Station (USW00094728) from 2018-01-01 to 2018-12-31, which contains daily weather records such as wind, precipitation, snow and snow depth.

Statistics through December 31, 2018:

Existing problem:

III. Description of data import / cleaning / transformation

3.1 Libraries and Dependencies

library(tibble) # data wrangling
library(reshape2) # data wrangling
library(tidyr) # data wrangling
library(dplyr) # data manipulation
library(purrr) # data manipulation
library(data.table) # data manipulation
library(ggplot2) # visualisation
library(plotly) # visualisation
library(lubridate) # date and time
library(naniar) #missing visualization

3.2 Data collection

First of all, we write a shell script to download original data from public websites

curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-01.csv >201801.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-02.csv >201802.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-03.csv >201803.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-04.csv >201804.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-05.csv >201805.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-06.csv >201806.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-07.csv >201807.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-08.csv >201808.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-09.csv >201809.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-10.csv >201810.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-11.csv >201811.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-12.csv >201812.csv

3.3 Data Import

Due to local memeory issue and efficiency issue in R, the solution of processing large data is using high-performance version of base R’s data.frame data.table and randomly sampling 3% of total data.

we use fread function to boost data process, select the vehicle company (Uber, Lyft, Via) based on the license number, and store into data.table format to perform our structured data. Each row contains trip information such as travel time, pick-up, drop-off date, time, location ID, and vehicle type.

Weather data can be read by local csv file, and inner join on trip data by key date.

base = read.csv("../Data/base_number.csv")$x %>% as.character()

weather = read.csv("../Data/weather.csv") %>%
  mutate(date = as.Date(as.character(DATE))) %>%
  select(-STATION, -TAVG, -DATE)

import_data = function(start, end)
{
  data = c()
  for(i in start:end)
  {
    file = paste("../Data/20180", i, ".csv") %>% gsub(" ", "", .)
    
    temp = fread(file) %>% data.table() %>%
      .[Dispatching_base_number %in% c("B02510", "B02800", base)] %>%
      .[, type := ifelse(Dispatching_base_number == "B02510", "Lyft",
                         ifelse(Dispatching_base_number == "B02800", "Via", "Uber"))] %>%
      .[, day := day(pickup_datetime)] %>%
      .[, pick_hour := hour(pickup_datetime)]
    
    temp = temp[, r := row_number(pickup_datetime), by = .(day, pick_hour)][r <= 400]

    data = rbindlist(list(data, temp), use.names = FALSE)
  }
  
  remove(temp)
  
  data = data[weather, on = 'date', nomatch = 0][, r:=NULL]
  fwrite(data, paste("../Data/", start, "-", end, "_weather.csv") %>% gsub(" ", "", .))

  return(data)
}

data = import_data(1,12)

3.4 Data Processing

Next, we use lubridate:ymd_hms and lubridate:mdy_hms transformat string to standard time stamp variables, and calucate the travel time in second by sbustracting drop-off time and pick-up time.

data = data %>%
  .[, pickup_datetime := ymd_hms(Pickup_DateTime)] %>%
  .[, dropoff_datetime := ymd_hms(DropOff_datetime)] %>%
  .[, travel_time := as.numeric(dropoff_datetime-pickup_datetime)] %>%
  .[, date := date(pickup_datetime)] %>%
  .[, month := month(pickup_datetime)] %>%
  .[, day := day(pickup_datetime)] %>%
  .[, wkday := weekdays(date)] %>%
  .[, pick_hour := hour(pickup_datetime)] %>%
  .[, pickup_location_id := PUlocationID] %>%
  .[, dropoff_location_id := DOlocationID] %>%
  .[, list(travel_time, pickup_datetime, dropoff_datetime, date, month, day,
           pick_hour, pickup_location_id, dropoff_location_id, type,
           AWND, PRCP, SNOW, SNWD, TMAX, TMIN)]

3.5 Data Structure Overview

Let’s have an overview of the first 10 rows of data.

3.6 Data Missing & Outliers

Let’s visualize the pattern of missing value in the dataset.

Due to FHV companies’ policy, the trip is allowed to be cancelled in 2 minutes. In New York City, it’s unlikely the travel time is longer than 5 hours.

Following heat map shows the percentage of trips are less than 2 minutes and larger than 5 hours for each pick-up and drop-off borough.

We find: * We found there are 25% missing value in the AWND and few in the pickup_location_id and dropoff_location_id. To conclude accurate analysis, we are going to fill in median of AWND, and remove data if locationID is missing * A lot of trip orders in the same borough are less than 2 minutes duration, which might be correct * However, it is impossible the trip is crossing two borough taking less than 2 minutes, so we are considered as cancalled orders.

3.7 Data Transformation

The density plot shows the duration distribution has a significantly right skew.

density1 = density(data[,travel_time])
plot_ly(x = ~ density1$x, y = ~ density1$y, type = 'scatter', mode = 'lines', name = 'Fair cut', fill = 'tozeroy') %>%
  layout(title = "Density Plot for Trip Duration",
    xaxis = list(title = 'Duration'),
         yaxis = list(title = 'Density'))

We might take Log transformation on the duration to solve the skewness issue for furture modeling.

density1 = density(log(data[, travel_time]))
plot_ly(x = ~ density1$x, y = ~ density1$y, type = 'scatter', mode = 'lines', name = 'Fair cut', fill = 'tozeroy') %>%
  layout(title = "Density Plot for Transformated Travel Time",
    xaxis = list(title = 'Log(Duration)'),
         yaxis = list(title = 'Density'))

3.8 Data Aggregation

we can aggregate data by different levels in order to calcuate daily hour pick-ups, and median travel time in weekday.

data[, .N, by = .(month.abb[month(date)], wkday, pick_hour)]
data[, .(d.med = median(travel_time)), by = .(wkday, pick_hour)]

V. Results

5.1 Overall

First of all, we would likd to view overall median travel time based on hour, weekday and month bases. * The median is more robust measurement because it has less effect on outliers. * Hourly base is showing the peak hour effects in a typical day. * The weekly base tells the difference between work day and weekends. * The monthly base has a good explaination on seasonality.

p1 = data[, .(d.med = median(travel_time)), by = pick_hour] %>%
  .[order(pick_hour)] %>%
plot_ly(., x = ~ pick_hour, y = ~ d.med, type ="scatter",
        mode = 'lines+markers',
        line = list(color="#2E86C1", width = 4),
        marker = list(size = 8, color = 'rgba(255, 182, 193, .9)', line = list(color = 'rgba(152, 0, 0, .8)', width = 2,simplyfy = F))) %>%
  layout(title = 'Median Travel Time',
         yaxis = list(title = 'Time(sec) (sec)',
                      range=c(800,1200), zeroline = FALSE),
         xaxis = list(title = 'Hour',
                      zeroline = FALSE))
lvl = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
p2 = data[, .(d.med = median(travel_time)), by = sort(ordered(wkday, levels = lvl))] %>%
  plot_ly(., x = ~ sort, y = ~ d.med, type ="scatter",
          mode = 'lines+markers',
          line = list(width=4,simplyfy = F, color='rgb(114, 186, 59)'),
          marker = list(size = 8, color = '#8E44AD', line = list(color = '#3498DB', width = 2))) %>%
  layout(yaxis = list(title = 'Time (sec)', range=c(950,1100),zeroline = FALSE),
         xaxis = list(title = 'Weekday', zeroline = FALSE))
p3 = data[, .(d.med = median(travel_time)), by = sort(ordered(month.abb[month], levels = month.abb))] %>%
  plot_ly(., x = ~ sort, y = ~ d.med, type ="scatter",
          mode = 'lines+markers',
          line=list(color="#FF5733",width = 4),
          marker = list(size = 8, color = '#FFFF00', line = list(color = '#E67E22', width = 2))) %>%
  layout(yaxis = list(title = 'Travel Time',range=c(900,1150), zeroline = FALSE),
         xaxis = list(title = 'Month', zeroline = FALSE))
subplot(p1, p2, p3, nrows = 3)

We find:

  • The rush hour occurs from 8 AM - 7 PM about 18 minutes, othervise less traffic is after 8 PM
  • The highest travel time is 20 minutes at 4 PM, the lowest travel time is 14 minutest at 2 AM (intersting!)
  • During weekends, the travel time is higher than weekdays
  • For monthly, it shows very strong seasonality that spring and fall have higher travel time; summer and winter are much lower.

5.2 Types

We investigate on how the median travel time dependes on the different companies in hourly, weekly and monthly bases.

data[, .(d.med = median(travel_time)), by = .(pick_hour, type)] %>% 
  .[order(pick_hour, type)] %>%
  as_tibble() %>% accumulate_by(~ pick_hour) %>%
  plot_ly(., x = ~ pick_hour, y = ~ d.med, frame = ~ frame,
          colors=c("hotpink","black","steelblue"),
          color = ~ type, type ="scatter", mode = 'lines+markers',
          line = list(width=4,simplyfy = F), marker = list(size=10),
          text = ~paste("Hour: ", pick_hour, '<br>Average duration:',
                        round(d.med,2))) %>%
  layout(title = 'Hourly Median Travel Time',
         yaxis = list(title = 'Duration (Sec)', zeroline = FALSE),
         xaxis = list(title = 'Hour', zeroline = FALSE)) %>% 
  animation_opts(frame = 300, transition = 200, redraw = F) %>%
  animation_slider(hide = T) %>%
  animation_button(x = 1, xanchor = "right", y = 0, yanchor = "bottom")
data[, .N, by = .(pick_hour, type)] %>% 
  .[order(pick_hour, type)] %>%
  dcast(., pick_hour ~ type, value.var = "N" ) %>%
  plot_ly(., x = ~pick_hour, y = ~Uber, type = 'bar', name = 'Uber',
          marker = list(color = 'black')) %>%
  add_trace(y = ~Lyft, name = 'Lyft', marker = list(color = "hotpink")) %>%
  add_trace(y = ~Via, name = 'Via', marker = list(color = "steelblue")) %>%
  layout(yaxis = list(title = 'Counts'), barmode = 'group',
         title = "Hour Trip Counts")
data[, .(d.med = median(travel_time)), by = .(sort(ordered(wkday, levels = lvl)), type)] %>% 
  as_tibble() %>%
  accumulate_by(~ sort) %>%
  arrange(sort) %>%
plot_ly(., x = ~ sort, y = ~ d.med,  colors= c("hotpink","black","steelblue"), color = ~ type,
        type ="scatter", mode = 'lines+markers',line = list(width=4,simplyfy = F), marker = list(size=10),
        text = ~paste("WeekdayHour: ",sort, '<br>Median duration:', round(d.med,2))) %>%
  layout(title = 'Weekday Median Travel Time',
         yaxis = list(title = 'Duration (Sec)',zeroline = FALSE),
         xaxis = list(title = 'Weekday', zeroline = FALSE)) %>%
  animation_opts(frame = 300, transition = 200, redraw = F)
data[, .N, by = .(sort(ordered(wkday, levels = lvl)), type)] %>%
  dcast(., sort ~ type, value.var = "N") %>%
  plot_ly(., x = ~sort, y = ~Uber, type = 'bar', name = 'Uber',
          marker = list(color = 'black')) %>%
  add_trace(y = ~Lyft, name = 'Lyft', marker = list(color = "hotpink")) %>%
  add_trace(y = ~Via, name = 'Via', marker = list(color = "steelblue")) %>%
  layout(yaxis = list(title = 'Counts'), barmode = 'group',
         title = "Weekday Trip Counts")
data[, .(d.med = median(travel_time)), by = .(month, type)] %>% 
  as_tibble() %>%
  accumulate_by(~ month) %>%
  arrange(month) %>%
  plot_ly(., x = ~ month, y = ~ d.med, colors= c("hotpink","black","steelblue"),
          frame = ~ frame, color = ~ type, type ="scatter", mode = 'lines+markers',
          line = list(width=4,simplyfy = F), marker = list(size=10),
          text = ~paste("Month: ",month, '<br>Median duration:', round(d.med,2))) %>%
  layout(title = 'Monthly Median Travel Time',
         yaxis = list(title = 'Duration (Sec)',zeroline = FALSE),
         xaxis = list(title = 'Month', zeroline = FALSE)) %>% 
  animation_opts(frame = 300, transition = 200, redraw = F) %>%
  animation_slider(hide = T) %>%
  animation_button(x = 1, xanchor = "right", y = 0, yanchor = "bottom")
data[, .N, by = .(month, type)] %>%
  dcast(., month ~ type, value.var = "N") %>%
  plot_ly(., x = ~month, y = ~Uber, type = 'bar', name = 'Uber',
          marker = list(color = 'black')) %>%
  add_trace(y = ~Lyft, name = 'Lyft', marker = list(color = "hotpink")) %>%
  add_trace(y = ~Via, name = 'Via', marker = list(color = "steelblue")) %>%
  layout(yaxis = list(title = 'Counts'), barmode = 'group',
         title = "Month Trip Counts")

We find:

  • For tipical day, Uber, Lyft and Via have simmilar trip duration in each hour
  • For weekly base, Lyft has a litter higher trip duration than others, especially on Monday (interesting!)
  • For monthly base, Via is the highest because most trips are share riders, which takes longer time
  • Overally, Uber has lowest trip duration comparing other two!

5.3 Market Share

We also study the market shares on the both space and time line, so create an interactive map NYC FHV Marketshare map to indictate percentage of marketshare for Uber, Lyft and Via at different pick up zone. By simply clicking the map, you can see marketshare data in each zone. The legend lies in the right hand side, where you can also alter different views for each types of taxi by clicking three Teardrop-shaped buttons of applying auto style. (https://zxf71699.carto.com/builder/62d8c815-2839-41fe-95e0-84ac6e4eccb6/embed)

We find:

  • Uber has dominant on the FHV market, reaching 75% entire market
  • Lyft is second dominant on the FHV market, and weekends have higher numbers of trips.
  • Via is a growing company, so it takes a small proportion of market, but it focuses on the peaking hour.

5.4 Weather Effect

We have encouraged to supplement our analysis with combining the extural NYC weather data to study how weather is impacted on the trip duration. Of particular interest here will be the rain, snow fall, and sun statistics.

df_rain = data[(PRCP > 0) & (SNOW == 0), travel_time]
df_snow_rain = data[(PRCP > 0) & (SNOW > 0), travel_time]
df_sunny = data[(PRCP == 0) & (SNOW == 0), travel_time]
df_snow = data[(SNWD > 0), travel_time]
plot_ly(type = 'box') %>%
  add_boxplot(x = df_rain, name = "Rainy Days", fillcolor = 'rgba(254,231,37,0.4)',
              marker = list(color = 'rgba(219, 64, 82, 1.0)'),
              line = list(color = 'rgba(253,231,37,100)')) %>%
  add_boxplot(x = df_snow_rain, name = "Snowy and Rainy Days",fillcolor = 'rgba(93,200,99,0.4)',
              marker = list(color = 'rgba(219, 64, 82, 1.0)'),
                            line = list(color = 'rgba(93,200,99,100)')) %>%
  add_boxplot(x = df_sunny, name = "Sunny Days", fillcolor = 'rgba(33,144,140,0.4)',
              marker = list(color = 'rgba(219, 64, 82, 1.0)'),
              line = list(color = 'rgba(33,144,140,100)')) %>%
  add_boxplot(x = df_snow, name = "Snowy Days", fillcolor = 'rgba(59,82,39,0.4)',
              marker = list(color = 'rgba(219, 64, 82, 1.0)'),
              line = list(color = 'rgba(59,82,39,100)')) %>%
  layout(title = "Weather Effects on Travel Time", hovermode = 'compare',
          yaxis = list(title = '', zeroline =T, showgrid = T),
          xaxis = list(title = 'Duration (Sec)', zeroline = T, showgrid = T))
plot_ly(type = 'bar') %>%
  add_bars(x = length(df_rain), y= "Rainy Days",name = "Rainy Days", fillcolor = 'rgba(254,231,37,0.4)',
           marker = list(color = 'rgba(254,231,37,0.4)'),
           line = list(color = 'rgba(253,231,37,100)'),
           text = ~paste("weather:","Rainy Days","<br>Trip counts:",length(df_rain))) %>%
  add_bars(x = length(df_snow_rain),   y= "Snowy and Rainy Days",
           name = "Snowy and Rainy Days", fillcolor ='rgba(93,200,99,0.4)',
           marker = list(color = 'rgba(93,200,99,0.4)'),
           line = list(color = 'rgba(93,200,99,100)'),
           text = ~ paste("weather:","Snowy and Rainy Days","<br>Trip counts:",length(df_snow_rain)))%>%
  add_bars(x = length(df_sunny), y = "Sunny Days", name = "Sunny Days", fillcolor = 'rgba(33,144,140,0.4)',
           marker = list(color = 'rgba(33,144,140,0.4)'),
           line = list(color = 'rgba(33,144,140,100)'), 
           text = ~paste("weather:","Sunny Days","<br>Trip counts:",length(df_sunny))) %>%
  add_bars(x = length(df_snow), y= "Snowy Days",name = "Snowy Days", fillcolor = 'rgba(59,82,39,0.4)',
              marker = list(color = 'rgba(59,82,39,0.4)'),
              line = list(color = 'rgba(59,82,39,100)'),
           text = ~paste("weather:","Snowy Days","<br>Trip counts:",length(df_snow))) %>%
  layout(title = "Weather Effect on Trip Counts",
          yaxis = list(TITLE="Weather", zeroline = FALSE, showgrid = T),
          xaxis = list(title = 'Trip Counts', zeroline = F, showgrid = T))

We find:

  • For sunny days, there are the largest amount of trip requests. It tells most people prefer to hand out.
  • Rain causes the larger amount order requests and longer trip duration
  • For snowy days, there are a few outliers, so it might tells more likely occurs extremely cases in the bad weather.
  • It seems more like snow would lead to shorter trips, so it could simply mean passengers were more likely to travel shorter distances, or stay at home.
LS0tDQp0aXRsZTogIk5ZQyBUYXhpICYgRkhWIFByb2plY3QgLSBFeHBsb3JhdG9yeSINCmF1dGhvcjogIkppZSBMaSwgWGlhb2ZhbiBaaGFuZyINCmRhdGU6ICI4LzExLzIwMTkiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KIyMgSS4gSW50cm9kdWN0aW9uDQpUaGlzIGlzIGEgY29tcHJlaGVuc2l2ZSBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIGZvciAzMDAgbWlsbGlvbnMgb2YgZm9yLWhpcmUgdmVoaWNsZSAoVWJlciwgTHlmdCwgVmlhKSB0cmlwcyBvcmlnaW5hdGluZyBpbiBOZXcgWW9yayBDaXR5IGZyb20gKioyMDE4LTAxLTAxKiogdG8gKioyMDE4LTEyLTMxKiosIGFuZCB3ZSBmb2N1c2Ugb24gdHJpcCBjb3VudHMgYW5kIGR1cmF0aW9uIGluIE5ldyBZb3JrIGNvbXBldGl0aW9uIHdpdGggdGlkeSBSLCBnZ3Bsb3QyLCBhbmQgcGxvdGx5Lg0KDQpUaGUgZ29hbCBvZiB0aGlzIGNoYWxsZW5nZSBpcyB0byBwcm9jZXNzIGxhcmdlIGRhdGEgc2V0cyBhbmQgdG8gdW5kZXJzdGFuZCB0aGUgZHVyYXRpb24gb2YgRkhWIGluIE5ZQyBiYXNlZCBvbiBmZWF0dXJlczogdHJpcCBsb2NhdGlvbiwgcGljay11cCwgZHJvcC1vZmYgdGltZSwgYW5kIHdlYXRoZXIgZWZmZWN0LiBBbHNvLCB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVlbiB0aHJlZSBjb21wYW5pZXMgc3VjaCBhcyBtYXJrZXQgc2hhcmVzLCB0YXJnZXRlZCBjdXN0b21lcnMsIGFuZCBidXNpbmVzcyBzdHJhdGVneS4gRmlyc3Qgb2YgYWxsLCB3ZSBhbmFseXNpcyBhbmQgdmlzdWFsaXplIHRoZSBvcmlnaW5hbCBkYXRhLCBlbmdpbmVlciBuZXcgZmVhdHVyZXMsIGFnZ3JlZ2F0ZSB0aW1lLXNlcmllcyB2YXJpYWJsZXMgdG8gdW5kZXJzdGFuZCB0aGUgZGF0YSBhbmQgcGF0dGVybi4gU2Vjb25kLCB3ZSBjb21wYXJlIHRocmVlIGNvbXBhbmllcyAoVWJlciwgTHlmdCwgVmlhKSBvdmVyIHZhcmlvdXMgdGltZSBmcmFtZSBvbiB0cmlwIGFtb3VudCBhbmQgZHVyYXRpb24gdG8gYW5hbHl6ZSB0aGUgbWFya2V0IHNoYXJlIGFuZCBidXNpbmVzcyBzdHJhdGVneS4gTGFzdGx5LCB3ZSBhZGQgZXh0ZXJuYWwgTllDIHdlYXRoZXIgZGF0YSB0byBzdHVkeSBob3cgdGhlIHdlYXRoZXIgaW1wYWN0IG9uIHRoZSB0cmlwIGR1cmF0aW9uIGFuZCBvcmRlciByZXF1ZXN0cyBpbiBvcmRlciB0byB1bmRlcnN0YW5kIHVzZXJzIGJlaGF2aW9yLg0KDQojIyBJSS4gRGVzY3JpcHRpb24gb2YgdGhlIGRhdGEgc291cmNlDQpUaGUgZGF0YSB3ZXJlIGNvbGxlY3RlZCBhbmQgcHJvdmlkZWQgdG8gdGhlIE5ZQyBUYXhpIGFuZCBMaW1vdXNpbmUgQ29tbWlzc2lvbiAoVExDKSBieSB0ZWNobm9sb2d5IHByb3ZpZGVycyBhdXRob3JpemVkIHVuZGVyIHRoZSBUYXhpY2FiICYgTGl2ZXJ5IFBhc3NlbmdlciBFbmhhbmNlbWVudCBQcm9ncmFtcyAoaHR0cHM6Ly93d3cxLm55Yy5nb3Yvc2l0ZS90bGMvYWJvdXQvdGxjLXRyaXAtcmVjb3JkLWRhdGEucGFnZSkuDQoNClRoZSBGb3ItSGlyZSBWZWhpY2xlICgiRkhWIikgdHJpcCByZWNvcmRzIHNpbmNlIDIwMDkgdW50aWwgcHJlc2VudCBpbmNsdWRpbmcgZmllbGRzIGNhcHR1cmluZyB0aGUgZGlzcGF0Y2hpbmcgYmFzZSBsaWNlbnNlIG51bWJlciBhbmQgdGhlIHBpY2stdXAgZGF0ZSwgdGltZSwgYW5kIHRheGkgem9uZSBsb2NhdGlvbiBJRC4gV2UgYXJlIGZvY3VzaW5nIG9uIHRoZSB0aW1lIHBlcmlvZCBmcm9tICoqMjAxOC0wMS0wMSoqIHRvICoqMjAxOC0xMi0zMSoqLCBzbyB0aGUgZGF0YSBjb21lcyBpbiB0aGUgc2hhcGUgb2YgMjAwKyBtaWxsaW9uIG9ic2VydmF0aW9ucywgYW5kIGVhY2ggcm93IGNvbnRhaW5zIG9uZSB0cmlwIGluZm9tYXRpb24uDQoNClRoZSBiYXNlIGxpY2Vuc2UgbnVtYmVyIGlzIG1hdGNoaW5nIHdpdGggZGlmZmVyZW50IHZlaGljbGUgY29tcGFuaWVzLCBzbyB0aGF0IHdlIHdpbGwgam9pbiB0aGUgYGJhc2UtbnVtYmVyYCBmaWxlIHRvIGRlZmluZSB0aGUgdmVoaWNsZSB0eXBlcywgYW5kIHdlIG9ubHkgZm9jdXMgb24gVWJlciwgTHlmdCwgVmlhIGF0IHRoaXMgcG9pbnQuDQoNClRoZSBOWUMgVGF4aSBab25lcyBtYXAgcHJvdmlkZWQgYnkgVExDIGFuZCBwdWJsaXNoZWQgdG8gTllDIE9wZW4gRGF0YShodHRwczovL2RhdGEuY2l0eW9mbmV3eW9yay51cy9UcmFuc3BvcnRhdGlvbi9OWUMtVGF4aS1ab25lcy9kM2M1LWRkZ2MpLiBUaGlzIG1hcCBzaG93cyB0aGUgTllDIHRheGkgem9uZXMgY29ycmVzcG9uZGluZyB0byB0aGUgcGljayB1cCB6b21lcyBhbmQgZHJvcCBvZmYgem9uZXMsIG9yIGxvY2F0aW9uIElEcywgaW5jbHVkZWQgaW4gdGhlIEZIViB0cmlwIHJlY29yZHMuIFRoZSB0YXhpIHpvbmVzIGFyZSByb3VnaGx5IGJhc2VkIG9uIE5ZQyBEZXBhcnRtZW50IG9mIENpdHkgUGxhbm5pbmcncyBOZWlnaGJvcmhvb2QgVGFidWxhdGlvbiBBcmVhcyAoTlRBcykgYW5kIGFyZSBtZWFudCB0byBhcHByb3hpbWF0ZSBuZWlnaGJvcmhvb2RzLg0KDQpUaGUgTllDIFdlYXRoZXIgZGF0YSBpcyBwcm92aWRlZCBieSBOYXRpb25hbCBDZW50ZXJzIEZvciBFbnZpcm9ubWVudGFsIEluZm9ybWF0aW9uIChodHRwczovL3d3dy5uY2RjLm5vYWEuZ292L2RhdGEtYWNjZXNzKS4gTkNFSSBpcyB0aGUgd29ybGQncyBsYXJnZXN0IHByb3ZpZGVyIG9mIHdlYXRoZXIgYW5kIGNsaW1hdGUgZGF0YS4gTGFuZC1iYXNlZCwgbWFyaW5lLCBtb2RlbCwgcmFkYXIsIHdlYXRoZXIgYmFsbG9vbiwgc2F0ZWxsaXRlLCBhbmQgcGFsZW9jbGltYXRpYyBhcmUganVzdCBhIGZldyBvZiB0aGUgdHlwZXMgb2YgZGF0YXNldHMgYXZhaWxhYmxlLiBUaGUgd2VhdGhlciBkYXRhIHdlIGFyZSB1c2luZyBpcyBjb2xsZWN0ZWQgZnJvbSBOWSBDZW50cmFsIFBhcmsgU3RhdGlvbiAoVVNXMDAwOTQ3MjgpIGZyb20gKioyMDE4LTAxLTAxKiogdG8gKioyMDE4LTEyLTMxKiosIHdoaWNoIGNvbnRhaW5zIGRhaWx5IHdlYXRoZXIgcmVjb3JkcyBzdWNoIGFzIHdpbmQsIHByZWNpcGl0YXRpb24sIHNub3cgYW5kIHNub3cgZGVwdGguDQoNClN0YXRpc3RpY3MgdGhyb3VnaCBEZWNlbWJlciAzMSwgMjAxODoNCg0KKiAxNy4yIEdCIG9mIHJhdyBkYXRhDQoqIDMwMCsgbWlsbGlvbiBmb3ItaGlyZSB2ZWhpY2xlIHRvdGFsIHRyaXBzDQoqIDM2NSBkYWlseSB3ZWF0aGVyIHJlY29yZHMNCg0KRXhpc3RpbmcgcHJvYmxlbToNCg0KKiBSIHJlYWRzIGVudGlyZSBkYXRhIHNldCBpbnRvIFJBTSBhbGwgYXQgb25jZS4gVG90YWwgMTcuMiBHQiBvZiByYXcgZGF0YSB3b3VsZCBub3QgZml0IGluIGxvY2FsIG1lbW9yeSBhdCBvbmNlLg0KKiBSIE9iamVjdHMgbGl2ZSBpbiBtZW1vcnkgZW50aXJlbHksIHdoaWNoIGNhdXNlIHNsb3duZXNzIGZvciBkYXRhIGFuYWx5c2lzLg0KKiBUaGUgVExDIHB1Ymxpc2hlcyBiYXNlIHRyaXAgcmVjb3JkIGRhdGEgYXMgc3VibWl0dGVkIGJ5IHRoZSBiYXNlcywgYW5kIHdlIGNhbm5vdCBndWFyYW50ZWUgb3IgY29uZmlybSB0aGVpciBhY2N1cmFjeSBvciBjb21wbGV0ZW5lc3MuDQoNCg0KIyMgSUlJLiBEZXNjcmlwdGlvbiBvZiBkYXRhIGltcG9ydCAvIGNsZWFuaW5nIC8gdHJhbnNmb3JtYXRpb24NCiMjIyAzLjEgTGlicmFyaWVzIGFuZCBEZXBlbmRlbmNpZXMNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCmxpYnJhcnkodGliYmxlKSAjIGRhdGEgd3JhbmdsaW5nDQpsaWJyYXJ5KHJlc2hhcGUyKSAjIGRhdGEgd3JhbmdsaW5nDQpsaWJyYXJ5KHRpZHlyKSAjIGRhdGEgd3JhbmdsaW5nDQpsaWJyYXJ5KGRwbHlyKSAjIGRhdGEgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KHB1cnJyKSAjIGRhdGEgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KGRhdGEudGFibGUpICMgZGF0YSBtYW5pcHVsYXRpb24NCmxpYnJhcnkoZ2dwbG90MikgIyB2aXN1YWxpc2F0aW9uDQpsaWJyYXJ5KHBsb3RseSkgIyB2aXN1YWxpc2F0aW9uDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgIyBkYXRlIGFuZCB0aW1lDQpsaWJyYXJ5KG5hbmlhcikgI21pc3NpbmcgdmlzdWFsaXphdGlvbg0KYGBgDQoNCmBgYHtyLCBlY2hvPUZ9DQpzZXR3ZCgiRTovV29yay9UYXhpL0FuYWx5c2lzIikNCmBgYA0KDQojIyMgMy4yIERhdGEgY29sbGVjdGlvbg0KRmlyc3Qgb2YgYWxsLCB3ZSB3cml0ZSBhIGBzaGVsbGAgc2NyaXB0IHRvIGRvd25sb2FkIG9yaWdpbmFsIGRhdGEgZnJvbSBwdWJsaWMgd2Vic2l0ZXMNCmBgYHtyLCBldmFsPUZ9DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wMS5jc3YgPjIwMTgwMS5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTAyLmNzdiA+MjAxODAyLmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDMuY3N2ID4yMDE4MDMuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wNC5jc3YgPjIwMTgwNC5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTA1LmNzdiA+MjAxODA1LmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDYuY3N2ID4yMDE4MDYuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wNy5jc3YgPjIwMTgwNy5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTA4LmNzdiA+MjAxODA4LmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDkuY3N2ID4yMDE4MDkuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0xMC5jc3YgPjIwMTgxMC5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTExLmNzdiA+MjAxODExLmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMTIuY3N2ID4yMDE4MTIuY3N2DQpgYGANCg0KIyMjIDMuMyBEYXRhIEltcG9ydA0KRHVlIHRvIGxvY2FsIG1lbWVvcnkgaXNzdWUgYW5kIGVmZmljaWVuY3kgaXNzdWUgaW4gUiwgdGhlIHNvbHV0aW9uIG9mIHByb2Nlc3NpbmcgbGFyZ2UgZGF0YSBpcyB1c2luZyBoaWdoLXBlcmZvcm1hbmNlIHZlcnNpb24gb2YgYmFzZSBSJ3MgZGF0YS5mcmFtZSBgZGF0YS50YWJsZWAgYW5kIHJhbmRvbWx5IHNhbXBsaW5nIDMlIG9mIHRvdGFsIGRhdGEuDQoNCndlIHVzZSBgZnJlYWRgIGZ1bmN0aW9uIHRvIGJvb3N0IGRhdGEgcHJvY2Vzcywgc2VsZWN0IHRoZSB2ZWhpY2xlIGNvbXBhbnkgKFViZXIsIEx5ZnQsIFZpYSkgYmFzZWQgb24gdGhlIGxpY2Vuc2UgbnVtYmVyLCBhbmQgc3RvcmUgaW50byBgZGF0YS50YWJsZWAgZm9ybWF0IHRvIHBlcmZvcm0gb3VyIHN0cnVjdHVyZWQgZGF0YS4gRWFjaCByb3cgY29udGFpbnMgdHJpcCBpbmZvcm1hdGlvbiBzdWNoIGFzIHRyYXZlbCB0aW1lLCBwaWNrLXVwLCBkcm9wLW9mZiBkYXRlLCB0aW1lLCBsb2NhdGlvbiBJRCwgYW5kIHZlaGljbGUgdHlwZS4NCg0KV2VhdGhlciBkYXRhIGNhbiBiZSByZWFkIGJ5IGxvY2FsIGBjc3ZgIGZpbGUsIGFuZCBpbm5lciBqb2luIG9uIHRyaXAgZGF0YSBieSBrZXkgYGRhdGVgLg0KDQpgYGB7ciwgZXZhbD1GfQ0KYmFzZSA9IHJlYWQuY3N2KCIuLi9EYXRhL2Jhc2VfbnVtYmVyLmNzdiIpJHggJT4lIGFzLmNoYXJhY3RlcigpDQoNCndlYXRoZXIgPSByZWFkLmNzdigiLi4vRGF0YS93ZWF0aGVyLmNzdiIpICU+JQ0KICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoYXMuY2hhcmFjdGVyKERBVEUpKSkgJT4lDQogIHNlbGVjdCgtU1RBVElPTiwgLVRBVkcsIC1EQVRFKQ0KDQppbXBvcnRfZGF0YSA9IGZ1bmN0aW9uKHN0YXJ0LCBlbmQpDQp7DQogIGRhdGEgPSBjKCkNCiAgZm9yKGkgaW4gc3RhcnQ6ZW5kKQ0KICB7DQogICAgZmlsZSA9IHBhc3RlKCIuLi9EYXRhLzIwMTgwIiwgaSwgIi5jc3YiKSAlPiUgZ3N1YigiICIsICIiLCAuKQ0KICAgIA0KICAgIHRlbXAgPSBmcmVhZChmaWxlKSAlPiUgZGF0YS50YWJsZSgpICU+JQ0KICAgICAgLltEaXNwYXRjaGluZ19iYXNlX251bWJlciAlaW4lIGMoIkIwMjUxMCIsICJCMDI4MDAiLCBiYXNlKV0gJT4lDQogICAgICAuWywgdHlwZSA6PSBpZmVsc2UoRGlzcGF0Y2hpbmdfYmFzZV9udW1iZXIgPT0gIkIwMjUxMCIsICJMeWZ0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoRGlzcGF0Y2hpbmdfYmFzZV9udW1iZXIgPT0gIkIwMjgwMCIsICJWaWEiLCAiVWJlciIpKV0gJT4lDQogICAgICAuWywgZGF5IDo9IGRheShwaWNrdXBfZGF0ZXRpbWUpXSAlPiUNCiAgICAgIC5bLCBwaWNrX2hvdXIgOj0gaG91cihwaWNrdXBfZGF0ZXRpbWUpXQ0KICAgIA0KICAgIHRlbXAgPSB0ZW1wWywgciA6PSByb3dfbnVtYmVyKHBpY2t1cF9kYXRldGltZSksIGJ5ID0gLihkYXksIHBpY2tfaG91cildW3IgPD0gNDAwXQ0KDQogICAgZGF0YSA9IHJiaW5kbGlzdChsaXN0KGRhdGEsIHRlbXApLCB1c2UubmFtZXMgPSBGQUxTRSkNCiAgfQ0KICANCiAgcmVtb3ZlKHRlbXApDQogIA0KICBkYXRhID0gZGF0YVt3ZWF0aGVyLCBvbiA9ICdkYXRlJywgbm9tYXRjaCA9IDBdWywgcjo9TlVMTF0NCiAgZndyaXRlKGRhdGEsIHBhc3RlKCIuLi9EYXRhLyIsIHN0YXJ0LCAiLSIsIGVuZCwgIl93ZWF0aGVyLmNzdiIpICU+JSBnc3ViKCIgIiwgIiIsIC4pKQ0KDQogIHJldHVybihkYXRhKQ0KfQ0KDQpkYXRhID0gaW1wb3J0X2RhdGEoMSwxMikNCmBgYA0KDQojIyMgMy40IERhdGEgUHJvY2Vzc2luZw0KTmV4dCwgd2UgdXNlIGBsdWJyaWRhdGU6eW1kX2htc2AgYW5kIGBsdWJyaWRhdGU6bWR5X2htc2AgdHJhbnNmb3JtYXQgc3RyaW5nIHRvIHN0YW5kYXJkIHRpbWUgc3RhbXAgdmFyaWFibGVzLCBhbmQgY2FsdWNhdGUgdGhlIHRyYXZlbCB0aW1lIGluICoqc2Vjb25kKiogYnkgc2J1c3RyYWN0aW5nIGRyb3Atb2ZmIHRpbWUgYW5kIHBpY2stdXAgdGltZS4NCg0KYGBge3IsIGV2YWw9Rn0NCmRhdGEgPSBkYXRhICU+JQ0KICAuWywgcGlja3VwX2RhdGV0aW1lIDo9IHltZF9obXMoUGlja3VwX0RhdGVUaW1lKV0gJT4lDQogIC5bLCBkcm9wb2ZmX2RhdGV0aW1lIDo9IHltZF9obXMoRHJvcE9mZl9kYXRldGltZSldICU+JQ0KICAuWywgdHJhdmVsX3RpbWUgOj0gYXMubnVtZXJpYyhkcm9wb2ZmX2RhdGV0aW1lLXBpY2t1cF9kYXRldGltZSldICU+JQ0KICAuWywgZGF0ZSA6PSBkYXRlKHBpY2t1cF9kYXRldGltZSldICU+JQ0KICAuWywgbW9udGggOj0gbW9udGgocGlja3VwX2RhdGV0aW1lKV0gJT4lDQogIC5bLCBkYXkgOj0gZGF5KHBpY2t1cF9kYXRldGltZSldICU+JQ0KICAuWywgd2tkYXkgOj0gd2Vla2RheXMoZGF0ZSldICU+JQ0KICAuWywgcGlja19ob3VyIDo9IGhvdXIocGlja3VwX2RhdGV0aW1lKV0gJT4lDQogIC5bLCBwaWNrdXBfbG9jYXRpb25faWQgOj0gUFVsb2NhdGlvbklEXSAlPiUNCiAgLlssIGRyb3BvZmZfbG9jYXRpb25faWQgOj0gRE9sb2NhdGlvbklEXSAlPiUNCiAgLlssIGxpc3QodHJhdmVsX3RpbWUsIHBpY2t1cF9kYXRldGltZSwgZHJvcG9mZl9kYXRldGltZSwgZGF0ZSwgbW9udGgsIGRheSwNCiAgICAgICAgICAgcGlja19ob3VyLCBwaWNrdXBfbG9jYXRpb25faWQsIGRyb3BvZmZfbG9jYXRpb25faWQsIHR5cGUsDQogICAgICAgICAgIEFXTkQsIFBSQ1AsIFNOT1csIFNOV0QsIFRNQVgsIFRNSU4pXQ0KYGBgDQoNCiMjIyAzLjUgRGF0YSBTdHJ1Y3R1cmUgT3ZlcnZpZXcNCkxldCdzIGhhdmUgYW4gb3ZlcnZpZXcgb2YgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgZGF0YS4NCg0KYGBge3IsIGVjaG89Rn0NCiMgZGF0YSA9IHJlYWQuY3N2KCIuLi9EYXRhLzEtMTJfd2VhdGhlcl9taW5pLmNzdiIpICU+JSBhc190aWJibGUoKSAlPiUNCiMgICBtdXRhdGUocGlja3VwX2RhdGV0aW1lID0geW1kX2htcyhwaWNrdXBfZGF0ZXRpbWUpLA0KIyAgICAgICAgICBkcm9wb2ZmX2RhdGV0aW1lID0geW1kX2htcyhwaWNrdXBfZGF0ZXRpbWUpLA0KIyAgICAgICAgICBkYXRlID0gZGF0ZShkYXRlKSkNCg0KZGF0YSA9IHJlYWQuY3N2KCIuLi9EYXRhLzEtMTJfd2VhdGhlcl9taW5pLmNzdiIpICU+JSBkYXRhLnRhYmxlKCkgJT4lDQogIC5bLCBwaWNrdXBfZGF0ZXRpbWUgOj0geW1kX2htcyhwaWNrdXBfZGF0ZXRpbWUpXSAlPiUNCiAgLlssIGRyb3BvZmZfZGF0ZXRpbWUgOj0geW1kX2htcyhkcm9wb2ZmX2RhdGV0aW1lKV0gJT4lDQogIC5bLCBkYXRlIDo9IGRhdGUoZGF0ZSldDQoNCmRhdGENCmBgYA0KDQoNCiMjIyAzLjYgRGF0YSBNaXNzaW5nICYgT3V0bGllcnMNCkxldCdzIHZpc3VhbGl6ZSB0aGUgcGF0dGVybiBvZiBtaXNzaW5nIHZhbHVlIGluIHRoZSBkYXRhc2V0Lg0KDQpgYGB7ciwgZWNobz1GLCBmaWcua2VlcD0nbGFzdCd9DQpnZ19taXNzX3Vwc2V0KGRhdGEpDQpgYGANCg0KYGBge3IsIGVjaG89Rn0NCmRhdGFbcm93U3Vtcyhpcy5uYShkYXRhKSkgPiAwLF0NCmBgYA0KDQoNCkR1ZSB0byBGSFYgY29tcGFuaWVzJyBwb2xpY3ksIHRoZSB0cmlwIGlzIGFsbG93ZWQgdG8gYmUgY2FuY2VsbGVkIGluIDIgbWludXRlcy4gSW4gTmV3IFlvcmsgQ2l0eSwgaXQncyB1bmxpa2VseSB0aGUgdHJhdmVsIHRpbWUgaXMgbG9uZ2VyIHRoYW4gNSBob3Vycy4NCg0KRm9sbG93aW5nICoqaGVhdCBtYXAqKiBzaG93cyB0aGUgcGVyY2VudGFnZSBvZiB0cmlwcyBhcmUgbGVzcyB0aGFuIDIgbWludXRlcyBhbmQgbGFyZ2VyIHRoYW4gNSBob3VycyBmb3IgZWFjaCBwaWNrLXVwIGFuZCBkcm9wLW9mZiBib3JvdWdoLg0KDQpgYGB7ciwgZWNobz1GLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQ0KcGljayA9IHJlYWQuY3N2KCIuLi9EYXRhL2RpY3Rpb25hcnlfcGlja3VwLmNzdiIpICU+JQ0KICByZW5hbWUocGlja3VwX2xvY2F0aW9uX2lkID0gUFVsb2NhdGlvbklELCAgcGlja19ib3JvdWdoID0gYm9yb3VnaCkgJT4lDQogIGRhdGEudGFibGUoKQ0KDQpkcm9wID0gcmVhZC5jc3YoIi4uL0RhdGEvZGljdGlvbmFyeV9kcm9wb2ZmLmNzdiIpICU+JQ0KICByZW5hbWUoZHJvcG9mZl9sb2NhdGlvbl9pZCA9IERPbG9jYXRpb25JRCwgIGRyb3BfYm9yb3VnaCA9IGJvcm91Z2gpICU+JQ0KICBkYXRhLnRhYmxlKCkNCg0KI01hdGNoIGJvcm91Z2ggdG8gbG9jYXRpb24gaWQNCm1hdGNoX3pvbmUgPSBmdW5jdGlvbihkZixwaWNrLGRyb3ApDQp7DQogIGRmID0gcGlja1tkZiwgb24gPSAicGlja3VwX2xvY2F0aW9uX2lkIl0NCiAgZGYgPSBkcm9wW2RmLCBvbiA9ICJkcm9wb2ZmX2xvY2F0aW9uX2lkIl0NCiAgZGYgPSBuYS5vbWl0KGRmKQ0KICByZXR1cm4oZGYpDQp9DQoNCnRyaXBfbGVzc18yX21pbiA9IGRhdGFbdHJhdmVsX3RpbWUgPCAxMjBdICU+JQ0KICBtYXRjaF96b25lKC4sIHBpY2ssIGRyb3ApICU+JQ0KICAuWywgLk4sIGJ5ID0gbGlzdChwaWNrX2Jvcm91Z2gsZHJvcF9ib3JvdWdoKV0NCg0KdHJpcF9sZXNzXzJfbWluWywgZnJhYzo9IHJvdW5kKE4vc3VtKE4pLDMpXQ0KDQp0cmlwX2xhcmdlXzMwMF9taW4gPSBkYXRhW3RyYXZlbF90aW1lID4gMTgwMDBdICU+JQ0KICBtYXRjaF96b25lKC4sIHBpY2ssIGRyb3ApICU+JQ0KICAuWywgLk4sIGJ5ID0gbGlzdChwaWNrX2Jvcm91Z2gsZHJvcF9ib3JvdWdoKV0NCg0KdHJpcF9sYXJnZV8zMDBfbWluWywgZnJhYzo9IHJvdW5kKE4vc3VtKE4pLDMpXQ0KDQp2YWxzID0gdW5pcXVlKHNjYWxlczo6cmVzY2FsZSh0cmlwX2xlc3NfMl9taW4kZnJhYykpDQpvID0gb3JkZXIodmFscywgZGVjcmVhc2luZyA9IEZBTFNFKQ0KY29scyA9IHNjYWxlczo6Y29sX251bWVyaWMoIlJlZHMiLCBkb21haW4gPSBOVUxMKSh2YWxzKQ0KY29seiA9IHNldE5hbWVzKGRhdGEuZnJhbWUodmFsc1tvXSwgY29sc1tvXSksIE5VTEwpDQoNCiNQbG90IEhlYXRtYXAgZm9yIHRyaXBzIHdob3NlIGR1cmF0aW9uIGxlc3MgdGhhbiAyIG1pbnMNCnBsb3RfbHkoZGF0YT10cmlwX2xlc3NfMl9taW4sIHg9fnBpY2tfYm9yb3VnaCx5PX5kcm9wX2Jvcm91Z2gsej1+ZnJhYywgY29sb3JzY2FsZSA9IGNvbHosIHR5cGUgPSAiaGVhdG1hcCIpICU+JSBsYXlvdXQodGl0bGUgPSAiTGVzcyB0aGVuIDIgbWlucyIpDQoNCiNQbG90IEhlYXRtYXAgZm9yIHRyaXBzIHdob3NlIGR1cmF0aW9uIGxhcmdlciB0aGFuIDQgaG91cnMNCnBsb3RfbHkoZGF0YT10cmlwX2xhcmdlXzMwMF9taW4sIHg9fnBpY2tfYm9yb3VnaCx5PX5kcm9wX2Jvcm91Z2gsej1+ZnJhYywgY29sb3JzY2FsZSA9IGNvbHosIHR5cGUgPSAiaGVhdG1hcCIpICU+JSBsYXlvdXQodGl0bGUgPSAiTGFyZ2VyIHRoYW4gNSBob3VycyIpDQpgYGANCg0KV2UgZmluZDoNCiogV2UgZm91bmQgdGhlcmUgYXJlIDI1JSBtaXNzaW5nIHZhbHVlIGluIHRoZSBgQVdORGAgYW5kIGZldyBpbiB0aGUgYHBpY2t1cF9sb2NhdGlvbl9pZGAgYW5kIGBkcm9wb2ZmX2xvY2F0aW9uX2lkYC4gVG8gY29uY2x1ZGUgYWNjdXJhdGUgYW5hbHlzaXMsIHdlIGFyZSBnb2luZyB0byBmaWxsIGluIG1lZGlhbiBvZiBgQVdORGAsICBhbmQgcmVtb3ZlIGRhdGEgaWYgYGxvY2F0aW9uSURgIGlzIG1pc3NpbmcNCiogQSBsb3Qgb2YgdHJpcCBvcmRlcnMgaW4gdGhlIHNhbWUgYm9yb3VnaCBhcmUgbGVzcyB0aGFuIDIgbWludXRlcyBkdXJhdGlvbiwgd2hpY2ggbWlnaHQgYmUgY29ycmVjdA0KKiBIb3dldmVyLCBpdCBpcyBpbXBvc3NpYmxlIHRoZSB0cmlwIGlzIGNyb3NzaW5nIHR3byBib3JvdWdoIHRha2luZyBsZXNzIHRoYW4gMiBtaW51dGVzLCBzbyB3ZSBhcmUgY29uc2lkZXJlZCBhcyBjYW5jYWxsZWQgb3JkZXJzLg0KDQoNCmBgYHtyLCBlY2hvPUZ9DQojIHJlcGxhY2UgTkEgQVdORA0KZGF0YVtpcy5uYShkYXRhWywgQVdORF0pLCBBV05EIDo9IG1lZGlhbihkYXRhWywgQVdORF0sIG5hLnJtID0gVCldDQoNCiMgcmVtb3ZlIE5BIG9mIGxvY2F0aW9uSUQNCmRhdGEgPSBkYXRhW3Jvd1N1bXMoaXMubmEoZGF0YSkpID09IDAsXQ0KDQojIHJlbW92ZSBzaG9ydCAmIGxhcmdlIHRyYXZlbCB0aW1lDQpkYXRhID0gZGF0YVt0cmF2ZWxfdGltZSA+IDEyMCAmICB0cmF2ZWxfdGltZSA8IDE4MDAwXQ0KYGBgDQoNCiMjIyAzLjcgRGF0YSBUcmFuc2Zvcm1hdGlvbg0KVGhlIGRlbnNpdHkgcGxvdCBzaG93cyB0aGUgZHVyYXRpb24gZGlzdHJpYnV0aW9uIGhhcyBhIHNpZ25pZmljYW50bHkgcmlnaHQgc2tldy4NCg0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTQsIHJlc3VsdHM9ImhpZGUifQ0KZGVuc2l0eTEgPSBkZW5zaXR5KGRhdGFbLHRyYXZlbF90aW1lXSkNCnBsb3RfbHkoeCA9IH4gZGVuc2l0eTEkeCwgeSA9IH4gZGVuc2l0eTEkeSwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcycsIG5hbWUgPSAnRmFpciBjdXQnLCBmaWxsID0gJ3RvemVyb3knKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkRlbnNpdHkgUGxvdCBmb3IgVHJpcCBEdXJhdGlvbiIsDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gJ0R1cmF0aW9uJyksDQogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnRGVuc2l0eScpKQ0KYGBgDQoNCldlIG1pZ2h0IHRha2UgKipMb2cqKiB0cmFuc2Zvcm1hdGlvbiBvbiB0aGUgZHVyYXRpb24gdG8gc29sdmUgdGhlIHNrZXduZXNzIGlzc3VlIGZvciBmdXJ0dXJlIG1vZGVsaW5nLg0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9DQpkZW5zaXR5MSA9IGRlbnNpdHkobG9nKGRhdGFbLCB0cmF2ZWxfdGltZV0pKQ0KcGxvdF9seSh4ID0gfiBkZW5zaXR5MSR4LCB5ID0gfiBkZW5zaXR5MSR5LCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzJywgbmFtZSA9ICdGYWlyIGN1dCcsIGZpbGwgPSAndG96ZXJveScpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiRGVuc2l0eSBQbG90IGZvciBUcmFuc2Zvcm1hdGVkIFRyYXZlbCBUaW1lIiwNCiAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAnTG9nKER1cmF0aW9uKScpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0RlbnNpdHknKSkNCmBgYA0KDQoNCiMjIyAzLjggRGF0YSBBZ2dyZWdhdGlvbg0Kd2UgY2FuIGFnZ3JlZ2F0ZSBkYXRhIGJ5IGRpZmZlcmVudCBsZXZlbHMgaW4gb3JkZXIgdG8gY2FsY3VhdGUgZGFpbHkgaG91ciBwaWNrLXVwcywgYW5kIG1lZGlhbiB0cmF2ZWwgdGltZSBpbiB3ZWVrZGF5Lg0KDQpgYGB7cn0NCmRhdGFbLCAuTiwgYnkgPSAuKG1vbnRoLmFiYlttb250aChkYXRlKV0sIHdrZGF5LCBwaWNrX2hvdXIpXQ0KYGBgDQoNCg0KYGBge3J9DQpkYXRhWywgLihkLm1lZCA9IG1lZGlhbih0cmF2ZWxfdGltZSkpLCBieSA9IC4od2tkYXksIHBpY2tfaG91cildDQpgYGANCg0KIyMgVi4gUmVzdWx0cw0KDQojIyMgNS4xIE92ZXJhbGwNCkZpcnN0IG9mIGFsbCwgd2Ugd291bGQgbGlrZCB0byB2aWV3IG92ZXJhbGwgbWVkaWFuIHRyYXZlbCB0aW1lIGJhc2VkIG9uIGhvdXIsIHdlZWtkYXkgYW5kIG1vbnRoIGJhc2VzLiANCiogVGhlIG1lZGlhbiBpcyBtb3JlIHJvYnVzdCBtZWFzdXJlbWVudCBiZWNhdXNlIGl0IGhhcyBsZXNzIGVmZmVjdCBvbiBvdXRsaWVycy4NCiogSG91cmx5IGJhc2UgaXMgc2hvd2luZyB0aGUgcGVhayBob3VyIGVmZmVjdHMgaW4gYSB0eXBpY2FsIGRheS4NCiogVGhlIHdlZWtseSBiYXNlIHRlbGxzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gd29yayBkYXkgYW5kIHdlZWtlbmRzLg0KKiBUaGUgbW9udGhseSBiYXNlIGhhcyBhIGdvb2QgZXhwbGFpbmF0aW9uIG9uIHNlYXNvbmFsaXR5Lg0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIHJlc3VsdHM9ImhpZGUiLCBmaWcud2lkdGg9IDgsIGZpZy5oZWlnaHQ9IDR9DQpwMSA9IGRhdGFbLCAuKGQubWVkID0gbWVkaWFuKHRyYXZlbF90aW1lKSksIGJ5ID0gcGlja19ob3VyXSAlPiUNCiAgLltvcmRlcihwaWNrX2hvdXIpXSAlPiUNCnBsb3RfbHkoLiwgeCA9IH4gcGlja19ob3VyLCB5ID0gfiBkLm1lZCwgdHlwZSA9InNjYXR0ZXIiLA0KICAgICAgICBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLA0KICAgICAgICBsaW5lID0gbGlzdChjb2xvcj0iIzJFODZDMSIsIHdpZHRoID0gNCksDQogICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDgsIGNvbG9yID0gJ3JnYmEoMjU1LCAxODIsIDE5MywgLjkpJywgbGluZSA9IGxpc3QoY29sb3IgPSAncmdiYSgxNTIsIDAsIDAsIC44KScsIHdpZHRoID0gMixzaW1wbHlmeSA9IEYpKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICdNZWRpYW4gVHJhdmVsIFRpbWUnLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ1RpbWUoc2VjKSAoc2VjKScsDQogICAgICAgICAgICAgICAgICAgICAgcmFuZ2U9Yyg4MDAsMTIwMCksIHplcm9saW5lID0gRkFMU0UpLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gJ0hvdXInLA0KICAgICAgICAgICAgICAgICAgICAgIHplcm9saW5lID0gRkFMU0UpKQ0KDQpsdmwgPSBjKCJNb25kYXkiLCAiVHVlc2RheSIsICJXZWRuZXNkYXkiLCAiVGh1cnNkYXkiLCAiRnJpZGF5IiwgIlNhdHVyZGF5IiwgIlN1bmRheSIpDQoNCnAyID0gZGF0YVssIC4oZC5tZWQgPSBtZWRpYW4odHJhdmVsX3RpbWUpKSwgYnkgPSBzb3J0KG9yZGVyZWQod2tkYXksIGxldmVscyA9IGx2bCkpXSAlPiUNCiAgcGxvdF9seSguLCB4ID0gfiBzb3J0LCB5ID0gfiBkLm1lZCwgdHlwZSA9InNjYXR0ZXIiLA0KICAgICAgICAgIG1vZGUgPSAnbGluZXMrbWFya2VycycsDQogICAgICAgICAgbGluZSA9IGxpc3Qod2lkdGg9NCxzaW1wbHlmeSA9IEYsIGNvbG9yPSdyZ2IoMTE0LCAxODYsIDU5KScpLA0KICAgICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDgsIGNvbG9yID0gJyM4RTQ0QUQnLCBsaW5lID0gbGlzdChjb2xvciA9ICcjMzQ5OERCJywgd2lkdGggPSAyKSkpICU+JQ0KICBsYXlvdXQoeWF4aXMgPSBsaXN0KHRpdGxlID0gJ1RpbWUgKHNlYyknLCByYW5nZT1jKDk1MCwxMTAwKSx6ZXJvbGluZSA9IEZBTFNFKSwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdXZWVrZGF5JywgemVyb2xpbmUgPSBGQUxTRSkpDQoNCnAzID0gZGF0YVssIC4oZC5tZWQgPSBtZWRpYW4odHJhdmVsX3RpbWUpKSwgYnkgPSBzb3J0KG9yZGVyZWQobW9udGguYWJiW21vbnRoXSwgbGV2ZWxzID0gbW9udGguYWJiKSldICU+JQ0KICBwbG90X2x5KC4sIHggPSB+IHNvcnQsIHkgPSB+IGQubWVkLCB0eXBlID0ic2NhdHRlciIsDQogICAgICAgICAgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywNCiAgICAgICAgICBsaW5lPWxpc3QoY29sb3I9IiNGRjU3MzMiLHdpZHRoID0gNCksDQogICAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gOCwgY29sb3IgPSAnI0ZGRkYwMCcsIGxpbmUgPSBsaXN0KGNvbG9yID0gJyNFNjdFMjInLCB3aWR0aCA9IDIpKSkgJT4lDQogIGxheW91dCh5YXhpcyA9IGxpc3QodGl0bGUgPSAnVHJhdmVsIFRpbWUnLHJhbmdlPWMoOTAwLDExNTApLCB6ZXJvbGluZSA9IEZBTFNFKSwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdNb250aCcsIHplcm9saW5lID0gRkFMU0UpKQ0KDQpzdWJwbG90KHAxLCBwMiwgcDMsIG5yb3dzID0gMykNCmBgYA0KV2UgZmluZDoNCg0KKiBUaGUgcnVzaCBob3VyIG9jY3VycyBmcm9tIDggQU0gLSA3IFBNIGFib3V0IDE4IG1pbnV0ZXMsIG90aGVydmlzZSBsZXNzIHRyYWZmaWMgaXMgYWZ0ZXIgOCBQTQ0KKiBUaGUgaGlnaGVzdCB0cmF2ZWwgdGltZSBpcyAyMCBtaW51dGVzIGF0IDQgUE0sIHRoZSBsb3dlc3QgdHJhdmVsIHRpbWUgaXMgMTQgbWludXRlc3QgYXQgMiBBTSAoaW50ZXJzdGluZyEpDQoqIER1cmluZyB3ZWVrZW5kcywgdGhlIHRyYXZlbCB0aW1lIGlzIGhpZ2hlciB0aGFuIHdlZWtkYXlzDQoqIEZvciBtb250aGx5LCBpdCBzaG93cyB2ZXJ5IHN0cm9uZyBzZWFzb25hbGl0eSB0aGF0IHNwcmluZyBhbmQgZmFsbCBoYXZlIGhpZ2hlciB0cmF2ZWwgdGltZTsgc3VtbWVyIGFuZCB3aW50ZXIgYXJlIG11Y2ggbG93ZXIuDQoNCiMjIyA1LjIgVHlwZXMNCldlIGludmVzdGlnYXRlIG9uIGhvdyB0aGUgbWVkaWFuIHRyYXZlbCB0aW1lIGRlcGVuZGVzIG9uIHRoZSBkaWZmZXJlbnQgY29tcGFuaWVzIGluIGhvdXJseSwgd2Vla2x5IGFuZCBtb250aGx5IGJhc2VzLg0KYGBge3IsIGVjaG89Rn0NCmFjY3VtdWxhdGVfYnkgPSBmdW5jdGlvbihkYXQsIHZhcikNCnsNCiAgdmFyID0gbGF6eWV2YWw6OmZfZXZhbCh2YXIsIGRhdCkNCiAgbHZscyA9IHBsb3RseTo6OmdldExldmVscyh2YXIpDQogIGRhdHMgPSBsYXBwbHkoc2VxX2Fsb25nKGx2bHMpLCBmdW5jdGlvbih4KSB7Y2JpbmQoZGF0W3ZhciAlaW4lIGx2bHNbc2VxKDEsIHgpXSwgXSwgZnJhbWUgPSBsdmxzW1t4XV0pfSkNCiAgZHBseXI6OmJpbmRfcm93cyhkYXRzKQ0KfQ0KYGBgDQoNCg0KYGBge3IsIGZpZy53aWR0aD0gOCwgZmlnLmhlaWdodD0gNH0NCmRhdGFbLCAuKGQubWVkID0gbWVkaWFuKHRyYXZlbF90aW1lKSksIGJ5ID0gLihwaWNrX2hvdXIsIHR5cGUpXSAlPiUgDQogIC5bb3JkZXIocGlja19ob3VyLCB0eXBlKV0gJT4lDQogIGFzX3RpYmJsZSgpICU+JSBhY2N1bXVsYXRlX2J5KH4gcGlja19ob3VyKSAlPiUNCiAgcGxvdF9seSguLCB4ID0gfiBwaWNrX2hvdXIsIHkgPSB+IGQubWVkLCBmcmFtZSA9IH4gZnJhbWUsDQogICAgICAgICAgY29sb3JzPWMoImhvdHBpbmsiLCJibGFjayIsInN0ZWVsYmx1ZSIpLA0KICAgICAgICAgIGNvbG9yID0gfiB0eXBlLCB0eXBlID0ic2NhdHRlciIsIG1vZGUgPSAnbGluZXMrbWFya2VycycsDQogICAgICAgICAgbGluZSA9IGxpc3Qod2lkdGg9NCxzaW1wbHlmeSA9IEYpLCBtYXJrZXIgPSBsaXN0KHNpemU9MTApLA0KICAgICAgICAgIHRleHQgPSB+cGFzdGUoIkhvdXI6ICIsIHBpY2tfaG91ciwgJzxicj5BdmVyYWdlIGR1cmF0aW9uOicsDQogICAgICAgICAgICAgICAgICAgICAgICByb3VuZChkLm1lZCwyKSkpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnSG91cmx5IE1lZGlhbiBUcmF2ZWwgVGltZScsDQogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnRHVyYXRpb24gKFNlYyknLCB6ZXJvbGluZSA9IEZBTFNFKSwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdIb3VyJywgemVyb2xpbmUgPSBGQUxTRSkpICU+JSANCiAgYW5pbWF0aW9uX29wdHMoZnJhbWUgPSAzMDAsIHRyYW5zaXRpb24gPSAyMDAsIHJlZHJhdyA9IEYpICU+JQ0KICBhbmltYXRpb25fc2xpZGVyKGhpZGUgPSBUKSAlPiUNCiAgYW5pbWF0aW9uX2J1dHRvbih4ID0gMSwgeGFuY2hvciA9ICJyaWdodCIsIHkgPSAwLCB5YW5jaG9yID0gImJvdHRvbSIpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aD0gOCwgZmlnLmhlaWdodD0gNH0NCmRhdGFbLCAuTiwgYnkgPSAuKHBpY2tfaG91ciwgdHlwZSldICU+JSANCiAgLltvcmRlcihwaWNrX2hvdXIsIHR5cGUpXSAlPiUNCiAgZGNhc3QoLiwgcGlja19ob3VyIH4gdHlwZSwgdmFsdWUudmFyID0gIk4iICkgJT4lDQogIHBsb3RfbHkoLiwgeCA9IH5waWNrX2hvdXIsIHkgPSB+VWJlciwgdHlwZSA9ICdiYXInLCBuYW1lID0gJ1ViZXInLA0KICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAnYmxhY2snKSkgJT4lDQogIGFkZF90cmFjZSh5ID0gfkx5ZnQsIG5hbWUgPSAnTHlmdCcsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiaG90cGluayIpKSAlPiUNCiAgYWRkX3RyYWNlKHkgPSB+VmlhLCBuYW1lID0gJ1ZpYScsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAic3RlZWxibHVlIikpICU+JQ0KICBsYXlvdXQoeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0NvdW50cycpLCBiYXJtb2RlID0gJ2dyb3VwJywNCiAgICAgICAgIHRpdGxlID0gIkhvdXIgVHJpcCBDb3VudHMiKQ0KYGBgDQoNCg0KYGBge3IsIHJlc3VsdHM9ImhpZGUiLCBmaWcud2lkdGg9IDgsIGZpZy5oZWlnaHQ9IDQsbWVzc2FnZT1GLGVycm9yPUZ9DQpkYXRhWywgLihkLm1lZCA9IG1lZGlhbih0cmF2ZWxfdGltZSkpLCBieSA9IC4oc29ydChvcmRlcmVkKHdrZGF5LCBsZXZlbHMgPSBsdmwpKSwgdHlwZSldICU+JSANCiAgYXNfdGliYmxlKCkgJT4lDQogIGFjY3VtdWxhdGVfYnkofiBzb3J0KSAlPiUNCiAgYXJyYW5nZShzb3J0KSAlPiUNCnBsb3RfbHkoLiwgeCA9IH4gc29ydCwgeSA9IH4gZC5tZWQsICBjb2xvcnM9IGMoImhvdHBpbmsiLCJibGFjayIsInN0ZWVsYmx1ZSIpLCBjb2xvciA9IH4gdHlwZSwNCiAgICAgICAgdHlwZSA9InNjYXR0ZXIiLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLGxpbmUgPSBsaXN0KHdpZHRoPTQsc2ltcGx5ZnkgPSBGKSwgbWFya2VyID0gbGlzdChzaXplPTEwKSwNCiAgICAgICAgdGV4dCA9IH5wYXN0ZSgiV2Vla2RheUhvdXI6ICIsc29ydCwgJzxicj5NZWRpYW4gZHVyYXRpb246Jywgcm91bmQoZC5tZWQsMikpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gJ1dlZWtkYXkgTWVkaWFuIFRyYXZlbCBUaW1lJywNCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdEdXJhdGlvbiAoU2VjKScsemVyb2xpbmUgPSBGQUxTRSksDQogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAnV2Vla2RheScsIHplcm9saW5lID0gRkFMU0UpKSAlPiUNCiAgYW5pbWF0aW9uX29wdHMoZnJhbWUgPSAzMDAsIHRyYW5zaXRpb24gPSAyMDAsIHJlZHJhdyA9IEYpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aD0gOCwgZmlnLmhlaWdodD0gNH0NCmRhdGFbLCAuTiwgYnkgPSAuKHNvcnQob3JkZXJlZCh3a2RheSwgbGV2ZWxzID0gbHZsKSksIHR5cGUpXSAlPiUNCiAgZGNhc3QoLiwgc29ydCB+IHR5cGUsIHZhbHVlLnZhciA9ICJOIikgJT4lDQogIHBsb3RfbHkoLiwgeCA9IH5zb3J0LCB5ID0gflViZXIsIHR5cGUgPSAnYmFyJywgbmFtZSA9ICdVYmVyJywNCiAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gJ2JsYWNrJykpICU+JQ0KICBhZGRfdHJhY2UoeSA9IH5MeWZ0LCBuYW1lID0gJ0x5ZnQnLCBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImhvdHBpbmsiKSkgJT4lDQogIGFkZF90cmFjZSh5ID0gflZpYSwgbmFtZSA9ICdWaWEnLCBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInN0ZWVsYmx1ZSIpKSAlPiUNCiAgbGF5b3V0KHlheGlzID0gbGlzdCh0aXRsZSA9ICdDb3VudHMnKSwgYmFybW9kZSA9ICdncm91cCcsDQogICAgICAgICB0aXRsZSA9ICJXZWVrZGF5IFRyaXAgQ291bnRzIikNCmBgYA0KDQoNCmBgYHtyLCBmaWcud2lkdGg9IDgsIGZpZy5oZWlnaHQ9IDR9DQpkYXRhWywgLihkLm1lZCA9IG1lZGlhbih0cmF2ZWxfdGltZSkpLCBieSA9IC4obW9udGgsIHR5cGUpXSAlPiUgDQogIGFzX3RpYmJsZSgpICU+JQ0KICBhY2N1bXVsYXRlX2J5KH4gbW9udGgpICU+JQ0KICBhcnJhbmdlKG1vbnRoKSAlPiUNCiAgcGxvdF9seSguLCB4ID0gfiBtb250aCwgeSA9IH4gZC5tZWQsIGNvbG9ycz0gYygiaG90cGluayIsImJsYWNrIiwic3RlZWxibHVlIiksDQogICAgICAgICAgZnJhbWUgPSB+IGZyYW1lLCBjb2xvciA9IH4gdHlwZSwgdHlwZSA9InNjYXR0ZXIiLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLA0KICAgICAgICAgIGxpbmUgPSBsaXN0KHdpZHRoPTQsc2ltcGx5ZnkgPSBGKSwgbWFya2VyID0gbGlzdChzaXplPTEwKSwNCiAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCJNb250aDogIixtb250aCwgJzxicj5NZWRpYW4gZHVyYXRpb246Jywgcm91bmQoZC5tZWQsMikpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gJ01vbnRobHkgTWVkaWFuIFRyYXZlbCBUaW1lJywNCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdEdXJhdGlvbiAoU2VjKScsemVyb2xpbmUgPSBGQUxTRSksDQogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAnTW9udGgnLCB6ZXJvbGluZSA9IEZBTFNFKSkgJT4lIA0KICBhbmltYXRpb25fb3B0cyhmcmFtZSA9IDMwMCwgdHJhbnNpdGlvbiA9IDIwMCwgcmVkcmF3ID0gRikgJT4lDQogIGFuaW1hdGlvbl9zbGlkZXIoaGlkZSA9IFQpICU+JQ0KICBhbmltYXRpb25fYnV0dG9uKHggPSAxLCB4YW5jaG9yID0gInJpZ2h0IiwgeSA9IDAsIHlhbmNob3IgPSAiYm90dG9tIikNCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPSA4LCBmaWcuaGVpZ2h0PSA0fQ0KZGF0YVssIC5OLCBieSA9IC4obW9udGgsIHR5cGUpXSAlPiUNCiAgZGNhc3QoLiwgbW9udGggfiB0eXBlLCB2YWx1ZS52YXIgPSAiTiIpICU+JQ0KICBwbG90X2x5KC4sIHggPSB+bW9udGgsIHkgPSB+VWJlciwgdHlwZSA9ICdiYXInLCBuYW1lID0gJ1ViZXInLA0KICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAnYmxhY2snKSkgJT4lDQogIGFkZF90cmFjZSh5ID0gfkx5ZnQsIG5hbWUgPSAnTHlmdCcsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiaG90cGluayIpKSAlPiUNCiAgYWRkX3RyYWNlKHkgPSB+VmlhLCBuYW1lID0gJ1ZpYScsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAic3RlZWxibHVlIikpICU+JQ0KICBsYXlvdXQoeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0NvdW50cycpLCBiYXJtb2RlID0gJ2dyb3VwJywNCiAgICAgICAgIHRpdGxlID0gIk1vbnRoIFRyaXAgQ291bnRzIikNCmBgYA0KDQpXZSBmaW5kOg0KDQoqIEZvciB0aXBpY2FsIGRheSwgVWJlciwgTHlmdCBhbmQgVmlhIGhhdmUgc2ltbWlsYXIgdHJpcCBkdXJhdGlvbiBpbiBlYWNoIGhvdXIgDQoqIEZvciB3ZWVrbHkgYmFzZSwgTHlmdCBoYXMgYSBsaXR0ZXIgaGlnaGVyIHRyaXAgZHVyYXRpb24gdGhhbiBvdGhlcnMsIGVzcGVjaWFsbHkgb24gTW9uZGF5IChpbnRlcmVzdGluZyEpDQoqIEZvciBtb250aGx5IGJhc2UsIFZpYSBpcyB0aGUgaGlnaGVzdCBiZWNhdXNlIG1vc3QgdHJpcHMgYXJlIHNoYXJlIHJpZGVycywgd2hpY2ggdGFrZXMgbG9uZ2VyIHRpbWUNCiogT3ZlcmFsbHksIFViZXIgaGFzIGxvd2VzdCB0cmlwIGR1cmF0aW9uIGNvbXBhcmluZyBvdGhlciB0d28hDQoNCiMjIyA1LjMgTWFya2V0IFNoYXJlDQoNCldlIGFsc28gc3R1ZHkgdGhlIG1hcmtldCBzaGFyZXMgb24gdGhlIGJvdGggc3BhY2UgYW5kIHRpbWUgbGluZSwgc28gY3JlYXRlIGFuIGludGVyYWN0aXZlIG1hcCAqKk5ZQyBGSFYgTWFya2V0c2hhcmUgbWFwKiogdG8gaW5kaWN0YXRlIHBlcmNlbnRhZ2Ugb2YgbWFya2V0c2hhcmUgZm9yIFViZXIsIEx5ZnQgYW5kIFZpYSBhdCBkaWZmZXJlbnQgcGljayB1cCB6b25lLiBCeSBzaW1wbHkgY2xpY2tpbmcgdGhlIG1hcCwgeW91IGNhbiBzZWUgbWFya2V0c2hhcmUgZGF0YSBpbiBlYWNoIHpvbmUuIFRoZSBsZWdlbmQgbGllcyBpbiB0aGUgcmlnaHQgaGFuZCBzaWRlLCB3aGVyZSB5b3UgY2FuIGFsc28gYWx0ZXIgZGlmZmVyZW50IHZpZXdzIGZvciBlYWNoIHR5cGVzIG9mIHRheGkgYnkgY2xpY2tpbmcgdGhyZWUgVGVhcmRyb3Atc2hhcGVkIGJ1dHRvbnMgb2YgYXBwbHlpbmcgYXV0byBzdHlsZS4gKGh0dHBzOi8venhmNzE2OTkuY2FydG8uY29tL2J1aWxkZXIvNjJkOGM4MTUtMjgzOS00MWZlLTk1ZTAtODRhYzZlNGVjY2I2L2VtYmVkKQ0KDQohW05ZQyBGSFYgTWFya2V0c2hhcmUgbWFwIChodHRwczovL3p4ZjcxNjk5LmNhcnRvLmNvbS9idWlsZGVyLzYyZDhjODE1LTI4MzktNDFmZS05NWUwLTg0YWM2ZTRlY2NiNi9lbWJlZCldKC4vaW50ZXJhY3RpdmUgbWFya2V0IHNoYXJlLmpwZykNCg0KV2UgZmluZDoNCg0KKiBVYmVyIGhhcyBkb21pbmFudCBvbiB0aGUgRkhWIG1hcmtldCwgcmVhY2hpbmcgNzUlIGVudGlyZSBtYXJrZXQNCiogTHlmdCBpcyBzZWNvbmQgZG9taW5hbnQgb24gdGhlIEZIViBtYXJrZXQsIGFuZCB3ZWVrZW5kcyBoYXZlIGhpZ2hlciBudW1iZXJzIG9mIHRyaXBzLg0KKiBWaWEgaXMgYSBncm93aW5nIGNvbXBhbnksIHNvIGl0IHRha2VzIGEgc21hbGwgcHJvcG9ydGlvbiBvZiBtYXJrZXQsIGJ1dCBpdCBmb2N1c2VzIG9uIHRoZSBwZWFraW5nIGhvdXIuDQoNCiMjIyA1LjQgV2VhdGhlciBFZmZlY3QNCg0KV2UgaGF2ZSBlbmNvdXJhZ2VkIHRvIHN1cHBsZW1lbnQgb3VyIGFuYWx5c2lzIHdpdGggY29tYmluaW5nIHRoZSBleHR1cmFsIE5ZQyB3ZWF0aGVyIGRhdGEgdG8gc3R1ZHkgaG93IHdlYXRoZXIgaXMgaW1wYWN0ZWQgb24gdGhlIHRyaXAgZHVyYXRpb24uIE9mIHBhcnRpY3VsYXIgaW50ZXJlc3QgaGVyZSB3aWxsIGJlIHRoZSByYWluLCBzbm93IGZhbGwsIGFuZCBzdW4gc3RhdGlzdGljcy4NCg0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9DQpkZl9yYWluID0gZGF0YVsoUFJDUCA+IDApICYgKFNOT1cgPT0gMCksIHRyYXZlbF90aW1lXQ0KZGZfc25vd19yYWluID0gZGF0YVsoUFJDUCA+IDApICYgKFNOT1cgPiAwKSwgdHJhdmVsX3RpbWVdDQpkZl9zdW5ueSA9IGRhdGFbKFBSQ1AgPT0gMCkgJiAoU05PVyA9PSAwKSwgdHJhdmVsX3RpbWVdDQpkZl9zbm93ID0gZGF0YVsoU05XRCA+IDApLCB0cmF2ZWxfdGltZV0NCg0KcGxvdF9seSh0eXBlID0gJ2JveCcpICU+JQ0KICBhZGRfYm94cGxvdCh4ID0gZGZfcmFpbiwgbmFtZSA9ICJSYWlueSBEYXlzIiwgZmlsbGNvbG9yID0gJ3JnYmEoMjU0LDIzMSwzNywwLjQpJywNCiAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICdyZ2JhKDIxOSwgNjQsIDgyLCAxLjApJyksDQogICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJ3JnYmEoMjUzLDIzMSwzNywxMDApJykpICU+JQ0KICBhZGRfYm94cGxvdCh4ID0gZGZfc25vd19yYWluLCBuYW1lID0gIlNub3d5IGFuZCBSYWlueSBEYXlzIixmaWxsY29sb3IgPSAncmdiYSg5MywyMDAsOTksMC40KScsDQogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiYSgyMTksIDY0LCA4MiwgMS4wKScpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJ3JnYmEoOTMsMjAwLDk5LDEwMCknKSkgJT4lDQogIGFkZF9ib3hwbG90KHggPSBkZl9zdW5ueSwgbmFtZSA9ICJTdW5ueSBEYXlzIiwgZmlsbGNvbG9yID0gJ3JnYmEoMzMsMTQ0LDE0MCwwLjQpJywNCiAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICdyZ2JhKDIxOSwgNjQsIDgyLCAxLjApJyksDQogICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJ3JnYmEoMzMsMTQ0LDE0MCwxMDApJykpICU+JQ0KICBhZGRfYm94cGxvdCh4ID0gZGZfc25vdywgbmFtZSA9ICJTbm93eSBEYXlzIiwgZmlsbGNvbG9yID0gJ3JnYmEoNTksODIsMzksMC40KScsDQogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiYSgyMTksIDY0LCA4MiwgMS4wKScpLA0KICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICdyZ2JhKDU5LDgyLDM5LDEwMCknKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICJXZWF0aGVyIEVmZmVjdHMgb24gVHJhdmVsIFRpbWUiLCBob3Zlcm1vZGUgPSAnY29tcGFyZScsDQogICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJycsIHplcm9saW5lID1ULCBzaG93Z3JpZCA9IFQpLA0KICAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdEdXJhdGlvbiAoU2VjKScsIHplcm9saW5lID0gVCwgc2hvd2dyaWQgPSBUKSkNCmBgYA0KDQoNCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00LCByZXN1bHRzPSJoaWRlIiwgd2FybmluZz1GLCB3YXJuaW5nPUYsZXJyb3I9Rn0NCnBsb3RfbHkodHlwZSA9ICdiYXInKSAlPiUNCiAgYWRkX2JhcnMoeCA9IGxlbmd0aChkZl9yYWluKSwgeT0gIlJhaW55IERheXMiLG5hbWUgPSAiUmFpbnkgRGF5cyIsIGZpbGxjb2xvciA9ICdyZ2JhKDI1NCwyMzEsMzcsMC40KScsDQogICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiYSgyNTQsMjMxLDM3LDAuNCknKSwNCiAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAncmdiYSgyNTMsMjMxLDM3LDEwMCknKSwNCiAgICAgICAgICAgdGV4dCA9IH5wYXN0ZSgid2VhdGhlcjoiLCJSYWlueSBEYXlzIiwiPGJyPlRyaXAgY291bnRzOiIsbGVuZ3RoKGRmX3JhaW4pKSkgJT4lDQogIGFkZF9iYXJzKHggPSBsZW5ndGgoZGZfc25vd19yYWluKSwgICB5PSAiU25vd3kgYW5kIFJhaW55IERheXMiLA0KICAgICAgICAgICBuYW1lID0gIlNub3d5IGFuZCBSYWlueSBEYXlzIiwgZmlsbGNvbG9yID0ncmdiYSg5MywyMDAsOTksMC40KScsDQogICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiYSg5MywyMDAsOTksMC40KScpLA0KICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICdyZ2JhKDkzLDIwMCw5OSwxMDApJyksDQogICAgICAgICAgIHRleHQgPSB+IHBhc3RlKCJ3ZWF0aGVyOiIsIlNub3d5IGFuZCBSYWlueSBEYXlzIiwiPGJyPlRyaXAgY291bnRzOiIsbGVuZ3RoKGRmX3Nub3dfcmFpbikpKSU+JQ0KICBhZGRfYmFycyh4ID0gbGVuZ3RoKGRmX3N1bm55KSwgeSA9ICJTdW5ueSBEYXlzIiwgbmFtZSA9ICJTdW5ueSBEYXlzIiwgZmlsbGNvbG9yID0gJ3JnYmEoMzMsMTQ0LDE0MCwwLjQpJywNCiAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICdyZ2JhKDMzLDE0NCwxNDAsMC40KScpLA0KICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICdyZ2JhKDMzLDE0NCwxNDAsMTAwKScpLCANCiAgICAgICAgICAgdGV4dCA9IH5wYXN0ZSgid2VhdGhlcjoiLCJTdW5ueSBEYXlzIiwiPGJyPlRyaXAgY291bnRzOiIsbGVuZ3RoKGRmX3N1bm55KSkpICU+JQ0KICBhZGRfYmFycyh4ID0gbGVuZ3RoKGRmX3Nub3cpLCB5PSAiU25vd3kgRGF5cyIsbmFtZSA9ICJTbm93eSBEYXlzIiwgZmlsbGNvbG9yID0gJ3JnYmEoNTksODIsMzksMC40KScsDQogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiYSg1OSw4MiwzOSwwLjQpJyksDQogICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJ3JnYmEoNTksODIsMzksMTAwKScpLA0KICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCJ3ZWF0aGVyOiIsIlNub3d5IERheXMiLCI8YnI+VHJpcCBjb3VudHM6IixsZW5ndGgoZGZfc25vdykpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIldlYXRoZXIgRWZmZWN0IG9uIFRyaXAgQ291bnRzIiwNCiAgICAgICAgICB5YXhpcyA9IGxpc3QoVElUTEU9IldlYXRoZXIiLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93Z3JpZCA9IFQpLA0KICAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdUcmlwIENvdW50cycsIHplcm9saW5lID0gRiwgc2hvd2dyaWQgPSBUKSkNCmBgYA0KDQpXZSBmaW5kOg0KDQoqIEZvciBzdW5ueSBkYXlzLCB0aGVyZSBhcmUgdGhlIGxhcmdlc3QgYW1vdW50IG9mIHRyaXAgcmVxdWVzdHMuIEl0IHRlbGxzIG1vc3QgcGVvcGxlIHByZWZlciB0byBoYW5kIG91dC4NCiogUmFpbiBjYXVzZXMgdGhlIGxhcmdlciBhbW91bnQgb3JkZXIgcmVxdWVzdHMgYW5kIGxvbmdlciB0cmlwIGR1cmF0aW9uIA0KKiBGb3Igc25vd3kgZGF5cywgdGhlcmUgYXJlIGEgZmV3IG91dGxpZXJzLCBzbyBpdCBtaWdodCB0ZWxscyBtb3JlIGxpa2VseSBvY2N1cnMgZXh0cmVtZWx5IGNhc2VzIGluIHRoZSBiYWQgd2VhdGhlci4NCiogSXQgc2VlbXMgbW9yZSBsaWtlIHNub3cgd291bGQgbGVhZCB0byBzaG9ydGVyIHRyaXBzLCBzbyBpdCBjb3VsZCBzaW1wbHkgbWVhbiBwYXNzZW5nZXJzIHdlcmUgbW9yZSBsaWtlbHkgdG8gdHJhdmVsIHNob3J0ZXIgZGlzdGFuY2VzLCBvciBzdGF5IGF0IGhvbWUu