From 6b51817302ce2ff46049996e3516c4dde3bdbea5 Mon Sep 17 00:00:00 2001 From: Jack Mechem Date: Thu, 26 Mar 2026 19:56:57 -0700 Subject: [PATCH] Stats route --- Cargo.lock | 363 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +- src/main.rs | 127 ++++++++++++++++-- src/models.rs | 59 ++++++++ 4 files changed, 543 insertions(+), 12 deletions(-) create mode 100644 src/models.rs diff --git a/Cargo.lock b/Cargo.lock index da0b101..2a2330b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,27 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "axum" version = "0.8.8" @@ -60,12 +75,75 @@ dependencies = [ "tracing", ] +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -189,12 +267,46 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.183" @@ -236,6 +348,43 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -278,6 +427,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.23" @@ -291,6 +446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -354,9 +510,29 @@ name = "server-stats-rust" version = "0.1.0" dependencies = [ "axum", + "chrono", + "serde", + "serde_json", + "sysinfo", "tokio", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "slab" version = "0.4.12" @@ -396,15 +572,31 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "sysinfo" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + [[package]] name = "tokio" version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ + "bytes", "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys", @@ -481,12 +673,174 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -496,6 +850,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index ad47b44..35a238c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,8 @@ edition = "2024" [dependencies] axum = "0.8.8" -tokio = { version = "1.50.0", features = ["macros", "rt-multi-thread"] } +chrono = "0.4.44" +serde = { version = "1", features = ["derive"] } +serde_json = "1.0.149" +sysinfo = "0.38.4" +tokio = { version = "1.50.0", features = ["macros", "rt-multi-thread", "process"] } diff --git a/src/main.rs b/src/main.rs index 36774c9..b08adfa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,132 @@ -use axum::{ - body::Body, - routing::get, - response::Json, - Router, -}; +use axum::{Router, body::Body, response::Json, routing::get}; use serde_json::{Value, json}; +use std::collections::HashMap; +use sysinfo::{Components, Disks, Networks, System}; +use tokio::process::Command; +mod models; +use models::SystemStats; + +use self::models::MemoryStats; #[tokio::main] async fn main() { - let app = Router::new() .route("/", get(root)) .route("/stats", get(get_stats)); let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await.unwrap(); - axum::serve(listener, app).await.unwrap(); - + axum::serve(listener, app).await.unwrap(); } // which calls one of these handlers async fn root() {} -async fn get_stats() -> Json { - Json(json!({ "data": 67 })) +async fn get_stats() -> Json { + let mut sys = System::new_all(); + sys.refresh_all(); + + // Memory (MB) + let memory = models::MemoryStats { + total: sys.total_memory() / 1_000_000, + used: sys.used_memory() / 1_000_000, + available: sys.available_memory() / 1_000_000, + percent: (sys.used_memory() as f64 / sys.total_memory() as f64 * 100.0) as u64, + }; + + // CPU + let cpu = models::CpuStats { + percent: sys.global_cpu_usage(), + model: sys.cpus()[0].brand().to_string(), + cores: sys.cpus().len(), + }; + + // Disk (MB) + let disks = Disks::new_with_refreshed_list(); + let total_disk: u64 = disks.iter().map(|d: &sysinfo::Disk| d.total_space()).sum(); + let available_disk: u64 = disks + .iter() + .map(|d: &sysinfo::Disk| d.available_space()) + .sum(); + let used_disk = total_disk - available_disk; + let disk = models::DiskStats { + total: total_disk / 1_000_000, + used: used_disk / 1_000_000, + available: available_disk / 1_000_000, + percent: (used_disk as f64 / total_disk as f64 * 100.0) as u64, + }; + + // Uptime + let seconds = System::uptime(); + let uptime = models::UptimeStats { + seconds, + days: seconds / 86400, + hours: (seconds % 86400) / 3600, + minutes: (seconds % 3600) / 60, + }; + + // Network (bytes, same as original) + let networks = Networks::new_with_refreshed_list(); + let network: HashMap = networks + .iter() + .map(|(name, data): (&String, &sysinfo::NetworkData)| { + ( + name.clone(), + models::NetworkStats { + rx: data.total_received(), + tx: data.total_transmitted(), + }, + ) + }) + .collect(); + + // Load average + let load = System::load_average(); + let load_avg = models::LoadAvgStats { + one: load.one, + five: load.five, + fifteen: load.fifteen, + }; + + // Temperature + let components = Components::new_with_refreshed_list(); + let temperature: f32 = components + .iter() + .next() + .and_then(|c: &sysinfo::Component| c.temperature()) + .unwrap_or(0.0f32); + + // Services — check a list of known services via systemctl + let service_names = vec![ + "syncthing", + "caddy", + "sshd", + "cloudflare-dyndns.timer", + "cloudflare-dyndns", + "docker", + ]; + let mut services: HashMap = HashMap::new(); + for name in service_names { + let output = Command::new("systemctl") + .args(["is-active", name]) + .output() + .await; + let status = match output { + Ok(out) => String::from_utf8_lossy(&out.stdout).trim().to_string(), + Err(_) => "unknown".to_string(), + }; + services.insert(name.to_string(), status); + } + + Json(models::SystemStats { + timestamp: chrono::Utc::now().to_rfc3339(), + memory, + cpu, + disk, + uptime, + network, + load_avg, + temperature, + services, + }) } diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..fea5e9f --- /dev/null +++ b/src/models.rs @@ -0,0 +1,59 @@ +use serde::Serialize; +use std::collections::HashMap; + +#[derive(Serialize)] +pub struct SystemStats { + pub timestamp: String, + pub memory: MemoryStats, + pub cpu: CpuStats, + pub disk: DiskStats, + pub uptime: UptimeStats, + pub network: HashMap, + pub services: HashMap, + pub load_avg: LoadAvgStats, + pub temperature: f32, +} + +#[derive(Serialize)] +pub struct MemoryStats { + pub total: u64, + pub used: u64, + pub available: u64, + pub percent: u64, +} + +#[derive(Serialize)] +pub struct CpuStats { + pub percent: f32, + pub model: String, + pub cores: usize, +} + +#[derive(Serialize)] +pub struct DiskStats { + pub total: u64, + pub used: u64, + pub available: u64, + pub percent: u64, +} + +#[derive(Serialize)] +pub struct UptimeStats { + pub seconds: u64, + pub days: u64, + pub hours: u64, + pub minutes: u64, +} + +#[derive(Serialize)] +pub struct NetworkStats { + pub rx: u64, + pub tx: u64, +} + +#[derive(Serialize)] +pub struct LoadAvgStats { + pub one: f64, + pub five: f64, + pub fifteen: f64, +}