Skip to content

Commit 2cce1a7

Browse files
Merge pull request #502 from rfbgo/workload_groups
Proof of concept for workload_groups
2 parents abe7146 + 5e821d8 commit 2cce1a7

File tree

6 files changed

+223
-28
lines changed

6 files changed

+223
-28
lines changed

lib/ramble/ramble/language/application_language.py

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,42 @@ def _execute_workload(app):
7575
executable, executables, app.executables, "executable", "executables", "workload"
7676
)
7777

78-
all_inputs = ramble.language.language_helpers.merge_definitions(input, inputs, app.inputs)
78+
all_inputs = ramble.language.language_helpers.merge_definitions(
79+
input, inputs, app.inputs, "input", "inputs", "workload"
80+
)
7981

8082
app.workloads[name] = ramble.workload.Workload(name, all_execs, all_inputs, tags)
8183

8284
return _execute_workload
8385

8486

87+
@application_directive("workload_groups")
88+
def workload_group(name, workloads=[], mode=None, **kwargs):
89+
"""Adds a workload group to this application
90+
91+
Defines a new workload group that can be used within the context of its
92+
application.
93+
94+
Args:
95+
name: The name of the group
96+
workloads: A list of workloads to be grouped
97+
"""
98+
99+
def _execute_workload_groups(app):
100+
if mode == "append":
101+
app.workload_groups[name].update(set(workloads))
102+
else:
103+
app.workload_groups[name] = set(workloads)
104+
105+
# Apply any existing variables in the group to the workload
106+
for workload in workloads:
107+
if name in app.workload_group_vars:
108+
for var in app.workload_group_vars[name]:
109+
app.workloads[workload].add_variable(var)
110+
111+
return _execute_workload_groups
112+
113+
85114
@application_directive("executables")
86115
def executable(name, template, **kwargs):
87116
"""Adds an executable to this application
@@ -156,14 +185,15 @@ def _execute_input_file(app):
156185
return _execute_input_file
157186

158187

159-
@application_directive(dicts=())
188+
@application_directive("workload_group_vars")
160189
def workload_variable(
161190
name,
162191
default,
163192
description,
164193
values=None,
165194
workload=None,
166195
workloads=None,
196+
workload_group=None,
167197
expandable=True,
168198
**kwargs,
169199
):
@@ -177,20 +207,33 @@ def workload_variable(
177207
"""
178208

