1 #!/usr/bin/python 2 # Copyright 2013 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 # Use: ../mergetraces.py `ls cyglog.* -Sr` > merged_cyglog 7 8 """"Merge multiple logs files from different processes into a single log. 9 10 Given two log files of execution traces, merge the traces into a single trace. 11 Merging will use timestamps (i.e. the first two columns of logged calls) to 12 create a single log that is an ordered trace of calls by both processes. 13 """ 14 15 import optparse 16 import os 17 import string 18 import subprocess 19 import sys 20 21 def ParseLogLines(lines): 22 """Parse log file lines. 23 24 Args: 25 lines: lines from log file produced by profiled run 26 27 Below is an example of a small log file: 28 5086e000-52e92000 r-xp 00000000 b3:02 51276 libchromeview.so 29 secs msecs pid:threadid func 30 START 31 1314897086 795828 3587:1074648168 0x509e105c 32 1314897086 795874 3587:1074648168 0x509e0eb4 33 1314897086 796326 3587:1074648168 0x509e0e3c 34 1314897086 796552 3587:1074648168 0x509e07bc 35 END 36 37 Returns: 38 tuple conisiting of 1) an ordered list of the logged calls, as an array of 39 fields, 2) the virtual start address of the library, used to compute the 40 offset of the symbol in the library and 3) the virtual end address 41 """ 42 call_lines = [] 43 vm_start = 0 44 vm_end = 0 45 dash_index = lines[0].find ('-') 46 space_index = lines[0].find (' ') 47 vm_start = int (lines[0][:dash_index], 16) 48 vm_end = int (lines[0][dash_index+1:space_index], 16) 49 for line in lines[2:]: 50 line = line.strip() 51 # print hex (vm_start) 52 fields = line.split() 53 call_lines.append (fields) 54 55 return (call_lines, vm_start, vm_end) 56 57 def HasDuplicates(calls): 58 """Funcition is a sanity check to make sure that calls are only logged once. 59 60 Args: 61 calls: list of calls logged 62 63 Returns: 64 boolean indicating if calls has duplicate calls 65 """ 66 seen = [] 67 for call in calls: 68 if call[3] in seen: 69 return true 70 else: 71 seen.append(call[3]) 72 73 def CheckTimestamps(calls): 74 """Prints warning to stderr if the call timestamps are not in order. 75 76 Args: 77 calls: list of calls logged 78 """ 79 index = 0 80 last_timestamp_secs = -1 81 last_timestamp_ms = -1 82 while (index < len (calls)): 83 timestamp_secs = int (calls[index][0]) 84 timestamp_ms = int (calls[index][1]) 85 timestamp = (timestamp_secs * 1000000) + timestamp_ms 86 last_timestamp = (last_timestamp_secs * 1000000) + last_timestamp_ms 87 if (timestamp < last_timestamp): 88 sys.stderr.write("WARNING: last_timestamp: " + str(last_timestamp_secs) 89 + " " + str(last_timestamp_ms) + " timestamp: " 90 + str(timestamp_secs) + " " + str(timestamp_ms) + "\n") 91 last_timestamp_secs = timestamp_secs 92 last_timestamp_ms = timestamp_ms 93 index = index + 1 94 95 def Convert (call_lines, startAddr, endAddr): 96 """Converts the call addresses to static offsets and removes invalid calls. 97 98 Removes profiled calls not in shared library using start and end virtual 99 addresses, converts strings to integer values, coverts virtual addresses to 100 address in shared library. 101 102 Returns: 103 list of calls as tuples (sec, msec, pid:tid, callee) 104 """ 105 converted_calls = [] 106 call_addresses = [] 107 for fields in call_lines: 108 secs = int (fields[0]) 109 msecs = int (fields[1]) 110 callee = int (fields[3], 16) 111 # print ("callee: " + hex (callee) + " start: " + hex (startAddr) + " end: " 112 # + hex (endAddr)) 113 if (callee >= startAddr and callee < endAddr 114 and (not callee in call_addresses)): 115 converted_calls.append((secs, msecs, fields[2], (callee - startAddr))) 116 call_addresses.append(callee) 117 return converted_calls 118 119 def Timestamp(trace_entry): 120 return int (trace_entry[0]) * 1000000 + int(trace_entry[1]) 121 122 def AddTrace (tracemap, trace): 123 """Adds a trace to the tracemap. 124 125 Adds entries in the trace to the tracemap. All new calls will be added to 126 the tracemap. If the calls already exist in the tracemap then they will be 127 replaced if they happened sooner in the new trace. 128 129 Args: 130 tracemap: the tracemap 131 trace: the trace 132 133 """ 134 for trace_entry in trace: 135 call = trace_entry[3] 136 if (not call in tracemap) or ( 137 Timestamp(tracemap[call]) > Timestamp(trace_entry)): 138 tracemap[call] = trace_entry 139 140 def main(): 141 """Merge two traces for code in specified library and write to stdout. 142 143 Merges the two traces and coverts the virtual addresses to the offsets in the 144 library. First line of merged trace has dummy virtual address of 0-ffffffff 145 so that symbolizing the addresses uses the addresses in the log, since the 146 addresses have already been converted to static offsets. 147 """ 148 parser = optparse.OptionParser('usage: %prog trace1 ... traceN') 149 (_, args) = parser.parse_args() 150 if len(args) <= 1: 151 parser.error('expected at least the following args: trace1 trace2') 152 153 step = 0 154 tracemap = dict() 155 for trace_file in args: 156 step += 1 157 sys.stderr.write(" " + str(step) + "/" + str(len(args)) + 158 ": " + trace_file + ":\n") 159 160 trace_lines = map(string.rstrip, open(trace_file).readlines()) 161 (trace_calls, trace_start, trace_end) = ParseLogLines(trace_lines) 162 CheckTimestamps(trace_calls) 163 sys.stderr.write("Len: " + str(len(trace_calls)) + 164 ". Start: " + hex(trace_start) + 165 ", end: " + hex(trace_end) + '\n') 166 167 trace_calls = Convert(trace_calls, trace_start, trace_end) 168 sys.stderr.write("Converted len: " + str(len(trace_calls)) + "\n") 169 170 AddTrace(tracemap, trace_calls) 171 sys.stderr.write("Merged len: " + str(len(tracemap)) + "\n") 172 173 # Extract the resulting trace from the tracemap 174 merged_trace = [] 175 for call in tracemap: 176 merged_trace.append(tracemap[call]) 177 merged_trace.sort(key=Timestamp) 178 179 print "0-ffffffff r-xp 00000000 xx:00 00000 ./" 180 print "secs\tmsecs\tpid:threadid\tfunc" 181 for call in merged_trace: 182 print (str(call[0]) + "\t" + str(call[1]) + "\t" + call[2] + "\t" + 183 hex(call[3])) 184 185 if __name__ == '__main__': 186 main() 187