Hapus spasi dan ubah ke lowercase
wifi <- wifi[tolower(trimws(wifi$Building)) == "library", ]
# jumlah baris
cat("Jumlah baris untuk Library:", nrow(wifi), "\n")
Jumlah baris untuk Library: 28652
Wifi
cat("Wifi missing values:\n")
Wifi missing values:
colSums(is.na(wifi))
time Event.Time Associated.Client.Count Authenticated.Client.Count Uni Building
0 0 0 0 0 0
Floor
0
Library1
cat("Library1 missing values:\n")
Library1 missing values:
colSums(is.na(lib1))
ts name reading units cumulative rate
0 0 3041 0 3041 3047
Library2
cat("Library2 missing values:\n")
Library2 missing values:
colSums(is.na(lib2))
ts name reading units cumulative rate
0 0 3041 0 3041 3047
Library3
cat("Library3 missing values:\n")
Library3 missing values:
colSums(is.na(lib3))
ts name reading units cumulative rate
0 0 3041 0 3041 3047
Isi kolom missing values
dfs <- list(lib1 = lib1, lib2 = lib2, lib3 = lib3)
for (name in names(dfs)) {
df <- dfs[[name]]
# kolom numerik yang mau diisi
numeric_cols <- c("reading", "cumulative", "rate")
# hitung rata-rata dari 144 baris pertama
means <- colMeans(df[1:144, numeric_cols], na.rm = TRUE)
# ganti NA dengan mean
for (col in numeric_cols) {
na_idx <- is.na(df[[col]])
df[na_idx, col] <- means[col]
}
dfs[[name]] <- df
}
Kembalikan ke variabel asli
lib1 <- dfs$lib1
lib2 <- dfs$lib2
lib3 <- dfs$lib3
Cek kembali missing value
# Wifi
cat("Wifi missing values:\n")
Wifi missing values:
colSums(is.na(wifi))
time Event.Time Associated.Client.Count Authenticated.Client.Count Uni Building
0 0 0 0 0 0
Floor
0
# Library1
cat("Library1 missing values:\n")
Library1 missing values:
colSums(is.na(lib1))
ts name reading units cumulative rate
0 0 0 0 0 0
# Library2
cat("Library2 missing values:\n")
Library2 missing values:
colSums(is.na(lib2))
ts name reading units cumulative rate
0 0 0 0 0 0
# Library3
cat("Library3 missing values:\n")
Library3 missing values:
colSums(is.na(lib3))
ts name reading units cumulative rate
0 0 0 0 0 0
Cek Duplikat
cat("\nJumlah duplikat WiFi:", sum(duplicated(wifi)), "\n")
Jumlah duplikat WiFi: 0
cat("Jumlah duplikat Library1:", sum(duplicated(lib1)), "\n")
Jumlah duplikat Library1: 0
cat("Jumlah duplikat Library2:", sum(duplicated(lib2)), "\n")
Jumlah duplikat Library2: 0
cat("Jumlah duplikat Library3:", sum(duplicated(lib3)), "\n")
Jumlah duplikat Library3: 0
sapply(wifi, class)
time Event.Time Associated.Client.Count Authenticated.Client.Count Uni Building
"character" "character" "integer" "integer" "character" "character"
Floor
"character"
sapply(lib1, class)
ts name reading units cumulative rate
"character" "character" "numeric" "character" "numeric" "numeric"
sapply(lib2, class)
ts name reading units cumulative rate
"character" "character" "numeric" "character" "numeric" "numeric"
sapply(lib3, class)
ts name reading units cumulative rate
"character" "character" "numeric" "character" "numeric" "numeric"
wifi <- wifi %>%
mutate(
time = ymd_hms(time)) # otomatis parse jadi datetime
lib1 <- lib1 %>%
mutate(ts = ymd_hms(ts))
lib2 <- lib2 %>%
mutate(ts = ymd_hms(ts))
lib3 <- lib3 %>%
mutate(ts = ymd_hms(ts))
sapply(wifi, class)
$time
[1] "POSIXct" "POSIXt"
$Event.Time
[1] "character"
$Associated.Client.Count
[1] "integer"
$Authenticated.Client.Count
[1] "integer"
$Uni
[1] "character"
$Building
[1] "character"
$Floor
[1] "character"
sapply(lib1, class)
$ts
[1] "POSIXct" "POSIXt"
$name
[1] "character"
$reading
[1] "numeric"
$units
[1] "character"
$cumulative
[1] "numeric"
$rate
[1] "numeric"
sapply(lib2, class)
$ts
[1] "POSIXct" "POSIXt"
$name
[1] "character"
$reading
[1] "numeric"
$units
[1] "character"
$cumulative
[1] "numeric"
$rate
[1] "numeric"
sapply(lib3, class)
$ts
[1] "POSIXct" "POSIXt"
$name
[1] "character"
$reading
[1] "numeric"
$units
[1] "character"
$cumulative
[1] "numeric"
$rate
[1] "numeric"
Ambil kolom ts dan rate masing-masing energi
lib1_clean <- lib1 %>%
select(ts, rate) %>%
rename(energy1 = rate)
lib2_clean <- lib2 %>%
select(ts, rate) %>%
rename(energy2 = rate)
lib3_clean <- lib3 %>%
select(ts, rate) %>%
rename(energy3 = rate)
energy_df <- lib1_clean %>%
inner_join(lib2_clean, by = "ts") %>%
inner_join(lib3_clean, by = "ts")
Hitung total konsumsi energi
energy_df <- energy_df %>%
mutate(Total_Energy = energy1 + energy2 + energy3) %>%
arrange(ts)
head(energy_df,10)
Pilih hanya kolom yang dibutuhkan di wifi
wifi <- wifi %>%
select(time, `Associated.Client.Count`) %>%
rename(Associated_Client_Count = `Associated.Client.Count`)
head(wifi,10)
Resampling per 10 menit (ambil rata-rata jumlah koneksi)
wifi_resampled <- wifi %>%
mutate(time = floor_date(time, "10 minutes")) %>%
group_by(time) %>%
summarise(Associated_Client_Count = mean(Associated_Client_Count, na.rm = TRUE)) %>%
ungroup()
head(wifi_resampled,10)
Gabungkan berdasarkan timestamp
final_df <- wifi_resampled %>%
inner_join(energy_df, by = c("time" = "ts"))
head(final_df,10)
Simpan sebagai CSV
write_csv(final_df, "final_wifi_energy.csv")
getwd()
[1] "C:/Users/Diana Eka Justitia/Downloads"
##jika excel
#write_xlsx(final_df, "final_wifi_energy.xlsx")
Visualisasi
Time Series Plot
final_df <- read.csv("C:\\Users\\Diana Eka Justitia\\Downloads\\final_wifi_energy.csv")
head(final_df)
Plot Time Series
ggplot(final_df) +
geom_line(aes(x = time, y = Associated_Client_Count, color = "Associated_Client_Count")) +
geom_line(aes(x = time, y = Total_Energy, color = "Total Energy")) +
scale_x_datetime(date_breaks = "1 day", date_labels = "%b-%d") +
xlab("") +
ylab("Value") +
ggtitle("Time Series: Associated_Client_Count vs Total Energy - Februari") +
scale_color_manual(
name = "Variable",
values = c("Associated_Client_Count" = "blue", "Total Energy" = "red")
)+theme(axis.text.x = element_text(angle = 90, hjust = 1))

