| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| //! Pressure stall information (PSI) utilities. | |
| //! | |
| //! PSI documentation: https://docs.kernel.org/accounting/psi.html | |
| use std::fs::File; | |
| use std::fs::OpenOptions; | |
| use std::io; | |
| use std::io::Write; | |
| use std::os::unix::fs::OpenOptionsExt; | |
| use std::os::unix::io::AsRawFd; | |
| use std::time::Duration; | |
| use anyhow::bail; | |
| use anyhow::Context; | |
| use anyhow::Result; | |
| use tokio::io::unix::AsyncFd; | |
| use tokio::io::Interest; | |
| use tokio::time::timeout; | |
| /// Wait for PSI monitor event that memory stall time exceeded a certain threshold in recent time | |
| /// window. Returns Ok(true) if the PSI monitor event is triggered. Returns Ok(false) when waiting | |
| /// time exceeded `max_waiting_ms`. | |
| /// | |
| /// # Arguments | |
| /// | |
| /// * `stall_ms` - Memory stall time in millisecond to trigger the PSI monitor event. | |
| /// * `window_ms` - Time window in millisecond to check the stall threshold. | |
| /// * `min_waiting_ms` - Minimal waiting time in millisecond. Used to prevent too frequent | |
| /// triggering. | |
| /// * `max_waiting_ms` - Maximal waiting time in millisecond. Used to prevent indefinite waiting. | |
| /// | |
| /// PSI monitor documentation: https://docs.kernel.org/accounting/psi.html#monitoring-for-pressure-thresholds | |
| pub async fn wait_psi_monitor_memory_event( | |
| stall_ms: u64, | |
| window_ms: u64, | |
| min_waiting_ms: u64, | |
| max_waiting_ms: u64, | |
| ) -> Result<bool> { | |
| // Check the parameters. | |
| if stall_ms > window_ms { | |
| bail!("The stall time couldn't be larger than the time window."); | |
| } | |
| if min_waiting_ms > max_waiting_ms { | |
| bail!("The minimal waiting time couldn't be larger than the maximal waiting time."); | |
| } | |
| let mut monitor_fd = OpenOptions::new() | |
| .read(true) | |
| .write(true) | |
| .custom_flags(libc::O_NONBLOCK) | |
| .open("/proc/pressure/memory") | |
| .context("Failed to open /proc/pressure/memory")?; | |
| // The config shall be a C Style null terminated string. | |
| let monitor_config = format!("some {} {}\0", stall_ms * 1000, window_ms * 1000); | |
| monitor_fd | |
| .write(monitor_config.as_bytes()) | |
| .context("Failed to write psi memory file")?; | |
| let async_fd = AsyncFd::with_interest(monitor_fd.as_raw_fd(), Interest::PRIORITY) | |
| .context("Failed to Create AsyncFd")?; | |
| tokio::time::sleep(Duration::from_millis(min_waiting_ms)).await; | |
| match timeout( | |
| Duration::from_millis(max_waiting_ms - min_waiting_ms), | |
| async_fd.readable(), | |
| ) | |
| .await | |
| { | |
| Ok(_guard) => Ok(true), // Got psi monitor event. | |
| Err(_) => Ok(false), // Wait for psi monitor timed out. | |
| } | |
| } | |
| /// The target of the PSI. Either "some" or "full". | |
| pub enum Target { | |
| Some, | |
| Full, | |
| } | |
| impl Target { | |
| fn to_str(&self) -> &'static str { | |
| match self { | |
| Target::Some => "some", | |
| Target::Full => "full", | |
| } | |
| } | |
| } | |
| /// A watcher for PSI events. | |
| pub struct PsiWatcher { | |
| fd: AsyncFd<File>, | |
| } | |
| impl PsiWatcher { | |
| /// Creates a new PSI watcher for memory pressure. | |
| pub fn new_memory_pressure( | |
| target: Target, | |
| stall: Duration, | |
| window: Duration, | |
| ) -> io::Result<Self> { | |
| Self::new("/proc/pressure/memory", target, stall, window) | |
| } | |
| fn new(path: &str, target: Target, stall: Duration, window: Duration) -> io::Result<Self> { | |
| let mut file = OpenOptions::new().read(true).write(true).open(path)?; | |
| let monitor_config = format!( | |
| "{} {} {}\0", | |
| target.to_str(), | |
| stall.as_micros(), | |
| window.as_micros() | |
| ); | |
| file.write_all(monitor_config.as_bytes())?; | |
| // Monitor POLLPRI for PSI events. | |
| // https://docs.kernel.org/accounting/psi.html#userspace-monitor-usage-example | |
| let fd = AsyncFd::with_interest(file, Interest::PRIORITY)?; | |
| Ok(Self { fd }) | |
| } | |
| /// Waits for a PSI event to occur. | |
| pub async fn wait(&mut self) -> io::Result<()> { | |
| self.fd.readable().await?.clear_ready(); | |
| Ok(()) | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| #[tokio::test] | |
| async fn test_wait_psi_monitor_memory_event() { | |
| const MIN_WAITING_MS: u64 = 500; | |
| const MAX_WAITING_MS: u64 = 10000; | |
| const STALL_MS: u64 = 150; | |
| const WINDOW_MS: u64 = 1000; | |
| const WRONG_STALL_MS: u64 = 1001; | |
| const WRONG_MIN_WAITING_MS: u64 = 10001; | |
| // It should return error when stall is larger than window. | |
| assert!(wait_psi_monitor_memory_event( | |
| WRONG_STALL_MS, | |
| WINDOW_MS, | |
| MIN_WAITING_MS, | |
| MAX_WAITING_MS | |
| ) | |
| .await | |
| .is_err()); | |
| // It should return error when min waiting is larger than max waiting. | |
| assert!(wait_psi_monitor_memory_event( | |
| STALL_MS, | |
| WINDOW_MS, | |
| WRONG_MIN_WAITING_MS, | |
| MAX_WAITING_MS | |
| ) | |
| .await | |
| .is_err()); | |
| } | |
| } |