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