wlan_telemetry/processors/
pno_scan.rs1use crate::util::cobalt_logger::log_cobalt_batch;
6use fidl_fuchsia_metrics::{MetricEvent, MetricEventPayload};
7use fuchsia_async as fasync;
8use wlan_legacy_metrics_registry as metrics;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum PnoScanDisabledReason {
12 ApiRequest,
13 Internal,
14 Firmware,
15}
16
17pub struct PnoScanLogger {
18 cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
19 enabled_at: Option<fasync::BootInstant>,
20 has_scan_results: bool,
21}
22
23impl PnoScanLogger {
24 pub fn new(cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy) -> Self {
25 Self { cobalt_proxy, enabled_at: None, has_scan_results: false }
26 }
27
28 pub async fn handle_pno_scan_enabled(&mut self, is_connected: bool) {
29 if self.enabled_at.is_none() {
30 self.enabled_at = Some(fasync::BootInstant::now());
31 self.has_scan_results = false;
32
33 if is_connected {
34 let metric_events = vec![MetricEvent {
35 metric_id: metrics::PNO_SCAN_ENABLED_WHILE_CONNECTED_METRIC_ID,
36 event_codes: vec![],
37 payload: MetricEventPayload::Count(1),
38 }];
39 log_cobalt_batch!(
40 self.cobalt_proxy,
41 &metric_events,
42 "pno_scan_enabled_while_connected"
43 );
44 }
45 } else {
46 let metric_events = vec![MetricEvent {
51 metric_id: metrics::PNO_SCAN_REQUEST_COLLISION_METRIC_ID,
52 event_codes: vec![],
53 payload: MetricEventPayload::Count(1),
54 }];
55 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_pno_scan_enabled");
56 }
57 }
58
59 pub async fn handle_pno_scan_results_received(&mut self) {
60 if !self.has_scan_results {
64 self.has_scan_results = true;
65
66 if let Some(enabled_at) = self.enabled_at {
67 let elapsed = fasync::BootInstant::now() - enabled_at;
68 let metric_events = vec![MetricEvent {
69 metric_id: metrics::PNO_SCAN_FIRST_RESULTS_ELAPSED_TIME_METRIC_ID,
70 event_codes: vec![],
71 payload: MetricEventPayload::IntegerValue(elapsed.into_millis()),
72 }];
73 log_cobalt_batch!(
74 self.cobalt_proxy,
75 &metric_events,
76 "handle_pno_scan_results_received"
77 );
78 }
79 }
80 }
81
82 pub async fn handle_pno_scan_disabled(&mut self, reason: PnoScanDisabledReason) {
83 if let Some(enabled_at) = self.enabled_at.take() {
84 let now = fasync::BootInstant::now();
85 let elapsed = now - enabled_at;
86
87 let mut metric_events = vec![];
88
89 metric_events.push(MetricEvent {
91 metric_id: metrics::PNO_SCAN_CANCELLED_ELAPSED_TIME_METRIC_ID,
92 event_codes: vec![if self.has_scan_results {
93 metrics::PnoScanCancelledElapsedTimeMetricDimensionHadAnyScanResults::True
94 as u32
95 } else {
96 metrics::PnoScanCancelledElapsedTimeMetricDimensionHadAnyScanResults::False
97 as u32
98 }],
99 payload: MetricEventPayload::IntegerValue(elapsed.into_millis()),
100 });
101
102 let had_scan_results = if self.has_scan_results {
104 metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionHasScanResults::True as u32
105 } else {
106 metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionHasScanResults::False as u32
107 };
108
109 let cancellation_source = match reason {
110 PnoScanDisabledReason::ApiRequest => metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionCancellationSource::ApiRequest as u32,
111 PnoScanDisabledReason::Internal => metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionCancellationSource::Internal as u32,
112 PnoScanDisabledReason::Firmware => metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionCancellationSource::Firmware as u32,
113 };
114
115 metric_events.push(MetricEvent {
116 metric_id: metrics::PNO_SCAN_CANCELLATION_BREAKDOWN_BY_RESULTS_AND_SOURCE_METRIC_ID,
117 event_codes: vec![had_scan_results, cancellation_source],
118 payload: MetricEventPayload::Count(1),
119 });
120
121 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_pno_scan_disabled");
122 }
123
124 self.has_scan_results = false;
125 }
126
127 pub async fn handle_periodic_telemetry(&mut self) {
128 if let Some(enabled_at) = self.enabled_at {
129 let elapsed = fasync::BootInstant::now() - enabled_at;
130 let hours = elapsed.into_hours();
131 let capped_hours = std::cmp::min(hours, 24);
132
133 let metric_events = vec![MetricEvent {
134 metric_id: metrics::ONGOING_PNO_SCAN_ELAPSED_HOURS_METRIC_ID,
135 event_codes: vec![],
136 payload: MetricEventPayload::IntegerValue(capped_hours),
137 }];
138 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_periodic_telemetry");
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::testing::setup_test;
147 use fidl_fuchsia_metrics::MetricEventPayload;
148 use futures::task::Poll;
149 use std::pin::pin;
150
151 #[fuchsia::test]
152 fn test_pno_scan_collision() {
153 let mut test_helper = setup_test();
154 let mut logger = PnoScanLogger::new(test_helper.cobalt_proxy.clone());
155
156 {
157 let mut test_fut = pin!(logger.handle_pno_scan_enabled(false));
158 assert_eq!(
159 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
160 Poll::Ready(())
161 );
162 }
163
164 {
166 let mut test_fut = pin!(logger.handle_pno_scan_enabled(false));
167 assert_eq!(
168 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
169 Poll::Ready(())
170 );
171 }
172
173 let metrics = test_helper.get_logged_metrics(metrics::PNO_SCAN_REQUEST_COLLISION_METRIC_ID);
174 assert_eq!(metrics.len(), 1);
175 assert_eq!(metrics[0].payload, MetricEventPayload::Count(1));
176 }
177
178 #[fuchsia::test]
179 fn test_pno_scan_enabled_while_connected() {
180 let mut test_helper = setup_test();
181 let mut logger = PnoScanLogger::new(test_helper.cobalt_proxy.clone());
182
183 {
184 let mut test_fut = pin!(logger.handle_pno_scan_enabled(true));
185 assert_eq!(
186 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
187 Poll::Ready(())
188 );
189 }
190
191 let metrics =
192 test_helper.get_logged_metrics(metrics::PNO_SCAN_ENABLED_WHILE_CONNECTED_METRIC_ID);
193 assert_eq!(metrics.len(), 1);
194 assert_eq!(metrics[0].payload, MetricEventPayload::Count(1));
195 }
196
197 #[fuchsia::test]
198 fn test_pno_scan_results_received_metrics() {
199 let mut test_helper = setup_test();
200 let mut logger = PnoScanLogger::new(test_helper.cobalt_proxy.clone());
201
202 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
203 {
204 let mut test_fut = pin!(logger.handle_pno_scan_enabled(false));
205 assert_eq!(
206 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
207 Poll::Ready(())
208 );
209 }
210
211 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(25_000_000));
212 {
213 let mut test_fut = pin!(logger.handle_pno_scan_results_received());
214 assert_eq!(
215 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
216 Poll::Ready(())
217 );
218 }
219
220 let metrics =
221 test_helper.get_logged_metrics(metrics::PNO_SCAN_FIRST_RESULTS_ELAPSED_TIME_METRIC_ID);
222 assert_eq!(metrics.len(), 1);
223 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(15)); }
225
226 #[fuchsia::test]
227 fn test_pno_scan_disabled_no_results() {
228 let mut test_helper = setup_test();
229 let mut logger = PnoScanLogger::new(test_helper.cobalt_proxy.clone());
230
231 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
232 {
233 let mut test_fut = pin!(logger.handle_pno_scan_enabled(false));
234 assert_eq!(
235 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
236 Poll::Ready(())
237 );
238 }
239
240 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(30_000_000));
241 {
242 let mut test_fut =
243 pin!(logger.handle_pno_scan_disabled(PnoScanDisabledReason::Internal));
244 assert_eq!(
245 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
246 Poll::Ready(())
247 );
248 }
249
250 let metrics =
251 test_helper.get_logged_metrics(metrics::PNO_SCAN_CANCELLED_ELAPSED_TIME_METRIC_ID);
252 assert_eq!(metrics.len(), 1);
253 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(20)); assert_eq!(
255 metrics[0].event_codes,
256 vec![
257 metrics::PnoScanCancelledElapsedTimeMetricDimensionHadAnyScanResults::False as u32
258 ]
259 ); let metrics = test_helper.get_logged_metrics(
262 metrics::PNO_SCAN_CANCELLATION_BREAKDOWN_BY_RESULTS_AND_SOURCE_METRIC_ID,
263 );
264 assert_eq!(metrics.len(), 1);
265 assert_eq!(metrics[0].event_codes, vec![
266 metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionHasScanResults::False as u32,
267 metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionCancellationSource::Internal as u32
268 ]); }
270
271 #[fuchsia::test]
272 fn test_pno_scan_disabled_with_results() {
273 let mut test_helper = setup_test();
274 let mut logger = PnoScanLogger::new(test_helper.cobalt_proxy.clone());
275
276 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
277 {
278 let mut test_fut = pin!(logger.handle_pno_scan_enabled(false));
279 assert_eq!(
280 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
281 Poll::Ready(())
282 );
283 }
284
285 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(20_000_000));
286 {
287 let mut test_fut = pin!(logger.handle_pno_scan_results_received());
288 assert_eq!(
289 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
290 Poll::Ready(())
291 );
292 }
293
294 let metrics =
295 test_helper.get_logged_metrics(metrics::PNO_SCAN_FIRST_RESULTS_ELAPSED_TIME_METRIC_ID);
296 assert_eq!(metrics.len(), 1);
297 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(10)); test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(30_000_000));
300 {
301 let mut test_fut =
302 pin!(logger.handle_pno_scan_disabled(PnoScanDisabledReason::ApiRequest));
303 assert_eq!(
304 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
305 Poll::Ready(())
306 );
307 }
308
309 let metrics =
310 test_helper.get_logged_metrics(metrics::PNO_SCAN_CANCELLED_ELAPSED_TIME_METRIC_ID);
311 assert_eq!(metrics.len(), 1);
312 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(20)); assert_eq!(
314 metrics[0].event_codes,
315 vec![metrics::PnoScanCancelledElapsedTimeMetricDimensionHadAnyScanResults::True as u32]
316 ); let metrics = test_helper.get_logged_metrics(
319 metrics::PNO_SCAN_CANCELLATION_BREAKDOWN_BY_RESULTS_AND_SOURCE_METRIC_ID,
320 );
321 assert_eq!(metrics.len(), 1);
322 assert_eq!(metrics[0].event_codes, vec![
323 metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionHasScanResults::True as u32,
324 metrics::PnoScanCancellationBreakdownByResultsAndSourceMetricDimensionCancellationSource::ApiRequest as u32
325 ]); }
327
328 #[fuchsia::test]
329 fn test_pno_scan_periodic_telemetry() {
330 let mut test_helper = setup_test();
331 let mut logger = PnoScanLogger::new(test_helper.cobalt_proxy.clone());
332
333 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
334 {
335 let mut test_fut = pin!(logger.handle_pno_scan_enabled(false));
336 assert_eq!(
337 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
338 Poll::Ready(())
339 );
340 }
341
342 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(
344 10_000_000 + 5 * 3600 * 1_000_000_000,
345 ));
346 {
347 let mut test_fut = pin!(logger.handle_periodic_telemetry());
348 assert_eq!(
349 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
350 Poll::Ready(())
351 );
352 }
353
354 let metrics =
355 test_helper.get_logged_metrics(metrics::ONGOING_PNO_SCAN_ELAPSED_HOURS_METRIC_ID);
356 assert_eq!(metrics.len(), 1);
357 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(5));
358
359 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(
361 10_000_000 + 25 * 3600 * 1_000_000_000,
362 ));
363 {
364 let mut test_fut = pin!(logger.handle_periodic_telemetry());
365 assert_eq!(
366 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
367 Poll::Ready(())
368 );
369 }
370
371 let metrics =
372 test_helper.get_logged_metrics(metrics::ONGOING_PNO_SCAN_ELAPSED_HOURS_METRIC_ID);
373 assert_eq!(metrics.len(), 2);
374 assert_eq!(metrics[1].payload, MetricEventPayload::IntegerValue(24)); }
376}