Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #! /usr/bin/env python | 1 #! /usr/bin/env python |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. | 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Instructs Chrome to load series of web pages and reports results. | 6 """Instructs Chrome to load series of web pages and reports results. |
| 7 | 7 |
| 8 When running Chrome is sandwiched between preprocessed disk caches and | 8 When running Chrome is sandwiched between preprocessed disk caches and |
| 9 WepPageReplay serving all connections. | 9 WepPageReplay serving all connections. |
| 10 | 10 |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 28 | 28 |
| 29 sys.path.append(os.path.join(_SRC_DIR, 'third_party', 'catapult', 'devil')) | 29 sys.path.append(os.path.join(_SRC_DIR, 'third_party', 'catapult', 'devil')) |
| 30 from devil.android import device_utils | 30 from devil.android import device_utils |
| 31 | 31 |
| 32 sys.path.append(os.path.join(_SRC_DIR, 'build', 'android')) | 32 sys.path.append(os.path.join(_SRC_DIR, 'build', 'android')) |
| 33 from pylib import constants | 33 from pylib import constants |
| 34 import devil_chromium | 34 import devil_chromium |
| 35 | 35 |
| 36 import device_setup | 36 import device_setup |
| 37 import devtools_monitor | 37 import devtools_monitor |
| 38 import json | 38 import options |
| 39 import page_track | 39 import page_track |
| 40 import pull_sandwich_metrics | 40 import pull_sandwich_metrics |
| 41 import trace_recorder | |
| 41 import tracing | 42 import tracing |
| 42 | 43 |
| 43 | 44 |
| 45 # Use options layer to access contants. | |
|
Benoit L
2016/02/22 10:26:13
nit: s/contants/constants/
gabadie
2016/02/22 11:05:06
Done.
| |
| 46 OPTIONS = options.OPTIONS | |
| 47 | |
| 44 _JOB_SEARCH_PATH = 'sandwich_jobs' | 48 _JOB_SEARCH_PATH = 'sandwich_jobs' |
| 45 | 49 |
| 46 # Directory name under --output to save the cache from the device. | 50 # Directory name under --output to save the cache from the device. |
| 47 _CACHE_DIRECTORY_NAME = 'cache' | 51 _CACHE_DIRECTORY_NAME = 'cache' |
| 48 | 52 |
| 49 # Name of cache subdirectory on the device where the cache index is stored. | 53 # Name of cache subdirectory on the device where the cache index is stored. |
| 50 _INDEX_DIRECTORY_NAME = 'index-dir' | 54 _INDEX_DIRECTORY_NAME = 'index-dir' |
| 51 | 55 |
| 52 # Name of the file containing the cache index. This file is stored on the device | 56 # Name of the file containing the cache index. This file is stored on the device |
| 53 # in the cache directory under _INDEX_DIRECTORY_NAME. | 57 # in the cache directory under _INDEX_DIRECTORY_NAME. |
| 54 _REAL_INDEX_FILE_NAME = 'the-real-index' | 58 _REAL_INDEX_FILE_NAME = 'the-real-index' |
| 55 | 59 |
| 56 # Name of the chrome package. | |
| 57 _CHROME_PACKAGE = ( | |
| 58 constants.PACKAGE_INFO[device_setup.DEFAULT_CHROME_PACKAGE].package) | |
| 59 | |
| 60 # An estimate of time to wait for the device to become idle after expensive | 60 # An estimate of time to wait for the device to become idle after expensive |
| 61 # operations, such as opening the launcher activity. | 61 # operations, such as opening the launcher activity. |
| 62 _TIME_TO_DEVICE_IDLE_SECONDS = 2 | 62 _TIME_TO_DEVICE_IDLE_SECONDS = 2 |
| 63 | 63 |
| 64 # Cache directory's path on the device. | 64 |
| 65 _REMOTE_CACHE_DIRECTORY = '/data/data/' + _CHROME_PACKAGE + '/cache/Cache' | 65 def _RemoteCacheDirectory(): |
| 66 return '/data/data/{}/cache/Cache'.format(OPTIONS.chrome_package_name) | |
| 66 | 67 |
| 67 | 68 |
| 68 def _ReadUrlsFromJobDescription(job_name): | 69 def _ReadUrlsFromJobDescription(job_name): |
| 69 """Retrieves the list of URLs associated with the job name.""" | 70 """Retrieves the list of URLs associated with the job name.""" |
| 70 try: | 71 try: |
| 71 # Extra sugar: attempt to load from a relative path. | 72 # Extra sugar: attempt to load from a relative path. |
| 72 json_file_name = os.path.join(os.path.dirname(__file__), _JOB_SEARCH_PATH, | 73 json_file_name = os.path.join(os.path.dirname(__file__), _JOB_SEARCH_PATH, |
| 73 job_name) | 74 job_name) |
| 74 with open(json_file_name) as f: | 75 with open(json_file_name) as f: |
| 75 json_data = json.load(f) | 76 json_data = json.load(f) |
| 76 except IOError: | 77 except IOError: |
| 77 # Attempt to read by regular file name. | 78 # Attempt to read by regular file name. |
| 78 with open(job_name) as f: | 79 with open(job_name) as f: |
| 79 json_data = json.load(f) | 80 json_data = json.load(f) |
| 80 | 81 |
| 81 key = 'urls' | 82 key = 'urls' |
| 82 if json_data and key in json_data: | 83 if json_data and key in json_data: |
| 83 url_list = json_data[key] | 84 url_list = json_data[key] |
| 84 if isinstance(url_list, list) and len(url_list) > 0: | 85 if isinstance(url_list, list) and len(url_list) > 0: |
| 85 return url_list | 86 return url_list |
| 86 raise Exception('Job description does not define a list named "urls"') | 87 raise Exception('Job description does not define a list named "urls"') |
| 87 | 88 |
| 88 | 89 |
| 89 def _SaveChromeTrace(events, target_directory): | |
| 90 """Saves the trace events, ignores IO errors. | |
| 91 | |
| 92 Args: | |
| 93 events: a dict as returned by TracingTrack.ToJsonDict() | |
| 94 target_directory: Directory path where trace is created. | |
| 95 """ | |
| 96 filename = os.path.join(target_directory, 'trace.json') | |
| 97 try: | |
| 98 os.makedirs(target_directory) | |
| 99 with open(filename, 'w') as f: | |
| 100 json.dump({'traceEvents': events['events'], 'metadata': {}}, f, indent=2) | |
| 101 except IOError: | |
| 102 logging.warning('Could not save a trace: %s' % filename) | |
| 103 # Swallow the exception. | |
| 104 | |
| 105 | |
| 106 def _UpdateTimestampFromAdbStat(filename, stat): | 90 def _UpdateTimestampFromAdbStat(filename, stat): |
| 107 os.utime(filename, (stat.st_time, stat.st_time)) | 91 os.utime(filename, (stat.st_time, stat.st_time)) |
| 108 | 92 |
| 109 | 93 |
| 110 def _AdbShell(adb, cmd): | 94 def _AdbShell(adb, cmd): |
| 111 adb.Shell(subprocess.list2cmdline(cmd)) | 95 adb.Shell(subprocess.list2cmdline(cmd)) |
| 112 | 96 |
| 113 | 97 |
| 114 def _AdbUtime(adb, filename, timestamp): | 98 def _AdbUtime(adb, filename, timestamp): |
| 115 """Adb equivalent of os.utime(filename, (timestamp, timestamp)) | 99 """Adb equivalent of os.utime(filename, (timestamp, timestamp)) |
| 116 """ | 100 """ |
| 117 touch_stamp = datetime.fromtimestamp(timestamp).strftime('%Y%m%d.%H%M%S') | 101 touch_stamp = datetime.fromtimestamp(timestamp).strftime('%Y%m%d.%H%M%S') |
| 118 _AdbShell(adb, ['touch', '-t', touch_stamp, filename]) | 102 _AdbShell(adb, ['touch', '-t', touch_stamp, filename]) |
| 119 | 103 |
| 120 | 104 |
| 121 def _PullBrowserCache(device): | 105 def _PullBrowserCache(device): |
| 122 """Pulls the browser cache from the device and saves it locally. | 106 """Pulls the browser cache from the device and saves it locally. |
| 123 | 107 |
| 124 Cache is saved with the same file structure as on the device. Timestamps are | 108 Cache is saved with the same file structure as on the device. Timestamps are |
| 125 important to preserve because indexing and eviction depends on them. | 109 important to preserve because indexing and eviction depends on them. |
| 126 | 110 |
| 127 Returns: | 111 Returns: |
| 128 Temporary directory containing all the browser cache. | 112 Temporary directory containing all the browser cache. |
| 129 """ | 113 """ |
| 130 save_target = tempfile.mkdtemp(suffix='.cache') | 114 save_target = tempfile.mkdtemp(suffix='.cache') |
| 131 for filename, stat in device.adb.Ls(_REMOTE_CACHE_DIRECTORY): | 115 for filename, stat in device.adb.Ls(_RemoteCacheDirectory()): |
| 132 if filename == '..': | 116 if filename == '..': |
| 133 continue | 117 continue |
| 134 if filename == '.': | 118 if filename == '.': |
| 135 cache_directory_stat = stat | 119 cache_directory_stat = stat |
| 136 continue | 120 continue |
| 137 original_file = os.path.join(_REMOTE_CACHE_DIRECTORY, filename) | 121 original_file = os.path.join(_RemoteCacheDirectory(), filename) |
| 138 saved_file = os.path.join(save_target, filename) | 122 saved_file = os.path.join(save_target, filename) |
| 139 device.adb.Pull(original_file, saved_file) | 123 device.adb.Pull(original_file, saved_file) |
| 140 _UpdateTimestampFromAdbStat(saved_file, stat) | 124 _UpdateTimestampFromAdbStat(saved_file, stat) |
| 141 if filename == _INDEX_DIRECTORY_NAME: | 125 if filename == _INDEX_DIRECTORY_NAME: |
| 142 # The directory containing the index was pulled recursively, update the | 126 # The directory containing the index was pulled recursively, update the |
| 143 # timestamps for known files. They are ignored by cache backend, but may | 127 # timestamps for known files. They are ignored by cache backend, but may |
| 144 # be useful for debugging. | 128 # be useful for debugging. |
| 145 index_dir_stat = stat | 129 index_dir_stat = stat |
| 146 saved_index_dir = os.path.join(save_target, _INDEX_DIRECTORY_NAME) | 130 saved_index_dir = os.path.join(save_target, _INDEX_DIRECTORY_NAME) |
| 147 saved_index_file = os.path.join(saved_index_dir, _REAL_INDEX_FILE_NAME) | 131 saved_index_file = os.path.join(saved_index_dir, _REAL_INDEX_FILE_NAME) |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 159 | 143 |
| 160 | 144 |
| 161 def _PushBrowserCache(device, local_cache_path): | 145 def _PushBrowserCache(device, local_cache_path): |
| 162 """Pushes the browser cache saved locally to the device. | 146 """Pushes the browser cache saved locally to the device. |
| 163 | 147 |
| 164 Args: | 148 Args: |
| 165 device: Android device. | 149 device: Android device. |
| 166 local_cache_path: The directory's path containing the cache locally. | 150 local_cache_path: The directory's path containing the cache locally. |
| 167 """ | 151 """ |
| 168 # Clear previous cache. | 152 # Clear previous cache. |
| 169 _AdbShell(device.adb, ['rm', '-rf', _REMOTE_CACHE_DIRECTORY]) | 153 _AdbShell(device.adb, ['rm', '-rf', _RemoteCacheDirectory()]) |
| 170 _AdbShell(device.adb, ['mkdir', _REMOTE_CACHE_DIRECTORY]) | 154 _AdbShell(device.adb, ['mkdir', _RemoteCacheDirectory()]) |
| 171 | 155 |
| 172 # Push cache content. | 156 # Push cache content. |
| 173 device.adb.Push(local_cache_path, _REMOTE_CACHE_DIRECTORY) | 157 device.adb.Push(local_cache_path, _RemoteCacheDirectory()) |
| 174 | 158 |
| 175 # Walk through the local cache to update mtime on the device. | 159 # Walk through the local cache to update mtime on the device. |
| 176 def MirrorMtime(local_path): | 160 def MirrorMtime(local_path): |
| 177 cache_relative_path = os.path.relpath(local_path, start=local_cache_path) | 161 cache_relative_path = os.path.relpath(local_path, start=local_cache_path) |
| 178 remote_path = os.path.join(_REMOTE_CACHE_DIRECTORY, cache_relative_path) | 162 remote_path = os.path.join(_RemoteCacheDirectory(), cache_relative_path) |
| 179 _AdbUtime(device.adb, remote_path, os.stat(local_path).st_mtime) | 163 _AdbUtime(device.adb, remote_path, os.stat(local_path).st_mtime) |
| 180 | 164 |
| 181 for local_directory_path, dirnames, filenames in os.walk( | 165 for local_directory_path, dirnames, filenames in os.walk( |
| 182 local_cache_path, topdown=False): | 166 local_cache_path, topdown=False): |
| 183 for filename in filenames: | 167 for filename in filenames: |
| 184 MirrorMtime(os.path.join(local_directory_path, filename)) | 168 MirrorMtime(os.path.join(local_directory_path, filename)) |
| 185 for dirname in dirnames: | 169 for dirname in dirnames: |
| 186 MirrorMtime(os.path.join(local_directory_path, dirname)) | 170 MirrorMtime(os.path.join(local_directory_path, dirname)) |
| 187 MirrorMtime(local_cache_path) | 171 MirrorMtime(local_cache_path) |
| 188 | 172 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 271 int(dirname) | 255 int(dirname) |
| 272 except ValueError: | 256 except ValueError: |
| 273 continue | 257 continue |
| 274 shutil.rmtree(directory_path) | 258 shutil.rmtree(directory_path) |
| 275 | 259 |
| 276 | 260 |
| 277 def main(): | 261 def main(): |
| 278 logging.basicConfig(level=logging.INFO) | 262 logging.basicConfig(level=logging.INFO) |
| 279 devil_chromium.Initialize() | 263 devil_chromium.Initialize() |
| 280 | 264 |
| 265 # Don't give the argument yet. All we are interested in for now is accessing | |
| 266 # the default values of OPTIONS. | |
| 267 OPTIONS.ParseArgs([]) | |
| 268 | |
| 281 parser = argparse.ArgumentParser() | 269 parser = argparse.ArgumentParser() |
| 282 parser.add_argument('--job', required=True, | 270 parser.add_argument('--job', required=True, |
| 283 help='JSON file with job description.') | 271 help='JSON file with job description.') |
| 284 parser.add_argument('--output', required=True, | 272 parser.add_argument('--output', required=True, |
| 285 help='Name of output directory to create.') | 273 help='Name of output directory to create.') |
| 286 parser.add_argument('--repeat', default=1, type=int, | 274 parser.add_argument('--repeat', default=1, type=int, |
| 287 help='How many times to run the job') | 275 help='How many times to run the job') |
| 288 parser.add_argument('--cache-op', | 276 parser.add_argument('--cache-op', |
| 289 choices=['clear', 'save', 'push', 'reload'], | 277 choices=['clear', 'save', 'push', 'reload'], |
| 290 default='clear', | 278 default='clear', |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 324 assert os.path.isfile(local_cache_archive_path) | 312 assert os.path.isfile(local_cache_archive_path) |
| 325 local_cache_directory_path = tempfile.mkdtemp(suffix='.cache') | 313 local_cache_directory_path = tempfile.mkdtemp(suffix='.cache') |
| 326 _UnzipDirectoryContent(local_cache_archive_path, local_cache_directory_path) | 314 _UnzipDirectoryContent(local_cache_archive_path, local_cache_directory_path) |
| 327 | 315 |
| 328 with device_setup.WprHost(device, args.wpr_archive, args.wpr_record, | 316 with device_setup.WprHost(device, args.wpr_archive, args.wpr_record, |
| 329 args.disable_wpr_script_injection) as additional_flags: | 317 args.disable_wpr_script_injection) as additional_flags: |
| 330 def _RunNavigation(url, clear_cache, trace_id): | 318 def _RunNavigation(url, clear_cache, trace_id): |
| 331 with device_setup.DeviceConnection( | 319 with device_setup.DeviceConnection( |
| 332 device=device, | 320 device=device, |
| 333 additional_flags=additional_flags) as connection: | 321 additional_flags=additional_flags) as connection: |
| 334 if clear_cache: | 322 loading_trace = trace_recorder.MonitorUrl( |
| 335 connection.ClearCache() | 323 connection, url, |
| 336 page_track.PageTrack(connection) | 324 clear_cache=clear_cache, |
| 337 tracing_track = tracing.TracingTrack(connection, | |
| 338 categories=pull_sandwich_metrics.CATEGORIES) | 325 categories=pull_sandwich_metrics.CATEGORIES) |
| 339 connection.SetUpMonitoring() | |
| 340 connection.SendAndIgnoreResponse('Page.navigate', {'url': url}) | |
| 341 connection.StartMonitoring() | |
| 342 if trace_id != None: | 326 if trace_id != None: |
| 343 trace_target_directory = os.path.join(args.output, str(trace_id)) | 327 loading_trace_path = os.path.join( |
| 344 _SaveChromeTrace(tracing_track.ToJsonDict(), trace_target_directory) | 328 args.output, str(trace_id), 'trace.json') |
| 329 os.makedirs(os.path.dirname(loading_trace_path)) | |
| 330 loading_trace.SaveToJsonFile(loading_trace_path) | |
| 345 | 331 |
| 346 for _ in xrange(args.repeat): | 332 for _ in xrange(args.repeat): |
| 347 for url in job_urls: | 333 for url in job_urls: |
| 348 clear_cache = False | 334 clear_cache = False |
| 349 if args.cache_op == 'clear': | 335 if args.cache_op == 'clear': |
| 350 clear_cache = True | 336 clear_cache = True |
| 351 elif args.cache_op == 'push': | 337 elif args.cache_op == 'push': |
| 352 device.KillAll(_CHROME_PACKAGE, quiet=True) | 338 device.KillAll(OPTIONS.chrome_package_name, quiet=True) |
| 353 _PushBrowserCache(device, local_cache_directory_path) | 339 _PushBrowserCache(device, local_cache_directory_path) |
| 354 elif args.cache_op == 'reload': | 340 elif args.cache_op == 'reload': |
| 355 _RunNavigation(url, clear_cache=True, trace_id=None) | 341 _RunNavigation(url, clear_cache=True, trace_id=None) |
| 356 elif args.cache_op == 'save': | 342 elif args.cache_op == 'save': |
| 357 clear_cache = not run_infos['urls'] | 343 clear_cache = not run_infos['urls'] |
| 358 _RunNavigation(url, clear_cache=clear_cache, | 344 _RunNavigation(url, clear_cache=clear_cache, |
| 359 trace_id=len(run_infos['urls'])) | 345 trace_id=len(run_infos['urls'])) |
| 360 run_infos['urls'].append(url) | 346 run_infos['urls'].append(url) |
| 361 | 347 |
| 362 if local_cache_directory_path: | 348 if local_cache_directory_path: |
| 363 shutil.rmtree(local_cache_directory_path) | 349 shutil.rmtree(local_cache_directory_path) |
| 364 | 350 |
| 365 if args.cache_op == 'save': | 351 if args.cache_op == 'save': |
| 366 # Move Chrome to background to allow it to flush the index. | 352 # Move Chrome to background to allow it to flush the index. |
| 367 device.adb.Shell('am start com.google.android.launcher') | 353 device.adb.Shell('am start com.google.android.launcher') |
| 368 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) | 354 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) |
| 369 device.KillAll(_CHROME_PACKAGE, quiet=True) | 355 device.KillAll(OPTIONS.chrome_package_name, quiet=True) |
| 370 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) | 356 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) |
| 371 | 357 |
| 372 cache_directory_path = _PullBrowserCache(device) | 358 cache_directory_path = _PullBrowserCache(device) |
| 373 _ZipDirectoryContent(cache_directory_path, local_cache_archive_path) | 359 _ZipDirectoryContent(cache_directory_path, local_cache_archive_path) |
| 374 shutil.rmtree(cache_directory_path) | 360 shutil.rmtree(cache_directory_path) |
| 375 | 361 |
| 376 with open(os.path.join(args.output, 'run_infos.json'), 'w') as file_output: | 362 with open(os.path.join(args.output, 'run_infos.json'), 'w') as file_output: |
| 377 json.dump(run_infos, file_output, indent=2) | 363 json.dump(run_infos, file_output, indent=2) |
| 378 | 364 |
| 379 | 365 |
| 380 if __name__ == '__main__': | 366 if __name__ == '__main__': |
| 381 sys.exit(main()) | 367 sys.exit(main()) |
| OLD | NEW |