use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
fn enabled() -> bool {
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| {
matches!(
std::env::var("BLVM_ECDSA_TIMERS")
.ok()
.as_deref()
.map(str::trim)
.map(str::to_ascii_lowercase)
.as_deref(),
Some("1") | Some("true") | Some("yes") | Some("on")
)
})
}
static COLLECT_NS: AtomicU64 = AtomicU64::new(0);
static BATCH_NS: AtomicU64 = AtomicU64::new(0);
static BLOCKS: AtomicU64 = AtomicU64::new(0);
static LAST_LOG_BLOCKS: AtomicU64 = AtomicU64::new(0);
pub fn note_collect_ns(ns: u64) {
if !enabled() {
return;
}
COLLECT_NS.fetch_add(ns, Ordering::Relaxed);
}
pub fn note_batch_ns(ns: u64) {
if !enabled() {
return;
}
BATCH_NS.fetch_add(ns, Ordering::Relaxed);
}
pub fn note_block_done() {
if !enabled() {
return;
}
let b = BLOCKS.fetch_add(1, Ordering::Relaxed) + 1;
let last = LAST_LOG_BLOCKS.load(Ordering::Relaxed);
if (b == 1 || b.saturating_sub(last) >= 128)
&& LAST_LOG_BLOCKS
.compare_exchange(last, b, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
let c = COLLECT_NS.load(Ordering::Relaxed) as f64 / 1_000_000.0;
let v = BATCH_NS.load(Ordering::Relaxed) as f64 / 1_000_000.0;
let tot = c + v;
let pct = if tot > 0.0 { 100.0 * v / tot } else { 0.0 };
eprintln!(
"[BLVM_ECDSA_TIMERS] blocks={b} collect_ms={c:.1} batch_ms={v:.1} \
batch_share={pct:.1}% (batch/(script_check_loop+batch); \
collect_ms=whole process_check loop)"
);
}
}
pub fn snapshot() -> (u64, u64, u64) {
(
BLOCKS.load(Ordering::Relaxed),
COLLECT_NS.load(Ordering::Relaxed),
BATCH_NS.load(Ordering::Relaxed),
)
}
pub fn reset() {
COLLECT_NS.store(0, Ordering::Relaxed);
BATCH_NS.store(0, Ordering::Relaxed);
BLOCKS.store(0, Ordering::Relaxed);
LAST_LOG_BLOCKS.store(0, Ordering::Relaxed);
}
pub fn collect_threads() -> usize {
static N: OnceLock<usize> = OnceLock::new();
*N.get_or_init(|| {
std::env::var("BLVM_ECDSA_COLLECT_THREADS")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0)
.min(8)
})
}
#[cfg(feature = "rayon")]
pub fn collect_pool() -> Option<&'static rayon::ThreadPool> {
let per_block = collect_threads();
if per_block < 2 {
return None;
}
static POOL: OnceLock<rayon::ThreadPool> = OnceLock::new();
Some(POOL.get_or_init(|| {
let cpus = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(8)
.clamp(2, 64);
let max_par = std::env::var("BLVM_IBD_MAX_PARALLEL")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
if max_par > 0 && max_par.saturating_mul(per_block) > cpus {
eprintln!(
"[BLVM_ECDSA_COLLECT] WARN MAX_PARALLEL={max_par}×COLLECT={per_block} \
> nproc={cpus} — expect oversub (C3-class REVERT)"
);
}
eprintln!(
"[BLVM_ECDSA_COLLECT] shared rayon pool threads={cpus} \
per_block_width={per_block} (budget MAX_PARALLEL×COLLECT≤nproc)"
);
rayon::ThreadPoolBuilder::new()
.num_threads(cpus)
.thread_name(|i| format!("blvm-ecdsa-collect-{i}"))
.build()
.expect("ecdsa collect pool")
}))
}
#[cfg(not(feature = "rayon"))]
pub fn collect_pool() -> Option<&'static ()> {
None
}