syswatch-terminal-diagnostics

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SysWatch Terminal Diagnostics

SysWatch 终端诊断工具

Skill by 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.

来自 ara.so 的技能 — 2026每日技能合集。
SysWatch 是一款基于 Rust 开发、面向 macOS 和 Linux 的单主机系统诊断 TUI 工具。它将
htop
iostat
vm_stat
powermetrics
launchctl
等多款工具的功能整合到12个可导航的标签页中,提供通俗易懂的异常检测功能以及全局会话回溯功能。

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.

bash
git clone https://github.com/matthart1983/syswatch.git && cd syswatch
cargo build --release
./target/release/syswatch
要求: Rust 1.75 及以上版本。Linux 无需额外系统依赖;macOS 会自动链接系统框架。
Crates.io、Homebrew 以及预编译二进制包计划在 v0.1 版本中推出。

Running SysWatch

运行 SysWatch

bash
undefined
bash
undefined

Default — 1 Hz refresh

默认配置 — 1 Hz 刷新频率

./target/release/syswatch
./target/release/syswatch

2 Hz refresh (500 ms tick)

2 Hz 刷新频率(500ms 一次更新)

syswatch --tick 500
syswatch --tick 500

Boot directly into a specific tab

直接启动到指定标签页

syswatch --tab procs syswatch --tab cpu syswatch --tab insights

---
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

text
1 2 3 4 5 6 7 8 9   概览 / CPU / 内存 / 磁盘 / 文件系统 / 进程 / GPU / 电源 / 服务
0 - +               网络 / 时间线 / 分析见解
Tab / Shift-Tab     向前/向后切换标签页
↑ / ↓               选择行(进程、服务标签页)
s                   切换排序列(进程、服务标签页)
← / →               向前/向后回溯会话(时间线标签页)
Home / End          跳转到最早记录 / 返回实时状态
p                   暂停数据收集
q / Ctrl-C          退出程序

Tabs Reference

标签页参考

KeyTabData Source / Replaces
1
OverviewDashboard 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
-
TimelineSession log + scrubber
+
InsightsPlain-English anomaly cards

按键标签页数据源 / 替代工具
1
概览所有子系统的仪表盘
2
CPU
htop
CPU面板、
mpstat
top -d
3
内存
free
vm_stat
htop
内存面板
4
磁盘
iostat
iotop
(聚合数据)
5
文件系统
df -h
df -i
mount
6
进程
htop
ps auxf
pstree
7
GPU
ioreg AGXAccelerator
/
/sys/class/drm
8
电源
pmset
ioreg AppleSmartBattery
/
/sys/class/power_supply
9
服务
launchctl list
/
systemctl list-units
0
网络
nettop
iftop
-
时间线会话日志 + 回溯器
+
分析见解通俗易懂的异常提示卡片

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

text
src/
├── main.rs              CLI入口 + 参数解析
├── app.rs               事件循环、标签页状态、回溯机制
├── collect/
│   ├── collector.rs     基于sysinfo的CPU/内存/进程数据收集 + 调度
│   ├── gpu.rs           system_profiler / sysfs DRM
│   ├── power.rs         ioreg / pmset / sysfs power_supply
│   ├── services.rs      launchctl / systemctl
│   └── ring.rs          有限容量历史环形缓冲区 + 回溯读取nth_back
├── insights/            基于(History, &Snapshot)的纯函数逻辑
├── tabs/                每个标签页对应一个文件 — 基于数据模型的轻量渲染器
└── ui/
    ├── chrome.rs        页眉、标签栏、页脚
    ├── palette.rs       统一颜色配置源
    └── widgets.rs       块进度条、火花图、面板辅助工具
刷新模型:
  • 1 Hz 快速循环:CPU、内存、进程、网络、IO
  • 5秒 慢速循环:电源、服务(macOS下依赖子进程,开销较大)
  • CPU占用目标:空闲时 < 0.5%

Extending SysWatch: Adding a Collector

扩展 SysWatch:添加收集器

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.

