aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/scripts/thread_cpu.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/thread_cpu.py')
-rwxr-xr-xscripts/thread_cpu.py60
1 files changed, 60 insertions, 0 deletions
diff --git a/scripts/thread_cpu.py b/scripts/thread_cpu.py
new file mode 100755
index 0000000..f22ad51
--- /dev/null
+++ b/scripts/thread_cpu.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+"""Per-thread CPU of a process over an interval, grouped by thread name (Linux /proc).
+Shows where a smoke run's CPU goes: UI/render thread, audio thread, OpenAL mixer,
+compositor, GC... (smoke_run.sh step `threads:<s>`).
+
+Usage: scripts/thread_cpu.py PID SECONDS [--top N]
+"""
+import argparse
+import os
+import time
+from collections import defaultdict
+
+
+def sample(pid):
+ """{tid: (name, cpu_ticks)} for every live thread of pid."""
+ out = {}
+ base = f"/proc/{pid}/task"
+ for tid in os.listdir(base):
+ try:
+ with open(f"{base}/{tid}/stat") as f:
+ stat = f.read()
+ except OSError:
+ continue # thread ended
+ name = stat[stat.index("(") + 1: stat.rindex(")")]
+ fields = stat[stat.rindex(")") + 2:].split()
+ out[tid] = (name, int(fields[11]) + int(fields[12])) # utime + stime
+ return out
+
+
+def per_name(before, after, seconds, hz):
+ """[(name, percent of one core, thread count)] busiest first; threads that started
+ during the interval count from zero."""
+ total = defaultdict(float)
+ count = defaultdict(int)
+ for tid, (name, ticks) in after.items():
+ start = before.get(tid, (name, 0))[1]
+ total[name] += (ticks - start) * 100.0 / hz / seconds
+ count[name] += 1
+ return sorted(((n, total[n], count[n]) for n in total), key=lambda r: -r[1])
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument("pid", type=int)
+ ap.add_argument("seconds", type=float)
+ ap.add_argument("--top", type=int, default=10)
+ args = ap.parse_args()
+ hz = os.sysconf("SC_CLK_TCK")
+ a = sample(args.pid)
+ time.sleep(args.seconds)
+ b = sample(args.pid)
+ rows = per_name(a, b, args.seconds, hz)
+ print(f"threads: {sum(r[1] for r in rows):.1f}% total over {args.seconds:g}s")
+ for name, pct, n in rows[: args.top]:
+ if pct >= 0.05:
+ print(f" {pct:5.1f}% {name}" + (f" ×{n}" if n > 1 else ""))
+
+
+if __name__ == "__main__":
+ main()