#!/usr/bin/env python3
"""Per time window: frame-time p50/p95 (present to present), GPU busy per frame (UNION of
workload intervals -- labels nest, so never a sum), and the top labelled passes.
python3 -I tools/perf/nsys_windows.py CAPTURE.sqlite [SECONDS=10] [TOP=4]
GPU busy ~= frame time means GPU-bound; far below it means the CPU or a sync is the limit.
"""
import collections, re, sqlite3, statistics as st, sys
db = sqlite3.connect(sys.argv[1])
W = float(sys.argv[2] if len(sys.argv) > 2 else 10) * 1e9
top = int(sys.argv[3]) if len(sys.argv) > 3 else 4
pres = [r[0] for r in db.execute(
"select v.start from VULKAN_API v join StringIds s on s.id=v.nameId "
"where s.value='vkQueuePresentKHR' order by v.start")]
rows = db.execute(
"select w.start, w.end, coalesce(s.value,'') from VULKAN_WORKLOAD w "
"left join StringIds s on s.id=w.textId order by w.start").fetchall()
t0, i, ws = pres[0], 0, pres[0]
while ws < pres[-1]:
we = ws + W
fp = [p for p in pres if ws <= p < we]
if len(fp) < 3:
ws = we
continue
ft = [(b - a) / 1e6 for a, b in zip(fp, fp[1:])]
nfr = len(ft)
busy, cs, ce, agg = 0, None, None, collections.Counter()
while i < len(rows) and rows[i][0] < fp[0]:
i += 1
j = i
while j < len(rows) and rows[j][0] < fp[-1]:
s, e, n = rows[j]
if cs is None or s > ce:
if cs is not None:
busy += ce - cs
cs, ce = s, e
else:
ce = max(ce, e)
if n:
agg[re.sub(r' \(L-?\d+\)', '', n)] += (e - s) / 1e6 / nfr
j += 1
if cs is not None:
busy += ce - cs
tops = ", ".join(f"{n[:26]} {v:.1f}" for n, v in agg.most_common(top))
print(f"{(ws - t0) / 1e9:6.0f}s frames {nfr:4d} frame p50 {st.median(ft):6.1f} "
f"p95 {sorted(ft)[int(.95 * nfr)]:6.1f} | GPU busy {busy / 1e6 / nfr:5.1f} ms/f | {tops}")
ws = we