← 返回文章

用 Crossbar 打造三台 Jetson AGX Orin 实时监控面板

记录如何使用 Jetson tegrastats、Python WAMP 客户端、Crossbar 和 Next.js,为三台 AGX Orin 搭建一个可实时查看温度、GPU 负载和连通状态的监控页面。

#Jetson#AGX Orin#Crossbar#WAMP#Python#监控

用 Crossbar 打造三台 Jetson AGX Orin 实时监控面板

手上有三台 Jetson AGX Orin 时,最先遇到的问题往往不是“能不能跑模型”,而是:设备现在到底是不是在线?温度有没有升高?GPU 有没有真正工作?某一台停止响应时,能不能第一时间看出来?

这次我给三台 Orin 做了一个轻量的实时监控面板,最终页面位于:

https://imeows.com/monitor

整个方案以已经部署好的 Crossbar 为消息中枢,设备端使用 Python 采集指标并通过 WAMP 主动上报,云端网关保留每台设备的最新状态,Next.js 页面每 5 秒读取一次数据。

最终效果不是再为每台设备单独开放一个 Web 端口,而是让设备只负责上报,网页只负责展示,中间所有状态流转都通过统一的 Crossbar topic 完成。

1. 需求与最终结果

本次需要监控三台设备:

节点 局域网地址 页面名称
orin-28 192.168.1.28 AGX Orin 01
orin-29 192.168.1.29 AGX Orin 02
orin-30 192.168.1.30 AGX Orin 03

页面展示以下信息:

  • 当前是否在线;
  • 最近一次上报时间;
  • GPU 3D 利用率;
  • CPU 利用率;
  • 内存占用率和已用/总内存;
  • GPU、CPU、板卡和结温;
  • 磁盘占用率;
  • 设备运行时长;
  • Crossbar 中枢当前是否连接。

采集周期为 5 秒。监控网关如果连续 15 秒没有收到某台设备的新快照,就把它标记为“失联”。这样可以把“设备从未上线”和“曾经上线但后来停止上报”区分开来。

2. 整体架构

最终链路如下:

┌────────────────┐
│ AGX Orin 01    │
│ agent.py       │
└───────┬────────┘
        │
┌───────▼────────┐       ┌────────────────────┐
│ AGX Orin 02/03 │ WSS   │ Caddy              │
│ agent.py       ├──────►│ imeows.com/ws      │
└────────────────┘       └─────────┬──────────┘
                                   │
                         ┌─────────▼──────────┐
                         │ Crossbar           │
                         │ realm: imeows      │
                         │ topic: snapshot    │
                         └─────────┬──────────┘
                                   │ WAMP subscribe
                         ┌─────────▼──────────┐
                         │ Monitor Gateway    │
                         │ latest state       │
                         └─────────┬──────────┘
                                   │ HTTP
                         ┌─────────▼──────────┐
                         │ Next.js             │
                         │ /api/monitor/nodes  │
                         └─────────┬──────────┘
                                   │
                         ┌─────────▼──────────┐
                         │ /monitor 页面       │
                         └────────────────────┘

这里有三个关键设计。

第一,Orin 不需要把局域网端口暴露给网页。设备端只建立一条到云端的加密 WebSocket 连接,然后主动发布指标。

第二,浏览器不直接连接 Crossbar。网页通过 Next.js 的同源 API 读取网关数据,避免把 WAMP 认证细节放进前端,也让前端和消息系统保持解耦。

第三,网关只保存每台设备的最新快照。当前需求是实时状态面板,不需要为了显示“现在的温度”引入数据库和复杂的时序系统。

3. Crossbar 的 topic 与权限设计

监控数据使用一个固定 topic:

imeows.monitor.snapshot

每台 Orin 只有发布权限,监控网关只有订阅权限:

