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 5import threading 6import zlib 7 8from adb_profile_chrome import controllers 9from adb_profile_chrome import util 10 11from 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 25class 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 # TODO(jbudorick) can this be made work with DeviceUtils? 60 # We use a separate interface to adb because the one from AndroidCommands 61 # isn't re-entrant. 62 device_param = (['-s', self._device.old_interface.GetDevice()] 63 if self._device.old_interface.GetDevice() 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