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