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 ProcessSpecial(self, input):
128    code = ord(input.read(1))
129    if code == 1:
130      self.ProcessMethod(input)
131    elif code == 2:
132      self.ProcessThread(input)
133    else:
134      raise MyException("Unknown special!")
135
136  def Process(self, input, body):
137    try:
138      while True:
139        threadId = ReadShortLE(input)
140        if threadId == 0:
141          self.ProcessSpecial(input)
142        else:
143          # Regular package, just copy
144          WriteShortLE(body, threadId)
145          Copy(input, body, self._mRecordSize - 2)
146    except BufferUnderrun:
147      print 'Buffer underrun, file was probably truncated. Results should still be usable.'
148
149  def Finalize(self, header):
150    header.write('*threads\n')
151    for t in self._threads:
152      header.write(t)
153    header.write('*methods\n')
154    for m in self._methods:
155      header.write(m)
156    header.write('*end\n')
157
158  def ProcessFile(self, filename):
159    input = open(filename, 'rb')                     # Input file
160    header = open(filename + '.header', 'w')         # Header part
161    body = open(filename + '.body', 'wb')            # Body part
162
163    self.PrintHeader(header)
164
165    self.ProcessDataHeader(input, body)
166
167    self._methods = []
168    self._threads = []
169    self.Process(input, body)
170
171    self.Finalize(header)
172
173    input.close()
174    header.close()
175    body.close()
176
177def main():
178  Rewriter().ProcessFile(sys.argv[1])
179  header_name = sys.argv[1] + '.header'
180  body_name = sys.argv[1] + '.body'
181  print 'Results have been written to %s and %s.' % (header_name, body_name)
182  print 'Concatenate the files to get a result usable with traceview.'
183  sys.exit(0)
184
185if __name__ == '__main__':
186  main()