Skip to content

Commit 35bdfe9

Browse files
committed
Initial bisect command
1 parent 4d7a6fa commit 35bdfe9

File tree

2 files changed

+121
-0
lines changed

2 files changed

+121
-0
lines changed

lib/pavilion/commands/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
'_log_results': ('_log_results', 'LogResults'),
1717
'_run': ('_run', '_RunCommand'),
1818
'_series': ('_series', 'AutoSeries'),
19+
'bisect': ('bisect', 'BisectCommand'),
1920
'build': ('build', 'BuildCommand'),
2021
'cancel': ('cancel', 'CancelCommand'),
2122
'cat': ('cat', 'CatCommand'),

lib/pavilion/commands/bisect.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from argparse import ArgumentParser
2+
3+
from pavilion import output
4+
from pavilion.errors import TestSeriesError
5+
from pavilion.config import PavConfig
6+
from pavilion.series.series import TestSeries
7+
from pavilion.schedulers.config import parse_node_range
8+
from .base_classes import Command
9+
10+
11+
class BisectCommand(Command):
12+
"""Identify bad nodes by using a test to perform a binary search."""
13+
14+
def __init__(self):
15+
super().__init__(
16+
'bisect',
17+
'Perform a bisection search on a given set of nodes using a particular test.',
18+
short_help="Perform a bisection search."
19+
)
20+
21+
def _setup_arguments(self, parser: ArgumentParser) -> None:
22+
"""Set up the parser arguments."""
23+
24+
parser.add_argument("test_name", type=str,
25+
help="The name of the test to use to bisect the nodes.")
26+
parser.add_argument("nodes", type=str, default="",
27+
help="The set of nodes with which to start the search.")
28+
parser.add_argument(
29+
'-p', '--platform', action='store',
30+
help='The platform to configure this test for. If not '
31+
'specified, the current platform as denoted by the sys '
32+
'plugin \'platform\' is used.')
33+
parser.add_argument(
34+
'-H', '--host', action='store',
35+
help='The host to configure this test for. If not specified, the '
36+
'current host as denoted by the sys plugin \'sys_host\' is '
37+
'used. Host configurations are overlayed on operating system '
38+
'configurations.')
39+
parser.add_argument(
40+
'-n', '--name', action='store', default=''
41+
)
42+
parser.add_argument(
43+
'-m', '--mode', action='append', dest='modes', default=[],
44+
help='Mode configurations to overlay on the host configuration for '
45+
'each test. These are overlayed in the order given.')
46+
parser.add_argument(
47+
'-c', dest='overrides', action='append', default=[],
48+
help='Overrides for specific configuration options. These are '
49+
'gathered used as a final set of overrides before the '
50+
'configs are resolved. They should take the form '
51+
'\'key=value\', where key is the dot separated key name, '
52+
'and value is a json object. Example: `-c schedule.nodes=23`')
53+
54+
def run(pav_cfg: PavConfig, args: Namespace) -> int:
55+
"""Run the bisection search."""
56+
57+
try:
58+
nodes = self._parse_nodes(args.nodes)
59+
except ValueError:
60+
output.fprint(self.errfile, f"Error parsing node list {args.nodes}.")
61+
62+
return 1
63+
64+
# 1. Split the set of nodes in half.
65+
num_nodes = len(nodes)
66+
first_half = nodes[:num_nodes]
67+
second_half = nodes[num_nodes:]
68+
69+
# 2. Run the test on each set of nodes
70+
series_cfg = generate_series_config(
71+
name="bisect",
72+
modes=args.modes,
73+
platform=args.platform,
74+
host=args.host,
75+
overrides=args.overrides,
76+
ignore_errors=args.ignore_errors,
77+
)
78+
79+
series_obj = TestSeries(pav_cfg, series_cfg=series_cfg, outfile=self.outfile)
80+
testset_name = cmd_utils.get_testset_name(pav_cfg, [args.test_name], [])
81+
82+
series_obj.add_test_set_config(
83+
testset_name,
84+
[args.test_name],
85+
modes=args.modes,
86+
)
87+
88+
self.last_series = series_obj
89+
90+
try:
91+
series_obj.run(
92+
rebuild=args.rebuild,
93+
local_builds_only=local_builds_only,
94+
log_results=log_results)
95+
self.last_tests = list(series_obj.tests.values())
96+
except TestSeriesError as err:
97+
self.last_tests = list(series_obj.tests.values())
98+
output.fprint(self.errfile, err, color=output.RED)
99+
100+
return 2
101+
102+
# 3. Wait for tests to finish
103+
series_obj.wait()
104+
105+
# 4. Choose test with failed nodes and repeat
106+
107+
return 0
108+
109+
@staticmethod
110+
def _parse_nodes(nodes: str) -> List[str]:
111+
"""Parse a list of nodes into individual nodes."""
112+
113+
nodes = []
114+
115+
ranges = nodes.split(",")
116+
117+
for rng in ranges:
118+
nodes.append(parse_node_range(rng))
119+
120+
return nodes

0 commit comments

Comments
 (0)