Skip to content

Commit 1417345

Browse files
authored
#567: QOIs: refactor to use a decorator (#569)
1 parent 24d8797 commit 1417345

File tree

5 files changed

+116
-30
lines changed

5 files changed

+116
-30
lines changed

src/lbaf/Applications/LBAF_app.py

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
import importlib
4949
import yaml
5050

51+
from ..Model.lbsRank import Rank
52+
from ..Model.lbsObject import Object
53+
5154
try:
5255
import vttv
5356
USING_VTTV = True
@@ -415,44 +418,29 @@ def __print_qoi(self) -> int:
415418
"""Print list of implemented QOI based on the '-verbosity' command line argument."""
416419
verbosity = int(self.__args.verbose)
417420

418-
# Initialize file paths
419-
current_script_path = os.path.abspath(__file__)
420-
target_dir = os.path.join(
421-
os.path.dirname(os.path.dirname(current_script_path)), "Model")
422-
rank_script_name = "lbsRank.py"
423-
object_script_name = "lbsObject.py"
424-
425-
# Create list of all Rank QOI (Rank.get_*)
426-
r_qoi_list = ["work"]
427-
with open(os.path.join(target_dir, rank_script_name), 'r', encoding="utf-8") as f:
428-
lines = f.readlines()
429-
for line in lines:
430-
if line[8:12] == "get_":
431-
r_qoi_list.append(line[12:line.find("(")])
432-
433-
# Create list of all Object QOI (Object.get_*)
434-
o_qoi_list = []
435-
with open(os.path.join(target_dir, object_script_name), 'r', encoding="utf-8") as f:
436-
lines = f.readlines()
437-
for line in lines:
438-
if line[8:12] == "get_":
439-
o_qoi_list.append(line[12:line.find("(")])
421+
def remove_get(name : str) -> str:
422+
return name[4:] if name.startswith("get_") else name
423+
424+
r = Rank(self.__logger)
425+
rank_qois = r.get_QOIs()
426+
o = Object(seq_id=0)
427+
object_qois = o.get_QOIs()
440428

441429
# Print QOI based on verbosity level
442430
if verbosity > 0:
443431
self.__logger.info("List of Implemented QOI:")
444432
if verbosity == 1:
445433
self.__logger.info("\tRank QOI:")
446-
for r_qoi in r_qoi_list:
447-
self.__logger.info(f"\t\t{r_qoi}")
434+
for name, _ in rank_qois.items():
435+
self.__logger.info("\t" + remove_get(name))
448436
elif verbosity > 1:
449437
self.__logger.info("\tRank QOI:")
450-
for r_qoi in r_qoi_list:
451-
self.__logger.info(f"\t\t{r_qoi}")
438+
for name, _ in rank_qois.items():
439+
self.__logger.info("\t" + remove_get(name))
452440
self.__logger.info("")
453441
self.__logger.info("\tObject QOI:")
454-
for o_qoi in o_qoi_list:
455-
self.__logger.info(f"\t\t{o_qoi}")
442+
for name, _ in object_qois.items():
443+
self.__logger.info("\t\t" + remove_get(name))
456444

457445
def run(self, cfg=None, cfg_dir=None):
458446
"""Run the LBAF application."""

src/lbaf/Execution/lbsAlgorithmBase.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
from ..IO.lbsStatistics import compute_function_statistics
4848
from ..Model.lbsRank import Rank
49+
from ..Model.lbsObject import Object
4950
from ..Model.lbsPhase import Phase
5051
from ..Model.lbsWorkModelBase import WorkModelBase
5152
from ..Utils.lbsLogging import Logger

src/lbaf/Model/lbsObject.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
from .lbsBlock import Block
4646
from .lbsObjectCommunicator import ObjectCommunicator
47+
from .lbsQOIDecorator import qoi
4748

4849
class Object:
4950
"""A class representing an object with load and communicator
@@ -157,18 +158,22 @@ def __init__(
157158
def __repr__(self):
158159
return f"Object id: {self.get_id()}, load: {self.__load}"
159160

161+
@qoi
160162
def get_id(self) -> int:
161163
"""Return object bit-packed ID if available. Else return the object seq ID"""
162164
return self.__packed_id if self.__packed_id is not None else self.__seq_id
163165

166+
@qoi
164167
def get_packed_id(self) -> Optional[int]:
165168
"""Return object bit-packed ID (seq_id, home_id, migratable)."""
166169
return self.__packed_id
167170

171+
@qoi
168172
def get_seq_id(self) -> Optional[int]:
169173
"""Return object seq ID."""
170174
return self.__seq_id
171175

176+
@qoi
172177
def get_collection_id(self) -> Optional[int]:
173178
"""Return object collection ID (required for migratable objects)."""
174179
return self.__collection_id
@@ -177,6 +182,7 @@ def set_collection_id(self, collection_id: Optional[int]):
177182
""" Set object collection ID (required for migratable objects)."""
178183
self.__collection_id = collection_id
179184

185+
@qoi
180186
def get_index(self) -> Optional[list]:
181187
"""Return the object's index."""
182188
return self.__index
@@ -189,14 +195,17 @@ def set_load(self, load: float):
189195
""" Set object load."""
190196
self.__load = load
191197

