hpcflow-new2 0.2.0a179__py3-none-any.whl → 0.2.0a180__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.
- hpcflow/_version.py +1 -1
- hpcflow/data/demo_data_manifest/__init__.py +3 -0
- hpcflow/sdk/__init__.py +4 -1
- hpcflow/sdk/app.py +160 -15
- hpcflow/sdk/cli.py +14 -0
- hpcflow/sdk/cli_common.py +83 -0
- hpcflow/sdk/config/__init__.py +4 -0
- hpcflow/sdk/config/callbacks.py +25 -2
- hpcflow/sdk/config/cli.py +4 -1
- hpcflow/sdk/config/config.py +188 -14
- hpcflow/sdk/config/config_file.py +91 -3
- hpcflow/sdk/config/errors.py +33 -0
- hpcflow/sdk/core/__init__.py +2 -0
- hpcflow/sdk/core/actions.py +492 -35
- hpcflow/sdk/core/cache.py +22 -0
- hpcflow/sdk/core/command_files.py +221 -5
- hpcflow/sdk/core/commands.py +57 -0
- hpcflow/sdk/core/element.py +407 -8
- hpcflow/sdk/core/environment.py +92 -0
- hpcflow/sdk/core/errors.py +245 -61
- hpcflow/sdk/core/json_like.py +72 -14
- hpcflow/sdk/core/loop.py +122 -21
- hpcflow/sdk/core/loop_cache.py +34 -9
- hpcflow/sdk/core/object_list.py +172 -26
- hpcflow/sdk/core/parallel.py +14 -0
- hpcflow/sdk/core/parameters.py +478 -25
- hpcflow/sdk/core/rule.py +31 -1
- hpcflow/sdk/core/run_dir_files.py +12 -2
- hpcflow/sdk/core/task.py +407 -80
- hpcflow/sdk/core/task_schema.py +70 -9
- hpcflow/sdk/core/test_utils.py +35 -0
- hpcflow/sdk/core/utils.py +101 -4
- hpcflow/sdk/core/validation.py +13 -1
- hpcflow/sdk/core/workflow.py +316 -96
- hpcflow/sdk/core/zarr_io.py +23 -0
- hpcflow/sdk/data/__init__.py +13 -0
- hpcflow/sdk/demo/__init__.py +3 -0
- hpcflow/sdk/helper/__init__.py +3 -0
- hpcflow/sdk/helper/cli.py +9 -0
- hpcflow/sdk/helper/helper.py +28 -0
- hpcflow/sdk/helper/watcher.py +33 -0
- hpcflow/sdk/log.py +40 -0
- hpcflow/sdk/persistence/__init__.py +14 -4
- hpcflow/sdk/persistence/base.py +289 -23
- hpcflow/sdk/persistence/json.py +29 -0
- hpcflow/sdk/persistence/pending.py +217 -107
- hpcflow/sdk/persistence/store_resource.py +58 -2
- hpcflow/sdk/persistence/utils.py +8 -0
- hpcflow/sdk/persistence/zarr.py +68 -1
- hpcflow/sdk/runtime.py +52 -10
- hpcflow/sdk/submission/__init__.py +3 -0
- hpcflow/sdk/submission/jobscript.py +198 -9
- hpcflow/sdk/submission/jobscript_info.py +13 -0
- hpcflow/sdk/submission/schedulers/__init__.py +60 -0
- hpcflow/sdk/submission/schedulers/direct.py +53 -0
- hpcflow/sdk/submission/schedulers/sge.py +45 -7
- hpcflow/sdk/submission/schedulers/slurm.py +45 -8
- hpcflow/sdk/submission/schedulers/utils.py +4 -0
- hpcflow/sdk/submission/shells/__init__.py +11 -1
- hpcflow/sdk/submission/shells/base.py +32 -1
- hpcflow/sdk/submission/shells/bash.py +36 -1
- hpcflow/sdk/submission/shells/os_version.py +18 -6
- hpcflow/sdk/submission/shells/powershell.py +22 -0
- hpcflow/sdk/submission/submission.py +88 -3
- hpcflow/sdk/typing.py +10 -1
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/METADATA +1 -1
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/RECORD +70 -70
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
Model of information submitted to a scheduler.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from __future__ import annotations
|
2
6
|
import copy
|
3
7
|
|
@@ -28,8 +32,10 @@ def generate_EAR_resource_map(
|
|
28
32
|
task: app.WorkflowTask,
|
29
33
|
loop_idx: Dict,
|
30
34
|
) -> Tuple[List[app.ElementResources], List[int], NDArray, NDArray]:
|
31
|
-
"""
|
32
|
-
|
35
|
+
"""
|
36
|
+
Generate an integer array whose rows represent actions and columns represent task
|
37
|
+
elements and whose values index unique resources.
|
38
|
+
"""
|
33
39
|
# TODO: assume single iteration for now; later we will loop over Loop tasks for each
|
34
40
|
# included task and call this func with specific loop indices
|
35
41
|
none_val = -1
|
@@ -83,6 +89,9 @@ def group_resource_map_into_jobscripts(
|
|
83
89
|
resource_map: Union[List, NDArray],
|
84
90
|
none_val: Any = -1,
|
85
91
|
):
|
92
|
+
"""
|
93
|
+
Convert a resource map into a plan for what elements to group together into jobscripts.
|
94
|
+
"""
|
86
95
|
resource_map = np.asanyarray(resource_map)
|
87
96
|
resource_idx = np.unique(resource_map)
|
88
97
|
jobscripts = []
|
@@ -152,6 +161,9 @@ def group_resource_map_into_jobscripts(
|
|
152
161
|
|
153
162
|
@TimeIt.decorator
|
154
163
|
def resolve_jobscript_dependencies(jobscripts, element_deps):
|
164
|
+
"""
|
165
|
+
Discover concrete dependencies between jobscripts.
|
166
|
+
"""
|
155
167
|
# first pass is to find the mappings between jobscript elements:
|
156
168
|
jobscript_deps = {}
|
157
169
|
for js_idx, elem_deps in element_deps.items():
|
@@ -302,6 +314,52 @@ def jobscripts_to_list(jobscripts: Dict[int, Dict]) -> List[Dict]:
|
|
302
314
|
|
303
315
|
|
304
316
|
class Jobscript(JSONLike):
|
317
|
+
"""
|
318
|
+
A group of actions that are submitted together to be executed by the underlying job
|
319
|
+
management system as a single unit.
|
320
|
+
|
321
|
+
Parameters
|
322
|
+
----------
|
323
|
+
task_insert_IDs: list[int]
|
324
|
+
The task insertion IDs.
|
325
|
+
task_actions: list[tuple]
|
326
|
+
The actions of the tasks.
|
327
|
+
``task insert ID, action_idx, index into task_loop_idx`` for each ``JS_ACTION_IDX``
|
328
|
+
task_elements: dict[int, list[int]]
|
329
|
+
The elements of the tasks.
|
330
|
+
Maps ``JS_ELEMENT_IDX`` to list of ``TASK_ELEMENT_IDX`` for each ``TASK_INSERT_ID``
|
331
|
+
EAR_ID:
|
332
|
+
Element action run information.
|
333
|
+
resources: ~hpcflow.app.ElementResources
|
334
|
+
Resources to use
|
335
|
+
task_loop_idx: list[dict]
|
336
|
+
Description of what loops are in play.
|
337
|
+
dependencies: dict[int, dict]
|
338
|
+
Description of dependencies.
|
339
|
+
submit_time: datetime
|
340
|
+
When the jobscript was submitted, if known.
|
341
|
+
submit_hostname: str
|
342
|
+
Where the jobscript was submitted, if known.
|
343
|
+
submit_machine: str
|
344
|
+
Description of what the jobscript was submitted to, if known.
|
345
|
+
submit_cmdline: str
|
346
|
+
The command line used to do the commit, if known.
|
347
|
+
scheduler_job_ID: str
|
348
|
+
The job ID from the scheduler, if known.
|
349
|
+
process_ID: int
|
350
|
+
The process ID of the subprocess, if known.
|
351
|
+
version_info: tuple[str, ...]
|
352
|
+
Version info about the target system.
|
353
|
+
os_name: str
|
354
|
+
The name of the OS.
|
355
|
+
shell_name: str
|
356
|
+
The name of the shell.
|
357
|
+
scheduler_name: str
|
358
|
+
The scheduler used.
|
359
|
+
running: bool
|
360
|
+
Whether the jobscript is currently running.
|
361
|
+
"""
|
362
|
+
|
305
363
|
_app_attr = "app"
|
306
364
|
_EAR_files_delimiter = ":"
|
307
365
|
_workflow_app_alias = "wkflow_app"
|
@@ -399,9 +457,15 @@ class Jobscript(JSONLike):
|
|
399
457
|
|
400
458
|
@property
|
401
459
|
def workflow_app_alias(self):
|
460
|
+
"""
|
461
|
+
Alias for the workflow app in job scripts.
|
462
|
+
"""
|
402
463
|
return self._workflow_app_alias
|
403
464
|
|
404
465
|
def get_commands_file_name(self, js_action_idx, shell=None):
|
466
|
+
"""
|
467
|
+
Get the name of a file containing commands for a particular jobscript action.
|
468
|
+
"""
|
405
469
|
return self.app.RunDirAppFiles.get_commands_file_name(
|
406
470
|
js_idx=self.index,
|
407
471
|
js_action_idx=js_action_idx,
|
@@ -410,47 +474,74 @@ class Jobscript(JSONLike):
|
|
410
474
|
|
411
475
|
@property
|
412
476
|
def task_insert_IDs(self):
|
477
|
+
"""
|
478
|
+
The insertion IDs of tasks in this jobscript.
|
479
|
+
"""
|
413
480
|
return self._task_insert_IDs
|
414
481
|
|
415
482
|
@property
|
416
483
|
def task_actions(self):
|
484
|
+
"""
|
485
|
+
The IDs of actions of each task in this jobscript.
|
486
|
+
"""
|
417
487
|
return self._task_actions
|
418
488
|
|
419
489
|
@property
|
420
490
|
def task_elements(self):
|
491
|
+
"""
|
492
|
+
The IDs of elements of each task in this jobscript.
|
493
|
+
"""
|
421
494
|
return self._task_elements
|
422
495
|
|
423
496
|
@property
|
424
497
|
def EAR_ID(self):
|
498
|
+
"""
|
499
|
+
The array of EAR IDs.
|
500
|
+
"""
|
425
501
|
return self._EAR_ID
|
426
502
|
|
427
503
|
@property
|
428
504
|
def all_EAR_IDs(self) -> List[int]:
|
505
|
+
"""
|
506
|
+
The IDs of all EARs in this jobscript.
|
507
|
+
"""
|
429
508
|
return self.EAR_ID.flatten()
|
430
509
|
|
431
510
|
@property
|
432
511
|
@TimeIt.decorator
|
433
512
|
def all_EARs(self) -> List:
|
513
|
+
"""
|
514
|
+
Description of EAR information for this jobscript.
|
515
|
+
"""
|
434
516
|
if not self._all_EARs:
|
435
517
|
self._all_EARs = self.workflow.get_EARs_from_IDs(self.all_EAR_IDs)
|
436
518
|
return self._all_EARs
|
437
519
|
|
438
520
|
@property
|
439
521
|
def resources(self):
|
522
|
+
"""
|
523
|
+
The common resources that this jobscript requires.
|
524
|
+
"""
|
440
525
|
return self._resources
|
441
526
|
|
442
527
|
@property
|
443
528
|
def task_loop_idx(self):
|
529
|
+
"""
|
530
|
+
The description of where various task loops are.
|
531
|
+
"""
|
444
532
|
return self._task_loop_idx
|
445
533
|
|
446
534
|
@property
|
447
535
|
def dependencies(self):
|
536
|
+
"""
|
537
|
+
The dependency descriptor.
|
538
|
+
"""
|
448
539
|
return self._dependencies
|
449
540
|
|
450
541
|
@property
|
451
542
|
@TimeIt.decorator
|
452
543
|
def start_time(self):
|
453
|
-
"""
|
544
|
+
"""The first known start time of any EAR in this jobscript."""
|
454
545
|
if not self.is_submitted:
|
455
546
|
return
|
456
547
|
all_times = [i.start_time for i in self.all_EARs if i.start_time]
|
@@ -462,7 +553,7 @@ class Jobscript(JSONLike):
|
|
462
553
|
@property
|
463
554
|
@TimeIt.decorator
|
464
555
|
def end_time(self):
|
465
|
-
"""
|
556
|
+
"""The last known end time of any EAR in this jobscript."""
|
466
557
|
if not self.is_submitted:
|
467
558
|
return
|
468
559
|
all_times = [i.end_time for i in self.all_EARs if i.end_time]
|
@@ -473,6 +564,9 @@ class Jobscript(JSONLike):
|
|
473
564
|
|
474
565
|
@property
|
475
566
|
def submit_time(self):
|
567
|
+
"""
|
568
|
+
When the jobscript was submitted, if known.
|
569
|
+
"""
|
476
570
|
if self._submit_time_obj is None and self._submit_time:
|
477
571
|
self._submit_time_obj = (
|
478
572
|
datetime.strptime(self._submit_time, self.workflow.ts_fmt)
|
@@ -483,50 +577,86 @@ class Jobscript(JSONLike):
|
|
483
577
|
|
484
578
|
@property
|
485
579
|
def submit_hostname(self):
|
580
|
+
"""
|
581
|
+
Where the jobscript was submitted, if known.
|
582
|
+
"""
|
486
583
|
return self._submit_hostname
|
487
584
|
|
488
585
|
@property
|
489
586
|
def submit_machine(self):
|
587
|
+
"""
|
588
|
+
Description of what the jobscript was submitted to, if known.
|
589
|
+
"""
|
490
590
|
return self._submit_machine
|
491
591
|
|
492
592
|
@property
|
493
593
|
def submit_cmdline(self):
|
594
|
+
"""
|
595
|
+
The command line used to do the commit, if known.
|
596
|
+
"""
|
494
597
|
return self._submit_cmdline
|
495
598
|
|
496
599
|
@property
|
497
600
|
def scheduler_job_ID(self):
|
601
|
+
"""
|
602
|
+
The job ID from the scheduler, if known.
|
603
|
+
"""
|
498
604
|
return self._scheduler_job_ID
|
499
605
|
|
500
606
|
@property
|
501
607
|
def process_ID(self):
|
608
|
+
"""
|
609
|
+
The process ID from direct execution, if known.
|
610
|
+
"""
|
502
611
|
return self._process_ID
|
503
612
|
|
504
613
|
@property
|
505
614
|
def version_info(self):
|
615
|
+
"""
|
616
|
+
Version information about the execution environment (OS, etc).
|
617
|
+
"""
|
506
618
|
return self._version_info
|
507
619
|
|
508
620
|
@property
|
509
621
|
def index(self):
|
622
|
+
"""
|
623
|
+
The index of this jobscript within its parent :py:class:`Submission`.
|
624
|
+
"""
|
510
625
|
return self._index
|
511
626
|
|
512
627
|
@property
|
513
628
|
def submission(self):
|
629
|
+
"""
|
630
|
+
The parent submission.
|
631
|
+
"""
|
514
632
|
return self._submission
|
515
633
|
|
516
634
|
@property
|
517
635
|
def workflow(self):
|
636
|
+
"""
|
637
|
+
The workflow this is all on behalf of.
|
638
|
+
"""
|
518
639
|
return self.submission.workflow
|
519
640
|
|
520
641
|
@property
|
521
642
|
def num_actions(self):
|
643
|
+
"""
|
644
|
+
The number of actions in this jobscript.
|
645
|
+
"""
|
522
646
|
return self.EAR_ID.shape[0]
|
523
647
|
|
524
648
|
@property
|
525
649
|
def num_elements(self):
|
650
|
+
"""
|
651
|
+
The number of elements in this jobscript.
|
652
|
+
"""
|
526
653
|
return self.EAR_ID.shape[1]
|
527
654
|
|
528
655
|
@property
|
529
656
|
def is_array(self):
|
657
|
+
"""
|
658
|
+
Whether to generate an array job.
|
659
|
+
"""
|
530
660
|
if self.scheduler_name == "direct":
|
531
661
|
return False
|
532
662
|
|
@@ -546,14 +676,23 @@ class Jobscript(JSONLike):
|
|
546
676
|
|
547
677
|
@property
|
548
678
|
def os_name(self) -> Union[str, None]:
|
679
|
+
"""
|
680
|
+
The name of the OS to use.
|
681
|
+
"""
|
549
682
|
return self._os_name or self.resources.os_name
|
550
683
|
|
551
684
|
@property
|
552
685
|
def shell_name(self) -> Union[str, None]:
|
686
|
+
"""
|
687
|
+
The name of the shell to use.
|
688
|
+
"""
|
553
689
|
return self._shell_name or self.resources.shell
|
554
690
|
|
555
691
|
@property
|
556
692
|
def scheduler_name(self) -> Union[str, None]:
|
693
|
+
"""
|
694
|
+
The name of the scheduler to use.
|
695
|
+
"""
|
557
696
|
return self._scheduler_name or self.resources.scheduler
|
558
697
|
|
559
698
|
def _get_submission_os_args(self):
|
@@ -578,7 +717,7 @@ class Jobscript(JSONLike):
|
|
578
717
|
|
579
718
|
@property
|
580
719
|
def shell(self):
|
581
|
-
"""
|
720
|
+
"""The shell for composing submission scripts."""
|
582
721
|
if self._shell_obj is None:
|
583
722
|
self._shell_obj = self._get_shell(
|
584
723
|
os_name=self.os_name,
|
@@ -590,7 +729,7 @@ class Jobscript(JSONLike):
|
|
590
729
|
|
591
730
|
@property
|
592
731
|
def scheduler(self):
|
593
|
-
"""
|
732
|
+
"""The scheduler that submissions go to from this jobscript."""
|
594
733
|
if self._scheduler_obj is None:
|
595
734
|
self._scheduler_obj = self.app.get_scheduler(
|
596
735
|
scheduler_name=self.scheduler_name,
|
@@ -601,52 +740,81 @@ class Jobscript(JSONLike):
|
|
601
740
|
|
602
741
|
@property
|
603
742
|
def EAR_ID_file_name(self):
|
743
|
+
"""
|
744
|
+
The name of a file containing EAR IDs.
|
745
|
+
"""
|
604
746
|
return f"js_{self.index}_EAR_IDs.txt"
|
605
747
|
|
606
748
|
@property
|
607
749
|
def element_run_dir_file_name(self):
|
750
|
+
"""
|
751
|
+
The name of a file containing run directory names.
|
752
|
+
"""
|
608
753
|
return f"js_{self.index}_run_dirs.txt"
|
609
754
|
|
610
755
|
@property
|
611
756
|
def direct_stdout_file_name(self):
|
612
|
-
"""
|
757
|
+
"""File for direct execution stdout."""
|
613
758
|
return f"js_{self.index}_stdout.log"
|
614
759
|
|
615
760
|
@property
|
616
761
|
def direct_stderr_file_name(self):
|
617
|
-
"""
|
762
|
+
"""File for direct execution stderr."""
|
618
763
|
return f"js_{self.index}_stderr.log"
|
619
764
|
|
620
765
|
@property
|
621
766
|
def direct_win_pid_file_name(self):
|
767
|
+
"""File for holding the direct execution PID."""
|
622
768
|
return f"js_{self.index}_pid.txt"
|
623
769
|
|
624
770
|
@property
|
625
771
|
def jobscript_name(self):
|
772
|
+
"""The name of the jobscript file."""
|
626
773
|
return f"js_{self.index}{self.shell.JS_EXT}"
|
627
774
|
|
628
775
|
@property
|
629
776
|
def EAR_ID_file_path(self):
|
777
|
+
"""
|
778
|
+
The path to the file containing EAR IDs for this jobscript.
|
779
|
+
"""
|
630
780
|
return self.submission.path / self.EAR_ID_file_name
|
631
781
|
|
632
782
|
@property
|
633
783
|
def element_run_dir_file_path(self):
|
784
|
+
"""
|
785
|
+
The path to the file containing run directory names for this jobscript.
|
786
|
+
"""
|
634
787
|
return self.submission.path / self.element_run_dir_file_name
|
635
788
|
|
636
789
|
@property
|
637
790
|
def jobscript_path(self):
|
791
|
+
"""
|
792
|
+
The path to the file containing the jobscript file.
|
793
|
+
"""
|
638
794
|
return self.submission.path / self.jobscript_name
|
639
795
|
|
640
796
|
@property
|
641
797
|
def direct_stdout_path(self):
|
798
|
+
"""
|
799
|
+
The path to the file containing the stdout from directly executed commands
|
800
|
+
for this jobscript.
|
801
|
+
"""
|
642
802
|
return self.submission.path / self.direct_stdout_file_name
|
643
803
|
|
644
804
|
@property
|
645
805
|
def direct_stderr_path(self):
|
806
|
+
"""
|
807
|
+
The path to the file containing the stderr from directly executed commands
|
808
|
+
for this jobscript.
|
809
|
+
"""
|
646
810
|
return self.submission.path / self.direct_stderr_file_name
|
647
811
|
|
648
812
|
@property
|
649
813
|
def direct_win_pid_file_path(self):
|
814
|
+
"""
|
815
|
+
The path to the file containing PIDs for directly executed commands for this
|
816
|
+
jobscript. Windows only.
|
817
|
+
"""
|
650
818
|
return self.submission.path / self.direct_win_pid_file_name
|
651
819
|
|
652
820
|
def _set_submit_time(self, submit_time: datetime) -> None:
|
@@ -737,6 +905,9 @@ class Jobscript(JSONLike):
|
|
737
905
|
)
|
738
906
|
|
739
907
|
def get_task_loop_idx_array(self):
|
908
|
+
"""
|
909
|
+
Get an array of task loop indices.
|
910
|
+
"""
|
740
911
|
loop_idx = np.empty_like(self.EAR_ID)
|
741
912
|
loop_idx[:] = np.array([i[2] for i in self.task_actions]).reshape(
|
742
913
|
(len(self.task_actions), 1)
|
@@ -916,6 +1087,9 @@ class Jobscript(JSONLike):
|
|
916
1087
|
scheduler_name: Optional[str] = None,
|
917
1088
|
scheduler_args: Optional[Dict] = None,
|
918
1089
|
):
|
1090
|
+
"""
|
1091
|
+
Write the jobscript to its file.
|
1092
|
+
"""
|
919
1093
|
js_str = self.compose_jobscript(
|
920
1094
|
deps=deps,
|
921
1095
|
os_name=os_name,
|
@@ -935,6 +1109,9 @@ class Jobscript(JSONLike):
|
|
935
1109
|
|
936
1110
|
@TimeIt.decorator
|
937
1111
|
def make_artifact_dirs(self):
|
1112
|
+
"""
|
1113
|
+
Create the directories that will hold artifacts associated with this jobscript.
|
1114
|
+
"""
|
938
1115
|
EARs_arr = self._get_EARs_arr()
|
939
1116
|
task_loop_idx_arr = self.get_task_loop_idx_array()
|
940
1117
|
|
@@ -1036,6 +1213,9 @@ class Jobscript(JSONLike):
|
|
1036
1213
|
scheduler_refs: Dict[int, (str, bool)],
|
1037
1214
|
print_stdout: Optional[bool] = False,
|
1038
1215
|
) -> str:
|
1216
|
+
"""
|
1217
|
+
Submit the jobscript to the scheduler.
|
1218
|
+
"""
|
1039
1219
|
# map each dependency jobscript index to the JS ref (job/process ID) and if the
|
1040
1220
|
# dependency is an array dependency:
|
1041
1221
|
deps = {}
|
@@ -1143,11 +1323,14 @@ class Jobscript(JSONLike):
|
|
1143
1323
|
|
1144
1324
|
@property
|
1145
1325
|
def is_submitted(self):
|
1146
|
-
"""
|
1326
|
+
"""Whether this jobscript has been submitted."""
|
1147
1327
|
return self.index in self.submission.submitted_jobscripts
|
1148
1328
|
|
1149
1329
|
@property
|
1150
1330
|
def scheduler_js_ref(self):
|
1331
|
+
"""
|
1332
|
+
The reference to the submitted job for the jobscript.
|
1333
|
+
"""
|
1151
1334
|
if isinstance(self.scheduler, Scheduler):
|
1152
1335
|
return self.scheduler_job_ID
|
1153
1336
|
else:
|
@@ -1155,6 +1338,9 @@ class Jobscript(JSONLike):
|
|
1155
1338
|
|
1156
1339
|
@property
|
1157
1340
|
def scheduler_ref(self):
|
1341
|
+
"""
|
1342
|
+
The generalised scheduler reference descriptor.
|
1343
|
+
"""
|
1158
1344
|
out = {"js_refs": [self.scheduler_js_ref]}
|
1159
1345
|
if not isinstance(self.scheduler, Scheduler):
|
1160
1346
|
out["num_js_elements"] = self.num_elements
|
@@ -1210,6 +1396,9 @@ class Jobscript(JSONLike):
|
|
1210
1396
|
return out
|
1211
1397
|
|
1212
1398
|
def cancel(self):
|
1399
|
+
"""
|
1400
|
+
Cancel this jobscript.
|
1401
|
+
"""
|
1213
1402
|
self.app.submission_logger.info(
|
1214
1403
|
f"Cancelling jobscript {self.index} of submission {self.submission.index}"
|
1215
1404
|
)
|
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
Jobscript state enumeration.
|
3
|
+
"""
|
4
|
+
|
1
5
|
import enum
|
2
6
|
|
3
7
|
|
@@ -13,36 +17,42 @@ class JobscriptElementState(enum.Enum):
|
|
13
17
|
member.__doc__ = doc
|
14
18
|
return member
|
15
19
|
|
20
|
+
#: Waiting for resource allocation.
|
16
21
|
pending = (
|
17
22
|
0,
|
18
23
|
"○",
|
19
24
|
"yellow",
|
20
25
|
"Waiting for resource allocation.",
|
21
26
|
)
|
27
|
+
#: Waiting for one or more dependencies to finish.
|
22
28
|
waiting = (
|
23
29
|
1,
|
24
30
|
"◊",
|
25
31
|
"grey46",
|
26
32
|
"Waiting for one or more dependencies to finish.",
|
27
33
|
)
|
34
|
+
#: Executing now.
|
28
35
|
running = (
|
29
36
|
2,
|
30
37
|
"●",
|
31
38
|
"dodger_blue1",
|
32
39
|
"Executing now.",
|
33
40
|
)
|
41
|
+
#: Previously submitted but is no longer active.
|
34
42
|
finished = (
|
35
43
|
3,
|
36
44
|
"■",
|
37
45
|
"grey46",
|
38
46
|
"Previously submitted but is no longer active.",
|
39
47
|
)
|
48
|
+
#: Cancelled by the user.
|
40
49
|
cancelled = (
|
41
50
|
4,
|
42
51
|
"C",
|
43
52
|
"red3",
|
44
53
|
"Cancelled by the user.",
|
45
54
|
)
|
55
|
+
#: The scheduler reports an error state.
|
46
56
|
errored = (
|
47
57
|
5,
|
48
58
|
"E",
|
@@ -52,4 +62,7 @@ class JobscriptElementState(enum.Enum):
|
|
52
62
|
|
53
63
|
@property
|
54
64
|
def rich_repr(self):
|
65
|
+
"""
|
66
|
+
Rich representation of this enumeration element.
|
67
|
+
"""
|
55
68
|
return f"[{self.colour}]{self.symbol}[/{self.colour}]"
|
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
Job scheduler models.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from pathlib import Path
|
2
6
|
import sys
|
3
7
|
import time
|
@@ -5,7 +9,22 @@ from typing import Any, List, Tuple
|
|
5
9
|
|
6
10
|
|
7
11
|
class NullScheduler:
|
12
|
+
"""
|
13
|
+
Abstract base class for schedulers.
|
14
|
+
|
15
|
+
Keyword Args
|
16
|
+
------------
|
17
|
+
shell_args: str
|
18
|
+
Arguments to pass to the shell. Pre-quoted.
|
19
|
+
shebang_args: str
|
20
|
+
Arguments to set on the shebang line. Pre-quoted.
|
21
|
+
options: dict
|
22
|
+
Options to the scheduler.
|
23
|
+
"""
|
24
|
+
|
25
|
+
#: Default value for arguments to the shell.
|
8
26
|
DEFAULT_SHELL_ARGS = ""
|
27
|
+
#: Default value for arguments on the shebang line.
|
9
28
|
DEFAULT_SHEBANG_ARGS = ""
|
10
29
|
|
11
30
|
def __init__(
|
@@ -20,6 +39,9 @@ class NullScheduler:
|
|
20
39
|
|
21
40
|
@property
|
22
41
|
def unique_properties(self):
|
42
|
+
"""
|
43
|
+
Unique properties, for hashing.
|
44
|
+
"""
|
23
45
|
return (self.__class__.__name__,)
|
24
46
|
|
25
47
|
def __eq__(self, other) -> bool:
|
@@ -29,20 +51,52 @@ class NullScheduler:
|
|
29
51
|
return self.__dict__ == other.__dict__
|
30
52
|
|
31
53
|
def get_version_info(self):
|
54
|
+
"""
|
55
|
+
Get the version of the scheduler.
|
56
|
+
"""
|
32
57
|
return {}
|
33
58
|
|
34
59
|
def parse_submission_output(self, stdout: str) -> None:
|
60
|
+
"""
|
61
|
+
Parse the output from a submission to determine the submission ID.
|
62
|
+
"""
|
35
63
|
return None
|
36
64
|
|
37
65
|
@staticmethod
|
38
66
|
def is_num_cores_supported(num_cores, core_range: List[int]):
|
67
|
+
"""
|
68
|
+
Test whether particular number of cores is supported in given range of cores.
|
69
|
+
"""
|
39
70
|
step = core_range[1] if core_range[1] is not None else 1
|
40
71
|
upper = core_range[2] + 1 if core_range[2] is not None else sys.maxsize
|
41
72
|
return num_cores in range(core_range[0], upper, step)
|
42
73
|
|
43
74
|
|
44
75
|
class Scheduler(NullScheduler):
|
76
|
+
"""
|
77
|
+
Base class for schedulers that use a job submission system.
|
78
|
+
|
79
|
+
Parameters
|
80
|
+
----------
|
81
|
+
submit_cmd: str
|
82
|
+
The submission command, if overridden from default.
|
83
|
+
show_cmd: str
|
84
|
+
The show command, if overridden from default.
|
85
|
+
del_cmd: str
|
86
|
+
The delete command, if overridden from default.
|
87
|
+
js_cmd: str
|
88
|
+
The job script command, if overridden from default.
|
89
|
+
login_nodes_cmd: str
|
90
|
+
The login nodes command, if overridden from default.
|
91
|
+
array_switch: str
|
92
|
+
The switch to enable array jobs, if overridden from default.
|
93
|
+
array_item_var: str
|
94
|
+
The variable for array items, if overridden from default.
|
95
|
+
"""
|
96
|
+
|
97
|
+
#: Default command for logging into nodes.
|
45
98
|
DEFAULT_LOGIN_NODES_CMD = None
|
99
|
+
#: Default pattern for matching the names of login nodes.
|
46
100
|
DEFAULT_LOGIN_NODE_MATCH = "*login*"
|
47
101
|
|
48
102
|
def __init__(
|
@@ -72,6 +126,9 @@ class Scheduler(NullScheduler):
|
|
72
126
|
return (self.__class__.__name__, self.submit_cmd, self.show_cmd, self.del_cmd)
|
73
127
|
|
74
128
|
def format_switch(self, switch):
|
129
|
+
"""
|
130
|
+
Format a particular switch to use the JS command.
|
131
|
+
"""
|
75
132
|
return f"{self.js_cmd} {switch}"
|
76
133
|
|
77
134
|
def is_jobscript_active(self, job_ID: str):
|
@@ -79,6 +136,9 @@ class Scheduler(NullScheduler):
|
|
79
136
|
return bool(self.get_job_state_info([job_ID]))
|
80
137
|
|
81
138
|
def wait_for_jobscripts(self, js_refs: List[Any]) -> None:
|
139
|
+
"""
|
140
|
+
Wait for jobscripts to update their state.
|
141
|
+
"""
|
82
142
|
while js_refs:
|
83
143
|
info = self.get_job_state_info(js_refs)
|
84
144
|
print(info)
|