aboutsummaryrefslogtreecommitdiff
path: root/crosperf/results_report.py
diff options
context:
space:
mode:
Diffstat (limited to 'crosperf/results_report.py')
-rw-r--r--crosperf/results_report.py1474
1 files changed, 784 insertions, 690 deletions
diff --git a/crosperf/results_report.py b/crosperf/results_report.py
index dc80b53b..045e623b 100644
--- a/crosperf/results_report.py
+++ b/crosperf/results_report.py
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Copyright 2013 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A module to handle the report format."""
-from __future__ import print_function
import datetime
import functools
@@ -14,15 +13,15 @@ import os
import re
import time
+from column_chart import ColumnChart
from cros_utils.tabulator import AmeanResult
from cros_utils.tabulator import Cell
from cros_utils.tabulator import CoeffVarFormat
from cros_utils.tabulator import CoeffVarResult
from cros_utils.tabulator import Column
-from cros_utils.tabulator import SamplesTableGenerator
from cros_utils.tabulator import Format
-from cros_utils.tabulator import IterationResult
from cros_utils.tabulator import GmeanRatioResult
+from cros_utils.tabulator import IterationResult
from cros_utils.tabulator import LiteralResult
from cros_utils.tabulator import MaxResult
from cros_utils.tabulator import MinResult
@@ -30,20 +29,18 @@ from cros_utils.tabulator import PValueFormat
from cros_utils.tabulator import PValueResult
from cros_utils.tabulator import RatioFormat
from cros_utils.tabulator import RawResult
+from cros_utils.tabulator import SamplesTableGenerator
from cros_utils.tabulator import StdResult
from cros_utils.tabulator import TableFormatter
from cros_utils.tabulator import TableGenerator
from cros_utils.tabulator import TablePrinter
-from update_telemetry_defaults import TelemetryDefaults
-
-from column_chart import ColumnChart
from results_organizer import OrganizeResults
-
import results_report_templates as templates
+from update_telemetry_defaults import TelemetryDefaults
def ParseChromeosImage(chromeos_image):
- """Parse the chromeos_image string for the image and version.
+ """Parse the chromeos_image string for the image and version.
The chromeos_image string will probably be in one of two formats:
1: <path-to-chroot>/src/build/images/<board>/<ChromeOS-version>.<datetime>/ \
@@ -64,760 +61,857 @@ def ParseChromeosImage(chromeos_image):
version, image: The results of parsing the input string, as explained
above.
"""
- # Find the Chromeos Version, e.g. R45-2345.0.0.....
- # chromeos_image should have been something like:
- # <path>/<board-trybot-release>/<chromeos-version>/chromiumos_test_image.bin"
- if chromeos_image.endswith('/chromiumos_test_image.bin'):
- full_version = chromeos_image.split('/')[-2]
- # Strip the date and time off of local builds (which have the format
- # "R43-2345.0.0.date-and-time").
- version, _ = os.path.splitext(full_version)
- else:
- version = ''
-
- # Find the chromeos image. If it's somewhere in .../chroot/tmp/..., then
- # it's an official image that got downloaded, so chop off the download path
- # to make the official image name more clear.
- official_image_path = '/chroot/tmp'
- if official_image_path in chromeos_image:
- image = chromeos_image.split(official_image_path, 1)[1]
- else:
- image = chromeos_image
- return version, image
+ # Find the Chromeos Version, e.g. R45-2345.0.0.....
+ # chromeos_image should have been something like:
+ # <path>/<board-trybot-release>/<chromeos-version>/chromiumos_test_image.bin"
+ if chromeos_image.endswith("/chromiumos_test_image.bin"):
+ full_version = chromeos_image.split("/")[-2]
+ # Strip the date and time off of local builds (which have the format
+ # "R43-2345.0.0.date-and-time").
+ version, _ = os.path.splitext(full_version)
+ else:
+ version = ""
+
+ # Find the chromeos image. If it's somewhere in .../chroot/tmp/..., then
+ # it's an official image that got downloaded, so chop off the download path
+ # to make the official image name more clear.
+ official_image_path = "/chroot/tmp"
+ if official_image_path in chromeos_image:
+ image = chromeos_image.split(official_image_path, 1)[1]
+ else:
+ image = chromeos_image
+ return version, image
def _AppendUntilLengthIs(gen, the_list, target_len):
- """Appends to `list` until `list` is `target_len` elements long.
+ """Appends to `list` until `list` is `target_len` elements long.
- Uses `gen` to generate elements.
- """
- the_list.extend(gen() for _ in range(target_len - len(the_list)))
- return the_list
+ Uses `gen` to generate elements.
+ """
+ the_list.extend(gen() for _ in range(target_len - len(the_list)))
+ return the_list
def _FilterPerfReport(event_threshold, report):
- """Filters out entries with `< event_threshold` percent in a perf report."""
+ """Filters out entries with `< event_threshold` percent in a perf report."""
- def filter_dict(m):
- return {
- fn_name: pct for fn_name, pct in m.items() if pct >= event_threshold
- }
+ def filter_dict(m):
+ return {
+ fn_name: pct for fn_name, pct in m.items() if pct >= event_threshold
+ }
- return {event: filter_dict(m) for event, m in report.items()}
+ return {event: filter_dict(m) for event, m in report.items()}
class _PerfTable(object):
- """Generates dicts from a perf table.
-
- Dicts look like:
- {'benchmark_name': {'perf_event_name': [LabelData]}}
- where LabelData is a list of perf dicts, each perf dict coming from the same
- label.
- Each perf dict looks like {'function_name': 0.10, ...} (where 0.10 is the
- percentage of time spent in function_name).
- """
-
- def __init__(self,
- benchmark_names_and_iterations,
- label_names,
- read_perf_report,
- event_threshold=None):
- """Constructor.
-
- read_perf_report is a function that takes a label name, benchmark name, and
- benchmark iteration, and returns a dictionary describing the perf output for
- that given run.
+ """Generates dicts from a perf table.
+
+ Dicts look like:
+ {'benchmark_name': {'perf_event_name': [LabelData]}}
+ where LabelData is a list of perf dicts, each perf dict coming from the same
+ label.
+ Each perf dict looks like {'function_name': 0.10, ...} (where 0.10 is the
+ percentage of time spent in function_name).
"""
- self.event_threshold = event_threshold
- self._label_indices = {name: i for i, name in enumerate(label_names)}
- self.perf_data = {}
- for label in label_names:
- for bench_name, bench_iterations in benchmark_names_and_iterations:
- for i in range(bench_iterations):
- report = read_perf_report(label, bench_name, i)
- self._ProcessPerfReport(report, label, bench_name, i)
-
- def _ProcessPerfReport(self, perf_report, label, benchmark_name, iteration):
- """Add the data from one run to the dict."""
- perf_of_run = perf_report
- if self.event_threshold is not None:
- perf_of_run = _FilterPerfReport(self.event_threshold, perf_report)
- if benchmark_name not in self.perf_data:
- self.perf_data[benchmark_name] = {event: [] for event in perf_of_run}
- ben_data = self.perf_data[benchmark_name]
- label_index = self._label_indices[label]
- for event in ben_data:
- _AppendUntilLengthIs(list, ben_data[event], label_index + 1)
- data_for_label = ben_data[event][label_index]
- _AppendUntilLengthIs(dict, data_for_label, iteration + 1)
- data_for_label[iteration] = perf_of_run[event] if perf_of_run else {}
+
+ def __init__(
+ self,
+ benchmark_names_and_iterations,
+ label_names,
+ read_perf_report,
+ event_threshold=None,
+ ):
+ """Constructor.
+
+ read_perf_report is a function that takes a label name, benchmark name, and
+ benchmark iteration, and returns a dictionary describing the perf output for
+ that given run.
+ """
+ self.event_threshold = event_threshold
+ self._label_indices = {name: i for i, name in enumerate(label_names)}
+ self.perf_data = {}
+ for label in label_names:
+ for bench_name, bench_iterations in benchmark_names_and_iterations:
+ for i in range(bench_iterations):
+ report = read_perf_report(label, bench_name, i)
+ self._ProcessPerfReport(report, label, bench_name, i)
+
+ def _ProcessPerfReport(self, perf_report, label, benchmark_name, iteration):
+ """Add the data from one run to the dict."""
+ perf_of_run = perf_report
+ if self.event_threshold is not None:
+ perf_of_run = _FilterPerfReport(self.event_threshold, perf_report)
+ if benchmark_name not in self.perf_data:
+ self.perf_data[benchmark_name] = {
+ event: [] for event in perf_of_run
+ }
+ ben_data = self.perf_data[benchmark_name]
+ label_index = self._label_indices[label]
+ for event in ben_data:
+ _AppendUntilLengthIs(list, ben_data[event], label_index + 1)
+ data_for_label = ben_data[event][label_index]
+ _AppendUntilLengthIs(dict, data_for_label, iteration + 1)
+ data_for_label[iteration] = (
+ perf_of_run[event] if perf_of_run else {}
+ )
def _GetResultsTableHeader(ben_name, iterations):
- benchmark_info = ('Benchmark: {0}; Iterations: {1}'.format(
- ben_name, iterations))
- cell = Cell()
- cell.string_value = benchmark_info
- cell.header = True
- return [[cell]]
+ benchmark_info = "Benchmark: {0}; Iterations: {1}".format(
+ ben_name, iterations
+ )
+ cell = Cell()
+ cell.string_value = benchmark_info
+ cell.header = True
+ return [[cell]]
def _GetDSOHeader(cwp_dso):
- info = 'CWP_DSO: %s' % cwp_dso
- cell = Cell()
- cell.string_value = info
- cell.header = False
- return [[cell]]
+ info = "CWP_DSO: %s" % cwp_dso
+ cell = Cell()
+ cell.string_value = info
+ cell.header = False
+ return [[cell]]
def _ParseColumn(columns, iteration):
- new_column = []
- for column in columns:
- if column.result.__class__.__name__ != 'RawResult':
- new_column.append(column)
- else:
- new_column.extend(
- Column(LiteralResult(i), Format(), str(i + 1))
- for i in range(iteration))
- return new_column
+ new_column = []
+ for column in columns:
+ if column.result.__class__.__name__ != "RawResult":
+ new_column.append(column)
+ else:
+ new_column.extend(
+ Column(LiteralResult(i), Format(), str(i + 1))
+ for i in range(iteration)
+ )
+ return new_column
def _GetTables(benchmark_results, columns, table_type):
- iter_counts = benchmark_results.iter_counts
- result = benchmark_results.run_keyvals
- tables = []
- for bench_name, runs in result.items():
- iterations = iter_counts[bench_name]
- ben_table = _GetResultsTableHeader(bench_name, iterations)
-
- all_runs_empty = all(not dict for label in runs for dict in label)
- if all_runs_empty:
- cell = Cell()
- cell.string_value = ('This benchmark contains no result.'
- ' Is the benchmark name valid?')
- cell_table = [[cell]]
- else:
- table = TableGenerator(runs, benchmark_results.label_names).GetTable()
- parsed_columns = _ParseColumn(columns, iterations)
- tf = TableFormatter(table, parsed_columns)
- cell_table = tf.GetCellTable(table_type)
- tables.append(ben_table)
- tables.append(cell_table)
- return tables
+ iter_counts = benchmark_results.iter_counts
+ result = benchmark_results.run_keyvals
+ tables = []
+ for bench_name, runs in result.items():
+ iterations = iter_counts[bench_name]
+ ben_table = _GetResultsTableHeader(bench_name, iterations)
+
+ all_runs_empty = all(not dict for label in runs for dict in label)
+ if all_runs_empty:
+ cell = Cell()
+ cell.string_value = (
+ "This benchmark contains no result."
+ " Is the benchmark name valid?"
+ )
+ cell_table = [[cell]]
+ else:
+ table = TableGenerator(
+ runs, benchmark_results.label_names
+ ).GetTable()
+ parsed_columns = _ParseColumn(columns, iterations)
+ tf = TableFormatter(table, parsed_columns)
+ cell_table = tf.GetCellTable(table_type)
+ tables.append(ben_table)
+ tables.append(cell_table)
+ return tables
def _GetPerfTables(benchmark_results, columns, table_type):
- p_table = _PerfTable(benchmark_results.benchmark_names_and_iterations,
- benchmark_results.label_names,
- benchmark_results.read_perf_report)
-
- tables = []
- for benchmark in p_table.perf_data:
- iterations = benchmark_results.iter_counts[benchmark]
- ben_table = _GetResultsTableHeader(benchmark, iterations)
- tables.append(ben_table)
- benchmark_data = p_table.perf_data[benchmark]
- table = []
- for event in benchmark_data:
- tg = TableGenerator(
- benchmark_data[event],
- benchmark_results.label_names,
- sort=TableGenerator.SORT_BY_VALUES_DESC)
- table = tg.GetTable(ResultsReport.PERF_ROWS)
- parsed_columns = _ParseColumn(columns, iterations)
- tf = TableFormatter(table, parsed_columns)
- tf.GenerateCellTable(table_type)
- tf.AddColumnName()
- tf.AddLabelName()
- tf.AddHeader(str(event))
- table = tf.GetCellTable(table_type, headers=False)
- tables.append(table)
- return tables
+ p_table = _PerfTable(
+ benchmark_results.benchmark_names_and_iterations,
+ benchmark_results.label_names,
+ benchmark_results.read_perf_report,
+ )
+
+ tables = []
+ for benchmark in p_table.perf_data:
+ iterations = benchmark_results.iter_counts[benchmark]
+ ben_table = _GetResultsTableHeader(benchmark, iterations)
+ tables.append(ben_table)
+ benchmark_data = p_table.perf_data[benchmark]
+ table = []
+ for event in benchmark_data:
+ tg = TableGenerator(
+ benchmark_data[event],
+ benchmark_results.label_names,
+ sort=TableGenerator.SORT_BY_VALUES_DESC,
+ )
+ table = tg.GetTable(ResultsReport.PERF_ROWS)
+ parsed_columns = _ParseColumn(columns, iterations)
+ tf = TableFormatter(table, parsed_columns)
+ tf.GenerateCellTable(table_type)
+ tf.AddColumnName()
+ tf.AddLabelName()
+ tf.AddHeader(str(event))
+ table = tf.GetCellTable(table_type, headers=False)
+ tables.append(table)
+ return tables
def _GetSamplesTables(benchmark_results, columns, table_type):
- tables = []
- dso_header_table = _GetDSOHeader(benchmark_results.cwp_dso)
- tables.append(dso_header_table)
- (table, new_keyvals, iter_counts) = SamplesTableGenerator(
- benchmark_results.run_keyvals, benchmark_results.label_names,
- benchmark_results.iter_counts, benchmark_results.weights).GetTable()
- parsed_columns = _ParseColumn(columns, 1)
- tf = TableFormatter(table, parsed_columns, samples_table=True)
- cell_table = tf.GetCellTable(table_type)
- tables.append(cell_table)
- return (tables, new_keyvals, iter_counts)
+ tables = []
+ dso_header_table = _GetDSOHeader(benchmark_results.cwp_dso)
+ tables.append(dso_header_table)
+ (table, new_keyvals, iter_counts) = SamplesTableGenerator(
+ benchmark_results.run_keyvals,
+ benchmark_results.label_names,
+ benchmark_results.iter_counts,
+ benchmark_results.weights,
+ ).GetTable()
+ parsed_columns = _ParseColumn(columns, 1)
+ tf = TableFormatter(table, parsed_columns, samples_table=True)
+ cell_table = tf.GetCellTable(table_type)
+ tables.append(cell_table)
+ return (tables, new_keyvals, iter_counts)
class ResultsReport(object):
- """Class to handle the report format."""
- MAX_COLOR_CODE = 255
- PERF_ROWS = 5
-
- def __init__(self, results):
- self.benchmark_results = results
-
- def _GetTablesWithColumns(self, columns, table_type, summary_type):
- if summary_type == 'perf':
- get_tables = _GetPerfTables
- elif summary_type == 'samples':
- get_tables = _GetSamplesTables
- else:
- get_tables = _GetTables
- ret = get_tables(self.benchmark_results, columns, table_type)
- # If we are generating a samples summary table, the return value of
- # get_tables will be a tuple, and we will update the benchmark_results for
- # composite benchmark so that full table can use it.
- if isinstance(ret, tuple):
- self.benchmark_results.run_keyvals = ret[1]
- self.benchmark_results.iter_counts = ret[2]
- ret = ret[0]
- return ret
-
- def GetFullTables(self, perf=False):
- ignore_min_max = self.benchmark_results.ignore_min_max
- columns = [
- Column(RawResult(), Format()),
- Column(MinResult(), Format()),
- Column(MaxResult(), Format()),
- Column(AmeanResult(ignore_min_max), Format()),
- Column(StdResult(ignore_min_max), Format(), 'StdDev'),
- Column(CoeffVarResult(ignore_min_max), CoeffVarFormat(), 'StdDev/Mean'),
- Column(GmeanRatioResult(ignore_min_max), RatioFormat(), 'GmeanSpeedup'),
- Column(PValueResult(ignore_min_max), PValueFormat(), 'p-value')
- ]
- return self._GetTablesWithColumns(columns, 'full', perf)
-
- def GetSummaryTables(self, summary_type=''):
- ignore_min_max = self.benchmark_results.ignore_min_max
- columns = []
- if summary_type == 'samples':
- columns += [Column(IterationResult(), Format(), 'Iterations [Pass:Fail]')]
- columns += [
- Column(
- AmeanResult(ignore_min_max), Format(),
- 'Weighted Samples Amean' if summary_type == 'samples' else ''),
- Column(StdResult(ignore_min_max), Format(), 'StdDev'),
- Column(CoeffVarResult(ignore_min_max), CoeffVarFormat(), 'StdDev/Mean'),
- Column(GmeanRatioResult(ignore_min_max), RatioFormat(), 'GmeanSpeedup'),
- Column(PValueResult(ignore_min_max), PValueFormat(), 'p-value')
- ]
- return self._GetTablesWithColumns(columns, 'summary', summary_type)
+ """Class to handle the report format."""
+
+ MAX_COLOR_CODE = 255
+ PERF_ROWS = 5
+
+ def __init__(self, results):
+ self.benchmark_results = results
+
+ def _GetTablesWithColumns(self, columns, table_type, summary_type):
+ if summary_type == "perf":
+ get_tables = _GetPerfTables
+ elif summary_type == "samples":
+ get_tables = _GetSamplesTables
+ else:
+ get_tables = _GetTables
+ ret = get_tables(self.benchmark_results, columns, table_type)
+ # If we are generating a samples summary table, the return value of
+ # get_tables will be a tuple, and we will update the benchmark_results for
+ # composite benchmark so that full table can use it.
+ if isinstance(ret, tuple):
+ self.benchmark_results.run_keyvals = ret[1]
+ self.benchmark_results.iter_counts = ret[2]
+ ret = ret[0]
+ return ret
+
+ def GetFullTables(self, perf=False):
+ ignore_min_max = self.benchmark_results.ignore_min_max
+ columns = [
+ Column(RawResult(), Format()),
+ Column(MinResult(), Format()),
+ Column(MaxResult(), Format()),
+ Column(AmeanResult(ignore_min_max), Format()),
+ Column(StdResult(ignore_min_max), Format(), "StdDev"),
+ Column(
+ CoeffVarResult(ignore_min_max), CoeffVarFormat(), "StdDev/Mean"
+ ),
+ Column(
+ GmeanRatioResult(ignore_min_max), RatioFormat(), "GmeanSpeedup"
+ ),
+ Column(PValueResult(ignore_min_max), PValueFormat(), "p-value"),
+ ]
+ return self._GetTablesWithColumns(columns, "full", perf)
+
+ def GetSummaryTables(self, summary_type=""):
+ ignore_min_max = self.benchmark_results.ignore_min_max
+ columns = []
+ if summary_type == "samples":
+ columns += [
+ Column(IterationResult(), Format(), "Iterations [Pass:Fail]")
+ ]
+ columns += [
+ Column(
+ AmeanResult(ignore_min_max),
+ Format(),
+ "Weighted Samples Amean" if summary_type == "samples" else "",
+ ),
+ Column(StdResult(ignore_min_max), Format(), "StdDev"),
+ Column(
+ CoeffVarResult(ignore_min_max), CoeffVarFormat(), "StdDev/Mean"
+ ),
+ Column(
+ GmeanRatioResult(ignore_min_max), RatioFormat(), "GmeanSpeedup"
+ ),
+ Column(PValueResult(ignore_min_max), PValueFormat(), "p-value"),
+ ]
+ return self._GetTablesWithColumns(columns, "summary", summary_type)
def _PrintTable(tables, out_to):
- # tables may be None.
- if not tables:
- return ''
-
- if out_to == 'HTML':
- out_type = TablePrinter.HTML
- elif out_to == 'PLAIN':
- out_type = TablePrinter.PLAIN
- elif out_to == 'CONSOLE':
- out_type = TablePrinter.CONSOLE
- elif out_to == 'TSV':
- out_type = TablePrinter.TSV
- elif out_to == 'EMAIL':
- out_type = TablePrinter.EMAIL
- else:
- raise ValueError('Invalid out_to value: %s' % (out_to,))
-
- printers = (TablePrinter(table, out_type) for table in tables)
- return ''.join(printer.Print() for printer in printers)
+ # tables may be None.
+ if not tables:
+ return ""
+
+ if out_to == "HTML":
+ out_type = TablePrinter.HTML
+ elif out_to == "PLAIN":
+ out_type = TablePrinter.PLAIN
+ elif out_to == "CONSOLE":
+ out_type = TablePrinter.CONSOLE
+ elif out_to == "TSV":
+ out_type = TablePrinter.TSV
+ elif out_to == "EMAIL":
+ out_type = TablePrinter.EMAIL
+ else:
+ raise ValueError("Invalid out_to value: %s" % (out_to,))
+ printers = (TablePrinter(table, out_type) for table in tables)
+ return "".join(printer.Print() for printer in printers)
-class TextResultsReport(ResultsReport):
- """Class to generate text result report."""
-
- H1_STR = '==========================================='
- H2_STR = '-------------------------------------------'
-
- def __init__(self, results, email=False, experiment=None):
- super(TextResultsReport, self).__init__(results)
- self.email = email
- self.experiment = experiment
-
- @staticmethod
- def _MakeTitle(title):
- header_line = TextResultsReport.H1_STR
- # '' at the end gives one newline.
- return '\n'.join([header_line, title, header_line, ''])
-
- @staticmethod
- def _MakeSection(title, body):
- header_line = TextResultsReport.H2_STR
- # '\n' at the end gives us two newlines.
- return '\n'.join([header_line, title, header_line, body, '\n'])
-
- @staticmethod
- def FromExperiment(experiment, email=False):
- results = BenchmarkResults.FromExperiment(experiment)
- return TextResultsReport(results, email, experiment)
-
- def GetStatusTable(self):
- """Generate the status table by the tabulator."""
- table = [['', '']]
- columns = [
- Column(LiteralResult(iteration=0), Format(), 'Status'),
- Column(LiteralResult(iteration=1), Format(), 'Failing Reason')
- ]
-
- for benchmark_run in self.experiment.benchmark_runs:
- status = [
- benchmark_run.name,
- [benchmark_run.timeline.GetLastEvent(), benchmark_run.failure_reason]
- ]
- table.append(status)
- cell_table = TableFormatter(table, columns).GetCellTable('status')
- return [cell_table]
-
- def GetTotalWaitCooldownTime(self):
- """Get cooldown wait time in seconds from experiment benchmark runs.
-
- Returns:
- Dictionary {'dut': int(wait_time_in_seconds)}
- """
- waittime_dict = {}
- for dut in self.experiment.machine_manager.GetMachines():
- waittime_dict[dut.name] = dut.GetCooldownWaitTime()
- return waittime_dict
-
- def GetReport(self):
- """Generate the report for email and console."""
- output_type = 'EMAIL' if self.email else 'CONSOLE'
- experiment = self.experiment
-
- sections = []
- if experiment is not None:
- title_contents = "Results report for '%s'" % (experiment.name,)
- else:
- title_contents = 'Results report'
- sections.append(self._MakeTitle(title_contents))
- if not self.benchmark_results.cwp_dso:
- summary_table = _PrintTable(self.GetSummaryTables(), output_type)
- else:
- summary_table = _PrintTable(
- self.GetSummaryTables(summary_type='samples'), output_type)
- sections.append(self._MakeSection('Summary', summary_table))
-
- if experiment is not None:
- table = _PrintTable(self.GetStatusTable(), output_type)
- sections.append(self._MakeSection('Benchmark Run Status', table))
-
- if not self.benchmark_results.cwp_dso:
- perf_table = _PrintTable(
- self.GetSummaryTables(summary_type='perf'), output_type)
- sections.append(self._MakeSection('Perf Data', perf_table))
-
- if experiment is not None:
- experiment_file = experiment.experiment_file
- sections.append(self._MakeSection('Experiment File', experiment_file))
-
- cpu_info = experiment.machine_manager.GetAllCPUInfo(experiment.labels)
- sections.append(self._MakeSection('CPUInfo', cpu_info))
-
- totaltime = (time.time() -
- experiment.start_time) if experiment.start_time else 0
- totaltime_str = 'Total experiment time:\n%d min' % (totaltime // 60)
- cooldown_waittime_list = ['Cooldown wait time:']
- # When running experiment on multiple DUTs cooldown wait time may vary
- # on different devices. In addition its combined time may exceed total
- # experiment time which will look weird but it is reasonable.
- # For this matter print cooldown time per DUT.
- for dut, waittime in sorted(self.GetTotalWaitCooldownTime().items()):
- cooldown_waittime_list.append('DUT %s: %d min' % (dut, waittime // 60))
- cooldown_waittime_str = '\n'.join(cooldown_waittime_list)
- sections.append(
- self._MakeSection('Duration',
- '\n\n'.join([totaltime_str,
- cooldown_waittime_str])))
-
- return '\n'.join(sections)
+class TextResultsReport(ResultsReport):
+ """Class to generate text result report."""
+
+ H1_STR = "==========================================="
+ H2_STR = "-------------------------------------------"
+
+ def __init__(self, results, email=False, experiment=None):
+ super(TextResultsReport, self).__init__(results)
+ self.email = email
+ self.experiment = experiment
+
+ @staticmethod
+ def _MakeTitle(title):
+ header_line = TextResultsReport.H1_STR
+ # '' at the end gives one newline.
+ return "\n".join([header_line, title, header_line, ""])
+
+ @staticmethod
+ def _MakeSection(title, body):
+ header_line = TextResultsReport.H2_STR
+ # '\n' at the end gives us two newlines.
+ return "\n".join([header_line, title, header_line, body, "\n"])
+
+ @staticmethod
+ def FromExperiment(experiment, email=False):
+ results = BenchmarkResults.FromExperiment(experiment)
+ return TextResultsReport(results, email, experiment)
+
+ def GetStatusTable(self):
+ """Generate the status table by the tabulator."""
+ table = [["", ""]]
+ columns = [
+ Column(LiteralResult(iteration=0), Format(), "Status"),
+ Column(LiteralResult(iteration=1), Format(), "Failing Reason"),
+ ]
+
+ for benchmark_run in self.experiment.benchmark_runs:
+ status = [
+ benchmark_run.name,
+ [
+ benchmark_run.timeline.GetLastEvent(),
+ benchmark_run.failure_reason,
+ ],
+ ]
+ table.append(status)
+ cell_table = TableFormatter(table, columns).GetCellTable("status")
+ return [cell_table]
+
+ def GetTotalWaitCooldownTime(self):
+ """Get cooldown wait time in seconds from experiment benchmark runs.
+
+ Returns:
+ Dictionary {'dut': int(wait_time_in_seconds)}
+ """
+ waittime_dict = {}
+ for dut in self.experiment.machine_manager.GetMachines():
+ waittime_dict[dut.name] = dut.GetCooldownWaitTime()
+ return waittime_dict
+
+ def GetReport(self):
+ """Generate the report for email and console."""
+ output_type = "EMAIL" if self.email else "CONSOLE"
+ experiment = self.experiment
+
+ sections = []
+ if experiment is not None:
+ title_contents = "Results report for '%s'" % (experiment.name,)
+ else:
+ title_contents = "Results report"
+ sections.append(self._MakeTitle(title_contents))
+
+ if not self.benchmark_results.cwp_dso:
+ summary_table = _PrintTable(self.GetSummaryTables(), output_type)
+ else:
+ summary_table = _PrintTable(
+ self.GetSummaryTables(summary_type="samples"), output_type
+ )
+ sections.append(self._MakeSection("Summary", summary_table))
+
+ if experiment is not None:
+ table = _PrintTable(self.GetStatusTable(), output_type)
+ sections.append(self._MakeSection("Benchmark Run Status", table))
+
+ if not self.benchmark_results.cwp_dso:
+ perf_table = _PrintTable(
+ self.GetSummaryTables(summary_type="perf"), output_type
+ )
+ sections.append(self._MakeSection("Perf Data", perf_table))
+
+ if experiment is not None:
+ experiment_file = experiment.experiment_file
+ sections.append(
+ self._MakeSection("Experiment File", experiment_file)
+ )
+
+ cpu_info = experiment.machine_manager.GetAllCPUInfo(
+ experiment.labels
+ )
+ sections.append(self._MakeSection("CPUInfo", cpu_info))
+
+ totaltime = (
+ (time.time() - experiment.start_time)
+ if experiment.start_time
+ else 0
+ )
+ totaltime_str = "Total experiment time:\n%d min" % (totaltime // 60)
+ cooldown_waittime_list = ["Cooldown wait time:"]
+ # When running experiment on multiple DUTs cooldown wait time may vary
+ # on different devices. In addition its combined time may exceed total
+ # experiment time which will look weird but it is reasonable.
+ # For this matter print cooldown time per DUT.
+ for dut, waittime in sorted(
+ self.GetTotalWaitCooldownTime().items()
+ ):
+ cooldown_waittime_list.append(
+ "DUT %s: %d min" % (dut, waittime // 60)
+ )
+ cooldown_waittime_str = "\n".join(cooldown_waittime_list)
+ sections.append(
+ self._MakeSection(
+ "Duration",
+ "\n\n".join([totaltime_str, cooldown_waittime_str]),
+ )
+ )
+
+ return "\n".join(sections)
def _GetHTMLCharts(label_names, test_results):
- charts = []
- for item, runs in test_results.items():
- # Fun fact: label_names is actually *entirely* useless as a param, since we
- # never add headers. We still need to pass it anyway.
- table = TableGenerator(runs, label_names).GetTable()
- columns = [
- Column(AmeanResult(), Format()),
- Column(MinResult(), Format()),
- Column(MaxResult(), Format())
- ]
- tf = TableFormatter(table, columns)
- data_table = tf.GetCellTable('full', headers=False)
-
- for cur_row_data in data_table:
- test_key = cur_row_data[0].string_value
- title = '{0}: {1}'.format(item, test_key.replace('/', ''))
- chart = ColumnChart(title, 300, 200)
- chart.AddColumn('Label', 'string')
- chart.AddColumn('Average', 'number')
- chart.AddColumn('Min', 'number')
- chart.AddColumn('Max', 'number')
- chart.AddSeries('Min', 'line', 'black')
- chart.AddSeries('Max', 'line', 'black')
- cur_index = 1
- for label in label_names:
- chart.AddRow([
- label, cur_row_data[cur_index].value,
- cur_row_data[cur_index + 1].value, cur_row_data[cur_index + 2].value
- ])
- if isinstance(cur_row_data[cur_index].value, str):
- chart = None
- break
- cur_index += 3
- if chart:
- charts.append(chart)
- return charts
+ charts = []
+ for item, runs in test_results.items():
+ # Fun fact: label_names is actually *entirely* useless as a param, since we
+ # never add headers. We still need to pass it anyway.
+ table = TableGenerator(runs, label_names).GetTable()
+ columns = [
+ Column(AmeanResult(), Format()),
+ Column(MinResult(), Format()),
+ Column(MaxResult(), Format()),
+ ]
+ tf = TableFormatter(table, columns)
+ data_table = tf.GetCellTable("full", headers=False)
+
+ for cur_row_data in data_table:
+ test_key = cur_row_data[0].string_value
+ title = "{0}: {1}".format(item, test_key.replace("/", ""))
+ chart = ColumnChart(title, 300, 200)
+ chart.AddColumn("Label", "string")
+ chart.AddColumn("Average", "number")
+ chart.AddColumn("Min", "number")
+ chart.AddColumn("Max", "number")
+ chart.AddSeries("Min", "line", "black")
+ chart.AddSeries("Max", "line", "black")
+ cur_index = 1
+ for label in label_names:
+ chart.AddRow(
+ [
+ label,
+ cur_row_data[cur_index].value,
+ cur_row_data[cur_index + 1].value,
+ cur_row_data[cur_index + 2].value,
+ ]
+ )
+ if isinstance(cur_row_data[cur_index].value, str):
+ chart = None
+ break
+ cur_index += 3
+ if chart:
+ charts.append(chart)
+ return charts
class HTMLResultsReport(ResultsReport):
- """Class to generate html result report."""
-
- def __init__(self, benchmark_results, experiment=None):
- super(HTMLResultsReport, self).__init__(benchmark_results)
- self.experiment = experiment
-
- @staticmethod
- def FromExperiment(experiment):
- return HTMLResultsReport(
- BenchmarkResults.FromExperiment(experiment), experiment=experiment)
-
- def GetReport(self):
- label_names = self.benchmark_results.label_names
- test_results = self.benchmark_results.run_keyvals
- charts = _GetHTMLCharts(label_names, test_results)
- chart_javascript = ''.join(chart.GetJavascript() for chart in charts)
- chart_divs = ''.join(chart.GetDiv() for chart in charts)
-
- if not self.benchmark_results.cwp_dso:
- summary_table = self.GetSummaryTables()
- perf_table = self.GetSummaryTables(summary_type='perf')
- else:
- summary_table = self.GetSummaryTables(summary_type='samples')
- perf_table = None
- full_table = self.GetFullTables()
-
- experiment_file = ''
- if self.experiment is not None:
- experiment_file = self.experiment.experiment_file
- # Use kwargs for code readability, and so that testing is a bit easier.
- return templates.GenerateHTMLPage(
- perf_table=perf_table,
- chart_js=chart_javascript,
- summary_table=summary_table,
- print_table=_PrintTable,
- chart_divs=chart_divs,
- full_table=full_table,
- experiment_file=experiment_file)
+ """Class to generate html result report."""
+
+ def __init__(self, benchmark_results, experiment=None):
+ super(HTMLResultsReport, self).__init__(benchmark_results)
+ self.experiment = experiment
+
+ @staticmethod
+ def FromExperiment(experiment):
+ return HTMLResultsReport(
+ BenchmarkResults.FromExperiment(experiment), experiment=experiment
+ )
+
+ def GetReport(self):
+ label_names = self.benchmark_results.label_names
+ test_results = self.benchmark_results.run_keyvals
+ charts = _GetHTMLCharts(label_names, test_results)
+ chart_javascript = "".join(chart.GetJavascript() for chart in charts)
+ chart_divs = "".join(chart.GetDiv() for chart in charts)
+
+ if not self.benchmark_results.cwp_dso:
+ summary_table = self.GetSummaryTables()
+ perf_table = self.GetSummaryTables(summary_type="perf")
+ else:
+ summary_table = self.GetSummaryTables(summary_type="samples")
+ perf_table = None
+ full_table = self.GetFullTables()
+
+ experiment_file = ""
+ if self.experiment is not None:
+ experiment_file = self.experiment.experiment_file
+ # Use kwargs for code readability, and so that testing is a bit easier.
+ return templates.GenerateHTMLPage(
+ perf_table=perf_table,
+ chart_js=chart_javascript,
+ summary_table=summary_table,
+ print_table=_PrintTable,
+ chart_divs=chart_divs,
+ full_table=full_table,
+ experiment_file=experiment_file,
+ )
def ParseStandardPerfReport(report_data):
- """Parses the output of `perf report`.
+ """Parses the output of `perf report`.
- It'll parse the following:
- {{garbage}}
- # Samples: 1234M of event 'foo'
+ It'll parse the following:
+ {{garbage}}
+ # Samples: 1234M of event 'foo'
- 1.23% command shared_object location function::name
+ 1.23% command shared_object location function::name
- 1.22% command shared_object location function2::name
+ 1.22% command shared_object location function2::name
- # Samples: 999K of event 'bar'
+ # Samples: 999K of event 'bar'
- 0.23% command shared_object location function3::name
- {{etc.}}
+ 0.23% command shared_object location function3::name
+ {{etc.}}
- Into:
- {'foo': {'function::name': 1.23, 'function2::name': 1.22},
- 'bar': {'function3::name': 0.23, etc.}}
- """
- # This function fails silently on its if it's handed a string (as opposed to a
- # list of lines). So, auto-split if we do happen to get a string.
- if isinstance(report_data, str):
- report_data = report_data.splitlines()
- # When switching to python3 catch the case when bytes are passed.
- elif isinstance(report_data, bytes):
- raise TypeError()
-
- # Samples: N{K,M,G} of event 'event-name'
- samples_regex = re.compile(r"#\s+Samples: \d+\S? of event '([^']+)'")
-
- # We expect lines like:
- # N.NN% command samples shared_object [location] symbol
- #
- # Note that we're looking at stripped lines, so there is no space at the
- # start.
- perf_regex = re.compile(r'^(\d+(?:.\d*)?)%' # N.NN%
- r'\s*\d+' # samples count (ignored)
- r'\s*\S+' # command (ignored)
- r'\s*\S+' # shared_object (ignored)
- r'\s*\[.\]' # location (ignored)
- r'\s*(\S.+)' # function
- )
-
- stripped_lines = (l.strip() for l in report_data)
- nonempty_lines = (l for l in stripped_lines if l)
- # Ignore all lines before we see samples_regex
- interesting_lines = itertools.dropwhile(lambda x: not samples_regex.match(x),
- nonempty_lines)
-
- first_sample_line = next(interesting_lines, None)
- # Went through the entire file without finding a 'samples' header. Quit.
- if first_sample_line is None:
- return {}
-
- sample_name = samples_regex.match(first_sample_line).group(1)
- current_result = {}
- results = {sample_name: current_result}
- for line in interesting_lines:
- samples_match = samples_regex.match(line)
- if samples_match:
- sample_name = samples_match.group(1)
- current_result = {}
- results[sample_name] = current_result
- continue
-
- match = perf_regex.match(line)
- if not match:
- continue
- percentage_str, func_name = match.groups()
- try:
- percentage = float(percentage_str)
- except ValueError:
- # Couldn't parse it; try to be "resilient".
- continue
- current_result[func_name] = percentage
- return results
+ Into:
+ {'foo': {'function::name': 1.23, 'function2::name': 1.22},
+ 'bar': {'function3::name': 0.23, etc.}}
+ """
+ # This function fails silently on its if it's handed a string (as opposed to a
+ # list of lines). So, auto-split if we do happen to get a string.
+ if isinstance(report_data, str):
+ report_data = report_data.splitlines()
+ # When switching to python3 catch the case when bytes are passed.
+ elif isinstance(report_data, bytes):
+ raise TypeError()
+
+ # Samples: N{K,M,G} of event 'event-name'
+ samples_regex = re.compile(r"#\s+Samples: \d+\S? of event '([^']+)'")
+
+ # We expect lines like:
+ # N.NN% command samples shared_object [location] symbol
+ #
+ # Note that we're looking at stripped lines, so there is no space at the
+ # start.
+ perf_regex = re.compile(
+ r"^(\d+(?:.\d*)?)%" # N.NN%
+ r"\s*\d+" # samples count (ignored)
+ r"\s*\S+" # command (ignored)
+ r"\s*\S+" # shared_object (ignored)
+ r"\s*\[.\]" # location (ignored)
+ r"\s*(\S.+)" # function
+ )
+
+ stripped_lines = (l.strip() for l in report_data)
+ nonempty_lines = (l for l in stripped_lines if l)
+ # Ignore all lines before we see samples_regex
+ interesting_lines = itertools.dropwhile(
+ lambda x: not samples_regex.match(x), nonempty_lines
+ )
+
+ first_sample_line = next(interesting_lines, None)
+ # Went through the entire file without finding a 'samples' header. Quit.
+ if first_sample_line is None:
+ return {}
+ sample_name = samples_regex.match(first_sample_line).group(1)
+ current_result = {}
+ results = {sample_name: current_result}
+ for line in interesting_lines:
+ samples_match = samples_regex.match(line)
+ if samples_match:
+ sample_name = samples_match.group(1)
+ current_result = {}
+ results[sample_name] = current_result
+ continue
+
+ match = perf_regex.match(line)
+ if not match:
+ continue
+ percentage_str, func_name = match.groups()
+ try:
+ percentage = float(percentage_str)
+ except ValueError:
+ # Couldn't parse it; try to be "resilient".
+ continue
+ current_result[func_name] = percentage
+ return results
-def _ReadExperimentPerfReport(results_directory, label_name, benchmark_name,
- benchmark_iteration):
- """Reads a perf report for the given benchmark. Returns {} on failure.
- The result should be a map of maps; it should look like:
- {perf_event_name: {function_name: pct_time_spent}}, e.g.
- {'cpu_cycles': {'_malloc': 10.0, '_free': 0.3, ...}}
- """
- raw_dir_name = label_name + benchmark_name + str(benchmark_iteration + 1)
- dir_name = ''.join(c for c in raw_dir_name if c.isalnum())
- file_name = os.path.join(results_directory, dir_name, 'perf.data.report.0')
- try:
- with open(file_name) as in_file:
- return ParseStandardPerfReport(in_file)
- except IOError:
- # Yes, we swallow any IO-related errors.
- return {}
+def _ReadExperimentPerfReport(
+ results_directory, label_name, benchmark_name, benchmark_iteration
+):
+ """Reads a perf report for the given benchmark. Returns {} on failure.
+
+ The result should be a map of maps; it should look like:
+ {perf_event_name: {function_name: pct_time_spent}}, e.g.
+ {'cpu_cycles': {'_malloc': 10.0, '_free': 0.3, ...}}
+ """
+ raw_dir_name = label_name + benchmark_name + str(benchmark_iteration + 1)
+ dir_name = "".join(c for c in raw_dir_name if c.isalnum())
+ file_name = os.path.join(results_directory, dir_name, "perf.data.report.0")
+ try:
+ with open(file_name) as in_file:
+ return ParseStandardPerfReport(in_file)
+ except IOError:
+ # Yes, we swallow any IO-related errors.
+ return {}
# Split out so that testing (specifically: mocking) is easier
def _ExperimentToKeyvals(experiment, for_json_report):
- """Converts an experiment to keyvals."""
- return OrganizeResults(
- experiment.benchmark_runs, experiment.labels, json_report=for_json_report)
+ """Converts an experiment to keyvals."""
+ return OrganizeResults(
+ experiment.benchmark_runs,
+ experiment.labels,
+ json_report=for_json_report,
+ )
class BenchmarkResults(object):
- """The minimum set of fields that any ResultsReport will take."""
-
- def __init__(self,
- label_names,
- benchmark_names_and_iterations,
- run_keyvals,
- ignore_min_max=False,
- read_perf_report=None,
- cwp_dso=None,
- weights=None):
- if read_perf_report is None:
-
- def _NoPerfReport(*_args, **_kwargs):
- return {}
-
- read_perf_report = _NoPerfReport
-
- self.label_names = label_names
- self.benchmark_names_and_iterations = benchmark_names_and_iterations
- self.iter_counts = dict(benchmark_names_and_iterations)
- self.run_keyvals = run_keyvals
- self.ignore_min_max = ignore_min_max
- self.read_perf_report = read_perf_report
- self.cwp_dso = cwp_dso
- self.weights = dict(weights) if weights else None
-
- @staticmethod
- def FromExperiment(experiment, for_json_report=False):
- label_names = [label.name for label in experiment.labels]
- benchmark_names_and_iterations = [(benchmark.name, benchmark.iterations)
- for benchmark in experiment.benchmarks]
- run_keyvals = _ExperimentToKeyvals(experiment, for_json_report)
- ignore_min_max = experiment.ignore_min_max
- read_perf_report = functools.partial(_ReadExperimentPerfReport,
- experiment.results_directory)
- cwp_dso = experiment.cwp_dso
- weights = [(benchmark.name, benchmark.weight)
- for benchmark in experiment.benchmarks]
- return BenchmarkResults(label_names, benchmark_names_and_iterations,
- run_keyvals, ignore_min_max, read_perf_report,
- cwp_dso, weights)
+ """The minimum set of fields that any ResultsReport will take."""
+
+ def __init__(
+ self,
+ label_names,
+ benchmark_names_and_iterations,
+ run_keyvals,
+ ignore_min_max=False,
+ read_perf_report=None,
+ cwp_dso=None,
+ weights=None,
+ ):
+ if read_perf_report is None:
+
+ def _NoPerfReport(*_args, **_kwargs):
+ return {}
+
+ read_perf_report = _NoPerfReport
+
+ self.label_names = label_names
+ self.benchmark_names_and_iterations = benchmark_names_and_iterations
+ self.iter_counts = dict(benchmark_names_and_iterations)
+ self.run_keyvals = run_keyvals
+ self.ignore_min_max = ignore_min_max
+ self.read_perf_report = read_perf_report
+ self.cwp_dso = cwp_dso
+ self.weights = dict(weights) if weights else None
+
+ @staticmethod
+ def FromExperiment(experiment, for_json_report=False):
+ label_names = [label.name for label in experiment.labels]
+ benchmark_names_and_iterations = [
+ (benchmark.name, benchmark.iterations)
+ for benchmark in experiment.benchmarks
+ ]
+ run_keyvals = _ExperimentToKeyvals(experiment, for_json_report)
+ ignore_min_max = experiment.ignore_min_max
+ read_perf_report = functools.partial(
+ _ReadExperimentPerfReport, experiment.results_directory
+ )
+ cwp_dso = experiment.cwp_dso
+ weights = [
+ (benchmark.name, benchmark.weight)
+ for benchmark in experiment.benchmarks
+ ]
+ return BenchmarkResults(
+ label_names,
+ benchmark_names_and_iterations,
+ run_keyvals,
+ ignore_min_max,
+ read_perf_report,
+ cwp_dso,
+ weights,
+ )
def _GetElemByName(name, from_list):
- """Gets an element from the given list by its name field.
+ """Gets an element from the given list by its name field.
- Raises an error if it doesn't find exactly one match.
- """
- elems = [e for e in from_list if e.name == name]
- if len(elems) != 1:
- raise ValueError('Expected 1 item named %s, found %d' % (name, len(elems)))
- return elems[0]
+ Raises an error if it doesn't find exactly one match.
+ """
+ elems = [e for e in from_list if e.name == name]
+ if len(elems) != 1:
+ raise ValueError(
+ "Expected 1 item named %s, found %d" % (name, len(elems))
+ )
+ return elems[0]
def _Unlist(l):
- """If l is a list, extracts the first element of l. Otherwise, returns l."""
- return l[0] if isinstance(l, list) else l
+ """If l is a list, extracts the first element of l. Otherwise, returns l."""
+ return l[0] if isinstance(l, list) else l
class JSONResultsReport(ResultsReport):
- """Class that generates JSON reports for experiments."""
-
- def __init__(self,
- benchmark_results,
- benchmark_date=None,
- benchmark_time=None,
- experiment=None,
- json_args=None):
- """Construct a JSONResultsReport.
-
- json_args is the dict of arguments we pass to json.dumps in GetReport().
- """
- super(JSONResultsReport, self).__init__(benchmark_results)
-
- defaults = TelemetryDefaults()
- defaults.ReadDefaultsFile()
- summary_field_defaults = defaults.GetDefault()
- if summary_field_defaults is None:
- summary_field_defaults = {}
- self.summary_field_defaults = summary_field_defaults
-
- if json_args is None:
- json_args = {}
- self.json_args = json_args
-
- self.experiment = experiment
- if not benchmark_date:
- timestamp = datetime.datetime.strftime(datetime.datetime.now(),
- '%Y-%m-%d %H:%M:%S')
- benchmark_date, benchmark_time = timestamp.split(' ')
- self.date = benchmark_date
- self.time = benchmark_time
-
- @staticmethod
- def FromExperiment(experiment,
- benchmark_date=None,
- benchmark_time=None,
- json_args=None):
- benchmark_results = BenchmarkResults.FromExperiment(
- experiment, for_json_report=True)
- return JSONResultsReport(benchmark_results, benchmark_date, benchmark_time,
- experiment, json_args)
-
- def GetReportObjectIgnoringExperiment(self):
- """Gets the JSON report object specifically for the output data.
-
- Ignores any experiment-specific fields (e.g. board, machine checksum, ...).
- """
- benchmark_results = self.benchmark_results
- label_names = benchmark_results.label_names
- summary_field_defaults = self.summary_field_defaults
- final_results = []
- for test, test_results in benchmark_results.run_keyvals.items():
- for label_name, label_results in zip(label_names, test_results):
- for iter_results in label_results:
- passed = iter_results.get('retval') == 0
- json_results = {
- 'date': self.date,
- 'time': self.time,
- 'label': label_name,
- 'test_name': test,
- 'pass': passed,
- }
- final_results.append(json_results)
-
- if not passed:
- continue
-
- # Get overall results.
- summary_fields = summary_field_defaults.get(test)
- if summary_fields is not None:
- value = []
- json_results['overall_result'] = value
- for f in summary_fields:
- v = iter_results.get(f)
- if v is None:
+ """Class that generates JSON reports for experiments."""
+
+ def __init__(
+ self,
+ benchmark_results,
+ benchmark_date=None,
+ benchmark_time=None,
+ experiment=None,
+ json_args=None,
+ ):
+ """Construct a JSONResultsReport.
+
+ json_args is the dict of arguments we pass to json.dumps in GetReport().
+ """
+ super(JSONResultsReport, self).__init__(benchmark_results)
+
+ defaults = TelemetryDefaults()
+ defaults.ReadDefaultsFile()
+ summary_field_defaults = defaults.GetDefault()
+ if summary_field_defaults is None:
+ summary_field_defaults = {}
+ self.summary_field_defaults = summary_field_defaults
+
+ if json_args is None:
+ json_args = {}
+ self.json_args = json_args
+
+ self.experiment = experiment
+ if not benchmark_date:
+ timestamp = datetime.datetime.strftime(
+ datetime.datetime.now(), "%Y-%m-%d %H:%M:%S"
+ )
+ benchmark_date, benchmark_time = timestamp.split(" ")
+ self.date = benchmark_date
+ self.time = benchmark_time
+
+ @staticmethod
+ def FromExperiment(
+ experiment, benchmark_date=None, benchmark_time=None, json_args=None
+ ):
+ benchmark_results = BenchmarkResults.FromExperiment(
+ experiment, for_json_report=True
+ )
+ return JSONResultsReport(
+ benchmark_results,
+ benchmark_date,
+ benchmark_time,
+ experiment,
+ json_args,
+ )
+
+ def GetReportObjectIgnoringExperiment(self):
+ """Gets the JSON report object specifically for the output data.
+
+ Ignores any experiment-specific fields (e.g. board, machine checksum, ...).
+ """
+ benchmark_results = self.benchmark_results
+ label_names = benchmark_results.label_names
+ summary_field_defaults = self.summary_field_defaults
+ final_results = []
+ for test, test_results in benchmark_results.run_keyvals.items():
+ for label_name, label_results in zip(label_names, test_results):
+ for iter_results in label_results:
+ passed = iter_results.get("retval") == 0
+ json_results = {
+ "date": self.date,
+ "time": self.time,
+ "label": label_name,
+ "test_name": test,
+ "pass": passed,
+ }
+ final_results.append(json_results)
+
+ if not passed:
+ continue
+
+ # Get overall results.
+ summary_fields = summary_field_defaults.get(test)
+ if summary_fields is not None:
+ value = []
+ json_results["overall_result"] = value
+ for f in summary_fields:
+ v = iter_results.get(f)
+ if v is None:
+ continue
+ # New telemetry results format: sometimes we get a list of lists
+ # now.
+ v = _Unlist(_Unlist(v))
+ value.append((f, float(v)))
+
+ # Get detailed results.
+ detail_results = {}
+ json_results["detailed_results"] = detail_results
+ for k, v in iter_results.items():
+ if (
+ k == "retval"
+ or k == "PASS"
+ or k == ["PASS"]
+ or v == "PASS"
+ ):
+ continue
+
+ v = _Unlist(v)
+ if "machine" in k:
+ json_results[k] = v
+ elif v is not None:
+ if isinstance(v, list):
+ detail_results[k] = [float(d) for d in v]
+ else:
+ detail_results[k] = float(v)
+ return final_results
+
+ def GetReportObject(self):
+ """Generate the JSON report, returning it as a python object."""
+ report_list = self.GetReportObjectIgnoringExperiment()
+ if self.experiment is not None:
+ self._AddExperimentSpecificFields(report_list)
+ return report_list
+
+ def _AddExperimentSpecificFields(self, report_list):
+ """Add experiment-specific data to the JSON report."""
+ board = self.experiment.labels[0].board
+ manager = self.experiment.machine_manager
+ for report in report_list:
+ label_name = report["label"]
+ label = _GetElemByName(label_name, self.experiment.labels)
+
+ img_path = os.path.realpath(
+ os.path.expanduser(label.chromeos_image)
+ )
+ ver, img = ParseChromeosImage(img_path)
+
+ report.update(
+ {
+ "board": board,
+ "chromeos_image": img,
+ "chromeos_version": ver,
+ "chrome_version": label.chrome_version,
+ "compiler": label.compiler,
+ }
+ )
+
+ if not report["pass"]:
continue
- # New telemetry results format: sometimes we get a list of lists
- # now.
- v = _Unlist(_Unlist(v))
- value.append((f, float(v)))
-
- # Get detailed results.
- detail_results = {}
- json_results['detailed_results'] = detail_results
- for k, v in iter_results.items():
- if k == 'retval' or k == 'PASS' or k == ['PASS'] or v == 'PASS':
- continue
-
- v = _Unlist(v)
- if 'machine' in k:
- json_results[k] = v
- elif v is not None:
- if isinstance(v, list):
- detail_results[k] = [float(d) for d in v]
- else:
- detail_results[k] = float(v)
- return final_results
-
- def GetReportObject(self):
- """Generate the JSON report, returning it as a python object."""
- report_list = self.GetReportObjectIgnoringExperiment()
- if self.experiment is not None:
- self._AddExperimentSpecificFields(report_list)
- return report_list
-
- def _AddExperimentSpecificFields(self, report_list):
- """Add experiment-specific data to the JSON report."""
- board = self.experiment.labels[0].board
- manager = self.experiment.machine_manager
- for report in report_list:
- label_name = report['label']
- label = _GetElemByName(label_name, self.experiment.labels)
-
- img_path = os.path.realpath(os.path.expanduser(label.chromeos_image))
- ver, img = ParseChromeosImage(img_path)
-
- report.update({
- 'board': board,
- 'chromeos_image': img,
- 'chromeos_version': ver,
- 'chrome_version': label.chrome_version,
- 'compiler': label.compiler
- })
-
- if not report['pass']:
- continue
- if 'machine_checksum' not in report:
- report['machine_checksum'] = manager.machine_checksum[label_name]
- if 'machine_string' not in report:
- report['machine_string'] = manager.machine_checksum_string[label_name]
-
- def GetReport(self):
- """Dump the results of self.GetReportObject() to a string as JSON."""
- # This exists for consistency with the other GetReport methods.
- # Specifically, they all return strings, so it's a bit awkward if the JSON
- # results reporter returns an object.
- return json.dumps(self.GetReportObject(), **self.json_args)
+ if "machine_checksum" not in report:
+ report["machine_checksum"] = manager.machine_checksum[
+ label_name
+ ]
+ if "machine_string" not in report:
+ report["machine_string"] = manager.machine_checksum_string[
+ label_name
+ ]
+
+ def GetReport(self):
+ """Dump the results of self.GetReportObject() to a string as JSON."""
+ # This exists for consistency with the other GetReport methods.
+ # Specifically, they all return strings, so it's a bit awkward if the JSON
+ # results reporter returns an object.
+ return json.dumps(self.GetReportObject(), **self.json_args)