198+
@qoi
192199
def get_load(self) -> float:
193200
"""Return object load."""
194201
return self.__load
195202

203+
@qoi
196204
def get_size(self) -> float:
197205
"""Return object size."""
198206
return self.__size
199207

208+
@qoi
200209
def get_overhead(self) -> float:
201210
"""Return additional runtime memory of object."""
202211
return self.__overhead
@@ -214,14 +223,17 @@ def get_received(self) -> dict:
214223

215224
return self.__communicator.get_received() if self.__communicator else {}
216225

226+
@qoi
217227
def get_received_volume(self) -> float:
218228
"""Return volume of communications received by object."""
219229
return sum(v for v in self.__communicator.get_received().values()) if self.__communicator else 0
220230

231+
@qoi
221232
def get_sent_volume(self) -> float:
222233
"""Return volume of communications sent by object."""
223234
return sum(v for v in self.__communicator.get_sent().values()) if self.__communicator else 0
224235

236+
@qoi
225237
def get_max_volume(self) -> float:
226238
"""Return the maximum bytes received or sent by object."""
227239
return self.__communicator.get_max_volume() if self.__communicator else 0
@@ -230,6 +242,7 @@ def set_rank_id(self, r_id: int) -> None:
230242
"""Assign object to rank ID"""
231243
self.__rank_id = r_id
232244

245+
@qoi
233246
def get_rank_id(self) -> int:
234247
"""Return ID of rank to which object is currently assigned."""
235248
return self.__rank_id
@@ -244,6 +257,7 @@ def get_shared_block(self) -> Optional[Block]:
244257
"""Return shared memory block assigned to object."""
245258
return self.__shared_block
246259

260+
@qoi
247261
def get_shared_id(self) -> Optional[int]:
248262
"""Return ID of shared memory block assigned to object."""
249263
return self.__shared_block.get_id() if self.__shared_block is not None else None
@@ -274,3 +288,12 @@ def set_unused_params(self, unused_params: dict):
274288
def get_unused_params(self) -> dict:
275289
"""Return all current unused parameters."""
276290
return self.__unused_params
291+
292+
def get_QOIs(self) -> list:
293+
"""Get all methods decorated with the QOI decorator.
294+
"""
295+
qoi_methods : dict = {
296+
name: getattr(self, name)
297+
for name in dir(self)
298+
if callable(getattr(self, name)) and not name.startswith("__") and hasattr(getattr(self, name), "is_qoi") }
299+
return qoi_methods

src/lbaf/Model/lbsQOIDecorator.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#
2+
#@HEADER
3+
###############################################################################
4+
#
5+
# lbsQOIDecorator.py
6+
# DARMA/LB-analysis-framework => LB Analysis Framework
7+
#
8+
# Copyright 2019-2024 National Technology & Engineering Solutions of Sandia, LLC
9+
# (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
10+
# Government retains certain rights in this software.
11+
#
12+
# Redistribution and use in source and binary forms, with or without
13+
# modification, are permitted provided that the following conditions are met:
14+
#
15+
# * Redistributions of source code must retain the above copyright notice,
16+
# this list of conditions and the following disclaimer.
17+
#
18+
# * Redistributions in binary form must reproduce the above copyright notice,
19+
# this list of conditions and the following disclaimer in the documentation
20+
# and/or other materials provided with the distribution.
21+
#
22+
# * Neither the name of the copyright holder nor the names of its
23+
# contributors may be used to endorse or promote products derived from this
24+
# software without specific prior written permission.
25+
#
26+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
30+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36+
# POSSIBILITY OF SUCH DAMAGE.
37+
#
38+
# Questions? Contact [email protected]
39+
#
40+
###############################################################################
41+
#@HEADER
42+
#
43+
44+
def qoi(func, *args, **kwargs):
45+
"""Decorator function to wrap getters that will be used as QOIs"""
46+
def decorated_func(*args, **kwargs):
47+
return_value = func(*args, **kwargs)
48+
return return_value
49+
decorated_func.is_qoi = True
50+
return decorated_func

src/lbaf/Model/lbsRank.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
from .lbsBlock import Block
4949
from .lbsObject import Object
50-
50+
from .lbsQOIDecorator import qoi
5151

