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 |
11 TODO(pasko): implement cache preparation and WPR. | 11 TODO(pasko): implement cache preparation and WPR. |
12 """ | 12 """ |
13 | 13 |
14 import argparse | 14 import argparse |
15 import json | |
15 import logging | 16 import logging |
16 import os | 17 import os |
18 import shutil | |
17 import sys | 19 import sys |
20 import tempfile | |
18 import time | 21 import time |
22 import zipfile | |
19 | 23 |
20 _SRC_DIR = os.path.abspath(os.path.join( | 24 _SRC_DIR = os.path.abspath(os.path.join( |
21 os.path.dirname(__file__), '..', '..', '..')) | 25 os.path.dirname(__file__), '..', '..', '..')) |
22 | 26 |
23 sys.path.append(os.path.join(_SRC_DIR, 'third_party', 'catapult', 'devil')) | 27 sys.path.append(os.path.join(_SRC_DIR, 'third_party', 'catapult', 'devil')) |
24 from devil.android import device_utils | 28 from devil.android import device_utils |
25 | 29 |
26 sys.path.append(os.path.join(_SRC_DIR, 'build', 'android')) | 30 sys.path.append(os.path.join(_SRC_DIR, 'build', 'android')) |
27 from pylib import constants | 31 from pylib import constants |
28 import devil_chromium | 32 import devil_chromium |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
93 json.dump({'traceEvents': events['events'], 'metadata': {}}, f, indent=2) | 97 json.dump({'traceEvents': events['events'], 'metadata': {}}, f, indent=2) |
94 except IOError: | 98 except IOError: |
95 logging.warning('Could not save a trace: %s' % filename) | 99 logging.warning('Could not save a trace: %s' % filename) |
96 # Swallow the exception. | 100 # Swallow the exception. |
97 | 101 |
98 | 102 |
99 def _UpdateTimestampFromAdbStat(filename, stat): | 103 def _UpdateTimestampFromAdbStat(filename, stat): |
100 os.utime(filename, (stat.st_time, stat.st_time)) | 104 os.utime(filename, (stat.st_time, stat.st_time)) |
101 | 105 |
102 | 106 |
103 def _SaveBrowserCache(device, output_directory): | 107 def _PullBrowserCache(device): |
104 """Pulls the browser cache from the device and saves it locally. | 108 """Pulls the browser cache from the device and saves it locally. |
105 | 109 |
106 Cache is saved with the same file structure as on the device. Timestamps are | 110 Cache is saved with the same file structure as on the device. Timestamps are |
107 important to preserve because indexing and eviction depends on them. | 111 important to preserve because indexing and eviction depends on them. |
108 | 112 |
109 Args: | 113 Returns: |
110 output_directory: name of the directory for saving cache. | 114 Oemporary directory containing all the browser cache. |
mattcary
2016/02/12 10:46:45
Temporary
Benoit L
2016/02/15 12:49:19
s/Oemporary/Temporary/
gabadie
2016/02/15 13:16:44
Done.
| |
111 """ | 115 """ |
112 save_target = os.path.join(output_directory, _CACHE_DIRECTORY_NAME) | 116 save_target = tempfile.mkdtemp(suffix='.cache') |
113 try: | |
114 os.makedirs(save_target) | |
115 except IOError: | |
116 logging.warning('Could not create directory: %s' % save_target) | |
117 raise | |
118 | |
119 cache_directory = '/data/data/' + _CHROME_PACKAGE + '/cache/Cache' | 117 cache_directory = '/data/data/' + _CHROME_PACKAGE + '/cache/Cache' |
120 for filename, stat in device.adb.Ls(cache_directory): | 118 for filename, stat in device.adb.Ls(cache_directory): |
121 if filename == '..': | 119 if filename == '..': |
122 continue | 120 continue |
123 if filename == '.': | 121 if filename == '.': |
124 cache_directory_stat = stat | 122 cache_directory_stat = stat |
125 continue | 123 continue |
126 original_file = os.path.join(cache_directory, filename) | 124 original_file = os.path.join(cache_directory, filename) |
127 saved_file = os.path.join(save_target, filename) | 125 saved_file = os.path.join(save_target, filename) |
128 device.adb.Pull(original_file, saved_file) | 126 device.adb.Pull(original_file, saved_file) |
129 _UpdateTimestampFromAdbStat(saved_file, stat) | 127 _UpdateTimestampFromAdbStat(saved_file, stat) |
130 if filename == _INDEX_DIRECTORY_NAME: | 128 if filename == _INDEX_DIRECTORY_NAME: |
131 # The directory containing the index was pulled recursively, update the | 129 # The directory containing the index was pulled recursively, update the |
132 # timestamps for known files. They are ignored by cache backend, but may | 130 # timestamps for known files. They are ignored by cache backend, but may |
133 # be useful for debugging. | 131 # be useful for debugging. |
134 index_dir_stat = stat | 132 index_dir_stat = stat |
135 saved_index_dir = os.path.join(save_target, _INDEX_DIRECTORY_NAME) | 133 saved_index_dir = os.path.join(save_target, _INDEX_DIRECTORY_NAME) |
136 saved_index_file = os.path.join(saved_index_dir, _REAL_INDEX_FILE_NAME) | 134 saved_index_file = os.path.join(saved_index_dir, _REAL_INDEX_FILE_NAME) |
137 for sub_file, sub_stat in device.adb.Ls(original_file): | 135 for sub_file, sub_stat in device.adb.Ls(original_file): |
138 if sub_file == _REAL_INDEX_FILE_NAME: | 136 if sub_file == _REAL_INDEX_FILE_NAME: |
139 _UpdateTimestampFromAdbStat(saved_index_file, sub_stat) | 137 _UpdateTimestampFromAdbStat(saved_index_file, sub_stat) |
140 break | 138 break |
141 _UpdateTimestampFromAdbStat(saved_index_dir, index_dir_stat) | 139 _UpdateTimestampFromAdbStat(saved_index_dir, index_dir_stat) |
142 | 140 |
143 # Store the cache directory modification time. It is important to update it | 141 # Store the cache directory modification time. It is important to update it |
144 # after all files in it have been written. The timestamp is compared with | 142 # after all files in it have been written. The timestamp is compared with |
145 # the contents of the index file when freshness is determined. | 143 # the contents of the index file when freshness is determined. |
146 _UpdateTimestampFromAdbStat(save_target, cache_directory_stat) | 144 _UpdateTimestampFromAdbStat(save_target, cache_directory_stat) |
145 return save_target | |
147 | 146 |
147 def _ZipDirectoryContent(root_directory_path, archive_dest_path): | |
Benoit L
2016/02/15 12:49:19
nit: 2 blank lines.
gabadie
2016/02/15 13:16:44
Done.
| |
148 """Zip a directory's content recursively with all the directories's | |
Benoit L
2016/02/15 12:49:19
s/'s/'/
gabadie
2016/02/15 13:16:44
Done.
| |
149 timestamp preserved. | |
150 | |
151 Args: | |
152 root_directory_path: The directory's path to archive. | |
153 archive_dest_path: Archive destination's path. | |
154 """ | |
155 with zipfile.ZipFile(archive_dest_path, 'w') as zip_output: | |
156 timestamps = {} | |
157 for directory_path, dirnames, filenames in os.walk(root_directory_path): | |
158 for dirname in dirnames: | |
159 subdirectory_path = os.path.join(directory_path, dirname) | |
160 subdirectory_relative_path = os.path.relpath(subdirectory_path, | |
161 root_directory_path) | |
162 subdirectory_stats = os.stat(subdirectory_path) | |
163 timestamps[subdirectory_relative_path] = { | |
164 'atime': subdirectory_stats.st_atime, | |
165 'mtime': subdirectory_stats.st_mtime} | |
166 for filename in filenames: | |
167 file_path = os.path.join(directory_path, filename) | |
168 file_archive_name = os.path.join('content', | |
169 os.path.relpath(file_path, root_directory_path)) | |
170 file_stats = os.stat(file_path) | |
171 timestamps[file_archive_name[8:]] = { | |
172 'atime': file_stats.st_atime, | |
173 'mtime': file_stats.st_mtime} | |
174 zip_output.write(file_path, arcname=file_archive_name) | |
175 zip_output.writestr('timestamps.json', | |
176 json.dumps(timestamps, indent=2)) | |
177 | |
178 def _UnzipDirectoryContent(archive_path, directory_dest_path): | |
179 """Unzip a directory's content recursively with all the directories's | |
Benoit L
2016/02/15 12:49:19
s/'s/'/
gabadie
2016/02/15 13:16:44
Done.
| |
180 timestamp preserved. | |
Benoit L
2016/02/15 12:49:19
timestamps.
gabadie
2016/02/15 13:16:44
Done.
| |
181 | |
182 Args: | |
183 archive_path: Archive's path to unzip. | |
184 directory_dest_path: Directory destination path. | |
185 """ | |
186 if not os.path.exists(directory_dest_path): | |
187 os.makedirs(directory_dest_path) | |
188 | |
189 with zipfile.ZipFile(archive_path) as zip_input: | |
190 timestamps = None | |
191 for file_archive_name in zip_input.namelist(): | |
192 if file_archive_name == 'timestamps.json': | |
193 timestamps = json.loads(zip_input.read(file_archive_name)) | |
194 | |
Benoit L
2016/02/15 12:49:19
nit: blank line here is weird.
gabadie
2016/02/15 13:16:44
Done.
| |
195 elif file_archive_name.startswith('content/'): | |
196 file_relative_path = file_archive_name[8:] | |
197 file_output_path = os.path.join(directory_dest_path, file_relative_path) | |
198 file_parent_directory_path = os.path.dirname(file_output_path) | |
199 if not os.path.exists(file_parent_directory_path): | |
200 os.makedirs(file_parent_directory_path) | |
201 with open(file_output_path, 'w') as f: | |
202 f.write(zip_input.read(file_archive_name)) | |
203 | |
204 assert timestamps | |
205 for relative_path, stats in timestamps.iteritems(): | |
206 output_path = os.path.join(directory_dest_path, relative_path) | |
207 if not os.path.exists(output_path): | |
208 os.makedirs(output_path) | |
209 os.utime(output_path, (stats['atime'], stats['mtime'])) | |
148 | 210 |
149 def main(): | 211 def main(): |
150 logging.basicConfig(level=logging.INFO) | 212 logging.basicConfig(level=logging.INFO) |
151 devil_chromium.Initialize() | 213 devil_chromium.Initialize() |
152 | 214 |
153 parser = argparse.ArgumentParser() | 215 parser = argparse.ArgumentParser() |
154 parser.add_argument('--job', required=True, | 216 parser.add_argument('--job', required=True, |
155 help='JSON file with job description.') | 217 help='JSON file with job description.') |
156 parser.add_argument('--output', required=True, | 218 parser.add_argument('--output', required=True, |
157 help='Name of output directory to create.') | 219 help='Name of output directory to create.') |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
196 pages_loaded += 1 | 258 pages_loaded += 1 |
197 _SaveChromeTrace(tracing_track.ToJsonDict(), args.output, | 259 _SaveChromeTrace(tracing_track.ToJsonDict(), args.output, |
198 str(pages_loaded)) | 260 str(pages_loaded)) |
199 | 261 |
200 if args.save_cache: | 262 if args.save_cache: |
201 # Move Chrome to background to allow it to flush the index. | 263 # Move Chrome to background to allow it to flush the index. |
202 device.adb.Shell('am start com.google.android.launcher') | 264 device.adb.Shell('am start com.google.android.launcher') |
203 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) | 265 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) |
204 device.KillAll(_CHROME_PACKAGE, quiet=True) | 266 device.KillAll(_CHROME_PACKAGE, quiet=True) |
205 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) | 267 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) |
206 _SaveBrowserCache(device, args.output) | 268 |
269 cache_directory_path = _PullBrowserCache(device) | |
270 _ZipDirectoryContent(cache_directory_path, | |
271 os.path.join(args.output, 'cache.zip')) | |
272 shutil.rmtree(cache_directory_path) | |
207 | 273 |
208 | 274 |
209 if __name__ == '__main__': | 275 if __name__ == '__main__': |
210 sys.exit(main()) | 276 sys.exit(main()) |
OLD | NEW |