1 Objektif

Ini adalah catatan untuk memandu secara singkat dalam melakukan visualisasi data untuk kebutuhan eksplorasi data atau publikasi menggunakan R dan package ggplot2. Tujuan dari tutorial ini adalah agar peserta dapat mencoba membuat grafik yang menarik dan mudah.

2 Cakupan Materi

Materi yang akan dibahas:

  • Dasar-dasar ggplot2
  • Penggunaan fungsi qplot()
  • Aesthetic pada ggplot2
  • Barplot
  • Histogram & Density
  • Boxplot
  • Scatter plot
  • Line plot
  • Menentukan warna
  • Faceting
  • Annotation
  • Tema (theme)

3 Prasyarat

Untuk dapat mengikuti tutorial ini dengan baik, ada beberapa hal yang perlu dipersiapkan, yaitu:

  1. Koneksi internet yang baik

  2. Menginstall software

    1. R program https://cran.r-project.org/

    2. RStudio https://www.rstudio.com/products/rstudio/download/

  3. Data & Script yang dapat diperoleh dari repository ini dan pada database yang disediakan pembicara.

  4. Package R yang dibutuhkan: ggplot2, dplyr (atau tidyverse) dan nycflights13.

Catatan: Data diperoleh dari packages nycflights13 yang disimpan ke dalam database. Jika Anda ingin mencoba diluar kegiatan atau tidak dapat terhubung dengan database pembicara, Anda dapat menginstall package nycflights13 untuk memperoleh data yang digunakan pada database. Dua data lain berupa file CSV untuk disesuaikan dengan kebutuhan tutorial.

Data ini terdiri dari 336,776 penerbangan dari New York City (NYC) selama tahun 2013. Data asli berasal dari US Bureau of Transportation Statistics, dan dapat dilihat dokumentasinya dengan ?nycflights13::flights. Pastikan Anda sudah berhasil install package tersebut.

4 Install dan Load Packages

Jalankan perintah di bawah ini untuk install package (jika Anda belum pernah install) yang akan digunakan untuk dapat mengikuti tutorial ini sampai selesai.

install.packages(c("ggplot2", "dplyr", "tidyr", "nycflights13"))
# atau                                                                         
install.packages(c("tidyverse", "nycflights13"))                         

Panggil package yang sudah Anda install dengan fungsi library().

# Panggil package yang sudah terisntall
library(ggplot2)
library(dplyr)
library(tidyr)

# # atau cukup memanggil `tidyverse` untuk memanggil package ggplot2 dan package lain di tidyverse
# library(tidyverse)

library(nycflights13)

Package ggplot2 (dan beberapa package lain yang tidak digunakan di tutorial ini) termasuk dalam bagian package tidyverse. tidyverse adalah kumpulan package yang dibuat oleh Hadley Wickham dkk untuk kebutuhan data science menggunakan R.

  • ggplot2 adalah salah satu package yang sangat banyak digunakan oleh pengguna R untuk kebutuhan visualisasi.
  • dplyr dan tidyr adalah package yang sangat berguna untuk melakukan manipulasi/transformasi data menggunakan R.
  • nycflights13 adalah package yang menyediakan 5 data frame dalam format tibble tentang penerbangan di NYC selama tahun 2013.

Tidyverse

Tidyverse

tidyverse menggunakan tibble sebagai pengganti data.frame.

Tibbles are data frames, but they tweak some older behaviours to make life a little easier. R is an old language, and some things that were useful 10 or 20 years ago now get in your way. It’s difficult to change base R without breaking existing code, so most innovation occurs in packages – Grolemund & Wickham.

4.1 Operator Pipe %>%

Sebelum melangkah lebih jauh, kita harus mengeksploitasi operator pipe yang diimpor dari package magrittr oleh Stefan Bache. Ini akan mengubah kehidupan analitik data Anda. Anda tidak perlu lagi memberlakukan perintah multi-operasi dengan menyatukannya di dalam satu sama lain. Sintaks baru ini mengarah ke kode yang lebih mudah untuk ditulis dan dibaca.

Begini tampilannya: %>%. Pintasan keyboard RStudio: Ctrl + Shift + M (Windows), Cmd + Shift + M (Mac). Lebih lanjut mengenai operator %>% silahkan baca penjelasan operator pipe.

5 Eksplorasi dan Visualisasi Data

Kita lihat terlebih dulu data yang akan kita gunakan. Data yang akan digunakan ada 3, yaitu flights, airlines dan weather.

flights

Ada 336,776 jadwal penerbangan selama tahun 2013. Variabel dep_delay dengan nilai positif (dep_delay > 0) menunjukkan bahwa penerbangan tersebut mengalami keterlambatan dari jadwal berangkat yang seharusnya, sedangkan dep_delay negatif (dep_delay < 0) menunjukkan penerbanga dilakukan lebih awal dari yang seharusnya, dalam satuan menit. Adapun dep_delay yang NA (kosong atau missing value) menunjukkan penerbangan dibatalkan atau cancel.

airlines

Di data ini terdapat 16 perusahaan maskapai penerbangan. Variabel carrier nantinya digunakan untuk di-join dengan variabel carrier juga pada data flights.

weather

Data di atas adalah data cuaca berdasarkan bandara yang ada pada varibel origin selama tahun 2013 dengan rentang waktu setiap satu jam (lihat time_hour).

Sekarang kita lihat dulu secara sekilas seperti apa data flights yang akan kita gunakan.

str(flights)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   336776 obs. of  19 variables:
 $ year          : int  2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 ...
 $ month         : int  1 1 1 1 1 1 1 1 1 1 ...
 $ day           : int  1 1 1 1 1 1 1 1 1 1 ...
 $ dep_time      : int  517 533 542 544 554 554 555 557 557 558 ...
 $ sched_dep_time: int  515 529 540 545 600 558 600 600 600 600 ...
 $ dep_delay     : num  2 4 2 -1 -6 -4 -5 -3 -3 -2 ...
 $ arr_time      : int  830 850 923 1004 812 740 913 709 838 753 ...
 $ sched_arr_time: int  819 830 850 1022 837 728 854 723 846 745 ...
 $ arr_delay     : num  11 20 33 -18 -25 12 19 -14 -8 8 ...
 $ carrier       : chr  "UA" "UA" "AA" "B6" ...
 $ flight        : int  1545 1714 1141 725 461 1696 507 5708 79 301 ...
 $ tailnum       : chr  "N14228" "N24211" "N619AA" "N804JB" ...
 $ origin        : chr  "EWR" "LGA" "JFK" "JFK" ...
 $ dest          : chr  "IAH" "IAH" "MIA" "BQN" ...
 $ air_time      : num  227 227 160 183 116 150 158 53 140 138 ...
 $ distance      : num  1400 1416 1089 1576 762 ...
 $ hour          : num  5 5 5 5 6 5 6 6 6 6 ...
 $ minute        : num  15 29 40 45 0 58 0 0 0 0 ...
 $ time_hour     : POSIXct, format: "2013-01-01 05:00:00" "2013-01-01 05:00:00" ...
summary(flights)
      year          month             day           dep_time    sched_dep_time
 Min.   :2013   Min.   : 1.000   Min.   : 1.00   Min.   :   1   Min.   : 106  
 1st Qu.:2013   1st Qu.: 4.000   1st Qu.: 8.00   1st Qu.: 907   1st Qu.: 906  
 Median :2013   Median : 7.000   Median :16.00   Median :1401   Median :1359  
 Mean   :2013   Mean   : 6.549   Mean   :15.71   Mean   :1349   Mean   :1344  
 3rd Qu.:2013   3rd Qu.:10.000   3rd Qu.:23.00   3rd Qu.:1744   3rd Qu.:1729  
 Max.   :2013   Max.   :12.000   Max.   :31.00   Max.   :2400   Max.   :2359  
                                                 NA's   :8255                 
   dep_delay          arr_time    sched_arr_time   arr_delay       
 Min.   : -43.00   Min.   :   1   Min.   :   1   Min.   : -86.000  
 1st Qu.:  -5.00   1st Qu.:1104   1st Qu.:1124   1st Qu.: -17.000  
 Median :  -2.00   Median :1535   Median :1556   Median :  -5.000  
 Mean   :  12.64   Mean   :1502   Mean   :1536   Mean   :   6.895  
 3rd Qu.:  11.00   3rd Qu.:1940   3rd Qu.:1945   3rd Qu.:  14.000  
 Max.   :1301.00   Max.   :2400   Max.   :2359   Max.   :1272.000  
 NA's   :8255      NA's   :8713                  NA's   :9430      
   carrier              flight       tailnum             origin         
 Length:336776      Min.   :   1   Length:336776      Length:336776     
 Class :character   1st Qu.: 553   Class :character   Class :character  
 Mode  :character   Median :1496   Mode  :character   Mode  :character  
                    Mean   :1972                                        
                    3rd Qu.:3465                                        
                    Max.   :8500                                        
                                                                        
     dest              air_time        distance         hour           minute     
 Length:336776      Min.   : 20.0   Min.   :  17   Min.   : 1.00   Min.   : 0.00  
 Class :character   1st Qu.: 82.0   1st Qu.: 502   1st Qu.: 9.00   1st Qu.: 8.00  
 Mode  :character   Median :129.0   Median : 872   Median :13.00   Median :29.00  
                    Mean   :150.7   Mean   :1040   Mean   :13.18   Mean   :26.23  
                    3rd Qu.:192.0   3rd Qu.:1389   3rd Qu.:17.00   3rd Qu.:44.00  
                    Max.   :695.0   Max.   :4983   Max.   :23.00   Max.   :59.00  
                    NA's   :9430                                                  
   time_hour                  
 Min.   :2013-01-01 05:00:00  
 1st Qu.:2013-04-04 13:00:00  
 Median :2013-07-03 10:00:00  
 Mean   :2013-07-03 05:22:54  
 3rd Qu.:2013-10-01 07:00:00  
 Max.   :2013-12-31 23:00:00  
                              

Dari hasil di atas saja sudah banyak hal yang kita dapatkan. Pertama, isi dari variabel year semuanya adalah 2013. Tidak ada nilai lain selain itu. Variabel seperti ini biasanya akan langsung dibuang karena tidak mempunyai informasi yang dapat digunakan untuk analisis. Atau digunakan untuk membuat variabel baru. Misalnya dengan menggabungkan variabel year, month dan day menjadi sebuah variabel tanggal.

Kedua, pada variabel dep_time, dep_delay, arr_time, arr_delay dan air_time terdapat beberapa data yang nilainya NA. Artinya ada data penerbangan yang tidak memiliki data-data tersebut. Perlu diketahui dulu penyebab dan konteks data tersebut NA. Misalnya, jika suatu penerbangan tidak memiliki data dep_time artinya penerbangan tersebut tidak mempunyai catatan waktu take-off. Bisa jadi penerbangan tersebut di-cancel. Jika suatu penerbangan mempunyai data dep_time tapi tidak memiliki data arr_time maka bisa jadi penerbangan tersebut mengalami kecelakaan atau hal lainnya.

Siapkan data yang akan digunakan. Kita join data flights dengan airlines untuk mendapatkan variabel nama perusahaan maskapai penerbangan (name), kemudian join lagi dengan weather untuk mendapatkan data cuaca.

flights_tbl <- flights %>% 
  left_join(airlines, by = "carrier") %>% 
  left_join(weather, suffix = c("_flight", "_weather"))

Untuk lebih mengetahui mengenai join menggunakan dplyr silahkan baca artikel ini dan ini.

str(flights_tbl)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   336776 obs. of  29 variables:
 $ year          : num  2013 2013 2013 2013 2013 ...
 $ month         : num  1 1 1 1 1 1 1 1 1 1 ...
 $ day           : int  1 1 1 1 1 1 1 1 1 1 ...
 $ dep_time      : int  517 533 542 544 554 554 555 557 557 558 ...
 $ sched_dep_time: int  515 529 540 545 600 558 600 600 600 600 ...
 $ dep_delay     : num  2 4 2 -1 -6 -4 -5 -3 -3 -2 ...
 $ arr_time      : int  830 850 923 1004 812 740 913 709 838 753 ...
 $ sched_arr_time: int  819 830 850 1022 837 728 854 723 846 745 ...
 $ arr_delay     : num  11 20 33 -18 -25 12 19 -14 -8 8 ...
 $ carrier       : chr  "UA" "UA" "AA" "B6" ...
 $ flight        : int  1545 1714 1141 725 461 1696 507 5708 79 301 ...
 $ tailnum       : chr  "N14228" "N24211" "N619AA" "N804JB" ...
 $ origin        : chr  "EWR" "LGA" "JFK" "JFK" ...
 $ dest          : chr  "IAH" "IAH" "MIA" "BQN" ...
 $ air_time      : num  227 227 160 183 116 150 158 53 140 138 ...
 $ distance      : num  1400 1416 1089 1576 762 ...
 $ hour          : num  5 5 5 5 6 5 6 6 6 6 ...
 $ minute        : num  15 29 40 45 0 58 0 0 0 0 ...
 $ time_hour     : POSIXct, format: "2013-01-01 05:00:00" "2013-01-01 05:00:00" ...
 $ name          : chr  "United Air Lines Inc." "United Air Lines Inc." "American Airlines Inc." "JetBlue Airways" ...
 $ temp          : num  39 39.9 39 39 39.9 ...
 $ dewp          : num  28 25 27 27 25 ...
 $ humid         : num  64.4 54.8 61.6 61.6 54.8 ...
 $ wind_dir      : num  260 250 260 260 260 260 240 260 260 260 ...
 $ wind_speed    : num  12.7 15 15 15 16.1 ...
 $ wind_gust     : num  NA 21.9 NA NA 23 ...
 $ precip        : num  0 0 0 0 0 0 0 0 0 0 ...
 $ pressure      : num  1012 1011 1012 1012 1012 ...
 $ visib         : num  10 10 10 10 10 10 10 10 10 10 ...

6 Dasar-dasar ggplot2

Package ggplot2 merupakan salah satu package untuk visualisasi yang paling banyak digunakan oleh pengguna R. Package ini juga yang menjadikan salah satu keutamaan R dibanding software pemrograman dan analisis data yang lain.

ggplot2 didesain untuk bekerja secara iteratif. Kata ggplot sendiri merupaka kependekan dari grammar of graphics plot. Terdapat dua fungsi utama yang digunakan untuk visualisasi data. Fungsi qplot(), yang merupakan kependekan dari quick-plot. Penggunaan qplot() sendiri tidak terlalu banyak, bahkan pembuatnya sendiri mengatakan bahwa qplot() dibuat untuk mereka yang memang sudah sangat terbiasa menggunakan fungsi plot() pada base R.

Fungsi kedua, dan yang paling sering digunakan, adalah ggplot(). Perbedaan yang paling mencolok dari fungsi qplot() dan ggplot() adalah tipe data yang dapat digunakan. Fungsi qplot() dapat menggunakan vector atau dataframe, sedangkan ggplot()hanya menerima dataframe. Fungsi-fungsi pada ggplot2 berperan sebagai layer yang ditandai dengan +.

Berikut contoh dari penggunaan qplot().

not_cancel <- flights_tbl %>% 
  filter(!is.na(dep_delay))
qplot(x = name, data = not_cancel)

Tentu saja tampilan grafik seperti di atas tidak baik karena informasi yang diberikan kurang sempurna. Nama maskapai penerbangan yang saling tumpang tindih juga membuat grafik tersebut tidak menarik.

qplot(x = name, data = not_cancel) +
  theme(axis.text.x = element_text(angle = 90))

Fungsi theme() akan diabhas lebih banyak di bagian berikutnya.

7 Visualisasi dengan ggplot()

Fungsi yang paling sering digunakan dari package ggplot2 adalah ggplot(). Secara umum visualisasi menggunakan ggplot() adalah seperti berikut.

ggplot(data = <DATA>, mapping = aes(x = <VAR>, y = <VAR>)) + 
  <GEOM_FUNCTION>()

Anda dapat mempelajari tentang ggplot2 dari buku ggplot2 Elegant Graphics for Data Analysis second edition.

7.1 Barplot

Yang pertama kita akan melakukan eksplorasi data dengan menggunakan diagram batang atau barplot. Grafik yang akan dihasilkan sama dengan yang dihasilkan oleh qplot() sebelumnya.

ggplot(data = not_cancel, mapping = aes(x = name)) +
    geom_bar() +
    theme(axis.text.x = element_text(angle = 90))

Geom geom_bar() dapat menerima data dengan format memanjang (raw) atau tabulasi.

not_cancel %>% 
  select(name)

Jika bentuk datanya memanjang seperti ini maka cukup menyebutkan variabelnya di argumen x = pada aes().

ggplot(data = not_cancel, mapping = aes(x = name)) +
    geom_bar() +
    theme(axis.text.x = element_text(angle = 90))

Jika datanya dalam bentuk tabulasi, sebutkan kategorinya di argumen x = dan nilainya di argumen y =, kemudian tambahkan stat = "identity" pada geom_bar().

not_cancel <- not_cancel %>% 
  count(name) 
not_cancel
ggplot(data = not_cancel, mapping = aes(x = name, y = n)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 90))

Secara default tampilan barplot diurutkan berdasarkan alfabet kategori pada argumen x. Jika ingin barplot diurutkan berdasarkan nilainya, gunakan fungsi reorder().

ggplot(data = not_cancel, mapping = aes(x = reorder(name, -n), y = n)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 90))

Untuk mengurutkan barplot berdasarkan value

  1. Buat terlebih dahulu tabel frekuensi dari kategori yang diinginkan.
  2. gunakan fungsi reorder() pada aestethic x dan n sbg y.
  3. gunakan stat = "identity" pada geom_bar().
not_cancel <- not_cancel %>% 
  mutate(pct = n/sum(n))

ggplot(data = not_cancel, mapping = aes(x = reorder(name, -pct), y = pct)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 90))

canceled <- flights_tbl %>% 
  filter(is.na(dep_delay)) %>% 
  count(name) %>% 
  mutate(pct = n/sum(n))
canceled

Dari tabel di atas kita dapat mengetahui bahwa ExpressJet Airlines Inc. yang paling banyak melakukan cancel selama tahun 2013. Sekarang mari kita tampilkan dalam visualisasi.

g <- ggplot(data = canceled, mapping = aes(x = reorder(name, -pct), y = pct)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 90))

g

g +
  labs(title = "Persentase Maskapai Yang Sering Melakukan Cancel",
       x = "Maskapai",
       y = "Persentase") +
  theme(axis.title.y = element_text(angle = 0, vjust = 1))

Hasil dari ggplot dapat disimpan dalam sebuah objek di R kemudian ditambahkan layer dengan + dan geom atau komponen lain.

Kita juga dapat menentukan warna untuk masing-masing batang pada barplot. Misalnya, setiap batang berbeda warnanya berdasarkan nama kategorinya secara default.

Untuk menambahkan judul (title), mengganti judul masing-masing axis x dan y atau menambahkan subtitledapat menggunakan labs().

ggplot(data = canceled, mapping = aes(x = reorder(name, -pct), y = pct, fill = name)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 90),
        axis.title.y = element_text(angle = 0, vjust = 1),
        legend.position = "none") +
  labs(title = "Persentase Maskapai Yang Sering Melakukan Cancel",
       x = "Maskapai",
       y = "Persentase")

Argumen legend.position = "none" digunakan untuk menghilangkan legend dari grafik yang dibuat. Cobalah untuk menghapus argumen legend.position = "none" dan lihat hasilnya.

ggplot(data = canceled, mapping = aes(x = reorder(name, -pct), y = pct*100, fill = name)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = paste(round(pct*100, 2), "%")), vjust = -0.25) +
  theme(axis.text.x = element_text(angle = 90),
        axis.title.y = element_text(angle = 0, vjust = 1),
        legend.position = "none") +
  labs(title = "Persentase Maskapai Yang Sering Melakukan Cancel",
       x = "Maskapai",
       y = "Persentase")

Anda juga dapat melakukan operasi matematika sederhana untuk variabel yang akan digunakan. Misalnya mengubah satuan pct dengan dikalikan 100. geom_text() digunakan untuk menambahkan komponen layer teks pada grafik. Dalam kasus ini digunakan untuk menampilkan nilai di atas masing-masing batang. vjust = -0.25 agar teks berada di atas batang.

ggplot(data = canceled, mapping = aes(x = reorder(name, pct), y = pct*100, fill = name)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = paste(round(pct*100, 2), "%")), hjust = -0.01) +
  theme(legend.position = "none") +
  labs(title = "Persentase Maskapai Yang Sering Melakukan Cancel",
       x = "Maskapai",
       y = "Persentase") +
  coord_flip()

Agar tampilan barplot dengan axis.text.x lebih menarik, gunakan coord_flip() untuk memutar grafik. Posisi axis x di posisi y dan sebaliknya.

7.2 Histogram & Density

Histogram atau Density dapat digunakan untuk memvisualisasikan sebaran dari sebuah variabel numerik.

g <- ggplot(data = flights_tbl, mapping = aes(x = air_time))
g

Karena variabel yang akan kita gunakan sama, yaitu hanya air_time maka kita simpan dahulu hasil dari ggplot ke dalam objek g sehingga kita dapat mengganti argumen pada geom_histogram() tanpa perlu memanggil lagi fungsi ggplot() dengan argumen yang sama sepertisebelumnya. Ketika kita panggil objek dari ggplot tersebut, maka hnaya menapilkan sebuah kanvas kosong karena kita belum menambahkan layer geom apa yang akan digunakan.

g +
  geom_histogram()

Ketika kita tambahkan geom_histogram() maka akan ditambahkan histogram. Secara default geom_histogram() menggunakan 30 bins. bins adalah banyaknya kotak/batang, sedangkan lebar batang disebut binwidth. Jika ada NA maka secara otomatis akan dibuang.

g +
  geom_histogram(bins = 50, fill = "skyblue", color = "white")

Dengan bins = 50 artinya pada histogram tersebut akan digunakan 50 batang. Argumen fill = "skyblue" untuk memberikan warna pada batang histogram. Anda juga dapat menggunakan warna lain, misalnya fill = "pink" atau fill = "lightblue" atau bahkan menggunakan kode warna Hex.

Sebagian besar lamanya penerbangan kurang dari 200 menit. Namun dari grafik di atas terlihat ada beberapa puncak yang terjadi. Mari kita lihat menggunakan geom_density()

g +
  geom_histogram(aes(y = ..density..), bins = 50, fill = "skyblue", color = "white") +
  geom_density(color = "darkgreen", size = 0.7)

g +
  geom_density(fill = "skyblue", alpha = 0.5)

Namun jika diperhatikan, ada juga penerbangan yang lama waktu terbangnya lebih dari 600 menit atau 10 jam. Kita lihat data dengan penerbangan selama sekitar 10 jam tersebut.

flights10h <- flights_tbl %>% 
  filter(air_time > 500)

flights10h %>% 
  group_by(origin, dest) %>% 
  summarise(min = min(air_time), avg = mean(air_time), std = sd(air_time), max = max(air_time))

Ternyata penerbangan dari bandara EWR dan JFK menuju bandara HNL rata-rata membutuhkan waktu terbang lebih dari 600 menit atau 10 jam. Apa itu HNL?

airports %>% 
  filter(faa == "HNL")

7.3 Boxplot

Secara default, boxplot di ggplot2 memerlukan variabel kategorik sebagai argumen x dan variabel numerik sebagai argumen y.

ggplot(data = flights_tbl, mapping = aes(x = name, y = wind_speed)) +
  geom_boxplot() +
  coord_flip()

