A. INTRODUCTION

B. ANALYSIS

C. CONCLUSION




A. INTRODUCTION

“We can get creative: we can start to send out messages in a bottle: we can sing, write poetry, produce books and blogs, activities stemming from the realisation that people around us won’t ever fully get us but that others – separated across time and space – might just. The history of art is the record of people who couldn’t find anyone in the vicinity to talk to.” —Alain de Botton

The quote above might represent what I felt when I started producing music, among other things. But this time, I hereby hold myself to face the dire state of my musical project in the form of data. Specifically, the data I’ve got from my Spotify for Artists account. Maybe, I can learn something from it. Perhaps, answering questions such as “On what day of week I have the most streams?”, or “How long since the date released that song streams plummeted?””

There are two csv files. The first one captures a timeline of streams, listeners, and followers; while another offers similar metrics but is grouped by song titles. They’re pretty bare bones. I might need to perform some features engineering.

1. Identify Tasks

Almost halfway in writing this notebook, I realized I was going nowhere since I didn’t really have any specific direction. So here I am, writing lists of questions I want to investigate:

  1. On what day of week I have the most streams, or listeners, or increase in followers?

  2. How many days since the date released that song streams plummeted?

  3. Does releasing music in EP or Single have differences in streams?

  4. What metric has more effect to increase in followers?

2. Data Preparation

First, let’s load some tools to look into the data.

2.a. Timelines

I started releasing music in Spotify in 2020. The 2022 hasn’t ended yet, so let’s create the cut off range from 2020 to 2021.

# I started releasing Syh project in 2020
# filter the date
timelines <- timelines %>% 
  mutate(date = as.Date(date)) %>% 
  filter(date >= "2020-01-01",
         date <= "2021-12-31") %>%
  arrange(date)

Let’s visualize this data.

ggplot(timelines, aes(x = date, y = followers))+
  geom_line(aes(color="Total Followers"))+
  geom_point(aes(y=streams, 
                 color="Streams"
                 )
             )+
  geom_point(aes(y=listeners, 
                 color="Listeners"
                 ),
             )+
  theme(axis.title.y = element_blank(),
        legend.position = "top")+
  scale_color_manual(name="Spotify Metrics",
                     breaks=c("Total Followers","Streams","Listeners"),
                     values=c("Total Followers"="red","Streams"="green", "Listeners"="blue"))

NA
NA

As we can see, the red line that represents followers, rise cumulatively, while the green and blue dots that represent streams and listeners are not cumulative. I need to create cumulative data for them.



timelines_edit <- timelines %>% 
  mutate(cum_listeners = cumsum(listeners),
         cum_streams = cumsum(streams),
         day_of_year = yday(date))
head(timelines_edit, 3)

Wait. I don’t remember releasing music on the first day of the year. Maybe, using day_of_year is a bit off though may be useful as well. I think I need to add a column that counts the number of days since the release date of the first song, not from the first day of the year. To do that, it’s necessary to find at what date should I start to count.

# to count day since the first day of release
start_count_date <- recordings_last5years %>%
  filter(release_date == min(release_date)) %>%  pull(release_date) %>% unique()
start_count_date 
[1] "2020-01-09"

Then use the date we found to count how many integer necessary to create lag in day_count.

n_for_lag <- timelines_edit %>% 
  filter(date == start_count_date) %>% pull(day_of_year) - 1 %>% as.integer()
n_for_lag
[1] 8

Then we use lag to start counting, don’t forget to replace the NA with zero.

timelines_edit <- timelines_edit %>% 
  mutate(count_days = lag(seq_along(date), n=n_for_lag)) %>%
  replace_na(list(count_days =0))

head(timelines_edit, 20)

Let’s visualise the new features.

  ggplot(timelines_edit, aes(x = date, y = followers))+
  geom_line(aes(color="Total Followers"))+
  geom_point(aes(y=cum_streams, 
                 color="Cumulative Streams"
                 )
             )+
  geom_point(aes(y=cum_listeners, 
                 color="Cumulative Listeners"
                 ),
             )+
  geom_line(aes(y=count_days,
                color="Cumulative Day Counts"),
            )+
  theme(axis.title.y = element_blank(),
        legend.position = "right")+
  scale_color_manual(name="Cumulative Spotify Metrics",
                     breaks=c("Total Followers",
                              "Cumulative Streams",
                              "Cumulative Listeners",
                              "Cumulative Day Counts"),
                     values=c("Total Followers"="red",
                              "Cumulative Streams"="green", 
                              "Cumulative Listeners"="blue",
                              "Cumulative Day Counts"="black"))

Before getting too carried away into what cumulative metrics might say, let’s take a step back and extract some more features from the date column.

timelines_edit <- timelines_edit %>%
  mutate(day_of_week = wday(date, label = TRUE, abbr = FALSE),
         )

I’m a bit bothered that the followers feature is cumulative only. I need to create a new feature that allows me to see when was the number follower increased by subtracting the followers column with its lagged version using lag(). Because lag() creates NA, I also need to impute it with 0 immediately.

timelines_edit <- timelines_edit %>%
  mutate(followers_diff = followers - lag(followers)) %>%
  mutate_if(is.numeric, ~replace(., is.na(.),0))
head(timelines_edit)

If I wanted to see how some features in the timelines_edit relate to the songs I released, I need to prepare the recordings_last5years dataframe first.

2.b. Recordings

Some caveats about this recording data: 1. In the Spotify for Artists web where I downloaded the csv for this particular data, the only options for adjusting cut off date were: Last 24 Hours, Last 7 Days, Last 28 Days, Since 2015, and All time. So the metrics of streams and listeners is as up to date as the date when I downloaded the csv which was at 14 April 2022. I cannot limit the date up to the end of 2021 to equal the timeline csv. It’s better to see the data bellow as taking a look from different perspective.

  1. This recordings_last5years dataframe consists of 17 songs, and I want to explain somethings about 2 of them. First, the song Short Left Drive is not mine. Someone somewhere had the unfortunate fate to release music under my Spotify Artist ID tag, and I don’t know how to remove it, and I hope whoever they are might do something about it. Another song I need to explain is the song Froth and Foam. It was previously released in 2019 under my other Artist ID tag, and only in 2021 I decided to move it under my Syh Artist ID tag. I need to decide, should I remove them both from the dataframe? Well, I will remove Short Left Drive but not Froth and Foam.
recordings <- recordings_last5years %>%
  filter(song != "Short Left Drive")

Let’s take a look.

print(recordings)

Because this dataframe lacks information, I want to add how the song was released, whether as EP or as Single.

Before that, I have to arrange it by date.

recordings <- recordings %>% 
  arrange(release_date)
recordings
discography_title <- 
  as.factor(c("Manusial", 
    "Manusial", 
    "Manusial", 
    "Manusial", 
    "Antinatal in C", 
    "Biru Memar", 
    "Biru Memar", 
    "Biru Memar", 
    "Biru Memar", 
    "Biru Memar", 
    "Biru Memar",
    "Nyamuk Anjing",
    "Halulintas",
    "Within an Inch of Losing It",
    "Tuhan Cabut Nyawaku",
    "Froth and Foam"
    ))

discography_form <-
 as.factor(c("EP",
    "EP",
    "EP",
    "EP",
    "Single",
    "EP",
    "EP",
    "EP",
    "EP",
    "EP",
    "EP",
    "Single",
    "Single",
    "Single",
    "Single",
    "Single"))
recordings_edit <- recordings %>%
  mutate(disc_title = discography_title,
         disc_form = discography_form
         ) %>%
  select(-saves) # since it's zero, let's just drop it

recordings_edit

B. ANALYSIS

1. Aggregations and Discoveries

1.a. Task1: “On what day of week I have the most streams, or listeners, or increase in followers?”

We can answer this by aggregating the data with group by and summarize.

metrics_per_dow <- timelines_edit %>%
  group_by(day_of_week) %>%
  summarize(total_streams = sum(streams),
            total_listeners = sum(listeners),
            total_followers_diff = sum(followers_diff))
metrics_per_dow

The table above is how the metrics summarized in day of week. How these metrics spread out will be more easily digestable if we pivot them longer so they’re better prepared to be visualized.

dowtop_streams <- max(metrics_per_dow$total_streams)
dowtop_listeners <- max(metrics_per_dow$total_listeners)
dowtop_folldiff <- max(metrics_per_dow$total_followers_diff)

best_dow_stream <- as.data.frame(metrics_per_dow[metrics_per_dow$total_streams == dowtop_streams, c("day_of_week","total_streams")])
best_dow_listeners <- as.data.frame(metrics_per_dow[metrics_per_dow$total_listeners == dowtop_listeners, c("day_of_week","total_listeners")])
best_dow_folldiff <- as.data.frame(metrics_per_dow[metrics_per_dow$total_followers_diff == dowtop_folldiff, c("day_of_week","total_followers_diff")])

answer_1 <- cbind(best_dow_stream, best_dow_listeners, best_dow_folldiff)
metrics_per_dow_longer <- pivot_longer(data = metrics_per_dow,
                                       cols = c("total_streams","total_listeners","total_followers_diff"),
                                       names_to = "metrics",
                                       values_to = "total")



# pivot day
pivot_day <- pivot_longer(data = answer_1,
                          cols = "day_of_week",
                          values_to = "best_day_of_week") %>% 
  select(best_day_of_week)

# pivot stats
pivot_stats <- pivot_longer(data = answer_1, 
                            cols = c("total_streams","total_listeners","total_followers_diff"), 
                            names_to = "metrics", 
                            values_to = "total") %>% 
  select(metrics, total)

# join
answer_1longer<- bind_cols(pivot_stats, pivot_day)

metrics_per_dow_longer
answer_1longer

Let’s visualize how the metrics achieved in day of week.

ggplot(metrics_per_dow_longer, aes(x=reorder(day_of_week, total), y=total))+
  geom_col(aes(fill=metrics), position = "dodge")+
  scale_fill_manual(name = "Metrics", 
                    labels = c("Total Followers Increase","Total Listeners","Total Streams"), 
                    values = c("total_followers_diff"="red",
                                "total_listeners"="blue",
                                "total_streams"="green"))+
  labs(title = "Metrics Total in Day of Weeks")+
  theme(axis.title.x = element_blank(),
        axis.title.y = element_blank())+
  coord_flip()


ggplot(answer_1longer, aes(x=reorder(best_day_of_week, -total), y=total))+
  geom_col(aes(y=total, fill=metrics), position = "dodge")+
  scale_fill_manual(name = "Metrics", 
                    labels = c("Total Followers Increase","Total Listeners","Total Streams"), 
                    values = c("total_followers_diff"="red",
                                "total_listeners"="blue",
                                "total_streams"="green"))+
  labs(title = "What Day of Week has the Highest Total Metrics?")+
    theme(axis.title.x = element_blank(),
        axis.title.y = element_blank())

So, to answer the task, On what day of week I have the most streams, or listeners, or increase in followers? The data shows that I have the most streams at Friday, most listeners at Tuesday, and most increase in followers at Thursday.

1.b. Task2: “How many days since the date released that song streams plummeted?”

Before we go, let’s make something clear. Streams that happened after the release date of some songs were not exclusively streams from the same said songs. The release though, might triggered the streams of other songs that was also already available.

To answer the task, I have to mark timelines_edit. I’ll create recordings_marker. Then, I’ll split timelines_marked according from each release date up until the day before a new release by using recordings_marker as a guide.

recordings_marker <- recordings_edit %>% select(release_date, song, disc_title, disc_form)

Left join them to create timelines_marked

timelines_marked <- timelines_edit %>%
  left_join(recordings_marker,
            by = c("date"="release_date"))
timelines_marked[!is.na(timelines_marked$song),c("date","streams","song","disc_title")]
date_manusial <- timelines_marked %>% filter(disc_title == "Manusial") %>% pull(date)
date_antinatal_in_c <- timelines_marked %>% filter(disc_title == "Antinatal in C") %>% pull(date)
date_biru_memar <- timelines_marked %>% filter(disc_title == "Biru Memar") %>% pull(date)
date_nyamuk_anjing <- timelines_marked %>% filter(disc_title == "Nyamuk Anjing") %>% pull(date)
date_halulintas <- timelines_marked %>% filter(disc_title == "Halulintas") %>% pull(date)
date_waioli <- timelines_marked %>% filter(disc_title == "Within an Inch of Losing It") %>% pull(date)
date_tc_nyawaku <- timelines_marked %>% filter(disc_title == "Tuhan Cabut Nyawaku") %>% pull(date)
date_froth_n_foam <- timelines_marked %>% filter(disc_title == "Froth and Foam") %>% pull(date)
date_end <- timelines_marked %>% filter(date == max(date)) %>% pull(date)

streams_post_manusial <- timelines_marked %>% 
  filter(date >= date_manusial, 
         date < date_antinatal_in_c) %>% 
  mutate(disc_title = as.character(disc_title)) %>% 
  select(date,streams,disc_title)
Warning in `>=.default`(date, date_manusial) :
  longer object length is not a multiple of shorter object length
streams_post_manusial[,"disc_title"] <- replace_na(data = streams_post_manusial$disc_title,
                                    replace = "Manusial")

streams_post_manusial

It’s getting tedious doing repeat code, maybe I should start trying to create my own function.

func_tlChopper <- function(xdate, ydate, streams_xname)
{
streams_xname <- timelines_marked %>% 
                  filter(date >= xdate, 
                         date < ydate) %>% 
                  mutate(disc_title = as.character(disc_title)) %>% 
                  select(date,streams,disc_title)
disc_varname <- streams_xname %>% select(disc_title) %>% filter(!is.na(disc_title)) %>% slice_head() %>% pull()

streams_xname[,"disc_title"] <- replace_na(data = streams_xname$disc_title,
                                    replace = disc_varname)

streams_xname
}

Let’s chop all the song timelines!

streams_post_antinatalinc <- 
  func_tlChopper(date_antinatal_in_c, date_biru_memar, "streams_post_antinatalinc")
Warning in `<.default`(date, ydate) :
  longer object length is not a multiple of shorter object length
streams_post_birumemar <- 
  func_tlChopper(date_biru_memar, date_nyamuk_anjing, "streams_post_birumemar")
Warning in `>=.default`(date, xdate) :
  longer object length is not a multiple of shorter object length
streams_post_nyamukanjing <- 
  func_tlChopper(date_nyamuk_anjing, date_halulintas, "streams_post_nyamukanjing")

streams_post_halulintas <- 
  func_tlChopper(date_halulintas, date_waioli, "streams_post_halulintas")

streams_post_waioli <- 
  func_tlChopper(date_waioli, date_tc_nyawaku, "streams_post_waioli")

streams_post_tcnyawaku <- 
  func_tlChopper(date_tc_nyawaku, date_froth_n_foam, "streams_post_tcnyawaku")

streams_post_fnf <- 
  func_tlChopper(date_froth_n_foam, date_end, "streams_post_tcnyawaku")
streams_merged <- rbind(streams_post_manusial, 
                     streams_post_antinatalinc,
                     streams_post_birumemar,
                     streams_post_nyamukanjing,
                     streams_post_halulintas,
                     streams_post_waioli,
                     streams_post_tcnyawaku,
                     streams_post_fnf)

range <-  c(as.Date("2019-12-05"), as.Date("2022-02-01"))

ggplot(streams_merged, aes(x=date,y=streams))+
  geom_line(aes(col=disc_title))+
  theme(plot.subtitle = element_text(face = "italic", size = 7),
        axis.title = element_blank(),
        legend.position = "right")+
  geom_vline(data=recordings_marker, 
             aes(xintercept=release_date),
             linetype="dashed")+
  geom_text(data=recordings_marker,
            aes(x=release_date,
                label=song,
                y=50, 
                col=disc_title),
            position = position_jitter(height = 40, width = 3, seed = 9),
            size = 5
            )+
  labs(title = "Timelines of Streams", 
       col="Release Title")+
  scale_x_date(date_labels = "%F", 
               limits = range)

For future uses, I think it’s better if I gather the streams_post into a list.

streams_list <- list(streams_post_manusial, 
                     streams_post_antinatalinc,
                     streams_post_birumemar,
                     streams_post_nyamukanjing,
                     streams_post_halulintas,
                     streams_post_waioli,
                     streams_post_tcnyawaku,
                     streams_post_fnf
                     )

Now let’s count how many days that there were still some streams until no streams at all.

streams_post_manusial_dummy <- streams_post_manusial %>% 
  na_if(y = 0) %>% # replace 0 with na
  fill(streams, .direction = "up") 
day_count_stream_post_manusial_dummy <- streams_post_manusial_dummy  %>% 
  count(is.na(streams))
day_count_stream_post_manusial_dummy

How to interpret the table above? It took 44 days since the release of Manusial EP until there was completely no streams at all that lasted for 19 days before the next release. But if we want to look a graph that illustrates this with actual number of streams, I can create markers for the original streams count, then draw up a line plot.

markers_post_manusial_dummy_plot <-streams_post_manusial_dummy %>% 
  mutate(markers = case_when(is.na(streams) ~ "No One Listened",
                           TRUE ~ "Some Streams Happened"))

Bind the markers column to the original stream chopped data.

streams_post_manusial_complete <- streams_post_manusial %>% 
  cbind(markers = markers_post_manusial_dummy_plot$markers)

