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 <andreafioraldi@gmail.com>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
radl97 2022-12-16 23:46:33 +01:00 committed by GitHub
parent d04346c870
commit 016a4c3778
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 24 deletions

View File

@ -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 /// A simple struct to keep track of client monitor
#[derive(Debug, Clone, Default, Serialize)] #[derive(Debug, Clone, Default, Serialize)]
pub struct ClientStats { pub struct ClientStats {
@ -116,24 +139,24 @@ impl ClientStats {
} }
/// Get the calculated executions per second for this client /// 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")] #[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 { if self.executions == 0 {
return 0; return 0.0;
} }
let elapsed = cur_time let elapsed = cur_time
.checked_sub(self.last_window_time) .checked_sub(self.last_window_time)
.map_or(0.0, |d| d.as_secs_f64()); .map_or(0.0, |d| d.as_secs_f64());
if elapsed as u64 == 0 { 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; let cur_avg = ((self.executions - self.last_window_executions) as f64) / elapsed;
if self.last_window_executions == 0 { if self.last_window_executions == 0 {
self.last_execs_per_sec = cur_avg; 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 // 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 =
self.last_execs_per_sec * (1.0 - 1.0 / 16.0) + cur_avg * (1.0 / 16.0); 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 /// 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"))] #[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 { if self.executions == 0 {
return 0; return 0.0;
} }
let elapsed = cur_time let elapsed = cur_time
.checked_sub(self.last_window_time) .checked_sub(self.last_window_time)
.map_or(0.0, |d| d.as_secs_f64()); .map_or(0.0, |d| d.as_secs_f64());
if elapsed as u64 == 0 { 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 /// Update the user-defined stat with name and value
@ -218,12 +246,18 @@ pub trait Monitor {
} }
/// Executions per second /// Executions per second
#[allow(clippy::cast_sign_loss)]
#[inline] #[inline]
fn execs_per_sec(&mut self) -> u64 { fn execs_per_sec(&mut self) -> f64 {
let cur_time = current_time(); let cur_time = current_time();
self.client_stats_mut() self.client_stats_mut()
.iter_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 /// 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.corpus_size(),
self.objective_size(), self.objective_size(),
self.total_execs(), self.total_execs(),
self.execs_per_sec() self.execs_per_sec_pretty()
); );
// Only print perf monitor if the feature is enabled // Only print perf monitor if the feature is enabled
@ -396,7 +430,7 @@ where
self.corpus_size(), self.corpus_size(),
self.objective_size(), self.objective_size(),
self.total_execs(), self.total_execs(),
self.execs_per_sec() self.execs_per_sec_pretty()
); );
(self.print_fn)(fmt); (self.print_fn)(fmt);
@ -1034,3 +1068,22 @@ pub mod pybind {
Ok(()) 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");
}
}

View File

@ -56,13 +56,13 @@ where
self.corpus_size(), self.corpus_size(),
self.objective_size(), self.objective_size(),
self.total_execs(), self.total_execs(),
self.execs_per_sec() self.execs_per_sec_pretty()
); );
(self.print_fn)(global_fmt); (self.print_fn)(global_fmt);
let client = self.client_stats_mut_for(sender_id); let client = self.client_stats_mut_for(sender_id);
let cur_time = current_time(); 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 pad = " ".repeat(head.len());
let mut fmt = format!( let mut fmt = format!(

View File

@ -91,6 +91,7 @@ where
self.start_time self.start_time
} }
#[allow(clippy::cast_sign_loss)]
fn display(&mut self, event_msg: String, sender_id: u32) { fn display(&mut self, event_msg: String, sender_id: u32) {
// Update the prometheus metrics // Update the prometheus metrics
// Label each metric with the sender / client_id // Label each metric with the sender / client_id
@ -115,7 +116,7 @@ where
stat: String::new(), stat: String::new(),
}) })
.set(total_execs); .set(total_execs);
let execs_per_sec = self.execs_per_sec(); let execs_per_sec = self.execs_per_sec() as u64;
self.exec_rate self.exec_rate
.get_or_create(&Labels { .get_or_create(&Labels {
client: sender_id, client: sender_id,
@ -147,7 +148,7 @@ where
self.corpus_size(), self.corpus_size(),
self.objective_size(), self.objective_size(),
self.total_execs(), self.total_execs(),
self.execs_per_sec() self.execs_per_sec_pretty()
); );
(self.print_fn)(fmt); (self.print_fn)(fmt);

View File

@ -169,13 +169,14 @@ pub struct ClientTuiContext {
pub corpus: u64, pub corpus: u64,
pub objectives: u64, pub objectives: u64,
pub executions: u64, pub executions: u64,
pub exec_sec: u64, /// Float value formatted as String
pub exec_sec: String,
pub user_stats: HashMap<String, UserStats>, pub user_stats: HashMap<String, UserStats>,
} }
impl ClientTuiContext { 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.corpus = client.corpus_size;
self.objectives = client.objective_size; self.objectives = client.objective_size;
self.executions = client.executions; self.executions = client.executions;
@ -256,11 +257,13 @@ impl Monitor for TuiMonitor {
self.start_time self.start_time
} }
#[allow(clippy::cast_sign_loss)]
fn display(&mut self, event_msg: String, sender_id: u32) { fn display(&mut self, event_msg: String, sender_id: u32) {
let cur_time = current_time(); 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 totalexec = self.total_execs();
let run_time = cur_time - self.start_time; 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 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 sender = format!("#{sender_id}");
let pad = if event_msg.len() + sender.len() < 13 { let pad = if event_msg.len() + sender.len() < 13 {

View File

@ -397,7 +397,7 @@ impl TuiUI {
])); ]));
client_items.push(Row::new(vec![ client_items.push(Row::new(vec![
Cell::from(Span::raw("exec/sec")), 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![ client_items.push(Row::new(vec![
Cell::from(Span::raw("corpus")), Cell::from(Span::raw("corpus")),