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

Side by Side 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: Refactored. Support non-default adb device. 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2013 The Chromium Authors. All rights reserved. 3 # Copyright 2013 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 import gzip 7 import gzip
8 import logging 8 import logging
9 import optparse 9 import optparse
10 import os 10 import os
11 import pexpect 11 import pexpect
12 import re 12 import re
13 import subprocess
13 import sys 14 import sys
15 import threading
14 import time 16 import time
17 import zipfile
18 import zlib
15 19
16 from pylib import android_commands 20 from pylib import android_commands
17 from pylib import constants 21 from pylib import constants
18 22
19 23
24 def _GetTraceTimestamp():
25 return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
26
27
28 class ChromeTracingController(object):
29 def __init__(self, adb, package_info, categories, continuous):
30 self._adb = adb
31 self._package_info = package_info
32 self._categories = categories
33 self._continuous = continuous
34 self._trace_file = None
35 self._trace_interval = None
36 self._trace_start_re = re.compile(r'Logging performance trace to file')
37 self._trace_finish_re = \
38 re.compile(r'Profiler finished[.] Results are in (.*)[.]')
39 self._adb.StartMonitoringLogcat(clear=False)
40
41 def __str__(self):
42 return 'Chrome trace'
43
44 def StartTracing(self, interval):
45 self._adb.SyncLogCat()
46 self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_START',
47 '-e categories "%s"' % ','.join(self._categories),
48 '-e continuous' if self._continuous else '')
49 # Chrome logs two different messages related to tracing:
50 #
51 # 1. "Logging performance trace to file [...]"
52 # 2. "Profiler finished. Results are in [...]"
53 #
54 # The first one is printed when tracing starts and the second one indicates
55 # that the trace file is ready to be downloaded.
bulach 2013/10/04 14:30:11 nit: how about s/Download/Pull/g ? I think we norm
56 try:
57 self._adb.WaitForLogMatch(self._trace_start_re, None, timeout=5)
58 except pexpect.TIMEOUT:
59 raise RuntimeError('Trace start marker not found. Is the correct version '
60 'of the browser running?')
61 self._trace_interval = interval
62
63 def StopTracing(self):
64 if not self._trace_interval:
65 return
66 self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_STOP')
67 self._trace_file = self._adb.WaitForLogMatch(self._trace_finish_re, None,
68 timeout=120).group(1)
69
70 def DownloadTrace(self):
71 # Wait a bit for the browser to finish writing the trace file.
72 time.sleep(self._trace_interval / 4 + 1)
73
74 trace_file = self._trace_file.replace('/storage/emulated/0/', '/sdcard/')
75 host_file = os.path.join(os.path.curdir, os.path.basename(trace_file))
76 self._adb.PullFileFromDevice(trace_file, host_file)
77 return host_file
78
79 _SYSTRACE_OPTIONS = [
80 # Compress the trace before sending it over USB.
81 '-z',
82 # Use a large trace buffer to increase the polling interval.
83 '-b', '16384'
84 ]
85
86 class SystraceController(object):
87 def __init__(self, device, categories):
88 self._device = device
89 self._categories = categories
90 self._done = threading.Event()
91 self._thread = None
92 self._trace_data = None
93
94 def __str__(self):
95 return 'Systrace'
96
97 @staticmethod
98 def GetCategories(adb):
99 return adb.RunShellCommand('atrace --list_categories')
100
101 def StartTracing(self, interval):
102 self._thread = threading.Thread(target=self._CollectData)
103 self._thread.start()
104
105 def StopTracing(self):
106 self._done.set()
107
108 def DownloadTrace(self):
109 self._thread.join()
110 self._thread = None
111 if self._trace_data:
112 output_name = 'systrace-%s' % _GetTraceTimestamp()
113 with open(output_name, 'w') as out:
114 out.write(self._trace_data)
115 return output_name
116
117 def _RunATraceCommand(self, command):
118 device = ['-s', self._device] if self._device else []
119 cmd = ['adb'] + device + ['shell', 'atrace', '--%s' % command] + \
120 _SYSTRACE_OPTIONS + self._categories
121 return subprocess.check_output(cmd)
bulach 2013/10/04 14:30:11 comment from tonyg :) there's a pylib/cmd_helper.p
122
123 def _CollectData(self):
124 trace_data = []
125 self._RunATraceCommand('async_start')
126 try:
127 while not self._done.is_set():
128 self._done.wait(15)
129 trace_data.append(
130 self._DecodeTraceData(self._RunATraceCommand('async_dump')))
131 finally:
132 trace_data.append(
133 self._DecodeTraceData(self._RunATraceCommand('async_stop')))
134 self._trace_data = ''.join([zlib.decompress(d) for d in trace_data])
135
136 @staticmethod
137 def _DecodeTraceData(trace_data):
138 try:
139 trace_start = trace_data.index('TRACE:')
140 except ValueError:
141 raise RuntimeError('Systrace start marker not found')
142 trace_data = trace_data[trace_start + 6:]
143
144 # Collapse CRLFs that are added by adb shell.
145 if trace_data.startswith('\r\n'):
146 trace_data = trace_data.replace('\r\n', '\n')
147
148 # Skip the initial newline.
149 return trace_data[1:]
150
151
20 def _GetSupportedBrowsers(): 152 def _GetSupportedBrowsers():
21 # Add aliases for backwards compatibility. 153 # Add aliases for backwards compatibility.
22 supported_browsers = { 154 supported_browsers = {
23 'stable': constants.PACKAGE_INFO['chrome_stable'], 155 'stable': constants.PACKAGE_INFO['chrome_stable'],
24 'beta': constants.PACKAGE_INFO['chrome_beta'], 156 'beta': constants.PACKAGE_INFO['chrome_beta'],
25 'dev': constants.PACKAGE_INFO['chrome_dev'], 157 'dev': constants.PACKAGE_INFO['chrome_dev'],
26 'build': constants.PACKAGE_INFO['chrome'], 158 'build': constants.PACKAGE_INFO['chrome'],
27 } 159 }
28 supported_browsers.update(constants.PACKAGE_INFO) 160 supported_browsers.update(constants.PACKAGE_INFO)
29 unsupported_browsers = ['content_browsertests', 'gtest', 'legacy_browser'] 161 unsupported_browsers = ['content_browsertests', 'gtest', 'legacy_browser']
30 for browser in unsupported_browsers: 162 for browser in unsupported_browsers:
31 del supported_browsers[browser] 163 del supported_browsers[browser]
32 return supported_browsers 164 return supported_browsers
33 165
34 166
35 def _StartTracing(adb, package_info, categories, continuous): 167 def _CompressFile(host_file, output):
36 adb.BroadcastIntent(package_info.package, 'GPU_PROFILER_START', 168 with gzip.open(output, 'wb') as out:
37 '-e categories "%s"' % categories,
38 '-e continuous' if continuous else '')
39
40
41 def _StopTracing(adb, package_info):
42 adb.BroadcastIntent(package_info.package, 'GPU_PROFILER_STOP')
43
44
45 def _GetLatestTraceFileName(adb, check_for_multiple_traces=True):
46 # Chrome logs two different messages related to tracing:
47 #
48 # 1. "Logging performance trace to file [...]"
49 # 2. "Profiler finished. Results are in [...]"
50 #
51 # The first one is printed when tracing starts and the second one indicates
52 # that the trace file is ready to be downloaded.
53 #
54 # We have to look for both of these messages to make sure we get the results
55 # from the latest trace and that the trace file is complete.
56 trace_start_re = re.compile(r'Logging performance trace to file')
57 trace_finish_re = re.compile(r'Profiler finished[.] Results are in (.*)[.]')
58
59 start_timeout = 5
60 start_match = None
61 finish_match = None
62
63 while True:
64 try:
65 start_match = adb.WaitForLogMatch(trace_start_re, None,
66 timeout=start_timeout)
67 except pexpect.TIMEOUT:
68 if start_match:
69 break
70 raise RuntimeError('Trace start marker not found. Is the correct version '
71 'of the browser running?')
72 finish_match = adb.WaitForLogMatch(trace_finish_re, None, timeout=120)
73 if not check_for_multiple_traces:
74 break
75 # Now that we've found one trace file, switch to polling for the rest of the
76 # log to see if there are more.
77 start_timeout = 1
78 return finish_match.group(1)
79
80
81 def _DownloadTrace(adb, trace_file):
82 trace_file = trace_file.replace('/storage/emulated/0/', '/sdcard/')
83 host_file = os.path.abspath(os.path.basename(trace_file))
84 adb.PullFileFromDevice(trace_file, host_file)
85 return host_file
86
87
88 def _CompressFile(host_file):
89 compressed_file = host_file + '.gz'
90 with gzip.open(compressed_file, 'wb') as out:
91 with open(host_file, 'rb') as input_file: 169 with open(host_file, 'rb') as input_file:
92 out.write(input_file.read()) 170 out.write(input_file.read())
93 os.unlink(host_file) 171 os.unlink(host_file)
94 return compressed_file 172
173
174 def _ArchiveFiles(host_files, output):
175 with zipfile.ZipFile(output, 'w') as z:
176 for host_file in host_files:
177 z.write(host_file)
178 os.unlink(host_file)
95 179
96 180
97 def _PrintMessage(heading, eol='\n'): 181 def _PrintMessage(heading, eol='\n'):
98 sys.stdout.write(heading + eol) 182 sys.stdout.write('%s%s' % (heading, eol))
99 sys.stdout.flush() 183 sys.stdout.flush()
100 184
101 185
102 def _DownloadLatestTrace(adb, compress, check_for_multiple_traces=True): 186 def _StartTracing(controllers, interval):
103 _PrintMessage('Downloading trace...', eol='') 187 for controller in controllers:
104 trace_file = _GetLatestTraceFileName(adb, check_for_multiple_traces) 188 controller.StartTracing(interval)
105 host_file = _DownloadTrace(adb, trace_file)
106 if compress:
107 host_file = _CompressFile(host_file)
108 _PrintMessage('done')
109 _PrintMessage('Trace written to %s' % host_file)
110 189
111 190
112 def _CaptureAndDownloadTimedTrace(adb, package_info, categories, interval, 191 def _StopTracing(controllers):
113 continuous, compress): 192 for controller in controllers:
114 adb.StartMonitoringLogcat(clear=False) 193 controller.StopTracing()
115 adb.SyncLogCat()
116 194
195
196 def _DownloadTraces(controllers, output, compress):
197 _PrintMessage('Downloading...', eol='')
198 trace_files = []
199 for controller in controllers:
200 trace_files.append(controller.DownloadTrace())
201
202 if compress and len(trace_files) == 1:
203 result = output or trace_files[0] + '.gz'
204 _CompressFile(trace_files[0], result)
205 elif len(trace_files) > 1:
206 result = output or 'chrome-combined-trace-%s.zip' % _GetTraceTimestamp()
207 _ArchiveFiles(trace_files, result)
208 elif output:
209 result = output
210 os.rename(trace_files[0], result)
211 else:
212 result = trace_files[0]
213
214 _PrintMessage('done')
215 _PrintMessage('Trace written to %s' % os.path.abspath(result))
216
217
218 def _CaptureAndDownloadTimedTrace(controllers, interval, output, compress):
219 trace_type = ' + '.join(map(str, controllers))
117 try: 220 try:
118 _PrintMessage('Capturing %d-second trace. Press Ctrl-C to stop early...' \ 221 _StartTracing(controllers, interval)
119 % interval, eol='') 222 _PrintMessage('Capturing %d-second %s. Press Ctrl-C to stop early...' % \
120 _StartTracing(adb, package_info, categories, continuous) 223 (interval, trace_type), eol='')
121 time.sleep(interval) 224 time.sleep(interval)
122 except KeyboardInterrupt: 225 except KeyboardInterrupt:
123 _PrintMessage('\nInterrupted, stopping...', eol='') 226 _PrintMessage('\nInterrupted...', eol='')
124 _StopTracing(adb, package_info) 227 finally:
228 _StopTracing(controllers)
125 _PrintMessage('done') 229 _PrintMessage('done')
126 230
127 # Wait a bit for the browser to finish writing the trace file. 231 _DownloadTraces(controllers, output, compress)
128 time.sleep(interval / 4 + 1)
129
130 _DownloadLatestTrace(adb, compress, check_for_multiple_traces=False)
131 232
132 233
133 def _ComputeCategories(options): 234 def _ComputeChromeCategories(options):
134 categories = [options.categories] 235 categories = []
135 if options.trace_cc: 236 if options.trace_cc:
136 categories.append('disabled-by-default-cc.debug*') 237 categories.append('disabled-by-default-cc.debug*')
137 if options.trace_gpu: 238 if options.trace_gpu:
138 categories.append('disabled-by-default-gpu.debug*') 239 categories.append('disabled-by-default-gpu.debug*')
139 return ",".join(categories) 240 if options.chrome_categories:
241 categories += options.chrome_categories.split(',')
242 return categories
243
244
245 def _ComputeSystraceCategories(options):
246 if not options.systrace_categories:
247 return []
248 return options.systrace_categories.split(',')
140 249
141 250
142 def main(): 251 def main():
143 parser = optparse.OptionParser(description='Record about://tracing profiles ' 252 parser = optparse.OptionParser(description='Record about://tracing profiles '
144 'from Android browsers. See http://dev.' 253 'from Android browsers. See http://dev.'
145 'chromium.org/developers/how-tos/trace-event-' 254 'chromium.org/developers/how-tos/trace-event-'
146 'profiling-tool for detailed instructions for ' 255 'profiling-tool for detailed instructions for '
147 'profiling.') 256 'profiling.')
148 manual_options = optparse.OptionGroup(parser, 'Manual tracing')
149 manual_options.add_option('--start', help='Start tracing.',
150 action='store_true')
151 manual_options.add_option('--stop', help='Stop tracing.',
152 action='store_true')
153 manual_options.add_option('-d', '--download', help='Download latest trace.',
154 action='store_true')
155 parser.add_option_group(manual_options)
156 257
157 auto_options = optparse.OptionGroup(parser, 'Automated tracing') 258 auto_options = optparse.OptionGroup(parser, 'Timed tracing')
158 auto_options.add_option('-t', '--time', help='Profile for N seconds and ' 259 auto_options.add_option('-t', '--time', help='Profile for N seconds and '
159 'download the resulting trace.', metavar='N', 260 'download the resulting trace.', metavar='N',
160 type='float') 261 type='float')
161 parser.add_option_group(auto_options) 262 parser.add_option_group(auto_options)
162 263
163 categories = optparse.OptionGroup(parser, 'Trace categories') 264 categories = optparse.OptionGroup(parser, 'Trace categories')
164 categories.add_option('-c', '--categories', help='Select categories to trace ' 265 categories.add_option('-c', '--categories', help='Select categories to trace '
165 'with comma-delimited wildcards, e.g., ' 266 'with comma-delimited wildcards, e.g., '
166 '"*", "cat1*,-cat1a". Default is "*".', default='*') 267 '"*", "cat1*,-cat1a". Default is "*".', default='*',
268 dest='chrome_categories')
269 categories.add_option('-s', '--systrace', help='Capture a systrace with the '
270 'chosen comma-delimited systrace categories. You can '
271 'also capture a combined Chrome + systrace by enabling '
272 'both types of categories. Use "list" to see the '
273 'available categories.', metavar='SYS_CATEGORIES',
274 dest='systrace_categories', default='')
167 categories.add_option('--trace-cc', help='Enable extra trace categories for ' 275 categories.add_option('--trace-cc', help='Enable extra trace categories for '
168 'compositor frame viewer data.', action='store_true') 276 'compositor frame viewer data.', action='store_true')
169 categories.add_option('--trace-gpu', help='Enable extra trace categories for ' 277 categories.add_option('--trace-gpu', help='Enable extra trace categories for '
170 'GPU data.', action='store_true') 278 'GPU data.', action='store_true')
171 parser.add_option_group(categories) 279 parser.add_option_group(categories)
172 280
173 parser.add_option('-o', '--output', help='Save profile output to file. ' 281 parser.add_option('-o', '--output', help='Save profile output to file.')
174 'Default is "/sdcard/Download/chrome-profile-results-*".')
175 parser.add_option('--continuous', help='Using the trace buffer as a ring ' 282 parser.add_option('--continuous', help='Using the trace buffer as a ring '
176 'buffer, continuously profile until stopped.', 283 'buffer, continuously profile until stopped.',
177 action='store_true') 284 action='store_true')
178 browsers = sorted(_GetSupportedBrowsers().keys()) 285 browsers = sorted(_GetSupportedBrowsers().keys())
179 parser.add_option('-b', '--browser', help='Select among installed browsers. ' 286 parser.add_option('-b', '--browser', help='Select among installed browsers. '
180 'One of ' + ', '.join(browsers) + ', "stable" is used by ' 287 'One of ' + ', '.join(browsers) + ', "stable" is used by '
181 'default.', type='choice', choices=browsers, 288 'default.', type='choice', choices=browsers,
182 default='stable') 289 default='stable')
183 parser.add_option('-v', '--verbose', help='Verbose logging.', 290 parser.add_option('-v', '--verbose', help='Verbose logging.',
184 action='store_true') 291 action='store_true')
185 parser.add_option('-z', '--compress', help='Compress the resulting trace ' 292 parser.add_option('-z', '--compress', help='Compress the resulting trace '
186 'with gzip. ', action='store_true') 293 'with gzip. ', action='store_true')
187 options, args = parser.parse_args() 294 options, args = parser.parse_args()
188 295
189 if not any([options.start, options.stop, options.time, options.download]):
190 _PrintMessage('One of start/stop/download/time should be specified.')
191 return 1
192
193 if options.verbose: 296 if options.verbose:
194 logging.getLogger().setLevel(logging.DEBUG) 297 logging.getLogger().setLevel(logging.DEBUG)
195 298
196 adb = android_commands.AndroidCommands() 299 adb = android_commands.AndroidCommands()
197 categories = _ComputeCategories(options) 300 if options.systrace_categories in ['list', 'help']:
301 _PrintMessage('\n'.join(SystraceController.GetCategories(adb)))
302 return 0
303
304 if not options.time:
bulach 2013/10/04 14:30:11 how does this play with --continuous? should we re
305 _PrintMessage('Trace time interval should be specified.')
306 return 1
307
308 chrome_categories = _ComputeChromeCategories(options)
309 systrace_categories = _ComputeSystraceCategories(options)
198 package_info = _GetSupportedBrowsers()[options.browser] 310 package_info = _GetSupportedBrowsers()[options.browser]
199 311
200 if options.start: 312 if chrome_categories and 'webview' in systrace_categories:
201 _StartTracing(adb, package_info, categories, options.continuous) 313 logging.warning('Using the "webview" category in systrace together with '
202 elif options.stop: 314 'Chrome tracing results in duplicate trace events.')
203 _StopTracing(adb, package_info) 315
204 elif options.download: 316 controllers = []
205 _DownloadLatestTrace(adb, options.compress) 317 if chrome_categories:
206 elif options.time: 318 controllers.append(ChromeTracingController(adb,
207 _CaptureAndDownloadTimedTrace(adb, package_info, categories, options.time, 319 package_info,
208 options.continuous, options.compress) 320 chrome_categories,
321 options.continuous))
322 if systrace_categories:
323 controllers.append(SystraceController(adb.GetDevice(), systrace_categories))
324
325 if not controllers:
326 _PrintMessage('No trace categories enabled.')
327 return 1
328
329 _CaptureAndDownloadTimedTrace(controllers, options.time, options.output,
330 options.compress)
209 331
210 332
211 if __name__ == '__main__': 333 if __name__ == '__main__':
212 sys.exit(main()) 334 sys.exit(main())
OLDNEW
« 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