Home | History | Annotate | Download | only in tools
      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 
     21 import sys
     22 
     23 class MyException(Exception):
     24   pass
     25 
     26 class BufferUnderrun(Exception):
     27   pass
     28 
     29 def 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 
     38 def WriteShortLE(f, val):
     39   bytes = [ (val & 0xFF), ((val >> 8) & 0xFF) ]
     40   asbytearray = bytearray(bytes)
     41   f.write(asbytearray)
     42 
     43 def 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 
     58 def 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 
     63 def Copy(input, output, length):
     64   buf = input.read(length)
     65   if len(buf) != length:
     66     raise BufferUnderrun()
     67   output.write(buf)
     68 
     69 class 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 
    201 def 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 
    209 if __name__ == '__main__':
    210   main()