1 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import threading 6 import zlib 7 8 from profile_chrome import controllers 9 from profile_chrome import util 10 11 from pylib import cmd_helper 12 13 14 _SYSTRACE_OPTIONS = [ 15 # Compress the trace before sending it over USB. 16 '-z', 17 # Use a large trace buffer to increase the polling interval. 18 '-b', '16384' 19 ] 20 21 # Interval in seconds for sampling systrace data. 22 _SYSTRACE_INTERVAL = 15 23 24 25 class SystraceController(controllers.BaseController): 26 def __init__(self, device, categories, ring_buffer): 27 controllers.BaseController.__init__(self) 28 self._device = device 29 self._categories = categories 30 self._ring_buffer = ring_buffer 31 self._done = threading.Event() 32 self._thread = None 33 self._trace_data = None 34 35 def __repr__(self): 36 return 'systrace' 37 38 @staticmethod 39 def GetCategories(device): 40 return device.RunShellCommand('atrace --list_categories') 41 42 def StartTracing(self, _): 43 self._thread = threading.Thread(target=self._CollectData) 44 self._thread.start() 45 46 def StopTracing(self): 47 self._done.set() 48 49 def PullTrace(self): 50 self._thread.join() 51 self._thread = None 52 if self._trace_data: 53 output_name = 'systrace-%s' % util.GetTraceTimestamp() 54 with open(output_name, 'w') as out: 55 out.write(self._trace_data) 56 return output_name 57 58 def _RunATraceCommand(self, command): 59 # We use a separate interface to adb because the one from AndroidCommands 60 # isn't re-entrant. 61 # TODO(jbudorick) Look at providing a way to unhandroll this once the 62 # adb rewrite has fully landed. 63 device_param = (['-s', str(self._device)] if str(self._device) else []) 64 cmd = ['adb'] + device_param + ['shell', 'atrace', '--%s' % command] + \ 65 _SYSTRACE_OPTIONS + self._categories 66 return cmd_helper.GetCmdOutput(cmd) 67 68 def _CollectData(self): 69 trace_data = [] 70 self._RunATraceCommand('async_start') 71 try: 72 while not self._done.is_set(): 73 self._done.wait(_SYSTRACE_INTERVAL) 74 if not self._ring_buffer or self._done.is_set(): 75 trace_data.append( 76 self._DecodeTraceData(self._RunATraceCommand('async_dump'))) 77 finally: 78 trace_data.append( 79 self._DecodeTraceData(self._RunATraceCommand('async_stop'))) 80 self._trace_data = ''.join([zlib.decompress(d) for d in trace_data]) 81 82 @staticmethod 83 def _DecodeTraceData(trace_data): 84 try: 85 trace_start = trace_data.index('TRACE:') 86 except ValueError: 87 raise RuntimeError('Systrace start marker not found') 88 trace_data = trace_data[trace_start + 6:] 89 90 # Collapse CRLFs that are added by adb shell. 91 if trace_data.startswith('\r\n'): 92 trace_data = trace_data.replace('\r\n', '\n') 93 94 # Skip the initial newline. 95 return trace_data[1:] 96