wlan_telemetry/processors/
toggle_events.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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 {fuchsia_async as fasync, wlan_legacy_metrics_registry as metrics, zx};
11
12pub const INSPECT_TOGGLE_EVENTS_LIMIT: usize = 20;
13const TIME_QUICK_TOGGLE_WIFI: zx::BootDuration = zx::BootDuration::from_seconds(5);
14
15#[derive(Debug, PartialEq)]
16pub enum ClientConnectionsToggleEvent {
17    Enabled,
18    Disabled,
19}
20
21pub struct ToggleLogger {
22    toggle_inspect_node: BoundedListNode,
23    cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
24    /// This is None until telemetry is notified of an off or on event, since these metrics don't
25    /// currently need to know the starting state.
26    current_state: Option<ClientConnectionsToggleEvent>,
27    /// The last time wlan was toggled on
28    time_started: Option<fasync::BootInstant>,
29    /// The last time wlan was toggled off, or None if it hasn't been. Used to determine if WLAN
30    /// was turned on right after being turned off.
31    time_stopped: Option<fasync::BootInstant>,
32}
33
34impl ToggleLogger {
35    pub fn new(
36        cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
37        inspect_node: &InspectNode,
38    ) -> Self {
39        // Initialize inspect children
40        let toggle_events = inspect_node.create_child("client_connections_toggle_events");
41        let toggle_inspect_node = BoundedListNode::new(toggle_events, INSPECT_TOGGLE_EVENTS_LIMIT);
42        let current_state = None;
43        let time_started = None;
44        let time_stopped = None;
45
46        Self { toggle_inspect_node, cobalt_proxy, current_state, time_started, time_stopped }
47    }
48
49    pub async fn log_toggle_event(&mut self, event_type: ClientConnectionsToggleEvent) {
50        // This inspect macro logs the time as well
51        inspect_log!(self.toggle_inspect_node, {
52            event_type: std::format!("{:?}", event_type)
53        });
54
55        let mut metric_events = vec![];
56        let now = fasync::BootInstant::now();
57        match &event_type {
58            ClientConnectionsToggleEvent::Enabled => {
59                // Log an occurrence if the client connection was not already enabled
60                if self.current_state != Some(ClientConnectionsToggleEvent::Enabled) {
61                    self.time_started = Some(now);
62
63                    metric_events.push(MetricEvent {
64                        metric_id: metrics::CLIENT_CONNECTION_ENABLED_OCCURRENCE_METRIC_ID,
65                        event_codes: vec![],
66                        payload: MetricEventPayload::Count(1),
67                    });
68                }
69
70                // If connections were just disabled before this, log a metric for the quick wifi
71                // restart.
72                if self.current_state == Some(ClientConnectionsToggleEvent::Disabled) {
73                    if let Some(time_stopped) = self.time_stopped {
74                        if now - time_stopped < TIME_QUICK_TOGGLE_WIFI {
75                            metric_events.push(MetricEvent {
76                                metric_id: metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID,
77                                event_codes: vec![],
78                                payload: MetricEventPayload::Count(1),
79                            });
80                        }
81                    }
82                }
83            }
84            ClientConnectionsToggleEvent::Disabled => {
85                // Only change the time and log duration if connections were not already disabled.
86                if self.current_state == Some(ClientConnectionsToggleEvent::Enabled) {
87                    self.time_stopped = Some(now);
88
89                    if let Some(time_started) = self.time_started {
90                        let duration = now - time_started;
91                        metric_events.push(MetricEvent {
92                            metric_id: metrics::CLIENT_CONNECTION_ENABLED_DURATION_METRIC_ID,
93                            event_codes: vec![],
94                            payload: MetricEventPayload::IntegerValue(duration.into_millis()),
95                        });
96                    }
97                }
98            }
99        }
100        self.current_state = Some(event_type);
101
102        if !metric_events.is_empty() {
103            log_cobalt_batch!(self.cobalt_proxy, &metric_events, "log_toggle_events");
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::testing::{setup_test, TestHelper};
112    use diagnostics_assertions::{assert_data_tree, AnyNumericProperty};
113    use futures::task::Poll;
114    use std::pin::pin;
115    use wlan_common::assert_variant;
116
117    #[fuchsia::test]
118    fn test_toggle_is_recorded_to_inspect() {
119        let mut test_helper = setup_test();
120        let node = test_helper.create_inspect_node("wlan_mock_node");
121        let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &node);
122
123        let event = ClientConnectionsToggleEvent::Enabled;
124        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
125
126        let event = ClientConnectionsToggleEvent::Disabled;
127        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
128
129        let event = ClientConnectionsToggleEvent::Enabled;
130        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
131
132        assert_data_tree!(test_helper.inspector, root: contains {
133            wlan_mock_node: {
134                client_connections_toggle_events: {
135                    "0": {
136                        "event_type": "Enabled",
137                        "@time": AnyNumericProperty
138                    },
139                    "1": {
140                        "event_type": "Disabled",
141                        "@time": AnyNumericProperty
142                    },
143                    "2": {
144                        "event_type": "Enabled",
145                        "@time": AnyNumericProperty
146                    },
147                }
148            }
149        });
150    }
151
152    // Uses the test helper to run toggle_logger.log_toggle_event so that any cobalt metrics sent
153    // will be acked and not block anything. It expects no response from the log_toggle_event.
154    fn run_log_toggle_event(
155        test_helper: &mut TestHelper,
156        toggle_logger: &mut ToggleLogger,
157        event: ClientConnectionsToggleEvent,
158    ) {
159        let mut test_fut = pin!(toggle_logger.log_toggle_event(event));
160        assert_eq!(
161            test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
162            Poll::Ready(())
163        );
164    }
165
166    #[fuchsia::test]
167    fn test_quick_toggle_metric_is_recorded() {
168        let mut test_helper = setup_test();
169        let inspect_node = test_helper.create_inspect_node("test_stats");
170        let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
171
172        // Start with client connections enabled.
173        let mut test_time = fasync::MonotonicInstant::from_nanos(123);
174        let event = ClientConnectionsToggleEvent::Enabled;
175        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
176
177        // Stop client connections and quickly start them again.
178        test_time += fasync::MonotonicDuration::from_minutes(40);
179        test_helper.exec.set_fake_time(test_time);
180        let event = ClientConnectionsToggleEvent::Disabled;
181        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
182
183        test_time += fasync::MonotonicDuration::from_seconds(1);
184        test_helper.exec.set_fake_time(test_time);
185        let event = ClientConnectionsToggleEvent::Enabled;
186        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
187
188        // Check that a metric is logged for the quick stop and start.
189        let logged_metrics =
190            test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
191        assert_variant!(&logged_metrics[..], [metric] => {
192            let expected_metric = fidl_fuchsia_metrics::MetricEvent {
193                metric_id: metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID,
194                event_codes: vec![],
195                payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
196            };
197            assert_eq!(metric, &expected_metric);
198        });
199    }
200
201    #[fuchsia::test]
202    fn test_quick_toggle_no_metric_is_recorded_if_not_quick() {
203        let mut test_helper = setup_test();
204        let inspect_node = test_helper.create_inspect_node("test_stats");
205        let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
206
207        // Start with client connections enabled.
208        let mut test_time = fasync::MonotonicInstant::from_nanos(123);
209        let event = ClientConnectionsToggleEvent::Enabled;
210        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
211
212        // Stop client connections and a while later start them again.
213        test_time += fasync::MonotonicDuration::from_minutes(20);
214        test_helper.exec.set_fake_time(test_time);
215        let event = ClientConnectionsToggleEvent::Disabled;
216        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
217
218        test_time += fasync::MonotonicDuration::from_minutes(30);
219        test_helper.exec.set_fake_time(test_time);
220        let event = ClientConnectionsToggleEvent::Enabled;
221        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
222
223        // Check that no metric is logged for quick toggles since there was a while between the
224        // stop and start.
225        let logged_metrics =
226            test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
227        assert!(logged_metrics.is_empty());
228    }
229
230    #[fuchsia::test]
231    fn test_quick_toggle_metric_second_disable_doesnt_update_time() {
232        // Verify that if two consecutive disables happen, only the first is used to determine
233        // quick toggles since the following ones don't change the state.
234        let mut test_helper = setup_test();
235        let inspect_node = test_helper.create_inspect_node("test_stats");
236        let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
237
238        // Start with client connections enabled.
239        let mut test_time = fasync::MonotonicInstant::from_nanos(123);
240        let event = ClientConnectionsToggleEvent::Enabled;
241        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
242
243        // Stop client connections and a while later stop them again.
244        test_time += fasync::MonotonicDuration::from_minutes(40);
245        test_helper.exec.set_fake_time(test_time);
246        let event = ClientConnectionsToggleEvent::Disabled;
247        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
248
249        test_time += fasync::MonotonicDuration::from_minutes(30);
250        test_helper.exec.set_fake_time(test_time);
251        let event = ClientConnectionsToggleEvent::Disabled;
252        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
253
254        // Start client connections right after the last disable message.
255        test_time += fasync::MonotonicDuration::from_seconds(1);
256        test_helper.exec.set_fake_time(test_time);
257        let event = ClientConnectionsToggleEvent::Enabled;
258        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
259
260        // Check that no metric is logged since the enable message came a while after the first
261        // disable message.
262        let logged_metrics =
263            test_helper.get_logged_metrics(metrics::CLIENT_CONNECTIONS_STOP_AND_START_METRIC_ID);
264        assert!(logged_metrics.is_empty());
265    }
266
267    #[fuchsia::test]
268    fn test_log_client_connection_enabled() {
269        let mut test_helper = setup_test();
270        let inspect_node = test_helper.create_inspect_node("test_stats");
271        let mut toggle_logger = ToggleLogger::new(test_helper.cobalt_proxy.clone(), &inspect_node);
272
273        // Start with client connections enabled.
274        test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000));
275        let event = ClientConnectionsToggleEvent::Enabled;
276        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
277
278        let metrics =
279            test_helper.get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_OCCURRENCE_METRIC_ID);
280        assert_eq!(metrics.len(), 1);
281        assert_eq!(metrics[0].payload, MetricEventPayload::Count(1));
282
283        // Send enabled event again. This should not log any metric because the device was not
284        // in a disabled state
285        test_helper.clear_cobalt_events();
286        test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(50_000_000));
287        let event = ClientConnectionsToggleEvent::Enabled;
288        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
289
290        let metrics =
291            test_helper.get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_OCCURRENCE_METRIC_ID);
292        assert!(metrics.is_empty());
293
294        // Send disabled event. This should log the duration between now and when the
295        // first enabled event was sent.
296        test_helper.clear_cobalt_events();
297        test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(100_000_000));
298        let event = ClientConnectionsToggleEvent::Disabled;
299        run_log_toggle_event(&mut test_helper, &mut toggle_logger, event);
300
301        let metrics =
302            test_helper.get_logged_metrics(metrics::CLIENT_CONNECTION_ENABLED_DURATION_METRIC_ID);
303        assert_eq!(metrics.len(), 1);
304        assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(90));
305    }
306}