Text mining
Data retrieval
Now, let’s move forward to simple text analysis. First, we need to prepare the data! (as usual)
tokens <- tweets %>%
mutate(id = 1:nrow(tweets)) %>% # This creates a tweet id
select(id, text) %>% # Keeps only id and text of the tweet
unnest_tokens(word, text) # Creates tokens!
tokens
Let’s have a look at word frequencies.
tokens %>%
count(word, sort = TRUE)
This is polluted by small words. Let’s filter that (FIRST METHOD).
tokens %>% mutate(length = nchar(word))
Data frequencies
Now let’s omit the small words (smaller than 5 characters).
NOTE: all the thresholds below depend on the sample!
tokens %>%
mutate(length = nchar(word)) %>%
filter(length > 4) %>% # Keep words with length larger than 4
count(word, sort = TRUE) %>% # Count words
head(21) %>% # Keep only top 12 words
ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")

A better way to proceed is to remove “stop words” like “a”, “I”, “of”, “the”, etc (SECOND METHOD). Also, it would make sense to remove the search item and “https”.
data("stop_words")
tidy_tokens <- tokens %>%
anti_join(stop_words) # Remove unrelevant terms
tidy_tokens %>%
count(word, sort = TRUE) %>% # Count words
head(20) %>% # Keep only top 15 words
ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")

Problem: strange characters remain. We are going to remove them by converting the text to ASCII format and omit NA data.
tidy_tokens <- tokens %>%
anti_join(stop_words) %>% # Remove unrelevant
mutate(word = iconv(word, from = "UTF-8", to = "ASCII")) %>% # Put in latin format
na.omit() %>% # Remove missing
filter(nchar(word) > 1, # Remove small words
!(word %in% c("https", "t.co", search_term)) # search_term defined above
)
tidy_tokens %>%
count(word, sort = TRUE) %>% # Count words
head(20) %>% # Keep only top words
ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")

