A couple of days ago, I read about this news that said Cicago motorists will get a longer grace period before getting hit with a $100 red light camera ticket. Chicago extend the “enforcement threshold” when cars are given tickets from 0.1 seconds to 0.3 seconds after the light turns red. It’s believed the change will cut the number of tickets issued by about 29 percent and result in an expected revenue loss of $17 million this year.
This is great news, I guess, to many drivers in the city. To people who are interested in data science this is also a great opportunity to explore the impact of a policy change, by studying the public available data. Luckily, city of Chicago publishes Red Light Camera Violations dataset at the City of Chicago Data Portal
In this first part of the series, we will explore the dataset and try to answer the following questions,
- Where are the cameras?
- Which intersections have the most or the fewest violations?
- Can we build a model to predict the number of violations?
In the next post, we will use an updated dataset after the new policy kicks in for a few weeks along with our model to examine if the new policy brings down ticket issuance.
The whole analysis will be done in R notebook. We start by loading all necessary packages.
library(plyr)
library(dplyr)
library(tibble)
library(ggplot2)
library(ggmap)
library(ggthemes)
library(viridis)
library(lubridate)
library(forecast)
library(xts)
library(prophet)
The dataset was downloaded as of March 22. We read in the data saved as a CSV file and show the last six rows of the file. The rows are sorted by violaion date. We can see that the latest violation records are from 03/08/2017. Remember that the new rule does not take effect until March 20. So the data in the file records those violations 0.1 second from the light turns red. Another quick look at the summary reveals that there’re NAs in the camera ID column, coordinates columns and locations columns. The City of Chicago publishes the list of current Red Light Camera intersections. By looking at the list we know that at each section, usually there are multiple cameras, facing different directions. We take a simplified approach here and aggregate the violations of such cameras at the same intersections. We care about the violations at the intersection level and won’t distinguish if east-west traffic or north-south traffic is more violation-prone.
rlcv <- read.csv("Red_Light_Camera_Violations.csv") %>% tbl_df()
rlcv %>% tail() %>% print()
rlcv %>% summary()
INTERSECTION CAMERA.ID
CALIFORNIA AND DIVERSEY: 2836 Min. :1002
KOSTNER AND NORTH : 2491 1st Qu.:1444
BELMONT AND KEDZIE : 2383 Median :1851
STONEY ISLAND AND 79TH : 2119 Mean :1860
WENTWORTH AND GARFIELD : 1947 3rd Qu.:2294
ROOSEVELT AND HALSTED : 1944 Max. :8313
(Other) :251278 NA's :305
ADDRESS VIOLATION.DATE VIOLATIONS
800 W ROOSEVELT ROAD : 1944 09/26/2014: 315 Min. : 1.000
1000 W HOLLYWOOD AVE : 1943 02/27/2015: 314 1st Qu.: 2.000
2400 N ASHLAND AVENUE: 1940 09/13/2014: 313 Median : 4.000
2000 W DIVISION : 1928 08/16/2014: 308 Mean : 5.615
6800 W GRAND AVENUE : 1925 09/17/2014: 308 3rd Qu.: 7.000
3600 N CICERO AVENUE : 1908 09/27/2014: 308 Max. :186.000
(Other) :253410 (Other) :263132
X.COORDINATE Y.COORDINATE LATITUDE LONGITUDE
Min. :1125717 Min. :1825911 Min. :41.68 Min. :-87.81
1st Qu.:1147201 1st Qu.:1869738 1st Qu.:41.80 1st Qu.:-87.73
Median :1158152 Median :1907723 Median :41.90 Median :-87.69
Mean :1157552 Mean :1898415 Mean :41.88 Mean :-87.70
3rd Qu.:1167172 3rd Qu.:1921821 3rd Qu.:41.94 3rd Qu.:-87.66
Max. :1191188 Max. :1947754 Max. :42.01 Max. :-87.58
NA's :15826 NA's :15826 NA's :15826 NA's :15826
LOCATION
: 15826
(41.86726309836901, -87.64697268601648) : 1944
(41.98564979859328, -87.65533580655139) : 1943
(41.92519388981048, -87.66829436339881) : 1940
(41.903298792681944, -87.67738324739636): 1928
(41.92370819286044, -87.79530078123082) : 1925
(Other) :239492
As we see below, the missing camera IDs composite a small portion of records in the dataset. And when aggregating the violations at the intersection level, the missing IDs shouldn’t affect our analysis.
rlcv %>% group_by(INTERSECTION, CAMERA.ID) %>%
#select(INTERSECTION, CAMERA.ID, VIOLATION.DATE, VIOLATIONS) %>%
dplyr::summarize(Sum.Of.Violations = sum(VIOLATIONS))
We want to map the red lights onto a Chicago map. Missing location data could be a challenge to us. Let’s see how we can fix that. We notice that every record with missing locations has a valid intersection value. Since we analyze at the intersection level, as long as we have the location for that intersection, we will use that to fill in the blanks. We still have three intersections without location data. We will ignore them in visualization for the moment.
intersection_geo <- rlcv %>% group_by(INTERSECTION) %>%
dplyr::summarize(intersection_lat = round(mean(LATITUDE, na.rm = T), 3),
intersection_lon = round(mean(LONGITUDE, na.rm = T), 3))
intersection_geo
Let’s first see where these cameras are. The network of the red light cameras is pretty much covering the core neighborhoods of Chicago. But there aren’t many cameras in the Loop or South Loop area.
qmplot(intersection_lon, intersection_lat, data = intersection_geo,
zoom = 11, size = I(1.25), color = I("red"),
maptype = "toner-lite",
main = "Intersections with Red Light Cameras") +
theme_minimal() + xlab("") + ylab("")

It would be interesting to see which intersections have seen the most violations since the installation of the cameras. LAKE SHORE DR AND BELMONT has seen the most violations. CICERO AND I55 and VAN BUREN AND WESTERN are not far away.
rlcv %>% group_by(INTERSECTION) %>%
dplyr::summarize(sum_violations = sum(VIOLATIONS),
med_violations = median(VIOLATIONS),
lat2 = round(mean(LATITUDE, na.rm = T), 4),
lon2 = round(mean(LONGITUDE, na.rm = T), 4)) %>%
arrange(-sum_violations) -> intersection_violations
intersection_violations
qmplot(lon2, lat2, data = intersection_violations,
zoom = 11, size = sum_violations,
maptype = "toner-lite", color = sum_violations,
main = "Sum of violations at each intersection") +
viridis::scale_color_viridis(option = "magma", direction = -1) +
theme_minimal() + xlab("") + ylab("") +
guides(color=guide_legend(title = ""),
size = guide_legend(title = ""))

