Resources

Pre-requirements

The easiest way to install the core bupaR packages is by installing the bupaverse-package.

install.packages("bupaverse")
install.packages("processanimateR")
install.packages("psmineR")

You can then load the packages using library().

library(bupaverse)
print(R.version.string)

In case R needs to be updated to version 4.4.2, update the packages after installation.

update.packages("bupaR")
update.packages("eventdataR")
update.packages("xesreadR")
update.packages("edeaR")
update.packages("processmapR")
update.packages("processmonitR")
library(bupaverse)
library(dplyr)
library(processanimateR)
library(bpmnR)
library(processcheckR)

Loading an event log

log_xes <- read_xes("C:/Users/Admin/Downloads/Sepsis Cases - Event Log.xes")
log_xes
# Log of 15214 events consisting of:
846 traces 
1050 cases 
15214 instances of 16 activities 
1 resource 
Events occurred from 2013-11-07 07:18:29 until 2015-06-05 10:25:11 
 
# Variables were mapped as follows:
Case identifier:        CASE_concept_name 
Activity identifier:        activity_id 
Resource identifier:        resource_id 
Activity instance identifier:   activity_instance_id 
Timestamp:          timestamp 
Lifecycle transition:       lifecycle_id 

The mapping function can be used to retrieve all the meta data from a log object, i.e. the relation between log identifiers and the corresponding data fields.

log_xes %>% activities()
#log_xes %>% cases()
#log_xes %>% resources()
#log_xes %>% traces()

Discovery

Log statistics

An analysis of the control flow can be done by using metrics on activities and traces.

activity_presence <- log_xes %>% activity_presence()
activity_presence %>% plot()

The trace coverage metric shows the relationship between the number of different activity sequences (i.e. traces) and the number of cases they cover.

trace_coverage <- log_xes %>% trace_coverage("trace")
trace_coverage %>% plot()

The trace length metric describes the length of traces, i.e. the number of activity instances for each case. It can be computed at the levels case, trace and log.

trace_length <- log_xes %>% trace_length("log")
trace_length %>% plot()

Process models

Frequency maps

A process map of a log can be created using process_map(). A process map is a directly-follows graph, where each distinct activity is represented by a node, and each directly-follows relationship between activities is shown by directed edges, i.e. arrows between the nodes.

frequency <- log_xes %>% filter_trace_frequency(percentage = 0.8)
frequency %>% process_map(frequency("absolute"))

Performance maps

Instead of a frequencies, process maps can also be used to visualize performance of the process, by using performance() to configure the map, instead of frequency().

performance <- log_xes %>% process_map(performance())
performance

Information about frequencies and performance, or any other value, can be combined in the same graph.

frequency_performance <- log_xes %>% process_map(type_nodes = frequency("relative_case"), type_edges = performance(mean))
frequency_performance

Animation

It is possible to determine the aesthetics of tokens regardless of the timestamps at which activities occurred. This could be useful if some measurements were taken throughout a process, but the measurement event itself should not be included in the process map.

For example, the lacticacid measurements could be used in that way:

# Extract only the lacticacid measurements
lactic <- log_xes %>%
    mutate(LacticAcid = as.numeric(LacticAcid)) %>%
    filter_activity(c("LacticAcid")) %>%
    as.data.frame() %>%
    select("case" = CASE_concept_name, 
            "time" =  timestamp, 
            value = LacticAcid) # format needs to be 'case,time,value'

# Remove the measurement events from the sepsis log
sepsisBase <- log_xes %>%
    filter_activity(c("LacticAcid", "CRP", "Leucocytes", "Return ER",
                      "IV Liquid", "IV Antibiotics"), reverse = T) %>%
    filter_trace_frequency(percentage = 0.95)

# Animate with the secondary data frame `lactic`
animate_process(sepsisBase, 
                mode = "relative", 
                duration = 300,
                legend = "color", 
                mapping = token_aes(color = token_scale(lactic, 
                                                        scale = "linear", 
                                                        range = c("#fff5eb","#7f2704")))) 

Process visualizations

Process matrix

A process matrix is a two-dimensional matrix showing the flows between activities. Its configuration is exactly the same as that used by process_map().

matrix_frequency <- log_xes %>% process_matrix(frequency("absolute")) 
matrix_frequency %>% plot()

Dotted chart

Dotted charts can be made with dotted_chart(). A dotted chart is a graph in which each activity instance is displayed by a dot. The x-axis refers to the time aspect, while the y-axis refers to cases.

dotted_chart <- log_xes %>% dotted_chart(x = "absolute")
dotted_chart

Trace explorer

Different activity sequences in the log can be visualized with trace_explorer(). It can be used to explore frequent as well as infrequent traces.

