collect_metrics.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. #!/usr/bin/env python3
  2. """采集本机运行指标 → JSON (跨平台: Linux/macOS/Windows-WSL)"""
  3. # 数据根目录可通过 FMODE_DATA_DIR 覆盖(默认 ~/.fmode 或 /opt/data)
  4. import json, os, subprocess, datetime, socket, sys, time
  5. DATA_DIR = os.environ.get("FMODE_DATA_DIR") or (
  6. "/opt/data" if os.path.isdir("/opt/data/profiles") else os.path.expanduser("~/.hermes")
  7. )
  8. WORK = os.path.join(DATA_DIR, ".fmode-harness-agent")
  9. def sh(cmd, timeout=15):
  10. try:
  11. return subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout).stdout.strip()
  12. except Exception as e:
  13. return f"ERR:{e}"
  14. def collect():
  15. now = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+08:00")
  16. data = {
  17. "timestamp": now,
  18. "agent": os.environ.get("AGENT_NAME", socket.gethostname()),
  19. "host": {
  20. "platform": sys.platform,
  21. "hostname": socket.gethostname(),
  22. },
  23. "resources": {},
  24. "concurrency": {},
  25. "services": {},
  26. "events": {}
  27. }
  28. # --- CPU/内存 (跨平台: /proc/meminfo + uptime) ---
  29. try:
  30. meminfo = {}
  31. for line in open("/proc/meminfo"):
  32. k, v = line.split(":")
  33. meminfo[k.strip()] = int(v.strip().split()[0]) # kB
  34. total = meminfo["MemTotal"]
  35. avail = meminfo["MemAvailable"]
  36. data["resources"]["memory"] = {
  37. "total_mb": round(total / 1024),
  38. "used_mb": round((total - avail) / 1024),
  39. "percent": round((total - avail) / total * 100, 1)
  40. }
  41. load = open("/proc/loadavg").read().split()
  42. data["resources"]["cpu_load"] = {"m1": float(load[0]), "m5": float(load[1]), "m15": float(load[2])}
  43. # CPU%
  44. cpu = sh("top -bn1 | grep 'Cpu(s)' | awk '{print $2+$4}'")
  45. data["resources"]["cpu_percent"] = round(float(cpu), 1) if cpu and not cpu.startswith("ERR") else None
  46. except Exception as e:
  47. data["resources"]["error"] = str(e)[:100]
  48. # --- 磁盘 (工作区所在分区) ---
  49. disk = sh(f"df -B1 {WORK} | tail -1 | awk '{{print $2, $3, $5}}'")
  50. if disk and not disk.startswith("ERR"):
  51. t, u, p = disk.split()
  52. data["resources"]["disk"] = {"total_gb": round(int(t)/1e9,1), "used_gb": round(int(u)/1e9,1), "percent": p}
  53. # --- 网络 (累计上下行, 取主网卡) ---
  54. net = sh("cat /proc/net/dev | awk 'NR>2 && $1!~/lo:/ {gsub(\":\",\"\",$1); print $1, $2, $10}' | sort -k2 -rn | head -1")
  55. if net and not net.startswith("ERR"):
  56. iface, rx, tx = net.split()
  57. data["resources"]["network"] = {"iface": iface, "rx_total_mb": round(int(rx)/1e6,1), "tx_total_mb": round(int(tx)/1e6,1)}
  58. # --- GPU (有则记) ---
  59. gpu = sh("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits 2>/dev/null")
  60. if gpu:
  61. u, mu, mt = gpu.split(", ")
  62. data["resources"]["gpu"] = {"percent": int(u), "mem_used_mb": int(mu), "mem_total_mb": int(mt)}
  63. # --- 并发: profile / session / subagent ---
  64. profiles = sh("ls %s/profiles/ 2>/dev/null | wc -l" % DATA_DIR)
  65. data["concurrency"]["profiles_total"] = int(profiles) if profiles.isdigit() else None
  66. # 活跃session(hermes state.db最近30分钟)
  67. try:
  68. import sqlite3
  69. conn = sqlite3.connect("file:%s/state.db?mode=ro" % DATA_DIR, uri=True, timeout=5)
  70. cur = conn.cursor()
  71. cur.execute("SELECT COUNT(DISTINCT session_id) FROM messages WHERE timestamp > ?", (time.time()-1800,))
  72. data["concurrency"]["active_sessions_30min"] = cur.fetchone()[0]
  73. cur.execute("SELECT COUNT(*) FROM async_delegations WHERE status='running'")
  74. data["concurrency"]["running_subagents"] = cur.fetchone()[0]
  75. conn.close()
  76. except Exception:
  77. data["concurrency"]["active_sessions_30min"] = None
  78. # --- 服务存活 ---
  79. for svc, pat in [("gateway", "hermes gateway"), ("dashboard", "hermes-dashboard"), ("studio", "fmode-studio")]:
  80. out = sh(f"pgrep -f '{pat}' | wc -l")
  81. data["services"][svc] = "up" if out.isdigit() and int(out) > 0 else "down"
  82. # --- 事件: 最近1小时错误日志计数 ---
  83. errc = sh("find %s/logs -name '*.log' -mmin -60 -exec grep -ci 'error\\|fatal' {} + 2>/dev/null | awk -F: '{s+=$2} END {print s}'")
  84. data["events"]["errors_last_1h"] = int(errc) if errc.isdigit() else None
  85. rest = sh("grep -c 'Reconnected' %s/logs/gateway.log 2>/dev/null")
  86. data["events"]["wecom_reconnects_total"] = int(rest) if rest.isdigit() else None
  87. return data
  88. if __name__ == "__main__":
  89. import time
  90. out = os.path.join(WORK, "tmp")
  91. os.makedirs(out, exist_ok=True)
  92. d = collect()
  93. path = os.path.join(out, "agent-log.json")
  94. json.dump(d, open(path, "w"), ensure_ascii=False, indent=2)
  95. print(json.dumps(d, ensure_ascii=False)[:300])
  96. print(f"\nsaved: {path}")