0. INTRODUCTION
I’ve used the website Towards Data Science often to augment my studies on learning analytics and am curious as to what text mining can reveal about the changes in research focus over the last 4 years. My initial research questions include the following:
Can article metadata be used to identify trends in data science research topics?
Does research topic frequency/popularity change over time or remain consistent year to year?
Can data science research trends identify shifts or advances in technology?
Data scientists, students, or any professionals researching this field could benefit by understanding how research/publication trends are linked to shifts in technology. Understanding these shifts could influence how quickly people adopt or advance new technologies in the data science field. Additionally, this information could be useful to understand technologies ripe for study in higher education leading to how students select focus areas, majors, or minors.
2. WRANGLE
For this case study, I plan to tokenize the text variables for analysis at the n-gram level. Context is important as the data set is comprised of article titles and taglines. I will need to experiment with how stop words impact trends as well as stemming. As I’m not interested in sentiment, I don’t plan on employing dictionary or subject-specific lexicons for analysis.
Import Article Titles and Taglines
My raw data was initially developed by Johannes Hötter earlier this year and posted to Kaggle.
tds_raw <- read_csv("data/towards_data_science.csv")
The dataset contains the titles and taglines for over 30,000 articles. My plan is to use only that information to experiment with topic modeling as opposed to the traditional method of using the complete article text. Now that I’ve successfully created a data frame, I can continue to manipulate the data into a format fit for exploratory analysis. Wrangling will consist of the following:
Combine title and tagline columns to create a single ‘text’ variable.
Mutate the date column into separate ‘year’ and ‘month’ variables to enable time-based exploration.
Create corpora through both the tidytext and tm packages to enable additional text mining tools.
Stem the tm corpus to compare sparsity levels between stemmed and contextual text.
Tokenize tidytext corpus into unigrams, bigrams, and trigrams to enable term frequency analysis.
Create document term matrices (DTMs) from each corpus to compare various topic modeling techniques.
Create Single Text Variable
tds_raw$text <- paste(tds_raw$title,tds_raw$tagline,sep=" ")
Create Year and Month Variables
tds_dates <- tds_raw %>%
mutate(date = mdy(date)) %>%
mutate_at(vars(date), funs(year, month))
Create tidytext Corpus
tds_tidy <- tds_dates %>%
unnest_tokens(output = word, input = text) %>%
anti_join(stop_words, by = "word")
# Remove numbers
tds_tidy <- tds_tidy[-grep("\\b\\d+\\b", tds_tidy$word),]
tidy_top_tokens <- tds_tidy %>%
count(word, sort = TRUE) %>%
top_n(10)
## Selecting by n
tidy_top_tokens
## # A tibble: 10 × 2
## word n
## <chr> <int>
## 1 data 13884
## 2 learning 7084
## 3 python 6457
## 4 machine 4155
## 5 science 3930
## 6 model 2498
## 7 ai 2151
## 8 analysis 2096
## 9 guide 2096
## 10 deep 1986
The above code created a tidy version of the corpus at the single word (unigram) level while also removing stop words and numbers.
Create tm Corpus
tds_corpus <- Corpus(VectorSource(tds_raw$text))
# Remove punctuation and numbers
tds_corpus <- tm_map(tds_corpus, content_transformer(removePunctuation))
tds_corpus <- tm_map(tds_corpus, content_transformer(removeNumbers))
# Transform corpus to all lower case
tds_corpus <- tm_map(tds_corpus, content_transformer(tolower))
Stemming
Stemming reduces the feature size of a corpus by transforming terms to their base stem. I predict stemming will limit redundancy in terms and phrases as I attempt various topic modeling techniques.
# Stem tm corpus
tds_corpus <- tm_map(tds_corpus, content_transformer(stemDocument), language = "english")
# Stem tidytext corpus
tds_tidy <- tds_tidy %>%
mutate(word = wordStem(word))
Cast a Document Term Matrix
tds_DTM <- DocumentTermMatrix(tds_corpus, control = list(wordLengths = c(2, Inf)))
tds_DTM
## <<DocumentTermMatrix (documents: 30665, terms: 28771)>>
## Non-/sparse entries: 486830/881775885
## Sparsity : 100%
## Maximal term length: 45
## Weighting : term frequency (tf)
tidy_tds_DTM <- tds_tidy %>%
count(title, word) %>%
cast_dtm(title, word, n)
tidy_tds_DTM
## <<DocumentTermMatrix (documents: 30128, terms: 14604)>>
## Non-/sparse entries: 294029/439695283
## Sparsity : 100%
## Maximal term length: 34
## Weighting : term frequency (tf)
With the two basic DTMs built, I’d also like to compare those results to that of a DTM that is less sparse to see if I can generate a different outcome:
sparse_tds_DTM = removeSparseTerms(tds_DTM, 0.99)
sparse_tds_DTM
## <<DocumentTermMatrix (documents: 30665, terms: 248)>>
## Non-/sparse entries: 289174/7315746
## Sparsity : 96%
## Maximal term length: 13
## Weighting : term frequency (tf)
This created a DTM with significantly fewer terms (248) and should produce a much different selection of topics once we get to the modeling phase.
Tokenization
Finally, I’ll complete the tokenization of the original data to enable further term frequency analysis of bigrams and trigrams. For these iterations, I’ve incorporated stop word removal and stemming:
tds_bigrams <- tds_dates %>%
unnest_tokens(output = bigram, input = text, token = "ngrams", n = 2)
tds_bigrams <- tds_bigrams %>%
separate(bigram, into = c("word1", "word2"), sep = " ") %>%
filter(!word1 %in% stop_words$word) %>%
filter(!word2 %in% stop_words$word) %>%
mutate(word1 = wordStem(word1)) %>%
mutate(word2 = wordStem(word2)) %>%
unite(bigram, c(word1, word2), sep = " ")
bigram_top_tokens <- tds_bigrams %>%
count(bigram, sort = TRUE) %>%
top_n(10)
bigram_top_tokens
## # A tibble: 10 × 2
## bigram n
## <chr> <int>
## 1 machin learn 3905
## 2 data scienc 3727
## 3 data scientist 1654
## 4 deep learn 1419
## 5 neural network 1366
## 6 learn model 765
## 7 time seri 678
## 8 data analysi 583
## 9 covid 19 560
## 10 reinforc learn 458
tds_trigrams <- tds_dates %>%
unnest_tokens(output = trigram, input = text, token = "ngrams", n = 3)
tds_trigrams <- tds_trigrams %>%
separate(trigram, into = c("word1", "word2", "word3"), sep = " ") %>%
filter(!word1 %in% stop_words$word) %>%
filter(!word2 %in% stop_words$word) %>%
filter(!word3 %in% stop_words$word) %>%
mutate(word1 = wordStem(word1)) %>%
mutate(word2 = wordStem(word2)) %>%
mutate(word3 = wordStem(word3)) %>%
unite(trigram, c(word1, word2, word3), sep = " ")
trigram_top_tokens <- tds_trigrams %>%
count(trigram, sort = TRUE) %>%
top_n(10)
trigram_top_tokens
## # A tibble: 10 × 2
## trigram n
## <chr> <int>
## 1 machin learn model 602
## 2 data scienc project 341
## 3 natur languag process 289
## 4 convolut neural network 233
## 5 exploratori data analysi 230
## 6 machin learn algorithm 201
## 7 deep learn model 138
## 8 time seri forecast 134
## 9 machin learn project 133
## 10 learn data scienc 132
3. Exploratory Analysis
Published Article Counts
tds_dates %>%
ggplot(aes(x = date, color = factor(month))) +
geom_bar() +
labs(y = "Date",
x = "Article Counts",
title = "Towards Data Science Articles",
subtitle = "Published from 2018 - 2021")

