Feedback should be send to goran.milovanovic_ext@wikimedia.de.

Reference Document: New editors - WMDE deep dive - analytics questions

Reference Phabricator Ticket: https://phabricator.wikimedia.org/T256433

Background/ Reason why

The first campaign which contained tracking was conducted in 2017. Since then several campaigns with different content and user journeys have been realized. For strategic decisions on future activities to gain new editors we want to comprehensively analyze past activities and their impact. Besides qualitative results we need to consider quantitative results. As campaign reports usually cover a certain time during and after the campaign but no long term effects. This should be done in this comprehensive analysis.

Timing Briefing: 9th July 2020 Delivery of report: week 31

General requirements

  • The report should be delivered in tables and charts in html as in previous reports

  • The report might be publicly available in the future. For this delivery deadline this is no requirement.

  • Communication will happen in phabricator

  • Based on this report we will have more questions which should be addressed in a phase 2 in August.

  • The time span for this report is January 2017 until June 2020

0. Data Acquisiton

NOTE: the Data Acquisition code chunk is not fully reproducible from this Report. The data are collected by running the PySpark script CampaignsReview2020_S01_ETL.py on the stat1005.eqiad.wmnet server, collecting the data either as .tsv files or storing large datasets directly to HDFS in the WMF Data Lake. Immediately following this step the R script CampaignsReview2020_S01_ETL.R is run to clean the data, produce aggregated datasets, prepare the data for visualization, and compute all requested statistics.

0.1 ETL: dewiki user registrations and revisions

The CampaignsReview2020_S01_ETL.py script: collect data on user registrations and revisions from the wmf.mediawiki_history table:

# - Setup
import pyspark
from pyspark.sql import SparkSession, DataFrame, Window
from pyspark.sql.functions import rank, col, explode, regexp_extract, array_contains, when, sum, count, expr
import re
import csv
import pandas as pd

### --- dir structure and params
mwwikiSnapshot = "2020-06"

# - Spark Session
sc = SparkSession\
    .builder\
    .appName("WD Human Edits per Class")\
    .enableHiveSupport()\
    .getOrCreate()
# - SQL context
sqlContext = pyspark.SQLContext(sc)

# - Process wmf.mediawiki_history: all users ever registered with dewiki
dewiki_regusers = sqlContext.sql("""SELECT user_id, user_registration_timestamp 
                                    FROM wmf.mediawiki_history 
                                    WHERE event_entity = 'user' 
                                        AND event_type = 'create' 
                                        AND user_is_anonymous = false 
                                        AND user_is_created_by_self = true 
                                        AND user_id IS NOT NULL  
                                        AND user_registration_timestamp IS NOT NULL 
                                        AND NOT ARRAY_CONTAINS(user_is_bot_by, 'name') 
                                        AND NOT ARRAY_CONTAINS(user_is_bot_by, 'group') 
                                        AND NOT ARRAY_CONTAINS(user_is_bot_by_historical, 'name') 
                                        AND NOT ARRAY_CONTAINS(user_is_bot_by_historical, 'group') 
                                        AND wiki_db = 'dewiki' 
                                        AND snapshot='""" + mwwikiSnapshot + """'""" + 
                                 """ORDER BY user_id""")

dewiki_regusers.toPandas().to_csv("/home/goransm/Analytics/NewEditors/CampaignsReview2020/_data/dewiki_regusers.csv", 
                                  header=True, 
                                  index=False)
                                  
# - Process wmf.mediawiki_history: all revisions ever made on dewiki
dewiki_revisions = sqlContext.sql("""SELECT event_user_id, 
                                           event_user_registration_timestamp, 
                                           event_timestamp 
                                    FROM wmf.mediawiki_history 
                                    WHERE event_entity = 'revision' 
                                        AND event_type = 'create' 
                                        AND event_user_is_anonymous = false 
                                        AND event_user_is_created_by_self = true 
                                        AND event_user_id IS NOT NULL 
                                        AND event_user_registration_timestamp IS NOT NULL 
                                        AND NOT ARRAY_CONTAINS(event_user_is_bot_by, 'name') 
                                        AND NOT ARRAY_CONTAINS(event_user_is_bot_by, 'group') 
                                        AND NOT ARRAY_CONTAINS(event_user_is_bot_by_historical, 'name') 
                                        AND NOT ARRAY_CONTAINS(event_user_is_bot_by_historical, 'group') 
                                        AND page_namespace_is_content = 0 
                                        AND page_namespace_is_content_historical = 0 
                                        AND wiki_db = 'dewiki' 
                                        AND snapshot='""" + mwwikiSnapshot + """'""" + 
                                 """ORDER BY event_user_id, event_timestamp""")

dewiki_revisions.repartition(10).write.format('csv').save('dewiki_revisions')

0.2 Definitions: user registration and user revision

From the CampaignsReview2020_S01_ETL.py script we can see exactly what definitions of user registration and user revision are used in this report:

  • User registration: we exclude anonymous users (user_is_anonymous = false), users who where not self-created (user_is_created_by_self = true), users who have no values found in the user_id or the registration timestamp field (user_id IS NOT NULL AND user_registration_timestamp IS NOT NULL), and any users who are currently classified as bots or used to be classified as bots in the past.

  • User revision: we focus on revisions made on content pages only (page_namespace_is_content = true AND page_namespace_is_content_historical = true) holding on to the same set of constraints for users who have cause a particular revision as we did in the definition of user registration.

0.3 Data pre-processing: dewiki user registrations and revisions

The CampaignsReview2020_S01_ETL.R script: clean the data, produce aggregated datasets, prepare the data for visualization, and compute all requested statistics:

### --- 2020/07/14
### --- Phab: https://phabricator.wikimedia.org/T256433
### --- WMDE Banner Campaigns Comprehensive Report 2017-2020

t1 <- Sys.time()

library(tidyverse)
library(data.table)

fPath <- '/home/goransm/Analytics/NewEditors/CampaignsReview2020/'
dataDir <- paste0(fPath, "_data/")
analyticsDir <- paste0(fPath, "_analytics/")
hdfsPath <- 'hdfs:///user/goransm/dewiki_revisions'

### ---------------------------------------------------------------------
### --- Section 0. Campaigns Dataset
### ---------------------------------------------------------------------

### --- load Campaign Registered Users dataset
campaignIDs <- read.csv(paste0(dataDir, "_campaignIDs/WMDE_Campaign_Registered_Users_IDs.csv"), 
                        header = T,
                        check.names = F,
                        stringsAsFactors = F)

### ---------------------------------------------------------------------
### --- Section 1. Datasets
### ---------------------------------------------------------------------

### --- Compose final revision dataset from hdfs: dewiki_revisions
# - copy splits from hdfs to local dataDir
system(paste0('sudo -u analytics-privatedata kerberos-run-command analytics-privatedata hdfs dfs -ls ', 
              hdfsPath, ' > ', 
              dataDir, 'files.txt'), 
       wait = T)
files <- read.table(paste0(dataDir, 'files.txt'), skip = 1)
files <- as.character(files$V8)[2:length(as.character(files$V8))]
file.remove(paste0(dataDir, 'files.txt'))
for (i in 1:length(files)) {
  system(paste0('sudo -u analytics-privatedata kerberos-run-command analytics-privatedata hdfs dfs -text ', 
                files[i], ' > ',  
                paste0(dataDir, "dewiki_revisions", i, ".csv")), wait = T)
}
# - read splits: dewiki_revisions
# - load
lF <- list.files(dataDir)
lF <- lF[grepl("dewiki_revisions", lF)]
dewiki_revisions <- lapply(lF, function(x) {fread(paste0(dataDir, x), header = F)})
# - collect
dewiki_revisions <- rbindlist(dewiki_revisions)
# - schema
colnames(dewiki_revisions) <- c('user_id', 'reg_time', 'rev_time')

### --- Load registration data: dewiki_regusers
dewiki_regusers <- fread(paste0(dataDir, "dewiki_regusers.csv"), header = T)

### ---------------------------------------------------------------------
### --- Section 2. Statistics and Analytical Datasets
### ---------------------------------------------------------------------

### -----------------------------------------------------
### --- Statistics on user registrations since the beginning of time
### -----------------------------------------------------

# - remove campaign registered users from dewiki_regusers
campaign_regIDS <- unique(campaignIDs$user_id[campaignIDs$registered == 1])
dewiki_regusers <- dewiki_regusers[!(dewiki_regusers$user_id %in% campaign_regIDS), ]

# - stats
stats <- list()
stats$total_registered_users <- dim(dewiki_regusers)[1]
wEdited <- which(dewiki_regusers$user_id %in% dewiki_revisions$user_id)
stats$total_users_who_edited <- length(wEdited)

# - distibution of account age
dewiki_regusers$reg_time <- as.Date(dewiki_regusers$user_registration_timestamp)
dewiki_regusers$account_age_weeks <- as.numeric(
  difftime(Sys.time(),
           dewiki_regusers$reg_time,
           units = "weeks")
)
dewiki_regusers$account_age_years = dewiki_regusers$account_age_weeks/52.1429

# - stats
stats$min_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers$account_age_weeks))[1]
  )
stats$max_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers$account_age_weeks))[6]
)
stats$mean_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers$account_age_weeks))[4]
)
stats$median_account_age_weeks <- median(dewiki_regusers$account_age_weeks)
stats$min_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers$account_age_years))[1]
)
stats$max_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers$account_age_years))[6]
)
stats$mean_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers$account_age_years))[4]
)
stats$median_account_age_years <- median(dewiki_regusers$account_age_years)
saveRDS(stats, 
        paste0(analyticsDir, "dewiki_stats.Rds"))

# - store data file
saveRDS(dewiki_regusers, 
        paste0(analyticsDir, "dewiki_regusers.Rds"))

### -----------------------------------------------------
### --- Statistics on user registrations since 2017
### -----------------------------------------------------

dewiki_regusers_2017 <- dewiki_regusers[dewiki_regusers$user_registration_timestamp >= 2017, ]

# - stats
stats_2017 <- list()
stats_2017$total_registered_users <- dim(dewiki_regusers_2017)[1]
wEdited <- which(dewiki_regusers_2017$user_id %in% dewiki_revisions$user_id)
stats_2017$total_users_who_edited <- length(wEdited)
stats_2017$min_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers$account_age_weeks))[1]
)
stats_2017$max_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers_2017$account_age_weeks))[6]
)
stats_2017$mean_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers_2017$account_age_weeks))[4]
)
stats_2017$median_account_age_weeks <- median(dewiki_regusers_2017$account_age_weeks)
stats_2017$min_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers_2017$account_age_years))[1]
)
stats_2017$max_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers_2017$account_age_years))[6]
)
stats_2017$mean_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers_2017$account_age_years))[4]
)
stats_2017$median_account_age_years <- median(dewiki_regusers_2017$account_age_years)
saveRDS(stats_2017, 
        paste0(analyticsDir, "dewiki_stats_2017.Rds"))

# - store data file
saveRDS(dewiki_regusers_2017, 
        paste0(analyticsDir, "dewiki_regusers_2017.Rds"))

# - clean up
rm(dewiki_regusers); rm(dewiki_regusers_2017); gc()

### -----------------------------------------------------
### --- Statistics on revisions since the beginning of time
### -----------------------------------------------------

# - remove campaign registered users from dewiki_revisions
campaign_regIDS <- unique(campaignIDs$user_id[campaignIDs$registered == 1])
dewiki_revisions <- dewiki_revisions[!(dewiki_revisions$user_id %in% campaign_regIDS), ]
# - for non-registering campaigns: keep only user revisions before the campaign onset
# - there is currently one non-registering campaign present in campaignIDs:
non_registering_campaignIDs <- campaignIDs %>% 
  filter(registered == 0)
non_registering_campaigns <- unique(non_registering_campaignIDs$campaign)
non_registering_campaigns
# - "occasional_editors2020"
# - the campaign onset for "occasional_editors2020" is:
# - 2020/05/14
wRemoveRevisions <- which(
  (dewiki_revisions$user_id %in% non_registering_campaignIDs$user_id) & 
    (dewiki_revisions$rev_time >= "2020-05-14")
  )
dewiki_revisions <- dewiki_revisions[-wRemoveRevisions, ]

# - statistics
stats_revisions <- list()
stats_revisions$total_revisions <- dim(dewiki_revisions)[1]

# - distribution of number of revisions per user
rev_dist <- table(dewiki_revisions$user_id)
rev_dist <- as.data.frame(rev_dist)
colnames(rev_dist) <- c('user_id', 'revisions')
rev_dist <- arrange(rev_dist, desc(revisions))
rev_dist <- table(rev_dist$revisions)
rev_dist <- as.data.frame(rev_dist)
colnames(rev_dist) <- c('revisions', 'users')
rev_dist <- arrange(rev_dist, desc(users))
saveRDS(rev_dist, 
        paste0(analyticsDir, "rev_dist.Rds"))
rm(rev_dist); gc()

# - edit classes
editClasses <- dewiki_revisions %>% 
  select(user_id) %>% 
  group_by(user_id) %>% 
  summarise(revisions = n())
editBoundaries <- list(
  c(0, 1), 
  c(2, 5),
  c(6, 9),
  c(10, 49)
)
editClasses$editClass <- sapply(editClasses$revisions, function(x) {
  wEC <- sapply(editBoundaries, function(y) {
    x >= y[1] & x <= y[2]
  })
  if (sum(wEC) == 0) {
    return("> 50")
  } else {
    return(paste0(editBoundaries[[which(wEC)]][1],
                  " - ",
                  editBoundaries[[which(wEC)]][2]
    )
    )
  }
})
editClasses$editClass[editClasses$editClass == "0 - 1"] <- "1"
editClasses <- arrange(editClasses, desc(revisions))
editClasses_dist <- table(editClasses$editClass)
editClasses_dist <- as.data.frame(editClasses_dist)
colnames(editClasses_dist) <- c("Edit Class", "Users")
editClasses_dist$`Edit Class` <- factor(editClasses_dist$`Edit Class`, 
                                        levels = c('1', 
                                                   '2 - 5', 
                                                   '6 - 9', 
                                                   '10 - 49', 
                                                   '> 50'))
editClasses_dist <- arrange(editClasses_dist, `Edit Class`)
editClasses_dist$`% Users` <- editClasses_dist$Users/sum(editClasses_dist$Users)*100
saveRDS(editClasses_dist, 
        paste0(analyticsDir, "editClasses_dist.Rds"))

# - cummulative edits in dewiki_revisions
setkey(dewiki_revisions, user_id, rev_time)
dewiki_revisions <- dewiki_revisions[order(user_id, rev_time)]
dewiki_revisions[, cum_revisions := seq_len(.N), by = user_id]
dewiki_revisions[, cum_revisions := rowid(user_id)]
dewiki_revisions$reg_time <- as.Date(dewiki_revisions$reg_time)
dewiki_revisions$rev_time <- as.Date(dewiki_revisions$rev_time)
dewiki_revisions$account_age_rev_time_weeks <- difftime(dewiki_revisions$rev_time,
                                                        dewiki_revisions$reg_time,
                                                        units = "weeks")
dewiki_revisions$account_age_rev_time_years <- 
  dewiki_revisions$account_age_rev_time_weeks/52.1429
dewiki_revisions$account_age_rev_time_weeks <- 
  as.numeric(dewiki_revisions$account_age_rev_time_weeks)
dewiki_revisions$account_age_rev_time_years <- 
  as.numeric(dewiki_revisions$account_age_rev_time_years)
dewiki_revisions$editClass <- sapply(dewiki_revisions$cum_revisions, function(x) {
  wEC <- sapply(editBoundaries, function(y) {
    x >= y[1] & x <= y[2]
  })
  if (sum(wEC) == 0) {
    return("> 50")
  } else {
    return(paste0(editBoundaries[[which(wEC)]][1],
                  " - ",
                  editBoundaries[[which(wEC)]][2]
    )
    )
  }
})
dewiki_revisions <- dewiki_revisions[order(rev_time)]

### ___ NOTE ___
# - There are 29148 observations where reg_time > rev_time:
sum(dewiki_revisions$account_age_rev_time_weeks < 0)
### ___ ACTION ___
# - remove from dewiki_revisions:
w <- which(dewiki_revisions$account_age_rev_time_weeks < 0)
dewiki_revisions <- dewiki_revisions[-w, ]

# - intoduce account age in weeks and years classes
dewiki_revisions$rev_time_ym <- substr(dewiki_revisions$rev_time, 1, 7)
dewiki_revisions$account_age_rev_time_years_class <- 
  round(dewiki_revisions$account_age_rev_time_years)
dewiki_revisions$account_age_rev_time_years_class <- 
  paste0(dewiki_revisions$account_age_rev_time_years_class, 
         " - ", 
         dewiki_revisions$account_age_rev_time_years_class + 1)

# - save the elaborated version of dewiki_revisions
saveRDS(dewiki_revisions, 
        paste0(analyticsDir, "dewiki_revisions_elaborated.Rds"))

# - produce dewiki_revisions_overview for visualization
dewiki_revisions_overview <- dewiki_revisions %>% 
  select(rev_time_ym, editClass, account_age_rev_time_years_class) %>% 
  group_by(rev_time_ym, editClass, account_age_rev_time_years_class) %>% 
  summarise(n_users = n())
saveRDS(dewiki_revisions_overview, 
        paste0(analyticsDir, "dewiki_revisions_overview.Rds"))

# - users active (at least one edit) after
# - two weeks, one month, six months, and one year
two_weeks = 2
one_month = 4.34524
six_months = 26.0715
one_year = 52.1429
active_users <- list()
active_users$two_weeks <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > two_weeks]
      )
    )
active_users$one_month <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > one_month]
    )
  )
active_users$six_months <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > six_months]
    )
  )
active_users$one_year <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > one_year]
    )
  )
active_users$two_weeks_p_total_registered_users <- 
  active_users$two_weeks/stats$total_registered_users
active_users$one_month_p_total_registered_users <- 
  active_users$one_month/stats$total_registered_users
active_users$six_months_p_total_registered_users <- 
  active_users$six_months/stats$total_registered_users
active_users$one_year_p_total_registered_users <- 
  active_users$one_year/stats$total_registered_users
active_users$two_weeks_p_total_users_who_edited <- 
  active_users$two_weeks/stats$total_users_who_edited
active_users$one_month_p_total_users_who_edited <- 
  active_users$one_month/stats$total_users_who_edited
active_users$six_months_total_users_who_edited <- 
  active_users$six_months/stats$total_users_who_edited
active_users$one_year_p_total_users_who_edited <- 
  active_users$one_year/stats$total_users_who_edited
saveRDS(active_users, 
        paste0(analyticsDir, "active_users.Rds"))

### -----------------------------------------------------
### --- Statistics on revisions since 2017
### -----------------------------------------------------

rm(dewiki_revisions); gc()
# - read splits: dewiki_revisions
# - load
lF <- list.files(dataDir)
lF <- lF[grepl("dewiki_revisions", lF)]
dewiki_revisions <- lapply(lF, function(x) {fread(paste0(dataDir, x), header = F)})
# - collect
dewiki_revisions <- rbindlist(dewiki_revisions)
# - schema
colnames(dewiki_revisions) <- c('user_id', 'reg_time', 'rev_time')

# - filter for >= 2017 on user registration
dewiki_revisions <- filter(dewiki_revisions, reg_time >= 2017)
dewiki_revisions <- as.data.table(dewiki_revisions)

# - remove campaign registered users from dewiki_revisions
campaign_regIDS <- unique(campaignIDs$user_id[campaignIDs$registered == 1])
dewiki_revisions <- dewiki_revisions[!(dewiki_revisions$user_id %in% campaign_regIDS), ]
# - for non-registering campaigns: keep only user revisions before the campaign onset
# - there is currently one non-registering campaign present in campaignIDs:
non_registering_campaignIDs <- campaignIDs %>% 
  filter(registered == 0)
non_registering_campaigns <- unique(non_registering_campaignIDs$campaign)
non_registering_campaigns
# - "occasional_editors2020"
# - the campaign onset for "occasional_editors2020" is:
# - 2020/05/14
wRemoveRevisions <- which(
  (dewiki_revisions$user_id %in% non_registering_campaignIDs$user_id) & 
    (dewiki_revisions$rev_time >= "2020-05-14")
)
dewiki_revisions <- dewiki_revisions[-wRemoveRevisions, ]

# - statistics
stats_revisions_2017 <- list()
stats_revisions_2017$total_revisions <- dim(dewiki_revisions)[1]

# - distribution of number of revisions per user
rev_dist_2017 <- table(dewiki_revisions$user_id)
rev_dist_2017 <- as.data.frame(rev_dist_2017)
colnames(rev_dist_2017) <- c('user_id', 'revisions')
rev_dist_2017 <- arrange(rev_dist_2017, desc(revisions))
rev_dist_2017 <- table(rev_dist_2017$revisions)
rev_dist_2017 <- as.data.frame(rev_dist_2017)
colnames(rev_dist_2017) <- c('revisions', 'users')
rev_dist_2017 <- arrange(rev_dist_2017, desc(users))
saveRDS(rev_dist_2017, 
        paste0(analyticsDir, "rev_dist_2017.Rds"))
rm(rev_dist_2017); gc()

# - edit classes
editClasses_2017 <- dewiki_revisions %>% 
  select(user_id) %>% 
  group_by(user_id) %>% 
  summarise(revisions = n())
editBoundaries <- list(
  c(0, 1), 
  c(2, 5),
  c(6, 9),
  c(10, 49)
)
editClasses_2017$editClass <- sapply(editClasses_2017$revisions, function(x) {
  wEC <- sapply(editBoundaries, function(y) {
    x >= y[1] & x <= y[2]
  })
  if (sum(wEC) == 0) {
    return("> 50")
  } else {
    return(paste0(editBoundaries[[which(wEC)]][1],
                  " - ",
                  editBoundaries[[which(wEC)]][2]
    )
    )
  }
})
editClasses_2017$editClass[editClasses_2017$editClass == "0 - 1"] <- "1"
editClasses_2017 <- arrange(editClasses_2017, desc(revisions))
editClasses_dist_2017 <- table(editClasses_2017$editClass)
editClasses_dist_2017 <- as.data.frame(editClasses_dist_2017)
colnames(editClasses_dist_2017) <- c("Edit Class", "Users")
editClasses_dist_2017$`Edit Class` <- factor(editClasses_dist_2017$`Edit Class`, 
                                        levels = c('1', 
                                                   '2 - 5', 
                                                   '6 - 9', 
                                                   '10 - 49', 
                                                   '> 50'))
editClasses_dist_2017 <- arrange(editClasses_dist_2017, `Edit Class`)
editClasses_dist_2017$`% Users` <- editClasses_dist_2017$Users/sum(editClasses_dist_2017$Users)*100
saveRDS(editClasses_dist_2017, 
        paste0(analyticsDir, "editClasses_dist_2017.Rds"))

# - cummulative edits in dewiki_revisions
setkey(dewiki_revisions, user_id, rev_time)
dewiki_revisions <- dewiki_revisions[order(user_id, rev_time)]
dewiki_revisions[, cum_revisions := seq_len(.N), by = user_id]
dewiki_revisions[, cum_revisions := rowid(user_id)]
dewiki_revisions$reg_time <- as.Date(dewiki_revisions$reg_time)
dewiki_revisions$rev_time <- as.Date(dewiki_revisions$rev_time)
dewiki_revisions$account_age_rev_time_weeks <- difftime(dewiki_revisions$rev_time,
                                                        dewiki_revisions$reg_time,
                                                        units = "weeks")
dewiki_revisions$account_age_rev_time_years <- 
  dewiki_revisions$account_age_rev_time_weeks/52.1429
dewiki_revisions$account_age_rev_time_weeks <- 
  as.numeric(dewiki_revisions$account_age_rev_time_weeks)
dewiki_revisions$account_age_rev_time_years <- 
  as.numeric(dewiki_revisions$account_age_rev_time_years)
dewiki_revisions$editClass <- sapply(dewiki_revisions$cum_revisions, function(x) {
  wEC <- sapply(editBoundaries, function(y) {
    x >= y[1] & x <= y[2]
  })
  if (sum(wEC) == 0) {
    return("> 50")
  } else {
    return(paste0(editBoundaries[[which(wEC)]][1],
                  " - ",
                  editBoundaries[[which(wEC)]][2]
    )
    )
  }
})
dewiki_revisions <- dewiki_revisions[order(rev_time)]

# - intoduce account age in weeks and years classes
dewiki_revisions$rev_time_ym <- substr(dewiki_revisions$rev_time, 1, 7)
dewiki_revisions$account_age_rev_time_years_class <- 
  round(dewiki_revisions$account_age_rev_time_years)
