给路由器加了散热风扇,效果到底怎么样?用手摸只能叫“感觉”,用数据说话才叫“硬核”。

为了验证中兴 AX3000Pro+ 加装风扇后的散热效果,同时精准掌握这台网络中枢的各项身体机能,我决定为它量身打造一套企业级监控大屏。从底层的 Linux /proc 文件系统抓取数据,到 InfluxDB 存储,再到 Grafana 炫酷展示,本文记录了完整的折腾过程。

最终结果极度舒适:加装风扇后,芯片温度从 62℃ 瞬间断崖式跌至 43℃,并稳如老狗。

整体架构思路

中兴 AX3000Pro+ 本质上是一台运行定制 Linux 的双核小电脑。虽然官方没有提供完善的 SNMP 或 API 监控接口,但我们可以通过 Telnet 登录底层,直接读取 /proc 目录下的系统状态文件。

数据流向:

  1. **Python 脚本 (Telnet)**:每 10 秒登录一次路由器,疯狂读取系统底层文件,解析出温度、CPU、内存、负载、网速等数据。
  2. **InfluxDB (时序数据库)**:接收并高效存储 Python 传过来的实时时序数据。
  3. **Grafana (数据可视化)**:读取数据库,将枯燥的数字转换为仪表盘、折线图和状态栏。

编写数据抓取脚本

在局域网内的一台 Ubuntu 22.04 服务器上,编写 Python 脚本。脚本的核心在于模拟 Telnet 交互,并使用正则表达式精准清洗提取数据。

踩坑提示: 读取 /proc/uptime/proc/loadavg 等文件时,Telnet 往往会把命令本身(如 cat)也一并返回,因此必须使用精确的正则跳过第一行,否则会导致类型转换报错。

新建文件 router_monitor.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import asyncio
import telnetlib3
import re
import time
from influxdb import InfluxDBClient

# ================= 路由器配置 =================
HOST = "192.168.1.1"
USER = "root"
PASSWORD = "你的密码" # 替换为真实密码
INTERVAL = 10 # 10秒采集一次
# ==============================================

print("正在连接 InfluxDB 数据库...")
db_client = InfluxDBClient(host='127.0.0.1', port=8086)
db_client.create_database('router_metrics')
db_client.switch_database('router_metrics')

def get_net_bytes(data, iface):
for line in data.split('\n'):
if iface + ':' in line:
parts = line.split(':')[1].split()
if len(parts) >= 9:
return int(parts[0]), int(parts[8]) # RX, TX
return 0, 0

def get_port_speed(data, port):
m = re.search(rf'{port} rx_bps:(\d+) tx_bps:(\d+)', data)
return (int(m.group(1)), int(m.group(2))) if m else (0, 0)

async def run_command(reader, writer, cmd):
writer.write(cmd + '\n')
data = ""
while True:
chunk = await reader.read(1024)
data += chunk
if '#' in chunk:
break
return data

async def fetch_and_push():
current_time = time.strftime('%H:%M:%S')
try:
# 1. 登录路由器
reader, writer = await telnetlib3.open_connection(HOST, 23)
while True:
out = await reader.read(1024)
if not out: break
out_lower = out.lower()
if 'login:' in out_lower: writer.write(USER + '\n')
elif 'password:' in out_lower:
writer.write(PASSWORD + '\n')
break
elif '#' in out_lower: break

while True:
if '#' in await reader.read(1024): break

# 2. 连续发送命令抓取数据
temp_data = await run_command(reader, writer, 'cat /proc/tempsensor')
cpu_data = await run_command(reader, writer, 'cat /proc/cpuusage')
mem_data = await run_command(reader, writer, 'cat /proc/meminfo')
uptime_data = await run_command(reader, writer, 'cat /proc/uptime')
loadavg_data = await run_command(reader, writer, "cat /proc/loadavg")
port_data = await run_command(reader, writer, 'cat /proc/laninfo/portspeed')
net_data = await run_command(reader, writer, 'cat /proc/net/dev')

writer.close()

