potassco-benchmark-tool 2.1.1__py3-none-any.whl → 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchmarktool/entry_points.py +71 -33
- benchmarktool/init/runscripts/runscript-all.xml +2 -2
- benchmarktool/init/runscripts/runscript-dist.xml +2 -2
- benchmarktool/init/runscripts/runscript-example.xml +1 -1
- benchmarktool/init/templates/seq-generic.sh +20 -5
- benchmarktool/result/ipynb_gen.py +2 -0
- benchmarktool/result/result.py +26 -16
- benchmarktool/result/xlsx_gen.py +935 -0
- benchmarktool/resultparser/clasp.py +20 -9
- benchmarktool/runscript/parser.py +235 -134
- benchmarktool/runscript/runscript.py +190 -191
- benchmarktool/tools.py +22 -2
- {potassco_benchmark_tool-2.1.1.dist-info → potassco_benchmark_tool-2.2.0.dist-info}/METADATA +24 -11
- potassco_benchmark_tool-2.2.0.dist-info/RECORD +26 -0
- benchmarktool/init/templates/seq-generic-single.sh +0 -27
- benchmarktool/init/templates/seq-generic-zip.sh +0 -14
- benchmarktool/result/ods_config.py +0 -42
- benchmarktool/result/ods_gen.py +0 -714
- potassco_benchmark_tool-2.1.1.dist-info/RECORD +0 -29
- {potassco_benchmark_tool-2.1.1.dist-info → potassco_benchmark_tool-2.2.0.dist-info}/WHEEL +0 -0
- {potassco_benchmark_tool-2.1.1.dist-info → potassco_benchmark_tool-2.2.0.dist-info}/entry_points.txt +0 -0
- {potassco_benchmark_tool-2.1.1.dist-info → potassco_benchmark_tool-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {potassco_benchmark_tool-2.1.1.dist-info → potassco_benchmark_tool-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -7,9 +7,9 @@ specified by the run script.
|
|
|
7
7
|
__author__ = "Roland Kaminski"
|
|
8
8
|
|
|
9
9
|
import importlib
|
|
10
|
-
import importlib.util
|
|
11
10
|
import os
|
|
12
11
|
import re
|
|
12
|
+
import shutil
|
|
13
13
|
import sys
|
|
14
14
|
from dataclasses import dataclass, field
|
|
15
15
|
from functools import total_ordering
|
|
@@ -44,7 +44,7 @@ class Machine:
|
|
|
44
44
|
out (Any): Output stream to write to.
|
|
45
45
|
indent (str): Amount of indentation.
|
|
46
46
|
"""
|
|
47
|
-
out.write('{
|
|
47
|
+
out.write(f'{indent}<machine name="{self.name}" cpu="{self.cpu}" memory="{self.memory}"/>\n')
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
@dataclass(order=True, frozen=True)
|
|
@@ -99,11 +99,11 @@ class System:
|
|
|
99
99
|
settings = list(self.settings.values())
|
|
100
100
|
for setting in sorted(settings, key=lambda s: s.order):
|
|
101
101
|
setting.to_xml(out, indent + "\t")
|
|
102
|
-
out.write("{
|
|
102
|
+
out.write(f"{indent}</system>\n")
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
# pylint: disable=too-many-instance-attributes
|
|
106
|
-
@dataclass(order=True, frozen=True)
|
|
105
|
+
# pylint: disable=too-many-instance-attributes
|
|
106
|
+
@dataclass(order=True, frozen=True, kw_only=True)
|
|
107
107
|
class Setting:
|
|
108
108
|
"""
|
|
109
109
|
Describes a setting for a system. This are command line options
|
|
@@ -117,7 +117,7 @@ class Setting:
|
|
|
117
117
|
order (int): An integer specifying the order of settings.
|
|
118
118
|
(This should denote the occurrence in the job specification.
|
|
119
119
|
Again in the scope of a system.)
|
|
120
|
-
|
|
120
|
+
dist_template (str): Path to dist-template file. (dist only, related to mpi-version)
|
|
121
121
|
attr (dict[str, Any]): A dictionary of additional optional attributes.
|
|
122
122
|
dist_options (Optional[str]): Additional dist options for this setting.
|
|
123
123
|
encodings (dict[str, set[str]]): Encodings used with this setting, keyed with tags.
|
|
@@ -127,7 +127,7 @@ class Setting:
|
|
|
127
127
|
cmdline: str = field(compare=False)
|
|
128
128
|
tag: set[str] = field(compare=False)
|
|
129
129
|
order: int = field(compare=False)
|
|
130
|
-
|
|
130
|
+
dist_template: str = field(compare=False)
|
|
131
131
|
attr: dict[str, Any] = field(compare=False)
|
|
132
132
|
|
|
133
133
|
dist_options: str = field(default="", compare=False)
|
|
@@ -142,25 +142,25 @@ class Setting:
|
|
|
142
142
|
indent (str): Amount of indentation.
|
|
143
143
|
"""
|
|
144
144
|
tag = " ".join(sorted(self.tag))
|
|
145
|
-
out.write('{
|
|
146
|
-
if self.
|
|
147
|
-
out.write('
|
|
145
|
+
out.write(f'{indent}<setting name="{self.name}" cmdline="{self.cmdline}" tag="{tag}"')
|
|
146
|
+
if self.dist_template is not None:
|
|
147
|
+
out.write(f' dist_template="{self.dist_template}"')
|
|
148
148
|
for key, val in self.attr.items():
|
|
149
|
-
out.write(' {
|
|
149
|
+
out.write(f' {key}="{val}"')
|
|
150
150
|
if self.dist_options != "":
|
|
151
|
-
out.write('
|
|
151
|
+
out.write(f' dist_options="{self.dist_options}"')
|
|
152
152
|
out.write(">\n")
|
|
153
153
|
for enctag, encodings in self.encodings.items():
|
|
154
154
|
for enc in sorted(encodings):
|
|
155
155
|
if enctag == "_default_":
|
|
156
|
-
out.write('{
|
|
156
|
+
out.write(f'{indent}\t<encoding file="{enc}"/>\n')
|
|
157
157
|
else:
|
|
158
|
-
out.write('{
|
|
159
|
-
out.write("{
|
|
158
|
+
out.write(f'{indent}\t<encoding file="{enc}" tag="{enctag}"/>\n')
|
|
159
|
+
out.write(f"{indent}</setting>\n")
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
@total_ordering
|
|
163
|
-
@dataclass(eq=False, frozen=True)
|
|
163
|
+
@dataclass(eq=False, frozen=True, kw_only=True)
|
|
164
164
|
class Job:
|
|
165
165
|
"""
|
|
166
166
|
Base class for all jobs.
|
|
@@ -168,7 +168,9 @@ class Job:
|
|
|
168
168
|
Attributes:
|
|
169
169
|
name (str): A unique name for a job.
|
|
170
170
|
timeout (int): A timeout in seconds for individual benchmark runs.
|
|
171
|
+
memout (int): A memory limit in MB for individual benchmark runs (20GB).
|
|
171
172
|
runs (int): The number of runs per benchmark.
|
|
173
|
+
template_options (str): Template options.
|
|
172
174
|
attr (dict[str, Any]): A dictionary of arbitrary attributes.
|
|
173
175
|
"""
|
|
174
176
|
|
|
@@ -176,6 +178,8 @@ class Job:
|
|
|
176
178
|
timeout: int = field(compare=False)
|
|
177
179
|
runs: int = field(compare=False)
|
|
178
180
|
attr: dict[str, Any] = field(compare=False)
|
|
181
|
+
memout: int = field(compare=False, default=20000)
|
|
182
|
+
template_options: str = field(compare=False, default="")
|
|
179
183
|
|
|
180
184
|
def __eq__(self, other: Any) -> bool:
|
|
181
185
|
if not isinstance(other, Job):
|
|
@@ -201,10 +205,11 @@ class Job:
|
|
|
201
205
|
extra (str): Additional arguments for the job.
|
|
202
206
|
"""
|
|
203
207
|
out.write(
|
|
204
|
-
'{
|
|
208
|
+
f'{indent}<{xmltag} name="{self.name}" timeout="{self.timeout}" memout="{self.memout}" '
|
|
209
|
+
f'runs="{self.runs}" template_options="{self.template_options}"{extra}'
|
|
205
210
|
)
|
|
206
211
|
for key, val in self.attr.items():
|
|
207
|
-
out.write(' {
|
|
212
|
+
out.write(f' {key}="{val}"')
|
|
208
213
|
out.write("/>\n")
|
|
209
214
|
|
|
210
215
|
def script_gen(self) -> Any:
|
|
@@ -214,74 +219,6 @@ class Job:
|
|
|
214
219
|
raise NotImplementedError
|
|
215
220
|
|
|
216
221
|
|
|
217
|
-
# pylint: disable=too-few-public-methods
|
|
218
|
-
@dataclass
|
|
219
|
-
class Run:
|
|
220
|
-
"""
|
|
221
|
-
Base class for all runs.
|
|
222
|
-
|
|
223
|
-
Attributes:
|
|
224
|
-
path (str): Path that holds the target location for start scripts.
|
|
225
|
-
root (str): directory relative to the location of the run's path.
|
|
226
|
-
"""
|
|
227
|
-
|
|
228
|
-
path: str
|
|
229
|
-
|
|
230
|
-
root: str = field(init=False)
|
|
231
|
-
|
|
232
|
-
def __post_init__(self) -> None:
|
|
233
|
-
self.root = os.path.relpath(".", self.path)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# pylint: disable=too-many-instance-attributes, too-many-positional-arguments, too-few-public-methods
|
|
237
|
-
@dataclass
|
|
238
|
-
class SeqRun(Run):
|
|
239
|
-
"""
|
|
240
|
-
Describes a sequential run.
|
|
241
|
-
|
|
242
|
-
Attributes:
|
|
243
|
-
path (str): Path that holds the target location for start scripts.
|
|
244
|
-
run (int): The number of the run.
|
|
245
|
-
job (Job): A reference to the job description.
|
|
246
|
-
runspec (Runspec): A reference to the run description.
|
|
247
|
-
instance (Benchmark.Instance): A reference to the instance to benchmark.
|
|
248
|
-
root (str): Directory relative to the location of the run's path.
|
|
249
|
-
files (str): Relative paths to all instances.
|
|
250
|
-
encodings (str): Relative paths to all encodings.
|
|
251
|
-
args (str): The command line arguments for this run.
|
|
252
|
-
solver (str): The solver for this run.
|
|
253
|
-
timeout (int): The timeout of this run.
|
|
254
|
-
memout (int): The memory limit of this run.
|
|
255
|
-
"""
|
|
256
|
-
|
|
257
|
-
run: int
|
|
258
|
-
job: "Job"
|
|
259
|
-
runspec: "Runspec"
|
|
260
|
-
instance: "Benchmark.Instance"
|
|
261
|
-
|
|
262
|
-
files: str = field(init=False)
|
|
263
|
-
encodings: str = field(init=False)
|
|
264
|
-
args: str = field(init=False)
|
|
265
|
-
solver: str = field(init=False)
|
|
266
|
-
timeout: int = field(init=False)
|
|
267
|
-
memout: int = field(init=False)
|
|
268
|
-
|
|
269
|
-
def __post_init__(self) -> None:
|
|
270
|
-
super().__post_init__()
|
|
271
|
-
self.files = " ".join([f'"{os.path.relpath(i, self.path)}"' for i in sorted(self.instance.paths())])
|
|
272
|
-
|
|
273
|
-
encodings = self.instance.encodings
|
|
274
|
-
encodings = encodings.union(self.runspec.setting.encodings.get("_default_", set()))
|
|
275
|
-
for i in self.instance.enctags:
|
|
276
|
-
encodings = encodings.union(self.runspec.setting.encodings.get(i, set()))
|
|
277
|
-
self.encodings = " ".join([f'"{os.path.relpath(e, self.path)}"' for e in sorted(encodings)])
|
|
278
|
-
|
|
279
|
-
self.args = self.runspec.setting.cmdline
|
|
280
|
-
self.solver = self.runspec.system.name + "-" + self.runspec.system.version
|
|
281
|
-
self.timeout = self.job.timeout
|
|
282
|
-
self.memout = int(self.job.attr.get("memout", 20000))
|
|
283
|
-
|
|
284
|
-
|
|
285
222
|
class ScriptGen:
|
|
286
223
|
"""
|
|
287
224
|
A class providing basic functionality to generate
|
|
@@ -317,7 +254,7 @@ class ScriptGen:
|
|
|
317
254
|
instance (Benchmark.Instance): The benchmark instance for the start script.
|
|
318
255
|
run (int): The number of the run for the start script.
|
|
319
256
|
"""
|
|
320
|
-
return os.path.join(runspec.path(), instance.benchclass.name, instance.name, "run
|
|
257
|
+
return os.path.join(runspec.path(), instance.benchclass.name, instance.name, f"run{run}")
|
|
321
258
|
|
|
322
259
|
def add_to_script(self, runspec: "Runspec", instance: "Benchmark.Instance") -> None:
|
|
323
260
|
"""
|
|
@@ -338,13 +275,41 @@ class ScriptGen:
|
|
|
338
275
|
continue
|
|
339
276
|
with open(runspec.system.config.template, "r", encoding="utf8") as f:
|
|
340
277
|
template = f.read()
|
|
278
|
+
|
|
279
|
+
template_options = self.job.template_options
|
|
280
|
+
if template_options != "":
|
|
281
|
+
template_options = " \\\n\t".join(template_options.split(","))
|
|
282
|
+
encodings = instance.encodings
|
|
283
|
+
encodings = encodings.union(runspec.setting.encodings.get("_default_", set()))
|
|
284
|
+
for i in instance.enctags:
|
|
285
|
+
encodings = encodings.union(runspec.setting.encodings.get(i, set()))
|
|
286
|
+
encodings_str = " ".join([f'"{os.path.relpath(e, path)}"' for e in sorted(encodings)])
|
|
287
|
+
|
|
341
288
|
with open(startpath, "w", encoding="utf8") as startfile:
|
|
342
|
-
startfile.write(
|
|
289
|
+
startfile.write(
|
|
290
|
+
template.format(
|
|
291
|
+
root=os.path.relpath(".", path),
|
|
292
|
+
options=template_options,
|
|
293
|
+
memout=self.job.memout,
|
|
294
|
+
timeout=self.job.timeout,
|
|
295
|
+
solver=runspec.system.name + "-" + runspec.system.version,
|
|
296
|
+
args=runspec.setting.cmdline,
|
|
297
|
+
files=" ".join([f'"{os.path.relpath(i, path)}"' for i in sorted(instance.paths())]),
|
|
298
|
+
encodings=encodings_str,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
343
301
|
self.startfiles.append((runspec, path, "start.sh"))
|
|
344
302
|
tools.set_executable(startpath)
|
|
345
303
|
|
|
346
304
|
def eval_results(
|
|
347
|
-
self,
|
|
305
|
+
self,
|
|
306
|
+
*,
|
|
307
|
+
out: Any,
|
|
308
|
+
indent: str,
|
|
309
|
+
runspec: "Runspec",
|
|
310
|
+
instance: "Benchmark.Instance",
|
|
311
|
+
result_parser: Optional[ModuleType],
|
|
312
|
+
parx: int = 2,
|
|
348
313
|
) -> None:
|
|
349
314
|
"""
|
|
350
315
|
Parses the results of a given benchmark instance and outputs them as XML.
|
|
@@ -357,44 +322,12 @@ class ScriptGen:
|
|
|
357
322
|
parx (int): Factor for penalized-average-runtime score.
|
|
358
323
|
"""
|
|
359
324
|
|
|
360
|
-
def import_from_path(module_name: str, file_path: str) -> ModuleType: # nocoverage
|
|
361
|
-
"""
|
|
362
|
-
Helper function to import modules from path.
|
|
363
|
-
|
|
364
|
-
Attributes:
|
|
365
|
-
module_name (str): Name of the module.
|
|
366
|
-
file_path (str): Path to the module.
|
|
367
|
-
"""
|
|
368
|
-
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
369
|
-
assert spec is not None
|
|
370
|
-
module = importlib.util.module_from_spec(spec)
|
|
371
|
-
sys.modules[module_name] = module
|
|
372
|
-
assert spec.loader is not None
|
|
373
|
-
spec.loader.exec_module(module)
|
|
374
|
-
return module
|
|
375
|
-
|
|
376
|
-
result_parser: Optional[ModuleType] = None
|
|
377
|
-
# dynamicly import result parser
|
|
378
|
-
# prioritize local resultparsers over included in package
|
|
379
|
-
rp_name = "{0}".format(runspec.system.measures)
|
|
380
|
-
try:
|
|
381
|
-
result_parser = import_from_path(
|
|
382
|
-
rp_name, os.path.join(os.getcwd(), "resultparsers", "{0}.py".format(runspec.system.measures))
|
|
383
|
-
)
|
|
384
|
-
except FileNotFoundError:
|
|
385
|
-
try:
|
|
386
|
-
result_parser = importlib.import_module(f"benchmarktool.resultparser.{rp_name}")
|
|
387
|
-
except ModuleNotFoundError:
|
|
388
|
-
sys.stderr.write(
|
|
389
|
-
f"*** ERROR: Result parser import failed: {rp_name}! "
|
|
390
|
-
"All runs using this parser will have no measures recorded!\n."
|
|
391
|
-
)
|
|
392
325
|
for run in range(1, self.job.runs + 1):
|
|
393
|
-
out.write('{
|
|
326
|
+
out.write(f'{indent}<run number="{run}">\n')
|
|
394
327
|
# result parser call
|
|
395
328
|
try:
|
|
396
329
|
result: dict[str, tuple[str, Any]] = result_parser.parse( # type: ignore
|
|
397
|
-
self._path(runspec, instance, run), runspec, instance
|
|
330
|
+
self._path(runspec, instance, run), runspec, instance, run
|
|
398
331
|
)
|
|
399
332
|
# penalized-average-runtime score
|
|
400
333
|
if all(key in result for key in ["time", "timeout"]):
|
|
@@ -402,13 +335,12 @@ class ScriptGen:
|
|
|
402
335
|
result[f"par{parx}"] = ("float", value)
|
|
403
336
|
|
|
404
337
|
for key, (valtype, val) in sorted(result.items()):
|
|
405
|
-
out.write(
|
|
406
|
-
'{0}<measure name="{1}" type="{2}" val="{3}"/>\n'.format(indent + "\t", key, valtype, val)
|
|
407
|
-
)
|
|
338
|
+
out.write(f'{indent}\t<measure name="{key}" type="{valtype}" val="{val}"/>\n')
|
|
408
339
|
except AttributeError:
|
|
340
|
+
# skip if parser import failed or no parse function is defined
|
|
409
341
|
pass
|
|
410
342
|
|
|
411
|
-
out.write("{
|
|
343
|
+
out.write(f"{indent}</run>\n")
|
|
412
344
|
|
|
413
345
|
|
|
414
346
|
class SeqScriptGen(ScriptGen):
|
|
@@ -447,7 +379,7 @@ class SeqScriptGen(ScriptGen):
|
|
|
447
379
|
comma = True
|
|
448
380
|
queue += repr(os.path.join(relpath, instname))
|
|
449
381
|
startfile.write(
|
|
450
|
-
"""\
|
|
382
|
+
f"""\
|
|
451
383
|
#!/usr/bin/python -u
|
|
452
384
|
|
|
453
385
|
import optparse
|
|
@@ -458,7 +390,7 @@ import sys
|
|
|
458
390
|
import signal
|
|
459
391
|
import time
|
|
460
392
|
|
|
461
|
-
queue = [{
|
|
393
|
+
queue = [{queue}]
|
|
462
394
|
|
|
463
395
|
class Main:
|
|
464
396
|
def __init__(self):
|
|
@@ -469,7 +401,7 @@ class Main:
|
|
|
469
401
|
self.finished = threading.Condition()
|
|
470
402
|
self.coreLock = threading.Lock()
|
|
471
403
|
c = 0
|
|
472
|
-
while len(self.cores) < {
|
|
404
|
+
while len(self.cores) < {self.job.parallel}:
|
|
473
405
|
self.cores.add(c)
|
|
474
406
|
c += 1
|
|
475
407
|
|
|
@@ -488,7 +420,7 @@ class Main:
|
|
|
488
420
|
thread = Run(cmd, self, core)
|
|
489
421
|
self.started += 1
|
|
490
422
|
self.running.add(thread)
|
|
491
|
-
print("({{
|
|
423
|
+
print(f"({{len(self.running)}}/{{self.started}}/{{self.total}}/{{core}}) {{cmd}}")
|
|
492
424
|
thread.start()
|
|
493
425
|
|
|
494
426
|
def run(self, queue):
|
|
@@ -498,7 +430,7 @@ class Main:
|
|
|
498
430
|
self.finished.acquire()
|
|
499
431
|
self.total = len(queue)
|
|
500
432
|
for cmd in queue:
|
|
501
|
-
while len(self.running) >= {
|
|
433
|
+
while len(self.running) >= {self.job.parallel}:
|
|
502
434
|
self.finished.wait()
|
|
503
435
|
self.start(cmd)
|
|
504
436
|
while len(self.running) != 0:
|
|
@@ -595,9 +527,7 @@ if __name__ == '__main__':
|
|
|
595
527
|
|
|
596
528
|
m = Main()
|
|
597
529
|
m.run(queue)
|
|
598
|
-
"""
|
|
599
|
-
queue, self.job.parallel
|
|
600
|
-
)
|
|
530
|
+
"""
|
|
601
531
|
)
|
|
602
532
|
tools.set_executable(os.path.join(path, "start.py"))
|
|
603
533
|
|
|
@@ -638,13 +568,13 @@ class DistScriptGen(ScriptGen):
|
|
|
638
568
|
assert isinstance(self.runspec.project, Project)
|
|
639
569
|
assert isinstance(self.runspec.project.job, DistJob)
|
|
640
570
|
self.num = 0
|
|
641
|
-
with open(self.runspec.setting.
|
|
571
|
+
with open(self.runspec.setting.dist_template, "r", encoding="utf8") as f:
|
|
642
572
|
template = f.read()
|
|
643
|
-
script = os.path.join(self.path, "start{
|
|
573
|
+
script = os.path.join(self.path, f"start{len(self.queue):04}.dist")
|
|
644
574
|
if self.runspec.setting.dist_options != "":
|
|
645
|
-
|
|
575
|
+
dist_options = "\n".join(self.runspec.setting.dist_options.split(",")) + "\n"
|
|
646
576
|
else:
|
|
647
|
-
|
|
577
|
+
dist_options = ""
|
|
648
578
|
with open(script, "w", encoding="utf8") as f:
|
|
649
579
|
f.write(
|
|
650
580
|
template.format(
|
|
@@ -652,7 +582,7 @@ class DistScriptGen(ScriptGen):
|
|
|
652
582
|
jobs=self.startscripts,
|
|
653
583
|
cpt=self.runspec.project.job.cpt,
|
|
654
584
|
partition=self.runspec.project.job.partition,
|
|
655
|
-
dist_options=
|
|
585
|
+
dist_options=dist_options,
|
|
656
586
|
)
|
|
657
587
|
)
|
|
658
588
|
self.queue.append(script)
|
|
@@ -703,7 +633,7 @@ class DistScriptGen(ScriptGen):
|
|
|
703
633
|
relpath = os.path.relpath(instpath, path)
|
|
704
634
|
job_script = os.path.join(relpath, instname)
|
|
705
635
|
dist_key = (
|
|
706
|
-
runspec.setting.
|
|
636
|
+
runspec.setting.dist_template,
|
|
707
637
|
runspec.setting.dist_options,
|
|
708
638
|
runspec.project.job.walltime,
|
|
709
639
|
runspec.project.job.cpt,
|
|
@@ -731,22 +661,23 @@ class DistScriptGen(ScriptGen):
|
|
|
731
661
|
|
|
732
662
|
with open(os.path.join(path, "start.sh"), "w", encoding="utf8") as startfile:
|
|
733
663
|
startfile.write(
|
|
734
|
-
|
|
735
|
-
+ "\n".join(['sbatch "{0}"'.format(os.path.basename(x)) for x in queue])
|
|
664
|
+
'#!/bin/bash\n\ncd "$(dirname $0)"\n' + "\n".join([f'sbatch "{os.path.basename(x)}"' for x in queue])
|
|
736
665
|
)
|
|
737
666
|
tools.set_executable(os.path.join(path, "start.sh"))
|
|
738
667
|
|
|
739
668
|
|
|
740
|
-
@dataclass(eq=False, frozen=True)
|
|
669
|
+
@dataclass(eq=False, frozen=True, kw_only=True)
|
|
741
670
|
class SeqJob(Job):
|
|
742
671
|
"""
|
|
743
672
|
Describes a sequential job.
|
|
744
673
|
|
|
745
674
|
Attributes:
|
|
746
|
-
name (str):
|
|
747
|
-
timeout (int):
|
|
748
|
-
|
|
749
|
-
|
|
675
|
+
name (str): A unique name for a job.
|
|
676
|
+
timeout (int): A timeout in seconds for individual benchmark runs.
|
|
677
|
+
memout (int): A memory limit in MB for individual benchmark runs (20GB).
|
|
678
|
+
runs (int): The number of runs per benchmark.
|
|
679
|
+
template_options (str): Template options.
|
|
680
|
+
attr (dict[str, Any]): A dictionary of arbitrary attributes.
|
|
750
681
|
parallel (int): The number of runs that can be started in parallel.
|
|
751
682
|
"""
|
|
752
683
|
|
|
@@ -767,20 +698,22 @@ class SeqJob(Job):
|
|
|
767
698
|
out (Any): Output stream to write to.
|
|
768
699
|
indent (str): Amount of indentation.
|
|
769
700
|
"""
|
|
770
|
-
extra = ' parallel="{
|
|
701
|
+
extra = f' parallel="{self.parallel}"'
|
|
771
702
|
Job._to_xml(self, out, indent, "seqjob", extra)
|
|
772
703
|
|
|
773
704
|
|
|
774
|
-
@dataclass(eq=False, frozen=True)
|
|
705
|
+
@dataclass(eq=False, frozen=True, kw_only=True)
|
|
775
706
|
class DistJob(Job):
|
|
776
707
|
"""
|
|
777
708
|
Describes a dist job.
|
|
778
709
|
|
|
779
710
|
Attributes:
|
|
780
|
-
name (str):
|
|
781
|
-
timeout (int):
|
|
782
|
-
|
|
783
|
-
|
|
711
|
+
name (str): A unique name for a job.
|
|
712
|
+
timeout (int): A timeout in seconds for individual benchmark runs.
|
|
713
|
+
memout (int): A memory limit in MB for individual benchmark runs (20GB).
|
|
714
|
+
runs (int): The number of runs per benchmark.
|
|
715
|
+
template_options (str): Template options.
|
|
716
|
+
attr (dict[str, Any]): A dictionary of arbitrary attributes.
|
|
784
717
|
script_mode (str): Specifies the script generation mode.
|
|
785
718
|
walltime (int): The walltime for a distributed job.
|
|
786
719
|
cpt (int): Number of cpus per task for distributed jobs.
|
|
@@ -790,7 +723,7 @@ class DistJob(Job):
|
|
|
790
723
|
script_mode: str = field(compare=False)
|
|
791
724
|
walltime: int = field(compare=False)
|
|
792
725
|
cpt: int = field(compare=False)
|
|
793
|
-
partition: str = field(compare=False)
|
|
726
|
+
partition: str = field(compare=False, default="kr")
|
|
794
727
|
|
|
795
728
|
def to_xml(self, out: Any, indent: str) -> None:
|
|
796
729
|
"""
|
|
@@ -800,8 +733,9 @@ class DistJob(Job):
|
|
|
800
733
|
out (Any): Output stream to write to
|
|
801
734
|
indent (str): Amount of indentation
|
|
802
735
|
"""
|
|
803
|
-
extra =
|
|
804
|
-
self
|
|
736
|
+
extra = (
|
|
737
|
+
f' script_mode="{self.script_mode}" walltime="{self.walltime}" cpt="{self.cpt}" '
|
|
738
|
+
f'partition="{self.partition}"'
|
|
805
739
|
)
|
|
806
740
|
Job._to_xml(self, out, indent, "distjob", extra)
|
|
807
741
|
|
|
@@ -835,7 +769,7 @@ class Config:
|
|
|
835
769
|
out (Any): Output stream to write to.
|
|
836
770
|
indent (str): Amount of indentation.
|
|
837
771
|
"""
|
|
838
|
-
out.write('{
|
|
772
|
+
out.write(f'{indent}<config name="{self.name}" template="{self.template}"/>\n')
|
|
839
773
|
|
|
840
774
|
|
|
841
775
|
@dataclass(order=True, unsafe_hash=True)
|
|
@@ -899,10 +833,10 @@ class Benchmark:
|
|
|
899
833
|
out (Any): Output stream to write to
|
|
900
834
|
indent (str): Amount of indentation
|
|
901
835
|
"""
|
|
902
|
-
out.write('{
|
|
836
|
+
out.write(f'{indent}<instance name="{self.name}" id="{self.id}">\n')
|
|
903
837
|
for instance in sorted(self.files):
|
|
904
|
-
out.write('{
|
|
905
|
-
out.write("{
|
|
838
|
+
out.write(f'{indent}\t<file name="{instance}"/>\n')
|
|
839
|
+
out.write(f"{indent}</instance>\n")
|
|
906
840
|
|
|
907
841
|
def paths(self) -> Iterator[str]:
|
|
908
842
|
"""
|
|
@@ -994,11 +928,12 @@ class Benchmark:
|
|
|
994
928
|
for filename in files:
|
|
995
929
|
if self._skip(relroot, filename):
|
|
996
930
|
continue
|
|
997
|
-
m = re.match(r"^(([
|
|
931
|
+
m = re.match(r"^(([^.]+(?:\.[^.]+)*?)(?:\.[^.]+)?)\.[^.]+$", filename)
|
|
998
932
|
if m is None:
|
|
999
|
-
|
|
933
|
+
sys.stderr.write(f"*** WARNING: skipping invalid file name: {filename}\n")
|
|
934
|
+
continue
|
|
1000
935
|
if self.group:
|
|
1001
|
-
# remove file
|
|
936
|
+
# remove last 2 file extensions, file.1.txt -> file
|
|
1002
937
|
group = m.group(2)
|
|
1003
938
|
else:
|
|
1004
939
|
# remove last file extension, file.1.txt -> file.1
|
|
@@ -1007,7 +942,13 @@ class Benchmark:
|
|
|
1007
942
|
instances[group] = set()
|
|
1008
943
|
instances[group].add(filename)
|
|
1009
944
|
for group, instfiles in instances.items():
|
|
1010
|
-
benchmark.add_instance(
|
|
945
|
+
benchmark.add_instance(
|
|
946
|
+
root=self.path,
|
|
947
|
+
relroot=relroot,
|
|
948
|
+
files=(group, instfiles),
|
|
949
|
+
encodings=self.encodings,
|
|
950
|
+
enctags=self.enctags,
|
|
951
|
+
)
|
|
1011
952
|
|
|
1012
953
|
class Files:
|
|
1013
954
|
"""
|
|
@@ -1035,9 +976,10 @@ class Benchmark:
|
|
|
1035
976
|
group (Optional[str]): Instance group.
|
|
1036
977
|
"""
|
|
1037
978
|
if group is None:
|
|
1038
|
-
m = re.match(r"^(([
|
|
979
|
+
m = re.match(r"^([^.]+(?:\.[^.]+)*)\.[^.]+$", os.path.basename(path))
|
|
1039
980
|
if m is None:
|
|
1040
|
-
|
|
981
|
+
sys.stderr.write(f"*** WARNING: skipping invalid file name: {path}\n")
|
|
982
|
+
return
|
|
1041
983
|
# remove file extension, file.1.txt -> file.1
|
|
1042
984
|
group = m.group(1)
|
|
1043
985
|
if group not in self.files:
|
|
@@ -1073,15 +1015,23 @@ class Benchmark:
|
|
|
1073
1015
|
benchmark (Benchmark): The benchmark to be populated.
|
|
1074
1016
|
"""
|
|
1075
1017
|
for group, files in self.files.items():
|
|
1076
|
-
for file in files:
|
|
1077
|
-
|
|
1078
|
-
|
|
1018
|
+
if not all(os.path.exists(os.path.join(self.path, file)) for file in files):
|
|
1019
|
+
sys.stderr.write(f"*** WARNING: skipping instance '{group}' due to missing files!\n")
|
|
1020
|
+
continue
|
|
1079
1021
|
paths = list(map(os.path.split, sorted(files)))
|
|
1080
1022
|
if len(set(map(lambda x: x[0], paths))) != 1:
|
|
1081
|
-
|
|
1023
|
+
sys.stderr.write(
|
|
1024
|
+
f"*** WARNING: skipping instance '{group}' due to inconsistent file paths!\n"
|
|
1025
|
+
"Grouped files must be located in the same folder.\n"
|
|
1026
|
+
)
|
|
1027
|
+
continue
|
|
1082
1028
|
relroot = paths[0][0]
|
|
1083
1029
|
benchmark.add_instance(
|
|
1084
|
-
self.path,
|
|
1030
|
+
root=self.path,
|
|
1031
|
+
relroot=relroot,
|
|
1032
|
+
files=(group, set(map(lambda x: x[1], paths))),
|
|
1033
|
+
encodings=self.encodings,
|
|
1034
|
+
enctags=self.enctags,
|
|
1085
1035
|
)
|
|
1086
1036
|
|
|
1087
1037
|
def add_element(self, element: Any) -> None:
|
|
@@ -1094,7 +1044,7 @@ class Benchmark:
|
|
|
1094
1044
|
self.elements.append(element)
|
|
1095
1045
|
|
|
1096
1046
|
def add_instance(
|
|
1097
|
-
self, root: str, relroot: str, files: tuple[str, set[str]], encodings: set[str], enctags: set[str]
|
|
1047
|
+
self, *, root: str, relroot: str, files: tuple[str, set[str]], encodings: set[str], enctags: set[str]
|
|
1098
1048
|
) -> None:
|
|
1099
1049
|
"""
|
|
1100
1050
|
Adds an instance to the benchmark set. (This function
|
|
@@ -1152,14 +1102,14 @@ class Benchmark:
|
|
|
1152
1102
|
indent (str): Amount of indentation.
|
|
1153
1103
|
"""
|
|
1154
1104
|
self.init()
|
|
1155
|
-
out.write('{
|
|
1105
|
+
out.write(f'{indent}<benchmark name="{self.name}">\n')
|
|
1156
1106
|
for classname in sorted(self.instances.keys()):
|
|
1157
1107
|
instances = self.instances[classname]
|
|
1158
|
-
out.write('{
|
|
1108
|
+
out.write(f'{indent}\t<class name="{classname.name}" id="{classname.id}">\n')
|
|
1159
1109
|
for instance in sorted(instances):
|
|
1160
1110
|
instance.to_xml(out, indent + "\t\t")
|
|
1161
|
-
out.write("{
|
|
1162
|
-
out.write("{
|
|
1111
|
+
out.write(f"{indent}\t</class>\n")
|
|
1112
|
+
out.write(f"{indent}</benchmark>\n")
|
|
1163
1113
|
|
|
1164
1114
|
|
|
1165
1115
|
@dataclass(order=True, frozen=True)
|
|
@@ -1187,7 +1137,7 @@ class Runspec:
|
|
|
1187
1137
|
Returns an output path under which start scripts
|
|
1188
1138
|
and benchmark results are stored.
|
|
1189
1139
|
"""
|
|
1190
|
-
name = self.system.name
|
|
1140
|
+
name = f"{self.system.name}-{self.system.version}-{self.setting.name}"
|
|
1191
1141
|
return os.path.join(self.project.path(), self.machine.name, "results", self.benchmark.name, name)
|
|
1192
1142
|
|
|
1193
1143
|
def gen_scripts(self, script_gen: "ScriptGen") -> None:
|
|
@@ -1237,10 +1187,16 @@ class Project:
|
|
|
1237
1187
|
for system in self.runscript.systems.values():
|
|
1238
1188
|
for setting in system.settings.values():
|
|
1239
1189
|
if disj.match(setting.tag):
|
|
1240
|
-
self.add_runspec(
|
|
1190
|
+
self.add_runspec(
|
|
1191
|
+
machine_name=machine_name,
|
|
1192
|
+
system_name=system.name,
|
|
1193
|
+
system_version=system.version,
|
|
1194
|
+
setting_name=setting.name,
|
|
1195
|
+
benchmark_name=benchmark_name,
|
|
1196
|
+
)
|
|
1241
1197
|
|
|
1242
1198
|
def add_runspec(
|
|
1243
|
-
self, machine_name: str, system_name: str,
|
|
1199
|
+
self, *, machine_name: str, system_name: str, system_version: str, setting_name: str, benchmark_name: str
|
|
1244
1200
|
) -> None:
|
|
1245
1201
|
"""
|
|
1246
1202
|
Adds a run specification, described by machine, system+settings, and benchmark set,
|
|
@@ -1255,8 +1211,8 @@ class Project:
|
|
|
1255
1211
|
"""
|
|
1256
1212
|
runspec = Runspec(
|
|
1257
1213
|
self.runscript.machines[machine_name],
|
|
1258
|
-
self.runscript.systems[(system_name,
|
|
1259
|
-
self.runscript.systems[(system_name,
|
|
1214
|
+
self.runscript.systems[(system_name, system_version)],
|
|
1215
|
+
self.runscript.systems[(system_name, system_version)].settings[setting_name],
|
|
1260
1216
|
self.runscript.benchmarks[benchmark_name],
|
|
1261
1217
|
self,
|
|
1262
1218
|
)
|
|
@@ -1358,11 +1314,17 @@ class Runscript:
|
|
|
1358
1314
|
"""
|
|
1359
1315
|
self.projects[project.name] = project
|
|
1360
1316
|
|
|
1361
|
-
def gen_scripts(self, skip: bool) -> None:
|
|
1317
|
+
def gen_scripts(self, skip: bool, force: bool = False) -> None:
|
|
1362
1318
|
"""
|
|
1363
1319
|
Generates the start scripts for all benchmarks described by
|
|
1364
1320
|
this run script.
|
|
1365
1321
|
"""
|
|
1322
|
+
if os.path.isdir(self.output):
|
|
1323
|
+
if force:
|
|
1324
|
+
shutil.rmtree(self.output)
|
|
1325
|
+
else:
|
|
1326
|
+
sys.stderr.write("*** ERROR: Output directory already exists.\n")
|
|
1327
|
+
sys.exit(1)
|
|
1366
1328
|
for project in self.projects.values():
|
|
1367
1329
|
project.gen_scripts(skip)
|
|
1368
1330
|
|
|
@@ -1372,6 +1334,36 @@ class Runscript:
|
|
|
1372
1334
|
"""
|
|
1373
1335
|
return self.output
|
|
1374
1336
|
|
|
1337
|
+
def _get_result_parser(self, runspec: Runspec) -> Optional[ModuleType]: # nocoverage
|
|
1338
|
+
"""
|
|
1339
|
+
Helper function to obtain result parsers.
|
|
1340
|
+
|
|
1341
|
+
Attributes:
|
|
1342
|
+
runspec (Runspec): The run specification.
|
|
1343
|
+
"""
|
|
1344
|
+
result_parser: Optional[ModuleType] = None
|
|
1345
|
+
# dynamicly import result parser
|
|
1346
|
+
# prioritize local resultparsers over included in package
|
|
1347
|
+
rp_name = runspec.system.measures
|
|
1348
|
+
try:
|
|
1349
|
+
result_parser = tools.import_from_path(rp_name, os.path.join(os.getcwd(), "resultparsers", f"{rp_name}.py"))
|
|
1350
|
+
except FileNotFoundError:
|
|
1351
|
+
try:
|
|
1352
|
+
result_parser = importlib.import_module(f"benchmarktool.resultparser.{rp_name}")
|
|
1353
|
+
except ModuleNotFoundError:
|
|
1354
|
+
sys.stderr.write(
|
|
1355
|
+
f"*** WARNING: Import of resultparser '{rp_name}' for system "
|
|
1356
|
+
f"'{runspec.system.name}-{runspec.system.version}' failed! "
|
|
1357
|
+
"All runs using this parser will have no measures recorded!\n"
|
|
1358
|
+
)
|
|
1359
|
+
if result_parser is not None and not callable(getattr(result_parser, "parse", None)):
|
|
1360
|
+
sys.stderr.write(
|
|
1361
|
+
f"*** WARNING: Resultparser '{rp_name}' for system "
|
|
1362
|
+
f"'{runspec.system.name}-{runspec.system.version}' has no callable 'parse' function! "
|
|
1363
|
+
"All runs using this parser will have no measures recorded!\n"
|
|
1364
|
+
)
|
|
1365
|
+
return result_parser
|
|
1366
|
+
|
|
1375
1367
|
# pylint: disable=too-many-branches
|
|
1376
1368
|
def eval_results(self, out: Any, parx: int = 2) -> None:
|
|
1377
1369
|
"""
|
|
@@ -1416,24 +1408,31 @@ class Runscript:
|
|
|
1416
1408
|
|
|
1417
1409
|
for project in self.projects.values():
|
|
1418
1410
|
assert isinstance(project.job, (SeqJob, DistJob))
|
|
1419
|
-
out.write('\t<project name="{
|
|
1411
|
+
out.write(f'\t<project name="{project.name}" job="{project.job.name}">\n')
|
|
1420
1412
|
job_gen = project.job.script_gen()
|
|
1421
1413
|
jobs.add(project.job)
|
|
1422
1414
|
for runspecs in project.runspecs.values():
|
|
1423
1415
|
for runspec in runspecs:
|
|
1424
1416
|
out.write(
|
|
1425
1417
|
(
|
|
1426
|
-
'\t\t<runspec machine="{
|
|
1427
|
-
'version="{
|
|
1428
|
-
'setting="{
|
|
1429
|
-
)
|
|
1418
|
+
f'\t\t<runspec machine="{runspec.machine.name}" system="{runspec.system.name}" '
|
|
1419
|
+
f'version="{runspec.system.version}" benchmark="{runspec.benchmark.name}" '
|
|
1420
|
+
f'setting="{runspec.setting.name}">\n'
|
|
1421
|
+
)
|
|
1430
1422
|
)
|
|
1431
1423
|
for classname in sorted(runspec.benchmark.instances):
|
|
1432
|
-
out.write('\t\t\t<class id="{
|
|
1424
|
+
out.write(f'\t\t\t<class id="{classname.id}">\n')
|
|
1433
1425
|
instances = runspec.benchmark.instances[classname]
|
|
1434
1426
|
for instance in instances:
|
|
1435
|
-
out.write('\t\t\t\t<instance id="{
|
|
1436
|
-
job_gen.eval_results(
|
|
1427
|
+
out.write(f'\t\t\t\t<instance id="{instance.id}">\n')
|
|
1428
|
+
job_gen.eval_results(
|
|
1429
|
+
out=out,
|
|
1430
|
+
indent="\t\t\t\t\t",
|
|
1431
|
+
runspec=runspec,
|
|
1432
|
+
instance=instance,
|
|
1433
|
+
result_parser=self._get_result_parser(runspec),
|
|
1434
|
+
parx=parx,
|
|
1435
|
+
)
|
|
1437
1436
|
out.write("\t\t\t\t</instance>\n")
|
|
1438
1437
|
out.write("\t\t\t</class>\n")
|
|
1439
1438
|
out.write("\t\t</runspec>\n")
|