"""Best-effort Linux process-tree observations without reading command payloads.""" from collections import Counter from pathlib import Path import os import resource import time class ProcessMetrics: def __init__(self, pid): self.pid = pid self.started = time.monotonic() self.initial = resource.getrusage(resource.RUSAGE_CHILDREN) self.peak_rss = self.peak_threads = self.peak_processes = self.peak_fds = 0 self.samples = 0 self.wait_channels = Counter() self.names = set() self.sample() def sample(self): pending, seen = [self.pid], set() rss = threads = fds = processes = 0 while pending: pid = pending.pop() if pid in seen: continue seen.add(pid) directory = Path("/proc") / str(pid) try: fields = (directory / "stat").read_text().rsplit(")", 1)[1].split() children = (directory / "task" / str(pid) / "children").read_text().split() pending.extend(map(int, children)) rss += int(fields[21]) * os.sysconf("SC_PAGE_SIZE") threads += int(fields[17]) fds += sum(1 for _ in (directory / "fd").iterdir()) processes += 1 self.names.add((directory / "comm").read_text().strip()) wait = (directory / "wchan").read_text().strip() if wait and wait != "0": self.wait_channels[wait] += 1 except (OSError, ValueError, IndexError): continue self.samples += 1 self.peak_rss = max(self.peak_rss, rss) self.peak_threads = max(self.peak_threads, threads) self.peak_processes = max(self.peak_processes, processes) self.peak_fds = max(self.peak_fds, fds) def finish(self): usage = resource.getrusage(resource.RUSAGE_CHILDREN) cpu = max(0, usage.ru_utime + usage.ru_stime - self.initial.ru_utime - self.initial.ru_stime) wall = time.monotonic() - self.started return { "ownerProcessGroup": self.pid, "wallSeconds": round(wall, 6), "reapedChildCpuSeconds": round(cpu, 6), "cpuPercent": round(100 * cpu / wall, 2) if wall else 0, "peakSampledRssBytes": self.peak_rss, "peakSampledThreads": self.peak_threads, "peakSampledProcesses": self.peak_processes, "peakSampledFileDescriptors": self.peak_fds, "samples": self.samples, "processNames": sorted(self.names), "waitChannels": dict(self.wait_channels.most_common(8)), "limits": "Sampled peaks may miss short-lived children; child CPU is wait4 accounting, not GIL or event-loop timing.", }