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