blob: 0774423aa79824fd9215129bbf74930bfc11aabc [file] [log] [blame] [edit]
#!/usr/bin/Rscript
#
# Write an overview of task status, per-metric task status, task histograms.
library(data.table)
library(ggplot2)
options(stringsAsFactors = FALSE) # get rid of annoying behavior
Log <- function(fmt, ...) {
cat(sprintf(fmt, ...))
cat('\n')
}
# max of non-NA values; NA if there are none
MaybeMax <- function(values) {
v <- values[!is.na(values)]
if (length(v) == 0) {
m <- NA
} else {
m <- max(v)
}
as.numeric(m) # data.table requires this; otherwise we get type errors
}
# mean of non-NA values; NA if there are none
MaybeMean <- function(values) {
v <- values[!is.na(values)]
if (length(v) == 0) {
m <- NA
} else {
m <- mean(v)
}
as.numeric(m) # data.table require this; otherwise we get type errors
}
WriteDistOverview <- function(summary, output_dir) {
s <- data.table(summary) # data.table syntax is easier here
by_metric <- s[ , list(
params_file = unique(params_file),
map_file = unique(map_file),
days = length(date),
max_num_reports = MaybeMax(num_reports),
# summarize status
ok = sum(status == 'OK'),
fail = sum(status == 'FAIL'),
timeout = sum(status == 'TIMEOUT'),
skipped = sum(status == 'SKIPPED'),
# TODO: Need to document the meaning of these metrics.
# All could be NA
# KiB -> MB
#max_vm5_peak_mb = MaybeMax(vm5_peak_kib * 1024 / 1e6),
#mean_vm5_mean_mb = MaybeMean(vm5_mean_kib * 1024 / 1e6),
mean_secs = MaybeMean(seconds),
mean_allocated_mass = MaybeMean(allocated_mass)
# unique failure reasons
# This can be used when there are different call stacks.
#fail_reasons = length(unique(fail_reason[fail_reason != ""]))
), by=metric]
# Case insensitive sort by metric name
by_metric <- by_metric[order(tolower(by_metric$metric)), ]
overview_path <- file.path(output_dir, 'overview.csv')
write.csv(by_metric, file = overview_path, row.names = FALSE)
Log("Wrote %s", overview_path)
by_metric
}
WriteDistMetricStatus <- function(summary, output_dir) {
# Write status.csv, num_reports.csv, and mass.csv for each metric.
s <- data.table(summary)
# loop over unique metrics, and write a CSV for each one
for (m in unique(s$metric)) {
# Select cols, and convert units. Don't need params / map / metric.
subframe <- s[s$metric == m,
list(job_id, date, status,
#vm5_peak_mb = vm5_peak_kib * 1024 / 1e6,
#vm5_mean_mb = vm5_mean_kib * 1024 / 1e6,
num_reports,
seconds,
allocated_mass, num_rappor)]
# Sort by descending date. Alphabetical sort works fine for YYYY-MM-DD.
subframe <- subframe[order(subframe$date, decreasing = TRUE), ]
out_path = file.path(output_dir, m, 'status.csv')
write.csv(subframe, file = out_path, row.names = FALSE)
Log("Wrote %s", out_path)
}
# This one is just for plotting with dygraphs. TODO: can dygraphs do
# something smarter? Maybe you need to select the column in JavaScript, and
# pass it an array, rather than CSV text.
for (m in unique(s$metric)) {
f1 <- s[s$metric == m, list(date, num_reports)]
path1 <- file.path(output_dir, m, 'num_reports.csv')
# NOTE: dygraphs (only in Firefox?) doesn't like the quotes around
# "2015-04-03". In general, we can't turn off quotes, because strings with
# double quotes will be invalid CSV files. But in this case, we only have
# date and number columns, so we can. dygraphs is mistaken here.
write.csv(f1, file = path1, row.names = FALSE, quote = FALSE)
Log("Wrote %s", path1)
# Write unallocated mass. TODO: Write the other 2 vars too?
f2 <- s[s$metric == m,
list(date,
unallocated_mass = 1.0 - allocated_mass)]
path2 <- file.path(output_dir, m, 'mass.csv')
write.csv(f2, file = path2, row.names = FALSE, quote = FALSE)
Log("Wrote %s", path2)
}
}
WritePlot <- function(p, outdir, filename, width = 800, height = 600) {
filename <- file.path(outdir, filename)
png(filename, width = width, height = height)
plot(p)
dev.off()
Log('Wrote %s', filename)
}
# Make sure the histogram has some valid input. If we don't do this, ggplot
# blows up with an unintuitive error message.
CheckHistogramInput <- function(v) {
if (all(is.na(v))) {
arg_name <- deparse(substitute(v)) # R idiom to get name
Log('FATAL: All values in %s are NA (no successful runs?)', arg_name)
quit(status = 1)
}
}
WriteDistHistograms <- function(s, output_dir) {
CheckHistogramInput(s$allocated_mass)
p <- qplot(s$allocated_mass, geom = "histogram")
t <- ggtitle("Allocated Mass by Task")
x <- xlab("allocated mass")
y <- ylab("number of tasks")
WritePlot(p + t + x + y, output_dir, 'allocated_mass.png')
CheckHistogramInput(s$num_rappor)
p <- qplot(s$num_rappor, geom = "histogram")
t <- ggtitle("Detected Strings by Task")
x <- xlab("detected strings")
y <- ylab("number of tasks")
WritePlot(p + t + x + y, output_dir, 'num_rappor.png')
CheckHistogramInput(s$num_reports)
p <- qplot(s$num_reports / 1e6, geom = "histogram")
t <- ggtitle("Raw Reports by Task")
x <- xlab("millions of reports")
y <- ylab("number of tasks")
WritePlot(p + t + x + y, output_dir, 'num_reports.png')
CheckHistogramInput(s$seconds)
p <- qplot(s$seconds, geom = "histogram")
t <- ggtitle("Analysis Duration by Task")
x <- xlab("seconds")
y <- ylab("number of tasks")
WritePlot(p + t + x + y, output_dir, 'seconds.png')
# NOTE: Skipping this for 'series' jobs.
if (sum(!is.na(s$vm5_peak_kib)) > 0) {
p <- qplot(s$vm5_peak_kib * 1024 / 1e6, geom = "histogram")
t <- ggtitle("Peak Memory Usage by Task")
x <- xlab("Peak megabytes (1e6 bytes) of memory")
y <- ylab("number of tasks")
WritePlot(p + t + x + y, output_dir, 'memory.png')
}
}
ProcessAllDist <- function(s, output_dir) {
Log('dist: Writing per-metric status.csv')
WriteDistMetricStatus(s, output_dir)
Log('dist: Writing histograms')
WriteDistHistograms(s, output_dir)
Log('dist: Writing aggregated overview.csv')
WriteDistOverview(s, output_dir)
}
# Write the single CSV file loaded by assoc-overview.html.
WriteAssocOverview <- function(summary, output_dir) {
s <- data.table(summary) # data.table syntax is easier here
by_metric <- s[ , list(
#params_file = unique(params_file),
#map_file = unique(map_file),
days = length(date),
max_num_reports = MaybeMax(num_reports),
# summarize status
ok = sum(status == 'OK'),
fail = sum(status == 'FAIL'),
timeout = sum(status == 'TIMEOUT'),
skipped = sum(status == 'SKIPPED'),
mean_total_secs = MaybeMean(total_elapsed_seconds),
mean_em_secs = MaybeMean(em_elapsed_seconds)
), by=list(metric)]
# Case insensitive sort by metric name
by_metric <- by_metric[order(tolower(by_metric$metric)), ]
overview_path <- file.path(output_dir, 'assoc-overview.csv')
write.csv(by_metric, file = overview_path, row.names = FALSE)
Log("Wrote %s", overview_path)
by_metric
}
# Write the CSV files loaded by assoc-metric.html -- that is, one
# metric-status.csv for each metric name.
WriteAssocMetricStatus <- function(summary, output_dir) {
s <- data.table(summary)
csv_list <- unique(s[, list(metric)])
for (i in 1:nrow(csv_list)) {
u <- csv_list[i, ]
# Select cols, and convert units. Don't need params / map / metric.
by_pair <- s[s$metric == u$metric,
list(days = length(date),
max_num_reports = MaybeMax(num_reports),
# summarize status
ok = sum(status == 'OK'),
fail = sum(status == 'FAIL'),
timeout = sum(status == 'TIMEOUT'),
skipped = sum(status == 'SKIPPED'),
mean_total_secs = MaybeMean(total_elapsed_seconds),
mean_em_secs = MaybeMean(em_elapsed_seconds)
),
by=list(var1, var2)]
# Case insensitive sort by var1 name
by_pair <- by_pair[order(tolower(by_pair$var1)), ]
csv_path <- file.path(output_dir, u$metric, 'metric-status.csv')
write.csv(by_pair, file = csv_path, row.names = FALSE)
Log("Wrote %s", csv_path)
}
}
# This naming convention is in task_spec.py AssocTaskSpec.
FormatAssocRelPath <- function(metric, var1, var2) {
v2 <- gsub('..', '_', var2, fixed = TRUE)
var_dir <- sprintf('%s_X_%s', var1, v2)
file.path(metric, var_dir)
}
# Write the CSV files loaded by assoc-pair.html -- that is, one pair-status.csv
# for each (metric, var1, var2) pair.
WriteAssocPairStatus <- function(summary, output_dir) {
s <- data.table(summary)
csv_list <- unique(s[, list(metric, var1, var2)])
Log('CSV list:')
print(csv_list)
# loop over unique metrics, and write a CSV for each one
for (i in 1:nrow(csv_list)) {
u <- csv_list[i, ]
# Select cols, and convert units. Don't need params / map / metric.
subframe <- s[s$metric == u$metric & s$var1 == u$var1 & s$var2 == u$var2,
list(job_id, date, status,
num_reports, d1, d2,
total_elapsed_seconds,
em_elapsed_seconds)]
# Sort by descending date. Alphabetical sort works fine for YYYY-MM-DD.
subframe <- subframe[order(subframe$date, decreasing = TRUE), ]
pair_rel_path <- FormatAssocRelPath(u$metric, u$var1, u$var2)
csv_path <- file.path(output_dir, pair_rel_path, 'pair-status.csv')
write.csv(subframe, file = csv_path, row.names = FALSE)
Log("Wrote %s", csv_path)
# Write a file with the raw variable names. Parsed by ui.sh, to pass to
# csv_to_html.py.
meta_path <- file.path(output_dir, pair_rel_path, 'pair-metadata.txt')
# NOTE: The conversion from data.table to character vector requires
# stringsAsFactors to work correctly!
lines <- as.character(u)
writeLines(lines, con = meta_path)
Log("Wrote %s", meta_path)
}
}
ProcessAllAssoc <- function(s, output_dir) {
Log('assoc: Writing pair-status.csv for each variable pair in each metric')
WriteAssocPairStatus(s, output_dir)
Log('assoc: Writing metric-status.csv for each metric')
WriteAssocMetricStatus(s, output_dir)
Log('assoc: Writing aggregated overview.csv')
WriteAssocOverview(s, output_dir)
}
main <- function(argv) {
# increase ggplot font size globally
theme_set(theme_grey(base_size = 16))
action = argv[[1]]
input = argv[[2]]
output_dir = argv[[3]]
if (action == 'dist') {
summary = read.csv(input)
ProcessAllDist(summary, output_dir)
} else if (action == 'assoc') {
summary = read.csv(input)
ProcessAllAssoc(summary, output_dir)
} else {
stop(sprintf('Invalid action %s', action))
}
Log('Done')
}
if (length(sys.frames()) == 0) {
main(commandArgs(TRUE))
}