Jika hanya ingin satu boxplot untuk satu variabel tanpa dibedakan berdasarkan kategori lain maka cukup gunakan x = '' atau string lain seperti contoh berikut.

ggplot(data = flights_tbl, mapping = aes(x = "Statistics", y = temp)) +
  geom_boxplot()

ggplot(data = flights_tbl, mapping = aes(x = origin, y = temp)) +
  geom_boxplot() +
  coord_flip()

ggplot(data = flights_tbl, mapping = aes(x = origin, y = temp)) +
  geom_boxplot(color = "skyblue") +
  coord_flip()

Anda dapat membaca beberapa artikel tentang geom_boxplot(), salah satunya geom_boxplot.

7.4 Scatter plot

Scatter plot adalah visualisasi data dari dua buah variabel numerik. Misalkan dari data weather kita ambil data cuaca di bandara JFK saja selama tahun 2013.

weather_jfk <- weather %>% 
  filter(origin == "JFK") 
ggplot(data = weather_jfk, mapping = aes(x = time_hour, y = temp)) + 
  geom_point()

Anda dapat merubah shape dari geom_point dengan sebuah bilangan integer.

Point Shape

Point Shape

ggplot(data = weather_jfk, mapping = aes(x = time_hour, y = temp)) + 
  geom_point(shape = 1) +
  labs(title = "Suhu Bandara JFK Selama Tahun 2013",
       x = "waktu",
       y = "Suhu")

7.5 Line plot/time series plot

ggplot(data = weather_jfk, mapping = aes(x = time_hour, y = temp)) + 
  geom_line()

ggplot(data = weather_jfk, mapping = aes(x = time_hour)) + 
  geom_line(aes(y = temp), color = "skyblue")

Ketebalan garis dapat disesuaikan dengan size =, misalnya size = 1 untuk garis yang lebih tebal dari default.

ggplot(data = weather_jfk, mapping = aes(x = time_hour, y = temp)) + 
  geom_line(color = "skyblue") +
  geom_point(size = 0.5) +
  scale_x_datetime(breaks = "1 month", date_labels = "%b %d") +
  theme(axis.text.x = element_text(angle = 90))

ggplot(data = weather_jfk, mapping = aes(x = time_hour, y = temp)) + 
  geom_line() +
  geom_point(size = 0.5) +
  scale_x_datetime(breaks = "days", date_labels = "%b %d") +
  theme(axis.text.x = element_text(angle = 90))

ggplot(data = weather_jfk, mapping = aes(x = time_hour, y = temp)) + 
  geom_line() +
  geom_point(size = 0.5) +
  scale_x_datetime(breaks = "1 month", date_labels = "%b %d") +
  theme(axis.text.x = element_text(angle = 90)) +
  labs(title = "Suhu Bandara JFK Selama Tahun 2013",
       x = "Waktu",
       y = "Suhu")

weather_jfk %>% 
  ggplot(mapping = aes(x = time_hour, y = temp)) +
  geom_line() +
  geom_smooth()

weather %>% 
  ggplot(mapping = aes(x = time_hour, y = temp, color = origin)) +
  # geom_line() +
  geom_smooth() +
  scale_color_manual(values = c("#ea5454", "#6f45d8", "#57c158"))

8 Facet

flights_aug2 <- flights_tbl %>% 
  inner_join(airports, by = c("origin" = "faa"), suffix = c("_carrier", "_originairports")) %>% 
  filter(!is.na(dep_delay) & month == 8) %>% 
  mutate(tgl = as.Date(paste(year, month, day, sep = "-"))) %>% 
  group_by(name_originairports, tgl) %>% 
  summarise(n = n()) 
ggplot(data = flights_aug2, mapping = aes(x = tgl, y = n)) + 
  geom_line(color = "skyblue") +
  geom_point() +
  facet_grid(rows = vars(name_originairports)) +
  scale_x_date(breaks = "days", date_labels = "%d") +
  theme(axis.text.x = element_text(angle = 45)) +
  labs(title = "Jumlah Penerbangan Harian Bulan Agustus 2013",
       x = "Tanggal",
       y = "Jumlah")

ggplot(data = flights_aug2, mapping = aes(x = tgl, y = n)) + 
  geom_line(color = "skyblue") +
  geom_point() +
  facet_grid(name_originairports ~ . ) +
  scale_x_date(breaks = "days", date_labels = "%d") +
  theme(axis.text.x = element_text(angle = 45)) +
  labs(title = "Jumlah Penerbangan Harian Bulan Agustus 2013",
       x = "Tanggal",
       y = "Jumlah")


ggplot(data = flights_aug2, mapping = aes(x = tgl, y = n)) + 
  geom_line(color = "skyblue") +
  geom_point() +
  facet_grid(cols = vars(name_originairports)) +
  labs(title = "Jumlah Penerbangan Harian Bulan Agustus 2013",
       x = "Tanggal",
       y = "Jumlah")


ggplot(data = flights_aug2, mapping = aes(x = tgl, y = n)) + 
  geom_line(color = "skyblue") +
  geom_point() +
  facet_grid( . ~ name_originairports) +
  labs(title = "Jumlah Penerbangan Harian Bulan Agustus 2013",
       x = "Tanggal",
       y = "Jumlah")

ggplot(data = flights_aug2, mapping = aes(x = tgl, y = n)) + 
  geom_line(color = "skyblue") +
  geom_point() +
  facet_grid(rows = vars(), cols = vars(name_originairports)) +
  labs(title = "Jumlah Penerbangan Harian Bulan Agustus 2013",
       x = "Tanggal",
       y = "Jumlah")

Setelah selesai membuat grafik yang Anda inginkan, Anda dapat menyimpan grafik tersebut dengan fungsi ggsave().

g <- flights_tbl %>% 
  filter(origin == "JFK") %>% 
  ggplot(mapping = aes(x = time_hour, y = temp)) +
  geom_line() +
  geom_smooth()

ggsave(filename = "plotR.jpg", plot = g, width = 10, height = 7)

Jika Anda tidak menyebutkan objek ggplot2 pada plot = maka secara otomatis plot yang terakhir dibuat yang akan dipilih.

LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiBUbyBEYXRhIFZpc3VhbGl6YXRpb24gVXNpbmcgZ2dwbG90MiINCmF1dGhvcjogIkFlcCBIaWRheWF0dWxvaCINCmRhdGU6ICJMYXN0IFVwZGF0ZTogYHIgZm9ybWF0KFN5cy5EYXRlKCksICclWSAlYiAlZCcpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICBodG1sX2RvY3VtZW50Og0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICc0Jw0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KLS0tDQoNCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCmJvZHl7IC8qIE5vcm1hbCAgKi8NCiAgICAgIGZvbnQtc2l6ZTogMTJweDsNCiAgfQ0KdGQgeyAgLyogVGFibGUgICovDQogIGZvbnQtc2l6ZTogMTJweDsNCn0NCmgxLnRpdGxlIHsNCiAgZm9udC1zaXplOiAzOHB4Ow0KICBjb2xvcjogbGlnaHRibHVlOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgKi8NCiAgZm9udC1zaXplOiAyNHB4Ow0KICBjb2xvcjogRGFya0JsdWU7DQp9DQpoMiB7IC8qIEhlYWRlciAyICovDQogIGZvbnQtc2l6ZTogMjBweDsNCiAgY29sb3I6IERhcmtCbHVlOw0KfQ0KaDMgeyAvKiBIZWFkZXIgMyAqLw0KICBmb250LXNpemU6IDE2cHg7DQojICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgY29sb3I6IERhcmtCbHVlOw0KfQ0KaDQgeyAvKiBIZWFkZXIgNCAqLw0KICBmb250LXNpemU6IDE0cHg7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCn0NCmNvZGUucnsgLyogQ29kZSBibG9jayAqLw0KICAgIGZvbnQtc2l6ZTogMTJweDsNCn0NCnByZSB7IC8qIENvZGUgYmxvY2sgLSBkZXRlcm1pbmVzIGNvZGUgc3BhY2luZyBiZXR3ZWVuIGxpbmVzICovDQogICAgZm9udC1zaXplOiAxMnB4Ow0KfQ0KPC9zdHlsZT4NCg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiNrbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTkuMiwgcmVzdWx0cz0naG9sZCcsIHdhcm5pbmc9RkFMU0UsIGZpZy5zaG93PSdob2xkJywgbWVzc2FnZT1GQUxTRSkgDQpvcHRpb25zKHNjaXBlbiA9IDk5KQ0KYGBgDQoNCg0KIyBPYmpla3RpZg0KDQpJbmkgYWRhbGFoIGNhdGF0YW4gdW50dWsgbWVtYW5kdSBzZWNhcmEgc2luZ2thdCBkYWxhbSBtZWxha3VrYW4gdmlzdWFsaXNhc2kgZGF0YSB1bnR1ayBrZWJ1dHVoYW4gZWtzcGxvcmFzaSBkYXRhIGF0YXUgcHVibGlrYXNpIG1lbmdndW5ha2FuIFIgZGFuIHBhY2thZ2UgYGdncGxvdDJgLiBUdWp1YW4gZGFyaSB0dXRvcmlhbCBpbmkgYWRhbGFoIGFnYXIgcGVzZXJ0YSBkYXBhdCBtZW5jb2JhIG1lbWJ1YXQgZ3JhZmlrIHlhbmcgbWVuYXJpayBkYW4gbXVkYWguIA0KDQojIENha3VwYW4gTWF0ZXJpDQpNYXRlcmkgeWFuZyBha2FuIGRpYmFoYXM6DQoNCisgRGFzYXItZGFzYXIgZ2dwbG90Mg0KKyBQZW5nZ3VuYWFuIGZ1bmdzaSBgcXBsb3QoKWANCisgQWVzdGhldGljIHBhZGEgZ2dwbG90Mg0KKyBCYXJwbG90DQorIEhpc3RvZ3JhbSAmIERlbnNpdHkNCisgQm94cGxvdA0KKyBTY2F0dGVyIHBsb3QNCisgTGluZSBwbG90DQorIE1lbmVudHVrYW4gd2FybmENCisgRmFjZXRpbmcNCisgQW5ub3RhdGlvbg0KKyBUZW1hIChgdGhlbWVgKQ0KDQojIFByYXN5YXJhdA0KDQpVbnR1ayBkYXBhdCBtZW5naWt1dGkgdHV0b3JpYWwgaW5pIGRlbmdhbiBiYWlrLCBhZGEgYmViZXJhcGEgaGFsIHlhbmcgcGVybHUgZGlwZXJzaWFwa2FuLCB5YWl0dToNCg0KMS4gS29uZWtzaSBpbnRlcm5ldCB5YW5nIGJhaWsNCg0KMi4gTWVuZ2luc3RhbGwgc29mdHdhcmUNCg0KICAgIGEuIFIgcHJvZ3JhbSA8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvPg0KDQogICAgYi4gUlN0dWRpbyA8aHR0cHM6Ly93d3cucnN0dWRpby5jb20vcHJvZHVjdHMvcnN0dWRpby9kb3dubG9hZC8+DQoNCjMuIERhdGEgJiBTY3JpcHQgeWFuZyBkYXBhdCBkaXBlcm9sZWggZGFyaSBbcmVwb3NpdG9yeSBpbmldKGh0dHBzOi8vZ2l0aHViLmNvbS9hZXBoaWRheWF0dWxvaC9la3NwbG9yYXNpLWRhdGEpIGRhbiBwYWRhIGRhdGFiYXNlIHlhbmcgZGlzZWRpYWthbiBwZW1iaWNhcmEuDQoNCjQuIFBhY2thZ2UgUiB5YW5nIGRpYnV0dWhrYW46IGBnZ3Bsb3QyYCwgYGRwbHlyYCAoYXRhdSBgdGlkeXZlcnNlYCkgZGFuIGBueWNmbGlnaHRzMTNgLg0KDQpDYXRhdGFuOiBEYXRhIGRpcGVyb2xlaCBkYXJpIHBhY2thZ2VzIGBueWNmbGlnaHRzMTNgIHlhbmcgZGlzaW1wYW4ga2UgZGFsYW0gZGF0YWJhc2UuIEppa2EgQW5kYSBpbmdpbiBtZW5jb2JhIGRpbHVhciBrZWdpYXRhbiBhdGF1IHRpZGFrIGRhcGF0IHRlcmh1YnVuZyBkZW5nYW4gZGF0YWJhc2UgcGVtYmljYXJhLCBBbmRhIGRhcGF0IG1lbmdpbnN0YWxsIHBhY2thZ2UgYG55Y2ZsaWdodHMxM2AgdW50dWsgbWVtcGVyb2xlaCBkYXRhIHlhbmcgZGlndW5ha2FuIHBhZGEgZGF0YWJhc2UuIER1YSBkYXRhIGxhaW4gYmVydXBhIGZpbGUgQ1NWIHVudHVrIGRpc2VzdWFpa2FuIGRlbmdhbiBrZWJ1dHVoYW4gdHV0b3JpYWwuIA0KDQpEYXRhIGluaSB0ZXJkaXJpIGRhcmkgMzM2LDc3NiBwZW5lcmJhbmdhbiBkYXJpIE5ldyBZb3JrIENpdHkgKE5ZQykgc2VsYW1hIHRhaHVuIDIwMTMuIERhdGEgYXNsaSBiZXJhc2FsIGRhcmkgVVMgQnVyZWF1IG9mIFRyYW5zcG9ydGF0aW9uIFN0YXRpc3RpY3MsIGRhbiBkYXBhdCBkaWxpaGF0IGRva3VtZW50YXNpbnlhIGRlbmdhbiBgP255Y2ZsaWdodHMxMzo6ZmxpZ2h0c2AuDQpQYXN0aWthbiBBbmRhIHN1ZGFoIGJlcmhhc2lsIGluc3RhbGwgcGFja2FnZSB0ZXJzZWJ1dC4NCg0KIyBJbnN0YWxsIGRhbiBMb2FkIFBhY2thZ2VzDQoNCkphbGFua2FuIHBlcmludGFoIGRpIGJhd2FoIGluaSB1bnR1ayBpbnN0YWxsIHBhY2thZ2UgKCoqamlrYSBBbmRhIGJlbHVtIHBlcm5haCBpbnN0YWxsKiopIHlhbmcgYWthbiBkaWd1bmFrYW4gdW50dWsgZGFwYXQgbWVuZ2lrdXRpIHR1dG9yaWFsIGluaSBzYW1wYWkgc2VsZXNhaS4NCg0KYGBge3IgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoYygiZ2dwbG90MiIsICJkcGx5ciIsICJ0aWR5ciIsICJueWNmbGlnaHRzMTMiKSkNCiMgYXRhdSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCmluc3RhbGwucGFja2FnZXMoYygidGlkeXZlcnNlIiwgIm55Y2ZsaWdodHMxMyIpKSAgICAgICAgICAgICAgICAgICAgICAgICANCmBgYA0KDQpQYW5nZ2lsIHBhY2thZ2UgeWFuZyBzdWRhaCBBbmRhIGluc3RhbGwgZGVuZ2FuIGZ1bmdzaSBgbGlicmFyeSgpYC4NCg0KYGBge3IgbG9hZHBrZ30NCiMgUGFuZ2dpbCBwYWNrYWdlIHlhbmcgc3VkYWggdGVyaXNudGFsbA0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQoNCiMgIyBhdGF1IGN1a3VwIG1lbWFuZ2dpbCBgdGlkeXZlcnNlYCB1bnR1ayBtZW1hbmdnaWwgcGFja2FnZSBnZ3Bsb3QyIGRhbiBwYWNrYWdlIGxhaW4gZGkgdGlkeXZlcnNlDQojIGxpYnJhcnkodGlkeXZlcnNlKQ0KDQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykNCmBgYA0KDQpQYWNrYWdlIGBnZ3Bsb3QyYCAoZGFuIGJlYmVyYXBhIHBhY2thZ2UgbGFpbiB5YW5nIHRpZGFrIGRpZ3VuYWthbiBkaSB0dXRvcmlhbCBpbmkpIHRlcm1hc3VrIGRhbGFtIGJhZ2lhbiBwYWNrYWdlIGB0aWR5dmVyc2VgLiBgdGlkeXZlcnNlYCBhZGFsYWgga3VtcHVsYW4gcGFja2FnZSB5YW5nIGRpYnVhdCBvbGVoICoqSGFkbGV5IFdpY2toYW0qKiBka2sgdW50dWsga2VidXR1aGFuIGRhdGEgc2NpZW5jZSBtZW5nZ3VuYWthbiBSLg0KDQorIGBnZ3Bsb3QyYCBhZGFsYWggc2FsYWggc2F0dSBwYWNrYWdlIHlhbmcgc2FuZ2F0IGJhbnlhayBkaWd1bmFrYW4gb2xlaCBwZW5nZ3VuYSBSIHVudHVrIGtlYnV0dWhhbiB2aXN1YWxpc2FzaS4NCisgYGRwbHlyYCBkYW4gYHRpZHlyYCBhZGFsYWggcGFja2FnZSB5YW5nIHNhbmdhdCBiZXJndW5hIHVudHVrIG1lbGFrdWthbiBtYW5pcHVsYXNpL3RyYW5zZm9ybWFzaSBkYXRhIG1lbmdndW5ha2FuIFIuDQorIGBueWNmbGlnaHRzMTNgIGFkYWxhaCBwYWNrYWdlIHlhbmcgbWVueWVkaWFrYW4gNSBkYXRhIGZyYW1lIGRhbGFtIGZvcm1hdCBbdGliYmxlXShodHRwczovL3I0ZHMuaGFkLmNvLm56L3RpYmJsZS5odG1sKSB0ZW50YW5nIHBlbmVyYmFuZ2FuIGRpIE5ZQyBzZWxhbWEgdGFodW4gMjAxMy4NCg0KPHAgYWxpZ249ImNlbnRlciI+DQogICAgPGltZyBzcmM9InRpZHl2ZXJzZS5wbmciIGFsdD0iVGlkeXZlcnNlIj4NCiAgICA8YnIvPg0KICAgIDxici8+DQogICAgPGVtPlRpZHl2ZXJzZTwvZW0+DQo8L3A+DQoNCmB0aWR5dmVyc2VgIG1lbmdndW5ha2FuICoqdGliYmxlKiogc2ViYWdhaSBwZW5nZ2FudGkgYGRhdGEuZnJhbWVgLiANCg0KPiBUaWJibGVzIGFyZSBkYXRhIGZyYW1lcywgYnV0IHRoZXkgdHdlYWsgc29tZSBvbGRlciBiZWhhdmlvdXJzIHRvIG1ha2UgbGlmZSBhIGxpdHRsZSBlYXNpZXIuIFIgaXMgYW4gb2xkIGxhbmd1YWdlLCBhbmQgc29tZSB0aGluZ3MgdGhhdCB3ZXJlIHVzZWZ1bCAxMCBvciAyMCB5ZWFycyBhZ28gbm93IGdldCBpbiB5b3VyIHdheS4gSXTigJlzIGRpZmZpY3VsdCB0byBjaGFuZ2UgYmFzZSBSIHdpdGhvdXQgYnJlYWtpbmcgZXhpc3RpbmcgY29kZSwgc28gbW9zdCBpbm5vdmF0aW9uIG9jY3VycyBpbiBwYWNrYWdlcyAtLSBbR3JvbGVtdW5kICYgV2lja2hhbV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5ueikuIA0KDQojIyBPcGVyYXRvciBQaXBlIGAlPiVgDQoNClNlYmVsdW0gbWVsYW5na2FoIGxlYmloIGphdWgsIGtpdGEgaGFydXMgbWVuZ2Vrc3Bsb2l0YXNpIG9wZXJhdG9yIHBpcGUgeWFuZyBkaWltcG9yIGRhcmkgcGFja2FnZSBgbWFncml0dHJgIG9sZWggU3RlZmFuIEJhY2hlLiBJbmkgYWthbiBtZW5ndWJhaCBrZWhpZHVwYW4gYW5hbGl0aWsgZGF0YSBBbmRhLiBBbmRhIHRpZGFrIHBlcmx1IGxhZ2kgbWVtYmVybGFrdWthbiBwZXJpbnRhaCBtdWx0aS1vcGVyYXNpIGRlbmdhbiBtZW55YXR1a2FubnlhIGRpIGRhbGFtIHNhdHUgc2FtYSBsYWluLiBTaW50YWtzIGJhcnUgaW5pIG1lbmdhcmFoIGtlIGtvZGUgeWFuZyBsZWJpaCBtdWRhaCB1bnR1ayBkaXR1bGlzIGRhbiBkaWJhY2EuDQoNCkJlZ2luaSB0YW1waWxhbm55YTogYCU+JWAuIFBpbnRhc2FuIGtleWJvYXJkIFJTdHVkaW86IGBDdHJsICsgU2hpZnQgKyBNYCAoV2luZG93cyksIGBDbWQgKyBTaGlmdCArIE1gIChNYWMpLiBMZWJpaCBsYW5qdXQgbWVuZ2VuYWkgb3BlcmF0b3IgYCU+JWAgc2lsYWhrYW4gYmFjYSBbcGVuamVsYXNhbiBvcGVyYXRvciBwaXBlXShodHRwOi8vcnB1YnMuY29tL2FlcGhpZGF5YXR1bG9oL3BpcGVzKS4NCg0KIyBFa3NwbG9yYXNpIGRhbiBWaXN1YWxpc2FzaSBEYXRhDQoNCktpdGEgbGloYXQgdGVybGViaWggZHVsdSBkYXRhIHlhbmcgYWthbiBraXRhIGd1bmFrYW4uIERhdGEgeWFuZyBha2FuIGRpZ3VuYWthbiBhZGEgMywgeWFpdHUgYGZsaWdodHNgLCBgYWlybGluZXNgIGRhbiBgd2VhdGhlcmAuDQoNCmBgYHtyIGhlYWRmbH0NCmZsaWdodHMNCmBgYA0KDQpBZGEgMzM2LDc3NiBqYWR3YWwgcGVuZXJiYW5nYW4gc2VsYW1hIHRhaHVuIDIwMTMuIFZhcmlhYmVsIGBkZXBfZGVsYXlgIGRlbmdhbiBuaWxhaSBwb3NpdGlmIChgZGVwX2RlbGF5ID4gMGApIG1lbnVuanVra2FuIGJhaHdhIHBlbmVyYmFuZ2FuIHRlcnNlYnV0IG1lbmdhbGFtaSBrZXRlcmxhbWJhdGFuIGRhcmkgamFkd2FsIGJlcmFuZ2thdCB5YW5nIHNlaGFydXNueWEsIHNlZGFuZ2thbiBgZGVwX2RlbGF5YCBuZWdhdGlmIChgZGVwX2RlbGF5IDwgMGApIG1lbnVuanVra2FuIHBlbmVyYmFuZ2EgZGlsYWt1a2FuIGxlYmloIGF3YWwgZGFyaSB5YW5nIHNlaGFydXNueWEsIGRhbGFtIHNhdHVhbiBtZW5pdC4gQWRhcHVuIGBkZXBfZGVsYXlgIHlhbmcgYE5BYCAoa29zb25nIGF0YXUgbWlzc2luZyB2YWx1ZSkgbWVudW5qdWtrYW4gcGVuZXJiYW5nYW4gZGliYXRhbGthbiBhdGF1IGNhbmNlbC4NCg0KYGBge3IgaGVhZGFpcmxpbmVzfQ0KYWlybGluZXMNCmBgYA0KDQpEaSBkYXRhIGluaSB0ZXJkYXBhdCAxNiBwZXJ1c2FoYWFuIG1hc2thcGFpIHBlbmVyYmFuZ2FuLiBWYXJpYWJlbCBgY2FycmllcmAgbmFudGlueWEgZGlndW5ha2FuIHVudHVrIGRpLWpvaW4gZGVuZ2FuIHZhcmlhYmVsIGBjYXJyaWVyYCBqdWdhIHBhZGEgZGF0YSBgZmxpZ2h0c2AuDQoNCmBgYHtyIGhlYWR3ZWF0aGVyfQ0Kd2VhdGhlcg0KYGBgDQoNCkRhdGEgZGkgYXRhcyBhZGFsYWggZGF0YSBjdWFjYSBiZXJkYXNhcmthbiBiYW5kYXJhIHlhbmcgYWRhIHBhZGEgdmFyaWJlbCBgb3JpZ2luYCBzZWxhbWEgdGFodW4gMjAxMyBkZW5nYW4gcmVudGFuZyB3YWt0dSBzZXRpYXAgc2F0dSBqYW0gKGxpaGF0IGB0aW1lX2hvdXJgKS4gDQoNClNla2FyYW5nIGtpdGEgbGloYXQgZHVsdSBzZWNhcmEgc2VraWxhcyBzZXBlcnRpIGFwYSBkYXRhIGBmbGlnaHRzYCB5YW5nIGFrYW4ga2l0YSBndW5ha2FuLg0KDQpgYGB7ciBzdHJmbH0NCnN0cihmbGlnaHRzKQ0KYGBgDQoNCg0KYGBge3Igc3VtbWFyeWZsfQ0Kc3VtbWFyeShmbGlnaHRzKQ0KYGBgDQoNCkRhcmkgaGFzaWwgZGkgYXRhcyBzYWphIHN1ZGFoIGJhbnlhayBoYWwgeWFuZyBraXRhIGRhcGF0a2FuLiBQZXJ0YW1hLCBpc2kgZGFyaSB2YXJpYWJlbCBgeWVhcmAgc2VtdWFueWEgYWRhbGFoIGAyMDEzYC4gVGlkYWsgYWRhIG5pbGFpIGxhaW4gc2VsYWluIGl0dS4gVmFyaWFiZWwgc2VwZXJ0aSBpbmkgYmlhc2FueWEgYWthbiBsYW5nc3VuZyBkaWJ1YW5nIGthcmVuYSB0aWRhayBtZW1wdW55YWkgaW5mb3JtYXNpIHlhbmcgZGFwYXQgZGlndW5ha2FuIHVudHVrIGFuYWxpc2lzLiBBdGF1IGRpZ3VuYWthbiB1bnR1ayBtZW1idWF0IHZhcmlhYmVsIGJhcnUuIE1pc2FsbnlhIGRlbmdhbiBtZW5nZ2FidW5na2FuIHZhcmlhYmVsIGB5ZWFyYCwgYG1vbnRoYCBkYW4gYGRheWAgbWVuamFkaSBzZWJ1YWggdmFyaWFiZWwgdGFuZ2dhbC4NCg0KS2VkdWEsIHBhZGEgdmFyaWFiZWwgYGRlcF90aW1lYCwgYGRlcF9kZWxheWAsIGBhcnJfdGltZWAsIGBhcnJfZGVsYXlgIGRhbiBgYWlyX3RpbWVgIHRlcmRhcGF0IGJlYmVyYXBhIGRhdGEgeWFuZyBuaWxhaW55YSBgTkFgLiBBcnRpbnlhIGFkYSBkYXRhIHBlbmVyYmFuZ2FuIHlhbmcgdGlkYWsgbWVtaWxpa2kgZGF0YS1kYXRhIHRlcnNlYnV0LiBQZXJsdSBkaWtldGFodWkgZHVsdSBwZW55ZWJhYiBkYW4ga29udGVrcyBkYXRhIHRlcnNlYnV0IGBOQWAuIE1pc2FsbnlhLCBqaWthIHN1YXR1IHBlbmVyYmFuZ2FuIHRpZGFrIG1lbWlsaWtpIGRhdGEgYGRlcF90aW1lYCBhcnRpbnlhIHBlbmVyYmFuZ2FuIHRlcnNlYnV0IHRpZGFrIG1lbXB1bnlhaSBjYXRhdGFuIHdha3R1ICp0YWtlLW9mZiouIEJpc2EgamFkaSBwZW5lcmJhbmdhbiB0ZXJzZWJ1dCBkaS0qY2FuY2VsKi4gSmlrYSBzdWF0dSBwZW5lcmJhbmdhbiBtZW1wdW55YWkgZGF0YSBgZGVwX3RpbWVgIHRhcGkgdGlkYWsgbWVtaWxpa2kgZGF0YSBgYXJyX3RpbWVgIG1ha2EgYmlzYSBqYWRpIHBlbmVyYmFuZ2FuIHRlcnNlYnV0IG1lbmdhbGFtaSBrZWNlbGFrYWFuIGF0YXUgaGFsIGxhaW5ueWEuDQoNClNpYXBrYW4gZGF0YSB5YW5nIGFrYW4gZGlndW5ha2FuLiBLaXRhIGpvaW4gZGF0YSBgZmxpZ2h0c2AgZGVuZ2FuIGBhaXJsaW5lc2AgdW50dWsgbWVuZGFwYXRrYW4gdmFyaWFiZWwgbmFtYSBwZXJ1c2FoYWFuIG1hc2thcGFpIHBlbmVyYmFuZ2FuIChgbmFtZWApLCBrZW11ZGlhbiBqb2luIGxhZ2kgZGVuZ2FuIGB3ZWF0aGVyYCB1bnR1ayBtZW5kYXBhdGthbiBkYXRhIGN1YWNhLg0KDQpgYGB7cn0NCmZsaWdodHNfdGJsIDwtIGZsaWdodHMgJT4lIA0KICBsZWZ0X2pvaW4oYWlybGluZXMsIGJ5ID0gImNhcnJpZXIiKSAlPiUgDQogIGxlZnRfam9pbih3ZWF0aGVyLCBzdWZmaXggPSBjKCJfZmxpZ2h0IiwgIl93ZWF0aGVyIikpDQpgYGANCg0KVW50dWsgbGViaWggbWVuZ2V0YWh1aSBtZW5nZW5haSBqb2luIG1lbmdndW5ha2FuIGBkcGx5cmAgc2lsYWhrYW4gYmFjYSBhcnRpa2VsIFtpbmldKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2Uvam9pbi5odG1sKSBkYW4gW2luaV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9yZWxhdGlvbmFsLWRhdGEuaHRtbCkuDQoNCmBgYHtyfQ0Kc3RyKGZsaWdodHNfdGJsKQ0KYGBgDQoNCiMgRGFzYXItZGFzYXIgZ2dwbG90Mg0KDQpQYWNrYWdlIGBnZ3Bsb3QyYCBtZXJ1cGFrYW4gc2FsYWggc2F0dSBwYWNrYWdlIHVudHVrIHZpc3VhbGlzYXNpIHlhbmcgcGFsaW5nIGJhbnlhayBkaWd1bmFrYW4gb2xlaCBwZW5nZ3VuYSBSLiBQYWNrYWdlIGluaSBqdWdhIHlhbmcgbWVuamFkaWthbiBzYWxhaCBzYXR1IGtldXRhbWFhbiBSIGRpYmFuZGluZyBzb2Z0d2FyZSBwZW1yb2dyYW1hbiBkYW4gYW5hbGlzaXMgZGF0YSB5YW5nIGxhaW4uDQoNCmBnZ3Bsb3QyYCBkaWRlc2FpbiB1bnR1ayBiZWtlcmphIHNlY2FyYSBpdGVyYXRpZi4gS2F0YSBgZ2dwbG90YCBzZW5kaXJpIG1lcnVwYWthIGtlcGVuZGVrYW4gZGFyaSBbKipncmFtbWFyIG9mIGdyYXBoaWNzIHBsb3QqKl0oKS4gVGVyZGFwYXQgZHVhIGZ1bmdzaSB1dGFtYSB5YW5nIGRpZ3VuYWthbiB1bnR1ayB2aXN1YWxpc2FzaSBkYXRhLiBGdW5nc2kgYHFwbG90KClgLCB5YW5nIG1lcnVwYWthbiBrZXBlbmRla2FuIGRhcmkgKnF1aWNrLXBsb3QqLiBQZW5nZ3VuYWFuIGBxcGxvdCgpYCBzZW5kaXJpIHRpZGFrIHRlcmxhbHUgYmFueWFrLCBiYWhrYW4gcGVtYnVhdG55YSBzZW5kaXJpIG1lbmdhdGFrYW4gYmFod2EgYHFwbG90KClgIGRpYnVhdCB1bnR1ayBtZXJla2EgeWFuZyBtZW1hbmcgc3VkYWggc2FuZ2F0IHRlcmJpYXNhIG1lbmdndW5ha2FuIGZ1bmdzaSBgcGxvdCgpYCBwYWRhIGJhc2UgUi4gDQoNCkZ1bmdzaSBrZWR1YSwgZGFuIHlhbmcgcGFsaW5nIHNlcmluZyBkaWd1bmFrYW4sIGFkYWxhaCBgZ2dwbG90KClgLiBQZXJiZWRhYW4geWFuZyBwYWxpbmcgbWVuY29sb2sgZGFyaSBmdW5nc2kgYHFwbG90KClgIGRhbiBgZ2dwbG90KClgIGFkYWxhaCB0aXBlIGRhdGEgeWFuZyBkYXBhdCBkaWd1bmFrYW4uIEZ1bmdzaSBgcXBsb3QoKWAgZGFwYXQgbWVuZ2d1bmFrYW4gdmVjdG9yIGF0YXUgZGF0YWZyYW1lLCBzZWRhbmdrYW4gYGdncGxvdCgpYGhhbnlhIG1lbmVyaW1hIGRhdGFmcmFtZS4gRnVuZ3NpLWZ1bmdzaSBwYWRhIGBnZ3Bsb3QyYCBiZXJwZXJhbiBzZWJhZ2FpIGxheWVyIHlhbmcgZGl0YW5kYWkgZGVuZ2FuIGArYC4gDQoNCkJlcmlrdXQgY29udG9oIGRhcmkgcGVuZ2d1bmFhbiBgcXBsb3QoKWAuIA0KDQpgYGB7cn0NCm5vdF9jYW5jZWwgPC0gZmxpZ2h0c190YmwgJT4lIA0KICBmaWx0ZXIoIWlzLm5hKGRlcF9kZWxheSkpDQpgYGANCg0KYGBge3J9DQpxcGxvdCh4ID0gbmFtZSwgZGF0YSA9IG5vdF9jYW5jZWwpDQpgYGANCg0KVGVudHUgc2FqYSB0YW1waWxhbiBncmFmaWsgc2VwZXJ0aSBkaSBhdGFzIHRpZGFrIGJhaWsga2FyZW5hIGluZm9ybWFzaSB5YW5nIGRpYmVyaWthbiBrdXJhbmcgc2VtcHVybmEuIE5hbWEgbWFza2FwYWkgcGVuZXJiYW5nYW4geWFuZyBzYWxpbmcgdHVtcGFuZyB0aW5kaWgganVnYSBtZW1idWF0IGdyYWZpayB0ZXJzZWJ1dCB0aWRhayBtZW5hcmlrLg0KDQpgYGB7cn0NCnFwbG90KHggPSBuYW1lLCBkYXRhID0gbm90X2NhbmNlbCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmBgYA0KDQpGdW5nc2kgYHRoZW1lKClgIGFrYW4gZGlhYmhhcyBsZWJpaCBiYW55YWsgZGkgYmFnaWFuIGJlcmlrdXRueWEuDQoNCiMgVmlzdWFsaXNhc2kgZGVuZ2FuIGdncGxvdCgpDQoNCkZ1bmdzaSB5YW5nIHBhbGluZyBzZXJpbmcgZGlndW5ha2FuIGRhcmkgcGFja2FnZSBgZ2dwbG90MmAgYWRhbGFoIGBnZ3Bsb3QoKWAuIFNlY2FyYSB1bXVtIHZpc3VhbGlzYXNpIG1lbmdndW5ha2FuIGBnZ3Bsb3QoKWAgYWRhbGFoIHNlcGVydGkgYmVyaWt1dC4NCg0KYGBge3IgZ2dwbG90LCBldmFsPUZBTFNFfQ0KZ2dwbG90KGRhdGEgPSA8REFUQT4sIG1hcHBpbmcgPSBhZXMoeCA9IDxWQVI+LCB5ID0gPFZBUj4pKSArIA0KICA8R0VPTV9GVU5DVElPTj4oKQ0KYGBgDQoNCkFuZGEgZGFwYXQgbWVtcGVsYWphcmkgdGVudGFuZyBgZ2dwbG90MmAgZGFyaSBidWt1ICpnZ3Bsb3QyIEVsZWdhbnQgR3JhcGhpY3MgZm9yIERhdGEgQW5hbHlzaXMgc2Vjb25kIGVkaXRpb24qLg0KDQoNCiMjIEJhcnBsb3QNCg0KWWFuZyBwZXJ0YW1hIGtpdGEgYWthbiBtZWxha3VrYW4gZWtzcGxvcmFzaSBkYXRhIGRlbmdhbiBtZW5nZ3VuYWthbiBkaWFncmFtIGJhdGFuZyBhdGF1IGJhcnBsb3QuIEdyYWZpayB5YW5nIGFrYW4gZGloYXNpbGthbiBzYW1hIGRlbmdhbiB5YW5nIGRpaGFzaWxrYW4gb2xlaCBgcXBsb3QoKWAgc2ViZWx1bW55YS4NCg0KYGBge3IgYmFyMX0NCmdncGxvdChkYXRhID0gbm90X2NhbmNlbCwgbWFwcGluZyA9IGFlcyh4ID0gbmFtZSkpICsNCiAgICBnZW9tX2JhcigpICsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmBgYA0KDQpHZW9tIGBnZW9tX2JhcigpYCBkYXBhdCBtZW5lcmltYSBkYXRhIGRlbmdhbiBmb3JtYXQgbWVtYW5qYW5nIChyYXcpIGF0YXUgdGFidWxhc2kuDQoNCmBgYHtyfQ0Kbm90X2NhbmNlbCAlPiUgDQogIHNlbGVjdChuYW1lKQ0KYGBgDQoNCkppa2EgYmVudHVrIGRhdGFueWEgbWVtYW5qYW5nIHNlcGVydGkgaW5pIG1ha2EgY3VrdXAgbWVueWVidXRrYW4gdmFyaWFiZWxueWEgZGkgYXJndW1lbiBgeCA9IGAgcGFkYSBgYWVzKClgLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbm90X2NhbmNlbCwgbWFwcGluZyA9IGFlcyh4ID0gbmFtZSkpICsNCiAgICBnZW9tX2JhcigpICsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmBgYA0KDQpKaWthIGRhdGFueWEgZGFsYW0gYmVudHVrIHRhYnVsYXNpLCBzZWJ1dGthbiBrYXRlZ29yaW55YSBkaSBhcmd1bWVuIGB4ID0gYCBkYW4gbmlsYWlueWEgZGkgYXJndW1lbiBgeSA9IGAsIGtlbXVkaWFuIHRhbWJhaGthbiBgc3RhdCA9ICJpZGVudGl0eSJgIHBhZGEgYGdlb21fYmFyKClgLg0KDQpgYGB7cn0NCm5vdF9jYW5jZWwgPC0gbm90X2NhbmNlbCAlPiUgDQogIGNvdW50KG5hbWUpIA0Kbm90X2NhbmNlbA0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBub3RfY2FuY2VsLCBtYXBwaW5nID0gYWVzKHggPSBuYW1lLCB5ID0gbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpgYGANCg0KU2VjYXJhIGBkZWZhdWx0YCB0YW1waWxhbiBiYXJwbG90IGRpdXJ1dGthbiBiZXJkYXNhcmthbiBhbGZhYmV0IGthdGVnb3JpIHBhZGEgYXJndW1lbiBgeGAuIEppa2EgaW5naW4gYmFycGxvdCBkaXVydXRrYW4gYmVyZGFzYXJrYW4gbmlsYWlueWEsIGd1bmFrYW4gZnVuZ3NpIGByZW9yZGVyKClgLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbm90X2NhbmNlbCwgbWFwcGluZyA9IGFlcyh4ID0gcmVvcmRlcihuYW1lLCAtbiksIHkgPSBuKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmBgYA0KDQpVbnR1ayBtZW5ndXJ1dGthbiBiYXJwbG90IGJlcmRhc2Fya2FuIHZhbHVlDQoNCiAxLiBCdWF0IHRlcmxlYmloIGRhaHVsdSB0YWJlbCBmcmVrdWVuc2kgZGFyaSBrYXRlZ29yaSB5YW5nIGRpaW5naW5rYW4uDQogMi4gZ3VuYWthbiBmdW5nc2kgcmVvcmRlcigpIHBhZGEgYWVzdGV0aGljIGB4YCBkYW4gbiBzYmcgYHlgLg0KIDMuIGd1bmFrYW4gYHN0YXQgPSAiaWRlbnRpdHkiYCBwYWRhIGBnZW9tX2JhcigpYC4NCg0KYGBge3J9DQpub3RfY2FuY2VsIDwtIG5vdF9jYW5jZWwgJT4lIA0KICBtdXRhdGUocGN0ID0gbi9zdW0obikpDQoNCmdncGxvdChkYXRhID0gbm90X2NhbmNlbCwgbWFwcGluZyA9IGFlcyh4ID0gcmVvcmRlcihuYW1lLCAtcGN0KSwgeSA9IHBjdCkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpgYGANCg0KYGBge3J9DQpjYW5jZWxlZCA8LSBmbGlnaHRzX3RibCAlPiUgDQogIGZpbHRlcihpcy5uYShkZXBfZGVsYXkpKSAlPiUgDQogIGNvdW50KG5hbWUpICU+JSANCiAgbXV0YXRlKHBjdCA9IG4vc3VtKG4pKQ0KY2FuY2VsZWQNCmBgYA0KDQpEYXJpIHRhYmVsIGRpIGF0YXMga2l0YSBkYXBhdCBtZW5nZXRhaHVpIGJhaHdhIEV4cHJlc3NKZXQgQWlybGluZXMgSW5jLiB5YW5nIHBhbGluZyBiYW55YWsgbWVsYWt1a2FuIGNhbmNlbCBzZWxhbWEgdGFodW4gMjAxMy4gU2VrYXJhbmcgbWFyaSBraXRhIHRhbXBpbGthbiBkYWxhbSB2aXN1YWxpc2FzaS4NCg0KYGBge3J9DQpnIDwtIGdncGxvdChkYXRhID0gY2FuY2VsZWQsIG1hcHBpbmcgPSBhZXMoeCA9IHJlb3JkZXIobmFtZSwgLXBjdCksIHkgPSBwY3QpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KDQpnDQpgYGANCg0KYGBge3J9DQpnICsNCiAgbGFicyh0aXRsZSA9ICJQZXJzZW50YXNlIE1hc2thcGFpIFlhbmcgU2VyaW5nIE1lbGFrdWthbiBDYW5jZWwiLA0KICAgICAgIHggPSAiTWFza2FwYWkiLA0KICAgICAgIHkgPSAiUGVyc2VudGFzZSIpICsNCiAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgdmp1c3QgPSAxKSkNCmBgYA0KDQpIYXNpbCBkYXJpIGBnZ3Bsb3RgIGRhcGF0IGRpc2ltcGFuIGRhbGFtIHNlYnVhaCBvYmplayBkaSBSIGtlbXVkaWFuIGRpdGFtYmFoa2FuIGxheWVyIGRlbmdhbiBgK2AgZGFuIGBnZW9tYCBhdGF1IGtvbXBvbmVuIGxhaW4uDQoNCktpdGEganVnYSBkYXBhdCBtZW5lbnR1a2FuIHdhcm5hIHVudHVrIG1hc2luZy1tYXNpbmcgYmF0YW5nIHBhZGEgYmFycGxvdC4gTWlzYWxueWEsIHNldGlhcCBiYXRhbmcgYmVyYmVkYSB3YXJuYW55YSBiZXJkYXNhcmthbiBuYW1hIGthdGVnb3JpbnlhIHNlY2FyYSBkZWZhdWx0Lg0KDQpVbnR1ayBtZW5hbWJhaGthbiBqdWR1bCAoYHRpdGxlYCksIG1lbmdnYW50aSBqdWR1bCBtYXNpbmctbWFzaW5nIGF4aXMgYHhgIGRhbiBgeWAgYXRhdSBtZW5hbWJhaGthbiBgc3VidGl0bGVgZGFwYXQgbWVuZ2d1bmFrYW4gYGxhYnMoKWAuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBjYW5jZWxlZCwgbWFwcGluZyA9IGFlcyh4ID0gcmVvcmRlcihuYW1lLCAtcGN0KSwgeSA9IHBjdCwgZmlsbCA9IG5hbWUpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApLA0KICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCB2anVzdCA9IDEpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgbGFicyh0aXRsZSA9ICJQZXJzZW50YXNlIE1hc2thcGFpIFlhbmcgU2VyaW5nIE1lbGFrdWthbiBDYW5jZWwiLA0KICAgICAgIHggPSAiTWFza2FwYWkiLA0KICAgICAgIHkgPSAiUGVyc2VudGFzZSIpDQpgYGANCg0KQXJndW1lbiBgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiYCBkaWd1bmFrYW4gdW50dWsgbWVuZ2hpbGFuZ2thbiBsZWdlbmQgZGFyaSBncmFmaWsgeWFuZyBkaWJ1YXQuIENvYmFsYWggdW50dWsgbWVuZ2hhcHVzIGFyZ3VtZW4gYGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lImAgZGFuIGxpaGF0IGhhc2lsbnlhLg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTZ9DQpnZ3Bsb3QoZGF0YSA9IGNhbmNlbGVkLCBtYXBwaW5nID0gYWVzKHggPSByZW9yZGVyKG5hbWUsIC1wY3QpLCB5ID0gcGN0KjEwMCwgZmlsbCA9IG5hbWUpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZShyb3VuZChwY3QqMTAwLCAyKSwgIiUiKSksIHZqdXN0ID0gLTAuMjUpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHZqdXN0ID0gMSksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBsYWJzKHRpdGxlID0gIlBlcnNlbnRhc2UgTWFza2FwYWkgWWFuZyBTZXJpbmcgTWVsYWt1a2FuIENhbmNlbCIsDQogICAgICAgeCA9ICJNYXNrYXBhaSIsDQogICAgICAgeSA9ICJQZXJzZW50YXNlIikNCmBgYA0KDQpBbmRhIGp1Z2EgZGFwYXQgbWVsYWt1a2FuIG9wZXJhc2kgbWF0ZW1hdGlrYSBzZWRlcmhhbmEgdW50dWsgdmFyaWFiZWwgeWFuZyBha2FuIGRpZ3VuYWthbi4gTWlzYWxueWEgbWVuZ3ViYWggc2F0dWFuIGBwY3RgIGRlbmdhbiBkaWthbGlrYW4gMTAwLiBgZ2VvbV90ZXh0KClgIGRpZ3VuYWthbiB1bnR1ayBtZW5hbWJhaGthbiBrb21wb25lbiBsYXllciB0ZWtzIHBhZGEgZ3JhZmlrLiBEYWxhbSBrYXN1cyBpbmkgZGlndW5ha2FuIHVudHVrIG1lbmFtcGlsa2FuIG5pbGFpIGRpIGF0YXMgbWFzaW5nLW1hc2luZyBiYXRhbmcuIGB2anVzdCA9IC0wLjI1YCBhZ2FyIHRla3MgYmVyYWRhIGRpIGF0YXMgYmF0YW5nLg0KDQpgYGB7ciBmaWcud2lkdGggPSAxNiwgZmlnLmhlaWdodD0xMH0NCmdncGxvdChkYXRhID0gY2FuY2VsZWQsIG1hcHBpbmcgPSBhZXMoeCA9IHJlb3JkZXIobmFtZSwgcGN0KSwgeSA9IHBjdCoxMDAsIGZpbGwgPSBuYW1lKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUocm91bmQocGN0KjEwMCwgMiksICIlIikpLCBoanVzdCA9IC0wLjAxKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBsYWJzKHRpdGxlID0gIlBlcnNlbnRhc2UgTWFza2FwYWkgWWFuZyBTZXJpbmcgTWVsYWt1a2FuIENhbmNlbCIsDQogICAgICAgeCA9ICJNYXNrYXBhaSIsDQogICAgICAgeSA9ICJQZXJzZW50YXNlIikgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQpBZ2FyIHRhbXBpbGFuIGJhcnBsb3QgZGVuZ2FuIGBheGlzLnRleHQueGAgbGViaWggbWVuYXJpaywgZ3VuYWthbiBgY29vcmRfZmxpcCgpYCB1bnR1ayBtZW11dGFyIGdyYWZpay4gUG9zaXNpIGF4aXMgYHhgIGRpIHBvc2lzaSBgeWAgZGFuIHNlYmFsaWtueWEuDQoNCiMjIEhpc3RvZ3JhbSAmIERlbnNpdHkNCg0KSGlzdG9ncmFtIGF0YXUgRGVuc2l0eSBkYXBhdCBkaWd1bmFrYW4gdW50dWsgbWVtdmlzdWFsaXNhc2lrYW4gc2ViYXJhbiBkYXJpIHNlYnVhaCB2YXJpYWJlbCBudW1lcmlrLg0KDQoNCmBgYHtyfQ0KZyA8LSBnZ3Bsb3QoZGF0YSA9IGZsaWdodHNfdGJsLCBtYXBwaW5nID0gYWVzKHggPSBhaXJfdGltZSkpDQpnDQpgYGANCg0KS2FyZW5hIHZhcmlhYmVsIHlhbmcgYWthbiBraXRhIGd1bmFrYW4gc2FtYSwgeWFpdHUgaGFueWEgYGFpcl90aW1lYCBtYWthIGtpdGEgc2ltcGFuIGRhaHVsdSBoYXNpbCBkYXJpIGdncGxvdCBrZSBkYWxhbSBvYmplayBgZ2Agc2VoaW5nZ2Ega2l0YSBkYXBhdCBtZW5nZ2FudGkgYXJndW1lbiBwYWRhIGBnZW9tX2hpc3RvZ3JhbSgpYCB0YW5wYSBwZXJsdSBtZW1hbmdnaWwgbGFnaSBmdW5nc2kgYGdncGxvdCgpYCBkZW5nYW4gYXJndW1lbiB5YW5nIHNhbWEgc2VwZXJ0aXNlYmVsdW1ueWEuIEtldGlrYSBraXRhIHBhbmdnaWwgb2JqZWsgZGFyaSBgZ2dwbG90YCB0ZXJzZWJ1dCwgbWFrYSBobmF5YSBtZW5hcGlsa2FuIHNlYnVhaCBrYW52YXMga29zb25nIGthcmVuYSBraXRhIGJlbHVtIG1lbmFtYmFoa2FuIGxheWVyIGBnZW9tYCBhcGEgeWFuZyBha2FuIGRpZ3VuYWthbi4NCg0KYGBge3J9DQpnICsNCiAgZ2VvbV9oaXN0b2dyYW0oKQ0KYGBgDQoNCktldGlrYSBraXRhIHRhbWJhaGthbiBgZ2VvbV9oaXN0b2dyYW0oKWAgbWFrYSBha2FuIGRpdGFtYmFoa2FuIGhpc3RvZ3JhbS4gU2VjYXJhIGRlZmF1bHQgYGdlb21faGlzdG9ncmFtKClgIG1lbmdndW5ha2FuIDMwIGJpbnMuIGBiaW5zYCBhZGFsYWggYmFueWFrbnlhIGtvdGFrL2JhdGFuZywgc2VkYW5na2FuIGxlYmFyIGJhdGFuZyBkaXNlYnV0IGBiaW53aWR0aGAuIEppa2EgYWRhIGBOQWAgbWFrYSBzZWNhcmEgb3RvbWF0aXMgYWthbiBkaWJ1YW5nLg0KDQpgYGB7cn0NCmcgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNTAsIGZpbGwgPSAic2t5Ymx1ZSIsIGNvbG9yID0gIndoaXRlIikNCmBgYA0KDQpEZW5nYW4gYGJpbnMgPSA1MGAgYXJ0aW55YSBwYWRhIGhpc3RvZ3JhbSB0ZXJzZWJ1dCBha2FuIGRpZ3VuYWthbiA1MCBiYXRhbmcuIEFyZ3VtZW4gYGZpbGwgPSAic2t5Ymx1ZSJgIHVudHVrIG1lbWJlcmlrYW4gd2FybmEgcGFkYSBiYXRhbmcgaGlzdG9ncmFtLiBBbmRhIGp1Z2EgZGFwYXQgbWVuZ2d1bmFrYW4gd2FybmEgbGFpbiwgbWlzYWxueWEgYGZpbGwgPSAicGluayJgIGF0YXUgYGZpbGwgPSAibGlnaHRibHVlImAgYXRhdSBiYWhrYW4gbWVuZ2d1bmFrYW4gW2tvZGUgd2FybmEgSGV4XShodHRwczovL3d3dy5nb29nbGUuY29tL3NlYXJjaD9zYWZlPXN0cmljdCZlaT1HUzcxWE4yNkw1clh6N3NQcDl1bHdBdyZxPSUyM2ZmZmZmZiZvcT0lMjNmZmZmZmYmZ3NfbD1wc3ktYWIuMy4uLjUyMjQuNTIyNC4uNTgyNC4uLjAuMC4uMC4yNTEuMjUxLjItMS4uLi4uLjAuLi4uMS4uZ3dzLXdpei4uLi4uLi4waTcxLjgzdmlYcHJVWGE4KS4NCg0KU2ViYWdpYW4gYmVzYXIgbGFtYW55YSBwZW5lcmJhbmdhbiBrdXJhbmcgZGFyaSAyMDAgbWVuaXQuIE5hbXVuIGRhcmkgZ3JhZmlrIGRpIGF0YXMgdGVybGloYXQgYWRhIGJlYmVyYXBhIHB1bmNhayB5YW5nIHRlcmphZGkuIE1hcmkga2l0YSBsaWhhdCBtZW5nZ3VuYWthbiBgZ2VvbV9kZW5zaXR5KClgIA0KDQpgYGB7cn0NCmcgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgYmlucyA9IDUwLCBmaWxsID0gInNreWJsdWUiLCBjb2xvciA9ICJ3aGl0ZSIpICsNCiAgZ2VvbV9kZW5zaXR5KGNvbG9yID0gImRhcmtncmVlbiIsIHNpemUgPSAwLjcpDQpgYGANCg0KYGBge3J9DQpnICsNCiAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAic2t5Ymx1ZSIsIGFscGhhID0gMC41KQ0KYGBgDQoNCk5hbXVuIGppa2EgZGlwZXJoYXRpa2FuLCBhZGEganVnYSBwZW5lcmJhbmdhbiB5YW5nIGxhbWEgd2FrdHUgdGVyYmFuZ255YSBsZWJpaCBkYXJpIDYwMCBtZW5pdCBhdGF1IDEwIGphbS4gS2l0YSBsaWhhdCBkYXRhIGRlbmdhbiBwZW5lcmJhbmdhbiBzZWxhbWEgc2VraXRhciAxMCBqYW0gdGVyc2VidXQuDQoNCmBgYHtyfQ0KZmxpZ2h0czEwaCA8LSBmbGlnaHRzX3RibCAlPiUgDQogIGZpbHRlcihhaXJfdGltZSA+IDUwMCkNCg0KZmxpZ2h0czEwaCAlPiUgDQogIGdyb3VwX2J5KG9yaWdpbiwgZGVzdCkgJT4lIA0KICBzdW1tYXJpc2UobWluID0gbWluKGFpcl90aW1lKSwgYXZnID0gbWVhbihhaXJfdGltZSksIHN0ZCA9IHNkKGFpcl90aW1lKSwgbWF4ID0gbWF4KGFpcl90aW1lKSkNCmBgYA0KDQpUZXJueWF0YSBwZW5lcmJhbmdhbiBkYXJpIGJhbmRhcmEgYEVXUmAgZGFuIGBKRktgIG1lbnVqdSBiYW5kYXJhIGBITkxgIHJhdGEtcmF0YSBtZW1idXR1aGthbiB3YWt0dSB0ZXJiYW5nIGxlYmloIGRhcmkgNjAwIG1lbml0IGF0YXUgMTAgamFtLiBBcGEgaXR1IGBITkxgPw0KDQpgYGB7cn0NCmFpcnBvcnRzICU+JSANCiAgZmlsdGVyKGZhYSA9PSAiSE5MIikNCmBgYA0KDQoNCiMjIEJveHBsb3QNCg0KU2VjYXJhIGRlZmF1bHQsIGJveHBsb3QgZGkgYGdncGxvdDJgIG1lbWVybHVrYW4gdmFyaWFiZWwga2F0ZWdvcmlrIHNlYmFnYWkgYXJndW1lbiBgeGAgZGFuIHZhcmlhYmVsIG51bWVyaWsgc2ViYWdhaSBhcmd1bWVuIGB5YC4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGZsaWdodHNfdGJsLCBtYXBwaW5nID0gYWVzKHggPSBuYW1lLCB5ID0gd2luZF9zcGVlZCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQpKaWthIGhhbnlhIGluZ2luIHNhdHUgYm94cGxvdCB1bnR1ayBzYXR1IHZhcmlhYmVsIHRhbnBhIGRpYmVkYWthbiBiZXJkYXNhcmthbiBrYXRlZ29yaSBsYWluIG1ha2EgY3VrdXAgZ3VuYWthbiBgeCA9ICcnYCBhdGF1IHN0cmluZyBsYWluIHNlcGVydGkgY29udG9oIGJlcmlrdXQuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBmbGlnaHRzX3RibCwgbWFwcGluZyA9IGFlcyh4ID0gIlN0YXRpc3RpY3MiLCB5ID0gdGVtcCkpICsNCiAgZ2VvbV9ib3hwbG90KCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZmxpZ2h0c190YmwsIG1hcHBpbmcgPSBhZXMoeCA9IG9yaWdpbiwgeSA9IHRlbXApKSArDQogIGdlb21fYm94cGxvdCgpICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGZsaWdodHNfdGJsLCBtYXBwaW5nID0gYWVzKHggPSBvcmlnaW4sIHkgPSB0ZW1wKSkgKw0KICBnZW9tX2JveHBsb3QoY29sb3IgPSAic2t5Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KQW5kYSBkYXBhdCBtZW1iYWNhIGJlYmVyYXBhIGFydGlrZWwgdGVudGFuZyBgZ2VvbV9ib3hwbG90KClgLCBzYWxhaCBzYXR1bnlhIFtnZW9tX2JveHBsb3RdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9nZW9tX2JveHBsb3QuaHRtbCkuDQoNCg0KIyMgU2NhdHRlciBwbG90DQoNClNjYXR0ZXIgcGxvdCBhZGFsYWggdmlzdWFsaXNhc2kgZGF0YSBkYXJpIGR1YSBidWFoIHZhcmlhYmVsIG51bWVyaWsuIE1pc2Fsa2FuIGRhcmkgZGF0YSBgd2VhdGhlcmAga2l0YSBhbWJpbCBkYXRhIGN1YWNhIGRpIGJhbmRhcmEgYEpGS2Agc2FqYSBzZWxhbWEgdGFodW4gMjAxMy4NCg0KYGBge3J9DQp3ZWF0aGVyX2pmayA8LSB3ZWF0aGVyICU+JSANCiAgZmlsdGVyKG9yaWdpbiA9PSAiSkZLIikgDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHdlYXRoZXJfamZrLCBtYXBwaW5nID0gYWVzKHggPSB0aW1lX2hvdXIsIHkgPSB0ZW1wKSkgKyANCiAgZ2VvbV9wb2ludCgpDQpgYGANCg0KQW5kYSBkYXBhdCBtZXJ1YmFoIGBzaGFwZWAgZGFyaSBgZ2VvbV9wb2ludGAgZGVuZ2FuIHNlYnVhaCBiaWxhbmdhbiBpbnRlZ2VyLg0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCiAgICA8aW1nIHNyYz0icG9pbnRzaGFwZS5wbmciIGFsdD0iUG9pbnQgU2hhcGUiPg0KICAgIDxici8+DQogICAgPGJyLz4NCiAgICA8ZW0+UG9pbnQgU2hhcGU8L2VtPg0KPC9wPg0KDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSB3ZWF0aGVyX2pmaywgbWFwcGluZyA9IGFlcyh4ID0gdGltZV9ob3VyLCB5ID0gdGVtcCkpICsgDQogIGdlb21fcG9pbnQoc2hhcGUgPSAxKSArDQogIGxhYnModGl0bGUgPSAiU3VodSBCYW5kYXJhIEpGSyBTZWxhbWEgVGFodW4gMjAxMyIsDQogICAgICAgeCA9ICJ3YWt0dSIsDQogICAgICAgeSA9ICJTdWh1IikNCmBgYA0KDQojIyBMaW5lIHBsb3QvdGltZSBzZXJpZXMgcGxvdA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gd2VhdGhlcl9qZmssIG1hcHBpbmcgPSBhZXMoeCA9IHRpbWVfaG91ciwgeSA9IHRlbXApKSArIA0KICBnZW9tX2xpbmUoKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSB3ZWF0aGVyX2pmaywgbWFwcGluZyA9IGFlcyh4ID0gdGltZV9ob3VyKSkgKyANCiAgZ2VvbV9saW5lKGFlcyh5ID0gdGVtcCksIGNvbG9yID0gInNreWJsdWUiKQ0KYGBgDQoNCktldGViYWxhbiBnYXJpcyBkYXBhdCBkaXNlc3VhaWthbiBkZW5nYW4gYHNpemUgPWAsIG1pc2FsbnlhIGBzaXplID0gMWAgdW50dWsgZ2FyaXMgeWFuZyBsZWJpaCB0ZWJhbCBkYXJpIGRlZmF1bHQuIA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gd2VhdGhlcl9qZmssIG1hcHBpbmcgPSBhZXMoeCA9IHRpbWVfaG91ciwgeSA9IHRlbXApKSArIA0KICBnZW9tX2xpbmUoY29sb3IgPSAic2t5Ymx1ZSIpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMC41KSArDQogIHNjYWxlX3hfZGF0ZXRpbWUoYnJlYWtzID0gIjEgbW9udGgiLCBkYXRlX2xhYmVscyA9ICIlYiAlZCIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHdlYXRoZXJfamZrLCBtYXBwaW5nID0gYWVzKHggPSB0aW1lX2hvdXIsIHkgPSB0ZW1wKSkgKyANCiAgZ2VvbV9saW5lKCkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAwLjUpICsNCiAgc2NhbGVfeF9kYXRldGltZShicmVha3MgPSAiZGF5cyIsIGRhdGVfbGFiZWxzID0gIiViICVkIikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gd2VhdGhlcl9qZmssIG1hcHBpbmcgPSBhZXMoeCA9IHRpbWVfaG91ciwgeSA9IHRlbXApKSArIA0KICBnZW9tX2xpbmUoKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSkgKw0KICBzY2FsZV94X2RhdGV0aW1lKGJyZWFrcyA9ICIxIG1vbnRoIiwgZGF0ZV9sYWJlbHMgPSAiJWIgJWQiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKSArDQogIGxhYnModGl0bGUgPSAiU3VodSBCYW5kYXJhIEpGSyBTZWxhbWEgVGFodW4gMjAxMyIsDQogICAgICAgeCA9ICJXYWt0dSIsDQogICAgICAgeSA9ICJTdWh1IikNCmBgYA0KDQoNCmBgYHtyfQ0Kd2VhdGhlcl9qZmsgJT4lIA0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gdGltZV9ob3VyLCB5ID0gdGVtcCkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBnZW9tX3Ntb290aCgpDQpgYGANCg0KYGBge3J9DQp3ZWF0aGVyICU+JSANCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHRpbWVfaG91ciwgeSA9IHRlbXAsIGNvbG9yID0gb3JpZ2luKSkgKw0KICAjIGdlb21fbGluZSgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjZWE1NDU0IiwgIiM2ZjQ1ZDgiLCAiIzU3YzE1OCIpKQ0KYGBgDQoNCiMgRmFjZXQNCg0KYGBge3J9DQpmbGlnaHRzX2F1ZzIgPC0gZmxpZ2h0c190YmwgJT4lIA0KICBpbm5lcl9qb2luKGFpcnBvcnRzLCBieSA9IGMoIm9yaWdpbiIgPSAiZmFhIiksIHN1ZmZpeCA9IGMoIl9jYXJyaWVyIiwgIl9vcmlnaW5haXJwb3J0cyIpKSAlPiUgDQogIGZpbHRlcighaXMubmEoZGVwX2RlbGF5KSAmIG1vbnRoID09IDgpICU+JSANCiAgbXV0YXRlKHRnbCA9IGFzLkRhdGUocGFzdGUoeWVhciwgbW9udGgsIGRheSwgc2VwID0gIi0iKSkpICU+JSANCiAgZ3JvdXBfYnkobmFtZV9vcmlnaW5haXJwb3J0cywgdGdsKSAlPiUgDQogIHN1bW1hcmlzZShuID0gbigpKSANCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZmxpZ2h0c19hdWcyLCBtYXBwaW5nID0gYWVzKHggPSB0Z2wsIHkgPSBuKSkgKyANCiAgZ2VvbV9saW5lKGNvbG9yID0gInNreWJsdWUiKSArDQogIGdlb21fcG9pbnQoKSArDQogIGZhY2V0X2dyaWQocm93cyA9IHZhcnMobmFtZV9vcmlnaW5haXJwb3J0cykpICsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcyA9ICJkYXlzIiwgZGF0ZV9sYWJlbHMgPSAiJWQiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUpKSArDQogIGxhYnModGl0bGUgPSAiSnVtbGFoIFBlbmVyYmFuZ2FuIEhhcmlhbiBCdWxhbiBBZ3VzdHVzIDIwMTMiLA0KICAgICAgIHggPSAiVGFuZ2dhbCIsDQogICAgICAgeSA9ICJKdW1sYWgiKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBmbGlnaHRzX2F1ZzIsIG1hcHBpbmcgPSBhZXMoeCA9IHRnbCwgeSA9IG4pKSArIA0KICBnZW9tX2xpbmUoY29sb3IgPSAic2t5Ymx1ZSIpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZmFjZXRfZ3JpZChuYW1lX29yaWdpbmFpcnBvcnRzIH4gLiApICsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcyA9ICJkYXlzIiwgZGF0ZV9sYWJlbHMgPSAiJWQiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUpKSArDQogIGxhYnModGl0bGUgPSAiSnVtbGFoIFBlbmVyYmFuZ2FuIEhhcmlhbiBCdWxhbiBBZ3VzdHVzIDIwMTMiLA0KICAgICAgIHggPSAiVGFuZ2dhbCIsDQogICAgICAgeSA9ICJKdW1sYWgiKQ0KYGBgDQoNCmBgYHtyfQ0KDQpnZ3Bsb3QoZGF0YSA9IGZsaWdodHNfYXVnMiwgbWFwcGluZyA9IGFlcyh4ID0gdGdsLCB5ID0gbikpICsgDQogIGdlb21fbGluZShjb2xvciA9ICJza3libHVlIikgKw0KICBnZW9tX3BvaW50KCkgKw0KICBmYWNldF9ncmlkKGNvbHMgPSB2YXJzKG5hbWVfb3JpZ2luYWlycG9ydHMpKSArDQogIGxhYnModGl0bGUgPSAiSnVtbGFoIFBlbmVyYmFuZ2FuIEhhcmlhbiBCdWxhbiBBZ3VzdHVzIDIwMTMiLA0KICAgICAgIHggPSAiVGFuZ2dhbCIsDQogICAgICAgeSA9ICJKdW1sYWgiKQ0KYGBgDQoNCmBgYHtyfQ0KDQpnZ3Bsb3QoZGF0YSA9IGZsaWdodHNfYXVnMiwgbWFwcGluZyA9IGFlcyh4ID0gdGdsLCB5ID0gbikpICsgDQogIGdlb21fbGluZShjb2xvciA9ICJza3libHVlIikgKw0KICBnZW9tX3BvaW50KCkgKw0KICBmYWNldF9ncmlkKCAuIH4gbmFtZV9vcmlnaW5haXJwb3J0cykgKw0KICBsYWJzKHRpdGxlID0gIkp1bWxhaCBQZW5lcmJhbmdhbiBIYXJpYW4gQnVsYW4gQWd1c3R1cyAyMDEzIiwNCiAgICAgICB4ID0gIlRhbmdnYWwiLA0KICAgICAgIHkgPSAiSnVtbGFoIikNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZmxpZ2h0c19hdWcyLCBtYXBwaW5nID0gYWVzKHggPSB0Z2wsIHkgPSBuKSkgKyANCiAgZ2VvbV9saW5lKGNvbG9yID0gInNreWJsdWUiKSArDQogIGdlb21fcG9pbnQoKSArDQogIGZhY2V0X2dyaWQocm93cyA9IHZhcnMoKSwgY29scyA9IHZhcnMobmFtZV9vcmlnaW5haXJwb3J0cykpICsNCiAgbGFicyh0aXRsZSA9ICJKdW1sYWggUGVuZXJiYW5nYW4gSGFyaWFuIEJ1bGFuIEFndXN0dXMgMjAxMyIsDQogICAgICAgeCA9ICJUYW5nZ2FsIiwNCiAgICAgICB5ID0gIkp1bWxhaCIpDQpgYGANCg0KU2V0ZWxhaCBzZWxlc2FpIG1lbWJ1YXQgZ3JhZmlrIHlhbmcgQW5kYSBpbmdpbmthbiwgQW5kYSBkYXBhdCBtZW55aW1wYW4gZ3JhZmlrIHRlcnNlYnV0IGRlbmdhbiBmdW5nc2kgYGdnc2F2ZSgpYC4NCg0KYGBge3IgZ2dzYXZlfQ0KZyA8LSBmbGlnaHRzX3RibCAlPiUgDQogIGZpbHRlcihvcmlnaW4gPT0gIkpGSyIpICU+JSANCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHRpbWVfaG91ciwgeSA9IHRlbXApKSArDQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV9zbW9vdGgoKQ0KDQpnZ3NhdmUoZmlsZW5hbWUgPSAicGxvdFIuanBnIiwgcGxvdCA9IGcsIHdpZHRoID0gMTAsIGhlaWdodCA9IDcpDQpgYGANCg0KSmlrYSBBbmRhIHRpZGFrIG1lbnllYnV0a2FuIG9iamVrIGBnZ3Bsb3QyYCBwYWRhIGBwbG90ID1gIG1ha2Egc2VjYXJhIG90b21hdGlzIHBsb3QgeWFuZyB0ZXJha2hpciBkaWJ1YXQgeWFuZyBha2FuIGRpcGlsaWguDQo=