Access your data
We analyze the full taxi data as described by Todd Schneider in using R and sparklyr. We load the billion record trips table into Apache Spark and then use sparklyr and dplyr to manipulate the data and run machine learning algorithms at scale.
The data represent 200 GB of uncompressed data in CSV format. When converted and compressed in the parquet format, the data are 70 GB. The data are stored in HDFS and pre-loaded in a Hive table.
The Hadoop cluster runs on Elastic Map Reduce (EMR) in AWS and has 12 worker nodes and one master node. The master node has R, RStudio Server Pro, and sparklyr loaded onto it.
Connect to spark
Use sparklyr to create a new connection to Apache Spark.
# Load libraries
library(ggplot2)
library(leaflet)
library(geosphere)
library(tidyr)
library(shiny)
library(sparklyr)
library(dplyr)
library(miniUI)
library(DT)
# Configure cluster
Sys.setenv(SPARK_HOME="/usr/lib/spark")
config <- spark_config()
config$spark.driver.cores <- 32
config$spark.executor.cores <- 32
config$spark.executor.memory <- "40g"
# Connect to cluster
sc <- spark_connect(master = "yarn-client", config = config, version = '1.6.1')
Prepare your data
# Create table references
trips_tbl <- tbl(sc, "trips_par")
nyct2010_tbl <- tbl(sc, "nyct2010")
# Join tables
trips_joined_tbl <- trips_tbl %>%
filter(!is.na(pickup_nyct2010_gid) & !is.na(dropoff_nyct2010_gid)) %>%
select(pickup_datetime, dropoff_datetime,
pickup_latitude, dropoff_latitude,
pickup_longitude, dropoff_longitude,
pickup_nyct2010_gid, dropoff_nyct2010_gid,
tip_amount, fare_amount, vendor_id, passenger_count, trip_distance) %>%
left_join(
select(nyct2010_tbl, pickup_gid = gid, pickup_boro = boroname, pickup_nta = ntaname),
by = c("pickup_nyct2010_gid" = "pickup_gid")) %>%
left_join(
select(nyct2010_tbl, dropoff_gid = gid, dropoff_boro = boroname, dropoff_nta = ntaname),
by = c("dropoff_nyct2010_gid" = "dropoff_gid")) %>%
sdf_register("trips_par_joined")
# Cache table
tbl_cache(sc, "trips_par_joined")
Understand your data
Spark SQL and dplyr
Use dplyr syntax to write Spark SQL. When you’re ready to visualize your data, use collect to bring data into R memory. Notice the data describe roughly 170 million trips per year.
# Calculate total trips
trips_joined_tbl %>% count
Source: query [?? x 1]
Database: spark connection master=yarn-client app=sparklyr local=FALSE
n
<dbl>
1 1184591756
# Calculate trips by year
trip_by_year <- trips_joined_tbl %>%
mutate(year = year(pickup_datetime)) %>%
group_by(year) %>%
summarize(n = n()) %>%
collect()
# Plot trips by year
ggplot(trip_by_year, aes(year, n)) +
geom_bar(stat="Identity") +
scale_y_continuous(labels = scales::comma) +
labs(title = "Number of trips by year", x = "Year", y = "")

You can measure the time between pickup and dropoff for any two locations. In this example, we measure the time between JFK and mid-town. Notice the longest trip times occur around 4 PM.
# Calcluate trip time for a specific pickup and dropoff
pickup_dropoff_tbl <- trips_joined_tbl %>%
filter(pickup_nyct2010_gid == 1250 & dropoff_nyct2010_gid == 2056) %>%
mutate(pickup_hour = hour(pickup_datetime)) %>%
mutate(trip_time = unix_timestamp(dropoff_datetime) - unix_timestamp(pickup_datetime)) %>%
group_by(pickup_hour) %>%
summarize(n = n(),
trip_time_mean = mean(trip_time),
trip_time_p10 = percentile(trip_time, 0.10),
trip_time_p25 = percentile(trip_time, 0.25),
trip_time_p50 = percentile(trip_time, 0.50),
trip_time_p75 = percentile(trip_time, 0.75),
trip_time_p90 = percentile(trip_time, 0.90))
# Collect results
pickup_dropoff <- collect(pickup_dropoff_tbl)
# Plot
ggplot(pickup_dropoff, aes(x = pickup_hour)) +
geom_line(aes(y = trip_time_p50, alpha = "Median")) +
geom_ribbon(aes(ymin = trip_time_p25, ymax = trip_time_p75,
alpha = "25–75th percentile")) +
geom_ribbon(aes(ymin = trip_time_p10, ymax = trip_time_p90,
alpha = "10–90th percentile")) +
scale_y_continuous("trip duration in minutes")