Towards Data Science had a great year in 2021 with over 70 articles published monthly in the mid-year period.
Word Counts by Year
tds_tidy %>%
group_by(year) %>%
count(word, sort = TRUE) %>%
top_n(10) %>%
ungroup %>%
mutate(word = reorder_within(word, n, year)) %>%
ggplot(aes(x = word, y = n, fill = word)) +
geom_col(show.legend = FALSE) +
facet_wrap(~ year, scales = "free_y") +
coord_flip() +
scale_x_reordered() +
scale_y_continuous(expand = c(0,0)) +
labs(y = "Count",
x = "Unique words",
title = "Most frequent words found in TDS article titles & taglines",
subtitle = "Stop words removed from the list")

The first thing that pops out when I graph the top unigrams by year is that many terms are repeated each year. Since I’m analyzing a data science blogging site, it’s expected that terms such as data, science, machine, learning, etc would appear at the top. I was curious to see how this changed if I expanded the graph to include the top 20 terms:
tds_tidy %>%
group_by(year) %>%
count(word, sort = TRUE) %>%
top_n(20) %>%
ungroup %>%
mutate(word = reorder_within(word, n, year)) %>%
ggplot(aes(x = word, y = n, fill = word)) +
geom_col(show.legend = FALSE) +
facet_wrap(~ year, scales = "free_y") +
coord_flip() +
scale_x_reordered() +
scale_y_continuous(expand = c(0,0)) +
labs(y = "Count",
x = "Unique words",
title = "Most frequent words found in TDS article titles & taglines",
subtitle = "Stop words removed from the list")

This second attempt does begin to reveal some unique terms by year. Another way to achieve this is by adding words common to all years to the list of stop words. In the end, I decided not to focus too much on individual terms as in this community, many of the key topics are described by multiple terms, such as ‘machine learning’ as opposed to treating those words as separate and distinct entities. To that end, I repeated the above visuals, but for multi-word groupings.
Bigram Counts
tds_bigrams %>%
group_by(year) %>%
count(bigram, sort = TRUE) %>%
top_n(20) %>%
ungroup %>%
mutate(bigram = reorder_within(bigram, n, year)) %>%
ggplot(aes(x = bigram, y = n, fill = bigram)) +
geom_col(show.legend = FALSE) +
facet_wrap(~ year, scales = "free_y") +
coord_flip() +
scale_x_reordered() +
scale_y_continuous(expand = c(0,0)) +
labs(y = "Count",
x = "Unique Bigrams",
title = "Most frequent bigrams found in article titles & taglines",
subtitle = "Stop words removed from the list")

Expanding to 2-word phrases reveals unique topics in each year. Some interesting examples are random forest (2018), object detect (2019), covid 19 (2020), and python code (2021).
Trigram Counts
tds_trigrams %>%
group_by(year) %>%
count(trigram, sort = TRUE) %>%
top_n(10) %>%
ungroup %>%
mutate(trigram = reorder_within(trigram, n, year)) %>%
ggplot(aes(x = trigram, y = n, fill = trigram)) +
geom_col(show.legend = FALSE) +
facet_wrap(~ year, scales = "free_y") +
coord_flip() +
scale_x_reordered() +
scale_y_continuous(expand = c(0,0)) +
labs(y = "Count",
x = "Unique Trigrams",
title = "Most frequent trigrams found in article titles & taglines",
subtitle = "Stop words removed from the list")