dewiki_revisions$account_age_rev_time_years_class <- 
  paste0(dewiki_revisions$account_age_rev_time_years_class, 
         " - ", 
         dewiki_revisions$account_age_rev_time_years_class + 1)

# - save the elaborated version of dewiki_revisions_2017 
saveRDS(dewiki_revisions, 
        paste0(analyticsDir, "dewiki_revisions_2017_elaborated.Rds"))

# - produce dewiki_revisions_overview_2017 for visualization
dewiki_revisions_overview_2017 <- dewiki_revisions %>% 
  select(rev_time_ym, editClass, account_age_rev_time_years_class) %>% 
  group_by(rev_time_ym, editClass, account_age_rev_time_years_class) %>% 
  summarise(n_users = n())
saveRDS(dewiki_revisions_overview_2017, 
        paste0(analyticsDir, "dewiki_revisions_overview_2017.Rds"))

# - users active (at least one edit) after
# - two weeks, one month, six months, and one year
two_weeks = 2
one_month = 4.34524
six_months = 26.0715
one_year = 52.1429
active_users_2017 <- list()
active_users_2017$two_weeks <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > two_weeks]
    )
  )
active_users_2017$one_month <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > one_month]
    )
  )
active_users_2017$six_months <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > six_months]
    )
  )
active_users_2017$one_year <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > one_year]
    )
  )
active_users_2017$two_weeks_p_total_registered_users <- 
  active_users_2017$two_weeks/stats_2017$total_registered_users
active_users_2017$one_month_p_total_registered_users <- 
  active_users_2017$one_month/stats_2017$total_registered_users
active_users_2017$six_months_p_total_registered_users <- 
  active_users_2017$six_months/stats_2017$total_registered_users
active_users_2017$one_year_p_total_registered_users <- 
  active_users_2017$one_year/stats_2017$total_registered_users
active_users_2017$two_weeks_p_total_users_who_edited <- 
  active_users_2017$two_weeks/stats_2017$total_users_who_edited
active_users_2017$one_month_p_total_users_who_edited <- 
  active_users_2017$one_month/stats_2017$total_users_who_edited
active_users_2017$six_months_total_users_who_edited <- 
  active_users_2017$six_months/stats_2017$total_users_who_edited
active_users_2017$one_year_p_total_users_who_edited <- 
  active_users_2017$one_year/stats_2017$total_users_who_edited
saveRDS(active_users_2017, 
        paste0(analyticsDir, "active_users_2017.Rds"))

### -----------------------------------------------------
### --- Statistics on user registrations for 
### --- campaign registered users
### -----------------------------------------------------

### --- load Campaign Registered Users dataset
campaignIDs <- read.csv(paste0(dataDir, "_campaignIDs/WMDE_Campaign_Registered_Users_IDs.csv"), 
                        header = T,
                        check.names = F,
                        stringsAsFactors = F)

# - read splits: dewiki_revisions
# - load
lF <- list.files(dataDir)
lF <- lF[grepl("dewiki_revisions", lF)]
dewiki_revisions <- lapply(lF, function(x) {fread(paste0(dataDir, x), header = F)})
# - collect
dewiki_revisions <- rbindlist(dewiki_revisions)
# - schema
colnames(dewiki_revisions) <- c('user_id', 'reg_time', 'rev_time')

### --- Load registration data: dewiki_regusers
dewiki_regusers <- fread(paste0(dataDir, "dewiki_regusers.csv"), header = T)

# - which campaign registered users cannot be found in dewiki_regusers
wNotFound <- which(!(campaignIDs$user_id[campaignIDs$registered == 1] %in% dewiki_regusers$user_id))
campaignIDs$not_found_in_dewiki_regusers <- 0
campaignIDs$not_found_in_dewiki_regusers[wNotFound] <- 1
# - store elaborated campaignIDs
write.csv(campaignIDs, 
          paste0(dataDir, "_campaignIDs/WMDE_Campaign_Registered_Users_IDs_Elaborated.csv"))

# - keep only campaign registered users from dewiki_regusers
dim(dewiki_regusers)
campaign_regIDS <- unique(campaignIDs$user_id[campaignIDs$registered == 1])
dewiki_regusers <- dewiki_regusers[(dewiki_regusers$user_id %in% campaign_regIDS), ]
dim(dewiki_regusers)

# - stats
stats_campaigns <- list()
stats_campaigns$total_registered_users <- dim(dewiki_regusers)[1]
wEdited <- which(dewiki_regusers$user_id %in% dewiki_revisions$user_id)
stats_campaigns$total_users_who_edited <- length(wEdited)

# - distibution of account age
dewiki_regusers$reg_time <- as.Date(dewiki_regusers$user_registration_timestamp)
dewiki_regusers$account_age_weeks <- as.numeric(
  difftime(Sys.time(),
           dewiki_regusers$reg_time,
           units = "weeks")
)
dewiki_regusers$account_age_years = dewiki_regusers$account_age_weeks/52.1429

# - stats
stats_campaigns$min_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers$account_age_weeks))[1]
)
stats_campaigns$max_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers$account_age_weeks))[6]
)
stats_campaigns$mean_account_age_weeks <- unname(
  summary(as.numeric(dewiki_regusers$account_age_weeks))[4]
)
stats_campaigns$median_account_age_weeks <- median(dewiki_regusers$account_age_weeks)
stats_campaigns$min_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers$account_age_years))[1]
)
stats_campaigns$max_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers$account_age_years))[6]
)
stats_campaigns$mean_account_age_years <- unname(
  summary(as.numeric(dewiki_regusers$account_age_years))[4]
)
stats_campaigns$median_account_age_years <- median(dewiki_regusers$account_age_years)
saveRDS(stats_campaigns, 
        paste0(analyticsDir, "dewiki_stats_campaigns.Rds"))

# - store data file
saveRDS(dewiki_regusers, 
        paste0(analyticsDir, "dewiki_regusers_campaigns.Rds"))

### -----------------------------------------------------
### --- Statistics on revisions for campaign registered users
### -----------------------------------------------------

# - filter for campaign registered users on user registration
dim(dewiki_revisions)
dewiki_revisions <- filter(dewiki_revisions, user_id %in% campaign_regIDS)
dewiki_revisions <- as.data.table(dewiki_revisions)
dim(dewiki_revisions)

# - for non-registering campaigns: keep only user revisions following the campaign onset
# - there is currently one non-registering campaign present in campaignIDs:
non_registering_campaignIDs <- campaignIDs %>% 
  filter(registered == 0)
non_registering_campaigns <- unique(non_registering_campaignIDs$campaign)
non_registering_campaigns
# - "occasional_editors2020"
# - the campaign onset for "occasional_editors2020" is:
# - 2020/05/14
wRemoveRevisions <- which(
  (dewiki_revisions$user_id %in% non_registering_campaignIDs$user_id) & 
    (dewiki_revisions$rev_time < "2020-05-14")
)
dewiki_revisions <- dewiki_revisions[-wRemoveRevisions, ]

# - statistics
stats_revisions_campaigns <- list()
stats_revisions_campaigns$total_revisions <- dim(dewiki_revisions)[1]

# - distribution of number of revisions per user
rev_dist_campaigns <- table(dewiki_revisions$user_id)
rev_dist_campaigns <- as.data.frame(rev_dist_campaigns)
colnames(rev_dist_campaigns) <- c('user_id', 'revisions')
rev_dist_campaigns <- arrange(rev_dist_campaigns, desc(revisions))
rev_dist_campaigns <- table(rev_dist_campaigns$revisions)
rev_dist_campaigns <- as.data.frame(rev_dist_campaigns)
colnames(rev_dist_campaigns) <- c('revisions', 'users')
rev_dist_campaigns <- arrange(rev_dist_campaigns, desc(users))
saveRDS(rev_dist_campaigns, 
        paste0(analyticsDir, "rev_dist_campaigns.Rds"))
rm(rev_dist_campaigns); gc()

# - edit classes
editClasses_campaigns <- dewiki_revisions %>% 
  select(user_id) %>% 
  group_by(user_id) %>% 
  summarise(revisions = n())
editBoundaries <- list(
  c(0, 1), 
  c(2, 5),
  c(6, 9),
  c(10, 49)
)
editClasses_campaigns$editClass <- sapply(editClasses_campaigns$revisions, function(x) {
  wEC <- sapply(editBoundaries, function(y) {
    x >= y[1] & x <= y[2]
  })
  if (sum(wEC) == 0) {
    return("> 50")
  } else {
    return(paste0(editBoundaries[[which(wEC)]][1],
                  " - ",
                  editBoundaries[[which(wEC)]][2]
    )
    )
  }
})
editClasses_campaigns$editClass[editClasses_campaigns$editClass == "0 - 1"] <- "1"
editClasses_campaigns <- arrange(editClasses_campaigns, desc(revisions))
editClasses_dist_campaigns <- table(editClasses_campaigns$editClass)
editClasses_dist_campaigns <- as.data.frame(editClasses_dist_campaigns)
colnames(editClasses_dist_campaigns) <- c("Edit Class", "Users")
editClasses_dist_campaigns$`Edit Class` <- factor(editClasses_dist_campaigns$`Edit Class`, 
                                             levels = c('1', 
                                                        '2 - 5', 
                                                        '6 - 9', 
                                                        '10 - 49', 
                                                        '> 50'))
editClasses_dist_campaigns <- arrange(editClasses_dist_campaigns, `Edit Class`)
editClasses_dist_campaigns$`% Users` <- editClasses_dist_campaigns$Users/sum(editClasses_dist_campaigns$Users)*100
saveRDS(editClasses_dist_campaigns, 
        paste0(analyticsDir, "editClasses_dist_campaigns.Rds"))

# - cummulative edits in dewiki_revisions
setkey(dewiki_revisions, user_id, rev_time)
dewiki_revisions <- dewiki_revisions[order(user_id, rev_time)]
dewiki_revisions[, cum_revisions := seq_len(.N), by = user_id]
dewiki_revisions[, cum_revisions := rowid(user_id)]
dewiki_revisions$reg_time <- as.Date(dewiki_revisions$reg_time)
dewiki_revisions$rev_time <- as.Date(dewiki_revisions$rev_time)
dewiki_revisions$account_age_rev_time_weeks <- difftime(dewiki_revisions$rev_time,
                                                        dewiki_revisions$reg_time,
                                                        units = "weeks")
dewiki_revisions$account_age_rev_time_years <- 
  dewiki_revisions$account_age_rev_time_weeks/52.1429
dewiki_revisions$account_age_rev_time_weeks <- 
  as.numeric(dewiki_revisions$account_age_rev_time_weeks)
dewiki_revisions$account_age_rev_time_years <- 
  as.numeric(dewiki_revisions$account_age_rev_time_years)
dewiki_revisions$editClass <- sapply(dewiki_revisions$cum_revisions, function(x) {
  wEC <- sapply(editBoundaries, function(y) {
    x >= y[1] & x <= y[2]
  })
  if (sum(wEC) == 0) {
    return("> 50")
  } else {
    return(paste0(editBoundaries[[which(wEC)]][1],
                  " - ",
                  editBoundaries[[which(wEC)]][2]
    )
    )
  }
})
dewiki_revisions <- dewiki_revisions[order(rev_time)]

# - intoduce account age in weeks and years classes
dewiki_revisions$rev_time_ym <- substr(dewiki_revisions$rev_time, 1, 7)
dewiki_revisions$account_age_rev_time_years_class <- 
  round(dewiki_revisions$account_age_rev_time_years)
dewiki_revisions$account_age_rev_time_years_class <- 
  paste0(dewiki_revisions$account_age_rev_time_years_class, 
         " - ", 
         dewiki_revisions$account_age_rev_time_years_class + 1)

# - save the elaborated version of dewiki_revisions_campaigns
saveRDS(dewiki_revisions, 
        paste0(analyticsDir, "dewiki_revisions_campaigns_elaborated.Rds"))


# - produce dewiki_revisions_overview_campaigns for visualization
dewiki_revisions_overview_campaigns <- dewiki_revisions %>% 
  select(rev_time_ym, editClass, account_age_rev_time_years_class) %>% 
  group_by(rev_time_ym, editClass, account_age_rev_time_years_class) %>% 
  summarise(n_users = n())
saveRDS(dewiki_revisions_overview_campaigns, 
        paste0(analyticsDir, "dewiki_revisions_overview_campaigns.Rds"))

# - users active (at least one edit) after
# - two weeks, one month, six months, and one year
two_weeks = 2
one_month = 4.34524
six_months = 26.0715
one_year = 52.1429
active_users_campaigns <- list()
active_users_campaigns$two_weeks <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > two_weeks]
    )
  )
active_users_campaigns$one_month <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > one_month]
    )
  )
active_users_campaigns$six_months <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > six_months]
    )
  )
active_users_campaigns$one_year <- 
  length(
    unique(
      dewiki_revisions$user_id[dewiki_revisions$account_age_rev_time_weeks > one_year]
    )
  )
active_users_campaigns$two_weeks_p_total_registered_users <- 
  active_users_campaigns$two_weeks/stats_campaigns$total_registered_users

active_users_campaigns$one_month_p_total_registered_users <- 
  active_users_campaigns$one_month/stats_campaigns$total_registered_users

active_users_campaigns$six_months_p_total_registered_users <- 
  active_users_campaigns$six_months/stats_campaigns$total_registered_users

active_users_campaigns$one_year_p_total_registered_users <- 
  active_users_campaigns$one_year/stats_campaigns$total_registered_users

active_users_campaigns$two_weeks_p_total_users_who_edited <- 
  active_users_campaigns$two_weeks/stats_campaigns$total_users_who_edited

active_users_campaigns$one_month_p_total_users_who_edited <- 
  active_users_campaigns$one_month/stats_campaigns$total_users_who_edited

active_users_campaigns$six_months_total_users_who_edited <- 
  active_users_campaigns$six_months/stats_campaigns$total_users_who_edited

active_users_campaigns$one_year_p_total_users_who_edited <- 
  active_users_campaigns$one_year/stats_campaigns$total_users_who_edited

saveRDS(active_users_campaigns, 
        paste0(analyticsDir, "active_users_campaigns.Rds"))

### --- Final Reporting
paste0("Processing took: ", difftime(Sys.time(), t1, units = "mins"), " minutes.")
rm(list = ls()); gc()

1 Data Analysis

All statistics and visualizations reported in the following sections refer to the questions formulated in the reference Phabricator ticket.

Note. All revisions made by campaign registered users were removed from the revision datasets used in the sections addressing the organic registrations and revisions since 2017. For the non-registering WMDE Banner Campaigns (e.g. WMDE Occasional Editors 2020 campaign, which addressed the already existing, registered users only), we remove all the edits made on their behalf following their exposure to the respective campaign. Likewise, in the campaigns datasets, we remove all their edits made before their exposure to the respective campaign.

1.1 Organic Registrations and Revisions since 2017

1.1.1 Organic Registrations since 2017

Q. What is the age of the German Wikipedia Community in terms of account age?

stats_2017 <- readRDS(paste0(analyticsDir, 'dewiki_stats_2017.Rds'))

The following statistics all refer to dewiki user registrations since 2017:

  • The total number of registered users is 386471.
  • The total number of registered users who have ever edited is 145690.
  • That means that 37.7% of registered users ever edited dewiki.
  • Statistics on account age in weeks: the minimum account age is 2.39, the maximum account age is 184.8, the mean account age is 92.97, and the median account ages is 91.53;
  • while in years, that would be: the minimum account age is 0.05, the maximum account age is 3.54, the mean account age is 1.78, and the median account ages is 1.76.

1.1.2 User Revisions since 2017

Q. How many of them edited (since registration until 30th June 2020):

  • 1 edit

  • 2 to 5 edits

  • 5 to 9 edits

  • 10 to 49 edits

  • 50 or more edits

editClasses_dist_2017 <- readRDS(paste0(analyticsDir, 'editClasses_dist_2017.Rds'))
editClasses_dist_2017$`% Users` <- round(editClasses_dist_2017$`% Users`, 2)
datatable(editClasses_dist_2017)

Q. Retention rate: How many newly registered users are active after (active = at least 1 edit)

  • 2 weeks after registration
  • 1 month after registration
  • 6 months after registration
  • 12 months after registration

How high is the retention rate of these active users compared to the number of registrations?

active_users_2017 <- readRDS(paste0(analyticsDir, 'active_users_2017.Rds'))
active_users_2017 <- data.frame(
  `Retention Class` = c('2 weeks', '1 month', '6 months', '1 year'),
  Users = as.numeric(active_users_2017[1:4]),
  `As % of registered users` = round(as.numeric(active_users_2017[5:8]), 2),
  `As % of users who ever edited` = round(as.numeric(active_users_2017[9:12]), 2),
  stringsAsFactors = F, 
  check.names = F)
datatable(active_users_2017)

Q. Edit Classes (facets) x Account Age Classes (group, step: one year) x Time (horizontal) → do we observe always one and the same group of active editors, or do the newcomers join in to stay active editors? - start: 2017.

Note. In the following chart, the Account Age variable refers to the user account age at the moment when a respective revision was made by that user. Tabs refer to different edit classes.

dewiki_revisions_overview_2017 <- readRDS(paste0(analyticsDir, 'dewiki_revisions_overview_2017.Rds'))
dewiki_revisions_overview_2017 <- ungroup(dewiki_revisions_overview_2017)
colnames(dewiki_revisions_overview_2017) <- c('Revision Year-Month', 'Edit Class', 'Account Age', 'Users')
dewiki_revisions_overview_2017 <- filter(dewiki_revisions_overview_2017, 
                                         !(`Revision Year-Month` == "2020-07"))
dewiki_revisions_overview_2017$`Edit Class`[dewiki_revisions_overview_2017$`Edit Class` == "0 - 1"] <- "1"
dewiki_revisions_overview_2017$`Edit Class` <- factor(dewiki_revisions_overview_2017$`Edit Class`,
                                                      levels = c('1',
                                                                 '2 - 5',
                                                                 '6 - 9',
                                                                 '10 - 49',
                                                                 '> 50'))
ggplot(dewiki_revisions_overview_2017, 
       aes(x = `Revision Year-Month`, 
           y = Users,
           group = `Account Age`, 
           color = `Account Age`)) + 
  geom_line() + geom_point(size = 1) +
  scale_color_manual(values=c("#308FF3", "#70BA0A", "#FABC0A", "#ED2809")) +
  facet_wrap(~`Edit Class`, nrow = 5, ncol = 1, scales="free_y") + 
  theme_bw() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.95, vjust = 0.2))

1.2 Campaign Registrations and Revisions since 2017

Note. Some users found in the WMDE campaign user registration datasets could not be matched with the user IDs in the wmf.mediawiki_history table. The following table presents an overview of how many campaign registered users are missing in the wmf.mediawiki_history (see: Wikitech wmf.mediaWiki_history documentation). All WMDE campaign registered user IDs were checked for uniqueness and confirmed to be unique. The total number of WMDE campaign registered users that were not matched to the user ID fields (event_user_id for revisions, and user_id for registrations) in the wmf.mediawiki_history table is 49.

campaignIDs <- read.csv(paste0(dataDir, "_campaignIDs/WMDE_Campaign_Registered_Users_IDs_Elaborated.csv"), 
                        header = T, 
                        check.names = F,
                        row.names = 1,
                        stringsAsFactors = F)
not_found <- as.data.frame(
  table(campaignIDs$campaign[campaignIDs$not_found_in_dewiki_regusers == 1])
)
colnames(not_found) <- c('Campaign Code', 'Num.Users')
datatable(not_found)

Given that there are 4163 WMDE campaign registered users in total, that means what we will not be able to analyze 1.18% of them. To keep the analysis consistent with the numbers previously reported on user registrations and revisions since 2017, all user registration data are derived from the campaign registered users that were matched with the user IDs in the wmf.mediawiki_history table.

1.2.1 Campaign Registrations since 2017

Q. What is the age of the German Wikipedia Community in terms of account age for campaign registered users?

dewiki_stats_campaigns <- readRDS(paste0(analyticsDir, 'dewiki_stats_campaigns.Rds'))

The following statistics all refer to dewiki campaign user registrations since 2017:

  • The total number of registered users is 4114.
  • The total number of registered users who have ever edited is 1123.
  • That means that 27.3% of registered users ever edited dewiki.
  • Statistics on account age in weeks: the minimum account age is 2.68, the maximum account age is 170.4, the mean account age is 117.4, and the median account ages is 114.39;
  • while in years, that would be: the minimum account age is 0.05, the maximum account age is 3.27, the mean account age is 2.25, and the median account ages is 2.19.

1.2.2 Campaign Revisions since 2017

Q. How many of campaign registered users edited (since registration until 30th June 2020):

  • 1 edit

  • 2 to 5 edits

  • 5 to 9 edits

  • 10 to 49 edits

  • 50 or more edits

editClasses_dist_campaigns <- readRDS(paste0(analyticsDir, 'editClasses_dist_campaigns.Rds'))
editClasses_dist_campaigns$`% Users` <- round(editClasses_dist_campaigns$`% Users`, 2)
datatable(editClasses_dist_campaigns)

Q. Retention rate: How many campaign registered users are active after (active = at least 1 edit)

  • 2 weeks after registration
  • 1 month after registration
  • 6 months after registration
  • 12 months after registration

How high is the retention rate of these active users compared to the number of registrations?

active_users_campaigns <- readRDS(paste0(analyticsDir, 'active_users_campaigns.Rds'))
active_users_campaigns <- data.frame(
  `Retention Class` = c('2 weeks', '1 month', '6 months', '1 year'),
  Users = as.numeric(active_users_campaigns[1:4]),
  `As % of registered users` = round(as.numeric(active_users_campaigns[5:8]), 2),
  `As % of users who ever edited` = round(as.numeric(active_users_campaigns[9:12]), 2),
  stringsAsFactors = F, 
  check.names = F)
datatable(active_users_campaigns)

Q. Edit Classes (facets) x Account Age Classes (group, step: one year) x Time (horizontal) → do we observe always one and the same group of active editors, or do the newcomers join in to stay active editors? - start: 2017 (for campaign registered users):

Note. In the following chart, the Account Age variable refers to the user account age at the moment when a respective revision was made by that user. Tabs refer to different edit classes.

dewiki_revisions_overview_campaigns <- readRDS(paste0(analyticsDir, 'dewiki_revisions_overview_campaigns.Rds'))
dewiki_revisions_overview_campaigns <- ungroup(dewiki_revisions_overview_campaigns)
colnames(dewiki_revisions_overview_campaigns) <- c('Revision Year-Month', 'Edit Class', 'Account Age', 'Users')
dewiki_revisions_overview_campaigns <- filter(dewiki_revisions_overview_campaigns, 
                                         !(`Revision Year-Month` == "2020-07"))
dewiki_revisions_overview_campaigns$`Edit Class`[dewiki_revisions_overview_campaigns$`Edit Class` == "0 - 1"] <- "1"
dewiki_revisions_overview_campaigns$`Edit Class` <- factor(dewiki_revisions_overview_campaigns$`Edit Class`,
                                                           levels = c('1',
                                                                      '2 - 5',
                                                                      '6 - 9',
                                                                      '10 - 49',
                                                                      '> 50'))
ggplot(dewiki_revisions_overview_campaigns, 
       aes(x = `Revision Year-Month`, 
           y = Users,
           group = `Account Age`, 
           color = `Account Age`)) + 
  geom_line() + geom_point(size = 1) + 
  scale_color_manual(values=c("#308FF3", "#70BA0A", "#FABC0A", "#ED2809")) + 
  facet_wrap(~`Edit Class`, nrow = 5, ncol = 1, scales="free_y") + 
  theme_bw() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.95, vjust = 0.2))

1.3 Campaign Registrations and Revisions: Overview

1.3.1 Campaign User Registrations and Revisions

I have just one major remark on the report: I also need registrations and revisions per campaign to be able to compare the campaigns. Are you still on it or did you miss it out? (the edit class and account age comparison is not necessary for the campaign split).

Reference: Phab: T256433#6328618 Note. The recent editors column reports on number of campaign registered users who did at least one edit as of 30th June 2020, reference: Phab: T256433#6385973

campaignRegs <- read.csv(paste0(analyticsDir, "campaignRegistrationsSummary.csv"), 
                         header = T,
                         check.names = F,
                         row.names = 1,
                         stringsAsFactors = F)
campaignRevs <- read.csv(paste0(analyticsDir, "campaignRevisionsSummary.csv"), 
                         header = T,
                         check.names = F,
                         row.names = 1,
                         stringsAsFactors = F)
campaignsOverview <- left_join(campaignRevs, 
                               campaignRegs, 
                               by = "campaign")
