Skip to content

Statements

The kernel statements of a session or server block. Commands take no time: they run whenever a session is ready, in the order sessions became ready. Only run lets the clock move. Every expr below is evaluated at the Session moment unless the entry says otherwise.

The serving vocabulary provides shorthand for these statements. Resource amounts must have the matching Cost type: run work for its stage(s), and hold/grow/load amounts for their pools. Common reuse and cache expressions name all pools held by that hold. These conversions do not change evaluation moments. Lease durations, indices and predicates remain ordinary quantities.

Statement Does
Size / Cost declares request quantities or resource costs
set assigns a session attribute
observe records a sample
turn draws attributes and waits for the turn's response
hold takes units of pools for the scope of a block
grow enlarges the innermost hold
drop discards the session's cached prefix
release gives the innermost hold's units back now
load the KV of some tokens arrived from outside
run spends work at a stage
branch a test or a draw
while repeats while a condition holds
loop repeats a block
choose picks an index by the smallest key
end ends the session

Typed declarations

Size items = 3;
Cost duration = cost(svc, 2 * items);
Cost processing = { mem: items, svc: 2 * items };

Size declarations belong to the workload. Cost declares a scalar resource cost or a record of independent resource costs. A bare number cannot initialize a scalar Cost because it does not identify a resource. Declaration computes and stores values; hold, run, grow and load apply them at their ordinary execution moments. See attribute types for ownership, field order and initialization rules.

Several pools can share one scope:

hold P(processing.P), Q(processing.Q) { run svc(processing.svc); }

Here P and Q stand for declared pools. Admission needs room in both and waits in P's queue; this differs from nested holds, where P can remain allocated while waiting for Q. Use one hold for a common admission and lifetime, and nesting for different lifetimes.

A queue entry's admission header can read its parameters and own resources, but cannot read Cost fields computed by its body. Keep those conversions in the header, for example hold kv(cost(kv, prompt)), or pass the ordinary quantity as an entry parameter. Cost fields remain available to the body.

set

set NAME = expr;

Assigns the session attribute NAME. Workload sizes are read-only in the server; use a separate server attribute for derived quantities. Every name assigned by set or choose in session code is an attribute of every session. In an iteration body, set instead assigns an engine register.

observe

observe NAME = expr;

Records a sample of expr after warm-up, with the time, session and turn. --dump DIR writes the samples; the report summarises them. An observation whose outermost operator is a comparison, &&, || or !, and that stays 0 for at least 40 samples, produces a note: observe … is constant 0 diagnostic. Check whether the condition was expected to occur before interpreting its summary.

turn

turn;

Loads the next trace turn (if present), then runs the workload's turn block. The server handles that turn; the next session statement waits for its response. Allowed only in a workload's session. The client's time between turns is tool Z;, which runs on a delay of the workload's own unless a stage tool is declared.

hold

hold POOL (units) [reserve (r)] [, POOL (units) [reserve (r)]]*
     [reuse (ρ)]
     [at admission (NAME = expr, …)]
     block
     [cache (ℓ)]
     [lease POOL (t)];

Joins the queue of the first pool. When the head is admitted, units of each pool are allocated, the body runs, and at its end the units are released and min(ℓ, computed) stay cached (rounded down to blocks). The cache clause is what makes the hold take part in the prefix cache: with it the admission consumes the session's own cached prefix (cached); without it the hold leaves the session's cached blocks where they are and cached is 0 in its body (a body that reads it there, or a reuse there, does not link). cache (0) consumes the prefix and keeps nothing.

Argument Type Moment Default Description
POOL pool One or more. Admission needs room in every one; the hold waits in the first pool's queue.
units Cost(POOL) Admit Units to allocate at admission.
reserve Cost(POOL) Admit units Room required before admitting: max(units, r), rounded up to blocks. It does not change the allocation. See reserve held for outstanding reservations.
reuse Cost(all held pools) Admit no bound At most this many units of the session's own cached prefix are consumed (rounded down to blocks); cached is set to what was. The rest stays as a dead entry until evicted. Without reuse, the whole own prefix is consumed. Only with cache: a hold without the clause consumes nothing.
at admission NAME = expr, … Admit Names used in the header and body; see below.
block block Session The body.
cache Cost(all held pools) Session none Units kept cached at the end, at most what was computed. Read when the session releases. Its presence is what makes the hold consume the session's prefix at admission; cache (0) consumes and keeps nothing, no clause leaves the prefix where it is.
lease pool, expr Session none That pool's allocation outlives the scope: neither evictable nor a preemption victim until release of it, t clock units, or the session's end; cache applies then.

Admission bindings

The header (units, reserve, reuse) is read at admission, not when the session queues, and cannot draw. A header cannot use a value captured from live pool or stage state by an earlier set: that value may be stale by admission. Use at admission instead.

