blvm_consensus/profile_log.rs
1//! Non-blocking profile logging for IBD and consensus hot paths.
2//!
3//! Replaces `eprintln!` with channel-based logging so validation never blocks on I/O.
4//! A dedicated background thread drains the channel and writes to stderr.
5//! Note: `tracing` also uses stderr; without a global lock, lines can rarely interleave with
6//! timestamps (`evict_ms=0` + `2026-03-29T...` on one physical line). Log analyzers normalize this;
7//! prefer `analyze_ibd_profile.py` over naive `grep` for `[IBD_VALIDATION]` phase=end splits.
8
9use std::io::Write;
10use std::sync::mpsc;
11use std::sync::OnceLock;
12
13/// Capacity for the profile log channel. When full, new messages are dropped (non-blocking).
14const CHANNEL_CAPACITY: usize = 65_536;
15
16static LOGGER: OnceLock<mpsc::SyncSender<String>> = OnceLock::new();
17
18#[allow(dead_code)]
19pub fn sender() -> Option<&'static mpsc::SyncSender<String>> {
20 Some(LOGGER.get_or_init(|| {
21 let (tx, rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
22 std::thread::Builder::new()
23 .name("profile-log".into())
24 .spawn(move || {
25 for msg in rx {
26 let _ = writeln!(std::io::stderr(), "{msg}");
27 }
28 })
29 .expect("Failed to spawn profile log thread");
30 tx
31 }))
32}
33
34/// Log a profile message without blocking. Drops the message if the channel is full.
35#[macro_export]
36macro_rules! profile_log {
37 ($($arg:tt)*) => {{
38 #[cfg(feature = "profile")]
39 {
40 if let Some(tx) = $crate::profile_log::sender() {
41 let _ = tx.try_send(format!($($arg)*));
42 }
43 }
44 }};
45}