From e49a2f8f0373a111de583c6928125708daa1b3bd Mon Sep 17 00:00:00 2001 From: snowy-yang <102860110+snowy-yang@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:23:46 +0800 Subject: [PATCH] feat: remove power monitoring feature --- README.md | 10 +----- bot.py | 35 -------------------- monitor.py | 77 ++------------------------------------------ templates/power.txt | 2 -- templates/status.txt | 1 - tests/test_bot.py | 1 - 6 files changed, 3 insertions(+), 123 deletions(-) delete mode 100644 templates/power.txt diff --git a/README.md b/README.md index 72e6c9d..08b710d 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,7 @@ uv run pytest | 命令 | 说明 | |---|---| -| `/status` 或 `状态` | 总览:功耗、CPU、负载、内存、最高温度、网速、运行时间 | -| `/power` 或 `功耗` | 功耗详情(电池功率 / Intel RAPL 各域瓦数) | +| `/status` 或 `状态` | 总览:CPU、负载、内存、最高温度、网速、运行时间 | | `/top` 或 `进程` | CPU 占用 TOP10 进程 | | `/temp` 或 `温度` | 各传感器温度 | | `/net` 或 `网络` | 网络上下行速率 | @@ -68,19 +67,12 @@ uv run pytest 消息包含当前 CPU%、负载和 TOP5 高负载进程(PID/名称/CPU/内存)。同一轮高负载期间 冷却 10 分钟不重复告警;回落到阈值以下会发送恢复通知。以上均可在 `config.toml` 的 `[alert]` 中调整。 -## 功耗说明 - -- 笔记本:读取 `/sys/class/power_supply/BAT*/power_now` -- Intel CPU:读取 RAPL `/sys/class/powercap/intel-rapl:*/energy_uj`,两次采样差分计算瓦数 -- AMD CPU / 多数虚拟机 / 云主机:无硬件计数器,`/power` 会提示不支持(属正常) - ## 平台兼容性 | 功能 | Linux | Windows | |---|---|---| | CPU/内存/进程/磁盘/网络 | 完整支持 | 完整支持 | | 负载 (load average) | 内核提供 | psutil 模拟值 | -| 功耗 (瓦数) | RAPL / 电池功率 | 不可测,笔记本显示电池电量与充放电状态 | | 温度 | lm-sensors / sysfs | WMI 热区(部分主板支持,可能需管理员权限) | 机器人本体(OneBot 反向 WS、命令、告警)在两个平台行为一致。 diff --git a/bot.py b/bot.py index d02a860..efa355b 100644 --- a/bot.py +++ b/bot.py @@ -43,17 +43,6 @@ def _fmt_uptime(seconds: float) -> str: return f"{d}天{h}小时{m}分" if d else f"{h}小时{m}分" -def _power_str(p: dict) -> str: - if p["total_watts"] is not None: - src = {"battery": "电池", "rapl": "RAPL"}.get(p["source"], p["source"]) - extra = f" (电池 {p['battery']}W)" if p["battery"] is not None and p["source"] == "rapl" else "" - return f"{p['total_watts']}W [{src}]{extra}" - if p.get("battery_percent") is not None: - state = "充电中" if p.get("plugged") else "放电中" - return f"电量 {p['battery_percent']}% ({state}),功率不可测" - return "不支持(需要电池或Intel RAPL)" - - def _hottest(temps: dict): flat = [t for entries in temps.values() for t in entries] return max(flat, key=lambda t: t["current"]) if flat else None @@ -66,7 +55,6 @@ def fmt_status(s: dict) -> str: d = s["disk"][0] if s["disk"] else None return templates.render( "status", - power=_power_str(s["power"]), cpu_total=f"{sum(c['per_core']):.1f}%", cpu_max=f"{c['cores'] * 100}%", cpu_avg=f"{c['percent']}", @@ -83,26 +71,6 @@ def fmt_status(s: dict) -> str: ) -def fmt_power(s: dict) -> str: - p = s["power"] - if p["total_watts"] is None and p["battery"] is None and p.get("battery_percent") is None: - return templates.render( - "power", - power=("未获取到功耗数据。支持的来源:笔记本电池 /sys/class/power_supply 或系统电池接口," - "Intel RAPL /sys/class/powercap (AMD/多数虚拟机/台式机不支持)。"), - ) - lines = [] - if p["battery"] is not None: - lines.append(f"电池功率: {p['battery']}W") - if p.get("battery_percent") is not None: - state = "充电中" if p.get("plugged") else "放电中" - lines.append(f"电量: {p['battery_percent']}% ({state})") - if p["domains"]: - lines.append("RAPL 域:") - lines += [f" {name}: {watts}W" for name, watts in p["domains"].items()] - return templates.render("power", power="\n".join(lines) or "无数据") - - def fmt_top(s: dict) -> str: if not s["top"]: return templates.render("top", top_n=10, top_list="无数据") @@ -133,7 +101,6 @@ def fmt_net(net: dict) -> str: # 命令别名 -> 动作;只有此表内的词会触发,其他消息一律忽略 COMMANDS = { "status": "status", "状态": "status", - "power": "power", "功耗": "power", "top": "top", "进程": "top", "temp": "temp", "温度": "temp", "net": "net", "网络": "net", @@ -234,8 +201,6 @@ class Bot: try: if action == "status": return fmt_status(monitor.get_status()) - if action == "power": - return fmt_power({"power": monitor.get_power()}) if action == "top": return fmt_top({"top": monitor.get_top_processes(10)}) if action == "temp": diff --git a/monitor.py b/monitor.py index c729946..319a8a1 100644 --- a/monitor.py +++ b/monitor.py @@ -1,4 +1,4 @@ -"""Linux 系统监控:功耗(RAPL/电池)、CPU、内存、温度、磁盘、网络、进程。""" +"""Linux 系统监控:CPU、内存、温度、磁盘、网络、进程。""" import os import time from pathlib import Path @@ -6,79 +6,7 @@ from pathlib import Path import psutil -# ---------- 功耗 ---------- - -def _read_energy_uj(path: Path) -> int | None: - try: - return int(path.read_text().strip()) - except (OSError, ValueError): - return None - - -def get_power() -> dict: - """返回功耗信息,单位瓦(W)。优先电池,其次 Intel RAPL。""" - result = {"source": None, "total_watts": None, "domains": {}, "battery": None} - - # 1. 电池功率(笔记本放电/充电) - for p in Path("/sys/class/power_supply").glob("BAT*/power_now"): - try: - # power_now 单位微瓦 - watts = int(p.read_text().strip()) / 1_000_000 - result["battery"] = round(watts, 2) - result["source"] = "battery" - result["total_watts"] = round(watts, 2) - except (OSError, ValueError): - pass - - # 2. Intel RAPL energy 差分 - rapl_dir = Path("/sys/class/powercap") - domains_now = {} - for i, d in enumerate(sorted(rapl_dir.glob("intel-rapl:*"))): - name_file = d / "name" - energy_file = d / "energy_uj" - if not energy_file.exists(): - continue - domain = name_file.read_text().strip() if name_file.exists() else f"rapl{i}" - energy = _read_energy_uj(energy_file) - if energy is not None: - domains_now[domain] = (energy, time.monotonic()) - - if domains_now: - # 用上一次读数差分计算瓦数 - prev = getattr(get_power, "_prev_rapl", None) - get_power._prev_rapl = domains_now - if prev: - for domain, (energy, ts) in domains_now.items(): - if domain in prev: - p_energy, p_ts = prev[domain] - dt = ts - p_ts - if dt > 0: - delta = energy - p_energy - if delta < 0: # 计数器回绕 - max_uj = 1 << 32 - delta += max_uj - watts = delta / 1_000_000 / dt - result["domains"][domain] = round(watts, 2) - if result["domains"]: - result["source"] = result["source"] or "rapl" - if result["total_watts"] is None: - # 累加所有域(通常 dom0 已包含子域,取最大 dom0) - result["total_watts"] = max(result["domains"].values()) - - # 3. Windows/通用兜底:电池电量百分比(无功率数据) - if result["total_watts"] is None: - try: - bat = psutil.sensors_battery() - except (AttributeError, OSError): - bat = None - if bat is not None: - result["battery_percent"] = round(bat.percent, 1) - result["plugged"] = bat.power_plugged - - return result - - -# ---------- 其他指标 ---------- +# ---------- 指标 ---------- def get_cpu() -> dict: load1, load5, load15 = _safe_getloadavg() @@ -249,7 +177,6 @@ def get_uptime() -> float: def get_status(top_n: int | None = None) -> dict: """系统总览。top_n 为 None 时不枚举进程(Windows 上较慢)。""" data = { - "power": get_power(), "cpu": get_cpu(), "memory": get_memory(), "temps": get_temps(), diff --git a/templates/power.txt b/templates/power.txt deleted file mode 100644 index a81db7b..0000000 --- a/templates/power.txt +++ /dev/null @@ -1,2 +0,0 @@ -功耗详情 -{power} diff --git a/templates/status.txt b/templates/status.txt index f713eb9..353ed6a 100644 --- a/templates/status.txt +++ b/templates/status.txt @@ -1,5 +1,4 @@ 服务器状态 -功耗: {power} CPU: {cpu_total} / {cpu_max} (单核均值 {cpu_avg}%) 核心 {cores} 负载 {load} 内存: {memory} 最高温度: {temp} diff --git a/tests/test_bot.py b/tests/test_bot.py index 7edfbbb..105b015 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -82,7 +82,6 @@ async def wait_reply(fake: FakeOneBotClient, timeout: float = 30) -> str: @pytest.mark.asyncio @pytest.mark.parametrize("cmd,expect", [ ("状态", "服务器状态"), - ("功耗", "功耗"), ("top", "TOP10"), ("温度", "温度"), ("网络", "网络速率"),