roboplc/
system.rs

1use crate::{is_realtime, Result};
2use core::fmt;
3use std::convert::Infallible;
4use std::str::FromStr;
5
6/// Configure system parameters (global) while the process is running. Does nothing in simulated
7/// mode. A wrapper around [`rtsc::system::linux::SystemConfig`] which respects simulated/real-time
8/// mode.
9///
10/// Example:
11///
12/// ```rust,no_run
13/// use roboplc::system::SystemConfig;
14///
15/// let _sys = SystemConfig::new().set("kernel/sched_rt_runtime_us", -1)
16///     .apply()
17///     .expect("Unable to set system config");
18/// // some code
19/// // system config is restored at the end of the scope
20/// ```
21#[allow(clippy::module_name_repetitions)]
22#[derive(Default)]
23pub struct SystemConfig(rtsc::system::linux::SystemConfig);
24
25impl SystemConfig {
26    /// Creates a new system config object
27    #[must_use]
28    pub fn new() -> Self {
29        Self::default()
30    }
31    /// Set a parameter to configure
32    pub fn set<V: fmt::Display>(mut self, key: &'static str, value: V) -> Self {
33        if is_realtime() {
34            self.0 = self.0.set(key, value);
35        }
36        self
37    }
38    /// Apply values to /proc/sys keys
39    pub fn apply(self) -> Result<rtsc::system::linux::SystemConfigGuard> {
40        if is_realtime() {
41            return self.0.apply().map_err(Into::into);
42        }
43        Ok(rtsc::system::linux::SystemConfigGuard::default())
44    }
45}
46
47/// Configure CPU governors for the given CPUs. A wrapper around
48/// [`rtsc::system::linux::CpuGovernor`] which respects simulated/real-time mode.
49pub struct CpuGovernor(#[allow(dead_code)] rtsc::system::linux::CpuGovernor);
50
51impl CpuGovernor {
52    /// Set performance governor for the given CPUs. This sets the maximum frequency for the CPUs,
53    /// increasing the power consumption but lowering their latency. It is enough to specify a
54    /// single logical core number per physical core. The governor is restored when the returned
55    /// guard object is dropped.
56    pub fn performance<I>(performance_cpus: I) -> Result<CpuGovernor>
57    where
58        I: IntoIterator<Item = usize>,
59    {
60        if is_realtime() {
61            let inner = rtsc::system::linux::CpuGovernor::performance(performance_cpus)?;
62            Ok(Self(inner))
63        } else {
64            Ok(Self(rtsc::system::linux::CpuGovernor::default()))
65        }
66    }
67}
68
69/// Standard systemd system state variants.
70#[derive(Debug, Copy, Clone, Eq, PartialEq)]
71pub enum StateVariant {
72    /// The system is initializing.
73    Initializing,
74    /// The system is starting.
75    Starting,
76    /// The system is running.
77    Running,
78    /// The system is degraded.
79    Degraded,
80    /// The system is in maintenance mode.
81    Maintenance,
82    /// The system is stopping.
83    Stopping,
84    /// The system is in some other state.
85    Other,
86}
87
88impl fmt::Display for StateVariant {
89    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90        match *self {
91            StateVariant::Initializing => write!(f, "initializing"),
92            StateVariant::Starting => write!(f, "starting"),
93            StateVariant::Running => write!(f, "running"),
94            StateVariant::Degraded => write!(f, "degraded"),
95            StateVariant::Maintenance => write!(f, "maintenance"),
96            StateVariant::Stopping => write!(f, "stopping"),
97            StateVariant::Other => write!(f, "other"),
98        }
99    }
100}
101
102impl FromStr for StateVariant {
103    type Err = Infallible;
104
105    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
106        Ok(match s {
107            "initializing" => StateVariant::Initializing,
108            "starting" => StateVariant::Starting,
109            "running" => StateVariant::Running,
110            "degraded" => StateVariant::Degraded,
111            "maintenance" => StateVariant::Maintenance,
112            "stopping" => StateVariant::Stopping,
113            _ => StateVariant::Other,
114        })
115    }
116}
117
118/// Get the current system state. Use CLI instead of direct D-Bus calls to avoid unnecessary
119/// dependencies.
120pub fn state() -> Result<StateVariant> {
121    std::process::Command::new("systemctl")
122        .arg("is-system-running")
123        .output()
124        .map_err(Into::into)
125        .and_then(|output| {
126            let state = std::str::from_utf8(&output.stdout).unwrap_or_default();
127            state.trim().parse().map_err(Into::into)
128        })
129}
130
131/// Wait until the system is in the running state.
132pub fn wait_running_state() -> Result<()> {
133    loop {
134        if state()? == StateVariant::Running {
135            break;
136        }
137        std::thread::sleep(std::time::Duration::from_millis(500));
138    }
139    Ok(())
140}
141
142// A variant with D-Bus for future reference
143/*
144let connection = Connection::new_system()?;
145let proxy = connection.with_proxy(
146    "org.freedesktop.systemd1",
147    "/org/freedesktop/systemd1",
148    Duration::from_millis(5000),
149);
150let (state_variant,): (Variant<String>,) = proxy.method_call(
151    "org.freedesktop.DBus.Properties",
152    "Get",
153    ("org.freedesktop.systemd1.Manager", "SystemState"),
154)?;
155state_variant.0.parse::<SystemStateVariant>()?;
156*/