1 | //! A library to quickly get the live/total/max counts of allocated instances. |
2 | //! |
3 | //! # Example |
4 | //! |
5 | //! ``` |
6 | //! # if cfg!(not(feature = "enable" )) { return; } |
7 | //! |
8 | //! #[derive(Default)] |
9 | //! struct Widget { |
10 | //! _c: countme::Count<Self>, |
11 | //! } |
12 | //! |
13 | //! countme::enable(true); |
14 | //! |
15 | //! let w1 = Widget::default(); |
16 | //! let w2 = Widget::default(); |
17 | //! let w3 = Widget::default(); |
18 | //! drop(w1); |
19 | //! |
20 | //! let counts = countme::get::<Widget>(); |
21 | //! assert_eq!(counts.live, 2); |
22 | //! assert_eq!(counts.max_live, 3); |
23 | //! assert_eq!(counts.total, 3); |
24 | //! |
25 | //! eprintln!("{}" , countme::get_all()); |
26 | //! ``` |
27 | //! |
28 | //! # Configuration |
29 | //! |
30 | //! By default, the implementation compiles to no-ops. Therefore it is possible |
31 | //! to include `Count` fields into library types. |
32 | //! |
33 | //! The `enable` cargo feature ungates the counting code. The feature can be |
34 | //! enabled anywhere in the crate graph. |
35 | //! |
36 | //! At run-time, the counters are controlled with [`enable`] function. Counting |
37 | //! is enabled by default if `print_at_exit` feature is enabled. Otherwise |
38 | //! counting is disabled by default. Call `enable(true)` early in `main` to enable: |
39 | //! |
40 | //! ```rust |
41 | //! fn main() { |
42 | //! countme::enable(std::env::var("COUNTME" ).is_ok()); |
43 | //! } |
44 | //! ``` |
45 | //! |
46 | //! The code is optimized for the case where counting is not enabled at runtime |
47 | //! (counting is a relaxed load and a branch to a function call). |
48 | //! |
49 | //! The `print_at_exit` Cargo feature uses `atexit` call to print final counts |
50 | //! before the program exits (it also enables counting at runtime). Use it only |
51 | //! when you can't modify the main to print counts -- `atexit` is not guaranteed |
52 | //! to work with rust's runtime. |
53 | #[cfg (feature = "enable" )] |
54 | mod imp; |
55 | |
56 | use std::{fmt, marker::PhantomData}; |
57 | |
58 | #[derive (Debug, Clone, PartialEq, Eq, Default)] |
59 | #[non_exhaustive ] |
60 | pub struct Counts { |
61 | /// The total number of tokens created. |
62 | pub total: usize, |
63 | /// The historical maximum of the `live` count. |
64 | pub max_live: usize, |
65 | /// The number of tokens which were created, but are not destroyed yet. |
66 | pub live: usize, |
67 | } |
68 | |
69 | /// Store this inside your struct as `_c: countme::Count<Self>`. |
70 | #[derive (Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
71 | pub struct Count<T: 'static> { |
72 | ghost: PhantomData<fn(T)>, |
73 | } |
74 | |
75 | impl<T: 'static> Default for Count<T> { |
76 | #[inline ] |
77 | fn default() -> Self { |
78 | Self::new() |
79 | } |
80 | } |
81 | |
82 | impl<T: 'static> Clone for Count<T> { |
83 | #[inline ] |
84 | fn clone(&self) -> Self { |
85 | Self::new() |
86 | } |
87 | } |
88 | |
89 | impl<T: 'static> Count<T> { |
90 | /// Create new `Count`, incrementing the corresponding count. |
91 | #[inline ] |
92 | pub fn new() -> Count<T> { |
93 | #[cfg (feature = "enable" )] |
94 | imp::inc::<T>(); |
95 | Count { ghost: PhantomData } |
96 | } |
97 | } |
98 | |
99 | impl<T: 'static> Drop for Count<T> { |
100 | #[inline ] |
101 | fn drop(&mut self) { |
102 | #[cfg (feature = "enable" )] |
103 | imp::dec::<T>(); |
104 | } |
105 | } |
106 | |
107 | /// Enable or disable counting at runtime. |
108 | /// |
109 | /// Counting is enabled by default if `print_at_exit` feature is enabled. |
110 | /// Otherwise counting is disabled by default. |
111 | /// |
112 | /// If neither `enable` nor `print_at_exit` features are enabled, then this function is noop. |
113 | pub fn enable(_yes: bool) { |
114 | #[cfg (feature = "enable" )] |
115 | imp::enable(_yes); |
116 | } |
117 | |
118 | /// Returns the counts for the `T` type. |
119 | #[inline ] |
120 | pub fn get<T: 'static>() -> Counts { |
121 | #[cfg (feature = "enable" )] |
122 | { |
123 | return imp::get::<T>(); |
124 | } |
125 | #[cfg (not(feature = "enable" ))] |
126 | { |
127 | return Counts::default(); |
128 | } |
129 | } |
130 | |
131 | /// Returns a collection of counts for all types. |
132 | pub fn get_all() -> AllCounts { |
133 | #[cfg (feature = "enable" )] |
134 | { |
135 | return imp::get_all(); |
136 | } |
137 | #[cfg (not(feature = "enable" ))] |
138 | { |
139 | return AllCounts::default(); |
140 | } |
141 | } |
142 | |
143 | /// A collection of counts for all types. |
144 | #[derive (Default, Clone, Debug)] |
145 | pub struct AllCounts { |
146 | entries: Vec<(&'static str, Counts)>, |
147 | } |
148 | |
149 | impl fmt::Display for AllCounts { |
150 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
151 | fn sep(mut n: usize) -> String { |
152 | let mut groups = Vec::new(); |
153 | while n >= 1000 { |
154 | groups.push(format!(" {:03}" , n % 1000)); |
155 | n /= 1000; |
156 | } |
157 | groups.push(n.to_string()); |
158 | groups.reverse(); |
159 | groups.join("_" ) |
160 | } |
161 | |
162 | if self.entries.is_empty() { |
163 | return if cfg!(feature = "enable" ) { |
164 | writeln!(f, "all counts are zero" ) |
165 | } else { |
166 | writeln!(f, "counts are disabled" ) |
167 | }; |
168 | } |
169 | let max_width = |
170 | self.entries.iter().map(|(name, _count)| name.chars().count()).max().unwrap_or(0); |
171 | for (name, counts) in &self.entries { |
172 | writeln!( |
173 | f, |
174 | " {:<max_width$} {:>12} {:>12} {:>12}" , |
175 | name, |
176 | sep(counts.total), |
177 | sep(counts.max_live), |
178 | sep(counts.live), |
179 | max_width = max_width |
180 | )?; |
181 | } |
182 | writeln!( |
183 | f, |
184 | " {:<max_width$} {:>12} {:>12} {:>12}" , |
185 | "" , |
186 | "total" , |
187 | "max_live" , |
188 | "live" , |
189 | max_width = max_width |
190 | )?; |
191 | Ok(()) |
192 | } |
193 | } |
194 | |