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
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::testing::setup_test;
115 use assert_matches::assert_matches;
116 use diagnostics_assertions::{AnyNumericProperty, assert_data_tree};
117 use futures::pin_mut;
118 use futures::task::Poll;
119 use std::pin::pin;
120
121 #[fuchsia::test]
122 fn test_iface_power_event_in_inspect() {
123 let mut test_helper = setup_test();
124 let node = test_helper.create_inspect_node("wlan_mock_node");
125 let power_logger = PowerLogger::new(test_helper.filtered_cobalt_logger(), &node);
126
127 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 11);
128 pin_mut!(test_fut);
129 assert_eq!(
130 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
131 Poll::Ready(())
132 );
133 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 22);
134 pin_mut!(test_fut);
135 assert_eq!(
136 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
137 Poll::Ready(())
138 );
139 let mut test_fut = pin!(power_logger.log_iface_power_event(IfacePowerLevel::Normal, 33));
140 assert_eq!(
141 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
142 Poll::Ready(())
143 );
144
145 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::SuspendMode, 11);
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
153 assert_data_tree!(@executor test_helper.exec, test_helper.inspector, root: contains {
154 wlan_mock_node: {
155 iface_power_events: {
156 "0": {
157 "power_level": "NoPowerSavings",
158 "iface_id": 11_u64,
159 "@time": AnyNumericProperty
160 },
161 "1": {
162 "power_level": "NoPowerSavings",
163 "iface_id": 22_u64,
164 "@time": AnyNumericProperty
165 },
166 "2": {
167 "power_level": "Normal",
168 "iface_id": 33_u64,
169 "@time": AnyNumericProperty
170 },
171 "3": {
172 "power_level": "SuspendMode",
173 "iface_id": 11_u64,
174 "@time": AnyNumericProperty
175 },
176 }
177 }
178 });
179 }
180
181 #[fuchsia::test]
182 fn test_iface_power_event_adds_to_internal_hashmap() {
183 let mut test_helper = setup_test();
184 let node = test_helper.create_inspect_node("wlan_mock_node");
185 let power_logger = PowerLogger::new(test_helper.filtered_cobalt_logger(), &node);
186
187 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 11);
188 pin_mut!(test_fut);
189 assert_eq!(
190 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
191 Poll::Ready(())
192 );
193 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 22);
194 pin_mut!(test_fut);
195 assert_eq!(
196 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
197 Poll::Ready(())
198 );
199 let mut test_fut = pin!(power_logger.log_iface_power_event(IfacePowerLevel::Normal, 33));
200 assert_eq!(
201 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
202 Poll::Ready(())
203 );
204
205 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::SuspendMode, 11);
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
213 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 3);
214 assert_eq!(
215 power_logger.iface_power_states.try_lock().unwrap().get(&11),
216 Some(&IfacePowerLevel::SuspendMode)
217 );
218 assert_eq!(
219 power_logger.iface_power_states.try_lock().unwrap().get(&22),
220 Some(&IfacePowerLevel::NoPowerSavings)
221 );
222 assert_eq!(
223 power_logger.iface_power_states.try_lock().unwrap().get(&33),
224 Some(&IfacePowerLevel::Normal)
225 );
226 }
227
228 #[fuchsia::test]
229 fn test_disconnect_updates_internal_hashmap() {
230 let mut test_helper = setup_test();
231 let node = test_helper.create_inspect_node("wlan_mock_node");
232 let power_logger = PowerLogger::new(test_helper.filtered_cobalt_logger(), &node);
233
234 let _ =
235 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
236 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
237
238 let mut test_fut = pin!(power_logger.handle_iface_disconnect(33));
239 assert_eq!(
240 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
241 Poll::Ready(())
242 );
243 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
244 assert_eq!(
245 power_logger.iface_power_states.try_lock().unwrap().get(&33),
246 Some(&IfacePowerLevel::Disconnected)
247 );
248 }
249
250 #[fuchsia::test]
251 fn test_destroy_removes_from_internal_hashmap() {
252 let mut test_helper = setup_test();
253 let node = test_helper.create_inspect_node("wlan_mock_node");
254 let power_logger = PowerLogger::new(test_helper.filtered_cobalt_logger(), &node);
255
256 let _ =
257 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
258 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
259
260 let mut test_fut = pin!(power_logger.handle_iface_destroyed(33));
261 assert_eq!(
262 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
263 Poll::Ready(())
264 );
265 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 0);
266 }
267
268 #[fuchsia::test]
269 fn test_imminent_suspension_logs_to_cobalt() {
270 let mut test_helper = setup_test();
271 let node = test_helper.create_inspect_node("wlan_mock_node");
272 let power_logger = PowerLogger::new(test_helper.filtered_cobalt_logger(), &node);
273
274 let _ =
275 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
276 let mut test_fut = pin!(power_logger.handle_suspend_imminent());
277 assert_eq!(
278 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
279 Poll::Ready(())
280 );
281
282 let logged_metrics =
283 test_helper.get_logged_metrics(metrics::POWER_LEVEL_AT_SUSPEND_METRIC_ID);
284 assert_matches!(&logged_metrics[..], [metric] => {
285 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
286 metric_id: metrics::POWER_LEVEL_AT_SUSPEND_METRIC_ID,
287 event_codes: vec![metrics::PowerLevelAtSuspendMetricDimensionPowerLevel::PowerSaveMode
288 .as_event_code()],
289 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
290 };
291 assert_eq!(metric, &expected_metric);
292 });
293 }
294
295 #[fuchsia::test]
296 fn test_unclear_power_demand_logs_to_cobalt() {
297 let mut test_helper = setup_test();
298 let node = test_helper.create_inspect_node("wlan_mock_node");
299 let power_logger = PowerLogger::new(test_helper.filtered_cobalt_logger(), &node);
300
301 let mut test_fut = pin!(power_logger.handle_unclear_power_demand(
302 UnclearPowerDemand::PowerSaveRequestedWhileSuspendModeEnabled
303 ));
304 assert_eq!(
305 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
306 Poll::Ready(())
307 );
308
309 let logged_metrics =
310 test_helper.get_logged_metrics(metrics::UNCLEAR_POWER_LEVEL_DEMAND_METRIC_ID);
311 assert_matches!(&logged_metrics[..], [metric] => {
312 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
313 metric_id: metrics::UNCLEAR_POWER_LEVEL_DEMAND_METRIC_ID,
314 event_codes: vec![metrics::UnclearPowerLevelDemandMetricDimensionReason::PowerSaveRequestedWhileSuspendModeEnabled
315 .as_event_code()],
316 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
317 };
318 assert_eq!(metric, &expected_metric);
319 });
320 }
321
322 #[fuchsia::test]
323 fn test_chip_power_up_failure_logs_to_cobalt() {
324 let mut test_helper = setup_test();
325 let node = test_helper.create_inspect_node("wlan_mock_node");
326 let power_logger = PowerLogger::new(test_helper.filtered_cobalt_logger(), &node);
327
328 let mut test_fut = pin!(power_logger.handle_chip_power_up_failure());
329 assert_eq!(
330 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
331 Poll::Ready(())
332 );
333
334 let logged_metrics =
335 test_helper.get_logged_metrics(metrics::CHIP_POWER_UP_FAILURE_METRIC_ID);
336 assert_matches!(&logged_metrics[..], [metric] => {
337 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
338 metric_id: metrics::CHIP_POWER_UP_FAILURE_METRIC_ID,
339 event_codes: vec![],
340 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
341 };
342 assert_eq!(metric, &expected_metric);
343 });
344 }
345}