| OLD | NEW |
| 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 re | 11 import re |
| 12 import select | 12 import select |
| 13 import shutil | 13 import shutil |
| 14 import sys | 14 import sys |
| 15 import threading | 15 import threading |
| 16 import time | 16 import time |
| 17 import webbrowser | 17 import webbrowser |
| 18 import zipfile | 18 import zipfile |
| 19 import zlib | 19 import zlib |
| 20 | 20 |
| 21 from pylib import android_commands | 21 from pylib import android_commands |
| 22 from pylib import cmd_helper | 22 from pylib import cmd_helper |
| 23 from pylib import constants | 23 from pylib import constants |
| 24 from pylib import pexpect | 24 from pylib import pexpect |
| 25 from pylib.device import device_utils |
| 25 | 26 |
| 26 _TRACE_VIEWER_ROOT = os.path.join(constants.DIR_SOURCE_ROOT, | 27 _TRACE_VIEWER_ROOT = os.path.join(constants.DIR_SOURCE_ROOT, |
| 27 'third_party', 'trace-viewer') | 28 'third_party', 'trace-viewer') |
| 28 sys.path.append(_TRACE_VIEWER_ROOT) | 29 sys.path.append(_TRACE_VIEWER_ROOT) |
| 29 from trace_viewer.build import trace2html # pylint: disable=F0401 | 30 from trace_viewer.build import trace2html # pylint: disable=F0401 |
| 30 | 31 |
| 31 _DEFAULT_CHROME_CATEGORIES = '_DEFAULT_CHROME_CATEGORIES' | 32 _DEFAULT_CHROME_CATEGORIES = '_DEFAULT_CHROME_CATEGORIES' |
| 32 | 33 |
| 33 | 34 |
| 34 def _GetTraceTimestamp(): | 35 def _GetTraceTimestamp(): |
| 35 return time.strftime('%Y-%m-%d-%H%M%S', time.localtime()) | 36 return time.strftime('%Y-%m-%d-%H%M%S', time.localtime()) |
| 36 | 37 |
| 37 | 38 |
| 38 class ChromeTracingController(object): | 39 class ChromeTracingController(object): |
| 39 def __init__(self, adb, package_info, categories, ring_buffer): | 40 def __init__(self, device, package_info, categories, ring_buffer): |
| 40 self._adb = adb | 41 self._device = device |
| 41 self._package_info = package_info | 42 self._package_info = package_info |
| 42 self._categories = categories | 43 self._categories = categories |
| 43 self._ring_buffer = ring_buffer | 44 self._ring_buffer = ring_buffer |
| 44 self._trace_file = None | 45 self._trace_file = None |
| 45 self._trace_interval = None | 46 self._trace_interval = None |
| 46 self._trace_start_re = \ | 47 self._trace_start_re = \ |
| 47 re.compile(r'Logging performance trace to file: (.*)') | 48 re.compile(r'Logging performance trace to file: (.*)') |
| 48 self._trace_finish_re = \ | 49 self._trace_finish_re = \ |
| 49 re.compile(r'Profiler finished[.] Results are in (.*)[.]') | 50 re.compile(r'Profiler finished[.] Results are in (.*)[.]') |
| 50 self._adb.StartMonitoringLogcat(clear=False) | 51 self._device.old_interface.StartMonitoringLogcat(clear=False) |
| 51 | 52 |
| 52 def __str__(self): | 53 def __str__(self): |
| 53 return 'chrome trace' | 54 return 'chrome trace' |
| 54 | 55 |
| 55 def StartTracing(self, interval): | 56 def StartTracing(self, interval): |
| 56 self._trace_interval = interval | 57 self._trace_interval = interval |
| 57 self._adb.SyncLogCat() | 58 self._device.old_interface.SyncLogCat() |
| 58 self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_START', | 59 self._device.old_interface.BroadcastIntent( |
| 59 '-e categories "%s"' % ','.join(self._categories), | 60 self._package_info.package, 'GPU_PROFILER_START', |
| 60 '-e continuous' if self._ring_buffer else '') | 61 '-e categories "%s"' % ','.join(self._categories), |
| 62 '-e continuous' if self._ring_buffer else '') |
| 61 # Chrome logs two different messages related to tracing: | 63 # Chrome logs two different messages related to tracing: |
| 62 # | 64 # |
| 63 # 1. "Logging performance trace to file [...]" | 65 # 1. "Logging performance trace to file [...]" |
| 64 # 2. "Profiler finished. Results are in [...]" | 66 # 2. "Profiler finished. Results are in [...]" |
| 65 # | 67 # |
| 66 # The first one is printed when tracing starts and the second one indicates | 68 # The first one is printed when tracing starts and the second one indicates |
| 67 # that the trace file is ready to be pulled. | 69 # that the trace file is ready to be pulled. |
| 68 try: | 70 try: |
| 69 self._trace_file = self._adb.WaitForLogMatch(self._trace_start_re, | 71 self._trace_file = self._device.old_interface.WaitForLogMatch( |
| 70 None, | 72 self._trace_start_re, None, timeout=5).group(1) |
| 71 timeout=5).group(1) | |
| 72 except pexpect.TIMEOUT: | 73 except pexpect.TIMEOUT: |
| 73 raise RuntimeError('Trace start marker not found. Is the correct version ' | 74 raise RuntimeError('Trace start marker not found. Is the correct version ' |
| 74 'of the browser running?') | 75 'of the browser running?') |
| 75 | 76 |
| 76 def StopTracing(self): | 77 def StopTracing(self): |
| 77 if not self._trace_file: | 78 if not self._trace_file: |
| 78 return | 79 return |
| 79 self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_STOP') | 80 self._device.old_interface.BroadcastIntent(self._package_info.package, |
| 80 self._adb.WaitForLogMatch(self._trace_finish_re, None, timeout=120) | 81 'GPU_PROFILER_STOP') |
| 82 self._device.old_interface.WaitForLogMatch(self._trace_finish_re, None, |
| 83 timeout=120) |
| 81 | 84 |
| 82 def PullTrace(self): | 85 def PullTrace(self): |
| 83 # Wait a bit for the browser to finish writing the trace file. | 86 # Wait a bit for the browser to finish writing the trace file. |
| 84 time.sleep(self._trace_interval / 4 + 1) | 87 time.sleep(self._trace_interval / 4 + 1) |
| 85 | 88 |
| 86 trace_file = self._trace_file.replace('/storage/emulated/0/', '/sdcard/') | 89 trace_file = self._trace_file.replace('/storage/emulated/0/', '/sdcard/') |
| 87 host_file = os.path.join(os.path.curdir, os.path.basename(trace_file)) | 90 host_file = os.path.join(os.path.curdir, os.path.basename(trace_file)) |
| 88 self._adb.PullFileFromDevice(trace_file, host_file) | 91 self._device.old_interface.PullFileFromDevice(trace_file, host_file) |
| 89 return host_file | 92 return host_file |
| 90 | 93 |
| 91 | 94 |
| 92 _SYSTRACE_OPTIONS = [ | 95 _SYSTRACE_OPTIONS = [ |
| 93 # Compress the trace before sending it over USB. | 96 # Compress the trace before sending it over USB. |
| 94 '-z', | 97 '-z', |
| 95 # Use a large trace buffer to increase the polling interval. | 98 # Use a large trace buffer to increase the polling interval. |
| 96 '-b', '16384' | 99 '-b', '16384' |
| 97 ] | 100 ] |
| 98 | 101 |
| 99 # Interval in seconds for sampling systrace data. | 102 # Interval in seconds for sampling systrace data. |
| 100 _SYSTRACE_INTERVAL = 15 | 103 _SYSTRACE_INTERVAL = 15 |
| 101 | 104 |
| 102 | 105 |
| 103 class SystraceController(object): | 106 class SystraceController(object): |
| 104 def __init__(self, adb, categories, ring_buffer): | 107 def __init__(self, device, categories, ring_buffer): |
| 105 self._adb = adb | 108 self._device = device |
| 106 self._categories = categories | 109 self._categories = categories |
| 107 self._ring_buffer = ring_buffer | 110 self._ring_buffer = ring_buffer |
| 108 self._done = threading.Event() | 111 self._done = threading.Event() |
| 109 self._thread = None | 112 self._thread = None |
| 110 self._trace_data = None | 113 self._trace_data = None |
| 111 | 114 |
| 112 def __str__(self): | 115 def __str__(self): |
| 113 return 'systrace' | 116 return 'systrace' |
| 114 | 117 |
| 115 @staticmethod | 118 @staticmethod |
| 116 def GetCategories(adb): | 119 def GetCategories(device): |
| 117 return adb.RunShellCommand('atrace --list_categories') | 120 return device.old_interface.RunShellCommand('atrace --list_categories') |
| 118 | 121 |
| 119 def StartTracing(self, _): | 122 def StartTracing(self, _): |
| 120 self._thread = threading.Thread(target=self._CollectData) | 123 self._thread = threading.Thread(target=self._CollectData) |
| 121 self._thread.start() | 124 self._thread.start() |
| 122 | 125 |
| 123 def StopTracing(self): | 126 def StopTracing(self): |
| 124 self._done.set() | 127 self._done.set() |
| 125 | 128 |
| 126 def PullTrace(self): | 129 def PullTrace(self): |
| 127 self._thread.join() | 130 self._thread.join() |
| 128 self._thread = None | 131 self._thread = None |
| 129 if self._trace_data: | 132 if self._trace_data: |
| 130 output_name = 'systrace-%s' % _GetTraceTimestamp() | 133 output_name = 'systrace-%s' % _GetTraceTimestamp() |
| 131 with open(output_name, 'w') as out: | 134 with open(output_name, 'w') as out: |
| 132 out.write(self._trace_data) | 135 out.write(self._trace_data) |
| 133 return output_name | 136 return output_name |
| 134 | 137 |
| 135 def _RunATraceCommand(self, command): | 138 def _RunATraceCommand(self, command): |
| 139 # TODO(jbudorick) can this be made work with DeviceUtils? |
| 136 # We use a separate interface to adb because the one from AndroidCommands | 140 # We use a separate interface to adb because the one from AndroidCommands |
| 137 # isn't re-entrant. | 141 # isn't re-entrant. |
| 138 device = ['-s', self._adb.GetDevice()] if self._adb.GetDevice() else [] | 142 device_param = (['-s', self._device.old_interface.GetDevice()] |
| 139 cmd = ['adb'] + device + ['shell', 'atrace', '--%s' % command] + \ | 143 if self._device.old_interface.GetDevice() else []) |
| 144 cmd = ['adb'] + device_param + ['shell', 'atrace', '--%s' % command] + \ |
| 140 _SYSTRACE_OPTIONS + self._categories | 145 _SYSTRACE_OPTIONS + self._categories |
| 141 return cmd_helper.GetCmdOutput(cmd) | 146 return cmd_helper.GetCmdOutput(cmd) |
| 142 | 147 |
| 143 def _CollectData(self): | 148 def _CollectData(self): |
| 144 trace_data = [] | 149 trace_data = [] |
| 145 self._RunATraceCommand('async_start') | 150 self._RunATraceCommand('async_start') |
| 146 try: | 151 try: |
| 147 while not self._done.is_set(): | 152 while not self._done.is_set(): |
| 148 self._done.wait(_SYSTRACE_INTERVAL) | 153 self._done.wait(_SYSTRACE_INTERVAL) |
| 149 if not self._ring_buffer or self._done.is_set(): | 154 if not self._ring_buffer or self._done.is_set(): |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 368 | 373 |
| 369 For basic jank busting uses, use --trace-frame-viewer | 374 For basic jank busting uses, use --trace-frame-viewer |
| 370 For detailed study of ubercompositor, pass --trace-ubercompositor. | 375 For detailed study of ubercompositor, pass --trace-ubercompositor. |
| 371 | 376 |
| 372 When in doubt, just try out --trace-frame-viewer. | 377 When in doubt, just try out --trace-frame-viewer. |
| 373 """) | 378 """) |
| 374 | 379 |
| 375 if options.verbose: | 380 if options.verbose: |
| 376 logging.getLogger().setLevel(logging.DEBUG) | 381 logging.getLogger().setLevel(logging.DEBUG) |
| 377 | 382 |
| 378 adb = android_commands.AndroidCommands() | 383 devices = android_commands.GetAttachedDevices() |
| 384 if len(devices) != 1: |
| 385 parser.error('Exactly 1 device much be attached.') |
| 386 device = device_utils.DeviceUtils(devices[0]) |
| 387 |
| 379 if options.systrace_categories in ['list', 'help']: | 388 if options.systrace_categories in ['list', 'help']: |
| 380 _PrintMessage('\n'.join(SystraceController.GetCategories(adb))) | 389 _PrintMessage('\n'.join(SystraceController.GetCategories(device))) |
| 381 return 0 | 390 return 0 |
| 382 | 391 |
| 383 if not options.time and not options.continuous: | 392 if not options.time and not options.continuous: |
| 384 _PrintMessage('Time interval or continuous tracing should be specified.') | 393 _PrintMessage('Time interval or continuous tracing should be specified.') |
| 385 return 1 | 394 return 1 |
| 386 | 395 |
| 387 chrome_categories = _ComputeChromeCategories(options) | 396 chrome_categories = _ComputeChromeCategories(options) |
| 388 systrace_categories = _ComputeSystraceCategories(options) | 397 systrace_categories = _ComputeSystraceCategories(options) |
| 389 package_info = _GetSupportedBrowsers()[options.browser] | 398 package_info = _GetSupportedBrowsers()[options.browser] |
| 390 | 399 |
| 391 if chrome_categories and 'webview' in systrace_categories: | 400 if chrome_categories and 'webview' in systrace_categories: |
| 392 logging.warning('Using the "webview" category in systrace together with ' | 401 logging.warning('Using the "webview" category in systrace together with ' |
| 393 'Chrome tracing results in duplicate trace events.') | 402 'Chrome tracing results in duplicate trace events.') |
| 394 | 403 |
| 395 controllers = [] | 404 controllers = [] |
| 396 if chrome_categories: | 405 if chrome_categories: |
| 397 controllers.append(ChromeTracingController(adb, | 406 controllers.append(ChromeTracingController(device, |
| 398 package_info, | 407 package_info, |
| 399 chrome_categories, | 408 chrome_categories, |
| 400 options.ring_buffer)) | 409 options.ring_buffer)) |
| 401 if systrace_categories: | 410 if systrace_categories: |
| 402 controllers.append(SystraceController(adb, | 411 controllers.append(SystraceController(device, |
| 403 systrace_categories, | 412 systrace_categories, |
| 404 options.ring_buffer)) | 413 options.ring_buffer)) |
| 405 | 414 |
| 406 if not controllers: | 415 if not controllers: |
| 407 _PrintMessage('No trace categories enabled.') | 416 _PrintMessage('No trace categories enabled.') |
| 408 return 1 | 417 return 1 |
| 409 | 418 |
| 410 if options.output: | 419 if options.output: |
| 411 options.output = os.path.expanduser(options.output) | 420 options.output = os.path.expanduser(options.output) |
| 412 result = _CaptureAndPullTrace(controllers, | 421 result = _CaptureAndPullTrace(controllers, |
| 413 options.time if not options.continuous else 0, | 422 options.time if not options.continuous else 0, |
| 414 options.output, | 423 options.output, |
| 415 options.compress, | 424 options.compress, |
| 416 options.json) | 425 options.json) |
| 417 if options.view: | 426 if options.view: |
| 418 if sys.platform == 'darwin': | 427 if sys.platform == 'darwin': |
| 419 os.system('/usr/bin/open %s' % os.path.abspath(result)) | 428 os.system('/usr/bin/open %s' % os.path.abspath(result)) |
| 420 else: | 429 else: |
| 421 webbrowser.open(result) | 430 webbrowser.open(result) |
| 422 | 431 |
| 423 | 432 |
| 424 if __name__ == '__main__': | 433 if __name__ == '__main__': |
| 425 sys.exit(main()) | 434 sys.exit(main()) |
| OLD | NEW |