1use anyhow::Result;
6use cobalt_client::traits::AsEventCode;
7use fidl_fuchsia_metrics as fmetrics;
8use futures::StreamExt;
9use memory_metrics_registry::cobalt_registry;
10use stalls::refaults::PageFaultProvider;
11use stalls::{MemoryStallMetrics, StallProvider};
12use zx::MonotonicInstant;
13
14use crate::error_from_metrics_error;
15
16pub async fn collect_stalls_forever(
18 stalls_provider: impl StallProvider,
19 page_fault_provider: impl PageFaultProvider,
20 metric_event_logger: fmetrics::MetricEventLoggerProxy,
21) -> Result<()> {
22 let mut last_stall = MemoryStallMetrics::default();
23 let mut last_refaults = 0;
24
25 fuchsia_async::Timer::new(MonotonicInstant::ZERO + zx::Duration::from_hours(1)).await;
28
29 let mut timer = fuchsia_async::Interval::new(zx::Duration::from_hours(1));
30 loop {
31 let new_stall = stalls_provider.get_stall_info()?;
32 let new_refaults = page_fault_provider.get_refault_count();
33
34 let stall_some_event = fmetrics::MetricEvent {
36 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
37 payload: fmetrics::MetricEventPayload::IntegerValue(i64::try_from(
38 (new_stall.some - last_stall.some).as_millis(),
39 )?),
40 event_codes: vec![
41 cobalt_registry::MemoryMetricDimensionStallType::Some.as_event_code(),
42 ],
43 };
44 let stall_full_event = fmetrics::MetricEvent {
45 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
46 payload: fmetrics::MetricEventPayload::IntegerValue(i64::try_from(
47 (new_stall.full - last_stall.full).as_millis(),
48 )?),
49 event_codes: vec![
50 cobalt_registry::MemoryMetricDimensionStallType::Full.as_event_code(),
51 ],
52 };
53 let refaults_event = fmetrics::MetricEvent {
56 metric_id: cobalt_registry::MEMORY_PAGE_REFAULTS_PER_HOUR_METRIC_ID,
57 payload: fmetrics::MetricEventPayload::IntegerValue(
58 new_refaults.saturating_sub(last_refaults).try_into().unwrap_or(i64::MAX),
59 ),
60 event_codes: vec![],
61 };
62
63 last_stall = new_stall;
64 last_refaults = new_refaults;
65
66 let events = vec![stall_some_event, stall_full_event, refaults_event];
67 metric_event_logger.log_metric_events(&events).await?.map_err(error_from_metrics_error)?;
68 timer.next().await;
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use anyhow::anyhow;
76 use fuchsia_async as fasync;
77 use futures::task::Poll;
78 use std::sync::Arc;
79 use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
80 use std::time::Duration;
81
82 fn get_stall_provider() -> impl StallProvider {
83 #[derive(Clone)]
84 struct FakeStallProvider {
85 count: Arc<AtomicU32>,
86 }
87
88 impl Default for FakeStallProvider {
89 fn default() -> Self {
90 Self { count: Arc::new(AtomicU32::new(1)) }
91 }
92 }
93
94 impl StallProvider for FakeStallProvider {
95 fn get_stall_info(&self) -> Result<MemoryStallMetrics> {
96 let count = self.count.fetch_add(1, Ordering::Relaxed);
97 let memory_stall = MemoryStallMetrics {
98 some: Duration::from_millis((count * 10).into()),
99 full: Duration::from_millis((count * 20).into()),
100 };
101 Ok(memory_stall)
102 }
103 }
104
105 FakeStallProvider::default()
106 }
107
108 fn get_page_fault_provider() -> impl PageFaultProvider {
109 #[derive(Clone, Default)]
110 struct FakePageFaultProvider {
111 calls: Arc<AtomicU64>,
112 }
113
114 impl PageFaultProvider for FakePageFaultProvider {
115 fn get_refault_count(&self) -> u64 {
116 match self.calls.fetch_add(1, Ordering::Relaxed) {
119 0 => 100,
120 _ => 350,
121 }
122 }
123
124 fn get_fault_count(&self) -> u64 {
125 0
126 }
127 }
128
129 FakePageFaultProvider::default()
130 }
131
132 #[test]
133 fn test_periodic_stalls_collection() -> Result<()> {
134 let mut exec = fasync::TestExecutor::new_with_fake_time();
136
137 let data_provider = get_stall_provider();
139 let page_fault_provider = get_page_fault_provider();
140
141 let (metric_event_logger, metric_event_request_stream) =
143 fidl::endpoints::create_proxy_and_stream::<fmetrics::MetricEventLoggerMarker>();
144
145 exec.set_fake_time(
147 (zx::MonotonicInstant::ZERO + zx::Duration::from_seconds(3 * 60)).into(),
148 );
149 let mut stalls_collector = fuchsia_async::Task::spawn(async move {
151 collect_stalls_forever(data_provider, page_fault_provider, metric_event_logger).await
152 });
153
154 assert!(
156 exec.run_until_stalled(&mut stalls_collector).is_pending(),
157 "Stalls collection service returned unexpectedly early"
158 );
159
160 let mut metric_event_request_future = metric_event_request_stream.into_future();
162 assert!(
163 exec.run_until_stalled(&mut metric_event_request_future).is_pending(),
164 "Stalls collection service returned unexpectedly early"
165 );
166
167 assert!(
169 exec.run_until_stalled(&mut std::pin::pin!(fasync::TestExecutor::advance_to(
170 exec.now() + zx::Duration::from_seconds(60 * 60 + 10)
171 )))
172 .is_ready(),
173 "Failed to advance time"
174 );
175
176 let Poll::Ready((event, metric_event_request_stream)) =
178 exec.run_until_stalled(&mut metric_event_request_future)
179 else {
180 panic!("Failed to receive metrics")
181 };
182 let event = event.ok_or_else(|| anyhow!("Metrics stream unexpectedly closed"))??;
183 match event {
184 fmetrics::MetricEventLoggerRequest::LogMetricEvents { events, responder, .. } => {
185 assert_eq!(events.len(), 3);
186 assert_eq!(
188 events[0],
189 fmetrics::MetricEvent {
190 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
191 event_codes: vec![
192 cobalt_registry::MemoryMetricDimensionStallType::Some.as_event_code()
193 ],
194 payload: fmetrics::MetricEventPayload::IntegerValue(10)
195 }
196 );
197 assert_eq!(
198 events[1],
199 fmetrics::MetricEvent {
200 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
201 event_codes: vec![
202 cobalt_registry::MemoryMetricDimensionStallType::Full.as_event_code()
203 ],
204 payload: fmetrics::MetricEventPayload::IntegerValue(20)
205 }
206 );
207 assert_eq!(
208 events[2],
209 fmetrics::MetricEvent {
210 metric_id: cobalt_registry::MEMORY_PAGE_REFAULTS_PER_HOUR_METRIC_ID,
211 event_codes: vec![],
212 payload: fmetrics::MetricEventPayload::IntegerValue(100)
213 }
214 );
215 responder.send(Ok(()))?;
216 }
217 _ => panic!("Unexpected metric event"),
218 }
219
220 let mut metric_event_request_future = metric_event_request_stream.into_future();
221
222 assert!(exec.run_until_stalled(&mut metric_event_request_future).is_pending());
223
224 assert!(
226 exec.run_until_stalled(&mut std::pin::pin!(fasync::TestExecutor::advance_to(
227 (zx::MonotonicInstant::ZERO + zx::Duration::from_seconds(60 * 60 * 2 + 10)).into()
228 )))
229 .is_ready(),
230 "Failed to advance time"
231 );
232
233 let Poll::Ready((event, metric_event_request_stream)) =
235 exec.run_until_stalled(&mut metric_event_request_future)
236 else {
237 panic!("Failed to receive metrics")
238 };
239 let event = event.ok_or_else(|| anyhow!("Metrics stream unexpectedly closed"))??;
240 match event {
241 fmetrics::MetricEventLoggerRequest::LogMetricEvents { events, responder, .. } => {
242 assert_eq!(events.len(), 3);
243 assert_eq!(
245 events[0],
246 fmetrics::MetricEvent {
247 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
248 event_codes: vec![
249 cobalt_registry::MemoryMetricDimensionStallType::Some.as_event_code()
250 ],
251 payload: fmetrics::MetricEventPayload::IntegerValue(10)
252 }
253 );
254 assert_eq!(
255 events[1],
256 fmetrics::MetricEvent {
257 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
258 event_codes: vec![
259 cobalt_registry::MemoryMetricDimensionStallType::Full.as_event_code()
260 ],
261 payload: fmetrics::MetricEventPayload::IntegerValue(20)
262 }
263 );
264 assert_eq!(
265 events[2],
266 fmetrics::MetricEvent {
267 metric_id: cobalt_registry::MEMORY_PAGE_REFAULTS_PER_HOUR_METRIC_ID,
268 event_codes: vec![],
269 payload: fmetrics::MetricEventPayload::IntegerValue(250)
270 }
271 );
272 responder.send(Ok(()))?;
273 }
274 _ => panic!("Unexpected metric event"),
275 }
276
277 assert!(
278 exec.run_until_stalled(&mut metric_event_request_stream.into_future()).is_pending()
279 );
280
281 Ok(())
282 }
283}