Visualize the streams timeline, with markers.

ggplot(streams_post_manusial_complete, aes(x=date,y=streams, col=markers))+
  geom_line()+
  labs(title = "Streams Count after Manusial EP release",
        subtitle = "and before Antinatal in C release")

Now let’s do this for all the other timelines releases. I create 2 functions bellow to help me count days and prepare data for plot.

# for count

func_dayCountStreams <- function(streams_post_df){

# 1 create dummy with na, then fill upward
streams_post_df_dummy <- streams_post_df %>% 
  na_if(y = 0) %>% # replace 0 with na
  fill(streams, .direction = "up") 

# 2 create table how many not na, how many na
day_count_stream_post_df_dummy <- streams_post_df_dummy  %>% 
  count(is.na(streams))

day_count_stream_post_df_dummy
}
# for plot

func_streamsCompletePlot <- function(streams_post_df){

# 1 create dummy with NA, then fill upward    
streams_post_df_dummy <- streams_post_df %>% 
  na_if(y = 0) %>% # replace 0 with na
  fill(streams, .direction = "up") 

# 2 create markers 
markers_post_dummy_plot <-streams_post_df_dummy %>% 
  mutate(markers = case_when(is.na(streams) ~ "No One Listened",
                           TRUE ~ "Some Streams Happened"))
# 3 bind markers to each stream data
markers_post_dummy_plot
streams_post_complete <- streams_post_df %>% 
  cbind(markers = markers_post_dummy_plot$markers)

# 4 create title
title <- streams_post_df %>% select(disc_title) %>% slice_head() %>% pull()
plot_title = paste("Streams Count of", title)

# 5 plot to line plot
ggplot(streams_post_complete, aes(x=date,y=streams))+
  geom_line(aes(col=markers))+
  labs(title = plot_title,
       subtitle = "until the date before subsequent release was available or the end of 2022")+
  scale_x_date(date_labels = "%F")+
  scale_color_manual(name = "Stream Status", 
                    labels = c("Some Streams Happened","No One Listened"),
                    values = c("Some Streams Happened"="blue",
                               "No One Listened"="red"))+
  theme(plot.subtitle = element_text(face = "italic", size = 7),
        axis.title = element_blank())

}

Now let’s apply these functions to streams_list:

lapply(X = streams_list,FUN = func_dayCountStreams)
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

[[8]]
NA
lapply(X = streams_list,FUN = func_streamsCompletePlot)
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

[[8]]

Some notes about the results above: 1. The release date of Tuhan Cabut Nyawaku and Froth and Foam is too close, and the release date of Froth and Foam and the end of 2021 as a cut off range is also too close. The resulting counts and plots were a bit of an outlier so it’s probably wise to exclude them from conclusion. 2. To answer the task, how many days since the date released that song streams plummeted? It could be as short as 40 days, or could be as long as 242 days.

Let’s proceed to the next task.

1.c. Task3: “Does releasing music in EP or Single have differences in streams?”

Take a look.

print(recordings_edit)

Split the data into EPs and Singles

rec_ep <- recordings_edit %>% filter(disc_form == "EP")
rec_single <- recordings_edit %>% filter(disc_form == "Single")
ggplot(recordings_edit, aes(x=reorder(song, streams), y=streams))+
  geom_col(aes(y= streams, fill="Streams"))+
  geom_col(aes(y=listeners, fill="Listeners"))+
    theme(axis.title.x = element_blank(),
          axis.title.y = element_blank(),
        legend.position = "left")+
   coord_flip()+
  scale_fill_manual(name="Song Stats",
                     breaks=c("Streams","Listeners"),
                     values=c("Streams"="green","Listeners"="blue"))+
  labs(title = "Recordings Metrics")


ggplot(rec_ep, aes(x=reorder(song, streams), y=streams))+
  geom_col(aes(y= streams, fill="Streams"))+
  geom_col(aes(y=listeners, fill="Listeners"))+
    theme(axis.title.x = element_blank(),
          axis.title.y = element_blank(),
        legend.position = "left")+
   coord_flip()+
  scale_fill_manual(name="Song Stats",
                     breaks=c("Streams","Listeners"),
                     values=c("Streams"="green","Listeners"="blue"))+
  labs(title = "EP Metrics")


ggplot(rec_single, aes(x=reorder(song, streams), y=streams))+
  geom_col(aes(y= streams, fill="Streams"))+
  geom_col(aes(y=listeners, fill="Listeners"))+
    theme(axis.title.x = element_blank(),
          axis.title.y = element_blank(),
        legend.position = "left")+
   coord_flip()+
  scale_fill_manual(name="Song Stats",
                     breaks=c("Streams","Listeners"),
                     values=c("Streams"="green","Listeners"="blue"))+
  labs(title = "Single Metrics")

To see whether or not there are difference between the two group we need to perform a statistical test. First I need to check the normality of the data, to decide whether or not to use parametric or nonparametric test.

recordings_edit %>% 
  group_by(disc_form) %>% 
  normality(streams)
recordings_edit %>% 
  group_by(disc_form) %>% 
  plot_normality(streams)

recordings_edit[,c(6,3)]  
ggqqplot(recordings_edit[,c(6,3)], x = "streams")

As shown by the table and plot above, the streams among them are quite normally distributed. We can proceed with a parametric test. But witch one? To compare two groups, I can use either Student’s t-Test or Welch’s t-Test. If the data has equal variance, we’ll proceed with Student’s t-Test, else we go with Welch’s t-Test.

If p-value from LeveneTest is lower than 0.05, it means the variance between EP and Single is not equal.

leveneTest(streams ~ disc_form, recordings_edit[,c(6,3)] )
Levene's Test for Homogeneity of Variance (center = median)
      Df F value  Pr(>F)  
group  1  7.8457 0.01415 *
      14                  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Null Hypothesis = There is no difference between the streams count of releases in form of Single or EP.

Alternative Hypothesis = There is a difference between the streams count of releases in form of Single or EP

# load the package 
library(ggstatsplot)
ggbetweenstats(
  data = recordings_edit,
  x = disc_form,
  y = streams,
  type = "parametric",
  var.equal = FALSE
)

Despite the Hedges’g of 0.81 that implies large effect size, and posterior difference of 22.8 streams. With p.value of 0.10 it shows that there was a lack of evidence to reject the null hypothesis. Also, with the bayes factor of 0.07 it shows that there’s no evidence worth than a bare mention to reject the null hypothesis.

I conclude there are no differences whatsoever in term of streams count whether or not I released my music in EP or single.

On to the next task.

1.d. Task4: “What metric has more effect to increase in followers?”

I intend to investigate this relationships by using regression. But, as mentioned in the data preparation phase, followers is a cumulative metric, to compare evenly with followers, I can use the cumulative version of the other metrics. But if I want to use non-cumulative metrics, I can use the followers_diff metric. Maybe I’ll just do both.

tl_cum <- timelines_edit %>% select(followers, cum_listeners, cum_streams, count_days)
tl_noncum <- timelines_edit %>% select(followers_diff, listeners, streams, day_of_year, day_of_week)

First let’s check on assumption that the Response Variable and Explanatory Variables are linear.

plot(tl_cum)

plot(tl_noncum)

They’re pretty linear alright in the cumulative data, but not so in the in the noncumulative data.

Next, we observe whether or not they’re normally distributed.

plot_normality(tl_cum)

plot_normality(tl_noncum)

Looking at the plot, I would say that they’re not normally distributed.

I should stop.

Not just because the data fail to meet the assumptions. But more so that at the time I write this, I’ve had received a counsel from a mentoring group that informed me, when dealing with data such as this? It’s better to approach them with Time Series, not with Linear Regression.

So here I stop. But! Let me take a tiny peek at the model!

tlcum_model <- lm(formula = followers ~ cum_listeners * cum_streams + count_days + 0, 
                  data = tl_cum)
tlnoncum_model <- lm(formula = followers_diff ~ listeners * streams + day_of_week + 0, 
                  data = tl_noncum)

summary(tlcum_model)

Call:
lm(formula = followers ~ cum_listeners * cum_streams + count_days + 
    0, data = tl_cum)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.75922 -0.55944 -0.02806  0.54210  1.39665 

Coefficients:
                            Estimate Std. Error t value Pr(>|t|)    
cum_listeners             -1.352e-02  9.654e-03  -1.401    0.162    
cum_streams                6.091e-03  1.137e-03   5.360 1.12e-07 ***
count_days                 7.847e-03  5.404e-04  14.521  < 2e-16 ***
cum_listeners:cum_streams  4.847e-06  4.976e-06   0.974    0.330    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.7095 on 727 degrees of freedom
Multiple R-squared:  0.9866,    Adjusted R-squared:  0.9865 
F-statistic: 1.339e+04 on 4 and 727 DF,  p-value: < 2.2e-16
summary(tlnoncum_model)

Call:
lm(formula = followers_diff ~ listeners * streams + day_of_week + 
    0, data = tl_noncum)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.03531 -0.01757 -0.00583  0.01225  0.98243 

Coefficients:
                       Estimate Std. Error t value Pr(>|t|)    
listeners             0.0489391  0.0081377   6.014 2.88e-09 ***
streams              -0.0004790  0.0010408  -0.460 0.645494    
day_of_weekSunday     0.0058341  0.0119242   0.489 0.624804    
day_of_weekMonday     0.0048250  0.0119238   0.405 0.685854    
day_of_weekTuesday   -0.0140734  0.0119526  -1.177 0.239413    
day_of_weekWednesday -0.0125728  0.0118150  -1.064 0.287623    
day_of_weekThursday   0.0175744  0.0118126   1.488 0.137249    
day_of_weekFriday     0.0060414  0.0119152   0.507 0.612287    
day_of_weekSaturday  -0.0122483  0.0118711  -1.032 0.302522    
listeners:streams    -0.0009008  0.0002551  -3.532 0.000439 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.1192 on 721 degrees of freedom
Multiple R-squared:  0.0683,    Adjusted R-squared:  0.05538 
F-statistic: 5.286 on 10 and 721 DF,  p-value: 1.544e-07

Cool. The cumulative model’s estimate says, Count of days since the first time my musice released on Spotify has been having more impact than some cumulative streams and/or listeners I’ve scrapped along the way.

While the noncumulative model’s estimate says listeners and Thursday are the only things that matter and have positive impact. And streams has negative estimate value! Bravo! The data doesn’t lie. Perhaps because most streams are coming from my own streams thus it doesn’t really impact any impact in followers. While number of listeners actually broaden and increase the chance of having new followers.

Well, since this linear regression thing isn’t the right approach, I’m gonna take it with a grain–NO! A spoonful of salt and wash it down with alc–Coffee!

Even though I was only going to see the estimates, now I’m tempted to see a little prediction. Here we go blaming the tempation. >“Darn, temptation!”

explanatory_data1_cum <- timelines_edit %>% select(cum_listeners, cum_streams, count_days)
explanatory_data2_cum <- data.frame(cum_listeners = round(seq(242,484,242/731)),
         cum_streams = round(seq(1031,2062 ,1031/731)),
         count_days = 723:(723+731))
explanatory_data_cum <- rbind(explanatory_data1_cum, explanatory_data2_cum)

explanatory_data1_noncum <- timelines_edit %>% select(listeners, streams, day_of_week)
explanatory_data2_noncum <- explanatory_data1_noncum %>% mutate(
  listeners = listeners*2,
  streams = streams*2
)
explanatory_data_noncum <- rbind(explanatory_data1_noncum, explanatory_data2_noncum)
prediction_data_cum <- explanatory_data_cum %>% 
  mutate(followers = predict(tlcum_model, explanatory_data_cum))

prediction_data_noncum <- explanatory_data_noncum %>% 
  mutate(followers_diff = predict(tlnoncum_model, explanatory_data_noncum))
tail(prediction_data_cum, 3)
sum(prediction_data_noncum$followers_diff)
[1] 23.70941
plot(prediction_data_noncum$followers_diff)

cum_plot <- ggplot(tl_cum, aes(x= count_days, y=followers, col=cum_streams, size=cum_listeners))+
  geom_point(shape=1)+
  scale_color_viridis_b(option="D")+
  geom_jitter(data=prediction_data_cum, shape = 2, width = 6, height = 2, alpha=0.3)+
  labs(col="Cumulative Streams",
       size="Cumulative Listeners",
       x = "Count of Days Since First Release Date",
       y = "Number of Followers")

cum_plot


noncum_plot <- ggplot(tl_noncum, aes(x= listeners, y=followers_diff, col=streams))+
  geom_point(shape=1)+
  scale_color_viridis_c(option="D")+
  geom_jitter(data=prediction_data_noncum, height = 0.05, width = 3, alpha=0.3, shape = 2)+
  labs(col="Streams",
       x = "Number of Listeners",
       y = "Increas/Decrease in Followers")+
  facet_wrap(vars(day_of_week), scales = "free")


noncum_plot

Perhap’s that was not the right approach, but, did I have fun while practicing data science along the way? I did.

Let’s look at the plot. It’s quite sane and linear with the cumulative one, but a bit funky with the noncumulative one–what a wild wednesday that plot! Still, both results basically produced twice the Response Variable of the original data set since I merely doubled the numeric Explanatory Variables in both of them.

Welp, since I don’t yet to know how to do Time Series, and let’s say the regression above isn’t the right approach. Maybe at least we can look at the good old correlation bellow. We know the popular saying that says “correlation does not imply causation”, but we too need to acknowledge that, “there is no correlation without causation somewhere”–there’s probably a common cause somewhere upstream.

plot_correlation(tl_cum)

plot_correlation(tl_noncum)

Followers correlates most with cumulative streams and count of days equally on 0.96. While, followers difference correlates most with listeners. Not so different with the estimate coefficients when I called summary on the regression models before.

2. Results

2.a. “On what day of week I have the most streams, or listeners, or increase in followers?”

The data shows that I have the most streams at Friday, most listeners at Tuesday, and most increase in followers at Thursday.

Metrics Day of Week
Streams Friday
Listeners Tuesday
Increase in Followers Thursday

2.b. “How many days since the date released that song streams plummeted?”

It could be as short as 40 days, or could be as long as 242 days.

Count of Days Streaming Still Happened
40 >= <=242

2.c. “Does releasing music in EP or Single have differences in streams?”

Lacking evidence to reject the null hypothesis that there’s no difference.

P-value
0.10
Log natural of Bayes Factor
0.07

2.d. “What metric has more effect to increase in followers?”

Total followers and Followers Difference correlate most with:

Cumulative Metrics Noncumulative Metrics
Count of Days 0.96 Number of Listeners per day 0.18
Cumulative Streams 0.96 -

C. CONCLUSION

How would I conclude all this? Well, according to Cassie Kzyrkov, who works as Chief Decision Scientist at Google, data analytics serves to find good questions and probably should conclude nothing outside the data itself, while statistics may serves to find appropriate answers by inferring beyond the data.

The results for tasks a and b, might lead me to ask further questions such as: “Why does streams increased on Friday? Should I try to A/B test release in Friday and not Friday? Or is there any studies before, that already investigated the importance of Friday for music streaming?” “Are these streams mostly done on the song recently released? Or any release date might offer a starting point to stream other songs? So is it better to release frequently at shorter interval? What songs are short lived, what might be the cause? Whatsongs are generating many days of streams, is it because there are other people who actually streams them, other than me?”

Meanwhile the result for task c led me to conclude that it doesn’t matter whether to release my music in EP or Single–when it comes to stream counts. So when I want to release some musics in the future, I can weigh other considerations more than brooding over EP or Single. Maybe practicality such as the cost to release EP or Single should matter to me more.

Lastly, for the result of task d, let’s just say that it can be a starting point for better statistical investigations when I’m more capable of it later.