campaignsOverview$rev_per_reg <- round(campaignsOverview$revisions/campaignsOverview$registered, 2)
recentEditors <- read.csv(paste0(analyticsDir, "recentCampaignEditors.csv"), 
                         header = T,
                         check.names = F,
                         row.names = 1,
                         stringsAsFactors = F)
colnames(recentEditors)[2] <- "recent editors"
campaignsOverview <- left_join(campaignsOverview, 
                               recentEditors, 
                               by = "campaign")
datatable(campaignsOverview)

1.3.2 Campaign User Retention

I wondered if it were much additional work to compute not only # of revisions and registrations (in section 1.3) , but also retention/ retention rates (like you did in section 1.2.2) per campaign? I guess that’s what @Verena was initially asking for.

Reference: Phab: T256433#6337701

active_users_per_campaign <- readRDS(paste0(analyticsDir, "active_users_per_campaign.Rds"))
active_users_per_campaign <- active_users_per_campaign[, c('campaign',
                                                           'two_weeks',
                                                           'one_month',
                                                           'six_months',
                                                           'one_year')]
colnames(active_users_per_campaign) <- c('campaign',
                                         '2 weeks',
                                         '1 month',
                                         '6 months',
                                         '1 year')
datatable(active_users_per_campaign)

1.4 Training Modules in 2018 Campaigns: Active Users

Of the people who started training modules (onboarding content used in 2018 in thank you, spring and summer campaign), how is the rate of still active users?

active_users_campaigns_training_2018 <- 
  readRDS(paste0(analyticsDir, "active_users_campaigns_training.Rds"))
active_users_campaigns_training_2018 <- data.frame(
  `Retention Class` = c('2 weeks', '1 month', '6 months', '1 year'),
  Users = as.numeric(active_users_campaigns_training_2018[1:4]),
  stringsAsFactors = F, 
  check.names = F)
datatable(active_users_campaigns_training_2018)

2 Additional Requests

2.1 Organic Growth/Age of Community

Request. “Organic Growth/ Age of Community: For the years 2001 to 2019 we need for every year the average age of all accounts who did at least one edit in this year. age = number of years since registration ( I am aware that for a few accounts the registration date can’t be retrieved from the database. Because this should be a relatively small number of accounts we can neglect that here.)” reference Phab ticket

dewiki_revisions_elaborated <- readRDS("~/WMDE/NewEditors/CampaignsReview2020/_analytics/dewiki_revisions_elaborated.Rds")
dewiki_revisions_elaborated <- dplyr::select(dewiki_revisions_elaborated, 
                                             reg_time, 
                                             rev_time)
dewiki_revisions_elaborated$rev_year <- substr(dewiki_revisions_elaborated$rev_time, 1, 4)
dewiki_revisions_elaborated <- dplyr::filter(dewiki_revisions_elaborated, 
                                             rev_year != "2020")
dewiki_revisions_elaborated$account_age <- difftime(dewiki_revisions_elaborated$rev_time, 
                                                    dewiki_revisions_elaborated$reg_time, 
                                                    units = "weeks")
one_year = 52.1429
dewiki_revisions_elaborated$account_age <- dewiki_revisions_elaborated$account_age/one_year
organicGrowth <- dewiki_revisions_elaborated %>% 
  dplyr::select(rev_year, account_age) %>% 
  dplyr::group_by(rev_year) %>% 
  summarise(avg_account_age = mean(account_age))
datatable(organicGrowth)

