How to get and run this files on your own computer:
You can run and tweak this code by cloning my repository. If you do
not know what a repository is, I recommend you to begin reading about it
if you want to work collaboratively with other people.
- Install git https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
- Go to the folder you want the folder with the files to be
located.
- Open that folder in the terminal and type
git clone https://git.disroot.org/luangonz/feeder-analytics-demo.git
Git will automatically create a folder and download all the files
required to run this code.
Setup
Since the idea of this project is to be as modular and automated as
possible, I created a set of custom functions that do the heavy lifting
in the background, without cluttering too much the script file. This has
some advantages and disadvantages, since although it is easier to read
and the workflow is easy to follow along, if issues appear, then
debugging and following the function that originated the issue is more
time-consuming.
The folder is organized in the main project files and two
folders:
- The
data
folder holds the data files we will be loading
into the environment.
- The
setup
folder (used in this section) holds two key
files:
functions.R
that holds all the custom functions
loadlibraries.R
that has all the libraries needed for
any script file that uses the same set of libraries (so you do not need
to copy and paste it in every file of the project)
When we run the source()
function at this step, we are
loading both the libraries and the functions that we are going to use
throughout the demo.
source("setup/functions.R")
source("setup/loadlibraries.R")
Importing data
First step in the process is to load the excel file in the
environment. There are packages that can natively import excel files
that are very straightforward, but some of them do not handle the Date
information properly. For this I have a custom function that takes into
account some common issues and with the method =
parameter,
I can fine-tune the importing method according to where the data comes
from.
farmA <- xlsx_to_dataframe(filename = "data/farm_A_demo.xlsx", # selects the file
method = "farm_A_11rows") # selects the farm A method
Lucky for us, farm B has its data directly formatted in the RData
format, which helps a lot in the importing process. A simple
load()
function and the data is there.
Checking structure of data
We will look at the raw imported data as it comes from the import
procedure.
str(farmA)
tibble [268,199 x 10] (S3: tbl_df/tbl/data.frame)
$ Date : POSIXct[1:268199], format: "2021-04-27" "2021-04-27" "2021-04-27" ...
$ Tatouage: chr [1:268199] NA NA NA NA ...
$ Station : num [1:268199] 1 1 1 1 1 1 1 1 1 1 ...
$ pdsdeb : num [1:268199] 1.408 1.392 0.778 1.248 1.247 ...
$ pdsfin : num [1:268199] 1.392 1.385 0.771 1.189 1.245 ...
$ cons : num [1:268199] 0.016 0.007 0.007 0.059 0.002 0.02 0.04 0.468 0.261 0.364 ...
$ remp : num [1:268199] 0 0 0 0 0 0 0 0 0 0.48 ...
$ hredeb : chr [1:268199] "4:43:19" "4:44:09" "8:06:28" "8:38:20" ...
$ hrefin : chr [1:268199] "4:43:43" "4:44:14" "8:06:40" "8:40:52" ...
$ duree1 : chr [1:268199] "0:00:24" "0:00:05" "0:00:12" "0:02:32" ...
We see some issues that are of concern, for example time of start of
visit is not in a proper date/time type, but it is only a character.
Lets check Farm B:
str(farmB)
'data.frame': 32827 obs. of 12 variables:
$ Date_fin : Date, format: "2020-05-12" "2020-05-12" "2020-05-12" ...
$ Animal : chr "4817" "4817" "4815" "4817" ...
$ RFID : num 7406487 7406487 13333982 7406487 13333982 ...
$ Parc : int 6 6 6 6 6 6 6 6 6 6 ...
$ Poids_aliment_debut: num 13.4 13.4 13.4 13.4 13.4 ...
$ Poids_aliment_fin : num 13.4 13.4 13.4 13.4 13.4 ...
$ Qte_aliment : num 0 0.01 -0.01 0 0 0 0 0 0 0.01 ...
$ Tdebut : chr "10:28:15" "10:30:06" "10:32:47" "10:34:17" ...
$ Tfin : chr "10:28:56" "10:32:47" "10:34:17" "10:34:42" ...
$ Duree_insentec : num 0.41 2.41 1.3 0.25 0.36 0.21 0.05 0.1 0.58 0.02 ...
$ Dummy : num 0 0 0 0 0 0 0 0 0 0 ...
$ Duree_s : num 41 161 90 25 36 21 5 10 58 2 ...
Similar issues with the time and also the titles of the variables
between these two are different, making it hard to work with them with
just a piece of code. So the strategy is to take this (or any) kind of
dataframe that we work with, and standardize it to a format that any of
the next functions can work with. Next step then, is
standardization.
Standardization
The harmonize_feeder_data()
function is a custom
function that allows us to funnel any kind of source file into a single,
homogenous data structure so it can be fed into the following functions
in the workflow. It has two parameters:
groupstations
: If TRUE, the station number becomes a
group in the dataframe (useful for summarizations).
method
: a selector for the method that it will use,
according to which source the data frame comes from
remove_filling
: if TRUE, it will remove the FILLING
events of the feeder (when the feeder is filled up).
remove_na
: if TRUE, it will remove unavailable data
that might interfere in some of the calculations.
We will check the structure again to see if everything is in
order:
str(farmA_standard)
tibble [261,636 x 9] (S3: tbl_df/tbl/data.frame)
$ Date : POSIXct[1:261636], format: "2021-04-27" "2021-04-27" "2021-04-27" ...
$ id : Factor w/ 308 levels "SOGE60281J","SOGE60286J",..: 4 4 4 4 7 7 7 7 7 7 ...
$ station : Factor w/ 22 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
$ pdsdeb : num [1:261636] 1.282 1.068 0.991 1.03 1.448 ...
$ pdsfin : num [1:261636] 0.814 0.807 0.627 0.524 1.282 ...
$ cons : num [1:261636] 0.468 0.261 0.364 0.506 0.166 0.265 0.209 0.092 0.122 0.295 ...
$ hour.in : POSIXct[1:261636], format: "2021-04-27 04:56:28" "2021-04-27 08:54:00" "2021-04-27 13:24:29" ...
$ hour.out : POSIXct[1:261636], format: "2021-04-27 05:15:48" "2021-04-27 09:05:44" "2021-04-27 13:46:15" ...
$ visit_dur_secs: 'difftime' num [1:261636] 1160 704 1306 1651 ...
..- attr(*, "units")= chr "secs"
str(farmB_standard)
'data.frame': 26853 obs. of 9 variables:
$ Date : Date, format: "2020-05-12" "2020-05-12" "2020-05-12" ...
$ id : Factor w/ 20 levels "4804","4805",..: 14 14 12 14 12 14 1 14 5 5 ...
$ station : Factor w/ 1 level "6": 1 1 1 1 1 1 1 1 1 1 ...
$ pdsdeb : num 13.4 13.4 13.4 13.4 13.4 ...
$ pdsfin : num 13.4 13.4 13.4 13.4 13.4 ...
$ cons : num 0 0.01 -0.01 0 0 0 0 0 0 0.01 ...
$ hour.in : POSIXct, format: "2020-05-12 10:28:15" "2020-05-12 10:30:06" "2020-05-12 10:32:47" ...
$ hour.out : POSIXct, format: "2020-05-12 10:28:56" "2020-05-12 10:32:47" "2020-05-12 10:34:17" ...
$ visit_dur_secs: 'difftime' num 41 161 90 25 ...
..- attr(*, "units")= chr "secs"
With this function we`ve managed to homogenize the data structure so
we can move on now to our next step.
Inspecting data integrity
Well be running some more custom functions to plot valuable data.
Population plot
Farm A has 22 different pens. It would be valuable to see if there
are any issues regarding the population of these pens, for example a
quick reduction or increase in size or a quick drop due to data loss
form the hardware
Population plot of farm A
populationPlot(farmA_standard)
Population plot of farm B
populationPlot(farmB_standard)
We can evidence with these plots that there are some pen size
fluctuations and some data loss in some of the periods. These losses
will need to be taken into account during the analyses.
Visualizing visits to the feeder
We can visualize a timeline of visits to the feeder for any station
or day with this custom function, visitPlotsDay()
:
visitPlotsDay(farmA_standard,
thedate = "2021-06-03",
thestation = 11,
singlestrip = FALSE)
With the last plot, we have one line per pig, but sometimes seeing
all the visit in a single line is useful. This is what the
singlestrip
parameter is useful for.
visitPlotsDay(farmA_standard,
thedate = "2021-06-03",
thestation = 11,
singlestrip = TRUE)
A birdseye view of all the data for a station
The inspectDay
function can show the visits to a feeder
for the whole period, in a single plot. It can also show a population
plot similar to the previous section.
inspectDay(farmA_standard, thestation = 11)
Building network visualizations and analyisis
Building the igraph objects and plotting
The following steps succesively converts the data into the network
objects of the igraph
package.
farmA_list <- makeAllStationsPerdate(farmA_standard, domerge = "F")
farmA_pairs <- makePairsPerStation(farmA_list, mythreshold = 5) # TAKES A LONG TIME
farmA_network <- makeIGraphObjects(farmA_pairs, directed = T)
plot(farmA_network[["12"]][["2021-05-20"]])
Making summarizations based on the network data
The following steps will analyze how a whole-network parameter, the
Network
Density progresses through time. It looks that there is a downward
trend in the group we are studying.
getmetheplot_pliz(site = "Farm A",
df = farmA_standard,
thestation = 12)
The reason why this trend occurs is not clear, but it could be that
these animals are learning to avoid each other. Another possible
explanation is that the animals are going less to the feeder as they
grow, and thus there is less of a chance that the animals can interact
with each other. The
getmetheplot_pliz_but_corrected_this_time()
function
corrects the network density by the times the animals visit the
feeder.
getmetheplot_pliz_but_corrected_this_time(site = "Farm A",
df = farmA_standard,
thestation = 5)
Even with this correction, we still see a downward trend.
Thanks for reading all of this, you can reach me at Microsoft Teams
or e-mail at lagog6@ulaval.ca.
LS0tDQp0aXRsZTogIkRlbW8gZm9yIHRoZSBwcmVzZW50YXRpb24gJ0dldHRpbmcgaW5zaWdodHMgZnJvbSBhdXRvbWF0aWMgZmVlZGVyIGRhdGEnIg0KYXV0aG9yOg0KLSBMdWlzIEdvbnphbGV6LUdyYWNpYSAobGFnb2c2QHVsYXZhbC5jYSkgDQotIFR3aXR0ZXIgLSBAZ29uemFsdWlzYW5kcmVzDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCnRhZ3M6IFtzb2NpYWwgbmV0d29yayBhbmFseXNpcywgYW5pbWFsIHNjaWVuY2VdDQphYnN0cmFjdDoNCiAgVGhpcyBzaG9ydCBSIE5vdGVib29rIHdpbGwgc2hvdyB0aGUgd29ya2Zsb3cgYXMgcHJlc2VudGVkIGluIG15IHByZXNlbnRhdGlvbiB0b2RheS4NCg0KICBXZSB3aWxsIGdyYWIgdG8gZGlmZmVyZW50IHNvdXJjZXMgb2YgZGF0YSwgYW5kIGRvIHNvbWUgd3JhbmdsaW5nLCB2aXN1YWxpemF0aW9uLA0KICBhbmQgYW5hbHlzaXMuIA0KDQogIFRoZSBzY3JpcHQgY2FuIGFsc28gYmUgYWNjZXNzZWQgaW4gdGhlIGBkZW1vLlIgYCBmaWxlLCBhbHNvIGluIHRoaXMgcmVwb3NpdG9yeS4gDQpvdXRwdXQ6DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQplZGl0b3Jfb3B0aW9uczoNCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCg0KDQojIEhvdyB0byBnZXQgYW5kIHJ1biB0aGlzIGZpbGVzIG9uIHlvdXIgb3duIGNvbXB1dGVyOg0KIVtTb21lIG9mIHlvdSBtaWdodCBiZSB0b28geW91bmcgdG8gZ2V0IHRoZSByZWZlcmVuY2VdKGltZy96b29sYW5kZXJmaWxlc2luY29tcHV0ZXIuanBnKQ0KDQpZb3UgY2FuIHJ1biBhbmQgdHdlYWsgdGhpcyBjb2RlIGJ5IGNsb25pbmcgbXkgcmVwb3NpdG9yeS4gSWYgeW91IGRvIG5vdCBrbm93IHdoYXQNCmEgcmVwb3NpdG9yeSBpcywgSSByZWNvbW1lbmQgeW91IHRvIGJlZ2luIHJlYWRpbmcgYWJvdXQgaXQgaWYgeW91IHdhbnQgdG8gd29yaw0KY29sbGFib3JhdGl2ZWx5IHdpdGggb3RoZXIgcGVvcGxlLiANCg0KIDEuIEluc3RhbGwgZ2l0IGh0dHBzOi8vZ2l0LXNjbS5jb20vYm9vay9lbi92Mi9HZXR0aW5nLVN0YXJ0ZWQtSW5zdGFsbGluZy1HaXQNCiAyLiBHbyB0byB0aGUgZm9sZGVyIHlvdSB3YW50IHRoZSBmb2xkZXIgd2l0aCB0aGUgZmlsZXMgdG8gYmUgbG9jYXRlZC4NCiAzLiBPcGVuIHRoYXQgZm9sZGVyIGluIHRoZSB0ZXJtaW5hbCBhbmQgdHlwZQ0KIA0KYGBge2Jhc2h9DQpnaXQgY2xvbmUgaHR0cHM6Ly9naXQuZGlzcm9vdC5vcmcvbHVhbmdvbnovZmVlZGVyLWFuYWx5dGljcy1kZW1vLmdpdA0KYGBgDQoNCkdpdCB3aWxsIGF1dG9tYXRpY2FsbHkgY3JlYXRlIGEgZm9sZGVyIGFuZCBkb3dubG9hZCBhbGwgdGhlIGZpbGVzIHJlcXVpcmVkIHRvIHJ1bg0KdGhpcyBjb2RlLiANCg0KIyBTZXR1cA0KDQpTaW5jZSB0aGUgaWRlYSBvZiB0aGlzIHByb2plY3QgaXMgdG8gYmUgYXMgbW9kdWxhciBhbmQgYXV0b21hdGVkIGFzIHBvc3NpYmxlLCANCkkgY3JlYXRlZCBhIHNldCBvZiBjdXN0b20gZnVuY3Rpb25zIHRoYXQgZG8gdGhlIGhlYXZ5IGxpZnRpbmcgaW4gdGhlIGJhY2tncm91bmQsIA0Kd2l0aG91dCBjbHV0dGVyaW5nIHRvbyBtdWNoIHRoZSBzY3JpcHQgZmlsZS4gVGhpcyBoYXMgc29tZSBhZHZhbnRhZ2VzIGFuZCBkaXNhZHZhbnRhZ2VzLA0Kc2luY2UgYWx0aG91Z2ggaXQgaXMgZWFzaWVyIHRvIHJlYWQgYW5kIHRoZSB3b3JrZmxvdyBpcyBlYXN5IHRvIGZvbGxvdyBhbG9uZywgaWYNCmlzc3VlcyBhcHBlYXIsIHRoZW4gZGVidWdnaW5nIGFuZCBmb2xsb3dpbmcgdGhlIGZ1bmN0aW9uIHRoYXQgb3JpZ2luYXRlZCB0aGUgaXNzdWUNCmlzIG1vcmUgdGltZS1jb25zdW1pbmcuIA0KDQpUaGUgZm9sZGVyIGlzIG9yZ2FuaXplZCBpbiB0aGUgbWFpbiBwcm9qZWN0IGZpbGVzIGFuZCB0d28gZm9sZGVyczoNCg0KIC0gVGhlIGBkYXRhYCBmb2xkZXIgaG9sZHMgdGhlIGRhdGEgZmlsZXMgd2Ugd2lsbCBiZSBsb2FkaW5nIGludG8gdGhlIGVudmlyb25tZW50LiANCiAtIFRoZSBgc2V0dXBgIGZvbGRlciAodXNlZCBpbiB0aGlzIHNlY3Rpb24pIGhvbGRzIHR3byBrZXkgZmlsZXM6DQogICAgLSBgZnVuY3Rpb25zLlJgIHRoYXQgaG9sZHMgYWxsIHRoZSBjdXN0b20gZnVuY3Rpb25zDQogICAgLSBgbG9hZGxpYnJhcmllcy5SYCB0aGF0IGhhcyBhbGwgdGhlIGxpYnJhcmllcyBuZWVkZWQgZm9yIGFueSBzY3JpcHQgZmlsZSB0aGF0IA0KICAgIHVzZXMgdGhlIHNhbWUgc2V0IG9mIGxpYnJhcmllcyAoc28geW91IGRvIG5vdCBuZWVkIHRvIGNvcHkgYW5kIHBhc3RlIGl0IGluIGV2ZXJ5DQogICAgZmlsZSBvZiB0aGUgcHJvamVjdCkNCiAgICANCldoZW4gd2UgcnVuIHRoZSBgc291cmNlKClgIGZ1bmN0aW9uIGF0IHRoaXMgc3RlcCwgd2UgYXJlIGxvYWRpbmcgYm90aCB0aGUgbGlicmFyaWVzDQphbmQgdGhlIGZ1bmN0aW9ucyB0aGF0IHdlIGFyZSBnb2luZyB0byB1c2UgdGhyb3VnaG91dCB0aGUgZGVtby4gDQoNCmBgYHtyIHNldHVwLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzb3VyY2UoInNldHVwL2Z1bmN0aW9ucy5SIikNCnNvdXJjZSgic2V0dXAvbG9hZGxpYnJhcmllcy5SIikNCmBgYA0KDQoNCiMgSW1wb3J0aW5nIGRhdGEgDQoNCkZpcnN0IHN0ZXAgaW4gdGhlIHByb2Nlc3MgaXMgdG8gbG9hZCB0aGUgZXhjZWwgZmlsZSBpbiB0aGUgZW52aXJvbm1lbnQuIA0KVGhlcmUgYXJlIHBhY2thZ2VzIHRoYXQgY2FuIG5hdGl2ZWx5IGltcG9ydCBleGNlbCBmaWxlcyB0aGF0IGFyZSB2ZXJ5IHN0cmFpZ2h0Zm9yd2FyZCwgDQpidXQgc29tZSBvZiB0aGVtIGRvIG5vdCBoYW5kbGUgdGhlIERhdGUgaW5mb3JtYXRpb24gcHJvcGVybHkuIEZvciB0aGlzIEkgaGF2ZSBhIGN1c3RvbSBmdW5jdGlvbiB0aGF0IHRha2VzIGludG8gYWNjb3VudCBzb21lIGNvbW1vbiBpc3N1ZXMgYW5kIHdpdGggdGhlIGBtZXRob2QgPWAgcGFyYW1ldGVyLCBJIA0KY2FuIGZpbmUtdHVuZSB0aGUgaW1wb3J0aW5nIG1ldGhvZCBhY2NvcmRpbmcgdG8gd2hlcmUgdGhlIGRhdGEgY29tZXMgZnJvbS4gDQpgYGB7cn0NCmZhcm1BIDwtIHhsc3hfdG9fZGF0YWZyYW1lKGZpbGVuYW1lID0gImRhdGEvZmFybV9BX2RlbW8ueGxzeCIsICMgc2VsZWN0cyB0aGUgZmlsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImZhcm1fQV8xMXJvd3MiKSAjIHNlbGVjdHMgdGhlIGZhcm0gQSBtZXRob2QNCmBgYA0KDQpMdWNreSBmb3IgdXMsIGZhcm0gQiBoYXMgaXRzIGRhdGEgZGlyZWN0bHkgZm9ybWF0dGVkIGluIHRoZSBSRGF0YSBmb3JtYXQsIHdoaWNoDQpoZWxwcyBhIGxvdCBpbiB0aGUgaW1wb3J0aW5nIHByb2Nlc3MuIEEgc2ltcGxlIGBsb2FkKClgIGZ1bmN0aW9uIGFuZCB0aGUgZGF0YSBpcyB0aGVyZS4gDQoNCg0KDQpgYGB7cn0NCmxvYWQoImRhdGEvZmFybUIuUkRhdGEiKSAjIGxvYWQgdGhlIGZpbGUgaW50byB0aGUgZW52aXJvbm1lbnQNCg0KZmFybUIgPC0gZGF0YV9hbGltICMgcmVuYW1pbmcgdG8gbWFrZSBpdCBhIGJldHRlciB1bmRlcnN0YW5kYWJsZSBmaWxlbmFtZQ0Kcm0oZGF0YV9hbGltKSAjIHJlbW92aW5nIHRoZSBvcmlnaW5hbCBpbXBvcnRlZCBkYXRhZnJhbWUNCmBgYA0KDQojIENoZWNraW5nIHN0cnVjdHVyZSBvZiBkYXRhDQoNCldlIHdpbGwgbG9vayBhdCB0aGUgcmF3IGltcG9ydGVkIGRhdGEgYXMgaXQgY29tZXMgZnJvbSB0aGUgaW1wb3J0IHByb2NlZHVyZS4NCg0KYGBge3J9DQpzdHIoZmFybUEpDQpgYGANCg0KV2Ugc2VlIHNvbWUgaXNzdWVzIHRoYXQgYXJlIG9mIGNvbmNlcm4sIGZvciBleGFtcGxlIHRpbWUgb2Ygc3RhcnQgb2YgdmlzaXQgaXMgbm90IA0KaW4gYSBwcm9wZXIgZGF0ZS90aW1lIHR5cGUsIGJ1dCBpdCBpcyBvbmx5IGEgY2hhcmFjdGVyLiBMZXRzIGNoZWNrIEZhcm0gQjoNCmBgYHtyfQ0Kc3RyKGZhcm1CKQ0KYGBgDQoNClNpbWlsYXIgaXNzdWVzIHdpdGggdGhlIHRpbWUgYW5kIGFsc28gdGhlIHRpdGxlcyBvZiB0aGUgdmFyaWFibGVzIGJldHdlZW4gdGhlc2UgdHdvDQphcmUgZGlmZmVyZW50LCBtYWtpbmcgaXQgaGFyZCB0byB3b3JrIHdpdGggdGhlbSB3aXRoIGp1c3QgYSBwaWVjZSBvZiBjb2RlLiBTbyB0aGUgDQpzdHJhdGVneSBpcyB0byB0YWtlIHRoaXMgKG9yIGFueSkga2luZCBvZiBkYXRhZnJhbWUgdGhhdCB3ZSB3b3JrIHdpdGgsIGFuZCBzdGFuZGFyZGl6ZQ0KaXQgdG8gYSBmb3JtYXQgdGhhdCBhbnkgb2YgdGhlIG5leHQgZnVuY3Rpb25zIGNhbiB3b3JrIHdpdGguIE5leHQgc3RlcCB0aGVuLCBpcyANCnN0YW5kYXJkaXphdGlvbi4gDQoNCiMgU3RhbmRhcmRpemF0aW9uDQoNClRoZSBgaGFybW9uaXplX2ZlZWRlcl9kYXRhKClgIGZ1bmN0aW9uIGlzIGEgY3VzdG9tIGZ1bmN0aW9uIHRoYXQgYWxsb3dzIHVzIHRvIA0KZnVubmVsIGFueSBraW5kIG9mIHNvdXJjZSBmaWxlIGludG8gYSBzaW5nbGUsIGhvbW9nZW5vdXMgZGF0YSBzdHJ1Y3R1cmUgc28gaXQgDQpjYW4gYmUgZmVkIGludG8gdGhlIGZvbGxvd2luZyBmdW5jdGlvbnMgaW4gdGhlIHdvcmtmbG93LiBJdCBoYXMgdHdvIHBhcmFtZXRlcnM6DQoNCiAtIGBncm91cHN0YXRpb25zYDogSWYgVFJVRSwgdGhlIHN0YXRpb24gbnVtYmVyIGJlY29tZXMgYSBncm91cCBpbiB0aGUgZGF0YWZyYW1lDQogKHVzZWZ1bCBmb3Igc3VtbWFyaXphdGlvbnMpLiANCiAtIGBtZXRob2RgOiBhIHNlbGVjdG9yIGZvciB0aGUgbWV0aG9kIHRoYXQgaXQgd2lsbCB1c2UsIGFjY29yZGluZyB0byB3aGljaCBzb3VyY2UNCiB0aGUgZGF0YSBmcmFtZSBjb21lcyBmcm9tDQogLSBgcmVtb3ZlX2ZpbGxpbmdgOiBpZiBUUlVFLCBpdCB3aWxsIHJlbW92ZSB0aGUgRklMTElORyBldmVudHMgb2YgdGhlIGZlZWRlciAod2hlbg0KIHRoZSBmZWVkZXIgaXMgZmlsbGVkIHVwKS4gDQogLSBgcmVtb3ZlX25hYDogaWYgVFJVRSwgaXQgd2lsbCByZW1vdmUgdW5hdmFpbGFibGUgZGF0YSB0aGF0IG1pZ2h0IGludGVyZmVyZSBpbg0KIHNvbWUgb2YgdGhlIGNhbGN1bGF0aW9ucy4gDQoNCmBgYHtyfQ0KZmFybUJfc3RhbmRhcmQgPC0gaGFybW9uaXplX2ZlZWRlcl9kYXRhKGZhcm1CLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBzdGF0aW9ucyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZGVzY2hhbWJhdWx0IikNCg0KZmFybUFfc3RhbmRhcmQgPC0gaGFybW9uaXplX2ZlZWRlcl9kYXRhKGZhcm1BLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cHN0YXRpb25zID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImZhcm1fQV9yYXciLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX2ZpbGxpbmcgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZW1vdmVfbmEgPSBUUlVFKQ0KYGBgDQoNCldlIHdpbGwgY2hlY2sgdGhlIHN0cnVjdHVyZSBhZ2FpbiB0byBzZWUgaWYgZXZlcnl0aGluZyBpcyBpbiBvcmRlcjoNCg0KYGBge3IgZWNobz1UUlVFfQ0Kc3RyKGZhcm1BX3N0YW5kYXJkKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRX0NCnN0cihmYXJtQl9zdGFuZGFyZCkNCmBgYA0KDQpXaXRoIHRoaXMgZnVuY3Rpb24gd2VgdmUgbWFuYWdlZCB0byBob21vZ2VuaXplIHRoZSBkYXRhIHN0cnVjdHVyZSBzbyB3ZSBjYW4gbW92ZSANCm9uIG5vdyB0byBvdXIgbmV4dCBzdGVwLg0KDQojIEluc3BlY3RpbmcgZGF0YSBpbnRlZ3JpdHkNCg0KV2VsbCBiZSBydW5uaW5nIHNvbWUgbW9yZSBjdXN0b20gZnVuY3Rpb25zIHRvIHBsb3QgdmFsdWFibGUgZGF0YS4gDQoNCiMjIFBvcHVsYXRpb24gcGxvdA0KDQpGYXJtIEEgaGFzIDIyIGRpZmZlcmVudCBwZW5zLiBJdCB3b3VsZCBiZSB2YWx1YWJsZSB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBpc3N1ZXMgDQpyZWdhcmRpbmcgdGhlIHBvcHVsYXRpb24gb2YgdGhlc2UgcGVucywgZm9yIGV4YW1wbGUgYSBxdWljayByZWR1Y3Rpb24gb3IgaW5jcmVhc2UgDQppbiBzaXplIG9yIGEgcXVpY2sgZHJvcCBkdWUgdG8gZGF0YSBsb3NzIGZvcm0gdGhlIGhhcmR3YXJlDQoNCiMjIyBQb3B1bGF0aW9uIHBsb3Qgb2YgZmFybSBBDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcG9wdWxhdGlvblBsb3QoZmFybUFfc3RhbmRhcmQpDQoNCmBgYA0KDQojIyMgUG9wdWxhdGlvbiBwbG90IG9mIGZhcm0gQg0KYGBge3J9DQpwb3B1bGF0aW9uUGxvdChmYXJtQl9zdGFuZGFyZCkNCmBgYA0KDQpXZSBjYW4gZXZpZGVuY2Ugd2l0aCB0aGVzZSBwbG90cyB0aGF0IHRoZXJlIGFyZSBzb21lIHBlbiBzaXplIGZsdWN0dWF0aW9ucyBhbmQgDQpzb21lIGRhdGEgbG9zcyBpbiBzb21lIG9mIHRoZSBwZXJpb2RzLiBUaGVzZSBsb3NzZXMgd2lsbCBuZWVkIHRvIGJlIHRha2VuIGludG8gDQphY2NvdW50IGR1cmluZyB0aGUgYW5hbHlzZXMuIA0KDQojIyBWaXN1YWxpemluZyB2aXNpdHMgdG8gdGhlIGZlZWRlcg0KDQpXZSBjYW4gdmlzdWFsaXplIGEgdGltZWxpbmUgb2YgdmlzaXRzIHRvIHRoZSBmZWVkZXIgZm9yIGFueSBzdGF0aW9uIG9yIGRheSANCndpdGggdGhpcyBjdXN0b20gZnVuY3Rpb24sIGB2aXNpdFBsb3RzRGF5KClgOg0KYGBge3J9DQp2aXNpdFBsb3RzRGF5KGZhcm1BX3N0YW5kYXJkLCANCiAgICAgICAgICAgICAgdGhlZGF0ZSA9ICIyMDIxLTA2LTAzIiwgDQogICAgICAgICAgICAgIHRoZXN0YXRpb24gPSAxMSwNCiAgICAgICAgICAgICAgc2luZ2xlc3RyaXAgPSBGQUxTRSkNCmBgYA0KDQpXaXRoIHRoZSBsYXN0IHBsb3QsIHdlIGhhdmUgb25lIGxpbmUgcGVyIHBpZywgYnV0IHNvbWV0aW1lcyBzZWVpbmcgYWxsIHRoZSB2aXNpdA0KaW4gYSBzaW5nbGUgbGluZSBpcyB1c2VmdWwuIFRoaXMgaXMgd2hhdCB0aGUgYHNpbmdsZXN0cmlwYCBwYXJhbWV0ZXIgaXMgdXNlZnVsIGZvci4gDQoNCmBgYHtyfQ0KdmlzaXRQbG90c0RheShmYXJtQV9zdGFuZGFyZCwgDQogICAgICAgICAgICAgIHRoZWRhdGUgPSAiMjAyMS0wNi0wMyIsIA0KICAgICAgICAgICAgICB0aGVzdGF0aW9uID0gMTEsDQogICAgICAgICAgICAgIHNpbmdsZXN0cmlwID0gVFJVRSkNCmBgYA0KDQojIyBBIGJpcmRzZXllIHZpZXcgb2YgYWxsIHRoZSBkYXRhIGZvciBhIHN0YXRpb24NCg0KVGhlIGBpbnNwZWN0RGF5YCBmdW5jdGlvbiBjYW4gc2hvdyB0aGUgdmlzaXRzIHRvIGEgZmVlZGVyIGZvciB0aGUgd2hvbGUgcGVyaW9kLCANCmluIGEgc2luZ2xlIHBsb3QuIEl0IGNhbiBhbHNvIHNob3cgYSBwb3B1bGF0aW9uIHBsb3Qgc2ltaWxhciB0byB0aGUgcHJldmlvdXMgc2VjdGlvbi4gDQoNCmBgYHtyfQ0KaW5zcGVjdERheShmYXJtQV9zdGFuZGFyZCwgdGhlc3RhdGlvbiA9IDExKQ0KYGBgDQoNCg0KIyBCdWlsZGluZyBuZXR3b3JrIHZpc3VhbGl6YXRpb25zIGFuZCBhbmFseWlzaXMNCg0KIyMgQnVpbGRpbmcgdGhlIGlncmFwaCBvYmplY3RzIGFuZCBwbG90dGluZw0KDQpUaGUgZm9sbG93aW5nIHN0ZXBzIHN1Y2Nlc2l2ZWx5IGNvbnZlcnRzIHRoZSBkYXRhIGludG8gdGhlIG5ldHdvcmsgb2JqZWN0cyBvZiB0aGUgDQpgaWdyYXBoYCBwYWNrYWdlLiANCg0KYGBge3J9DQpmYXJtQV9saXN0IDwtIG1ha2VBbGxTdGF0aW9uc1BlcmRhdGUoZmFybUFfc3RhbmRhcmQsIGRvbWVyZ2UgPSAiRiIpDQoNCmZhcm1BX3BhaXJzIDwtIG1ha2VQYWlyc1BlclN0YXRpb24oZmFybUFfbGlzdCwgbXl0aHJlc2hvbGQgPSA1KSAjIFRBS0VTIEEgTE9ORyBUSU1FDQoNCmZhcm1BX25ldHdvcmsgPC0gbWFrZUlHcmFwaE9iamVjdHMoZmFybUFfcGFpcnMsIGRpcmVjdGVkID0gVCkNCg0KcGxvdChmYXJtQV9uZXR3b3JrW1siMTIiXV1bWyIyMDIxLTA1LTIwIl1dKQ0KYGBgDQoNCiMjIE1ha2luZyBzdW1tYXJpemF0aW9ucyBiYXNlZCBvbiB0aGUgbmV0d29yayBkYXRhDQoNClRoZSBmb2xsb3dpbmcgc3RlcHMgd2lsbCBhbmFseXplIGhvdyBhIHdob2xlLW5ldHdvcmsgcGFyYW1ldGVyLCB0aGUgW05ldHdvcmsgRGVuc2l0eV0oaHR0cHM6Ly9tZXRob2RzLnNhZ2VwdWIuY29tL3JlZmVyZW5jZS90aGUtc2FnZS1lbmN5Y2xvcGVkaWEtb2YtZWR1Y2F0aW9uYWwtcmVzZWFyY2gtbWVhc3VyZW1lbnQtYW5kLWV2YWx1YXRpb24vaTE0NTUwLnhtbCkgcHJvZ3Jlc3NlcyB0aHJvdWdoIHRpbWUuIEl0IGxvb2tzIHRoYXQgdGhlcmUgaXMgYSBkb3dud2FyZA0KdHJlbmQgaW4gdGhlIGdyb3VwIHdlIGFyZSBzdHVkeWluZy4gDQoNCmBgYHtyfQ0KZ2V0bWV0aGVwbG90X3BsaXooc2l0ZSA9ICJGYXJtIEEiLA0KICAgICAgICAgICAgICAgICAgZGYgPSBmYXJtQV9zdGFuZGFyZCwNCiAgICAgICAgICAgICAgICAgIHRoZXN0YXRpb24gPSAxMikNCmBgYA0KDQpUaGUgcmVhc29uIHdoeSB0aGlzIHRyZW5kIG9jY3VycyBpcyBub3QgY2xlYXIsIGJ1dCBpdCBjb3VsZCBiZSANCnRoYXQgdGhlc2UgYW5pbWFscyBhcmUgbGVhcm5pbmcgdG8gYXZvaWQgZWFjaCBvdGhlci4gQW5vdGhlciBwb3NzaWJsZSBleHBsYW5hdGlvbg0KaXMgdGhhdCB0aGUgYW5pbWFscyBhcmUgZ29pbmcgbGVzcyB0byB0aGUgZmVlZGVyIGFzIHRoZXkgZ3JvdywgYW5kIHRodXMgdGhlcmUNCmlzIGxlc3Mgb2YgYSBjaGFuY2UgdGhhdCB0aGUgYW5pbWFscyBjYW4gaW50ZXJhY3Qgd2l0aCBlYWNoIG90aGVyLiBUaGUgDQpgZ2V0bWV0aGVwbG90X3BsaXpfYnV0X2NvcnJlY3RlZF90aGlzX3RpbWUoKWAgZnVuY3Rpb24gY29ycmVjdHMgdGhlIG5ldHdvcmsNCmRlbnNpdHkgYnkgdGhlIHRpbWVzIHRoZSBhbmltYWxzIHZpc2l0IHRoZSBmZWVkZXIuDQoNCmBgYHtyfQ0KZ2V0bWV0aGVwbG90X3BsaXpfYnV0X2NvcnJlY3RlZF90aGlzX3RpbWUoc2l0ZSA9ICJGYXJtIEEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGYgPSBmYXJtQV9zdGFuZGFyZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXN0YXRpb24gPSA1KQ0KYGBgDQpFdmVuIHdpdGggdGhpcyBjb3JyZWN0aW9uLCB3ZSBzdGlsbCBzZWUgYSBkb3dud2FyZCB0cmVuZC4gDQoNClRoYW5rcyBmb3IgcmVhZGluZyBhbGwgb2YgdGhpcywgeW91IGNhbiByZWFjaCBtZSBhdCBNaWNyb3NvZnQgVGVhbXMgb3IgZS1tYWlsIGF0DQoqbGFnb2c2QHVsYXZhbC5jYSouIA0K