
NodeLite 是一个用 Rust 编写的轻量级服务器监控面板,包含:
nodelite-server
中心服务,提供 WebSocket 接入、只读页面、只读 JSON API、SQLite 短期历史和快照恢复。nodelite-agent
Linux / macOS agent,采集 CPU、负载、内存、磁盘、网络总流量、实时速率和 WebSocket RTT。nodelite-proto
服务端与 agent 共用的配置、协议和数据模型。适合这样的场景:
完整的公开部署文档可直接查看 GitHub Pages: https://xinian-dada.github.io/NodeLite/
版本建议:请始终使用 GitHub Releases 中的最新正式版本(不含
-alpha、-beta、-rc标记)。latest指向的即是最新正式版,pre-release 不会被自动选中。不要在正式环境中使用测试版本。
推荐按下面 3 步体验完整流程:
curl -fsSL https://github.com/XiNian-dada/NodeLite/releases/latest/download/install-server.sh | sudo sh
/usr/local/bin/nodelite-server \
--config /opt/nodelite/config/server.toml \
install-agent \
--node-id hk-01 \
--node-label "Hong Kong 01"
安装成功后:
https://你的域名/ 访问wss://你的域名/ws 自动接入nodelite-server
推荐部署在 Linux(systemd 环境),官方发布产物提供 x86_64-unknown-linux-musl 与 aarch64-unknown-linux-muslnodelite-agent
支持 Linux 与 macOS;官方发布二进制覆盖 Linux 与 macOS(Intel / Apple Silicon),其中 macOS 的一键安装 / launchd 集成仍属实验性支持,推荐先在测试机验证后再用于长期运行



















在 4 个被控端的实际运行环境下,v2.1.2 的长期观测值大致如下:
这些数字是观测值,不是固定上限。当前版本的 Agent 常驻内存会随着运行时长增加而缓慢上升,因此 800-1000 KB 更适合被理解为冷启动基线,而不是长时间运行后的稳定占用。
如果你对 Agent 的长期常驻内存敏感,建议在自己的环境中持续观察 RSS 变化并预留余量。
下面这组数据来自 v2.2.6 在同一台机器上对当前版本连续执行 3 次仓库内置 loopback 压测后得到的均值,使用 release 构建直接实测:
cargo test -p nodelite-server --release load_test_scaling_scores -- --ignored --nocapture
cargo test -p nodelite-server --release load_test_api_surface_scores -- --ignored --nocapture
cargo test -p nodelite-server --release load_test_reconnect_storm_scores -- --ignored --nocapture
测试机:Apple M1 Pro / 32 GB / macOS 26.5
这组基线走的是真实 WebSocket 建链、真实 metrics 上报和真实只读 API 轮询,但仍然是单机 loopback,不包含反向代理、TLS 终结和跨机网络抖动。
相较 README 中上一版 v2.2.5 基线,本版本当前样本可见的改进包括:
1000 节点 dashboard fanout 场景下,/api/nodes 响应体从 968995 B 降到 285001 B,约缩小 70.6%。1000 节点 dashboard fanout 场景下,/api/nodes p95 从 12.61 ms 降到 3.51 ms,约下降 72.2%。1000 节点 dashboard fanout 场景下,/metrics p95 从 20.22 ms 降到 7.29 ms,约下降 64.0%。1000 节点 history pressure 场景下,history p95 从 70.90 ms 降到 33.86 ms,约下降 52.2%。500 节点 64 disk entry 场景下,/metrics 响应体从 3146308 B 降到 637182 B,约缩小 79.7%。| 节点数 | 全部接入耗时 | 收敛耗时 | 总 metrics 数 | metrics 吞吐 | overview p50 | overview p95 | overview max |
|---|---|---|---|---|---|---|---|
| 20 | 186.5 ms | 22.3 ms | 240 | 10761.2/s | 0.45 ms | 0.62 ms | 0.83 ms |
| 50 | 456.6 ms | 21.7 ms | 600 | 27671.0/s | 0.46 ms | 0.87 ms | 2.72 ms |
| 100 | 1131.8 ms | 22.5 ms | 1200 | 53484.1/s | 0.58 ms | 1.42 ms | 5.01 ms |
| 200 | 2269.4 ms | 36.5 ms | 2400 | 72864.0/s | 0.54 ms | 5.30 ms | 12.55 ms |
load_test_api_surface_scores 在 steady-state 下持续上报 3600 条 metrics,同时对只读 API 做 20 轮采样;其中历史接口返回的是一个节点 360 个种子历史点对应的精确时间区间。下表同样使用 3 次重复执行后的均值。
| 接口 | p50 | p95 | max |
|---|---|---|---|
/api/overview |
0.66 ms | 2.72 ms | 6.54 ms |
/api/nodes |
0.67 ms | 2.93 ms | 3.48 ms |
/api/nodes/{node_id} |
0.66 ms | 2.98 ms | 3.21 ms |
/api/nodes/{node_id}/history |
1.59 ms | 3.54 ms | 4.99 ms |
load_test_reconnect_storm_scores 会把 200 个节点连续拉起和断开 4 轮,共计 800 次会话建立。下面这些数值也使用 3 次重复执行后的均值:
/api/overview p95:2.56 ms/api/nodes p95:3.12 ms说明:
下面这组压测默认标记为 ignored,只在手动排查性能回归时运行,不影响默认 CI:
cargo test -p nodelite-server --release load_test_large_fleet_scores -- --ignored --nocapture
cargo test -p nodelite-server --release load_test_dashboard_fanout_scores -- --ignored --nocapture
cargo test -p nodelite-server --release load_test_history_pressure_scores -- --ignored --nocapture
cargo test -p nodelite-server --release load_test_payload_size_scores -- --ignored --nocapture
覆盖范围:
load_test_large_fleet_scores:500 / 1000 节点 loopback WebSocket 上报,同时采样 /api/overview、/api/nodes 和 Prometheus /metrics。load_test_dashboard_fanout_scores:1000 节点、20 个 dashboard reader 并发刷新,并穿插 Prometheus scrape。load_test_history_pressure_scores:1000 节点下对历史查询做并发 reader 压力,覆盖 SQLite 读写竞争。load_test_payload_size_scores:500 节点、每节点 64 个 disk entry,观察大 payload 的 API body 和渲染前置压力。每条 *_RESULT 输出都会包含对应场景的 p95 延迟、API body bytes 的 p50/p95/max、当前进程 RSS、history writer queue depth、dropped writes,以及 SQLite db/wal/shm 文件大小。
下面这张表仍然是本次同机实测的单次样本结果,用来展示更大规模场景的大致量级,不作为多轮均值基线:
| 场景 | 关键规模 | metrics 吞吐 | 关键 p95 | 响应体大小 | RSS | history dropped writes |
|---|---|---|---|---|---|---|
load_test_large_fleet_scores |
500 节点 |
68678.5/s |
overview 1.18 ms / nodes 1.67 ms / metrics 7.55 ms |
overview 257 B / nodes 142501 B / metrics 637186 B |
194.8 MiB |
0 |
load_test_large_fleet_scores |
1000 节点 |
89675.3/s |
overview 1.56 ms / nodes 4.08 ms / metrics 18.54 ms |
overview 261 B / nodes 285001 B / metrics 1264700 B |
335.7 MiB |
0 |
load_test_dashboard_fanout_scores |
1000 节点 + 20 readers |
88934.8/s |
overview 2.08 ms / nodes 3.51 ms / metrics 7.29 ms |
overview 261 B / nodes 285001 B / metrics 1264700 B |
337.9 MiB |
0 |
load_test_history_pressure_scores |
1000 节点 + 20 history readers |
90773.1/s |
history 33.86 ms |
history 50823 B |
334.6 MiB |
0 |
load_test_payload_size_scores |
500 节点 + 64 disk entries |
43559.1/s |
nodes 1.55 ms / metrics 14.81 ms |
nodes 142501 B / metrics 637182 B |
222.8 MiB |
0 |
首页 DOM 渲染压力可以用真实 nodelite-server/assets/index.html 生成自包含 fixture:
node scripts/benchmark-index-dom.mjs --nodes 500
node scripts/benchmark-index-dom.mjs --nodes 1000
脚本会写入 target/load-test/index-dom-*.html。用浏览器打开生成文件后,页面右下角会显示 renderMs、jsHeapBytes、domNodeCount 和 nodeCardCount,同一份结果也会挂到 window.__NODELITE_DOM_BENCHMARK__ 便于控制台读取。需要模拟大磁盘 payload 时可追加 --disks 64。
install-server.shinstall-agent.shnodelite-server install-agentnodelite-server upgrade-agent//nodes/{node_id}/api/overview/api/nodes/api/nodes/{node_id}/api/nodes/{node_id}/historyhellometricspingpongserver_noticerefresh_token_requestrefresh_token_response历史图用于展示基础趋势,不是逐条 metrics 上报的完整归档。Server 默认同一节点每 30 秒最多写入一个历史点,并在查询时按时间窗口和 max_points 返回适合绘图的采样结果。
历史写入走 bounded queue + batch SQLite writer。队列满时 Server 会优先保证实时 WebSocket 心跳和面板当前状态,丢弃该次历史写入并递增 /metrics 中的 nodelite_history_dropped_writes_total。这个值长期应为 0;如果持续增长,说明历史 writer 跟不上上报速率或磁盘 I/O,历史图可能出现缺口,但实时视图仍会继续更新。
cargo check
NodeLite 现在提供受保护的 /metrics 端点,输出 Prometheus exposition text。它和仪表盘共用只读认证,因此抓取端需要带上同一组 Basic Auth 凭据。
默认 /metrics 只导出 server / overview 聚合和少量 node summary 指标,避免大规模集群下 scrape 响应体过大。如果需要按节点导出 CPU、uptime、memory、load、network 等最新快照细节,可在 server.toml 的 [metrics] 中设置 export_node_resource_metrics = true;如果还需要按挂载点导出磁盘指标,再设置 export_node_disk_metrics = true。这些开关会按节点数和挂载点数量增加 series 与响应体大小,建议只在确实需要 Prometheus 长期采集这些细节时开启。
先用 curl 验证:
curl -u viewer:secret https://monitor.example.com/metrics
Prometheus 示例:
scrape_configs:
- job_name: nodelite
scheme: https
metrics_path: /metrics
basic_auth:
username: viewer
password: secret
static_configs:
- targets:
- monitor.example.com
常用运维指标:
nodelite_history_dropped_writes_total: 历史写入队列满时丢弃的历史点总数。nodelite_history_queue_depth / nodelite_history_queue_capacity: 历史 writer 当前排队量与队列容量。nodelite_audit_dropped_writes_total: 审计写入队列满时丢弃的审计事件总数。nodelite_audit_queue_depth / nodelite_audit_queue_capacity: 审计 writer 当前排队量与队列容量。nodelite_audit_write_failures_total: 审计 writer 入队或落库失败总数。nodelite_view_cache_hits_total{kind} / nodelite_view_cache_misses_total{kind}: overview、nodes、metrics 视图响应体缓存命中与未命中次数。nodelite_api_body_bytes{kind} / nodelite_metrics_response_body_bytes: 最近一次构建的 API、基础 /metrics 与最终 /metrics 响应体大小。nodelite_process_resident_memory_bytes: Server 进程 RSS,用于定位常驻内存增长。nodelite_sqlite_file_bytes{kind}: history/audit SQLite 主文件、WAL 与 SHM 文件大小。nodelite_sqlite_wal_checkpoint_observed{database}: 最近一次 passive WAL checkpoint probe 是否成功。nodelite_sqlite_wal_checkpoint_active{database}: SQLite 库当前是否处于 WAL journal mode。nodelite_sqlite_wal_checkpoint_busy{database}: PRAGMA wal_checkpoint(PASSIVE) 返回的 busy 标志。nodelite_sqlite_wal_checkpoint_pages{database,state}: log、checkpointed 与 backlog WAL 页数。backlog 持续增长且 WAL 文件 bytes 同步增长时,通常说明 checkpoint 被长读事务或磁盘 I/O 拖住。nodelite_registry_nodes: 当前加载的注册节点数量。nodelite_registry_disk_entries_total: 当前节点快照中持有的磁盘条目总数。nodelite_ws_messages_total{type}: 已认证 WebSocket 消息按类型累计计数。SQLite 运维提示:
/metrics 会最多每 60 秒执行一次受控的 wal_checkpoint(PASSIVE) probe。PASSIVE 不会截断 WAL,也不会等待其它连接释放锁;它会尝试推进可 checkpoint 的 WAL 页,并返回当前 WAL 页数、已 checkpoint 页数和 busy 状态。DELETE 删除过期行。SQLite 主库文件不会因为 DELETE 立即缩小,WAL 也可能在 checkpoint 前保持较大;这属于 SQLite 的正常文件复用行为。nodelite_sqlite_file_bytes{kind="history_wal"} 或 kind="audit_wal" 持续增长时,建议同时告警 nodelite_sqlite_wal_checkpoint_pages{state="backlog"} 和 busy 标志,用来区分写入增长、checkpoint 被阻塞和外部磁盘问题。VACUUM;如需强制截断 WAL,应在维护窗口对目标库执行 PRAGMA wal_checkpoint(TRUNCATE)。不要在高峰期把这些操作放进自动热路径。安装 cargo-tarpaulin(仅需一次,仅支持 Linux):
cargo install cargo-tarpaulin
运行覆盖率分析:
cargo tarpaulin --config tarpaulin.toml
HTML 报告输出到 target/tarpaulin/tarpaulin-report.html,可用浏览器打开查看逐行覆盖情况。
| 阶段 | 目标 | 时间 |
|---|---|---|
| 基线 | 记录当前值 | 立即 |
| 短期 | 75% | 2 周内 |
| 长期 | 80% | 持续 |
重点覆盖模块:auth、admission、sanitize、registry(安全关键路径)。
如需单独跑新增的属性测试,可直接使用:
cargo test -p nodelite-server sanitize::tests
cargo test -p nodelite-server registry::tests
fuzz/ 是独立的 Cargo crate,不属于默认 workspace,因此不会被普通
cargo test --workspace 编译。它覆盖外部 WebSocket 文本帧的 JSON 解析入口:
WireMessage(Agent 通道)和 BrowserMessage(浏览器通道)。
常用手动验证命令:
cargo test --manifest-path fuzz/Cargo.toml
cargo run --manifest-path fuzz/Cargo.toml --bin wire_message -- fuzz/corpus/protocol_messages
cargo run --manifest-path fuzz/Cargo.toml --bin browser_message -- fuzz/corpus/protocol_messages
cargo run --manifest-path fuzz/Cargo.toml --bin protocol_messages -- 10000
其中 protocol_messages 使用固定种子的伪随机输入跑指定迭代数,适合在本地或
定期任务中做“任意输入不崩溃”的快速 smoke;真实崩溃应以能复现的 corpus 文件
形式加入 fuzz/corpus/protocol_messages/。
仓库内已经包含 musl 目标的 lld 链接配置,可以直接构建静态 Linux 二进制:
cargo build --release --target x86_64-unknown-linux-musl \
-p nodelite-server \
-p nodelite-agent
cargo build --release --target aarch64-unknown-linux-musl \
-p nodelite-server \
-p nodelite-agent
产物位置:
target/x86_64-unknown-linux-musl/release/nodelite-server
target/x86_64-unknown-linux-musl/release/nodelite-agent
target/aarch64-unknown-linux-musl/release/nodelite-server
target/aarch64-unknown-linux-musl/release/nodelite-agent
生产环境建议这样放:
nodelite-server 监听在 127.0.0.1:8080443wss://你的域名/ws 接入这样可以把 TLS、访问日志、限流和基础访问控制都放到反代层。
推荐直接用 GitHub Release 里的交互式安装器。它会清
$ claude mcp add NodeLite \
-- python -m otcore.mcp_server <graph>