wlan_telemetry/
lib.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.
4use anyhow::{format_err, Context as _, Error};
5use fuchsia_inspect::Node as InspectNode;
6use futures::channel::mpsc;
7use futures::{future, select, Future, StreamExt, TryFutureExt};
8use log::error;
9use std::boxed::Box;
10use windowed_stats::experimental::serve::serve_time_matrix_inspection;
11use wlan_common::bss::BssDescription;
12use {
13    fidl_fuchsia_power_battery as fidl_battery, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
14    fuchsia_async as fasync, fuchsia_inspect_auto_persist as auto_persist,
15    wlan_legacy_metrics_registry as metrics,
16};
17
18mod processors;
19pub(crate) mod util;
20pub use crate::processors::connect_disconnect::DisconnectInfo;
21pub use crate::processors::power::{IfacePowerLevel, UnclearPowerDemand};
22pub use crate::processors::scan::ScanResult;
23pub use crate::processors::toggle_events::ClientConnectionsToggleEvent;
24pub use util::sender::TelemetrySender;
25#[cfg(test)]
26mod testing;
27
28#[derive(Debug)]
29pub enum TelemetryEvent {
30    ConnectResult {
31        result: fidl_ieee80211::StatusCode,
32        bss: Box<BssDescription>,
33    },
34    Disconnect {
35        info: DisconnectInfo,
36    },
37    // We should maintain docstrings if we can see any possibility of ambiguity for an enum
38    /// Client connections enabled or disabled
39    ClientConnectionsToggle {
40        event: ClientConnectionsToggleEvent,
41    },
42    ClientIfaceCreated {
43        iface_id: u16,
44    },
45    ClientIfaceDestroyed {
46        iface_id: u16,
47    },
48    IfaceCreationFailure,
49    IfaceDestructionFailure,
50    ScanStart,
51    ScanResult {
52        result: ScanResult,
53    },
54    IfacePowerLevelChanged {
55        iface_power_level: IfacePowerLevel,
56        iface_id: u16,
57    },
58    /// System suspension imminent
59    SuspendImminent,
60    /// Unclear power level requested by policy layer
61    UnclearPowerDemand(UnclearPowerDemand),
62    BatteryChargeStatus(fidl_battery::ChargeStatus),
63    RecoveryEvent,
64    SmeTimeout,
65}
66
67/// Attempts to connect to the Cobalt service.
68pub async fn setup_cobalt_proxy(
69) -> Result<fidl_fuchsia_metrics::MetricEventLoggerProxy, anyhow::Error> {
70    let cobalt_svc = fuchsia_component::client::connect_to_protocol::<
71        fidl_fuchsia_metrics::MetricEventLoggerFactoryMarker,
72    >()
73    .context("failed to connect to metrics service")?;
74
75    let (cobalt_proxy, cobalt_server) =
76        fidl::endpoints::create_proxy::<fidl_fuchsia_metrics::MetricEventLoggerMarker>();
77
78    let project_spec = fidl_fuchsia_metrics::ProjectSpec {
79        customer_id: Some(metrics::CUSTOMER_ID),
80        project_id: Some(metrics::PROJECT_ID),
81        ..Default::default()
82    };
83
84    match cobalt_svc.create_metric_event_logger(&project_spec, cobalt_server).await {
85        Ok(_) => Ok(cobalt_proxy),
86        Err(err) => Err(format_err!("failed to create metrics event logger: {:?}", err)),
87    }
88}
89
90/// Attempts to create a disconnected FIDL channel with types matching the Cobalt service. This
91/// allows for a fallback with a uniform code path in case of a failure to connect to Cobalt.
92pub fn setup_disconnected_cobalt_proxy(
93) -> Result<fidl_fuchsia_metrics::MetricEventLoggerProxy, anyhow::Error> {
94    // Create a disconnected proxy
95    Ok(fidl::endpoints::create_proxy::<fidl_fuchsia_metrics::MetricEventLoggerMarker>().0)
96}
97
98pub fn setup_persistence_req_sender(
99) -> Result<(auto_persist::PersistenceReqSender, impl Future<Output = ()>), anyhow::Error> {
100    fuchsia_component::client::connect_to_protocol::<
101        fidl_fuchsia_diagnostics_persist::DataPersistenceMarker,
102    >()
103    .map(auto_persist::create_persistence_req_sender)
104}
105
106/// Creates a disconnected channel with the same types as the persistence service. This allows for
107/// a fallback with a uniform code path in case of a failure to connect to the persistence service.
108pub fn setup_disconnected_persistence_req_sender() -> auto_persist::PersistenceReqSender {
109    let (sender, _receiver) = mpsc::channel::<String>(1);
110    // Note: because we drop the receiver here, be careful about log spam when sending
111    // tags through the `sender` below. This is automatically handled by `auto_persist::AutoPersist`
112    // because it only logs the first time sending fails, so just use that wrapper type instead of
113    // writing directly to this channel.
114    sender
115}
116
117/// How often to refresh time series stats. Also how often to request packet counters.
118const TELEMETRY_QUERY_INTERVAL: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(10);
119
120pub fn serve_telemetry(
121    cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
122    monitor_svc_proxy: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy,
123    inspect_node: InspectNode,
124    inspect_path: &str,
125    persistence_req_sender: auto_persist::PersistenceReqSender,
126) -> (TelemetrySender, impl Future<Output = Result<(), Error>>) {
127    let (sender, mut receiver) =
128        mpsc::channel::<TelemetryEvent>(util::sender::TELEMETRY_EVENT_BUFFER_SIZE);
129    let sender = TelemetrySender::new(sender);
130
131    // Inspect nodes to hold time series and metadata for other nodes
132    const METADATA_NODE_NAME: &str = "metadata";
133    let inspect_metadata_node = inspect_node.create_child(METADATA_NODE_NAME);
134    let inspect_metadata_path = format!("{inspect_path}/{METADATA_NODE_NAME}");
135    let inspect_time_series_node = inspect_node.create_child("time_series");
136    let driver_specific_time_series_node = inspect_time_series_node.create_child("driver_specific");
137    let driver_counters_time_series_node =
138        driver_specific_time_series_node.create_child("counters");
139    let driver_gauges_time_series_node = driver_specific_time_series_node.create_child("gauges");
140
141    let (time_matrix_client, time_series_fut) =
142        serve_time_matrix_inspection(inspect_time_series_node);
143    let (driver_counters_time_series_client, driver_counters_time_series_fut) =
144        serve_time_matrix_inspection(driver_counters_time_series_node);
145    let (driver_gauges_time_series_client, driver_gauges_time_series_fut) =
146        serve_time_matrix_inspection(driver_gauges_time_series_node);
147
148    // Create and initialize modules
149    let connect_disconnect = processors::connect_disconnect::ConnectDisconnectLogger::new(
150        cobalt_proxy.clone(),
151        &inspect_node,
152        &inspect_metadata_node,
153        &inspect_metadata_path,
154        persistence_req_sender,
155        &time_matrix_client,
156    );
157    let iface_logger = processors::iface::IfaceLogger::new(cobalt_proxy.clone());
158    let power_logger = processors::power::PowerLogger::new(cobalt_proxy.clone(), &inspect_node);
159    let recovery_logger = processors::recovery::RecoveryLogger::new(cobalt_proxy.clone());
160    let mut scan_logger = processors::scan::ScanLogger::new(cobalt_proxy.clone());
161    let sme_timeout_logger = processors::sme_timeout::SmeTimeoutLogger::new(cobalt_proxy.clone());
162    let mut toggle_logger =
163        processors::toggle_events::ToggleLogger::new(cobalt_proxy.clone(), &inspect_node);
164
165    let client_iface_counters_logger =
166        processors::client_iface_counters::ClientIfaceCountersLogger::new(
167            cobalt_proxy,
168            monitor_svc_proxy,
169            &inspect_metadata_node,
170            &inspect_metadata_path,
171            &time_matrix_client,
172            driver_counters_time_series_client,
173            driver_gauges_time_series_client,
174        );
175
176    let fut = async move {
177        // Prevent the inspect nodes from being dropped while the loop is running.
178        let _inspect_node = inspect_node;
179        let _inspect_metadata_node = inspect_metadata_node;
180        let _driver_specific_time_series_node = driver_specific_time_series_node;
181
182        let mut telemetry_interval = fasync::Interval::new(TELEMETRY_QUERY_INTERVAL);
183        loop {
184            select! {
185                event = receiver.next() => {
186                    let Some(event) = event else {
187                        error!("Telemetry event stream unexpectedly terminated.");
188                        return Err(format_err!("Telemetry event stream unexpectedly terminated."));
189                    };
190                    use TelemetryEvent::*;
191                    match event {
192                        ConnectResult { result, bss } => {
193                            connect_disconnect.handle_connect_attempt(result, &bss).await;
194                        }
195                        Disconnect { info } => {
196                            connect_disconnect.log_disconnect(&info).await;
197                            power_logger.handle_iface_disconnect(info.iface_id).await;
198                        }
199                        ClientConnectionsToggle { event } => {
200                            toggle_logger.handle_toggle_event(event).await;
201                        }
202                        ClientIfaceCreated { iface_id } => {
203                            client_iface_counters_logger.handle_iface_created(iface_id).await;
204                        }
205                        ClientIfaceDestroyed { iface_id } => {
206                            client_iface_counters_logger.handle_iface_destroyed(iface_id).await;
207                            power_logger.handle_iface_destroyed(iface_id).await;
208                        }
209                        IfaceCreationFailure => {
210                            iface_logger.handle_iface_creation_failure().await;
211                        }
212                        IfaceDestructionFailure => {
213                            iface_logger.handle_iface_destruction_failure().await;
214                        }
215                        ScanStart => {
216                            scan_logger.handle_scan_start().await;
217                        }
218                        ScanResult { result } => {
219                            scan_logger.handle_scan_result(result).await;
220                        }
221                        IfacePowerLevelChanged { iface_power_level, iface_id } => {
222                            power_logger.log_iface_power_event(iface_power_level, iface_id).await;
223                        }
224                        // TODO(b/340921554): either watch for suspension directly in the library,
225                        // or plumb this from callers once suspend mechanisms are integrated
226                        SuspendImminent => {
227                            power_logger.handle_suspend_imminent().await;
228                            connect_disconnect.handle_suspend_imminent().await;
229                        }
230                        UnclearPowerDemand(demand) => {
231                            power_logger.handle_unclear_power_demand(demand).await;
232                        }
233                        BatteryChargeStatus(charge_status) => {
234                            scan_logger.handle_battery_charge_status(charge_status).await;
235                            toggle_logger.handle_battery_charge_status(charge_status).await;
236                        }
237                        RecoveryEvent => {
238                            recovery_logger.handle_recovery_event().await;
239                        }
240                        SmeTimeout => {
241                            sme_timeout_logger.handle_sme_timeout_event().await;
242                        }
243                    }
244                }
245                _ = telemetry_interval.next() => {
246                    connect_disconnect.handle_periodic_telemetry().await;
247                    client_iface_counters_logger.handle_periodic_telemetry().await;
248                }
249            }
250        }
251    };
252    let fut = future::try_join4(
253        fut,
254        time_series_fut,
255        driver_counters_time_series_fut,
256        driver_gauges_time_series_fut,
257    )
258    .map_ok(|((), (), (), ())| ());
259    (sender, fut)
260}