Trigrams repeat quite a bit in the top 10, though you start seeing more complete ideas emerge about specific activities. Examples include data science job and time seri data.
4. Preliminary Findings
This project is designed to answer three key questions about using topic modeling to identify latent themes in a body of short-form text. Specifically:
- Can article metadata be used to identify trends in data science research topics?
In this dataset, the only real metadata comes in the form of article publish dates. Some data that could be available, just not yet a part of my dataset, are author, article length, and number of reader comments.
- Does research topic frequency/popularity change over time or remain consistent year to year?
Exploratory analysis has revealed some unique patterns over time. While some overarching topics are consistently written about every year, there are also unique terms in each individual year. This leads me to believe topic modeling has a good chance to reveal unique technology topics by time period.
- Can data science research trends identify shifts or advances in technology?
So far, exploratory analysis on term frequency has shown changes in article topics from year to year. This makes me believe that shifts in tech will be visible as latent topics emerge from the planned models.
Stemming provided another insight regarding the potential for duplicate topics. I initially ran term frequency counts prior to stemming and the results were much different. Prior to stemming, the term ‘data scientist’ was viewed as distinct from ‘data scientists’ and those phrases would occupy two separate lines. They are obviously very similar, so stemming combined them. This allowed me to see more terms and identify potentially more topics.
Lastly, context is hugely important for models to discover topics. Bigram and trigram analysis yielded much better differentiation among topics over different time frames.
LS0tCnRpdGxlOiAnVG9waWMgTW9kZWxpbmcgaW4gU2hvcnRmb3JtIFRleHQ6IEFydGljbGUgVGl0bGVzICYgVGFnbGluZXMnCnN1YnRpdGxlOiAnRmluYWwgUHJvamVjdCBmb3IgRUNJIDU4OCwgVGV4dCBNaW5pbmcgaW4gRWR1Y2F0aW9uJwphdXRob3I6ICJKYW1lcyBIYXJkYXdheSIKZGF0ZTogIjQvMTcvMjAyMiIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUKZWRpdG9yX29wdGlvbnM6IAogIG1hcmtkb3duOiAKICAgIHdyYXA6IDcyCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgojIyAwLiBJTlRST0RVQ1RJT04KCkkndmUgdXNlZCB0aGUgd2Vic2l0ZSBbVG93YXJkcyBEYXRhClNjaWVuY2VdKGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS8pIG9mdGVuIHRvIGF1Z21lbnQgbXkgc3R1ZGllcyBvbgpsZWFybmluZyBhbmFseXRpY3MgYW5kIGFtIGN1cmlvdXMgYXMgdG8gd2hhdCB0ZXh0IG1pbmluZyBjYW4gcmV2ZWFsCmFib3V0IHRoZSBjaGFuZ2VzIGluIHJlc2VhcmNoIGZvY3VzIG92ZXIgdGhlIGxhc3QgNCB5ZWFycy4gTXkgaW5pdGlhbApyZXNlYXJjaCBxdWVzdGlvbnMgaW5jbHVkZSB0aGUgZm9sbG93aW5nOgoKLSAgIENhbiBhcnRpY2xlIG1ldGFkYXRhIGJlIHVzZWQgdG8gaWRlbnRpZnkgdHJlbmRzIGluIGRhdGEgc2NpZW5jZQogICAgcmVzZWFyY2ggdG9waWNzPwoKLSAgIERvZXMgcmVzZWFyY2ggdG9waWMgZnJlcXVlbmN5L3BvcHVsYXJpdHkgY2hhbmdlIG92ZXIgdGltZSBvciByZW1haW4KICAgIGNvbnNpc3RlbnQgeWVhciB0byB5ZWFyPwoKLSAgIENhbiBkYXRhIHNjaWVuY2UgcmVzZWFyY2ggdHJlbmRzIGlkZW50aWZ5IHNoaWZ0cyBvciBhZHZhbmNlcyBpbgogICAgdGVjaG5vbG9neT8KCkRhdGEgc2NpZW50aXN0cywgc3R1ZGVudHMsIG9yIGFueSBwcm9mZXNzaW9uYWxzIHJlc2VhcmNoaW5nIHRoaXMgZmllbGQKY291bGQgYmVuZWZpdCBieSB1bmRlcnN0YW5kaW5nIGhvdyByZXNlYXJjaC9wdWJsaWNhdGlvbiB0cmVuZHMgYXJlCmxpbmtlZCB0byBzaGlmdHMgaW4gdGVjaG5vbG9neS4gVW5kZXJzdGFuZGluZyB0aGVzZSBzaGlmdHMgY291bGQKaW5mbHVlbmNlIGhvdyBxdWlja2x5IHBlb3BsZSBhZG9wdCBvciBhZHZhbmNlIG5ldyB0ZWNobm9sb2dpZXMgaW4gdGhlCmRhdGEgc2NpZW5jZSBmaWVsZC4gQWRkaXRpb25hbGx5LCB0aGlzIGluZm9ybWF0aW9uIGNvdWxkIGJlIHVzZWZ1bCB0bwp1bmRlcnN0YW5kIHRlY2hub2xvZ2llcyByaXBlIGZvciBzdHVkeSBpbiBoaWdoZXIgZWR1Y2F0aW9uIGxlYWRpbmcgdG8KaG93IHN0dWRlbnRzIHNlbGVjdCBmb2N1cyBhcmVhcywgbWFqb3JzLCBvciBtaW5vcnMuCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIDEuIFBSRVBBUkUKCiMjIyBSIFBhY2thZ2UgU2V0IFVwCgpOb3Qgc3VyZSBpZiBhbGwgd2lsbCBiZSB1c2VkIGR1cmluZyB0aGUgZmluYWwgYW5hbHlzaXMsIGJ1dCB0aGUKZm9sbG93aW5nIHBhY2thZ2VzIHdlcmUgaW5zdGFsbGVkIGFuZC9vciBsb2FkZWQgdG8gcHJlcGFyZSBmb3IgdGhpcwpwcm9qZWN0OgoKYGBge3IgbG9hZC1wYWNrYWdlcywgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeXRleHQpCmxpYnJhcnkocnZlc3QpCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShYTUwpCmxpYnJhcnkoUkN1cmwpCmxpYnJhcnkoU25vd2JhbGxDKQpsaWJyYXJ5KHRvcGljbW9kZWxzKQpsaWJyYXJ5KHN0bSkKbGlicmFyeShsZGF0dW5pbmcpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkod29yZGNsb3VkMikKbGlicmFyeSh0bSkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoa2FibGVFeHRyYSkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIDIuIFdSQU5HTEUKCkZvciB0aGlzIGNhc2Ugc3R1ZHksIEkgcGxhbiB0byB0b2tlbml6ZSB0aGUgdGV4dCB2YXJpYWJsZXMgZm9yIGFuYWx5c2lzCmF0IHRoZSBuLWdyYW0gbGV2ZWwuIENvbnRleHQgaXMgaW1wb3J0YW50IGFzIHRoZSBkYXRhIHNldCBpcyBjb21wcmlzZWQKb2YgYXJ0aWNsZSB0aXRsZXMgYW5kIHRhZ2xpbmVzLiBJIHdpbGwgbmVlZCB0byBleHBlcmltZW50IHdpdGggaG93IHN0b3AKd29yZHMgaW1wYWN0IHRyZW5kcyBhcyB3ZWxsIGFzIHN0ZW1taW5nLiBBcyBJJ20gbm90IGludGVyZXN0ZWQgaW4Kc2VudGltZW50LCBJIGRvbid0IHBsYW4gb24gZW1wbG95aW5nIGRpY3Rpb25hcnkgb3Igc3ViamVjdC1zcGVjaWZpYwpsZXhpY29ucyBmb3IgYW5hbHlzaXMuCgojIyMgSW1wb3J0IEFydGljbGUgVGl0bGVzIGFuZCBUYWdsaW5lcwoKTXkgcmF3IGRhdGEgd2FzIGluaXRpYWxseSBkZXZlbG9wZWQgYnkgW0pvaGFubmVzCkjDtnR0ZXJdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vam9ob2V0dGVyKSBlYXJsaWVyIHRoaXMgeWVhciBhbmQgcG9zdGVkCnRvCltLYWdnbGVdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvam9ob2V0dGVyL3Rvd2FyZHMtZGF0YS1zY2llbmNlKS4KCmBgYHtyIHRpdGxlcyBpbXBvcnQsIG1lc3NhZ2U9RkFMU0V9CnRkc19yYXcgPC0gcmVhZF9jc3YoImRhdGEvdG93YXJkc19kYXRhX3NjaWVuY2UuY3N2IikKYGBgCgpUaGUgZGF0YXNldCBjb250YWlucyB0aGUgdGl0bGVzIGFuZCB0YWdsaW5lcyBmb3Igb3ZlciAzMCwwMDAgYXJ0aWNsZXMuCk15IHBsYW4gaXMgdG8gdXNlIG9ubHkgdGhhdCBpbmZvcm1hdGlvbiB0byBleHBlcmltZW50IHdpdGggdG9waWMKbW9kZWxpbmcgYXMgb3Bwb3NlZCB0byB0aGUgdHJhZGl0aW9uYWwgbWV0aG9kIG9mIHVzaW5nIHRoZSBjb21wbGV0ZQphcnRpY2xlIHRleHQuIE5vdyB0aGF0IEkndmUgc3VjY2Vzc2Z1bGx5IGNyZWF0ZWQgYSBkYXRhIGZyYW1lLCBJIGNhbgpjb250aW51ZSB0byBtYW5pcHVsYXRlIHRoZSBkYXRhIGludG8gYSBmb3JtYXQgZml0IGZvciBleHBsb3JhdG9yeQphbmFseXNpcy4gV3JhbmdsaW5nIHdpbGwgY29uc2lzdCBvZiB0aGUgZm9sbG93aW5nOgoKMS4gIENvbWJpbmUgdGl0bGUgYW5kIHRhZ2xpbmUgY29sdW1ucyB0byBjcmVhdGUgYSBzaW5nbGUgKiondGV4dCcqKgogICAgdmFyaWFibGUuCgoyLiAgTXV0YXRlIHRoZSBkYXRlIGNvbHVtbiBpbnRvIHNlcGFyYXRlICoqJ3llYXInKiogYW5kICoqJ21vbnRoJyoqCiAgICB2YXJpYWJsZXMgdG8gZW5hYmxlIHRpbWUtYmFzZWQgZXhwbG9yYXRpb24uCgozLiAgQ3JlYXRlIGNvcnBvcmEgdGhyb3VnaCBib3RoIHRoZSAqKmB0aWR5dGV4dGAqKiBhbmQgKipgdG1gKiogcGFja2FnZXMKICAgIHRvIGVuYWJsZSBhZGRpdGlvbmFsIHRleHQgbWluaW5nIHRvb2xzLgoKNC4gIFN0ZW0gdGhlICoqYHRtYCoqIGNvcnB1cyB0byBjb21wYXJlIHNwYXJzaXR5IGxldmVscyBiZXR3ZWVuIHN0ZW1tZWQKICAgIGFuZCBjb250ZXh0dWFsIHRleHQuCgo1LiAgVG9rZW5pemUgKipgdGlkeXRleHRgKiogY29ycHVzIGludG8gdW5pZ3JhbXMsIGJpZ3JhbXMsIGFuZCB0cmlncmFtcwogICAgdG8gZW5hYmxlIHRlcm0gZnJlcXVlbmN5IGFuYWx5c2lzLgoKNi4gIENyZWF0ZSBkb2N1bWVudCB0ZXJtIG1hdHJpY2VzIChEVE1zKSBmcm9tIGVhY2ggY29ycHVzIHRvIGNvbXBhcmUKICAgIHZhcmlvdXMgdG9waWMgbW9kZWxpbmcgdGVjaG5pcXVlcy4KCiMjIyBDcmVhdGUgU2luZ2xlIFRleHQgVmFyaWFibGUKCmBgYHtyIGNvbWJpbmUgdGV4dH0KdGRzX3JhdyR0ZXh0IDwtIHBhc3RlKHRkc19yYXckdGl0bGUsdGRzX3JhdyR0YWdsaW5lLHNlcD0iICIpCmBgYAoKIyMjIENyZWF0ZSBZZWFyIGFuZCBNb250aCBWYXJpYWJsZXMKCmBgYHtyIG11dGF0ZSBkYXRlLCB3YXJuaW5nPUZBTFNFfQp0ZHNfZGF0ZXMgPC0gdGRzX3JhdyAlPiUgCiAgbXV0YXRlKGRhdGUgPSBtZHkoZGF0ZSkpICU+JQogIG11dGF0ZV9hdCh2YXJzKGRhdGUpLCBmdW5zKHllYXIsIG1vbnRoKSkKYGBgCgojIyMgQ3JlYXRlICoqYHRpZHl0ZXh0YCoqIENvcnB1cwoKYGBge3IgdGlkeXRleHQgY29ycHVzfQp0ZHNfdGlkeSA8LSB0ZHNfZGF0ZXMgJT4lCiAgdW5uZXN0X3Rva2VucyhvdXRwdXQgPSB3b3JkLCBpbnB1dCA9IHRleHQpICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9ICJ3b3JkIikKCiMgUmVtb3ZlIG51bWJlcnMKdGRzX3RpZHkgPC0gdGRzX3RpZHlbLWdyZXAoIlxcYlxcZCtcXGIiLCB0ZHNfdGlkeSR3b3JkKSxdCgp0aWR5X3RvcF90b2tlbnMgPC0gdGRzX3RpZHkgJT4lIAogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUgCiAgdG9wX24oMTApCgp0aWR5X3RvcF90b2tlbnMKYGBgCgpUaGUgYWJvdmUgY29kZSBjcmVhdGVkIGEgdGlkeSB2ZXJzaW9uIG9mIHRoZSBjb3JwdXMgYXQgdGhlIHNpbmdsZSB3b3JkCih1bmlncmFtKSBsZXZlbCB3aGlsZSBhbHNvIHJlbW92aW5nIHN0b3Agd29yZHMgYW5kIG51bWJlcnMuCgojIyMgQ3JlYXRlICoqYHRtYCoqIENvcnB1cwoKYGBge3IgdG0gY29ycHVzLCB3YXJuaW5nPUZBTFNFfQp0ZHNfY29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGRzX3JhdyR0ZXh0KSkKCiMgUmVtb3ZlIHB1bmN0dWF0aW9uIGFuZCBudW1iZXJzCnRkc19jb3JwdXMgPC0gdG1fbWFwKHRkc19jb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIocmVtb3ZlUHVuY3R1YXRpb24pKQp0ZHNfY29ycHVzIDwtIHRtX21hcCh0ZHNfY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHJlbW92ZU51bWJlcnMpKQoKIyBUcmFuc2Zvcm0gY29ycHVzIHRvIGFsbCBsb3dlciBjYXNlCnRkc19jb3JwdXMgPC0gdG1fbWFwKHRkc19jb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpCmBgYAoKIyMjIFN0ZW1taW5nCgpTdGVtbWluZyByZWR1Y2VzIHRoZSBmZWF0dXJlIHNpemUgb2YgYSBjb3JwdXMgYnkgdHJhbnNmb3JtaW5nIHRlcm1zIHRvCnRoZWlyIGJhc2Ugc3RlbS4gSSBwcmVkaWN0IHN0ZW1taW5nIHdpbGwgbGltaXQgcmVkdW5kYW5jeSBpbiB0ZXJtcyBhbmQKcGhyYXNlcyBhcyBJIGF0dGVtcHQgdmFyaW91cyB0b3BpYyBtb2RlbGluZyB0ZWNobmlxdWVzLgoKYGBge3Igc3RlbSBlYWNoIGNvcnB1cywgd2FybmluZz1GQUxTRX0KIyBTdGVtIHRtIGNvcnB1cwp0ZHNfY29ycHVzIDwtIHRtX21hcCh0ZHNfY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHN0ZW1Eb2N1bWVudCksIGxhbmd1YWdlID0gImVuZ2xpc2giKQoKIyBTdGVtIHRpZHl0ZXh0IGNvcnB1cwp0ZHNfdGlkeSA8LSB0ZHNfdGlkeSAlPiUgCiAgbXV0YXRlKHdvcmQgPSB3b3JkU3RlbSh3b3JkKSkKYGBgCgojIyMgQ2FzdCBhIERvY3VtZW50IFRlcm0gTWF0cml4CgpgYGB7ciB0bSBEVE19CnRkc19EVE0gPC0gRG9jdW1lbnRUZXJtTWF0cml4KHRkc19jb3JwdXMsIGNvbnRyb2wgPSBsaXN0KHdvcmRMZW5ndGhzID0gYygyLCBJbmYpKSkKCnRkc19EVE0KYGBgCgpgYGB7ciB0aWR5dGV4dCBEVE19CnRpZHlfdGRzX0RUTSA8LSB0ZHNfdGlkeSAlPiUKICBjb3VudCh0aXRsZSwgd29yZCkgJT4lCiAgY2FzdF9kdG0odGl0bGUsIHdvcmQsIG4pCgp0aWR5X3Rkc19EVE0KYGBgCgpXaXRoIHRoZSB0d28gYmFzaWMgRFRNcyBidWlsdCwgSSdkIGFsc28gbGlrZSB0byBjb21wYXJlIHRob3NlIHJlc3VsdHMgdG8KdGhhdCBvZiBhIERUTSB0aGF0IGlzIGxlc3Mgc3BhcnNlIHRvIHNlZSBpZiBJIGNhbiBnZW5lcmF0ZSBhIGRpZmZlcmVudApvdXRjb21lOgoKYGBge3Igc3BhcnNlIHRtIERUTX0Kc3BhcnNlX3Rkc19EVE0gPSByZW1vdmVTcGFyc2VUZXJtcyh0ZHNfRFRNLCAwLjk5KSAKc3BhcnNlX3Rkc19EVE0KYGBgCgpUaGlzIGNyZWF0ZWQgYSBEVE0gd2l0aCBzaWduaWZpY2FudGx5IGZld2VyIHRlcm1zICgyNDgpIGFuZCBzaG91bGQKcHJvZHVjZSBhIG11Y2ggZGlmZmVyZW50IHNlbGVjdGlvbiBvZiB0b3BpY3Mgb25jZSB3ZSBnZXQgdG8gdGhlIG1vZGVsaW5nCnBoYXNlLgoKIyMjIFRva2VuaXphdGlvbgoKRmluYWxseSwgSSdsbCBjb21wbGV0ZSB0aGUgdG9rZW5pemF0aW9uIG9mIHRoZSBvcmlnaW5hbCBkYXRhIHRvIGVuYWJsZQpmdXJ0aGVyIHRlcm0gZnJlcXVlbmN5IGFuYWx5c2lzIG9mIGJpZ3JhbXMgYW5kIHRyaWdyYW1zLiBGb3IgdGhlc2UKaXRlcmF0aW9ucywgSSd2ZSBpbmNvcnBvcmF0ZWQgc3RvcCB3b3JkIHJlbW92YWwgYW5kIHN0ZW1taW5nOgoKYGBge3IgdG9rZW5pemUgYmlncmFtcywgbWVzc2FnZT1GQUxTRX0KdGRzX2JpZ3JhbXMgPC0gdGRzX2RhdGVzICU+JSAgIAogIHVubmVzdF90b2tlbnMob3V0cHV0ID0gYmlncmFtLCBpbnB1dCA9IHRleHQsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKQoKdGRzX2JpZ3JhbXMgPC0gdGRzX2JpZ3JhbXMgJT4lIAogIHNlcGFyYXRlKGJpZ3JhbSwgaW50byA9IGMoIndvcmQxIiwgIndvcmQyIiksIHNlcCA9ICIgIikgJT4lCiAgZmlsdGVyKCF3b3JkMSAlaW4lIHN0b3Bfd29yZHMkd29yZCkgJT4lCiAgZmlsdGVyKCF3b3JkMiAlaW4lIHN0b3Bfd29yZHMkd29yZCkgJT4lCiAgbXV0YXRlKHdvcmQxID0gd29yZFN0ZW0od29yZDEpKSAlPiUgCiAgbXV0YXRlKHdvcmQyID0gd29yZFN0ZW0od29yZDIpKSAlPiUgCiAgdW5pdGUoYmlncmFtLCBjKHdvcmQxLCB3b3JkMiksIHNlcCA9ICIgIikKCmJpZ3JhbV90b3BfdG9rZW5zIDwtIHRkc19iaWdyYW1zICU+JSAKICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKSAlPiUgCiAgdG9wX24oMTApCgpiaWdyYW1fdG9wX3Rva2VucwpgYGAKCmBgYHtyIHRva2VuaXplIHRyaWdyYW1zLCBtZXNzYWdlPUZBTFNFfQp0ZHNfdHJpZ3JhbXMgPC0gdGRzX2RhdGVzICU+JSAgIAogIHVubmVzdF90b2tlbnMob3V0cHV0ID0gdHJpZ3JhbSwgaW5wdXQgPSB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMykKCnRkc190cmlncmFtcyA8LSB0ZHNfdHJpZ3JhbXMgJT4lIAogIHNlcGFyYXRlKHRyaWdyYW0sIGludG8gPSBjKCJ3b3JkMSIsICJ3b3JkMiIsICJ3b3JkMyIpLCBzZXAgPSAiICIpICU+JQogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JQogIGZpbHRlcighd29yZDIgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JSAKICBmaWx0ZXIoIXdvcmQzICVpbiUgc3RvcF93b3JkcyR3b3JkKSAlPiUKICBtdXRhdGUod29yZDEgPSB3b3JkU3RlbSh3b3JkMSkpICU+JSAKICBtdXRhdGUod29yZDIgPSB3b3JkU3RlbSh3b3JkMikpICU+JSAKICBtdXRhdGUod29yZDMgPSB3b3JkU3RlbSh3b3JkMykpICU+JSAKICB1bml0ZSh0cmlncmFtLCBjKHdvcmQxLCB3b3JkMiwgd29yZDMpLCBzZXAgPSAiICIpCgp0cmlncmFtX3RvcF90b2tlbnMgPC0gdGRzX3RyaWdyYW1zICU+JSAKICBjb3VudCh0cmlncmFtLCBzb3J0ID0gVFJVRSkgJT4lIAogIHRvcF9uKDEwKQoKdHJpZ3JhbV90b3BfdG9rZW5zCmBgYAoKIyMgMy4gRXhwbG9yYXRvcnkgQW5hbHlzaXMKCiMjIyBQdWJsaXNoZWQgQXJ0aWNsZSBDb3VudHMKCmBgYHtyIEFydGljbGUgY291bnRzfQp0ZHNfZGF0ZXMgJT4lIAogIGdncGxvdChhZXMoeCA9IGRhdGUsIGNvbG9yID0gZmFjdG9yKG1vbnRoKSkpICsKICBnZW9tX2JhcigpICsKICBsYWJzKHkgPSAiRGF0ZSIsCiAgICAgeCA9ICJBcnRpY2xlIENvdW50cyIsCiAgICAgdGl0bGUgPSAiVG93YXJkcyBEYXRhIFNjaWVuY2UgQXJ0aWNsZXMiLAogICAgIHN1YnRpdGxlID0gIlB1Ymxpc2hlZCBmcm9tIDIwMTggLSAyMDIxIikKYGBgCgpUb3dhcmRzIERhdGEgU2NpZW5jZSBoYWQgYSBncmVhdCB5ZWFyIGluIDIwMjEgd2l0aCBvdmVyIDcwIGFydGljbGVzCnB1Ymxpc2hlZCBtb250aGx5IGluIHRoZSBtaWQteWVhciBwZXJpb2QuCgojIyMgV29yZCBDb3VudHMgYnkgWWVhcgoKYGBge3IgVW5pZ3JhbSBjb3VudHMsIG1lc3NhZ2U9RkFMU0V9CnRkc190aWR5ICU+JQogIGdyb3VwX2J5KHllYXIpICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUKICB0b3BfbigxMCkgJT4lCiAgdW5ncm91cCAlPiUKICBtdXRhdGUod29yZCA9IHJlb3JkZXJfd2l0aGluKHdvcmQsIG4sIHllYXIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB3b3JkLCB5ID0gbiwgZmlsbCA9IHdvcmQpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofiB5ZWFyLCBzY2FsZXMgPSAiZnJlZV95IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeF9yZW9yZGVyZWQoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKwogIGxhYnMoeSA9ICJDb3VudCIsCiAgICAgICB4ID0gIlVuaXF1ZSB3b3JkcyIsCiAgICAgICB0aXRsZSA9ICJNb3N0IGZyZXF1ZW50IHdvcmRzIGZvdW5kIGluIFREUyBhcnRpY2xlIHRpdGxlcyAmIHRhZ2xpbmVzIiwKICAgICAgIHN1YnRpdGxlID0gIlN0b3Agd29yZHMgcmVtb3ZlZCBmcm9tIHRoZSBsaXN0IikKYGBgCgpUaGUgZmlyc3QgdGhpbmcgdGhhdCBwb3BzIG91dCB3aGVuIEkgZ3JhcGggdGhlIHRvcCB1bmlncmFtcyBieSB5ZWFyIGlzCnRoYXQgbWFueSB0ZXJtcyBhcmUgcmVwZWF0ZWQgZWFjaCB5ZWFyLiBTaW5jZSBJJ20gYW5hbHl6aW5nIGEgZGF0YQpzY2llbmNlIGJsb2dnaW5nIHNpdGUsIGl0J3MgZXhwZWN0ZWQgdGhhdCB0ZXJtcyBzdWNoIGFzIGRhdGEsIHNjaWVuY2UsCm1hY2hpbmUsIGxlYXJuaW5nLCBldGMgd291bGQgYXBwZWFyIGF0IHRoZSB0b3AuIEkgd2FzIGN1cmlvdXMgdG8gc2VlIGhvdwp0aGlzIGNoYW5nZWQgaWYgSSBleHBhbmRlZCB0aGUgZ3JhcGggdG8gaW5jbHVkZSB0aGUgdG9wIDIwIHRlcm1zOgoKYGBge3IgVW5pZ3JhbSB0b3AgMjAsIG1lc3NhZ2U9RkFMU0V9CnRkc190aWR5ICU+JQogIGdyb3VwX2J5KHllYXIpICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUKICB0b3BfbigyMCkgJT4lCiAgdW5ncm91cCAlPiUKICBtdXRhdGUod29yZCA9IHJlb3JkZXJfd2l0aGluKHdvcmQsIG4sIHllYXIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB3b3JkLCB5ID0gbiwgZmlsbCA9IHdvcmQpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofiB5ZWFyLCBzY2FsZXMgPSAiZnJlZV95IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeF9yZW9yZGVyZWQoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKwogIGxhYnMoeSA9ICJDb3VudCIsCiAgICAgICB4ID0gIlVuaXF1ZSB3b3JkcyIsCiAgICAgICB0aXRsZSA9ICJNb3N0IGZyZXF1ZW50IHdvcmRzIGZvdW5kIGluIFREUyBhcnRpY2xlIHRpdGxlcyAmIHRhZ2xpbmVzIiwKICAgICAgIHN1YnRpdGxlID0gIlN0b3Agd29yZHMgcmVtb3ZlZCBmcm9tIHRoZSBsaXN0IikKYGBgCgpUaGlzIHNlY29uZCBhdHRlbXB0IGRvZXMgYmVnaW4gdG8gcmV2ZWFsIHNvbWUgdW5pcXVlIHRlcm1zIGJ5IHllYXIuCkFub3RoZXIgd2F5IHRvIGFjaGlldmUgdGhpcyBpcyBieSBhZGRpbmcgd29yZHMgY29tbW9uIHRvIGFsbCB5ZWFycyB0bwp0aGUgbGlzdCBvZiBzdG9wIHdvcmRzLiBJbiB0aGUgZW5kLCBJIGRlY2lkZWQgbm90IHRvIGZvY3VzIHRvbyBtdWNoIG9uCmluZGl2aWR1YWwgdGVybXMgYXMgaW4gdGhpcyBjb21tdW5pdHksIG1hbnkgb2YgdGhlIGtleSB0b3BpY3MgYXJlCmRlc2NyaWJlZCBieSBtdWx0aXBsZSB0ZXJtcywgc3VjaCBhcyAnbWFjaGluZSBsZWFybmluZycgYXMgb3Bwb3NlZCB0bwp0cmVhdGluZyB0aG9zZSB3b3JkcyBhcyBzZXBhcmF0ZSBhbmQgZGlzdGluY3QgZW50aXRpZXMuIFRvIHRoYXQgZW5kLCBJCnJlcGVhdGVkIHRoZSBhYm92ZSB2aXN1YWxzLCBidXQgZm9yIG11bHRpLXdvcmQgZ3JvdXBpbmdzLgoKIyMjIEJpZ3JhbSBDb3VudHMKCmBgYHtyIEJpZ3JhbSBjb3VudHMsIG1lc3NhZ2U9RkFMU0V9CnRkc19iaWdyYW1zICU+JQogIGdyb3VwX2J5KHllYXIpICU+JQogIGNvdW50KGJpZ3JhbSwgc29ydCA9IFRSVUUpICU+JQogIHRvcF9uKDIwKSAlPiUKICB1bmdyb3VwICU+JQogIG11dGF0ZShiaWdyYW0gPSByZW9yZGVyX3dpdGhpbihiaWdyYW0sIG4sIHllYXIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBiaWdyYW0sIHkgPSBuLCBmaWxsID0gYmlncmFtKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH4geWVhciwgc2NhbGVzID0gImZyZWVfeSIpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3hfcmVvcmRlcmVkKCkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBsYWJzKHkgPSAiQ291bnQiLAogICAgICAgeCA9ICJVbmlxdWUgQmlncmFtcyIsCiAgICAgICB0aXRsZSA9ICJNb3N0IGZyZXF1ZW50IGJpZ3JhbXMgZm91bmQgaW4gYXJ0aWNsZSB0aXRsZXMgJiB0YWdsaW5lcyIsCiAgICAgICBzdWJ0aXRsZSA9ICJTdG9wIHdvcmRzIHJlbW92ZWQgZnJvbSB0aGUgbGlzdCIpCmBgYAoKRXhwYW5kaW5nIHRvIDItd29yZCBwaHJhc2VzIHJldmVhbHMgdW5pcXVlIHRvcGljcyBpbiBlYWNoIHllYXIuIFNvbWUKaW50ZXJlc3RpbmcgZXhhbXBsZXMgYXJlICoqcmFuZG9tIGZvcmVzdCoqICgyMDE4KSwgKipvYmplY3QgZGV0ZWN0KioKKDIwMTkpLCAqKmNvdmlkIDE5KiogKDIwMjApLCBhbmQgKipweXRob24gY29kZSoqICgyMDIxKS4KCiMjIyMgVHJpZ3JhbSBDb3VudHMKCmBgYHtyIFRyaWdyYW0gY291bnRzLCBtZXNzYWdlPUZBTFNFfQp0ZHNfdHJpZ3JhbXMgJT4lCiAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgY291bnQodHJpZ3JhbSwgc29ydCA9IFRSVUUpICU+JQogIHRvcF9uKDEwKSAlPiUKICB1bmdyb3VwICU+JQogIG11dGF0ZSh0cmlncmFtID0gcmVvcmRlcl93aXRoaW4odHJpZ3JhbSwgbiwgeWVhcikpICU+JQogIGdncGxvdChhZXMoeCA9IHRyaWdyYW0sIHkgPSBuLCBmaWxsID0gdHJpZ3JhbSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+IHllYXIsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV94X3Jlb3JkZXJlZCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgbGFicyh5ID0gIkNvdW50IiwKICAgICAgIHggPSAiVW5pcXVlIFRyaWdyYW1zIiwKICAgICAgIHRpdGxlID0gIk1vc3QgZnJlcXVlbnQgdHJpZ3JhbXMgZm91bmQgaW4gYXJ0aWNsZSB0aXRsZXMgJiB0YWdsaW5lcyIsCiAgICAgICBzdWJ0aXRsZSA9ICJTdG9wIHdvcmRzIHJlbW92ZWQgZnJvbSB0aGUgbGlzdCIpCmBgYAoKVHJpZ3JhbXMgcmVwZWF0IHF1aXRlIGEgYml0IGluIHRoZSB0b3AgMTAsIHRob3VnaCB5b3Ugc3RhcnQgc2VlaW5nIG1vcmUKY29tcGxldGUgaWRlYXMgZW1lcmdlIGFib3V0IHNwZWNpZmljIGFjdGl2aXRpZXMuIEV4YW1wbGVzIGluY2x1ZGUgKipkYXRhCnNjaWVuY2Ugam9iKiogYW5kICoqdGltZSBzZXJpIGRhdGEqKi4KCiMjIDQuIFByZWxpbWluYXJ5IEZpbmRpbmdzCgpUaGlzIHByb2plY3QgaXMgZGVzaWduZWQgdG8gYW5zd2VyIHRocmVlIGtleSBxdWVzdGlvbnMgYWJvdXQgdXNpbmcgdG9waWMKbW9kZWxpbmcgdG8gaWRlbnRpZnkgbGF0ZW50IHRoZW1lcyBpbiBhIGJvZHkgb2Ygc2hvcnQtZm9ybSB0ZXh0LgpTcGVjaWZpY2FsbHk6CgoxLiAgKipDYW4gYXJ0aWNsZSBtZXRhZGF0YSBiZSB1c2VkIHRvIGlkZW50aWZ5IHRyZW5kcyBpbiBkYXRhIHNjaWVuY2UKICAgIHJlc2VhcmNoIHRvcGljcz8qKgoKSW4gdGhpcyBkYXRhc2V0LCB0aGUgb25seSByZWFsIG1ldGFkYXRhIGNvbWVzIGluIHRoZSBmb3JtIG9mIGFydGljbGUKcHVibGlzaCBkYXRlcy4gU29tZSBkYXRhIHRoYXQgY291bGQgYmUgYXZhaWxhYmxlLCBqdXN0IG5vdCB5ZXQgYSBwYXJ0IG9mCm15IGRhdGFzZXQsIGFyZSBhdXRob3IsIGFydGljbGUgbGVuZ3RoLCBhbmQgbnVtYmVyIG9mIHJlYWRlciBjb21tZW50cy4KCjIuICAqKkRvZXMgcmVzZWFyY2ggdG9waWMgZnJlcXVlbmN5L3BvcHVsYXJpdHkgY2hhbmdlIG92ZXIgdGltZSBvcgogICAgcmVtYWluIGNvbnNpc3RlbnQgeWVhciB0byB5ZWFyPyoqCgpFeHBsb3JhdG9yeSBhbmFseXNpcyBoYXMgcmV2ZWFsZWQgc29tZSB1bmlxdWUgcGF0dGVybnMgb3ZlciB0aW1lLiBXaGlsZQpzb21lIG92ZXJhcmNoaW5nIHRvcGljcyBhcmUgY29uc2lzdGVudGx5IHdyaXR0ZW4gYWJvdXQgZXZlcnkgeWVhciwgdGhlcmUKYXJlIGFsc28gdW5pcXVlIHRlcm1zIGluIGVhY2ggaW5kaXZpZHVhbCB5ZWFyLiBUaGlzIGxlYWRzIG1lIHRvIGJlbGlldmUKdG9waWMgbW9kZWxpbmcgaGFzIGEgZ29vZCBjaGFuY2UgdG8gcmV2ZWFsIHVuaXF1ZSB0ZWNobm9sb2d5IHRvcGljcyBieQp0aW1lIHBlcmlvZC4KCjMuICAqKkNhbiBkYXRhIHNjaWVuY2UgcmVzZWFyY2ggdHJlbmRzIGlkZW50aWZ5IHNoaWZ0cyBvciBhZHZhbmNlcyBpbgogICAgdGVjaG5vbG9neT8qKgoKU28gZmFyLCBleHBsb3JhdG9yeSBhbmFseXNpcyBvbiB0ZXJtIGZyZXF1ZW5jeSBoYXMgc2hvd24gY2hhbmdlcyBpbgphcnRpY2xlIHRvcGljcyBmcm9tIHllYXIgdG8geWVhci4gVGhpcyBtYWtlcyBtZSBiZWxpZXZlIHRoYXQgc2hpZnRzIGluCnRlY2ggd2lsbCBiZSB2aXNpYmxlIGFzIGxhdGVudCB0b3BpY3MgZW1lcmdlIGZyb20gdGhlIHBsYW5uZWQgbW9kZWxzLgoKU3RlbW1pbmcgcHJvdmlkZWQgYW5vdGhlciBpbnNpZ2h0IHJlZ2FyZGluZyB0aGUgcG90ZW50aWFsIGZvciBkdXBsaWNhdGUKdG9waWNzLiBJIGluaXRpYWxseSByYW4gdGVybSBmcmVxdWVuY3kgY291bnRzIHByaW9yIHRvIHN0ZW1taW5nIGFuZCB0aGUKcmVzdWx0cyB3ZXJlIG11Y2ggZGlmZmVyZW50LiBQcmlvciB0byBzdGVtbWluZywgdGhlIHRlcm0gJ2RhdGEKc2NpZW50aXN0JyB3YXMgdmlld2VkIGFzIGRpc3RpbmN0IGZyb20gJ2RhdGEgc2NpZW50aXN0cycgYW5kIHRob3NlCnBocmFzZXMgd291bGQgb2NjdXB5IHR3byBzZXBhcmF0ZSBsaW5lcy4gVGhleSBhcmUgb2J2aW91c2x5IHZlcnkKc2ltaWxhciwgc28gc3RlbW1pbmcgY29tYmluZWQgdGhlbS4gVGhpcyBhbGxvd2VkIG1lIHRvIHNlZSBtb3JlIHRlcm1zCmFuZCBpZGVudGlmeSBwb3RlbnRpYWxseSBtb3JlIHRvcGljcy4KCkxhc3RseSwgY29udGV4dCBpcyBodWdlbHkgaW1wb3J0YW50IGZvciBtb2RlbHMgdG8gZGlzY292ZXIgdG9waWNzLgpCaWdyYW0gYW5kIHRyaWdyYW0gYW5hbHlzaXMgeWllbGRlZCBtdWNoIGJldHRlciBkaWZmZXJlbnRpYXRpb24gYW1vbmcKdG9waWNzIG92ZXIgZGlmZmVyZW50IHRpbWUgZnJhbWVzLgo=