feat: 群分析本地消息库 / 处理中占位图 / Web 日志窗口重构
- 群分析: 新增 history_store 只读本地消息源(读 learning_chat 落库,含 uninfo 昵称补齐/去重/截断判定),适配器优先读本地库、失败回退 OneBot 分页;分页 锚点字段回退链修复 NapCat 传 message_seq 翻页断裂;新增「本地记录」开关 - core/message_utils: 新增 common_proc_reply 占位图通用回复(引用消息 + 处理中 动图,支持后台任务显式指定 target),群分析/战况/倒放改用 - web_hub + web: 日志页改固定窗口滚动 + 翻页锚定 + 自动换行,SSE 日志轮转发 reset 帧,入口 HTML no-cache,行数统计增量缓存,CPU 改非阻塞采样,登录信息缓存 - 插件内 CLAUDE.md / DESIGN.md 不入库(.gitignore),galgame_card 两份文档取消 跟踪(文件保留在磁盘) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+32
-14
@@ -6,7 +6,10 @@
|
||||
- host 性能:psutil(CPU/内存/磁盘/进程)+ platform + nonebot 版本
|
||||
仅在 /hub/api/dashboard 被调用时执行,不做常驻采样。
|
||||
同步采集(psutil 等可能阻塞的调用)整体放入线程池执行,避免阻塞事件循环;
|
||||
结果做 1 秒短缓存,多个前端标签页(含 SSE 循环)不至于各自重复采集。
|
||||
结果做短缓存,多个前端标签页(含 SSE 循环)不至于各自重复采集。
|
||||
CPU 采样走非阻塞模式(interval=None,与上一次调用求差):带 interval 的
|
||||
psutil.cpu_percent/Process.cpu_percent 会真的 sleep 住调用线程,而 SSE 循环
|
||||
每轮都调用一次,会凭空常驻烧掉约四分之一个核。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -28,21 +31,31 @@ _MODULE_START = time.time()
|
||||
# 网络速率采样:记录上一次累计计数与时间,用于计算实时 bytes/s 用量
|
||||
_NET_LAST: dict = {"time": None, "sent": None, "recv": None}
|
||||
|
||||
# 采集结果短缓存(多标签页 / SSE 每 2s 循环共用一份结果)
|
||||
# 采集结果短缓存(多标签页 / SSE 循环共用一份结果)
|
||||
_DASH_CACHE: dict = {"time": 0.0, "data": None}
|
||||
_DASH_CACHE_TTL = 1.0
|
||||
_DASH_CACHE_TTL = 2.0
|
||||
|
||||
# 进程对象与 CPU 采样必须跨调用复用:psutil 的非阻塞模式(interval=None)
|
||||
# 是「与上一次调用」求差,换对象或首次调用只会拿到 0.0。
|
||||
_PROC = psutil.Process()
|
||||
psutil.cpu_percent(None, True) # 预热 CPU 采样(首次调用恒为 0.0)
|
||||
_PROC.cpu_percent(None) # 预热进程采样
|
||||
|
||||
# bot 昵称(get_login_info)基本不变,长缓存避免每轮都打一次 OneBot API
|
||||
_LOGIN_CACHE: dict[str, tuple[float, str]] = {}
|
||||
_LOGIN_TTL = 600.0
|
||||
|
||||
|
||||
def _cpu_sync() -> dict:
|
||||
try:
|
||||
per_core = psutil.cpu_percent(0.3, True)
|
||||
per_core = psutil.cpu_percent(None, True)
|
||||
except TypeError:
|
||||
# 兼容部分平台不接受 percpu 参数
|
||||
per_core = []
|
||||
if per_core:
|
||||
percent = sum(per_core) / len(per_core)
|
||||
else:
|
||||
percent = psutil.cpu_percent(0.3)
|
||||
percent = psutil.cpu_percent(None)
|
||||
info: dict = {
|
||||
"percent": round(float(percent), 1),
|
||||
"per_core": [round(float(x), 1) for x in (per_core or [])],
|
||||
@@ -84,8 +97,8 @@ def _memory_sync() -> dict:
|
||||
|
||||
def _process_sync() -> dict:
|
||||
try:
|
||||
proc = psutil.Process()
|
||||
cpu = proc.cpu_percent(0.2)
|
||||
proc = _PROC
|
||||
cpu = proc.cpu_percent(None)
|
||||
info = {
|
||||
"pid": proc.pid,
|
||||
"name": proc.name(),
|
||||
@@ -253,13 +266,18 @@ async def _collect_bots() -> list:
|
||||
"msg_sent": None,
|
||||
}
|
||||
if ws_connected:
|
||||
try:
|
||||
login = await bot.get_login_info()
|
||||
item["nick"] = login.get("nickname") or item["nick"]
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.warning(
|
||||
f"获取登录信息失败({bot.self_id}): {type(e).__name__}: {e}"
|
||||
)
|
||||
cached = _LOGIN_CACHE.get(bot.self_id)
|
||||
if cached and time.time() - cached[0] < _LOGIN_TTL:
|
||||
item["nick"] = cached[1]
|
||||
else:
|
||||
try:
|
||||
login = await bot.get_login_info()
|
||||
item["nick"] = login.get("nickname") or item["nick"]
|
||||
_LOGIN_CACHE[bot.self_id] = (time.time(), item["nick"])
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.warning(
|
||||
f"获取登录信息失败({bot.self_id}): {type(e).__name__}: {e}"
|
||||
)
|
||||
try:
|
||||
status = await bot.get_status()
|
||||
item["online"] = status.get("online")
|
||||
|
||||
Reference in New Issue
Block a user