blob: 23e47f3cf0435dc70dc7683ad287c4f8657529b6 [file] [log] [blame]
# Copyright (c) 2012 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.
"""Generate detail table .html files for board-netbook-testcategories."""
import datetime
import logging
import os
from django.shortcuts import render_to_response
import dash_util
from build_info import BuildInfo
from dash_view import SummaryRanges
from table_builder import TableBuilder
# String resources.
from dash_strings import BVT_TAG
from dash_strings import DASHBOARD_MAIN
from dash_strings import EMAILS_SUMMARY_FILE
from dash_strings import KERNEL_TABLE_FILE
from dash_strings import KERNEL_WATERFALL_FILE
from dash_strings import KERNELTEST_TAG
from dash_strings import PERF_BUILDS_FILE
from dash_strings import TEST_DETAILS_FILE
from dash_strings import TEST_LANDING_FILE
from dash_strings import TEST_WATERFALL_FILE
from dash_strings import UNKNOWN_TIME_STR
def _BuildNetbookHTML(
dash_base_dir, dash_view, tpl_netbook, tpl_board,
default_category, dash_options):
"""Create table files for all categories of a netbook-board.
Produces a set of pages of test results for a given netbook,
board and categories with tests executed.
Args:
dash_base_dir: base of dashboard output files.
dash_view: data model with all test result details.
tpl_netbook: netbook (e.g. netbook_MARIO_MP) for these pages.
tpl_board: board (e.g. x86-mario) for these pages.
default_category: landing page when switching pages.
dash_options: config options used for setting landing pages.
"""
base_dir = os.path.join(dash_base_dir, tpl_netbook, tpl_board)
if not os.path.exists(base_dir):
dash_util.MakeChmodDirs(base_dir)
logging.info('build %s into %s', tpl_netbook, base_dir)
# Build a details page for each category.
alternate_landings = dash_options.get('alternatelandings', {})
tpl_board_netbooks = [
(n, alternate_landings.get(tpl_board, {}).get(n, BVT_TAG))
for n in dash_view.GetNetbooksWithBoardType(tpl_board)
if n != tpl_netbook]
tpl_other_boards = sorted(
[b
for b in dash_view.GetNetbookBoardTypes(tpl_netbook)
if b != tpl_board])
tpl_last_updated = dash_view.GetFormattedLastUpdated()
tpl_categories = dash_view.GetUICategories(tpl_netbook, tpl_board)
if not default_category in tpl_categories:
tpl_categories.append(default_category)
tpl_categories.sort()
tpl_categories_with_color = []
for tpl_category in tpl_categories:
table_builder = TableBuilder(dash_base_dir, tpl_netbook, tpl_board,
tpl_category)
total, failed = table_builder.CountTestList()
if failed:
label = tpl_category + '(%d/%d)' % (total, failed)
else:
label = tpl_category + '(%d)' % total
if total == 0:
bg_class = 'white'
elif failed == 0:
bg_class = 'success'
elif total == failed:
bg_class = 'failure'
else:
bg_class = 'warning'
tpl_categories_with_color.append(
(tpl_category, table_builder, label, bg_class))
# Produce a test results page for each test category.
tpl_perf_builds = None
for tpl_category, table_builder, label, bg_class in tpl_categories_with_color:
tpl_tables = table_builder.BuildTables()
if tpl_category == BVT_TAG and tpl_tables:
tpl_perf_builds = tpl_tables[0]['body']
dash_util.SaveHTML(
os.path.join(base_dir, '%s.html' % tpl_category),
render_to_response(
os.path.join('tables/details', TEST_DETAILS_FILE),
locals()).content)
# Produce a performance landing page.
tpl_perf_available = False
if 'alerts' in dash_options:
for alert in dash_options['alerts']:
if ('platforms' in alert and
{tpl_board: tpl_netbook} in alert['platforms']):
tpl_perf_available = True
dash_util.SaveHTML(
os.path.join(base_dir, PERF_BUILDS_FILE),
render_to_response(
os.path.join('tables/details', PERF_BUILDS_FILE),
locals()).content)
def _GetLandingDetails(dash_view, summary_ranges, netbook, board, category,
build):
"""Gather the summary details for one build (row).
If the dashboard presented results from one source this
would not be needed. Since we grab data from test results,
test attributes and crash summaries this sort of function
is needed to collect distributed data for one build and category.
Args:
dash_view: data model with all test result details.
summary_ranges: limits for data queries in this summary.
netbook: netbook (e.g. netbook_MARIO_MP) for these pages.
board: board (e.g. x86-mario) for these pages.
category: summary producted for bvt now, maybe something else later.
build: build to use.
Returns:
Tuple of data for populating one waterfall row (build).
"""
job_attempted, job_good, passed, total, xpassed, xtotal = (
dash_view.GetCategorySummary(netbook, board, category, build))
kernel = summary_ranges.GetKernel(board, netbook, build)
failed_tests = dash_view.GetCategoryFailedTests(
netbook, board, category, build)
if category == BVT_TAG:
category = None
crashes, crash_count, crash_category = (
dash_view.GetCrashes().GetBuildCrashSummary(netbook, board, build,
category))
# x86-alex-r18 -> x86-alex
# x86-generic-full -> x86-generic-full
release_index = board.rfind('-r')
if release_index > 0:
pure_board = board[:release_index]
else:
pure_board = board
return (board, pure_board, netbook, build, job_attempted, job_good, passed,
total, xpassed, xtotal, kernel, failed_tests, crashes, crash_count,
crash_category)
def BuildLandingSummaries(dash_view, category, tots, branches, summary_ranges):
"""Produces individual table for each board of test summaries per build.
This produces the data for the 'new-style' (waterfall) dashboard summary.
Args:
dash_view: data model with all test result details.
category: summary producted for bvt now, maybe something else later.
tots: boards to be grouped at the top of page.
branches: boards to be grouped next.
summary_ranges: limits for data queries in this summary.
Returns:
A dictionary with the data for the waterfall display.
"""
platforms = []
builds = {}
build_info = BuildInfo()
results_dict = {}
releases = set()
irregular_releases = set()
for board in tots + branches:
parsed_board, release = dash_view.ParseBoard(board)
if release:
releases.add(release)
else:
irregular_releases.add(board)
for build_number in summary_ranges.GetBuildNumbers(board):
build_results = results_dict.setdefault(build_number, {})
for netbook in summary_ranges.GetNetbooks(board):
# Aggregate the test summaries for each platform.
platform = (parsed_board, netbook)
if not platform in platforms:
platforms.append(platform)
if build_results.get(platform):
logging.info('Multiple results for %s, %s', build_number, platform)
continue
build_results[platform] = _GetLandingDetails(
dash_view, summary_ranges, netbook, board, category, build_number)
# Keep track of earliest test job start time for each build.
time_key = (netbook, board, category, build_number)
start_time, _, _ = dash_view.GetJobTimesNone(*time_key)
if not start_time:
continue
early_start = builds.setdefault(build_number, (start_time, time_key))
if start_time < early_start[0]:
builds[build_number] = (start_time, time_key)
# Include the earliest job date among the platforms to be shown as the
# overall 'release' (r15) test start date-time.
organized_results = []
for build, (start_time, time_key) in sorted(builds.iteritems(), reverse=True,
key=lambda (k, v): v[0]):
build_results = []
for platform in platforms:
build_results.append(results_dict.get(build, {}).get(platform))
if time_key:
formatted_start, _, _ = dash_view.GetFormattedJobTimes(*time_key)
else:
formatted_start = None
if build[0].lower() == 'r':
# R16-w.x.y format build number.
build_release = build.split('-')[0][1:]
else:
# 0.15.x.y format build number.
build_release = build.split('.')[1]
organized_results.append((build, build_release,
formatted_start, build_results))
return {'platforms': platforms,
'irregular': irregular_releases,
'releases': sorted(releases, reverse=True),
'results': organized_results}
def BuildSummaryHTML(base_dir, html_filename, tpl_summary_data, last_updated):
"""Render actual page and save to an html file.
Args:
base_dir: base where resulting html file goes.
html_filename: actual filename differs in views.
tpl_summary_data: this data consumed by the template.
last_updated: published on output pages.
Render Variables:
last_updated: date used by the template.
"""
full_filepath = os.path.join(base_dir, html_filename)
tpl_last_updated = last_updated
dash_util.SaveHTML(
full_filepath,
render_to_response(
os.path.join('tables/summary', html_filename), locals()).content)
def BuildAllTables(dash_base_dir, dash_view, dash_options, summary_limit,
waterfall_limit):
"""Build all detail pages and a few summary pages as well.
Builds the detail pages for each netbook/board/category and then a
waterfall-style summary and a few other summary pages.
Args:
dash_base_dir: base of dashboard output files.
dash_view: data model with all test result details.
dash_options: config options used for setting landing pages.
summary_limit: only show n rows/table on the summary page.
waterfall_limit: only show n rows on the waterfall summary page.
Render Variables:
last_updated: date used by the template.
"""
tpl_last_updated = dash_view.GetFormattedLastUpdated()
netbooks = dash_options.get('debug_netbook', dash_view.netbooks)
for netbook in netbooks:
for board_type in dash_view.GetNetbookBoardTypes(netbook):
_BuildNetbookHTML(dash_base_dir, dash_view, netbook, board_type,
BVT_TAG, dash_options)
for summary_file, build_fn, category, limit in (
(TEST_WATERFALL_FILE, BuildLandingSummaries, BVT_TAG, waterfall_limit),
(KERNEL_WATERFALL_FILE, BuildLandingSummaries, KERNELTEST_TAG,
waterfall_limit)):
summary_ranges = SummaryRanges(dash_view, category, limit)
boards = summary_ranges.GetBoards()
tots = []
branches = []
nonpriority = []
for board in boards:
if board in dash_options['priorityboards_tot']:
tots.append(board)
elif board in dash_options['priorityboards']:
branches.append(board)
else:
nonpriority.append(board)
branches += nonpriority
BuildSummaryHTML(
dash_base_dir,
summary_file,
build_fn(dash_view, category, tots, branches, summary_ranges),
tpl_last_updated)
dash_util.SaveHTML(
os.path.join(dash_base_dir, TEST_LANDING_FILE),
render_to_response(
os.path.join('tables/summary', TEST_LANDING_FILE),
locals()).content)
dash_util.SaveHTML(
os.path.join(dash_base_dir, EMAILS_SUMMARY_FILE),
render_to_response(
os.path.join('tables/summary', EMAILS_SUMMARY_FILE),
locals()).content)
if __name__ == '__main__':
print 'Run %s with --table-generate.' % DASHBOARD_MAIN