snakemake-executor-plugin-slurm 1.8.0__py3-none-any.whl → 1.9.1__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.
Potentially problematic release.
This version of snakemake-executor-plugin-slurm might be problematic. Click here for more details.
- snakemake_executor_plugin_slurm/__init__.py +5 -21
- snakemake_executor_plugin_slurm/efficiency_report.py +56 -13
- snakemake_executor_plugin_slurm/submit_string.py +31 -1
- snakemake_executor_plugin_slurm/validation.py +75 -0
- {snakemake_executor_plugin_slurm-1.8.0.dist-info → snakemake_executor_plugin_slurm-1.9.1.dist-info}/METADATA +1 -1
- snakemake_executor_plugin_slurm-1.9.1.dist-info/RECORD +9 -0
- {snakemake_executor_plugin_slurm-1.8.0.dist-info → snakemake_executor_plugin_slurm-1.9.1.dist-info}/WHEEL +1 -1
- snakemake_executor_plugin_slurm-1.8.0.dist-info/RECORD +0 -8
- {snakemake_executor_plugin_slurm-1.8.0.dist-info → snakemake_executor_plugin_slurm-1.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,6 +18,7 @@ import uuid
|
|
|
18
18
|
|
|
19
19
|
from snakemake_interface_executor_plugins.executors.base import SubmittedJobInfo
|
|
20
20
|
from snakemake_interface_executor_plugins.executors.remote import RemoteExecutor
|
|
21
|
+
|
|
21
22
|
from snakemake_interface_executor_plugins.settings import (
|
|
22
23
|
ExecutorSettingsBase,
|
|
23
24
|
CommonSettings,
|
|
@@ -34,6 +35,7 @@ from .utils import (
|
|
|
34
35
|
)
|
|
35
36
|
from .efficiency_report import create_efficiency_report
|
|
36
37
|
from .submit_string import get_submit_command
|
|
38
|
+
from .validation import validate_slurm_extra
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
@dataclass
|
|
@@ -339,17 +341,6 @@ class Executor(RemoteExecutor):
|
|
|
339
341
|
"- submitting without. This might or might not work on your cluster."
|
|
340
342
|
)
|
|
341
343
|
|
|
342
|
-
# MPI job
|
|
343
|
-
if job.resources.get("mpi", False):
|
|
344
|
-
if not job.resources.get("tasks_per_node") and not job.resources.get(
|
|
345
|
-
"nodes"
|
|
346
|
-
):
|
|
347
|
-
self.logger.warning(
|
|
348
|
-
"MPI job detected, but no 'tasks_per_node' or 'nodes' "
|
|
349
|
-
"specified. Assuming 'tasks_per_node=1'."
|
|
350
|
-
"Probably not what you want."
|
|
351
|
-
)
|
|
352
|
-
|
|
353
344
|
exec_job = self.format_job_exec(job)
|
|
354
345
|
|
|
355
346
|
# and finally the job to execute with all the snakemake parameters
|
|
@@ -682,6 +673,7 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
682
673
|
# we have to quote the account, because it might
|
|
683
674
|
# contain build-in shell commands - see issue #354
|
|
684
675
|
for account in accounts:
|
|
676
|
+
self.test_account(account)
|
|
685
677
|
yield f" -A {shlex.quote(account)}"
|
|
686
678
|
else:
|
|
687
679
|
if self._fallback_account_arg is None:
|
|
@@ -820,13 +812,5 @@ We leave it to SLURM to resume your job(s)"""
|
|
|
820
812
|
return ""
|
|
821
813
|
|
|
822
814
|
def check_slurm_extra(self, job):
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
raise WorkflowError(
|
|
826
|
-
"The --job-name option is not allowed in the 'slurm_extra' parameter. "
|
|
827
|
-
"The job name is set by snakemake and must not be overwritten. "
|
|
828
|
-
"It is internally used to check the stati of the all submitted jobs "
|
|
829
|
-
"by this workflow."
|
|
830
|
-
"Please consult the documentation if you are unsure how to "
|
|
831
|
-
"query the status of your jobs."
|
|
832
|
-
)
|
|
815
|
+
"""Validate that slurm_extra doesn't contain executor-managed options."""
|
|
816
|
+
validate_slurm_extra(job)
|
|
@@ -3,24 +3,67 @@ import pandas as pd
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import subprocess
|
|
5
5
|
import shlex
|
|
6
|
-
|
|
7
|
-
import os # only temporarily needed for printf debugging
|
|
6
|
+
from datetime import datetime
|
|
8
7
|
import numpy as np
|
|
8
|
+
import os
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def time_to_seconds(time_str):
|
|
12
|
-
"""
|
|
13
|
-
|
|
12
|
+
"""
|
|
13
|
+
Convert SLURM sacct time format to seconds.
|
|
14
|
+
|
|
15
|
+
Handles sacct output formats:
|
|
16
|
+
- Elapsed: [D-]HH:MM:SS or [DD-]HH:MM:SS (no fractional seconds)
|
|
17
|
+
- TotalCPU: [D-][HH:]MM:SS or [DD-][HH:]MM:SS (with fractional seconds)
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
- "1-12:30:45" -> 131445 seconds (1 day + 12h 30m 45s)
|
|
21
|
+
- "23:59:59" -> 86399 seconds
|
|
22
|
+
- "45:30" -> 2730 seconds (45 minutes 30 seconds)
|
|
23
|
+
- "30.5" -> 30.5 seconds (fractional seconds for TotalCPU)
|
|
24
|
+
"""
|
|
25
|
+
if (
|
|
26
|
+
pd.isna(time_str)
|
|
27
|
+
or str(time_str).strip() == ""
|
|
28
|
+
or str(time_str).strip() == "invalid"
|
|
29
|
+
):
|
|
14
30
|
return 0
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
|
|
32
|
+
time_str = str(time_str).strip()
|
|
33
|
+
|
|
34
|
+
# Try different SLURM time formats with datetime parsing
|
|
35
|
+
time_formats = [
|
|
36
|
+
"%d-%H:%M:%S.%f", # D-HH:MM:SS.ffffff (with fractional seconds)
|
|
37
|
+
"%d-%H:%M:%S", # D-HH:MM:SS
|
|
38
|
+
"%d-%M:%S", # D-MM:SS
|
|
39
|
+
"%d-%M:%S.%f", # D-MM:SS.ffffff (with fractional seconds)
|
|
40
|
+
"%H:%M:%S.%f", # HH:MM:SS.ffffff (with fractional seconds)
|
|
41
|
+
"%H:%M:%S", # HH:MM:SS
|
|
42
|
+
"%M:%S.%f", # MM:SS.ffffff (with fractional seconds)
|
|
43
|
+
"%M:%S", # MM:SS
|
|
44
|
+
"%S.%f", # SS.ffffff (with fractional seconds)
|
|
45
|
+
"%S", # SS
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
for fmt in time_formats:
|
|
49
|
+
try:
|
|
50
|
+
time_obj = datetime.strptime(time_str, fmt)
|
|
51
|
+
|
|
52
|
+
total_seconds = (
|
|
53
|
+
time_obj.hour * 3600
|
|
54
|
+
+ time_obj.minute * 60
|
|
55
|
+
+ time_obj.second
|
|
56
|
+
+ time_obj.microsecond / 1000000
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Add days if present (datetime treats day 1 as the first day)
|
|
60
|
+
if fmt.startswith("%d-"):
|
|
61
|
+
total_seconds += time_obj.day * 86400
|
|
62
|
+
|
|
63
|
+
return total_seconds
|
|
64
|
+
except ValueError:
|
|
65
|
+
continue
|
|
66
|
+
return 0 # If all parsing attempts fail, return 0
|
|
24
67
|
|
|
25
68
|
|
|
26
69
|
def parse_maxrss(maxrss):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from snakemake_interface_common.exceptions import WorkflowError
|
|
1
2
|
from snakemake_executor_plugin_slurm_jobstep import get_cpu_setting
|
|
2
3
|
from types import SimpleNamespace
|
|
3
4
|
import shlex
|
|
@@ -83,7 +84,36 @@ def get_submit_command(job, params):
|
|
|
83
84
|
else:
|
|
84
85
|
# fixes #40 - set ntasks regardless of mpi, because
|
|
85
86
|
# SLURM v22.05 will require it for all jobs
|
|
86
|
-
|
|
87
|
+
# if the job is a MPI job, ntasks will be set later
|
|
88
|
+
if not job.resources.get("mpi", False):
|
|
89
|
+
call += f" --ntasks={job.resources.get('tasks') or 1}"
|
|
90
|
+
|
|
91
|
+
# if the job is an MPI job, we need to have some task setting:
|
|
92
|
+
if job.resources.get("mpi", False):
|
|
93
|
+
if not job.resources.get("tasks_per_node") and not job.resources.get("tasks"):
|
|
94
|
+
raise WorkflowError(
|
|
95
|
+
"For MPI jobs, please specify either "
|
|
96
|
+
"'tasks_per_node' or 'tasks' (at least one is required)."
|
|
97
|
+
)
|
|
98
|
+
# raise an error if both task settings are used
|
|
99
|
+
if job.resources.get("tasks_per_node") and job.resources.get("tasks"):
|
|
100
|
+
raise WorkflowError(
|
|
101
|
+
"For MPI jobs, please specify either 'tasks_per_node' or 'tasks', "
|
|
102
|
+
"but not both."
|
|
103
|
+
)
|
|
104
|
+
if job.resources.get("tasks_per_node"):
|
|
105
|
+
if job.resources.get("tasks_per_node") <= 1:
|
|
106
|
+
raise WorkflowError(
|
|
107
|
+
"For MPI jobs, 'tasks_per_node' must be greater than 1."
|
|
108
|
+
)
|
|
109
|
+
call += f" --ntasks-per-node={job.resources.tasks_per_node}"
|
|
110
|
+
elif job.resources.get("tasks"):
|
|
111
|
+
if job.resources.get("tasks") == 1:
|
|
112
|
+
raise WorkflowError("For MPI jobs, 'tasks' must be greater than 1.")
|
|
113
|
+
call += f" --ntasks={job.resources.tasks}"
|
|
114
|
+
# nodes CAN be set independently of tasks or tasks_per_node
|
|
115
|
+
# this is at a user's discretion. The nodes flag might already
|
|
116
|
+
# be set above, if the user specified it.
|
|
87
117
|
|
|
88
118
|
# we need to set cpus-per-task OR cpus-per-gpu, the function
|
|
89
119
|
# will return a string with the corresponding value
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SLURM parameter validation functions for the Snakemake executor plugin.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from snakemake_interface_common.exceptions import WorkflowError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_forbidden_slurm_options():
|
|
10
|
+
"""
|
|
11
|
+
Return a dictionary of forbidden SLURM options that the executor manages.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
dict: Mapping of regex patterns to human-readable option names
|
|
15
|
+
"""
|
|
16
|
+
return {
|
|
17
|
+
# Job identification and output
|
|
18
|
+
r"--job-name[=\s]|-J\s?": "job name",
|
|
19
|
+
r"--output[=\s]|-o\s": "output file",
|
|
20
|
+
r"--error[=\s]|-e\s": "error file",
|
|
21
|
+
r"--parsable": "parsable output",
|
|
22
|
+
r"--export[=\s]": "environment export",
|
|
23
|
+
r"--comment[=\s]": "job comment",
|
|
24
|
+
r"--workdir[=\s]|-D\s": "working directory",
|
|
25
|
+
# Account and partition
|
|
26
|
+
r"--account[=\s]|-A\s": "account",
|
|
27
|
+
r"--partition[=\s]|-p\s": "partition",
|
|
28
|
+
# Memory options
|
|
29
|
+
r"--mem[=\s]": "memory",
|
|
30
|
+
r"--mem-per-cpu[=\s]": "memory per CPU",
|
|
31
|
+
# CPU and task options
|
|
32
|
+
r"--ntasks[=\s]|-n\s": "number of tasks",
|
|
33
|
+
r"--ntasks-per-gpu[=\s]": "tasks per GPU",
|
|
34
|
+
r"--cpus-per-task[=\s]|-c\s": "CPUs per task",
|
|
35
|
+
r"--cpus-per-gpu[=\s]": "CPUs per GPU",
|
|
36
|
+
# Time and resource constraints
|
|
37
|
+
r"--time[=\s]|-t\s": "runtime/time limit",
|
|
38
|
+
r"--constraint[=\s]|-C\s": "node constraints",
|
|
39
|
+
r"--qos[=\s]": "quality of service",
|
|
40
|
+
r"--nodes[=\s]|-N\s": "number of nodes",
|
|
41
|
+
r"--clusters[=\s]": "cluster specification",
|
|
42
|
+
# GPU options
|
|
43
|
+
r"--gres[=\s]": "generic resources (GRES)",
|
|
44
|
+
r"--gpus[=\s]": "GPU allocation",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def validate_slurm_extra(job):
|
|
49
|
+
"""
|
|
50
|
+
Validate that slurm_extra doesn't contain executor-managed options.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
job: Snakemake job object with resources attribute
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
WorkflowError: If forbidden SLURM options are found in slurm_extra
|
|
57
|
+
"""
|
|
58
|
+
# skip testing if no slurm_extra is set
|
|
59
|
+
slurm_extra = getattr(job.resources, "slurm_extra", None)
|
|
60
|
+
if not slurm_extra:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
forbidden_options = get_forbidden_slurm_options()
|
|
64
|
+
|
|
65
|
+
for pattern, option_name in forbidden_options.items():
|
|
66
|
+
if re.search(pattern, slurm_extra):
|
|
67
|
+
raise WorkflowError(
|
|
68
|
+
f"The --{option_name.replace(' ', '-')} option is not "
|
|
69
|
+
f"allowed in the 'slurm_extra' parameter. "
|
|
70
|
+
f"The {option_name} is set by the snakemake executor plugin "
|
|
71
|
+
f"and must not be overwritten. "
|
|
72
|
+
f"Please use the appropriate snakemake resource "
|
|
73
|
+
f"specification instead. "
|
|
74
|
+
f"Consult the documentation for proper resource configuration."
|
|
75
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
snakemake_executor_plugin_slurm/__init__.py,sha256=YUFJMWKlrbId4VgRreaea3B0JXJ_xTB1zNBJgAZ59Vw,33953
|
|
2
|
+
snakemake_executor_plugin_slurm/efficiency_report.py,sha256=3F6AqkKOZBn9MxI7T27u3FF3-OAxY_ee_toHXnTGirs,8812
|
|
3
|
+
snakemake_executor_plugin_slurm/submit_string.py,sha256=HL2QyT7-_HRu4yg_7tJSs1VtxGebNODOV8OQd2hYirg,5021
|
|
4
|
+
snakemake_executor_plugin_slurm/utils.py,sha256=7XVXtzu7bg_89wWZisW-Zk7TNQyEgK4v_y4Y3F9uOwc,4491
|
|
5
|
+
snakemake_executor_plugin_slurm/validation.py,sha256=hyWQWDjqHDLhO7caksYILTKjA2Ak91CMJeFwEcltYFo,2702
|
|
6
|
+
snakemake_executor_plugin_slurm-1.9.1.dist-info/METADATA,sha256=kbo2DqmPPULY7OAXxivzfXA4nFk-KJoRP7iyyLx7Gow,1507
|
|
7
|
+
snakemake_executor_plugin_slurm-1.9.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
8
|
+
snakemake_executor_plugin_slurm-1.9.1.dist-info/licenses/LICENSE,sha256=YVc4xTLWMqGfFL36120k7rzXtsT6e4RkJsh68VVn12s,1076
|
|
9
|
+
snakemake_executor_plugin_slurm-1.9.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
snakemake_executor_plugin_slurm/__init__.py,sha256=G40Rbm62GFQF7bo38OJBFEFAxa4BLYeNO66wPQuki-c,34755
|
|
2
|
-
snakemake_executor_plugin_slurm/efficiency_report.py,sha256=crPfJDK4NojfRbu_wEw3ZmC3suMRABr5r-1rO5q3WEo,7429
|
|
3
|
-
snakemake_executor_plugin_slurm/submit_string.py,sha256=12DaJ9BIqEMC19dKuViPU8W5mZRjBRtNDQQu7M-iEjM,3448
|
|
4
|
-
snakemake_executor_plugin_slurm/utils.py,sha256=7XVXtzu7bg_89wWZisW-Zk7TNQyEgK4v_y4Y3F9uOwc,4491
|
|
5
|
-
snakemake_executor_plugin_slurm-1.8.0.dist-info/METADATA,sha256=8-ktC0sGhHDPOAyxpFl1LwWc7wPueZ2PxREKrQguhME,1507
|
|
6
|
-
snakemake_executor_plugin_slurm-1.8.0.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
|
|
7
|
-
snakemake_executor_plugin_slurm-1.8.0.dist-info/licenses/LICENSE,sha256=YVc4xTLWMqGfFL36120k7rzXtsT6e4RkJsh68VVn12s,1076
|
|
8
|
-
snakemake_executor_plugin_slurm-1.8.0.dist-info/RECORD,,
|
|
File without changes
|