use std::sync::Mutex;
use std::time::Duration;
const CAPACITY: usize = 256;
#[derive(Debug, Clone, Copy)]
struct Poll {
duration: Duration,
}
#[derive(Debug, Default, Clone, Copy)]
struct Bucket {
calls: u64,
spent: Duration,
worst: Duration,
}
impl Bucket {
fn record(&mut self, duration: Duration) {
self.calls += 1;
self.spent += duration;
if duration > self.worst {
self.worst = duration;
}
}
fn absorb(&mut self, other: &Bucket) {
self.calls += other.calls;
self.spent += other.spent;
if other.worst > self.worst {
self.worst = other.worst;
}
}
}
#[derive(Debug, Default)]
struct Log {
statics: std::collections::BTreeMap<&'static str, Bucket>,
dynamic: std::collections::BTreeMap<String, Bucket>,
polls: Vec<Poll>,
total: u64,
productive: u64,
spent: Duration,
}
static LOG: Mutex<Option<Log>> = Mutex::new(None);
thread_local! {
static LOCAL_STATICS: std::cell::RefCell<Vec<(&'static str, Bucket)>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub fn record_static(label: &'static str, duration: Duration) {
let _ = LOCAL_STATICS.try_with(|local| {
let Ok(mut buckets) = local.try_borrow_mut() else {
return;
};
if let Some((_, bucket)) = buckets
.iter_mut()
.find(|(seen, _)| std::ptr::eq(*seen, label) || *seen == label)
{
bucket.record(duration);
return;
}
let mut bucket = Bucket::default();
bucket.record(duration);
buckets.push((label, bucket));
});
}
fn drain_local_statics(log: &mut Log) {
let _ = LOCAL_STATICS.try_with(|local| {
let Ok(mut buckets) = local.try_borrow_mut() else {
return;
};
for (label, bucket) in buckets.iter_mut() {
log.statics.entry(*label).or_default().absorb(bucket);
*bucket = Bucket::default();
}
});
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct BoundaryCounters {
pub strings_crossed: u64,
pub bytes_copied: u64,
}
thread_local! {
static BOUNDARY: std::cell::Cell<BoundaryCounters> =
const { std::cell::Cell::new(BoundaryCounters { strings_crossed: 0, bytes_copied: 0 }) };
}
pub(crate) fn record_boundary_string(bytes: usize) {
if !blitz_traits::profiling::deep_profiling_enabled() {
return;
}
let _ = BOUNDARY.try_with(|cell| {
let mut counters = cell.get();
counters.strings_crossed += 1;
counters.bytes_copied += bytes as u64;
cell.set(counters);
});
}
#[must_use]
pub fn boundary_counters() -> BoundaryCounters {
BOUNDARY.try_with(std::cell::Cell::get).unwrap_or_default()
}
pub fn reset_boundary_counters() {
let _ = BOUNDARY.try_with(|cell| cell.set(BoundaryCounters::default()));
}
pub fn record_work(label: &str, duration: Duration) {
let Ok(mut guard) = LOG.lock() else {
return;
};
let log = guard.get_or_insert_with(Log::default);
log.dynamic
.entry(label.to_string())
.or_default()
.record(duration);
}
#[must_use]
pub fn work_breakdown() -> Vec<(String, u64, f64, f64)> {
let Ok(mut guard) = LOG.lock() else {
return Vec::new();
};
let log = guard.get_or_insert_with(Log::default);
drain_local_statics(log);
let mut rows: Vec<(String, u64, f64, f64)> = log
.statics
.iter()
.map(|(label, bucket)| {
(
(*label).to_string(),
bucket.calls,
bucket.spent.as_secs_f64() * 1_000.0,
bucket.worst.as_secs_f64() * 1_000.0,
)
})
.chain(log.dynamic.iter().map(|(label, bucket)| {
(
label.clone(),
bucket.calls,
bucket.spent.as_secs_f64() * 1_000.0,
bucket.worst.as_secs_f64() * 1_000.0,
)
}))
.collect();
rows.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
rows
}
pub fn record_poll(duration: Duration, ran_script: bool) {
let Ok(mut guard) = LOG.lock() else {
return;
};
let log = guard.get_or_insert_with(Log::default);
drain_local_statics(log);
log.total += 1;
log.spent += duration;
maybe_report(log);
if !ran_script {
return;
}
log.productive += 1;
if log.polls.len() == CAPACITY {
log.polls.remove(0);
}
log.polls.push(Poll { duration });
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScriptStatsSnapshot {
pub mean_ms: f64,
pub p95_ms: f64,
pub max_ms: f64,
pub window_polls: u64,
pub total_polls: u64,
pub productive_polls: u64,
pub spent_ms: f64,
}
#[must_use]
pub fn latest_script_stats() -> Option<ScriptStatsSnapshot> {
if !blitz_traits::profiling::deep_profiling_permitted() {
return None;
}
let guard = LOG.lock().ok()?;
let log = guard.as_ref()?;
if log.polls.is_empty() {
return None;
}
let mut millis: Vec<f64> = log
.polls
.iter()
.map(|poll| poll.duration.as_secs_f64() * 1_000.0)
.collect();
let sum: f64 = millis.iter().sum();
let mean = sum / millis.len() as f64;
millis.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let rank = ((millis.len() as f64) * 0.95).ceil() as usize;
let p95 = millis[rank.saturating_sub(1).min(millis.len() - 1)];
Some(ScriptStatsSnapshot {
mean_ms: mean,
p95_ms: p95,
max_ms: *millis.last().unwrap_or(&0.0),
window_polls: millis.len() as u64,
total_polls: log.total,
productive_polls: log.productive,
spent_ms: log.spent.as_secs_f64() * 1_000.0,
})
}
#[cfg(feature = "dom-stats")]
pub struct Timed {
label: &'static str,
started: Option<std::time::Instant>,
}
#[cfg(feature = "dom-stats")]
impl Timed {
#[must_use]
pub(crate) fn new(ctx: &crate::state::DomCtx, label: &'static str) -> Self {
Self {
label,
started: ctx.deep_profiling_enabled().then(std::time::Instant::now),
}
}
}
#[cfg(feature = "dom-stats")]
impl Drop for Timed {
fn drop(&mut self) {
if let Some(started) = self.started {
record_static(self.label, started.elapsed());
}
}
}
pub fn clear() {
if let Ok(mut log) = LOG.lock() {
*log = None;
}
let _ = LOCAL_STATICS.try_with(|local| {
if let Ok(mut buckets) = local.try_borrow_mut() {
buckets.clear();
}
});
}
#[cfg(not(feature = "dom-stats"))]
pub struct Timed;
#[cfg(not(feature = "dom-stats"))]
impl Timed {
#[must_use]
#[inline(always)]
pub(crate) fn new(_ctx: &crate::state::DomCtx, _label: &'static str) -> Self {
Self
}
}
#[cfg(test)]
mod tests {
use super::*;
static SERIAL: Mutex<()> = Mutex::new(());
struct TestCapture {
_serial: std::sync::MutexGuard<'static, ()>,
_sampling: blitz_traits::profiling::DeepProfilingGuard,
}
fn reset() -> TestCapture {
let guard = SERIAL
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*LOG.lock().unwrap() = None;
LOCAL_STATICS.with(|local| local.borrow_mut().clear());
blitz_traits::profiling::set_deep_profiling_permitted(true);
let sampling =
blitz_traits::profiling::begin_deep_profiling().expect("permission was just granted");
TestCapture {
_serial: guard,
_sampling: sampling,
}
}
#[test]
fn static_labels_reach_the_breakdown_without_locking_per_call() {
let _serial = reset();
for _ in 0..3 {
record_static("dom:appendChild", Duration::from_micros(10));
}
record_static("dom:appendChild", Duration::from_micros(90));
let rows = work_breakdown();
let row = rows
.iter()
.find(|(label, ..)| label == "dom:appendChild")
.expect("the static bucket is reported");
assert_eq!(row.1, 4, "every call counted: {rows:?}");
assert!(
(row.3 - 0.09).abs() < 0.01,
"the worst call survives the total: {rows:?}"
);
}
#[test]
fn folding_twice_does_not_double_count() {
let _serial = reset();
record_static("dom:createElement", Duration::from_micros(50));
let first = work_breakdown();
let second = work_breakdown();
assert_eq!(
first, second,
"a drained bucket must not be added to the shared log again"
);
}
#[test]
fn nothing_is_reported_before_script_runs() {
let _serial = reset();
record_poll(Duration::from_millis(5), false);
assert!(
latest_script_stats().is_none(),
"idle polls are not a measurement of script cost"
);
}
#[test]
fn the_worst_poll_survives_the_mean() {
let _serial = reset();
for _ in 0..40 {
record_poll(Duration::from_millis(1), true);
}
record_poll(Duration::from_millis(60), true);
let stats = latest_script_stats().expect("script ran");
assert!(stats.mean_ms < 3.0, "one outlier must not move the mean");
assert!(
(stats.max_ms - 60.0).abs() < 1.0,
"the outlier is the whole point: {stats:?}"
);
}
#[test]
fn idle_polls_are_counted_without_diluting_the_window() {
let _serial = reset();
record_poll(Duration::from_millis(2), true);
for _ in 0..10 {
record_poll(Duration::from_micros(10), false);
}
let stats = latest_script_stats().expect("script ran");
assert_eq!(stats.window_polls, 1);
assert_eq!(stats.total_polls, 11);
assert_eq!(stats.productive_polls, 1);
}
#[cfg(feature = "dom-stats")]
#[test]
fn poll_keeps_its_selected_mode_when_the_global_flag_changes_inside_it() {
use blitz_dom::{Document, DocumentConfig};
let _serial = reset();
let mut document =
crate::ScriptDocument::from_html("<body></body>", DocumentConfig::default());
document.set_poll_hook(|document, _| {
blitz_traits::profiling::set_deep_profiling_permitted(false);
document.eval("document.body.appendChild(document.createElement('div'))");
true
});
assert!(document.poll(None));
blitz_traits::profiling::set_deep_profiling_permitted(true);
assert!(
work_breakdown()
.iter()
.any(|(label, ..)| label == "dom:createElement"),
"DOM attribution follows the enclosing poll mode"
);
assert!(latest_script_stats().is_some());
}
#[cfg(feature = "dom-stats")]
#[test]
fn disabled_poll_does_not_start_collecting_if_the_global_turns_on_inside_it() {
use blitz_dom::{Document, DocumentConfig};
let _serial = reset();
clear();
blitz_traits::profiling::set_deep_profiling_permitted(false);
let mut document =
crate::ScriptDocument::from_html("<body></body>", DocumentConfig::default());
document.set_poll_hook(|document, _| {
blitz_traits::profiling::set_deep_profiling_permitted(true);
document.eval("document.body.appendChild(document.createElement('div'))");
true
});
assert!(document.poll(None));
assert!(work_breakdown().is_empty());
assert!(latest_script_stats().is_none());
}
}
fn maybe_report(log: &Log) {
use std::sync::OnceLock;
use std::time::Instant;
static ENABLED: OnceLock<bool> = OnceLock::new();
if !*ENABLED.get_or_init(|| {
matches!(
std::env::var("BLITZ_SCRIPT_STATS").ok().as_deref(),
Some("1") | Some("true")
)
}) {
return;
}
static LAST: std::sync::Mutex<Option<Instant>> = std::sync::Mutex::new(None);
let Ok(mut last) = LAST.lock() else { return };
let now = Instant::now();
if last.is_some_and(|t| now.duration_since(t) < Duration::from_secs(1)) {
return;
}
let elapsed = last.map(|t| now.duration_since(t));
*last = Some(now);
drop(last);
let spent_ms = log.spent.as_secs_f64() * 1000.0;
static PREV_SPENT: std::sync::Mutex<f64> = std::sync::Mutex::new(0.0);
let delta_ms = if let Ok(mut prev) = PREV_SPENT.lock() {
let d = spent_ms - *prev;
*prev = spent_ms;
d
} else {
0.0
};
let share = elapsed
.map(|e| delta_ms / (e.as_secs_f64() * 1000.0) * 100.0)
.unwrap_or(0.0);
eprintln!(
"[script] polls={} productive={} spent={spent_ms:.0}ms last_second={delta_ms:.1}ms ({share:.1}% of wall clock)",
log.total, log.productive,
);
}