blob: 16670459638e56320838b6f4aa32629a4fa880d6 [file]
# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import annotations
# pylint: disable=too-many-lines
import csv
import os
import pathlib
import io
import shlex
import urllib.parse
from typing import Callable, Final, Iterable, Optional, Union
from core import benchmark_finders
from core import benchmark_utils
from telemetry.story import story_filter
_SHARD_MAP_DIR = os.path.join(os.path.dirname(__file__), 'shard_maps')
_ALL_BENCHMARKS_BY_NAMES = dict(
(b.Name(), b) for b in benchmark_finders.GetAllBenchmarks())
GTEST_STORY_NAME = '_gtest_'
def _IsPlatformSupported(benchmark, platform: str) -> bool:
supported = benchmark.GetSupportedPlatformNames(benchmark.SUPPORTED_PLATFORMS)
return 'all' in supported or platform in supported
class _PerfPlatform(object):
def __init__(self,
name: str,
description: str,
benchmark_configs: PerfSuite,
num_shards: int,
platform_os: str,
is_fyi: bool = False,
run_reference_build: bool = False,
pinpoint_only: bool = False,
executables: Optional[frozenset[ExecutableConfig]] = None,
crossbench: Optional[frozenset[CrossbenchConfig]] = None):
benchmark_config_set = benchmark_configs.Frozenset()
self._name = name
self._description = description
self._platform_os = platform_os
# For sorting ignore case and 'segments' in the bot name.
self._sort_key = name.lower().replace('-', ' ')
self._is_fyi = is_fyi
self.run_reference_build = run_reference_build
self.pinpoint_only = pinpoint_only
self.executables = executables or frozenset()
self.crossbench = crossbench or frozenset()
assert num_shards
self._num_shards = num_shards
# pylint: disable=redefined-outer-name
self._benchmark_configs: frozenset[TelemetryConfig] = frozenset([
b for b in benchmark_config_set
if _IsPlatformSupported(b.benchmark, self._platform_os)
])
# pylint: enable=redefined-outer-name
benchmark_names = [config.name for config in self._benchmark_configs]
assert len(set(benchmark_names)) == len(benchmark_names), (
'Make sure that a benchmark does not appear twice.')
base_file_name = name.replace(' ', '_').lower()
self._timing_file_path = os.path.join(
_SHARD_MAP_DIR, 'timing_data', base_file_name + '_timing.json')
self.shards_map_file_name = base_file_name + '_map.json'
self._shards_map_file_path = os.path.join(
_SHARD_MAP_DIR, self.shards_map_file_name)
def __lt__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
# pylint: disable=protected-access
return self._sort_key < other._sort_key
@property
def num_shards(self) -> int:
return self._num_shards
@property
def shards_map_file_path(self) -> str:
return self._shards_map_file_path
@property
def timing_file_path(self) -> str:
return self._timing_file_path
@property
def name(self) -> str:
return self._name
@property
def description(self) -> str:
return self._description
@property
def platform(self) -> str:
return self._platform_os
@property
def benchmarks_to_run(self) -> frozenset[TelemetryConfig]:
# TODO(crbug.com/40628256): Deprecate this in favor of benchmark_configs
# as part of change to make sharding scripts accommodate abridged
# benchmarks.
return frozenset({b.benchmark for b in self._benchmark_configs})
@property
def benchmark_configs(self) -> frozenset[TelemetryConfig]:
return self._benchmark_configs
@property
def is_fyi(self) -> bool:
return self._is_fyi
@property
def is_official(self) -> bool:
return not self._is_fyi
@property
def builder_url(self) -> Optional[str]:
if self.pinpoint_only:
return None
return ('https://ci.chromium.org/p/chrome/builders/ci/%s' %
urllib.parse.quote(self._name))
class BenchmarkConfig(object):
def __init__(self,
name: str,
repeat: int = 1,
flags: tuple[str, ...] = ()) -> None:
self.name: Final[str] = name
self.repeat: Final[int] = repeat
self.flags: Final[tuple[str, ...]] = flags
class TelemetryConfig(BenchmarkConfig):
def __init__(self,
benchmark,
abridged: bool = False,
repeat: Optional[int] = None):
"""A configuration for a benchmark that helps decide how to shard it.
Args:
benchmark: the benchmark.Benchmark object.
abridged: True if the benchmark should be abridged so fewer stories
are run, and False if the whole benchmark should be run.
repeat: number of times to repeat the entire story set.
can be None, which defaults to the benchmark default pageset_repeat.
"""
self.benchmark = benchmark
super().__init__(benchmark.Name(), self.get_repeat(repeat))
self.abridged: Final[bool] = abridged
self._stories: Optional[tuple[str, ...]] = None
self._exhaustive_stories: Optional[tuple[str, ...]] = None
def get_repeat(self, repeat: Optional[int]) -> int:
if repeat is not None:
return repeat
return self.benchmark.options.get('pageset_repeat', 1)
@property
def stories(self) -> tuple[str, ...]:
if self._stories is not None:
return self._stories
story_set = benchmark_utils.GetBenchmarkStorySet(self.benchmark())
abridged_story_set_tag = (story_set.GetAbridgedStorySetTagFilter()
if self.abridged else None)
story_filter_obj = story_filter.StoryFilter(
abridged_story_set_tag=abridged_story_set_tag)
stories = story_filter_obj.FilterStories(story_set)
self._stories = tuple(story.name for story in stories)
return self._stories
@property
def exhaustive_stories(self) -> tuple[str, ...]:
if self._exhaustive_stories is not None:
return self._exhaustive_stories
story_set = benchmark_utils.GetBenchmarkStorySet(self.benchmark(),
exhaustive=True)
abridged_story_set_tag = (story_set.GetAbridgedStorySetTagFilter()
if self.abridged else None)
story_filter_obj = story_filter.StoryFilter(
abridged_story_set_tag=abridged_story_set_tag)
stories = story_filter_obj.FilterStories(story_set)
self._exhaustive_stories = tuple(story.name for story in stories)
return self._exhaustive_stories
class ExecutableConfig(BenchmarkConfig):
def __init__(self,
name: str,
path: Optional[str] = None,
flags: tuple[str, ...] = (),
estimated_runtime: int = 60,
repeat: int = 1):
super().__init__(name, repeat, flags)
self.path: Final[str] = path or name
self.estimated_runtime: Final[int] = estimated_runtime
self.abridged: Final[bool] = False
self.stories: Final[tuple[str, ...]] = (GTEST_STORY_NAME, )
class CrossbenchConfig(BenchmarkConfig):
def __init__(self,
name: str,
crossbench_name: str,
estimated_runtime: int = 60,
stories: Optional[tuple[str]] = None,
flags: tuple[str, ...] = (),
repeat: int = 1,
auto_enable_field_trials: bool = True):
flags = self._process_flags(flags, auto_enable_field_trials)
super().__init__(name, repeat, flags)
self.crossbench_name: Final[str] = crossbench_name
self.estimated_runtime: Final[int] = estimated_runtime
self.stories: Final[tuple[str, ...]] = stories or ('default', )
def _process_flags(self, flags: tuple[str, ...],
auto_enable_field_trials: bool) -> tuple[str, ...]:
if auto_enable_field_trials:
# Somewhat hacky solution if we want to run without field trials.
if "--disable-field-trials" not in flags and (
"--disable-field-trial-config" not in flags):
flags += ("--enable-field-trials", )
assert len(flags) == len(
set(flags)), (f"Found duplicate arguments in {flags}")
return flags
class PerfSuite(object):
def __init__(self, configs):
self._configs: dict[str, TelemetryConfig] = dict()
self.Add(configs)
def Frozenset(self) -> frozenset[TelemetryConfig]:
return frozenset(self._configs.values())
def Add(
self, configs: Union[Iterable[Union[str, TelemetryConfig]], PerfSuite]
) -> PerfSuite:
if isinstance(configs, PerfSuite):
configs = configs.Frozenset()
for config in configs:
if isinstance(config, str):
config = _TelemetryConfig(config)
if config.name in self._configs:
raise ValueError('Cannot have duplicate benchmarks/executables.')
self._configs[config.name] = config
return self
def Remove(self, config_names: Iterable[str]) -> PerfSuite:
for name in config_names:
del self._configs[name]
return self
def Abridge(self, config_names: Iterable[str]) -> PerfSuite:
for name in config_names:
del self._configs[name]
self._configs[name] = _TelemetryConfig(name, abridged=True)
return self
def Repeat(self, config_names: Iterable[str],
pageset_repeat: int) -> PerfSuite:
for name in config_names:
self._configs[name] = _TelemetryConfig(
name,
abridged=self._configs[name].abridged,
pageset_repeat=pageset_repeat)
return self
def _TelemetryConfig(benchmark_name: str,
abridged: bool = False,
pageset_repeat: int | None = None):
benchmark = _ALL_BENCHMARKS_BY_NAMES[benchmark_name]
return TelemetryConfig(benchmark, abridged, pageset_repeat)
BenchmarkConfigFactory = Callable[..., BenchmarkConfig]
_BENCHMARKS_CONFIG_FACTORIES: dict[str, BenchmarkConfigFactory] = {}
for b in _ALL_BENCHMARKS_BY_NAMES:
_BENCHMARKS_CONFIG_FACTORIES[b] = _TelemetryConfig
def _register(name):
def _decorator(func):
if name in _BENCHMARKS_CONFIG_FACTORIES:
raise ValueError('Duplicate benchmark config: %s' % name)
_BENCHMARKS_CONFIG_FACTORIES[name] = func
return func
return _decorator
@_register('sync_performance_tests')
def _sync_performance_tests(estimated_runtime: int = 110,
path=None,
flags: tuple[str, ...] = ()):
return ExecutableConfig('sync_performance_tests',
path=path,
flags=('--test-launcher-jobs=1',
'--test-launcher-retry-limit=0', *flags),
estimated_runtime=estimated_runtime)
@_register('base_perftests')
def _base_perftests(estimated_runtime: int = 270,
path=None,
flags: tuple[str, ...] = ()):
return ExecutableConfig('base_perftests',
path=path,
flags=('--test-launcher-jobs=1',
'--test-launcher-retry-limit=0', *flags),
estimated_runtime=estimated_runtime)
@_register('components_perftests')
def _components_perftests(estimated_runtime: int = 110,
flags: tuple[str, ...] = ()):
return ExecutableConfig('components_perftests',
flags=('--xvfb', *flags),
estimated_runtime=estimated_runtime)
@_register('dawn_perf_tests')
def _dawn_perf_tests(estimated_runtime: int = 270, flags: tuple[str, ...] = ()):
return ExecutableConfig('dawn_perf_tests',
flags=('--test-launcher-jobs=1',
'--test-launcher-retry-limit=0', *flags),
estimated_runtime=estimated_runtime)
@_register('tint_benchmark')
def _tint_benchmark(estimated_runtime: int = 180, flags: tuple[str, ...] = ()):
return ExecutableConfig('tint_benchmark',
flags=('--use-chrome-perf-format', *flags),
estimated_runtime=estimated_runtime)
@_register('load_library_perf_tests')
def _load_library_perf_tests(estimated_runtime: int = 3,
flags: tuple[str, ...] = ()):
return ExecutableConfig('load_library_perf_tests',
flags=flags,
estimated_runtime=estimated_runtime)
@_register('performance_browser_tests')
def _performance_browser_tests(estimated_runtime: int = 67,
flags: tuple[str, ...] = ()):
return ExecutableConfig(
'performance_browser_tests',
path='browser_tests',
flags=(
'--full-performance-run',
'--test-launcher-jobs=1',
'--test-launcher-retry-limit=0',
# Allow the full performance runs to take up to 60 seconds (rather
# than the default of 30 for normal CQ browser test runs).
'--ui-test-action-timeout=60000',
'--ui-test-action-max-timeout=60000',
'--test-launcher-timeout=60000',
'--gtest_filter=*/TabCapturePerformanceTest.*:'
'*/CastV2PerformanceTest.*',
*flags),
estimated_runtime=estimated_runtime)
@_register('tracing_perftests')
def _tracing_perftests(estimated_runtime: int = 5, flags: tuple[str, ...] = ()):
return ExecutableConfig('tracing_perftests',
flags=flags,
estimated_runtime=estimated_runtime)
@_register('views_perftests')
def _views_perftests(estimated_runtime: int = 7, flags: tuple[str, ...] = ()):
return ExecutableConfig('views_perftests',
flags=('--xvfb', *flags),
estimated_runtime=estimated_runtime)
@_register('web_tests_cuj')
def _web_tests_cuj(estimated_runtime: int = 10, flags: tuple[str, ...] = ()):
return CrossbenchConfig('web_tests_cuj',
'speedometer_3.1',
estimated_runtime=estimated_runtime,
flags=('--web-tests-cuj', '--debug', *flags))
# Speedometer:
@_register('speedometer2.0.crossbench')
def _speedometer2_0_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('speedometer2.0.crossbench',
'speedometer_2.0',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('speedometer2.1.crossbench')
def _speedometer2_1_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('speedometer2.1.crossbench',
'speedometer_2.1',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('speedometer2.crossbench')
def _speedometer2_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
"""Alias for the latest Speedometer 2.X version."""
return CrossbenchConfig('speedometer2.crossbench',
'speedometer_2',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('speedometer3.0.crossbench')
def _speedometer3_0_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('speedometer3.0.crossbench',
'speedometer_3.0',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('speedometer3.1.crossbench')
def _speedometer3_1_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('speedometer3.1.crossbench',
'speedometer_3.1',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('speedometer3.crossbench')
def _speedometer3_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
"""Alias for the latest Speedometer 3.X version."""
return CrossbenchConfig('speedometer3.crossbench',
'speedometer_3',
estimated_runtime=estimated_runtime,
flags=flags)
@_register("speedometer3-turbolev.crossbench")
def _speedometer3_turbolev_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
flags += ("--js-flags=--turbolev", )
return CrossbenchConfig(
"speedometer3-turbolev.crossbench",
"speedometer_3",
estimated_runtime=estimated_runtime,
flags=flags,
)
@_register("speedometer3-turbolev_future.crossbench")
def _speedometer3_turbolev_future_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
flags += ("--js-flags=--turbolev-future", )
return CrossbenchConfig(
"speedometer3-turbolev_future.crossbench",
"speedometer_3",
estimated_runtime=estimated_runtime,
flags=flags,
)
@_register('speedometer_main.crossbench')
def _speedometer_main_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
"""The latest WIP speedometer version running all stories."""
return CrossbenchConfig('speedometer_main.crossbench',
'speedometer_main',
estimated_runtime=estimated_runtime,
flags=('--detailed-metrics', *flags))
@_register('speedometer_main.all.crossbench')
def _speedometer_main_all_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
"""The latest WIP speedometer version running all stories,
including experimental"""
return CrossbenchConfig('speedometer_main.all.crossbench',
'speedometer_main',
estimated_runtime=estimated_runtime,
flags=('--stories=all', '--detailed-metrics', *flags))
@_register('browser_startup.crossbench')
def _browser_startup_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
"""Browser startup benchmark for InitialWebUI vs Baseline."""
# We cannot use --browser-config here because it conflicts with the
# --browser flag automatically added by the Chromium test runner.
# NOTE: Keep this list in sync with:
# third_party/crossbench/config/benchmark/browser_startup/browser.config.hjson
INITIAL_WEBUI_FEATURES = (
"InitialWebUI:high_stream_priority/true,"
"WebUIReloadButton:WebUIReloadButtonDeferBrowserViewShow/true/"
"WebUIReloadButtonKeepVisibleUntilPaint/true/"
"WebUIReloadButtonPrewarmWebUI/true,"
"SkipIPCChannelPausingForNonGuests,WebUIInProcessResourceLoadingV2,"
"InitialWebUISyncNavStartToCommit,InitialWebUIWithoutExtensions,"
"WebUIBundledCodeCache,SendGPUChannelEarly"
)
return CrossbenchConfig('browser_startup.crossbench',
'browser-startup',
estimated_runtime=estimated_runtime,
flags=(f'--enable-features={INITIAL_WEBUI_FEATURES}',
*flags),
repeat=10)
@_register('speedometer3.a11y.crossbench')
def _speedometer3_a11y_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
"""Latest Speedometer 3 with accessibility flag enabled."""
return CrossbenchConfig(
'speedometer3.a11y.crossbench',
'speedometer_3',
estimated_runtime=estimated_runtime,
flags=('--extra-browser-args=--force-renderer-accessibility', *flags))
# MotionMark:
@_register('motionmark1.2.crossbench')
def _motionmark1_2_crossbench(estimated_runtime: int = 360,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('motionmark1.2.crossbench',
'motionmark_1.2',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('motionmark1.3.0.crossbench')
def _motionmark1_3_0_crossbench(estimated_runtime: int = 360,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('motionmark1.3.0.crossbench',
'motionmark_1.3.0',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('motionmark1.3.1.crossbench')
def _motionmark1_3_1_crossbench(estimated_runtime: int = 360,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('motionmark1.3.1.crossbench',
'motionmark_1.3.1',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('motionmark1.3.crossbench')
def _motionmark1_3_crossbench(estimated_runtime: int = 360,
flags: tuple[str, ...] = ()):
"""Alias for the latest MotionMark 1.3.X version."""
return CrossbenchConfig('motionmark1.3.crossbench',
'motionmark_1.3',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('motionmark1.3-turbolev.crossbench')
def _motionmark1_3_turbolev_crossbench(estimated_runtime: int = 360,
flags: tuple[str, ...] = ()):
flags += ('--js-flags=--turbolev', )
return CrossbenchConfig('motionmark1.3-turbolev.crossbench',
'motionmark_1.3',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('motionmark_main.crossbench')
def _motionmark_main_crossbench(estimated_runtime: int = 360,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('motionmark_main.crossbench',
'motionmark_main',
estimated_runtime=estimated_runtime,
flags=flags)
# JetStream:
@_register('jetstream2.0.crossbench')
def _jetstream2_0_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('jetstream2.0.crossbench',
'jetstream_2.0',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('jetstream2.1.crossbench')
def _jetstream2_1_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('jetstream2.1.crossbench',
'jetstream_2.1',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('jetstream2.2.crossbench')
def _jetstream2_2_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('jetstream2.2.crossbench',
'jetstream_2.2',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('jetstream2.crossbench')
def _jetstream2_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
"""Alias of the latest JetStream 2.X version."""
return CrossbenchConfig('jetstream2.crossbench',
'jetstream_2',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('jetstream3.0.crossbench')
def _jetstream3_0_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('jetstream3_0.crossbench',
'jetstream_3.0',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('jetstream3.crossbench')
def _jetstream3_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('jetstream3.crossbench',
'jetstream_3',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('jetstream_main.crossbench')
def _jetstream_main_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('jetstream_main.crossbench',
'jetstream_main',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('jetstream3-turbolev.crossbench')
def _jetstream3_turbolev_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
flags += ('--js-flags=--turbolev', )
return CrossbenchConfig('jetstream3-turbolev.crossbench',
'jetstream_3',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('jetstream3-turbolev_future.crossbench')
def _jetstream3_turbolev_future_crossbench(estimated_runtime: int = 180,
flags: tuple[str, ...] = ()):
flags += ('--js-flags=--turbolev-future', )
return CrossbenchConfig('jetstream3-turbolev_future.crossbench',
'jetstream_3',
estimated_runtime=estimated_runtime,
flags=flags)
# LoadLine:
@_register('loadline_phone.crossbench')
def _loadline_phone_crossbench(estimated_runtime: int = 7000,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('loadline_phone.crossbench',
'loadline-phone-fast',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('loadline_tablet.crossbench')
def _loadline_tablet_crossbench(estimated_runtime: int = 3600,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('loadline_tablet.crossbench',
'loadline-tablet-fast',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('loadline2_phone.crossbench')
def _loadline2_phone_crossbench(estimated_runtime: int = 1000,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('loadline2_phone.crossbench',
'loadline2-phone',
estimated_runtime=estimated_runtime,
flags=flags)
# Webview:
@_register('loading.crossbench')
def _crossbench_loading(estimated_runtime: int = 750,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('loading.crossbench',
'loading',
estimated_runtime=estimated_runtime,
flags=flags,
auto_enable_field_trials=False)
@_register('embedder.crossbench')
def _crossbench_embedder(estimated_runtime: int = 900,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('embedder.crossbench',
'embedder',
estimated_runtime=estimated_runtime,
flags=flags,
auto_enable_field_trials=False)
@_register('gma.embedder.crossbench')
def _crossbench_gma_embedder(estimated_runtime: int = 900,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('gma.embedder.crossbench',
'embedder',
estimated_runtime=estimated_runtime,
flags=flags,
auto_enable_field_trials=False)
@_register('shell.embedder.crossbench')
def _crossbench_shell_embedder(estimated_runtime: int = 900,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('shell.embedder.crossbench',
'embedder',
estimated_runtime=estimated_runtime,
flags=flags,
auto_enable_field_trials=False)
@_register('devtools_frontend.crossbench')
def _devtools_frontend_crossbench(estimated_runtime: int = 60,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('devtools_frontend.crossbench',
'devtools_frontend',
estimated_runtime=estimated_runtime,
flags=flags)
@_register('blink-ai.crossbench')
def _crossbench_blink_ai(estimated_runtime: int = 300,
flags: tuple[str, ...] = ()):
return CrossbenchConfig('blink-ai.crossbench',
'blink-ai',
estimated_runtime=estimated_runtime,
stories=('language_model', ),
flags=flags,
auto_enable_field_trials=False)
PLATFORM_INFO = {
'linux-perf': {
'description': ('Ubuntu-22.04, Precision 3930 Rack, '
'NVIDIA GeForce GTX 1660'),
'num_shards':
4,
'platform_os':
'linux',
'is_fyi':
False
},
'linux-perf-pgo': {
'description': 'Ubuntu-18.04, 8 core, NVIDIA Quadro P400',
'num_shards': 26,
'platform_os': 'linux',
'is_fyi': False,
'pinpoint_only': True
},
'linux-perf-rel': {
'description': 'Ubuntu-18.04, 8 core, NVIDIA Quadro P400',
'num_shards': 2,
'platform_os': 'linux',
'is_fyi': False
},
'linux-r350-perf': {
'description': 'Ubuntu-22.04, 16 core',
'num_shards': 30,
'platform_os': 'linux',
'is_fyi': False
},
'linux-falcon-rak-5070-perf': {
'description': 'Linux Falcon RAK 5070',
'num_shards': 1,
'platform_os': 'linux',
'is_fyi': False
},
'mac-intel-perf': {
'description': 'Mac Mini 8,1, Core i7 3.2 GHz',
'num_shards': 24,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m1_mini_2020-perf': {
'description': 'Mac M1 Mini 2020',
'num_shards': 28,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m1_mini_2020-perf-pgo': {
'description': 'Mac M1 Mini 2020',
'num_shards': 7,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m1_mini_2020-no-brp-perf': {
'description': 'Mac M1 Mini 2020 with BRP disabled',
'num_shards': 20,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m1-pro-perf': {
'description': 'Mac M1 PRO 2020',
'num_shards': 4,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m2-pro-perf': {
'description': 'Mac M2 PRO Baremetal ARM',
'num_shards': 20,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m3-pro-perf': {
'description': 'Mac M3 PRO ARM',
'num_shards': 4,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m4-mini-perf': {
'description': 'Mac M4 mini ARM',
'num_shards': 25,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m4-pro-perf': {
'description': 'MacBook Pro M4 ARM',
'num_shards': 15,
'platform_os': 'mac',
'is_fyi': False
},
'mac-m5-pro-perf': {
'description': 'Mac M5 PRO ARM',
'num_shards': 2,
'platform_os': 'mac',
'is_fyi': False
},
'win-10_laptop_low_end-perf': {
'description': ('Low end windows 10 HP laptops. HD Graphics 5500, '
'x86-64-i3-5005U, SSD, 4GB RAM.'),
'num_shards':
15,
'platform_os':
'win',
'is_fyi':
False
},
'win-10_laptop_low_end-perf-pgo': {
'description': ('Low end windows 10 HP laptops. HD Graphics 5500, '
'x86-64-i3-5005U, SSD, 4GB RAM.'),
'num_shards':
40,
'platform_os':
'win',
'is_fyi':
False,
'pinpoint_only':
True
},
'win-10-perf': {
'description': ('Windows Intel HD 630 towers, Core i7-7700 3.6 GHz, '
'16GB RAM, Intel Kaby Lake HD Graphics 630'),
'num_shards':
18,
'platform_os':
'win',
'is_fyi':
False
},
'win-10-perf-pgo': {
'description': ('Windows Intel HD 630 towers, Core i7-7700 3.6 GHz, '
'16GB RAM, Intel Kaby Lake HD Graphics 630'),
'num_shards':
18,
'platform_os':
'win',
'is_fyi':
False,
'pinpoint_only':
True
},
'win-10_amd_laptop-perf': {
'description': 'Windows 10 Laptop with AMD chipset.',
'num_shards': 3,
'platform_os': 'win',
'is_fyi': False
},
'win-10_amd_laptop-perf-pgo': {
'description': 'Windows 10 Laptop with AMD chipset.',
'num_shards': 3,
'platform_os': 'win',
'is_fyi': False,
'pinpoint_only': True
},
'win-11-perf': {
'description': 'Windows Dell PowerEdge R350',
'num_shards': 20,
'platform_os': 'win',
'is_fyi': False
},
'win-11-perf-pgo': {
'description': 'Windows Dell PowerEdge R350',
'num_shards': 26,
'platform_os': 'win',
'is_fyi': False,
'pinpoint_only': True
},
'win-falcon-rak-5070-perf': {
'description': 'Windows Falcon RAK 5070',
'num_shards': 1,
'platform_os': 'win',
'is_fyi': False
},
'win-arm64-snapdragon-elite-perf': {
'description': 'Windows Dell Snapdragon Elite',
# TODO(crbug.com/541980022): reset to 28 when missing bots are found.
'num_shards': 18,
'platform_os': 'win',
'is_fyi': False
},
# TODO(crbug.com/525430279): Use all 8 bots to test the Canary image.
'android-brya-kano-i5-8gb-perf': {
'description': 'Brya SKU kano_12th_Gen_IntelR_CoreTM_i5_1235U_8GB',
'num_shards': 8,
'platform_os': 'android',
'is_fyi': False
},
'android-corsola-steelix-8gb-perf': {
'description': 'Corsola SKU steelix_MT8186_8GB',
'num_shards': 7,
'platform_os': 'android',
'is_fyi': False
},
'android-nissa-uldren-8gb-perf': {
'description': 'Nissa SKU uldren_99C4LZ/Q1XT/6W_8GB',
'num_shards': 7,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel4-perf': {
'description': 'Android R',
'num_shards': 38,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel4-perf-pgo': {
'description': 'Android R',
'num_shards': 28,
'platform_os': 'android',
'is_fyi': False,
'pinpoint_only': True
},
'android-pixel4_webview-perf': {
'description': 'Android R',
'num_shards': 19,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel4_webview-perf-pgo': {
'description': 'Android R',
'num_shards': 12,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel6-perf-pgo': {
'description': 'Android U',
'num_shards': 8,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel6-pro-perf-pgo': {
'description': 'Android T',
'num_shards': 16,
'platform_os': 'android',
'is_fyi': False,
'pinpoint_only': True
},
'android-pixel-fold-perf': {
'description': 'Android U',
'num_shards': 8,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel-tangor-perf': {
'description': 'Android U',
'num_shards': 8,
'platform_os': 'android',
'is_fyi': False
},
'android-go-wembley-perf': {
'description': 'Android U',
'num_shards': 5,
'platform_os': 'android',
'is_fyi': False
},
'android-go-wembley_webview-perf': {
'description': 'Android U',
'num_shards': 5,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel9-perf': {
'description': 'Android B',
'num_shards': 4,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel9-pro-perf': {
'description': 'Android B',
'num_shards': 4,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel9-pro-xl-perf': {
'description': 'Android B',
'num_shards': 4,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel10-perf': {
'description': 'Android B',
'num_shards': 25,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel10_webview-perf': {
'description': 'Android B',
'num_shards': 23,
'platform_os': 'android',
'is_fyi': False
},
'android-pixel10_webview-perf-pgo': {
'description': 'Android B',
'num_shards': 23,
'platform_os': 'android',
'is_fyi': False
},
'fuchsia-perf-nsn': {
'description': '',
'num_shards': 1,
'platform_os': 'fuchsia',
'is_fyi': True
},
'fuchsia-perf-shk': {
'description': '',
'num_shards': 1,
'platform_os': 'fuchsia',
'is_fyi': True
},
'win-10_laptop_low_end-perf_HP-Candidate': {
'description': 'HP 15-BS121NR Laptop Candidate',
'num_shards': 1,
'platform_os': 'win',
'is_fyi': True
},
'chromeos-kevin-perf-fyi': {
'description': '',
'num_shards': 4,
'platform_os': 'chromeos',
'is_fyi': True
},
'linux-perf-fyi': {
'description': '',
'num_shards': 1,
'platform_os': 'linux',
'is_fyi': True
},
}
# TODO: add more details.
BENCHMARK_INFO = {k: {} for k, v in _BENCHMARKS_CONFIG_FACTORIES.items()}
def LoadAllScheduleFiles() -> set[_PerfPlatform]:
schedule_dir = pathlib.Path(__file__).resolve().parent / 'schedule'
assert schedule_dir.is_dir(), f'Missing schedule directory {schedule_dir}'
bot_to_csv_configs: dict[str, list[BenchmarkConfig]] = {}
for file_path in schedule_dir.glob('*.csv'):
LoadScheduleFile(file_path, bot_to_csv_configs)
assert bot_to_csv_configs, 'No benchmark schedule configs generated'
new_platforms = set()
for name, platform_info in PLATFORM_INFO.items():
csv_configs = bot_to_csv_configs.get(name, [])
benchmark_configs = [
c for c in csv_configs if isinstance(c, TelemetryConfig)
]
executable_configs = frozenset(c for c in csv_configs
if isinstance(c, ExecutableConfig))
crossbench_configs = frozenset(c for c in csv_configs
if isinstance(c, CrossbenchConfig))
new_platform = _PerfPlatform(
name=name,
description=platform_info['description'],
benchmark_configs=PerfSuite(benchmark_configs),
num_shards=platform_info['num_shards'],
platform_os=platform_info['platform_os'],
is_fyi=platform_info.get('is_fyi', False),
run_reference_build=platform_info.get('run_reference_build', False),
pinpoint_only=platform_info.get('pinpoint_only', False),
executables=executable_configs,
crossbench=crossbench_configs)
new_platforms.add(new_platform)
return new_platforms
def LoadScheduleFile(file_path: pathlib.Path,
configs: dict[str, list[BenchmarkConfig]]):
name = file_path.stem
factory = _BENCHMARKS_CONFIG_FACTORIES[name]
# pylint: disable=comparison-with-callable)
is_telemetry = (factory == _TelemetryConfig)
# pylint: enable=comparison-with-callable)
reader = ReadCSV(file_path)
fieldnames = reader.fieldnames
assert fieldnames, 'Missing field names'
has_flags = 'flags' in fieldnames
seen_bots: set[str] = set()
for row in reader:
if flags := ParseFlags(file_path, row, has_flags):
row['flags'] = flags
bot = row['bot']
assert bot not in seen_bots, (
f'Duplicate bot {bot!r} in schedule file {file_path}')
seen_bots.add(bot)
config = _ParseScheduleConfigRow(row, name, factory, is_telemetry)
configs.setdefault(bot, []).append(config)
def ReadCSV(file_path: pathlib.Path):
contents = file_path.read_text(encoding='utf-8')
reader = csv.DictReader(_StripComments(io.StringIO(contents)),
restkey='*flag')
return reader
def _StripComments(iterator):
for line in iterator:
if line and line[0] != '#':
yield line
def ParseFlags(file_path: pathlib.Path, row, has_flags: bool) -> str | None:
if extraFlags := row.pop('*flag', None):
assert has_flags, (
f'Unexpected extra columns in {file_path}. Extra columns are only '
'supported if a "flags" column exists.')
# Merge trailing extra flag values with the explicit flags.
return ','.join((row['flags'], *extraFlags))
return None
def _ParseScheduleConfigRow(row, name: str, factory: BenchmarkConfigFactory,
is_telemetry: bool) -> BenchmarkConfig:
repeat = int(row.get('repeat', 1))
kwargs = {}
for k, v in row.items():
if k in ('bot', 'repeat', 'shard'):
continue
kwargs[k] = _ParseScheduleConfigValue(k, v)
if is_telemetry:
return factory(name, pageset_repeat=repeat, **kwargs)
config = factory(**kwargs)
if hasattr(config, 'repeat'):
config.repeat = repeat
else:
assert repeat == 1, f'Cannot use repeat > 1 yet on {name}'
return config
def _ParseScheduleConfigValue(k, v):
if k == 'flags':
v = str(v)
if v and v[0] == "'":
raise ValueError(f'Unsupported single quote for flag escaping: {v!r}')
return tuple(shlex.split(v))
if v.lower() == 'true':
return True
if v.lower() == 'false':
return False
return int(v)
ALL_PLATFORMS: set[_PerfPlatform] = set()
ALL_PLATFORMS = LoadAllScheduleFiles()
assert ALL_PLATFORMS, 'No PerfPlatform found'
PLATFORMS_BY_NAME = {p.name: p for p in ALL_PLATFORMS}
FYI_PLATFORMS = {
p for p in ALL_PLATFORMS if p.is_fyi
}
OFFICIAL_PLATFORMS = {p for p in ALL_PLATFORMS if p.is_official}
ALL_PLATFORM_NAMES = {
p.name for p in ALL_PLATFORMS
}
OFFICIAL_PLATFORM_NAMES = {
p.name for p in OFFICIAL_PLATFORMS
}
def find_bot_platform(builder_name):
for bot_platform in ALL_PLATFORMS:
if bot_platform.name == builder_name:
return bot_platform
return None