use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::OnceLock;
use std::time::Duration;
pub(crate) fn confirmed_prof_enabled() -> bool {
super::profile::enabled()
}
static CONFIRMED_PAT_NS: OnceLock<Vec<AtomicU64>> = OnceLock::new();
static CONFIRMED_PAT_RUNS: OnceLock<Vec<AtomicU64>> = OnceLock::new();
static CONFIRMED_STAGE_NS: [AtomicU64; 3] =
[AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0)];
static CONFIRMED_STAGE_RUNS: [AtomicU64; 3] =
[AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0)];
#[derive(Clone, Copy)]
pub(crate) enum ConfirmedStage {
SuffixGate = 0,
AnchorCollect = 1,
Extract = 2,
}
pub(crate) fn confirmed_prof_record(stage: ConfirmedStage, elapsed: Duration) {
let idx = stage as usize;
CONFIRMED_STAGE_NS[idx].fetch_add(elapsed.as_nanos() as u64, Relaxed);
CONFIRMED_STAGE_RUNS[idx].fetch_add(1, Relaxed);
}
pub(crate) fn confirmed_prof_stage_take() -> [(u64, u64); 3] {
std::array::from_fn(|idx| {
(
CONFIRMED_STAGE_NS[idx].swap(0, Relaxed),
CONFIRMED_STAGE_RUNS[idx].swap(0, Relaxed),
)
})
}
pub(crate) fn confirmed_prof_vecs(len: usize) -> (&'static [AtomicU64], &'static [AtomicU64]) {
let ns = CONFIRMED_PAT_NS.get_or_init(|| (0..len).map(|_| AtomicU64::new(0)).collect());
let runs = CONFIRMED_PAT_RUNS.get_or_init(|| (0..len).map(|_| AtomicU64::new(0)).collect());
(ns.as_slice(), runs.as_slice())
}
pub(crate) fn confirmed_prof_reset(len: usize) {
let (ns, runs) = confirmed_prof_vecs(len);
for n in ns {
n.store(0, Relaxed);
}
for r in runs {
r.store(0, Relaxed);
}
for n in &CONFIRMED_STAGE_NS {
n.store(0, Relaxed);
}
for r in &CONFIRMED_STAGE_RUNS {
r.store(0, Relaxed);
}
}
impl super::CompiledScanner {
pub(crate) fn confirmed_profile_dump(&self, label: &str) {
let total = self.ac_map.len() + self.phase2_patterns.len();
let (ns, runs) = confirmed_prof_vecs(total);
let mut rows: Vec<(usize, u64, u64)> = (0..total)
.map(|i| (i, ns[i].swap(0, Relaxed), runs[i].swap(0, Relaxed)))
.filter(|&(_, n, _)| n > 0)
.collect();
rows.sort_unstable_by(|a, b| b.1.cmp(&a.1));
let grand: u64 = rows.iter().map(|r| r.1).sum();
eprintln!(
"=== CONFIRMED per-pattern [{label}] total={:.1} ms over {} triggered patterns ===",
grand as f64 / 1e6,
rows.len()
);
let stages = confirmed_prof_stage_take();
let stage_total: u64 = stages.iter().map(|(ns, _)| *ns).sum();
if stage_total > 0 {
let labels = ["suffix-gate", "anchor-collect", "extract"];
eprintln!(
"=== CONFIRMED stages [{label}] total={:.1} ms ===",
stage_total as f64 / 1e6
);
for (idx, name) in labels.iter().enumerate() {
let (ns, runs) = stages[idx];
if ns == 0 {
continue;
}
let per = if runs > 0 { ns / runs } else { 0 };
eprintln!(
" {:<15} {:>6.1}ms {:>5.1}% runs={:<6} {:>7}ns/run",
name,
ns as f64 / 1e6,
100.0 * ns as f64 / stage_total.max(1) as f64,
runs,
per
);
}
}
for (i, n, r) in rows.iter().take(30) {
let src = if *i < self.ac_map.len() {
self.ac_map[*i].regex.as_str()
} else {
self.phase2_patterns[*i - self.ac_map.len()]
.0
.regex
.as_str()
};
let per = if *r > 0 { *n / *r } else { 0 };
let s: String = src.chars().take(60).collect();
eprintln!(
" {:>6.1}ms {:>5.1}% runs={:<6} {:>7}ns/run {}",
*n as f64 / 1e6,
100.0 * *n as f64 / grand.max(1) as f64,
r,
per,
s
);
}
}
pub(crate) fn confirmed_profile_reset(&self) {
confirmed_prof_reset(self.ac_map.len() + self.phase2_patterns.len());
}
}
#[cfg(feature = "ml")]
pub(crate) fn ml_batch_prof_enabled() -> bool {
super::profile::enabled()
}
#[cfg(feature = "ml")]
static ML_BATCH_BUCKETS: [AtomicU64; 10] = [
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
];
#[cfg(feature = "ml")]
static ML_BATCH_CALLS: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "ml")]
static ML_BATCH_CANDIDATES: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "ml")]
static ML_BATCH_CALLS_GE64: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "ml")]
static ML_BATCH_CANDIDATES_GE64: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "ml")]
fn ml_batch_bucket(n: usize) -> usize {
match n {
0 => 0,
1 => 1,
2..=7 => 2,
8..=15 => 3,
16..=31 => 4,
32..=63 => 5,
64..=127 => 6,
128..=255 => 7,
256..=1023 => 8,
_ => 9,
}
}
#[cfg(feature = "ml")]
pub(crate) fn ml_batch_record(n: usize) {
ML_BATCH_BUCKETS[ml_batch_bucket(n)].fetch_add(1, Relaxed);
ML_BATCH_CALLS.fetch_add(1, Relaxed);
ML_BATCH_CANDIDATES.fetch_add(n as u64, Relaxed);
if n >= 64 {
ML_BATCH_CALLS_GE64.fetch_add(1, Relaxed);
ML_BATCH_CANDIDATES_GE64.fetch_add(n as u64, Relaxed);
}
}
#[cfg(feature = "ml")]
pub(crate) fn ml_batch_profile_dump() {
let calls = ML_BATCH_CALLS.swap(0, Relaxed);
let cands = ML_BATCH_CANDIDATES.swap(0, Relaxed);
let calls_ge64 = ML_BATCH_CALLS_GE64.swap(0, Relaxed);
let cands_ge64 = ML_BATCH_CANDIDATES_GE64.swap(0, Relaxed);
let buckets: [u64; 10] = std::array::from_fn(|i| ML_BATCH_BUCKETS[i].swap(0, Relaxed));
if calls == 0 {
return;
}
let names = [
"0", "1", "2-7", "8-15", "16-31", "32-63", "64-127", "128-255", "256-1023", "1024+",
];
eprintln!(
"=== ML batch-size histogram: calls={calls} candidates={cands} (avg {:.1}/call) | \
GPU-eligible (>=64): {calls_ge64} calls ({:.1}%), {cands_ge64} candidates ({:.1}% of all ML work) ===",
cands as f64 / calls as f64,
100.0 * calls_ge64 as f64 / calls as f64,
100.0 * cands_ge64 as f64 / cands.max(1) as f64,
);
for i in 0..10 {
eprintln!(" {:>9}: {}", names[i], buckets[i]);
}
}
#[cfg(feature = "ml")]
pub(crate) fn ml_batch_profile_reset() {
for bucket in &ML_BATCH_BUCKETS {
bucket.store(0, Relaxed);
}
ML_BATCH_CALLS.store(0, Relaxed);
ML_BATCH_CANDIDATES.store(0, Relaxed);
ML_BATCH_CALLS_GE64.store(0, Relaxed);
ML_BATCH_CANDIDATES_GE64.store(0, Relaxed);
}
#[cfg(not(feature = "ml"))]
pub(crate) fn ml_batch_profile_dump() {}
#[cfg(not(feature = "ml"))]
pub(crate) fn ml_batch_profile_reset() {}
#[cfg(feature = "decode")]
pub(crate) fn decode_prof_enabled() -> bool {
super::profile::enabled()
}
#[cfg(feature = "decode")]
pub(crate) static DECODE_PARENTS: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "decode")]
pub(crate) static DECODE_SUBCHUNKS: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "decode")]
pub(crate) static DECODE_SUBCHUNK_BYTES: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "decode")]
pub(crate) static DECODE_GEN_NS: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "decode")]
pub(crate) static DECODE_SCAN_NS: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "decode")]
pub(crate) fn decode_profile_dump() -> (u64, u64, u64, f64, f64) {
let parents = DECODE_PARENTS.swap(0, Relaxed);
let subchunks = DECODE_SUBCHUNKS.swap(0, Relaxed);
let bytes = DECODE_SUBCHUNK_BYTES.swap(0, Relaxed);
let gen_ms = DECODE_GEN_NS.swap(0, Relaxed) as f64 / 1e6;
let scan_ms = DECODE_SCAN_NS.swap(0, Relaxed) as f64 / 1e6;
if parents == 0 && subchunks == 0 && bytes == 0 && gen_ms == 0.0 && scan_ms == 0.0 {
return (parents, subchunks, bytes, gen_ms, scan_ms);
}
eprintln!(
"decode-recursion: parents={parents} subchunks={subchunks} \
({:.1} sub/parent) bytes={bytes} gen={gen_ms:.1}ms scan={scan_ms:.1}ms \
({:.2} MB/s rescan)",
if parents > 0 {
subchunks as f64 / parents as f64
} else {
0.0
},
if scan_ms > 0.0 {
(bytes as f64 / 1e6) / (scan_ms / 1e3)
} else {
0.0
},
);
(parents, subchunks, bytes, gen_ms, scan_ms)
}
#[cfg(feature = "decode")]
pub(crate) fn decode_profile_reset() {
DECODE_PARENTS.store(0, Relaxed);
DECODE_SUBCHUNKS.store(0, Relaxed);
DECODE_SUBCHUNK_BYTES.store(0, Relaxed);
DECODE_GEN_NS.store(0, Relaxed);
DECODE_SCAN_NS.store(0, Relaxed);
}
#[cfg(not(feature = "decode"))]
pub(crate) fn decode_profile_dump() -> (u64, u64, u64, f64, f64) {
(0, 0, 0, 0.0, 0.0)
}
#[cfg(not(feature = "decode"))]
pub(crate) fn decode_profile_reset() {}