| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # | 2 # |
| 3 # Copyright 2015 The Chromium Authors. All rights reserved. | 3 # Copyright 2015 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 """Loops Custom Tabs tests and outputs the results into a CSV file.""" | 7 """Loops Custom Tabs tests and outputs the results into a CSV file.""" |
| 8 | 8 |
| 9 import contextlib | 9 import contextlib |
| 10 import logging | 10 import logging |
| 11 import optparse | 11 import optparse |
| 12 import os | 12 import os |
| 13 import random |
| 13 import re | 14 import re |
| 14 import subprocess | 15 import subprocess |
| 15 import sys | 16 import sys |
| 16 import time | 17 import time |
| 17 | 18 |
| 18 _SRC_PATH = os.path.abspath(os.path.join( | 19 _SRC_PATH = os.path.abspath(os.path.join( |
| 19 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, os.pardir)) | 20 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, os.pardir)) |
| 20 | 21 |
| 21 sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil')) | 22 sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil')) |
| 22 from devil.android import device_errors | 23 from devil.android import device_errors |
| 23 from devil.android import device_utils | 24 from devil.android import device_utils |
| 24 from devil.android.perf import cache_control | 25 from devil.android.perf import cache_control |
| 25 from devil.android.sdk import intent | 26 from devil.android.sdk import intent |
| 26 | 27 |
| 27 sys.path.append(os.path.join(_SRC_PATH, 'build', 'android')) | 28 sys.path.append(os.path.join(_SRC_PATH, 'build', 'android')) |
| 28 import devil_chromium | 29 import devil_chromium |
| 29 | 30 |
| 30 sys.path.append(os.path.join(_SRC_PATH, 'tools', 'android', 'loading')) | 31 sys.path.append(os.path.join(_SRC_PATH, 'tools', 'android', 'loading')) |
| 31 import device_setup | 32 import device_setup |
| 32 | 33 |
| 33 | 34 |
| 34 # Local build of Chrome (not Chromium). | 35 # Local build of Chrome (not Chromium). |
| 35 _CHROME_PACKAGE = 'com.google.android.apps.chrome' | 36 _CHROME_PACKAGE = 'com.google.android.apps.chrome' |
| 36 | 37 _COMMAND_LINE_PATH = '/data/local/tmp/chrome-command-line' |
| 38 _TEST_APP_PACKAGE_NAME = 'org.chromium.customtabsclient.test' |
| 37 | 39 |
| 38 # Command line arguments for Chrome. | 40 # Command line arguments for Chrome. |
| 39 _CHROME_ARGS = [ | 41 _CHROME_ARGS = [ |
| 40 # Disable backgound network requests that may pollute WPR archive, pollute | 42 # Disable backgound network requests that may pollute WPR archive, pollute |
| 41 # HTTP cache generation, and introduce noise in loading performance. | 43 # HTTP cache generation, and introduce noise in loading performance. |
| 42 '--disable-background-networking', | 44 '--disable-background-networking', |
| 43 '--disable-default-apps', | 45 '--disable-default-apps', |
| 44 '--no-proxy-server', | 46 '--no-proxy-server', |
| 45 # TODO(droger): Remove once crbug.com/354743 is fixed. | 47 # TODO(droger): Remove once crbug.com/354743 is fixed. |
| 46 '--safebrowsing-disable-auto-update', | 48 '--safebrowsing-disable-auto-update', |
| (...skipping 10 matching lines...) Expand all Loading... |
| 57 def ResetChromeLocalState(device): | 59 def ResetChromeLocalState(device): |
| 58 """Remove the Chrome Profile and the various disk caches.""" | 60 """Remove the Chrome Profile and the various disk caches.""" |
| 59 profile_dirs = ['app_chrome/Default', 'cache', 'app_chrome/ShaderCache', | 61 profile_dirs = ['app_chrome/Default', 'cache', 'app_chrome/ShaderCache', |
| 60 'app_tabs'] | 62 'app_tabs'] |
| 61 cmd = ['rm', '-rf'] | 63 cmd = ['rm', '-rf'] |
| 62 cmd.extend( | 64 cmd.extend( |
| 63 '/data/data/{}/{}'.format(_CHROME_PACKAGE, d) for d in profile_dirs) | 65 '/data/data/{}/{}'.format(_CHROME_PACKAGE, d) for d in profile_dirs) |
| 64 device.adb.Shell(subprocess.list2cmdline(cmd)) | 66 device.adb.Shell(subprocess.list2cmdline(cmd)) |
| 65 | 67 |
| 66 | 68 |
| 67 def RunOnce(device, url, warmup, prerender_mode, delay_to_may_launch_url, | 69 def RunOnce(device, url, warmup, speculation_mode, delay_to_may_launch_url, |
| 68 delay_to_launch_url, cold): | 70 delay_to_launch_url, cold, chrome_args): |
| 69 """Runs a test on a device once. | 71 """Runs a test on a device once. |
| 70 | 72 |
| 71 Args: | 73 Args: |
| 72 device: (DeviceUtils) device to run the tests on. | 74 device: (DeviceUtils) device to run the tests on. |
| 75 url: (str) URL to load. |
| 73 warmup: (bool) Whether to call warmup. | 76 warmup: (bool) Whether to call warmup. |
| 74 prerender_mode: (str) Prerender mode (disabled, enabled or prefetch). | 77 speculation_mode: (str) Speculation Mode. |
| 75 delay_to_may_launch_url: (int) Delay to mayLaunchUrl() in ms. | 78 delay_to_may_launch_url: (int) Delay to mayLaunchUrl() in ms. |
| 76 delay_to_launch_url: (int) Delay to launchUrl() in ms. | 79 delay_to_launch_url: (int) Delay to launchUrl() in ms. |
| 77 cold: (bool) Whether the page cache should be dropped. | 80 cold: (bool) Whether the page cache should be dropped. |
| 81 chrome_args: ([str]) List of arguments to pass to Chrome. |
| 78 | 82 |
| 79 Returns: | 83 Returns: |
| 80 The output line (str), like this (one line only): | 84 The output line (str), like this (one line only): |
| 81 <warmup>,<prerender_mode>,<delay_to_may_launch_url>,<delay_to_launch>, | 85 <warmup>,<prerender_mode>,<delay_to_may_launch_url>,<delay_to_launch>, |
| 82 <intent_sent_ms>,<page_load_started_ms>,<page_load_finished_ms>, | 86 <intent_sent_ms>,<page_load_started_ms>,<page_load_finished_ms>, |
| 83 <first_contentful_paint> | 87 <first_contentful_paint> |
| 84 or None on error. | 88 or None on error. |
| 85 """ | 89 """ |
| 86 launch_intent = intent.Intent( | 90 with device_setup.FlagReplacer(device, _COMMAND_LINE_PATH, chrome_args): |
| 87 action='android.intent.action.MAIN', | 91 launch_intent = intent.Intent( |
| 88 package='org.chromium.customtabsclient.test', | 92 action='android.intent.action.MAIN', |
| 89 activity='org.chromium.customtabs.test.MainActivity', | 93 package=_TEST_APP_PACKAGE_NAME, |
| 90 extras={'url': url, 'warmup': warmup, 'prerender_mode': prerender_mode, | 94 activity='org.chromium.customtabs.test.MainActivity', |
| 91 'delay_to_may_launch_url': delay_to_may_launch_url, | 95 extras={'url': str(url), 'warmup': warmup, |
| 92 'delay_to_launch_url': delay_to_launch_url}) | 96 'speculation_mode': str(speculation_mode), |
| 93 result_line_re = re.compile(r'CUSTOMTABSBENCH.*: (.*)') | 97 'delay_to_may_launch_url': delay_to_may_launch_url, |
| 94 logcat_monitor = device.GetLogcatMonitor(clear=True) | 98 'delay_to_launch_url': delay_to_launch_url}) |
| 95 logcat_monitor.Start() | 99 result_line_re = re.compile(r'CUSTOMTABSBENCH.*: (.*)') |
| 96 device.ForceStop(_CHROME_PACKAGE) | 100 logcat_monitor = device.GetLogcatMonitor(clear=True) |
| 97 device.ForceStop('org.chromium.customtabsclient.test') | 101 logcat_monitor.Start() |
| 98 ResetChromeLocalState(device) | 102 device.ForceStop(_CHROME_PACKAGE) |
| 103 device.ForceStop(_TEST_APP_PACKAGE_NAME) |
| 99 | 104 |
| 100 if cold: | |
| 101 if not device.HasRoot(): | 105 if not device.HasRoot(): |
| 102 device.EnableRoot() | 106 device.EnableRoot() |
| 103 cache_control.CacheControl(device).DropRamCaches() | 107 ResetChromeLocalState(device) |
| 104 device.StartActivity(launch_intent, blocking=True) | 108 |
| 105 match = None | 109 if cold: |
| 106 try: | 110 cache_control.CacheControl(device).DropRamCaches() |
| 107 match = logcat_monitor.WaitFor(result_line_re, timeout=20) | 111 |
| 108 except device_errors.CommandTimeoutError as e: | 112 device.StartActivity(launch_intent, blocking=True) |
| 109 logging.warning('Timeout waiting for the result line') | 113 |
| 110 return match.group(1) if match is not None else None | 114 match = None |
| 115 try: |
| 116 match = logcat_monitor.WaitFor(result_line_re, timeout=20) |
| 117 except device_errors.CommandTimeoutError as _: |
| 118 logging.warning('Timeout waiting for the result line') |
| 119 return match.group(1) if match is not None else None |
| 111 | 120 |
| 112 | 121 |
| 113 def LoopOnDevice(device, url, warmup, prerender_mode, delay_to_may_launch_url, | 122 def LoopOnDevice(device, configs, output_filename, wpr_archive_path=None, |
| 114 delay_to_launch_url, cold, output_filename, once=False): | 123 wpr_record=None, network_condition=None, wpr_log_path=None, |
| 124 once=False, should_stop=None): |
| 115 """Loops the tests on a device. | 125 """Loops the tests on a device. |
| 116 | 126 |
| 117 Args: | 127 Args: |
| 118 device: (DeviceUtils) device to run the tests on. | 128 device: (DeviceUtils) device to run the tests on. |
| 119 url: (str) URL to navigate to. | 129 configs: ([dict]) |
| 120 warmup: (bool) Whether to call warmup. | |
| 121 prerender_mode: (str) Prerender mode (disabled, enabled or prefetch). | |
| 122 delay_to_may_launch_url: (int) Delay to mayLaunchUrl() in ms. | |
| 123 delay_to_launch_url: (int) Delay to launchUrl() in ms. | |
| 124 cold: (bool) Whether the page cache should be dropped. | |
| 125 output_filename: (str) Output filename. '-' for stdout. | 130 output_filename: (str) Output filename. '-' for stdout. |
| 131 wpr_archive_path: (str) Path to the WPR archive. |
| 132 wpr_record: (bool) Whether WPR is set to recording. |
| 133 network_condition: (str) Name of the network configuration for throttling. |
| 134 wpr_log_path: (str) Path the the WPR log. |
| 126 once: (bool) Run only once. | 135 once: (bool) Run only once. |
| 136 should_stop: (threading.Event or None) When the event is set, stop looping. |
| 127 """ | 137 """ |
| 128 while True: | 138 with SetupWpr(device, wpr_archive_path, wpr_record, network_condition, |
| 129 out = sys.stdout if output_filename == '-' else open(output_filename, 'a') | 139 wpr_log_path) as wpr_attributes: |
| 140 to_stdout = output_filename == '-' |
| 141 out = sys.stdout if to_stdout else open(output_filename, 'a') |
| 130 try: | 142 try: |
| 131 result = RunOnce(device, url, warmup, prerender_mode, | 143 while should_stop is None or not should_stop.is_set(): |
| 132 delay_to_may_launch_url, delay_to_launch_url, cold) | 144 config = configs[random.randint(0, len(configs) - 1)] |
| 133 if result is not None: | 145 chrome_args = _CHROME_ARGS + wpr_attributes.chrome_args |
| 134 out.write(result + '\n') | 146 if config['speculation_mode'] == 'no_state_prefetch': |
| 135 out.flush() | 147 chrome_args.append('--prerender=' + config['speculation_mode']) |
| 136 if once: | 148 result = RunOnce(device, config['url'], config['warmup'], |
| 137 return | 149 config['speculation_mode'], |
| 138 time.sleep(10) | 150 config['delay_to_may_launch_url'], |
| 151 config['delay_to_launch_url'], config['cold'], |
| 152 chrome_args) |
| 153 if result is not None: |
| 154 out.write(result + '\n') |
| 155 out.flush() |
| 156 if once: |
| 157 return |
| 158 if should_stop is not None: |
| 159 should_stop.wait(10.) |
| 160 else: |
| 161 time.sleep(10) |
| 139 finally: | 162 finally: |
| 140 if output_filename != '-': | 163 if not to_stdout: |
| 141 out.close() | 164 out.close() |
| 142 | 165 |
| 143 | 166 |
| 144 def ProcessOutput(filename): | 167 def ProcessOutput(filename): |
| 145 """Reads an output file, and returns a processed numpy array. | 168 """Reads an output file, and returns a processed numpy array. |
| 146 | 169 |
| 147 Args: | 170 Args: |
| 148 filename: (str) file to process. | 171 filename: (str) file to process. |
| 149 | 172 |
| 150 Returns: | 173 Returns: |
| 151 A numpy structured array. | 174 A numpy structured array. |
| 152 """ | 175 """ |
| 153 import numpy as np | 176 import numpy as np |
| 154 data = np.genfromtxt(filename, delimiter=',') | 177 data = np.genfromtxt(filename, delimiter=',') |
| 155 result = np.array(np.zeros(len(data)), | 178 result = np.array(np.zeros(len(data)), |
| 156 dtype=[('warmup', bool), ('prerender_mode', np.int32), | 179 dtype=[('warmup', bool), ('speculation_mode', np.int32), |
| 157 ('delay_to_may_launch_url', np.int32), | 180 ('delay_to_may_launch_url', np.int32), |
| 158 ('delay_to_launch_url', np.int32), | 181 ('delay_to_launch_url', np.int32), |
| 159 ('commit', np.int32), ('plt', np.int32), | 182 ('commit', np.int32), ('plt', np.int32), |
| 160 ('first_contentful_paint', np.int32)]) | 183 ('first_contentful_paint', np.int32)]) |
| 161 result['warmup'] = data[:, 0] | 184 result['warmup'] = data[:, 0] |
| 162 result['prerender_mode'] = data[:, 1] | 185 result['speculation_mode'] = data[:, 1] |
| 163 result['delay_to_may_launch_url'] = data[:, 2] | 186 result['delay_to_may_launch_url'] = data[:, 2] |
| 164 result['delay_to_launch_url'] = data[:, 3] | 187 result['delay_to_launch_url'] = data[:, 3] |
| 165 result['commit'] = data[:, 5] - data[:, 4] | 188 result['commit'] = data[:, 5] - data[:, 4] |
| 166 result['plt'] = data[:, 6] - data[:, 4] | 189 result['plt'] = data[:, 6] - data[:, 4] |
| 167 result['first_contentful_paint'] = data[7] | 190 result['first_contentful_paint'] = data[7] |
| 168 return result | 191 return result |
| 169 | 192 |
| 170 | 193 |
| 171 def _CreateOptionParser(): | 194 def _CreateOptionParser(): |
| 172 parser = optparse.OptionParser(description='Loops Custom Tabs tests on a ' | 195 parser = optparse.OptionParser(description='Loops Custom Tabs tests on a ' |
| 173 'device, and outputs the navigation timings ' | 196 'device, and outputs the navigation timings ' |
| 174 'in a CSV file.') | 197 'in a CSV file.') |
| 175 parser.add_option('--device', help='Device ID') | 198 parser.add_option('--device', help='Device ID') |
| 176 parser.add_option('--url', help='URL to navigate to.', | 199 parser.add_option('--url', help='URL to navigate to.', |
| 177 default='https://www.android.com') | 200 default='https://www.android.com') |
| 178 parser.add_option('--warmup', help='Call warmup.', default=False, | 201 parser.add_option('--warmup', help='Call warmup.', default=False, |
| 179 action='store_true') | 202 action='store_true') |
| 180 parser.add_option('--prerender_mode', default='enabled', | 203 parser.add_option('--speculation_mode', default='prerender', |
| 181 help='The prerender mode (disabled, enabled or prefetch).', | 204 help='The speculation mode (prerender, disabled, ' |
| 182 choices=['disabled', 'enabled', 'prefetch']) | 205 'speculative_prefetch or no_state_prefetch).', |
| 206 choices=['prerender', 'disabled', 'speculative_prefetch', |
| 207 'no_state_prefetch']) |
| 183 parser.add_option('--delay_to_may_launch_url', | 208 parser.add_option('--delay_to_may_launch_url', |
| 184 help='Delay before calling mayLaunchUrl() in ms.', | 209 help='Delay before calling mayLaunchUrl() in ms.', |
| 185 type='int') | 210 type='int') |
| 186 parser.add_option('--delay_to_launch_url', | 211 parser.add_option('--delay_to_launch_url', |
| 187 help='Delay before calling launchUrl() in ms.', | 212 help='Delay before calling launchUrl() in ms.', |
| 188 type='int') | 213 type='int') |
| 189 parser.add_option('--cold', help='Purge the page cache before each run.', | 214 parser.add_option('--cold', help='Purge the page cache before each run.', |
| 190 default=False, action='store_true') | 215 default=False, action='store_true') |
| 191 parser.add_option('--output_file', help='Output file (append). "-" for ' | 216 parser.add_option('--output_file', help='Output file (append). "-" for ' |
| 192 'stdout') | 217 'stdout') |
| 193 parser.add_option('--once', help='Run only one iteration.', | 218 parser.add_option('--once', help='Run only one iteration.', |
| 194 action='store_true', default=False) | 219 action='store_true', default=False) |
| 195 | 220 |
| 196 # WebPageReplay-related options. | 221 # WebPageReplay-related options. |
| 197 group = optparse.OptionGroup( | 222 group = optparse.OptionGroup( |
| 198 parser, 'WebPageReplay options', | 223 parser, 'WebPageReplay options', |
| 199 'Setting any of these enables WebPageReplay.') | 224 'Setting any of these enables WebPageReplay.') |
| 200 group.add_option('--record', help='Record the WPR archive.', | 225 group.add_option('--record', help='Record the WPR archive.', |
| 201 action='store_true', default=False) | 226 action='store_true', default=False) |
| 202 group.add_option('--wpr_archive', help='WPR archive path.') | 227 group.add_option('--wpr_archive', help='WPR archive path.') |
| 203 group.add_option('--wpr_log', help='WPR log path.') | 228 group.add_option('--wpr_log', help='WPR log path.') |
| 204 group.add_option('--network_condition', | 229 group.add_option('--network_condition', |
| 205 help='Network condition for emulation.') | 230 help='Network condition for emulation.') |
| 206 parser.add_option_group(group) | 231 parser.add_option_group(group) |
| 207 | 232 |
| 208 return parser | 233 return parser |
| 209 | 234 |
| 210 | 235 |
| 211 @contextlib.contextmanager | 236 @contextlib.contextmanager |
| 212 def DummyWprHost(): | 237 def DummyWprHost(): |
| 213 """Dummy context used to run without WebPageReplay.""" | 238 """Dummy context used to run without WebPageReplay.""" |
| 214 yield device_setup.WprAttribute(chrome_args=[], chrome_env_override={}) | 239 yield device_setup.WprAttribute(chrome_args=[], chrome_env_override={}) |
| 215 | 240 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 229 parser = _CreateOptionParser() | 254 parser = _CreateOptionParser() |
| 230 options, _ = parser.parse_args() | 255 options, _ = parser.parse_args() |
| 231 devil_chromium.Initialize() | 256 devil_chromium.Initialize() |
| 232 devices = device_utils.DeviceUtils.HealthyDevices() | 257 devices = device_utils.DeviceUtils.HealthyDevices() |
| 233 device = devices[0] | 258 device = devices[0] |
| 234 if len(devices) != 1 and options.device is None: | 259 if len(devices) != 1 and options.device is None: |
| 235 logging.error('Several devices attached, must specify one with --device.') | 260 logging.error('Several devices attached, must specify one with --device.') |
| 236 sys.exit(0) | 261 sys.exit(0) |
| 237 if options.device is not None: | 262 if options.device is not None: |
| 238 matching_devices = [d for d in devices if str(d) == options.device] | 263 matching_devices = [d for d in devices if str(d) == options.device] |
| 239 if len(matching_devices) == 0: | 264 if not matching_devices: |
| 240 logging.error('Device not found.') | 265 logging.error('Device not found.') |
| 241 sys.exit(0) | 266 sys.exit(0) |
| 242 device = matching_devices[0] | 267 device = matching_devices[0] |
| 243 | 268 |
| 244 with SetupWpr(device, options.wpr_archive, options.record, | 269 config = { |
| 245 options.network_condition, options.wpr_log) as wpr_attributes: | 270 'url': options.url, |
| 246 chrome_args = (_CHROME_ARGS + ['--prerender=' + options.prerender_mode] + | 271 'warmup': options.warmup, |
| 247 wpr_attributes.chrome_args) | 272 'speculation_mode': options.speculation_mode, |
| 248 with device_setup.FlagReplacer( | 273 'delay_to_may_launch_url': options.delay_to_may_launch_url, |
| 249 device, '/data/local/tmp/chrome-command-line', chrome_args): | 274 'delay_to_launch_url': options.delay_to_launch_url, |
| 250 LoopOnDevice(device, options.url, options.warmup, options.prerender_mode, | 275 'cold': options.cold, |
| 251 options.delay_to_may_launch_url, options.delay_to_launch_url, | 276 } |
| 252 options.cold, options.output_file, options.once) | 277 LoopOnDevice(device, [config], options.output_file, options.wpr_archive, |
| 278 options.record, options.network_condition, options.wpr_log, |
| 279 once=options.once) |
| 253 | 280 |
| 254 | 281 |
| 255 if __name__ == '__main__': | 282 if __name__ == '__main__': |
| 256 main() | 283 main() |
| OLD | NEW |