1use super::private::InspectTypeInternal;
37use super::{Inspector, Node, Property, UintProperty};
38use futures::FutureExt;
39
40const FUCHSIA_INSPECT_STATS: &str = "fuchsia.inspect.Stats";
42const CURRENT_SIZE_KEY: &str = "current_size";
43const MAXIMUM_SIZE_KEY: &str = "maximum_size";
44const UTILIZATION_PER_TEN_K_KEY: &str = "utilization_per_ten_k";
45const TOTAL_DYNAMIC_CHILDREN_KEY: &str = "total_dynamic_children";
46const ALLOCATED_BLOCKS_KEY: &str = "allocated_blocks";
47const DEALLOCATED_BLOCKS_KEY: &str = "deallocated_blocks";
48const FAILED_ALLOCATIONS_KEY: &str = "failed_allocations";
49const PEAK_BYTES_REQUESTED_KEY: &str = "peak_bytes_requested";
50
51pub trait InspectorExt {
54 fn record_lazy_stats(&self);
56}
57
58impl InspectorExt for Inspector {
59 fn record_lazy_stats(&self) {
60 let weak_root_node = self.root().clone_weak();
61 self.root().record_lazy_child(FUCHSIA_INSPECT_STATS, move || {
62 let weak_root_node = weak_root_node.clone_weak();
63 async move {
64 let local_inspector = Inspector::default();
65 let stats =
66 StatsNode::from_nodes(weak_root_node, local_inspector.root().clone_weak());
67 stats.record_data_to(local_inspector.root());
68 Ok(local_inspector)
69 }
70 .boxed()
71 });
72 }
73}
74
75#[derive(Default)]
77pub struct StatsNode {
78 root_of_instrumented_inspector: Node,
79 stats_root: Node,
80 current_size: UintProperty,
81 maximum_size: UintProperty,
82 utilization_per_ten_k: UintProperty,
83 total_dynamic_children: UintProperty,
84 allocated_blocks: UintProperty,
85 deallocated_blocks: UintProperty,
86 failed_allocations: UintProperty,
87 peak_bytes_requested: UintProperty,
88}
89
90impl StatsNode {
91 pub fn new(inspector: &Inspector) -> Self {
95 let stats_root = inspector.root().create_child(FUCHSIA_INSPECT_STATS);
96 Self::from_nodes(inspector.root().clone_weak(), stats_root)
97 }
98
99 pub fn update(&self) {
101 if let Some(stats) = self
102 .root_of_instrumented_inspector
103 .state()
104 .and_then(|outer_state| outer_state.try_lock().ok().map(|state| state.stats()))
105 {
106 self.current_size.atomic_update(|_| {
109 self.current_size.set(stats.current_size as u64);
110 self.maximum_size.set(stats.maximum_size as u64);
111 self.utilization_per_ten_k.set(
112 ((stats.current_size as f64) / (stats.maximum_size as f64) * 10000.0) as u64,
113 );
114 self.total_dynamic_children.set(stats.total_dynamic_children as u64);
115 self.allocated_blocks.set(stats.allocated_blocks as u64);
116 self.deallocated_blocks.set(stats.deallocated_blocks as u64);
117 self.failed_allocations.set(stats.failed_allocations as u64);
118 self.peak_bytes_requested.set(stats.peak_bytes_requested as u64);
119 });
120 }
121 }
122
123 pub fn record_data_to(self, lifetime: &Node) {
125 lifetime.record(self.stats_root);
126 lifetime.record(self.current_size);
127 lifetime.record(self.maximum_size);
128 lifetime.record(self.utilization_per_ten_k);
129 lifetime.record(self.total_dynamic_children);
130 lifetime.record(self.allocated_blocks);
131 lifetime.record(self.deallocated_blocks);
132 lifetime.record(self.failed_allocations);
133 lifetime.record(self.peak_bytes_requested);
134 }
135
136 fn from_nodes(root_of_instrumented_inspector: Node, stats_root: Node) -> Self {
138 if let Some(stats) = root_of_instrumented_inspector
139 .state()
140 .and_then(|outer_state| outer_state.try_lock().ok().map(|state| state.stats()))
141 {
142 let mut n = stats_root.atomic_update(|stats_root| StatsNode {
143 root_of_instrumented_inspector,
144 current_size: stats_root.create_uint(CURRENT_SIZE_KEY, stats.current_size as u64),
145 maximum_size: stats_root.create_uint(MAXIMUM_SIZE_KEY, stats.maximum_size as u64),
146 utilization_per_ten_k: stats_root.create_uint(
147 UTILIZATION_PER_TEN_K_KEY,
148 ((stats.current_size as f64) / (stats.maximum_size as f64) * 10000.0) as u64,
149 ),
150 total_dynamic_children: stats_root
151 .create_uint(TOTAL_DYNAMIC_CHILDREN_KEY, stats.total_dynamic_children as u64),
152 allocated_blocks: stats_root
153 .create_uint(ALLOCATED_BLOCKS_KEY, stats.allocated_blocks as u64),
154 deallocated_blocks: stats_root
155 .create_uint(DEALLOCATED_BLOCKS_KEY, stats.deallocated_blocks as u64),
156 failed_allocations: stats_root
157 .create_uint(FAILED_ALLOCATIONS_KEY, stats.failed_allocations as u64),
158 peak_bytes_requested: stats_root
159 .create_uint(PEAK_BYTES_REQUESTED_KEY, stats.peak_bytes_requested as u64),
160 ..StatsNode::default()
161 });
162 n.stats_root = stats_root;
163 n
164 } else {
165 StatsNode { root_of_instrumented_inspector, ..StatsNode::default() }
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use diagnostics_assertions::{assert_data_tree, assert_json_diff};
174 use inspect_format::constants;
175
176 #[fuchsia::test]
177 async fn inspect_stats() {
178 let inspector = Inspector::default();
179 inspector.record_lazy_stats();
180
181 assert_data_tree!(inspector, root: {
182 "fuchsia.inspect.Stats": {
183 current_size: 4096u64,
184 maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
185 utilization_per_ten_k: 156u64,
186 total_dynamic_children: 1u64, allocated_blocks: 4u64,
188 deallocated_blocks: 0u64,
189 failed_allocations: 0u64,
190 peak_bytes_requested: 176u64,
191 },
192 });
193
194 inspector.root().record_lazy_child("foo", || {
195 async move {
196 let inspector = Inspector::default();
197 inspector.root().record_uint("a", 1);
198 Ok(inspector)
199 }
200 .boxed()
201 });
202 assert_data_tree!(inspector, root: {
203 foo: {
204 a: 1u64,
205 },
206 "fuchsia.inspect.Stats": {
207 current_size: 4096u64,
208 maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
209 utilization_per_ten_k: 156u64,
210 total_dynamic_children: 2u64,
211 allocated_blocks: 7u64,
212 deallocated_blocks: 0u64,
213 failed_allocations: 0u64,
214 peak_bytes_requested: 240u64,
215 },
216 });
217
218 for i in 0..100 {
219 inspector.root().record_string(format!("testing-{i}"), "testing".repeat(i + 1));
220 }
221
222 {
223 let _ = inspector.root().create_int("drop", 1);
224 }
225
226 assert_data_tree!(inspector, root: contains {
227 "fuchsia.inspect.Stats": {
228 current_size: 61440u64,
229 maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
230 utilization_per_ten_k: 2343u64,
231 total_dynamic_children: 2u64,
232 allocated_blocks: 309u64,
233 deallocated_blocks: 2u64,
236 failed_allocations: 0u64,
237 peak_bytes_requested: 59856u64,
238 }
239 });
240
241 for i in 101..220 {
242 inspector.root().record_string(format!("testing-{i}"), "testing".repeat(i + 1));
243 }
244
245 assert_data_tree!(inspector, root: contains {
246 "fuchsia.inspect.Stats": {
247 current_size: 262144u64,
248 maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
249 utilization_per_ten_k: 10000u64,
250 total_dynamic_children: 2u64,
251 allocated_blocks: 664u64,
252 deallocated_blocks: 6u64,
253 failed_allocations: 2u64,
254 peak_bytes_requested: 265168u64,
255 }
256 });
257 }
258
259 #[fuchsia::test]
260 async fn stats_are_updated() {
261 let inspector = Inspector::default();
262 let stats = super::StatsNode::new(&inspector);
263 assert_json_diff!(inspector, root: {
264 "fuchsia.inspect.Stats": {
265 current_size: 4096u64,
266 maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
267 utilization_per_ten_k: 156u64,
268 total_dynamic_children: 0u64,
269 allocated_blocks: 3u64,
270 deallocated_blocks: 0u64,
271 failed_allocations: 0u64,
272 peak_bytes_requested: 112u64,
273 }
274 });
275
276 inspector.root().record_int("abc", 5);
277
278 assert_json_diff!(inspector, root: {
280 abc: 5i64,
281 "fuchsia.inspect.Stats": {
282 current_size: 4096u64,
283 maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
284 utilization_per_ten_k: 156u64,
285 total_dynamic_children: 0u64,
286 allocated_blocks: 3u64,
287 deallocated_blocks: 0u64,
288 failed_allocations: 0u64,
289 peak_bytes_requested: 112u64,
290 }
291 });
292
293 stats.update();
294
295 assert_json_diff!(inspector, root: {
296 abc: 5i64,
297 "fuchsia.inspect.Stats": {
298 current_size: 4096u64,
299 maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
300 utilization_per_ten_k: 156u64,
301 total_dynamic_children: 0u64,
302 allocated_blocks: 21u64,
303 deallocated_blocks: 0u64,
304 failed_allocations: 0u64,
305 peak_bytes_requested: 592u64,
306 }
307 });
308 }
309
310 #[fuchsia::test]
311 async fn recorded_stats_are_persisted() {
312 let inspector = Inspector::default();
313 {
314 let stats = super::StatsNode::new(&inspector);
315 stats.record_data_to(inspector.root());
316 }
317
318 assert_data_tree!(inspector, root: {
319 "fuchsia.inspect.Stats": {
320 current_size: 4096u64,
321 maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
322 utilization_per_ten_k: 156u64,
323 total_dynamic_children: 0u64,
324 allocated_blocks: 3u64,
325 deallocated_blocks: 0u64,
326 failed_allocations: 0u64,
327 peak_bytes_requested: 112u64,
328 }
329 });
330 }
331}