stalls/
lib.rs

1// Copyright 2024 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
5use fsync::Mutex;
6use futures_util::StreamExt;
7use std::sync::Arc;
8use zx::MonotonicDuration;
9use {fuchsia_async as fasync, fuchsia_sync as fsync};
10
11/// Holds the memory stall rate increase of the system.
12#[derive(Clone)]
13pub struct MemoryStallRate {
14    /// Measurement interval of the stall rate.
15    pub interval: MonotonicDuration,
16    /// Rate of memory stalls on some CPUs.
17    pub rate_some: zx::sys::zx_duration_mono_t,
18    /// Rate of memory stalls on all CPUs.
19    pub rate_full: zx::sys::zx_duration_mono_t,
20}
21
22pub trait StallProviderTrait: Sync + Send {
23    /// Return the current memory stall values from the kernel.
24    fn get_stall_info(&self) -> Result<zx::MemoryStall, anyhow::Error>;
25    /// Return the
26    fn get_stall_rate(&self) -> Option<MemoryStallRate>;
27}
28
29pub struct StallProvider {
30    /// Task that regularly polls the memory stall information from the kernel.
31    _monitoring_task: fasync::Task<Result<(), anyhow::Error>>,
32    /// Last values and rates of memory stalls computed.
33    last_stall_info: Arc<Mutex<StallInformation>>,
34    /// Memory stall kernel resource, for issuing queries.
35    stall_resource: Arc<dyn StallResource>,
36}
37
38struct StallInformation {
39    last_stall_values: zx::MemoryStall,
40    stall_rate: Option<MemoryStallRate>,
41}
42
43/// Trait for a resource exposing memory stall information. Used for dependency injection in unit
44/// tests.
45pub trait StallResource: Sync + Send {
46    fn get_memory_stall(&self) -> Result<zx::MemoryStall, zx::Status>;
47}
48
49impl StallResource for zx::Resource {
50    fn get_memory_stall(&self) -> Result<zx::MemoryStall, zx::Status> {
51        self.memory_stall()
52    }
53}
54
55impl StallProvider {
56    /// Create a new [StallProvider]. `stall_rate_interval` represents the polling delay between two
57    /// memory stall measurements, and the interval for memory stall rate computation.
58    pub fn new(
59        stall_rate_interval: MonotonicDuration,
60        stall_resource: Arc<dyn StallResource>,
61    ) -> Result<StallProvider, anyhow::Error> {
62        let last_stall_info = Arc::new(Mutex::new(StallInformation {
63            last_stall_values: stall_resource.get_memory_stall()?,
64            stall_rate: None,
65        }));
66        let stall_resource_clone = stall_resource.clone();
67        let last_stall_clone = last_stall_info.clone();
68        let monitoring_task = fasync::Task::spawn(async move {
69            let mut interval_timer = fasync::Interval::new(stall_rate_interval);
70            while let Some(()) = interval_timer.next().await {
71                let new_stall_values = stall_resource.get_memory_stall()?;
72
73                let mut stall_info = last_stall_clone.lock();
74                stall_info.stall_rate = Some(MemoryStallRate {
75                    interval: stall_rate_interval,
76                    rate_some: new_stall_values.stall_time_some
77                        - stall_info.last_stall_values.stall_time_some,
78                    rate_full: new_stall_values.stall_time_full
79                        - stall_info.last_stall_values.stall_time_full,
80                });
81
82                stall_info.last_stall_values = new_stall_values;
83            }
84            Ok(())
85        });
86
87        Ok(StallProvider {
88            _monitoring_task: monitoring_task,
89            last_stall_info,
90            stall_resource: stall_resource_clone,
91        })
92    }
93}
94
95impl StallProviderTrait for StallProvider {
96    fn get_stall_info(&self) -> Result<zx::MemoryStall, anyhow::Error> {
97        Ok(self.stall_resource.get_memory_stall()?)
98    }
99
100    fn get_stall_rate(&self) -> Option<MemoryStallRate> {
101        self.last_stall_info.lock().stall_rate.clone()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use fuchsia_async::MonotonicInstant;
108
109    use super::*;
110    use std::pin::pin;
111
112    struct FakeStallResource {
113        pub stall_value: Arc<Mutex<zx::MemoryStall>>,
114    }
115
116    impl StallResource for FakeStallResource {
117        fn get_memory_stall(&self) -> Result<zx::MemoryStall, zx::Status> {
118            Ok(self.stall_value.lock().clone())
119        }
120    }
121
122    #[test]
123    fn test_get_stall_rate() {
124        let mut exec = fasync::TestExecutor::new_with_fake_time();
125        exec.set_fake_time(MonotonicInstant::from_nanos(0));
126
127        let stall_value =
128            Arc::new(Mutex::new(zx::MemoryStall { stall_time_some: 0, stall_time_full: 0 }));
129
130        stall_value.lock().stall_time_some = 1;
131        stall_value.lock().stall_time_full = 2;
132
133        let stall_provider = StallProvider::new(
134            MonotonicDuration::from_minutes(1),
135            Arc::new(FakeStallResource { stall_value: stall_value.clone() }),
136        )
137        .expect("Failed to create StallProvider");
138
139        let current_stall_value = stall_provider.get_stall_info().expect("No stall info");
140        assert_eq!(stall_value.lock().stall_time_some, current_stall_value.stall_time_some);
141        assert_eq!(stall_value.lock().stall_time_full, current_stall_value.stall_time_full);
142        assert!(stall_provider.get_stall_rate().is_none());
143
144        assert!(exec
145            .run_until_stalled(&mut pin!(async {
146                fasync::TestExecutor::advance_to(MonotonicInstant::after(
147                    MonotonicDuration::from_seconds(1),
148                ))
149                .await
150            }))
151            .is_ready());
152
153        assert!(stall_provider.get_stall_rate().is_none());
154
155        stall_value.lock().stall_time_some = 2;
156        stall_value.lock().stall_time_full = 4;
157
158        assert!(exec
159            .run_until_stalled(&mut pin!(async {
160                fasync::TestExecutor::advance_to(MonotonicInstant::after(
161                    MonotonicDuration::from_seconds(60),
162                ))
163                .await
164            }))
165            .is_ready());
166
167        let rate = stall_provider.get_stall_rate().expect("No stall rate");
168
169        assert_eq!(1, rate.rate_some);
170        assert_eq!(2, rate.rate_full);
171
172        let current_stall_value = stall_provider.get_stall_info().expect("No stall info");
173        assert_eq!(stall_value.lock().stall_time_some, current_stall_value.stall_time_some);
174        assert_eq!(stall_value.lock().stall_time_full, current_stall_value.stall_time_full);
175    }
176}