Skip to content

Commit a1b9281

Browse files
committed
Support native .pftrace output in merge_traces
Route merged events through write_track_event_trace when the output path ends in .pftrace, so merged multi-process traces can be viewed as native Perfetto TrackEvent protobufs instead of Chrome JSON only.
1 parent 4b2342d commit a1b9281

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

test/test_perfetto.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,34 @@ def test_track_event_conversion_keeps_back_to_back_slices_separate():
330330

331331
assert all(not stack for stack in open_stacks.values())
332332
assert rendered == {"a": (10_000, 20_000), "b": (20_000, 30_000)}
333+
334+
335+
def test_merge_traces_writes_native_pftrace(tmp_path):
336+
from perfetto.protos.perfetto.trace.perfetto_trace_pb2 import Trace
337+
338+
from transformer_nuggets.utils.merge_traces import merge_traces
339+
340+
inputs = []
341+
for idx in range(2):
342+
path = tmp_path / f"rank{idx}.json"
343+
events = [{"ph": "X", "name": f"op{idx}", "pid": 7, "tid": 3, "ts": 100 + idx, "dur": 5}]
344+
path.write_text(json.dumps({"traceEvents": events}))
345+
inputs.append(str(path))
346+
347+
output = tmp_path / "merged.pftrace"
348+
merge_traces(inputs, str(output), labels=["impl a", "impl b"], align_timestamps=True)
349+
350+
trace = Trace()
351+
trace.ParseFromString(output.read_bytes())
352+
process_names = {
353+
p.track_descriptor.process.process_name
354+
for p in trace.packet
355+
if p.HasField("track_descriptor") and p.track_descriptor.HasField("process")
356+
}
357+
assert {"impl a", "impl b"} <= process_names
358+
slice_names = {
359+
p.track_event.name
360+
for p in trace.packet
361+
if p.HasField("track_event") and p.track_event.name
362+
}
363+
assert {"op0", "op1"} <= slice_names

transformer_nuggets/utils/merge_traces.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import typer
1111

12+
from transformer_nuggets.utils.track_event import write_track_event_trace
13+
1214
app = typer.Typer(help="Merge per-rank Chrome/Perfetto traces into one file.")
1315

1416

@@ -31,6 +33,12 @@ def merge_traces(
3133
labels: list[str] | None = None,
3234
align_timestamps: bool = False,
3335
) -> None:
36+
"""Merge Chrome JSON traces into one multi-process trace.
37+
38+
Output format follows the ``output_path`` suffix: ``.pftrace`` writes a native
39+
Perfetto TrackEvent protobuf, anything else writes Chrome JSON (gzipped for
40+
``.gz``).
41+
"""
3442
merged_events: list[dict] = []
3543

3644
for idx, path in enumerate(input_paths):
@@ -62,18 +70,26 @@ def merge_traces(
6270
ev["id"] = ev["id"] + idx * (1 << 32)
6371
merged_events.append(ev)
6472

65-
with _open_trace(output_path, "w") as f:
66-
json.dump({"traceEvents": merged_events}, f, indent=0)
73+
if output_path.endswith(".pftrace"):
74+
write_track_event_trace(output_path, {"traceEvents": merged_events})
75+
else:
76+
with _open_trace(output_path, "w") as f:
77+
json.dump({"traceEvents": merged_events}, f, indent=0)
6778

6879

6980
@app.command()
7081
def main(
7182
traces: Annotated[
7283
list[Path], typer.Argument(help="Input trace files, one per rank, in rank order.")
7384
],
74-
output: Annotated[Path, typer.Option("-o", "--output", help="Output path.")] = Path(
75-
"merged_trace.json.gz"
76-
),
85+
output: Annotated[
86+
Path,
87+
typer.Option(
88+
"-o",
89+
"--output",
90+
help="Output path (.pftrace for native Perfetto, .json/.json.gz for Chrome JSON).",
91+
),
92+
] = Path("merged_trace.json.gz"),
7793
label: Annotated[
7894
list[str] | None,
7995
typer.Option("-l", "--label", help="Label for each trace (repeat for each file)."),

0 commit comments

Comments
 (0)