Scatter Plot Occupancy vs Total Energy
ggplot(final_df, aes(x =Associated_Client_Count , y = Total_Energy)) +
geom_point(color = "blue", size = 2, alpha = 0.6) +
xlab("Occupancy") +
ylab("Total Energy") +
ggtitle("Scatter Plot: Occupancy vs Total Energy") +
theme_minimal()

Daily Profile Occupancy
tambah variabel time_of_day
final_df <- final_df %>%
mutate(time_of_day = as.numeric(format(time, "%H")) + as.numeric(format(time, "%M"))/60,
date = as.factor(as.Date(time)))
Hitung rata-rata per 10 menit
daily_avg <- final_df %>%
group_by(time_of_day) %>%
summarise(
avg_occupancy = mean(Associated_Client_Count, na.rm = TRUE),
avg_energy = mean(Total_Energy, na.rm = TRUE)
)
Plot Daily Occupancy Profile
ggplot(final_df, aes(x = time_of_day, y = Associated_Client_Count)) +
geom_line(aes(group = date), color = "gray", alpha = 0.3) + # tiap hari
geom_line(data = daily_avg, aes(x = time_of_day, y = avg_occupancy), color = "red", size = 1.5) +
xlab("Hour of Day") +
ylab("Occupancy") +
ggtitle("Daily Occupancy Profiles with Average (26 Days)") +
scale_x_continuous(breaks = seq(0, 24, by = 1)) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, hjust = 1))

Analisis
Peak
Peak Occupancy
peak_occupancy_time <- daily_avg$time_of_day[which.max(daily_avg$avg_occupancy)]
Peak Total Energy
peak_energy_time <- daily_avg$time_of_day[which.max(daily_avg$avg_energy)]
Plot Peak Time
ggplot(daily_avg, aes(x = time_of_day)) +
geom_line(aes(y = avg_occupancy, color = "Occupancy"), linewidth = 1.2) +
geom_line(aes(y = avg_energy, color = "Total Energy"), linewidth = 1.2) +
# Garis vertikal peak otomatis
geom_vline(xintercept = peak_occupancy_time, color = "blue", linetype = "dashed", linewidth = 1) +
geom_vline(xintercept = peak_energy_time, color = "red", linetype = "dashed", linewidth = 1) +
xlab("Jam") +
ylab("Rata-rata") +
ggtitle("Profil Harian (10 Menit) - Occupancy & Total Energy dengan Peak Otomatis") +
scale_x_continuous(
breaks = seq(0, 24, by = 10/60),
labels = function(x) sprintf("%02d:%02d", floor(x), round((x - floor(x))*60))
) +
scale_color_manual(values = c("Occupancy" = "blue", "Total Energy" = "red")) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 90, hjust = 1),
legend.title = element_blank()
)

Regres Polinomial
# orde 2
model_poly2 <- lm(Total_Energy ~ poly(Associated_Client_Count, 2, raw = TRUE), data = final_df)
# ringkasan model
summary(model_poly2)
Call:
lm(formula = Total_Energy ~ poly(Associated_Client_Count, 2,
raw = TRUE), data = final_df)
Residuals:
Min 1Q Median 3Q Max
-45.619 -9.043 -0.504 8.922 50.936
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 8.784e+01 4.173e-01 210.51 <2e-16 ***
poly(Associated_Client_Count, 2, raw = TRUE)1 6.288e-01 5.821e-03 108.02 <2e-16 ***
poly(Associated_Client_Count, 2, raw = TRUE)2 -8.855e-04 1.367e-05 -64.78 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 14.29 on 3680 degrees of freedom
Multiple R-squared: 0.8931, Adjusted R-squared: 0.8931
F-statistic: 1.538e+04 on 2 and 3680 DF, p-value: < 2.2e-16
Anomalies
#menghitung residual
final_df <- final_df %>%
mutate(predicted_energy = predict(model_poly2, newdata = ),
residual = Total_Energy - predicted_energy)
# menghitung batas toleransi (+-2σ)
res_sd <- sd(final_df$residual, na.rm = TRUE)
upper_bound <- 2 * res_sd
lower_bound <- -2 * res_sd
# Menandai anomali berdasarkan
final_df <- final_df %>%
mutate(anomaly = case_when(
residual > upper_bound ~ "High Energy",
residual < lower_bound ~ "Low Energy",
TRUE ~ "Normal"
))
Plot Anomali
ggplot(final_df, aes(x = Associated_Client_Count, y = Total_Energy, color = anomaly)) +
geom_point(alpha = 0.6) +
geom_line(aes(y = predicted_energy), color = "green", linewidth = 1) +
scale_color_manual(values = c("Normal" = "gray", "High Energy" = "red", "Low Energy" = "blue")) +
xlab("Occupancy") +
ylab("Total Energy") +
ggtitle("Deteksi Anomali Energy vs Occupancy (±2σ Residual)") +
theme_minimal()