trace_explorer <- log_xes %>% trace_explorer()
Warning: No `coverage` or `n_traces` set.
! Defaulting to `coverage` = 0.2 for `type` = "frequent" traces.
trace_explorer

Performance spectrum

Both detailed and aggregated performance spectrum can be created using ps_detailed() and ps_aggregated(), respectively.

library(psmineR)

spectrum_detailed <- log_xes %>% ps_detailed()
spectrum_detailed

Filtering

Infrequent flows

Filtering infrequent flows allows us to select a set of cases in which every directly-follows flow has a minimum frequency. For example, consider the process map below.

log_xes %>% process_map()

In this map, we can observe several unique directly follows relations, as well as flows occurring less than 30 times. Using the filter, we can remove the cases that lead to these flows as follows:

log_xes <- log_xes %>%
  mutate(activity_instance_id = as.character(activity_instance_id))

infrequent_flows <- log_xes %>% filter_infrequent_flows(min_n = 30) %>% process_map()
infrequent_flows

Time period

Filtering cases by time period can be done using the filter_time_period(). There are four different filter_method’s that act as case filters:

  • “start”: all cases started in an interval.
  • “complete”: all cases completed in an interval.
  • “contained”: all cases contained in an interval.
  • “intersecting”: all cases with some activity in an interval.

Using the interval January 2015, you can compare the results of different filtering methods below using dotted charts.

time_period <- log_xes %>% filter_time_period(interval = ymd(c(20150101, 20150131)), filter_method = "start") %>% dotted_chart() 
time_period

Case condition

filter_case_condition() can be used to select cases for which a condition holds. This condition can be related to any of the variables in the log.

For example, select all cases where age higher than 85 is involved.

age_85 <- log_xes %>% filter(!is.na(Age)) %>% filter_case_condition(Age >= 85)
age_85
# Log of 304 events consisting of:
6 traces 
304 cases 
304 instances of 6 activities 
1 resource 
Events occurred from 2013-11-07 07:18:29 until 2015-02-19 17:15:45 
 
# Variables were mapped as follows:
Case identifier:        CASE_concept_name 
Activity identifier:        activity_id 
Resource identifier:        resource_id 
Activity instance identifier:   activity_instance_id 
Timestamp:          timestamp 
Lifecycle transition:       lifecycle_id 
process_map(age_85)

Precedence

The filter_precedence() allows us to filter cases based on flows between activities.

If there is more than one antecedent or consequent activity, the filter will test all possible pairs. The filter_method will tell the filter whether all of the rules should hold, at least one, or none are allowed.

The following filter takes only cases where Triage and Assessment is directly followed by Blood test.

precedence <- log_xes %>%
    filter_precedence(antecedents = "ER Triage",
                      consequents = "Leucocytes",
                      precedence_type = "directly_follows") %>%
    traces()
head(precedence)

Conformance checking

Rule-based conformance

Using the packages processcheckr prodecural rules can be checked in an event log. Checking rules will add a boolean case attribute, which can be used for filtering or in analysis.

Rules can be checked using the check_rule function (see example below). It will create a new logical variable to indicate for which cases the rule holds. The name of the variable can be configured using the label argument in check_rule.

In the following example, the first rule checks the starting activity, while the second rule checks whether CRP and LacticAcid occur together.

log_xes %>%
  # check if cases starts with "ER Registration"
  check_rule(starts("ER Registration"), label = "r1") %>%
  # check if activities "CRP" and "LacticAcid" occur together
  check_rule(and("CRP","LacticAcid"), label = "r2") %>%
  group_by(r1, r2) %>%
  n_cases()

Alignments

Alignments is under development and can be used with the bupaRminer library. More information can be found in the GitHub: https://github.com/bupaverse/bupaRminer.

