xref: /build/scripts/ninja2trace.py (revision 5f9996aa)
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2021 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18import json
19import gzip
20import shutil
21import argparse
22
23KFILESIGNATURE = "# ninja log v5\n"
24
25
26class StoringDataLine(object):
27    def __init__(self, start, end):
28        self.start = int(start)
29        self.end = int(end)
30        self.target_obj_names = []
31
32    def __str__(self):
33        return "{} {} {} ".format(self.start, self.end, self.target_obj_names)
34
35
36class NinjaToTrace(object):
37    def __init__(self):
38        self.datalist = list()
39        self.durations = list()
40
41    def parse_file(self, filename, ninja_start_time):
42        # ensure file exist
43        if not os.path.exists(filename):
44            print("file: {} not exists".format(filename))
45            return False
46        storing_data = {}
47        with open(filename, mode='r') as f:
48            firstline = f.readline()
49            if firstline != KFILESIGNATURE:
50                print("unrecognized ninja log format, we need {}".format(
51                    KFILESIGNATURE))
52
53            for _, line in enumerate(f.readlines()):
54                start, end, time_stamp, name, cmdhash = line.strip().split(
55                    '\t')
56                if time_stamp > ninja_start_time:
57                    storing_data.setdefault(cmdhash, StoringDataLine(start, end))
58                    storing_data.get(cmdhash).target_obj_names.append(name)
59
60        self.datalist = sorted(storing_data.values(),
61                               key=lambda line: line.start)
62        self.durations = sorted(storing_data.values(),
63                                key=lambda line: line.end - line.start,
64                                reverse=True)
65        return True
66
67    def save_durations(self, duration_file: str):
68        total_time = 0
69        with open(duration_file, 'w') as file:
70            for item in self.durations:
71                duration = item.end - item.start
72                total_time += duration
73                file.write('{}: {}\n'.format(item.target_obj_names[0],
74                                             duration))
75            file.write('total time: {} ms'.format(total_time))
76
77    def trans_to_trace_json(self, dest_file_name: str):
78        counter = CountingTheTid()
79        tracelist = list()
80        for storingdataline in self.datalist:
81            tracelist.append({
82                'name': '%0s' % ', '.join(storingdataline.target_obj_names),
83                'cat': 'targets',
84                'ph': 'X',
85                'ts': str(storingdataline.start * 1000),
86                'dur': str((storingdataline.end - storingdataline.start) * 1000),
87                'pid': str(0),
88                'tid': str(counter.counting_the_new_tid(storingdataline)),
89                'args': {},
90            })
91
92        if not dest_file_name.endswith('.gz'):
93            dest_file_name = dest_file_name + '.gz'
94
95        if os.path.exists(dest_file_name):
96            shutil.move(
97                dest_file_name, '%s/build.trace.%d.gz' %
98                                (os.path.dirname(dest_file_name),
99                                 int(os.stat(dest_file_name).st_mtime)))
100
101        with gzip.open(dest_file_name, "wt") as f:
102            json.dump(tracelist, f)
103
104
105class CountingTheTid(object):
106    def __init__(self):
107        self.tids = []  # store the tid's end time
108
109    def counting_the_new_tid(self, storingdataline):
110        for i, tid in enumerate(self.tids):
111            if tid <= storingdataline.start:
112                self.tids[i] = storingdataline.end
113                return i  # renew the endtime and return the current tid
114
115        # for the end time is newer than all tids so we need a new one
116        self.tids.append(storingdataline.end)
117        return len(self.tids) - 1  # the last index of the tids
118
119
120def main():
121    parser = argparse.ArgumentParser()
122    parser.add_argument('--ninja-log', help='path to ninja log')
123    parser.add_argument('--trace-file', help='path to build trace file')
124    parser.add_argument('--duration-file', help='path to duration file')
125    parser.add_argument(
126        '--ninja-start-time',
127        help='epoch time of "Starting ninja ..." in nanoseconds')
128
129    options = parser.parse_args()
130    myparser = NinjaToTrace()
131    if not myparser.parse_file(options.ninja_log, options.ninja_start_time):
132        print("parse file fail")
133        return
134
135    myparser.trans_to_trace_json(options.trace_file)
136    myparser.save_durations(options.duration_file)
137
138
139if __name__ == '__main__':
140    sys.exit(main())
141