mod demangle;
use std::collections::HashMap;
use std::sync::atomic::AtomicUsize;
pub type AllocId = usize;
#[derive(Debug)]
pub struct SymbolTable {
modules: &'static [&'static str],
symbols: HashMap<&'static str, Symbol>,
ptr_to_symbol: HashMap<AllocId, &'static str>,
}
impl SymbolTable {
pub(crate) fn new(size: usize, modules: &'static [&'static str]) -> Self {
Self {
modules,
symbols: HashMap::with_capacity(size),
ptr_to_symbol: HashMap::with_capacity(size),
}
}
pub fn iter(&self) -> impl Iterator<Item = (&&'static str, &Symbol)> {
self.symbols.iter()
}
pub fn get(&self, name: &'static str) -> Option<&Symbol> {
self.symbols.get(&name)
}
pub(crate) fn alloc(&mut self, alloc_id: AllocId, bytes: usize) {
let name = demangle::get_demangled_symbol(self.modules);
if !self.symbols.contains_key(&name) {
self.insert(name);
}
self.ptr_to_symbol.insert(alloc_id, name);
let symbol = self.symbols.get_mut(name).expect("Symbol should exist");
symbol
.allocated
.fetch_add(bytes, std::sync::atomic::Ordering::Relaxed);
symbol
.count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub(crate) fn dealloc(&mut self, alloc_id: AllocId, bytes: usize) {
let Some(name) = self.ptr_to_symbol.remove(&alloc_id) else {
return;
};
if let Some(symbol) = self.symbols.get_mut(name) {
symbol
.allocated
.fetch_update(
std::sync::atomic::Ordering::Relaxed,
std::sync::atomic::Ordering::Relaxed,
|current| Some(current.saturating_sub(bytes)),
)
.ok();
symbol
.count
.fetch_update(
std::sync::atomic::Ordering::Relaxed,
std::sync::atomic::Ordering::Relaxed,
|current| Some(current.saturating_sub(1)),
)
.ok();
}
}
fn insert(&mut self, name: &'static str) {
self.symbols.insert(
name,
Symbol {
allocated: AtomicUsize::new(0),
count: AtomicUsize::new(0),
},
);
}
}
#[derive(Debug)]
pub struct Symbol {
allocated: AtomicUsize,
count: AtomicUsize,
}
impl Symbol {
pub fn allocated(&self) -> usize {
self.allocated.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn count(&self) -> usize {
self.count.load(std::sync::atomic::Ordering::Relaxed)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_should_allocate_symbol() {
const ALLOC_ID: AllocId = 42;
const ALLOC_ID_2: AllocId = 43;
let mut table = SymbolTable::new(10, &["leaktracer"]);
table.alloc(ALLOC_ID, 100);
let name = demangle::get_demangled_symbol(&["leaktracer"]);
let symbol = table.get(name).expect("Symbol should exist");
assert_eq!(symbol.allocated(), 100);
assert_eq!(symbol.count(), 1);
assert_eq!(table.ptr_to_symbol.get(&ALLOC_ID), Some(&name));
table.alloc(ALLOC_ID_2, 50);
let symbol = table.get(name).expect("Symbol should exist");
assert_eq!(symbol.allocated(), 150);
assert_eq!(symbol.count(), 2);
assert_eq!(table.ptr_to_symbol.get(&ALLOC_ID_2), Some(&name));
table.dealloc(ALLOC_ID, 40);
let symbol = table.get(name).expect("Symbol should exist");
assert_eq!(symbol.allocated(), 110);
assert_eq!(symbol.count(), 1);
assert_eq!(table.ptr_to_symbol.get(&ALLOC_ID), None);
}
#[test]
fn test_should_iter_symbol_table() {
let mut table = SymbolTable::new(10, &["leaktracer"]);
table.insert("test_symbol_1");
table.insert("test_symbol_2");
let symbols: Vec<_> = table.iter().collect();
assert_eq!(symbols.len(), 2);
assert!(
symbols
.iter()
.any(|(symbol, _)| **symbol == "test_symbol_1")
);
assert!(
symbols
.iter()
.any(|(symbol, _)| **symbol == "test_symbol_2")
);
}
}