wlan_telemetry/processors/
power.rs1use crate::util::cobalt_logger::log_cobalt;
5use cobalt_client::traits::AsEventCode;
6use fuchsia_inspect::Node as InspectNode;
7use fuchsia_inspect_contrib::inspect_log;
8use fuchsia_inspect_contrib::nodes::BoundedListNode;
9use futures::lock::Mutex;
10use std::collections::HashMap;
11use std::sync::Arc;
12use wlan_legacy_metrics_registry as metrics;
13
14pub const INSPECT_POWER_EVENTS_LIMIT: usize = 20;
15
16#[derive(Debug, PartialEq)]
17pub enum IfacePowerLevel {
18 Disconnected,
19 SuspendMode,
20 Normal,
21 NoPowerSavings,
22}
23
24#[derive(Debug)]
25pub enum UnclearPowerDemand {
26 PowerSaveRequestedWhileSuspendModeEnabled,
27}
28
29pub struct PowerLogger {
30 power_inspect_node: Mutex<BoundedListNode>,
31 cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
32 iface_power_states: Arc<Mutex<HashMap<u16, IfacePowerLevel>>>,
33}
34
35impl PowerLogger {
36 pub fn new(
37 cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
38 inspect_node: &InspectNode,
39 ) -> Self {
40 let iface_power_events = inspect_node.create_child("iface_power_events");
42 let power_inspect_node: BoundedListNode =
43 BoundedListNode::new(iface_power_events, INSPECT_POWER_EVENTS_LIMIT);
44
45 Self {
46 power_inspect_node: Mutex::new(power_inspect_node),
47 cobalt_proxy,
48 iface_power_states: Arc::new(Mutex::new(HashMap::new())),
49 }
50 }
51
52 pub async fn log_iface_power_event(&self, iface_power_level: IfacePowerLevel, iface_id: u16) {
53 inspect_log!(self.power_inspect_node.lock().await, {
54 power_level: std::format!("{:?}", iface_power_level),
55 iface_id: iface_id,
56 });
57 let _ = self.iface_power_states.lock().await.insert(iface_id, iface_power_level);
58 }
59
60 pub async fn handle_iface_disconnect(&self, iface_id: u16) {
61 let _ =
62 self.iface_power_states.lock().await.insert(iface_id, IfacePowerLevel::Disconnected);
63 }
64
65 pub async fn handle_iface_destroyed(&self, iface_id: u16) {
66 let _ = self.iface_power_states.lock().await.remove(&iface_id);
67 }
68
69 pub async fn handle_suspend_imminent(&self) {
70 for (_iface_id, iface_power_level) in self.iface_power_states.lock().await.iter() {
71 use metrics::PowerLevelAtSuspendMetricDimensionPowerLevel as dim;
72 log_cobalt!(
73 self.cobalt_proxy,
74 log_occurrence,
75 metrics::POWER_LEVEL_AT_SUSPEND_METRIC_ID,
76 1,
77 &[match iface_power_level {
78 IfacePowerLevel::Disconnected => dim::Disconnected,
79 IfacePowerLevel::SuspendMode => dim::SuspendMode,
80 IfacePowerLevel::Normal => dim::PowerSaveMode,
81 IfacePowerLevel::NoPowerSavings => dim::HighPerformanceMode,
82 }
83 .as_event_code()]
84 )
85 }
86 }
87
88 pub async fn handle_unclear_power_demand(&self, demand: UnclearPowerDemand) {
89 use metrics::UnclearPowerLevelDemandMetricDimensionReason as dim;
90 log_cobalt!(
91 self.cobalt_proxy,
92 log_occurrence,
93 metrics::UNCLEAR_POWER_LEVEL_DEMAND_METRIC_ID,
94 1,
95 &[match demand {
96 UnclearPowerDemand::PowerSaveRequestedWhileSuspendModeEnabled =>
97 dim::PowerSaveRequestedWhileSuspendModeEnabled,
98 }
99 .as_event_code()]
100 )
101 }
102
103 pub async fn handle_chip_power_up_failure(&self) {
104 log_cobalt!(
105 self.cobalt_proxy,
106 log_occurrence,
107 metrics::CHIP_POWER_UP_FAILURE_METRIC_ID,
108 1,
109 &[]
110 )
111 }
112
113 pub async fn chip_power_down_failure(&self) {
114 log_cobalt!(
115 self.cobalt_proxy,
116 log_occurrence,
117 metrics::STOP_FAILURE_OCCURRENCE_METRIC_ID,
118 1,
119 &[]
120 )
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::testing::setup_test;
128 use assert_matches::assert_matches;
129 use diagnostics_assertions::{AnyNumericProperty, assert_data_tree};
130 use futures::pin_mut;
131 use futures::task::Poll;
132 use std::pin::pin;
133
134 #[fuchsia::test]
135 fn test_iface_power_event_in_inspect() {
136 let mut test_helper = setup_test();
137 let node = test_helper.create_inspect_node("wlan_mock_node");
138 let power_logger = PowerLogger::new(test_helper.cobalt_proxy.clone(), &node);
139
140 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 11);
141 pin_mut!(test_fut);
142 assert_eq!(
143 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
144 Poll::Ready(())
145 );
146 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 22);
147 pin_mut!(test_fut);
148 assert_eq!(
149 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
150 Poll::Ready(())
151 );
152 let mut test_fut = pin!(power_logger.log_iface_power_event(IfacePowerLevel::Normal, 33));
153 assert_eq!(
154 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
155 Poll::Ready(())
156 );
157
158 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::SuspendMode, 11);
160 pin_mut!(test_fut);
161 assert_eq!(
162 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
163 Poll::Ready(())
164 );
165
166 assert_data_tree!(@executor test_helper.exec, test_helper.inspector, root: contains {
167 wlan_mock_node: {
168 iface_power_events: {
169 "0": {
170 "power_level": "NoPowerSavings",
171 "iface_id": 11_u64,
172 "@time": AnyNumericProperty
173 },
174 "1": {
175 "power_level": "NoPowerSavings",
176 "iface_id": 22_u64,
177 "@time": AnyNumericProperty
178 },
179 "2": {
180 "power_level": "Normal",
181 "iface_id": 33_u64,
182 "@time": AnyNumericProperty
183 },
184 "3": {
185 "power_level": "SuspendMode",
186 "iface_id": 11_u64,
187 "@time": AnyNumericProperty
188 },
189 }
190 }
191 });
192 }
193
194 #[fuchsia::test]
195 fn test_iface_power_event_adds_to_internal_hashmap() {
196 let mut test_helper = setup_test();
197 let node = test_helper.create_inspect_node("wlan_mock_node");
198 let power_logger = PowerLogger::new(test_helper.cobalt_proxy.clone(), &node);
199
200 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 11);
201 pin_mut!(test_fut);
202 assert_eq!(
203 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
204 Poll::Ready(())
205 );
206 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 22);
207 pin_mut!(test_fut);
208 assert_eq!(
209 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
210 Poll::Ready(())
211 );
212 let mut test_fut = pin!(power_logger.log_iface_power_event(IfacePowerLevel::Normal, 33));
213 assert_eq!(
214 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
215 Poll::Ready(())
216 );
217
218 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::SuspendMode, 11);
220 pin_mut!(test_fut);
221 assert_eq!(
222 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
223 Poll::Ready(())
224 );
225
226 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 3);
227 assert_eq!(
228 power_logger.iface_power_states.try_lock().unwrap().get(&11),
229 Some(&IfacePowerLevel::SuspendMode)
230 );
231 assert_eq!(
232 power_logger.iface_power_states.try_lock().unwrap().get(&22),
233 Some(&IfacePowerLevel::NoPowerSavings)
234 );
235 assert_eq!(
236 power_logger.iface_power_states.try_lock().unwrap().get(&33),
237 Some(&IfacePowerLevel::Normal)
238 );
239 }
240
241 #[fuchsia::test]
242 fn test_disconnect_updates_internal_hashmap() {
243 let mut test_helper = setup_test();
244 let node = test_helper.create_inspect_node("wlan_mock_node");
245 let power_logger = PowerLogger::new(test_helper.cobalt_proxy.clone(), &node);
246
247 let _ =
248 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
249 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
250
251 let mut test_fut = pin!(power_logger.handle_iface_disconnect(33));
252 assert_eq!(
253 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
254 Poll::Ready(())
255 );
256 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
257 assert_eq!(
258 power_logger.iface_power_states.try_lock().unwrap().get(&33),
259 Some(&IfacePowerLevel::Disconnected)
260 );
261 }
262
263 #[fuchsia::test]
264 fn test_destroy_removes_from_internal_hashmap() {
265 let mut test_helper = setup_test();
266 let node = test_helper.create_inspect_node("wlan_mock_node");
267 let power_logger = PowerLogger::new(test_helper.cobalt_proxy.clone(), &node);
268
269 let _ =
270 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
271 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
272
273 let mut test_fut = pin!(power_logger.handle_iface_destroyed(33));
274 assert_eq!(
275 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
276 Poll::Ready(())
277 );
278 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 0);
279 }
280
281 #[fuchsia::test]
282 fn test_imminent_suspension_logs_to_cobalt() {
283 let mut test_helper = setup_test();
284 let node = test_helper.create_inspect_node("wlan_mock_node");
285 let power_logger = PowerLogger::new(test_helper.cobalt_proxy.clone(), &node);
286
287 let _ =
288 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
289 let mut test_fut = pin!(power_logger.handle_suspend_imminent());
290 assert_eq!(
291 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
292 Poll::Ready(())
293 );
294
295 let logged_metrics =
296 test_helper.get_logged_metrics(metrics::POWER_LEVEL_AT_SUSPEND_METRIC_ID);
297 assert_matches!(&logged_metrics[..], [metric] => {
298 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
299 metric_id: metrics::POWER_LEVEL_AT_SUSPEND_METRIC_ID,
300 event_codes: vec![metrics::PowerLevelAtSuspendMetricDimensionPowerLevel::PowerSaveMode
301 .as_event_code()],
302 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
303 };
304 assert_eq!(metric, &expected_metric);
305 });
306 }
307
308 #[fuchsia::test]
309 fn test_unclear_power_demand_logs_to_cobalt() {
310 let mut test_helper = setup_test();
311 let node = test_helper.create_inspect_node("wlan_mock_node");
312 let power_logger = PowerLogger::new(test_helper.cobalt_proxy.clone(), &node);
313
314 let mut test_fut = pin!(power_logger.handle_unclear_power_demand(
315 UnclearPowerDemand::PowerSaveRequestedWhileSuspendModeEnabled
316 ));
317 assert_eq!(
318 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
319 Poll::Ready(())
320 );
321
322 let logged_metrics =
323 test_helper.get_logged_metrics(metrics::UNCLEAR_POWER_LEVEL_DEMAND_METRIC_ID);
324 assert_matches!(&logged_metrics[..], [metric] => {
325 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
326 metric_id: metrics::UNCLEAR_POWER_LEVEL_DEMAND_METRIC_ID,
327 event_codes: vec![metrics::UnclearPowerLevelDemandMetricDimensionReason::PowerSaveRequestedWhileSuspendModeEnabled
328 .as_event_code()],
329 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
330 };
331 assert_eq!(metric, &expected_metric);
332 });
333 }
334
335 #[fuchsia::test]
336 fn test_chip_power_up_failure_logs_to_cobalt() {
337 let mut test_helper = setup_test();
338 let node = test_helper.create_inspect_node("wlan_mock_node");
339 let power_logger = PowerLogger::new(test_helper.cobalt_proxy.clone(), &node);
340
341 let mut test_fut = pin!(power_logger.handle_chip_power_up_failure());
342 assert_eq!(
343 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
344 Poll::Ready(())
345 );
346
347 let logged_metrics =
348 test_helper.get_logged_metrics(metrics::CHIP_POWER_UP_FAILURE_METRIC_ID);
349 assert_matches!(&logged_metrics[..], [metric] => {
350 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
351 metric_id: metrics::CHIP_POWER_UP_FAILURE_METRIC_ID,
352 event_codes: vec![],
353 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
354 };
355 assert_eq!(metric, &expected_metric);
356 });
357 }
358
359 #[fuchsia::test]
360 fn test_failed_to_stop_logs_to_cobalt() {
361 let mut test_helper = setup_test();
362 let node = test_helper.create_inspect_node("wlan_mock_node");
363 let power_logger = PowerLogger::new(test_helper.cobalt_proxy.clone(), &node);
364
365 let mut test_fut = pin!(power_logger.chip_power_down_failure());
366 assert_eq!(
367 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
368 Poll::Ready(())
369 );
370
371 let logged_metrics =
372 test_helper.get_logged_metrics(metrics::STOP_FAILURE_OCCURRENCE_METRIC_ID);
373 assert_matches!(&logged_metrics[..], [metric] => {
374 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
375 metric_id: metrics::STOP_FAILURE_OCCURRENCE_METRIC_ID,
376 event_codes: vec![],
377 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
378 };
379 assert_eq!(metric, &expected_metric);
380 });
381 }
382}