| #!/usr/bin/env python |
| # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Module that combines JSON summaries and outputs the summaries in HTML.""" |
| |
| import glob |
| import json |
| import optparse |
| import os |
| import posixpath |
| import sys |
| |
| sys.path.append( |
| os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) |
| import json_summary_constants |
| |
| # Add the django settings file to DJANGO_SETTINGS_MODULE. |
| import django |
| os.environ['DJANGO_SETTINGS_MODULE'] = 'csv-django-settings' |
| django.setup() |
| from django.template import loader |
| |
| STORAGE_HTTP_BASE = 'http://storage.cloud.google.com' |
| |
| |
| # Template variables used in the django templates defined in django-settings. |
| # If the values of these constants change then the django templates need to |
| # change as well. |
| SLAVE_NAME_TO_INFO_ITEMS_TEMPLATE_VAR = 'slave_name_to_info_items' |
| ABSOLUTE_URL_TEMPLATE_VAR = 'absolute_url' |
| SLAVE_INFO_TEMPLATE_VAR = 'slave_info' |
| FILE_INFO_TEMPLATE_VAR = 'file_info' |
| RENDER_PICTURES_ARGS_TEMPLATE_VAR = 'render_pictures_args' |
| NOPATCH_GPU_TEMPLATE_VAR = 'nopatch_gpu' |
| WITHPATCH_GPU_TEMPLATE_VAR = 'withpatch_gpu' |
| TOTAL_FAILING_FILES_TEMPLATE_VAR = 'failing_files_count' |
| GS_FILES_LOCATION_NO_PATCH_TEMPLATE_VAR = 'gs_http_files_location_nopatch' |
| GS_FILES_LOCATION_WITH_PATCH_TEMPLATE_VAR = 'gs_http_files_location_withpatch' |
| GS_FILES_LOCATION_DIFFS_TEMPLATE_VAR = 'gs_http_files_location_diffs' |
| GS_FILES_LOCATION_WHITE_DIFFS_TEMPLATE_VAR = 'gs_http_files_location_whitediffs' |
| |
| |
| class FileInfo(object): |
| """Container class that holds all file data.""" |
| def __init__(self, file_name, skp_location, num_pixels_differing, |
| percent_pixels_differing, |
| max_diff_per_channel, perceptual_diff): |
| self.file_name = file_name |
| self.diff_file_name = _GetDiffFileName(self.file_name) |
| self.skp_location = skp_location |
| self.num_pixels_differing = num_pixels_differing |
| self.percent_pixels_differing = percent_pixels_differing |
| self.max_diff_per_channel = max_diff_per_channel |
| self.perceptual_diff = perceptual_diff |
| |
| |
| def _GetDiffFileName(file_name): |
| file_name_no_ext, ext = os.path.splitext(file_name) |
| ext = ext.lstrip('.') |
| return '%s_nopatch_%s-vs-%s_withpatch_%s.%s' % ( |
| file_name_no_ext, ext, file_name_no_ext, ext, ext) |
| |
| |
| class SlaveInfo(object): |
| """Container class that holds all slave data.""" |
| def __init__(self, slave_name, failed_files, skps_location, |
| files_location_nopatch, files_location_withpatch, |
| files_location_diffs, files_location_whitediffs): |
| self.slave_name = slave_name |
| self.failed_files = failed_files |
| self.files_location_nopatch = files_location_nopatch |
| self.files_location_withpatch = files_location_withpatch |
| self.files_location_diffs = files_location_diffs |
| self.files_location_whitediffs = files_location_whitediffs |
| self.skps_location = skps_location |
| |
| |
| def CombineJsonSummaries(json_summaries_dir): |
| """Combines JSON summaries and returns the summaries in HTML.""" |
| slave_name_to_info = {} |
| for json_summary in glob.glob(os.path.join(json_summaries_dir, '*.json')): |
| with open(json_summary) as f: |
| data = json.load(f) |
| # There must be only one top level key and it must be the slave name. |
| assert len(data.keys()) == 1 |
| |
| slave_name = data.keys()[0] |
| slave_data = data[slave_name] |
| file_info_list = [] |
| for failed_file in slave_data[json_summary_constants.JSONKEY_FAILED_FILES]: |
| failed_file_name = failed_file[json_summary_constants.JSONKEY_FILE_NAME] |
| skp_location = posixpath.join( |
| STORAGE_HTTP_BASE, |
| failed_file[ |
| json_summary_constants.JSONKEY_SKP_LOCATION].lstrip('gs://')) |
| num_pixels_differing = failed_file[ |
| json_summary_constants.JSONKEY_NUM_PIXELS_DIFFERING] |
| percent_pixels_differing = failed_file[ |
| json_summary_constants.JSONKEY_PERCENT_PIXELS_DIFFERING] |
| max_diff_per_channel = failed_file[ |
| json_summary_constants.JSONKEY_MAX_DIFF_PER_CHANNEL] |
| perceptual_diff = failed_file[ |
| json_summary_constants.JSONKEY_PERCEPTUAL_DIFF] |
| |
| file_info = FileInfo( |
| file_name=failed_file_name, |
| skp_location=skp_location, |
| num_pixels_differing=num_pixels_differing, |
| percent_pixels_differing=percent_pixels_differing, |
| max_diff_per_channel=max_diff_per_channel, |
| perceptual_diff=perceptual_diff) |
| file_info_list.append(file_info) |
| |
| slave_info = SlaveInfo( |
| slave_name=slave_name, |
| failed_files=file_info_list, |
| skps_location=slave_data[json_summary_constants.JSONKEY_SKPS_LOCATION], |
| files_location_nopatch=slave_data[ |
| json_summary_constants.JSONKEY_FILES_LOCATION_NOPATCH], |
| files_location_withpatch=slave_data[ |
| json_summary_constants.JSONKEY_FILES_LOCATION_WITHPATCH], |
| files_location_diffs=slave_data[ |
| json_summary_constants.JSONKEY_FILES_LOCATION_DIFFS], |
| files_location_whitediffs=slave_data[ |
| json_summary_constants.JSONKEY_FILES_LOCATION_WHITE_DIFFS]) |
| slave_name_to_info[slave_name] = slave_info |
| |
| return slave_name_to_info |
| |
| |
| def OutputToHTML(slave_name_to_info, output_html_dir, absolute_url, |
| render_pictures_args, nopatch_gpu, withpatch_gpu): |
| """Outputs a slave name to SlaveInfo dict into HTML. |
| |
| Creates a top level HTML file that lists slave names to the number of failing |
| files. Also creates X number of HTML files that lists all the failing files |
| and displays the nopatch and withpatch images. X here corresponds to the |
| number of slaves that have failing files. |
| """ |
| # Get total failing file count. |
| total_failing_files = 0 |
| for slave_info in slave_name_to_info.values(): |
| total_failing_files += len(slave_info.failed_files) |
| |
| slave_name_to_info_items = sorted( |
| slave_name_to_info.items(), key=lambda tuple: tuple[0]) |
| rendered = loader.render_to_string( |
| 'slaves_totals.html', |
| {SLAVE_NAME_TO_INFO_ITEMS_TEMPLATE_VAR: slave_name_to_info_items, |
| ABSOLUTE_URL_TEMPLATE_VAR: absolute_url, |
| RENDER_PICTURES_ARGS_TEMPLATE_VAR: render_pictures_args, |
| NOPATCH_GPU_TEMPLATE_VAR: nopatch_gpu, |
| WITHPATCH_GPU_TEMPLATE_VAR: withpatch_gpu, |
| TOTAL_FAILING_FILES_TEMPLATE_VAR: total_failing_files} |
| ) |
| with open(os.path.join(output_html_dir, 'index.html'), 'w') as index_html: |
| index_html.write(rendered) |
| |
| rendered = loader.render_to_string( |
| 'list_of_all_files.html', |
| {SLAVE_NAME_TO_INFO_ITEMS_TEMPLATE_VAR: slave_name_to_info_items, |
| ABSOLUTE_URL_TEMPLATE_VAR: absolute_url} |
| ) |
| with open(os.path.join(output_html_dir, |
| 'list_of_all_files.html'), 'w') as files_html: |
| files_html.write(rendered) |
| |
| for slave_info in slave_name_to_info.values(): |
| for file_info in slave_info.failed_files: |
| rendered = loader.render_to_string( |
| 'single_file_details.html', |
| {FILE_INFO_TEMPLATE_VAR: file_info, |
| ABSOLUTE_URL_TEMPLATE_VAR: absolute_url, |
| GS_FILES_LOCATION_NO_PATCH_TEMPLATE_VAR: posixpath.join( |
| STORAGE_HTTP_BASE, |
| slave_info.files_location_nopatch.lstrip('gs://')), |
| GS_FILES_LOCATION_WITH_PATCH_TEMPLATE_VAR: posixpath.join( |
| STORAGE_HTTP_BASE, |
| slave_info.files_location_withpatch.lstrip('gs://')), |
| GS_FILES_LOCATION_DIFFS_TEMPLATE_VAR: posixpath.join( |
| STORAGE_HTTP_BASE, |
| slave_info.files_location_diffs.lstrip('gs://')), |
| GS_FILES_LOCATION_WHITE_DIFFS_TEMPLATE_VAR: posixpath.join( |
| STORAGE_HTTP_BASE, |
| slave_info.files_location_whitediffs.lstrip('gs://'))} |
| ) |
| with open(os.path.join(output_html_dir, '%s.html' % file_info.file_name), |
| 'w') as per_file_html: |
| per_file_html.write(rendered) |
| |
| |
| if '__main__' == __name__: |
| option_parser = optparse.OptionParser() |
| option_parser.add_option( |
| '', '--json_summaries_dir', |
| help='Location of JSON summary files from all GCE slaves.') |
| option_parser.add_option( |
| '', '--output_html_dir', |
| help='The absolute path of the HTML dir that will contain the results of' |
| ' this script.') |
| option_parser.add_option( |
| '', '--absolute_url', |
| help='Servers like Google Storage require an absolute url for links ' |
| 'within the HTML output files.', |
| default='') |
| option_parser.add_option( |
| '', '--render_pictures_args', |
| help='The arguments specified by the user to the render_pictures binary.') |
| option_parser.add_option( |
| '', '--nopatch_gpu', |
| help='Specifies whether the nopatch render_pictures run was done with ' |
| 'GPU.') |
| option_parser.add_option( |
| '', '--withpatch_gpu', |
| help='Specifies whether the withpatch render_pictures run was done with ' |
| 'GPU.') |
| options, unused_args = option_parser.parse_args() |
| if (not options.json_summaries_dir or not options.output_html_dir |
| or not options.render_pictures_args or not options.nopatch_gpu |
| or not options.withpatch_gpu): |
| option_parser.error( |
| 'Must specify json_summaries_dir, output_html_dir, ' |
| 'render_pictures_args, nopatch_gpu and withpatch_gpu') |
| |
| OutputToHTML( |
| slave_name_to_info=CombineJsonSummaries(options.json_summaries_dir), |
| output_html_dir=options.output_html_dir, |
| absolute_url=options.absolute_url, |
| render_pictures_args=options.render_pictures_args, |
| nopatch_gpu=options.nopatch_gpu, |
| withpatch_gpu=options.withpatch_gpu) |