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