#![cfg(feature = "diagnostics")]
use crate::memory_stats::memory_registry;
use crate::scheduler::{
ACTIVE_STRANDS, PEAK_STRANDS, TOTAL_COMPLETED, TOTAL_SPAWNED, strand_registry,
};
use std::sync::Once;
use std::sync::atomic::Ordering;
static SIGNAL_HANDLER_INIT: Once = Once::new();
const STRAND_DISPLAY_LIMIT: usize = 20;
pub fn install_signal_handler() {
SIGNAL_HANDLER_INIT.call_once(|| {
#[cfg(unix)]
{
use signal_hook::consts::SIGQUIT;
use signal_hook::iterator::Signals;
let mut signals = match Signals::new([SIGQUIT]) {
Ok(s) => s,
Err(_) => return, };
std::thread::Builder::new()
.name("seq-diagnostics".to_string())
.spawn(move || {
for sig in signals.forever() {
if sig == SIGQUIT {
dump_diagnostics();
}
}
})
.ok(); }
#[cfg(not(unix))]
{
}
});
}
pub fn dump_diagnostics() {
use std::io::Write;
let mut out = std::io::stderr().lock();
let _ = writeln!(out, "\n=== Seq Runtime Diagnostics ===");
let _ = writeln!(out, "Timestamp: {:?}", std::time::SystemTime::now());
let active = ACTIVE_STRANDS.load(Ordering::Relaxed);
let total_spawned = TOTAL_SPAWNED.load(Ordering::Relaxed);
let total_completed = TOTAL_COMPLETED.load(Ordering::Relaxed);
let peak = PEAK_STRANDS.load(Ordering::Relaxed);
let _ = writeln!(out, "\n[Strands]");
let _ = writeln!(out, " Active: {}", active);
let _ = writeln!(out, " Spawned: {} (total)", total_spawned);
let _ = writeln!(out, " Completed: {} (total)", total_completed);
let _ = writeln!(out, " Peak: {} (high-water mark)", peak);
let expected_completed = total_spawned.saturating_sub(active as u64);
if total_completed < expected_completed {
let lost = expected_completed - total_completed;
let _ = writeln!(
out,
" WARNING: {} strands may have been lost (panic/abort)",
lost
);
}
let registry = strand_registry();
let overflow = registry.overflow_count.load(Ordering::Relaxed);
let _ = writeln!(out, "\n[Active Strand Details]");
let _ = writeln!(out, " Registry capacity: {} slots", registry.capacity());
if overflow > 0 {
let _ = writeln!(
out,
" WARNING: {} strands exceeded registry capacity (not tracked)",
overflow
);
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let mut strands: Vec<_> = registry.active_strands().collect();
strands.sort_by_key(|(_, spawn_time)| *spawn_time);
if strands.is_empty() {
let _ = writeln!(out, " (no active strands in registry)");
} else {
let _ = writeln!(out, " {} strand(s) tracked:", strands.len());
for (idx, (strand_id, spawn_time)) in strands.iter().take(STRAND_DISPLAY_LIMIT).enumerate()
{
let duration = now.saturating_sub(*spawn_time);
let _ = writeln!(
out,
" [{:2}] Strand #{:<8} running for {}s",
idx + 1,
strand_id,
duration
);
}
if strands.len() > STRAND_DISPLAY_LIMIT {
let _ = writeln!(
out,
" ... and {} more strands",
strands.len() - STRAND_DISPLAY_LIMIT
);
}
}
let _ = writeln!(out, "\n[Memory]");
let mem_stats = memory_registry().aggregate_stats();
let _ = writeln!(out, " Tracked threads: {}", mem_stats.active_threads);
let _ = writeln!(
out,
" Arena bytes: {} (across all threads)",
format_bytes(mem_stats.total_arena_bytes)
);
if mem_stats.overflow_count > 0 {
let _ = writeln!(
out,
" WARNING: {} threads exceeded registry capacity (memory not tracked)",
mem_stats.overflow_count
);
let _ = writeln!(
out,
" Consider increasing MAX_THREADS in memory_stats.rs (currently 64)"
);
}
let _ = writeln!(out, "\n=== End Diagnostics ===\n");
}
fn format_bytes(bytes: u64) -> String {
if bytes >= 1024 * 1024 * 1024 {
format!("{:.2} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
} else if bytes >= 1024 * 1024 {
format!("{:.2} MB", bytes as f64 / (1024.0 * 1024.0))
} else if bytes >= 1024 {
format!("{:.2} KB", bytes as f64 / 1024.0)
} else {
format!("{} B", bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dump_diagnostics_runs() {
dump_diagnostics();
}
#[test]
fn test_install_signal_handler_idempotent() {
install_signal_handler();
install_signal_handler();
install_signal_handler();
}
}