Download src/codex_dashboard/processes.py from smlflg/CodexDashBoard: direct link, hf CLI and curl.
- Browser
- Download file 4.84 kB
-
https://huggingface.co/smlflg/CodexDashBoard/resolve/main/src/codex_dashboard/processes.py
- Command line
-
hf download hf://smlflg/CodexDashBoard/src/codex_dashboard/processes.py
-
curl -L -o processes.py https://huggingface.co/smlflg/CodexDashBoard/resolve/main/src/codex_dashboard/processes.py
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")) | |
| 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" | |