#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct SiteTally {
pub count: u64,
pub ms: f64,
pub features: u64,
pub reused: u64,
pub errors: u64,
}
#[cfg(not(target_arch = "wasm32"))]
mod native {
use super::SiteTally;
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Mutex;
use std::time::Instant;
static ENABLED: AtomicU8 = AtomicU8::new(0);
pub struct Tally(Mutex<BTreeMap<&'static str, SiteTally>>);
impl Tally {
pub const fn new() -> Self {
Tally(Mutex::new(BTreeMap::new()))
}
fn fold(&self, site: &'static str, ms: f64, features: u64, reused: u64, errors: u64) {
if let Ok(mut map) = self.0.lock() {
let entry = map.entry(site).or_default();
entry.count += 1;
entry.ms += ms;
entry.features += features;
entry.reused += reused;
entry.errors += errors;
}
}
pub fn sites(&self) -> Vec<(&'static str, SiteTally)> {
match self.0.lock() {
Ok(map) => map.iter().map(|(site, t)| (*site, *t)).collect(),
Err(poisoned) => poisoned.into_inner().iter().map(|(site, t)| (*site, *t)).collect(),
}
}
pub fn clear(&self) {
match self.0.lock() {
Ok(mut map) => map.clear(),
Err(poisoned) => poisoned.into_inner().clear(),
}
}
}
static TALLY: Tally = Tally::new();
pub fn enabled() -> bool {
match ENABLED.load(Ordering::Relaxed) {
0 => {
let on = std::env::var("BREP_RUN_TRACE").is_ok_and(|value| value != "0");
ENABLED.store(if on { 2 } else { 1 }, Ordering::Relaxed);
on
}
2 => true,
_ => false,
}
}
pub fn tally() -> Vec<(&'static str, SiteTally)> {
TALLY.sites()
}
pub fn reset() {
TALLY.clear()
}
pub struct Span {
sink: &'static Tally,
site: &'static str,
start: Instant,
features: u64,
reused: u64,
errors: u64,
reported: bool,
}
impl Span {
pub fn result(&mut self, result: &brep_kernel::HistoryResult) {
self.features = result.results.len() as u64;
self.reused = result.results.iter().filter(|feature| feature.reused).count() as u64;
self.errors = result.results.iter().filter(|feature| feature.error.is_some()).count() as u64;
self.reported = true;
}
}
impl Drop for Span {
fn drop(&mut self) {
let ms = self.start.elapsed().as_secs_f64() * 1e3;
self.sink.fold(self.site, ms, self.features, self.reused, self.errors);
if enabled() {
let note = if self.reported {
format!(" features={} reused={} errors={}", self.features, self.reused, self.errors)
} else {
String::new()
};
eprintln!(
"[run-trace] site={} thread={:?} ms={ms:.1}{note}",
self.site,
std::thread::current().id(),
);
}
}
}
pub fn span(site: &'static str) -> Option<Span> {
span_into(site, &TALLY)
}
pub(crate) fn span_into(site: &'static str, sink: &'static Tally) -> Option<Span> {
Some(Span { sink, site, start: Instant::now(), features: 0, reused: 0, errors: 0, reported: false })
}
}
#[cfg(not(target_arch = "wasm32"))]
pub use native::{reset, span, tally, Tally};
#[cfg(target_arch = "wasm32")]
pub struct Span;
#[cfg(target_arch = "wasm32")]
impl Span {
pub fn result(&mut self, _result: &brep_kernel::HistoryResult) {}
}
#[cfg(target_arch = "wasm32")]
pub fn span(_site: &'static str) -> Option<Span> {
None
}
#[cfg(target_arch = "wasm32")]
pub fn tally() -> Vec<(&'static str, SiteTally)> {
Vec::new()
}
#[cfg(target_arch = "wasm32")]
pub fn reset() {}
pub fn tally_json() -> serde_json::Value {
json_of(tally())
}
fn json_of(tally: Vec<(&'static str, SiteTally)>) -> serde_json::Value {
let mut sites = serde_json::Map::new();
let mut total = SiteTally::default();
for (site, t) in tally {
total.count += t.count;
total.ms += t.ms;
total.features += t.features;
total.reused += t.reused;
total.errors += t.errors;
sites.insert(
site.to_string(),
serde_json::json!({
"count": t.count,
"ms": (t.ms * 10.0).round() / 10.0,
"features": t.features,
"reused": t.reused,
"errors": t.errors,
}),
);
}
serde_json::json!({
"sites": serde_json::Value::Object(sites),
"total": {
"count": total.count,
"ms": (total.ms * 10.0).round() / 10.0,
"features": total.features,
"reused": total.reused,
"errors": total.errors,
},
})
}