mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-27 19:54:40 +00:00
87 lines
4.4 KiB
Python
87 lines
4.4 KiB
Python
"""Summarize this engine's line-oriented Chrome trace JSON without loading all events.
|
|
|
|
Usage: python tools/analyse_performance_trace.py CAPTURE.json > summary.json
|
|
CPU durations are inclusive; totals across nested/parallel spans are not frame time.
|
|
"""
|
|
import argparse
|
|
from array import array
|
|
from collections import defaultdict
|
|
import json
|
|
import math
|
|
|
|
|
|
def stats(values):
|
|
ordered = sorted(values)
|
|
if not ordered:
|
|
return {"count": 0}
|
|
def percentile(p):
|
|
return ordered[max(0, math.ceil(p * len(ordered)) - 1)]
|
|
return {"count": len(ordered), "total_ms": sum(ordered),
|
|
"mean_ms": sum(ordered) / len(ordered), "p50_ms": percentile(.5),
|
|
"p95_ms": percentile(.95), "p99_ms": percentile(.99), "max_ms": ordered[-1]}
|
|
|
|
|
|
def analyse(path):
|
|
spans = defaultdict(lambda: array("d"))
|
|
frames = array("d")
|
|
render_durations = defaultdict(lambda: array("d"))
|
|
metadata = {}
|
|
resources = {}
|
|
active_begin = None
|
|
active_end = None
|
|
count = 0
|
|
decoder = json.JSONDecoder()
|
|
with open(path, encoding="utf-8") as stream:
|
|
if stream.readline().strip() != '{"traceEvents":[':
|
|
raise ValueError("Expected a native engine recorder file (one event per line)")
|
|
for line in stream:
|
|
event, _ = decoder.raw_decode(line.lstrip())
|
|
count += 1
|
|
name = event.get("name", "")
|
|
if name in {"build", "loaded_map", "capture_complete", "adapter"}:
|
|
metadata[name] = event.get("args", {})
|
|
if event.get("ph") == "X":
|
|
spans[(event.get("cat", ""), name)].append(event["dur"] / 1000)
|
|
ts = event["ts"]
|
|
active_begin = ts if active_begin is None else min(active_begin, ts)
|
|
active_end = max(active_end or 0, ts + event["dur"])
|
|
if name == "frame_interval_ms":
|
|
frames.append(event["args"]["value"])
|
|
if event.get("ph") == "C" and name.startswith("render/") and name.endswith(("/elapsed_gpu", "/elapsed_cpu")):
|
|
render_durations[name].append(event["args"]["value"])
|
|
if name in {"assets", "render_resources", "configuration", "game_configuration"}:
|
|
resources.setdefault(name, {"first": event["args"], "last": event["args"], "samples": 0})
|
|
resources[name]["last"] = event["args"]
|
|
resources[name]["samples"] += 1
|
|
complete = metadata.get("capture_complete", {})
|
|
warnings = []
|
|
if not complete:
|
|
warnings.append("No completion marker: capture may be incomplete")
|
|
if complete.get("reason") == "size_limit":
|
|
warnings.append("Capture reached its byte limit before the requested duration")
|
|
if complete.get("dropped_events", 0):
|
|
warnings.append("Events were lost to the bounded writer queue")
|
|
if any("Enable the debug feature" in name for _, name in spans):
|
|
warnings.append("Release system names were unavailable; system attribution is invalid")
|
|
if complete.get("min_span_us", 0):
|
|
warnings.append("Short CPU slices are filtered; span totals/counts exclude them")
|
|
if not metadata.get("build", {}).get("gpu_requested", False):
|
|
warnings.append("CPU-only capture: no GPU execution durations were requested")
|
|
elif not any(name.endswith("/elapsed_gpu") for name in render_durations):
|
|
warnings.append("GPU diagnostics requested but no GPU duration samples found")
|
|
results = [{"category": cat, "name": name, **stats(values)} for (cat, name), values in spans.items()]
|
|
results.sort(key=lambda row: row["total_ms"], reverse=True)
|
|
return {"schema": 1, "event_count": count, "metadata": metadata, "warnings": warnings,
|
|
"active_cpu_span_window_ms": None if active_begin is None else (active_end-active_begin)/1000,
|
|
"frame_interval": stats(frames), "resource_samples": resources,
|
|
"cpu_spans_inclusive": results,
|
|
"render_pass_duration_counters": {name: stats(values) for name, values in render_durations.items()},
|
|
"interpretation": "Main-frame intervals are not display scanout times. CPU slices include waits; nested/parallel totals must not be summed into frame time. GPU support is not proof of GPU timing measurement."}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("capture")
|
|
options = parser.parse_args()
|
|
print(json.dumps(analyse(options.capture), indent=2))
|