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.
@@ -32,25 +32,32 @@ PAR = 2
32
32
 
33
33
  # pylint: disable=unused-argument
34
34
  def parse(
35
- root: str, runspec: "runscript.Runspec", instance: "runscript.Benchmark.Instance"
35
+ path: str, runspec: "runscript.Runspec", instance: "runscript.Benchmark.Instance", run: int
36
36
  ) -> dict[str, tuple[str, Any]]:
37
37
  """
38
38
  Extracts some clasp statistics.
39
39
 
40
40
  Attributes:
41
- root (str): The folder with results.
41
+ path (str): The path to the run directory.
42
42
  runspec (Runspec): The run specification of the benchmark.
43
43
  instance (Benchmark.Instance): The benchmark instance.
44
+ run (int): The run number.
44
45
  """
45
46
  timeout = runspec.project.job.timeout
46
47
  res: dict[str, tuple[str, Any]] = {"time": ("float", timeout)}
47
48
  for f in ["runsolver.solver", "runsolver.watcher"]:
48
- with open(os.path.join(root, f), errors="ignore", encoding="utf-8") as file:
49
- for line in file:
50
- for val, reg in clasp_re.items():
51
- m = reg[1].match(line)
52
- if m:
53
- res[val] = (reg[0], float(m.group("val")) if reg[0] == "float" else m.group("val"))
49
+ try:
50
+ with open(os.path.join(path, f), errors="ignore", encoding="utf-8") as file:
51
+ for line in file:
52
+ for val, reg in clasp_re.items():
53
+ m = reg[1].match(line)
54
+ if m:
55
+ res[val] = (reg[0], float(m.group("val")) if reg[0] == "float" else m.group("val"))
56
+ except FileNotFoundError:
57
+ sys.stderr.write(
58
+ f"*** WARNING: Result file '{f}' not found for run {run} of instance '{instance.name}' "
59
+ f"for system '{runspec.system.name}-{runspec.system.version}'! ({path})\n"
60
+ )
54
61
 
55
62
  if "rstatus" in res and res["rstatus"][1] == "out of memory":
56
63
  res["error"] = ("string", "std::bad_alloc")
@@ -70,7 +77,11 @@ def parse(
70
77
  if timedout:
71
78
  res["time"] = ("float", timeout)
72
79
  if error:
73
- sys.stderr.write("*** ERROR: Run {0} failed with unrecognized status or error!\n".format(root))
80
+ sys.stderr.write(
81
+ f"*** WARNING: Run {run} of instance '{instance.name}' "
82
+ f"for system '{runspec.system.name}-{runspec.system.version}' "
83
+ f"failed with unrecognized status or error! ({path})\n"
84
+ )
74
85
  result["error"] = ("float", int(error))
75
86
  result["timeout"] = ("float", int(timedout))
76
87
  result["memout"] = ("float", int(memout))
@@ -6,6 +6,7 @@ representation in form of python classes.
6
6
 
7
7
  __author__ = "Roland Kaminski"
8
8
  import os
9
+ import sys
9
10
  from typing import Any
10
11
 
11
12
  from lxml import etree # type: ignore[import-untyped]
@@ -112,13 +113,14 @@ class Parser:
112
113
  </xs:sequence>
113
114
  <xs:attribute name="name" type="nameType" use="required"/>
114
115
  <xs:attribute name="cmdline" type="xs:string"/>
116
+ <xs:attribute name="cmdline_post" type="xs:string"/>
115
117
  <xs:attribute name="tag">
116
118
  <xs:simpleType>
117
119
  <xs:list itemType="nameType"/>
118
120
  </xs:simpleType>
119
121
  </xs:attribute>
120
- <xs:attribute name="disttemplate" type="xs:string"/>
121
- <xs:attribute name="distopts" type="xs:string"/>
122
+ <xs:attribute name="dist_template" type="xs:string"/>
123
+ <xs:attribute name="dist_options" type="xs:string"/>
122
124
  <xs:anyAttribute processContents="lax"/>
123
125
  </xs:complexType>
124
126
  </xs:element>
@@ -128,6 +130,7 @@ class Parser:
128
130
  <xs:attribute name="measures" type="nameType" use="required"/>
129
131
  <xs:attribute name="config" type="nameType" use="required"/>
130
132
  <xs:attribute name="cmdline" type="xs:string"/>
133
+ <xs:attribute name="cmdline_post" type="xs:string"/>
131
134
  </xs:complexType>
132
135
 
133
136
  <!-- generic attributes for jobs-->
@@ -136,6 +139,7 @@ class Parser:
136
139
  <xs:attribute name="timeout" type="timeType" use="required"/>
137
140
  <xs:attribute name="runs" type="xs:positiveInteger" use="required"/>
138
141
  <xs:attribute name="memout" type="xs:positiveInteger"/>
142
+ <xs:attribute name="template_options" type="xs:string"/>
139
143
  <xs:anyAttribute processContents="lax"/>
140
144
  </xs:attributeGroup>
141
145
 
@@ -326,145 +330,188 @@ class Parser:
326
330
  )
