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