Skip to main content

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::{Context as _, Error, format_err};
5use fidl_fuchsia_power_battery as fidl_battery;
6use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
7use fidl_fuchsia_wlan_internal as fidl_internal;
8use fuchsia_async as fasync;
9use fuchsia_inspect::Node as InspectNode;
10use futures::channel::mpsc;
11use futures::{Future, StreamExt, select};
12use log::error;
13use std::boxed::Box;
14use windowed_stats::experimental::inspect::TimeMatrixClient;
15use wlan_common::bss::BssDescription;
16use wlan_legacy_metrics_registry as metrics;
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        result: Result<(), ()>,
65    },
66    SmeTimeout,
67    ChipPowerUpFailure,
68    ResetTxPowerScenario,
69    SetTxPowerScenario {
70        scenario: fidl_internal::TxPowerScenario,
71    },
72}
73
74/// Attempts to connect to the Cobalt service.
75pub async fn setup_cobalt_proxy()
76-> Result<fidl_fuchsia_metrics::MetricEventLoggerProxy, anyhow::Error> {
77    let cobalt_svc = fuchsia_component::client::connect_to_protocol::<
78        fidl_fuchsia_metrics::MetricEventLoggerFactoryMarker,
79    >()
80    .context("failed to connect to metrics service")?;
81
82    let (cobalt_proxy, cobalt_server) =
83        fidl::endpoints::create_proxy::<fidl_fuchsia_metrics::MetricEventLoggerMarker>();
84
85    let project_spec = fidl_fuchsia_metrics::ProjectSpec {
86        customer_id: Some(metrics::CUSTOMER_ID),
87        project_id: Some(metrics::PROJECT_ID),
88        ..Default::default()
89    };
90
91    match cobalt_svc.create_metric_event_logger(&project_spec, cobalt_server).await {
92        Ok(_) => Ok(cobalt_proxy),
93        Err(err) => Err(format_err!("failed to create metrics event logger: {:?}", err)),
94    }
95}
96
97/// Attempts to create a disconnected FIDL channel with types matching the Cobalt service. This
98/// allows for a fallback with a uniform code path in case of a failure to connect to Cobalt.
99pub fn setup_disconnected_cobalt_proxy()
100-> Result<fidl_fuchsia_metrics::MetricEventLoggerProxy, anyhow::Error> {
101    // Create a disconnected proxy
102    Ok(fidl::endpoints::create_proxy::<fidl_fuchsia_metrics::MetricEventLoggerMarker>().0)
103}
104
105/// How often to refresh time series stats. Also how often to request packet counters.
106const TELEMETRY_QUERY_INTERVAL: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(10);
107
108pub fn serve_telemetry(
109    cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
110    monitor_svc_proxy: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy,
111    inspect_node: InspectNode,
112    inspect_path: &str,
113) -> (TelemetrySender, impl Future<Output = Result<(), Error>> + use<>) {
114    let (sender, mut receiver) =
115        mpsc::channel::<TelemetryEvent>(util::sender::TELEMETRY_EVENT_BUFFER_SIZE);
116    let sender = TelemetrySender::new(sender);
117
118    // Inspect nodes to hold time series and metadata for other nodes
119    const METADATA_NODE_NAME: &str = "metadata";
120    let inspect_metadata_node = inspect_node.create_child(METADATA_NODE_NAME);
121    let inspect_metadata_path = format!("{inspect_path}/{METADATA_NODE_NAME}");
122    let inspect_time_series_node = inspect_node.create_child("time_series");
123    let driver_specific_time_series_node = inspect_time_series_node.create_child("driver_specific");
124    let driver_counters_time_series_node =
125        driver_specific_time_series_node.create_child("counters");
126    let driver_gauges_time_series_node = driver_specific_time_series_node.create_child("gauges");
127
128    let time_matrix_client = TimeMatrixClient::new(inspect_time_series_node.clone_weak());
129    let driver_counters_time_series_client =
130        TimeMatrixClient::new(driver_counters_time_series_node.clone_weak());
131    let driver_gauges_time_series_client =
132        TimeMatrixClient::new(driver_gauges_time_series_node.clone_weak());
133
134    // Create and initialize modules
135    let connect_disconnect = processors::connect_disconnect::ConnectDisconnectLogger::new(
136        cobalt_proxy.clone(),
137        &inspect_node,
138        &inspect_metadata_node,
139        &inspect_metadata_path,
140        &time_matrix_client,
141    );
142    let iface_logger = processors::iface::IfaceLogger::new(cobalt_proxy.clone());
143    let power_logger = processors::power::PowerLogger::new(cobalt_proxy.clone(), &inspect_node);
144    let recovery_logger = processors::recovery::RecoveryLogger::new(cobalt_proxy.clone());
145    let mut scan_logger =
146        processors::scan::ScanLogger::new(cobalt_proxy.clone(), &time_matrix_client);
147    let sme_timeout_logger = processors::sme_timeout::SmeTimeoutLogger::new(cobalt_proxy.clone());
148    let mut toggle_logger =
149        processors::toggle_events::ToggleLogger::new(cobalt_proxy.clone(), &inspect_node);
150    let tx_power_scenario_logger =
151        processors::tx_power_scenario::TxPowerScenarioLogger::new(cobalt_proxy.clone());
152
153    let client_iface_counters_logger =
154        processors::client_iface_counters::ClientIfaceCountersLogger::new(
155            cobalt_proxy,
156            monitor_svc_proxy,
157            &inspect_metadata_node,
158            &inspect_metadata_path,
159            &time_matrix_client,
160            driver_counters_time_series_client,
161            driver_gauges_time_series_client,
162        );
163
164    let fut = async move {
165        // Prevent the inspect nodes from being dropped while the loop is running.
166        let _inspect_node = inspect_node;
167        let _inspect_metadata_node = inspect_metadata_node;
168        let _inspect_time_series_node = inspect_time_series_node;
169        let _driver_specific_time_series_node = driver_specific_time_series_node;
170        let _driver_counters_time_series_node = driver_counters_time_series_node;
171        let _driver_gauges_time_series_node = driver_gauges_time_series_node;
172
173        let mut telemetry_interval = fasync::Interval::new(TELEMETRY_QUERY_INTERVAL);
174        loop {
175            select! {
176                event = receiver.next() => {
177                    let Some(event) = event else {
178                        error!("Telemetry event stream unexpectedly terminated.");
179                        return Err(format_err!("Telemetry event stream unexpectedly terminated."));
180                    };
181                    use TelemetryEvent::*;
182                    match event {
183                        ConnectResult { result, bss } => {
184                            connect_disconnect.handle_connect_attempt(result, &bss).await;
185                        }
186                        Disconnect { info } => {
187                            connect_disconnect.log_disconnect(&info).await;
188                            power_logger.handle_iface_disconnect(info.iface_id).await;
189                        }
190                        ClientConnectionsToggle { event } => {
191                            connect_disconnect.handle_client_connections_toggle(&event).await;
192                            toggle_logger.handle_toggle_event(event).await;
193                        }
194                        ClientIfaceCreated { iface_id } => {
195                            client_iface_counters_logger.handle_iface_created(iface_id).await;
196                        }
197                        ClientIfaceDestroyed { iface_id } => {
198                            connect_disconnect.handle_iface_destroyed().await;
199                            client_iface_counters_logger.handle_iface_destroyed(iface_id).await;
200                            power_logger.handle_iface_destroyed(iface_id).await;
201                        }
202                        IfaceCreationFailure => {
203                            iface_logger.handle_iface_creation_failure().await;
204                        }
205                        IfaceDestructionFailure => {
206                            iface_logger.handle_iface_destruction_failure().await;
207                        }
208                        ScanStart => {
209                            scan_logger.handle_scan_start().await;
210                        }
211                        ScanResult { result } => {
212                            scan_logger.handle_scan_result(result).await;
213                        }
214                        IfacePowerLevelChanged { iface_power_level, iface_id } => {
215                            power_logger.log_iface_power_event(iface_power_level, iface_id).await;
216                        }
217                        // TODO(b/340921554): either watch for suspension directly in the library,
218                        // or plumb this from callers once suspend mechanisms are integrated
219                        SuspendImminent => {
220                            power_logger.handle_suspend_imminent().await;
221                            connect_disconnect.handle_suspend_imminent().await;
222                        }
223                        UnclearPowerDemand(demand) => {
224                            power_logger.handle_unclear_power_demand(demand).await;
225                        }
226                        ChipPowerUpFailure => {
227                            power_logger.handle_chip_power_up_failure().await;
228                        }
229                        BatteryChargeStatus(charge_status) => {
230                            scan_logger.handle_battery_charge_status(charge_status).await;
231                            toggle_logger.handle_battery_charge_status(charge_status).await;
232                        }
233                        RecoveryEvent { result } => {
234                            recovery_logger.handle_recovery_event(result).await;
235                        }
236                        SmeTimeout => {
237                            sme_timeout_logger.handle_sme_timeout_event().await;
238                        }
239                        ResetTxPowerScenario => {
240                            tx_power_scenario_logger.handle_sar_reset().await;
241                        }
242                        SetTxPowerScenario {scenario} => {
243                            tx_power_scenario_logger.handle_set_sar(scenario).await;
244                        }
245                    }
246                }
247                _ = telemetry_interval.next() => {
248                    connect_disconnect.handle_periodic_telemetry().await;
249                    client_iface_counters_logger.handle_periodic_telemetry().await;
250                }
251            }
252        }
253    };
254    (sender, fut)
255}