smlflg's picture
Initial public upload from Projekte/CodexDashBoard
981aa5d verified
Raw History Blame Contribute Delete
4.84 kB
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import os
import signal
import subprocess
import time
PROCESS_KEYWORDS = (
"claudecodepanel",
"claude-code",
"claude code",
"claude",
"codex",
"opencode",
"mcp-server",
"server-filesystem",
"server-memory",
"server-github",
"gemini-mcp",
"gemini",
)
STALE_SECONDS = 24 * 3600
CLK_TCK = os.sysconf(os.sysconf_names.get("SC_CLK_TCK", "SC_CLK_TCK"))
@dataclass(frozen=True)
class ProcessInfo:
pid: int
name: str
command: str
cpu: float
ram_mb: float
tty: str
cwd: str
uptime_seconds: float
stale: bool
family: str = "Agent"
def _match_keyword(command: str) -> str | None:
lower = command.lower()
if "codex_dashboard" in lower or "codex-dashboard" in lower:
return None
for keyword in PROCESS_KEYWORDS:
if keyword in lower:
return keyword
return None
def _friendly_name(command: str, keyword: str) -> str:
first = Path(command.split()[0]).name if command.split() else keyword
if keyword == "claudecodepanel":
return "Claude Panel"
if keyword in {"claude", "claude-code", "claude code"}:
return "Claude Code"
if keyword == "codex":
return "Codex CLI"
if keyword == "opencode":
return "OpenCode"
if keyword in {"gemini", "gemini-mcp"}:
return "Gemini"
if keyword.startswith("server-"):
return keyword.replace("server-", "").title() + " MCP"
if keyword == "mcp-server":
return "MCP Server"
if first in {"node", "python", "python3"}:
return f"{first}/{keyword}"
return first or keyword
def _family(keyword: str) -> str:
if keyword in {"claude", "claude-code", "claude code", "claudecodepanel"}:
return "Claude"
if keyword == "codex":
return "Codex"
if keyword == "opencode":
return "OpenCode"
if "mcp" in keyword or keyword.startswith("server-"):
return "MCP"
if keyword.startswith("gemini"):
return "Gemini"
return "Agent"
def _read_cwd(pid: int) -> str:
try:
return os.readlink(f"/proc/{pid}/cwd")
except OSError:
return ""
def _read_uptime_seconds(pid: int, now: float | None = None) -> float:
now = time.time() if now is None else now
try:
stat_data = Path(f"/proc/{pid}/stat").read_text().split()
starttime_ticks = int(stat_data[21])
uptime = float(Path("/proc/uptime").read_text().split()[0])
except (OSError, ValueError, IndexError):
return 0.0
process_start = now - uptime + (starttime_ticks / CLK_TCK)
return max(0.0, now - process_start)
def parse_ps_aux_line(
line: str, *, now: float | None = None, read_proc_metadata: bool = True
) -> ProcessInfo | None:
parts = line.split(None, 10)
if len(parts) < 11:
return None
try:
pid = int(parts[1])
cpu = float(parts[2])
rss_kb = int(parts[5])
except ValueError:
return None
command = parts[10]
keyword = _match_keyword(command)
if keyword is None or pid == os.getpid():
return None
uptime_seconds = _read_uptime_seconds(pid, now) if read_proc_metadata else 0.0
return ProcessInfo(
pid=pid,
name=_friendly_name(command, keyword),
command=command,
cpu=cpu,
ram_mb=rss_kb / 1024,
tty="" if parts[6] == "?" else parts[6],
cwd=_read_cwd(pid) if read_proc_metadata else "",
uptime_seconds=uptime_seconds,
stale=uptime_seconds >= STALE_SECONDS,
family=_family(keyword),
)
def scan_processes(*, ps_output: str | None = None) -> list[ProcessInfo]:
read_proc_metadata = ps_output is None
if ps_output is None:
try:
result = subprocess.run(
["ps", "aux", "--no-headers"],
capture_output=True,
text=True,
timeout=5,
)
except (OSError, subprocess.TimeoutExpired):
return []
if result.returncode != 0:
return []
ps_output = result.stdout
processes = [
process
for line in ps_output.splitlines()
if (process := parse_ps_aux_line(
line, read_proc_metadata=read_proc_metadata
)) is not None
]
processes.sort(key=lambda item: (-int(item.stale), -item.ram_mb, item.name))
return processes
def terminate_process(pid: int) -> None:
os.kill(pid, signal.SIGTERM)
def format_uptime(seconds: float) -> str:
if seconds < 60:
return "<1m"
minutes = int(seconds // 60)
if minutes < 60:
return f"{minutes}m"
hours = minutes // 60
if hours < 48:
return f"{hours}h"
return f"{hours // 24}d"