blob: 3203498994077e53209c33c94e3d7b1983a34ae2 [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Converts crossbench result into histogram format.
See example inputs in testdata/crossbench_output folder.
"""
import argparse
import csv
import json
import pathlib
import sys
from typing import List, Optional
tracing_dir = (pathlib.Path(__file__).absolute().parents[2] /
'third_party/catapult/tracing')
sys.path.append(str(tracing_dir))
from tracing.value import histogram, histogram_set
from tracing.value.diagnostics import generic_set
from tracing.value.diagnostics import reserved_infos
def _get_crossbench_json_paths(out_dir: pathlib.Path) -> List[pathlib.Path]:
"""Given a crossbench output directory, find the result json file.
Args:
out_dir: Crossbench output directory. This should be the value passed
as --out-dir to crossbench.
Returns:
A list of paths to the result json files created by crossbench probes.
"""
if not out_dir.exists():
raise FileNotFoundError(
f'Crossbench output directory does not exist: {out_dir}')
cb_results_json_path = out_dir / 'cb.results.json'
if not cb_results_json_path.exists():
raise FileNotFoundError(
f'Missing crossbench results file: {cb_results_json_path}')
debug_info = ''
with cb_results_json_path.open() as f:
results_info = json.load(f)
debug_info += f'results_info={results_info}\n'
browsers = results_info.get('browsers', {})
if len(browsers) != 1:
raise ValueError(
f'Expected to have one "browsers" in {cb_results_json_path}, '
f'debug_info={debug_info}')
browser_info = list(browsers.values())[0]
debug_info += f'browser_info={browser_info}\n'
probe_json_paths = []
try:
for probe, probe_data in browser_info.get('probes', {}).items():
if probe.startswith('cb.') or not probe_data:
continue
candidates = probe_data.get('json', [])
if len(candidates) > 1:
raise ValueError(f'Probe {probe} generated multiple json files, '
f'debug_info={debug_info}')
if len(candidates) == 1:
probe_json_paths.append(pathlib.Path(candidates[0]))
except AttributeError as e:
raise AttributeError(f'debug_info={debug_info}') from e
if not probe_json_paths:
raise ValueError(f'No output json file found in {cb_results_json_path}, '
f'debug_info={debug_info}')
return probe_json_paths
def convert(crossbench_out_dir: pathlib.Path,
out_filename: pathlib.Path,
benchmark: Optional[str] = None,
story: Optional[str] = None,
results_label: Optional[str] = None) -> None:
"""Do the conversion of crossbench output into histogram format.
Args: See the help strings passed to argparse.ArgumentParser.
"""
if benchmark and benchmark.startswith('loadline'):
_loadline(crossbench_out_dir, out_filename, benchmark, results_label)
return
crossbench_json_filenames = _get_crossbench_json_paths(crossbench_out_dir)
crossbench_result = {}
for filename in crossbench_json_filenames:
with filename.open() as f:
crossbench_result.update(json.load(f))
results = histogram_set.HistogramSet()
for key, value in crossbench_result.items():
metric = None
key_parts = key.split('/')
if len(key_parts) == 1:
if key.startswith('Iteration') or key == 'Geomean':
continue
metric = key
if key.lower() == 'score':
unit = 'unitless_biggerIsBetter'
else:
unit = 'ms_smallerIsBetter'
else:
if len(key_parts) == 2 and key_parts[1] == 'total':
metric = key_parts[0]
unit = 'ms_smallerIsBetter'
elif len(key_parts) == 2 and key_parts[1] == 'score':
metric = key_parts[0]
unit = 'unitless_biggerIsBetter'
if metric:
data_point = histogram.Histogram.Create(metric, unit, value['values'])
results.AddHistogram(data_point)
if benchmark:
results.AddSharedDiagnosticToAllHistograms(
reserved_infos.BENCHMARKS.name, generic_set.GenericSet([benchmark]))
if story:
results.AddSharedDiagnosticToAllHistograms(
reserved_infos.STORIES.name, generic_set.GenericSet([story]))
if results_label:
results.AddSharedDiagnosticToAllHistograms(
reserved_infos.LABELS.name, generic_set.GenericSet([results_label]))
with out_filename.open('w') as f:
json.dump(results.AsDicts(), f)
def _loadline(crossbench_out_dir: pathlib.Path,
out_filename: pathlib.Path,
benchmark: Optional[str] = None,
results_label: Optional[str] = None) -> None:
"""Converts `loadline-*` benchmarks."""
crossbench_json_filename = crossbench_out_dir / 'loadline_probe.csv'
if not crossbench_json_filename.exists():
raise FileNotFoundError(
f'Missing crossbench results file: {crossbench_json_filename}')
with crossbench_json_filename.open() as f:
crossbench_result = next(csv.DictReader(f))
results = histogram_set.HistogramSet()
for key, value in crossbench_result.items():
data_point = None
if key == 'browser':
results.AddSharedDiagnosticToAllHistograms(
key, generic_set.GenericSet([value]))
else:
data_point = histogram.Histogram.Create(key, 'unitless_biggerIsBetter',
float(value))
if data_point:
results.AddHistogram(data_point)
if benchmark:
results.AddSharedDiagnosticToAllHistograms(
reserved_infos.BENCHMARKS.name, generic_set.GenericSet([benchmark]))
if results_label:
results.AddSharedDiagnosticToAllHistograms(
reserved_infos.LABELS.name, generic_set.GenericSet([results_label]))
with out_filename.open('w') as f:
json.dump(results.AsDicts(), f)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('crossbench_out_dir',
type=pathlib.Path,
help='value of --out-dir passed to crossbench')
parser.add_argument('out_filename',
type=pathlib.Path,
help='name of output histogram file to generate')
parser.add_argument('--benchmark', help='name of the benchmark')
parser.add_argument('--story', help='name of the story')
args = parser.parse_args()
convert(args.crossbench_out_dir,
args.out_filename,
benchmark=args.benchmark,
story=args.story)
if __name__ == '__main__':
sys.exit(main())