Claude
Skills
Sign in
Back

syswatch-terminal-diagnostics

Included with Lifetime
$97 forever

SysWatch is a single-host system diagnostics TUI for macOS and Linux with twelve tabs, plain-English insights, and session scrubbing.

General

What this skill does


# SysWatch Terminal Diagnostics

> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.

SysWatch is a single-host system diagnostics TUI written in Rust for macOS and Linux. It consolidates what you'd normally get from `htop`, `iostat`, `vm_stat`, `powermetrics`, `launchctl`, and many other tools into twelve navigable tabs, with plain-English anomaly detection and a session-wide scrubber.

---

## Install

```bash
git clone https://github.com/matthart1983/syswatch.git && cd syswatch
cargo build --release
./target/release/syswatch
```

**Requirements:** Rust 1.75+. No extra system dependencies on Linux. macOS links against system frameworks automatically.

> Crates.io, Homebrew, and pre-built binaries are planned for the v0.1 release.

---

## Running SysWatch

```bash
# Default — 1 Hz refresh
./target/release/syswatch

# 2 Hz refresh (500 ms tick)
syswatch --tick 500

# Boot directly into a specific tab
syswatch --tab procs
syswatch --tab cpu
syswatch --tab insights
```

---

## Key Bindings

```text
1 2 3 4 5 6 7 8 9   Overview / CPU / Mem / Disks / FS / Procs / GPU / Power / Services
0 - +               Net / Timeline / Insights
Tab / Shift-Tab     Cycle tabs forward / backward
↑ / ↓               Select row (Procs, Services tabs)
s                   Cycle sort column (Procs, Services tabs)
← / →               Scrub session backward / forward (Timeline tab)
Home / End          Jump to oldest sample / return to live
p                   Pause collection
q / Ctrl-C          Quit
```

---

## Tabs Reference

| Key | Tab | Data Source / Replaces |
|-----|-----|------------------------|
| `1` | Overview | Dashboard of all subsystems |
| `2` | CPU | `htop` CPU panel, `mpstat`, `top -d` |
| `3` | Memory | `free`, `vm_stat`, `htop` mem panel |
| `4` | Disks | `iostat`, `iotop` (aggregate) |
| `5` | Filesystems | `df -h`, `df -i`, `mount` |
| `6` | Procs | `htop`, `ps auxf`, `pstree` |
| `7` | GPU | `ioreg AGXAccelerator` / `/sys/class/drm` |
| `8` | Power | `pmset`, `ioreg AppleSmartBattery` / `/sys/class/power_supply` |
| `9` | Services | `launchctl list` / `systemctl list-units` |
| `0` | Net | `nettop`, `iftop` |
| `-` | Timeline | Session log + scrubber |
| `+` | Insights | Plain-English anomaly cards |

---

## Architecture Overview

```text
src/
├── main.rs              CLI entry point + arg parsing
├── app.rs               Event loop, tab state, scrub plumbing
├── collect/
│   ├── collector.rs     sysinfo-backed CPU/Mem/Procs + dispatch
│   ├── gpu.rs           system_profiler / sysfs DRM
│   ├── power.rs         ioreg / pmset / sysfs power_supply
│   ├── services.rs      launchctl / systemctl
│   └── ring.rs          Bounded history ring + nth_back for scrubbing
├── insights/            Pure functions over (History, &Snapshot)
├── tabs/                One file per tab — thin renderers over the model
└── ui/
    ├── chrome.rs        Header, tab bar, footer
    ├── palette.rs       Single color source of truth
    └── widgets.rs       block_bar, sparkline, panel helpers
```

**Refresh model:**
- **1 Hz** fast loop: CPU, Memory, Procs, Net, IO
- **5 s** slow loop: Power, Services (subprocess-heavy on macOS)
- CPU budget target: < 0.5% at idle

---

## Extending SysWatch: Adding a Collector

Collectors live in `src/collect/`. Each one populates a typed `Snapshot` struct and is called from `collector.rs`.

```rust
// src/collect/my_subsystem.rs

use crate::collect::ring::Ring;

#[derive(Debug, Clone)]
pub struct MySnapshot {
    pub value: f64,
    pub label: String,
}

pub struct MyCollector {
    history: Ring<MySnapshot>,
}

impl MyCollector {
    pub fn new(capacity: usize) -> Self {
        Self {
            history: Ring::new(capacity),
        }
    }

    pub fn collect(&mut self) -> MySnapshot {
        // Replace with real data collection
        let snap = MySnapshot {
            value: 42.0,
            label: "example".to_string(),
        };
        self.history.push(snap.clone());
        snap
    }

    /// Returns the nth most recent snapshot (for scrubbing).
    pub fn nth_back(&self, n: usize) -> Option<&MySnapshot> {
        self.history.nth_back(n)
    }
}
```