179209
def _execute_workload_variable(app):
180-
all_workloads = ramble.language.language_helpers.require_definition(
210+
# Always apply passes workload/workloads
211+
all_workloads = ramble.language.language_helpers.merge_definitions(
181212
workload, workloads, app.workloads, "workload", "workloads", "workload_variable"
182213
)
183214

215+
workload_var = ramble.workload.WorkloadVariable(
216+
name, default=default, description=description, values=values, expandable=expandable
217+
)
218+
184219
for wl_name in all_workloads:
185-
app.workloads[wl_name].add_variable(
186-
ramble.workload.WorkloadVariable(
187-
name,
188-
default=default,
189-
description=description,
190-
values=values,
191-
expandable=expandable,
192-
)
193-
)
220+
app.workloads[wl_name].add_variable(workload_var.copy())
221+
222+
if workload_group is not None:
223+
workload_group_list = app.workload_groups[workload_group]
224+
225+
if workload_group not in app.workload_group_vars:
226+
app.workload_group_vars[workload_group] = []
227+
228+
# Track which vars we add to, to allow us to re-apply during inheritance
229+
app.workload_group_vars[workload_group].append(workload_var.copy())
230+
231+
for wl_name in workload_group_list:
232+
# Apply the variable
233+
app.workloads[wl_name].add_variable(workload_var.copy())
234+
235+
if not all_workloads and workload_group is None:
236+
raise DirectiveError("A workload or workload group is required")
194237

195238
return _execute_workload_variable
196239

lib/ramble/ramble/language/language_helpers.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,46 @@
1212
from ramble.language.language_base import DirectiveError
1313

1414

15-
def merge_definitions(single_type, multiple_type, multiple_pattern_match):
15+
def check_definition(
16+
single_type, multiple_type, single_arg_name, multiple_arg_name, directive_name
17+
):
18+
"""
19+
Sanity check definitions before merging or require
20+
21+
Args:
22+
single_type: Single string for type name
23+
multiple_type: List of strings for type names, may contain wildcards
24+
multiple_pattern_match: List of strings to match against patterns in multiple_type
25+
single_arg_name: String name of the single_type argument in the directive
26+
multiple_arg_name: String name of the multiple_type argument in the directive
27+
directive_name: Name of the directive requiring a type
28+
29+
Returns:
30+
List of all type names (Merged if both single_type and multiple_type definitions are valid)
31+
"""
32+
if single_type and not isinstance(single_type, six.string_types):
33+
raise DirectiveError(
34+
f"Directive {directive_name} was given an invalid type "
35+
f"for the {single_arg_name} argument. "
36+
f"Type was {type(single_type)}"
37+
)
38+
39+
if multiple_type and not isinstance(multiple_type, list):
40+
raise DirectiveError(
41+
f"Directive {directive_name} was given an invalid type "
42+
f"for the {multiple_arg_name} argument. "
43+
f"Type was {type(multiple_type)}"
44+
)
45+
46+
47+
def merge_definitions(
48+
single_type,
49+
multiple_type,
50+
multiple_pattern_match,
51+
single_arg_name,
52+
multiple_arg_name,
53+
directive_name,
54+
):
1655
"""Merge definitions of a type
1756
1857
This method will merge two optional definitions of single_type and
@@ -22,11 +61,18 @@ def merge_definitions(single_type, multiple_type, multiple_pattern_match):
2261
single_type: Single string for type name
2362
multiple_type: List of strings for type names, may contain wildcards
2463
multiple_pattern_match: List of strings to match against patterns in multiple_type
64+
single_arg_name: String name of the single_type argument in the directive
65+
multiple_arg_name: String name of the multiple_type argument in the directive
66+
directive_name: Name of the directive requiring a type
2567
2668
Returns:
2769
List of all type names (Merged if both single_type and multiple_type definitions are valid)
2870
"""
2971

72+
check_definition(
73+
single_type, multiple_type, single_arg_name, multiple_arg_name, directive_name
74+
)
75+
3076
all_types = []
3177

3278
if single_type:
@@ -72,21 +118,14 @@ def require_definition(
72118
f"{single_arg_name} or {multiple_arg_name} to be defined."
73119
)
74120

75-
if single_type and not isinstance(single_type, six.string_types):
76-
raise DirectiveError(
77-
f"Directive {directive_name} was given an invalid type "
78-
f"for the {single_arg_name} argument. "
79-
f"Type was {type(single_type)}"
80-
)
81-
82-
if multiple_type and not isinstance(multiple_type, list):
83-
raise DirectiveError(
84-
f"Directive {directive_name} was given an invalid type "
85-
f"for the {multiple_arg_name} argument. "
86-
f"Type was {type(multiple_type)}"
87-
)
88-
89-
return merge_definitions(single_type, multiple_type, multiple_pattern_match)
121+
return merge_definitions(
122+
single_type,
123+
multiple_type,
124+
multiple_pattern_match,
125+
single_arg_name,
126+
multiple_arg_name,
127+
directive_name,
128+
)
90129

91130

92131
def expand_patterns(multiple_type: list, multiple_pattern_match: list):

lib/ramble/ramble/test/application_tests.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
)
2323
def test_app_features(mutable_mock_apps_repo, app):
2424
app_inst = mutable_mock_apps_repo.get(app)
25+
2526
assert hasattr(app_inst, "workloads")
27+
assert hasattr(app_inst, "workload_groups")
2628
assert hasattr(app_inst, "executables")
2729
assert hasattr(app_inst, "figures_of_merit")
2830
assert hasattr(app_inst, "inputs")
@@ -498,3 +500,46 @@ def test_class_attributes(mutable_mock_apps_repo):
498500