Weekend VS Weekday
Memberi label untuk hari
final_df$day_name <- weekdays(final_df$time)
head(final_df)
Memberi label weekend dan weekday
final_df$is_weekend <- ifelse(final_df$day_name %in% c("Saturday", "Sunday"), "Weekend", "Weekday")
head(final_df)
Plot
daily_avg <- final_df %>%
group_by(time_of_day, is_weekend) %>%
summarise(avg_occupancy = mean(Associated_Client_Count, na.rm = TRUE),
.groups = "drop")
ggplot(daily_avg, aes(x = time_of_day, y = avg_occupancy, color = is_weekend)) +
geom_line(linewidth = 1.2) +
scale_x_continuous(
breaks = seq(0, 24, by = 2),
labels = function(x) sprintf("%02d:%02d", floor(x), round((x - floor(x))*60))
) +
labs(title = "Profil Harian Occupancy: Weekday vs Weekend",
x = "Jam", y = "Rata-rata Occupancy", color = "Kategori") +
theme_minimal()

NA
NA
Plot Time Series Weekend dan Weekday
final_df_hourly <- final_df %>%
mutate(hour = lubridate::floor_date(time, "hour")) %>%
group_by(hour, is_weekend, day_name) %>%
summarise(avg_occupancy = mean (Associated_Client_Count, na.rm = TRUE), .group = "drop")
`summarise()` has grouped output by 'hour', 'is_weekend'. You can override using the `.groups` argument.
ggplot(final_df_hourly, aes(x = hour, y = avg_occupancy,
color = is_weekend,
group = is_weekend)) +
geom_line(linewidth = 0.9) +
labs(title = "Time Series Occupancy per Jam (Weekday vs Weekend)",
x = "Waktu", y = "Rata-rata Occupancy", color = "Kategori") +
theme_minimal()

NA
NA
Scatter Plot Weekend dan Weekday
ggplot(final_df, aes(x = Associated_Client_Count,
y = Total_Energy,
color = is_weekend)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "loess", se = FALSE, color = "green", linewidth = 1) +
xlab("Occupancy") +
ylab("Total Energy") +
ggtitle("Occupancy vs Total Energy: Weekend vs Weekday") +
theme_minimal()

