From e3be326c42bcbf70a01ea03b3fddf70e052360bf Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Sun, 14 Jun 2026 17:06:43 +0200 Subject: [PATCH] Allow to select datafile for charts/explore/compare (filtered/non-filtered) --- scripts/Modules/Util.pm | 21 ++++ scripts/charts/combined_sankey.r | 17 +-- scripts/charts/single_heatmap.r | 197 ++++++++++++++++++++++--------- scripts/charts/single_result.r | 8 +- scripts/charts/single_scatter.r | 6 +- scripts/explore.pl | 5 +- scripts/menu.pl | 42 ++++++- 7 files changed, 226 insertions(+), 70 deletions(-) diff --git a/scripts/Modules/Util.pm b/scripts/Modules/Util.pm index e35f3cc..038b364 100644 --- a/scripts/Modules/Util.pm +++ b/scripts/Modules/Util.pm @@ -281,6 +281,27 @@ sub delete_marker_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 { my ($multi) = @_; diff --git a/scripts/charts/combined_sankey.r b/scripts/charts/combined_sankey.r index 88f6e9b..2ffe9c2 100644 --- a/scripts/charts/combined_sankey.r +++ b/scripts/charts/combined_sankey.r @@ -6,13 +6,16 @@ library(ggalluvial) args <- commandArgs(trailingOnly = TRUE) argc <- length(args) -if (argc != 2) { - print("Expecting two input files") +if (argc < 2 || argc > 3) { + print("Expecting two or three arguments: exp1 exp2 [faults_file]") stop() } -for (experiment in args) { - datafile <- paste(experiment, "/faults.csv", sep = "") +faults_file <- if (argc == 3) args[3] else "faults.csv" +suffix <- gsub("^faults|\\.csv$", "", faults_file) + +for (experiment in args[1:2]) { + datafile <- file.path(experiment, faults_file) if (!file.exists(datafile)) { print(paste("Input file", datafile, "is missing")) stop() @@ -30,13 +33,13 @@ resulttype_labels <- c( ) # Read data -datafile1 <- paste(args[1], "/faults.csv", sep = "") +datafile1 <- file.path(args[1], faults_file) data1 <- readr::read_csv(datafile1) data1$fault_address <- strtoi(data1$fault_address) data1$resulttype <- resulttype_labels[data1$resulttype] # tibble::glimpse(data1) -datafile2 <- paste(args[2], "/faults.csv", sep = "") +datafile2 <- file.path(args[2], faults_file) data2 <- readr::read_csv(datafile2) data2$fault_address <- strtoi(data2$fault_address) data2$resulttype <- resulttype_labels[data2$resulttype] @@ -75,6 +78,6 @@ plot <- ggplot( # TODO: Name the file according to the benchmarks ggsave( - paste(args[2], "/../sankey.svg", sep = ""), + paste0(args[2], "/../sankey", suffix, ".svg"), plot = plot, ) diff --git a/scripts/charts/single_heatmap.r b/scripts/charts/single_heatmap.r index 99d739b..744858d 100644 --- a/scripts/charts/single_heatmap.r +++ b/scripts/charts/single_heatmap.r @@ -29,27 +29,84 @@ max_tile <- 0.5 # Generate all heatmaps with crossproduct of this benchmarks <- c("ip", "mem", "regs") markers <- c( - "OK_MARKER", "FAIL_MARKER", "DETECTED_MARKER", - "ACCESS_OUTERSPACE", "WRITE_TEXTSEGMENT", - "GROUP1_MARKER", "TRAP", "TIMEOUT" + "OK_MARKER", + "FAIL_MARKER", + "DETECTED_MARKER", + "ACCESS_OUTERSPACE", + "WRITE_TEXTSEGMENT", + "GROUP1_MARKER", + "TRAP", + "TIMEOUT" ) # Labels for _start/_end symbols from linker.ld regions <- list( 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(label = "WAMR runtime mem", start = "_wamr_runtime_pool_start", end = "_wamr_runtime_pool_end"), - list(label = "WAMR linear mem", 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 = "WAMR os_mmap", + start = "_wamr_mmap_start", + end = "_wamr_mmap_end" + ), + list( + label = "WAMR runtime mem", + start = "_wamr_runtime_pool_start", + end = "_wamr_runtime_pool_end" + ), + list( + label = "WAMR linear mem", + 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 = "BSS", start = "_sbss", end = "_ebss") ) @@ -75,22 +132,27 @@ if (length(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) # ============================================================================= -datafile <- file.path(experiment, "faults.csv") +datafile <- file.path(experiment, faults_file) if (!file.exists(datafile)) { stop(paste("Input file not found:", datafile)) } -raw <- read_csv(datafile, col_types = cols( - benchmark = col_character(), - resulttype = col_character(), - faults = col_double(), - fault_address = col_character() # hex string "0x10001A"; converted below -)) +raw <- read_csv( + datafile, + col_types = cols( + benchmark = col_character(), + resulttype = col_character(), + faults = col_double(), + fault_address = col_character() # hex string "0x10001A"; converted below + ) +) # ============================================================================= # ELF SYMBOLS (parsed once) @@ -155,10 +217,12 @@ make_heatmap <- function(target_resulttype, target_benchmark) { # "0x10001A" -> substr strips "0x" -> strtoi parses base-16 -> integer aggregated <- aggregated |> - mutate(addr_int = strtoi( - substr(.data$fault_address, 3L, nchar(.data$fault_address)), - 16L - )) + mutate( + addr_int = strtoi( + substr(.data$fault_address, 3L, nchar(.data$fault_address)), + 16L + ) + ) # =========================================================================== # SCALE ROWS @@ -168,17 +232,24 @@ make_heatmap <- function(target_resulttype, target_benchmark) { row_width <- row_width_init # Double row_width until occupied rows <= max_rows - while (row_width < 65536L && n_occupied_rows( - aggregated$addr_int, row_width - ) > max_rows) { + while ( + row_width < 65536L && + n_occupied_rows( + aggregated$addr_int, + row_width + ) > + max_rows + ) { row_width <- row_width * 2L } if (row_width > row_width_init) { message(sprintf( "Note: [%s/%s] row_width auto-scaled to %d (%d occupied rows)", - target_resulttype, target_benchmark, - row_width, n_occupied_rows(aggregated$addr_int, row_width) + target_resulttype, + 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 cumulative_gaps <- cumsum(has_gap_before) row_order <- tibble( - row = rows_sorted, - row_idx = seq_len(n_data_rows) + cumulative_gaps, + row = rows_sorted, + row_idx = seq_len(n_data_rows) + cumulative_gaps, has_gap_before = has_gap_before ) @@ -240,8 +311,8 @@ make_heatmap <- function(target_resulttype, target_benchmark) { region_rects <- data.frame( label = character(0), - ymin = numeric(0), - ymax = numeric(0) + ymin = numeric(0), + ymax = numeric(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. # Overlap if r < e && r + row_width > s 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 ] @@ -266,8 +338,8 @@ make_heatmap <- function(target_resulttype, target_benchmark) { data.frame( label = reg$label, - ymin = min(overlapping$row_idx) - 0.5, - ymax = max(overlapping$row_idx) + 0.5 + ymin = min(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 <- ggplot(grid_complete, aes( - x = col, y = .data$row_idx, fill = .data$faults - )) + + plot <- ggplot( + grid_complete, + aes( + x = col, + y = .data$row_idx, + fill = .data$faults + ) + ) + # One rectangle per (col, row_idx) tuple geom_tile(width = 1, height = 1, colour = NA) + @@ -330,10 +407,10 @@ make_heatmap <- function(target_resulttype, target_benchmark) { # Heatmap color ramp scale_fill_viridis_c( - name = "Faults", - trans = "log1p", + name = "Faults", + trans = "log1p", na.value = "grey85", - option = "viridis" + option = "viridis" ) + # X-axis hex labels @@ -355,10 +432,13 @@ make_heatmap <- function(target_resulttype, target_benchmark) { # Title + axis labels labs( title = paste(target_resulttype, "/", target_benchmark), - subtitle = paste("Total:", format( - sum(aggregated$faults, na.rm = TRUE), - big.mark = "," - )), + subtitle = paste( + "Total:", + format( + sum(aggregated$faults, na.rm = TRUE), + big.mark = "," + ) + ), x = "Byte Offset", y = "Base Address" ) + @@ -367,7 +447,10 @@ make_heatmap <- function(target_resulttype, target_benchmark) { theme_minimal() + theme( 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), panel.grid = element_blank(), @@ -399,9 +482,17 @@ make_heatmap <- function(target_resulttype, target_benchmark) { fig_w <- row_width * tile_size + 4.5 fig_h <- total_slots * tile_size + 2.5 - outfile <- file.path(experiment, paste0( - "heatmap_", target_resulttype, "_", target_benchmark, ".svg" - )) + outfile <- file.path( + experiment, + paste0( + "heatmap_", + target_resulttype, + "_", + target_benchmark, + suffix, + ".svg" + ) + ) ggsave(outfile, plot = plot, width = fig_w, height = fig_h, units = "in") message(sprintf("Saved: %s", basename(outfile))) diff --git a/scripts/charts/single_result.r b/scripts/charts/single_result.r index c97fe7d..77491d1 100644 --- a/scripts/charts/single_result.r +++ b/scripts/charts/single_result.r @@ -1,10 +1,12 @@ library(ggplot2) -# Usage: Rscript single_result.r exp_abspath +# Usage: Rscript single_result.r exp_abspath [resultsdata_file] args <- commandArgs(trailingOnly = TRUE) 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)) { print(paste("Input file", datafile, "is missing")) @@ -21,6 +23,6 @@ plot <- ggplot(data, aes(x = benchmark, y = faults, fill = resulttype)) + theme_minimal() ggsave( - paste(experiment, "/single_result.svg", sep = ""), + paste0(experiment, "/single_result", suffix, ".svg"), plot = plot, ) diff --git a/scripts/charts/single_scatter.r b/scripts/charts/single_scatter.r index 8bb609e..4decd2a 100644 --- a/scripts/charts/single_scatter.r +++ b/scripts/charts/single_scatter.r @@ -6,7 +6,9 @@ library(ggplot2) args <- commandArgs(trailingOnly = TRUE) 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)) { print(paste("Input file", datafile, "is missing")) @@ -27,6 +29,6 @@ plot <- ggplot(data, aes(x = fault_address, y = faults)) + theme_minimal() ggsave( - paste(experiment, "/scatter.svg", sep = ""), + paste0(experiment, "/scatter", suffix, ".svg"), plot = plot, ) diff --git a/scripts/explore.pl b/scripts/explore.pl index e102096..4a16ed0 100644 --- a/scripts/explore.pl +++ b/scripts/explore.pl @@ -23,6 +23,9 @@ my $local_archive_dir = "$local_root/injections"; # Select experiment to open 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(); # 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 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' ); diff --git a/scripts/menu.pl b/scripts/menu.pl index 269e4c9..eeb7f6c 100644 --- a/scripts/menu.pl +++ b/scripts/menu.pl @@ -241,13 +241,18 @@ my %handlers = ( 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 my %all_results; foreach my $experiment (@selected_experiments) { # Schema: benchmark, resulttype, faults my $data = Text::CSV_XS::csv( - in => "$local_archive_dir/$experiment/resultsdata.csv", + in => "$local_archive_dir/$experiment/$resultsdata_csv", headers => 'auto' ); @@ -479,15 +484,38 @@ my %handlers = ( TUI::select_from_list( "Select Plots to Generate", 1, @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; foreach my $experiment (@selected_experiments) { foreach my $chart (@single_charts) { say " - Generating plot $chart for $experiment..."; - system( + my @r_args = ( 'Rscript', "$local_charts_dir/$chart.r", "$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; foreach my $chart (@combined_charts) { say " - Generating plot $chart for ($print_experiments)..."; - system( 'Rscript', "$local_charts_dir/$chart.r", - @path_experiments ); + my @r_args = + ( '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); } },