diff --git a/src/rust/lqos_bus/src/anonymous/mod.rs b/src/rust/lqos_bus/src/anonymous/mod.rs new file mode 100644 index 00000000..26af6293 --- /dev/null +++ b/src/rust/lqos_bus/src/anonymous/mod.rs @@ -0,0 +1,2 @@ +mod v1; +pub use v1::*; \ No newline at end of file diff --git a/src/rust/lqos_bus/src/anonymous/v1.rs b/src/rust/lqos_bus/src/anonymous/v1.rs new file mode 100644 index 00000000..7295b514 --- /dev/null +++ b/src/rust/lqos_bus/src/anonymous/v1.rs @@ -0,0 +1,68 @@ +#[derive(Default, Debug)] + +/// Defines data to be submitted if anonymous usage submission is +/// enabled. This is protocol version 1. +pub struct AnonymousUsageV1 { + /// Total installed RAM (bytes) + pub total_memory: u64, + + /// Total available RAM (bytes) + pub available_memory: u64, + + /// Linux Kernel Version + pub kernel_version: String, + + /// Number of "usable" CPU cores, as used by eBPF. This may not + /// be exactly equal to the number of actual cores. + pub usable_cores: u32, + + /// CPU brand + pub cpu_brand: String, + + /// CPU vendor + pub cpu_vendor: String, + + /// CPU frequency + pub cpu_frequency: u64, + + /// Installed network cards + pub nics: Vec, + + /// SQM setting from the ispConfig.py file + pub sqm: String, + + /// Is Monitor-ony mode enabled? + pub monitor_mode: bool, + + /// Capacity as specified in ispConfig.py + pub total_capacity: (u32, u32), + + /// Generated node capacity from ispConfig.py + pub generated_pdn_capacity: (u32, u32), + + /// Number of shaped devices from ShapedDevices.csv + pub shaped_device_count: usize, + + /// Number of nodes read from network.json + pub net_json_len: usize, +} + + +/// Description of installed NIC (version 1 data) +#[derive(Default, Debug)] +pub struct NicV1 { + /// Description, usually "Ethernet" + pub description: String, + + /// Product name as specified by the driver + pub product: String, + + /// Vendor as specified by the driver + pub vendor: String, + + /// Clock speed, specified by the vendor (may not be accurate) + pub clock: String, + + /// NIC possible capacity (as reported by the driver) + pub capacity: String, +} \ No newline at end of file diff --git a/src/rust/lqos_bus/src/lib.rs b/src/rust/lqos_bus/src/lib.rs index a6a68576..ecd0b468 100644 --- a/src/rust/lqos_bus/src/lib.rs +++ b/src/rust/lqos_bus/src/lib.rs @@ -20,3 +20,6 @@ pub use bus::{ UnixSocketServer, BUS_SOCKET_PATH, }; pub use tc_handle::TcHandle; + +/// Anonymous Usage Statistics Data Types +pub mod anonymous; diff --git a/src/rust/lqosd/src/anonymous_usage/lshw.rs b/src/rust/lqosd/src/anonymous_usage/lshw.rs new file mode 100644 index 00000000..da6750bd --- /dev/null +++ b/src/rust/lqosd/src/anonymous_usage/lshw.rs @@ -0,0 +1,70 @@ +use std::process::Command; + +use lqos_bus::anonymous::NicV1; + +#[derive(Default)] +pub(crate) struct Nic { + pub(crate) description: String, + pub(crate) product: String, + pub(crate) vendor: String, + pub(crate) clock: String, + pub(crate) capacity: String, +} + +#[allow(clippy::from_over_into)] +impl Into for Nic { + fn into(self) -> NicV1 { + NicV1 { + description: self.description, + product: self.product, + vendor: self.vendor, + clock: self.clock, + capacity: self.capacity, + } + } +} + +pub(crate) fn get_nic_info() -> anyhow::Result> { + let mut current_nic = None; + let mut result = Vec::new(); + + let output = Command::new("/bin/lshw") + .args(["-C", "network"]) + .output()?; + let stdout = String::from_utf8(output.stdout)?; + let lines = stdout.split('\n'); + for line in lines { + let trimmed = line.trim(); + + // Starting a new record + if trimmed.starts_with("*-network:") { + if let Some(nic) = current_nic { + result.push(nic); + } + current_nic = Some(Nic::default()); + } + + if let Some(mut nic) = current_nic.as_mut() { + if let Some(d) = trimmed.strip_prefix("description: ") { + nic.description = d.to_string(); + } + if let Some(d) = trimmed.strip_prefix("product: ") { + nic.product = d.to_string(); + } + if let Some(d) = trimmed.strip_prefix("vendor: ") { + nic.vendor = d.to_string(); + } + if let Some(d) = trimmed.strip_prefix("clock: ") { + nic.clock = d.to_string(); + } + if let Some(d) = trimmed.strip_prefix("capacity: ") { + nic.capacity = d.to_string(); + } + } + } + + if let Some(nic) = current_nic { + result.push(nic); + } + Ok(result) +} diff --git a/src/rust/lqosd/src/anonymous_usage/mod.rs b/src/rust/lqosd/src/anonymous_usage/mod.rs new file mode 100644 index 00000000..5ebd5cc9 --- /dev/null +++ b/src/rust/lqosd/src/anonymous_usage/mod.rs @@ -0,0 +1,69 @@ +mod lshw; +use std::time::Duration; +use lqos_bus::anonymous::AnonymousUsageV1; +use lqos_config::{EtcLqos, LibreQoSConfig}; +use lqos_sys::num_possible_cpus; +use sysinfo::{System, SystemExt, CpuExt}; + +use crate::shaped_devices_tracker::{SHAPED_DEVICES, NETWORK_JSON}; + +const SLOW_START_SECS: u64 = 1; +const INTERVAL_SECS: u64 = 60 * 60 * 24; + +pub async fn start_anonymous_usage() { + if let Ok(cfg) = EtcLqos::load() { + if let Some(usage) = cfg.usage_stats { + if usage.send_anonymous { + std::thread::spawn(|| { + std::thread::sleep(Duration::from_secs(SLOW_START_SECS)); + loop { + let _ = anonymous_usage_dump(); + std::thread::sleep(Duration::from_secs(INTERVAL_SECS)); + } + }); + } + } + } +} + +fn anonymous_usage_dump() -> anyhow::Result<()> { + let mut data = AnonymousUsageV1::default(); + let mut sys = System::new_all(); + sys.refresh_all(); + data.total_memory = sys.total_memory(); + data.available_memory = sys.available_memory(); + if let Some(kernel) = sys.kernel_version() { + data.kernel_version = kernel; + } + if let Ok(cores) = num_possible_cpus() { + data.usable_cores = cores; + } + let cpu = sys.cpus().first(); + if let Some(cpu) = cpu { + data.cpu_brand = cpu.brand().to_string(); + data.cpu_vendor = cpu.vendor_id().to_string(); + data.cpu_frequency = cpu.frequency(); + } + for nic in lshw::get_nic_info()? { + data.nics.push(nic.into()); + } + + if let Ok(cfg) = LibreQoSConfig::load() { + data.sqm = cfg.sqm; + data.monitor_mode = cfg.monitor_mode; + data.total_capacity = ( + cfg.total_download_mbps, + cfg.total_upload_mbps, + ); + data.generated_pdn_capacity = ( + cfg.generated_download_mbps, + cfg.generated_upload_mbps, + ); + } + + data.shaped_device_count = SHAPED_DEVICES.read().unwrap().devices.len(); + data.net_json_len = NETWORK_JSON.read().unwrap().nodes.len(); + + println!("{data:#?}"); + Ok(()) +} \ No newline at end of file diff --git a/src/rust/lqosd/src/main.rs b/src/rust/lqosd/src/main.rs index 05615c59..56c9d01d 100644 --- a/src/rust/lqosd/src/main.rs +++ b/src/rust/lqosd/src/main.rs @@ -5,6 +5,7 @@ mod lqos_daht_test; mod program_control; mod shaped_devices_tracker; mod throughput_tracker; +mod anonymous_usage; mod tuning; mod validation; use crate::{ @@ -69,7 +70,8 @@ async fn main() -> Result<()> { join!( spawn_queue_structure_monitor(), shaped_devices_tracker::shaped_devices_watcher(), - shaped_devices_tracker::network_json_watcher() + shaped_devices_tracker::network_json_watcher(), + anonymous_usage::start_anonymous_usage(), ); throughput_tracker::spawn_throughput_monitor(); spawn_queue_monitor();