fix: health check PID now advisory-only, remote services skip local port, add ?§ to task entries
This commit is contained in:
@@ -30,6 +30,7 @@ for svc in _SR:
|
||||
"fix_script": svc["script"],
|
||||
"fix_args": svc["args"],
|
||||
"fix_cwd": svc["workdir"],
|
||||
"remote": svc.get("remote", False),
|
||||
})
|
||||
|
||||
TODO_FILE = os.path.join(TEMP, "health_todos.jsonl")
|
||||
@@ -126,13 +127,17 @@ def main():
|
||||
entry["pid"] = pid if pid_ok else 0
|
||||
entry["pid_ok"] = pid_ok
|
||||
|
||||
# 2. Port check
|
||||
port_ok = port_open(svc["port"])
|
||||
# 2. Port check(远程服务跳过本地端口检测)
|
||||
is_remote = svc.get("remote", False)
|
||||
if is_remote:
|
||||
port_ok = True # 远程服务不检查本地端口
|
||||
else:
|
||||
port_ok = port_open(svc["port"])
|
||||
entry["port_ok"] = port_ok
|
||||
|
||||
# 3. HTTP health check
|
||||
# 3. HTTP health check(远程服务直接用 HTTP 判断)
|
||||
http_msg = ""
|
||||
if port_ok:
|
||||
if port_ok or is_remote:
|
||||
ok, detail = http_check(svc["health_url"])
|
||||
# accept 401/403 as healthy if configured
|
||||
if not ok and svc.get("accept_401") and ("401" in detail or "403" in detail):
|
||||
@@ -146,20 +151,21 @@ def main():
|
||||
entry["http_ok"] = False
|
||||
entry["http_detail"] = "port_closed"
|
||||
|
||||
healthy = (pid_ok or not pid_file) and port_ok and entry.get("http_ok", False)
|
||||
# 健康判定:port+HTTP 都 OK → 健康;PID 仅作为辅助信号,不阻塞判定
|
||||
primary_ok = port_ok and entry.get("http_ok", False)
|
||||
entry["status"] = "ok" if primary_ok else "fail"
|
||||
|
||||
if healthy:
|
||||
entry["status"] = "ok"
|
||||
if primary_ok:
|
||||
if not pid_ok:
|
||||
entry["pid_warning"] = True # PID 异常但服务在正常运行
|
||||
report["summary"]["ok"] += 1
|
||||
else:
|
||||
entry["status"] = "fail"
|
||||
report["summary"]["fail"] += 1
|
||||
dirty = True
|
||||
# 收集具体失败原因
|
||||
reasons = []
|
||||
if not pid_ok: reasons.append("pid_dead")
|
||||
if not port_ok: reasons.append("port_closed")
|
||||
if not entry.get("http_ok") and port_ok: reasons.append(f"http_{http_msg}")
|
||||
if not port_ok and not is_remote: reasons.append("port_closed")
|
||||
if not entry.get("http_ok"): reasons.append(f"http_{http_msg}")
|
||||
issue = f"异常: {' + '.join(reasons)}"
|
||||
write_todo(name, issue, svc["fix_script"], svc.get("fix_args", []), svc["fix_cwd"])
|
||||
|
||||
@@ -170,7 +176,9 @@ def main():
|
||||
# 只输出异常服务到 stdout(silent-by-default)
|
||||
for svc in report["services"]:
|
||||
if svc["status"] != "ok":
|
||||
print(f"[FAIL] {svc['name']}: PID={svc['pid_ok']} PORT={svc['port_ok']} HTTP={svc.get('http_detail','?')}")
|
||||
print(f"[FAIL] {svc['name']}: PORT={svc['port_ok']} HTTP={svc.get('http_detail','?')}")
|
||||
elif svc.get("pid_warning"):
|
||||
print(f"[WARN] {svc['name']}: PID异常但服务正常")
|
||||
else:
|
||||
# 全正常→完全静默(silent-by-default)
|
||||
pass
|
||||
|
||||
@@ -63,6 +63,7 @@ SERVICES = [
|
||||
"health_url": "http://192.168.1.246:8642/v1/health",
|
||||
"accept_401": True, # may need auth
|
||||
"depends_on": ["ejabberd"],
|
||||
"remote": True, # 服务运行在 Linux 246,不检查本地端口
|
||||
"log_files": lambda: [],
|
||||
"description": "Hermes Gateway (mohe@yoin.fun)",
|
||||
},
|
||||
@@ -76,6 +77,7 @@ SERVICES = [
|
||||
"health_url": "http://192.168.1.246:8643/v1/health",
|
||||
"accept_401": True,
|
||||
"depends_on": ["ejabberd"],
|
||||
"remote": True, # 服务运行在 Linux 246,不检查本地端口
|
||||
"log_files": lambda: [],
|
||||
"description": "Hermes Gateway (zhiwei@yoin.fun)",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"module": "daily_health",
|
||||
"version": "1.0",
|
||||
"purpose": "Tier 2 全面健康检查(每日 08:00)。在 Tier 1 基础上增加:磁盘空间检查、定时任务存活检查、数据新鲜度、跨服务依赖链检查、看门狗日志新鲜度。",
|
||||
"ui_location": "F Tab → 定时任务 → 日报健康检查(每日)",
|
||||
|
||||
"human_help": {
|
||||
"title": "Tier 2 每日健康检查",
|
||||
"description": [
|
||||
"每天早上 8:00 由 Windows Task Scheduler 触发,生成全面系统健康日报。",
|
||||
"检查范围:服务状态(PID+端口+HTTP)、磁盘空间(C盘)、定时任务存活、看门狗日志新鲜度。",
|
||||
"输出结构化 JSON 报告供 Dashboard 和人工审阅。",
|
||||
"发现异常时生成推荐动作清单。"
|
||||
],
|
||||
"usage": [
|
||||
"报告文件:gateway/temp/last_daily_health.json",
|
||||
"日志:gateway/logs/daily_health_report.log",
|
||||
"Dashboard 通过 /api/monitor 读取报告显示 Tier2 统计"
|
||||
],
|
||||
"troubleshooting": [
|
||||
"如果定时任务未运行:检查 Windows Task Scheduler → agents-daily-health",
|
||||
"磁盘警告阈值:剩余 < 10GB 警告,< 2GB 严重",
|
||||
"看门狗日志超过 1 小时未更新 → 可能看门狗已死"
|
||||
],
|
||||
"related": "依赖 service_registry.py(服务定义); 参考 agents_health_check.py(Tier1 检查逻辑)"
|
||||
},
|
||||
|
||||
"ai_spec": {
|
||||
"dependencies": [
|
||||
"service_registry.py — 服务定义",
|
||||
"Python 3.10",
|
||||
"Windows Task Scheduler — 每天 08:00 触发",
|
||||
"wmic — Windows 磁盘空间查询"
|
||||
],
|
||||
"constraints": [
|
||||
"只在 Windows 上运行(使用 tasklist/netstat/wmic)",
|
||||
"磁盘检查仅针对 C: 盘",
|
||||
"定时任务检查通过 schtasks /Query 验证"
|
||||
],
|
||||
"related_files": [
|
||||
"gateway/scripts/agents_daily_health.py — 本脚本",
|
||||
"gateway/scripts/service_registry.py — 服务注册表",
|
||||
"gateway/scripts/specs/daily_health.json — 本 spec"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"module": "health_check",
|
||||
"version": "1.0",
|
||||
"purpose": "Tier 1 快速健康检查(每5分钟)。对 service_registry 中所有注册服务做三合一检查:PID存活 → 端口监听 → HTTP /health。异常时写 TODO 给 self_todo_executor 消费。正常时静默。",
|
||||
"ui_location": "F Tab → 定时任务 → 健康检查(5min)",
|
||||
|
||||
"human_help": {
|
||||
"title": "Tier 1 健康检查",
|
||||
"description": [
|
||||
"每 5 分钟由 Windows Task Scheduler 触发,检查所有注册服务的运行状态。",
|
||||
"检查项:端口监听(netstat)+ HTTP /health 端点响应。远程服务跳过本地端口检测。",
|
||||
"PID 检查仅作为辅助信号——端口+HTTP 都正常即判定为健康。",
|
||||
"异常服务会写入 health_todos.jsonl,由自修复执行器消费。",
|
||||
"全正常时静默运行,不输出任何噪音。"
|
||||
],
|
||||
"usage": [
|
||||
"报告文件:gateway/temp/last_health_check.json",
|
||||
"TODO 文件:gateway/temp/health_todos.jsonl",
|
||||
"日志:gateway/logs/health_check_report.log",
|
||||
"Dashboard 通过 /api/monitor 读取报告显示 Tier1 统计"
|
||||
],
|
||||
"troubleshooting": [
|
||||
"如果定时任务未运行:检查 Windows Task Scheduler → agents-health-check",
|
||||
"如果报告未更新:检查 C:\Users\hmo\AppData\Local\Programs\Python\Python310\python.exe 是否可用",
|
||||
"如果 TODO 堆积:检查 self_todo_executor 是否正常消费"
|
||||
],
|
||||
"related": "依赖 service_registry.py(服务定义); 输出给 self_todo_executor.py(修复消费); 报告被 dashboard.py 读取"
|
||||
},
|
||||
|
||||
"ai_spec": {
|
||||
"dependencies": [
|
||||
"service_registry.py — 服务定义(名称/端口/health URL/fix_script)",
|
||||
"Python 3.10 (C:\\Users\\hmo\\AppData\\Local\\Programs\\Python\\Python310\\python.exe)",
|
||||
"Windows Task Scheduler — 每5分钟触发"
|
||||
],
|
||||
"constraints": [
|
||||
"PID 检查不是硬性失败条件——端口+HTTP OK 即视为健康",
|
||||
"远程服务(remote:true)跳过本地 netstat 端口检测,直接用 HTTP 判断",
|
||||
"静默原则:全正常时零输出;仅异常时写 TODO + 打印报告"
|
||||
],
|
||||
"related_files": [
|
||||
"gateway/scripts/agents_health_check.py — 本脚本",
|
||||
"gateway/scripts/service_registry.py — 服务注册表",
|
||||
"gateway/scripts/self_todo_executor.py — TODO 消费者",
|
||||
"gateway/scripts/specs/health_check.json — 本 spec"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"module": "todo_executor",
|
||||
"version": "1.0",
|
||||
"purpose": "TODO 自修复执行器(每10分钟)。轮询 health_todos.jsonl 中 status=pending 的条目,执行 fix_script 重启异常服务。成功标记 completed,失败 escalation 到 XMPP 群聊。",
|
||||
"ui_location": "F Tab → 定时任务 → TODO自修复",
|
||||
|
||||
"human_help": {
|
||||
"title": "TODO 自修复执行器",
|
||||
"description": [
|
||||
"每 10 分钟由 Windows Task Scheduler 触发,自动修复健康检查发现的异常。",
|
||||
"读取 health_todos.jsonl 中的 pending 条目,执行对应的 fix_script。",
|
||||
"修复成功 → 标记 completed。",
|
||||
"修复失败 → 通过 XMPP HTTP 桥(:5802/send)发送告警到群聊。",
|
||||
"特殊情况:如果错误信息为 'another instance is already running',说明服务已在正常运行,标记 completed 跳过。"
|
||||
],
|
||||
"usage": [
|
||||
"TODO 文件:gateway/temp/health_todos.jsonl(输入+输出)",
|
||||
"日志:gateway/logs/todo_executor.log",
|
||||
"告警通道:HTTP POST → 127.0.0.1:5802/send → XMPP 群聊"
|
||||
],
|
||||
"troubleshooting": [
|
||||
"如果 TODO 堆积不减少:检查 agents-todo-executor 定时任务是否运行",
|
||||
"如果修复失败:检查 fix_script 路径是否存在(远程服务的 fix_script 为 null,无法自动修复)",
|
||||
"如果告警未发出:检查 xmpp_bot HTTP 桥 :5802/send 是否可达"
|
||||
],
|
||||
"related": "消费 agents_health_check.py 的 TODO 输出; 通过 xmpp_bot HTTP 桥发送告警"
|
||||
},
|
||||
|
||||
"ai_spec": {
|
||||
"dependencies": [
|
||||
"health_todos.jsonl — TODO 输入(由 agents_health_check.py 写入)",
|
||||
"xmpp_bot HTTP 桥 (:5802/send) — 告警输出通道",
|
||||
"Python 3.10",
|
||||
"Windows Task Scheduler — 每10分钟触发"
|
||||
],
|
||||
"constraints": [
|
||||
"只修复有 fix_script 的服务(远程服务无本地脚本,无法自动修复)",
|
||||
"already_running 错误视为成功(服务已在运行)",
|
||||
"单次执行超时 30s — 大脚本可能被截断"
|
||||
],
|
||||
"related_files": [
|
||||
"gateway/scripts/self_todo_executor.py — 本脚本",
|
||||
"gateway/scripts/agents_health_check.py — TODO 生产者",
|
||||
"gateway/scripts/specs/todo_executor.json — 本 spec"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -176,8 +176,9 @@ async function fHealth(){try{
|
||||
M.dashboard={n:'管理门户',l:'辅助服务',i:'Dashboard无法访问',spec:'dashboard'};
|
||||
M.watchdog={n:'进程看门狗',l:'辅助服务',i:'异常进程无自动恢复'};
|
||||
M.ejabberd={n:'XMPP服务器',l:'通信层',i:'所有XMPP连接不可用',spec:'ejabberd'};
|
||||
M['agents-health-check']={n:'健康检查(5min)',l:'定时任务',i:'5分钟级健康检测未运行'};
|
||||
M['agents-daily-health']={n:'日报健康检查(每日)',l:'定时任务',i:'日报统计未运行'};
|
||||
M['agents-health-check']={n:'健康检查(5min)',l:'定时任务',i:'5分钟级健康检测未运行',spec:'health_check'};
|
||||
M['agents-daily-health']={n:'日报健康检查(每日)',l:'定时任务',i:'日报统计未运行',spec:'daily_health'};
|
||||
M['agents-todo-executor']={n:'TODO自修复(10min)',l:'定时任务',i:'服务异常无法自动恢复',spec:'todo_executor'};
|
||||
var layerOrder=['通信层','AI网关','辅助服务','定时任务'];
|
||||
|
||||
var h='<h2>F 系统健康<span class="help-btn" onclick="showModuleHelp(\'health\',\'human\')" title="使用说明">?</span><span class="help-btn ai" onclick="showModuleHelp(\'health\',\'ai\')" title="AI Spec">§</span></h2>';
|
||||
@@ -263,11 +264,14 @@ async function fHealth(){try{
|
||||
h+='<div class=section><h2 style=margin-bottom:8px>定时任务</h2>';
|
||||
for(var i=0;i<mD.tasks.length;i++){var t=mD.tasks[i];
|
||||
var m=M[t.name]||{n:t.name};
|
||||
var ms=m.spec||'';
|
||||
var tk=t.status==='ok'||t.status==='timer_ok'||t.status==='cron_ok'||t.status==='schtasks_ok';
|
||||
var tn=t.status==='not_deployed';
|
||||
h+='<div style="display:flex;align-items:center;gap:8px;padding:4px 8px;margin:2px 0">';
|
||||
h+='<span class="sd '+(tk?'g':tn?'y':'r')+'"></span>';
|
||||
h+='<span style=flex:1>'+esc(m.n)+'</span>';
|
||||
if(ms){h+='<span class="help-btn" onclick="showModuleHelp(\''+ms+'\',\'human\')" title="使用说明">?</span>';
|
||||
h+='<span class="help-btn ai" onclick="showModuleHelp(\''+ms+'\',\'ai\')" title="AI Spec">§</span>';}
|
||||
h+='<span class="tag '+(tk?'g':tn?'y':'r')+'">'+(tk?'正常':tn?'未部署':'异常')+'</span></div>';}
|
||||
h+='</div>';}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user