1
1
//! Monitor based on ratatui
2
2
3
- use alloc:: boxed:: Box ;
3
+ use alloc:: { boxed:: Box , string :: ToString } ;
4
4
use std:: {
5
5
collections:: VecDeque ,
6
6
fmt:: Write ,
@@ -22,6 +22,7 @@ use crossterm::{
22
22
use hashbrown:: HashMap ;
23
23
use libafl_bolts:: { current_time, format_duration_hms, ClientId } ;
24
24
use ratatui:: { backend:: CrosstermBackend , Terminal } ;
25
+ use serde_json:: { self , Value } ;
25
26
26
27
#[ cfg( feature = "introspection" ) ]
27
28
use super :: { ClientPerfMonitor , PerfFeature } ;
@@ -162,14 +163,53 @@ impl PerfTuiContext {
162
163
}
163
164
}
164
165
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
+
165
201
#[ derive( Debug , Default , Clone ) ]
166
202
pub struct ClientTuiContext {
167
203
pub corpus : u64 ,
168
204
pub objectives : u64 ,
169
205
pub executions : u64 ,
170
206
/// Float value formatted as String
171
- pub exec_sec : String ,
207
+ pub map_density : String ,
208
+
209
+ pub cycles_done : u64 ,
172
210
211
+ pub process_timing : ProcessTiming ,
212
+ pub item_geometry : ItemGeometry ,
173
213
pub user_stats : HashMap < String , UserStats > ,
174
214
}
175
215
@@ -178,7 +218,47 @@ impl ClientTuiContext {
178
218
self . corpus = client. corpus_size ;
179
219
self . objectives = client. objective_size ;
180
220
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;
182
262
183
263
for ( key, val) in & client. user_monitor {
184
264
self . user_stats . insert ( key. clone ( ) , val. clone ( ) ) ;
@@ -205,6 +285,14 @@ pub struct TuiContext {
205
285
pub clients_num : usize ,
206
286
pub total_execs : u64 ,
207
287
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 ,
208
296
}
209
297
210
298
impl TuiContext {
@@ -226,6 +314,13 @@ impl TuiContext {
226
314
clients_num : 0 ,
227
315
total_execs : 0 ,
228
316
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 ( ) ,
229
324
}
230
325
}
231
326
}
@@ -264,14 +359,21 @@ impl Monitor for TuiMonitor {
264
359
let execsec = self . execs_per_sec ( ) as u64 ;
265
360
let totalexec = self . total_execs ( ) ;
266
361
let run_time = cur_time - self . start_time ;
362
+ let total_process_timing = self . process_timing ( ) ;
267
363
268
364
let mut ctx = self . context . write ( ) . unwrap ( ) ;
365
+ ctx. total_process_timing = total_process_timing;
269
366
ctx. corpus_size_timed . add ( run_time, self . corpus_size ( ) ) ;
270
367
ctx. objective_size_timed
271
368
. add ( run_time, self . objective_size ( ) ) ;
272
369
ctx. execs_per_sec_timed . add ( run_time, execsec) ;
273
370
ctx. total_execs = totalexec;
274
371
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 ( ) ;
275
377
}
276
378
277
379
let client = self . client_stats_mut_for ( sender_id) ;
@@ -339,6 +441,91 @@ impl TuiMonitor {
339
441
client_stats : vec ! [ ] ,
340
442
}
341
443
}
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
+ }
342
529
}
343
530
344
531
fn run_tui_thread ( context : Arc < RwLock < TuiContext > > , tick_rate : Duration , tui_ui : TuiUI ) {
0 commit comments