收集器位于
src/collect/
目录下。每个收集器会填充一个类型化的
Snapshot
结构体,并由
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 {
        // 替换为真实的数据收集逻辑
        let snap = MySnapshot {
            value: 42.0,
            label: "example".to_string(),
        };
        self.history.push(snap.clone());
        snap
    }

    /// 返回第n个最近的快照(用于回溯)。
    pub fn nth_back(&self, n: usize) -> Option<&MySnapshot> {
        self.history.nth_back(n)
    }
}
collector.rs
中注册该收集器,并根据情况在快速或慢速循环中调用
collect()

Extending SysWatch: Adding a Tab Renderer

扩展 SysWatch:添加标签页渲染器

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
.

标签页渲染器位于
src/tabs/
目录下。它们接收当前(或回溯的)快照,并渲染到
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);
}
将其接入
app.rs
的标签页调度匹配分支,并在
ui/chrome.rs
中添加标签页名称。

Extending SysWatch: Adding an Insight

扩展 SysWatch:添加分析见解

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.

分析见解是
src/insights/
目录下的纯函数。它们接收历史数据和最新快照,返回零或多个异常提示卡片。
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
}
insights/mod.rs
中注册该检测函数,使其包含在分析见解标签页和概览徽章中。

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.

src/collect/ring.rs
中的
Ring<T>
类型是会话回溯的核心。任何将历史数据包装在
Ring
中的收集器,当标签页渲染器调用
nth_back
时,都能自动获得回溯功能。
rust
use crate::collect::ring::Ring;

// 创建一个可存储3600个样本的环形缓冲区(1 Hz频率下存储1小时数据)
let mut ring: Ring<f64> = Ring::new(3600);

// 每次更新时推入新样本
ring.push(42.0);

// 在回溯模式下,app.rs 跟踪 `scrub_offset: usize`
// 0 = 实时状态,N = 过去N次更新的状态
let scrub_offset = 5; // 5秒前的状态
if let Some(val) = ring.nth_back(scrub_offset) {
    println!("Value 5s ago: {}", val);
}
app.rs
中,
/
键用于增减
scrub_offset
,所有标签页渲染器都会接收该偏移量,因此它们会显示同一时间点的数据。

Optional Cargo Features

可选 Cargo 特性

toml
undefined
toml
undefined

Cargo.toml — enable NVIDIA GPU stats (requires NVML / nvidia-smi)

Cargo.toml — 启用NVIDIA GPU统计(需要NVML / nvidia-smi)

[features] gpu-nvidia = ["nvml-wrapper"]
[features] gpu-nvidia = ["nvml-wrapper"]

Enable SMART disk health (requires smartctl in PATH)

启用SMART磁盘健康检测(需要PATH中存在smartctl)

smart = []

Build with a feature:

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

smart = []

启用特性构建:

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

Platform Notes

平台说明

macOS

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).
  • Apple Silicon 上的GPU利用率和已用内存:无需sudo即可通过
    ioreg AGXAccelerator PerformanceStatistics
    获取。
  • 风扇转速、组件功耗、GPU温度:需要
    sudo powermetrics
    。SysWatch会显示可用数据,并在需要sudo的位置显示一行提示,但绝不会主动请求权限。
  • 热区温度:需要IOReport私有FFI(暂未实现)。

Linux

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.

  • 热区:可通过sysfs免费获取(
    /sys/class/thermal/
    )。
  • GPU数据:从
    /sys/class/drm
    读取。
  • 电源供应:
    /sys/class/power_supply
  • 核心功能无需提升权限。

Common Patterns

常见模式

Checking live vs. scrubbed state in a tab

在标签页中检查实时/回溯状态

rust
// In app.rs, scrub_offset == 0 means "live"
pub struct App {
    pub scrub_offset: usize,
    // ...
}

// In a tab renderer:
pub fn render(f: &mut Frame, area: Rect, app: &App) {
    let snap = if app.scrub_offset == 0 {
        app.collector.latest()
    } else {
        app.collector.nth_back(app.scrub_offset)
            .unwrap_or_else(|| app.collector.latest())
    };
    // render snap...
}
rust
// 在app.rs中,scrub_offset == 0 表示"实时状态"
pub struct App {
    pub scrub_offset: usize,
    // ...
}

