uv run profile.py ~/.hermes/logs/agent.log
Created
May 25, 2026 18:23
-
-
Save chadbrewbaker/8277662fc3701e4b076731318caa5133 to your computer and use it in GitHub Desktop.
Simple Hermes Agent profiler to diagnose API call hangs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # /// script | |
| # dependencies =[ | |
| # "pandas", | |
| # "matplotlib", | |
| # ] | |
| # /// | |
| import re | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| import sys | |
| import json | |
| import glob | |
| import os | |
| from datetime import datetime | |
| # Path where your sessions are stored | |
| SESSION_DIR = os.path.expanduser("~/.hermes/sessions/") | |
| def get_session_context(log_ts): | |
| """Finds the session file covering the log_ts and grabs the last message.""" | |
| for session_file in glob.glob(os.path.join(SESSION_DIR, "*.json")): | |
| try: | |
| with open(session_file, 'r') as f: | |
| data = json.load(f) | |
| start = datetime.fromisoformat(data.get('session_start', '1970-01-01T00:00:00')) | |
| end = datetime.fromisoformat(data.get('last_updated', '1970-01-01T00:00:00')) | |
| # Check if our log timestamp falls in this session | |
| if start <= log_ts <= end: | |
| # Replace 'messages' with 'history' if your JSON uses that key | |
| messages = data.get('messages', []) | |
| last_msg = messages[-1].get('content', 'No content') if messages else "Empty session" | |
| return f"Model: {data.get('model', 'Unknown')}\nLast Context: {last_msg[:200]}..." | |
| except Exception: | |
| continue | |
| return "No matching session found." | |
| def parse_logs(log_file): | |
| data =[] | |
| # Regex to capture timestamp and duration from your log format | |
| pattern = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),\d+ INFO .*?(?:latency=|completed \()([\d\.]+)") | |
| with open(log_file, 'r') as f: | |
| for line in f: | |
| match = pattern.search(line) | |
| if match: | |
| ts = pd.to_datetime(match.group(1)) | |
| duration = float(match.group(2)) | |
| task_type = "API" if "latency=" in line else "Tool" | |
| data.append({'timestamp': ts, 'type': task_type, 'duration': duration}) | |
| return pd.DataFrame(data) | |
| if __name__ == "__main__": | |
| if len(sys.argv) < 2: | |
| print("Usage: uv run profile.py <path_to_agent.log>") | |
| sys.exit(1) | |
| df = parse_logs(sys.argv[1]) | |
| fig, ax = plt.subplots(figsize=(12, 6)) | |
| sc = ax.scatter(df['timestamp'], df['duration'], | |
| c=df['type'].map({'API': 'red', 'Tool': 'blue'}), | |
| picker=True, alpha=0.7) | |
| ax.set_title("Click a dot to see LLM context") | |
| ax.set_ylabel("Duration (s)") | |
| ax.grid(True) | |
| def on_pick(event): | |
| ind = event.ind[0] | |
| row = df.iloc[ind] | |
| context = get_session_context(row['timestamp']) | |
| print(f"\n{'='*40}\nSTALL DETAILS\n{'='*40}") | |
| print(f"Time: {row['timestamp']}\nType: {row['type']} ({row['duration']}s)") | |
| print(f"Context Snippet:\n{context}") | |
| fig.canvas.mpl_connect('pick_event', on_pick) | |
| plt.show() |
chadbrewbaker
commented
May 25, 2026
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment