cijoe 0.9.41__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.
Files changed (107) hide show
  1. cijoe/cli/__init__.py +0 -0
  2. cijoe/cli/__main__.py +3 -0
  3. cijoe/cli/cli.py +561 -0
  4. cijoe/core/__init__.py +1 -0
  5. cijoe/core/analyser.py +136 -0
  6. cijoe/core/auxiliary/__init__.py +0 -0
  7. cijoe/core/auxiliary/cijoe-completions +81 -0
  8. cijoe/core/auxiliary/example.perfreq +7 -0
  9. cijoe/core/command.py +261 -0
  10. cijoe/core/configs/__init__.py +0 -0
  11. cijoe/core/configs/default-config.toml +1 -0
  12. cijoe/core/configs/transport-ssh.toml +5 -0
  13. cijoe/core/errors.py +22 -0
  14. cijoe/core/misc.py +33 -0
  15. cijoe/core/processing.py +205 -0
  16. cijoe/core/resources.py +552 -0
  17. cijoe/core/scripts/__init__.py +0 -0
  18. cijoe/core/scripts/cmdrunner.py +27 -0
  19. cijoe/core/scripts/example.py +15 -0
  20. cijoe/core/scripts/get.py +26 -0
  21. cijoe/core/scripts/put.py +26 -0
  22. cijoe/core/scripts/reporter.py +82 -0
  23. cijoe/core/scripts/repository_prep.py +93 -0
  24. cijoe/core/scripts/testrunner.py +178 -0
  25. cijoe/core/selftest/__init__.py +0 -0
  26. cijoe/core/selftest/conftest.py +30 -0
  27. cijoe/core/selftest/test_collector.py +73 -0
  28. cijoe/core/selftest/test_commands.py +14 -0
  29. cijoe/core/selftest/test_transfer.py +23 -0
  30. cijoe/core/selftest/test_workflow.py +17 -0
  31. cijoe/core/templates/__init__.py +3 -0
  32. cijoe/core/templates/example-tmp-workflow.yaml.jinja2 +9 -0
  33. cijoe/core/templates/report-workflow.html +534 -0
  34. cijoe/core/transport.py +224 -0
  35. cijoe/core/workflows/__init__.py +3 -0
  36. cijoe/core/workflows/example-workflow.yaml +30 -0
  37. cijoe/core/worklets/__init__.py +0 -0
  38. cijoe/fio/__init__.py +0 -0
  39. cijoe/fio/configs/__init__.py +0 -0
  40. cijoe/fio/configs/default-config.toml +31 -0
  41. cijoe/fio/scripts/__init__.py +0 -0
  42. cijoe/fio/scripts/build.py +29 -0
  43. cijoe/fio/scripts/check.py +20 -0
  44. cijoe/fio/scripts/install.py +20 -0
  45. cijoe/fio/selftest/__init__.py +0 -0
  46. cijoe/fio/selftest/test_fio_wrapper.py +36 -0
  47. cijoe/fio/workflows/__init__.py +0 -0
  48. cijoe/fio/workflows/example.workflow +13 -0
  49. cijoe/fio/wrapper.py +277 -0
  50. cijoe/gha/__init__.py +0 -0
  51. cijoe/gha/configs/__init__.py +0 -0
  52. cijoe/gha/configs/default-config.toml +13 -0
  53. cijoe/gha/scripts/__init__.py +0 -0
  54. cijoe/gha/scripts/runner_download.py +58 -0
  55. cijoe/gha/scripts/runner_remove.py +57 -0
  56. cijoe/gha/scripts/runner_setup.py +83 -0
  57. cijoe/gha/workflows/__init__.py +0 -0
  58. cijoe/gha/workflows/example-workflow.yaml +19 -0
  59. cijoe/linux/__init__.py +0 -0
  60. cijoe/linux/configs/__init__.py +0 -0
  61. cijoe/linux/configs/default-config.toml +43 -0
  62. cijoe/linux/kmemleak.py +27 -0
  63. cijoe/linux/null_blk.py +46 -0
  64. cijoe/linux/scripts/__init__.py +0 -0
  65. cijoe/linux/scripts/build_kdebs.py +53 -0
  66. cijoe/linux/scripts/null_blk.py +33 -0
  67. cijoe/linux/scripts/sysinfo.py +36 -0
  68. cijoe/linux/scripts/transfer_and_install_kdebs.py +50 -0
  69. cijoe/linux/selftest/__init__.py +0 -0
  70. cijoe/linux/selftest/test_kmemleak.py +36 -0
  71. cijoe/linux/selftest/test_null_blk.py +40 -0
  72. cijoe/linux/workflows/__init__.py +0 -0
  73. cijoe/linux/workflows/example-null_blk.yaml +18 -0
  74. cijoe/linux/workflows/example-workflow.yaml +17 -0
  75. cijoe/pytest_plugin/__init__.py +0 -0
  76. cijoe/pytest_plugin/hooks_and_fixtures.py +110 -0
  77. cijoe/qemu/__init__.py +0 -0
  78. cijoe/qemu/auxiliary/__init__.py +0 -0
  79. cijoe/qemu/auxiliary/cloudinit-debian-meta.meta +2 -0
  80. cijoe/qemu/auxiliary/cloudinit-debian-user-amd64.user +27 -0
  81. cijoe/qemu/auxiliary/cloudinit-debian-user-arm64.user +24 -0
  82. cijoe/qemu/auxiliary/cloudinit-freebsd-meta.meta +2 -0
  83. cijoe/qemu/auxiliary/cloudinit-freebsd-user.user +24 -0
  84. cijoe/qemu/configs/__init__.py +0 -0
  85. cijoe/qemu/configs/debian-bookworm-amd64-kvm.toml +58 -0
  86. cijoe/qemu/configs/debian-bookworm-arm64-hvf.toml +58 -0
  87. cijoe/qemu/configs/debian-bullseye-amd64-kvm.toml +58 -0
  88. cijoe/qemu/configs/default-config.toml +58 -0
  89. cijoe/qemu/configs/freebsd-13-amd64-kvm.toml +58 -0
  90. cijoe/qemu/scripts/__init__.py +0 -0
  91. cijoe/qemu/scripts/build.py +64 -0
  92. cijoe/qemu/scripts/guest_init_using_bootimage.py +27 -0
  93. cijoe/qemu/scripts/guest_init_using_cloudinit.py +33 -0
  94. cijoe/qemu/scripts/guest_kill.py +17 -0
  95. cijoe/qemu/scripts/guest_start.py +31 -0
  96. cijoe/qemu/scripts/install.py +24 -0
  97. cijoe/qemu/selftest/__init__.py +0 -0
  98. cijoe/qemu/selftest/test_nop.py +2 -0
  99. cijoe/qemu/workflows/__init__.py +0 -0
  100. cijoe/qemu/workflows/example-workflow.yaml +44 -0
  101. cijoe/qemu/wrapper.py +323 -0
  102. cijoe-0.9.41.dist-info/LICENSE +29 -0
  103. cijoe-0.9.41.dist-info/METADATA +99 -0
  104. cijoe-0.9.41.dist-info/RECORD +107 -0
  105. cijoe-0.9.41.dist-info/WHEEL +5 -0
  106. cijoe-0.9.41.dist-info/entry_points.txt +5 -0
  107. cijoe-0.9.41.dist-info/top_level.txt +1 -0
