Skip to main content

monitord/varlink/
unit.rs

1//! Varlink proxy for the io.systemd.Unit interface on PID 1's socket.
2//! Adapted from the interface definition in systemd's
3//! `src/core/varlink-unit.c`. The `List` method exists from v258, but the
4//! per-type context sections monitord reads land later: `src/core/varlink-service.c`
5//! and `src/core/varlink-timer.c` first appear in **v261**, so that is the real
6//! minimum for anything here.
7//!
8//! Only the fields monitord maps onto its own stats types are declared; serde
9//! drops the rest, which is most of a ~7KB per-unit reply.
10//!
11//! Note that systemd omits fields sitting at their default rather than sending
12//! a zero, so almost everything here is optional and the caller supplies the
13//! default (see `varlink_units`, where those defaults have to match what the
14//! D-Bus properties return for the same unit).
15
16use serde::{Deserialize, Serialize};
17use zlink::{proxy, ReplyError};
18
19/// Proxy trait for calling methods on the io.systemd.Unit interface.
20#[proxy("io.systemd.Unit")]
21pub trait Unit {
22    /// Look up a single unit by name.
23    ///
24    /// Called without the `more` flag and with a name, this returns one unit
25    /// rather than streaming every unit — which is what monitord wants: a
26    /// filtered call costs ~0.5ms against ~92ms to stream all 305 units on a
27    /// test host, and monitord only needs a handful per collection.
28    async fn list(&mut self, name: Option<&str>) -> zlink::Result<Result<ListOutput, UnitError>>;
29}
30
31/// Output parameters for the List method.
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ListOutput {
34    /// Unit configuration.
35    pub context: Option<UnitContext>,
36    /// Unit runtime state.
37    pub runtime: Option<UnitRuntime>,
38}
39
40/// Configuration of a unit.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct UnitContext {
43    #[serde(rename = "Service")]
44    pub service: Option<ServiceContext>,
45    #[serde(rename = "Exec")]
46    pub exec: Option<ExecContext>,
47    #[serde(rename = "Timer")]
48    pub timer: Option<TimerContext>,
49}
50
51/// Execution configuration shared by every unit type that runs processes.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct ExecContext {
54    /// Timeout for cleaning up resources after the unit exits. Lives here
55    /// rather than under `Service`, unlike the D-Bus property of the same name.
56    #[serde(rename = "TimeoutCleanUSec")]
57    pub timeout_clean_usec: Option<u64>,
58}
59
60/// Timer-specific configuration.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct TimerContext {
63    /// Unit this timer triggers, e.g. "logrotate.service".
64    #[serde(rename = "Unit")]
65    pub unit: Option<String>,
66    #[serde(rename = "AccuracyUSec")]
67    pub accuracy_usec: Option<u64>,
68    #[serde(rename = "RandomizedDelayUSec")]
69    pub randomized_delay_usec: Option<u64>,
70    #[serde(rename = "FixedRandomDelay")]
71    pub fixed_random_delay: Option<bool>,
72    #[serde(rename = "Persistent")]
73    pub persistent: Option<bool>,
74    #[serde(rename = "RemainAfterElapse")]
75    pub remain_after_elapse: Option<bool>,
76}
77
78/// Service-specific configuration.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ServiceContext {
81    /// Service type: simple, oneshot, notify, notify-reload, …
82    #[serde(rename = "Type")]
83    pub r#type: Option<String>,
84    /// Configured restart delay in microseconds.
85    #[serde(rename = "RestartUSec")]
86    pub restart_usec: Option<u64>,
87    /// Configured watchdog timeout in microseconds.
88    ///
89    /// This is the *configured* value; the D-Bus property of the same name is
90    /// the currently armed one, which reads `infinity` while a service is not
91    /// running. They agree for running services, which is the case monitord
92    /// collects, and systemd exposes no runtime equivalent over varlink.
93    #[serde(rename = "WatchdogUSec")]
94    pub watchdog_usec: Option<u64>,
95}
96
97/// Runtime state of a unit.
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct UnitRuntime {
100    #[serde(rename = "StateChangeTimestamp")]
101    pub state_change_timestamp: Option<Timestamp>,
102    #[serde(rename = "ActiveEnterTimestamp")]
103    pub active_enter_timestamp: Option<Timestamp>,
104    #[serde(rename = "InactiveExitTimestamp")]
105    pub inactive_exit_timestamp: Option<Timestamp>,
106    /// Emitted once the unit has actually left the active state; absent (not
107    /// zero) before that.
108    #[serde(rename = "ActiveExitTimestamp")]
109    pub active_exit_timestamp: Option<Timestamp>,
110    #[serde(rename = "CGroup")]
111    pub cgroup: Option<CGroupRuntime>,
112    #[serde(rename = "Service")]
113    pub service: Option<ServiceRuntime>,
114    #[serde(rename = "Timer")]
115    pub timer: Option<TimerRuntime>,
116}
117
118/// Timer-specific runtime state.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct TimerRuntime {
121    /// Next elapse on CLOCK_REALTIME; 0 for a purely monotonic timer.
122    #[serde(rename = "NextElapseUSecRealtime")]
123    pub next_elapse_usec_realtime: Option<u64>,
124    #[serde(rename = "NextElapseUSecMonotonic")]
125    pub next_elapse_usec_monotonic: Option<u64>,
126    /// When the timer last fired; absent if it never has.
127    #[serde(rename = "LastTriggerUSec")]
128    pub last_trigger_usec: Option<Timestamp>,
129}
130
131/// A systemd timestamp, carrying both clocks.
132#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
133pub struct Timestamp {
134    pub realtime: Option<u64>,
135    pub monotonic: Option<u64>,
136}
137
138/// cgroup accounting for a unit. Fields appear only when the matching
139/// accounting option is enabled for the unit.
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct CGroupRuntime {
142    /// cgroup path, relative to the cgroup mount point.
143    #[serde(rename = "Path")]
144    pub path: Option<String>,
145    #[serde(rename = "CPUUsageNSec")]
146    pub cpu_usage_nsec: Option<u64>,
147    #[serde(rename = "MemoryCurrent")]
148    pub memory_current: Option<u64>,
149    #[serde(rename = "MemoryAvailable")]
150    pub memory_available: Option<u64>,
151    #[serde(rename = "TasksCurrent")]
152    pub tasks_current: Option<u64>,
153    #[serde(rename = "IOReadBytes")]
154    pub io_read_bytes: Option<u64>,
155    #[serde(rename = "IOReadOperations")]
156    pub io_read_operations: Option<u64>,
157}
158
159/// Service-specific runtime state.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ServiceRuntime {
162    /// Main process of the service, if it has one.
163    #[serde(rename = "MainPID")]
164    pub main_pid: Option<ProcessId>,
165    /// Control process (ExecStartPre/ExecReload/ExecStop/…), if one is
166    /// running. Folded into the process count the way `GetProcesses` does,
167    /// since it can sit outside the cgroup.
168    #[serde(rename = "ControlPID")]
169    pub control_pid: Option<ProcessId>,
170    /// errno-style status reported by the service via sd_notify.
171    #[serde(rename = "StatusErrno")]
172    pub status_errno: Option<i32>,
173    /// Number of times systemd has restarted this service.
174    #[serde(rename = "NRestarts")]
175    pub n_restarts: Option<u32>,
176}
177
178/// A process reference, carrying the pid plus fields that disambiguate reuse.
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct ProcessId {
181    pub pid: Option<u32>,
182}
183
184/// Errors that can occur in the io.systemd.Unit interface.
185#[derive(Debug, Clone, PartialEq, ReplyError)]
186#[zlink(interface = "io.systemd.Unit")]
187pub enum UnitError {
188    /// No unit by that name is loaded.
189    NoSuchUnit,
190    /// The unit is masked.
191    UnitMasked,
192    /// The unit is in a fatal error state (e.g. its unit file failed to load).
193    UnitError,
194}
195
196impl std::fmt::Display for UnitError {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        match self {
199            UnitError::NoSuchUnit => write!(f, "No such unit"),
200            UnitError::UnitMasked => write!(f, "Unit is masked"),
201            UnitError::UnitError => write!(f, "Unit is in a fatal error state"),
202        }
203    }
204}
205
206impl std::error::Error for UnitError {}