Data Import with readr
Getting Started
These functions are part of readr (which is loaded by tidyverse)
read_csv() reads comma separated values
read_csv2() semicolon separated
read_tsv() Tab delimited files
read_delim()(read any delimited files
read_fwf() reads fixed width files- use fwf_widths() or fwf_positions()
read_table() reas a common variation of fixed-width files where columns are separated by white space
read_log reads Apache style log files (also check out webreadr)
They all have similar syntax. The first argument is the path to the file to read.
read_csv(file, col_names = TRUE, col_types = NULL, locale = default_locale(), na = c(“”, “NA”), quoted_na = TRUE, comment = “”, trim_ws = TRUE, skip = 0, n_max = Inf, guess_max = min(1000, n_max), progress = interactive())
library(tidyverse)
read_csv("g:\\Downloads\\Earthquake.csv")
Parsed with column specification:
cols(
.default = col_double(),
time = col_datetime(format = ""),
magType = col_character(),
nst = col_character(),
gap = col_integer(),
net = col_character(),
id = col_character(),
updated = col_datetime(format = ""),
place = col_character(),
type = col_character(),
magNst = col_integer(),
status = col_character(),
locationSource = col_character(),
magSource = col_character()
)
See spec(...) for full column specifications.
4175 parsing failures.
row col expected actual
1964 gap no trailing characters .9
1975 gap no trailing characters .4
1987 gap no trailing characters .7
1991 gap no trailing characters .8
2007 gap no trailing characters .6
.... ... ...................... ......
See problems(...) for more details.
You can use skip argument to skip the first few lines - typically used for skipping blank lines.
library(tidyverse)
read_csv("g:\\Downloads\\Earthquake.csv", skip = 2)
Missing column names filled in: 'X8' [8]Duplicated column names deduplicated: 'us' => 'us_1' [22], 'us' => 'us_2' [23]Parsed with column specification:
cols(
.default = col_double(),
`2017-04-10T10:38:53.620Z` = col_datetime(format = ""),
mb = col_character(),
X8 = col_character(),
`45` = col_integer(),
us = col_character(),
us10008g7h = col_character(),
`2017-04-10T12:42:59.585Z` = col_datetime(format = ""),
`137km ESE of Pondaguitan, Philippines` = col_character(),
earthquake = col_character(),
`204` = col_integer(),
reviewed = col_character(),
us_1 = col_character(),
us_2 = col_character()
)
See spec(...) for full column specifications.
4175 parsing failures.
row col expected actual
1962 45 no trailing characters .9
1973 45 no trailing characters .4
1985 45 no trailing characters .7
1989 45 no trailing characters .8
2005 45 no trailing characters .6
.... ... ...................... ......
See problems(...) for more details.
You can supply inline CSV (Each row must be in a new line.)
read_csv("a,b,c
1,2,3
4,5,6")
You can use colnames = FALSE to tell R that there are no headers. (If you get Unused argument error, make sure that you are using read_csv and not read.csv)
library(tidyverse)
read_csv("g:\\Downloads\\Earthquake.csv", col_names = FALSE)
Parsed with column specification:
cols(
.default = col_character()
)
See spec(...) for full column specifications.
You can also use header = FALSE when your csv data does not have headers in them.
library(tidyverse)
read.csv("g:\\Downloads\\Earthquake.csv", header = FALSE)
Or you can pass character vectors for column names (doesnt work)
library(tidyverse)
read_csv(file = "g:\\Downloads\\query-noheader.csv",
col_names = c("x","y","z","wilson","jane","William"))
Parsed with column specification:
cols(
x = col_datetime(format = ""),
y = col_double(),
z = col_double(),
wilson = col_integer(),
jane = col_double(),
William = col_character()
)
70 parsing failures.
row col expected actual
1 -- 6 columns 22 columns
2 -- 6 columns 22 columns
3 -- 6 columns 22 columns
4 -- 6 columns 22 columns
5 -- 6 columns 22 columns
... ... ......... ..........
See problems(...) for more details.
How to treat NA
To specify the values or values that are used to repesent missing values in your table.
read_csv("a,b,c
1,2,-",
na ="-")
Compared to Base R
There are two main alternatives to readr: base R and data.table’s fread(). The most important differences are discussed below.
Base R
Compared to the corresponding base functions, readr functions:
Use a consistent naming scheme for the parameters (e.g. col_names and col_types not header and colClasses).
Are much faster (up to 10x).
Leave strings as is by default, and automatically parse common date/time formats.
Have a helpful progress bar if loading is going to take a while.
All functions work exactly the same way regardless of the current locale. To override the US-centric defaults, use locale().
They produce tibbles. Base R function inherit some behavior from your OS and environment variables, so import code that works on one computer might not work on other computers.
data.table and fread()
data.table has a function similar to read_csv() called fread. Compared to fread, readr functions:
Are slower (currently ~1.2-2x slower. If you want absolutely the best performance, use data.table::fread().
Use a slightly more sophisticated parser, recognising both doubled (“”“”) and backslash escapes (“"”), and can produce factors and date/times directly.
Forces you to supply all parameters, where fread() saves you work by automatically guessing the delimiter, whether or not the file has a header, and how many lines to skip.
Are built on a different underlying infrastructure. Readr functions are designed to be quite general, which makes it easier to add support for new rectangular data formats. fread() is designed to be as fast as possible.
Exercises
Other arguments: read_csv(file, col_names = TRUE, col_types = NULL, locale = default_locale(), na = c(“”, “NA”), quoted_na = TRUE, comment = “”, trim_ws = TRUE, skip = 0, n_max = Inf, guess_max = min(1000, n_max), progress = interactive())
Parsing a Vector
The parse_* () functions take a character vector and return a more specialized vector like a logical, integer, or date.
str(parse_logical(c("TRUE","FALSE","NA")))
logi [1:3] TRUE FALSE NA
str(parse_integer(c("1","2","3")))
int [1:3] 1 2 3
str(parse_date(c("2001-01-01","1979-10-14")))
Date[1:2], format: "2001-01-01" "1979-10-14"
The na argument specifies which strings should be treated as missing.
parse_integer(c("1","2",".","4"), na=".")
[1] 1 2 NA 4
If parsing fails, you will get a warning. Plus, the failures will be missing in the output. If there are many failures, better use problems() to get the complete set.
x <- parse_integer(c("123","345","abc","123.45"))
2 parsing failures.
row col expected actual
3 -- an integer abc
4 -- no trailing characters .45
x
[1] 123 345 NA NA
attr(,"problems")
problems(x)
Eight particualarly important parsers:
parse_logical() for true or false values
parse_integer() for whole numbers
parse_double() strict numeric parser
parse_number()
parse_character() be aware of character encodings
parse_factor() creates factors for categorical values
Parse_datetime()
parse_date()
parse_time
x <- parse_number("$10,000")
y <- parse_number("10%")
z <- parse_number("It cost $123.56")
x
[1] 10000
y
[1] 10
z
[1] 123.56
Using locale argument to specify , as the decimal point
parse_double("1.23")
[1] 1.23
parse_double("1,23", locale = locale(decimal_mark = ","))
[1] 1.23
Using grouping mark.
# US
parse_number("$123,456,789.00")
[1] 123456789
# Europe
parse_number("$123.456.789,00",
locale=locale(grouping_mark = "."))
[1] 123456789
# Switzerland
parse_number("$123'456'789.00",
locale=locale(grouping_mark = "'"))
[1] 123456789
Readr’s default locale is US centric.
Strings
# reader uses UTF-8 encoding
charToRaw("Wilson was here")
[1] 57 69 6c 73 6f 6e 20 77 61 73 20 68 65 72 65
x1 <- "El Ni\xf1o was particularly bad this year"
parse_character(x1, locale = locale(encoding = "Latin1"))
[1] "El Niño was particularly bad this year"
If you dont know the encoding method that was used, you can try guess_encoding() function
guess_encoding(charToRaw(x1))
Factors
Parse_factor() to generate cateorical variables that have a known set of possible values.
fruit <- c("apple","banana")
parse_factor(c("apple","banana", "bananana"), levels = fruit)
1 parsing failure.
row col expected actual
3 -- value in level set bananana
[1] apple banana <NA>
attr(,"problems")
Levels: apple banana
Dates, Date-times and Times
We can use 3 different parsers depending on whether we want date, time or date-time.
Parse_date() expects an ISO8601 date-time.
parse_datetime("2010-10-01T2010")
[1] "2010-10-01 20:10:00 UTC"
# if time is omitted, it will be set to midnight
parse_datetime("2010-10-01")
[1] "2010-10-01 UTC"
Parse_date expects a four digit year, a - or /, the month, a - or / then the day.
parse_date("2010-10-01")
[1] "2010-10-01"
parse_date("2010/10/01")
[1] "2010-10-01"
parse_date("2010-10/01")
[1] "2010-10-01"
parse_date("10-01-2010")
1 parsing failure.
row col expected actual
1 -- date like 10-01-2010
[1] NA
Parse_time() expect the hour, : minutes, optionally : and seconds and an optional am/pm specifier
library(hms)
parse_time("1:10 am")
01:10:00
parse_time("02:10 am")
02:10:00
parse_time("20:10:01")
20:10:01
parse_time("8:10:01 pm")
20:10:01
You can supply your own data formats.
Year: “%Y” (4 digits). “%y” (2 digits); 00-69 -> 2000-2069, 70-99 -> 1970-1999.
Month: “%m” (2 digits), “%b” (abbreviated name in current locale), “%B” (full name in current locale).
Day: “%d” (2 digits), “%e” (optional leading space)
Hour: “%H”
Minutes: “%M”
Seconds: “%S” (integer seconds), “%OS” (partial seconds)
Time zone: “%Z” (as name, e.g. “America/Chicago”), “%z” (as offset from UTC, e.g. “+0800”)
AM/PM indicator: “%p”.
Non-digits: “%.” skips one non-digit character, “%+” skips one or more non-digit characters, “%*" skips any number of non-digits characters.
Automatic parsers: “%AD” parses with a flexible YMD parser, “%AT” parses with a flexible HMS parser.
Shortcuts: “%D” = “%m/%d/%y”, “%F” = “%Y-%m-%d”, “%R” = “%H:%M”, “%T” = “%H:%M:%S”, “%x” = “%y/%m/%d”.
parse_date("01/02/15", "%m/%d/%y")
[1] "2015-01-02"
parse_date("01/02/15", "%d/%m/%y")
[1] "2015-02-01"
parse_date("01/02/15", "%y/%m/%y")
[1] "2015-02-01"
parse_date("01/02/15", "%D")
[1] "2015-01-02"
parse_date("01/02/15", "%x")
[1] "2001-02-15"
parse_date("1 January 2015", "%d %B %Y")
[1] "2015-01-01"
parse_date("1 Jan 2015", "%d %b %Y")
[1] "2015-01-01"
Parsing a File
readr uses a heuristic to figure out the type of each column: it reads the first 1000 rows and uses some moderately conservative heuristics to figure out the type of each column. The heuristic tries each of the following types, stopping when it finds a match. Logical, integer, double, number, time, date, date-time. Catch all is string.
challenge <- read_csv(readr_example("challenge.csv"))
Parsed with column specification:
cols(
x = col_integer(),
y = col_character()
)
1000 parsing failures.
row col expected actual
1001 x no trailing characters .23837975086644292
1002 x no trailing characters .41167997173033655
1003 x no trailing characters .7460716762579978
1004 x no trailing characters .723450553836301
1005 x no trailing characters .614524137461558
.... ... ...................... ..................
See problems(...) for more details.
problems(challenge)
The error message shows that the integer column contains data with decimals in it. So we use parse_double.
challenge <- read_csv(readr_example("challenge.csv"),
col_types = cols(
x = col_double(),
y = col_character()
)
)
# look at the last few rows
tail(challenge)
Aha Y is a date field, not a character field.
challenge <- read_csv(readr_example("challenge.csv"),
col_types = cols(
x = col_double(),
y = col_date()
)
)
# look at the last few rows
tail(challenge)
It is best practice to use col_types to ensure consistent and reproducible data import script. To be really strict, we can also use stop_for_problems() which will throw an error and stop the script if there are any errors.
Other Strategies
Using guess_max argument
challenge2 <- read_csv(
readr_example("challenge.csv"),
guess_max = 1001
)
Parsed with column specification:
cols(
x = col_double(),
y = col_date(format = "")
)
challenge2
Sometimes it is easier to diagnose problems if you just read in all the columns as character vectors.
challenge2 <- read_csv(
readr_example("challenge.csv"),
col_types = cols(.default = col_character())
)
challenge2
This is useful in conjunction with the type_convert() which applies the parsing heuristics to the character columns in a data frame.
df <- tribble(
~x, ~ y,
"1", "1.21",
"2","2.32",
"3", "4.56"
)
df
# Note the column types after the convertion
type_convert(df)
Parsed with column specification:
cols(
x = col_integer(),
y = col_double()
)
Note: for very large files, you might want to set n_max to a smallish number like 10,000 or 100,000.
Writing to a File
Two functions write_csv and write_tsv(). Always uses UTF-8 encoding Saving dates and date-times in ISO8601 format so they are esily parsed elsewhere.
If you want to export a CSV file to Excel, use write_excel_csv() this writes a special character (a ’byte order mark“) at the start of the file, which tells Excel that you’re using the UTF-8 encoding.
# to append to an existing file.
# Note: type information is lost when you save to csv format
write_csv(challenge, "Challenge.csv")
But saving in CSV format is a little unreliable for caching interim results. You need to re-create the coumn specification every time you load in. There are two alternatives: writerds() and read_rds() will store in R’s custom binary format called RDS.
write_rds(challenge, "challenge.rds")
read_rds("challenge.rds")
You can also use the feather package to implement a fast binary file format tha tcan be shared across programming languages.
library(feather)
write_feather(challenge, "challenge.feather")
read_feather("challenge.feather")
Notes: Feather is faster than RDS and can be used outside of R but RDS supposrts list-columns which feather does not.
Other Types of Data
haven reads SPSS, Stata and SAS files readxl reads Excel files (both xls. and xlsx) DBI allows you to run SQL queries against a database and return a data frame. jsonlite for hierarchical data xml2 for XML data See also Jenny Bryan at https://jennybc.github.io/purrr-tutorial
LS0tDQp0aXRsZTogIlIgZm9yIERhdGEgU2NpZW5jZSBDaGFwdGVyIDgiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo8aDE+IERhdGEgSW1wb3J0IHdpdGggcmVhZHIgPGgxPg0KDQo8aDI+IEdldHRpbmcgU3RhcnRlZCA8L2gxPg0KVGhlc2UgZnVuY3Rpb25zIGFyZSBwYXJ0IG9mIHJlYWRyICh3aGljaCBpcyBsb2FkZWQgYnkgdGlkeXZlcnNlKTwvYnI+DQo8bGk+cmVhZF9jc3YoKSByZWFkcyBjb21tYSBzZXBhcmF0ZWQgdmFsdWVzPC9saT4NCjxsaT5yZWFkX2NzdjIoKSBzZW1pY29sb24gc2VwYXJhdGVkIDwvbGk+DQo8bGk+cmVhZF90c3YoKSBUYWIgZGVsaW1pdGVkIGZpbGVzPC9saT4NCjxsaT5yZWFkX2RlbGltKCkocmVhZCBhbnkgZGVsaW1pdGVkIGZpbGVzIDwvbGk+DQo8bGk+cmVhZF9md2YoKSByZWFkcyBmaXhlZCB3aWR0aCBmaWxlcy0gdXNlIGZ3Zl93aWR0aHMoKSBvciBmd2ZfcG9zaXRpb25zKCk8L2xpPg0KPGxpPnJlYWRfdGFibGUoKSByZWFzIGEgY29tbW9uIHZhcmlhdGlvbiBvZiBmaXhlZC13aWR0aCBmaWxlcyB3aGVyZSBjb2x1bW5zIGFyZSBzZXBhcmF0ZWQgYnkgd2hpdGUgc3BhY2UgPC9saT4NCjxsaT5yZWFkX2xvZyByZWFkcyBBcGFjaGUgc3R5bGUgbG9nIGZpbGVzIChhbHNvIGNoZWNrIG91dCB3ZWJyZWFkcikgPC9saT4NCjwvcD4NCg0KDQpUaGV5IGFsbCBoYXZlIHNpbWlsYXIgc3ludGF4LiBUaGUgZmlyc3QgYXJndW1lbnQgaXMgdGhlIHBhdGggdG8gdGhlIGZpbGUgdG8gcmVhZC4gPC9wPg0KDQpyZWFkX2NzdihmaWxlLCBjb2xfbmFtZXMgPSBUUlVFLCBjb2xfdHlwZXMgPSBOVUxMLA0KICBsb2NhbGUgPSBkZWZhdWx0X2xvY2FsZSgpLCBuYSA9IGMoIiIsICJOQSIpLCBxdW90ZWRfbmEgPSBUUlVFLA0KICBjb21tZW50ID0gIiIsIHRyaW1fd3MgPSBUUlVFLCBza2lwID0gMCwgbl9tYXggPSBJbmYsDQogIGd1ZXNzX21heCA9IG1pbigxMDAwLCBuX21heCksIHByb2dyZXNzID0gaW50ZXJhY3RpdmUoKSkNCg0KPC9wPg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KcmVhZF9jc3YoImc6XFxEb3dubG9hZHNcXEVhcnRocXVha2UuY3N2IikNCmBgYA0KDQpZb3UgY2FuIHVzZSBza2lwIGFyZ3VtZW50IHRvIHNraXAgdGhlIGZpcnN0IGZldyBsaW5lcyAtIHR5cGljYWxseSB1c2VkIGZvciBza2lwcGluZyBibGFuayBsaW5lcy4gDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpyZWFkX2NzdigiZzpcXERvd25sb2Fkc1xcRWFydGhxdWFrZS5jc3YiLCBza2lwID0gMikNCmBgYA0KDQpZb3UgY2FuIHN1cHBseSBpbmxpbmUgQ1NWDQooRWFjaCByb3cgbXVzdCBiZSBpbiBhIG5ldyBsaW5lLikNCmBgYHtyfQ0KcmVhZF9jc3YoImEsYixjDQogICAgICAgICAxLDIsMw0KICAgICAgICAgNCw1LDYiKQ0KYGBgDQoNCg0KWW91IGNhbiB1c2UgY29sbmFtZXMgPSBGQUxTRSB0byB0ZWxsIFIgdGhhdCB0aGVyZSBhcmUgbm8gaGVhZGVycy4NCihJZiB5b3UgZ2V0IFVudXNlZCBhcmd1bWVudCBlcnJvciwgbWFrZSBzdXJlIHRoYXQgeW91IGFyZSB1c2luZyByZWFkX2NzdiBhbmQgbm90IHJlYWQuY3N2KSA8L3A+DQoNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCnJlYWRfY3N2KCJnOlxcRG93bmxvYWRzXFxFYXJ0aHF1YWtlLmNzdiIsIGNvbF9uYW1lcyA9IEZBTFNFKQ0KYGBgDQoNCllvdSBjYW4gYWxzbyB1c2UgaGVhZGVyID0gRkFMU0Ugd2hlbiB5b3VyIGNzdiBkYXRhIGRvZXMgbm90IGhhdmUgaGVhZGVycyBpbiB0aGVtLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KcmVhZC5jc3YoImc6XFxEb3dubG9hZHNcXEVhcnRocXVha2UuY3N2IiwgaGVhZGVyID0gRkFMU0UpDQpgYGANCg0KT3IgeW91IGNhbiBwYXNzIGNoYXJhY3RlciB2ZWN0b3JzIGZvciBjb2x1bW4gbmFtZXMNCihkb2VzbnQgd29yaykNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCiMgdXNlIGEgY29sbGVjdGlvbiBvZiBjaGFyYWN0ZXJzIGFzIHRoZSBoZWFkZXINCg0KcmVhZF9jc3YoZmlsZSA9ICJnOlxcRG93bmxvYWRzXFxxdWVyeS1ub2hlYWRlci5jc3YiLCANCiAgICAgICAgIGNvbF9uYW1lcyA9IGMoIngiLCJ5IiwieiIsIndpbHNvbiIsImphbmUiLCJXaWxsaWFtIikpDQpgYGANCg0KPGgzPiBIb3cgdG8gdHJlYXQgTkEgPC9oMz4NCg0KVG8gc3BlY2lmeSB0aGUgdmFsdWVzIG9yIHZhbHVlcyB0aGF0IGFyZSB1c2VkIHRvIHJlcGVzZW50IG1pc3NpbmcgdmFsdWVzIGluIHlvdXIgdGFibGUuIA0KDQpgYGB7cn0NCnJlYWRfY3N2KCJhLGIsYw0KICAgICAgICAgMSwyLC4iLCANCiAgICAgICAgIG5hID0iLiIpDQpgYGANCg0KPGgyPkNvbXBhcmVkIHRvIEJhc2UgUjwvaDI+DQoNClRoZXJlIGFyZSB0d28gbWFpbiBhbHRlcm5hdGl2ZXMgdG8gcmVhZHI6IGJhc2UgUiBhbmQgZGF0YS50YWJsZSdzIGZyZWFkKCkuIFRoZSBtb3N0IGltcG9ydGFudCBkaWZmZXJlbmNlcyBhcmUgZGlzY3Vzc2VkIGJlbG93LjwvcD4NCg0KPGgzPkJhc2UgUjwvaDM+DQoNCkNvbXBhcmVkIHRvIHRoZSBjb3JyZXNwb25kaW5nIGJhc2UgZnVuY3Rpb25zLCByZWFkciBmdW5jdGlvbnM6DQoNClVzZSBhIGNvbnNpc3RlbnQgbmFtaW5nIHNjaGVtZSBmb3IgdGhlIHBhcmFtZXRlcnMgKGUuZy4gY29sX25hbWVzIGFuZCBjb2xfdHlwZXMgbm90IGhlYWRlciBhbmQgY29sQ2xhc3NlcykuDQoNCkFyZSBtdWNoIGZhc3RlciAodXAgdG8gMTB4KS4NCg0KTGVhdmUgc3RyaW5ncyBhcyBpcyBieSBkZWZhdWx0LCBhbmQgYXV0b21hdGljYWxseSBwYXJzZSBjb21tb24gZGF0ZS90aW1lIGZvcm1hdHMuDQoNCkhhdmUgYSBoZWxwZnVsIHByb2dyZXNzIGJhciBpZiBsb2FkaW5nIGlzIGdvaW5nIHRvIHRha2UgYSB3aGlsZS4NCg0KQWxsIGZ1bmN0aW9ucyB3b3JrIGV4YWN0bHkgdGhlIHNhbWUgd2F5IHJlZ2FyZGxlc3Mgb2YgdGhlIGN1cnJlbnQgbG9jYWxlLiBUbyBvdmVycmlkZSB0aGUgVVMtY2VudHJpYyBkZWZhdWx0cywgdXNlIGxvY2FsZSgpLg0KDQpUaGV5IHByb2R1Y2UgdGliYmxlcy4gQmFzZSBSIGZ1bmN0aW9uIGluaGVyaXQgc29tZSBiZWhhdmlvciBmcm9tIHlvdXIgT1MgYW5kIGVudmlyb25tZW50IHZhcmlhYmxlcywgc28gaW1wb3J0IGNvZGUgdGhhdCB3b3JrcyBvbiBvbmUgY29tcHV0ZXIgbWlnaHQgbm90IHdvcmsgb24gb3RoZXIgY29tcHV0ZXJzLg0KDQoNCjxoMz5kYXRhLnRhYmxlIGFuZCBmcmVhZCgpPC9oMz4NCg0KZGF0YS50YWJsZSBoYXMgYSBmdW5jdGlvbiBzaW1pbGFyIHRvIHJlYWRfY3N2KCkgY2FsbGVkIGZyZWFkLiBDb21wYXJlZCB0byBmcmVhZCwgcmVhZHIgZnVuY3Rpb25zOg0KDQpBcmUgc2xvd2VyIChjdXJyZW50bHkgfjEuMi0yeCBzbG93ZXIuIElmIHlvdSB3YW50IGFic29sdXRlbHkgdGhlIGJlc3QgcGVyZm9ybWFuY2UsIHVzZSBkYXRhLnRhYmxlOjpmcmVhZCgpLg0KDQpVc2UgYSBzbGlnaHRseSBtb3JlIHNvcGhpc3RpY2F0ZWQgcGFyc2VyLCByZWNvZ25pc2luZyBib3RoIGRvdWJsZWQgKCIiIiIpIGFuZCBiYWNrc2xhc2ggZXNjYXBlcyAoIlwiIiksIGFuZCBjYW4gcHJvZHVjZSBmYWN0b3JzIGFuZCBkYXRlL3RpbWVzIGRpcmVjdGx5Lg0KDQpGb3JjZXMgeW91IHRvIHN1cHBseSBhbGwgcGFyYW1ldGVycywgd2hlcmUgZnJlYWQoKSBzYXZlcyB5b3Ugd29yayBieSBhdXRvbWF0aWNhbGx5IGd1ZXNzaW5nIHRoZSBkZWxpbWl0ZXIsIHdoZXRoZXIgb3Igbm90IHRoZSBmaWxlIGhhcyBhIGhlYWRlciwgYW5kIGhvdyBtYW55IGxpbmVzIHRvIHNraXAuDQoNCkFyZSBidWlsdCBvbiBhIGRpZmZlcmVudCB1bmRlcmx5aW5nIGluZnJhc3RydWN0dXJlLiBSZWFkciBmdW5jdGlvbnMgYXJlIGRlc2lnbmVkIHRvIGJlIHF1aXRlIGdlbmVyYWwsIHdoaWNoIG1ha2VzIGl0IGVhc2llciB0byBhZGQgc3VwcG9ydCBmb3IgbmV3IHJlY3Rhbmd1bGFyIGRhdGEgZm9ybWF0cy4gZnJlYWQoKSBpcyBkZXNpZ25lZCB0byBiZSBhcyBmYXN0IGFzIHBvc3NpYmxlLg0KDQo8aDM+IEV4ZXJjaXNlcyA8L2gzPg0KDQpPdGhlciBhcmd1bWVudHM6PC9icj4NCnJlYWRfY3N2KGZpbGUsIGNvbF9uYW1lcyA9IFRSVUUsIGNvbF90eXBlcyA9IE5VTEwsDQogIGxvY2FsZSA9IGRlZmF1bHRfbG9jYWxlKCksIG5hID0gYygiIiwgIk5BIiksIHF1b3RlZF9uYSA9IFRSVUUsDQogIGNvbW1lbnQgPSAiIiwgdHJpbV93cyA9IFRSVUUsIHNraXAgPSAwLCBuX21heCA9IEluZiwNCiAgZ3Vlc3NfbWF4ID0gbWluKDEwMDAsIG5fbWF4KSwgcHJvZ3Jlc3MgPSBpbnRlcmFjdGl2ZSgpKTwvcD4NCiAgDQoNCg0KPGgyPiBQYXJzaW5nIGEgVmVjdG9yIDwvaDI+DQoNClRoZSBwYXJzZV8qICgpIGZ1bmN0aW9ucyB0YWtlIGEgY2hhcmFjdGVyIHZlY3RvciBhbmQgcmV0dXJuIGEgbW9yZSBzcGVjaWFsaXplZCB2ZWN0b3IgbGlrZSBhIGxvZ2ljYWwsIGludGVnZXIsIG9yIGRhdGUuIDwvcD4NCmBgYHtyfQ0Kc3RyKHBhcnNlX2xvZ2ljYWwoYygiVFJVRSIsIkZBTFNFIiwiTkEiKSkpDQpgYGANCg0KYGBge3J9DQpzdHIocGFyc2VfaW50ZWdlcihjKCIxIiwiMiIsIjMiKSkpDQpgYGANCg0KYGBge3J9DQpzdHIocGFyc2VfZGF0ZShjKCIyMDAxLTAxLTAxIiwiMTk3OS0xMC0xNCIpKSkNCmBgYA0KDQpUaGUgbmEgYXJndW1lbnQgc3BlY2lmaWVzIHdoaWNoIHN0cmluZ3Mgc2hvdWxkIGJlIHRyZWF0ZWQgYXMgbWlzc2luZy4NCg0KYGBge3J9DQpwYXJzZV9pbnRlZ2VyKGMoIjEiLCIyIiwiLiIsIjQiKSwgbmE9Ii4iKQ0KDQpgYGANCg0KDQpJZiBwYXJzaW5nIGZhaWxzLCB5b3Ugd2lsbCBnZXQgYSB3YXJuaW5nLiBQbHVzLCB0aGUgZmFpbHVyZXMgd2lsbCBiZSBtaXNzaW5nIGluIHRoZSBvdXRwdXQuIElmIHRoZXJlIGFyZSBtYW55IGZhaWx1cmVzLCBiZXR0ZXIgdXNlIHByb2JsZW1zKCkgdG8gZ2V0IHRoZSBjb21wbGV0ZSBzZXQuIA0KDQpgYGB7cn0NCnggPC0gIHBhcnNlX2ludGVnZXIoYygiMTIzIiwiMzQ1IiwiYWJjIiwiMTIzLjQ1IikpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KeA0KDQpgYGANCg0KYGBge3J9DQpwcm9ibGVtcyh4KQ0KYGBgDQoNCjxoMz4gRWlnaHQgcGFydGljdWFsYXJseSBpbXBvcnRhbnQgcGFyc2VyczogPC9oMz4NCg0KPGxpPnBhcnNlX2xvZ2ljYWwoKSBmb3IgdHJ1ZSBvciBmYWxzZSB2YWx1ZXMgPC9saT4NCjxsaT5wYXJzZV9pbnRlZ2VyKCkgZm9yIHdob2xlIG51bWJlcnMgPC9saT4NCjxsaT5wYXJzZV9kb3VibGUoKSBzdHJpY3QgbnVtZXJpYyBwYXJzZXIgPC9saT4NCjxsaT5wYXJzZV9udW1iZXIoKSA8L2xpPg0KPGxpPnBhcnNlX2NoYXJhY3RlcigpIGJlIGF3YXJlIG9mIGNoYXJhY3RlciBlbmNvZGluZ3M8L2xpPg0KPGxpPnBhcnNlX2ZhY3RvcigpIGNyZWF0ZXMgZmFjdG9ycyBmb3IgY2F0ZWdvcmljYWwgdmFsdWVzIDwvbGk+DQo8bGk+UGFyc2VfZGF0ZXRpbWUoKTwvbGk+DQo8bGk+cGFyc2VfZGF0ZSgpPC9saT4NCjxsaT5wYXJzZV90aW1lIDwvbGk+DQoNCmBgYHtyfQ0KeCA8LSBwYXJzZV9udW1iZXIoIiQxMCwwMDAiKQ0KeSA8LSBwYXJzZV9udW1iZXIoIjEwJSIpDQp6IDwtIHBhcnNlX251bWJlcigiSXQgY29zdCAkMTIzLjU2IikNCngNCnkNCnoNCg0KDQpgYGANCg0KVXNpbmcgbG9jYWxlIGFyZ3VtZW50IHRvIHNwZWNpZnkgLCBhcyB0aGUgZGVjaW1hbCBwb2ludCANCg0KYGBge3J9DQpwYXJzZV9kb3VibGUoIjEuMjMiKQ0KcGFyc2VfZG91YmxlKCIxLDIzIiwgbG9jYWxlID0gbG9jYWxlKGRlY2ltYWxfbWFyayA9ICIsIikpDQpgYGANCg0KVXNpbmcgZ3JvdXBpbmcgbWFyay4NCmBgYHtyfQ0KIyBVUw0KcGFyc2VfbnVtYmVyKCIkMTIzLDQ1Niw3ODkuMDAiKQ0KIyBFdXJvcGUNCnBhcnNlX251bWJlcigiJDEyMy40NTYuNzg5LDAwIiwNCiAgICAgICAgICAgICBsb2NhbGUgPSBsb2NhbGUoZ3JvdXBpbmdfbWFyayA9ICIuIikpDQoNCiMgU3dpdHplcmxhbmQNCnBhcnNlX251bWJlcigiJDEyMyc0NTYnNzg5LjAwIiwNCiAgICAgICAgICAgICBsb2NhbGUgPSBsb2NhbGUoZ3JvdXBpbmdfbWFyayA9ICInIikpDQpgYGANCg0KUmVhZHIncyBkZWZhdWx0IGxvY2FsZSBpcyBVUyBjZW50cmljLiANCg0KPGgyPiBTdHJpbmdzIDwvaDI+DQoNCg0KYGBge3J9DQojIHJlYWRlciB1c2VzIFVURi04IGVuY29kaW5nIA0KY2hhclRvUmF3KCJXaWxzb24gd2FzIGhlcmUiKQ0KeDEgPC0gICJFbCBOaVx4ZjFvIHdhcyBwYXJ0aWN1bGFybHkgYmFkIHRoaXMgeWVhciINCnBhcnNlX2NoYXJhY3Rlcih4MSwgbG9jYWxlID0gbG9jYWxlKGVuY29kaW5nID0gIkxhdGluMSIpKQ0KYGBgDQoNCklmIHlvdSBkb250IGtub3cgdGhlIGVuY29kaW5nIG1ldGhvZCB0aGF0IHdhcyB1c2VkLCB5b3UgY2FuIHRyeSBndWVzc19lbmNvZGluZygpIGZ1bmN0aW9uIA0KDQpgYGB7cn0NCmd1ZXNzX2VuY29kaW5nKGNoYXJUb1Jhdyh4MSkpDQpgYGANCg0KPGgyPiBGYWN0b3JzIDwvaDI+DQoNClBhcnNlX2ZhY3RvcigpIHRvIGdlbmVyYXRlIGNhdGVvcmljYWwgdmFyaWFibGVzIHRoYXQgaGF2ZSBhIGtub3duIHNldCBvZiBwb3NzaWJsZSB2YWx1ZXMuIA0KYGBge3J9DQpmcnVpdCA8LSAgYygiYXBwbGUiLCJiYW5hbmEiKQ0KcGFyc2VfZmFjdG9yKGMoImFwcGxlIiwiYmFuYW5hIiwgImJhbmFuYW5hIiksIGxldmVscyA9IGZydWl0KQ0KDQpgYGANCg0KDQo8aDI+IERhdGVzLCBEYXRlLXRpbWVzIGFuZCBUaW1lcyA8L2gyPg0KICANCldlIGNhbiB1c2UgMyBkaWZmZXJlbnQgcGFyc2VycyBkZXBlbmRpbmcgb24gd2hldGhlciB3ZSB3YW50IGRhdGUsIHRpbWUgb3IgZGF0ZS10aW1lLiANCg0KUGFyc2VfZGF0ZSgpIGV4cGVjdHMgYW4gSVNPODYwMSBkYXRlLXRpbWUuIA0KYGBge3J9DQpwYXJzZV9kYXRldGltZSgiMjAxMC0xMC0wMVQyMDEwIikNCiMgaWYgdGltZSBpcyBvbWl0dGVkLCBpdCB3aWxsIGJlIHNldCB0byBtaWRuaWdodA0KcGFyc2VfZGF0ZXRpbWUoIjIwMTAtMTAtMDEiKQ0KYGBgDQoNCg0KDQpQYXJzZV9kYXRlIGV4cGVjdHMgYSBmb3VyIGRpZ2l0IHllYXIsIGEgLSBvciAvLCB0aGUgbW9udGgsIGEgLSBvciAvIHRoZW4gdGhlIGRheS4NCg0KYGBge3J9DQpwYXJzZV9kYXRlKCIyMDEwLTEwLTAxIikNCnBhcnNlX2RhdGUoIjIwMTAvMTAvMDEiKQ0KcGFyc2VfZGF0ZSgiMjAxMC0xMC8wMSIpDQpwYXJzZV9kYXRlKCIxMC0wMS0yMDEwIikNCg0KYGBgDQoNCg0KUGFyc2VfdGltZSgpIGV4cGVjdCB0aGUgaG91ciwgOiBtaW51dGVzLCBvcHRpb25hbGx5IDogYW5kIHNlY29uZHMgYW5kIGFuIG9wdGlvbmFsIGFtL3BtIHNwZWNpZmllcg0KDQpgYGB7cn0NCiMgUiBkb2VzbnQgaGF2ZSBhIGdyZWF0IGIgdWlsdC1pbiBjbGFzcyBmb3IgdGltZSBkYXRhLCBzbyB3ZSB1c2UgdGhlIG9uZSBwcm92aWRlZCBpbiB0aGUgaG1zIHBhY2thZ2UNCmxpYnJhcnkoaG1zKQ0KcGFyc2VfdGltZSgiMToxMCBhbSIpDQpwYXJzZV90aW1lKCIwMjoxMCBhbSIpDQpwYXJzZV90aW1lKCIyMDoxMDowMSIpDQpwYXJzZV90aW1lKCI4OjEwOjAxIHBtIikNCmBgYA0KDQoNCllvdSBjYW4gc3VwcGx5IHlvdXIgb3duIGRhdGEgZm9ybWF0cy48L2JyPg0KDQpZZWFyOiAiJVkiICg0IGRpZ2l0cykuICIleSIgKDIgZGlnaXRzKTsgMDAtNjkgLT4gMjAwMC0yMDY5LCA3MC05OSAtPiAxOTcwLTE5OTkuPC9icj4NCg0KTW9udGg6ICIlbSIgKDIgZGlnaXRzKSwgIiViIiAoYWJicmV2aWF0ZWQgbmFtZSBpbiBjdXJyZW50IGxvY2FsZSksICIlQiIgKGZ1bGwgbmFtZSBpbiBjdXJyZW50IGxvY2FsZSkuPC9icj4NCg0KRGF5OiAiJWQiICgyIGRpZ2l0cyksICIlZSIgKG9wdGlvbmFsIGxlYWRpbmcgc3BhY2UpPC9icj4NCg0KSG91cjogIiVIIjwvYnI+DQoNCk1pbnV0ZXM6ICIlTSI8L2JyPg0KDQpTZWNvbmRzOiAiJVMiIChpbnRlZ2VyIHNlY29uZHMpLCAiJU9TIiAocGFydGlhbCBzZWNvbmRzKTwvYnI+DQoNClRpbWUgem9uZTogIiVaIiAoYXMgbmFtZSwgZS5nLiAiQW1lcmljYS9DaGljYWdvIiksICIleiIgKGFzIG9mZnNldCBmcm9tIFVUQywgZS5nLiAiKzA4MDAiKTwvYnI+DQoNCkFNL1BNIGluZGljYXRvcjogIiVwIi48L2JyPg0KDQpOb24tZGlnaXRzOiAiJS4iIHNraXBzIG9uZSBub24tZGlnaXQgY2hhcmFjdGVyLCAiJSsiIHNraXBzIG9uZSBvciBtb3JlIG5vbi1kaWdpdCBjaGFyYWN0ZXJzLCAiJSoiIHNraXBzIGFueSBudW1iZXIgb2Ygbm9uLWRpZ2l0cyBjaGFyYWN0ZXJzLjwvYnI+DQoNCkF1dG9tYXRpYyBwYXJzZXJzOiAiJUFEIiBwYXJzZXMgd2l0aCBhIGZsZXhpYmxlIFlNRCBwYXJzZXIsICIlQVQiIHBhcnNlcyB3aXRoIGEgZmxleGlibGUgSE1TIHBhcnNlci48L2JyPg0KDQpTaG9ydGN1dHM6ICIlRCIgPSAiJW0vJWQvJXkiLCAiJUYiID0gIiVZLSVtLSVkIiwgIiVSIiA9ICIlSDolTSIsICIlVCIgPSAiJUg6JU06JVMiLCAiJXgiID0gIiV5LyVtLyVkIi48L2JyPg0KDQoNCmBgYHtyfQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJW0vJWQvJXkiKQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJWQvJW0vJXkiKQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJXkvJW0vJXkiKQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJUQiKQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJXgiKQ0KDQpwYXJzZV9kYXRlKCIxIEphbnVhcnkgMjAxNSIsICIlZCAlQiAlWSIpDQpwYXJzZV9kYXRlKCIxIEphbiAyMDE1IiwgIiVkICViICVZIikNCg0KYGBgDQoNCg0KDQo8aDI+IFBhcnNpbmcgYSBGaWxlIDwvaDI+DQoNCnJlYWRyIHVzZXMgYSBoZXVyaXN0aWMgdG8gZmlndXJlIG91dCB0aGUgdHlwZSBvZiBlYWNoIGNvbHVtbjogaXQgcmVhZHMgdGhlIGZpcnN0IDEwMDAgcm93cyBhbmQgdXNlcyBzb21lIG1vZGVyYXRlbHkgY29uc2VydmF0aXZlIGhldXJpc3RpY3MgdG8gZmlndXJlIG91dCB0aGUgdHlwZSBvZiBlYWNoIGNvbHVtbi4gVGhlIGhldXJpc3RpYyB0cmllcyBlYWNoIG9mIHRoZSBmb2xsb3dpbmcgdHlwZXMsIHN0b3BwaW5nIHdoZW4gaXQgZmluZHMgYSBtYXRjaC4gTG9naWNhbCwgaW50ZWdlciwgZG91YmxlLCBudW1iZXIsIHRpbWUsIGRhdGUsIGRhdGUtdGltZS4gQ2F0Y2ggYWxsIGlzIHN0cmluZy4NCg0KYGBge3J9DQpjaGFsbGVuZ2UgPC0gIHJlYWRfY3N2KHJlYWRyX2V4YW1wbGUoImNoYWxsZW5nZS5jc3YiKSkNCnByb2JsZW1zKGNoYWxsZW5nZSkNCmBgYA0KDQpUaGUgZXJyb3IgbWVzc2FnZSBzaG93cyB0aGF0IHRoZSBpbnRlZ2VyIGNvbHVtbiBjb250YWlucyBkYXRhIHdpdGggZGVjaW1hbHMgaW4gaXQuIA0KU28gd2UgdXNlIHBhcnNlX2RvdWJsZS4gDQoNCg0KYGBge3J9DQpjaGFsbGVuZ2UgPC0gIHJlYWRfY3N2KHJlYWRyX2V4YW1wbGUoImNoYWxsZW5nZS5jc3YiKSwNCmNvbF90eXBlcyA9IGNvbHMoDQogIHggPSBjb2xfZG91YmxlKCksDQogIHkgPSBjb2xfY2hhcmFjdGVyKCkNCikNCikNCg0KIyBsb29rIGF0IHRoZSBsYXN0IGZldyByb3dzDQp0YWlsKGNoYWxsZW5nZSkNCmBgYA0KDQpBaGEgWSBpcyBhIGRhdGUgZmllbGQsIG5vdCBhIGNoYXJhY3RlciBmaWVsZC4gDQoNCmBgYHtyfQ0KY2hhbGxlbmdlIDwtICByZWFkX2NzdihyZWFkcl9leGFtcGxlKCJjaGFsbGVuZ2UuY3N2IiksDQpjb2xfdHlwZXMgPSBjb2xzKA0KICB4ID0gY29sX2RvdWJsZSgpLA0KICB5ID0gY29sX2RhdGUoKQ0KKQ0KKQ0KIyBsb29rIGF0IHRoZSBsYXN0IGZldyByb3dzDQp0YWlsKGNoYWxsZW5nZSkNCmBgYA0KDQoNCkl0IGlzIGJlc3QgcHJhY3RpY2UgdG8gdXNlIGNvbF90eXBlcyAgdG8gZW5zdXJlIGNvbnNpc3RlbnQgYW5kIHJlcHJvZHVjaWJsZSBkYXRhIGltcG9ydCBzY3JpcHQuIFRvIGJlIHJlYWxseSBzdHJpY3QsIHdlIGNhbiBhbHNvIHVzZSBzdG9wX2Zvcl9wcm9ibGVtcygpIHdoaWNoIHdpbGwgdGhyb3cgYW4gZXJyb3IgYW5kIHN0b3AgdGhlIHNjcmlwdCBpZiB0aGVyZSBhcmUgYW55IGVycm9ycy4NCg0KPGgyPiBPdGhlciBTdHJhdGVnaWVzIDwvaDI+DQoNClVzaW5nIGd1ZXNzX21heCBhcmd1bWVudA0KDQpgYGB7cn0NCmNoYWxsZW5nZTIgPC0gIHJlYWRfY3N2KA0KICByZWFkcl9leGFtcGxlKCJjaGFsbGVuZ2UuY3N2IiksDQogIGd1ZXNzX21heCA9IDEwMDENCiAgKQ0KDQpjaGFsbGVuZ2UyDQpgYGANCg0KDQpTb21ldGltZXMgaXQgaXMgZWFzaWVyIHRvIGRpYWdub3NlIHByb2JsZW1zIGlmIHlvdSBqdXN0IHJlYWQgaW4gYWxsIHRoZSBjb2x1bW5zIGFzIGNoYXJhY3RlciB2ZWN0b3JzLiANCg0KYGBge3J9DQpjaGFsbGVuZ2UyIDwtICByZWFkX2NzdigNCiAgcmVhZHJfZXhhbXBsZSgiY2hhbGxlbmdlLmNzdiIpLA0KICBjb2xfdHlwZXMgPSBjb2xzKC5kZWZhdWx0ID0gY29sX2NoYXJhY3RlcigpKQ0KICApDQpjaGFsbGVuZ2UyDQpgYGANCg0KDQpUaGlzIGlzIHVzZWZ1bCBpbiBjb25qdW5jdGlvbiB3aXRoIHRoZSB0eXBlX2NvbnZlcnQoKSB3aGljaCBhcHBsaWVzIHRoZSBwYXJzaW5nIGhldXJpc3RpY3MgdG8gdGhlIGNoYXJhY3RlciBjb2x1bW5zIGluIGEgZGF0YSBmcmFtZS4gDQoNCmBgYHtyfQ0KZGYgPC0gIHRyaWJibGUoDQogIH54LCB+IHksIA0KICAiMSIsICIxLjIxIiwNCiAgIjIiLCIyLjMyIiwNCiAgIjMiLCAiNC41NiINCikNCg0KZGYNCg0KYGBgDQoNCg0KYGBge3J9DQojIE5vdGUgdGhlIGNvbHVtbiB0eXBlcyBhZnRlciB0aGUgY29udmVydGlvbg0KdHlwZV9jb252ZXJ0KGRmKQ0KDQpgYGANCg0KDQpOb3RlOiBmb3IgdmVyeSBsYXJnZSBmaWxlcywgeW91IG1pZ2h0IHdhbnQgdG8gc2V0IG5fbWF4IHRvIGEgc21hbGxpc2ggbnVtYmVyIGxpa2UgMTAsMDAwIG9yIDEwMCwwMDAuIA0KDQo8SDI+IFdyaXRpbmcgdG8gYSBGaWxlIDwvaDI+DQoNClR3byBmdW5jdGlvbnMgd3JpdGVfY3N2IGFuZCB3cml0ZV90c3YoKS4NCkFsd2F5cyB1c2VzIFVURi04IGVuY29kaW5nDQpTYXZpbmcgZGF0ZXMgYW5kIGRhdGUtdGltZXMgaW4gSVNPODYwMSBmb3JtYXQgc28gdGhleSBhcmUgZXNpbHkgcGFyc2VkIGVsc2V3aGVyZS4gDQoNCklmIHlvdSB3YW50IHRvIGV4cG9ydCBhIENTViBmaWxlIHRvIEV4Y2VsLCB1c2Ugd3JpdGVfZXhjZWxfY3N2KCkgdGhpcyB3cml0ZXMgYSBzcGVjaWFsIGNoYXJhY3RlciAoYSAnYnl0ZSBvcmRlciBtYXJrIikgYXQgdGhlIHN0YXJ0IG9mIHRoZSBmaWxlLCB3aGljaCB0ZWxscyBFeGNlbCB0aGF0IHlvdSdyZSB1c2luZyB0aGUgVVRGLTggZW5jb2RpbmcuIDwvcD4NCg0KYGBge3J9DQojIHRvIGFwcGVuZCB0byBhbiBleGlzdGluZyBmaWxlLiANCiMgTm90ZTogdHlwZSBpbmZvcm1hdGlvbiBpcyBsb3N0IHdoZW4geW91IHNhdmUgdG8gY3N2IGZvcm1hdA0KDQp3cml0ZV9jc3YoY2hhbGxlbmdlLCAiQ2hhbGxlbmdlLmNzdiIpDQpgYGANCg0KQnV0IHNhdmluZyBpbiBDU1YgZm9ybWF0IGlzIGEgbGl0dGxlIHVucmVsaWFibGUgZm9yIGNhY2hpbmcgaW50ZXJpbSByZXN1bHRzLiBZb3UgbmVlZCB0byByZS1jcmVhdGUgdGhlIGNvdW1uIHNwZWNpZmljYXRpb24gZXZlcnkgdGltZSB5b3UgbG9hZCBpbi4gVGhlcmUgYXJlIHR3byBhbHRlcm5hdGl2ZXM6IHdyaXRlcmRzKCkgYW5kIHJlYWRfcmRzKCkgd2lsbCBzdG9yZSBpbiBSJ3MgY3VzdG9tIGJpbmFyeSBmb3JtYXQgY2FsbGVkIFJEUy4gDQoNCmBgYHtyfQ0Kd3JpdGVfcmRzKGNoYWxsZW5nZSwgImNoYWxsZW5nZS5yZHMiKQ0KcmVhZF9yZHMoImNoYWxsZW5nZS5yZHMiKQ0KYGBgDQoNCg0KWW91IGNhbiBhbHNvIHVzZSB0aGUgZmVhdGhlciBwYWNrYWdlIHRvIGltcGxlbWVudCBhIGZhc3QgYmluYXJ5IGZpbGUgZm9ybWF0IHRoYSB0Y2FuIGJlIHNoYXJlZCBhY3Jvc3MgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzLg0KDQpgYGB7cn0NCmxpYnJhcnkoZmVhdGhlcikNCndyaXRlX2ZlYXRoZXIoY2hhbGxlbmdlLCAiY2hhbGxlbmdlLmZlYXRoZXIiKQ0KcmVhZF9mZWF0aGVyKCJjaGFsbGVuZ2UuZmVhdGhlciIpDQpgYGANCg0KTm90ZXM6IEZlYXRoZXIgaXMgZmFzdGVyIHRoYW4gUkRTIGFuZCBjYW4gYmUgdXNlZCBvdXRzaWRlIG9mIFIgYnV0IFJEUyBzdXBwb3NydHMgbGlzdC1jb2x1bW5zIHdoaWNoIGZlYXRoZXIgZG9lcyBub3QuIA0KDQo8aDI+IE90aGVyIFR5cGVzIG9mIERhdGE8L2gyPg0KaGF2ZW4gcmVhZHMgU1BTUywgU3RhdGEgYW5kIFNBUyBmaWxlczwvYnI+DQpyZWFkeGwgcmVhZHMgRXhjZWwgZmlsZXMgKGJvdGggeGxzLiBhbmQgeGxzeCk8L2JyPg0KREJJIGFsbG93cyB5b3UgdG8gcnVuIFNRTCBxdWVyaWVzIGFnYWluc3QgYSBkYXRhYmFzZSBhbmQgcmV0dXJuIGEgZGF0YSBmcmFtZS4gPC9icj4NCmpzb25saXRlIGZvciBoaWVyYXJjaGljYWwgZGF0YSA8L2JyPg0KeG1sMiBmb3IgWE1MIGRhdGEgPC9icj4NClNlZSBhbHNvIEplbm55IEJyeWFuIGF0IGh0dHBzOi8vamVubnliYy5naXRodWIuaW8vcHVycnItdHV0b3JpYWwgPC9icj4NCg0K