1#!/usr/bin/env python 2# 3# Copyright (C) 2014 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Script that parses a trace filed produced in streaming mode. The file is broken up into 18 a header and body part, which, when concatenated, make up a non-streaming trace file that 19 can be used with traceview.""" 20 21import sys 22 23class MyException(Exception): 24 pass 25 26class BufferUnderrun(Exception): 27 pass 28 29def ReadShortLE(f): 30 byte1 = f.read(1) 31 if not byte1: 32 raise BufferUnderrun() 33 byte2 = f.read(1) 34 if not byte2: 35 raise BufferUnderrun() 36 return ord(byte1) + (ord(byte2) << 8); 37 38def WriteShortLE(f, val): 39 bytes = [ (val & 0xFF), ((val >> 8) & 0xFF) ] 40 asbytearray = bytearray(bytes) 41 f.write(asbytearray) 42 43def ReadIntLE(f): 44 byte1 = f.read(1) 45 if not byte1: 46 raise BufferUnderrun() 47 byte2 = f.read(1) 48 if not byte2: 49 raise BufferUnderrun() 50 byte3 = f.read(1) 51 if not byte3: 52 raise BufferUnderrun() 53 byte4 = f.read(1) 54 if not byte4: 55 raise BufferUnderrun() 56 return ord(byte1) + (ord(byte2) << 8) + (ord(byte3) << 16) + (ord(byte4) << 24); 57 58def WriteIntLE(f, val): 59 bytes = [ (val & 0xFF), ((val >> 8) & 0xFF), ((val >> 16) & 0xFF), ((val >> 24) & 0xFF) ] 60 asbytearray = bytearray(bytes) 61 f.write(asbytearray) 62 63def Copy(input, output, length): 64 buf = input.read(length) 65 if len(buf) != length: 66 raise BufferUnderrun() 67 output.write(buf) 68 69class Rewriter: 70 71 def PrintHeader(self, header): 72 header.write('*version\n'); 73 header.write('3\n'); 74 header.write('data-file-overflow=false\n'); 75 header.write('clock=dual\n'); 76 header.write('vm=art\n'); 77 78 def ProcessDataHeader(self, input, body): 79 magic = ReadIntLE(input) 80 if magic != 0x574f4c53: 81 raise MyException("Magic wrong") 82 83 WriteIntLE(body, magic) 84 85 version = ReadShortLE(input) 86 if (version & 0xf0) != 0xf0: 87 raise MyException("Does not seem to be a streaming trace: %d." % version) 88 version = version ^ 0xf0 89 90 if version != 3: 91 raise MyException("Only support version 3") 92 93 WriteShortLE(body, version) 94 95 # read offset 96 offsetToData = ReadShortLE(input) - 16 97 WriteShortLE(body, offsetToData + 16) 98 99 # copy startWhen 100 Copy(input, body, 8) 101 102 if version == 1: 103 self._mRecordSize = 9; 104 elif version == 2: 105 self._mRecordSize = 10; 106 else: 107 self._mRecordSize = ReadShortLE(input) 108 WriteShortLE(body, self._mRecordSize) 109 offsetToData -= 2; 110 111 # Skip over offsetToData bytes 112 Copy(input, body, offsetToData) 113 114 def ProcessMethod(self, input): 115 stringLength = ReadShortLE(input) 116 str = input.read(stringLength) 117 self._methods.append(str) 118 print 'New method: %s' % str 119 120 def ProcessThread(self, input): 121 tid = ReadShortLE(input) 122 stringLength = ReadShortLE(input) 123 str = input.read(stringLength) 124 self._threads.append('%d\t%s\n' % (tid, str)) 125 print 'New thread: %d/%s' % (tid, str) 126 127 def ProcessTraceSummary(self, input): 128 summaryLength = ReadIntLE(input) 129 str = input.read(summaryLength) 130 self._summary = str 131 print 'Summary: \"%s\"' % str 132 133 def ProcessSpecial(self, input): 134 code = ord(input.read(1)) 135 if code == 1: 136 self.ProcessMethod(input) 137 elif code == 2: 138 self.ProcessThread(input) 139 elif code == 3: 140 self.ProcessTraceSummary(input) 141 else: 142 raise MyException("Unknown special!") 143 144 def Process(self, input, body): 145 try: 146 while True: 147 threadId = ReadShortLE(input) 148 if threadId == 0: 149 self.ProcessSpecial(input) 150 else: 151 # Regular package, just copy 152 WriteShortLE(body, threadId) 153 Copy(input, body, self._mRecordSize - 2) 154 except BufferUnderrun: 155 print 'Buffer underrun, file was probably truncated. Results should still be usable.' 156 157 def Finalize(self, header): 158 # If the summary is present in the input file, use it as the header except 159 # for the methods section which is emtpy in the input file. If not present, 160 # apppend header with the threads that are recorded in the input stream. 161 if (self._summary): 162 # Erase the contents that's already written earlier by PrintHeader. 163 header.seek(0) 164 header.truncate() 165 # Copy the lines from the input summary to the output header until 166 # the methods section is seen. 167 for line in self._summary.splitlines(True): 168 if line == "*methods\n": 169 break 170 else: 171 header.write(line) 172 else: 173 header.write('*threads\n') 174 for t in self._threads: 175 header.write(t) 176 header.write('*methods\n') 177 for m in self._methods: 178 header.write(m) 179 header.write('*end\n') 180 181 def ProcessFile(self, filename): 182 input = open(filename, 'rb') # Input file 183 header = open(filename + '.header', 'w') # Header part 184 body = open(filename + '.body', 'wb') # Body part 185 186 self.PrintHeader(header) 187 188 self.ProcessDataHeader(input, body) 189 190 self._methods = [] 191 self._threads = [] 192 self._summary = None 193 self.Process(input, body) 194 195 self.Finalize(header) 196 197 input.close() 198 header.close() 199 body.close() 200 201def main(): 202 Rewriter().ProcessFile(sys.argv[1]) 203 header_name = sys.argv[1] + '.header' 204 body_name = sys.argv[1] + '.body' 205 print 'Results have been written to %s and %s.' % (header_name, body_name) 206 print 'Concatenate the files to get a result usable with traceview.' 207 sys.exit(0) 208 209if __name__ == '__main__': 210 main()