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 Temporary directory containing all the browser cache. |
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 |
| 146 |
| 147 |
| 148 def _ZipDirectoryContent(root_directory_path, archive_dest_path): |
| 149 """Zip a directory's content recursively with all the directories' |
| 150 timestamps preserved. |
| 151 |
| 152 Args: |
| 153 root_directory_path: The directory's path to archive. |
| 154 archive_dest_path: Archive destination's path. |
| 155 """ |
| 156 with zipfile.ZipFile(archive_dest_path, 'w') as zip_output: |
| 157 timestamps = {} |
| 158 for directory_path, dirnames, filenames in os.walk(root_directory_path): |
| 159 for dirname in dirnames: |
| 160 subdirectory_path = os.path.join(directory_path, dirname) |
| 161 subdirectory_relative_path = os.path.relpath(subdirectory_path, |
| 162 root_directory_path) |
| 163 subdirectory_stats = os.stat(subdirectory_path) |
| 164 timestamps[subdirectory_relative_path] = { |
| 165 'atime': subdirectory_stats.st_atime, |
| 166 'mtime': subdirectory_stats.st_mtime} |
| 167 for filename in filenames: |
| 168 file_path = os.path.join(directory_path, filename) |
| 169 file_archive_name = os.path.join('content', |
| 170 os.path.relpath(file_path, root_directory_path)) |
| 171 file_stats = os.stat(file_path) |
| 172 timestamps[file_archive_name[8:]] = { |
| 173 'atime': file_stats.st_atime, |
| 174 'mtime': file_stats.st_mtime} |
| 175 zip_output.write(file_path, arcname=file_archive_name) |
| 176 zip_output.writestr('timestamps.json', |
| 177 json.dumps(timestamps, indent=2)) |
| 178 |
| 179 |
| 180 def _UnzipDirectoryContent(archive_path, directory_dest_path): |
| 181 """Unzip a directory's content recursively with all the directories' |
| 182 timestamps preserved. |
| 183 |
| 184 Args: |
| 185 archive_path: Archive's path to unzip. |
| 186 directory_dest_path: Directory destination path. |
| 187 """ |
| 188 if not os.path.exists(directory_dest_path): |
| 189 os.makedirs(directory_dest_path) |
| 190 |
| 191 with zipfile.ZipFile(archive_path) as zip_input: |
| 192 timestamps = None |
| 193 for file_archive_name in zip_input.namelist(): |
| 194 if file_archive_name == 'timestamps.json': |
| 195 timestamps = json.loads(zip_input.read(file_archive_name)) |
| 196 elif file_archive_name.startswith('content/'): |
| 197 file_relative_path = file_archive_name[8:] |
| 198 file_output_path = os.path.join(directory_dest_path, file_relative_path) |
| 199 file_parent_directory_path = os.path.dirname(file_output_path) |
| 200 if not os.path.exists(file_parent_directory_path): |
| 201 os.makedirs(file_parent_directory_path) |
| 202 with open(file_output_path, 'w') as f: |
| 203 f.write(zip_input.read(file_archive_name)) |
| 204 |
| 205 assert timestamps |
| 206 for relative_path, stats in timestamps.iteritems(): |
| 207 output_path = os.path.join(directory_dest_path, relative_path) |
| 208 if not os.path.exists(output_path): |
| 209 os.makedirs(output_path) |
| 210 os.utime(output_path, (stats['atime'], stats['mtime'])) |
147 | 211 |
148 | 212 |
149 def main(): | 213 def main(): |
150 logging.basicConfig(level=logging.INFO) | 214 logging.basicConfig(level=logging.INFO) |
151 devil_chromium.Initialize() | 215 devil_chromium.Initialize() |
152 | 216 |
153 parser = argparse.ArgumentParser() | 217 parser = argparse.ArgumentParser() |
154 parser.add_argument('--job', required=True, | 218 parser.add_argument('--job', required=True, |
155 help='JSON file with job description.') | 219 help='JSON file with job description.') |
156 parser.add_argument('--output', required=True, | 220 parser.add_argument('--output', required=True, |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
196 pages_loaded += 1 | 260 pages_loaded += 1 |
197 _SaveChromeTrace(tracing_track.ToJsonDict(), args.output, | 261 _SaveChromeTrace(tracing_track.ToJsonDict(), args.output, |
198 str(pages_loaded)) | 262 str(pages_loaded)) |
199 | 263 |
200 if args.save_cache: | 264 if args.save_cache: |
201 # Move Chrome to background to allow it to flush the index. | 265 # Move Chrome to background to allow it to flush the index. |
202 device.adb.Shell('am start com.google.android.launcher') | 266 device.adb.Shell('am start com.google.android.launcher') |
203 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) | 267 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) |
204 device.KillAll(_CHROME_PACKAGE, quiet=True) | 268 device.KillAll(_CHROME_PACKAGE, quiet=True) |
205 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) | 269 time.sleep(_TIME_TO_DEVICE_IDLE_SECONDS) |
206 _SaveBrowserCache(device, args.output) | 270 |
| 271 cache_directory_path = _PullBrowserCache(device) |
| 272 _ZipDirectoryContent(cache_directory_path, |
| 273 os.path.join(args.output, 'cache.zip')) |
| 274 shutil.rmtree(cache_directory_path) |
207 | 275 |
208 | 276 |
209 if __name__ == '__main__': | 277 if __name__ == '__main__': |
210 sys.exit(main()) | 278 sys.exit(main()) |
OLD | NEW |