# 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)]
"""
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)