1use struct_field_names_as_array::FieldNamesAsArray;
7use thiserror::Error;
8use tracing::error;
9
10#[derive(Error, Debug)]
11pub enum MonitordTimerError {
12 #[error("Timer D-Bus error: {0}")]
13 ZbusError(#[from] zbus::Error),
14}
15
16#[derive(
17 serde::Serialize, serde::Deserialize, Clone, Debug, Default, Eq, FieldNamesAsArray, PartialEq,
18)]
19
20pub struct TimerStats {
23 pub accuracy_usec: u64,
25 pub fixed_random_delay: bool,
27 pub last_trigger_usec: u64,
29 pub last_trigger_usec_monotonic: u64,
31 pub next_elapse_usec_monotonic: u64,
33 pub next_elapse_usec_realtime: u64,
35 pub persistent: bool,
37 pub randomized_delay_usec: u64,
39 pub remain_after_elapse: bool,
41 pub service_unit_last_state_change_usec: u64,
43 pub service_unit_last_state_change_usec_monotonic: u64,
45}
46
47pub const TIMER_STATS_FIELD_NAMES: &[&str] = &TimerStats::FIELD_NAMES_AS_ARRAY;
48
49#[tracing::instrument(level = "debug", skip(connection))]
50pub async fn collect_timer_stats(
51 connection: &zbus::Connection,
52 unit: &crate::units::ListedUnit,
53) -> Result<TimerStats, MonitordTimerError> {
54 let mut timer_stats = TimerStats::default();
55
56 let pt = crate::dbus::zbus_timer::TimerProxy::builder(connection)
57 .cache_properties(zbus::proxy::CacheProperties::No)
58 .path(unit.unit_object_path.clone())?
59 .build()
60 .await?;
61 let service_unit = pt.unit().await?;
64 let mut service_unit_last_state_change_usec: Result<u64, zbus::Error> = Ok(0);
65 let mut service_unit_last_state_change_usec_monotonic: Result<u64, zbus::Error> = Ok(0);
66 if service_unit.is_empty() {
67 error!("{}: No service unit name found for timer.", unit.name);
68 } else {
69 let mp = crate::dbus::zbus_systemd::ManagerProxy::builder(connection)
71 .cache_properties(zbus::proxy::CacheProperties::No)
72 .build()
73 .await?;
74 let service_unit_path = mp.get_unit(&service_unit).await?;
75 let up = crate::dbus::zbus_unit::UnitProxy::builder(connection)
77 .cache_properties(zbus::proxy::CacheProperties::No)
78 .path(service_unit_path)?
79 .build()
80 .await?;
81
82 (
83 service_unit_last_state_change_usec,
84 service_unit_last_state_change_usec_monotonic,
85 ) = tokio::join!(
86 up.state_change_timestamp(),
87 up.state_change_timestamp_monotonic(),
88 );
89 }
90 timer_stats.service_unit_last_state_change_usec = service_unit_last_state_change_usec?;
91 timer_stats.service_unit_last_state_change_usec_monotonic =
92 service_unit_last_state_change_usec_monotonic?;
93
94 let (
97 accuracy_usec,
98 fixed_random_delay,
99 last_trigger_usec,
100 last_trigger_usec_monotonic,
101 persistent,
102 next_elapse_usec_monotonic,
103 next_elapse_usec_realtime,
104 randomized_delay_usec,
105 remain_after_elapse,
106 ) = tokio::join!(
107 pt.accuracy_usec(),
108 pt.fixed_random_delay(),
109 pt.last_trigger_usec(),
110 pt.last_trigger_usec_monotonic(),
111 pt.persistent(),
112 pt.next_elapse_usec_monotonic(),
113 pt.next_elapse_usec_realtime(),
114 pt.randomized_delay_usec(),
115 pt.remain_after_elapse(),
116 );
117
118 timer_stats.accuracy_usec = accuracy_usec?;
119 timer_stats.fixed_random_delay = fixed_random_delay?;
120 timer_stats.last_trigger_usec = last_trigger_usec?;
121 timer_stats.last_trigger_usec_monotonic = last_trigger_usec_monotonic?;
122 timer_stats.persistent = persistent?;
123 timer_stats.next_elapse_usec_monotonic = next_elapse_usec_monotonic?;
124 timer_stats.next_elapse_usec_realtime = next_elapse_usec_realtime?;
125 timer_stats.randomized_delay_usec = randomized_delay_usec?;
126 timer_stats.remain_after_elapse = remain_after_elapse?;
127
128 Ok(timer_stats)
129}
130
131pub async fn collect_all_timers_dbus(
137 connection: &zbus::Connection,
138 config: &crate::config::Config,
139) -> anyhow::Result<crate::units::SystemdUnitStats> {
140 use std::collections::HashMap;
141 use tracing::debug;
142
143 if !config.timers.enabled {
144 return Ok(crate::units::SystemdUnitStats::default());
145 }
146
147 let p = crate::dbus::zbus_systemd::ManagerProxy::builder(connection)
148 .cache_properties(zbus::proxy::CacheProperties::No)
149 .build()
150 .await?;
151 let units = p.list_units().await?;
152
153 let mut stats = crate::units::SystemdUnitStats::default();
154 let mut timer_stats_map = HashMap::new();
155
156 for unit_raw in units {
157 let unit: crate::units::ListedUnit = unit_raw.into();
158 if !unit.name.contains(".timer") {
159 continue;
160 }
161 if config.timers.blocklist.contains(&unit.name) {
162 debug!("Skipping timer stats for {} due to blocklist", &unit.name);
163 continue;
164 }
165 if !config.timers.allowlist.is_empty() && !config.timers.allowlist.contains(&unit.name) {
166 continue;
167 }
168 match collect_timer_stats(connection, &unit).await {
169 Ok(ts) => {
170 if ts.persistent {
171 stats.timer_persistent_units += 1;
172 }
173 if ts.remain_after_elapse {
174 stats.timer_remain_after_elapse += 1;
175 }
176 timer_stats_map.insert(unit.name.clone(), ts);
177 }
178 Err(err) => {
179 error!("Failed to get {} stats: {:#?}", &unit.name, err);
180 }
181 }
182 }
183
184 stats.timer_stats = timer_stats_map;
185 Ok(stats)
186}