身份 作用 权限
monitor-agent-28 Orin 01 发布 imeows.monitor.snapshot
monitor-agent-29 Orin 02 发布 imeows.monitor.snapshot
monitor-agent-30 Orin 03 发布 imeows.monitor.snapshot
monitor-gateway 云端网关 订阅 imeows.monitor.snapshot

每个 agent 使用独立的 WAMP ticket。ticket 只存在于设备环境文件和云端环境变量中,不写进源码、不提交 Git。

Crossbar 的角色权限采用最小授权原则。agent 不需要调用 RPC,也不需要订阅其他 topic;网关也不需要发布设备指标。这样即使某个客户端配置泄漏,影响范围也限定在监控 topic 内。

4. Orin 端:采集 Jetson 指标

4.1 为什么使用 tegrastats

Jetson 自带的 tegrastats 能直接输出 SoC 运行状态,包括:

  • RAM 使用情况;
  • CPU 核心利用率;
  • GR3D_FREQ GPU 3D 利用率;
  • GPU、CPU、板卡、结温等硬件温度;
  • 部分平台上的频率和功耗信息。

官方文档可以参考:NVIDIA tegrastats Utility。

agent 每次采集时启动一轮 tegrastats,读取稳定输出行,再用正则表达式提取字段:

RAM 2028/31920MB CPU [1%@729,0%@729,2%@729,0%@729] \
GR3D_FREQ 14%@408 \
GPU@51C CPU@53C Tboard@48C Tj@55C

不同 JetPack 版本的输出字段可能略有差异,因此解析器没有依赖某一整行的固定位置,而是分别匹配 RAM、CPU、GR3D_FREQ 和温度字段。

4.2 psutil 作为补充

tegrastats 很适合获取 Jetson 专有指标,但磁盘占用、系统运行时长等通用指标用 psutil 更方便。

因此采集端采用“优先使用 psutil、缺失时保留 tegrastats 数据”的策略:

  • CPU 和内存优先读取 psutil;
  • GPU 负载和硬件温度读取 tegrastats;
  • 磁盘占用使用 Python 标准库;
  • psutil 不可用时,CPU 和内存仍然可以从 tegrastats 结果中得到。

这种组合让 agent 不会因为某个可选 Python 包不存在,就完全失去上报能力。

4.3 WAMP 客户端

agent 使用 Autobahn 的 asyncio Component 连接 Crossbar。客户端明确指定 JSON serializer:

component = Component(
    transports={
        "type": "websocket",
        "url": "wss://imeows.com/ws",
        "serializers": ["json"],
    },
    realm="imeows",
    authentication={
        "ticket": {
            "authid": "monitor-agent-28",
            "ticket": "replace-with-node-ticket",
        }
    },
)

连接成功后,agent 每 5 秒生成一个快照并发布:

session.publish("imeows.monitor.snapshot", snapshot)

WAMP Component 自身负责连接生命周期;设备上的启动脚本还会在进程退出后重新拉起 agent,形成两层保护:连接断开时自动重连,进程异常退出时自动恢复。

4.4 上报数据结构

每台设备发布的 payload 统一使用 JSON 对象:

{
  "schema_version": 1,
  "node_id": "orin-28",
  "name": "AGX Orin 01",
  "address": "192.168.1.28",
  "hostname": "ORIN-001",
  "reported_at": "2026-08-27T10:14:38.240502+00:00",
  "uptime_seconds": 86400.0,
  "cpu_percent": 1.7,
  "memory_percent": 6.3,
  "memory_used_mb": 2028.0,
  "memory_total_mb": 31920.0,
  "disk_percent": 18.4,
  "gpu_percent": 14.0,
  "gpu_frequency_mhz": 408.0,
  "temperatures": {
    "gpu_c": 51.0,
    "cpu_c": 53.0,
    "board_c": 48.0,
    "junction_c": 55.0,
    "max_c": 55.0
  },
  "power_mw": null
}

其中 schema_version 用来给未来的字段升级留出空间。网关和前端只依赖稳定字段,不把 Jetson 命令的原始字符串直接暴露给页面。

5. 云端监控网关

5.1 MonitorHub

监控网关的核心是 MonitorHub:

  1. 使用 monitor-gateway 身份连接 Crossbar;
  2. 订阅 imeows.monitor.snapshot;
  3. 根据 node_id 把快照写入内存中的节点状态表;
  4. 每次 HTTP 请求时计算快照年龄和节点状态;
  5. 把统一的 JSON 返回给 Next.js。

状态判断非常简单:

没有收到过快照       -> never
快照年龄 <= 15 秒    -> online
快照年龄 > 15 秒     -> offline

never 和 offline 分开很有用。前者表示设备还没有完成首次接入,后者表示设备之前正常工作过,但当前已经停止上报。

5.2 两个 HTTP 接口

网关提供两个接口:

GET /health
GET /api/monitor/nodes

健康接口用于容器健康检查:

{
  "status": "ok",
  "crossbar_connected": true,
  "online_nodes": 3
}

节点接口返回完整的状态集合:

{
  "generated_at": "2026-08-27T10:14:38.678712+00:00",
  "crossbar_connected": true,
  "summary": {
    "total": 3,
    "online": 3
  },
  "nodes": []
}

生产网关采用轻量的 Python HTTP 服务,并直接复用已有 Crossbar 镜像中的 Autobahn 运行环境。这样网关和 Crossbar 使用一致的 WAMP 依赖,容器职责仍然保持清晰:Crossbar 负责消息路由,Monitor Gateway 负责状态聚合。

6. Next.js 页面

6.1 同源 API 代理

浏览器访问的是:

/api/monitor/nodes

Next.js 服务端再把请求转发到内部网关:

http://monitor:8100/api/monitor/nodes

这一步有三个好处:

  • 浏览器不需要知道 Docker 内部服务名;
  • Crossbar ticket 不会进入浏览器;
  • 页面只使用同源请求,不需要额外配置跨域。

6.2 页面轮询

监控页面加载时先显示三台预配置节点,然后每 5 秒请求一次 API:

useEffect(() => {
  void refresh();
  const timer = window.setInterval(() => void refresh(), 5000);
  return () => window.clearInterval(timer);
}, []);

页面顶部显示集群摘要:

  • 节点总数;
  • 当前在线数;
  • 平均 GPU 负载;
  • 集群最高温度。

每台设备使用独立卡片展示详细数据,并用不同颜色区分 GPU、CPU 和内存利用率。接口暂时没有响应时,页面不会直接清空已有数据,而是显示提示并继续等待下一轮刷新。

7. Docker Compose 与 Caddy 部署

生产环境包含四个相关服务:

caddy       对外提供 HTTPS/WSS
crossbar    WAMP 消息中枢
monitor     指标订阅与状态网关
web         Next.js 页面和 API 代理

Caddy 保留原站点主页,同时增加 WebSocket 路由:

imeows.com, www.imeows.com {
    encode zstd gzip

    handle /ws {
        reverse_proxy crossbar:8080
    }

    handle {
        reverse_proxy web:3000
    }
}

因此 Orin 端只需要连接:

wss://imeows.com/ws

页面仍然使用原来的:

https://imeows.com/monitor

云端敏感变量只保存在生产环境文件中,结构类似:

CROSSBAR_MONITOR_TICKET_28=replace-with-ticket-28
CROSSBAR_MONITOR_TICKET_29=replace-with-ticket-29
CROSSBAR_MONITOR_TICKET_30=replace-with-ticket-30
CROSSBAR_MONITOR_GATEWAY_TICKET=replace-with-gateway-ticket
MONITOR_STALE_AFTER_SECONDS=15

实际 ticket 不应出现在本文、源码、截图或 Git 提交里。

8. Orin 端的用户级部署

采集端不需要修改系统 Python,也不依赖系统级服务目录。每台设备使用自己的用户目录:

~/orin-monitor/
├── agent.py
├── run-agent.sh
├── node.env
├── agent.log
└── wheels/

node.env 只保存本机配置和本机对应的 ticket:

NODE_ID=orin-28
NODE_NAME='AGX Orin 01'
NODE_ADDRESS=192.168.1.28
CROSSBAR_URL=wss://imeows.com/ws
CROSSBAR_REALM=imeows
CROSSBAR_AUTHID=monitor-agent-28
CROSSBAR_TICKET=replace-with-node-ticket
TEGRASTATS_BIN=/usr/bin/tegrastats
INTERVAL_SECONDS=5

启动脚本负责三件事:

  1. 读取 node.env;
  2. 使用文件锁避免重复启动多个 agent;
  3. agent 退出后等待几秒再次启动。

最后加入用户 crontab:

@reboot /home/wooley/orin-monitor/run-agent.sh # imeows-orin-monitor

这种方式足够支撑轻量采集任务,同时不会修改设备上其他服务。查看单台设备状态时,可以执行:

pgrep -af "orin-monitor/agent.py"
tail -f ~/orin-monitor/agent.log

9. 调通后的验证方法

9.1 检查 Crossbar 与网关

在云主机上查看容器:

docker compose ps
docker logs --tail 50 imeows-production-crossbar-1
docker logs --tail 50 imeows-production-monitor-1

网关健康接口应返回:

curl -fsS http://127.0.0.1:8100/health

9.2 检查公网页面和接口

curl -fsS -o /dev/null -w "%{http_code}\n" \
  https://imeows.com/monitor

curl -fsS https://imeows.com/api/monitor/nodes \
  | python3 -m json.tool

接口中最重要的三个字段是:

{
  "crossbar_connected": true,
  "summary": {
    "total": 3,
    "online": 3
  }
}

9.3 检查 Orin 端

每台 Orin 的日志中应能看到类似信息:

Connected to Crossbar: node=orin-28 realm=imeows session=... authid=monitor-agent-28

随后访问节点 API,确认每个节点都有新的 last_seen 和 snapshot。本次最终验证结果为三台设备全部在线,页面能够持续显示 CPU、GPU、内存和温度数据。

10. 这套方案的边界

当前实现保存的是“最新状态”,不是历史时序数据。如果后续需要画温度曲线、统计 GPU 使用率峰值或查看过去 30 天的运行情况,可以把 Monitor Gateway 的内存状态替换成:

  • Prometheus + node exporter;
  • InfluxDB;
  • TimescaleDB;
  • ClickHouse;
  • 或者一张专门的监控快照表。

当前页面也暂时没有操作设备的能力。重启、停止任务、切换模型等动作应该另行设计 RPC topic,并为每个动作增加审计、权限和幂等机制,不建议直接把控制能力混入只读监控 topic。

如果监控页面需要只允许管理员查看,可以在 /monitor 和 /api/monitor/nodes 外层增加 Firebase 登录校验。展示层、消息层和设备认证已经分开,后续增加访问控制不会影响 Orin agent 的采集逻辑。

结语

这次的核心不是做一个复杂的监控平台,而是先把三台设备的状态链路做短:

采集 → Crossbar → 网关 → 页面

Jetson 专有指标交给 tegrastats,通用系统指标交给 Python,消息传输交给 Crossbar,网页只消费统一 JSON。每一层都只做自己擅长的事情,最终得到一个足够轻、容易维护,也方便继续扩展的 AGX Orin 监控基础设施。

本次实现涉及的主要文件:

apps/web/app/monitor/page.tsx
apps/web/components/MonitorDashboard.tsx
apps/web/app/api/monitor/nodes/route.ts
services/monitor/app/hub.py
services/monitor/app/standalone.py
infra/orin-monitor/agent.py
infra/orin-monitor/run-agent.sh
infra/crossbar/production.json
infra/docker/compose.production.yaml

Conversation

留言

加载中

正在读取留言…

Leave a note

留下你的想法

留言提交后会先进入审核,审核通过才会公开显示。