# 3. 数据解析
temp_match = re.search(r'temper value:\s*(\d+)', temp_data)
temperature = float(temp_match.group(1)) if temp_match else 0.0

cpu0_match = re.search(r'\[0\]Usage:\s*([\d\.]+)%', cpu_data)
cpu1_match = re.search(r'\[1\]Usage:\s*([\d\.]+)%', cpu_data)
cpu0 = float(cpu0_match.group(1)) if cpu0_match else 0.0
cpu1 = float(cpu1_match.group(1)) if cpu1_match else 0.0

# 系统负载 Load Average
loadavg_match = re.search(r'\n([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)', loadavg_data)
if loadavg_match:
load_1m, load_5m, load_15m = float(loadavg_match.group(1)), float(loadavg_match.group(2)), float(loadavg_match.group(3))
else:
load_1m = load_5m = load_15m = 0.0

# 内存占用率
mem_total = int(re.search(r'MemTotal:\s+(\d+)', mem_data).group(1)) if re.search(r'MemTotal:\s+(\d+)', mem_data) else 1
mem_avail = int(re.search(r'MemAvailable:\s+(\d+)', mem_data).group(1)) if re.search(r'MemAvailable:\s+(\d+)', mem_data) else 0
mem_usage_percent = round((mem_total - mem_avail) / mem_total * 100, 2)

# 运行时间
uptime_match = re.search(r'\n([\d\.]+)\s+[\d\.]+', uptime_data)
uptime_sec = float(uptime_match.group(1)) if uptime_match else 0.0

# 全局流量 (ppp0 为拨号 WAN 口)
ppp0_rx, ppp0_tx = get_net_bytes(net_data, 'ppp0')

# 4. 推送 InfluxDB
json_body = [{
"measurement": "zte_AX3000Pro+",
"fields": {
"temperature": temperature,
"cpu0": cpu0, "cpu1": cpu1,
"load_1m": load_1m, "load_5m": load_5m, "load_15m": load_15m,
"mem_usage_percent": mem_usage_percent,
"uptime_sec": uptime_sec,
"ppp0_rx_bytes": ppp0_rx, "ppp0_tx_bytes": ppp0_tx
}
}]
db_client.write_points(json_body)
print(f"[{current_time}] 满血推送 -> 温度:{temperature} | CPU:{cpu0}% | 内存:{mem_usage_percent}%")

except Exception as e:
print(f"[{current_time}] 抓取失败: {e}")

async def main():
while True:
await fetch_and_push()
await asyncio.sleep(INTERVAL)

if __name__ == "__main__":
asyncio.run(main())


使用 Systemd 守护进程托管服务

为了让脚本实现开机自启、崩溃重启,并能优雅地管理日志,使用 Ubuntu 的 systemd 进行托管是最佳方案。

新建服务配置文件:

1
sudo nano /etc/systemd/system/router_monitor.service

填入以下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=ZTE AX3000Pro+ Monitor Service
After=network.target docker.service

[Service]
Type=simple
User=mkr
WorkingDirectory=/home/mkr/router_monitor
# 注意1:使用真实的 Python 路径,避免 Conda 环境冲突
# 注意2:必须加上 -u 参数,关闭 Python 缓冲,否则 journalctl 看不到实时日志
ExecStart=/home/mkr/anaconda3/bin/python3 -u /home/mkr/router_monitor/router_monitor.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

1
2
3
4
sudo systemctl daemon-reload
sudo systemctl enable router_monitor.service
sudo systemctl start router_monitor.service

查看运行日志:journalctl -u router_monitor -f


Grafana 排版与数据可视化

docker-compose.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
services:
influxdb:
image: influxdb:1.8 # 使用 1.8 版本,非常轻量且对 Python 极度友好
container_name: router_influxdb
ports:
- "8086:8086"
volumes:
- ./influxdb_data:/var/lib/influxdb
restart: always

grafana:
image: grafana/grafana:latest
container_name: router_grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=
- HTTP_PROXY=http://172.18.0.1:7890
- HTTPS_PROXY=http://172.18.0.1:7890
- NO_PROXY=localhost,127.0.0.1,influxdb,172.18.0.1
volumes:
- ./grafana_data:/var/lib/grafana
depends_on:
- influxdb
restart: always

数据入库后,来到 Grafana 创建仪表板 (Dashboard),并添加面板 (Panel)。效果图:

可以看到降加上风扇后,降温效果很不错:

配置通知


去 Telegram 雇佣一个“跑腿机器人”

我们需要在 TG 上创建一个专属的 Bot,并拿到它的“通行证”(Token)和你的“门牌号”(Chat ID)。

  1. 申请机器人 (拿 Token):
    • 打开 Telegram,在搜索框搜 @BotFather(认准带官方蓝V认证的那个爹)。
    • 点击 Start,然后发送 /newbot
    • 给机器人起个名字(比如:AX3000Pro 监控警报)。
    • 再给它起个唯一的用户名(必须以 bot 结尾,比如 xxx_router_bot)。
    • 创建成功后,BotFather 会发给你一长串字符,这就是 Bot Token(格式大概是 123456789:ABCdefGhIJKlmNoPQRsTuvWxyz)。先把它复制保存下来!
  2. 获取你的门牌号 (拿 Chat ID):
    • 机器人有了,它还得知道把消息发给谁。在 TG 搜索里搜 @userinfobot,点 Start
    • 它会瞬间回复你一串数字(比如 Id: 123456789)。这就是个人 Chat ID复制下来!
  3. 激活你的机器人:
    • 在 TG 搜索你刚才创建的机器人名字(mkr_router_bot),点进去,按下 Start。你不点 Start,它是发不了消息给你的。

让 Grafana 认识你的机器人

  1. 进入报警中心:

    在 Grafana 左侧的汉堡菜单(导航栏)里,找到一个小喇叭图标 **Alerting (告警)**,点击展开,选择 **Contact points (联系点)**。

  2. 添加 Telegram:

    • 点击右上角的 **+ Add contact point (添加联系点)**。
    • **Name (名称)**:随便填,比如 TG报警发给我
    • **Integration (集成类型)**:在下拉菜单里找到并选择 Telegram
  3. 填入绝密信息:

    • BOT API Token:填入你刚才从 BotFather 那里复制的一长串字符。
    • Chat ID:填入你从 userinfobot 那里拿到的一串数字。
  4. 见证奇迹的时刻 (测试):

    • 点击右上角的 Test (测试) 按钮。
    • 此时,手机 Telegram 应该会“叮”地一声,收到一条来自你机器人的测试消息:“Test notification from Grafana”。
    • 收到的话,直接点击最下方的 **Save contact point (保存)**。

温度报警器

设定:只要温度超过 55℃ 持续 3 分钟,立刻报警!

  1. 新建报警规则:

    • 在 Grafana 左侧菜单栏的 Alerting 下,点击 **Alert rules (告警规则)**。
    • 点击右上角的 **+ New alert rule (新建告警规则)**。
  2. 配置报警条件 (照抄温度表):

    • **Define query and condition (定义查询)**:
      • SELECTfield(temperature)
      • 下面有个模块叫 BC(这是 Grafana 的数学计算模块)。
      • C 模块里,把阈值逻辑改成:**IS ABOVE 55**(当数值大于 55 时触发)。
    • **Condition (条件)**:确保选中的是最后的那个阈值判断 C
  3. 设置触发时间 (防误报):

    • 往下滚,找到 **Evaluate behavior (评估行为)**。
    • 设定为:Evaluate every 1m for 3m。(意思是:每 1 分钟检查一次,如果连续 3 分钟温度都大于 55℃,才真正拉响警报。这能完美防止温度偶然跳变导致的“假报警”)。
  4. 发给机器人:

    • 在最后面的 Notifications (通知) 模块,选择刚才创建的 Contact point(TG报警发给我)。
    • **Message (消息)**:“⚠️ 警告!中兴 AX3000Pro 核心温度已突破 55℃,风扇可能停转,请立即检查!”
  5. 保存生效!

    点击右上角的 Save and exit