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::RefaultProvider;
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 refault_provider: impl RefaultProvider,
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 = refault_provider.get_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_refault_provider() -> impl RefaultProvider {
109 #[derive(Clone, Default)]
110 struct FakeRefaultProvider {
111 calls: Arc<AtomicU64>,
112 }
113
114 impl RefaultProvider for FakeRefaultProvider {
115 fn get_count(&self) -> u64 {
116 match self.calls.fetch_add(1, Ordering::Relaxed) {
119 0 => 100,
120 _ => 350,
121 }
122 }
123 }
124
125 FakeRefaultProvider::default()
126 }
127
128 #[test]
129 fn test_periodic_stalls_collection() -> Result<()> {
130 let mut exec = fasync::TestExecutor::new_with_fake_time();
132
133 let data_provider = get_stall_provider();
135 let refault_provider = get_refault_provider();
136
137 let (metric_event_logger, metric_event_request_stream) =
139 fidl::endpoints::create_proxy_and_stream::<fmetrics::MetricEventLoggerMarker>();
140
141 exec.set_fake_time(
143 (zx::MonotonicInstant::ZERO + zx::Duration::from_seconds(3 * 60)).into(),
144 );
145 let mut stalls_collector = fuchsia_async::Task::spawn(async move {
147 collect_stalls_forever(data_provider, refault_provider, metric_event_logger).await
148 });
149
150 assert!(
152 exec.run_until_stalled(&mut stalls_collector).is_pending(),
153 "Stalls collection service returned unexpectedly early"
154 );
155
156 let mut metric_event_request_future = metric_event_request_stream.into_future();
158 assert!(
159 exec.run_until_stalled(&mut metric_event_request_future).is_pending(),
160 "Stalls collection service returned unexpectedly early"
161 );
162
163 assert!(
165 exec.run_until_stalled(&mut std::pin::pin!(fasync::TestExecutor::advance_to(
166 exec.now() + zx::Duration::from_seconds(60 * 60 + 10)
167 )))
168 .is_ready(),
169 "Failed to advance time"
170 );
171
172 let Poll::Ready((event, metric_event_request_stream)) =
174 exec.run_until_stalled(&mut metric_event_request_future)
175 else {
176 panic!("Failed to receive metrics")
177 };
178 let event = event.ok_or_else(|| anyhow!("Metrics stream unexpectedly closed"))??;
179 match event {
180 fmetrics::MetricEventLoggerRequest::LogMetricEvents { events, responder, .. } => {
181 assert_eq!(events.len(), 3);
182 assert_eq!(
184 events[0],
185 fmetrics::MetricEvent {
186 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
187 event_codes: vec![
188 cobalt_registry::MemoryMetricDimensionStallType::Some.as_event_code()
189 ],
190 payload: fmetrics::MetricEventPayload::IntegerValue(10)
191 }
192 );
193 assert_eq!(
194 events[1],
195 fmetrics::MetricEvent {
196 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
197 event_codes: vec![
198 cobalt_registry::MemoryMetricDimensionStallType::Full.as_event_code()
199 ],
200 payload: fmetrics::MetricEventPayload::IntegerValue(20)
201 }
202 );
203 assert_eq!(
204 events[2],
205 fmetrics::MetricEvent {
206 metric_id: cobalt_registry::MEMORY_PAGE_REFAULTS_PER_HOUR_METRIC_ID,
207 event_codes: vec![],
208 payload: fmetrics::MetricEventPayload::IntegerValue(100)
209 }
210 );
211 responder.send(Ok(()))?;
212 }
213 _ => panic!("Unexpected metric event"),
214 }
215
216 let mut metric_event_request_future = metric_event_request_stream.into_future();
217
218 assert!(exec.run_until_stalled(&mut metric_event_request_future).is_pending());
219
220 assert!(
222 exec.run_until_stalled(&mut std::pin::pin!(fasync::TestExecutor::advance_to(
223 (zx::MonotonicInstant::ZERO + zx::Duration::from_seconds(60 * 60 * 2 + 10)).into()
224 )))
225 .is_ready(),
226 "Failed to advance time"
227 );
228
229 let Poll::Ready((event, metric_event_request_stream)) =
231 exec.run_until_stalled(&mut metric_event_request_future)
232 else {
233 panic!("Failed to receive metrics")
234 };
235 let event = event.ok_or_else(|| anyhow!("Metrics stream unexpectedly closed"))??;
236 match event {
237 fmetrics::MetricEventLoggerRequest::LogMetricEvents { events, responder, .. } => {
238 assert_eq!(events.len(), 3);
239 assert_eq!(
241 events[0],
242 fmetrics::MetricEvent {
243 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
244 event_codes: vec![
245 cobalt_registry::MemoryMetricDimensionStallType::Some.as_event_code()
246 ],
247 payload: fmetrics::MetricEventPayload::IntegerValue(10)
248 }
249 );
250 assert_eq!(
251 events[1],
252 fmetrics::MetricEvent {
253 metric_id: cobalt_registry::MEMORY_STALLS_PER_HOUR_METRIC_ID,
254 event_codes: vec![
255 cobalt_registry::MemoryMetricDimensionStallType::Full.as_event_code()
256 ],
257 payload: fmetrics::MetricEventPayload::IntegerValue(20)
258 }
259 );
260 assert_eq!(
261 events[2],
262 fmetrics::MetricEvent {
263 metric_id: cobalt_registry::MEMORY_PAGE_REFAULTS_PER_HOUR_METRIC_ID,
264 event_codes: vec![],
265 payload: fmetrics::MetricEventPayload::IntegerValue(250)
266 }
267 );
268 responder.send(Ok(()))?;
269 }
270 _ => panic!("Unexpected metric event"),
271 }
272
273 assert!(
274 exec.run_until_stalled(&mut metric_event_request_stream.into_future()).is_pending()
275 );
276
277 Ok(())
278 }
279}