This R Notebook is the complement to my blog post Benchmarking Modern GPUs for Maximum Cloud Cost Efficiency in Deep Learning.

This notebook is licensed under the MIT License. If you use the code or data visualization designs contained within this notebook, it would be greatly appreciated if proper attribution is given back to this notebook and/or myself. Thanks! :)

1 Setup

library(scales)
library(tidyverse)
── Attaching packages ────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 2.2.1.9000     ✔ purrr   0.2.4     
✔ tibble  1.3.4          ✔ dplyr   0.7.4     
✔ tidyr   0.7.2          ✔ stringr 1.2.0     
✔ readr   1.1.1          ✔ forcats 0.2.0     
── Conflicts ───────────────────────────────── tidyverse_conflicts() ──
✖ readr::col_factor() masks scales::col_factor()
✖ purrr::discard()    masks scales::discard()
✖ dplyr::filter()     masks stats::filter()
✖ dplyr::lag()        masks stats::lag()
library(RColorBrewer)
sessionInfo()
R version 3.4.2 (2017-09-28)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.1

Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base     

other attached packages:
 [1] RColorBrewer_1.1-2 forcats_0.2.0      stringr_1.2.0     
 [4] dplyr_0.7.4        purrr_0.2.4        readr_1.1.1       
 [7] tidyr_0.7.2        tibble_1.3.4       ggplot2_2.2.1.9000
[10] tidyverse_1.2.1    scales_0.5.0      

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.14     cellranger_1.1.0 compiler_3.4.2  
 [4] plyr_1.8.4       bindr_0.1        tools_3.4.2     
 [7] jsonlite_1.5     lubridate_1.7.1  nlme_3.1-131    
[10] gtable_0.2.0     lattice_0.20-35  pkgconfig_2.0.1 
[13] rlang_0.1.4      psych_1.7.8      cli_1.0.0       
[16] rstudioapi_0.7   yaml_2.1.14      parallel_3.4.2  
[19] haven_1.1.0      bindrcpp_0.2     xml2_1.1.1      
[22] httr_1.3.1       knitr_1.17       hms_0.4.0       
[25] grid_3.4.2       glue_1.2.0       R6_2.2.2        
[28] readxl_1.0.0     foreign_0.8-69   modelr_0.1.1    
[31] reshape2_1.4.2   magrittr_1.5     rvest_0.3.2     
[34] assertthat_0.2.0 mnormt_1.5-5     colorspace_1.3-2
[37] stringi_1.1.6    lazyeval_0.2.1   munsell_0.4.3   
[40] broom_0.4.3      crayon_1.3.4    

Set ggplot2 theme.

theme_set(theme_minimal(base_size=9, base_family="Source Sans Pro") +
            theme(plot.title = element_text(size=11, family="Source Sans Pro Bold"),
                  axis.title.x = element_blank(),
                  axis.title.y = element_blank(),
                  plot.subtitle = element_text(family="Source Sans Pro Semibold", color="#969696"),
                  plot.caption = element_text(size=6, color="#969696"),
                  axis.text.x = element_text(size = 7),
                  legend.position="none"))
relative <- function(x) {
  lab <- paste0(sprintf("%.2f", x), 'x')
}

Set known price rates from Google Compute Engine Pricing.

standard_cost_hr <- 0.0475
k80_cost_hr <- 0.450
p100_cost_hr <- 1.460
cpu16_cost_hr <- 0.120

Derive the remaining rates, in seconds.

k80_cost_s <- (k80_cost_hr + standard_cost_hr) / 3600
p100_cost_s <- (p100_cost_hr + standard_cost_hr) / 3600
cpu16_cost_s <- cpu16_cost_hr / 3600
cpu32_cost_s <- cpu16_cost_s * 2
# works like a Python dict
costs <- c(k80=k80_cost_s, p100=p100_cost_s, cpu16=cpu16_cost_s, cpu32=cpu32_cost_s)

2 Analysis

Create a helper function to return the results for all permutations of a given test file name.

# frameworks <- c('tensorflow','cntk')
# platforms <- c("p100", "k80", "cpu32", "cpu16")
labels <- c('p100-tensorflow','p100-cntk', 'k80-tensorflow','k80-cntk', 'cpu32-tensorflow','cpu16-tensorflow')
process_test_data <- function(file_name) {
  base_label <- 'k80-tensorflow'
  label_split <- str_split(base_label, "-")[[1]]
  label_str <- paste(label_split[1], label_split[2], sep="\n")
  results <- read_csv(sprintf("../logs/%s/%s_%s.csv", label_split[1], file_name, label_split[2]), col_types = cols()) %>%
              mutate(platform = label_split[1], framework = label_split[2]) %>%
              group_by(platform, framework) %>%
              summarize(total_time = sum(elapsed),
                        total_cost = total_time * costs[label_split[1]]) %>%
              mutate(label = label_str)
  
  base_total_time <- results %>% pull(total_time)
  base_total_cost <- results %>% pull(total_cost)
  
  labels_tf <- labels[!(labels %in% base_label)]
  
  for(i in 1:length(labels_tf)) {
      label = labels_tf[i]
      label_split <- str_split(label, "-")[[1]]
      label_str <- paste(label_split[1], label_split[2], sep="\n")
       temp_df <- read_csv(sprintf("../logs/%s/%s_%s.csv", label_split[1], file_name, label_split[2]), col_types = cols()) %>%
              mutate(platform = label_split[1],
                     framework = label_split[2]) %>%
              group_by(platform, framework) %>%
              summarize(total_time = sum(elapsed),
                        total_cost = total_time * costs[label_split[1]]) %>%
              ungroup() %>%
              mutate(label = label_str)
      
      results <- results %>% bind_rows(temp_df)
      
    }
  # Normalize
  results_final <- results %>%
              as_tibble() %>%
              mutate(total_time_norm = total_time / base_total_time,
                     total_cost_norm = total_cost / base_total_cost,
                     label = as_factor(label)
              )
  
  return(results_final)
  
}
process_test_data('cifar10_cnn')
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector

2.1 IMDB Bidirectional LSTM

df_imdb_lstm <- process_test_data("imdb_bidirectional_lstm")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
df_imdb_lstm
plot <- ggplot(df_imdb_lstm, aes(x=fct_rev(label), y=total_time_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_time_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Speed of Training Bidirectional LSTMs",
               x = "Platform",
               subtitle = "Total Model Training Time, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-1.png", plot, width=4, height=3)
plot <- ggplot(df_imdb_lstm, aes(x=fct_rev(label), y=total_cost_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_cost_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Cost of Training Bidirectional LSTMs",
               x = "Platform",
               subtitle = "Total Model Training Cost, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-2.png", plot, width=4, height=3)

2.2 IMDB Fasttext

df_imdb_fasttext <- process_test_data("imdb_fasttext")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
df_imdb_fasttext
plot <- ggplot(df_imdb_fasttext, aes(x=fct_rev(label), y=total_time_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_time_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Speed of Training fasttext",
               x = "Platform",
               subtitle = "Total Model Training Time, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-3.png", plot, width=4, height=3)
plot <- ggplot(df_imdb_fasttext, aes(x=fct_rev(label), y=total_cost_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_cost_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Cost of Training fasttext",
               x = "Platform",
               subtitle = "Total Model Training Cost, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-4.png", plot, width=4, height=3)

2.3 MNIST MLP

df_mnist_mlp <- process_test_data("mnist_mlp")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
df_mnist_mlp
plot <- ggplot(df_mnist_mlp, aes(x=fct_rev(label), y=total_time_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_time_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Speed of Training MLP for MNIST",
               x = "Platform",
               subtitle = "Total Model Training Time, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-5.png", plot, width=4, height=3)
plot <- ggplot(df_mnist_mlp, aes(x=fct_rev(label), y=total_cost_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_cost_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Cost of Training MLP for MNIST",
               x = "Platform",
               subtitle = "Total Model Training Cost, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-6.png", plot, width=4, height=3)

2.4 MNIST CNN

df_mnist_cnn <- process_test_data("mnist_cnn")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
df_mnist_cnn
plot <- ggplot(df_mnist_cnn, aes(x=fct_rev(label), y=total_time_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_time_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Speed of Training CNN for MNIST",
               x = "Platform",
               subtitle = "Total Model Training Time, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-7.png", plot, width=4, height=3)
plot <- ggplot(df_mnist_cnn, aes(x=fct_rev(label), y=total_cost_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_cost_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Cost of Training CNN for MNIST",
               x = "Platform",
               subtitle = "Total Model Training Cost, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-8.png", plot, width=4, height=3)

2.5 CIFAR10 Deep CNN + MLP

df_cifar10_cnn <- process_test_data("cifar10_cnn")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
df_cifar10_cnn
plot <- ggplot(df_cifar10_cnn, aes(x=fct_rev(label), y=total_time_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_time_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmark Speed of Training MLP + CNN for CIFAR10",
               x = "Platform",
               subtitle = "Total Model Training Time, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-9.png", plot, width=4, height=3)
plot <- ggplot(df_cifar10_cnn, aes(x=fct_rev(label), y=total_cost_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_cost_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmark Cost of Training MLP + CNN for CIFAR10",
               x = "Platform",
               subtitle = "Total Model Training Cost, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-10.png", plot, width=4, height=3)

2.6 LSTM Text Generation

df_lstm_text <- process_test_data("lstm_text_generation")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
df_lstm_text
plot <- ggplot(df_lstm_text, aes(x=fct_rev(label), y=total_time_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_time_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Speed of Training LSTM Text Generator",
               x = "Platform",
               subtitle = "Total Model Training Time, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-11.png", plot, width=4, height=3)
plot <- ggplot(df_lstm_text, aes(x=fct_rev(label), y=total_cost_norm, fill=platform)) +
          geom_bar(stat="identity") +
          geom_hline(yintercept = 1, linetype="dashed", color="#1a1a1a") +
          geom_text(aes(label = relative(total_cost_norm), color=platform), vjust=-0.2, family="Source Sans Pro Bold", size=3) +
          scale_y_continuous(labels = relative) +
          scale_fill_hue(l=50) +
          scale_color_hue(l=50) +
          labs(title = "Benchmarking Cost of Training LSTM Text Generator",
               x = "Platform",
               subtitle = "Total Model Training Cost, Relative to TensorFlow on K80 GPU",
               caption = "Max Woolf — minimaxir.com")
ggsave("dl-cpu-gpu-12.png", plot, width=4, height=3)

2.7 LICENSE

MIT License

Copyright (c) 2017 Max Woolf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

LS0tCnRpdGxlOiAiQmVuY2htYXJraW5nIE1vZGVybiBHUFVzIGZvciBNYXhpbXVtIENsb3VkIENvc3QgRWZmaWNpZW5jeSBpbiBEZWVwIExlYXJuaW5nIgphdXRob3I6ICJNYXggV29vbGYgKEBtaW5pbWF4aXIpIgpkYXRlOiAiMjAxNy0xMS0yOCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBtYXRoamF4OiBudWxsCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdG9jOiB5ZXMKLS0tCgpUaGlzIFIgTm90ZWJvb2sgaXMgdGhlIGNvbXBsZW1lbnQgdG8gbXkgYmxvZyBwb3N0IFtCZW5jaG1hcmtpbmcgTW9kZXJuIEdQVXMgZm9yIE1heGltdW0gQ2xvdWQgQ29zdCBFZmZpY2llbmN5IGluIERlZXAgTGVhcm5pbmddKGh0dHA6Ly9taW5pbWF4aXIuY29tLzIwMTcvMTEvYmVuY2htYXJrLWdwdXMvKS4KClRoaXMgbm90ZWJvb2sgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBJZiB5b3UgdXNlIHRoZSBjb2RlIG9yIGRhdGEgdmlzdWFsaXphdGlvbiBkZXNpZ25zIGNvbnRhaW5lZCB3aXRoaW4gdGhpcyBub3RlYm9vaywgaXQgd291bGQgYmUgZ3JlYXRseSBhcHByZWNpYXRlZCBpZiBwcm9wZXIgYXR0cmlidXRpb24gaXMgZ2l2ZW4gYmFjayB0byB0aGlzIG5vdGVib29rIGFuZC9vciBteXNlbGYuIFRoYW5rcyEgOikKCiMgU2V0dXAKCmBgYHtyfQpsaWJyYXJ5KHNjYWxlcykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQoKc2Vzc2lvbkluZm8oKQpgYGAKClNldCBnZ3Bsb3QyIHRoZW1lLgoKYGBge3J9CnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZT05LCBiYXNlX2ZhbWlseT0iU291cmNlIFNhbnMgUHJvIikgKwogICAgICAgICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiKSwKICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gU2VtaWJvbGQiLCBjb2xvcj0iIzk2OTY5NiIpLAogICAgICAgICAgICAgICAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZT02LCBjb2xvcj0iIzk2OTY5NiIpLAogICAgICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gNyksCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKQpgYGAKCmBgYHtyfQpyZWxhdGl2ZSA8LSBmdW5jdGlvbih4KSB7CiAgbGFiIDwtIHBhc3RlMChzcHJpbnRmKCIlLjJmIiwgeCksICd4JykKfQpgYGAKClNldCBrbm93biBwcmljZSByYXRlcyBmcm9tIFtHb29nbGUgQ29tcHV0ZSBFbmdpbmUgUHJpY2luZ10oaHR0cHM6Ly9jbG91ZC5nb29nbGUuY29tL2NvbXB1dGUvcHJpY2luZykuCgpgYGB7cn0Kc3RhbmRhcmRfY29zdF9ociA8LSAwLjA0NzUKazgwX2Nvc3RfaHIgPC0gMC40NTAKcDEwMF9jb3N0X2hyIDwtIDEuNDYwCmNwdTE2X2Nvc3RfaHIgPC0gMC4xMjAKYGBgCgpEZXJpdmUgdGhlIHJlbWFpbmluZyByYXRlcywgaW4gc2Vjb25kcy4KCmBgYHtyfQprODBfY29zdF9zIDwtIChrODBfY29zdF9ociArIHN0YW5kYXJkX2Nvc3RfaHIpIC8gMzYwMApwMTAwX2Nvc3RfcyA8LSAocDEwMF9jb3N0X2hyICsgc3RhbmRhcmRfY29zdF9ocikgLyAzNjAwCmNwdTE2X2Nvc3RfcyA8LSBjcHUxNl9jb3N0X2hyIC8gMzYwMApjcHUzMl9jb3N0X3MgPC0gY3B1MTZfY29zdF9zICogMgoKIyB3b3JrcyBsaWtlIGEgUHl0aG9uIGRpY3QKY29zdHMgPC0gYyhrODA9azgwX2Nvc3RfcywgcDEwMD1wMTAwX2Nvc3RfcywgY3B1MTY9Y3B1MTZfY29zdF9zLCBjcHUzMj1jcHUzMl9jb3N0X3MpCmBgYAoKIyBBbmFseXNpcwoKQ3JlYXRlIGEgaGVscGVyIGZ1bmN0aW9uIHRvIHJldHVybiB0aGUgcmVzdWx0cyBmb3IgYWxsIHBlcm11dGF0aW9ucyBvZiBhIGdpdmVuIHRlc3QgZmlsZSBuYW1lLgoKYGBge3J9CiMgZnJhbWV3b3JrcyA8LSBjKCd0ZW5zb3JmbG93JywnY250aycpCiMgcGxhdGZvcm1zIDwtIGMoInAxMDAiLCAiazgwIiwgImNwdTMyIiwgImNwdTE2IikKbGFiZWxzIDwtIGMoJ3AxMDAtdGVuc29yZmxvdycsJ3AxMDAtY250aycsICdrODAtdGVuc29yZmxvdycsJ2s4MC1jbnRrJywgJ2NwdTMyLXRlbnNvcmZsb3cnLCdjcHUxNi10ZW5zb3JmbG93JykKCgpwcm9jZXNzX3Rlc3RfZGF0YSA8LSBmdW5jdGlvbihmaWxlX25hbWUpIHsKICBiYXNlX2xhYmVsIDwtICdrODAtdGVuc29yZmxvdycKICBsYWJlbF9zcGxpdCA8LSBzdHJfc3BsaXQoYmFzZV9sYWJlbCwgIi0iKVtbMV1dCiAgbGFiZWxfc3RyIDwtIHBhc3RlKGxhYmVsX3NwbGl0WzFdLCBsYWJlbF9zcGxpdFsyXSwgc2VwPSJcbiIpCiAgcmVzdWx0cyA8LSByZWFkX2NzdihzcHJpbnRmKCIuLi9sb2dzLyVzLyVzXyVzLmNzdiIsIGxhYmVsX3NwbGl0WzFdLCBmaWxlX25hbWUsIGxhYmVsX3NwbGl0WzJdKSwgY29sX3R5cGVzID0gY29scygpKSAlPiUKICAgICAgICAgICAgICBtdXRhdGUocGxhdGZvcm0gPSBsYWJlbF9zcGxpdFsxXSwgZnJhbWV3b3JrID0gbGFiZWxfc3BsaXRbMl0pICU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KHBsYXRmb3JtLCBmcmFtZXdvcmspICU+JQogICAgICAgICAgICAgIHN1bW1hcml6ZSh0b3RhbF90aW1lID0gc3VtKGVsYXBzZWQpLAogICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9jb3N0ID0gdG90YWxfdGltZSAqIGNvc3RzW2xhYmVsX3NwbGl0WzFdXSkgJT4lCiAgICAgICAgICAgICAgbXV0YXRlKGxhYmVsID0gbGFiZWxfc3RyKQogIAogIGJhc2VfdG90YWxfdGltZSA8LSByZXN1bHRzICU+JSBwdWxsKHRvdGFsX3RpbWUpCiAgYmFzZV90b3RhbF9jb3N0IDwtIHJlc3VsdHMgJT4lIHB1bGwodG90YWxfY29zdCkKICAKICBsYWJlbHNfdGYgPC0gbGFiZWxzWyEobGFiZWxzICVpbiUgYmFzZV9sYWJlbCldCiAgCiAgZm9yKGkgaW4gMTpsZW5ndGgobGFiZWxzX3RmKSkgewogICAgICBsYWJlbCA9IGxhYmVsc190ZltpXQogICAgICBsYWJlbF9zcGxpdCA8LSBzdHJfc3BsaXQobGFiZWwsICItIilbWzFdXQogICAgICBsYWJlbF9zdHIgPC0gcGFzdGUobGFiZWxfc3BsaXRbMV0sIGxhYmVsX3NwbGl0WzJdLCBzZXA9IlxuIikKCiAgICAgICB0ZW1wX2RmIDwtIHJlYWRfY3N2KHNwcmludGYoIi4uL2xvZ3MvJXMvJXNfJXMuY3N2IiwgbGFiZWxfc3BsaXRbMV0sIGZpbGVfbmFtZSwgbGFiZWxfc3BsaXRbMl0pLCBjb2xfdHlwZXMgPSBjb2xzKCkpICU+JQogICAgICAgICAgICAgIG11dGF0ZShwbGF0Zm9ybSA9IGxhYmVsX3NwbGl0WzFdLAogICAgICAgICAgICAgICAgICAgICBmcmFtZXdvcmsgPSBsYWJlbF9zcGxpdFsyXSkgJT4lCiAgICAgICAgICAgICAgZ3JvdXBfYnkocGxhdGZvcm0sIGZyYW1ld29yaykgJT4lCiAgICAgICAgICAgICAgc3VtbWFyaXplKHRvdGFsX3RpbWUgPSBzdW0oZWxhcHNlZCksCiAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2Nvc3QgPSB0b3RhbF90aW1lICogY29zdHNbbGFiZWxfc3BsaXRbMV1dKSAlPiUKICAgICAgICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgICAgICAgbXV0YXRlKGxhYmVsID0gbGFiZWxfc3RyKQogICAgICAKICAgICAgcmVzdWx0cyA8LSByZXN1bHRzICU+JSBiaW5kX3Jvd3ModGVtcF9kZikKICAgICAgCiAgICB9CgogICMgTm9ybWFsaXplCiAgcmVzdWx0c19maW5hbCA8LSByZXN1bHRzICU+JQogICAgICAgICAgICAgIGFzX3RpYmJsZSgpICU+JQogICAgICAgICAgICAgIG11dGF0ZSh0b3RhbF90aW1lX25vcm0gPSB0b3RhbF90aW1lIC8gYmFzZV90b3RhbF90aW1lLAogICAgICAgICAgICAgICAgICAgICB0b3RhbF9jb3N0X25vcm0gPSB0b3RhbF9jb3N0IC8gYmFzZV90b3RhbF9jb3N0LAogICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGFzX2ZhY3RvcihsYWJlbCkKICAgICAgICAgICAgICApCiAgCiAgcmV0dXJuKHJlc3VsdHNfZmluYWwpCiAgCn0KCnByb2Nlc3NfdGVzdF9kYXRhKCdjaWZhcjEwX2NubicpCmBgYAoKIyMgSU1EQiBCaWRpcmVjdGlvbmFsIExTVE0KCmBgYHtyfQpkZl9pbWRiX2xzdG0gPC0gcHJvY2Vzc190ZXN0X2RhdGEoImltZGJfYmlkaXJlY3Rpb25hbF9sc3RtIikKCmRmX2ltZGJfbHN0bQpgYGAKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9pbWRiX2xzdG0sIGFlcyh4PWZjdF9yZXYobGFiZWwpLCB5PXRvdGFsX3RpbWVfbm9ybSwgZmlsbD1wbGF0Zm9ybSkpICsKICAgICAgICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogICAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yPSIjMWExYTFhIikgKwogICAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJlbGF0aXZlKHRvdGFsX3RpbWVfbm9ybSksIGNvbG9yPXBsYXRmb3JtKSwgdmp1c3Q9LTAuMiwgZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gQm9sZCIsIHNpemU9MykgKwogICAgICAgICAgCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcmVsYXRpdmUpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfaHVlKGw9NTApICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2h1ZShsPTUwKSArCiAgICAgICAgICBsYWJzKHRpdGxlID0gIkJlbmNobWFya2luZyBTcGVlZCBvZiBUcmFpbmluZyBCaWRpcmVjdGlvbmFsIExTVE1zIiwKICAgICAgICAgICAgICAgeCA9ICJQbGF0Zm9ybSIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIlRvdGFsIE1vZGVsIFRyYWluaW5nIFRpbWUsIFJlbGF0aXZlIHRvIFRlbnNvckZsb3cgb24gSzgwIEdQVSIsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikKCmdnc2F2ZSgiZGwtY3B1LWdwdS0xLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShkbC1jcHUtZ3B1LTEucG5nKQoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX2ltZGJfbHN0bSwgYWVzKHg9ZmN0X3JldihsYWJlbCksIHk9dG90YWxfY29zdF9ub3JtLCBmaWxsPXBsYXRmb3JtKSkgKwogICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgICAgICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3I9IiMxYTFhMWEiKSArCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcmVsYXRpdmUodG90YWxfY29zdF9ub3JtKSwgY29sb3I9cGxhdGZvcm0pLCB2anVzdD0tMC4yLCBmYW1pbHk9IlNvdXJjZSBTYW5zIFBybyBCb2xkIiwgc2l6ZT0zKSArCgogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHJlbGF0aXZlKSArCiAgICAgICAgICBzY2FsZV9maWxsX2h1ZShsPTUwKSArCiAgICAgICAgICBzY2FsZV9jb2xvcl9odWUobD01MCkgKwogICAgICAgICAgbGFicyh0aXRsZSA9ICJCZW5jaG1hcmtpbmcgQ29zdCBvZiBUcmFpbmluZyBCaWRpcmVjdGlvbmFsIExTVE1zIiwKICAgICAgICAgICAgICAgeCA9ICJQbGF0Zm9ybSIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIlRvdGFsIE1vZGVsIFRyYWluaW5nIENvc3QsIFJlbGF0aXZlIHRvIFRlbnNvckZsb3cgb24gSzgwIEdQVSIsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikKCmdnc2F2ZSgiZGwtY3B1LWdwdS0yLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShkbC1jcHUtZ3B1LTIucG5nKQoKIyMgSU1EQiBGYXN0dGV4dAoKYGBge3J9CmRmX2ltZGJfZmFzdHRleHQgPC0gcHJvY2Vzc190ZXN0X2RhdGEoImltZGJfZmFzdHRleHQiKQoKZGZfaW1kYl9mYXN0dGV4dApgYGAKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9pbWRiX2Zhc3R0ZXh0LCBhZXMoeD1mY3RfcmV2KGxhYmVsKSwgeT10b3RhbF90aW1lX25vcm0sIGZpbGw9cGxhdGZvcm0pKSArCiAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICAgICAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iIzFhMWExYSIpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByZWxhdGl2ZSh0b3RhbF90aW1lX25vcm0pLCBjb2xvcj1wbGF0Zm9ybSksIHZqdXN0PS0wLjIsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiLCBzaXplPTMpICsKICAgICAgICAgIAogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHJlbGF0aXZlKSArCiAgICAgICAgICBzY2FsZV9maWxsX2h1ZShsPTUwKSArCiAgICAgICAgICBzY2FsZV9jb2xvcl9odWUobD01MCkgKwogICAgICAgICAgbGFicyh0aXRsZSA9ICJCZW5jaG1hcmtpbmcgU3BlZWQgb2YgVHJhaW5pbmcgZmFzdHRleHQiLAogICAgICAgICAgICAgICB4ID0gIlBsYXRmb3JtIiwKICAgICAgICAgICAgICAgc3VidGl0bGUgPSAiVG90YWwgTW9kZWwgVHJhaW5pbmcgVGltZSwgUmVsYXRpdmUgdG8gVGVuc29yRmxvdyBvbiBLODAgR1BVIiwKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iKQoKZ2dzYXZlKCJkbC1jcHUtZ3B1LTMucG5nIiwgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTMpCmBgYAoKIVtdKGRsLWNwdS1ncHUtMy5wbmcpCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfaW1kYl9mYXN0dGV4dCwgYWVzKHg9ZmN0X3JldihsYWJlbCksIHk9dG90YWxfY29zdF9ub3JtLCBmaWxsPXBsYXRmb3JtKSkgKwogICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgICAgICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3I9IiMxYTFhMWEiKSArCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcmVsYXRpdmUodG90YWxfY29zdF9ub3JtKSwgY29sb3I9cGxhdGZvcm0pLCB2anVzdD0tMC4yLCBmYW1pbHk9IlNvdXJjZSBTYW5zIFBybyBCb2xkIiwgc2l6ZT0zKSArCgogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHJlbGF0aXZlKSArCiAgICAgICAgICBzY2FsZV9maWxsX2h1ZShsPTUwKSArCiAgICAgICAgICBzY2FsZV9jb2xvcl9odWUobD01MCkgKwogICAgICAgICAgbGFicyh0aXRsZSA9ICJCZW5jaG1hcmtpbmcgQ29zdCBvZiBUcmFpbmluZyBmYXN0dGV4dCIsCiAgICAgICAgICAgICAgIHggPSAiUGxhdGZvcm0iLAogICAgICAgICAgICAgICBzdWJ0aXRsZSA9ICJUb3RhbCBNb2RlbCBUcmFpbmluZyBDb3N0LCBSZWxhdGl2ZSB0byBUZW5zb3JGbG93IG9uIEs4MCBHUFUiLAogICAgICAgICAgICAgICBjYXB0aW9uID0gIk1heCBXb29sZiDigJQgbWluaW1heGlyLmNvbSIpCgpnZ3NhdmUoImRsLWNwdS1ncHUtNC5wbmciLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MykKYGBgCgohW10oZGwtY3B1LWdwdS00LnBuZykKCiMjIE1OSVNUIE1MUAoKYGBge3J9CmRmX21uaXN0X21scCA8LSBwcm9jZXNzX3Rlc3RfZGF0YSgibW5pc3RfbWxwIikKCmRmX21uaXN0X21scApgYGAKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9tbmlzdF9tbHAsIGFlcyh4PWZjdF9yZXYobGFiZWwpLCB5PXRvdGFsX3RpbWVfbm9ybSwgZmlsbD1wbGF0Zm9ybSkpICsKICAgICAgICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogICAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yPSIjMWExYTFhIikgKwogICAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJlbGF0aXZlKHRvdGFsX3RpbWVfbm9ybSksIGNvbG9yPXBsYXRmb3JtKSwgdmp1c3Q9LTAuMiwgZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gQm9sZCIsIHNpemU9MykgKwogICAgICAgICAgCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcmVsYXRpdmUpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfaHVlKGw9NTApICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2h1ZShsPTUwKSArCiAgICAgICAgICBsYWJzKHRpdGxlID0gIkJlbmNobWFya2luZyBTcGVlZCBvZiBUcmFpbmluZyBNTFAgZm9yIE1OSVNUIiwKICAgICAgICAgICAgICAgeCA9ICJQbGF0Zm9ybSIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIlRvdGFsIE1vZGVsIFRyYWluaW5nIFRpbWUsIFJlbGF0aXZlIHRvIFRlbnNvckZsb3cgb24gSzgwIEdQVSIsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikKCmdnc2F2ZSgiZGwtY3B1LWdwdS01LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShkbC1jcHUtZ3B1LTUucG5nKQoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX21uaXN0X21scCwgYWVzKHg9ZmN0X3JldihsYWJlbCksIHk9dG90YWxfY29zdF9ub3JtLCBmaWxsPXBsYXRmb3JtKSkgKwogICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgICAgICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3I9IiMxYTFhMWEiKSArCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcmVsYXRpdmUodG90YWxfY29zdF9ub3JtKSwgY29sb3I9cGxhdGZvcm0pLCB2anVzdD0tMC4yLCBmYW1pbHk9IlNvdXJjZSBTYW5zIFBybyBCb2xkIiwgc2l6ZT0zKSArCgogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHJlbGF0aXZlKSArCiAgICAgICAgICBzY2FsZV9maWxsX2h1ZShsPTUwKSArCiAgICAgICAgICBzY2FsZV9jb2xvcl9odWUobD01MCkgKwogICAgICAgICAgbGFicyh0aXRsZSA9ICJCZW5jaG1hcmtpbmcgQ29zdCBvZiBUcmFpbmluZyBNTFAgZm9yIE1OSVNUIiwKICAgICAgICAgICAgICAgeCA9ICJQbGF0Zm9ybSIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIlRvdGFsIE1vZGVsIFRyYWluaW5nIENvc3QsIFJlbGF0aXZlIHRvIFRlbnNvckZsb3cgb24gSzgwIEdQVSIsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikKCmdnc2F2ZSgiZGwtY3B1LWdwdS02LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShkbC1jcHUtZ3B1LTYucG5nKQoKIyMgTU5JU1QgQ05OCgpgYGB7cn0KZGZfbW5pc3RfY25uIDwtIHByb2Nlc3NfdGVzdF9kYXRhKCJtbmlzdF9jbm4iKQoKZGZfbW5pc3RfY25uCmBgYAoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX21uaXN0X2NubiwgYWVzKHg9ZmN0X3JldihsYWJlbCksIHk9dG90YWxfdGltZV9ub3JtLCBmaWxsPXBsYXRmb3JtKSkgKwogICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgICAgICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3I9IiMxYTFhMWEiKSArCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcmVsYXRpdmUodG90YWxfdGltZV9ub3JtKSwgY29sb3I9cGxhdGZvcm0pLCB2anVzdD0tMC4yLCBmYW1pbHk9IlNvdXJjZSBTYW5zIFBybyBCb2xkIiwgc2l6ZT0zKSArCiAgICAgICAgICAKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSByZWxhdGl2ZSkgKwogICAgICAgICAgc2NhbGVfZmlsbF9odWUobD01MCkgKwogICAgICAgICAgc2NhbGVfY29sb3JfaHVlKGw9NTApICsKICAgICAgICAgIGxhYnModGl0bGUgPSAiQmVuY2htYXJraW5nIFNwZWVkIG9mIFRyYWluaW5nIENOTiBmb3IgTU5JU1QiLAogICAgICAgICAgICAgICB4ID0gIlBsYXRmb3JtIiwKICAgICAgICAgICAgICAgc3VidGl0bGUgPSAiVG90YWwgTW9kZWwgVHJhaW5pbmcgVGltZSwgUmVsYXRpdmUgdG8gVGVuc29yRmxvdyBvbiBLODAgR1BVIiwKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iKQoKZ2dzYXZlKCJkbC1jcHUtZ3B1LTcucG5nIiwgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTMpCmBgYAoKIVtdKGRsLWNwdS1ncHUtNy5wbmcpCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfbW5pc3RfY25uLCBhZXMoeD1mY3RfcmV2KGxhYmVsKSwgeT10b3RhbF9jb3N0X25vcm0sIGZpbGw9cGxhdGZvcm0pKSArCiAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICAgICAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iIzFhMWExYSIpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByZWxhdGl2ZSh0b3RhbF9jb3N0X25vcm0pLCBjb2xvcj1wbGF0Zm9ybSksIHZqdXN0PS0wLjIsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiLCBzaXplPTMpICsKCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcmVsYXRpdmUpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfaHVlKGw9NTApICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2h1ZShsPTUwKSArCiAgICAgICAgICBsYWJzKHRpdGxlID0gIkJlbmNobWFya2luZyBDb3N0IG9mIFRyYWluaW5nIENOTiBmb3IgTU5JU1QiLAogICAgICAgICAgICAgICB4ID0gIlBsYXRmb3JtIiwKICAgICAgICAgICAgICAgc3VidGl0bGUgPSAiVG90YWwgTW9kZWwgVHJhaW5pbmcgQ29zdCwgUmVsYXRpdmUgdG8gVGVuc29yRmxvdyBvbiBLODAgR1BVIiwKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iKQoKZ2dzYXZlKCJkbC1jcHUtZ3B1LTgucG5nIiwgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTMpCmBgYAoKIVtdKGRsLWNwdS1ncHUtOC5wbmcpCgojIyBDSUZBUjEwIERlZXAgQ05OICsgTUxQCgpgYGB7cn0KZGZfY2lmYXIxMF9jbm4gPC0gcHJvY2Vzc190ZXN0X2RhdGEoImNpZmFyMTBfY25uIikKCmRmX2NpZmFyMTBfY25uCmBgYAoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX2NpZmFyMTBfY25uLCBhZXMoeD1mY3RfcmV2KGxhYmVsKSwgeT10b3RhbF90aW1lX25vcm0sIGZpbGw9cGxhdGZvcm0pKSArCiAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICAgICAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iIzFhMWExYSIpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByZWxhdGl2ZSh0b3RhbF90aW1lX25vcm0pLCBjb2xvcj1wbGF0Zm9ybSksIHZqdXN0PS0wLjIsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiLCBzaXplPTMpICsKICAgICAgICAgIAogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHJlbGF0aXZlKSArCiAgICAgICAgICBzY2FsZV9maWxsX2h1ZShsPTUwKSArCiAgICAgICAgICBzY2FsZV9jb2xvcl9odWUobD01MCkgKwogICAgICAgICAgbGFicyh0aXRsZSA9ICJCZW5jaG1hcmsgU3BlZWQgb2YgVHJhaW5pbmcgTUxQICsgQ05OIGZvciBDSUZBUjEwIiwKICAgICAgICAgICAgICAgeCA9ICJQbGF0Zm9ybSIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIlRvdGFsIE1vZGVsIFRyYWluaW5nIFRpbWUsIFJlbGF0aXZlIHRvIFRlbnNvckZsb3cgb24gSzgwIEdQVSIsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikKCmdnc2F2ZSgiZGwtY3B1LWdwdS05LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShkbC1jcHUtZ3B1LTkucG5nKQoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX2NpZmFyMTBfY25uLCBhZXMoeD1mY3RfcmV2KGxhYmVsKSwgeT10b3RhbF9jb3N0X25vcm0sIGZpbGw9cGxhdGZvcm0pKSArCiAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICAgICAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iIzFhMWExYSIpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByZWxhdGl2ZSh0b3RhbF9jb3N0X25vcm0pLCBjb2xvcj1wbGF0Zm9ybSksIHZqdXN0PS0wLjIsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiLCBzaXplPTMpICsKCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcmVsYXRpdmUpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfaHVlKGw9NTApICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2h1ZShsPTUwKSArCiAgICAgICAgICBsYWJzKHRpdGxlID0gIkJlbmNobWFyayBDb3N0IG9mIFRyYWluaW5nIE1MUCArIENOTiBmb3IgQ0lGQVIxMCIsCiAgICAgICAgICAgICAgIHggPSAiUGxhdGZvcm0iLAogICAgICAgICAgICAgICBzdWJ0aXRsZSA9ICJUb3RhbCBNb2RlbCBUcmFpbmluZyBDb3N0LCBSZWxhdGl2ZSB0byBUZW5zb3JGbG93IG9uIEs4MCBHUFUiLAogICAgICAgICAgICAgICBjYXB0aW9uID0gIk1heCBXb29sZiDigJQgbWluaW1heGlyLmNvbSIpCgpnZ3NhdmUoImRsLWNwdS1ncHUtMTAucG5nIiwgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTMpCmBgYAoKIVtdKGRsLWNwdS1ncHUtMTAucG5nKQoKIyMgTFNUTSBUZXh0IEdlbmVyYXRpb24KCmBgYHtyfQpkZl9sc3RtX3RleHQgPC0gcHJvY2Vzc190ZXN0X2RhdGEoImxzdG1fdGV4dF9nZW5lcmF0aW9uIikKCmRmX2xzdG1fdGV4dApgYGAKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9sc3RtX3RleHQsIGFlcyh4PWZjdF9yZXYobGFiZWwpLCB5PXRvdGFsX3RpbWVfbm9ybSwgZmlsbD1wbGF0Zm9ybSkpICsKICAgICAgICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogICAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yPSIjMWExYTFhIikgKwogICAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJlbGF0aXZlKHRvdGFsX3RpbWVfbm9ybSksIGNvbG9yPXBsYXRmb3JtKSwgdmp1c3Q9LTAuMiwgZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gQm9sZCIsIHNpemU9MykgKwogICAgICAgICAgCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcmVsYXRpdmUpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfaHVlKGw9NTApICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2h1ZShsPTUwKSArCiAgICAgICAgICBsYWJzKHRpdGxlID0gIkJlbmNobWFya2luZyBTcGVlZCBvZiBUcmFpbmluZyBMU1RNIFRleHQgR2VuZXJhdG9yIiwKICAgICAgICAgICAgICAgeCA9ICJQbGF0Zm9ybSIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIlRvdGFsIE1vZGVsIFRyYWluaW5nIFRpbWUsIFJlbGF0aXZlIHRvIFRlbnNvckZsb3cgb24gSzgwIEdQVSIsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikKCmdnc2F2ZSgiZGwtY3B1LWdwdS0xMS5wbmciLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MykKYGBgCgohW10oZGwtY3B1LWdwdS0xMS5wbmcpCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfbHN0bV90ZXh0LCBhZXMoeD1mY3RfcmV2KGxhYmVsKSwgeT10b3RhbF9jb3N0X25vcm0sIGZpbGw9cGxhdGZvcm0pKSArCiAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICAgICAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iIzFhMWExYSIpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByZWxhdGl2ZSh0b3RhbF9jb3N0X25vcm0pLCBjb2xvcj1wbGF0Zm9ybSksIHZqdXN0PS0wLjIsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiLCBzaXplPTMpICsKCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcmVsYXRpdmUpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfaHVlKGw9NTApICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2h1ZShsPTUwKSArCiAgICAgICAgICBsYWJzKHRpdGxlID0gIkJlbmNobWFya2luZyBDb3N0IG9mIFRyYWluaW5nIExTVE0gVGV4dCBHZW5lcmF0b3IiLAogICAgICAgICAgICAgICB4ID0gIlBsYXRmb3JtIiwKICAgICAgICAgICAgICAgc3VidGl0bGUgPSAiVG90YWwgTW9kZWwgVHJhaW5pbmcgQ29zdCwgUmVsYXRpdmUgdG8gVGVuc29yRmxvdyBvbiBLODAgR1BVIiwKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iKQoKZ2dzYXZlKCJkbC1jcHUtZ3B1LTEyLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShkbC1jcHUtZ3B1LTEyLnBuZykKCiMjIExJQ0VOU0UKCk1JVCBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMTcgTWF4IFdvb2xmCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLg==