Visualización de Datos con ggplot2

La visualización de datos es una de las etapas fundamentales en el análisis de datos porque se relaciona con la comunicación de los resultados.

Una buena visualización permite:

  • condensar mucha información
  • evidenciar nuestro punto de vista
  • contar historias con datos

ggplot2 es uno de los paquetes que está dentro de tidyverse y es el paquete más utilizado para hacer gráficos.

Podés consultar un cheatsheet en castellano en este link.

El paquete ggplot2 es uno de los paquetes dentro de la colección tidyverse, y que está basado en la teoría de “grammar of graphics” (la gramática de los gráficos), porque permite construir los gráficos por capas, controlando las características individuales de cada uno.

Las capas de los gráficos

Esto implica que podemos controlar desde la estructura del gráfico (títulos, ejes) hasta todo lo que pasa dentro de la visualización.

Veamos en detalle cada una de las capas:

  • Data (Datos): La fuente de datos que usaremos para graficar los datos.

  • Aesthetics (Estética): Son las modificaciones estéticas que haremos asignando variables al gráfico.

  • Geometries (Geometría): Va a ser la forma geométrica con la que vamos a representar los datos (barras, líneas, puntos, etc.).

Estas tres capas son las esenciales para hacer un gráfico en ggplot2. Las siguientes capas nos permiten controlar otros aspectos de la visualización.

  • Facets (Facetas o lados): Si tenemos un gráfico que combina varios elementos, los facets nos permiten un gráfico individual por cada elemento.

  • Statistics (Estadística): Podemos agregar una capa que incorpore por ejemplo una regresión lineal o bien una referencia como un valor promedio.

  • Coordinates (Ejes): Nos permite controlar los ejes, desde su presentación, o incluso los límites, etc..

  • Theme (Estilo): Controla aspectos visuales sobre el fondo, fuente, ejes, etc..

Preparándonos

Al igual que con cualquier paquete, necesitamos instalar y cargar el paquete para utilizarlo.

Si ya instalamos tidyverse() no hace faltar volver a instalar ggplot2.

install.packages("ggplot2")

Luego, tenemos que cargar el paquete. También cargaremos otros paquetes para cargar y manipular datos.

# Cargar los paquetes ggplot2, readxl, y dplyr
library(ggplot2)   # Visualización de datos
library(readxl)    # Cargar archivos de Excel
library(dplyr)    # Limpieza y manipulación de datos

Fuentes de datos

Los datos que utilizaremos para trabajar son los siguientes.

  • maestro.xlsx y salarios.xlsx que son un listado de empleados y también un listado de sueldos de empleados mensualizados.
  • rotacion.csv que muestra la evolución de ingresos y de egresos a lo largo de varios años.
  • kiwi_ar.RDS, un archivo de datos propio de R que contiene un data frame más limpio y manteniendo algunas características.
# Cargar los archivos de Excel
maestro <- read_xlsx("maestro.xlsx")
salarios <- read_xlsx("salarios.xlsx")

## Unir ambos data frames
mensuales <- left_join(maestro, salarios, by = "ID") %>% 
  filter(!is.na(PUESTO))  # Filtra todas las filas que no están vacías de la columna PUESTO

# Cargar el archivo rotacion.csv
rotacion <- read.csv("rotacion.csv", 
                     sep = ";")   # Indica el símbolo que separa las columnas

# Cargar el archivo kiwi_ar.RDS
kiwi <- readRDS("kiwi_ar.RDS")

Haciendo gráficos con ggplot2

La sintáxis básica de ggplot2 es la siguiente:

ggplot(nombre_dataframe, aes(x, y)) +
         geom_XXX

La función para hacer gráficos siempre será ggplot().

Luego tenemos que poner el nombre del data frame.

Adentro del aes() ponemos las columnas que queremos ver reflejadas en el gráfico. Esto en la jerga de R se llama mapear variables en el gráfico. Las variables las podemos utilizar además de verlas reflejadas en los ejes x, e y, también pueden usarse para modificar el color y el tamaño de los elementos de un gráfico.

Por último, con geom_xxx() le indicamos la forma geométrica que puede adoptar la visualización (gráficos, puntos, líneas, etc.).

Para sumar capas en un gráfico tenemos que usar el signo +. Un error común que cometo es confundirlo por el pipe %>%.

Los geoms que veremos en estas clases son:

  • geom_histogram(): Para graficar histogramas. Nos permiten ver la distribución de una variable numérica. Se puede graficar mapeando una sola variable al menos.
  • geom_freqpoly(): También nos permite ver la distribución de una variable numérica, atenuando la curva. Se puede graficar mapeando una sola variable al menos.
  • geom_boxplot(): Hace gráficos boxplot, también conocidos como de bigote y caja. Permite visualizar una gran cantidad de información estadística.
  • geom_bar(): Hace gráficos de barras. El largo de las barras lo determina la cantidad de filas de lo que estemos mapeando. Funciona incluso con un sólo eje.
  • geom_col(): También hace gráficos de barra pero el largo lo determina el valor de la/s celda/s que grafiquemos. Necesita por lo menos mapear una variable para el eje x, y otro para el eje y.
  • geom_line(): Para hacer gráficos de línea.
  • geom_point(): Para hacer gráficos de dispersión (también conocidos como scatter plots).

Estos no son todos los tipos de gráficos que se pueden hacer con ggplot2. En 2020 la comunidad hispanoparlante de R lanzó una actividad llamada 30 Días de Gráficos en honor de Florence Nightingale, un desafío en el cual a lo largo de 30 días quien quisiera podría hacer un tipo de gráfico diferente.

Pueden ver una muestra de todos los gráficos en el repositorio de GitHub de Ariadna Angulo Brunet, una psicóloga española que realizó los 30 gráficos.

Histogramas

Los histogramas son un tipo de gráfico que nos permite ver la distribución de una variable numérica, y entre otras cosas, analizar por ejemplo su simetría.

Por default, geom_histogram() divide a la variable numérica en 30 secciones con un rango de valores. El largo de cada barra estará determinado por la cantidad de observaciones que quedan dentro de cada rango de valores.

Hagamos un histograma de la variable sueldo_bruto del data frame kiwi.

# Hagamos un histograma de sueldo_bruto del data frame kiwi
ggplot(kiwi, aes(x = sueldo_bruto)) +
  geom_histogram()

Cada barra de un histograma en R se llama bin. Podemos controlar la cantidad de barras que vemos usando dentro de geom_histogram() el parámetro bins y asignando el número que más nos sirva. También podemos modificar el ancho de los bins con el parámetro bindwidth.

# Modifiquemos la cantidad de bins a 15
ggplot(kiwi, aes(x = sueldo_bruto)) +
  geom_histogram(bins = 15)

# Modifiquemos el ancho de los bins por 20000
ggplot(kiwi, aes(x = sueldo_bruto)) +
  geom_histogram(binwidth = 25000)

Una alternativa a los histogramas es geom_freqpoly() que permite ver la forma de la distribución, generando un gráfico de líneas al centro de cada bin.

También existe geom_density() que permite ver una curva atenuada de la distribución, pero como el eje y muestra la proporción de la distribución es un poco más compleja de interpretar.

# Hacer un histograma de sueldo_bruto
ggplot(kiwi, aes(x = sueldo_bruto)) +
  geom_histogram()

# Hacer un histograma de sueldo_bruto y combinarlo con geom_freqpoly()
ggplot(kiwi, aes(x = sueldo_bruto)) +
  geom_histogram() +
  geom_freqpoly()

# Hacer un gráfico de densidad
ggplot(kiwi, aes(x = sueldo_bruto)) +
  geom_density()

Boxplots

Los boxplots son gráficos que contienen una gran cantidad de información estadística.

En este gráfico podemos ver:

  • Los cuartiles 1 y 3 (Q1 y Q3) en los bordes de la caja.
  • La mediana que es la linea dentro de la caja.
  • Los outliers, representados en puntos, que son observaciones que superan una vez y media (1,5 veces) el rango intercuartil.

Además, la extensión de la caja nos permite tener una idea de la distribución de los datos.

# Hacer un boxplot básico
ggplot(kiwi, aes(x = sueldo_bruto)) +
  geom_boxplot()

# Compararlo con un histograma
ggplot(kiwi, aes(x = sueldo_bruto)) +
  geom_histogram()

En la explicación previa, decíamos que podemos “mapear” una variable para modificar el color de un gráfico. En los tipos de gráficos que contienen una superficie, como los boxplots o los gráficos de barras, el parámetro que necesitamos para modificar los colores en función de una variable se llama fill y lo tenemos que usar dentro del aes().

# Mapear la variable género con el parámetro fill
ggplot(kiwi, aes(y = sueldo_bruto, fill = genero)) +
  geom_boxplot()

Prácticas

Usando los data frames maestro y mensuales realizar los siguientes gráficos:

# Realizar un histograma de EDAD del data frame maestro


# Realizar un histograma y un freqpoly de los sueldos en mensuales


# Realizar un boxplot de mensuales, mapeando PUESTO en el eje x y SUELDO en y.

Gráficos de barra

geom_bar()

En R contamos con dos geoms para hacer gráficos de barra:

  • geom_bar(): El largo de la barra lo determina la cantidad de filas de una columna. Se pueden hacer mapeando una sola variable.

  • geom_col(): El largo de la barra lo determina el valor de la celda. Otra diferencia respecto de geom_bar() es que necesitamos mapear una variable al eje x y otra variable al eje y.

Usemos el data frame kiwi para probar ambos gráficos.

# Hacer un gráfico de barras mapeando puesto al eje x
# Usar geom_bar()
ggplot(kiwi, aes(x = puesto, fill = genero)) +
  geom_bar() 

Si mapeamos, por ejemplo la variable genero al color, podemos controlar la posición de las barras con los siguientes parámetros:

  • position = "dodge": Para poner las barras una al lado de la otra.

  • position = "identity": Pone una barra detrás de la otra.

  • position = "stack": Para hacer gráficos apilados. Es la opción por default de geom_bar().

  • position = "fill": Para hacer un gráfico apilado al 100%

Las visualizaciones también se pueden guardar en objetos. Lo cual nos permite ahorrar mucho tiempo escribiendo.

# Guardar la primera parte del gráfico en un objeto
barras <- ggplot(kiwi, aes(x = puesto, fill = genero)) 

# Reutilizamos el objeto para hacer gráficos de barras
barras + 
  geom_bar()

Ahora probemos modificar los parámetros con las opciones dodge, identity y fill.

# Usar el objeto vis1 y modificar el parámetro dodge dentro de geom_bar()
barras +
  geom_bar(position = "dodge")

# Usar position = "identity"
barras +
  geom_bar(position = "identity", alpha = 0.6)

# Usar position = "fill"
barras + 
  geom_bar(position = "fill")

geom_col()

A diferencia de geom_bar() necesitamos pasar una columna al eje x y una columna al eje y. Pero antes, vamos a necesitar hacer un par de cálculos previos.

sueldo_rubro <- kiwi %>% 
  group_by(rubro) %>%     # Agrupo por rubro
  summarise(sueldo_promedio = mean(sueldo_bruto)) %>% # Calculo sueldo promedio
  slice_max(n = 10,                     # Nos permite recortar la salida
            order_by = sueldo_promedio) # Criterio para ordenar resultados

# Veamos el contenido
sueldo_rubro
## # A tibble: 10 × 2
##    rubro                                         sueldo_promedio
##    <chr>                                                   <dbl>
##  1 Minería (carbón, otra minería)                        105792.
##  2 Industrias químicas                                   104261.
##  3 Servicios financieros; seguros                        103052.
##  4 Ingeniería mecánica                                   102000 
##  5 Medios de comunicación; cultura; gráficos             101120 
##  6 Oil & gas                                              96244.
##  7 Silvicultura; madera; celulosa; papel                  96000 
##  8 Transporte marítimo; puertos;                          91667.
##  9 Alimentación; bebidas; tabaco                          91389.
## 10 Servicios de correos, y de telecomunicaciones          84620

Como decíamos antes, ahora podemos mapear una variable (asignar) al eje x y asignar otra variable al eje y.

# Hacer un gráfico de columnas
ggplot(sueldo_rubro, aes(x = rubro, y = sueldo_promedio)) +
  geom_col()

No necesariamente tenemos que mapear las columnas de texto al eje x. Cuando las etiquetas son largas, para hacerlo más legible al eje, podemos asignar las variables de texto al eje y.

# Repetir el gráfico anterior pero invirtiendo los ejes
ggplot(sueldo_rubro, aes(y = rubro, x = sueldo_promedio)) +
  geom_col() +
  geom_text(aes(label = round(sueldo_promedio)),
            size = 3,
            hjust = 1.2,
            color = "white")