LS0tDQp0aXRsZTogIkJ1cGFSIHR1dG9yaWFsIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMgUmVzb3VyY2VzDQoNCiogRG9jdW1lbnRhdGlvbjogaHR0cHM6Ly9idXBhci5uZXQvDQoqIENoZWV0IHNoZWV0Omh0dHBzOi8vd3d3LmJ1cGFyLm5ldC9tYXRlcmlhbHMvMjAxNzA5MDQlMjBwb3N0ZXIlMjBidXBhUi5wZGYNCiogR2l0SHViOiBodHRwczovL2dpdGh1Yi5jb20vYnVwYXZlcnNlLw0KDQojIyBQcmUtcmVxdWlyZW1lbnRzDQoNClRoZSBlYXNpZXN0IHdheSB0byBpbnN0YWxsIHRoZSBjb3JlIGJ1cGFSIHBhY2thZ2VzIGlzIGJ5IGluc3RhbGxpbmcgdGhlIGJ1cGF2ZXJzZS1wYWNrYWdlLg0KDQpgYGB7cn0NCmluc3RhbGwucGFja2FnZXMoImJ1cGF2ZXJzZSIpDQpgYGANCg0KYGBge3J9DQppbnN0YWxsLnBhY2thZ2VzKCJwcm9jZXNzYW5pbWF0ZVIiKQ0KaW5zdGFsbC5wYWNrYWdlcygicHNtaW5lUiIpDQpgYGANCg0KWW91IGNhbiB0aGVuIGxvYWQgdGhlIHBhY2thZ2VzIHVzaW5nIGxpYnJhcnkoKS4NCg0KYGBge3J9DQpsaWJyYXJ5KGJ1cGF2ZXJzZSkNCnByaW50KFIudmVyc2lvbi5zdHJpbmcpDQpgYGANCg0KSW4gY2FzZSBSIG5lZWRzIHRvIGJlIHVwZGF0ZWQgdG8gdmVyc2lvbiA0LjQuMiwgdXBkYXRlIHRoZSBwYWNrYWdlcyBhZnRlciBpbnN0YWxsYXRpb24uDQoNCmBgYHtyfQ0KdXBkYXRlLnBhY2thZ2VzKCJidXBhUiIpDQp1cGRhdGUucGFja2FnZXMoImV2ZW50ZGF0YVIiKQ0KdXBkYXRlLnBhY2thZ2VzKCJ4ZXNyZWFkUiIpDQp1cGRhdGUucGFja2FnZXMoImVkZWFSIikNCnVwZGF0ZS5wYWNrYWdlcygicHJvY2Vzc21hcFIiKQ0KdXBkYXRlLnBhY2thZ2VzKCJwcm9jZXNzbW9uaXRSIikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoYnVwYXZlcnNlKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocHJvY2Vzc2FuaW1hdGVSKQ0KbGlicmFyeShicG1uUikNCmxpYnJhcnkocHJvY2Vzc2NoZWNrUikNCmBgYA0KDQojIyBMb2FkaW5nIGFuIGV2ZW50IGxvZw0KDQpgYGB7cn0NCmxvZ194ZXMgPC0gcmVhZF94ZXMoIkM6L1VzZXJzL0FkbWluL0Rvd25sb2Fkcy9TZXBzaXMgQ2FzZXMgLSBFdmVudCBMb2cueGVzIikNCmBgYA0KDQpgYGB7cn0NCmxvZ194ZXMNCmBgYA0KVGhlIG1hcHBpbmcgZnVuY3Rpb24gY2FuIGJlIHVzZWQgdG8gcmV0cmlldmUgYWxsIHRoZSBtZXRhIGRhdGEgZnJvbSBhIGxvZyBvYmplY3QsIGkuZS4gdGhlIHJlbGF0aW9uIGJldHdlZW4gbG9nIGlkZW50aWZpZXJzIGFuZCB0aGUgY29ycmVzcG9uZGluZyBkYXRhIGZpZWxkcy4NCg0KYGBge3J9DQpsb2dfeGVzICU+JSBhY3Rpdml0aWVzKCkNCiNsb2dfeGVzICU+JSBjYXNlcygpDQojbG9nX3hlcyAlPiUgcmVzb3VyY2VzKCkNCiNsb2dfeGVzICU+JSB0cmFjZXMoKQ0KYGBgDQoNCiMjIERpc2NvdmVyeQ0KDQojIyMgTG9nIHN0YXRpc3RpY3MNCg0KQW4gYW5hbHlzaXMgb2YgdGhlIGNvbnRyb2wgZmxvdyBjYW4gYmUgZG9uZSBieSB1c2luZyBtZXRyaWNzIG9uIGFjdGl2aXRpZXMgYW5kIHRyYWNlcy4NCg0KYGBge3J9DQphY3Rpdml0eV9wcmVzZW5jZSA8LSBsb2dfeGVzICU+JSBhY3Rpdml0eV9wcmVzZW5jZSgpDQphY3Rpdml0eV9wcmVzZW5jZSAlPiUgcGxvdCgpDQpgYGANClRoZSB0cmFjZSBjb3ZlcmFnZSBtZXRyaWMgc2hvd3MgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBudW1iZXIgb2YgZGlmZmVyZW50IGFjdGl2aXR5IHNlcXVlbmNlcyAoaS5lLiB0cmFjZXMpIGFuZCB0aGUgbnVtYmVyIG9mIGNhc2VzIHRoZXkgY292ZXIuDQoNCmBgYHtyfQ0KdHJhY2VfY292ZXJhZ2UgPC0gbG9nX3hlcyAlPiUgdHJhY2VfY292ZXJhZ2UoInRyYWNlIikNCnRyYWNlX2NvdmVyYWdlICU+JSBwbG90KCkNCmBgYA0KVGhlIHRyYWNlIGxlbmd0aCBtZXRyaWMgZGVzY3JpYmVzIHRoZSBsZW5ndGggb2YgdHJhY2VzLCBpLmUuIHRoZSBudW1iZXIgb2YgYWN0aXZpdHkgaW5zdGFuY2VzIGZvciBlYWNoIGNhc2UuIEl0IGNhbiBiZSBjb21wdXRlZCBhdCB0aGUgbGV2ZWxzIGNhc2UsIHRyYWNlIGFuZCBsb2cuDQoNCmBgYHtyfQ0KdHJhY2VfbGVuZ3RoIDwtIGxvZ194ZXMgJT4lIHRyYWNlX2xlbmd0aCgibG9nIikNCnRyYWNlX2xlbmd0aCAlPiUgcGxvdCgpDQpgYGANCg0KIyMjIFByb2Nlc3MgbW9kZWxzDQoNCiMjIyMgRnJlcXVlbmN5IG1hcHMNCg0KQSBwcm9jZXNzIG1hcCBvZiBhIGxvZyBjYW4gYmUgY3JlYXRlZCB1c2luZyBwcm9jZXNzX21hcCgpLiBBIHByb2Nlc3MgbWFwIGlzIGEgZGlyZWN0bHktZm9sbG93cyBncmFwaCwgd2hlcmUgZWFjaCBkaXN0aW5jdCBhY3Rpdml0eSBpcyByZXByZXNlbnRlZCBieSBhIG5vZGUsIGFuZCBlYWNoIGRpcmVjdGx5LWZvbGxvd3MgcmVsYXRpb25zaGlwIGJldHdlZW4gYWN0aXZpdGllcyBpcyBzaG93biBieSBkaXJlY3RlZCBlZGdlcywgaS5lLiBhcnJvd3MgYmV0d2VlbiB0aGUgbm9kZXMuDQoNCmBgYHtyfQ0KZnJlcXVlbmN5IDwtIGxvZ194ZXMgJT4lIGZpbHRlcl90cmFjZV9mcmVxdWVuY3kocGVyY2VudGFnZSA9IDAuOCkNCmBgYA0KDQpgYGB7cn0NCmZyZXF1ZW5jeSAlPiUgcHJvY2Vzc19tYXAoZnJlcXVlbmN5KCJhYnNvbHV0ZSIpKQ0KYGBgDQoNCiMjIyMgUGVyZm9ybWFuY2UgbWFwcw0KDQpJbnN0ZWFkIG9mIGEgZnJlcXVlbmNpZXMsIHByb2Nlc3MgbWFwcyBjYW4gYWxzbyBiZSB1c2VkIHRvIHZpc3VhbGl6ZSBwZXJmb3JtYW5jZSBvZiB0aGUgcHJvY2VzcywgYnkgdXNpbmcgcGVyZm9ybWFuY2UoKSB0byBjb25maWd1cmUgdGhlIG1hcCwgaW5zdGVhZCBvZiBmcmVxdWVuY3koKS4NCg0KYGBge3J9DQpwZXJmb3JtYW5jZSA8LSBsb2dfeGVzICU+JSBwcm9jZXNzX21hcChwZXJmb3JtYW5jZSgpKQ0KcGVyZm9ybWFuY2UNCmBgYA0KDQpJbmZvcm1hdGlvbiBhYm91dCBmcmVxdWVuY2llcyBhbmQgcGVyZm9ybWFuY2UsIG9yIGFueSBvdGhlciB2YWx1ZSwgY2FuIGJlIGNvbWJpbmVkIGluIHRoZSBzYW1lIGdyYXBoLg0KDQpgYGB7cn0NCmZyZXF1ZW5jeV9wZXJmb3JtYW5jZSA8LSBsb2dfeGVzICU+JSBwcm9jZXNzX21hcCh0eXBlX25vZGVzID0gZnJlcXVlbmN5KCJyZWxhdGl2ZV9jYXNlIiksIHR5cGVfZWRnZXMgPSBwZXJmb3JtYW5jZShtZWFuKSkNCmZyZXF1ZW5jeV9wZXJmb3JtYW5jZQ0KYGBgDQoNCiMjIyMgQW5pbWF0aW9uDQoNCkl0IGlzIHBvc3NpYmxlIHRvIGRldGVybWluZSB0aGUgYWVzdGhldGljcyBvZiB0b2tlbnMgcmVnYXJkbGVzcyBvZiB0aGUgdGltZXN0YW1wcyBhdCB3aGljaCBhY3Rpdml0aWVzIG9jY3VycmVkLiBUaGlzIGNvdWxkIGJlIHVzZWZ1bCBpZiBzb21lIG1lYXN1cmVtZW50cyB3ZXJlIHRha2VuIHRocm91Z2hvdXQgYSBwcm9jZXNzLCBidXQgdGhlIG1lYXN1cmVtZW50IGV2ZW50IGl0c2VsZiBzaG91bGQgbm90IGJlIGluY2x1ZGVkIGluIHRoZSBwcm9jZXNzIG1hcC4NCg0KRm9yIGV4YW1wbGUsIHRoZSBsYWN0aWNhY2lkIG1lYXN1cmVtZW50cyBjb3VsZCBiZSB1c2VkIGluIHRoYXQgd2F5Og0KDQpgYGB7cn0NCiMgRXh0cmFjdCBvbmx5IHRoZSBsYWN0aWNhY2lkIG1lYXN1cmVtZW50cw0KbGFjdGljIDwtIGxvZ194ZXMgJT4lDQogICAgbXV0YXRlKExhY3RpY0FjaWQgPSBhcy5udW1lcmljKExhY3RpY0FjaWQpKSAlPiUNCiAgICBmaWx0ZXJfYWN0aXZpdHkoYygiTGFjdGljQWNpZCIpKSAlPiUNCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgc2VsZWN0KCJjYXNlIiA9IENBU0VfY29uY2VwdF9uYW1lLCANCiAgICAgICAgICAgICJ0aW1lIiA9ICB0aW1lc3RhbXAsIA0KICAgICAgICAgICAgdmFsdWUgPSBMYWN0aWNBY2lkKSAjIGZvcm1hdCBuZWVkcyB0byBiZSAnY2FzZSx0aW1lLHZhbHVlJw0KDQojIFJlbW92ZSB0aGUgbWVhc3VyZW1lbnQgZXZlbnRzIGZyb20gdGhlIHNlcHNpcyBsb2cNCnNlcHNpc0Jhc2UgPC0gbG9nX3hlcyAlPiUNCiAgICBmaWx0ZXJfYWN0aXZpdHkoYygiTGFjdGljQWNpZCIsICJDUlAiLCAiTGV1Y29jeXRlcyIsICJSZXR1cm4gRVIiLA0KICAgICAgICAgICAgICAgICAgICAgICJJViBMaXF1aWQiLCAiSVYgQW50aWJpb3RpY3MiKSwgcmV2ZXJzZSA9IFQpICU+JQ0KICAgIGZpbHRlcl90cmFjZV9mcmVxdWVuY3kocGVyY2VudGFnZSA9IDAuOTUpDQoNCiMgQW5pbWF0ZSB3aXRoIHRoZSBzZWNvbmRhcnkgZGF0YSBmcmFtZSBgbGFjdGljYA0KYW5pbWF0ZV9wcm9jZXNzKHNlcHNpc0Jhc2UsIA0KICAgICAgICAgICAgICAgIG1vZGUgPSAicmVsYXRpdmUiLCANCiAgICAgICAgICAgICAgICBkdXJhdGlvbiA9IDMwMCwNCiAgICAgICAgICAgICAgICBsZWdlbmQgPSAiY29sb3IiLCANCiAgICAgICAgICAgICAgICBtYXBwaW5nID0gdG9rZW5fYWVzKGNvbG9yID0gdG9rZW5fc2NhbGUobGFjdGljLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGUgPSAibGluZWFyIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmdlID0gYygiI2ZmZjVlYiIsIiM3ZjI3MDQiKSkpKSANCmBgYA0KDQojIyMgUHJvY2VzcyB2aXN1YWxpemF0aW9ucw0KDQojIyMjIFByb2Nlc3MgbWF0cml4DQoNCkEgcHJvY2VzcyBtYXRyaXggaXMgYSB0d28tZGltZW5zaW9uYWwgbWF0cml4IHNob3dpbmcgdGhlIGZsb3dzIGJldHdlZW4gYWN0aXZpdGllcy4gSXRzIGNvbmZpZ3VyYXRpb24gaXMgZXhhY3RseSB0aGUgc2FtZSBhcyB0aGF0IHVzZWQgYnkgcHJvY2Vzc19tYXAoKS4NCg0KYGBge3J9DQptYXRyaXhfZnJlcXVlbmN5IDwtIGxvZ194ZXMgJT4lIHByb2Nlc3NfbWF0cml4KGZyZXF1ZW5jeSgiYWJzb2x1dGUiKSkgDQptYXRyaXhfZnJlcXVlbmN5ICU+JSBwbG90KCkNCmBgYA0KDQojIyMjIERvdHRlZCBjaGFydA0KDQpEb3R0ZWQgY2hhcnRzIGNhbiBiZSBtYWRlIHdpdGggZG90dGVkX2NoYXJ0KCkuIEEgZG90dGVkIGNoYXJ0IGlzIGEgZ3JhcGggaW4gd2hpY2ggZWFjaCBhY3Rpdml0eSBpbnN0YW5jZSBpcyBkaXNwbGF5ZWQgYnkgYSBkb3QuIFRoZSB4LWF4aXMgcmVmZXJzIHRvIHRoZSB0aW1lIGFzcGVjdCwgd2hpbGUgdGhlIHktYXhpcyByZWZlcnMgdG8gY2FzZXMuDQoNCmBgYHtyfQ0KZG90dGVkX2NoYXJ0IDwtIGxvZ194ZXMgJT4lIGRvdHRlZF9jaGFydCh4ID0gImFic29sdXRlIikNCmRvdHRlZF9jaGFydA0KYGBgDQoNCiMjIyMgVHJhY2UgZXhwbG9yZXINCg0KRGlmZmVyZW50IGFjdGl2aXR5IHNlcXVlbmNlcyBpbiB0aGUgbG9nIGNhbiBiZSB2aXN1YWxpemVkIHdpdGggdHJhY2VfZXhwbG9yZXIoKS4gSXQgY2FuIGJlIHVzZWQgdG8gZXhwbG9yZSBmcmVxdWVudCBhcyB3ZWxsIGFzIGluZnJlcXVlbnQgdHJhY2VzLg0KDQpgYGB7cn0NCnRyYWNlX2V4cGxvcmVyIDwtIGxvZ194ZXMgJT4lIHRyYWNlX2V4cGxvcmVyKCkNCnRyYWNlX2V4cGxvcmVyDQpgYGANCg0KIyMjIyBQZXJmb3JtYW5jZSBzcGVjdHJ1bQ0KDQpCb3RoIGRldGFpbGVkIGFuZCBhZ2dyZWdhdGVkIHBlcmZvcm1hbmNlIHNwZWN0cnVtIGNhbiBiZSBjcmVhdGVkIHVzaW5nIHBzX2RldGFpbGVkKCkgYW5kIHBzX2FnZ3JlZ2F0ZWQoKSwgcmVzcGVjdGl2ZWx5Lg0KDQpgYGB7cn0NCmxpYnJhcnkocHNtaW5lUikNCg0Kc3BlY3RydW1fZGV0YWlsZWQgPC0gbG9nX3hlcyAlPiUgcHNfZGV0YWlsZWQoKQ0Kc3BlY3RydW1fZGV0YWlsZWQNCmBgYA0KDQojIyBGaWx0ZXJpbmcNCg0KIyMjIEluZnJlcXVlbnQgZmxvd3MNCg0KRmlsdGVyaW5nIGluZnJlcXVlbnQgZmxvd3MgYWxsb3dzIHVzIHRvIHNlbGVjdCBhIHNldCBvZiBjYXNlcyBpbiB3aGljaCBldmVyeSBkaXJlY3RseS1mb2xsb3dzIGZsb3cgaGFzIGEgbWluaW11bSBmcmVxdWVuY3kuIEZvciBleGFtcGxlLCBjb25zaWRlciB0aGUgcHJvY2VzcyBtYXAgYmVsb3cuDQoNCmBgYHtyfQ0KbG9nX3hlcyAlPiUgcHJvY2Vzc19tYXAoKQ0KYGBgDQoNCkluIHRoaXMgbWFwLCB3ZSBjYW4gb2JzZXJ2ZSBzZXZlcmFsIHVuaXF1ZSBkaXJlY3RseSBmb2xsb3dzIHJlbGF0aW9ucywgYXMgd2VsbCBhcyBmbG93cyBvY2N1cnJpbmcgbGVzcyB0aGFuIDMwIHRpbWVzLiBVc2luZyB0aGUgZmlsdGVyLCB3ZSBjYW4gcmVtb3ZlIHRoZSBjYXNlcyB0aGF0IGxlYWQgdG8gdGhlc2UgZmxvd3MgYXMgZm9sbG93czoNCg0KYGBge3J9DQpsb2dfeGVzIDwtIGxvZ194ZXMgJT4lDQogIG11dGF0ZShhY3Rpdml0eV9pbnN0YW5jZV9pZCA9IGFzLmNoYXJhY3RlcihhY3Rpdml0eV9pbnN0YW5jZV9pZCkpDQoNCmluZnJlcXVlbnRfZmxvd3MgPC0gbG9nX3hlcyAlPiUgZmlsdGVyX2luZnJlcXVlbnRfZmxvd3MobWluX24gPSAzMCkgJT4lIHByb2Nlc3NfbWFwKCkNCmluZnJlcXVlbnRfZmxvd3MNCmBgYA0KDQojIyMgVGltZSBwZXJpb2QNCg0KRmlsdGVyaW5nIGNhc2VzIGJ5IHRpbWUgcGVyaW9kIGNhbiBiZSBkb25lIHVzaW5nIHRoZSBmaWx0ZXJfdGltZV9wZXJpb2QoKS4gVGhlcmUgYXJlIGZvdXIgZGlmZmVyZW50IGZpbHRlcl9tZXRob2TigJlzIHRoYXQgYWN0IGFzIGNhc2UgZmlsdGVyczoNCg0KKiDigJxzdGFydOKAnTogYWxsIGNhc2VzIHN0YXJ0ZWQgaW4gYW4gaW50ZXJ2YWwuDQoqIOKAnGNvbXBsZXRl4oCdOiBhbGwgY2FzZXMgY29tcGxldGVkIGluIGFuIGludGVydmFsLg0KKiDigJxjb250YWluZWTigJ06IGFsbCBjYXNlcyBjb250YWluZWQgaW4gYW4gaW50ZXJ2YWwuDQoqIOKAnGludGVyc2VjdGluZ+KAnTogYWxsIGNhc2VzIHdpdGggc29tZSBhY3Rpdml0eSBpbiBhbiBpbnRlcnZhbC4NCg0KVXNpbmcgdGhlIGludGVydmFsIEphbnVhcnkgMjAxNSwgeW91IGNhbiBjb21wYXJlIHRoZSByZXN1bHRzIG9mIGRpZmZlcmVudCBmaWx0ZXJpbmcgbWV0aG9kcyBiZWxvdyB1c2luZyBkb3R0ZWQgY2hhcnRzLg0KDQpgYGB7cn0NCnRpbWVfcGVyaW9kIDwtIGxvZ194ZXMgJT4lIGZpbHRlcl90aW1lX3BlcmlvZChpbnRlcnZhbCA9IHltZChjKDIwMTUwMTAxLCAyMDE1MDEzMSkpLCBmaWx0ZXJfbWV0aG9kID0gInN0YXJ0IikgJT4lIGRvdHRlZF9jaGFydCgpIA0KdGltZV9wZXJpb2QNCmBgYA0KDQojIyMgQ2FzZSBjb25kaXRpb24NCg0KZmlsdGVyX2Nhc2VfY29uZGl0aW9uKCkgY2FuIGJlIHVzZWQgdG8gc2VsZWN0IGNhc2VzIGZvciB3aGljaCBhIGNvbmRpdGlvbiBob2xkcy4gVGhpcyBjb25kaXRpb24gY2FuIGJlIHJlbGF0ZWQgdG8gYW55IG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIGxvZy4NCg0KRm9yIGV4YW1wbGUsIHNlbGVjdCBhbGwgY2FzZXMgd2hlcmUgYWdlIGhpZ2hlciB0aGFuIDg1IGlzIGludm9sdmVkLg0KDQpgYGB7cn0NCmFnZV84NSA8LSBsb2dfeGVzICU+JSBmaWx0ZXIoIWlzLm5hKEFnZSkpICU+JSBmaWx0ZXJfY2FzZV9jb25kaXRpb24oQWdlID49IDg1KQ0KYWdlXzg1DQpwcm9jZXNzX21hcChhZ2VfODUpDQpgYGANCg0KIyMjIFByZWNlZGVuY2UNCg0KVGhlIGZpbHRlcl9wcmVjZWRlbmNlKCkgYWxsb3dzIHVzIHRvIGZpbHRlciBjYXNlcyBiYXNlZCBvbiBmbG93cyBiZXR3ZWVuIGFjdGl2aXRpZXMuDQoNCklmIHRoZXJlIGlzIG1vcmUgdGhhbiBvbmUgYW50ZWNlZGVudCBvciBjb25zZXF1ZW50IGFjdGl2aXR5LCB0aGUgZmlsdGVyIHdpbGwgdGVzdCBhbGwgcG9zc2libGUgcGFpcnMuIFRoZSBmaWx0ZXJfbWV0aG9kIHdpbGwgdGVsbCB0aGUgZmlsdGVyIHdoZXRoZXIgYWxsIG9mIHRoZSBydWxlcyBzaG91bGQgaG9sZCwgYXQgbGVhc3Qgb25lLCBvciBub25lIGFyZSBhbGxvd2VkLg0KDQpUaGUgZm9sbG93aW5nIGZpbHRlciB0YWtlcyBvbmx5IGNhc2VzIHdoZXJlIFRyaWFnZSBhbmQgQXNzZXNzbWVudCBpcyBkaXJlY3RseSBmb2xsb3dlZCBieSBCbG9vZCB0ZXN0Lg0KDQpgYGB7cn0NCnByZWNlZGVuY2UgPC0gbG9nX3hlcyAlPiUNCiAgICBmaWx0ZXJfcHJlY2VkZW5jZShhbnRlY2VkZW50cyA9ICJFUiBUcmlhZ2UiLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbnNlcXVlbnRzID0gIkxldWNvY3l0ZXMiLA0KICAgICAgICAgICAgICAgICAgICAgIHByZWNlZGVuY2VfdHlwZSA9ICJkaXJlY3RseV9mb2xsb3dzIikgJT4lDQogICAgdHJhY2VzKCkNCmhlYWQocHJlY2VkZW5jZSkNCmBgYA0KDQojIyBDb25mb3JtYW5jZSBjaGVja2luZw0KDQojIyMgUnVsZS1iYXNlZCBjb25mb3JtYW5jZQ0KDQpVc2luZyB0aGUgcGFja2FnZXMgcHJvY2Vzc2NoZWNrciBwcm9kZWN1cmFsIHJ1bGVzIGNhbiBiZSBjaGVja2VkIGluIGFuIGV2ZW50IGxvZy4gQ2hlY2tpbmcgcnVsZXMgd2lsbCBhZGQgYSBib29sZWFuIGNhc2UgYXR0cmlidXRlLCB3aGljaCBjYW4gYmUgdXNlZCBmb3IgZmlsdGVyaW5nIG9yIGluIGFuYWx5c2lzLg0KDQpSdWxlcyBjYW4gYmUgY2hlY2tlZCB1c2luZyB0aGUgY2hlY2tfcnVsZSBmdW5jdGlvbiAoc2VlIGV4YW1wbGUgYmVsb3cpLiBJdCB3aWxsIGNyZWF0ZSBhIG5ldyBsb2dpY2FsIHZhcmlhYmxlIHRvIGluZGljYXRlIGZvciB3aGljaCBjYXNlcyB0aGUgcnVsZSBob2xkcy4gVGhlIG5hbWUgb2YgdGhlIHZhcmlhYmxlIGNhbiBiZSBjb25maWd1cmVkIHVzaW5nIHRoZSBsYWJlbCBhcmd1bWVudCBpbiBjaGVja19ydWxlLg0KDQpJbiB0aGUgZm9sbG93aW5nIGV4YW1wbGUsIHRoZSBmaXJzdCBydWxlIGNoZWNrcyB0aGUgc3RhcnRpbmcgYWN0aXZpdHksIHdoaWxlIHRoZSBzZWNvbmQgcnVsZSBjaGVja3Mgd2hldGhlciBDUlAgYW5kIExhY3RpY0FjaWQgb2NjdXIgdG9nZXRoZXIuDQoNCmBgYHtyfQ0KbG9nX3hlcyAlPiUNCiAgIyBjaGVjayBpZiBjYXNlcyBzdGFydHMgd2l0aCAiRVIgUmVnaXN0cmF0aW9uIg0KICBjaGVja19ydWxlKHN0YXJ0cygiRVIgUmVnaXN0cmF0aW9uIiksIGxhYmVsID0gInIxIikgJT4lDQogICMgY2hlY2sgaWYgYWN0aXZpdGllcyAiQ1JQIiBhbmQgIkxhY3RpY0FjaWQiIG9jY3VyIHRvZ2V0aGVyDQogIGNoZWNrX3J1bGUoYW5kKCJDUlAiLCJMYWN0aWNBY2lkIiksIGxhYmVsID0gInIyIikgJT4lDQogIGdyb3VwX2J5KHIxLCByMikgJT4lDQogIG5fY2FzZXMoKQ0KYGBgDQoNCiMjIyBBbGlnbm1lbnRzDQoNCkFsaWdubWVudHMgaXMgdW5kZXIgZGV2ZWxvcG1lbnQgYW5kIGNhbiBiZSB1c2VkIHdpdGggdGhlIGJ1cGFSbWluZXIgbGlicmFyeS4gTW9yZSBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgaW4gdGhlIEdpdEh1YjogaHR0cHM6Ly9naXRodWIuY29tL2J1cGF2ZXJzZS9idXBhUm1pbmVyLg0KDQoNCg0KDQo=