use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::OnceLock;
use std::time::Instant;
static ENABLED: OnceLock<bool> = OnceLock::new();
fn enabled() -> bool {
*ENABLED.get_or_init(|| {
std::env::var("MIR_TIMING")
.map(|v| v != "0" && !v.is_empty())
.unwrap_or(false)
})
}
#[derive(Default)]
pub struct Counters {
pub file_analyses: AtomicU64,
pub body_analysis_runs: AtomicU64,
pub lazy_loads_attempted: AtomicU64,
pub lazy_loads_resolved: AtomicU64,
pub body_analysis_micros: AtomicU64,
pub stub_cache_hits: AtomicU64,
pub stub_cache_misses: AtomicU64,
pub ll_fail_no_resolver: AtomicU64,
pub ll_fail_resolver_none: AtomicU64,
pub ll_fail_source_unreadable: AtomicU64,
pub ll_fail_ingest_then_missing: AtomicU64,
}
static COUNTERS: Counters = Counters {
file_analyses: AtomicU64::new(0),
body_analysis_runs: AtomicU64::new(0),
lazy_loads_attempted: AtomicU64::new(0),
lazy_loads_resolved: AtomicU64::new(0),
body_analysis_micros: AtomicU64::new(0),
stub_cache_hits: AtomicU64::new(0),
stub_cache_misses: AtomicU64::new(0),
ll_fail_no_resolver: AtomicU64::new(0),
ll_fail_resolver_none: AtomicU64::new(0),
ll_fail_source_unreadable: AtomicU64::new(0),
ll_fail_ingest_then_missing: AtomicU64::new(0),
};
pub fn record_file_analysis() {
if enabled() {
COUNTERS.file_analyses.fetch_add(1, Ordering::Relaxed);
}
}
pub fn record_body_analysis(duration_micros: u64) {
if enabled() {
COUNTERS.body_analysis_runs.fetch_add(1, Ordering::Relaxed);
COUNTERS
.body_analysis_micros
.fetch_add(duration_micros, Ordering::Relaxed);
}
}
pub fn record_lazy_load_attempt(resolved: bool) {
if enabled() {
COUNTERS
.lazy_loads_attempted
.fetch_add(1, Ordering::Relaxed);
if resolved {
COUNTERS.lazy_loads_resolved.fetch_add(1, Ordering::Relaxed);
}
}
}
pub fn record_stub_cache_hit() {
if enabled() {
COUNTERS.stub_cache_hits.fetch_add(1, Ordering::Relaxed);
}
}
pub fn record_stub_cache_miss() {
if enabled() {
COUNTERS.stub_cache_misses.fetch_add(1, Ordering::Relaxed);
}
}
#[derive(Copy, Clone, Debug)]
pub enum LazyLoadFailure {
NoResolver,
ResolverNone,
SourceUnreadable,
IngestThenMissing,
}
static FAILURE_SAMPLES: std::sync::Mutex<FailureSamples> =
std::sync::Mutex::new(FailureSamples::new());
struct FailureSamples {
no_resolver: Vec<String>,
resolver_none: Vec<String>,
source_unreadable: Vec<String>,
ingest_then_missing: Vec<String>,
}
impl FailureSamples {
const fn new() -> Self {
Self {
no_resolver: Vec::new(),
resolver_none: Vec::new(),
source_unreadable: Vec::new(),
ingest_then_missing: Vec::new(),
}
}
fn push(bucket: &mut Vec<String>, fqcn: &str) {
if bucket.len() < 40 && !bucket.iter().any(|s| s == fqcn) {
bucket.push(fqcn.to_string());
}
}
}
pub fn record_lazy_load_failure(reason: LazyLoadFailure, fqcn: &str) {
if !enabled() {
return;
}
let counter = match reason {
LazyLoadFailure::NoResolver => &COUNTERS.ll_fail_no_resolver,
LazyLoadFailure::ResolverNone => &COUNTERS.ll_fail_resolver_none,
LazyLoadFailure::SourceUnreadable => &COUNTERS.ll_fail_source_unreadable,
LazyLoadFailure::IngestThenMissing => &COUNTERS.ll_fail_ingest_then_missing,
};
counter.fetch_add(1, Ordering::Relaxed);
let mut samples = FAILURE_SAMPLES.lock().unwrap();
let bucket = match reason {
LazyLoadFailure::NoResolver => &mut samples.no_resolver,
LazyLoadFailure::ResolverNone => &mut samples.resolver_none,
LazyLoadFailure::SourceUnreadable => &mut samples.source_unreadable,
LazyLoadFailure::IngestThenMissing => &mut samples.ingest_then_missing,
};
FailureSamples::push(bucket, fqcn);
}
fn render_samples() -> String {
let s = FAILURE_SAMPLES.lock().unwrap();
let mut out = String::new();
let buckets = [
("no_resolver", &s.no_resolver),
("resolver_none", &s.resolver_none),
("source_unreadable", &s.source_unreadable),
("ingest_then_missing", &s.ingest_then_missing),
];
for (name, b) in buckets {
if b.is_empty() {
continue;
}
out.push_str(&format!("\n sample {} ({}):", name, b.len()));
for fqcn in b.iter().take(20) {
out.push_str(&format!("\n {fqcn}"));
}
}
out
}
pub struct BodyAnalysisScope {
start: Option<Instant>,
}
impl BodyAnalysisScope {
pub fn new() -> Self {
Self {
start: if enabled() {
Some(Instant::now())
} else {
None
},
}
}
}
impl Default for BodyAnalysisScope {
fn default() -> Self {
Self::new()
}
}
impl Drop for BodyAnalysisScope {
fn drop(&mut self) {
if let Some(t0) = self.start {
record_body_analysis(t0.elapsed().as_micros() as u64);
}
}
}
pub fn dump() -> Option<String> {
if !enabled() {
return None;
}
let analyses = COUNTERS.file_analyses.load(Ordering::Relaxed);
let body_analysis_runs = COUNTERS.body_analysis_runs.load(Ordering::Relaxed);
let attempts = COUNTERS.lazy_loads_attempted.load(Ordering::Relaxed);
let resolved = COUNTERS.lazy_loads_resolved.load(Ordering::Relaxed);
let body_analysis_micros = COUNTERS.body_analysis_micros.load(Ordering::Relaxed);
let cache_hits = COUNTERS.stub_cache_hits.load(Ordering::Relaxed);
let cache_misses = COUNTERS.stub_cache_misses.load(Ordering::Relaxed);
let ll_no_resolver = COUNTERS.ll_fail_no_resolver.load(Ordering::Relaxed);
let ll_resolver_none = COUNTERS.ll_fail_resolver_none.load(Ordering::Relaxed);
let ll_source_unreadable = COUNTERS.ll_fail_source_unreadable.load(Ordering::Relaxed);
let ll_ingest_missing = COUNTERS.ll_fail_ingest_then_missing.load(Ordering::Relaxed);
let avg_pass2_us = body_analysis_micros
.checked_div(body_analysis_runs)
.unwrap_or(0);
let samples = render_samples();
Some(format!(
"mir metrics:\n \
file analyses : {analyses}\n \
body analysis runs : {body_analysis_runs}\n \
body analysis time : {body_analysis_micros} us (avg/run: {avg_pass2_us} us)\n \
lazy load attempts : {attempts} resolved: {resolved}\n \
lazy load failures : no_resolver={ll_no_resolver} resolver_none={ll_resolver_none} \
source_unreadable={ll_source_unreadable} ingest_then_missing={ll_ingest_missing}\n \
stub cache : hits {cache_hits} misses {cache_misses}{samples}"
))
}