1use anyhow::{anyhow, Result};
5use attribution_processing::AttributionDataProvider;
6use fuchsia_inspect::{ArrayProperty, Inspector};
7use futures::FutureExt;
8use inspect_runtime::PublishedInspectController;
9use log::debug;
10use stalls::StallProvider;
11use std::sync::Arc;
12use {fidl_fuchsia_kernel as fkernel, fuchsia_inspect as _, inspect_runtime as _};
13
14pub struct ServiceTask {
17 _inspect_controller: PublishedInspectController,
18}
19
20pub fn start_service(
23 attribution_data_service: Arc<impl AttributionDataProvider>,
24 kernel_stats_proxy: fkernel::StatsProxy,
25 stall_provider: Arc<impl StallProvider>,
26) -> Result<ServiceTask> {
27 debug!("Start serving inspect tree.");
28
29 let inspector = fuchsia_inspect::component::inspector();
32
33 let inspect_controller =
35 inspect_runtime::publish(inspector, inspect_runtime::PublishOptions::default())
36 .ok_or_else(|| anyhow!("Failed to serve server handling `fuchsia.inspect.Tree`"))?;
37
38 build_inspect_tree(attribution_data_service, kernel_stats_proxy, stall_provider, inspector);
39
40 Ok(ServiceTask { _inspect_controller: inspect_controller })
41}
42
43fn build_inspect_tree(
44 attribution_data_service: Arc<impl AttributionDataProvider>,
45 kernel_stats_proxy: fkernel::StatsProxy,
46 stall_provider: Arc<impl StallProvider>,
47 inspector: &Inspector,
48) {
49 {
51 let kernel_stats_proxy = kernel_stats_proxy.clone();
52 inspector.root().record_lazy_child("kmem_stats", move || {
53 let kernel_stats_proxy = kernel_stats_proxy.clone();
54 async move {
55 let inspector = Inspector::default();
56 let root = inspector.root();
57 let mem_stats = kernel_stats_proxy.get_memory_stats().await?;
58 mem_stats.total_bytes.map(|v| root.record_uint("total_bytes", v));
59 mem_stats.free_bytes.map(|v| root.record_uint("free_bytes", v));
60 mem_stats.free_loaned_bytes.map(|v| root.record_uint("free_loaned_bytes", v));
61 mem_stats.wired_bytes.map(|v| root.record_uint("wired_bytes", v));
62 mem_stats.total_heap_bytes.map(|v| root.record_uint("total_heap_bytes", v));
63 mem_stats.free_heap_bytes.map(|v| root.record_uint("free_heap_bytes", v));
64 mem_stats.vmo_bytes.map(|v| root.record_uint("vmo_bytes", v));
65 mem_stats.mmu_overhead_bytes.map(|v| root.record_uint("mmu_overhead_bytes", v));
66 mem_stats.ipc_bytes.map(|v| root.record_uint("ipc_bytes", v));
67 mem_stats.cache_bytes.map(|v| root.record_uint("cache_bytes", v));
68 mem_stats.slab_bytes.map(|v| root.record_uint("slab_bytes", v));
69 mem_stats.zram_bytes.map(|v| root.record_uint("zram_bytes", v));
70 mem_stats.other_bytes.map(|v| root.record_uint("other_bytes", v));
71 mem_stats
72 .vmo_reclaim_total_bytes
73 .map(|v| root.record_uint("vmo_reclaim_total_bytes", v));
74 mem_stats
75 .vmo_reclaim_newest_bytes
76 .map(|v| root.record_uint("vmo_reclaim_newest_bytes", v));
77 mem_stats
78 .vmo_reclaim_oldest_bytes
79 .map(|v| root.record_uint("vmo_reclaim_oldest_bytes", v));
80 mem_stats
81 .vmo_reclaim_disabled_bytes
82 .map(|v| root.record_uint("vmo_reclaim_disabled_bytes", v));
83 mem_stats
84 .vmo_discardable_locked_bytes
85 .map(|v| root.record_uint("vmo_discardable_locked_bytes", v));
86 mem_stats
87 .vmo_discardable_unlocked_bytes
88 .map(|v| root.record_uint("vmo_discardable_unlocked_bytes", v));
89 Ok(inspector)
90 }
91 .boxed()
92 })
93 };
94
95 {
96 inspector.root().record_lazy_child("kmem_stats_compression", move || {
97 let kernel_stats_proxy = kernel_stats_proxy.clone();
98 async move {
99 let inspector = Inspector::default();
100 let cmp_stats = kernel_stats_proxy.get_memory_stats_compression().await?;
101 cmp_stats
102 .uncompressed_storage_bytes
103 .map(|v| inspector.root().record_uint("uncompressed_storage_bytes", v));
104 cmp_stats
105 .compressed_storage_bytes
106 .map(|v| inspector.root().record_uint("compressed_storage_bytes", v));
107 cmp_stats
108 .compressed_fragmentation_bytes
109 .map(|v| inspector.root().record_uint("compressed_fragmentation_bytes", v));
110 cmp_stats
111 .compression_time
112 .map(|v| inspector.root().record_int("compression_time", v));
113 cmp_stats
114 .decompression_time
115 .map(|v| inspector.root().record_int("decompression_time", v));
116 cmp_stats
117 .total_page_compression_attempts
118 .map(|v| inspector.root().record_uint("total_page_compression_attempts", v));
119 cmp_stats
120 .failed_page_compression_attempts
121 .map(|v| inspector.root().record_uint("failed_page_compression_attempts", v));
122 cmp_stats
123 .total_page_decompressions
124 .map(|v| inspector.root().record_uint("total_page_decompressions", v));
125 cmp_stats
126 .compressed_page_evictions
127 .map(|v| inspector.root().record_uint("compressed_page_evictions", v));
128 cmp_stats
129 .eager_page_compressions
130 .map(|v| inspector.root().record_uint("eager_page_compressions", v));
131 cmp_stats
132 .memory_pressure_page_compressions
133 .map(|v| inspector.root().record_uint("memory_pressure_page_compressions", v));
134 cmp_stats
135 .critical_memory_page_compressions
136 .map(|v| inspector.root().record_uint("critical_memory_page_compressions", v));
137 cmp_stats
138 .pages_decompressed_unit_ns
139 .map(|v| inspector.root().record_uint("pages_decompressed_unit_ns", v));
140 cmp_stats.pages_decompressed_within_log_time.map(|v| {
141 let array =
142 inspector.root().create_uint_array("pages_decompressed_within_log_time", 8);
143 array.set(0, v[0]);
145 array.set(1, v[1]);
146 array.set(2, v[2]);
147 array.set(3, v[3]);
148 array.set(4, v[4]);
149 array.set(5, v[5]);
150 array.set(6, v[6]);
151 array.set(7, v[7]);
152 inspector.root().record(array);
153 });
154 Ok(inspector)
155 }
156 .boxed()
157 });
158 }
159
160 {
161 inspector.root().record_lazy_child("current", move || {
162 let attribution_data_service = attribution_data_service.clone();
163 async move {
164 let inspector = Inspector::default();
165 let current_attribution_data =
166 attribution_data_service.get_attribution_data().await?;
167 let summary =
168 attribution_processing::attribute_vmos(current_attribution_data).summary();
169
170 summary.principals.into_iter().for_each(|p| {
171 let node = inspector.root().create_child(p.name);
172 node.record_uint("committed_private", p.committed_private);
173 node.record_double("committed_scaled", p.committed_scaled);
174 node.record_uint("committed_total", p.committed_total);
175 node.record_uint("populated_private", p.populated_private);
176 node.record_double("populated_scaled", p.populated_scaled);
177 node.record_uint("populated_total", p.populated_total);
178 inspector.root().record(node);
179 });
180 Ok(inspector)
181 }
182 .boxed()
183 });
184 }
185
186 {
187 inspector.root().record_lazy_child("stalls", move || {
188 let stall_info = stall_provider.get_stall_info().unwrap();
189 let stall_rate_opt = stall_provider.get_stall_rate();
190 async move {
191 let inspector = Inspector::default();
192 inspector.root().record_int("current_some", stall_info.stall_time_some);
193 inspector.root().record_int("current_full", stall_info.stall_time_full);
194 if let Some(stall_rate) = stall_rate_opt {
195 inspector
196 .root()
197 .record_int("rate_interval_s", stall_rate.interval.into_seconds());
198 inspector.root().record_int("rate_some", stall_rate.rate_some);
199 inspector.root().record_int("rate_full", stall_rate.rate_full);
200 }
201 Ok(inspector)
202 }
203 .boxed()
204 });
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use attribution_processing::{
211 Attribution, AttributionData, Principal, PrincipalDescription, PrincipalIdentifier,
212 PrincipalType, Resource, ResourceReference,
213 };
214 use diagnostics_assertions::assert_data_tree;
215 use futures::future::BoxFuture;
216 use futures::TryStreamExt;
217 use {fidl_fuchsia_memory_attribution_plugin as fplugin, fuchsia_async as fasync};
218
219 use super::*;
220
221 struct FakeAttributionDataProvider {}
222
223 impl AttributionDataProvider for FakeAttributionDataProvider {
224 fn get_attribution_data(&self) -> BoxFuture<'_, Result<AttributionData, anyhow::Error>> {
225 async {
226 Ok(AttributionData {
227 principals_vec: vec![Principal {
228 identifier: PrincipalIdentifier(1),
229 description: PrincipalDescription::Component("principal".to_owned()),
230 principal_type: PrincipalType::Runnable,
231 parent: None,
232 }],
233 resources_vec: vec![Resource {
234 koid: 10,
235 name_index: 0,
236 resource_type: fplugin::ResourceType::Vmo(fplugin::Vmo {
237 parent: None,
238 private_committed_bytes: Some(1024),
239 private_populated_bytes: Some(2048),
240 scaled_committed_bytes: Some(1024),
241 scaled_populated_bytes: Some(2048),
242 total_committed_bytes: Some(1024),
243 total_populated_bytes: Some(2048),
244 ..Default::default()
245 }),
246 }],
247 resource_names: vec!["resource".to_owned()],
248 attributions: vec![Attribution {
249 source: PrincipalIdentifier(1),
250 subject: PrincipalIdentifier(1),
251 resources: vec![ResourceReference::KernelObject(10)],
252 }],
253 })
254 }
255 .boxed()
256 }
257 }
258
259 async fn serve_kernel_stats(
260 mut request_stream: fkernel::StatsRequestStream,
261 ) -> Result<(), fidl::Error> {
262 while let Some(request) = request_stream.try_next().await? {
263 match request {
264 fkernel::StatsRequest::GetMemoryStats { responder } => {
265 responder
266 .send(&fkernel::MemoryStats {
267 total_bytes: Some(1),
268 free_bytes: Some(2),
269 wired_bytes: Some(3),
270 total_heap_bytes: Some(4),
271 free_heap_bytes: Some(5),
272 vmo_bytes: Some(6),
273 mmu_overhead_bytes: Some(7),
274 ipc_bytes: Some(8),
275 other_bytes: Some(9),
276 free_loaned_bytes: Some(10),
277 cache_bytes: Some(11),
278 slab_bytes: Some(12),
279 zram_bytes: Some(13),
280 vmo_reclaim_total_bytes: Some(14),
281 vmo_reclaim_newest_bytes: Some(15),
282 vmo_reclaim_oldest_bytes: Some(16),
283 vmo_reclaim_disabled_bytes: Some(17),
284 vmo_discardable_locked_bytes: Some(18),
285 vmo_discardable_unlocked_bytes: Some(19),
286 ..Default::default()
287 })
288 .unwrap();
289 }
290 fkernel::StatsRequest::GetMemoryStatsExtended { responder: _ } => {
291 unimplemented!("Deprecated call, should not be used")
292 }
293 fkernel::StatsRequest::GetMemoryStatsCompression { responder } => {
294 responder
295 .send(&fkernel::MemoryStatsCompression {
296 uncompressed_storage_bytes: Some(20),
297 compressed_storage_bytes: Some(21),
298 compressed_fragmentation_bytes: Some(22),
299 compression_time: Some(23),
300 decompression_time: Some(24),
301 total_page_compression_attempts: Some(25),
302 failed_page_compression_attempts: Some(26),
303 total_page_decompressions: Some(27),
304 compressed_page_evictions: Some(28),
305 eager_page_compressions: Some(29),
306 memory_pressure_page_compressions: Some(30),
307 critical_memory_page_compressions: Some(31),
308 pages_decompressed_unit_ns: Some(32),
309 pages_decompressed_within_log_time: Some([
310 40, 41, 42, 43, 44, 45, 46, 47,
311 ]),
312 ..Default::default()
313 })
314 .unwrap();
315 }
316 fkernel::StatsRequest::GetCpuStats { responder: _ } => unimplemented!(),
317 fkernel::StatsRequest::GetCpuLoad { duration: _, responder: _ } => unimplemented!(),
318 }
319 }
320 Ok(())
321 }
322
323 #[test]
324 fn test_build_inspect_tree() {
325 let mut exec = fasync::TestExecutor::new();
326
327 let data_provider = Arc::new(FakeAttributionDataProvider {});
328
329 let (stats_provider, stats_request_stream) =
330 fidl::endpoints::create_proxy_and_stream::<fkernel::StatsMarker>();
331
332 fasync::Task::spawn(async move {
333 serve_kernel_stats(stats_request_stream).await.unwrap();
334 })
335 .detach();
336
337 let inspector = fuchsia_inspect::Inspector::default();
338
339 struct FakeStallProvider {}
340 impl StallProvider for FakeStallProvider {
341 fn get_stall_info(&self) -> Result<zx::MemoryStall, anyhow::Error> {
342 Ok(zx::MemoryStall { stall_time_some: 10, stall_time_full: 20 })
343 }
344
345 fn get_stall_rate(&self) -> Option<stalls::MemoryStallRate> {
346 Some(stalls::MemoryStallRate {
347 interval: fasync::MonotonicDuration::from_seconds(60),
348 rate_some: 1,
349 rate_full: 2,
350 })
351 }
352 }
353
354 build_inspect_tree(
355 data_provider,
356 stats_provider,
357 Arc::new(FakeStallProvider {}),
358 &inspector,
359 );
360
361 let output = exec
362 .run_singlethreaded(fuchsia_inspect::reader::read(&inspector))
363 .expect("got hierarchy");
364
365 assert_data_tree!(output, root: {
366 current: {
367 principal: {
368 committed_private: 1024u64,
369 committed_scaled: 1024.0,
370 committed_total: 1024u64,
371 populated_private: 2048u64,
372 populated_scaled: 2048.0,
373 populated_total: 2048u64
374 }
375 },
376 kmem_stats: {
377 total_bytes: 1u64,
378 free_bytes: 2u64,
379 wired_bytes: 3u64,
380 total_heap_bytes: 4u64,
381 free_heap_bytes: 5u64,
382 vmo_bytes: 6u64,
383 mmu_overhead_bytes: 7u64,
384 ipc_bytes: 8u64,
385 other_bytes: 9u64,
386 free_loaned_bytes: 10u64,
387 cache_bytes: 11u64,
388 slab_bytes: 12u64,
389 zram_bytes: 13u64,
390 vmo_reclaim_total_bytes: 14u64,
391 vmo_reclaim_newest_bytes: 15u64,
392 vmo_reclaim_oldest_bytes: 16u64,
393 vmo_reclaim_disabled_bytes: 17u64,
394 vmo_discardable_locked_bytes: 18u64,
395 vmo_discardable_unlocked_bytes: 19u64
396 },
397 kmem_stats_compression: {
398 uncompressed_storage_bytes: 20u64,
399 compressed_storage_bytes: 21u64,
400 compressed_fragmentation_bytes: 22u64,
401 compression_time: 23i64,
402 decompression_time: 24i64,
403 total_page_compression_attempts: 25u64,
404 failed_page_compression_attempts: 26u64,
405 total_page_decompressions: 27u64,
406 compressed_page_evictions: 28u64,
407 eager_page_compressions: 29u64,
408 memory_pressure_page_compressions: 30u64,
409 critical_memory_page_compressions: 31u64,
410 pages_decompressed_unit_ns: 32u64,
411 pages_decompressed_within_log_time: vec![
412 40u64, 41u64, 42u64, 43u64, 44u64, 45u64, 46u64, 47u64,
413 ]
414 },
415 stalls: {
416 current_some: 10i64,
417 current_full: 20i64,
418 rate_some: 1i64,
419 rate_full: 2i64,
420 rate_interval_s: 60i64
421 }
422 });
423 }
424}