Visualizations with ggplot2

Now that we have our data visualization imaginations going, let’s get into how we can visually represent our data in R.

The go-to package for data visualization in R is ggplot2, which is part of the tidyverse. You can find more information about ggplot2 on the tidyverse website.

This package approaches data visualization through “a grammar of graphics.” In other words, using the same syntax, you can create an infinite number of data visualizations. Although there are a lot of functions and components to learn at first, once you understand the overall structure of building graphics in ggplot2, you can replicate and expand on this structure to visualize data in an unlimited number of ways.

There are many ways to make data visualizations in R; however, other approaches tend to be more automatic and consequently limit the amount you can change and adapt your visualization to your needs. ggplot2 works in layers, allowing for maximum control and flexibility.

Layers in ggplot2

Here are some of the most common layers (i.e., functions) used in ggplot2. Typically you connect these layers using the + symbol. There is often more than one way to build the same plot with the ggplot package.

- ggplot(): how you will start most plots you build in ggplot2. The rest of the information goes within this function.

- aes(): this is the aesthetic mapping function, in which you can control aesthetic components of the plot. You can add colors, axis labels, font sizes and more within this function. Color and shape can be defined both within and outside of the aesthetic function.

- geom_point(): used for making a scatter plot

- geom_line(): used for adding a line to a plot

- geom_histogram(): used for making a histogram

- geom_col(): used for making a bar plot

- xlab, ylab, and labs(title = ): used for adding axis labels and an overall title to plots

- color: assigns a color to part of the plot, such as different groups or the data points

- fill: assigns the interior color of part of the plot, such as a confidence band or the bars in bar plots

- alpha: used to change the transparency of a component of the plot. Useful if you lots of have overlapping data points or distributions from multiple groups.

- size: used to set the size of part of the plot, such as how big the data points or text should be

There are many more data visualization options in ggplot but to get started today we are going to focus on making a bar plot (good for categorical data) and a scatter plot (good for continuous data).

Let’s take a look at the ggplot2 documentation

Create a New Script

Let’s start by creating a new script.

If you have installed the tidyverse, then ggplot2 is included. Otherwise you can install it now. Let’s also load our dataset for today.

install.packages("ggplot2")
library(ggplot2)

Let’s load our dataset. For this exercise, we are using a dataset that is exactly the same as the one that was produced in the session Data Types and Structures, but that has one extra variable added called isFeelRushed. This variable is derived from the feelRushed variable, with the number 1 indicating that somebody feels rushed, and 0 indicating they do not feel rushed. For more information on this variable and how it was created, see the session R: Filter and Select

load("data/block-5_visualization.RData")

Basic Bar Plot: Counts

Let’s make our first plot in R! We are going to slowly add layers, building up to a box plot representing the number of people in each group in the isFeelRushed variable.

When creating visualizations, it’s always good to have a sense of the variables you’re working with. It can be helpful to use the View() command we discussed in the session First Steps in R:

View(js_data)

Because we know that we’re going to be starting with the isFeelRushed variable, it’s also helpful to know what its data type is:

class(js_data$isFeelRushed)


Starting the Plot

First we create the blank plot on which we will add our data. We nearly always start with the function ggplot and then telling the function what dataset to use.

ggplot(js_data)

This creates our blank canvas.

Next we tell ggplot what variable we want to use and put it in the aes() function. If you want a bar chart representing the number of people in each group, you can add just one variable to the aes() function. We include the as.factor function around the isFeelRushed variable so it is treated as a categorical variable for the box plot instead of a numeric variable.

ggplot(js_data, aes(x = as.factor(isFeelRushed)))

You now see that the grid represents a scale relevant to that variable.

Next, we tell ggplot what type of data visualization we want. To create a bar chart we use the function geom_bar(). To see how many people are in each of the isFeelRushed groups, we use the default geom_bar(stat = "count"). Remember that we connect layers with a + symbol.

ggplot(js_data, aes(x = as.factor(isFeelRushed))) +
  geom_bar(stat = "count")

We’ve got a bar chart!

Lastly, let’s add some labels to the x-axis and y-axis to make it clear what is being plotted.

ggplot(js_data, aes(x = as.factor(isFeelRushed))) +
  geom_bar(stat = "count") + 
  xlab("Feeling Rushed") +
  ylab ("Number of Participants") 

Basic Bar Plot: Group Means

Now, let’s see if people who feel rushed tend to work more than people who do not feel rushed. To represent the mean for each group or some other variable, you add both an x and a y variable to the aes() function and use the geom_bar(stat = "summary"). Note that the Y-axis scale has now adjusted to a scale that matches the variable we are using (i.e., mean number of minutes spent working for each group).

ggplot(js_data, aes(x = as.factor(isFeelRushed), y = durWork)) +
  geom_bar(stat = "summary") + 
  xlab("Feeling Rushed") +
  ylab ("Average Minutes Working") 

  • Most plots will start with the ggplot()function
  • You have to include the object where ggplot() will get the information for the plot from. In this case, it’s our dataset js_data
  • Within the aes() function, we identify what the x and y variables are for this plot
  • We add layers using the + sign
  • Next we tell ggplot() what type of plot we are making; in this case we are creating a bar plot using the function geom_bar()
  • Then we add what type of statistic we want presented on the plot. Here we ask for the mean of each group for the variable durWork using the function geom_bar(stat = "summary")
  • We add a label to the x-axis with lab("Feeling Rushed")
  • We add the label to the y-axis with ylab ("Time Working")

Improved Box Plot!

This plot gets the idea across, but we can add more layers and functions to make more adjustments. See the walkthrough below for how we made all these changes.

plotlabels <- c("Not Rushed", "Rushed", "Did Not Respond")

ggplot(js_data, aes(x = as.factor(isFeelRushed), y = durWork)) +
  geom_bar(stat = "summary", fill = "#2D5E7F") + 
  xlab("Feeling Rushed") +
  ylab ("Average Minutes Working") +
  labs(title = "Working and Feeling Rushed") +
  scale_x_discrete(labels = plotlabels) +
  theme(text = element_text(size = 18),
        axis.text.x = element_text(angle = 25, hjust = 1))

  • We can change the color of the bars using fill =. Here we added the specific color using a hex code. But you can also write in the names of colors such as “blue”.
  • Using labs(title = "") we added an overall title to the plot
  • We probably want to indicate what each category represents, rather than the “0”, “1” and “NA” labels. To add text labels, we first create an object with each of those labels in order (plotlabels <- c("Not Rushed", "Rushed", "Did Not Respond")). Then in the scale_x_discrete(labels = plotlabels) function we call to that object we created as the labels for the x-axis. There are many other ways to adjust the labels for each axis, but this method works well for a small number of groups.
  • In the theme() layer you can add many different specifications. Here we added text = element_text(size = 18) to make the text size bigger than the default and made the x-axis labels angled so they fit better using axis.text.x = element_text(angle = 25, hjust = 1). The hjust = adjusts the vertical location of the axis labels so they don’t overlap with the plot itself. vjust = can be used to move the labels right and left.

Try manipulating this plot in some way. Can you change the color of the bars? What happens if you change angle = 90?

Basic Scatter Plot

