1 Introduction

The goal of the project is to execute data science exploration steps against flights data and create a regression model to predict what flight will be delayed

The data consists of flight arrival and departure details for all commercial flights within the USA, from October 1987 to April 2008.

For my analysis I will be using data for 2007-2008 years only. Data for 2007 will be splitted into training and validation sets, data for 2008 will serve as validation set to evaluate the model performance

2 Batch Ingestion & Processing

The first step is to set up Big Data environemnt using the following technology stack - HDFS, YARN, Apache Spark, Apache kafka and Apache Flume

2.1 Big Data local environment (Mac OS)

2.1.1 HDFS

Start Hadoop standalone cluster

cd /usr/local/Cellar/hadoop/2.8.1/
./sbin/start-dfs.sh && 

2.1.2 YARN

Start YARN manager

./sbin/start-yarn.sh

2.1.3 Apache Spark

Start Spark cluster with 2 workers

cd /Users/olegbaydakov/Downloads/spark-2.2.0-bin-hadoop2.7
./sbin/start-master.sh -i $SPARK_LOCAL_IP 
./sbin/start-slave.sh spark://$SPARK_LOCAL_IP:7077

2.1.4 Kafka and Zookeper

Start zookeper server

bin/zookeeper-server-start.sh config/zookeeper.properties

Start Apache Kafka cluser consisting from 3 instances

bin/kafka-server-start.sh config/server.properties
bin/kafka-server-start.sh config/server-1.properties
bin/kafka-server-start.sh config/server-2.properties

2.2 Data preparation

2.2.1 Create Kafka topics

sh kafka-topics --create --zookeeper localhost:2181 --replication-factor 2 --partitions 2 --topic airports

sh kafka-topics --create --zookeeper localhost:2181 --replication-factor 2 --partitions 2 --topic planedate

sh kafka-topics --create --zookeeper localhost:2181 --replication-factor 2 --partitions 2 --topic carriers

sh kafka-topics --list --zookeeper localhost:2181

2.2.2 Download data

Run the following script to download data from the web onto local folder. Download the yearly flight data and the airlines lookup table.

# Make download directory
mkdir /tmp/flights

# Download flight data by year
for i in {1987..2008}
  do
    echo "$(date) $i Download"
    fnam=$i.csv.bz2
    wget -O /tmp/flights/$fnam http://stat-computing.org/dataexpo/2009/$fnam
    echo "$(date) $i Unzip"
    bunzip2 /tmp/flights/$fnam
  done

# Download airline carrier data
wget -O /tmp/airlines.csv http://www.transtats.bts.gov/Download_Lookup.asp?Lookup=L_UNIQUE_CARRIERS

# Download airports data
wget -O /tmp/airports.csv https://raw.githubusercontent.com/jpatokal/openflights/master/data/airports.dat

2.2.3 Submit data to Kafka topics

// kafka producer
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic planedate < ~/tmp/2008.csv
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic airports < ~/tmp/airports.csv
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic carriers < ~/tmp/airlines.csv

2.3 Batch ingestion (HDFS)

2.3.1 Create folders in HDFS:

hadoop fs -mkdir /data/raw
hadoop fs -mkdir /data/modelled
hadoop fs -mkdir /data/decomposed

hadoop fs -mkdir /data/raw/airports
hadoop fs -mkdir /data/raw/airlines
hadoop fs -mkdir /data/raw/flights

hadoop fs -mkdir /data/decomposed/airports
hadoop fs -mkdir /data/decomposed/airlines
hadoop fs -mkdir /data/decomposed/flights

hadoop fs -mkdir /data/modelled/airports
hadoop fs -mkdir /data/modelled/airlines
hadoop fs -mkdir /data/modelled/flights

2.3.2 Flume configuration

Create Flume configuration files for airports and planedate topcis to be ingested into HDFS (raw zone)

2.3.2.1 Airports

a1.sources.r1.zookeeperConnect=localhost:2181
a1.sources.r1.topic=airports

a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.writeFormat=Text
a1.sinks.k1.hdfs.fileType=DataStream
a1.sinks.k1.hdfs.path=/data/raw/airports/
a1.sinks.k1.hdfs.filePrefix=airports
a1.sinks.k1.hdfs.rollCount=100000
a1.sinks.k1.hdfs.rollSize=0

# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 10000
a1.channels.c1.transactionCapacity = 10000

# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

2.3.2.2 Planedate

# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1

a1.sources.r1.type=org.apache.flume.source.kafka.KafkaSource
a1.sources.r1.zookeeperConnect=localhost:2181
a1.sources.r1.topic=planedate

a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.writeFormat=Text
a1.sinks.k1.hdfs.fileType=DataStream
a1.sinks.k1.hdfs.path=/data/raw/planedate/
a1.sinks.k1.hdfs.filePrefix=planedate
a1.sinks.k1.hdfs.rollCount=10000000
a1.sinks.k1.hdfs.rollSize=0

# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 10000
a1.channels.c1.transactionCapacity = 10000

# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

2.3.3 Kafka and Spark structured streaming integration

Scala code to read data from Kafka topic using Spark structured streaming

  • Scala trait to initialize Spark

  • Main object to pull data from Kafaka topic and ingest into HDFS

2.3.3.1 SparkInit.scala

package kafka_stream

import org.apache.hadoop.yarn.util.RackResolver
import org.apache.log4j.{Level, LogManager, Logger}
import org.apache.spark.sql.SparkSession

trait InitSpark {
  Logger.getLogger(classOf[RackResolver]).getLevel
  Logger.getLogger("org").setLevel(Level.OFF)
  Logger.getLogger("akka").setLevel(Level.OFF)

  val spark: SparkSession = SparkSession.builder()
    .appName("Spark Structured Streaming Example")
    //.master("local[*]")
    .master("spark://127.0.01:7077")
    .getOrCreate()

  val sc = spark.sparkContext
  val sqlContext = spark.sqlContext

  def reader = spark.read
    .option("header",true)
    .option("inferSchema", false)
    .option("mode", "DROPMALFORMED")
    .option("dateFormat", "dd/MM/yyyy")

  def readerWithoutHeader = spark.read
    .option("header",true)
    .option("inferSchema", false)
    .option("mode", "DROPMALFORMED")
    .option("dateFormat", "MM/dd/yyyy")



  def close = {
    spark.close()
  }
}

2.3.3.2 KafkaStream.scala

package kafka_stream

import org.apache.hadoop.yarn.util.RackResolver
import org.apache.spark.sql.{Dataset, SaveMode, SparkSession}
import org.apache.spark.sql.streaming.{OutputMode, ProcessingTime}

import org.apache.spark.streaming.kafka.KafkaUtils
import kafka.serializer.StringDecoder

import org.apache.spark.streaming._

import org.apache.spark.streaming.Durations
import org.apache.log4j.{Level, Logger}

object kafka_stream extends InitSpark {

  import spark.implicits._

  def main(args: Array[String]): Unit = {

    Logger.getLogger(classOf[RackResolver]).getLevel
    Logger.getLogger("org").setLevel(Level.OFF)
    Logger.getLogger("akka").setLevel(Level.OFF)
    
    read_from_kafka()
    }

 def read_from_kafka():Unit ={
    import spark.implicits._
   
   // READ FROM KAFKA TOPIC
    val df = spark
      .readStream
      .format("kafka")
      .option("kafka.bootstrap.servers", "localhost:9092")
      .option("subscribe", "carriers")
      .option("startingOffsets", "earliest")
      .option("failOnDataLoss", "false")
      .load()

    val values = df.selectExpr("CAST(value AS STRING)").as[String]
    
   // WRITE TO HDFS
   val query = values //.orderBy("window")
      .repartition(1)
      .writeStream
      .outputMode(OutputMode.Append())
      .format("csv")
      .option("checkpointLocation", "hdfs://localhost:8020/tmp/checkpoints")
      .option("path", "hdfs://localhost:8020/data/raw/carriers")
      .start()
      .awaitTermination()
 }
}

2.3.3.3 build.sbt

name := "KafakSpark"

version := "1.0"

scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
  "org.apache.spark" % "spark-core_2.11" % "2.2.0" ,
  "org.apache.spark" % "spark-sql_2.11" % "2.2.0" ,
  "org.apache.spark" % "spark-streaming_2.11" % "2.2.0",
  "org.apache.spark" % "spark-streaming-kafka_2.11" % "1.6.3"
)

// https://mvnrepository.com/artifact/org.apache.kafka/kafka_2.11
libraryDependencies += "org.apache.kafka" % "kafka_2.11" % "0.11.0.0"

// https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients
libraryDependencies += "org.apache.kafka" % "kafka-clients" % "0.11.0.0"

// https://mvnrepository.com/artifact/org.apache.kafka/kafka-streams
libraryDependencies += "org.apache.kafka" % "kafka-streams" % "0.11.0.0"

// https://mvnrepository.com/artifact/org.apache.kafka/connect-api
libraryDependencies += "org.apache.kafka" % "connect-api" % "0.11.0.0"

libraryDependencies += "com.databricks" %% "spark-avro" % "4.0.0"

resolvers += Resolver.mavenLocal
resolvers += "central maven" at "https://repo1.maven.org/maven2/"

2.4 Batch processing raw -> decomposed -> modelled (HDFS)

Create external Hive tables in every HDFS zone.

2.4.1 Raw zone

2.4.1.1 Airports

CREATE EXTERNAL TABLE IF NOT EXISTS airports(
iata STRING,
airport STRING,
city STRING,
state STRING,
country STRING,
lat STRING,
longt STRING)
ROW FORMAT DELIMITED
    FIELDS TERMINATED BY ','
    STORED AS TEXTFILE
    LOCATION '/data/raw/airports'
   tblproperties ("skip.header.line.count"="1") 
    ;

2.4.1.2 Carriers (airlines)

