1use std::collections::BTreeMap;
7use std::collections::HashMap;
8
9use tracing::debug;
10
11use crate::dbus_stats;
12use crate::networkd;
13use crate::pid1;
14use crate::units;
15use crate::MachineStats;
16use crate::MonitordStats;
17
18fn gen_base_metric_key(key_prefix: &str, metric_name: &str) -> String {
20 match key_prefix.is_empty() {
21 true => String::from(metric_name),
22 false => format!("{}.{}", key_prefix, metric_name),
23 }
24}
25
26fn flatten_networkd(
27 networkd_stats: &networkd::NetworkdState,
28 key_prefix: &str,
29) -> Vec<(String, serde_json::Value)> {
30 let mut flat_stats = vec![];
31 let base_metric_name = gen_base_metric_key(key_prefix, "networkd");
32
33 let managed_interfaces_key = format!("{}.managed_interfaces", base_metric_name);
34 flat_stats.push((
35 managed_interfaces_key,
36 networkd_stats.managed_interfaces.into(),
37 ));
38
39 if networkd_stats.interfaces_state.is_empty() {
40 debug!("No networkd interfaces to add to flat JSON");
41 return flat_stats;
42 }
43
44 for interface in &networkd_stats.interfaces_state {
45 let interface_base = format!("{}.{}", base_metric_name, interface.name);
46 flat_stats.push((
47 format!("{interface_base}.address_state"),
48 (interface.address_state as u64).into(),
49 ));
50 flat_stats.push((
51 format!("{interface_base}.admin_state"),
52 (interface.admin_state as u64).into(),
53 ));
54 flat_stats.push((
55 format!("{interface_base}.carrier_state"),
56 (interface.carrier_state as u64).into(),
57 ));
58 flat_stats.push((
59 format!("{interface_base}.ipv4_address_state"),
60 (interface.ipv4_address_state as u64).into(),
61 ));
62 flat_stats.push((
63 format!("{interface_base}.ipv6_address_state"),
64 (interface.ipv6_address_state as u64).into(),
65 ));
66 flat_stats.push((
67 format!("{interface_base}.oper_state"),
68 (interface.oper_state as u64).into(),
69 ));
70 flat_stats.push((
71 format!("{interface_base}.required_for_online"),
72 (interface.required_for_online as u64).into(),
73 ));
74 }
75 flat_stats
76}
77
78fn flatten_pid1(
79 optional_pid1_stats: &Option<pid1::Pid1Stats>,
80 key_prefix: &str,
81) -> Vec<(String, serde_json::Value)> {
82 let pid1_stats = match optional_pid1_stats {
84 Some(ps) => ps,
85 None => {
86 debug!("Skipping flattening pid1 stats as we got None ...");
87 return Vec::new();
88 }
89 };
90
91 let base_metric_name = gen_base_metric_key(key_prefix, "pid1");
92
93 vec![
94 (
95 format!("{}.cpu_time_kernel", base_metric_name),
96 pid1_stats.cpu_time_kernel.into(),
97 ),
98 (
99 format!("{}.cpu_user_kernel", base_metric_name),
100 pid1_stats.cpu_time_user.into(),
101 ),
102 (
103 format!("{}.memory_usage_bytes", base_metric_name),
104 pid1_stats.memory_usage_bytes.into(),
105 ),
106 (
107 format!("{}.fd_count", base_metric_name),
108 pid1_stats.fd_count.into(),
109 ),
110 (
111 format!("{}.tasks", base_metric_name),
112 pid1_stats.tasks.into(),
113 ),
114 ]
115}
116
117fn flatten_services(
118 service_stats_hash: &HashMap<String, units::ServiceStats>,
119 key_prefix: &str,
120) -> Vec<(String, serde_json::Value)> {
121 let mut flat_stats = Vec::new();
122 let base_metric_name = gen_base_metric_key(key_prefix, "services");
123
124 for (service_name, service_stats) in service_stats_hash.iter() {
125 if let Ok(serde_json::Value::Object(map)) = serde_json::to_value(service_stats) {
126 for (field_name, value) in map {
127 if value.is_number() {
128 let key = format!("{base_metric_name}.{service_name}.{field_name}");
129 flat_stats.push((key, value));
130 }
131 }
132 }
133 }
134 flat_stats
135}
136
137fn flatten_timers(
138 timer_stats_hash: &HashMap<String, crate::timer::TimerStats>,
139 key_prefix: &str,
140) -> Vec<(String, serde_json::Value)> {
141 let mut flat_stats = Vec::new();
142 let base_metric_name = gen_base_metric_key(key_prefix, "timers");
143
144 for (timer_name, timer_stats) in timer_stats_hash.iter() {
145 if let Ok(serde_json::Value::Object(map)) = serde_json::to_value(timer_stats) {
146 for (field_name, value) in map {
147 let key = format!("{base_metric_name}.{timer_name}.{field_name}");
148 if value.is_number() {
149 flat_stats.push((key, value));
150 } else if let Some(b) = value.as_bool() {
151 flat_stats.push((key, (b as u64).into()));
152 }
153 }
154 }
155 }
156 flat_stats
157}
158
159fn flatten_unit_states(
160 unit_states_hash: &HashMap<String, units::UnitStates>,
161 key_prefix: &str,
162) -> Vec<(String, serde_json::Value)> {
163 let mut flat_stats = Vec::new();
164 let base_metric_name = gen_base_metric_key(key_prefix, "unit_states");
165
166 for (unit_name, unit_state_stats) in unit_states_hash.iter() {
167 if let Ok(serde_json::Value::Object(map)) = serde_json::to_value(unit_state_stats) {
168 for (field_name, value) in map {
169 let key = format!("{base_metric_name}.{unit_name}.{field_name}");
170 if value.is_number() {
171 flat_stats.push((key, value));
172 } else if let Some(b) = value.as_bool() {
173 flat_stats.push((key, (b as u64).into()));
174 }
175 }
176 }
177 }
178
179 flat_stats
180}
181
182#[derive(serde::Serialize)]
186struct UnitCounters {
187 activating_units: u64,
188 active_units: u64,
189 automount_units: u64,
190 device_units: u64,
191 failed_units: u64,
192 inactive_units: u64,
193 jobs_queued: u64,
194 loaded_units: u64,
195 masked_units: u64,
196 mount_units: u64,
197 not_found_units: u64,
198 path_units: u64,
199 scope_units: u64,
200 service_units: u64,
201 slice_units: u64,
202 socket_units: u64,
203 target_units: u64,
204 timer_units: u64,
205 timer_persistent_units: u64,
206 timer_remain_after_elapse: u64,
207 total_units: u64,
208}
209
210impl From<&units::SystemdUnitStats> for UnitCounters {
211 fn from(s: &units::SystemdUnitStats) -> Self {
212 Self {
213 activating_units: s.activating_units,
214 active_units: s.active_units,
215 automount_units: s.automount_units,
216 device_units: s.device_units,
217 failed_units: s.failed_units,
218 inactive_units: s.inactive_units,
219 jobs_queued: s.jobs_queued,
220 loaded_units: s.loaded_units,
221 masked_units: s.masked_units,
222 mount_units: s.mount_units,
223 not_found_units: s.not_found_units,
224 path_units: s.path_units,
225 scope_units: s.scope_units,
226 service_units: s.service_units,
227 slice_units: s.slice_units,
228 socket_units: s.socket_units,
229 target_units: s.target_units,
230 timer_units: s.timer_units,
231 timer_persistent_units: s.timer_persistent_units,
232 timer_remain_after_elapse: s.timer_remain_after_elapse,
233 total_units: s.total_units,
234 }
235 }
236}
237
238fn flatten_units(
239 units_stats: &units::SystemdUnitStats,
240 key_prefix: &str,
241) -> Vec<(String, serde_json::Value)> {
242 let mut flat_stats = Vec::new();
243 let base_metric_name = gen_base_metric_key(key_prefix, "units");
244
245 if let Ok(serde_json::Value::Object(map)) =
246 serde_json::to_value(UnitCounters::from(units_stats))
247 {
248 for (field_name, value) in map {
249 if value.is_number() {
250 let key = format!("{base_metric_name}.{field_name}");
251 flat_stats.push((key, value));
252 }
253 }
254 }
255 flat_stats
256}
257
258fn flatten_machines(
259 machines_stats: &HashMap<String, MachineStats>,
260 key_prefix: &str,
261) -> BTreeMap<String, serde_json::Value> {
262 let mut flat_stats = BTreeMap::new();
263
264 if machines_stats.is_empty() {
265 return flat_stats;
266 }
267
268 for (machine, stats) in machines_stats {
269 let machine_key_prefix = match key_prefix.is_empty() {
270 true => format!("machines.{}", machine),
271 false => format!("{}.machines.{}", key_prefix, machine),
272 };
273 flat_stats.extend(flatten_networkd(&stats.networkd, &machine_key_prefix));
274 flat_stats.extend(flatten_units(&stats.units, &machine_key_prefix));
275 flat_stats.extend(flatten_units_collection_timings(
276 &stats.units.collection_timings,
277 &machine_key_prefix,
278 ));
279 flat_stats.extend(flatten_pid1(&stats.pid1, &machine_key_prefix));
280 flat_stats.insert(
281 gen_base_metric_key(&machine_key_prefix, "system-state"),
282 (stats.system_state as u64).into(),
283 );
284 flat_stats.extend(flatten_services(
285 &stats.units.service_stats,
286 &machine_key_prefix,
287 ));
288 flat_stats.extend(flatten_timers(
289 &stats.units.timer_stats,
290 &machine_key_prefix,
291 ));
292 flat_stats.extend(flatten_boot_blame(&stats.boot_blame, &machine_key_prefix));
293 flat_stats.extend(flatten_verify_stats(
294 &stats.verify_stats,
295 &machine_key_prefix,
296 ));
297 }
298
299 flat_stats
300}
301
302fn flatten_dbus_stats(
303 optional_dbus_stats: &Option<dbus_stats::DBusStats>,
304 key_prefix: &str,
305) -> BTreeMap<String, serde_json::Value> {
306 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
307 let dbus_stats = match optional_dbus_stats {
308 Some(ds) => ds,
309 None => {
310 debug!("Skipping flattening dbus stats as we got None ...");
311 return flat_stats;
312 }
313 };
314
315 let base_metric_name = gen_base_metric_key(key_prefix, "dbus");
316 let fields = [
317 ("active_connections", dbus_stats.active_connections),
319 ("incomplete_connections", dbus_stats.incomplete_connections),
320 ("bus_names", dbus_stats.bus_names),
321 ("peak_bus_names", dbus_stats.peak_bus_names),
322 (
323 "peak_bus_names_per_connection",
324 dbus_stats.peak_bus_names_per_connection,
325 ),
326 ("match_rules", dbus_stats.match_rules),
327 ("peak_match_rules", dbus_stats.peak_match_rules),
328 (
329 "peak_match_rules_per_connection",
330 dbus_stats.peak_match_rules_per_connection,
331 ),
332 ];
333
334 for (field_name, value) in fields {
335 if let Some(val) = value {
336 flat_stats.insert(format!("{base_metric_name}.{field_name}"), val.into());
337 }
338 }
339
340 if let Some(peer_accounting) = dbus_stats.peer_accounting() {
341 for peer in peer_accounting.values() {
342 let peer_name = peer.get_name();
343 let peer_fields = [
344 ("name_objects", peer.name_objects),
345 ("match_bytes", peer.match_bytes),
346 ("matches", peer.matches),
347 ("reply_objects", peer.reply_objects),
348 ("incoming_bytes", peer.incoming_bytes),
349 ("incoming_fds", peer.incoming_fds),
350 ("outgoing_bytes", peer.outgoing_bytes),
351 ("outgoing_fds", peer.outgoing_fds),
352 ("activation_request_bytes", peer.activation_request_bytes),
353 ("activation_request_fds", peer.activation_request_fds),
354 ];
355
356 for (field_name, value) in peer_fields {
357 if let Some(val) = value {
358 flat_stats.insert(
359 format!("{base_metric_name}.peer.{peer_name}.{field_name}"),
360 val.into(),
361 );
362 }
363 }
364 }
365 }
366
367 if let Some(cgroup_accounting) = dbus_stats.cgroup_accounting() {
368 for cgroup in cgroup_accounting.values() {
369 let cgroup_name = &cgroup.name;
370 let cgroup_fields = [
371 ("name_objects", cgroup.name_objects),
372 ("match_bytes", cgroup.match_bytes),
373 ("matches", cgroup.matches),
374 ("reply_objects", cgroup.reply_objects),
375 ("incoming_bytes", cgroup.incoming_bytes),
376 ("incoming_fds", cgroup.incoming_fds),
377 ("outgoing_bytes", cgroup.outgoing_bytes),
378 ("outgoing_fds", cgroup.outgoing_fds),
379 ("activation_request_bytes", cgroup.activation_request_bytes),
380 ("activation_request_fds", cgroup.activation_request_fds),
381 ];
382
383 for (field_name, value) in cgroup_fields {
384 if let Some(val) = value {
385 flat_stats.insert(
386 format!("{base_metric_name}.cgroup.{cgroup_name}.{field_name}"),
387 val.into(),
388 );
389 }
390 }
391 }
392 }
393
394 if let Some(user_accounting) = dbus_stats.user_accounting() {
395 for user in user_accounting.values() {
397 let user_name = &user.username;
398 let user_fields = [
399 ("bytes", user.bytes.clone()),
400 ("fds", user.fds.clone()),
401 ("matches", user.matches.clone()),
402 ("objects", user.objects.clone()),
403 ];
404
405 for (field_name, value) in user_fields {
406 if let Some(val) = value {
407 flat_stats.insert(
408 format!("{base_metric_name}.user.{user_name}.{field_name}"),
409 val.get_usage().into(),
410 );
411 }
412 }
413 }
414 }
415
416 flat_stats
417}
418
419fn flatten_boot_blame(
420 optional_boot_blame: &Option<crate::boot::BootBlameStats>,
421 key_prefix: &str,
422) -> BTreeMap<String, serde_json::Value> {
423 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
424 let boot_blame_stats = match optional_boot_blame {
425 Some(bb) => bb,
426 None => {
427 debug!("Skipping flattening boot blame stats as we got None ...");
428 return flat_stats;
429 }
430 };
431
432 let base_metric_name = gen_base_metric_key(key_prefix, "boot.blame");
433
434 for (unit_name, activation_time) in boot_blame_stats.iter() {
435 let key = format!("{}.{}", base_metric_name, unit_name);
436 flat_stats.insert(key, (*activation_time).into());
437 }
438
439 flat_stats
440}
441
442fn flatten_verify_stats(
443 optional_verify_stats: &Option<crate::verify::VerifyStats>,
444 key_prefix: &str,
445) -> BTreeMap<String, serde_json::Value> {
446 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
447 let verify_stats = match optional_verify_stats {
448 Some(vs) => vs,
449 None => {
450 debug!("Skipping flattening verify stats as we got None ...");
451 return flat_stats;
452 }
453 };
454
455 let base_metric_name = gen_base_metric_key(key_prefix, "verify.failing");
456
457 flat_stats.insert(
459 format!("{base_metric_name}.total"),
460 verify_stats.total.into(),
461 );
462
463 for (unit_type, count) in &verify_stats.by_type {
465 flat_stats.insert(format!("{base_metric_name}.{unit_type}"), (*count).into());
466 }
467
468 flat_stats
469}
470
471fn flatten_collector_timings(
472 timings: &[crate::CollectorTiming],
473 key_prefix: &str,
474) -> BTreeMap<String, serde_json::Value> {
475 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
476 let base_metric_name = gen_base_metric_key(key_prefix, "collector_timings");
477 for t in timings {
478 flat_stats.insert(
479 format!("{base_metric_name}.{}.start_offset_ms", t.name),
480 t.start_offset_ms.into(),
481 );
482 flat_stats.insert(
483 format!("{base_metric_name}.{}.elapsed_ms", t.name),
484 t.elapsed_ms.into(),
485 );
486 flat_stats.insert(
487 format!("{base_metric_name}.{}.success", t.name),
488 (if t.success { 1u64 } else { 0u64 }).into(),
489 );
490 }
491 flat_stats
492}
493
494fn flatten_units_collection_timings(
495 timings: &units::UnitsCollectionTimings,
496 key_prefix: &str,
497) -> BTreeMap<String, serde_json::Value> {
498 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
499 let base_metric_name = gen_base_metric_key(key_prefix, "collection_timings");
500 flat_stats.insert(
501 format!("{base_metric_name}.list_units_ms"),
502 timings.list_units_ms.into(),
503 );
504 flat_stats.insert(
505 format!("{base_metric_name}.per_unit_loop_ms"),
506 timings.per_unit_loop_ms.into(),
507 );
508 flat_stats.insert(
509 format!("{base_metric_name}.timer_dbus_fetches"),
510 timings.timer_dbus_fetches.into(),
511 );
512 flat_stats.insert(
513 format!("{base_metric_name}.state_dbus_fetches"),
514 timings.state_dbus_fetches.into(),
515 );
516 flat_stats.insert(
517 format!("{base_metric_name}.service_dbus_fetches"),
518 timings.service_dbus_fetches.into(),
519 );
520 flat_stats
521}
522
523fn flatten_stats(
525 stats_struct: &MonitordStats,
526 key_prefix: &str,
527) -> BTreeMap<String, serde_json::Value> {
528 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
529 flat_stats.insert(
530 gen_base_metric_key(key_prefix, "stat_collection_run_time_ms"),
531 stats_struct.stat_collection_run_time_ms.into(),
532 );
533 flat_stats.extend(flatten_collector_timings(
534 &stats_struct.collector_timings,
535 key_prefix,
536 ));
537 flat_stats.extend(flatten_units_collection_timings(
538 &stats_struct.units.collection_timings,
539 key_prefix,
540 ));
541 flat_stats.extend(flatten_networkd(&stats_struct.networkd, key_prefix));
542 flat_stats.extend(flatten_pid1(&stats_struct.pid1, key_prefix));
543 flat_stats.insert(
544 gen_base_metric_key(key_prefix, "system-state"),
545 (stats_struct.system_state as u64).into(),
546 );
547 flat_stats.extend(flatten_services(
548 &stats_struct.units.service_stats,
549 key_prefix,
550 ));
551 flat_stats.extend(flatten_timers(&stats_struct.units.timer_stats, key_prefix));
552 flat_stats.extend(flatten_unit_states(
553 &stats_struct.units.unit_states,
554 key_prefix,
555 ));
556 flat_stats.extend(flatten_units(&stats_struct.units, key_prefix));
557 flat_stats.insert(
558 gen_base_metric_key(key_prefix, "version"),
559 stats_struct.version.to_string().into(),
560 );
561 flat_stats.extend(flatten_machines(&stats_struct.machines, key_prefix));
562 flat_stats.extend(flatten_dbus_stats(&stats_struct.dbus_stats, key_prefix));
563 flat_stats.extend(flatten_boot_blame(&stats_struct.boot_blame, key_prefix));
564 flat_stats.extend(flatten_verify_stats(&stats_struct.verify_stats, key_prefix));
565 flat_stats
566}
567
568pub fn flatten(
570 stats_struct: &MonitordStats,
571 key_prefix: &str,
572) -> Result<String, serde_json::Error> {
573 serde_json::to_string_pretty(&flatten_stats(stats_struct, key_prefix))
574}
575
576#[cfg(test)]
577mod tests {
578 use crate::timer;
579
580 use super::*;
581
582 const EXPECTED_FLAT_JSON: &str = r###"{
584 "boot.blame.cpe_chef.service": 103.05,
585 "boot.blame.dnf5-automatic.service": 204.159,
586 "boot.blame.sys-module-fuse.device": 16.21,
587 "collection_timings.list_units_ms": 5.0,
588 "collection_timings.per_unit_loop_ms": 37.0,
589 "collection_timings.service_dbus_fetches": 1,
590 "collection_timings.state_dbus_fetches": 0,
591 "collection_timings.timer_dbus_fetches": 4,
592 "collector_timings.boot_blame.elapsed_ms": 12.5,
593 "collector_timings.boot_blame.start_offset_ms": 0.25,
594 "collector_timings.boot_blame.success": 0,
595 "collector_timings.units.elapsed_ms": 42.0,
596 "collector_timings.units.start_offset_ms": 0.5,
597 "collector_timings.units.success": 1,
598 "machines.foo.collection_timings.list_units_ms": 0.0,
599 "machines.foo.collection_timings.per_unit_loop_ms": 0.0,
600 "machines.foo.collection_timings.service_dbus_fetches": 0,
601 "machines.foo.collection_timings.state_dbus_fetches": 0,
602 "machines.foo.collection_timings.timer_dbus_fetches": 0,
603 "machines.foo.networkd.managed_interfaces": 0,
604 "machines.foo.system-state": 0,
605 "machines.foo.timers.unittest.timer.accuracy_usec": 69,
606 "machines.foo.timers.unittest.timer.fixed_random_delay": 1,
607 "machines.foo.timers.unittest.timer.last_trigger_usec": 69,
608 "machines.foo.timers.unittest.timer.last_trigger_usec_monotonic": 69,
609 "machines.foo.timers.unittest.timer.next_elapse_usec_monotonic": 69,
610 "machines.foo.timers.unittest.timer.next_elapse_usec_realtime": 69,
611 "machines.foo.timers.unittest.timer.persistent": 0,
612 "machines.foo.timers.unittest.timer.randomized_delay_usec": 69,
613 "machines.foo.timers.unittest.timer.remain_after_elapse": 1,
614 "machines.foo.timers.unittest.timer.service_unit_last_state_change_usec": 69,
615 "machines.foo.timers.unittest.timer.service_unit_last_state_change_usec_monotonic": 69,
616 "machines.foo.units.activating_units": 0,
617 "machines.foo.units.active_units": 0,
618 "machines.foo.units.automount_units": 0,
619 "machines.foo.units.device_units": 0,
620 "machines.foo.units.failed_units": 0,
621 "machines.foo.units.inactive_units": 0,
622 "machines.foo.units.jobs_queued": 0,
623 "machines.foo.units.loaded_units": 0,
624 "machines.foo.units.masked_units": 0,
625 "machines.foo.units.mount_units": 0,
626 "machines.foo.units.not_found_units": 0,
627 "machines.foo.units.path_units": 0,
628 "machines.foo.units.scope_units": 0,
629 "machines.foo.units.service_units": 0,
630 "machines.foo.units.slice_units": 0,
631 "machines.foo.units.socket_units": 0,
632 "machines.foo.units.target_units": 0,
633 "machines.foo.units.timer_persistent_units": 0,
634 "machines.foo.units.timer_remain_after_elapse": 0,
635 "machines.foo.units.timer_units": 0,
636 "machines.foo.units.total_units": 0,
637 "networkd.eth0.address_state": 3,
638 "networkd.eth0.admin_state": 4,
639 "networkd.eth0.carrier_state": 5,
640 "networkd.eth0.ipv4_address_state": 3,
641 "networkd.eth0.ipv6_address_state": 2,
642 "networkd.eth0.oper_state": 9,
643 "networkd.eth0.required_for_online": 1,
644 "networkd.managed_interfaces": 1,
645 "pid1.cpu_time_kernel": 69,
646 "pid1.cpu_user_kernel": 69,
647 "pid1.fd_count": 69,
648 "pid1.memory_usage_bytes": 69,
649 "pid1.tasks": 1,
650 "services.unittest.service.active_enter_timestamp": 0,
651 "services.unittest.service.active_exit_timestamp": 0,
652 "services.unittest.service.cpuusage_nsec": 0,
653 "services.unittest.service.inactive_exit_timestamp": 0,
654 "services.unittest.service.ioread_bytes": 0,
655 "services.unittest.service.ioread_operations": 0,
656 "services.unittest.service.memory_available": 0,
657 "services.unittest.service.memory_current": 0,
658 "services.unittest.service.nrestarts": 0,
659 "services.unittest.service.processes": 0,
660 "services.unittest.service.restart_usec": 0,
661 "services.unittest.service.state_change_timestamp": 0,
662 "services.unittest.service.status_errno": -69,
663 "services.unittest.service.tasks_current": 0,
664 "services.unittest.service.timeout_clean_usec": 0,
665 "services.unittest.service.watchdog_usec": 0,
666 "stat_collection_run_time_ms": 69.0,
667 "system-state": 3,
668 "timers.unittest.timer.accuracy_usec": 69,
669 "timers.unittest.timer.fixed_random_delay": 1,
670 "timers.unittest.timer.last_trigger_usec": 69,
671 "timers.unittest.timer.last_trigger_usec_monotonic": 69,
672 "timers.unittest.timer.next_elapse_usec_monotonic": 69,
673 "timers.unittest.timer.next_elapse_usec_realtime": 69,
674 "timers.unittest.timer.persistent": 0,
675 "timers.unittest.timer.randomized_delay_usec": 69,
676 "timers.unittest.timer.remain_after_elapse": 1,
677 "timers.unittest.timer.service_unit_last_state_change_usec": 69,
678 "timers.unittest.timer.service_unit_last_state_change_usec_monotonic": 69,
679 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.active_state": 1,
680 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.load_state": 1,
681 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.unhealthy": 0,
682 "unit_states.unittest.service.active_state": 1,
683 "unit_states.unittest.service.load_state": 1,
684 "unit_states.unittest.service.time_in_state_usecs": 69,
685 "unit_states.unittest.service.unhealthy": 0,
686 "units.activating_units": 0,
687 "units.active_units": 0,
688 "units.automount_units": 0,
689 "units.device_units": 0,
690 "units.failed_units": 0,
691 "units.inactive_units": 0,
692 "units.jobs_queued": 0,
693 "units.loaded_units": 0,
694 "units.masked_units": 0,
695 "units.mount_units": 0,
696 "units.not_found_units": 0,
697 "units.path_units": 0,
698 "units.scope_units": 0,
699 "units.service_units": 0,
700 "units.slice_units": 0,
701 "units.socket_units": 0,
702 "units.target_units": 0,
703 "units.timer_persistent_units": 0,
704 "units.timer_remain_after_elapse": 0,
705 "units.timer_units": 0,
706 "units.total_units": 0,
707 "verify.failing.service": 2,
708 "verify.failing.slice": 1,
709 "verify.failing.total": 3,
710 "version": "255.7-1.fc40"
711}"###;
712
713 fn return_monitord_stats() -> MonitordStats {
714 let mut stats = MonitordStats {
715 networkd: networkd::NetworkdState {
716 interfaces_state: vec![networkd::InterfaceState {
717 address_state: networkd::AddressState::routable,
718 admin_state: networkd::AdminState::configured,
719 carrier_state: networkd::CarrierState::carrier,
720 ipv4_address_state: networkd::AddressState::routable,
721 ipv6_address_state: networkd::AddressState::degraded,
722 name: "eth0".to_string(),
723 network_file: "/etc/systemd/network/69-eno4.network".to_string(),
724 oper_state: networkd::OperState::routable,
725 required_for_online: networkd::BoolState::True,
726 }],
727 managed_interfaces: 1,
728 },
729 pid1: Some(crate::pid1::Pid1Stats {
730 cpu_time_kernel: 69,
731 cpu_time_user: 69,
732 memory_usage_bytes: 69,
733 fd_count: 69,
734 tasks: 1,
735 }),
736 system_state: crate::system::SystemdSystemState::running,
737 units: crate::units::SystemdUnitStats::default(),
738 version: String::from("255.7-1.fc40")
739 .try_into()
740 .expect("Unable to make SystemdVersion struct"),
741 machines: HashMap::from([(String::from("foo"), MachineStats::default())]),
742 dbus_stats: None,
743 boot_blame: None,
744 verify_stats: Some(crate::verify::VerifyStats {
745 total: 3,
746 by_type: HashMap::from([("service".to_string(), 2), ("slice".to_string(), 1)]),
747 }),
748 stat_collection_run_time_ms: 69.0,
749 collector_timings: vec![
750 crate::CollectorTiming {
751 name: "units".to_string(),
752 start_offset_ms: 0.5,
753 elapsed_ms: 42.0,
754 success: true,
755 },
756 crate::CollectorTiming {
757 name: "boot_blame".to_string(),
758 start_offset_ms: 0.25,
759 elapsed_ms: 12.5,
760 success: false,
761 },
762 ],
763 };
764 stats.units.collection_timings = units::UnitsCollectionTimings {
765 list_units_ms: 5.0,
766 per_unit_loop_ms: 37.0,
767 timer_dbus_fetches: 4,
768 state_dbus_fetches: 0,
769 service_dbus_fetches: 1,
770 };
771 let service_unit_name = String::from("unittest.service");
772 stats.units.service_stats.insert(
773 service_unit_name.clone(),
774 units::ServiceStats {
775 status_errno: -69,
777 ..Default::default()
778 },
779 );
780 stats.units.unit_states.insert(
781 String::from("unittest.service"),
782 units::UnitStates {
783 active_state: units::SystemdUnitActiveState::active,
784 load_state: units::SystemdUnitLoadState::loaded,
785 unhealthy: false,
786 time_in_state_usecs: Some(69),
787 },
788 );
789 let timer_unit = String::from("unittest.timer");
790 let timer_stats = timer::TimerStats {
791 accuracy_usec: 69,
792 fixed_random_delay: true,
793 last_trigger_usec: 69,
794 last_trigger_usec_monotonic: 69,
795 next_elapse_usec_monotonic: 69,
796 next_elapse_usec_realtime: 69,
797 persistent: false,
798 randomized_delay_usec: 69,
799 remain_after_elapse: true,
800 service_unit_last_state_change_usec: 69,
801 service_unit_last_state_change_usec_monotonic: 69,
802 };
803 stats
804 .units
805 .timer_stats
806 .insert(timer_unit.clone(), timer_stats.clone());
807 stats
808 .machines
809 .get_mut("foo")
810 .expect("No machine foo? WTF")
811 .units
812 .timer_stats
813 .insert(timer_unit, timer_stats);
814 stats.units.unit_states.insert(
816 String::from(
817 r"nvme\x2dWDC_CL_SN730_SDBQNTY\x2d512G\x2d2020_37222H80070511\x2dpart3.device",
818 ),
819 units::UnitStates {
820 active_state: units::SystemdUnitActiveState::active,
821 load_state: units::SystemdUnitLoadState::loaded,
822 unhealthy: false,
823 time_in_state_usecs: None,
824 },
825 );
826 let mut boot_blame = crate::boot::BootBlameStats::new();
828 boot_blame.insert(String::from("dnf5-automatic.service"), 204.159);
829 boot_blame.insert(String::from("cpe_chef.service"), 103.050);
830 boot_blame.insert(String::from("sys-module-fuse.device"), 16.210);
831 stats.boot_blame = Some(boot_blame);
832 stats
833 }
834
835 #[test]
836 fn test_flatten_map() {
837 let json_flat_map = flatten_stats(&return_monitord_stats(), "");
838 assert_eq!(127, json_flat_map.len());
839 }
840
841 #[test]
842 fn test_flatten() {
843 let json_flat = flatten(&return_monitord_stats(), "").expect("JSON serialize failed");
844 assert_eq!(EXPECTED_FLAT_JSON, json_flat);
845 }
846
847 #[test]
848 fn test_flatten_prefixed() {
849 let json_flat =
850 flatten(&return_monitord_stats(), "monitord").expect("JSON serialize failed");
851 let json_flat_unserialized: BTreeMap<String, serde_json::Value> =
852 serde_json::from_str(&json_flat).expect("JSON from_str failed");
853 for (key, _value) in json_flat_unserialized.iter() {
854 assert!(key.starts_with("monitord."));
855 }
856 }
857
858 #[test]
864 fn test_unit_counters_covers_all_scalar_fields() {
865 const NON_COUNTER_FIELDS: &[&str] = &[
867 "service_stats",
868 "timer_stats",
869 "unit_states",
870 "collection_timings",
871 ];
872
873 let expected: std::collections::BTreeSet<&str> = units::UNIT_FIELD_NAMES
875 .iter()
876 .copied()
877 .filter(|f| !NON_COUNTER_FIELDS.contains(f))
878 .collect();
879
880 let counters_json =
882 serde_json::to_value(UnitCounters::from(&units::SystemdUnitStats::default()))
883 .expect("UnitCounters serialization failed");
884 let actual: std::collections::BTreeSet<&str> = counters_json
885 .as_object()
886 .expect("UnitCounters must serialize to a JSON object")
887 .keys()
888 .map(|s| s.as_str())
889 .collect();
890
891 assert_eq!(
892 expected,
893 actual,
894 "UnitCounters is out of sync with SystemdUnitStats scalar fields.\n\
895 Missing from UnitCounters: {:?}\n\
896 Extra in UnitCounters: {:?}",
897 expected.difference(&actual).collect::<Vec<_>>(),
898 actual.difference(&expected).collect::<Vec<_>>(),
899 );
900 }
901}