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    /// errno-style status reported by the service via sd_notify.
166    #[serde(rename = "StatusErrno")]
167    pub status_errno: Option<i32>,
168    /// Number of times systemd has restarted this service.
169    #[serde(rename = "NRestarts")]
170    pub n_restarts: Option<u32>,
171}
172
173/// A process reference, carrying the pid plus fields that disambiguate reuse.
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct ProcessId {
176    pub pid: Option<u32>,
177}
178
179/// Errors that can occur in the io.systemd.Unit interface.
180#[derive(Debug, Clone, PartialEq, ReplyError)]
181#[zlink(interface = "io.systemd.Unit")]
182pub enum UnitError {
183    /// No unit by that name is loaded.
184    NoSuchUnit,
185}
186
187impl std::fmt::Display for UnitError {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        match self {
190            UnitError::NoSuchUnit => write!(f, "No such unit"),
191        }
192    }
193}
194
195impl std::error::Error for UnitError {}