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_varlink_usage(
308 &stats.varlink_usage,
309 &machine_key_prefix,
310 ));
311 flat_stats.extend(flatten_units(&stats.units, &machine_key_prefix));
312 flat_stats.extend(flatten_unit_files(
313 &stats.units.unit_files,
314 &machine_key_prefix,
315 ));
316 flat_stats.extend(flatten_units_collection_timings(
317 &stats.units.collection_timings,
318 &machine_key_prefix,
319 ));
320 flat_stats.extend(flatten_pid1(&stats.pid1, &machine_key_prefix));
321 flat_stats.insert(
322 gen_base_metric_key(&machine_key_prefix, "system-state"),
323 (stats.system_state as u64).into(),
324 );
325 flat_stats.extend(flatten_services(
326 &stats.units.service_stats,
327 &machine_key_prefix,
328 ));
329 flat_stats.extend(flatten_timers(
330 &stats.units.timer_stats,
331 &machine_key_prefix,
332 ));
333 flat_stats.extend(flatten_boot_blame(&stats.boot_blame, &machine_key_prefix));
334 flat_stats.extend(flatten_verify_stats(
335 &stats.verify_stats,
336 &machine_key_prefix,
337 ));
338 }
339
340 flat_stats
341}
342
343fn flatten_dbus_stats(
344 optional_dbus_stats: &Option<dbus_stats::DBusStats>,
345 key_prefix: &str,
346) -> BTreeMap<String, serde_json::Value> {
347 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
348 let dbus_stats = match optional_dbus_stats {
349 Some(ds) => ds,
350 None => {
351 debug!("Skipping flattening dbus stats as we got None ...");
352 return flat_stats;
353 }
354 };
355
356 let base_metric_name = gen_base_metric_key(key_prefix, "dbus");
357 let fields = [
358 ("active_connections", dbus_stats.active_connections),
360 ("incomplete_connections", dbus_stats.incomplete_connections),
361 ("bus_names", dbus_stats.bus_names),
362 ("peak_bus_names", dbus_stats.peak_bus_names),
363 (
364 "peak_bus_names_per_connection",
365 dbus_stats.peak_bus_names_per_connection,
366 ),
367 ("match_rules", dbus_stats.match_rules),
368 ("peak_match_rules", dbus_stats.peak_match_rules),
369 (
370 "peak_match_rules_per_connection",
371 dbus_stats.peak_match_rules_per_connection,
372 ),
373 ("stale_fds", dbus_stats.stale_fds),
374 ];
375
376 for (field_name, value) in fields {
377 if let Some(val) = value {
378 flat_stats.insert(format!("{base_metric_name}.{field_name}"), val.into());
379 }
380 }
381
382 if let Some(peer_accounting) = dbus_stats.peer_accounting() {
383 for peer in peer_accounting.values() {
384 let peer_name = peer.get_name();
385 let peer_fields = [
386 ("name_objects", peer.name_objects),
387 ("match_bytes", peer.match_bytes),
388 ("matches", peer.matches),
389 ("reply_objects", peer.reply_objects),
390 ("incoming_bytes", peer.incoming_bytes),
391 ("incoming_fds", peer.incoming_fds),
392 ("outgoing_bytes", peer.outgoing_bytes),
393 ("outgoing_fds", peer.outgoing_fds),
394 ("activation_request_bytes", peer.activation_request_bytes),
395 ("activation_request_fds", peer.activation_request_fds),
396 ];
397
398 for (field_name, value) in peer_fields {
399 if let Some(val) = value {
400 flat_stats.insert(
401 format!("{base_metric_name}.peer.{peer_name}.{field_name}"),
402 val.into(),
403 );
404 }
405 }
406 }
407 }
408
409 if let Some(cgroup_accounting) = dbus_stats.cgroup_accounting() {
410 for cgroup in cgroup_accounting.values() {
411 let cgroup_name = &cgroup.name;
412 let cgroup_fields = [
413 ("name_objects", cgroup.name_objects),
414 ("match_bytes", cgroup.match_bytes),
415 ("matches", cgroup.matches),
416 ("reply_objects", cgroup.reply_objects),
417 ("incoming_bytes", cgroup.incoming_bytes),
418 ("incoming_fds", cgroup.incoming_fds),
419 ("outgoing_bytes", cgroup.outgoing_bytes),
420 ("outgoing_fds", cgroup.outgoing_fds),
421 ("activation_request_bytes", cgroup.activation_request_bytes),
422 ("activation_request_fds", cgroup.activation_request_fds),
423 ];
424
425 for (field_name, value) in cgroup_fields {
426 if let Some(val) = value {
427 flat_stats.insert(
428 format!("{base_metric_name}.cgroup.{cgroup_name}.{field_name}"),
429 val.into(),
430 );
431 }
432 }
433 }
434 }
435
436 if let Some(user_accounting) = dbus_stats.user_accounting() {
437 for user in user_accounting.values() {
439 let user_name = &user.username;
440 let user_fields = [
441 ("bytes", user.bytes.clone()),
442 ("fds", user.fds.clone()),
443 ("matches", user.matches.clone()),
444 ("objects", user.objects.clone()),
445 ];
446
447 for (field_name, value) in user_fields {
448 if let Some(val) = value {
449 flat_stats.insert(
450 format!("{base_metric_name}.user.{user_name}.{field_name}"),
451 val.get_usage().into(),
452 );
453 }
454 }
455
456 if let Some(stale_fds) = user.stale_fds {
457 flat_stats.insert(
458 format!("{base_metric_name}.user.{user_name}.stale_fds"),
459 stale_fds.into(),
460 );
461 }
462 }
463 }
464
465 flat_stats
466}
467
468fn flatten_boot_blame(
469 optional_boot_blame: &Option<crate::boot::BootBlameStats>,
470 key_prefix: &str,
471) -> BTreeMap<String, serde_json::Value> {
472 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
473 let boot_blame_stats = match optional_boot_blame {
474 Some(bb) => bb,
475 None => {
476 debug!("Skipping flattening boot blame stats as we got None ...");
477 return flat_stats;
478 }
479 };
480
481 let base_metric_name = gen_base_metric_key(key_prefix, "boot.blame");
482
483 for (unit_name, activation_time) in boot_blame_stats.iter() {
484 let key = format!("{}.{}", base_metric_name, unit_name);
485 flat_stats.insert(key, (*activation_time).into());
486 }
487
488 flat_stats
489}
490
491fn flatten_verify_stats(
492 optional_verify_stats: &Option<crate::verify::VerifyStats>,
493 key_prefix: &str,
494) -> BTreeMap<String, serde_json::Value> {
495 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
496 let verify_stats = match optional_verify_stats {
497 Some(vs) => vs,
498 None => {
499 debug!("Skipping flattening verify stats as we got None ...");
500 return flat_stats;
501 }
502 };
503
504 let base_metric_name = gen_base_metric_key(key_prefix, "verify.failing");
505
506 flat_stats.insert(
508 format!("{base_metric_name}.total"),
509 verify_stats.total.into(),
510 );
511
512 for (unit_type, count) in &verify_stats.by_type {
514 flat_stats.insert(format!("{base_metric_name}.{unit_type}"), (*count).into());
515 }
516
517 flat_stats
518}
519
520fn flatten_varlink_usage(
530 usage: &crate::VarlinkUsage,
531 key_prefix: &str,
532) -> BTreeMap<String, serde_json::Value> {
533 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
534 let base_metric_name = gen_base_metric_key(key_prefix, "varlink_usage");
535 let fields: [(&str, Option<crate::CollectorTransport>); 7] = [
536 ("version", usage.version),
537 ("system_state", usage.system_state),
538 ("units", usage.units),
539 ("networkd", usage.networkd),
540 ("machines", usage.machines),
541 ("boot_blame", usage.boot_blame),
542 ("verify", usage.verify),
543 ];
544 for (name, transport) in fields {
545 if let Some(transport) = transport {
546 flat_stats.insert(
547 format!("{base_metric_name}.{name}"),
548 transport.as_u64().into(),
549 );
550 }
551 }
552 flat_stats
553}
554
555fn flatten_collector_timings(
556 timings: &[crate::CollectorTiming],
557 key_prefix: &str,
558) -> BTreeMap<String, serde_json::Value> {
559 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
560 let base_metric_name = gen_base_metric_key(key_prefix, "collector_timings");
561 for t in timings {
562 flat_stats.insert(
563 format!("{base_metric_name}.{}.start_offset_ms", t.name),
564 t.start_offset_ms.into(),
565 );
566 flat_stats.insert(
567 format!("{base_metric_name}.{}.elapsed_ms", t.name),
568 t.elapsed_ms.into(),
569 );
570 flat_stats.insert(
571 format!("{base_metric_name}.{}.success", t.name),
572 (if t.success { 1u64 } else { 0u64 }).into(),
573 );
574 }
575 flat_stats
576}
577
578fn flatten_units_collection_timings(
579 timings: &units::UnitsCollectionTimings,
580 key_prefix: &str,
581) -> BTreeMap<String, serde_json::Value> {
582 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
583 let base_metric_name = gen_base_metric_key(key_prefix, "collection_timings");
584 flat_stats.insert(
585 format!("{base_metric_name}.list_units_ms"),
586 timings.list_units_ms.into(),
587 );
588 flat_stats.insert(
589 format!("{base_metric_name}.per_unit_loop_ms"),
590 timings.per_unit_loop_ms.into(),
591 );
592 flat_stats.insert(
593 format!("{base_metric_name}.timer_dbus_fetches"),
594 timings.timer_dbus_fetches.into(),
595 );
596 flat_stats.insert(
597 format!("{base_metric_name}.state_dbus_fetches"),
598 timings.state_dbus_fetches.into(),
599 );
600 flat_stats.insert(
601 format!("{base_metric_name}.service_dbus_fetches"),
602 timings.service_dbus_fetches.into(),
603 );
604 for (idx, (unit_name, duration_ms)) in timings.slowest_units.iter().enumerate() {
607 flat_stats.insert(
608 format!("{base_metric_name}.slowest_units.{idx}.{unit_name}"),
609 (*duration_ms).into(),
610 );
611 }
612 flat_stats
613}
614
615fn flatten_stats(
617 stats_struct: &MonitordStats,
618 key_prefix: &str,
619) -> BTreeMap<String, serde_json::Value> {
620 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
621 flat_stats.insert(
622 gen_base_metric_key(key_prefix, "stat_collection_run_time_ms"),
623 stats_struct.stat_collection_run_time_ms.into(),
624 );
625 flat_stats.extend(flatten_collector_timings(
626 &stats_struct.collector_timings,
627 key_prefix,
628 ));
629 flat_stats.extend(flatten_varlink_usage(
630 &stats_struct.varlink_usage,
631 key_prefix,
632 ));
633 flat_stats.extend(flatten_units_collection_timings(
634 &stats_struct.units.collection_timings,
635 key_prefix,
636 ));
637 flat_stats.extend(flatten_networkd(&stats_struct.networkd, key_prefix));
638 flat_stats.extend(flatten_pid1(&stats_struct.pid1, key_prefix));
639 flat_stats.insert(
640 gen_base_metric_key(key_prefix, "system-state"),
641 (stats_struct.system_state as u64).into(),
642 );
643 flat_stats.extend(flatten_services(
644 &stats_struct.units.service_stats,
645 key_prefix,
646 ));
647 flat_stats.extend(flatten_timers(&stats_struct.units.timer_stats, key_prefix));
648 flat_stats.extend(flatten_unit_states(
649 &stats_struct.units.unit_states,
650 key_prefix,
651 ));
652 flat_stats.extend(flatten_units(&stats_struct.units, key_prefix));
653 flat_stats.extend(flatten_unit_files(
654 &stats_struct.units.unit_files,
655 key_prefix,
656 ));
657 flat_stats.insert(
658 gen_base_metric_key(key_prefix, "version"),
659 stats_struct.version.to_string().into(),
660 );
661 flat_stats.extend(flatten_machines(&stats_struct.machines, key_prefix));
662 flat_stats.extend(flatten_dbus_stats(&stats_struct.dbus_stats, key_prefix));
663 flat_stats.extend(flatten_boot_blame(&stats_struct.boot_blame, key_prefix));
664 flat_stats.extend(flatten_verify_stats(&stats_struct.verify_stats, key_prefix));
665 flat_stats
666}
667
668pub fn flatten(
670 stats_struct: &MonitordStats,
671 key_prefix: &str,
672) -> Result<String, serde_json::Error> {
673 serde_json::to_string_pretty(&flatten_stats(stats_struct, key_prefix))
674}
675
676#[cfg(test)]
677mod tests {
678 use crate::timer;
679
680 use super::*;
681
682 const EXPECTED_FLAT_JSON: &str = r###"{
684 "boot.blame.cpe_chef.service": 103.05,
685 "boot.blame.dnf5-automatic.service": 204.159,
686 "boot.blame.sys-module-fuse.device": 16.21,
687 "collection_timings.list_units_ms": 5.0,
688 "collection_timings.per_unit_loop_ms": 37.0,
689 "collection_timings.service_dbus_fetches": 1,
690 "collection_timings.slowest_units.0.unittest.service": 12.5,
691 "collection_timings.slowest_units.1.unittest.timer": 8.25,
692 "collection_timings.state_dbus_fetches": 0,
693 "collection_timings.timer_dbus_fetches": 4,
694 "collector_timings.boot_blame.elapsed_ms": 12.5,
695 "collector_timings.boot_blame.start_offset_ms": 0.25,
696 "collector_timings.boot_blame.success": 0,
697 "collector_timings.units.elapsed_ms": 42.0,
698 "collector_timings.units.start_offset_ms": 0.5,
699 "collector_timings.units.success": 1,
700 "machines.foo.collection_timings.list_units_ms": 0.0,
701 "machines.foo.collection_timings.per_unit_loop_ms": 0.0,
702 "machines.foo.collection_timings.service_dbus_fetches": 0,
703 "machines.foo.collection_timings.state_dbus_fetches": 0,
704 "machines.foo.collection_timings.timer_dbus_fetches": 0,
705 "machines.foo.networkd.managed_interfaces": 0,
706 "machines.foo.system-state": 0,
707 "machines.foo.timers.unittest.timer.accuracy_usec": 69,
708 "machines.foo.timers.unittest.timer.fixed_random_delay": 1,
709 "machines.foo.timers.unittest.timer.last_trigger_usec": 69,
710 "machines.foo.timers.unittest.timer.last_trigger_usec_monotonic": 69,
711 "machines.foo.timers.unittest.timer.next_elapse_usec_monotonic": 69,
712 "machines.foo.timers.unittest.timer.next_elapse_usec_realtime": 69,
713 "machines.foo.timers.unittest.timer.persistent": 0,
714 "machines.foo.timers.unittest.timer.randomized_delay_usec": 69,
715 "machines.foo.timers.unittest.timer.remain_after_elapse": 1,
716 "machines.foo.timers.unittest.timer.service_unit_last_state_change_usec": 69,
717 "machines.foo.timers.unittest.timer.service_unit_last_state_change_usec_monotonic": 69,
718 "machines.foo.units.activating_units": 0,
719 "machines.foo.units.active_units": 0,
720 "machines.foo.units.automount_units": 0,
721 "machines.foo.units.device_units": 0,
722 "machines.foo.units.failed_units": 0,
723 "machines.foo.units.inactive_units": 0,
724 "machines.foo.units.jobs_queued": 0,
725 "machines.foo.units.loaded_units": 0,
726 "machines.foo.units.masked_units": 0,
727 "machines.foo.units.mount_units": 0,
728 "machines.foo.units.not_found_units": 0,
729 "machines.foo.units.path_units": 0,
730 "machines.foo.units.scope_units": 0,
731 "machines.foo.units.service_units": 0,
732 "machines.foo.units.slice_units": 0,
733 "machines.foo.units.socket_units": 0,
734 "machines.foo.units.target_units": 0,
735 "machines.foo.units.timer_persistent_units": 0,
736 "machines.foo.units.timer_remain_after_elapse": 0,
737 "machines.foo.units.timer_units": 0,
738 "machines.foo.units.total_units": 0,
739 "networkd.eth0.address_state": 3,
740 "networkd.eth0.admin_state": 4,
741 "networkd.eth0.carrier_state": 5,
742 "networkd.eth0.ipv4_address_state": 3,
743 "networkd.eth0.ipv6_address_state": 2,
744 "networkd.eth0.oper_state": 9,
745 "networkd.eth0.required_for_online": 1,
746 "networkd.managed_interfaces": 1,
747 "pid1.cpu_time_kernel": 69,
748 "pid1.cpu_user_kernel": 69,
749 "pid1.fd_count": 69,
750 "pid1.memory_usage_bytes": 69,
751 "pid1.tasks": 1,
752 "services.unittest.service.active_enter_timestamp": 0,
753 "services.unittest.service.active_exit_timestamp": 0,
754 "services.unittest.service.cpuusage_nsec": 0,
755 "services.unittest.service.inactive_exit_timestamp": 0,
756 "services.unittest.service.ioread_bytes": 0,
757 "services.unittest.service.ioread_operations": 0,
758 "services.unittest.service.memory_available": 0,
759 "services.unittest.service.memory_current": 0,
760 "services.unittest.service.nrestarts": 0,
761 "services.unittest.service.processes": 0,
762 "services.unittest.service.restart_usec": 0,
763 "services.unittest.service.state_change_timestamp": 0,
764 "services.unittest.service.status_errno": -69,
765 "services.unittest.service.tasks_current": 0,
766 "services.unittest.service.timeout_clean_usec": 0,
767 "services.unittest.service.watchdog_usec": 0,
768 "stat_collection_run_time_ms": 69.0,
769 "system-state": 3,
770 "timers.unittest.timer.accuracy_usec": 69,
771 "timers.unittest.timer.fixed_random_delay": 1,
772 "timers.unittest.timer.last_trigger_usec": 69,
773 "timers.unittest.timer.last_trigger_usec_monotonic": 69,
774 "timers.unittest.timer.next_elapse_usec_monotonic": 69,
775 "timers.unittest.timer.next_elapse_usec_realtime": 69,
776 "timers.unittest.timer.persistent": 0,
777 "timers.unittest.timer.randomized_delay_usec": 69,
778 "timers.unittest.timer.remain_after_elapse": 1,
779 "timers.unittest.timer.service_unit_last_state_change_usec": 69,
780 "timers.unittest.timer.service_unit_last_state_change_usec_monotonic": 69,
781 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.active_state": 1,
782 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.load_state": 1,
783 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.unhealthy": 0,
784 "unit_states.unittest.service.active_state": 1,
785 "unit_states.unittest.service.load_state": 1,
786 "unit_states.unittest.service.time_in_state_usecs": 69,
787 "unit_states.unittest.service.unhealthy": 0,
788 "units.activating_units": 0,
789 "units.active_units": 0,
790 "units.automount_units": 0,
791 "units.device_units": 0,
792 "units.failed_units": 0,
793 "units.inactive_units": 0,
794 "units.jobs_queued": 0,
795 "units.loaded_units": 0,
796 "units.masked_units": 0,
797 "units.mount_units": 0,
798 "units.not_found_units": 0,
799 "units.path_units": 0,
800 "units.scope_units": 0,
801 "units.service_units": 0,
802 "units.slice_units": 0,
803 "units.socket_units": 0,
804 "units.target_units": 0,
805 "units.timer_persistent_units": 0,
806 "units.timer_remain_after_elapse": 0,
807 "units.timer_units": 0,
808 "units.total_units": 0,
809 "varlink_usage.boot_blame": 1,
810 "varlink_usage.machines": 0,
811 "varlink_usage.networkd": 1,
812 "varlink_usage.system_state": 1,
813 "varlink_usage.units": 0,
814 "varlink_usage.verify": 0,
815 "varlink_usage.version": 1,
816 "verify.failing.service": 2,
817 "verify.failing.slice": 1,
818 "verify.failing.total": 3,
819 "version": "255.7-1.fc40"
820}"###;
821
822 fn return_monitord_stats() -> MonitordStats {
823 let mut stats = MonitordStats {
824 networkd: networkd::NetworkdState {
825 interfaces_state: vec![networkd::InterfaceState {
826 address_state: networkd::AddressState::routable,
827 admin_state: networkd::AdminState::configured,
828 carrier_state: networkd::CarrierState::carrier,
829 ipv4_address_state: networkd::AddressState::routable,
830 ipv6_address_state: networkd::AddressState::degraded,
831 name: "eth0".to_string(),
832 network_file: "/etc/systemd/network/69-eno4.network".to_string(),
833 oper_state: networkd::OperState::routable,
834 required_for_online: networkd::BoolState::True,
835 }],
836 managed_interfaces: 1,
837 },
838 pid1: Some(crate::pid1::Pid1Stats {
839 cpu_time_kernel: 69,
840 cpu_time_user: 69,
841 memory_usage_bytes: 69,
842 fd_count: 69,
843 tasks: 1,
844 }),
845 system_state: crate::system::SystemdSystemState::running,
846 units: crate::units::SystemdUnitStats::default(),
847 version: String::from("255.7-1.fc40")
848 .try_into()
849 .expect("Unable to make SystemdVersion struct"),
850 machines: HashMap::from([(String::from("foo"), MachineStats::default())]),
851 dbus_stats: None,
852 boot_blame: None,
853 verify_stats: Some(crate::verify::VerifyStats {
854 total: 3,
855 by_type: HashMap::from([("service".to_string(), 2), ("slice".to_string(), 1)]),
856 }),
857 stat_collection_run_time_ms: 69.0,
858 collector_timings: vec![
859 crate::CollectorTiming {
860 name: "units".to_string(),
861 start_offset_ms: 0.5,
862 elapsed_ms: 42.0,
863 success: true,
864 },
865 crate::CollectorTiming {
866 name: "boot_blame".to_string(),
867 start_offset_ms: 0.25,
868 elapsed_ms: 12.5,
869 success: false,
870 },
871 ],
872 varlink_usage: crate::VarlinkUsage {
876 version: Some(crate::CollectorTransport::Varlink),
877 system_state: Some(crate::CollectorTransport::Varlink),
878 units: Some(crate::CollectorTransport::Dbus),
879 networkd: Some(crate::CollectorTransport::Varlink),
880 machines: Some(crate::CollectorTransport::Dbus),
881 boot_blame: Some(crate::CollectorTransport::Varlink),
882 verify: Some(crate::CollectorTransport::Dbus),
883 },
884 };
885 stats.units.collection_timings = units::UnitsCollectionTimings {
886 list_units_ms: 5.0,
887 unit_files_ms: 2.0,
888 per_unit_loop_ms: 37.0,
889 timer_dbus_fetches: 4,
890 state_dbus_fetches: 0,
891 service_dbus_fetches: 1,
892 slowest_units: vec![
893 ("unittest.service".to_string(), 12.5),
894 ("unittest.timer".to_string(), 8.25),
895 ],
896 };
897 let service_unit_name = String::from("unittest.service");
898 stats.units.service_stats.insert(
899 service_unit_name.clone(),
900 units::ServiceStats {
901 status_errno: -69,
903 ..Default::default()
904 },
905 );
906 stats.units.unit_states.insert(
907 String::from("unittest.service"),
908 units::UnitStates {
909 active_state: units::SystemdUnitActiveState::active,
910 load_state: units::SystemdUnitLoadState::loaded,
911 unhealthy: false,
912 time_in_state_usecs: Some(69),
913 },
914 );
915 let timer_unit = String::from("unittest.timer");
916 let timer_stats = timer::TimerStats {
917 accuracy_usec: 69,
918 fixed_random_delay: true,
919 last_trigger_usec: 69,
920 last_trigger_usec_monotonic: 69,
921 next_elapse_usec_monotonic: 69,
922 next_elapse_usec_realtime: 69,
923 persistent: false,
924 randomized_delay_usec: 69,
925 remain_after_elapse: true,
926 service_unit_last_state_change_usec: 69,
927 service_unit_last_state_change_usec_monotonic: 69,
928 };
929 stats
930 .units
931 .timer_stats
932 .insert(timer_unit.clone(), timer_stats.clone());
933 stats
934 .machines
935 .get_mut("foo")
936 .expect("No machine foo? WTF")
937 .units
938 .timer_stats
939 .insert(timer_unit, timer_stats);
940 stats.units.unit_states.insert(
942 String::from(
943 r"nvme\x2dWDC_CL_SN730_SDBQNTY\x2d512G\x2d2020_37222H80070511\x2dpart3.device",
944 ),
945 units::UnitStates {
946 active_state: units::SystemdUnitActiveState::active,
947 load_state: units::SystemdUnitLoadState::loaded,
948 unhealthy: false,
949 time_in_state_usecs: None,
950 },
951 );
952 let mut boot_blame = crate::boot::BootBlameStats::new();
954 boot_blame.insert(String::from("dnf5-automatic.service"), 204.159);
955 boot_blame.insert(String::from("cpe_chef.service"), 103.050);
956 boot_blame.insert(String::from("sys-module-fuse.device"), 16.210);
957 stats.boot_blame = Some(boot_blame);
958 stats
959 }
960
961 #[test]
962 fn test_flatten_map() {
963 let json_flat_map = flatten_stats(&return_monitord_stats(), "");
964 assert_eq!(136, json_flat_map.len());
965 }
966
967 #[test]
968 fn test_collector_transport_serializes_as_integer_everywhere() {
969 let usage = crate::VarlinkUsage {
972 version: Some(crate::CollectorTransport::Varlink),
973 units: Some(crate::CollectorTransport::Dbus),
974 ..Default::default()
975 };
976 let value = serde_json::to_value(&usage).expect("serialize");
977 assert_eq!(value["version"], serde_json::Value::from(1u64));
978 assert_eq!(value["units"], serde_json::Value::from(0u64));
979 assert!(value.get("networkd").is_none());
980 }
981
982 #[test]
983 fn test_flatten_varlink_usage_only_emits_ran_collectors() {
984 let flat = flatten_varlink_usage(
988 &crate::VarlinkUsage {
989 version: Some(crate::CollectorTransport::Varlink),
990 units: Some(crate::CollectorTransport::Dbus),
991 ..Default::default()
992 },
993 "",
994 );
995 assert_eq!(flat.len(), 2);
996 assert_eq!(flat["varlink_usage.version"], serde_json::Value::from(1u64));
997 assert_eq!(flat["varlink_usage.units"], serde_json::Value::from(0u64));
998 assert!(flatten_varlink_usage(&crate::VarlinkUsage::default(), "").is_empty());
999 }
1000
1001 #[test]
1002 fn test_flatten() {
1003 let json_flat = flatten(&return_monitord_stats(), "").expect("JSON serialize failed");
1004 assert_eq!(EXPECTED_FLAT_JSON, json_flat);
1005 }
1006
1007 #[test]
1008 fn test_flatten_prefixed() {
1009 let json_flat =
1010 flatten(&return_monitord_stats(), "monitord").expect("JSON serialize failed");
1011 let json_flat_unserialized: BTreeMap<String, serde_json::Value> =
1012 serde_json::from_str(&json_flat).expect("JSON from_str failed");
1013 for (key, _value) in json_flat_unserialized.iter() {
1014 assert!(key.starts_with("monitord."));
1015 }
1016 }
1017
1018 #[test]
1024 fn test_unit_counters_covers_all_scalar_fields() {
1025 const NON_COUNTER_FIELDS: &[&str] = &[
1027 "unit_files",
1028 "service_stats",
1029 "timer_stats",
1030 "unit_states",
1031 "collection_timings",
1032 ];
1033
1034 let expected: std::collections::BTreeSet<&str> = units::UNIT_FIELD_NAMES
1036 .iter()
1037 .copied()
1038 .filter(|f| !NON_COUNTER_FIELDS.contains(f))
1039 .collect();
1040
1041 let counters_json =
1043 serde_json::to_value(UnitCounters::from(&units::SystemdUnitStats::default()))
1044 .expect("UnitCounters serialization failed");
1045 let actual: std::collections::BTreeSet<&str> = counters_json
1046 .as_object()
1047 .expect("UnitCounters must serialize to a JSON object")
1048 .keys()
1049 .map(|s| s.as_str())
1050 .collect();
1051
1052 assert_eq!(
1053 expected,
1054 actual,
1055 "UnitCounters is out of sync with SystemdUnitStats scalar fields.\n\
1056 Missing from UnitCounters: {:?}\n\
1057 Extra in UnitCounters: {:?}",
1058 expected.difference(&actual).collect::<Vec<_>>(),
1059 actual.difference(&expected).collect::<Vec<_>>(),
1060 );
1061 }
1062}