LS0tDQp0aXRsZTogIlN5aCBTcG90aWZ5IERhdGEgRXhwbG9yYXRpb24iDQphdXRob3I6ICJTLlkuIEh1c2FkYSINCmRhdGU6ICcyMDIyLTA1LTAxJw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KIyMgQS4gW0lOVFJPRFVDVElPTl0oI2ludHJvKQ0KDQojIyMjIDEuW0lkZW50aWZ5IFRhc2tzXSgjdGFza3MpDQoNCiMjIyMgMi5bRGF0YSBQcmVwYXJhdGlvbl0oI3ByZXApDQoNCiMjIyMjIDIuYS5bVGltZWxpbmVzXSgjdGwpDQoNCiMjIyMjIDIuYi5bUmVjb3JkaW5nc10oI3JlYykNCg0KIyMgQi4gW0FOQUxZU0lTXSgjYW5hbHlzaXMpDQoNCiMjIyMgMS4gW0FnZ3JlZ2F0aW9ucyBhbmQgRGlzY292ZXJpZXNdKCNhZ2cpDQoNCiMjIyMjIDEuYS4gW1Rhc2sgMTogIk9uIHdoYXQgZGF5IG9mIHdlZWsgSSBoYXZlIHRoZSBtb3N0IHN0cmVhbXMsIG9yIGxpc3RlbmVycywgb3IgaW5jcmVhc2UgaW4gZm9sbG93ZXJzPyJdKCN0MSkNCg0KIyMjIyMgMS5iLiBbVGFzayAyOiAiSG93IG1hbnkgZGF5cyBzaW5jZSB0aGUgZGF0ZSByZWxlYXNlZCB0aGF0IHNvbmcgc3RyZWFtcyBwbHVtbWV0ZWQ/Il0oI3QyKQ0KDQojIyMjIyAxLmMuIFtUYXNrIDM6ICJEb2VzIHJlbGVhc2luZyBtdXNpYyBpbiBFUCBvciBTaW5nbGUgaGF2ZSBkaWZmZXJlbmNlcyBpbiBzdHJlYW1zPyJdKCN0MykNCg0KIyMjIyMgMS5kLiBbVGFzayA0OiAiV2hhdCBtZXRyaWMgaGFzIG1vcmUgZWZmZWN0IHRvIGluY3JlYXNlIGluIGZvbGxvd2Vycz8iXSgjdDQpDQoNCiMjIyMgMi4gW1Jlc3VsdHNdKCNyZXMpDQoNCiMjIyMjIDIuYS4gW1Jlc3VsdCAxOiAiT24gd2hhdCBkYXkgb2Ygd2VlayBJIGhhdmUgdGhlIG1vc3Qgc3RyZWFtcywgb3IgbGlzdGVuZXJzLCBvciBpbmNyZWFzZSBpbiBmb2xsb3dlcnM/Il0oIzJhKSANCg0KIyMjIyMgMi5iLiBbUmVzdWx0IDI6ICJIb3cgbWFueSBkYXlzIHNpbmNlIHRoZSBkYXRlIHJlbGVhc2VkIHRoYXQgc29uZyBzdHJlYW1zIHBsdW1tZXRlZD8iXSgjMmIpDQoNCiMjIyMjIDIuYy4gW1Jlc3VsdCAzOiAiRG9lcyByZWxlYXNpbmcgbXVzaWMgaW4gRVAgb3IgU2luZ2xlIGhhdmUgZGlmZmVyZW5jZXMgaW4gc3RyZWFtcz8iXSgjMmMpDQoNCiMjIyMjIDIuZC4gW1Jlc3VsdCA0OiAiV2hhdCBtZXRyaWMgaGFzIG1vcmUgZWZmZWN0IHRvIGluY3JlYXNlIGluIGZvbGxvd2Vycz8iXSgjMmQpIA0KDQojIyBDLiBbQ09OQ0xVU0lPTl0oI2NvbmNsKQ0KICANCi0tLSAgDQogIA0KLS0tICANCg0KLS0tICANCiAgDQoNCiMgKipBLiBJTlRST0RVQ1RJT04qKiA8YSBpZD0iaW50cm8iPjwvYT4NCg0KPioiV2UgY2FuIGdldCBjcmVhdGl2ZTogd2UgY2FuIHN0YXJ0IHRvIHNlbmQgb3V0IG1lc3NhZ2VzIGluIGEgYm90dGxlOiB3ZSBjYW4gc2luZywgd3JpdGUgcG9ldHJ5LCBwcm9kdWNlIGJvb2tzIGFuZCBibG9ncywgYWN0aXZpdGllcyBzdGVtbWluZyBmcm9tIHRoZSByZWFsaXNhdGlvbiB0aGF0IHBlb3BsZSBhcm91bmQgdXMgd29uJ3QgZXZlciBmdWxseSBnZXQgdXMgYnV0IHRoYXQgb3RoZXJzIC0tIHNlcGFyYXRlZCBhY3Jvc3MgdGltZSBhbmQgc3BhY2UgLS0gbWlnaHQganVzdC4gVGhlIGhpc3Rvcnkgb2YgYXJ0IGlzIHRoZSByZWNvcmQgb2YgcGVvcGxlIHdobyBjb3VsZG4ndCBmaW5kIGFueW9uZSBpbiB0aGUgdmljaW5pdHkgdG8gdGFsayB0by4iKiAtLS1BbGFpbiBkZSBCb3R0b24NCg0KVGhlIHF1b3RlIGFib3ZlIG1pZ2h0IHJlcHJlc2VudCB3aGF0IEkgZmVsdCB3aGVuIEkgc3RhcnRlZCBwcm9kdWNpbmcgbXVzaWMsIGFtb25nIG90aGVyIHRoaW5ncy4NCkJ1dCB0aGlzIHRpbWUsIEkgaGVyZWJ5IGhvbGQgbXlzZWxmIHRvIGZhY2UgdGhlIGRpcmUgc3RhdGUgb2YgbXkgbXVzaWNhbCBwcm9qZWN0IGluIHRoZSBmb3JtIG9mIGRhdGEuDQpTcGVjaWZpY2FsbHksIHRoZSBkYXRhIEkndmUgZ290IGZyb20gbXkgU3BvdGlmeSBmb3IgQXJ0aXN0cyBhY2NvdW50Lg0KTWF5YmUsIEkgY2FuIGxlYXJuIHNvbWV0aGluZyBmcm9tIGl0Lg0KUGVyaGFwcywgYW5zd2VyaW5nIHF1ZXN0aW9ucyBzdWNoIGFzICJPbiB3aGF0IGRheSBvZiB3ZWVrIEkgaGF2ZSB0aGUgbW9zdCBzdHJlYW1zPyIsIG9yICJIb3cgbG9uZyBzaW5jZSB0aGUgZGF0ZSByZWxlYXNlZCB0aGF0IHNvbmcgc3RyZWFtcyBwbHVtbWV0ZWQ/IiINCg0KVGhlcmUgYXJlIHR3byBjc3YgZmlsZXMuDQpUaGUgZmlyc3Qgb25lIGNhcHR1cmVzIGEgdGltZWxpbmUgb2Ygc3RyZWFtcywgbGlzdGVuZXJzLCBhbmQgZm9sbG93ZXJzOyB3aGlsZSBhbm90aGVyIG9mZmVycyBzaW1pbGFyIG1ldHJpY3MgYnV0IGlzIGdyb3VwZWQgYnkgc29uZyB0aXRsZXMuDQpUaGV5J3JlIHByZXR0eSBiYXJlIGJvbmVzLg0KSSBtaWdodCBuZWVkIHRvIHBlcmZvcm0gc29tZSBmZWF0dXJlcyBlbmdpbmVlcmluZy4NCg0KIyMgKioxLiBJZGVudGlmeSBUYXNrcyoqIDxhIGlkPSJ0YXNrcyI+PC9hPg0KDQpBbG1vc3QgaGFsZndheSBpbiB3cml0aW5nIHRoaXMgbm90ZWJvb2ssIEkgcmVhbGl6ZWQgSSB3YXMgZ29pbmcgbm93aGVyZSBzaW5jZSBJIGRpZG4ndCByZWFsbHkgaGF2ZSBhbnkgc3BlY2lmaWMgZGlyZWN0aW9uLg0KU28gaGVyZSBJIGFtLCB3cml0aW5nIGxpc3RzIG9mIHF1ZXN0aW9ucyBJIHdhbnQgdG8gaW52ZXN0aWdhdGU6DQoNCjEuICBPbiB3aGF0IGRheSBvZiB3ZWVrIEkgaGF2ZSB0aGUgbW9zdCBzdHJlYW1zLCBvciBsaXN0ZW5lcnMsIG9yIGluY3JlYXNlIGluIGZvbGxvd2Vycz8NCg0KMi4gIEhvdyBtYW55IGRheXMgc2luY2UgdGhlIGRhdGUgcmVsZWFzZWQgdGhhdCBzb25nIHN0cmVhbXMgcGx1bW1ldGVkPw0KDQozLiAgRG9lcyByZWxlYXNpbmcgbXVzaWMgaW4gRVAgb3IgU2luZ2xlIGhhdmUgZGlmZmVyZW5jZXMgaW4gc3RyZWFtcz8NCg0KNC4gIFdoYXQgbWV0cmljIGhhcyBtb3JlIGVmZmVjdCB0byBpbmNyZWFzZSBpbiBmb2xsb3dlcnM/DQoNCiMjICoqMi4gRGF0YSBQcmVwYXJhdGlvbioqIDxhIGlkPSJwcmVwIj48L2E+DQoNCkZpcnN0LCBsZXQncyBsb2FkIHNvbWUgdG9vbHMgdG8gbG9vayBpbnRvIHRoZSBkYXRhLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoZGxvb2tyKQ0KbGlicmFyeShEYXRhRXhwbG9yZXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkoZ2dzdGF0c3Bsb3QpDQpsaWJyYXJ5KGVmZmVjdHNpemUpDQpsaWJyYXJ5KGdncHVicikNCmxpYnJhcnkoY2FyKQ0KcmVjb3JkaW5nc19sYXN0NXllYXJzIDwtIHJlYWRfY3N2KCJyZWNvcmRpbmdzLWxhc3Q1eWVhcnMuY3N2IikNCnRpbWVsaW5lcyA8LSByZWFkX2NzdigidGltZWxpbmVzLmNzdiIpDQpgYGANCg0KIyMjICoyLmEuIFRpbWVsaW5lcyogPGEgaWQ9InRsIj48L2E+DQoNCkkgc3RhcnRlZCByZWxlYXNpbmcgbXVzaWMgaW4gU3BvdGlmeSBpbiAyMDIwLg0KVGhlIDIwMjIgaGFzbid0IGVuZGVkIHlldCwgc28gbGV0J3MgY3JlYXRlIHRoZSBjdXQgb2ZmIHJhbmdlIGZyb20gMjAyMCB0byAyMDIxLg0KYGBge3J9DQojIEkgc3RhcnRlZCByZWxlYXNpbmcgU3loIHByb2plY3QgaW4gMjAyMA0KIyBmaWx0ZXIgdGhlIGRhdGUNCnRpbWVsaW5lcyA8LSB0aW1lbGluZXMgJT4lIA0KICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0ZSkpICU+JSANCiAgZmlsdGVyKGRhdGUgPj0gIjIwMjAtMDEtMDEiLA0KICAgICAgICAgZGF0ZSA8PSAiMjAyMS0xMi0zMSIpICU+JQ0KICBhcnJhbmdlKGRhdGUpDQpgYGANCg0KTGV0J3MgdmlzdWFsaXplIHRoaXMgZGF0YS4NCmBgYHtyfQ0KZ2dwbG90KHRpbWVsaW5lcywgYWVzKHggPSBkYXRlLCB5ID0gZm9sbG93ZXJzKSkrDQogIGdlb21fbGluZShhZXMoY29sb3I9IlRvdGFsIEZvbGxvd2VycyIpKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1zdHJlYW1zLCANCiAgICAgICAgICAgICAgICAgY29sb3I9IlN0cmVhbXMiDQogICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICApKw0KICBnZW9tX3BvaW50KGFlcyh5PWxpc3RlbmVycywgDQogICAgICAgICAgICAgICAgIGNvbG9yPSJMaXN0ZW5lcnMiDQogICAgICAgICAgICAgICAgICksDQogICAgICAgICAgICAgKSsNCiAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAidG9wIikrDQogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSJTcG90aWZ5IE1ldHJpY3MiLA0KICAgICAgICAgICAgICAgICAgICAgYnJlYWtzPWMoIlRvdGFsIEZvbGxvd2VycyIsIlN0cmVhbXMiLCJMaXN0ZW5lcnMiKSwNCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcz1jKCJUb3RhbCBGb2xsb3dlcnMiPSJyZWQiLCJTdHJlYW1zIj0iZ3JlZW4iLCAiTGlzdGVuZXJzIj0iYmx1ZSIpKQ0KIA0KICANCmBgYA0KDQpBcyB3ZSBjYW4gc2VlLCB0aGUgcmVkIGxpbmUgdGhhdCByZXByZXNlbnRzIGZvbGxvd2VycywgcmlzZSBjdW11bGF0aXZlbHksIHdoaWxlIHRoZSBncmVlbiBhbmQgYmx1ZSBkb3RzIHRoYXQgcmVwcmVzZW50IHN0cmVhbXMgYW5kIGxpc3RlbmVycyBhcmUgbm90IGN1bXVsYXRpdmUuDQpJIG5lZWQgdG8gY3JlYXRlIGN1bXVsYXRpdmUgZGF0YSBmb3IgdGhlbS4NCmBgYHtyfQ0KDQoNCnRpbWVsaW5lc19lZGl0IDwtIHRpbWVsaW5lcyAlPiUgDQogIG11dGF0ZShjdW1fbGlzdGVuZXJzID0gY3Vtc3VtKGxpc3RlbmVycyksDQogICAgICAgICBjdW1fc3RyZWFtcyA9IGN1bXN1bShzdHJlYW1zKSwNCiAgICAgICAgIGRheV9vZl95ZWFyID0geWRheShkYXRlKSkNCmBgYA0KDQpgYGB7cn0NCmhlYWQodGltZWxpbmVzX2VkaXQsIDMpDQpgYGANCg0KV2FpdC4NCkkgZG9uJ3QgcmVtZW1iZXIgcmVsZWFzaW5nIG11c2ljIG9uIHRoZSBmaXJzdCBkYXkgb2YgdGhlIHllYXIuDQpNYXliZSwgdXNpbmcgZGF5X29mX3llYXIgaXMgYSBiaXQgb2ZmIHRob3VnaCBtYXkgYmUgdXNlZnVsIGFzIHdlbGwuDQpJIHRoaW5rIEkgbmVlZCB0byBhZGQgYSBjb2x1bW4gdGhhdCBjb3VudHMgdGhlIG51bWJlciBvZiBkYXlzIHNpbmNlIHRoZSByZWxlYXNlIGRhdGUgb2YgdGhlIGZpcnN0IHNvbmcsIG5vdCBmcm9tIHRoZSBmaXJzdCBkYXkgb2YgdGhlIHllYXIuIFRvIGRvIHRoYXQsIGl0J3MgbmVjZXNzYXJ5IHRvIGZpbmQgYXQgd2hhdCBkYXRlIHNob3VsZCBJIHN0YXJ0IHRvIGNvdW50Lg0KYGBge3J9DQojIHRvIGNvdW50IGRheSBzaW5jZSB0aGUgZmlyc3QgZGF5IG9mIHJlbGVhc2UNCnN0YXJ0X2NvdW50X2RhdGUgPC0gcmVjb3JkaW5nc19sYXN0NXllYXJzICU+JQ0KICBmaWx0ZXIocmVsZWFzZV9kYXRlID09IG1pbihyZWxlYXNlX2RhdGUpKSAlPiUgIHB1bGwocmVsZWFzZV9kYXRlKSAlPiUgdW5pcXVlKCkNCnN0YXJ0X2NvdW50X2RhdGUgDQpgYGANCg0KVGhlbiB1c2UgdGhlIGRhdGUgd2UgZm91bmQgdG8gY291bnQgaG93IG1hbnkgaW50ZWdlciBuZWNlc3NhcnkgdG8gY3JlYXRlIGxhZyBpbiBkYXlfY291bnQuDQpgYGB7cn0NCm5fZm9yX2xhZyA8LSB0aW1lbGluZXNfZWRpdCAlPiUgDQogIGZpbHRlcihkYXRlID09IHN0YXJ0X2NvdW50X2RhdGUpICU+JSBwdWxsKGRheV9vZl95ZWFyKSAtIDEgJT4lIGFzLmludGVnZXIoKQ0Kbl9mb3JfbGFnDQpgYGANCg0KVGhlbiB3ZSB1c2UgbGFnIHRvIHN0YXJ0IGNvdW50aW5nLCBkb24ndCBmb3JnZXQgdG8gcmVwbGFjZSB0aGUgTkEgd2l0aCB6ZXJvLg0KYGBge3J9DQp0aW1lbGluZXNfZWRpdCA8LSB0aW1lbGluZXNfZWRpdCAlPiUgDQogIG11dGF0ZShjb3VudF9kYXlzID0gbGFnKHNlcV9hbG9uZyhkYXRlKSwgbj1uX2Zvcl9sYWcpKSAlPiUNCiAgcmVwbGFjZV9uYShsaXN0KGNvdW50X2RheXMgPTApKQ0KDQpoZWFkKHRpbWVsaW5lc19lZGl0LCAyMCkNCmBgYA0KDQpMZXQncyB2aXN1YWxpc2UgdGhlIG5ldyBmZWF0dXJlcy4NCmBgYHtyfQ0KICBnZ3Bsb3QodGltZWxpbmVzX2VkaXQsIGFlcyh4ID0gZGF0ZSwgeSA9IGZvbGxvd2VycykpKw0KICBnZW9tX2xpbmUoYWVzKGNvbG9yPSJUb3RhbCBGb2xsb3dlcnMiKSkrDQogIGdlb21fcG9pbnQoYWVzKHk9Y3VtX3N0cmVhbXMsIA0KICAgICAgICAgICAgICAgICBjb2xvcj0iQ3VtdWxhdGl2ZSBTdHJlYW1zIg0KICAgICAgICAgICAgICAgICApDQogICAgICAgICAgICAgKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1jdW1fbGlzdGVuZXJzLCANCiAgICAgICAgICAgICAgICAgY29sb3I9IkN1bXVsYXRpdmUgTGlzdGVuZXJzIg0KICAgICAgICAgICAgICAgICApLA0KICAgICAgICAgICAgICkrDQogIGdlb21fbGluZShhZXMoeT1jb3VudF9kYXlzLA0KICAgICAgICAgICAgICAgIGNvbG9yPSJDdW11bGF0aXZlIERheSBDb3VudHMiKSwNCiAgICAgICAgICAgICkrDQogIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikrDQogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSJDdW11bGF0aXZlIFNwb3RpZnkgTWV0cmljcyIsDQogICAgICAgICAgICAgICAgICAgICBicmVha3M9YygiVG90YWwgRm9sbG93ZXJzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDdW11bGF0aXZlIFN0cmVhbXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkN1bXVsYXRpdmUgTGlzdGVuZXJzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDdW11bGF0aXZlIERheSBDb3VudHMiKSwNCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcz1jKCJUb3RhbCBGb2xsb3dlcnMiPSJyZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkN1bXVsYXRpdmUgU3RyZWFtcyI9ImdyZWVuIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ3VtdWxhdGl2ZSBMaXN0ZW5lcnMiPSJibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDdW11bGF0aXZlIERheSBDb3VudHMiPSJibGFjayIpKQ0KYGBgDQoNCkJlZm9yZSBnZXR0aW5nIHRvbyBjYXJyaWVkIGF3YXkgaW50byB3aGF0IGN1bXVsYXRpdmUgbWV0cmljcyBtaWdodCBzYXksIGxldCdzIHRha2UgYSBzdGVwIGJhY2sgYW5kIGV4dHJhY3Qgc29tZSBtb3JlIGZlYXR1cmVzIGZyb20gdGhlIGRhdGUgY29sdW1uLg0KYGBge3J9DQp0aW1lbGluZXNfZWRpdCA8LSB0aW1lbGluZXNfZWRpdCAlPiUNCiAgbXV0YXRlKGRheV9vZl93ZWVrID0gd2RheShkYXRlLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBGQUxTRSksDQogICAgICAgICApDQpgYGANCg0KSSdtIGEgYml0IGJvdGhlcmVkIHRoYXQgdGhlIGZvbGxvd2VycyBmZWF0dXJlIGlzIGN1bXVsYXRpdmUgb25seS4NCkkgbmVlZCB0byBjcmVhdGUgYSBuZXcgZmVhdHVyZSB0aGF0IGFsbG93cyBtZSB0byBzZWUgd2hlbiB3YXMgdGhlIG51bWJlciBmb2xsb3dlciBpbmNyZWFzZWQgYnkgc3VidHJhY3RpbmcgdGhlIGZvbGxvd2VycyBjb2x1bW4gd2l0aCBpdHMgbGFnZ2VkIHZlcnNpb24gdXNpbmcgbGFnKCkuDQpCZWNhdXNlIGxhZygpIGNyZWF0ZXMgTkEsIEkgYWxzbyBuZWVkIHRvIGltcHV0ZSBpdCB3aXRoIDAgaW1tZWRpYXRlbHkuDQpgYGB7cn0NCnRpbWVsaW5lc19lZGl0IDwtIHRpbWVsaW5lc19lZGl0ICU+JQ0KICBtdXRhdGUoZm9sbG93ZXJzX2RpZmYgPSBmb2xsb3dlcnMgLSBsYWcoZm9sbG93ZXJzKSkgJT4lDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCB+cmVwbGFjZSguLCBpcy5uYSguKSwwKSkNCmBgYA0KDQpgYGB7cn0NCmhlYWQodGltZWxpbmVzX2VkaXQpDQpgYGANCg0KSWYgSSB3YW50ZWQgdG8gc2VlIGhvdyBzb21lIGZlYXR1cmVzIGluIHRoZSBgdGltZWxpbmVzX2VkaXRgIHJlbGF0ZSB0byB0aGUgc29uZ3MgSSByZWxlYXNlZCwgSSBuZWVkIHRvIHByZXBhcmUgdGhlIGByZWNvcmRpbmdzX2xhc3Q1eWVhcnNgIGRhdGFmcmFtZSBmaXJzdC4NCg0KIyMjICoqMi5iLiBSZWNvcmRpbmdzKiogPGEgaWQ9InJlYyI+PC9hPg0KDQpTb21lIGNhdmVhdHMgYWJvdXQgdGhpcyByZWNvcmRpbmcgZGF0YTogMS4NCkluIHRoZSBTcG90aWZ5IGZvciBBcnRpc3RzIHdlYiB3aGVyZSBJIGRvd25sb2FkZWQgdGhlIGNzdiBmb3IgdGhpcyBwYXJ0aWN1bGFyIGRhdGEsIHRoZSBvbmx5IG9wdGlvbnMgZm9yIGFkanVzdGluZyBjdXQgb2ZmIGRhdGUgd2VyZTogTGFzdCAyNCBIb3VycywgTGFzdCA3IERheXMsIExhc3QgMjggRGF5cywgU2luY2UgMjAxNSwgYW5kIEFsbCB0aW1lLg0KU28gdGhlIG1ldHJpY3Mgb2Ygc3RyZWFtcyBhbmQgbGlzdGVuZXJzIGlzIGFzIHVwIHRvIGRhdGUgYXMgdGhlIGRhdGUgd2hlbiBJIGRvd25sb2FkZWQgdGhlIGNzdiB3aGljaCB3YXMgYXQgMTQgQXByaWwgMjAyMi4NCkkgY2Fubm90IGxpbWl0IHRoZSBkYXRlIHVwIHRvIHRoZSBlbmQgb2YgMjAyMSB0byBlcXVhbCB0aGUgdGltZWxpbmUgY3N2Lg0KSXQncyBiZXR0ZXIgdG8gc2VlIHRoZSBkYXRhIGJlbGxvdyBhcyB0YWtpbmcgYSBsb29rIGZyb20gZGlmZmVyZW50IHBlcnNwZWN0aXZlLg0KDQoyLiAgVGhpcyBgcmVjb3JkaW5nc19sYXN0NXllYXJzYCBkYXRhZnJhbWUgY29uc2lzdHMgb2YgMTcgc29uZ3MsIGFuZCBJIHdhbnQgdG8gZXhwbGFpbiBzb21ldGhpbmdzIGFib3V0IDIgb2YgdGhlbS4gRmlyc3QsIHRoZSBzb25nICoqU2hvcnQgTGVmdCBEcml2ZSoqIGlzIG5vdCBtaW5lLiBTb21lb25lIHNvbWV3aGVyZSBoYWQgdGhlIHVuZm9ydHVuYXRlIGZhdGUgdG8gcmVsZWFzZSBtdXNpYyB1bmRlciBteSBTcG90aWZ5IEFydGlzdCBJRCB0YWcsIGFuZCBJIGRvbid0IGtub3cgaG93IHRvIHJlbW92ZSBpdCwgYW5kIEkgaG9wZSB3aG9ldmVyIHRoZXkgYXJlIG1pZ2h0IGRvIHNvbWV0aGluZyBhYm91dCBpdC4gQW5vdGhlciBzb25nIEkgbmVlZCB0byBleHBsYWluIGlzIHRoZSBzb25nICoqRnJvdGggYW5kIEZvYW0qKi4gSXQgd2FzIHByZXZpb3VzbHkgcmVsZWFzZWQgaW4gMjAxOSB1bmRlciBteSBvdGhlciBBcnRpc3QgSUQgdGFnLCBhbmQgb25seSBpbiAyMDIxIEkgZGVjaWRlZCB0byBtb3ZlIGl0IHVuZGVyIG15IFtTeWggQXJ0aXN0IElEIHRhZ10oaHR0cHM6Ly9vcGVuLnNwb3RpZnkuY29tL2FydGlzdC8zNExZcFBJTlZ6R093V2VNQVo3bVd6P3NpPTNoOV9waGM3Ukt1UGE4WGNxUllRLWcpLiBJIG5lZWQgdG8gZGVjaWRlLCBzaG91bGQgSSByZW1vdmUgdGhlbSBib3RoIGZyb20gdGhlIGRhdGFmcmFtZT8gV2VsbCwgSSB3aWxsIHJlbW92ZSAqKlNob3J0IExlZnQgRHJpdmUqKiBidXQgbm90ICoqRnJvdGggYW5kIEZvYW0qKi4NCg0KYGBge3J9DQpyZWNvcmRpbmdzIDwtIHJlY29yZGluZ3NfbGFzdDV5ZWFycyAlPiUNCiAgZmlsdGVyKHNvbmcgIT0gIlNob3J0IExlZnQgRHJpdmUiKQ0KYGBgDQoNCkxldCdzIHRha2UgYSBsb29rLg0KYGBge3J9DQpwcmludChyZWNvcmRpbmdzKQ0KYGBgDQoNCkJlY2F1c2UgdGhpcyBkYXRhZnJhbWUgbGFja3MgaW5mb3JtYXRpb24sIEkgd2FudCB0byBhZGQgaG93IHRoZSBzb25nIHdhcyByZWxlYXNlZCwgd2hldGhlciBhcyBFUCBvciBhcyBTaW5nbGUuDQoNCkJlZm9yZSB0aGF0LCBJIGhhdmUgdG8gYXJyYW5nZSBpdCBieSBkYXRlLg0KYGBge3J9DQpyZWNvcmRpbmdzIDwtIHJlY29yZGluZ3MgJT4lIA0KICBhcnJhbmdlKHJlbGVhc2VfZGF0ZSkNCnJlY29yZGluZ3MNCmBgYA0KYGBge3J9DQpkaXNjb2dyYXBoeV90aXRsZSA8LSANCiAgYXMuZmFjdG9yKGMoIk1hbnVzaWFsIiwgDQogICAgIk1hbnVzaWFsIiwgDQogICAgIk1hbnVzaWFsIiwgDQogICAgIk1hbnVzaWFsIiwgDQogICAgIkFudGluYXRhbCBpbiBDIiwgDQogICAgIkJpcnUgTWVtYXIiLCANCiAgICAiQmlydSBNZW1hciIsIA0KICAgICJCaXJ1IE1lbWFyIiwgDQogICAgIkJpcnUgTWVtYXIiLCANCiAgICAiQmlydSBNZW1hciIsIA0KICAgICJCaXJ1IE1lbWFyIiwNCiAgICAiTnlhbXVrIEFuamluZyIsDQogICAgIkhhbHVsaW50YXMiLA0KICAgICJXaXRoaW4gYW4gSW5jaCBvZiBMb3NpbmcgSXQiLA0KICAgICJUdWhhbiBDYWJ1dCBOeWF3YWt1IiwNCiAgICAiRnJvdGggYW5kIEZvYW0iDQogICAgKSkNCg0KZGlzY29ncmFwaHlfZm9ybSA8LQ0KIGFzLmZhY3RvcihjKCJFUCIsDQogICAgIkVQIiwNCiAgICAiRVAiLA0KICAgICJFUCIsDQogICAgIlNpbmdsZSIsDQogICAgIkVQIiwNCiAgICAiRVAiLA0KICAgICJFUCIsDQogICAgIkVQIiwNCiAgICAiRVAiLA0KICAgICJFUCIsDQogICAgIlNpbmdsZSIsDQogICAgIlNpbmdsZSIsDQogICAgIlNpbmdsZSIsDQogICAgIlNpbmdsZSIsDQogICAgIlNpbmdsZSIpKQ0KYGBgDQpgYGB7cn0NCnJlY29yZGluZ3NfZWRpdCA8LSByZWNvcmRpbmdzICU+JQ0KICBtdXRhdGUoZGlzY190aXRsZSA9IGRpc2NvZ3JhcGh5X3RpdGxlLA0KICAgICAgICAgZGlzY19mb3JtID0gZGlzY29ncmFwaHlfZm9ybQ0KICAgICAgICAgKSAlPiUNCiAgc2VsZWN0KC1zYXZlcykgIyBzaW5jZSBpdCdzIHplcm8sIGxldCdzIGp1c3QgZHJvcCBpdA0KDQpyZWNvcmRpbmdzX2VkaXQNCmBgYA0KDQoNCiMgKipCLiBBTkFMWVNJUyoqIDxhIGlkPSJhbmFseXNpcyI+PC9hPg0KDQojIyAqKjEuIEFnZ3JlZ2F0aW9ucyBhbmQgRGlzY292ZXJpZXMqKiA8YSBpZD0iYWdnIj48L2E+DQoNCiMjIyAqKjEuYS4gVGFzazE6ICJPbiB3aGF0IGRheSBvZiB3ZWVrIEkgaGF2ZSB0aGUgbW9zdCBzdHJlYW1zLCBvciBsaXN0ZW5lcnMsIG9yIGluY3JlYXNlIGluIGZvbGxvd2Vycz8iKiogPGEgaWQ9InQxIj48L2E+DQoNCldlIGNhbiBhbnN3ZXIgdGhpcyBieSBhZ2dyZWdhdGluZyB0aGUgZGF0YSB3aXRoIGdyb3VwIGJ5IGFuZCBzdW1tYXJpemUuDQoNCmBgYHtyfQ0KbWV0cmljc19wZXJfZG93IDwtIHRpbWVsaW5lc19lZGl0ICU+JQ0KICBncm91cF9ieShkYXlfb2Zfd2VlaykgJT4lDQogIHN1bW1hcml6ZSh0b3RhbF9zdHJlYW1zID0gc3VtKHN0cmVhbXMpLA0KICAgICAgICAgICAgdG90YWxfbGlzdGVuZXJzID0gc3VtKGxpc3RlbmVycyksDQogICAgICAgICAgICB0b3RhbF9mb2xsb3dlcnNfZGlmZiA9IHN1bShmb2xsb3dlcnNfZGlmZikpDQptZXRyaWNzX3Blcl9kb3cNCmBgYA0KDQpUaGUgdGFibGUgYWJvdmUgaXMgaG93IHRoZSBtZXRyaWNzIHN1bW1hcml6ZWQgaW4gZGF5IG9mIHdlZWsuDQpIb3cgdGhlc2UgbWV0cmljcyBzcHJlYWQgb3V0IHdpbGwgYmUgbW9yZSBlYXNpbHkgZGlnZXN0YWJsZSBpZiB3ZSBwaXZvdCB0aGVtIGxvbmdlciBzbyB0aGV5J3JlIGJldHRlciBwcmVwYXJlZCB0byBiZSB2aXN1YWxpemVkLg0KDQpgYGB7cn0NCmRvd3RvcF9zdHJlYW1zIDwtIG1heChtZXRyaWNzX3Blcl9kb3ckdG90YWxfc3RyZWFtcykNCmRvd3RvcF9saXN0ZW5lcnMgPC0gbWF4KG1ldHJpY3NfcGVyX2RvdyR0b3RhbF9saXN0ZW5lcnMpDQpkb3d0b3BfZm9sbGRpZmYgPC0gbWF4KG1ldHJpY3NfcGVyX2RvdyR0b3RhbF9mb2xsb3dlcnNfZGlmZikNCg0KYmVzdF9kb3dfc3RyZWFtIDwtIGFzLmRhdGEuZnJhbWUobWV0cmljc19wZXJfZG93W21ldHJpY3NfcGVyX2RvdyR0b3RhbF9zdHJlYW1zID09IGRvd3RvcF9zdHJlYW1zLCBjKCJkYXlfb2Zfd2VlayIsInRvdGFsX3N0cmVhbXMiKV0pDQpiZXN0X2Rvd19saXN0ZW5lcnMgPC0gYXMuZGF0YS5mcmFtZShtZXRyaWNzX3Blcl9kb3dbbWV0cmljc19wZXJfZG93JHRvdGFsX2xpc3RlbmVycyA9PSBkb3d0b3BfbGlzdGVuZXJzLCBjKCJkYXlfb2Zfd2VlayIsInRvdGFsX2xpc3RlbmVycyIpXSkNCmJlc3RfZG93X2ZvbGxkaWZmIDwtIGFzLmRhdGEuZnJhbWUobWV0cmljc19wZXJfZG93W21ldHJpY3NfcGVyX2RvdyR0b3RhbF9mb2xsb3dlcnNfZGlmZiA9PSBkb3d0b3BfZm9sbGRpZmYsIGMoImRheV9vZl93ZWVrIiwidG90YWxfZm9sbG93ZXJzX2RpZmYiKV0pDQoNCmFuc3dlcl8xIDwtIGNiaW5kKGJlc3RfZG93X3N0cmVhbSwgYmVzdF9kb3dfbGlzdGVuZXJzLCBiZXN0X2Rvd19mb2xsZGlmZikNCmBgYA0KDQpgYGB7cn0NCm1ldHJpY3NfcGVyX2Rvd19sb25nZXIgPC0gcGl2b3RfbG9uZ2VyKGRhdGEgPSBtZXRyaWNzX3Blcl9kb3csDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xzID0gYygidG90YWxfc3RyZWFtcyIsInRvdGFsX2xpc3RlbmVycyIsInRvdGFsX2ZvbGxvd2Vyc19kaWZmIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJtZXRyaWNzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ0b3RhbCIpDQoNCg0KDQojIHBpdm90IGRheQ0KcGl2b3RfZGF5IDwtIHBpdm90X2xvbmdlcihkYXRhID0gYW5zd2VyXzEsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSAiZGF5X29mX3dlZWsiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiYmVzdF9kYXlfb2Zfd2VlayIpICU+JSANCiAgc2VsZWN0KGJlc3RfZGF5X29mX3dlZWspDQoNCiMgcGl2b3Qgc3RhdHMNCnBpdm90X3N0YXRzIDwtIHBpdm90X2xvbmdlcihkYXRhID0gYW5zd2VyXzEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJ0b3RhbF9zdHJlYW1zIiwidG90YWxfbGlzdGVuZXJzIiwidG90YWxfZm9sbG93ZXJzX2RpZmYiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibWV0cmljcyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ0b3RhbCIpICU+JSANCiAgc2VsZWN0KG1ldHJpY3MsIHRvdGFsKQ0KDQojIGpvaW4NCmFuc3dlcl8xbG9uZ2VyPC0gYmluZF9jb2xzKHBpdm90X3N0YXRzLCBwaXZvdF9kYXkpDQoNCm1ldHJpY3NfcGVyX2Rvd19sb25nZXINCmFuc3dlcl8xbG9uZ2VyDQpgYGANCg0KTGV0J3MgdmlzdWFsaXplIGhvdyB0aGUgbWV0cmljcyBhY2hpZXZlZCBpbiBkYXkgb2Ygd2Vlay4NCg0KYGBge3J9DQpnZ3Bsb3QobWV0cmljc19wZXJfZG93X2xvbmdlciwgYWVzKHg9cmVvcmRlcihkYXlfb2Zfd2VlaywgdG90YWwpLCB5PXRvdGFsKSkrDQogIGdlb21fY29sKGFlcyhmaWxsPW1ldHJpY3MpLCBwb3NpdGlvbiA9ICJkb2RnZSIpKw0KICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gIk1ldHJpY3MiLCANCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiVG90YWwgRm9sbG93ZXJzIEluY3JlYXNlIiwiVG90YWwgTGlzdGVuZXJzIiwiVG90YWwgU3RyZWFtcyIpLCANCiAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygidG90YWxfZm9sbG93ZXJzX2RpZmYiPSJyZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidG90YWxfbGlzdGVuZXJzIj0iYmx1ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0b3RhbF9zdHJlYW1zIj0iZ3JlZW4iKSkrDQogIGxhYnModGl0bGUgPSAiTWV0cmljcyBUb3RhbCBpbiBEYXkgb2YgV2Vla3MiKSsNCiAgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpKw0KICBjb29yZF9mbGlwKCkNCg0KZ2dwbG90KGFuc3dlcl8xbG9uZ2VyLCBhZXMoeD1yZW9yZGVyKGJlc3RfZGF5X29mX3dlZWssIC10b3RhbCksIHk9dG90YWwpKSsNCiAgZ2VvbV9jb2woYWVzKHk9dG90YWwsIGZpbGw9bWV0cmljcyksIHBvc2l0aW9uID0gImRvZGdlIikrDQogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAiTWV0cmljcyIsIA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJUb3RhbCBGb2xsb3dlcnMgSW5jcmVhc2UiLCJUb3RhbCBMaXN0ZW5lcnMiLCJUb3RhbCBTdHJlYW1zIiksIA0KICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCJ0b3RhbF9mb2xsb3dlcnNfZGlmZiI9InJlZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0b3RhbF9saXN0ZW5lcnMiPSJibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRvdGFsX3N0cmVhbXMiPSJncmVlbiIpKSsNCiAgbGFicyh0aXRsZSA9ICJXaGF0IERheSBvZiBXZWVrIGhhcyB0aGUgSGlnaGVzdCBUb3RhbCBNZXRyaWNzPyIpKw0KICAgIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNClNvLCB0byBhbnN3ZXIgdGhlIHRhc2ssIE9uIHdoYXQgZGF5IG9mIHdlZWsgSSBoYXZlIHRoZSBtb3N0IHN0cmVhbXMsIG9yIGxpc3RlbmVycywgb3IgaW5jcmVhc2UgaW4gZm9sbG93ZXJzPw0KVGhlIGRhdGEgc2hvd3MgdGhhdCBJIGhhdmUgdGhlIG1vc3Qgc3RyZWFtcyBhdCBGcmlkYXksIG1vc3QgbGlzdGVuZXJzIGF0IFR1ZXNkYXksIGFuZCBtb3N0IGluY3JlYXNlIGluIGZvbGxvd2VycyBhdCBUaHVyc2RheS4NCg0KIyMjICoqMS5iLiBUYXNrMjogIkhvdyBtYW55IGRheXMgc2luY2UgdGhlIGRhdGUgcmVsZWFzZWQgdGhhdCBzb25nIHN0cmVhbXMgcGx1bW1ldGVkPyIqKiA8YSBpZD0idDIiPjwvYT4NCg0KQmVmb3JlIHdlIGdvLCBsZXQncyBtYWtlIHNvbWV0aGluZyBjbGVhci4NClN0cmVhbXMgdGhhdCBoYXBwZW5lZCBhZnRlciB0aGUgcmVsZWFzZSBkYXRlIG9mIHNvbWUgc29uZ3Mgd2VyZSBub3QgZXhjbHVzaXZlbHkgc3RyZWFtcyBmcm9tIHRoZSBzYW1lIHNhaWQgc29uZ3MuDQpUaGUgcmVsZWFzZSB0aG91Z2gsIG1pZ2h0IHRyaWdnZXJlZCB0aGUgc3RyZWFtcyBvZiBvdGhlciBzb25ncyB0aGF0IHdhcyBhbHNvIGFscmVhZHkgYXZhaWxhYmxlLg0KDQpUbyBhbnN3ZXIgdGhlIHRhc2ssIEkgaGF2ZSB0byBtYXJrIGB0aW1lbGluZXNfZWRpdGAuDQpJJ2xsIGNyZWF0ZSBgcmVjb3JkaW5nc19tYXJrZXJgLg0KVGhlbiwgSSdsbCBzcGxpdCBgdGltZWxpbmVzX21hcmtlZGAgYWNjb3JkaW5nIGZyb20gZWFjaCByZWxlYXNlIGRhdGUgdXAgdW50aWwgdGhlIGRheSBiZWZvcmUgYSBuZXcgcmVsZWFzZSBieSB1c2luZyBgcmVjb3JkaW5nc19tYXJrZXJgIGFzIGEgZ3VpZGUuDQoNCmBgYHtyfQ0KcmVjb3JkaW5nc19tYXJrZXIgPC0gcmVjb3JkaW5nc19lZGl0ICU+JSBzZWxlY3QocmVsZWFzZV9kYXRlLCBzb25nLCBkaXNjX3RpdGxlLCBkaXNjX2Zvcm0pDQpgYGANCg0KTGVmdCBqb2luIHRoZW0gdG8gY3JlYXRlIGB0aW1lbGluZXNfbWFya2VkYA0KDQpgYGB7cn0NCnRpbWVsaW5lc19tYXJrZWQgPC0gdGltZWxpbmVzX2VkaXQgJT4lDQogIGxlZnRfam9pbihyZWNvcmRpbmdzX21hcmtlciwNCiAgICAgICAgICAgIGJ5ID0gYygiZGF0ZSI9InJlbGVhc2VfZGF0ZSIpKQ0KDQpgYGANCg0KYGBge3J9DQp0aW1lbGluZXNfbWFya2VkWyFpcy5uYSh0aW1lbGluZXNfbWFya2VkJHNvbmcpLGMoImRhdGUiLCJzdHJlYW1zIiwic29uZyIsImRpc2NfdGl0bGUiKV0NCmBgYA0KDQpgYGB7cn0NCmRhdGVfbWFudXNpYWwgPC0gdGltZWxpbmVzX21hcmtlZCAlPiUgZmlsdGVyKGRpc2NfdGl0bGUgPT0gIk1hbnVzaWFsIikgJT4lIHB1bGwoZGF0ZSkNCmRhdGVfYW50aW5hdGFsX2luX2MgPC0gdGltZWxpbmVzX21hcmtlZCAlPiUgZmlsdGVyKGRpc2NfdGl0bGUgPT0gIkFudGluYXRhbCBpbiBDIikgJT4lIHB1bGwoZGF0ZSkNCmRhdGVfYmlydV9tZW1hciA8LSB0aW1lbGluZXNfbWFya2VkICU+JSBmaWx0ZXIoZGlzY190aXRsZSA9PSAiQmlydSBNZW1hciIpICU+JSBwdWxsKGRhdGUpDQpkYXRlX255YW11a19hbmppbmcgPC0gdGltZWxpbmVzX21hcmtlZCAlPiUgZmlsdGVyKGRpc2NfdGl0bGUgPT0gIk55YW11ayBBbmppbmciKSAlPiUgcHVsbChkYXRlKQ0KZGF0ZV9oYWx1bGludGFzIDwtIHRpbWVsaW5lc19tYXJrZWQgJT4lIGZpbHRlcihkaXNjX3RpdGxlID09ICJIYWx1bGludGFzIikgJT4lIHB1bGwoZGF0ZSkNCmRhdGVfd2Fpb2xpIDwtIHRpbWVsaW5lc19tYXJrZWQgJT4lIGZpbHRlcihkaXNjX3RpdGxlID09ICJXaXRoaW4gYW4gSW5jaCBvZiBMb3NpbmcgSXQiKSAlPiUgcHVsbChkYXRlKQ0KZGF0ZV90Y19ueWF3YWt1IDwtIHRpbWVsaW5lc19tYXJrZWQgJT4lIGZpbHRlcihkaXNjX3RpdGxlID09ICJUdWhhbiBDYWJ1dCBOeWF3YWt1IikgJT4lIHB1bGwoZGF0ZSkNCmRhdGVfZnJvdGhfbl9mb2FtIDwtIHRpbWVsaW5lc19tYXJrZWQgJT4lIGZpbHRlcihkaXNjX3RpdGxlID09ICJGcm90aCBhbmQgRm9hbSIpICU+JSBwdWxsKGRhdGUpDQpkYXRlX2VuZCA8LSB0aW1lbGluZXNfbWFya2VkICU+JSBmaWx0ZXIoZGF0ZSA9PSBtYXgoZGF0ZSkpICU+JSBwdWxsKGRhdGUpDQpgYGANCg0KYGBge3J9DQoNCnN0cmVhbXNfcG9zdF9tYW51c2lhbCA8LSB0aW1lbGluZXNfbWFya2VkICU+JSANCiAgZmlsdGVyKGRhdGUgPj0gZGF0ZV9tYW51c2lhbCwgDQogICAgICAgICBkYXRlIDwgZGF0ZV9hbnRpbmF0YWxfaW5fYykgJT4lIA0KICBtdXRhdGUoZGlzY190aXRsZSA9IGFzLmNoYXJhY3RlcihkaXNjX3RpdGxlKSkgJT4lIA0KICBzZWxlY3QoZGF0ZSxzdHJlYW1zLGRpc2NfdGl0bGUpDQoNCnN0cmVhbXNfcG9zdF9tYW51c2lhbFssImRpc2NfdGl0bGUiXSA8LSByZXBsYWNlX25hKGRhdGEgPSBzdHJlYW1zX3Bvc3RfbWFudXNpYWwkZGlzY190aXRsZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2UgPSAiTWFudXNpYWwiKQ0KDQpzdHJlYW1zX3Bvc3RfbWFudXNpYWwNCmBgYA0KDQpJdCdzIGdldHRpbmcgdGVkaW91cyBkb2luZyByZXBlYXQgY29kZSwgbWF5YmUgSSBzaG91bGQgc3RhcnQgdHJ5aW5nIHRvIGNyZWF0ZSBteSBvd24gZnVuY3Rpb24uDQoNCmBgYHtyfQ0KZnVuY190bENob3BwZXIgPC0gZnVuY3Rpb24oeGRhdGUsIHlkYXRlLCBzdHJlYW1zX3huYW1lKQ0Kew0Kc3RyZWFtc194bmFtZSA8LSB0aW1lbGluZXNfbWFya2VkICU+JSANCiAgICAgICAgICAgICAgICAgIGZpbHRlcihkYXRlID49IHhkYXRlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlIDwgeWRhdGUpICU+JSANCiAgICAgICAgICAgICAgICAgIG11dGF0ZShkaXNjX3RpdGxlID0gYXMuY2hhcmFjdGVyKGRpc2NfdGl0bGUpKSAlPiUgDQogICAgICAgICAgICAgICAgICBzZWxlY3QoZGF0ZSxzdHJlYW1zLGRpc2NfdGl0bGUpDQpkaXNjX3Zhcm5hbWUgPC0gc3RyZWFtc194bmFtZSAlPiUgc2VsZWN0KGRpc2NfdGl0bGUpICU+JSBmaWx0ZXIoIWlzLm5hKGRpc2NfdGl0bGUpKSAlPiUgc2xpY2VfaGVhZCgpICU+JSBwdWxsKCkNCg0Kc3RyZWFtc194bmFtZVssImRpc2NfdGl0bGUiXSA8LSByZXBsYWNlX25hKGRhdGEgPSBzdHJlYW1zX3huYW1lJGRpc2NfdGl0bGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlID0gZGlzY192YXJuYW1lKQ0KDQpzdHJlYW1zX3huYW1lDQp9DQpgYGANCg0KTGV0J3MgY2hvcCBhbGwgdGhlIHNvbmcgdGltZWxpbmVzIQ0KDQpgYGB7cn0NCnN0cmVhbXNfcG9zdF9hbnRpbmF0YWxpbmMgPC0gDQogIGZ1bmNfdGxDaG9wcGVyKGRhdGVfYW50aW5hdGFsX2luX2MsIGRhdGVfYmlydV9tZW1hciwgInN0cmVhbXNfcG9zdF9hbnRpbmF0YWxpbmMiKQ0KDQpzdHJlYW1zX3Bvc3RfYmlydW1lbWFyIDwtIA0KICBmdW5jX3RsQ2hvcHBlcihkYXRlX2JpcnVfbWVtYXIsIGRhdGVfbnlhbXVrX2FuamluZywgInN0cmVhbXNfcG9zdF9iaXJ1bWVtYXIiKQ0KDQpzdHJlYW1zX3Bvc3RfbnlhbXVrYW5qaW5nIDwtIA0KICBmdW5jX3RsQ2hvcHBlcihkYXRlX255YW11a19hbmppbmcsIGRhdGVfaGFsdWxpbnRhcywgInN0cmVhbXNfcG9zdF9ueWFtdWthbmppbmciKQ0KDQpzdHJlYW1zX3Bvc3RfaGFsdWxpbnRhcyA8LSANCiAgZnVuY190bENob3BwZXIoZGF0ZV9oYWx1bGludGFzLCBkYXRlX3dhaW9saSwgInN0cmVhbXNfcG9zdF9oYWx1bGludGFzIikNCg0Kc3RyZWFtc19wb3N0X3dhaW9saSA8LSANCiAgZnVuY190bENob3BwZXIoZGF0ZV93YWlvbGksIGRhdGVfdGNfbnlhd2FrdSwgInN0cmVhbXNfcG9zdF93YWlvbGkiKQ0KDQpzdHJlYW1zX3Bvc3RfdGNueWF3YWt1IDwtIA0KICBmdW5jX3RsQ2hvcHBlcihkYXRlX3RjX255YXdha3UsIGRhdGVfZnJvdGhfbl9mb2FtLCAic3RyZWFtc19wb3N0X3Rjbnlhd2FrdSIpDQoNCnN0cmVhbXNfcG9zdF9mbmYgPC0gDQogIGZ1bmNfdGxDaG9wcGVyKGRhdGVfZnJvdGhfbl9mb2FtLCBkYXRlX2VuZCwgInN0cmVhbXNfcG9zdF90Y255YXdha3UiKQ0KYGBgDQpgYGB7ciB0bHMsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0yMH0NCnN0cmVhbXNfbWVyZ2VkIDwtIHJiaW5kKHN0cmVhbXNfcG9zdF9tYW51c2lhbCwgDQogICAgICAgICAgICAgICAgICAgICBzdHJlYW1zX3Bvc3RfYW50aW5hdGFsaW5jLA0KICAgICAgICAgICAgICAgICAgICAgc3RyZWFtc19wb3N0X2JpcnVtZW1hciwNCiAgICAgICAgICAgICAgICAgICAgIHN0cmVhbXNfcG9zdF9ueWFtdWthbmppbmcsDQogICAgICAgICAgICAgICAgICAgICBzdHJlYW1zX3Bvc3RfaGFsdWxpbnRhcywNCiAgICAgICAgICAgICAgICAgICAgIHN0cmVhbXNfcG9zdF93YWlvbGksDQogICAgICAgICAgICAgICAgICAgICBzdHJlYW1zX3Bvc3RfdGNueWF3YWt1LA0KICAgICAgICAgICAgICAgICAgICAgc3RyZWFtc19wb3N0X2ZuZikNCg0KcmFuZ2UgPC0gIGMoYXMuRGF0ZSgiMjAxOS0xMi0wNSIpLCBhcy5EYXRlKCIyMDIyLTAyLTAxIikpDQoNCmdncGxvdChzdHJlYW1zX21lcmdlZCwgYWVzKHg9ZGF0ZSx5PXN0cmVhbXMpKSsNCiAgZ2VvbV9saW5lKGFlcyhjb2w9ZGlzY190aXRsZSkpKw0KICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiaXRhbGljIiwgc2l6ZSA9IDcpLA0KICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSsNCiAgZ2VvbV92bGluZShkYXRhPXJlY29yZGluZ3NfbWFya2VyLCANCiAgICAgICAgICAgICBhZXMoeGludGVyY2VwdD1yZWxlYXNlX2RhdGUpLA0KICAgICAgICAgICAgIGxpbmV0eXBlPSJkYXNoZWQiKSsNCiAgZ2VvbV90ZXh0KGRhdGE9cmVjb3JkaW5nc19tYXJrZXIsDQogICAgICAgICAgICBhZXMoeD1yZWxlYXNlX2RhdGUsDQogICAgICAgICAgICAgICAgbGFiZWw9c29uZywNCiAgICAgICAgICAgICAgICB5PTUwLCANCiAgICAgICAgICAgICAgICBjb2w9ZGlzY190aXRsZSksDQogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcihoZWlnaHQgPSA0MCwgd2lkdGggPSAzLCBzZWVkID0gOSksDQogICAgICAgICAgICBzaXplID0gNQ0KICAgICAgICAgICAgKSsNCiAgbGFicyh0aXRsZSA9ICJUaW1lbGluZXMgb2YgU3RyZWFtcyIsIA0KICAgICAgIGNvbD0iUmVsZWFzZSBUaXRsZSIpKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9sYWJlbHMgPSAiJUYiLCANCiAgICAgICAgICAgICAgIGxpbWl0cyA9IHJhbmdlKQ0KYGBgDQpGb3IgZnV0dXJlIHVzZXMsIEkgdGhpbmsgaXQncyBiZXR0ZXIgaWYgSSBnYXRoZXIgdGhlIHN0cmVhbXNfcG9zdCBpbnRvIGEgbGlzdC4NCg0KYGBge3J9DQpzdHJlYW1zX2xpc3QgPC0gbGlzdChzdHJlYW1zX3Bvc3RfbWFudXNpYWwsIA0KICAgICAgICAgICAgICAgICAgICAgc3RyZWFtc19wb3N0X2FudGluYXRhbGluYywNCiAgICAgICAgICAgICAgICAgICAgIHN0cmVhbXNfcG9zdF9iaXJ1bWVtYXIsDQogICAgICAgICAgICAgICAgICAgICBzdHJlYW1zX3Bvc3RfbnlhbXVrYW5qaW5nLA0KICAgICAgICAgICAgICAgICAgICAgc3RyZWFtc19wb3N0X2hhbHVsaW50YXMsDQogICAgICAgICAgICAgICAgICAgICBzdHJlYW1zX3Bvc3Rfd2Fpb2xpLA0KICAgICAgICAgICAgICAgICAgICAgc3RyZWFtc19wb3N0X3Rjbnlhd2FrdSwNCiAgICAgICAgICAgICAgICAgICAgIHN0cmVhbXNfcG9zdF9mbmYNCiAgICAgICAgICAgICAgICAgICAgICkNCmBgYA0KDQpOb3cgbGV0J3MgY291bnQgaG93IG1hbnkgZGF5cyB0aGF0IHRoZXJlIHdlcmUgc3RpbGwgc29tZSBzdHJlYW1zIHVudGlsIG5vIHN0cmVhbXMgYXQgYWxsLg0KDQpgYGB7cn0NCnN0cmVhbXNfcG9zdF9tYW51c2lhbF9kdW1teSA8LSBzdHJlYW1zX3Bvc3RfbWFudXNpYWwgJT4lIA0KICBuYV9pZih5ID0gMCkgJT4lICMgcmVwbGFjZSAwIHdpdGggbmENCiAgZmlsbChzdHJlYW1zLCAuZGlyZWN0aW9uID0gInVwIikgDQpgYGANCg0KYGBge3J9DQpkYXlfY291bnRfc3RyZWFtX3Bvc3RfbWFudXNpYWxfZHVtbXkgPC0gc3RyZWFtc19wb3N0X21hbnVzaWFsX2R1bW15ICAlPiUgDQogIGNvdW50KGlzLm5hKHN0cmVhbXMpKQ0KZGF5X2NvdW50X3N0cmVhbV9wb3N0X21hbnVzaWFsX2R1bW15DQpgYGANCg0KSG93IHRvIGludGVycHJldCB0aGUgdGFibGUgYWJvdmU/DQpJdCB0b29rIDQ0IGRheXMgc2luY2UgdGhlIHJlbGVhc2Ugb2YgTWFudXNpYWwgRVAgdW50aWwgdGhlcmUgd2FzIGNvbXBsZXRlbHkgbm8gc3RyZWFtcyBhdCBhbGwgdGhhdCBsYXN0ZWQgZm9yIDE5IGRheXMgYmVmb3JlIHRoZSBuZXh0IHJlbGVhc2UuDQpCdXQgaWYgd2Ugd2FudCB0byBsb29rIGEgZ3JhcGggdGhhdCBpbGx1c3RyYXRlcyB0aGlzIHdpdGggYWN0dWFsIG51bWJlciBvZiBzdHJlYW1zLCBJIGNhbiBjcmVhdGUgbWFya2VycyBmb3IgdGhlIG9yaWdpbmFsIHN0cmVhbXMgY291bnQsIHRoZW4gZHJhdyB1cCBhIGxpbmUgcGxvdC4NCg0KYGBge3J9DQptYXJrZXJzX3Bvc3RfbWFudXNpYWxfZHVtbXlfcGxvdCA8LXN0cmVhbXNfcG9zdF9tYW51c2lhbF9kdW1teSAlPiUgDQogIG11dGF0ZShtYXJrZXJzID0gY2FzZV93aGVuKGlzLm5hKHN0cmVhbXMpIH4gIk5vIE9uZSBMaXN0ZW5lZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIlNvbWUgU3RyZWFtcyBIYXBwZW5lZCIpKQ0KYGBgDQoNCkJpbmQgdGhlIG1hcmtlcnMgY29sdW1uIHRvIHRoZSBvcmlnaW5hbCBzdHJlYW0gY2hvcHBlZCBkYXRhLg0KDQpgYGB7cn0NCnN0cmVhbXNfcG9zdF9tYW51c2lhbF9jb21wbGV0ZSA8LSBzdHJlYW1zX3Bvc3RfbWFudXNpYWwgJT4lIA0KICBjYmluZChtYXJrZXJzID0gbWFya2Vyc19wb3N0X21hbnVzaWFsX2R1bW15X3Bsb3QkbWFya2VycykNCmBgYA0KDQpWaXN1YWxpemUgdGhlIHN0cmVhbXMgdGltZWxpbmUsIHdpdGggbWFya2Vycy4NCg0KYGBge3J9DQpnZ3Bsb3Qoc3RyZWFtc19wb3N0X21hbnVzaWFsX2NvbXBsZXRlLCBhZXMoeD1kYXRlLHk9c3RyZWFtcywgY29sPW1hcmtlcnMpKSsNCiAgZ2VvbV9saW5lKCkrDQogIGxhYnModGl0bGUgPSAiU3RyZWFtcyBDb3VudCBhZnRlciBNYW51c2lhbCBFUCByZWxlYXNlIiwNCiAgICAgICAgc3VidGl0bGUgPSAiYW5kIGJlZm9yZSBBbnRpbmF0YWwgaW4gQyByZWxlYXNlIikNCmBgYA0KDQpOb3cgbGV0J3MgZG8gdGhpcyBmb3IgYWxsIHRoZSBvdGhlciB0aW1lbGluZXMgcmVsZWFzZXMuDQpJIGNyZWF0ZSAyIGZ1bmN0aW9ucyBiZWxsb3cgdG8gaGVscCBtZSBjb3VudCBkYXlzIGFuZCBwcmVwYXJlIGRhdGEgZm9yIHBsb3QuDQoNCmBgYHtyfQ0KIyBmb3IgY291bnQNCg0KZnVuY19kYXlDb3VudFN0cmVhbXMgPC0gZnVuY3Rpb24oc3RyZWFtc19wb3N0X2RmKXsNCg0KIyAxIGNyZWF0ZSBkdW1teSB3aXRoIG5hLCB0aGVuIGZpbGwgdXB3YXJkDQpzdHJlYW1zX3Bvc3RfZGZfZHVtbXkgPC0gc3RyZWFtc19wb3N0X2RmICU+JSANCiAgbmFfaWYoeSA9IDApICU+JSAjIHJlcGxhY2UgMCB3aXRoIG5hDQogIGZpbGwoc3RyZWFtcywgLmRpcmVjdGlvbiA9ICJ1cCIpIA0KDQojIDIgY3JlYXRlIHRhYmxlIGhvdyBtYW55IG5vdCBuYSwgaG93IG1hbnkgbmENCmRheV9jb3VudF9zdHJlYW1fcG9zdF9kZl9kdW1teSA8LSBzdHJlYW1zX3Bvc3RfZGZfZHVtbXkgICU+JSANCiAgY291bnQoaXMubmEoc3RyZWFtcykpDQoNCmRheV9jb3VudF9zdHJlYW1fcG9zdF9kZl9kdW1teQ0KfQ0KDQpgYGANCg0KYGBge3J9DQojIGZvciBwbG90DQoNCmZ1bmNfc3RyZWFtc0NvbXBsZXRlUGxvdCA8LSBmdW5jdGlvbihzdHJlYW1zX3Bvc3RfZGYpew0KDQojIDEgY3JlYXRlIGR1bW15IHdpdGggTkEsIHRoZW4gZmlsbCB1cHdhcmQgICAgDQpzdHJlYW1zX3Bvc3RfZGZfZHVtbXkgPC0gc3RyZWFtc19wb3N0X2RmICU+JSANCiAgbmFfaWYoeSA9IDApICU+JSAjIHJlcGxhY2UgMCB3aXRoIG5hDQogIGZpbGwoc3RyZWFtcywgLmRpcmVjdGlvbiA9ICJ1cCIpIA0KDQojIDIgY3JlYXRlIG1hcmtlcnMgDQptYXJrZXJzX3Bvc3RfZHVtbXlfcGxvdCA8LXN0cmVhbXNfcG9zdF9kZl9kdW1teSAlPiUgDQogIG11dGF0ZShtYXJrZXJzID0gY2FzZV93aGVuKGlzLm5hKHN0cmVhbXMpIH4gIk5vIE9uZSBMaXN0ZW5lZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIlNvbWUgU3RyZWFtcyBIYXBwZW5lZCIpKQ0KIyAzIGJpbmQgbWFya2VycyB0byBlYWNoIHN0cmVhbSBkYXRhDQptYXJrZXJzX3Bvc3RfZHVtbXlfcGxvdA0Kc3RyZWFtc19wb3N0X2NvbXBsZXRlIDwtIHN0cmVhbXNfcG9zdF9kZiAlPiUgDQogIGNiaW5kKG1hcmtlcnMgPSBtYXJrZXJzX3Bvc3RfZHVtbXlfcGxvdCRtYXJrZXJzKQ0KDQojIDQgY3JlYXRlIHRpdGxlDQp0aXRsZSA8LSBzdHJlYW1zX3Bvc3RfZGYgJT4lIHNlbGVjdChkaXNjX3RpdGxlKSAlPiUgc2xpY2VfaGVhZCgpICU+JSBwdWxsKCkNCnBsb3RfdGl0bGUgPSBwYXN0ZSgiU3RyZWFtcyBDb3VudCBvZiIsIHRpdGxlKQ0KDQojIDUgcGxvdCB0byBsaW5lIHBsb3QNCmdncGxvdChzdHJlYW1zX3Bvc3RfY29tcGxldGUsIGFlcyh4PWRhdGUseT1zdHJlYW1zKSkrDQogIGdlb21fbGluZShhZXMoY29sPW1hcmtlcnMpKSsNCiAgbGFicyh0aXRsZSA9IHBsb3RfdGl0bGUsDQogICAgICAgc3VidGl0bGUgPSAidW50aWwgdGhlIGRhdGUgYmVmb3JlIHN1YnNlcXVlbnQgcmVsZWFzZSB3YXMgYXZhaWxhYmxlIG9yIHRoZSBlbmQgb2YgMjAyMiIpKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9sYWJlbHMgPSAiJUYiKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWUgPSAiU3RyZWFtIFN0YXR1cyIsIA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJTb21lIFN0cmVhbXMgSGFwcGVuZWQiLCJObyBPbmUgTGlzdGVuZWQiKSwNCiAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygiU29tZSBTdHJlYW1zIEhhcHBlbmVkIj0iYmx1ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5vIE9uZSBMaXN0ZW5lZCI9InJlZCIpKSsNCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gIml0YWxpYyIsIHNpemUgPSA3KSwNCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KfQ0KDQoNCmBgYA0KDQpOb3cgbGV0J3MgYXBwbHkgdGhlc2UgZnVuY3Rpb25zIHRvIHN0cmVhbXNfbGlzdDoNCg0KYGBge3J9DQpsYXBwbHkoWCA9IHN0cmVhbXNfbGlzdCxGVU4gPSBmdW5jX2RheUNvdW50U3RyZWFtcykNCmBgYA0KDQpgYGB7cn0NCmxhcHBseShYID0gc3RyZWFtc19saXN0LEZVTiA9IGZ1bmNfc3RyZWFtc0NvbXBsZXRlUGxvdCkNCmBgYA0KDQoNCg0KU29tZSBub3RlcyBhYm91dCB0aGUgcmVzdWx0cyBhYm92ZTogMS4NClRoZSByZWxlYXNlIGRhdGUgb2YgVHVoYW4gQ2FidXQgTnlhd2FrdSBhbmQgRnJvdGggYW5kIEZvYW0gaXMgdG9vIGNsb3NlLCBhbmQgdGhlIHJlbGVhc2UgZGF0ZSBvZiBGcm90aCBhbmQgRm9hbSBhbmQgdGhlIGVuZCBvZiAyMDIxIGFzIGEgY3V0IG9mZiByYW5nZSBpcyBhbHNvIHRvbyBjbG9zZS4NClRoZSByZXN1bHRpbmcgY291bnRzIGFuZCBwbG90cyB3ZXJlIGEgYml0IG9mIGFuIG91dGxpZXIgc28gaXQncyBwcm9iYWJseSB3aXNlIHRvIGV4Y2x1ZGUgdGhlbSBmcm9tIGNvbmNsdXNpb24uDQoyLg0KVG8gYW5zd2VyIHRoZSB0YXNrLCBob3cgbWFueSBkYXlzIHNpbmNlIHRoZSBkYXRlIHJlbGVhc2VkIHRoYXQgc29uZyBzdHJlYW1zIHBsdW1tZXRlZD8NCkl0IGNvdWxkIGJlIGFzIHNob3J0IGFzIDQwIGRheXMsIG9yIGNvdWxkIGJlIGFzIGxvbmcgYXMgMjQyIGRheXMuDQoNCkxldCdzIHByb2NlZWQgdG8gdGhlIG5leHQgdGFzay4NCg0KIyMjICoqMS5jLiBUYXNrMzogIkRvZXMgcmVsZWFzaW5nIG11c2ljIGluIEVQIG9yIFNpbmdsZSBoYXZlIGRpZmZlcmVuY2VzIGluIHN0cmVhbXM/IioqIDxhIGlkPSJ0MyI+PC9hPg0KDQpUYWtlIGEgbG9vay4NCg0KYGBge3J9DQpwcmludChyZWNvcmRpbmdzX2VkaXQpDQpgYGANCg0KU3BsaXQgdGhlIGRhdGEgaW50byBFUHMgYW5kIFNpbmdsZXMNCg0KYGBge3J9DQpyZWNfZXAgPC0gcmVjb3JkaW5nc19lZGl0ICU+JSBmaWx0ZXIoZGlzY19mb3JtID09ICJFUCIpDQpyZWNfc2luZ2xlIDwtIHJlY29yZGluZ3NfZWRpdCAlPiUgZmlsdGVyKGRpc2NfZm9ybSA9PSAiU2luZ2xlIikNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChyZWNvcmRpbmdzX2VkaXQsIGFlcyh4PXJlb3JkZXIoc29uZywgc3RyZWFtcyksIHk9c3RyZWFtcykpKw0KICBnZW9tX2NvbChhZXMoeT0gc3RyZWFtcywgZmlsbD0iU3RyZWFtcyIpKSsNCiAgZ2VvbV9jb2woYWVzKHk9bGlzdGVuZXJzLCBmaWxsPSJMaXN0ZW5lcnMiKSkrDQogICAgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImxlZnQiKSsNCiAgIGNvb3JkX2ZsaXAoKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iU29uZyBTdGF0cyIsDQogICAgICAgICAgICAgICAgICAgICBicmVha3M9YygiU3RyZWFtcyIsIkxpc3RlbmVycyIpLA0KICAgICAgICAgICAgICAgICAgICAgdmFsdWVzPWMoIlN0cmVhbXMiPSJncmVlbiIsIkxpc3RlbmVycyI9ImJsdWUiKSkrDQogIGxhYnModGl0bGUgPSAiUmVjb3JkaW5ncyBNZXRyaWNzIikNCg0KZ2dwbG90KHJlY19lcCwgYWVzKHg9cmVvcmRlcihzb25nLCBzdHJlYW1zKSwgeT1zdHJlYW1zKSkrDQogIGdlb21fY29sKGFlcyh5PSBzdHJlYW1zLCBmaWxsPSJTdHJlYW1zIikpKw0KICBnZW9tX2NvbChhZXMoeT1saXN0ZW5lcnMsIGZpbGw9Ikxpc3RlbmVycyIpKSsNCiAgICB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibGVmdCIpKw0KICAgY29vcmRfZmxpcCgpKw0KICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSJTb25nIFN0YXRzIiwNCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1jKCJTdHJlYW1zIiwiTGlzdGVuZXJzIiksDQogICAgICAgICAgICAgICAgICAgICB2YWx1ZXM9YygiU3RyZWFtcyI9ImdyZWVuIiwiTGlzdGVuZXJzIj0iYmx1ZSIpKSsNCiAgbGFicyh0aXRsZSA9ICJFUCBNZXRyaWNzIikNCg0KZ2dwbG90KHJlY19zaW5nbGUsIGFlcyh4PXJlb3JkZXIoc29uZywgc3RyZWFtcyksIHk9c3RyZWFtcykpKw0KICBnZW9tX2NvbChhZXMoeT0gc3RyZWFtcywgZmlsbD0iU3RyZWFtcyIpKSsNCiAgZ2VvbV9jb2woYWVzKHk9bGlzdGVuZXJzLCBmaWxsPSJMaXN0ZW5lcnMiKSkrDQogICAgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImxlZnQiKSsNCiAgIGNvb3JkX2ZsaXAoKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iU29uZyBTdGF0cyIsDQogICAgICAgICAgICAgICAgICAgICBicmVha3M9YygiU3RyZWFtcyIsIkxpc3RlbmVycyIpLA0KICAgICAgICAgICAgICAgICAgICAgdmFsdWVzPWMoIlN0cmVhbXMiPSJncmVlbiIsIkxpc3RlbmVycyI9ImJsdWUiKSkrDQogIGxhYnModGl0bGUgPSAiU2luZ2xlIE1ldHJpY3MiKQ0KDQpgYGANCg0KVG8gc2VlIHdoZXRoZXIgb3Igbm90IHRoZXJlIGFyZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHR3byBncm91cCB3ZSBuZWVkIHRvIHBlcmZvcm0gYSBzdGF0aXN0aWNhbCB0ZXN0Lg0KRmlyc3QgSSBuZWVkIHRvIGNoZWNrIHRoZSBub3JtYWxpdHkgb2YgdGhlIGRhdGEsIHRvIGRlY2lkZSB3aGV0aGVyIG9yIG5vdCB0byB1c2UgcGFyYW1ldHJpYyBvciBub25wYXJhbWV0cmljIHRlc3QuDQoNCmBgYHtyfQ0KcmVjb3JkaW5nc19lZGl0ICU+JSANCiAgZ3JvdXBfYnkoZGlzY19mb3JtKSAlPiUgDQogIG5vcm1hbGl0eShzdHJlYW1zKQ0KYGBgDQoNCmBgYHtyfQ0KcmVjb3JkaW5nc19lZGl0ICU+JSANCiAgZ3JvdXBfYnkoZGlzY19mb3JtKSAlPiUgDQogIHBsb3Rfbm9ybWFsaXR5KHN0cmVhbXMpDQpgYGANCg0KYGBge3J9DQpyZWNvcmRpbmdzX2VkaXRbLGMoNiwzKV0gIA0KZ2dxcXBsb3QocmVjb3JkaW5nc19lZGl0WyxjKDYsMyldLCB4ID0gInN0cmVhbXMiKQ0KYGBgDQoNCkFzIHNob3duIGJ5IHRoZSB0YWJsZSBhbmQgcGxvdCBhYm92ZSwgdGhlIHN0cmVhbXMgYW1vbmcgdGhlbSBhcmUgcXVpdGUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuDQpXZSBjYW4gcHJvY2VlZCB3aXRoIGEgcGFyYW1ldHJpYyB0ZXN0Lg0KQnV0IHdpdGNoIG9uZT8NClRvIGNvbXBhcmUgdHdvIGdyb3VwcywgSSBjYW4gdXNlIGVpdGhlciBTdHVkZW50J3MgdC1UZXN0IG9yIFdlbGNoJ3MgdC1UZXN0Lg0KSWYgdGhlIGRhdGEgaGFzIGVxdWFsIHZhcmlhbmNlLCB3ZSdsbCBwcm9jZWVkIHdpdGggU3R1ZGVudCdzIHQtVGVzdCwgZWxzZSB3ZSBnbyB3aXRoIFdlbGNoJ3MgdC1UZXN0Lg0KDQpJZiBwLXZhbHVlIGZyb20gTGV2ZW5lVGVzdCBpcyBsb3dlciB0aGFuIDAuMDUsIGl0IG1lYW5zIHRoZSB2YXJpYW5jZSBiZXR3ZWVuIEVQIGFuZCBTaW5nbGUgaXMgbm90IGVxdWFsLg0KDQpgYGB7cn0NCmxldmVuZVRlc3Qoc3RyZWFtcyB+IGRpc2NfZm9ybSwgcmVjb3JkaW5nc19lZGl0WyxjKDYsMyldICkNCmBgYA0KDQpOdWxsIEh5cG90aGVzaXMgPSBUaGVyZSBpcyBubyBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHN0cmVhbXMgY291bnQgb2YgcmVsZWFzZXMgaW4gZm9ybSBvZiBTaW5nbGUgb3IgRVAuIA0KDQpBbHRlcm5hdGl2ZSBIeXBvdGhlc2lzID0gVGhlcmUgaXMgYSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHN0cmVhbXMgY291bnQgb2YgcmVsZWFzZXMgaW4gZm9ybSBvZiBTaW5nbGUgb3IgRVANCg0KYGBge3J9DQojIGxvYWQgdGhlIHBhY2thZ2UgDQpsaWJyYXJ5KGdnc3RhdHNwbG90KQ0KYGBgDQoNCmBgYHtyfQ0KZ2diZXR3ZWVuc3RhdHMoDQogIGRhdGEgPSByZWNvcmRpbmdzX2VkaXQsDQogIHggPSBkaXNjX2Zvcm0sDQogIHkgPSBzdHJlYW1zLA0KICB0eXBlID0gInBhcmFtZXRyaWMiLA0KICB2YXIuZXF1YWwgPSBGQUxTRQ0KKQ0KYGBgDQoNCkRlc3BpdGUgdGhlIEhlZGdlcydnIG9mIDAuODEgdGhhdCBpbXBsaWVzIGxhcmdlIGVmZmVjdCBzaXplLCBhbmQgcG9zdGVyaW9yIGRpZmZlcmVuY2Ugb2YgMjIuOCBzdHJlYW1zLg0KV2l0aCBwLnZhbHVlIG9mIDAuMTAgaXQgc2hvd3MgdGhhdCB0aGVyZSB3YXMgYSBsYWNrIG9mIGV2aWRlbmNlIHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzLg0KQWxzbywgd2l0aCB0aGUgYmF5ZXMgZmFjdG9yIG9mIDAuMDcgaXQgc2hvd3MgdGhhdCB0aGVyZSdzIG5vIGV2aWRlbmNlIHdvcnRoIHRoYW4gYSBiYXJlIG1lbnRpb24gdG8gcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMuDQoNCkkgY29uY2x1ZGUgdGhlcmUgYXJlIG5vIGRpZmZlcmVuY2VzIHdoYXRzb2V2ZXIgaW4gdGVybSBvZiBzdHJlYW1zIGNvdW50IHdoZXRoZXIgb3Igbm90IEkgcmVsZWFzZWQgbXkgbXVzaWMgaW4gRVAgb3Igc2luZ2xlLg0KDQpPbiB0byB0aGUgbmV4dCB0YXNrLg0KDQojIyMgKioxLmQuIFRhc2s0OiAiV2hhdCBtZXRyaWMgaGFzIG1vcmUgZWZmZWN0IHRvIGluY3JlYXNlIGluIGZvbGxvd2Vycz8iKiogPGEgaWQ9InQ0Ij48L2E+DQoNCkkgaW50ZW5kIHRvIGludmVzdGlnYXRlIHRoaXMgcmVsYXRpb25zaGlwcyBieSB1c2luZyByZWdyZXNzaW9uLg0KQnV0LCBhcyBtZW50aW9uZWQgaW4gdGhlIGRhdGEgcHJlcGFyYXRpb24gcGhhc2UsIGZvbGxvd2VycyBpcyBhIGN1bXVsYXRpdmUgbWV0cmljLCB0byBjb21wYXJlIGV2ZW5seSB3aXRoIGZvbGxvd2VycywgSSBjYW4gdXNlIHRoZSBjdW11bGF0aXZlIHZlcnNpb24gb2YgdGhlIG90aGVyIG1ldHJpY3MuDQpCdXQgaWYgSSB3YW50IHRvIHVzZSBub24tY3VtdWxhdGl2ZSBtZXRyaWNzLCBJIGNhbiB1c2UgdGhlIGZvbGxvd2Vyc19kaWZmIG1ldHJpYy4NCk1heWJlIEknbGwganVzdCBkbyBib3RoLg0KDQpgYGB7cn0NCnRsX2N1bSA8LSB0aW1lbGluZXNfZWRpdCAlPiUgc2VsZWN0KGZvbGxvd2VycywgY3VtX2xpc3RlbmVycywgY3VtX3N0cmVhbXMsIGNvdW50X2RheXMpDQp0bF9ub25jdW0gPC0gdGltZWxpbmVzX2VkaXQgJT4lIHNlbGVjdChmb2xsb3dlcnNfZGlmZiwgbGlzdGVuZXJzLCBzdHJlYW1zLCBkYXlfb2ZfeWVhciwgZGF5X29mX3dlZWspDQpgYGANCg0KRmlyc3QgbGV0J3MgY2hlY2sgb24gYXNzdW1wdGlvbiB0aGF0IHRoZSBSZXNwb25zZSBWYXJpYWJsZSBhbmQgRXhwbGFuYXRvcnkgVmFyaWFibGVzIGFyZSBsaW5lYXIuDQpgYGB7cn0NCnBsb3QodGxfY3VtKQ0KcGxvdCh0bF9ub25jdW0pDQpgYGANCg0KVGhleSdyZSBwcmV0dHkgbGluZWFyIGFscmlnaHQgaW4gdGhlIGN1bXVsYXRpdmUgZGF0YSwgYnV0IG5vdCBzbyBpbiB0aGUgaW4gdGhlIG5vbmN1bXVsYXRpdmUgZGF0YS4NCg0KTmV4dCwgd2Ugb2JzZXJ2ZSB3aGV0aGVyIG9yIG5vdCB0aGV5J3JlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkLg0KYGBge3J9DQpwbG90X25vcm1hbGl0eSh0bF9jdW0pDQpwbG90X25vcm1hbGl0eSh0bF9ub25jdW0pDQpgYGANCkxvb2tpbmcgYXQgdGhlIHBsb3QsIEkgd291bGQgc2F5IHRoYXQgdGhleSdyZSBub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuIA0KDQpJIHNob3VsZCBzdG9wLiANCg0KTm90IGp1c3QgYmVjYXVzZSB0aGUgZGF0YSBmYWlsIHRvIG1lZXQgdGhlIGFzc3VtcHRpb25zLiBCdXQgbW9yZSBzbyB0aGF0IGF0IHRoZSB0aW1lIEkgd3JpdGUgdGhpcywgSSd2ZSBoYWQgcmVjZWl2ZWQgYSBjb3Vuc2VsIGZyb20gYSBtZW50b3JpbmcgZ3JvdXAgdGhhdCBpbmZvcm1lZCBtZSwgd2hlbiBkZWFsaW5nIHdpdGggZGF0YSBzdWNoIGFzIHRoaXM/IEl0J3MgYmV0dGVyIHRvIGFwcHJvYWNoIHRoZW0gd2l0aCBUaW1lIFNlcmllcywgbm90IHdpdGggTGluZWFyIFJlZ3Jlc3Npb24uIA0KDQpTbyBoZXJlIEkgc3RvcC4NCkJ1dCEgDQpMZXQgbWUgdGFrZSBhIHRpbnkgcGVlayBhdCB0aGUgbW9kZWwhIA0KYGBge3J9DQp0bGN1bV9tb2RlbCA8LSBsbShmb3JtdWxhID0gZm9sbG93ZXJzIH4gY3VtX2xpc3RlbmVycyAqIGN1bV9zdHJlYW1zICsgY291bnRfZGF5cyArIDAsIA0KICAgICAgICAgICAgICAgICAgZGF0YSA9IHRsX2N1bSkNCnRsbm9uY3VtX21vZGVsIDwtIGxtKGZvcm11bGEgPSBmb2xsb3dlcnNfZGlmZiB+IGxpc3RlbmVycyAqIHN0cmVhbXMgKyBkYXlfb2Zfd2VlayArIDAsIA0KICAgICAgICAgICAgICAgICAgZGF0YSA9IHRsX25vbmN1bSkNCmBgYA0KYGBge3J9DQoNCnN1bW1hcnkodGxjdW1fbW9kZWwpDQpzdW1tYXJ5KHRsbm9uY3VtX21vZGVsKQ0KYGBgDQpDb29sLiANClRoZSBjdW11bGF0aXZlIG1vZGVsJ3MgZXN0aW1hdGUgc2F5cywgQ291bnQgb2YgZGF5cyBzaW5jZSB0aGUgZmlyc3QgdGltZSBteSBtdXNpY2UgcmVsZWFzZWQgb24gU3BvdGlmeSBoYXMgYmVlbiBoYXZpbmcgbW9yZSBpbXBhY3QgdGhhbiBzb21lIGN1bXVsYXRpdmUgc3RyZWFtcyBhbmQvb3IgbGlzdGVuZXJzIEkndmUgc2NyYXBwZWQgYWxvbmcgdGhlIHdheS4gDQoNCldoaWxlIHRoZSBub25jdW11bGF0aXZlIG1vZGVsJ3MgZXN0aW1hdGUgc2F5cyBsaXN0ZW5lcnMgYW5kIFRodXJzZGF5IGFyZSB0aGUgb25seSB0aGluZ3MgdGhhdCBtYXR0ZXIgYW5kIGhhdmUgcG9zaXRpdmUgaW1wYWN0LiBBbmQgc3RyZWFtcyBoYXMgbmVnYXRpdmUgZXN0aW1hdGUgdmFsdWUhIEJyYXZvISBUaGUgZGF0YSBkb2Vzbid0IGxpZS4gUGVyaGFwcyBiZWNhdXNlIG1vc3Qgc3RyZWFtcyBhcmUgY29taW5nIGZyb20gbXkgb3duIHN0cmVhbXMgdGh1cyBpdCBkb2Vzbid0IHJlYWxseSBpbXBhY3QgYW55IGltcGFjdCBpbiBmb2xsb3dlcnMuIFdoaWxlIG51bWJlciBvZiBsaXN0ZW5lcnMgYWN0dWFsbHkgYnJvYWRlbiBhbmQgaW5jcmVhc2UgdGhlIGNoYW5jZSBvZiBoYXZpbmcgbmV3IGZvbGxvd2Vycy4gDQoNCldlbGwsIHNpbmNlIHRoaXMgbGluZWFyIHJlZ3Jlc3Npb24gdGhpbmcgaXNuJ3QgdGhlIHJpZ2h0IGFwcHJvYWNoLCBJJ20gZ29ubmEgdGFrZSBpdCB3aXRoIGEgZ3JhaW4tLU5PISBBIHNwb29uZnVsIG9mIHNhbHQgYW5kIHdhc2ggaXQgZG93biB3aXRoIGFsYy0tQ29mZmVlISANCg0KRXZlbiB0aG91Z2ggSSB3YXMgb25seSBnb2luZyB0byBzZWUgdGhlIGVzdGltYXRlcywgbm93IEknbSB0ZW1wdGVkIHRvIHNlZSBhIGxpdHRsZSBwcmVkaWN0aW9uLg0KSGVyZSB3ZSBnbyBibGFtaW5nIHRoZSB0ZW1wYXRpb24uDQo+IkRhcm4sIHRlbXB0YXRpb24hIg0KDQpgYGB7cn0NCmV4cGxhbmF0b3J5X2RhdGExX2N1bSA8LSB0aW1lbGluZXNfZWRpdCAlPiUgc2VsZWN0KGN1bV9saXN0ZW5lcnMsIGN1bV9zdHJlYW1zLCBjb3VudF9kYXlzKQ0KZXhwbGFuYXRvcnlfZGF0YTJfY3VtIDwtIGRhdGEuZnJhbWUoY3VtX2xpc3RlbmVycyA9IHJvdW5kKHNlcSgyNDIsNDg0LDI0Mi83MzEpKSwNCiAgICAgICAgIGN1bV9zdHJlYW1zID0gcm91bmQoc2VxKDEwMzEsMjA2MiAsMTAzMS83MzEpKSwNCiAgICAgICAgIGNvdW50X2RheXMgPSA3MjM6KDcyMys3MzEpKQ0KZXhwbGFuYXRvcnlfZGF0YV9jdW0gPC0gcmJpbmQoZXhwbGFuYXRvcnlfZGF0YTFfY3VtLCBleHBsYW5hdG9yeV9kYXRhMl9jdW0pDQoNCmV4cGxhbmF0b3J5X2RhdGExX25vbmN1bSA8LSB0aW1lbGluZXNfZWRpdCAlPiUgc2VsZWN0KGxpc3RlbmVycywgc3RyZWFtcywgZGF5X29mX3dlZWspDQpleHBsYW5hdG9yeV9kYXRhMl9ub25jdW0gPC0gZXhwbGFuYXRvcnlfZGF0YTFfbm9uY3VtICU+JSBtdXRhdGUoDQogIGxpc3RlbmVycyA9IGxpc3RlbmVycyoyLA0KICBzdHJlYW1zID0gc3RyZWFtcyoyDQopDQpleHBsYW5hdG9yeV9kYXRhX25vbmN1bSA8LSByYmluZChleHBsYW5hdG9yeV9kYXRhMV9ub25jdW0sIGV4cGxhbmF0b3J5X2RhdGEyX25vbmN1bSkNCmBgYA0KYGBge3J9DQpwcmVkaWN0aW9uX2RhdGFfY3VtIDwtIGV4cGxhbmF0b3J5X2RhdGFfY3VtICU+JSANCiAgbXV0YXRlKGZvbGxvd2VycyA9IHByZWRpY3QodGxjdW1fbW9kZWwsIGV4cGxhbmF0b3J5X2RhdGFfY3VtKSkNCg0KcHJlZGljdGlvbl9kYXRhX25vbmN1bSA8LSBleHBsYW5hdG9yeV9kYXRhX25vbmN1bSAlPiUgDQogIG11dGF0ZShmb2xsb3dlcnNfZGlmZiA9IHByZWRpY3QodGxub25jdW1fbW9kZWwsIGV4cGxhbmF0b3J5X2RhdGFfbm9uY3VtKSkNCmBgYA0KYGBge3J9DQp0YWlsKHByZWRpY3Rpb25fZGF0YV9jdW0sIDMpDQpzdW0ocHJlZGljdGlvbl9kYXRhX25vbmN1bSRmb2xsb3dlcnNfZGlmZikNCmBgYA0KYGBge3J9DQpwbG90KHByZWRpY3Rpb25fZGF0YV9ub25jdW0kZm9sbG93ZXJzX2RpZmYpDQpgYGANCg0KYGBge3J9DQpjdW1fcGxvdCA8LSBnZ3Bsb3QodGxfY3VtLCBhZXMoeD0gY291bnRfZGF5cywgeT1mb2xsb3dlcnMsIGNvbD1jdW1fc3RyZWFtcywgc2l6ZT1jdW1fbGlzdGVuZXJzKSkrDQogIGdlb21fcG9pbnQoc2hhcGU9MSkrDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYihvcHRpb249IkQiKSsNCiAgZ2VvbV9qaXR0ZXIoZGF0YT1wcmVkaWN0aW9uX2RhdGFfY3VtLCBzaGFwZSA9IDIsIHdpZHRoID0gNiwgaGVpZ2h0ID0gMiwgYWxwaGE9MC4zKSsNCiAgbGFicyhjb2w9IkN1bXVsYXRpdmUgU3RyZWFtcyIsDQogICAgICAgc2l6ZT0iQ3VtdWxhdGl2ZSBMaXN0ZW5lcnMiLA0KICAgICAgIHggPSAiQ291bnQgb2YgRGF5cyBTaW5jZSBGaXJzdCBSZWxlYXNlIERhdGUiLA0KICAgICAgIHkgPSAiTnVtYmVyIG9mIEZvbGxvd2VycyIpDQoNCmN1bV9wbG90DQoNCm5vbmN1bV9wbG90IDwtIGdncGxvdCh0bF9ub25jdW0sIGFlcyh4PSBsaXN0ZW5lcnMsIHk9Zm9sbG93ZXJzX2RpZmYsIGNvbD1zdHJlYW1zKSkrDQogIGdlb21fcG9pbnQoc2hhcGU9MSkrDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhvcHRpb249IkQiKSsNCiAgZ2VvbV9qaXR0ZXIoZGF0YT1wcmVkaWN0aW9uX2RhdGFfbm9uY3VtLCBoZWlnaHQgPSAwLjA1LCB3aWR0aCA9IDMsIGFscGhhPTAuMywgc2hhcGUgPSAyKSsNCiAgbGFicyhjb2w9IlN0cmVhbXMiLA0KICAgICAgIHggPSAiTnVtYmVyIG9mIExpc3RlbmVycyIsDQogICAgICAgeSA9ICJJbmNyZWFzL0RlY3JlYXNlIGluIEZvbGxvd2VycyIpKw0KICBmYWNldF93cmFwKHZhcnMoZGF5X29mX3dlZWspLCBzY2FsZXMgPSAiZnJlZSIpDQoNCg0Kbm9uY3VtX3Bsb3QNCmBgYA0KUGVyaGFwJ3MgdGhhdCB3YXMgbm90IHRoZSByaWdodCBhcHByb2FjaCwgYnV0LCBkaWQgSSBoYXZlIGZ1biB3aGlsZSBwcmFjdGljaW5nIGRhdGEgc2NpZW5jZSBhbG9uZyB0aGUgd2F5PyBJIGRpZC4NCg0KTGV0J3MgbG9vayBhdCB0aGUgcGxvdC4gSXQncyBxdWl0ZSBzYW5lIGFuZCBsaW5lYXIgd2l0aCB0aGUgY3VtdWxhdGl2ZSBvbmUsIGJ1dCBhIGJpdCBmdW5reSB3aXRoIHRoZSBub25jdW11bGF0aXZlIG9uZS0td2hhdCBhIHdpbGQgd2VkbmVzZGF5IHRoYXQgcGxvdCEgU3RpbGwsIGJvdGggcmVzdWx0cyBiYXNpY2FsbHkgcHJvZHVjZWQgdHdpY2UgdGhlIFJlc3BvbnNlIFZhcmlhYmxlIG9mIHRoZSBvcmlnaW5hbCBkYXRhIHNldCBzaW5jZSBJIG1lcmVseSBkb3VibGVkIHRoZSBudW1lcmljIEV4cGxhbmF0b3J5IFZhcmlhYmxlcyBpbiBib3RoIG9mIHRoZW0uDQoNCldlbHAsIHNpbmNlIEkgZG9uJ3QgeWV0IHRvIGtub3cgaG93IHRvIGRvIFRpbWUgU2VyaWVzLCBhbmQgbGV0J3Mgc2F5IHRoZSByZWdyZXNzaW9uIGFib3ZlIGlzbid0IHRoZSByaWdodCBhcHByb2FjaC4gTWF5YmUgYXQgbGVhc3Qgd2UgY2FuIGxvb2sgYXQgdGhlIGdvb2Qgb2xkIGNvcnJlbGF0aW9uIGJlbGxvdy4gV2Uga25vdyB0aGUgcG9wdWxhciBzYXlpbmcgdGhhdCBzYXlzICJjb3JyZWxhdGlvbiBkb2VzIG5vdCBpbXBseSBjYXVzYXRpb24iLCBidXQgd2UgdG9vIG5lZWQgdG8gYWNrbm93bGVkZ2UgdGhhdCwgInRoZXJlIGlzIG5vIGNvcnJlbGF0aW9uIHdpdGhvdXQgY2F1c2F0aW9uIHNvbWV3aGVyZSItLXRoZXJlJ3MgcHJvYmFibHkgYSBjb21tb24gY2F1c2Ugc29tZXdoZXJlIHVwc3RyZWFtLg0KDQoNCmBgYHtyfQ0KcGxvdF9jb3JyZWxhdGlvbih0bF9jdW0pDQpwbG90X2NvcnJlbGF0aW9uKHRsX25vbmN1bSkNCmBgYA0KRm9sbG93ZXJzIGNvcnJlbGF0ZXMgbW9zdCB3aXRoIGN1bXVsYXRpdmUgc3RyZWFtcyBhbmQgY291bnQgb2YgZGF5cyBlcXVhbGx5IG9uIDAuOTYuIFdoaWxlLCBmb2xsb3dlcnMgZGlmZmVyZW5jZSBjb3JyZWxhdGVzIG1vc3Qgd2l0aCBsaXN0ZW5lcnMuIE5vdCBzbyBkaWZmZXJlbnQgd2l0aCB0aGUgZXN0aW1hdGUgY29lZmZpY2llbnRzIHdoZW4gSSBjYWxsZWQgc3VtbWFyeSBvbiB0aGUgcmVncmVzc2lvbiBtb2RlbHMgYmVmb3JlLg0KDQoNCiMjICoqMi4gUmVzdWx0cyoqIDxhIGlkPSJyZXMiPjwvYT4NCg0KIyMjICoqMi5hLiAiT24gd2hhdCBkYXkgb2Ygd2VlayBJIGhhdmUgdGhlIG1vc3Qgc3RyZWFtcywgb3IgbGlzdGVuZXJzLCBvciBpbmNyZWFzZSBpbiBmb2xsb3dlcnM/IioqIDxhIGlkPSIyYSI+PC9hPg0KVGhlIGRhdGEgc2hvd3MgdGhhdCBJIGhhdmUgdGhlIG1vc3Qgc3RyZWFtcyBhdCBGcmlkYXksIG1vc3QgbGlzdGVuZXJzIGF0IFR1ZXNkYXksIGFuZCBtb3N0IGluY3JlYXNlIGluIGZvbGxvd2VycyBhdCBUaHVyc2RheS4NCg0KTWV0cmljcyAgICAgICAgICAgICAgIHwgRGF5IG9mIFdlZWsNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS18IC0tLS0tLS0tLS0tLS0NClN0cmVhbXMgICAgICAgICAgICAgICB8IEZyaWRheQ0KTGlzdGVuZXJzICAgICAgICAgICAgIHwgVHVlc2RheQ0KSW5jcmVhc2UgaW4gRm9sbG93ZXJzIHwgVGh1cnNkYXkNCg0KDQojIyMgKioyLmIuICJIb3cgbWFueSBkYXlzIHNpbmNlIHRoZSBkYXRlIHJlbGVhc2VkIHRoYXQgc29uZyBzdHJlYW1zIHBsdW1tZXRlZD8iKiogPGEgaWQ9IjJiIj48L2E+DQpJdCBjb3VsZCBiZSBhcyBzaG9ydCBhcyA0MCBkYXlzLCBvciBjb3VsZCBiZSBhcyBsb25nIGFzIDI0MiBkYXlzLiANCg0KQ291bnQgb2YgRGF5cyBTdHJlYW1pbmcgU3RpbGwgSGFwcGVuZWQgfCAgICAgICAgICAgICAgIA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tDQo0MCA+PSAgICAgICAgICAgICAgICAgfCA8PTI0MiAgICAgICAgIA0KDQoNCg0KIyMjICoqMi5jLiAiRG9lcyByZWxlYXNpbmcgbXVzaWMgaW4gRVAgb3IgU2luZ2xlIGhhdmUgZGlmZmVyZW5jZXMgaW4gc3RyZWFtcz8iKiogPGEgaWQ9IjJjIj48L2E+DQpMYWNraW5nIGV2aWRlbmNlIHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzIHRoYXQgdGhlcmUncyBubyBkaWZmZXJlbmNlLg0KDQp8UC12YWx1ZSB8IA0KfC0tLS0tLS0tfA0KfDAuMTAgICAgfA0KDQp8TG9nIG5hdHVyYWwgb2YgQmF5ZXMgRmFjdG9yIHwgDQp8LS0tLS0tLS18DQp8MC4wNyAgICB8DQoNCg0KDQojIyMgKioyLmQuICJXaGF0IG1ldHJpYyBoYXMgbW9yZSBlZmZlY3QgdG8gaW5jcmVhc2UgaW4gZm9sbG93ZXJzPyIqKiA8YSBpZD0iMmQiPjwvYT4NClRvdGFsIGZvbGxvd2VycyBhbmQgRm9sbG93ZXJzIERpZmZlcmVuY2UgY29ycmVsYXRlIG1vc3Qgd2l0aDogIA0KDQpDdW11bGF0aXZlIE1ldHJpY3MgICAgICAgICB8IE5vbmN1bXVsYXRpdmUgTWV0cmljcw0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KQ291bnQgb2YgRGF5cyAwLjk2ICAgICAgICAgfCBOdW1iZXIgb2YgTGlzdGVuZXJzIHBlciBkYXkgMC4xOA0KQ3VtdWxhdGl2ZSBTdHJlYW1zIDAuOTYgICAgfCAtDQoNCg0KICANCiMgKipDLiBDT05DTFVTSU9OKiogPGEgaWQ9ImNvbmNsIj48L2E+DQpIb3cgd291bGQgSSBjb25jbHVkZSBhbGwgdGhpcz8gV2VsbCwgW2FjY29yZGluZyB0byBDYXNzaWUgS3p5cmtvdl0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3doYXRzLXRoZS1kaWZmZXJlbmNlLWJldHdlZW4tYW5hbHl0aWNzLWFuZC1zdGF0aXN0aWNzLWNkMzVkNDU3ZTE3KSwgd2hvIHdvcmtzIGFzIENoaWVmIERlY2lzaW9uIFNjaWVudGlzdCBhdCBHb29nbGUsIGRhdGEgYW5hbHl0aWNzIHNlcnZlcyB0byBmaW5kIGdvb2QgcXVlc3Rpb25zIGFuZCBwcm9iYWJseSBzaG91bGQgY29uY2x1ZGUgbm90aGluZyBvdXRzaWRlIHRoZSBkYXRhIGl0c2VsZiwgd2hpbGUgc3RhdGlzdGljcyBtYXkgc2VydmVzIHRvIGZpbmQgYXBwcm9wcmlhdGUgYW5zd2VycyBieSBpbmZlcnJpbmcgYmV5b25kIHRoZSBkYXRhLiANCg0KVGhlIHJlc3VsdHMgZm9yIHRhc2tzIGEgYW5kIGIsIG1pZ2h0IGxlYWQgbWUgdG8gYXNrIGZ1cnRoZXIgcXVlc3Rpb25zIHN1Y2ggYXM6DQoiV2h5IGRvZXMgc3RyZWFtcyBpbmNyZWFzZWQgb24gRnJpZGF5PyBTaG91bGQgSSB0cnkgdG8gQS9CIHRlc3QgcmVsZWFzZSBpbiBGcmlkYXkgYW5kIG5vdCBGcmlkYXk/IE9yIGlzIHRoZXJlIGFueSBzdHVkaWVzIGJlZm9yZSwgdGhhdCBhbHJlYWR5IGludmVzdGlnYXRlZCB0aGUgaW1wb3J0YW5jZSBvZiBGcmlkYXkgZm9yIG11c2ljIHN0cmVhbWluZz8iIA0KIkFyZSB0aGVzZSBzdHJlYW1zIG1vc3RseSBkb25lIG9uIHRoZSBzb25nIHJlY2VudGx5IHJlbGVhc2VkPyBPciBhbnkgcmVsZWFzZSBkYXRlIG1pZ2h0IG9mZmVyIGEgc3RhcnRpbmcgcG9pbnQgdG8gc3RyZWFtIG90aGVyIHNvbmdzPyBTbyBpcyBpdCBiZXR0ZXIgdG8gcmVsZWFzZSBmcmVxdWVudGx5IGF0IHNob3J0ZXIgaW50ZXJ2YWw/IFdoYXQgc29uZ3MgYXJlIHNob3J0IGxpdmVkLCB3aGF0IG1pZ2h0IGJlIHRoZSBjYXVzZT8gV2hhdHNvbmdzIGFyZSBnZW5lcmF0aW5nIG1hbnkgZGF5cyBvZiBzdHJlYW1zLCBpcyBpdCBiZWNhdXNlIHRoZXJlIGFyZSBvdGhlciBwZW9wbGUgd2hvIGFjdHVhbGx5IHN0cmVhbXMgdGhlbSwgb3RoZXIgdGhhbiBtZT8iDQoNCk1lYW53aGlsZSB0aGUgcmVzdWx0IGZvciB0YXNrIGMgbGVkIG1lIHRvIGNvbmNsdWRlIHRoYXQgaXQgZG9lc24ndCBtYXR0ZXIgd2hldGhlciB0byByZWxlYXNlIG15IG11c2ljIGluIEVQIG9yIFNpbmdsZS0td2hlbiBpdCBjb21lcyB0byBzdHJlYW0gY291bnRzLiBTbyB3aGVuIEkgd2FudCB0byByZWxlYXNlIHNvbWUgbXVzaWNzIGluIHRoZSBmdXR1cmUsIEkgY2FuIHdlaWdoIG90aGVyIGNvbnNpZGVyYXRpb25zIG1vcmUgdGhhbiBicm9vZGluZyBvdmVyIEVQIG9yIFNpbmdsZS4gTWF5YmUgcHJhY3RpY2FsaXR5IHN1Y2ggYXMgdGhlIGNvc3QgdG8gcmVsZWFzZSBFUCBvciBTaW5nbGUgc2hvdWxkIG1hdHRlciB0byBtZSBtb3JlLiANCg0KTGFzdGx5LCBmb3IgdGhlIHJlc3VsdCBvZiB0YXNrIGQsIGxldCdzIGp1c3Qgc2F5IHRoYXQgaXQgY2FuIGJlIGEgc3RhcnRpbmcgcG9pbnQgZm9yIGJldHRlciBzdGF0aXN0aWNhbCBpbnZlc3RpZ2F0aW9ucyB3aGVuIEknbSBtb3JlIGNhcGFibGUgb2YgaXQgbGF0ZXIuIA0KDQo=