diff --git a/Cargo.lock b/Cargo.lock index 65376ae..3d20bdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1142,7 +1142,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2245,7 +2245,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -2581,7 +2581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2804,6 +2804,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2878,6 +2887,25 @@ dependencies = [ "syn", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", +] + +[[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 = "oid-registry" version = "0.8.1" @@ -2971,7 +2999,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -3682,6 +3710,7 @@ dependencies = [ "rohas-telemetry", "serde", "serde_json", + "sysinfo", "thiserror 2.0.17", "tokio", "tokio-test", @@ -4371,6 +4400,20 @@ dependencies = [ "syn", ] +[[package]] +name = "sysinfo" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + [[package]] name = "target-lexicon" version = "0.13.3" @@ -5190,6 +5233,41 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -5198,9 +5276,20 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", ] [[package]] @@ -5225,19 +5314,53 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", ] [[package]] @@ -5246,7 +5369,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5282,7 +5405,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5307,7 +5430,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -5318,6 +5441,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index b242c9c..a7660d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,9 @@ notify-debouncer-full = "0.6.0" uuid = { version = "1.6", features = ["v4", "serde"] } base64 = "0.22" +# System information +sysinfo = "0.37.2" + # Testing mockall = "0.14.0" proptest = "1.4" diff --git a/crates/rohas-engine/Cargo.toml b/crates/rohas-engine/Cargo.toml index 2f19759..4bd647e 100644 --- a/crates/rohas-engine/Cargo.toml +++ b/crates/rohas-engine/Cargo.toml @@ -34,6 +34,7 @@ futures-util = "0.3" regex = "1.11" base64 = { workspace = true } async-trait = "0.1" +sysinfo = { workspace = true } [dev-dependencies] tokio-test = "0.4" diff --git a/crates/rohas-engine/src/workbench.rs b/crates/rohas-engine/src/workbench.rs index e94841e..5da2e91 100644 --- a/crates/rohas-engine/src/workbench.rs +++ b/crates/rohas-engine/src/workbench.rs @@ -11,6 +11,9 @@ use serde_json::json; use std::collections::HashMap; use std::fs; use std::path::Path as StdPath; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; +use sysinfo::System; use tracing::error; #[derive(Serialize, Deserialize)] @@ -184,6 +187,7 @@ pub fn workbench_routes() -> Router { .route("/api/workbench/endpoints", get(get_endpoints)) .route("/api/workbench/types/{type_name}", get(get_type_schema)) .route("/api/workbench/events/{name}/trigger", post(trigger_event)) + .route("/api/workbench/system-metrics", get(get_system_metrics)) } async fn get_snapshot(State(state): State) -> Result { @@ -1111,6 +1115,69 @@ async fn trigger_event( .into_response()) } +#[derive(Serialize, Deserialize)] +pub struct SystemMetrics { + pub cpu: f32, + pub ram: RamMetrics, +} + +#[derive(Serialize, Deserialize)] +pub struct RamMetrics { + pub used_mb: u64, + pub total_mb: u64, + pub percentage: f32, +} + +static SYSTEM: std::sync::OnceLock>> = std::sync::OnceLock::new(); +static LAST_UPDATE: std::sync::OnceLock>> = std::sync::OnceLock::new(); + +fn get_system() -> Arc> { + SYSTEM.get_or_init(|| Arc::new(Mutex::new(System::new_all()))).clone() +} + +fn get_last_update() -> Arc> { + LAST_UPDATE.get_or_init(|| Arc::new(Mutex::new(Instant::now()))).clone() +} + +async fn get_system_metrics() -> Result { + let system = get_system(); + let mut system_guard = system.lock().map_err(|e| { + WorkbenchError::Internal(format!("Failed to acquire system lock: {}", e)) + })?; + + let last_update = get_last_update(); + let mut last_update_guard = last_update.lock().map_err(|e| { + WorkbenchError::Internal(format!("Failed to acquire update lock: {}", e)) + })?; + + if last_update_guard.elapsed() > Duration::from_secs(1) { + system_guard.refresh_all(); + *last_update_guard = Instant::now(); + } + let cpu_usage = system_guard.global_cpu_usage(); + + let total_memory = system_guard.total_memory(); + let used_memory = system_guard.used_memory(); + let total_memory_mb = total_memory / (1024 * 1024); + let used_memory_mb = used_memory / (1024 * 1024); + let memory_percentage = if total_memory > 0 { + (used_memory as f32 / total_memory as f32) * 100.0 + } else { + 0.0 + }; + + let metrics = SystemMetrics { + cpu: cpu_usage, + ram: RamMetrics { + used_mb: used_memory_mb, + total_mb: total_memory_mb, + percentage: memory_percentage, + }, + }; + + Ok(Json(metrics).into_response()) +} + #[derive(Debug)] pub enum WorkbenchError { NotFound(String), diff --git a/workbench/src/components/workbench/system-metrics.tsx b/workbench/src/components/workbench/system-metrics.tsx index b9a953c..426d919 100644 --- a/workbench/src/components/workbench/system-metrics.tsx +++ b/workbench/src/components/workbench/system-metrics.tsx @@ -19,6 +19,7 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { fetchSystemMetrics } from "@/lib/workbench-data"; interface DataPoint { time: string; @@ -35,57 +36,46 @@ export function SystemMetrics() { const timeRef = useRef(0); useEffect(() => { - const updateMetrics = () => { - // Get memory info if available (Chrome/Edge only) - const memory = (performance as any).memory; - let ramUsed = 0; - let ramTotal = 0; - - if (memory) { - // Convert bytes to MB - ramUsed = Math.round(memory.usedJSHeapSize / 1024 / 1024); - ramTotal = Math.round(memory.jsHeapSizeLimit / 1024 / 1024); - } else { - // Fallback: simulate realistic values with some variation - const baseRam = 3000; - ramUsed = baseRam + Math.floor(Math.random() * 2000) - 1000; // 2-4 GB with variation - ramTotal = 16384; // 16 GB - } - - // Simulate CPU usage (in a real app, this would come from a server API) - const cpuUsage = Math.floor(Math.random() * 40) + 20; // 20-60% - - const ramPercentage = Math.round((ramUsed / ramTotal) * 100); - - setCurrentRam({ - used: ramUsed, - total: ramTotal, - percentage: ramPercentage, - }); - setCurrentCpu(cpuUsage); - - // Add new data point - timeRef.current += 2; - const minutes = Math.floor(timeRef.current / 60); - const seconds = timeRef.current % 60; - const timeLabel = `${minutes}:${seconds.toString().padStart(2, "0")}`; - - setData((prev) => { - const newData = [ - ...prev, - { - time: timeLabel, - ram: ramPercentage, - cpu: cpuUsage, - }, - ]; - - // Keep only the last MAX_DATA_POINTS - if (newData.length > MAX_DATA_POINTS) { - return newData.slice(-MAX_DATA_POINTS); + const updateMetrics = async () => { + try { + const metrics = await fetchSystemMetrics(); + + if (metrics) { + const ramPercentage = Math.round(metrics.ram.percentage); + + setCurrentRam({ + used: metrics.ram.used_mb, + total: metrics.ram.total_mb, + percentage: ramPercentage, + }); + setCurrentCpu(Math.round(metrics.cpu)); + + // Add new data point + timeRef.current += 2; + const minutes = Math.floor(timeRef.current / 60); + const seconds = timeRef.current % 60; + const timeLabel = `${minutes}:${seconds.toString().padStart(2, "0")}`; + + setData((prev) => { + const newData = [ + ...prev, + { + time: timeLabel, + ram: ramPercentage, + cpu: Math.round(metrics.cpu), + }, + ]; + + // Keep only the last MAX_DATA_POINTS + if (newData.length > MAX_DATA_POINTS) { + return newData.slice(-MAX_DATA_POINTS); + } + return newData; + }); } - return newData; - }); + } catch (error) { + console.error("Failed to update system metrics:", error); + } }; // Initialize with first data point diff --git a/workbench/src/lib/workbench-data.ts b/workbench/src/lib/workbench-data.ts index c48a07a..f41f8a0 100644 --- a/workbench/src/lib/workbench-data.ts +++ b/workbench/src/lib/workbench-data.ts @@ -361,3 +361,22 @@ export async function fetchTypeSchema(typeName: string | null): Promise { + try { + const metrics = await apiRequest("/api/workbench/system-metrics"); + return metrics; + } catch (error) { + console.error("Failed to fetch system metrics:", error); + return null; + } +}