collect_metrics.py 4.6 KB

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