Skip to main content

mod_alloc/dhat_compat/
stats.rs

1//! dhat-rs-shaped statistics types.
2//!
3//! Mirrors the public surface of `dhat::HeapStats` and
4//! `dhat::AdHocStats` so downstream consumers can swap dhat for
5//! mod-alloc with no field renames.
6
7use std::sync::atomic::{AtomicU64, Ordering};
8
9/// Heap-mode statistics snapshot. Mirrors the shape of
10/// `dhat::HeapStats` field-for-field so consumers migrating from
11/// dhat can drop in the type alias without code edits.
12///
13/// Note the `total_*` fields are `u64` while `curr_*` and `max_*`
14/// are `usize` — this matches dhat-rs exactly. On 32-bit targets
15/// the `u64 -> usize` casts in [`HeapStats::get`] are saturating;
16/// in practice mod-alloc targets 64-bit only (the Tier 2 walker
17/// requires `x86_64` / `aarch64`).
18///
19/// # Example
20///
21/// ```no_run
22/// # #[cfg(feature = "dhat-compat")]
23/// # fn demo() {
24/// use mod_alloc::dhat_compat::{Alloc, HeapStats};
25///
26/// #[global_allocator]
27/// static ALLOC: Alloc = Alloc;
28///
29/// let _v: Vec<u8> = vec![0; 1024];
30/// let stats = HeapStats::get();
31/// assert!(stats.total_bytes >= 1024);
32/// # }
33/// ```
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct HeapStats {
36    /// Total blocks ever allocated (lifetime count).
37    pub total_blocks: u64,
38    /// Total bytes ever allocated (lifetime sum).
39    pub total_bytes: u64,
40    /// Currently-alive block count.
41    pub curr_blocks: usize,
42    /// Currently-resident bytes.
43    pub curr_bytes: usize,
44    /// Peak live block count.
45    pub max_blocks: usize,
46    /// Peak resident bytes.
47    pub max_bytes: usize,
48}
49
50impl HeapStats {
51    /// Snapshot the current heap statistics from the installed
52    /// allocator.
53    ///
54    /// If no [`crate::dhat_compat::Alloc`] (or [`crate::ModAlloc`])
55    /// is installed as `#[global_allocator]` and no allocation has
56    /// occurred yet, all fields are zero.
57    pub fn get() -> Self {
58        let snap = crate::current_snapshot_or_zeros();
59        Self {
60            total_blocks: snap.alloc_count,
61            total_bytes: snap.total_bytes,
62            curr_blocks: snap.live_count as usize,
63            curr_bytes: snap.current_bytes as usize,
64            max_blocks: snap.peak_live_count as usize,
65            max_bytes: snap.peak_bytes as usize,
66        }
67    }
68}
69
70/// Ad-hoc-mode statistics snapshot. Mirrors `dhat::AdHocStats`.
71///
72/// Populated by [`ad_hoc_event`] calls, which are independent of
73/// the heap allocator hot path.
74///
75/// # Example
76///
77/// ```no_run
78/// # #[cfg(feature = "dhat-compat")]
79/// # fn demo() {
80/// use mod_alloc::dhat_compat::{ad_hoc_event, AdHocStats};
81///
82/// ad_hoc_event(42);
83/// let stats = AdHocStats::get();
84/// assert_eq!(stats.total_events, 1);
85/// assert_eq!(stats.total_units, 42);
86/// # }
87/// ```
88#[derive(Clone, Debug, PartialEq, Eq, Default)]
89pub struct AdHocStats {
90    /// Number of [`ad_hoc_event`] calls.
91    pub total_events: u64,
92    /// Sum of all `weight` arguments passed to [`ad_hoc_event`].
93    pub total_units: u64,
94}
95
96impl AdHocStats {
97    /// Snapshot the current ad-hoc statistics.
98    pub fn get() -> Self {
99        Self {
100            total_events: AD_HOC_EVENTS.load(Ordering::Relaxed),
101            total_units: AD_HOC_UNITS.load(Ordering::Relaxed),
102        }
103    }
104}
105
106pub(crate) static AD_HOC_EVENTS: AtomicU64 = AtomicU64::new(0);
107pub(crate) static AD_HOC_UNITS: AtomicU64 = AtomicU64::new(0);
108
109/// Record one ad-hoc event with the given weight.
110///
111/// Two atomic operations per call; no allocation. Safe to call
112/// from any context, including transitively from inside the
113/// allocator hook.
114///
115/// # Example
116///
117/// ```no_run
118/// # #[cfg(feature = "dhat-compat")]
119/// # {
120/// use mod_alloc::dhat_compat::ad_hoc_event;
121///
122/// ad_hoc_event(1);     // one unit of work
123/// ad_hoc_event(1024);  // a chunk of work
124/// # }
125/// ```
126pub fn ad_hoc_event(weight: usize) {
127    AD_HOC_EVENTS.fetch_add(1, Ordering::Relaxed);
128    AD_HOC_UNITS.fetch_add(weight as u64, Ordering::Relaxed);
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn ad_hoc_event_accumulates() {
137        let before = AdHocStats::get();
138        ad_hoc_event(10);
139        ad_hoc_event(5);
140        let after = AdHocStats::get();
141        assert_eq!(after.total_events - before.total_events, 2);
142        assert_eq!(after.total_units - before.total_units, 15);
143    }
144}