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:
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¶
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¶
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¶
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¶
| 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¶
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¶
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¶
| 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¶
| 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¶
| 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¶
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¶
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¶
| 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¶
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: