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:
parent
d04346c870
commit
016a4c3778
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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!(
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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")),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user