wlan_telemetry/processors/
toggle_events.rs1use crate::util::cobalt_logger::log_cobalt_batch;
6use fidl_fuchsia_metrics::{MetricEvent, MetricEventPayload};
7use fuchsia_inspect::Node as InspectNode;
8use fuchsia_inspect_contrib::inspect_log;
9use fuchsia_inspect_contrib::nodes::BoundedListNode;
10use std::cmp::max;
11use {
12 fidl_fuchsia_power_battery as fidl_battery, fuchsia_async as fasync,
13 wlan_legacy_metrics_registry as metrics, zx,
14};
15
16pub const INSPECT_TOGGLE_EVENTS_LIMIT: usize = 20;
17const TIME_QUICK_TOGGLE_WIFI: zx::BootDuration = zx::BootDuration::from_seconds(5);
18
19#[derive(Debug, PartialEq)]
20pub enum ClientConnectionsToggleEvent {
21 Enabled,
22 Disabled,
23}
24
25pub struct ToggleLogger {
26 toggle_inspect_node: BoundedListNode,
27 cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
28 current_state: Option<ClientConnectionsToggleEvent>,
31 time_started: Option<fasync::BootInstant>,
33 time_stopped: Option<fasync::BootInstant>,
36 on_battery_since: Option<fasync::BootInstant>,
39}
40
41impl ToggleLogger {
42 pub fn new(
43 cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
44 inspect_node: &InspectNode,
45 ) -> Self {
46 let toggle_events = inspect_node.create_child("client_connections_toggle_events");
48 let toggle_inspect_node = BoundedListNode::new(toggle_events, INSPECT_TOGGLE_EVENTS_LIMIT);
49 let current_state = None;
50 let time_started = None;
51 let time_stopped = None;
52 let on_battery_since = None;
53
54 Self {
55 toggle_inspect_node,
56 cobalt_proxy,
57 current_state,
58 time_started,
59 time_stopped,
60 on_battery_since,
61 }
62 }
63
64 pub async fn handle_toggle_event(&mut self, event_type: ClientConnectionsToggleEvent) {
65 inspect_log!(self.toggle_inspect_node, {
67 event_type: std::format!("{:?}", event_type)
68 });
69
70 let mut metric_events = vec![];
71 let now = fasync::BootInstant::now();
72 match &event_type {
73 ClientConnectionsToggleEvent::Enabled => {
74 if self.current_state != Some(ClientConnectionsToggleEvent::Enabled) {
76 self.time_started = Some(now);
77
78 metric_events.push(MetricEvent {
79 metric_id: metrics::CLIENT_CONNECTION_ENABLED_OCCURRENCE_METRIC_ID,
80 event_codes: vec![],
81 payload: MetricEventPayload::Count(1),
82 });
83 }
84
85 if self.current_state == Some(ClientConnectionsToggleEvent::Disabled) {
88 if let Some(time_stopped) = self.time_stopped {
89 if now - time_stopped < TIME_QUICK_TOGGLE_WIFI {
90 metric_events.push(MetricEvent {
91 metric_id: metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID,
92 event_codes: vec![],
93 payload: MetricEventPayload::Count(1),
94 });
95 }
96 }
97 }
98 }
99 ClientConnectionsToggleEvent::Disabled => {
100 if self.current_state == Some(ClientConnectionsToggleEvent::Enabled) {
102 self.time_stopped = Some(now);
103
104 if let Some(time_started) = self.time_started {
105 let duration = now - time_started;
106 metric_events.push(MetricEvent {
107 metric_id: metrics::CLIENT_CONNECTION_ENABLED_DURATION_METRIC_ID,
108 event_codes: vec![],
109 payload: MetricEventPayload::IntegerValue(duration.into_millis()),
110 });
111
112 if let Some(on_battery_since) = self.on_battery_since {
121 let duration = now - max(time_started, on_battery_since);
124 metric_events.push(MetricEvent {
125 metric_id: metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID,
126 event_codes: vec![],
127 payload: MetricEventPayload::IntegerValue(duration.into_millis()),
128 });
129 }
130 }
131 }
132 }
133 }
134 self.current_state = Some(event_type);
135
136 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_toggle_events");
137 }
138
139 pub async fn handle_battery_charge_status(
140 &mut self,
141 charge_status: fidl_battery::ChargeStatus,
142 ) {
143 let mut metric_events = vec![];
144 let now = fasync::BootInstant::now();
145 let on_battery_now = matches!(charge_status, fidl_battery::ChargeStatus::Discharging);
146
147 match (self.on_battery_since, on_battery_now) {
148 (None, true) => self.on_battery_since = Some(now),
149 (Some(on_battery_since), false) => {
150 let _on_battery_since = self.on_battery_since.take();
151 if let Some(ClientConnectionsToggleEvent::Enabled) = self.current_state {
156 if let Some(time_started) = self.time_started {
157 let duration = now - max(time_started, on_battery_since);
160 metric_events.push(MetricEvent {
161 metric_id:
162 metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID,
163 event_codes: vec![],
164 payload: MetricEventPayload::IntegerValue(duration.into_millis()),
165 });
166 }
167 }
168 }
169 _ => (),
170 }
171
172 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_battery_charge_status");
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::testing::{setup_test, TestHelper};
180 use diagnostics_assertions::{assert_data_tree, AnyNumericProperty};
181 use futures::task::Poll;
182 use std::pin::pin;
183 use wlan_common::assert_variant;
184
185 #[fuchsia::test]
186 fn test_toggle_is_recorded_to_inspect() {
187 let mut test_helper = setup_test();
188 let node = test_helper.create_inspect_node("wlan_mock_node");
189 let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &node);
190
191 let event = ClientConnectionsToggleEvent::Enabled;
192 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
193
194 let event = ClientConnectionsToggleEvent::Disabled;
195 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
196
197 let event = ClientConnectionsToggleEvent::Enabled;
198 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
199
200 assert_data_tree!(@executor test_helper.exec, test_helper.inspector, root: contains {
201 wlan_mock_node: {
202 client_connections_toggle_events: {
203 "0": {
204 "event_type": "Enabled",
205 "@time": AnyNumericProperty
206 },
207 "1": {
208 "event_type": "Disabled",
209 "@time": AnyNumericProperty
210 },
211 "2": {
212 "event_type": "Enabled",
213 "@time": AnyNumericProperty
214 },
215 }
216 }
217 });
218 }
219
220 fn run_handle_toggle_event(
223 test_helper: &mut TestHelper,
224 toggle_logger: &mut ToggleLogger,
225 event: ClientConnectionsToggleEvent,
226 ) {
227 let mut test_fut = pin!(toggle_logger.handle_toggle_event(event));
228 assert_eq!(
229 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
230 Poll::Ready(())
231 );
232 }
233
234 fn run_handle_battery_charge_status(
235 test_helper: &mut TestHelper,
236 toggle_logger: &mut ToggleLogger,
237 charge_status: fidl_battery::ChargeStatus,
238 ) {
239 let mut test_fut = pin!(toggle_logger.handle_battery_charge_status(charge_status));
240 assert_eq!(
241 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
242 Poll::Ready(())
243 );
244 }
245
246 #[fuchsia::test]
247 fn test_quick_toggle_metric_is_recorded() {
248 let mut test_helper = setup_test();
249 let inspect_node = test_helper.create_inspect_node("test_stats");
250 let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
251
252 let mut test_time = fasync::MonotonicInstant::from_nanos(123);
254 let event = ClientConnectionsToggleEvent::Enabled;
255 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
256
257 test_time += fasync::MonotonicDuration::from_minutes(40);
259 test_helper.exec.set_fake_time(test_time);
260 let event = ClientConnectionsToggleEvent::Disabled;
261 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
262
263 test_time += fasync::MonotonicDuration::from_seconds(1);
264 test_helper.exec.set_fake_time(test_time);
265 let event = ClientConnectionsToggleEvent::Enabled;
266 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
267
268 let logged_metrics =
270 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
271 assert_variant!(&logged_metrics[..], [metric] => {
272 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
273 metric_id: metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID,
274 event_codes: vec![],
275 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
276 };
277 assert_eq!(metric, &expected_metric);
278 });
279 }
280
281 #[fuchsia::test]
282 fn test_quick_toggle_no_metric_is_recorded_if_not_quick() {
283 let mut test_helper = setup_test();
284 let inspect_node = test_helper.create_inspect_node("test_stats");
285 let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
286
287 let mut test_time = fasync::MonotonicInstant::from_nanos(123);
289 let event = ClientConnectionsToggleEvent::Enabled;
290 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
291
292 test_time += fasync::MonotonicDuration::from_minutes(20);
294 test_helper.exec.set_fake_time(test_time);
295 let event = ClientConnectionsToggleEvent::Disabled;
296 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
297
298 test_time += fasync::MonotonicDuration::from_minutes(30);
299 test_helper.exec.set_fake_time(test_time);
300 let event = ClientConnectionsToggleEvent::Enabled;
301 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
302
303 let logged_metrics =
306 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
307 assert!(logged_metrics.is_empty());
308 }
309
310 #[fuchsia::test]
311 fn test_quick_toggle_metric_second_disable_doesnt_update_time() {
312 let mut test_helper = setup_test();
315 let inspect_node = test_helper.create_inspect_node("test_stats");
316 let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
317
318 let mut test_time = fasync::MonotonicInstant::from_nanos(123);
320 let event = ClientConnectionsToggleEvent::Enabled;
321 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
322
323 test_time += fasync::MonotonicDuration::from_minutes(40);
325 test_helper.exec.set_fake_time(test_time);
326 let event = ClientConnectionsToggleEvent::Disabled;
327 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
328
329 test_time += fasync::MonotonicDuration::from_minutes(30);
330 test_helper.exec.set_fake_time(test_time);
331 let event = ClientConnectionsToggleEvent::Disabled;
332 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
333
334 test_time += fasync::MonotonicDuration::from_seconds(1);
336 test_helper.exec.set_fake_time(test_time);
337 let event = ClientConnectionsToggleEvent::Enabled;
338 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
339
340 let logged_metrics =
343 test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
344 assert!(logged_metrics.is_empty());
345 }
346
347 #[fuchsia::test]
348 fn test_log_client_connection_enabled() {
349 let mut test_helper = setup_test();
350 let inspect_node = test_helper.create_inspect_node("test_stats");
351 let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &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 = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
395
396 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
398 let event = ClientConnectionsToggleEvent::Enabled;
399 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
400
401 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(30_000_000));
403 let charge_status = fidl_battery::ChargeStatus::Discharging;
404 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
405
406 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(100_000_000));
409 let event = ClientConnectionsToggleEvent::Disabled;
410 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
411
412 let metrics = test_helper
413 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
414 assert_eq!(metrics.len(), 1);
415 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(70));
416
417 test_helper.clear_cobalt_events();
418 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(110_000_000));
420 let event = ClientConnectionsToggleEvent::Enabled;
421 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
422 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(150_000_000));
423 let event = ClientConnectionsToggleEvent::Disabled;
424 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
425
426 let metrics = test_helper
428 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
429 assert_eq!(metrics.len(), 1);
430 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(40));
431 }
432
433 #[fuchsia::test]
434 fn test_log_client_connection_enabled_duration_on_battery_with_repeated_battery_change() {
435 let mut test_helper = setup_test();
436 let inspect_node = test_helper.create_inspect_node("test_stats");
437 let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
438
439 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
441 let event = ClientConnectionsToggleEvent::Enabled;
442 run_handle_toggle_event(&mut test_helper, &mut toggle_logger, event);
443
444 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(30_000_000));
446 let charge_status = fidl_battery::ChargeStatus::Discharging;
447 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
448
449 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(100_000_000));
452 let charge_status = fidl_battery::ChargeStatus::Charging;
453 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
454
455 let metrics = test_helper
456 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
457 assert_eq!(metrics.len(), 1);
458 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(70));
459
460 test_helper.clear_cobalt_events();
461 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(110_000_000));
463 let charge_status = fidl_battery::ChargeStatus::Discharging;
464 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
465 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(150_000_000));
466 let charge_status = fidl_battery::ChargeStatus::Charging;
467 run_handle_battery_charge_status(&mut test_helper, &mut toggle_logger, charge_status);
468
469 let metrics = test_helper
471 .get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_ON_BATTERY_METRIC_ID);
472 assert_eq!(metrics.len(), 1);
473 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(40));
474 }
475}