Allow to select datafile for charts/explore/compare (filtered/non-filtered)

This commit is contained in:
2026-06-14 17:06:43 +02:00
parent 1d1ed3581a
commit e3be326c42
7 changed files with 226 additions and 70 deletions

View File

@ -281,6 +281,27 @@ sub delete_marker_info {
"$local_archive_dir/$experiment/markers/$benchmark-$address.info" ); "$local_archive_dir/$experiment/markers/$benchmark-$address.info" );
} }
sub pick_data_file {
my ( $dir, $prefix ) = @_;
# \Q...\E treats ... as literal string
my @files = sort grep { /^\Q$prefix\E.*\.csv$/ } find_files($dir);
return "$prefix.csv" unless @files > 1;
# Make sure the unfiltered file is at the top
my @sorted = sort {
( $a eq "$prefix.csv" ) ? -1
: ( $b eq "$prefix.csv" ) ? 1
: $a cmp $b
} @files;
my @selected =
TUI::select_from_list( "Select $prefix CSV file", 0, @sorted );
die "No $prefix CSV file selected" unless @selected;
return $selected[0];
}
sub select_experiment { sub select_experiment {
my ($multi) = @_; my ($multi) = @_;

View File

@ -6,13 +6,16 @@ library(ggalluvial)
args <- commandArgs(trailingOnly = TRUE) args <- commandArgs(trailingOnly = TRUE)
argc <- length(args) argc <- length(args)
if (argc != 2) { if (argc < 2 || argc > 3) {
print("Expecting two input files") print("Expecting two or three arguments: exp1 exp2 [faults_file]")
stop() stop()
} }
for (experiment in args) { faults_file <- if (argc == 3) args[3] else "faults.csv"
datafile <- paste(experiment, "/faults.csv", sep = "") suffix <- gsub("^faults|\\.csv$", "", faults_file)
for (experiment in args[1:2]) {
datafile <- file.path(experiment, faults_file)
if (!file.exists(datafile)) { if (!file.exists(datafile)) {
print(paste("Input file", datafile, "is missing")) print(paste("Input file", datafile, "is missing"))
stop() stop()
@ -30,13 +33,13 @@ resulttype_labels <- c(
) )
# Read data # Read data
datafile1 <- paste(args[1], "/faults.csv", sep = "") datafile1 <- file.path(args[1], faults_file)
data1 <- readr::read_csv(datafile1) data1 <- readr::read_csv(datafile1)
data1$fault_address <- strtoi(data1$fault_address) data1$fault_address <- strtoi(data1$fault_address)
data1$resulttype <- resulttype_labels[data1$resulttype] data1$resulttype <- resulttype_labels[data1$resulttype]
# tibble::glimpse(data1) # tibble::glimpse(data1)
datafile2 <- paste(args[2], "/faults.csv", sep = "") datafile2 <- file.path(args[2], faults_file)
data2 <- readr::read_csv(datafile2) data2 <- readr::read_csv(datafile2)
data2$fault_address <- strtoi(data2$fault_address) data2$fault_address <- strtoi(data2$fault_address)
data2$resulttype <- resulttype_labels[data2$resulttype] data2$resulttype <- resulttype_labels[data2$resulttype]
@ -75,6 +78,6 @@ plot <- ggplot(
# TODO: Name the file according to the benchmarks # TODO: Name the file according to the benchmarks
ggsave( ggsave(
paste(args[2], "/../sankey.svg", sep = ""), paste0(args[2], "/../sankey", suffix, ".svg"),
plot = plot, plot = plot,
) )

View File

@ -29,27 +29,84 @@ max_tile <- 0.5
# Generate all heatmaps with crossproduct of this # Generate all heatmaps with crossproduct of this
benchmarks <- c("ip", "mem", "regs") benchmarks <- c("ip", "mem", "regs")
markers <- c( markers <- c(
"OK_MARKER", "FAIL_MARKER", "DETECTED_MARKER", "OK_MARKER",
"ACCESS_OUTERSPACE", "WRITE_TEXTSEGMENT", "FAIL_MARKER",
"GROUP1_MARKER", "TRAP", "TIMEOUT" "DETECTED_MARKER",
"ACCESS_OUTERSPACE",
"WRITE_TEXTSEGMENT",
"GROUP1_MARKER",
"TRAP",
"TIMEOUT"
) )
# Labels for _start/_end symbols from linker.ld # Labels for _start/_end symbols from linker.ld
regions <- list( regions <- list(
list(label = "WAMR AOT", start = "_wamr_aot_start", end = "_wamr_aot_end"), list(label = "WAMR AOT", start = "_wamr_aot_start", end = "_wamr_aot_end"),
list(label = "WAMR os_mmap", start = "_wamr_mmap_start", end = "_wamr_mmap_end"), list(
list(label = "WAMR runtime mem", start = "_wamr_runtime_pool_start", end = "_wamr_runtime_pool_end"), label = "WAMR os_mmap",
list(label = "WAMR linear mem", start = "_wamr_linear_pool_start", end = "_wamr_linear_pool_end"), start = "_wamr_mmap_start",
list(label = "WAMR global heap", start = "_wamr_global_heap_start", end = "_wamr_global_heap_end"), end = "_wamr_mmap_end"
list(label = "IWASM AOT runtime", start = "_iwasm_aot_runtime_start", end = "_iwasm_aot_runtime_end"), ),
list(label = "IWASM bh/util", start = "_iwasm_bh_start", end = "_iwasm_bh_end"), list(
list(label = "IWASM mem_alloc", start = "_iwasm_mem_alloc_start", end = "_iwasm_mem_alloc_end"), label = "WAMR runtime mem",
list(label = "IWASM platform", start = "_iwasm_platform_init_start", end = "_iwasm_platform_init_end"), start = "_wamr_runtime_pool_start",
list(label = "IWASM exec_env", start = "_iwasm_exec_env_start", end = "_iwasm_exec_env_end"), end = "_wamr_runtime_pool_end"
list(label = "IWASM interp", start = "_iwasm_interp_classic_start", end = "_iwasm_interp_classic_end"), ),
list(label = "IWASM memory", start = "_iwasm_memory_start", end = "_iwasm_memory_end"), list(
list(label = "IWASM native", start = "_iwasm_native_start", end = "_iwasm_native_end"), label = "WAMR linear mem",
list(label = "IWASM runtime", start = "_iwasm_runtime_start", end = "_iwasm_runtime_end"), start = "_wamr_linear_pool_start",
end = "_wamr_linear_pool_end"
),
list(
label = "WAMR global heap",
start = "_wamr_global_heap_start",
end = "_wamr_global_heap_end"
),
list(
label = "IWASM AOT runtime",
start = "_iwasm_aot_runtime_start",
end = "_iwasm_aot_runtime_end"
),
list(
label = "IWASM bh/util",
start = "_iwasm_bh_start",
end = "_iwasm_bh_end"
),
list(
label = "IWASM mem_alloc",
start = "_iwasm_mem_alloc_start",
end = "_iwasm_mem_alloc_end"
),
list(
label = "IWASM platform",
start = "_iwasm_platform_init_start",
end = "_iwasm_platform_init_end"
),
list(
label = "IWASM exec_env",
start = "_iwasm_exec_env_start",
end = "_iwasm_exec_env_end"
),
list(
label = "IWASM interp",
start = "_iwasm_interp_classic_start",
end = "_iwasm_interp_classic_end"
),
list(
label = "IWASM memory",
start = "_iwasm_memory_start",
end = "_iwasm_memory_end"
),
list(
label = "IWASM native",
start = "_iwasm_native_start",
end = "_iwasm_native_end"
),
list(
label = "IWASM runtime",
start = "_iwasm_runtime_start",
end = "_iwasm_runtime_end"
),
list(label = "TEXT", start = "_text_start", end = "_text_end"), list(label = "TEXT", start = "_text_start", end = "_text_end"),
list(label = "BSS", start = "_sbss", end = "_ebss") list(label = "BSS", start = "_sbss", end = "_ebss")
) )
@ -75,22 +132,27 @@ if (length(args) < 1) {
} }
experiment <- args[1] experiment <- args[1]
faults_file <- if (length(args) >= 2) args[2] else "faults.csv"
suffix <- gsub("^faults|\\.csv$", "", faults_file)
# ============================================================================= # =============================================================================
# INPUT DATA (read once) # INPUT DATA (read once)
# ============================================================================= # =============================================================================
datafile <- file.path(experiment, "faults.csv") datafile <- file.path(experiment, faults_file)
if (!file.exists(datafile)) { if (!file.exists(datafile)) {
stop(paste("Input file not found:", datafile)) stop(paste("Input file not found:", datafile))
} }
raw <- read_csv(datafile, col_types = cols( raw <- read_csv(
benchmark = col_character(), datafile,
resulttype = col_character(), col_types = cols(
faults = col_double(), benchmark = col_character(),
fault_address = col_character() # hex string "0x10001A"; converted below resulttype = col_character(),
)) faults = col_double(),
fault_address = col_character() # hex string "0x10001A"; converted below
)
)
# ============================================================================= # =============================================================================
# ELF SYMBOLS (parsed once) # ELF SYMBOLS (parsed once)
@ -155,10 +217,12 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
# "0x10001A" -> substr strips "0x" -> strtoi parses base-16 -> integer # "0x10001A" -> substr strips "0x" -> strtoi parses base-16 -> integer
aggregated <- aggregated |> aggregated <- aggregated |>
mutate(addr_int = strtoi( mutate(
substr(.data$fault_address, 3L, nchar(.data$fault_address)), addr_int = strtoi(
16L substr(.data$fault_address, 3L, nchar(.data$fault_address)),
)) 16L
)
)
# =========================================================================== # ===========================================================================
# SCALE ROWS # SCALE ROWS
@ -168,17 +232,24 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
row_width <- row_width_init row_width <- row_width_init
# Double row_width until occupied rows <= max_rows # Double row_width until occupied rows <= max_rows
while (row_width < 65536L && n_occupied_rows( while (
aggregated$addr_int, row_width row_width < 65536L &&
) > max_rows) { n_occupied_rows(
aggregated$addr_int,
row_width
) >
max_rows
) {
row_width <- row_width * 2L row_width <- row_width * 2L
} }
if (row_width > row_width_init) { if (row_width > row_width_init) {
message(sprintf( message(sprintf(
"Note: [%s/%s] row_width auto-scaled to %d (%d occupied rows)", "Note: [%s/%s] row_width auto-scaled to %d (%d occupied rows)",
target_resulttype, target_benchmark, target_resulttype,
row_width, n_occupied_rows(aggregated$addr_int, row_width) target_benchmark,
row_width,
n_occupied_rows(aggregated$addr_int, row_width)
)) ))
} }
@ -210,8 +281,8 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
# - Adding that offset to 1...n gives the row_idx values with gap slots # - Adding that offset to 1...n gives the row_idx values with gap slots
cumulative_gaps <- cumsum(has_gap_before) cumulative_gaps <- cumsum(has_gap_before)
row_order <- tibble( row_order <- tibble(
row = rows_sorted, row = rows_sorted,
row_idx = seq_len(n_data_rows) + cumulative_gaps, row_idx = seq_len(n_data_rows) + cumulative_gaps,
has_gap_before = has_gap_before has_gap_before = has_gap_before
) )
@ -240,8 +311,8 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
region_rects <- data.frame( region_rects <- data.frame(
label = character(0), label = character(0),
ymin = numeric(0), ymin = numeric(0),
ymax = numeric(0) ymax = numeric(0)
) )
if (length(sym_addr) > 0) { if (length(sym_addr) > 0) {
@ -256,7 +327,8 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
# Row with base address r covers bytes r ... r + row_width - 1. # Row with base address r covers bytes r ... r + row_width - 1.
# Overlap if r < e && r + row_width > s # Overlap if r < e && r + row_width > s
overlapping <- row_order[ overlapping <- row_order[
row_order$row < e & (row_order$row + row_width) > s, , row_order$row < e & (row_order$row + row_width) > s,
,
drop = FALSE drop = FALSE
] ]
@ -266,8 +338,8 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
data.frame( data.frame(
label = reg$label, label = reg$label,
ymin = min(overlapping$row_idx) - 0.5, ymin = min(overlapping$row_idx) - 0.5,
ymax = max(overlapping$row_idx) + 0.5 ymax = max(overlapping$row_idx) + 0.5
) )
}) })
@ -310,9 +382,14 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
# PLOT # PLOT
# =========================================================================== # ===========================================================================
plot <- ggplot(grid_complete, aes( plot <- ggplot(
x = col, y = .data$row_idx, fill = .data$faults grid_complete,
)) + aes(
x = col,
y = .data$row_idx,
fill = .data$faults
)
) +
# One rectangle per (col, row_idx) tuple # One rectangle per (col, row_idx) tuple
geom_tile(width = 1, height = 1, colour = NA) + geom_tile(width = 1, height = 1, colour = NA) +
@ -330,10 +407,10 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
# Heatmap color ramp # Heatmap color ramp
scale_fill_viridis_c( scale_fill_viridis_c(
name = "Faults", name = "Faults",
trans = "log1p", trans = "log1p",
na.value = "grey85", na.value = "grey85",
option = "viridis" option = "viridis"
) + ) +
# X-axis hex labels # X-axis hex labels
@ -355,10 +432,13 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
# Title + axis labels # Title + axis labels
labs( labs(
title = paste(target_resulttype, "/", target_benchmark), title = paste(target_resulttype, "/", target_benchmark),
subtitle = paste("Total:", format( subtitle = paste(
sum(aggregated$faults, na.rm = TRUE), "Total:",
big.mark = "," format(
)), sum(aggregated$faults, na.rm = TRUE),
big.mark = ","
)
),
x = "Byte Offset", x = "Byte Offset",
y = "Base Address" y = "Base Address"
) + ) +
@ -367,7 +447,10 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
theme_minimal() + theme_minimal() +
theme( theme(
axis.text.x = element_text( axis.text.x = element_text(
family = "mono", angle = 45, hjust = 1, size = 9 family = "mono",
angle = 45,
hjust = 1,
size = 9
), ),
axis.text.y = element_text(family = "mono", size = 9), axis.text.y = element_text(family = "mono", size = 9),
panel.grid = element_blank(), panel.grid = element_blank(),
@ -399,9 +482,17 @@ make_heatmap <- function(target_resulttype, target_benchmark) {
fig_w <- row_width * tile_size + 4.5 fig_w <- row_width * tile_size + 4.5
fig_h <- total_slots * tile_size + 2.5 fig_h <- total_slots * tile_size + 2.5
outfile <- file.path(experiment, paste0( outfile <- file.path(
"heatmap_", target_resulttype, "_", target_benchmark, ".svg" experiment,
)) paste0(
"heatmap_",
target_resulttype,
"_",
target_benchmark,
suffix,
".svg"
)
)
ggsave(outfile, plot = plot, width = fig_w, height = fig_h, units = "in") ggsave(outfile, plot = plot, width = fig_w, height = fig_h, units = "in")
message(sprintf("Saved: %s", basename(outfile))) message(sprintf("Saved: %s", basename(outfile)))

View File

@ -1,10 +1,12 @@
library(ggplot2) library(ggplot2)
# Usage: Rscript single_result.r exp_abspath # Usage: Rscript single_result.r exp_abspath [resultsdata_file]
args <- commandArgs(trailingOnly = TRUE) args <- commandArgs(trailingOnly = TRUE)
experiment <- args[1] experiment <- args[1]
datafile <- paste(experiment, "/resultsdata.csv", sep = "") resultsdata_file <- if (length(args) >= 2) args[2] else "resultsdata.csv"
suffix <- gsub("^resultsdata|\\.csv$", "", resultsdata_file)
datafile <- file.path(experiment, resultsdata_file)
if (!file.exists(datafile)) { if (!file.exists(datafile)) {
print(paste("Input file", datafile, "is missing")) print(paste("Input file", datafile, "is missing"))
@ -21,6 +23,6 @@ plot <- ggplot(data, aes(x = benchmark, y = faults, fill = resulttype)) +
theme_minimal() theme_minimal()
ggsave( ggsave(
paste(experiment, "/single_result.svg", sep = ""), paste0(experiment, "/single_result", suffix, ".svg"),
plot = plot, plot = plot,
) )

View File

@ -6,7 +6,9 @@ library(ggplot2)
args <- commandArgs(trailingOnly = TRUE) args <- commandArgs(trailingOnly = TRUE)
experiment <- args[1] experiment <- args[1]
datafile <- paste(experiment, "/faults.csv", sep = "") faults_file <- if (length(args) >= 2) args[2] else "faults.csv"
suffix <- gsub("^faults|\\.csv$", "", faults_file)
datafile <- file.path(experiment, faults_file)
if (!file.exists(datafile)) { if (!file.exists(datafile)) {
print(paste("Input file", datafile, "is missing")) print(paste("Input file", datafile, "is missing"))
@ -27,6 +29,6 @@ plot <- ggplot(data, aes(x = fault_address, y = faults)) +
theme_minimal() theme_minimal()
ggsave( ggsave(
paste(experiment, "/scatter.svg", sep = ""), paste0(experiment, "/scatter", suffix, ".svg"),
plot = plot, plot = plot,
) )

View File

@ -23,6 +23,9 @@ my $local_archive_dir = "$local_root/injections";
# Select experiment to open # Select experiment to open
my $selected_experiment = Util::select_experiment(0); my $selected_experiment = Util::select_experiment(0);
my $selected_faults_csv =
Util::pick_data_file( "$local_archive_dir/$selected_experiment", "faults" );
my $cui = TUI::init_cui(); my $cui = TUI::init_cui();
# TODO: Add a TextEditor panel beside the experiment selection for notes. # TODO: Add a TextEditor panel beside the experiment selection for notes.
@ -95,7 +98,7 @@ sub load_faults_csv {
# Schema: benchmark, resulttype, faults, fault_address # Schema: benchmark, resulttype, faults, fault_address
my $data = Text::CSV_XS::csv( my $data = Text::CSV_XS::csv(
in => "$local_archive_dir/$selected_experiment/faults.csv", in => "$local_archive_dir/$selected_experiment/$selected_faults_csv",
headers => 'auto' headers => 'auto'
); );

View File

@ -241,13 +241,18 @@ my %handlers = (
my @selected_experiments = Util::select_experiment(1); my @selected_experiments = Util::select_experiment(1);
# TODO: Fails silently if not every selected experiment has this datafile
my $resultsdata_csv =
Util::pick_data_file( "$local_archive_dir/$selected_experiments[0]",
"resultsdata" );
# Read results # Read results
my %all_results; my %all_results;
foreach my $experiment (@selected_experiments) { foreach my $experiment (@selected_experiments) {
# Schema: benchmark, resulttype, faults # Schema: benchmark, resulttype, faults
my $data = Text::CSV_XS::csv( my $data = Text::CSV_XS::csv(
in => "$local_archive_dir/$experiment/resultsdata.csv", in => "$local_archive_dir/$experiment/$resultsdata_csv",
headers => 'auto' headers => 'auto'
); );
@ -479,15 +484,38 @@ my %handlers = (
TUI::select_from_list( "Select Plots to Generate", 1, @charts ); TUI::select_from_list( "Select Plots to Generate", 1, @charts );
die "No plot selected" unless @selected_charts; die "No plot selected" unless @selected_charts;
# Need to know which chart uses which datafile
my @faults_charts = grep { /heatmap|scatter|sankey/ } @selected_charts;
my @resultsdata_charts =
grep { /result$|combined_comparison/ } @selected_charts;
my $faults_csv;
my $resultsdata_csv;
if (@faults_charts) {
$faults_csv = Util::pick_data_file(
"$local_archive_dir/$selected_experiments[0]", "faults" );
}
if (@resultsdata_charts) {
$resultsdata_csv = Util::pick_data_file(
"$local_archive_dir/$selected_experiments[0]",
"resultsdata" );
}
my @single_charts = grep { /single/ } @selected_charts; my @single_charts = grep { /single/ } @selected_charts;
foreach my $experiment (@selected_experiments) { foreach my $experiment (@selected_experiments) {
foreach my $chart (@single_charts) { foreach my $chart (@single_charts) {
say " - Generating plot $chart for $experiment..."; say " - Generating plot $chart for $experiment...";
system( my @r_args = (
'Rscript', 'Rscript',
"$local_charts_dir/$chart.r", "$local_charts_dir/$chart.r",
"$local_archive_dir/$experiment" "$local_archive_dir/$experiment"
); );
push @r_args, $faults_csv
if defined $faults_csv && $chart =~ /heatmap|scatter|sankey/;
push @r_args, $resultsdata_csv
if defined $resultsdata_csv
&& $chart =~ /result$|combined_comparison/;
system(@r_args);
} }
} }
@ -497,8 +525,14 @@ my %handlers = (
map { "$local_archive_dir/$_" } @selected_experiments; map { "$local_archive_dir/$_" } @selected_experiments;
foreach my $chart (@combined_charts) { foreach my $chart (@combined_charts) {
say " - Generating plot $chart for ($print_experiments)..."; say " - Generating plot $chart for ($print_experiments)...";
system( 'Rscript', "$local_charts_dir/$chart.r", my @r_args =
@path_experiments ); ( 'Rscript', "$local_charts_dir/$chart.r", @path_experiments );
push @r_args, $faults_csv
if defined $faults_csv && $chart =~ /heatmap|scatter|sankey/;
push @r_args, $resultsdata_csv
if defined $resultsdata_csv
&& $chart =~ /result$|combined_comparison/;
system(@r_args);
} }
}, },