1313# See the License for the specific language governing permissions and
1414# limitations under the License.
1515
16- """Converts one (or several) .ninja_log files into chrome's about:tracing format
16+ """Converts one (or several) .ninja_log files into chrome's about:tracing format.
17+
18+ If clang -ftime-trace .json files are found adjacent to generated files they
19+ are embedded.
1720
1821Usage:
1922 ninja -C $BUILDDIR
@@ -22,13 +25,15 @@ Usage:
2225
2326from __future__ import print_function
2427import json
28+ import os
2529import optparse
2630import re
2731import sys
2832
2933
3034class Target :
31- """Represents a single line read for a .ninja_log file."""
35+ """Represents a single line read for a .ninja_log file. Start and end times
36+ are milliseconds."""
3237 def __init__ (self , start , end ):
3338 self .start = int (start )
3439 self .end = int (end )
@@ -76,17 +81,78 @@ class Threads:
7681 return len (self .workers ) - 1
7782
7883
79- def log_to_dicts (log , pid , show_all ):
84+ def read_events (trace , options ):
85+ """Reads all events from time-trace json file |trace|."""
86+ trace_data = json .load (trace )
87+
88+ def include_event (event , options ):
89+ """Only include events if they are complete events, are longer than
90+ granularity, and are not totals."""
91+ return ((event ['ph' ] == 'X' ) and
92+ (event ['dur' ] >= options ['granularity' ]) and
93+ (not event ['name' ].startswith ('Total' )))
94+
95+ return [x for x in trace_data ['traceEvents' ] if include_event (x , options )]
96+
97+
98+ def trace_to_dicts (target , trace , options , pid , tid ):
99+ """Read a file-like object |trace| containing -ftime-trace data and yields
100+ about:tracing dict per eligible event in that log."""
101+ for event in read_events (trace , options ):
102+ # Check if any event duration is greater than the duration from ninja.
103+ ninja_time = (target .end - target .start ) * 1000
104+ if event ['dur' ] > ninja_time :
105+ print ("Inconsistent timing found (clang time > ninja time). Please"
106+ " ensure that timings are from consistent builds." )
107+ sys .exit (1 )
108+
109+ # Set tid and pid from ninja log.
110+ event ['pid' ] = pid
111+ event ['tid' ] = tid
112+
113+ # Offset trace time stamp by ninja start time.
114+ event ['ts' ] += (target .start * 1000 )
115+
116+ yield event
117+
118+
119+ def embed_time_trace (ninja_log_dir , target , pid , tid , options ):
120+ """Produce time trace output for the specified ninja target. Expects
121+ time-trace file to be in .json file named based on .o file."""
122+ for t in target .targets :
123+ o_path = os .path .join (ninja_log_dir , t )
124+ json_trace_path = os .path .splitext (o_path )[0 ] + '.json'
125+ try :
126+ with open (json_trace_path , 'r' ) as trace :
127+ for time_trace_event in trace_to_dicts (target , trace , options ,
128+ pid , tid ):
129+ yield time_trace_event
130+ except IOError :
131+ pass
132+
133+
134+ def log_to_dicts (log , pid , options ):
80135 """Reads a file-like object |log| containing a .ninja_log, and yields one
81136 about:tracing dict per command found in the log."""
82137 threads = Threads ()
83- for target in read_targets (log , show_all ):
138+ for target in read_targets (log , options ['showall' ]):
139+ tid = threads .alloc (target )
140+
84141 yield {
85142 'name' : '%0s' % ', ' .join (target .targets ), 'cat' : 'targets' ,
86143 'ph' : 'X' , 'ts' : (target .start * 1000 ),
87144 'dur' : ((target .end - target .start ) * 1000 ),
88- 'pid' : pid , 'tid' : threads . alloc ( target ) , 'args' : {},
145+ 'pid' : pid , 'tid' : tid , 'args' : {},
89146 }
147+ if options .get ('embed_time_trace' , False ):
148+ # Add time-trace information into the ninja trace.
149+ try :
150+ ninja_log_dir = os .path .dirname (log .name )
151+ except AttributeError :
152+ continue
153+ for time_trace in embed_time_trace (ninja_log_dir , target , pid ,
154+ tid , options ):
155+ yield time_trace
90156
91157
92158def main (argv ):
@@ -97,6 +163,14 @@ def main(argv):
97163 help = 'report on last build step for all outputs. Default '
98164 'is to report just on the last (possibly incremental) '
99165 'build' )
166+ parser .add_option ('-g' , '--granularity' , type = 'int' , default = 50000 ,
167+ dest = 'granularity' ,
168+ help = 'minimum length time-trace event to embed in '
169+ 'microseconds. Default: %default' )
170+ parser .add_option ('-e' , '--embed-time-trace' , action = 'store_true' ,
171+ default = False , dest = 'embed_time_trace' ,
172+ help = 'embed clang -ftime-trace json file found adjacent '
173+ 'to a target file' )
100174 (options , args ) = parser .parse_args ()
101175
102176 if len (args ) == 0 :
@@ -107,7 +181,7 @@ def main(argv):
107181 entries = []
108182 for pid , log_file in enumerate (args ):
109183 with open (log_file , 'r' ) as log :
110- entries += list (log_to_dicts (log , pid , options . showall ))
184+ entries += list (log_to_dicts (log , pid , vars ( options ) ))
111185 json .dump (entries , sys .stdout )
112186
113187
0 commit comments