1use crate::device::service_watcher::watch_fidl_service;
8use crate::task::Kernel;
9use fidl::endpoints::{Proxy as _, SynchronousProxy as _};
10use fidl_fuchsia_hardware_power_stats as fpowerstats;
11use fuchsia_async::TimeoutExt as _;
12use starnix_logging::log_warn;
13use starnix_sync::{LockDepMutex, PowerStatsManagerLock};
14use starnix_uapi::errors::Errno;
15use starnix_uapi::{errno, error};
16use std::collections::{HashMap, HashSet};
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::{Arc, Weak};
19
20const PROVIDER_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(1);
22
23#[derive(Debug)]
25pub struct PowerStatsProvider {
26 instance_name: String,
27 entity_names: HashSet<String>,
28 proxy: fpowerstats::DeviceSynchronousProxy,
29}
30
31impl PowerStatsProvider {
32 pub fn new(
34 instance_name: impl Into<String>,
35 entity_names: impl IntoIterator<Item = impl Into<String>>,
36 proxy: fpowerstats::DeviceSynchronousProxy,
37 ) -> Self {
38 Self {
39 instance_name: instance_name.into(),
40 entity_names: entity_names.into_iter().map(Into::into).collect(),
41 proxy,
42 }
43 }
44
45 pub fn instance_name(&self) -> &str {
47 &self.instance_name
48 }
49
50 pub fn entity_names(&self) -> &HashSet<String> {
55 &self.entity_names
56 }
57
58 fn is_closed(&self) -> bool {
60 self.proxy.is_closed().unwrap_or(true)
61 }
62
63 fn entity_stats(&self, entity_name: &str) -> Result<fpowerstats::Stats, Errno> {
65 let deadline = zx::MonotonicInstant::after(PROVIDER_TIMEOUT);
66 let reports = self.proxy.get_stats(deadline).map_err(|e| {
67 log_warn!(
68 "Failed to read power stats for {entity_name} from {}: {e}",
69 self.instance_name
70 );
71 errno!(EIO)
72 })?;
73 let Some(stats) = reports.into_iter().find(|stats| stats.entity == entity_name) else {
74 log_warn!(
75 "Power stats provider {} no longer reports {entity_name}",
76 self.instance_name
77 );
78 return error!(EIO);
79 };
80 Ok(stats)
81 }
82}
83
84type ProviderListener = dyn FnMut(&Kernel, &Arc<PowerStatsProvider>) + Send;
85
86#[derive(Default)]
87struct PowerStatsManagerState {
88 providers: Vec<Arc<PowerStatsProvider>>,
90 entity_providers: HashMap<String, Arc<PowerStatsProvider>>,
92 listeners: Vec<Box<ProviderListener>>,
94}
95
96impl std::fmt::Debug for PowerStatsManagerState {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.debug_struct("PowerStatsManagerState")
99 .field("providers", &self.providers)
100 .field("entity_providers", &self.entity_providers)
101 .field("listeners_count", &self.listeners.len())
102 .finish()
103 }
104}
105
106#[derive(Debug, Default)]
109struct PowerStatsManagerInner {
110 watch_started: AtomicBool,
111 state: LockDepMutex<PowerStatsManagerState, PowerStatsManagerLock>,
112}
113
114#[derive(Clone, Debug)]
122pub struct PowerStatsManager {
123 inner: Arc<PowerStatsManagerInner>,
124}
125
126impl PowerStatsManager {
127 pub fn get_or_init(kernel: &Kernel) -> Self {
130 let inner = kernel.expando.get::<PowerStatsManagerInner>();
131 if !inner.watch_started.swap(true, Ordering::AcqRel) {
132 watch_fidl_service(
133 kernel,
134 fpowerstats::ServiceMarker,
135 "power_stats",
136 Self::handle_service_instance,
137 );
138 }
139 Self { inner }
140 }
141
142 pub fn get_or_init_for_testing(kernel: &Kernel) -> Self {
145 let inner = kernel.expando.get::<PowerStatsManagerInner>();
146 inner.watch_started.store(true, Ordering::Release);
147 Self { inner }
148 }
149
150 pub fn add_provider_for_testing(kernel: &Kernel, provider: PowerStatsProvider) {
153 Self::get_or_init_for_testing(kernel).register_provider(kernel, provider);
154 }
155
156 pub fn watch_providers<F>(&self, kernel: &Kernel, mut listener: F)
171 where
172 F: FnMut(&Kernel, &Arc<PowerStatsProvider>) + Send + 'static,
173 {
174 let mut state = self.inner.state.lock();
175 for provider in &state.providers {
176 listener(kernel, provider);
177 }
178 state.listeners.push(Box::new(listener));
179 }
180
181 pub fn entity_stats(&self, entity_name: &str) -> Result<Option<fpowerstats::Stats>, Errno> {
184 let provider = self.inner.state.lock().entity_providers.get(entity_name).cloned();
185 provider.map(|provider| provider.entity_stats(entity_name)).transpose()
186 }
187
188 async fn handle_service_instance(
189 weak_kernel: &Weak<Kernel>,
190 instance: fpowerstats::ServiceProxy,
191 ) {
192 let instance_name = instance.instance_name().to_string();
193 let device_proxy = match instance.connect_to_device() {
194 Ok(proxy) => proxy,
195 Err(e) => {
196 log_warn!("Failed to connect to power stats provider {instance_name}: {e:#}");
197 return;
198 }
199 };
200 let entities = match async { Some(device_proxy.get_entities().await) }
201 .on_timeout(PROVIDER_TIMEOUT, || None)
202 .await
203 {
204 Some(Ok(entities)) => entities,
205 Some(Err(e)) => {
206 log_warn!("Failed to get power entities from {instance_name}: {e:#}");
207 return;
208 }
209 None => {
210 log_warn!("Timed out getting power entities from {instance_name}");
211 return;
212 }
213 };
214 let Some(kernel) = weak_kernel.upgrade() else {
215 return;
216 };
217 let client_end = match device_proxy.into_client_end() {
218 Ok(client_end) => client_end,
219 Err(_) => {
220 log_warn!("Failed to unwrap device proxy channel for {instance_name}");
221 return;
222 }
223 };
224 let entity_names = entities.into_iter().map(|entity| entity.name);
225 let sync_proxy = fpowerstats::DeviceSynchronousProxy::new(client_end.into_channel());
226 let provider = PowerStatsProvider::new(instance_name, entity_names, sync_proxy);
227 let inner = kernel.expando.get::<PowerStatsManagerInner>();
228 Self { inner }.register_provider(&kernel, provider);
229 }
230
231 fn register_provider(&self, kernel: &Kernel, mut provider: PowerStatsProvider) {
232 let mut state = self.inner.state.lock();
233 let instance_name = provider.instance_name.clone();
234 let served_entities: Vec<String> = provider
235 .entity_names
236 .iter()
237 .filter(|&entity_name| {
238 if let Some(existing) = state
239 .entity_providers
240 .get(entity_name)
241 .filter(|existing| !existing.is_closed())
242 {
243 log_warn!(
244 "Ignoring duplicate power entity {entity_name} from provider {instance_name} (already served by {})",
245 existing.instance_name()
246 );
247 false
248 } else {
249 true
250 }
251 })
252 .cloned()
253 .collect();
254 provider
255 .entity_names
256 .retain(|entity_name| !state.entity_providers.contains_key(entity_name));
257 let provider = Arc::new(provider);
258 for entity_name in served_entities {
259 state.entity_providers.insert(entity_name, provider.clone());
260 }
261 if !provider.entity_names.is_empty() {
262 state.providers.push(provider.clone());
263 for listener in &mut state.listeners {
264 listener(kernel, &provider);
265 }
266 }
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::device::service_watcher::run_fidl_service_watcher;
274 use crate::testing::{spawn_kernel_and_run, spawn_kernel_and_run_sync};
275 use fidl::endpoints::{ServerEnd, create_endpoints, create_sync_proxy};
276 use fidl_fuchsia_io as fio;
277 use fuchsia_async as fasync;
278 use fuchsia_component::client as fclient;
279 use fuchsia_component::server::ServiceFs;
280 use futures::StreamExt;
281 use futures::channel::oneshot;
282 use futures::future::{Either, select};
283 use std::pin::pin;
284 use std::sync::atomic::AtomicUsize;
285
286 fn state(id: i32, entry_count: u32, total_time_ms: u64) -> fpowerstats::StateResidency {
287 fpowerstats::StateResidency {
288 id,
289 name: id.to_string(),
290 total_time_ms,
291 entry_count,
292 last_entry_timestamp_ms: 0,
293 }
294 }
295
296 fn stats(
297 entity: &str,
298 state_residency_data: Vec<fpowerstats::StateResidency>,
299 ) -> fpowerstats::Stats {
300 fpowerstats::Stats { entity: entity.to_string(), state_residency_data }
301 }
302
303 async fn serve_provider(
304 stream: fpowerstats::DeviceRequestStream,
305 reports: Vec<fpowerstats::Stats>,
306 ) {
307 let _ = stream
308 .for_each(|request| {
309 let reports = &reports;
310 async move {
311 match request {
312 Ok(fpowerstats::DeviceRequest::GetEntities { responder }) => {
313 let entities: Vec<_> = reports
314 .iter()
315 .map(|stats| fpowerstats::Entity {
316 name: stats.entity.clone(),
317 states: vec![],
318 })
319 .collect();
320 let _ = responder.send(&entities);
321 }
322 Ok(fpowerstats::DeviceRequest::GetStats { responder }) => {
323 let _ = responder.send(reports);
324 }
325 _ => {}
326 }
327 }
328 })
329 .await;
330 }
331
332 fn spawn_server<F: Future<Output = ()> + 'static>(serve: impl FnOnce() -> F + Send + 'static) {
333 std::thread::spawn(move || fasync::LocalExecutor::default().run_singlethreaded(serve()));
334 }
335
336 #[derive(Clone)]
337 enum ProviderBehavior {
338 Healthy(Vec<fpowerstats::Stats>),
339 Closed,
340 Hung,
341 }
342
343 fn serve_service(
344 providers: Vec<(&'static str, ProviderBehavior)>,
345 ) -> fclient::Service<fpowerstats::ServiceMarker> {
346 let (client, server) = create_endpoints::<fio::DirectoryMarker>();
347 spawn_server(move || async move {
348 let mut fs = ServiceFs::new_local();
349 for (instance, behavior) in providers {
350 fs.add_fidl_service_instance(instance, move |request| {
351 let fpowerstats::ServiceRequest::Device(stream) = request;
352 (stream, behavior.clone())
353 });
354 }
355 fs.serve_connection(server).expect("serve service directory");
356 fs.for_each_concurrent(None, |(stream, behavior)| async move {
357 match behavior {
358 ProviderBehavior::Healthy(reports) => {
359 serve_provider(stream, reports).await;
360 }
361 ProviderBehavior::Closed => {}
362 ProviderBehavior::Hung => {
363 let _stream = stream;
364 std::future::pending::<()>().await;
365 }
366 }
367 })
368 .await;
369 });
370 fclient::Service::open_from_dir(client, fpowerstats::ServiceMarker).expect("open service")
371 }
372
373 #[::fuchsia::test]
374 async fn discovers_entities_and_queries_stats() {
375 spawn_kernel_and_run(async |current_task| {
376 let kernel = current_task.kernel();
377 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
378 assert!(Arc::ptr_eq(&manager.inner, &PowerStatsManager::get_or_init(kernel).inner));
379 assert_eq!(manager.entity_stats("cpu_c2"), Ok(None));
380
381 let (tx, rx) = oneshot::channel();
382 let mut tx = Some(tx);
383 manager.watch_providers(kernel, move |_, _| {
384 if let Some(tx) = tx.take() {
385 let _ = tx.send(());
386 }
387 });
388
389 let service = serve_service(vec![(
390 "default",
391 ProviderBehavior::Healthy(vec![
392 stats("cpu_c2", vec![state(0, 5, 120)]),
393 stats("lpcm_soc", vec![]),
394 ]),
395 )]);
396 let weak_kernel = kernel.weak_self.clone();
397 let watcher = pin!(run_fidl_service_watcher(
398 &weak_kernel,
399 service,
400 PowerStatsManager::handle_service_instance,
401 ));
402
403 match select(watcher, rx).await {
404 Either::Left((result, _)) => {
405 panic!("watcher exited before provider was discovered: {result:?}");
406 }
407 Either::Right((Ok(()), _)) => {}
408 Either::Right((Err(e), _)) => {
409 panic!("oneshot sender dropped before signalling: {e:?}");
410 }
411 }
412
413 let cpu_c2 = manager
414 .entity_stats("cpu_c2")
415 .expect("GetStats succeeded")
416 .expect("cpu_c2 entity discovered");
417 assert_eq!(cpu_c2.state_residency_data.len(), 1);
418 assert_eq!(cpu_c2.state_residency_data[0].entry_count, 5);
419 assert_eq!(cpu_c2.state_residency_data[0].total_time_ms, 120);
420 assert_eq!(manager.entity_stats("unknown"), Ok(None));
421 })
422 .await;
423 }
424
425 #[::fuchsia::test]
426 async fn closed_provider_returns_eio_and_restarted_provider_recovers() {
427 spawn_kernel_and_run_sync(|current_task| {
428 let kernel = current_task.kernel();
429 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
430
431 let (live_proxy, live_server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
432 spawn_server(move || {
433 serve_provider(
434 live_server.into_stream(),
435 vec![stats("cpu_c2", vec![state(0, 9, 42)])],
436 )
437 });
438 PowerStatsManager::add_provider_for_testing(
439 kernel,
440 PowerStatsProvider::new("first", ["cpu_c2"], live_proxy),
441 );
442
443 let (closed_proxy, server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
444 drop::<ServerEnd<_>>(server);
445 PowerStatsManager::add_provider_for_testing(
446 kernel,
447 PowerStatsProvider::new(
448 "second",
449 ["cpu_c2", "dead_entity", "unrecovered_entity"],
450 closed_proxy,
451 ),
452 );
453
454 let early_seen = Arc::new(std::sync::Mutex::new(Vec::new()));
456 let early_seen_for_listener = early_seen.clone();
457 manager.watch_providers(kernel, move |_, provider| {
458 early_seen_for_listener
459 .lock()
460 .unwrap()
461 .push((provider.instance_name().to_string(), provider.entity_names().clone()));
462 });
463 assert_eq!(
464 *early_seen.lock().unwrap(),
465 vec![
466 ("first".to_string(), HashSet::from(["cpu_c2".to_string()])),
467 (
468 "second".to_string(),
469 HashSet::from([
470 "dead_entity".to_string(),
471 "unrecovered_entity".to_string()
472 ])
473 ),
474 ]
475 );
476
477 let cpu_c2 = manager
480 .entity_stats("cpu_c2")
481 .expect("GetStats succeeded")
482 .expect("cpu_c2 entity served by first provider");
483 assert_eq!(cpu_c2.state_residency_data[0].total_time_ms, 42);
484
485 assert_eq!(manager.entity_stats("dead_entity"), error!(EIO));
489 let (unrelated_proxy, _unrelated_server) =
490 create_sync_proxy::<fpowerstats::DeviceMarker>();
491 PowerStatsManager::add_provider_for_testing(
492 kernel,
493 PowerStatsProvider::new("unrelated", ["other_entity"], unrelated_proxy),
494 );
495 assert_eq!(manager.entity_stats("dead_entity"), error!(EIO));
496
497 let (restarted_proxy, restarted_server) =
501 create_sync_proxy::<fpowerstats::DeviceMarker>();
502 spawn_server(move || {
503 serve_provider(
504 restarted_server.into_stream(),
505 vec![stats("dead_entity", vec![state(0, 3, 99)])],
506 )
507 });
508 PowerStatsManager::add_provider_for_testing(
509 kernel,
510 PowerStatsProvider::new("second_restarted", ["dead_entity"], restarted_proxy),
511 );
512 let recovered = manager
513 .entity_stats("dead_entity")
514 .expect("GetStats succeeded")
515 .expect("dead_entity served by restarted provider");
516 assert_eq!(recovered.state_residency_data[0].total_time_ms, 99);
517 assert_eq!(manager.entity_stats("unrecovered_entity"), error!(EIO));
518
519 let late_seen = Arc::new(std::sync::Mutex::new(Vec::new()));
520 let late_seen_for_listener = late_seen.clone();
521 manager.watch_providers(kernel, move |_, provider| {
522 late_seen_for_listener
523 .lock()
524 .unwrap()
525 .push((provider.instance_name().to_string(), provider.entity_names().clone()));
526 });
527 let expected_announcements = vec![
528 ("first".to_string(), HashSet::from(["cpu_c2".to_string()])),
529 (
530 "second".to_string(),
531 HashSet::from(["dead_entity".to_string(), "unrecovered_entity".to_string()]),
532 ),
533 ("unrelated".to_string(), HashSet::from(["other_entity".to_string()])),
534 ];
535 assert_eq!(*early_seen.lock().unwrap(), expected_announcements);
536 assert_eq!(*late_seen.lock().unwrap(), expected_announcements);
537 })
538 .await;
539 }
540
541 #[::fuchsia::test]
542 async fn watch_providers_notifies_for_existing_and_new_providers() {
543 spawn_kernel_and_run_sync(|current_task| {
544 let kernel = current_task.kernel();
545 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
546
547 let (proxy1, _server1) = create_sync_proxy::<fpowerstats::DeviceMarker>();
548 PowerStatsManager::add_provider_for_testing(
549 kernel,
550 PowerStatsProvider::new("first", ["first_entity"], proxy1),
551 );
552
553 let seen = Arc::new(AtomicUsize::new(0));
554 let seen_for_listener = seen.clone();
555 manager.watch_providers(kernel, move |_, _| {
556 seen_for_listener.fetch_add(1, Ordering::SeqCst);
557 });
558 assert_eq!(seen.load(Ordering::SeqCst), 1);
559
560 let (proxy2, _server2) = create_sync_proxy::<fpowerstats::DeviceMarker>();
561 PowerStatsManager::add_provider_for_testing(
562 kernel,
563 PowerStatsProvider::new("second", ["second_entity"], proxy2),
564 );
565 assert_eq!(seen.load(Ordering::SeqCst), 2);
566 })
567 .await;
568 }
569
570 #[::fuchsia::test]
571 async fn entity_stats_returns_eio_when_entity_omitted_from_stats_report() {
572 spawn_kernel_and_run_sync(|current_task| {
573 let kernel = current_task.kernel();
574 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
575
576 let (proxy, server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
577 spawn_server(move || {
578 serve_provider(server.into_stream(), vec![stats("b", vec![state(0, 1, 10)])])
579 });
580 PowerStatsManager::add_provider_for_testing(
581 kernel,
582 PowerStatsProvider::new("test", ["a", "b"], proxy),
583 );
584
585 assert!(manager.entity_stats("b").expect("GetStats succeeded").is_some());
586 assert_eq!(manager.entity_stats("a"), error!(EIO));
587 })
588 .await;
589 }
590
591 #[::fuchsia::test]
592 async fn entity_stats_returns_eio_when_provider_times_out() {
593 spawn_kernel_and_run_sync(|current_task| {
594 let kernel = current_task.kernel();
595 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
596
597 let (proxy, _server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
599 PowerStatsManager::add_provider_for_testing(
600 kernel,
601 PowerStatsProvider::new("hung", ["a"], proxy),
602 );
603
604 assert_eq!(manager.entity_stats("a"), error!(EIO));
605 })
606 .await;
607 }
608
609 #[::fuchsia::test]
610 async fn handle_service_instance_skips_failing_provider_and_discovers_subsequent_instances() {
611 spawn_kernel_and_run(async |current_task| {
612 let kernel = current_task.kernel();
613 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
614
615 let (tx, rx) = oneshot::channel();
616 let mut tx = Some(tx);
617 let mut seen_names = Vec::new();
618 manager.watch_providers(kernel, move |_, provider| {
619 seen_names.push(provider.instance_name().to_string());
620 if provider.instance_name() == "healthy" {
621 if let Some(tx) = tx.take() {
622 let _ = tx.send(std::mem::take(&mut seen_names));
623 }
624 }
625 });
626
627 let service = serve_service(vec![
628 ("broken_closed", ProviderBehavior::Closed),
629 ("broken_hung", ProviderBehavior::Hung),
630 (
631 "healthy",
632 ProviderBehavior::Healthy(vec![stats("cpu_c2", vec![state(0, 4, 80)])]),
633 ),
634 ]);
635
636 let weak_kernel = kernel.weak_self.clone();
637 let watcher = pin!(run_fidl_service_watcher(
638 &weak_kernel,
639 service,
640 PowerStatsManager::handle_service_instance,
641 ));
642
643 let seen_names = match select(watcher, rx).await {
644 Either::Left((result, _)) => {
645 panic!("watcher exited before healthy provider was discovered: {result:?}");
646 }
647 Either::Right((Ok(seen_names), _)) => seen_names,
648 Either::Right((Err(e), _)) => {
649 panic!("oneshot sender dropped before signalling: {e:?}");
650 }
651 };
652
653 assert_eq!(seen_names, vec!["healthy".to_string()]);
654 let cpu_c2 = manager
655 .entity_stats("cpu_c2")
656 .expect("GetStats succeeded")
657 .expect("cpu_c2 entity discovered from healthy provider");
658 assert_eq!(cpu_c2.state_residency_data[0].total_time_ms, 80);
659 })
660 .await;
661 }
662
663 #[::fuchsia::test]
664 async fn duplicate_entities_across_live_providers_keep_first_provider_and_serve_disjoint_entities()
665 {
666 spawn_kernel_and_run_sync(|current_task| {
667 let kernel = current_task.kernel();
668 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
669
670 let (proxy1, server1) = create_sync_proxy::<fpowerstats::DeviceMarker>();
671 spawn_server(move || {
672 serve_provider(
673 server1.into_stream(),
674 vec![
675 stats("shared", vec![state(0, 1, 10)]),
676 stats("only_first", vec![state(0, 2, 20)]),
677 ],
678 )
679 });
680 PowerStatsManager::add_provider_for_testing(
681 kernel,
682 PowerStatsProvider::new("first", ["shared", "only_first"], proxy1),
683 );
684
685 let (proxy2, server2) = create_sync_proxy::<fpowerstats::DeviceMarker>();
686 spawn_server(move || {
687 serve_provider(
688 server2.into_stream(),
689 vec![
690 stats("shared", vec![state(0, 9, 999)]),
691 stats("only_second", vec![state(0, 3, 30)]),
692 ],
693 )
694 });
695 PowerStatsManager::add_provider_for_testing(
696 kernel,
697 PowerStatsProvider::new("second", ["shared", "only_second"], proxy2),
698 );
699
700 let shared = manager.entity_stats("shared").unwrap().unwrap();
701 assert_eq!(shared.state_residency_data[0].total_time_ms, 10);
702
703 let only_first = manager.entity_stats("only_first").unwrap().unwrap();
704 assert_eq!(only_first.state_residency_data[0].total_time_ms, 20);
705
706 let only_second = manager.entity_stats("only_second").unwrap().unwrap();
707 assert_eq!(only_second.state_residency_data[0].total_time_ms, 30);
708 })
709 .await;
710 }
711}