use std::time::{Duration, Instant};
const DISCOVERY_HEARTBEAT: Duration = Duration::from_secs(10);
const HEARTBEAT_CHECK_EVERY: u64 = 4096;
#[derive(Clone, Copy, Debug)]
pub struct WalkerTally {
pub elapsed: Duration,
pub paths_yielded: u64,
pub advances_since_yield: u64,
pub max_stack_depth: usize,
}
pub(crate) type ProgressHook = dyn FnMut(&WalkerTally) + Send + Sync;
pub(crate) struct ProgressReporter {
search_started: Instant,
paths_yielded: u64,
advances_since_yield: u64,
last_heartbeat: Instant,
max_stack_depth: usize,
progress: Option<Box<ProgressHook>>,
progress_every: Duration,
}
impl ProgressReporter {
pub(crate) fn new(now: Instant) -> Self {
Self {
search_started: now,
paths_yielded: 0,
advances_since_yield: 0,
last_heartbeat: now,
max_stack_depth: 0,
progress: None,
progress_every: DISCOVERY_HEARTBEAT,
}
}
pub(crate) fn install_hook(&mut self, every: Duration, sink: Box<ProgressHook>) {
self.progress = Some(sink);
self.progress_every = every;
}
pub(crate) fn on_yield(&mut self) {
self.paths_yielded += 1;
self.advances_since_yield = 0;
}
pub(crate) fn on_advance(&mut self, stack_depth: usize) {
self.advances_since_yield = self.advances_since_yield.wrapping_add(1);
if stack_depth > self.max_stack_depth {
self.max_stack_depth = stack_depth;
}
if self
.advances_since_yield
.is_multiple_of(HEARTBEAT_CHECK_EVERY)
{
let now = Instant::now();
if now.duration_since(self.last_heartbeat) >= self.progress_every {
self.last_heartbeat = now;
let tally = WalkerTally {
elapsed: now.duration_since(self.search_started),
paths_yielded: self.paths_yielded,
advances_since_yield: self.advances_since_yield,
max_stack_depth: self.max_stack_depth,
};
match self.progress.as_mut() {
Some(sink) => sink(&tally),
None => {
#[expect(clippy::print_stderr)]
{
eprintln!(
"discovery heartbeat: elapsed={:?} \
paths_yielded={} advances_since_yield={} max_stack_depth={}",
tally.elapsed,
tally.paths_yielded,
tally.advances_since_yield,
tally.max_stack_depth
);
}
}
}
}
}
}
pub(crate) fn finish(&self) {
let elapsed = self.search_started.elapsed();
#[expect(clippy::print_stderr)]
{
eprintln!(
"discovery complete: elapsed={elapsed:?} paths_yielded={} max_stack_depth={}",
self.paths_yielded, self.max_stack_depth
);
}
}
}