toride is a Rust library for collecting system status, hardware details, and live telemetry. It provides clean Rust APIs that other projects can embed.
It collects CPU, memory, disk, network, GPU, battery, sensor, and process metrics through a composable provider architecture. Snapshots are serializable (JSON/TOML), privacy-aware (three redaction modes), and filterable through presets that control which metrics are gathered.
The app-management layer is released on crates.io as six versioned 0.x crates (the first 0.1.0 upload is performed by the Publish crates workflow, see docs/publishing.md), so embedders can depend on it instead of vendoring:
| Crate | Role |
|---|---|
toride-runner |
Command execution seam: sync Runner, policy knobs, FakeRunner |
toride-fs |
Atomic writes, fd locking, path expansion, permission audits |
toride-registry |
Homebrew/Flathub/AppStream/Repology normalized into one App model |
toride-installer |
Verified direct-download pipeline: resolve, download, digest, extract, install |
toride-mise |
Typed mise CLI integration |
toride-apps |
Execution facade: plan, run, and record installs across all backends |
The heavy features stay off the default sets so a minimal dependency graph is the default: tokio-runner, stream, and serde on toride-runner, http on toride-registry and toride-installer, bootstrap on toride-mise, and everything on toride-apps (tokio, registry-http, direct, mise).
[dependencies]
toride-apps = "0.2"The release process (ordering, version lockstep, changelog conventions) is documented in docs/publishing.md.
use toride::status::TorideStatus;
fn main() {
let status = TorideStatus::collect();
println!("Hostname: {}", status.system.hostname);
println!("CPU: {:.1}%", status.system.cpu_usage.unwrap_or(0.0));
println!("Memory: {} / {} bytes",
status.system.memory.used_bytes,
status.system.memory.total_bytes,
);
println!("Uptime: {:?}s", status.system.uptime_secs);
}Serialize to JSON for logging or transport:
use toride::status::TorideStatus;
let status = TorideStatus::collect();
let json = serde_json::to_string_pretty(&status).unwrap();
println!("{json}");SysProbe is the primary entry point. Use its builder to select a preset and privacy mode:
use toride::status::{SysProbe, Preset, PrivacyMode};
let probe = SysProbe::builder()
.preset(Preset::Diagnostics)
.privacy(PrivacyMode::Diagnostics)
.build();
let snapshot = probe.snapshot();
// Inspect the snapshot
println!("CPU: {:.1}%", snapshot.system.cpu_usage.unwrap_or(0.0));
println!("Memory: {:.1}%", snapshot.system.memory.percentage);
println!("Swap: {:?}", snapshot.system.swap);
println!("Processes: {}", snapshot.system.processes.total_count);
// Top 5 processes by CPU
for proc in snapshot.system.processes.top_by_cpu(5) {
println!(" {}: {:.1}% CPU, {} bytes RSS", proc.name, proc.cpu_usage, proc.memory_bytes);
}Collector tracks rate-based metrics (network throughput, disk I/O, process churn) by comparing consecutive snapshots:
use std::time::Duration;
use toride::status::Collector;
let mut collector = Collector::new(Duration::from_secs(1), Default::default());
// First collect: snapshot only, no delta.
let (status, delta) = collector.collect();
assert!(delta.is_none());
// Wait and collect again.
std::thread::sleep(Duration::from_secs(1));
let (status, delta) = collector.collect();
if let Some(d) = delta {
println!("RX rate: {:.1} B/s", d.bytes_received_rate);
println!("TX rate: {:.1} B/s", d.bytes_transmitted_rate);
if let Some(cpu_delta) = d.cpu_usage_delta {
println!("CPU delta: {cpu_delta:+.1}%");
}
if let Some(ref disk_io) = d.disk_io {
println!("Disk read rate: {:.1} B/s", disk_io.read_bytes_rate);
println!("Disk write rate: {:.1} B/s", disk_io.written_bytes_rate);
}
if let Some(ref proc) = d.process {
println!("Processes: {:+} new, {} exited", proc.new_count, proc.exited_count);
}
}Blocking variant that enforces the interval:
use std::time::Duration;
use toride::status::Collector;
let mut collector = Collector::new(Duration::from_secs(5), Default::default());
loop {
let (status, delta) = collector.collect_after_interval();
// Process status and delta...
}use toride::status::{TorideStatus, Preset};
let status = TorideStatus::collect_with_preset(Preset::TaskManager);
println!("Total processes: {}", status.system.processes.total_count);
println!("\nTop 5 by CPU:");
for proc in status.system.processes.top_by_cpu(5) {
println!(" {:>6} {:<20} CPU: {:5.1}% Mem: {}",
proc.pid, proc.name, proc.cpu_usage, proc.memory_bytes);
}
println!("\nTop 5 by Memory:");
for proc in status.system.processes.top_by_memory(5) {
println!(" {:>6} {:<20} CPU: {:5.1}% Mem: {}",
proc.pid, proc.name, proc.cpu_usage, proc.memory_bytes);
}| Feature | Linux | macOS | Windows |
|---|---|---|---|
| CPU usage | Full | Full | Full |
| Per-core CPU | Full | Full | Full |
| Memory | Full + /proc/meminfo | Full | Full |
| Swap | Full | Full | Full |
| Disk usage | Full + /proc/diskstats | Full | Full |
| Network I/O | Full + /sys/class/net | Full | Full |
| Load average | Full | Full | Not available |
| Uptime | Full | Full | Full |
| Hostname | Full | Full | Full |
| OS info | Full + os-release | Full | Full |
| Sensors | sysinfo + /sys/class/hwmon | sysinfo (SMC) | sysinfo |
| Processes | Full + /proc | Full | Full |
| GPU | nvidia-smi | system_profiler | Not available |
| Battery | starship-battery | starship-battery | starship-battery |
| DMI/SMBIOS | /sys/class/dmi/id | static (Apple) | Not read |
| CPU topology | /proc/cpuinfo + sysfs | sysctl | Not read |
| Cgroups | /proc/self/cgroup | N/A | N/A |
GPU support: NVIDIA GPUs report through one nvidia-smi subprocess per collect (identity, VRAM, utilization, temperature, power draw, clocks, encoder/decoder utilization). NVML is not linked. macOS reports identity and VRAM via system_profiler. AMD and Intel GPUs are not probed, and Windows reports no GPUs.
toride-status has one optional feature: battery (on by default, backed by starship-battery). sysinfo is the only metrics backend; there are no backend-alternative features to enable.
The PrivacyMode enum controls which sensitive fields are redacted in status output. Redaction is applied before data is stored, not at display time.
| Mode | Hostname | MAC / Serial | Command-line | Username | UUID / Asset Tag |
|---|---|---|---|---|---|
Safe |
[redacted] |
[redacted] |
[redacted] |
[redacted] |
[redacted] |
Diagnostics |
shown | [redacted] |
name only | [redacted] |
[redacted] |
Full |
shown | shown | shown | shown | shown |
Safe is the default. Callers that forget to configure privacy still get safe output.
use toride::status::{TorideStatus, PrivacyMode};
// Safe mode: all sensitive fields redacted.
let status = TorideStatus::collect_with_privacy(PrivacyMode::Safe);
assert_eq!(status.system.hostname, "[redacted]");
// Diagnostics mode: hostnames visible, identifiers redacted.
let status = TorideStatus::collect_with_privacy(PrivacyMode::Diagnostics);
// Combine with presets.
let status = TorideStatus::collect_with_options(
Preset::Minimal,
PrivacyMode::Safe,
);Redaction applies to: hostname, MAC addresses, serial numbers, UUIDs, asset tags, disk serial numbers, command-line arguments, and usernames.
The provider system abstracts data sources behind composable traits. The default implementation (SysinfoProvider) uses the sysinfo crate. You can implement custom providers for mock testing or alternative data sources.
StatusProvider (composite, blanket-implemented)
+-- CpuProvider -> cpu_usage(), cpu_cores(), physical_cores()
+-- MemoryProvider -> memory(), swap()
+-- DiskProvider -> root_disk(), all_disks()
+-- NetworkProvider -> aggregate(), interfaces()
+-- OsProvider -> os_info(), hostname(), uptime(), boot_time(), load_average()
+-- ProcessProvider -> processes()
+-- GpuProvider -> gpus()
+-- BatteryProvider -> battery()
+-- SensorProvider -> sensors()
Implementing a custom provider:
use toride::status::provider::*;
use toride::status::system::*;
use toride::status::error::StatusResult;
struct MyMockProvider;
impl CpuProvider for MyMockProvider {
fn cpu_usage(&mut self) -> StatusResult<Option<f64>> {
Ok(Some(42.0))
}
fn cpu_cores(&mut self) -> StatusResult<Vec<CpuCore>> {
Ok(vec![])
}
fn physical_cores(&self) -> StatusResult<Option<usize>> {
Ok(Some(8))
}
}
// Implement remaining traits...
// StatusProvider is automatically implemented via blanket impl:
fn use_provider<P: StatusProvider>(provider: &mut P) {
let cpu = provider.cpu_usage().unwrap();
let mem = provider.memory().unwrap();
}The concrete SysinfoProvider struct wraps sysinfo::System and implements all nine traits:
use toride::status::system::SysinfoProvider;
use toride::status::provider::*;
let mut provider = SysinfoProvider::new();
let cpu = provider.cpu_usage().unwrap();
let mem = provider.memory().unwrap();
let procs = provider.processes().unwrap();Metrics that are unavailable on the current platform return None, empty vectors, or zero-length snapshots. The library never fakes zeros for unavailable data.
| Return type | Unavailable value | Available zero case |
|---|---|---|
Option<f64> |
None |
Some(0.0) |
Option<T> |
None |
Some(T { .. }) |
Vec<T> |
vec![] (empty) |
vec![] (empty, same) |
String |
"" (empty) |
"0" (string "0") |
Use Capabilities::detect() to check what is available before collecting:
use toride::status::Capabilities;
let caps = Capabilities::detect();
if caps.system.load_average {
// Load average is available on this platform.
}
if caps.ssh.mux_check {
// ssh binary found on PATH.
}Capabilities are detected using compile-time cfg! macros for platform features and runtime binary detection for external tools (ssh, ssh-add).
| Vendor | Method | Identity | VRAM | Utilization | Temperature | Power | Clock |
|---|---|---|---|---|---|---|---|
| NVIDIA | nvidia-smi | Yes | Yes | Yes | Yes | Yes | Yes |
| Apple | system_profiler | Yes | Yes | No | No | No | No |
| AMD | not probed | No | No | No | No | No | No |
| Intel | not probed | No | No | No | No | No | No |
NVIDIA data comes from one nvidia-smi --query-gpu=... subprocess per collect on Linux. NVML is not linked, and there is no GPU feature flag. Windows reports no GPUs.
Apple Silicon GPUs report through system_profiler but macOS does not expose utilization, temperature, or power draw through public APIs.
Linux has the deepest support. Beyond sysinfo, the crate reads the kernel's own files directly:
/proc/meminfofor cached and buffer memory./proc/diskstatsfor disk I/O counters (base devices only; partitions are not double-counted)./sys/class/netfor link state, speed, duplex, and drop counters./sys/class/hwmonfor fan and voltage readings./proc/cpuinfoand/sys/devices/system/cpufor topology and frequencies./sys/class/dmi/idfor the hardware inventory./proc/<pid>for per-process I/O, cwd, fd, and thread counts.
Virtualization detection (always available on Linux): Docker, LXC, containerd, Kubernetes, WSL, and VMs (VirtualBox, VMware, KVM, QEMU, Hyper-V).
- Battery status via
starship-battery. - GPU detection via
system_profiler SPDisplaysDataType. - Load average available (Unix).
- No load average (returns
None). - No GPU detection.
- Battery via
starship-battery. - Swap is reported as
Nonewhen not configured. - Sensors via sysinfo.
The library detects container and virtualization environments automatically. Detection results are available in status.system.virtualization:
use toride::status::TorideStatus;
let status = TorideStatus::collect();
let virt = &status.system.virtualization;
if virt.in_docker {
println!("Running in Docker");
}
if virt.in_kubernetes {
println!("Running in Kubernetes");
}
if virt.in_wsl {
println!("Running in WSL");
}
// Cgroup limit fields exist but stay None in this release.
if let Some(quota) = virt.cpu_quota {
println!("CPU quota: {quota}");
}
if let Some(limit) = virt.memory_limit_bytes {
println!("Memory limit: {limit} bytes");
}| Detection | Method |
|---|---|
| Docker | /.dockerenv exists |
| LXC | /proc/self/cgroup contains /lxc/ |
| containerd | /proc/self/cgroup contains containerd |
| Kubernetes | /proc/self/cgroup contains kubepods |
| WSL | /proc/version contains microsoft/wsl |
| VM | /sys/class/dmi/id/product_name patterns |
| Cgroup version | /proc/self/cgroup prefix (0::/ = v2) |
| Podman | Container runtime detection |
The limit fields on VirtualizationSnapshot (cpu_quota, memory_limit_bytes, swap_limit, blkio_limit, cpuset_cpus) stay None in this release. The crate reads only the detection flags above and the cgroup version from /proc/self/cgroup.
A single SystemStatus::collect() call includes a mandatory sleep (sysinfo::MINIMUM_CPU_UPDATE_INTERVAL, typically 200-300ms) to allow sysinfo to measure CPU usage accurately. The total time for a full snapshot is typically 250-400ms depending on the number of processes.
Collector compares consecutive snapshots to compute rates. The default interval is 1 second. Very short intervals (< 100ms) produce noisy rate data. Very long intervals (> 60s) smooth out bursts.
use std::time::Duration;
use toride::status::Collector;
// Good for dashboards: 2-second interval.
let collector = Collector::new(Duration::from_secs(2), Default::default());
// Good for logging: 30-second interval.
let collector = Collector::new(Duration::from_secs(30), Default::default());Process enumeration is the most expensive operation. On a system with 500+ processes, processes() can take 50-100ms. Use the Minimal preset to skip process scanning:
use toride::status::{TorideStatus, Preset};
// Fast: no process scan, no per-core CPU, no sensors.
let status = TorideStatus::collect_with_preset(Preset::Minimal);| Preset | Relative Overhead | Includes Processes | Includes Sensors |
|---|---|---|---|
| Minimal | Low | No | No |
| TaskManager | Medium | Yes | Yes |
| Diagnostics | High | Yes | Yes |
| ServerMonitoring | Low-Medium | No | No |
| PrivacySafeBugReport | Low | No | No |
| HardwareInventory | Medium | No | Yes |
DoctorReport runs health checks across system, daemon, and SSH subsystems. Each check reports Pass, Warn, or Fail status.
use toride::status::DoctorReport;
let report = DoctorReport::check();
println!("{report}");
if !report.all_passed() {
let (pass, warn, fail) = report.summary();
eprintln!("Issues: {warn} warnings, {fail} failures");
}Check categories:
- system: hostname, CPU, memory, disks, OS info, GPU, battery, sensors, CPU sample quality, memory sanity, disk duplicates, virtualization, disk I/O
- daemon: PID liveness, stale socket detection
- ssh: ssh binary on PATH, ssh-add binary, config validation, agent status
Run checks against pre-collected snapshots:
use toride::status::{DoctorReport, TorideStatus};
let status = TorideStatus::collect();
let report = DoctorReport::check_with(&status.system, &status.daemon, &status.ssh);Presets control which metrics are collected, allowing you to optimize for your specific use case.
| Preset | Description |
|---|---|
Minimal |
CPU, memory, disk, network totals, uptime |
TaskManager |
Per-core CPU, all disks, sensors, processes |
Diagnostics |
Everything (default) |
ServerMonitoring |
CPU, memory, network interfaces, swap, disk I/O |
PrivacySafeBugReport |
OS info, CPU family, memory total, GPU model only |
HardwareInventory |
Static info, all disks, sensors, GPU, battery |
use toride::status::{TorideStatus, Preset};
// Lightweight monitoring
let status = TorideStatus::collect_with_preset(Preset::Minimal);
// Interactive task manager
let status = TorideStatus::collect_with_preset(Preset::TaskManager);
// Server monitoring with network interfaces
let status = TorideStatus::collect_with_preset(Preset::ServerMonitoring);
// Safe for sharing in bug reports
let status = TorideStatus::collect_with_preset(Preset::PrivacySafeBugReport);Capabilities::detect() reports which metrics are available on the current platform. Use it to build adaptive UIs or skip unavailable features.
use toride::status::Capabilities;
let caps = Capabilities::detect();
// System capabilities
if caps.system.load_average { /* show load average */ }
if caps.system.sensors { /* show temperature panel */ }
if caps.system.swap { /* show swap usage */ }
// Daemon capabilities
if caps.daemon.pid_check { /* check daemon liveness */ }
if caps.daemon.stale_socket_detection { /* check socket health */ }
// SSH capabilities (depends on ssh/ssh-add on PATH)
if caps.ssh.mux_check { /* check SSH mux master */ }
if caps.ssh.agent_check { /* check SSH agent */ }The units module provides wrappers that prevent unit confusion:
use toride::status::units::{Bytes, Hertz, Celsius, Watts, Volts, Rpm};
let mem = Bytes(1073741824);
println!("{mem}"); // "1.00 GiB"
println!("{}", mem.human_readable()); // "1.00 GiB"
let freq = Hertz(3200000000);
println!("{freq}"); // "3.20 GHz"
println!("{:.0} MHz", freq.as_mhz()); // "3200 MHz"
let temp = Celsius(55.5);
println!("{temp}"); // "55.5C"
println!("{:.1}F", temp.to_fahrenheit()); // "131.9F"
let power = Watts(75.5);
println!("{power}"); // "75.5 W"MIT: every published crate ships a LICENSE-MIT.