Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2720)

Unified Diff: build/android/adb_profile_chrome.py

Issue 25686006: android: Add systrace and combined trace support to adb_profile_chrome (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: s/Download/Pull/ Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: build/android/adb_profile_chrome.py
diff --git a/build/android/adb_profile_chrome.py b/build/android/adb_profile_chrome.py
index b328e47ca979fc6ecd5fc817f4ed5c6394617caf..c8cea89ceb2406430293fed702220fae239ea433 100755
--- a/build/android/adb_profile_chrome.py
+++ b/build/android/adb_profile_chrome.py
@@ -10,13 +10,156 @@ import optparse
import os
import re
import sys
+import threading
import time
+import zipfile
+import zlib
from pylib import android_commands
+from pylib import cmd_helper
from pylib import constants
from pylib import pexpect
+def _GetTraceTimestamp():
+ return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
+
+
+class ChromeTracingController(object):
+ def __init__(self, adb, package_info, categories, ring_buffer):
+ self._adb = adb
+ self._package_info = package_info
+ self._categories = categories
+ self._ring_buffer = ring_buffer
+ self._trace_file = None
+ self._trace_interval = None
+ self._trace_start_re = \
+ re.compile(r'Logging performance trace to file: (.*)')
+ self._trace_finish_re = \
+ re.compile(r'Profiler finished[.] Results are in (.*)[.]')
+ self._adb.StartMonitoringLogcat(clear=False)
+
+ def __str__(self):
+ return 'chrome trace'
+
+ def StartTracing(self, interval):
+ self._trace_interval = interval
+ self._adb.SyncLogCat()
+ self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_START',
+ '-e categories "%s"' % ','.join(self._categories),
+ '-e continuous' if self._ring_buffer else '')
+ # Chrome logs two different messages related to tracing:
+ #
+ # 1. "Logging performance trace to file [...]"
+ # 2. "Profiler finished. Results are in [...]"
+ #
+ # The first one is printed when tracing starts and the second one indicates
+ # that the trace file is ready to be pulled.
+ try:
+ self._trace_file = self._adb.WaitForLogMatch(self._trace_start_re,
+ None,
+ timeout=5).group(1)
+ except pexpect.TIMEOUT:
+ raise RuntimeError('Trace start marker not found. Is the correct version '
+ 'of the browser running?')
+
+ def StopTracing(self):
+ if not self._trace_file:
+ return
+ self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_STOP')
+ self._adb.WaitForLogMatch(self._trace_finish_re, None, timeout=120)
+
+ def PullTrace(self):
+ # Wait a bit for the browser to finish writing the trace file.
+ time.sleep(self._trace_interval / 4 + 1)
+
+ trace_file = self._trace_file.replace('/storage/emulated/0/', '/sdcard/')
+ host_file = os.path.join(os.path.curdir, os.path.basename(trace_file))
+ self._adb.PullFileFromDevice(trace_file, host_file)
+ return host_file
+
+
+_SYSTRACE_OPTIONS = [
+ # Compress the trace before sending it over USB.
+ '-z',
+ # Use a large trace buffer to increase the polling interval.
+ '-b', '16384'
+]
+
+# Interval in seconds for sampling systrace data.
+_SYSTRACE_INTERVAL = 15
+
+
+class SystraceController(object):
+ def __init__(self, adb, categories, ring_buffer):
+ self._adb = adb
+ self._categories = categories
+ self._ring_buffer = ring_buffer
+ self._done = threading.Event()
+ self._thread = None
+ self._trace_data = None
+
+ def __str__(self):
+ return 'systrace'
+
+ @staticmethod
+ def GetCategories(adb):
+ return adb.RunShellCommand('atrace --list_categories')
+
+ def StartTracing(self, interval):
+ self._thread = threading.Thread(target=self._CollectData)
+ self._thread.start()
+
+ def StopTracing(self):
+ self._done.set()
+
+ def PullTrace(self):
+ self._thread.join()
+ self._thread = None
+ if self._trace_data:
+ output_name = 'systrace-%s' % _GetTraceTimestamp()
+ with open(output_name, 'w') as out:
+ out.write(self._trace_data)
+ return output_name
+
+ def _RunATraceCommand(self, command):
+ # We use a separate interface to adb because the one from AndroidCommands
+ # isn't re-entrant.
+ device = ['-s', self._adb.GetDevice()] if self._adb.GetDevice() else []
+ cmd = ['adb'] + device + ['shell', 'atrace', '--%s' % command] + \
+ _SYSTRACE_OPTIONS + self._categories
+ return cmd_helper.GetCmdOutput(cmd)
+
+ def _CollectData(self):
+ trace_data = []
+ self._RunATraceCommand('async_start')
+ try:
+ while not self._done.is_set():
+ self._done.wait(_SYSTRACE_INTERVAL)
+ if not self._ring_buffer or self._done.is_set():
+ trace_data.append(
+ self._DecodeTraceData(self._RunATraceCommand('async_dump')))
+ finally:
+ trace_data.append(
+ self._DecodeTraceData(self._RunATraceCommand('async_stop')))
+ self._trace_data = ''.join([zlib.decompress(d) for d in trace_data])
+
+ @staticmethod
+ def _DecodeTraceData(trace_data):
+ try:
+ trace_start = trace_data.index('TRACE:')
+ except ValueError:
+ raise RuntimeError('Systrace start marker not found')
+ trace_data = trace_data[trace_start + 6:]
+
+ # Collapse CRLFs that are added by adb shell.
+ if trace_data.startswith('\r\n'):
+ trace_data = trace_data.replace('\r\n', '\n')
+
+ # Skip the initial newline.
+ return trace_data[1:]
+
+
def _GetSupportedBrowsers():
# Add aliases for backwards compatibility.
supported_browsers = {
@@ -32,111 +175,93 @@ def _GetSupportedBrowsers():
return supported_browsers
-def _StartTracing(adb, package_info, categories, continuous):
- adb.BroadcastIntent(package_info.package, 'GPU_PROFILER_START',
- '-e categories "%s"' % categories,
- '-e continuous' if continuous else '')
-
-
-def _StopTracing(adb, package_info):
- adb.BroadcastIntent(package_info.package, 'GPU_PROFILER_STOP')
+def _CompressFile(host_file, output):
+ with gzip.open(output, 'wb') as out:
+ with open(host_file, 'rb') as input_file:
+ out.write(input_file.read())
+ os.unlink(host_file)
-def _GetLatestTraceFileName(adb, check_for_multiple_traces=True):
- # Chrome logs two different messages related to tracing:
- #
- # 1. "Logging performance trace to file [...]"
- # 2. "Profiler finished. Results are in [...]"
- #
- # The first one is printed when tracing starts and the second one indicates
- # that the trace file is ready to be downloaded.
- #
- # We have to look for both of these messages to make sure we get the results
- # from the latest trace and that the trace file is complete.
- trace_start_re = re.compile(r'Logging performance trace to file')
- trace_finish_re = re.compile(r'Profiler finished[.] Results are in (.*)[.]')
+def _ArchiveFiles(host_files, output):
+ with zipfile.ZipFile(output, 'w') as z:
+ for host_file in host_files:
+ z.write(host_file)
+ os.unlink(host_file)
- start_timeout = 5
- start_match = None
- finish_match = None
- while True:
- try:
- start_match = adb.WaitForLogMatch(trace_start_re, None,
- timeout=start_timeout)
- except pexpect.TIMEOUT:
- if start_match:
- break
- raise RuntimeError('Trace start marker not found. Is the correct version '
- 'of the browser running?')
- finish_match = adb.WaitForLogMatch(trace_finish_re, None, timeout=120)
- if not check_for_multiple_traces:
- break
- # Now that we've found one trace file, switch to polling for the rest of the
- # log to see if there are more.
- start_timeout = 1
- return finish_match.group(1)
+def _PrintMessage(heading, eol='\n'):
+ sys.stdout.write('%s%s' % (heading, eol))
+ sys.stdout.flush()
-def _DownloadTrace(adb, trace_file):
- trace_file = trace_file.replace('/storage/emulated/0/', '/sdcard/')
- host_file = os.path.abspath(os.path.basename(trace_file))
- adb.PullFileFromDevice(trace_file, host_file)
- return host_file
+def _StartTracing(controllers, interval):
+ for controller in controllers:
+ controller.StartTracing(interval)
-def _CompressFile(host_file):
- compressed_file = host_file + '.gz'
- with gzip.open(compressed_file, 'wb') as out:
- with open(host_file, 'rb') as input_file:
- out.write(input_file.read())
- os.unlink(host_file)
- return compressed_file
+def _StopTracing(controllers):
+ for controller in controllers:
+ controller.StopTracing()
-def _PrintMessage(heading, eol='\n'):
- sys.stdout.write(heading + eol)
- sys.stdout.flush()
+def _PullTraces(controllers, output, compress):
+ _PrintMessage('Downloading...', eol='')
+ trace_files = []
+ for controller in controllers:
+ trace_files.append(controller.PullTrace())
+ if compress and len(trace_files) == 1:
+ result = output or trace_files[0] + '.gz'
+ _CompressFile(trace_files[0], result)
+ elif len(trace_files) > 1:
+ result = output or 'chrome-combined-trace-%s.zip' % _GetTraceTimestamp()
+ _ArchiveFiles(trace_files, result)
+ elif output:
+ result = output
+ os.rename(trace_files[0], result)
+ else:
+ result = trace_files[0]
-def _DownloadLatestTrace(adb, compress, check_for_multiple_traces=True):
- _PrintMessage('Downloading trace...', eol='')
- trace_file = _GetLatestTraceFileName(adb, check_for_multiple_traces)
- host_file = _DownloadTrace(adb, trace_file)
- if compress:
- host_file = _CompressFile(host_file)
_PrintMessage('done')
- _PrintMessage('Trace written to %s' % host_file)
-
+ _PrintMessage('Trace written to %s' % os.path.abspath(result))
-def _CaptureAndDownloadTimedTrace(adb, package_info, categories, interval,
- continuous, compress):
- adb.StartMonitoringLogcat(clear=False)
- adb.SyncLogCat()
+def _CaptureAndPullTrace(controllers, interval, output, compress):
+ trace_type = ' + '.join(map(str, controllers))
try:
- _PrintMessage('Capturing %d-second trace. Press Ctrl-C to stop early...' \
- % interval, eol='')
- _StartTracing(adb, package_info, categories, continuous)
- time.sleep(interval)
+ _StartTracing(controllers, interval)
+ if interval:
+ _PrintMessage('Capturing %d-second %s. Press Ctrl-C to stop early...' % \
+ (interval, trace_type), eol='')
+ time.sleep(interval)
+ else:
+ _PrintMessage('Capturing %s. Press Enter to stop...' % trace_type, eol='')
+ raw_input()
except KeyboardInterrupt:
- _PrintMessage('\nInterrupted, stopping...', eol='')
- _StopTracing(adb, package_info)
- _PrintMessage('done')
+ _PrintMessage('\nInterrupted...', eol='')
+ finally:
+ _StopTracing(controllers)
+ if interval:
+ _PrintMessage('done')
- # Wait a bit for the browser to finish writing the trace file.
- time.sleep(interval / 4 + 1)
+ _PullTraces(controllers, output, compress)
- _DownloadLatestTrace(adb, compress, check_for_multiple_traces=False)
-
-def _ComputeCategories(options):
- categories = [options.categories]
+def _ComputeChromeCategories(options):
+ categories = []
if options.trace_cc:
categories.append('disabled-by-default-cc.debug*')
if options.trace_gpu:
categories.append('disabled-by-default-gpu.debug*')
- return ",".join(categories)
+ if options.chrome_categories:
+ categories += options.chrome_categories.split(',')
+ return categories
+
+
+def _ComputeSystraceCategories(options):
+ if not options.systrace_categories:
+ return []
+ return options.systrace_categories.split(',')
def main():
@@ -145,36 +270,40 @@ def main():
'chromium.org/developers/how-tos/trace-event-'
'profiling-tool for detailed instructions for '
'profiling.')
- manual_options = optparse.OptionGroup(parser, 'Manual tracing')
- manual_options.add_option('--start', help='Start tracing.',
- action='store_true')
- manual_options.add_option('--stop', help='Stop tracing.',
- action='store_true')
- manual_options.add_option('-d', '--download', help='Download latest trace.',
- action='store_true')
- parser.add_option_group(manual_options)
-
- auto_options = optparse.OptionGroup(parser, 'Automated tracing')
- auto_options.add_option('-t', '--time', help='Profile for N seconds and '
+
+ timed_options = optparse.OptionGroup(parser, 'Timed tracing')
+ timed_options.add_option('-t', '--time', help='Profile for N seconds and '
'download the resulting trace.', metavar='N',
- type='float')
- parser.add_option_group(auto_options)
+ type='float')
+ parser.add_option_group(timed_options)
+
+ cont_options = optparse.OptionGroup(parser, 'Continuous tracing')
+ cont_options.add_option('--continuous', help='Profile continuously until '
+ 'stopped.', action='store_true')
+ cont_options.add_option('--ring-buffer', help='Use the trace buffer as a '
+ 'ring buffer and save its contents when stopping '
+ 'instead of appending events into one long trace.',
+ action='store_true')
+ parser.add_option_group(cont_options)
categories = optparse.OptionGroup(parser, 'Trace categories')
categories.add_option('-c', '--categories', help='Select categories to trace '
'with comma-delimited wildcards, e.g., '
- '"*", "cat1*,-cat1a". Default is "*".', default='*')
+ '"*", "cat1*,-cat1a". Default is "*".', default='*',
+ dest='chrome_categories')
+ categories.add_option('-s', '--systrace', help='Capture a systrace with the '
+ 'chosen comma-delimited systrace categories. You can '
+ 'also capture a combined Chrome + systrace by enabling '
+ 'both types of categories. Use "list" to see the '
+ 'available categories.', metavar='SYS_CATEGORIES',
+ dest='systrace_categories', default='')
categories.add_option('--trace-cc', help='Enable extra trace categories for '
'compositor frame viewer data.', action='store_true')
categories.add_option('--trace-gpu', help='Enable extra trace categories for '
'GPU data.', action='store_true')
parser.add_option_group(categories)
- parser.add_option('-o', '--output', help='Save profile output to file. '
- 'Default is "/sdcard/Download/chrome-profile-results-*".')
- parser.add_option('--continuous', help='Using the trace buffer as a ring '
- 'buffer, continuously profile until stopped.',
- action='store_true')
+ parser.add_option('-o', '--output', help='Save profile output to file.')
browsers = sorted(_GetSupportedBrowsers().keys())
parser.add_option('-b', '--browser', help='Select among installed browsers. '
'One of ' + ', '.join(browsers) + ', "stable" is used by '
@@ -186,26 +315,45 @@ def main():
'with gzip. ', action='store_true')
options, args = parser.parse_args()
- if not any([options.start, options.stop, options.time, options.download]):
- _PrintMessage('One of start/stop/download/time should be specified.')
- return 1
-
if options.verbose:
logging.getLogger().setLevel(logging.DEBUG)
adb = android_commands.AndroidCommands()
- categories = _ComputeCategories(options)
+ if options.systrace_categories in ['list', 'help']:
+ _PrintMessage('\n'.join(SystraceController.GetCategories(adb)))
+ return 0
+
+ if not options.time and not options.continuous:
+ _PrintMessage('Time interval or continuous tracing should be specified.')
+ return 1
+
+ chrome_categories = _ComputeChromeCategories(options)
+ systrace_categories = _ComputeSystraceCategories(options)
package_info = _GetSupportedBrowsers()[options.browser]
- if options.start:
- _StartTracing(adb, package_info, categories, options.continuous)
- elif options.stop:
- _StopTracing(adb, package_info)
- elif options.download:
- _DownloadLatestTrace(adb, options.compress)
- elif options.time:
- _CaptureAndDownloadTimedTrace(adb, package_info, categories, options.time,
- options.continuous, options.compress)
+ if chrome_categories and 'webview' in systrace_categories:
+ logging.warning('Using the "webview" category in systrace together with '
+ 'Chrome tracing results in duplicate trace events.')
+
+ controllers = []
+ if chrome_categories:
+ controllers.append(ChromeTracingController(adb,
+ package_info,
+ chrome_categories,
+ options.ring_buffer))
+ if systrace_categories:
+ controllers.append(SystraceController(adb,
+ systrace_categories,
+ options.ring_buffer))
+
+ if not controllers:
+ _PrintMessage('No trace categories enabled.')
+ return 1
+
+ _CaptureAndPullTrace(controllers,
+ options.time if not options.continuous else 0,
+ options.output,
+ options.compress)
if __name__ == '__main__':
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698