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 continue 58 storing_data.setdefault(cmdhash, StoringDataLine(start, end)) 59 storing_data.get(cmdhash).target_obj_names.append(name) 60 61 self.datalist = sorted(storing_data.values(), 62 key=lambda line: line.start) 63 self.durations = sorted(storing_data.values(), 64 key=lambda line: line.end - line.start, 65 reverse=True) 66 return True 67 68 def save_durations(self, duration_file): 69 total_time = 0 70 with open(duration_file, 'w') as file: 71 for item in self.durations: 72 duration = item.end - item.start 73 total_time += duration 74 file.write('{}: {}\n'.format(item.target_obj_names[0], 75 duration)) 76 file.write('total time: {} ms'.format(total_time)) 77 78 def trans_to_trace_json(self, dest_file_name): 79 counter = CountingTheTid() 80 tracelist = list() 81 for storingdataline in self.datalist: 82 tracelist.append({ 83 'name': '%0s' % ', '.join(storingdataline.target_obj_names), 84 'cat': 'targets', 85 'ph': 'X', 86 'ts': str(storingdataline.start * 1000), 87 'dur': str((storingdataline.end - storingdataline.start) * 1000), 88 'pid': str(0), 89 'tid': str(counter.counting_the_new_tid(storingdataline)), 90 'args': {}, 91 }) 92 93 if not dest_file_name.endswith('.gz'): 94 dest_file_name = dest_file_name + '.gz' 95 96 if os.path.exists(dest_file_name): 97 shutil.move( 98 dest_file_name, '%s/build.trace.%d.gz' % 99 (os.path.dirname(dest_file_name), 100 int(os.stat(dest_file_name).st_mtime))) 101 102 with gzip.open(dest_file_name, "wt") as f: 103 json.dump(tracelist, f) 104 105 106class CountingTheTid(object): 107 def __init__(self): 108 self.tids = [] # store the tid's end time 109 110 def counting_the_new_tid(self, storingdataline): 111 for i in range(len(self.tids)): 112 if self.tids[i] <= storingdataline.start: 113 self.tids[i] = storingdataline.end 114 return i # renew the endtime and return the current tid 115 116 # for the end time is newer than all tids so we need a new one 117 self.tids.append(storingdataline.end) 118 return len(self.tids) - 1 # the last index of the tids 119 120 121def main(): 122 parser = argparse.ArgumentParser() 123 parser.add_argument('--ninja-log', help='path to ninja log') 124 parser.add_argument('--trace-file', help='path to build trace file') 125 parser.add_argument('--duration-file', help='path to duration file') 126 parser.add_argument( 127 '--ninja-start-time', 128 help='epoch time of "Starting ninja ..." in nanoseconds') 129 130 options = parser.parse_args() 131 myparser = NinjaToTrace() 132 if not myparser.parse_file(options.ninja_log, options.ninja_start_time): 133 print("parse file fail") 134 return 135 136 myparser.trans_to_trace_json(options.trace_file) 137 myparser.save_durations(options.duration_file) 138 139 140if __name__ == '__main__': 141 sys.exit(main()) 142