# Copyright 2017 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """ This is a utility to build an html page based on the directory summaries collected during the test. """ import os import re import common from autotest_lib.client.bin.result_tools import utils_lib from autotest_lib.client.common_lib import global_config CONFIG = global_config.global_config # Base url to open a file from Google Storage GS_FILE_BASE_URL = CONFIG.get_config_value('CROS', 'gs_file_base_url') # Default width of `size_trimmed_width`. If throttle is not applied, the block # of `size_trimmed_width` will be set to minimum to make the view more compact. DEFAULT_SIZE_TRIMMED_WIDTH = 50 DEFAULT_RESULT_SUMMARY_NAME = 'result_summary.html' DIR_SUMMARY_PATTERN = 'dir_summary_\d+.json' # ================================================== # Following are key names used in the html templates: CSS = 'css' DIRS = 'dirs' GS_FILE_BASE_URL_KEY = 'gs_file_base_url' INDENTATION_KEY = 'indentation' JAVASCRIPT = 'javascript' JOB_DIR = 'job_dir' NAME = 'name' PATH = 'path' SIZE_CLIENT_COLLECTED = 'size_client_collected' SIZE_INFO = 'size_info' SIZE_ORIGINAL = 'size_original' SIZE_PERCENT = 'size_percent' SIZE_PERCENT_CLASS = 'size_percent_class' SIZE_PERCENT_CLASS_REGULAR = 'size_percent' SIZE_PERCENT_CLASS_TOP = 'top_size_percent' SIZE_SUMMARY = 'size_summary' SIZE_TRIMMED = 'size_trimmed' # Width of `size_trimmed` block` SIZE_TRIMMED_WIDTH = 'size_trimmed_width' SUBDIRS = 'subdirs' SUMMARY_TREE = 'summary_tree' # ================================================== # Text to show when test result is not throttled. NOT_THROTTLED = '(Not throttled)' PAGE_TEMPLATE = """

Summary of test results

%(size_summary)s

Display format of a file or directory:

[percentage of size in the parent directory] [original size] [size after throttling (empty if not throttled)] [file name (strikethrough if file was deleted due to throttling)]

%(summary_tree)s %(css)s %(javascript)s """ CSS_TEMPLATE = """ """ JAVASCRIPT_TEMPLATE = """ """ SIZE_SUMMARY_TEMPLATE = """
Results collected from test device: %(size_client_collected)s
Original size of test results: %(size_original)s
Size of test results after throttling: %(size_trimmed)s
""" SIZE_INFO_TEMPLATE = """ %(indentation)s%(size_percent)s %(indentation)s%(size_original)s %(indentation)s%(size_trimmed)s """ FILE_ENTRY_TEMPLATE = """ %(indentation)s
  • %(indentation)s\t
    %(size_info)s %(indentation)s\t\t %(indentation)s\t\t\t%(name)s %(indentation)s\t\t %(indentation)s\t
    %(indentation)s
  • """ DELETED_FILE_ENTRY_TEMPLATE = """ %(indentation)s
  • %(indentation)s\t
    %(size_info)s %(indentation)s\t\t%(name)s %(indentation)s\t
    %(indentation)s
  • """ DIR_ENTRY_TEMPLATE = """ %(indentation)s
  • %(size_info)s %(name)s %(subdirs)s %(indentation)s
  • """ SUBDIRS_WRAPPER_TEMPLATE = """ %(indentation)s""" INDENTATION = '\t' def _get_size_percent(size_original, total_bytes): """Get the percentage of file size in the parent directory before throttled. @param size_original: Original size of the file, in bytes. @param total_bytes: Total size of all files under the parent directory, in bytes. @return: A formatted string of the percentage of file size in the parent directory before throttled. """ if total_bytes == 0: return '0%' return '%.1f%%' % (100*float(size_original)/total_bytes) def _get_dirs_html(dirs, parent_path, total_bytes, indentation): """Get the html string for the given directory. @param dirs: A list of ResultInfo. @param parent_path: Path to the parent directory. @param total_bytes: Total of the original size of files in the given directories in bytes. @param indentation: Indentation to be used for the html. """ if not dirs: return '' summary_html = '' top_size_limit = max([entry.original_size for entry in dirs]) # A map between file name to ResultInfo that contains the summary of the # file. entries = dict((entry.keys()[0], entry) for entry in dirs) for name in sorted(entries.keys()): entry = entries[name] if not entry.is_dir and re.match(DIR_SUMMARY_PATTERN, name): # Do not include directory summary json files in the html, as they # will be deleted. continue size_data = {SIZE_PERCENT: _get_size_percent(entry.original_size, total_bytes), SIZE_ORIGINAL: utils_lib.get_size_string(entry.original_size), SIZE_TRIMMED: utils_lib.get_size_string(entry.trimmed_size), INDENTATION_KEY: indentation + 2*INDENTATION} if entry.original_size < top_size_limit: size_data[SIZE_PERCENT_CLASS] = SIZE_PERCENT_CLASS_REGULAR else: size_data[SIZE_PERCENT_CLASS] = SIZE_PERCENT_CLASS_TOP if entry.trimmed_size == entry.original_size: size_data[SIZE_TRIMMED] = '' entry_path = '%s/%s' % (parent_path, name) if not entry.is_dir: # This is a file data = {NAME: name, PATH: entry_path, SIZE_INFO: SIZE_INFO_TEMPLATE % size_data, INDENTATION_KEY: indentation} if entry.original_size > 0 and entry.trimmed_size == 0: summary_html += DELETED_FILE_ENTRY_TEMPLATE % data else: summary_html += FILE_ENTRY_TEMPLATE % data else: subdir_total_size = entry.original_size sub_indentation = indentation + INDENTATION subdirs_html = ( SUBDIRS_WRAPPER_TEMPLATE % {DIRS: _get_dirs_html( entry.files, entry_path, subdir_total_size, sub_indentation), INDENTATION_KEY: indentation}) data = {NAME: entry.name, SIZE_INFO: SIZE_INFO_TEMPLATE % size_data, SUBDIRS: subdirs_html, INDENTATION_KEY: indentation} summary_html += DIR_ENTRY_TEMPLATE % data return summary_html def build(client_collected_bytes, summary, html_file): """Generate an HTML file to visualize the given directory summary. @param client_collected_bytes: The total size of results collected from the DUT. The number can be larger than the total file size of the given path, as files can be overwritten or removed. @param summary: A ResultInfo instance containing the directory summary. @param html_file: Path to save the html file to. """ size_original = summary.original_size size_trimmed = summary.trimmed_size size_summary_data = {SIZE_CLIENT_COLLECTED: utils_lib.get_size_string(client_collected_bytes), SIZE_ORIGINAL: utils_lib.get_size_string(size_original), SIZE_TRIMMED: utils_lib.get_size_string(size_trimmed)} size_trimmed_width = DEFAULT_SIZE_TRIMMED_WIDTH if size_original == size_trimmed: size_summary_data[SIZE_TRIMMED] = NOT_THROTTLED size_trimmed_width = 0 size_summary = SIZE_SUMMARY_TEMPLATE % size_summary_data indentation = INDENTATION dirs_html = _get_dirs_html( summary.files, '..', size_original, indentation + INDENTATION) summary_tree = SUBDIRS_WRAPPER_TEMPLATE % {DIRS: dirs_html, INDENTATION_KEY: indentation} # job_dir is the path between Autotest `results` folder and the summary html # file, e.g., 123-debug_user/host1. Assume it always contains 2 levels. job_dir_sections = html_file.split(os.sep)[:-1] try: job_dir = '/'.join(job_dir_sections[ (job_dir_sections.index('results')+1):]) except ValueError: # 'results' is not in the path, default to two levels up of the summary # file. job_dir = '/'.join(job_dir_sections[-2:]) javascript = (JAVASCRIPT_TEMPLATE % {GS_FILE_BASE_URL_KEY: GS_FILE_BASE_URL, JOB_DIR: job_dir}) css = CSS_TEMPLATE % {SIZE_TRIMMED_WIDTH: size_trimmed_width} html = PAGE_TEMPLATE % {SIZE_SUMMARY: size_summary, SUMMARY_TREE: summary_tree, CSS: css, JAVASCRIPT: javascript} with open(html_file, 'w') as f: f.write(html)