cijoe/cli/__init__.py ADDED
File without changes
cijoe/cli/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from cijoe.cli import cli
2
+
3
+ cli.main()
cijoe/cli/cli.py ADDED
@@ -0,0 +1,561 @@
1
+ import argparse
2
+ import errno
3
+ import logging as log
4
+ import os
5
+ import shutil
6
+ import sys
7
+ import tempfile
8
+ import time
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ import jinja2
13
+
14
+ import cijoe.core
15
+ from cijoe.core.command import Cijoe, default_output_path
16
+ from cijoe.core.resources import (
17
+ Config,
18
+ Workflow,
19
+ dict_from_yamlfile,
20
+ dict_substitute,
21
+ get_resources,
22
+ )
23
+
24
+ DEFAULT_CONFIG_FILENAME = "cijoe-config.toml"
25
+ DEFAULT_WORKFLOW_FILENAME = "cijoe-workflow.yaml"
26
+ DEFAULT_SCRIPT_FILENAME = "cijoe-script.py"
27
+ SEARCH_PATHS = [
28
+ Path.cwd(),
29
+ Path.cwd() / ".cijoe",
30
+ Path.home() / ".config" / "cijoe",
31
+ Path.home() / ".cijoe",
32
+ ]
33
+
34
+
35
+ def search_for_file(path: Path) -> Optional[Path]:
36
+ """
37
+ Search for a file named path.name. In case the given 'path' does not
38
+ have a directory path, then SEARCH_PATH is used.
39
+
40
+ Returns a resolved Path object on success and None on error.
41
+ """
42
+
43
+ # Has a directory part; resolve it and check that it exists
44
+ if path.is_absolute() or len(path.parts) != 1:
45
+ path = path.resolve()
46
+ if path.exists():
47
+ return path
48
+
49
+ return None
50
+
51
+ # Only a filename; look in search paths
52
+ for spath in SEARCH_PATHS:
53
+ candidate = (spath / path).resolve()
54
+ if candidate.exists():
55
+ return candidate
56
+
57
+ return None
58
+
59
+
60
+ def log_errors(errors):
61
+ for error in errors:
62
+ log.error(error)
63
+
64
+
65
+ def cli_integrity_check(args):
66
+ """Lint a workflow"""
67
+
68
+ log.info("cli: lint")
69
+ log.info(f"workflow: '{args.workflow}'")
70
+ log.info(f"config: '{args.config}'")
71
+
72
+ workflow_dict = dict_from_yamlfile(args.workflow.resolve())
73
+ errors = Workflow.dict_normalize(workflow_dict) # Normalize it
74
+ errors += Workflow.dict_lint(workflow_dict) # Check the yaml-file
75
+
76
+ config = Config.from_path(args.config)
77
+ if not config:
78
+ log.error(f"failed: Config.from_path({args.config})")
79
+ return errno.EINVAL
80
+
81
+ errors += dict_substitute(workflow_dict, config.options)
82
+
83
+ if errors:
84
+ log_errors(errors)
85
+ log.error("failed: 'see errors above'; Failed")
86
+ return errno.EINVAL
87
+
88
+ return 0
89
+
90
+
91
+ def cli_resources(args):
92
+ """List the reference configuration files provided with cijoe packages"""
93
+
94
+ log.info("cli: resources")
95
+ resources = get_resources()
96
+
97
+ print("Resources collected by the CIJOE collector are listed below.")
98
+ for category, category_resources in sorted(resources.items()):
99
+ print(f"{category}:" + ("" if category_resources.items() else " ~"))
100
+
101
+ for ident, path in sorted(category_resources.items()):
102
+ print(f" - ident: {ident}")
103
+ print(f" path: {path}")
104
+
105
+ return 0
106
+
107
+
108
+ def cli_archive(args):
109
+ """Move 'output' directory into archive"""
110
+
111
+ if args.output.exists():
112
+ state_path = args.output / "workflow.state"
113
+ tag = ""
114
+ if state_path.exists():
115
+ state = dict_from_yamlfile(state_path)
116
+ tag = "" if state.get("tag", None) is None else "-".join(state["tag"]) + "-"
117
+ t = str(time.strftime("%Y-%m-%d_%H:%M:%S"))
118
+ archive = args.output.with_name("cijoe-archive") / f"{tag}{t}"
119
+ os.makedirs(archive)
120
+ log.info(f"moving existing output-directory({args.output}) to '{archive}'")
121
+ os.rename(args.output, archive)
122
+
123
+
124
+ def cli_produce_report(args):
125
+ """Produce workflow-report"""
126
+
127
+ config_path = args.output / "config.orig"
128
+ state_path = args.output / "workflow.state"
129
+
130
+ if not state_path.exists():
131
+ log.error("no workflow.state, nothing to produce a report for")
132
+ return errno.EINVAL
133
+
134
+ if not config_path.exists():
135
+ log.error("missing config")
136
+ return errno.EINVAL
137
+
138
+ # Check config/substitutions
139
+ config = Config.from_path(config_path)
140
+ if not config:
141
+ log.error(f"failed: Config.from_path({config_path})")
142
+ return errno.EINVAL
143
+
144
+ cijoe = Cijoe(config, args.output, args.monitor)
145
+
146
+ resources = get_resources()
147
+
148
+ reporter = resources["scripts"]["core.reporter"]
149
+ if reporter.func is None:
150
+ reporter.load()
151
+
152
+ return reporter.func(
153
+ args,
154
+ cijoe,
155
+ {
156
+ "name": "report",
157
+ "uses": "core.reporter",
158
+ "with": {"report_open": args.skip_report},
159
+ },
160
+ )
161
+
162
+
163
+ def cli_example(args):
164
+ """Create example config.toml and workflow.yaml"""
165
+
166
+ log.info("cli: examples")
167
+ err = 0
168
+
169
+ resources = get_resources()
170
+
171
+ resource = resources["configs"].get(f"{args.example}.default-config", None)
172
+ if resource is None:
173
+ log.error(
174
+ f"'default-config{Config.SUFFIX}' from '{args.example}'; not available"
175
+ )
176
+ return errno.EINVAL
177
+ src_config = resource.path
178
+
179
+ resource = resources["workflows"].get(f"{args.example}.example-workflow", None)
180
+ if resource is None:
181
+ log.error(
182
+ f"'example-workflow{Workflow.SUFFIX}' from '{args.example}'; not available"
183
+ )
184
+ return errno.EINVAL
185
+ src_workflow = resource.path
186
+
187
+ resource = resources["scripts"].get("core.example", None)
188
+ if resource is None:
189
+ log.error(f"example.py' from '{args.example}'; not available")
190
+ return errno.EINVAL
191
+ src_script = resource.path
192
+
193
+ dst_config = Path.cwd().joinpath(DEFAULT_CONFIG_FILENAME)
194
+ dst_workflow = Path.cwd().joinpath(DEFAULT_WORKFLOW_FILENAME)
195
+ dst_script = Path.cwd().joinpath(DEFAULT_SCRIPT_FILENAME)
196
+
197
+ log.info(f"config: {dst_config}")
198
+ log.info(f"workflow: {dst_workflow}")
199
+ log.info(f"script: {dst_script}")
200
+
201
+ if not src_config.exists():
202
+ log.error(
203
+ f"'default-config{Config.SUFFIX}' from '{args.example}'; not available"
204
+ )
205
+ return errno.EINVAL
206
+ if not src_workflow.exists():
207
+ log.error(
208
+ f"example-workflow{Workflow.SUFFIX}' from '{args.example}'; not available"
209
+ )
210
+ return errno.EINVAL
211
+ if not src_script.exists():
212
+ log.error(f"example.py' from '{args.example}'; not available")
213
+ return errno.EINVAL
214
+
215
+ if dst_config.exists():
216
+ err = errno.EEXIST
217
+ log.error(f"skipping config({dst_config}); already exists")
218
+ else:
219
+ shutil.copyfile(src_config, dst_config)
220
+
221
+ if dst_workflow.exists():
222
+ err = errno.EEXIST
223
+ log.error(f"skipping workflow({dst_workflow}); already exists")
224
+ else:
225
+ shutil.copyfile(src_workflow, dst_workflow)
226
+
227
+ if dst_script.exists():
228
+ err = errno.EEXIST
229
+ log.error(f"skipping script({dst_script}); already exists")
230
+ else:
231
+ shutil.copyfile(src_script, dst_script)
232
+
233
+ return err
234
+
235
+
236
+ def cli_version(args):
237
+ """Print version and exit"""
238
+
239
+ print(f"cijoe {cijoe.core.__version__}")
240
+
241
+ return 0
242
+
243
+
244
+ def cli_workflow(args):
245
+ """Process workflow"""
246
+
247
+ log.info("cli: run")
248
+ log.info(f"workflow: {args.workflow}")
249
+ log.info(f"config: {args.config}")
250
+ log.info(f"output: {args.output}")
251
+
252
+ cli_archive(args)
253
+
254
+ state_path = args.output / "workflow.state"
255
+ if state_path.exists():
256
+ log.error(f"aborting; output({args.output}) directory already exists")
257
+ return errno.EPERM
258
+
259
+ config = Config(args.config)
260
+ errors = config.load()
261
+ if errors:
262
+ log_errors(errors)
263
+ log.error("failed: Config(args.config).load()")
264
+ return errno.EINVAL
265
+
266
+ workflow = Workflow(args.workflow)
267
+
268
+ errors = workflow.load(config)
269
+ if errors:
270
+ log_errors(errors)
271
+ log.error("workflow.load(): see errors above or run 'cijoe -i'")
272
+ return errno.EINVAL
273
+
274
+ workflow.state["tag"] = args.tag
275
+ step_names = [step["name"] for step in workflow.state["steps"]]
276
+ for step_name in args.step:
277
+ if step_name in step_names:
278
+ continue
279
+
280
+ log.error(f"step({step_name}) not in workflow")
281
+ return errno.EINVAL
282
+
283
+ os.makedirs(args.output)
284
+ shutil.copyfile(args.config, args.output / "config.orig")
285
+ shutil.copyfile(args.workflow, args.output / "workflow.orig")
286
+ resources = get_resources()
287
+
288
+ # pre-load scripts and augment state with step-descriptions.
289
+ # TODO: for some reason when mod.__doc__ is None, then the docstring from a previous
290
+ # mod trickles in. This should be fixed...
291
+ for step in workflow.state["steps"]:
292
+ script_ident = step["uses"]
293
+ resources["scripts"][script_ident].load()
294
+
295
+ step["description"] = "Undocumented"
296
+ if resources["scripts"][script_ident].mod.__doc__:
297
+ step["description"] = str(resources["scripts"][script_ident].mod.__doc__)
298
+
299
+ workflow.state["status"]["started"] = time.time()
300
+
301
+ fail_fast = False
302
+
303
+ cijoe = Cijoe(config, args.output, args.monitor)
304
+ for step in workflow.state["steps"]:
305
+ log.info(f"step({step['name']}) - begin")
306
+
307
+ begin = time.time()
308
+ step["status"]["started"] = begin
309
+ workflow.state_dump(args.output / Workflow.STATE_FILENAME)
310
+
311
+ cijoe.set_output_ident(step["id"])
312
+ os.makedirs(os.path.join(cijoe.output_path, step["id"]), exist_ok=True)
313
+
314
+ if args.step and step["name"] not in args.step:
315
+ step["status"]["skipped"] = 1
316
+ else:
317
+ script_ident = step["uses"]
318
+
319
+ try:
320
+ err = resources["scripts"][script_ident].func(args, cijoe, step)
321
+ if err:
322
+ log.error(f"script({script_ident}) : err({err})")
323
+ step["status"]["failed" if err else "passed"] = 1
324
+ except KeyboardInterrupt:
325
+ log.exception(f"script({script_ident}) : failed")
326
+ step["status"]["failed"] = 1
327
+ except Exception:
328
+ log.exception(f"script({script_ident}) : failed")
329
+ step["status"]["failed"] = 1
330
+
331
+ for key in ["failed", "passed", "skipped"]:
332
+ workflow.state["status"][key] += step["status"][key]
333
+
334
+ step["status"]["elapsed"] = time.time() - begin
335
+ workflow.state["status"]["elapsed"] += step["status"]["elapsed"]
336
+
337
+ for text, status in step["status"].items():
338
+ if text != "elapsed" and status:
339
+ log.info(f"step({step['name']}) : {text}")
340
+
341
+ if step["status"]["failed"] and fail_fast:
342
+ log.error(f"exiting, fail_fast({fail_fast})")
343
+ break
344
+
345
+ workflow.state_dump(args.output / Workflow.STATE_FILENAME)
346
+
347
+ err = errno.EIO if workflow.state["status"]["failed"] else 0
348
+ if err:
349
+ log.error("one or more steps failed")
350
+
351
+ return err
352
+
353
+
354
+ def create_adhoc_workflow(args, paths):
355
+ paths = list(map(Path, paths))
356
+ if len(set(path.stem for path in paths)) != len(paths):
357
+ log.error("Duplicate script file names not allowed.")
358
+ sys.exit(1)
359
+
360
+ resources = get_resources([step.parent for step in paths])
361
+
362
+ template_path = resources["templates"]["core.example-tmp-workflow.yaml"].path
363
+ jinja_env = jinja2.Environment(
364
+ autoescape=True, loader=jinja2.FileSystemLoader(template_path.parent)
365
+ )
366
+ template = jinja_env.get_template(template_path.name)
367
+
368
+ with tempfile.NamedTemporaryFile() as workflow:
369
+ setattr(args, "workflow", Path(workflow.name))
370
+ setattr(args, "step", [])
371
+
372
+ content = template.render(paths=paths)
373
+ workflow.write(bytes(content, "utf-8"))
374
+ workflow.seek(0)
375
+
376
+ sys.exit(main(args))
377
+
378
+
379
+ def cli_interface(path):
380
+ path = Path(path)
381
+ args = parse_args()
382
+ create_adhoc_workflow(args, [path])
383
+
384
+
385
+ def parse_args():
386
+ """Parse command-line interface."""
387
+
388
+ parser = argparse.ArgumentParser(
389
+ prog=Path(sys.argv[0]).stem,
390
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
391
+ )
392
+
393
+ workflow_group = parser.add_argument_group(
394
+ "workflow", "Run workflow at '-w', using config at '-c', and output at '-o'"
395
+ )
396
+
397
+ workflow_group.add_argument(
398
+ "step",
399
+ nargs="*",
400
+ help="Given a workflow; one or more workflow steps to run. Else; one or more cijoe Python scripts to run.",
401
+ )
402
+
403
+ workflow_group.add_argument(
404
+ "--config",
405
+ "-c",
406
+ type=Path,
407
+ default=Path(os.environ.get("CIJOE_DEFAULT_CONFIG", DEFAULT_CONFIG_FILENAME)),
408
+ help="Path to the Configuration file.",
409
+ )
410
+ workflow_group.add_argument(
411
+ "--workflow",
412
+ "-w",
413
+ type=Path,
414
+ default=Path(
415
+ os.environ.get("CIJOE_DEFAULT_WORKFLOW", DEFAULT_WORKFLOW_FILENAME)
416
+ ),
417
+ help="Path to workflow file.",
418
+ )
419
+ workflow_group.add_argument(
420
+ "--output",
421
+ "-o",
422
+ type=Path,
423
+ default=default_output_path(),
424
+ help="Path to output directory.",
425
+ )
426
+ workflow_group.add_argument(
427
+ "--log-level",
428
+ "-l",
429
+ action="append_const",
430
+ const=1,
431
+ help="Increase log-level. Provide '-l' for info and '-ll' for debug.",
432
+ )
433
+ workflow_group.add_argument(
434
+ "--monitor",
435
+ "-m",
436
+ action="store_true",
437
+ help="Dump command output to stdout",
438
+ )
439
+ workflow_group.add_argument(
440
+ "--no-report",
441
+ "-n",
442
+ action="store_true",
443
+ help="Skip the producing, and opening, a report at the end of the workflow-run",
444
+ )
445
+ workflow_group.add_argument(
446
+ "--skip-report",
447
+ "-s",
448
+ action="store_false",
449
+ help="Skip the report opening at the end of the workflow-run",
450
+ )
451
+ workflow_group.add_argument(
452
+ "--tag",
453
+ "-t",
454
+ type=str,
455
+ action="append",
456
+ help="Tags to identify a workflow-run."
457
+ " This will be prefixed while storing in archive",
458
+ )
459
+
460
+ utils_group = parser.add_argument_group(
461
+ "utilities", "Workflow, and workflow-related utilities"
462
+ )
463
+ utils_group.add_argument(
464
+ "--archive",
465
+ "-a",
466
+ action="store_true",
467
+ help="Move the output at '-o / --output' to 'cijoe-archive/YYYY-MM-DD_HH:MM:SS",
468
+ )
469
+ utils_group.add_argument(
470
+ "--produce-report",
471
+ "-p",
472
+ action="append_const",
473
+ const=1,
474
+ help="Produce report, and open it, for output at '-o / --output' and exit.",
475
+ )
476
+ utils_group.add_argument(
477
+ "--integrity-check",
478
+ "-i",
479
+ action="store_true",
480
+ help="Check integrity of workflow at '-w / --workflow' and exit.",
481
+ )
482
+ utils_group.add_argument(
483
+ "--resources",
484
+ "-r",
485
+ action="store_true",
486
+ help="List collected resources and exit.",
487
+ )
488
+ utils_group.add_argument(
489
+ "--example",
490
+ "-e",
491
+ action="store",
492
+ const="core",
493
+ type=str,
494
+ nargs="?",
495
+ default=None,
496
+ help="Create 'default-config.toml', 'example-workflow.yaml' and 'cijoe-script.py' and exit.",
497
+ )
498
+ utils_group.add_argument(
499
+ "--version",
500
+ "-v",
501
+ action="store_true",
502
+ help="Print the version number of 'cijoe' and exit.",
503
+ )
504
+
505
+ return parser.parse_args()
506
+
507
+
508
+ def main(args=None):
509
+ """Main entry point for the CLI"""
510
+
511
+ if args is None:
512
+ args = parse_args()
513
+ if args.step and all(step.endswith(".py") for step in args.step):
514
+ create_adhoc_workflow(args, args.step)
515
+
516
+ levels = [log.ERROR, log.INFO, log.DEBUG]
517
+ log.basicConfig(
518
+ format="%(levelname)s:%(module)s:%(funcName)s(): %(message)s",
519
+ level=levels[
520
+ min(sum(args.log_level), len(levels) - 1) if args.log_level else 0
521
+ ],
522
+ )
523
+
524
+ if args.resources:
525
+ return cli_resources(args)
526
+
527
+ if args.example:
528
+ return cli_example(args)
529
+
530
+ if args.version:
531
+ return cli_version(args)
532
+
533
+ if args.produce_report:
534
+ return cli_produce_report(args)
535
+
536
+ if args.archive:
537
+ return cli_archive(args)
538
+
539
+ for filearg in ["config", "workflow"]:
540
+ argv = getattr(args, filearg)
541
+ path = search_for_file(argv)
542
+ if path is None:
543
+ log.error(f"{filearg}({argv}) does not exist; exiting")
544
+ return errno.EINVAL
545
+
546
+ setattr(args, filearg, path)
547
+
548
+ if args.integrity_check:
549
+ return cli_integrity_check(args)
550
+
551
+ # At this point all Path objects are resolved, except for output, do that
552
+ # that here.
553
+ args.output = args.output.resolve()
554
+
555
+ err = cli_workflow(args)
556
+
557
+ if not args.no_report:
558
+ report_err = cli_produce_report(args)
559
+ err = err if err else report_err
560
+
561
+ return err
cijoe/core/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.9.41"
cijoe/core/analyser.py ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Library functions for performance requirements and normalization of metrics
4
+ """
5
+ import copy
6
+ import dataclasses
7
+ import os
8
+ import re
9
+ from typing import List, Tuple
10
+
11
+ import yaml
12
+
13
+ from cijoe.core.errors import InvalidRangeError, UnknownUnitError
14
+
15
+ UNITS = {
16
+ # general
17
+ "": 1, # no unit
18
+ "B": 1, # bytes
19
+ "k": 1000, # kilo
20
+ "M": 1000**2, # mega
21
+ "G": 1000**3, # giga
22
+ # kibi
23
+ "KiB": 1024**1, # kibibytes
24
+ "MiB": 1024**2, # mibibytes
25
+ "GiB": 1024**3, # gibibytes
26
+ "TiB": 1024**4, # tibibytes
27
+ # kilo
28
+ "kB": 1000**1, # kilobytes
29
+ "MB": 1000**2, # megabytes
30
+ "GB": 1000**3, # gigabytes
31
+ "TB": 1000**4, # gigabytes
32
+ # time
33
+ "nsec": 1 / 1000**3, # nanoseconds
34
+ "usec": 1 / 1000**2, # microseconds
35
+ "msec": 1 / 1000**1, # milliseconds
36
+ "sec": 1, # seconds
37
+ "min": 60, # minutes
38
+ }
39
+
40
+
41
+ class Range:
42
+ """
43
+ Range implements parsing and validation of mathematical range notation,
44
+ e.g. `[-5;100[` which translates to "must be >= -5 and < 100".
45
+ """
46
+
47
+ # pylint: disable=no-self-use
48
+ # pylint: disable=too-few-public-methods
49
+
50
+ _rng_re = re.compile(
51
+ r"^(?P<elower>\[|\])\s*(?P<rstart>-inf|-?\d+(\.\d*)?)\s*;" # [1.0;
52
+ r"\s*(?P<rend>inf|-?\d+(\.\d*)?)\s*(?P<eupper>\[|\])" # 1.0]
53
+ rf"\s*(?P<unit>({'|'.join(UNITS)}))$" # ms
54
+ )
55
+
56
+ def __init__(self, rng: str):
57
+ match = self._rng_re.match(rng)
58
+ if not match:
59
+ raise InvalidRangeError(f'invalid syntax or unit for "{rng}"')
60
+
61
+ rng_start = float(match["rstart"])
62
+ rng_end = float(match["rend"])
63
+ if rng_start > rng_end:
64
+ raise InvalidRangeError(
65
+ "expected lower bound <= upper bound, " f"{rng_start} <= {rng_end}"
66
+ )
67
+
68
+ # NOTE: _rng_re enforces that match["unit"] exists in UNITS.
69
+ unit_val = UNITS[match["unit"]]
70
+
71
+ self._rng_start = rng_start
72
+ self._rng_end = rng_end
73
+ self._elower = match["elower"]
74
+ self._eupper = match["eupper"]
75
+ self._unit = match["unit"]
76
+
77
+ self._check_lower = self._make_check_lower(
78
+ match["elower"], rng_start * unit_val
79
+ )
80
+ self._check_upper = self._make_check_upper(match["eupper"], rng_end * unit_val)
81
+
82
+ def contains(self, val: float) -> bool:
83
+ """Check whether n is contained in range.
84
+
85
+ val must be given in the base unit of the measurement, e.g. seconds for
86
+ time and bytes for storage.
87
+ """
88
+ return self._check_lower(val) and self._check_upper(val)
89
+
90
+ def _make_check_lower(self, edge_lower: str, rng_start: float):
91
+ if edge_lower == "[":
92
+ return lambda n: n >= rng_start
93
+ if edge_lower == "]":
94
+ return lambda n: n > rng_start
95
+ raise InvalidRangeError("invalid input _make_check_lower")
96
+
97
+ def _make_check_upper(self, edge_upper: str, rng_end: float):
98
+ if edge_upper == "[":
99
+ return lambda n: n < rng_end
100
+ if edge_upper == "]":
101
+ return lambda n: n <= rng_end
102
+ raise InvalidRangeError("invalid input _make_check_upper")
103
+
104
+ def format_val(self, val: float) -> str:
105
+ """Formats and returns val using the unit of the range.
106
+
107
+ Example:
108
+ range: "[250; 750]usec"
109
+ val: 0.0005
110
+ output: "500 usec"
111
+ """
112
+
113
+ val_conv = val / UNITS[self._unit]
114
+ return f"{val_conv:.3f} {self._unit}"
115
+
116
+ def __str__(self):
117
+ return (
118
+ f"{self._elower}{self._rng_start};"
119
+ f"{self._rng_end}{self._eupper} {self._unit}"
120
+ )
121
+
122
+
123
+ def to_base_unit(val: float, unit: str = "") -> float:
124
+ """Converts val in the given unit to its base unit.
125
+ Example:
126
+ val: 100, unit: 'KiB'
127
+ output: 102400 (bytes)
128
+
129
+ val: 500, unit: 'msec'
130
+ output: 0.5 (seconds)
131
+ """
132
+ unit_scalar = UNITS.get(unit, None)
133
+ if not unit_scalar:
134
+ raise UnknownUnitError(f"Unit '{unit}' is not supported")
135
+
136
+ return val * unit_scalar
File without changes