use std::cell::{Cell, RefCell};
use std::time::Instant;
use crate::env::{list, Value};
const MAX_EVENTS: usize = 100_000;
pub struct Event {
pub seq: u64,
pub t_micros: u64, pub kind: &'static str, pub kind_dyn: Option<String>, pub name: String,
pub dur_micros: Option<u64>,
pub data: Option<String>,
}
thread_local! {
static ENABLED: Cell<bool> = Cell::new(false);
static EPOCH: RefCell<Option<Instant>> = RefCell::new(None);
static EVENTS: RefCell<Vec<Event>> = RefCell::new(Vec::new());
static SEQ: Cell<u64> = Cell::new(0);
static DROPPED: Cell<u64> = Cell::new(0);
static COV_ON: Cell<bool> = Cell::new(false);
static COVERED: RefCell<std::collections::HashSet<String>> =
RefCell::new(std::collections::HashSet::new());
}
pub fn coverage_enabled() -> bool { COV_ON.with(|c| c.get()) }
#[allow(dead_code)] pub fn coverage_set_enabled(on: bool) { COV_ON.with(|c| c.set(on)); }
pub fn cover(name: &str) {
if !coverage_enabled() { return; }
COVERED.with(|c| { c.borrow_mut().insert(name.to_string()); });
}
#[allow(dead_code)] pub fn coverage_names() -> Vec<String> {
COVERED.with(|c| c.borrow().iter().cloned().collect())
}
pub fn enabled() -> bool {
ENABLED.with(|e| e.get())
}
pub fn set_enabled(on: bool) {
ENABLED.with(|e| e.set(on));
if on {
EPOCH.with(|t| *t.borrow_mut() = Some(Instant::now()));
}
}
pub fn clear() {
EVENTS.with(|ev| ev.borrow_mut().clear());
SEQ.with(|s| s.set(0));
DROPPED.with(|d| d.set(0));
EPOCH.with(|t| *t.borrow_mut() = Some(Instant::now()));
}
fn now_micros() -> u64 {
EPOCH.with(|t| t.borrow().map(|e| e.elapsed().as_micros() as u64).unwrap_or(0))
}
pub fn start() -> Option<Instant> {
if enabled() { Some(Instant::now()) } else { None }
}
pub fn record(kind: &'static str, name: &str, dur_micros: Option<u64>, data: Option<String>) {
if !enabled() { return; }
push(Event {
seq: 0, t_micros: now_micros(), kind, kind_dyn: None,
name: name.to_string(), dur_micros, data,
});
}
pub fn record_since(kind: &'static str, name: &str, t0: Option<Instant>, data: Option<String>) {
if let Some(t0) = t0 {
if enabled() {
let dur = t0.elapsed().as_micros() as u64;
record(kind, name, Some(dur), data);
}
}
}
pub fn record_dyn(kind: String, name: String, data: Option<String>) {
if !enabled() { return; }
push(Event {
seq: 0, t_micros: now_micros(), kind: "", kind_dyn: Some(kind),
name, dur_micros: None, data,
});
}
fn push(mut ev: Event) {
EVENTS.with(|events| {
let mut events = events.borrow_mut();
if events.len() >= MAX_EVENTS {
DROPPED.with(|d| d.set(d.get() + 1));
return;
}
let seq = SEQ.with(|s| { let n = s.get(); s.set(n + 1); n });
ev.seq = seq;
events.push(ev);
});
}
pub fn report() -> Value {
EVENTS.with(|events| {
let rows: Vec<Value> = events.borrow().iter().map(|e| {
let kind = e.kind_dyn.clone().unwrap_or_else(|| e.kind.to_string());
list(vec![
Value::Number(e.seq as f64),
Value::Number(e.t_micros as f64),
Value::Symbol(kind),
Value::Symbol(e.name.clone()),
e.dur_micros.map(|d| Value::Number(d as f64)).unwrap_or(Value::Nil),
e.data.clone().map(Value::String).unwrap_or(Value::Nil),
])
}).collect();
list(rows)
})
}
pub fn dropped() -> u64 {
DROPPED.with(|d| d.get())
}