resource_accounting/
stats.rs1use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering::Relaxed};
2
3pub struct ResourceStats {
5 allocated_bytes: AtomicUsize,
6 allocated_objects: AtomicUsize,
7 deallocated_bytes: AtomicUsize,
8 deallocated_objects: AtomicUsize,
9 cpu_time_nanos: AtomicU64,
10}
11
12impl ResourceStats {
13 pub(super) const fn new() -> Self {
14 Self {
15 allocated_bytes: AtomicUsize::new(0),
16 allocated_objects: AtomicUsize::new(0),
17 deallocated_bytes: AtomicUsize::new(0),
18 deallocated_objects: AtomicUsize::new(0),
19 cpu_time_nanos: AtomicU64::new(0),
20 }
21 }
22
23 pub fn has_allocated(&self) -> bool {
25 self.allocated_bytes.load(Relaxed) > 0
26 }
27
28 #[inline]
29 pub(super) fn track_allocation(&self, size: usize) {
30 self.allocated_bytes.fetch_add(size, Relaxed);
31 self.allocated_objects.fetch_add(1, Relaxed);
32 }
33
34 #[inline]
35 pub(super) fn track_deallocation(&self, size: usize) {
36 self.deallocated_bytes.fetch_add(size, Relaxed);
37 self.deallocated_objects.fetch_add(1, Relaxed);
38 }
39
40 #[inline]
41 pub(super) fn track_cpu_time(&self, nanos: u64) {
42 self.cpu_time_nanos.fetch_add(nanos, Relaxed);
43 }
44
45 pub fn snapshot_delta(&self, previous: &ResourceStatsSnapshot) -> ResourceStatsSnapshot {
54 ResourceStatsSnapshot {
55 allocated_bytes: self.allocated_bytes.load(Relaxed) - previous.allocated_bytes,
56 allocated_objects: self.allocated_objects.load(Relaxed) - previous.allocated_objects,
57 deallocated_bytes: self.deallocated_bytes.load(Relaxed) - previous.deallocated_bytes,
58 deallocated_objects: self.deallocated_objects.load(Relaxed) - previous.deallocated_objects,
59 cpu_time_nanos: self.cpu_time_nanos.load(Relaxed) - previous.cpu_time_nanos,
60 }
61 }
62}
63
64pub struct ResourceStatsSnapshot {
66 pub allocated_bytes: usize,
68
69 pub allocated_objects: usize,
71
72 pub deallocated_bytes: usize,
74
75 pub deallocated_objects: usize,
77
78 pub cpu_time_nanos: u64,
80}
81
82impl ResourceStatsSnapshot {
83 pub const fn empty() -> Self {
85 Self {
86 allocated_bytes: 0,
87 allocated_objects: 0,
88 deallocated_bytes: 0,
89 deallocated_objects: 0,
90 cpu_time_nanos: 0,
91 }
92 }
93
94 pub fn live_bytes(&self) -> usize {
96 self.allocated_bytes - self.deallocated_bytes
97 }
98
99 pub fn live_objects(&self) -> usize {
101 self.allocated_objects - self.deallocated_objects
102 }
103
104 pub fn merge(&mut self, other: &Self) {
109 self.allocated_bytes += other.allocated_bytes;
110 self.allocated_objects += other.allocated_objects;
111 self.deallocated_bytes += other.deallocated_bytes;
112 self.deallocated_objects += other.deallocated_objects;
113 self.cpu_time_nanos += other.cpu_time_nanos;
114 }
115}
116
117#[cfg(target_os = "linux")]
119#[inline]
120pub(crate) fn thread_cpu_time_nanos() -> Option<u64> {
121 let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
129 let ret = unsafe { libc::clock_gettime(libc::CLOCK_THREAD_CPUTIME_ID, &mut ts) };
132 if ret == 0 {
133 Some(ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64)
134 } else {
135 None
136 }
137}
138
139#[cfg(not(target_os = "linux"))]
141#[inline]
142pub(crate) fn thread_cpu_time_nanos() -> Option<u64> {
143 None
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[cfg(target_os = "linux")]
151 #[test]
152 fn thread_cpu_time_returns_some() {
153 let t = thread_cpu_time_nanos();
154 assert!(t.is_some());
155 assert!(t.unwrap() > 0);
156 }
157
158 #[cfg(target_os = "linux")]
159 #[test]
160 fn thread_cpu_time_is_monotonic() {
161 let t1 = thread_cpu_time_nanos().unwrap();
162 let mut sum = 0u64;
164 for i in 0..100_000 {
165 sum = sum.wrapping_add(i);
166 }
167 std::hint::black_box(sum);
168 let t2 = thread_cpu_time_nanos().unwrap();
169 assert!(t2 > t1);
170 }
171
172 #[cfg(not(target_os = "linux"))]
173 #[test]
174 fn thread_cpu_time_returns_none_on_non_linux() {
175 assert!(thread_cpu_time_nanos().is_none());
176 }
177}