From a938c87fa43be51bfa54c49844e3555438df0fd7 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Wed, 22 Apr 2026 22:21:24 +0300 Subject: [PATCH] fix: guard statistics calls in compare-backends.py against empty duration lists When all runs for a backend fail, durations_by_backend[backend] is empty, causing StatisticsError from statistics.quantiles (needs >= 2 points) and statistics.mean (needs >= 1 point). Print placeholder messages instead. --- CHANGELOG.md | 1 + scripts/compare-backends.py | 56 +++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e07a3544..36c82d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ - fix: Scopes.from_dict uses cls instead of self so subclasses return the correct type @williballenthin - fix: correct wrong dict key in VMRay _compute_monitor_threads assertion (used thread_id instead of process_id) @williballenthin - fix: replace assert with isinstance guard in get_callee for invalid MethodSpec tokens @williballenthin +- fix: guard statistics.quantiles/mean in compare-backends.py report() against empty duration lists @williballenthin (SURF-89) - fix: replace zipfile with pyzipper in minimize_vmray_results.py so output archive is AES-encrypted @williballenthin (SURF-88) - fix: assign yara_strings/yara_condition to empty string when Some has cmin=0 to prevent UnboundLocalError @williballenthin (SURF-87) - fix: parenthesize s_type checks in capa2yara.py so kid.name != "Some" guard applies to And/Or/Not uniformly @williballenthin (SURF-86) diff --git a/scripts/compare-backends.py b/scripts/compare-backends.py index 4120919a..d338ec3e 100644 --- a/scripts/compare-backends.py +++ b/scripts/compare-backends.py @@ -252,33 +252,53 @@ def report(args): console.print("durations:", style="bold") console.print(" (10-quantiles, in seconds)", style="grey37") for backend in BACKENDS: - q = statistics.quantiles(durations_by_backend[backend], n=10) - console.print(f" {backend: <8}: ", end="") - for i in range(9): - if i in (4, 8): - style = "bold" - else: - style = "default" - console.print(f"{q[i]: >6.1f}", style=style, end=" ") - console.print() - console.print(" ^-- 10% of samples took less than this ^", style="grey37") - console.print(" 10% of samples took more than this -----------------+", style="grey37") + durations = durations_by_backend[backend] + if len(durations) >= 2: + q = statistics.quantiles(durations, n=10) + console.print(f" {backend: <8}: ", end="") + for i in range(9): + if i in (4, 8): + style = "bold" + else: + style = "default" + console.print(f"{q[i]: >6.1f}", style=style, end=" ") + console.print() + else: + console.print(f" {backend: <8}: (no data)") + console.print( + " ^-- 10% of samples took less than this ^", + style="grey37", + ) + console.print( + " 10% of samples took more than this -----------------+", + style="grey37", + ) console.print() for backend in BACKENDS: - total = sum(durations_by_backend[backend]) - successes = len(durations_by_backend[backend]) - avg = statistics.mean(durations_by_backend[backend]) - console.print( - f" {backend: <8}: {total: >7.0f} seconds across {successes: >4d} successful runs, {avg: >4.1f} average" - ) + durations = durations_by_backend[backend] + if durations: + total = sum(durations) + avg = statistics.mean(durations) + console.print( + f" {backend: <8}: {total: >7.0f} seconds across {len(durations): >4d} successful runs, {avg: >4.1f} average" + ) + else: + console.print(f" {backend: <8}: (no successful runs)") console.print() console.print("slowest samples:", style="bold") for backend in BACKENDS: + durations = durations_by_backend[backend] + if not durations: + console.print(f" {backend: <8}: (no successful runs)") + continue + console.print(backend) + successful_keys = {k for k, v in doc[backend].items() if not v["err"]} for duration, path in sorted( - ((d["duration"], Path(d["path"]).name) for d in doc[backend].values()), reverse=True + ((d["duration"], Path(d["path"]).name) for k, d in doc[backend].items() if k in successful_keys), + reverse=True, )[:5]: console.print(f" - {duration: >6.1f} {path}")