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 |