About this project
One important note: this project was originally made in Dutch. I translated every description and comments in the code. Only table names are in Dutch.
This project is all about process improvement when organizing festivals. I designed the SQL database for this project for the festival organizers and used R to manipulate and visualize data.
Workflow:
1. Data collection
2. Pre-processing
3. Exploratory Data Analysis
1.1 Importing libraries:
library(dplyr)
library(ggplot2)
library(RSQLite)
library(forcats)
library(lubridate)
library(openxlsx)
library(rio)
library(ggrepel)
1.2 Database connection
con <- dbConnect(SQLite(), "/Users/milanpatty/Documents/Business/Semester_2/R/Proftaak/Festivate2.db")
1.3 Check whether the tables have been loaded successfully
as.data.frame(dbListTables(con))
| | | | |
---|
aanmeldingen | | | | |
beschikbare_middelen | | | | |
bezoeker | | | | |
campagne | | | | |
campagne_festival | | | | |
festival | | | | |
festival_categorie | | | | |
festival_categorie_festival | | | | |
festival_partners | | | | |
inkoop | | | | |
1.4 Create objects from the tables
aanmeldingen <- dbReadTable(con, 'aanmeldingen')
beschikbare_middelen <- dbReadTable(con, 'beschikbare_middelen')
Column `aantal`: mixed type, first seen values of type integer, coercing other values of type stringColumn `waarde`: mixed type, first seen values of type integer, coercing other values of type string
bezoeker <- dbReadTable(con, 'bezoeker')
campagne <- dbReadTable(con, 'festival')
campagne_festival <- dbReadTable(con, 'campagne_festival')
festival <- dbReadTable(con, 'festival')
festival_categorie <- dbReadTable(con, 'festival_categorie')
festival_categorie_festival <- dbReadTable(con, 'festival_categorie_festival')
festival_partners <- dbReadTable(con, 'festival_partners')
inkoop <- dbReadTable(con, 'inkoop')
inkoop_festival <- dbReadTable(con, 'inkoop_festival')
locatie <- dbReadTable(con, 'locatie')
partners <- dbReadTable(con, 'partners')
sponsoren <- dbReadTable(con, 'sponsoren')
ticket_categorie <- dbReadTable(con, 'ticket_categorie')
tickets <- dbReadTable(con, 'tickets')
verkoop_producten <- dbReadTable(con, 'verkoop_producten')
locatie_festival <- dbReadTable(con, 'locatie_festival')
verkoop_producten_festival <- dbReadTable(con, 'verkoop_producten_festival')
werknemers <- dbReadTable(con, 'werknemers')
werknemers_werkzaamheden <- dbReadTable(con, 'werknemers_werkzaamheden')
werkzaamheden <- dbReadTable(con, 'werkzaamheden')
werkzaamheden_festival <- dbReadTable(con, 'werkzaamheden_festival')
2. Pre-processing
The second step after data collection is the pre-processing of data. This includes various things such as class labelling (indicating the correct data type), data cleaning (correcting, for example, incorrectly spelt names) and the handling of missing values. Furthermore, the data I use from my database is tidy according to the 3 rules:
- Each variable has its own column.
- Each observation has its own row.
- Each value has its own cell.
2.1 Table Location
While setting up the database, a group member responsible for the table miswritten the word ‘capaciteit’(capacity) Of course, we want to fix this bug and make sure it gets correct everywhere. Since bezoekers_capiciteit (visitor_capacity) is a column, it can easily be renamed with the function rename
.
locatie <-locatie %>%
rename(bezoekers_capaciteit = bezoekers_capiciteit)
To see if the change is correct:
[1] "locatie_id" "naam"
[3] "adres" "plaats"
[5] "areas" "bezoekers_capaciteit"
3. EDA
3.1 What is the visitor capacity per location?
The figure below shows all locations with the corresponding visitor capacity. These are a total of 15 locations, each of which has its areas. What is striking is that the top 3 locations with the most capacity take up 59.01% of the total capacity and the 5 locations with the least capacity only take up 6.83%. By chance, it does not mean that locations with more areas can have more visitors. The location with the most areas is the Berendonck in Wijchen, where the annual Emporium festival is held. These locations have no fewer than 17 areas and they only take up 6.21% of the total capacity. This substantiation has been validated with calculation functions in Excel and is also stored there.
locatie %>%
mutate(naam = fct_reorder(naam, bezoekers_capaciteit)) %>%
ggplot( aes(x=naam, y=bezoekers_capaciteit)) +
geom_bar(stat="identity", fill="#f68060", alpha=.7, width=.6) +
coord_flip() +
xlab('Location') +
ylab('Visitor capacity')+
ggtitle('Visitor capacity per location')+
theme_bw(base_size = 15)
3.2 What are the sales per month?
Below you can see the sales per month divided over 2018, 2019 and 2021. What can be seen in this figure is that there are years where some months have no sales. For 2018 this will be April and for 2019 and 2021 it will both be March. in 2018 total sales were € 491,043.00 compared to 2019 and 2021, where it was both € 113,104.00. Average sales in 2018 were € 61,381.25, in 2019 and 2021 it was both € 14,138.88. These calculations have been validated with Excel’s calculation functions.
SELECT product, ROUND(prijs,2) AS prijs, aantal, leverancier, datum
FROM inkoop i
JOIN inkoop_festival ink
ON ink.inkoop_id = i.inkoop_id
JOIN festival f
ON ink.festival_id = f.festival_id
afzet %>%
mutate(jaar = year(datum), maand= month(datum), afzet_prijs = aantal * prijs) %>%
group_by(jaar, maand) %>%
summarise(afzet = sum(afzet_prijs)) %>%
ggplot(aes(maand, afzet, fill=factor(maand))) + geom_bar(stat = 'identity') + facet_grid(~ jaar) +
scale_y_continuous(breaks=seq(0,200000,10000)) +
scale_x_continuous(breaks=seq(3,9,1)) +
ggtitle('Sales per month divided over 2018, 2019 & 2021') +
xlab('Month') +
ylab('Sales in €') +
theme_bw(base_size = 16) +
theme(legend.position = "none")
afzet_output <- afzet %>%
mutate(jaar = year(datum), maand= month(datum), afzet_prijs = aantal * prijs)
export(afzet_output, 'afzet.xlsx')
3.4 Which Festivals have more than 3 partners?
For this query, I am using a CTE as a resultset. I hereby show which festivals have more than 3 partners. In the CTE I select the festivals and count the number of partners they have. I hereby join on 2 tables: partners and the intermediate table festival_partners. Then I group by festival name. In the query, I select all festivals that have more than 3 partners. I validated this by looking in the database which festivals have which partners.
WITH festival_partners_cte
AS
(
SELECT f.naam AS festival_naam, COUNT(p.naam) AS aantal_partners
FROM festival f
JOIN festival_partners fp
ON fp.festival_id = f.festival_id
JOIN partners p
ON p.partner_id = fp.partner_id
GROUP BY f.naam
)
SELECT festival_naam, aantal_partners
FROM festival_partners_cte
WHERE aantal_partners > 3
3.5 Which purchasing supplier supplies the most products?
In this query, I show which supplier delivers the most products in numbers and what percentage this is of the total deliveries. First of all, I add up the products by grouping them by the supplier name, then I count all products and divide them by all the products that are in the purchase table. I round this to 2 decimal places. Below you can see that the Makro supplies no less than 33.33% of all products and the numbers 2 and 3 are also good for 33.33% in total. This means that only the top 3 suppliers already supply 66.66% of all products. These calculations have been validated in excel.
SELECT leverancier,COUNT(product) AS aantal, ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM inkoop),2) AS percentage
FROM inkoop
GROUP BY leverancier
ORDER BY percentage DESC
| | | | |
---|
makro | 10 | 33.33 | | |
sligro | 6 | 20.00 | | |
hanos | 4 | 13.33 | | |
goedkoopdrank | 3 | 10.00 | | |
wijnvoordeel | 1 | 3.33 | | |
watercoolergigant | 1 | 3.33 | | |
treb | 1 | 3.33 | | |
plus | 1 | 3.33 | | |
horecabier | 1 | 3.33 | | |
eventtrading | 1 | 3.33 | | |
3.6 The ratio of sex of visitors
Below you can see the gender ratio of the festival visitors. 52% of the festival visitors are male while 48% of the visitors are female(vrouw). For this chunk I did the following: from the visitor table, I started grouping by gender. Then I started to minimize all rows by using summarise and counting the rows for each gender. Then I started plotting everything. I want to put the percentages on the Y-axis and I want to use the levels of the factor as colour. This chunk has been validated using an SQL query that gives the same result.
bezoeker %>%
group_by(geslacht) %>%
summarise(aantal = n()) %>%
mutate(procent = round(100 * aantal / sum(aantal), 1)) %>%
ggplot(aes(x = '', y = aantal ,fill = factor(geslacht))) +
geom_bar(width = 1, stat='identity') +
coord_polar(theta = "y") +
theme_void() +
ggtitle('The ratio of sex of visitors') +
guides(fill=guide_legend(title="Sex")) +
scale_fill_manual(values=c("#00ccff", "#ff0000")) +
geom_label_repel(aes(label = procent), size = 5, show.legend = F)
The validation:
select geslacht, ROUND(COUNT(*) *100 / (select count(*) FROM bezoeker),2) AS percentage
FROm bezoeker
group by geslacht
3.7 Which visitors went to Mysteryland in 2018 and have also been to Mysteryland in subsequent years?
Below you can see which festival visitors have been to Mysteryland in 2018 and then returned in consecutive years. The first query consists of an inline view and 2 subqueries, the last validation query consists of a CTE and a query. Here I use a CASE statement as an Inline IF to select values that meet a certain condition.
SELECT voornaam, achternaam, bezoeker_id
FROM
(
SELECT f.naam AS festival_naam, b.voornaam AS voornaam, b.achternaam AS achternaam, b.bezoeker_id, strftime('%Y',datum) as "jaar"
FROM festival f
JOIN aanmeldingen a
ON a.festival_id = f.festival_id
JOIN bezoeker b
ON b.bezoeker_id = a.bezoeker_id
)t
WHERE jaar = '2018' AND festival_naam = 'Mysteryland'
AND bezoeker_id in
(
SELECT bezoeker_id
FROM
(
SELECT f.naam AS festival_naam, b.voornaam AS voornaam, b.achternaam AS achternaam, b.bezoeker_id, strftime('%Y',datum) as "jaar"
FROM festival f
JOIN aanmeldingen a
ON a.festival_id = f.festival_id
JOIN bezoeker b
ON b.bezoeker_id = a.bezoeker_id
)t
WHERE jaar = '2019' AND festival_naam = 'Mysteryland'
)
AND bezoeker_id IN
(
SELECT bezoeker_id
FROM
(
SELECT f.naam AS festival_naam, b.voornaam AS voornaam, b.achternaam AS achternaam, b.bezoeker_id, strftime('%Y',datum) as "jaar"
FROM festival f
JOIN aanmeldingen a
ON a.festival_id = f.festival_id
JOIN bezoeker b
ON b.bezoeker_id = a.bezoeker_id
)t
WHERE jaar = '2021' AND festival_naam = 'Mysteryland'
)
| | | | |
---|
Piet | Jansen | 2 | | |
Jan | Peters | 1 | | |
Boudewijn | Stockovich | 35 | | |
WITH result_set AS
(
SELECT DISTINCT f.naam AS festival_naam, b.voornaam AS voornaam, b.achternaam AS achternaam, b.bezoeker_id, strftime('%Y',datum) as "jaar"
FROM festival f
JOIN aanmeldingen a
ON a.festival_id = f.festival_id
JOIN bezoeker b
ON b.bezoeker_id = a.bezoeker_id
)
SELECT DISTINCT bezoeker_id, voornaam, achternaam,
CASE
WHEN festival_naam ='Mysteryland' AND jaar = '2018' THEN 'Ja'
WHEN jaar = '2019' AND jaar = '2021' THEN 'Ja'
ELSE 'Nee' END AS geweest
from result_set
ORDER BY geweest
| | | | |
---|
2 | Piet | Jansen | Ja | |
1 | Jan | Peters | Ja | |
35 | Boudewijn | Stockovich | Ja | |
2 | Piet | Jansen | Nee | |
3 | Hans | Hendriks | Nee | |
4 | Simone | Frederiks | Nee | |
5 | Lies | van Straten | Nee | |
6 | Erik | Smeets | Nee | |
7 | Linda | Bouwer | Nee | |
8 | Bob | Gerards | Nee | |
3.8 What is the age distribution of the festival visitors?
The figure below shows the age distribution of the festival visitors by gender. What is obvious is that fewer 40+ men go to festivals compared to 40+ women. Up to the age of 32, more men than women attend festivals, while many young women (18 to 20) attend festivals. The average age of the festival visitors is 27 years and the median is 23. The oldest visitor is 52 for men and 54 for women. The youngest visitors of both men and women are both 18 years old. Man = Male, vrouw = Female
bezoeker$leeftijd <- as.numeric(difftime(Sys.Date(),bezoeker$geboorte_datum, units = "weeks"))/52.25
bezoeker$leeftijd <- floor(bezoeker$leeftijd)
ggplot(bezoeker, aes(x=leeftijd, fill=geslacht)) +
geom_histogram(position="identity",binwidth=2, alpha=0.6) +
scale_x_continuous(breaks=seq(18,60,2)) +
scale_y_continuous(breaks=seq(0,15,1)) +
geom_vline(aes(xintercept=mean(leeftijd)), color="purple",
linetype="dashed") +
geom_vline(aes(xintercept=median(leeftijd)), color="black",
linetype="dashed") +
labs(title="Age distribution Festival visitors",x="Age", y = "Count")+
theme_bw(base_size = 16) +
scale_fill_manual(values=c("#00ccff", "#ff0000"))
ggplot(bezoeker, aes(x=leeftijd)) +
geom_histogram(position="identity",binwidth=2, alpha=0.6) +
scale_x_continuous(breaks=seq(18,60,2)) +
scale_y_continuous(breaks=seq(0,15,1)) +
labs(title="Age distribution Festival visitors by sex",x="Age", y = "Count")+
theme_bw(base_size = 16) +
scale_fill_manual(values=c("#00ccff", "#ff0000")) + facet_wrap(~ geslacht)
3.9 What is the average age per gender of the festival visitors who attend Techno festivals in 2019?
Below is the average age per gender of the festival visitors who went to Techno festivals in 2019. This query consists of 3 parts: The query, inline view as result set and a subquery. First of all, use a cast to round the average age down because SQLite has no floor function. Then I use an inline view where I select all unique visitors who went to festivals in 2019. I also calculate to calculate the age. I also create a subquery that serves as a result set where visitors went to techno festivals. This, combined with the rest, provides an overview of the average age per gender of the festival visitors who go to techno festivals.
SELECT geslacht ,cast ( AVG(leeftijd) as int ) - ( AVG(leeftijd) < cast ( AVG(leeftijd) as int )) AS gemiddelde_leeftijd
FROM
(
SELECT DISTINCT b.bezoeker_id, voornaam, achternaam,
(strftime('%Y', 'now') - strftime('%Y', geboorte_datum)) - (strftime('%m-%d', 'now') < strftime('%m-%d', geboorte_datum)) AS leeftijd, geslacht, strftime('%Y', datum) AS jaar
FROM bezoeker b
JOIN aanmeldingen a ON a.bezoeker_id = b.bezoeker_id
JOIN festival f ON f.festival_id = a.festival_id
WHERE jaar = '2019' AND b.bezoeker_id IN
(
SELECT b.bezoeker_id
FROM festival f
JOIN aanmeldingen a ON a.festival_id = f.festival_id
JOIN bezoeker b on b.bezoeker_id = a.bezoeker_id
JOIN festival_categorie_festival fcf ON fcf.festival_id = f.festival_id
JOIN festival_categorie fc ON fc.categorie_id = fcf.categorie_id
WHERE fc.naam =='Techno'
)
)t
GROUP BY geslacht
3.10 What is the average age of the festival visitors who went to Emporium in 2019?
Below is the average age of the festival visitors who went to Emporium in 2019. From the festival table I join 4 other tables: aanmeldingen(registrations), bezoeker(visitor), festival_categorie_festival(between table) & festival_categorie(festival category). Then I filter on the festival name and year of the festival. Then I group by sex and minimize all rows because I want to know the average age based on sex.
festival %>%
inner_join(aanmeldingen, by='festival_id') %>%
inner_join(bezoeker, by='bezoeker_id') %>%
inner_join(festival_categorie_festival, by='festival_id') %>%
inner_join(festival_categorie, by='categorie_id') %>%
filter(naam.x == 'Emporium' & year(datum) =='2019') %>%
group_by(geslacht) %>%
summarise(gemiddelde_leeftijd = mean(leeftijd.y))
3.11 What is the lowest, average and greatest revenue of the products from the sales table for each year?
Below is an overview of the revenue from the sales table for each year. For each year I show the lowest, average and highest turnover that was achieved in that year. From the verkoop_producten (sales_products) table I join 2 other tables: the between table and festival. Then I create a new column for the revenue: the price times the number of pieces is the revenue. Then I group by the year of each date and I minimize the rows with the corresponding revenue for each year.
verkoop_producten %>%
inner_join(verkoop_producten_festival, by='verkoop_id') %>%
inner_join(festival, by='festival_id') %>%
mutate(omzet = prijs * aantal) %>%
group_by(year(datum)) %>%
summarise(gemiddelde_omzet = round(mean(omzet),2),
laagste_omzet = round(min(omzet),2),
hoogste_omzet = round(max(omzet),2))
| | | | |
---|
2018 | 25395.87 | 4400 | 80000 | |
2019 | 16906.47 | 4400 | 62785 | |
2021 | 25120.40 | 4400 | 80000 | |
3.12 How many pop festivals were organized per year?
Below is a small overview where you can see how many pop festivals are held each year. First of all, I select the festival table and join 4 other tables, 2 intermediate tables and the festival_category + location table. Then I group by each year and I filter by the music genre and finally I count this so that you can see how many pop festivals there are given each year.
festival %>%
inner_join(festival_categorie_festival, by='festival_id') %>%
inner_join(festival_categorie, by='categorie_id') %>%
inner_join(locatie_festival, by='festival_id') %>%
inner_join(locatie, by='locatie_id') %>%
group_by(year(datum.x)) %>%
filter(genre=='Pop') %>%
count(genre)
LS0tCnRpdGxlOiAiUHJvamVjdDogRmVzdGl2YXRlIChFREEpIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBBYm91dCB0aGlzIHByb2plY3QKT25lIGltcG9ydGFudCBub3RlOiB0aGlzIHByb2plY3Qgd2FzIG9yaWdpbmFsbHkgbWFkZSBpbiBEdXRjaC4gSSB0cmFuc2xhdGVkIGV2ZXJ5IGRlc2NyaXB0aW9uIGFuZCBjb21tZW50cyBpbiB0aGUgY29kZS4gT25seSB0YWJsZSBuYW1lcyBhcmUgaW4gRHV0Y2guCgpUaGlzIHByb2plY3QgaXMgYWxsIGFib3V0IHByb2Nlc3MgaW1wcm92ZW1lbnQgd2hlbiBvcmdhbml6aW5nIGZlc3RpdmFscy4gSSBkZXNpZ25lZCB0aGUgU1FMIGRhdGFiYXNlIGZvciB0aGlzIHByb2plY3QgZm9yIHRoZSBmZXN0aXZhbCBvcmdhbml6ZXJzIGFuZCB1c2VkIFIgdG8gbWFuaXB1bGF0ZSBhbmQgdmlzdWFsaXplIGRhdGEuCgojIyMgV29ya2Zsb3c6CgojIyMgMS4gRGF0YSBjb2xsZWN0aW9uCiMjIyAyLiBQcmUtcHJvY2Vzc2luZwojIyMgMy4gRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcwoKIyMjIDEuMSBJbXBvcnRpbmcgbGlicmFyaWVzOgpgYGB7ciBlY2hvPVRSVUV9CmxpYnJhcnkoZHBseXIpICMgRGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShnZ3Bsb3QyKSAjIERhdGEgdmlzdWFsaXphdGlvbgpsaWJyYXJ5KFJTUUxpdGUpICMgRGF0YWJhc2UgY29ubmVjdGlvbgpsaWJyYXJ5KGZvcmNhdHMpICMgRmFjdG9ycwpsaWJyYXJ5KGx1YnJpZGF0ZSkgIyBEYXRlcwpsaWJyYXJ5KG9wZW54bHN4KSAjIEV4cG9ydCB0byBFeGNlbApsaWJyYXJ5KHJpbykgIyBFeHBvcnQgdG8gRXhjZWwKbGlicmFyeShnZ3JlcGVsKSAjIFBpZSBjaGFydApgYGAKCiMjIyAxLjIgRGF0YWJhc2UgY29ubmVjdGlvbiAKYGBge3IgZWNobz1UUlVFfQpjb24gPC0gZGJDb25uZWN0KFNRTGl0ZSgpLCAiL1VzZXJzL21pbGFucGF0dHkvRG9jdW1lbnRzL0J1c2luZXNzL1NlbWVzdGVyXzIvUi9Qcm9mdGFhay9GZXN0aXZhdGUyLmRiIikKYGBgCgojIyMgMS4zIENoZWNrIHdoZXRoZXIgdGhlIHRhYmxlcyBoYXZlIGJlZW4gbG9hZGVkIHN1Y2Nlc3NmdWxseQpgYGB7cn0KYXMuZGF0YS5mcmFtZShkYkxpc3RUYWJsZXMoY29uKSkKYGBgCgojIyMgMS40IENyZWF0ZSBvYmplY3RzIGZyb20gdGhlIHRhYmxlcwpgYGB7ciBlY2hvPVRSVUV9CmFhbm1lbGRpbmdlbiA8LSBkYlJlYWRUYWJsZShjb24sICdhYW5tZWxkaW5nZW4nKQpiZXNjaGlrYmFyZV9taWRkZWxlbiA8LSBkYlJlYWRUYWJsZShjb24sICdiZXNjaGlrYmFyZV9taWRkZWxlbicpCmJlem9la2VyIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ2Jlem9la2VyJykKY2FtcGFnbmUgPC0gZGJSZWFkVGFibGUoY29uLCAnZmVzdGl2YWwnKQpjYW1wYWduZV9mZXN0aXZhbCA8LSBkYlJlYWRUYWJsZShjb24sICdjYW1wYWduZV9mZXN0aXZhbCcpCmZlc3RpdmFsIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ2Zlc3RpdmFsJykKZmVzdGl2YWxfY2F0ZWdvcmllIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ2Zlc3RpdmFsX2NhdGVnb3JpZScpCmZlc3RpdmFsX2NhdGVnb3JpZV9mZXN0aXZhbCA8LSBkYlJlYWRUYWJsZShjb24sICdmZXN0aXZhbF9jYXRlZ29yaWVfZmVzdGl2YWwnKQpmZXN0aXZhbF9wYXJ0bmVycyA8LSBkYlJlYWRUYWJsZShjb24sICdmZXN0aXZhbF9wYXJ0bmVycycpCmlua29vcCA8LSBkYlJlYWRUYWJsZShjb24sICdpbmtvb3AnKQoKaW5rb29wX2Zlc3RpdmFsIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ2lua29vcF9mZXN0aXZhbCcpCmxvY2F0aWUgPC0gZGJSZWFkVGFibGUoY29uLCAnbG9jYXRpZScpCnBhcnRuZXJzIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3BhcnRuZXJzJykKc3BvbnNvcmVuIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3Nwb25zb3JlbicpCnRpY2tldF9jYXRlZ29yaWUgPC0gZGJSZWFkVGFibGUoY29uLCAndGlja2V0X2NhdGVnb3JpZScpCnRpY2tldHMgPC0gZGJSZWFkVGFibGUoY29uLCAndGlja2V0cycpCnZlcmtvb3BfcHJvZHVjdGVuIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3Zlcmtvb3BfcHJvZHVjdGVuJykKbG9jYXRpZV9mZXN0aXZhbCA8LSBkYlJlYWRUYWJsZShjb24sICdsb2NhdGllX2Zlc3RpdmFsJykKdmVya29vcF9wcm9kdWN0ZW5fZmVzdGl2YWwgPC0gZGJSZWFkVGFibGUoY29uLCAndmVya29vcF9wcm9kdWN0ZW5fZmVzdGl2YWwnKQp3ZXJrbmVtZXJzIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3dlcmtuZW1lcnMnKQoKd2Vya25lbWVyc193ZXJremFhbWhlZGVuIDwtIGRiUmVhZFRhYmxlKGNvbiwgJ3dlcmtuZW1lcnNfd2Vya3phYW1oZWRlbicpCndlcmt6YWFtaGVkZW4gPC0gZGJSZWFkVGFibGUoY29uLCAnd2Vya3phYW1oZWRlbicpCndlcmt6YWFtaGVkZW5fZmVzdGl2YWwgPC0gZGJSZWFkVGFibGUoY29uLCAnd2Vya3phYW1oZWRlbl9mZXN0aXZhbCcpCmBgYAoKIyMgMi4gUHJlLXByb2Nlc3NpbmcKVGhlIHNlY29uZCBzdGVwIGFmdGVyIGRhdGEgY29sbGVjdGlvbiBpcyB0aGUgcHJlLXByb2Nlc3Npbmcgb2YgZGF0YS4gVGhpcyBpbmNsdWRlcyB2YXJpb3VzIHRoaW5ncyBzdWNoIGFzIGNsYXNzIGxhYmVsbGluZyAoaW5kaWNhdGluZyB0aGUgY29ycmVjdCBkYXRhIHR5cGUpLCBkYXRhIGNsZWFuaW5nIChjb3JyZWN0aW5nLCBmb3IgZXhhbXBsZSwgaW5jb3JyZWN0bHkgc3BlbHQgbmFtZXMpIGFuZCB0aGUgaGFuZGxpbmcgb2YgbWlzc2luZyB2YWx1ZXMuIEZ1cnRoZXJtb3JlLCB0aGUgZGF0YSBJIHVzZSBmcm9tIG15IGRhdGFiYXNlIGlzIHRpZHkgYWNjb3JkaW5nIHRvIHRoZSAzIHJ1bGVzOgoKMS4gRWFjaCB2YXJpYWJsZSBoYXMgaXRzIG93biBjb2x1bW4uCjIuIEVhY2ggb2JzZXJ2YXRpb24gaGFzIGl0cyBvd24gcm93LgozLiBFYWNoIHZhbHVlIGhhcyBpdHMgb3duIGNlbGwuCgojIyMgMi4xIFRhYmxlIExvY2F0aW9uCldoaWxlIHNldHRpbmcgdXAgdGhlIGRhdGFiYXNlLCBhIGdyb3VwIG1lbWJlciByZXNwb25zaWJsZSBmb3IgdGhlIHRhYmxlIG1pc3dyaXR0ZW4gdGhlIHdvcmQgJ2NhcGFjaXRlaXQnKGNhcGFjaXR5KSBPZiBjb3Vyc2UsIHdlIHdhbnQgdG8gZml4IHRoaXMgYnVnIGFuZCBtYWtlIHN1cmUgaXQgZ2V0cyBjb3JyZWN0IGV2ZXJ5d2hlcmUuIFNpbmNlIGJlem9la2Vyc19jYXBpY2l0ZWl0ICh2aXNpdG9yX2NhcGFjaXR5KSBpcyBhIGNvbHVtbiwgaXQgY2FuIGVhc2lseSBiZSByZW5hbWVkIHdpdGggdGhlIGZ1bmN0aW9uIGByZW5hbWVgLgpgYGB7cn0KbG9jYXRpZSA8LWxvY2F0aWUgJT4lCiAgcmVuYW1lKGJlem9la2Vyc19jYXBhY2l0ZWl0ID0gYmV6b2VrZXJzX2NhcGljaXRlaXQpCmBgYApUbyBzZWUgaWYgdGhlIGNoYW5nZSBpcyBjb3JyZWN0OgpgYGB7cn0KY29sbmFtZXMobG9jYXRpZSkKYGBgCgojIyAzLiBFREEKCiMjIyAzLjEgV2hhdCBpcyB0aGUgdmlzaXRvciBjYXBhY2l0eSBwZXIgbG9jYXRpb24/ClRoZSBmaWd1cmUgYmVsb3cgc2hvd3MgYWxsIGxvY2F0aW9ucyB3aXRoIHRoZSBjb3JyZXNwb25kaW5nIHZpc2l0b3IgY2FwYWNpdHkuIFRoZXNlIGFyZSBhIHRvdGFsIG9mIDE1IGxvY2F0aW9ucywgZWFjaCBvZiB3aGljaCBoYXMgaXRzIGFyZWFzLiBXaGF0IGlzIHN0cmlraW5nIGlzIHRoYXQgdGhlIHRvcCAzIGxvY2F0aW9ucyB3aXRoIHRoZSBtb3N0IGNhcGFjaXR5IHRha2UgdXAgNTkuMDElIG9mIHRoZSB0b3RhbCBjYXBhY2l0eSBhbmQgdGhlIDUgbG9jYXRpb25zIHdpdGggdGhlIGxlYXN0IGNhcGFjaXR5IG9ubHkgdGFrZSB1cCA2LjgzJS4gQnkgY2hhbmNlLCBpdCBkb2VzIG5vdCBtZWFuIHRoYXQgbG9jYXRpb25zIHdpdGggbW9yZSBhcmVhcyBjYW4gaGF2ZSBtb3JlIHZpc2l0b3JzLiBUaGUgbG9jYXRpb24gd2l0aCB0aGUgbW9zdCBhcmVhcyBpcyB0aGUgQmVyZW5kb25jayBpbiBXaWpjaGVuLCB3aGVyZSB0aGUgYW5udWFsIEVtcG9yaXVtIGZlc3RpdmFsIGlzIGhlbGQuIFRoZXNlIGxvY2F0aW9ucyBoYXZlIG5vIGZld2VyIHRoYW4gMTcgYXJlYXMgYW5kIHRoZXkgb25seSB0YWtlIHVwIDYuMjElIG9mIHRoZSB0b3RhbCBjYXBhY2l0eS4gVGhpcyBzdWJzdGFudGlhdGlvbiBoYXMgYmVlbiB2YWxpZGF0ZWQgd2l0aCBjYWxjdWxhdGlvbiBmdW5jdGlvbnMgaW4gRXhjZWwgYW5kIGlzIGFsc28gc3RvcmVkIHRoZXJlLgpgYGB7cn0KbG9jYXRpZSAlPiUKICBtdXRhdGUobmFhbSA9IGZjdF9yZW9yZGVyKG5hYW0sIGJlem9la2Vyc19jYXBhY2l0ZWl0KSkgJT4lICMgTXV0YXRlIGZvciB0aGUgbmV3IGNvbHVtbiB3aXRoIHRoZSBjb3JyZWN0IGZhY3RvciBvcmRlcgogIGdncGxvdCggYWVzKHg9bmFhbSwgeT1iZXpvZWtlcnNfY2FwYWNpdGVpdCkpICsgIyBGaXJzdCAyIGxheWVycyBvZiBhIHBsb3Q6IERhdGEgKyBBZXN0aGV0aWNzCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGZpbGw9IiNmNjgwNjAiLCBhbHBoYT0uNywgd2lkdGg9LjYpICsgIyAzcmQgbGF5ZXIgb2YgdGhlIHBsb3QsIHN0YXQ9J2lkZW50aXR5JyBiZWNhdXNlIEkgaGF2ZSB2YWx1ZXMgZm9yIHgmeQogICAgY29vcmRfZmxpcCgpICsgIyBTd2l0Y2hpbmcgYXhpcwogICAgeGxhYignTG9jYXRpb24nKSArCiAgICB5bGFiKCdWaXNpdG9yIGNhcGFjaXR5JykrCiAgICBnZ3RpdGxlKCdWaXNpdG9yIGNhcGFjaXR5IHBlciBsb2NhdGlvbicpKwogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTUpCmBgYAoKIyMjIDMuMiBXaGF0IGFyZSB0aGUgc2FsZXMgcGVyIG1vbnRoPwpCZWxvdyB5b3UgY2FuIHNlZSB0aGUgc2FsZXMgcGVyIG1vbnRoIGRpdmlkZWQgb3ZlciAyMDE4LCAyMDE5IGFuZCAyMDIxLiBXaGF0IGNhbiBiZSBzZWVuIGluIHRoaXMgZmlndXJlIGlzIHRoYXQgdGhlcmUgYXJlIHllYXJzIHdoZXJlIHNvbWUgbW9udGhzIGhhdmUgbm8gc2FsZXMuIEZvciAyMDE4IHRoaXMgd2lsbCBiZSBBcHJpbCBhbmQgZm9yIDIwMTkgYW5kIDIwMjEgaXQgd2lsbCBib3RoIGJlIE1hcmNoLiBpbiAyMDE4IHRvdGFsIHNhbGVzIHdlcmUg4oKsIDQ5MSwwNDMuMDAgY29tcGFyZWQgdG8gMjAxOSBhbmQgMjAyMSwgd2hlcmUgaXQgd2FzIGJvdGgg4oKsIDExMywxMDQuMDAuIEF2ZXJhZ2Ugc2FsZXMgaW4gMjAxOCB3ZXJlIOKCrCA2MSwzODEuMjUsIGluIDIwMTkgYW5kIDIwMjEgaXQgd2FzIGJvdGgg4oKsIDE0LDEzOC44OC4gVGhlc2UgY2FsY3VsYXRpb25zIGhhdmUgYmVlbiB2YWxpZGF0ZWQgd2l0aCBFeGNlbCdzIGNhbGN1bGF0aW9uIGZ1bmN0aW9ucy4KCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9J2FmemV0J30KU0VMRUNUIHByb2R1Y3QsIFJPVU5EKHByaWpzLDIpIEFTIHByaWpzLCBhYW50YWwsIGxldmVyYW5jaWVyLCBkYXR1bSAvKiBSb3VuZCBvZmYgdG8gMiBkZWNpbWFsIHBsYWNlcyBhdCBSb3VuZCBiZWNhdXNlIGl0IGlzIGEgcHJpY2UgKi8KRlJPTSBpbmtvb3AgaQpKT0lOIGlua29vcF9mZXN0aXZhbCBpbmsKT04gaW5rLmlua29vcF9pZCA9IGkuaW5rb29wX2lkCkpPSU4gZmVzdGl2YWwgZgpPTiBpbmsuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCmBgYAoKYGBge3J9CmFmemV0JGFhbnRhbFszMV0gPC0gMzAwICMgQ2hhbmdlIHF1YW50aXR5IGJlY2F1c2UgdGhpcyBlcnJvciBpcyBpbiB0aGUgZGF0YWJhc2UKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTJ9CmFmemV0ICU+JQogIG11dGF0ZShqYWFyID0geWVhcihkYXR1bSksIG1hYW5kPSBtb250aChkYXR1bSksIGFmemV0X3ByaWpzID0gYWFudGFsICogcHJpanMpICU+JSAjIENyZWF0ZSBuZXcgY29sdW1ucyBmb3IgdGhlIHllYXIsIG1vbnRoIGFuZCBzYWxlcwogIGdyb3VwX2J5KGphYXIsIG1hYW5kKSAlPiUgIyBHcm91cCBieSB5ZWFyIGFuZCBtb250aAogIHN1bW1hcmlzZShhZnpldCA9IHN1bShhZnpldF9wcmlqcykpICU+JSAjIE1pbmltaXppbmcgYWxsIHJvd3MgYnkgbWVhbnMgb2YgYW4gYWNjdW11bGF0ZWQgc2FsZXMgcGVyIHllYXIgYW5kIG1vbnRoCgpnZ3Bsb3QoYWVzKG1hYW5kLCBhZnpldCwgZmlsbD1mYWN0b3IobWFhbmQpKSkgKyBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKyBmYWNldF9ncmlkKH4gamFhcikgKyAjQXMgY29sb3IgSSB1c2UgdGhlIGZhY3RvciBsZXZlbHMgb2YgbW9udGgsIHN0YXQgPSAnaWRlbnRpdHknIGJlY2F1c2UgSSB1c2UgdmFsdWVzIGZvciB4ICYgeSwgdGhlbiBmYWNldF9ncmlkIHRvIGRpc3RyaWJ1dGUgdGhlIHZpc3VhbGl6YXRpb25zIG92ZXIgdGhlIHllYXJzLgogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsMjAwMDAwLDEwMDAwKSkgKyAjIHkgYXhpcyByZXNldAogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDMsOSwxKSkgKyAjIHJlc2V0IGF4aXMKICBnZ3RpdGxlKCdTYWxlcyBwZXIgbW9udGggZGl2aWRlZCBvdmVyIDIwMTgsIDIwMTkgJiAyMDIxJykgKwogIHhsYWIoJ01vbnRoJykgKwogIHlsYWIoJ1NhbGVzIGluIOKCrCcpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpgYGB7cn0KYWZ6ZXRfb3V0cHV0IDwtIGFmemV0ICU+JSAjIGNyZWF0ZSBhbiBvYmplY3QgZnJvbSB0aGlzIGRhdGEgdG8gbGF0ZXIgdmFsaWRhdGUgaXQgaW4gZXhjZWwKICBtdXRhdGUoamFhciA9IHllYXIoZGF0dW0pLCBtYWFuZD0gbW9udGgoZGF0dW0pLCBhZnpldF9wcmlqcyA9IGFhbnRhbCAqIHByaWpzKQpgYGAKCmBgYHtyfQpleHBvcnQoYWZ6ZXRfb3V0cHV0LCAnYWZ6ZXQueGxzeCcpICMgRXhwb3J0CmBgYAoKIyMjIDMuMyBXaGljaCBGZXN0aXZhbHMgaGF2ZSBhIHNwb25zb3Igd2hvIHNwb25zb3JzIGFuIGl0ZW0gd2l0aCBhIHZhbHVlIGFib3ZlIOKCrCA1NTAwPwpUaGUgZmlndXJlIGJlbG93IHNob3dzIHdoaWNoIGZlc3RpdmFscyBoYXZlIHNwb25zb3JlZCBhbiBpdGVtIHdpdGggYSB2YWx1ZSBhYm92ZSDigqwgNTUwMC4gV2hhdCBpcyBvYnZpb3VzIGlzIHRoYXQgdGhlIDIgZmVzdGl2YWxzIHRoYXQgaGF2ZSB0aGUgaGlnaGVzdCB2YWx1ZSBoYXZlIGJvdGggYmVlbiBnaXZlbiBhIGJ1ZGdldCB0aGF0IHRvZ2V0aGVyIGlzIDQ5LjglIG9mIHRoZSB0b3RhbCBzcG9uc29yZWQgdmFsdWVzIGFib3ZlIOKCrCA1NTAwLiBUaGUgNSBmZXN0aXZhbHMgd2l0aCB0aGUgbG93ZXN0IHNwb25zb3JlZCB2YWx1ZXMgYWNjb3VudCBmb3Igb25seSAxNS4zNCUgb2YgdGhlIHRvdGFsIHNwb25zb3JlZCBhbW91bnQuIFRoZXNlIGNhbGN1bGF0aW9ucyBoYXZlIGJlZW4gdmFsaWRhdGVkIHdpdGggZXhjZWwuCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9J3Nwb25zb3JfbGlqc3RqZSd9Ci8qIFVzZSBTUUwgcXVlcnkgYW5kIHNhdmUgdGhpcyBvdXRwdXQgdG8gdXNlIGluIFIuIElubGluZSB2aWV3IGFzIHJlc3VsdCBzZXQgYW5kIGFkZCBhIG5ldyBjb2x1bW4gZm9yIHllYXIuIFRoZW4gZmlsdGVyIGZvciB2YWx1ZXMgYWJvdmUgNSw1MDAgZXVyb3MgYW5kIHZhbHVlcyB0aGF0IGFyZSBub3QgbnVsbC4gKi8KU0VMRUNUIGZlc3RpdmFsX25hYW0sIHNwb25zb3JfbmFhbSwgaXRlbSwgd2FhcmRlLCBqYWFyCkZST00gIAooCiAgICBTRUxFQ1QgZi5uYWFtIEFTIGZlc3RpdmFsX25hYW0sIGl0ZW0sIHMubmFhbSBBUyBzcG9uc29yX25hYW0sIHdhYXJkZSwgc3RyZnRpbWUoJyVZJyxkYXR1bSkgYXMgImphYXIiCiAgICBGUk9NIGZlc3RpdmFsIGYKICAgIEpPSU4gYmVzY2hpa2JhcmVfbWlkZGVsZW4gYm0KICAgIE9OIGJtLmZlc3RpdmFsX2lkID0gZi5mZXN0aXZhbF9pZAogICAgSk9JTiBzcG9uc29yZW4gcwogICAgT04gcy5zcG9uc29yX2lkID0gYm0uc3BvbnNvcl9pZAopdApXSEVSRSB3YWFyZGUgPiA1NTAwIEFORCB3YWFyZGUgSVMgTk9UIE5VTEwKQU5EIGphYXIgPSAnMjAxOScKT1JERVIgQlkgd2FhcmRlCmBgYAoKYGBge3IgZmlnLndpZHRoPTE1fQpvcHRpb25zKHNjaXBlbj05OTkpICMgUmVtb3ZlIG1hdGggbm90YXRpb24Kc3BvbnNvcl9saWpzdGplPC0gZGF0YS5mcmFtZShzcG9uc29yX2xpanN0amUpWzE6MTAsXSAjIFZhbGlkYXRlIG15IFNRTCBjaHVuay4gVGhlIG91dHB1dCBpbmRpY2F0ZWQgMTAgaXRlbXMgYWJvdmUgNTAwMCBldXJvIGFuZCAzIG51bGwgdmFsdWVzLCB3aXRoIHRoaXMgSSBzZWxlY3Qgb25seSB0aG9zZSAxMCBpdGVtcyB0aGF0IGhhdmUgYSB2YWx1ZSBhbmQgdGhvc2UgMyBvdGhlcnMgYXJlIGRyb3BwZWQuCiAgc3BvbnNvcl9saWpzdGplICU+JQogIG11dGF0ZShmZXN0aXZhbF9uYWFtID0gZmN0X3Jlb3JkZXIoZmVzdGl2YWxfbmFhbSwgd2FhcmRlKSkgJT4lICMgdXNlIG9mIGZvcmNhdHMgdG8gcmVhcnJhbmdlIHRoZSBmYWN0b3IgbGV2ZWxzIGJhc2VkIG9uIHRoZSB2YWx1ZXMgdGhleSBoYXZlIHNvIHRoYXQgdGhleSBnbyBmcm9tIGhpZ2ggdG8gbG93CiAgZ2dwbG90KGFlcyhmZXN0aXZhbF9uYWFtLCB3YWFyZGUsIGZpbGw9aXRlbSkpICsgICMgYXMgY29sb3IgSSB3YW50IHRvIHNob3cgdGhlIGl0ZW0gYmVpbmcgc3BvbnNvcmVkCiAgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknLCB3aWR0aCA9IC42KSArICMgZm9yIHRoZSAyIHZhbHVlcwogIGNvb3JkX2ZsaXAoKSArICMgU3dpdGNoaW5nIHRoZSB4ICYgeSBheGlzCiAgZ2d0aXRsZSgnRmVzdGl2YWxzIGluIDIwMTkgdGhhdCB3ZXJlIHNwb25zb3JlZCAxIGl0ZW0gd2l0aCBhIHZhbHVlIGFib3ZlIOKCrCA1NTAwJykgKwogIHhsYWIoJ0Zlc3RpdmFsIG5hbWUnKSArCiAgeWxhYignVmFsdWUgaXRlbSBpbiDigqwnKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQodGl0bGU9Ik5hbWUgSXRlbSIpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwxNTAwMDAsMjAwMDApKSAjIHJlYXJyYW5nZSB5IGF4aXMKYGBgCmBgYHtyfQpleHBvcnQoc3BvbnNvcl9saWpzdGplLCAnc3BvbnNvcnMueGxzeCcpICMgZXhwb3J0IGZvciB2YWxpZGF0aW9uCmBgYAoKCiMjIyAzLjQgV2hpY2ggRmVzdGl2YWxzIGhhdmUgbW9yZSB0aGFuIDMgcGFydG5lcnM/CkZvciB0aGlzIHF1ZXJ5LCBJIGFtIHVzaW5nIGEgQ1RFIGFzIGEgcmVzdWx0c2V0LiBJIGhlcmVieSBzaG93IHdoaWNoIGZlc3RpdmFscyBoYXZlIG1vcmUgdGhhbiAzIHBhcnRuZXJzLiBJbiB0aGUgQ1RFIEkgc2VsZWN0IHRoZSBmZXN0aXZhbHMgYW5kIGNvdW50IHRoZSBudW1iZXIgb2YgcGFydG5lcnMgdGhleSBoYXZlLiBJIGhlcmVieSBqb2luIG9uIDIgdGFibGVzOiBwYXJ0bmVycyBhbmQgdGhlIGludGVybWVkaWF0ZSB0YWJsZSBmZXN0aXZhbF9wYXJ0bmVycy4gVGhlbiBJIGdyb3VwIGJ5IGZlc3RpdmFsIG5hbWUuIEluIHRoZSBxdWVyeSwgSSBzZWxlY3QgYWxsIGZlc3RpdmFscyB0aGF0IGhhdmUgbW9yZSB0aGFuIDMgcGFydG5lcnMuIEkgdmFsaWRhdGVkIHRoaXMgYnkgbG9va2luZyBpbiB0aGUgZGF0YWJhc2Ugd2hpY2ggZmVzdGl2YWxzIGhhdmUgd2hpY2ggcGFydG5lcnMuCmBgYHtzcWwgY29ubmVjdGlvbj1jb259CldJVEggZmVzdGl2YWxfcGFydG5lcnNfY3RlCkFTCigKU0VMRUNUIGYubmFhbSBBUyBmZXN0aXZhbF9uYWFtLCBDT1VOVChwLm5hYW0pIEFTIGFhbnRhbF9wYXJ0bmVycyAvKiBQYXJ0bmVycyBjb3VudCBwZXIgZmVzdGl2YWwgKi8KRlJPTSBmZXN0aXZhbCBmCkpPSU4gZmVzdGl2YWxfcGFydG5lcnMgZnAKT04gZnAuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCkpPSU4gcGFydG5lcnMgcCAKT04gcC5wYXJ0bmVyX2lkID0gZnAucGFydG5lcl9pZApHUk9VUCBCWSBmLm5hYW0KKQoKU0VMRUNUIGZlc3RpdmFsX25hYW0sIGFhbnRhbF9wYXJ0bmVycwpGUk9NIGZlc3RpdmFsX3BhcnRuZXJzX2N0ZQpXSEVSRSBhYW50YWxfcGFydG5lcnMgPiAzIC8qIFNlbGVjdCBmZXN0aXZhbHMgdGhhdCBoYXZlIG1vcmUgdGhhbiAzIHBhcnRuZXJzLiAqLwoKYGBgCgoKIyMjIDMuNSBXaGljaCBwdXJjaGFzaW5nIHN1cHBsaWVyIHN1cHBsaWVzIHRoZSBtb3N0IHByb2R1Y3RzPwpJbiB0aGlzIHF1ZXJ5LCBJIHNob3cgd2hpY2ggc3VwcGxpZXIgZGVsaXZlcnMgdGhlIG1vc3QgcHJvZHVjdHMgaW4gbnVtYmVycyBhbmQgd2hhdCBwZXJjZW50YWdlIHRoaXMgaXMgb2YgdGhlIHRvdGFsIGRlbGl2ZXJpZXMuIEZpcnN0IG9mIGFsbCwgSSBhZGQgdXAgdGhlIHByb2R1Y3RzIGJ5IGdyb3VwaW5nIHRoZW0gYnkgdGhlIHN1cHBsaWVyIG5hbWUsIHRoZW4gSSBjb3VudCBhbGwgcHJvZHVjdHMgYW5kIGRpdmlkZSB0aGVtIGJ5IGFsbCB0aGUgcHJvZHVjdHMgdGhhdCBhcmUgaW4gdGhlIHB1cmNoYXNlIHRhYmxlLiBJIHJvdW5kIHRoaXMgdG8gMiBkZWNpbWFsIHBsYWNlcy4gQmVsb3cgeW91IGNhbiBzZWUgdGhhdCB0aGUgTWFrcm8gc3VwcGxpZXMgbm8gbGVzcyB0aGFuIDMzLjMzJSBvZiBhbGwgcHJvZHVjdHMgYW5kIHRoZSBudW1iZXJzIDIgYW5kIDMgYXJlIGFsc28gZ29vZCBmb3IgMzMuMzMlIGluIHRvdGFsLiBUaGlzIG1lYW5zIHRoYXQgb25seSB0aGUgdG9wIDMgc3VwcGxpZXJzIGFscmVhZHkgc3VwcGx5IDY2LjY2JSBvZiBhbGwgcHJvZHVjdHMuIFRoZXNlIGNhbGN1bGF0aW9ucyBoYXZlIGJlZW4gdmFsaWRhdGVkIGluIGV4Y2VsLgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQpTRUxFQ1QgbGV2ZXJhbmNpZXIsQ09VTlQocHJvZHVjdCkgQVMgYWFudGFsLCBST1VORCgxMDAuMCAqIENPVU5UKCopIC8gKFNFTEVDVCBDT1VOVCgqKSBGUk9NIGlua29vcCksMikgQVMgcGVyY2VudGFnZSAvKiBjYWxjdWxhdGUgcGVyY2VudGFnZSAqLwpGUk9NIGlua29vcApHUk9VUCBCWSBsZXZlcmFuY2llcgpPUkRFUiBCWSBwZXJjZW50YWdlIERFU0MKYGBgCgojIyMgMy42IFRoZSByYXRpbyBvZiBzZXggb2YgdmlzaXRvcnMKQmVsb3cgeW91IGNhbiBzZWUgdGhlIGdlbmRlciByYXRpbyBvZiB0aGUgZmVzdGl2YWwgdmlzaXRvcnMuIDUyJSBvZiB0aGUgZmVzdGl2YWwgdmlzaXRvcnMgYXJlIG1hbGUgd2hpbGUgNDglIG9mIHRoZSB2aXNpdG9ycyBhcmUgZmVtYWxlKHZyb3V3KS4gRm9yIHRoaXMgY2h1bmsgSSBkaWQgdGhlIGZvbGxvd2luZzogZnJvbSB0aGUgdmlzaXRvciB0YWJsZSwgSSBzdGFydGVkIGdyb3VwaW5nIGJ5IGdlbmRlci4gVGhlbiBJIHN0YXJ0ZWQgdG8gbWluaW1pemUgYWxsIHJvd3MgYnkgdXNpbmcgc3VtbWFyaXNlIGFuZCBjb3VudGluZyB0aGUgcm93cyBmb3IgZWFjaCBnZW5kZXIuIFRoZW4gSSBzdGFydGVkIHBsb3R0aW5nIGV2ZXJ5dGhpbmcuIEkgd2FudCB0byBwdXQgdGhlIHBlcmNlbnRhZ2VzIG9uIHRoZSBZLWF4aXMgYW5kIEkgd2FudCB0byB1c2UgdGhlIGxldmVscyBvZiB0aGUgZmFjdG9yIGFzIGNvbG91ci4gVGhpcyBjaHVuayBoYXMgYmVlbiB2YWxpZGF0ZWQgdXNpbmcgYW4gU1FMIHF1ZXJ5IHRoYXQgZ2l2ZXMgdGhlIHNhbWUgcmVzdWx0LgpgYGB7cn0KYmV6b2VrZXIgJT4lCiAgZ3JvdXBfYnkoZ2VzbGFjaHQpICU+JSAjIGdyb3VwIGJ5CiAgc3VtbWFyaXNlKGFhbnRhbCA9IG4oKSkgJT4lICMgY291bnQKICBtdXRhdGUocHJvY2VudCA9IHJvdW5kKDEwMCAqIGFhbnRhbCAvIHN1bShhYW50YWwpLCAxKSkgJT4lICMgY3JlYXRlIG5ldyBjb2x1bW4KZ2dwbG90KGFlcyh4ID0gJycsIHkgPSBhYW50YWwgLGZpbGwgPSBmYWN0b3IoZ2VzbGFjaHQpKSkgKyAjIHBsb3R0aW5nCiAgZ2VvbV9iYXIod2lkdGggPSAxLCBzdGF0PSdpZGVudGl0eScpICsgIyBzdGF0ID0gJ2lkZW50aXR5JyBiZWNhdXNlIHggYW5kIHkgYXJlIHVzZWQKICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKyAKICB0aGVtZV92b2lkKCkgKyAjIHJlbW92ZSBiYWNrZ3JvdW5kIGxpbmVzCiAgZ2d0aXRsZSgnVGhlIHJhdGlvIG9mIHNleCBvZiB2aXNpdG9ycycpICsKICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQodGl0bGU9IlNleCIpKSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMDBjY2ZmIiwgIiNmZjAwMDAiKSkgKyAjIGNvbG9ycwogIGdlb21fbGFiZWxfcmVwZWwoYWVzKGxhYmVsID0gcHJvY2VudCksIHNpemUgPSA1LCBzaG93LmxlZ2VuZCA9IEYpIApgYGAKClRoZSB2YWxpZGF0aW9uOgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQpzZWxlY3QgZ2VzbGFjaHQsICBST1VORChDT1VOVCgqKSAqMTAwIC8gKHNlbGVjdCBjb3VudCgqKSBGUk9NIGJlem9la2VyKSwyKSBBUyBwZXJjZW50YWdlCkZST20gYmV6b2VrZXIKZ3JvdXAgYnkgZ2VzbGFjaHQKYGBgCgojIyMgMy43IFdoaWNoIHZpc2l0b3JzIHdlbnQgdG8gTXlzdGVyeWxhbmQgaW4gMjAxOCBhbmQgaGF2ZSBhbHNvIGJlZW4gdG8gTXlzdGVyeWxhbmQgaW4gc3Vic2VxdWVudCB5ZWFycz8KQmVsb3cgeW91IGNhbiBzZWUgd2hpY2ggZmVzdGl2YWwgdmlzaXRvcnMgaGF2ZSBiZWVuIHRvIE15c3RlcnlsYW5kIGluIDIwMTggYW5kIHRoZW4gcmV0dXJuZWQgaW4gY29uc2VjdXRpdmUgeWVhcnMuIFRoZSBmaXJzdCBxdWVyeSBjb25zaXN0cyBvZiBhbiBpbmxpbmUgdmlldyBhbmQgMiBzdWJxdWVyaWVzLCB0aGUgbGFzdCB2YWxpZGF0aW9uIHF1ZXJ5IGNvbnNpc3RzIG9mIGEgQ1RFIGFuZCBhIHF1ZXJ5LiBIZXJlIEkgdXNlIGEgQ0FTRSBzdGF0ZW1lbnQgYXMgYW4gSW5saW5lIElGIHRvIHNlbGVjdCB2YWx1ZXMgdGhhdCBtZWV0IGEgY2VydGFpbiBjb25kaXRpb24uCmBgYHtzcWwgY29ubmVjdGlvbj1jb259ClNFTEVDVCB2b29ybmFhbSwgYWNodGVybmFhbSwgYmV6b2VrZXJfaWQKRlJPTQooCiAgU0VMRUNUICBmLm5hYW0gQVMgZmVzdGl2YWxfbmFhbSwgYi52b29ybmFhbSBBUyB2b29ybmFhbSwgYi5hY2h0ZXJuYWFtIEFTIGFjaHRlcm5hYW0sIGIuYmV6b2VrZXJfaWQsIHN0cmZ0aW1lKCclWScsZGF0dW0pIGFzICJqYWFyIgogIEZST00gZmVzdGl2YWwgZgogICBKT0lOIGFhbm1lbGRpbmdlbiBhCiAgT04gYS5mZXN0aXZhbF9pZCA9IGYuZmVzdGl2YWxfaWQKICAgSk9JTiBiZXpvZWtlciBiCiAgT04gYi5iZXpvZWtlcl9pZCA9IGEuYmV6b2VrZXJfaWQKKXQKV0hFUkUgamFhciA9ICcyMDE4JyBBTkQgZmVzdGl2YWxfbmFhbSA9ICdNeXN0ZXJ5bGFuZCcKQU5EIGJlem9la2VyX2lkIGluIC8qIFN1YnF1ZXJ5ICovCigKCVNFTEVDVCBiZXpvZWtlcl9pZAoJRlJPTQooCiAgLyogSW5saW5lIHZpZXcgKi8KCVNFTEVDVCAgZi5uYWFtIEFTIGZlc3RpdmFsX25hYW0sIGIudm9vcm5hYW0gQVMgdm9vcm5hYW0sIGIuYWNodGVybmFhbSBBUyBhY2h0ZXJuYWFtLCBiLmJlem9la2VyX2lkLCBzdHJmdGltZSgnJVknLGRhdHVtKSBhcyAiamFhciIKCUZST00gZmVzdGl2YWwgZgoJIEpPSU4gYWFubWVsZGluZ2VuIGEKCU9OIGEuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCgkgSk9JTiBiZXpvZWtlciBiCglPTiBiLmJlem9la2VyX2lkID0gYS5iZXpvZWtlcl9pZAopdApXSEVSRSBqYWFyID0gJzIwMTknIEFORCBmZXN0aXZhbF9uYWFtID0gJ015c3RlcnlsYW5kJwopCkFORCBiZXpvZWtlcl9pZCBJTiAvKiBTdWJxdWVyeSAqLwooClNFTEVDVCBiZXpvZWtlcl9pZApGUk9NCigKICAvKiBJbmxpbmUgdmlldyAqLwogIFNFTEVDVCAgZi5uYWFtIEFTIGZlc3RpdmFsX25hYW0sIGIudm9vcm5hYW0gQVMgdm9vcm5hYW0sIGIuYWNodGVybmFhbSBBUyBhY2h0ZXJuYWFtLCBiLmJlem9la2VyX2lkLCBzdHJmdGltZSgnJVknLGRhdHVtKSBhcyAiamFhciIKICBGUk9NIGZlc3RpdmFsIGYKICAgSk9JTiBhYW5tZWxkaW5nZW4gYQogIE9OIGEuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCiAgIEpPSU4gYmV6b2VrZXIgYgogIE9OIGIuYmV6b2VrZXJfaWQgPSBhLmJlem9la2VyX2lkCil0CldIRVJFIGphYXIgPSAnMjAyMScgQU5EIGZlc3RpdmFsX25hYW0gPSAnTXlzdGVyeWxhbmQnCikKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQpXSVRIIHJlc3VsdF9zZXQgQVMgLyogQ1RFIGFzIHJlc3VsdCBzZXQgKi8KKAoJU0VMRUNUIERJU1RJTkNUIGYubmFhbSBBUyBmZXN0aXZhbF9uYWFtLCBiLnZvb3JuYWFtIEFTIHZvb3JuYWFtLCBiLmFjaHRlcm5hYW0gQVMgYWNodGVybmFhbSwgYi5iZXpvZWtlcl9pZCwgc3RyZnRpbWUoJyVZJyxkYXR1bSkgYXMgImphYXIiCgkgIEZST00gZmVzdGl2YWwgZgoJICAgSk9JTiBhYW5tZWxkaW5nZW4gYQoJICBPTiBhLmZlc3RpdmFsX2lkID0gZi5mZXN0aXZhbF9pZAoJICAgSk9JTiBiZXpvZWtlciBiCgkgIE9OIGIuYmV6b2VrZXJfaWQgPSBhLmJlem9la2VyX2lkCiAgKQogIAogIFNFTEVDVCBESVNUSU5DVCBiZXpvZWtlcl9pZCwgdm9vcm5hYW0sIGFjaHRlcm5hYW0sIAogIENBU0UgCglXSEVOIGZlc3RpdmFsX25hYW0gPSdNeXN0ZXJ5bGFuZCcgQU5EIGphYXIgPSAnMjAxOCcgVEhFTiAnSmEnICAvKiBDYXNlIGFzIGEgcmVwbGFjZW1lbnQgZm9yIElubGluZSBJRiAqLwoJV0hFTiBqYWFyID0gJzIwMTknIEFORCBqYWFyID0gJzIwMjEnIFRIRU4gJ0phJyAvKiBDb25zaXN0cyBvZiAzIG1hbmRhdG9yeSBwYXJ0czogQ0FTRSwgV0hFTiAmIEVORCwgRUxTRSBpcyBvcHRpb25hbCAqLwoJRUxTRSAnTmVlJyBFTkQgQVMgZ2V3ZWVzdAogIGZyb20gcmVzdWx0X3NldAogIE9SREVSIEJZIGdld2Vlc3QKYGBgCgojIyMgMy44IFdoYXQgaXMgdGhlIGFnZSBkaXN0cmlidXRpb24gb2YgdGhlIGZlc3RpdmFsIHZpc2l0b3JzPwpUaGUgZmlndXJlIGJlbG93IHNob3dzIHRoZSBhZ2UgZGlzdHJpYnV0aW9uIG9mIHRoZSBmZXN0aXZhbCB2aXNpdG9ycyBieSBnZW5kZXIuIFdoYXQgaXMgb2J2aW91cyBpcyB0aGF0IGZld2VyIDQwKyBtZW4gZ28gdG8gZmVzdGl2YWxzIGNvbXBhcmVkIHRvIDQwKyB3b21lbi4gVXAgdG8gdGhlIGFnZSBvZiAzMiwgbW9yZSBtZW4gdGhhbiB3b21lbiBhdHRlbmQgZmVzdGl2YWxzLCB3aGlsZSBtYW55IHlvdW5nIHdvbWVuICgxOCB0byAyMCkgYXR0ZW5kIGZlc3RpdmFscy4gVGhlIGF2ZXJhZ2UgYWdlIG9mIHRoZSBmZXN0aXZhbCB2aXNpdG9ycyBpcyAyNyB5ZWFycyBhbmQgdGhlIG1lZGlhbiBpcyAyMy4gVGhlIG9sZGVzdCB2aXNpdG9yIGlzIDUyIGZvciBtZW4gYW5kIDU0IGZvciB3b21lbi4gVGhlIHlvdW5nZXN0IHZpc2l0b3JzIG9mIGJvdGggbWVuIGFuZCB3b21lbiBhcmUgYm90aCAxOCB5ZWFycyBvbGQuCk1hbiA9IE1hbGUsIHZyb3V3ID0gRmVtYWxlCmBgYHtyIGZpZy53aWR0aD0xMn0KYmV6b2VrZXIkbGVlZnRpamQgPC0gYXMubnVtZXJpYyhkaWZmdGltZShTeXMuRGF0ZSgpLGJlem9la2VyJGdlYm9vcnRlX2RhdHVtLCB1bml0cyA9ICJ3ZWVrcyIpKS81Mi4yNSAjIGFnZSBmb3JtdWxhCmJlem9la2VyJGxlZWZ0aWpkIDwtIGZsb29yKGJlem9la2VyJGxlZWZ0aWpkKSAjIHRvIHJvdW5kIGRvd24KCmdncGxvdChiZXpvZWtlciwgYWVzKHg9bGVlZnRpamQsIGZpbGw9Z2VzbGFjaHQpKSArICMgcGxvdHRpbmcKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLGJpbndpZHRoPTIsIGFscGhhPTAuNikgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxOCw2MCwyKSkgKyAjIHJlYXJyYW5nZSB0aGUgYXhlcwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsMTUsMSkpICsgIyByZWFycmFuZ2UgdGhlIGF4ZXMKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW1lYW4obGVlZnRpamQpKSwgY29sb3I9InB1cnBsZSIsICMgTWVhbiAKICAgICAgICAgICAgIGxpbmV0eXBlPSJkYXNoZWQiKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1tZWRpYW4obGVlZnRpamQpKSwgY29sb3I9ImJsYWNrIiwgIyBNZWRpYW4KICAgICAgICAgICAgIGxpbmV0eXBlPSJkYXNoZWQiKSArCiAgbGFicyh0aXRsZT0iQWdlIGRpc3RyaWJ1dGlvbiBGZXN0aXZhbCB2aXNpdG9ycyIseD0iQWdlIiwgeSA9ICJDb3VudCIpKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMDBjY2ZmIiwgIiNmZjAwMDAiKSkgIyBjb2xvcnMKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTJ9CmdncGxvdChiZXpvZWtlciwgYWVzKHg9bGVlZnRpamQpKSArICMgcGxvdHRpbmcKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLGJpbndpZHRoPTIsIGFscGhhPTAuNikgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxOCw2MCwyKSkgKyAjIHJlYXJyYW5nZSB0aGUgYXhlcwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9c2VxKDAsMTUsMSkpICsgIyByZWFycmFuZ2UgdGhlIGF4ZXMKICBsYWJzKHRpdGxlPSJBZ2UgZGlzdHJpYnV0aW9uIEZlc3RpdmFsIHZpc2l0b3JzIGJ5IHNleCIseD0iQWdlIiwgeSA9ICJDb3VudCIpKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMDBjY2ZmIiwgIiNmZjAwMDAiKSkgKyBmYWNldF93cmFwKH4gZ2VzbGFjaHQpICMgc2VwYXJhdGUgdmlzdWFsaXphdGlvbiBiYXNlZCBvbiBzZXgKYGBgCgojIyMgMy45IFdoYXQgaXMgdGhlIGF2ZXJhZ2UgYWdlIHBlciBnZW5kZXIgb2YgdGhlIGZlc3RpdmFsIHZpc2l0b3JzIHdobyBhdHRlbmQgVGVjaG5vIGZlc3RpdmFscyBpbiAyMDE5PwpCZWxvdyBpcyB0aGUgYXZlcmFnZSBhZ2UgcGVyIGdlbmRlciBvZiB0aGUgZmVzdGl2YWwgdmlzaXRvcnMgd2hvIHdlbnQgdG8gVGVjaG5vIGZlc3RpdmFscyBpbiAyMDE5LiBUaGlzIHF1ZXJ5IGNvbnNpc3RzIG9mIDMgcGFydHM6IFRoZSBxdWVyeSwgaW5saW5lIHZpZXcgYXMgcmVzdWx0IHNldCBhbmQgYSBzdWJxdWVyeS4gRmlyc3Qgb2YgYWxsLCB1c2UgYSBjYXN0IHRvIHJvdW5kIHRoZSBhdmVyYWdlIGFnZSBkb3duIGJlY2F1c2UgU1FMaXRlIGhhcyBubyBmbG9vciBmdW5jdGlvbi4gVGhlbiBJIHVzZSBhbiBpbmxpbmUgdmlldyB3aGVyZSBJIHNlbGVjdCBhbGwgdW5pcXVlIHZpc2l0b3JzIHdobyB3ZW50IHRvIGZlc3RpdmFscyBpbiAyMDE5LiBJIGFsc28gY2FsY3VsYXRlIHRvIGNhbGN1bGF0ZSB0aGUgYWdlLiBJIGFsc28gY3JlYXRlIGEgc3VicXVlcnkgdGhhdCBzZXJ2ZXMgYXMgYSByZXN1bHQgc2V0IHdoZXJlIHZpc2l0b3JzIHdlbnQgdG8gdGVjaG5vIGZlc3RpdmFscy4gVGhpcywgY29tYmluZWQgd2l0aCB0aGUgcmVzdCwgcHJvdmlkZXMgYW4gb3ZlcnZpZXcgb2YgdGhlIGF2ZXJhZ2UgYWdlIHBlciBnZW5kZXIgb2YgdGhlIGZlc3RpdmFsIHZpc2l0b3JzIHdobyBnbyB0byB0ZWNobm8gZmVzdGl2YWxzLgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQovKiBDYXN0IGlzIHVzZWQgZm9yIGNvbnZlcnRpbmcgdG8gaW50IGRhdGEgdHlwZSB0byByb3VuZCBkb3duIGV2ZW50dWFsbHkgYmVjYXVzZSBTUUxpdGUgaGFzIG5vIGZsb29yIGZ1bmN0aW9uLiBJIHVzZSBhbiBpbmxpbmUgdmlldyBhcyBhIHJlc3VsdCBzZXQgaW4gd2hpY2ggSSBjYWxjdWxhdGUgc29tZW9uZSdzIGFnZSB3aXRoIGEgZm9ybXVsYS4gRmluYWxseSwgSSB1c2UgYSBzdWJxdWVyeSB0aGF0IGluZGljYXRlcyB3aGljaCB2aXNpdG9ycyB3ZW50IHRvIHRlY2hubyBmZXN0aXZhbHMuKi8KU0VMRUNUIGdlc2xhY2h0ICxjYXN0ICggQVZHKGxlZWZ0aWpkKSBhcyBpbnQgKSAtICggQVZHKGxlZWZ0aWpkKSA8IGNhc3QgKCBBVkcobGVlZnRpamQpIGFzIGludCApKSBBUyBnZW1pZGRlbGRlX2xlZWZ0aWpkIApGUk9NIAooCiAgU0VMRUNUIERJU1RJTkNUIGIuYmV6b2VrZXJfaWQsIHZvb3JuYWFtLCBhY2h0ZXJuYWFtLAogIChzdHJmdGltZSgnJVknLCAnbm93JykgLSBzdHJmdGltZSgnJVknLCBnZWJvb3J0ZV9kYXR1bSkpIC0gKHN0cmZ0aW1lKCclbS0lZCcsICdub3cnKSA8IHN0cmZ0aW1lKCclbS0lZCcsIGdlYm9vcnRlX2RhdHVtKSkgQVMgbGVlZnRpamQsICAgZ2VzbGFjaHQsIHN0cmZ0aW1lKCclWScsIGRhdHVtKSBBUyBqYWFyCiAgRlJPTSBiZXpvZWtlciBiCiAgSk9JTiBhYW5tZWxkaW5nZW4gYSBPTiBhLmJlem9la2VyX2lkID0gYi5iZXpvZWtlcl9pZAogIEpPSU4gZmVzdGl2YWwgZiBPTiBmLmZlc3RpdmFsX2lkID0gYS5mZXN0aXZhbF9pZAogIFdIRVJFIGphYXIgPSAnMjAxOScgQU5EIGIuYmV6b2VrZXJfaWQgSU4KICAoCiAgICAgU0VMRUNUIGIuYmV6b2VrZXJfaWQKICAgICBGUk9NIGZlc3RpdmFsIGYKICAgICBKT0lOIGFhbm1lbGRpbmdlbiBhIE9OIGEuZmVzdGl2YWxfaWQgPSBmLmZlc3RpdmFsX2lkCiAgICAgSk9JTiBiZXpvZWtlciBiIG9uIGIuYmV6b2VrZXJfaWQgPSBhLmJlem9la2VyX2lkCiAgICAgSk9JTiBmZXN0aXZhbF9jYXRlZ29yaWVfZmVzdGl2YWwgZmNmIE9OIGZjZi5mZXN0aXZhbF9pZCA9IGYuZmVzdGl2YWxfaWQKICAgICBKT0lOIGZlc3RpdmFsX2NhdGVnb3JpZSBmYyBPTiBmYy5jYXRlZ29yaWVfaWQgPSBmY2YuY2F0ZWdvcmllX2lkCiAgICAgV0hFUkUgZmMubmFhbSA9PSdUZWNobm8nCiAgKQopdApHUk9VUCBCWSBnZXNsYWNodApgYGAKCiMjIyAzLjEwIFdoYXQgaXMgdGhlIGF2ZXJhZ2UgYWdlIG9mIHRoZSBmZXN0aXZhbCB2aXNpdG9ycyB3aG8gd2VudCB0byBFbXBvcml1bSBpbiAyMDE5PwpCZWxvdyBpcyB0aGUgYXZlcmFnZSBhZ2Ugb2YgdGhlIGZlc3RpdmFsIHZpc2l0b3JzIHdobyB3ZW50IHRvIEVtcG9yaXVtIGluIDIwMTkuIEZyb20gdGhlIGZlc3RpdmFsIHRhYmxlIEkgam9pbiA0IG90aGVyIHRhYmxlczogYWFubWVsZGluZ2VuKHJlZ2lzdHJhdGlvbnMpLCBiZXpvZWtlcih2aXNpdG9yKSwgZmVzdGl2YWxfY2F0ZWdvcmllX2Zlc3RpdmFsKGJldHdlZW4gdGFibGUpICYgZmVzdGl2YWxfY2F0ZWdvcmllKGZlc3RpdmFsIGNhdGVnb3J5KS4gVGhlbiBJIGZpbHRlciBvbiB0aGUgZmVzdGl2YWwgbmFtZSBhbmQgeWVhciBvZiB0aGUgZmVzdGl2YWwuIFRoZW4gSSBncm91cCBieSBzZXggYW5kIG1pbmltaXplIGFsbCByb3dzIGJlY2F1c2UgSSB3YW50IHRvIGtub3cgdGhlIGF2ZXJhZ2UgYWdlIGJhc2VkIG9uIHNleC4KYGBge3J9CmZlc3RpdmFsICU+JQogIGlubmVyX2pvaW4oYWFubWVsZGluZ2VuLCBieT0nZmVzdGl2YWxfaWQnKSAlPiUKICBpbm5lcl9qb2luKGJlem9la2VyLCBieT0nYmV6b2VrZXJfaWQnKSAlPiUKICBpbm5lcl9qb2luKGZlc3RpdmFsX2NhdGVnb3JpZV9mZXN0aXZhbCwgYnk9J2Zlc3RpdmFsX2lkJykgJT4lCiAgaW5uZXJfam9pbihmZXN0aXZhbF9jYXRlZ29yaWUsIGJ5PSdjYXRlZ29yaWVfaWQnKSAlPiUKICBmaWx0ZXIobmFhbS54ID09ICdFbXBvcml1bScgJiB5ZWFyKGRhdHVtKSA9PScyMDE5JykgJT4lICMgZmlsdGVyaW5nCiAgZ3JvdXBfYnkoZ2VzbGFjaHQpICU+JSAjIGdyb3VwIGJ5IHNleAogIHN1bW1hcmlzZShnZW1pZGRlbGRlX2xlZWZ0aWpkID0gbWVhbihsZWVmdGlqZC55KSkgIyBhdmVyYWdlIGFnZQpgYGAKCiMjIyAzLjExIFdoYXQgaXMgdGhlIGxvd2VzdCwgYXZlcmFnZSBhbmQgZ3JlYXRlc3QgcmV2ZW51ZSBvZiB0aGUgcHJvZHVjdHMgZnJvbSB0aGUgc2FsZXMgdGFibGUgZm9yIGVhY2ggeWVhcj8KQmVsb3cgaXMgYW4gb3ZlcnZpZXcgb2YgdGhlIHJldmVudWUgZnJvbSB0aGUgc2FsZXMgdGFibGUgZm9yIGVhY2ggeWVhci4gRm9yIGVhY2ggeWVhciBJIHNob3cgdGhlIGxvd2VzdCwgYXZlcmFnZSBhbmQgaGlnaGVzdCB0dXJub3ZlciB0aGF0IHdhcyBhY2hpZXZlZCBpbiB0aGF0IHllYXIuIEZyb20gdGhlIHZlcmtvb3BfcHJvZHVjdGVuIChzYWxlc19wcm9kdWN0cykgdGFibGUgSSBqb2luIDIgb3RoZXIgdGFibGVzOiB0aGUgYmV0d2VlbiB0YWJsZSBhbmQgZmVzdGl2YWwuIFRoZW4gSSBjcmVhdGUgYSBuZXcgY29sdW1uIGZvciB0aGUgcmV2ZW51ZTogdGhlIHByaWNlIHRpbWVzIHRoZSBudW1iZXIgb2YgcGllY2VzIGlzIHRoZSByZXZlbnVlLiBUaGVuIEkgZ3JvdXAgYnkgdGhlIHllYXIgb2YgZWFjaCBkYXRlIGFuZCBJIG1pbmltaXplIHRoZSByb3dzIHdpdGggdGhlIGNvcnJlc3BvbmRpbmcgcmV2ZW51ZSBmb3IgZWFjaCB5ZWFyLgpgYGB7cn0KdmVya29vcF9wcm9kdWN0ZW4gJT4lCiAgaW5uZXJfam9pbih2ZXJrb29wX3Byb2R1Y3Rlbl9mZXN0aXZhbCwgYnk9J3Zlcmtvb3BfaWQnKSAlPiUgIyBqb2luIG1hdGNoaW5nIHZhbHVlcwogIGlubmVyX2pvaW4oZmVzdGl2YWwsIGJ5PSdmZXN0aXZhbF9pZCcpICU+JSAjIGpvaW4gbWF0Y2hpbmcgdmFsdWVzCiAgbXV0YXRlKG9temV0ID0gcHJpanMgKiBhYW50YWwpICAlPiUgIyBhZGQgbmV3IGNvbHVtbiBmb3Igc2FsZXMKICBncm91cF9ieSh5ZWFyKGRhdHVtKSkgJT4lICMgZ3JvdXAgYnkgeWVhcgogIHN1bW1hcmlzZShnZW1pZGRlbGRlX29temV0ID0gcm91bmQobWVhbihvbXpldCksMiksICMgbWluaW1pemUgb24gYXZlcmFnZSByZXZlbnVlCiAgICAgICAgICAgIGxhYWdzdGVfb216ZXQgPSByb3VuZChtaW4ob216ZXQpLDIpLCAjIG1pbmltaXplIG9uIGxvd2VzdCByZXZlbnVlCiAgICAgICAgICAgIGhvb2dzdGVfb216ZXQgPSByb3VuZChtYXgob216ZXQpLDIpKSAjIG1pbmltaXplIG9uIGhpZ2hlc3QgcmV2ZW51ZQpgYGAKCiMjIyAzLjEyIEhvdyBtYW55IHBvcCBmZXN0aXZhbHMgd2VyZSBvcmdhbml6ZWQgcGVyIHllYXI/CkJlbG93IGlzIGEgc21hbGwgb3ZlcnZpZXcgd2hlcmUgeW91IGNhbiBzZWUgaG93IG1hbnkgcG9wIGZlc3RpdmFscyBhcmUgaGVsZCBlYWNoIHllYXIuIEZpcnN0IG9mIGFsbCwgSSBzZWxlY3QgdGhlIGZlc3RpdmFsIHRhYmxlIGFuZCBqb2luIDQgb3RoZXIgdGFibGVzLCAyIGludGVybWVkaWF0ZSB0YWJsZXMgYW5kIHRoZSBmZXN0aXZhbF9jYXRlZ29yeSArIGxvY2F0aW9uIHRhYmxlLiBUaGVuIEkgZ3JvdXAgYnkgZWFjaCB5ZWFyIGFuZCBJIGZpbHRlciBieSB0aGUgbXVzaWMgZ2VucmUgYW5kIGZpbmFsbHkgSSBjb3VudCB0aGlzIHNvIHRoYXQgeW91IGNhbiBzZWUgaG93IG1hbnkgcG9wIGZlc3RpdmFscyB0aGVyZSBhcmUgZ2l2ZW4gZWFjaCB5ZWFyLgpgYGB7cn0KZmVzdGl2YWwgJT4lCiAgaW5uZXJfam9pbihmZXN0aXZhbF9jYXRlZ29yaWVfZmVzdGl2YWwsIGJ5PSdmZXN0aXZhbF9pZCcpICU+JSAjIGpvaW4gbWF0Y2hpbmcgdmFsdWVzCiAgaW5uZXJfam9pbihmZXN0aXZhbF9jYXRlZ29yaWUsIGJ5PSdjYXRlZ29yaWVfaWQnKSAlPiUgIyBqb2luIG1hdGNoaW5nIHZhbHVlcwogIGlubmVyX2pvaW4obG9jYXRpZV9mZXN0aXZhbCwgYnk9J2Zlc3RpdmFsX2lkJykgJT4lICMgam9pbiBtYXRjaGluZyB2YWx1ZXMKICBpbm5lcl9qb2luKGxvY2F0aWUsIGJ5PSdsb2NhdGllX2lkJykgJT4lICMgam9pbiBtYXRjaGluZyB2YWx1ZXMKICBncm91cF9ieSh5ZWFyKGRhdHVtLngpKSAlPiUgIyBncm91cCBieSB5ZWFyCiAgZmlsdGVyKGdlbnJlPT0nUG9wJykgJT4lICMgZmlsdGVyIG9uIHBvcAogIGNvdW50KGdlbnJlKSAjIGNvdW50IGdlbnJlcwpgYGAKCgoKCgoK