499501
assert "added_workload" in basic_copy.workloads
500502
assert "added_workload" not in basic_inst.workloads
503+
504+
505+
def test_workload_groups(mutable_mock_apps_repo):
506+
workload_group_inst = mutable_mock_apps_repo.get("workload-groups")
507+
508+
assert "test_wl" in workload_group_inst.workloads
509+
510+
assert "empty" in workload_group_inst.workload_groups
511+
assert "test_wlg" in workload_group_inst.workload_groups
512+
513+
my_var = workload_group_inst.workloads["test_wl"].find_variable("test_var")
514+
assert my_var is not None
515+
assert my_var.default == "2.0"
516+
assert my_var.description == "Test workload vars and groups"
517+
518+
my_mixed_var_wl = workload_group_inst.workloads["test_wl"].find_variable("test_var_mixed")
519+
assert my_mixed_var_wl is not None
520+
assert my_mixed_var_wl.default == "3.0"
521+
assert my_mixed_var_wl.description == "Test vars for workload and groups"
522+
523+
524+
def test_workload_groups_inherited(mutable_mock_apps_repo):
525+
wlgi_inst = mutable_mock_apps_repo.get("workload-groups-inherited")
526+
527+
assert "test_wl" in wlgi_inst.workloads
528+
assert "test_wl3" in wlgi_inst.workloads
529+
530+
# check we inherit groups we don't touch
531+
assert "empty" in wlgi_inst.workload_groups
532+
assert "test_wlg" in wlgi_inst.workload_groups
533+
534+
assert "test_wl" in wlgi_inst.workload_groups["test_wlg"]
535+
536+
# Ensure a new workload can obtain the parent level vars via groups
537+
my_var = wlgi_inst.workloads["test_wl3"].find_variable("test_var")
538+
assert my_var is not None
539+
assert my_var.default == "2.0"
540+
assert my_var.description == "Test workload vars and groups"
541+
542+
for wl in ["test_wl", "test_wl3"]:
543+
my_mixed_var_wl = wlgi_inst.workloads[wl].find_variable("test_var_mixed")
544+
assert my_mixed_var_wl is not None
545+
assert my_mixed_var_wl.default == "3.0"

lib/ramble/ramble/workload.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
# except according to those terms.
88

99
from typing import List
10+
import copy
11+
1012
import ramble.util.colors as rucolor
1113

1214

@@ -62,6 +64,9 @@ def as_str(self, n_indent: int = 0):
6264
out_str += f'{indentation} {name}: {str(attr_val).replace("@", "@@")}\n'
6365
return out_str
6466

67+
def copy(self):
68+
return copy.deepcopy(self)
69+
6570

6671
class WorkloadEnvironmentVariable(object):
6772
"""Class representing an environment variable in a workload"""
@@ -99,6 +104,9 @@ def as_str(self, n_indent: int = 0):
99104
out_str += f'{indentation} {name}: {attr_val.replace("@", "@@")}\n'
100105
return out_str
101106

107+
def copy(self):
108+
return copy.deepcopy(self)
109+
102110

103111
class Workload(object):
104112
"""Class representing a single workload"""
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2022-2024 The Ramble Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
# option. This file may not be copied, modified, or distributed
7+
# except according to those terms.
8+
9+
from ramble.appkit import *
10+
11+
from ramble.app.builtin.mock.workload_groups import WorkloadGroups
12+
13+
14+
class WorkloadGroupsInherited(WorkloadGroups):
15+
name = "workload-groups-inherited"
16+
17+
workload('test_wl3', executable='baz')
18+
19+
# Test populated group applies existing vars to new workload
20+
workload_group('test_wlg',
21+
workloads=['test_wl3'],
22+
mode='append')
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2022-2024 The Ramble Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
# option. This file may not be copied, modified, or distributed
7+
# except according to those terms.
8+
9+
from ramble.appkit import *
10+
11+
12+
class WorkloadGroups(ExecutableApplication):
13+
name = "workload-groups"
14+
15+
executable('foo', 'echo "bar"', use_mpi=False)
16+
executable('bar', 'echo "baz"', use_mpi=False)
17+
18+
workload('test_wl', executable='foo')
19+
workload('test_wl2', executable='bar')
20+
21+
# Test empty group
22+
workload_group('empty',
23+
workloads=[])
24+
25+
# Test populated group
26+
workload_group('test_wlg',
27+
workloads=['test_wl', 'test_wl2'])
28+
29+
# Test workload_variable that uses a group
30+
workload_variable('test_var', default='2.0',
31+
description='Test workload vars and groups',
32+
workload_group='test_wlg')
33+
34+
# Test passing both groups an explicit workloads
35+
workload_variable('test_var_mixed', default='3.0',
36+
description='Test vars for workload and groups',
37+
workload='test_wl',
38+
workload_group='test_wlg')

0 commit comments

Comments
 (0)