Skip to main content

starnix_core/device/
power_stats.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Per-[`Kernel`] discovery and querying of `fuchsia.hardware.power.stats.Service` providers.
6
7use 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
20/// How long to wait for a power stats provider to respond to a FIDL request before failing.
21const PROVIDER_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(1);
22
23/// Connected `fuchsia.hardware.power.stats.Service` provider instance and its reported entities.
24#[derive(Debug)]
25pub struct PowerStatsProvider {
26    instance_name: String,
27    entity_names: HashSet<String>,
28    proxy: fpowerstats::DeviceSynchronousProxy,
29}
30
31impl PowerStatsProvider {
32    /// Creates a [`PowerStatsProvider`] for `instance_name` serving `entity_names` over `proxy`.
33    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    /// Name of the service instance within `fuchsia.hardware.power.stats.Service`.
46    pub fn instance_name(&self) -> &str {
47        &self.instance_name
48    }
49
50    /// Names of the newly discovered power entities announced by this provider.
51    ///
52    /// Entities that were already announced by an earlier provider (whether still active or
53    /// disconnected) are excluded so listeners observe each discovered entity name only once.
54    pub fn entity_names(&self) -> &HashSet<String> {
55        &self.entity_names
56    }
57
58    /// Returns whether the underlying provider channel has observed `PEER_CLOSED`.
59    fn is_closed(&self) -> bool {
60        self.proxy.is_closed().unwrap_or(true)
61    }
62
63    /// Queries the provider for the current residency [`fpowerstats::Stats`] of `entity_name`.
64    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    /// Discovered provider instances that announced new entities, in discovery order.
89    providers: Vec<Arc<PowerStatsProvider>>,
90    /// Active or last-known provider serving each discovered entity name.
91    entity_providers: HashMap<String, Arc<PowerStatsProvider>>,
92    /// Listeners notified whenever a provider instance announces new entities.
93    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/// Internal state stored in [`Kernel::expando`] so callers must access [`PowerStatsManager`] via
107/// [`PowerStatsManager::get_or_init`].
108#[derive(Debug, Default)]
109struct PowerStatsManagerInner {
110    watch_started: AtomicBool,
111    state: LockDepMutex<PowerStatsManagerState, PowerStatsManagerLock>,
112}
113
114/// Handle to the per-[`Kernel`] singleton that watches `fuchsia.hardware.power.stats.Service` on
115/// the kernel's main async executor and tracks discovered power entities for kernel subsystems and
116/// modules.
117///
118/// Discovery is performed asynchronously as providers appear in `/svc`, while
119/// [`Self::entity_stats`] looks up the [`PowerStatsProvider`] serving a given entity and issues the
120/// synchronous `GetStats` call outside the internal lock.
121#[derive(Clone, Debug)]
122pub struct PowerStatsManager {
123    inner: Arc<PowerStatsManagerInner>,
124}
125
126impl PowerStatsManager {
127    /// Returns the per-[`Kernel`] [`PowerStatsManager`], starting the asynchronous watcher for
128    /// `/svc/fuchsia.hardware.power.stats.Service` on first access.
129    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    /// Returns the per-[`Kernel`] [`PowerStatsManager`] for unit testing, suppressing the
143    /// background `/svc` watcher if it has not been started yet.
144    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    /// Registers `provider` with the per-[`Kernel`] [`PowerStatsManager`] for unit testing,
151    /// suppressing the background `/svc` watcher if it has not been started yet.
152    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    /// Registers `listener` to be invoked for every [`PowerStatsProvider`] that announces newly
157    /// discovered power entities, including any providers discovered prior to this call.
158    ///
159    /// Each discovered entity name is announced at most once across all provider notifications:
160    /// if a provider disconnects and a restarted provider re-registers the same entities,
161    /// [`Self::entity_stats`] switches to the new provider while listeners are only invoked for
162    /// any previously unannounced entities.
163    ///
164    /// Listeners are invoked while holding [`PowerStatsManagerLock`], which is ordered before
165    /// [`DeviceRegistryStateLevel`](starnix_sync::DeviceRegistryStateLevel) and
166    /// [`SimpleDirectoryEntriesLock`](starnix_sync::SimpleDirectoryEntriesLock) so listeners may
167    /// register devices and sysfs nodes without race windows between existing and newly discovered
168    /// providers. Because [`PowerStatsManagerLock`] is held during the callback, `listener` must
169    /// not synchronously re-enter [`PowerStatsManager`] methods.
170    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    /// Queries the current residency [`fpowerstats::Stats`] for `entity_name`, returning
182    /// `Ok(None)` if no provider reporting `entity_name` has been discovered yet.
183    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            // Listeners only observe newly announced entities on subsequent providers.
455            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            // The first provider to report `cpu_c2` continues to serve it even when a second
478            // provider reports a duplicate `cpu_c2`.
479            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            // Querying an entity served by a closed provider returns `EIO` until a replacement
486            // provider registers for that entity, even if an unrelated provider registers in the
487            // meantime.
488            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            // When a restarted provider registers, it replaces the closed provider for
498            // `dead_entity` without re-announcing `dead_entity` to existing or late listeners,
499            // while `unrecovered_entity` continues returning `EIO`.
500            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            // Keep `_server` open without servicing requests so `GetStats` times out.
598            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}