6"""Archives or replays webpages and creates SKPs in a Google Storage location.
8To archive webpages and store SKP files (archives should be rarely updated):
11python tools/skp/webpages_playback.py --data_store=gs://your-bucket-name \
12--record --page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \
13--browser_executable=/tmp/chromium/out/Release/chrome
15The above command uses Google Storage bucket 'your-bucket-name' to download
18To replay archived webpages and re-generate SKP files (should be run whenever
19SkPicture.PICTURE_VERSION changes):
22python tools/skp/webpages_playback.py --data_store=gs://your-bucket-name \
23--page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \
24--browser_executable=/tmp/chromium/out/Release/chrome
27Specify the --page_sets flag (default value is 'all') to pick a list of which
28webpages should be archived and/or replayed. Eg:
30--page_sets=tools/skp/page_sets/skia_yahooanswers_desktop.py,\
31tools/skp/page_sets/skia_googlecalendar_nexus10.py
33The --browser_executable flag should point to the browser binary you want to use
34to capture archives and/or capture SKP files. Majority of the time it should be
35a newly built chrome binary.
37The --data_store flag controls where the needed artifacts are downloaded from.
38It also controls where the generated artifacts, such as recorded webpages and
39resulting skp renderings, are uploaded to. URLs with scheme 'gs://' use Google
40Storage. Otherwise use local filesystem.
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).
46The --non-interactive flag controls whether the script will prompt the user
47(default value is False if not specified).
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.
56from __future__
import print_function
70ROOT_PLAYBACK_DIR_NAME =
'playback'
71SKPICTURES_DIR_NAME =
'skps'
75PARTNERS_GS_BUCKET =
'gs://chrome-partner-telemetry'
78LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR = os.path.join(
79 os.path.abspath(os.path.dirname(__file__)),
'page_sets',
'data')
80TMP_SKP_DIR = tempfile.mkdtemp()
84CREDENTIALS_FILE_PATH = os.path.join(
85 os.path.abspath(os.path.dirname(__file__)),
'page_sets',
'data',
90SKP_BENCHMARK =
'skpicture_printer'
93MAX_SKP_BASE_NAME_LEN = 31
96DEVICE_TO_PLATFORM_PREFIX = {
103RETRY_RECORD_WPR_COUNT = 5
105RETRY_RUN_MEASUREMENT_COUNT = 3
108CREDENTIALS_GS_PATH =
'playback/credentials/credentials.json'
110X11_DISPLAY = os.getenv(
'DISPLAY',
':0')
113CHROMIUM_PAGE_SETS_PATH = os.path.join(
'tools',
'perf',
'page_sets')
116CHROMIUM_PAGE_SETS_TO_PREFIX = {
119PAGE_SETS_TO_EXCLUSIONS = {
121 'key_mobile_sites_smooth.py':
'"(digg|worldjournal|twitter|espn)"',
123 'top_25_smooth.py':
'"(mail\.google\.com)"',
128 """Raised when the created SKP is invalid."""
133 if s.startswith(prefix):
134 return s[
len(prefix):]
139 """Class that archives or replays webpages and creates SKPs."""
142 """Constructs a SkPicturePlayback BuildStep instance."""
143 assert parse_options.browser_executable,
'Must specify --browser_executable'
146 if parse_options.browser_extra_args:
151 CHROMIUM_PAGE_SETS_PATH)
155 self.
_record = parse_options.record
158 self.
_upload = parse_options.upload
160 data_store_location = parse_options.data_store
161 if data_store_location.startswith(GS_PREFIX):
169 self.
_catapult_dir = os.path.join(parse_options.chrome_src_path,
170 'third_party',
'catapult')
173 parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, SKPICTURES_DIR_NAME)
175 parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME,
'webpages_archive')
180 def _ParsePageSets(self, page_sets):
182 raise ValueError(
'Must specify at least one page_set!')
185 page_sets_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
187 ps = [os.path.join(page_sets_dir, page_set)
188 for page_set
in os.listdir(page_sets_dir)
189 if not os.path.isdir(os.path.join(page_sets_dir, page_set))
and
190 page_set.endswith(
'.py')]
193 for cr_page_set
in CHROMIUM_PAGE_SETS_TO_PREFIX]
194 ps.extend(chromium_ps)
195 elif '*' in page_sets:
197 ps = glob.glob(page_sets)
199 ps = page_sets.split(
',')
203 def _IsChromiumPageSet(self, page_set):
204 """Returns true if the specified page set is a Chromium page set."""
208 """Run the SkPicturePlayback BuildStep."""
211 if not os.path.isfile(CREDENTIALS_FILE_PATH):
213 self.
gs.download_file(CREDENTIALS_GS_PATH, CREDENTIALS_FILE_PATH)
215 if not os.path.isfile(CREDENTIALS_FILE_PATH):
216 raise Exception(
"""Could not locate credentials file in the storage.
217 Please create a credentials file in gs://%s that contains:
220 "username":
"google_testing_account_username",
221 "password":
"google_testing_account_password"
223 }\n\n
""" % CREDENTIALS_GS_PATH)
226 for archive_file
in glob.glob(
227 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
'skia_*')):
228 os.remove(archive_file)
234 start_time = time.time()
238 if os.path.basename(page_set) ==
'__init__.py':
240 page_set_basename = os.path.basename(page_set).split(
'.')[0]
241 page_set_json_name = page_set_basename +
'.json'
242 wpr_data_file_glob = (
243 page_set.split(os.path.sep)[-1].split(
'.')[0] +
'_*.wprgo')
244 page_set_dir = os.path.dirname(page_set)
247 print(
'Using Chromium\'s captured archives for Chromium\'s page sets.')
252 'PYTHONPATH=%s:%s:$PYTHONPATH' % (page_set_dir, self.
_catapult_dir),
253 'DISPLAY=%s' % X11_DISPLAY,
258 '%s_page_set' % page_set_basename,
259 '--page-set-base-dir=%s' % page_set_dir
261 for _
in range(RETRY_RECORD_WPR_COUNT):
263 subprocess.check_call(
' '.
join(record_wpr_cmd), shell=
True)
267 for wpr_data_file
in glob.glob(os.path.join(
268 LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file_glob)):
270 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file),
273 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
281 traceback.print_exc()
285 raise Exception(
'record_wpr failed for page_set: %s' % page_set)
291 run_benchmark_cmd = [
292 'PYTHONPATH=%s:%s:$PYTHONPATH' % (page_set_dir, self.
_catapult_dir),
293 'DISPLAY=%s' % X11_DISPLAY,
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',
306 exclusions = PAGE_SETS_TO_EXCLUSIONS.get(os.path.basename(page_set))
308 run_benchmark_cmd.append(
'--story-filter-exclude=' + exclusions)
310 for _
in range(RETRY_RUN_MEASUREMENT_COUNT):
312 print(
'\n\n=======Capturing SKP of %s=======\n\n' % page_set)
313 subprocess.check_call(
' '.
join(run_benchmark_cmd), shell=
True)
314 except subprocess.CalledProcessError:
316 traceback.print_exc()
317 print(
'\n\n=======Retrying %s=======\n\n' % page_set)
324 except InvalidSKPException:
326 traceback.print_exc()
327 print(
'\n\n=======Retrying %s=======\n\n' % page_set)
336 raise Exception(
'run_benchmark failed for page_set: %s' % page_set)
338 print(
'\n\n=======Capturing SKP files took %s seconds=======\n\n' % (
339 time.time() - start_time))
342 render_pictures_cmd = [
351 for tools_cmd
in (render_pictures_cmd, render_pdfs_cmd):
352 print(
'\n\n=======Running %s=======' %
' '.
join(tools_cmd))
353 subprocess.check_call(tools_cmd)
356 print(
'\n\n=======Running debugger=======')
357 os.system(
'%s %s' % (os.path.join(self.
_skia_tools,
'debugger'),
363 print(
'\n\n=======Uploading to %s=======\n\n' % self.
gs.target_type())
365 dest_dir_name = ROOT_PLAYBACK_DIR_NAME
369 self.
gs.upload_dir_contents(
372 print(
'\n\n=======New SKPs have been uploaded to %s =======\n\n' %
373 posixpath.join(self.
gs.target_name(), dest_dir_name,
374 SKPICTURES_DIR_NAME))
377 print(
'\n\n=======Not Uploading to %s=======\n\n' % self.
gs.target_type())
381 print(
'\n\n=======Uploading to Partner bucket %s =======\n\n' %
384 timestamp = datetime.datetime.utcnow().strftime(
'%Y-%m-%d')
385 upload_dir = posixpath.join(SKPICTURES_DIR_NAME, timestamp)
387 partner_gs.delete_path(upload_dir)
388 except subprocess.CalledProcessError:
389 print(
'Cannot delete %s because it does not exist yet.' % upload_dir)
392 print(
'\n\n=======New SKPs have been uploaded to %s =======\n\n' %
393 posixpath.join(partner_gs.target_name(), upload_dir))
397 def _GetSkiaSkpFileName(self, page_set):
398 """Returns the SKP file name for Skia page sets."""
400 ps_filename = os.path.basename(page_set)
402 ps_basename, _ = os.path.splitext(ps_filename)
404 _, page_name, device = ps_basename.split(
'_')
405 basename =
'%s_%s' % (DEVICE_TO_PLATFORM_PREFIX[device], page_name)
406 return basename[:MAX_SKP_BASE_NAME_LEN] +
'.skp'
408 def _GetChromiumSkpFileName(self, page_set, site):
409 """Returns the SKP file name for Chromium page sets."""
411 _, webpage = os.path.split(site)
413 for prefix
in (
'http___',
'https___',
'www_'):
414 if webpage.startswith(prefix):
415 webpage = webpage[
len(prefix):]
417 ps_filename = os.path.basename(page_set)
419 basename =
'%s_%s' % (CHROMIUM_PAGE_SETS_TO_PREFIX[ps_filename], webpage)
420 return basename[:MAX_SKP_BASE_NAME_LEN] +
'.skp'
422 def _RenameSkpFiles(self, page_set):
423 """Rename generated SKP files into more descriptive names.
425 Look into the subdirectory of TMP_SKP_DIR and find the most interesting
426 .skp
in there to be this page_set
's representative .skp.
428 Throws InvalidSKPException if the chosen .skp
is less than 1KB. This
429 typically happens when there
is a 404
or a redirect loop. Anything greater
430 than 1KB seems to have captured at least some useful information.
432 subdirs = glob.glob(os.path.join(TMP_SKP_DIR, '*'))
438 filename = filename.lower()
444 largest_skp =
max(glob.glob(os.path.join(site,
'*.skp')),
445 key=
lambda path: os.stat(path).st_size)
447 print(
'Moving', largest_skp,
'to', dest)
448 shutil.move(largest_skp, dest)
451 skp_size = os.path.getsize(dest)
454 'Size of %s is only %d. Something is wrong.' % (dest, skp_size))
457 def _CreateLocalStorageDirs(self):
458 """Creates required local storage directories for this script."""
461 if os.path.exists(d):
465 def _DownloadWebpagesArchive(self, wpr_data_file, page_set_json_name):
466 """Downloads the webpages archive and its required page set from GS."""
467 wpr_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME,
'webpages_archive',
469 page_set_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME,
473 if (gs.does_storage_object_exist(wpr_source)
and
474 gs.does_storage_object_exist(page_set_source)):
475 gs.download_file(wpr_source, LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR)
476 gs.download_file(page_set_source,
477 os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
480 raise Exception(
'%s and %s do not exist in %s!' % (gs.target_type(),
481 wpr_source, page_set_source))
484 """An abstract base class for uploading recordings to a data storage.
485 The interface emulates the google storage api."""
487 raise NotImplementedError()
489 raise NotImplementedError()
491 raise NotImplementedError()
493 raise NotImplementedError()
495 raise NotImplementedError()
500 self.
_url = data_store_url.rstrip(
'/')
506 return 'Google Storage'
510 output = subprocess.check_output([
511 'gsutil',
'ls',
'/'.
join((self.
_url, name))])
512 except subprocess.CalledProcessError:
514 if len(output.splitlines()) != 1:
519 subprocess.check_call([
'gsutil',
'rm',
'-r',
'/'.
join((self.
_url, path))])
522 subprocess.check_call([
523 'gsutil',
'cp',
'/'.
join((self.
_url, name)), local_path])
526 subprocess.check_call([
527 'gsutil',
'cp',
'-r', source_dir,
'/'.
join((self.
_url, dest_dir))])
538 return os.path.isfile(os.path.join(self.
_base_dir, name))
542 shutil.copyfile(os.path.join(self.
_base_dir, name), local_path)
545 if not os.path.exists(dest_dir):
546 os.makedirs(dest_dir)
547 for item
in os.listdir(source_dir):
548 source = os.path.join(source_dir, item)
549 dest = os.path.join(dest_dir, item)
550 if os.path.isdir(source):
553 shutil.copy2(source, dest)
556if '__main__' == __name__:
557 option_parser = optparse.OptionParser()
558 option_parser.add_option(
560 help=
'Specifies the page sets to use to archive. Supports globs.',
562 option_parser.add_option(
563 '',
'--record', action=
'store_true',
564 help=
'Specifies whether a new website archive should be created.',
566 option_parser.add_option(
568 help=(
'Path to compiled Skia executable tools. '
569 'render_pictures/render_pdfs is run on the set '
570 'after all SKPs are captured. If the script is run without '
571 '--non-interactive then the debugger is also run at the end. Debug '
572 'builds are recommended because they seem to catch more failures '
573 'than Release builds.'),
575 option_parser.add_option(
576 '',
'--upload', action=
'store_true',
577 help=(
'Uploads to Google Storage or copies to local filesystem storage '
578 ' if this is True.'),
580 option_parser.add_option(
581 '',
'--upload_to_partner_bucket', action=
'store_true',
582 help=(
'Uploads SKPs to the chrome-partner-telemetry Google Storage '
585 option_parser.add_option(
587 help=(
'The location of the file storage to use to download and upload '
588 'files. Can be \'gs://<bucket>\' for Google Storage, or '
589 'a directory for local filesystem storage'),
590 default=
'gs://skia-skps')
591 option_parser.add_option(
592 '',
'--alternate_upload_dir',
593 help= (
'Uploads to a different directory in Google Storage or local '
594 'storage if this flag is specified'),
596 option_parser.add_option(
598 help=(
'Temporary directory where SKPs and webpage archives will be '
600 default=tempfile.gettempdir())
601 option_parser.add_option(
602 '',
'--browser_executable',
603 help=
'The exact browser executable to run.',
605 option_parser.add_option(
606 '',
'--browser_extra_args',
607 help=
'Additional arguments to pass to the browser.',
609 option_parser.add_option(
610 '',
'--chrome_src_path',
611 help=
'Path to the chromium src directory.',
613 option_parser.add_option(
614 '',
'--non-interactive', action=
'store_true',
615 help=
'Runs the script without any prompts. If this flag is specified and '
616 '--skia_tools is specified then the debugger is not run.',
618 option_parser.add_option(
620 help=
'Prefix to add to the names of generated SKPs.',
622 options, unused_args = option_parser.parse_args()
625 sys.exit(playback.Run())
def upload_dir_contents(self, source_dir, dest_dir)
def does_storage_object_exist(self, name)
def download_file(self, name, local_path)
def upload_dir_contents(self, source_dir, dest_dir)
def __init__(self, data_store_url)
def download_file(self, name, local_path)
def delete_path(self, path)
def does_storage_object_exist(self, name)
def upload_dir_contents(self, source_dir, dest_dir)
def delete_path(self, path)
def __init__(self, data_store_location)
def does_storage_object_exist(self, name)
def download_file(self, name, local_path)
_local_record_webpages_archive_dir
def __init__(self, parse_options)
def _IsChromiumPageSet(self, page_set)
_upload_to_partner_bucket
def _RenameSkpFiles(self, page_set)
def _GetSkiaSkpFileName(self, page_set)
def _CreateLocalStorageDirs(self)
def _ParsePageSets(self, page_sets)
def _GetChromiumSkpFileName(self, page_set, site)
def _DownloadWebpagesArchive(self, wpr_data_file, page_set_json_name)
static void append(char **dst, size_t *count, const char *src, size_t n)
static float max(float r, float g, float b)
def print(*args, **kwargs)
def remove_prefix(s, prefix)
static SkString join(const CommandLineFlags::StringArray &)