File size: 3,001 Bytes
3fd1a35 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | #!/usr/bin/env python3
"""Read-only low-rate CPU/NPU sampling of an already running inference process."""
import argparse
import json
import os
from pathlib import Path
import re
import statistics
import time
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--pid", type=int, required=True)
p.add_argument("--duration", type=float, default=30)
p.add_argument("--interval", type=float, default=0.1)
p.add_argument("--output", type=Path, required=True)
a = p.parse_args()
if a.duration <= 0 or a.interval <= 0:
p.error("duration and interval must be positive")
hz = os.sysconf("SC_CLK_TCK")
base = Path(f"/proc/{a.pid}")
def ticks(path):
text = path.read_text()
fields = text[text.rfind(")") + 2:].split()
return {"cpu": int(fields[11]) + int(fields[12]),
"minflt": int(fields[7]), "majflt": int(fields[9])}
start = time.monotonic()
before = ticks(base / "stat")
thread_before = {x.name: ticks(x / "stat")["cpu"] for x in (base / "task").iterdir()}
samples = []
deadline = start
while time.monotonic() - start < a.duration:
sample = {"elapsed_s": time.monotonic() - start, "timestamp": time.time()}
sample["npu_load_pct"] = [int(x) for x in re.findall(
r"Core\d+:\s*(\d+)%", Path("/sys/kernel/debug/rknpu/load").read_text())]
samples.append(sample)
deadline += a.interval
time.sleep(max(0, deadline - time.monotonic()))
elapsed = time.monotonic() - start
after = ticks(base / "stat")
threads = []
for thread in (base / "task").iterdir():
if thread.name in thread_before:
threads.append({"tid": int(thread.name), "name": (thread / "comm").read_text().strip(),
"cpu_pct": 100 * (ticks(thread / "stat")["cpu"] - thread_before[thread.name]) / hz / elapsed})
valid = [x["npu_load_pct"] for x in samples if len(x["npu_load_pct"]) == 3]
summary = {"pid": a.pid, "elapsed_s": elapsed,
"process_cpu_pct": 100 * (after["cpu"] - before["cpu"]) / hz / elapsed,
"minor_faults": after["minflt"] - before["minflt"],
"major_faults": after["majflt"] - before["majflt"],
"threads": sorted(threads, key=lambda x: -x["cpu_pct"]),
"npu_mean_pct": [statistics.mean(x[i] for x in valid) for i in range(3)] if valid else [],
"npu_peak_pct": [max(x[i] for x in valid) for i in range(3)] if valid else [],
"npu_all_zero_fraction": sum(not any(x) for x in valid) / len(valid) if valid else None,
"frequencies_khz": {f"cpu{i}": Path(f"/sys/devices/system/cpu/cpufreq/policy{i}/scaling_cur_freq").read_text().strip() for i in (4, 6)},
"npu_frequency_hz": Path("/sys/class/devfreq/fdab0000.npu/cur_freq").read_text().strip(),
"cpu_temperature_millic": Path("/sys/class/thermal/thermal_zone0/temp").read_text().strip()}
a.output.parent.mkdir(parents=True, exist_ok=True)
a.output.write_text(json.dumps({"summary": summary, "samples": samples}, indent=2) + "\n")
print(json.dumps(summary, indent=2), flush=True)
|