cobalt/
buckets.rs

1// Copyright 2025 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
5use crate::error_from_metrics_error;
6use anyhow::Result;
7use attribution_processing::digest::{BucketDefinition, Digest};
8use attribution_processing::AttributionDataProvider;
9use cobalt_client::traits::{AsEventCode, AsEventCodes};
10use futures::stream::StreamExt;
11use futures::{try_join, TryFutureExt};
12use memory_metrics_registry::cobalt_registry;
13use std::sync::Arc;
14
15use cobalt_registry::MemoryLeakMigratedMetricDimensionTimeSinceBoot as TimeSinceBoot;
16use {fidl_fuchsia_kernel as fkernel, fidl_fuchsia_metrics as fmetrics};
17
18/// Sorted list mapping durations to the largest event that is lower.
19const UPTIME_LEVEL_INDEX: &[(zx::BootDuration, TimeSinceBoot)] = &[
20    (zx::BootDuration::from_minutes(1), TimeSinceBoot::Up),
21    (zx::BootDuration::from_minutes(30), TimeSinceBoot::UpOneMinute),
22    (zx::BootDuration::from_hours(1), TimeSinceBoot::UpThirtyMinutes),
23    (zx::BootDuration::from_hours(6), TimeSinceBoot::UpOneHour),
24    (zx::BootDuration::from_hours(12), TimeSinceBoot::UpSixHours),
25    (zx::BootDuration::from_hours(24), TimeSinceBoot::UpTwelveHours),
26    (zx::BootDuration::from_hours(48), TimeSinceBoot::UpOneDay),
27    (zx::BootDuration::from_hours(72), TimeSinceBoot::UpTwoDays),
28    (zx::BootDuration::from_hours(144), TimeSinceBoot::UpThreeDays),
29];
30
31fn bucket_name_to_dimension(
32    name: &str,
33) -> Option<cobalt_registry::MemoryMigratedMetricDimensionBucket> {
34    match name {
35        "TotalBytes" => Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::TotalBytes),
36        "Free" => Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::Free),
37        "Kernel" => Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::Kernel),
38        "Orphaned" => Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::Orphaned),
39        "Undigested" => Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::Undigested),
40        "[Addl]PagerTotal" => {
41            Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::__Addl_PagerTotal)
42        }
43        "[Addl]PagerNewest" => {
44            Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::__Addl_PagerNewest)
45        }
46        "[Addl]PagerOldest" => {
47            Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::__Addl_PagerOldest)
48        }
49        "[Addl]DiscardableLocked" => {
50            Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::__Addl_DiscardableLocked)
51        }
52        "[Addl]DiscardableUnlocked" => {
53            Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::__Addl_DiscardableUnlocked)
54        }
55        "[Addl]ZramCompressedBytes" => {
56            Some(cobalt_registry::MemoryMigratedMetricDimensionBucket::__Addl_ZramCompressedBytes)
57        }
58        _ => None,
59    }
60}
61
62/// Convert an instant to the code corresponding to the largest uptime that is smaller.
63fn get_uptime_event_code(capture_time: zx::BootInstant) -> TimeSinceBoot {
64    let uptime = zx::Duration::from_nanos(capture_time.into_nanos());
65    UPTIME_LEVEL_INDEX
66        .into_iter()
67        .find(|&&(time, _)| uptime < time)
68        .map(|(_, code)| *code)
69        .unwrap_or(TimeSinceBoot::UpSixDays)
70}
71
72fn kmem_events(kmem_stats: &fkernel::MemoryStats) -> impl Iterator<Item = fmetrics::MetricEvent> {
73    use cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown as Breakdown;
74    let make_event = |code: Breakdown, value| {
75        Some(fmetrics::MetricEvent {
76            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
77            event_codes: vec![code.as_event_code()],
78            payload: fmetrics::MetricEventPayload::IntegerValue(value? as i64),
79        })
80    };
81    vec![
82        make_event(Breakdown::TotalBytes, kmem_stats.total_bytes),
83        make_event(
84            Breakdown::UsedBytes,
85            (|| Some((kmem_stats.total_bytes? as i64 - kmem_stats.free_bytes? as i64) as u64))(),
86        ),
87        make_event(Breakdown::FreeBytes, kmem_stats.free_bytes),
88        make_event(Breakdown::VmoBytes, kmem_stats.vmo_bytes),
89        make_event(Breakdown::KernelFreeHeapBytes, kmem_stats.free_heap_bytes),
90        make_event(Breakdown::MmuBytes, kmem_stats.mmu_overhead_bytes),
91        make_event(Breakdown::IpcBytes, kmem_stats.ipc_bytes),
92        make_event(Breakdown::KernelTotalHeapBytes, kmem_stats.total_heap_bytes),
93        make_event(Breakdown::WiredBytes, kmem_stats.wired_bytes),
94        make_event(Breakdown::OtherBytes, kmem_stats.other_bytes),
95    ]
96    .into_iter()
97    .flatten()
98}
99
100fn kmem_events_with_uptime(
101    kmem_stats: &fkernel::MemoryStats,
102    capture_time: zx::BootInstant,
103) -> impl Iterator<Item = fmetrics::MetricEvent> {
104    use cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown as Breakdown;
105    let make_event = |code: Breakdown, value| {
106        Some(fmetrics::MetricEvent {
107            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
108            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
109                general_breakdown: code,
110                time_since_boot: get_uptime_event_code(capture_time),
111            }
112            .as_event_codes(),
113            payload: fmetrics::MetricEventPayload::IntegerValue(value? as i64),
114        })
115    };
116    vec![
117        make_event(Breakdown::TotalBytes, kmem_stats.total_bytes),
118        make_event(
119            Breakdown::UsedBytes,
120            (|| Some((kmem_stats.total_bytes? as i64 - kmem_stats.free_bytes? as i64) as u64))(),
121        ),
122        make_event(Breakdown::FreeBytes, kmem_stats.free_bytes),
123        make_event(Breakdown::VmoBytes, kmem_stats.vmo_bytes),
124        make_event(Breakdown::KernelFreeHeapBytes, kmem_stats.free_heap_bytes),
125        make_event(Breakdown::MmuBytes, kmem_stats.mmu_overhead_bytes),
126        make_event(Breakdown::IpcBytes, kmem_stats.ipc_bytes),
127        make_event(Breakdown::KernelTotalHeapBytes, kmem_stats.total_heap_bytes),
128        make_event(Breakdown::WiredBytes, kmem_stats.wired_bytes),
129        make_event(Breakdown::OtherBytes, kmem_stats.other_bytes),
130    ]
131    .into_iter()
132    .flatten()
133}
134
135fn digest_events(digest: Digest) -> impl Iterator<Item = fmetrics::MetricEvent> {
136    digest.buckets.into_iter().filter_map(|bucket| {
137        Some(fmetrics::MetricEvent {
138            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
139            event_codes: vec![bucket_name_to_dimension(&bucket.name)?.as_event_code()],
140            payload: fmetrics::MetricEventPayload::IntegerValue(bucket.size as i64),
141        })
142    })
143}
144
145/// Periodically upload metrics related to memory usage.
146pub async fn collect_metrics_forever(
147    attribution_data_service: Arc<impl AttributionDataProvider + 'static>,
148    kernel_stats_proxy: fkernel::StatsProxy,
149    metric_event_logger: fmetrics::MetricEventLoggerProxy,
150    bucket_definitions: Arc<[BucketDefinition]>,
151) {
152    let mut timer = fuchsia_async::Interval::new(zx::Duration::from_minutes(5));
153    loop {
154        timer.next().await;
155
156        let result = collect_metrics_once(
157            &*attribution_data_service,
158            &kernel_stats_proxy,
159            &metric_event_logger,
160            &*bucket_definitions,
161        )
162        .await;
163
164        if let Err(e) = result {
165            log::error!("Metrics collection failed: {e}");
166        }
167    }
168}
169
170async fn collect_metrics_once(
171    attribution_data_service: &impl AttributionDataProvider,
172    kernel_stats_proxy: &fkernel::StatsProxy,
173    metric_event_logger: &fmetrics::MetricEventLoggerProxy,
174    bucket_definitions: &[BucketDefinition],
175) -> Result<()> {
176    let timestamp = zx::BootInstant::get();
177    let (kmem_stats, kmem_stats_compression) = try_join!(
178        kernel_stats_proxy.get_memory_stats().map_err(anyhow::Error::from),
179        kernel_stats_proxy.get_memory_stats_compression().map_err(anyhow::Error::from)
180    )?;
181    let digest = Digest::compute(
182        &*attribution_data_service,
183        &kmem_stats,
184        &kmem_stats_compression,
185        &bucket_definitions,
186    )?;
187
188    let events = kmem_events(&kmem_stats)
189        .chain(kmem_events_with_uptime(&kmem_stats, timestamp))
190        .chain(digest_events(digest));
191    metric_event_logger
192        .log_metric_events(&events.collect::<Vec<fmetrics::MetricEvent>>())
193        .await?
194        .map_err(error_from_metrics_error)?;
195    Ok(())
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201    use anyhow::anyhow;
202    use attribution_processing::testing::FakeAttributionDataProvider;
203    use attribution_processing::{
204        Attribution, AttributionData, Principal, PrincipalDescription, PrincipalIdentifier,
205        PrincipalType, Resource, ResourceReference, ZXName,
206    };
207    use futures::task::Poll;
208    use futures::TryStreamExt;
209    use std::time::Duration;
210    use {fidl_fuchsia_memory_attribution_plugin as fplugin, fuchsia_async as fasync};
211
212    fn get_attribution_data_provider() -> Arc<impl AttributionDataProvider + 'static> {
213        let attribution_data = AttributionData {
214            principals_vec: vec![Principal {
215                identifier: PrincipalIdentifier(1),
216                description: PrincipalDescription::Component("principal".to_owned()),
217                principal_type: PrincipalType::Runnable,
218                parent: None,
219            }],
220            resources_vec: vec![Resource {
221                koid: 10,
222                name_index: 0,
223                resource_type: fplugin::ResourceType::Vmo(fplugin::Vmo {
224                    parent: None,
225                    private_committed_bytes: Some(1024),
226                    private_populated_bytes: Some(2048),
227                    scaled_committed_bytes: Some(1024),
228                    scaled_populated_bytes: Some(2048),
229                    total_committed_bytes: Some(1024),
230                    total_populated_bytes: Some(2048),
231                    ..Default::default()
232                }),
233            }],
234            resource_names: vec![ZXName::from_string_lossy("resource")],
235            attributions: vec![Attribution {
236                source: PrincipalIdentifier(1),
237                subject: PrincipalIdentifier(1),
238                resources: vec![ResourceReference::KernelObject(10)],
239            }],
240        };
241        Arc::new(FakeAttributionDataProvider { attribution_data })
242    }
243
244    async fn serve_kernel_stats(
245        mut request_stream: fkernel::StatsRequestStream,
246    ) -> Result<(), fidl::Error> {
247        while let Some(request) = request_stream.try_next().await? {
248            match request {
249                fkernel::StatsRequest::GetMemoryStats { responder } => {
250                    responder
251                        .send(&fkernel::MemoryStats {
252                            total_bytes: Some(1),
253                            free_bytes: Some(2),
254                            wired_bytes: Some(3),
255                            total_heap_bytes: Some(4),
256                            free_heap_bytes: Some(5),
257                            vmo_bytes: Some(6),
258                            mmu_overhead_bytes: Some(7),
259                            ipc_bytes: Some(8),
260                            other_bytes: Some(9),
261                            free_loaned_bytes: Some(10),
262                            cache_bytes: Some(11),
263                            slab_bytes: Some(12),
264                            zram_bytes: Some(13),
265                            vmo_reclaim_total_bytes: Some(14),
266                            vmo_reclaim_newest_bytes: Some(15),
267                            vmo_reclaim_oldest_bytes: Some(16),
268                            vmo_reclaim_disabled_bytes: Some(17),
269                            vmo_discardable_locked_bytes: Some(18),
270                            vmo_discardable_unlocked_bytes: Some(19),
271                            ..Default::default()
272                        })
273                        .unwrap();
274                }
275                fkernel::StatsRequest::GetMemoryStatsExtended { responder: _ } => {
276                    unimplemented!("Deprecated call, should not be used")
277                }
278                fkernel::StatsRequest::GetMemoryStatsCompression { responder } => {
279                    responder
280                        .send(&fkernel::MemoryStatsCompression {
281                            uncompressed_storage_bytes: Some(20),
282                            compressed_storage_bytes: Some(21),
283                            compressed_fragmentation_bytes: Some(22),
284                            compression_time: Some(23),
285                            decompression_time: Some(24),
286                            total_page_compression_attempts: Some(25),
287                            failed_page_compression_attempts: Some(26),
288                            total_page_decompressions: Some(27),
289                            compressed_page_evictions: Some(28),
290                            eager_page_compressions: Some(29),
291                            memory_pressure_page_compressions: Some(30),
292                            critical_memory_page_compressions: Some(31),
293                            pages_decompressed_unit_ns: Some(32),
294                            pages_decompressed_within_log_time: Some([
295                                40, 41, 42, 43, 44, 45, 46, 47,
296                            ]),
297                            ..Default::default()
298                        })
299                        .unwrap();
300                }
301                fkernel::StatsRequest::GetCpuStats { responder: _ } => unimplemented!(),
302                fkernel::StatsRequest::GetCpuLoad { duration: _, responder: _ } => unimplemented!(),
303            }
304        }
305        Ok(())
306    }
307
308    #[test]
309    fn test_periodic_metrics_collection() -> anyhow::Result<()> {
310        // Setup executor.
311        let mut exec = fasync::TestExecutor::new_with_fake_time();
312
313        // Setup mock data providers.
314        let data_provider = get_attribution_data_provider();
315        let (stats_provider, stats_request_stream) =
316            fidl::endpoints::create_proxy_and_stream::<fkernel::StatsMarker>();
317        fasync::Task::spawn(async move {
318            serve_kernel_stats(stats_request_stream).await.unwrap();
319        })
320        .detach();
321        let bucket_definitions = Arc::new([]);
322
323        // Setup test proxy to observe emitted events from the service.
324        let (metric_event_logger, metric_event_request_stream) =
325            fidl::endpoints::create_proxy_and_stream::<fmetrics::MetricEventLoggerMarker>();
326
327        // Service under test.
328        let mut metrics_collector = fuchsia_async::Task::spawn(collect_metrics_forever(
329            data_provider,
330            stats_provider,
331            metric_event_logger,
332            bucket_definitions,
333        ));
334
335        // Give the service the opportunity to run.
336        assert!(
337            exec.run_until_stalled(&mut metrics_collector).is_pending(),
338            "Metrics collection service returned unexpectedly early"
339        );
340
341        // Ensure no metrics has been uploaded yet.
342        let mut metric_event_request_future = metric_event_request_stream.into_future();
343        assert!(
344            exec.run_until_stalled(&mut metric_event_request_future).is_pending(),
345            "Metrics collection service returned unexpectedly early"
346        );
347
348        // Fake the passage of time, so that collect_metrics may do a capture.
349        assert!(
350            exec.run_until_stalled(&mut std::pin::pin!(fasync::TestExecutor::advance_to(
351                exec.now() + Duration::from_secs(5 * 60).into()
352            )))
353            .is_ready(),
354            "Failed to advance time"
355        );
356        let uptime = get_uptime_event_code(zx::BootInstant::get());
357
358        // Ensure we have one and only one event ready for consumption.
359        let Poll::Ready((event, metric_event_request_stream)) =
360            exec.run_until_stalled(&mut metric_event_request_future)
361        else {
362            panic!("Failed to receive metrics")
363        };
364        let event = event.ok_or_else(|| anyhow!("Metrics stream unexpectedly closed"))??;
365        match event {
366            fmetrics::MetricEventLoggerRequest::LogMetricEvents { events, .. } => {
367                // Kernel metrics
368                assert_eq!(
369                    &events[0..10],
370                    vec![
371                        fmetrics::MetricEvent {
372                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
373                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::TotalBytes.as_event_code()],
374                            payload: fmetrics::MetricEventPayload::IntegerValue(1)
375                        },
376                        fmetrics::MetricEvent {
377                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
378                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::UsedBytes.as_event_code()],
379                            payload: fmetrics::MetricEventPayload::IntegerValue(-1)
380                        },
381                        fmetrics::MetricEvent {
382                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
383                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::FreeBytes.as_event_code()],
384                            payload: fmetrics::MetricEventPayload::IntegerValue(2)
385                        },
386                        fmetrics::MetricEvent {
387                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
388                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::VmoBytes.as_event_code()],
389                            payload: fmetrics::MetricEventPayload::IntegerValue(6)
390                        },
391                        fmetrics::MetricEvent {
392                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
393                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::KernelFreeHeapBytes.as_event_code()],
394                            payload: fmetrics::MetricEventPayload::IntegerValue(5)
395                        },
396                        fmetrics::MetricEvent {
397                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
398                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::MmuBytes.as_event_code()],
399                            payload: fmetrics::MetricEventPayload::IntegerValue(7)
400                        },
401                        fmetrics::MetricEvent {
402                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
403                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::IpcBytes.as_event_code()],
404                            payload: fmetrics::MetricEventPayload::IntegerValue(8)
405                        },
406                        fmetrics::MetricEvent {
407                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
408                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::KernelTotalHeapBytes.as_event_code()],
409                            payload: fmetrics::MetricEventPayload::IntegerValue(4)
410                        },
411                        fmetrics::MetricEvent {
412                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
413                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::WiredBytes.as_event_code()],
414                            payload: fmetrics::MetricEventPayload::IntegerValue(3)
415                        },
416                        fmetrics::MetricEvent {
417                            metric_id: cobalt_registry::MEMORY_GENERAL_BREAKDOWN_MIGRATED_METRIC_ID,
418                            event_codes: vec![cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown::OtherBytes.as_event_code()],
419                            payload: fmetrics::MetricEventPayload::IntegerValue(9)
420                        },]);
421                // Kernel metrics with uptime
422                assert_eq!(
423                    &events[10..20],
424                    vec![
425                        fmetrics::MetricEvent {
426                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
427                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
428                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::TotalBytes, time_since_boot: uptime}.as_event_codes(),
429                            payload: fmetrics::MetricEventPayload::IntegerValue(1)
430                        },
431                        fmetrics::MetricEvent {
432                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
433                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
434                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::UsedBytes, time_since_boot:uptime}.as_event_codes(),
435                            payload: fmetrics::MetricEventPayload::IntegerValue(-1)
436                        },
437                        fmetrics::MetricEvent {
438                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
439                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
440                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::FreeBytes, time_since_boot:uptime}.as_event_codes(),                            payload: fmetrics::MetricEventPayload::IntegerValue(2)
441                        },
442                        fmetrics::MetricEvent {
443                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
444                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
445                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::VmoBytes, time_since_boot:uptime}.as_event_codes(),
446                            payload: fmetrics::MetricEventPayload::IntegerValue(6)
447                        },
448                        fmetrics::MetricEvent {
449                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
450                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
451                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::KernelFreeHeapBytes, time_since_boot:uptime}.as_event_codes(),
452                            payload: fmetrics::MetricEventPayload::IntegerValue(5)
453                        },
454                        fmetrics::MetricEvent {
455                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
456                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
457                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::MmuBytes, time_since_boot:uptime}.as_event_codes(),
458                            payload: fmetrics::MetricEventPayload::IntegerValue(7)
459                        },
460                        fmetrics::MetricEvent {
461                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
462                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
463                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::IpcBytes, time_since_boot:uptime}.as_event_codes(),
464                            payload: fmetrics::MetricEventPayload::IntegerValue(8)
465                        },
466                        fmetrics::MetricEvent {
467                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
468                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
469                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::KernelTotalHeapBytes, time_since_boot:uptime}.as_event_codes(),
470                            payload: fmetrics::MetricEventPayload::IntegerValue(4)
471                        },
472                        fmetrics::MetricEvent {
473                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
474                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
475                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::WiredBytes, time_since_boot:uptime}.as_event_codes(),
476                            payload: fmetrics::MetricEventPayload::IntegerValue(3)
477                        },
478                        fmetrics::MetricEvent {
479                            metric_id: cobalt_registry::MEMORY_LEAK_MIGRATED_METRIC_ID,
480                            event_codes: cobalt_registry::MemoryLeakMigratedEventCodes {
481                                general_breakdown: cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown::OtherBytes, time_since_boot:uptime}.as_event_codes(),
482                            payload: fmetrics::MetricEventPayload::IntegerValue(9)
483                        },
484                    ]
485                );
486                // Digest metrics
487                assert_eq!(
488                    &events[20..],
489                    vec![
490                        fmetrics::MetricEvent {
491                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
492                            event_codes: vec![19],
493                            payload: fmetrics::MetricEventPayload::IntegerValue(1024)
494                        },
495                        fmetrics::MetricEvent {
496                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
497                            event_codes: vec![18],
498                            payload: fmetrics::MetricEventPayload::IntegerValue(6)
499                        },
500                        fmetrics::MetricEvent {
501                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
502                            event_codes: vec![17],
503                            payload: fmetrics::MetricEventPayload::IntegerValue(31)
504                        },
505                        fmetrics::MetricEvent {
506                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
507                            event_codes: vec![16],
508                            payload: fmetrics::MetricEventPayload::IntegerValue(2)
509                        },
510                        fmetrics::MetricEvent {
511                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
512                            event_codes: vec![32],
513                            payload: fmetrics::MetricEventPayload::IntegerValue(14)
514                        },
515                        fmetrics::MetricEvent {
516                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
517                            event_codes: vec![33],
518                            payload: fmetrics::MetricEventPayload::IntegerValue(15)
519                        },
520                        fmetrics::MetricEvent {
521                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
522                            event_codes: vec![34],
523                            payload: fmetrics::MetricEventPayload::IntegerValue(16)
524                        },
525                        fmetrics::MetricEvent {
526                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
527                            event_codes: vec![35],
528                            payload: fmetrics::MetricEventPayload::IntegerValue(18)
529                        },
530                        fmetrics::MetricEvent {
531                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
532                            event_codes: vec![36],
533                            payload: fmetrics::MetricEventPayload::IntegerValue(19)
534                        },
535                        fmetrics::MetricEvent {
536                            metric_id: cobalt_registry::MEMORY_MIGRATED_METRIC_ID,
537                            event_codes: vec![76],
538                            payload: fmetrics::MetricEventPayload::IntegerValue(21)
539                        }
540                    ]
541                )
542            }
543            _ => panic!("Unexpected metric event"),
544        }
545
546        assert!(exec
547            .run_until_stalled(&mut metric_event_request_stream.into_future())
548            .is_pending());
549        Ok(())
550    }
551}