Skip to content

Commit 1272fe6

Browse files
author
lukacan
committed
🔥 Improve progress bar
1 parent 39f65ea commit 1272fe6

3 files changed

Lines changed: 343 additions & 169 deletions

File tree

crates/fuzz/src/trident/flow_executor.rs

Lines changed: 56 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::io::IsTerminal;
21
use std::panic::catch_unwind;
32
use std::panic::AssertUnwindSafe;
43
use std::sync::atomic::AtomicBool;
@@ -10,6 +9,7 @@ use std::time::Instant;
109
use trident_config::TridentConfig;
1110
use trident_fuzz_metrics::TridentFuzzingData;
1211

12+
use crate::trident::progress;
1313
use crate::trident::Trident;
1414

1515
// Thread-local storage for panic location information.
@@ -61,13 +61,6 @@ impl ExitCodeMode {
6161
}
6262
}
6363

64-
/// Events sent from worker threads to the UI/controller thread in parallel fuzzing.
65-
enum WorkerEvent {
66-
ProgressDelta(u64),
67-
InvariantFailure(String),
68-
ProgramPanicsDelta(u64),
69-
}
70-
7164
/// Final process exit outcomes for a fuzzing run.
7265
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7366
enum FuzzRunExit {
@@ -114,86 +107,6 @@ fn determine_exit_outcome(input: ExitDecisionInput) -> FuzzRunExit {
114107
}
115108
}
116109

117-
fn colors_enabled() -> bool {
118-
std::io::stderr().is_terminal() && std::env::var_os("NO_COLOR").is_none()
119-
}
120-
121-
fn paint_red(text: &str) -> String {
122-
if colors_enabled() {
123-
format!("\x1b[31m{}\x1b[0m", text)
124-
} else {
125-
text.to_string()
126-
}
127-
}
128-
129-
fn paint_bold_yellow(text: &str) -> String {
130-
if colors_enabled() {
131-
format!("\x1b[1;33m{}\x1b[0m", text)
132-
} else {
133-
text.to_string()
134-
}
135-
}
136-
137-
fn paint_cyan(text: &str) -> String {
138-
if colors_enabled() {
139-
format!("\x1b[36m{}\x1b[0m", text)
140-
} else {
141-
text.to_string()
142-
}
143-
}
144-
145-
fn paint_magenta(text: &str) -> String {
146-
if colors_enabled() {
147-
format!("\x1b[35m{}\x1b[0m", text)
148-
} else {
149-
text.to_string()
150-
}
151-
}
152-
153-
fn format_invariant_line(text: &str) -> String {
154-
const PREFIX: &str = "Assertion failed at ";
155-
const SEED_PREFIX: &str = " (seed: ";
156-
157-
if !colors_enabled() {
158-
return text.to_string();
159-
}
160-
161-
// Expected format from handle_panic:
162-
// "Assertion failed at <location>: <message> (seed: <seed>)"
163-
if let Some(location_start) = text.strip_prefix(PREFIX) {
164-
if let Some(seed_idx) = location_start.rfind(SEED_PREFIX) {
165-
let before_seed = &location_start[..seed_idx];
166-
let seed_with_suffix = &location_start[seed_idx + SEED_PREFIX.len()..];
167-
if let Some(seed) = seed_with_suffix.strip_suffix(')') {
168-
if let Some(separator_idx) = before_seed.find(": ") {
169-
let location = &before_seed[..separator_idx];
170-
let message = &before_seed[separator_idx + 2..];
171-
return format!(
172-
"{} {}{}: {}{}{}{}",
173-
paint_red("!"),
174-
PREFIX,
175-
paint_cyan(location),
176-
message,
177-
SEED_PREFIX,
178-
paint_magenta(seed),
179-
")"
180-
);
181-
}
182-
}
183-
}
184-
}
185-
186-
// Fallback to accent-only if line doesn't match expected format.
187-
format!("{} {}", paint_red("!"), text)
188-
}
189-
190-
/// Final aggregated runtime summary produced by the UI/controller thread.
191-
struct ParallelRunSummary {
192-
invariant_failures: u64,
193-
program_panics: u64,
194-
panic_messages: Vec<String>,
195-
}
196-
197110
/// Trait for executing fuzzing flows in the Trident framework
198111
///
199112
/// This trait defines the interface for fuzzing executors that can run
@@ -406,6 +319,9 @@ pub trait FlowExecutor: Send + 'static + Sized {
406319
let mut invariant_failed = false; // Tracks fuzz test assertion/invariant failures
407320
let mut invariant_failure_count: u64 = 0;
408321
let mut panic_messages: Vec<String> = Vec::new();
322+
let total_flow_calls = iterations * flow_calls_per_iteration;
323+
let mut completed_flow_calls = 0u64;
324+
let start_time = Instant::now();
409325