CREATE EXTERNAL TABLE IF NOT EXISTS airlines
(
Code string,
Description string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
WITH SERDEPROPERTIES
(
"separatorChar" = '\,',
"quoteChar"     = '\"'
)
STORED AS TEXTFILE
 LOCATION '/data/raw/carriers'
tblproperties("skip.header.line.count"="1");

2.4.1.3 Planedate (flights)

drop table if exists flights;
CREATE EXTERNAL TABLE IF NOT EXISTS flights(
 year STRING,
 month STRING,
 dayofmonth STRING,
 dayofweek STRING,
 deptime STRING,
 crsdeptime STRING,
 arrtime STRING,
 crsarrtime STRING,
 uniquecarrier STRING,
 flightnum STRING,
 tailnum STRING,
 actualelapsedtime STRING,
 crselapsedtime STRING,
 airtime STRING,
 arrdelay STRING,
 depdelay STRING,
 origin STRING,
 dest STRING,
 distance STRING,
 taxiin STRING,
 taxiout STRING,
 cancelled STRING,
 cancellationcode STRING,
 diverted STRING,
 carrierdelay STRING,
 weatherdelay STRING,
 nasdelay STRING,
 securitydelay STRING,
 lateaircraftdelay STRING)
ROW FORMAT DELIMITED
    FIELDS TERMINATED BY ','
    STORED AS TEXTFILE
    LOCATION '/data/raw/planedate'
   tblproperties ("skip.header.line.count"="1") 
    ;

2.4.2 Decomposed zone

I used Hive SQl to add additional columns and convert Hive tables from CSV to Avro since Apache Pig is pretty slow and in my opinion it’s pretty outdated for using in production.

2.4.2.1 Airports

use decomposed;
drop table if exists airports;
CREATE EXTERNAL TABLE IF NOT EXISTS airports(
iata STRING,
airport STRING,
city STRING,
state STRING,
country STRING,
lat STRING,
longt STRING,
 insert_time timestamp,
 uuid string
)
 STORED AS AVRO
    LOCATION '/data/decomposed/airports';

 use decomposed;
INSERT OVERWRITE TABLE airports SELECT 
REGEXP_REPLACE(airport,'"',''),REGEXP_REPLACE(airport,'"',''),REGEXP_REPLACE(city,'"',''),
REGEXP_REPLACE(state,'"',''),REGEXP_REPLACE(country,'"','') ,lat,long as longt  , CURRENT_TIMESTAMP AS insert_time, 
reflect("java.util.UUID", "randomUUID") as uuid FROM default.airports;

2.4.2.2 Carriers(airlines)

use decomposed;
drop table if exists airlines;
CREATE EXTERNAL TABLE IF NOT EXISTS airlines(
    Code STRING, Description STRING,
    insert_time timestamp,
    uuid string
   )
    STORED AS AVRO
    LOCATION '/data/decomposed/carriers';

use decomposed;
INSERT OVERWRITE TABLE airlines SELECT * , CURRENT_TIMESTAMP AS insert_time, 
reflect("java.util.UUID", "randomUUID") as uuid FROM default.airlines;

2.4.2.3 Planedate(flights)

use decomposed;
drop table if exists flights;
CREATE EXTERNAL TABLE IF NOT EXISTS flights(
 year STRING,
 month STRING,
 dayofmonth STRING,
 dayofweek STRING,
 deptime STRING,
 crsdeptime STRING,
 arrtime STRING,
 crsarrtime STRING,
 uniquecarrier STRING,
 flightnum STRING,
 tailnum STRING,
 actualelapsedtime STRING,
 crselapsedtime STRING,
 airtime STRING,
 arrdelay STRING,
 depdelay STRING,
 origin STRING,
 dest STRING,
 distance STRING,
 taxiin STRING,
 taxiout STRING,
 cancelled STRING,
 cancellationcode STRING,
 diverted STRING,
 carrierdelay STRING,
 weatherdelay STRING,
 nasdelay STRING,
 securitydelay STRING,
 lateaircraftdelay STRING,
 insert_time timestamp,
 uuid string)   
STORED AS AVRO
    LOCATION '/data/decomposed/planedate';

use decomposed;
INSERT OVERWRITE TABLE flights SELECT * , CURRENT_TIMESTAMP AS insert_time, 
reflect("java.util.UUID", "randomUUID") as uuid FROM default.flights;

2.4.3 Modelled zone

Hive SQL scripts to create external tables Parquet format

2.4.3.1 Airports

use modelled;
drop table if exists airports;
CREATE EXTERNAL TABLE IF NOT EXISTS airports(
id string,
name string,
city string,
country string,
faa string,
icao string,
lat double,
lon double,
alt int,
tz_offset double,
dst string,
tz_name string
)
 STORED AS PARQUET
    LOCATION '/data/modelled/airports';

2.4.3.2 Carriers(airlines)

use modelled;
drop table if exists airlines;
CREATE EXTERNAL TABLE IF NOT EXISTS airlines(
    Code STRING, Description STRING,
    insert_time timestamp,
    uuid string
   )
    STORED AS PARQUET
    LOCATION '/data/modelled/carriers';

2.4.3.3 Planedate(flights)

use modelled;
drop table if exists flights;
CREATE EXTERNAL TABLE IF NOT EXISTS flights(
 year int,
month int,
dayofmonth int,
dayofweek int,
deptime int,
crsdeptime int,
arrtime int, 
crsarrtime int,
uniquecarrier string,
flightnum int,
tailnum string, 
actualelapsedtime int,
crselapsedtime int,
airtime string,
arrdelay int,
depdelay int, 
origin string,
dest string,
distance int,
taxiin string,
taxiout string,
cancelled int,
cancellationcode string,
diverted int,
carrierdelay string,
weatherdelay string,
nasdelay string,
securitydelay string,
lateaircraftdelay string
)   
STORED AS PARQUET
    LOCATION '/data/modelled/planedate';

2.4.4 Data transfer from decomposed to modelled zone

2.4.4.1 Hive SQL example

use modelled;
INSERT OVERWRITE TABLE flights  SELECT *  FROM modelled.flights;

2.4.4.2 Spark example (scala code)

Read avro file to Spark Data Frame based on Scala case classes and then save it to parquet file

case class airports(iata:String,    airport:String, city:String,    state:String,   country:String,lat:String,  longt:String, insert_time:String, uuid:String)

case class carriers(code:String,    description:String, insert_time:String, uuid:String )

case class planedate(year: String, month : String, dayofmonth : String, dayofweek : String, deptime : String, crsdeptime : String, arrtime : String, crsarrtime : String, uniquecarrier : String,flightnum : String, tailnum : String, actualelapsedtime : String, crselapsedtime : String, airtime : String, arrdelay : String, depdelay : String, origin : String, dest : String, distance : String, taxiin : String, taxiout : String, cancelled : String, cancellationcode : String,
 diverted : String, carrierdelay : String, weatherdelay : String, nasdelay : String, securitydelay : String, lateAircraftdelay : String, insert_time: String, uuid : String )
 
import org.apache.spark.sql.functions._
import com.databricks.spark.avro._
import org.apache.spark.sql.SparkSession

val spark = SparkSession.builder()
                         .master("spark://127.0.01:7077")
                        .getOrCreate()

// The Avro records get converted to Spark types and
// then written back out as Parquet records
val df_planedate_decomposed = spark.read
              .avro("hdfs://localhost:8020/data/decomposed/planedate/")           
              .as[planedate]
              
val df_airports_decomposed = spark.read
              .avro("hdfs://localhost:8020/data/decomposed/airports/")           
              .as[planedate]

 df_planedate_decomposed.registerTempTable("planedate_DF") 
 
  //What are the primary causes for flight delays
    spark.sql("SELECT sum(weatherDelay) Weather,sum(nasdelay) NAS, sum(securitydelay) Security, sum(lateaircraftdelay) lateAircraft, sum(carrierdelay) Carrier FROM planedate_DF ").show()

    // Which Airports have the Most Delays

    spark.sql("SELECT origin, count(*) conFlight, avg(depdelay) delay FROM planedate_DF GROUP BY origin")
      .sort(desc("delay")).show()

    // Join airports and flights data
    df_planedate_decomposed.join(df_airports_decomposed, $"origin" === $"iata", joinType="inner").show(5)
 
    // Write to Parquet file to modelled zone
     df_planedate_decomposed.write.parquet("hdfs://localhost:8020/data/modelled/planedate/")
     df_airports_decomposed .write.parquet("hdfs://localhost:8020/data/modelled/airports/")
     
    

3 Data Analysis

This analysis predicts time gained in flight by airline carrier using integration between R and Spark

library(rsparkling)
## Warning: package 'rsparkling' was built under R version 3.4.2
library(sparklyr)
## Warning: package 'sparklyr' was built under R version 3.4.2
library(dplyr)
## Warning: package 'dplyr' was built under R version 3.4.2
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
config=spark_config()
sc <- spark_connect(master = "yarn-client", 
                    version = "2.2.0",
                    app_name = "sparklyr4",
                    spark_home = "/Users/olegbaydakov/Downloads/spark-2.2.0-bin-hadoop2.7/",
                    config = config)

3.1 Cache the tables into memory

Use tbl_cache to load the flights table into memory. Caching tables will make analysis much faster. Create a dplyr reference to the Spark DataFrame.

# Cache flights Hive table into Spark
tbl_cache(sc, 'flights')
flights_tbl <- tbl(sc, 'flights')

# Cache airlines Hive table into Spark
tbl_cache(sc, 'airlines')
airlines_tbl <- tbl(sc, 'airlines')

# Cache airports Hive table into Spark
tbl_cache(sc, 'airports')
airports_tbl <- tbl(sc, 'airports')

3.2 Create a model data set

Filter the data to contain only the records to be used in the fitted model. Join carrier descriptions for reference. Create a new variable called gain which represents the amount of time gained (or lost) in flight.

library(dplyr)
# Filter records and create target variable 'gain'
model_data <- flights_tbl %>%
  filter(!is.na(arrdelay) & !is.na(depdelay) & !is.na(distance)) %>%
  filter(depdelay > 15 & depdelay < 240) %>%
  filter(arrdelay > -60 & arrdelay < 360) %>%
  filter(year >= 2003 & year <= 2007) %>%
  left_join(airlines_tbl, by = c("uniquecarrier" = "code")) %>%
  mutate(gain = depdelay - arrdelay) %>%
  select(year, month, arrdelay, depdelay, distance, uniquecarrier, description, gain)

# Summarize data by carrier
model_data %>%
  group_by(uniquecarrier) %>%
  summarize(description = min(description), gain=mean(gain), 
            distance=mean(distance), depdelay=mean(depdelay)) %>%
  select(description, gain, distance, depdelay) %>%
  arrange(gain)

3.3 Train a linear model

Predict time gained or lost in flight as a function of distance, departure delay, and airline carrier.

# Partition the data into training and validation sets
model_partition <- model_data %>% 
  sdf_partition(train = 0.8, valid = 0.2, seed = 5555)

# Fit a linear model
ml1 <- model_partition$train %>%
  ml_linear_regression(gain ~ distance + depdelay + uniquecarrier)

# Summarize the linear model
summary(ml1)


3.4 Assess model performance

Compare the model performance using the validation data.

# Calculate average gains by predicted decile
model_deciles <- lapply(model_partition, function(x) {
  sdf_predict(ml1, x) %>%
    mutate(decile = ntile(desc(prediction), 10)) %>%
    group_by(decile) %>%
    summarize(gain = mean(gain)) %>%
    select(decile, gain) %>%
    collect()
})

# Create a summary dataset for plotting
deciles <- rbind(
  data.frame(data = 'train', model_deciles$train),
  data.frame(data = 'valid', model_deciles$valid),
  make.row.names = FALSE
)

# Plot average gains by predicted decile
deciles %>%
  ggplot(aes(factor(decile), gain, fill = data)) +
  geom_bar(stat = 'identity', position = 'dodge') +
  labs(title = 'Average gain by predicted decile', x = 'Decile', y = 'Minutes')

3.5 Visualize predictions

Compare actual gains to predicted gains for an out of time sample.

# Select data from an out of time sample
data_2008 <- flights_tbl %>%
  filter(!is.na(arrdelay) & !is.na(depdelay) & !is.na(distance)) %>%
  filter(depdelay > 15 & depdelay < 240) %>%
  filter(arrdelay > -60 & arrdelay < 360) %>%
  filter(year == 2008) %>%
  left_join(airlines_tbl, by = c("uniquecarrier" = "code")) %>%
  mutate(gain = depdelay - arrdelay) %>%
  select(year, month, arrdelay, depdelay, distance, uniquecarrier, description, gain, origin,dest)

# Summarize data by carrier
carrier <- sdf_predict(ml1, data_2008) %>%
  group_by(description) %>%
  summarize(gain = mean(gain), prediction = mean(prediction), freq = n()) %>%
  filter(freq > 10000) %>%
  collect

# Plot actual gains and predicted gains by airline carrier
p <- ggplot(carrier, aes(gain, prediction)) + 
  geom_point(alpha = 0.75, color = 'red', shape = 3) +
  geom_abline(intercept = 0, slope = 1, alpha = 0.15, color = 'blue') +
  geom_text(aes(label = substr(description, 1, 20)), size = 3, alpha = 0.75, vjust = -1) +
  labs(title='Average Gains Forecast', x = 'Actual', y = 'Predicted')
p

Some carriers make up more time than others in flight, but the differences are relatively small. The average time gains between the best and worst airlines is only six minutes. The best predictor of time gained is not carrier but flight distance. The biggest gains were associated with the longest flights.

3.6 Share Insights - deploy model to production

3.6.1 Build dashboard

Aggregate the scored data by origin, destination, and airline. Save the aggregated data.

setwd("~/Documents/airlines_dubai")
# Summarize by origin, destination, and carrier
summary_2008 <- sdf_predict(ml1, data_2008) %>%
  rename(carrier = uniquecarrier, airline = description) %>%
  group_by(origin, dest, carrier, airline) %>%
  summarize(
    flights = n(),
    distance = mean(distance),
    avg_dep_delay = mean(depdelay),
    avg_arr_delay = mean(arrdelay),
    avg_gain = mean(gain),
    pred_gain = mean(prediction)
    )

# Collect and save objects
pred_data <- collect(summary_2008)
airports <- collect(select(airports_tbl, name, faa, lat, lon))
ml1_summary <- capture.output(summary(ml1))
save(pred_data, airports, ml1_summary, file = 'flights_pred_2008.RData')

spark_disconnect(sc)

3.6.2 Publish the dashboard to shinyapps.io

The example of the interactive dashboard - click the link

4 Summary

The objective of our analysis was to investigate whether certain carriers gained time after a departure delay. We built a simplistic linear model against a subset of the data and then validated the model against a hold out group. Finally, we assessed the model against an out of time sample within the the whole dataset and created the interective dashboard to deploy the model to production

We found that distance was the most significant predictor of gained time. The longer the flight, the more time gained. We also found that carrier effects were significant, but less so than distance. Certain carriers had significant positive effects, which is evidence that some carriers gain time after a departure delay.

This model included only a few predictors and used a simple form. More sophisticated models using more predictors (e.g. adjusting for weather) might lead to more conclusive results. However, given the weak fit of this model the level of opportunity is questionable.

4.1 Next steps

  1. Transform the dataset to graph format (Spark GraphX DataFrames) using departure and arrival airports as a grpah vertexes and flights as the edges with metadata attachments (weather, delays etc).
  2. Run PageRank algorithm by counting the number and quality of flights to a airport to determine a rough estimate of how important the airport is
  3. Take top 5 most important airports and calculate the most common destinations in the dataset from airport to airport to . We can do this by performing a grouping operator and adding the edge counts together. This will yield a new graph except each edge will now be the sum of all of the semantically same edges
  4. Evaluate what impact the delays has on the departures from the same airport by calculating vertex “In Degrees”" and “Out Degrees”"
LS0tCnRpdGxlOiA8Y2VudGVyPiBBaXJsaW5lIG9uLXRpbWUgcGVyZm9ybWFuY2UgPC9jZW50ZXI+CnN1YnRpdGxlOiA8Y2VudGVyPiA8L2NlbnRlcj4KYXV0aG9yOiA8Y2VudGVyPiBPbGVnIEJheWRha292IDwvY2VudGVyPgpkYXRlOiA8Y2VudGVyPiBEZWNlbWJlciAxMCwgMjAxNyA8L2NlbnRlcj4Kb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OiAKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBqb3VybmFsCiAgICBkZl9wcmludDoga2FibGUKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCi0tLQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlPVRSVUUpCmBgYAoKPGNlbnRlcj4hW10obWFpbl9pbWFnZS5qcGcpeyB3aWR0aD04NSV9PC9jZW50ZXI+Cjxicj4KCiMgSW50cm9kdWN0aW9uCioqVGhlIGdvYWwgb2YgdGhlIHByb2plY3QqKiBpcyB0byBleGVjdXRlIGRhdGEgc2NpZW5jZSBleHBsb3JhdGlvbiBzdGVwcyBhZ2FpbnN0IGZsaWdodHMgZGF0YSAgYW5kIGNyZWF0ZSBhIHJlZ3Jlc3Npb24gbW9kZWwgdG8gcHJlZGljdCB3aGF0IGZsaWdodCB3aWxsIGJlIGRlbGF5ZWQKClRoZSBkYXRhIGNvbnNpc3RzIG9mIGZsaWdodCBhcnJpdmFsIGFuZCBkZXBhcnR1cmUgZGV0YWlscyBmb3IgYWxsIGNvbW1lcmNpYWwgZmxpZ2h0cyB3aXRoaW4gdGhlIFVTQSwgZnJvbSBPY3RvYmVyIDE5ODcgdG8gQXByaWwgMjAwOC4gIAoKRm9yIG15IGFuYWx5c2lzIEkgd2lsbCBiZSB1c2luZyAgZGF0YSBmb3IgMjAwNy0yMDA4IHllYXJzIG9ubHkuIERhdGEgZm9yIDIwMDcgd2lsbCBiZSBzcGxpdHRlZCBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMsIGRhdGEgZm9yIDIwMDggd2lsbCBzZXJ2ZSBhcyB2YWxpZGF0aW9uIHNldCB0byBldmFsdWF0ZSB0aGUgbW9kZWwgcGVyZm9ybWFuY2UKCiMgQmF0Y2ggSW5nZXN0aW9uICYgUHJvY2Vzc2luZyAKCiBUaGUgZmlyc3Qgc3RlcCBpcyB0byBzZXQgdXAgQmlnIERhdGEgZW52aXJvbmVtbnQgdXNpbmcgdGhlIGZvbGxvd2luZyB0ZWNobm9sb2d5IHN0YWNrIC0gSERGUywgWUFSTiwgQXBhY2hlIFNwYXJrLCBBcGFjaGUga2Fma2EgYW5kIEFwYWNoZSBGbHVtZQogCiMjIEJpZyBEYXRhIGxvY2FsIGVudmlyb25tZW50IChNYWMgT1MpIHsudGFic2V0fQoKIyMjIEhERlMKU3RhcnQgSGFkb29wIHN0YW5kYWxvbmUgY2x1c3RlciAKCmBgYHtiYXNoIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY2QgL3Vzci9sb2NhbC9DZWxsYXIvaGFkb29wLzIuOC4xLwouL3NiaW4vc3RhcnQtZGZzLnNoICYmIApgYGAKPGNlbnRlcj4hW10oTmFtZW5vZGVfaW5mb3JtYXRpb24ucG5nKXsgd2lkdGg9NjAlfTwvY2VudGVyPgoKIyMjIFlBUk4KU3RhcnQgWUFSTiBtYW5hZ2VyCmBgYHtiYXNoIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KLi9zYmluL3N0YXJ0LXlhcm4uc2gKYGBgCjxjZW50ZXI+IVtdKHlhcm4ucG5nKXsgd2lkdGg9MTAwJX08L2NlbnRlcj4KCiMjIyBBcGFjaGUgU3BhcmsKU3RhcnQgU3BhcmsgY2x1c3RlciB3aXRoIDIgd29ya2VycwpgYGB7YmFzaCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNkIC9Vc2Vycy9vbGVnYmF5ZGFrb3YvRG93bmxvYWRzL3NwYXJrLTIuMi4wLWJpbi1oYWRvb3AyLjcKLi9zYmluL3N0YXJ0LW1hc3Rlci5zaCAtaSAkU1BBUktfTE9DQUxfSVAgCi4vc2Jpbi9zdGFydC1zbGF2ZS5zaCBzcGFyazovLyRTUEFSS19MT0NBTF9JUDo3MDc3CmBgYAohW10oc3BhcmsucG5nKXsgd2lkdGg9MTAwJX0KCiMjIyBLYWZrYSBhbmQgWm9va2VwZXIKU3RhcnQgem9va2VwZXIgc2VydmVyCmBgYHtiYXNoIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KYmluL3pvb2tlZXBlci1zZXJ2ZXItc3RhcnQuc2ggY29uZmlnL3pvb2tlZXBlci5wcm9wZXJ0aWVzCmBgYApTdGFydCBBcGFjaGUgS2Fma2EgY2x1c2VyIGNvbnNpc3RpbmcgZnJvbSAzIGluc3RhbmNlcwpgYGB7YmFzaCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmJpbi9rYWZrYS1zZXJ2ZXItc3RhcnQuc2ggY29uZmlnL3NlcnZlci5wcm9wZXJ0aWVzCmJpbi9rYWZrYS1zZXJ2ZXItc3RhcnQuc2ggY29uZmlnL3NlcnZlci0xLnByb3BlcnRpZXMKYmluL2thZmthLXNlcnZlci1zdGFydC5zaCBjb25maWcvc2VydmVyLTIucHJvcGVydGllcwpgYGAKCgojIyBEYXRhIHByZXBhcmF0aW9uCiMjIyBDcmVhdGUgS2Fma2EgdG9waWNzCmBgYHtiYXNoIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0Kc2gga2Fma2EtdG9waWNzIC0tY3JlYXRlIC0tem9va2VlcGVyIGxvY2FsaG9zdDoyMTgxIC0tcmVwbGljYXRpb24tZmFjdG9yIDIgLS1wYXJ0aXRpb25zIDIgLS10b3BpYyBhaXJwb3J0cwoKc2gga2Fma2EtdG9waWNzIC0tY3JlYXRlIC0tem9va2VlcGVyIGxvY2FsaG9zdDoyMTgxIC0tcmVwbGljYXRpb24tZmFjdG9yIDIgLS1wYXJ0aXRpb25zIDIgLS10b3BpYyBwbGFuZWRhdGUKCnNoIGthZmthLXRvcGljcyAtLWNyZWF0ZSAtLXpvb2tlZXBlciBsb2NhbGhvc3Q6MjE4MSAtLXJlcGxpY2F0aW9uLWZhY3RvciAyIC0tcGFydGl0aW9ucyAyIC0tdG9waWMgY2FycmllcnMKCnNoIGthZmthLXRvcGljcyAtLWxpc3QgLS16b29rZWVwZXIgbG9jYWxob3N0OjIxODEKYGBgCgojIyMgRG93bmxvYWQgZGF0YQpSdW4gdGhlIGZvbGxvd2luZyBzY3JpcHQgdG8gZG93bmxvYWQgZGF0YSBmcm9tIHRoZSB3ZWIgb250byBsb2NhbCBmb2xkZXIuIERvd25sb2FkIHRoZSB5ZWFybHkgZmxpZ2h0IGRhdGEgYW5kIHRoZSBhaXJsaW5lcyBsb29rdXAgdGFibGUuIApgYGB7YmFzaCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CiMgTWFrZSBkb3dubG9hZCBkaXJlY3RvcnkKbWtkaXIgL3RtcC9mbGlnaHRzCgojIERvd25sb2FkIGZsaWdodCBkYXRhIGJ5IHllYXIKZm9yIGkgaW4gezE5ODcuLjIwMDh9CiAgZG8KICAgIGVjaG8gIiQoZGF0ZSkgJGkgRG93bmxvYWQiCiAgICBmbmFtPSRpLmNzdi5iejIKICAgIHdnZXQgLU8gL3RtcC9mbGlnaHRzLyRmbmFtIGh0dHA6Ly9zdGF0LWNvbXB1dGluZy5vcmcvZGF0YWV4cG8vMjAwOS8kZm5hbQogICAgZWNobyAiJChkYXRlKSAkaSBVbnppcCIKICAgIGJ1bnppcDIgL3RtcC9mbGlnaHRzLyRmbmFtCiAgZG9uZQoKIyBEb3dubG9hZCBhaXJsaW5lIGNhcnJpZXIgZGF0YQp3Z2V0IC1PIC90bXAvYWlybGluZXMuY3N2IGh0dHA6Ly93d3cudHJhbnN0YXRzLmJ0cy5nb3YvRG93bmxvYWRfTG9va3VwLmFzcD9Mb29rdXA9TF9VTklRVUVfQ0FSUklFUlMKCiMgRG93bmxvYWQgYWlycG9ydHMgZGF0YQp3Z2V0IC1PIC90bXAvYWlycG9ydHMuY3N2IGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qcGF0b2thbC9vcGVuZmxpZ2h0cy9tYXN0ZXIvZGF0YS9haXJwb3J0cy5kYXQKCmBgYAohW10oZGF0YV9kb3dubG9hZC5wbmcpeyB3aWR0aD0xMDAlfQoKIyMjIFN1Ym1pdCBkYXRhIHRvIEthZmthIHRvcGljcwpgYGB7YmFzaCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9Ci8vIGthZmthIHByb2R1Y2VyCmJpbi9rYWZrYS1jb25zb2xlLXByb2R1Y2VyLnNoIC0tYnJva2VyLWxpc3QgbG9jYWxob3N0OjkwOTIgLS10b3BpYyBwbGFuZWRhdGUgPCB+L3RtcC8yMDA4LmNzdgpiaW4va2Fma2EtY29uc29sZS1wcm9kdWNlci5zaCAtLWJyb2tlci1saXN0IGxvY2FsaG9zdDo5MDkyIC0tdG9waWMgYWlycG9ydHMgPCB+L3RtcC9haXJwb3J0cy5jc3YKYmluL2thZmthLWNvbnNvbGUtcHJvZHVjZXIuc2ggLS1icm9rZXItbGlzdCBsb2NhbGhvc3Q6OTA5MiAtLXRvcGljIGNhcnJpZXJzIDwgfi90bXAvYWlybGluZXMuY3N2CmBgYAoKIyMgQmF0Y2ggaW5nZXN0aW9uIChIREZTKQojIyMgQ3JlYXRlIGZvbGRlcnMgaW4gSERGUzoKYGBge2Jhc2ggZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQpoYWRvb3AgZnMgLW1rZGlyIC9kYXRhL3JhdwpoYWRvb3AgZnMgLW1rZGlyIC9kYXRhL21vZGVsbGVkCmhhZG9vcCBmcyAtbWtkaXIgL2RhdGEvZGVjb21wb3NlZAoKaGFkb29wIGZzIC1ta2RpciAvZGF0YS9yYXcvYWlycG9ydHMKaGFkb29wIGZzIC1ta2RpciAvZGF0YS9yYXcvYWlybGluZXMKaGFkb29wIGZzIC1ta2RpciAvZGF0YS9yYXcvZmxpZ2h0cwoKaGFkb29wIGZzIC1ta2RpciAvZGF0YS9kZWNvbXBvc2VkL2FpcnBvcnRzCmhhZG9vcCBmcyAtbWtkaXIgL2RhdGEvZGVjb21wb3NlZC9haXJsaW5lcwpoYWRvb3AgZnMgLW1rZGlyIC9kYXRhL2RlY29tcG9zZWQvZmxpZ2h0cwoKaGFkb29wIGZzIC1ta2RpciAvZGF0YS9tb2RlbGxlZC9haXJwb3J0cwpoYWRvb3AgZnMgLW1rZGlyIC9kYXRhL21vZGVsbGVkL2FpcmxpbmVzCmhhZG9vcCBmcyAtbWtkaXIgL2RhdGEvbW9kZWxsZWQvZmxpZ2h0cwoKYGBgCgojIyMgRmx1bWUgY29uZmlndXJhdGlvbiB7LnRhYnNldH0KQ3JlYXRlIEZsdW1lIGNvbmZpZ3VyYXRpb24gZmlsZXMgZm9yIGFpcnBvcnRzIGFuZCBwbGFuZWRhdGUgdG9wY2lzIHRvIGJlIGluZ2VzdGVkIGludG8gSERGUyAocmF3IHpvbmUpCgojIyMjIEFpcnBvcnRzCmBgYHtiYXNoIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KYTEuc291cmNlcy5yMS56b29rZWVwZXJDb25uZWN0PWxvY2FsaG9zdDoyMTgxCmExLnNvdXJjZXMucjEudG9waWM9YWlycG9ydHMKCmExLnNpbmtzLmsxLnR5cGUgPSBoZGZzCmExLnNpbmtzLmsxLmhkZnMud3JpdGVGb3JtYXQ9VGV4dAphMS5zaW5rcy5rMS5oZGZzLmZpbGVUeXBlPURhdGFTdHJlYW0KYTEuc2lua3MuazEuaGRmcy5wYXRoPS9kYXRhL3Jhdy9haXJwb3J0cy8KYTEuc2lua3MuazEuaGRmcy5maWxlUHJlZml4PWFpcnBvcnRzCmExLnNpbmtzLmsxLmhkZnMucm9sbENvdW50PTEwMDAwMAphMS5zaW5rcy5rMS5oZGZzLnJvbGxTaXplPTAKCiMgVXNlIGEgY2hhbm5lbCB3aGljaCBidWZmZXJzIGV2ZW50cyBpbiBtZW1vcnkKYTEuY2hhbm5lbHMuYzEudHlwZSA9IG1lbW9yeQphMS5jaGFubmVscy5jMS5jYXBhY2l0eSA9IDEwMDAwCmExLmNoYW5uZWxzLmMxLnRyYW5zYWN0aW9uQ2FwYWNpdHkgPSAxMDAwMAoKIyBCaW5kIHRoZSBzb3VyY2UgYW5kIHNpbmsgdG8gdGhlIGNoYW5uZWwKYTEuc291cmNlcy5yMS5jaGFubmVscyA9IGMxCmExLnNpbmtzLmsxLmNoYW5uZWwgPSBjMQpgYGAKIyMjIyBQbGFuZWRhdGUKYGBge2Jhc2ggZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQojIE5hbWUgdGhlIGNvbXBvbmVudHMgb24gdGhpcyBhZ2VudAphMS5zb3VyY2VzID0gcjEKYTEuc2lua3MgPSBrMQphMS5jaGFubmVscyA9IGMxCgphMS5zb3VyY2VzLnIxLnR5cGU9b3JnLmFwYWNoZS5mbHVtZS5zb3VyY2Uua2Fma2EuS2Fma2FTb3VyY2UKYTEuc291cmNlcy5yMS56b29rZWVwZXJDb25uZWN0PWxvY2FsaG9zdDoyMTgxCmExLnNvdXJjZXMucjEudG9waWM9cGxhbmVkYXRlCgphMS5zaW5rcy5rMS50eXBlID0gaGRmcwphMS5zaW5rcy5rMS5oZGZzLndyaXRlRm9ybWF0PVRleHQKYTEuc2lua3MuazEuaGRmcy5maWxlVHlwZT1EYXRhU3RyZWFtCmExLnNpbmtzLmsxLmhkZnMucGF0aD0vZGF0YS9yYXcvcGxhbmVkYXRlLwphMS5zaW5rcy5rMS5oZGZzLmZpbGVQcmVmaXg9cGxhbmVkYXRlCmExLnNpbmtzLmsxLmhkZnMucm9sbENvdW50PTEwMDAwMDAwCmExLnNpbmtzLmsxLmhkZnMucm9sbFNpemU9MAoKIyBVc2UgYSBjaGFubmVsIHdoaWNoIGJ1ZmZlcnMgZXZlbnRzIGluIG1lbW9yeQphMS5jaGFubmVscy5jMS50eXBlID0gbWVtb3J5CmExLmNoYW5uZWxzLmMxLmNhcGFjaXR5ID0gMTAwMDAKYTEuY2hhbm5lbHMuYzEudHJhbnNhY3Rpb25DYXBhY2l0eSA9IDEwMDAwCgojIEJpbmQgdGhlIHNvdXJjZSBhbmQgc2luayB0byB0aGUgY2hhbm5lbAphMS5zb3VyY2VzLnIxLmNoYW5uZWxzID0gYzEKYTEuc2lua3MuazEuY2hhbm5lbCA9IGMxCmBgYAoKIyMjIEthZmthIGFuZCBTcGFyayBzdHJ1Y3R1cmVkIHN0cmVhbWluZyBpbnRlZ3JhdGlvbiB7LnRhYnNldH0KU2NhbGEgY29kZSB0byByZWFkIGRhdGEgZnJvbSBLYWZrYSB0b3BpYyB1c2luZyBTcGFyayBzdHJ1Y3R1cmVkIHN0cmVhbWluZwoKKyBTY2FsYSB0cmFpdCB0byBpbml0aWFsaXplIFNwYXJrCgorIE1haW4gb2JqZWN0IHRvIHB1bGwgZGF0YSBmcm9tIEthZmFrYSB0b3BpYyBhbmQgaW5nZXN0IGludG8gSERGUwoKIyMjIyBTcGFya0luaXQuc2NhbGEKYGBge3B5dGhvbiBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhY2thZ2Uga2Fma2Ffc3RyZWFtCgppbXBvcnQgb3JnLmFwYWNoZS5oYWRvb3AueWFybi51dGlsLlJhY2tSZXNvbHZlcgppbXBvcnQgb3JnLmFwYWNoZS5sb2c0ai57TGV2ZWwsIExvZ01hbmFnZXIsIExvZ2dlcn0KaW1wb3J0IG9yZy5hcGFjaGUuc3Bhcmsuc3FsLlNwYXJrU2Vzc2lvbgoKdHJhaXQgSW5pdFNwYXJrIHsKICBMb2dnZXIuZ2V0TG9nZ2VyKGNsYXNzT2ZbUmFja1Jlc29sdmVyXSkuZ2V0TGV2ZWwKICBMb2dnZXIuZ2V0TG9nZ2VyKCJvcmciKS5zZXRMZXZlbChMZXZlbC5PRkYpCiAgTG9nZ2VyLmdldExvZ2dlcigiYWtrYSIpLnNldExldmVsKExldmVsLk9GRikKCiAgdmFsIHNwYXJrOiBTcGFya1Nlc3Npb24gPSBTcGFya1Nlc3Npb24uYnVpbGRlcigpCiAgICAuYXBwTmFtZSgiU3BhcmsgU3RydWN0dXJlZCBTdHJlYW1pbmcgRXhhbXBsZSIpCiAgICAvLy5tYXN0ZXIoImxvY2FsWypdIikKICAgIC5tYXN0ZXIoInNwYXJrOi8vMTI3LjAuMDE6NzA3NyIpCiAgICAuZ2V0T3JDcmVhdGUoKQoKICB2YWwgc2MgPSBzcGFyay5zcGFya0NvbnRleHQKICB2YWwgc3FsQ29udGV4dCA9IHNwYXJrLnNxbENvbnRleHQKCiAgZGVmIHJlYWRlciA9IHNwYXJrLnJlYWQKICAgIC5vcHRpb24oImhlYWRlciIsdHJ1ZSkKICAgIC5vcHRpb24oImluZmVyU2NoZW1hIiwgZmFsc2UpCiAgICAub3B0aW9uKCJtb2RlIiwgIkRST1BNQUxGT1JNRUQiKQogICAgLm9wdGlvbigiZGF0ZUZvcm1hdCIsICJkZC9NTS95eXl5IikKCiAgZGVmIHJlYWRlcldpdGhvdXRIZWFkZXIgPSBzcGFyay5yZWFkCiAgICAub3B0aW9uKCJoZWFkZXIiLHRydWUpCiAgICAub3B0aW9uKCJpbmZlclNjaGVtYSIsIGZhbHNlKQogICAgLm9wdGlvbigibW9kZSIsICJEUk9QTUFMRk9STUVEIikKICAgIC5vcHRpb24oImRhdGVGb3JtYXQiLCAiTU0vZGQveXl5eSIpCgoKCiAgZGVmIGNsb3NlID0gewogICAgc3BhcmsuY2xvc2UoKQogIH0KfQpgYGAKIyMjIyBLYWZrYVN0cmVhbS5zY2FsYQpgYGB7cHl0aG9uIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KcGFja2FnZSBrYWZrYV9zdHJlYW0KCmltcG9ydCBvcmcuYXBhY2hlLmhhZG9vcC55YXJuLnV0aWwuUmFja1Jlc29sdmVyCmltcG9ydCBvcmcuYXBhY2hlLnNwYXJrLnNxbC57RGF0YXNldCwgU2F2ZU1vZGUsIFNwYXJrU2Vzc2lvbn0KaW1wb3J0IG9yZy5hcGFjaGUuc3Bhcmsuc3FsLnN0cmVhbWluZy57T3V0cHV0TW9kZSwgUHJvY2Vzc2luZ1RpbWV9CgppbXBvcnQgb3JnLmFwYWNoZS5zcGFyay5zdHJlYW1pbmcua2Fma2EuS2Fma2FVdGlscwppbXBvcnQga2Fma2Euc2VyaWFsaXplci5TdHJpbmdEZWNvZGVyCgppbXBvcnQgb3JnLmFwYWNoZS5zcGFyay5zdHJlYW1pbmcuXwoKaW1wb3J0IG9yZy5hcGFjaGUuc3Bhcmsuc3RyZWFtaW5nLkR1cmF0aW9ucwppbXBvcnQgb3JnLmFwYWNoZS5sb2c0ai57TGV2ZWwsIExvZ2dlcn0KCm9iamVjdCBrYWZrYV9zdHJlYW0gZXh0ZW5kcyBJbml0U3BhcmsgewoKICBpbXBvcnQgc3BhcmsuaW1wbGljaXRzLl8KCiAgZGVmIG1haW4oYXJnczogQXJyYXlbU3RyaW5nXSk6IFVuaXQgPSB7CgogICAgTG9nZ2VyLmdldExvZ2dlcihjbGFzc09mW1JhY2tSZXNvbHZlcl0pLmdldExldmVsCiAgICBMb2dnZXIuZ2V0TG9nZ2VyKCJvcmciKS5zZXRMZXZlbChMZXZlbC5PRkYpCiAgICBMb2dnZXIuZ2V0TG9nZ2VyKCJha2thIikuc2V0TGV2ZWwoTGV2ZWwuT0ZGKQogICAgCiAgICByZWFkX2Zyb21fa2Fma2EoKQogICAgfQoKIGRlZiByZWFkX2Zyb21fa2Fma2EoKTpVbml0ID17CiAgICBpbXBvcnQgc3BhcmsuaW1wbGljaXRzLl8KICAgCiAgIC8vIFJFQUQgRlJPTSBLQUZLQSBUT1BJQwogICAgdmFsIGRmID0gc3BhcmsKICAgICAgLnJlYWRTdHJlYW0KICAgICAgLmZvcm1hdCgia2Fma2EiKQogICAgICAub3B0aW9uKCJrYWZrYS5ib290c3RyYXAuc2VydmVycyIsICJsb2NhbGhvc3Q6OTA5MiIpCiAgICAgIC5vcHRpb24oInN1YnNjcmliZSIsICJjYXJyaWVycyIpCiAgICAgIC5vcHRpb24oInN0YXJ0aW5nT2Zmc2V0cyIsICJlYXJsaWVzdCIpCiAgICAgIC5vcHRpb24oImZhaWxPbkRhdGFMb3NzIiwgImZhbHNlIikKICAgICAgLmxvYWQoKQoKICAgIHZhbCB2YWx1ZXMgPSBkZi5zZWxlY3RFeHByKCJDQVNUKHZhbHVlIEFTIFNUUklORykiKS5hc1tTdHJpbmddCiAgICAKICAgLy8gV1JJVEUgVE8gSERGUwogICB2YWwgcXVlcnkgPSB2YWx1ZXMgLy8ub3JkZXJCeSgid2luZG93IikKICAgICAgLnJlcGFydGl0aW9uKDEpCiAgICAgIC53cml0ZVN0cmVhbQogICAgICAub3V0cHV0TW9kZShPdXRwdXRNb2RlLkFwcGVuZCgpKQogICAgICAuZm9ybWF0KCJjc3YiKQogICAgICAub3B0aW9uKCJjaGVja3BvaW50TG9jYXRpb24iLCAiaGRmczovL2xvY2FsaG9zdDo4MDIwL3RtcC9jaGVja3BvaW50cyIpCiAgICAgIC5vcHRpb24oInBhdGgiLCAiaGRmczovL2xvY2FsaG9zdDo4MDIwL2RhdGEvcmF3L2NhcnJpZXJzIikKICAgICAgLnN0YXJ0KCkKICAgICAgLmF3YWl0VGVybWluYXRpb24oKQogfQp9CgpgYGAKIyMjIyBidWlsZC5zYnQKYGBge3B5dGhvbiBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9Cm5hbWUgOj0gIkthZmFrU3BhcmsiCgp2ZXJzaW9uIDo9ICIxLjAiCgpzY2FsYVZlcnNpb24gOj0gIjIuMTEuOCIKCmxpYnJhcnlEZXBlbmRlbmNpZXMgKys9IFNlcSgKICAib3JnLmFwYWNoZS5zcGFyayIgJSAic3BhcmstY29yZV8yLjExIiAlICIyLjIuMCIgLAogICJvcmcuYXBhY2hlLnNwYXJrIiAlICJzcGFyay1zcWxfMi4xMSIgJSAiMi4yLjAiICwKICAib3JnLmFwYWNoZS5zcGFyayIgJSAic3Bhcmstc3RyZWFtaW5nXzIuMTEiICUgIjIuMi4wIiwKICAib3JnLmFwYWNoZS5zcGFyayIgJSAic3Bhcmstc3RyZWFtaW5nLWthZmthXzIuMTEiICUgIjEuNi4zIgopCgovLyBodHRwczovL212bnJlcG9zaXRvcnkuY29tL2FydGlmYWN0L29yZy5hcGFjaGUua2Fma2Eva2Fma2FfMi4xMQpsaWJyYXJ5RGVwZW5kZW5jaWVzICs9ICJvcmcuYXBhY2hlLmthZmthIiAlICJrYWZrYV8yLjExIiAlICIwLjExLjAuMCIKCi8vIGh0dHBzOi8vbXZucmVwb3NpdG9yeS5jb20vYXJ0aWZhY3Qvb3JnLmFwYWNoZS5rYWZrYS9rYWZrYS1jbGllbnRzCmxpYnJhcnlEZXBlbmRlbmNpZXMgKz0gIm9yZy5hcGFjaGUua2Fma2EiICUgImthZmthLWNsaWVudHMiICUgIjAuMTEuMC4wIgoKLy8gaHR0cHM6Ly9tdm5yZXBvc2l0b3J5LmNvbS9hcnRpZmFjdC9vcmcuYXBhY2hlLmthZmthL2thZmthLXN0cmVhbXMKbGlicmFyeURlcGVuZGVuY2llcyArPSAib3JnLmFwYWNoZS5rYWZrYSIgJSAia2Fma2Etc3RyZWFtcyIgJSAiMC4xMS4wLjAiCgovLyBodHRwczovL212bnJlcG9zaXRvcnkuY29tL2FydGlmYWN0L29yZy5hcGFjaGUua2Fma2EvY29ubmVjdC1hcGkKbGlicmFyeURlcGVuZGVuY2llcyArPSAib3JnLmFwYWNoZS5rYWZrYSIgJSAiY29ubmVjdC1hcGkiICUgIjAuMTEuMC4wIgoKbGlicmFyeURlcGVuZGVuY2llcyArPSAiY29tLmRhdGFicmlja3MiICUlICJzcGFyay1hdnJvIiAlICI0LjAuMCIKCnJlc29sdmVycyArPSBSZXNvbHZlci5tYXZlbkxvY2FsCnJlc29sdmVycyArPSAiY2VudHJhbCBtYXZlbiIgYXQgImh0dHBzOi8vcmVwbzEubWF2ZW4ub3JnL21hdmVuMi8iCmBgYAoKIyMgQmF0Y2ggcHJvY2Vzc2luZyAgcmF3IC0+IGRlY29tcG9zZWQgLT4gbW9kZWxsZWQgKEhERlMpCkNyZWF0ZSBleHRlcm5hbCBIaXZlIHRhYmxlcyBpbiBldmVyeSBIREZTIHpvbmUuCgojIyMgUmF3IHpvbmUgIHsudGFic2V0fQoKIyMjIyBBaXJwb3J0cwpgYGB7c3FsIGV2YWw9RkFMU0UsICBpbmNsdWRlPVRSVUV9CkNSRUFURSBFWFRFUk5BTCBUQUJMRSBJRiBOT1QgRVhJU1RTIGFpcnBvcnRzKAppYXRhIFNUUklORywKYWlycG9ydCBTVFJJTkcsCmNpdHkgU1RSSU5HLApzdGF0ZSBTVFJJTkcsCmNvdW50cnkgU1RSSU5HLApsYXQgU1RSSU5HLApsb25ndCBTVFJJTkcpClJPVyBGT1JNQVQgREVMSU1JVEVECiAgICBGSUVMRFMgVEVSTUlOQVRFRCBCWSAnLCcKICAgIFNUT1JFRCBBUyBURVhURklMRQogICAgTE9DQVRJT04gJy9kYXRhL3Jhdy9haXJwb3J0cycKICAgdGJscHJvcGVydGllcyAoInNraXAuaGVhZGVyLmxpbmUuY291bnQiPSIxIikgCiAgICA7CgpgYGAKIyMjIyBDYXJyaWVycyAoYWlybGluZXMpCmBgYHtzcWwgZXZhbD1GQUxTRSwgIGluY2x1ZGU9VFJVRX0KQ1JFQVRFIEVYVEVSTkFMIFRBQkxFIElGIE5PVCBFWElTVFMgYWlybGluZXMKKApDb2RlIHN0cmluZywKRGVzY3JpcHRpb24gc3RyaW5nCikKUk9XIEZPUk1BVCBTRVJERSAnb3JnLmFwYWNoZS5oYWRvb3AuaGl2ZS5zZXJkZTIuT3BlbkNTVlNlcmRlJwpXSVRIIFNFUkRFUFJPUEVSVElFUwooCiJzZXBhcmF0b3JDaGFyIiA9ICdcLCcsCiJxdW90ZUNoYXIiICAgICA9ICdcIicKKQpTVE9SRUQgQVMgVEVYVEZJTEUKIExPQ0FUSU9OICcvZGF0YS9yYXcvY2FycmllcnMnCnRibHByb3BlcnRpZXMoInNraXAuaGVhZGVyLmxpbmUuY291bnQiPSIxIik7CmBgYAojIyMjIFBsYW5lZGF0ZSAoZmxpZ2h0cykgCmBgYHtzcWwgZXZhbD1GQUxTRSwgIGluY2x1ZGU9VFJVRX0KZHJvcCB0YWJsZSBpZiBleGlzdHMgZmxpZ2h0czsKQ1JFQVRFIEVYVEVSTkFMIFRBQkxFIElGIE5PVCBFWElTVFMgZmxpZ2h0cygKIHllYXIgU1RSSU5HLAogbW9udGggU1RSSU5HLAogZGF5b2Ztb250aCBTVFJJTkcsCiBkYXlvZndlZWsgU1RSSU5HLAogZGVwdGltZSBTVFJJTkcsCiBjcnNkZXB0aW1lIFNUUklORywKIGFycnRpbWUgU1RSSU5HLAogY3JzYXJydGltZSBTVFJJTkcsCiB1bmlxdWVjYXJyaWVyIFNUUklORywKIGZsaWdodG51bSBTVFJJTkcsCiB0YWlsbnVtIFNUUklORywKIGFjdHVhbGVsYXBzZWR0aW1lIFNUUklORywKIGNyc2VsYXBzZWR0aW1lIFNUUklORywKIGFpcnRpbWUgU1RSSU5HLAogYXJyZGVsYXkgU1RSSU5HLAogZGVwZGVsYXkgU1RSSU5HLAogb3JpZ2luIFNUUklORywKIGRlc3QgU1RSSU5HLAogZGlzdGFuY2UgU1RSSU5HLAogdGF4aWluIFNUUklORywKIHRheGlvdXQgU1RSSU5HLAogY2FuY2VsbGVkIFNUUklORywKIGNhbmNlbGxhdGlvbmNvZGUgU1RSSU5HLAogZGl2ZXJ0ZWQgU1RSSU5HLAogY2FycmllcmRlbGF5IFNUUklORywKIHdlYXRoZXJkZWxheSBTVFJJTkcsCiBuYXNkZWxheSBTVFJJTkcsCiBzZWN1cml0eWRlbGF5IFNUUklORywKIGxhdGVhaXJjcmFmdGRlbGF5IFNUUklORykKUk9XIEZPUk1BVCBERUxJTUlURUQKICAgIEZJRUxEUyBURVJNSU5BVEVEIEJZICcsJwogICAgU1RPUkVEIEFTIFRFWFRGSUxFCiAgICBMT0NBVElPTiAnL2RhdGEvcmF3L3BsYW5lZGF0ZScKICAgdGJscHJvcGVydGllcyAoInNraXAuaGVhZGVyLmxpbmUuY291bnQiPSIxIikgCiAgICA7CmBgYAoKIyMjIERlY29tcG9zZWQgem9uZSAgey50YWJzZXR9CgpJIHVzZWQgSGl2ZSBTUWwgdG8gYWRkIGFkZGl0aW9uYWwgY29sdW1ucyBhbmQgY29udmVydCBIaXZlIHRhYmxlcyBmcm9tIENTViB0byBBdnJvIHNpbmNlIEFwYWNoZSBQaWcgaXMgcHJldHR5IHNsb3cgYW5kIGluIG15IG9waW5pb24gaXQncyAgcHJldHR5IG91dGRhdGVkIGZvciB1c2luZyBpbiBwcm9kdWN0aW9uLgoKIyMjIyBBaXJwb3J0cwpgYGB7c3FsIGV2YWw9RkFMU0UsICBpbmNsdWRlPVRSVUV9CnVzZSBkZWNvbXBvc2VkOwpkcm9wIHRhYmxlIGlmIGV4aXN0cyBhaXJwb3J0czsKQ1JFQVRFIEVYVEVSTkFMIFRBQkxFIElGIE5PVCBFWElTVFMgYWlycG9ydHMoCmlhdGEgU1RSSU5HLAphaXJwb3J0IFNUUklORywKY2l0eSBTVFJJTkcsCnN0YXRlIFNUUklORywKY291bnRyeSBTVFJJTkcsCmxhdCBTVFJJTkcsCmxvbmd0IFNUUklORywKIGluc2VydF90aW1lIHRpbWVzdGFtcCwKIHV1aWQgc3RyaW5nCikKIFNUT1JFRCBBUyBBVlJPCiAgICBMT0NBVElPTiAnL2RhdGEvZGVjb21wb3NlZC9haXJwb3J0cyc7CgogdXNlIGRlY29tcG9zZWQ7CklOU0VSVCBPVkVSV1JJVEUgVEFCTEUgYWlycG9ydHMgU0VMRUNUIApSRUdFWFBfUkVQTEFDRShhaXJwb3J0LCciJywnJyksUkVHRVhQX1JFUExBQ0UoYWlycG9ydCwnIicsJycpLFJFR0VYUF9SRVBMQUNFKGNpdHksJyInLCcnKSwKUkVHRVhQX1JFUExBQ0Uoc3RhdGUsJyInLCcnKSxSRUdFWFBfUkVQTEFDRShjb3VudHJ5LCciJywnJykgLGxhdCxsb25nIGFzIGxvbmd0ICAsIENVUlJFTlRfVElNRVNUQU1QIEFTIGluc2VydF90aW1lLCAKcmVmbGVjdCgiamF2YS51dGlsLlVVSUQiLCAicmFuZG9tVVVJRCIpIGFzIHV1aWQgRlJPTSBkZWZhdWx0LmFpcnBvcnRzOwpgYGAKCiMjIyMgQ2FycmllcnMoYWlybGluZXMpCmBgYHtzcWwgZXZhbD1GQUxTRSwgIGluY2x1ZGU9VFJVRX0KdXNlIGRlY29tcG9zZWQ7CmRyb3AgdGFibGUgaWYgZXhpc3RzIGFpcmxpbmVzOwpDUkVBVEUgRVhURVJOQUwgVEFCTEUgSUYgTk9UIEVYSVNUUyBhaXJsaW5lcygKICAgIENvZGUgU1RSSU5HLCBEZXNjcmlwdGlvbiBTVFJJTkcsCiAgICBpbnNlcnRfdGltZSB0aW1lc3RhbXAsCiAgICB1dWlkIHN0cmluZwogICApCiAgICBTVE9SRUQgQVMgQVZSTwogICAgTE9DQVRJT04gJy9kYXRhL2RlY29tcG9zZWQvY2FycmllcnMnOwoKdXNlIGRlY29tcG9zZWQ7CklOU0VSVCBPVkVSV1JJVEUgVEFCTEUgYWlybGluZXMgU0VMRUNUICogLCBDVVJSRU5UX1RJTUVTVEFNUCBBUyBpbnNlcnRfdGltZSwgCnJlZmxlY3QoImphdmEudXRpbC5VVUlEIiwgInJhbmRvbVVVSUQiKSBhcyB1dWlkIEZST00gZGVmYXVsdC5haXJsaW5lczsKYGBgCiMjIyMgUGxhbmVkYXRlKGZsaWdodHMpCmBgYHtzcWwgZXZhbD1GQUxTRSwgIGluY2x1ZGU9VFJVRX0KdXNlIGRlY29tcG9zZWQ7CmRyb3AgdGFibGUgaWYgZXhpc3RzIGZsaWdodHM7CkNSRUFURSBFWFRFUk5BTCBUQUJMRSBJRiBOT1QgRVhJU1RTIGZsaWdodHMoCiB5ZWFyIFNUUklORywKIG1vbnRoIFNUUklORywKIGRheW9mbW9udGggU1RSSU5HLAogZGF5b2Z3ZWVrIFNUUklORywKIGRlcHRpbWUgU1RSSU5HLAogY3JzZGVwdGltZSBTVFJJTkcsCiBhcnJ0aW1lIFNUUklORywKIGNyc2FycnRpbWUgU1RSSU5HLAogdW5pcXVlY2FycmllciBTVFJJTkcsCiBmbGlnaHRudW0gU1RSSU5HLAogdGFpbG51bSBTVFJJTkcsCiBhY3R1YWxlbGFwc2VkdGltZSBTVFJJTkcsCiBjcnNlbGFwc2VkdGltZSBTVFJJTkcsCiBhaXJ0aW1lIFNUUklORywKIGFycmRlbGF5IFNUUklORywKIGRlcGRlbGF5IFNUUklORywKIG9yaWdpbiBTVFJJTkcsCiBkZXN0IFNUUklORywKIGRpc3RhbmNlIFNUUklORywKIHRheGlpbiBTVFJJTkcsCiB0YXhpb3V0IFNUUklORywKIGNhbmNlbGxlZCBTVFJJTkcsCiBjYW5jZWxsYXRpb25jb2RlIFNUUklORywKIGRpdmVydGVkIFNUUklORywKIGNhcnJpZXJkZWxheSBTVFJJTkcsCiB3ZWF0aGVyZGVsYXkgU1RSSU5HLAogbmFzZGVsYXkgU1RSSU5HLAogc2VjdXJpdHlkZWxheSBTVFJJTkcsCiBsYXRlYWlyY3JhZnRkZWxheSBTVFJJTkcsCiBpbnNlcnRfdGltZSB0aW1lc3RhbXAsCiB1dWlkIHN0cmluZykgICAKU1RPUkVEIEFTIEFWUk8KICAgIExPQ0FUSU9OICcvZGF0YS9kZWNvbXBvc2VkL3BsYW5lZGF0ZSc7Cgp1c2UgZGVjb21wb3NlZDsKSU5TRVJUIE9WRVJXUklURSBUQUJMRSBmbGlnaHRzIFNFTEVDVCAqICwgQ1VSUkVOVF9USU1FU1RBTVAgQVMgaW5zZXJ0X3RpbWUsIApyZWZsZWN0KCJqYXZhLnV0aWwuVVVJRCIsICJyYW5kb21VVUlEIikgYXMgdXVpZCBGUk9NIGRlZmF1bHQuZmxpZ2h0czsKYGBgCiMjIyBNb2RlbGxlZCB6b25lICB7LnRhYnNldH0KCkhpdmUgU1FMIHNjcmlwdHMgdG8gY3JlYXRlIGV4dGVybmFsIHRhYmxlcyBQYXJxdWV0IGZvcm1hdAoKIyMjIyBBaXJwb3J0cwpgYGB7c3FsIGV2YWw9RkFMU0UsICBpbmNsdWRlPVRSVUV9CnVzZSBtb2RlbGxlZDsKZHJvcCB0YWJsZSBpZiBleGlzdHMgYWlycG9ydHM7CkNSRUFURSBFWFRFUk5BTCBUQUJMRSBJRiBOT1QgRVhJU1RTIGFpcnBvcnRzKAppZCBzdHJpbmcsCm5hbWUgc3RyaW5nLApjaXR5IHN0cmluZywKY291bnRyeSBzdHJpbmcsCmZhYSBzdHJpbmcsCmljYW8gc3RyaW5nLApsYXQgZG91YmxlLApsb24gZG91YmxlLAphbHQgaW50LAp0el9vZmZzZXQgZG91YmxlLApkc3Qgc3RyaW5nLAp0el9uYW1lIHN0cmluZwopCiBTVE9SRUQgQVMgUEFSUVVFVAogICAgTE9DQVRJT04gJy9kYXRhL21vZGVsbGVkL2FpcnBvcnRzJzsKYGBgCgojIyMjIENhcnJpZXJzKGFpcmxpbmVzKQpgYGB7c3FsIGV2YWw9RkFMU0UsICBpbmNsdWRlPVRSVUV9CnVzZSBtb2RlbGxlZDsKZHJvcCB0YWJsZSBpZiBleGlzdHMgYWlybGluZXM7CkNSRUFURSBFWFRFUk5BTCBUQUJMRSBJRiBOT1QgRVhJU1RTIGFpcmxpbmVzKAogICAgQ29kZSBTVFJJTkcsIERlc2NyaXB0aW9uIFNUUklORywKICAgIGluc2VydF90aW1lIHRpbWVzdGFtcCwKICAgIHV1aWQgc3RyaW5nCiAgICkKICAgIFNUT1JFRCBBUyBQQVJRVUVUCiAgICBMT0NBVElPTiAnL2RhdGEvbW9kZWxsZWQvY2FycmllcnMnOwpgYGAKIyMjIyBQbGFuZWRhdGUoZmxpZ2h0cykKYGBge3NxbCBldmFsPUZBTFNFLCAgaW5jbHVkZT1UUlVFfQp1c2UgbW9kZWxsZWQ7CmRyb3AgdGFibGUgaWYgZXhpc3RzIGZsaWdodHM7CkNSRUFURSBFWFRFUk5BTCBUQUJMRSBJRiBOT1QgRVhJU1RTIGZsaWdodHMoCiB5ZWFyIGludCwKbW9udGggaW50LApkYXlvZm1vbnRoIGludCwKZGF5b2Z3ZWVrIGludCwKZGVwdGltZSBpbnQsCmNyc2RlcHRpbWUgaW50LAphcnJ0aW1lIGludCwgCmNyc2FycnRpbWUgaW50LAp1bmlxdWVjYXJyaWVyIHN0cmluZywKZmxpZ2h0bnVtIGludCwKdGFpbG51bSBzdHJpbmcsIAphY3R1YWxlbGFwc2VkdGltZSBpbnQsCmNyc2VsYXBzZWR0aW1lIGludCwKYWlydGltZSBzdHJpbmcsCmFycmRlbGF5IGludCwKZGVwZGVsYXkgaW50LCAKb3JpZ2luIHN0cmluZywKZGVzdCBzdHJpbmcsCmRpc3RhbmNlIGludCwKdGF4aWluIHN0cmluZywKdGF4aW91dCBzdHJpbmcsCmNhbmNlbGxlZCBpbnQsCmNhbmNlbGxhdGlvbmNvZGUgc3RyaW5nLApkaXZlcnRlZCBpbnQsCmNhcnJpZXJkZWxheSBzdHJpbmcsCndlYXRoZXJkZWxheSBzdHJpbmcsCm5hc2RlbGF5IHN0cmluZywKc2VjdXJpdHlkZWxheSBzdHJpbmcsCmxhdGVhaXJjcmFmdGRlbGF5IHN0cmluZwopICAgClNUT1JFRCBBUyBQQVJRVUVUCiAgICBMT0NBVElPTiAnL2RhdGEvbW9kZWxsZWQvcGxhbmVkYXRlJzsKCmBgYAoKIyMjIERhdGEgdHJhbnNmZXIgZnJvbSBkZWNvbXBvc2VkIHRvIG1vZGVsbGVkIHpvbmUKCiMjIyMgSGl2ZSBTUUwgZXhhbXBsZQpgYGB7c3FsIGV2YWw9RkFMU0UsICBpbmNsdWRlPVRSVUV9CnVzZSBtb2RlbGxlZDsKSU5TRVJUIE9WRVJXUklURSBUQUJMRSBmbGlnaHRzICBTRUxFQ1QgKiAgRlJPTSBtb2RlbGxlZC5mbGlnaHRzOwpgYGAKIyMjIyBTcGFyayBleGFtcGxlIChzY2FsYSBjb2RlKQpSZWFkIGF2cm8gZmlsZSB0byBTcGFyayBEYXRhIEZyYW1lIGJhc2VkIG9uIFNjYWxhIGNhc2UgY2xhc3NlcyBhbmQgdGhlbiBzYXZlIGl0IHRvIHBhcnF1ZXQgZmlsZQoKYGBge3B5dGhvbiBldmFsPUZBTFNFLCAgaW5jbHVkZT1UUlVFfQpjYXNlIGNsYXNzIGFpcnBvcnRzKGlhdGE6U3RyaW5nLAlhaXJwb3J0OlN0cmluZywJY2l0eTpTdHJpbmcsCXN0YXRlOlN0cmluZywJY291bnRyeTpTdHJpbmcsbGF0OlN0cmluZywJbG9uZ3Q6U3RyaW5nLCBpbnNlcnRfdGltZTpTdHJpbmcsIHV1aWQ6U3RyaW5nKQoKY2FzZSBjbGFzcyBjYXJyaWVycyhjb2RlOlN0cmluZywJZGVzY3JpcHRpb246U3RyaW5nLCBpbnNlcnRfdGltZTpTdHJpbmcsIHV1aWQ6U3RyaW5nICkKCmNhc2UgY2xhc3MgcGxhbmVkYXRlKHllYXI6IFN0cmluZywgbW9udGggOiBTdHJpbmcsIGRheW9mbW9udGggOiBTdHJpbmcsIGRheW9md2VlayA6IFN0cmluZywgZGVwdGltZSA6IFN0cmluZywgY3JzZGVwdGltZSA6IFN0cmluZywgYXJydGltZSA6IFN0cmluZywgY3JzYXJydGltZSA6IFN0cmluZywgdW5pcXVlY2FycmllciA6IFN0cmluZyxmbGlnaHRudW0gOiBTdHJpbmcsIHRhaWxudW0gOiBTdHJpbmcsIGFjdHVhbGVsYXBzZWR0aW1lIDogU3RyaW5nLCBjcnNlbGFwc2VkdGltZSA6IFN0cmluZywgYWlydGltZSA6IFN0cmluZywgYXJyZGVsYXkgOiBTdHJpbmcsIGRlcGRlbGF5IDogU3RyaW5nLCBvcmlnaW4gOiBTdHJpbmcsIGRlc3QgOiBTdHJpbmcsIGRpc3RhbmNlIDogU3RyaW5nLCB0YXhpaW4gOiBTdHJpbmcsIHRheGlvdXQgOiBTdHJpbmcsIGNhbmNlbGxlZCA6IFN0cmluZywgY2FuY2VsbGF0aW9uY29kZSA6IFN0cmluZywKIGRpdmVydGVkIDogU3RyaW5nLCBjYXJyaWVyZGVsYXkgOiBTdHJpbmcsIHdlYXRoZXJkZWxheSA6IFN0cmluZywgbmFzZGVsYXkgOiBTdHJpbmcsIHNlY3VyaXR5ZGVsYXkgOiBTdHJpbmcsIGxhdGVBaXJjcmFmdGRlbGF5IDogU3RyaW5nLCBpbnNlcnRfdGltZTogU3RyaW5nLCB1dWlkIDogU3RyaW5nICkKIAppbXBvcnQgb3JnLmFwYWNoZS5zcGFyay5zcWwuZnVuY3Rpb25zLl8KaW1wb3J0IGNvbS5kYXRhYnJpY2tzLnNwYXJrLmF2cm8uXwppbXBvcnQgb3JnLmFwYWNoZS5zcGFyay5zcWwuU3BhcmtTZXNzaW9uCgp2YWwgc3BhcmsgPSBTcGFya1Nlc3Npb24uYnVpbGRlcigpCiAgICAgICAgICAgICAgICAgICAgICAgICAubWFzdGVyKCJzcGFyazovLzEyNy4wLjAxOjcwNzciKQogICAgICAgICAgICAgICAgICAgICAgICAuZ2V0T3JDcmVhdGUoKQoKLy8gVGhlIEF2cm8gcmVjb3JkcyBnZXQgY29udmVydGVkIHRvIFNwYXJrIHR5cGVzIGFuZAovLyB0aGVuIHdyaXR0ZW4gYmFjayBvdXQgYXMgUGFycXVldCByZWNvcmRzCnZhbCBkZl9wbGFuZWRhdGVfZGVjb21wb3NlZCA9IHNwYXJrLnJlYWQKICAgICAgICAgICAgICAuYXZybygiaGRmczovL2xvY2FsaG9zdDo4MDIwL2RhdGEvZGVjb21wb3NlZC9wbGFuZWRhdGUvIikgICAgICAgICAgIAogICAgICAgICAgICAgIC5hc1twbGFuZWRhdGVdCiAgICAgICAgICAgICAgCnZhbCBkZl9haXJwb3J0c19kZWNvbXBvc2VkID0gc3BhcmsucmVhZAogICAgICAgICAgICAgIC5hdnJvKCJoZGZzOi8vbG9jYWxob3N0OjgwMjAvZGF0YS9kZWNvbXBvc2VkL2FpcnBvcnRzLyIpICAgICAgICAgICAKICAgICAgICAgICAgICAuYXNbcGxhbmVkYXRlXQoKIGRmX3BsYW5lZGF0ZV9kZWNvbXBvc2VkLnJlZ2lzdGVyVGVtcFRhYmxlKCJwbGFuZWRhdGVfREYiKSAKIAogIC8vV2hhdCBhcmUgdGhlIHByaW1hcnkgY2F1c2VzIGZvciBmbGlnaHQgZGVsYXlzCiAgICBzcGFyay5zcWwoIlNFTEVDVCBzdW0od2VhdGhlckRlbGF5KSBXZWF0aGVyLHN1bShuYXNkZWxheSkgTkFTLCBzdW0oc2VjdXJpdHlkZWxheSkgU2VjdXJpdHksIHN1bShsYXRlYWlyY3JhZnRkZWxheSkgbGF0ZUFpcmNyYWZ0LCBzdW0oY2FycmllcmRlbGF5KSBDYXJyaWVyIEZST00gcGxhbmVkYXRlX0RGICIpLnNob3coKQoKICAgIC8vIFdoaWNoIEFpcnBvcnRzIGhhdmUgdGhlIE1vc3QgRGVsYXlzCgogICAgc3Bhcmsuc3FsKCJTRUxFQ1Qgb3JpZ2luLCBjb3VudCgqKSBjb25GbGlnaHQsIGF2ZyhkZXBkZWxheSkgZGVsYXkgRlJPTSBwbGFuZWRhdGVfREYgR1JPVVAgQlkgb3JpZ2luIikKICAgICAgLnNvcnQoZGVzYygiZGVsYXkiKSkuc2hvdygpCgogICAgLy8gSm9pbiBhaXJwb3J0cyBhbmQgZmxpZ2h0cyBkYXRhCiAgICBkZl9wbGFuZWRhdGVfZGVjb21wb3NlZC5qb2luKGRmX2FpcnBvcnRzX2RlY29tcG9zZWQsICQib3JpZ2luIiA9PT0gJCJpYXRhIiwgam9pblR5cGU9ImlubmVyIikuc2hvdyg1KQogCiAgICAvLyBXcml0ZSB0byBQYXJxdWV0IGZpbGUgdG8gbW9kZWxsZWQgem9uZQogICAgIGRmX3BsYW5lZGF0ZV9kZWNvbXBvc2VkLndyaXRlLnBhcnF1ZXQoImhkZnM6Ly9sb2NhbGhvc3Q6ODAyMC9kYXRhL21vZGVsbGVkL3BsYW5lZGF0ZS8iKQogICAgIGRmX2FpcnBvcnRzX2RlY29tcG9zZWQgLndyaXRlLnBhcnF1ZXQoImhkZnM6Ly9sb2NhbGhvc3Q6ODAyMC9kYXRhL21vZGVsbGVkL2FpcnBvcnRzLyIpCiAgICAgCiAgICAKYGBgCgojIERhdGEgQW5hbHlzaXMKVGhpcyBhbmFseXNpcyBwcmVkaWN0cyB0aW1lIGdhaW5lZCBpbiBmbGlnaHQgYnkgYWlybGluZSBjYXJyaWVyIHVzaW5nIGludGVncmF0aW9uIGJldHdlZW4gUiBhbmQgU3BhcmsKCmBgYHtyfQpsaWJyYXJ5KHJzcGFya2xpbmcpCmxpYnJhcnkoc3BhcmtseXIpCmxpYnJhcnkoZHBseXIpCmBgYAoKCmBgYHtyIGV2YWw9RkFMU0UsICBpbmNsdWRlPVRSVUV9Cgpjb25maWc9c3BhcmtfY29uZmlnKCkKc2MgPC0gc3BhcmtfY29ubmVjdChtYXN0ZXIgPSAieWFybi1jbGllbnQiLCAKICAgICAgICAgICAgICAgICAgICB2ZXJzaW9uID0gIjIuMi4wIiwKICAgICAgICAgICAgICAgICAgICBhcHBfbmFtZSA9ICJzcGFya2x5cjQiLAogICAgICAgICAgICAgICAgICAgIHNwYXJrX2hvbWUgPSAiL1VzZXJzL29sZWdiYXlkYWtvdi9Eb3dubG9hZHMvc3BhcmstMi4yLjAtYmluLWhhZG9vcDIuNy8iLAogICAgICAgICAgICAgICAgICAgIGNvbmZpZyA9IGNvbmZpZykKCmBgYAoKIyMgQ2FjaGUgdGhlIHRhYmxlcyBpbnRvIG1lbW9yeQoKVXNlIHRibF9jYWNoZSB0byBsb2FkIHRoZSBmbGlnaHRzIHRhYmxlIGludG8gbWVtb3J5LiBDYWNoaW5nIHRhYmxlcyB3aWxsIG1ha2UgYW5hbHlzaXMgbXVjaCBmYXN0ZXIuIENyZWF0ZSBhIGRwbHlyIHJlZmVyZW5jZSB0byB0aGUgU3BhcmsgRGF0YUZyYW1lLgpgYGB7ciBldmFsPUZBTFNFLCAgaW5jbHVkZT1UUlVFfQojIENhY2hlIGZsaWdodHMgSGl2ZSB0YWJsZSBpbnRvIFNwYXJrCnRibF9jYWNoZShzYywgJ2ZsaWdodHMnKQpmbGlnaHRzX3RibCA8LSB0Ymwoc2MsICdmbGlnaHRzJykKCiMgQ2FjaGUgYWlybGluZXMgSGl2ZSB0YWJsZSBpbnRvIFNwYXJrCnRibF9jYWNoZShzYywgJ2FpcmxpbmVzJykKYWlybGluZXNfdGJsIDwtIHRibChzYywgJ2FpcmxpbmVzJykKCiMgQ2FjaGUgYWlycG9ydHMgSGl2ZSB0YWJsZSBpbnRvIFNwYXJrCnRibF9jYWNoZShzYywgJ2FpcnBvcnRzJykKYWlycG9ydHNfdGJsIDwtIHRibChzYywgJ2FpcnBvcnRzJykKCgpgYGAKCiMjIENyZWF0ZSBhIG1vZGVsIGRhdGEgc2V0CgpGaWx0ZXIgdGhlIGRhdGEgdG8gY29udGFpbiBvbmx5IHRoZSByZWNvcmRzIHRvIGJlIHVzZWQgaW4gdGhlIGZpdHRlZCBtb2RlbC4gSm9pbiBjYXJyaWVyIGRlc2NyaXB0aW9ucyBmb3IgcmVmZXJlbmNlLiBDcmVhdGUgYSBuZXcgdmFyaWFibGUgY2FsbGVkIGdhaW4gd2hpY2ggcmVwcmVzZW50cyB0aGUgYW1vdW50IG9mIHRpbWUgZ2FpbmVkIChvciBsb3N0KSBpbiBmbGlnaHQuCmBgYHtyIGV2YWw9RkFMU0UsICBpbmNsdWRlPVRSVUV9CmxpYnJhcnkoZHBseXIpCiMgRmlsdGVyIHJlY29yZHMgYW5kIGNyZWF0ZSB0YXJnZXQgdmFyaWFibGUgJ2dhaW4nCm1vZGVsX2RhdGEgPC0gZmxpZ2h0c190YmwgJT4lCiAgZmlsdGVyKCFpcy5uYShhcnJkZWxheSkgJiAhaXMubmEoZGVwZGVsYXkpICYgIWlzLm5hKGRpc3RhbmNlKSkgJT4lCiAgZmlsdGVyKGRlcGRlbGF5ID4gMTUgJiBkZXBkZWxheSA8IDI0MCkgJT4lCiAgZmlsdGVyKGFycmRlbGF5ID4gLTYwICYgYXJyZGVsYXkgPCAzNjApICU+JQogIGZpbHRlcih5ZWFyID49IDIwMDMgJiB5ZWFyIDw9IDIwMDcpICU+JQogIGxlZnRfam9pbihhaXJsaW5lc190YmwsIGJ5ID0gYygidW5pcXVlY2FycmllciIgPSAiY29kZSIpKSAlPiUKICBtdXRhdGUoZ2FpbiA9IGRlcGRlbGF5IC0gYXJyZGVsYXkpICU+JQogIHNlbGVjdCh5ZWFyLCBtb250aCwgYXJyZGVsYXksIGRlcGRlbGF5LCBkaXN0YW5jZSwgdW5pcXVlY2FycmllciwgZGVzY3JpcHRpb24sIGdhaW4pCgojIFN1bW1hcml6ZSBkYXRhIGJ5IGNhcnJpZXIKbW9kZWxfZGF0YSAlPiUKICBncm91cF9ieSh1bmlxdWVjYXJyaWVyKSAlPiUKICBzdW1tYXJpemUoZGVzY3JpcHRpb24gPSBtaW4oZGVzY3JpcHRpb24pLCBnYWluPW1lYW4oZ2FpbiksIAogICAgICAgICAgICBkaXN0YW5jZT1tZWFuKGRpc3RhbmNlKSwgZGVwZGVsYXk9bWVhbihkZXBkZWxheSkpICU+JQogIHNlbGVjdChkZXNjcmlwdGlvbiwgZ2FpbiwgZGlzdGFuY2UsIGRlcGRlbGF5KSAlPiUKICBhcnJhbmdlKGdhaW4pCmBgYAo8Y2VudGVyPiFbXShtb2RlbF9kYXRhLnBuZyl7IHdpZHRoPTEwMCV9PC9jZW50ZXI+CgojIyBUcmFpbiBhIGxpbmVhciBtb2RlbAoKUHJlZGljdCB0aW1lIGdhaW5lZCBvciBsb3N0IGluIGZsaWdodCBhcyBhIGZ1bmN0aW9uIG9mIGRpc3RhbmNlLCBkZXBhcnR1cmUgZGVsYXksIGFuZCBhaXJsaW5lIGNhcnJpZXIuCmBgYHtyIGV2YWw9RkFMU0UsICBpbmNsdWRlPVRSVUV9CiMgUGFydGl0aW9uIHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0cwptb2RlbF9wYXJ0aXRpb24gPC0gbW9kZWxfZGF0YSAlPiUgCiAgc2RmX3BhcnRpdGlvbih0cmFpbiA9IDAuOCwgdmFsaWQgPSAwLjIsIHNlZWQgPSA1NTU1KQoKIyBGaXQgYSBsaW5lYXIgbW9kZWwKbWwxIDwtIG1vZGVsX3BhcnRpdGlvbiR0cmFpbiAlPiUKICBtbF9saW5lYXJfcmVncmVzc2lvbihnYWluIH4gZGlzdGFuY2UgKyBkZXBkZWxheSArIHVuaXF1ZWNhcnJpZXIpCgojIFN1bW1hcml6ZSB0aGUgbGluZWFyIG1vZGVsCnN1bW1hcnkobWwxKQpgYGAKCjxjZW50ZXI+IVtdKG1vZGVsLnBuZyl7IHdpZHRoPTEwMCV9PC9jZW50ZXI+Cgo8L2JyPgoKIyMgQXNzZXNzIG1vZGVsIHBlcmZvcm1hbmNlCgpDb21wYXJlIHRoZSBtb2RlbCBwZXJmb3JtYW5jZSB1c2luZyB0aGUgdmFsaWRhdGlvbiBkYXRhLgpgYGB7ciBldmFsPUZBTFNFLCAgaW5jbHVkZT1UUlVFfQojIENhbGN1bGF0ZSBhdmVyYWdlIGdhaW5zIGJ5IHByZWRpY3RlZCBkZWNpbGUKbW9kZWxfZGVjaWxlcyA8LSBsYXBwbHkobW9kZWxfcGFydGl0aW9uLCBmdW5jdGlvbih4KSB7CiAgc2RmX3ByZWRpY3QobWwxLCB4KSAlPiUKICAgIG11dGF0ZShkZWNpbGUgPSBudGlsZShkZXNjKHByZWRpY3Rpb24pLCAxMCkpICU+JQogICAgZ3JvdXBfYnkoZGVjaWxlKSAlPiUKICAgIHN1bW1hcml6ZShnYWluID0gbWVhbihnYWluKSkgJT4lCiAgICBzZWxlY3QoZGVjaWxlLCBnYWluKSAlPiUKICAgIGNvbGxlY3QoKQp9KQoKIyBDcmVhdGUgYSBzdW1tYXJ5IGRhdGFzZXQgZm9yIHBsb3R0aW5nCmRlY2lsZXMgPC0gcmJpbmQoCiAgZGF0YS5mcmFtZShkYXRhID0gJ3RyYWluJywgbW9kZWxfZGVjaWxlcyR0cmFpbiksCiAgZGF0YS5mcmFtZShkYXRhID0gJ3ZhbGlkJywgbW9kZWxfZGVjaWxlcyR2YWxpZCksCiAgbWFrZS5yb3cubmFtZXMgPSBGQUxTRQopCgojIFBsb3QgYXZlcmFnZSBnYWlucyBieSBwcmVkaWN0ZWQgZGVjaWxlCmRlY2lsZXMgJT4lCiAgZ2dwbG90KGFlcyhmYWN0b3IoZGVjaWxlKSwgZ2FpbiwgZmlsbCA9IGRhdGEpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIHBvc2l0aW9uID0gJ2RvZGdlJykgKwogIGxhYnModGl0bGUgPSAnQXZlcmFnZSBnYWluIGJ5IHByZWRpY3RlZCBkZWNpbGUnLCB4ID0gJ0RlY2lsZScsIHkgPSAnTWludXRlcycpCmBgYAo8Y2VudGVyPiFbXShncmFwaF9jaGFydC5wbmcpeyB3aWR0aD0xMDAlfTwvY2VudGVyPgoKIyMgVmlzdWFsaXplIHByZWRpY3Rpb25zCgpDb21wYXJlIGFjdHVhbCBnYWlucyB0byBwcmVkaWN0ZWQgZ2FpbnMgZm9yIGFuIG91dCBvZiB0aW1lIHNhbXBsZS4KYGBge3IgZXZhbD1GQUxTRSwgIGluY2x1ZGU9VFJVRX0KIyBTZWxlY3QgZGF0YSBmcm9tIGFuIG91dCBvZiB0aW1lIHNhbXBsZQpkYXRhXzIwMDggPC0gZmxpZ2h0c190YmwgJT4lCiAgZmlsdGVyKCFpcy5uYShhcnJkZWxheSkgJiAhaXMubmEoZGVwZGVsYXkpICYgIWlzLm5hKGRpc3RhbmNlKSkgJT4lCiAgZmlsdGVyKGRlcGRlbGF5ID4gMTUgJiBkZXBkZWxheSA8IDI0MCkgJT4lCiAgZmlsdGVyKGFycmRlbGF5ID4gLTYwICYgYXJyZGVsYXkgPCAzNjApICU+JQogIGZpbHRlcih5ZWFyID09IDIwMDgpICU+JQogIGxlZnRfam9pbihhaXJsaW5lc190YmwsIGJ5ID0gYygidW5pcXVlY2FycmllciIgPSAiY29kZSIpKSAlPiUKICBtdXRhdGUoZ2FpbiA9IGRlcGRlbGF5IC0gYXJyZGVsYXkpICU+JQogIHNlbGVjdCh5ZWFyLCBtb250aCwgYXJyZGVsYXksIGRlcGRlbGF5LCBkaXN0YW5jZSwgdW5pcXVlY2FycmllciwgZGVzY3JpcHRpb24sIGdhaW4sIG9yaWdpbixkZXN0KQoKIyBTdW1tYXJpemUgZGF0YSBieSBjYXJyaWVyCmNhcnJpZXIgPC0gc2RmX3ByZWRpY3QobWwxLCBkYXRhXzIwMDgpICU+JQogIGdyb3VwX2J5KGRlc2NyaXB0aW9uKSAlPiUKICBzdW1tYXJpemUoZ2FpbiA9IG1lYW4oZ2FpbiksIHByZWRpY3Rpb24gPSBtZWFuKHByZWRpY3Rpb24pLCBmcmVxID0gbigpKSAlPiUKICBmaWx0ZXIoZnJlcSA+IDEwMDAwKSAlPiUKICBjb2xsZWN0CgojIFBsb3QgYWN0dWFsIGdhaW5zIGFuZCBwcmVkaWN0ZWQgZ2FpbnMgYnkgYWlybGluZSBjYXJyaWVyCnAgPC0gZ2dwbG90KGNhcnJpZXIsIGFlcyhnYWluLCBwcmVkaWN0aW9uKSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gMC43NSwgY29sb3IgPSAncmVkJywgc2hhcGUgPSAzKSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBhbHBoYSA9IDAuMTUsIGNvbG9yID0gJ2JsdWUnKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHN1YnN0cihkZXNjcmlwdGlvbiwgMSwgMjApKSwgc2l6ZSA9IDMsIGFscGhhID0gMC43NSwgdmp1c3QgPSAtMSkgKwogIGxhYnModGl0bGU9J0F2ZXJhZ2UgR2FpbnMgRm9yZWNhc3QnLCB4ID0gJ0FjdHVhbCcsIHkgPSAnUHJlZGljdGVkJykKcApgYGAKPGNlbnRlcj4hW10oZ2Fpbl9mb3JlY2FzdC5wbmcpeyB3aWR0aD0xMDAlfTwvY2VudGVyPgoKClNvbWUgY2FycmllcnMgbWFrZSB1cCBtb3JlIHRpbWUgdGhhbiBvdGhlcnMgaW4gZmxpZ2h0LCBidXQgdGhlIGRpZmZlcmVuY2VzIGFyZSByZWxhdGl2ZWx5IHNtYWxsLiBUaGUgYXZlcmFnZSB0aW1lIGdhaW5zIGJldHdlZW4gdGhlIGJlc3QgYW5kIHdvcnN0IGFpcmxpbmVzIGlzIG9ubHkgc2l4IG1pbnV0ZXMuIFRoZSBiZXN0IHByZWRpY3RvciBvZiB0aW1lIGdhaW5lZCBpcyBub3QgY2FycmllciBidXQgZmxpZ2h0IGRpc3RhbmNlLiBUaGUgYmlnZ2VzdCBnYWlucyB3ZXJlIGFzc29jaWF0ZWQgd2l0aCB0aGUgbG9uZ2VzdCBmbGlnaHRzLgoKIyMgU2hhcmUgSW5zaWdodHMgLSBkZXBsb3kgbW9kZWwgdG8gcHJvZHVjdGlvbgoKIyMjIEJ1aWxkIGRhc2hib2FyZAoKQWdncmVnYXRlIHRoZSBzY29yZWQgZGF0YSBieSBvcmlnaW4sIGRlc3RpbmF0aW9uLCBhbmQgYWlybGluZS4gU2F2ZSB0aGUgYWdncmVnYXRlZCBkYXRhLgpgYGB7ciBldmFsPUZBTFNFLCAgaW5jbHVkZT1UUlVFfQpzZXR3ZCgifi9Eb2N1bWVudHMvYWlybGluZXNfZHViYWkiKQojIFN1bW1hcml6ZSBieSBvcmlnaW4sIGRlc3RpbmF0aW9uLCBhbmQgY2FycmllcgpzdW1tYXJ5XzIwMDggPC0gc2RmX3ByZWRpY3QobWwxLCBkYXRhXzIwMDgpICU+JQogIHJlbmFtZShjYXJyaWVyID0gdW5pcXVlY2FycmllciwgYWlybGluZSA9IGRlc2NyaXB0aW9uKSAlPiUKICBncm91cF9ieShvcmlnaW4sIGRlc3QsIGNhcnJpZXIsIGFpcmxpbmUpICU+JQogIHN1bW1hcml6ZSgKICAgIGZsaWdodHMgPSBuKCksCiAgICBkaXN0YW5jZSA9IG1lYW4oZGlzdGFuY2UpLAogICAgYXZnX2RlcF9kZWxheSA9IG1lYW4oZGVwZGVsYXkpLAogICAgYXZnX2Fycl9kZWxheSA9IG1lYW4oYXJyZGVsYXkpLAogICAgYXZnX2dhaW4gPSBtZWFuKGdhaW4pLAogICAgcHJlZF9nYWluID0gbWVhbihwcmVkaWN0aW9uKQogICAgKQoKIyBDb2xsZWN0IGFuZCBzYXZlIG9iamVjdHMKcHJlZF9kYXRhIDwtIGNvbGxlY3Qoc3VtbWFyeV8yMDA4KQphaXJwb3J0cyA8LSBjb2xsZWN0KHNlbGVjdChhaXJwb3J0c190YmwsIG5hbWUsIGZhYSwgbGF0LCBsb24pKQptbDFfc3VtbWFyeSA8LSBjYXB0dXJlLm91dHB1dChzdW1tYXJ5KG1sMSkpCnNhdmUocHJlZF9kYXRhLCBhaXJwb3J0cywgbWwxX3N1bW1hcnksIGZpbGUgPSAnZmxpZ2h0c19wcmVkXzIwMDguUkRhdGEnKQoKc3BhcmtfZGlzY29ubmVjdChzYykKYGBgCiMjIyBQdWJsaXNoIHRoZSBkYXNoYm9hcmQgdG8gc2hpbnlhcHBzLmlvCgpUaGUgZXhhbXBsZSBvZiB0aGUgaW50ZXJhY3RpdmUgZGFzaGJvYXJkIC0gWyoqY2xpY2sgdGhlICBsaW5rKipdKGh0dHBzOi8vaXBwcm9tZWsuc2hpbnlhcHBzLmlvL1RpbWVfR2FpbmVkX2luX0ZsaWdodC8pCgo8Y2VudGVyPiFbXShmbGV4ZGFzaGJvYXJkLnBuZyl7IHdpZHRoPTEwMCV9PC9jZW50ZXI+CgojIFN1bW1hcnkKVGhlIG9iamVjdGl2ZSBvZiBvdXIgYW5hbHlzaXMgd2FzIHRvIGludmVzdGlnYXRlIHdoZXRoZXIgY2VydGFpbiBjYXJyaWVycyBnYWluZWQgdGltZSBhZnRlciBhIGRlcGFydHVyZSBkZWxheS4gV2UgYnVpbHQgYSBzaW1wbGlzdGljIGxpbmVhciBtb2RlbCBhZ2FpbnN0IGEgc3Vic2V0IG9mIHRoZSBkYXRhIGFuZCB0aGVuIHZhbGlkYXRlZCB0aGUgbW9kZWwgYWdhaW5zdCBhIGhvbGQgb3V0IGdyb3VwLiBGaW5hbGx5LCB3ZSBhc3Nlc3NlZCB0aGUgbW9kZWwgYWdhaW5zdCBhbiBvdXQgb2YgdGltZSBzYW1wbGUgd2l0aGluIHRoZSB0aGUgd2hvbGUgZGF0YXNldCAgYW5kIGNyZWF0ZWQgdGhlIGludGVyZWN0aXZlIGRhc2hib2FyZCB0byBkZXBsb3kgdGhlIG1vZGVsIHRvIHByb2R1Y3Rpb24gCgpXZSBmb3VuZCB0aGF0IGRpc3RhbmNlIHdhcyB0aGUgbW9zdCBzaWduaWZpY2FudCBwcmVkaWN0b3Igb2YgZ2FpbmVkIHRpbWUuIFRoZSBsb25nZXIgdGhlIGZsaWdodCwgdGhlIG1vcmUgdGltZSBnYWluZWQuIFdlIGFsc28gZm91bmQgdGhhdCBjYXJyaWVyIGVmZmVjdHMgd2VyZSBzaWduaWZpY2FudCwgYnV0IGxlc3Mgc28gdGhhbiBkaXN0YW5jZS4gQ2VydGFpbiBjYXJyaWVycyBoYWQgc2lnbmlmaWNhbnQgcG9zaXRpdmUgZWZmZWN0cywgd2hpY2ggaXMgZXZpZGVuY2UgdGhhdCBzb21lIGNhcnJpZXJzIGdhaW4gdGltZSBhZnRlciBhIGRlcGFydHVyZSBkZWxheS4KClRoaXMgbW9kZWwgaW5jbHVkZWQgb25seSBhIGZldyBwcmVkaWN0b3JzIGFuZCB1c2VkIGEgc2ltcGxlIGZvcm0uIE1vcmUgc29waGlzdGljYXRlZCBtb2RlbHMgdXNpbmcgbW9yZSBwcmVkaWN0b3JzIChlLmcuIGFkanVzdGluZyBmb3Igd2VhdGhlcikgbWlnaHQgbGVhZCB0byBtb3JlIGNvbmNsdXNpdmUgcmVzdWx0cy4gSG93ZXZlciwgZ2l2ZW4gdGhlIHdlYWsgZml0IG9mIHRoaXMgbW9kZWwgdGhlIGxldmVsIG9mIG9wcG9ydHVuaXR5IGlzIHF1ZXN0aW9uYWJsZS4KCiMjIE5leHQgc3RlcHMKMS4gVHJhbnNmb3JtIHRoZSBkYXRhc2V0IHRvIGdyYXBoIGZvcm1hdCAoU3BhcmsgR3JhcGhYIERhdGFGcmFtZXMpIHVzaW5nIGRlcGFydHVyZSBhbmQgYXJyaXZhbCBhaXJwb3J0cyBhcyBhIGdycGFoIHZlcnRleGVzIGFuZCBmbGlnaHRzIGFzIHRoZSBlZGdlcyB3aXRoIG1ldGFkYXRhIGF0dGFjaG1lbnRzICh3ZWF0aGVyLCBkZWxheXMgZXRjKS4KMi4gUnVuIFBhZ2VSYW5rIGFsZ29yaXRobSAgYnkgY291bnRpbmcgdGhlIG51bWJlciBhbmQgcXVhbGl0eSBvZiBmbGlnaHRzIHRvIGEgYWlycG9ydCB0byBkZXRlcm1pbmUgYSByb3VnaCBlc3RpbWF0ZSBvZiBob3cgaW1wb3J0YW50IHRoZSBhaXJwb3J0IGlzICAKMy4gVGFrZSB0b3AgNSBtb3N0IGltcG9ydGFudCBhaXJwb3J0cyBhbmQgY2FsY3VsYXRlIHRoZSBtb3N0IGNvbW1vbiBkZXN0aW5hdGlvbnMgaW4gdGhlIGRhdGFzZXQgZnJvbSBhaXJwb3J0IHRvIGFpcnBvcnQgdG8gLiBXZSBjYW4gZG8gdGhpcyBieSBwZXJmb3JtaW5nIGEgZ3JvdXBpbmcgb3BlcmF0b3IgYW5kIGFkZGluZyB0aGUgZWRnZSBjb3VudHMgdG9nZXRoZXIuIFRoaXMgd2lsbCB5aWVsZCBhIG5ldyBncmFwaCBleGNlcHQgZWFjaCBlZGdlIHdpbGwgbm93IGJlIHRoZSBzdW0gb2YgYWxsIG9mIHRoZSBzZW1hbnRpY2FsbHkgc2FtZSBlZGdlcwo0LiBFdmFsdWF0ZSB3aGF0IGltcGFjdCB0aGUgZGVsYXlzIGhhcyBvbiB0aGUgZGVwYXJ0dXJlcyBmcm9tIHRoZSBzYW1lIGFpcnBvcnQgYnkgY2FsY3VsYXRpbmcgdmVydGV4ICJJbiBEZWdyZWVzIiIgYW5kICJPdXQgRGVncmVlcyIiCgoKCg==