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