Skip to main content

inspect_nodes/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#![warn(clippy::unwrap_used)]
5
6use anyhow::{Result, anyhow};
7use fidl_fuchsia_kernel as fkernel;
8use fuchsia_inspect::{ArrayProperty, Inspector};
9use futures::FutureExt;
10use log::debug;
11use stalls::StallProvider;
12use stalls::refaults::PageFaultProvider;
13use std::future::Future;
14
15/// Serve the inspect tree, and return an object holding the server's resources.
16pub fn serve(
17    kernel_stats_proxy: fkernel::StatsProxy,
18    stall_provider: impl StallProvider,
19    page_fault_provider: impl PageFaultProvider,
20) -> Result<impl Future<Output = ()>> {
21    debug!("Start serving inspect tree.");
22
23    // This creates the root of an Inspect tree
24    // The Inspector is a singleton that you can access from any scope
25    let inspector = fuchsia_inspect::component::inspector();
26
27    // This serves the Inspect tree, converting failures into fatal errors
28    let inspect_controller =
29        inspect_runtime::publish(inspector, inspect_runtime::PublishOptions::default())
30            .ok_or_else(|| anyhow!("Failed to serve server handling `fuchsia.inspect.Tree`"))?;
31
32    build_inspect_tree(kernel_stats_proxy, stall_provider, page_fault_provider, inspector);
33    Ok(inspect_controller)
34}
35
36fn build_inspect_tree(
37    kernel_stats_proxy: fkernel::StatsProxy,
38    stall_provider: impl StallProvider,
39    page_fault_provider: impl PageFaultProvider,
40    inspector: &Inspector,
41) {
42    // Lazy evaluation is unregistered when the `LazyNode` is dropped.
43    {
44        let kernel_stats_proxy = kernel_stats_proxy.clone();
45        inspector.root().record_lazy_child("kmem_stats", move || {
46            let kernel_stats_proxy = kernel_stats_proxy.clone();
47            async move {
48                let inspector = Inspector::default();
49                let root = inspector.root();
50                let mem_stats = kernel_stats_proxy.get_memory_stats().await?;
51                mem_stats.total_bytes.map(|v| root.record_uint("total_bytes", v));
52                mem_stats.free_bytes.map(|v| root.record_uint("free_bytes", v));
53                mem_stats.free_loaned_bytes.map(|v| root.record_uint("free_loaned_bytes", v));
54                mem_stats.wired_bytes.map(|v| root.record_uint("wired_bytes", v));
55                mem_stats.total_heap_bytes.map(|v| root.record_uint("total_heap_bytes", v));
56                mem_stats.free_heap_bytes.map(|v| root.record_uint("free_heap_bytes", v));
57                mem_stats.vmo_bytes.map(|v| root.record_uint("vmo_bytes", v));
58                mem_stats.mmu_overhead_bytes.map(|v| root.record_uint("mmu_overhead_bytes", v));
59                mem_stats.ipc_bytes.map(|v| root.record_uint("ipc_bytes", v));
60                mem_stats.cache_bytes.map(|v| root.record_uint("cache_bytes", v));
61                mem_stats.slab_bytes.map(|v| root.record_uint("slab_bytes", v));
62                mem_stats.zram_bytes.map(|v| root.record_uint("zram_bytes", v));
63                mem_stats.other_bytes.map(|v| root.record_uint("other_bytes", v));
64                mem_stats
65                    .vmo_reclaim_total_bytes
66                    .map(|v| root.record_uint("vmo_reclaim_total_bytes", v));
67                mem_stats
68                    .vmo_reclaim_newest_bytes
69                    .map(|v| root.record_uint("vmo_reclaim_newest_bytes", v));
70                mem_stats
71                    .vmo_reclaim_oldest_bytes
72                    .map(|v| root.record_uint("vmo_reclaim_oldest_bytes", v));
73                mem_stats
74                    .vmo_reclaim_disabled_bytes
75                    .map(|v| root.record_uint("vmo_reclaim_disabled_bytes", v));
76                mem_stats
77                    .vmo_discardable_locked_bytes
78                    .map(|v| root.record_uint("vmo_discardable_locked_bytes", v));
79                mem_stats
80                    .vmo_discardable_unlocked_bytes
81                    .map(|v| root.record_uint("vmo_discardable_unlocked_bytes", v));
82                Ok(inspector)
83            }
84            .boxed()
85        })
86    };
87
88    {
89        inspector.root().record_lazy_child("kmem_stats_compression", move || {
90            let kernel_stats_proxy = kernel_stats_proxy.clone();
91            async move {
92                let inspector = Inspector::default();
93                let cmp_stats = kernel_stats_proxy.get_memory_stats_compression().await?;
94                cmp_stats
95                    .uncompressed_storage_bytes
96                    .map(|v| inspector.root().record_uint("uncompressed_storage_bytes", v));
97                cmp_stats
98                    .compressed_storage_bytes
99                    .map(|v| inspector.root().record_uint("compressed_storage_bytes", v));
100                cmp_stats
101                    .compressed_fragmentation_bytes
102                    .map(|v| inspector.root().record_uint("compressed_fragmentation_bytes", v));
103                cmp_stats
104                    .compression_time
105                    .map(|v| inspector.root().record_int("compression_time", v));
106                cmp_stats
107                    .decompression_time
108                    .map(|v| inspector.root().record_int("decompression_time", v));
109                cmp_stats
110                    .total_page_compression_attempts
111                    .map(|v| inspector.root().record_uint("total_page_compression_attempts", v));
112                cmp_stats
113                    .failed_page_compression_attempts
114                    .map(|v| inspector.root().record_uint("failed_page_compression_attempts", v));
115                cmp_stats
116                    .total_page_decompressions
117                    .map(|v| inspector.root().record_uint("total_page_decompressions", v));
118                cmp_stats
119                    .compressed_page_evictions
120                    .map(|v| inspector.root().record_uint("compressed_page_evictions", v));
121                cmp_stats
122                    .eager_page_compressions
123                    .map(|v| inspector.root().record_uint("eager_page_compressions", v));
124                cmp_stats
125                    .memory_pressure_page_compressions
126                    .map(|v| inspector.root().record_uint("memory_pressure_page_compressions", v));
127                cmp_stats
128                    .critical_memory_page_compressions
129                    .map(|v| inspector.root().record_uint("critical_memory_page_compressions", v));
130                cmp_stats
131                    .pages_decompressed_unit_ns
132                    .map(|v| inspector.root().record_uint("pages_decompressed_unit_ns", v));
133                cmp_stats.pages_decompressed_within_log_time.map(|v| {
134                    let array =
135                        inspector.root().create_uint_array("pages_decompressed_within_log_time", 8);
136                    // Using constant strings saves allocations.
137                    array.set(0, v[0]);
138                    array.set(1, v[1]);
139                    array.set(2, v[2]);
140                    array.set(3, v[3]);
141                    array.set(4, v[4]);
142                    array.set(5, v[5]);
143                    array.set(6, v[6]);
144                    array.set(7, v[7]);
145                    inspector.root().record(array);
146                });
147                Ok(inspector)
148            }
149            .boxed()
150        });
151    }
152
153    {
154        inspector.root().record_lazy_child("stalls", move || {
155            let stall_provider = stall_provider.clone();
156            async move {
157                let stall_info = stall_provider.get_stall_info()?;
158                let inspector = Inspector::default();
159                inspector.root().record_uint(
160                    "some_ms",
161                    stall_info.some.as_millis().try_into().unwrap_or(u64::MAX),
162                );
163                inspector.root().record_uint(
164                    "full_ms",
165                    stall_info.full.as_millis().try_into().unwrap_or(u64::MAX),
166                );
167                Ok(inspector)
168            }
169            .boxed()
170        });
171    }
172
173    {
174        let page_fault_provider = page_fault_provider.clone();
175        inspector.root().record_lazy_child("faults", move || {
176            let page_fault_provider = page_fault_provider.clone();
177            async move {
178                let fault_info = page_fault_provider.get_fault_count();
179                let inspector = Inspector::default();
180                inspector.root().record_uint("count", fault_info);
181                Ok(inspector)
182            }
183            .boxed()
184        });
185    }
186
187    {
188        inspector.root().record_lazy_child("refaults", move || {
189            let page_fault_provider = page_fault_provider.clone();
190            async move {
191                let refault_info = page_fault_provider.get_refault_count();
192                let inspector = Inspector::default();
193                inspector.root().record_uint("count", refault_info);
194                Ok(inspector)
195            }
196            .boxed()
197        });
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    #![allow(clippy::unwrap_used)]
204    use super::*;
205    use diagnostics_assertions::assert_data_tree;
206    use fuchsia_async as fasync;
207    use futures::TryStreamExt;
208    use stalls::MemoryStallMetrics;
209    use std::time::Duration;
210
211    async fn serve_kernel_stats(
212        mut request_stream: fkernel::StatsRequestStream,
213    ) -> Result<(), fidl::Error> {
214        while let Some(request) = request_stream.try_next().await? {
215            match request {
216                fkernel::StatsRequest::GetMemoryStats { responder } => {
217                    responder
218                        .send(&fkernel::MemoryStats {
219                            total_bytes: Some(1),
220                            free_bytes: Some(2),
221                            wired_bytes: Some(3),
222                            total_heap_bytes: Some(4),
223                            free_heap_bytes: Some(5),
224                            vmo_bytes: Some(6),
225                            mmu_overhead_bytes: Some(7),
226                            ipc_bytes: Some(8),
227                            other_bytes: Some(9),
228                            free_loaned_bytes: Some(10),
229                            cache_bytes: Some(11),
230                            slab_bytes: Some(12),
231                            zram_bytes: Some(13),
232                            vmo_reclaim_total_bytes: Some(14),
233                            vmo_reclaim_newest_bytes: Some(15),
234                            vmo_reclaim_oldest_bytes: Some(16),
235                            vmo_reclaim_disabled_bytes: Some(17),
236                            vmo_discardable_locked_bytes: Some(18),
237                            vmo_discardable_unlocked_bytes: Some(19),
238                            ..Default::default()
239                        })
240                        .unwrap();
241                }
242                fkernel::StatsRequest::GetMemoryStatsExtended { responder: _ } => {
243                    unimplemented!("Deprecated call, should not be used")
244                }
245                fkernel::StatsRequest::GetMemoryStatsCompression { responder } => {
246                    responder
247                        .send(&fkernel::MemoryStatsCompression {
248                            uncompressed_storage_bytes: Some(20),
249                            compressed_storage_bytes: Some(21),
250                            compressed_fragmentation_bytes: Some(22),
251                            compression_time: Some(23),
252                            decompression_time: Some(24),
253                            total_page_compression_attempts: Some(25),
254                            failed_page_compression_attempts: Some(26),
255                            total_page_decompressions: Some(27),
256                            compressed_page_evictions: Some(28),
257                            eager_page_compressions: Some(29),
258                            memory_pressure_page_compressions: Some(30),
259                            critical_memory_page_compressions: Some(31),
260                            pages_decompressed_unit_ns: Some(32),
261                            pages_decompressed_within_log_time: Some([
262                                40, 41, 42, 43, 44, 45, 46, 47,
263                            ]),
264                            ..Default::default()
265                        })
266                        .unwrap();
267                }
268                fkernel::StatsRequest::GetCpuStats { responder: _ } => unimplemented!(),
269                fkernel::StatsRequest::GetCpuLoad { duration: _, responder: _ } => unimplemented!(),
270            }
271        }
272        Ok(())
273    }
274
275    #[derive(Clone)]
276    struct FakeStallProvider {}
277    impl StallProvider for FakeStallProvider {
278        fn get_stall_info(&self) -> Result<MemoryStallMetrics, anyhow::Error> {
279            Ok(MemoryStallMetrics {
280                some: Duration::from_millis(10),
281                full: Duration::from_millis(20),
282            })
283        }
284    }
285
286    #[derive(Clone)]
287    struct FakePageFaultProvider {}
288    impl PageFaultProvider for FakePageFaultProvider {
289        fn get_refault_count(&self) -> u64 {
290            1000
291        }
292
293        fn get_fault_count(&self) -> u64 {
294            2000
295        }
296    }
297
298    #[test]
299    fn test_build_inspect_tree() {
300        let mut exec = fasync::TestExecutor::new();
301        let (stats_provider, stats_request_stream) =
302            fidl::endpoints::create_proxy_and_stream::<fkernel::StatsMarker>();
303
304        fasync::Task::spawn(async move {
305            serve_kernel_stats(stats_request_stream).await.unwrap();
306        })
307        .detach();
308
309        let inspector = fuchsia_inspect::Inspector::default();
310
311        build_inspect_tree(
312            stats_provider,
313            FakeStallProvider {},
314            FakePageFaultProvider {},
315            &inspector,
316        );
317
318        let output = exec
319            .run_singlethreaded(fuchsia_inspect::reader::read(&inspector))
320            .expect("got hierarchy");
321
322        assert_data_tree!(@executor exec, output, root: {
323            kmem_stats: {
324                total_bytes: 1u64,
325                free_bytes: 2u64,
326                wired_bytes: 3u64,
327                total_heap_bytes: 4u64,
328                free_heap_bytes: 5u64,
329                vmo_bytes: 6u64,
330                mmu_overhead_bytes: 7u64,
331                ipc_bytes: 8u64,
332                other_bytes: 9u64,
333                free_loaned_bytes: 10u64,
334                cache_bytes: 11u64,
335                slab_bytes: 12u64,
336                zram_bytes: 13u64,
337                vmo_reclaim_total_bytes: 14u64,
338                vmo_reclaim_newest_bytes: 15u64,
339                vmo_reclaim_oldest_bytes: 16u64,
340                vmo_reclaim_disabled_bytes: 17u64,
341                vmo_discardable_locked_bytes: 18u64,
342                vmo_discardable_unlocked_bytes: 19u64
343            },
344            kmem_stats_compression: {
345                uncompressed_storage_bytes: 20u64,
346                compressed_storage_bytes: 21u64,
347                compressed_fragmentation_bytes: 22u64,
348                compression_time: 23i64,
349                decompression_time: 24i64,
350                total_page_compression_attempts: 25u64,
351                failed_page_compression_attempts: 26u64,
352                total_page_decompressions: 27u64,
353                compressed_page_evictions: 28u64,
354                eager_page_compressions: 29u64,
355                memory_pressure_page_compressions: 30u64,
356                critical_memory_page_compressions: 31u64,
357                pages_decompressed_unit_ns: 32u64,
358                pages_decompressed_within_log_time: vec![
359                    40u64, 41u64, 42u64, 43u64, 44u64, 45u64, 46u64, 47u64,
360                ]
361            },
362            stalls: {
363                some_ms: 10u64,
364                full_ms: 20u64,
365            },
366            faults: {
367                count: 2000u64,
368            },
369            refaults: {
370                count: 1000u64,
371            },
372        });
373    }
374}