adb_profile_chrome: Refactor into multiple modules and add tests
The adb_profile_chrome tool has grown quite a bit since its inception, so now seems to be a good time to split it into smaller modules and add some rudimentary tests. BUG=375754 TEST=build/android/chrome_profiler/run_tests NOTRY=true Review URL: https://codereview.chromium.org/290013006 git-svn-id: http://src.chromium.org/svn/trunk/src/build@272946 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
Родитель
71545b826c
Коммит
00187128e2
|
@ -4,472 +4,10 @@
|
|||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import gzip
|
||||
import json
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import shutil
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
import zipfile
|
||||
import zlib
|
||||
|
||||
from pylib import android_commands
|
||||
from pylib import cmd_helper
|
||||
from pylib import constants
|
||||
from pylib import pexpect
|
||||
from pylib.device import device_utils
|
||||
|
||||
_TRACE_VIEWER_ROOT = os.path.join(constants.DIR_SOURCE_ROOT,
|
||||
'third_party', 'trace-viewer')
|
||||
sys.path.append(_TRACE_VIEWER_ROOT)
|
||||
from trace_viewer.build import trace2html # pylint: disable=F0401
|
||||
|
||||
_DEFAULT_CHROME_CATEGORIES = '_DEFAULT_CHROME_CATEGORIES'
|
||||
|
||||
|
||||
def _GetTraceTimestamp():
|
||||
return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
|
||||
|
||||
|
||||
class ChromeTracingController(object):
|
||||
def __init__(self, device, package_info, categories, ring_buffer):
|
||||
self._device = device
|
||||
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._device.old_interface.StartMonitoringLogcat(clear=False)
|
||||
|
||||
def __str__(self):
|
||||
return 'chrome trace'
|
||||
|
||||
@staticmethod
|
||||
def GetCategories(device, package_info):
|
||||
device.old_interface.BroadcastIntent(
|
||||
package_info.package, 'GPU_PROFILER_LIST_CATEGORIES')
|
||||
try:
|
||||
json_category_list = device.old_interface.WaitForLogMatch(
|
||||
re.compile(r'{"traceCategoriesList(.*)'), None, timeout=5).group(0)
|
||||
except pexpect.TIMEOUT:
|
||||
raise RuntimeError('Performance trace category list marker not found. '
|
||||
'Is the correct version of the browser running?')
|
||||
|
||||
record_categories = []
|
||||
disabled_by_default_categories = []
|
||||
json_data = json.loads(json_category_list)['traceCategoriesList']
|
||||
for item in json_data:
|
||||
if item.startswith('disabled-by-default'):
|
||||
disabled_by_default_categories.append(item)
|
||||
else:
|
||||
record_categories.append(item)
|
||||
|
||||
return record_categories, disabled_by_default_categories
|
||||
|
||||
def StartTracing(self, interval):
|
||||
self._trace_interval = interval
|
||||
self._device.old_interface.SyncLogCat()
|
||||
self._device.old_interface.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._device.old_interface.WaitForLogMatch(
|
||||
self._trace_start_re, None, timeout=5)
|
||||
except pexpect.TIMEOUT:
|
||||
raise RuntimeError('Trace start marker not found. Is the correct version '
|
||||
'of the browser running?')
|
||||
|
||||
def StopTracing(self):
|
||||
self._device.old_interface.BroadcastIntent(
|
||||
self._package_info.package,
|
||||
'GPU_PROFILER_STOP')
|
||||
self._trace_file = self._device.old_interface.WaitForLogMatch(
|
||||
self._trace_finish_re, None, timeout=120).group(1)
|
||||
|
||||
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._device.old_interface.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, device, categories, ring_buffer):
|
||||
self._device = device
|
||||
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(device):
|
||||
return device.old_interface.RunShellCommand('atrace --list_categories')
|
||||
|
||||
def StartTracing(self, _):
|
||||
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):
|
||||
# TODO(jbudorick) can this be made work with DeviceUtils?
|
||||
# We use a separate interface to adb because the one from AndroidCommands
|
||||
# isn't re-entrant.
|
||||
device_param = (['-s', self._device.old_interface.GetDevice()]
|
||||
if self._device.old_interface.GetDevice() else [])
|
||||
cmd = ['adb'] + device_param + ['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 = {
|
||||
'stable': constants.PACKAGE_INFO['chrome_stable'],
|
||||
'beta': constants.PACKAGE_INFO['chrome_beta'],
|
||||
'dev': constants.PACKAGE_INFO['chrome_dev'],
|
||||
'build': constants.PACKAGE_INFO['chrome'],
|
||||
}
|
||||
supported_browsers.update(constants.PACKAGE_INFO)
|
||||
unsupported_browsers = ['content_browsertests', 'gtest', 'legacy_browser']
|
||||
for browser in unsupported_browsers:
|
||||
del supported_browsers[browser]
|
||||
return supported_browsers
|
||||
|
||||
|
||||
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 _ArchiveFiles(host_files, output):
|
||||
with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as z:
|
||||
for host_file in host_files:
|
||||
z.write(host_file)
|
||||
os.unlink(host_file)
|
||||
|
||||
|
||||
def _PackageTracesAsHtml(trace_files, html_file):
|
||||
with open(html_file, 'w') as f:
|
||||
trace2html.WriteHTMLForTracesToFile(trace_files, f)
|
||||
for trace_file in trace_files:
|
||||
os.unlink(trace_file)
|
||||
|
||||
|
||||
def _PrintMessage(heading, eol='\n'):
|
||||
sys.stdout.write('%s%s' % (heading, eol))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def _WaitForEnter(timeout):
|
||||
select.select([sys.stdin], [], [], timeout)
|
||||
|
||||
|
||||
def _StartTracing(controllers, interval):
|
||||
for controller in controllers:
|
||||
controller.StartTracing(interval)
|
||||
|
||||
|
||||
def _StopTracing(controllers):
|
||||
for controller in controllers:
|
||||
controller.StopTracing()
|
||||
|
||||
|
||||
def _PullTraces(controllers, output, compress, write_json):
|
||||
_PrintMessage('Downloading...', eol='')
|
||||
trace_files = []
|
||||
for controller in controllers:
|
||||
trace_files.append(controller.PullTrace())
|
||||
|
||||
if not write_json:
|
||||
html_file = os.path.splitext(trace_files[0])[0] + '.html'
|
||||
_PackageTracesAsHtml(trace_files, html_file)
|
||||
trace_files = [html_file]
|
||||
|
||||
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
|
||||
shutil.move(trace_files[0], result)
|
||||
else:
|
||||
result = trace_files[0]
|
||||
|
||||
_PrintMessage('done')
|
||||
_PrintMessage('Trace written to file://%s' % os.path.abspath(result))
|
||||
return result
|
||||
|
||||
|
||||
def _CaptureAndPullTrace(controllers, interval, output, compress, write_json):
|
||||
trace_type = ' + '.join(map(str, controllers))
|
||||
try:
|
||||
_StartTracing(controllers, interval)
|
||||
if interval:
|
||||
_PrintMessage('Capturing %d-second %s. Press Enter to stop early...' % \
|
||||
(interval, trace_type), eol='')
|
||||
_WaitForEnter(interval)
|
||||
else:
|
||||
_PrintMessage('Capturing %s. Press Enter to stop...' % trace_type, eol='')
|
||||
raw_input()
|
||||
finally:
|
||||
_StopTracing(controllers)
|
||||
if interval:
|
||||
_PrintMessage('done')
|
||||
|
||||
return _PullTraces(controllers, output, compress, write_json)
|
||||
|
||||
|
||||
def _ComputeChromeCategories(options):
|
||||
categories = []
|
||||
if options.trace_frame_viewer:
|
||||
categories.append('disabled-by-default-cc.debug')
|
||||
if options.trace_ubercompositor:
|
||||
categories.append('disabled-by-default-cc.debug*')
|
||||
if options.trace_gpu:
|
||||
categories.append('disabled-by-default-gpu.debug*')
|
||||
if options.trace_flow:
|
||||
categories.append('disabled-by-default-toplevel.flow')
|
||||
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():
|
||||
parser = optparse.OptionParser(description='Record about://tracing profiles '
|
||||
'from Android browsers. See http://dev.'
|
||||
'chromium.org/developers/how-tos/trace-event-'
|
||||
'profiling-tool for detailed instructions for '
|
||||
'profiling.')
|
||||
|
||||
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(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 Chrome tracing '
|
||||
'categories with comma-delimited wildcards, '
|
||||
'e.g., "*", "cat1*,-cat1a". Omit this option to trace '
|
||||
'Chrome\'s default categories. Chrome tracing can be '
|
||||
'disabled with "--categories=\'\'". Use "list" to see '
|
||||
'the available categories.',
|
||||
metavar='CHROME_CATEGORIES', dest='chrome_categories',
|
||||
default=_DEFAULT_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. Systrace is disabled by '
|
||||
'default.', metavar='SYS_CATEGORIES',
|
||||
dest='systrace_categories', default='')
|
||||
categories.add_option('--trace-cc',
|
||||
help='Deprecated, use --trace-frame-viewer.',
|
||||
action='store_true')
|
||||
categories.add_option('--trace-frame-viewer',
|
||||
help='Enable enough trace categories for '
|
||||
'compositor frame viewing.', action='store_true')
|
||||
categories.add_option('--trace-ubercompositor',
|
||||
help='Enable enough trace categories for '
|
||||
'ubercompositor frame data.', action='store_true')
|
||||
categories.add_option('--trace-gpu', help='Enable extra trace categories for '
|
||||
'GPU data.', action='store_true')
|
||||
categories.add_option('--trace-flow', help='Enable extra trace categories '
|
||||
'for IPC message flows.', action='store_true')
|
||||
parser.add_option_group(categories)
|
||||
|
||||
output_options = optparse.OptionGroup(parser, 'Output options')
|
||||
output_options.add_option('-o', '--output', help='Save trace output to file.')
|
||||
output_options.add_option('--json', help='Save trace as raw JSON instead of '
|
||||
'HTML.', action='store_true')
|
||||
output_options.add_option('--view', help='Open resulting trace file in a '
|
||||
'browser.', action='store_true')
|
||||
parser.add_option_group(output_options)
|
||||
|
||||
browsers = sorted(_GetSupportedBrowsers().keys())
|
||||
parser.add_option('-b', '--browser', help='Select among installed browsers. '
|
||||
'One of ' + ', '.join(browsers) + ', "stable" is used by '
|
||||
'default.', type='choice', choices=browsers,
|
||||
default='stable')
|
||||
parser.add_option('-v', '--verbose', help='Verbose logging.',
|
||||
action='store_true')
|
||||
parser.add_option('-z', '--compress', help='Compress the resulting trace '
|
||||
'with gzip. ', action='store_true')
|
||||
options, _args = parser.parse_args()
|
||||
if options.trace_cc:
|
||||
parser.parse_error("""--trace-cc is deprecated.
|
||||
|
||||
For basic jank busting uses, use --trace-frame-viewer
|
||||
For detailed study of ubercompositor, pass --trace-ubercompositor.
|
||||
|
||||
When in doubt, just try out --trace-frame-viewer.
|
||||
""")
|
||||
|
||||
if options.verbose:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
devices = android_commands.GetAttachedDevices()
|
||||
if len(devices) != 1:
|
||||
parser.error('Exactly 1 device much be attached.')
|
||||
device = device_utils.DeviceUtils(devices[0])
|
||||
package_info = _GetSupportedBrowsers()[options.browser]
|
||||
|
||||
if options.chrome_categories in ['list', 'help']:
|
||||
_PrintMessage('Collecting record categories list...', eol='')
|
||||
record_categories = []
|
||||
disabled_by_default_categories = []
|
||||
record_categories, disabled_by_default_categories = \
|
||||
ChromeTracingController.GetCategories(device, package_info)
|
||||
|
||||
_PrintMessage('done')
|
||||
_PrintMessage('Record Categories:')
|
||||
_PrintMessage('\n'.join('\t%s' % item \
|
||||
for item in sorted(record_categories)))
|
||||
|
||||
_PrintMessage('\nDisabled by Default Categories:')
|
||||
_PrintMessage('\n'.join('\t%s' % item \
|
||||
for item in sorted(disabled_by_default_categories)))
|
||||
|
||||
return 0
|
||||
|
||||
if options.systrace_categories in ['list', 'help']:
|
||||
_PrintMessage('\n'.join(SystraceController.GetCategories(device)))
|
||||
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)
|
||||
|
||||
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(device,
|
||||
package_info,
|
||||
chrome_categories,
|
||||
options.ring_buffer))
|
||||
if systrace_categories:
|
||||
controllers.append(SystraceController(device,
|
||||
systrace_categories,
|
||||
options.ring_buffer))
|
||||
|
||||
if not controllers:
|
||||
_PrintMessage('No trace categories enabled.')
|
||||
return 1
|
||||
|
||||
if options.output:
|
||||
options.output = os.path.expanduser(options.output)
|
||||
result = _CaptureAndPullTrace(controllers,
|
||||
options.time if not options.continuous else 0,
|
||||
options.output,
|
||||
options.compress,
|
||||
options.json)
|
||||
if options.view:
|
||||
if sys.platform == 'darwin':
|
||||
os.system('/usr/bin/open %s' % os.path.abspath(result))
|
||||
else:
|
||||
webbrowser.open(result)
|
||||
from chrome_profiler import main
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
sys.exit(main.main())
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
|
@ -0,0 +1,90 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
from chrome_profiler import controllers
|
||||
|
||||
from pylib import pexpect
|
||||
|
||||
|
||||
class ChromeTracingController(controllers.BaseController):
|
||||
def __init__(self, device, package_info, categories, ring_buffer):
|
||||
controllers.BaseController.__init__(self)
|
||||
self._device = device
|
||||
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._device.old_interface.StartMonitoringLogcat(clear=False)
|
||||
|
||||
def __repr__(self):
|
||||
return 'chrome trace'
|
||||
|
||||
@staticmethod
|
||||
def GetCategories(device, package_info):
|
||||
device.old_interface.BroadcastIntent(
|
||||
package_info.package, 'GPU_PROFILER_LIST_CATEGORIES')
|
||||
try:
|
||||
json_category_list = device.old_interface.WaitForLogMatch(
|
||||
re.compile(r'{"traceCategoriesList(.*)'), None, timeout=5).group(0)
|
||||
except pexpect.TIMEOUT:
|
||||
raise RuntimeError('Performance trace category list marker not found. '
|
||||
'Is the correct version of the browser running?')
|
||||
|
||||
record_categories = []
|
||||
disabled_by_default_categories = []
|
||||
json_data = json.loads(json_category_list)['traceCategoriesList']
|
||||
for item in json_data:
|
||||
if item.startswith('disabled-by-default'):
|
||||
disabled_by_default_categories.append(item)
|
||||
else:
|
||||
record_categories.append(item)
|
||||
|
||||
return record_categories, disabled_by_default_categories
|
||||
|
||||
def StartTracing(self, interval):
|
||||
self._trace_interval = interval
|
||||
self._device.old_interface.SyncLogCat()
|
||||
self._device.old_interface.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._device.old_interface.WaitForLogMatch(
|
||||
self._trace_start_re, None, timeout=5)
|
||||
except pexpect.TIMEOUT:
|
||||
raise RuntimeError('Trace start marker not found. Is the correct version '
|
||||
'of the browser running?')
|
||||
|
||||
def StopTracing(self):
|
||||
self._device.old_interface.BroadcastIntent(
|
||||
self._package_info.package,
|
||||
'GPU_PROFILER_STOP')
|
||||
self._trace_file = self._device.old_interface.WaitForLogMatch(
|
||||
self._trace_finish_re, None, timeout=120).group(1)
|
||||
|
||||
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._device.old_interface.PullFileFromDevice(trace_file, host_file)
|
||||
return host_file
|
|
@ -0,0 +1,46 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from chrome_profiler import chrome_controller
|
||||
from chrome_profiler import controllers_unittest
|
||||
|
||||
|
||||
class ChromeControllerTest(controllers_unittest.BaseControllerTest):
|
||||
def testGetCategories(self):
|
||||
# Not supported on stable yet.
|
||||
# TODO(skyostil): Remove this once category queries roll into stable.
|
||||
if self.browser == 'stable':
|
||||
return
|
||||
|
||||
categories = \
|
||||
chrome_controller.ChromeTracingController.GetCategories(
|
||||
self.device, self.package_info)
|
||||
|
||||
self.assertEquals(len(categories), 2)
|
||||
self.assertTrue(categories[0])
|
||||
self.assertTrue(categories[1])
|
||||
|
||||
def testTracing(self):
|
||||
categories = '*'
|
||||
ring_buffer = False
|
||||
controller = chrome_controller.ChromeTracingController(self.device,
|
||||
self.package_info,
|
||||
categories,
|
||||
ring_buffer)
|
||||
|
||||
interval = 1
|
||||
try:
|
||||
controller.StartTracing(interval)
|
||||
finally:
|
||||
controller.StopTracing()
|
||||
|
||||
result = controller.PullTrace()
|
||||
try:
|
||||
with open(result) as f:
|
||||
json.loads(f.read())
|
||||
finally:
|
||||
os.remove(result)
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import exceptions
|
||||
|
||||
# pylint: disable=R0201
|
||||
class BaseController(object):
|
||||
def StartTracing(self, _):
|
||||
raise exceptions.NotImplementError
|
||||
|
||||
def StopTracing(self):
|
||||
raise exceptions.NotImplementError
|
||||
|
||||
def PullTrace(self):
|
||||
raise exceptions.NotImplementError
|
|
@ -0,0 +1,23 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
from chrome_profiler import profiler
|
||||
|
||||
from pylib import android_commands
|
||||
from pylib.device import device_utils
|
||||
|
||||
|
||||
class BaseControllerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
devices = android_commands.GetAttachedDevices()
|
||||
self.browser = 'stable'
|
||||
self.package_info = profiler.GetSupportedBrowsers()[self.browser]
|
||||
self.device = device_utils.DeviceUtils(devices[0])
|
||||
|
||||
adb = android_commands.AndroidCommands(devices[0])
|
||||
adb.StartActivity(self.package_info.package,
|
||||
self.package_info.activity,
|
||||
wait_for_completion=True)
|
|
@ -0,0 +1,211 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import webbrowser
|
||||
|
||||
from chrome_profiler import chrome_controller
|
||||
from chrome_profiler import profiler
|
||||
from chrome_profiler import systrace_controller
|
||||
from chrome_profiler import ui
|
||||
|
||||
from pylib import android_commands
|
||||
from pylib.device import device_utils
|
||||
|
||||
|
||||
_DEFAULT_CHROME_CATEGORIES = '_DEFAULT_CHROME_CATEGORIES'
|
||||
|
||||
|
||||
def _ComputeChromeCategories(options):
|
||||
categories = []
|
||||
if options.trace_frame_viewer:
|
||||
categories.append('disabled-by-default-cc.debug')
|
||||
if options.trace_ubercompositor:
|
||||
categories.append('disabled-by-default-cc.debug*')
|
||||
if options.trace_gpu:
|
||||
categories.append('disabled-by-default-gpu.debug*')
|
||||
if options.trace_flow:
|
||||
categories.append('disabled-by-default-toplevel.flow')
|
||||
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 _CreateOptionParser():
|
||||
parser = optparse.OptionParser(description='Record about://tracing profiles '
|
||||
'from Android browsers. See http://dev.'
|
||||
'chromium.org/developers/how-tos/trace-event-'
|
||||
'profiling-tool for detailed instructions for '
|
||||
'profiling.')
|
||||
|
||||
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(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)
|
||||
|
||||
chrome_opts = optparse.OptionGroup(parser, 'Chrome tracing options')
|
||||
chrome_opts.add_option('-c', '--categories', help='Select Chrome tracing '
|
||||
'categories with comma-delimited wildcards, '
|
||||
'e.g., "*", "cat1*,-cat1a". Omit this option to trace '
|
||||
'Chrome\'s default categories. Chrome tracing can be '
|
||||
'disabled with "--categories=\'\'". Use "list" to '
|
||||
'see the available categories.',
|
||||
metavar='CHROME_CATEGORIES', dest='chrome_categories',
|
||||
default=_DEFAULT_CHROME_CATEGORIES)
|
||||
chrome_opts.add_option('--trace-cc',
|
||||
help='Deprecated, use --trace-frame-viewer.',
|
||||
action='store_true')
|
||||
chrome_opts.add_option('--trace-frame-viewer',
|
||||
help='Enable enough trace categories for '
|
||||
'compositor frame viewing.', action='store_true')
|
||||
chrome_opts.add_option('--trace-ubercompositor',
|
||||
help='Enable enough trace categories for '
|
||||
'ubercompositor frame data.', action='store_true')
|
||||
chrome_opts.add_option('--trace-gpu', help='Enable extra trace categories '
|
||||
'for GPU data.', action='store_true')
|
||||
chrome_opts.add_option('--trace-flow', help='Enable extra trace categories '
|
||||
'for IPC message flows.', action='store_true')
|
||||
parser.add_option_group(chrome_opts)
|
||||
|
||||
systrace_opts = optparse.OptionGroup(parser, 'Systrace tracing options')
|
||||
systrace_opts.add_option('-s', '--systrace', help='Capture a systrace with '
|
||||
'the chosen comma-delimited systrace categories. You '
|
||||
'can also capture a combined Chrome + systrace by '
|
||||
'enable both types of categories. Use "list" to see '
|
||||
'the available categories. Systrace is disabled by '
|
||||
'default.', metavar='SYS_CATEGORIES',
|
||||
dest='systrace_categories', default='')
|
||||
parser.add_option_group(systrace_opts)
|
||||
|
||||
output_options = optparse.OptionGroup(parser, 'Output options')
|
||||
output_options.add_option('-o', '--output', help='Save trace output to file.')
|
||||
output_options.add_option('--json', help='Save trace as raw JSON instead of '
|
||||
'HTML.', action='store_true')
|
||||
output_options.add_option('--view', help='Open resulting trace file in a '
|
||||
'browser.', action='store_true')
|
||||
parser.add_option_group(output_options)
|
||||
|
||||
browsers = sorted(profiler.GetSupportedBrowsers().keys())
|
||||
parser.add_option('-b', '--browser', help='Select among installed browsers. '
|
||||
'One of ' + ', '.join(browsers) + ', "stable" is used by '
|
||||
'default.', type='choice', choices=browsers,
|
||||
default='stable')
|
||||
parser.add_option('-v', '--verbose', help='Verbose logging.',
|
||||
action='store_true')
|
||||
parser.add_option('-z', '--compress', help='Compress the resulting trace '
|
||||
'with gzip. ', action='store_true')
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
parser = _CreateOptionParser()
|
||||
options, _args = parser.parse_args()
|
||||
if options.trace_cc:
|
||||
parser.parse_error("""--trace-cc is deprecated.
|
||||
|
||||
For basic jank busting uses, use --trace-frame-viewer
|
||||
For detailed study of ubercompositor, pass --trace-ubercompositor.
|
||||
|
||||
When in doubt, just try out --trace-frame-viewer.
|
||||
""")
|
||||
|
||||
if options.verbose:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
devices = android_commands.GetAttachedDevices()
|
||||
if len(devices) != 1:
|
||||
parser.error('Exactly 1 device must be attached.')
|
||||
device = device_utils.DeviceUtils(devices[0])
|
||||
package_info = profiler.GetSupportedBrowsers()[options.browser]
|
||||
|
||||
if options.chrome_categories in ['list', 'help']:
|
||||
ui.PrintMessage('Collecting record categories list...', eol='')
|
||||
record_categories = []
|
||||
disabled_by_default_categories = []
|
||||
record_categories, disabled_by_default_categories = \
|
||||
chrome_controller.ChromeTracingController.GetCategories(
|
||||
device, package_info)
|
||||
|
||||
ui.PrintMessage('done')
|
||||
ui.PrintMessage('Record Categories:')
|
||||
ui.PrintMessage('\n'.join('\t%s' % item \
|
||||
for item in sorted(record_categories)))
|
||||
|
||||
ui.PrintMessage('\nDisabled by Default Categories:')
|
||||
ui.PrintMessage('\n'.join('\t%s' % item \
|
||||
for item in sorted(disabled_by_default_categories)))
|
||||
|
||||
return 0
|
||||
|
||||
if options.systrace_categories in ['list', 'help']:
|
||||
ui.PrintMessage('\n'.join(
|
||||
systrace_controller.SystraceController.GetCategories(device)))
|
||||
return 0
|
||||
|
||||
if not options.time and not options.continuous:
|
||||
ui.PrintMessage('Time interval or continuous tracing should be specified.')
|
||||
return 1
|
||||
|
||||
chrome_categories = _ComputeChromeCategories(options)
|
||||
systrace_categories = _ComputeSystraceCategories(options)
|
||||
|
||||
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.')
|
||||
|
||||
enabled_controllers = []
|
||||
if chrome_categories:
|
||||
enabled_controllers.append(
|
||||
chrome_controller.ChromeTracingController(device,
|
||||
package_info,
|
||||
chrome_categories,
|
||||
options.ring_buffer))
|
||||
if systrace_categories:
|
||||
enabled_controllers.append(
|
||||
systrace_controller.SystraceController(device,
|
||||
systrace_categories,
|
||||
options.ring_buffer))
|
||||
|
||||
if not enabled_controllers:
|
||||
ui.PrintMessage('No trace categories enabled.')
|
||||
return 1
|
||||
|
||||
if options.output:
|
||||
options.output = os.path.expanduser(options.output)
|
||||
result = profiler.CaptureProfile(
|
||||
enabled_controllers,
|
||||
options.time if not options.continuous else 0,
|
||||
output=options.output,
|
||||
compress=options.compress,
|
||||
write_json=options.json)
|
||||
if options.view:
|
||||
if sys.platform == 'darwin':
|
||||
os.system('/usr/bin/open %s' % os.path.abspath(result))
|
||||
else:
|
||||
webbrowser.open(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
|
@ -0,0 +1,130 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import gzip
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
from chrome_profiler import ui
|
||||
from chrome_profiler import util
|
||||
|
||||
from pylib import constants
|
||||
|
||||
sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
|
||||
'third_party',
|
||||
'trace-viewer'))
|
||||
# pylint: disable=F0401
|
||||
from trace_viewer.build import trace2html
|
||||
|
||||
|
||||
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 _ArchiveFiles(host_files, output):
|
||||
with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as z:
|
||||
for host_file in host_files:
|
||||
z.write(host_file)
|
||||
os.unlink(host_file)
|
||||
|
||||
|
||||
def _PackageTracesAsHtml(trace_files, html_file):
|
||||
with open(html_file, 'w') as f:
|
||||
trace2html.WriteHTMLForTracesToFile(trace_files, f)
|
||||
for trace_file in trace_files:
|
||||
os.unlink(trace_file)
|
||||
|
||||
|
||||
def _StartTracing(controllers, interval):
|
||||
for controller in controllers:
|
||||
controller.StartTracing(interval)
|
||||
|
||||
|
||||
def _StopTracing(controllers):
|
||||
for controller in controllers:
|
||||
controller.StopTracing()
|
||||
|
||||
|
||||
def _PullTraces(controllers, output, compress, write_json):
|
||||
ui.PrintMessage('Downloading...', eol='')
|
||||
trace_files = []
|
||||
for controller in controllers:
|
||||
trace_files.append(controller.PullTrace())
|
||||
|
||||
if not write_json:
|
||||
html_file = os.path.splitext(trace_files[0])[0] + '.html'
|
||||
_PackageTracesAsHtml(trace_files, html_file)
|
||||
trace_files = [html_file]
|
||||
|
||||
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' % util.GetTraceTimestamp()
|
||||
_ArchiveFiles(trace_files, result)
|
||||
elif output:
|
||||
result = output
|
||||
shutil.move(trace_files[0], result)
|
||||
else:
|
||||
result = trace_files[0]
|
||||
|
||||
ui.PrintMessage('done')
|
||||
ui.PrintMessage('Trace written to file://%s' % os.path.abspath(result))
|
||||
return result
|
||||
|
||||
|
||||
def GetSupportedBrowsers():
|
||||
"""Returns the package names of all supported browsers."""
|
||||
# Add aliases for backwards compatibility.
|
||||
supported_browsers = {
|
||||
'stable': constants.PACKAGE_INFO['chrome_stable'],
|
||||
'beta': constants.PACKAGE_INFO['chrome_beta'],
|
||||
'dev': constants.PACKAGE_INFO['chrome_dev'],
|
||||
'build': constants.PACKAGE_INFO['chrome'],
|
||||
}
|
||||
supported_browsers.update(constants.PACKAGE_INFO)
|
||||
unsupported_browsers = ['content_browsertests', 'gtest', 'legacy_browser']
|
||||
for browser in unsupported_browsers:
|
||||
del supported_browsers[browser]
|
||||
return supported_browsers
|
||||
|
||||
|
||||
def CaptureProfile(controllers, interval, output=None, compress=False,
|
||||
write_json=False):
|
||||
"""Records a profiling trace saves the result to a file.
|
||||
|
||||
Args:
|
||||
controllers: List of tracing controllers.
|
||||
interval: Time interval to capture in seconds. An interval of None (or 0)
|
||||
continues tracing until stopped by the user.
|
||||
output: Output file name or None to use an automatically generated name.
|
||||
compress: If True, the result will be compressed either with gzip or zip
|
||||
depending on the number of captured subtraces.
|
||||
write_json: If True, prefer JSON output over HTML.
|
||||
|
||||
Returns:
|
||||
Path to saved profile.
|
||||
"""
|
||||
trace_type = ' + '.join(map(str, controllers))
|
||||
try:
|
||||
_StartTracing(controllers, interval)
|
||||
if interval:
|
||||
ui.PrintMessage('Capturing %d-second %s. Press Enter to stop early...' % \
|
||||
(interval, trace_type), eol='')
|
||||
ui.WaitForEnter(interval)
|
||||
else:
|
||||
ui.PrintMessage('Capturing %s. Press Enter to stop...' % \
|
||||
trace_type, eol='')
|
||||
raw_input()
|
||||
finally:
|
||||
_StopTracing(controllers)
|
||||
if interval:
|
||||
ui.PrintMessage('done')
|
||||
|
||||
return _PullTraces(controllers, output, compress, write_json)
|
|
@ -0,0 +1,78 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
|
||||
from chrome_profiler import profiler
|
||||
from chrome_profiler import ui
|
||||
|
||||
|
||||
class FakeController(object):
|
||||
def __init__(self, contents='fake-contents'):
|
||||
self.contents = contents
|
||||
self.interval = None
|
||||
self.stopped = False
|
||||
self.filename = None
|
||||
|
||||
def StartTracing(self, interval):
|
||||
self.interval = interval
|
||||
|
||||
def StopTracing(self):
|
||||
self.stopped = True
|
||||
|
||||
def PullTrace(self):
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
self.filename = f.name
|
||||
f.write(self.contents)
|
||||
return f.name
|
||||
|
||||
def __repr__(self):
|
||||
return 'faketrace'
|
||||
|
||||
|
||||
class ProfilerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
ui.EnableTestMode()
|
||||
|
||||
def testCaptureBasicProfile(self):
|
||||
controller = FakeController()
|
||||
interval = 1.5
|
||||
result = profiler.CaptureProfile([controller], interval)
|
||||
|
||||
try:
|
||||
self.assertEquals(controller.interval, interval)
|
||||
self.assertTrue(controller.stopped)
|
||||
self.assertTrue(os.path.exists(result))
|
||||
self.assertFalse(os.path.exists(controller.filename))
|
||||
self.assertTrue(result.endswith('.html'))
|
||||
finally:
|
||||
os.remove(result)
|
||||
|
||||
def testCaptureJsonProfile(self):
|
||||
controller = FakeController()
|
||||
result = profiler.CaptureProfile([controller], 1, write_json=True)
|
||||
|
||||
try:
|
||||
self.assertFalse(result.endswith('.html'))
|
||||
with open(result) as f:
|
||||
self.assertEquals(f.read(), controller.contents)
|
||||
finally:
|
||||
os.remove(result)
|
||||
|
||||
def testCaptureMultipleProfiles(self):
|
||||
controllers = [FakeController('c1'), FakeController('c2')]
|
||||
result = profiler.CaptureProfile(controllers, 1, write_json=True)
|
||||
|
||||
try:
|
||||
self.assertTrue(result.endswith('.zip'))
|
||||
self.assertTrue(zipfile.is_zipfile(result))
|
||||
with zipfile.ZipFile(result) as f:
|
||||
self.assertEquals(
|
||||
f.namelist(),
|
||||
[controllers[0].filename[1:], controllers[1].filename[1:]])
|
||||
finally:
|
||||
os.remove(result)
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
cd $(dirname $0)/..
|
||||
exec python -m unittest discover chrome_profiler '*_unittest.py'
|
|
@ -0,0 +1,95 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import threading
|
||||
import zlib
|
||||
|
||||
from chrome_profiler import controllers
|
||||
from chrome_profiler import util
|
||||
|
||||
from pylib import cmd_helper
|
||||
|
||||
|
||||
_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(controllers.BaseController):
|
||||
def __init__(self, device, categories, ring_buffer):
|
||||
controllers.BaseController.__init__(self)
|
||||
self._device = device
|
||||
self._categories = categories
|
||||
self._ring_buffer = ring_buffer
|
||||
self._done = threading.Event()
|
||||
self._thread = None
|
||||
self._trace_data = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'systrace'
|
||||
|
||||
@staticmethod
|
||||
def GetCategories(device):
|
||||
return device.old_interface.RunShellCommand('atrace --list_categories')
|
||||
|
||||
def StartTracing(self, _):
|
||||
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' % util.GetTraceTimestamp()
|
||||
with open(output_name, 'w') as out:
|
||||
out.write(self._trace_data)
|
||||
return output_name
|
||||
|
||||
def _RunATraceCommand(self, command):
|
||||
# TODO(jbudorick) can this be made work with DeviceUtils?
|
||||
# We use a separate interface to adb because the one from AndroidCommands
|
||||
# isn't re-entrant.
|
||||
device_param = (['-s', self._device.old_interface.GetDevice()]
|
||||
if self._device.old_interface.GetDevice() else [])
|
||||
cmd = ['adb'] + device_param + ['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:]
|
|
@ -0,0 +1,36 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import os
|
||||
|
||||
from chrome_profiler import controllers_unittest
|
||||
from chrome_profiler import systrace_controller
|
||||
|
||||
|
||||
class SystraceControllerTest(controllers_unittest.BaseControllerTest):
|
||||
def testGetCategories(self):
|
||||
categories = \
|
||||
systrace_controller.SystraceController.GetCategories(self.device)
|
||||
self.assertTrue(categories)
|
||||
assert 'gfx' in ' '.join(categories)
|
||||
|
||||
def testTracing(self):
|
||||
categories = ['gfx', 'input', 'view']
|
||||
ring_buffer = False
|
||||
controller = systrace_controller.SystraceController(self.device,
|
||||
categories,
|
||||
ring_buffer)
|
||||
|
||||
interval = 1
|
||||
try:
|
||||
controller.StartTracing(interval)
|
||||
finally:
|
||||
controller.StopTracing()
|
||||
|
||||
result = controller.PullTrace()
|
||||
try:
|
||||
with open(result) as f:
|
||||
self.assertTrue('CPU#' in f.read())
|
||||
finally:
|
||||
os.remove(result)
|
|
@ -0,0 +1,25 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import select
|
||||
import sys
|
||||
|
||||
|
||||
def PrintMessage(heading, eol='\n'):
|
||||
sys.stdout.write('%s%s' % (heading, eol))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def WaitForEnter(timeout):
|
||||
select.select([sys.stdin], [], [], timeout)
|
||||
|
||||
|
||||
def EnableTestMode():
|
||||
def NoOp(*_, **__):
|
||||
pass
|
||||
# pylint: disable=W0601
|
||||
global PrintMessage
|
||||
global WaitForEnter
|
||||
PrintMessage = NoOp
|
||||
WaitForEnter = NoOp
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import time
|
||||
|
||||
def GetTraceTimestamp():
|
||||
return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
|
Загрузка…
Ссылка в новой задаче