#!/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()