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;
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    refault_provider: impl RefaultProvider,
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, refault_provider, inspector);
33    Ok(inspect_controller)
34}
35
36fn build_inspect_tree(
37    kernel_stats_proxy: fkernel::StatsProxy,
38    stall_provider: impl StallProvider,
39    refault_provider: impl RefaultProvider,
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        inspector.root().record_lazy_child("refaults", move || {
175            let refault_provider = refault_provider.clone();
176            async move {
177                let refault_info = refault_provider.get_count();
178                let inspector = Inspector::default();
179                inspector.root().record_uint("count", refault_info);
180                Ok(inspector)
181            }
182            .boxed()
183        });
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    #![allow(clippy::unwrap_used)]
190    use super::*;
191    use diagnostics_assertions::assert_data_tree;
192    use fuchsia_async as fasync;
193    use futures::TryStreamExt;
194    use stalls::MemoryStallMetrics;
195    use std::time::Duration;
196
197    async fn serve_kernel_stats(
198        mut request_stream: fkernel::StatsRequestStream,
199    ) -> Result<(), fidl::Error> {
200        while let Some(request) = request_stream.try_next().await? {
201            match request {
202                fkernel::StatsRequest::GetMemoryStats { responder } => {
203                    responder
204                        .send(&fkernel::MemoryStats {
205                            total_bytes: Some(1),
206                            free_bytes: Some(2),
207                            wired_bytes: Some(3),
208                            total_heap_bytes: Some(4),
209                            free_heap_bytes: Some(5),
210                            vmo_bytes: Some(6),
211                            mmu_overhead_bytes: Some(7),
212                            ipc_bytes: Some(8),
213                            other_bytes: Some(9),
214                            free_loaned_bytes: Some(10),
215                            cache_bytes: Some(11),
216                            slab_bytes: Some(12),
217                            zram_bytes: Some(13),
218                            vmo_reclaim_total_bytes: Some(14),
219                            vmo_reclaim_newest_bytes: Some(15),
220                            vmo_reclaim_oldest_bytes: Some(16),
221                            vmo_reclaim_disabled_bytes: Some(17),
222                            vmo_discardable_locked_bytes: Some(18),
223                            vmo_discardable_unlocked_bytes: Some(19),
224                            ..Default::default()
225                        })
226                        .unwrap();
227                }
228                fkernel::StatsRequest::GetMemoryStatsExtended { responder: _ } => {
229                    unimplemented!("Deprecated call, should not be used")
230                }
231                fkernel::StatsRequest::GetMemoryStatsCompression { responder } => {
232                    responder
233                        .send(&fkernel::MemoryStatsCompression {
234                            uncompressed_storage_bytes: Some(20),
235                            compressed_storage_bytes: Some(21),
236                            compressed_fragmentation_bytes: Some(22),
237                            compression_time: Some(23),
238                            decompression_time: Some(24),
239                            total_page_compression_attempts: Some(25),
240                            failed_page_compression_attempts: Some(26),
241                            total_page_decompressions: Some(27),
242                            compressed_page_evictions: Some(28),
243                            eager_page_compressions: Some(29),
244                            memory_pressure_page_compressions: Some(30),
245                            critical_memory_page_compressions: Some(31),
246                            pages_decompressed_unit_ns: Some(32),
247                            pages_decompressed_within_log_time: Some([
248                                40, 41, 42, 43, 44, 45, 46, 47,
249                            ]),
250                            ..Default::default()
251                        })
252                        .unwrap();
253                }
254                fkernel::StatsRequest::GetCpuStats { responder: _ } => unimplemented!(),
255                fkernel::StatsRequest::GetCpuLoad { duration: _, responder: _ } => unimplemented!(),
256            }
257        }
258        Ok(())
259    }
260
261    #[derive(Clone)]
262    struct FakeStallProvider {}
263    impl StallProvider for FakeStallProvider {
264        fn get_stall_info(&self) -> Result<MemoryStallMetrics, anyhow::Error> {
265            Ok(MemoryStallMetrics {
266                some: Duration::from_millis(10),
267                full: Duration::from_millis(20),
268            })
269        }
270    }
271
272    #[derive(Clone)]
273    struct FakeRefaultProvider {}
274    impl RefaultProvider for FakeRefaultProvider {
275        fn get_count(&self) -> u64 {
276            1000
277        }
278    }
279
280    #[test]
281    fn test_build_inspect_tree() {
282        let mut exec = fasync::TestExecutor::new();
283        let (stats_provider, stats_request_stream) =
284            fidl::endpoints::create_proxy_and_stream::<fkernel::StatsMarker>();
285
286        fasync::Task::spawn(async move {
287            serve_kernel_stats(stats_request_stream).await.unwrap();
288        })
289        .detach();
290
291        let inspector = fuchsia_inspect::Inspector::default();
292
293        build_inspect_tree(
294            stats_provider,
295            FakeStallProvider {},
296            FakeRefaultProvider {},
297            &inspector,
298        );
299
300        let output = exec
301            .run_singlethreaded(fuchsia_inspect::reader::read(&inspector))
302            .expect("got hierarchy");
303
304        assert_data_tree!(@executor exec, output, root: {
305            kmem_stats: {
306                total_bytes: 1u64,
307                free_bytes: 2u64,
308                wired_bytes: 3u64,
309                total_heap_bytes: 4u64,
310                free_heap_bytes: 5u64,
311                vmo_bytes: 6u64,
312                mmu_overhead_bytes: 7u64,
313                ipc_bytes: 8u64,
314                other_bytes: 9u64,
315                free_loaned_bytes: 10u64,
316                cache_bytes: 11u64,
317                slab_bytes: 12u64,
318                zram_bytes: 13u64,
319                vmo_reclaim_total_bytes: 14u64,
320                vmo_reclaim_newest_bytes: 15u64,
321                vmo_reclaim_oldest_bytes: 16u64,
322                vmo_reclaim_disabled_bytes: 17u64,
323                vmo_discardable_locked_bytes: 18u64,
324                vmo_discardable_unlocked_bytes: 19u64
325            },
326            kmem_stats_compression: {
327                uncompressed_storage_bytes: 20u64,
328                compressed_storage_bytes: 21u64,
329                compressed_fragmentation_bytes: 22u64,
330                compression_time: 23i64,
331                decompression_time: 24i64,
332                total_page_compression_attempts: 25u64,
333                failed_page_compression_attempts: 26u64,
334                total_page_decompressions: 27u64,
335                compressed_page_evictions: 28u64,
336                eager_page_compressions: 29u64,
337                memory_pressure_page_compressions: 30u64,
338                critical_memory_page_compressions: 31u64,
339                pages_decompressed_unit_ns: 32u64,
340                pages_decompressed_within_log_time: vec![
341                    40u64, 41u64, 42u64, 43u64, 44u64, 45u64, 46u64, 47u64,
342                ]
343            },
344            stalls: {
345                some_ms: 10u64,
346                full_ms: 20u64,
347            },
348            refaults: {
349                count: 1000u64,
350            },
351        });
352    }
353}