Now let’s make a scatter plot to visualize two continuous variables. We are going to check if it looks like there is a correlation between how much time people work (durWork) and how much they sleep (durSleep).

ggplot(js_data, aes(durWork, durSleep)) +
  geom_point() +
  geom_smooth() +
  xlab("Minutes Spent Working") +
  ylab ("Minutes Spent Sleeping")

  • Again we start with the ggplot() function, including telling it to use the dataset js_data
  • In the aes() function we list the x and y variables (here durWork and durSleep)
  • The geom_point() function is what makes a scatter plot
  • The geom_smooth() is what adds the correlation line to the plot
  • The xlab() and ylab() add the x-axis and y-axis labels to the plot

Improved Scatter Plot!

Now let’s add some layers and aesthetic adjustments to improve this plot.

ggplot(js_data, aes(durWork, durSleep)) +
  geom_point(color = "#2D5E7F", alpha = .2) +
  geom_smooth(method = lm, color = "black") +
  xlab("Minutes Spent Working") +
  ylab ("Minutes Spent Sleeping") +
  scale_x_continuous(breaks = seq(0, 1500, 250)) +
  labs(title = "Association Between Working and Sleeping") +
  theme(text = element_text(size = 18))

  • We can change the color of the data points by adding color = "#2D5E7F" and change the transparency of the data points (so you can see where there are overlapping clusters) with the addition of alpha = .2.
  • In the geom_smooth() function we changed the method for calculating the line to be linear (instead of the ggplot default) using method = lm and adjusted the color of the line with color =. Remember that you can write hex code numbers or the names of colors to adjust colors. Make sure both are in quotes to avoid error messages.
  • We adjust the x-axis tick marks with the scale_x_continuous(breaks = seq(0, 1500, 250)) part, which tells R to plot the x-axis on a sequence from 0 to 1500 (this captures all of the responses in our data), with labels every 250 minutes.
  • The labs(title = "Association Between Working and Sleeping") adds an overall title to the plot
  • Lastly, theme(text = element_text(size = 18)) adjust the text size

Try manipulating this plot in some way. Can you make the data points more and less transparent? Can you change the color of the line? What happens if the x-axis has labels every 100 minutes?

Your Turn!

For the following questions, the goal is to get you thinking about the building blocks that go into making a visual. Don’t get too focused on the finished product right away, and try to think about the individual steps needed to get you to there. As with any coding task, it can be very helpful to think of or write each of the steps down in plain English, then work through each one.

As a final note, a huge part of learning how to code is using search engines, and there is a ton of documentation out there to help you. This also relates back to putting things into plain-language steps, because being able to clearly articulate your goals is synonymous with using good search terms.

The data dictionary will also be an asset in working through this exercise:

Good luck!

Take a look at our data set and make two new plots.

  1. Make a plot comparing groups (i.e., a categorical variable) on one of the duration variables (i.e., a continuous variable).

  2. Make a plot comparing two continuous variables.

Challenge: What is one component of your plot you would like to change? Can you look up a solution?

See if you can recreate the plot below.


Hint: This type of chart is called a “proportional bar chart”, and search engines are your best friend!

prov_edu_plot <- ggplot(js_data, aes(x = province_fact, fill = eduLevel_fact)) +
  geom_bar(position = "fill") +
  labs(
    title = "Proportion of Education Levels by Province",
    x = "Province",
    y = "Proportion",
    fill = "Education Level"
  ) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

See if you can recreate the plot below.

Hint: First try to recreate the plot with NA values. Then, to remove NA values from the plot, you will need to use both the na.omit and the na.rm = TRUE commands.

time_alone_plot <- ggplot(na.omit(js_data), aes(x = factor(timeWantAlone), y = durAlone)) +
  geom_boxplot(fill = "blue", na.rm = TRUE) +
  scale_x_discrete(labels = c(
    "1" = "Would like more time alone",
    "2" = "Do not want more time alone"
  )) +
  labs(
    title = "Distribution of Time Spent Alone by Desire to Be Alone",
    x = "Desire to Spend Time Alone",
    y = "Duration Spent Alone - Minutes"
  )

Save Your Work

We didn’t make any changes to the data in this session, but we did create a new R script. Save that script and back it up to OSF.

Update Documentation

We’ve added a new file to our project, so we’ll also need to update the README document to reflect this.

