Files
MoFin/archive/evolution-cleanup-20260821/meta_watchdog.py
T
xxm 5b9d46efc6 refactor: 归档策略进化模块+新建评估页面+API
- 归档 evolution/ + meta_growth/meta_watchdog/ab_research_daily
- docs/evolution-archive-readme.md: 归档说明(旧模块功能+替代方案)
- server.py: 新增 /api/research/effectiveness + effectiveness/summary + recommendation_log + execution_log
- static/effectiveness.html: 新评估页面(概览/详细评估/推荐记录/执行记录)
- 策略进化改为人驱动闭环(评估→用户决策→调整)
2026-08-21 02:47:38 +08:00

121 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""meta_watchdog.py — L4 自检系统的自检(看门狗的看门狗)
检查 L1-L3 各自检组件本身是否在正常运转:
- L1 functional_health_check: functional_health.json 是否 <20min(交易时段)
- L2 system_hygiene_audit: hygiene_report.json 是否 <26h(每日)
- L3 self_repair: repair_state.json 存在性 + cron 是否注册
- mofin_health 采集: mofin_health.json 是否 <20min(交易时段)
- XMPP 桥: :5805 是否可发(self_repair 的报备通道)
2026-08-13 删除 L0 agents_health_check 检查项:MoFin 无该组件,且 L1 functional_health_check 已覆盖健康检查功能,检查项是死代码)
任何一层死了 → 推 XMPP 点名(这是最后的兜底,必须直达用户)。
频率:每小时(cron)。输出 gateway/logs/meta_watchdog.json。
"""
import os, sys, json, subprocess
from datetime import datetime
OUT = '/home/hmo/MoFin/gateway/logs/meta_watchdog.json'
LAYERS = [
{"layer": "L1 functional_health", "file": "/home/hmo/MoFin/gateway/logs/functional_health.json",
"max_age_min": 25, "when": "trading",
"repair": "L1 cron 停摆,检查 hermes cron 引擎"},
{"layer": "L2 hygiene_audit", "file": "/home/hmo/MoFin/gateway/logs/hygiene_report.json",
"max_age_min": 26 * 60, "when": "always",
"repair": "L2 每日审计未跑,检查 hermes cron"},
{"layer": "L1.5 mofin_health采集", "file": "/home/hmo/web-dashboard/static/mofin_health.json",
"max_age_min": 25, "when": "trading",
"repair": "mofin_health.py 采集停摆"},
{"layer": "L3 self_repair", "file": "/home/hmo/MoFin/gateway/logs/repair_log.jsonl",
"max_age_min": None, "when": "meta",
"repair": "self_repair cron 未注册"},
]
def is_trading(now):
return now.weekday() < 5 and 9 <= now.hour <= 16
def main():
now = datetime.now()
trading = is_trading(now)
results = []
for L in LAYERS:
if L["when"] == "trading" and not trading:
results.append({"layer": L["layer"], "status": "skip", "reason": "非交易时段"})
continue
if L["when"] == "meta":
# 检查 self_repair 是否注册在 cron
try:
d = json.load(open('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json'))
jobs = d if isinstance(d, list) else d.get('jobs', [])
registered = any(j.get('script') == 'self_repair.py' and j.get('enabled', True) for j in jobs)
results.append({"layer": L["layer"],
"status": "ok" if registered else "fail",
"reason": "已注册" if registered else "未在 cron 注册"})
except Exception as e:
results.append({"layer": L["layer"], "status": "fail", "reason": str(e)[:60]})
continue
f = L["file"]
if not os.path.exists(f):
results.append({"layer": L["layer"], "status": "fail",
"reason": f"输出物不存在", "repair": L["repair"]})
continue
age_min = (now.timestamp() - os.path.getmtime(f)) / 60
if L["max_age_min"] and age_min > L["max_age_min"]:
results.append({"layer": L["layer"], "status": "fail",
"reason": f"输出物 {age_min/60:.1f}h 未更新(阈值 {L['max_age_min']}min",
"repair": L["repair"]})
else:
results.append({"layer": L["layer"], "status": "ok",
"reason": f"{age_min:.0f}min 前"})
# XMPP 桥(报备通道):只收 POSTGET 会 501,但任何 HTTP 响应都说明进程活着
try:
import urllib.request
urllib.request.urlopen('http://127.0.0.1:5805/', timeout=3)
results.append({"layer": "XMPP桥 :5805", "status": "ok", "reason": "可达"})
except urllib.error.HTTPError as e:
results.append({"layer": "XMPP桥 :5805", "status": "ok", "reason": f"可达(HTTP {e.code})"})
except Exception:
results.append({"layer": "XMPP桥 :5805", "status": "fail",
"reason": "不可达", "repair": "重启 xmpp-zhiwei"})
fails = [r for r in results if r["status"] == "fail"]
report = {
"generated_at": now.strftime("%Y-%m-%d %H:%M:%S"),
"status": "fail" if fails else "ok",
"layers": results,
}
os.makedirs(os.path.dirname(OUT), exist_ok=True)
with open(OUT, 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print(f"meta_watchdog: {report['status']}")
for r in results:
icon = {"ok": "✅", "fail": "❌", "skip": "⏭"}[r["status"]]
print(f" {icon} {r['layer']}: {r['reason']}")
if fails:
try:
import urllib.request
lines = [f"🚨 自检系统自检(L4兜底)发现 {len(fails)} 层异常:"]
for r in fails:
lines.append(f"❌ {r['layer']}: {r['reason']}")
if r.get('repair'):
lines.append(f" → 处置建议: {r['repair']}")
payload = json.dumps({'to': 'hmo@yoin.fun', 'body': '\n'.join(lines), 'type': 'chat'}).encode()
req = urllib.request.Request('http://127.0.0.1:5805/', data=payload,
headers={'Content-Type': 'application/json'})
urllib.request.urlopen(req, timeout=5)
print(' 📨 已推 XMPP(兜底直达)')
except Exception as e:
print(f' XMPP 失败: {e}')
if __name__ == '__main__':
main()