| | |
| | from __future__ import annotations |
| | import os |
| | import sys |
| | import json |
| | import shutil |
| | import subprocess |
| | import platform |
| | import getpass |
| | import datetime |
| | from typing import Dict, Any, Optional, Sequence |
| |
|
| | |
| | try: |
| | import psutil |
| | except Exception: |
| | psutil = None |
| |
|
| |
|
| | def get_env_vars() -> Dict[str, str]: |
| | """Return all environment variables as a dict.""" |
| | |
| | return dict(os.environ) |
| |
|
| |
|
| | def _shorten_text(s: str, max_lines: int = 5, max_chars: int = 2000) -> str: |
| | """Trim text to the given number of lines and characters to keep output small.""" |
| | out = "\n".join(s.splitlines()[:max_lines]) |
| | if len(out) > max_chars: |
| | out = out[: max_chars - 3] + "..." |
| | return out |
| |
|
| |
|
| | def safe_run_version(cmd: Sequence[str], timeout: float = 3.0) -> Dict[str, Any]: |
| | """Try to run `cmd` and capture stdout/stderr (combined).""" |
| | try: |
| | p = subprocess.run( |
| | list(cmd), |
| | stdout=subprocess.PIPE, |
| | stderr=subprocess.STDOUT, |
| | timeout=timeout, |
| | check=False, |
| | ) |
| | out = p.stdout.decode(errors="replace").strip() |
| | short_out = _shorten_text(out) |
| | return {"ok": True, "rc": p.returncode, "output": short_out} |
| | except FileNotFoundError: |
| | return {"ok": False, "error": "not found"} |
| | except subprocess.TimeoutExpired: |
| | return {"ok": False, "error": "timeout"} |
| | except Exception as e: |
| | return {"ok": False, "error": str(e)} |
| |
|
| |
|
| | |
| | TOOL_VERSION_FLAGS = ("--version", "-V", "-v", "--help") |
| |
|
| |
|
| | def check_tools(tools: Sequence[str]) -> Dict[str, Dict[str, Any]]: |
| | """Check whether tools are callable (in PATH) and try to get version/help output.""" |
| | results: Dict[str, Dict[str, Any]] = {} |
| | for t in tools: |
| | found_path: Optional[str] = shutil.which(t) |
| | info: Dict[str, Any] = {"path": found_path, "callable": bool(found_path)} |
| | |
| | cmd_base = found_path or t |
| | |
| | for flag in TOOL_VERSION_FLAGS: |
| | res = safe_run_version([cmd_base, flag]) |
| | if res.get("ok") and res.get("output"): |
| | info["version_cmd"] = flag |
| | info["version_output"] = res.get("output") |
| | break |
| | else: |
| | |
| | res = safe_run_version([cmd_base]) |
| | if res.get("ok") and res.get("output"): |
| | info["version_cmd"] = None |
| | info["version_output"] = res.get("output") |
| | else: |
| | |
| | info["version_error"] = res.get("error") |
| | |
| | if res.get("error") == "not found": |
| | info["callable"] = False |
| | results[t] = info |
| | return results |
| |
|
| |
|
| | def _safe_get_user() -> Optional[str]: |
| | try: |
| | return getpass.getuser() |
| | except Exception: |
| | |
| | return os.environ.get("USER") or os.environ.get("USERNAME") |
| |
|
| |
|
| | def get_process_info() -> Dict[str, Any]: |
| | """Collect basic process information. If psutil is available, include richer info.""" |
| | info: Dict[str, Any] = {} |
| | info["platform"] = platform.system() |
| | info["platform_release"] = platform.release() |
| | info["platform_version"] = platform.version() |
| | info["machine"] = platform.machine() |
| | info["processor"] = platform.processor() |
| | info["python_version"] = platform.python_version() |
| | info["python_executable"] = sys.executable |
| | info["argv"] = sys.argv |
| | try: |
| | info["cwd"] = os.getcwd() |
| | except Exception: |
| | info["cwd"] = None |
| | info["pid"] = os.getpid() |
| | try: |
| | info["ppid"] = os.getppid() |
| | except Exception: |
| | info["ppid"] = None |
| | info["user"] = _safe_get_user() |
| |
|
| | |
| | if hasattr(os, "getuid"): |
| | try: |
| | info["uid"] = os.getuid() |
| | info["gid"] = os.getgid() |
| | if hasattr(os, "geteuid"): |
| | info["euid"] = os.geteuid() |
| | if hasattr(os, "getegid"): |
| | info["egid"] = os.getegid() |
| | except Exception: |
| | pass |
| |
|
| | |
| | if psutil is not None: |
| | try: |
| | p = psutil.Process(info["pid"]) |
| | with p.oneshot(): |
| | try: |
| | info["exe"] = p.exe() |
| | except Exception: |
| | info["exe"] = sys.executable |
| | try: |
| | info["cmdline"] = p.cmdline() |
| | except Exception: |
| | info["cmdline"] = sys.argv |
| | try: |
| | ct = p.create_time() |
| | info["create_time"] = datetime.datetime.fromtimestamp(ct).isoformat() |
| | except Exception: |
| | info["create_time"] = None |
| | try: |
| | info["cpu_percent"] = p.cpu_percent(interval=0.1) |
| | except Exception: |
| | info["cpu_percent"] = None |
| | try: |
| | mem = p.memory_info() |
| | info["memory_rss"] = getattr(mem, "rss", None) |
| | info["memory_vms"] = getattr(mem, "vms", None) |
| | except Exception: |
| | info["memory_rss"] = None |
| | info["memory_vms"] = None |
| | try: |
| | info["num_threads"] = p.num_threads() |
| | except Exception: |
| | info["num_threads"] = None |
| | try: |
| | info["open_files"] = [f.path for f in p.open_files()] |
| | except Exception: |
| | info["open_files"] = None |
| | |
| | try: |
| | |
| | conns = getattr(p, "connections", None) |
| | if callable(conns): |
| | conns_list = p.connections() |
| | else: |
| | net_conns = getattr(p, "net_connections", None) |
| | if callable(net_conns): |
| | conns_list = p.net_connections() |
| | else: |
| | conns_list = None |
| | if conns_list is None: |
| | info["connections"] = None |
| | else: |
| | info["connections"] = [str(c) for c in conns_list] |
| | except Exception: |
| | info["connections"] = None |
| | except Exception: |
| | |
| | pass |
| | else: |
| | |
| | info["exe"] = sys.executable |
| | info["cmdline"] = sys.argv |
| | info["create_time"] = None |
| | return info |
| |
|
| |
|
| | def main() -> int: |
| | try: |
| | env = get_env_vars() |
| | process = get_process_info() |
| | tools = check_tools(["curl", "wget"]) |
| | result = {"env": env, "process": process, "tools": tools} |
| | |
| | out = json.dumps(result, indent=2, ensure_ascii=False, sort_keys=False) |
| | print(out, flush=True) |
| | return 0 |
| | except Exception as e: |
| | |
| | try: |
| | print(json.dumps({"error": str(e)}), flush=True) |
| | except Exception: |
| | print(f"error: {e}", flush=True) |
| | return 2 |
| |
|
| |
|
| | main() |