327
331
  schema = etree.XMLSchema(schemadoc)
328
332
 
329
- with open(file_name, "r", encoding="utf8") as file:
330
- doc = etree.parse(file)
331
- schema.assertValid(doc)
332
-
333
- root = doc.getroot()
334
- run = Runscript(root.get("output"))
333
+ try:
334
+ doc = etree.parse(file_name)
335
+ except (etree.XMLSyntaxError, OSError) as e:
336
+ if isinstance(e, OSError):
337
+ sys.stderr.write(f"*** ERROR: Runscript file '{file_name}' not found.\n")
338
+ sys.exit(1)
339
+ sys.stderr.write(f"*** ERROR: XML Syntax Error in runscript: {e}\n")
340
+ sys.exit(1)
335
341
 
336
- job: DistJob | SeqJob
337
-
338
- for node in root.xpath("./distjob"):
342
+ try:
343
+ schema.assertValid(doc)
344
+ except etree.DocumentInvalid as e:
345
+ sys.stderr.write(f"*** ERROR: Invalid runscript file: {e}\n")
346
+ sys.exit(1)
347
+
348
+ root = doc.getroot()
349
+ run = Runscript(root.get("output"))
350
+
351
+ for node in root.xpath("./distjob"):
352
+ run.add_job(self._parse_job(node, "distjob"))
353
+
354
+ for node in root.xpath("./seqjob"):
355
+ run.add_job(self._parse_job(node, "seqjob"))
356
+
357
+ for node in root.xpath("./machine"):
358
+ machine = Machine(node.get("name"), node.get("cpu"), node.get("memory"))
359
+ run.add_machine(machine)
360
+
361
+ for node in root.xpath("./config"):
362
+ config = Config(node.get("name"), node.get("template"))
363
+ run.add_config(config)
364
+
365
+ compound_settings: dict[str, list[str]] = {}
366
+ system_order = 0
367
+ for node in root.xpath("./system"):
368
+ config = run.configs[node.get("config")]
369
+ system = System(node.get("name"), node.get("version"), node.get("measures"), system_order, config)
370
+ setting_order = 0
371
+ sys_cmdline = node.get("cmdline")
372
+ sys_cmdline_post = node.get("cmdline_post")
373
+ for child in node.xpath("setting"):
339
374
  attr = self._filter_attr(
340
- node, ["name", "timeout", "runs", "script_mode", "walltime", "cpt", "partition"]
375
+ child, ["name", "cmdline", "cmdline_post", "tag", "dist_options", "dist_template"]
341
376
  )
342
-
343
- partition = node.get("partition")
344
- if partition is None:
345
- partition = "kr"
346
- job = DistJob(
347
- node.get("name"),
348
- tools.xml_to_seconds_time(node.get("timeout")),
349
- int(node.get("runs")),
350
- attr,
351
- node.get("script_mode"),
352
- tools.xml_to_seconds_time(node.get("walltime")),
353
- int(node.get("cpt")),
354
- partition,
377
+ compound_settings[child.get("name")] = []
378
+ dist_template = child.get("dist_template")
379
+ if dist_template is None:
380
+ dist_template = "templates/single.dist"
381
+ if child.get("tag") is None:
382
+ tag = set()
383
+ else:
384
+ tag = set(child.get("tag").split(None))
385
+ dist_options = child.get("dist_options")
386
+ if dist_options is None:
387
+ dist_options = ""
388
+ encodings: dict[str, set[str]] = {"_default_": set()}
389
+ for grandchild in child.xpath("./encoding"):
390
+ if grandchild.get("enctag") is None:
391
+ encodings["_default_"].add(os.path.normpath(grandchild.get("file")))
392
+ else:
393
+ enctags = set(grandchild.get("enctag").split(None))
394
+ for t in enctags:
395
+ if t not in encodings:
396
+ encodings[t] = set()
397
+ encodings[t].add(os.path.normpath(grandchild.get("file")))
398
+
399
+ cmdline = " ".join(
400
+ filter(None, [sys_cmdline, child.get("cmdline"), sys_cmdline_post, child.get("cmdline_post")])
355
401
  )
356
- run.add_job(job)
357
-
358
- for node in root.xpath("./seqjob"):
359
- attr = self._filter_attr(node, ["name", "timeout", "runs", "parallel"])
360
- job = SeqJob(
361
- node.get("name"),
362
- tools.xml_to_seconds_time(node.get("timeout")),
363
- int(node.get("runs")),
364
- attr,
365
- int(node.get("parallel")),
402
+ name = child.get("name")
403
+ compound_settings[child.get("name")].append(name)
404
+ keys = list(attr.keys())
405
+ if keys:
406
+ sys.stderr.write(
407
+ f"""*** INFO: Attribute{'s' if len(keys) > 1 else ''} {', '.join(f"'{k}'" for k in keys)} in setting '{name}' {'are' if len(keys) > 1 else 'is'} currently unused.\n"""
408
+ )
409
+ setting = Setting(
410
+ name=name,
411
+ cmdline=cmdline,
412
+ tag=tag,
413
+ order=setting_order,
414
+ dist_template=dist_template,
415
+ attr=attr,
416
+ dist_options=dist_options,
417
+ encodings=encodings,
366
418
  )
367
- run.add_job(job)
368
-
369
- for node in root.xpath("./machine"):
370
- machine = Machine(node.get("name"), node.get("cpu"), node.get("memory"))
371
- run.add_machine(machine)
372
-
373
- for node in root.xpath("./config"):
374
- config = Config(node.get("name"), node.get("template"))
375
- run.add_config(config)
376
-
377
- compound_settings: dict[str, list[str]] = {}
378
- system_order = 0
379
- for node in root.xpath("./system"):
380
- config = run.configs[node.get("config")]
381
- system = System(node.get("name"), node.get("version"), node.get("measures"), system_order, config)
382
- setting_order = 0
383
- sys_cmdline = node.get("cmdline")
384
- for child in node.xpath("setting"):
385
- attr = self._filter_attr(child, ["name", "cmdline", "tag", "distopts", "disttemplate"])
386
- compound_settings[child.get("name")] = []
387
- disttemplate = child.get("disttemplate")
388
- if disttemplate is None:
389
- disttemplate = "templates/single.dist"
390
- if child.get("tag") is None:
391
- tag = set()
392
- else:
393
- tag = set(child.get("tag").split(None))
394
- dist_options = child.get("distopts")
395
- if dist_options is None:
396
- dist_options = ""
397
- encodings: dict[str, set[str]] = {"_default_": set()}
398
- for grandchild in child.xpath("./encoding"):
399
- if grandchild.get("enctag") is None:
400
- encodings["_default_"].add(os.path.normpath(grandchild.get("file")))
401
- else:
402
- enctags = set(grandchild.get("enctag").split(None))
403
- for t in enctags:
404
- if t not in encodings:
405
- encodings[t] = set()
406
- encodings[t].add(os.path.normpath(grandchild.get("file")))
407
-
408
- cmdline = " ".join(filter(None, [sys_cmdline, child.get("cmdline")]))
409
- name = child.get("name")
410
- compound_settings[child.get("name")].append(name)
411
- setting = Setting(name, cmdline, tag, setting_order, disttemplate, attr, dist_options, encodings)
412
- system.add_setting(setting)
413
- setting_order += 1
414
-
415
- run.systems[(system.name, system.version)] = system
416
- system_order += 1
417
-
418
- element: Any
419
- for node in root.xpath("./benchmark"):
420
- benchmark = Benchmark(node.get("name"))
421
- for child in node.xpath("./folder"):
422
- if child.get("group") is not None:
423
- group = child.get("group").lower() == "true"
424
- else:
425
- group = False
426
- element = Benchmark.Folder(child.get("path"), group)
427
- if child.get("enctag") is None:
428
- tag = set()
429
- else:
430
- tag = set(child.get("enctag").split(None))
431
- element.add_enctags(tag)
432
- for grandchild in child.xpath("./encoding"):
433
- element.add_encoding(grandchild.get("file"))
434
- for grandchild in child.xpath("./ignore"):
435
- element.add_ignore(grandchild.get("prefix"))
436
- benchmark.add_element(element)
437
- for child in node.xpath("./files"):
438
- element = Benchmark.Files(child.get("path"))
439
- if child.get("enctag") is None:
440
- tag = set()
441
- else:
442
- tag = set(child.get("enctag").split(None))
443
- element.add_enctags(tag)
444
- for grandchild in child.xpath("./encoding"):
445
- element.add_encoding(grandchild.get("file"))
446
- for grandchild in child.xpath("./add"):
447
- element.add_file(grandchild.get("file"), grandchild.get("group"))
448
- benchmark.add_element(element)
449
- run.add_benchmark(benchmark)
450
-
451
- for node in root.xpath("./project"):
452
- project = Project(node.get("name"), run, run.jobs[node.get("job")])
453
- run.add_project(project)
454
- for child in node.xpath("./runspec"):
455
- for setting_name in compound_settings[child.get("setting")]:
456
- project.add_runspec(
457
- child.get("machine"),
458
- child.get("system"),
459
- child.get("version"),
460
- setting_name,
461
- child.get("benchmark"),
462
- )
463
-
464
- for child in node.xpath("./runtag"):
465
- project.add_runtag(child.get("machine"), child.get("benchmark"), child.get("tag"))
419
+ system.add_setting(setting)
420
+ setting_order += 1
421
+
422
+ run.systems[(system.name, system.version)] = system
423
+ system_order += 1
424
+
425
+ element: Any
426
+ for node in root.xpath("./benchmark"):
427
+ benchmark = Benchmark(node.get("name"))
428
+ for child in node.xpath("./folder"):
429
+ if child.get("group") is not None:
430
+ group = child.get("group").lower() == "true"
431
+ else:
432
+ group = False
433
+ element = Benchmark.Folder(child.get("path"), group)
434
+ if child.get("enctag") is None:
435
+ tag = set()
436
+ else:
437
+ tag = set(child.get("enctag").split(None))
438
+ element.add_enctags(tag)
439
+ for grandchild in child.xpath("./encoding"):
440
+ element.add_encoding(grandchild.get("file"))
441
+ for grandchild in child.xpath("./ignore"):
442
+ element.add_ignore(grandchild.get("prefix"))
443
+ benchmark.add_element(element)
444
+ for child in node.xpath("./files"):
445
+ element = Benchmark.Files(child.get("path"))
446
+ if child.get("enctag") is None:
447
+ tag = set()
448
+ else:
449
+ tag = set(child.get("enctag").split(None))
450
+ element.add_enctags(tag)
451
+ for grandchild in child.xpath("./encoding"):
452
+ element.add_encoding(grandchild.get("file"))
453
+ for grandchild in child.xpath("./add"):
454
+ element.add_file(grandchild.get("file"), grandchild.get("group"))
455
+ benchmark.add_element(element)
456
+ run.add_benchmark(benchmark)
457
+
458
+ for node in root.xpath("./project"):
459
+ project = Project(node.get("name"), run, run.jobs[node.get("job")])
460
+ run.add_project(project)
461
+ for child in node.xpath("./runspec"):
462
+ for setting_name in compound_settings[child.get("setting")]:
463
+ project.add_runspec(
464
+ machine_name=child.get("machine"),
465
+ system_name=child.get("system"),
466
+ system_version=child.get("version"),
467
+ setting_name=setting_name,
468
+ benchmark_name=child.get("benchmark"),
469
+ )
470
+
471
+ for child in node.xpath("./runtag"):
472
+ project.add_runtag(child.get("machine"), child.get("benchmark"), child.get("tag"))
473
+
474
+ self.validate_components(run)
466
475
  return run
467
476
 
477
+ def validate_components(self, run: Runscript) -> None:
478
+ """
479
+ Check runscript for the existence of all required components.
480
+ """
481
+ # machine
482
+ if not run.machines:
483
+ sys.stderr.write("*** WARNING: No machine defined in runscript.\n")
484
+
485
+ # config
486
+ if not run.configs:
487
+ sys.stderr.write("*** WARNING: No config defined in runscript.\n")
488
+
489
+ # system
490
+ if not run.systems:
491
+ sys.stderr.write("*** WARNING: No system defined in runscript.\n")
492
+
493
+ # setting
494
+ for system in run.systems.values():
495
+ if not system.settings:
496
+ sys.stderr.write(f"*** WARNING: No setting defined for system '{system.name}-{system.version}'.\n")
497
+
498
+ # job
499
+ if not run.jobs:
500
+ sys.stderr.write("*** WARNING: No job defined in runscript.\n")
501
+
502
+ # benchmark
503
+ if not run.benchmarks:
504
+ sys.stderr.write("*** WARNING: No benchmark defined in runscript.\n")
505
+
506
+ # instances
507
+ for benchmark in run.benchmarks.values():
508
+ if not benchmark.elements:
509
+ sys.stderr.write(f"*** WARNING: No instance folder/files defined for benchmark '{benchmark.name}'.\n")
510
+
511
+ # project
512
+ if not run.projects:
513
+ sys.stderr.write("*** WARNING: No project defined in runscript.\n")
514
+
468
515
  def _filter_attr(self, node: etree._Element, skip: list[str]) -> dict[str, Any]:
469
516
  """
470
517
  Returns a dictionary containing all attributes of a given node.
@@ -475,3 +522,57 @@ class Parser:
475
522
  if not key in skip:
476
523
  attr[key] = val
477
524
  return attr
525
+
526
+ def _parse_job(self, node: etree._Element, job_type: str) -> DistJob | SeqJob:
527
+ """
528
+ Parses a job node and returns the corresponding job instance.
529
+ """
530
+ attr_filter = ["name", "timeout", "memout", "runs", "template_options"]
531
+ kwargs = {
532
+ "name": node.get("name"),
533
+ "timeout": tools.xml_to_seconds_time(node.get("timeout")),
534
+ "runs": int(node.get("runs")),
535
+ }
536
+ memout = node.get("memout")
537
+ if memout is not None:
538
+ kwargs["memout"] = int(memout)
539
+ template_options = node.get("template_options")
540
+ if template_options is None:
541
+ template_options = ""
542
+ kwargs["template_options"] = template_options
543
+
544
+ if job_type == "distjob":
545
+ attr = self._filter_attr(node, attr_filter + ["script_mode", "walltime", "cpt", "partition"])
546
+ keys = list(attr.keys())
547
+ if keys:
548
+ sys.stderr.write(
549
+ f"""*** INFO: Attribute{'s' if len(keys) > 1 else ''} {', '.join(f"'{k}'" for k in keys)} in distjob '{node.get('name')}' {'are' if len(keys) > 1 else 'is'} currently unused.\n"""
550
+ )
551
+ kwargs.update(
552
+ {
553
+ "attr": attr,
554
+ "script_mode": node.get("script_mode"),
555
+ "walltime": tools.xml_to_seconds_time(node.get("walltime")),
556
+ "cpt": int(node.get("cpt")),
557
+ }
558
+ )
559
+ partition = node.get("partition")
560
+ if partition is not None:
561
+ kwargs["partition"] = partition
562
+ return DistJob(**kwargs) # pylint: disable=missing-kwoa
563
+ if job_type == "seqjob":
564
+ attr = self._filter_attr(node, attr_filter + ["parallel"])
565
+ keys = list(attr.keys())
566
+ if keys:
567
+ sys.stderr.write(
568
+ f"""*** INFO: Attribute{'s' if len(keys) > 1 else ''} {', '.join(f"'{k}'" for k in keys)} in seqjob '{node.get('name')}' {'are' if len(keys) > 1 else 'is'} currently unused.\n"""
569
+ )
570
+ kwargs.update(
571
+ {
572
+ "attr": attr,
573
+ "parallel": int(node.get("parallel")),
574
+ }
575
+ )
576
+ return SeqJob(**kwargs) # pylint: disable=missing-kwoa
577
+ # Should never happen, checked by xml schema
578
+ raise ValueError(f"Unknown job type: {job_type}") # nocoverage