Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions eessi_bot_job_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ def parse_scontrol_show_job_output(self, output):
"""
The output of 'scontrol --oneliner show job' is a list of key=value pairs
separated by whitespaces.
Note that with newer Slurm versions (25.11), some values can also contain whitespaces
(e.g. for SubmitLine), making it complex to distinguish between keys and values.
To solve this, we assume that all Slurm keys start with an uppercase letter.
Copy link
Copy Markdown
Contributor

@trz42 trz42 Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could have constrained this a bit more by handing over a list of "expected" keys (and only return key-value pairs for those). While the new assumption may be correct now, we don't know if it will be in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did consider that, but I found this a bit more flexible, and it's now doing the exact same thing as before. They're using these kinds of names everywhere (in scontrol, sacct, sstat, etc), so I guess it's a rather safe assumption?

But I can easily change this if you want to, though it could still break in the future if they decide to change the structure/names.

Ah, you merged it while I was typing this, then I guess I'll leave it like this 😄


Args:
output (string): the output of the scontrol command
Expand All @@ -281,8 +284,20 @@ def parse_scontrol_show_job_output(self, output):
"""
job_info = {}
stripped_output = output.strip()
for pair in stripped_output.split():
key, value = pair.split('=', 1)

# Match keys that start with uppercase and continue until '='
key_pattern = re.compile(r'([A-Z][A-Za-z0-9:/_]*)=')

keys_matches = list(key_pattern.finditer(stripped_output))

for idx, key_match in enumerate(keys_matches):
# The key is what actually got matched by the regex
key = key_match.group(1)
# The value starts where the key ends...
value_start = key_match.end()
# and it ends where the next key starts (or, if it's the last one, where the string ends)
value_end = keys_matches[idx+1].start() if (idx + 1) < len(keys_matches) else len(stripped_output)
value = stripped_output[value_start:value_end].strip()
job_info[key] = value

return job_info
Expand Down
28 changes: 28 additions & 0 deletions tests/test_eessi_bot_job_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,31 @@ def test_determine_finished_jobs():
assert job_manager.determine_finished_jobs(known_jobs, current_jobs_all_jobs) == []
assert job_manager.determine_finished_jobs(known_jobs, current_jobs_one_job) == ['1', '2']
assert job_manager.determine_finished_jobs(known_jobs, {}) == ['0', '1', '2']


def test_parse_scontrol_show_job_output():
# Dummy output (shortened) from Slurm 25.11.3 for "scontrol show job <jobid>"
scontrol_output = 'JobId=123 JobName=bot_test_job UserId=eessibot(12345) MCS_label=N/A EligibleTime=Unknown' \
' AllocNode:Sid=my.node.name:123456 SubmitLine=/opt/slurm/25.11.3/bin/sbatch --hold' \
' --time=10-0:0:0 --nodes=1 --exclusive --cpus-per-task=1 --job-name=bot_test_job ' \
'/home/eessibot/job.slurm WorkDir=/jobs/2026.01/pr_123/event_123-456-789/run_000/riscv64/' \
'generic/dev.eessi.io-riscv StdErr= StdIn=/dev/null StdOut=/jobs/2026.01/pr_123/' \
'event_123-456-789/run_000/riscv64/generic/dev.eessi.io-riscv/slurm-123.out TresPerTask=cpu=1'
job_manager = EESSIBotSoftwareLayerJobManager()
job_info = job_manager.parse_scontrol_show_job_output(scontrol_output)
job_info_expected = {
'JobId': '123',
'JobName': 'bot_test_job',
'UserId': 'eessibot(12345)',
'MCS_label': 'N/A',
'EligibleTime': 'Unknown',
'AllocNode:Sid': 'my.node.name:123456',
'SubmitLine': '/opt/slurm/25.11.3/bin/sbatch --hold --time=10-0:0:0 --nodes=1 --exclusive --cpus-per-task=1 '
'--job-name=bot_test_job /home/eessibot/job.slurm',
'WorkDir': '/jobs/2026.01/pr_123/event_123-456-789/run_000/riscv64/generic/dev.eessi.io-riscv',
'StdErr': '',
'StdIn': '/dev/null',
'StdOut': '/jobs/2026.01/pr_123/event_123-456-789/run_000/riscv64/generic/dev.eessi.io-riscv/slurm-123.out',
'TresPerTask': 'cpu=1',
}
assert job_info == job_info_expected