use std::any::TypeId;
use std::any::type_name;
use std::collections::BTreeMap;
use std::fmt;
use std::marker::PhantomData;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use dashmap::DashMap;
#[derive(Debug)]
pub struct Stats {
#[allow(dead_code)]
pub type_name: &'static str,
pub total: AtomicUsize,
pub max_live: AtomicUsize,
pub live: AtomicUsize,
}
impl Stats {
fn new(type_name: &'static str) -> Self {
Self {
type_name,
total: AtomicUsize::new(0),
max_live: AtomicUsize::new(0),
live: AtomicUsize::new(0),
}
}
}
static COUNTS: std::sync::OnceLock<DashMap<TypeId, Stats>> = std::sync::OnceLock::new();
pub fn counts() -> &'static DashMap<TypeId, Stats> {
COUNTS.get_or_init(DashMap::new)
}
#[derive(Debug)]
pub struct Count<T: 'static> {
_phantom: PhantomData<T>,
}
impl<T: 'static> Count<T> {
pub fn new() -> Self {
let type_id = TypeId::of::<T>();
let type_name = type_name::<T>();
let entry = counts()
.entry(type_id)
.or_insert_with(|| Stats::new(type_name));
entry.total.fetch_add(1, Ordering::Relaxed);
let live = entry.live.fetch_add(1, Ordering::Relaxed) + 1;
entry.max_live.fetch_max(live, Ordering::Relaxed);
Self {
_phantom: PhantomData,
}
}
}
impl<T: 'static> Drop for Count<T> {
fn drop(&mut self) {
let type_id = TypeId::of::<T>();
if let Some(entry) = counts().get(&type_id) {
entry.live.fetch_sub(1, Ordering::Relaxed);
}
}
}
impl<T: 'static> Default for Count<T> {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
pub struct Report {
pub by_type: BTreeMap<&'static str, (usize, usize, usize)>,
}
impl Report {
#[allow(dead_code)]
fn new() -> Self {
let mut by_type = BTreeMap::new();
for entry in counts().iter() {
let stats = entry.value();
by_type.insert(
stats.type_name,
(
stats.total.load(Ordering::Relaxed),
stats.max_live.load(Ordering::Relaxed),
stats.live.load(Ordering::Relaxed),
),
);
}
Self { by_type }
}
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name_width = self
.by_type
.keys()
.map(|k| k.len())
.max()
.unwrap_or(0)
.max(5);
let total_width = 12;
let max_live_width = 12;
let live_width = 12;
writeln!(
f,
"{:<name_width$} {:>total_width$} {:>max_live_width$} {:>live_width$}",
"", "total", "max_live", "live"
)?;
for (name, (total, max_live, live)) in &self.by_type {
writeln!(
f,
"{name:<name_width$} {total:>total_width$} {max_live:>max_live_width$} {live:>live_width$}"
)?;
}
Ok(())
}
}
#[allow(dead_code)]
pub fn report() -> Report {
Report::new()
}
#[allow(dead_code)]
pub fn report_string() -> String {
Report::new().to_string()
}