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