1use crate::device::service_watcher::{ServiceBindingScope, ServiceWatcher};
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::Arc;
18use std::sync::atomic::{AtomicBool, Ordering};
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: Arc<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: Arc::new(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, Option<Arc<PowerStatsProvider>>>,
93 listeners: Vec<Box<ProviderListener>>,
95}
96
97impl std::fmt::Debug for PowerStatsManagerState {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("PowerStatsManagerState")
100 .field("providers", &self.providers)
101 .field("entity_providers", &self.entity_providers)
102 .field("listeners_count", &self.listeners.len())
103 .finish()
104 }
105}
106
107#[derive(Debug, Default)]
110struct PowerStatsManagerInner {
111 driver_registered: AtomicBool,
112 state: LockDepMutex<PowerStatsManagerState, PowerStatsManagerLock>,
113}
114
115#[derive(Clone, Debug)]
118pub struct PowerStatsManager {
119 inner: Arc<PowerStatsManagerInner>,
120}
121
122impl PowerStatsManager {
123 pub fn get_or_init(kernel: &Kernel) -> Self {
126 let inner = kernel.expando.get::<PowerStatsManagerInner>();
127 let manager = Self { inner };
128 if !manager.inner.driver_registered.swap(true, Ordering::AcqRel) {
129 let manager_for_bind = manager.clone();
130 ServiceWatcher::register_with_probe(
131 kernel,
132 fpowerstats::ServiceMarker,
133 Self::probe_service_instance,
134 move |current_task, provider, scope| {
135 manager_for_bind.bind_provider(current_task.kernel(), provider, scope);
136 },
137 );
138 }
139 manager
140 }
141
142 pub fn get_or_init_for_testing(kernel: &Kernel) -> Self {
145 let inner = kernel.expando.get::<PowerStatsManagerInner>();
146 inner.driver_registered.store(true, Ordering::Release);
147 Self { inner }
148 }
149
150 pub fn add_provider_for_testing(kernel: &Kernel, provider: PowerStatsProvider) {
153 let _ = 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> {
187 let provider = match self.inner.state.lock().entity_providers.get(entity_name).cloned() {
188 None => return Ok(None),
189 Some(None) => return error!(EIO),
190 Some(Some(provider)) => provider,
191 };
192 provider.entity_stats(entity_name).map(Some)
193 }
194
195 async fn probe_service_instance(
196 instance: Arc<fpowerstats::ServiceProxy>,
197 ) -> Option<PowerStatsProvider> {
198 let instance_name = instance.instance_name().to_string();
199 let device_proxy = match instance.connect_to_device() {
200 Ok(proxy) => proxy,
201 Err(e) => {
202 log_warn!("Failed to connect to power stats provider {instance_name}: {e:#}");
203 return None;
204 }
205 };
206 let entities = match async { Some(device_proxy.get_entities().await) }
207 .on_timeout(PROVIDER_TIMEOUT, || None)
208 .await
209 {
210 Some(Ok(entities)) => entities,
211 Some(Err(e)) => {
212 log_warn!("Failed to get power entities from {instance_name}: {e:#}");
213 return None;
214 }
215 None => {
216 log_warn!("Timed out getting power entities from {instance_name}");
217 return None;
218 }
219 };
220 let client_end = match device_proxy.into_client_end() {
221 Ok(client_end) => client_end,
222 Err(_) => {
223 log_warn!("Failed to unwrap device proxy channel for {instance_name}");
224 return None;
225 }
226 };
227 let entity_names = entities.into_iter().map(|entity| entity.name);
228 let sync_proxy = fpowerstats::DeviceSynchronousProxy::new(client_end.into_channel());
229 Some(PowerStatsProvider::new(instance_name, entity_names, sync_proxy))
230 }
231
232 fn bind_provider(
233 &self,
234 kernel: &Kernel,
235 provider: PowerStatsProvider,
236 scope: &mut ServiceBindingScope,
237 ) {
238 scope.monitor_channel(provider.proxy.clone());
239 let provider = self.register_provider(kernel, provider);
240 let manager = self.clone();
241 scope.on_disconnect(move |_current_task| {
242 manager.disconnect_provider(&provider);
243 });
244 }
245
246 fn register_provider(
247 &self,
248 kernel: &Kernel,
249 mut provider: PowerStatsProvider,
250 ) -> Arc<PowerStatsProvider> {
251 let mut state = self.inner.state.lock();
252 let instance_name = provider.instance_name.clone();
253 let served_entities: Vec<String> = provider
254 .entity_names
255 .iter()
256 .filter(|&entity_name| {
257 if let Some(existing) = state
258 .entity_providers
259 .get(entity_name)
260 .and_then(|slot| slot.as_ref())
261 .filter(|existing| !existing.is_closed())
262 {
263 log_warn!(
264 "Ignoring duplicate power entity {entity_name} from provider {instance_name} (already served by {})",
265 existing.instance_name()
266 );
267 false
268 } else {
269 true
270 }
271 })
272 .cloned()
273 .collect();
274 provider
275 .entity_names
276 .retain(|entity_name| !state.entity_providers.contains_key(entity_name));
277 let provider = Arc::new(provider);
278 for entity_name in served_entities {
279 state.entity_providers.insert(entity_name, Some(provider.clone()));
280 }
281 if !provider.entity_names.is_empty() {
282 state.providers.push(provider.clone());
283 for listener in &mut state.listeners {
284 listener(kernel, &provider);
285 }
286 }
287 provider
288 }
289
290 fn disconnect_provider(&self, disconnected: &Arc<PowerStatsProvider>) {
291 let mut state = self.inner.state.lock();
292 for slot in state.entity_providers.values_mut() {
293 if slot.as_ref().is_some_and(|current| Arc::ptr_eq(current, disconnected)) {
294 *slot = None;
295 }
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::device::service_watcher::watch_service_dir_for_testing;
304 use crate::testing::{spawn_kernel_and_run, spawn_kernel_and_run_sync};
305 use fidl::endpoints::{ServerEnd, create_sync_proxy};
306 use fuchsia_async as fasync;
307 use futures::StreamExt;
308 use futures::channel::{mpsc, oneshot};
309 use futures::future::select;
310 use starnix_sync::Mutex;
311 use std::pin::pin;
312 use std::sync::atomic::AtomicUsize;
313 use vfs::directory::helper::DirectlyMutable as _;
314 use vfs::directory::immutable::Simple;
315
316 fn state(id: i32, entry_count: u32, total_time_ms: u64) -> fpowerstats::StateResidency {
317 fpowerstats::StateResidency {
318 id,
319 name: id.to_string(),
320 total_time_ms,
321 entry_count,
322 last_entry_timestamp_ms: 0,
323 }
324 }
325
326 fn stats(
327 entity: &str,
328 state_residency_data: Vec<fpowerstats::StateResidency>,
329 ) -> fpowerstats::Stats {
330 fpowerstats::Stats { entity: entity.to_string(), state_residency_data }
331 }
332
333 async fn serve_provider(
334 stream: fpowerstats::DeviceRequestStream,
335 reports: Vec<fpowerstats::Stats>,
336 ) {
337 let _ = stream
338 .for_each(|request| {
339 let reports = &reports;
340 async move {
341 match request {
342 Ok(fpowerstats::DeviceRequest::GetEntities { responder }) => {
343 let entities: Vec<_> = reports
344 .iter()
345 .map(|stats| fpowerstats::Entity {
346 name: stats.entity.clone(),
347 states: vec![],
348 })
349 .collect();
350 let _ = responder.send(&entities);
351 }
352 Ok(fpowerstats::DeviceRequest::GetStats { responder }) => {
353 let _ = responder.send(reports);
354 }
355 _ => {}
356 }
357 }
358 })
359 .await;
360 }
361
362 fn spawn_server<F: Future<Output = ()> + 'static>(serve: impl FnOnce() -> F + Send + 'static) {
363 std::thread::spawn(move || fasync::LocalExecutor::default().run_singlethreaded(serve()));
364 }
365
366 fn start_test_service_dir(kernel: &Kernel) -> Arc<Simple> {
367 let dir = vfs::directory::immutable::simple();
368 let proxy = vfs::directory::serve_read_only(dir.clone(), vfs::ExecutionScope::new());
369 watch_service_dir_for_testing::<fpowerstats::ServiceMarker>(kernel, proxy);
370 dir
371 }
372
373 fn add_instance(
374 dir: &Arc<Simple>,
375 name: &str,
376 on_open: impl Fn(zx::Channel) + Send + Sync + 'static,
377 ) {
378 let instance_dir = vfs::pseudo_directory! {
379 "device" => vfs::service::endpoint(move |_scope, channel| {
380 on_open(channel.into_zx_channel());
381 }),
382 };
383 dir.add_entry_may_overwrite(name, instance_dir, true)
384 .expect("add service instance");
385 }
386
387 #[::fuchsia::test]
388 async fn discovers_entities_and_queries_stats() {
389 spawn_kernel_and_run(async |current_task| {
390 let kernel = current_task.kernel();
391 let dir = start_test_service_dir(kernel);
392 let manager = PowerStatsManager::get_or_init(kernel);
393 assert!(Arc::ptr_eq(&manager.inner, &PowerStatsManager::get_or_init(kernel).inner));
394 assert_eq!(manager.entity_stats("cpu_c2"), Ok(None));
395
396 let (notified_tx, mut notified_rx) = mpsc::unbounded();
397 manager.watch_providers(kernel, move |_, provider| {
398 let _ = notified_tx.unbounded_send(provider.instance_name().to_string());
399 });
400
401 add_instance(&dir, "default", move |server_end| {
402 spawn_server(move || {
403 let stream =
404 ServerEnd::<fpowerstats::DeviceMarker>::new(server_end).into_stream();
405 serve_provider(
406 stream,
407 vec![stats("cpu_c2", vec![state(0, 5, 120)]), stats("lpcm_soc", vec![])],
408 )
409 });
410 });
411
412 assert_eq!(notified_rx.next().await.as_deref(), Some("default"));
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 watch_providers_notifies_for_existing_and_new_providers() {
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 (proxy1, _server1) = create_sync_proxy::<fpowerstats::DeviceMarker>();
432 PowerStatsManager::add_provider_for_testing(
433 kernel,
434 PowerStatsProvider::new("first", ["first_entity"], proxy1),
435 );
436
437 let seen = Arc::new(AtomicUsize::new(0));
438 let seen_for_listener = seen.clone();
439 manager.watch_providers(kernel, move |_, _| {
440 seen_for_listener.fetch_add(1, Ordering::SeqCst);
441 });
442 assert_eq!(seen.load(Ordering::SeqCst), 1);
443
444 let (proxy2, _server2) = create_sync_proxy::<fpowerstats::DeviceMarker>();
445 PowerStatsManager::add_provider_for_testing(
446 kernel,
447 PowerStatsProvider::new("second", ["second_entity"], proxy2),
448 );
449 assert_eq!(seen.load(Ordering::SeqCst), 2);
450 })
451 .await;
452 }
453
454 #[::fuchsia::test]
455 async fn entity_stats_returns_eio_when_entity_omitted_from_stats_report() {
456 spawn_kernel_and_run_sync(|current_task| {
457 let kernel = current_task.kernel();
458 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
459
460 let (proxy, server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
461 spawn_server(move || {
462 serve_provider(server.into_stream(), vec![stats("b", vec![state(0, 1, 10)])])
463 });
464 PowerStatsManager::add_provider_for_testing(
465 kernel,
466 PowerStatsProvider::new("test", ["a", "b"], proxy),
467 );
468
469 assert!(manager.entity_stats("b").expect("GetStats succeeded").is_some());
470 assert_eq!(manager.entity_stats("a"), error!(EIO));
471 })
472 .await;
473 }
474
475 #[::fuchsia::test]
476 async fn entity_stats_returns_eio_when_provider_times_out() {
477 spawn_kernel_and_run_sync(|current_task| {
478 let kernel = current_task.kernel();
479 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
480
481 let (proxy, _server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
483 PowerStatsManager::add_provider_for_testing(
484 kernel,
485 PowerStatsProvider::new("hung", ["a"], proxy),
486 );
487
488 assert_eq!(manager.entity_stats("a"), error!(EIO));
489 })
490 .await;
491 }
492
493 #[::fuchsia::test]
494 async fn probe_service_instance_skips_failing_provider_and_discovers_subsequent_instances() {
495 spawn_kernel_and_run(async |current_task| {
496 let kernel = current_task.kernel();
497 let dir = start_test_service_dir(kernel);
498 let manager = PowerStatsManager::get_or_init(kernel);
499
500 let (notified_tx, mut notified_rx) = mpsc::unbounded();
501 manager.watch_providers(kernel, move |_, provider| {
502 let _ = notified_tx.unbounded_send(provider.instance_name().to_string());
503 });
504
505 add_instance(&dir, "broken_closed", |_| {});
506 add_instance(&dir, "healthy", move |server_end| {
507 spawn_server(move || {
508 let stream =
509 ServerEnd::<fpowerstats::DeviceMarker>::new(server_end).into_stream();
510 serve_provider(stream, vec![stats("cpu_c2", vec![state(0, 4, 80)])])
511 });
512 });
513
514 assert_eq!(notified_rx.next().await.as_deref(), Some("healthy"));
515 let cpu_c2 = manager
516 .entity_stats("cpu_c2")
517 .expect("GetStats succeeded")
518 .expect("cpu_c2 entity discovered from healthy provider");
519 assert_eq!(cpu_c2.state_residency_data[0].total_time_ms, 80);
520 })
521 .await;
522 }
523
524 #[::fuchsia::test]
525 async fn duplicate_entities_across_live_providers_keep_first_provider_and_serve_disjoint_entities()
526 {
527 spawn_kernel_and_run_sync(|current_task| {
528 let kernel = current_task.kernel();
529 let manager = PowerStatsManager::get_or_init_for_testing(kernel);
530
531 let (proxy1, server1) = create_sync_proxy::<fpowerstats::DeviceMarker>();
532 spawn_server(move || {
533 serve_provider(
534 server1.into_stream(),
535 vec![
536 stats("shared", vec![state(0, 1, 10)]),
537 stats("only_first", vec![state(0, 2, 20)]),
538 ],
539 )
540 });
541 PowerStatsManager::add_provider_for_testing(
542 kernel,
543 PowerStatsProvider::new("first", ["shared", "only_first"], proxy1),
544 );
545
546 let (proxy2, server2) = create_sync_proxy::<fpowerstats::DeviceMarker>();
547 spawn_server(move || {
548 serve_provider(
549 server2.into_stream(),
550 vec![
551 stats("shared", vec![state(0, 9, 999)]),
552 stats("only_second", vec![state(0, 3, 30)]),
553 ],
554 )
555 });
556 PowerStatsManager::add_provider_for_testing(
557 kernel,
558 PowerStatsProvider::new("second", ["shared", "only_second"], proxy2),
559 );
560
561 let announcements = Arc::new(Mutex::new(Vec::new()));
562 let announcements_for_listener = announcements.clone();
563 manager.watch_providers(kernel, move |_, provider| {
564 announcements_for_listener
565 .lock()
566 .push((provider.instance_name().to_string(), provider.entity_names().clone()));
567 });
568 assert_eq!(
569 *announcements.lock(),
570 vec![
571 (
572 "first".to_string(),
573 HashSet::from(["shared".to_string(), "only_first".to_string()])
574 ),
575 ("second".to_string(), HashSet::from(["only_second".to_string()])),
576 ]
577 );
578
579 let shared = manager.entity_stats("shared").unwrap().unwrap();
580 assert_eq!(shared.state_residency_data[0].total_time_ms, 10);
581
582 let only_first = manager.entity_stats("only_first").unwrap().unwrap();
583 assert_eq!(only_first.state_residency_data[0].total_time_ms, 20);
584
585 let only_second = manager.entity_stats("only_second").unwrap().unwrap();
586 assert_eq!(only_second.state_residency_data[0].total_time_ms, 30);
587 })
588 .await;
589 }
590
591 #[::fuchsia::test]
592 async fn watcher_disconnect_clears_provider_and_restarted_instance_recovers() {
593 spawn_kernel_and_run(async |current_task| {
594 let kernel = current_task.kernel();
595 let dir = start_test_service_dir(kernel);
596 let manager = PowerStatsManager::get_or_init(kernel);
597
598 let (announced_tx, mut announced_rx) = mpsc::unbounded();
599 manager.watch_providers(kernel, move |_, provider| {
600 let _ = announced_tx.unbounded_send((
601 provider.instance_name().to_string(),
602 provider.entity_names().clone(),
603 ));
604 });
605
606 let (stop_tx, stop_rx) = oneshot::channel::<()>();
607 let stop_rx = Arc::new(Mutex::new(Some(stop_rx)));
608 let (stopped_tx, stopped_rx) = oneshot::channel::<()>();
609 let stopped_tx = Arc::new(Mutex::new(Some(stopped_tx)));
610 add_instance(&dir, "first", move |server_end| {
611 let stop_rx = stop_rx.lock().take().expect("stop_rx taken once");
612 let stopped_tx = stopped_tx.lock().take().expect("stopped_tx taken once");
613 spawn_server(move || async move {
614 {
615 let stream =
616 ServerEnd::<fpowerstats::DeviceMarker>::new(server_end).into_stream();
617 let serve_fut = pin!(serve_provider(
618 stream,
619 vec![
620 stats("cpu_c2", vec![state(0, 5, 100)]),
621 stats("unrecovered_entity", vec![state(0, 1, 10)]),
622 ],
623 ));
624 let _ = select(serve_fut, stop_rx).await;
625 }
626 let _ = stopped_tx.send(());
627 });
628 });
629
630 assert_eq!(
631 announced_rx.next().await,
632 Some((
633 "first".to_string(),
634 HashSet::from(["cpu_c2".to_string(), "unrecovered_entity".to_string()]),
635 ))
636 );
637 let cpu_c2 = manager.entity_stats("cpu_c2").unwrap().unwrap();
638 assert_eq!(cpu_c2.state_residency_data[0].total_time_ms, 100);
639
640 let _ = stop_tx.send(());
642 let _ = stopped_rx.await;
643 assert_eq!(manager.entity_stats("cpu_c2"), error!(EIO));
644 assert_eq!(manager.entity_stats("unrecovered_entity"), error!(EIO));
645
646 add_instance(&dir, "first_restarted", move |server_end| {
650 spawn_server(move || {
651 let stream =
652 ServerEnd::<fpowerstats::DeviceMarker>::new(server_end).into_stream();
653 serve_provider(
654 stream,
655 vec![
656 stats("cpu_c2", vec![state(0, 7, 250)]),
657 stats("new_entity", vec![state(0, 1, 20)]),
658 ],
659 )
660 });
661 });
662
663 assert_eq!(
664 announced_rx.next().await,
665 Some(("first_restarted".to_string(), HashSet::from(["new_entity".to_string()])))
666 );
667 let recovered = manager.entity_stats("cpu_c2").unwrap().unwrap();
668 assert_eq!(recovered.state_residency_data[0].total_time_ms, 250);
669 assert_eq!(manager.entity_stats("unrecovered_entity"), error!(EIO));
670 })
671 .await;
672 }
673}