Bindings are substituted into units, reserve, reuse, cache and lease. Later bindings may use earlier ones. A binding read by the body is also assigned at its start; such a binding may read only attributes and constants. A body cannot read a binding of live state (cachedin(p), cached, now); use cached in the body to read the prefix actually reused.

Binding names are local to the holds that bind them, and each is the binding's own: no declaration anywhere in the program (a def, an observation, a gauge, a pool, a stage, …, a queue's own pools included) may share one, nor may a builtin attribute, a context variable, a let constant or an attribute the program assigns. Bindings cannot draw.

If the hold is preempted (preempt lifo, or preempt by) it re-enters the head of the queue (or, under requeue tail, the queue as a newcomer) and the statement executes again with computed set to the position it had reached.

grow

grow POOL (d);
Argument Type Description
POOL pool A pool the session holds.
d Cost(POOL) Units added to the innermost hold on POOL, rounded to blocks.

If it does not fit, the pool's preempt applies.

drop

drop POOL;

Discards the session's own cached prefix in POOL. end keeps the session's cached prefixes (the cache does not know a session has left), so a program that models dropping them writes this before end.

release

release POOL;

The innermost enclosing hold on POOL gives its allocation back now, caching per its cache, and holds POOL no longer. Outside any hold on POOL it ends a lease of POOL. A session that holds and leases nothing there releases nothing.

load

load POOL (n);
Argument Type Description
POOL pool
n Cost(POOL) Tokens of KV that arrived from outside the engine.

The innermost enclosing hold's computed position on POOL advances by n, within its allocation; a program that needs more grows first. Afterwards cache and cached count them.

run

run STAGE [prefill | decode] (work) [growing POOL];
run STAGE, STAGE [, STAGE]* (work);
Argument Type Description
STAGE stage Indexed when the stage is an array.
mode prefill | decode Required on a step stage, forbidden on any other.
work Cost(STAGE) Clock time at rate 1 (fifo, delay), or at φ(present)/present (ps), or tokens on a step stage. A run of zero work completes at once.
growing pool Only on a step stage. The hold on this pool grows block by block as the run advances, preempting if needed.

Blocks the session until the work is done.

With several stages the run is one job, a flow, that holds all of them from its start to its end; its work goes down at one rate everywhere, which the program's share sets from the stages' capacities. Every stage is ps(φ) with a constant φ above 0, a run names each stage array once, and the work is in the unit the capacities share (tokens at tokens per second, say). A stage array some run holds with another is shared for the whole run: every job on it, a single-stage run included, is a flow, and the report's utilisation is the capacity its flows carry.

stage egress[2] : ps(BwP);     // a prefiller's NIC, tokens per second
stage ingress[2] : ps(BwD);    // a decoder's NIC
share maxmin;
…
run egress[i], ingress[j] (cost(egress, ingress, tokens));

branch

branch (test) block [else block]
branch with (p) block [else block]
Form Argument Takes the first block
branch (test) expr, must be 0 or 1 when test is 1
branch with (p) expr, a probability with probability p; sugar for branch (~bernoulli(p))

Any other value of test (a fraction, a count, a negative, NaN) is a run-time error, and a constant strictly between 0 and 1 is refused at link time as a draw written as a test.

while

while (condition) { … }

Tests the condition before each pass. It must be exactly 0 or 1; any other value is a validation error when constant and a run-time error otherwise. Zero skips the body and continues after the loop. A draw is explicit, for example while (~bernoulli(p)) { … }. Every path through the body must let time pass or end the session, just as for loop. An inner conditional loop may execute zero times, so it cannot by itself establish progress for an outer loop.

loop

loop block

Repeats the block until an end. Every pass must let time pass; a body with a path that can repeat without doing so is a link error.

choose

choose NAME in n by (key, …);
Argument Type Description
NAME identifier Becomes a session attribute.
n Cost(POOL) Number of candidates, 0 … n-1 (rounded down; 0 or less leaves NAME at 0).
key, … expr, one or more Evaluated with NAME bound to each candidate. Several keys compare in order: the second decides among the first's ties, and so on.

NAME is set to the index with the smallest key (tuple), ties to the smallest index. Used with an array of stages: choose j in 2 by (work(prefill[j]));.

end

end;

The session leaves. It releases every hold but keeps its cached prefixes. In a server, end; refuses the request: the session ends and its turn is not answered, so the report's client section records nothing of it (what the client sees). It is refused there inside a hold, inside a fork and in a queue's entry.

Examples

A complete program:

fn main() {
  pool reqs { cap 1; }
  stage svc : fifo;
  workload {
    arrive batch(2);
  }
  server {
    set t0 = now;
    hold reqs (cost(reqs, 1)) { run svc (cost(svc, 2)); }
    observe response = now - t0;
  }
}

Save as model.sq, then run:

serq run model.sq --horizon 10

See also

Serving vocabulary, pools, stages.