captrack 0.1.0

Capacity telemetry for Rust collections — call-site macros that record peak capacity, with zero overhead when disabled.
use std::collections::BTreeMap;

use crate::registry;
use crate::IntoInner;

/// A `BTreeMap<K, V>` wrapper that records creation count and capacity samples.
///
/// `BTreeMap` has no `with_capacity` so the capacity hint passed to the macro
/// is accepted but ignored for the inner allocation.
///
/// # Samples record `len()`, NOT peak occupancy
///
/// B-tree capacity is not observable.  On every `Drop` (or `IntoIterator` /
/// `From` conversion), `inner.len()` is pushed as the sample — this is the
/// element count **at the moment of Drop**, not the maximum ever observed.
///
/// For maps that may shrink before Drop (e.g. via `BTreeMap::clear` or
/// repeated `remove` calls), the recorded sample **undercounts** the true
/// peak.  Use `BTreeMap::len()` at the known peak point if accurate peak
/// tracking is required.
pub struct TrackedBTreeMap<K: Ord, V> {
    inner: BTreeMap<K, V>,
    #[allow(dead_code)]
    name: &'static str,
    file: &'static str,
    line: u32,
    column: u32,
}

impl<K: Ord, V> TrackedBTreeMap<K, V> {
    /// `_cap_hint` is accepted for API uniformity (matches the macro signature)
    /// but is not passed to `BTreeMap` which has no capacity concept.
    pub fn new_named(
        _cap_hint: usize,
        name: &'static str,
        file: &'static str,
        line: u32,
        column: u32,
    ) -> Self {
        registry::record_creation(name, file, line, column);
        Self {
            inner: BTreeMap::new(),
            name,
            file,
            line,
            column,
        }
    }

    /// Wrap an already-constructed `BTreeMap<K, V>` for capacity telemetry.
    ///
    /// Records creation in the registry; `inner` is moved as-is.
    /// The sample metric at `Drop` is `inner.len()` (BTreeMap has no capacity).
    #[inline]
    pub fn wrap_from(
        inner: BTreeMap<K, V>,
        name: &'static str,
        file: &'static str,
        line: u32,
        column: u32,
    ) -> Self {
        registry::record_creation(name, file, line, column);
        Self {
            inner,
            name,
            file,
            line,
            column,
        }
    }
}

impl<K: Ord, V> std::ops::Deref for TrackedBTreeMap<K, V> {
    type Target = BTreeMap<K, V>;
    fn deref(&self) -> &BTreeMap<K, V> {
        &self.inner
    }
}

impl<K: Ord, V> std::ops::DerefMut for TrackedBTreeMap<K, V> {
    fn deref_mut(&mut self) -> &mut BTreeMap<K, V> {
        &mut self.inner
    }
}

impl<K: Ord, V> Drop for TrackedBTreeMap<K, V> {
    fn drop(&mut self) {
        // BTreeMap has no `capacity()` — record len as the sample metric.
        registry::record_sample(self.file, self.line, self.column, self.inner.len());
    }
}

impl<K: Ord, V> From<TrackedBTreeMap<K, V>> for BTreeMap<K, V> {
    fn from(tracked: TrackedBTreeMap<K, V>) -> BTreeMap<K, V> {
        registry::record_sample(
            tracked.file,
            tracked.line,
            tracked.column,
            tracked.inner.len(),
        );
        // SAFETY: `tracked` is owned and forgotten below; ptr::read bit-copies
        // `inner` (BTreeMap has no capacity, no Default needed).
        let inner = unsafe { std::ptr::read(&tracked.inner) };
        std::mem::forget(tracked);
        inner
    }
}

impl<K: Ord, V> IntoInner for TrackedBTreeMap<K, V> {
    type Inner = BTreeMap<K, V>;
    #[inline]
    fn into_inner(self) -> BTreeMap<K, V> {
        BTreeMap::from(self)
    }
}

impl<K: Ord, V> IntoIterator for TrackedBTreeMap<K, V> {
    type Item = (K, V);
    type IntoIter = std::collections::btree_map::IntoIter<K, V>;

    fn into_iter(self) -> Self::IntoIter {
        registry::record_sample(self.file, self.line, self.column, self.inner.len());
        // SAFETY: `self` is owned and forgotten below; ptr::read bit-copies `inner`.
        let inner = unsafe { std::ptr::read(&self.inner) };
        std::mem::forget(self);
        inner.into_iter()
    }
}