But by looking at the total of the violations since the start of the data may not be an accurate description. It’s possible that some red light cameras are newly installed and hasn’t had accumulated many violations. However these new cameras could send out many tickets on a daily basis. To accomodate that thought, we look at the median of the violations at the intersections, which would give us a view of what a typical day looks like. And we take a snap shot of February 2017 data. This should give us a most recent view of which cameras have seen the most violations in a month.
qmplot(lon2, lat2, data = intersection_violations,
zoom = 11, size = med_violations,
maptype = "toner-lite", color = med_violations,
main = "Median of violations at each intersection") +
viridis::scale_color_viridis(option = "magma", direction = -1) +
theme_minimal() + xlab("") + ylab("") +
guides(color=guide_legend(title = ""),
size = guide_legend(title = ""))

rlcv %>%
dplyr::mutate(VIOLATION.DATE = mdy(VIOLATION.DATE)) %>%
filter(VIOLATION.DATE >= "2017-02-01" &
VIOLATION.DATE < "2017-03-01") -> feb.violation
feb.violation %>% group_by(INTERSECTION) %>%
dplyr::summarize(sum_violations = sum(VIOLATIONS),
n = n(),
lat2 = round(mean(LATITUDE, na.rm = T), 4),
lon2 = round(mean(LONGITUDE, na.rm = T), 4)) -> feb.vio.sum
qmplot(lon2, lat2, data = feb.vio.sum,
zoom = 11, size = sum_violations,
maptype = "toner-lite", color = sum_violations,
main = "Sum of violations at each intersection in February 2017") +
viridis::scale_color_viridis(option = "magma", direction = -1) +
theme_minimal() + xlab("") + ylab("") +
guides(color=guide_legend(title = ""),
size = guide_legend(title = ""))

The charts provides different angles to the number of violations. But it’s very obvious from all three charts that the intersection of CICERO AND I55 is probably the most notorious red camera intersection in the Chicagoland.
Let’s try to build a model to see if we can predict daily violations at the intersection of our interest.
rlcv %>% filter(INTERSECTION == "CICERO AND I55") %>%
dplyr::mutate(VIOLATION.DATE = mdy(VIOLATION.DATE)) %>%
group_by(VIOLATION.DATE) %>%
dplyr::summarize(sum.violations = sum(VIOLATIONS)) %>%
arrange(VIOLATION.DATE) -> cicero.55
rlcv %>% filter(INTERSECTION == "LAKE SHORE DR AND BELMONT") %>%
dplyr::mutate(VIOLATION.DATE = mdy(VIOLATION.DATE)) %>%
group_by(VIOLATION.DATE) %>%
dplyr::summarize(sum.violations = sum(VIOLATIONS)) %>%
arrange(VIOLATION.DATE) -> lakeshore.belmont
cicero.55.xts <- xts(cicero.55[,-1], order.by=cicero.55$VIOLATION.DATE)
xts::plot.xts(cicero.55.xts)

lakeshore.belmont.xts <- xts(lakeshore.belmont[,-1],
order.by=lakeshore.belmont$VIOLATION.DATE)
xts::plot.xts(lakeshore.belmont.xts)

Now we realize the intersection CICERO AND I55 misses data from April to October in 2015. We decide to use data from Nov 2015 to end of 2016 to build a model and test it on 2017 data. There are many time series modeling approach out there. This time we will try a new method, namely prophet, published by Facebook recently.
cicero.55 %>%
filter(VIOLATION.DATE > "2015/11/1",
VIOLATION.DATE < "2017/1/1") %>%
dplyr::rename(y = sum.violations, ds = VIOLATION.DATE) -> cicero.55.data
cicero.55 %>%
filter(VIOLATION.DATE >= "2017/1/1") %>%
dplyr::rename(y = sum.violations, ds = VIOLATION.DATE) -> cicero.55.test
fit.prophet <- prophet(cicero.55.data)
STAN OPTIMIZATION COMMAND (LBFGS)
init = user
save_iterations = 1
init_alpha = 0.001
tol_obj = 1e-12
tol_grad = 1e-08
tol_param = 1e-08
tol_rel_obj = 10000
tol_rel_grad = 1e+07
history_size = 5
seed = 159829875
initial log joint probability = -12.5338
Optimization terminated normally:
Convergence detected: relative gradient magnitude is below tolerance
#summary(fit.prophet)
future <- make_future_dataframe(fit.prophet, periods = 68)
fcst <- predict(fit.prophet, future)
plot(fit.prophet, fcst) +
geom_line(data = cicero.55.data, aes(x = ds, y = y), alpha = 0.25) +
geom_point(data = cicero.55.test, aes(x = ds, y = y), color = "orange") +
xlab("Date")

prophet_plot_components(fit.prophet, fcst)

fcst %>% tbl_df() %>%
filter(ds >= "2017/1/1") %>%
select(ds, yhat) -> fcst.test
inner_join(cicero.55.test, fcst.test, by = "ds") -> cicero.55.test.acc
ggplot(cicero.55.test.acc, aes(x = ds)) +
geom_point(aes(y = y)) +
geom_line(aes(y = yhat), color = "blue") +
xlab("Date") + ylab("Out of sample actuals and predicted")

(cicero.55.test.acc$y - cicero.55.test.acc$yhat) %>%
abs() %>% mean()
[1] 21.23464
We can see that the model recognizes a general trend but fails to capture the volatility of the violations from day to day. The mean absolute error in 2017 is about 21. We try the model on another intersection with a longer violation history.
lakeshore.belmont %>%
filter(VIOLATION.DATE < "2017/1/1") %>%
dplyr::rename(y = sum.violations, ds = VIOLATION.DATE) -> lakeshore.belmont.data
lakeshore.belmont %>%
filter(VIOLATION.DATE >= "2017/1/1") %>%
dplyr::rename(y = sum.violations, ds = VIOLATION.DATE) -> lakeshore.belmont.test
fit.prophet <- prophet(lakeshore.belmont.data)
STAN OPTIMIZATION COMMAND (LBFGS)
init = user
save_iterations = 1
init_alpha = 0.001
tol_obj = 1e-12
tol_grad = 1e-08
tol_param = 1e-08
tol_rel_obj = 10000
tol_rel_grad = 1e+07
history_size = 5
seed = 548628329
initial log joint probability = -27.6485
Optimization terminated normally:
Convergence detected: relative gradient magnitude is below tolerance
future <- make_future_dataframe(fit.prophet, periods = 68)
fcst <- predict(fit.prophet, future)
plot(fit.prophet, fcst) +
geom_line(data = lakeshore.belmont.data, aes(x = ds, y = y), alpha = 0.25) +
geom_point(data = lakeshore.belmont.test, aes(x = ds, y = y), color = "orange") +
xlab("Date") + ylab("Violations")

