The actual process of analysis can be constructed from a set of verb actions that we apply to raw data so that we can extract meaningful insights.

The Data

For this topic, we will use a moderate sized data set from the Rice Rivers Center which contains water and atmospheric data from a stream of sensors in both the James River and on the bluff overlooking the river.

library( readr )
url <- "https://docs.google.com/spreadsheets/d/1Mk1YGH9LqjF7drJE-td1G_JkdADOU0eMlrP01WFBT8s/pub?gid=0&single=true&output=csv"
rice <- read_csv( url )

These data have 8199 records measured on the 23 columns and represent samples collected every 15-minutes starting on 1/1/2014 12:00:00 AM and ending on 3/27/2014 9:30:00 AM.

names( rice )
 [1] "DateTime"                       "RecordID"                      
 [3] "PAR"                            "WindSpeed_mph"                 
 [5] "WindDir"                        "AirTempF"                      
 [7] "RelHumidity"                    "BP_HG"                         
 [9] "Rain_in"                        "H2O_TempC"                     
[11] "SpCond_mScm"                    "Salinity_ppt"                  
[13] "PH"                             "PH_mv"                         
[15] "Turbidity_ntu"                  "Chla_ugl"                      
[17] "BGAPC_CML"                      "BGAPC_rfu"                     
[19] "ODO_sat"                        "ODO_mgl"                       
[21] "Depth_ft"                       "Depth_m"                       
[23] "SurfaceWaterElev_m_levelNad83m"
DateTime

RecordID

PAR

WindSpeed_mph

WindDir

AirTempF

RelHumidity

BP_HG

Rain_in

H2O_TempC

SpCond_mScm

Salinity_ppt

PH

PH_mv

Turbidity_ntu

Chla_ugl

BGAPC_CML

BGAPC_rfu

ODO_sat

ODO_mgl

Depth_ft

Depth_m

SurfaceWaterElev_m_levelNad83m

Workflows for Data Analyses.

If we think about it, there are some fundamental processes that we use to work with and manipulate data. These include:

These verbs and/or actions can be combined in many ways to allow us to extract information from raw data. Examples may include:

Standard R Approaches

To begin, we will walk through the ways that this can be done using normal R syntax, which will include a being cleaver in how we use indices, mix in a bunch of logical operators, and a smattering of $ operators, and we are good to go!

Selecting Columns

To select columns, we use either the column names as character objects in the column index position (e.g., after the comma in the square brackets1)

df <- rice[, c("DateTime","PAR","WindDir","PH")]
summary( df )
   DateTime              PAR              WindDir             PH      
 Length:8199        Min.   :   0.000   Min.   :  0.00   Min.   :6.43  
 Class :character   1st Qu.:   0.000   1st Qu.: 37.31   1st Qu.:7.50  
 Mode  :character   Median :   0.046   Median :137.30   Median :7.58  
                    Mean   : 241.984   Mean   :146.20   Mean   :7.60  
                    3rd Qu.: 337.900   3rd Qu.:249.95   3rd Qu.:7.69  
                    Max.   :1957.000   Max.   :360.00   Max.   :9.00  
                                                        NA's   :1     

or the column indices in the square-brackets.

df <- rice[ c(1,3,5,13)]
summary( df )
   DateTime              PAR              WindDir             PH      
 Length:8199        Min.   :   0.000   Min.   :  0.00   Min.   :6.43  
 Class :character   1st Qu.:   0.000   1st Qu.: 37.31   1st Qu.:7.50  
 Mode  :character   Median :   0.046   Median :137.30   Median :7.58  
                    Mean   : 241.984   Mean   :146.20   Mean   :7.60  
                    3rd Qu.: 337.900   3rd Qu.:249.95   3rd Qu.:7.69  
                    Max.   :1957.000   Max.   :360.00   Max.   :9.00  
                                                        NA's   :1     

As you can see, the first one is probably more effective than the second one because by simply looking at the code, we can see what columns we are going to grab. Moreover, if you are using RStudio for this, you should be able to get the very helpful autocorrect to pop up and give you the names of the columns.

Figure 1: Popup help for column names in RStudio for a data frame in memory

Using numbers has no help like this.

Filtering Rows

To filter rows, we can do the same thing as for selecting columns. Here we can either numerical values to take slices (e.g., I’m grabbing the first 96 entries which correspond to all the entries taken on January 1, 2014):

df1 <- df[ 1:96, ]
head( df1 )

Or we can use logical operators that test some logical condition based upon the values in the data.frame.

For logical operators (returning TRUE, FALSE or NA), the following are available:

Operator Definition
!= Not equal to
== Equal to
> Strictly greater than
>= Greater than OR equal to
< Strictly less than
<= Less than or equal to.

For example, maybe I only want estimates where PAR (Photosynthetically Active Radition—the spectral range ofsolar radiation from 400 to 700 nanometers that photosynthetic organisms are able to use in the process of photosynthesis) is greater than zero (e.g., daytime to plants).

df1 <- df[ df$PAR > 0, ]
summary( df1 )
   DateTime              PAR              WindDir             PH      
 Length:4967        Min.   :   0.003   Min.   :  0.00   Min.   :6.48  
 Class :character   1st Qu.:   3.828   1st Qu.: 48.94   1st Qu.:7.51  
 Mode  :character   Median : 213.300   Median :182.80   Median :7.59  
                    Mean   : 399.442   Mean   :164.10   Mean   :7.61  
                    3rd Qu.: 679.250   3rd Qu.:258.90   3rd Qu.:7.71  
                    Max.   :1957.000   Max.   :359.90   Max.   :8.64  
                                                        NA's   :1     

We can also use functions that return TRUE or FALSE such as is.na()

df[ is.na(df$PH), ]

or mathematical expressions (here I use the moduluo operator—the remainder left over after normal division) to grab every other row. If I apply it to a sequence of values, I can get every even index