LS0tCnRpdGxlOiAnMjAyMCBXTURFIE5ldyBFZGl0b3JzIENhbXBhaWducyBSZXZpZXcnCmF1dGhvcjogIkdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERSIKZGF0ZTogIkp1bHkgMTcsIDIwMjAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogc2ltcGxleAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0b2NfZGVwdGg6IDUKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNQotLS0KCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZWAuIAoKKipSZWZlcmVuY2UgRG9jdW1lbnQ6KiogW05ldyBlZGl0b3JzIC0gV01ERSBkZWVwIGRpdmUgLSBhbmFseXRpY3MgcXVlc3Rpb25zXShodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9kb2N1bWVudC9kLzF0T0NwbW40S2dKTERQUjFVYXEza0JkT2lMX0U2NzdDT3luaGhicHI2TVprL2VkaXQ/dHM9NWYwNWUxY2MpCgoqKlJlZmVyZW5jZSBQaGFicmljYXRvciBUaWNrZXQ6KiogW2h0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjU2NDMzXShodHRwczovL3BoYWJyaWNhdG9yLndpa2ltZWRpYS5vcmcvVDI1NjQzMykKCioqQmFja2dyb3VuZC8gUmVhc29uIHdoeSoqCgpUaGUgZmlyc3QgY2FtcGFpZ24gd2hpY2ggY29udGFpbmVkIHRyYWNraW5nIHdhcyBjb25kdWN0ZWQgaW4gMjAxNy4gU2luY2UgdGhlbiBzZXZlcmFsIGNhbXBhaWducyB3aXRoIGRpZmZlcmVudCBjb250ZW50IGFuZCB1c2VyIGpvdXJuZXlzIGhhdmUgYmVlbiByZWFsaXplZC4gRm9yIHN0cmF0ZWdpYyBkZWNpc2lvbnMgb24gZnV0dXJlIGFjdGl2aXRpZXMgdG8gZ2FpbiBuZXcgZWRpdG9ycyB3ZSB3YW50IHRvIGNvbXByZWhlbnNpdmVseSBhbmFseXplIHBhc3QgYWN0aXZpdGllcyBhbmQgdGhlaXIgaW1wYWN0LiBCZXNpZGVzIHF1YWxpdGF0aXZlIHJlc3VsdHMgd2UgbmVlZCB0byBjb25zaWRlciBxdWFudGl0YXRpdmUgcmVzdWx0cy4gQXMgY2FtcGFpZ24gcmVwb3J0cyB1c3VhbGx5IGNvdmVyIGEgY2VydGFpbiB0aW1lIGR1cmluZyBhbmQgYWZ0ZXIgdGhlIGNhbXBhaWduIGJ1dCBubyBsb25nIHRlcm0gZWZmZWN0cy4gVGhpcyBzaG91bGQgYmUgZG9uZSBpbiB0aGlzIGNvbXByZWhlbnNpdmUgYW5hbHlzaXMuCgoqKlRpbWluZyoqCkJyaWVmaW5nOiA5dGggSnVseSAyMDIwCkRlbGl2ZXJ5IG9mIHJlcG9ydDogd2VlayAzMQoKKipHZW5lcmFsIHJlcXVpcmVtZW50cyoqCgotIFRoZSByZXBvcnQgc2hvdWxkIGJlIGRlbGl2ZXJlZCBpbiB0YWJsZXMgYW5kIGNoYXJ0cyBpbiBodG1sIGFzIGluIHByZXZpb3VzIHJlcG9ydHMKCi0gVGhlIHJlcG9ydCBtaWdodCBiZSBwdWJsaWNseSBhdmFpbGFibGUgaW4gdGhlIGZ1dHVyZS4gRm9yIHRoaXMgZGVsaXZlcnkgZGVhZGxpbmUgdGhpcyBpcyBubyByZXF1aXJlbWVudC4KCi0gQ29tbXVuaWNhdGlvbiB3aWxsIGhhcHBlbiBpbiBbcGhhYnJpY2F0b3JdKGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjU2NDMzKQoKLSBCYXNlZCBvbiB0aGlzIHJlcG9ydCB3ZSB3aWxsIGhhdmUgbW9yZSBxdWVzdGlvbnMgd2hpY2ggc2hvdWxkIGJlIGFkZHJlc3NlZCBpbiBhIHBoYXNlIDIgaW4gQXVndXN0LgoKLSBUaGUgdGltZSBzcGFuIGZvciB0aGlzIHJlcG9ydCBpcyBKYW51YXJ5IDIwMTcgdW50aWwgSnVuZSAyMDIwCgpgYGB7ciwgZWNobyA9IEYsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCByZXN1bHRzID0gJ2hpZGUnfQojICFkaWFnbm9zdGljcyBvZmYKIyMjIC0tLSBTZXR1cApsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkocm1hcmtkb3duKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KERUKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKCiMjIyAtLS0gZGlyZWN0b3J5IHRyZWUKZGF0YURpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi8iLCAiX2RhdGEvIikKYW5hbHl0aWNzRGlyIDwtIHBhc3RlMChnZXR3ZCgpLCAiLyIsICJfYW5hbHl0aWNzLyIpCmBgYAoKIyMgMC4gRGF0YSBBY3F1aXNpdG9uCgoqKk5PVEU6KiogdGhlIERhdGEgQWNxdWlzaXRpb24gY29kZSBjaHVuayBpcyBub3QgZnVsbHkgcmVwcm9kdWNpYmxlIGZyb20gdGhpcyBSZXBvcnQuIFRoZSBkYXRhIGFyZSBjb2xsZWN0ZWQgYnkgcnVubmluZyB0aGUgUHlTcGFyayBzY3JpcHQgYENhbXBhaWduc1JldmlldzIwMjBfUzAxX0VUTC5weWAgb24gdGhlIHN0YXQxMDA1LmVxaWFkLndtbmV0IHNlcnZlciwgY29sbGVjdGluZyB0aGUgZGF0YSBlaXRoZXIgYXMgYC50c3ZgIGZpbGVzIG9yIHN0b3JpbmcgbGFyZ2UgZGF0YXNldHMgZGlyZWN0bHkgdG8gSERGUyBpbiB0aGUgV01GIERhdGEgTGFrZS4gSW1tZWRpYXRlbHkgZm9sbG93aW5nIHRoaXMgc3RlcCB0aGUgUiBzY3JpcHQgYENhbXBhaWduc1JldmlldzIwMjBfUzAxX0VUTC5SYCBpcyBydW4gdG8gY2xlYW4gdGhlIGRhdGEsIHByb2R1Y2UgYWdncmVnYXRlZCBkYXRhc2V0cywgcHJlcGFyZSB0aGUgZGF0YSBmb3IgdmlzdWFsaXphdGlvbiwgYW5kIGNvbXB1dGUgYWxsIHJlcXVlc3RlZCBzdGF0aXN0aWNzLgoKIyMjIDAuMSBFVEw6IGBkZXdpa2lgIHVzZXIgcmVnaXN0cmF0aW9ucyBhbmQgcmV2aXNpb25zCgpUaGUgYENhbXBhaWduc1JldmlldzIwMjBfUzAxX0VUTC5weWAgc2NyaXB0OiBjb2xsZWN0IGRhdGEgb24gdXNlciByZWdpc3RyYXRpb25zIGFuZCByZXZpc2lvbnMgZnJvbSB0aGUgYHdtZi5tZWRpYXdpa2lfaGlzdG9yeWAgdGFibGU6CgpgYGB7ciwgZWNobyA9IFQsIGV2YWwgPSBGfQojIC0gU2V0dXAKaW1wb3J0IHB5c3BhcmsKZnJvbSBweXNwYXJrLnNxbCBpbXBvcnQgU3BhcmtTZXNzaW9uLCBEYXRhRnJhbWUsIFdpbmRvdwpmcm9tIHB5c3Bhcmsuc3FsLmZ1bmN0aW9ucyBpbXBvcnQgcmFuaywgY29sLCBleHBsb2RlLCByZWdleHBfZXh0cmFjdCwgYXJyYXlfY29udGFpbnMsIHdoZW4sIHN1bSwgY291bnQsIGV4cHIKaW1wb3J0IHJlCmltcG9ydCBjc3YKaW1wb3J0IHBhbmRhcyBhcyBwZAoKIyMjIC0tLSBkaXIgc3RydWN0dXJlIGFuZCBwYXJhbXMKbXd3aWtpU25hcHNob3QgPSAiMjAyMC0wNiIKCiMgLSBTcGFyayBTZXNzaW9uCnNjID0gU3BhcmtTZXNzaW9uXAogICAgLmJ1aWxkZXJcCiAgICAuYXBwTmFtZSgiV0QgSHVtYW4gRWRpdHMgcGVyIENsYXNzIilcCiAgICAuZW5hYmxlSGl2ZVN1cHBvcnQoKVwKICAgIC5nZXRPckNyZWF0ZSgpCiMgLSBTUUwgY29udGV4dApzcWxDb250ZXh0ID0gcHlzcGFyay5TUUxDb250ZXh0KHNjKQoKIyAtIFByb2Nlc3Mgd21mLm1lZGlhd2lraV9oaXN0b3J5OiBhbGwgdXNlcnMgZXZlciByZWdpc3RlcmVkIHdpdGggZGV3aWtpCmRld2lraV9yZWd1c2VycyA9IHNxbENvbnRleHQuc3FsKCIiIlNFTEVDVCB1c2VyX2lkLCB1c2VyX3JlZ2lzdHJhdGlvbl90aW1lc3RhbXAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZST00gd21mLm1lZGlhd2lraV9oaXN0b3J5IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVSRSBldmVudF9lbnRpdHkgPSAndXNlcicgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgZXZlbnRfdHlwZSA9ICdjcmVhdGUnIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIHVzZXJfaXNfYW5vbnltb3VzID0gZmFsc2UgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgdXNlcl9pc19jcmVhdGVkX2J5X3NlbGYgPSB0cnVlIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIHVzZXJfaWQgSVMgTk9UIE5VTEwgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIHVzZXJfcmVnaXN0cmF0aW9uX3RpbWVzdGFtcCBJUyBOT1QgTlVMTCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFORCBOT1QgQVJSQVlfQ09OVEFJTlModXNlcl9pc19ib3RfYnksICduYW1lJykgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgTk9UIEFSUkFZX0NPTlRBSU5TKHVzZXJfaXNfYm90X2J5LCAnZ3JvdXAnKSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFORCBOT1QgQVJSQVlfQ09OVEFJTlModXNlcl9pc19ib3RfYnlfaGlzdG9yaWNhbCwgJ25hbWUnKSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFORCBOT1QgQVJSQVlfQ09OVEFJTlModXNlcl9pc19ib3RfYnlfaGlzdG9yaWNhbCwgJ2dyb3VwJykgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgd2lraV9kYiA9ICdkZXdpa2knIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIHNuYXBzaG90PSciIiIgKyBtd3dpa2lTbmFwc2hvdCArICIiIiciIiIgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiIiT1JERVIgQlkgdXNlcl9pZCIiIikKCmRld2lraV9yZWd1c2Vycy50b1BhbmRhcygpLnRvX2NzdigiL2hvbWUvZ29yYW5zbS9BbmFseXRpY3MvTmV3RWRpdG9ycy9DYW1wYWlnbnNSZXZpZXcyMDIwL19kYXRhL2Rld2lraV9yZWd1c2Vycy5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlcj1UcnVlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4PUZhbHNlKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiMgLSBQcm9jZXNzIHdtZi5tZWRpYXdpa2lfaGlzdG9yeTogYWxsIHJldmlzaW9ucyBldmVyIG1hZGUgb24gZGV3aWtpCmRld2lraV9yZXZpc2lvbnMgPSBzcWxDb250ZXh0LnNxbCgiIiJTRUxFQ1QgZXZlbnRfdXNlcl9pZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudF91c2VyX3JlZ2lzdHJhdGlvbl90aW1lc3RhbXAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRfdGltZXN0YW1wIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIHdtZi5tZWRpYXdpa2lfaGlzdG9yeSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0hFUkUgZXZlbnRfZW50aXR5ID0gJ3JldmlzaW9uJyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFORCBldmVudF90eXBlID0gJ2NyZWF0ZScgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgZXZlbnRfdXNlcl9pc19hbm9ueW1vdXMgPSBmYWxzZSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFORCBldmVudF91c2VyX2lzX2NyZWF0ZWRfYnlfc2VsZiA9IHRydWUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgZXZlbnRfdXNlcl9pZCBJUyBOT1QgTlVMTCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFORCBldmVudF91c2VyX3JlZ2lzdHJhdGlvbl90aW1lc3RhbXAgSVMgTk9UIE5VTEwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgTk9UIEFSUkFZX0NPTlRBSU5TKGV2ZW50X3VzZXJfaXNfYm90X2J5LCAnbmFtZScpIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIE5PVCBBUlJBWV9DT05UQUlOUyhldmVudF91c2VyX2lzX2JvdF9ieSwgJ2dyb3VwJykgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgTk9UIEFSUkFZX0NPTlRBSU5TKGV2ZW50X3VzZXJfaXNfYm90X2J5X2hpc3RvcmljYWwsICduYW1lJykgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgTk9UIEFSUkFZX0NPTlRBSU5TKGV2ZW50X3VzZXJfaXNfYm90X2J5X2hpc3RvcmljYWwsICdncm91cCcpIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIHBhZ2VfbmFtZXNwYWNlX2lzX2NvbnRlbnQgPSAwIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIHBhZ2VfbmFtZXNwYWNlX2lzX2NvbnRlbnRfaGlzdG9yaWNhbCA9IDAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgd2lraV9kYiA9ICdkZXdpa2knIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EIHNuYXBzaG90PSciIiIgKyBtd3dpa2lTbmFwc2hvdCArICIiIiciIiIgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiIiT1JERVIgQlkgZXZlbnRfdXNlcl9pZCwgZXZlbnRfdGltZXN0YW1wIiIiKQoKZGV3aWtpX3JldmlzaW9ucy5yZXBhcnRpdGlvbigxMCkud3JpdGUuZm9ybWF0KCdjc3YnKS5zYXZlKCdkZXdpa2lfcmV2aXNpb25zJykKYGBgCgojIyMgMC4yIERlZmluaXRpb25zOiB1c2VyIHJlZ2lzdHJhdGlvbiBhbmQgdXNlciByZXZpc2lvbgoKRnJvbSB0aGUgYENhbXBhaWduc1JldmlldzIwMjBfUzAxX0VUTC5weWAgc2NyaXB0IHdlIGNhbiBzZWUgZXhhY3RseSB3aGF0IGRlZmluaXRpb25zIG9mIGB1c2VyIHJlZ2lzdHJhdGlvbmAgYW5kIGB1c2VyIHJldmlzaW9uYCBhcmUgdXNlZCBpbiB0aGlzIHJlcG9ydDoKCi0gKipVc2VyIHJlZ2lzdHJhdGlvbjoqKiB3ZSAqKmV4Y2x1ZGUqKiBfYW5vbnltb3VzIHVzZXJzXyAoYHVzZXJfaXNfYW5vbnltb3VzID0gZmFsc2VgKSwgdXNlcnMgd2hvIF93aGVyZSBub3Qgc2VsZi1jcmVhdGVkXyAoYHVzZXJfaXNfY3JlYXRlZF9ieV9zZWxmID0gdHJ1ZWApLCAKdXNlcnMgX3dobyBoYXZlIG5vIHZhbHVlcyBmb3VuZCBpbiB0aGUgYHVzZXJfaWRgIG9yIHRoZSByZWdpc3RyYXRpb24gdGltZXN0YW1wIGZpZWxkXyAoYHVzZXJfaWQgSVMgTk9UIE5VTEwgQU5EIHVzZXJfcmVnaXN0cmF0aW9uX3RpbWVzdGFtcCBJUyBOT1QgTlVMTGApLCBhbmQgYW55IHVzZXJzIF93aG8gYXJlIGN1cnJlbnRseSBjbGFzc2lmaWVkIGFzIGJvdHMgb3IgdXNlZCB0byBiZSBjbGFzc2lmaWVkIGFzIGJvdHMgaW4gdGhlIHBhc3RfLiAgCgotICoqVXNlciByZXZpc2lvbjoqKiB3ZSAqKmZvY3VzIG9uKiogcmV2aXNpb25zIG1hZGUgb24gX2NvbnRlbnQgcGFnZXNfIG9ubHkgKGBwYWdlX25hbWVzcGFjZV9pc19jb250ZW50ID0gdHJ1ZSBBTkQgcGFnZV9uYW1lc3BhY2VfaXNfY29udGVudF9oaXN0b3JpY2FsID0gdHJ1ZWApIGhvbGRpbmcgb24gdG8gdGhlIHNhbWUgc2V0IG9mIGNvbnN0cmFpbnRzIGZvciB1c2VycyB3aG8gaGF2ZSBjYXVzZSBhIHBhcnRpY3VsYXIgcmV2aXNpb24gYXMgd2UgZGlkIGluIHRoZSBkZWZpbml0aW9uIG9mIHVzZXIgcmVnaXN0cmF0aW9uLgoKCiMjIyAwLjMgRGF0YSBwcmUtcHJvY2Vzc2luZzogYGRld2lraWAgdXNlciByZWdpc3RyYXRpb25zIGFuZCByZXZpc2lvbnMKClRoZSBgQ2FtcGFpZ25zUmV2aWV3MjAyMF9TMDFfRVRMLlJgIHNjcmlwdDogY2xlYW4gdGhlIGRhdGEsIHByb2R1Y2UgYWdncmVnYXRlZCBkYXRhc2V0cywgcHJlcGFyZSB0aGUgZGF0YSBmb3IgdmlzdWFsaXphdGlvbiwgYW5kIGNvbXB1dGUgYWxsIHJlcXVlc3RlZCBzdGF0aXN0aWNzOgoKYGBge3IsIGVjaG8gPSBULCBldmFsID0gRn0KCiMjIyAtLS0gMjAyMC8wNy8xNAojIyMgLS0tIFBoYWI6IGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjU2NDMzCiMjIyAtLS0gV01ERSBCYW5uZXIgQ2FtcGFpZ25zIENvbXByZWhlbnNpdmUgUmVwb3J0IDIwMTctMjAyMAoKdDEgPC0gU3lzLnRpbWUoKQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZGF0YS50YWJsZSkKCmZQYXRoIDwtICcvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9OZXdFZGl0b3JzL0NhbXBhaWduc1JldmlldzIwMjAvJwpkYXRhRGlyIDwtIHBhc3RlMChmUGF0aCwgIl9kYXRhLyIpCmFuYWx5dGljc0RpciA8LSBwYXN0ZTAoZlBhdGgsICJfYW5hbHl0aWNzLyIpCmhkZnNQYXRoIDwtICdoZGZzOi8vL3VzZXIvZ29yYW5zbS9kZXdpa2lfcmV2aXNpb25zJwoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFNlY3Rpb24gMC4gQ2FtcGFpZ25zIERhdGFzZXQKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLSBsb2FkIENhbXBhaWduIFJlZ2lzdGVyZWQgVXNlcnMgZGF0YXNldApjYW1wYWlnbklEcyA8LSByZWFkLmNzdihwYXN0ZTAoZGF0YURpciwgIl9jYW1wYWlnbklEcy9XTURFX0NhbXBhaWduX1JlZ2lzdGVyZWRfVXNlcnNfSURzLmNzdiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTZWN0aW9uIDEuIERhdGFzZXRzCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gQ29tcG9zZSBmaW5hbCByZXZpc2lvbiBkYXRhc2V0IGZyb20gaGRmczogZGV3aWtpX3JldmlzaW9ucwojIC0gY29weSBzcGxpdHMgZnJvbSBoZGZzIHRvIGxvY2FsIGRhdGFEaXIKc3lzdGVtKHBhc3RlMCgnc3VkbyAtdSBhbmFseXRpY3MtcHJpdmF0ZWRhdGEga2VyYmVyb3MtcnVuLWNvbW1hbmQgYW5hbHl0aWNzLXByaXZhdGVkYXRhIGhkZnMgZGZzIC1scyAnLCAKICAgICAgICAgICAgICBoZGZzUGF0aCwgJyA+ICcsIAogICAgICAgICAgICAgIGRhdGFEaXIsICdmaWxlcy50eHQnKSwgCiAgICAgICB3YWl0ID0gVCkKZmlsZXMgPC0gcmVhZC50YWJsZShwYXN0ZTAoZGF0YURpciwgJ2ZpbGVzLnR4dCcpLCBza2lwID0gMSkKZmlsZXMgPC0gYXMuY2hhcmFjdGVyKGZpbGVzJFY4KVsyOmxlbmd0aChhcy5jaGFyYWN0ZXIoZmlsZXMkVjgpKV0KZmlsZS5yZW1vdmUocGFzdGUwKGRhdGFEaXIsICdmaWxlcy50eHQnKSkKZm9yIChpIGluIDE6bGVuZ3RoKGZpbGVzKSkgewogIHN5c3RlbShwYXN0ZTAoJ3N1ZG8gLXUgYW5hbHl0aWNzLXByaXZhdGVkYXRhIGtlcmJlcm9zLXJ1bi1jb21tYW5kIGFuYWx5dGljcy1wcml2YXRlZGF0YSBoZGZzIGRmcyAtdGV4dCAnLCAKICAgICAgICAgICAgICAgIGZpbGVzW2ldLCAnID4gJywgIAogICAgICAgICAgICAgICAgcGFzdGUwKGRhdGFEaXIsICJkZXdpa2lfcmV2aXNpb25zIiwgaSwgIi5jc3YiKSksIHdhaXQgPSBUKQp9CiMgLSByZWFkIHNwbGl0czogZGV3aWtpX3JldmlzaW9ucwojIC0gbG9hZApsRiA8LSBsaXN0LmZpbGVzKGRhdGFEaXIpCmxGIDwtIGxGW2dyZXBsKCJkZXdpa2lfcmV2aXNpb25zIiwgbEYpXQpkZXdpa2lfcmV2aXNpb25zIDwtIGxhcHBseShsRiwgZnVuY3Rpb24oeCkge2ZyZWFkKHBhc3RlMChkYXRhRGlyLCB4KSwgaGVhZGVyID0gRil9KQojIC0gY29sbGVjdApkZXdpa2lfcmV2aXNpb25zIDwtIHJiaW5kbGlzdChkZXdpa2lfcmV2aXNpb25zKQojIC0gc2NoZW1hCmNvbG5hbWVzKGRld2lraV9yZXZpc2lvbnMpIDwtIGMoJ3VzZXJfaWQnLCAncmVnX3RpbWUnLCAncmV2X3RpbWUnKQoKIyMjIC0tLSBMb2FkIHJlZ2lzdHJhdGlvbiBkYXRhOiBkZXdpa2lfcmVndXNlcnMKZGV3aWtpX3JlZ3VzZXJzIDwtIGZyZWFkKHBhc3RlMChkYXRhRGlyLCAiZGV3aWtpX3JlZ3VzZXJzLmNzdiIpLCBoZWFkZXIgPSBUKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFNlY3Rpb24gMi4gU3RhdGlzdGljcyBhbmQgQW5hbHl0aWNhbCBEYXRhc2V0cwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTdGF0aXN0aWNzIG9uIHVzZXIgcmVnaXN0cmF0aW9ucyBzaW5jZSB0aGUgYmVnaW5uaW5nIG9mIHRpbWUKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIC0gcmVtb3ZlIGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMgZnJvbSBkZXdpa2lfcmVndXNlcnMKY2FtcGFpZ25fcmVnSURTIDwtIHVuaXF1ZShjYW1wYWlnbklEcyR1c2VyX2lkW2NhbXBhaWduSURzJHJlZ2lzdGVyZWQgPT0gMV0pCmRld2lraV9yZWd1c2VycyA8LSBkZXdpa2lfcmVndXNlcnNbIShkZXdpa2lfcmVndXNlcnMkdXNlcl9pZCAlaW4lIGNhbXBhaWduX3JlZ0lEUyksIF0KCiMgLSBzdGF0cwpzdGF0cyA8LSBsaXN0KCkKc3RhdHMkdG90YWxfcmVnaXN0ZXJlZF91c2VycyA8LSBkaW0oZGV3aWtpX3JlZ3VzZXJzKVsxXQp3RWRpdGVkIDwtIHdoaWNoKGRld2lraV9yZWd1c2VycyR1c2VyX2lkICVpbiUgZGV3aWtpX3JldmlzaW9ucyR1c2VyX2lkKQpzdGF0cyR0b3RhbF91c2Vyc193aG9fZWRpdGVkIDwtIGxlbmd0aCh3RWRpdGVkKQoKIyAtIGRpc3RpYnV0aW9uIG9mIGFjY291bnQgYWdlCmRld2lraV9yZWd1c2VycyRyZWdfdGltZSA8LSBhcy5EYXRlKGRld2lraV9yZWd1c2VycyR1c2VyX3JlZ2lzdHJhdGlvbl90aW1lc3RhbXApCmRld2lraV9yZWd1c2VycyRhY2NvdW50X2FnZV93ZWVrcyA8LSBhcy5udW1lcmljKAogIGRpZmZ0aW1lKFN5cy50aW1lKCksCiAgICAgICAgICAgZGV3aWtpX3JlZ3VzZXJzJHJlZ190aW1lLAogICAgICAgICAgIHVuaXRzID0gIndlZWtzIikKKQpkZXdpa2lfcmVndXNlcnMkYWNjb3VudF9hZ2VfeWVhcnMgPSBkZXdpa2lfcmVndXNlcnMkYWNjb3VudF9hZ2Vfd2Vla3MvNTIuMTQyOQoKIyAtIHN0YXRzCnN0YXRzJG1pbl9hY2NvdW50X2FnZV93ZWVrcyA8LSB1bm5hbWUoCiAgc3VtbWFyeShhcy5udW1lcmljKGRld2lraV9yZWd1c2VycyRhY2NvdW50X2FnZV93ZWVrcykpWzFdCiAgKQpzdGF0cyRtYXhfYWNjb3VudF9hZ2Vfd2Vla3MgPC0gdW5uYW1lKAogIHN1bW1hcnkoYXMubnVtZXJpYyhkZXdpa2lfcmVndXNlcnMkYWNjb3VudF9hZ2Vfd2Vla3MpKVs2XQopCnN0YXRzJG1lYW5fYWNjb3VudF9hZ2Vfd2Vla3MgPC0gdW5uYW1lKAogIHN1bW1hcnkoYXMubnVtZXJpYyhkZXdpa2lfcmVndXNlcnMkYWNjb3VudF9hZ2Vfd2Vla3MpKVs0XQopCnN0YXRzJG1lZGlhbl9hY2NvdW50X2FnZV93ZWVrcyA8LSBtZWRpYW4oZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3dlZWtzKQpzdGF0cyRtaW5fYWNjb3VudF9hZ2VfeWVhcnMgPC0gdW5uYW1lKAogIHN1bW1hcnkoYXMubnVtZXJpYyhkZXdpa2lfcmVndXNlcnMkYWNjb3VudF9hZ2VfeWVhcnMpKVsxXQopCnN0YXRzJG1heF9hY2NvdW50X2FnZV95ZWFycyA8LSB1bm5hbWUoCiAgc3VtbWFyeShhcy5udW1lcmljKGRld2lraV9yZWd1c2VycyRhY2NvdW50X2FnZV95ZWFycykpWzZdCikKc3RhdHMkbWVhbl9hY2NvdW50X2FnZV95ZWFycyA8LSB1bm5hbWUoCiAgc3VtbWFyeShhcy5udW1lcmljKGRld2lraV9yZWd1c2VycyRhY2NvdW50X2FnZV95ZWFycykpWzRdCikKc3RhdHMkbWVkaWFuX2FjY291bnRfYWdlX3llYXJzIDwtIG1lZGlhbihkZXdpa2lfcmVndXNlcnMkYWNjb3VudF9hZ2VfeWVhcnMpCnNhdmVSRFMoc3RhdHMsIAogICAgICAgIHBhc3RlMChhbmFseXRpY3NEaXIsICJkZXdpa2lfc3RhdHMuUmRzIikpCgojIC0gc3RvcmUgZGF0YSBmaWxlCnNhdmVSRFMoZGV3aWtpX3JlZ3VzZXJzLCAKICAgICAgICBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiZGV3aWtpX3JlZ3VzZXJzLlJkcyIpKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gU3RhdGlzdGljcyBvbiB1c2VyIHJlZ2lzdHJhdGlvbnMgc2luY2UgMjAxNwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCmRld2lraV9yZWd1c2Vyc18yMDE3IDwtIGRld2lraV9yZWd1c2Vyc1tkZXdpa2lfcmVndXNlcnMkdXNlcl9yZWdpc3RyYXRpb25fdGltZXN0YW1wID49IDIwMTcsIF0KCiMgLSBzdGF0cwpzdGF0c18yMDE3IDwtIGxpc3QoKQpzdGF0c18yMDE3JHRvdGFsX3JlZ2lzdGVyZWRfdXNlcnMgPC0gZGltKGRld2lraV9yZWd1c2Vyc18yMDE3KVsxXQp3RWRpdGVkIDwtIHdoaWNoKGRld2lraV9yZWd1c2Vyc18yMDE3JHVzZXJfaWQgJWluJSBkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWQpCnN0YXRzXzIwMTckdG90YWxfdXNlcnNfd2hvX2VkaXRlZCA8LSBsZW5ndGgod0VkaXRlZCkKc3RhdHNfMjAxNyRtaW5fYWNjb3VudF9hZ2Vfd2Vla3MgPC0gdW5uYW1lKAogIHN1bW1hcnkoYXMubnVtZXJpYyhkZXdpa2lfcmVndXNlcnMkYWNjb3VudF9hZ2Vfd2Vla3MpKVsxXQopCnN0YXRzXzIwMTckbWF4X2FjY291bnRfYWdlX3dlZWtzIDwtIHVubmFtZSgKICBzdW1tYXJ5KGFzLm51bWVyaWMoZGV3aWtpX3JlZ3VzZXJzXzIwMTckYWNjb3VudF9hZ2Vfd2Vla3MpKVs2XQopCnN0YXRzXzIwMTckbWVhbl9hY2NvdW50X2FnZV93ZWVrcyA8LSB1bm5hbWUoCiAgc3VtbWFyeShhcy5udW1lcmljKGRld2lraV9yZWd1c2Vyc18yMDE3JGFjY291bnRfYWdlX3dlZWtzKSlbNF0KKQpzdGF0c18yMDE3JG1lZGlhbl9hY2NvdW50X2FnZV93ZWVrcyA8LSBtZWRpYW4oZGV3aWtpX3JlZ3VzZXJzXzIwMTckYWNjb3VudF9hZ2Vfd2Vla3MpCnN0YXRzXzIwMTckbWluX2FjY291bnRfYWdlX3llYXJzIDwtIHVubmFtZSgKICBzdW1tYXJ5KGFzLm51bWVyaWMoZGV3aWtpX3JlZ3VzZXJzXzIwMTckYWNjb3VudF9hZ2VfeWVhcnMpKVsxXQopCnN0YXRzXzIwMTckbWF4X2FjY291bnRfYWdlX3llYXJzIDwtIHVubmFtZSgKICBzdW1tYXJ5KGFzLm51bWVyaWMoZGV3aWtpX3JlZ3VzZXJzXzIwMTckYWNjb3VudF9hZ2VfeWVhcnMpKVs2XQopCnN0YXRzXzIwMTckbWVhbl9hY2NvdW50X2FnZV95ZWFycyA8LSB1bm5hbWUoCiAgc3VtbWFyeShhcy5udW1lcmljKGRld2lraV9yZWd1c2Vyc18yMDE3JGFjY291bnRfYWdlX3llYXJzKSlbNF0KKQpzdGF0c18yMDE3JG1lZGlhbl9hY2NvdW50X2FnZV95ZWFycyA8LSBtZWRpYW4oZGV3aWtpX3JlZ3VzZXJzXzIwMTckYWNjb3VudF9hZ2VfeWVhcnMpCnNhdmVSRFMoc3RhdHNfMjAxNywgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgImRld2lraV9zdGF0c18yMDE3LlJkcyIpKQoKIyAtIHN0b3JlIGRhdGEgZmlsZQpzYXZlUkRTKGRld2lraV9yZWd1c2Vyc18yMDE3LCAKICAgICAgICBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiZGV3aWtpX3JlZ3VzZXJzXzIwMTcuUmRzIikpCgojIC0gY2xlYW4gdXAKcm0oZGV3aWtpX3JlZ3VzZXJzKTsgcm0oZGV3aWtpX3JlZ3VzZXJzXzIwMTcpOyBnYygpCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTdGF0aXN0aWNzIG9uIHJldmlzaW9ucyBzaW5jZSB0aGUgYmVnaW5uaW5nIG9mIHRpbWUKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIC0gcmVtb3ZlIGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMgZnJvbSBkZXdpa2lfcmV2aXNpb25zCmNhbXBhaWduX3JlZ0lEUyA8LSB1bmlxdWUoY2FtcGFpZ25JRHMkdXNlcl9pZFtjYW1wYWlnbklEcyRyZWdpc3RlcmVkID09IDFdKQpkZXdpa2lfcmV2aXNpb25zIDwtIGRld2lraV9yZXZpc2lvbnNbIShkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWQgJWluJSBjYW1wYWlnbl9yZWdJRFMpLCBdCiMgLSBmb3Igbm9uLXJlZ2lzdGVyaW5nIGNhbXBhaWduczoga2VlcCBvbmx5IHVzZXIgcmV2aXNpb25zIGJlZm9yZSB0aGUgY2FtcGFpZ24gb25zZXQKIyAtIHRoZXJlIGlzIGN1cnJlbnRseSBvbmUgbm9uLXJlZ2lzdGVyaW5nIGNhbXBhaWduIHByZXNlbnQgaW4gY2FtcGFpZ25JRHM6Cm5vbl9yZWdpc3RlcmluZ19jYW1wYWlnbklEcyA8LSBjYW1wYWlnbklEcyAlPiUgCiAgZmlsdGVyKHJlZ2lzdGVyZWQgPT0gMCkKbm9uX3JlZ2lzdGVyaW5nX2NhbXBhaWducyA8LSB1bmlxdWUobm9uX3JlZ2lzdGVyaW5nX2NhbXBhaWduSURzJGNhbXBhaWduKQpub25fcmVnaXN0ZXJpbmdfY2FtcGFpZ25zCiMgLSAib2NjYXNpb25hbF9lZGl0b3JzMjAyMCIKIyAtIHRoZSBjYW1wYWlnbiBvbnNldCBmb3IgIm9jY2FzaW9uYWxfZWRpdG9yczIwMjAiIGlzOgojIC0gMjAyMC8wNS8xNAp3UmVtb3ZlUmV2aXNpb25zIDwtIHdoaWNoKAogIChkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWQgJWluJSBub25fcmVnaXN0ZXJpbmdfY2FtcGFpZ25JRHMkdXNlcl9pZCkgJiAKICAgIChkZXdpa2lfcmV2aXNpb25zJHJldl90aW1lID49ICIyMDIwLTA1LTE0IikKICApCmRld2lraV9yZXZpc2lvbnMgPC0gZGV3aWtpX3JldmlzaW9uc1std1JlbW92ZVJldmlzaW9ucywgXQoKIyAtIHN0YXRpc3RpY3MKc3RhdHNfcmV2aXNpb25zIDwtIGxpc3QoKQpzdGF0c19yZXZpc2lvbnMkdG90YWxfcmV2aXNpb25zIDwtIGRpbShkZXdpa2lfcmV2aXNpb25zKVsxXQoKIyAtIGRpc3RyaWJ1dGlvbiBvZiBudW1iZXIgb2YgcmV2aXNpb25zIHBlciB1c2VyCnJldl9kaXN0IDwtIHRhYmxlKGRld2lraV9yZXZpc2lvbnMkdXNlcl9pZCkKcmV2X2Rpc3QgPC0gYXMuZGF0YS5mcmFtZShyZXZfZGlzdCkKY29sbmFtZXMocmV2X2Rpc3QpIDwtIGMoJ3VzZXJfaWQnLCAncmV2aXNpb25zJykKcmV2X2Rpc3QgPC0gYXJyYW5nZShyZXZfZGlzdCwgZGVzYyhyZXZpc2lvbnMpKQpyZXZfZGlzdCA8LSB0YWJsZShyZXZfZGlzdCRyZXZpc2lvbnMpCnJldl9kaXN0IDwtIGFzLmRhdGEuZnJhbWUocmV2X2Rpc3QpCmNvbG5hbWVzKHJldl9kaXN0KSA8LSBjKCdyZXZpc2lvbnMnLCAndXNlcnMnKQpyZXZfZGlzdCA8LSBhcnJhbmdlKHJldl9kaXN0LCBkZXNjKHVzZXJzKSkKc2F2ZVJEUyhyZXZfZGlzdCwgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgInJldl9kaXN0LlJkcyIpKQpybShyZXZfZGlzdCk7IGdjKCkKCiMgLSBlZGl0IGNsYXNzZXMKZWRpdENsYXNzZXMgPC0gZGV3aWtpX3JldmlzaW9ucyAlPiUgCiAgc2VsZWN0KHVzZXJfaWQpICU+JSAKICBncm91cF9ieSh1c2VyX2lkKSAlPiUgCiAgc3VtbWFyaXNlKHJldmlzaW9ucyA9IG4oKSkKZWRpdEJvdW5kYXJpZXMgPC0gbGlzdCgKICBjKDAsIDEpLCAKICBjKDIsIDUpLAogIGMoNiwgOSksCiAgYygxMCwgNDkpCikKZWRpdENsYXNzZXMkZWRpdENsYXNzIDwtIHNhcHBseShlZGl0Q2xhc3NlcyRyZXZpc2lvbnMsIGZ1bmN0aW9uKHgpIHsKICB3RUMgPC0gc2FwcGx5KGVkaXRCb3VuZGFyaWVzLCBmdW5jdGlvbih5KSB7CiAgICB4ID49IHlbMV0gJiB4IDw9IHlbMl0KICB9KQogIGlmIChzdW0od0VDKSA9PSAwKSB7CiAgICByZXR1cm4oIj4gNTAiKQogIH0gZWxzZSB7CiAgICByZXR1cm4ocGFzdGUwKGVkaXRCb3VuZGFyaWVzW1t3aGljaCh3RUMpXV1bMV0sCiAgICAgICAgICAgICAgICAgICIgLSAiLAogICAgICAgICAgICAgICAgICBlZGl0Qm91bmRhcmllc1tbd2hpY2god0VDKV1dWzJdCiAgICApCiAgICApCiAgfQp9KQplZGl0Q2xhc3NlcyRlZGl0Q2xhc3NbZWRpdENsYXNzZXMkZWRpdENsYXNzID09ICIwIC0gMSJdIDwtICIxIgplZGl0Q2xhc3NlcyA8LSBhcnJhbmdlKGVkaXRDbGFzc2VzLCBkZXNjKHJldmlzaW9ucykpCmVkaXRDbGFzc2VzX2Rpc3QgPC0gdGFibGUoZWRpdENsYXNzZXMkZWRpdENsYXNzKQplZGl0Q2xhc3Nlc19kaXN0IDwtIGFzLmRhdGEuZnJhbWUoZWRpdENsYXNzZXNfZGlzdCkKY29sbmFtZXMoZWRpdENsYXNzZXNfZGlzdCkgPC0gYygiRWRpdCBDbGFzcyIsICJVc2VycyIpCmVkaXRDbGFzc2VzX2Rpc3QkYEVkaXQgQ2xhc3NgIDwtIGZhY3RvcihlZGl0Q2xhc3Nlc19kaXN0JGBFZGl0IENsYXNzYCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCcxJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyIC0gNScsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnNiAtIDknLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzEwIC0gNDknLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJz4gNTAnKSkKZWRpdENsYXNzZXNfZGlzdCA8LSBhcnJhbmdlKGVkaXRDbGFzc2VzX2Rpc3QsIGBFZGl0IENsYXNzYCkKZWRpdENsYXNzZXNfZGlzdCRgJSBVc2Vyc2AgPC0gZWRpdENsYXNzZXNfZGlzdCRVc2Vycy9zdW0oZWRpdENsYXNzZXNfZGlzdCRVc2VycykqMTAwCnNhdmVSRFMoZWRpdENsYXNzZXNfZGlzdCwgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgImVkaXRDbGFzc2VzX2Rpc3QuUmRzIikpCgojIC0gY3VtbXVsYXRpdmUgZWRpdHMgaW4gZGV3aWtpX3JldmlzaW9ucwpzZXRrZXkoZGV3aWtpX3JldmlzaW9ucywgdXNlcl9pZCwgcmV2X3RpbWUpCmRld2lraV9yZXZpc2lvbnMgPC0gZGV3aWtpX3JldmlzaW9uc1tvcmRlcih1c2VyX2lkLCByZXZfdGltZSldCmRld2lraV9yZXZpc2lvbnNbLCBjdW1fcmV2aXNpb25zIDo9IHNlcV9sZW4oLk4pLCBieSA9IHVzZXJfaWRdCmRld2lraV9yZXZpc2lvbnNbLCBjdW1fcmV2aXNpb25zIDo9IHJvd2lkKHVzZXJfaWQpXQpkZXdpa2lfcmV2aXNpb25zJHJlZ190aW1lIDwtIGFzLkRhdGUoZGV3aWtpX3JldmlzaW9ucyRyZWdfdGltZSkKZGV3aWtpX3JldmlzaW9ucyRyZXZfdGltZSA8LSBhcy5EYXRlKGRld2lraV9yZXZpc2lvbnMkcmV2X3RpbWUpCmRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MgPC0gZGlmZnRpbWUoZGV3aWtpX3JldmlzaW9ucyRyZXZfdGltZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXdpa2lfcmV2aXNpb25zJHJlZ190aW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuaXRzID0gIndlZWtzIikKZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFycyA8LSAKICBkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzLzUyLjE0MjkKZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV93ZWVrcyA8LSAKICBhcy5udW1lcmljKGRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MpCmRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnMgPC0gCiAgYXMubnVtZXJpYyhkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzKQpkZXdpa2lfcmV2aXNpb25zJGVkaXRDbGFzcyA8LSBzYXBwbHkoZGV3aWtpX3JldmlzaW9ucyRjdW1fcmV2aXNpb25zLCBmdW5jdGlvbih4KSB7CiAgd0VDIDwtIHNhcHBseShlZGl0Qm91bmRhcmllcywgZnVuY3Rpb24oeSkgewogICAgeCA+PSB5WzFdICYgeCA8PSB5WzJdCiAgfSkKICBpZiAoc3VtKHdFQykgPT0gMCkgewogICAgcmV0dXJuKCI+IDUwIikKICB9IGVsc2UgewogICAgcmV0dXJuKHBhc3RlMChlZGl0Qm91bmRhcmllc1tbd2hpY2god0VDKV1dWzFdLAogICAgICAgICAgICAgICAgICAiIC0gIiwKICAgICAgICAgICAgICAgICAgZWRpdEJvdW5kYXJpZXNbW3doaWNoKHdFQyldXVsyXQogICAgKQogICAgKQogIH0KfSkKZGV3aWtpX3JldmlzaW9ucyA8LSBkZXdpa2lfcmV2aXNpb25zW29yZGVyKHJldl90aW1lKV0KCiMjIyBfX18gTk9URSBfX18KIyAtIFRoZXJlIGFyZSAyOTE0OCBvYnNlcnZhdGlvbnMgd2hlcmUgcmVnX3RpbWUgPiByZXZfdGltZToKc3VtKGRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MgPCAwKQojIyMgX19fIEFDVElPTiBfX18KIyAtIHJlbW92ZSBmcm9tIGRld2lraV9yZXZpc2lvbnM6CncgPC0gd2hpY2goZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV93ZWVrcyA8IDApCmRld2lraV9yZXZpc2lvbnMgPC0gZGV3aWtpX3JldmlzaW9uc1stdywgXQoKIyAtIGludG9kdWNlIGFjY291bnQgYWdlIGluIHdlZWtzIGFuZCB5ZWFycyBjbGFzc2VzCmRld2lraV9yZXZpc2lvbnMkcmV2X3RpbWVfeW0gPC0gc3Vic3RyKGRld2lraV9yZXZpc2lvbnMkcmV2X3RpbWUsIDEsIDcpCmRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnNfY2xhc3MgPC0gCiAgcm91bmQoZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFycykKZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFyc19jbGFzcyA8LSAKICBwYXN0ZTAoZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFyc19jbGFzcywgCiAgICAgICAgICIgLSAiLCAKICAgICAgICAgZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFyc19jbGFzcyArIDEpCgojIC0gc2F2ZSB0aGUgZWxhYm9yYXRlZCB2ZXJzaW9uIG9mIGRld2lraV9yZXZpc2lvbnMKc2F2ZVJEUyhkZXdpa2lfcmV2aXNpb25zLCAKICAgICAgICBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiZGV3aWtpX3JldmlzaW9uc19lbGFib3JhdGVkLlJkcyIpKQoKIyAtIHByb2R1Y2UgZGV3aWtpX3JldmlzaW9uc19vdmVydmlldyBmb3IgdmlzdWFsaXphdGlvbgpkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3IDwtIGRld2lraV9yZXZpc2lvbnMgJT4lIAogIHNlbGVjdChyZXZfdGltZV95bSwgZWRpdENsYXNzLCBhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFyc19jbGFzcykgJT4lIAogIGdyb3VwX2J5KHJldl90aW1lX3ltLCBlZGl0Q2xhc3MsIGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzX2NsYXNzKSAlPiUgCiAgc3VtbWFyaXNlKG5fdXNlcnMgPSBuKCkpCnNhdmVSRFMoZGV3aWtpX3JldmlzaW9uc19vdmVydmlldywgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgImRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXcuUmRzIikpCgojIC0gdXNlcnMgYWN0aXZlIChhdCBsZWFzdCBvbmUgZWRpdCkgYWZ0ZXIKIyAtIHR3byB3ZWVrcywgb25lIG1vbnRoLCBzaXggbW9udGhzLCBhbmQgb25lIHllYXIKdHdvX3dlZWtzID0gMgpvbmVfbW9udGggPSA0LjM0NTI0CnNpeF9tb250aHMgPSAyNi4wNzE1Cm9uZV95ZWFyID0gNTIuMTQyOQphY3RpdmVfdXNlcnMgPC0gbGlzdCgpCmFjdGl2ZV91c2VycyR0d29fd2Vla3MgPC0gCiAgbGVuZ3RoKAogICAgdW5pcXVlKAogICAgICBkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWRbZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV93ZWVrcyA+IHR3b193ZWVrc10KICAgICAgKQogICAgKQphY3RpdmVfdXNlcnMkb25lX21vbnRoIDwtIAogIGxlbmd0aCgKICAgIHVuaXF1ZSgKICAgICAgZGV3aWtpX3JldmlzaW9ucyR1c2VyX2lkW2Rld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MgPiBvbmVfbW9udGhdCiAgICApCiAgKQphY3RpdmVfdXNlcnMkc2l4X21vbnRocyA8LSAKICBsZW5ndGgoCiAgICB1bmlxdWUoCiAgICAgIGRld2lraV9yZXZpc2lvbnMkdXNlcl9pZFtkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzID4gc2l4X21vbnRoc10KICAgICkKICApCmFjdGl2ZV91c2VycyRvbmVfeWVhciA8LSAKICBsZW5ndGgoCiAgICB1bmlxdWUoCiAgICAgIGRld2lraV9yZXZpc2lvbnMkdXNlcl9pZFtkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzID4gb25lX3llYXJdCiAgICApCiAgKQphY3RpdmVfdXNlcnMkdHdvX3dlZWtzX3BfdG90YWxfcmVnaXN0ZXJlZF91c2VycyA8LSAKICBhY3RpdmVfdXNlcnMkdHdvX3dlZWtzL3N0YXRzJHRvdGFsX3JlZ2lzdGVyZWRfdXNlcnMKYWN0aXZlX3VzZXJzJG9uZV9tb250aF9wX3RvdGFsX3JlZ2lzdGVyZWRfdXNlcnMgPC0gCiAgYWN0aXZlX3VzZXJzJG9uZV9tb250aC9zdGF0cyR0b3RhbF9yZWdpc3RlcmVkX3VzZXJzCmFjdGl2ZV91c2VycyRzaXhfbW9udGhzX3BfdG90YWxfcmVnaXN0ZXJlZF91c2VycyA8LSAKICBhY3RpdmVfdXNlcnMkc2l4X21vbnRocy9zdGF0cyR0b3RhbF9yZWdpc3RlcmVkX3VzZXJzCmFjdGl2ZV91c2VycyRvbmVfeWVhcl9wX3RvdGFsX3JlZ2lzdGVyZWRfdXNlcnMgPC0gCiAgYWN0aXZlX3VzZXJzJG9uZV95ZWFyL3N0YXRzJHRvdGFsX3JlZ2lzdGVyZWRfdXNlcnMKYWN0aXZlX3VzZXJzJHR3b193ZWVrc19wX3RvdGFsX3VzZXJzX3dob19lZGl0ZWQgPC0gCiAgYWN0aXZlX3VzZXJzJHR3b193ZWVrcy9zdGF0cyR0b3RhbF91c2Vyc193aG9fZWRpdGVkCmFjdGl2ZV91c2VycyRvbmVfbW9udGhfcF90b3RhbF91c2Vyc193aG9fZWRpdGVkIDwtIAogIGFjdGl2ZV91c2VycyRvbmVfbW9udGgvc3RhdHMkdG90YWxfdXNlcnNfd2hvX2VkaXRlZAphY3RpdmVfdXNlcnMkc2l4X21vbnRoc190b3RhbF91c2Vyc193aG9fZWRpdGVkIDwtIAogIGFjdGl2ZV91c2VycyRzaXhfbW9udGhzL3N0YXRzJHRvdGFsX3VzZXJzX3dob19lZGl0ZWQKYWN0aXZlX3VzZXJzJG9uZV95ZWFyX3BfdG90YWxfdXNlcnNfd2hvX2VkaXRlZCA8LSAKICBhY3RpdmVfdXNlcnMkb25lX3llYXIvc3RhdHMkdG90YWxfdXNlcnNfd2hvX2VkaXRlZApzYXZlUkRTKGFjdGl2ZV91c2VycywgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgImFjdGl2ZV91c2Vycy5SZHMiKSkKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFN0YXRpc3RpY3Mgb24gcmV2aXNpb25zIHNpbmNlIDIwMTcKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpybShkZXdpa2lfcmV2aXNpb25zKTsgZ2MoKQojIC0gcmVhZCBzcGxpdHM6IGRld2lraV9yZXZpc2lvbnMKIyAtIGxvYWQKbEYgPC0gbGlzdC5maWxlcyhkYXRhRGlyKQpsRiA8LSBsRltncmVwbCgiZGV3aWtpX3JldmlzaW9ucyIsIGxGKV0KZGV3aWtpX3JldmlzaW9ucyA8LSBsYXBwbHkobEYsIGZ1bmN0aW9uKHgpIHtmcmVhZChwYXN0ZTAoZGF0YURpciwgeCksIGhlYWRlciA9IEYpfSkKIyAtIGNvbGxlY3QKZGV3aWtpX3JldmlzaW9ucyA8LSByYmluZGxpc3QoZGV3aWtpX3JldmlzaW9ucykKIyAtIHNjaGVtYQpjb2xuYW1lcyhkZXdpa2lfcmV2aXNpb25zKSA8LSBjKCd1c2VyX2lkJywgJ3JlZ190aW1lJywgJ3Jldl90aW1lJykKCiMgLSBmaWx0ZXIgZm9yID49IDIwMTcgb24gdXNlciByZWdpc3RyYXRpb24KZGV3aWtpX3JldmlzaW9ucyA8LSBmaWx0ZXIoZGV3aWtpX3JldmlzaW9ucywgcmVnX3RpbWUgPj0gMjAxNykKZGV3aWtpX3JldmlzaW9ucyA8LSBhcy5kYXRhLnRhYmxlKGRld2lraV9yZXZpc2lvbnMpCgojIC0gcmVtb3ZlIGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMgZnJvbSBkZXdpa2lfcmV2aXNpb25zCmNhbXBhaWduX3JlZ0lEUyA8LSB1bmlxdWUoY2FtcGFpZ25JRHMkdXNlcl9pZFtjYW1wYWlnbklEcyRyZWdpc3RlcmVkID09IDFdKQpkZXdpa2lfcmV2aXNpb25zIDwtIGRld2lraV9yZXZpc2lvbnNbIShkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWQgJWluJSBjYW1wYWlnbl9yZWdJRFMpLCBdCiMgLSBmb3Igbm9uLXJlZ2lzdGVyaW5nIGNhbXBhaWduczoga2VlcCBvbmx5IHVzZXIgcmV2aXNpb25zIGJlZm9yZSB0aGUgY2FtcGFpZ24gb25zZXQKIyAtIHRoZXJlIGlzIGN1cnJlbnRseSBvbmUgbm9uLXJlZ2lzdGVyaW5nIGNhbXBhaWduIHByZXNlbnQgaW4gY2FtcGFpZ25JRHM6Cm5vbl9yZWdpc3RlcmluZ19jYW1wYWlnbklEcyA8LSBjYW1wYWlnbklEcyAlPiUgCiAgZmlsdGVyKHJlZ2lzdGVyZWQgPT0gMCkKbm9uX3JlZ2lzdGVyaW5nX2NhbXBhaWducyA8LSB1bmlxdWUobm9uX3JlZ2lzdGVyaW5nX2NhbXBhaWduSURzJGNhbXBhaWduKQpub25fcmVnaXN0ZXJpbmdfY2FtcGFpZ25zCiMgLSAib2NjYXNpb25hbF9lZGl0b3JzMjAyMCIKIyAtIHRoZSBjYW1wYWlnbiBvbnNldCBmb3IgIm9jY2FzaW9uYWxfZWRpdG9yczIwMjAiIGlzOgojIC0gMjAyMC8wNS8xNAp3UmVtb3ZlUmV2aXNpb25zIDwtIHdoaWNoKAogIChkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWQgJWluJSBub25fcmVnaXN0ZXJpbmdfY2FtcGFpZ25JRHMkdXNlcl9pZCkgJiAKICAgIChkZXdpa2lfcmV2aXNpb25zJHJldl90aW1lID49ICIyMDIwLTA1LTE0IikKKQpkZXdpa2lfcmV2aXNpb25zIDwtIGRld2lraV9yZXZpc2lvbnNbLXdSZW1vdmVSZXZpc2lvbnMsIF0KCiMgLSBzdGF0aXN0aWNzCnN0YXRzX3JldmlzaW9uc18yMDE3IDwtIGxpc3QoKQpzdGF0c19yZXZpc2lvbnNfMjAxNyR0b3RhbF9yZXZpc2lvbnMgPC0gZGltKGRld2lraV9yZXZpc2lvbnMpWzFdCgojIC0gZGlzdHJpYnV0aW9uIG9mIG51bWJlciBvZiByZXZpc2lvbnMgcGVyIHVzZXIKcmV2X2Rpc3RfMjAxNyA8LSB0YWJsZShkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWQpCnJldl9kaXN0XzIwMTcgPC0gYXMuZGF0YS5mcmFtZShyZXZfZGlzdF8yMDE3KQpjb2xuYW1lcyhyZXZfZGlzdF8yMDE3KSA8LSBjKCd1c2VyX2lkJywgJ3JldmlzaW9ucycpCnJldl9kaXN0XzIwMTcgPC0gYXJyYW5nZShyZXZfZGlzdF8yMDE3LCBkZXNjKHJldmlzaW9ucykpCnJldl9kaXN0XzIwMTcgPC0gdGFibGUocmV2X2Rpc3RfMjAxNyRyZXZpc2lvbnMpCnJldl9kaXN0XzIwMTcgPC0gYXMuZGF0YS5mcmFtZShyZXZfZGlzdF8yMDE3KQpjb2xuYW1lcyhyZXZfZGlzdF8yMDE3KSA8LSBjKCdyZXZpc2lvbnMnLCAndXNlcnMnKQpyZXZfZGlzdF8yMDE3IDwtIGFycmFuZ2UocmV2X2Rpc3RfMjAxNywgZGVzYyh1c2VycykpCnNhdmVSRFMocmV2X2Rpc3RfMjAxNywgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgInJldl9kaXN0XzIwMTcuUmRzIikpCnJtKHJldl9kaXN0XzIwMTcpOyBnYygpCgojIC0gZWRpdCBjbGFzc2VzCmVkaXRDbGFzc2VzXzIwMTcgPC0gZGV3aWtpX3JldmlzaW9ucyAlPiUgCiAgc2VsZWN0KHVzZXJfaWQpICU+JSAKICBncm91cF9ieSh1c2VyX2lkKSAlPiUgCiAgc3VtbWFyaXNlKHJldmlzaW9ucyA9IG4oKSkKZWRpdEJvdW5kYXJpZXMgPC0gbGlzdCgKICBjKDAsIDEpLCAKICBjKDIsIDUpLAogIGMoNiwgOSksCiAgYygxMCwgNDkpCikKZWRpdENsYXNzZXNfMjAxNyRlZGl0Q2xhc3MgPC0gc2FwcGx5KGVkaXRDbGFzc2VzXzIwMTckcmV2aXNpb25zLCBmdW5jdGlvbih4KSB7CiAgd0VDIDwtIHNhcHBseShlZGl0Qm91bmRhcmllcywgZnVuY3Rpb24oeSkgewogICAgeCA+PSB5WzFdICYgeCA8PSB5WzJdCiAgfSkKICBpZiAoc3VtKHdFQykgPT0gMCkgewogICAgcmV0dXJuKCI+IDUwIikKICB9IGVsc2UgewogICAgcmV0dXJuKHBhc3RlMChlZGl0Qm91bmRhcmllc1tbd2hpY2god0VDKV1dWzFdLAogICAgICAgICAgICAgICAgICAiIC0gIiwKICAgICAgICAgICAgICAgICAgZWRpdEJvdW5kYXJpZXNbW3doaWNoKHdFQyldXVsyXQogICAgKQogICAgKQogIH0KfSkKZWRpdENsYXNzZXNfMjAxNyRlZGl0Q2xhc3NbZWRpdENsYXNzZXNfMjAxNyRlZGl0Q2xhc3MgPT0gIjAgLSAxIl0gPC0gIjEiCmVkaXRDbGFzc2VzXzIwMTcgPC0gYXJyYW5nZShlZGl0Q2xhc3Nlc18yMDE3LCBkZXNjKHJldmlzaW9ucykpCmVkaXRDbGFzc2VzX2Rpc3RfMjAxNyA8LSB0YWJsZShlZGl0Q2xhc3Nlc18yMDE3JGVkaXRDbGFzcykKZWRpdENsYXNzZXNfZGlzdF8yMDE3IDwtIGFzLmRhdGEuZnJhbWUoZWRpdENsYXNzZXNfZGlzdF8yMDE3KQpjb2xuYW1lcyhlZGl0Q2xhc3Nlc19kaXN0XzIwMTcpIDwtIGMoIkVkaXQgQ2xhc3MiLCAiVXNlcnMiKQplZGl0Q2xhc3Nlc19kaXN0XzIwMTckYEVkaXQgQ2xhc3NgIDwtIGZhY3RvcihlZGl0Q2xhc3Nlc19kaXN0XzIwMTckYEVkaXQgQ2xhc3NgLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoJzEnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzIgLSA1JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc2IC0gOScsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMTAgLSA0OScsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPiA1MCcpKQplZGl0Q2xhc3Nlc19kaXN0XzIwMTcgPC0gYXJyYW5nZShlZGl0Q2xhc3Nlc19kaXN0XzIwMTcsIGBFZGl0IENsYXNzYCkKZWRpdENsYXNzZXNfZGlzdF8yMDE3JGAlIFVzZXJzYCA8LSBlZGl0Q2xhc3Nlc19kaXN0XzIwMTckVXNlcnMvc3VtKGVkaXRDbGFzc2VzX2Rpc3RfMjAxNyRVc2VycykqMTAwCnNhdmVSRFMoZWRpdENsYXNzZXNfZGlzdF8yMDE3LCAKICAgICAgICBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiZWRpdENsYXNzZXNfZGlzdF8yMDE3LlJkcyIpKQoKIyAtIGN1bW11bGF0aXZlIGVkaXRzIGluIGRld2lraV9yZXZpc2lvbnMKc2V0a2V5KGRld2lraV9yZXZpc2lvbnMsIHVzZXJfaWQsIHJldl90aW1lKQpkZXdpa2lfcmV2aXNpb25zIDwtIGRld2lraV9yZXZpc2lvbnNbb3JkZXIodXNlcl9pZCwgcmV2X3RpbWUpXQpkZXdpa2lfcmV2aXNpb25zWywgY3VtX3JldmlzaW9ucyA6PSBzZXFfbGVuKC5OKSwgYnkgPSB1c2VyX2lkXQpkZXdpa2lfcmV2aXNpb25zWywgY3VtX3JldmlzaW9ucyA6PSByb3dpZCh1c2VyX2lkKV0KZGV3aWtpX3JldmlzaW9ucyRyZWdfdGltZSA8LSBhcy5EYXRlKGRld2lraV9yZXZpc2lvbnMkcmVnX3RpbWUpCmRld2lraV9yZXZpc2lvbnMkcmV2X3RpbWUgPC0gYXMuRGF0ZShkZXdpa2lfcmV2aXNpb25zJHJldl90aW1lKQpkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzIDwtIGRpZmZ0aW1lKGRld2lraV9yZXZpc2lvbnMkcmV2X3RpbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGV3aWtpX3JldmlzaW9ucyRyZWdfdGltZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bml0cyA9ICJ3ZWVrcyIpCmRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnMgPC0gCiAgZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV93ZWVrcy81Mi4xNDI5CmRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MgPC0gCiAgYXMubnVtZXJpYyhkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzKQpkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzIDwtIAogIGFzLm51bWVyaWMoZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFycykKZGV3aWtpX3JldmlzaW9ucyRlZGl0Q2xhc3MgPC0gc2FwcGx5KGRld2lraV9yZXZpc2lvbnMkY3VtX3JldmlzaW9ucywgZnVuY3Rpb24oeCkgewogIHdFQyA8LSBzYXBwbHkoZWRpdEJvdW5kYXJpZXMsIGZ1bmN0aW9uKHkpIHsKICAgIHggPj0geVsxXSAmIHggPD0geVsyXQogIH0pCiAgaWYgKHN1bSh3RUMpID09IDApIHsKICAgIHJldHVybigiPiA1MCIpCiAgfSBlbHNlIHsKICAgIHJldHVybihwYXN0ZTAoZWRpdEJvdW5kYXJpZXNbW3doaWNoKHdFQyldXVsxXSwKICAgICAgICAgICAgICAgICAgIiAtICIsCiAgICAgICAgICAgICAgICAgIGVkaXRCb3VuZGFyaWVzW1t3aGljaCh3RUMpXV1bMl0KICAgICkKICAgICkKICB9Cn0pCmRld2lraV9yZXZpc2lvbnMgPC0gZGV3aWtpX3JldmlzaW9uc1tvcmRlcihyZXZfdGltZSldCgojIC0gaW50b2R1Y2UgYWNjb3VudCBhZ2UgaW4gd2Vla3MgYW5kIHllYXJzIGNsYXNzZXMKZGV3aWtpX3JldmlzaW9ucyRyZXZfdGltZV95bSA8LSBzdWJzdHIoZGV3aWtpX3JldmlzaW9ucyRyZXZfdGltZSwgMSwgNykKZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFyc19jbGFzcyA8LSAKICByb3VuZChkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzKQpkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzX2NsYXNzIDwtIAogIHBhc3RlMChkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzX2NsYXNzLCAKICAgICAgICAgIiAtICIsIAogICAgICAgICBkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzX2NsYXNzICsgMSkKCiMgLSBzYXZlIHRoZSBlbGFib3JhdGVkIHZlcnNpb24gb2YgZGV3aWtpX3JldmlzaW9uc18yMDE3IApzYXZlUkRTKGRld2lraV9yZXZpc2lvbnMsIAogICAgICAgIHBhc3RlMChhbmFseXRpY3NEaXIsICJkZXdpa2lfcmV2aXNpb25zXzIwMTdfZWxhYm9yYXRlZC5SZHMiKSkKCiMgLSBwcm9kdWNlIGRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfMjAxNyBmb3IgdmlzdWFsaXphdGlvbgpkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTcgPC0gZGV3aWtpX3JldmlzaW9ucyAlPiUgCiAgc2VsZWN0KHJldl90aW1lX3ltLCBlZGl0Q2xhc3MsIGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzX2NsYXNzKSAlPiUgCiAgZ3JvdXBfYnkocmV2X3RpbWVfeW0sIGVkaXRDbGFzcywgYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnNfY2xhc3MpICU+JSAKICBzdW1tYXJpc2Uobl91c2VycyA9IG4oKSkKc2F2ZVJEUyhkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTcsIAogICAgICAgIHBhc3RlMChhbmFseXRpY3NEaXIsICJkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTcuUmRzIikpCgojIC0gdXNlcnMgYWN0aXZlIChhdCBsZWFzdCBvbmUgZWRpdCkgYWZ0ZXIKIyAtIHR3byB3ZWVrcywgb25lIG1vbnRoLCBzaXggbW9udGhzLCBhbmQgb25lIHllYXIKdHdvX3dlZWtzID0gMgpvbmVfbW9udGggPSA0LjM0NTI0CnNpeF9tb250aHMgPSAyNi4wNzE1Cm9uZV95ZWFyID0gNTIuMTQyOQphY3RpdmVfdXNlcnNfMjAxNyA8LSBsaXN0KCkKYWN0aXZlX3VzZXJzXzIwMTckdHdvX3dlZWtzIDwtIAogIGxlbmd0aCgKICAgIHVuaXF1ZSgKICAgICAgZGV3aWtpX3JldmlzaW9ucyR1c2VyX2lkW2Rld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MgPiB0d29fd2Vla3NdCiAgICApCiAgKQphY3RpdmVfdXNlcnNfMjAxNyRvbmVfbW9udGggPC0gCiAgbGVuZ3RoKAogICAgdW5pcXVlKAogICAgICBkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWRbZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV93ZWVrcyA+IG9uZV9tb250aF0KICAgICkKICApCmFjdGl2ZV91c2Vyc18yMDE3JHNpeF9tb250aHMgPC0gCiAgbGVuZ3RoKAogICAgdW5pcXVlKAogICAgICBkZXdpa2lfcmV2aXNpb25zJHVzZXJfaWRbZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV93ZWVrcyA+IHNpeF9tb250aHNdCiAgICApCiAgKQphY3RpdmVfdXNlcnNfMjAxNyRvbmVfeWVhciA8LSAKICBsZW5ndGgoCiAgICB1bmlxdWUoCiAgICAgIGRld2lraV9yZXZpc2lvbnMkdXNlcl9pZFtkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzID4gb25lX3llYXJdCiAgICApCiAgKQphY3RpdmVfdXNlcnNfMjAxNyR0d29fd2Vla3NfcF90b3RhbF9yZWdpc3RlcmVkX3VzZXJzIDwtIAogIGFjdGl2ZV91c2Vyc18yMDE3JHR3b193ZWVrcy9zdGF0c18yMDE3JHRvdGFsX3JlZ2lzdGVyZWRfdXNlcnMKYWN0aXZlX3VzZXJzXzIwMTckb25lX21vbnRoX3BfdG90YWxfcmVnaXN0ZXJlZF91c2VycyA8LSAKICBhY3RpdmVfdXNlcnNfMjAxNyRvbmVfbW9udGgvc3RhdHNfMjAxNyR0b3RhbF9yZWdpc3RlcmVkX3VzZXJzCmFjdGl2ZV91c2Vyc18yMDE3JHNpeF9tb250aHNfcF90b3RhbF9yZWdpc3RlcmVkX3VzZXJzIDwtIAogIGFjdGl2ZV91c2Vyc18yMDE3JHNpeF9tb250aHMvc3RhdHNfMjAxNyR0b3RhbF9yZWdpc3RlcmVkX3VzZXJzCmFjdGl2ZV91c2Vyc18yMDE3JG9uZV95ZWFyX3BfdG90YWxfcmVnaXN0ZXJlZF91c2VycyA8LSAKICBhY3RpdmVfdXNlcnNfMjAxNyRvbmVfeWVhci9zdGF0c18yMDE3JHRvdGFsX3JlZ2lzdGVyZWRfdXNlcnMKYWN0aXZlX3VzZXJzXzIwMTckdHdvX3dlZWtzX3BfdG90YWxfdXNlcnNfd2hvX2VkaXRlZCA8LSAKICBhY3RpdmVfdXNlcnNfMjAxNyR0d29fd2Vla3Mvc3RhdHNfMjAxNyR0b3RhbF91c2Vyc193aG9fZWRpdGVkCmFjdGl2ZV91c2Vyc18yMDE3JG9uZV9tb250aF9wX3RvdGFsX3VzZXJzX3dob19lZGl0ZWQgPC0gCiAgYWN0aXZlX3VzZXJzXzIwMTckb25lX21vbnRoL3N0YXRzXzIwMTckdG90YWxfdXNlcnNfd2hvX2VkaXRlZAphY3RpdmVfdXNlcnNfMjAxNyRzaXhfbW9udGhzX3RvdGFsX3VzZXJzX3dob19lZGl0ZWQgPC0gCiAgYWN0aXZlX3VzZXJzXzIwMTckc2l4X21vbnRocy9zdGF0c18yMDE3JHRvdGFsX3VzZXJzX3dob19lZGl0ZWQKYWN0aXZlX3VzZXJzXzIwMTckb25lX3llYXJfcF90b3RhbF91c2Vyc193aG9fZWRpdGVkIDwtIAogIGFjdGl2ZV91c2Vyc18yMDE3JG9uZV95ZWFyL3N0YXRzXzIwMTckdG90YWxfdXNlcnNfd2hvX2VkaXRlZApzYXZlUkRTKGFjdGl2ZV91c2Vyc18yMDE3LCAKICAgICAgICBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiYWN0aXZlX3VzZXJzXzIwMTcuUmRzIikpCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBTdGF0aXN0aWNzIG9uIHVzZXIgcmVnaXN0cmF0aW9ucyBmb3IgCiMjIyAtLS0gY2FtcGFpZ24gcmVnaXN0ZXJlZCB1c2VycwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gbG9hZCBDYW1wYWlnbiBSZWdpc3RlcmVkIFVzZXJzIGRhdGFzZXQKY2FtcGFpZ25JRHMgPC0gcmVhZC5jc3YocGFzdGUwKGRhdGFEaXIsICJfY2FtcGFpZ25JRHMvV01ERV9DYW1wYWlnbl9SZWdpc3RlcmVkX1VzZXJzX0lEcy5jc3YiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgojIC0gcmVhZCBzcGxpdHM6IGRld2lraV9yZXZpc2lvbnMKIyAtIGxvYWQKbEYgPC0gbGlzdC5maWxlcyhkYXRhRGlyKQpsRiA8LSBsRltncmVwbCgiZGV3aWtpX3JldmlzaW9ucyIsIGxGKV0KZGV3aWtpX3JldmlzaW9ucyA8LSBsYXBwbHkobEYsIGZ1bmN0aW9uKHgpIHtmcmVhZChwYXN0ZTAoZGF0YURpciwgeCksIGhlYWRlciA9IEYpfSkKIyAtIGNvbGxlY3QKZGV3aWtpX3JldmlzaW9ucyA8LSByYmluZGxpc3QoZGV3aWtpX3JldmlzaW9ucykKIyAtIHNjaGVtYQpjb2xuYW1lcyhkZXdpa2lfcmV2aXNpb25zKSA8LSBjKCd1c2VyX2lkJywgJ3JlZ190aW1lJywgJ3Jldl90aW1lJykKCiMjIyAtLS0gTG9hZCByZWdpc3RyYXRpb24gZGF0YTogZGV3aWtpX3JlZ3VzZXJzCmRld2lraV9yZWd1c2VycyA8LSBmcmVhZChwYXN0ZTAoZGF0YURpciwgImRld2lraV9yZWd1c2Vycy5jc3YiKSwgaGVhZGVyID0gVCkKCiMgLSB3aGljaCBjYW1wYWlnbiByZWdpc3RlcmVkIHVzZXJzIGNhbm5vdCBiZSBmb3VuZCBpbiBkZXdpa2lfcmVndXNlcnMKd05vdEZvdW5kIDwtIHdoaWNoKCEoY2FtcGFpZ25JRHMkdXNlcl9pZFtjYW1wYWlnbklEcyRyZWdpc3RlcmVkID09IDFdICVpbiUgZGV3aWtpX3JlZ3VzZXJzJHVzZXJfaWQpKQpjYW1wYWlnbklEcyRub3RfZm91bmRfaW5fZGV3aWtpX3JlZ3VzZXJzIDwtIDAKY2FtcGFpZ25JRHMkbm90X2ZvdW5kX2luX2Rld2lraV9yZWd1c2Vyc1t3Tm90Rm91bmRdIDwtIDEKIyAtIHN0b3JlIGVsYWJvcmF0ZWQgY2FtcGFpZ25JRHMKd3JpdGUuY3N2KGNhbXBhaWduSURzLCAKICAgICAgICAgIHBhc3RlMChkYXRhRGlyLCAiX2NhbXBhaWduSURzL1dNREVfQ2FtcGFpZ25fUmVnaXN0ZXJlZF9Vc2Vyc19JRHNfRWxhYm9yYXRlZC5jc3YiKSkKCiMgLSBrZWVwIG9ubHkgY2FtcGFpZ24gcmVnaXN0ZXJlZCB1c2VycyBmcm9tIGRld2lraV9yZWd1c2VycwpkaW0oZGV3aWtpX3JlZ3VzZXJzKQpjYW1wYWlnbl9yZWdJRFMgPC0gdW5pcXVlKGNhbXBhaWduSURzJHVzZXJfaWRbY2FtcGFpZ25JRHMkcmVnaXN0ZXJlZCA9PSAxXSkKZGV3aWtpX3JlZ3VzZXJzIDwtIGRld2lraV9yZWd1c2Vyc1soZGV3aWtpX3JlZ3VzZXJzJHVzZXJfaWQgJWluJSBjYW1wYWlnbl9yZWdJRFMpLCBdCmRpbShkZXdpa2lfcmVndXNlcnMpCgojIC0gc3RhdHMKc3RhdHNfY2FtcGFpZ25zIDwtIGxpc3QoKQpzdGF0c19jYW1wYWlnbnMkdG90YWxfcmVnaXN0ZXJlZF91c2VycyA8LSBkaW0oZGV3aWtpX3JlZ3VzZXJzKVsxXQp3RWRpdGVkIDwtIHdoaWNoKGRld2lraV9yZWd1c2VycyR1c2VyX2lkICVpbiUgZGV3aWtpX3JldmlzaW9ucyR1c2VyX2lkKQpzdGF0c19jYW1wYWlnbnMkdG90YWxfdXNlcnNfd2hvX2VkaXRlZCA8LSBsZW5ndGgod0VkaXRlZCkKCiMgLSBkaXN0aWJ1dGlvbiBvZiBhY2NvdW50IGFnZQpkZXdpa2lfcmVndXNlcnMkcmVnX3RpbWUgPC0gYXMuRGF0ZShkZXdpa2lfcmVndXNlcnMkdXNlcl9yZWdpc3RyYXRpb25fdGltZXN0YW1wKQpkZXdpa2lfcmVndXNlcnMkYWNjb3VudF9hZ2Vfd2Vla3MgPC0gYXMubnVtZXJpYygKICBkaWZmdGltZShTeXMudGltZSgpLAogICAgICAgICAgIGRld2lraV9yZWd1c2VycyRyZWdfdGltZSwKICAgICAgICAgICB1bml0cyA9ICJ3ZWVrcyIpCikKZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3llYXJzID0gZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3dlZWtzLzUyLjE0MjkKCiMgLSBzdGF0cwpzdGF0c19jYW1wYWlnbnMkbWluX2FjY291bnRfYWdlX3dlZWtzIDwtIHVubmFtZSgKICBzdW1tYXJ5KGFzLm51bWVyaWMoZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3dlZWtzKSlbMV0KKQpzdGF0c19jYW1wYWlnbnMkbWF4X2FjY291bnRfYWdlX3dlZWtzIDwtIHVubmFtZSgKICBzdW1tYXJ5KGFzLm51bWVyaWMoZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3dlZWtzKSlbNl0KKQpzdGF0c19jYW1wYWlnbnMkbWVhbl9hY2NvdW50X2FnZV93ZWVrcyA8LSB1bm5hbWUoCiAgc3VtbWFyeShhcy5udW1lcmljKGRld2lraV9yZWd1c2VycyRhY2NvdW50X2FnZV93ZWVrcykpWzRdCikKc3RhdHNfY2FtcGFpZ25zJG1lZGlhbl9hY2NvdW50X2FnZV93ZWVrcyA8LSBtZWRpYW4oZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3dlZWtzKQpzdGF0c19jYW1wYWlnbnMkbWluX2FjY291bnRfYWdlX3llYXJzIDwtIHVubmFtZSgKICBzdW1tYXJ5KGFzLm51bWVyaWMoZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3llYXJzKSlbMV0KKQpzdGF0c19jYW1wYWlnbnMkbWF4X2FjY291bnRfYWdlX3llYXJzIDwtIHVubmFtZSgKICBzdW1tYXJ5KGFzLm51bWVyaWMoZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3llYXJzKSlbNl0KKQpzdGF0c19jYW1wYWlnbnMkbWVhbl9hY2NvdW50X2FnZV95ZWFycyA8LSB1bm5hbWUoCiAgc3VtbWFyeShhcy5udW1lcmljKGRld2lraV9yZWd1c2VycyRhY2NvdW50X2FnZV95ZWFycykpWzRdCikKc3RhdHNfY2FtcGFpZ25zJG1lZGlhbl9hY2NvdW50X2FnZV95ZWFycyA8LSBtZWRpYW4oZGV3aWtpX3JlZ3VzZXJzJGFjY291bnRfYWdlX3llYXJzKQpzYXZlUkRTKHN0YXRzX2NhbXBhaWducywgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgImRld2lraV9zdGF0c19jYW1wYWlnbnMuUmRzIikpCgojIC0gc3RvcmUgZGF0YSBmaWxlCnNhdmVSRFMoZGV3aWtpX3JlZ3VzZXJzLCAKICAgICAgICBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiZGV3aWtpX3JlZ3VzZXJzX2NhbXBhaWducy5SZHMiKSkKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFN0YXRpc3RpY3Mgb24gcmV2aXNpb25zIGZvciBjYW1wYWlnbiByZWdpc3RlcmVkIHVzZXJzCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAtIGZpbHRlciBmb3IgY2FtcGFpZ24gcmVnaXN0ZXJlZCB1c2VycyBvbiB1c2VyIHJlZ2lzdHJhdGlvbgpkaW0oZGV3aWtpX3JldmlzaW9ucykKZGV3aWtpX3JldmlzaW9ucyA8LSBmaWx0ZXIoZGV3aWtpX3JldmlzaW9ucywgdXNlcl9pZCAlaW4lIGNhbXBhaWduX3JlZ0lEUykKZGV3aWtpX3JldmlzaW9ucyA8LSBhcy5kYXRhLnRhYmxlKGRld2lraV9yZXZpc2lvbnMpCmRpbShkZXdpa2lfcmV2aXNpb25zKQoKIyAtIGZvciBub24tcmVnaXN0ZXJpbmcgY2FtcGFpZ25zOiBrZWVwIG9ubHkgdXNlciByZXZpc2lvbnMgZm9sbG93aW5nIHRoZSBjYW1wYWlnbiBvbnNldAojIC0gdGhlcmUgaXMgY3VycmVudGx5IG9uZSBub24tcmVnaXN0ZXJpbmcgY2FtcGFpZ24gcHJlc2VudCBpbiBjYW1wYWlnbklEczoKbm9uX3JlZ2lzdGVyaW5nX2NhbXBhaWduSURzIDwtIGNhbXBhaWduSURzICU+JSAKICBmaWx0ZXIocmVnaXN0ZXJlZCA9PSAwKQpub25fcmVnaXN0ZXJpbmdfY2FtcGFpZ25zIDwtIHVuaXF1ZShub25fcmVnaXN0ZXJpbmdfY2FtcGFpZ25JRHMkY2FtcGFpZ24pCm5vbl9yZWdpc3RlcmluZ19jYW1wYWlnbnMKIyAtICJvY2Nhc2lvbmFsX2VkaXRvcnMyMDIwIgojIC0gdGhlIGNhbXBhaWduIG9uc2V0IGZvciAib2NjYXNpb25hbF9lZGl0b3JzMjAyMCIgaXM6CiMgLSAyMDIwLzA1LzE0CndSZW1vdmVSZXZpc2lvbnMgPC0gd2hpY2goCiAgKGRld2lraV9yZXZpc2lvbnMkdXNlcl9pZCAlaW4lIG5vbl9yZWdpc3RlcmluZ19jYW1wYWlnbklEcyR1c2VyX2lkKSAmIAogICAgKGRld2lraV9yZXZpc2lvbnMkcmV2X3RpbWUgPCAiMjAyMC0wNS0xNCIpCikKZGV3aWtpX3JldmlzaW9ucyA8LSBkZXdpa2lfcmV2aXNpb25zWy13UmVtb3ZlUmV2aXNpb25zLCBdCgojIC0gc3RhdGlzdGljcwpzdGF0c19yZXZpc2lvbnNfY2FtcGFpZ25zIDwtIGxpc3QoKQpzdGF0c19yZXZpc2lvbnNfY2FtcGFpZ25zJHRvdGFsX3JldmlzaW9ucyA8LSBkaW0oZGV3aWtpX3JldmlzaW9ucylbMV0KCiMgLSBkaXN0cmlidXRpb24gb2YgbnVtYmVyIG9mIHJldmlzaW9ucyBwZXIgdXNlcgpyZXZfZGlzdF9jYW1wYWlnbnMgPC0gdGFibGUoZGV3aWtpX3JldmlzaW9ucyR1c2VyX2lkKQpyZXZfZGlzdF9jYW1wYWlnbnMgPC0gYXMuZGF0YS5mcmFtZShyZXZfZGlzdF9jYW1wYWlnbnMpCmNvbG5hbWVzKHJldl9kaXN0X2NhbXBhaWducykgPC0gYygndXNlcl9pZCcsICdyZXZpc2lvbnMnKQpyZXZfZGlzdF9jYW1wYWlnbnMgPC0gYXJyYW5nZShyZXZfZGlzdF9jYW1wYWlnbnMsIGRlc2MocmV2aXNpb25zKSkKcmV2X2Rpc3RfY2FtcGFpZ25zIDwtIHRhYmxlKHJldl9kaXN0X2NhbXBhaWducyRyZXZpc2lvbnMpCnJldl9kaXN0X2NhbXBhaWducyA8LSBhcy5kYXRhLmZyYW1lKHJldl9kaXN0X2NhbXBhaWducykKY29sbmFtZXMocmV2X2Rpc3RfY2FtcGFpZ25zKSA8LSBjKCdyZXZpc2lvbnMnLCAndXNlcnMnKQpyZXZfZGlzdF9jYW1wYWlnbnMgPC0gYXJyYW5nZShyZXZfZGlzdF9jYW1wYWlnbnMsIGRlc2ModXNlcnMpKQpzYXZlUkRTKHJldl9kaXN0X2NhbXBhaWducywgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgInJldl9kaXN0X2NhbXBhaWducy5SZHMiKSkKcm0ocmV2X2Rpc3RfY2FtcGFpZ25zKTsgZ2MoKQoKIyAtIGVkaXQgY2xhc3NlcwplZGl0Q2xhc3Nlc19jYW1wYWlnbnMgPC0gZGV3aWtpX3JldmlzaW9ucyAlPiUgCiAgc2VsZWN0KHVzZXJfaWQpICU+JSAKICBncm91cF9ieSh1c2VyX2lkKSAlPiUgCiAgc3VtbWFyaXNlKHJldmlzaW9ucyA9IG4oKSkKZWRpdEJvdW5kYXJpZXMgPC0gbGlzdCgKICBjKDAsIDEpLCAKICBjKDIsIDUpLAogIGMoNiwgOSksCiAgYygxMCwgNDkpCikKZWRpdENsYXNzZXNfY2FtcGFpZ25zJGVkaXRDbGFzcyA8LSBzYXBwbHkoZWRpdENsYXNzZXNfY2FtcGFpZ25zJHJldmlzaW9ucywgZnVuY3Rpb24oeCkgewogIHdFQyA8LSBzYXBwbHkoZWRpdEJvdW5kYXJpZXMsIGZ1bmN0aW9uKHkpIHsKICAgIHggPj0geVsxXSAmIHggPD0geVsyXQogIH0pCiAgaWYgKHN1bSh3RUMpID09IDApIHsKICAgIHJldHVybigiPiA1MCIpCiAgfSBlbHNlIHsKICAgIHJldHVybihwYXN0ZTAoZWRpdEJvdW5kYXJpZXNbW3doaWNoKHdFQyldXVsxXSwKICAgICAgICAgICAgICAgICAgIiAtICIsCiAgICAgICAgICAgICAgICAgIGVkaXRCb3VuZGFyaWVzW1t3aGljaCh3RUMpXV1bMl0KICAgICkKICAgICkKICB9Cn0pCmVkaXRDbGFzc2VzX2NhbXBhaWducyRlZGl0Q2xhc3NbZWRpdENsYXNzZXNfY2FtcGFpZ25zJGVkaXRDbGFzcyA9PSAiMCAtIDEiXSA8LSAiMSIKZWRpdENsYXNzZXNfY2FtcGFpZ25zIDwtIGFycmFuZ2UoZWRpdENsYXNzZXNfY2FtcGFpZ25zLCBkZXNjKHJldmlzaW9ucykpCmVkaXRDbGFzc2VzX2Rpc3RfY2FtcGFpZ25zIDwtIHRhYmxlKGVkaXRDbGFzc2VzX2NhbXBhaWducyRlZGl0Q2xhc3MpCmVkaXRDbGFzc2VzX2Rpc3RfY2FtcGFpZ25zIDwtIGFzLmRhdGEuZnJhbWUoZWRpdENsYXNzZXNfZGlzdF9jYW1wYWlnbnMpCmNvbG5hbWVzKGVkaXRDbGFzc2VzX2Rpc3RfY2FtcGFpZ25zKSA8LSBjKCJFZGl0IENsYXNzIiwgIlVzZXJzIikKZWRpdENsYXNzZXNfZGlzdF9jYW1wYWlnbnMkYEVkaXQgQ2xhc3NgIDwtIGZhY3RvcihlZGl0Q2xhc3Nlc19kaXN0X2NhbXBhaWducyRgRWRpdCBDbGFzc2AsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCcxJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzIgLSA1JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzYgLSA5JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzEwIC0gNDknLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPiA1MCcpKQplZGl0Q2xhc3Nlc19kaXN0X2NhbXBhaWducyA8LSBhcnJhbmdlKGVkaXRDbGFzc2VzX2Rpc3RfY2FtcGFpZ25zLCBgRWRpdCBDbGFzc2ApCmVkaXRDbGFzc2VzX2Rpc3RfY2FtcGFpZ25zJGAlIFVzZXJzYCA8LSBlZGl0Q2xhc3Nlc19kaXN0X2NhbXBhaWducyRVc2Vycy9zdW0oZWRpdENsYXNzZXNfZGlzdF9jYW1wYWlnbnMkVXNlcnMpKjEwMApzYXZlUkRTKGVkaXRDbGFzc2VzX2Rpc3RfY2FtcGFpZ25zLCAKICAgICAgICBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiZWRpdENsYXNzZXNfZGlzdF9jYW1wYWlnbnMuUmRzIikpCgojIC0gY3VtbXVsYXRpdmUgZWRpdHMgaW4gZGV3aWtpX3JldmlzaW9ucwpzZXRrZXkoZGV3aWtpX3JldmlzaW9ucywgdXNlcl9pZCwgcmV2X3RpbWUpCmRld2lraV9yZXZpc2lvbnMgPC0gZGV3aWtpX3JldmlzaW9uc1tvcmRlcih1c2VyX2lkLCByZXZfdGltZSldCmRld2lraV9yZXZpc2lvbnNbLCBjdW1fcmV2aXNpb25zIDo9IHNlcV9sZW4oLk4pLCBieSA9IHVzZXJfaWRdCmRld2lraV9yZXZpc2lvbnNbLCBjdW1fcmV2aXNpb25zIDo9IHJvd2lkKHVzZXJfaWQpXQpkZXdpa2lfcmV2aXNpb25zJHJlZ190aW1lIDwtIGFzLkRhdGUoZGV3aWtpX3JldmlzaW9ucyRyZWdfdGltZSkKZGV3aWtpX3JldmlzaW9ucyRyZXZfdGltZSA8LSBhcy5EYXRlKGRld2lraV9yZXZpc2lvbnMkcmV2X3RpbWUpCmRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MgPC0gZGlmZnRpbWUoZGV3aWtpX3JldmlzaW9ucyRyZXZfdGltZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXdpa2lfcmV2aXNpb25zJHJlZ190aW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuaXRzID0gIndlZWtzIikKZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFycyA8LSAKICBkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzLzUyLjE0MjkKZGV3aWtpX3JldmlzaW9ucyRhY2NvdW50X2FnZV9yZXZfdGltZV93ZWVrcyA8LSAKICBhcy5udW1lcmljKGRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MpCmRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnMgPC0gCiAgYXMubnVtZXJpYyhkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzKQpkZXdpa2lfcmV2aXNpb25zJGVkaXRDbGFzcyA8LSBzYXBwbHkoZGV3aWtpX3JldmlzaW9ucyRjdW1fcmV2aXNpb25zLCBmdW5jdGlvbih4KSB7CiAgd0VDIDwtIHNhcHBseShlZGl0Qm91bmRhcmllcywgZnVuY3Rpb24oeSkgewogICAgeCA+PSB5WzFdICYgeCA8PSB5WzJdCiAgfSkKICBpZiAoc3VtKHdFQykgPT0gMCkgewogICAgcmV0dXJuKCI+IDUwIikKICB9IGVsc2UgewogICAgcmV0dXJuKHBhc3RlMChlZGl0Qm91bmRhcmllc1tbd2hpY2god0VDKV1dWzFdLAogICAgICAgICAgICAgICAgICAiIC0gIiwKICAgICAgICAgICAgICAgICAgZWRpdEJvdW5kYXJpZXNbW3doaWNoKHdFQyldXVsyXQogICAgKQogICAgKQogIH0KfSkKZGV3aWtpX3JldmlzaW9ucyA8LSBkZXdpa2lfcmV2aXNpb25zW29yZGVyKHJldl90aW1lKV0KCiMgLSBpbnRvZHVjZSBhY2NvdW50IGFnZSBpbiB3ZWVrcyBhbmQgeWVhcnMgY2xhc3NlcwpkZXdpa2lfcmV2aXNpb25zJHJldl90aW1lX3ltIDwtIHN1YnN0cihkZXdpa2lfcmV2aXNpb25zJHJldl90aW1lLCAxLCA3KQpkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzX2NsYXNzIDwtIAogIHJvdW5kKGRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnMpCmRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnNfY2xhc3MgPC0gCiAgcGFzdGUwKGRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnNfY2xhc3MsIAogICAgICAgICAiIC0gIiwgCiAgICAgICAgIGRld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfeWVhcnNfY2xhc3MgKyAxKQoKIyAtIHNhdmUgdGhlIGVsYWJvcmF0ZWQgdmVyc2lvbiBvZiBkZXdpa2lfcmV2aXNpb25zX2NhbXBhaWducwpzYXZlUkRTKGRld2lraV9yZXZpc2lvbnMsIAogICAgICAgIHBhc3RlMChhbmFseXRpY3NEaXIsICJkZXdpa2lfcmV2aXNpb25zX2NhbXBhaWduc19lbGFib3JhdGVkLlJkcyIpKQoKCiMgLSBwcm9kdWNlIGRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfY2FtcGFpZ25zIGZvciB2aXN1YWxpemF0aW9uCmRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfY2FtcGFpZ25zIDwtIGRld2lraV9yZXZpc2lvbnMgJT4lIAogIHNlbGVjdChyZXZfdGltZV95bSwgZWRpdENsYXNzLCBhY2NvdW50X2FnZV9yZXZfdGltZV95ZWFyc19jbGFzcykgJT4lIAogIGdyb3VwX2J5KHJldl90aW1lX3ltLCBlZGl0Q2xhc3MsIGFjY291bnRfYWdlX3Jldl90aW1lX3llYXJzX2NsYXNzKSAlPiUgCiAgc3VtbWFyaXNlKG5fdXNlcnMgPSBuKCkpCnNhdmVSRFMoZGV3aWtpX3JldmlzaW9uc19vdmVydmlld19jYW1wYWlnbnMsIAogICAgICAgIHBhc3RlMChhbmFseXRpY3NEaXIsICJkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3X2NhbXBhaWducy5SZHMiKSkKCiMgLSB1c2VycyBhY3RpdmUgKGF0IGxlYXN0IG9uZSBlZGl0KSBhZnRlcgojIC0gdHdvIHdlZWtzLCBvbmUgbW9udGgsIHNpeCBtb250aHMsIGFuZCBvbmUgeWVhcgp0d29fd2Vla3MgPSAyCm9uZV9tb250aCA9IDQuMzQ1MjQKc2l4X21vbnRocyA9IDI2LjA3MTUKb25lX3llYXIgPSA1Mi4xNDI5CmFjdGl2ZV91c2Vyc19jYW1wYWlnbnMgPC0gbGlzdCgpCmFjdGl2ZV91c2Vyc19jYW1wYWlnbnMkdHdvX3dlZWtzIDwtIAogIGxlbmd0aCgKICAgIHVuaXF1ZSgKICAgICAgZGV3aWtpX3JldmlzaW9ucyR1c2VyX2lkW2Rld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MgPiB0d29fd2Vla3NdCiAgICApCiAgKQphY3RpdmVfdXNlcnNfY2FtcGFpZ25zJG9uZV9tb250aCA8LSAKICBsZW5ndGgoCiAgICB1bmlxdWUoCiAgICAgIGRld2lraV9yZXZpc2lvbnMkdXNlcl9pZFtkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzID4gb25lX21vbnRoXQogICAgKQogICkKYWN0aXZlX3VzZXJzX2NhbXBhaWducyRzaXhfbW9udGhzIDwtIAogIGxlbmd0aCgKICAgIHVuaXF1ZSgKICAgICAgZGV3aWtpX3JldmlzaW9ucyR1c2VyX2lkW2Rld2lraV9yZXZpc2lvbnMkYWNjb3VudF9hZ2VfcmV2X3RpbWVfd2Vla3MgPiBzaXhfbW9udGhzXQogICAgKQogICkKYWN0aXZlX3VzZXJzX2NhbXBhaWducyRvbmVfeWVhciA8LSAKICBsZW5ndGgoCiAgICB1bmlxdWUoCiAgICAgIGRld2lraV9yZXZpc2lvbnMkdXNlcl9pZFtkZXdpa2lfcmV2aXNpb25zJGFjY291bnRfYWdlX3Jldl90aW1lX3dlZWtzID4gb25lX3llYXJdCiAgICApCiAgKQphY3RpdmVfdXNlcnNfY2FtcGFpZ25zJHR3b193ZWVrc19wX3RvdGFsX3JlZ2lzdGVyZWRfdXNlcnMgPC0gCiAgYWN0aXZlX3VzZXJzX2NhbXBhaWducyR0d29fd2Vla3Mvc3RhdHNfY2FtcGFpZ25zJHRvdGFsX3JlZ2lzdGVyZWRfdXNlcnMKCmFjdGl2ZV91c2Vyc19jYW1wYWlnbnMkb25lX21vbnRoX3BfdG90YWxfcmVnaXN0ZXJlZF91c2VycyA8LSAKICBhY3RpdmVfdXNlcnNfY2FtcGFpZ25zJG9uZV9tb250aC9zdGF0c19jYW1wYWlnbnMkdG90YWxfcmVnaXN0ZXJlZF91c2VycwoKYWN0aXZlX3VzZXJzX2NhbXBhaWducyRzaXhfbW9udGhzX3BfdG90YWxfcmVnaXN0ZXJlZF91c2VycyA8LSAKICBhY3RpdmVfdXNlcnNfY2FtcGFpZ25zJHNpeF9tb250aHMvc3RhdHNfY2FtcGFpZ25zJHRvdGFsX3JlZ2lzdGVyZWRfdXNlcnMKCmFjdGl2ZV91c2Vyc19jYW1wYWlnbnMkb25lX3llYXJfcF90b3RhbF9yZWdpc3RlcmVkX3VzZXJzIDwtIAogIGFjdGl2ZV91c2Vyc19jYW1wYWlnbnMkb25lX3llYXIvc3RhdHNfY2FtcGFpZ25zJHRvdGFsX3JlZ2lzdGVyZWRfdXNlcnMKCmFjdGl2ZV91c2Vyc19jYW1wYWlnbnMkdHdvX3dlZWtzX3BfdG90YWxfdXNlcnNfd2hvX2VkaXRlZCA8LSAKICBhY3RpdmVfdXNlcnNfY2FtcGFpZ25zJHR3b193ZWVrcy9zdGF0c19jYW1wYWlnbnMkdG90YWxfdXNlcnNfd2hvX2VkaXRlZAoKYWN0aXZlX3VzZXJzX2NhbXBhaWducyRvbmVfbW9udGhfcF90b3RhbF91c2Vyc193aG9fZWRpdGVkIDwtIAogIGFjdGl2ZV91c2Vyc19jYW1wYWlnbnMkb25lX21vbnRoL3N0YXRzX2NhbXBhaWducyR0b3RhbF91c2Vyc193aG9fZWRpdGVkCgphY3RpdmVfdXNlcnNfY2FtcGFpZ25zJHNpeF9tb250aHNfdG90YWxfdXNlcnNfd2hvX2VkaXRlZCA8LSAKICBhY3RpdmVfdXNlcnNfY2FtcGFpZ25zJHNpeF9tb250aHMvc3RhdHNfY2FtcGFpZ25zJHRvdGFsX3VzZXJzX3dob19lZGl0ZWQKCmFjdGl2ZV91c2Vyc19jYW1wYWlnbnMkb25lX3llYXJfcF90b3RhbF91c2Vyc193aG9fZWRpdGVkIDwtIAogIGFjdGl2ZV91c2Vyc19jYW1wYWlnbnMkb25lX3llYXIvc3RhdHNfY2FtcGFpZ25zJHRvdGFsX3VzZXJzX3dob19lZGl0ZWQKCnNhdmVSRFMoYWN0aXZlX3VzZXJzX2NhbXBhaWducywgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgImFjdGl2ZV91c2Vyc19jYW1wYWlnbnMuUmRzIikpCgojIyMgLS0tIEZpbmFsIFJlcG9ydGluZwpwYXN0ZTAoIlByb2Nlc3NpbmcgdG9vazogIiwgZGlmZnRpbWUoU3lzLnRpbWUoKSwgdDEsIHVuaXRzID0gIm1pbnMiKSwgIiBtaW51dGVzLiIpCnJtKGxpc3QgPSBscygpKTsgZ2MoKQpgYGAKCgojIyAxIERhdGEgQW5hbHlzaXMKCkFsbCBzdGF0aXN0aWNzIGFuZCB2aXN1YWxpemF0aW9ucyByZXBvcnRlZCBpbiB0aGUgZm9sbG93aW5nIHNlY3Rpb25zIHJlZmVyIHRvIHRoZSBxdWVzdGlvbnMgZm9ybXVsYXRlZCBpbiB0aGUgW3JlZmVyZW5jZSBQaGFicmljYXRvciB0aWNrZXRdKGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjU2NDMzKS4gCgoqKk5vdGUuKiogQWxsIHJldmlzaW9ucyBtYWRlIGJ5IGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMgd2VyZSByZW1vdmVkIGZyb20gdGhlIHJldmlzaW9uIGRhdGFzZXRzIHVzZWQgaW4gdGhlIHNlY3Rpb25zIGFkZHJlc3NpbmcgdGhlIG9yZ2FuaWMgcmVnaXN0cmF0aW9ucyBhbmQgcmV2aXNpb25zIHNpbmNlIDIwMTcuIEZvciB0aGUgbm9uLXJlZ2lzdGVyaW5nIFdNREUgQmFubmVyIENhbXBhaWducyAoZS5nLiBXTURFIE9jY2FzaW9uYWwgRWRpdG9ycyAyMDIwIGNhbXBhaWduLCB3aGljaCBhZGRyZXNzZWQgdGhlIGFscmVhZHkgZXhpc3RpbmcsIHJlZ2lzdGVyZWQgdXNlcnMgb25seSksIHdlIHJlbW92ZSBhbGwgdGhlIGVkaXRzIG1hZGUgb24gdGhlaXIgYmVoYWxmIF9mb2xsb3dpbmdfIHRoZWlyIGV4cG9zdXJlIHRvIHRoZSByZXNwZWN0aXZlIGNhbXBhaWduLiBMaWtld2lzZSwgaW4gdGhlIGNhbXBhaWducyBkYXRhc2V0cywgd2UgcmVtb3ZlIGFsbCB0aGVpciBlZGl0cyBtYWRlIF9iZWZvcmVfIHRoZWlyIGV4cG9zdXJlIHRvIHRoZSByZXNwZWN0aXZlIGNhbXBhaWduLgoKIyMjIDEuMSBPcmdhbmljIFJlZ2lzdHJhdGlvbnMgYW5kIFJldmlzaW9ucyBzaW5jZSAyMDE3CgojIyMjIDEuMS4xIE9yZ2FuaWMgUmVnaXN0cmF0aW9ucyBzaW5jZSAyMDE3CgoqKlEuKiogV2hhdCBpcyB0aGUgYWdlIG9mIHRoZSBHZXJtYW4gV2lraXBlZGlhIENvbW11bml0eSBpbiB0ZXJtcyBvZiBhY2NvdW50IGFnZT8KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDEwfQpzdGF0c18yMDE3IDwtIHJlYWRSRFMocGFzdGUwKGFuYWx5dGljc0RpciwgJ2Rld2lraV9zdGF0c18yMDE3LlJkcycpKQpgYGAKClRoZSBmb2xsb3dpbmcgc3RhdGlzdGljcyBhbGwgcmVmZXIgdG8gYGRld2lraWAgdXNlciByZWdpc3RyYXRpb25zIHNpbmNlIDIwMTc6CgotIFRoZSB0b3RhbCBudW1iZXIgb2YgcmVnaXN0ZXJlZCB1c2VycyBpcyAqKmByIHN0YXRzXzIwMTckdG90YWxfcmVnaXN0ZXJlZF91c2Vyc2AqKi4KLSBUaGUgdG90YWwgbnVtYmVyIG9mIHJlZ2lzdGVyZWQgdXNlcnMgd2hvIGhhdmUgZXZlciBlZGl0ZWQgaXMgKipgciBzdGF0c18yMDE3JHRvdGFsX3VzZXJzX3dob19lZGl0ZWRgKiouCi0gVGhhdCBtZWFucyB0aGF0ICoqYHIgcm91bmQoc3RhdHNfMjAxNyR0b3RhbF91c2Vyc193aG9fZWRpdGVkL3N0YXRzXzIwMTckdG90YWxfcmVnaXN0ZXJlZF91c2VycyoxMDAsIDIpYCUqKiBvZiByZWdpc3RlcmVkIHVzZXJzIGV2ZXIgZWRpdGVkIGBkZXdpa2lgLgotIFN0YXRpc3RpY3Mgb24gYWNjb3VudCBhZ2UgX2luIHdlZWtzXzogdGhlIG1pbmltdW0gYWNjb3VudCBhZ2UgaXMgKipgciByb3VuZChzdGF0c18yMDE3JG1pbl9hY2NvdW50X2FnZV93ZWVrcywgMilgKiosIHRoZSBtYXhpbXVtIGFjY291bnQgYWdlIGlzICoqYHIgcm91bmQoc3RhdHNfMjAxNyRtYXhfYWNjb3VudF9hZ2Vfd2Vla3MsIDIpYCoqLCB0aGUgbWVhbiBhY2NvdW50IGFnZSBpcyAqKmByIHJvdW5kKHN0YXRzXzIwMTckbWVhbl9hY2NvdW50X2FnZV93ZWVrcywgMilgKiosIGFuZCB0aGUgbWVkaWFuIGFjY291bnQgYWdlcyBpcyAqKmByIHJvdW5kKHN0YXRzXzIwMTckbWVkaWFuX2FjY291bnRfYWdlX3dlZWtzLCAyKWAqKjsKLSB3aGlsZSBfaW4geWVhcnNfLCB0aGF0IHdvdWxkIGJlOiB0aGUgbWluaW11bSBhY2NvdW50IGFnZSBpcyAqKmByIHJvdW5kKHN0YXRzXzIwMTckbWluX2FjY291bnRfYWdlX3llYXJzLCAyKWAqKiwgdGhlIG1heGltdW0gYWNjb3VudCBhZ2UgaXMgKipgciByb3VuZChzdGF0c18yMDE3JG1heF9hY2NvdW50X2FnZV95ZWFycywgMilgKiosIHRoZSBtZWFuIGFjY291bnQgYWdlIGlzICoqYHIgcm91bmQoc3RhdHNfMjAxNyRtZWFuX2FjY291bnRfYWdlX3llYXJzLCAyKWAqKiwgYW5kIHRoZSBtZWRpYW4gYWNjb3VudCBhZ2VzIGlzICoqYHIgcm91bmQoc3RhdHNfMjAxNyRtZWRpYW5fYWNjb3VudF9hZ2VfeWVhcnMsIDIpYCoqLgoKIyMjIyAxLjEuMiBVc2VyIFJldmlzaW9ucyBzaW5jZSAyMDE3CgoqKlEuKiogSG93IG1hbnkgb2YgdGhlbSBlZGl0ZWQgKHNpbmNlIHJlZ2lzdHJhdGlvbiB1bnRpbCAzMHRoIEp1bmUgMjAyMCk6CgotIDEgZWRpdAoKLSAyIHRvIDUgZWRpdHMKCi0gNSB0byA5IGVkaXRzCgotIDEwIHRvIDQ5IGVkaXRzCgotIDUwIG9yIG1vcmUgZWRpdHMKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDEwfQplZGl0Q2xhc3Nlc19kaXN0XzIwMTcgPC0gcmVhZFJEUyhwYXN0ZTAoYW5hbHl0aWNzRGlyLCAnZWRpdENsYXNzZXNfZGlzdF8yMDE3LlJkcycpKQplZGl0Q2xhc3Nlc19kaXN0XzIwMTckYCUgVXNlcnNgIDwtIHJvdW5kKGVkaXRDbGFzc2VzX2Rpc3RfMjAxNyRgJSBVc2Vyc2AsIDIpCmRhdGF0YWJsZShlZGl0Q2xhc3Nlc19kaXN0XzIwMTcpCmBgYAoKKipRLioqIFJldGVudGlvbiByYXRlOiBIb3cgbWFueSBuZXdseSByZWdpc3RlcmVkIHVzZXJzIGFyZSBhY3RpdmUgYWZ0ZXIgKGFjdGl2ZSA9IGF0IGxlYXN0IDEgZWRpdCkKCi0gMiB3ZWVrcyBhZnRlciByZWdpc3RyYXRpb24KLSAxIG1vbnRoIGFmdGVyIHJlZ2lzdHJhdGlvbgotIDYgbW9udGhzIGFmdGVyIHJlZ2lzdHJhdGlvbgotIDEyIG1vbnRocyBhZnRlciByZWdpc3RyYXRpb24KCkhvdyBoaWdoIGlzIHRoZSByZXRlbnRpb24gcmF0ZSBvZiB0aGVzZSBhY3RpdmUgdXNlcnMgY29tcGFyZWQgdG8gdGhlIG51bWJlciBvZiByZWdpc3RyYXRpb25zPwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTB9CmFjdGl2ZV91c2Vyc18yMDE3IDwtIHJlYWRSRFMocGFzdGUwKGFuYWx5dGljc0RpciwgJ2FjdGl2ZV91c2Vyc18yMDE3LlJkcycpKQphY3RpdmVfdXNlcnNfMjAxNyA8LSBkYXRhLmZyYW1lKAogIGBSZXRlbnRpb24gQ2xhc3NgID0gYygnMiB3ZWVrcycsICcxIG1vbnRoJywgJzYgbW9udGhzJywgJzEgeWVhcicpLAogIFVzZXJzID0gYXMubnVtZXJpYyhhY3RpdmVfdXNlcnNfMjAxN1sxOjRdKSwKICBgQXMgJSBvZiByZWdpc3RlcmVkIHVzZXJzYCA9IHJvdW5kKGFzLm51bWVyaWMoYWN0aXZlX3VzZXJzXzIwMTdbNTo4XSksIDIpLAogIGBBcyAlIG9mIHVzZXJzIHdobyBldmVyIGVkaXRlZGAgPSByb3VuZChhcy5udW1lcmljKGFjdGl2ZV91c2Vyc18yMDE3Wzk6MTJdKSwgMiksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEYsIAogIGNoZWNrLm5hbWVzID0gRikKZGF0YXRhYmxlKGFjdGl2ZV91c2Vyc18yMDE3KQpgYGAKCioqUS4qKiBFZGl0IENsYXNzZXMgKGZhY2V0cykgeCBBY2NvdW50IEFnZSBDbGFzc2VzIChncm91cCwgc3RlcDogb25lIHllYXIpIHggVGltZSAoaG9yaXpvbnRhbCkg4oaSIGRvIHdlIG9ic2VydmUgYWx3YXlzIG9uZSBhbmQgdGhlIHNhbWUgZ3JvdXAgb2YgYWN0aXZlIGVkaXRvcnMsIG9yIGRvIHRoZSBuZXdjb21lcnMgam9pbiBpbiB0byBzdGF5IGFjdGl2ZSBlZGl0b3JzPyAtIHN0YXJ0OiAyMDE3LgoKKipOb3RlLioqIEluIHRoZSBmb2xsb3dpbmcgY2hhcnQsIHRoZSBgQWNjb3VudCBBZ2VgIHZhcmlhYmxlIHJlZmVycyB0byB0aGUgdXNlciBhY2NvdW50IGFnZSBhdCB0aGUgbW9tZW50IHdoZW4gYSByZXNwZWN0aXZlIHJldmlzaW9uIHdhcyBtYWRlIGJ5IHRoYXQgdXNlci4gVGFicyByZWZlciB0byBkaWZmZXJlbnQgZWRpdCBjbGFzc2VzLgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTB9CmRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfMjAxNyA8LSByZWFkUkRTKHBhc3RlMChhbmFseXRpY3NEaXIsICdkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTcuUmRzJykpCmRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfMjAxNyA8LSB1bmdyb3VwKGRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfMjAxNykKY29sbmFtZXMoZGV3aWtpX3JldmlzaW9uc19vdmVydmlld18yMDE3KSA8LSBjKCdSZXZpc2lvbiBZZWFyLU1vbnRoJywgJ0VkaXQgQ2xhc3MnLCAnQWNjb3VudCBBZ2UnLCAnVXNlcnMnKQpkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTcgPC0gZmlsdGVyKGRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfMjAxNywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIShgUmV2aXNpb24gWWVhci1Nb250aGAgPT0gIjIwMjAtMDciKSkKZGV3aWtpX3JldmlzaW9uc19vdmVydmlld18yMDE3JGBFZGl0IENsYXNzYFtkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTckYEVkaXQgQ2xhc3NgID09ICIwIC0gMSJdIDwtICIxIgpkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTckYEVkaXQgQ2xhc3NgIDwtIGZhY3RvcihkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTckYEVkaXQgQ2xhc3NgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCcxJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMiAtIDUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc2IC0gOScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzEwIC0gNDknLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc+IDUwJykpCmdncGxvdChkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3XzIwMTcsIAogICAgICAgYWVzKHggPSBgUmV2aXNpb24gWWVhci1Nb250aGAsIAogICAgICAgICAgIHkgPSBVc2VycywKICAgICAgICAgICBncm91cCA9IGBBY2NvdW50IEFnZWAsIAogICAgICAgICAgIGNvbG9yID0gYEFjY291bnQgQWdlYCkpICsgCiAgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KHNpemUgPSAxKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjMzA4RkYzIiwgIiM3MEJBMEEiLCAiI0ZBQkMwQSIsICIjRUQyODA5IikpICsKICBmYWNldF93cmFwKH5gRWRpdCBDbGFzc2AsIG5yb3cgPSA1LCBuY29sID0gMSwgc2NhbGVzPSJmcmVlX3kiKSArIAogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAuOTUsIHZqdXN0ID0gMC4yKSkKYGBgCgojIyMgMS4yICBDYW1wYWlnbiBSZWdpc3RyYXRpb25zIGFuZCBSZXZpc2lvbnMgc2luY2UgMjAxNwoKKipOb3RlLioqIFNvbWUgdXNlcnMgZm91bmQgaW4gdGhlIFdNREUgY2FtcGFpZ24gdXNlciByZWdpc3RyYXRpb24gZGF0YXNldHMgY291bGQgbm90IGJlIG1hdGNoZWQgd2l0aCB0aGUgdXNlciBJRHMgaW4gdGhlIGB3bWYubWVkaWF3aWtpX2hpc3RvcnlgIHRhYmxlLiBUaGUgZm9sbG93aW5nIHRhYmxlIHByZXNlbnRzIGFuIG92ZXJ2aWV3IG9mIGhvdyBtYW55IGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMgYXJlIG1pc3NpbmcgaW4gdGhlIGB3bWYubWVkaWF3aWtpX2hpc3RvcnlgIChzZWU6IFtXaWtpdGVjaCB3bWYubWVkaWFXaWtpX2hpc3RvcnkgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93aWtpdGVjaC53aWtpbWVkaWEub3JnL3dpa2kvQW5hbHl0aWNzL0RhdGFfTGFrZS9FZGl0cy9NZWRpYVdpa2lfaGlzdG9yeSkpLiBBbGwgV01ERSBjYW1wYWlnbiByZWdpc3RlcmVkIHVzZXIgSURzIHdlcmUgY2hlY2tlZCBmb3IgdW5pcXVlbmVzcyBhbmQgY29uZmlybWVkIHRvIGJlIHVuaXF1ZS4gVGhlIHRvdGFsIG51bWJlciBvZiBXTURFIGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMgdGhhdCB3ZXJlIG5vdCBtYXRjaGVkIHRvIHRoZSB1c2VyIElEIGZpZWxkcyAoYGV2ZW50X3VzZXJfaWRgIGZvciByZXZpc2lvbnMsIGFuZCBgdXNlcl9pZGAgZm9yIHJlZ2lzdHJhdGlvbnMpIGluIHRoZSBgd21mLm1lZGlhd2lraV9oaXN0b3J5YCB0YWJsZSBpcyAqKjQ5KiouCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KY2FtcGFpZ25JRHMgPC0gcmVhZC5jc3YocGFzdGUwKGRhdGFEaXIsICJfY2FtcGFpZ25JRHMvV01ERV9DYW1wYWlnbl9SZWdpc3RlcmVkX1VzZXJzX0lEc19FbGFib3JhdGVkLmNzdiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgCiAgICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCm5vdF9mb3VuZCA8LSBhcy5kYXRhLmZyYW1lKAogIHRhYmxlKGNhbXBhaWduSURzJGNhbXBhaWduW2NhbXBhaWduSURzJG5vdF9mb3VuZF9pbl9kZXdpa2lfcmVndXNlcnMgPT0gMV0pCikKY29sbmFtZXMobm90X2ZvdW5kKSA8LSBjKCdDYW1wYWlnbiBDb2RlJywgJ051bS5Vc2VycycpCmRhdGF0YWJsZShub3RfZm91bmQpCmBgYAoKR2l2ZW4gdGhhdCB0aGVyZSBhcmUgKipgciBsZW5ndGgoKGNhbXBhaWduSURzJHVzZXJfaWRbY2FtcGFpZ25JRHMkcmVnaXN0ZXJlZD09MV0pKWAqKiBXTURFIGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMgaW4gdG90YWwsIHRoYXQgbWVhbnMgd2hhdCB3ZSB3aWxsIG5vdCBiZSBhYmxlIHRvIGFuYWx5emUgKipgciByb3VuZCg0OS9sZW5ndGgoY2FtcGFpZ25JRHMkdXNlcl9pZFtjYW1wYWlnbklEcyRyZWdpc3RlcmVkPT0xXSkqMTAwLCAyKWAlKiogb2YgdGhlbS4gVG8ga2VlcCB0aGUgYW5hbHlzaXMgY29uc2lzdGVudCB3aXRoIHRoZSBudW1iZXJzIHByZXZpb3VzbHkgcmVwb3J0ZWQgb24gdXNlciByZWdpc3RyYXRpb25zIGFuZCByZXZpc2lvbnMgc2luY2UgMjAxNywgYWxsIHVzZXIgcmVnaXN0cmF0aW9uIGRhdGEgYXJlIGRlcml2ZWQgZnJvbSB0aGUgY2FtcGFpZ24gcmVnaXN0ZXJlZCB1c2VycyB0aGF0IHdlcmUgbWF0Y2hlZCB3aXRoIHRoZSB1c2VyIElEcyBpbiB0aGUgYHdtZi5tZWRpYXdpa2lfaGlzdG9yeWAgdGFibGUuCgojIyMjIDEuMi4xIENhbXBhaWduIFJlZ2lzdHJhdGlvbnMgc2luY2UgMjAxNwoKKipRLioqIFdoYXQgaXMgdGhlIGFnZSBvZiB0aGUgR2VybWFuIFdpa2lwZWRpYSBDb21tdW5pdHkgaW4gdGVybXMgb2YgYWNjb3VudCBhZ2UgKipmb3IgY2FtcGFpZ24gcmVnaXN0ZXJlZCB1c2VycyoqPwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTB9CmRld2lraV9zdGF0c19jYW1wYWlnbnMgPC0gcmVhZFJEUyhwYXN0ZTAoYW5hbHl0aWNzRGlyLCAnZGV3aWtpX3N0YXRzX2NhbXBhaWducy5SZHMnKSkKYGBgCgpUaGUgZm9sbG93aW5nIHN0YXRpc3RpY3MgYWxsIHJlZmVyIHRvIGBkZXdpa2lgICoqY2FtcGFpZ24qKiB1c2VyIHJlZ2lzdHJhdGlvbnMgc2luY2UgMjAxNzoKCi0gVGhlIHRvdGFsIG51bWJlciBvZiByZWdpc3RlcmVkIHVzZXJzIGlzICoqYHIgZGV3aWtpX3N0YXRzX2NhbXBhaWducyR0b3RhbF9yZWdpc3RlcmVkX3VzZXJzYCoqLgotIFRoZSB0b3RhbCBudW1iZXIgb2YgcmVnaXN0ZXJlZCB1c2VycyB3aG8gaGF2ZSBldmVyIGVkaXRlZCBpcyAqKmByIGRld2lraV9zdGF0c19jYW1wYWlnbnMkdG90YWxfdXNlcnNfd2hvX2VkaXRlZGAqKi4KLSBUaGF0IG1lYW5zIHRoYXQgKipgciByb3VuZChkZXdpa2lfc3RhdHNfY2FtcGFpZ25zJHRvdGFsX3VzZXJzX3dob19lZGl0ZWQvZGV3aWtpX3N0YXRzX2NhbXBhaWducyR0b3RhbF9yZWdpc3RlcmVkX3VzZXJzKjEwMCwgMilgJSoqIG9mIHJlZ2lzdGVyZWQgdXNlcnMgZXZlciBlZGl0ZWQgYGRld2lraWAuCi0gU3RhdGlzdGljcyBvbiBhY2NvdW50IGFnZSBfaW4gd2Vla3NfOiB0aGUgbWluaW11bSBhY2NvdW50IGFnZSBpcyAqKmByIHJvdW5kKGRld2lraV9zdGF0c19jYW1wYWlnbnMkbWluX2FjY291bnRfYWdlX3dlZWtzLCAyKWAqKiwgdGhlIG1heGltdW0gYWNjb3VudCBhZ2UgaXMgKipgciByb3VuZChkZXdpa2lfc3RhdHNfY2FtcGFpZ25zJG1heF9hY2NvdW50X2FnZV93ZWVrcywgMilgKiosIHRoZSBtZWFuIGFjY291bnQgYWdlIGlzICoqYHIgcm91bmQoZGV3aWtpX3N0YXRzX2NhbXBhaWducyRtZWFuX2FjY291bnRfYWdlX3dlZWtzLCAyKWAqKiwgYW5kIHRoZSBtZWRpYW4gYWNjb3VudCBhZ2VzIGlzICoqYHIgcm91bmQoZGV3aWtpX3N0YXRzX2NhbXBhaWducyRtZWRpYW5fYWNjb3VudF9hZ2Vfd2Vla3MsIDIpYCoqOwotIHdoaWxlIF9pbiB5ZWFyc18sIHRoYXQgd291bGQgYmU6IHRoZSBtaW5pbXVtIGFjY291bnQgYWdlIGlzICoqYHIgcm91bmQoZGV3aWtpX3N0YXRzX2NhbXBhaWducyRtaW5fYWNjb3VudF9hZ2VfeWVhcnMsIDIpYCoqLCB0aGUgbWF4aW11bSBhY2NvdW50IGFnZSBpcyAqKmByIHJvdW5kKGRld2lraV9zdGF0c19jYW1wYWlnbnMkbWF4X2FjY291bnRfYWdlX3llYXJzLCAyKWAqKiwgdGhlIG1lYW4gYWNjb3VudCBhZ2UgaXMgKipgciByb3VuZChkZXdpa2lfc3RhdHNfY2FtcGFpZ25zJG1lYW5fYWNjb3VudF9hZ2VfeWVhcnMsIDIpYCoqLCBhbmQgdGhlIG1lZGlhbiBhY2NvdW50IGFnZXMgaXMgKipgciByb3VuZChkZXdpa2lfc3RhdHNfY2FtcGFpZ25zJG1lZGlhbl9hY2NvdW50X2FnZV95ZWFycywgMilgKiouCgojIyMjIDEuMi4yIENhbXBhaWduIFJldmlzaW9ucyBzaW5jZSAyMDE3CgoqKlEuKiogSG93IG1hbnkgb2YgKipjYW1wYWlnbiByZWdpc3RlcmVkIHVzZXJzKiogZWRpdGVkIChzaW5jZSByZWdpc3RyYXRpb24gdW50aWwgMzB0aCBKdW5lIDIwMjApOgoKLSAxIGVkaXQKCi0gMiB0byA1IGVkaXRzCgotIDUgdG8gOSBlZGl0cwoKLSAxMCB0byA0OSBlZGl0cwoKLSA1MCBvciBtb3JlIGVkaXRzCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KZWRpdENsYXNzZXNfZGlzdF9jYW1wYWlnbnMgPC0gcmVhZFJEUyhwYXN0ZTAoYW5hbHl0aWNzRGlyLCAnZWRpdENsYXNzZXNfZGlzdF9jYW1wYWlnbnMuUmRzJykpCmVkaXRDbGFzc2VzX2Rpc3RfY2FtcGFpZ25zJGAlIFVzZXJzYCA8LSByb3VuZChlZGl0Q2xhc3Nlc19kaXN0X2NhbXBhaWducyRgJSBVc2Vyc2AsIDIpCmRhdGF0YWJsZShlZGl0Q2xhc3Nlc19kaXN0X2NhbXBhaWducykKYGBgCgoqKlEuKiogUmV0ZW50aW9uIHJhdGU6IEhvdyBtYW55ICoqY2FtcGFpZ24gcmVnaXN0ZXJlZCoqIHVzZXJzIGFyZSBhY3RpdmUgYWZ0ZXIgKGFjdGl2ZSA9IGF0IGxlYXN0IDEgZWRpdCkKCi0gMiB3ZWVrcyBhZnRlciByZWdpc3RyYXRpb24KLSAxIG1vbnRoIGFmdGVyIHJlZ2lzdHJhdGlvbgotIDYgbW9udGhzIGFmdGVyIHJlZ2lzdHJhdGlvbgotIDEyIG1vbnRocyBhZnRlciByZWdpc3RyYXRpb24KCkhvdyBoaWdoIGlzIHRoZSByZXRlbnRpb24gcmF0ZSBvZiB0aGVzZSBhY3RpdmUgdXNlcnMgY29tcGFyZWQgdG8gdGhlIG51bWJlciBvZiByZWdpc3RyYXRpb25zPwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTB9CmFjdGl2ZV91c2Vyc19jYW1wYWlnbnMgPC0gcmVhZFJEUyhwYXN0ZTAoYW5hbHl0aWNzRGlyLCAnYWN0aXZlX3VzZXJzX2NhbXBhaWducy5SZHMnKSkKYWN0aXZlX3VzZXJzX2NhbXBhaWducyA8LSBkYXRhLmZyYW1lKAogIGBSZXRlbnRpb24gQ2xhc3NgID0gYygnMiB3ZWVrcycsICcxIG1vbnRoJywgJzYgbW9udGhzJywgJzEgeWVhcicpLAogIFVzZXJzID0gYXMubnVtZXJpYyhhY3RpdmVfdXNlcnNfY2FtcGFpZ25zWzE6NF0pLAogIGBBcyAlIG9mIHJlZ2lzdGVyZWQgdXNlcnNgID0gcm91bmQoYXMubnVtZXJpYyhhY3RpdmVfdXNlcnNfY2FtcGFpZ25zWzU6OF0pLCAyKSwKICBgQXMgJSBvZiB1c2VycyB3aG8gZXZlciBlZGl0ZWRgID0gcm91bmQoYXMubnVtZXJpYyhhY3RpdmVfdXNlcnNfY2FtcGFpZ25zWzk6MTJdKSwgMiksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEYsIAogIGNoZWNrLm5hbWVzID0gRikKZGF0YXRhYmxlKGFjdGl2ZV91c2Vyc19jYW1wYWlnbnMpCmBgYAoKKipRLioqIEVkaXQgQ2xhc3NlcyAoZmFjZXRzKSB4IEFjY291bnQgQWdlIENsYXNzZXMgKGdyb3VwLCBzdGVwOiBvbmUgeWVhcikgeCBUaW1lIChob3Jpem9udGFsKSDihpIgZG8gd2Ugb2JzZXJ2ZSBhbHdheXMgb25lIGFuZCB0aGUgc2FtZSBncm91cCBvZiBhY3RpdmUgZWRpdG9ycywgb3IgZG8gdGhlIG5ld2NvbWVycyBqb2luIGluIHRvIHN0YXkgYWN0aXZlIGVkaXRvcnM/IC0gc3RhcnQ6IDIwMTcgKCoqZm9yIGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMqKik6CgoqKk5vdGUuKiogSW4gdGhlIGZvbGxvd2luZyBjaGFydCwgdGhlIGBBY2NvdW50IEFnZWAgdmFyaWFibGUgcmVmZXJzIHRvIHRoZSB1c2VyIGFjY291bnQgYWdlIGF0IHRoZSBtb21lbnQgd2hlbiBhIHJlc3BlY3RpdmUgcmV2aXNpb24gd2FzIG1hZGUgYnkgdGhhdCB1c2VyLiBUYWJzIHJlZmVyIHRvIGRpZmZlcmVudCBlZGl0IGNsYXNzZXMuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KZGV3aWtpX3JldmlzaW9uc19vdmVydmlld19jYW1wYWlnbnMgPC0gcmVhZFJEUyhwYXN0ZTAoYW5hbHl0aWNzRGlyLCAnZGV3aWtpX3JldmlzaW9uc19vdmVydmlld19jYW1wYWlnbnMuUmRzJykpCmRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfY2FtcGFpZ25zIDwtIHVuZ3JvdXAoZGV3aWtpX3JldmlzaW9uc19vdmVydmlld19jYW1wYWlnbnMpCmNvbG5hbWVzKGRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfY2FtcGFpZ25zKSA8LSBjKCdSZXZpc2lvbiBZZWFyLU1vbnRoJywgJ0VkaXQgQ2xhc3MnLCAnQWNjb3VudCBBZ2UnLCAnVXNlcnMnKQpkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3X2NhbXBhaWducyA8LSBmaWx0ZXIoZGV3aWtpX3JldmlzaW9uc19vdmVydmlld19jYW1wYWlnbnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICEoYFJldmlzaW9uIFllYXItTW9udGhgID09ICIyMDIwLTA3IikpCmRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfY2FtcGFpZ25zJGBFZGl0IENsYXNzYFtkZXdpa2lfcmV2aXNpb25zX292ZXJ2aWV3X2NhbXBhaWducyRgRWRpdCBDbGFzc2AgPT0gIjAgLSAxIl0gPC0gIjEiCmRld2lraV9yZXZpc2lvbnNfb3ZlcnZpZXdfY2FtcGFpZ25zJGBFZGl0IENsYXNzYCA8LSBmYWN0b3IoZGV3aWtpX3JldmlzaW9uc19vdmVydmlld19jYW1wYWlnbnMkYEVkaXQgQ2xhc3NgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoJzEnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzIgLSA1JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc2IC0gOScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMTAgLSA0OScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPiA1MCcpKQpnZ3Bsb3QoZGV3aWtpX3JldmlzaW9uc19vdmVydmlld19jYW1wYWlnbnMsIAogICAgICAgYWVzKHggPSBgUmV2aXNpb24gWWVhci1Nb250aGAsIAogICAgICAgICAgIHkgPSBVc2VycywKICAgICAgICAgICBncm91cCA9IGBBY2NvdW50IEFnZWAsIAogICAgICAgICAgIGNvbG9yID0gYEFjY291bnQgQWdlYCkpICsgCiAgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KHNpemUgPSAxKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzMwOEZGMyIsICIjNzBCQTBBIiwgIiNGQUJDMEEiLCAiI0VEMjgwOSIpKSArIAogIGZhY2V0X3dyYXAofmBFZGl0IENsYXNzYCwgbnJvdyA9IDUsIG5jb2wgPSAxLCBzY2FsZXM9ImZyZWVfeSIpICsgCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMC45NSwgdmp1c3QgPSAwLjIpKQpgYGAKCiMjIyAxLjMgQ2FtcGFpZ24gUmVnaXN0cmF0aW9ucyBhbmQgUmV2aXNpb25zOiBPdmVydmlldwoKIyMjIyAxLjMuMSBDYW1wYWlnbiBVc2VyIFJlZ2lzdHJhdGlvbnMgYW5kIFJldmlzaW9ucwoKPiBJIGhhdmUganVzdCBvbmUgbWFqb3IgcmVtYXJrIG9uIHRoZSByZXBvcnQ6IEkgYWxzbyBuZWVkIHJlZ2lzdHJhdGlvbnMgYW5kIHJldmlzaW9ucyBwZXIgY2FtcGFpZ24gdG8gYmUgYWJsZSB0byBjb21wYXJlIHRoZSBjYW1wYWlnbnMuIEFyZSB5b3Ugc3RpbGwgb24gaXQgb3IgZGlkIHlvdSBtaXNzIGl0IG91dD8gKHRoZSBlZGl0IGNsYXNzIGFuZCBhY2NvdW50IGFnZSBjb21wYXJpc29uIGlzIG5vdCBuZWNlc3NhcnkgZm9yIHRoZSBjYW1wYWlnbiBzcGxpdCkuCgpSZWZlcmVuY2U6IFtQaGFiOiBUMjU2NDMzIzYzMjg2MThdKGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjU2NDMzIzYzMjg2MTgpCioqTm90ZS4qKiBUaGUgYHJlY2VudCBlZGl0b3JzYCBjb2x1bW4gcmVwb3J0cyBvbiBudW1iZXIgb2YgY2FtcGFpZ24gcmVnaXN0ZXJlZCB1c2VycyB3aG8gZGlkIGF0IGxlYXN0IG9uZSBlZGl0IGFzIG9mIDMwdGggSnVuZSAyMDIwLCByZWZlcmVuY2U6IFtQaGFiOiBUMjU2NDMzIzYzODU5NzNdKGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjU2NDMzIzYzODU5NzMpCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KY2FtcGFpZ25SZWdzIDwtIHJlYWQuY3N2KHBhc3RlMChhbmFseXRpY3NEaXIsICJjYW1wYWlnblJlZ2lzdHJhdGlvbnNTdW1tYXJ5LmNzdiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmNhbXBhaWduUmV2cyA8LSByZWFkLmNzdihwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiY2FtcGFpZ25SZXZpc2lvbnNTdW1tYXJ5LmNzdiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmNhbXBhaWduc092ZXJ2aWV3IDwtIGxlZnRfam9pbihjYW1wYWlnblJldnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FtcGFpZ25SZWdzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImNhbXBhaWduIikKY2FtcGFpZ25zT3ZlcnZpZXckcmV2X3Blcl9yZWcgPC0gcm91bmQoY2FtcGFpZ25zT3ZlcnZpZXckcmV2aXNpb25zL2NhbXBhaWduc092ZXJ2aWV3JHJlZ2lzdGVyZWQsIDIpCnJlY2VudEVkaXRvcnMgPC0gcmVhZC5jc3YocGFzdGUwKGFuYWx5dGljc0RpciwgInJlY2VudENhbXBhaWduRWRpdG9ycy5jc3YiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpjb2xuYW1lcyhyZWNlbnRFZGl0b3JzKVsyXSA8LSAicmVjZW50IGVkaXRvcnMiCmNhbXBhaWduc092ZXJ2aWV3IDwtIGxlZnRfam9pbihjYW1wYWlnbnNPdmVydmlldywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWNlbnRFZGl0b3JzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImNhbXBhaWduIikKZGF0YXRhYmxlKGNhbXBhaWduc092ZXJ2aWV3KQpgYGAKCiMjIyMgMS4zLjIgQ2FtcGFpZ24gVXNlciBSZXRlbnRpb24KCj4gSSB3b25kZXJlZCBpZiBpdCB3ZXJlIG11Y2ggYWRkaXRpb25hbCB3b3JrIHRvIGNvbXB1dGUgbm90IG9ubHkgIyBvZiByZXZpc2lvbnMgYW5kIHJlZ2lzdHJhdGlvbnMgKGluIHNlY3Rpb24gMS4zKSAsIGJ1dCBhbHNvIHJldGVudGlvbi8gcmV0ZW50aW9uIHJhdGVzIChsaWtlIHlvdSBkaWQgaW4gc2VjdGlvbiAxLjIuMikgcGVyIGNhbXBhaWduPyBJIGd1ZXNzIHRoYXQncyB3aGF0IEBWZXJlbmEgd2FzIGluaXRpYWxseSBhc2tpbmcgZm9yLgoKUmVmZXJlbmNlOiBbUGhhYjogVDI1NjQzMyM2MzM3NzAxXShodHRwczovL3BoYWJyaWNhdG9yLndpa2ltZWRpYS5vcmcvVDI1NjQzMyM2MzM3NzAxKQoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CmFjdGl2ZV91c2Vyc19wZXJfY2FtcGFpZ24gPC0gcmVhZFJEUyhwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiYWN0aXZlX3VzZXJzX3Blcl9jYW1wYWlnbi5SZHMiKSkKYWN0aXZlX3VzZXJzX3Blcl9jYW1wYWlnbiA8LSBhY3RpdmVfdXNlcnNfcGVyX2NhbXBhaWduWywgYygnY2FtcGFpZ24nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICd0d29fd2Vla3MnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdvbmVfbW9udGgnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdzaXhfbW9udGhzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnb25lX3llYXInKV0KY29sbmFtZXMoYWN0aXZlX3VzZXJzX3Blcl9jYW1wYWlnbikgPC0gYygnY2FtcGFpZ24nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyIHdlZWtzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMSBtb250aCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzYgbW9udGhzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMSB5ZWFyJykKZGF0YXRhYmxlKGFjdGl2ZV91c2Vyc19wZXJfY2FtcGFpZ24pCmBgYAoKCiMjIyAxLjQgVHJhaW5pbmcgTW9kdWxlcyBpbiAyMDE4IENhbXBhaWduczogQWN0aXZlIFVzZXJzCgo+IE9mIHRoZSBwZW9wbGUgd2hvIHN0YXJ0ZWQgdHJhaW5pbmcgbW9kdWxlcyAob25ib2FyZGluZyBjb250ZW50IHVzZWQgaW4gMjAxOCBpbiB0aGFuayB5b3UsIHNwcmluZyBhbmQgc3VtbWVyIGNhbXBhaWduKSwgaG93IGlzIHRoZSByYXRlIG9mIHN0aWxsIGFjdGl2ZSB1c2Vycz8KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFfQphY3RpdmVfdXNlcnNfY2FtcGFpZ25zX3RyYWluaW5nXzIwMTggPC0gCiAgcmVhZFJEUyhwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiYWN0aXZlX3VzZXJzX2NhbXBhaWduc190cmFpbmluZy5SZHMiKSkKYWN0aXZlX3VzZXJzX2NhbXBhaWduc190cmFpbmluZ18yMDE4IDwtIGRhdGEuZnJhbWUoCiAgYFJldGVudGlvbiBDbGFzc2AgPSBjKCcyIHdlZWtzJywgJzEgbW9udGgnLCAnNiBtb250aHMnLCAnMSB5ZWFyJyksCiAgVXNlcnMgPSBhcy5udW1lcmljKGFjdGl2ZV91c2Vyc19jYW1wYWlnbnNfdHJhaW5pbmdfMjAxOFsxOjRdKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRiwgCiAgY2hlY2submFtZXMgPSBGKQpkYXRhdGFibGUoYWN0aXZlX3VzZXJzX2NhbXBhaWduc190cmFpbmluZ18yMDE4KQpgYGAKCiMjIDIgQWRkaXRpb25hbCBSZXF1ZXN0cwoKIyMjIDIuMSBPcmdhbmljIEdyb3d0aC9BZ2Ugb2YgQ29tbXVuaXR5CgoqKlJlcXVlc3QqKi4gIk9yZ2FuaWMgR3Jvd3RoLyBBZ2Ugb2YgQ29tbXVuaXR5OiBGb3IgdGhlIHllYXJzIDIwMDEgdG8gMjAxOSB3ZSBuZWVkIGZvciBldmVyeSB5ZWFyIHRoZSBhdmVyYWdlIGFnZSBvZiBhbGwgYWNjb3VudHMgd2hvIGRpZCBhdCBsZWFzdCBvbmUgZWRpdCBpbiB0aGlzIHllYXIuIGFnZSA9IG51bWJlciBvZiB5ZWFycyBzaW5jZSByZWdpc3RyYXRpb24gKCBJIGFtIGF3YXJlIHRoYXQgZm9yIGEgZmV3IGFjY291bnRzIHRoZSByZWdpc3RyYXRpb24gZGF0ZSBjYW4ndCBiZSByZXRyaWV2ZWQgZnJvbSB0aGUgZGF0YWJhc2UuIEJlY2F1c2UgdGhpcyBzaG91bGQgYmUgYSByZWxhdGl2ZWx5IHNtYWxsIG51bWJlciBvZiBhY2NvdW50cyB3ZSBjYW4gbmVnbGVjdCB0aGF0IGhlcmUuKSIgW3JlZmVyZW5jZSBQaGFiIHRpY2tldF0oaHR0cHM6Ly9waGFicmljYXRvci53aWtpbWVkaWEub3JnL1QyNTY0MzMjNjM4NTk3MykKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFfQpkZXdpa2lfcmV2aXNpb25zX2VsYWJvcmF0ZWQgPC0gcmVhZFJEUygifi9XTURFL05ld0VkaXRvcnMvQ2FtcGFpZ25zUmV2aWV3MjAyMC9fYW5hbHl0aWNzL2Rld2lraV9yZXZpc2lvbnNfZWxhYm9yYXRlZC5SZHMiKQpkZXdpa2lfcmV2aXNpb25zX2VsYWJvcmF0ZWQgPC0gZHBseXI6OnNlbGVjdChkZXdpa2lfcmV2aXNpb25zX2VsYWJvcmF0ZWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdfdGltZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldl90aW1lKQpkZXdpa2lfcmV2aXNpb25zX2VsYWJvcmF0ZWQkcmV2X3llYXIgPC0gc3Vic3RyKGRld2lraV9yZXZpc2lvbnNfZWxhYm9yYXRlZCRyZXZfdGltZSwgMSwgNCkKZGV3aWtpX3JldmlzaW9uc19lbGFib3JhdGVkIDwtIGRwbHlyOjpmaWx0ZXIoZGV3aWtpX3JldmlzaW9uc19lbGFib3JhdGVkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV2X3llYXIgIT0gIjIwMjAiKQpkZXdpa2lfcmV2aXNpb25zX2VsYWJvcmF0ZWQkYWNjb3VudF9hZ2UgPC0gZGlmZnRpbWUoZGV3aWtpX3JldmlzaW9uc19lbGFib3JhdGVkJHJldl90aW1lLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRld2lraV9yZXZpc2lvbnNfZWxhYm9yYXRlZCRyZWdfdGltZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bml0cyA9ICJ3ZWVrcyIpCm9uZV95ZWFyID0gNTIuMTQyOQpkZXdpa2lfcmV2aXNpb25zX2VsYWJvcmF0ZWQkYWNjb3VudF9hZ2UgPC0gZGV3aWtpX3JldmlzaW9uc19lbGFib3JhdGVkJGFjY291bnRfYWdlL29uZV95ZWFyCm9yZ2FuaWNHcm93dGggPC0gZGV3aWtpX3JldmlzaW9uc19lbGFib3JhdGVkICU+JSAKICBkcGx5cjo6c2VsZWN0KHJldl95ZWFyLCBhY2NvdW50X2FnZSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShyZXZfeWVhcikgJT4lIAogIHN1bW1hcmlzZShhdmdfYWNjb3VudF9hZ2UgPSBtZWFuKGFjY291bnRfYWdlKSkKZGF0YXRhYmxlKG9yZ2FuaWNHcm93dGgpCmBgYAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTB9Cm9yZ2FuaWNHcm93dGgkcmV2X3llYXIgPC0gYXMubnVtZXJpYyhvcmdhbmljR3Jvd3RoJHJldl95ZWFyKQpvcmdhbmljR3Jvd3RoJGF2Z19hY2NvdW50X2FnZSA8LSBhcy5udW1lcmljKG9yZ2FuaWNHcm93dGgkYXZnX2FjY291bnRfYWdlKQpnZ3Bsb3Qob3JnYW5pY0dyb3d0aCwgCiAgICAgICBhZXMoeCA9IHJldl95ZWFyLCAKICAgICAgICAgICB5ID0gYXZnX2FjY291bnRfYWdlLCAKICAgICAgICAgICBsYWJlbCA9IHJvdW5kKGF2Z19hY2NvdW50X2FnZSwgMikpKSArIAogIGdlb21fcGF0aChzaXplID0gLjI1KSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKAogICAgYnJlYWtzID0gb3JnYW5pY0dyb3d0aCRyZXZfeWVhciwgCiAgICBsYWJlbHMgPSBhcy5jaGFyYWN0ZXIob3JnYW5pY0dyb3d0aCRyZXZfeWVhcikpICsKICBnZW9tX3RleHRfcmVwZWwoKSArIAogIHhsYWIoJ1llYXInKSArIHlsYWIoJ0F2ZXJhZ2UgQWNjb3VudCBBZ2UgKFlycyknKSArCiAgZ2d0aXRsZSgnQXZlcmFnZSBBY291bnQgQWdlIGluIFllYXJzOiBEZXdpa2kgUmV2aXNpb25zIHVudGlsIDIwMTkuJykgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAuOTUsIHZqdXN0ID0gMC4yKSkKYGBgCgoKCgoKCgoKCg==