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