(1:10 %% 2)==0
 [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE

or every odd index

(1:10 %% 2) == 1 
 [1]  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE

or every 3 (or whatever)

(1:20 %% 3) == 0 
 [1] FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE
[13] FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE

We can even select a random subset of rows using the sample() function. Here I’ll select 5 rows randomly (see ?sample for more specifics on using this):

idx <- sample( 1:nrow(df), size=5, replace=FALSE)
idx 
[1] 5168 1248 5547  505 1755
df[ idx,  ]

Combinations of Indices

We can combine individual statements using AND as well as OR operators to make more complicated selections. In R, to combine logical operators we use the & for AND and | for OR.

So if I’m looking for a sample of data where the is light and/or the wind is coming from a certain direction, I could combine these operators as (n.b., I am only showing rows 25-35 as there are some interesting combinsions here):

par <- df$PAR > 0
wind <- df$WindDir < 30
cbind( par, wind )[30:35,]
       par  wind
[1,] FALSE  TRUE
[2,] FALSE  TRUE
[3,] FALSE  TRUE
[4,]  TRUE  TRUE
[5,]  TRUE FALSE
[6,]  TRUE FALSE

To look at what happens when we combine them, using &

cbind( par, wind, par & wind)[30:35,]
       par  wind      
[1,] FALSE  TRUE FALSE
[2,] FALSE  TRUE FALSE
[3,] FALSE  TRUE FALSE
[4,]  TRUE  TRUE  TRUE
[5,]  TRUE FALSE FALSE
[6,]  TRUE FALSE FALSE

as well as |

cbind( par, wind, par | wind)[30:35,]
       par  wind     
[1,] FALSE  TRUE TRUE
[2,] FALSE  TRUE TRUE
[3,] FALSE  TRUE TRUE
[4,]  TRUE  TRUE TRUE
[5,]  TRUE FALSE TRUE
[6,]  TRUE FALSE TRUE

So to put it all together as indices for df we can just shove these into the square brackets:

df1 <- df[ df$PAR > 0 & df$WindDir < 30, ]
head( df1 )

Mutation

To mutate columns of data in a data.frame, we use re-assignment. As an example, let’s jump into the first column of data in the rice data, the character column with Date & Time values. As it stands now, these data are of class:

class( rice$DateTime )
[1] "character"
character

So, the value in

rice$DateTime[234]
[1] "1/3/2014 10:15:00 AM"
1/3/2014 10:15:00 AM

is simply a string of characters, as far as R is concerned. Dates and times are sticky things in data analysis because they do not work the way we think they should. Here are some wrinkles:

  1. There are many types of calendars, we use the Julian calendar. However, there are many other calendars that are in use that we may run into. Each of these calendars has a different starting year (e.g., in the Assyrian calendar it is year 6770, it is 4718 in the Chinese calendar, 2020 in the Gregorian, and 1442 in the Islamic calendar).
  2. Our calendar has leap years (+1 day in February) as well as leap seconds because it is based on the rotation around the sun, others are based upon the lunar cycle and have other corrections.
  3. On this planet, we have 24 different time zones. Some states (looking at you Arizona) don’t feel it necessary to follow the other states around so they may be the same as PST some of the year and the same as MST the rest of the year. The provence of Newfoundland decided to be half-way between time zones so they are GMT-2:30. Some states have more than one time zone even if they are not large in size (hello Indiana).
  4. Dates and time are made up of odd units, 60-seconds a minute, 60-minutes an hour, 24-hours a day, 7-days a week, 2-weeks a fortnight, 28,29,30,or 31-days in a month, 365 or 366 days in a year, 100 years in a century, etc.

Fortunately, some smart programmers have figured this out for us already. What they did is made the second as the base unit of time and designated 00:00:00 on 1 January 1970 as the unix epoch. Time on most modern computers is measured from that starting point. It is much easier to measure the difference between two points in time using the seconds since unix epich and then translate it into one or more of these caledars than to deal with all the different calendars each time. So under the hood, much of the date and time issues are kept in terms of epoch seconds.

unclass( Sys.time() )
[1] 1605102028

To make date data types you need to ahve a raw character string such as

rice$DateTime[1]
[1] "1/1/2014 12:00:00 AM"
1/1/2014 12:00:00 AM

and then we need to be able to tell the conversion function what the various elements within that string represent. There are many ways to make dates and time, 10/14 or 14 Oct or October 14 or Julian day 287, etc. These are designated by a format string were we indicate what element represents a day or month or year object. These are found by looking at the documentation for?strptime.

In our case, we have:
- Month as 1 or 2 digits
- Day as 1 or 2 digits
- Year as 4 digits
- a space to separate date from time
- hour (not 24-hour though)
- minutes in 2 digits
- seconds in 2 digits
- a space to separate time from timezone
- timezone
- / separating date objects
- : separating time objects

To make the format string, we need to look up how to encode these items. Essentially, the format consists of a percent sign with an upper or lower case letter to represent all these objects (except for the whitespace and punctuation parts).

The items in rice for a date & time object such as 1/1/2014 11:00:00 AM are encoded as:

format <- "%m/%d/%Y %I:%M:%S %p"

Now, we can convert the character string in the data frame

record <- rice$DateTime[1]
record
[1] "1/1/2014 12:00:00 AM"
1/1/2014 12:00:00 AM
class( record )
[1] "character"
character

to a Date object. I like using the lubridate library2 as it has a lot of additional functionality that we’ll play with a bit later.

library( lubridate )
x <- parse_date_time( record, orders=format, tz = "EST" )
x
[1] "2014-01-01 EST"
class(x)
[1] "POSIXct" "POSIXt" 
POSIXct

POSIXt

Very cool.

So, now let’s apply that and mutate the original data, replacing the date and time column with a Date object.

rice$Date <- parse_date_time( rice$DateTime, 
                              orders=format,
                              tz = "EST")
summary( rice$Date )
                 Min.               1st Qu.                Median 
"2014-01-01 00:00:00" "2014-01-22 08:22:30" "2014-02-12 16:45:00" 
                 Mean               3rd Qu.                  Max. 
"2014-02-12 16:45:00" "2014-03-06 01:07:30" "2014-03-27 09:30:00" 

Now, we can ask Date-like questions about the data such as what day of the week was the first sample taken?

weekdays( rice$Date[1] )
[1] "Wednesday"
Wednesday

What is the range of dates?

range( rice$Date )
[1] "2014-01-01 00:00:00 EST" "2014-03-27 09:30:00 EST"

What is the median of samples

median( rice$Date )
[1] "2014-02-12 16:45:00 EST"

and what julian ordinal day (e.g., how many days since start of the year) is the last record.

yday( rice$Date[8199] )
[1] 86

Just for fun, I’ll add a column to the data that has weekday.

rice$Weekday <- weekdays( rice$Date )
summary( rice$Weekday )
   Length     Class      Mode 
     8199 character character 
class( rice$Weekday )
[1] "character"
character

However, we should probably turn it into a factor (e.g., a data type with pre-defined levels—and for us here—an intrinsic order of the levels).

rice$Weekday <- factor( rice$Weekday, 
                        ordered = TRUE, 
                        levels = c("Monday","Tuesday","Wednesday",
                                   "Thursday", "Friday",
                                   "Saturday", "Sunday")
                        )
summary( rice$Weekday )
   Monday   Tuesday Wednesday  Thursday    Friday  Saturday    Sunday 
     1152      1152      1248      1191      1152      1152      1152 

Arrange

To arrange data in a data.frame, we order it. Let’s say we want sort in decreasing order of PAR.

df <- rice[ order( rice$PAR, decreasing=TRUE), ]
head( df )

For sorting with more than one column, just add it to the order() function.

df <- rice[ order( rice$PAR, rice$WindSpeed_mph, decreasing = TRUE), ]
head( df )

(compare the last two rows between these two outputs).

Grouping

As we showed in the lecture on basic graphics on the slide discussing making data for the barplot, we can group data by existing data columns (Species in that case or Weekday in this one) by filtering the rows under some condition.

monday <- rice[ rice$Weekday == "Monday",]
max( monday$PAR )
[1] 1733
tuesday <- rice[ rice$Weekday == "Tuesday",]
max( tuesday$PAR )
[1] 1622

Summarizing

To summarize some data, we did have a shortcut back on those barplot slides using the by() function.

So for PAR, we can look at maximum values by day of the week

maxPAR <- by( rice$PAR, rice$Weekday, max )
barplot( maxPAR, las=2 , ylab="PAR", main="Maximum PAR at the Rice Center")

If we wanted to make a data.frame from the summary values, say to get the mean non-zero values of PAR and the standard deviation of records taken at noon each day we’d have to combine some of the methods above.

noon <- seq( rice$Date[49], rice$Date[7729], length.out = 81 )
noon[1:10]
 [1] "2014-01-01 12:00:00 EST" "2014-01-02 12:00:00 EST"
 [3] "2014-01-03 12:00:00 EST" "2014-01-04 12:00:00 EST"
 [5] "2014-01-05 12:00:00 EST" "2014-01-06 12:00:00 EST"
 [7] "2014-01-07 12:00:00 EST" "2014-01-08 12:00:00 EST"
 [9] "2014-01-09 12:00:00 EST" "2014-01-10 12:00:00 EST"

Notice what I did here. The Date variable we made above knows how to make sequences out of date objects because it knows what constitutes a day. I took the noon entry for 1/1/2014 and the noon entry for 3/22/2014 and made a sequence equal in length to the number of days between them. Next I can pull out only the entries in the full data set equal to values in that vector (I use the %in% operator, which is a set operator) and make a new data set from just the noon entries.

rice_noon <- rice[ rice$Date %in% noon, ]

And then make a new data set of both the mean and standard deviation of those values.

df <- data.frame( Weekday = levels( rice_noon$Weekday ) )
df$PAR <- by( rice_noon$PAR, rice_noon$Weekday, mean )
df$sd <- by( rice_noon$PAR, rice_noon$Weekday, sd )

Then we could use it to make a table:

library( knitr )
kable( df, output="html",digits = 2,caption = "Table 1: Mean and standard deviation of PAR at the Rice Center in 2014." )
Table 1: Mean and standard deviation of PAR at the Rice Center in 2014.
Weekday PAR sd
Monday 690.5000 443.2262
Tuesday 680.8709 495.1324
Wednesday 569.5000 285.6899
Thursday 845.8917 500.8599
Friday 980.6083 436.2707
Saturday 845.9667 513.4987
Sunday 937.5727 315.4537

or them using ggplot

library( ggplot2 )
ggplot( df, aes(x=Weekday, y=PAR) ) + 
  geom_col() + 
  geom_errorbar( aes(ymin=PAR, ymax=PAR+sd ) ) + 
  theme_classic()

HOLD THE BOAT!

Notice how those values on the x-axis do not conform to the ordering of the week. That is because we made this new data with just the levels but did not make them into an ordered factor. To clean up, let’s do that first.

df$Weekday <- factor( df$Weekday, 
                      ordered=TRUE,
                      levels = levels( rice$Weekday) )
df$Workdays <- c( rep("Work Day", 5), "Weekend","Weekend")
summary( df )
      Weekday       PAR              sd          Workdays        
 Monday   :1   Min.   :569.5   Min.   :285.7   Length:7          
 Tuesday  :1   1st Qu.:685.7   1st Qu.:375.9   Class :character  
 Wednesday:1   Median :845.9   Median :443.2   Mode  :character  
 Thursday :1   Mean   :793.0   Mean   :427.2                     
 Friday   :1   3rd Qu.:891.8   3rd Qu.:498.0                     
 Saturday :1   Max.   :980.6   Max.   :513.5                     
 Sunday   :1                                                     

Now when we plot it, it looks correct (same plot code copied here but using modified data frame).

library( RColorBrewer )
ggplot( df, aes(x=Weekday, y=PAR) ) + 
  geom_errorbar( aes(ymin=PAR-sd, ymax=PAR+sd ) ) + 
  geom_col( aes( fill=Workdays ) ) + 
  theme_classic() +
  scale_fill_brewer( type="qual",palette = 4)

Data Workflows for Standard R

OK, so the main workflows we adopt in R require us to do some fundamental data manipulations including tasks such as select, filter, mutate, arrange, group, and summarize. All basic tasks can be combined in various ways to yield informative insights into the data, such as that awesome noon PAR plot above, which incidentally shows that there is more sun at noon on Fridays than any other day implying that we should just take Fridays off to have more epic sunny weekends in winter!

While this is all fine and dandy, the process here requires a lot of complexity such as:
- Overuse of logical statements to do boolean math on indices.
- Lots of using the $-operator to reference specific columns of data within the data.frame objects. - Overuse of reassignment for derivative data frames. Above, we made rice, rice_noon, and df just to get that last figure above. In each of these cases, we are reallocating a new data.frame variable each time. We could reassign these back onto themselves (e.g., reuse variable names) but each time we do this, we actually have to allocate and reallocate large blocks of memory and for data sets larger that these (which are commonly used) it becomes a bit of a pain. - The overall syntax is hard to read.

For these reasons, tidyverse was made…


  1. Remember that if you leave either the row or column index empty in the square brackets, it will include all observations (e.g., if you leave row empty all rows will be returned whereas if you leave column empty all columns will be returned).↩︎

  2. If you get an error saying something like, “there is no package named lubridate” then use install.packages("lubridate") and install it. You only need to do this once.↩︎

LS0tCnRpdGxlOiAiRGF0YSBXb3JrZmxvd3MiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KCB0aWR5dmVyc2UgKQpsaWJyYXJ5KCBsdWJyaWRhdGUgKQpsaWJyYXJ5KCByZWFkciApCmxpYnJhcnkoIGtuaXRyICkKa25pdHI6Om9wdHNfY2h1bmskc2V0KCB3YXJuaW5nPUZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgIGZpZy5yZXRpbmEgPSAzKQpsaWJyYXJ5KCBnZ3Bsb3QyICkKZ2dwbG90Mjo6dGhlbWVfc2V0KCB0aGVtZV9jbGFzc2ljKCkgKQpgYGAKCgoKPiBUaGUgYWN0dWFsIHByb2Nlc3Mgb2YgYW5hbHlzaXMgY2FuIGJlIGNvbnN0cnVjdGVkIGZyb20gYSBzZXQgb2YgdmVyYiBhY3Rpb25zIHRoYXQgd2UgYXBwbHkgdG8gcmF3IGRhdGEgc28gdGhhdCB3ZSBjYW4gZXh0cmFjdCBtZWFuaW5nZnVsIGluc2lnaHRzLgoKIyBUaGUgRGF0YQoKRm9yIHRoaXMgdG9waWMsIHdlIHdpbGwgdXNlIGEgbW9kZXJhdGUgc2l6ZWQgZGF0YSBzZXQgZnJvbSB0aGUgW1JpY2UgUml2ZXJzIENlbnRlcl0oaHR0cHM6Ly9yaWNlcml2ZXJzLnZjdS5lZHUpIHdoaWNoIGNvbnRhaW5zIHdhdGVyIGFuZCBhdG1vc3BoZXJpYyBkYXRhIGZyb20gYSBzdHJlYW0gb2Ygc2Vuc29ycyBpbiBib3RoIHRoZSBKYW1lcyBSaXZlciBhbmQgb24gdGhlIGJsdWZmIG92ZXJsb29raW5nIHRoZSByaXZlci4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmdzPUZBTFNFfQpsaWJyYXJ5KCByZWFkciApCnVybCA8LSAiaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXRzL2QvMU1rMVlHSDlMcWpGN2RySkUtdGQxR19Ka2RBRE9VMGVNbHJQMDFXRkJUOHMvcHViP2dpZD0wJnNpbmdsZT10cnVlJm91dHB1dD1jc3YiCnJpY2UgPC0gcmVhZF9jc3YoIHVybCApCmBgYAoKVGhlc2UgZGF0YSBoYXZlIGByIG5yb3coIHJpY2UgKSBgIHJlY29yZHMgbWVhc3VyZWQgb24gdGhlIGByIG5jb2woIHJpY2UgKWAgY29sdW1ucyBhbmQgcmVwcmVzZW50IHNhbXBsZXMgY29sbGVjdGVkIGV2ZXJ5IDE1LW1pbnV0ZXMgc3RhcnRpbmcgb24gYHIgcmljZSREYXRlVGltZVsxXWAgYW5kIGVuZGluZyBvbiBgciByaWNlJERhdGVUaW1lWzgxOTldYC4KCmBgYHtyfQpuYW1lcyggcmljZSApCmBgYAoKCgojIFdvcmtmbG93cyBmb3IgRGF0YSBBbmFseXNlcy4KCklmIHdlIHRoaW5rIGFib3V0IGl0LCB0aGVyZSBhcmUgc29tZSBmdW5kYW1lbnRhbCBwcm9jZXNzZXMgdGhhdCB3ZSB1c2UgdG8gd29yayB3aXRoIGFuZCBtYW5pcHVsYXRlIGRhdGEuICBUaGVzZSBpbmNsdWRlOiAgCgotICpTZWxlY3QqIGNvbHVtbnMgb2YgZGF0YSB0byB1c2UgKGUuZy4sIHNldCB0aGUgY29sdW1ucyB0aGF0IGBnZ3Bsb3RgIHdpbGwgdXNlIHRvIHBsb3QpLiAgCi0gKkZpbHRlciogcm93cyBvZiBkYXRhIGJhc2VkIHVwb24gc29tZSBjcml0ZXJpYSAoZS5nLiwgc2VsZWN0IG9ubHkgYFNwZWNpZXMgPT0gc2V0b3NhYCBvbiB0aGUgYGlyaXNgIGRhdGEgc2V0KS4KLSAqTXV0YXRlKiB0aGUgZGF0YSBpdHNlbGYgYnkgY29udmVydGluZyBpdCAoZS5nLiwgY29udmVydCBgcmljZSRBaXJUZW1wRmAgdG8gY2VsY2l1cykuCi0gKkFycmFuZ2UqIHRoZSBvcmRlciBvZiB0aGUgcm93cyBpbiBhIGBkYXRhLmZyYW1lYCAoZS5nLiwgc29ydCBieSBgcmljZSREZXRwaF9tYCB0byBmaW5kIHRoZSBsb3dlc3Qtb3IgaGlnaHRlc3QtdGlkZXMgaW4gdGhlIGRhdGEgZnJhbWUpLgotICpHcm91cCogZGF0YSBpbnRvIHBhcnRpdGlvbnMgYmFzZWQgdXBvbiBhIGBmYWN0b3JgIG9yIG90aGVyIGNvbHVtbiBvZiBkYXRhIChlLmcuLCBzZXBhcmF0aW5nIHRoZSB2YWx1ZXMgb2YgYFNlcGFsLkxlbmd0aGAgZm9yIGVhY2ggb2YgdGhlIGBTcGVjaWVzYCBncm91cHMgaW4gdGhlIGBpcmlzYCBkYXRhIHNldCkuCi0gKlN1bW1hcml6ZSogZGF0YSB0byBzaG93IG1lYW5zLCB2YXJpYW5jZXMsIHN1bXMsIGV0Yy4gKGUuZy4sIGVzdGltYXRlIHRoZSBgbWVhbmAgdmFsdWUgb2YgYFNlcGFsLkxlbmd0aGAgZm9yIGVhY2ggYFNwZWNpZXNgIGluIHRoZSBgaXJpc2AgZGF0YSBzZXQpLgoKVGhlc2UgKnZlcmJzKiBhbmQvb3IgKmFjdGlvbnMqIGNhbiBiZSBjb21iaW5lZCBpbiBtYW55IHdheXMgdG8gYWxsb3cgdXMgdG8gZXh0cmFjdCBpbmZvcm1hdGlvbiBmcm9tIHJhdyBkYXRhLiAgRXhhbXBsZXMgbWF5IGluY2x1ZGU6CgotICJEb2VzIGl0IHJhaW4gbW9yZSBvZnRlbiBvbiBNb25kYXlzIHRoYW4gb3RoZXIgZGF5cyBvZiB0aGUgd2Vlaz8iICAKLSAiV2hhdCBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIGhpZ2ggdGlkZSBtYXJrcyBmb3IgZWFjaCBkYXkgaW4gSmFudWFyeT8iCi0gIk1ha2UgYSBwbG90IHNob3dpbmcgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHdhdGVyIHNhbGluaXR5IGFuZCBwSC4iCgoKIyBTdGFuZGFyZCBgUmAgQXBwcm9hY2hlcwoKVG8gYmVnaW4sIHdlIHdpbGwgd2FsayB0aHJvdWdoIHRoZSB3YXlzIHRoYXQgdGhpcyBjYW4gYmUgZG9uZSB1c2luZyBub3JtYWwgYFJgIHN5bnRheCwgd2hpY2ggd2lsbCBpbmNsdWRlIGEgYmVpbmcgY2xlYXZlciBpbiBob3cgd2UgdXNlIGluZGljZXMsIG1peCBpbiBhIGJ1bmNoIG9mIGxvZ2ljYWwgb3BlcmF0b3JzLCBhbmQgYSBzbWF0dGVyaW5nIG9mIGAkYCBvcGVyYXRvcnMsIGFuZCB3ZSBhcmUgZ29vZCB0byBnbyEKCgojIyBTZWxlY3RpbmcgQ29sdW1ucwoKVG8gc2VsZWN0IGNvbHVtbnMsIHdlIHVzZSBlaXRoZXIgdGhlIGNvbHVtbiBuYW1lcyBhcyBjaGFyYWN0ZXIgb2JqZWN0cyBpbiB0aGUgY29sdW1uIGluZGV4IHBvc2l0aW9uIChlLmcuLCBhZnRlciB0aGUgY29tbWEgaW4gdGhlIHNxdWFyZSBicmFja2V0c1teMV0pCgpgYGB7cn0KZGYgPC0gcmljZVssIGMoIkRhdGVUaW1lIiwiUEFSIiwiV2luZERpciIsIlBIIildCnN1bW1hcnkoIGRmICkKYGBgCgpvciB0aGUgY29sdW1uIGluZGljZXMgaW4gdGhlIHNxdWFyZS1icmFja2V0cy4KCmBgYHtyfQpkZiA8LSByaWNlWyBjKDEsMyw1LDEzKV0Kc3VtbWFyeSggZGYgKQpgYGAKCkFzIHlvdSBjYW4gc2VlLCB0aGUgZmlyc3Qgb25lIGlzIHByb2JhYmx5IG1vcmUgZWZmZWN0aXZlIHRoYW4gdGhlIHNlY29uZCBvbmUgYmVjYXVzZSBieSBzaW1wbHkgbG9va2luZyBhdCB0aGUgY29kZSwgd2UgY2FuIHNlZSB3aGF0IGNvbHVtbnMgd2UgYXJlIGdvaW5nIHRvIGdyYWIuICBNb3Jlb3ZlciwgaWYgeW91IGFyZSB1c2luZyBgUlN0dWRpb2AgZm9yIHRoaXMsIHlvdSBzaG91bGQgYmUgYWJsZSB0byBnZXQgdGhlIHZlcnkgaGVscGZ1bCBhdXRvY29ycmVjdCB0byBwb3AgdXAgYW5kIGdpdmUgeW91IHRoZSBuYW1lcyBvZiB0aGUgY29sdW1ucy4gIAoKIVtGaWd1cmUgMTogUG9wdXAgaGVscCBmb3IgY29sdW1uIG5hbWVzIGluIGBSU3R1ZGlvYCBmb3IgYSBkYXRhIGZyYW1lIGluIG1lbW9yeV0oaHR0cHM6Ly9saXZlLnN0YXRpY2ZsaWNrci5jb20vNjU1MzUvNTAzNTk5NjQ0NjdfMDMyMzJhMzcxNl93X2QuanBnKQoKVXNpbmcgbnVtYmVycyBoYXMgbm8gaGVscCBsaWtlIHRoaXMuCgojIyBGaWx0ZXJpbmcgUm93cwoKVG8gZmlsdGVyIHJvd3MsIHdlIGNhbiBkbyB0aGUgc2FtZSB0aGluZyBhcyBmb3Igc2VsZWN0aW5nIGNvbHVtbnMuICBIZXJlIHdlIGNhbiBlaXRoZXIgbnVtZXJpY2FsIHZhbHVlcyB0byB0YWtlIHNsaWNlcyAoZS5nLiwgSSdtIGdyYWJiaW5nIHRoZSBmaXJzdCA5NiBlbnRyaWVzIHdoaWNoIGNvcnJlc3BvbmQgdG8gYWxsIHRoZSBlbnRyaWVzIHRha2VuIG9uIEphbnVhcnkgMSwgMjAxNCk6CgoKYGBge3J9CmRmMSA8LSBkZlsgMTo5NiwgXQpoZWFkKCBkZjEgKQpgYGAKT3Igd2UgY2FuIHVzZSBsb2dpY2FsIG9wZXJhdG9ycyB0aGF0IHRlc3Qgc29tZSBgbG9naWNhbGAgY29uZGl0aW9uIGJhc2VkIHVwb24gdGhlIHZhbHVlcyBpbiB0aGUgYGRhdGEuZnJhbWVgLiAgCgpGb3IgbG9naWNhbCBvcGVyYXRvcnMgKHJldHVybmluZyBgVFJVRWAsIGBGQUxTRWAgb3IgYE5BYCksIHRoZSBmb2xsb3dpbmcgYXJlIGF2YWlsYWJsZToKCk9wZXJhdG9yIHwgRGVmaW5pdGlvbiAKLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpgIT1gICAgICB8IE5vdCBlcXVhbCB0bwpgPT1gICAgICB8IEVxdWFsIHRvCmA+YCAgICAgIHwgU3RyaWN0bHkgZ3JlYXRlciB0aGFuCmA+PWAgICAgIHwgR3JlYXRlciB0aGFuIE9SIGVxdWFsIHRvCmA8YCAgICAgIHwgU3RyaWN0bHkgbGVzcyB0aGFuCmA8PWAgICAgIHwgTGVzcyB0aGFuIG9yIGVxdWFsIHRvLgoKRm9yIGV4YW1wbGUsIG1heWJlIEkgb25seSB3YW50IGVzdGltYXRlcyB3aGVyZSBQQVIgKFBob3Rvc3ludGhldGljYWxseSBBY3RpdmUgUmFkaXRpb27igJR0aGUgc3BlY3RyYWwgcmFuZ2Ugb2Zzb2xhciByYWRpYXRpb24gZnJvbSA0MDAgdG8gNzAwIG5hbm9tZXRlcnMgdGhhdCBwaG90b3N5bnRoZXRpYyBvcmdhbmlzbXMgYXJlIGFibGUgdG8gdXNlIGluIHRoZSBwcm9jZXNzIG9mIHBob3Rvc3ludGhlc2lzKSBpcyBncmVhdGVyIHRoYW4gemVybyAoZS5nLiwgZGF5dGltZSB0byBwbGFudHMpLgoKYGBge3J9CmRmMSA8LSBkZlsgZGYkUEFSID4gMCwgXQpzdW1tYXJ5KCBkZjEgKQpgYGAKCldlIGNhbiBhbHNvIHVzZSBmdW5jdGlvbnMgdGhhdCByZXR1cm4gYFRSVUVgIG9yIGBGQUxTRWAgc3VjaCBhcyBgaXMubmEoKWAKCmBgYHtyfQpkZlsgaXMubmEoZGYkUEgpLCBdCmBgYAoKb3IgbWF0aGVtYXRpY2FsIGV4cHJlc3Npb25zIChoZXJlIEkgdXNlIHRoZSBbbW9kdWx1byBvcGVyYXRvcl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTW9kdWxvX29wZXJhdGlvbinigJR0aGUgcmVtYWluZGVyIGxlZnQgb3ZlciBhZnRlciBub3JtYWwgZGl2aXNpb24pIHRvIGdyYWIgZXZlcnkgb3RoZXIgcm93LiAgSWYgSSBhcHBseSBpdCB0byBhIHNlcXVlbmNlIG9mIHZhbHVlcywgSSBjYW4gZ2V0IGV2ZXJ5IGV2ZW4gaW5kZXgKCmBgYHtyfQooMToxMCAlJSAyKT09MApgYGAKCm9yIGV2ZXJ5IG9kZCBpbmRleCAKCmBgYHtyfQooMToxMCAlJSAyKSA9PSAxIApgYGAKCm9yIGV2ZXJ5IDMgKG9yIHdoYXRldmVyKQoKYGBge3J9CigxOjIwICUlIDMpID09IDAgCmBgYAoKV2UgY2FuIGV2ZW4gc2VsZWN0IGEgKnJhbmRvbSogc3Vic2V0IG9mIHJvd3MgdXNpbmcgdGhlIGBzYW1wbGUoKWAgZnVuY3Rpb24uICAgSGVyZSBJJ2xsIHNlbGVjdCA1IHJvd3MgcmFuZG9tbHkgKHNlZSBgP3NhbXBsZWAgZm9yIG1vcmUgc3BlY2lmaWNzIG9uIHVzaW5nIHRoaXMpOgoKYGBge3J9CmlkeCA8LSBzYW1wbGUoIDE6bnJvdyhkZiksIHNpemU9NSwgcmVwbGFjZT1GQUxTRSkKaWR4IApkZlsgaWR4LCAgXQpgYGAKCgoKIyMjIENvbWJpbmF0aW9ucyBvZiBJbmRpY2VzCgpXZSBjYW4gY29tYmluZSBpbmRpdmlkdWFsIHN0YXRlbWVudHMgdXNpbmcgYEFORGAgYXMgd2VsbCBhcyBgT1JgIG9wZXJhdG9ycyB0byBtYWtlIG1vcmUgY29tcGxpY2F0ZWQgc2VsZWN0aW9ucy4gIEluIFIsIHRvIGNvbWJpbmUgbG9naWNhbCBvcGVyYXRvcnMgd2UgdXNlIHRoZSBgJmAgZm9yIGBBTkRgIGFuZCBgfGAgZm9yIGBPUmAuCgpTbyBpZiBJJ20gbG9va2luZyBmb3IgYSBzYW1wbGUgb2YgZGF0YSB3aGVyZSB0aGUgaXMgbGlnaHQgYW5kL29yIHRoZSB3aW5kIGlzIGNvbWluZyBmcm9tIGEgY2VydGFpbiBkaXJlY3Rpb24sIEkgY291bGQgY29tYmluZSB0aGVzZSBvcGVyYXRvcnMgYXMgKG4uYi4sIEkgYW0gb25seSBzaG93aW5nIHJvd3MgMjUtMzUgYXMgdGhlcmUgYXJlIHNvbWUgaW50ZXJlc3RpbmcgY29tYmluc2lvbnMgaGVyZSk6CgpgYGB7cn0KcGFyIDwtIGRmJFBBUiA+IDAKd2luZCA8LSBkZiRXaW5kRGlyIDwgMzAKY2JpbmQoIHBhciwgd2luZCApWzMwOjM1LF0KYGBgCgpUbyBsb29rIGF0IHdoYXQgaGFwcGVucyB3aGVuIHdlIGNvbWJpbmUgdGhlbSwgdXNpbmcgYCZgIAoKYGBge3J9CmNiaW5kKCBwYXIsIHdpbmQsIHBhciAmIHdpbmQpWzMwOjM1LF0KYGBgCgphcyB3ZWxsIGFzIGB8YAoKYGBge3J9CmNiaW5kKCBwYXIsIHdpbmQsIHBhciB8IHdpbmQpWzMwOjM1LF0KYGBgCgpTbyB0byBwdXQgaXQgYWxsIHRvZ2V0aGVyIGFzIGluZGljZXMgZm9yIGBkZmAgd2UgY2FuIGp1c3Qgc2hvdmUgdGhlc2UgaW50byB0aGUgc3F1YXJlIGJyYWNrZXRzOgoKYGBge3J9CmRmMSA8LSBkZlsgZGYkUEFSID4gMCAmIGRmJFdpbmREaXIgPCAzMCwgXQpoZWFkKCBkZjEgKQpgYGAKCiMjIE11dGF0aW9uCgpUbyBtdXRhdGUgY29sdW1ucyBvZiBkYXRhIGluIGEgYGRhdGEuZnJhbWVgLCB3ZSB1c2UgcmUtYXNzaWdubWVudC4gIEFzIGFuIGV4YW1wbGUsIGxldCdzIGp1bXAgaW50byB0aGUgZmlyc3QgY29sdW1uIG9mIGRhdGEgaW4gdGhlIGByaWNlYCBkYXRhLCB0aGUgY2hhcmFjdGVyIGNvbHVtbiB3aXRoIERhdGUgJiBUaW1lIHZhbHVlcy4gIEFzIGl0IHN0YW5kcyBub3csIHRoZXNlIGRhdGEgYXJlIG9mIGNsYXNzOgoKYGBge3J9CmNsYXNzKCByaWNlJERhdGVUaW1lICkKYGBgCgpTbywgdGhlIHZhbHVlIGluIAoKYGBge3J9CnJpY2UkRGF0ZVRpbWVbMjM0XQpgYGAKCmlzIHNpbXBseSBhIHN0cmluZyBvZiBjaGFyYWN0ZXJzLCBhcyBmYXIgYXMgYFJgIGlzIGNvbmNlcm5lZC4gIERhdGVzIGFuZCB0aW1lcyBhcmUgc3RpY2t5IHRoaW5ncyBpbiBkYXRhIGFuYWx5c2lzIGJlY2F1c2UgdGhleSBkbyBub3Qgd29yayB0aGUgd2F5IHdlIHRoaW5rIHRoZXkgc2hvdWxkLiAgSGVyZSBhcmUgc29tZSB3cmlua2xlczoKCjEuIFRoZXJlIGFyZSBtYW55IHR5cGVzIG9mIGNhbGVuZGFycywgd2UgdXNlIHRoZSBKdWxpYW4gY2FsZW5kYXIuICBIb3dldmVyLCB0aGVyZSBhcmUgbWFueSBvdGhlciBjYWxlbmRhcnMgdGhhdCBhcmUgaW4gdXNlIHRoYXQgd2UgbWF5IHJ1biBpbnRvLiAgRWFjaCBvZiB0aGVzZSBjYWxlbmRhcnMgaGFzIGEgZGlmZmVyZW50IHN0YXJ0aW5nIHllYXIgKGUuZy4sIGluIHRoZSBBc3N5cmlhbiBjYWxlbmRhciBpdCBpcyB5ZWFyIDY3NzAsIGl0IGlzIDQ3MTggaW4gdGhlIENoaW5lc2UgY2FsZW5kYXIsIDIwMjAgaW4gdGhlIEdyZWdvcmlhbiwgYW5kIDE0NDIgaW4gdGhlIElzbGFtaWMgY2FsZW5kYXIpLgoyLiBPdXIgY2FsZW5kYXIgaGFzIGxlYXAgeWVhcnMgKCsxIGRheSBpbiBGZWJydWFyeSkgYXMgd2VsbCBhcyBsZWFwIHNlY29uZHMgYmVjYXVzZSBpdCBpcyBiYXNlZCBvbiB0aGUgcm90YXRpb24gYXJvdW5kIHRoZSBzdW4sIG90aGVycyBhcmUgYmFzZWQgdXBvbiB0aGUgbHVuYXIgY3ljbGUgYW5kIGhhdmUgb3RoZXIgY29ycmVjdGlvbnMuIAozLiBPbiB0aGlzIHBsYW5ldCwgd2UgaGF2ZSAyNCBkaWZmZXJlbnQgdGltZSB6b25lcy4gU29tZSBzdGF0ZXMgKGxvb2tpbmcgYXQgeW91IEFyaXpvbmEpIGRvbid0IGZlZWwgaXQgbmVjZXNzYXJ5IHRvIGZvbGxvdyB0aGUgb3RoZXIgc3RhdGVzIGFyb3VuZCBzbyB0aGV5IG1heSBiZSB0aGUgc2FtZSBhcyBQU1Qgc29tZSBvZiB0aGUgeWVhciBhbmQgdGhlIHNhbWUgYXMgTVNUIHRoZSByZXN0IG9mIHRoZSB5ZWFyLiAgVGhlIHByb3ZlbmNlIG9mIE5ld2ZvdW5kbGFuZCBkZWNpZGVkIHRvIGJlIGhhbGYtd2F5IGJldHdlZW4gdGltZSB6b25lcyBzbyB0aGV5IGFyZSBHTVQtMjozMC4gU29tZSBzdGF0ZXMgaGF2ZSBtb3JlIHRoYW4gb25lIHRpbWUgem9uZSBldmVuIGlmIHRoZXkgYXJlIG5vdCBsYXJnZSBpbiBzaXplIChoZWxsbyBJbmRpYW5hKS4KNC4gRGF0ZXMgYW5kIHRpbWUgYXJlIG1hZGUgdXAgb2Ygb2RkIHVuaXRzLCA2MC1zZWNvbmRzIGEgbWludXRlLCA2MC1taW51dGVzIGFuIGhvdXIsIDI0LWhvdXJzIGEgZGF5LCA3LWRheXMgYSB3ZWVrLCAyLXdlZWtzIGEgZm9ydG5pZ2h0LCAyOCwyOSwzMCxvciAzMS1kYXlzIGluIGEgbW9udGgsIDM2NSBvciAzNjYgZGF5cyBpbiBhIHllYXIsIDEwMCB5ZWFycyBpbiBhIGNlbnR1cnksIGV0Yy4KCkZvcnR1bmF0ZWx5LCBzb21lIHNtYXJ0IHByb2dyYW1tZXJzIGhhdmUgZmlndXJlZCB0aGlzIG91dCBmb3IgdXMgYWxyZWFkeS4gIFdoYXQgdGhleSBkaWQgaXMgbWFkZSB0aGUgc2Vjb25kIGFzIHRoZSBiYXNlIHVuaXQgb2YgdGltZSBhbmQgZGVzaWduYXRlZCAwMDowMDowMCBvbiAxIEphbnVhcnkgMTk3MCBhcyB0aGUgKnVuaXggZXBvY2gqLiAgVGltZSBvbiBtb3N0IG1vZGVybiBjb21wdXRlcnMgaXMgbWVhc3VyZWQgZnJvbSB0aGF0IHN0YXJ0aW5nIHBvaW50LiAgSXQgaXMgbXVjaCBlYXNpZXIgdG8gbWVhc3VyZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHR3byBwb2ludHMgaW4gdGltZSB1c2luZyB0aGUgc2Vjb25kcyBzaW5jZSB1bml4IGVwaWNoICphbmQgdGhlbiogdHJhbnNsYXRlIGl0IGludG8gb25lIG9yIG1vcmUgb2YgdGhlc2UgY2FsZWRhcnMgdGhhbiB0byBkZWFsIHdpdGggYWxsIHRoZSBkaWZmZXJlbnQgY2FsZW5kYXJzIGVhY2ggdGltZS4gU28gdW5kZXIgdGhlIGhvb2QsIG11Y2ggb2YgdGhlIGRhdGUgYW5kIHRpbWUgaXNzdWVzIGFyZSBrZXB0IGluIHRlcm1zIG9mIGVwb2NoIHNlY29uZHMuICAKCmBgYHtyfQp1bmNsYXNzKCBTeXMudGltZSgpICkKYGBgCgpUbyBtYWtlIGRhdGUgZGF0YSB0eXBlcyB5b3UgbmVlZCB0byBhaHZlIGEgcmF3IGBjaGFyYWN0ZXJgIHN0cmluZyBzdWNoIGFzIAoKYGBge3J9CnJpY2UkRGF0ZVRpbWVbMV0KYGBgCgphbmQgdGhlbiB3ZSBuZWVkIHRvIGJlIGFibGUgdG8gdGVsbCB0aGUgY29udmVyc2lvbiBmdW5jdGlvbiB3aGF0IHRoZSB2YXJpb3VzIGVsZW1lbnRzIHdpdGhpbiB0aGF0IHN0cmluZyByZXByZXNlbnQuICBUaGVyZSBhcmUgbWFueSB3YXlzIHRvIG1ha2UgZGF0ZXMgYW5kIHRpbWUsIDEwLzE0IG9yIDE0IE9jdCBvciBPY3RvYmVyIDE0IG9yIEp1bGlhbiBkYXkgMjg3LCBldGMuICBUaGVzZSBhcmUgZGVzaWduYXRlZCBieSBhIGZvcm1hdCBzdHJpbmcgd2VyZSB3ZSBpbmRpY2F0ZSB3aGF0IGVsZW1lbnQgcmVwcmVzZW50cyBhICpkYXkqIG9yICptb250aCogb3IgKnllYXIqIG9iamVjdC4gIFRoZXNlIGFyZSBmb3VuZCBieSBsb29raW5nIGF0IHRoZSBkb2N1bWVudGF0aW9uIGZvcmA/c3RycHRpbWVgLgoKSW4gb3VyIGNhc2UsIHdlIGhhdmU6ICAKLSBNb250aCBhcyAxIG9yIDIgZGlnaXRzICAKLSBEYXkgYXMgMSBvciAyIGRpZ2l0cyAgCi0gWWVhciBhcyA0IGRpZ2l0cyAgCi0gYSBzcGFjZSB0byBzZXBhcmF0ZSBkYXRlIGZyb20gdGltZSAgIAotIGhvdXIgKG5vdCAyNC1ob3VyIHRob3VnaCkgIAotIG1pbnV0ZXMgaW4gMiBkaWdpdHMgIAotIHNlY29uZHMgaW4gMiBkaWdpdHMgIAotIGEgc3BhY2UgdG8gc2VwYXJhdGUgdGltZSBmcm9tIHRpbWV6b25lICAgCi0gdGltZXpvbmUgIAotIGAvYCBzZXBhcmF0aW5nIGRhdGUgb2JqZWN0cyAgCi0gYDpgIHNlcGFyYXRpbmcgdGltZSBvYmplY3RzICAKClRvIG1ha2UgdGhlIGZvcm1hdCBzdHJpbmcsIHdlIG5lZWQgdG8gbG9vayB1cCBob3cgdG8gZW5jb2RlIHRoZXNlIGl0ZW1zLiAgIEVzc2VudGlhbGx5LCB0aGUgZm9ybWF0IGNvbnNpc3RzIG9mIGEgcGVyY2VudCBzaWduIHdpdGggYW4gdXBwZXIgb3IgbG93ZXIgY2FzZSBsZXR0ZXIgdG8gcmVwcmVzZW50IGFsbCB0aGVzZSBvYmplY3RzIChleGNlcHQgZm9yIHRoZSB3aGl0ZXNwYWNlIGFuZCBwdW5jdHVhdGlvbiBwYXJ0cykuCgpUaGUgaXRlbXMgaW4gYHJpY2VgIGZvciBhIGRhdGUgJiB0aW1lIG9iamVjdCBzdWNoIGFzIGByIHJpY2UkRGF0ZVRpbWVbNDVdYCBhcmUgZW5jb2RlZCBhczoKCmBgYHtyfQpmb3JtYXQgPC0gIiVtLyVkLyVZICVJOiVNOiVTICVwIgpgYGAKCk5vdywgd2UgY2FuIGNvbnZlcnQgdGhlIGNoYXJhY3RlciBzdHJpbmcgaW4gdGhlIGRhdGEgZnJhbWUgCgpgYGB7cn0KcmVjb3JkIDwtIHJpY2UkRGF0ZVRpbWVbMV0KcmVjb3JkCmNsYXNzKCByZWNvcmQgKQpgYGAKCnRvIGEgRGF0ZSBvYmplY3QuICBJIGxpa2UgdXNpbmcgdGhlIGBsdWJyaWRhdGVgIGxpYnJhcnlbXjJdIGFzIGl0IGhhcyBhIGxvdCBvZiBhZGRpdGlvbmFsIGZ1bmN0aW9uYWxpdHkgdGhhdCB3ZSdsbCBwbGF5IHdpdGggYSBiaXQgbGF0ZXIuCgpgYGB7cn0KbGlicmFyeSggbHVicmlkYXRlICkKeCA8LSBwYXJzZV9kYXRlX3RpbWUoIHJlY29yZCwgb3JkZXJzPWZvcm1hdCwgdHogPSAiRVNUIiApCngKY2xhc3MoeCkKYGBgCgoKVmVyeSBjb29sLiAgCgpTbywgbm93IGxldCdzIGFwcGx5IHRoYXQgYW5kICptdXRhdGUqIHRoZSBvcmlnaW5hbCBkYXRhLCByZXBsYWNpbmcgdGhlIGRhdGUgYW5kIHRpbWUgY29sdW1uIHdpdGggYSBEYXRlIG9iamVjdC4KCmBgYHtyfQpyaWNlJERhdGUgPC0gcGFyc2VfZGF0ZV90aW1lKCByaWNlJERhdGVUaW1lLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJzPWZvcm1hdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHogPSAiRVNUIikKc3VtbWFyeSggcmljZSREYXRlICkKYGBgCgpOb3csIHdlIGNhbiBhc2sgKkRhdGUtbGlrZSogcXVlc3Rpb25zIGFib3V0IHRoZSBkYXRhIHN1Y2ggYXMgd2hhdCBkYXkgb2YgdGhlIHdlZWsgd2FzIHRoZSBmaXJzdCBzYW1wbGUgdGFrZW4/CgpgYGB7ciB3YXJuaW5nPUZBTFNFIH0Kd2Vla2RheXMoIHJpY2UkRGF0ZVsxXSApCmBgYAoKV2hhdCBpcyB0aGUgcmFuZ2Ugb2YgZGF0ZXM/CgpgYGB7cn0KcmFuZ2UoIHJpY2UkRGF0ZSApCmBgYAoKV2hhdCBpcyB0aGUgbWVkaWFuIG9mIHNhbXBsZXMKCmBgYHtyfQptZWRpYW4oIHJpY2UkRGF0ZSApCmBgYAoKYW5kIHdoYXQganVsaWFuIG9yZGluYWwgZGF5IChlLmcuLCBob3cgbWFueSBkYXlzIHNpbmNlIHN0YXJ0IG9mIHRoZSB5ZWFyKSBpcyB0aGUgbGFzdCByZWNvcmQuCgpgYGB7cn0KeWRheSggcmljZSREYXRlWzgxOTldICkKYGBgCgoKCkp1c3QgZm9yIGZ1biwgSSdsbCBhZGQgYSBjb2x1bW4gdG8gdGhlIGRhdGEgdGhhdCBoYXMgd2Vla2RheS4KCmBgYHtyfQpyaWNlJFdlZWtkYXkgPC0gd2Vla2RheXMoIHJpY2UkRGF0ZSApCnN1bW1hcnkoIHJpY2UkV2Vla2RheSApCmNsYXNzKCByaWNlJFdlZWtkYXkgKQpgYGAKCkhvd2V2ZXIsIHdlIHNob3VsZCBwcm9iYWJseSB0dXJuIGl0IGludG8gYSBmYWN0b3IgKGUuZy4sIGEgZGF0YSB0eXBlIHdpdGggcHJlLWRlZmluZWQgbGV2ZWxz4oCUYW5kIGZvciB1cyBoZXJl4oCUYW4gaW50cmluc2ljIG9yZGVyIG9mIHRoZSBsZXZlbHMpLgoKYGBge3J9CnJpY2UkV2Vla2RheSA8LSBmYWN0b3IoIHJpY2UkV2Vla2RheSwgCiAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiTW9uZGF5IiwiVHVlc2RheSIsIldlZG5lc2RheSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRodXJzZGF5IiwgIkZyaWRheSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNhdHVyZGF5IiwgIlN1bmRheSIpCiAgICAgICAgICAgICAgICAgICAgICAgICkKc3VtbWFyeSggcmljZSRXZWVrZGF5ICkKYGBgCgoKIyMgQXJyYW5nZQoKVG8gYXJyYW5nZSBkYXRhIGluIGEgZGF0YS5mcmFtZSwgd2UgYG9yZGVyYCBpdC4gIExldCdzIHNheSB3ZSB3YW50IHNvcnQgaW4gZGVjcmVhc2luZyBvcmRlciBvZiBgUEFSYC4KCmBgYHtyfQpkZiA8LSByaWNlWyBvcmRlciggcmljZSRQQVIsIGRlY3JlYXNpbmc9VFJVRSksIF0KaGVhZCggZGYgKQpgYGAKCkZvciBzb3J0aW5nIHdpdGggbW9yZSB0aGFuIG9uZSBjb2x1bW4sIGp1c3QgYWRkIGl0IHRvIHRoZSBgb3JkZXIoKWAgZnVuY3Rpb24uCgpgYGB7cn0KZGYgPC0gcmljZVsgb3JkZXIoIHJpY2UkUEFSLCByaWNlJFdpbmRTcGVlZF9tcGgsIGRlY3JlYXNpbmcgPSBUUlVFKSwgXQpoZWFkKCBkZiApCmBgYAooY29tcGFyZSB0aGUgbGFzdCB0d28gcm93cyBiZXR3ZWVuIHRoZXNlIHR3byBvdXRwdXRzKS4KCiMjIEdyb3VwaW5nIAoKQXMgd2Ugc2hvd2VkIGluIHRoZSBsZWN0dXJlIG9uIGJhc2ljIGdyYXBoaWNzIG9uIHRoZSBbc2xpZGVdKGh0dHBzOi8vZHllcmxhYi5naXRodWIuaW8vRU5WUy1MZWN0dXJlcy92aXN1YWxpemF0aW9uL2Jhc2ljX3Zpc3VhbGl6YXRpb24vc2xpZGVzLmh0bWwjNDgpIGRpc2N1c3NpbmcgbWFraW5nIGRhdGEgZm9yIHRoZSBiYXJwbG90LCB3ZSBjYW4gZ3JvdXAgZGF0YSBieSBleGlzdGluZyBkYXRhIGNvbHVtbnMgKFNwZWNpZXMgaW4gdGhhdCBjYXNlIG9yIFdlZWtkYXkgaW4gdGhpcyBvbmUpIGJ5IGZpbHRlcmluZyB0aGUgcm93cyB1bmRlciBzb21lIGNvbmRpdGlvbi4gCgpgYGB7cn0KbW9uZGF5IDwtIHJpY2VbIHJpY2UkV2Vla2RheSA9PSAiTW9uZGF5IixdCm1heCggbW9uZGF5JFBBUiApCnR1ZXNkYXkgPC0gcmljZVsgcmljZSRXZWVrZGF5ID09ICJUdWVzZGF5IixdCm1heCggdHVlc2RheSRQQVIgKQpgYGAKCiMjIFN1bW1hcml6aW5nCgpUbyBzdW1tYXJpemUgc29tZSBkYXRhLCB3ZSBkaWQgaGF2ZSBhIHNob3J0Y3V0IGJhY2sgb24gdGhvc2UgYmFycGxvdCBbc2xpZGVzXShodHRwczovL2R5ZXJsYWIuZ2l0aHViLmlvL0VOVlMtTGVjdHVyZXMvdmlzdWFsaXphdGlvbi9iYXNpY192aXN1YWxpemF0aW9uL3NsaWRlcy5odG1sIzUxKSB1c2luZyB0aGUgYGJ5KClgIGZ1bmN0aW9uLgoKU28gZm9yIFBBUiwgd2UgY2FuIGxvb2sgYXQgbWF4aW11bSB2YWx1ZXMgYnkgZGF5IG9mIHRoZSB3ZWVrCgpgYGB7cn0KbWF4UEFSIDwtIGJ5KCByaWNlJFBBUiwgcmljZSRXZWVrZGF5LCBtYXggKQpiYXJwbG90KCBtYXhQQVIsIGxhcz0yICwgeWxhYj0iUEFSIiwgbWFpbj0iTWF4aW11bSBQQVIgYXQgdGhlIFJpY2UgQ2VudGVyIikKYGBgCgpJZiB3ZSB3YW50ZWQgdG8gbWFrZSBhIGRhdGEuZnJhbWUgZnJvbSB0aGUgc3VtbWFyeSB2YWx1ZXMsIHNheSB0byBnZXQgdGhlIG1lYW4gbm9uLXplcm8gdmFsdWVzIG9mIFBBUiBhbmQgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiByZWNvcmRzIHRha2VuIGF0IG5vb24gZWFjaCBkYXkgd2UnZCBoYXZlIHRvIGNvbWJpbmUgc29tZSBvZiB0aGUgbWV0aG9kcyBhYm92ZS4KCmBgYHtyfQpub29uIDwtIHNlcSggcmljZSREYXRlWzQ5XSwgcmljZSREYXRlWzc3MjldLCBsZW5ndGgub3V0ID0gODEgKQpub29uWzE6MTBdCmBgYAoKTm90aWNlIHdoYXQgSSBkaWQgaGVyZS4gIFRoZSBgRGF0ZWAgdmFyaWFibGUgd2UgbWFkZSBhYm92ZSBrbm93cyBob3cgdG8gbWFrZSBzZXF1ZW5jZXMgb3V0IG9mIGRhdGUgb2JqZWN0cyBiZWNhdXNlIGl0IGtub3dzIHdoYXQgY29uc3RpdHV0ZXMgYSBkYXkuICBJIHRvb2sgdGhlIG5vb24gZW50cnkgZm9yIDEvMS8yMDE0IGFuZCB0aGUgbm9vbiBlbnRyeSBmb3IgMy8yMi8yMDE0IGFuZCBtYWRlIGEgc2VxdWVuY2UgZXF1YWwgaW4gbGVuZ3RoIHRvIHRoZSBudW1iZXIgb2YgZGF5cyBiZXR3ZWVuIHRoZW0uICBOZXh0IEkgY2FuIHB1bGwgb3V0IG9ubHkgdGhlIGVudHJpZXMgaW4gdGhlIGZ1bGwgZGF0YSBzZXQgZXF1YWwgdG8gdmFsdWVzICppbiogdGhhdCB2ZWN0b3IgKEkgdXNlIHRoZSBgJWluJWAgb3BlcmF0b3IsIHdoaWNoIGlzIGEgKnNldCogb3BlcmF0b3IpIGFuZCBtYWtlIGEgbmV3IGRhdGEgc2V0IGZyb20ganVzdCB0aGUgbm9vbiBlbnRyaWVzLgoKYGBge3J9CnJpY2Vfbm9vbiA8LSByaWNlWyByaWNlJERhdGUgJWluJSBub29uLCBdCmBgYAoKQW5kIHRoZW4gbWFrZSBhIG5ldyBkYXRhIHNldCBvZiBib3RoIHRoZSBtZWFuIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhvc2UgdmFsdWVzLgoKYGBge3J9CmRmIDwtIGRhdGEuZnJhbWUoIFdlZWtkYXkgPSBsZXZlbHMoIHJpY2Vfbm9vbiRXZWVrZGF5ICkgKQpkZiRQQVIgPC0gYnkoIHJpY2Vfbm9vbiRQQVIsIHJpY2Vfbm9vbiRXZWVrZGF5LCBtZWFuICkKZGYkc2QgPC0gYnkoIHJpY2Vfbm9vbiRQQVIsIHJpY2Vfbm9vbiRXZWVrZGF5LCBzZCApCmBgYAoKVGhlbiB3ZSBjb3VsZCB1c2UgaXQgdG8gbWFrZSBhIHRhYmxlOgoKYGBge3J9CmxpYnJhcnkoIGtuaXRyICkKa2FibGUoIGRmLCBvdXRwdXQ9Imh0bWwiLGRpZ2l0cyA9IDIsY2FwdGlvbiA9ICJUYWJsZSAxOiBNZWFuIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgUEFSIGF0IHRoZSBSaWNlIENlbnRlciBpbiAyMDE0LiIgKQpgYGAKCm9yIHRoZW0gdXNpbmcgYGdncGxvdGAKCmBgYHtyfQpsaWJyYXJ5KCBnZ3Bsb3QyICkKZ2dwbG90KCBkZiwgYWVzKHg9V2Vla2RheSwgeT1QQVIpICkgKyAKICBnZW9tX2NvbCgpICsgCiAgZ2VvbV9lcnJvcmJhciggYWVzKHltaW49UEFSLCB5bWF4PVBBUitzZCApICkgKyAKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpIT0xEIFRIRSBCT0FUIQoKTm90aWNlIGhvdyB0aG9zZSB2YWx1ZXMgb24gdGhlIHgtYXhpcyBkbyBub3QgY29uZm9ybSB0byB0aGUgb3JkZXJpbmcgb2YgdGhlIHdlZWsuICBUaGF0IGlzIGJlY2F1c2Ugd2UgbWFkZSB0aGlzIG5ldyBkYXRhIHdpdGgganVzdCB0aGUgbGV2ZWxzIGJ1dCBkaWQgbm90IG1ha2UgdGhlbSBpbnRvIGFuIG9yZGVyZWQgZmFjdG9yLiAgVG8gY2xlYW4gdXAsIGxldCdzIGRvIHRoYXQgZmlyc3QuCgpgYGB7cn0KZGYkV2Vla2RheSA8LSBmYWN0b3IoIGRmJFdlZWtkYXksIAogICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZD1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gbGV2ZWxzKCByaWNlJFdlZWtkYXkpICkKZGYkV29ya2RheXMgPC0gYyggcmVwKCJXb3JrIERheSIsIDUpLCAiV2Vla2VuZCIsIldlZWtlbmQiKQpzdW1tYXJ5KCBkZiApCmBgYAoKTm93IHdoZW4gd2UgcGxvdCBpdCwgaXQgbG9va3MgY29ycmVjdCAoc2FtZSBwbG90IGNvZGUgY29waWVkIGhlcmUgYnV0IHVzaW5nIG1vZGlmaWVkIGRhdGEgZnJhbWUpLgoKYGBge3J9CmxpYnJhcnkoIFJDb2xvckJyZXdlciApCmdncGxvdCggZGYsIGFlcyh4PVdlZWtkYXksIHk9UEFSKSApICsgCiAgZ2VvbV9lcnJvcmJhciggYWVzKHltaW49UEFSLXNkLCB5bWF4PVBBUitzZCApICkgKyAKICBnZW9tX2NvbCggYWVzKCBmaWxsPVdvcmtkYXlzICkgKSArIAogIHRoZW1lX2NsYXNzaWMoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIoIHR5cGU9InF1YWwiLHBhbGV0dGUgPSA0KQpgYGAKCiMgRGF0YSBXb3JrZmxvd3MgZm9yIFN0YW5kYXJkIGBSYAoKT0ssIHNvIHRoZSBtYWluIHdvcmtmbG93cyB3ZSBhZG9wdCBpbiBSIHJlcXVpcmUgdXMgdG8gZG8gc29tZSBmdW5kYW1lbnRhbCBkYXRhIG1hbmlwdWxhdGlvbnMgaW5jbHVkaW5nIHRhc2tzIHN1Y2ggYXMgKnNlbGVjdCosICpmaWx0ZXIqLCAqbXV0YXRlKiwgKmFycmFuZ2UqLCAqZ3JvdXAqLCBhbmQgKnN1bW1hcml6ZSouICBBbGwgYmFzaWMgdGFza3MgY2FuIGJlIGNvbWJpbmVkIGluIHZhcmlvdXMgd2F5cyB0byB5aWVsZCBpbmZvcm1hdGl2ZSBpbnNpZ2h0cyBpbnRvIHRoZSBkYXRhLCBzdWNoIGFzIHRoYXQgYXdlc29tZSBub29uIFBBUiBwbG90IGFib3ZlLCB3aGljaCBpbmNpZGVudGFsbHkgc2hvd3MgdGhhdCB0aGVyZSBpcyBtb3JlIHN1biBhdCBub29uIG9uIEZyaWRheXMgdGhhbiBhbnkgb3RoZXIgZGF5IGltcGx5aW5nIHRoYXQgd2Ugc2hvdWxkIGp1c3QgdGFrZSBGcmlkYXlzIG9mZiB0byBoYXZlIG1vcmUgZXBpYyBzdW5ueSB3ZWVrZW5kcyBpbiB3aW50ZXIhCgpXaGlsZSB0aGlzIGlzIGFsbCBmaW5lIGFuZCBkYW5keSwgdGhlIHByb2Nlc3MgaGVyZSByZXF1aXJlcyBhIGxvdCBvZiBjb21wbGV4aXR5IHN1Y2ggYXM6ICAKLSBPdmVydXNlIG9mIGxvZ2ljYWwgc3RhdGVtZW50cyB0byBkbyBib29sZWFuIG1hdGggb24gaW5kaWNlcy4gIAotIExvdHMgb2YgdXNpbmcgdGhlICQtb3BlcmF0b3IgdG8gcmVmZXJlbmNlIHNwZWNpZmljIGNvbHVtbnMgb2YgZGF0YSB3aXRoaW4gdGhlIGBkYXRhLmZyYW1lYCBvYmplY3RzLgotIE92ZXJ1c2Ugb2YgcmVhc3NpZ25tZW50IGZvciBkZXJpdmF0aXZlIGRhdGEgZnJhbWVzLiAgQWJvdmUsIHdlIG1hZGUgYHJpY2VgLCBgcmljZV9ub29uYCwgYW5kIGBkZmAganVzdCB0byBnZXQgdGhhdCBsYXN0IGZpZ3VyZSBhYm92ZS4gIEluIGVhY2ggb2YgdGhlc2UgY2FzZXMsIHdlIGFyZSByZWFsbG9jYXRpbmcgYSBuZXcgYGRhdGEuZnJhbWVgIHZhcmlhYmxlIGVhY2ggdGltZS4gIFdlICpjb3VsZCogcmVhc3NpZ24gdGhlc2UgYmFjayBvbnRvIHRoZW1zZWx2ZXMgKGUuZy4sIHJldXNlIHZhcmlhYmxlIG5hbWVzKSBidXQgZWFjaCB0aW1lIHdlIGRvIHRoaXMsIHdlIGFjdHVhbGx5IGhhdmUgdG8gYWxsb2NhdGUgYW5kIHJlYWxsb2NhdGUgbGFyZ2UgYmxvY2tzIG9mIG1lbW9yeSBhbmQgZm9yIGRhdGEgc2V0cyBsYXJnZXIgdGhhdCB0aGVzZSAod2hpY2ggYXJlIGNvbW1vbmx5IHVzZWQpIGl0IGJlY29tZXMgYSBiaXQgb2YgYSBwYWluLgotIFRoZSBvdmVyYWxsIHN5bnRheCBpcyBoYXJkIHRvIHJlYWQuICAKCkZvciB0aGVzZSByZWFzb25zLCBgdGlkeXZlcnNlYCB3YXMgbWFkZS4uLgoKClteMV06IFJlbWVtYmVyIHRoYXQgaWYgeW91IGxlYXZlIGVpdGhlciB0aGUgcm93IG9yIGNvbHVtbiBpbmRleCBlbXB0eSBpbiB0aGUgc3F1YXJlIGJyYWNrZXRzLCBpdCB3aWxsIGluY2x1ZGUgYWxsIG9ic2VydmF0aW9ucyAoZS5nLiwgaWYgeW91IGxlYXZlIHJvdyBlbXB0eSBhbGwgcm93cyB3aWxsIGJlIHJldHVybmVkIHdoZXJlYXMgaWYgeW91IGxlYXZlIGNvbHVtbiBlbXB0eSBhbGwgY29sdW1ucyB3aWxsIGJlIHJldHVybmVkKS4KClteMl06IElmIHlvdSBnZXQgYW4gZXJyb3Igc2F5aW5nIHNvbWV0aGluZyBsaWtlLCAidGhlcmUgaXMgbm8gcGFja2FnZSBuYW1lZCBsdWJyaWRhdGUiIHRoZW4gdXNlIGBpbnN0YWxsLnBhY2thZ2VzKCJsdWJyaWRhdGUiKWAgYW5kIGluc3RhbGwgaXQuICBZb3Ugb25seSBuZWVkIHRvIGRvIHRoaXMgb25jZS4KCgo=