| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env 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 copy | 9 import copy |
| 10 import json | 10 import json |
| 11 import logging | 11 import logging |
| 12 import optparse | 12 import optparse |
| 13 import os | 13 import os |
| 14 import random | |
| 15 import sys | 14 import sys |
| 16 import threading | 15 import threading |
| 17 | 16 |
| 18 import customtabs_benchmark | 17 import customtabs_benchmark |
| 19 | 18 |
| 20 _SRC_PATH = os.path.abspath(os.path.join( | 19 _SRC_PATH = os.path.abspath(os.path.join( |
| 21 os.path.dirname(__file__), '..', '..', '..', '..')) | 20 os.path.dirname(__file__), '..', '..', '..', '..')) |
| 22 | 21 |
| 23 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')) |
| 24 from devil.android import device_utils | 23 from devil.android import device_utils |
| 25 | 24 |
| 26 sys.path.append(os.path.join(_SRC_PATH, 'build', 'android')) | 25 sys.path.append(os.path.join(_SRC_PATH, 'build', 'android')) |
| 27 import devil_chromium | 26 import devil_chromium |
| 28 | 27 |
| 29 | 28 |
| 30 _KEYS = ['url', 'warmup', 'no_prerendering', 'delay_to_may_launch_url', | 29 _KEYS = ['url', 'warmup', 'speculation_mode', 'delay_to_may_launch_url', |
| 31 'delay_to_launch_url', 'cold'] | 30 'delay_to_launch_url', 'cold'] |
| 32 | 31 |
| 33 | 32 |
| 34 def _ParseConfiguration(filename): | 33 def _ParseConfiguration(filename): |
| 35 """Reads a JSON file and returns a list of configurations. | 34 """Reads a JSON file and returns a list of configurations. |
| 36 | 35 |
| 37 Each valid value in the JSON file can be either a scalar or a list of | 36 Each valid value in the JSON file can be either a scalar or a list of |
| 38 values. This function expands the scalar values to be lists. All list must | 37 values. This function expands the scalar values to be lists. All list must |
| 39 have the same length. | 38 have the same length. |
| 40 | 39 |
| 41 Sample configuration: | 40 Sample configuration: |
| 42 { | 41 { |
| 43 "url": "https://www.android.com", | 42 "url": "https://www.android.com", |
| 44 "warmup": [false, true], | 43 "warmup": [false, true], |
| 45 "no_prerendering": false, | 44 "speculation_mode": "speculative_prefetch", |
| 46 "delay_to_may_launch_url": [-1, 1000], | 45 "delay_to_may_launch_url": [-1, 1000], |
| 47 "delay_to_launch_url": [-1, 1000], | 46 "delay_to_launch_url": [-1, 1000], |
| 48 "cold": true | 47 "cold": true |
| 49 } | 48 } |
| 49 See sample_config.json in this directory as well. |
| 50 | 50 |
| 51 Args: | 51 Args: |
| 52 filename: (str) Point to a file containins a JSON dictionnary of config | 52 filename: (str) Point to a file containins a JSON dictionnary of config |
| 53 values. | 53 values. |
| 54 | 54 |
| 55 Returns: | 55 Returns: |
| 56 A list of configurations, where each value is specified. | 56 A list of configurations, where each value is specified. |
| 57 """ | 57 """ |
| 58 config = json.load(open(filename, 'r')) | 58 config = json.load(open(filename, 'r')) |
| 59 has_all_values = all(k in config for k in _KEYS) | 59 has_all_values = all(k in config for k in _KEYS) |
| 60 assert has_all_values | 60 assert has_all_values |
| 61 config['url'] = str(config['url']) # Intents don't like unicode. | 61 config['url'] = str(config['url']) # Intents don't like unicode. |
| 62 has_list = any(isinstance(config[k], list) for k in _KEYS) | 62 has_list = any(isinstance(config[k], list) for k in _KEYS) |
| 63 if not has_list: | 63 if not has_list: |
| 64 return [config] | 64 return [config] |
| 65 list_keys = [k for k in _KEYS if isinstance(config[k], list)] | 65 list_keys = [k for k in _KEYS if isinstance(config[k], list)] |
| 66 list_length = len(config[list_keys[0]]) | 66 list_length = len(config[list_keys[0]]) |
| 67 assert all(len(config[k]) == list_length for k in list_keys) | 67 assert all(len(config[k]) == list_length for k in list_keys) |
| 68 result = [] | 68 result = [] |
| 69 for i in range(list_length): | 69 for i in range(list_length): |
| 70 result.append(copy.deepcopy(config)) | 70 result.append(copy.deepcopy(config)) |
| 71 for k in list_keys: | 71 for k in list_keys: |
| 72 result[-1][k] = result[-1][k][i] | 72 result[-1][k] = result[-1][k][i] |
| 73 return result | 73 return result |
| 74 | 74 |
| 75 | 75 |
| 76 def _CreateOptionParser(): | 76 def _CreateOptionParser(): |
| 77 parser = optparse.OptionParser(description='Loops tests on all attached ' | 77 parser = optparse.OptionParser(description='Loops tests on all attached ' |
| 78 'devices, with randomly selected ' | 78 'devices, with randomly selected ' |
| 79 'configurations, and outputs the results in ' | 79 'configurations, and outputs the results in ' |
| 80 'CSV files.') | 80 'CSV files.') |
| 81 parser.add_option('--config', help='JSON configuration file. Required.') | 81 parser.add_option('--config', help='JSON configuration file. Required.') |
| 82 parser.add_option('--output_file_prefix', help='Output file prefix. Actual ' | 82 parser.add_option('--output_file_prefix', help='Output file prefix. Actual ' |
| 83 'output file is prefix_<device ID>.csv', default='result') | 83 'output file is prefix_<device ID>.csv', default='result') |
| 84 parser.add_option('--once', help='Run only once.', default=False, |
| 85 action='store_true') |
| 84 return parser | 86 return parser |
| 85 | 87 |
| 86 | 88 |
| 87 def _RunOnDevice(device, output_filename, configs, should_stop): | |
| 88 """Loops the tests described by configs on a device. | |
| 89 | |
| 90 Args: | |
| 91 device: (DeviceUtils) device to run the tests on. | |
| 92 output_filename: (str) Output file name. | |
| 93 configs: (list of dict) List of configurations. | |
| 94 should_stop: (Event) When set, this function should return. | |
| 95 """ | |
| 96 with open(output_filename, 'a') as f: | |
| 97 while not should_stop.is_set(): | |
| 98 config = configs[random.randint(0, len(configs) - 1)] | |
| 99 result = customtabs_benchmark.RunOnce( | |
| 100 device, config['url'], config['warmup'], config['no_prerendering'], | |
| 101 config['delay_to_may_launch_url'], config['delay_to_launch_url'], | |
| 102 config['cold']) | |
| 103 if result is not None: | |
| 104 f.write(result + '\n') | |
| 105 f.flush() | |
| 106 should_stop.wait(10.) | |
| 107 | |
| 108 | |
| 109 def _Run(output_file_prefix, configs): | 89 def _Run(output_file_prefix, configs): |
| 110 """Loops the tests described by the configs on connected devices. | 90 """Loops the tests described by the configs on connected devices. |
| 111 | 91 |
| 112 Args: | 92 Args: |
| 113 output_file_prefix: (str) Prefix for the output file name. | 93 output_file_prefix: (str) Prefix for the output file name. |
| 114 configs: (list of dict) List of configurations. | 94 configs: ([dict]) List of configurations. |
| 115 """ | 95 """ |
| 116 devices = device_utils.DeviceUtils.HealthyDevices() | 96 devices = device_utils.DeviceUtils.HealthyDevices() |
| 117 should_stop = threading.Event() | 97 stop_event = threading.Event() |
| 118 threads = [] | 98 threads = [] |
| 119 for device in devices: | 99 for device in devices: |
| 120 output_filename = '%s_%s.csv' % (output_file_prefix, str(device)) | 100 output_filename = '%s_%s.csv' % (output_file_prefix, str(device)) |
| 121 thread = threading.Thread( | 101 thread = threading.Thread( |
| 122 target=_RunOnDevice, | 102 target=customtabs_benchmark.LoopOnDevice, |
| 123 args=(device, output_filename, configs, should_stop)) | 103 args=(device, configs, output_filename), |
| 104 kwargs={'should_stop': stop_event}) |
| 124 thread.start() | 105 thread.start() |
| 125 threads.append(thread) | 106 threads.append(thread) |
| 126 for thread in threads: | 107 while any(thread.is_alive() for thread in threads): |
| 127 try: | 108 try: |
| 128 thread.join() | 109 for thread in threads: |
| 129 except KeyboardInterrupt as e: | 110 if thread.is_alive(): |
| 130 should_stop.set() | 111 thread.join(1.) |
| 112 except KeyboardInterrupt as _: |
| 113 logging.warning('Stopping now.') |
| 114 stop_event.set() |
| 131 | 115 |
| 132 | 116 |
| 133 def main(): | 117 def main(): |
| 134 parser = _CreateOptionParser() | 118 parser = _CreateOptionParser() |
| 135 options, _ = parser.parse_args() | 119 options, _ = parser.parse_args() |
| 136 if options.config is None: | 120 if options.config is None: |
| 137 logging.error('A configuration file must be provided.') | 121 logging.error('A configuration file must be provided.') |
| 138 sys.exit(0) | 122 sys.exit(0) |
| 139 devil_chromium.Initialize() | 123 devil_chromium.Initialize() |
| 140 configs = _ParseConfiguration(options.config) | 124 configs = _ParseConfiguration(options.config) |
| 141 _Run(options.output_file_prefix, configs) | 125 if options.once: |
| 126 device = device_utils.DeviceUtils.HealthyDevices()[0] |
| 127 customtabs_benchmark.LoopOnDevice(device, [configs[0]], '-', once=True) |
| 128 else: |
| 129 _Run(options.output_file_prefix, configs) |
| 142 | 130 |
| 143 | 131 |
| 144 if __name__ == '__main__': | 132 if __name__ == '__main__': |
| 145 main() | 133 main() |
| OLD | NEW |