Cuando la variable de texto no tiene un orden implícito, como las jerarquías de un puesto por ejemplo, una buena práctica es ordenar las barras de mayor a menor. Esto simplifica al lector la interpretación del gráfico (o en otras palabras, hace que nuestro trabajo sea de mayor calidad).

Hay varias formas de lograr esto, una forma simple de hacerlo es con la función reorder().

# Repetir el gráfico anterior, pero ordenando las barras de mayor a menor
ggplot(sueldo_rubro, aes(x = sueldo_promedio,
                         y = reorder(rubro, sueldo_promedio))) +
  geom_col()

La sintaxis de reorder() es la siguiente:

eje = reorder(variable_eje, variable_criterio) 

En el ejemplo anterior hicimos y = reorder(rubro, sueldo_promedio). Vamos por partes:

  • y: es el eje al cual estamos asignando (mapeando) la variable.

  • reorder(): es la función:

  • rubro: es la variable que estamos asignando al eje y en este caso.

  • sueldo_promedio: El criterio por el cual vamos a ordenar las barras. Si queremos ordenar las barras al revés sólo tenemos que poner el signo menos (-) delante de la variable que estamos usando como criterio de orden (por ejemplo -sueldo_promedio).

Algo que no está bueno en el gráfico anterior es que se muestran los nombres de los ejes tal cual como vienen en el data frame original. Una función que nos permite añadir títulos y corregir los nombres de las variables es la función labs().

# Repetir el gráfico anterior, y añadir títulos, subtítulos, ejes, y nota al pie
ggplot(sueldo_rubro, aes(x = sueldo_promedio,
                         y = reorder(rubro, sueldo_promedio))) +
  geom_col() +
  labs(title = "Sueldo promedio por rubro",
       subtitle = "En AR$ - Puestos de RRHH",
       x = "Sueldo Promedio",
       y = "Rubro",
       caption = "Fuente: Encuesta KIWI 2020")

Para saber más sobre estética de los gráficos pueden ver la sesión 4 de R4HR Club de R para RRHH, y también la sesión de Microaprendizajes.

Gráficos de líneas

Los gráficos de líneas son el gráfico por excelencia para visualizar series de tiempo, es decir la evolución temporal de una variable.

Para este ejemplo vamos a utilizar el data frame de rotacion.

# Veamos el contenido del data frame 'rotacion'
rotacion
##    Periodo Movimientos Cantidades
## 1     2010    Ingresos          9
## 2     2010     Egresos          2
## 3     2011    Ingresos         84
## 4     2011     Egresos         14
## 5     2012    Ingresos         44
## 6     2012     Egresos         17
## 7     2013    Ingresos         44
## 8     2013     Egresos         15
## 9     2014    Ingresos         60
## 10    2014     Egresos         14
## 11    2015    Ingresos         36
## 12    2015     Egresos         27
## 13    2016    Ingresos         14
## 14    2016     Egresos         14

Para ver la evolución de ambas líneas, podemos mapear:

  • Periodo al eje x

  • Cantidades al eje y

  • Movimientos como color.

  • geom_line() como visualización

# Hacer un gráfico de líneas con la evolución temporal de los Ingresos y Egresos
ggplot(rotacion, aes(x = Periodo, y = Cantidades, color = Movimientos)) +
  geom_line()

Para marcar la sección donde corta cada año, podemos agregar una capa de puntos al gráfico anterior. Juguemos con el parámetro size dentro de geom_point().

# Agregar una capa de puntos
ggplot(rotacion, aes(x = Periodo, y = Cantidades, color = Movimientos)) +
  geom_line() +
  geom_point(size = 2)

Este es un buen momento para ver la utilidad de los facets.

# Añadir una capa de facet_wrap(~Movimientos)
ggplot(rotacion, aes(x = Periodo, y = Cantidades, color = Movimientos)) +
  geom_line() +
  geom_point(size = 2) +
  facet_wrap(~Movimientos)

Gráficos de dispersión - Scatter plots

Los gráficos, o diagramas de dispersión, son una gran forma de visualizar relaciones entre variables numéricas, concentraciones de datos, entre otras cosas. Es una forma de introducirnos en el análisis de regresiones lineales.

También permiten la concentración de datos entre variables numéricas y categóricas. El geom que necesitaremos es geom_point().

# Realizar un gráfico de dispersión entre anios_experiencia y sueldo_bruto
ggplot(kiwi, aes(x = anios_experiencia, y = sueldo_bruto)) +
  geom_point()

Si quisiéramos trazar una recta de regresión, una forma de hacerlo es la siguiente:

# Agregar una regresión lineal
ggplot(kiwi, aes(x = anios_experiencia, y = sueldo_bruto)) +
  geom_point() +
  geom_smooth(method = "lm")

Como decíamos, también podemos analizar la distribución de los datos entre una variable categórica y una variable numérica

# Graficar genero vs. sueldo_bruto
ggplot(kiwi, aes(x = genero, y = sueldo_bruto)) +
  geom_point()

Cuando hacemos gráficos de dispersión, dos parámetros muy útiles para analizar donde hay más concentración de datos, son los parámetros jitter y alpha.

Jitter le añade un poco de ruido a los datos para evitar la superposición de puntos, pero manteniendo la distribución de los datos. Hablando mal y pronto, lo que hace es mover un poquito los puntos dentro del gráfico para que sean más visibles.

Otro parámetro muy útil es alpha que le añade transparencia a los puntos, lo cual nos ayuda a ver donde hay más concentración de datos, en donde haya más oscuridad del color.

También se puede jugar con el parámetro size o shape para modificar el tamaño, o la forma de los puntos.

En este gráfico ponemos estos parámetros en práctica.

# Hacemos el gráfico añadiendo el parámetro jitter y alpha
ggplot(kiwi, aes(x = genero, y = sueldo_bruto)) +
  geom_point(position = "jitter",
             alpha = 0.3,
             size = 2) +
  theme_minimal()

Gráficos de torta

Todas las personas que trabajan en visualización de datos AMAN odiar los gráficos de torta. De hecho en ggplot2 no hay un geom_pie y no conozco ninguna extensión que los realice de manera sencilla. Aunque si existe la función de R base pie

# Preprocesamiento
satisfaccion <- kiwi %>% 
  select(satisfaccion) %>% 
  filter(!is.na(satisfaccion)) %>% 
  group_by(satisfaccion) %>% 
  tally() %>% 
  arrange(satisfaccion)

# Hacemos el gráfico en R base
pie(satisfaccion$n)

De todas maneras, como dice mi abuela, siempre hay un roto para un descosido y aquí les presento una forma de realizar un gráfico de dona.

En esencia, lo que vamos a hacer es un gráfico de barras apiladas al 100% al cual doblaremos, de la misma manera que las cucharas en Matrix.

Primero haremos un gráfico de barras apiladas al 100%, cuyas porciones (rectángulos) “doblaremos” posteriormente. En este ejemplo, vamos a hacer un gráfico de dona sobre el porcentaje de gente y el tipo de universidad. Para lo cual tenemos que hacer un pequeño preprocesamiento de datos:

# Crear un objeto llamado educacion
educacion <- kiwi %>% 
  select(tipo_universidad) %>%            # Selecciona la columna
  group_by(tipo_universidad) %>%          # Agrupa por tipo_universidad
  summarise(cantidad = n()) %>%           # Agrega columna con cantidad de casos
  mutate(porcentaje = cantidad / sum(cantidad)) %>% # Agrega columna de porcentaje
  arrange(-cantidad)                      # Ordena descendemtente por cantidad

# Veamos el resultado
educacion
## # A tibble: 3 × 3
##   tipo_universidad             cantidad porcentaje
##   <chr>                           <int>      <dbl>
## 1 Universidad Pública               246     0.502 
## 2 Universidad Privada               222     0.453 
## 3 No estudié en la Universidad       22     0.0449

El siguiente paso es determinar dónde empieza y dónde termina cada rectángulo que vamos a crear. Eso lo vamos a poner en unas columnas nuevas que llamaremos ymax y ymin.

# Calculamos los límites superiores de cada rectángulo
educacion$ymax <- cumsum(educacion$porcentaje)

# Calculamos el límite inferior de cada porción
educacion$ymin <- c(0, head(educacion$ymax, n=-1))

# Calculamos la posición de la etiqueta
educacion$posicion_etiqueta <- (educacion$ymax + educacion$ymin) / 2

# Creamos las etiquetas de cada porción
educacion$etiqueta <- paste0(educacion$tipo_universidad, # paste0 pega elementos
                             "\n Cant: ", 
                             educacion$cantidad)

# Ver como quedó el data frame
educacion
## # A tibble: 3 × 7
##   tipo_universidad             cantidad porcentaje  ymax  ymin posicio…¹ etiqu…²
##   <chr>                           <int>      <dbl> <dbl> <dbl>     <dbl> <chr>  
## 1 Universidad Pública               246     0.502  0.502 0         0.251 "Unive…
## 2 Universidad Privada               222     0.453  0.955 0.502     0.729 "Unive…
## 3 No estudié en la Universidad       22     0.0449 1     0.955     0.978 "No es…
## # … with abbreviated variable names ¹​posicion_etiqueta, ²​etiqueta

Los pasos anteriores crearon la posición donde comienzan y donde termina cada barra, en donde se ubica cada etiqueta, y por último, qué va a decir cada etiqueta.

Con la función paste0() lo que hacemos es concatenar varios elementos, primero, los valores de la columna tipo_universidad (Universidad Pública, Universidad Privada, etc.), luego, con la barra invertida y la n \n en otro renglón agrega la palabra Cant: y finalmente pone el valor de la columna cantidad.

Ahora, vamos a hacer el gráfico capa por capa para que vean cómo se construye:

# Realizar el gráfico de dona

# Primera capa del gráfico
ggplot(educacion, aes(ymax=ymax, 
                      ymin=ymin, 
                      xmax=4, 
                      xmin=3, 
                      fill=tipo_universidad)) +
  geom_rect() 

# There is no spoon! Nuestro primer gráfico de torta
 ggplot(educacion, aes(ymax=ymax, 
                      ymin=ymin, 
                      xmax=4, 
                      xmin=3, 
                      fill=tipo_universidad)) +
  geom_rect() +
  coord_polar(theta = "y")

# Recortamos el centro de la torta
ggplot(educacion, aes(ymax=ymax, 
                      ymin=ymin, 
                      xmax=4, 
                      xmin=3, 
                      fill=tipo_universidad)) +
  geom_rect() +
  coord_polar(theta = "y") +
  xlim(c(2,4))

# Así quedaría el gráfico de barra sin coord_polar
ggplot(educacion, aes(ymax=ymax, 
                      ymin=ymin, 
                      xmax=4, 
                      xmin=3, 
                      fill=tipo_universidad)) +
  geom_rect() +
  xlim(c(2,4))

# Agregamos algunas modificaciones estéticas
ggplot(educacion, aes(ymax=ymax, 
                      ymin=ymin, 
                      xmax=4, 
                      xmin=3, 
                      fill=tipo_universidad)) +
  geom_rect() +
  coord_polar(theta = "y") +
  xlim(c(2,4)) +
  theme_void() +             # Elimina fondos y referencias
  scale_fill_viridis_d() +   # Define una escala de colores
  theme(legend.position = "right",
        plot.title.position = "plot") + # Modifica posición leyendas y del título
  labs(title = "Respuestas según Tipo de Universidad",
       fill = "Tipo de Universidad", 
       caption = "Fuente: Encuesta KIWI de Sueldos de RH 2020") +
  geom_label(x = 3.5,
             aes(y = posicion_etiqueta,
                 label = etiqueta),
             size = 3)

Check out

ggplot2 es un paquete que tiene una infinidad de posibilidades, tiene muchas extensiones que posibilitan construir todo tipo de gráficos, añadirles interactividad, información dinámica y muchas cosas más.

Este paquete construye los gráficos por capas. Las capas fundamentales son:

  • Datos

  • Variables, que se asignan dentro del aes().

  • Un geom_"algo" que le va a dar la forma al gráfico.

Los gráficos además se pueden combinar agregando capas, lo cual posibilita agregar información, o limpiar los gráficos para añadir información de contexto.

Customizar un gráfico lleva trabajo, pero todos esos elementos se pueden guardar en objetos y reutilizarlos para ahorrar código y esfuerzo.

Esto me permite ir trabajando progresivamente con los gráficos. Primero me aseguro que el gráfico funcione, y luego voy incorporando capas para definir colores, los ejes, los títulos, y finalmente la estética general del gráfico.

Pueden explorar algunas extensiones en este link: https://exts.ggplot2.tidyverse.org/gallery/. También hay un libro online (en inglés) que pueden revisar acá: https://ggplot2-book.org/.

