From 016a4c37786cec7871acdd2b7476aa5b93e99567 Mon Sep 17 00:00:00 2001 From: radl97 Date: Fri, 16 Dec 2022 23:46:33 +0100 Subject: [PATCH] Human readable execs & run/exec rounding fix (#936) * Calculate run/exec statistics as float to solve rounding issues * Fixup * Clippy fixes * Clippy fixes * Change execs_per_sec() to return float per suggestions * Monitors: Write 2 decimal floating-point for execs/sec * Prettify exec/sec * Formatting & fix copy pasta * Pretty-print floats in monitor: use mega and kilo SI suffices * prettify -> prettify_float, apply suggestion * Clippy * Fix prometheus client cannot handle float values yet Co-authored-by: Andrea Fioraldi Co-authored-by: Dominik Maier --- libafl/src/monitors/mod.rs | 83 +++++++++++++++++++++++++------ libafl/src/monitors/multi.rs | 4 +- libafl/src/monitors/prometheus.rs | 5 +- libafl/src/monitors/tui/mod.rs | 11 ++-- libafl/src/monitors/tui/ui.rs | 2 +- 5 files changed, 81 insertions(+), 24 deletions(-) diff --git a/libafl/src/monitors/mod.rs b/libafl/src/monitors/mod.rs index 5e9b25d339..d88bad7004 100644 --- a/libafl/src/monitors/mod.rs +++ b/libafl/src/monitors/mod.rs @@ -59,6 +59,29 @@ impl fmt::Display for UserStats { } } +/// Prettifies float values for human-readable output +fn prettify_float(value: f64) -> String { + let (value, suffix) = match value { + value if value >= 1000000.0 => (value / 1000000.0, "M"), + value if value >= 1000.0 => (value / 1000.0, "k"), + value => (value, ""), + }; + match value { + value if value >= 1000.0 => { + format!("{value}{suffix}") + } + value if value >= 100.0 => { + format!("{value:.1}{suffix}") + } + value if value >= 10.0 => { + format!("{value:.2}{suffix}") + } + value => { + format!("{value:.3}{suffix}") + } + } +} + /// A simple struct to keep track of client monitor #[derive(Debug, Clone, Default, Serialize)] pub struct ClientStats { @@ -116,24 +139,24 @@ impl ClientStats { } /// Get the calculated executions per second for this client - #[allow(clippy::cast_sign_loss, clippy::cast_precision_loss)] + #[allow(clippy::cast_precision_loss, clippy::cast_sign_loss)] #[cfg(feature = "afl_exec_sec")] - pub fn execs_per_sec(&mut self, cur_time: Duration) -> u64 { + pub fn execs_per_sec(&mut self, cur_time: Duration) -> f64 { if self.executions == 0 { - return 0; + return 0.0; } let elapsed = cur_time .checked_sub(self.last_window_time) .map_or(0.0, |d| d.as_secs_f64()); if elapsed as u64 == 0 { - return self.last_execs_per_sec as u64; + return self.last_execs_per_sec; } let cur_avg = ((self.executions - self.last_window_executions) as f64) / elapsed; if self.last_window_executions == 0 { self.last_execs_per_sec = cur_avg; - return self.last_execs_per_sec as u64; + return self.last_execs_per_sec; } // If there is a dramatic (5x+) jump in speed, reset the indicator more quickly @@ -143,25 +166,30 @@ impl ClientStats { self.last_execs_per_sec = self.last_execs_per_sec * (1.0 - 1.0 / 16.0) + cur_avg * (1.0 / 16.0); - self.last_execs_per_sec as u64 + self.last_execs_per_sec } /// Get the calculated executions per second for this client - #[allow(clippy::cast_sign_loss, clippy::cast_precision_loss)] + #[allow(clippy::cast_precision_loss, clippy::cast_sign_loss)] #[cfg(not(feature = "afl_exec_sec"))] - pub fn execs_per_sec(&mut self, cur_time: Duration) -> u64 { + pub fn execs_per_sec(&mut self, cur_time: Duration) -> f64 { if self.executions == 0 { - return 0; + return 0.0; } let elapsed = cur_time .checked_sub(self.last_window_time) .map_or(0.0, |d| d.as_secs_f64()); if elapsed as u64 == 0 { - return 0; + return 0.0; } - ((self.executions as f64) / elapsed) as u64 + (self.executions as f64) / elapsed + } + + /// Executions per second + fn execs_per_sec_pretty(&mut self, cur_time: Duration) -> String { + prettify_float(self.execs_per_sec(cur_time)) } /// Update the user-defined stat with name and value @@ -218,12 +246,18 @@ pub trait Monitor { } /// Executions per second + #[allow(clippy::cast_sign_loss)] #[inline] - fn execs_per_sec(&mut self) -> u64 { + fn execs_per_sec(&mut self) -> f64 { let cur_time = current_time(); self.client_stats_mut() .iter_mut() - .fold(0_u64, |acc, x| acc + x.execs_per_sec(cur_time)) + .fold(0.0, |acc, x| acc + x.execs_per_sec(cur_time)) + } + + /// Executions per second + fn execs_per_sec_pretty(&mut self) -> String { + prettify_float(self.execs_per_sec()) } /// The client monitor for a specific id, creating new if it doesn't exist @@ -327,7 +361,7 @@ impl Monitor for SimplePrintingMonitor { self.corpus_size(), self.objective_size(), self.total_execs(), - self.execs_per_sec() + self.execs_per_sec_pretty() ); // Only print perf monitor if the feature is enabled @@ -396,7 +430,7 @@ where self.corpus_size(), self.objective_size(), self.total_execs(), - self.execs_per_sec() + self.execs_per_sec_pretty() ); (self.print_fn)(fmt); @@ -1034,3 +1068,22 @@ pub mod pybind { Ok(()) } } + +#[cfg(test)] +mod test { + use crate::monitors::prettify_float; + #[test] + fn test_prettify_float() { + assert_eq!(prettify_float(123423123.0), "123.4M"); + assert_eq!(prettify_float(12342312.3), "12.34M"); + assert_eq!(prettify_float(1234231.23), "1.234M"); + assert_eq!(prettify_float(123423.123), "123.4k"); + assert_eq!(prettify_float(12342.3123), "12.34k"); + assert_eq!(prettify_float(1234.23123), "1.234k"); + assert_eq!(prettify_float(123.423123), "123.4"); + assert_eq!(prettify_float(12.3423123), "12.34"); + assert_eq!(prettify_float(1.23423123), "1.234"); + assert_eq!(prettify_float(0.123423123), "0.123"); + assert_eq!(prettify_float(0.0123423123), "0.012"); + } +} diff --git a/libafl/src/monitors/multi.rs b/libafl/src/monitors/multi.rs index 27b2668d57..e377f22f5c 100644 --- a/libafl/src/monitors/multi.rs +++ b/libafl/src/monitors/multi.rs @@ -56,13 +56,13 @@ where self.corpus_size(), self.objective_size(), self.total_execs(), - self.execs_per_sec() + self.execs_per_sec_pretty() ); (self.print_fn)(global_fmt); let client = self.client_stats_mut_for(sender_id); let cur_time = current_time(); - let exec_sec = client.execs_per_sec(cur_time); + let exec_sec = client.execs_per_sec_pretty(cur_time); let pad = " ".repeat(head.len()); let mut fmt = format!( diff --git a/libafl/src/monitors/prometheus.rs b/libafl/src/monitors/prometheus.rs index 7a1e6f83c8..2d5196c076 100644 --- a/libafl/src/monitors/prometheus.rs +++ b/libafl/src/monitors/prometheus.rs @@ -91,6 +91,7 @@ where self.start_time } + #[allow(clippy::cast_sign_loss)] fn display(&mut self, event_msg: String, sender_id: u32) { // Update the prometheus metrics // Label each metric with the sender / client_id @@ -115,7 +116,7 @@ where stat: String::new(), }) .set(total_execs); - let execs_per_sec = self.execs_per_sec(); + let execs_per_sec = self.execs_per_sec() as u64; self.exec_rate .get_or_create(&Labels { client: sender_id, @@ -147,7 +148,7 @@ where self.corpus_size(), self.objective_size(), self.total_execs(), - self.execs_per_sec() + self.execs_per_sec_pretty() ); (self.print_fn)(fmt); diff --git a/libafl/src/monitors/tui/mod.rs b/libafl/src/monitors/tui/mod.rs index 224cb9a1d5..dc56bb01d8 100644 --- a/libafl/src/monitors/tui/mod.rs +++ b/libafl/src/monitors/tui/mod.rs @@ -169,13 +169,14 @@ pub struct ClientTuiContext { pub corpus: u64, pub objectives: u64, pub executions: u64, - pub exec_sec: u64, + /// Float value formatted as String + pub exec_sec: String, pub user_stats: HashMap, } impl ClientTuiContext { - pub fn grab_data(&mut self, client: &ClientStats, exec_sec: u64) { + pub fn grab_data(&mut self, client: &ClientStats, exec_sec: String) { self.corpus = client.corpus_size; self.objectives = client.objective_size; self.executions = client.executions; @@ -256,11 +257,13 @@ impl Monitor for TuiMonitor { self.start_time } + #[allow(clippy::cast_sign_loss)] fn display(&mut self, event_msg: String, sender_id: u32) { let cur_time = current_time(); { - let execsec = self.execs_per_sec(); + // TODO implement floating-point support for TimedStat + let execsec = self.execs_per_sec() as u64; let totalexec = self.total_execs(); let run_time = cur_time - self.start_time; @@ -274,7 +277,7 @@ impl Monitor for TuiMonitor { } let client = self.client_stats_mut_for(sender_id); - let exec_sec = client.execs_per_sec(cur_time); + let exec_sec = client.execs_per_sec_pretty(cur_time); let sender = format!("#{sender_id}"); let pad = if event_msg.len() + sender.len() < 13 { diff --git a/libafl/src/monitors/tui/ui.rs b/libafl/src/monitors/tui/ui.rs index 5d879a3439..ad0ea6f44a 100644 --- a/libafl/src/monitors/tui/ui.rs +++ b/libafl/src/monitors/tui/ui.rs @@ -397,7 +397,7 @@ impl TuiUI { ])); client_items.push(Row::new(vec![ Cell::from(Span::raw("exec/sec")), - Cell::from(Span::raw(format!("{}", client.exec_sec))), + Cell::from(Span::raw(client.exec_sec.clone())), ])); client_items.push(Row::new(vec![ Cell::from(Span::raw("corpus")),