LS0tDQp0aXRsZTogIlZpc3VhbGl6YXRpb24gd2l0aCBnZ3Bsb3Rsb3QiDQpwYWdldGl0bGU6ICJWaXN1YWxpemF0aW9uIHdpdGggZ2dwbG90Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZm9sZGluZzogc2hvdyAjIGFsbG93cyB0b2dnbGluZyBvZiBzaG93aW5nIGFuZCBoaWRpbmcgY29kZS4gUmVtb3ZlIGlmIG5vdCB1c2luZyBjb2RlLg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUgIyBhbGxvd3MgdGhlIHVzZXIgdG8gZG93bmxvYWQgdGhlIHNvdXJjZSAuUm1kIGZpbGUuIFJlbW92ZSBpZiBub3QgdXNpbmcgY29kZS4NCiAgICBpbmNsdWRlczoNCiAgICAgIGFmdGVyX2JvZHk6IGZvb3Rlci5odG1sICMgaW5jbHVkZSBhIGN1c3RvbSBmb290ZXIuDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiAzDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UNCi0tLQ0KDQpgYGB7ciwgbGlicmFyaWVzLCBpbmNsdWRlID0gRkFMU0V9DQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KYGBgDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmdzID0gRkFMU0UpDQpgYGANCg0KDQojIyBWaXN1YWxpemF0aW9ucyB3aXRoIGdncGxvdDINCg0KTm93IHRoYXQgd2UgaGF2ZSBvdXIgZGF0YSB2aXN1YWxpemF0aW9uIGltYWdpbmF0aW9ucyBnb2luZywgbGV0J3MgZ2V0IGludG8gaG93IHdlIGNhbiB2aXN1YWxseSByZXByZXNlbnQgb3VyIGRhdGEgaW4gUi4NCg0KVGhlIGdvLXRvIHBhY2thZ2UgZm9yIGRhdGEgdmlzdWFsaXphdGlvbiBpbiBSIGlzIGBnZ3Bsb3QyYCwgd2hpY2ggaXMgcGFydCBvZiB0aGUgdGlkeXZlcnNlLiBZb3UgY2FuIGZpbmQgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBgZ2dwbG90MmAgb24gdGhlIDxhIGhyZWY9Imh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnLyI+dGlkeXZlcnNlIHdlYnNpdGU8L2E+Lg0KDQpUaGlzIHBhY2thZ2UgYXBwcm9hY2hlcyBkYXRhIHZpc3VhbGl6YXRpb24gdGhyb3VnaCAiYSBncmFtbWFyIG9mIGdyYXBoaWNzLiIgSW4gb3RoZXIgd29yZHMsIHVzaW5nIHRoZSBzYW1lIHN5bnRheCwgeW91IGNhbiBjcmVhdGUgYW4gaW5maW5pdGUgbnVtYmVyIG9mIGRhdGEgdmlzdWFsaXphdGlvbnMuIEFsdGhvdWdoIHRoZXJlIGFyZSBhIGxvdCBvZiBmdW5jdGlvbnMgYW5kIGNvbXBvbmVudHMgdG8gbGVhcm4gYXQgZmlyc3QsIG9uY2UgeW91IHVuZGVyc3RhbmQgdGhlIG92ZXJhbGwgc3RydWN0dXJlIG9mIGJ1aWxkaW5nIGdyYXBoaWNzIGluIGBnZ3Bsb3QyYCwgeW91IGNhbiByZXBsaWNhdGUgYW5kIGV4cGFuZCBvbiB0aGlzIHN0cnVjdHVyZSB0byB2aXN1YWxpemUgZGF0YSBpbiBhbiB1bmxpbWl0ZWQgbnVtYmVyIG9mIHdheXMuIA0KDQoNClRoZXJlIGFyZSBtYW55IHdheXMgdG8gbWFrZSBkYXRhIHZpc3VhbGl6YXRpb25zIGluIFI7IGhvd2V2ZXIsIG90aGVyIGFwcHJvYWNoZXMgdGVuZCB0byBiZSBtb3JlIGF1dG9tYXRpYyBhbmQgY29uc2VxdWVudGx5IGxpbWl0IHRoZSBhbW91bnQgeW91IGNhbiBjaGFuZ2UgYW5kIGFkYXB0IHlvdXIgdmlzdWFsaXphdGlvbiB0byB5b3VyIG5lZWRzLiBgZ2dwbG90MmAgd29ya3MgaW4gbGF5ZXJzLCBhbGxvd2luZyBmb3IgbWF4aW11bSBjb250cm9sIGFuZCBmbGV4aWJpbGl0eS4gDQoNCiMjIyBMYXllcnMgaW4gZ2dwbG90Mg0KDQpIZXJlIGFyZSBzb21lIG9mIHRoZSBtb3N0IGNvbW1vbiBsYXllcnMgKGkuZS4sIGZ1bmN0aW9ucykgdXNlZCBpbiBgZ2dwbG90MmAuIFR5cGljYWxseSB5b3UgY29ubmVjdCB0aGVzZSBsYXllcnMgdXNpbmcgdGhlIGArYCBzeW1ib2wuIFRoZXJlIGlzIG9mdGVuIG1vcmUgdGhhbiBvbmUgd2F5IHRvIGJ1aWxkIHRoZSBzYW1lIHBsb3Qgd2l0aCB0aGUgYGdncGxvdGAgcGFja2FnZS4gDQoNCioqLSBgZ2dwbG90KClgOioqIGhvdyB5b3Ugd2lsbCBzdGFydCBtb3N0IHBsb3RzIHlvdSBidWlsZCBpbiBgZ2dwbG90MmAuIFRoZSByZXN0IG9mIHRoZSBpbmZvcm1hdGlvbiBnb2VzIHdpdGhpbiB0aGlzIGZ1bmN0aW9uLg0KDQoqKi0gYGFlcygpYDoqKiB0aGlzIGlzIHRoZSBhZXN0aGV0aWMgbWFwcGluZyBmdW5jdGlvbiwgaW4gd2hpY2ggeW91IGNhbiBjb250cm9sIGFlc3RoZXRpYyBjb21wb25lbnRzIG9mIHRoZSBwbG90LiBZb3UgY2FuIGFkZCBjb2xvcnMsIGF4aXMgbGFiZWxzLCBmb250IHNpemVzIGFuZCBtb3JlIHdpdGhpbiB0aGlzIGZ1bmN0aW9uLiBDb2xvciBhbmQgc2hhcGUgY2FuIGJlIGRlZmluZWQgYm90aCB3aXRoaW4gYW5kIG91dHNpZGUgb2YgdGhlIGFlc3RoZXRpYyBmdW5jdGlvbi4gDQoNCioqLSBgZ2VvbV9wb2ludCgpYDoqKiB1c2VkIGZvciBtYWtpbmcgYSBzY2F0dGVyIHBsb3QNCg0KKiotIGBnZW9tX2xpbmUoKWA6KiogdXNlZCBmb3IgYWRkaW5nIGEgbGluZSB0byBhIHBsb3QNCg0KKiotIGBnZW9tX2hpc3RvZ3JhbSgpYDoqKiB1c2VkIGZvciBtYWtpbmcgYSBoaXN0b2dyYW0NCg0KKiotIGBnZW9tX2NvbCgpYDoqKiB1c2VkIGZvciBtYWtpbmcgYSBiYXIgcGxvdA0KDQoqKi0gYHhsYWJgLCBgeWxhYmAsIGFuZCBgbGFicyh0aXRsZSA9IClgOioqIHVzZWQgZm9yIGFkZGluZyBheGlzIGxhYmVscyBhbmQgYW4gb3ZlcmFsbCB0aXRsZSB0byBwbG90cw0KDQoqKi0gYGNvbG9yYDoqKiBhc3NpZ25zIGEgY29sb3IgdG8gcGFydCBvZiB0aGUgcGxvdCwgc3VjaCBhcyBkaWZmZXJlbnQgZ3JvdXBzIG9yIHRoZSBkYXRhIHBvaW50cw0KDQoqKi0gYGZpbGxgOioqIGFzc2lnbnMgdGhlIGludGVyaW9yIGNvbG9yIG9mIHBhcnQgb2YgdGhlIHBsb3QsIHN1Y2ggYXMgYSBjb25maWRlbmNlIGJhbmQgb3IgdGhlIGJhcnMgaW4gYmFyIHBsb3RzDQoNCioqLSBgYWxwaGFgOioqIHVzZWQgdG8gY2hhbmdlIHRoZSB0cmFuc3BhcmVuY3kgb2YgYSBjb21wb25lbnQgb2YgdGhlIHBsb3QuIFVzZWZ1bCBpZiB5b3UgbG90cyBvZiBoYXZlIG92ZXJsYXBwaW5nIGRhdGEgcG9pbnRzIG9yIGRpc3RyaWJ1dGlvbnMgZnJvbSBtdWx0aXBsZSBncm91cHMuIA0KDQoqKi0gYHNpemVgOioqIHVzZWQgdG8gc2V0IHRoZSBzaXplIG9mIHBhcnQgb2YgdGhlIHBsb3QsIHN1Y2ggYXMgaG93IGJpZyB0aGUgZGF0YSBwb2ludHMgb3IgdGV4dCBzaG91bGQgYmUNCg0KVGhlcmUgYXJlIG1hbnkgbW9yZSBkYXRhIHZpc3VhbGl6YXRpb24gb3B0aW9ucyBpbiBgZ2dwbG90YCBidXQgdG8gZ2V0IHN0YXJ0ZWQgdG9kYXkgd2UgYXJlIGdvaW5nIHRvIGZvY3VzIG9uIG1ha2luZyBhIGJhciBwbG90IChnb29kIGZvciBjYXRlZ29yaWNhbCBkYXRhKSBhbmQgYSBzY2F0dGVyIHBsb3QgKGdvb2QgZm9yIGNvbnRpbnVvdXMgZGF0YSkuIA0KDQpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgW2dncGxvdDIgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2luZGV4Lmh0bWwpDQoNCiMjIyBDcmVhdGUgYSBOZXcgU2NyaXB0DQoNCkxldCdzIHN0YXJ0IGJ5IGNyZWF0aW5nIGEgbmV3IHNjcmlwdC4NCg0KIVtdKGltYWdlcy9ibG9jazNfY3JlYXRlLXItc2NyaXB0LmdpZikNCg0KSWYgeW91IGhhdmUgaW5zdGFsbGVkIHRoZSB0aWR5dmVyc2UsIHRoZW4gYGdncGxvdDJgIGlzIGluY2x1ZGVkLiBPdGhlcndpc2UgeW91IGNhbiBpbnN0YWxsIGl0IG5vdy4gTGV0J3MgYWxzbyBsb2FkIG91ciBkYXRhc2V0IGZvciB0b2RheS4NCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQoNCg0KOjo6bm90ZQ0KTGV0J3MgbG9hZCBvdXIgZGF0YXNldC4gIEZvciB0aGlzIGV4ZXJjaXNlLCB3ZSBhcmUgdXNpbmcgYSBkYXRhc2V0IHRoYXQgaXMgZXhhY3RseSB0aGUgc2FtZSBhcyB0aGUgb25lIHRoYXQgd2FzIHByb2R1Y2VkIGluIHRoZSBzZXNzaW9uICoqRGF0YSBUeXBlcyBhbmQgU3RydWN0dXJlcyoqLCBidXQgdGhhdCBoYXMgb25lIGV4dHJhIHZhcmlhYmxlIGFkZGVkIGNhbGxlZCBgaXNGZWVsUnVzaGVkYC4gIFRoaXMgdmFyaWFibGUgaXMgZGVyaXZlZCBmcm9tIHRoZSBgZmVlbFJ1c2hlZGAgdmFyaWFibGUsIHdpdGggdGhlIG51bWJlciBgMWAgaW5kaWNhdGluZyB0aGF0IHNvbWVib2R5IGZlZWxzIHJ1c2hlZCwgYW5kIGAwYCBpbmRpY2F0aW5nIHRoZXkgZG8gbm90IGZlZWwgcnVzaGVkLiAgRm9yIG1vcmUgaW5mb3JtYXRpb24gb24gdGhpcyB2YXJpYWJsZSBhbmQgaG93IGl0IHdhcyBjcmVhdGVkLCBzZWUgdGhlIHNlc3Npb24gW1I6IEZpbHRlciBhbmQgU2VsZWN0XShodHRwczovL2FsbGlhbmNlLXJkbS1nZHIuZ2l0aHViLmlvL3JkbS1qdW1wc3RhcnQvMy1BQ1QtMy1GaWx0ZXJTZWxlY3QuaHRtbCkNCg0KOjo6DQoNCmBgYHtyfQ0KbG9hZCgiZGF0YS9ibG9jay01X3Zpc3VhbGl6YXRpb24uUkRhdGEiKQ0KYGBgDQoNCiMjIyBCYXNpYyBCYXIgUGxvdDogQ291bnRzDQpMZXQncyBtYWtlIG91ciBmaXJzdCBwbG90IGluIFIhIFdlIGFyZSBnb2luZyB0byBzbG93bHkgYWRkIGxheWVycywgYnVpbGRpbmcgdXAgdG8gYSBib3ggcGxvdCByZXByZXNlbnRpbmcgdGhlIG51bWJlciBvZiBwZW9wbGUgaW4gZWFjaCBncm91cCBpbiB0aGUgYGlzRmVlbFJ1c2hlZGAgdmFyaWFibGUuDQoNCldoZW4gY3JlYXRpbmcgdmlzdWFsaXphdGlvbnMsIGl0J3MgYWx3YXlzIGdvb2QgdG8gaGF2ZSBhIHNlbnNlIG9mIHRoZSB2YXJpYWJsZXMgeW91J3JlIHdvcmtpbmcgd2l0aC4gIEl0IGNhbiBiZSBoZWxwZnVsIHRvIHVzZSB0aGUgYFZpZXcoKWAgY29tbWFuZCB3ZSBkaXNjdXNzZWQgaW4gdGhlIHNlc3Npb24gKipGaXJzdCBTdGVwcyBpbiBSKio6DQoNCmBgYHtyLCByZXN1bHRzID0gJ2hpZGUnfQ0KVmlldyhqc19kYXRhKQ0KYGBgDQoNCkJlY2F1c2Ugd2Uga25vdyB0aGF0IHdlJ3JlIGdvaW5nIHRvIGJlIHN0YXJ0aW5nIHdpdGggdGhlIGBpc0ZlZWxSdXNoZWRgIHZhcmlhYmxlLCBpdCdzIGFsc28gaGVscGZ1bCB0byBrbm93IHdoYXQgaXRzIGRhdGEgdHlwZSBpczoNCg0KYGBge3IsIHJlc3VsdHMgPSAnaGlkZSd9DQpjbGFzcyhqc19kYXRhJGlzRmVlbFJ1c2hlZCkNCmBgYA0KDQo8YnI+DQoNCiMjIyMgU3RhcnRpbmcgdGhlIFBsb3QNCg0KRmlyc3Qgd2UgY3JlYXRlIHRoZSBibGFuayBwbG90IG9uIHdoaWNoIHdlIHdpbGwgYWRkIG91ciBkYXRhLiBXZSBuZWFybHkgYWx3YXlzIHN0YXJ0IHdpdGggdGhlIGZ1bmN0aW9uIGBnZ3Bsb3RgIGFuZCB0aGVuIHRlbGxpbmcgdGhlIGZ1bmN0aW9uIHdoYXQgZGF0YXNldCB0byB1c2UuIA0KDQpgYGB7cn0NCmdncGxvdChqc19kYXRhKQ0KYGBgDQoNClRoaXMgY3JlYXRlcyBvdXIgYmxhbmsgY2FudmFzLg0KDQpOZXh0IHdlIHRlbGwgZ2dwbG90IHdoYXQgdmFyaWFibGUgd2Ugd2FudCB0byB1c2UgYW5kIHB1dCBpdCBpbiB0aGUgYGFlcygpYCBmdW5jdGlvbi4gSWYgeW91IHdhbnQgYSBiYXIgY2hhcnQgcmVwcmVzZW50aW5nIHRoZSBudW1iZXIgb2YgcGVvcGxlIGluIGVhY2ggZ3JvdXAsIHlvdSBjYW4gYWRkIGp1c3Qgb25lIHZhcmlhYmxlIHRvIHRoZSBgYWVzKClgIGZ1bmN0aW9uLiBXZSBpbmNsdWRlIHRoZSBgYXMuZmFjdG9yYCBmdW5jdGlvbiBhcm91bmQgdGhlIGBpc0ZlZWxSdXNoZWRgIHZhcmlhYmxlIHNvIGl0IGlzIHRyZWF0ZWQgYXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBmb3IgdGhlIGJveCBwbG90IGluc3RlYWQgb2YgYSBudW1lcmljIHZhcmlhYmxlLiANCg0KYGBge3J9DQpnZ3Bsb3QoanNfZGF0YSwgYWVzKHggPSBhcy5mYWN0b3IoaXNGZWVsUnVzaGVkKSkpDQpgYGANCg0KWW91IG5vdyBzZWUgdGhhdCB0aGUgZ3JpZCByZXByZXNlbnRzIGEgc2NhbGUgcmVsZXZhbnQgdG8gdGhhdCB2YXJpYWJsZS4gDQoNCk5leHQsIHdlIHRlbGwgZ2dwbG90IHdoYXQgdHlwZSBvZiBkYXRhIHZpc3VhbGl6YXRpb24gd2Ugd2FudC4gVG8gY3JlYXRlIGEgYmFyIGNoYXJ0IHdlIHVzZSB0aGUgZnVuY3Rpb24gYGdlb21fYmFyKClgLiBUbyBzZWUgaG93IG1hbnkgcGVvcGxlIGFyZSBpbiBlYWNoIG9mIHRoZSBgaXNGZWVsUnVzaGVkYCBncm91cHMsIHdlIHVzZSB0aGUgZGVmYXVsdCBgZ2VvbV9iYXIoc3RhdCA9ICJjb3VudCIpYC4gUmVtZW1iZXIgdGhhdCB3ZSBjb25uZWN0IGxheWVycyB3aXRoIGEgYCtgIHN5bWJvbC4gDQoNCmBgYHtyfQ0KZ2dwbG90KGpzX2RhdGEsIGFlcyh4ID0gYXMuZmFjdG9yKGlzRmVlbFJ1c2hlZCkpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiY291bnQiKQ0KYGBgDQoNCldlJ3ZlIGdvdCBhIGJhciBjaGFydCENCg0KTGFzdGx5LCBsZXQncyBhZGQgc29tZSBsYWJlbHMgdG8gdGhlIHgtYXhpcyBhbmQgeS1heGlzIHRvIG1ha2UgaXQgY2xlYXIgd2hhdCBpcyBiZWluZyBwbG90dGVkLiANCg0KYGBge3J9DQpnZ3Bsb3QoanNfZGF0YSwgYWVzKHggPSBhcy5mYWN0b3IoaXNGZWVsUnVzaGVkKSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJjb3VudCIpICsgDQogIHhsYWIoIkZlZWxpbmcgUnVzaGVkIikgKw0KICB5bGFiICgiTnVtYmVyIG9mIFBhcnRpY2lwYW50cyIpIA0KYGBgDQoNCiMjIyBCYXNpYyBCYXIgUGxvdDogR3JvdXAgTWVhbnMNCg0KTm93LCBsZXQncyBzZWUgaWYgcGVvcGxlIHdobyBmZWVsIHJ1c2hlZCB0ZW5kIHRvIHdvcmsgbW9yZSB0aGFuIHBlb3BsZSB3aG8gZG8gbm90IGZlZWwgcnVzaGVkLiBUbyByZXByZXNlbnQgdGhlIG1lYW4gZm9yIGVhY2ggZ3JvdXAgb3Igc29tZSBvdGhlciB2YXJpYWJsZSwgeW91IGFkZCBib3RoIGFuIHggYW5kIGEgeSB2YXJpYWJsZSB0byB0aGUgYGFlcygpYCBmdW5jdGlvbiBhbmQgdXNlIHRoZSBgZ2VvbV9iYXIoc3RhdCA9ICJzdW1tYXJ5IilgLiBOb3RlIHRoYXQgdGhlIFktYXhpcyBzY2FsZSBoYXMgbm93IGFkanVzdGVkIHRvIGEgc2NhbGUgdGhhdCBtYXRjaGVzIHRoZSB2YXJpYWJsZSB3ZSBhcmUgdXNpbmcgKGkuZS4sIG1lYW4gbnVtYmVyIG9mIG1pbnV0ZXMgc3BlbnQgd29ya2luZyBmb3IgZWFjaCBncm91cCkuIA0KDQpgYGB7cn0NCmdncGxvdChqc19kYXRhLCBhZXMoeCA9IGFzLmZhY3Rvcihpc0ZlZWxSdXNoZWQpLCB5ID0gZHVyV29yaykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJzdW1tYXJ5IikgKyANCiAgeGxhYigiRmVlbGluZyBSdXNoZWQiKSArDQogIHlsYWIgKCJBdmVyYWdlIE1pbnV0ZXMgV29ya2luZyIpIA0KYGBgDQoNCjo6OndhbGt0aHJvdWdoDQogIC0gTW9zdCBwbG90cyB3aWxsIHN0YXJ0IHdpdGggdGhlIGBnZ3Bsb3QoKWBmdW5jdGlvbg0KICAtIFlvdSBoYXZlIHRvIGluY2x1ZGUgdGhlIG9iamVjdCB3aGVyZSBgZ2dwbG90KClgIHdpbGwgZ2V0IHRoZSBpbmZvcm1hdGlvbiBmb3IgdGhlIHBsb3QgZnJvbS4gSW4gdGhpcyBjYXNlLCBpdCdzIG91ciBkYXRhc2V0IGBqc19kYXRhYA0KICAtIFdpdGhpbiB0aGUgYGFlcygpYCBmdW5jdGlvbiwgd2UgaWRlbnRpZnkgd2hhdCB0aGUgeCBhbmQgeSB2YXJpYWJsZXMgYXJlIGZvciB0aGlzIHBsb3QNCiAgLSBXZSBhZGQgbGF5ZXJzIHVzaW5nIHRoZSBgK2Agc2lnbg0KICAtIE5leHQgd2UgdGVsbCBgZ2dwbG90KClgIHdoYXQgdHlwZSBvZiBwbG90IHdlIGFyZSBtYWtpbmc7IGluIHRoaXMgY2FzZSB3ZSBhcmUgY3JlYXRpbmcgYSBiYXIgcGxvdCB1c2luZyB0aGUgZnVuY3Rpb24gYGdlb21fYmFyKClgDQogIC0gVGhlbiB3ZSBhZGQgd2hhdCB0eXBlIG9mIHN0YXRpc3RpYyB3ZSB3YW50IHByZXNlbnRlZCBvbiB0aGUgcGxvdC4gSGVyZSB3ZSBhc2sgZm9yIHRoZSBtZWFuIG9mIGVhY2ggZ3JvdXAgZm9yIHRoZSB2YXJpYWJsZSBkdXJXb3JrIHVzaW5nIHRoZSBmdW5jdGlvbiBgZ2VvbV9iYXIoc3RhdCA9ICJzdW1tYXJ5IilgDQogIC0gV2UgYWRkIGEgbGFiZWwgdG8gdGhlIHgtYXhpcyB3aXRoIGBsYWIoIkZlZWxpbmcgUnVzaGVkIilgDQogIC0gV2UgYWRkIHRoZSBsYWJlbCB0byB0aGUgeS1heGlzIHdpdGggYHlsYWIgKCJUaW1lIFdvcmtpbmciKWANCiAgDQo6OjoNCg0KIyMjIEltcHJvdmVkIEJveCBQbG90ISANClRoaXMgcGxvdCBnZXRzIHRoZSBpZGVhIGFjcm9zcywgYnV0IHdlIGNhbiBhZGQgbW9yZSBsYXllcnMgYW5kIGZ1bmN0aW9ucyB0byBtYWtlIG1vcmUgYWRqdXN0bWVudHMuIFNlZSB0aGUgd2Fsa3Rocm91Z2ggYmVsb3cgZm9yIGhvdyB3ZSBtYWRlIGFsbCB0aGVzZSBjaGFuZ2VzLg0KDQpgYGB7cn0NCg0KcGxvdGxhYmVscyA8LSBjKCJOb3QgUnVzaGVkIiwgIlJ1c2hlZCIsICJEaWQgTm90IFJlc3BvbmQiKQ0KDQpnZ3Bsb3QoanNfZGF0YSwgYWVzKHggPSBhcy5mYWN0b3IoaXNGZWVsUnVzaGVkKSwgeSA9IGR1cldvcmspKSArDQogIGdlb21fYmFyKHN0YXQgPSAic3VtbWFyeSIsIGZpbGwgPSAiIzJENUU3RiIpICsgDQogIHhsYWIoIkZlZWxpbmcgUnVzaGVkIikgKw0KICB5bGFiICgiQXZlcmFnZSBNaW51dGVzIFdvcmtpbmciKSArDQogIGxhYnModGl0bGUgPSAiV29ya2luZyBhbmQgRmVlbGluZyBSdXNoZWQiKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gcGxvdGxhYmVscykgKw0KICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxOCksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMjUsIGhqdXN0ID0gMSkpDQogIA0KYGBgDQoNCjo6OndhbGt0aHJvdWdoDQogIC0gV2UgY2FuIGNoYW5nZSB0aGUgY29sb3Igb2YgdGhlIGJhcnMgdXNpbmcgYGZpbGwgPSBgLiBIZXJlIHdlIGFkZGVkIHRoZSBzcGVjaWZpYyBjb2xvciB1c2luZyBhIGhleCBjb2RlLiBCdXQgeW91IGNhbiBhbHNvIHdyaXRlIGluIHRoZSBuYW1lcyBvZiBjb2xvcnMgc3VjaCBhcyAiYmx1ZSIuDQogIC0gVXNpbmcgYGxhYnModGl0bGUgPSAiIilgIHdlIGFkZGVkIGFuIG92ZXJhbGwgdGl0bGUgdG8gdGhlIHBsb3QNCiAgLSBXZSBwcm9iYWJseSB3YW50IHRvIGluZGljYXRlIHdoYXQgZWFjaCBjYXRlZ29yeSByZXByZXNlbnRzLCByYXRoZXIgdGhhbiB0aGUgIjAiLCAiMSIgYW5kICJOQSIgbGFiZWxzLiBUbyBhZGQgdGV4dCBsYWJlbHMsIHdlIGZpcnN0IGNyZWF0ZSBhbiBvYmplY3Qgd2l0aCBlYWNoIG9mIHRob3NlIGxhYmVscyBpbiBvcmRlciAoYHBsb3RsYWJlbHMgPC0gYygiTm90IFJ1c2hlZCIsICJSdXNoZWQiLCAiRGlkIE5vdCBSZXNwb25kIilgKS4gVGhlbiBpbiB0aGUgYHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gcGxvdGxhYmVscylgIGZ1bmN0aW9uIHdlIGNhbGwgdG8gdGhhdCBvYmplY3Qgd2UgY3JlYXRlZCBhcyB0aGUgbGFiZWxzIGZvciB0aGUgeC1heGlzLiBUaGVyZSBhcmUgbWFueSBvdGhlciB3YXlzIHRvIGFkanVzdCB0aGUgbGFiZWxzIGZvciBlYWNoIGF4aXMsIGJ1dCB0aGlzIG1ldGhvZCB3b3JrcyB3ZWxsIGZvciBhIHNtYWxsIG51bWJlciBvZiBncm91cHMuDQogIC0gSW4gdGhlIGB0aGVtZSgpYCBsYXllciB5b3UgY2FuIGFkZCBtYW55IGRpZmZlcmVudCBzcGVjaWZpY2F0aW9ucy4gSGVyZSB3ZSBhZGRlZCBgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgpYCB0byBtYWtlIHRoZSB0ZXh0IHNpemUgYmlnZ2VyIHRoYW4gdGhlIGRlZmF1bHQgYW5kIG1hZGUgdGhlIHgtYXhpcyBsYWJlbHMgYW5nbGVkIHNvIHRoZXkgZml0IGJldHRlciB1c2luZyBgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAyNSwgaGp1c3QgPSAxKWAuIFRoZSBgaGp1c3QgPSBgIGFkanVzdHMgdGhlIHZlcnRpY2FsIGxvY2F0aW9uIG9mIHRoZSBheGlzIGxhYmVscyBzbyB0aGV5IGRvbid0IG92ZXJsYXAgd2l0aCB0aGUgcGxvdCBpdHNlbGYuIGB2anVzdCA9IGAgY2FuIGJlIHVzZWQgdG8gbW92ZSB0aGUgbGFiZWxzIHJpZ2h0IGFuZCBsZWZ0LiANCiAgDQo6OjoNCg0KOjo6cXVlc3Rpb24NClRyeSBtYW5pcHVsYXRpbmcgdGhpcyBwbG90IGluIHNvbWUgd2F5LiBDYW4geW91IGNoYW5nZSB0aGUgY29sb3Igb2YgdGhlIGJhcnM/IFdoYXQgaGFwcGVucyBpZiB5b3UgY2hhbmdlIGBhbmdsZSA9IDkwYD8NCjo6Og0KDQojIyMgQmFzaWMgU2NhdHRlciBQbG90DQpOb3cgbGV0J3MgbWFrZSBhIHNjYXR0ZXIgcGxvdCB0byB2aXN1YWxpemUgdHdvIGNvbnRpbnVvdXMgdmFyaWFibGVzLiBXZSBhcmUgZ29pbmcgdG8gY2hlY2sgaWYgaXQgbG9va3MgbGlrZSB0aGVyZSBpcyBhIGNvcnJlbGF0aW9uIGJldHdlZW4gaG93IG11Y2ggdGltZSBwZW9wbGUgd29yayAoZHVyV29yaykgYW5kIGhvdyBtdWNoIHRoZXkgc2xlZXAgKGR1clNsZWVwKS4NCg0KYGBge3J9DQpnZ3Bsb3QoanNfZGF0YSwgYWVzKGR1cldvcmssIGR1clNsZWVwKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgeGxhYigiTWludXRlcyBTcGVudCBXb3JraW5nIikgKw0KICB5bGFiICgiTWludXRlcyBTcGVudCBTbGVlcGluZyIpDQogICAgICANCmBgYA0KDQo6Ojp3YWxrdGhyb3VnaA0KICAtIEFnYWluIHdlIHN0YXJ0IHdpdGggdGhlIGBnZ3Bsb3QoKWAgZnVuY3Rpb24sIGluY2x1ZGluZyB0ZWxsaW5nIGl0IHRvIHVzZSB0aGUgZGF0YXNldCBganNfZGF0YWANCiAgLSBJbiB0aGUgYGFlcygpYCBmdW5jdGlvbiB3ZSBsaXN0IHRoZSB4IGFuZCB5IHZhcmlhYmxlcyAoaGVyZSBkdXJXb3JrIGFuZCBkdXJTbGVlcCkNCiAgLSBUaGUgYGdlb21fcG9pbnQoKWAgZnVuY3Rpb24gaXMgd2hhdCBtYWtlcyBhIHNjYXR0ZXIgcGxvdA0KICAtIFRoZSBnZW9tX3Ntb290aCgpIGlzIHdoYXQgYWRkcyB0aGUgY29ycmVsYXRpb24gbGluZSB0byB0aGUgcGxvdA0KICAtIFRoZSBgeGxhYigpYCBhbmQgYHlsYWIoKWAgYWRkIHRoZSB4LWF4aXMgYW5kIHktYXhpcyBsYWJlbHMgdG8gdGhlIHBsb3QNCiAgDQo6OjoNCiMjIyBJbXByb3ZlZCBTY2F0dGVyIFBsb3QhDQoNCk5vdyBsZXQncyBhZGQgc29tZSBsYXllcnMgYW5kIGFlc3RoZXRpYyBhZGp1c3RtZW50cyB0byBpbXByb3ZlIHRoaXMgcGxvdC4NCg0KYGBge3J9DQpnZ3Bsb3QoanNfZGF0YSwgYWVzKGR1cldvcmssIGR1clNsZWVwKSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gIiMyRDVFN0YiLCBhbHBoYSA9IC4yKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtLCBjb2xvciA9ICJibGFjayIpICsNCiAgeGxhYigiTWludXRlcyBTcGVudCBXb3JraW5nIikgKw0KICB5bGFiICgiTWludXRlcyBTcGVudCBTbGVlcGluZyIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAxNTAwLCAyNTApKSArDQogIGxhYnModGl0bGUgPSAiQXNzb2NpYXRpb24gQmV0d2VlbiBXb3JraW5nIGFuZCBTbGVlcGluZyIpICsNCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgpKQ0KYGBgDQoNCjo6OndhbGt0aHJvdWdoDQogIC0gV2UgY2FuIGNoYW5nZSB0aGUgY29sb3Igb2YgdGhlIGRhdGEgcG9pbnRzIGJ5IGFkZGluZyBgY29sb3IgPSAiIzJENUU3RiJgIGFuZCBjaGFuZ2UgdGhlIHRyYW5zcGFyZW5jeSBvZiB0aGUgZGF0YSBwb2ludHMgKHNvIHlvdSBjYW4gc2VlIHdoZXJlIHRoZXJlIGFyZSBvdmVybGFwcGluZyBjbHVzdGVycykgd2l0aCB0aGUgYWRkaXRpb24gb2YgYGFscGhhID0gLjJgLiANCiAgLSBJbiB0aGUgYGdlb21fc21vb3RoKClgIGZ1bmN0aW9uIHdlIGNoYW5nZWQgdGhlIG1ldGhvZCBmb3IgY2FsY3VsYXRpbmcgdGhlIGxpbmUgdG8gYmUgbGluZWFyIChpbnN0ZWFkIG9mIHRoZSBnZ3Bsb3QgZGVmYXVsdCkgdXNpbmcgYG1ldGhvZCA9IGxtYCBhbmQgYWRqdXN0ZWQgdGhlIGNvbG9yIG9mIHRoZSBsaW5lIHdpdGggYGNvbG9yID0gYC4gUmVtZW1iZXIgdGhhdCB5b3UgY2FuIHdyaXRlIGhleCBjb2RlIG51bWJlcnMgb3IgdGhlIG5hbWVzIG9mIGNvbG9ycyB0byBhZGp1c3QgY29sb3JzLiBNYWtlIHN1cmUgYm90aCBhcmUgaW4gcXVvdGVzIHRvIGF2b2lkIGVycm9yIG1lc3NhZ2VzLiANCiAgLSBXZSBhZGp1c3QgdGhlIHgtYXhpcyB0aWNrIG1hcmtzIHdpdGggdGhlIGBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE1MDAsIDI1MCkpYCBwYXJ0LCB3aGljaCB0ZWxscyBSIHRvIHBsb3QgdGhlIHgtYXhpcyBvbiBhIHNlcXVlbmNlIGZyb20gMCB0byAxNTAwICh0aGlzIGNhcHR1cmVzIGFsbCBvZiB0aGUgcmVzcG9uc2VzIGluIG91ciBkYXRhKSwgd2l0aCBsYWJlbHMgZXZlcnkgMjUwIG1pbnV0ZXMuIA0KICAtIFRoZSBgbGFicyh0aXRsZSA9ICJBc3NvY2lhdGlvbiBCZXR3ZWVuIFdvcmtpbmcgYW5kIFNsZWVwaW5nIilgIGFkZHMgYW4gb3ZlcmFsbCB0aXRsZSB0byB0aGUgcGxvdA0KICAtIExhc3RseSwgYHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4KSlgIGFkanVzdCB0aGUgdGV4dCBzaXplDQogIA0KOjo6DQoNCjo6OnF1ZXN0aW9uDQpUcnkgbWFuaXB1bGF0aW5nIHRoaXMgcGxvdCBpbiBzb21lIHdheS4gQ2FuIHlvdSBtYWtlIHRoZSBkYXRhIHBvaW50cyBtb3JlIGFuZCBsZXNzIHRyYW5zcGFyZW50PyBDYW4geW91IGNoYW5nZSB0aGUgY29sb3Igb2YgdGhlIGxpbmU/IFdoYXQgaGFwcGVucyBpZiB0aGUgeC1heGlzIGhhcyBsYWJlbHMgZXZlcnkgMTAwIG1pbnV0ZXM/DQo6OjoNCg0KIyMjIFlvdXIgVHVybiENCg0KOjo6bm90ZQ0KDQpGb3IgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnMsIHRoZSBnb2FsIGlzIHRvIGdldCB5b3UgdGhpbmtpbmcgYWJvdXQgdGhlIGJ1aWxkaW5nIGJsb2NrcyB0aGF0IGdvIGludG8gbWFraW5nIGEgdmlzdWFsLiAgRG9uJ3QgZ2V0IHRvbyBmb2N1c2VkIG9uIHRoZSBmaW5pc2hlZCBwcm9kdWN0IHJpZ2h0IGF3YXksIGFuZCB0cnkgdG8gdGhpbmsgYWJvdXQgdGhlIGluZGl2aWR1YWwgc3RlcHMgbmVlZGVkIHRvIGdldCB5b3UgdG8gdGhlcmUuICBBcyB3aXRoIGFueSBjb2RpbmcgdGFzaywgaXQgY2FuIGJlIHZlcnkgaGVscGZ1bCB0byB0aGluayBvZiBvciB3cml0ZSBlYWNoIG9mIHRoZSBzdGVwcyBkb3duIGluIHBsYWluIEVuZ2xpc2gsIHRoZW4gd29yayB0aHJvdWdoIGVhY2ggb25lLiAgDQoNCkFzIGEgZmluYWwgbm90ZSwgYSAqKmh1Z2UqKiBwYXJ0IG9mIGxlYXJuaW5nIGhvdyB0byBjb2RlIGlzIHVzaW5nIHNlYXJjaCBlbmdpbmVzLCBhbmQgdGhlcmUgaXMgYSB0b24gb2YgZG9jdW1lbnRhdGlvbiBvdXQgdGhlcmUgdG8gaGVscCB5b3UuICBUaGlzIGFsc28gcmVsYXRlcyBiYWNrIHRvIHB1dHRpbmcgdGhpbmdzIGludG8gcGxhaW4tbGFuZ3VhZ2Ugc3RlcHMsIGJlY2F1c2UgYmVpbmcgYWJsZSB0byBjbGVhcmx5IGFydGljdWxhdGUgeW91ciBnb2FscyBpcyBzeW5vbnltb3VzIHdpdGggdXNpbmcgZ29vZCBzZWFyY2ggdGVybXMuDQoNClRoZSBkYXRhIGRpY3Rpb25hcnkgd2lsbCBhbHNvIGJlIGFuIGFzc2V0IGluIHdvcmtpbmcgdGhyb3VnaCB0aGlzIGV4ZXJjaXNlOg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgb3V0LndpZHRoPSI5NSUiLCBvdXQuaGVpZ2h0PSI1MDBpbiJ9DQogICAga25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9CbG9jazQtMl9EaWN0aW9uYXJ5LnBkZiIpDQpgYGANCg0KR29vZCBsdWNrIQ0KDQo6OjoNCg0KDQo6OjogcXVlc3Rpb24NClRha2UgYSBsb29rIGF0IG91ciBkYXRhIHNldCBhbmQgbWFrZSB0d28gbmV3IHBsb3RzLiANCg0KMS4gTWFrZSBhIHBsb3QgY29tcGFyaW5nIGdyb3VwcyAoaS5lLiwgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSkgb24gb25lIG9mIHRoZSBkdXJhdGlvbiB2YXJpYWJsZXMgKGkuZS4sIGEgY29udGludW91cyB2YXJpYWJsZSkuDQoNCjIuIE1ha2UgYSBwbG90IGNvbXBhcmluZyB0d28gY29udGludW91cyB2YXJpYWJsZXMuIA0KOjo6DQoNCjo6OiBxdWVzdGlvbg0KDQoqKkNoYWxsZW5nZToqKiBXaGF0IGlzIG9uZSBjb21wb25lbnQgb2YgeW91ciBwbG90IHlvdSB3b3VsZCBsaWtlIHRvIGNoYW5nZT8gQ2FuIHlvdSBsb29rIHVwIGEgc29sdXRpb24/IA0KOjo6DQoNCjo6OnF1ZXN0aW9uDQpTZWUgaWYgeW91IGNhbiByZWNyZWF0ZSB0aGUgcGxvdCBiZWxvdy4NCg0KPGJyPg0KDQoqKkhpbnQ6KiogVGhpcyB0eXBlIG9mIGNoYXJ0IGlzIGNhbGxlZCBhICJwcm9wb3J0aW9uYWwgYmFyIGNoYXJ0IiwgYW5kIHNlYXJjaCBlbmdpbmVzIGFyZSB5b3VyIGJlc3QgZnJpZW5kIQ0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL2Jsb2NrNS0yX3Byb3YtZWR1LnBuZyIpDQpgYGANCg0KYGBge3IgY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZScsIHJlc3VsdHMgPSAnaGlkZSd9DQpwcm92X2VkdV9wbG90IDwtIGdncGxvdChqc19kYXRhLCBhZXMoeCA9IHByb3ZpbmNlX2ZhY3QsIGZpbGwgPSBlZHVMZXZlbF9mYWN0KSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlByb3BvcnRpb24gb2YgRWR1Y2F0aW9uIExldmVscyBieSBQcm92aW5jZSIsDQogICAgeCA9ICJQcm92aW5jZSIsDQogICAgeSA9ICJQcm9wb3J0aW9uIiwNCiAgICBmaWxsID0gIkVkdWNhdGlvbiBMZXZlbCINCiAgKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQpgYGANCg0KOjo6DQoNCjo6OnF1ZXN0aW9uDQpTZWUgaWYgeW91IGNhbiByZWNyZWF0ZSB0aGUgcGxvdCBiZWxvdy4gIA0KPGJyPg0KKipIaW50OioqIEZpcnN0IHRyeSB0byByZWNyZWF0ZSB0aGUgcGxvdCB3aXRoIE5BIHZhbHVlcy4gIFRoZW4sIHRvIHJlbW92ZSBgTkFgIHZhbHVlcyBmcm9tIHRoZSBwbG90LCB5b3Ugd2lsbCBuZWVkIHRvIHVzZSBib3RoIHRoZSBgbmEub21pdGAgYW5kIHRoZSBgbmEucm0gPSBUUlVFYCBjb21tYW5kcy4NCg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL2Jsb2NrNS0yX3RpbWUtYWxvbmUucG5nIikNCmBgYA0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJywgcmVzdWx0cyA9ICdoaWRlJ30NCnRpbWVfYWxvbmVfcGxvdCA8LSBnZ3Bsb3QobmEub21pdChqc19kYXRhKSwgYWVzKHggPSBmYWN0b3IodGltZVdhbnRBbG9uZSksIHkgPSBkdXJBbG9uZSkpICsNCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAiYmx1ZSIsIG5hLnJtID0gVFJVRSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGMoDQogICAgIjEiID0gIldvdWxkIGxpa2UgbW9yZSB0aW1lIGFsb25lIiwNCiAgICAiMiIgPSAiRG8gbm90IHdhbnQgbW9yZSB0aW1lIGFsb25lIg0KICApKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIFRpbWUgU3BlbnQgQWxvbmUgYnkgRGVzaXJlIHRvIEJlIEFsb25lIiwNCiAgICB4ID0gIkRlc2lyZSB0byBTcGVuZCBUaW1lIEFsb25lIiwNCiAgICB5ID0gIkR1cmF0aW9uIFNwZW50IEFsb25lIC0gTWludXRlcyINCiAgKQ0KYGBgDQoNCjo6Og0KDQoNCg0KIyMgU2F2ZSBZb3VyIFdvcmsNCg0KV2UgZGlkbid0IG1ha2UgYW55IGNoYW5nZXMgdG8gdGhlIGRhdGEgaW4gdGhpcyBzZXNzaW9uLCBidXQgd2UgZGlkIGNyZWF0ZSBhIG5ldyBSIHNjcmlwdC4gIFNhdmUgdGhhdCBzY3JpcHQgYW5kIGJhY2sgaXQgdXAgdG8gT1NGLg0KDQohW10oaW1hZ2VzL29zZi9vc2ZVcGxvYWQuZ2lmKQ0KDQoNCiMjIFVwZGF0ZSBEb2N1bWVudGF0aW9uDQoNCldlJ3ZlIGFkZGVkIGEgbmV3IGZpbGUgdG8gb3VyIHByb2plY3QsIHNvIHdlJ2xsIGFsc28gbmVlZCB0byB1cGRhdGUgdGhlIFJFQURNRSBkb2N1bWVudCB0byByZWZsZWN0IHRoaXMuDQoNCg0KDQoNCg0KDQo=