syswatch-terminal-diagnostics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSysWatch 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 , , , , , and many other tools into twelve navigable tabs, with plain-English anomaly detection and a session-wide scrubber.
htopiostatvm_statpowermetricslaunchctl来自 ara.so 的技能 — 2026每日技能合集。
SysWatch 是一款基于 Rust 开发、面向 macOS 和 Linux 的单主机系统诊断 TUI 工具。它将、、、、等多款工具的功能整合到12个可导航的标签页中,提供通俗易懂的异常检测功能以及全局会话回溯功能。
htopiostatvm_statpowermetricslaunchctlInstall
安装
bash
git clone https://github.com/matthart1983/syswatch.git && cd syswatch
cargo build --release
./target/release/syswatchRequirements: 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
undefinedbash
undefinedDefault — 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 Quittext
1 2 3 4 5 6 7 8 9 概览 / CPU / 内存 / 磁盘 / 文件系统 / 进程 / GPU / 电源 / 服务
0 - + 网络 / 时间线 / 分析见解
Tab / Shift-Tab 向前/向后切换标签页
↑ / ↓ 选择行(进程、服务标签页)
s 切换排序列(进程、服务标签页)
← / → 向前/向后回溯会话(时间线标签页)
Home / End 跳转到最早记录 / 返回实时状态
p 暂停数据收集
q / Ctrl-C 退出程序Tabs Reference
标签页参考
| Key | Tab | Data Source / Replaces |
|---|---|---|
| Overview | Dashboard of all subsystems |
| CPU | |
| Memory | |
| Disks | |
| Filesystems | |
| Procs | |
| GPU | |
| Power | |
| Services | |
| Net | |
| Timeline | Session log + scrubber |
| Insights | Plain-English anomaly cards |
| 按键 | 标签页 | 数据源 / 替代工具 |
|---|---|---|
| 概览 | 所有子系统的仪表盘 |
| CPU | |
| 内存 | |
| 磁盘 | |
| 文件系统 | |
| 进程 | |
| GPU | |
| 电源 | |
| 服务 | |
| 网络 | |
| 时间线 | 会话日志 + 回溯器 |
| 分析见解 | 通俗易懂的异常提示卡片 |
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 helpersRefresh 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 . Each one populates a typed struct and is called from .
src/collect/Snapshotcollector.rsrust
// 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 and call in the fast or slow loop as appropriate.
collector.rscollect()收集器位于 目录下。每个收集器会填充一个类型化的 结构体,并由 调用。
src/collect/Snapshotcollector.rsrust
// 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.rscollect()Extending SysWatch: Adding a Tab Renderer
扩展 SysWatch:添加标签页渲染器
Tab renderers live in . They receive the current (or scrubbed) snapshot and render into a .
src/tabs/ratatuiFramerust
// 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 's tab dispatch match arm and add the tab label to .
app.rsui/chrome.rs标签页渲染器位于 目录下。它们接收当前(或回溯的)快照,并渲染到 的 中。
src/tabs/ratatuiFramerust
// 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.rsui/chrome.rsExtending SysWatch: Adding an Insight
扩展 SysWatch:添加分析见解
Insights are pure functions in . They take history + the latest snapshot and return zero or more anomaly cards.
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
}Register the check in so it's included in the Insights tab and Overview badge.
insights/mod.rs分析见解是 目录下的纯函数。它们接收历史数据和最新快照,返回零或多个异常提示卡片。
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.rsUsing the Ring Buffer (Session Scrubbing)
使用环形缓冲区(会话回溯)
The type in is the backbone of session scrubbing. Any collector that wraps its history in a gets scrubbing for free when the tab renderer calls .
Ring<T>src/collect/ring.rsRingnth_backrust
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 , the / keys increment/decrement , and every tab renderer receives the offset so they all show the same point in time.
app.rs←→scrub_offsetsrc/collect/ring.rsRing<T>Ringnth_backrust
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_offsetOptional Cargo Features
可选 Cargo 特性
toml
undefinedtoml
undefinedCargo.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,smartsmart = []
启用特性构建:
```bash
cargo build --release --features gpu-nvidia
cargo build --release --features smart
cargo build --release --features gpu-nvidia,smartPlatform 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 . SysWatch shows available data and displays a one-line note where sudo is needed — it never prompts.
sudo powermetrics - Thermal zone temps require IOReport private FFI (deferred).
- Apple Silicon 上的GPU利用率和已用内存:无需sudo即可通过获取。
ioreg AGXAccelerator PerformanceStatistics - 风扇转速、组件功耗、GPU温度:需要。SysWatch会显示可用数据,并在需要sudo的位置显示一行提示,但绝不会主动请求权限。
sudo powermetrics - 热区温度:需要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 key sets . The event loop skips calls but the UI still redraws on keypress, so scrubbing works while paused.
papp.paused = truecollect()rust
if !app.paused {
app.collector.collect();
}按下键会设置。事件循环会跳过调用,但UI仍会在按键时重绘,因此暂停时仍可使用回溯功能。
papp.paused = truecollect()rust
if !app.paused {
app.collector.collect();
}Troubleshooting
故障排除
| Symptom | Fix |
|---|---|
| Ensure Xcode Command Line Tools are installed: |
| GPU tab shows "no data" on Apple Silicon | Normal without sudo for temp/power; util + memory should appear via ioreg |
| Services tab is slow to update | Expected — launchctl/systemctl are subprocess-heavy; they run on the 5 s slow loop |
| Timeline scrubbing shows stale data | The ring capacity is set at startup; scrubbing is limited to collected history |
| Net tab shows no interfaces | May need to run as a user with access to network stats; check |
| Use lowercase tab names: |
| 症状 | 解决方法 |
|---|---|
macOS下 | 确保安装了Xcode命令行工具: |
| Apple Silicon上GPU标签页显示"无数据" | 温度/功耗数据需要sudo,属于正常情况;利用率和内存数据应通过ioreg显示 |
| 服务标签页更新缓慢 | 属于预期情况 — launchctl/systemctl依赖子进程,运行在5秒慢速循环中 |
| 时间线回溯显示过期数据 | 环形缓冲区容量在启动时设置;回溯范围仅限于已收集的历史数据 |
| 网络标签页无接口显示 | 可能需要使用有权限访问网络统计的用户运行;检查 |
| 使用小写标签页名称: |
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,被两款工具共用。