1use anyhow::{Context, Result};
5use attribution_processing::digest::{BucketDefinition, Digest};
6use attribution_processing::summary::MemorySummary;
7use attribution_processing::{
8 AttributionData, AttributionDataProvider, ProcessedAttributionData, attribute_vmos,
9};
10use fuchsia_async::WakeupTime;
11use fuchsia_inspect::{ArrayProperty, Node, StringProperty};
12use fuchsia_inspect_contrib::nodes::BoundedListNode;
13use fuchsia_trace::{category_enabled, duration};
14use fuchsia_trace_observer::TraceObserver;
15use futures::stream::FusedStream;
16use futures::{FutureExt, Stream, StreamExt, TryFutureExt, join, select, try_join};
17use humansize::{BINARY, FormatSizeOptions, format_size};
18use stalls::StallProvider;
19use stalls::refaults::PageFaultProvider;
20use std::future::Future;
21use traces::{CATEGORY_MEMORY_CAPTURE, CATEGORY_MEMORY_SUPPRESS_PERIODIC};
22
23use fidl_fuchsia_kernel as fkernel;
24use fidl_fuchsia_metrics as fmetrics;
25
26const PERIODIC_MONITORING_INTERVAL: zx::MonotonicDuration = zx::MonotonicDuration::from_minutes(5);
27
28async fn wait_while_periodic_suppressed<Fut, T, E>(
32 mut is_suppressed: impl FnMut() -> bool,
33 mut on_state_changed: impl FnMut() -> Fut,
34) where
35 Fut: Future<Output = Result<T, E>>,
36{
37 while is_suppressed() {
38 if on_state_changed().await.is_err() {
39 break;
40 }
41 }
42}
43
44pub struct ManualMonitoringData {
47 pub timestamp: zx::BootInstant,
48 pub attribution_data: AttributionData,
49 pub kmem_stats: fkernel::MemoryStats,
50 pub kmem_stats_compression: fkernel::MemoryStatsCompression,
51 pub done_tx: Option<futures::channel::oneshot::Sender<()>>,
52}
53
54pub enum ManualMonitoringEvent {
56 Started,
59 Completed(Box<ManualMonitoringData>),
62}
63
64async fn wait_for_next_collection(
70 manual_events: &mut (impl FusedStream<Item = ManualMonitoringEvent> + Unpin),
71 last_timestamp: zx::BootInstant,
72) -> Option<Box<ManualMonitoringData>> {
73 let mut timer = std::pin::pin!(PERIODIC_MONITORING_INTERVAL.into_timer().fuse());
74 loop {
75 select! {
76 _ = timer => return None,
77 first_event = manual_events.select_next_some() => {
78 timer.set(PERIODIC_MONITORING_INTERVAL.into_timer().fuse());
79 let mut latest_completed: Option<Box<ManualMonitoringData>> = None;
80 let mut next_event = Some(first_event);
81 while let Some(event) = next_event {
82 if let ManualMonitoringEvent::Completed(data) = event {
83 let is_newer_than_last = data.timestamp > last_timestamp;
84 let is_newer_than_candidate = latest_completed
85 .as_ref()
86 .is_none_or(|prev| data.timestamp >= prev.timestamp);
87 if is_newer_than_last && is_newer_than_candidate {
88 latest_completed = Some(data);
89 }
90 }
91 next_event = if manual_events.is_terminated() {
92 None
93 } else {
94 manual_events.next().now_or_never().flatten()
95 };
96 }
97 if latest_completed.is_some() {
98 return latest_completed;
99 }
100 }
101 }
102 }
103}
104
105pub async fn periodic_monitoring(
112 kernel_stats_proxy: fkernel::StatsProxy,
113 attribution_data_service: &impl AttributionDataProvider,
114 stall_provider: &impl StallProvider,
115 page_fault_provider: &impl PageFaultProvider,
116 metric_event_logger: &fmetrics::MetricEventLoggerProxy,
117 bucket_definitions: &[BucketDefinition],
118 inspect_root: Node,
119 manual_events: impl Stream<Item = ManualMonitoringEvent> + Unpin,
120) -> Result<()> {
121 let mut _current: Option<StringProperty> = None; let mut bucket_list_node = std::cell::OnceCell::new();
123 let bucket_names = std::cell::OnceCell::new();
124 let bucket_codes = cobalt::prepare_bucket_codes(bucket_definitions);
125 let trace_observer = TraceObserver::new();
126 let mut manual_events = manual_events.fuse();
127 let mut manual_collection: Option<Box<ManualMonitoringData>> = None;
128 loop {
129 if manual_collection.is_none() {
130 wait_while_periodic_suppressed(
133 || category_enabled(CATEGORY_MEMORY_SUPPRESS_PERIODIC),
134 || trace_observer.on_state_changed(),
135 )
136 .await;
137 }
138 let mut manual_done_tx = None;
139 let timestamp = {
140 duration!(CATEGORY_MEMORY_CAPTURE, c"periodic_monitoring");
141 let (timestamp, kmem_stats, kmem_stats_compression, raw_attribution_data) =
142 match manual_collection.take() {
143 None => {
144 let timestamp = zx::BootInstant::get();
145 let (kmem_stats, kmem_stats_compression) = try_join!(
147 kernel_stats_proxy.get_memory_stats().map_err(anyhow::Error::from),
148 kernel_stats_proxy
149 .get_memory_stats_compression()
150 .map_err(anyhow::Error::from)
151 )
152 .with_context(|| "Failed to get kernel memory stats")?;
153 let raw_attribution_data =
155 attribution_data_service.get_attribution_data()?;
156 (timestamp, kmem_stats, kmem_stats_compression, raw_attribution_data)
157 }
158 Some(manual) => {
159 let ManualMonitoringData {
160 timestamp,
161 attribution_data,
162 kmem_stats,
163 kmem_stats_compression,
164 done_tx,
165 } = *manual;
166 manual_done_tx = done_tx;
167 (timestamp, kmem_stats, kmem_stats_compression, attribution_data)
168 }
169 };
170 let attribution_data = attribute_vmos(raw_attribution_data);
171 let digest = Digest::compute(
172 &attribution_data,
173 &kmem_stats,
174 &kmem_stats_compression,
175 bucket_definitions,
176 false,
177 )?;
178 drop(_current.take());
179 _current = Some(update_inspect_summary(
180 attribution_data,
181 timestamp,
182 &kmem_stats,
183 &inspect_root,
184 ));
185 cobalt::upload_metrics(
186 timestamp,
187 &kmem_stats,
188 metric_event_logger,
189 &digest,
190 &bucket_codes,
191 )
192 .await?;
193 {
194 let _ = bucket_names.get_or_init(|| {
196 let bucket_names =
198 inspect_root.create_string_array("buckets", digest.buckets.len());
199 for (i, attribution_processing::digest::Bucket { name, .. }) in
200 digest.buckets.iter().enumerate()
201 {
202 bucket_names.set(i, name);
203 }
204 bucket_names
205 });
206 }
207 update_inspect_history(
208 timestamp,
209 &digest,
210 stall_provider,
211 page_fault_provider,
212 &mut bucket_list_node,
213 &inspect_root,
214 )?;
215 timestamp
216 };
217 let (_, next) = join!(
218 fuchsia_async::Task::local(async move {
219 let _ = scudo::mallopt(scudo::M_PURGE_ALL, 0);
220 if let Some(done_tx) = manual_done_tx {
221 let _ = done_tx.send(());
222 }
223 }),
224 wait_for_next_collection(&mut manual_events, timestamp)
225 );
226 manual_collection = next;
227 }
228}
229
230fn update_inspect_summary(
231 attribution_data: ProcessedAttributionData,
232 timestamp: zx::BootInstant,
233 kmem_stats: &fkernel::MemoryStats,
234 inspect_root: &Node,
235) -> StringProperty {
236 inspect_root.create_string(
237 "current",
238 record_summary(attribution_data.summary(), timestamp, &kmem_stats),
239 )
240}
241
242fn update_inspect_history(
244 timestamp: zx::BootInstant,
245 digest: &Digest,
246 stall_provider: &impl StallProvider,
247 page_fault_provider: &impl PageFaultProvider,
248 bucket_list_node: &mut std::cell::OnceCell<BoundedListNode>,
249 inspect_root: &Node,
250) -> Result<()> {
251 let stall_values =
252 stall_provider.get_stall_info().with_context(|| "Unable to retrieve stall information")?;
253 let page_faults = page_fault_provider.get_fault_count();
254 let page_refaults = page_fault_provider.get_refault_count();
255 let _ = bucket_list_node
257 .get_or_init(|| BoundedListNode::new(inspect_root.create_child("measurements"), 100));
258 bucket_list_node.get_mut().unwrap().add_entry(|n| {
259 n.record_int("timestamp", timestamp.into_nanos());
260 {
261 let committed_sizes = n.create_uint_array("bucket_sizes", digest.buckets.len());
262 let populated_sizes =
263 n.create_uint_array("bucket_sizes_populated", digest.buckets.len());
264 for (i, b) in digest.buckets.iter().enumerate() {
265 committed_sizes.set(i, b.committed_size as u64);
266 populated_sizes.set(i, b.populated_size as u64);
267 }
268 n.record(committed_sizes);
269 n.record(populated_sizes);
270 }
271
272 n.record_child("stalls", |child| {
273 child.record_uint(
274 "some_ms",
275 stall_values.some.as_millis().try_into().unwrap_or(u64::MAX),
276 );
277 child.record_uint(
278 "full_ms",
279 stall_values.full.as_millis().try_into().unwrap_or(u64::MAX),
280 );
281 });
282
283 n.record_child("faults", |child| {
284 child.record_uint("count", page_faults);
285 });
286
287 n.record_child("refaults", |child| {
288 child.record_uint("count", page_refaults);
289 });
290 });
291 Ok(())
292}
293
294fn record_summary(
295 mut summary: MemorySummary,
296 timestamp: zx::Instant<zx::BootTimeline>,
297 kmem_stats: &fkernel::MemoryStats,
298) -> String {
299 let size_options = FormatSizeOptions::from(BINARY).space_after_value(false);
300 summary.principals.sort_by_key(|p| std::cmp::Reverse(p.populated_private));
301 format!(
302 "Time: {} VMO: {} Free: {}\n{}",
303 timestamp.into_nanos(),
304 kmem_stats
305 .vmo_bytes
306 .and_then(|b| Some(format_size(b, size_options)))
307 .unwrap_or_else(|| "?".to_string()),
308 kmem_stats
309 .free_bytes
310 .and_then(|b| Some(format_size(b, size_options)))
311 .unwrap_or_else(|| "?".to_string()),
312 summary
313 .principals
314 .iter_mut()
315 .filter_map(|principal| {
316 if principal.populated_total == 0 {
317 return None;
318 }
319 let (populated_private, populated_scaled, populated_total) = match (|| {
320 Some((
321 format_size(principal.populated_private, size_options),
322 format_size(principal.populated_scaled as u64, size_options),
323 format_size(principal.populated_total, size_options),
324 ))
325 })(
326 ) {
327 Some(ok) => ok,
328 None => return None,
329 };
330 let mut vmos = principal.vmos.iter().collect::<Vec<_>>();
331 vmos.sort_by_key(|(_, vmo)| {
332 std::cmp::Reverse((vmo.committed_private, vmo.committed_scaled as u64))
333 });
334 let sizes = if populated_total == populated_private {
335 format_args!("{}", populated_total)
336 } else {
337 format_args!("{} {} {}", populated_private, populated_scaled, populated_total)
338 };
339 Some(format!(
340 "{}: {}; {}",
341 principal.name,
342 sizes,
343 vmos.iter()
344 .filter_map(|(name, vmo)| {
345 if vmo.committed_total == 0 {
346 None
347 } else {
348 Some(format!(
349 "{} {} {} {}",
350 name,
351 format_size(vmo.populated_private, size_options),
352 format_size(vmo.populated_scaled as u64, size_options),
353 format_size(vmo.populated_total, size_options)
354 ))
355 }
356 })
357 .collect::<Vec<_>>()
358 .join("; ")
359 ))
360 })
361 .collect::<Vec<_>>()
362 .join("\n")
363 )
364}
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use attribution_processing::{
369 Attribution, AttributionData, GlobalPrincipalIdentifier, Principal, PrincipalDescription,
370 PrincipalType, Resource, ResourceReference, ZXName,
371 };
372 use diagnostics_assertions::{NonZeroIntProperty, assert_data_tree};
373 use std::num::NonZero;
374 use std::time::Duration;
375
376 use fidl_fuchsia_memory_attribution_plugin as fplugin;
377
378 fn get_kernel_stats() -> (fkernel::MemoryStats, fkernel::MemoryStatsCompression) {
379 (
380 fkernel::MemoryStats {
381 total_bytes: Some(1),
382 free_bytes: Some(2),
383 wired_bytes: Some(3),
384 total_heap_bytes: Some(4),
385 free_heap_bytes: Some(5),
386 vmo_bytes: Some(6),
387 mmu_overhead_bytes: Some(7),
388 ipc_bytes: Some(8),
389 other_bytes: Some(9),
390 free_loaned_bytes: Some(10),
391 cache_bytes: Some(11),
392 slab_bytes: Some(12),
393 zram_bytes: Some(13),
394 vmo_reclaim_total_bytes: Some(14),
395 vmo_reclaim_newest_bytes: Some(15),
396 vmo_reclaim_oldest_bytes: Some(16),
397 vmo_reclaim_disabled_bytes: Some(17),
398 vmo_discardable_locked_bytes: Some(18),
399 vmo_discardable_unlocked_bytes: Some(19),
400 ..Default::default()
401 },
402 fkernel::MemoryStatsCompression {
403 uncompressed_storage_bytes: Some(20),
404 compressed_storage_bytes: Some(21),
405 compressed_fragmentation_bytes: Some(22),
406 compression_time: Some(23),
407 decompression_time: Some(24),
408 total_page_compression_attempts: Some(25),
409 failed_page_compression_attempts: Some(26),
410 total_page_decompressions: Some(27),
411 compressed_page_evictions: Some(28),
412 eager_page_compressions: Some(29),
413 memory_pressure_page_compressions: Some(30),
414 critical_memory_page_compressions: Some(31),
415 pages_decompressed_unit_ns: Some(32),
416 pages_decompressed_within_log_time: Some([40, 41, 42, 43, 44, 45, 46, 47]),
417
418 ..Default::default()
419 },
420 )
421 }
422
423 fn get_raw_attribution_data() -> AttributionData {
424 AttributionData {
425 principals_vec: vec![Principal {
426 identifier: GlobalPrincipalIdentifier(NonZero::new(1).unwrap()),
427 description: Some(PrincipalDescription::Component("principal".to_owned())),
428 principal_type: PrincipalType::Runnable,
429 parent: None,
430 }],
431 resources_vec: vec![Resource {
432 koid: 10,
433 name_index: 0,
434 resource_type: fplugin::ResourceType::Vmo(fplugin::Vmo {
435 parent: None,
436 private_committed_bytes: Some(1024),
437 private_populated_bytes: Some(2048),
438 scaled_committed_bytes: Some(1024),
439 scaled_populated_bytes: Some(2048),
440 total_committed_bytes: Some(1024),
441 total_populated_bytes: Some(2048),
442 ..Default::default()
443 }),
444 }],
445 resource_names: vec![ZXName::from_string_lossy("resource")],
446 attributions: vec![Attribution {
447 source: GlobalPrincipalIdentifier(NonZero::new(1).unwrap()),
448 subject: GlobalPrincipalIdentifier(NonZero::new(1).unwrap()),
449 resources: vec![ResourceReference::KernelObject(10)],
450 }],
451 }
452 }
453
454 fn get_attribution_data() -> ProcessedAttributionData {
455 attribute_vmos(get_raw_attribution_data())
456 }
457
458 #[derive(Clone)]
459 struct FakeStallProvider {}
460 impl StallProvider for FakeStallProvider {
461 fn get_stall_info(&self) -> Result<stalls::MemoryStallMetrics, anyhow::Error> {
462 Ok(stalls::MemoryStallMetrics {
463 some: Duration::from_millis(10),
464 full: Duration::from_millis(20),
465 })
466 }
467 }
468
469 #[derive(Clone)]
470 struct FakePageFaultProvider {
471 refault_count: u64,
472 fault_count: u64,
473 }
474
475 impl PageFaultProvider for FakePageFaultProvider {
476 fn get_refault_count(&self) -> u64 {
477 self.refault_count
478 }
479
480 fn get_fault_count(&self) -> u64 {
481 self.fault_count
482 }
483 }
484
485 #[fuchsia::test]
486 async fn test_update_inspect() -> Result<()> {
487 let inspector = fuchsia_inspect::Inspector::default();
488 let digest_node = inspector.root().create_child("logger");
489 let timestamp = zx::BootInstant::get();
490 let attribution_data = get_attribution_data();
491 let (kernel_stats, kernel_stats_compression) = get_kernel_stats();
492 let digest = Digest::compute(
493 &attribution_data,
494 &kernel_stats,
495 &kernel_stats_compression,
496 &vec![],
497 false,
498 )?;
499 let mut bucket_list_node = std::cell::OnceCell::new();
500 let _summary =
502 update_inspect_summary(attribution_data, timestamp, &kernel_stats, &digest_node);
503 update_inspect_history(
504 timestamp,
505 &digest,
506 &FakeStallProvider {},
507 &FakePageFaultProvider { refault_count: 300, fault_count: 400 },
508 &mut bucket_list_node,
509 &digest_node,
510 )?;
511
512 update_inspect_history(
513 timestamp,
514 &digest,
515 &FakeStallProvider {},
516 &FakePageFaultProvider { refault_count: 700, fault_count: 800 },
517 &mut bucket_list_node,
518 &digest_node,
519 )?;
520 assert_data_tree!(inspector, root: {
521 logger: {
522 measurements: {
523 "0": {
525 timestamp: NonZeroIntProperty,
526 bucket_sizes: vec![
527 1024u64, 0u64,
531 54u64, 2u64, 14u64, 15u64, 16u64, 18u64, 19u64, 21u64, 6u64, ],
541 bucket_sizes_populated: vec![
542 2048u64, 0u64,
546 54u64, 2u64, 14u64, 15u64, 16u64, 18u64, 19u64, 21u64, 6u64, ],
556
557 stalls: {
558 some_ms: 10u64,
559 full_ms: 20u64,
560 },
561 faults: {
562 count: 400u64,
563 },
564 refaults: {
565 count: 300u64,
566 },
567 },
568 "1": {
570 timestamp: NonZeroIntProperty,
571 bucket_sizes: vec![
572 1024u64, 0u64,
576 54u64, 2u64, 14u64, 15u64, 16u64, 18u64, 19u64, 21u64, 6u64, ],
586 bucket_sizes_populated: vec![
587 2048u64, 0u64,
591 54u64, 2u64, 14u64, 15u64, 16u64, 18u64, 19u64, 21u64, 6u64, ],
601 stalls: {
602 some_ms: 10u64,
603 full_ms: 20u64,
604 },
605 faults: {
606 count: 800u64,
607 },
608 refaults: {
609 count: 700u64,
610 },
611 },
612 },
613 current: regex::Regex::new(r"^Time: \d+ VMO: 6B Free: 2B\nprincipal: 2KiB; resource 2KiB 2KiB 2KiB")?,
614 },
615 });
616 Ok(())
617 }
618
619 #[test]
620 fn test_wait_while_periodic_suppressed_defers_while_category_enabled() {
621 use std::cell::{Cell, RefCell};
622
623 let mut exec = fuchsia_async::TestExecutor::new();
624 let is_suppressed = Cell::new(true);
625 let (tx, rx) = futures::channel::mpsc::unbounded::<Result<(), zx::Status>>();
626 let rx = RefCell::new(rx);
627
628 {
629 let mut wait_fut = std::pin::pin!(wait_while_periodic_suppressed(
630 || is_suppressed.get(),
631 || async { rx.borrow_mut().next().await.unwrap_or(Err(zx::Status::PEER_CLOSED)) },
632 ));
633
634 assert!(exec.run_until_stalled(&mut wait_fut).is_pending());
636
637 tx.unbounded_send(Ok(())).unwrap();
640 assert!(exec.run_until_stalled(&mut wait_fut).is_pending());
641
642 is_suppressed.set(false);
645 tx.unbounded_send(Ok(())).unwrap();
646 assert!(exec.run_until_stalled(&mut wait_fut).is_ready());
647 }
648
649 is_suppressed.set(true);
652 let mut wait_fut = std::pin::pin!(wait_while_periodic_suppressed(
653 || is_suppressed.get(),
654 || async { rx.borrow_mut().next().await.unwrap_or(Err(zx::Status::PEER_CLOSED)) },
655 ));
656 assert!(exec.run_until_stalled(&mut wait_fut).is_pending());
657 tx.unbounded_send(Err(zx::Status::BAD_STATE)).unwrap();
658 assert!(exec.run_until_stalled(&mut wait_fut).is_ready());
659 }
660
661 struct CountingAttributionDataProvider {
662 call_count: std::sync::atomic::AtomicUsize,
663 }
664
665 impl AttributionDataProvider for CountingAttributionDataProvider {
666 fn get_attribution_data(&self) -> Result<AttributionData, anyhow::Error> {
667 self.call_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
668 Ok(get_raw_attribution_data())
669 }
670 }
671
672 #[test]
673 fn test_periodic_monitoring_reuses_manual_and_resets_timer() -> Result<()> {
674 let mut exec = fuchsia_async::TestExecutor::new_with_fake_time();
675 let start = exec.now();
676
677 let (kernel_stats_proxy, mut kernel_stats_stream) =
678 fidl::endpoints::create_proxy_and_stream::<fkernel::StatsMarker>();
679 let _kernel_stats_task = fuchsia_async::Task::local(async move {
680 while let Some(Ok(request)) = kernel_stats_stream.next().await {
681 let (mem_stats, comp_stats) = get_kernel_stats();
682 match request {
683 fkernel::StatsRequest::GetMemoryStats { responder } => {
684 let _ = responder.send(&mem_stats);
685 }
686 fkernel::StatsRequest::GetMemoryStatsCompression { responder } => {
687 let _ = responder.send(&comp_stats);
688 }
689 _ => unreachable!(),
690 }
691 }
692 });
693
694 let (metric_event_logger, mut metric_event_stream) =
695 fidl::endpoints::create_proxy_and_stream::<fmetrics::MetricEventLoggerMarker>();
696 let _metrics_task = fuchsia_async::Task::local(async move {
697 while let Some(Ok(request)) = metric_event_stream.next().await {
698 if let fmetrics::MetricEventLoggerRequest::LogMetricEvents { responder, .. } =
699 request
700 {
701 let _ = responder.send(Ok(()));
702 }
703 }
704 });
705
706 let inspector = fuchsia_inspect::Inspector::default();
707 let attribution_provider =
708 CountingAttributionDataProvider { call_count: std::sync::atomic::AtomicUsize::new(0) };
709 let stall_provider = FakeStallProvider {};
710 let page_fault_provider = FakePageFaultProvider { refault_count: 300, fault_count: 400 };
711 let (tx, rx) = futures::channel::mpsc::unbounded::<ManualMonitoringEvent>();
712
713 let mut monitoring_fut = std::pin::pin!(periodic_monitoring(
714 kernel_stats_proxy,
715 &attribution_provider,
716 &stall_provider,
717 &page_fault_provider,
718 &metric_event_logger,
719 &[],
720 inspector.root().create_child("logger"),
721 rx,
722 ));
723
724 assert!(exec.run_until_stalled(&mut monitoring_fut).is_pending());
726 assert_eq!(attribution_provider.call_count.load(std::sync::atomic::Ordering::SeqCst), 1);
727
728 exec.set_fake_time(start + fuchsia_async::MonotonicDuration::from_minutes(4));
730 assert!(exec.run_until_stalled(&mut monitoring_fut).is_pending());
731 assert_eq!(attribution_provider.call_count.load(std::sync::atomic::Ordering::SeqCst), 1);
732
733 tx.unbounded_send(ManualMonitoringEvent::Started).unwrap();
735 assert!(exec.run_until_stalled(&mut monitoring_fut).is_pending());
736
737 exec.set_fake_time(start + fuchsia_async::MonotonicDuration::from_minutes(6));
739 assert!(exec.run_until_stalled(&mut monitoring_fut).is_pending());
740 assert_eq!(attribution_provider.call_count.load(std::sync::atomic::Ordering::SeqCst), 1);
741
742 exec.set_fake_time(start + fuchsia_async::MonotonicDuration::from_minutes(9));
744 assert!(exec.run_until_stalled(&mut monitoring_fut).is_pending());
745 assert_eq!(attribution_provider.call_count.load(std::sync::atomic::Ordering::SeqCst), 2);
746
747 exec.set_fake_time(start + fuchsia_async::MonotonicDuration::from_minutes(10));
751 let (kmem_stats, kmem_stats_compression) = get_kernel_stats();
752 let (stale_done_tx, mut stale_done_rx) = futures::channel::oneshot::channel();
753 tx.unbounded_send(ManualMonitoringEvent::Completed(Box::new(ManualMonitoringData {
754 timestamp: zx::BootInstant::from_nanos(1),
755 attribution_data: get_raw_attribution_data(),
756 kmem_stats: kmem_stats.clone(),
757 kmem_stats_compression: kmem_stats_compression.clone(),
758 done_tx: Some(stale_done_tx),
759 })))
760 .unwrap();
761
762 let base_ts = zx::BootInstant::get() + zx::BootDuration::from_seconds(10);
763 let newer_ts = base_ts + zx::BootDuration::from_seconds(1);
764 let (superseded_done_tx, mut superseded_done_rx) = futures::channel::oneshot::channel();
765 tx.unbounded_send(ManualMonitoringEvent::Completed(Box::new(ManualMonitoringData {
766 timestamp: base_ts,
767 attribution_data: get_raw_attribution_data(),
768 kmem_stats: kmem_stats.clone(),
769 kmem_stats_compression: kmem_stats_compression.clone(),
770 done_tx: Some(superseded_done_tx),
771 })))
772 .unwrap();
773
774 let (done_tx, mut done_rx) = futures::channel::oneshot::channel();
775 tx.unbounded_send(ManualMonitoringEvent::Completed(Box::new(ManualMonitoringData {
776 timestamp: newer_ts,
777 attribution_data: get_raw_attribution_data(),
778 kmem_stats,
779 kmem_stats_compression,
780 done_tx: Some(done_tx),
781 })))
782 .unwrap();
783
784 assert!(exec.run_until_stalled(&mut monitoring_fut).is_pending());
785 assert_eq!(attribution_provider.call_count.load(std::sync::atomic::Ordering::SeqCst), 2);
787 assert!(matches!(done_rx.try_recv(), Ok(Some(()))));
789 assert!(stale_done_rx.try_recv().is_err());
790 assert!(superseded_done_rx.try_recv().is_err());
791
792 exec.set_fake_time(start + fuchsia_async::MonotonicDuration::from_minutes(14));
795 assert!(exec.run_until_stalled(&mut monitoring_fut).is_pending());
796 assert_eq!(attribution_provider.call_count.load(std::sync::atomic::Ordering::SeqCst), 2);
797
798 exec.set_fake_time(start + fuchsia_async::MonotonicDuration::from_minutes(15));
799 assert!(exec.run_until_stalled(&mut monitoring_fut).is_pending());
800 assert_eq!(attribution_provider.call_count.load(std::sync::atomic::Ordering::SeqCst), 3);
801
802 drop(monitoring_fut);
803
804 let mut assert_fut = std::pin::pin!(async {
807 assert_data_tree!(inspector, root: {
808 logger: contains {
809 buckets: vec![
810 "Undigested",
811 "Orphaned",
812 "Kernel",
813 "Free",
814 "[Addl]PagerTotal",
815 "[Addl]PagerNewest",
816 "[Addl]PagerOldest",
817 "[Addl]DiscardableLocked",
818 "[Addl]DiscardableUnlocked",
819 "[Addl]ZramCompressedBytes",
820 "[Addl]PopulatedAnonymousBytes",
821 ],
822 current: regex::Regex::new(r"^Time: \d+ VMO: 6B Free: 2B\nprincipal: 2KiB; resource 2KiB 2KiB 2KiB").unwrap(),
823 measurements: {
824 "0": contains {
825 timestamp: NonZeroIntProperty,
826 },
827 "1": contains {
828 timestamp: NonZeroIntProperty,
829 },
830 "2": contains {
831 timestamp: newer_ts.into_nanos(),
832 },
833 "3": contains {
834 timestamp: NonZeroIntProperty,
835 },
836 },
837 },
838 });
839 });
840 assert!(exec.run_until_stalled(&mut assert_fut).is_ready());
841 Ok(())
842 }
843}