LS0tDQp0aXRsZTogIlZpc3VhbGl6YWNpw7NuIGRlIERhdG9zIg0KYXV0aG9yOiAiU2VyZ2lvIEdhcmNpYSBNb3JhIHwgRGF0YSA0SFIiDQpkYXRlOiAiMTEvMTAvMjAyMiINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdGhlbWU6IGx1bWVuDQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5yZXRpbmEgPSAzLCBvdXQud2lkdGggPSAiODAlIikNCmBgYA0KDQojIFZpc3VhbGl6YWNpw7NuIGRlIERhdG9zIGNvbiBgZ2dwbG90MmANCg0KTGEgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MgZXMgdW5hIGRlIGxhcyBldGFwYXMgZnVuZGFtZW50YWxlcyBlbiBlbCBhbsOhbGlzaXMgZGUgZGF0b3MgcG9ycXVlIHNlIHJlbGFjaW9uYSBjb24gKipsYSBjb211bmljYWNpw7NuKiogZGUgbG9zIHJlc3VsdGFkb3MuDQoNClVuYSBidWVuYSB2aXN1YWxpemFjacOzbiBwZXJtaXRlOg0KDQotICAgY29uZGVuc2FyIG11Y2hhIGluZm9ybWFjacOzbg0KLSAgIGV2aWRlbmNpYXIgbnVlc3RybyBwdW50byBkZSB2aXN0YQ0KLSAgIGNvbnRhciBoaXN0b3JpYXMgY29uIGRhdG9zDQoNCioqZ2dwbG90MioqIGVzIHVubyBkZSBsb3MgcGFxdWV0ZXMgcXVlIGVzdMOhIGRlbnRybyBkZSAqdGlkeXZlcnNlKiB5IGVzICoqZWwqKiBwYXF1ZXRlIG3DoXMgdXRpbGl6YWRvIHBhcmEgaGFjZXIgZ3LDoWZpY29zLg0KDQpQb2TDqXMgY29uc3VsdGFyIHVuIFtjaGVhdHNoZWV0IGVuIGNhc3RlbGxhbm8gZW4gZXN0ZSBsaW5rXShodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9yYXcvbWFzdGVyL3RyYW5zbGF0aW9ucy9zcGFuaXNoL2RhdGEtdmlzdWFsaXphdGlvbl9lcy5wZGYpLg0KDQohW10oQXJjaGl2b3MvZGlhMTItMi5wbmcpe3dpZHRoPSI0MjAifQ0KDQohW10oQXJjaGl2b3Mvc2NhdHRlci1maW4tMS5wbmcpe3dpZHRoPSIzODMifQ0KDQpFbCBwYXF1ZXRlIGBnZ3Bsb3QyYCBlcyB1bm8gZGUgbG9zIHBhcXVldGVzIGRlbnRybyBkZSBsYSBjb2xlY2Npw7NuIGB0aWR5dmVyc2VgLCB5IHF1ZSBlc3TDoSBiYXNhZG8gZW4gbGEgdGVvcsOtYSBkZSAqKiJncmFtbWFyIG9mIGdyYXBoaWNzIioqIChsYSBncmFtw6F0aWNhIGRlIGxvcyBncsOhZmljb3MpLCBwb3JxdWUgcGVybWl0ZSBjb25zdHJ1aXIgbG9zIGdyw6FmaWNvcyBwb3IgY2FwYXMsIGNvbnRyb2xhbmRvIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGluZGl2aWR1YWxlcyBkZSBjYWRhIHVuby4NCg0KIVtMYXMgY2FwYXMgZGUgbG9zIGdyw6FmaWNvc10oQXJjaGl2b3MvZ3JhbW1hcl9ncmFwaGljc19kZXRhaWxzLnBuZykNCg0KRXN0byBpbXBsaWNhIHF1ZSBwb2RlbW9zIGNvbnRyb2xhciBkZXNkZSBsYSAqKmVzdHJ1Y3R1cmEqKiBkZWwgZ3LDoWZpY28gKHTDrXR1bG9zLCBlamVzKSBoYXN0YSB0b2RvIGxvIHF1ZSBwYXNhIGRlbnRybyBkZSBsYSAqKnZpc3VhbGl6YWNpw7NuKiouDQoNClZlYW1vcyBlbiBkZXRhbGxlIGNhZGEgdW5hIGRlIGxhcyBjYXBhczoNCg0KLSAgICoqRGF0YSAoRGF0b3MpOioqIExhIGZ1ZW50ZSBkZSBkYXRvcyBxdWUgdXNhcmVtb3MgcGFyYSBncmFmaWNhciBsb3MgZGF0b3MuDQoNCi0gICAqKkFlc3RoZXRpY3MgKEVzdMOpdGljYSk6KiogU29uIGxhcyBtb2RpZmljYWNpb25lcyBlc3TDqXRpY2FzIHF1ZSBoYXJlbW9zIGFzaWduYW5kbyB2YXJpYWJsZXMgYWwgZ3LDoWZpY28uDQoNCi0gICAqKkdlb21ldHJpZXMgKEdlb21ldHLDrWEpOioqIFZhIGEgc2VyIGxhIGZvcm1hIGdlb23DqXRyaWNhIGNvbiBsYSBxdWUgdmFtb3MgYSByZXByZXNlbnRhciBsb3MgZGF0b3MgKGJhcnJhcywgbMOtbmVhcywgcHVudG9zLCBldGMuKS4NCg0KRXN0YXMgdHJlcyBjYXBhcyBzb24gbGFzIGVzZW5jaWFsZXMgcGFyYSBoYWNlciB1biBncsOhZmljbyBlbiBgZ2dwbG90MmAuIExhcyBzaWd1aWVudGVzIGNhcGFzIG5vcyBwZXJtaXRlbiBjb250cm9sYXIgb3Ryb3MgYXNwZWN0b3MgZGUgbGEgdmlzdWFsaXphY2nDs24uDQoNCi0gICAqKkZhY2V0cyAoRmFjZXRhcyBvIGxhZG9zKToqKiBTaSB0ZW5lbW9zIHVuIGdyw6FmaWNvIHF1ZSBjb21iaW5hIHZhcmlvcyBlbGVtZW50b3MsIGxvcyAqZmFjZXRzKiBub3MgcGVybWl0ZW4gdW4gZ3LDoWZpY28gaW5kaXZpZHVhbCBwb3IgY2FkYSBlbGVtZW50by4NCg0KLSAgICoqU3RhdGlzdGljcyAoRXN0YWTDrXN0aWNhKToqKiBQb2RlbW9zIGFncmVnYXIgdW5hIGNhcGEgcXVlIGluY29ycG9yZSBwb3IgZWplbXBsbyB1bmEgcmVncmVzacOzbiBsaW5lYWwgbyBiaWVuIHVuYSByZWZlcmVuY2lhIGNvbW8gdW4gdmFsb3IgcHJvbWVkaW8uDQoNCi0gICAqKkNvb3JkaW5hdGVzIChFamVzKToqKiBOb3MgcGVybWl0ZSBjb250cm9sYXIgbG9zIGVqZXMsIGRlc2RlIHN1IHByZXNlbnRhY2nDs24sIG8gaW5jbHVzbyBsb3MgbMOtbWl0ZXMsIGV0Yy4uDQoNCi0gICAqKlRoZW1lIChFc3RpbG8pKio6IENvbnRyb2xhIGFzcGVjdG9zIHZpc3VhbGVzIHNvYnJlIGVsIGZvbmRvLCBmdWVudGUsIGVqZXMsIGV0Yy4uDQoNCiMgUHJlcGFyw6FuZG9ub3MNCg0KQWwgaWd1YWwgcXVlIGNvbiBjdWFscXVpZXIgcGFxdWV0ZSwgbmVjZXNpdGFtb3MgaW5zdGFsYXIgeSBjYXJnYXIgZWwgcGFxdWV0ZSBwYXJhIHV0aWxpemFybG8uDQoNCipTaSB5YSBpbnN0YWxhbW9zIGB0aWR5dmVyc2UoKWAgbm8gaGFjZSBmYWx0YXIgdm9sdmVyIGEgaW5zdGFsYXIgYGdncGxvdDJgLioNCg0KYGBge3IgaW5zdGFsYXItcGtnLCBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQpgYGANCg0KTHVlZ28sIHRlbmVtb3MgcXVlIGNhcmdhciBlbCBwYXF1ZXRlLiBUYW1iacOpbiBjYXJnYXJlbW9zIG90cm9zIHBhcXVldGVzIHBhcmEgY2FyZ2FyIHkgbWFuaXB1bGFyIGRhdG9zLg0KDQpgYGB7ciBjYXJnYXItZ2dwbG90Mn0NCiMgQ2FyZ2FyIGxvcyBwYXF1ZXRlcyBnZ3Bsb3QyLCByZWFkeGwsIHkgZHBseXINCmxpYnJhcnkoZ2dwbG90MikgICAjIFZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zDQpsaWJyYXJ5KHJlYWR4bCkgICAgIyBDYXJnYXIgYXJjaGl2b3MgZGUgRXhjZWwNCmxpYnJhcnkoZHBseXIpICAgICMgTGltcGllemEgeSBtYW5pcHVsYWNpw7NuIGRlIGRhdG9zDQpgYGANCg0KIyMgRnVlbnRlcyBkZSBkYXRvcw0KDQpMb3MgZGF0b3MgcXVlIHV0aWxpemFyZW1vcyBwYXJhIHRyYWJhamFyIHNvbiBsb3Mgc2lndWllbnRlcy4NCg0KLSAgIGBtYWVzdHJvLnhsc3hgIHkgYHNhbGFyaW9zLnhsc3hgIHF1ZSBzb24gdW4gbGlzdGFkbyBkZSBlbXBsZWFkb3MgeSB0YW1iacOpbiB1biBsaXN0YWRvIGRlIHN1ZWxkb3MgZGUgZW1wbGVhZG9zIG1lbnN1YWxpemFkb3MuDQotICAgYHJvdGFjaW9uLmNzdmAgcXVlIG11ZXN0cmEgbGEgZXZvbHVjacOzbiBkZSBpbmdyZXNvcyB5IGRlIGVncmVzb3MgYSBsbyBsYXJnbyBkZSB2YXJpb3MgYcOxb3MuDQotICAgYGtpd2lfYXIuUkRTYCwgdW4gYXJjaGl2byBkZSBkYXRvcyBwcm9waW8gZGUgUiBxdWUgY29udGllbmUgdW4gZGF0YSBmcmFtZSBtw6FzIGxpbXBpbyB5IG1hbnRlbmllbmRvIGFsZ3VuYXMgY2FyYWN0ZXLDrXN0aWNhcy4NCg0KYGBge3IgZGF0b3N9DQojIENhcmdhciBsb3MgYXJjaGl2b3MgZGUgRXhjZWwNCm1hZXN0cm8gPC0gcmVhZF94bHN4KCJtYWVzdHJvLnhsc3giKQ0Kc2FsYXJpb3MgPC0gcmVhZF94bHN4KCJzYWxhcmlvcy54bHN4IikNCg0KIyMgVW5pciBhbWJvcyBkYXRhIGZyYW1lcw0KbWVuc3VhbGVzIDwtIGxlZnRfam9pbihtYWVzdHJvLCBzYWxhcmlvcywgYnkgPSAiSUQiKSAlPiUgDQogIGZpbHRlcighaXMubmEoUFVFU1RPKSkgICMgRmlsdHJhIHRvZGFzIGxhcyBmaWxhcyBxdWUgbm8gZXN0w6FuIHZhY8OtYXMgZGUgbGEgY29sdW1uYSBQVUVTVE8NCg0KIyBDYXJnYXIgZWwgYXJjaGl2byByb3RhY2lvbi5jc3YNCnJvdGFjaW9uIDwtIHJlYWQuY3N2KCJyb3RhY2lvbi5jc3YiLCANCiAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICI7IikgICAjIEluZGljYSBlbCBzw61tYm9sbyBxdWUgc2VwYXJhIGxhcyBjb2x1bW5hcw0KDQojIENhcmdhciBlbCBhcmNoaXZvIGtpd2lfYXIuUkRTDQpraXdpIDwtIHJlYWRSRFMoImtpd2lfYXIuUkRTIikNCg0KDQpgYGANCg0KIyBIYWNpZW5kbyBncsOhZmljb3MgY29uIGdncGxvdDINCg0KTGEgc2ludMOheGlzIGLDoXNpY2EgZGUgYGdncGxvdDJgIGVzIGxhIHNpZ3VpZW50ZToNCg0KYGBge3IgZ2dwMSwgZXZhbCA9IEZBTFNFfQ0KZ2dwbG90KG5vbWJyZV9kYXRhZnJhbWUsIGFlcyh4LCB5KSkgKw0KICAgICAgICAgZ2VvbV9YWFgNCmBgYA0KDQpMYSBmdW5jacOzbiBwYXJhIGhhY2VyIGdyw6FmaWNvcyBzaWVtcHJlIHNlcsOhIGBnZ3Bsb3QoKWAuDQoNCkx1ZWdvIHRlbmVtb3MgcXVlIHBvbmVyIGVsIG5vbWJyZSBkZWwgZGF0YSBmcmFtZS4NCg0KQWRlbnRybyBkZWwgYGFlcygpYCBwb25lbW9zIGxhcyBjb2x1bW5hcyBxdWUgcXVlcmVtb3MgdmVyIHJlZmxlamFkYXMgZW4gZWwgZ3LDoWZpY28uIEVzdG8gZW4gbGEgamVyZ2EgZGUgUiBzZSBsbGFtYSAqbWFwZWFyIHZhcmlhYmxlcyBlbiBlbCBncsOhZmljbyouIExhcyB2YXJpYWJsZXMgbGFzIHBvZGVtb3MgdXRpbGl6YXIgYWRlbcOhcyBkZSB2ZXJsYXMgcmVmbGVqYWRhcyBlbiBsb3MgZWplcyBgeGAsIGUgYHlgLCB0YW1iacOpbiBwdWVkZW4gdXNhcnNlIHBhcmEgbW9kaWZpY2FyIGVsIGNvbG9yIHkgZWwgdGFtYcOxbyBkZSBsb3MgZWxlbWVudG9zIGRlIHVuIGdyw6FmaWNvLg0KDQpQb3Igw7psdGltbywgY29uIGBnZW9tX3h4eCgpYCBsZSBpbmRpY2Ftb3MgbGEgZm9ybWEgZ2VvbcOpdHJpY2EgcXVlIHB1ZWRlIGFkb3B0YXIgbGEgdmlzdWFsaXphY2nDs24gKGdyw6FmaWNvcywgcHVudG9zLCBsw61uZWFzLCBldGMuKS4NCg0KUGFyYSBzdW1hciBjYXBhcyBlbiB1biBncsOhZmljbyB0ZW5lbW9zIHF1ZSB1c2FyIGVsIHNpZ25vIGArYC4gVW4gZXJyb3IgY29tw7puIHF1ZSBjb21ldG8gZXMgY29uZnVuZGlybG8gcG9yIGVsICpwaXBlKiBgJT4lYC4NCg0KTG9zICpnZW9tcyogcXVlIHZlcmVtb3MgZW4gZXN0YXMgY2xhc2VzIHNvbjoNCg0KLSAgIGBnZW9tX2hpc3RvZ3JhbSgpYDogUGFyYSBncmFmaWNhciBoaXN0b2dyYW1hcy4gTm9zIHBlcm1pdGVuIHZlciBsYSBkaXN0cmlidWNpw7NuIGRlIHVuYSB2YXJpYWJsZSBudW3DqXJpY2EuIFNlIHB1ZWRlIGdyYWZpY2FyIG1hcGVhbmRvIHVuYSBzb2xhIHZhcmlhYmxlIGFsIG1lbm9zLg0KLSAgIGBnZW9tX2ZyZXFwb2x5KClgOiBUYW1iacOpbiBub3MgcGVybWl0ZSB2ZXIgbGEgZGlzdHJpYnVjacOzbiBkZSB1bmEgdmFyaWFibGUgbnVtw6lyaWNhLCBhdGVudWFuZG8gbGEgY3VydmEuIFNlIHB1ZWRlIGdyYWZpY2FyIG1hcGVhbmRvIHVuYSBzb2xhIHZhcmlhYmxlIGFsIG1lbm9zLg0KLSAgIGBnZW9tX2JveHBsb3QoKWA6IEhhY2UgZ3LDoWZpY29zIGJveHBsb3QsIHRhbWJpw6luIGNvbm9jaWRvcyBjb21vIGRlIGJpZ290ZSB5IGNhamEuIFBlcm1pdGUgdmlzdWFsaXphciB1bmEgZ3JhbiBjYW50aWRhZCBkZSBpbmZvcm1hY2nDs24gZXN0YWTDrXN0aWNhLg0KLSAgIGBnZW9tX2JhcigpYDogSGFjZSBncsOhZmljb3MgZGUgYmFycmFzLiBFbCBsYXJnbyBkZSBsYXMgYmFycmFzIGxvIGRldGVybWluYSBsYSBjYW50aWRhZCBkZSBmaWxhcyBkZSBsbyBxdWUgZXN0ZW1vcyBtYXBlYW5kby4gRnVuY2lvbmEgaW5jbHVzbyBjb24gdW4gc8OzbG8gZWplLg0KLSAgIGBnZW9tX2NvbCgpYDogVGFtYmnDqW4gaGFjZSBncsOhZmljb3MgZGUgYmFycmEgcGVybyBlbCBsYXJnbyBsbyBkZXRlcm1pbmEgZWwgdmFsb3IgZGUgbGEvcyBjZWxkYS9zIHF1ZSBncmFmaXF1ZW1vcy4gTmVjZXNpdGEgcG9yIGxvIG1lbm9zIG1hcGVhciB1bmEgdmFyaWFibGUgcGFyYSBlbCBlamUgeCwgeSBvdHJvIHBhcmEgZWwgZWplIHkuDQotICAgYGdlb21fbGluZSgpYDogUGFyYSBoYWNlciBncsOhZmljb3MgZGUgbMOtbmVhLg0KLSAgIGBnZW9tX3BvaW50KClgOiBQYXJhIGhhY2VyIGdyw6FmaWNvcyBkZSBkaXNwZXJzacOzbiAodGFtYmnDqW4gY29ub2NpZG9zIGNvbW8gc2NhdHRlciBwbG90cykuDQoNCkVzdG9zIG5vIHNvbiB0b2RvcyBsb3MgdGlwb3MgZGUgZ3LDoWZpY29zIHF1ZSBzZSBwdWVkZW4gaGFjZXIgY29uIGBnZ3Bsb3QyYC4gRW4gMjAyMCBsYSBjb211bmlkYWQgaGlzcGFub3BhcmxhbnRlIGRlIFIgbGFuesOzIHVuYSBhY3RpdmlkYWQgbGxhbWFkYSAqKjMwIETDrWFzIGRlIEdyw6FmaWNvcyoqIGVuIGhvbm9yIGRlIFtGbG9yZW5jZSBOaWdodGluZ2FsZV0oaHR0cHM6Ly9tdWplcmVzY29uY2llbmNpYS5jb20vMjAxNC8wNS8xMi9mbG9yZW5jZS1uaWd0aGluZ2FsZS1waW9uZXJhLWVzdGFkaXN0aWNhLyksIHVuIGRlc2Fmw61vIGVuIGVsIGN1YWwgYSBsbyBsYXJnbyBkZSAzMCBkw61hcyBxdWllbiBxdWlzaWVyYSBwb2Ryw61hIGhhY2VyIHVuIHRpcG8gZGUgZ3LDoWZpY28gZGlmZXJlbnRlLg0KDQpQdWVkZW4gdmVyIHVuYSBtdWVzdHJhIGRlIHRvZG9zIGxvcyBncsOhZmljb3MgZW4gZWwgcmVwb3NpdG9yaW8gZGUgR2l0SHViIGRlIFtBcmlhZG5hIEFuZ3VsbyBCcnVuZXRdKGh0dHBzOi8vZ2l0aHViLmNvbS9Bbmd1bG9CL2RhdG9zZGVtaWVyY29sZXMpLCB1bmEgcHNpY8OzbG9nYSBlc3Bhw7FvbGEgcXVlIHJlYWxpesOzIGxvcyAzMCBncsOhZmljb3MuDQoNCiMjIEhpc3RvZ3JhbWFzDQoNCkxvcyBoaXN0b2dyYW1hcyBzb24gdW4gdGlwbyBkZSBncsOhZmljbyBxdWUgbm9zIHBlcm1pdGUgdmVyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgdW5hIHZhcmlhYmxlIG51bcOpcmljYSwgeSBlbnRyZSBvdHJhcyBjb3NhcywgYW5hbGl6YXIgcG9yIGVqZW1wbG8gc3Ugc2ltZXRyw61hLg0KDQpQb3IgZGVmYXVsdCwgYGdlb21faGlzdG9ncmFtKClgIGRpdmlkZSBhIGxhIHZhcmlhYmxlIG51bcOpcmljYSBlbiAzMCBzZWNjaW9uZXMgY29uIHVuIHJhbmdvIGRlIHZhbG9yZXMuIEVsIGxhcmdvIGRlIGNhZGEgYmFycmEgZXN0YXLDoSBkZXRlcm1pbmFkbyBwb3IgbGEgY2FudGlkYWQgZGUgb2JzZXJ2YWNpb25lcyBxdWUgcXVlZGFuIGRlbnRybyBkZSBjYWRhIHJhbmdvIGRlIHZhbG9yZXMuDQoNCkhhZ2Ftb3MgdW4gaGlzdG9ncmFtYSBkZSBsYSB2YXJpYWJsZSBgc3VlbGRvX2JydXRvYCBkZWwgZGF0YSBmcmFtZSBga2l3aWAuDQoNCmBgYHtyIGhpc3QxfQ0KIyBIYWdhbW9zIHVuIGhpc3RvZ3JhbWEgZGUgc3VlbGRvX2JydXRvIGRlbCBkYXRhIGZyYW1lIGtpd2kNCmdncGxvdChraXdpLCBhZXMoeCA9IHN1ZWxkb19icnV0bykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKQ0KDQpgYGANCg0KQ2FkYSAqYmFycmEqIGRlIHVuIGhpc3RvZ3JhbWEgZW4gUiBzZSBsbGFtYSBgYmluYC4gUG9kZW1vcyBjb250cm9sYXIgbGEgY2FudGlkYWQgZGUgYmFycmFzIHF1ZSB2ZW1vcyB1c2FuZG8gZGVudHJvIGRlIGBnZW9tX2hpc3RvZ3JhbSgpYCBlbCBwYXLDoW1ldHJvIGBiaW5zYCB5IGFzaWduYW5kbyBlbCBuw7ptZXJvIHF1ZSBtw6FzIG5vcyBzaXJ2YS4gVGFtYmnDqW4gcG9kZW1vcyBtb2RpZmljYXIgZWwgYW5jaG8gZGUgbG9zICpiaW5zKiBjb24gZWwgcGFyw6FtZXRybyBgYmluZHdpZHRoYC4NCg0KYGBge3IgaGlzdDJ9DQojIE1vZGlmaXF1ZW1vcyBsYSBjYW50aWRhZCBkZSBiaW5zIGEgMTUNCmdncGxvdChraXdpLCBhZXMoeCA9IHN1ZWxkb19icnV0bykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDE1KQ0KDQojIE1vZGlmaXF1ZW1vcyBlbCBhbmNobyBkZSBsb3MgYmlucyBwb3IgMjAwMDANCmdncGxvdChraXdpLCBhZXMoeCA9IHN1ZWxkb19icnV0bykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAyNTAwMCkNCmBgYA0KDQpVbmEgYWx0ZXJuYXRpdmEgYSBsb3MgaGlzdG9ncmFtYXMgZXMgYGdlb21fZnJlcXBvbHkoKWAgcXVlIHBlcm1pdGUgdmVyIGxhIGZvcm1hIGRlIGxhIGRpc3RyaWJ1Y2nDs24sIGdlbmVyYW5kbyB1biBncsOhZmljbyBkZSBsw61uZWFzIGFsIGNlbnRybyBkZSBjYWRhIGJpbi4NCg0KVGFtYmnDqW4gZXhpc3RlIGBnZW9tX2RlbnNpdHkoKWAgcXVlIHBlcm1pdGUgdmVyIHVuYSBjdXJ2YSBhdGVudWFkYSBkZSBsYSBkaXN0cmlidWNpw7NuLCBwZXJvIGNvbW8gZWwgZWplIGB5YCBtdWVzdHJhIGxhIHByb3BvcmNpw7NuIGRlIGxhIGRpc3RyaWJ1Y2nDs24gZXMgdW4gcG9jbyBtw6FzIGNvbXBsZWphIGRlIGludGVycHJldGFyLg0KDQpgYGB7ciBoaXN0M30NCiMgSGFjZXIgdW4gaGlzdG9ncmFtYSBkZSBzdWVsZG9fYnJ1dG8NCmdncGxvdChraXdpLCBhZXMoeCA9IHN1ZWxkb19icnV0bykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKQ0KDQojIEhhY2VyIHVuIGhpc3RvZ3JhbWEgZGUgc3VlbGRvX2JydXRvIHkgY29tYmluYXJsbyBjb24gZ2VvbV9mcmVxcG9seSgpDQpnZ3Bsb3Qoa2l3aSwgYWVzKHggPSBzdWVsZG9fYnJ1dG8pKSArDQogIGdlb21faGlzdG9ncmFtKCkgKw0KICBnZW9tX2ZyZXFwb2x5KCkNCg0KIyBIYWNlciB1biBncsOhZmljbyBkZSBkZW5zaWRhZA0KZ2dwbG90KGtpd2ksIGFlcyh4ID0gc3VlbGRvX2JydXRvKSkgKw0KICBnZW9tX2RlbnNpdHkoKQ0KYGBgDQoNCiMjIEJveHBsb3RzDQoNCkxvcyBib3hwbG90cyBzb24gZ3LDoWZpY29zIHF1ZSBjb250aWVuZW4gdW5hIGdyYW4gY2FudGlkYWQgZGUgaW5mb3JtYWNpw7NuIGVzdGFkw61zdGljYS4NCg0KIVtdKEFyY2hpdm9zL2JveHBsb3QucG5nKXt3aWR0aD0iNDk0In0NCg0KRW4gZXN0ZSBncsOhZmljbyBwb2RlbW9zIHZlcjoNCg0KLSAgIExvcyBjdWFydGlsZXMgMSB5IDMgKFExIHkgUTMpIGVuIGxvcyBib3JkZXMgZGUgbGEgY2FqYS4NCi0gICBMYSBtZWRpYW5hIHF1ZSBlcyBsYSBsaW5lYSBkZW50cm8gZGUgbGEgY2FqYS4NCi0gICBMb3Mgb3V0bGllcnMsIHJlcHJlc2VudGFkb3MgZW4gcHVudG9zLCBxdWUgc29uIG9ic2VydmFjaW9uZXMgcXVlIHN1cGVyYW4gdW5hIHZleiB5IG1lZGlhICgxLDUgdmVjZXMpIGVsIHJhbmdvIGludGVyY3VhcnRpbC4NCg0KQWRlbcOhcywgbGEgZXh0ZW5zacOzbiBkZSBsYSBjYWphIG5vcyBwZXJtaXRlIHRlbmVyIHVuYSBpZGVhIGRlIGxhICoqZGlzdHJpYnVjacOzbioqIGRlIGxvcyBkYXRvcy4NCg0KYGBge3IgYm94cDF9DQojIEhhY2VyIHVuIGJveHBsb3QgYsOhc2ljbw0KZ2dwbG90KGtpd2ksIGFlcyh4ID0gc3VlbGRvX2JydXRvKSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KDQojIENvbXBhcmFybG8gY29uIHVuIGhpc3RvZ3JhbWENCmdncGxvdChraXdpLCBhZXMoeCA9IHN1ZWxkb19icnV0bykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKQ0KDQpgYGANCg0KRW4gbGEgZXhwbGljYWNpw7NuIHByZXZpYSwgZGVjw61hbW9zIHF1ZSBwb2RlbW9zICoibWFwZWFyIiogdW5hIHZhcmlhYmxlIHBhcmEgbW9kaWZpY2FyIGVsIGNvbG9yIGRlIHVuIGdyw6FmaWNvLiBFbiBsb3MgdGlwb3MgZGUgZ3LDoWZpY29zIHF1ZSBjb250aWVuZW4gdW5hIHN1cGVyZmljaWUsIGNvbW8gbG9zIGJveHBsb3RzIG8gbG9zIGdyw6FmaWNvcyBkZSBiYXJyYXMsIGVsIHBhcsOhbWV0cm8gcXVlIG5lY2VzaXRhbW9zIHBhcmEgbW9kaWZpY2FyIGxvcyBjb2xvcmVzIGVuIGZ1bmNpw7NuIGRlIHVuYSB2YXJpYWJsZSBzZSBsbGFtYSBgZmlsbGAgeSBsbyB0ZW5lbW9zIHF1ZSB1c2FyIGRlbnRybyBkZWwgYGFlcygpYC4NCg0KYGBge3IgYm94cDJ9DQojIE1hcGVhciBsYSB2YXJpYWJsZSBnw6luZXJvIGNvbiBlbCBwYXLDoW1ldHJvIGZpbGwNCmdncGxvdChraXdpLCBhZXMoeSA9IHN1ZWxkb19icnV0bywgZmlsbCA9IGdlbmVybykpICsNCiAgZ2VvbV9ib3hwbG90KCkNCg0KYGBgDQoNCiMjIyBQcsOhY3RpY2FzDQoNClVzYW5kbyBsb3MgZGF0YSBmcmFtZXMgYG1hZXN0cm9gIHkgYG1lbnN1YWxlc2AgcmVhbGl6YXIgbG9zIHNpZ3VpZW50ZXMgZ3LDoWZpY29zOg0KDQpgYGB7ciBwcmFjdGljYTF9DQojIFJlYWxpemFyIHVuIGhpc3RvZ3JhbWEgZGUgRURBRCBkZWwgZGF0YSBmcmFtZSBtYWVzdHJvDQoNCg0KIyBSZWFsaXphciB1biBoaXN0b2dyYW1hIHkgdW4gZnJlcXBvbHkgZGUgbG9zIHN1ZWxkb3MgZW4gbWVuc3VhbGVzDQoNCg0KIyBSZWFsaXphciB1biBib3hwbG90IGRlIG1lbnN1YWxlcywgbWFwZWFuZG8gUFVFU1RPIGVuIGVsIGVqZSB4IHkgU1VFTERPIGVuIHkuDQoNCmBgYA0KDQohW10oaHR0cHM6Ly9tZWRpYS5naXBoeS5jb20vbWVkaWEvTFRZVDVHVElpQU1CYS9naXBoeS5naWYpe3dpZHRoPSIzMTEifQ0KDQojIyBHcsOhZmljb3MgZGUgYmFycmENCg0KIyMjIGdlb21fYmFyKCkNCg0KRW4gUiBjb250YW1vcyBjb24gZG9zICpnZW9tcyogcGFyYSBoYWNlciBncsOhZmljb3MgZGUgYmFycmE6DQoNCi0gICBgZ2VvbV9iYXIoKWA6IEVsIGxhcmdvIGRlIGxhIGJhcnJhIGxvIGRldGVybWluYSBsYSAqKmNhbnRpZGFkIGRlIGZpbGFzKiogZGUgdW5hIGNvbHVtbmEuIFNlIHB1ZWRlbiBoYWNlciBtYXBlYW5kbyB1bmEgc29sYSB2YXJpYWJsZS4NCg0KLSAgIGBnZW9tX2NvbCgpYDogRWwgbGFyZ28gZGUgbGEgYmFycmEgbG8gZGV0ZXJtaW5hIGVsICoqdmFsb3IgZGUgbGEgY2VsZGEqKi4gT3RyYSBkaWZlcmVuY2lhIHJlc3BlY3RvIGRlIGBnZW9tX2JhcigpYCBlcyBxdWUgbmVjZXNpdGFtb3MgbWFwZWFyIHVuYSB2YXJpYWJsZSBhbCBlamUgYHhgIHkgb3RyYSB2YXJpYWJsZSBhbCBlamUgYHlgLg0KDQpVc2Vtb3MgZWwgZGF0YSBmcmFtZSBga2l3aWAgcGFyYSBwcm9iYXIgYW1ib3MgZ3LDoWZpY29zLg0KDQpgYGB7ciBiYXIxfQ0KIyBIYWNlciB1biBncsOhZmljbyBkZSBiYXJyYXMgbWFwZWFuZG8gcHVlc3RvIGFsIGVqZSB4DQojIFVzYXIgZ2VvbV9iYXIoKQ0KZ2dwbG90KGtpd2ksIGFlcyh4ID0gcHVlc3RvLCBmaWxsID0gZ2VuZXJvKSkgKw0KICBnZW9tX2JhcigpIA0KYGBgDQoNClNpIG1hcGVhbW9zLCBwb3IgZWplbXBsbyBsYSB2YXJpYWJsZSBgZ2VuZXJvYCBhbCBjb2xvciwgcG9kZW1vcyBjb250cm9sYXIgbGEgcG9zaWNpw7NuIGRlIGxhcyBiYXJyYXMgY29uIGxvcyBzaWd1aWVudGVzIHBhcsOhbWV0cm9zOg0KDQotICAgYHBvc2l0aW9uID0gImRvZGdlImA6IFBhcmEgcG9uZXIgbGFzIGJhcnJhcyB1bmEgYWwgbGFkbyBkZSBsYSBvdHJhLg0KDQotICAgYHBvc2l0aW9uID0gImlkZW50aXR5ImA6IFBvbmUgdW5hIGJhcnJhIGRldHLDoXMgZGUgbGEgb3RyYS4NCg0KLSAgIGBwb3NpdGlvbiA9ICJzdGFjayJgOiBQYXJhIGhhY2VyIGdyw6FmaWNvcyBhcGlsYWRvcy4gRXMgbGEgb3BjacOzbiBwb3IgZGVmYXVsdCBkZSBgZ2VvbV9iYXIoKWAuDQoNCi0gICBgcG9zaXRpb24gPSAiZmlsbCJgOiBQYXJhIGhhY2VyIHVuIGdyw6FmaWNvIGFwaWxhZG8gYWwgMTAwJQ0KDQpMYXMgdmlzdWFsaXphY2lvbmVzIHRhbWJpw6luIHNlIHB1ZWRlbiBndWFyZGFyIGVuIG9iamV0b3MuIExvIGN1YWwgbm9zIHBlcm1pdGUgYWhvcnJhciBtdWNobyB0aWVtcG8gZXNjcmliaWVuZG8uDQoNCmBgYHtyIGJhcjJ9DQojIEd1YXJkYXIgbGEgcHJpbWVyYSBwYXJ0ZSBkZWwgZ3LDoWZpY28gZW4gdW4gb2JqZXRvDQpiYXJyYXMgPC0gZ2dwbG90KGtpd2ksIGFlcyh4ID0gcHVlc3RvLCBmaWxsID0gZ2VuZXJvKSkgDQoNCiMgUmV1dGlsaXphbW9zIGVsIG9iamV0byBwYXJhIGhhY2VyIGdyw6FmaWNvcyBkZSBiYXJyYXMNCmJhcnJhcyArIA0KICBnZW9tX2JhcigpDQpgYGANCg0KQWhvcmEgcHJvYmVtb3MgbW9kaWZpY2FyIGxvcyBwYXLDoW1ldHJvcyBjb24gbGFzIG9wY2lvbmVzIGBkb2RnZWAsIGBpZGVudGl0eWAgeSBgZmlsbGAuDQoNCmBgYHtyIGJhcjN9DQojIFVzYXIgZWwgb2JqZXRvIHZpczEgeSBtb2RpZmljYXIgZWwgcGFyw6FtZXRybyBkb2RnZSBkZW50cm8gZGUgZ2VvbV9iYXIoKQ0KYmFycmFzICsNCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKQ0KDQojIFVzYXIgcG9zaXRpb24gPSAiaWRlbnRpdHkiDQpiYXJyYXMgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJpZGVudGl0eSIsIGFscGhhID0gMC42KQ0KDQojIFVzYXIgcG9zaXRpb24gPSAiZmlsbCINCmJhcnJhcyArIA0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikNCg0KYGBgDQoNCiMjIyBnZW9tX2NvbCgpDQoNCkEgZGlmZXJlbmNpYSBkZSBgZ2VvbV9iYXIoKWAgbmVjZXNpdGFtb3MgcGFzYXIgdW5hIGNvbHVtbmEgYWwgZWplIGB4YCB5IHVuYSBjb2x1bW5hIGFsIGVqZSBgeWAuIFBlcm8gYW50ZXMsIHZhbW9zIGEgbmVjZXNpdGFyIGhhY2VyIHVuIHBhciBkZSBjw6FsY3Vsb3MgcHJldmlvcy4NCg0KYGBge3IgcHJvbWVkaW9zfQ0Kc3VlbGRvX3J1YnJvIDwtIGtpd2kgJT4lIA0KICBncm91cF9ieShydWJybykgJT4lICAgICAjIEFncnVwbyBwb3IgcnVicm8NCiAgc3VtbWFyaXNlKHN1ZWxkb19wcm9tZWRpbyA9IG1lYW4oc3VlbGRvX2JydXRvKSkgJT4lICMgQ2FsY3VsbyBzdWVsZG8gcHJvbWVkaW8NCiAgc2xpY2VfbWF4KG4gPSAxMCwgICAgICAgICAgICAgICAgICAgICAjIE5vcyBwZXJtaXRlIHJlY29ydGFyIGxhIHNhbGlkYQ0KICAgICAgICAgICAgb3JkZXJfYnkgPSBzdWVsZG9fcHJvbWVkaW8pICMgQ3JpdGVyaW8gcGFyYSBvcmRlbmFyIHJlc3VsdGFkb3MNCg0KIyBWZWFtb3MgZWwgY29udGVuaWRvDQpzdWVsZG9fcnVicm8NCmBgYA0KDQpDb21vIGRlY8OtYW1vcyBhbnRlcywgYWhvcmEgcG9kZW1vcyBtYXBlYXIgdW5hIHZhcmlhYmxlIChhc2lnbmFyKSBhbCBlamUgYHhgIHkgYXNpZ25hciBvdHJhIHZhcmlhYmxlIGFsIGVqZSBgeWAuDQoNCmBgYHtyIGNvbDF9DQojIEhhY2VyIHVuIGdyw6FmaWNvIGRlIGNvbHVtbmFzDQpnZ3Bsb3Qoc3VlbGRvX3J1YnJvLCBhZXMoeCA9IHJ1YnJvLCB5ID0gc3VlbGRvX3Byb21lZGlvKSkgKw0KICBnZW9tX2NvbCgpDQpgYGANCg0KTm8gbmVjZXNhcmlhbWVudGUgdGVuZW1vcyBxdWUgbWFwZWFyIGxhcyBjb2x1bW5hcyBkZSB0ZXh0byBhbCBlamUgYHhgLiBDdWFuZG8gbGFzIGV0aXF1ZXRhcyBzb24gbGFyZ2FzLCBwYXJhIGhhY2VybG8gbcOhcyBsZWdpYmxlIGFsIGVqZSwgcG9kZW1vcyBhc2lnbmFyIGxhcyB2YXJpYWJsZXMgZGUgdGV4dG8gYWwgZWplIGB5YC4NCg0KYGBge3IgY29sMn0NCiMgUmVwZXRpciBlbCBncsOhZmljbyBhbnRlcmlvciBwZXJvIGludmlydGllbmRvIGxvcyBlamVzDQpnZ3Bsb3Qoc3VlbGRvX3J1YnJvLCBhZXMoeSA9IHJ1YnJvLCB4ID0gc3VlbGRvX3Byb21lZGlvKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKHN1ZWxkb19wcm9tZWRpbykpLA0KICAgICAgICAgICAgc2l6ZSA9IDMsDQogICAgICAgICAgICBoanVzdCA9IDEuMiwNCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIikNCmBgYA0KDQpDdWFuZG8gbGEgdmFyaWFibGUgZGUgdGV4dG8gbm8gdGllbmUgdW4gb3JkZW4gaW1wbMOtY2l0bywgY29tbyBsYXMgamVyYXJxdcOtYXMgZGUgdW4gcHVlc3RvIHBvciBlamVtcGxvLCB1bmEgYnVlbmEgcHLDoWN0aWNhIGVzIG9yZGVuYXIgbGFzIGJhcnJhcyBkZSBtYXlvciBhIG1lbm9yLiBFc3RvIHNpbXBsaWZpY2EgYWwgbGVjdG9yIGxhIGludGVycHJldGFjacOzbiBkZWwgZ3LDoWZpY28gKG8gZW4gb3RyYXMgcGFsYWJyYXMsIGhhY2UgcXVlIG51ZXN0cm8gdHJhYmFqbyBzZWEgZGUgbWF5b3IgY2FsaWRhZCkuDQoNCkhheSB2YXJpYXMgZm9ybWFzIGRlIGxvZ3JhciBlc3RvLCB1bmEgZm9ybWEgc2ltcGxlIGRlIGhhY2VybG8gZXMgY29uIGxhIGZ1bmNpw7NuIGByZW9yZGVyKClgLg0KDQpgYGB7ciBjb2wzfQ0KIyBSZXBldGlyIGVsIGdyw6FmaWNvIGFudGVyaW9yLCBwZXJvIG9yZGVuYW5kbyBsYXMgYmFycmFzIGRlIG1heW9yIGEgbWVub3INCmdncGxvdChzdWVsZG9fcnVicm8sIGFlcyh4ID0gc3VlbGRvX3Byb21lZGlvLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSByZW9yZGVyKHJ1YnJvLCBzdWVsZG9fcHJvbWVkaW8pKSkgKw0KICBnZW9tX2NvbCgpDQogDQpgYGANCg0KTGEgc2ludGF4aXMgZGUgYHJlb3JkZXIoKWAgZXMgbGEgc2lndWllbnRlOg0KDQpgYGB7ciByZW9yZGVyLCBldmFsPUZBTFNFfQ0KZWplID0gcmVvcmRlcih2YXJpYWJsZV9lamUsIHZhcmlhYmxlX2NyaXRlcmlvKSANCmBgYA0KDQpFbiBlbCBlamVtcGxvIGFudGVyaW9yIGhpY2ltb3MgYHkgPSByZW9yZGVyKHJ1YnJvLCBzdWVsZG9fcHJvbWVkaW8pYC4gVmFtb3MgcG9yIHBhcnRlczoNCg0KLSAgIGB5YDogZXMgZWwgZWplIGFsIGN1YWwgZXN0YW1vcyBhc2lnbmFuZG8gKG1hcGVhbmRvKSBsYSB2YXJpYWJsZS4NCg0KLSAgIGByZW9yZGVyKClgOiBlcyBsYSBmdW5jacOzbjoNCg0KLSAgIGBydWJyb2A6IGVzIGxhIHZhcmlhYmxlIHF1ZSBlc3RhbW9zIGFzaWduYW5kbyBhbCBlamUgYHlgIGVuIGVzdGUgY2Fzby4NCg0KLSAgIGBzdWVsZG9fcHJvbWVkaW9gOiBFbCBjcml0ZXJpbyBwb3IgZWwgY3VhbCB2YW1vcyBhIG9yZGVuYXIgbGFzIGJhcnJhcy4gU2kgcXVlcmVtb3Mgb3JkZW5hciBsYXMgYmFycmFzIGFsIHJldsOpcyBzw7NsbyB0ZW5lbW9zIHF1ZSBwb25lciBlbCBzaWdubyBtZW5vcyAoYC1gKSBkZWxhbnRlIGRlIGxhIHZhcmlhYmxlIHF1ZSBlc3RhbW9zIHVzYW5kbyBjb21vIGNyaXRlcmlvIGRlIG9yZGVuIChwb3IgZWplbXBsbyBgLXN1ZWxkb19wcm9tZWRpb2ApLg0KDQpBbGdvIHF1ZSBubyBlc3TDoSBidWVubyBlbiBlbCBncsOhZmljbyBhbnRlcmlvciBlcyBxdWUgc2UgbXVlc3RyYW4gbG9zIG5vbWJyZXMgZGUgbG9zIGVqZXMgdGFsIGN1YWwgY29tbyB2aWVuZW4gZW4gZWwgZGF0YSBmcmFtZSBvcmlnaW5hbC4gVW5hIGZ1bmNpw7NuIHF1ZSBub3MgcGVybWl0ZSBhw7FhZGlyIHTDrXR1bG9zIHkgY29ycmVnaXIgbG9zIG5vbWJyZXMgZGUgbGFzIHZhcmlhYmxlcyBlcyBsYSBmdW5jacOzbiBgbGFicygpYC4NCg0KYGBge3IgY29sNH0NCiMgUmVwZXRpciBlbCBncsOhZmljbyBhbnRlcmlvciwgeSBhw7FhZGlyIHTDrXR1bG9zLCBzdWJ0w610dWxvcywgZWplcywgeSBub3RhIGFsIHBpZQ0KZ2dwbG90KHN1ZWxkb19ydWJybywgYWVzKHggPSBzdWVsZG9fcHJvbWVkaW8sDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHJlb3JkZXIocnVicm8sIHN1ZWxkb19wcm9tZWRpbykpKSArDQogIGdlb21fY29sKCkgKw0KICBsYWJzKHRpdGxlID0gIlN1ZWxkbyBwcm9tZWRpbyBwb3IgcnVicm8iLA0KICAgICAgIHN1YnRpdGxlID0gIkVuIEFSJCAtIFB1ZXN0b3MgZGUgUlJISCIsDQogICAgICAgeCA9ICJTdWVsZG8gUHJvbWVkaW8iLA0KICAgICAgIHkgPSAiUnVicm8iLA0KICAgICAgIGNhcHRpb24gPSAiRnVlbnRlOiBFbmN1ZXN0YSBLSVdJIDIwMjAiKQ0KDQpgYGANCg0KUGFyYSBzYWJlciBtw6FzIHNvYnJlIGVzdMOpdGljYSBkZSBsb3MgZ3LDoWZpY29zIHB1ZWRlbiB2ZXIgbGEgW3Nlc2nDs24gNCBkZSBSNEhSIENsdWIgZGUgUiBwYXJhIFJSSEhdKGh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS9kcml2ZS9mb2xkZXJzLzFhSGlDYllGZE1QNEduLXBBRGtxUlRwcTBNNzEyMzZxWj91c3A9c2hhcmluZyksIHkgdGFtYmnDqW4gbGEgc2VzacOzbiBkZSBbTWljcm9hcHJlbmRpemFqZXNdKGh0dHBzOi8vcnB1YnMuY29tL0RhdGE0SFIvcjRoci1taWNyb2FwcmVuZGl6YWplcykuDQoNCiMjIEdyw6FmaWNvcyBkZSBsw61uZWFzDQoNCkxvcyBncsOhZmljb3MgZGUgbMOtbmVhcyBzb24gZWwgZ3LDoWZpY28gcG9yIGV4Y2VsZW5jaWEgcGFyYSB2aXN1YWxpemFyIHNlcmllcyBkZSB0aWVtcG8sIGVzIGRlY2lyIGxhIGV2b2x1Y2nDs24gdGVtcG9yYWwgZGUgdW5hIHZhcmlhYmxlLg0KDQpQYXJhIGVzdGUgZWplbXBsbyB2YW1vcyBhIHV0aWxpemFyIGVsIGRhdGEgZnJhbWUgZGUgYHJvdGFjaW9uYC4NCg0KYGBge3IgbGluZWF9DQojIFZlYW1vcyBlbCBjb250ZW5pZG8gZGVsIGRhdGEgZnJhbWUgJ3JvdGFjaW9uJw0Kcm90YWNpb24NCmBgYA0KDQpQYXJhIHZlciBsYSBldm9sdWNpw7NuIGRlIGFtYmFzIGzDrW5lYXMsIHBvZGVtb3MgbWFwZWFyOg0KDQotICAgYFBlcmlvZG9gIGFsIGVqZSB4DQoNCi0gICBgQ2FudGlkYWRlc2AgYWwgZWplIHkNCg0KLSAgIGBNb3ZpbWllbnRvc2AgY29tbyBgY29sb3JgLg0KDQotICAgYGdlb21fbGluZSgpYCBjb21vIHZpc3VhbGl6YWNpw7NuDQoNCmBgYHtyIGxpbmVhMn0NCiMgSGFjZXIgdW4gZ3LDoWZpY28gZGUgbMOtbmVhcyBjb24gbGEgZXZvbHVjacOzbiB0ZW1wb3JhbCBkZSBsb3MgSW5ncmVzb3MgeSBFZ3Jlc29zDQpnZ3Bsb3Qocm90YWNpb24sIGFlcyh4ID0gUGVyaW9kbywgeSA9IENhbnRpZGFkZXMsIGNvbG9yID0gTW92aW1pZW50b3MpKSArDQogIGdlb21fbGluZSgpDQpgYGANCg0KUGFyYSBtYXJjYXIgbGEgc2VjY2nDs24gZG9uZGUgY29ydGEgY2FkYSBhw7FvLCBwb2RlbW9zIGFncmVnYXIgdW5hIGNhcGEgZGUgcHVudG9zIGFsIGdyw6FmaWNvIGFudGVyaW9yLiBKdWd1ZW1vcyBjb24gZWwgcGFyw6FtZXRybyBgc2l6ZWAgZGVudHJvIGRlIGBnZW9tX3BvaW50KClgLg0KDQpgYGB7ciBsaW5lYTN9DQojIEFncmVnYXIgdW5hIGNhcGEgZGUgcHVudG9zDQpnZ3Bsb3Qocm90YWNpb24sIGFlcyh4ID0gUGVyaW9kbywgeSA9IENhbnRpZGFkZXMsIGNvbG9yID0gTW92aW1pZW50b3MpKSArDQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMikNCg0KYGBgDQoNCkVzdGUgZXMgdW4gYnVlbiBtb21lbnRvIHBhcmEgdmVyIGxhIHV0aWxpZGFkIGRlIGxvcyAqZmFjZXRzKi4NCg0KYGBge3IgbGluZWEtNH0NCiMgQcOxYWRpciB1bmEgY2FwYSBkZSBmYWNldF93cmFwKH5Nb3ZpbWllbnRvcykNCmdncGxvdChyb3RhY2lvbiwgYWVzKHggPSBQZXJpb2RvLCB5ID0gQ2FudGlkYWRlcywgY29sb3IgPSBNb3ZpbWllbnRvcykpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIGZhY2V0X3dyYXAofk1vdmltaWVudG9zKQ0KYGBgDQoNCiMjIEdyw6FmaWNvcyBkZSBkaXNwZXJzacOzbiAtIFNjYXR0ZXIgcGxvdHMNCg0KTG9zIGdyw6FmaWNvcywgbyBkaWFncmFtYXMgZGUgZGlzcGVyc2nDs24sIHNvbiB1bmEgZ3JhbiBmb3JtYSBkZSB2aXN1YWxpemFyIHJlbGFjaW9uZXMgZW50cmUgdmFyaWFibGVzIG51bcOpcmljYXMsIGNvbmNlbnRyYWNpb25lcyBkZSBkYXRvcywgZW50cmUgb3RyYXMgY29zYXMuIEVzIHVuYSBmb3JtYSBkZSBpbnRyb2R1Y2lybm9zIGVuIGVsIGFuw6FsaXNpcyBkZSByZWdyZXNpb25lcyBsaW5lYWxlcy4NCg0KVGFtYmnDqW4gcGVybWl0ZW4gbGEgY29uY2VudHJhY2nDs24gZGUgZGF0b3MgZW50cmUgdmFyaWFibGVzIG51bcOpcmljYXMgeSBjYXRlZ8OzcmljYXMuIEVsICpnZW9tKiBxdWUgbmVjZXNpdGFyZW1vcyBlcyBgZ2VvbV9wb2ludCgpYC4NCg0KYGBge3Igc2NhdGVyMX0NCiMgUmVhbGl6YXIgdW4gZ3LDoWZpY28gZGUgZGlzcGVyc2nDs24gZW50cmUgYW5pb3NfZXhwZXJpZW5jaWEgeSBzdWVsZG9fYnJ1dG8NCmdncGxvdChraXdpLCBhZXMoeCA9IGFuaW9zX2V4cGVyaWVuY2lhLCB5ID0gc3VlbGRvX2JydXRvKSkgKw0KICBnZW9tX3BvaW50KCkNCmBgYA0KDQpTaSBxdWlzacOpcmFtb3MgdHJhemFyIHVuYSByZWN0YSBkZSByZWdyZXNpw7NuLCB1bmEgZm9ybWEgZGUgaGFjZXJsbyBlcyBsYSBzaWd1aWVudGU6DQoNCmBgYHtyIHNjYXR0ZXItcmVnfQ0KIyBBZ3JlZ2FyIHVuYSByZWdyZXNpw7NuIGxpbmVhbA0KZ2dwbG90KGtpd2ksIGFlcyh4ID0gYW5pb3NfZXhwZXJpZW5jaWEsIHkgPSBzdWVsZG9fYnJ1dG8pKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpDQpgYGANCg0KQ29tbyBkZWPDrWFtb3MsIHRhbWJpw6luIHBvZGVtb3MgYW5hbGl6YXIgbGEgZGlzdHJpYnVjacOzbiBkZSBsb3MgZGF0b3MgZW50cmUgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhIHkgdW5hIHZhcmlhYmxlIG51bcOpcmljYQ0KDQpgYGB7ciBzY2F0ZXIyfQ0KIyBHcmFmaWNhciBnZW5lcm8gdnMuIHN1ZWxkb19icnV0bw0KZ2dwbG90KGtpd2ksIGFlcyh4ID0gZ2VuZXJvLCB5ID0gc3VlbGRvX2JydXRvKSkgKw0KICBnZW9tX3BvaW50KCkNCmBgYA0KDQpDdWFuZG8gaGFjZW1vcyBncsOhZmljb3MgZGUgZGlzcGVyc2nDs24sIGRvcyBwYXLDoW1ldHJvcyBtdXkgw7p0aWxlcyBwYXJhIGFuYWxpemFyIGRvbmRlIGhheSBtw6FzIGNvbmNlbnRyYWNpw7NuIGRlIGRhdG9zLCBzb24gbG9zIHBhcsOhbWV0cm9zICoqaml0dGVyKiogeSAqKmFscGhhKiouDQoNCioqSml0dGVyKiogbGUgYcOxYWRlIHVuIHBvY28gZGUgcnVpZG8gYSBsb3MgZGF0b3MgcGFyYSBldml0YXIgbGEgc3VwZXJwb3NpY2nDs24gZGUgcHVudG9zLCBwZXJvIG1hbnRlbmllbmRvIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbG9zIGRhdG9zLiBIYWJsYW5kbyBtYWwgeSBwcm9udG8sIGxvIHF1ZSBoYWNlIGVzIG1vdmVyIHVuIHBvcXVpdG8gbG9zIHB1bnRvcyBkZW50cm8gZGVsIGdyw6FmaWNvIHBhcmEgcXVlIHNlYW4gbcOhcyB2aXNpYmxlcy4NCg0KT3RybyBwYXLDoW1ldHJvIG11eSDDunRpbCBlcyAqKmFscGhhKiogcXVlIGxlIGHDsWFkZSB0cmFuc3BhcmVuY2lhIGEgbG9zIHB1bnRvcywgbG8gY3VhbCBub3MgYXl1ZGEgYSB2ZXIgZG9uZGUgaGF5IG3DoXMgY29uY2VudHJhY2nDs24gZGUgZGF0b3MsIGVuIGRvbmRlIGhheWEgbcOhcyBvc2N1cmlkYWQgZGVsIGNvbG9yLg0KDQpUYW1iacOpbiBzZSBwdWVkZSBqdWdhciBjb24gZWwgcGFyw6FtZXRybyAqKnNpemUqKiBvICoqc2hhcGUqKiBwYXJhIG1vZGlmaWNhciBlbCB0YW1hw7FvLCBvIGxhIGZvcm1hIGRlIGxvcyBwdW50b3MuDQoNCkVuIGVzdGUgZ3LDoWZpY28gcG9uZW1vcyBlc3RvcyBwYXLDoW1ldHJvcyBlbiBwcsOhY3RpY2EuDQoNCmBgYHtyIHNjYXR0ZXItZmlufQ0KIyBIYWNlbW9zIGVsIGdyw6FmaWNvIGHDsWFkaWVuZG8gZWwgcGFyw6FtZXRybyBqaXR0ZXIgeSBhbHBoYQ0KZ2dwbG90KGtpd2ksIGFlcyh4ID0gZ2VuZXJvLCB5ID0gc3VlbGRvX2JydXRvKSkgKw0KICBnZW9tX3BvaW50KHBvc2l0aW9uID0gImppdHRlciIsDQogICAgICAgICAgICAgYWxwaGEgPSAwLjMsDQogICAgICAgICAgICAgc2l6ZSA9IDIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyMgR3LDoWZpY29zIGRlIHRvcnRhDQoNCiFbXShodHRwczovL2dpdGh1Yi5jb20vY2hlY2hvaWQvcGVwYWl0YmEvYmxvYi9tYWluL2luc3QvdHV0b3JpYWxzL3BlcGExL3BpZV9jcm9jcy5qcGc/cmF3PXRydWUpe3dpZHRoPSIzMDIifQ0KDQpUb2RhcyBsYXMgcGVyc29uYXMgcXVlIHRyYWJhamFuIGVuIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIEFNQU4gb2RpYXIgbG9zIGdyw6FmaWNvcyBkZSB0b3J0YS4gRGUgaGVjaG8gZW4gYGdncGxvdDJgIG5vIGhheSB1biBgZ2VvbV9waWVgIHkgbm8gY29ub3pjbyBuaW5ndW5hIGV4dGVuc2nDs24gcXVlIGxvcyByZWFsaWNlIGRlIG1hbmVyYSBzZW5jaWxsYS4gQXVucXVlIHNpIGV4aXN0ZSBsYSBmdW5jacOzbiBkZSBSIGJhc2UgYHBpZWANCg0KYGBge3IgcGllLWJhc2V9DQojIFByZXByb2Nlc2FtaWVudG8NCnNhdGlzZmFjY2lvbiA8LSBraXdpICU+JSANCiAgc2VsZWN0KHNhdGlzZmFjY2lvbikgJT4lIA0KICBmaWx0ZXIoIWlzLm5hKHNhdGlzZmFjY2lvbikpICU+JSANCiAgZ3JvdXBfYnkoc2F0aXNmYWNjaW9uKSAlPiUgDQogIHRhbGx5KCkgJT4lIA0KICBhcnJhbmdlKHNhdGlzZmFjY2lvbikNCg0KIyBIYWNlbW9zIGVsIGdyw6FmaWNvIGVuIFIgYmFzZQ0KcGllKHNhdGlzZmFjY2lvbiRuKQ0KYGBgDQoNCkRlIHRvZGFzIG1hbmVyYXMsIGNvbW8gZGljZSBtaSBhYnVlbGEsICpzaWVtcHJlIGhheSB1biByb3RvIHBhcmEgdW4gZGVzY29zaWRvKiB5IGFxdcOtIGxlcyBwcmVzZW50byB1bmEgZm9ybWEgZGUgcmVhbGl6YXIgdW4gKipncsOhZmljbyBkZSBkb25hKiouDQoNCkVuIGVzZW5jaWEsIGxvIHF1ZSB2YW1vcyBhIGhhY2VyIGVzIHVuIGdyw6FmaWNvIGRlIGJhcnJhcyBhcGlsYWRhcyBhbCAxMDAlIGFsIGN1YWwgZG9ibGFyZW1vcywgZGUgbGEgbWlzbWEgbWFuZXJhIHF1ZSBsYXMgY3VjaGFyYXMgZW4gTWF0cml4Lg0KDQohW10oaHR0cHM6Ly9pbWcxLndzaW1nLmNvbS9pc3RlYW0vaXAvMzIwYmRlNDQtNTYxZS00MWU0LWE1YWEtNDAyOGE1YzVkODgzL3RoZXJlJTI1MjBpcyUyNTIwbm8lMjUyMHNwb29uLmpwZyl7d2lkdGg9IjM0NCJ9DQoNClByaW1lcm8gaGFyZW1vcyB1biBncsOhZmljbyBkZSBiYXJyYXMgYXBpbGFkYXMgYWwgMTAwJSwgY3V5YXMgcG9yY2lvbmVzIChyZWN0w6FuZ3Vsb3MpICJkb2JsYXJlbW9zIiBwb3N0ZXJpb3JtZW50ZS4gRW4gZXN0ZSBlamVtcGxvLCB2YW1vcyBhIGhhY2VyIHVuIGdyw6FmaWNvIGRlIGRvbmEgc29icmUgZWwgcG9yY2VudGFqZSBkZSBnZW50ZSB5IGVsIHRpcG8gZGUgdW5pdmVyc2lkYWQuIFBhcmEgbG8gY3VhbCB0ZW5lbW9zIHF1ZSBoYWNlciB1biBwZXF1ZcOxbyBwcmVwcm9jZXNhbWllbnRvIGRlIGRhdG9zOg0KDQpgYGB7ciBwaWUtcHJlfQ0KIyBDcmVhciB1biBvYmpldG8gbGxhbWFkbyBlZHVjYWNpb24NCmVkdWNhY2lvbiA8LSBraXdpICU+JSANCiAgc2VsZWN0KHRpcG9fdW5pdmVyc2lkYWQpICU+JSAgICAgICAgICAgICMgU2VsZWNjaW9uYSBsYSBjb2x1bW5hDQogIGdyb3VwX2J5KHRpcG9fdW5pdmVyc2lkYWQpICU+JSAgICAgICAgICAjIEFncnVwYSBwb3IgdGlwb191bml2ZXJzaWRhZA0KICBzdW1tYXJpc2UoY2FudGlkYWQgPSBuKCkpICU+JSAgICAgICAgICAgIyBBZ3JlZ2EgY29sdW1uYSBjb24gY2FudGlkYWQgZGUgY2Fzb3MNCiAgbXV0YXRlKHBvcmNlbnRhamUgPSBjYW50aWRhZCAvIHN1bShjYW50aWRhZCkpICU+JSAjIEFncmVnYSBjb2x1bW5hIGRlIHBvcmNlbnRhamUNCiAgYXJyYW5nZSgtY2FudGlkYWQpICAgICAgICAgICAgICAgICAgICAgICMgT3JkZW5hIGRlc2NlbmRlbXRlbnRlIHBvciBjYW50aWRhZA0KDQojIFZlYW1vcyBlbCByZXN1bHRhZG8NCmVkdWNhY2lvbg0KYGBgDQoNCkVsIHNpZ3VpZW50ZSBwYXNvIGVzIGRldGVybWluYXIgZMOzbmRlIGVtcGllemEgeSBkw7NuZGUgdGVybWluYSBjYWRhIHJlY3TDoW5ndWxvIHF1ZSB2YW1vcyBhIGNyZWFyLiBFc28gbG8gdmFtb3MgYSBwb25lciBlbiB1bmFzIGNvbHVtbmFzIG51ZXZhcyBxdWUgbGxhbWFyZW1vcyBgeW1heGAgeSBgeW1pbmAuDQoNCmBgYHtyIHBpZS1tYXhtaW59DQojIENhbGN1bGFtb3MgbG9zIGzDrW1pdGVzIHN1cGVyaW9yZXMgZGUgY2FkYSByZWN0w6FuZ3Vsbw0KZWR1Y2FjaW9uJHltYXggPC0gY3Vtc3VtKGVkdWNhY2lvbiRwb3JjZW50YWplKQ0KDQojIENhbGN1bGFtb3MgZWwgbMOtbWl0ZSBpbmZlcmlvciBkZSBjYWRhIHBvcmNpw7NuDQplZHVjYWNpb24keW1pbiA8LSBjKDAsIGhlYWQoZWR1Y2FjaW9uJHltYXgsIG49LTEpKQ0KDQojIENhbGN1bGFtb3MgbGEgcG9zaWNpw7NuIGRlIGxhIGV0aXF1ZXRhDQplZHVjYWNpb24kcG9zaWNpb25fZXRpcXVldGEgPC0gKGVkdWNhY2lvbiR5bWF4ICsgZWR1Y2FjaW9uJHltaW4pIC8gMg0KDQojIENyZWFtb3MgbGFzIGV0aXF1ZXRhcyBkZSBjYWRhIHBvcmNpw7NuDQplZHVjYWNpb24kZXRpcXVldGEgPC0gcGFzdGUwKGVkdWNhY2lvbiR0aXBvX3VuaXZlcnNpZGFkLCAjIHBhc3RlMCBwZWdhIGVsZW1lbnRvcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiXG4gQ2FudDogIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVkdWNhY2lvbiRjYW50aWRhZCkNCg0KIyBWZXIgY29tbyBxdWVkw7MgZWwgZGF0YSBmcmFtZQ0KZWR1Y2FjaW9uDQpgYGANCg0KTG9zIHBhc29zIGFudGVyaW9yZXMgY3JlYXJvbiBsYSBwb3NpY2nDs24gZG9uZGUgY29taWVuemFuIHkgZG9uZGUgdGVybWluYSBjYWRhIGJhcnJhLCBlbiBkb25kZSBzZSB1YmljYSBjYWRhIGV0aXF1ZXRhLCB5IHBvciDDumx0aW1vLCBxdcOpIHZhIGEgZGVjaXIgY2FkYSBldGlxdWV0YS4NCg0KQ29uIGxhIGZ1bmNpw7NuIGBwYXN0ZTAoKWAgbG8gcXVlIGhhY2Vtb3MgZXMgY29uY2F0ZW5hciB2YXJpb3MgZWxlbWVudG9zLCBwcmltZXJvLCBsb3MgdmFsb3JlcyBkZSBsYSBjb2x1bW5hIGB0aXBvX3VuaXZlcnNpZGFkYCAoKlVuaXZlcnNpZGFkIFDDumJsaWNhLCBVbml2ZXJzaWRhZCBQcml2YWRhLCogZXRjLiksIGx1ZWdvLCBjb24gbGEgYmFycmEgaW52ZXJ0aWRhIHkgbGEgbiBgXG5gIGVuIG90cm8gcmVuZ2zDs24gYWdyZWdhIGxhIHBhbGFicmEgYENhbnQ6YCB5IGZpbmFsbWVudGUgcG9uZSBlbCB2YWxvciBkZSBsYSBjb2x1bW5hIGBjYW50aWRhZGAuDQoNCkFob3JhLCB2YW1vcyBhIGhhY2VyIGVsIGdyw6FmaWNvIGNhcGEgcG9yIGNhcGEgcGFyYSBxdWUgdmVhbiBjw7NtbyBzZSBjb25zdHJ1eWU6DQoNCmBgYHtyIHBpZS1wbG90fQ0KIyBSZWFsaXphciBlbCBncsOhZmljbyBkZSBkb25hDQoNCiMgUHJpbWVyYSBjYXBhIGRlbCBncsOhZmljbw0KZ2dwbG90KGVkdWNhY2lvbiwgYWVzKHltYXg9eW1heCwgDQogICAgICAgICAgICAgICAgICAgICAgeW1pbj15bWluLCANCiAgICAgICAgICAgICAgICAgICAgICB4bWF4PTQsIA0KICAgICAgICAgICAgICAgICAgICAgIHhtaW49MywgDQogICAgICAgICAgICAgICAgICAgICAgZmlsbD10aXBvX3VuaXZlcnNpZGFkKSkgKw0KICBnZW9tX3JlY3QoKSANCg0KIyBUaGVyZSBpcyBubyBzcG9vbiEgTnVlc3RybyBwcmltZXIgZ3LDoWZpY28gZGUgdG9ydGENCiBnZ3Bsb3QoZWR1Y2FjaW9uLCBhZXMoeW1heD15bWF4LCANCiAgICAgICAgICAgICAgICAgICAgICB5bWluPXltaW4sIA0KICAgICAgICAgICAgICAgICAgICAgIHhtYXg9NCwgDQogICAgICAgICAgICAgICAgICAgICAgeG1pbj0zLCANCiAgICAgICAgICAgICAgICAgICAgICBmaWxsPXRpcG9fdW5pdmVyc2lkYWQpKSArDQogIGdlb21fcmVjdCgpICsNCiAgY29vcmRfcG9sYXIodGhldGEgPSAieSIpDQogDQoNCiMgUmVjb3J0YW1vcyBlbCBjZW50cm8gZGUgbGEgdG9ydGENCmdncGxvdChlZHVjYWNpb24sIGFlcyh5bWF4PXltYXgsIA0KICAgICAgICAgICAgICAgICAgICAgIHltaW49eW1pbiwgDQogICAgICAgICAgICAgICAgICAgICAgeG1heD00LCANCiAgICAgICAgICAgICAgICAgICAgICB4bWluPTMsIA0KICAgICAgICAgICAgICAgICAgICAgIGZpbGw9dGlwb191bml2ZXJzaWRhZCkpICsNCiAgZ2VvbV9yZWN0KCkgKw0KICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKw0KICB4bGltKGMoMiw0KSkNCg0KIyBBc8OtIHF1ZWRhcsOtYSBlbCBncsOhZmljbyBkZSBiYXJyYSBzaW4gY29vcmRfcG9sYXINCmdncGxvdChlZHVjYWNpb24sIGFlcyh5bWF4PXltYXgsIA0KICAgICAgICAgICAgICAgICAgICAgIHltaW49eW1pbiwgDQogICAgICAgICAgICAgICAgICAgICAgeG1heD00LCANCiAgICAgICAgICAgICAgICAgICAgICB4bWluPTMsIA0KICAgICAgICAgICAgICAgICAgICAgIGZpbGw9dGlwb191bml2ZXJzaWRhZCkpICsNCiAgZ2VvbV9yZWN0KCkgKw0KICB4bGltKGMoMiw0KSkNCg0KDQojIEFncmVnYW1vcyBhbGd1bmFzIG1vZGlmaWNhY2lvbmVzIGVzdMOpdGljYXMNCmdncGxvdChlZHVjYWNpb24sIGFlcyh5bWF4PXltYXgsIA0KICAgICAgICAgICAgICAgICAgICAgIHltaW49eW1pbiwgDQogICAgICAgICAgICAgICAgICAgICAgeG1heD00LCANCiAgICAgICAgICAgICAgICAgICAgICB4bWluPTMsIA0KICAgICAgICAgICAgICAgICAgICAgIGZpbGw9dGlwb191bml2ZXJzaWRhZCkpICsNCiAgZ2VvbV9yZWN0KCkgKw0KICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKw0KICB4bGltKGMoMiw0KSkgKw0KICB0aGVtZV92b2lkKCkgKyAgICAgICAgICAgICAjIEVsaW1pbmEgZm9uZG9zIHkgcmVmZXJlbmNpYXMNCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKSArICAgIyBEZWZpbmUgdW5hIGVzY2FsYSBkZSBjb2xvcmVzDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsDQogICAgICAgIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIpICsgIyBNb2RpZmljYSBwb3NpY2nDs24gbGV5ZW5kYXMgeSBkZWwgdMOtdHVsbw0KICBsYWJzKHRpdGxlID0gIlJlc3B1ZXN0YXMgc2Vnw7puIFRpcG8gZGUgVW5pdmVyc2lkYWQiLA0KICAgICAgIGZpbGwgPSAiVGlwbyBkZSBVbml2ZXJzaWRhZCIsIA0KICAgICAgIGNhcHRpb24gPSAiRnVlbnRlOiBFbmN1ZXN0YSBLSVdJIGRlIFN1ZWxkb3MgZGUgUkggMjAyMCIpICsNCiAgZ2VvbV9sYWJlbCh4ID0gMy41LA0KICAgICAgICAgICAgIGFlcyh5ID0gcG9zaWNpb25fZXRpcXVldGEsDQogICAgICAgICAgICAgICAgIGxhYmVsID0gZXRpcXVldGEpLA0KICAgICAgICAgICAgIHNpemUgPSAzKQ0KDQoNCmBgYA0KDQojIyBDaGVjayBvdXQNCg0KYGdncGxvdDJgIGVzIHVuIHBhcXVldGUgcXVlIHRpZW5lIHVuYSBpbmZpbmlkYWQgZGUgcG9zaWJpbGlkYWRlcywgdGllbmUgbXVjaGFzIGV4dGVuc2lvbmVzIHF1ZSBwb3NpYmlsaXRhbiBjb25zdHJ1aXIgdG9kbyB0aXBvIGRlIGdyw6FmaWNvcywgYcOxYWRpcmxlcyBpbnRlcmFjdGl2aWRhZCwgaW5mb3JtYWNpw7NuIGRpbsOhbWljYSB5IG11Y2hhcyBjb3NhcyBtw6FzLg0KDQpFc3RlIHBhcXVldGUgY29uc3RydXllIGxvcyBncsOhZmljb3MgcG9yIGNhcGFzLiBMYXMgY2FwYXMgZnVuZGFtZW50YWxlcyBzb246DQoNCi0gICBEYXRvcw0KDQotICAgVmFyaWFibGVzLCBxdWUgc2UgYXNpZ25hbiBkZW50cm8gZGVsIGBhZXMoKWAuDQoNCi0gICBVbiBgZ2VvbV8iYWxnbyJgIHF1ZSBsZSB2YSBhIGRhciBsYSBmb3JtYSBhbCBncsOhZmljby4NCg0KTG9zIGdyw6FmaWNvcyBhZGVtw6FzIHNlIHB1ZWRlbiBjb21iaW5hciBhZ3JlZ2FuZG8gY2FwYXMsIGxvIGN1YWwgcG9zaWJpbGl0YSBhZ3JlZ2FyIGluZm9ybWFjacOzbiwgbyBsaW1waWFyIGxvcyBncsOhZmljb3MgcGFyYSBhw7FhZGlyIGluZm9ybWFjacOzbiBkZSBjb250ZXh0by4NCg0KQ3VzdG9taXphciB1biBncsOhZmljbyBsbGV2YSB0cmFiYWpvLCBwZXJvIHRvZG9zIGVzb3MgZWxlbWVudG9zIHNlIHB1ZWRlbiBndWFyZGFyIGVuIG9iamV0b3MgeSByZXV0aWxpemFybG9zIHBhcmEgYWhvcnJhciBjw7NkaWdvIHkgZXNmdWVyem8uDQoNCkVzdG8gbWUgcGVybWl0ZSBpciB0cmFiYWphbmRvIHByb2dyZXNpdmFtZW50ZSBjb24gbG9zIGdyw6FmaWNvcy4gUHJpbWVybyBtZSBhc2VndXJvIHF1ZSBlbCBncsOhZmljbyBmdW5jaW9uZSwgeSBsdWVnbyB2b3kgaW5jb3Jwb3JhbmRvIGNhcGFzIHBhcmEgZGVmaW5pciBjb2xvcmVzLCBsb3MgZWplcywgbG9zIHTDrXR1bG9zLCB5IGZpbmFsbWVudGUgbGEgZXN0w6l0aWNhIGdlbmVyYWwgZGVsIGdyw6FmaWNvLg0KDQpQdWVkZW4gZXhwbG9yYXIgYWxndW5hcyBleHRlbnNpb25lcyBlbiBlc3RlIGxpbms6IDxodHRwczovL2V4dHMuZ2dwbG90Mi50aWR5dmVyc2Uub3JnL2dhbGxlcnkvPi4gVGFtYmnDqW4gaGF5IHVuIGxpYnJvIG9ubGluZSAoZW4gaW5nbMOpcykgcXVlIHB1ZWRlbiByZXZpc2FyIGFjw6E6IDxodHRwczovL2dncGxvdDItYm9vay5vcmcvPi4NCg==