NA
NA
LS0tDQp0aXRsZTogIkRhdGEgTWluaW5nIEFkdmVudHVyZVIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyBMb2FkIExpYnJhcnkNCg0KYGBge3J9DQpsaWJyYXJ5KHdyaXRleGwpIA0KbGlicmFyeShkcGx5cikgDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeSh0aWR5dmVyc2UpIA0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQojIyMgRGF0YSBMb2FkIHdpZmkNCg0KYGBge3J9DQp3aWZpIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXERpYW5hIEVrYSBKdXN0aXRpYVxcRG93bmxvYWRzXFx3aWZpLmNzdiIpDQpoZWFkKHdpZmkpDQoNCmxpYjEgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcRGlhbmEgRWthIEp1c3RpdGlhXFxEb3dubG9hZHNcXGxpYnJhcnkxLmNzdiIpDQpoZWFkKGxpYjEpDQoNCmxpYjIgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcRGlhbmEgRWthIEp1c3RpdGlhXFxEb3dubG9hZHNcXGxpYnJhcnkyLmNzdiIpDQpoZWFkKGxpYjIpDQoNCmxpYjMgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcRGlhbmEgRWthIEp1c3RpdGlhXFxEb3dubG9hZHNcXGxpYnJhcnkzLmNzdiIpDQpoZWFkKGxpYjMpDQoNCmBgYA0KDQojIyBDZWsgZGF0YSBCdWlsZGluZw0KYGBge3J9DQp0YWJsZSh3aWZpJEJ1aWxkaW5nKQ0KYGBgDQojIyBIYXB1cyBzcGFzaSBkYW4gdWJhaCBrZSBsb3dlcmNhc2UgDQoNCmBgYHtyfQ0Kd2lmaSA8LSB3aWZpW3RvbG93ZXIodHJpbXdzKHdpZmkkQnVpbGRpbmcpKSA9PSAibGlicmFyeSIsIF0gDQojIGp1bWxhaCBiYXJpcyANCmNhdCgiSnVtbGFoIGJhcmlzIHVudHVrIExpYnJhcnk6IiwgbnJvdyh3aWZpKSwgIlxuIikNCmBgYA0KIyMjIFdpZmkNCg0KYGBge3J9DQpjYXQoIldpZmkgbWlzc2luZyB2YWx1ZXM6XG4iKQ0KY29sU3Vtcyhpcy5uYSh3aWZpKSkNCmBgYA0KIyMjIExpYnJhcnkxDQoNCmBgYHtyfQ0KY2F0KCJMaWJyYXJ5MSBtaXNzaW5nIHZhbHVlczpcbiIpDQpjb2xTdW1zKGlzLm5hKGxpYjEpKQ0KYGBgDQoNCiMjIyBMaWJyYXJ5Mg0KDQpgYGB7cn0NCmNhdCgiTGlicmFyeTIgbWlzc2luZyB2YWx1ZXM6XG4iKQ0KY29sU3Vtcyhpcy5uYShsaWIyKSkNCmBgYA0KDQojIyMgTGlicmFyeTMNCg0KYGBge3J9DQpjYXQoIkxpYnJhcnkzIG1pc3NpbmcgdmFsdWVzOlxuIikNCmNvbFN1bXMoaXMubmEobGliMykpDQpgYGANCg0KIyMjIElzaSBrb2xvbSBtaXNzaW5nIHZhbHVlcw0KYGBge3J9DQpkZnMgPC0gbGlzdChsaWIxID0gbGliMSwgbGliMiA9IGxpYjIsIGxpYjMgPSBsaWIzKQ0KDQpmb3IgKG5hbWUgaW4gbmFtZXMoZGZzKSkgew0KICBkZiA8LSBkZnNbW25hbWVdXQ0KICANCiAgIyBrb2xvbSBudW1lcmlrIHlhbmcgbWF1IGRpaXNpDQogIG51bWVyaWNfY29scyA8LSBjKCJyZWFkaW5nIiwgImN1bXVsYXRpdmUiLCAicmF0ZSIpDQogIA0KICAjIGhpdHVuZyByYXRhLXJhdGEgZGFyaSAxNDQgYmFyaXMgcGVydGFtYQ0KICBtZWFucyA8LSBjb2xNZWFucyhkZlsxOjE0NCwgbnVtZXJpY19jb2xzXSwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBnYW50aSBOQSBkZW5nYW4gbWVhbg0KICBmb3IgKGNvbCBpbiBudW1lcmljX2NvbHMpIHsNCiAgICBuYV9pZHggPC0gaXMubmEoZGZbW2NvbF1dKQ0KICAgIGRmW25hX2lkeCwgY29sXSA8LSBtZWFuc1tjb2xdDQogIH0NCiAgDQogIGRmc1tbbmFtZV1dIDwtIGRmDQp9DQpgYGANCiMjIyBLZW1iYWxpa2FuIGtlIHZhcmlhYmVsIGFzbGkNCg0KYGBge3J9DQpsaWIxIDwtIGRmcyRsaWIxDQpsaWIyIDwtIGRmcyRsaWIyDQpsaWIzIDwtIGRmcyRsaWIzDQpgYGANCg0KIyMjIENlayBrZW1iYWxpIG1pc3NpbmcgdmFsdWUgDQoNCmBgYHtyfQ0KIyBXaWZpDQpjYXQoIldpZmkgbWlzc2luZyB2YWx1ZXM6XG4iKQ0KY29sU3Vtcyhpcy5uYSh3aWZpKSkNCg0KIyBMaWJyYXJ5MQ0KY2F0KCJMaWJyYXJ5MSBtaXNzaW5nIHZhbHVlczpcbiIpDQpjb2xTdW1zKGlzLm5hKGxpYjEpKQ0KDQojIExpYnJhcnkyDQpjYXQoIkxpYnJhcnkyIG1pc3NpbmcgdmFsdWVzOlxuIikNCmNvbFN1bXMoaXMubmEobGliMikpDQoNCiMgTGlicmFyeTMNCmNhdCgiTGlicmFyeTMgbWlzc2luZyB2YWx1ZXM6XG4iKQ0KY29sU3Vtcyhpcy5uYShsaWIzKSkNCmBgYA0KDQojIyMgQ2VrIER1cGxpa2F0DQoNCmBgYHtyfQ0KY2F0KCJcbkp1bWxhaCBkdXBsaWthdCBXaUZpOiIsIHN1bShkdXBsaWNhdGVkKHdpZmkpKSwgIlxuIikNCmNhdCgiSnVtbGFoIGR1cGxpa2F0IExpYnJhcnkxOiIsIHN1bShkdXBsaWNhdGVkKGxpYjEpKSwgIlxuIikNCmNhdCgiSnVtbGFoIGR1cGxpa2F0IExpYnJhcnkyOiIsIHN1bShkdXBsaWNhdGVkKGxpYjIpKSwgIlxuIikNCmNhdCgiSnVtbGFoIGR1cGxpa2F0IExpYnJhcnkzOiIsIHN1bShkdXBsaWNhdGVkKGxpYjMpKSwgIlxuIikNCg0Kc2FwcGx5KHdpZmksIGNsYXNzKQ0Kc2FwcGx5KGxpYjEsIGNsYXNzKQ0Kc2FwcGx5KGxpYjIsIGNsYXNzKQ0Kc2FwcGx5KGxpYjMsIGNsYXNzKQ0KDQp3aWZpIDwtIHdpZmkgJT4lDQogIG11dGF0ZSgNCiAgICB0aW1lID0geW1kX2htcyh0aW1lKSkgICAgICAgICAgIyBvdG9tYXRpcyBwYXJzZSBqYWRpIGRhdGV0aW1lDQoNCmxpYjEgPC0gbGliMSAlPiUNCiAgbXV0YXRlKHRzID0geW1kX2htcyh0cykpDQoNCmxpYjIgPC0gbGliMiAlPiUNCiAgbXV0YXRlKHRzID0geW1kX2htcyh0cykpDQoNCmxpYjMgPC0gbGliMyAlPiUNCiAgbXV0YXRlKHRzID0geW1kX2htcyh0cykpDQoNCg0KDQpzYXBwbHkod2lmaSwgY2xhc3MpDQpzYXBwbHkobGliMSwgY2xhc3MpDQpzYXBwbHkobGliMiwgY2xhc3MpDQpzYXBwbHkobGliMywgY2xhc3MpDQpgYGANCg0KDQojIyMgQW1iaWwga29sb20gdHMgZGFuIHJhdGUgbWFzaW5nLW1hc2luZyBlbmVyZ2kNCg0KYGBge3J9DQpsaWIxX2NsZWFuIDwtIGxpYjEgJT4lDQogIHNlbGVjdCh0cywgcmF0ZSkgJT4lDQogIHJlbmFtZShlbmVyZ3kxID0gcmF0ZSkNCg0KbGliMl9jbGVhbiA8LSBsaWIyICU+JQ0KICBzZWxlY3QodHMsIHJhdGUpICU+JQ0KICByZW5hbWUoZW5lcmd5MiA9IHJhdGUpDQoNCmxpYjNfY2xlYW4gPC0gbGliMyAlPiUNCiAgc2VsZWN0KHRzLCByYXRlKSAlPiUNCiAgcmVuYW1lKGVuZXJneTMgPSByYXRlKQ0KYGBgDQoNCiMjIw0KDQpgYGB7cn0NCmVuZXJneV9kZiA8LSBsaWIxX2NsZWFuICU+JQ0KICBpbm5lcl9qb2luKGxpYjJfY2xlYW4sIGJ5ID0gInRzIikgJT4lDQogIGlubmVyX2pvaW4obGliM19jbGVhbiwgYnkgPSAidHMiKQ0KYGBgDQojIyMgSGl0dW5nIHRvdGFsIGtvbnN1bXNpIGVuZXJnaQ0KDQpgYGB7cn0NCmVuZXJneV9kZiA8LSBlbmVyZ3lfZGYgJT4lDQogIG11dGF0ZShUb3RhbF9FbmVyZ3kgPSBlbmVyZ3kxICsgZW5lcmd5MiArIGVuZXJneTMpICU+JQ0KICBhcnJhbmdlKHRzKQ0KaGVhZChlbmVyZ3lfZGYsMTApDQpgYGANCiMjIyBQaWxpaCBoYW55YSBrb2xvbSB5YW5nIGRpYnV0dWhrYW4gZGkgd2lmaQ0KDQpgYGB7cn0NCndpZmkgPC0gd2lmaSAlPiUNCiAgc2VsZWN0KHRpbWUsIGBBc3NvY2lhdGVkLkNsaWVudC5Db3VudGApICU+JQ0KICByZW5hbWUoQXNzb2NpYXRlZF9DbGllbnRfQ291bnQgPSBgQXNzb2NpYXRlZC5DbGllbnQuQ291bnRgKQ0KDQpoZWFkKHdpZmksMTApDQpgYGANCg0KIyMjIFJlc2FtcGxpbmcgcGVyIDEwIG1lbml0IChhbWJpbCByYXRhLXJhdGEganVtbGFoIGtvbmVrc2kpDQoNCmBgYHtyfQ0Kd2lmaV9yZXNhbXBsZWQgPC0gd2lmaSAlPiUNCiAgbXV0YXRlKHRpbWUgPSBmbG9vcl9kYXRlKHRpbWUsICIxMCBtaW51dGVzIikpICU+JQ0KICBncm91cF9ieSh0aW1lKSAlPiUNCiAgc3VtbWFyaXNlKEFzc29jaWF0ZWRfQ2xpZW50X0NvdW50ID0gbWVhbihBc3NvY2lhdGVkX0NsaWVudF9Db3VudCwgbmEucm0gPSBUUlVFKSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQpoZWFkKHdpZmlfcmVzYW1wbGVkLDEwKQ0KYGBgDQoNCiMjIyBHYWJ1bmdrYW4gYmVyZGFzYXJrYW4gdGltZXN0YW1wDQoNCmBgYHtyfQ0KZmluYWxfZGYgPC0gd2lmaV9yZXNhbXBsZWQgJT4lDQogIGlubmVyX2pvaW4oZW5lcmd5X2RmLCBieSA9IGMoInRpbWUiID0gInRzIikpDQoNCmhlYWQoZmluYWxfZGYsMTApDQpgYGANCg0KIyMjIFNpbXBhbiBzZWJhZ2FpIENTVg0KYGBge3J9DQp3cml0ZV9jc3YoZmluYWxfZGYsICJmaW5hbF93aWZpX2VuZXJneS5jc3YiKQ0KZ2V0d2QoKQ0KDQojI2ppa2EgZXhjZWwNCiN3cml0ZV94bHN4KGZpbmFsX2RmLCAiZmluYWxfd2lmaV9lbmVyZ3kueGxzeCIpDQpgYGANCiMjICoqVmlzdWFsaXNhc2kqKg0KIyMjICpUaW1lIFNlcmllcyBQbG90Kg0KYGBge3J9DQpmaW5hbF9kZiA8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxEaWFuYSBFa2EgSnVzdGl0aWFcXERvd25sb2Fkc1xcZmluYWxfd2lmaV9lbmVyZ3kuY3N2IikNCmhlYWQoZmluYWxfZGYpIA0KYGBgDQoNCiMjIyBVYmFoIGZvcm1hdCB2YXJpYWJlbCB0aW1lDQpgYGB7cn0NCmZpbmFsX2RmJHRpbWUgPC0gYXMuUE9TSVhjdChmaW5hbF9kZiR0aW1lLCBmb3JtYXQgPSAiJVktJW0tJWRUJUg6JU06JVNaIiwgdHogPSAiVVRDIikNCnN0cihmaW5hbF9kZiR0aW1lKQ0KaGVhZChmaW5hbF9kZiR0aW1lKQ0KYGBgDQojIyMgUGxvdCBUaW1lIFNlcmllcw0KYGBge3J9DQpnZ3Bsb3QoZmluYWxfZGYpICsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gdGltZSwgeSA9IEFzc29jaWF0ZWRfQ2xpZW50X0NvdW50LCBjb2xvciA9ICJBc3NvY2lhdGVkX0NsaWVudF9Db3VudCIpKSArIA0KICBnZW9tX2xpbmUoYWVzKHggPSB0aW1lLCB5ID0gVG90YWxfRW5lcmd5LCBjb2xvciA9ICJUb3RhbCBFbmVyZ3kiKSkgKw0KICBzY2FsZV94X2RhdGV0aW1lKGRhdGVfYnJlYWtzID0gIjEgZGF5IiwgZGF0ZV9sYWJlbHMgPSAiJWItJWQiKSArDQogIHhsYWIoIiIpICsNCiAgeWxhYigiVmFsdWUiKSArDQogIGdndGl0bGUoIlRpbWUgU2VyaWVzOiBBc3NvY2lhdGVkX0NsaWVudF9Db3VudCB2cyBUb3RhbCBFbmVyZ3kgLSBGZWJydWFyaSIpICArDQogIHNjYWxlX2NvbG9yX21hbnVhbCgNCiAgICBuYW1lID0gIlZhcmlhYmxlIiwNCiAgICB2YWx1ZXMgPSBjKCJBc3NvY2lhdGVkX0NsaWVudF9Db3VudCIgPSAiYmx1ZSIsICJUb3RhbCBFbmVyZ3kiID0gInJlZCIpDQogICkrdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkNCmBgYA0KIyMjIFNjYXR0ZXIgUGxvdCBPY2N1cGFuY3kgdnMgVG90YWwgRW5lcmd5DQpgYGB7cn0NCmdncGxvdChmaW5hbF9kZiwgYWVzKHggPUFzc29jaWF0ZWRfQ2xpZW50X0NvdW50ICwgeSA9IFRvdGFsX0VuZXJneSkpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDIsIGFscGhhID0gMC42KSArDQogIHhsYWIoIk9jY3VwYW5jeSIpICsNCiAgeWxhYigiVG90YWwgRW5lcmd5IikgKw0KICBnZ3RpdGxlKCJTY2F0dGVyIFBsb3Q6IE9jY3VwYW5jeSB2cyBUb3RhbCBFbmVyZ3kiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQojIyMgRGFpbHkgUHJvZmlsZSBPY2N1cGFuY3kNCiMjIyMgKnRhbWJhaCB2YXJpYWJlbCB0aW1lX29mX2RheSoNCmBgYHtyfQ0KZmluYWxfZGYgPC0gZmluYWxfZGYgJT4lDQogIG11dGF0ZSh0aW1lX29mX2RheSA9IGFzLm51bWVyaWMoZm9ybWF0KHRpbWUsICIlSCIpKSArIGFzLm51bWVyaWMoZm9ybWF0KHRpbWUsICIlTSIpKS82MCwNCiBkYXRlID0gYXMuZmFjdG9yKGFzLkRhdGUodGltZSkpKQ0KYGBgDQojIyMjICpIaXR1bmcgcmF0YS1yYXRhIHBlciAxMCBtZW5pdCoNCmBgYHtyfQ0KZGFpbHlfYXZnIDwtIGZpbmFsX2RmICU+JQ0KICBncm91cF9ieSh0aW1lX29mX2RheSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBhdmdfb2NjdXBhbmN5ID0gbWVhbihBc3NvY2lhdGVkX0NsaWVudF9Db3VudCwgbmEucm0gPSBUUlVFKSwNCiAgICBhdmdfZW5lcmd5ID0gbWVhbihUb3RhbF9FbmVyZ3ksIG5hLnJtID0gVFJVRSkNCiAgKQ0KYGBgDQoNCiMjIyBQbG90IERhaWx5IE9jY3VwYW5jeSBQcm9maWxlDQpgYGB7cn0NCmdncGxvdChmaW5hbF9kZiwgYWVzKHggPSB0aW1lX29mX2RheSwgeSA9IEFzc29jaWF0ZWRfQ2xpZW50X0NvdW50KSkgKw0KICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gZGF0ZSksIGNvbG9yID0gImdyYXkiLCBhbHBoYSA9IDAuMykgKyAgIyB0aWFwIGhhcmkNCiAgZ2VvbV9saW5lKGRhdGEgPSBkYWlseV9hdmcsIGFlcyh4ID0gdGltZV9vZl9kYXksIHkgPSBhdmdfb2NjdXBhbmN5KSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDEuNSkgKw0KICB4bGFiKCJIb3VyIG9mIERheSIpICsNCiAgeWxhYigiT2NjdXBhbmN5IikgKw0KICBnZ3RpdGxlKCJEYWlseSBPY2N1cGFuY3kgUHJvZmlsZXMgd2l0aCBBdmVyYWdlICgyNiBEYXlzKSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAyNCwgYnkgPSAxKSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQ0KYGBgDQojIyAqKkFuYWxpc2lzKioNCiMjIyAqUGVhayoNCiMjIyMgUGVhayBPY2N1cGFuY3kNCmBgYHtyfQ0KcGVha19vY2N1cGFuY3lfdGltZSA8LSBkYWlseV9hdmckdGltZV9vZl9kYXlbd2hpY2gubWF4KGRhaWx5X2F2ZyRhdmdfb2NjdXBhbmN5KV0NCmBgYA0KIyMjIyBQZWFrIFRvdGFsIEVuZXJneQ0KDQpgYGB7cn0NCnBlYWtfZW5lcmd5X3RpbWUgPC0gZGFpbHlfYXZnJHRpbWVfb2ZfZGF5W3doaWNoLm1heChkYWlseV9hdmckYXZnX2VuZXJneSldDQpgYGANCiMjIyBQbG90IFBlYWsgVGltZQ0KYGBge3J9DQpnZ3Bsb3QoZGFpbHlfYXZnLCBhZXMoeCA9IHRpbWVfb2ZfZGF5KSkgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBhdmdfb2NjdXBhbmN5LCBjb2xvciA9ICJPY2N1cGFuY3kiKSwgbGluZXdpZHRoID0gMS4yKSArDQogIGdlb21fbGluZShhZXMoeSA9IGF2Z19lbmVyZ3ksIGNvbG9yID0gIlRvdGFsIEVuZXJneSIpLCBsaW5ld2lkdGggPSAxLjIpICsNCiAgIyBHYXJpcyB2ZXJ0aWthbCBwZWFrIG90b21hdGlzDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IHBlYWtfb2NjdXBhbmN5X3RpbWUsIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBsaW5ld2lkdGggPSAxKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IHBlYWtfZW5lcmd5X3RpbWUsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIsIGxpbmV3aWR0aCA9IDEpICsNCiAgeGxhYigiSmFtIikgKw0KICB5bGFiKCJSYXRhLXJhdGEiKSArDQogIGdndGl0bGUoIlByb2ZpbCBIYXJpYW4gKDEwIE1lbml0KSAtIE9jY3VwYW5jeSAmIFRvdGFsIEVuZXJneSBkZW5nYW4gUGVhayBPdG9tYXRpcyIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKA0KICAgIGJyZWFrcyA9IHNlcSgwLCAyNCwgYnkgPSAxMC82MCksDQogICAgbGFiZWxzID0gZnVuY3Rpb24oeCkgc3ByaW50ZigiJTAyZDolMDJkIiwgZmxvb3IoeCksIHJvdW5kKCh4IC0gZmxvb3IoeCkpKjYwKSkNCiAgKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJPY2N1cGFuY3kiID0gImJsdWUiLCAiVG90YWwgRW5lcmd5IiA9ICJyZWQiKSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpLA0KICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKQ0KICApDQpgYGANCiMjIyBSZWdyZXMgUG9saW5vbWlhbCANCmBgYHtyfQ0KIyBvcmRlIDINCm1vZGVsX3BvbHkyIDwtIGxtKFRvdGFsX0VuZXJneSB+IHBvbHkoQXNzb2NpYXRlZF9DbGllbnRfQ291bnQsIDIsIHJhdyA9IFRSVUUpLCBkYXRhID0gZmluYWxfZGYpDQoNCiMgcmluZ2thc2FuIG1vZGVsDQpzdW1tYXJ5KG1vZGVsX3BvbHkyKQ0KYGBgDQojIyMgQW5vbWFsaWVzDQoNCmBgYHtyfQ0KI21lbmdoaXR1bmcgcmVzaWR1YWwgDQpmaW5hbF9kZiA8LSBmaW5hbF9kZiAlPiUNCiAgbXV0YXRlKHByZWRpY3RlZF9lbmVyZ3kgPSBwcmVkaWN0KG1vZGVsX3BvbHkyLCBuZXdkYXRhID0gKSwNCiAgICAgICAgIHJlc2lkdWFsID0gVG90YWxfRW5lcmd5IC0gcHJlZGljdGVkX2VuZXJneSkNCg0KIyBtZW5naGl0dW5nIGJhdGFzIHRvbGVyYW5zaSAoKy0yz4MpDQpyZXNfc2QgPC0gc2QoZmluYWxfZGYkcmVzaWR1YWwsIG5hLnJtID0gVFJVRSkNCnVwcGVyX2JvdW5kIDwtIDIgKiByZXNfc2QNCmxvd2VyX2JvdW5kIDwtIC0yICogcmVzX3NkDQoNCiMgTWVuYW5kYWkgYW5vbWFsaSBiZXJkYXNhcmthbiANCmZpbmFsX2RmIDwtIGZpbmFsX2RmICU+JQ0KICBtdXRhdGUoYW5vbWFseSA9IGNhc2Vfd2hlbigNCiAgICByZXNpZHVhbCA+IHVwcGVyX2JvdW5kIH4gIkhpZ2ggRW5lcmd5IiwNCiAgICByZXNpZHVhbCA8IGxvd2VyX2JvdW5kIH4gIkxvdyBFbmVyZ3kiLA0KICAgIFRSVUUgfiAiTm9ybWFsIg0KICApKQ0KYGBgDQojIyMgUGxvdCBBbm9tYWxpDQoNCmBgYHtyfQ0KZ2dwbG90KGZpbmFsX2RmLCBhZXMoeCA9IEFzc29jaWF0ZWRfQ2xpZW50X0NvdW50LCB5ID0gVG90YWxfRW5lcmd5LCBjb2xvciA9IGFub21hbHkpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsNCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZGljdGVkX2VuZXJneSksIGNvbG9yID0gImdyZWVuIiwgbGluZXdpZHRoID0gMSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiTm9ybWFsIiA9ICJncmF5IiwgIkhpZ2ggRW5lcmd5IiA9ICJyZWQiLCAiTG93IEVuZXJneSIgPSAiYmx1ZSIpKSArDQogIHhsYWIoIk9jY3VwYW5jeSIpICsNCiAgeWxhYigiVG90YWwgRW5lcmd5IikgKw0KICBnZ3RpdGxlKCJEZXRla3NpIEFub21hbGkgRW5lcmd5IHZzIE9jY3VwYW5jeSAowrEyz4MgUmVzaWR1YWwpIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KIyMgV2Vla2VuZCBWUyBXZWVrZGF5DQojIyMgTWVtYmVyaSBsYWJlbCB1bnR1ayBoYXJpDQoNCmBgYHtyfQ0KZmluYWxfZGYkZGF5X25hbWUgPC0gd2Vla2RheXMoZmluYWxfZGYkdGltZSkNCmhlYWQoZmluYWxfZGYpDQpgYGANCiMjIyBNZW1iZXJpIGxhYmVsIHdlZWtlbmQgZGFuIHdlZWtkYXkNCg0KYGBge3J9DQpmaW5hbF9kZiRpc193ZWVrZW5kIDwtIGlmZWxzZShmaW5hbF9kZiRkYXlfbmFtZSAlaW4lIGMoIlNhdHVyZGF5IiwgIlN1bmRheSIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IikNCmhlYWQoZmluYWxfZGYpDQpgYGANCiMjIyBQbG90DQpgYGB7cn0NCmRhaWx5X2F2ZyA8LSBmaW5hbF9kZiAlPiUNCiAgZ3JvdXBfYnkodGltZV9vZl9kYXksIGlzX3dlZWtlbmQpICU+JQ0KICBzdW1tYXJpc2UoYXZnX29jY3VwYW5jeSA9IG1lYW4oQXNzb2NpYXRlZF9DbGllbnRfQ291bnQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICAuZ3JvdXBzID0gImRyb3AiKQ0KDQoNCmdncGxvdChkYWlseV9hdmcsIGFlcyh4ID0gdGltZV9vZl9kYXksIHkgPSBhdmdfb2NjdXBhbmN5LCBjb2xvciA9IGlzX3dlZWtlbmQpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxLjIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKA0KICAgIGJyZWFrcyA9IHNlcSgwLCAyNCwgYnkgPSAyKSwNCiAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSBzcHJpbnRmKCIlMDJkOiUwMmQiLCBmbG9vcih4KSwgcm91bmQoKHggLSBmbG9vcih4KSkqNjApKQ0KICApICsNCiAgbGFicyh0aXRsZSA9ICJQcm9maWwgSGFyaWFuIE9jY3VwYW5jeTogV2Vla2RheSB2cyBXZWVrZW5kIiwNCiAgICAgICB4ID0gIkphbSIsIHkgPSAiUmF0YS1yYXRhIE9jY3VwYW5jeSIsIGNvbG9yID0gIkthdGVnb3JpIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KDQpgYGANCiMjIyBQbG90IFRpbWUgU2VyaWVzIFdlZWtlbmQgZGFuIFdlZWtkYXkNCmBgYHtyfQ0KZmluYWxfZGZfaG91cmx5IDwtIGZpbmFsX2RmICU+JQ0KICBtdXRhdGUoaG91ciA9IGx1YnJpZGF0ZTo6Zmxvb3JfZGF0ZSh0aW1lLCAiaG91ciIpKSAlPiUNCiAgZ3JvdXBfYnkoaG91ciwgaXNfd2Vla2VuZCwgZGF5X25hbWUpICU+JQ0KICBzdW1tYXJpc2UoYXZnX29jY3VwYW5jeSA9IG1lYW4gKEFzc29jaWF0ZWRfQ2xpZW50X0NvdW50LCBuYS5ybSA9IFRSVUUpLCAuZ3JvdXAgPSAiZHJvcCIpDQoNCmdncGxvdChmaW5hbF9kZl9ob3VybHksIGFlcyh4ID0gaG91ciwgeSA9IGF2Z19vY2N1cGFuY3ksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gaXNfd2Vla2VuZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBpc193ZWVrZW5kKSkgKw0KICBnZW9tX2xpbmUobGluZXdpZHRoID0gMC45KSArDQogIGxhYnModGl0bGUgPSAiVGltZSBTZXJpZXMgT2NjdXBhbmN5IHBlciBKYW0gKFdlZWtkYXkgdnMgV2Vla2VuZCkiLA0KICAgICAgIHggPSAiV2FrdHUiLCB5ID0gIlJhdGEtcmF0YSBPY2N1cGFuY3kiLCBjb2xvciA9ICJLYXRlZ29yaSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCg0KYGBgDQojIyMgU2NhdHRlciBQbG90IFdlZWtlbmQgZGFuIFdlZWtkYXkNCmBgYHtyfQ0KZ2dwbG90KGZpbmFsX2RmLCBhZXMoeCA9IEFzc29jaWF0ZWRfQ2xpZW50X0NvdW50LCANCiAgICAgICAgICAgICAgICAgICAgIHkgPSBUb3RhbF9FbmVyZ3ksIA0KICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBpc193ZWVrZW5kKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIHNlID0gRkFMU0UsIGNvbG9yID0gImdyZWVuIiwgbGluZXdpZHRoID0gMSkgKw0KICB4bGFiKCJPY2N1cGFuY3kiKSArDQogIHlsYWIoIlRvdGFsIEVuZXJneSIpICsNCiAgZ2d0aXRsZSgiT2NjdXBhbmN5IHZzIFRvdGFsIEVuZXJneTogV2Vla2VuZCB2cyBXZWVrZGF5IikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KDQpgYGA=