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)