wlan_telemetry/processors/
power.rs1use crate::util::cobalt_logger::log_cobalt_1dot1;
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_1dot1_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
32 iface_power_states: Arc<Mutex<HashMap<u16, IfacePowerLevel>>>,
33}
34
35impl PowerLogger {
36 pub fn new(
37 cobalt_1dot1_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_1dot1_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_1dot1!(
73 self.cobalt_1dot1_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_1dot1!(
91 self.cobalt_1dot1_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
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::testing::setup_test;
108 use diagnostics_assertions::{assert_data_tree, AnyNumericProperty};
109 use futures::pin_mut;
110 use futures::task::Poll;
111 use std::pin::pin;
112 use wlan_common::assert_variant;
113
114 #[fuchsia::test]
115 fn test_iface_power_event_in_inspect() {
116 let mut test_helper = setup_test();
117 let node = test_helper.create_inspect_node("wlan_mock_node");
118 let power_logger = PowerLogger::new(test_helper.cobalt_1dot1_proxy.clone(), &node);
119
120 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 11);
121 pin_mut!(test_fut);
122 assert_eq!(
123 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
124 Poll::Ready(())
125 );
126 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 22);
127 pin_mut!(test_fut);
128 assert_eq!(
129 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
130 Poll::Ready(())
131 );
132 let mut test_fut = pin!(power_logger.log_iface_power_event(IfacePowerLevel::Normal, 33));
133 assert_eq!(
134 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
135 Poll::Ready(())
136 );
137
138 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::SuspendMode, 11);
140 pin_mut!(test_fut);
141 assert_eq!(
142 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
143 Poll::Ready(())
144 );
145
146 assert_data_tree!(test_helper.inspector, root: contains {
147 wlan_mock_node: {
148 iface_power_events: {
149 "0": {
150 "power_level": "NoPowerSavings",
151 "iface_id": 11_u64,
152 "@time": AnyNumericProperty
153 },
154 "1": {
155 "power_level": "NoPowerSavings",
156 "iface_id": 22_u64,
157 "@time": AnyNumericProperty
158 },
159 "2": {
160 "power_level": "Normal",
161 "iface_id": 33_u64,
162 "@time": AnyNumericProperty
163 },
164 "3": {
165 "power_level": "SuspendMode",
166 "iface_id": 11_u64,
167 "@time": AnyNumericProperty
168 },
169 }
170 }
171 });
172 }
173
174 #[fuchsia::test]
175 fn test_iface_power_event_adds_to_internal_hashmap() {
176 let mut test_helper = setup_test();
177 let node = test_helper.create_inspect_node("wlan_mock_node");
178 let power_logger = PowerLogger::new(test_helper.cobalt_1dot1_proxy.clone(), &node);
179
180 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 11);
181 pin_mut!(test_fut);
182 assert_eq!(
183 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
184 Poll::Ready(())
185 );
186 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::NoPowerSavings, 22);
187 pin_mut!(test_fut);
188 assert_eq!(
189 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
190 Poll::Ready(())
191 );
192 let mut test_fut = pin!(power_logger.log_iface_power_event(IfacePowerLevel::Normal, 33));
193 assert_eq!(
194 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
195 Poll::Ready(())
196 );
197
198 let test_fut = power_logger.log_iface_power_event(IfacePowerLevel::SuspendMode, 11);
200 pin_mut!(test_fut);
201 assert_eq!(
202 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
203 Poll::Ready(())
204 );
205
206 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 3);
207 assert_eq!(
208 power_logger.iface_power_states.try_lock().unwrap().get(&11),
209 Some(&IfacePowerLevel::SuspendMode)
210 );
211 assert_eq!(
212 power_logger.iface_power_states.try_lock().unwrap().get(&22),
213 Some(&IfacePowerLevel::NoPowerSavings)
214 );
215 assert_eq!(
216 power_logger.iface_power_states.try_lock().unwrap().get(&33),
217 Some(&IfacePowerLevel::Normal)
218 );
219 }
220
221 #[fuchsia::test]
222 fn test_disconnect_updates_internal_hashmap() {
223 let mut test_helper = setup_test();
224 let node = test_helper.create_inspect_node("wlan_mock_node");
225 let power_logger = PowerLogger::new(test_helper.cobalt_1dot1_proxy.clone(), &node);
226
227 let _ =
228 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
229 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
230
231 let mut test_fut = pin!(power_logger.handle_iface_disconnect(33));
232 assert_eq!(
233 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
234 Poll::Ready(())
235 );
236 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
237 assert_eq!(
238 power_logger.iface_power_states.try_lock().unwrap().get(&33),
239 Some(&IfacePowerLevel::Disconnected)
240 );
241 }
242
243 #[fuchsia::test]
244 fn test_destroy_removes_from_internal_hashmap() {
245 let mut test_helper = setup_test();
246 let node = test_helper.create_inspect_node("wlan_mock_node");
247 let power_logger = PowerLogger::new(test_helper.cobalt_1dot1_proxy.clone(), &node);
248
249 let _ =
250 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
251 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 1);
252
253 let mut test_fut = pin!(power_logger.handle_iface_destroyed(33));
254 assert_eq!(
255 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
256 Poll::Ready(())
257 );
258 assert_eq!(power_logger.iface_power_states.try_lock().unwrap().len(), 0);
259 }
260
261 #[fuchsia::test]
262 fn test_imminent_suspension_logs_to_cobalt() {
263 let mut test_helper = setup_test();
264 let node = test_helper.create_inspect_node("wlan_mock_node");
265 let power_logger = PowerLogger::new(test_helper.cobalt_1dot1_proxy.clone(), &node);
266
267 let _ =
268 power_logger.iface_power_states.try_lock().unwrap().insert(33, IfacePowerLevel::Normal);
269 let mut test_fut = pin!(power_logger.handle_suspend_imminent());
270 assert_eq!(
271 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
272 Poll::Ready(())
273 );
274
275 let logged_metrics =
276 test_helper.get_logged_metrics(metrics::POWER_LEVEL_AT_SUSPEND_METRIC_ID);
277 assert_variant!(&logged_metrics[..], [metric] => {
278 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
279 metric_id: metrics::POWER_LEVEL_AT_SUSPEND_METRIC_ID,
280 event_codes: vec![metrics::PowerLevelAtSuspendMetricDimensionPowerLevel::PowerSaveMode
281 .as_event_code()],
282 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
283 };
284 assert_eq!(metric, &expected_metric);
285 });
286 }
287
288 #[fuchsia::test]
289 fn test_unclear_power_demand_logs_to_cobalt() {
290 let mut test_helper = setup_test();
291 let node = test_helper.create_inspect_node("wlan_mock_node");
292 let power_logger = PowerLogger::new(test_helper.cobalt_1dot1_proxy.clone(), &node);
293
294 let mut test_fut = pin!(power_logger.handle_unclear_power_demand(
295 UnclearPowerDemand::PowerSaveRequestedWhileSuspendModeEnabled
296 ));
297 assert_eq!(
298 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
299 Poll::Ready(())
300 );
301
302 let logged_metrics =
303 test_helper.get_logged_metrics(metrics::UNCLEAR_POWER_LEVEL_DEMAND_METRIC_ID);
304 assert_variant!(&logged_metrics[..], [metric] => {
305 let expected_metric = fidl_fuchsia_metrics::MetricEvent {
306 metric_id: metrics::UNCLEAR_POWER_LEVEL_DEMAND_METRIC_ID,
307 event_codes: vec![metrics::UnclearPowerLevelDemandMetricDimensionReason::PowerSaveRequestedWhileSuspendModeEnabled
308 .as_event_code()],
309 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
310 };
311 assert_eq!(metric, &expected_metric);
312 });
313 }
314}