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.dbus_broker_peer_accounting {
444 for peer in peer_accounting.values() {
446 let peer_name = peer.get_name_for_metric();
447
448 let peer_fields = [
449 ("name_objects", peer.name_objects),
450 ("match_bytes", peer.match_bytes),
451 ("matches", peer.matches),
452 ("reply_objects", peer.reply_objects),
453 ("incoming_bytes", peer.incoming_bytes),
454 ("incoming_fds", peer.incoming_fds),
455 ("outgoing_bytes", peer.outgoing_bytes),
456 ("outgoing_fds", peer.outgoing_fds),
457 ("activation_request_bytes", peer.activation_request_bytes),
458 ("activation_request_fds", peer.activation_request_fds),
459 ];
460
461 for (field_name, value) in peer_fields {
462 if let Some(val) = value {
463 flat_stats.insert(
464 format!("{base_metric_name}.peer.{peer_name}.{field_name}"),
465 val.into(),
466 );
467 }
468 }
469 }
470 }
471
472 if let Some(user_accounting) = &dbus_stats.dbus_broker_user_accounting {
473 for user in user_accounting.values() {
475 let user_name = user.get_name_for_metric();
476 let user_fields = [
477 ("bytes", user.bytes.clone()),
478 ("fds", user.fds.clone()),
479 ("matches", user.matches.clone()),
480 ("objects", user.objects.clone()),
481 ];
482
483 for (field_name, value) in user_fields {
484 if let Some(val) = value {
485 flat_stats.insert(
486 format!("{base_metric_name}.user.{user_name}.{field_name}"),
487 val.cur.into(),
488 );
489
490 }
492 }
493 }
494 }
495
496 flat_stats
497}
498
499fn flatten_stats(
501 stats_struct: &MonitordStats,
502 key_prefix: &String,
503) -> BTreeMap<String, serde_json::Value> {
504 let mut flat_stats: BTreeMap<String, serde_json::Value> = BTreeMap::new();
505 flat_stats.extend(flatten_networkd(&stats_struct.networkd, key_prefix));
506 flat_stats.extend(flatten_pid1(&stats_struct.pid1, key_prefix));
507 flat_stats.insert(
508 gen_base_metric_key(key_prefix, &String::from("system-state")),
509 (stats_struct.system_state as u64).into(),
510 );
511 flat_stats.extend(flatten_services(
512 &stats_struct.units.service_stats,
513 key_prefix,
514 ));
515 flat_stats.extend(flatten_timers(&stats_struct.units.timer_stats, key_prefix));
516 flat_stats.extend(flatten_unit_states(
517 &stats_struct.units.unit_states,
518 key_prefix,
519 ));
520 flat_stats.extend(flatten_units(&stats_struct.units, key_prefix));
521 flat_stats.insert(
522 gen_base_metric_key(key_prefix, &String::from("version")),
523 stats_struct.version.to_string().into(),
524 );
525 flat_stats.extend(flatten_machines(&stats_struct.machines, key_prefix));
526 flat_stats.extend(flatten_dbus_stats(&stats_struct.dbus_stats, key_prefix));
527 flat_stats
528}
529
530pub fn flatten(
532 stats_struct: &MonitordStats,
533 key_prefix: &String,
534) -> Result<String, serde_json::Error> {
535 serde_json::to_string_pretty(&flatten_stats(stats_struct, key_prefix))
536}
537
538#[cfg(test)]
539mod tests {
540 use crate::timer;
541
542 use super::*;
543
544 const EXPECTED_FLAT_JSON: &str = r###"{
546 "machines.foo.networkd.managed_interfaces": 0,
547 "machines.foo.system-state": 0,
548 "machines.foo.timers.unittest.timer.accuracy_usec": 69,
549 "machines.foo.timers.unittest.timer.fixed_random_delay": 1,
550 "machines.foo.timers.unittest.timer.last_trigger_usec": 69,
551 "machines.foo.timers.unittest.timer.last_trigger_usec_monotonic": 69,
552 "machines.foo.timers.unittest.timer.next_elapse_usec_monotonic": 69,
553 "machines.foo.timers.unittest.timer.next_elapse_usec_realtime": 69,
554 "machines.foo.timers.unittest.timer.persistent": 0,
555 "machines.foo.timers.unittest.timer.randomized_delay_usec": 69,
556 "machines.foo.timers.unittest.timer.remain_after_elapse": 1,
557 "machines.foo.timers.unittest.timer.service_unit_last_state_change_usec": 69,
558 "machines.foo.timers.unittest.timer.service_unit_last_state_change_usec_monotonic": 69,
559 "machines.foo.units.active_units": 0,
560 "machines.foo.units.automount_units": 0,
561 "machines.foo.units.device_units": 0,
562 "machines.foo.units.failed_units": 0,
563 "machines.foo.units.inactive_units": 0,
564 "machines.foo.units.jobs_queued": 0,
565 "machines.foo.units.loaded_units": 0,
566 "machines.foo.units.masked_units": 0,
567 "machines.foo.units.mount_units": 0,
568 "machines.foo.units.not_found_units": 0,
569 "machines.foo.units.path_units": 0,
570 "machines.foo.units.scope_units": 0,
571 "machines.foo.units.service_units": 0,
572 "machines.foo.units.slice_units": 0,
573 "machines.foo.units.socket_units": 0,
574 "machines.foo.units.target_units": 0,
575 "machines.foo.units.timer_persistent_units": 0,
576 "machines.foo.units.timer_remain_after_elapse": 0,
577 "machines.foo.units.timer_units": 0,
578 "machines.foo.units.total_units": 0,
579 "networkd.eth0.address_state": 3,
580 "networkd.eth0.admin_state": 4,
581 "networkd.eth0.carrier_state": 5,
582 "networkd.eth0.ipv4_address_state": 3,
583 "networkd.eth0.ipv6_address_state": 2,
584 "networkd.eth0.oper_state": 9,
585 "networkd.eth0.required_for_online": 1,
586 "networkd.managed_interfaces": 1,
587 "pid1.cpu_time_kernel": 69,
588 "pid1.cpu_user_kernel": 69,
589 "pid1.fd_count": 69,
590 "pid1.memory_usage_bytes": 69,
591 "pid1.tasks": 1,
592 "services.unittest.service.active_enter_timestamp": 0,
593 "services.unittest.service.active_exit_timestamp": 0,
594 "services.unittest.service.cpuusage_nsec": 0,
595 "services.unittest.service.inactive_exit_timestamp": 0,
596 "services.unittest.service.ioread_bytes": 0,
597 "services.unittest.service.ioread_operations": 0,
598 "services.unittest.service.memory_available": 0,
599 "services.unittest.service.memory_current": 0,
600 "services.unittest.service.nrestarts": 0,
601 "services.unittest.service.processes": 0,
602 "services.unittest.service.restart_usec": 0,
603 "services.unittest.service.state_change_timestamp": 0,
604 "services.unittest.service.status_errno": -69,
605 "services.unittest.service.tasks_current": 0,
606 "services.unittest.service.timeout_clean_usec": 0,
607 "services.unittest.service.watchdog_usec": 0,
608 "system-state": 3,
609 "timers.unittest.timer.accuracy_usec": 69,
610 "timers.unittest.timer.fixed_random_delay": 1,
611 "timers.unittest.timer.last_trigger_usec": 69,
612 "timers.unittest.timer.last_trigger_usec_monotonic": 69,
613 "timers.unittest.timer.next_elapse_usec_monotonic": 69,
614 "timers.unittest.timer.next_elapse_usec_realtime": 69,
615 "timers.unittest.timer.persistent": 0,
616 "timers.unittest.timer.randomized_delay_usec": 69,
617 "timers.unittest.timer.remain_after_elapse": 1,
618 "timers.unittest.timer.service_unit_last_state_change_usec": 69,
619 "timers.unittest.timer.service_unit_last_state_change_usec_monotonic": 69,
620 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.active_state": 1,
621 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.load_state": 1,
622 "unit_states.nvme\\x2dWDC_CL_SN730_SDBQNTY\\x2d512G\\x2d2020_37222H80070511\\x2dpart3.device.unhealthy": 0,
623 "unit_states.unittest.service.active_state": 1,
624 "unit_states.unittest.service.load_state": 1,
625 "unit_states.unittest.service.time_in_state_usecs": 69,
626 "unit_states.unittest.service.unhealthy": 0,
627 "units.active_units": 0,
628 "units.automount_units": 0,
629 "units.device_units": 0,
630 "units.failed_units": 0,
631 "units.inactive_units": 0,
632 "units.jobs_queued": 0,
633 "units.loaded_units": 0,
634 "units.masked_units": 0,
635 "units.mount_units": 0,
636 "units.not_found_units": 0,
637 "units.path_units": 0,
638 "units.scope_units": 0,
639 "units.service_units": 0,
640 "units.slice_units": 0,
641 "units.socket_units": 0,
642 "units.target_units": 0,
643 "units.timer_persistent_units": 0,
644 "units.timer_remain_after_elapse": 0,
645 "units.timer_units": 0,
646 "units.total_units": 0,
647 "version": "255.7-1.fc40"
648}"###;
649
650 fn return_monitord_stats() -> MonitordStats {
651 let mut stats = MonitordStats {
652 networkd: networkd::NetworkdState {
653 interfaces_state: vec![networkd::InterfaceState {
654 address_state: networkd::AddressState::routable,
655 admin_state: networkd::AdminState::configured,
656 carrier_state: networkd::CarrierState::carrier,
657 ipv4_address_state: networkd::AddressState::routable,
658 ipv6_address_state: networkd::AddressState::degraded,
659 name: "eth0".to_string(),
660 network_file: "/etc/systemd/network/69-eno4.network".to_string(),
661 oper_state: networkd::OperState::routable,
662 required_for_online: networkd::BoolState::True,
663 }],
664 managed_interfaces: 1,
665 },
666 pid1: Some(crate::pid1::Pid1Stats {
667 cpu_time_kernel: 69,
668 cpu_time_user: 69,
669 memory_usage_bytes: 69,
670 fd_count: 69,
671 tasks: 1,
672 }),
673 system_state: crate::system::SystemdSystemState::running,
674 units: crate::units::SystemdUnitStats::default(),
675 version: String::from("255.7-1.fc40")
676 .try_into()
677 .expect("Unable to make SystemdVersion struct"),
678 machines: HashMap::from([(String::from("foo"), MachineStats::default())]),
679 dbus_stats: None,
680 };
681 let service_unit_name = String::from("unittest.service");
682 stats.units.service_stats.insert(
683 service_unit_name.clone(),
684 units::ServiceStats {
685 status_errno: -69,
687 ..Default::default()
688 },
689 );
690 stats.units.unit_states.insert(
691 String::from("unittest.service"),
692 units::UnitStates {
693 active_state: units::SystemdUnitActiveState::active,
694 load_state: units::SystemdUnitLoadState::loaded,
695 unhealthy: false,
696 time_in_state_usecs: Some(69),
697 },
698 );
699 let timer_unit = String::from("unittest.timer");
700 let timer_stats = timer::TimerStats {
701 accuracy_usec: 69,
702 fixed_random_delay: true,
703 last_trigger_usec: 69,
704 last_trigger_usec_monotonic: 69,
705 next_elapse_usec_monotonic: 69,
706 next_elapse_usec_realtime: 69,
707 persistent: false,
708 randomized_delay_usec: 69,
709 remain_after_elapse: true,
710 service_unit_last_state_change_usec: 69,
711 service_unit_last_state_change_usec_monotonic: 69,
712 };
713 stats
714 .units
715 .timer_stats
716 .insert(timer_unit.clone(), timer_stats.clone());
717 stats
718 .machines
719 .get_mut("foo")
720 .expect("No machine foo? WTF")
721 .units
722 .timer_stats
723 .insert(timer_unit, timer_stats);
724 stats.units.unit_states.insert(
726 String::from(
727 r"nvme\x2dWDC_CL_SN730_SDBQNTY\x2d512G\x2d2020_37222H80070511\x2dpart3.device",
728 ),
729 units::UnitStates {
730 active_state: units::SystemdUnitActiveState::active,
731 load_state: units::SystemdUnitLoadState::loaded,
732 unhealthy: false,
733 time_in_state_usecs: None,
734 },
735 );
736 stats
737 }
738
739 #[test]
740 fn test_flatten_map() {
741 let json_flat_map = flatten_stats(
742 &return_monitord_stats(),
743 &String::from("JSON serialize failed"),
744 );
745 assert_eq!(102, json_flat_map.len());
746 }
747
748 #[test]
749 fn test_flatten() {
750 let json_flat =
751 flatten(&return_monitord_stats(), &String::from("")).expect("JSON serialize failed");
752 assert_eq!(EXPECTED_FLAT_JSON, json_flat);
753 }
754
755 #[test]
756 fn test_flatten_prefixed() {
757 let json_flat = flatten(&return_monitord_stats(), &String::from("monitord"))
758 .expect("JSON serialize failed");
759 let json_flat_unserialized: BTreeMap<String, serde_json::Value> =
760 serde_json::from_str(&json_flat).expect("JSON from_str failed");
761 for (key, _value) in json_flat_unserialized.iter() {
762 assert!(key.starts_with("monitord."));
763 }
764 }
765}