Files
InstanceBrick--nightcap/tests/summarize_profile_trace_test.py
T
vii 3bba795ea3 feat(profile): add --profile Chrome trace of Vulkan adapter work
Records presents, GPU waits, submits, ETC2 decode, and pipeline and shader creation for ui.perfetto.dev. Adds a benchmark guide and a trace summary script.
2026-09-16 21:22:30 -05:00

118 lines
4.1 KiB
Python

#!/usr/bin/env python3
# Copyright 2026 Mocktail Project Authors
# Licensed under the Apache License, Version 2.0.
from __future__ import annotations
import contextlib
import importlib.util
import io
import json
from pathlib import Path
import sys
import tempfile
import unittest
SCRIPT = Path(__file__).parents[1] / "scripts/summarize_profile_trace.py"
SPEC = importlib.util.spec_from_file_location("summarize_profile_trace",
SCRIPT)
assert SPEC is not None and SPEC.loader is not None
summary = importlib.util.module_from_spec(SPEC)
# Dataclasses resolve their module through sys.modules.
sys.modules[SPEC.name] = summary
SPEC.loader.exec_module(summary)
def frame(ts_us: int, interval_ms: float) -> dict:
return {"name": "frame interval (ms)", "ph": "C", "ts": ts_us,
"pid": 1, "tid": 1, "args": {"value": interval_ms}}
def slice_event(name: str, dur_us: int, ts_us: int = 0) -> dict:
return {"name": name, "cat": "test", "ph": "X", "ts": ts_us,
"dur": dur_us, "pid": 1, "tid": 1}
class SummarizeTest(unittest.TestCase):
def test_frame_interval_percentiles_use_nearest_rank(self) -> None:
events = [frame(index, float(index)) for index in range(1, 101)]
result = summary.summarize(events)
frames = result.frames
self.assertEqual(frames.count, 100)
self.assertAlmostEqual(frames.mean, 50.5)
self.assertEqual(frames.p50, 50.0)
self.assertEqual(frames.p95, 95.0)
self.assertEqual(frames.p99, 99.0)
self.assertEqual(frames.max, 100.0)
def test_slices_are_grouped_by_name_in_milliseconds(self) -> None:
events = [
slice_event("vkQueuePresentKHR", 2_000),
slice_event("vkQueuePresentKHR", 4_000),
slice_event("vkCreateGraphicsPipelines", 30_000),
]
result = summary.summarize(events)
present = result.slices["vkQueuePresentKHR"]
self.assertEqual(present.count, 2)
self.assertAlmostEqual(present.total, 6.0)
self.assertAlmostEqual(present.mean, 3.0)
self.assertAlmostEqual(present.max, 4.0)
self.assertEqual(result.slices["vkCreateGraphicsPipelines"].count, 1)
def test_other_events_are_ignored(self) -> None:
events = [
{"name": "thread", "ph": "M", "ts": 0, "pid": 1, "tid": 1},
{"name": "other counter", "ph": "C", "ts": 0, "pid": 1, "tid": 1,
"args": {"value": 5.0}},
]
result = summary.summarize(events)
self.assertIsNone(result.frames)
self.assertEqual(result.slices, {})
def test_markdown_puts_each_trace_in_its_own_column(self) -> None:
before = summary.summarize([frame(1, 20.0), slice_event("a", 1_000)])
after = summary.summarize([frame(1, 10.0), slice_event("a", 500)])
table = summary.to_markdown([("before.json", before),
("after.json", after)])
lines = table.splitlines()
self.assertEqual(lines[0], "| metric | before.json | after.json |")
self.assertIn("| frame interval p50 (ms) | 20.00 | 10.00 |", lines)
self.assertIn("| a mean (ms) | 1.00 | 0.50 |", lines)
def test_markdown_marks_missing_values(self) -> None:
with_slice = summary.summarize([slice_event("a", 1_000)])
without = summary.summarize([])
table = summary.to_markdown([("one", with_slice), ("two", without)])
self.assertIn("| a count | 1 | - |", table.splitlines())
self.assertIn("| frames | - | - |", table.splitlines())
def test_main_reads_trace_files(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "trace.json"
path.write_text(json.dumps([frame(1, 16.0),
slice_event("b", 2_000)]))
output = io.StringIO()
with contextlib.redirect_stdout(output):
code = summary.main([str(path)])
self.assertEqual(code, 0)
self.assertIn("| frame interval p99 (ms) | 16.00 |",
output.getvalue())
if __name__ == "__main__":
unittest.main()