// 在标签页渲染器中:
pub fn render(f: &mut Frame, area: Rect, app: &App) {
    let snap = if app.scrub_offset == 0 {
        app.collector.latest()
    } else {
        app.collector.nth_back(app.scrub_offset)
            .unwrap_or_else(|| app.collector.latest())
    };
    // 渲染snap...
}

Pausing collection

暂停数据收集

The
p
key sets
app.paused = true
. The event loop skips
collect()
calls but the UI still redraws on keypress, so scrubbing works while paused.
rust
if !app.paused {
    app.collector.collect();
}

按下
p
键会设置
app.paused = true
。事件循环会跳过
collect()
调用,但UI仍会在按键时重绘,因此暂停时仍可使用回溯功能。
rust
if !app.paused {
    app.collector.collect();
}

Troubleshooting

故障排除

SymptomFix
cargo build
fails on macOS with framework linker errors
Ensure Xcode Command Line Tools are installed:
xcode-select --install
GPU tab shows "no data" on Apple SiliconNormal without sudo for temp/power; util + memory should appear via ioreg
Services tab is slow to updateExpected — launchctl/systemctl are subprocess-heavy; they run on the 5 s slow loop
Timeline scrubbing shows stale dataThe ring capacity is set at startup; scrubbing is limited to collected history
Net tab shows no interfacesMay need to run as a user with access to network stats; check
netwatch-sdk
compatibility
syswatch --tab X
not recognized
Use lowercase tab names:
cpu
,
mem
,
disks
,
fs
,
procs
,
gpu
,
power
,
services
,
net
,
timeline
,
insights
,
overview

症状解决方法
macOS下
cargo build
因框架链接错误失败
确保安装了Xcode命令行工具:
xcode-select --install
Apple Silicon上GPU标签页显示"无数据"温度/功耗数据需要sudo,属于正常情况;利用率和内存数据应通过ioreg显示
服务标签页更新缓慢属于预期情况 — launchctl/systemctl依赖子进程,运行在5秒慢速循环中
时间线回溯显示过期数据环形缓冲区容量在启动时设置;回溯范围仅限于已收集的历史数据
网络标签页无接口显示可能需要使用有权限访问网络统计的用户运行;检查
netwatch-sdk
兼容性
syswatch --tab X
无法识别
使用小写标签页名称:
cpu
,
mem
,
disks
,
fs
,
procs
,
gpu
,
power
,
services
,
net
,
timeline
,
insights
,
overview

Anti-Goals (What SysWatch Will Not Do)

非目标(SysWatch 不会做的事)

  • Not multi-host — use NetWatch's web dashboard for fleet monitoring.
  • Not a daemon — no background collector, no Prometheus push.
  • Not interactive — read-only by design; does not kill, renice, unmount, or restart anything.
  • Not a log search UI — surfaces OOM kills as signals; does not index logs.
  • No smooth charts — block sparklines and real numbers only.

  • 不支持多主机 — 如需集群监控,请使用NetWatch的Web仪表盘。
  • 不是守护进程 — 无后台收集器,无Prometheus推送功能。
  • 无交互操作 — 设计为只读工具;不会执行杀死进程、调整优先级、卸载挂载或重启服务等操作。
  • 不是日志搜索UI — 仅将OOM终止作为信号显示;不会索引日志。
  • 无平滑图表 — 仅显示块火花图和真实数值。

Related Projects

相关项目

  • NetWatch — sibling network diagnostics TUI, same chrome and palette.
  • netwatch-sdk — shared parsers for net interface counters and aggregate disk IO used by both tools.
  • NetWatch — 同系列网络诊断TUI工具,拥有相同的界面框架和配色方案。
  • netwatch-sdk — 共享解析器,用于处理网络接口计数器和聚合磁盘IO,被两款工具共用。