Introduction

This is a report containing my analysis of bike ride company Divvy by Lyft. Data used in this project is made public by Divvy.

Ask

In order to guide the marketing program these 3 questions will guide the decision making.

  1. How do annual members and casual riders use Cyclistic bikes differently?
  2. Why would casual riders buy Cyclistic annual memberships?
  3. How can Cyclistic use digital media to influence casual riders to become members?

This report will work towards an awenser to question 1.

Business task

Gain insight into how Divvy’s annual members and casual riders use the service differently in order to get more members.

It is the belief that it is the most reasonable cause of action since casual riders already have tried the service.

Converting existing users should be easier than gaining new annual members from people unfamiliar with the service.

Annual members should be more profitable in the long run when we look at customer lifetime value.

The rest of the business tasks will be carried out by my competent team members and include:

Figure out why casual riders buy Divvy annual membership.

Understand how Divvy can use digital media to influence casual riders to become members.

Key stakeholders

Director of marketing - My direct manager.

Has assigned me the task of identifying the differences between members and casual riders, with the aim of making recommendations to improve the growth of annual membership.

Marketing analytics team - My team

Responsibilities include: Collecting, analyzing, and reporting data that helps guide Divvy’s marketing strategy.

Divvy’s executive team

Will decide whether to approve the recommended marketing program.

Prepare

Where is the data stored?

It is currently stored on Amazon Web Services (AWS): https://divvy-tripdata.s3.amazonaws.com/index.html

Structure of data

It is organized in zip folders with some data set ordered by: Year, Month, and then what the data includes. Other data sets organized by: What data is included, Year then Quarter.

Divvy Data

Historical trip data available to the public.

https://www.divvybikes.com/system-data

Here you’ll find Divvy’s trip data for public use. So whether you’re a policy maker, transportation professional, web developer, designer, or just plain curious, feel free to download it, map it, animate it, or bring it to life!

Note: This data is provided according to the Divvy Data License Agreement and released on a monthly schedule.

https://www.divvybikes.com/data-license-agreement

The Data

Each trip is anonymized and includes:

Trip start day and time Trip end day and time Trip start station Trip end station Rider type (Member, Single Ride, and Day Pass) The data has been processed to remove trips that are taken by staff as they service and inspect the system; and any trips that were below 60 seconds in length (potentially false starts or users trying to re-dock a bike to ensure it was secure).

Download Divvy trip history data.

https://divvy-tripdata.s3.amazonaws.com/index.html

You can get live station info on our station GBFS JSON feed.

https://gbfs.divvybikes.com/gbfs/gbfs.json

Installed libraries

library(tidyverse)  # collection of R packages data wrangling
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages ---------------------------------------------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5     v purrr   0.3.4
v tibble  3.1.5     v dplyr   1.0.7
v tidyr   1.1.4     v stringr 1.4.0
v readr   2.0.2     v forcats 0.5.1
-- Conflicts ------------------------------------------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(lubridate)  # wrangle date attributes

Vedhæfter pakke: ‘lubridate’

De følgende objekter er maskerede fra ‘package:base’:

    date, intersect, setdiff, union
library(ggplot2)  # data visualization

Upload Divvy datasets (csv files) here

library(readr)

X2020_09 <- read_csv("input/divvy-data/202009-divvy-tripdata.csv")
Rows: 532958 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (5): ride_id, rideable_type, start_station_name, end_station_name, member_casual
dbl  (6): start_station_id, end_station_id, start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2020_10 <- read_csv("input/divvy-data/202010-divvy-tripdata.csv")
Rows: 388653 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (5): ride_id, rideable_type, start_station_name, end_station_name, member_casual
dbl  (6): start_station_id, end_station_id, start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2020_11 <- read_csv("input/divvy-data/202011-divvy-tripdata.csv")
Rows: 259716 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (5): ride_id, rideable_type, start_station_name, end_station_name, member_casual
dbl  (6): start_station_id, end_station_id, start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2020_12 <- read_csv("input/divvy-data/202012-divvy-tripdata.csv")
Rows: 131573 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2021_01 <- read_csv("input/divvy-data/202101-divvy-tripdata.csv")
Rows: 96834 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2021_02 <- read_csv("input/divvy-data/202102-divvy-tripdata.csv")
Rows: 49622 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2021_03 <- read_csv("input/divvy-data/202103-divvy-tripdata.csv")
Rows: 228496 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2021_04 <- read_csv("input/divvy-data/202104-divvy-tripdata.csv")
Rows: 337230 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2021_05 <- read_csv("input/divvy-data/202105-divvy-tripdata.csv")
Rows: 531633 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2021_06 <- read_csv("input/divvy-data/202106-divvy-tripdata.csv")
Rows: 729595 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2021_07 <- read_csv("input/divvy-data/202107-divvy-tripdata.csv")
Rows: 822410 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
X2021_08 <- read_csv("input/divvy-data/202102-divvy-tripdata.csv")
Rows: 49622 Columns: 13
-- Column specification ---------------------------------------------------------------------------------------
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_station_name, end_station_id, m...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.

Process

Tools used:

Overview of data in R

