| #!/usr/bin/env python |
| # |
| # Copyright (C) 2014 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Script that parses a trace filed produced in streaming mode. The file is broken up into |
| a header and body part, which, when concatenated, make up a non-streaming trace file that |
| can be used with traceview.""" |
| |
| import sys |
| |
| class MyException(Exception): |
| pass |
| |
| class BufferUnderrun(Exception): |
| pass |
| |
| def ReadShortLE(f): |
| byte1 = f.read(1) |
| if not byte1: |
| raise BufferUnderrun() |
| byte2 = f.read(1) |
| if not byte2: |
| raise BufferUnderrun() |
| return ord(byte1) + (ord(byte2) << 8); |
| |
| def WriteShortLE(f, val): |
| bytes = [ (val & 0xFF), ((val >> 8) & 0xFF) ] |
| asbytearray = bytearray(bytes) |
| f.write(asbytearray) |
| |
| def ReadIntLE(f): |
| byte1 = f.read(1) |
| if not byte1: |
| raise BufferUnderrun() |
| byte2 = f.read(1) |
| if not byte2: |
| raise BufferUnderrun() |
| byte3 = f.read(1) |
| if not byte3: |
| raise BufferUnderrun() |
| byte4 = f.read(1) |
| if not byte4: |
| raise BufferUnderrun() |
| return ord(byte1) + (ord(byte2) << 8) + (ord(byte3) << 16) + (ord(byte4) << 24); |
| |
| def WriteIntLE(f, val): |
| bytes = [ (val & 0xFF), ((val >> 8) & 0xFF), ((val >> 16) & 0xFF), ((val >> 24) & 0xFF) ] |
| asbytearray = bytearray(bytes) |
| f.write(asbytearray) |
| |
| def Copy(input, output, length): |
| buf = input.read(length) |
| if len(buf) != length: |
| raise BufferUnderrun() |
| output.write(buf) |
| |
| class Rewriter: |
| |
| def PrintHeader(self, header): |
| header.write('*version\n'); |
| header.write('3\n'); |
| header.write('data-file-overflow=false\n'); |
| header.write('clock=dual\n'); |
| header.write('vm=art\n'); |
| |
| def ProcessDataHeader(self, input, body): |
| magic = ReadIntLE(input) |
| if magic != 0x574f4c53: |
| raise MyException("Magic wrong") |
| |
| WriteIntLE(body, magic) |
| |
| version = ReadShortLE(input) |
| if (version & 0xf0) != 0xf0: |
| raise MyException("Does not seem to be a streaming trace: %d." % version) |
| version = version ^ 0xf0 |
| |
| if version != 3: |
| raise MyException("Only support version 3") |
| |
| WriteShortLE(body, version) |
| |
| # read offset |
| offsetToData = ReadShortLE(input) - 16 |
| WriteShortLE(body, offsetToData + 16) |
| |
| # copy startWhen |
| Copy(input, body, 8) |
| |
| if version == 1: |
| self._mRecordSize = 9; |
| elif version == 2: |
| self._mRecordSize = 10; |
| else: |
| self._mRecordSize = ReadShortLE(input) |
| WriteShortLE(body, self._mRecordSize) |
| offsetToData -= 2; |
| |
| # Skip over offsetToData bytes |
| Copy(input, body, offsetToData) |
| |
| def ProcessMethod(self, input): |
| stringLength = ReadShortLE(input) |
| str = input.read(stringLength) |
| self._methods.append(str) |
| print 'New method: %s' % str |
| |
| def ProcessThread(self, input): |
| tid = ReadShortLE(input) |
| stringLength = ReadShortLE(input) |
| str = input.read(stringLength) |
| self._threads.append('%d\t%s\n' % (tid, str)) |
| print 'New thread: %d/%s' % (tid, str) |
| |
| def ProcessTraceSummary(self, input): |
| summaryLength = ReadIntLE(input) |
| str = input.read(summaryLength) |
| self._summary = str |
| print 'Summary: \"%s\"' % str |
| |
| def ProcessSpecial(self, input): |
| code = ord(input.read(1)) |
| if code == 1: |
| self.ProcessMethod(input) |
| elif code == 2: |
| self.ProcessThread(input) |
| elif code == 3: |
| self.ProcessTraceSummary(input) |
| else: |
| raise MyException("Unknown special!") |
| |
| def Process(self, input, body): |
| try: |
| while True: |
| threadId = ReadShortLE(input) |
| if threadId == 0: |
| self.ProcessSpecial(input) |
| else: |
| # Regular package, just copy |
| WriteShortLE(body, threadId) |
| Copy(input, body, self._mRecordSize - 2) |
| except BufferUnderrun: |
| print 'Buffer underrun, file was probably truncated. Results should still be usable.' |
| |
| def Finalize(self, header): |
| # If the summary is present in the input file, use it as the header except |
| # for the methods section which is emtpy in the input file. If not present, |
| # apppend header with the threads that are recorded in the input stream. |
| if (self._summary): |
| # Erase the contents that's already written earlier by PrintHeader. |
| header.seek(0) |
| header.truncate() |
| # Copy the lines from the input summary to the output header until |
| # the methods section is seen. |
| for line in self._summary.splitlines(True): |
| if line == "*methods\n": |
| break |
| else: |
| header.write(line) |
| else: |
| header.write('*threads\n') |
| for t in self._threads: |
| header.write(t) |
| header.write('*methods\n') |
| for m in self._methods: |
| header.write(m) |
| header.write('*end\n') |
| |
| def ProcessFile(self, filename): |
| input = open(filename, 'rb') # Input file |
| header = open(filename + '.header', 'w') # Header part |
| body = open(filename + '.body', 'wb') # Body part |
| |
| self.PrintHeader(header) |
| |
| self.ProcessDataHeader(input, body) |
| |
| self._methods = [] |
| self._threads = [] |
| self._summary = None |
| self.Process(input, body) |
| |
| self.Finalize(header) |
| |
| input.close() |
| header.close() |
| body.close() |
| |
| def main(): |
| Rewriter().ProcessFile(sys.argv[1]) |
| header_name = sys.argv[1] + '.header' |
| body_name = sys.argv[1] + '.body' |
| print 'Results have been written to %s and %s.' % (header_name, body_name) |
| print 'Concatenate the files to get a result usable with traceview.' |
| sys.exit(0) |
| |
| if __name__ == '__main__': |
| main() |