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:
- 17.2 GB of raw data
- 300+ million for-hire vehicle total trips
- 365 daily weather records
Existing problem:
- R reads entire data set into RAM all at once. Total 17.2 GB of raw data would not fit in local memory at once.
- R Objects live in memory entirely, which cause slowness for data analysis.
- The TLC publishes base trip record data as submitted by the bases, and we cannot guarantee or confirm their accuracy or completeness.
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.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