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