1use std::collections::HashMap;
22
23use tracing::debug;
24
25use crate::timer::TimerStats;
26use crate::units::ServiceStats;
27use crate::varlink::unit::{ListOutput, Unit};
28
29pub use crate::varlink::manager::MANAGER_SOCKET_PATH;
30
31const UNSET: u64 = u64::MAX;
34
35pub struct UnitLookup {
42 connection: zlink::tokio::unix::Connection,
43 cache: HashMap<String, Option<ListOutput>>,
44}
45
46impl UnitLookup {
47 pub async fn connect(
48 endpoint: &crate::varlink::endpoint::VarlinkEndpoint,
49 ) -> anyhow::Result<Self> {
50 Ok(Self {
51 connection: endpoint.connect().await?,
52 cache: HashMap::new(),
53 })
54 }
55
56 pub async fn get(&mut self, name: &str) -> anyhow::Result<Option<&ListOutput>> {
68 if !self.cache.contains_key(name) {
69 let fetched = match self.connection.list(Some(name)).await? {
70 Ok(output) => Some(output),
71 Err(err) => {
72 debug!("No unit {} over varlink: {}", name, err);
73 None
74 }
75 };
76 self.cache.insert(name.to_string(), fetched);
77 }
78 Ok(self.cache.get(name).and_then(|entry| entry.as_ref()))
79 }
80
81 pub fn fetches(&self) -> u64 {
83 self.cache.len() as u64
84 }
85}
86
87pub fn is_oneshot(output: &ListOutput) -> bool {
92 output
93 .context
94 .as_ref()
95 .and_then(|context| context.service.as_ref())
96 .and_then(|service| service.r#type.as_deref())
97 == Some("oneshot")
98}
99
100pub fn map_service_stats(output: &ListOutput, cgroup: &crate::cgroup::CgroupStats) -> ServiceStats {
109 let runtime = output.runtime.as_ref();
110 let cgroup_reply = runtime.and_then(|runtime| runtime.cgroup.as_ref());
111 let service_runtime = runtime.and_then(|runtime| runtime.service.as_ref());
112 let service_context = output
113 .context
114 .as_ref()
115 .and_then(|context| context.service.as_ref());
116
117 let realtime = |pick: fn(
118 &crate::varlink::unit::UnitRuntime,
119 ) -> Option<crate::varlink::unit::Timestamp>| {
120 runtime
121 .and_then(pick)
122 .and_then(|timestamp| timestamp.realtime)
123 .unwrap_or(0)
124 };
125
126 let reply =
131 |pick: fn(&crate::varlink::unit::CGroupRuntime) -> Option<u64>| cgroup_reply.and_then(pick);
132 let or_reply = |fs: Option<u64>, reply: Option<u64>| fs.or(reply).unwrap_or(UNSET);
133 ServiceStats {
134 active_enter_timestamp: realtime(|runtime| runtime.active_enter_timestamp),
135 active_exit_timestamp: realtime(|runtime| runtime.active_exit_timestamp),
136 cpuusage_nsec: or_reply(cgroup.cpu_usage_nsec, reply(|cgroup| cgroup.cpu_usage_nsec)),
137 inactive_exit_timestamp: realtime(|runtime| runtime.inactive_exit_timestamp),
138 ioread_bytes: or_reply(cgroup.io_read_bytes, reply(|cgroup| cgroup.io_read_bytes)),
139 ioread_operations: or_reply(
140 cgroup.io_read_operations,
141 reply(|cgroup| cgroup.io_read_operations),
142 ),
143 memory_available: or_reply(
144 cgroup.memory_available,
145 reply(|cgroup| cgroup.memory_available),
146 ),
147 memory_current: or_reply(cgroup.memory_current, reply(|cgroup| cgroup.memory_current)),
148 nrestarts: service_runtime
149 .and_then(|service| service.n_restarts)
150 .unwrap_or(0),
151 processes: cgroup.processes,
152 restart_usec: service_context
153 .and_then(|service| service.restart_usec)
154 .unwrap_or(0),
155 state_change_timestamp: realtime(|runtime| runtime.state_change_timestamp),
156 status_errno: service_runtime
157 .and_then(|service| service.status_errno)
158 .unwrap_or(0),
159 tasks_current: or_reply(cgroup.tasks_current, reply(|cgroup| cgroup.tasks_current)),
160 timeout_clean_usec: output
163 .context
164 .as_ref()
165 .and_then(|context| context.exec.as_ref())
166 .and_then(|exec| exec.timeout_clean_usec)
167 .unwrap_or(UNSET),
168 watchdog_usec: service_context
169 .and_then(|service| service.watchdog_usec)
170 .unwrap_or(0),
171 }
172}
173
174pub fn timer_triggered_unit(output: &ListOutput) -> Option<&str> {
176 output
177 .context
178 .as_ref()
179 .and_then(|context| context.timer.as_ref())
180 .and_then(|timer| timer.unit.as_deref())
181}
182
183pub fn map_timer_stats(output: &ListOutput, triggered: Option<&ListOutput>) -> TimerStats {
191 let context = output
192 .context
193 .as_ref()
194 .and_then(|context| context.timer.as_ref());
195 let runtime = output
196 .runtime
197 .as_ref()
198 .and_then(|runtime| runtime.timer.as_ref());
199 let last_trigger = runtime.and_then(|runtime| runtime.last_trigger_usec);
200 let service_state_change = triggered
201 .and_then(|triggered| triggered.runtime.as_ref())
202 .and_then(|runtime| runtime.state_change_timestamp);
203
204 TimerStats {
205 accuracy_usec: context.and_then(|timer| timer.accuracy_usec).unwrap_or(0),
206 fixed_random_delay: context
207 .and_then(|timer| timer.fixed_random_delay)
208 .unwrap_or(false),
209 last_trigger_usec: last_trigger
210 .and_then(|timestamp| timestamp.realtime)
211 .unwrap_or(0),
212 last_trigger_usec_monotonic: last_trigger
213 .and_then(|timestamp| timestamp.monotonic)
214 .unwrap_or(0),
215 next_elapse_usec_monotonic: runtime
216 .and_then(|timer| timer.next_elapse_usec_monotonic)
217 .unwrap_or(0),
218 next_elapse_usec_realtime: runtime
219 .and_then(|timer| timer.next_elapse_usec_realtime)
220 .unwrap_or(0),
221 persistent: context.and_then(|timer| timer.persistent).unwrap_or(false),
222 randomized_delay_usec: context
223 .and_then(|timer| timer.randomized_delay_usec)
224 .unwrap_or(0),
225 remain_after_elapse: context
226 .and_then(|timer| timer.remain_after_elapse)
227 .unwrap_or(false),
228 service_unit_last_state_change_usec: service_state_change
229 .and_then(|timestamp| timestamp.realtime)
230 .unwrap_or(0),
231 service_unit_last_state_change_usec_monotonic: service_state_change
232 .and_then(|timestamp| timestamp.monotonic)
233 .unwrap_or(0),
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::varlink::unit::{
241 CGroupRuntime, ExecContext, ServiceContext, ServiceRuntime, TimerContext, TimerRuntime,
242 Timestamp, UnitContext, UnitRuntime,
243 };
244
245 fn output(context: Option<UnitContext>, runtime: Option<UnitRuntime>) -> ListOutput {
246 ListOutput { context, runtime }
247 }
248
249 fn service_context(r#type: &str) -> UnitContext {
250 UnitContext {
251 service: Some(ServiceContext {
252 r#type: Some(r#type.to_string()),
253 restart_usec: None,
254 watchdog_usec: None,
255 }),
256 exec: None,
257 timer: None,
258 }
259 }
260
261 #[test]
262 fn test_is_oneshot() {
263 assert!(is_oneshot(&output(Some(service_context("oneshot")), None)));
264 assert!(!is_oneshot(&output(
265 Some(service_context("notify-reload")),
266 None
267 )));
268 assert!(!is_oneshot(&output(None, None)));
271 }
272
273 #[test]
274 fn test_map_service_stats_from_a_populated_reply() {
275 let stats = map_service_stats(
276 &output(
277 Some(UnitContext {
278 service: Some(ServiceContext {
279 r#type: Some("notify-reload".to_string()),
280 restart_usec: Some(100_000),
281 watchdog_usec: None,
282 }),
283 exec: Some(ExecContext {
285 timeout_clean_usec: Some(30_000_000),
286 }),
287 timer: None,
288 }),
289 Some(UnitRuntime {
290 state_change_timestamp: Some(Timestamp {
291 realtime: Some(1_789_701_442_296_989),
292 monotonic: Some(3_774_344_633),
293 }),
294 active_enter_timestamp: Some(Timestamp {
295 realtime: Some(1_789_701_442_296_989),
296 monotonic: Some(3_774_344_633),
297 }),
298 inactive_exit_timestamp: Some(Timestamp {
299 realtime: Some(1_789_701_442_287_084),
300 monotonic: Some(3_774_334_729),
301 }),
302 active_exit_timestamp: Some(Timestamp {
303 realtime: Some(1_789_701_442_280_000),
304 monotonic: Some(3_774_327_645),
305 }),
306 cgroup: Some(CGroupRuntime {
307 path: Some("/system.slice/dbus-broker.service".to_string()),
308 cpu_usage_nsec: Some(86_690_000),
309 memory_current: Some(3_457_024),
310 memory_available: Some(7_619_899_392),
311 tasks_current: Some(2),
312 io_read_bytes: None,
313 io_read_operations: None,
314 }),
315 service: Some(ServiceRuntime {
316 main_pid: None,
317 control_pid: None,
318 status_errno: Some(0),
319 n_restarts: Some(0),
320 }),
321 timer: None,
322 }),
323 ),
324 &crate::cgroup::CgroupStats {
327 cpu_usage_nsec: Some(90_000_000),
328 memory_current: Some(4_000_000),
329 tasks_current: Some(3),
330 processes: 3,
331 ..Default::default()
332 },
333 );
334
335 assert_eq!(stats.active_enter_timestamp, 1_789_701_442_296_989);
336 assert_eq!(stats.cpuusage_nsec, 90_000_000);
338 assert_eq!(stats.memory_current, 4_000_000);
339 assert_eq!(stats.tasks_current, 3);
340 assert_eq!(stats.processes, 3);
341 assert_eq!(stats.memory_available, 7_619_899_392);
343 assert_eq!(stats.restart_usec, 100_000);
344 assert_eq!(stats.active_exit_timestamp, 1_789_701_442_280_000);
346 assert_eq!(stats.timeout_clean_usec, 30_000_000);
348 assert_eq!(stats.ioread_bytes, u64::MAX);
351 assert_eq!(stats.ioread_operations, u64::MAX);
352 assert_eq!(stats.watchdog_usec, 0);
353 }
354
355 fn timer_output() -> ListOutput {
356 ListOutput {
358 context: Some(UnitContext {
359 service: None,
360 exec: None,
361 timer: Some(TimerContext {
362 unit: Some("systemd-tmpfiles-clean.service".to_string()),
363 accuracy_usec: Some(60_000_000),
364 randomized_delay_usec: None,
365 fixed_random_delay: Some(false),
366 persistent: Some(false),
367 remain_after_elapse: Some(true),
368 }),
369 }),
370 runtime: Some(UnitRuntime {
371 state_change_timestamp: None,
372 active_enter_timestamp: None,
373 inactive_exit_timestamp: None,
374 active_exit_timestamp: None,
375 cgroup: None,
376 service: None,
377 timer: Some(TimerRuntime {
378 next_elapse_usec_realtime: Some(0),
379 next_elapse_usec_monotonic: Some(91_091_400_515),
380 last_trigger_usec: Some(Timestamp {
381 realtime: Some(1_789_702_359_355_506),
382 monotonic: Some(4_691_399_604),
383 }),
384 }),
385 }),
386 }
387 }
388
389 #[test]
390 fn test_map_timer_stats() {
391 let triggered = output(
392 None,
393 Some(UnitRuntime {
394 state_change_timestamp: Some(Timestamp {
395 realtime: Some(1_789_702_359_400_000),
396 monotonic: Some(4_691_444_098),
397 }),
398 active_enter_timestamp: None,
399 inactive_exit_timestamp: None,
400 active_exit_timestamp: None,
401 cgroup: None,
402 service: None,
403 timer: None,
404 }),
405 );
406 let stats = map_timer_stats(&timer_output(), Some(&triggered));
407
408 assert_eq!(stats.accuracy_usec, 60_000_000);
409 assert_eq!(stats.last_trigger_usec, 1_789_702_359_355_506);
410 assert_eq!(stats.last_trigger_usec_monotonic, 4_691_399_604);
411 assert_eq!(stats.next_elapse_usec_monotonic, 91_091_400_515);
412 assert_eq!(stats.next_elapse_usec_realtime, 0);
414 assert!(stats.remain_after_elapse);
415 assert!(!stats.persistent);
416 assert_eq!(stats.randomized_delay_usec, 0);
418 assert_eq!(
420 stats.service_unit_last_state_change_usec,
421 1_789_702_359_400_000
422 );
423 assert_eq!(
424 stats.service_unit_last_state_change_usec_monotonic,
425 4_691_444_098
426 );
427 }
428
429 #[test]
430 fn test_map_timer_stats_without_the_triggered_unit() {
431 let stats = map_timer_stats(&timer_output(), None);
434 assert_eq!(stats.service_unit_last_state_change_usec, 0);
435 assert_eq!(stats.service_unit_last_state_change_usec_monotonic, 0);
436 assert_eq!(stats.accuracy_usec, 60_000_000);
437 }
438
439 #[test]
440 fn test_timer_triggered_unit() {
441 assert_eq!(
442 timer_triggered_unit(&timer_output()),
443 Some("systemd-tmpfiles-clean.service")
444 );
445 assert_eq!(timer_triggered_unit(&output(None, None)), None);
446 }
447
448 #[test]
449 fn test_map_service_stats_from_an_empty_reply() {
450 let stats = map_service_stats(&output(None, None), &crate::cgroup::CgroupStats::default());
454 assert_eq!(stats.ioread_bytes, u64::MAX);
455 assert_eq!(stats.ioread_operations, u64::MAX);
456 assert_eq!(stats.timeout_clean_usec, u64::MAX);
457 assert_eq!(stats.cpuusage_nsec, u64::MAX);
458 assert_eq!(stats.memory_current, u64::MAX);
459 assert_eq!(stats.memory_available, u64::MAX);
460 assert_eq!(stats.tasks_current, u64::MAX);
461 assert_eq!(stats.watchdog_usec, 0);
463 assert_eq!(stats.active_enter_timestamp, 0);
464 }
465
466 #[test]
467 fn test_map_service_stats_cgroupfs_only_no_reply() {
468 let stats = map_service_stats(
471 &output(None, None),
472 &crate::cgroup::CgroupStats {
473 cpu_usage_nsec: Some(1_000),
474 memory_current: Some(2_000),
475 memory_available: Some(3_000),
476 tasks_current: Some(1),
477 processes: 1,
478 io_read_bytes: Some(0),
479 io_read_operations: Some(0),
480 },
481 );
482 assert_eq!(stats.cpuusage_nsec, 1_000);
483 assert_eq!(stats.memory_current, 2_000);
484 assert_eq!(stats.memory_available, 3_000);
485 assert_eq!(stats.tasks_current, 1);
486 assert_eq!(stats.processes, 1);
487 assert_eq!(stats.ioread_bytes, 0);
488 assert_eq!(stats.ioread_operations, 0);
489 }
490}