From 77c76a0b71c88d9d4d37be7587e2a5e008b61492 Mon Sep 17 00:00:00 2001 From: zion Date: Sun, 24 Aug 2025 20:12:09 -0400 Subject: [PATCH 1/4] status of a vector of services --- src/collector.rs | 33 +++++++++++++++++++++++++++++++++ src/main.rs | 9 ++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/collector.rs b/src/collector.rs index 68d9e89..df9a117 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -1,5 +1,6 @@ //! Module to define behavior of sys info collection. use serde_json::{Value, json}; +use std::process::Command; use sysinfo::{Disks, Networks, System}; /// Function to get raw uptime. @@ -79,3 +80,35 @@ pub fn get_disk_usage() -> Vec { }) .collect() } + +/// Function to get status of specific systemd services by name +pub fn get_service_status(service_names: Vec) -> Vec { + let mut results = Vec::new(); + + for service_name in service_names { + let status = get_service_active_status(&service_name); + + let service_info = json!({ + "service_name": service_name, + "status": status + }); + + results.push(service_info); + } + + results +} + +/// Get the active status of a service (active, inactive, failed, etc.) +fn get_service_active_status(service_name: &str) -> String { + match Command::new("systemctl") + .args(&["is-active", service_name]) + .output() + { + Ok(output) => str::from_utf8(&output.stdout) + .unwrap_or("unknown") + .trim() + .to_string(), + Err(_) => "error".to_string(), + } +} diff --git a/src/main.rs b/src/main.rs index c65ce0d..3dc1938 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -//! Main module for tinycollectd. +//! Main module for tinyd. use clap::{Parser, ValueEnum}; use std::net::{Ipv4Addr, SocketAddrV4}; use std::time::Duration; @@ -10,9 +10,12 @@ struct Cli { /// destination for metrics (e.g. 127.0.0.1:1555) #[arg(long, default_value_t = SocketAddrV4::new(Ipv4Addr::new(127,0,0,1), 1555))] destination: SocketAddrV4, - /// metrics tinycollectd would collect + /// metrics tinyd would collect #[arg(long, value_enum, value_delimiter = ',', default_value = "All")] metrics: Vec, + /// list of services to pull status + #[arg(long)] + services: Vec, /// interval for data to be collected in seconds. #[arg(long, default_value = "10")] collection_interval: u64, @@ -28,7 +31,7 @@ enum MetricType { } /// Function to add hostname, timestamp, and other metadata to individual metrics -/// Entrypoint for tinycollectd async runtime. +/// Entrypoint for tinyd async runtime. #[tokio::main] async fn main() -> Result<(), Box> { From c5fa85417b5b4eef1cb0bab85ef4ae45322f968f Mon Sep 17 00:00:00 2001 From: zion Date: Sun, 24 Aug 2025 21:27:22 -0400 Subject: [PATCH 2/4] these names are poo poo --- src/collector.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/collector.rs b/src/collector.rs index df9a117..fb96ddc 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -82,27 +82,27 @@ pub fn get_disk_usage() -> Vec { } /// Function to get status of specific systemd services by name -pub fn get_service_status(service_names: Vec) -> Vec { +pub fn get_service_status(services: Vec) -> Vec { let mut results = Vec::new(); - for service_name in service_names { - let status = get_service_active_status(&service_name); + for service in services { + let status = get_service_active_status(&service); - let service_info = json!({ - "service_name": service_name, + let service_status = json!({ + "service_name": service, "status": status }); - results.push(service_info); + results.push(service_status); } results } /// Get the active status of a service (active, inactive, failed, etc.) -fn get_service_active_status(service_name: &str) -> String { +fn get_service_active_status(service: &str) -> String { match Command::new("systemctl") - .args(&["is-active", service_name]) + .args(&["is-active", service]) .output() { Ok(output) => str::from_utf8(&output.stdout) From 9a8af43f43dbee9294ee1172d30c1f9e8cf39910 Mon Sep 17 00:00:00 2001 From: zion Date: Tue, 2 Sep 2025 21:23:40 -0400 Subject: [PATCH 3/4] lost in the sauce at this point, a tmr problem im afraid --- src/collector.rs | 28 +++++++++++++++++----------- src/main.rs | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/collector.rs b/src/collector.rs index fb96ddc..f5f2361 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -12,20 +12,26 @@ fn cpu_freq_raw(sys: &System) -> String { let cpu_freq = sys.cpus().first().map(|cpu| cpu.frequency()).unwrap_or(0); cpu_freq.to_string() } -/// Function to collect system metrics as single json object. -pub fn get_sysinfo(sys: &System) -> Value { - let timestamp = std::time::SystemTime::now() + +/// Function to get timestamp +pub fn get_timestamp() -> u64 { + std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() - .as_secs(); + .as_secs() +} - let hostname = System::host_name() +/// Function to get hostname +pub fn get_hostname(sys: &System) -> String { + System::host_name() .unwrap_or_else(|| "unknown".to_string()) - .replace('"', "\\\""); - + .replace('"', "\\\"") +} +/// Function to collect system metrics as single json object. +pub fn get_sysinfo(sys: &System) -> Value { json!({ - "timestamp": timestamp, - "hostname": hostname, + "timestamp": get_timestamp(), + "hostname": get_hostname(sys), "uptime": uptime_raw(sys), "cpu_freq_mhz": cpu_freq_raw(sys), "disk_usage": get_disk_usage(), @@ -82,7 +88,7 @@ pub fn get_disk_usage() -> Vec { } /// Function to get status of specific systemd services by name -pub fn get_service_status(services: Vec) -> Vec { +pub fn get_service_status(services: &[String]) -> Vec { let mut results = Vec::new(); for service in services { @@ -99,7 +105,7 @@ pub fn get_service_status(services: Vec) -> Vec { results } -/// Get the active status of a service (active, inactive, failed, etc.) +/// Get the active status of a service fn get_service_active_status(service: &str) -> String { match Command::new("systemctl") .args(&["is-active", service]) diff --git a/src/main.rs b/src/main.rs index 3dc1938..be47651 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ //! Main module for tinyd. use clap::{Parser, ValueEnum}; +use serde_json::json; use std::net::{Ipv4Addr, SocketAddrV4}; use std::time::Duration; use sysinfo::System; @@ -20,7 +21,7 @@ struct Cli { #[arg(long, default_value = "10")] collection_interval: u64, } -#[derive(ValueEnum, Clone)] +#[derive(ValueEnum, Clone, Debug, PartialEq)] enum MetricType { All, DiskUsage, @@ -44,16 +45,41 @@ async fn main() -> Result<(), Box> { loop { sys.refresh_all(); // refresh once on every collection attempt - let bytes = serde_json::to_vec(&collector::get_sysinfo(&sys)).unwrap(); + let mut metrics: Vec = Vec::new(); // metrics vector to hold data to be sent + // Little state machine action, which I'm sure there is a better, more idiomatic way of doing this. + if cli.metrics.contains(&MetricType::All) { + metrics = serde_json::to_vec(&collector::get_sysinfo(&sys)).unwrap(); + } else { + if cli.metrics.contains(&MetricType::Service) { + metrics.extend( + serde_json::to_vec(&collector::get_service_status(&cli.services)).unwrap(), + ); + } else if cli.metrics.contains(&MetricType::DiskUsage) { + metrics.extend(serde_json::to_vec(&collector::get_disk_usage()).unwrap()); + } else if cli.metrics.contains(&MetricType::Network) { + metrics.extend(serde_json::to_vec(&collector::get_if_data()).unwrap()); + } else if cli.metrics.contains(&MetricType::Cpufreq) { + metrics.extend(serde_json::to_vec(&collector::cpu_freq_json(&sys)).unwrap()); + } else if cli.metrics.contains(&MetricType::Uptime) { + metrics.extend(serde_json::to_vec(&collector::uptime_json(&sys)).unwrap()); + } + } + // This feels like it should be in the collector module, but I don't see a clean way of getting it in there + let combined = json!({ + "timestamp": &collector::get_timestamp(), + "hostname": &collector::get_hostname(&sys), + "metrics": metrics, + }); + let bytes = serde_json::to_vec(&combined).unwrap(); // Send UDP packet if let Err(e) = socket.send_to(&bytes, cli.destination).await { eprintln!("Failed to send UDP packet: {}", e); } else { println!( - "Sent metrics to {} ({} bytes)", + "Sent metrics to {} ({} metrics)", cli.destination, - &bytes.len() + &metrics.len() ); } From ab6ca9c01d71bfcebc72f6d1b2abb87e009aa87a Mon Sep 17 00:00:00 2001 From: zion Date: Sun, 7 Sep 2025 18:38:48 -0400 Subject: [PATCH 4/4] this kinda works, the output looks like shit though --- src/main.rs | 78 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/src/main.rs b/src/main.rs index be47651..c47b836 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ //! Main module for tinyd. use clap::{Parser, ValueEnum}; -use serde_json::json; +use serde_json::{Value, json}; use std::net::{Ipv4Addr, SocketAddrV4}; use std::time::Duration; use sysinfo::System; @@ -45,44 +45,74 @@ async fn main() -> Result<(), Box> { loop { sys.refresh_all(); // refresh once on every collection attempt - let mut metrics: Vec = Vec::new(); // metrics vector to hold data to be sent - // Little state machine action, which I'm sure there is a better, more idiomatic way of doing this. - if cli.metrics.contains(&MetricType::All) { - metrics = serde_json::to_vec(&collector::get_sysinfo(&sys)).unwrap(); + let metrics_value = if cli.metrics.contains(&MetricType::All) { + collector::get_sysinfo(&sys) } else { + let mut combined_object = serde_json::Map::new(); + let mut combined_arrays = Vec::new(); + if cli.metrics.contains(&MetricType::Service) { - metrics.extend( - serde_json::to_vec(&collector::get_service_status(&cli.services)).unwrap(), - ); - } else if cli.metrics.contains(&MetricType::DiskUsage) { - metrics.extend(serde_json::to_vec(&collector::get_disk_usage()).unwrap()); - } else if cli.metrics.contains(&MetricType::Network) { - metrics.extend(serde_json::to_vec(&collector::get_if_data()).unwrap()); - } else if cli.metrics.contains(&MetricType::Cpufreq) { - metrics.extend(serde_json::to_vec(&collector::cpu_freq_json(&sys)).unwrap()); - } else if cli.metrics.contains(&MetricType::Uptime) { - metrics.extend(serde_json::to_vec(&collector::uptime_json(&sys)).unwrap()); + let service_data = collector::get_service_status(&cli.services); + combined_arrays.extend(service_data); } - } - // This feels like it should be in the collector module, but I don't see a clean way of getting it in there + + if cli.metrics.contains(&MetricType::DiskUsage) { + let disk_data = collector::get_disk_usage(); + combined_arrays.extend(disk_data); + } + + if cli.metrics.contains(&MetricType::Network) { + let network_data = collector::get_if_data(); + combined_arrays.extend(network_data); + } + + if cli.metrics.contains(&MetricType::Cpufreq) { + let cpu_data = collector::cpu_freq_json(&sys); + if let Value::Object(map) = cpu_data { + combined_object.extend(map); + } + } + + if cli.metrics.contains(&MetricType::Uptime) { + let uptime_data = collector::uptime_json(&sys); + if let Value::Object(map) = uptime_data { + combined_object.extend(map); + } + } + + // Combine single values and arrays + if !combined_object.is_empty() && !combined_arrays.is_empty() { + combined_object.insert("array_data".to_string(), Value::Array(combined_arrays)); + Value::Object(combined_object) + } else if !combined_object.is_empty() { + Value::Object(combined_object) + } else if !combined_arrays.is_empty() { + Value::Array(combined_arrays) + } else { + json!({}) + } + }; + let combined = json!({ - "timestamp": &collector::get_timestamp(), - "hostname": &collector::get_hostname(&sys), - "metrics": metrics, + "timestamp": collector::get_timestamp(), + "hostname": collector::get_hostname(&sys), + "metrics": metrics_value, }); + let bytes = serde_json::to_vec(&combined).unwrap(); + // Send UDP packet if let Err(e) = socket.send_to(&bytes, cli.destination).await { eprintln!("Failed to send UDP packet: {}", e); } else { println!( - "Sent metrics to {} ({} metrics)", + "Sent metrics to {} ({} bytes)", cli.destination, - &metrics.len() + bytes.len() ); } - tokio::time::sleep(Duration::from_secs(10)).await; + tokio::time::sleep(Duration::from_secs(cli.collection_interval)).await; } }