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