#!/usr/bin/env python3 """采集本机运行指标 → JSON (跨平台: Linux/macOS/Windows-WSL)""" import json, os, subprocess, datetime, socket, sys WORK = os.path.expanduser("~/.fmode-harness-agent") def sh(cmd, timeout=15): try: return subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout).stdout.strip() except Exception as e: return f"ERR:{e}" def collect(): now = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+08:00") data = { "timestamp": now, "agent": os.environ.get("AGENT_NAME", socket.gethostname()), "host": { "platform": sys.platform, "hostname": socket.gethostname(), }, "resources": {}, "concurrency": {}, "services": {}, "events": {} } # --- CPU/内存 (跨平台: /proc/meminfo + uptime) --- try: meminfo = {} for line in open("/proc/meminfo"): k, v = line.split(":") meminfo[k.strip()] = int(v.strip().split()[0]) # kB total = meminfo["MemTotal"] avail = meminfo["MemAvailable"] data["resources"]["memory"] = { "total_mb": round(total / 1024), "used_mb": round((total - avail) / 1024), "percent": round((total - avail) / total * 100, 1) } load = open("/proc/loadavg").read().split() data["resources"]["cpu_load"] = {"m1": float(load[0]), "m5": float(load[1]), "m15": float(load[2])} # CPU% cpu = sh("top -bn1 | grep 'Cpu(s)' | awk '{print $2+$4}'") data["resources"]["cpu_percent"] = round(float(cpu), 1) if cpu and not cpu.startswith("ERR") else None except Exception as e: data["resources"]["error"] = str(e)[:100] # --- 磁盘 (工作区所在分区) --- disk = sh(f"df -B1 {WORK} | tail -1 | awk '{{print $2, $3, $5}}'") if disk and not disk.startswith("ERR"): t, u, p = disk.split() data["resources"]["disk"] = {"total_gb": round(int(t)/1e9,1), "used_gb": round(int(u)/1e9,1), "percent": p} # --- 网络 (累计上下行, 取主网卡) --- net = sh("cat /proc/net/dev | awk 'NR>2 && $1!~/lo:/ {gsub(\":\",\"\",$1); print $1, $2, $10}' | sort -k2 -rn | head -1") if net and not net.startswith("ERR"): iface, rx, tx = net.split() data["resources"]["network"] = {"iface": iface, "rx_total_mb": round(int(rx)/1e6,1), "tx_total_mb": round(int(tx)/1e6,1)} # --- GPU (有则记) --- gpu = sh("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits 2>/dev/null") if gpu: u, mu, mt = gpu.split(", ") data["resources"]["gpu"] = {"percent": int(u), "mem_used_mb": int(mu), "mem_total_mb": int(mt)} # --- 并发: profile / session / subagent --- profiles = sh("ls /opt/data/profiles/ 2>/dev/null | wc -l") data["concurrency"]["profiles_total"] = int(profiles) if profiles.isdigit() else None # 活跃session(hermes state.db最近30分钟) try: import sqlite3 conn = sqlite3.connect("file:/opt/data/state.db?mode=ro", uri=True, timeout=5) cur = conn.cursor() cur.execute("SELECT COUNT(DISTINCT session_id) FROM messages WHERE timestamp > ?", (time.time()-1800,)) data["concurrency"]["active_sessions_30min"] = cur.fetchone()[0] cur.execute("SELECT COUNT(*) FROM async_delegations WHERE status='running'") data["concurrency"]["running_subagents"] = cur.fetchone()[0] conn.close() except Exception: data["concurrency"]["active_sessions_30min"] = None # --- 服务存活 --- for svc, pat in [("gateway", "hermes gateway"), ("dashboard", "hermes-dashboard"), ("studio", "fmode-studio")]: out = sh(f"pgrep -f '{pat}' | wc -l") data["services"][svc] = "up" if out.isdigit() and int(out) > 0 else "down" # --- 事件: 最近1小时错误日志计数 --- 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}'") data["events"]["errors_last_1h"] = int(errc) if errc.isdigit() else None rest = sh("grep -c 'Reconnected' /opt/data/logs/gateway.log 2>/dev/null") data["events"]["wecom_reconnects_total"] = int(rest) if rest.isdigit() else None return data if __name__ == "__main__": import time out = os.path.join(WORK, "tmp") os.makedirs(out, exist_ok=True) d = collect() path = os.path.join(out, "agent-log.json") json.dump(d, open(path, "w"), ensure_ascii=False, indent=2) print(json.dumps(d, ensure_ascii=False)[:300]) print(f"\nsaved: {path}")