Spark ML
With Spark ML you can run machine learning algorithms against all your data in Spark. Here we attempt to understand what factors influence tip amounts.
# Select a model data set
model_tbl <- trips_joined_tbl %>%
filter(pickup_nyct2010_gid == 1250 & dropoff_nyct2010_gid == 2056) %>%
mutate(pickup_hour = hour(pickup_datetime)) %>%
mutate(pickup_week = weekofyear(pickup_datetime)) %>%
mutate(pickup_year = year(pickup_datetime)) %>%
mutate(trip_time = unix_timestamp(dropoff_datetime) - unix_timestamp(pickup_datetime)) %>%
filter(!is.na(pickup_nyct2010_gid) & !is.na(dropoff_nyct2010_gid)) %>%
filter(!is.na(tip_amount)) %>%
filter(!is.na(fare_amount)) %>%
filter(!is.na(vendor_id)) %>%
filter(!is.na(pickup_hour)) %>%
filter(!is.na(pickup_week)) %>%
filter(!is.na(passenger_count)) %>%
filter(!is.na(trip_time)) %>%
filter(!is.na(trip_distance))
# Partitioin into train and validate
model_partition_tbl <- model_tbl %>%
sdf_partition(train = 0.8, test = 0.2, seed = 1234)
# Create table references
trips_train_tbl <- sdf_register(model_partition_tbl$train, "trips_train")
trips_test_tbl <- sdf_register(model_partition_tbl$train, "trips_test")
# Cache model data
tbl_cache(sc, "trips_train")
# Model data
model_formula <- formula(tip_amount ~
fare_amount + vendor_id + pickup_hour + pickup_week +
passenger_count + trip_time + trip_distance)
m1 <- ml_linear_regression(trips_train_tbl, model_formula)
summary(m1)
Call: ml_linear_regression(trips_train_tbl, model_formula)
Deviance Residuals: (approximate):
Min 1Q Median 3Q Max
-25.2997 -4.6801 -0.9295 5.0750 144.4012
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -1.3565e+00 1.6172e-01 -8.3879 < 2.2e-16 ***
fare_amount 1.7752e-01 2.7468e-03 64.6287 < 2.2e-16 ***
vendor_id_2 4.6725e-01 7.6849e-02 6.0800 1.205e-09 ***
vendor_id_CMT -1.5830e+00 6.2922e-02 -25.1581 < 2.2e-16 ***
vendor_id_DDS -3.1478e+00 1.3872e-01 -22.6917 < 2.2e-16 ***
vendor_id_VTS -1.4759e+00 6.2890e-02 -23.4686 < 2.2e-16 ***
pickup_hour -2.1475e-02 3.5354e-03 -6.0743 1.248e-09 ***
pickup_week 1.9910e-03 9.9476e-04 2.0015 0.04535 *
passenger_count -2.5653e-01 1.1200e-02 -22.9058 < 2.2e-16 ***
trip_time -8.0990e-06 8.6924e-06 -0.9317 0.35148
trip_distance -9.4866e-05 7.1767e-05 -1.3219 0.18622
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
R-Squared: 0.05971
Root Mean Squared Error: 5.526
Shiny Gadget
It is often useful to visualize the analyses interactively. Use a Shiny gadget to explore other pickup and dropoff locations.
# Create list of geo groups to select from
geo_group <- trips_joined_tbl %>%
distinct(pickup_nta) %>%
arrange(pickup_nta) %>%
collect
# Create the gadget user interface
ui <- miniPage(
gadgetTitleBar("NYC Taxi Trips"),
miniTabstripPanel(
miniTabPanel("Inputs", icon = icon("sliders"),
miniContentPanel(
selectInput("pickup", "Taxi Pickup", geo_group, "Lincoln Square"),
selectInput("dropoff", "Taxi Dropoff", geo_group, "Upper West Side")
)
),
miniTabPanel("Plot", icon = icon("area-chart"),
miniContentPanel(
plotOutput("tripTimePlot")
)
),
miniTabPanel("Map", icon = icon("map-o"),
miniContentPanel(
leafletOutput("tripLeaflet")
)
),
miniTabPanel("Data", icon = icon("table"),
miniContentPanel(
dataTableOutput("table", height = "100%")
)
)
)
)
# Create the shiny gadget functions
server <- function(input, output) {
shiny_pickup_dropoff_hour <- reactive({
trips_joined_tbl %>%
filter(pickup_nta == input$pickup & dropoff_nta == input$dropoff) %>%
mutate(pickup_hour = hour(pickup_datetime)) %>%
mutate(trip_time = unix_timestamp(dropoff_datetime) - unix_timestamp(pickup_datetime)) %>%
group_by(pickup_hour) %>%
summarize(n = n(),
pickup_latitude = mean(pickup_latitude),
pickup_longitude = mean(pickup_longitude),
dropoff_latitude = mean(dropoff_latitude),
dropoff_longitude = mean(dropoff_longitude),
trip_time_mean = mean(trip_time),
trip_time_p10 = percentile(trip_time, 0.10),
trip_time_p25 = percentile(trip_time, 0.25),
trip_time_p50 = percentile(trip_time, 0.50),
trip_time_p75 = percentile(trip_time, 0.75),
trip_time_p90 = percentile(trip_time, 0.90)) %>%
collect
})
shiny_pickup_dropoff <- reactive({
shiny_pickup_dropoff_hour() %>%
summarize(n = n(),
pickup_latitude = mean(pickup_latitude),
pickup_longitude = mean(pickup_longitude),
dropoff_latitude = mean(dropoff_latitude),
dropoff_longitude = mean(dropoff_longitude))
})
output$tripTimePlot <- renderPlot({
ggplot(shiny_pickup_dropoff_hour(), aes(x = pickup_hour)) +
geom_line(aes(y = trip_time_p50 / 60, alpha = "Median")) +
geom_ribbon(aes(ymin = trip_time_p25 / 60,
ymax = trip_time_p75 / 60,
alpha = "25–75th percentile")) +
geom_ribbon(aes(ymin = trip_time_p10 / 60,
ymax = trip_time_p90 / 60,
alpha = "10–90th percentile")) +
scale_y_continuous("trip duration in minutes") +
ggtitle("Trip time in minutes")
})
output$tripLeaflet <- renderLeaflet({
leaflet(shiny_pickup_dropoff()) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(~pickup_longitude, ~pickup_latitude, fill = FALSE, color = "green") %>%
addCircleMarkers(~dropoff_longitude, ~dropoff_latitude, stroke = FALSE, color = "red")
})
output$table <- renderDataTable({
shiny_pickup_dropoff_hour() %>%
mutate(trip_time_mean = round(trip_time_mean / 60)) %>%
mutate(trip_time_p50 = round(trip_time_p50 / 60)) %>%
select(pickup_hour, n, trip_time_mean, trip_time_p50)
})
observeEvent(input$done, {
stopApp(TRUE)
})
}
# Run the gadget
runGadget(ui, server)
[1] TRUE
LS0tCnRpdGxlOiAiQW5hbHl6aW5nIGEgYmlsbGlvbiBOWUMgdGF4aSB0cmlwcyBpbiBTcGFyayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGV2YWw9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9Cmluc3RhbGwucGFja2FnZXMoJ21pbmlVSScpCmluc3RhbGwucGFja2FnZXMoJ0RUJykKYGBgCgojIEFjY2VzcyB5b3VyIGRhdGEKCldlIGFuYWx5emUgdGhlIGZ1bGwgdGF4aSBkYXRhIGFzIGRlc2NyaWJlZCBieSBbVG9kZCBTY2huZWlkZXJdKGh0dHA6Ly90b2Rkd3NjaG5laWRlci5jb20vcG9zdHMvYW5hbHl6aW5nLTEtMS1iaWxsaW9uLW55Yy10YXhpLWFuZC11YmVyLXRyaXBzLXdpdGgtYS12ZW5nZWFuY2UvKSBpbiB1c2luZyBSIGFuZCBzcGFya2x5ci4gV2UgbG9hZCB0aGUgYmlsbGlvbiByZWNvcmQgdHJpcHMgdGFibGUgaW50byBBcGFjaGUgU3BhcmsgYW5kIHRoZW4gdXNlIHNwYXJrbHlyIGFuZCBkcGx5ciB0byBtYW5pcHVsYXRlIHRoZSBkYXRhIGFuZCBydW4gbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGF0IHNjYWxlLgoKVGhlIGRhdGEgcmVwcmVzZW50IDIwMCBHQiBvZiB1bmNvbXByZXNzZWQgZGF0YSBpbiBDU1YgZm9ybWF0LiBXaGVuIGNvbnZlcnRlZCBhbmQgY29tcHJlc3NlZCBpbiB0aGUgcGFycXVldCBmb3JtYXQsIHRoZSBkYXRhIGFyZSA3MCBHQi4gVGhlIGRhdGEgYXJlIHN0b3JlZCBpbiBIREZTIGFuZCBwcmUtbG9hZGVkIGluIGEgSGl2ZSB0YWJsZS4KClRoZSBIYWRvb3AgY2x1c3RlciBydW5zIG9uIEVsYXN0aWMgTWFwIFJlZHVjZSAoRU1SKSBpbiBBV1MgYW5kIGhhcyAxMiB3b3JrZXIgbm9kZXMgYW5kIG9uZSBtYXN0ZXIgbm9kZS4gVGhlIG1hc3RlciBub2RlIGhhcyBSLCBSU3R1ZGlvIFNlcnZlciBQcm8sIGFuZCBzcGFya2x5ciBsb2FkZWQgb250byBpdC4KCiMjIENvbm5lY3QgdG8gc3BhcmsKClVzZSBzcGFya2x5ciB0byBjcmVhdGUgYSBuZXcgY29ubmVjdGlvbiB0byBBcGFjaGUgU3BhcmsuIAoKYGBge3IgY29ubmVjdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBMb2FkIGxpYnJhcmllcwpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShnZW9zcGhlcmUpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoc3BhcmtseXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkobWluaVVJKQpsaWJyYXJ5KERUKQoKIyBDb25maWd1cmUgY2x1c3RlcgpTeXMuc2V0ZW52KFNQQVJLX0hPTUU9Ii91c3IvbGliL3NwYXJrIikKY29uZmlnIDwtIHNwYXJrX2NvbmZpZygpCmNvbmZpZyRzcGFyay5kcml2ZXIuY29yZXMJPC0gMzIKY29uZmlnJHNwYXJrLmV4ZWN1dG9yLmNvcmVzCTwtIDMyCmNvbmZpZyRzcGFyay5leGVjdXRvci5tZW1vcnkgPC0gIjQwZyIKCiMgQ29ubmVjdCB0byBjbHVzdGVyCnNjIDwtIHNwYXJrX2Nvbm5lY3QobWFzdGVyID0gInlhcm4tY2xpZW50IiwgY29uZmlnID0gY29uZmlnLCB2ZXJzaW9uID0gJzEuNi4xJykKYGBgCgojIyBQcmVwYXJlIHlvdXIgZGF0YQoKYGBge3IgZGF0YX0KIyBDcmVhdGUgdGFibGUgcmVmZXJlbmNlcwp0cmlwc190YmwgPC0gdGJsKHNjLCAidHJpcHNfcGFyIikKbnljdDIwMTBfdGJsIDwtIHRibChzYywgIm55Y3QyMDEwIikKCiMgSm9pbiB0YWJsZXMKdHJpcHNfam9pbmVkX3RibCA8LSB0cmlwc190YmwgJT4lCiAgZmlsdGVyKCFpcy5uYShwaWNrdXBfbnljdDIwMTBfZ2lkKSAmICFpcy5uYShkcm9wb2ZmX255Y3QyMDEwX2dpZCkpICU+JQogIHNlbGVjdChwaWNrdXBfZGF0ZXRpbWUsIGRyb3BvZmZfZGF0ZXRpbWUsCiAgICAgICAgIHBpY2t1cF9sYXRpdHVkZSwgZHJvcG9mZl9sYXRpdHVkZSwgCiAgICAgICAgIHBpY2t1cF9sb25naXR1ZGUsIGRyb3BvZmZfbG9uZ2l0dWRlLAogICAgICAgICBwaWNrdXBfbnljdDIwMTBfZ2lkLCBkcm9wb2ZmX255Y3QyMDEwX2dpZCwKICAgICAgICAgdGlwX2Ftb3VudCwgZmFyZV9hbW91bnQsIHZlbmRvcl9pZCwgcGFzc2VuZ2VyX2NvdW50LCB0cmlwX2Rpc3RhbmNlKSAlPiUKICBsZWZ0X2pvaW4oCiAgICBzZWxlY3QobnljdDIwMTBfdGJsLCBwaWNrdXBfZ2lkID0gZ2lkLCBwaWNrdXBfYm9ybyA9IGJvcm9uYW1lLCBwaWNrdXBfbnRhID0gbnRhbmFtZSksIAogICAgYnkgPSBjKCJwaWNrdXBfbnljdDIwMTBfZ2lkIiA9ICJwaWNrdXBfZ2lkIikpICU+JQogIGxlZnRfam9pbigKICAgIHNlbGVjdChueWN0MjAxMF90YmwsIGRyb3BvZmZfZ2lkID0gZ2lkLCBkcm9wb2ZmX2Jvcm8gPSBib3JvbmFtZSwgZHJvcG9mZl9udGEgPSBudGFuYW1lKSwgCiAgICBieSA9IGMoImRyb3BvZmZfbnljdDIwMTBfZ2lkIiA9ICJkcm9wb2ZmX2dpZCIpKSAlPiUKICBzZGZfcmVnaXN0ZXIoInRyaXBzX3Bhcl9qb2luZWQiKQoKIyBDYWNoZSB0YWJsZQp0YmxfY2FjaGUoc2MsICJ0cmlwc19wYXJfam9pbmVkIikKYGBgCgojIFVuZGVyc3RhbmQgeW91ciBkYXRhCgojIyBTcGFyayBTUUwgYW5kIGRwbHlyCgpVc2UgZHBseXIgc3ludGF4IHRvIHdyaXRlIFNwYXJrIFNRTC4gV2hlbiB5b3UncmUgcmVhZHkgdG8gdmlzdWFsaXplIHlvdXIgZGF0YSwgdXNlIGBjb2xsZWN0YCB0byBicmluZyBkYXRhIGludG8gUiBtZW1vcnkuIE5vdGljZSB0aGUgZGF0YSBkZXNjcmliZSByb3VnaGx5IDE3MCBtaWxsaW9uIHRyaXBzIHBlciB5ZWFyLgoKYGBge3IgY291bnRzfQojIENhbGN1bGF0ZSB0b3RhbCB0cmlwcwp0cmlwc19qb2luZWRfdGJsICU+JSBjb3VudAoKIyBDYWxjdWxhdGUgdHJpcHMgYnkgeWVhcgp0cmlwX2J5X3llYXIgPC0gdHJpcHNfam9pbmVkX3RibCAlPiUKICBtdXRhdGUoeWVhciA9IHllYXIocGlja3VwX2RhdGV0aW1lKSkgJT4lCiAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgc3VtbWFyaXplKG4gPSBuKCkpICU+JQogIGNvbGxlY3QoKQoKIyBQbG90IHRyaXBzIGJ5IHllYXIKZ2dwbG90KHRyaXBfYnlfeWVhciwgYWVzKHllYXIsIG4pKSArIAogIGdlb21fYmFyKHN0YXQ9IklkZW50aXR5IikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArCiAgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgdHJpcHMgYnkgeWVhciIsIHggPSAiWWVhciIsIHkgPSAiIikKYGBgCgpZb3UgY2FuIG1lYXN1cmUgdGhlIHRpbWUgYmV0d2VlbiBwaWNrdXAgYW5kIGRyb3BvZmYgZm9yIGFueSB0d28gbG9jYXRpb25zLiBJbiB0aGlzIGV4YW1wbGUsIHdlIG1lYXN1cmUgdGhlIHRpbWUgYmV0d2VlbiBKRksgYW5kIG1pZC10b3duLiBOb3RpY2UgdGhlIGxvbmdlc3QgdHJpcCB0aW1lcyBvY2N1ciBhcm91bmQgNCBQTS4KCmBgYHtyIGhvdXJseX0KIyBDYWxjbHVhdGUgdHJpcCB0aW1lIGZvciBhIHNwZWNpZmljIHBpY2t1cCBhbmQgZHJvcG9mZgpwaWNrdXBfZHJvcG9mZl90YmwgPC0gdHJpcHNfam9pbmVkX3RibCAlPiUKICBmaWx0ZXIocGlja3VwX255Y3QyMDEwX2dpZCA9PSAxMjUwICYgZHJvcG9mZl9ueWN0MjAxMF9naWQgPT0gMjA1NikgJT4lCiAgbXV0YXRlKHBpY2t1cF9ob3VyID0gaG91cihwaWNrdXBfZGF0ZXRpbWUpKSAlPiUKICBtdXRhdGUodHJpcF90aW1lID0gdW5peF90aW1lc3RhbXAoZHJvcG9mZl9kYXRldGltZSkgLSB1bml4X3RpbWVzdGFtcChwaWNrdXBfZGF0ZXRpbWUpKSAlPiUKICBncm91cF9ieShwaWNrdXBfaG91cikgJT4lIAogIHN1bW1hcml6ZShuID0gbigpLAogICAgICAgICAgICB0cmlwX3RpbWVfbWVhbiA9IG1lYW4odHJpcF90aW1lKSwKICAgICAgICAgICAgdHJpcF90aW1lX3AxMCA9IHBlcmNlbnRpbGUodHJpcF90aW1lLCAwLjEwKSwKICAgICAgICAgICAgdHJpcF90aW1lX3AyNSA9IHBlcmNlbnRpbGUodHJpcF90aW1lLCAwLjI1KSwKICAgICAgICAgICAgdHJpcF90aW1lX3A1MCA9IHBlcmNlbnRpbGUodHJpcF90aW1lLCAwLjUwKSwKICAgICAgICAgICAgdHJpcF90aW1lX3A3NSA9IHBlcmNlbnRpbGUodHJpcF90aW1lLCAwLjc1KSwKICAgICAgICAgICAgdHJpcF90aW1lX3A5MCA9IHBlcmNlbnRpbGUodHJpcF90aW1lLCAwLjkwKSkKCiMgQ29sbGVjdCByZXN1bHRzCnBpY2t1cF9kcm9wb2ZmIDwtIGNvbGxlY3QocGlja3VwX2Ryb3BvZmZfdGJsKQoKIyBQbG90CmdncGxvdChwaWNrdXBfZHJvcG9mZiwgYWVzKHggPSBwaWNrdXBfaG91cikpICsKICAgICAgICAgIGdlb21fbGluZShhZXMoeSA9IHRyaXBfdGltZV9wNTAsIGFscGhhID0gIk1lZGlhbiIpKSArCiAgICAgICAgICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IHRyaXBfdGltZV9wMjUsIHltYXggPSB0cmlwX3RpbWVfcDc1LCAKICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9ICIyNeKAkzc1dGggcGVyY2VudGlsZSIpKSArCiAgICAgICAgICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IHRyaXBfdGltZV9wMTAsIHltYXggPSB0cmlwX3RpbWVfcDkwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9ICIxMOKAkzkwdGggcGVyY2VudGlsZSIpKSArCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMoInRyaXAgZHVyYXRpb24gaW4gbWludXRlcyIpCgpgYGAKCiMjIFNwYXJrIE1MCgpXaXRoIFNwYXJrIE1MIHlvdSBjYW4gcnVuIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBhZ2FpbnN0IGFsbCB5b3VyIGRhdGEgaW4gU3BhcmsuIEhlcmUgd2UgYXR0ZW1wdCB0byB1bmRlcnN0YW5kIHdoYXQgZmFjdG9ycyBpbmZsdWVuY2UgdGlwIGFtb3VudHMuCgpgYGB7ciBtb2RlbH0KIyBTZWxlY3QgYSBtb2RlbCBkYXRhIHNldAptb2RlbF90YmwgPC0gdHJpcHNfam9pbmVkX3RibCAlPiUKICBmaWx0ZXIocGlja3VwX255Y3QyMDEwX2dpZCA9PSAxMjUwICYgZHJvcG9mZl9ueWN0MjAxMF9naWQgPT0gMjA1NikgJT4lCiAgbXV0YXRlKHBpY2t1cF9ob3VyID0gaG91cihwaWNrdXBfZGF0ZXRpbWUpKSAlPiUKICBtdXRhdGUocGlja3VwX3dlZWsgPSB3ZWVrb2Z5ZWFyKHBpY2t1cF9kYXRldGltZSkpICU+JQogIG11dGF0ZShwaWNrdXBfeWVhciA9IHllYXIocGlja3VwX2RhdGV0aW1lKSkgJT4lCiAgbXV0YXRlKHRyaXBfdGltZSA9IHVuaXhfdGltZXN0YW1wKGRyb3BvZmZfZGF0ZXRpbWUpIC0gdW5peF90aW1lc3RhbXAocGlja3VwX2RhdGV0aW1lKSkgJT4lCiAgZmlsdGVyKCFpcy5uYShwaWNrdXBfbnljdDIwMTBfZ2lkKSAmICFpcy5uYShkcm9wb2ZmX255Y3QyMDEwX2dpZCkpICU+JQogIGZpbHRlcighaXMubmEodGlwX2Ftb3VudCkpICU+JQogIGZpbHRlcighaXMubmEoZmFyZV9hbW91bnQpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKHZlbmRvcl9pZCkpICU+JQogIGZpbHRlcighaXMubmEocGlja3VwX2hvdXIpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKHBpY2t1cF93ZWVrKSkgJT4lCiAgZmlsdGVyKCFpcy5uYShwYXNzZW5nZXJfY291bnQpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKHRyaXBfdGltZSkpICU+JQogIGZpbHRlcighaXMubmEodHJpcF9kaXN0YW5jZSkpCgojIFBhcnRpdGlvaW4gaW50byB0cmFpbiBhbmQgdmFsaWRhdGUKbW9kZWxfcGFydGl0aW9uX3RibCA8LSBtb2RlbF90YmwgJT4lCiAgc2RmX3BhcnRpdGlvbih0cmFpbiA9IDAuOCwgdGVzdCA9IDAuMiwgc2VlZCA9IDEyMzQpCgojIENyZWF0ZSB0YWJsZSByZWZlcmVuY2VzCnRyaXBzX3RyYWluX3RibCA8LSBzZGZfcmVnaXN0ZXIobW9kZWxfcGFydGl0aW9uX3RibCR0cmFpbiwgInRyaXBzX3RyYWluIikKdHJpcHNfdGVzdF90YmwgPC0gc2RmX3JlZ2lzdGVyKG1vZGVsX3BhcnRpdGlvbl90YmwkdHJhaW4sICJ0cmlwc190ZXN0IikKCiMgQ2FjaGUgbW9kZWwgZGF0YQp0YmxfY2FjaGUoc2MsICJ0cmlwc190cmFpbiIpCgojIE1vZGVsIGRhdGEKbW9kZWxfZm9ybXVsYSA8LSBmb3JtdWxhKHRpcF9hbW91bnQgfiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFyZV9hbW91bnQgKyB2ZW5kb3JfaWQgKyBwaWNrdXBfaG91ciArIHBpY2t1cF93ZWVrICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3Nlbmdlcl9jb3VudCArIHRyaXBfdGltZSArIHRyaXBfZGlzdGFuY2UpCm0xIDwtIG1sX2xpbmVhcl9yZWdyZXNzaW9uKHRyaXBzX3RyYWluX3RibCwgbW9kZWxfZm9ybXVsYSkKc3VtbWFyeShtMSkKYGBgCgojIyBIVE1MIFdpZGdldHMKClNwYXJrIG1ha2VzIGl0IGVhc3kgdG8gZHJpbGwgZG93biB0byBhbnkgbGV2ZWwgb2YgeW91IGRhdGEuIFlvdSBjYW4gdmlzdWFsaXplIHRoZSB0b3AgZHJvcG9mZnMgZm9yIGFueSBwaWNrdXAgbG9jYXRpb24gYnkgZW50ZXJpbmcgeW91ciBwaWNrdXAgbG9jYXRpb24uIEluIHRoaXMgZXhhbXBsZSB0aGUgcGlja3VwIGxvY2F0aW9uIGlzIEpGSyBJbnRlcm5hdGlvbmFsIEFpcnBvcnQuCgpgYGB7ciB0b3Bkcm9wb2Zmc30KIyBTdW1tYXJpemUgdHJpcHMgZnJvbSBKRksgQWlycG9ydApqZmtfcGlja3VwX3RibCA8LSB0cmlwc19qb2luZWRfdGJsICU+JQogIGZpbHRlcihwaWNrdXBfbnljdDIwMTBfZ2lkID09IDIwNTYpICU+JQogIGZpbHRlcighaXMubmEoZHJvcG9mZl9ueWN0MjAxMF9naWQpKSAlPiUKICBtdXRhdGUodHJpcF90aW1lID0gdW5peF90aW1lc3RhbXAoZHJvcG9mZl9kYXRldGltZSkgLSB1bml4X3RpbWVzdGFtcChwaWNrdXBfZGF0ZXRpbWUpKSAlPiUKICBncm91cF9ieShkcm9wb2ZmX255Y3QyMDEwX2dpZCkgJT4lIAogIHN1bW1hcml6ZShuID0gbigpLAogICAgICAgICAgICB0cmlwX3RpbWVfbWVhbiA9IG1lYW4odHJpcF90aW1lKSwKICAgICAgICAgICAgdHJpcF9kaXN0X21lYW4gPSBtZWFuKHRyaXBfZGlzdGFuY2UpLAogICAgICAgICAgICBkcm9wb2ZmX2xhdGl0dWRlID0gbWVhbihkcm9wb2ZmX2xhdGl0dWRlKSwKICAgICAgICAgICAgZHJvcG9mZl9sb25naXR1ZGUgPSBtZWFuKGRyb3BvZmZfbG9uZ2l0dWRlKSwKICAgICAgICAgICAgcGFzc2VuZ2VyX21lYW4gPSBtZWFuKHBhc3Nlbmdlcl9jb3VudCksCiAgICAgICAgICAgIGZhcmVfYW1vdW50ID0gbWVhbihmYXJlX2Ftb3VudCksCiAgICAgICAgICAgIHRpcF9hbW91bnQgPSBtZWFuKHRpcF9hbW91bnQpKQoKIyBDb2xsZWN0IHRvcCByZXN1bHRzCmpma19waWNrdXAgPC0gamZrX3BpY2t1cF90YmwgJT4lCiAgbXV0YXRlKG5fcmFuayA9IG1pbl9yYW5rKGRlc2MobikpKSAlPiUKICBmaWx0ZXIobl9yYW5rIDw9IDI1KSAlPiUKICBjb2xsZWN0CgojIFBsb3QgdG9wIHRyaXBzIG9uIG1hcApsZWFmbGV0KGpma19waWNrdXApICU+JSAKICBzZXRWaWV3KGxuZyA9IC03My45LCBsYXQgPSA0MC43LCB6b29tID0gMTEpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuUG9zaXRyb24iKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKH5kcm9wb2ZmX2xvbmdpdHVkZSwgfmRyb3BvZmZfbGF0aXR1ZGUsIHN0cm9rZSA9IEYsIGNvbG9yID0gInJlZCIpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoLTczLjc3ODEsIDQwLjY0MTMsIGZpbGwgPSBGQUxTRSwgY29sb3IgPSAiZ3JlZW4iKQpgYGAKCiMjIFNoaW55IEdhZGdldAoKSXQgaXMgb2Z0ZW4gdXNlZnVsIHRvIHZpc3VhbGl6ZSB0aGUgYW5hbHlzZXMgaW50ZXJhY3RpdmVseS4gVXNlIGEgU2hpbnkgZ2FkZ2V0IHRvIGV4cGxvcmUgb3RoZXIgcGlja3VwIGFuZCBkcm9wb2ZmIGxvY2F0aW9ucy4KCmBgYHtyIGdhZGdldCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBDcmVhdGUgbGlzdCBvZiBnZW8gZ3JvdXBzIHRvIHNlbGVjdCBmcm9tCmdlb19ncm91cCA8LSB0cmlwc19qb2luZWRfdGJsICU+JSAKICBkaXN0aW5jdChwaWNrdXBfbnRhKSAlPiUKICBhcnJhbmdlKHBpY2t1cF9udGEpICU+JQogIGNvbGxlY3QKCiMgQ3JlYXRlIHRoZSBnYWRnZXQgdXNlciBpbnRlcmZhY2UKdWkgPC0gbWluaVBhZ2UoCiAgZ2FkZ2V0VGl0bGVCYXIoIk5ZQyBUYXhpIFRyaXBzIiksCiAgbWluaVRhYnN0cmlwUGFuZWwoCiAgICBtaW5pVGFiUGFuZWwoIklucHV0cyIsIGljb24gPSBpY29uKCJzbGlkZXJzIiksCiAgICAgICAgICAgICAgICAgbWluaUNvbnRlbnRQYW5lbCgKICAgICAgICAgICAgICAgICAgIHNlbGVjdElucHV0KCJwaWNrdXAiLCAgIlRheGkgUGlja3VwIiwgZ2VvX2dyb3VwLCAiTGluY29sbiBTcXVhcmUiKSwKICAgICAgICAgICAgICAgICAgIHNlbGVjdElucHV0KCJkcm9wb2ZmIiwgICJUYXhpIERyb3BvZmYiLCBnZW9fZ3JvdXAsICJVcHBlciBXZXN0IFNpZGUiKQogICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICksCiAgICBtaW5pVGFiUGFuZWwoIlBsb3QiLCBpY29uID0gaWNvbigiYXJlYS1jaGFydCIpLAogICAgICAgICAgICAgICAgIG1pbmlDb250ZW50UGFuZWwoCiAgICAgICAgICAgICAgICAgICBwbG90T3V0cHV0KCJ0cmlwVGltZVBsb3QiKQogICAgICAgICAgICAgICAgICAgKQogICAgKSwKICAgIG1pbmlUYWJQYW5lbCgiTWFwIiwgaWNvbiA9IGljb24oIm1hcC1vIiksCiAgICAgICAgICAgICAgICAgbWluaUNvbnRlbnRQYW5lbCgKICAgICAgICAgICAgICAgICAgIGxlYWZsZXRPdXRwdXQoInRyaXBMZWFmbGV0IikKICAgICAgICAgICAgICAgICApCiAgICApLAogICAgbWluaVRhYlBhbmVsKCJEYXRhIiwgaWNvbiA9IGljb24oInRhYmxlIiksCiAgICAgICAgICAgICAgICAgbWluaUNvbnRlbnRQYW5lbCgKICAgICAgICAgICAgICAgICAgIGRhdGFUYWJsZU91dHB1dCgidGFibGUiLCBoZWlnaHQgPSAiMTAwJSIpCiAgICAgICAgICAgICAgICAgKQogICAgKQogICkKKQoKIyBDcmVhdGUgdGhlIHNoaW55IGdhZGdldCBmdW5jdGlvbnMKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKCiAgc2hpbnlfcGlja3VwX2Ryb3BvZmZfaG91ciA8LSByZWFjdGl2ZSh7CiAgICB0cmlwc19qb2luZWRfdGJsICU+JQogICAgZmlsdGVyKHBpY2t1cF9udGEgPT0gaW5wdXQkcGlja3VwICYgZHJvcG9mZl9udGEgPT0gaW5wdXQkZHJvcG9mZikgJT4lCiAgICBtdXRhdGUocGlja3VwX2hvdXIgPSBob3VyKHBpY2t1cF9kYXRldGltZSkpICU+JQogICAgbXV0YXRlKHRyaXBfdGltZSA9IHVuaXhfdGltZXN0YW1wKGRyb3BvZmZfZGF0ZXRpbWUpIC0gdW5peF90aW1lc3RhbXAocGlja3VwX2RhdGV0aW1lKSkgJT4lCiAgICBncm91cF9ieShwaWNrdXBfaG91cikgJT4lIAogICAgc3VtbWFyaXplKG4gPSBuKCksCiAgICAgICAgICAgICAgcGlja3VwX2xhdGl0dWRlID0gbWVhbihwaWNrdXBfbGF0aXR1ZGUpLAogICAgICAgICAgICAgIHBpY2t1cF9sb25naXR1ZGUgPSBtZWFuKHBpY2t1cF9sb25naXR1ZGUpLAogICAgICAgICAgICAgIGRyb3BvZmZfbGF0aXR1ZGUgPSBtZWFuKGRyb3BvZmZfbGF0aXR1ZGUpLAogICAgICAgICAgICAgIGRyb3BvZmZfbG9uZ2l0dWRlID0gbWVhbihkcm9wb2ZmX2xvbmdpdHVkZSksCiAgICAgICAgICAgICAgdHJpcF90aW1lX21lYW4gPSBtZWFuKHRyaXBfdGltZSksCiAgICAgICAgICAgICAgdHJpcF90aW1lX3AxMCA9IHBlcmNlbnRpbGUodHJpcF90aW1lLCAwLjEwKSwKICAgICAgICAgICAgICB0cmlwX3RpbWVfcDI1ID0gcGVyY2VudGlsZSh0cmlwX3RpbWUsIDAuMjUpLAogICAgICAgICAgICAgIHRyaXBfdGltZV9wNTAgPSBwZXJjZW50aWxlKHRyaXBfdGltZSwgMC41MCksCiAgICAgICAgICAgICAgdHJpcF90aW1lX3A3NSA9IHBlcmNlbnRpbGUodHJpcF90aW1lLCAwLjc1KSwKICAgICAgICAgICAgICB0cmlwX3RpbWVfcDkwID0gcGVyY2VudGlsZSh0cmlwX3RpbWUsIDAuOTApKSAlPiUKICAgIGNvbGxlY3QKICB9KQoKICBzaGlueV9waWNrdXBfZHJvcG9mZiA8LSByZWFjdGl2ZSh7CiAgICBzaGlueV9waWNrdXBfZHJvcG9mZl9ob3VyKCkgJT4lCiAgICAgIHN1bW1hcml6ZShuID0gbigpLAogICAgICAgICAgICAgICAgcGlja3VwX2xhdGl0dWRlID0gbWVhbihwaWNrdXBfbGF0aXR1ZGUpLAogICAgICAgICAgICAgICAgcGlja3VwX2xvbmdpdHVkZSA9IG1lYW4ocGlja3VwX2xvbmdpdHVkZSksCiAgICAgICAgICAgICAgICBkcm9wb2ZmX2xhdGl0dWRlID0gbWVhbihkcm9wb2ZmX2xhdGl0dWRlKSwKICAgICAgICAgICAgICAgIGRyb3BvZmZfbG9uZ2l0dWRlID0gbWVhbihkcm9wb2ZmX2xvbmdpdHVkZSkpCiAgfSkKICAKICBvdXRwdXQkdHJpcFRpbWVQbG90IDwtIHJlbmRlclBsb3QoewogICAgZ2dwbG90KHNoaW55X3BpY2t1cF9kcm9wb2ZmX2hvdXIoKSwgYWVzKHggPSBwaWNrdXBfaG91cikpICsKICAgIGdlb21fbGluZShhZXMoeSA9IHRyaXBfdGltZV9wNTAgLyA2MCwgYWxwaGEgPSAiTWVkaWFuIikpICsKICAgIGdlb21fcmliYm9uKGFlcyh5bWluID0gdHJpcF90aW1lX3AyNSAvIDYwLCAKICAgICAgICAgICAgICAgICAgICB5bWF4ID0gdHJpcF90aW1lX3A3NSAvIDYwLCAKICAgICAgICAgICAgICAgICAgICBhbHBoYSA9ICIyNeKAkzc1dGggcGVyY2VudGlsZSIpKSArCiAgICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IHRyaXBfdGltZV9wMTAgLyA2MCwgCiAgICAgICAgICAgICAgICAgICAgeW1heCA9IHRyaXBfdGltZV9wOTAgLyA2MCwgCiAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAiMTDigJM5MHRoIHBlcmNlbnRpbGUiKSkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKCJ0cmlwIGR1cmF0aW9uIGluIG1pbnV0ZXMiKSArIAogICAgZ2d0aXRsZSgiVHJpcCB0aW1lIGluIG1pbnV0ZXMiKQogICB9KQoKICBvdXRwdXQkdHJpcExlYWZsZXQgPC0gcmVuZGVyTGVhZmxldCh7ICAKICAgICAgbGVhZmxldChzaGlueV9waWNrdXBfZHJvcG9mZigpKSAlPiUgCiAgICAgIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuUG9zaXRyb24iKSAlPiUKICAgICAgYWRkQ2lyY2xlTWFya2Vycyh+cGlja3VwX2xvbmdpdHVkZSwgfnBpY2t1cF9sYXRpdHVkZSwgZmlsbCA9IEZBTFNFLCBjb2xvciA9ICJncmVlbiIpICU+JQogICAgICBhZGRDaXJjbGVNYXJrZXJzKH5kcm9wb2ZmX2xvbmdpdHVkZSwgfmRyb3BvZmZfbGF0aXR1ZGUsIHN0cm9rZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKQogIH0pCiAgCiAgb3V0cHV0JHRhYmxlIDwtIHJlbmRlckRhdGFUYWJsZSh7CiAgICBzaGlueV9waWNrdXBfZHJvcG9mZl9ob3VyKCkgJT4lCiAgICAgIG11dGF0ZSh0cmlwX3RpbWVfbWVhbiA9IHJvdW5kKHRyaXBfdGltZV9tZWFuIC8gNjApKSAlPiUKICAgICAgbXV0YXRlKHRyaXBfdGltZV9wNTAgPSByb3VuZCh0cmlwX3RpbWVfcDUwIC8gNjApKSAlPiUKICAgICAgc2VsZWN0KHBpY2t1cF9ob3VyLCBuLCB0cmlwX3RpbWVfbWVhbiwgdHJpcF90aW1lX3A1MCkKICB9KQogIAogIG9ic2VydmVFdmVudChpbnB1dCRkb25lLCB7CiAgICBzdG9wQXBwKFRSVUUpCiAgfSkKCn0KCiMgUnVuIHRoZSBnYWRnZXQKcnVuR2FkZ2V0KHVpLCBzZXJ2ZXIpCmBgYAoKIyBDb21tdW5pY2F0ZSB5b3VyIGluc2lnaHRzCgpXZSBlc3RpbWF0ZWQgdGF4aSB0cmlwIHRpbWVzIGJ5IHRpbWUgb2YgZGF5IGZvciBtdWx0aXBsZSBwaWNrdXAgYW5kIGRyb3BvZmYgbG9jYXRpb25zLiBXZSBmb3VuZCB0aGUga2V5IGRyaXZlcnMgZm9yIHByZWRpY3RpbmcgdGlwIGFtb3VudHMgZm9yIHNwZWNpZmljIHBpY2t1cCBhbmQgZHJvcG9mZiBsb2NhdGlvbnMuIFdlIGFsc28gZm91bmQgdGhlIHRvcCBkcm9wb2ZmIGxvY2F0aW9ucyBmb3Igc3BlY2lmaWMgcGlja3VwIGxvY2F0aW9ucy4gRmluYWxseSwgd2UgcHVibGlzaGVkIHRoZXNlIHJlc3VsdHMgaW4gYW4gUiBtYXJrZG93biBub3RlYm9vay4KCg==