Skip to content

Commit 4763120

Browse files
authored
Merge pull request nico#20 from rgal/embed_time_trace
Embed clang -ftime-trace timing data into ninja log data
2 parents eb61d59 + d36907f commit 4763120

File tree

2 files changed

+99
-9
lines changed

2 files changed

+99
-9
lines changed

ninjatracing

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
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
1821
Usage:
1922
ninja -C $BUILDDIR
@@ -22,13 +25,15 @@ Usage:
2225

2326
from __future__ import print_function
2427
import json
28+
import os
2529
import optparse
2630
import re
2731
import sys
2832

2933

3034
class 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

92158
def 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

ninjatracing_test

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class TestNinjaTracing(unittest.TestCase):
2727
log = StringIO("# ninja log v5\n"
2828
"100\t200\t0\tmy_output\tdeadbeef\n"
2929
"50\t120\t0\tmy_first_output\t0afef\n")
30-
dicts = list(ninjatracing.log_to_dicts(log, 42, True))
30+
dicts = list(ninjatracing.log_to_dicts(log, 42, {'showall': True}))
3131
expected = [{
3232
'name': 'my_output', 'cat': 'targets', 'ph': 'X',
3333
'ts': 100000, 'dur': 100000, 'pid': 42, 'tid': 0,
@@ -45,7 +45,7 @@ class TestNinjaTracing(unittest.TestCase):
4545
log = StringIO("# ninja log v5\n"
4646
"100\t200\t0\tmy_output\tdeadbeef\n"
4747
"50\t120\t0\tmy_first_output\t0afef\n")
48-
dicts = list(ninjatracing.log_to_dicts(log, 42, False))
48+
dicts = list(ninjatracing.log_to_dicts(log, 42, {'showall': False}))
4949
expected = [{
5050
'name': 'my_first_output', 'cat': 'targets', 'ph': 'X',
5151
'ts': 50000, 'dur': 70000, 'pid': 42, 'tid': 0,
@@ -60,7 +60,7 @@ class TestNinjaTracing(unittest.TestCase):
6060
log = StringIO("# ninja log v5\n"
6161
"100\t200\t0\toutput\tdeadbeef\n"
6262
"100\t200\t0\tother_output\tdeadbeef\n")
63-
dicts = list(ninjatracing.log_to_dicts(log, 42, True))
63+
dicts = list(ninjatracing.log_to_dicts(log, 42, {'showall': True}))
6464
expected = [{
6565
'name': 'output, other_output', 'cat': 'targets', 'ph': 'X',
6666
'ts': 100000, 'dur': 100000, 'pid': 42, 'tid': 0,
@@ -69,6 +69,22 @@ class TestNinjaTracing(unittest.TestCase):
6969
]
7070
self.assertEqual(expected, dicts)
7171

72+
def test_trace(self):
73+
# Simple test for converting time trace to dictionaries. Tests removing
74+
# incomplete, "Total" and short events, replacing tid and pid and
75+
# adjusting timestamp.
76+
trace = StringIO('{ "traceEvents": ['
77+
'{ "dur": 1500, "name": "LongEvent", "ph": "X", "pid": 1, "tid": 12345, "ts": 1000 },'
78+
'{ "dur": 500, "name": "TooShort", "ph": "X", "pid": 1, "tid": 12345, "ts": 1000 },'
79+
'{ "args": { "avg ms": 1, "count": 2 }, "dur": 1111, "name": "Total Count", "ph": "X", "pid": 1, "tid": 12345, "ts": 0 },'
80+
'{ "args": { "name": "clang" }, "cat": "", "name": "process_name", "ph": "M", "pid": 1, "tid": 0, "ts": 0 }'
81+
']}')
82+
target = ninjatracing.Target(5, 10)
83+
dicts = list(ninjatracing.trace_to_dicts(target, trace, {'granularity': 1000}, 42, 5))
84+
expected = [{
85+
'dur': 1500, 'name': 'LongEvent', 'ph': 'X', 'pid': 42, 'tid': 5, 'ts': 6000},
86+
]
87+
self.assertEqual(expected, dicts)
7288

7389
if __name__ == '__main__':
7490
unittest.main()

0 commit comments

Comments
 (0)