Skip to content

Commit 167da4a

Browse files
authored
Merge pull request #369 from bedroge/fix_scontrol_output_parsing
Improved parsing of `scontrol` output
2 parents 5a0b562 + b4777fa commit 167da4a

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

eessi_bot_job_manager.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ def parse_scontrol_show_job_output(self, output):
272272
"""
273273
The output of 'scontrol --oneliner show job' is a list of key=value pairs
274274
separated by whitespaces.
275+
Note that with newer Slurm versions (25.11), some values can also contain whitespaces
276+
(e.g. for SubmitLine), making it complex to distinguish between keys and values.
277+
To solve this, we assume that all Slurm keys start with an uppercase letter.
275278
276279
Args:
277280
output (string): the output of the scontrol command
@@ -281,8 +284,20 @@ def parse_scontrol_show_job_output(self, output):
281284
"""
282285
job_info = {}
283286
stripped_output = output.strip()
284-
for pair in stripped_output.split():
285-
key, value = pair.split('=', 1)
287+
288+
# Match keys that start with uppercase and continue until '='
289+
key_pattern = re.compile(r'([A-Z][A-Za-z0-9:/_]*)=')
290+
291+
keys_matches = list(key_pattern.finditer(stripped_output))
292+
293+
for idx, key_match in enumerate(keys_matches):
294+
# The key is what actually got matched by the regex
295+
key = key_match.group(1)
296+
# The value starts where the key ends...
297+
value_start = key_match.end()
298+
# and it ends where the next key starts (or, if it's the last one, where the string ends)
299+
value_end = keys_matches[idx+1].start() if (idx + 1) < len(keys_matches) else len(stripped_output)
300+
value = stripped_output[value_start:value_end].strip()
286301
job_info[key] = value
287302

288303
return job_info

tests/test_eessi_bot_job_manager.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,31 @@ def test_determine_finished_jobs():
135135
assert job_manager.determine_finished_jobs(known_jobs, current_jobs_all_jobs) == []
136136
assert job_manager.determine_finished_jobs(known_jobs, current_jobs_one_job) == ['1', '2']
137137
assert job_manager.determine_finished_jobs(known_jobs, {}) == ['0', '1', '2']
138+
139+
140+
def test_parse_scontrol_show_job_output():
141+
# Dummy output (shortened) from Slurm 25.11.3 for "scontrol show job <jobid>"
142+
scontrol_output = 'JobId=123 JobName=bot_test_job UserId=eessibot(12345) MCS_label=N/A EligibleTime=Unknown' \
143+
' AllocNode:Sid=my.node.name:123456 SubmitLine=/opt/slurm/25.11.3/bin/sbatch --hold' \
144+
' --time=10-0:0:0 --nodes=1 --exclusive --cpus-per-task=1 --job-name=bot_test_job ' \
145+
'/home/eessibot/job.slurm WorkDir=/jobs/2026.01/pr_123/event_123-456-789/run_000/riscv64/' \
146+
'generic/dev.eessi.io-riscv StdErr= StdIn=/dev/null StdOut=/jobs/2026.01/pr_123/' \
147+
'event_123-456-789/run_000/riscv64/generic/dev.eessi.io-riscv/slurm-123.out TresPerTask=cpu=1'
148+
job_manager = EESSIBotSoftwareLayerJobManager()
149+
job_info = job_manager.parse_scontrol_show_job_output(scontrol_output)
150+
job_info_expected = {
151+
'JobId': '123',
152+
'JobName': 'bot_test_job',
153+
'UserId': 'eessibot(12345)',
154+
'MCS_label': 'N/A',
155+
'EligibleTime': 'Unknown',
156+
'AllocNode:Sid': 'my.node.name:123456',
157+
'SubmitLine': '/opt/slurm/25.11.3/bin/sbatch --hold --time=10-0:0:0 --nodes=1 --exclusive --cpus-per-task=1 '
158+
'--job-name=bot_test_job /home/eessibot/job.slurm',
159+
'WorkDir': '/jobs/2026.01/pr_123/event_123-456-789/run_000/riscv64/generic/dev.eessi.io-riscv',
160+
'StdErr': '',
161+
'StdIn': '/dev/null',
162+
'StdOut': '/jobs/2026.01/pr_123/event_123-456-789/run_000/riscv64/generic/dev.eessi.io-riscv/slurm-123.out',
163+
'TresPerTask': 'cpu=1',
164+
}
165+
assert job_info == job_info_expected

0 commit comments

Comments
 (0)