@@ -18,31 +18,48 @@ def _open_trace(path: str, mode: str):
1818 return open (path , mode , encoding = "utf-8" )
1919
2020
21- def merge_traces (input_paths : list [str ], output_path : str ) -> None :
21+ def _get_min_ts (events : list [dict ]) -> float :
22+ return min (
23+ (ev ["ts" ] for ev in events if "ts" in ev and ev .get ("ph" ) != "M" ),
24+ default = 0.0 ,
25+ )
26+
27+
28+ def merge_traces (
29+ input_paths : list [str ],
30+ output_path : str ,
31+ labels : list [str ] | None = None ,
32+ align_timestamps : bool = False ,
33+ ) -> None :
2234 merged_events : list [dict ] = []
2335
24- for rank , path in enumerate (input_paths ):
36+ for idx , path in enumerate (input_paths ):
2537 with _open_trace (path , "r" ) as f :
2638 data = json .load (f )
2739
2840 events = data .get ("traceEvents" , data ) if isinstance (data , dict ) else data
2941
42+ ts_offset = _get_min_ts (events ) if align_timestamps else 0.0
43+ label = labels [idx ] if labels else f"Rank { idx } "
44+
3045 merged_events .append (
3146 {
3247 "ph" : "M" ,
3348 "name" : "process_name" ,
34- "pid" : rank ,
49+ "pid" : idx ,
3550 "tid" : 0 ,
36- "args" : {"name" : f"Rank { rank } " },
51+ "args" : {"name" : label },
3752 }
3853 )
3954
4055 for ev in events :
4156 if ev .get ("ph" ) == "M" and ev .get ("name" ) == "process_name" :
4257 continue
43- ev ["pid" ] = rank
58+ ev ["pid" ] = idx
59+ if align_timestamps and "ts" in ev :
60+ ev ["ts" ] = ev ["ts" ] - ts_offset
4461 if "id" in ev and ev .get ("ph" ) in ("s" , "t" , "f" ):
45- ev ["id" ] = ev ["id" ] + rank * (1 << 32 )
62+ ev ["id" ] = ev ["id" ] + idx * (1 << 32 )
4663 merged_events .append (ev )
4764
4865 with _open_trace (output_path , "w" ) as f :
@@ -57,14 +74,21 @@ def main(
5774 output : Annotated [Path , typer .Option ("-o" , "--output" , help = "Output path." )] = Path (
5875 "merged_trace.json.gz"
5976 ),
77+ label : Annotated [
78+ list [str ] | None ,
79+ typer .Option ("-l" , "--label" , help = "Label for each trace (repeat for each file)." ),
80+ ] = None ,
81+ align : Annotated [
82+ bool , typer .Option ("--align" , help = "Align timestamps so all traces start at t=0." )
83+ ] = False ,
6084):
6185 """Merge per-rank Chrome/Perfetto traces into a single multi-process Perfetto trace."""
6286 for p in traces :
6387 if not p .exists ():
6488 typer .echo (f"Error: { p } not found" , err = True )
6589 raise typer .Exit (1 )
6690
67- merge_traces ([str (p ) for p in traces ], str (output ))
91+ merge_traces ([str (p ) for p in traces ], str (output ), labels = label , align_timestamps = align )
6892 typer .echo (f"Merged { len (traces )} traces -> { output } " )
6993
7094
0 commit comments