use std::{ any::{type_name, TypeId}, cell::RefCell, collections::HashMap, hash::BuildHasherDefault, os::raw::c_int, sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}, sync::Arc, }; use dashmap::DashMap; use once_cell::sync::OnceCell; use rustc_hash::FxHasher; use crate::{AllCounts, Counts}; static ENABLE: AtomicBool = AtomicBool::new(cfg!(feature = "print_at_exit")); type GlobalStore = DashMap, BuildHasherDefault>; #[inline] fn global_store() -> &'static GlobalStore { static MAP: OnceCell = OnceCell::new(); MAP.get_or_init(|| { if cfg!(feature = "print_at_exit") { extern "C" { fn atexit(f: extern "C" fn()) -> c_int; } extern "C" fn print_at_exit() { eprint!("{}", get_all()); } unsafe { atexit(print_at_exit); } } GlobalStore::default() }) } thread_local! { static LOCAL: RefCell, BuildHasherDefault>> = RefCell::default(); } pub(crate) fn enable(yes: bool) { ENABLE.store(yes, Relaxed); } #[inline] fn enabled() -> bool { ENABLE.load(Relaxed) } #[inline] pub(crate) fn dec() { if enabled() { do_dec(TypeId::of::()) } } #[inline(never)] fn do_dec(key: TypeId) { LOCAL.with(|local| { // Fast path: we have needed store in thread local map if let Some(store) = local.borrow().get(&key) { store.dec(); return; } let global = global_store(); // Slightly slower: we don't have needed store in our thread local map, // but some other thread has already initialized the needed store in the global map if let Some(store) = global.get(&key) { let store = store.value(); local.borrow_mut().insert(key, Arc::clone(store)); store.inc(); return; } // We only decrement counter after incremenrting it, so this line is unreachable }) } #[inline] pub(crate) fn inc() { if enabled() { do_inc(TypeId::of::(), type_name::()) } } #[inline(never)] fn do_inc(key: TypeId, name: &'static str) { LOCAL.with(|local| { // Fast path: we have needed store in thread local map if let Some(store) = local.borrow().get(&key) { store.inc(); return; } let global = global_store(); let copy = match global.get(&key) { // Slightly slower path: we don't have needed store in our thread local map, // but some other thread has already initialized the needed store in the global map Some(store) => { let store = store.value(); store.inc(); Arc::clone(store) } // Slow path: we are the first to initialize both global and local maps None => { let store = global .entry(key) .or_insert_with(|| Arc::new(Store { name, ..Store::default() })) .downgrade(); let store = store.value(); store.inc(); Arc::clone(store) } }; local.borrow_mut().insert(key, copy); }); } pub(crate) fn get() -> Counts { do_get(TypeId::of::()) } fn do_get(key: TypeId) -> Counts { global_store().entry(key).or_default().value().read() } pub(crate) fn get_all() -> AllCounts { let mut entries = global_store() .iter() .map(|entry| { let store = entry.value(); (store.type_name(), store.read()) }) .collect::>(); entries.sort_by_key(|(name, _counts)| *name); AllCounts { entries } } #[derive(Default)] struct Store { total: AtomicUsize, max_live: AtomicUsize, live: AtomicUsize, name: &'static str, } impl Store { fn inc(&self) { self.total.fetch_add(1, Relaxed); let live = self.live.fetch_add(1, Relaxed) + 1; self.max_live.fetch_max(live, Relaxed); } fn dec(&self) { self.live.fetch_sub(1, Relaxed); } fn read(&self) -> Counts { Counts { total: self.total.load(Relaxed), max_live: self.max_live.load(Relaxed), live: self.live.load(Relaxed), } } fn type_name(&self) -> &'static str { self.name } }