Home | History | Annotate | Download | only in adb_profile_chrome
      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 adb_profile_chrome import controllers
      9 from adb_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     # 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