5252
class Rank:
5353
"""A class representing a rank to which objects are assigned."""
@@ -99,12 +99,14 @@ def __lt__(self, other):
9999
def __repr__(self):
100100
return f"<Rank index: {self.__index}>"
101101

102+
@qoi
102103
def get_id(self) -> int:
103104
"""Return rank ID."""
104105
return self.__index
105106

107+
@qoi
106108
def get_size(self) -> float:
107-
"""Return object size."""
109+
"""Return rank working memory."""
108110
return self.__size
109111

110112
def set_size(self, size):
@@ -162,26 +164,31 @@ def get_shared_block_with_id(self, b_id: int) -> Block:
162164
return block
163165
return None
164166

167+
@qoi
165168
def get_number_of_shared_blocks(self) -> float:
166169
"""Return number of shared memory blocks on rank."""
167170
return len(self.__shared_blocks)
168171

172+
@qoi
169173
def get_number_of_homed_blocks(self) -> float:
170174
"""Return number of memory blocks on rank also homed there."""
171175
return sum(
172176
b.get_home_id() == self.get_id()
173177
for b in self.__shared_blocks)
174178

179+
@qoi
175180
def get_number_of_uprooted_blocks(self) -> float:
176181
"""Return number of uprooted memory blocks on rank."""
177182
return len(self.__shared_blocks) - self.get_number_of_homed_blocks()
178183

184+
@qoi
179185
def get_homed_blocks_ratio(self) -> float:
180186
"""Return fraction of memory blocks on rank also homed there."""
181187
if len(self.__shared_blocks) > 0:
182188
return self.get_number_of_homed_blocks() / len(self.__shared_blocks)
183189
return math.nan
184190

191+
@qoi
185192
def get_shared_memory(self):
186193
"""Return total shared memory on rank."""
187194
return float(sum(b.get_size() for b in self.__shared_blocks))
@@ -190,6 +197,7 @@ def get_objects(self) -> set:
190197
"""Return all objects assigned to rank."""
191198
return self.__migratable_objects.union(self.__sentinel_objects)
192199

200+
@qoi
193201
def get_number_of_objects(self) -> int:
194202
"""Return number of objects assigned to rank."""
195203
return len(self.__sentinel_objects) + len(self.__migratable_objects)
@@ -241,18 +249,22 @@ def remove_migratable_object(self, o: Object):
241249
"""Remove objects from migratable objects."""
242250
self.__migratable_objects.remove(o)
243251

252+
@qoi
244253
def get_load(self) -> float:
245254
"""Return total load on rank."""
246255
return sum(o.get_load() for o in self.__migratable_objects.union(self.__sentinel_objects))
247256

257+
@qoi
248258
def get_migratable_load(self) -> float:
249259
"""Return migratable load on rank."""
250260
return sum(o.get_load() for o in self.__migratable_objects)
251261

262+
@qoi
252263
def get_sentinel_load(self) -> float:
253264
"""Return sentinel load on rank."""
254265
return sum(o.get_load() for o in self.__sentinel_objects)
255266

267+
@qoi
256268
def get_received_volume(self):
257269
"""Return volume received by objects assigned to rank from other ranks."""
258270
# Iterate over all objects assigned to rank
@@ -269,6 +281,7 @@ def get_received_volume(self):
269281
# Return computed volume
270282
return volume
271283

284+
@qoi
272285
def get_sent_volume(self):
273286
"""Return volume sent by objects assigned to rank to other ranks."""
274287
# Iterate over all objects assigned to rank
@@ -287,6 +300,7 @@ def get_sent_volume(self):
287300
# Return computed volume
288301
return volume
289302

303+
@qoi
290304
def get_max_object_level_memory(self) -> float:
291305
"""Return maximum object-level memory on rank."""
292306
# Iterate over all objects assigned to rank
@@ -302,6 +316,16 @@ def get_max_object_level_memory(self) -> float:
302316
# Return maximum object-level memory for this rank
303317
return total_size + max_overhead
304318

319+
@qoi
305320
def get_max_memory_usage(self) -> float:
306321
"""Return maximum memory usage on rank."""
307322
return self.__size + self.get_shared_memory() + self.get_max_object_level_memory()
323+
324+
def get_QOIs(self) -> list:
325+
"""Get all methods decorated with the QOI decorator.
326+
"""
327+
qoi_methods : dict = {
328+
name: getattr(self, name)
329+
for name in dir(self)
330+
if callable(getattr(self, name)) and not name.startswith("__") and hasattr(getattr(self, name), "is_qoi") }
331+
return qoi_methods

0 commit comments

Comments
 (0)