1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! # monitord Crate
//!
//! `monitord` is a library to gather statistics about systemd.
//! Some APIs are a little ugly due to being a configparser INI based configuration
//! driven CLL at heart.

use std::thread;
use std::time::Duration;
use std::time::Instant;

use anyhow::Result;
use tracing::error;
use tracing::info;

pub mod config;
pub(crate) mod dbus;
pub mod json;
pub mod logging;
pub mod networkd;
pub mod pid1;
pub mod system;
pub mod units;

pub const DEFAULT_DBUS_ADDRESS: &str = "unix:path=/run/dbus/system_bus_socket";

/// Main monitord stats struct collection all enabled stats
#[derive(serde::Serialize, serde::Deserialize, Debug, Default, Eq, PartialEq)]
pub struct MonitordStats {
    pub networkd: networkd::NetworkdState,
    pub pid1: Option<pid1::Pid1Stats>,
    pub system_state: system::SystemdSystemState,
    pub units: units::SystemdUnitStats,
    pub version: system::SystemdVersion,
}

/// Print statistics in the format set in configuration
pub fn print_stats(
    key_prefix: &str,
    output_format: &config::MonitordOutputFormat,
    stats: &MonitordStats,
) {
    match output_format {
        config::MonitordOutputFormat::Json => println!(
            "{}",
            serde_json::to_string(&stats).expect("Invalid JSON serialization")
        ),
        config::MonitordOutputFormat::JsonFlat => println!(
            "{}",
            json::flatten(stats, &key_prefix.to_string()).expect("Invalid JSON serialization")
        ),
        config::MonitordOutputFormat::JsonPretty => println!(
            "{}",
            serde_json::to_string_pretty(&stats).expect("Invalid JSON serialization")
        ),
    }
}

/// Main statictic collection function running what's required by configuration
pub fn stat_collector(config: config::Config) -> Result<(), String> {
    let mut collect_interval_ms: u128 = 0;
    if config.monitord.daemon {
        collect_interval_ms = (config.monitord.daemon_stats_refresh_secs * 1000).into();
    }

    let mut monitord_stats = MonitordStats::default();
    loop {
        let collect_start_time = Instant::now();
        let mut ran_collector_count: u8 = 0;

        info!("Starting stat collection run");

        // Collect pid1 procfs stas
        if config.pid1.enabled {
            monitord_stats.pid1 = match crate::pid1::get_pid1_stats() {
                Ok(s) => Some(s),
                Err(err) => {
                    error!("Unable to set pid1 stats: {:?}", err);
                    None
                }
            }
        }

        // TODO: Move each collector into a function + thread
        // Run networkd collector if enabled
        if config.networkd.enabled {
            ran_collector_count += 1;
            match networkd::parse_interface_state_files(
                &config.networkd.link_state_dir,
                None,
                &config.monitord.dbus_address,
            ) {
                Ok(networkd_stats) => monitord_stats.networkd = networkd_stats,
                Err(err) => error!("networkd stats failed: {:?}", err),
            }
        }

        // Run system running (SystemState) state collector
        if config.system_state.enabled {
            ran_collector_count += 1;
            monitord_stats.system_state =
                crate::system::get_system_state(&config.monitord.dbus_address)
                    .map_err(|e| format!("Error getting system state: {:?}", e))?;
        }
        // Not incrementing the ran_collector_count on purpose as this is always on by default
        monitord_stats.version = crate::system::get_version(&config.monitord.dbus_address)
            .map_err(|e| format!("Error getting systemd versions: {:?}", e))?;

        // Run service collectors if there are services listed in config
        if config.units.enabled {
            ran_collector_count += 1;
            match units::parse_unit_state(&config) {
                Ok(units_stats) => monitord_stats.units = units_stats,
                Err(err) => error!("units stats failed: {:?}", err),
            }
        }

        if ran_collector_count < 1 {
            error!("No collectors ran. Exiting");
            std::process::exit(1);
        }

        let elapsed_runtime_ms = collect_start_time.elapsed().as_millis();
        info!("stat collection run took {}ms", elapsed_runtime_ms);
        print_stats(
            &config.monitord.key_prefix,
            &config.monitord.output_format,
            &monitord_stats,
        );
        if !config.monitord.daemon {
            break;
        }
        let sleep_time_ms = collect_interval_ms - elapsed_runtime_ms;
        info!("stat collection sleeping for {}s 😴", sleep_time_ms / 1000);
        thread::sleep(Duration::from_millis(
            sleep_time_ms
                .try_into()
                .expect("Sleep time does not fit into a u64 :O"),
        ));
    }
    Ok(())
}