Register it in `collector.rs` and call `collect()` in the fast or slow loop as appropriate.

---

## Extending SysWatch: Adding a Tab Renderer

Tab renderers live in `src/tabs/`. They receive the current (or scrubbed) snapshot and render into a `ratatui` `Frame`.

```rust
// src/tabs/my_tab.rs
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Style},
    widgets::{Block, Borders, Paragraph},
    Frame,
};
use crate::collect::my_subsystem::MySnapshot;

pub fn render(f: &mut Frame, area: Rect, snap: &MySnapshot) {
    let block = Block::default()
        .title(" My Tab ")
        .borders(Borders::ALL)
        .border_style(Style::default().fg(Color::Cyan));

    let text = Paragraph::new(format!(
        "Value: {:.2}\nLabel: {}",
        snap.value, snap.label
    ))
    .block(block);

    f.render_widget(text, area);
}
```

Wire it into `app.rs`'s tab dispatch match arm and add the tab label to `ui/chrome.rs`.

---

## Extending SysWatch: Adding an Insight

Insights are pure functions in `src/insights/`. They take history + the latest snapshot and return zero or more anomaly cards.

```rust
// src/insights/my_insight.rs
use crate::collect::my_subsystem::MySnapshot;

#[derive(Debug, Clone)]
pub struct InsightCard {
    pub title: String,
    pub body: String,
    pub suggested_tab: &'static str,
}

pub fn check(snap: &MySnapshot) -> Vec<InsightCard> {
    let mut cards = vec![];

    if snap.value > 90.0 {
        cards.push(InsightCard {
            title: "High value detected".to_string(),
            body: format!(
                "Current value is {:.1}, which exceeds the 90.0 threshold.",
                snap.value
            ),
            suggested_tab: "my_tab",
        });
    }

    cards
}
```

Register the check in `insights/mod.rs` so it's included in the Insights tab and Overview badge.

---

## Using the Ring Buffer (Session Scrubbing)

The `Ring<T>` type in `src/collect/ring.rs` is the backbone of session scrubbing. Any collector that wraps its history in a `Ring` gets scrubbing for free when the tab renderer calls `nth_back`.

```rust
use crate::collect::ring::Ring;

// Create a ring holding 3600 samples (1 hour at 1 Hz)
let mut ring: Ring<f64> = Ring::new(3600);

// Push a new sample each tick
ring.push(42.0);

// In scrub mode, app.rs tracks `scrub_offset: usize`
// 0 = live, N = N ticks in the past
let scrub_offset = 5; // 5 seconds ago
if let Some(val) = ring.nth_back(scrub_offset) {
    println!("Value 5s ago: {}", val);
}
```

In `app.rs`, the `←`/`→` keys increment/decrement `scrub_offset`, and every tab renderer receives the offset so they all show the same point in time.

---

## Optional Cargo Features

```toml
# Cargo.toml — enable NVIDIA GPU stats (requires NVML / nvidia-smi)
[features]
gpu-nvidia = ["nvml-wrapper"]

# Enable SMART disk health (requires smartctl in PATH)
smart = []
```

Build with a feature:

```bash
cargo build --release --features gpu-nvidia
cargo build --release --features smart
cargo build --release --features gpu-nvidia,smart
```

---

## Platform Notes

### macOS
- GPU utilization and used memory on Apple Silicon: available **without sudo** via `ioreg AGXAccelerator PerformanceStatistics`.
- Fan speeds, per-component power, GPU temperature: require `sudo powermetrics`. SysWatch shows available data and displays a one-line note where sudo is needed — it never prompts.
- Thermal zone temps require IOReport private FFI (deferred).

### Linux
- Thermal zones: available for free via sysfs (`/sys/class/thermal/`).
- GPU data: read from `/sys/class/drm`.
- Power supply: `/sys/class/power_supply`.
- No elevated privileges required for core functionality.

---

## Common Patterns

### Checking live vs. scrubbed state in a tab

```rust
// In app.rs, scrub_offset == 0 means "live"

Related in General