Skip to content

Commit a0bcdfa

Browse files
ToSeventosevendomenukk
authored
implement the AFL-Style Tui (#1432)
* implement an AFL-Style TUI * improve the tui/mod.rs according to the reviews * fixing fmt manually --------- Co-authored-by: toseven <[email protected]> Co-authored-by: Dominik Maier <[email protected]>
1 parent 1b6ef52 commit a0bcdfa

File tree

3 files changed

+655
-136
lines changed

3 files changed

+655
-136
lines changed

libafl/src/monitors/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,16 @@ pub struct ClientStats {
8787
// monitor (maybe we need a separated struct?)
8888
/// The corpus size for this client
8989
pub corpus_size: u64,
90+
/// The time for the last update of the corpus size
91+
pub last_corpus_time: Duration,
9092
/// The total executions for this client
9193
pub executions: u64,
9294
/// The number of executions of the previous state in case a client decrease the number of execution (e.g when restarting without saving the state)
9395
pub prev_state_executions: u64,
9496
/// The size of the objectives corpus for this client
9597
pub objective_size: u64,
98+
/// The time for the last update of the objective size
99+
pub last_objective_time: Duration,
96100
/// The last reported executions for this client
97101
#[cfg(feature = "afl_exec_sec")]
98102
pub last_window_executions: u64,
@@ -101,6 +105,8 @@ pub struct ClientStats {
101105
pub last_execs_per_sec: f64,
102106
/// The last time we got this information
103107
pub last_window_time: Duration,
108+
/// the start time of the client
109+
pub start_time: Duration,
104110
/// User-defined monitor
105111
pub user_monitor: HashMap<String, UserStats>,
106112
/// Client performance statistics
@@ -140,6 +146,7 @@ impl ClientStats {
140146
/// We got a new information about corpus size for this client, insert them.
141147
pub fn update_corpus_size(&mut self, corpus_size: u64) {
142148
self.corpus_size = corpus_size;
149+
self.last_corpus_time = current_time();
143150
}
144151

145152
/// We got a new information about objective corpus size for this client, insert them.
@@ -206,8 +213,9 @@ impl ClientStats {
206213
self.user_monitor.insert(name, value);
207214
}
208215

216+
#[must_use]
209217
/// Get a user-defined stat using the name
210-
pub fn get_user_stats(&mut self, name: &str) -> Option<&UserStats> {
218+
pub fn get_user_stats(&self, name: &str) -> Option<&UserStats> {
211219
self.user_monitor.get(name)
212220
}
213221

@@ -275,6 +283,7 @@ pub trait Monitor {
275283
for _ in client_stat_count..(client_id.0 + 1) as usize {
276284
self.client_stats_mut().push(ClientStats {
277285
last_window_time: current_time(),
286+
start_time: current_time(),
278287
..ClientStats::default()
279288
});
280289
}

libafl/src/monitors/tui/mod.rs

Lines changed: 190 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Monitor based on ratatui
22
3-
use alloc::boxed::Box;
3+
use alloc::{boxed::Box, string::ToString};
44
use std::{
55
collections::VecDeque,
66
fmt::Write,
@@ -22,6 +22,7 @@ use crossterm::{
2222
use hashbrown::HashMap;
2323
use libafl_bolts::{current_time, format_duration_hms, ClientId};
2424
use ratatui::{backend::CrosstermBackend, Terminal};
25+
use serde_json::{self, Value};
2526

2627
#[cfg(feature = "introspection")]
2728
use super::{ClientPerfMonitor, PerfFeature};
@@ -162,14 +163,53 @@ impl PerfTuiContext {
162163
}
163164
}
164165

166+
#[derive(Debug, Default, Clone)]
167+
pub struct ProcessTiming {
168+
pub client_start_time: Duration,
169+
pub exec_speed: String,
170+
pub last_new_entry: Duration,
171+
pub last_saved_solution: Duration,
172+
}
173+
174+
impl ProcessTiming {
175+
fn new() -> Self {
176+
Self {
177+
exec_speed: "0".to_string(),
178+
..Default::default()
179+
}
180+
}
181+
}
182+
183+
#[derive(Debug, Default, Clone)]
184+
pub struct ItemGeometry {
185+
pub pending: u64,
186+
pub pend_fav: u64,
187+
pub own_finds: u64,
188+
pub imported: u64,
189+
pub stability: String,
190+
}
191+
192+
impl ItemGeometry {
193+
fn new() -> Self {
194+
Self {
195+
stability: "0%".to_string(),
196+
..Default::default()
197+
}
198+
}
199+
}
200+
165201
#[derive(Debug, Default, Clone)]
166202
pub struct ClientTuiContext {
167203
pub corpus: u64,
168204
pub objectives: u64,
169205
pub executions: u64,
170206
/// Float value formatted as String
171-
pub exec_sec: String,
207+
pub map_density: String,
208+
209+
pub cycles_done: u64,
172210

211+
pub process_timing: ProcessTiming,
212+
pub item_geometry: ItemGeometry,
173213
pub user_stats: HashMap<String, UserStats>,
174214
}
175215

@@ -178,7 +218,47 @@ impl ClientTuiContext {
178218
self.corpus = client.corpus_size;
179219
self.objectives = client.objective_size;
180220
self.executions = client.executions;
181-
self.exec_sec = exec_sec;
221+
self.process_timing.client_start_time = client.start_time;
222+
self.process_timing.last_new_entry = if client.last_corpus_time > client.start_time {
223+
client.last_corpus_time - client.start_time
224+
} else {
225+
Duration::default()
226+
};
227+
228+
self.process_timing.last_saved_solution = if client.last_objective_time > client.start_time
229+
{
230+
client.last_objective_time - client.start_time
231+
} else {
232+
Duration::default()
233+
};
234+
235+
self.process_timing.exec_speed = exec_sec;
236+
237+
self.map_density = client
238+
.get_user_stats("edges")
239+
.map_or("0%".to_string(), ToString::to_string);
240+
241+
let default_json = serde_json::json!({
242+
"pending": 0,
243+
"pend_fav": 0,
244+
"imported": 0,
245+
"own_finds": 0,
246+
});
247+
let afl_stats = client
248+
.get_user_stats("AflStats")
249+
.map_or(default_json.to_string(), ToString::to_string);
250+
251+
let afl_stats_json: Value =
252+
serde_json::from_str(afl_stats.as_str()).unwrap_or(default_json);
253+
self.item_geometry.pending = afl_stats_json["pending"].as_u64().unwrap_or_default();
254+
self.item_geometry.pend_fav = afl_stats_json["pend_fav"].as_u64().unwrap_or_default();
255+
self.item_geometry.imported = afl_stats_json["imported"].as_u64().unwrap_or_default();
256+
self.item_geometry.own_finds = afl_stats_json["own_finds"].as_u64().unwrap_or_default();
257+
258+
let stability = client
259+
.get_user_stats("stability")
260+
.map_or("0%".to_string(), ToString::to_string);
261+
self.item_geometry.stability = stability;
182262

183263
for (key, val) in &client.user_monitor {
184264
self.user_stats.insert(key.clone(), val.clone());
@@ -205,6 +285,14 @@ pub struct TuiContext {
205285
pub clients_num: usize,
206286
pub total_execs: u64,
207287
pub start_time: Duration,
288+
289+
pub total_map_density: String,
290+
pub total_solutions: u64,
291+
pub total_cycles_done: u64,
292+
pub total_corpus_count: u64,
293+
294+
pub total_process_timing: ProcessTiming,
295+
pub total_item_geometry: ItemGeometry,
208296
}
209297

210298
impl TuiContext {
@@ -226,6 +314,13 @@ impl TuiContext {
226314
clients_num: 0,
227315
total_execs: 0,
228316
start_time,
317+
318+
total_map_density: "0%".to_string(),
319+
total_solutions: 0,
320+
total_cycles_done: 0,
321+
total_corpus_count: 0,
322+
total_item_geometry: ItemGeometry::new(),
323+
total_process_timing: ProcessTiming::new(),
229324
}
230325
}
231326
}
@@ -264,14 +359,21 @@ impl Monitor for TuiMonitor {
264359
let execsec = self.execs_per_sec() as u64;
265360
let totalexec = self.total_execs();
266361
let run_time = cur_time - self.start_time;
362+
let total_process_timing = self.process_timing();
267363

268364
let mut ctx = self.context.write().unwrap();
365+
ctx.total_process_timing = total_process_timing;
269366
ctx.corpus_size_timed.add(run_time, self.corpus_size());
270367
ctx.objective_size_timed
271368
.add(run_time, self.objective_size());
272369
ctx.execs_per_sec_timed.add(run_time, execsec);
273370
ctx.total_execs = totalexec;
274371
ctx.clients_num = self.client_stats.len();
372+
ctx.total_map_density = self.map_density();
373+
ctx.total_solutions = self.objective_size();
374+
ctx.total_cycles_done = 0;
375+
ctx.total_corpus_count = self.corpus_size();
376+
ctx.total_item_geometry = self.item_geometry();
275377
}
276378

277379
let client = self.client_stats_mut_for(sender_id);
@@ -339,6 +441,91 @@ impl TuiMonitor {
339441
client_stats: vec![],
340442
}
341443
}
444+
445+
fn map_density(&self) -> String {
446+
if self.client_stats.len() < 2 {
447+
return "0%".to_string();
448+
}
449+
let mut max_map_density = self
450+
.client_stats()
451+
.get(1)
452+
.unwrap()
453+
.get_user_stats("edges")
454+
.map_or("0%".to_string(), ToString::to_string);
455+
456+
for client in self.client_stats().iter().skip(2) {
457+
let client_map_density = client
458+
.get_user_stats("edges")
459+
.map_or(String::new(), ToString::to_string);
460+
if client_map_density > max_map_density {
461+
max_map_density = client_map_density;
462+
}
463+
}
464+
max_map_density
465+
}
466+
467+
fn item_geometry(&self) -> ItemGeometry {
468+
let mut total_item_geometry = ItemGeometry::new();
469+
if self.client_stats.len() < 2 {
470+
return total_item_geometry;
471+
}
472+
let mut ratio_a: u64 = 0;
473+
let mut ratio_b: u64 = 0;
474+
for client in self.client_stats().iter().skip(1) {
475+
let afl_stats = client
476+
.get_user_stats("AflStats")
477+
.map_or("None".to_string(), ToString::to_string);
478+
let stability = client
479+
.get_user_stats("stability")
480+
.map_or(&UserStats::Ratio(0, 100), |x| x);
481+
482+
if afl_stats != "None" {
483+
let default_json = serde_json::json!({
484+
"pending": 0,
485+
"pend_fav": 0,
486+
"imported": 0,
487+
"own_finds": 0,
488+
});
489+
let afl_stats_json: Value =
490+
serde_json::from_str(afl_stats.as_str()).unwrap_or(default_json);
491+
total_item_geometry.pending +=
492+
afl_stats_json["pending"].as_u64().unwrap_or_default();
493+
total_item_geometry.pend_fav +=
494+
afl_stats_json["pend_fav"].as_u64().unwrap_or_default();
495+
total_item_geometry.own_finds +=
496+
afl_stats_json["own_finds"].as_u64().unwrap_or_default();
497+
total_item_geometry.imported +=
498+
afl_stats_json["imported"].as_u64().unwrap_or_default();
499+
}
500+
501+
if let UserStats::Ratio(a, b) = stability {
502+
ratio_a += a;
503+
ratio_b += b;
504+
}
505+
}
506+
total_item_geometry.stability = format!("{}%", ratio_a * 100 / ratio_b);
507+
total_item_geometry
508+
}
509+
510+
fn process_timing(&mut self) -> ProcessTiming {
511+
let mut total_process_timing = ProcessTiming::new();
512+
total_process_timing.exec_speed = self.execs_per_sec_pretty();
513+
if self.client_stats.len() > 1 {
514+
let mut new_path_time = Duration::default();
515+
let mut new_objectives_time = Duration::default();
516+
for client in self.client_stats().iter().skip(1) {
517+
new_path_time = client.last_corpus_time.max(new_path_time);
518+
new_objectives_time = client.last_objective_time.max(new_objectives_time);
519+
}
520+
if new_path_time > self.start_time {
521+
total_process_timing.last_new_entry = new_path_time - self.start_time;
522+
}
523+
if new_objectives_time > self.start_time {
524+
total_process_timing.last_saved_solution = new_objectives_time - self.start_time;
525+
}
526+
}
527+
total_process_timing
528+
}
342529
}
343530

344531
fn run_tui_thread(context: Arc<RwLock<TuiContext>>, tick_rate: Duration, tui_ui: TuiUI) {

0 commit comments

Comments
 (0)