prophet_plot_components(fit.prophet, fcst)

fcst %>% tbl_df() %>%
filter(ds >= "2017/1/1") %>%
select(ds, yhat) -> fcst.test
inner_join(lakeshore.belmont.test, fcst.test, by = "ds") -> lakeshore.belmont.test.acc
ggplot(lakeshore.belmont.test.acc, aes(x = ds)) +
geom_point(aes(y = y)) +
geom_line(aes(y = yhat), color = "blue") +
xlab("Date") + ylab("Out of sample actuals and predicted")

This time the model seems to be able to fit well in sample. But it shows a upward bias in the 2017 out of sample predictions. There’s no doubt it’s no easy task to accurately predict the violations into the future. We will explore this topic in the next post.
In summary, what we have achieved in this post is to gather the data from City of Chicago data portal and draw the violations on a map. We also entertained the idea of building a predictive model to forecast violations into the future. Hope you find some of the content interesting or useful. Let us know if you have any thoughts.
LS0tCnRpdGxlOiAiRXhwbG9yaW5nIENoaWNhZ28gUmVkIExpZ2h0IENhbWVyYSBWaW9sYXRpb25zIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdGhlbWU6IHJlYWRhYmxlCmF1dGhvcjogWFlaCi0tLQoKQSBjb3VwbGUgb2YgZGF5cyBhZ28sIEkgcmVhZCBhYm91dCBbdGhpcyBuZXdzXShodHRwOi8vd3d3LmNoaWNhZ290cmlidW5lLmNvbS9uZXdzL3dhdGNoZG9nL3JlZGxpZ2h0L2N0LXJlZC1saWdodC1zdHVkeS0wMzIxLTIwMTcwMzIxLXN0b3J5Lmh0bWwpIHRoYXQgc2FpZCBDaWNhZ28gbW90b3Jpc3RzIHdpbGwgZ2V0IGEgbG9uZ2VyIGdyYWNlIHBlcmlvZCBiZWZvcmUgZ2V0dGluZyBoaXQgd2l0aCBhIFwkMTAwIHJlZCBsaWdodCBjYW1lcmEgdGlja2V0LiBDaGljYWdvIGV4dGVuZCB0aGUgImVuZm9yY2VtZW50IHRocmVzaG9sZCIgd2hlbiBjYXJzIGFyZSBnaXZlbiB0aWNrZXRzIGZyb20gMC4xIHNlY29uZHMgdG8gMC4zIHNlY29uZHMgYWZ0ZXIgdGhlIGxpZ2h0IHR1cm5zIHJlZC4gSXQncyBiZWxpZXZlZCB0aGUgY2hhbmdlIHdpbGwgY3V0IHRoZSBudW1iZXIgb2YgdGlja2V0cyBpc3N1ZWQgYnkgYWJvdXQgMjkgcGVyY2VudCBhbmQgcmVzdWx0IGluIGFuIGV4cGVjdGVkIHJldmVudWUgbG9zcyBvZiBcJDE3IG1pbGxpb24gdGhpcyB5ZWFyLgoKVGhpcyBpcyBncmVhdCBuZXdzLCBJIGd1ZXNzLCB0byBtYW55IGRyaXZlcnMgaW4gdGhlIGNpdHkuIFRvIHBlb3BsZSB3aG8gYXJlIGludGVyZXN0ZWQgaW4gZGF0YSBzY2llbmNlIHRoaXMgaXMgYWxzbyBhIGdyZWF0IG9wcG9ydHVuaXR5IHRvIGV4cGxvcmUgdGhlIGltcGFjdCBvZiBhIHBvbGljeSBjaGFuZ2UsIGJ5IHN0dWR5aW5nIHRoZSBwdWJsaWMgYXZhaWxhYmxlIGRhdGEuIEx1Y2tpbHksIGNpdHkgb2YgQ2hpY2FnbyBwdWJsaXNoZXMgKlJlZCBMaWdodCBDYW1lcmEgVmlvbGF0aW9ucyogZGF0YXNldCBhdCB0aGUgW0NpdHkgb2YgQ2hpY2FnbyBEYXRhIFBvcnRhbF0oaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1RyYW5zcG9ydGF0aW9uL1JlZC1MaWdodC1DYW1lcmEtVmlvbGF0aW9ucy9zcHF4LWpzMzcvZGF0YSkKCkluIHRoaXMgZmlyc3QgcGFydCBvZiB0aGUgc2VyaWVzLCB3ZSB3aWxsIGV4cGxvcmUgdGhlIGRhdGFzZXQgYW5kIHRyeSB0byBhbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnMsCgoxLiBXaGVyZSBhcmUgdGhlIGNhbWVyYXM/CjIuIFdoaWNoIGludGVyc2VjdGlvbnMgaGF2ZSB0aGUgbW9zdCBvciB0aGUgZmV3ZXN0IHZpb2xhdGlvbnM/CjMuIENhbiB3ZSBidWlsZCBhIG1vZGVsIHRvIHByZWRpY3QgdGhlIG51bWJlciBvZiB2aW9sYXRpb25zPwoKSW4gdGhlIG5leHQgcG9zdCwgd2Ugd2lsbCB1c2UgYW4gdXBkYXRlZCBkYXRhc2V0IGFmdGVyIHRoZSBuZXcgcG9saWN5IGtpY2tzIGluIGZvciBhIGZldyB3ZWVrcyBhbG9uZyB3aXRoIG91ciBtb2RlbCB0byBleGFtaW5lIGlmIHRoZSBuZXcgcG9saWN5IGJyaW5ncyBkb3duIHRpY2tldCBpc3N1YW5jZS4KClRoZSB3aG9sZSBhbmFseXNpcyB3aWxsIGJlIGRvbmUgaW4gUiBub3RlYm9vay4gV2Ugc3RhcnQgYnkgbG9hZGluZyBhbGwgbmVjZXNzYXJ5IHBhY2thZ2VzLgoKYGBge3IgTG9hZCBQYWNrYWdlcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShwbHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdnbWFwKQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KGZvcmVjYXN0KQpsaWJyYXJ5KHh0cykKbGlicmFyeShwcm9waGV0KQpgYGAKClRoZSBkYXRhc2V0IHdhcyBkb3dubG9hZGVkIGFzIG9mIE1hcmNoIDIyLiBXZSByZWFkIGluIHRoZSBkYXRhIHNhdmVkIGFzIGEgQ1NWIGZpbGUgYW5kIHNob3cgdGhlIGxhc3Qgc2l4IHJvd3Mgb2YgdGhlIGZpbGUuIFRoZSByb3dzIGFyZSBzb3J0ZWQgYnkgdmlvbGFpb24gZGF0ZS4gV2UgY2FuIHNlZSB0aGF0IHRoZSBsYXRlc3QgdmlvbGF0aW9uIHJlY29yZHMgYXJlIGZyb20gMDMvMDgvMjAxNy4gUmVtZW1iZXIgdGhhdCB0aGUgbmV3IHJ1bGUgZG9lcyBub3QgdGFrZSBlZmZlY3QgdW50aWwgTWFyY2ggMjAuIFNvIHRoZSBkYXRhIGluIHRoZSBmaWxlIHJlY29yZHMgdGhvc2UgdmlvbGF0aW9ucyAwLjEgc2Vjb25kIGZyb20gdGhlIGxpZ2h0IHR1cm5zIHJlZC4gQW5vdGhlciBxdWljayBsb29rIGF0IHRoZSBzdW1tYXJ5IHJldmVhbHMgdGhhdCB0aGVyZSdyZSBOQXMgaW4gdGhlIGNhbWVyYSBJRCBjb2x1bW4sIGNvb3JkaW5hdGVzIGNvbHVtbnMgYW5kIGxvY2F0aW9ucyBjb2x1bW5zLiBUaGUgQ2l0eSBvZiBDaGljYWdvIHB1Ymxpc2hlcyB0aGUgW2xpc3Qgb2YgY3VycmVudCBSZWQgTGlnaHQgQ2FtZXJhIGludGVyc2VjdGlvbnNdKGh0dHBzOi8vd3d3LmNpdHlvZmNoaWNhZ28ub3JnL2NvbnRlbnQvZGFtL2NpdHkvZGVwdHMvY2RvdC9SZWQlMjBMaWdodCUyMENhbWVyYXMvQWN0aXZlX1JMQ19JbnRlcnNlY3Rpb25zLnBkZikuIEJ5IGxvb2tpbmcgYXQgdGhlIGxpc3Qgd2Uga25vdyB0aGF0IGF0IGVhY2ggc2VjdGlvbiwgdXN1YWxseSB0aGVyZSBhcmUgbXVsdGlwbGUgY2FtZXJhcywgZmFjaW5nIGRpZmZlcmVudCBkaXJlY3Rpb25zLiBXZSB0YWtlIGEgc2ltcGxpZmllZCBhcHByb2FjaCBoZXJlIGFuZCBhZ2dyZWdhdGUgdGhlIHZpb2xhdGlvbnMgb2Ygc3VjaCBjYW1lcmFzIGF0IHRoZSBzYW1lIGludGVyc2VjdGlvbnMuIFdlIGNhcmUgYWJvdXQgdGhlIHZpb2xhdGlvbnMgYXQgdGhlIGludGVyc2VjdGlvbiBsZXZlbCBhbmQgd29uJ3QgZGlzdGluZ3Vpc2ggaWYgZWFzdC13ZXN0IHRyYWZmaWMgb3Igbm9ydGgtc291dGggdHJhZmZpYyBpcyBtb3JlIHZpb2xhdGlvbi1wcm9uZS4KCmBgYHtyIFJlYWQgdGhlIERhdGFzZXR9CnJsY3YgPC0gcmVhZC5jc3YoIlJlZF9MaWdodF9DYW1lcmFfVmlvbGF0aW9ucy5jc3YiKSAlPiUgdGJsX2RmKCkKYGBgCgpgYGB7ciBTdW1tYXJ5IFN0YXRpc3RpY3N9CnJsY3YgJT4lIHRhaWwoKSAlPiUgcHJpbnQoKQpybGN2ICU+JSBzdW1tYXJ5KCkKYGBgCgpBcyB3ZSBzZWUgYmVsb3csIHRoZSBtaXNzaW5nIGNhbWVyYSBJRHMgY29tcG9zaXRlIGEgc21hbGwgcG9ydGlvbiBvZiByZWNvcmRzIGluIHRoZSBkYXRhc2V0LiBBbmQgd2hlbiBhZ2dyZWdhdGluZyB0aGUgdmlvbGF0aW9ucyBhdCB0aGUgaW50ZXJzZWN0aW9uIGxldmVsLCB0aGUgbWlzc2luZyBJRHMgc2hvdWxkbid0IGFmZmVjdCBvdXIgYW5hbHlzaXMuCgpgYGB7ciBNaXNzaW5nIElEfQpybGN2ICU+JSBncm91cF9ieShJTlRFUlNFQ1RJT04sIENBTUVSQS5JRCkgJT4lIAogICNzZWxlY3QoSU5URVJTRUNUSU9OLCBDQU1FUkEuSUQsIFZJT0xBVElPTi5EQVRFLCBWSU9MQVRJT05TKSAlPiUgCiAgZHBseXI6OnN1bW1hcml6ZShTdW0uT2YuVmlvbGF0aW9ucyA9IHN1bShWSU9MQVRJT05TKSkKYGBgCgpXZSB3YW50IHRvIG1hcCB0aGUgcmVkIGxpZ2h0cyBvbnRvIGEgQ2hpY2FnbyBtYXAuIE1pc3NpbmcgbG9jYXRpb24gZGF0YSBjb3VsZCBiZSBhIGNoYWxsZW5nZSB0byB1cy4gTGV0J3Mgc2VlIGhvdyB3ZSBjYW4gZml4IHRoYXQuIFdlIG5vdGljZSB0aGF0IGV2ZXJ5IHJlY29yZCB3aXRoIG1pc3NpbmcgbG9jYXRpb25zIGhhcyBhIHZhbGlkIGludGVyc2VjdGlvbiB2YWx1ZS4gU2luY2Ugd2UgYW5hbHl6ZSBhdCB0aGUgaW50ZXJzZWN0aW9uIGxldmVsLCBhcyBsb25nIGFzIHdlIGhhdmUgdGhlIGxvY2F0aW9uIGZvciB0aGF0IGludGVyc2VjdGlvbiwgd2Ugd2lsbCB1c2UgdGhhdCB0byBmaWxsIGluIHRoZSBibGFua3MuIFdlIHN0aWxsIGhhdmUgdGhyZWUgaW50ZXJzZWN0aW9ucyB3aXRob3V0IGxvY2F0aW9uIGRhdGEuIFdlIHdpbGwgaWdub3JlIHRoZW0gaW4gdmlzdWFsaXphdGlvbiBmb3IgdGhlIG1vbWVudC4KCmBgYHtyfQppbnRlcnNlY3Rpb25fZ2VvIDwtIHJsY3YgJT4lIGdyb3VwX2J5KElOVEVSU0VDVElPTikgJT4lIAogIGRwbHlyOjpzdW1tYXJpemUoaW50ZXJzZWN0aW9uX2xhdCA9IHJvdW5kKG1lYW4oTEFUSVRVREUsIG5hLnJtID0gVCksIDMpLAogICAgICAgICAgICAgICAgICAgaW50ZXJzZWN0aW9uX2xvbiA9IHJvdW5kKG1lYW4oTE9OR0lUVURFLCBuYS5ybSA9IFQpLCAzKSkKaW50ZXJzZWN0aW9uX2dlbwpgYGAKCkxldCdzIGZpcnN0IHNlZSB3aGVyZSB0aGVzZSBjYW1lcmFzIGFyZS4gVGhlIG5ldHdvcmsgb2YgdGhlIHJlZCBsaWdodCBjYW1lcmFzIGlzIHByZXR0eSBtdWNoIGNvdmVyaW5nIHRoZSBjb3JlIG5laWdoYm9yaG9vZHMgb2YgQ2hpY2Fnby4gQnV0IHRoZXJlIGFyZW4ndCBtYW55IGNhbWVyYXMgaW4gdGhlIExvb3Agb3IgU291dGggTG9vcCBhcmVhLiAKCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpxbXBsb3QoaW50ZXJzZWN0aW9uX2xvbiwgaW50ZXJzZWN0aW9uX2xhdCwgZGF0YSA9IGludGVyc2VjdGlvbl9nZW8sCiAgICAgICB6b29tID0gMTEsIHNpemUgPSBJKDEuMjUpLCBjb2xvciA9IEkoInJlZCIpLAogICAgICAgbWFwdHlwZSA9ICJ0b25lci1saXRlIiwKICAgICAgIG1haW4gPSAiSW50ZXJzZWN0aW9ucyB3aXRoIFJlZCBMaWdodCBDYW1lcmFzIikgKwogIHRoZW1lX21pbmltYWwoKSArIHhsYWIoIiIpICsgeWxhYigiIikKYGBgCgpJdCB3b3VsZCBiZSBpbnRlcmVzdGluZyB0byBzZWUgd2hpY2ggaW50ZXJzZWN0aW9ucyBoYXZlIHNlZW4gdGhlIG1vc3QgdmlvbGF0aW9ucyBzaW5jZSB0aGUgaW5zdGFsbGF0aW9uIG9mIHRoZSBjYW1lcmFzLiBMQUtFIFNIT1JFIERSIEFORCBCRUxNT05UIGhhcyBzZWVuIHRoZSBtb3N0IHZpb2xhdGlvbnMuIENJQ0VSTyBBTkQgSTU1IGFuZCBWQU4gQlVSRU4gQU5EIFdFU1RFUk4gYXJlIG5vdCBmYXIgYXdheS4KCmBgYHtyLCB9CnJsY3YgJT4lIGdyb3VwX2J5KElOVEVSU0VDVElPTikgJT4lIAogIGRwbHlyOjpzdW1tYXJpemUoc3VtX3Zpb2xhdGlvbnMgPSBzdW0oVklPTEFUSU9OUyksCiAgICAgICAgICAgIG1lZF92aW9sYXRpb25zID0gbWVkaWFuKFZJT0xBVElPTlMpLAogICAgICAgICAgICBsYXQyID0gcm91bmQobWVhbihMQVRJVFVERSwgbmEucm0gPSBUKSwgNCksCiAgICAgICAgICAgIGxvbjIgPSByb3VuZChtZWFuKExPTkdJVFVERSwgbmEucm0gPSBUKSwgNCkpICU+JSAKICBhcnJhbmdlKC1zdW1fdmlvbGF0aW9ucykgLT4gaW50ZXJzZWN0aW9uX3Zpb2xhdGlvbnMKCmludGVyc2VjdGlvbl92aW9sYXRpb25zCmBgYAoKYGBge3IgU3VtIG9mIFZpb2xhdGlvbiBieSBJbnRlcnNlY3Rpb24sIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnFtcGxvdChsb24yLCBsYXQyLCBkYXRhID0gaW50ZXJzZWN0aW9uX3Zpb2xhdGlvbnMsCiAgICAgICB6b29tID0gMTEsIHNpemUgPSBzdW1fdmlvbGF0aW9ucywKICAgICAgIG1hcHR5cGUgPSAidG9uZXItbGl0ZSIsIGNvbG9yID0gc3VtX3Zpb2xhdGlvbnMsCiAgICAgICBtYWluID0gIlN1bSBvZiB2aW9sYXRpb25zIGF0IGVhY2ggaW50ZXJzZWN0aW9uIikgKwogIHZpcmlkaXM6OnNjYWxlX2NvbG9yX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIiwgZGlyZWN0aW9uID0gLTEpICsKICB0aGVtZV9taW5pbWFsKCkgKyB4bGFiKCIiKSArIHlsYWIoIiIpICsKICBndWlkZXMoY29sb3I9Z3VpZGVfbGVnZW5kKHRpdGxlID0gIiIpLAogICAgICAgICBzaXplID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIiIpKQpgYGAKCkJ1dCBieSBsb29raW5nIGF0IHRoZSB0b3RhbCBvZiB0aGUgdmlvbGF0aW9ucyBzaW5jZSB0aGUgc3RhcnQgb2YgdGhlIGRhdGEgbWF5IG5vdCBiZSBhbiBhY2N1cmF0ZSBkZXNjcmlwdGlvbi4gSXQncyBwb3NzaWJsZSB0aGF0IHNvbWUgcmVkIGxpZ2h0IGNhbWVyYXMgYXJlIG5ld2x5IGluc3RhbGxlZCBhbmQgaGFzbid0IGhhZCBhY2N1bXVsYXRlZCBtYW55IHZpb2xhdGlvbnMuIEhvd2V2ZXIgdGhlc2UgbmV3IGNhbWVyYXMgY291bGQgc2VuZCBvdXQgbWFueSB0aWNrZXRzIG9uIGEgZGFpbHkgYmFzaXMuIFRvIGFjY29tb2RhdGUgdGhhdCB0aG91Z2h0LCB3ZSBsb29rIGF0IHRoZSBtZWRpYW4gb2YgdGhlIHZpb2xhdGlvbnMgYXQgdGhlIGludGVyc2VjdGlvbnMsIHdoaWNoIHdvdWxkIGdpdmUgdXMgYSB2aWV3IG9mIHdoYXQgYSB0eXBpY2FsIGRheSBsb29rcyBsaWtlLiBBbmQgd2UgdGFrZSBhIHNuYXAgc2hvdCBvZiBGZWJydWFyeSAyMDE3IGRhdGEuIFRoaXMgc2hvdWxkIGdpdmUgdXMgYSBtb3N0IHJlY2VudCB2aWV3IG9mIHdoaWNoIGNhbWVyYXMgaGF2ZSBzZWVuIHRoZSBtb3N0IHZpb2xhdGlvbnMgaW4gYSBtb250aC4KCmBgYHtyIE1lZGlhbiBWaW9sYXRpb24gcGVyIGludGVyc2VjdGlvbiwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcW1wbG90KGxvbjIsIGxhdDIsIGRhdGEgPSBpbnRlcnNlY3Rpb25fdmlvbGF0aW9ucywKICAgICAgIHpvb20gPSAxMSwgc2l6ZSA9IG1lZF92aW9sYXRpb25zLAogICAgICAgbWFwdHlwZSA9ICJ0b25lci1saXRlIiwgY29sb3IgPSBtZWRfdmlvbGF0aW9ucywKICAgICAgIG1haW4gPSAiTWVkaWFuIG9mIHZpb2xhdGlvbnMgYXQgZWFjaCBpbnRlcnNlY3Rpb24iKSArCiAgdmlyaWRpczo6c2NhbGVfY29sb3JfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXJlY3Rpb24gPSAtMSkgKwogIHRoZW1lX21pbmltYWwoKSArIHhsYWIoIiIpICsgeWxhYigiIikgKwogIGd1aWRlcyhjb2xvcj1ndWlkZV9sZWdlbmQodGl0bGUgPSAiIiksCiAgICAgICAgIHNpemUgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiIikpCmBgYAoKYGBge3IgRmVicnVhcnkgVmlvbGF0aW9uIHBlciBpbnRlcnNlY3Rpb24sIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9VFJVRSwgd2FybmluZz1UUlVFfQpybGN2ICU+JQogIGRwbHlyOjptdXRhdGUoVklPTEFUSU9OLkRBVEUgPSBtZHkoVklPTEFUSU9OLkRBVEUpKSAlPiUgCiAgZmlsdGVyKFZJT0xBVElPTi5EQVRFID49ICIyMDE3LTAyLTAxIiAmCiAgICAgICAgICAgVklPTEFUSU9OLkRBVEUgPCAiMjAxNy0wMy0wMSIpIC0+IGZlYi52aW9sYXRpb24KYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZmViLnZpb2xhdGlvbiAlPiUgZ3JvdXBfYnkoSU5URVJTRUNUSU9OKSAlPiUgCiAgZHBseXI6OnN1bW1hcml6ZShzdW1fdmlvbGF0aW9ucyA9IHN1bShWSU9MQVRJT05TKSwKICAgICAgICAgICAgbiA9IG4oKSwKICAgICAgICAgICAgbGF0MiA9IHJvdW5kKG1lYW4oTEFUSVRVREUsIG5hLnJtID0gVCksIDQpLAogICAgICAgICAgICBsb24yID0gcm91bmQobWVhbihMT05HSVRVREUsIG5hLnJtID0gVCksIDQpKSAtPiBmZWIudmlvLnN1bQoKcW1wbG90KGxvbjIsIGxhdDIsIGRhdGEgPSBmZWIudmlvLnN1bSwKICAgICAgIHpvb20gPSAxMSwgc2l6ZSA9IHN1bV92aW9sYXRpb25zLAogICAgICAgbWFwdHlwZSA9ICJ0b25lci1saXRlIiwgY29sb3IgPSBzdW1fdmlvbGF0aW9ucywKICAgICAgIG1haW4gPSAiU3VtIG9mIHZpb2xhdGlvbnMgYXQgZWFjaCBpbnRlcnNlY3Rpb24gaW4gRmVicnVhcnkgMjAxNyIpICsKICB2aXJpZGlzOjpzY2FsZV9jb2xvcl92aXJpZGlzKG9wdGlvbiA9ICJtYWdtYSIsIGRpcmVjdGlvbiA9IC0xKSArCiAgdGhlbWVfbWluaW1hbCgpICsgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgZ3VpZGVzKGNvbG9yPWd1aWRlX2xlZ2VuZCh0aXRsZSA9ICIiKSwKICAgICAgICAgc2l6ZSA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICIiKSkKYGBgCgpUaGUgY2hhcnRzIHByb3ZpZGVzIGRpZmZlcmVudCBhbmdsZXMgdG8gdGhlIG51bWJlciBvZiB2aW9sYXRpb25zLiBCdXQgaXQncyB2ZXJ5IG9idmlvdXMgZnJvbSBhbGwgdGhyZWUgY2hhcnRzIHRoYXQgdGhlIGludGVyc2VjdGlvbiBvZiBDSUNFUk8gQU5EIEk1NSBpcyBwcm9iYWJseSB0aGUgbW9zdCBub3RvcmlvdXMgcmVkIGNhbWVyYSBpbnRlcnNlY3Rpb24gaW4gdGhlIENoaWNhZ29sYW5kLgoKTGV0J3MgdHJ5IHRvIGJ1aWxkIGEgbW9kZWwgdG8gc2VlIGlmIHdlIGNhbiBwcmVkaWN0IGRhaWx5IHZpb2xhdGlvbnMgYXQgdGhlIGludGVyc2VjdGlvbiBvZiBvdXIgaW50ZXJlc3QuCgpgYGB7cn0KcmxjdiAlPiUgZmlsdGVyKElOVEVSU0VDVElPTiA9PSAiQ0lDRVJPIEFORCBJNTUiKSAlPiUgCiAgZHBseXI6Om11dGF0ZShWSU9MQVRJT04uREFURSA9IG1keShWSU9MQVRJT04uREFURSkpICU+JSAKICBncm91cF9ieShWSU9MQVRJT04uREFURSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpemUoc3VtLnZpb2xhdGlvbnMgPSBzdW0oVklPTEFUSU9OUykpICU+JSAKICBhcnJhbmdlKFZJT0xBVElPTi5EQVRFKSAtPiBjaWNlcm8uNTUKCnJsY3YgJT4lIGZpbHRlcihJTlRFUlNFQ1RJT04gPT0gIkxBS0UgU0hPUkUgRFIgQU5EIEJFTE1PTlQiKSAlPiUgCiAgZHBseXI6Om11dGF0ZShWSU9MQVRJT04uREFURSA9IG1keShWSU9MQVRJT04uREFURSkpICU+JSAKICBncm91cF9ieShWSU9MQVRJT04uREFURSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpemUoc3VtLnZpb2xhdGlvbnMgPSBzdW0oVklPTEFUSU9OUykpICU+JSAKICBhcnJhbmdlKFZJT0xBVElPTi5EQVRFKSAtPiBsYWtlc2hvcmUuYmVsbW9udApgYGAKCmBgYHtyfQpjaWNlcm8uNTUueHRzIDwtIHh0cyhjaWNlcm8uNTVbLC0xXSwgb3JkZXIuYnk9Y2ljZXJvLjU1JFZJT0xBVElPTi5EQVRFKQp4dHM6OnBsb3QueHRzKGNpY2Vyby41NS54dHMpCgpsYWtlc2hvcmUuYmVsbW9udC54dHMgPC0geHRzKGxha2VzaG9yZS5iZWxtb250WywtMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIuYnk9bGFrZXNob3JlLmJlbG1vbnQkVklPTEFUSU9OLkRBVEUpCnh0czo6cGxvdC54dHMobGFrZXNob3JlLmJlbG1vbnQueHRzKQpgYGAKCk5vdyB3ZSByZWFsaXplIHRoZSBpbnRlcnNlY3Rpb24gQ0lDRVJPIEFORCBJNTUgbWlzc2VzIGRhdGEgZnJvbSBBcHJpbCB0byBPY3RvYmVyIGluIDIwMTUuIFdlIGRlY2lkZSB0byB1c2UgZGF0YSBmcm9tIE5vdiAyMDE1IHRvIGVuZCBvZiAyMDE2IHRvIGJ1aWxkIGEgbW9kZWwgYW5kIHRlc3QgaXQgb24gMjAxNyBkYXRhLiBUaGVyZSBhcmUgbWFueSB0aW1lIHNlcmllcyBtb2RlbGluZyBhcHByb2FjaCBvdXQgdGhlcmUuIFRoaXMgdGltZSB3ZSB3aWxsIHRyeSBhIG5ldyBtZXRob2QsIG5hbWVseSBwcm9waGV0LCBwdWJsaXNoZWQgYnkgRmFjZWJvb2sgcmVjZW50bHkuCgpgYGB7ciBDaWNlcm8gNTUgbW9kZWwsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNpY2Vyby41NSAlPiUKICBmaWx0ZXIoVklPTEFUSU9OLkRBVEUgPiAiMjAxNS8xMS8xIiwKICAgICAgICAgVklPTEFUSU9OLkRBVEUgPCAiMjAxNy8xLzEiKSAlPiUKICBkcGx5cjo6cmVuYW1lKHkgPSBzdW0udmlvbGF0aW9ucywgZHMgPSBWSU9MQVRJT04uREFURSkgLT4gY2ljZXJvLjU1LmRhdGEKCmNpY2Vyby41NSAlPiUKICBmaWx0ZXIoVklPTEFUSU9OLkRBVEUgPj0gIjIwMTcvMS8xIikgJT4lCiAgZHBseXI6OnJlbmFtZSh5ID0gc3VtLnZpb2xhdGlvbnMsIGRzID0gVklPTEFUSU9OLkRBVEUpIC0+IGNpY2Vyby41NS50ZXN0CgpmaXQucHJvcGhldCA8LSBwcm9waGV0KGNpY2Vyby41NS5kYXRhKQojc3VtbWFyeShmaXQucHJvcGhldCkKCmZ1dHVyZSA8LSBtYWtlX2Z1dHVyZV9kYXRhZnJhbWUoZml0LnByb3BoZXQsIHBlcmlvZHMgPSA2OCkKZmNzdCA8LSBwcmVkaWN0KGZpdC5wcm9waGV0LCBmdXR1cmUpCnBsb3QoZml0LnByb3BoZXQsIGZjc3QpICsKICBnZW9tX2xpbmUoZGF0YSA9IGNpY2Vyby41NS5kYXRhLCBhZXMoeCA9IGRzLCB5ID0geSksIGFscGhhID0gMC4yNSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGNpY2Vyby41NS50ZXN0LCBhZXMoeCA9IGRzLCB5ID0geSksIGNvbG9yID0gIm9yYW5nZSIpICsKICB4bGFiKCJEYXRlIikKcHJvcGhldF9wbG90X2NvbXBvbmVudHMoZml0LnByb3BoZXQsIGZjc3QpCgpmY3N0ICU+JSB0YmxfZGYoKSAlPiUgCiAgZmlsdGVyKGRzID49ICIyMDE3LzEvMSIpICU+JSAKICBzZWxlY3QoZHMsIHloYXQpIC0+IGZjc3QudGVzdAoKaW5uZXJfam9pbihjaWNlcm8uNTUudGVzdCwgZmNzdC50ZXN0LCBieSA9ICJkcyIpIC0+IGNpY2Vyby41NS50ZXN0LmFjYwpnZ3Bsb3QoY2ljZXJvLjU1LnRlc3QuYWNjLCBhZXMoeCA9IGRzKSkgKwogIGdlb21fcG9pbnQoYWVzKHkgPSB5KSkgKwogIGdlb21fbGluZShhZXMoeSA9IHloYXQpLCBjb2xvciA9ICJibHVlIikgKwogIHhsYWIoIkRhdGUiKSArIHlsYWIoIk91dCBvZiBzYW1wbGUgYWN0dWFscyBhbmQgcHJlZGljdGVkIikKCihjaWNlcm8uNTUudGVzdC5hY2MkeSAtIGNpY2Vyby41NS50ZXN0LmFjYyR5aGF0KSAlPiUKICBhYnMoKSAlPiUgbWVhbigpCmBgYAoKV2UgY2FuIHNlZSB0aGF0IHRoZSBtb2RlbCByZWNvZ25pemVzIGEgZ2VuZXJhbCB0cmVuZCBidXQgZmFpbHMgdG8gY2FwdHVyZSB0aGUgdm9sYXRpbGl0eSBvZiB0aGUgdmlvbGF0aW9ucyBmcm9tIGRheSB0byBkYXkuIFRoZSBtZWFuIGFic29sdXRlIGVycm9yIGluIDIwMTcgaXMgYWJvdXQgMjEuIFdlIHRyeSB0aGUgbW9kZWwgb24gYW5vdGhlciBpbnRlcnNlY3Rpb24gd2l0aCBhIGxvbmdlciB2aW9sYXRpb24gaGlzdG9yeS4KCmBgYHtyfQpsYWtlc2hvcmUuYmVsbW9udCAlPiUKICBmaWx0ZXIoVklPTEFUSU9OLkRBVEUgPCAiMjAxNy8xLzEiKSAlPiUKICBkcGx5cjo6cmVuYW1lKHkgPSBzdW0udmlvbGF0aW9ucywgZHMgPSBWSU9MQVRJT04uREFURSkgLT4gbGFrZXNob3JlLmJlbG1vbnQuZGF0YQoKbGFrZXNob3JlLmJlbG1vbnQgJT4lCiAgZmlsdGVyKFZJT0xBVElPTi5EQVRFID49ICIyMDE3LzEvMSIpICU+JQogIGRwbHlyOjpyZW5hbWUoeSA9IHN1bS52aW9sYXRpb25zLCBkcyA9IFZJT0xBVElPTi5EQVRFKSAtPiBsYWtlc2hvcmUuYmVsbW9udC50ZXN0CgpmaXQucHJvcGhldCA8LSBwcm9waGV0KGxha2VzaG9yZS5iZWxtb250LmRhdGEpCgpmdXR1cmUgPC0gbWFrZV9mdXR1cmVfZGF0YWZyYW1lKGZpdC5wcm9waGV0LCBwZXJpb2RzID0gNjgpCmZjc3QgPC0gcHJlZGljdChmaXQucHJvcGhldCwgZnV0dXJlKQpwbG90KGZpdC5wcm9waGV0LCBmY3N0KSArCiAgZ2VvbV9saW5lKGRhdGEgPSBsYWtlc2hvcmUuYmVsbW9udC5kYXRhLCBhZXMoeCA9IGRzLCB5ID0geSksIGFscGhhID0gMC4yNSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGxha2VzaG9yZS5iZWxtb250LnRlc3QsIGFlcyh4ID0gZHMsIHkgPSB5KSwgY29sb3IgPSAib3JhbmdlIikgKwogIHhsYWIoIkRhdGUiKSArIHlsYWIoIlZpb2xhdGlvbnMiKQpwcm9waGV0X3Bsb3RfY29tcG9uZW50cyhmaXQucHJvcGhldCwgZmNzdCkKCmZjc3QgJT4lIHRibF9kZigpICU+JSAKICBmaWx0ZXIoZHMgPj0gIjIwMTcvMS8xIikgJT4lIAogIHNlbGVjdChkcywgeWhhdCkgLT4gZmNzdC50ZXN0Cgppbm5lcl9qb2luKGxha2VzaG9yZS5iZWxtb250LnRlc3QsIGZjc3QudGVzdCwgYnkgPSAiZHMiKSAtPiBsYWtlc2hvcmUuYmVsbW9udC50ZXN0LmFjYwpnZ3Bsb3QobGFrZXNob3JlLmJlbG1vbnQudGVzdC5hY2MsIGFlcyh4ID0gZHMpKSArCiAgZ2VvbV9wb2ludChhZXMoeSA9IHkpKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0geWhhdCksIGNvbG9yID0gImJsdWUiKSArCiAgeGxhYigiRGF0ZSIpICsgeWxhYigiT3V0IG9mIHNhbXBsZSBhY3R1YWxzIGFuZCBwcmVkaWN0ZWQiKQoKYGBgCgpUaGlzIHRpbWUgdGhlIG1vZGVsIHNlZW1zIHRvIGJlIGFibGUgdG8gZml0IHdlbGwgaW4gc2FtcGxlLiBCdXQgaXQgc2hvd3MgYSB1cHdhcmQgYmlhcyBpbiB0aGUgMjAxNyBvdXQgb2Ygc2FtcGxlIHByZWRpY3Rpb25zLiBUaGVyZSdzIG5vIGRvdWJ0IGl0J3Mgbm8gZWFzeSB0YXNrIHRvIGFjY3VyYXRlbHkgcHJlZGljdCB0aGUgdmlvbGF0aW9ucyBpbnRvIHRoZSBmdXR1cmUuIFdlIHdpbGwgZXhwbG9yZSB0aGlzIHRvcGljIGluIHRoZSBuZXh0IHBvc3QuCgpJbiBzdW1tYXJ5LCB3aGF0IHdlIGhhdmUgYWNoaWV2ZWQgaW4gdGhpcyBwb3N0IGlzIHRvIGdhdGhlciB0aGUgZGF0YSBmcm9tIENpdHkgb2YgQ2hpY2FnbyBkYXRhIHBvcnRhbCBhbmQgZHJhdyB0aGUgdmlvbGF0aW9ucyBvbiBhIG1hcC4gV2UgYWxzbyBlbnRlcnRhaW5lZCB0aGUgaWRlYSBvZiBidWlsZGluZyBhIHByZWRpY3RpdmUgbW9kZWwgdG8gZm9yZWNhc3QgdmlvbGF0aW9ucyBpbnRvIHRoZSBmdXR1cmUuIEhvcGUgeW91IGZpbmQgc29tZSBvZiB0aGUgY29udGVudCBpbnRlcmVzdGluZyBvciB1c2VmdWwuIExldCB1cyBrbm93IGlmIHlvdSBoYXZlIGFueSB0aG91Z2h0cy4K