Monitoring & Controlling Jobs#
Once jobs are submitted, you inspect the queue, the nodes, and the accounting
history with a handful of commands, and you steer running jobs with cancel,
hold, and update operations. This page covers viewing the queue (spur queue),
nodes and partitions (spur nodes), accounting history (spur history),
live job stats (spur stat), detailed records (spur show), and the
controls that change a job’s state.
Note
In spur queue (squeue) and spur nodes (sinfo), -h means
--noheader, not help. spur history (sacct) uses -n for the
same purpose.
View the Queue — squeue#
spur queue (Slurm squeue) lists jobs currently in the system. With no
arguments it shows every active job, or only your own if your site sets
private_data = ["jobs"] (see Job and usage visibility (private_data)).
spur queue
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
1 gpu train-ll alice R 2:14 2 node[01-02]
2 default hello bob PD 0:00 1 (Priority)
The default columns are JOBID PARTITION NAME USER ST TIME NODES
NODELIST(REASON). --long/-l adds the full STATE and TIME_LIMIT.
Common flags:
Long |
Short |
Description |
|---|---|---|
|
|
Show only this user’s jobs. |
|
Show only your own jobs (same as |
|
|
|
Filter by partition. |
|
|
Filter by state codes or names (comma list). |
|
|
Filter by account. |
|
|
Filter by job name. |
|
|
Filter by QOS (comma-separated list). |
|
|
Filter by reservation (comma-separated list). |
|
|
Custom column format (see below). |
|
|
Long form; adds STATE and TIME_LIMIT. |
|
|
Omit the header line. |
Note
--me looks up your username on the machine you run it from. If your
login token names a different user than your local account, pass -u
with the token’s name instead: --me would list the local account’s
jobs, or nothing when your site sets private_data = ["jobs"].
When --states is omitted the default filter is PENDING, RUNNING,
SUSPENDED, COMPLETING.
--format/-o uses %[.][-][width]<letter> fields. The resolved letters:
Letter |
Column |
Letter |
Column |
|---|---|---|---|
|
JOBID |
|
CPUS |
|
NAME |
|
NODELIST |
|
USER |
|
NODELIST(REASON) |
|
ACCOUNT |
|
REASON |
|
PARTITION |
|
PRIORITY |
|
QOS |
|
WORK_DIR |
|
ST (short code) |
|
COMMAND |
|
STATE (full) |
|
RESERVATION |
|
TIME |
|
START_TIME |
|
TIME_LIMIT |
|
SUBMIT_TIME |
|
NODES |
|
END_TIME |
|
SCHEDNODES |
|
TIME_LEFT |
|
GRES |
|
PRIORITY |
|
BORROWED |
%p and %Q both render the integer priority. Slurm splits these (%p
is a normalized float, %Q the integer); Spur exposes the integer under both.
spur queue -u alice -t R
squeue --me -t PD
squeue -p gpu -o "%.18i %.9P %.8T %.10M %R"
squeue --states=PD,R --noheader
Verbose field names — squeue -O/--Format#
-O/--Format selects columns by field name instead of %-letter.
Fields are comma-separated, each type[:[.][size][suffix]]:
sizeis a minimum width (default20); unlike-o’s., it never truncates.A leading
.right-justifies the column. The default is left-justified, the opposite of-o.suffixis arbitrary text appended after the field, e.g. a separator.
Field names are case-insensitive and render the same data as their %-letter
equivalents. -O and -o/--format are mutually exclusive; passing both
is an error, matching Slurm.
squeue -O "JobID:10,Partition,State,QOS"
squeue -O "JobID:.18,Priority,Reason"
Supported field names: Account, ArrayJobID, Command, Comment,
EndTime, GRES, JobID, Name, NodeList, NumCPUs,
NumNodes, Partition, Priority, PriorityLong, QOS,
Reason, ReasonList, Reservation, SchedNodes, StartTime,
State, StateCompact, SubmitTime, TimeLeft, TimeLimit,
TimeUsed, UserName, WorkDir.
Slurm exposes many more -O fields that Spur has no backing data for
(Licenses, Dependency, Nice, Reboot, most of the tres-*
family, federation fields, job-step fields, and others). Requesting an
unsupported name is an error listing the supported fields, rather than the
blank column Slurm prints. This is a deliberate divergence: fail fast on a
typo instead of silently emitting an empty column. Priority renders Spur’s
integer priority (same as PriorityLong); Spur does not compute Slurm’s
normalized 0.0-1.0 float.
Projected Start Times — squeue --start#
--start answers “when will my job run, and where” for the whole queue at
once, rather than one job at a time through scontrol show job:
$ squeue --start
JOBID PARTITION NAME USER ST START_TIME NODES SCHEDNODES NODELIST(REASON)
1024 gpu train.sh alice PD 2026-08-27T17:09:56 2 node[07-08] (Resources)
1031 gpu eval.sh bob PD N/A 1 (null) (Priority)
It restricts the view to pending jobs and orders them by projected start,
soonest first; jobs with no reserved slot report N/A and sort last. Each of
those defaults is overridable by passing -t, -S, or -o explicitly.
%S and %e carry the same projection in any format string, so
squeue -o "%.18i %.19S %.19e" works without --start.
Note
Projections come from the backfill pass on the controller’s leader, and assume every running job uses its full time limit. A job that finishes early moves every projection behind it earlier.
Job state codes. The ST column uses these short codes:
Code |
State |
Code |
State |
|---|---|---|---|
|
PENDING |
|
CANCELLED |
|
RUNNING |
|
TIMEOUT |
|
COMPLETING |
|
NODE_FAIL |
|
COMPLETED |
|
PREEMPTED |
|
FAILED |
|
SUSPENDED |
|
DEADLINE |
|
OUT_OF_MEMORY |
For a pending job the NODELIST(REASON) column shows why it is waiting.
Common reasons include Priority (waiting its turn), Resources (waiting
for nodes to free up), Dependency (waiting on another job), Reservation
(waiting for a reservation window), Preempted (requeue-preempted and held
until its eligibility window reopens), JobArrayTaskLimit (held back by the
array’s own %N concurrency cap, see Job Arrays), and various QOS
or association limit reasons (QOSMax*, AssocMax*, AssocGrp*).
View Nodes & Partitions — sinfo#
spur nodes (Slurm sinfo) shows partition and node state. The default is a
partition-oriented view; -N switches to one line per node.
spur nodes
PARTITION AVAIL TIMELIMIT NODES STATE NODELIST
default* up infinite 4 idle node[01-04]
gpu up 1-00:00:00 2 mix node[05-06]
Flags:
Long |
Short |
Description |
|---|---|---|
|
|
Filter by partition. |
|
|
Filter by node state (comma-separated). Accepts the base states and the
display-only labels |
|
|
Filter by node. |
|
|
One line per node instead of per partition. |
|
|
List unavailable nodes (down/drain/draining/error) grouped by reason. |
|
|
Custom column format. |
|
|
Long form; adds CPUS and MEMORY. |
|
|
Omit the header line. |
Default columns (partition view): PARTITION AVAIL TIMELIMIT NODES STATE
NODELIST; -l adds CPUS and MEMORY. Node view (-N): NODELIST NODES
PARTITION STATE CPUS MEMORY GRES.
sinfo -N -o "%N %.6D %.11T %c %m %G"
sinfo -p gpu -l
-R / --list-reasons lists only nodes that are unavailable (down,
drain, drng, err), grouped by the admin reason set when the node was
drained or downed, one row per reason with a compressed nodelist:
REASON USER TIMESTAMP NODELIST
Memory errors alice 2026-08-27T08:03:07 node[05,08]
Not responding root 2026-08-27T09:15:44 node[01-03]
All four columns are populated from live state. USER (%u, or %U for
user(uid)) is the user who set the reason, resolved from the recorded UID;
automatic reasons (for example a heartbeat timeout) are attributed to root.
TIMESTAMP (%H) is when the reason was set. A UID that cannot be resolved
to a name, or a node whose reason predates provenance tracking, renders as
Unknown. -R -l adds a STATE column (%t).
scontrol show node appends the same provenance to the reason line as
Reason=<text> [<user>@<timestamp>] when a set-time is recorded, and the REST
node object carries reason_uid and reason_time fields.
Node states are shown as short abbreviations: idle (free), alloc (fully
allocated), mix (partly allocated), down, drain (offline, not
accepting jobs), drng (draining), err (error), unk
(unknown/unreachable), susp (suspended), and — all three only shown while
the node is currently idle — resv or maint for a node held by an admin
reservation, else plnd for a node currently held by the scheduler for a
specific pending job’s upcoming start. The idle gate is checked live, but the
job and start time shown for plnd reflect the most recent scheduling
cycle (scheduler.interval_secs), not the current instant.
Every abbreviation above, including the overlay labels resv, maint, and
plnd, can be passed to -t / --states. The long forms reserved
and planned are accepted as aliases for resv and plnd. -t resv
also matches maint nodes (a maintenance reservation is still a reservation),
while -t maint matches only maintenance nodes. -t idle returns all idle
nodes, including those shown as resv, maint, or plnd.
Accounting History — sacct#
spur history (Slurm sacct) reports finished and running jobs from the
accounting service.
spur history -u alice -S now-7days
Flags:
Long |
Short |
Description |
|---|---|---|
|
|
Filter by user. |
|
|
Filter by account. |
|
|
Earliest submit/start time to include. |
|
|
Latest time to include. |
|
|
Filter by state (comma list). Accepted values (long or short code):
|
|
|
Filter by job ID (comma list; step suffixes like |
|
|
Comma-separated field names (see below). |
|
|
Long form; adds DerivedExitCode, Start, End, TimeLimit, the three preemption fields, and Reason. |
|
|
Brief form: JobID, State, ExitCode. |
|
|
Omit the header line. |
|
Maximum rows to return. Default |
Unlike squeue, --format here takes comma-separated field names, not
% letters. Available fields include JobID, JobName, User,
Account, Partition, State, Elapsed, NNodes, ExitCode,
DerivedExitCode, Start, End, Submit, TimeLimit, NodeList,
NCPUS, ReqMem, QOS, PreemptedBy, PreemptMode, PreemptQOS,
Borrowed, Reason, ReasonDetail, and EndedBy. Set a per-field width with
Field%N, e.g. JobName%20.
PreemptedBy is the job ID of the higher-priority job that caused the
preemption (N/A when the job was not preempted). PreemptMode is one of
Requeue, Cancel, or Suspend. PreemptQOS is the QOS of that
preempting job — the QOS whose preempt allow-list authorized the
preemption. It is set for any job preempted by this release; jobs preempted by
an earlier release, which recorded no authorizing QOS, show N/A even though
they were preempted. All three appear in the long format (-l).
Borrowed is yes when the run took borrowed capacity under
Idle-Fill Scheduling, and no otherwise. It is the same
question squeue’s %W answers for a running job:
$ sacct --format=JobID,User,State,Borrowed
JobID User State Borrowed
19 ifbob PREEMPTED yes
21 ifalice COMPLETED no
Why a job ended#
Reason is a short code saying why the job’s run ended, and ReasonDetail
adds what Spur knew beyond the code. Use Reason to group failures, for
example sacct -n -s FAILED,NODE_FAIL --format=Reason piped through
sort | uniq -c.
$ sacct --format=JobID,State,ExitCode,Reason,ReasonDetail
JobID State ExitCode Reason ReasonDetail
41 FAILED 127:0 NonZeroExitCode exit 127: command not found (shell convention)
42 FAILED 0:11 RaisedSignal signal 11 (Segmentation_fault)
43 FAILED 1:0 CompletingTimeout no completion reported by node3
44 TIMEOUT 0:15 TimeLimit
Reason |
State |
When |
|---|---|---|
|
COMPLETED |
The job ended normally. |
|
FAILED |
The job exited with a non-zero code. For 126 and 127 the detail notes the shell convention (“not executable”, “command not found”); that is a hint, since a script may also exit 127 itself. |
|
FAILED |
A signal ended the job; the detail names it. |
|
FAILED |
The controller stopped waiting for the job’s nodes to report; the detail lists the nodes that never did. |
|
TIMEOUT |
The job reached its time limit, however the process then died. |
|
DEADLINE |
The job’s |
|
OUT_OF_MEMORY |
The kernel killed the job for exceeding its memory limit. |
|
PREEMPTED |
A higher-priority job preempted it; the detail names that job and its QOS. |
|
CANCELLED |
The job’s owner cancelled it. |
|
CANCELLED |
An operator or administrator cancelled someone else’s job. |
|
CANCELLED |
Spur cancelled it: a dependency that can never be met, an ended
reservation, a failed prolog on an interactive or |
|
CANCELLED |
Cancelled, but who did it was not recorded: the job was cancelled before this release, or by an older controller during a rolling upgrade. |
|
REQUEUED |
The job’s owner or an operator requeued it while it was running. |
|
NODE_FAIL |
A node the job ran on went down. The detail names the node and its
down reason, as |
|
NODE_FAIL |
A node the job ran on was removed from the cluster; the detail names the node and the removal reason. |
|
NODE_FAIL |
The job could not be started on every node; the detail says why. |
EndedBy is the user who cancelled the job, for UserCancel and
AdminCancel; a SystemCancel has no user and, like every other reason,
shows N/A. It is shown to operators and administrators, the same audience as
sacctmgr show txn; for anyone else it reads N/A. When [auth] runs in
disabled or permissive mode an unauthenticated caller counts as that
audience, as it does for the audit log.
Reason reads N/A for a job that is still running, for rows written
before this field existed, and for rows written through the external
RecordJobEnd accounting RPC. Accounting keeps one row per job,
so a requeued job’s next run replaces the reason with its own. During a
rolling upgrade, a job that restarts and then ends while an older controller
is leader keeps the reason of its previous run, and a run the older controller
force-finished reads NonZeroExitCode rather than CompletingTimeout.
The default columns are JobID JobName User Account Partition State Elapsed
NNodes ExitCode.
Time arguments accept an absolute date (YYYY-MM-DD or
YYYY-MM-DDTHH:MM:SS) or a relative offset (now-7days, now-6hours).
sacct -S 2026-07-01 -E 2026-07-25 -s FAILED --limit 500
sacct --format=JobID,JobName,State,Elapsed,ExitCode
sacct -s PREEMPTED --format=JobID,JobName,State,ExitCode,PreemptedBy,PreemptMode,PreemptQOS
sacct -s PR,CA # short codes also accepted
Running-Job Stats — sstat#
spur stat (Slurm sstat) reports live resource usage for running jobs.
--jobs/-j is required (comma list).
spur stat -j 12345
sstat -j 12345,12346 -o JobID,NTasks,GPUAlloc,Elapsed -p
Default fields: JobID NTasks NCPUS MemAlloc GPUAlloc Elapsed Nodelist.
--format/-o takes field names; --parsable/-p produces
|-delimited output.
Note
The per-process Ave* and Max* fields (AveCPU, AveRSS,
MaxRSS, and similar) always report N/A — Spur does not collect
per-process metrics.
Detailed Records — scontrol show#
spur show (Slurm scontrol show) prints the full Key=Value record for
an entity: job, node, partition, reservation, step, or
assoc_mgr.
Note
The diagnostic fields below are printed on every job, with (null) for an
unset string, so a parser must key on the field name rather than on a line
being absent. Some fields still appear only when set (Comment,
Reservation, ArrayJobId, SchedNodeList, StdIn, the GPU line,
the preemption line, and the SuspendedBy/LastRequeue line), and
MinMemoryNode is spelled MinMemoryCPU for a --mem-per-cpu job.
For a live job someone acted on, scontrol show job adds a line naming who.
SuspendedBy is the user who suspended the job, only while it stays
suspended; a job paused by preemption shows the preemption line instead.
LastRequeue says why the job last went back to the queue: Failure (a
launch failure, time limit or node failure with --requeue), Preempt, or
Admin (an scontrol requeue by the owner or an operator), with whoever
requeued it in parentheses for Admin:
SuspendedBy=alice LastRequeue=Admin(ops)
The user names are shown to the same audience as EndedBy in sacct
(operators, administrators, and unauthenticated callers when [auth] is
disabled or permissive); for anyone else they are blank and
LastRequeue shows the kind alone.
scontrol show job 1024
spur show node node01
scontrol show partition gpu
Expanding node lists — scontrol show hostnames / hostlist#
scontrol show hostnames expands a hostlist expression to one host per line;
scontrol show hostlist (and hostlistsorted) does the reverse, compressing
a list back into the bracketed form. Both are pure local transforms: they never
contact the controller, so they work inside a batch script on any compute node.
With no argument, hostnames reads SLURM_JOB_NODELIST (Spur also honors
SPUR_JOB_NODELIST), which makes it the standard way to derive a rendezvous
address or an MPI hostfile from an allocation:
scontrol show hostnames 'gpu[01-03]' # -> gpu01 / gpu02 / gpu03, one per line
scontrol show hostlist gpu03,gpu01,gpu02 # -> gpu[01-03]
# Inside a job: first allocated node as the rendezvous host, all nodes to a hostfile.
export MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n1)"
scontrol show hostnames > "$HOME/hostfile.$SLURM_JOB_ID"
hostlist and hostlistsorted produce identical output in Spur: both remove
duplicates and sort before compressing.
Diagnosing a job that will not start#
A job pending on Reason=Resources looks the same whether the cluster is full
or the job is pinned to a busy subset of it. scontrol show job reports the
request as submitted, which distinguishes the two:
A job that no node could host even when completely idle reports
Reason=ReqNodeNotAvail, Requested node configuration is not available
instead. Waiting will not help it: either the request exceeds every node’s
capacity, or it names a resource no node declares — a typo such as
--gres 1 asks for one unit of a resource literally named 1, which is
easy to miss because it never appears in ReqTRES. Compare ReqTRES and
MinCPUsNode / MinMemoryNode below against spur show node, and
check --gres spelling. The reason is recomputed every scheduling cycle, so
the job starts on its own once matching nodes join.
Field |
Meaning |
|---|---|
|
Nodes requested with |
|
Nodes excluded with |
|
Nodes actually allocated. Empty ( |
|
Node feature constraint from |
|
Requested resource totals, e.g. |
|
Per-node minima implied by the request. Memory is in MB with an |
|
Submission flags and the requeue count for this job. |
|
Elapsed and requested wall time. |
|
First executable line of the batch script. |
|
The submit command as invoked, so any submission-flag question is answerable from one command. |
|
Earliest the job may start ( |
|
When the job began accruing age priority. |
|
While pending, the slot the scheduler is holding: when it projects the
job will start, and on which nodes. With no slot reserved, |
|
The recorded end once the job finishes, otherwise its start plus its
time limit. |
|
The last scheduling cycle that considered this job, so it advances even
on a cycle that places nothing. |
Note
StartTime, SchedNodeList, and LastSchedEval live only on the
leader and are never replicated, so a read served by a follower reports them
unset even in a healthy cluster.
So a job pinned to a busy node reads (excerpt — the full record has more fields between these lines):
JobState=PENDING Reason=Resources Dependency=(null)
...
StartTime=2026-08-27T17:09:56 EndTime=2026-08-27T17:14:56 Deadline=N/A
...
ReqNodeList=node07 ExcNodeList=(null)
NodeList=(null) SchedNodeList=node07
...
SubmitLine=sbatch -w node07 --exclusive -t 5 job.sh
The controller logs the matching scheduler-side view at info level, one line
per job when its placement outcome changes (not every cycle). A cycle with no
schedulable nodes at all is skipped before this runs, so it logs nothing. Jobs beyond the
per-cycle scheduling depth limit are not reported:
backfill did not start job job_id=1024 reason=no_suitable_nodes needed_nodes=1 candidate_nodes=0
reason is one of het_group_incomplete, no_suitable_nodes,
requested_nodes_unavailable, too_few_candidates, no_capacity_at_start,
or future_slot_reserved. The last means the job can be placed and the
scheduler is holding a future slot for it, reported with planned_start.
Note
Spur accrues age priority from submit time for every pending job, including
held and dependency-blocked ones. Slurm suspends accrual for those, so
AccrueTime can read earlier here than on an equivalent Slurm job.
Preemption provenance#
When a job has been preempted, scontrol show job includes three additional
fields:
PreemptedBy=<job_id>— the ID of the higher-priority job that triggered the preemption. Only shown when the job has been preempted.PreemptMode=Requeue|Cancel|Suspend— how the preemption was carried out.PreemptQOS=<name>— the QOS of the preempting job, whosepreemptallow-list authorized the preemption. Set for any job preempted by this release; jobs preempted by an earlier release showN/Aeven though they were preempted.
Borrowed runs and reclaim#
When Idle-Fill Scheduling is enabled, a job that exceeded its
QOS group node quota may still run, on capacity no job with a quota claim wanted.
Such a run is borrowed, and squeue’s %W field reports it:
$ squeue -o "%i %j %u %t %N %W"
JOBID NAME USER ST NODELIST BORROWED
18 alice-legit ifalice R node-3 no
19 bob-borrow ifbob R node-4 yes
A borrowed job can be reclaimed when a job that does hold a quota claim needs its
nodes. Reclaim requeues: the job returns to the queue with its spec intact and
starts again once capacity allows, so the partial run is lost but the work is not.
It appears as an ordinary preemption, with PreemptMode=Requeue and
PreemptedBy naming the job that reclaimed it, and sacct records that the run
was borrowed.
Note that --no-requeue does not prevent reclaim, and that an over-quota job
which has not been lent capacity keeps reporting QOSGrpNodeLimit rather than a
placement reason — being over quota is the true reason it is waiting.
Quick reference — one command for any preempted job:
scontrol show job <id>
This works regardless of preemption mode (requeue, cancel, or suspend) as long as the job is still known to the controller (running, pending, suspended, or recently terminal). For cancel-preempted jobs that have been evicted from memory, fall back to:
sacct -j <id> --format=JobID,State,PreemptedBy,PreemptMode,PreemptQOS
The accounting database keeps the record permanently. In practice
scontrol show job is the right first instinct; fall back to sacct when
it reports Job <id> not found (a non-zero exit status), which means the
controller no longer holds the job. A malformed id is different:
scontrol show job abc fails with Invalid job id specified: abc, so a
script can tell a typo from a job that has left the controller.
Note
Suspend-mode preemption and accounting. When PreemptMode=Suspend,
the job receives SIGSTOP and stays running — no accounting end-record
is written. The provenance fields (PreemptedBy, PreemptMode,
PreemptQOS) are visible in scontrol show job while the job is
suspended, but are cleared when the job resumes so that a subsequent normal
completion is not miscounted as a preemption. As a result, sacct has
no record of the preemption for suspend-mode jobs: the accounting row for
that run will show the final completion state only. Requeue mode is
unaffected — it writes an accounting end-record (PREEMPTED) at the time
of preemption. Cancel mode writes the same record, but only once the run has
actually ended: the victim holds its allocation in COMPLETING until every
node reports the release, or until CompleteWait elapses.
Limits Against Usage — scontrol show assoc_mgr#
scontrol show assoc_mgr answers the question squeue cannot: how much of a
QOS or an association each user is holding right now, next to the caps that
govern them. Without it an operator has to infer per-user totals by counting
squeue rows.
scontrol show assoc_mgr
scontrol show assoc_mgr alice
scontrol show assoc_mgr users=alice
Output is two sections of Key=Value blocks — QOS records, then association
records. Each block opens with the scope itself and lists a line per user under
it:
QOS Records
QOS=highprio MaxWall=01:00:00 MaxTRESPJ=cpu=32 MaxJobsPU=2 MaxSubmitJobsPU=N MaxTRESPU=node=4 MaxSubmitJobsPA=20
GrpJobs=N(9) GrpSubmitJobs=N(11) GrpTRES=cpu=N(36),node=16(9) GrpWall=7-00:00:00(2-12:00:00)
User=alice MaxJobsPU=2(6) MaxSubmitJobsPU=N(7) MaxTRESPU=cpu=N(24),node=4(6) OverLimit=MaxJobsPU,MaxTRESPU
User=bob MaxJobsPU=2(1) MaxSubmitJobsPU=N(1) MaxTRESPU=cpu=N(4),node=4(1)
Association Records
Account=tenant-a MaxWall=N
GrpJobs=N(1) GrpSubmitJobs=N(1) GrpTRES=node=N(1)
User=alice MaxJobs=4(1) MaxSubmitJobs=N(1) MaxTRES=cpu=N(4),node=N(1) MaxTRESPJ=node=4
Reading it:
On the
Grp*line and theUser=lines every cap exceptMaxTRESPJis printed as ``Limit(Consumed)``, as in Slurm:node=4(6)is a cap of four nodes with six in use, andNmarks no cap.GrpWallfollows the same shape as a wall-clock time,budget(spent); anNin its consumed slot means the controller has not read spend yet (its usage cache holds no snapshot), which is not the same as none spent. The per-job and per-account caps (MaxWall,MaxTRESPJ,MaxSubmitJobsPA), and a QOS’s per-user caps on its scope line, print bare, with no consumption beside them, because they bound each job, account, or user rather than a total the scope accrues.For the count caps a literal
0is a real cap that blocks every job it governs. A TRES dimension is the exception:0there is treated as unset — it renders asNand is not enforced, soGrpTRES=node=0does not block a dimension the wayMaxJobs=0blocks jobs.Consumption is live, measured exactly as the scheduler measures it when it admits a job. Node counts are distinct occupied nodes, so two jobs sharing a node hold one node, not two. A TRES dimension appears when either the cap or the usage has something to say about it.
The scope line carries what belongs to the scope: its wall-clock ceiling
MaxWall, and — for a QOS, which caps every user identically — its per-job capMaxTRESPJ, the per-user caps it enforces (MaxJobsPU,MaxSubmitJobsPU,MaxTRESPU), and the per-account submit capMaxSubmitJobsPA. An association caps each(user, account)pair on its own, so its per-user caps and itsMaxTRESPJride on eachUser=line instead.MaxTRESPJbounds one job;MaxTRESPU, orMaxTRESon an associationUser=line, bounds that user’s total.MaxSubmitJobsPAandGrpWallare QOS-only and never appear in association records.Grp*figures are the whole scope’s, summed across every user, and stay on the scope line.GrpWallis the QOS’s wall-clock budget beside the spend measured overgrp_wall_window_days(see Accounting, Accounts, Users, and QOS). Filtering to one user narrows theUser=lines but never the group figures, since a group cap cannot be judged from one user’s share.OverLimitlists caps that are already exceeded, on the scope line for group caps — includingGrpWallonce spend reaches the budget, the state behind aQOSGrpWallLimithold — and on a user’s line for that user’s caps. It is absent when everything is within its cap. Usage over a cap is not a contradiction: caps are applied when a job is admitted and running jobs are never re-checked, so tightening a cap under running work leaves exactly this state.
Records come from the accounting definitions as well as from the queue, so a QOS
nobody is using still appears with its caps and no User= lines — an
over-tight cap on an idle QOS is exactly the kind of mistake that hides
otherwise. A QOS deleted after its jobs queued also stays reported for as long as
those jobs hold resources.
A LimitsReadable=NO line above the sections means accounting is enabled but a
cache has not loaded yet, so some caps below may be missing; the usage figures
are still current. A cluster with accounting disabled has no caps to read and so
never prints this line.
By default every caller sees every scope and may pass users=<name> to
inspect one user. When an administrator sets private_data = ["usage"] under
[auth] (see Job and usage visibility (private_data)), an unprivileged caller sees only the QOS
and accounts they take part in, and only their own User= lines; a users=
selector is replaced with their own name. Grp* totals on the scope line
still include every user’s jobs. Operators and Administrators are exempt.
Cluster Metrics — /metrics#
Beyond the per-job CLI commands above, spurctld exports cluster-wide metrics
over HTTP in Prometheus/OpenMetrics text format. This is the surface a
monitoring stack (Prometheus, Grafana, an OpenMetrics scraper) consumes to chart
queue depth, node utilization, and resource allocation over time.
The server is controlled by the [metrics] section of spur.conf (see
Configuration Reference (spur.conf)). It listens on port 6822 and by default
binds to loopback only; set bind = "all" to expose it to a scraper on
another host. Only the Raft leader serves data — followers return
503 Service Unavailable — so point your scraper at all controllers and let it
follow the leader.
curl http://127.0.0.1:6822/metrics/jobs
curl http://127.0.0.1:6822/metrics/nodes
Endpoints#
Path |
Status |
Contents |
|---|---|---|
|
Live |
Alias for |
|
Live |
Job counts by state and aggregate allocated resources. |
|
Live |
Node counts by state, cluster resource totals, and per-node gauges. |
|
Planned |
Route exists but currently returns an empty body. |
|
Planned |
Route exists but currently returns an empty body. |
|
Live |
Spur-managed k0s cluster lifecycle and per-node health ( |
|
Planned |
Per-user/per-account breakdown. Returns |
Job metrics — /metrics/jobs#
All gauges. <state> expands to one metric per job state: pending,
running, completing, completed, failed, cancelled,
timeout, node_fail, preempted, suspended, deadline,
out_of_memory.
Metric |
Description |
|---|---|
|
Total number of jobs. |
|
Number of jobs in each state. |
|
CPUs allocated to running/completing jobs. |
|
Memory (bytes) allocated to running/completing jobs. |
|
GPUs allocated to running/completing jobs. |
Node metrics — /metrics/nodes#
All gauges. Cluster-wide totals carry no labels; per-node gauges carry a
node=<name> label. <state> expands to one metric per node state:
idle, alloc, mixed, down, drain, draining, error,
unknown, suspended.
Metric |
Description |
|---|---|
|
Total number of nodes. |
|
Number of nodes in each state. |
|
Total and allocated CPUs across all nodes. |
|
Total and allocated memory (bytes) across all nodes. |
|
Total and allocated GPUs across all nodes. |
|
Total and allocated CPUs on the labeled node. |
|
Total and allocated memory (bytes) on the labeled node. |
|
Total and allocated GPUs on the labeled node. |
|
CPU load reported by the node agent. |
|
Free memory (bytes) reported by the node agent. |
k0s metrics from spurctld — /metrics/k8s#
When Spur manages a k0s cluster (spur k8s up, see
Spur-Managed Kubernetes (k0s)), spurctld exports its own view of the
cluster lifecycle and per-node health at /metrics/k8s. Every series carries
distribution="k0s" and cluster="<cluster-name>".
Metric |
Description |
|---|---|
|
Current cluster phase as a one-hot set ( |
|
1 when the cluster phase is Ready (primary alerting signal). |
|
Configured control-plane replica count. |
|
Total nodes with a k0s role, and the per-role count. |
|
Provisioning attempts and attempts that gave up before Ready. |
|
Phase transitions labeled by source and destination. |
|
Reconcile-loop iteration wall time (histogram) and error count. |
|
Nodes whose running k0s disagrees with the controller’s record
( |
|
Strays the controller stopped, counted once the node stays stopped across
two checks. Only |
|
Stops of a confirmed stray the controller declined: |
|
Stopped strays whose k0s came back. |
|
Nodes newly recorded as failing to start k0s. |
|
Whether a node’s k0s systemd unit reports active. |
|
Per-node k0s unit restarts and install time (histogram). |
Each controller counts only what it saw while it was the leader, and its
counters reset when that process restarts. A leader change also starts drift
episodes over, so a drift that is still present is counted again by the new
leader rather than staying flat. Alert on increase(...[1h]) > 0 summed
across controllers rather than on rate, which falls back to zero while the
drift is still present. The counters carry no node label, to keep series
bounded; the spurctld log line names the node.
k0s component metrics (incoming)#
Note
The endpoints in this section are incoming / in progress and are separate
from the spurctld /metrics/k8s surface above. The bundled k0s
components each expose their own upstream Kubernetes /metrics endpoint;
Spur does not yet aggregate or proxy these — the ports and paths below are the
standard Kubernetes surfaces documented here for planning a scrape
configuration.
These endpoints are served by the k0s-managed components, not by spurctld.
Control-plane endpoints live on the control-plane node; the kubelet endpoints
live on every node. Most require TLS and authentication.
Component |
Port / Path |
Status |
Contents |
|---|---|---|---|
kube-apiserver |
|
Incoming |
API request latency/counts, etcd cache, admission timings. |
kubelet |
|
Incoming |
Node agent, pod lifecycle, and volume stats. |
kubelet (cAdvisor) |
|
Incoming |
Per-container CPU/memory/network/disk usage. |
kubelet (resource) |
|
Incoming |
Node/pod CPU and memory for the metrics pipeline. |
kube-scheduler |
|
Incoming |
Scheduling attempts, latency, and queue depth. |
kube-controller-manager |
|
Incoming |
Controller work queues and reconcile timings. |
etcd |
|
Incoming |
Datastore health, latency, and DB size. |
CoreDNS |
|
Incoming |
Cluster DNS query counts, latency, and cache stats. |
Controlling Jobs#
Cancel jobs with spur cancel (Slurm scancel), by job ID or by filter:
scancel 1024 1025
spur cancel -u alice -p gpu --state PENDING
scancel --signal SIGTERM 2048
Filter flags include --user/-u, --partition/-p, --state/-t
(only PD or R), --name/-n, --account/-A, and
--signal/-s (KILL/9, TERM/15, INT/2, and others). You must
supply job IDs or at least one of --user, --name, --partition, or
--account; --state alone only narrows a selection. Without --user, a
filter matches every user’s jobs you can see, as in Slurm, and each one you do not
own is refused. In filter mode, jobs already in a terminal state are silently
skipped. scancel exits 1 and reports on stderr if any cancel or signal is
refused. A job that is already finished or unknown does not fail a plain cancel
(or -s KILL) and is not reported. With another signal it fails, and
--quiet/-Q hides the report but not the exit status.
Change job state with spur control (Slurm scontrol):
scontrol hold 1024 # prevent from starting
scontrol release 1024 # allow a held job to start
scontrol requeue 1024 # return a job to the queue
scontrol suspend 1024 # SIGSTOP, keep the allocation
scontrol resume 1024 # SIGCONT
Update a job or node with scontrol update and Key=Value pairs. Job
updates need JobId= and accept Priority=, TimeLimit=, Partition=,
Account=, Comment=, and QOS=. Node updates need NodeName= and
accept State= and Reason=.
scontrol update JobId=1024 TimeLimit=2:00:00 Priority=100
scontrol update NodeName=node01 State=drain Reason="maintenance"