wlan_telemetry/processors/
toggle_events.rs1use crate::util::cobalt_logger::{FilteredCobaltLogger, log_cobalt_batch};
6use fidl_fuchsia_metrics::{MetricEvent, MetricEventPayload};
7use fidl_fuchsia_power_battery as fidl_battery;
8use fuchsia_async as fasync;
9use fuchsia_inspect::Node as InspectNode;
10use fuchsia_inspect_contrib::inspect_log;
11use fuchsia_inspect_contrib::nodes::BoundedListNode;
12use std::cmp::max;
13use std::sync::Arc;
14use wlan_legacy_metrics_registry as metrics;
15use zx;
16
17pub const INSPECT_TOGGLE_EVENTS_LIMIT: usize = 20;
18const TIME_QUICK_TOGGLE_WIFI: zx::BootDuration = zx::BootDuration::from_seconds(5);
19
20#[derive(Debug, PartialEq)]
21pub enum ClientConnectionsToggleEvent {
22 Enabled,
23 Disabled,
24}
25
26pub struct ToggleLogger {
27 toggle_inspect_node: BoundedListNode,
28 cobalt_proxy: Arc<FilteredCobaltLogger>,
29 current_state: Option<ClientConnectionsToggleEvent>,
32 time_started: Option<fasync::BootInstant>,
34 time_stopped: Option<fasync::BootInstant>,
37 on_battery_since: Option<fasync::BootInstant>,
40}
41
42impl ToggleLogger {
43 pub fn new(cobalt_proxy: Arc<FilteredCobaltLogger>, inspect_node: &InspectNode) -> Self {
44 let toggle_events = inspect_node.create_child("client_connections_toggle_events");
46 let toggle_inspect_node = BoundedListNode::new(toggle_events, INSPECT_TOGGLE_EVENTS_LIMIT);
47 let current_state = None;
48 let time_started = None;
49 let time_stopped = None;
50 let on_battery_since = None;
51
52 Self {
53 toggle_inspect_node,
54 cobalt_proxy,
55 current_state,
56 time_started,
57 time_stopped,
58 on_battery_since,
59 }
60 }
61
62 pub async fn handle_toggle_event(&mut self, event_type: ClientConnectionsToggleEvent) {
63 inspect_log!(self.toggle_inspect_node, {
65 event_type: std::format!("{:?}", event_type)
66 });
67
68 let mut metric_events = vec![];
69 let now = fasync::BootInstant::now();
70 match &event_type {
71 ClientConnectionsToggleEvent::Enabled => {
72 if self.current_state != Some(ClientConnectionsToggleEvent::Enabled) {
74 self.time_started = Some(now);
75
76 metric_events.push(MetricEvent {
77 metric_id: metrics::CLIENT_CONNECTION_ENABLED_OCCURRENCE_METRIC_ID,
78 event_codes: vec![],
79 payload: MetricEventPayload::Count(1),
80 });
81 }
82
83 if self.current_state == Some(ClientConnectionsToggleEvent::Disabled)
86 && let Some(time_stopped) = self.time_stopped
87 && now - time_stopped < TIME_QUICK_TOGGLE_WIFI
88 {
89 metric_events.push(MetricEvent {
90 metric_id: metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID,
91 event_codes: vec![],
92 payload: MetricEventPayload::Count(1),
93 });
94 }
95 }
96 ClientConnectionsToggleEvent::Disabled => {
97 if self.current_state == Some(ClientConnectionsToggleEvent::Enabled) {
99 self.time_stopped = Some(now);
100
101 if let Some(time_started) = self.time_started {
102 let duration = now - time_started;
103 metric_events.push(MetricEvent {
104 metric_id: metrics::CLIENT_CONNECTION_ENABLED_DURATION_METRIC_ID,
105 event_codes: vec![],
106 payload: MetricEventPayload::IntegerValue(duration.into_millis()),
107 });
108
109 if let Some(on_battery_since) = self.on_battery_since {
118 let duration = now - max(time_started, on_battery_since);
121 metric_events.push(MetricEvent {
122 metric_id: metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID,
123 event_codes: vec![],
124 payload: MetricEventPayload::IntegerValue(duration.into_millis()),
125 });
126 }
127 }
128 }
129 }
130 }
131 self.current_state = Some(event_type);
132
133 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_toggle_events");
134 }
135
136 pub async fn handle_battery_charge_status(
137 &mut self,
138 charge_status: fidl_battery::ChargeStatus,
139 ) {
140 let mut metric_events = vec![];
141 let now = fasync::BootInstant::now();
142 let on_battery_now = matches!(charge_status, fidl_battery::ChargeStatus::Discharging);
143
144 match (self.on_battery_since, on_battery_now) {
145 (None, true) => self.on_battery_since = Some(now),
146 (Some(on_battery_since), false) => {
147 let _on_battery_since = self.on_battery_since.take();
148 if let Some(ClientConnectionsToggleEvent::Enabled) = self.current_state
153 && let Some(time_started) = self.time_started
154 {
155 let duration = now - max(time_started, on_battery_since);
158 metric_events.push(MetricEvent {
159 metric_id: metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID,
160 event_codes: vec![],
161 payload: MetricEventPayload::IntegerValue(duration.into_millis()),
162 });
163 }
164 }
165 _ => (),
166 }
167
168 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_battery_charge_status");
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::testing::{TestHelper, setup_test};
176 use assert_matches::assert_matches;
177 use diagnostics_assertions::{AnyNumericProperty, assert_data_tree};
178 use futures::task::Poll;
179 use std::pin::pin;
180
181 #[fuchsia::test]
182 fn test_toggle_is_recorded_to_inspect() {
183 let mut test_helper = setup_test();
184 let node = test_helper.create_inspect_node("wlan_mock_node");
185 let mut toggle_logger = ToggleLogger::new(test_helper.filtered_cobalt_logger(), &node);
186
187 let event = ClientConnectionsToggleEvent::Enabled;
188 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
189
190 let event = ClientConnectionsToggleEvent::Disabled;
191 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
192
193 let event = ClientConnectionsToggleEvent::Enabled;
194 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
195
196 assert_data_tree!(@executor test_helper.exec, test_helper.inspector, root: contains {
197 wlan_mock_node: {
198 client_connections_toggle_events: {
199 "0": {
200 "event_type": "Enabled",
201 "@time": AnyNumericProperty
202 },
203 "1": {
204 "event_type": "Disabled",
205 "@time": AnyNumericProperty
206 },
207 "2": {
208 "event_type": "Enabled",
209 "@time": AnyNumericProperty
210 },
211 }
212 }
213 });
214 }
215
216 fn run_handle_toggle_event(
219 test_helper: &mut TestHelper,
220 toggle_logger: &mut ToggleLogger,
221 event: ClientConnectionsToggleEvent,
222 ) {
223 let mut test_fut = pin!(toggle_logger.handle_toggle_event(event));
224 assert_eq!(
225 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
226 Poll::Ready(())
227 );
228 }
229
230 fn run_handle_battery_charge_status(
231 test_helper: &mut TestHelper,
232 toggle_logger: &mut ToggleLogger,
233 charge_status: fidl_battery::ChargeStatus,
234 ) {
235 let mut test_fut = pin!(toggle_logger.handle_battery_charge_status(charge_status));
236 assert_eq!(
237 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
238 Poll::Ready(())
239 );
240 }
241
242 #[fuchsia::test]
243 fn test_quick_toggle_metric_is_recorded() {
244 let mut test_helper = setup_test();
245 let inspect_node = test_helper.create_inspect_node("test_stats");
246 let mut toggle_logger =
247 ToggleLogger::new(test_helper.filtered_cobalt_logger(), &inspect_node);
248
249 let mut test_time = fasync::MonotonicInstant::from_nanos(123);
251 let event = ClientConnectionsToggleEvent::Enabled;
252 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
253
254 test_time += fasync::MonotonicDuration::from_minutes(40);
256 test_helper.exec.set_fake_time(test_time);
257 let event = ClientConnectionsToggleEvent::Disabled;
258 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
259
260 test_time += fasync::MonotonicDuration::from_seconds(1);
261 test_helper.exec.set_fake_time(test_time);
262 let event = ClientConnectionsToggleEvent::Enabled;
263 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
264
265 let logged_metrics =
267 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
268 assert_matches!(&logged_metrics[..], [metric] => {
269 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
270 metric_id: metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID,
271 event_codes: vec![],
272 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
273 };
274 assert_eq!(metric, &expected_metric);
275 });
276 }
277
278 #[fuchsia::test]
279 fn test_quick_toggle_no_metric_is_recorded_if_not_quick() {
280 let mut test_helper = setup_test();
281 let inspect_node = test_helper.create_inspect_node("test_stats");
282 let mut toggle_logger =
283 ToggleLogger::new(test_helper.filtered_cobalt_logger(), &inspect_node);
284
285 let mut test_time = fasync::MonotonicInstant::from_nanos(123);
287 let event = ClientConnectionsToggleEvent::Enabled;
288 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
289
290 test_time += fasync::MonotonicDuration::from_minutes(20);
292 test_helper.exec.set_fake_time(test_time);
293 let event = ClientConnectionsToggleEvent::Disabled;
294 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
295
296 test_time += fasync::MonotonicDuration::from_minutes(30);
297 test_helper.exec.set_fake_time(test_time);
298 let event = ClientConnectionsToggleEvent::Enabled;
299 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
300
301 let logged_metrics =
304 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
305 assert!(logged_metrics.is_empty());
306 }
307
308 #[fuchsia::test]
309 fn test_quick_toggle_metric_second_disable_doesnt_update_time() {
310 let mut test_helper = setup_test();
313 let inspect_node = test_helper.create_inspect_node("test_stats");
314 let mut toggle_logger =
315 ToggleLogger::new(test_helper.filtered_cobalt_logger(), &inspect_node);
316
317 let mut test_time = fasync::MonotonicInstant::from_nanos(123);
319 let event = ClientConnectionsToggleEvent::Enabled;
320 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
321
322 test_time += fasync::MonotonicDuration::from_minutes(40);
324 test_helper.exec.set_fake_time(test_time);
325 let event = ClientConnectionsToggleEvent::Disabled;
326 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
327
328 test_time += fasync::MonotonicDuration::from_minutes(30);
329 test_helper.exec.set_fake_time(test_time);
330 let event = ClientConnectionsToggleEvent::Disabled;
331 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
332
333 test_time += fasync::MonotonicDuration::from_seconds(1);
335 test_helper.exec.set_fake_time(test_time);
336 let event = ClientConnectionsToggleEvent::Enabled;
337 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
338
339 let logged_metrics =
342 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
343 assert!(logged_metrics.is_empty());
344 }
345
346 #[fuchsia::test]
347 fn test_log_client_connection_enabled() {
348 let mut test_helper = setup_test();
349 let inspect_node = test_helper.create_inspect_node("test_stats");
350 let mut toggle_logger =
351 ToggleLogger::new(test_helper.filtered_cobalt_logger(), &inspect_node);
352
353 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
355 let event = ClientConnectionsToggleEvent::Enabled;
356 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
357
358 let metrics =
359 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_OCCURRENCE_METRIC_ID);
360 assert_eq!(metrics.len(), 1);
361 assert_eq!(metrics[0].payload, MetricEventPayload::Count(1));
362
363 test_helper.clear_cobalt_events();
366 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(50_000_000));
367 let event = ClientConnectionsToggleEvent::Enabled;
368 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
369
370 let metrics =
371 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_OCCURRENCE_METRIC_ID);
372 assert!(metrics.is_empty());
373
374 test_helper.clear_cobalt_events();
377 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(100_000_000));
378 let event = ClientConnectionsToggleEvent::Disabled;
379 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
380
381 let metrics =
382 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_METRIC_ID);
383 assert_eq!(metrics.len(), 1);
384 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(90));
385 let metrics = test_helper
386 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
387 assert!(metrics.is_empty());
388 }
389
390 #[fuchsia::test]
391 fn test_log_client_connection_enabled_duration_on_battery_with_reenable() {
392 let mut test_helper = setup_test();
393 let inspect_node = test_helper.create_inspect_node("test_stats");
394 let mut toggle_logger =
395 ToggleLogger::new(test_helper.filtered_cobalt_logger(), &inspect_node);
396
397 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
399 let event = ClientConnectionsToggleEvent::Enabled;
400 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
401
402 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(30_000_000));
404 let charge_status = fidl_battery::ChargeStatus::Discharging;
405 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
406
407 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(100_000_000));
410 let event = ClientConnectionsToggleEvent::Disabled;
411 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
412
413 let metrics = test_helper
414 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
415 assert_eq!(metrics.len(), 1);
416 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(70));
417
418 test_helper.clear_cobalt_events();
419 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(110_000_000));
421 let event = ClientConnectionsToggleEvent::Enabled;
422 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
423 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(150_000_000));
424 let event = ClientConnectionsToggleEvent::Disabled;
425 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
426
427 let metrics = test_helper
429 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
430 assert_eq!(metrics.len(), 1);
431 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(40));
432 }
433
434 #[fuchsia::test]
435 fn test_log_client_connection_enabled_duration_on_battery_with_repeated_battery_change() {
436 let mut test_helper = setup_test();
437 let inspect_node = test_helper.create_inspect_node("test_stats");
438 let mut toggle_logger =
439 ToggleLogger::new(test_helper.filtered_cobalt_logger(), &inspect_node);
440
441 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
443 let event = ClientConnectionsToggleEvent::Enabled;
444 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
445
446 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(30_000_000));
448 let charge_status = fidl_battery::ChargeStatus::Discharging;
449 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
450
451 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(100_000_000));
454 let charge_status = fidl_battery::ChargeStatus::Charging;
455 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
456
457 let metrics = test_helper
458 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
459 assert_eq!(metrics.len(), 1);
460 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(70));
461
462 test_helper.clear_cobalt_events();
463 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(110_000_000));
465 let charge_status = fidl_battery::ChargeStatus::Discharging;
466 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
467 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(150_000_000));
468 let charge_status = fidl_battery::ChargeStatus::Charging;
469 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
470
471 let metrics = test_helper
473 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
474 assert_eq!(metrics.len(), 1);
475 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(40));
476 }
477}