summaryrefslogtreecommitdiffstats
path: root/solenv/bin/finish-gbuild-trace.py
blob: fcd31fc23fa95ab874f904b07dc804d7dc8c8eb2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env python3

# Process file generated by using GBUILD_TRACE=file with make.
# The file needs adjusting for viewing in Chromium's "chrome://tracing' URL.
# See solenv/gbuild/Trace.mk for details.

import os
import re
import sys

if len(sys.argv) != 2:
    print ("Usage: " + sys.argv[0] + " [trace.json]", file=sys.stderr)
    sys.exit(1)

filename=sys.argv[1]

with open(filename) as infile:
    lines = [ line.rstrip('\n') for line in infile ]

if len(lines) == 0 :
    print( "Empty file?", file=sys.stderr)
    sys.exit(1)

if lines[0] == '{"traceEvents": [':
    print( "File already processed", file=sys.stderr)
    sys.exit(3)

# sort items by time (parallel writes may not write them in time order)
def linekey(line):
    match = re.match( r'^.*, "ts": ([0-9]*)[0-9][0-9][0-9],.*$', line )
    assert match, "Unknown line: " + line
    return int(match.group(1))
lines.sort( key=linekey )

# 'chrome://tracing' shows several rows, we use those to show build parallelism,
# but we need to assign the proper ids by allocating them as slots.
slots = []
# start time of each slot
slot_start_time = []

def make_slot_string(type, detail):
    return type + '#' + detail

def allocate_slot(type, detail):
    for index in range(len(slots)):
        if slots[index] == "":
            slots[index] = make_slot_string(type, detail)
            return index + 1
    index = len(slots)
    slots.append(make_slot_string(type, detail))
    slot_start_time.append(0)
    return index + 1

def free_slot(type, detail):
    for index in range(len(slots)):
        if slots[index] == make_slot_string(type, detail):
            slots[index] = ""
            return index + 1
    assert False, "free_slot(" + type + "," + detail + ") not found"

# key: Type (e.g. CXX), value: time total
totals_time = {}
totals_count = {}

# time of the first item, to rebase all times to 0
start_time = 0

with open(filename + ".tmp", "w") as outfile:
    print( '{"traceEvents": [', file=outfile)
    for iline in range(len(lines)):
        line = lines[iline]
        # "ts" needs converting nanoseconds -> milliseconds
        match = re.match( r'^{"name": "([^"]*)", "ph": "(.)",.*"ts": ([0-9]*)[0-9][0-9][0-9],"args":{"message":"([^"]*)"}.*$', line )
        if not match:
            print( "Unknown line: " + line, file=sys.stderr)
            sys.exit(2)
        if start_time == 0:
            start_time = int(match.group(3))
        # "tid" needs replacing with proper slot
        tid = "0"
        # "ph"
        if match.group(2) == 'B':
            tid = allocate_slot(match.group(1), match.group(4)) # "name", "args"
            slot_start_time[tid-1] = int(match.group(3))
        elif match.group(2) == 'E':
            tid = free_slot(match.group(1), match.group(4)) # "name", "args"
            if not match.group(1) in totals_time:
                totals_time[match.group(1)] = 0
                totals_count[match.group(1)] = 0
            totals_time[match.group(1)] += int(match.group(3)) - slot_start_time[tid-1]
            totals_count[match.group(1)] += 1
        line = re.sub( r'"ts": [0-9]+,', '"ts": ' + str(int(match.group(3)) - start_time) + ",", line)
        line = re.sub( r'"tid": 1,', '"tid": ' + str(tid) + ",", line)
        if match.group(2) == 'i':
            rline = line
            # mark as affecting all slots
            line = re.sub( r'}},$', '}, "s": "p"},', line)
            print(line, file=outfile)
            # Chromium search doesn't find 'i' items, add extra 'R' for that
            rline = re.sub( r', "ph": "i",', ', "ph": "R",', rline)
            rline = re.sub( r', "tid": [0-9]+,', ',', rline)
            print(rline, file=outfile)
        else:
            print(line, file=outfile)
        # TODO: By the first time "[DEP]: LNK:Executable/makedepend.exe" is invoked the build tools
        # are not built yet, so the invocation fails, doesn't abort the build for some reason,
        # but the matching line about it ending is missing. So add the missing end line if it is
        # by another start line for it instead of an end line.
        if match.group(1) == "DEP" and match.group(4) == "[DEP]: LNK:Executable/makedepend.exe" and match.group(2) == "B":
            for iline2 in range(iline+1,len(lines)): # search following lines
                line2 = lines[iline2]
                match2 = re.match( r'^{"name": "([^"]*)", "ph": "(.)",.*"ts": ([0-9]*)[0-9][0-9][0-9],"args":{"message":"([^"]*)"}.*$', line2 )
                if match2.group(1) == "DEP" and match2.group(4) == "[DEP]: LNK:Executable/makedepend.exe":
                    if match2.group(2) == "E":
                        break # it has a matching close
                    if match2.group(2) == "B":
                        print(re.sub( r', "ph": "B",', ', "ph": "E",', line), file=outfile) # close the starting line
                        free_slot(match.group(1), match.group(4))
                        break

    total_num = 0
    for total in sorted(totals_time, key=totals_time.get, reverse=True):
        note = ""
        if total == "EXTERNAL":
            note = ',"note": "minimum (cannot detect parallelism)"'
        print( '{"pid":2,"tid":' + str(total_num) + ',"ts":0,"dur":' + str(totals_time[total]) + ',"ph":"X","name":"' + total
            + '","args":{"count":"' + str(totals_count[total]) + '"' + note + '}},', file=outfile)
        total_num += 1

    print( '{"pid":1,"tid":0,"ts":0,"ph":"M","name":"process_name","args":{"name":"gbuild"}},', file=outfile)
    print( '{"pid":2,"tid":0,"ts":0,"ph":"M","name":"process_name","args":{"name":"totals"}}]}', file=outfile)

for index in range(len(slots)):
    if slots[index] != "":
        print( "Unclosed range: " + slots[index], file=sys.stderr)

os.rename(filename + ".tmp", filename)