Skip to main content

scxtui/
kernel.rs

1// SPDX-License-Identifier: GPL-2.0
2
3//! The kernel's own view of `sched_ext`, read straight from sysfs.
4//!
5//! `scx.service` only knows whether its unit is active; the kernel knows
6//! what is actually attached. Reading `/sys/kernel/sched_ext` lets the UI
7//! show ground truth and flag disagreements — a scheduler launched by hand
8//! next to the service, a scheduler that got kicked out by the watchdog
9//! while the unit is still active, and so on. Everything here is plain
10//! world-readable sysfs, so no privileges are needed.
11
12use std::fs;
13use std::path::Path;
14
15use anyhow::{Context, Result};
16
17const SYSFS_DIR: &str = "/sys/kernel/sched_ext";
18
19#[derive(Debug, Clone, PartialEq)]
20pub struct KernelState {
21    /// Contents of `state`: "enabled", "disabled", or a transitional value
22    /// ("enabling"/"disabling") if we catch a switch mid-flight.
23    pub state: String,
24    /// BPF ops name from `root/ops` when a scheduler is attached — the name
25    /// the scheduler registered with, typically without the `scx_` prefix
26    /// (e.g. "lavd" for `scx_lavd`).
27    pub ops: Option<String>,
28}
29
30impl KernelState {
31    pub fn enabled(&self) -> bool {
32        self.state == "enabled"
33    }
34
35    /// Whether `sched_name` (full name, e.g. `scx_lavd`) plausibly matches
36    /// the ops the kernel reports. Ops names are chosen by each scheduler
37    /// and some embed extra detail, with or without the `scx_` prefix —
38    /// bpfland registers e.g. `bpfland_1.1.2_x86_64_unknown_linux_gnu`,
39    /// flow registers `scx_flow_3.1.1_x86_64_unknown_linux_gnu` — so
40    /// beyond an exact match (with or without the prefix) an ops name
41    /// that *starts with* either form of the scheduler name followed by
42    /// `_` also counts. Used for a soft warning only, so a rare false
43    /// negative is acceptable.
44    pub fn matches(&self, sched_name: &str) -> bool {
45        let stripped = sched_name.strip_prefix("scx_").unwrap_or(sched_name);
46        self.ops.as_deref().is_some_and(|ops| {
47            ops == sched_name
48                || ops == stripped
49                || ops
50                    .strip_prefix(sched_name)
51                    .is_some_and(|rest| rest.starts_with('_'))
52                || ops
53                    .strip_prefix(stripped)
54                    .is_some_and(|rest| rest.starts_with('_'))
55        })
56    }
57}
58
59/// Reads the kernel's `sched_ext` state. `Ok(None)` means the sysfs
60/// directory is absent, i.e. the running kernel was built without
61/// `sched_ext`; `Err` means it is there but `state` could not be read,
62/// which is a different fact and must not be reported as the former.
63/// `root/ops` is absent while nothing is attached, so NotFound on it is
64/// not an error; any other failure on it is.
65pub fn read() -> Result<Option<KernelState>> {
66    let dir = Path::new(SYSFS_DIR);
67    if !dir.is_dir() {
68        return Ok(None);
69    }
70    let state = fs::read_to_string(dir.join("state"))
71        .with_context(|| format!("cannot read {}/state", dir.display()))?
72        .trim()
73        .to_owned();
74    // `root/ops` exists only while a scheduler is attached, so NotFound
75    // is the normal "nothing attached" answer; any other failure is a
76    // broken observation and must not pass for one.
77    let ops = match fs::read_to_string(dir.join("root/ops")) {
78        Ok(s) => Some(s.trim().to_owned()).filter(|s| !s.is_empty()),
79        Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
80        Err(err) => {
81            return Err(err).with_context(|| format!("cannot read {}/root/ops", dir.display()));
82        }
83    };
84    Ok(Some(KernelState { state, ops }))
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    fn enabled_with(ops: Option<&str>) -> KernelState {
92        KernelState {
93            state: "enabled".to_owned(),
94            ops: ops.map(str::to_owned),
95        }
96    }
97
98    #[test]
99    fn matches_stripped_ops_name() {
100        assert!(enabled_with(Some("lavd")).matches("scx_lavd"));
101    }
102
103    #[test]
104    fn matches_full_ops_name() {
105        assert!(enabled_with(Some("scx_lavd")).matches("scx_lavd"));
106    }
107
108    #[test]
109    fn matches_versioned_ops_suffix() {
110        // bpfland registers e.g. "bpfland_1.1.2_x86_64_unknown_linux_gnu".
111        assert!(
112            enabled_with(Some("bpfland_1.1.2_x86_64_unknown_linux_gnu")).matches("scx_bpfland")
113        );
114    }
115
116    #[test]
117    fn matches_versioned_ops_suffix_with_prefix() {
118        // flow keeps the scx_ prefix in its versioned ops name.
119        assert!(enabled_with(Some("scx_flow_3.1.1_x86_64_unknown_linux_gnu")).matches("scx_flow"));
120    }
121
122    #[test]
123    fn rejects_full_name_prefix_without_separator() {
124        assert!(!enabled_with(Some("scx_flowmatic_1.0")).matches("scx_flow"));
125    }
126
127    #[test]
128    fn rejects_a_different_scheduler() {
129        assert!(!enabled_with(Some("flow_1.0")).matches("scx_flash"));
130    }
131
132    #[test]
133    fn rejects_prefix_without_separator() {
134        assert!(!enabled_with(Some("bpflandish")).matches("scx_bpfland"));
135    }
136
137    #[test]
138    fn no_ops_never_matches() {
139        assert!(!enabled_with(None).matches("scx_lavd"));
140    }
141}