Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/pavilion/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
'_log_results': ('_log_results', 'LogResults'),
'_run': ('_run', '_RunCommand'),
'_series': ('_series', 'AutoSeries'),
'bisect': ('bisect', 'BisectCommand'),
'build': ('build', 'BuildCommand'),
'cancel': ('cancel', 'CancelCommand'),
'cat': ('cat', 'CatCommand'),
Expand Down
121 changes: 121 additions & 0 deletions lib/pavilion/commands/bisect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from argparse import ArgumentParser, Namespace
from typing import List

from pavilion import output
from pavilion.errors import TestSeriesError
from pavilion.config import PavConfig
from pavilion.series.series import TestSeries
from pavilion.schedulers.config import parse_node_range
from .base_classes import Command


class BisectCommand(Command):
"""Identify bad nodes by using a test to perform a binary search."""

def __init__(self):
super().__init__(
'bisect',
'Perform a bisection search on a given set of nodes using a particular test.',
short_help="Perform a bisection search."
)

def _setup_arguments(self, parser: ArgumentParser) -> None:
"""Set up the parser arguments."""

parser.add_argument("test_name", type=str,
help="The name of the test to use to bisect the nodes.")
parser.add_argument("nodes", type=str, default="",
help="The set of nodes with which to start the search.")
parser.add_argument(
'-p', '--platform', action='store',
help='The platform to configure this test for. If not '
'specified, the current platform as denoted by the sys '
'plugin \'platform\' is used.')
parser.add_argument(
'-H', '--host', action='store',
help='The host to configure this test for. If not specified, the '
'current host as denoted by the sys plugin \'sys_host\' is '
'used. Host configurations are overlayed on operating system '
'configurations.')
parser.add_argument(
'-n', '--name', action='store', default=''
)
parser.add_argument(
'-m', '--mode', action='append', dest='modes', default=[],
help='Mode configurations to overlay on the host configuration for '
'each test. These are overlayed in the order given.')
parser.add_argument(
'-c', dest='overrides', action='append', default=[],
help='Overrides for specific configuration options. These are '
'gathered used as a final set of overrides before the '
'configs are resolved. They should take the form '
'\'key=value\', where key is the dot separated key name, '
'and value is a json object. Example: `-c schedule.nodes=23`')

def run(pav_cfg: PavConfig, args: Namespace) -> int:
"""Run the bisection search."""

try:
nodes = self._parse_nodes(args.nodes)
except ValueError:
output.fprint(self.errfile, f"Error parsing node list {args.nodes}.")

return 1

# 1. Split the set of nodes in half.
num_nodes = len(nodes)
first_half = nodes[:num_nodes]
second_half = nodes[num_nodes:]

# 2. Run the test on each set of nodes
series_cfg = generate_series_config(
name="bisect",
modes=args.modes,
platform=args.platform,
host=args.host,
overrides=args.overrides,
ignore_errors=args.ignore_errors,
)

series_obj = TestSeries(pav_cfg, series_cfg=series_cfg, outfile=self.outfile)
testset_name = cmd_utils.get_testset_name(pav_cfg, [args.test_name], [])

series_obj.add_test_set_config(
testset_name,
[args.test_name],
modes=args.modes,
)

self.last_series = series_obj

try:
series_obj.run(
rebuild=args.rebuild,
local_builds_only=local_builds_only,
log_results=log_results)
self.last_tests = list(series_obj.tests.values())
except TestSeriesError as err:
self.last_tests = list(series_obj.tests.values())
output.fprint(self.errfile, err, color=output.RED)

return 2

# 3. Wait for tests to finish
series_obj.wait()

# 4. Choose test with failed nodes and repeat

return 0

@staticmethod
def _parse_nodes(nodes: str) -> List[str]:
"""Parse a list of nodes into individual nodes."""

nodes = []

ranges = nodes.split(",")

for rng in ranges:
nodes.append(parse_node_range(rng))

return nodes
3 changes: 2 additions & 1 deletion lib/pavilion/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from pavilion.series_config import generate_series_config
from pavilion.status_utils import print_from_tests
from pavilion.test_ids import GroupID
from pavilion.resolver import TestConfigResolver
from .base_classes import Command


Expand Down Expand Up @@ -154,7 +155,7 @@ def run(self, pav_cfg, args, log_results: bool = True):
platform=args.platform,
host=args.host,
repeat=getattr(args, 'repeat', None),
overrides=args.overrides,
overrides=TestConfigResolver.config_from_overrides(args.overrides),
ignore_errors=args.ignore_errors,
)

Expand Down
4 changes: 3 additions & 1 deletion lib/pavilion/commands/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pavilion.test_ids import SeriesID, resolve_mixed_ids
from pavilion.errors import TestSeriesError, TestSeriesWarning
from pavilion.config import PavConfig
from pavilion.resolver import TestConfigResolver
from .base_classes import Command, sub_cmd


Expand Down Expand Up @@ -189,13 +190,14 @@ def _run_cmd(self, pav_cfg, args):
else:
# load series and test files
try:
overrides = TestConfigResolver.config_from_overrides(args.overrides)
# Pre-verify that all the series, tests, platform, modes, and hosts exist.
series_cfg = series_config.verify_configs(pav_cfg,
args.series_name,
platform=args.platform,
host=args.host,
modes=args.modes,
overrides=args.overrides)
overrides=overrides)
except series_config.SeriesConfigError as err:
output.fprint(self.errfile, err.pformat(), color=output.RED)
return errno.EINVAL
Expand Down
Loading
Loading