use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
#[derive(Clone, Copy)]
pub enum Phase {
Build = 0,
Clone = 1,
Interpret = 2,
Gpu = 3,
Frame = 4,
Present = 5,
}
const N: usize = 6;
const NAMES: [&str; N] = ["build", "clone", "interpret", "gpu", "frame", "present"];
const DUMP_EVERY: u64 = 60;
static ENABLED: OnceLock<bool> = OnceLock::new();
static SUMS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static COUNTS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static MAXES: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static FRAMES: AtomicU64 = AtomicU64::new(0);
static DAMAGE_FRAMES: AtomicU64 = AtomicU64::new(0);
#[inline]
pub fn note_damage(active: bool) {
if active && enabled() {
DAMAGE_FRAMES.fetch_add(1, Ordering::Relaxed);
}
}
#[inline]
pub fn enabled() -> bool {
*ENABLED.get_or_init(
|| matches!(std::env::var("TELAR_PERF").as_deref(), Ok(v) if !v.is_empty() && v != "0"),
)
}
#[inline]
pub fn now_if_enabled() -> Option<Instant> {
if enabled() {
Some(Instant::now())
} else {
None
}
}
#[inline]
pub fn record(phase: Phase, dur: Duration) {
let i = phase as usize;
let ns = dur.as_nanos() as u64;
SUMS[i].fetch_add(ns, Ordering::Relaxed);
COUNTS[i].fetch_add(1, Ordering::Relaxed);
MAXES[i].fetch_max(ns, Ordering::Relaxed);
}
#[inline]
pub fn record_since(phase: Phase, start: Option<Instant>) {
if let Some(t) = start {
record(phase, t.elapsed());
}
}
pub struct Span {
phase: Phase,
start: Instant,
}
#[inline]
pub fn span(phase: Phase) -> Option<Span> {
if enabled() {
Some(Span {
phase,
start: Instant::now(),
})
} else {
None
}
}
impl Drop for Span {
fn drop(&mut self) {
record(self.phase, self.start.elapsed());
}
}
pub fn tick() {
if !enabled() {
return;
}
let f = FRAMES.fetch_add(1, Ordering::Relaxed) + 1;
if f % DUMP_EVERY != 0 {
return;
}
let mut parts = String::new();
for i in 0..N {
let sum = SUMS[i].swap(0, Ordering::Relaxed);
let cnt = COUNTS[i].swap(0, Ordering::Relaxed);
let mx = MAXES[i].swap(0, Ordering::Relaxed);
if cnt == 0 {
continue;
}
let avg_us = (sum as f64 / cnt as f64) / 1000.0;
let max_us = mx as f64 / 1000.0;
parts.push_str(&format!("{}={avg_us:.0}/{max_us:.0}us(n{cnt}) ", NAMES[i]));
}
let damage = DAMAGE_FRAMES.swap(0, Ordering::Relaxed);
tracing::info!(target: "telar_perf", "perf[{DUMP_EVERY}f] {}damage={damage}", parts);
}