android: Reimplement adb_profile_chrome in Python
adb_profile_chrome is used to capture and download traces from Chrome on Android. To prepare for future feature improvements and reduce the maintenance burden, rewrite this bash script in Python. This reimplementation keeps all the features and command line options of the original and adds these new ones: --trace-cc Enable extra trace categories for compositor frame viewer data. --trace-gpu Enable extra trace categories for GPU data. -z, --compress Compress the resulting trace with gzip. BUG=299822,294883 Review URL: https://codereview.chromium.org/25044004 git-svn-id: http://src.chromium.org/svn/trunk/src/build@226259 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
Родитель
9fa86cb4e9
Коммит
a33e294a3c
|
@ -5,169 +5,4 @@
|
|||
# found in the LICENSE file.
|
||||
#
|
||||
# Start / stop profiling in chrome.
|
||||
|
||||
# The profiling data is saved to directory /sdcard/Download. The files
|
||||
# are named beginning chrome-profile-results-
|
||||
#
|
||||
# Assumes you have sourced the android build environment script
|
||||
# (e.g. 'adb' is on your path).
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo "adb_profile_chrome [--start [-o file] [-c C]|--stop|-d|-t N] [-v N]"
|
||||
echo "See http://dev.chromium.org/developers/how-tos/trace-event-profiling-tool for detailed instructions on profiling."
|
||||
echo ""
|
||||
echo " --start Start profiling."
|
||||
echo " --output|-o file Save profile output to file. "
|
||||
echo " (Default is /sdcard/Download/chrome-profile-results-*)"
|
||||
echo " --categories|-c C Select categories to trace with comma-delimited wildcards."
|
||||
echo " e.g. '*', 'cat1*,-cat1a'. Default is '*'."
|
||||
echo " --continuous Using the trace buffer as a ring buffer, continuously"
|
||||
echo " profile until stopped."
|
||||
echo " --stop Stop profiling."
|
||||
echo " --download|-d Download latest trace."
|
||||
echo " --time|-t N Profile for N seconds and download the resulting trace."
|
||||
echo " --version|v N Select among installed browsers."
|
||||
echo " One of stable (default), beta, dev, build"
|
||||
echo ""
|
||||
echo "Profiling data is saved to the device."
|
||||
exit 0
|
||||
}
|
||||
|
||||
send_intent() {
|
||||
local PACKAGE=$1
|
||||
local INTENT=$2
|
||||
shift
|
||||
shift
|
||||
adb shell am broadcast -a $PACKAGE.$INTENT $*
|
||||
}
|
||||
|
||||
download_latest_trace() {
|
||||
(adb logcat -d | grep -q "Logging performance trace to file") || {
|
||||
echo "WARNING: Trace start marker not found. Is the correct version of Chrome running?"
|
||||
}
|
||||
|
||||
local ITERATION=0
|
||||
while true; do
|
||||
# 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. This is done by
|
||||
# first looking for the last instance of the first message and then checking
|
||||
# for the second message in the remaining part of the log.
|
||||
TRACE_FILE=$(adb logcat -d | \
|
||||
tac | \
|
||||
grep --max-count=1 --before-context=100000 "Logging performance trace to file" | \
|
||||
tac | \
|
||||
grep "Profiler finished[.] Results are in " | \
|
||||
perl -pi -e "s{.*/storage/emulated/.+/([^\r]+)[.].*}{/sdcard/Download/\\1}g")
|
||||
if [ -n "$TRACE_FILE" ]; then
|
||||
break
|
||||
fi
|
||||
if [ $ITERATION -eq 0 ]; then
|
||||
echo -n "Waiting for Chrome to finish tracing..."
|
||||
else
|
||||
echo -n "."
|
||||
fi
|
||||
let ITERATION=ITERATION+1
|
||||
if [ $ITERATION -eq 60 ]; then
|
||||
echo "Timed out"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
adb pull $TRACE_FILE 2> /dev/null
|
||||
LOCAL_TRACE_FILE=$(basename $TRACE_FILE)
|
||||
if [ ! -f "$LOCAL_TRACE_FILE" ]; then
|
||||
echo "Unable to download trace file"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
do_timed_capture() {
|
||||
local PACKAGE=$1
|
||||
local INTERVAL=$2
|
||||
shift
|
||||
shift
|
||||
echo -n "Capturing trace..."
|
||||
send_intent ${PACKAGE} "GPU_PROFILER_START" $* > /dev/null
|
||||
sleep ${INTERVAL}
|
||||
send_intent ${PACKAGE} "GPU_PROFILER_STOP" > /dev/null
|
||||
echo "done"
|
||||
|
||||
echo -n "Downloading trace..."
|
||||
sleep $[${INTERVAL} / 4 + 1]
|
||||
download_latest_trace
|
||||
echo "done"
|
||||
|
||||
echo "Trace written to ${PWD}/${LOCAL_TRACE_FILE}"
|
||||
}
|
||||
|
||||
PACKAGE=${DEFAULT_PACKAGE:-com.android.chrome}
|
||||
|
||||
while test -n "$1"; do
|
||||
case "$1" in
|
||||
-v|--version)
|
||||
if [[ -z "$2" ]] ; then
|
||||
usage
|
||||
fi
|
||||
shift
|
||||
case "$1" in
|
||||
stable) PACKAGE="com.android.chrome" ;;
|
||||
beta) PACKAGE="com.chrome.beta" ;;
|
||||
dev) PACKAGE="com.google.android.apps.chrome_dev" ;;
|
||||
build) PACKAGE="com.google.android.apps.chrome" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
;;
|
||||
--start) FUNCTION="GPU_PROFILER_START" ;;
|
||||
--stop) FUNCTION="GPU_PROFILER_STOP" ;;
|
||||
-o|--output)
|
||||
if [ -z "$2" ] ; then
|
||||
usage
|
||||
fi
|
||||
OUTPUT="-e file '$2'"
|
||||
shift
|
||||
;;
|
||||
-c|--categories)
|
||||
if [ -z "$2" ]; then
|
||||
usage
|
||||
fi
|
||||
CATEGORIES="-e categories '$2'"
|
||||
shift
|
||||
;;
|
||||
--continuous) CONTINUOUS="-e continuous ." ;;
|
||||
-t|--time)
|
||||
shift
|
||||
if [ -z "$1" ] ; then
|
||||
usage
|
||||
fi
|
||||
INTERVAL="$1"
|
||||
;;
|
||||
-d|--download)
|
||||
shift
|
||||
download_latest_trace
|
||||
echo "Trace written to ${PWD}/${LOCAL_TRACE_FILE}"
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "${INTERVAL}" ] ; then
|
||||
if [ -z "${FUNCTION}" ] ; then
|
||||
usage
|
||||
else
|
||||
send_intent ${PACKAGE} ${FUNCTION} ${OUTPUT} ${CATEGORIES} ${CONTINUOUS}
|
||||
fi
|
||||
else
|
||||
do_timed_capture ${PACKAGE} ${INTERVAL} ${CATEGORIES} ${CONTINUOUS}
|
||||
fi
|
||||
exit 0
|
||||
exec $(dirname $0)/adb_profile_chrome.py $@
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2013 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 logging
|
||||
import optparse
|
||||
import os
|
||||
import pexpect
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pylib import android_commands
|
||||
from pylib import constants
|
||||
|
||||
|
||||
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 _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 _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 (.*)[.]')
|
||||
|
||||
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 _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 _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 _PrintMessage(heading, eol='\n'):
|
||||
sys.stdout.write(heading + eol)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def _CaptureAndDownloadTimedTrace(adb, package_info, categories, interval,
|
||||
continuous, compress):
|
||||
adb.StartMonitoringLogcat(clear=False)
|
||||
adb.SyncLogCat()
|
||||
|
||||
try:
|
||||
_PrintMessage('Capturing %d-second trace. Press Ctrl-C to stop early...' \
|
||||
% interval, eol='')
|
||||
_StartTracing(adb, package_info, categories, continuous)
|
||||
time.sleep(interval)
|
||||
except KeyboardInterrupt:
|
||||
_PrintMessage('\nInterrupted, stopping...', eol='')
|
||||
_StopTracing(adb, package_info)
|
||||
_PrintMessage('done')
|
||||
|
||||
# Wait a bit for the browser to finish writing the trace file.
|
||||
time.sleep(interval / 4 + 1)
|
||||
|
||||
_DownloadLatestTrace(adb, compress, check_for_multiple_traces=False)
|
||||
|
||||
|
||||
def _ComputeCategories(options):
|
||||
categories = [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)
|
||||
|
||||
|
||||
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.')
|
||||
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 '
|
||||
'download the resulting trace.', metavar='N',
|
||||
type='float')
|
||||
parser.add_option_group(auto_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='*')
|
||||
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')
|
||||
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 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)
|
||||
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 __name__ == '__main__':
|
||||
sys.exit(main())
|
|
@ -724,6 +724,17 @@ class AndroidCommands(object):
|
|||
start_line = m.group(0)
|
||||
return GetLogTimestamp(start_line, self.GetDeviceYear())
|
||||
|
||||
def BroadcastIntent(self, package, intent, *args):
|
||||
"""Send a broadcast intent.
|
||||
|
||||
Args:
|
||||
package: Name of package containing the intent.
|
||||
intent: Name of the intent.
|
||||
args: Optional extra arguments for the intent.
|
||||
"""
|
||||
cmd = 'am broadcast -a %s.%s %s' % (package, intent, ' '.join(args))
|
||||
self.RunShellCommand(cmd)
|
||||
|
||||
def GoHome(self):
|
||||
"""Tell the device to return to the home screen. Blocks until completion."""
|
||||
self.RunShellCommand('am start -W '
|
||||
|
@ -1144,19 +1155,31 @@ class AndroidCommands(object):
|
|||
if logfile:
|
||||
logfile = NewLineNormalizer(logfile)
|
||||
|
||||
# Spawn logcat and syncronize with it.
|
||||
# Spawn logcat and synchronize with it.
|
||||
for _ in range(4):
|
||||
self._logcat = pexpect.spawn(constants.ADB_PATH, args, timeout=10,
|
||||
logfile=logfile)
|
||||
self.RunShellCommand('log startup_sync')
|
||||
if self._logcat.expect(['startup_sync', pexpect.EOF,
|
||||
pexpect.TIMEOUT]) == 0:
|
||||
if not clear or self.SyncLogCat():
|
||||
break
|
||||
self._logcat.close(force=True)
|
||||
else:
|
||||
logging.critical('Error reading from logcat: ' + str(self._logcat.match))
|
||||
sys.exit(1)
|
||||
|
||||
def SyncLogCat(self):
|
||||
"""Synchronize with logcat.
|
||||
|
||||
Synchronize with the monitored logcat so that WaitForLogMatch will only
|
||||
consider new message that are received after this point in time.
|
||||
|
||||
Returns:
|
||||
True if the synchronization succeeded.
|
||||
"""
|
||||
assert self._logcat
|
||||
tag = 'logcat_sync_%s' % time.time()
|
||||
self.RunShellCommand('log ' + tag)
|
||||
return self._logcat.expect([tag, pexpect.EOF, pexpect.TIMEOUT]) == 0
|
||||
|
||||
def GetMonitoredLogCat(self):
|
||||
"""Returns an "adb logcat" command as created by pexpected.spawn."""
|
||||
if not self._logcat:
|
||||
|
@ -1510,6 +1533,15 @@ class AndroidCommands(object):
|
|||
os.makedirs(host_dir)
|
||||
device_file = '%s/screenshot.png' % self.GetExternalStorage()
|
||||
self.RunShellCommand('/system/bin/screencap -p %s' % device_file)
|
||||
self.PullFileFromDevice(device_file, host_file)
|
||||
|
||||
def PullFileFromDevice(self, device_file, host_file):
|
||||
"""Download |device_file| on the device from to |host_file| on the host.
|
||||
|
||||
Args:
|
||||
device_file: Absolute path to the file to retrieve from the device.
|
||||
host_file: Absolute path to the file to store on the host.
|
||||
"""
|
||||
assert self._adb.Pull(device_file, host_file)
|
||||
assert os.path.exists(host_file)
|
||||
|
||||
|
|
|
@ -43,6 +43,12 @@ PACKAGE_INFO = {
|
|||
'/data/local/chrome-command-line',
|
||||
'chrome_devtools_remote',
|
||||
None),
|
||||
'chrome_dev': PackageInfo(
|
||||
'com.google.android.apps.chrome_dev',
|
||||
'com.google.android.apps.chrome.Main',
|
||||
'/data/local/chrome-command-line',
|
||||
'chrome_devtools_remote',
|
||||
None),
|
||||
'legacy_browser': PackageInfo(
|
||||
'com.google.android.browser',
|
||||
'com.android.browser.BrowserActivity',
|
||||
|
|
Загрузка…
Ссылка в новой задаче