Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / third_party / skia / tools / skp / webpages_playback.py
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
8 To archive webpages and store SKP files (archives should be rarely updated):
9
10 cd ../buildbot/slave/skia_slave_scripts
11 python webpages_playback.py --dest_gsbase=gs://rmistry --record \
12 --page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \
13 --browser_executable=/tmp/chromium/out/Release/chrome
14
15
16 To replay archived webpages and re-generate SKP files (should be run whenever
17 SkPicture.PICTURE_VERSION changes):
18
19 cd ../buildbot/slave/skia_slave_scripts
20 python webpages_playback.py --dest_gsbase=gs://rmistry \
21 --page_sets=all --skia_tools=/home/default/trunk/out/Debug/ \
22 --browser_executable=/tmp/chromium/out/Release/chrome
23
24
25 Specify the --page_sets flag (default value is 'all') to pick a list of which
26 webpages should be archived and/or replayed. Eg:
27
28 --page_sets=page_sets/skia_yahooanswers_desktop.json,\
29 page_sets/skia_wikipedia_galaxynexus.json
30
31 The --browser_executable flag should point to the browser binary you want to use
32 to capture archives and/or capture SKP files. Majority of the time it should be
33 a newly built chrome binary.
34
35 The --upload_to_gs flag controls whether generated artifacts will be uploaded
36 to Google Storage (default value is False if not specified).
37
38 The --non-interactive flag controls whether the script will prompt the user
39 (default value is False if not specified).
40
41 The --skia_tools flag if specified will allow this script to run
42 debugger, render_pictures, and render_pdfs on the captured
43 SKP(s). The tools are run after all SKPs are succesfully captured to make sure
44 they can be added to the buildbots with no breakages.
45 To preview the captured SKP before proceeding to the next page_set specify both
46 --skia_tools and --view_debugger_output.
47 """
48
49 import glob
50 import optparse
51 import os
52 import posixpath
53 import shutil
54 import subprocess
55 import sys
56 import tempfile
57 import time
58 import traceback
59
60 sys.path.insert(0, os.getcwd())
61
62 from common.py.utils import gs_utils
63 from common.py.utils import shell_utils
64
65 ROOT_PLAYBACK_DIR_NAME = 'playback'
66 SKPICTURES_DIR_NAME = 'skps'
67
68
69 # Local archive and SKP directories.
70 LOCAL_PLAYBACK_ROOT_DIR = os.path.join(
71     tempfile.gettempdir(), ROOT_PLAYBACK_DIR_NAME)
72 LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR = os.path.join(
73     os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data')
74 TMP_SKP_DIR = tempfile.mkdtemp()
75
76 # Location of the credentials.json file and the string that represents missing
77 # passwords.
78 CREDENTIALS_FILE_PATH = os.path.join(
79     os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data',
80     'credentials.json'
81 )
82
83 # Name of the SKP benchmark
84 SKP_BENCHMARK = 'skpicture_printer'
85
86 # The max base name length of Skp files.
87 MAX_SKP_BASE_NAME_LEN = 31
88
89 # Dictionary of device to platform prefixes for SKP files.
90 DEVICE_TO_PLATFORM_PREFIX = {
91     'desktop': 'desk',
92     'galaxynexus': 'mobi',
93     'nexus10': 'tabl'
94 }
95
96 # How many times the record_wpr binary should be retried.
97 RETRY_RECORD_WPR_COUNT = 5
98 # How many times the run_benchmark binary should be retried.
99 RETRY_RUN_MEASUREMENT_COUNT = 5
100
101 # Location of the credentials.json file in Google Storage.
102 CREDENTIALS_GS_PATH = '/playback/credentials/credentials.json'
103
104 X11_DISPLAY = os.getenv('DISPLAY', ':0')
105
106 GS_PREDEFINED_ACL = gs_utils.GSUtils.PredefinedACL.PRIVATE
107 GS_FINE_GRAINED_ACL_LIST = [
108   (gs_utils.GSUtils.IdType.GROUP_BY_DOMAIN, 'google.com',
109    gs_utils.GSUtils.Permission.READ),
110 ]
111
112
113 class SkPicturePlayback(object):
114   """Class that archives or replays webpages and creates SKPs."""
115
116   def __init__(self, parse_options):
117     """Constructs a SkPicturePlayback BuildStep instance."""
118     assert parse_options.browser_executable, 'Must specify --browser_executable'
119     self._browser_executable = parse_options.browser_executable
120
121     self._all_page_sets_specified = parse_options.page_sets == 'all'
122     self._page_sets = self._ParsePageSets(parse_options.page_sets)
123
124     self._dest_gsbase = parse_options.dest_gsbase
125     self._record = parse_options.record
126     self._skia_tools = parse_options.skia_tools
127     self._non_interactive = parse_options.non_interactive
128     self._upload_to_gs = parse_options.upload_to_gs
129     self._alternate_upload_dir = parse_options.alternate_upload_dir
130     self._skip_all_gs_access = parse_options.skip_all_gs_access
131     self._telemetry_binaries_dir = os.path.join(parse_options.chrome_src_path,
132                                                 'tools', 'perf')
133
134     self._local_skp_dir = os.path.join(
135         parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, SKPICTURES_DIR_NAME)
136     self._local_record_webpages_archive_dir = os.path.join(
137         parse_options.output_dir, ROOT_PLAYBACK_DIR_NAME, 'webpages_archive')
138
139     # List of SKP files generated by this script.
140     self._skp_files = []
141
142   def _ParsePageSets(self, page_sets):
143     if not page_sets:
144       raise ValueError('Must specify at least one page_set!')
145     elif self._all_page_sets_specified:
146       # Get everything from the page_sets directory.
147       page_sets_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
148                                    'page_sets')
149       ps = [os.path.join(page_sets_dir, page_set)
150             for page_set in os.listdir(page_sets_dir)
151             if not os.path.isdir(os.path.join(page_sets_dir, page_set)) and
152                page_set.endswith('.py')]
153     elif '*' in page_sets:
154       # Explode and return the glob.
155       ps = glob.glob(page_sets)
156     else:
157       ps = page_sets.split(',')
158     ps.sort()
159     return ps
160
161   def Run(self):
162     """Run the SkPicturePlayback BuildStep."""
163
164     # Download the credentials file if it was not previously downloaded.
165     if self._skip_all_gs_access:
166       print """\n\nPlease create a %s file that contains:
167       {
168         "google": {
169           "username": "google_testing_account_username",
170           "password": "google_testing_account_password"
171         },
172         "facebook": {
173           "username": "facebook_testing_account_username",
174           "password": "facebook_testing_account_password"
175         }
176       }\n\n""" % CREDENTIALS_FILE_PATH
177       raw_input("Please press a key when you are ready to proceed...")
178     elif not os.path.isfile(CREDENTIALS_FILE_PATH):
179       # Download the credentials.json file from Google Storage.
180       gs_bucket = self._dest_gsbase.lstrip(gs_utils.GS_PREFIX)
181       gs_utils.GSUtils().download_file(gs_bucket, CREDENTIALS_GS_PATH,
182                                        CREDENTIALS_FILE_PATH)
183
184     # Delete any left over data files in the data directory.
185     for archive_file in glob.glob(
186         os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 'skia_*')):
187       os.remove(archive_file)
188
189     # Delete the local root directory if it already exists.
190     if os.path.exists(LOCAL_PLAYBACK_ROOT_DIR):
191       shutil.rmtree(LOCAL_PLAYBACK_ROOT_DIR)
192
193     # Create the required local storage directories.
194     self._CreateLocalStorageDirs()
195
196     # Start the timer.
197     start_time = time.time()
198
199     # Loop through all page_sets.
200     for page_set in self._page_sets:
201
202       page_set_basename = os.path.basename(page_set).split('.')[0]
203       page_set_json_name = page_set_basename + '.json'
204       wpr_data_file = page_set.split(os.path.sep)[-1].split('.')[0] + '_000.wpr'
205       page_set_dir = os.path.dirname(page_set)
206
207       if self._record:
208         # Create an archive of the specified webpages if '--record=True' is
209         # specified.
210         record_wpr_cmd = (
211           'PYTHONPATH=%s:$PYTHONPATH' % page_set_dir,
212           'DISPLAY=%s' % X11_DISPLAY,
213           os.path.join(self._telemetry_binaries_dir, 'record_wpr'),
214           '--extra-browser-args=--disable-setuid-sandbox',
215           '--browser=exact',
216           '--browser-executable=%s' % self._browser_executable,
217           '%s_page_set' % page_set_basename,
218           '--page-set-base-dir=%s' % page_set_dir
219         )
220         for _ in range(RETRY_RECORD_WPR_COUNT):
221           try:
222             shell_utils.run(' '.join(record_wpr_cmd), shell=True)
223             # Break out of the retry loop since there were no errors.
224             break
225           except Exception:
226             # There was a failure continue with the loop.
227             traceback.print_exc()
228         else:
229           # If we get here then record_wpr did not succeed and thus did not
230           # break out of the loop.
231           raise Exception('record_wpr failed for page_set: %s' % page_set)
232
233       else:
234         if not self._skip_all_gs_access:
235           # Get the webpages archive so that it can be replayed.
236           self._DownloadWebpagesArchive(wpr_data_file, page_set_json_name)
237
238       run_benchmark_cmd = (
239           'PYTHONPATH=%s:$PYTHONPATH' % page_set_dir,
240           'DISPLAY=%s' % X11_DISPLAY,
241           'timeout', '300',
242           os.path.join(self._telemetry_binaries_dir, 'run_benchmark'),
243           '--extra-browser-args=--disable-setuid-sandbox',
244           '--browser=exact',
245           '--browser-executable=%s' % self._browser_executable,
246           SKP_BENCHMARK,
247           '--page-set-name=%s' % page_set_basename,
248           '--page-set-base-dir=%s' % page_set_dir,
249           '--skp-outdir=%s' % TMP_SKP_DIR,
250           '--also-run-disabled-tests'
251       )
252
253       for _ in range(RETRY_RUN_MEASUREMENT_COUNT):
254         try:
255           print '\n\n=======Capturing SKP of %s=======\n\n' % page_set
256           shell_utils.run(' '.join(run_benchmark_cmd), shell=True)
257         except shell_utils.CommandFailedException:
258           # skpicture_printer sometimes fails with AssertionError but the
259           # captured SKP is still valid. This is a known issue.
260           pass
261
262         if self._record:
263           # Move over the created archive into the local webpages archive
264           # directory.
265           shutil.move(
266               os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file),
267               self._local_record_webpages_archive_dir)
268           shutil.move(
269               os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
270                            page_set_json_name),
271               self._local_record_webpages_archive_dir)
272
273         # Rename generated SKP files into more descriptive names.
274         try:
275           self._RenameSkpFiles(page_set)
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           print '\n\n=======Retrying %s=======\n\n' % page_set
282           time.sleep(10)
283       else:
284         # If we get here then run_benchmark did not succeed and thus did not
285         # break out of the loop.
286         raise Exception('run_benchmark failed for page_set: %s' % page_set)
287
288     print '\n\n=======Capturing SKP files took %s seconds=======\n\n' % (
289         time.time() - start_time)
290
291     if self._skia_tools:
292       render_pictures_cmd = [
293           os.path.join(self._skia_tools, 'render_pictures'),
294           '-r', self._local_skp_dir
295       ]
296       render_pdfs_cmd = [
297           os.path.join(self._skia_tools, 'render_pdfs'),
298           self._local_skp_dir
299       ]
300
301       for tools_cmd in (render_pictures_cmd, render_pdfs_cmd):
302         print '\n\n=======Running %s=======' % ' '.join(tools_cmd)
303         proc = subprocess.Popen(tools_cmd)
304         (code, _) = shell_utils.log_process_after_completion(proc, echo=False)
305         if code != 0:
306           raise Exception('%s failed!' % ' '.join(tools_cmd))
307
308       if not self._non_interactive:
309         print '\n\n=======Running debugger======='
310         os.system('%s %s' % (os.path.join(self._skia_tools, 'debugger'),
311                              os.path.join(self._local_skp_dir, '*')))
312
313     print '\n\n'
314
315     if not self._skip_all_gs_access and self._upload_to_gs:
316       print '\n\n=======Uploading to Google Storage=======\n\n'
317       # Copy the directory structure in the root directory into Google Storage.
318       dest_dir_name = ROOT_PLAYBACK_DIR_NAME
319       if self._alternate_upload_dir:
320         dest_dir_name = self._alternate_upload_dir
321
322       gs_bucket = self._dest_gsbase.lstrip(gs_utils.GS_PREFIX)
323       gs_utils.GSUtils().upload_dir_contents(
324           LOCAL_PLAYBACK_ROOT_DIR, gs_bucket, dest_dir_name,
325           upload_if=gs_utils.GSUtils.UploadIf.IF_MODIFIED,
326           predefined_acl=GS_PREDEFINED_ACL,
327           fine_grained_acl_list=GS_FINE_GRAINED_ACL_LIST)
328
329       print '\n\n=======New SKPs have been uploaded to %s =======\n\n' % (
330           posixpath.join(self._dest_gsbase, dest_dir_name, SKPICTURES_DIR_NAME))
331     else:
332       print '\n\n=======Not Uploading to Google Storage=======\n\n'
333       print 'Generated resources are available in %s\n\n' % (
334           LOCAL_PLAYBACK_ROOT_DIR)
335
336     return 0
337
338   def _RenameSkpFiles(self, page_set):
339     """Rename generated SKP files into more descriptive names.
340
341     Look into the subdirectory of TMP_SKP_DIR and find the most interesting
342     .skp in there to be this page_set's representative .skp.
343     """
344     # Here's where we're assuming there's one page per pageset.
345     # If there were more than one, we'd overwrite filename below.
346
347     # /path/to/skia_yahooanswers_desktop.json -> skia_yahooanswers_desktop.json
348     _, ps_filename = os.path.split(page_set)
349     # skia_yahooanswers_desktop.json -> skia_yahooanswers_desktop
350     ps_basename, _ = os.path.splitext(ps_filename)
351     # skia_yahooanswers_desktop -> skia, yahooanswers, desktop
352     _, page_name, device = ps_basename.split('_')
353
354     basename = '%s_%s' % (DEVICE_TO_PLATFORM_PREFIX[device], page_name)
355     filename = basename[:MAX_SKP_BASE_NAME_LEN] + '.skp'
356
357     subdirs = glob.glob(os.path.join(TMP_SKP_DIR, '*'))
358     assert len(subdirs) == 1
359     for site in subdirs:
360       # We choose the largest .skp as the most likely to be interesting.
361       largest_skp = max(glob.glob(os.path.join(site, '*.skp')),
362                         key=lambda path: os.stat(path).st_size)
363       dest = os.path.join(self._local_skp_dir, filename)
364       print 'Moving', largest_skp, 'to', dest
365       shutil.move(largest_skp, dest)
366       self._skp_files.append(filename)
367       shutil.rmtree(site)
368
369   def _CreateLocalStorageDirs(self):
370     """Creates required local storage directories for this script."""
371     for d in (self._local_record_webpages_archive_dir,
372               self._local_skp_dir):
373       if os.path.exists(d):
374         shutil.rmtree(d)
375       os.makedirs(d)
376
377   def _DownloadWebpagesArchive(self, wpr_data_file, page_set_json_name):
378     """Downloads the webpages archive and its required page set from GS."""
379     wpr_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME, 'webpages_archive',
380                                 wpr_data_file)
381     page_set_source = posixpath.join(ROOT_PLAYBACK_DIR_NAME,
382                                      'webpages_archive',
383                                      page_set_json_name)
384     gs = gs_utils.GSUtils()
385     gs_bucket = self._dest_gsbase.lstrip(gs_utils.GS_PREFIX)
386     if (gs.does_storage_object_exist(gs_bucket, wpr_source) and
387         gs.does_storage_object_exist(gs_bucket, page_set_source)):
388       gs.download_file(gs_bucket, wpr_source,
389                        os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
390                                     wpr_data_file))
391       gs.download_file(gs_bucket, page_set_source,
392                        os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
393                                     page_set_json_name))
394     else:
395       raise Exception('%s and %s do not exist in Google Storage!' % (
396           wpr_source, page_set_source))
397
398
399 if '__main__' == __name__:
400   option_parser = optparse.OptionParser()
401   option_parser.add_option(
402       '', '--page_sets',
403       help='Specifies the page sets to use to archive. Supports globs.',
404       default='all')
405   option_parser.add_option(
406       '', '--skip_all_gs_access', action='store_true',
407       help='All Google Storage interactions will be skipped if this flag is '
408            'specified. This is useful for cases where the user does not have '
409            'the required .boto file but would like to generate webpage '
410            'archives and SKPs from the Skia page sets.',
411       default=False)
412   option_parser.add_option(
413       '', '--record', action='store_true',
414       help='Specifies whether a new website archive should be created.',
415       default=False)
416   option_parser.add_option(
417       '', '--dest_gsbase',
418       help='gs:// bucket_name, the bucket to upload the file to.',
419       default='gs://chromium-skia-gm')
420   option_parser.add_option(
421       '', '--skia_tools',
422       help=('Path to compiled Skia executable tools. '
423             'render_pictures/render_pdfs is run on the set '
424             'after all SKPs are captured. If the script is run without '
425             '--non-interactive then the debugger is also run at the end. Debug '
426             'builds are recommended because they seem to catch more failures '
427             'than Release builds.'),
428       default=None)
429   option_parser.add_option(
430       '', '--upload_to_gs', action='store_true',
431       help='Does not upload to Google Storage if this is False.',
432       default=False)
433   option_parser.add_option(
434       '', '--alternate_upload_dir',
435       help='Uploads to a different directory in Google Storage if this flag is '
436            'specified',
437       default=None)
438   option_parser.add_option(
439       '', '--output_dir',
440       help='Directory where SKPs and webpage archives will be outputted to.',
441       default=tempfile.gettempdir())
442   option_parser.add_option(
443       '', '--browser_executable',
444       help='The exact browser executable to run.',
445       default=None)
446   option_parser.add_option(
447       '', '--chrome_src_path',
448       help='Path to the chromium src directory.',
449       default=None)
450   option_parser.add_option(
451       '', '--non-interactive', action='store_true',
452       help='Runs the script without any prompts. If this flag is specified and '
453            '--skia_tools is specified then the debugger is not run.',
454       default=False)
455   options, unused_args = option_parser.parse_args()
456
457   playback = SkPicturePlayback(options)
458   sys.exit(playback.Run())