77 lines
2.5 KiB
Python
77 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
proc_perf.py — Snapshot a live process's CPU time and peak memory from /proc.
|
|
|
|
Usage: proc_perf.py <pid> <label> <out.json>
|
|
|
|
Reads /proc/<pid>/stat (utime+stime, in clock ticks) and /proc/<pid>/status
|
|
(VmHWM = peak resident set, VmRSS = current) *while the process is alive* — peak
|
|
RSS is only legible before the process exits, so the orchestrator calls this just
|
|
before tearing the stack down. Emits a small JSON record:
|
|
|
|
{"label": ..., "avail": true, "cpu_s": float, "peak_rss_kb": int,
|
|
"rss_kb": int, "threads": int}
|
|
|
|
If the process is already gone, writes {"label": ..., "avail": false}.
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
|
|
def _clk_tck():
|
|
try:
|
|
return os.sysconf("SC_CLK_TCK") or 100
|
|
except (ValueError, OSError):
|
|
return 100
|
|
|
|
|
|
def snapshot(pid):
|
|
stat_p = f"/proc/{pid}/stat"
|
|
status_p = f"/proc/{pid}/status"
|
|
if not os.path.exists(stat_p):
|
|
return {"avail": False}
|
|
try:
|
|
with open(stat_p) as f:
|
|
raw = f.read()
|
|
# The comm field is parenthesised and may contain spaces/parens, so split
|
|
# on the final ')': everything after it is space-separated, with state as
|
|
# the first token. utime/stime are overall fields 14/15 → indices 11/12
|
|
# of the post-')' tokens (pid + comm consumed before the split).
|
|
after = raw[raw.rindex(")") + 1:].split()
|
|
utime = int(after[11])
|
|
stime = int(after[12])
|
|
cpu_s = (utime + stime) / float(_clk_tck())
|
|
rec: dict = {"avail": True, "cpu_s": cpu_s}
|
|
with open(status_p) as f:
|
|
for line in f:
|
|
if line.startswith("VmHWM:"):
|
|
rec["peak_rss_kb"] = int(line.split()[1])
|
|
elif line.startswith("VmRSS:"):
|
|
rec["rss_kb"] = int(line.split()[1])
|
|
elif line.startswith("Threads:"):
|
|
rec["threads"] = int(line.split()[1])
|
|
return rec
|
|
except (OSError, ValueError, IndexError):
|
|
return {"avail": False}
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) != 4:
|
|
print("usage: proc_perf.py <pid> <label> <out.json>", file=sys.stderr)
|
|
sys.exit(2)
|
|
pid, label, out = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
rec = snapshot(pid)
|
|
rec["label"] = label
|
|
with open(out, "w") as f:
|
|
json.dump(rec, f)
|
|
if rec.get("avail"):
|
|
print(f"perf {label}: cpu={rec.get('cpu_s', 0):.2f}s "
|
|
f"peakRSS={rec.get('peak_rss_kb', 0)/1024:.1f}MB")
|
|
else:
|
|
print(f"perf {label}: unavailable (process gone)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|