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