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