1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Archives or replays webpages and creates SKPs in a Google Storage location. 7 8To archive webpages and store SKP files (archives should be rarely updated): 9 10cd skia 11python tools/skp/webpages_playback.py --data_store=gs://rmistry --record \ 12--page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \ 13--browser_executable=/tmp/chromium/out/Release/chrome 14 15The above command uses Google Storage bucket 'rmistry' to download needed files. 16 17To replay archived webpages and re-generate SKP files (should be run whenever 18SkPicture.PICTURE_VERSION changes): 19 20cd skia 21python tools/skp/webpages_playback.py --data_store=gs://rmistry \ 22--page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \ 23--browser_executable=/tmp/chromium/out/Release/chrome 24 25 26Specify the --page_sets flag (default value is 'all') to pick a list of which 27webpages should be archived and/or replayed. Eg: 28 29--page_sets=tools/skp/page_sets/skia_yahooanswers_desktop.py,\ 30tools/skp/page_sets/skia_googlecalendar_nexus10.py 31 32The --browser_executable flag should point to the browser binary you want to use 33to capture archives and/or capture SKP files. Majority of the time it should be 34a newly built chrome binary. 35 36The --data_store flag controls where the needed artifacts are downloaded from. 37It also controls where the generated artifacts, such as recorded webpages and 38resulting skp renderings, are uploaded to. URLs with scheme 'gs://' use Google 39Storage. Otherwise use local filesystem. 40 41The --upload=True flag means generated artifacts will be 42uploaded or copied to the location specified by --data_store. (default value is 43False if not specified). 44 45The --non-interactive flag controls whether the script will prompt the user 46(default value is False if not specified). 47 48The --skia_tools flag if specified will allow this script to run 49debugger, render_pictures, and render_pdfs on the captured 50SKP(s). The tools are run after all SKPs are succesfully captured to make sure 51they can be added to the buildbots with no breakages. 52""" 53 54import datetime 55import glob 56import optparse 57import os 58import posixpath 59import shutil 60import subprocess 61import sys 62import tempfile 63import time 64import traceback 65 66 67ROOT_PLAYBACK_DIR_NAME = 'playback' 68SKPICTURES_DIR_NAME = 'skps' 69 70GS_PREFIX = 'gs://' 71 72PARTNERS_GS_BUCKET = 'gs://chrome-partner-telemetry' 73 74# Local archive and SKP directories. 75LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR = os.path.join( 76 os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data') 77TMP_SKP_DIR = tempfile.mkdtemp() 78 79# Location of the credentials.json file and the string that represents missing 80# passwords. 81CREDENTIALS_FILE_PATH = os.path.join( 82 os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data', 83 'credentials.json' 84) 85 86# Name of the SKP benchmark 87SKP_BENCHMARK = 'skpicture_printer' 88 89# The max base name length of Skp files. 90MAX_SKP_BASE_NAME_LEN = 31 91 92# Dictionary of device to platform prefixes for SKP files. 93DEVICE_TO_PLATFORM_PREFIX = { 94 'desktop': 'desk', 95 'mobile': 'mobi', 96 'tablet': 'tabl' 97} 98 99# How many times the record_wpr binary should be retried. 100RETRY_RECORD_WPR_COUNT = 5 101# How many times the run_benchmark binary should be retried. 102RETRY_RUN_MEASUREMENT_COUNT = 3 103 104# Location of the credentials.json file in Google Storage. 105CREDENTIALS_GS_PATH = 'playback/credentials/credentials.json' 106 107X11_DISPLAY = os.getenv('DISPLAY', ':0') 108 109# Path to Chromium's page sets. 110CHROMIUM_PAGE_SETS_PATH = os.path.join('tools', 'perf', 'page_sets') 111 112# Dictionary of supported Chromium page sets to their file prefixes. 113CHROMIUM_PAGE_SETS_TO_PREFIX = { 114} 115 116PAGE_SETS_TO_EXCLUSIONS = { 117 # See skbug.com/7348 118 'key_mobile_sites_smooth.py': '"(digg|worldjournal|twitter|espn)"', 119 # See skbug.com/7421 120 'top_25_smooth.py': '"(mail\.google\.com)"', 121} 122 123 124class InvalidSKPException(Exception): 125 """Raised when the created SKP is invalid.""" 126 pass 127 128 129def remove_prefix(s, prefix): 130 if s.startswith(prefix): 131 return s[len(prefix):] 132 return s 133 134 135class SkPicturePlayback(object): 136 """Class that archives or replays webpages and creates SKPs.""" 137 138 def __init__(self, parse_options): 139 """Constructs a SkPicturePlayback BuildStep instance.""" 140 assert parse_options.browser_executable, 'Must specify --browser_executable' 141 self._browser_executable = parse_options.browser_executable 142 self._browser_args = '--disable-setuid-sandbox' 143 if parse_options.browser_extra_args: 144 self._browser_args = '%s %s' % ( 145 self._browser_args, parse_options.browser_extra_args) 146 147 self._chrome_page_sets_path = os.path.join(parse_options.chrome_src_path, 148 CHROMIUM_PAGE_SETS_PATH) 149 self._all_page_sets_specified = parse_options.page_sets == 'all' 150 self._page_sets = self._ParsePageSets(parse_options.page_sets) 151 152 self._record = parse_options.record 153 self._skia_tools = parse_options.skia_tools 154 self._non_interactive = parse_options.non_interactive 155 self._upload = parse_options.upload 156 self._skp_prefix = parse_options.skp_prefix 157 data_store_location = parse_options.data_store 158 if data_store_location.startswith(GS_PREFIX): 159 self.gs = GoogleStorageDataStore(data_store_location) 160 else: 161 self.gs = LocalFileSystemDataStore(data_store_location) 162 self._upload_to_partner_bucket = parse_options.upload_to_partner_bucket 163 self._alternate_upload_dir = parse_options.alternate_upload_dir 164 self._telemetry_binaries_dir = os.path.join(parse_options.chrome_src_path, 165 'tools', 'perf') 166 self._catapult_dir = os.path.join(parse_options.chrome_src_path, 167 'third_party', 'catapult') 168 169 self._local_skp_dir = os.path.join( 170 parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, SKPICTURES_DIR_NAME) 171 self._local_record_webpages_archive_dir = os.path.join( 172 parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, 'webpages_archive') 173 174 # List of SKP files generated by this script. 175 self._skp_files = [] 176 177 def _ParsePageSets(self, page_sets): 178 if not page_sets: 179 raise ValueError('Must specify at least one page_set!') 180 elif self._all_page_sets_specified: 181 # Get everything from the page_sets directory. 182 page_sets_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 183 'page_sets') 184 ps = [os.path.join(page_sets_dir, page_set) 185 for page_set in os.listdir(page_sets_dir) 186 if not os.path.isdir(os.path.join(page_sets_dir, page_set)) and 187 page_set.endswith('.py')] 188 chromium_ps = [ 189 os.path.join(self._chrome_page_sets_path, cr_page_set) 190 for cr_page_set in CHROMIUM_PAGE_SETS_TO_PREFIX] 191 ps.extend(chromium_ps) 192 elif '*' in page_sets: 193 # Explode and return the glob. 194 ps = glob.glob(page_sets) 195 else: 196 ps = page_sets.split(',') 197 ps.sort() 198 return ps 199 200 def _IsChromiumPageSet(self, page_set): 201 """Returns true if the specified page set is a Chromium page set.""" 202 return page_set.startswith(self._chrome_page_sets_path) 203 204 def Run(self): 205 """Run the SkPicturePlayback BuildStep.""" 206 207 # Download the credentials file if it was not previously downloaded. 208 if not os.path.isfile(CREDENTIALS_FILE_PATH): 209 # Download the credentials.json file from Google Storage. 210 self.gs.download_file(CREDENTIALS_GS_PATH, CREDENTIALS_FILE_PATH) 211 212 if not os.path.isfile(CREDENTIALS_FILE_PATH): 213 raise Exception("""Could not locate credentials file in the storage. 214 Please create a credentials file in gs://%s that contains: 215 { 216 "google": { 217 "username": "google_testing_account_username", 218 "password": "google_testing_account_password" 219 } 220 }\n\n""" % CREDENTIALS_GS_PATH) 221 222 # Delete any left over data files in the data directory. 223 for archive_file in glob.glob( 224 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 'skia_*')): 225 os.remove(archive_file) 226 227 # Create the required local storage directories. 228 self._CreateLocalStorageDirs() 229 230 # Start the timer. 231 start_time = time.time() 232 233 # Loop through all page_sets. 234 for page_set in self._page_sets: 235 if os.path.basename(page_set) == '__init__.py': 236 continue 237 page_set_basename = os.path.basename(page_set).split('.')[0] 238 page_set_json_name = page_set_basename + '.json' 239 wpr_data_file_glob = ( 240 page_set.split(os.path.sep)[-1].split('.')[0] + '_*.wprgo') 241 page_set_dir = os.path.dirname(page_set) 242 243 if self._IsChromiumPageSet(page_set): 244 print 'Using Chromium\'s captured archives for Chromium\'s page sets.' 245 elif self._record: 246 # Create an archive of the specified webpages if '--record=True' is 247 # specified. 248 record_wpr_cmd = ( 249 'PYTHONPATH=%s:%s:$PYTHONPATH' % (page_set_dir, self._catapult_dir), 250 'DISPLAY=%s' % X11_DISPLAY, 251 os.path.join(self._telemetry_binaries_dir, 'record_wpr'), 252 '--extra-browser-args="%s"' % self._browser_args, 253 '--browser=exact', 254 '--browser-executable=%s' % self._browser_executable, 255 '%s_page_set' % page_set_basename, 256 '--page-set-base-dir=%s' % page_set_dir 257 ) 258 for _ in range(RETRY_RECORD_WPR_COUNT): 259 try: 260 subprocess.check_call(' '.join(record_wpr_cmd), shell=True) 261 262 # Copy over the created archive into the local webpages archive 263 # directory. 264 for wpr_data_file in glob.glob(os.path.join( 265 LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file_glob)): 266 shutil.copy( 267 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file), 268 self._local_record_webpages_archive_dir) 269 shutil.copy( 270 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 271 page_set_json_name), 272 self._local_record_webpages_archive_dir) 273 274 # Break out of the retry loop since there were no errors. 275 break 276 except Exception: 277 # There was a failure continue with the loop. 278 traceback.print_exc() 279 else: 280 # If we get here then record_wpr did not succeed and thus did not 281 # break out of the loop. 282 raise Exception('record_wpr failed for page_set: %s' % page_set) 283 284 else: 285 # Get the webpages archive so that it can be replayed. 286 self._DownloadWebpagesArchive(wpr_data_file_glob, page_set_json_name) 287 288 run_benchmark_cmd = [ 289 'PYTHONPATH=%s:%s:$PYTHONPATH' % (page_set_dir, self._catapult_dir), 290 'DISPLAY=%s' % X11_DISPLAY, 291 'timeout', '1800', 292 os.path.join(self._telemetry_binaries_dir, 'run_benchmark'), 293 '--extra-browser-args="%s"' % self._browser_args, 294 '--browser=exact', 295 '--browser-executable=%s' % self._browser_executable, 296 SKP_BENCHMARK, 297 '--page-set-name=%s' % page_set_basename, 298 '--page-set-base-dir=%s' % page_set_dir, 299 '--skp-outdir=%s' % TMP_SKP_DIR, 300 '--also-run-disabled-tests', 301 ] 302 303 exclusions = PAGE_SETS_TO_EXCLUSIONS.get(os.path.basename(page_set)) 304 if exclusions: 305 run_benchmark_cmd.append('--story-filter-exclude=' + exclusions) 306 307 for _ in range(RETRY_RUN_MEASUREMENT_COUNT): 308 try: 309 print '\n\n=======Capturing SKP of %s=======\n\n' % page_set 310 subprocess.check_call(' '.join(run_benchmark_cmd), shell=True) 311 except subprocess.CalledProcessError: 312 # There was a failure continue with the loop. 313 traceback.print_exc() 314 print '\n\n=======Retrying %s=======\n\n' % page_set 315 time.sleep(10) 316 continue 317 318 try: 319 # Rename generated SKP files into more descriptive names. 320 self._RenameSkpFiles(page_set) 321 except InvalidSKPException: 322 # There was a failure continue with the loop. 323 traceback.print_exc() 324 print '\n\n=======Retrying %s=======\n\n' % page_set 325 time.sleep(10) 326 continue 327 328 # Break out of the retry loop since there were no errors. 329 break 330 else: 331 # If we get here then run_benchmark did not succeed and thus did not 332 # break out of the loop. 333 raise Exception('run_benchmark failed for page_set: %s' % page_set) 334 335 print '\n\n=======Capturing SKP files took %s seconds=======\n\n' % ( 336 time.time() - start_time) 337 338 if self._skia_tools: 339 render_pictures_cmd = [ 340 os.path.join(self._skia_tools, 'render_pictures'), 341 '-r', self._local_skp_dir 342 ] 343 render_pdfs_cmd = [ 344 os.path.join(self._skia_tools, 'render_pdfs'), 345 '-r', self._local_skp_dir 346 ] 347 348 for tools_cmd in (render_pictures_cmd, render_pdfs_cmd): 349 print '\n\n=======Running %s=======' % ' '.join(tools_cmd) 350 subprocess.check_call(tools_cmd) 351 352 if not self._non_interactive: 353 print '\n\n=======Running debugger=======' 354 os.system('%s %s' % (os.path.join(self._skia_tools, 'debugger'), 355 self._local_skp_dir)) 356 357 print '\n\n' 358 359 if self._upload: 360 print '\n\n=======Uploading to %s=======\n\n' % self.gs.target_type() 361 # Copy the directory structure in the root directory into Google Storage. 362 dest_dir_name = ROOT_PLAYBACK_DIR_NAME 363 if self._alternate_upload_dir: 364 dest_dir_name = self._alternate_upload_dir 365 366 self.gs.upload_dir_contents( 367 self._local_skp_dir, dest_dir=dest_dir_name) 368 369 print '\n\n=======New SKPs have been uploaded to %s =======\n\n' % ( 370 posixpath.join(self.gs.target_name(), dest_dir_name, 371 SKPICTURES_DIR_NAME)) 372 373 else: 374 print '\n\n=======Not Uploading to %s=======\n\n' % self.gs.target_type() 375 print 'Generated resources are available in %s\n\n' % ( 376 self._local_skp_dir) 377 378 if self._upload_to_partner_bucket: 379 print '\n\n=======Uploading to Partner bucket %s =======\n\n' % ( 380 PARTNERS_GS_BUCKET) 381 partner_gs = GoogleStorageDataStore(PARTNERS_GS_BUCKET) 382 timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%d') 383 upload_dir = posixpath.join(SKPICTURES_DIR_NAME, timestamp) 384 try: 385 partner_gs.delete_path(upload_dir) 386 except subprocess.CalledProcessError: 387 print 'Cannot delete %s because it does not exist yet.' % upload_dir 388 print 'Uploading %s to %s' % (self._local_skp_dir, upload_dir) 389 partner_gs.upload_dir_contents(self._local_skp_dir, upload_dir) 390 print '\n\n=======New SKPs have been uploaded to %s =======\n\n' % ( 391 posixpath.join(partner_gs.target_name(), upload_dir)) 392 393 return 0 394 395 def _GetSkiaSkpFileName(self, page_set): 396 """Returns the SKP file name for Skia page sets.""" 397 # /path/to/skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop.py 398 ps_filename = os.path.basename(page_set) 399 # skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop 400 ps_basename, _ = os.path.splitext(ps_filename) 401 # skia_yahooanswers_desktop -> skia, yahooanswers, desktop 402 _, page_name, device = ps_basename.split('_') 403 basename = '%s_%s' % (DEVICE_TO_PLATFORM_PREFIX[device], page_name) 404 return basename[:MAX_SKP_BASE_NAME_LEN] + '.skp' 405 406 def _GetChromiumSkpFileName(self, page_set, site): 407 """Returns the SKP file name for Chromium page sets.""" 408 # /path/to/http___mobile_news_sandbox_pt0 -> http___mobile_news_sandbox_pt0 409 _, webpage = os.path.split(site) 410 # http___mobile_news_sandbox_pt0 -> mobile_news_sandbox_pt0 411 for prefix in ('http___', 'https___', 'www_'): 412 if webpage.startswith(prefix): 413 webpage = webpage[len(prefix):] 414 # /path/to/skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop.py 415 ps_filename = os.path.basename(page_set) 416 # http___mobile_news_sandbox -> pagesetprefix_http___mobile_news_sandbox 417 basename = '%s_%s' % (CHROMIUM_PAGE_SETS_TO_PREFIX[ps_filename], webpage) 418 return basename[:MAX_SKP_BASE_NAME_LEN] + '.skp' 419 420 def _RenameSkpFiles(self, page_set): 421 """Rename generated SKP files into more descriptive names. 422 423 Look into the subdirectory of TMP_SKP_DIR and find the most interesting 424 .skp in there to be this page_set's representative .skp. 425 426 Throws InvalidSKPException if the chosen .skp is less than 1KB. This 427 typically happens when there is a 404 or a redirect loop. Anything greater 428 than 1KB seems to have captured at least some useful information. 429 """ 430 subdirs = glob.glob(os.path.join(TMP_SKP_DIR, '*')) 431 for site in subdirs: 432 if self._IsChromiumPageSet(page_set): 433 filename = self._GetChromiumSkpFileName(page_set, site) 434 else: 435 filename = self._GetSkiaSkpFileName(page_set) 436 filename = filename.lower() 437 438 if self._skp_prefix: 439 filename = '%s%s' % (self._skp_prefix, filename) 440 441 # We choose the largest .skp as the most likely to be interesting. 442 largest_skp = max(glob.glob(os.path.join(site, '*.skp')), 443 key=lambda path: os.stat(path).st_size) 444 dest = os.path.join(self._local_skp_dir, filename) 445 print 'Moving', largest_skp, 'to', dest 446 shutil.move(largest_skp, dest) 447 self._skp_files.append(filename) 448 shutil.rmtree(site) 449 skp_size = os.path.getsize(dest) 450 if skp_size < 1024: 451 raise InvalidSKPException( 452 'Size of %s is only %d. Something is wrong.' % (dest, skp_size)) 453 454 455 def _CreateLocalStorageDirs(self): 456 """Creates required local storage directories for this script.""" 457 for d in (self._local_record_webpages_archive_dir, 458 self._local_skp_dir): 459 if os.path.exists(d): 460 shutil.rmtree(d) 461 os.makedirs(d) 462 463 def _DownloadWebpagesArchive(self, wpr_data_file, page_set_json_name): 464 """Downloads the webpages archive and its required page set from GS.""" 465 wpr_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME, 'webpages_archive', 466 wpr_data_file) 467 page_set_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME, 468 'webpages_archive', 469 page_set_json_name) 470 gs = self.gs 471 if (gs.does_storage_object_exist(wpr_source) and 472 gs.does_storage_object_exist(page_set_source)): 473 gs.download_file(wpr_source, LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR) 474 gs.download_file(page_set_source, 475 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 476 page_set_json_name)) 477 else: 478 raise Exception('%s and %s do not exist in %s!' % (gs.target_type(), 479 wpr_source, page_set_source)) 480 481class DataStore: 482 """An abstract base class for uploading recordings to a data storage. 483 The interface emulates the google storage api.""" 484 def target_name(self): 485 raise NotImplementedError() 486 def target_type(self): 487 raise NotImplementedError() 488 def does_storage_object_exist(self, name): 489 raise NotImplementedError() 490 def download_file(self, name, local_path): 491 raise NotImplementedError() 492 def upload_dir_contents(self, source_dir, dest_dir): 493 raise NotImplementedError() 494 495 496class GoogleStorageDataStore(DataStore): 497 def __init__(self, data_store_url): 498 self._url = data_store_url.rstrip('/') 499 500 def target_name(self): 501 return self._url 502 503 def target_type(self): 504 return 'Google Storage' 505 506 def does_storage_object_exist(self, name): 507 try: 508 output = subprocess.check_output([ 509 'gsutil', 'ls', '/'.join((self._url, name))]) 510 except subprocess.CalledProcessError: 511 return False 512 if len(output.splitlines()) != 1: 513 return False 514 return True 515 516 def delete_path(self, path): 517 subprocess.check_call(['gsutil', 'rm', '-r', '/'.join((self._url, path))]) 518 519 def download_file(self, name, local_path): 520 subprocess.check_call([ 521 'gsutil', 'cp', '/'.join((self._url, name)), local_path]) 522 523 def upload_dir_contents(self, source_dir, dest_dir): 524 subprocess.check_call([ 525 'gsutil', 'cp', '-r', source_dir, '/'.join((self._url, dest_dir))]) 526 527 528class LocalFileSystemDataStore(DataStore): 529 def __init__(self, data_store_location): 530 self._base_dir = data_store_location 531 def target_name(self): 532 return self._base_dir 533 def target_type(self): 534 return self._base_dir 535 def does_storage_object_exist(self, name): 536 return os.path.isfile(os.path.join(self._base_dir, name)) 537 def delete_path(self, path): 538 shutil.rmtree(path) 539 def download_file(self, name, local_path): 540 shutil.copyfile(os.path.join(self._base_dir, name), local_path) 541 def upload_dir_contents(self, source_dir, dest_dir): 542 def copytree(source_dir, dest_dir): 543 if not os.path.exists(dest_dir): 544 os.makedirs(dest_dir) 545 for item in os.listdir(source_dir): 546 source = os.path.join(source_dir, item) 547 dest = os.path.join(dest_dir, item) 548 if os.path.isdir(source): 549 copytree(source, dest) 550 else: 551 shutil.copy2(source, dest) 552 copytree(source_dir, os.path.join(self._base_dir, dest_dir)) 553 554if '__main__' == __name__: 555 option_parser = optparse.OptionParser() 556 option_parser.add_option( 557 '', '--page_sets', 558 help='Specifies the page sets to use to archive. Supports globs.', 559 default='all') 560 option_parser.add_option( 561 '', '--record', action='store_true', 562 help='Specifies whether a new website archive should be created.', 563 default=False) 564 option_parser.add_option( 565 '', '--skia_tools', 566 help=('Path to compiled Skia executable tools. ' 567 'render_pictures/render_pdfs is run on the set ' 568 'after all SKPs are captured. If the script is run without ' 569 '--non-interactive then the debugger is also run at the end. Debug ' 570 'builds are recommended because they seem to catch more failures ' 571 'than Release builds.'), 572 default=None) 573 option_parser.add_option( 574 '', '--upload', action='store_true', 575 help=('Uploads to Google Storage or copies to local filesystem storage ' 576 ' if this is True.'), 577 default=False) 578 option_parser.add_option( 579 '', '--upload_to_partner_bucket', action='store_true', 580 help=('Uploads SKPs to the chrome-partner-telemetry Google Storage ' 581 'bucket if true.'), 582 default=False) 583 option_parser.add_option( 584 '', '--data_store', 585 help=('The location of the file storage to use to download and upload ' 586 'files. Can be \'gs://<bucket>\' for Google Storage, or ' 587 'a directory for local filesystem storage'), 588 default='gs://skia-skps') 589 option_parser.add_option( 590 '', '--alternate_upload_dir', 591 help= ('Uploads to a different directory in Google Storage or local ' 592 'storage if this flag is specified'), 593 default=None) 594 option_parser.add_option( 595 '', '--output_dir', 596 help=('Temporary directory where SKPs and webpage archives will be ' 597 'outputted to.'), 598 default=tempfile.gettempdir()) 599 option_parser.add_option( 600 '', '--browser_executable', 601 help='The exact browser executable to run.', 602 default=None) 603 option_parser.add_option( 604 '', '--browser_extra_args', 605 help='Additional arguments to pass to the browser.', 606 default=None) 607 option_parser.add_option( 608 '', '--chrome_src_path', 609 help='Path to the chromium src directory.', 610 default=None) 611 option_parser.add_option( 612 '', '--non-interactive', action='store_true', 613 help='Runs the script without any prompts. If this flag is specified and ' 614 '--skia_tools is specified then the debugger is not run.', 615 default=False) 616 option_parser.add_option( 617 '', '--skp_prefix', 618 help='Prefix to add to the names of generated SKPs.', 619 default=None) 620 options, unused_args = option_parser.parse_args() 621 622 playback = SkPicturePlayback(options) 623 sys.exit(playback.Run()) 624