str(X2020_09)
spec_tbl_df [532,958 x 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ ride_id           : chr [1:532958] "2B22BD5F95FB2629" "A7FB70B4AFC6CAF2" "86057FA01BAC778E" "57F6DC9A153DB98C" ...
 $ rideable_type     : chr [1:532958] "electric_bike" "electric_bike" "electric_bike" "electric_bike" ...
 $ started_at        : POSIXct[1:532958], format: "2020-09-17 14:27:11" "2020-09-17 15:07:31" "2020-09-17 15:09:04" "2020-09-17 18:10:46" ...
 $ ended_at          : POSIXct[1:532958], format: "2020-09-17 14:44:24" "2020-09-17 15:07:45" "2020-09-17 15:09:35" "2020-09-17 18:35:49" ...
 $ start_station_name: chr [1:532958] "Michigan Ave & Lake St" "W Oakdale Ave & N Broadway" "W Oakdale Ave & N Broadway" "Ashland Ave & Belle Plaine Ave" ...
 $ start_station_id  : num [1:532958] 52 NA NA 246 24 94 291 NA NA NA ...
 $ end_station_name  : chr [1:532958] "Green St & Randolph St" "W Oakdale Ave & N Broadway" "W Oakdale Ave & N Broadway" "Montrose Harbor" ...
 $ end_station_id    : num [1:532958] 112 NA NA 249 24 NA 256 NA NA NA ...
 $ start_lat         : num [1:532958] 41.9 41.9 41.9 42 41.9 ...
 $ start_lng         : num [1:532958] -87.6 -87.6 -87.6 -87.7 -87.6 ...
 $ end_lat           : num [1:532958] 41.9 41.9 41.9 42 41.9 ...
 $ end_lng           : num [1:532958] -87.6 -87.6 -87.6 -87.6 -87.6 ...
 $ member_casual     : chr [1:532958] "casual" "casual" "casual" "casual" ...
 - attr(*, "spec")=
  .. cols(
  ..   ride_id = col_character(),
  ..   rideable_type = col_character(),
  ..   started_at = col_datetime(format = ""),
  ..   ended_at = col_datetime(format = ""),
  ..   start_station_name = col_character(),
  ..   start_station_id = col_double(),
  ..   end_station_name = col_character(),
  ..   end_station_id = col_double(),
  ..   start_lat = col_double(),
  ..   start_lng = col_double(),
  ..   end_lat = col_double(),
  ..   end_lng = col_double(),
  ..   member_casual = col_character()
  .. )
 - attr(*, "problems")=<externalptr> 

Making the data uniform

I notice the station id data type changed in December 2020 from num to chr.

To ensure all my data share the newst data type I modify data sets from November 2020 and earlier to data type chr.

Converting data type:

X2020_11 <- mutate(X2020_11, start_station_id =as.character(start_station_id))
X2020_10 <- mutate(X2020_10, start_station_id =as.character(start_station_id))
X2020_09 <- mutate(X2020_09, start_station_id =as.character(start_station_id))

X2020_11 <- mutate(X2020_11, end_station_id =as.character(end_station_id))
X2020_10 <- mutate(X2020_10, end_station_id =as.character(end_station_id))
X2020_09 <- mutate(X2020_09, end_station_id =as.character(end_station_id))

Convert all 12 months into 1 year data frame

all_trips <- bind_rows(X2020_09, X2020_10, X2020_11, X2020_12, X2021_01, X2021_02, X2021_03, X2021_04, X2021_05, X2021_06, X2021_07, X2021_08)

Check new table all_trps

colnames(all_trips) # Check columb names
 [1] "ride_id"            "rideable_type"      "started_at"         "ended_at"           "start_station_name"
 [6] "start_station_id"   "end_station_name"   "end_station_id"     "start_lat"          "start_lng"         
[11] "end_lat"            "end_lng"            "member_casual"     
head(all_trips) # Check first 6 rows of the dataframe


str(all_trips) # Check structure for all_trips
spec_tbl_df [4,158,342 x 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ ride_id           : chr [1:4158342] "2B22BD5F95FB2629" "A7FB70B4AFC6CAF2" "86057FA01BAC778E" "57F6DC9A153DB98C" ...
 $ rideable_type     : chr [1:4158342] "electric_bike" "electric_bike" "electric_bike" "electric_bike" ...
 $ started_at        : POSIXct[1:4158342], format: "2020-09-17 14:27:11" "2020-09-17 15:07:31" "2020-09-17 15:09:04" "2020-09-17 18:10:46" ...
 $ ended_at          : POSIXct[1:4158342], format: "2020-09-17 14:44:24" "2020-09-17 15:07:45" "2020-09-17 15:09:35" "2020-09-17 18:35:49" ...
 $ start_station_name: chr [1:4158342] "Michigan Ave & Lake St" "W Oakdale Ave & N Broadway" "W Oakdale Ave & N Broadway" "Ashland Ave & Belle Plaine Ave" ...
 $ start_station_id  : chr [1:4158342] "52" NA NA "246" ...
 $ end_station_name  : chr [1:4158342] "Green St & Randolph St" "W Oakdale Ave & N Broadway" "W Oakdale Ave & N Broadway" "Montrose Harbor" ...
 $ end_station_id    : chr [1:4158342] "112" NA NA "249" ...
 $ start_lat         : num [1:4158342] 41.9 41.9 41.9 42 41.9 ...
 $ start_lng         : num [1:4158342] -87.6 -87.6 -87.6 -87.7 -87.6 ...
 $ end_lat           : num [1:4158342] 41.9 41.9 41.9 42 41.9 ...
 $ end_lng           : num [1:4158342] -87.6 -87.6 -87.6 -87.6 -87.6 ...
 $ member_casual     : chr [1:4158342] "casual" "casual" "casual" "casual" ...
 - attr(*, "spec")=
  .. cols(
  ..   ride_id = col_character(),
  ..   rideable_type = col_character(),
  ..   started_at = col_datetime(format = ""),
  ..   ended_at = col_datetime(format = ""),
  ..   start_station_name = col_character(),
  ..   start_station_id = col_double(),
  ..   end_station_name = col_character(),
  ..   end_station_id = col_double(),
  ..   start_lat = col_double(),
  ..   start_lng = col_double(),
  ..   end_lat = col_double(),
  ..   end_lng = col_double(),
  ..   member_casual = col_character()
  .. )
 - attr(*, "problems")=<externalptr> 
dim(all_trips)
[1] 4158342      13
nrow(all_trips)
[1] 4158342
summary(all_trips) # Summary 
   ride_id          rideable_type        started_at                     ended_at                  
 Length:4158342     Length:4158342     Min.   :2020-09-01 00:00:07   Min.   :2020-09-01 00:04:43  
 Class :character   Class :character   1st Qu.:2020-11-09 16:53:46   1st Qu.:2020-11-09 17:14:31  
 Mode  :character   Mode  :character   Median :2021-05-01 11:42:16   Median :2021-05-01 12:06:31  
                                       Mean   :2021-03-17 01:06:13   Mean   :2021-03-17 01:27:19  
                                       3rd Qu.:2021-06-20 07:01:36   3rd Qu.:2021-06-20 07:52:01  
                                       Max.   :2021-07-31 23:59:58   Max.   :2021-08-12 17:45:41  
                                                                                                  
 start_station_name start_station_id   end_station_name   end_station_id       start_lat       start_lng     
 Length:4158342     Length:4158342     Length:4158342     Length:4158342     Min.   :41.64   Min.   :-87.84  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:41.88   1st Qu.:-87.66  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :41.90   Median :-87.64  
                                                                             Mean   :41.90   Mean   :-87.64  
                                                                             3rd Qu.:41.93   3rd Qu.:-87.63  
                                                                             Max.   :42.08   Max.   :-87.52  
                                                                                                             
    end_lat         end_lng       member_casual     
 Min.   :41.51   Min.   :-88.07   Length:4158342    
 1st Qu.:41.88   1st Qu.:-87.66   Class :character  
 Median :41.90   Median :-87.64   Mode  :character  
 Mean   :41.90   Mean   :-87.64                     
 3rd Qu.:41.93   3rd Qu.:-87.63                     
 Max.   :42.15   Max.   :-87.44                     
 NA's   :4523    NA's   :4523                       
table(all_trips$rideable_type) # Checking what rideable types are included

 classic_bike   docked_bike electric_bike 
      1820526       1003455       1334361 
table(all_trips$member_casual) # Check membership and casual users

 casual  member 
1822549 2335793 

Dates

all_trips$date <- as.Date(all_trips$started_at)
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
all_trips$year <- as.Date(all_trips$date, "%y")
all_trips$month <- as.Date(all_trips$date, "%m")
all_trips$day <- as.Date(all_trips$date, "%d")

all_trips$day_of_week <- as.Date(all_trips$date, "%a")

Ride length in seconds, calculated

all_trips$ride_length <- difftime(all_trips$ended_at, all_trips$started_at)

str(all_trips) # Checking structure of columns
spec_tbl_df [4,158,342 x 19] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ ride_id           : chr [1:4158342] "2B22BD5F95FB2629" "A7FB70B4AFC6CAF2" "86057FA01BAC778E" "57F6DC9A153DB98C" ...
 $ rideable_type     : chr [1:4158342] "electric_bike" "electric_bike" "electric_bike" "electric_bike" ...
 $ started_at        : POSIXct[1:4158342], format: "2020-09-17 14:27:11" "2020-09-17 15:07:31" "2020-09-17 15:09:04" "2020-09-17 18:10:46" ...
 $ ended_at          : POSIXct[1:4158342], format: "2020-09-17 14:44:24" "2020-09-17 15:07:45" "2020-09-17 15:09:35" "2020-09-17 18:35:49" ...
 $ start_station_name: chr [1:4158342] "Michigan Ave & Lake St" "W Oakdale Ave & N Broadway" "W Oakdale Ave & N Broadway" "Ashland Ave & Belle Plaine Ave" ...
 $ start_station_id  : chr [1:4158342] "52" NA NA "246" ...
 $ end_station_name  : chr [1:4158342] "Green St & Randolph St" "W Oakdale Ave & N Broadway" "W Oakdale Ave & N Broadway" "Montrose Harbor" ...
 $ end_station_id    : chr [1:4158342] "112" NA NA "249" ...
 $ start_lat         : num [1:4158342] 41.9 41.9 41.9 42 41.9 ...
 $ start_lng         : num [1:4158342] -87.6 -87.6 -87.6 -87.7 -87.6 ...
 $ end_lat           : num [1:4158342] 41.9 41.9 41.9 42 41.9 ...
 $ end_lng           : num [1:4158342] -87.6 -87.6 -87.6 -87.6 -87.6 ...
 $ member_casual     : chr [1:4158342] "casual" "casual" "casual" "casual" ...
 $ date              : Date[1:4158342], format: "2020-09-17" "2020-09-17" "2020-09-17" "2020-09-17" ...
 $ year              : Date[1:4158342], format: "2020-09-17" "2020-09-17" "2020-09-17" "2020-09-17" ...
 $ month             : Date[1:4158342], format: "2020-09-17" "2020-09-17" "2020-09-17" "2020-09-17" ...
 $ day               : Date[1:4158342], format: "2020-09-17" "2020-09-17" "2020-09-17" "2020-09-17" ...
 $ day_of_week       : Date[1:4158342], format: "2020-09-17" "2020-09-17" "2020-09-17" "2020-09-17" ...
 $ ride_length       : 'difftime' num [1:4158342] 1033 14 31 1503 ...
  ..- attr(*, "units")= chr "secs"
 - attr(*, "spec")=
  .. cols(
  ..   ride_id = col_character(),
  ..   rideable_type = col_character(),
  ..   started_at = col_datetime(format = ""),
  ..   ended_at = col_datetime(format = ""),
  ..   start_station_name = col_character(),
  ..   start_station_id = col_double(),
  ..   end_station_name = col_character(),
  ..   end_station_id = col_double(),
  ..   start_lat = col_double(),
  ..   start_lng = col_double(),
  ..   end_lat = col_double(),
  ..   end_lng = col_double(),
  ..   member_casual = col_character()
  .. )
 - attr(*, "problems")=<externalptr> 

Converting “ride_length” to numeric in order to make calculations

all_trips$ride_length <- as.numeric(as.character(all_trips$ride_length))

is.numeric(all_trips$ride_length) # Checking if converted to numeric.
[1] TRUE

New data frame without “docked_bike”

all_trips_v1 <- all_trips[!(all_trips$start_station_name == "HQ QR" | all_trips$ride_length<0),] # Remove negative ride lenth and bikes taken out for quality controle


all_trips_clean <- drop_na(all_trips_v1)

summary(all_trips_clean) # Check summary of new data set
   ride_id          rideable_type        started_at                     ended_at                  
 Length:3596608     Length:3596608     Min.   :2020-09-01 00:00:07   Min.   :2020-09-01 00:04:43  
 Class :character   Class :character   1st Qu.:2020-11-07 12:56:25   1st Qu.:2020-11-07 13:23:47  
 Mode  :character   Mode  :character   Median :2021-04-26 17:36:30   Median :2021-04-26 17:58:28  
                                       Mean   :2021-03-13 20:48:29   Mean   :2021-03-13 21:12:12  
                                       3rd Qu.:2021-06-19 11:00:00   3rd Qu.:2021-06-19 11:26:40  
                                       Max.   :2021-07-31 23:59:57   Max.   :2021-08-12 17:45:41  
 start_station_name start_station_id   end_station_name   end_station_id       start_lat       start_lng     
 Length:3596608     Length:3596608     Length:3596608     Length:3596608     Min.   :41.65   Min.   :-87.78  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:41.88   1st Qu.:-87.65  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :41.90   Median :-87.64  
                                                                             Mean   :41.90   Mean   :-87.64  
                                                                             3rd Qu.:41.93   3rd Qu.:-87.63  
                                                                             Max.   :42.06   Max.   :-87.53  
    end_lat         end_lng       member_casual           date                 year           
 Min.   :41.65   Min.   :-87.78   Length:3596608     Min.   :2020-09-01   Min.   :2020-09-01  
 1st Qu.:41.88   1st Qu.:-87.66   Class :character   1st Qu.:2020-11-07   1st Qu.:2020-11-07  
 Median :41.90   Median :-87.64   Mode  :character   Median :2021-04-26   Median :2021-04-26  
 Mean   :41.90   Mean   :-87.64                      Mean   :2021-03-13   Mean   :2021-03-13  
 3rd Qu.:41.93   3rd Qu.:-87.63                      3rd Qu.:2021-06-19   3rd Qu.:2021-06-19  
 Max.   :42.08   Max.   :-87.52                      Max.   :2021-07-31   Max.   :2021-07-31  
     month                 day              day_of_week          ride_length     
 Min.   :2020-09-01   Min.   :2020-09-01   Min.   :2020-09-01   Min.   :      0  
 1st Qu.:2020-11-07   1st Qu.:2020-11-07   1st Qu.:2020-11-07   1st Qu.:    438  
 Median :2021-04-26   Median :2021-04-26   Median :2021-04-26   Median :    776  
 Mean   :2021-03-13   Mean   :2021-03-13   Mean   :2021-03-13   Mean   :   1422  
 3rd Qu.:2021-06-19   3rd Qu.:2021-06-19   3rd Qu.:2021-06-19   3rd Qu.:   1413  
 Max.   :2021-07-31   Max.   :2021-07-31   Max.   :2021-07-31   Max.   :3356649  
head(all_trips_clean) # Check new data set

sum(is.na(all_trips_clean)) # Check if all "NA" entries was removed correctly.
[1] 0

Analyse

Now it is time to analyze the data to gain valuable insights.

min(all_trips_clean$ride_length) # Shortest ride time - in seconds
[1] 0
max(all_trips_clean$ride_length) # Longest ride time - in seconds
[1] 3356649
median(all_trips_clean$ride_length) # Median ride time - in seconds
[1] 776
mean(all_trips_clean$ride_length) # Mean ride time - in seconds
[1] 1422.252
summary(all_trips_clean$ride_length)/60
    Min.  1st Qu.   Median     Mean  3rd Qu. 
    0.00     7.30    12.93    23.70    23.55 
    Max. 
55944.15 

Median ride length in minutes

aggregate(all_trips_clean$ride_length/60 ~ all_trips_clean$member_casual, FUN = median)

Mean ride length in minutes

aggregate(all_trips_clean$ride_length/60 ~ all_trips_clean$member_casual, FUN = mean)

Order week days

all_trips_clean$day_of_week <- ordered(all_trips_clean$day_of_week, levels=c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"))

Lets look at a table of how each member type uses the platform and for how long based on day of the week.

# analyze ridership data by type and weekday
all_trips_clean %>% 
  mutate(weekday = wday(started_at, label = TRUE)) %>%  #creates weekday field using wday()
  group_by(member_casual, weekday) %>%  #groups by usertype and weekday
  summarise(number_of_rides = n()                           #calculates the number of rides and average duration 
            ,average_duration = mean(ride_length)) %>%      # calculates the average duration
  arrange(member_casual, weekday)                               # sorts
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

Share

Let us get an idea of how much time each rider type spends using our service over the week.

all_trips_clean %>% 
  mutate(weekday = wday(started_at, label = TRUE)) %>% 
  group_by(member_casual, weekday) %>% 
  summarise(number_of_rides = n()
            ,average_duration = mean(ride_length)) %>% 
  arrange(member_casual, weekday)  %>% 
  ggplot(aes(x = weekday, y = average_duration, fill = member_casual)) +
  geom_col(position = "dodge")+ labs(title = "Average ride duration by day", x="Day of the Week", y="Average duration")
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

Lets visualize the number of rides for each member type

# Visualization of the number of rides by rider type
all_trips_clean %>% 
  mutate(weekday = wday(started_at, label = TRUE)) %>% 
  group_by(member_casual, weekday) %>% 
  summarise(number_of_rides = n()
            ,average_duration = mean(ride_length)) %>% 
  arrange(member_casual, weekday)  %>% 
  ggplot(aes(x = weekday, y = number_of_rides, fill = member_casual)) +
  geom_col(position = "dodge")+ labs(title = "Rider type comparison by day", x="Day of the Week", y="Number of Rides")
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

Comparing number of rides based on membership month by month.

all_trips_clean %>% 
  mutate(mon = month(started_at, label = TRUE)) %>% 
  group_by(member_casual, mon) %>% 
  summarise(number_of_rides = n()
            ,average_duration = mean(ride_length),.groups = 'drop') %>% 
  arrange(member_casual)  %>% 
  ggplot(aes(x = mon, y = number_of_rides, fill = member_casual)) +
  geom_col(position = "dodge") +
  labs(title = "Comparing number of rides among casual and annual members",x="Month",y="Number of rides",caption = "Months not in sequence by year", fill="User type") +
  theme(legend.position="top")

# Visualization of Rides by Date and User Type
all_trips_clean %>% 
  group_by(member_casual, date) %>% 
  summarise(number_of_rides = n()
                ,average_duration = mean(ride_length)) %>% 
  arrange(member_casual, date)  %>% 
  ggplot(aes(x = date, y = number_of_rides, group = member_casual)) +
  geom_line(aes(color = member_casual)) + 
  labs(title = "Number of rides over time") + 
  ylab("Number of rides") + 
  xlab("Date")
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

Additional information - Understanding the price setup betwene members and casual

Casual / Single ride price: The Single Ride is just $3.30 and includes 30 minutes of ride time to get you anywhere you need to go. Head to your favorite restaurant, meet up with friends or run a quick errand.

Need to ride longer? If you keep a bike out for more than 30 minutes, It’s an extra $0.15/minute.

Annual Membership: You can take as many rides as you want throughout the year, and the first 45 minutes of each ride are included in your plan.

Taking a longer ride? If you keep a bike out for more than 45 minutes at a time, it’s an extra $0.15/min.

Other notes - Price changes

I went back to check if any pricing changes have been made to Divvy annual the last year and found a change in price 9 August 2020.The effect of this change can help get more annual members as prices for annual members who used bikes for more than 45min. got changed to: $0,15/min. from the bigger jump of $3 per additional 30 minutes ride time.

Source - Member pricing old (New Aug 2020):

https://web.archive.org/web/20200516162807/https://www.divvybikes.com/pricing/annual

If we go back to May 13 2020 however we will see a great deal on single use: The Single Ride is just $1 and includes 30 minutes of ride time. $3 for each additional 30 minutes.

As a new member of the team I think you were correct to change the prices from the old to the new. I expect the old prices was made in order to get as many new customers to install app and try as the price to “foot in the door” was just $1.

Source - Single ride pricing old May 2020: https://web.archive.org/web/20200513144309/https://www.divvybikes.com/pricing/single-ride

Findings

Casual riders tend to ride for twice as long as members.

Riding activity is very seasonal for all users types especially for casual users, where as the annual members tend to use the service more stable.

Ride activity for both annual and casual riders are significant lower in the winter months (December to February).

Peak months as seen on number of rides over time is the very seasonal and in July casual members actually out numbers the annual members.

Act

Actionable advice:

If users have allowed us to send relevant advertising to them I believe we should:

Identify users with ride lengths longer than 30 min. and inform them about the yearly plan with includes 45 min. free for each ride.

Based on my findings I recommend we look into why casual members use the service for longer ride times than our members.

Dig into why annual members don’t have as long ride times as casual members. I think this is somewhat due to the old pricing models.

What was the purpose of the ride? Joy ride Commute Transport of you and other stuff Other (please specify)

We could organize some fun marketing campaigns in the summer months eg. sign up and get a drink to stay hydrated or an ice cream.

LS0tDQp0aXRsZTogIkRpdnZ5IC0gTHlmdCBhIFJlcG9ydCBieSBDaHJpc3RpYW4gVi4iDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoaXMgaXMgYSByZXBvcnQgY29udGFpbmluZyBteSBhbmFseXNpcyBvZiBiaWtlIHJpZGUgY29tcGFueSBEaXZ2eSBieSBMeWZ0Lg0KRGF0YSB1c2VkIGluIHRoaXMgcHJvamVjdCBpcyBtYWRlIHB1YmxpYyBieSBEaXZ2eS4NCg0KDQoNCiMgQXNrDQoNCkluIG9yZGVyIHRvIGd1aWRlIHRoZSBtYXJrZXRpbmcgcHJvZ3JhbSB0aGVzZSAzIHF1ZXN0aW9ucyB3aWxsIGd1aWRlIHRoZSBkZWNpc2lvbiBtYWtpbmcuDQoNCjEuIEhvdyBkbyBhbm51YWwgbWVtYmVycyBhbmQgY2FzdWFsIHJpZGVycyB1c2UgQ3ljbGlzdGljIGJpa2VzIGRpZmZlcmVudGx5Pw0KMi4gV2h5IHdvdWxkIGNhc3VhbCByaWRlcnMgYnV5IEN5Y2xpc3RpYyBhbm51YWwgbWVtYmVyc2hpcHM/DQozLiBIb3cgY2FuIEN5Y2xpc3RpYyB1c2UgZGlnaXRhbCBtZWRpYSB0byBpbmZsdWVuY2UgY2FzdWFsIHJpZGVycyB0byBiZWNvbWUgbWVtYmVycz8NCg0KVGhpcyByZXBvcnQgd2lsbCB3b3JrIHRvd2FyZHMgYW4gYXdlbnNlciB0byBxdWVzdGlvbiAxLiANCg0KIyMjIEJ1c2luZXNzIHRhc2sNCg0KR2FpbiBpbnNpZ2h0IGludG8gaG93IERpdnZ5J3MgYW5udWFsIG1lbWJlcnMgYW5kIGNhc3VhbCByaWRlcnMgdXNlIHRoZSBzZXJ2aWNlIGRpZmZlcmVudGx5IGluIG9yZGVyIHRvIGdldCBtb3JlIG1lbWJlcnMuDQoNCkl0IGlzIHRoZSBiZWxpZWYgdGhhdCBpdCBpcyB0aGUgbW9zdCByZWFzb25hYmxlIGNhdXNlIG9mIGFjdGlvbiBzaW5jZSBjYXN1YWwgcmlkZXJzIGFscmVhZHkgaGF2ZSB0cmllZCB0aGUgc2VydmljZS4gDQoNCkNvbnZlcnRpbmcgZXhpc3RpbmcgdXNlcnMgc2hvdWxkIGJlIGVhc2llciB0aGFuIGdhaW5pbmcgbmV3IGFubnVhbCBtZW1iZXJzIGZyb20gcGVvcGxlIHVuZmFtaWxpYXIgd2l0aCB0aGUgc2VydmljZS4gDQoNCkFubnVhbCBtZW1iZXJzIHNob3VsZCBiZSBtb3JlIHByb2ZpdGFibGUgaW4gdGhlIGxvbmcgcnVuIHdoZW4gd2UgbG9vayBhdCBjdXN0b21lciBsaWZldGltZSB2YWx1ZS4gDQoNCg0KVGhlIHJlc3Qgb2YgdGhlIGJ1c2luZXNzIHRhc2tzIHdpbGwgYmUgY2FycmllZCBvdXQgYnkgbXkgY29tcGV0ZW50IHRlYW0gbWVtYmVycyBhbmQgaW5jbHVkZTogDQoNCkZpZ3VyZSBvdXQgd2h5IGNhc3VhbCByaWRlcnMgYnV5IERpdnZ5IGFubnVhbCBtZW1iZXJzaGlwLg0KDQpVbmRlcnN0YW5kIGhvdyBEaXZ2eSBjYW4gdXNlIGRpZ2l0YWwgbWVkaWEgdG8gaW5mbHVlbmNlIGNhc3VhbCByaWRlcnMgdG8gYmVjb21lIG1lbWJlcnMuDQoNCiMjIyBLZXkgc3Rha2Vob2xkZXJzDQojIyMjIERpcmVjdG9yIG9mIG1hcmtldGluZyAtIE15IGRpcmVjdCBtYW5hZ2VyLiANCkhhcyBhc3NpZ25lZCBtZSB0aGUgdGFzayBvZiBpZGVudGlmeWluZyB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBtZW1iZXJzIGFuZCBjYXN1YWwgcmlkZXJzLCB3aXRoIHRoZSBhaW0gb2YgbWFraW5nIHJlY29tbWVuZGF0aW9ucyB0byBpbXByb3ZlIHRoZSBncm93dGggb2YgYW5udWFsIG1lbWJlcnNoaXAuICANCg0KIyMjIyBNYXJrZXRpbmcgYW5hbHl0aWNzIHRlYW0gLSBNeSB0ZWFtDQpSZXNwb25zaWJpbGl0aWVzIGluY2x1ZGU6IENvbGxlY3RpbmcsIGFuYWx5emluZywgYW5kIHJlcG9ydGluZyBkYXRhIHRoYXQgaGVscHMgZ3VpZGUgRGl2dnkncyBtYXJrZXRpbmcgc3RyYXRlZ3kuDQoNCiMjIyMgRGl2dnnigJlzIGV4ZWN1dGl2ZSB0ZWFtIA0KV2lsbCBkZWNpZGUgd2hldGhlciB0byBhcHByb3ZlIHRoZSByZWNvbW1lbmRlZCBtYXJrZXRpbmcgcHJvZ3JhbS4NCg0KDQoNCiMgUHJlcGFyZQ0KDQojIyMgV2hlcmUgaXMgdGhlIGRhdGEgc3RvcmVkPw0KSXQgaXMgY3VycmVudGx5IHN0b3JlZCBvbiBBbWF6b24gV2ViIFNlcnZpY2VzIChBV1MpOiBodHRwczovL2RpdnZ5LXRyaXBkYXRhLnMzLmFtYXpvbmF3cy5jb20vaW5kZXguaHRtbA0KDQojIyMgU3RydWN0dXJlIG9mIGRhdGENCkl0IGlzIG9yZ2FuaXplZCBpbiB6aXAgZm9sZGVycyB3aXRoIHNvbWUgZGF0YSBzZXQgb3JkZXJlZCBieToNClllYXIsIE1vbnRoLCBhbmQgdGhlbiB3aGF0IHRoZSBkYXRhIGluY2x1ZGVzLiANCk90aGVyIGRhdGEgc2V0cyBvcmdhbml6ZWQgYnk6DQpXaGF0IGRhdGEgaXMgaW5jbHVkZWQsIFllYXIgdGhlbiBRdWFydGVyLg0KDQojIyMgRGl2dnkgRGF0YQ0KSGlzdG9yaWNhbCB0cmlwIGRhdGEgYXZhaWxhYmxlIHRvIHRoZSBwdWJsaWMuDQoNCmh0dHBzOi8vd3d3LmRpdnZ5YmlrZXMuY29tL3N5c3RlbS1kYXRhDQoNCkhlcmUgeW91J2xsIGZpbmQgRGl2dnkncyB0cmlwIGRhdGEgZm9yIHB1YmxpYyB1c2UuIFNvIHdoZXRoZXIgeW91J3JlIGEgcG9saWN5IG1ha2VyLCB0cmFuc3BvcnRhdGlvbiBwcm9mZXNzaW9uYWwsIHdlYiBkZXZlbG9wZXIsIGRlc2lnbmVyLCBvciBqdXN0IHBsYWluIGN1cmlvdXMsIGZlZWwgZnJlZSB0byBkb3dubG9hZCBpdCwgbWFwIGl0LCBhbmltYXRlIGl0LCBvciBicmluZyBpdCB0byBsaWZlIQ0KDQpOb3RlOiBUaGlzIGRhdGEgaXMgcHJvdmlkZWQgYWNjb3JkaW5nIHRvIHRoZSBEaXZ2eSBEYXRhIExpY2Vuc2UgQWdyZWVtZW50IGFuZCByZWxlYXNlZCBvbiBhIG1vbnRobHkgc2NoZWR1bGUuDQoNCmh0dHBzOi8vd3d3LmRpdnZ5YmlrZXMuY29tL2RhdGEtbGljZW5zZS1hZ3JlZW1lbnQNCg0KIyMjIyBUaGUgRGF0YQ0KRWFjaCB0cmlwIGlzIGFub255bWl6ZWQgYW5kIGluY2x1ZGVzOg0KDQpUcmlwIHN0YXJ0IGRheSBhbmQgdGltZQ0KVHJpcCBlbmQgZGF5IGFuZCB0aW1lDQpUcmlwIHN0YXJ0IHN0YXRpb24NClRyaXAgZW5kIHN0YXRpb24NClJpZGVyIHR5cGUgKE1lbWJlciwgU2luZ2xlIFJpZGUsIGFuZCBEYXkgUGFzcykNClRoZSBkYXRhIGhhcyBiZWVuIHByb2Nlc3NlZCB0byByZW1vdmUgdHJpcHMgdGhhdCBhcmUgdGFrZW4gYnkgc3RhZmYgYXMgdGhleSBzZXJ2aWNlIGFuZCBpbnNwZWN0IHRoZSBzeXN0ZW07IGFuZCBhbnkgdHJpcHMgdGhhdCB3ZXJlIGJlbG93IDYwIHNlY29uZHMgaW4gbGVuZ3RoIChwb3RlbnRpYWxseSBmYWxzZSBzdGFydHMgb3IgdXNlcnMgdHJ5aW5nIHRvIHJlLWRvY2sgYSBiaWtlIHRvIGVuc3VyZSBpdCB3YXMgc2VjdXJlKS4NCg0KRG93bmxvYWQgRGl2dnkgdHJpcCBoaXN0b3J5IGRhdGEuDQoNCmh0dHBzOi8vZGl2dnktdHJpcGRhdGEuczMuYW1hem9uYXdzLmNvbS9pbmRleC5odG1sDQoNCllvdSBjYW4gZ2V0IGxpdmUgc3RhdGlvbiBpbmZvIG9uIG91ciBzdGF0aW9uIEdCRlMgSlNPTiBmZWVkLg0KDQpodHRwczovL2diZnMuZGl2dnliaWtlcy5jb20vZ2Jmcy9nYmZzLmpzb24NCg0KDQojIyBJbnN0YWxsZWQgbGlicmFyaWVzDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpICAjIGNvbGxlY3Rpb24gb2YgUiBwYWNrYWdlcyBkYXRhIHdyYW5nbGluZw0KDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICMgd3JhbmdsZSBkYXRlIGF0dHJpYnV0ZXMNCg0KbGlicmFyeShnZ3Bsb3QyKSAgIyBkYXRhIHZpc3VhbGl6YXRpb24NCmBgYA0KDQoNCg0KIyMjIFVwbG9hZCBEaXZ2eSBkYXRhc2V0cyAoY3N2IGZpbGVzKSBoZXJlDQoNCmBgYHtyfQ0KbGlicmFyeShyZWFkcikNCg0KWDIwMjBfMDkgPC0gcmVhZF9jc3YoImlucHV0L2RpdnZ5LWRhdGEvMjAyMDA5LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpYMjAyMF8xMCA8LSByZWFkX2NzdigiaW5wdXQvZGl2dnktZGF0YS8yMDIwMTAtZGl2dnktdHJpcGRhdGEuY3N2IikNClgyMDIwXzExIDwtIHJlYWRfY3N2KCJpbnB1dC9kaXZ2eS1kYXRhLzIwMjAxMS1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KWDIwMjBfMTIgPC0gcmVhZF9jc3YoImlucHV0L2RpdnZ5LWRhdGEvMjAyMDEyLWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpYMjAyMV8wMSA8LSByZWFkX2NzdigiaW5wdXQvZGl2dnktZGF0YS8yMDIxMDEtZGl2dnktdHJpcGRhdGEuY3N2IikNClgyMDIxXzAyIDwtIHJlYWRfY3N2KCJpbnB1dC9kaXZ2eS1kYXRhLzIwMjEwMi1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KWDIwMjFfMDMgPC0gcmVhZF9jc3YoImlucHV0L2RpdnZ5LWRhdGEvMjAyMTAzLWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpYMjAyMV8wNCA8LSByZWFkX2NzdigiaW5wdXQvZGl2dnktZGF0YS8yMDIxMDQtZGl2dnktdHJpcGRhdGEuY3N2IikNClgyMDIxXzA1IDwtIHJlYWRfY3N2KCJpbnB1dC9kaXZ2eS1kYXRhLzIwMjEwNS1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KWDIwMjFfMDYgPC0gcmVhZF9jc3YoImlucHV0L2RpdnZ5LWRhdGEvMjAyMTA2LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpYMjAyMV8wNyA8LSByZWFkX2NzdigiaW5wdXQvZGl2dnktZGF0YS8yMDIxMDctZGl2dnktdHJpcGRhdGEuY3N2IikNClgyMDIxXzA4IDwtIHJlYWRfY3N2KCJpbnB1dC9kaXZ2eS1kYXRhLzIwMjEwMi1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KYGBgDQoNCg0KDQoNCiMgUHJvY2Vzcw0KDQpUb29scyB1c2VkOg0KDQotIFIgd2lsbCBiZSBteSBtYWluIHRvb2wgaW4gdGhpcyBwcm9qZWN0IGFzIGl0IGlzIGNhcGFibGUgb2YgaGFuZGxpbmcgbGFyZ2VyIGRhdGEgc2V0cyBhbmQgdmlzdWFsaXppbmcgZGF0YS4NCg0KLSBTUUwgd2FzIHVzZWQgdG8gcXVpY2tseSBnYWluIGluc2lnaHQgaW50byB3aGF0IHRoZSBkYXRhIHdhcyBhYm91dCBhbmQgaG93IG11Y2ggZGF0YSB3YXMgaW4gdGhlIGZpbGVzLg0KDQotIEV4Y2VsIHdhcyB1c2VkIHRvIHRlc3QgdGhlIGxpbWl0cyBmb3Igd2hhdCBhIHNwcmVhZHNoZWV0IGNhbiBoYW5kbGUsIGEgbGl0dGxlIGNsZWFuaW5nIGFuZCBzbWFsbCBjYWxjdWxhdGlvbnMgcG9zc2libGUgdG8gYmUgbWFkZSBoZXJlLCBidXQgZm9yIGxhcmdlIGRhdGEgUiBhbmQgU1FMIGlzIHJlY29tbWVuZGVkLg0KDQotIE90aGVyIHRvb2xzIHRvIGNvbnNpZGVyOg0KRGFzaGJvYXJkIHNvZnR3YXJlIGxpa2UgVGFibGVhdSBvciBQb3dlciBCaSBjb3VsZCBiZSB1c2VkIHRvZ2V0aGVyIHdpdGggRGl2dnkgbGl2ZSBkYXRhIGZyb20gc3RhdGlvbnMuIFNpbmNlIG15IGFzc2lnbm1lbnQgZm9yIG5vdyBpcyB0byBjb21lIHVwIHdpdGggYSByZWNvbW1lbmRhdGlvbiBmb3IgYSBsb25nZXIgdGVybSBwbGFuLCB0aGlzIGlzIG5vdCBhcyBiZW5lZmljaWFsIHRvIHN0YXJ0IHdpdGguIFRoaXMgY291bGQgYmUgdXNlZnVsIGZvciBzZXJ2aWNlIG9yIGNhbXBhaWduIHdpdGggaGFuZG91dHMgYW5kIERpdnZ5IHJlcHJlc2VudGF0aXZlcyBoZWxwaW5nIHVwZ3JhZGUgbWVtYmVycy4NCg0KIyMjIyBPdmVydmlldyBvZiBkYXRhIGluIFINCg0KDQoNCmBgYHtyfQ0KaGVhZChYMjAyMV8wOCkgIyBDaGVjayB0b3Agcm93cyBmb3IgbGF0ZXN0IG1vbnRoDQoNCnN0cihYMjAyMV8wOCkgIyBDaGVjayBzdHJ1Y3R1cmUgZm9yIG1vbnRoIDA4DQpzdHIoWDIwMjFfMDcpICMgQ2hlY2sgc3RydWN0dXJlIGZvciBtb250aCAwNw0Kc3RyKFgyMDIxXzA2KSAjIENoZWNrIHN0cnVjdHVyZSBmb3IgbW9udGggMDYNCnN0cihYMjAyMV8wNSkgIyBDaGVjayBzdHJ1Y3R1cmUgZm9yIG1vbnRoIDA1DQpzdHIoWDIwMjFfMDQpICMgQ2hlY2sgc3RydWN0dXJlIGZvciBtb250aCAwNA0Kc3RyKFgyMDIxXzAzKSAjIENoZWNrIHN0cnVjdHVyZSBmb3IgbW9udGggMDMgDQpzdHIoWDIwMjFfMDIpICMgQ2hlY2sgc3RydWN0dXJlIGZvciBtb250aCAwMg0Kc3RyKFgyMDIxXzAxKSAjIENoZWNrIHN0cnVjdHVyZSBmb3IgbW9udGggMDENCnN0cihYMjAyMF8xMikgIyBDaGVjayBzdHJ1Y3R1cmUgZm9yIG1vbnRoIDEyDQpzdHIoWDIwMjBfMTEpICMgQ2hlY2sgc3RydWN0dXJlIGZvciBtb250aCAxMQ0Kc3RyKFgyMDIwXzEwKSAjIENoZWNrIHN0cnVjdHVyZSBmb3IgbW9udGggMTANCnN0cihYMjAyMF8wOSkgIyBDaGVjayBzdHJ1Y3R1cmUgZm9yIG1vbnRoIDA5DQpgYGANCg0KDQojIyMjIE1ha2luZyB0aGUgZGF0YSB1bmlmb3JtDQoNCkkgbm90aWNlIHRoZSBzdGF0aW9uIGlkIGRhdGEgdHlwZSBjaGFuZ2VkIGluIERlY2VtYmVyIDIwMjAgZnJvbSBudW0gdG8gY2hyLiANCg0KVG8gZW5zdXJlIGFsbCBteSBkYXRhIHNoYXJlIHRoZSBuZXdzdCBkYXRhIHR5cGUgSSBtb2RpZnkgZGF0YSBzZXRzIGZyb20gTm92ZW1iZXIgMjAyMCBhbmQgZWFybGllciB0byBkYXRhIHR5cGUgY2hyLiANCg0KQ29udmVydGluZyBkYXRhIHR5cGU6DQoNCg0KDQpgYGB7cn0NClgyMDIwXzExIDwtIG11dGF0ZShYMjAyMF8xMSwgc3RhcnRfc3RhdGlvbl9pZCA9YXMuY2hhcmFjdGVyKHN0YXJ0X3N0YXRpb25faWQpKQ0KWDIwMjBfMTAgPC0gbXV0YXRlKFgyMDIwXzEwLCBzdGFydF9zdGF0aW9uX2lkID1hcy5jaGFyYWN0ZXIoc3RhcnRfc3RhdGlvbl9pZCkpDQpYMjAyMF8wOSA8LSBtdXRhdGUoWDIwMjBfMDksIHN0YXJ0X3N0YXRpb25faWQgPWFzLmNoYXJhY3RlcihzdGFydF9zdGF0aW9uX2lkKSkNCg0KWDIwMjBfMTEgPC0gbXV0YXRlKFgyMDIwXzExLCBlbmRfc3RhdGlvbl9pZCA9YXMuY2hhcmFjdGVyKGVuZF9zdGF0aW9uX2lkKSkNClgyMDIwXzEwIDwtIG11dGF0ZShYMjAyMF8xMCwgZW5kX3N0YXRpb25faWQgPWFzLmNoYXJhY3RlcihlbmRfc3RhdGlvbl9pZCkpDQpYMjAyMF8wOSA8LSBtdXRhdGUoWDIwMjBfMDksIGVuZF9zdGF0aW9uX2lkID1hcy5jaGFyYWN0ZXIoZW5kX3N0YXRpb25faWQpKQ0KYGBgDQoNCg0KIyMjIENvbnZlcnQgYWxsIDEyIG1vbnRocyBpbnRvIDEgeWVhciBkYXRhIGZyYW1lDQoNCg0KYGBge3J9DQphbGxfdHJpcHMgPC0gYmluZF9yb3dzKFgyMDIwXzA5LCBYMjAyMF8xMCwgWDIwMjBfMTEsIFgyMDIwXzEyLCBYMjAyMV8wMSwgWDIwMjFfMDIsIFgyMDIxXzAzLCBYMjAyMV8wNCwgWDIwMjFfMDUsIFgyMDIxXzA2LCBYMjAyMV8wNywgWDIwMjFfMDgpDQpgYGANCg0KDQoNCiMjIyBDaGVjayBuZXcgdGFibGUgYWxsX3RycHMNCg0KDQoNCmBgYHtyfQ0KY29sbmFtZXMoYWxsX3RyaXBzKSAjIENoZWNrIGNvbHVtbiBuYW1lcw0KDQpoZWFkKGFsbF90cmlwcykgIyBDaGVjayBmaXJzdCA2IHJvd3Mgb2YgdGhlIGRhdGEgZnJhbWUNCg0KDQpzdHIoYWxsX3RyaXBzKSAjIENoZWNrIHN0cnVjdHVyZSBmb3IgYWxsX3RyaXBzDQoNCmRpbShhbGxfdHJpcHMpICMgQ2hlY2sgZGltZW5zaW9ucw0KDQpucm93KGFsbF90cmlwcykgIyBDaGVjayBudW1iZXIgb2Ygcm93cw0KDQpzdW1tYXJ5KGFsbF90cmlwcykgIyBTdW1tYXJ5IA0KDQp0YWJsZShhbGxfdHJpcHMkcmlkZWFibGVfdHlwZSkgIyBDaGVja2luZyB3aGF0IHJpZGUgdHlwZXMgYXJlIGluY2x1ZGVkDQoNCnRhYmxlKGFsbF90cmlwcyRtZW1iZXJfY2FzdWFsKSAjIENoZWNrIG1lbWJlcnNoaXAgYW5kIGNhc3VhbCB1c2Vycw0KYGBgDQoNCg0KIyMjIyBEYXRlcw0KDQoNCg0KYGBge3J9DQphbGxfdHJpcHMkZGF0ZSA8LSBhcy5EYXRlKGFsbF90cmlwcyRzdGFydGVkX2F0KSANCmFsbF90cmlwcyR5ZWFyIDwtIGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUsICIleSIpICMgQWxsIHRyaXAgZGF0YSBpbiB5ZWFybHkgZm9ybWF0DQphbGxfdHJpcHMkbW9udGggPC0gYXMuRGF0ZShhbGxfdHJpcHMkZGF0ZSwgIiVtIikgIyBBbGwgdHJpcCBkYXRhIGluIG1vbnRobHkgZm9ybWF0DQphbGxfdHJpcHMkZGF5IDwtIGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUsICIlZCIpICMgQWxsIHRyaXAgZGF0YSBpbiBkYWlseSBmb3JtYXQNCg0KYWxsX3RyaXBzJGRheV9vZl93ZWVrIDwtIGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUsICIlYSIpICMgQWxsIHRyaXAgZGF0YSBkYXkgb2Ygd2Vlaw0KYGBgDQoNCg0KIyMjIyBSaWRlIGxlbmd0aCBpbiBzZWNvbmRzLCBjYWxjdWxhdGVkDQoNCg0KDQpgYGB7cn0NCmFsbF90cmlwcyRyaWRlX2xlbmd0aCA8LSBkaWZmdGltZShhbGxfdHJpcHMkZW5kZWRfYXQsIGFsbF90cmlwcyRzdGFydGVkX2F0KSAjIENoZWNrIHRpbWUgZGlmZmVyZW5jZQ0KDQpzdHIoYWxsX3RyaXBzKSAjIENoZWNraW5nIHN0cnVjdHVyZSBvZiBjb2x1bW5zDQpgYGANCg0KDQojIyMjIENvbnZlcnRpbmcgInJpZGVfbGVuZ3RoIiB0byBudW1lcmljIGluIG9yZGVyIHRvIG1ha2UgY2FsY3VsYXRpb25zDQoNCg0KDQpgYGB7cn0NCmFsbF90cmlwcyRyaWRlX2xlbmd0aCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihhbGxfdHJpcHMkcmlkZV9sZW5ndGgpKSAjIE1ha2luZyBhbGxfdHJpcHMgZGF0YSB0eXBlIGludG8gbnVtZXJpYyB0eXBlDQoNCmlzLm51bWVyaWMoYWxsX3RyaXBzJHJpZGVfbGVuZ3RoKSAjIENoZWNraW5nIGlmIGNvbnZlcnRlZCB0byBudW1lcmljLg0KYGBgDQoNCg0KIyMjIE5ldyBkYXRhIGZyYW1lIHdpdGhvdXQgImRvY2tlZF9iaWtlIg0KDQoNCmBgYHtyfQ0KYWxsX3RyaXBzX3YxIDwtIGFsbF90cmlwc1shKGFsbF90cmlwcyRzdGFydF9zdGF0aW9uX25hbWUgPT0gIkhRIFFSIiB8IGFsbF90cmlwcyRyaWRlX2xlbmd0aDwwKSxdICMgUmVtb3ZlIG5lZ2F0aXZlIHJpZGUgbGVuZ3RoIGFuZCBiaWtlcyB0YWtlbiBvdXQgZm9yIHF1YWxpdHkgY29udHJvbA0KDQoNCmFsbF90cmlwc19jbGVhbiA8LSBkcm9wX25hKGFsbF90cmlwc192MSkgIyBVc2luZyBwYWNrYWdlIHRvIHJlbW92ZSBOQSBlbmVyaWVzLg0KDQpzdW1tYXJ5KGFsbF90cmlwc19jbGVhbikgIyBDaGVjayBzdW1tYXJ5IG9mIG5ldyBkYXRhIHNldA0KDQpoZWFkKGFsbF90cmlwc19jbGVhbikgIyBDaGVjayBuZXcgZGF0YSBzZXQNCg0Kc3VtKGlzLm5hKGFsbF90cmlwc19jbGVhbikpICMgQ2hlY2sgaWYgYWxsICJOQSIgZW50cmllcyB3YXMgcmVtb3ZlZCBjb3JyZWN0bHkuDQoNCmBgYA0KDQoNCiMgQW5hbHlzZQ0KDQpOb3cgaXQgaXMgdGltZSB0byBhbmFseXplIHRoZSBkYXRhIHRvIGdhaW4gdmFsdWFibGUgaW5zaWdodHMuDQoNCg0KYGBge3J9DQptaW4oYWxsX3RyaXBzX2NsZWFuJHJpZGVfbGVuZ3RoKSAjIFNob3J0ZXN0IHJpZGUgdGltZSAtIGluIHNlY29uZHMNCm1heChhbGxfdHJpcHNfY2xlYW4kcmlkZV9sZW5ndGgpICMgTG9uZ2VzdCByaWRlIHRpbWUgLSBpbiBzZWNvbmRzDQoNCm1lZGlhbihhbGxfdHJpcHNfY2xlYW4kcmlkZV9sZW5ndGgpICMgTWVkaWFuIHJpZGUgdGltZSAtIGluIHNlY29uZHMNCm1lYW4oYWxsX3RyaXBzX2NsZWFuJHJpZGVfbGVuZ3RoKSAjIE1lYW4gcmlkZSB0aW1lIC0gaW4gc2Vjb25kcw0KDQpzdW1tYXJ5KGFsbF90cmlwc19jbGVhbiRyaWRlX2xlbmd0aCkvNjAgIyBTdW1tYXJ5IGluIG1pbnV0ZXMNCg0KYGBgDQoNCk1lZGlhbiByaWRlIGxlbmd0aCBpbiBtaW51dGVzDQpgYGB7cn0NCmFnZ3JlZ2F0ZShhbGxfdHJpcHNfY2xlYW4kcmlkZV9sZW5ndGgvNjAgfiBhbGxfdHJpcHNfY2xlYW4kbWVtYmVyX2Nhc3VhbCwgRlVOID0gbWVkaWFuKSAjIENhbGN1bGF0ZSBtZWRpYW4gcmlkZSBsZW5ndGgNCmBgYA0KDQpNZWFuIHJpZGUgbGVuZ3RoIGluIG1pbnV0ZXMNCg0KYGBge3J9DQphZ2dyZWdhdGUoYWxsX3RyaXBzX2NsZWFuJHJpZGVfbGVuZ3RoLzYwIH4gYWxsX3RyaXBzX2NsZWFuJG1lbWJlcl9jYXN1YWwsIEZVTiA9IG1lYW4pICMgQ2FsY3VsYXRlIG1lYW4gcmlkZSBsZW5ndGggaW4gbWludXRlcw0KYGBgDQoNCg0KDQoNCg0KDQpPcmRlciB3ZWVrIGRheXMNCmBgYHtyfQ0KYWxsX3RyaXBzX2NsZWFuJGRheV9vZl93ZWVrIDwtIG9yZGVyZWQoYWxsX3RyaXBzX2NsZWFuJGRheV9vZl93ZWVrLCBsZXZlbHM9YygiU3VuZGF5IiwgIk1vbmRheSIsICJUdWVzZGF5IiwgIldlZG5lc2RheSIsICJUaHVyc2RheSIsICJGcmlkYXkiLCAiU2F0dXJkYXkiKSkgIyBNYWtpbmcgc3VyZSB3ZWVrIGRheXMgYXJlIG9yZGVyZWQgY29ycmVjdA0KYGBgDQoNCkxldHMgbG9vayBhdCBhIHRhYmxlIG9mIGhvdyBlYWNoIG1lbWJlciB0eXBlIHVzZXMgdGhlIHBsYXRmb3JtIGFuZCBmb3IgaG93IGxvbmcgYmFzZWQgb24gZGF5IG9mIHRoZSB3ZWVrLg0KYGBge3J9DQoNCmFsbF90cmlwc19jbGVhbiAlPiUgDQogIG11dGF0ZSh3ZWVrZGF5ID0gd2RheShzdGFydGVkX2F0LCBsYWJlbCA9IFRSVUUpKSAlPiUgICNjcmVhdGVzIHdlZWtkYXkgZmllbGQgdXNpbmcgd2RheSgpDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JSAgIyBieSB1c2VyX3R5cGUgYW5kIHdlZWtkYXkNCiAgc3VtbWFyaXNlKG51bWJlcl9vZl9yaWRlcyA9IG4oKSAjIHN1bW1hcmllcyB0aGUgbnVtYmVyIG9mIHJpZGVzIGFuZCBhdmVyYWdlIGR1cmF0aW9uIA0KICAgICAgICAgICAgLGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHJpZGVfbGVuZ3RoKSkgJT4lICNhdmVyYWdlIGR1cmF0aW9uDQogIGFycmFuZ2UobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkgIyBzb3J0DQpgYGANCg0KIyBTaGFyZQ0KDQpMZXQgdXMgZ2V0IGFuIGlkZWEgb2YgaG93IG11Y2ggdGltZSBlYWNoIHJpZGVyIHR5cGUgc3BlbmRzIHVzaW5nIG91ciBzZXJ2aWNlIG92ZXIgdGhlIHdlZWsuDQoNCmBgYHtyfQ0KYWxsX3RyaXBzX2NsZWFuICU+JSANCiAgbXV0YXRlKHdlZWtkYXkgPSB3ZGF5KHN0YXJ0ZWRfYXQsIGxhYmVsID0gVFJVRSkpICU+JSANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkgJT4lIA0KICBzdW1tYXJpc2UobnVtYmVyX29mX3JpZGVzID0gbigpDQogICAgICAgICAgICAsYXZlcmFnZV9kdXJhdGlvbiA9IG1lYW4ocmlkZV9sZW5ndGgpKSAlPiUgDQogIGFycmFuZ2UobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkgICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gd2Vla2RheSwgeSA9IGF2ZXJhZ2VfZHVyYXRpb24sIGZpbGwgPSBtZW1iZXJfY2FzdWFsKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpKyBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgcmlkZSBkdXJhdGlvbiBieSBkYXkiLCB4PSJEYXkgb2YgdGhlIFdlZWsiLCB5PSJBdmVyYWdlIGR1cmF0aW9uIikNCmBgYA0KDQoNCg0KTGV0cyB2aXN1YWxpemUgdGhlIG51bWJlciBvZiByaWRlcyBmb3IgZWFjaCBtZW1iZXIgdHlwZQ0KDQpgYGB7cn0NCiMgVmlzdWFsaXphdGlvbiBvZiB0aGUgbnVtYmVyIG9mIHJpZGVzIGJ5IHJpZGVyIHR5cGUNCmFsbF90cmlwc19jbGVhbiAlPiUgDQogIG11dGF0ZSh3ZWVrZGF5ID0gd2RheShzdGFydGVkX2F0LCBsYWJlbCA9IFRSVUUpKSAlPiUgDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JSANCiAgc3VtbWFyaXNlKG51bWJlcl9vZl9yaWRlcyA9IG4oKQ0KICAgICAgICAgICAgLGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHJpZGVfbGVuZ3RoKSkgJT4lIA0KICBhcnJhbmdlKG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICAlPiUgDQogIGdncGxvdChhZXMoeCA9IHdlZWtkYXksIHkgPSBudW1iZXJfb2ZfcmlkZXMsIGZpbGwgPSBtZW1iZXJfY2FzdWFsKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpKyBsYWJzKHRpdGxlID0gIlJpZGVyIHR5cGUgY29tcGFyaXNvbiBieSBkYXkiLCB4PSJEYXkgb2YgdGhlIFdlZWsiLCB5PSJOdW1iZXIgb2YgcmlkZXMiKQ0KYGBgDQpDb21wYXJpbmcgbnVtYmVyIG9mIHJpZGVzIGJhc2VkIG9uIG1lbWJlcnNoaXAgbW9udGggYnkgbW9udGguDQoNCmBgYHtyfQ0KYWxsX3RyaXBzX2NsZWFuICU+JSANCiAgbXV0YXRlKG1vbiA9IG1vbnRoKHN0YXJ0ZWRfYXQsIGxhYmVsID0gVFJVRSkpICU+JSANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgbW9uKSAlPiUgDQogIHN1bW1hcmlzZShudW1iZXJfb2ZfcmlkZXMgPSBuKCkNCiAgICAgICAgICAgICxhdmVyYWdlX2R1cmF0aW9uID0gbWVhbihyaWRlX2xlbmd0aCksLmdyb3VwcyA9ICdkcm9wJykgJT4lIA0KICBhcnJhbmdlKG1lbWJlcl9jYXN1YWwpICAlPiUgDQogIGdncGxvdChhZXMoeCA9IG1vbiwgeSA9IG51bWJlcl9vZl9yaWRlcywgZmlsbCA9IG1lbWJlcl9jYXN1YWwpKSArDQogIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBsYWJzKHRpdGxlID0gIkNvbXBhcmluZyBudW1iZXIgb2YgcmlkZXMgYW1vbmcgY2FzdWFsIGFuZCBhbm51YWwgbWVtYmVycyIseD0iTW9udGgiLHk9Ik51bWJlciBvZiByaWRlcyIsY2FwdGlvbiA9ICJNb250aHMgbm90IGluIHNlcXVlbmNlIGJ5IHllYXIiLCBmaWxsPSJVc2VyIHR5cGUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikNCmBgYA0KDQpgYGB7cn0NCiMgVmlzdWFsaXphdGlvbiBvZiBSaWRlcyBieSBEYXRlIGFuZCBVc2VyIFR5cGUNCmFsbF90cmlwc19jbGVhbiAlPiUgDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIGRhdGUpICU+JSANCiAgc3VtbWFyaXNlKG51bWJlcl9vZl9yaWRlcyA9IG4oKQ0KICAgICAgICAgICAgICAgICxhdmVyYWdlX2R1cmF0aW9uID0gbWVhbihyaWRlX2xlbmd0aCkpICU+JSANCiAgYXJyYW5nZShtZW1iZXJfY2FzdWFsLCBkYXRlKSAgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gbnVtYmVyX29mX3JpZGVzLCBncm91cCA9IG1lbWJlcl9jYXN1YWwpKSArDQogIGdlb21fbGluZShhZXMoY29sb3IgPSBtZW1iZXJfY2FzdWFsKSkgKyANCiAgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgcmlkZXMgb3ZlciB0aW1lIikgKyANCiAgeWxhYigiTnVtYmVyIG9mIHJpZGVzIikgKyANCiAgeGxhYigiRGF0ZSIpDQpgYGANCg0KDQoNCiMjIyMgQWRkaXRpb25hbCBpbmZvcm1hdGlvbiAtIFVuZGVyc3RhbmRpbmcgdGhlIHByaWNlIHNldHVwIGJldHdlbmUgbWVtYmVycyBhbmQgY2FzdWFsDQoNCkNhc3VhbCAvIFNpbmdsZSByaWRlIHByaWNlOg0KVGhlIFNpbmdsZSBSaWRlIGlzIGp1c3QgJDMuMzAgYW5kIGluY2x1ZGVzIDMwIG1pbnV0ZXMgb2YgcmlkZSB0aW1lIHRvIGdldCB5b3UgYW55d2hlcmUgeW91IG5lZWQgdG8gZ28uIEhlYWQgdG8geW91ciBmYXZvcml0ZSByZXN0YXVyYW50LCBtZWV0IHVwIHdpdGggZnJpZW5kcyBvciBydW4gYSBxdWljayBlcnJhbmQuDQoNCk5lZWQgdG8gcmlkZSBsb25nZXI/IElmIHlvdSBrZWVwIGEgYmlrZSBvdXQgZm9yIG1vcmUgdGhhbiAzMCBtaW51dGVzLCBJdCdzIGFuIGV4dHJhICQwLjE1L21pbnV0ZS4NCg0KDQpBbm51YWwgTWVtYmVyc2hpcDoNCllvdSBjYW4gdGFrZSBhcyBtYW55IHJpZGVzIGFzIHlvdSB3YW50IHRocm91Z2hvdXQgdGhlIHllYXIsIGFuZCB0aGUgZmlyc3QgNDUgbWludXRlcyBvZiBlYWNoIHJpZGUgYXJlIGluY2x1ZGVkIGluIHlvdXIgcGxhbi4NCg0KVGFraW5nIGEgbG9uZ2VyIHJpZGU/IElmIHlvdSBrZWVwIGEgYmlrZSBvdXQgZm9yIG1vcmUgdGhhbiA0NSBtaW51dGVzIGF0IGEgdGltZSwgaXTigJlzIGFuIGV4dHJhICQwLjE1L21pbi4NCg0KDQoNCiMjIyMgT3RoZXIgbm90ZXMgLSBQcmljZSBjaGFuZ2VzDQoNCkkgd2VudCBiYWNrIHRvIGNoZWNrIGlmIGFueSBwcmljaW5nIGNoYW5nZXMgaGF2ZSBiZWVuIG1hZGUgdG8gRGl2dnkgYW5udWFsIHRoZSBsYXN0IHllYXIgYW5kIGZvdW5kIGEgY2hhbmdlIGluIHByaWNlIDkgQXVndXN0IDIwMjAuVGhlIGVmZmVjdCBvZiB0aGlzIGNoYW5nZSBjYW4gaGVscCBnZXQgbW9yZSBhbm51YWwgbWVtYmVycyBhcyBwcmljZXMgZm9yIGFubnVhbCBtZW1iZXJzIHdobyB1c2VkIGJpa2VzIGZvciBtb3JlIHRoYW4gNDVtaW4uIGdvdCBjaGFuZ2VkIHRvOiAkMCwxNS9taW4uIGZyb20gdGhlIGJpZ2dlciBqdW1wIG9mICQzIHBlciBhZGRpdGlvbmFsIDMwIG1pbnV0ZXMgcmlkZSB0aW1lLg0KDQpTb3VyY2UgLSBNZW1iZXIgcHJpY2luZyBvbGQgKE5ldyBBdWcgMjAyMCk6DQoNCmh0dHBzOi8vd2ViLmFyY2hpdmUub3JnL3dlYi8yMDIwMDUxNjE2MjgwNy9odHRwczovL3d3dy5kaXZ2eWJpa2VzLmNvbS9wcmljaW5nL2FubnVhbA0KDQoNCklmIHdlIGdvIGJhY2sgdG8gTWF5IDEzIDIwMjAgaG93ZXZlciB3ZSB3aWxsIHNlZSBhIGdyZWF0IGRlYWwgb24gc2luZ2xlIHVzZToNClRoZSBTaW5nbGUgUmlkZSBpcyBqdXN0ICQxIGFuZCBpbmNsdWRlcyAzMCBtaW51dGVzIG9mIHJpZGUgdGltZS4gJDMgZm9yIGVhY2ggYWRkaXRpb25hbCAzMCBtaW51dGVzLg0KDQpBcyBhIG5ldyBtZW1iZXIgb2YgdGhlIHRlYW0gSSB0aGluayB5b3Ugd2VyZSBjb3JyZWN0IHRvIGNoYW5nZSB0aGUgcHJpY2VzIGZyb20gdGhlIG9sZCB0byB0aGUgbmV3LiANCkkgZXhwZWN0IHRoZSBvbGQgcHJpY2VzIHdhcyBtYWRlIGluIG9yZGVyIHRvIGdldCBhcyBtYW55IG5ldyBjdXN0b21lcnMgdG8gaW5zdGFsbCBhcHAgYW5kIHRyeSBhcyB0aGUgcHJpY2UgdG8gImZvb3QgaW4gdGhlIGRvb3IiIHdhcyBqdXN0ICQxLiANCg0KU291cmNlIC0gU2luZ2xlIHJpZGUgcHJpY2luZyBvbGQgTWF5IDIwMjA6IA0KaHR0cHM6Ly93ZWIuYXJjaGl2ZS5vcmcvd2ViLzIwMjAwNTEzMTQ0MzA5L2h0dHBzOi8vd3d3LmRpdnZ5YmlrZXMuY29tL3ByaWNpbmcvc2luZ2xlLXJpZGUNCg0KDQoNCiMjIyBGaW5kaW5ncw0KDQpDYXN1YWwgcmlkZXJzIHRlbmQgdG8gcmlkZSBmb3IgdHdpY2UgYXMgbG9uZyBhcyBtZW1iZXJzLg0KDQpSaWRpbmcgYWN0aXZpdHkgaXMgdmVyeSBzZWFzb25hbCBmb3IgYWxsIHVzZXJzIHR5cGVzIGVzcGVjaWFsbHkgZm9yIGNhc3VhbCB1c2Vycywgd2hlcmUgYXMgdGhlIGFubnVhbCBtZW1iZXJzIHRlbmQgdG8gdXNlIHRoZSBzZXJ2aWNlIG1vcmUgc3RhYmxlLiANCg0KUmlkZSBhY3Rpdml0eSBmb3IgYm90aCBhbm51YWwgYW5kIGNhc3VhbCByaWRlcnMgYXJlIHNpZ25pZmljYW50IGxvd2VyIGluIHRoZSB3aW50ZXIgbW9udGhzIChEZWNlbWJlciB0byBGZWJydWFyeSkuDQoNClBlYWsgbW9udGhzIGFzIHNlZW4gb24gbnVtYmVyIG9mIHJpZGVzIG92ZXIgdGltZSBpcyB0aGUgdmVyeSBzZWFzb25hbCBhbmQgaW4gSnVseSBjYXN1YWwgbWVtYmVycyBhY3R1YWxseSBvdXQgbnVtYmVycyB0aGUgYW5udWFsIG1lbWJlcnMuDQoNCg0KDQojIEFjdA0KDQpBY3Rpb25hYmxlIGFkdmljZToNCg0KSWYgdXNlcnMgaGF2ZSBhbGxvd2VkIHVzIHRvIHNlbmQgcmVsZXZhbnQgYWR2ZXJ0aXNpbmcgdG8gdGhlbSBJIGJlbGlldmUgd2Ugc2hvdWxkOg0KDQpJZGVudGlmeSB1c2VycyB3aXRoIHJpZGUgbGVuZ3RocyBsb25nZXIgdGhhbiAzMCBtaW4uIGFuZCBpbmZvcm0gdGhlbSBhYm91dCB0aGUgeWVhcmx5IHBsYW4gd2l0aCBpbmNsdWRlcyA0NSBtaW4uIGZyZWUgZm9yIGVhY2ggcmlkZS4NCg0KQmFzZWQgb24gbXkgZmluZGluZ3MgSSByZWNvbW1lbmQgd2UgbG9vayBpbnRvIHdoeSBjYXN1YWwgbWVtYmVycyB1c2UgdGhlIHNlcnZpY2UgZm9yIGxvbmdlciByaWRlIHRpbWVzIHRoYW4gb3VyIG1lbWJlcnMuDQoNCg0KRGlnIGludG8gd2h5IGFubnVhbCBtZW1iZXJzIGRvbid0IGhhdmUgYXMgbG9uZyByaWRlIHRpbWVzIGFzIGNhc3VhbCBtZW1iZXJzLiBJIHRoaW5rIHRoaXMgaXMgc29tZXdoYXQgZHVlIHRvIHRoZSBvbGQgcHJpY2luZyBtb2RlbHMuIA0KDQpXaGF0IHdhcyB0aGUgcHVycG9zZSBvZiB0aGUgcmlkZT8NCkpveSByaWRlDQpDb21tdXRlDQpUcmFuc3BvcnQgb2YgeW91IGFuZCBvdGhlciBzdHVmZg0KT3RoZXIgKHBsZWFzZSBzcGVjaWZ5KQ0KDQogDQpXZSBjb3VsZCBvcmdhbml6ZSBzb21lIGZ1biBtYXJrZXRpbmcgY2FtcGFpZ25zIGluIHRoZSBzdW1tZXIgbW9udGhzIGVnLiBzaWduIHVwIGFuZCBnZXQgYSBkcmluayB0byBzdGF5IGh5ZHJhdGVkIG9yIGFuIGljZSBjcmVhbS4NCg0KDQoNCg0K