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, such as
37credential files, are downloaded from. It also controls where the
38generated artifacts, such as recorded webpages and resulting skp renderings,
39are uploaded to. URLs with scheme 'gs://' use Google Storage. Otherwise
40use local filesystem.
41
42The --upload=True flag means generated artifacts will be
43uploaded or copied to the location specified by --data_store. (default value is
44False if not specified).
45
46The --non-interactive flag controls whether the script will prompt the user
47(default value is False if not specified).
48
49The --skia_tools flag if specified will allow this script to run
50debugger, render_pictures, and render_pdfs on the captured
51SKP(s). The tools are run after all SKPs are succesfully captured to make sure
52they can be added to the buildbots with no breakages.
53"""
54
55import glob
56import optparse
57import os
58import posixpath
59import shutil
60import subprocess
61import sys
62import tempfile
63import time
64import traceback
65
66sys.path.insert(0, os.getcwd())
67
68from common.py.utils import gs_utils
69from common.py.utils import shell_utils
70
71ROOT_PLAYBACK_DIR_NAME = 'playback'
72SKPICTURES_DIR_NAME = 'skps'
73
74PARTNERS_GS_BUCKET = 'gs://chrome-partner-telemetry'
75
76# Local archive and SKP directories.
77LOCAL_PLAYBACK_ROOT_DIR = os.path.join(
78    tempfile.gettempdir(), ROOT_PLAYBACK_DIR_NAME)
79LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR = os.path.join(
80    os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data')
81TMP_SKP_DIR = tempfile.mkdtemp()
82
83# Location of the credentials.json file and the string that represents missing
84# passwords.
85CREDENTIALS_FILE_PATH = os.path.join(
86    os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data',
87    'credentials.json'
88)
89
90# Name of the SKP benchmark
91SKP_BENCHMARK = 'skpicture_printer'
92
93# The max base name length of Skp files.
94MAX_SKP_BASE_NAME_LEN = 31
95
96# Dictionary of device to platform prefixes for SKP files.
97DEVICE_TO_PLATFORM_PREFIX = {
98    'desktop': 'desk',
99    'galaxynexus': 'mobi',
100    'nexus10': 'tabl'
101}
102
103# How many times the record_wpr binary should be retried.
104RETRY_RECORD_WPR_COUNT = 5
105# How many times the run_benchmark binary should be retried.
106RETRY_RUN_MEASUREMENT_COUNT = 5
107
108# Location of the credentials.json file in Google Storage.
109CREDENTIALS_GS_PATH = '/playback/credentials/credentials.json'
110
111X11_DISPLAY = os.getenv('DISPLAY', ':0')
112
113GS_PREDEFINED_ACL = gs_utils.GSUtils.PredefinedACL.PRIVATE
114GS_FINE_GRAINED_ACL_LIST = [
115  (gs_utils.GSUtils.IdType.GROUP_BY_DOMAIN, 'google.com',
116   gs_utils.GSUtils.Permission.READ),
117]
118
119# Path to Chromium's page sets.
120CHROMIUM_PAGE_SETS_PATH = os.path.join('tools', 'perf', 'page_sets')
121
122# Dictionary of supported Chromium page sets to their file prefixes.
123CHROMIUM_PAGE_SETS_TO_PREFIX = {
124    'key_mobile_sites_smooth.py': 'keymobi',
125    'top_25_smooth.py': 'top25desk',
126}
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_utils.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
167    self._local_skp_dir = os.path.join(
168        parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, SKPICTURES_DIR_NAME)
169    self._local_record_webpages_archive_dir = os.path.join(
170        parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, 'webpages_archive')
171
172    # List of SKP files generated by this script.
173    self._skp_files = []
174
175  def _ParsePageSets(self, page_sets):
176    if not page_sets:
177      raise ValueError('Must specify at least one page_set!')
178    elif self._all_page_sets_specified:
179      # Get everything from the page_sets directory.
180      page_sets_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
181                                   'page_sets')
182      ps = [os.path.join(page_sets_dir, page_set)
183            for page_set in os.listdir(page_sets_dir)
184            if not os.path.isdir(os.path.join(page_sets_dir, page_set)) and
185               page_set.endswith('.py')]
186      chromium_ps = [
187          os.path.join(self._chrome_page_sets_path, cr_page_set)
188          for cr_page_set in CHROMIUM_PAGE_SETS_TO_PREFIX]
189      ps.extend(chromium_ps)
190    elif '*' in page_sets:
191      # Explode and return the glob.
192      ps = glob.glob(page_sets)
193    else:
194      ps = page_sets.split(',')
195    ps.sort()
196    return ps
197
198  def _IsChromiumPageSet(self, page_set):
199    """Returns true if the specified page set is a Chromium page set."""
200    return page_set.startswith(self._chrome_page_sets_path)
201
202  def Run(self):
203    """Run the SkPicturePlayback BuildStep."""
204
205    # Download the credentials file if it was not previously downloaded.
206    if not os.path.isfile(CREDENTIALS_FILE_PATH):
207      # Download the credentials.json file from Google Storage.
208      self.gs.download_file(CREDENTIALS_GS_PATH, CREDENTIALS_FILE_PATH)
209
210    if not os.path.isfile(CREDENTIALS_FILE_PATH):
211      print """\n\nCould not locate credentials file in the storage.
212      Please create a %s file that contains:
213      {
214        "google": {
215          "username": "google_testing_account_username",
216          "password": "google_testing_account_password"
217        },
218        "facebook": {
219          "username": "facebook_testing_account_username",
220          "password": "facebook_testing_account_password"
221        }
222      }\n\n""" % CREDENTIALS_FILE_PATH
223      raw_input("Please press a key when you are ready to proceed...")
224
225    # Delete any left over data files in the data directory.
226    for archive_file in glob.glob(
227        os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 'skia_*')):
228      os.remove(archive_file)
229
230    # Delete the local root directory if it already exists.
231    if os.path.exists(LOCAL_PLAYBACK_ROOT_DIR):
232      shutil.rmtree(LOCAL_PLAYBACK_ROOT_DIR)
233
234    # Create the required local storage directories.
235    self._CreateLocalStorageDirs()
236
237    # Start the timer.
238    start_time = time.time()
239
240    # Loop through all page_sets.
241    for page_set in self._page_sets:
242
243      page_set_basename = os.path.basename(page_set).split('.')[0]
244      page_set_json_name = page_set_basename + '.json'
245      wpr_data_file = page_set.split(os.path.sep)[-1].split('.')[0] + '_000.wpr'
246      page_set_dir = os.path.dirname(page_set)
247
248      if self._IsChromiumPageSet(page_set):
249        print 'Using Chromium\'s captured archives for Chromium\'s page sets.'
250      elif self._record:
251        # Create an archive of the specified webpages if '--record=True' is
252        # specified.
253        record_wpr_cmd = (
254          'PYTHONPATH=%s:$PYTHONPATH' % page_set_dir,
255          'DISPLAY=%s' % X11_DISPLAY,
256          os.path.join(self._telemetry_binaries_dir, 'record_wpr'),
257          '--extra-browser-args="%s"' % self._browser_args,
258          '--browser=exact',
259          '--browser-executable=%s' % self._browser_executable,
260          '%s_page_set' % page_set_basename,
261          '--page-set-base-dir=%s' % page_set_dir
262        )
263        for _ in range(RETRY_RECORD_WPR_COUNT):
264          try:
265            shell_utils.run(' '.join(record_wpr_cmd), shell=True)
266
267            # Move over the created archive into the local webpages archive
268            # directory.
269            shutil.move(
270              os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file),
271              self._local_record_webpages_archive_dir)
272            shutil.move(
273              os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
274                           page_set_json_name),
275              self._local_record_webpages_archive_dir)
276
277            # Break out of the retry loop since there were no errors.
278            break
279          except Exception:
280            # There was a failure continue with the loop.
281            traceback.print_exc()
282        else:
283          # If we get here then record_wpr did not succeed and thus did not
284          # break out of the loop.
285          raise Exception('record_wpr failed for page_set: %s' % page_set)
286
287      else:
288        # Get the webpages archive so that it can be replayed.
289        self._DownloadWebpagesArchive(wpr_data_file, page_set_json_name)
290
291      run_benchmark_cmd = (
292          'PYTHONPATH=%s:$PYTHONPATH' % page_set_dir,
293          'DISPLAY=%s' % X11_DISPLAY,
294          'timeout', '300',
295          os.path.join(self._telemetry_binaries_dir, 'run_benchmark'),
296          '--extra-browser-args="%s"' % self._browser_args,
297          '--browser=exact',
298          '--browser-executable=%s' % self._browser_executable,
299          SKP_BENCHMARK,
300          '--page-set-name=%s' % page_set_basename,
301          '--page-set-base-dir=%s' % page_set_dir,
302          '--skp-outdir=%s' % TMP_SKP_DIR,
303          '--also-run-disabled-tests'
304      )
305
306      for _ in range(RETRY_RUN_MEASUREMENT_COUNT):
307        try:
308          print '\n\n=======Capturing SKP of %s=======\n\n' % page_set
309          shell_utils.run(' '.join(run_benchmark_cmd), shell=True)
310        except shell_utils.CommandFailedException:
311          # skpicture_printer sometimes fails with AssertionError but the
312          # captured SKP is still valid. This is a known issue.
313          pass
314
315        # Rename generated SKP files into more descriptive names.
316        try:
317          self._RenameSkpFiles(page_set)
318          # Break out of the retry loop since there were no errors.
319          break
320        except Exception:
321          # There was a failure continue with the loop.
322          traceback.print_exc()
323          print '\n\n=======Retrying %s=======\n\n' % page_set
324          time.sleep(10)
325      else:
326        # If we get here then run_benchmark did not succeed and thus did not
327        # break out of the loop.
328        raise Exception('run_benchmark failed for page_set: %s' % page_set)
329
330    print '\n\n=======Capturing SKP files took %s seconds=======\n\n' % (
331        time.time() - start_time)
332
333    if self._skia_tools:
334      render_pictures_cmd = [
335          os.path.join(self._skia_tools, 'render_pictures'),
336          '-r', self._local_skp_dir
337      ]
338      render_pdfs_cmd = [
339          os.path.join(self._skia_tools, 'render_pdfs'),
340          '-r', self._local_skp_dir
341      ]
342
343      for tools_cmd in (render_pictures_cmd, render_pdfs_cmd):
344        print '\n\n=======Running %s=======' % ' '.join(tools_cmd)
345        proc = subprocess.Popen(tools_cmd)
346        (code, _) = shell_utils.log_process_after_completion(proc, echo=False)
347        if code != 0:
348          raise Exception('%s failed!' % ' '.join(tools_cmd))
349
350      if not self._non_interactive:
351        print '\n\n=======Running debugger======='
352        os.system('%s %s' % (os.path.join(self._skia_tools, 'debugger'),
353                             self._local_skp_dir))
354
355    print '\n\n'
356
357    if self._upload:
358      print '\n\n=======Uploading to %s=======\n\n' % self.gs.target_type()
359      # Copy the directory structure in the root directory into Google Storage.
360      dest_dir_name = ROOT_PLAYBACK_DIR_NAME
361      if self._alternate_upload_dir:
362        dest_dir_name = self._alternate_upload_dir
363
364      self.gs.upload_dir_contents(
365          LOCAL_PLAYBACK_ROOT_DIR, dest_dir=dest_dir_name,
366          upload_if=gs_utils.GSUtils.UploadIf.IF_MODIFIED,
367          predefined_acl=GS_PREDEFINED_ACL,
368          fine_grained_acl_list=GS_FINE_GRAINED_ACL_LIST)
369
370      print '\n\n=======New SKPs have been uploaded to %s =======\n\n' % (
371          posixpath.join(self.gs.target_name(), dest_dir_name,
372                         SKPICTURES_DIR_NAME))
373
374      if self._upload_to_partner_bucket:
375        print '\n\n=======Uploading to Partner bucket %s =======\n\n' % (
376            PARTNERS_GS_BUCKET)
377        partner_gs = GoogleStorageDataStore(PARTNERS_GS_BUCKET)
378        partner_gs.delete_path(SKPICTURES_DIR_NAME)
379        partner_gs.upload_dir_contents(
380            os.path.join(LOCAL_PLAYBACK_ROOT_DIR, SKPICTURES_DIR_NAME),
381            dest_dir=SKPICTURES_DIR_NAME,
382            upload_if=gs_utils.GSUtils.UploadIf.IF_MODIFIED)
383        print '\n\n=======New SKPs have been uploaded to %s =======\n\n' % (
384            posixpath.join(partner_gs.target_name(), SKPICTURES_DIR_NAME))
385    else:
386      print '\n\n=======Not Uploading to %s=======\n\n' % self.gs.target_type()
387      print 'Generated resources are available in %s\n\n' % (
388          LOCAL_PLAYBACK_ROOT_DIR)
389
390    return 0
391
392  def _GetSkiaSkpFileName(self, page_set):
393    """Returns the SKP file name for Skia page sets."""
394    # /path/to/skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop.py
395    ps_filename = os.path.basename(page_set)
396    # skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop
397    ps_basename, _ = os.path.splitext(ps_filename)
398    # skia_yahooanswers_desktop -> skia, yahooanswers, desktop
399    _, page_name, device = ps_basename.split('_')
400    basename = '%s_%s' % (DEVICE_TO_PLATFORM_PREFIX[device], page_name)
401    return basename[:MAX_SKP_BASE_NAME_LEN] + '.skp'
402
403  def _GetChromiumSkpFileName(self, page_set, site):
404    """Returns the SKP file name for Chromium page sets."""
405    # /path/to/http___mobile_news_sandbox_pt0 -> http___mobile_news_sandbox_pt0
406    _, webpage = os.path.split(site)
407    # http___mobile_news_sandbox_pt0 -> mobile_news_sandbox_pt0
408    for prefix in ('http___', 'https___', 'www_'):
409      if webpage.startswith(prefix):
410        webpage = webpage[len(prefix):]
411    # /path/to/skia_yahooanswers_desktop.py -> skia_yahooanswers_desktop.py
412    ps_filename = os.path.basename(page_set)
413    # http___mobile_news_sandbox -> pagesetprefix_http___mobile_news_sandbox
414    basename = '%s_%s' % (CHROMIUM_PAGE_SETS_TO_PREFIX[ps_filename], webpage)
415    return basename[:MAX_SKP_BASE_NAME_LEN] + '.skp'
416
417  def _RenameSkpFiles(self, page_set):
418    """Rename generated SKP files into more descriptive names.
419
420    Look into the subdirectory of TMP_SKP_DIR and find the most interesting
421    .skp in there to be this page_set's representative .skp.
422    """
423    subdirs = glob.glob(os.path.join(TMP_SKP_DIR, '*'))
424    for site in subdirs:
425      if self._IsChromiumPageSet(page_set):
426        filename = self._GetChromiumSkpFileName(page_set, site)
427      else:
428        filename = self._GetSkiaSkpFileName(page_set)
429      filename = filename.lower()
430
431      if self._skp_prefix:
432        filename = '%s%s' % (self._skp_prefix, filename)
433
434      # We choose the largest .skp as the most likely to be interesting.
435      largest_skp = max(glob.glob(os.path.join(site, '*.skp')),
436                        key=lambda path: os.stat(path).st_size)
437      dest = os.path.join(self._local_skp_dir, filename)
438      print 'Moving', largest_skp, 'to', dest
439      shutil.move(largest_skp, dest)
440      self._skp_files.append(filename)
441      shutil.rmtree(site)
442
443  def _CreateLocalStorageDirs(self):
444    """Creates required local storage directories for this script."""
445    for d in (self._local_record_webpages_archive_dir,
446              self._local_skp_dir):
447      if os.path.exists(d):
448        shutil.rmtree(d)
449      os.makedirs(d)
450
451  def _DownloadWebpagesArchive(self, wpr_data_file, page_set_json_name):
452    """Downloads the webpages archive and its required page set from GS."""
453    wpr_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME, 'webpages_archive',
454                                wpr_data_file)
455    page_set_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME,
456                                     'webpages_archive',
457                                     page_set_json_name)
458    gs = self.gs
459    if (gs.does_storage_object_exist(wpr_source) and
460        gs.does_storage_object_exist(page_set_source)):
461      gs.download_file(wpr_source,
462                       os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
463                                    wpr_data_file))
464      gs.download_file(page_set_source,
465                       os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
466                                    page_set_json_name))
467    else:
468      raise Exception('%s and %s do not exist in %s!' % (gs.target_type(),
469        wpr_source, page_set_source))
470
471class DataStore:
472  """An abstract base class for uploading recordings to a data storage.
473  The interface emulates the google storage api."""
474  def target_name(self):
475    raise NotImplementedError()
476  def target_type(self):
477    raise NotImplementedError()
478  def does_storage_object_exist(self, *args):
479    raise NotImplementedError()
480  def download_file(self, *args):
481    raise NotImplementedError()
482  def upload_dir_contents(self, source_dir, **kwargs):
483    raise NotImplementedError()
484
485class GoogleStorageDataStore(DataStore):
486  def __init__(self, data_store_url):
487    self._data_store_url = data_store_url
488    self._bucket = remove_prefix(self._data_store_url.lstrip(),
489                                 gs_utils.GS_PREFIX)
490    self.gs = gs_utils.GSUtils()
491  def target_name(self):
492    return self._data_store_url
493  def target_type(self):
494    return 'Google Storage'
495  def does_storage_object_exist(self, *args):
496    return self.gs.does_storage_object_exist(self._bucket, *args)
497  def delete_path(self, path):
498    _, files = self.gs.list_bucket_contents(self._bucket, subdir=path)
499    for f in files:
500      self.gs.delete_file(self._bucket, posixpath.join(path, f))
501  def download_file(self, *args):
502    self.gs.download_file(self._bucket, *args)
503  def upload_dir_contents(self, source_dir, **kwargs):
504    self.gs.upload_dir_contents(source_dir, self._bucket, **kwargs)
505
506class LocalFileSystemDataStore(DataStore):
507  def __init__(self, data_store_location):
508    self._base_dir = data_store_location
509  def target_name(self):
510    return self._base_dir
511  def target_type(self):
512    return self._base_dir
513  def does_storage_object_exist(self, name, *args):
514    return os.path.isfile(os.path.join(self._base_dir, name))
515  def delete_path(self, path):
516    shutil.rmtree(path)
517  def download_file(self, name, local_path, *args):
518    shutil.copyfile(os.path.join(self._base_dir, name), local_path)
519  def upload_dir_contents(self, source_dir, dest_dir, **kwargs):
520    def copytree(source_dir, dest_dir):
521      if not os.path.exists(dest_dir):
522        os.makedirs(dest_dir)
523      for item in os.listdir(source_dir):
524        source = os.path.join(source_dir, item)
525        dest = os.path.join(dest_dir, item)
526        if os.path.isdir(source):
527          copytree(source, dest)
528        else:
529          shutil.copy2(source, dest)
530    copytree(source_dir, os.path.join(self._base_dir, dest_dir))
531
532if '__main__' == __name__:
533  option_parser = optparse.OptionParser()
534  option_parser.add_option(
535      '', '--page_sets',
536      help='Specifies the page sets to use to archive. Supports globs.',
537      default='all')
538  option_parser.add_option(
539      '', '--record', action='store_true',
540      help='Specifies whether a new website archive should be created.',
541      default=False)
542  option_parser.add_option(
543      '', '--skia_tools',
544      help=('Path to compiled Skia executable tools. '
545            'render_pictures/render_pdfs is run on the set '
546            'after all SKPs are captured. If the script is run without '
547            '--non-interactive then the debugger is also run at the end. Debug '
548            'builds are recommended because they seem to catch more failures '
549            'than Release builds.'),
550      default=None)
551  option_parser.add_option(
552      '', '--upload', action='store_true',
553      help=('Uploads to Google Storage or copies to local filesystem storage '
554            ' if this is True.'),
555      default=False)
556  option_parser.add_option(
557      '', '--upload_to_partner_bucket', action='store_true',
558      help=('Uploads SKPs to the chrome-partner-telemetry Google Storage '
559            'bucket if true.'),
560      default=False)
561  option_parser.add_option(
562      '', '--data_store',
563    help=('The location of the file storage to use to download and upload '
564          'files. Can be \'gs://<bucket>\' for Google Storage, or '
565          'a directory for local filesystem storage'),
566      default='gs://chromium-skia-gm')
567  option_parser.add_option(
568      '', '--alternate_upload_dir',
569      help= ('Uploads to a different directory in Google Storage or local '
570             'storage if this flag is specified'),
571      default=None)
572  option_parser.add_option(
573      '', '--output_dir',
574      help=('Temporary directory where SKPs and webpage archives will be '
575            'outputted to.'),
576      default=tempfile.gettempdir())
577  option_parser.add_option(
578      '', '--browser_executable',
579      help='The exact browser executable to run.',
580      default=None)
581  option_parser.add_option(
582      '', '--browser_extra_args',
583      help='Additional arguments to pass to the browser.',
584      default=None)
585  option_parser.add_option(
586      '', '--chrome_src_path',
587      help='Path to the chromium src directory.',
588      default=None)
589  option_parser.add_option(
590      '', '--non-interactive', action='store_true',
591      help='Runs the script without any prompts. If this flag is specified and '
592           '--skia_tools is specified then the debugger is not run.',
593      default=False)
594  option_parser.add_option(
595      '', '--skp_prefix',
596      help='Prefix to add to the names of generated SKPs.',
597      default=None)
598  options, unused_args = option_parser.parse_args()
599
600  playback = SkPicturePlayback(options)
601  sys.exit(playback.Run())
602