3 # Process file generated by using GBUILD_TRACE=file with make.
4 # The file needs adjusting for viewing in Chromium's "chrome://tracing' URL.
5 # See solenv/gbuild/Trace.mk for details.
11 if len(sys
.argv
) != 2:
12 print ("Usage: " + sys
.argv
[0] + " [trace.json]", file=sys
.stderr
)
17 with
open(filename
) as infile
:
18 lines
= [ line
.rstrip('\n') for line
in infile
]
21 print( "Empty file?", file=sys
.stderr
)
24 if lines
[0] == '{"traceEvents": [':
25 print( "File already processed", file=sys
.stderr
)
28 # sort items by time (parallel writes may not write them in time order)
30 match
= re
.match( r
'^.*, "ts": ([0-9]*)[0-9][0-9][0-9],.*$', line
)
31 assert match
, "Unknown line: " + line
32 return int(match
.group(1))
33 lines
.sort( key
=linekey
)
35 # 'chrome://tracing' shows several rows, we use those to show build parallelism,
36 # but we need to assign the proper ids by allocating them as slots.
38 # start time of each slot
41 def make_slot_string(type, detail
):
42 return type + '#' + detail
44 def allocate_slot(type, detail
):
45 for index
in range(len(slots
)):
46 if slots
[index
] == "":
47 slots
[index
] = make_slot_string(type, detail
)
50 slots
.append(make_slot_string(type, detail
))
51 slot_start_time
.append(0)
54 def free_slot(type, detail
):
55 for index
in range(len(slots
)):
56 if slots
[index
] == make_slot_string(type, detail
):
59 assert False, "free_slot(" + type + "," + detail
+ ") not found"
61 # key: Type (e.g. CXX), value: time total
65 # time of the first item, to rebase all times to 0
68 with
open(filename
+ ".tmp", "w") as outfile
:
69 print( '{"traceEvents": [', file=outfile
)
70 for iline
in range(len(lines
)):
72 # "ts" needs converting nanoseconds -> milliseconds
73 match
= re
.match( r
'^{"name": "([^"]*)", "ph": "(.)",.*"ts": ([0-9]*)[0-9][0-9][0-9],"args":{"message":"([^"]*)"}.*$', line
)
75 print( "Unknown line: " + line
, file=sys
.stderr
)
78 start_time
= int(match
.group(3))
79 # "tid" needs replacing with proper slot
82 if match
.group(2) == 'B':
83 tid
= allocate_slot(match
.group(1), match
.group(4)) # "name", "args"
84 slot_start_time
[tid
-1] = int(match
.group(3))
85 elif match
.group(2) == 'E':
86 tid
= free_slot(match
.group(1), match
.group(4)) # "name", "args"
87 if not match
.group(1) in totals_time
:
88 totals_time
[match
.group(1)] = 0
89 totals_count
[match
.group(1)] = 0
90 totals_time
[match
.group(1)] += int(match
.group(3)) - slot_start_time
[tid
-1]
91 totals_count
[match
.group(1)] += 1
92 line
= re
.sub( r
'"ts": [0-9]+,', '"ts": ' + str(int(match
.group(3)) - start_time
) + ",", line
)
93 line
= re
.sub( r
'"tid": 1,', '"tid": ' + str(tid
) + ",", line
)
94 if match
.group(2) == 'i':
96 # mark as affecting all slots
97 line
= re
.sub( r
'}},$', '}, "s": "p"},', line
)
98 print(line
, file=outfile
)
99 # Chromium search doesn't find 'i' items, add extra 'R' for that
100 rline
= re
.sub( r
', "ph": "i",', ', "ph": "R",', rline
)
101 rline
= re
.sub( r
', "tid": [0-9]+,', ',', rline
)
102 print(rline
, file=outfile
)
104 print(line
, file=outfile
)
105 # TODO: By the first time "[DEP]: LNK:Executable/makedepend.exe" is invoked the build tools
106 # are not built yet, so the invocation fails, doesn't abort the build for some reason,
107 # but the matching line about it ending is missing. So add the missing end line if it is
108 # by another start line for it instead of an end line.
109 if match
.group(1) == "DEP" and match
.group(4) == "[DEP]: LNK:Executable/makedepend.exe" and match
.group(2) == "B":
110 for iline2
in range(iline
+1,len(lines
)): # search following lines
111 line2
= lines
[iline2
]
112 match2
= re
.match( r
'^{"name": "([^"]*)", "ph": "(.)",.*"ts": ([0-9]*)[0-9][0-9][0-9],"args":{"message":"([^"]*)"}.*$', line2
)
113 if match2
.group(1) == "DEP" and match2
.group(4) == "[DEP]: LNK:Executable/makedepend.exe":
114 if match2
.group(2) == "E":
115 break # it has a matching close
116 if match2
.group(2) == "B":
117 print(re
.sub( r
', "ph": "B",', ', "ph": "E",', line
), file=outfile
) # close the starting line
118 free_slot(match
.group(1), match
.group(4))
122 for total
in sorted(totals_time
, key
=totals_time
.get
, reverse
=True):
124 if total
== "EXTERNAL":
125 note
= ',"note": "minimum (cannot detect parallelism)"'
126 print( '{"pid":2,"tid":' + str(total_num
) + ',"ts":0,"dur":' + str(totals_time
[total
]) + ',"ph":"X","name":"' + total
127 + '","args":{"count":"' + str(totals_count
[total
]) + '"' + note
+ '}},', file=outfile
)
130 print( '{"pid":1,"tid":0,"ts":0,"ph":"M","name":"process_name","args":{"name":"gbuild"}},', file=outfile
)
131 print( '{"pid":2,"tid":0,"ts":0,"ph":"M","name":"process_name","args":{"name":"totals"}}]}', file=outfile
)
133 for index
in range(len(slots
)):
134 if slots
[index
] != "":
135 print( "Unclosed range: " + slots
[index
], file=sys
.stderr
)
137 os
.rename(filename
+ ".tmp", filename
)