70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Capture one local process's raw output, exit code, elapsed time and memory.
|
|
|
|
This wrapper samples RSS and private bytes; the values are observed maxima, not
|
|
proof of the true loading peak between samples.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import psutil
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--cwd", type=Path, required=True)
|
|
parser.add_argument("--stdout", type=Path, required=True)
|
|
parser.add_argument("--stderr", type=Path, required=True)
|
|
parser.add_argument("--report", type=Path, required=True)
|
|
parser.add_argument("--sample-interval-ms", type=int, default=100)
|
|
parser.add_argument("command", nargs=argparse.REMAINDER)
|
|
args = parser.parse_args()
|
|
if not args.command or args.sample_interval_ms < 10:
|
|
parser.error("a command and sample interval >= 10 ms are required")
|
|
command = args.command[1:] if args.command[0] == "--" else args.command
|
|
for path in (args.stdout, args.stderr, args.report):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
started = time.perf_counter()
|
|
with args.stdout.open("wb") as stdout, args.stderr.open("wb") as stderr:
|
|
child = subprocess.Popen(command, cwd=args.cwd, stdout=stdout, stderr=stderr)
|
|
process = psutil.Process(child.pid)
|
|
samples = 0
|
|
peak_rss = 0
|
|
peak_private = 0
|
|
while child.poll() is None:
|
|
try:
|
|
memory = process.memory_info()
|
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
break
|
|
peak_rss = max(peak_rss, memory.rss)
|
|
peak_private = max(peak_private, getattr(memory, "private", 0))
|
|
samples += 1
|
|
time.sleep(args.sample_interval_ms / 1000)
|
|
exit_code = child.wait()
|
|
report = {
|
|
"command": command,
|
|
"cwd": str(args.cwd),
|
|
"exit_code": exit_code,
|
|
"wall_s": time.perf_counter() - started,
|
|
"memory_sample_interval_ms": args.sample_interval_ms,
|
|
"memory_samples": samples,
|
|
"peak_sampled_rss_bytes": peak_rss,
|
|
"peak_sampled_private_bytes": peak_private,
|
|
"measurement_note": "sampled process values, not guaranteed loading peaks",
|
|
}
|
|
args.report.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
|
print(json.dumps(report, indent=2))
|
|
if exit_code:
|
|
raise SystemExit(exit_code)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|