Perfect!
n-grams
See https://www.tidytextmining.com/ngrams.html
tweets %>%
mutate(id = 1:nrow(tweets)) %>% # This creates a tweet id
select(id, text, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
group_by(bigram) %>%
count(sort = T) %>%
head(20) %>%
ggplot(aes(y = reorder(bigram, n), x = n)) + geom_col()

Again: same issue with stop words! So we must remove them again. But it’s more complicated now. We can use the separate() function to help us.
tweets %>%
mutate(id = 1:nrow(tweets)) %>% # This creates a tweet id
select(id, text, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
mutate(bigram = iconv(bigram, from = "UTF-8", to = "ASCII")) %>%
na.omit() %>%
separate(bigram, c("word1", "word2"), sep = " ", remove = F) %>%
filter(!(word1 %in% c(stop_words$word, "https", search_term)),
!(word2 %in% c(stop_words$word, "https", search_term)),
bigram != search_term) %>%
group_by(bigram) %>%
count(sort = T) %>%
head(20) %>%
ggplot(aes(y = reorder(bigram, n), x = n)) + geom_col() + ylab("Bi-gram")

Sentiment
This section is inspired from: https://www.tidytextmining.com/sentiment.html
Sometimes, you may be asked in the process if you really want to download data (lexicons).
Just say yes in the console (type the correct answer: if not, you will be blocked/struck).
First, we need to load some sentiment lexicon. AFINN is one such sentiment database.
if(!require(textdata)){install.packages("textdata", repos = "https://cloud.r-project.org/")}
Loading required package: textdata
library(tidytext)
library(textdata)
afinn <- get_sentiments("afinn")
afinn
To create a nice visualization, we need to extract the time of the tweets.
tokens_time <- tweets %>%
mutate(id = 1:nrow(tweets)) %>% # This creates a tweet id
select(id, text, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(word, text) # Creates tokens!
tokens_time
We then use inner_join() to merge the two sets. This function removes the cases when a match does not occur.
library(lubridate)
Attaching package: ‘lubridate’
The following objects are masked from ‘package:base’:
date, intersect, setdiff, union
sentiment <- tokens_time %>%
inner_join(afinn) %>%
mutate(day = day(created_at),
hour = hour(created_at) / 24,
minute = minute(created_at) / 60 / 24,
time = day + hour + minute)
Joining, by = "word"
sentiment
We then compute the average sentiment, minute-by-minute.
Of course, average sentiment can be misleading. Indeed, if a text contains the terms “I’m not happy”, then only “happy” will be tagged, which is the opposite of the intended meaning.
sentiment %>%
group_by(time, day, hour, minute) %>%
summarise(avg_sentiment = mean(value)) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = avg_sentiment)) + geom_col() + geom_smooth()
`summarise()` regrouping output by 'time', 'day', 'hour' (override with `.groups` argument)

There are 24 bars per day, but the y-axis is not optimal…
What about emotions? The NRC lexicon categorizes emotions. Below, we order emotions. The most important impact is the dichotomy between positive & negative emotions.
```r
if(!require(sentimentr)){install.packages(c(\sentimentr\, \textcat\))}
library(sentimentr)
library(textcat)
<!-- rnb-source-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
We then create the merged dataset.
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuZW1vdGlvbnMgPC0gdG9rZW5zX3RpbWUgJT4lIFxuICBpbm5lcl9qb2luKG5yYykgJT4lICAgICAgICAgICAgICAgICAgIyBNZXJnZSBkYXRhIHdpdGggc2VudGltZW50XG4gIG11dGF0ZShkYXkgPSBkYXkoY3JlYXRlZF9hdCksXG4gICAgICAgICBob3VyID0gaG91cihjcmVhdGVkX2F0KS8yNCxcbiAgICAgICAgIG1pbnV0ZSA9IG1pbnV0ZShjcmVhdGVkX2F0KS8yNC82MCxcbiAgICAgICAgIHRpbWUgPSBkYXkgKyBob3VyICsgbWludXRlKSAgICMgQ3JlYXRlIGRheSBjb2x1bW5cbmBgYCJ9 -->
```r
emotions <- tokens_time %>%
inner_join(nrc) %>% # Merge data with sentiment
mutate(day = day(created_at),
hour = hour(created_at)/24,
minute = minute(created_at)/24/60,
time = day + hour + minute) # Create day column
Joining, by = "word"
emotions # Show the result
The merging has reduced the size of the dataset, but there still remains enough to pursue the study.
Finally, we move to the pivot-table that counts emotions for each day.
g <- emotions %>%
group_by(time, sentiment, day, hour, minute) %>%
summarise(intensity = n()) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
filter(day == 16) %>%
ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col() +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time") +
scale_fill_viridis(option = "magma", discrete = T, direction = -1)
`summarise()` regrouping output by 'time', 'sentiment', 'day', 'hour' (override with `.groups` argument)
ggplotly(g)
This can also be shown in percentage format.
```r
tweets_en %>%
rowid_to_column(\element_id\) # This creates a new column with row number
tweets_en %>%
rowid_to_column(\element_id\) %>%
left_join(tweet_sent, by = \element_id\)
tweets_en %>%
rowid_to_column(\element_id\) %>%
left_join(tweet_sent, by = \element_id\) %>%
group_by(day = day(created_at)) %>%
summarise(avg_sent = mean(sentiment)) %>%
ggplot(aes(x = as.factor(day), y = avg_sent)) + geom_col()
tweets_en %>%
rowid_to_column(\element_id\) %>%
left_join(tweet_sent, by = \element_id\) %>%
ggplot(aes(x = as.factor(day(created_at)), y = sentiment)) +
geom_jitter(size = 0.2) +
geom_boxplot(aes(color = as.factor(day(created_at))), alpha = 0.5) +
theme(legend.position = \none\) + xlab(\day\)
<!-- rnb-source-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuZW1vdGlvbnMgJT4lIFxuICBtdXRhdGUoc2VudGltZW50ID0gaWZfZWxzZShzZW50aW1lbnQgPCBcIm5lZ2F0aXZlXCIsIFwicG9zaXRpdmVcIiwgXCJuZWdhdGl2ZVwiKSkgJT4lIFxuICBncm91cF9ieSh0aW1lLCBzZW50aW1lbnQsIGRheSwgaG91ciwgbWludXRlKSAlPiVcbiAgc3VtbWFyaXNlKGludGVuc2l0eSA9IG4oKSkgJT4lXG4gIG11dGF0ZSh0aW1lID0gbWFrZV9kYXRldGltZSh5ZWFyID0gMjAyMCwgbW9udGggPSAxMCwgZGF5ID0gZGF5LCBob3VyID0gaG91cioyNCwgbWluID0gbWludXRlKjI0KjYwKSkgJT4lXG4gIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKHBvc2l0aW9uID0gXCJmaWxsXCIpICtcbiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA4MCwgXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxMCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkgKyB4bGFiKFwiVGltZVwiKSArXG4gIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoXCIjMDAxMTQ0XCIsIFwiI0ZGREQ5OVwiKSlcbmBgYCJ9 -->
```r
emotions %>%
mutate(sentiment = if_else(sentiment < "negative", "positive", "negative")) %>%
group_by(time, sentiment, day, hour, minute) %>%
summarise(intensity = n()) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time") +
scale_fill_manual(values = c("#001144", "#FFDD99"))
Advanced sentiment
The problem with the preceding methods is that they don’t take into account valence shifters (i.e., negators, amplifiers (intensifiers), de-amplifiers (downtoners), and adversative conjunctions). If a tweet says not happy, counting the word happy is not a good idea! The package sentimentr is built to circumvent these issues: have a look at https://github.com/trinker/sentimentr
(see also: https://www.sentometrics.org and the book Supervised Machine Learning for Text Analysis in R hosted at https://smltar.com)
if(!require(sentimentr)){install.packages(c("sentimentr", "textcat"))}
library(sentimentr)
library(textcat)
First, let’s keep only the tweets written in English!
tweets_en <- tweets %>%
mutate(language = textcat(text)) %>%
filter(language == "english") %>%
dplyr::select(created_at, text)
NOTE: the code above was used to show the function textcat: the language is already coded in the tweets via the lang column/variable. (it suffices to keep the instances for which lang == “en”)
Next, we compute advanced sentiment.
tweet_sent <- tweets_en$text %>%
get_sentences() %>% # Intermediate function
sentiment() # Sentiment!
tweet_sent
NOTE: depending on frequency issues, it is better to analyze at daily or hourly scales. If a word is very popular, then, higher frequencies are more relevant.

LS0tCnRpdGxlOiAiVGhpcmQgcGFydHkgZGF0YSBhbmQgYmFzaWMgdGV4dCBtaW5pbmciCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgojIFRoZSBnZW5lcmFsIGlkZWEKCkRhdGEgdHJhbnNmZXIgaXMgaGlnaGx5IGNvbnRyb2xsZWQuIFRoZSBrZXkgbm90aW9ucyBhcmUgKiphdXRoZW50aWNhdGlvbioqIGFuZCAqKnByb3RvY29sKiouCgojIERvd25sb2FkaW5nIHR3ZWV0cyB3aXRoICpydHdlZXQqCgpUaGVyZSBhcmUgc2V2ZXJhbCBwYWNrYWdlcyB0aGF0IHJ1biBhbiBpbnRlcmZhY2Ugd2l0aCB0d2l0dGVyOiAqcnR3ZWV0KiwgKlJUd2l0dGVyQVBJKiwgKnN0cmVhbVIqIGFuZCAqdHdpdHRlUiouCQkKUmVjZW50IHBhY2thZ2VzIGFyZSBiZXR0ZXIgYmVjYXVzZSBmaXJtcyB1cGRhdGUgdGhlaXIgQVBJIHBvbGljaWVzIChhbmQgYWNjZXNzKSwgdGh1cyBvbGQgcHJvdG9jb2xzIHNvbWV0aW1lcyBkbyBub3Qgd29yayEKCiMjIEZpcnN0IHRoaW5ncyBmaXJzdAoqKkZpcnN0KiosIHRoZSBwYWNrYWdlcy4gRG93bmxvYWQuLi4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUocnR3ZWV0KSl7aW5zdGFsbC5wYWNrYWdlcygicnR3ZWV0Iil9CmBgYAoKLi4uIGFuZCBhY3RpdmF0ZS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHJ0d2VldCkKYGBgCgojIyBBdXRoZW50aWNhdGlvbgoKKipTZWNvbmQqKjogeW91IG5lZWQgeW91ciB0d2l0dGVyIGNyZWRlbnRpYWxzICh5b3UgbmVlZCBhIHR3aXR0ZXIgYWNjb3VudCkuCllvdSBhbHNvIG5lZWQgYSAqKmRldmVsb3BlciBhY2NvdW50Kio6IGh0dHBzOi8vZGV2ZWxvcGVyLnR3aXR0ZXIuY29tL2VuL2FwcGx5LWZvci1hY2Nlc3MgCkxvZ2luIG9uIHR3aXR0ZXIgYW5kIGdvIHRvOiBodHRwczovL2RldmVsb3Blci50d2l0dGVyLmNvbSAKCiFbXSh0d2l0dGVyMS5wbmcpCgpUaGUgbmV4dCBzdGVwIGlzIGNydWNpYWw6IHdlIG5lZWQgdG8gcmV0cmlldmUgaWRlbnRpZmljYXRpb24gY3JlZGVudGlhbHMuICAgCkluIG9yZGVyIHRvIGRvIHRoYXQsIHlvdSBuZWVkIHRvIGNyZWF0ZSBhIFR3aXR0ZXIgYXBwLiBCZWxvdywgeW91IGNhbiBzZWUgbWluZS4gClRvIGNyZWF0ZSBvbmUsIHNpbXBseSBjbGljayBvbiB0aGUgIkNyZWF0ZSBhbiBhcHAiICBidXR0b24gKG9uIHRoZSByaWdodCkKCiFbXSh0d2l0dGVyMi5wbmcpCgpJZiB5b3UgY2xpY2sgb24gdGhlICJkZXRhaWxzIiBvZiBhbiBhcHAsIHlvdSBjYW4gc2VlIHRoaXM6CgohW10odHdpdHRlcjMucG5nKQoKVGhlIHNlY29uZCB0YWIgaXMgY2FsbGVkICIqKktleXMgYW5kIHRva2VucyoqIiAkXHJpZ2h0YXJyb3ckIHRoYXQncyB3aGVyZSB0aGUgaW5mbyBpcyEhIQoKIVtdKHR3aXR0ZXI0LnBuZykKCgpOb3cgd2UgYXJlIHJlYWR5IHRvIHByb2NlZWQuIFRoZSBsaW5lcyBiZWxvdyBvcGVuIHRoZSBjb25uZXhpb24gd2l0aCB0aGUgQVBJLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpjb25zdW1lcl9rZXkgPC0gInlvdXJfY29uc3VtZXJfa2V5Igpjb25zdW1lcl9zZWNyZXQgPC0gInlvdV9jb25zdW1lcl9zZWNyZXQiCmFjY2Vzc190b2tlbiA8LSAieW91cl9hY2Nlc3NfdG9rZW4iCmFjY2Vzc19zZWNyZXQgPC0gInlvdXJfYWNjZXNzX3NlY3JldCIKCmNyZWF0ZV90b2tlbihhcHAgPSAidGhlX25hbWVfb2ZfeW91cl9hcHAiLAogICAgICAgICAgICAgY29uc3VtZXJfa2V5ID0gY29uc3VtZXJfa2V5LCAKICAgICAgICAgICAgIGNvbnN1bWVyX3NlY3JldCA9IGNvbnN1bWVyX3NlY3JldCwgCiAgICAgICAgICAgICBhY2Nlc3NfdG9rZW4gPSBhY2Nlc3NfdG9rZW4sIAogICAgICAgICAgICAgYWNjZXNzX3NlY3JldCA9IGFjY2Vzc19zZWNyZXQKKQpgYGAKCgoKCkF1dGhlbnRpY2F0aW9uIGlzIGFuIGltcG9ydGFudCBwYXJ0IG9mIHRoZSBwcm9jZXNzLiBGb3IgbW9yZSBpbmZvIG9uIHRoYXQ6ICAKLSBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ29vZ2xlc2hlZXRzL3ZpZ25ldHRlcy9tYW5hZ2luZy1hdXRoLXRva2Vucy5odG1sICAgCi0gaHR0cHM6Ly9odHRyLnItbGliLm9yZy9yZWZlcmVuY2UvaW5kZXguaHRtbCAoc2VjdGlvbiBBdXRoZW50aWNhdGlvbikKCiMjIEV4dHJhY3Rpb24KCklmIG5vIGVycm9yIGFwcGVhcnMsIHdlIGFyZSByZWFkeSB0byBxdWVyeS4gRGVwZW5kaW5nIG9uIHRoZSBudW1iZXIgb2YgcmVxdWVzdGVkIHR3ZWV0cywgdGhpcyBjYW4gdGFrZSBzb21lIHRpbWUuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CnNlYXJjaF90ZXJtIDwtICJlZGdlIGNvbXB1dGluZyIKdHdlZXRzIDwtIHNlYXJjaF90d2VldHMoCiAgc2VhcmNoX3Rlcm0sICAgICAgICAgICMgV2hhdCB0byBzZWFyY2ggZm9yCiAgbiA9IDUwMDAsICAgICAgICAgICAgICMgTnVtYmVyIG9mIHR3ZWV0cyB0byBkb3dubG9hZAogIGluY2x1ZGVfcnRzID0gRkFMU0UgICAjIEV4Y2x1ZGUgcmUtdHdlZXRzCikKYGBgCkZvciBsYXJnZSBxdWVyaWVzLCB0aGUgcHJvZ3Jlc3MgYmFyIGhlbHBzLiAgIApOb3RlIHRoYXQgbWFueSBvcHRpb25zIGFyZSBhdmFpbGFibGUsIGxpa2U6IGV4Y2x1ZGUgcmV0d2VldHMsIGxpbWl0IHNlYXJjaCB0byBwYXJ0aWN1bGFyIGdlb2dyYXBoaWNhbCB6b25lcyAoaW5zaWRlIHJhZGl1c2VzKS4KCiMgVGV4dCBtaW5pbmcKCiMjIFJlZmVyZW5jZXMKVGhlIHJlZmVyZW5jZSBib29rIGlzOiBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20gICAgICAKQSBncmVhdCBpbnRlcmFjdGl2ZSB0dXRvcmlhbDogaHR0cHM6Ly9qdWxpYXNpbGdlLnNoaW55YXBwcy5pby9sZWFybnRpZHl0ZXh0LyAgICAKQW5kIHRoZSBwYWNrYWdlIGlzOgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQppZighcmVxdWlyZSh0aWR5dGV4dCkpe2luc3RhbGwucGFja2FnZXMoInRpZHl0ZXh0IiwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnLyIpfQpsaWJyYXJ5KHRpZHl0ZXh0KQpgYGAKKHNlZSBhbHNvOiBodHRwczovL3F1YW50ZWRhLmlvL2luZGV4Lmh0bWwpCgojIyBEYXRhIHJldHJpZXZhbAoKTm93LCBsZXQncyBtb3ZlIGZvcndhcmQgdG8gc2ltcGxlIHRleHQgYW5hbHlzaXMuIEZpcnN0LCB3ZSBuZWVkIHRvIHByZXBhcmUgdGhlIGRhdGEhIChhcyB1c3VhbCkKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zIDwtIHR3ZWV0cyAlPiUgCiAgbXV0YXRlKGlkID0gMTpucm93KHR3ZWV0cykpICU+JSAgIyBUaGlzIGNyZWF0ZXMgYSB0d2VldCBpZAogIHNlbGVjdChpZCwgdGV4dCkgJT4lICAgICAgICAgICAgICMgS2VlcHMgb25seSBpZCBhbmQgdGV4dCBvZiB0aGUgdHdlZXQKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICAgICAgICAjIENyZWF0ZXMgdG9rZW5zIQp0b2tlbnMKYGBgCgpMZXQncyBoYXZlIGEgbG9vayBhdCB3b3JkIGZyZXF1ZW5jaWVzLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQp0b2tlbnMgJT4lCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCmBgYAoKVGhpcyBpcyBwb2xsdXRlZCBieSBzbWFsbCB3b3Jkcy4gTGV0J3MgZmlsdGVyIHRoYXQgKCpGSVJTVCBNRVRIT0QqKS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JSBtdXRhdGUobGVuZ3RoID0gbmNoYXIod29yZCkpCmBgYAoKCiMjIERhdGEgZnJlcXVlbmNpZXMKTm93IGxldCdzIG9taXQgdGhlIHNtYWxsIHdvcmRzIChzbWFsbGVyIHRoYW4gNSBjaGFyYWN0ZXJzKS4gICAKKipOT1RFKio6IGFsbCB0aGUgdGhyZXNob2xkcyBiZWxvdyBkZXBlbmQgb24gdGhlIHNhbXBsZSEgCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyAlPiUKICBtdXRhdGUobGVuZ3RoID0gbmNoYXIod29yZCkpICU+JQogIGZpbHRlcihsZW5ndGggPiA0KSAlPiUgICAgICAgICAgICAgIyBLZWVwIHdvcmRzIHdpdGggbGVuZ3RoIGxhcmdlciB0aGFuIDQKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICMgQ291bnQgd29yZHMKICBoZWFkKDIxKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRvcCAxMiB3b3JkcwogIGdncGxvdChhZXMoeSA9IHJlb3JkZXIod29yZCxuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJXb3JkcyIpCmBgYAoKQSBiZXR0ZXIgd2F5IHRvIHByb2NlZWQgaXMgdG8gcmVtb3ZlICJzdG9wIHdvcmRzIiBsaWtlICJhIiwgIkkiLCAib2YiLCAidGhlIiwgZXRjICgqU0VDT05EIE1FVEhPRCopLgpBbHNvLCBpdCB3b3VsZCBtYWtlIHNlbnNlIHRvIHJlbW92ZSB0aGUgc2VhcmNoIGl0ZW0gYW5kICJodHRwcyIuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmRhdGEoInN0b3Bfd29yZHMiKQp0aWR5X3Rva2VucyA8LSB0b2tlbnMgJT4lIAogIGFudGlfam9pbihzdG9wX3dvcmRzKSAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgdW5yZWxldmFudCB0ZXJtcwp0aWR5X3Rva2VucyAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICAgICAgICMgQ291bnQgd29yZHMKICBoZWFkKDIwKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRvcCAxNSB3b3JkcwogIGdncGxvdChhZXMoeSA9IHJlb3JkZXIod29yZCxuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJXb3JkcyIpCmBgYAoKKipQcm9ibGVtKio6IHN0cmFuZ2UgY2hhcmFjdGVycyByZW1haW4uIFdlIGFyZSBnb2luZyB0byByZW1vdmUgdGhlbSBieSBjb252ZXJ0aW5nIHRoZSB0ZXh0IHRvIEFTQ0lJIGZvcm1hdCBhbmQgb21pdCAqTkEqIGRhdGEuIAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQp0aWR5X3Rva2VucyA8LSB0b2tlbnMgJT4lIAogIGFudGlfam9pbihzdG9wX3dvcmRzKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgdW5yZWxldmFudAogIG11dGF0ZSh3b3JkID0gaWNvbnYod29yZCwgZnJvbSA9ICJVVEYtOCIsIHRvID0gIkFTQ0lJIikpICU+JSAjIFB1dCBpbiBsYXRpbiBmb3JtYXQKICBuYS5vbWl0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIG1pc3NpbmcKICBmaWx0ZXIobmNoYXIod29yZCkgPiAxLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHNtYWxsIHdvcmRzCiAgICAgICAgICEod29yZCAlaW4lIGMoImh0dHBzIiwgInQuY28iLCBzZWFyY2hfdGVybSkpICAjIHNlYXJjaF90ZXJtIGRlZmluZWQgYWJvdmUKICApCnRpZHlfdG9rZW5zICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUgICAgICAgICAjIENvdW50IHdvcmRzCiAgaGVhZCgyMCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRvcCB3b3JkcwogIGdncGxvdChhZXMoeSA9IHJlb3JkZXIod29yZCxuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJXb3JkcyIpCmBgYAoKUGVyZmVjdCEKCiMjIFdvcmQgY2xvdWQKClRoaXMgZGF0YSBjYW4gYWxzbyBiZSBzaG93biB3aXRoIGEgd29yZCBjbG91ZC4gV2Ugc2ltcGx5IHVzZSB0aGUgKndvcmRjbG91ZCogcGFja2FnZTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dvcmRjbG91ZC9pbmRleC5odG1sIAoKVGhlIHBhY2thZ2UgKndvcmRjbG91ZDIqIGFkZHMgYSBmZXcgZmVhdHVyZXM6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy93b3JkY2xvdWQyL3ZpZ25ldHRlcy93b3JkY2xvdWQuaHRtbAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQppZighcmVxdWlyZSh3b3JkY2xvdWQpKXtpbnN0YWxsLnBhY2thZ2VzKCJ3b3JkY2xvdWQiKX0KbGlicmFyeSh3b3JkY2xvdWQpCmNsb3VkX2RhdGEgPC0gdGlkeV90b2tlbnMgJT4lIGNvdW50KHdvcmQpCndvcmRjbG91ZCh3b3JkcyA9IGNsb3VkX2RhdGEkd29yZCwgCiAgICAgICAgICBmcmVxID0gY2xvdWRfZGF0YSRuLCBtaW4uZnJlcSA9IDIsCiAgICAgICAgICBtYXgud29yZHMgPSAxMDAsIHJhbmRvbS5vcmRlciA9IEZBTFNFLCByb3QucGVyID0gMC4xNSwgCiAgICAgICAgICBjb2xvcnMgPSBicmV3ZXIucGFsKDgsICJEYXJrMiIpKQpgYGAKCiMjIG4tZ3JhbXMKClNlZSBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vbmdyYW1zLmh0bWwKCmBgYHtyIGJpZ3JhbXMsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KdHdlZXRzICU+JSAKICBtdXRhdGUoaWQgPSAxOm5yb3codHdlZXRzKSkgJT4lICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICBzZWxlY3QoaWQsIHRleHQsIGNyZWF0ZWRfYXQpICU+JSAgICMgS2VlcHMgaWQsIHRleHQgYW5kIGRhdGUgb2YgdGhlIHR3ZWV0CiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRleHQsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUKICBncm91cF9ieShiaWdyYW0pICU+JQogIGNvdW50KHNvcnQgPSBUKSAlPiUKICBoZWFkKDIwKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKGJpZ3JhbSwgbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpCmBgYAoKQWdhaW46IHNhbWUgaXNzdWUgd2l0aCBzdG9wIHdvcmRzISBTbyB3ZSBtdXN0IHJlbW92ZSB0aGVtIGFnYWluLiBCdXQgaXQncyBtb3JlIGNvbXBsaWNhdGVkIG5vdy4KV2UgY2FuIHVzZSB0aGUgKnNlcGFyYXRlKigpIGZ1bmN0aW9uIHRvIGhlbHAgdXMuCgpgYGB7cn0KdHdlZXRzICU+JSAKICBtdXRhdGUoaWQgPSAxOm5yb3codHdlZXRzKSkgJT4lICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICBzZWxlY3QoaWQsIHRleHQsIGNyZWF0ZWRfYXQpICU+JSAgICMgS2VlcHMgaWQsIHRleHQgYW5kIGRhdGUgb2YgdGhlIHR3ZWV0CiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRleHQsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUKICBtdXRhdGUoYmlncmFtID0gaWNvbnYoYmlncmFtLCBmcm9tID0gIlVURi04IiwgdG8gPSAiQVNDSUkiKSkgJT4lCiAgbmEub21pdCgpICU+JQogIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiLCByZW1vdmUgPSBGKSAlPiUKICBmaWx0ZXIoISh3b3JkMSAlaW4lIGMoc3RvcF93b3JkcyR3b3JkLCAiaHR0cHMiLCBzZWFyY2hfdGVybSkpLAogICAgICAgICAhKHdvcmQyICVpbiUgYyhzdG9wX3dvcmRzJHdvcmQsICJodHRwcyIsIHNlYXJjaF90ZXJtKSksCiAgICAgICAgIGJpZ3JhbSAhPSBzZWFyY2hfdGVybSkgJT4lCiAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICBjb3VudChzb3J0ID0gVCkgJT4lCiAgaGVhZCgyMCkgJT4lCiAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcihiaWdyYW0sIG4pLCB4ID0gbikpICsgZ2VvbV9jb2woKSArIHlsYWIoIkJpLWdyYW0iKQpgYGAKCgojIyBTZW50aW1lbnQKClRoaXMgc2VjdGlvbiBpcyBpbnNwaXJlZCBmcm9tOiBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vc2VudGltZW50Lmh0bWwgICAgClNvbWV0aW1lcywgeW91IG1heSBiZSBhc2tlZCBpbiB0aGUgcHJvY2VzcyBpZiB5b3UgKnJlYWxseSogd2FudCB0byBkb3dubG9hZCBkYXRhIChsZXhpY29ucykuICAKSnVzdCBzYXkgeWVzIGluIHRoZSAqKmNvbnNvbGUqKiAodHlwZSB0aGUgY29ycmVjdCBhbnN3ZXI6IGlmIG5vdCwgeW91IHdpbGwgYmUgYmxvY2tlZC9zdHJ1Y2spLgoKRmlyc3QsIHdlIG5lZWQgdG8gbG9hZCBzb21lIHNlbnRpbWVudCBsZXhpY29uLiBBRklOTiBpcyBvbmUgc3VjaCBzZW50aW1lbnQgZGF0YWJhc2UuIAoKYGBge3J9CmlmKCFyZXF1aXJlKHRleHRkYXRhKSl7aW5zdGFsbC5wYWNrYWdlcygidGV4dGRhdGEiLCByZXBvcyA9ICJodHRwczovL2Nsb3VkLnItcHJvamVjdC5vcmcvIil9CmxpYnJhcnkodGlkeXRleHQpCmxpYnJhcnkodGV4dGRhdGEpCmFmaW5uIDwtIGdldF9zZW50aW1lbnRzKCJhZmlubiIpCmFmaW5uCmBgYAoKVG8gY3JlYXRlIGEgbmljZSB2aXN1YWxpemF0aW9uLCB3ZSBuZWVkIHRvIGV4dHJhY3QgdGhlICoqdGltZSoqIG9mIHRoZSB0d2VldHMuCgpgYGB7cn0KdG9rZW5zX3RpbWUgPC0gdHdlZXRzICU+JSAKICBtdXRhdGUoaWQgPSAxOm5yb3codHdlZXRzKSkgJT4lICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICBzZWxlY3QoaWQsIHRleHQsIGNyZWF0ZWRfYXQpICU+JSAgICMgS2VlcHMgaWQsIHRleHQgYW5kIGRhdGUgb2YgdGhlIHR3ZWV0CiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAgICAgICAgICAjIENyZWF0ZXMgdG9rZW5zIQp0b2tlbnNfdGltZQpgYGAKCldlIHRoZW4gdXNlICoqaW5uZXJfam9pbioqKCkgdG8gbWVyZ2UgdGhlIHR3byBzZXRzLiBUaGlzIGZ1bmN0aW9uIHJlbW92ZXMgdGhlIGNhc2VzIHdoZW4gYSBtYXRjaCBkb2VzIG5vdCBvY2N1ci4KCmBgYHtyfQpsaWJyYXJ5KGx1YnJpZGF0ZSkKc2VudGltZW50IDwtIHRva2Vuc190aW1lICU+JSAKICBpbm5lcl9qb2luKGFmaW5uKSAlPiUKICBtdXRhdGUoZGF5ID0gZGF5KGNyZWF0ZWRfYXQpLAogICAgICAgICBob3VyID0gaG91cihjcmVhdGVkX2F0KSAvIDI0LAogICAgICAgICBtaW51dGUgPSBtaW51dGUoY3JlYXRlZF9hdCkgLyA2MCAvIDI0LAogICAgICAgICB0aW1lID0gZGF5ICsgaG91ciArIG1pbnV0ZSkKc2VudGltZW50CmBgYAoKV2UgdGhlbiBjb21wdXRlIHRoZSBhdmVyYWdlIHNlbnRpbWVudCwgbWludXRlLWJ5LW1pbnV0ZS4gICAKT2YgY291cnNlLCBhdmVyYWdlIHNlbnRpbWVudCBjYW4gYmUgbWlzbGVhZGluZy4gSW5kZWVkLCBpZiBhIHRleHQgY29udGFpbnMgdGhlIHRlcm1zICIqSSdtIG5vdCBoYXBweSoiLCB0aGVuIG9ubHkgIipoYXBweSoiIHdpbGwgYmUgdGFnZ2VkLCB3aGljaCBpcyB0aGUgb3Bwb3NpdGUgb2YgdGhlIGludGVuZGVkIG1lYW5pbmcuCgpgYGB7cn0Kc2VudGltZW50ICU+JQogIGdyb3VwX2J5KHRpbWUsIGRheSwgaG91ciwgbWludXRlKSAlPiUKICBzdW1tYXJpc2UoYXZnX3NlbnRpbWVudCA9IG1lYW4odmFsdWUpKSAlPiUKICBtdXRhdGUodGltZSA9IG1ha2VfZGF0ZXRpbWUoeWVhciA9IDIwMjAsIG1vbnRoID0gMTAsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBhdmdfc2VudGltZW50KSkgCmBgYApUaGVyZSBhcmUgMjQgYmFycyBwZXIgZGF5LCBidXQgdGhlICp5Ki1heGlzIGlzIG5vdCBvcHRpbWFsLi4uICAKCldoYXQgYWJvdXQgZW1vdGlvbnM/IFRoZSAqKk5SQyoqIGxleGljb24gY2F0ZWdvcml6ZXMgZW1vdGlvbnMuIEJlbG93LCB3ZSBvcmRlciBlbW90aW9ucy4gVGhlIG1vc3QgaW1wb3J0YW50IGltcGFjdCBpcyB0aGUgZGljaG90b215IGJldHdlZW4gcG9zaXRpdmUgJiBuZWdhdGl2ZSBlbW90aW9ucy4gCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9Cm5yYyA8LSBnZXRfc2VudGltZW50cygibnJjIikKbnJjIDwtIG5yYyAlPiUKICBtdXRhdGUoc2VudGltZW50ID0gYXMuZmFjdG9yKHNlbnRpbWVudCksCiAgICAgICAgIHNlbnRpbWVudCA9IHJlY29kZV9mYWN0b3Ioc2VudGltZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGpveSA9ICJqb3kiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydXN0ID0gInRydXN0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJwcmlzZSA9ICJzdXJwcmlzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW50aWNpcGF0aW9uID0gImFudGljaXBhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAicG9zaXRpdmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5lZ2F0aXZlID0gIm5lZ2F0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYWRuZXNzID0gInNhZG5lc3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFuZ2VyID0gImFuZ2VyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWFyID0gImZlYXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpZ3VzdCA9ICJkaXNndXN0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAub3JkZXJlZCA9IFQpKQpgYGAKCldlIHRoZW4gY3JlYXRlIHRoZSBtZXJnZWQgZGF0YXNldC4KCmBgYHtyfQplbW90aW9ucyA8LSB0b2tlbnNfdGltZSAlPiUgCiAgaW5uZXJfam9pbihucmMpICU+JSAgICAgICAgICAgICAgICAgICMgTWVyZ2UgZGF0YSB3aXRoIHNlbnRpbWVudAogIG11dGF0ZShkYXkgPSBkYXkoY3JlYXRlZF9hdCksCiAgICAgICAgIGhvdXIgPSBob3VyKGNyZWF0ZWRfYXQpLzI0LAogICAgICAgICBtaW51dGUgPSBtaW51dGUoY3JlYXRlZF9hdCkvMjQvNjAsCiAgICAgICAgIHRpbWUgPSBkYXkgKyBob3VyICsgbWludXRlKSAgICMgQ3JlYXRlIGRheSBjb2x1bW4KZW1vdGlvbnMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTaG93IHRoZSByZXN1bHQKYGBgCgpUaGUgbWVyZ2luZyBoYXMgcmVkdWNlZCB0aGUgc2l6ZSBvZiB0aGUgZGF0YXNldCwgYnV0IHRoZXJlIHN0aWxsIHJlbWFpbnMgZW5vdWdoIHRvIHB1cnN1ZSB0aGUgc3R1ZHkuICAgCkZpbmFsbHksIHdlIG1vdmUgdG8gdGhlIHBpdm90LXRhYmxlIHRoYXQgY291bnRzIGVtb3Rpb25zIGZvciBlYWNoIGRheS4KCmBgYHtyfQpnIDwtIGVtb3Rpb25zICU+JSAKICBncm91cF9ieSh0aW1lLCBzZW50aW1lbnQsIGRheSwgaG91ciwgbWludXRlKSAlPiUKICBzdW1tYXJpc2UoaW50ZW5zaXR5ID0gbigpKSAlPiUKICBtdXRhdGUodGltZSA9IG1ha2VfZGF0ZXRpbWUoeWVhciA9IDIwMjAsIG1vbnRoID0gMTAsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkgKyB4bGFiKCJUaW1lIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXNjcmV0ZSA9IFQsIGRpcmVjdGlvbiA9IC0xKQpnZ3Bsb3RseShnKQpgYGAKClRoaXMgY2FuIGFsc28gYmUgc2hvd24gaW4gcGVyY2VudGFnZSBmb3JtYXQuIAoKYGBge3J9CmcgPC0gZW1vdGlvbnMgJT4lIAogIGdyb3VwX2J5KHRpbWUsIHNlbnRpbWVudCwgZGF5LCBob3VyLCBtaW51dGUpICU+JQogIHN1bW1hcmlzZShpbnRlbnNpdHkgPSBuKCkpICU+JQogIG11dGF0ZSh0aW1lID0gbWFrZV9kYXRldGltZSh5ZWFyID0gMjAyMCwgbW9udGggPSAxMCwgZGF5ID0gZGF5LCBob3VyID0gaG91cioyNCwgbWluID0gbWludXRlKjI0KjYwKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IGludGVuc2l0eSwgZmlsbCA9IHNlbnRpbWVudCkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkgKyB4bGFiKCJUaW1lIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXNjcmV0ZSA9IFQsIGRpcmVjdGlvbiA9IC0xKQpnZ3Bsb3RseShnKQpgYGAKCmBgYHtyfQplbW90aW9ucyAlPiUgCiAgbXV0YXRlKHNlbnRpbWVudCA9IGlmX2Vsc2Uoc2VudGltZW50IDwgIm5lZ2F0aXZlIiwgInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSAKICBncm91cF9ieSh0aW1lLCBzZW50aW1lbnQsIGRheSwgaG91ciwgbWludXRlKSAlPiUKICBzdW1tYXJpc2UoaW50ZW5zaXR5ID0gbigpKSAlPiUKICBtdXRhdGUodGltZSA9IG1ha2VfZGF0ZXRpbWUoeWVhciA9IDIwMjAsIG1vbnRoID0gMTAsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKHBvc2l0aW9uID0gImZpbGwiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA4MCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsgeGxhYigiVGltZSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjMDAxMTQ0IiwgIiNGRkREOTkiKSkKYGBgCgoKCgojIyBBZHZhbmNlZCBzZW50aW1lbnQgCgpUaGUgcHJvYmxlbSB3aXRoIHRoZSBwcmVjZWRpbmcgbWV0aG9kcyBpcyB0aGF0IHRoZXkgZG9uJ3QgdGFrZSBpbnRvIGFjY291bnQgKip2YWxlbmNlIHNoaWZ0ZXJzKiogKGkuZS4sIG5lZ2F0b3JzLCBhbXBsaWZpZXJzIChpbnRlbnNpZmllcnMpLCBkZS1hbXBsaWZpZXJzIChkb3dudG9uZXJzKSwgYW5kIGFkdmVyc2F0aXZlIGNvbmp1bmN0aW9ucykuIElmIGEgdHdlZXQgc2F5cyAqbm90IGhhcHB5KiwgY291bnRpbmcgdGhlIHdvcmQgKmhhcHB5KiBpcyBub3QgYSBnb29kIGlkZWEhIFRoZSBwYWNrYWdlICpzZW50aW1lbnRyKiBpcyBidWlsdCB0byBjaXJjdW12ZW50IHRoZXNlIGlzc3VlczogaGF2ZSBhIGxvb2sgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RyaW5rZXIvc2VudGltZW50ciAgCihzZWUgYWxzbzogaHR0cHM6Ly93d3cuc2VudG9tZXRyaWNzLm9yZyBhbmQgdGhlIGJvb2sgKipTdXBlcnZpc2VkIE1hY2hpbmUgTGVhcm5pbmcgZm9yIFRleHQgQW5hbHlzaXMgaW4gUioqIGhvc3RlZCBhdCBodHRwczovL3NtbHRhci5jb20pCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmlmKCFyZXF1aXJlKHNlbnRpbWVudHIpKXtpbnN0YWxsLnBhY2thZ2VzKGMoInNlbnRpbWVudHIiLCAidGV4dGNhdCIpKX0KbGlicmFyeShzZW50aW1lbnRyKQpsaWJyYXJ5KHRleHRjYXQpCmBgYAoKRmlyc3QsIGxldCdzIGtlZXAgb25seSB0aGUgdHdlZXRzIHdyaXR0ZW4gaW4gRW5nbGlzaCEKCmBgYHtyfQp0d2VldHNfZW4gPC0gdHdlZXRzICU+JQogIG11dGF0ZShsYW5ndWFnZSA9IHRleHRjYXQodGV4dCkpICU+JQogIGZpbHRlcihsYW5ndWFnZSA9PSAiZW5nbGlzaCIpICU+JQogIGRwbHlyOjpzZWxlY3QoY3JlYXRlZF9hdCwgdGV4dCkKYGBgCgoqKk5PVEUqKjogdGhlIGNvZGUgYWJvdmUgd2FzIHVzZWQgdG8gc2hvdyB0aGUgZnVuY3Rpb24gKnRleHRjYXQqOiB0aGUgbGFuZ3VhZ2UgaXMgYWxyZWFkeSBjb2RlZCBpbiB0aGUgdHdlZXRzIHZpYSB0aGUgKipsYW5nKiogY29sdW1uL3ZhcmlhYmxlLiAoaXQgc3VmZmljZXMgdG8ga2VlcCB0aGUgaW5zdGFuY2VzIGZvciB3aGljaCBsYW5nID09ICJlbiIpCgpOZXh0LCB3ZSBjb21wdXRlIGFkdmFuY2VkIHNlbnRpbWVudC4gCgpgYGB7cn0KdHdlZXRfc2VudCA8LSB0d2VldHNfZW4kdGV4dCAlPiUKICBnZXRfc2VudGVuY2VzKCkgJT4lICAjIEludGVybWVkaWF0ZSBmdW5jdGlvbgogIHNlbnRpbWVudCgpICAgICAgICAgICMgU2VudGltZW50IQp0d2VldF9zZW50CmBgYAoKKipOT1RFKio6IGRlcGVuZGluZyBvbiBmcmVxdWVuY3kgaXNzdWVzLCBpdCBpcyBiZXR0ZXIgdG8gYW5hbHl6ZSBhdCBkYWlseSBvciBob3VybHkgc2NhbGVzLiBJZiBhIHdvcmQgaXMgdmVyeSBwb3B1bGFyLCB0aGVuLCBoaWdoZXIgZnJlcXVlbmNpZXMgYXJlIG1vcmUgcmVsZXZhbnQuIAoKYGBge3J9CnR3ZWV0c19lbiAlPiUKICByb3dpZF90b19jb2x1bW4oImVsZW1lbnRfaWQiKSAjIFRoaXMgY3JlYXRlcyBhIG5ldyBjb2x1bW4gd2l0aCByb3cgbnVtYmVyCgp0d2VldHNfZW4gJT4lCiAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKQoKdHdlZXRzX2VuICU+JQogIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICU+JQogIGxlZnRfam9pbih0d2VldF9zZW50LCBieSA9ICJlbGVtZW50X2lkIikgJT4lCiAgZ3JvdXBfYnkoZGF5ID0gZGF5KGNyZWF0ZWRfYXQpKSAlPiUKICBzdW1tYXJpc2UoYXZnX3NlbnQgPSBtZWFuKHNlbnRpbWVudCkpICU+JQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3RvcihkYXkpLCB5ID0gYXZnX3NlbnQpKSArIGdlb21fY29sKCkgKyB4bGFiKCJkYXkiKQoKdHdlZXRzX2VuICU+JQogIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICU+JQogIGxlZnRfam9pbih0d2VldF9zZW50LCBieSA9ICJlbGVtZW50X2lkIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGRheShjcmVhdGVkX2F0KSksIHkgPSBzZW50aW1lbnQpKSArIAogIGdlb21faml0dGVyKHNpemUgPSAwLjIpICsKICBnZW9tX2JveHBsb3QoYWVzKGNvbG9yID0gYXMuZmFjdG9yKGRheShjcmVhdGVkX2F0KSkpLCBhbHBoYSA9IDAuNSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyB4bGFiKCJkYXkiKQpgYGAKCgoKIyBSZXNvdXJjZXMKCkJlbG93LCBhIHNob3J0IGxpc3Qgb2YgcmVzb3VyY2VzICh0byBhY2Nlc3MgdGhpcmQtcGFydHkgZGF0YSk6ICAgCgotICoqdGV4dCBtaW5pbmcgd2l0aCBSKiogKG9ubGluZSBib29rKTogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tICAgICAgCi0gKipCbG9vbWJlcmcqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1JibHBhcGkvaW5kZXguaHRtbCAgIAotICoqZ21haWwqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dtYWlsci92aWduZXR0ZXMvZ21haWxyLmh0bWwgICAKLSAqKkdvb2dsZSBNYXBzKio6IGh0dHBzOi8vY3Jhbi5yc3R1ZGlvLmNvbS93ZWIvcGFja2FnZXMvbWFwc2FwaS92aWduZXR0ZXMvaW50cm8uaHRtbCAgCi0gKipHb29nbGUgdHJlbmRzKio6IGh0dHBzOi8vZ2l0aHViLmNvbS9QTWFzc2ljb3R0ZS9ndHJlbmRzUgotICoqR29vZ2xlIEFQSXMqKiAobW9yZSBnZW5lcmFsbHkpOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2FyZ2xlL3ZpZ25ldHRlcy9hdXRoLWZyb20td2ViLmh0bWwKLSAqKkZhY2Vib29rIEFQSSoqOiBkZXZlbG9wZXJzLmZhY2Vib29rLmNvbS9hZHMvYmxvZy9wb3N0L3YyLzIwMTgvMDUvMTUvZmFjZWJvb2stcmVhY2gtZnJlcXVlbmN5LWFwaS8gIAoKUG9zc2libHkgZGVwcmVjYXRlZDogIAotICoqRmFjZWJvb2sqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1JmYWNlYm9vay9pbmRleC5odG1sICAgIAotICoqSW5zdGFncmFtKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9pbnN0YVIvaW5kZXguaHRtbAoKYGBge3J9CgpgYGAK