410326
// Configure debug seed if in debug mode
411327
if is_debug_mode {
@@ -419,20 +335,11 @@ pub trait FlowExecutor: Send + 'static + Sized {
419335
let pb = if is_debug_mode {
420336
None
421337
} else {
422-
let total_flow_calls = iterations * flow_calls_per_iteration;
423-
let pb = indicatif::ProgressBar::new(total_flow_calls);
424-
pb.set_style(
425-
indicatif::ProgressStyle::with_template(
426-
"{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({percent}%) [{eta_precise}] {msg}"
427-
)
428-
.unwrap()
429-
.progress_chars("#>-"),
430-
);
431-
pb.set_message(format!(
432-
"Fuzzing {} iterations with {} flow calls each...",
433-
iterations, flow_calls_per_iteration
434-
));
435-
Some(pb)
338+
Some(progress::create_single_progress_bar(
339+
total_flow_calls,
340+
iterations,
341+
flow_calls_per_iteration,
342+
))
436343
};
437344

438345
// Main fuzzing loop: execute flows, catch panics, and track progress
@@ -455,7 +362,7 @@ pub trait FlowExecutor: Send + 'static + Sized {
455362

456363
// In debug mode print immediately, otherwise collect for end
457364
if is_debug_mode {
458-
eprintln!("{}", format_invariant_line(&panic_msg));
365+
eprintln!("{}", progress::format_invariant_line(&panic_msg));
459366
} else {
460367
panic_messages.push(panic_msg);
461368
}
@@ -475,16 +382,19 @@ pub trait FlowExecutor: Send + 'static + Sized {
475382
// Update progress bar with live stats
476383
if let Some(ref pb) = pb {
477384
pb.inc(flow_calls_per_iteration);
385+
completed_flow_calls += flow_calls_per_iteration;
478386
let program_panics = fuzzer
479387
.trident_mut()
480388
.get_fuzzing_data()
481389
.get_program_panic_count();
482-
pb.set_message(format!(
483-
"Iteration {}/{} | Invariant failures: {} | Program panics: {}",
390+
pb.set_message(progress::format_live_status_single(
484391
i + 1,
485392
iterations,
393+
completed_flow_calls,
394+
total_flow_calls,
486395
invariant_failure_count,
487-
program_panics
396+
program_panics,
397+
start_time.elapsed(),
488398
));
489399
}
490400
}
@@ -498,13 +408,13 @@ pub trait FlowExecutor: Send + 'static + Sized {
498408
if !panic_messages.is_empty() {
499409
eprintln!(
500410
"\n{}",
501-
paint_bold_yellow(&format!(
411+
progress::paint_bold_yellow(&format!(
502412
"--- Invariant Failures ({}) ---",
503413
panic_messages.len()
504414
))
505415
);
506416
for msg in &panic_messages {
507-
eprintln!("{}", format_invariant_line(msg));
417+
eprintln!("{}", progress::format_invariant_line(msg));
508418
}
509419
}
510420

@@ -536,56 +446,12 @@ pub trait FlowExecutor: Send + 'static + Sized {
536446
let remainder_iterations = iterations % num_threads as u64;
537447
let total_flow_calls = iterations * flow_calls_per_iteration;
538448
let exit_code_mode = ExitCodeMode::from_env();
539-
let (event_tx, event_rx) = mpsc::channel::<WorkerEvent>();
540-
541-
// Setup shared progress bar
542-
let main_pb = indicatif::ProgressBar::new(total_flow_calls);
543-
main_pb.set_style(
544-
indicatif::ProgressStyle::with_template(
545-
"Overall: {spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({percent}%) [{eta_precise}] {msg}"
546-
)
547-
.unwrap()
548-
.progress_chars("#>-"),
549-
);
550-
main_pb.set_message(format!(
551-
"Fuzzing with {} threads | Invariant failures: 0 | Program panics: 0",
552-
num_threads
553-
));
449+
let (event_tx, event_rx) = mpsc::channel::<progress::WorkerEvent>();
554450

555451
// Single UI/controller owner: consumes worker events, updates progress bar,
556452
// and builds global live counters + invariant messages.
557-
let ui_handle = thread::spawn(move || -> ParallelRunSummary {
558-
let mut invariant_failures = 0u64;
559-
let mut program_panics = 0u64;
560-
let mut panic_messages = Vec::new();
561-
562-
while let Ok(event) = event_rx.recv() {
563-
match event {
564-
WorkerEvent::ProgressDelta(delta) => {
565-
main_pb.inc(delta);
566-
}
567-
WorkerEvent::InvariantFailure(message) => {
568-
invariant_failures += 1;
569-
panic_messages.push(message);
570-
}
571-
WorkerEvent::ProgramPanicsDelta(delta) => {
572-
program_panics += delta;
573-
}
574-
}
575-
576-
main_pb.set_message(format!(
577-
"Invariant failures: {} | Program panics: {}",
578-
invariant_failures, program_panics
579-
));
580-
}
581-
582-
main_pb.finish_with_message("Parallel fuzzing completed!");
583-
ParallelRunSummary {
584-
invariant_failures,
585-
program_panics,
586-
panic_messages,
587-
}
588-
});
453+
let ui_handle =
454+
progress::spawn_parallel_ui_controller(event_rx, num_threads, total_flow_calls);
589455

590456
// Spawn worker threads
591457
let mut handles = Vec::new();
@@ -658,23 +524,25 @@ pub trait FlowExecutor: Send + 'static + Sized {
658524
}
659525

660526
// Get final aggregated runtime summary from the controller thread.
661-
let run_summary = ui_handle.join().unwrap_or_else(|_| ParallelRunSummary {
662-
invariant_failures: 0,
663-
program_panics: 0,
664-
panic_messages: Vec::new(),
665-
});
527+
let run_summary = ui_handle
528+
.join()
529+
.unwrap_or_else(|_| progress::ParallelRunSummary {
530+
invariant_failures: 0,
531+
program_panics: 0,
532+
panic_messages: Vec::new(),
533+
});
666534

667535
// Print collected invariant failure messages
668536
if !run_summary.panic_messages.is_empty() {
669537
eprintln!(
670538
"\n{}",
671-
paint_bold_yellow(&format!(
539+
progress::paint_bold_yellow(&format!(
672540
"--- Invariant Failures ({}) ---",
673541
run_summary.panic_messages.len()
674542
))
675543
);
676544
for msg in &run_summary.panic_messages {
677-
eprintln!("{}", format_invariant_line(msg));
545+
eprintln!("{}", progress::format_invariant_line(msg));
678546
}
679547
}
680548

@@ -750,7 +618,10 @@ pub trait FlowExecutor: Send + 'static + Sized {
750618
}
751619
}
752620

753-
fn send_worker_event(event_tx: &mpsc::Sender<WorkerEvent>, event: WorkerEvent) -> bool {
621+
fn send_worker_event(
622+
event_tx: &mpsc::Sender<progress::WorkerEvent>,
623+
event: progress::WorkerEvent,
624+
) -> bool {
754625
event_tx.send(event).is_ok()
755626
}
756627

@@ -762,7 +633,7 @@ fn run_thread_workload_impl<E: FlowExecutor>(
762633
thread_id: usize,
763634
thread_iterations: u64,
764635
flow_calls_per_iteration: u64,
765-
event_tx: mpsc::Sender<WorkerEvent>,
636+
event_tx: mpsc::Sender<progress::WorkerEvent>,
766637
) -> TridentFuzzingData {
767638
let mut fuzzer = E::new();
768639
fuzzer
@@ -789,7 +660,10 @@ fn run_thread_workload_impl<E: FlowExecutor>(
789660
{
790661
// Intentional invariant failure - count it, continue fuzzing
791662
let panic_msg = E::handle_panic(&panic_err, &mut fuzzer, None);
792-
if !send_worker_event(&event_tx, WorkerEvent::InvariantFailure(panic_msg)) {
663+
if !send_worker_event(
664+
&event_tx,
665+
progress::WorkerEvent::InvariantFailure(panic_msg),
666+
) {
793667
return fuzzer.trident_mut().get_fuzzing_data();
794668
}
795669
} else {
@@ -814,7 +688,10 @@ fn run_thread_workload_impl<E: FlowExecutor>(
814688
|| i == thread_iterations - 1; // Always update on last iteration
815689

816690
if should_update {
817-
if !send_worker_event(&event_tx, WorkerEvent::ProgressDelta(local_counter)) {
691+
if !send_worker_event(
692+
&event_tx,
693+
progress::WorkerEvent::ProgressDelta(local_counter),
694+
) {
818695
return fuzzer.trident_mut().get_fuzzing_data();
819696
}
820697
let thread_prog_panics = fuzzer
@@ -823,7 +700,10 @@ fn run_thread_workload_impl<E: FlowExecutor>(
823700
.get_program_panic_count();
824701
let new_panics = thread_prog_panics.saturating_sub(local_observed_program_panics);
825702
if new_panics > 0 {
826-
if !send_worker_event(&event_tx, WorkerEvent::ProgramPanicsDelta(new_panics)) {
703+
if !send_worker_event(
704+
&event_tx,
705+
progress::WorkerEvent::ProgramPanicsDelta(new_panics),
706+
) {
827707
return fuzzer.trident_mut().get_fuzzing_data();
828708
}
829709
local_observed_program_panics = thread_prog_panics;
@@ -834,7 +714,11 @@ fn run_thread_workload_impl<E: FlowExecutor>(
834714
}
835715

836716
// Ensure any remaining progress is reported
837-
if local_counter > 0 && !send_worker_event(&event_tx, WorkerEvent::ProgressDelta(local_counter))
717+
if local_counter > 0
718+
&& !send_worker_event(
719+
&event_tx,
720+
progress::WorkerEvent::ProgressDelta(local_counter),
721+
)
838722
{
839723
return fuzzer.trident_mut().get_fuzzing_data();
840724
}
@@ -846,7 +730,10 @@ fn run_thread_workload_impl<E: FlowExecutor>(
846730
.get_program_panic_count();
847731
let final_new_panics = final_thread_prog_panics.saturating_sub(local_observed_program_panics);
848732
if final_new_panics > 0
849-
&& !send_worker_event(&event_tx, WorkerEvent::ProgramPanicsDelta(final_new_panics))
733+
&& !send_worker_event(
734+
&event_tx,
735+
progress::WorkerEvent::ProgramPanicsDelta(final_new_panics),
736+
)
850737
{
851738
return fuzzer.trident_mut().get_fuzzing_data();
852739
}

crates/fuzz/src/trident/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod system;
1515
pub mod transaction_result;
1616

1717
mod metrics;
18+
mod progress;
1819
mod random;
1920
mod seed;
2021
#[cfg(feature = "stake")]

0 commit comments

Comments
 (0)