@jmcombs/pi-steward 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/core/disconnected-source.ts +110 -0
- package/core/drift.ts +247 -0
- package/core/format.ts +317 -0
- package/core/host-metrics.ts +121 -0
- package/core/llama-config.ts +72 -0
- package/core/llama-connection.ts +215 -0
- package/core/llama-models.ts +261 -0
- package/core/llama-slots.ts +104 -0
- package/core/llama-source.ts +1523 -0
- package/core/log-parse.ts +440 -0
- package/core/model-color.ts +59 -0
- package/core/select.ts +2923 -0
- package/core/slot-activity.ts +658 -0
- package/core/source.ts +84 -0
- package/core/state.ts +609 -0
- package/core/status-widget.ts +222 -0
- package/core/temperature.ts +149 -0
- package/core/types.ts +431 -0
- package/index.ts +503 -0
- package/package.json +51 -0
- package/server/api.ts +216 -0
- package/server/assets.ts +198 -0
- package/server/config-wiring.ts +490 -0
- package/server/drift-probe.ts +150 -0
- package/server/host-collector.ts +272 -0
- package/server/index.ts +228 -0
- package/server/log-tailer.ts +432 -0
- package/server/service-control.ts +337 -0
- package/server/service-probe.ts +71 -0
- package/server/steward-config.ts +430 -0
- package/setup/init-prompt.ts +214 -0
- package/setup/steward-setup.d.mts +16 -0
- package/setup/steward-setup.mjs +1398 -0
- package/ui/components/console.ts +511 -0
- package/ui/components/gauges.ts +120 -0
- package/ui/components/metrics.ts +63 -0
- package/ui/components/models.ts +296 -0
- package/ui/components/service.ts +358 -0
- package/ui/components/slots.ts +114 -0
- package/ui/components/sparkline.ts +59 -0
- package/ui/components/toolbar.ts +211 -0
- package/ui/dom.ts +120 -0
- package/ui/favicon.svg +17 -0
- package/ui/index.html +34 -0
- package/ui/main.ts +678 -0
- package/ui/steward.css +2008 -0
package/core/types.ts
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types shared by every layer of Steward.
|
|
3
|
+
*
|
|
4
|
+
* The server produces these, the browser consumes them, and the data source
|
|
5
|
+
* (mock today, a live `llama-server` later) is the only thing that knows how
|
|
6
|
+
* they were obtained. Keep this module free of Node and DOM APIs: it is
|
|
7
|
+
* type-checked by both the Node project and the browser project.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { DriftState } from "./drift.js";
|
|
11
|
+
|
|
12
|
+
/** Log severities Steward renders. `DEBUG` is parsed but rarely emitted. */
|
|
13
|
+
export type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lifecycle of a model as shown on its card.
|
|
17
|
+
*
|
|
18
|
+
* `active` and `resident` both mean loaded — `active` when at least one of the
|
|
19
|
+
* model's parallel slots is generating, `resident` when it is idle. `loading`
|
|
20
|
+
* and `downloading` are the two ways a load is still in flight: spawning the
|
|
21
|
+
* child, or fetching the weights for the first time. A model llama.cpp reports
|
|
22
|
+
* as `sleeping` folds into `resident` — from the operator's seat it is loaded
|
|
23
|
+
* and ready, which is all the card says.
|
|
24
|
+
*/
|
|
25
|
+
export type ModelStatus = "active" | "resident" | "loading" | "downloading" | "unloaded";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether a parallel slot is currently generating.
|
|
29
|
+
*
|
|
30
|
+
* `unknown` is a real answer, not a placeholder: occupancy is established from
|
|
31
|
+
* the server's own log events, and a slot Steward has not yet seen an event for
|
|
32
|
+
* — a child that spawned a moment ago, a stream that was interrupted, a
|
|
33
|
+
* `release` that fell out of the buffer — is a slot whose state was never
|
|
34
|
+
* measured. Reporting it as `idle` would be a guess, and a slot silently stuck
|
|
35
|
+
* on `processing` because its `release` was missed is the exact failure this
|
|
36
|
+
* value exists to prevent.
|
|
37
|
+
*/
|
|
38
|
+
export type SlotState = "processing" | "idle" | "unknown";
|
|
39
|
+
|
|
40
|
+
/** Actions the operator can take on the service as a whole. */
|
|
41
|
+
export type ServiceAction = "start" | "stop" | "restart";
|
|
42
|
+
|
|
43
|
+
/** Actions the operator can take on a single model. */
|
|
44
|
+
export type ModelAction = "load" | "unload";
|
|
45
|
+
|
|
46
|
+
/** The `llama-server` process Steward is pointed at. */
|
|
47
|
+
export interface ServiceInfo {
|
|
48
|
+
running: boolean;
|
|
49
|
+
/** Epoch ms the current run began, or `null` when stopped. */
|
|
50
|
+
startedAt: number | null;
|
|
51
|
+
pid: number | null;
|
|
52
|
+
host: string;
|
|
53
|
+
port: number;
|
|
54
|
+
/** llama.cpp build tag, e.g. `b6122`. */
|
|
55
|
+
build: string;
|
|
56
|
+
/**
|
|
57
|
+
* The actions this machine can actually perform: one entry per action that
|
|
58
|
+
* `steward.json` declares a command for AND that the operator has consented
|
|
59
|
+
* to. Config, not a reading — a machine with no control configured reports an
|
|
60
|
+
* empty list, and the block shows a single setup affordance rather than
|
|
61
|
+
* buttons that could not work.
|
|
62
|
+
*/
|
|
63
|
+
controls: ServiceAction[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** One model known to the router, resident or not. */
|
|
67
|
+
export interface ModelInfo {
|
|
68
|
+
id: string;
|
|
69
|
+
/** Display name — the id with its repo prefix and quant suffix trimmed. */
|
|
70
|
+
short: string;
|
|
71
|
+
/**
|
|
72
|
+
* An embedding model — one whose `architecture.output_modalities` does not
|
|
73
|
+
* include `text`. Drives the card color (embedders get a reserved hue) and
|
|
74
|
+
* the detail line; it is not a guess at what the operator uses it for.
|
|
75
|
+
*/
|
|
76
|
+
embedding: boolean;
|
|
77
|
+
/** Quantisation label, e.g. `Q4_0`. Best-effort from the id when unloaded. */
|
|
78
|
+
quant: string;
|
|
79
|
+
/**
|
|
80
|
+
* Size on disk, or `null` when the model is not loaded: llama.cpp only
|
|
81
|
+
* reports a `meta` block (where the byte size lives) for resident models.
|
|
82
|
+
*/
|
|
83
|
+
sizeGB: number | null;
|
|
84
|
+
/**
|
|
85
|
+
* Per-slot context length. Loaded: `meta.n_ctx`. Unloaded preset: the pinned
|
|
86
|
+
* `--ctx-size ÷ --parallel`, so it matches the loaded per-slot figure. `null`
|
|
87
|
+
* when neither is known.
|
|
88
|
+
*/
|
|
89
|
+
ctx: number | null;
|
|
90
|
+
/**
|
|
91
|
+
* The model's native (training) context window, `meta.n_ctx_train`. Loaded
|
|
92
|
+
* only — llama.cpp ships no `meta` until then — so `null` when unloaded. It is
|
|
93
|
+
* the ceiling the per-slot {@link ctx} is carved out of.
|
|
94
|
+
*/
|
|
95
|
+
nativeCtx: number | null;
|
|
96
|
+
/**
|
|
97
|
+
* Layers offloaded to the GPU when `--n-gpu-layers` is pinned in the model's
|
|
98
|
+
* launch args, else `null` — the server's own default is not reported back.
|
|
99
|
+
*/
|
|
100
|
+
gpuLayers: number | null;
|
|
101
|
+
/** Trailing detail for the card's meta line, e.g. `embedding`; else `null`. */
|
|
102
|
+
detail: string | null;
|
|
103
|
+
/**
|
|
104
|
+
* Parallel decode slots this model has (`--parallel`). Per-model in routed
|
|
105
|
+
* mode: the router runs one `llama-server` per model, each with its own slot
|
|
106
|
+
* count. Once loaded the slot array is the authority; an unloaded preset model
|
|
107
|
+
* gets it from its pinned `--parallel` arg, and it is `null` when neither is
|
|
108
|
+
* known.
|
|
109
|
+
*/
|
|
110
|
+
parallel: number | null;
|
|
111
|
+
/**
|
|
112
|
+
* Flash-attention setting (`--flash-attn`). `auto` is the server default and
|
|
113
|
+
* resolves to on or off at load time; the launch args can pin it either way.
|
|
114
|
+
*/
|
|
115
|
+
flashAttn: "on" | "off" | "auto";
|
|
116
|
+
/** KV-cache types as `key/value`, e.g. `q8_0/q8_0` (`--cache-type-k/-v`). */
|
|
117
|
+
kvCache: string;
|
|
118
|
+
status: ModelStatus;
|
|
119
|
+
/** Generation rate while active, else `null`. */
|
|
120
|
+
tokensPerSecond: number | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One parallel slot of one loaded model. Slots are numbered per model, from 0,
|
|
125
|
+
* because in routed mode each model runs its own `llama-server` with its own
|
|
126
|
+
* slot pool — a flat, server-wide slot strip does not exist.
|
|
127
|
+
*/
|
|
128
|
+
export interface SlotInfo {
|
|
129
|
+
/** Per-model slot index, from 0. */
|
|
130
|
+
id: number;
|
|
131
|
+
/** The model this slot belongs to; always known (slots are fetched per model). */
|
|
132
|
+
modelId: string;
|
|
133
|
+
/**
|
|
134
|
+
* Tokens the slot's context currently holds, or `null` when nothing has
|
|
135
|
+
* reported it. The log states this at the end of a request (`release`'s
|
|
136
|
+
* `n_tokens`) and, for a long prefill, while it runs — so a slot that has not
|
|
137
|
+
* served a request since Steward started watching genuinely has no figure, and
|
|
138
|
+
* `0` would claim an empty context we never measured.
|
|
139
|
+
*/
|
|
140
|
+
promptTokens: number | null;
|
|
141
|
+
/**
|
|
142
|
+
* The slot's context length in tokens, or `null` when the model's launch
|
|
143
|
+
* configuration does not state it. Structural, not a reading: it comes from
|
|
144
|
+
* the model's `--ctx-size`/`meta.n_ctx`, which is fixed for the life of the
|
|
145
|
+
* child process.
|
|
146
|
+
*/
|
|
147
|
+
ctxTotal: number | null;
|
|
148
|
+
/**
|
|
149
|
+
* Tokens generated so far this turn, or `null` while unmeasured. llama.cpp
|
|
150
|
+
* only prints a running `n_decoded` for a generation long enough to cross its
|
|
151
|
+
* ~3 s reporting interval, so a short request has no count until it finishes.
|
|
152
|
+
*/
|
|
153
|
+
decoded: number | null;
|
|
154
|
+
state: SlotState;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* How the machine's memory is laid out, a static property of the hardware — not
|
|
159
|
+
* a per-sample reading (per M5: a per-line topology would thrash the gauge set).
|
|
160
|
+
* `discrete` machines have separate VRAM and RAM; `unified` machines (e.g. Apple
|
|
161
|
+
* Silicon) share one pool and expose no readable VRAM total, so they show a
|
|
162
|
+
* single Unified RAM gauge instead of the VRAM+RAM pair.
|
|
163
|
+
*/
|
|
164
|
+
export type MemoryTopology = "unified" | "discrete";
|
|
165
|
+
|
|
166
|
+
/** Host sensors. Temperatures are `null` where the platform cannot supply them. */
|
|
167
|
+
export interface HostMetrics {
|
|
168
|
+
vramUsedGB: number;
|
|
169
|
+
vramTotalGB: number;
|
|
170
|
+
ramUsedGB: number;
|
|
171
|
+
ramTotalGB: number;
|
|
172
|
+
/** GPU utilisation, 0–1. */
|
|
173
|
+
gpuUtil: number;
|
|
174
|
+
/** CPU utilisation, 0–1. */
|
|
175
|
+
cpuUtil: number;
|
|
176
|
+
gpuTempC: number | null;
|
|
177
|
+
cpuTempC: number | null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A read-only key/value row in the rail's config block. */
|
|
181
|
+
export interface ConfigEntry {
|
|
182
|
+
key: string;
|
|
183
|
+
value: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* What kind of record a line is, decided by the producer while it parses.
|
|
188
|
+
*
|
|
189
|
+
* `proxy` and `args` exist because the console suppresses or folds them by
|
|
190
|
+
* default and has to be able to say honestly what it is not showing; `rate`
|
|
191
|
+
* exists so a future suppression needs no parser change. Nothing is ever
|
|
192
|
+
* dropped for its kind — classification is tagging, so every filter the console
|
|
193
|
+
* builds on it stays reversible. Absent means `event`: an ordinary line, shown
|
|
194
|
+
* as-is.
|
|
195
|
+
*
|
|
196
|
+
* - `proxy` — the router's `proxy_reques` lines. 86.9% of a real log, and most
|
|
197
|
+
* of them are Steward's own `/slots` + `/metrics` polls watching itself.
|
|
198
|
+
* - `args` — the contiguous continuation run under `spawning … with args:`.
|
|
199
|
+
* Individually meaningless (`--ctx-size`, then `131072`), collectively the
|
|
200
|
+
* exact launch command line.
|
|
201
|
+
* - `rate` — the ~3 s in-flight `n_decoded … tg = N t/s` generation line.
|
|
202
|
+
*/
|
|
203
|
+
export type LogKind = "event" | "proxy" | "args" | "rate";
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Which process wrote the line: `child` when the router prefixed it with
|
|
207
|
+
* `[port]`, `router` otherwise. It is the only way to tell a router-wide line
|
|
208
|
+
* (no model involved — render `router`) from a child line whose port has not
|
|
209
|
+
* been mapped yet (model genuinely unknown — render `—`). Absent means the
|
|
210
|
+
* source predates the field, and a null `modelId` is treated as router-wide.
|
|
211
|
+
*/
|
|
212
|
+
export type LogOrigin = "router" | "child";
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Operator-facing grouping of records — the console's `kind` chips.
|
|
216
|
+
*
|
|
217
|
+
* Deliberately four values, not the research's six: proxied requests already
|
|
218
|
+
* have a purpose-built toggle and the launch-args block already has a fold, so
|
|
219
|
+
* neither earns a chip. Absent means `other`.
|
|
220
|
+
*
|
|
221
|
+
* `other` is the drift alarm and needs no alarm built: a llama-server message
|
|
222
|
+
* shape Steward has never seen lands there and stays visible, and a count that
|
|
223
|
+
* starts climbing is the signal.
|
|
224
|
+
*/
|
|
225
|
+
export type LogFamily = "requests" | "models" | "startup" | "other";
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The `id %2d | task %d | ` frame that every `SLT_*` macro emits.
|
|
229
|
+
*
|
|
230
|
+
* One shared literal in llama.cpp's `server-common.h`, byte-identical from
|
|
231
|
+
* b4500 (Jan 2025) through b10090 (Jul 2026) across two file moves — the
|
|
232
|
+
* strongest structural guarantee the log offers, and the only one worth
|
|
233
|
+
* relocating out of the message.
|
|
234
|
+
*/
|
|
235
|
+
export interface LogFrame {
|
|
236
|
+
/** `(slot).id`, 0 .. n_parallel-1. Always 0 on a `--parallel 1` server. */
|
|
237
|
+
slot: number;
|
|
238
|
+
/** `(slot).task->id`, or -1 where no task is attached yet (`get_available_slot`). */
|
|
239
|
+
task: number;
|
|
240
|
+
/**
|
|
241
|
+
* The frame exactly as the file wrote it, INCLUDING the `slot print_timing: `
|
|
242
|
+
* head it sits behind, so `raw + message` re-forms the line byte for byte.
|
|
243
|
+
* That is the guarantee that makes the relocation a relocation rather than a
|
|
244
|
+
* rewrite: Copy and Download reproduce the file, not a reassembly of it.
|
|
245
|
+
*/
|
|
246
|
+
raw: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** One line of the server log. */
|
|
250
|
+
export interface LogLine {
|
|
251
|
+
/** Monotonic per-source sequence number; also the render key. */
|
|
252
|
+
seq: number;
|
|
253
|
+
/**
|
|
254
|
+
* Which log SOURCE produced this line — bumped whenever the server starts
|
|
255
|
+
* reading a different one, and absent from a source that can never change.
|
|
256
|
+
*
|
|
257
|
+
* {@link seq} is monotonic per source and NOTHING else: two sources number
|
|
258
|
+
* their lines independently, and the file tailer reads a backlog window the
|
|
259
|
+
* moment it opens, so a replacement's counter is already in the thousands
|
|
260
|
+
* before it delivers anything. Comparing numbers across that boundary is
|
|
261
|
+
* meaningless in both directions — a higher one reads as "newer" and gets
|
|
262
|
+
* appended, so a console would silently concatenate two different logs under
|
|
263
|
+
* one buffer; a lower one reads as a restart. This field is what makes the
|
|
264
|
+
* boundary legible: a batch that carries a different generation than the
|
|
265
|
+
* buffer replaces it, whatever the sequence numbers say.
|
|
266
|
+
*/
|
|
267
|
+
gen?: number;
|
|
268
|
+
/** Epoch ms. Formatted as `HH:MM:SS.mmm` at render time. */
|
|
269
|
+
ts: number;
|
|
270
|
+
level: LogLevel;
|
|
271
|
+
/** Model the line was attributed to, or `null` when it is not slot traffic. */
|
|
272
|
+
modelId: string | null;
|
|
273
|
+
/**
|
|
274
|
+
* Everything after {@link LogFrame.raw} when the line was framed, and the
|
|
275
|
+
* whole text after the level letter when it was not. Always a verbatim SUFFIX
|
|
276
|
+
* of the line as the file wrote it — nothing is paraphrased, reordered or
|
|
277
|
+
* dropped.
|
|
278
|
+
*/
|
|
279
|
+
message: string;
|
|
280
|
+
/** What class of record this is; absent means {@link LogKind} `event`. */
|
|
281
|
+
kind?: LogKind;
|
|
282
|
+
/** Which process wrote it; absent means the source does not report it. */
|
|
283
|
+
origin?: LogOrigin;
|
|
284
|
+
/**
|
|
285
|
+
* The `[port]` prefix's port. HALF THE TRACE KEY: task ids are a per-process
|
|
286
|
+
* counter from 0, so task `0` appears under 8 different ports in a single
|
|
287
|
+
* measured corpus. A trace keyed on the id alone would be a real,
|
|
288
|
+
* user-visible bug — it would show one request's lines mixed with another
|
|
289
|
+
* model's.
|
|
290
|
+
*/
|
|
291
|
+
port?: number;
|
|
292
|
+
/**
|
|
293
|
+
* The pipe frame, when the line carried one. When absent, {@link message} is
|
|
294
|
+
* the whole text after the level letter, exactly as before this existed, and
|
|
295
|
+
* the task cell is empty — which is the whole degradation path.
|
|
296
|
+
*/
|
|
297
|
+
frame?: LogFrame;
|
|
298
|
+
/** Which chip the line answers to; absent means {@link LogFamily} `other`. */
|
|
299
|
+
family?: LogFamily;
|
|
300
|
+
/**
|
|
301
|
+
* `truncated = 1` on a release line: this request's reply was written from a
|
|
302
|
+
* context a shift had already cut the front off. Absent means the line did
|
|
303
|
+
* not say so — never that it said no.
|
|
304
|
+
*/
|
|
305
|
+
contextLost?: boolean;
|
|
306
|
+
/** `sim_best` — the fraction of the prompt already in KV cache, 0–1. */
|
|
307
|
+
cacheHit?: number;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Whether a log source is connected, and — when it is not — which way it
|
|
312
|
+
* failed. `unavailable` means no source was ever discovered (nothing to watch);
|
|
313
|
+
* `missing` means a path IS being watched and the file is not there right now,
|
|
314
|
+
* which on macOS is routinely `com.apple.tmp_cleaner` unlinking a `/tmp` log
|
|
315
|
+
* that went three days untouched. The second is self-healing and must not be
|
|
316
|
+
* reported as the first.
|
|
317
|
+
*/
|
|
318
|
+
export type LogSourceState = "ok" | "unavailable" | "missing";
|
|
319
|
+
|
|
320
|
+
/** The health of the log source itself, alongside the line stream. */
|
|
321
|
+
export interface LogStreamStatus {
|
|
322
|
+
source: LogSourceState;
|
|
323
|
+
/** The path being watched, so the console can name the file it is missing. */
|
|
324
|
+
path: string | null;
|
|
325
|
+
/** A readable reason when the source is not `ok`, else `null`. */
|
|
326
|
+
detail: string | null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Everything the dashboard needs for one repaint, other than the log stream.
|
|
331
|
+
* The browser polls this; nothing in it is incremental.
|
|
332
|
+
*/
|
|
333
|
+
export interface Snapshot {
|
|
334
|
+
/** Epoch ms the snapshot was taken, so the client need not trust its clock. */
|
|
335
|
+
now: number;
|
|
336
|
+
service: ServiceInfo;
|
|
337
|
+
models: ModelInfo[];
|
|
338
|
+
slots: SlotInfo[];
|
|
339
|
+
metrics: HostMetrics;
|
|
340
|
+
/**
|
|
341
|
+
* Which gauge SET the HOST block lays out (VRAM+RAM vs a single Unified
|
|
342
|
+
* Memory). Static machine config, not a reading — it lives here at the top
|
|
343
|
+
* level, never inside {@link HostMetrics}. In a later phase this is read from
|
|
344
|
+
* the `steward.json` config artifact; today the mock reports `discrete`.
|
|
345
|
+
*/
|
|
346
|
+
memoryTopology: MemoryTopology;
|
|
347
|
+
/**
|
|
348
|
+
* Whether this machine still matches what `steward.json` says about it: the
|
|
349
|
+
* live launch argv against the recorded one, and any command declared but not
|
|
350
|
+
* approved. Always present, because "we did not check" ({@link
|
|
351
|
+
* import("./drift.js").LaunchDrift} `unknown`) is a distinct answer from "we
|
|
352
|
+
* checked and it matches" — and only the first of those is honest when the
|
|
353
|
+
* check could not run.
|
|
354
|
+
*/
|
|
355
|
+
drift: DriftState;
|
|
356
|
+
/**
|
|
357
|
+
* Tokens generated per second of wall clock over {@link
|
|
358
|
+
* throughputWindowSeconds}, across all slots — or `null` when no window has
|
|
359
|
+
* been measured.
|
|
360
|
+
*
|
|
361
|
+
* This is throughput in the ordinary sense: what the server produced, divided
|
|
362
|
+
* by the time it had to produce it. It is NOT the speed of whatever request is
|
|
363
|
+
* running at this instant, and on a real box the two are nowhere near each
|
|
364
|
+
* other, because most of the wall clock has no request in it at all. A model's
|
|
365
|
+
* own live speed is still reported per model, on the model that is generating
|
|
366
|
+
* — see {@link ModelInfo.tokensPerSecond} — because "how fast is this request
|
|
367
|
+
* going" is a real question; it is just not one a box-wide tile can answer for
|
|
368
|
+
* a box that is idle nine seconds in ten.
|
|
369
|
+
*
|
|
370
|
+
* Reporting the instant instead was tried and is what this replaced. llama.cpp
|
|
371
|
+
* prints a live rate only once a generation passes 100 tokens AND ~3 s, and on
|
|
372
|
+
* a measured 16,517-request corpus 99.4% of requests never reached that, so
|
|
373
|
+
* the only rate they ever produced was the one on the `eval time` line their
|
|
374
|
+
* `release` follows 17 microseconds later. Sampled every 1.6 s, that tile read
|
|
375
|
+
* `0` while idle and `—` while busy and never once read the truth.
|
|
376
|
+
*
|
|
377
|
+
* `0` is a measurement — the window elapsed and nothing was generated in it.
|
|
378
|
+
* `null` is the absence of one: no window has closed yet, or the event stream
|
|
379
|
+
* broke and the span that window would cover cannot be vouched for.
|
|
380
|
+
*/
|
|
381
|
+
throughputTps: number | null;
|
|
382
|
+
/**
|
|
383
|
+
* The wall clock {@link throughputTps} and {@link throughputHistory} cover, in
|
|
384
|
+
* seconds — the span the strip is showing, which grows to ~2 minutes and then
|
|
385
|
+
* rolls.
|
|
386
|
+
*
|
|
387
|
+
* `null` when the figure is not a window measurement at all: a Steward with no
|
|
388
|
+
* log source cannot count tokens and samples llama.cpp's own rate gauge
|
|
389
|
+
* instead, and the mock invents a series outright. The tile reads this to know
|
|
390
|
+
* which claim it is allowed to print beside the number.
|
|
391
|
+
*/
|
|
392
|
+
throughputWindowSeconds: number | null;
|
|
393
|
+
/**
|
|
394
|
+
* Requests being processed across all slots right now, or `null` when any
|
|
395
|
+
* slot's occupancy is {@link SlotState} `unknown` and the true count can only
|
|
396
|
+
* be bounded below. llama.cpp exposes no request-rate metric, so the requests
|
|
397
|
+
* tile reports this live count (and {@link requestsQueued}) rather than a
|
|
398
|
+
* per-minute rate it cannot measure.
|
|
399
|
+
*/
|
|
400
|
+
requestsInFlight: number | null;
|
|
401
|
+
/**
|
|
402
|
+
* Rolling tok/s samples, oldest first. 42 samples ≈ 2 minutes.
|
|
403
|
+
*
|
|
404
|
+
* Each sample is its own span's tokens over its own span's wall clock, so a
|
|
405
|
+
* sample of `0` says the server generated nothing in those ~3 seconds — a
|
|
406
|
+
* measurement, and the shape of intermittent traffic is exactly what makes the
|
|
407
|
+
* strip worth drawing. An empty array means no span can be vouched for yet,
|
|
408
|
+
* which is what the strip shows after a restart or a break in the log.
|
|
409
|
+
*
|
|
410
|
+
* On a Steward with no log source the samples are gauge readings instead, and
|
|
411
|
+
* only measured ones are appended: a tick that could not be read contributes
|
|
412
|
+
* no sample rather than a fabricated `0`. {@link throughputWindowSeconds} is
|
|
413
|
+
* `null` there, and the axis is the only thing claiming a span.
|
|
414
|
+
*/
|
|
415
|
+
throughputHistory: number[];
|
|
416
|
+
/**
|
|
417
|
+
* Requests accepted but waiting for a free slot, or `null` when nothing can
|
|
418
|
+
* report it. The server's log says when a slot is taken and released but never
|
|
419
|
+
* mentions the queue behind it, so a Steward reading occupancy from the log —
|
|
420
|
+
* which is every Steward with a log source — has no honest figure here. It is
|
|
421
|
+
* `null` there, and a number only when the `/metrics` scrape supplied one.
|
|
422
|
+
*/
|
|
423
|
+
requestsQueued: number | null;
|
|
424
|
+
config: ConfigEntry[];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Number of samples in {@link Snapshot.throughputHistory}. */
|
|
428
|
+
export const THROUGHPUT_HISTORY_SIZE = 42;
|
|
429
|
+
|
|
430
|
+
/** Seconds between throughput samples, matching the metrics poll. */
|
|
431
|
+
export const THROUGHPUT_SAMPLE_SECONDS = 3;
|