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}