@camstack/system 1.2.154 → 1.2.156
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/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +101 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +24 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +348 -39
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +348 -39
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +37 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-BPlfW-CG.js → dist-CDgIzo82.js} +169 -12
- package/dist/{dist-D_NyUV6Y.mjs → dist-LvKus-mT.mjs} +169 -12
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -2
- package/dist/index.mjs +3 -3
- package/dist/kernel/heap-watch.d.ts +8 -0
- package/dist/kernel/socket-plane-report.d.ts +99 -0
- package/dist/kernel/transport/frame-codec.d.ts +25 -0
- package/dist/kernel/transport/index.d.ts +2 -0
- package/dist/kernel/transport/local-child-registry.d.ts +13 -0
- package/dist/kernel/transport/local-transport.d.ts +11 -0
- package/dist/kernel/transport/socket-channel.d.ts +23 -0
- package/dist/kernel/transport/socket-traffic.d.ts +118 -0
- package/dist/{manifest-python-deps-BILJm2mI.mjs → manifest-python-deps-CD14Pv0-.mjs} +360 -7
- package/dist/{manifest-python-deps-COeSr7el.js → manifest-python-deps-DVODn-qc.js} +431 -6
- package/dist/{retired-settings-keys-DBY6ebwV.js → retired-settings-keys-BfAzWvPC.js} +1 -1
- package/dist/{retired-settings-keys-Bq1Tb84I.mjs → retired-settings-keys-CV4QVdXj.mjs} +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./dist-
|
|
1
|
+
import "./dist-LvKus-mT.mjs";
|
|
2
2
|
import * as crypto$1 from "node:crypto";
|
|
3
3
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
4
4
|
import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
|
|
@@ -150,6 +150,282 @@ function formatEventPlane(window) {
|
|
|
150
150
|
return ` fanoutMode=uds:${window.udsMode}/cross-node:${window.crossNodeMode} children=${window.childrenConnected} childrenUndeclared=${window.childrenUndeclared} fanoutSent=${window.fanoutSent} fanoutSuppressed=${window.fanoutSuppressed} crossNodeDelivered=${window.crossNodeDelivered} crossNodeSuppressed=${window.crossNodeSuppressed}` + (top.length === 0 ? "" : ` fanoutTop=${top}`);
|
|
151
151
|
}
|
|
152
152
|
//#endregion
|
|
153
|
+
//#region src/kernel/transport/socket-traffic.ts
|
|
154
|
+
function createSocketDirectionCounters() {
|
|
155
|
+
return {
|
|
156
|
+
reqMessages: 0,
|
|
157
|
+
reqBytes: 0,
|
|
158
|
+
resMessages: 0,
|
|
159
|
+
resBytes: 0,
|
|
160
|
+
evtMessages: 0,
|
|
161
|
+
evtBytes: 0
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The hot-path write. Two additions and at most two string comparisons against
|
|
166
|
+
* interned literals — no allocation, no branch on anything the caller has to
|
|
167
|
+
* compute, and nothing that can throw.
|
|
168
|
+
*
|
|
169
|
+
* Measured on Node 24.17, 20 M iterations, three runs: **2.37–2.41 ns/call**
|
|
170
|
+
* against an empty loop. Its neighbours on the same path, measured in the same
|
|
171
|
+
* run: `encodeFrame` on a 0.33 KB body 1948–1975 ns, `FrameDecoder.push`
|
|
172
|
+
* 1156–1166 ns, `process.memoryUsage()` 920–945 ns. The counter is **0.12 % of
|
|
173
|
+
* `encodeFrame`** — and `encodeFrame` is followed by a syscall.
|
|
174
|
+
*
|
|
175
|
+
* Together with the decoder's per-frame size write (0.06–0.13 ns, see
|
|
176
|
+
* `frame-codec.ts`) the whole instrument adds **~2.5 ns per frame**: at 44 000
|
|
177
|
+
* frames/s that is 107–111 µs/s, i.e. **0.011 % of one core**. An instrument
|
|
178
|
+
* that changes what it measures is not an instrument, and this one cannot.
|
|
179
|
+
*/
|
|
180
|
+
function recordSocketFrame(counters, kind, bytes) {
|
|
181
|
+
if (kind === "evt") {
|
|
182
|
+
counters.evtMessages += 1;
|
|
183
|
+
counters.evtBytes += bytes;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (kind === "req") {
|
|
187
|
+
counters.reqMessages += 1;
|
|
188
|
+
counters.reqBytes += bytes;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
counters.resMessages += 1;
|
|
192
|
+
counters.resBytes += bytes;
|
|
193
|
+
}
|
|
194
|
+
/** Copy out. Called once per heartbeat line, never on the frame path. */
|
|
195
|
+
function sampleSocketDirection(counters) {
|
|
196
|
+
return {
|
|
197
|
+
reqMessages: counters.reqMessages,
|
|
198
|
+
reqBytes: counters.reqBytes,
|
|
199
|
+
resMessages: counters.resMessages,
|
|
200
|
+
resBytes: counters.resBytes,
|
|
201
|
+
evtMessages: counters.evtMessages,
|
|
202
|
+
evtBytes: counters.evtBytes
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** A direction that has seen nothing. */
|
|
206
|
+
var EMPTY_SOCKET_DIRECTION = {
|
|
207
|
+
reqMessages: 0,
|
|
208
|
+
reqBytes: 0,
|
|
209
|
+
resMessages: 0,
|
|
210
|
+
resBytes: 0,
|
|
211
|
+
evtMessages: 0,
|
|
212
|
+
evtBytes: 0
|
|
213
|
+
};
|
|
214
|
+
/** Total messages across the three kinds. */
|
|
215
|
+
function socketDirectionMessages(sample) {
|
|
216
|
+
return sample.reqMessages + sample.resMessages + sample.evtMessages;
|
|
217
|
+
}
|
|
218
|
+
/** Total on-the-wire bytes across the three kinds. */
|
|
219
|
+
function socketDirectionBytes(sample) {
|
|
220
|
+
return sample.reqBytes + sample.resBytes + sample.evtBytes;
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/kernel/socket-plane-report.ts
|
|
224
|
+
/**
|
|
225
|
+
* socket-plane-report — which PEER, exchanging what KIND of message, accounts
|
|
226
|
+
* for hub-main's ~44 000 socket syscalls per second.
|
|
227
|
+
*
|
|
228
|
+
* ## Why this exists, and why it is not the fan-out report
|
|
229
|
+
*
|
|
230
|
+
* `event-plane-report.ts` surfaced the counters the cluster had always kept and
|
|
231
|
+
* never read. It answered its own question and, in answering it, sharpened this
|
|
232
|
+
* one: measured 2026-08-29, **the event plane is 4 % of hub-main's socket
|
|
233
|
+
* traffic**. The remaining 96 % — ~22 000 reads/s and ~22 500 writes/s at a
|
|
234
|
+
* 0.33 KB mean, on the process that mediates every cross-runner call — had no
|
|
235
|
+
* instrument at all, and no instrument was obtainable:
|
|
236
|
+
*
|
|
237
|
+
* - `/proc/<pid>/io` gives ONE `rchar`/`wchar` pair for the whole process.
|
|
238
|
+
* There is no per-socket breakdown anywhere in `/proc`.
|
|
239
|
+
* - The strongest attribution available was a correlation — 78–82 % of
|
|
240
|
+
* hub-main's outbound bytes moving with `hub/pipeline-analytics`, in both
|
|
241
|
+
* measured windows — computed from that runner's own `rchar`, which also
|
|
242
|
+
* counts page-cache file reads on a runner that owns a vector store. A
|
|
243
|
+
* correlation this stable is worth one counter to settle; it is not itself
|
|
244
|
+
* an answer.
|
|
245
|
+
* - Three separate inferences about this traffic were closed early on
|
|
246
|
+
* 2026-08-29 and were wrong. The point of this module is that the next pass
|
|
247
|
+
* is evidence.
|
|
248
|
+
*
|
|
249
|
+
* ## What it reports, and why each field is load-bearing
|
|
250
|
+
*
|
|
251
|
+
* Per kind, per direction, cluster-wide, plus the top peers:
|
|
252
|
+
*
|
|
253
|
+
* - **`evt`** scales with the number of RUNNERS, not with anything an operator
|
|
254
|
+
* asked for — an idle `hub/ml` still reads 82 messages/s. If `evt` dominates,
|
|
255
|
+
* the cost is the plane's own shape and the fix is the fan-out gate.
|
|
256
|
+
* - **`req`** scales with work. If `req` dominates, some caller is chatty and
|
|
257
|
+
* the fix is that caller.
|
|
258
|
+
* - **`res`** is where a large payload can hide (a GOP crosses hub-main as a
|
|
259
|
+
* msgpack `Uint8Array` inside a `res`). **This is the falsifier**: the
|
|
260
|
+
* "message-RATE, not media" conclusion of
|
|
261
|
+
* `docs/design/2026-08-29-hub-main-memory-what-crosses-it.md` §4 rests on a
|
|
262
|
+
* 0.33/0.40 KB mean, and `res` bytes dominating at a large mean breaks it and
|
|
263
|
+
* re-opens the D9/D18 question. Bytes are reported beside messages on every
|
|
264
|
+
* kind precisely so the mean can be read off the line.
|
|
265
|
+
*
|
|
266
|
+
* ## Deltas, never totals
|
|
267
|
+
*
|
|
268
|
+
* The channel counters are cumulative for the life of the channel. A cumulative
|
|
269
|
+
* number on a heartbeat answers "how much since boot", which nobody asked; the
|
|
270
|
+
* question is a rate. So the meter is read ONCE PER LINE and reports the window
|
|
271
|
+
* since the previous read — the same discipline the loop-delay histogram and
|
|
272
|
+
* the fan-out counters already follow, for the same reason.
|
|
273
|
+
*
|
|
274
|
+
* A peer whose runner RESPAWNED reconnects under the same id with a fresh
|
|
275
|
+
* channel and counters that went backwards. Clamping at 0 reports that window
|
|
276
|
+
* as quiet rather than negative: its traffic is genuinely unknown, and a
|
|
277
|
+
* negative number on a rate reads as a broken meter.
|
|
278
|
+
*
|
|
279
|
+
* ## What this is NOT
|
|
280
|
+
*
|
|
281
|
+
* It is not a bound, a budget or a throttle. Nothing here changes a default,
|
|
282
|
+
* drops a frame or tunes a socket. It counts what already crosses, so that the
|
|
283
|
+
* thing that eventually gets fixed is chosen from evidence.
|
|
284
|
+
*/
|
|
285
|
+
/**
|
|
286
|
+
* Build a reader over the child registry.
|
|
287
|
+
*
|
|
288
|
+
* Returns `undefined` until the registry exists — the heartbeat starts as the
|
|
289
|
+
* FIRST statement of `bootstrap()` and `LocalChildRegistry` is built hundreds
|
|
290
|
+
* of lines later. Printing zeros in that window would report a silent plane
|
|
291
|
+
* where there is no plane at all.
|
|
292
|
+
*
|
|
293
|
+
* Wrapped: a registry mid-teardown may throw, and a diagnostic that can crash
|
|
294
|
+
* the process it watches is worse than none.
|
|
295
|
+
*/
|
|
296
|
+
function createSocketPlaneReader(sources) {
|
|
297
|
+
return () => {
|
|
298
|
+
try {
|
|
299
|
+
const registry = sources.registry();
|
|
300
|
+
if (registry === null) return void 0;
|
|
301
|
+
const listed = registry.listChildren();
|
|
302
|
+
const peers = [];
|
|
303
|
+
for (const child of listed) {
|
|
304
|
+
const traffic = registry.getChildSocketTraffic(child.childId);
|
|
305
|
+
if (traffic === null) continue;
|
|
306
|
+
peers.push({
|
|
307
|
+
peerId: child.childId,
|
|
308
|
+
tx: traffic.tx,
|
|
309
|
+
rx: traffic.rx
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
peers,
|
|
314
|
+
peersConnected: listed.length
|
|
315
|
+
};
|
|
316
|
+
} catch {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* How many peers get named on the line.
|
|
323
|
+
*
|
|
324
|
+
* Five, matching `EVENT_PLANE_TOP_N` for the same reason: the measured
|
|
325
|
+
* distribution is not flat — one peer accounted for 78–82 % of outbound bytes
|
|
326
|
+
* and the runner-up for ~6 % — so five names the dominant peer, its challenger
|
|
327
|
+
* and enough of the tail to see the shape change. Naming all ~38 would put a
|
|
328
|
+
* kilobyte-long line in the hub's shared log every minute, which is how a log
|
|
329
|
+
* stops being read.
|
|
330
|
+
*/
|
|
331
|
+
var SOCKET_PLANE_TOP_N = 5;
|
|
332
|
+
/** A process that has never read the plane. */
|
|
333
|
+
var EMPTY_SOCKET_PLANE_BASELINE = { peers: /* @__PURE__ */ new Map() };
|
|
334
|
+
function diffDirection(current, before) {
|
|
335
|
+
const previous = before ?? EMPTY_SOCKET_DIRECTION;
|
|
336
|
+
return {
|
|
337
|
+
reqMessages: Math.max(0, current.reqMessages - previous.reqMessages),
|
|
338
|
+
reqBytes: Math.max(0, current.reqBytes - previous.reqBytes),
|
|
339
|
+
resMessages: Math.max(0, current.resMessages - previous.resMessages),
|
|
340
|
+
resBytes: Math.max(0, current.resBytes - previous.resBytes),
|
|
341
|
+
evtMessages: Math.max(0, current.evtMessages - previous.evtMessages),
|
|
342
|
+
evtBytes: Math.max(0, current.evtBytes - previous.evtBytes)
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function addDirection(into, add) {
|
|
346
|
+
return {
|
|
347
|
+
reqMessages: into.reqMessages + add.reqMessages,
|
|
348
|
+
reqBytes: into.reqBytes + add.reqBytes,
|
|
349
|
+
resMessages: into.resMessages + add.resMessages,
|
|
350
|
+
resBytes: into.resBytes + add.resBytes,
|
|
351
|
+
evtMessages: into.evtMessages + add.evtMessages,
|
|
352
|
+
evtBytes: into.evtBytes + add.evtBytes
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/** Total messages a peer moved in the window, both directions. */
|
|
356
|
+
function peerMessages(delta) {
|
|
357
|
+
return socketDirectionMessages(delta.tx) + socketDirectionMessages(delta.rx);
|
|
358
|
+
}
|
|
359
|
+
/** Subtract the previous cumulative reading from the current one. */
|
|
360
|
+
function diffSocketPlane(baseline, current, topN = 5) {
|
|
361
|
+
const deltas = [];
|
|
362
|
+
let tx = EMPTY_SOCKET_DIRECTION;
|
|
363
|
+
let rx = EMPTY_SOCKET_DIRECTION;
|
|
364
|
+
for (const sample of current.peers) {
|
|
365
|
+
const before = baseline.peers.get(sample.peerId);
|
|
366
|
+
const delta = {
|
|
367
|
+
peerId: sample.peerId,
|
|
368
|
+
tx: diffDirection(sample.tx, before?.tx),
|
|
369
|
+
rx: diffDirection(sample.rx, before?.rx)
|
|
370
|
+
};
|
|
371
|
+
tx = addDirection(tx, delta.tx);
|
|
372
|
+
rx = addDirection(rx, delta.rx);
|
|
373
|
+
if (peerMessages(delta) > 0) deltas.push(delta);
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
peersConnected: current.peersConnected,
|
|
377
|
+
peersMeasured: current.peers.length,
|
|
378
|
+
tx,
|
|
379
|
+
rx,
|
|
380
|
+
top: deltas.toSorted((a, b) => peerMessages(b) - peerMessages(a) || a.peerId.localeCompare(b.peerId)).slice(0, topN)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function createSocketPlaneMeter(reader, topN = 5) {
|
|
384
|
+
let baseline = EMPTY_SOCKET_PLANE_BASELINE;
|
|
385
|
+
return { read: () => {
|
|
386
|
+
const current = reader();
|
|
387
|
+
if (current === void 0) return void 0;
|
|
388
|
+
const window = diffSocketPlane(baseline, current, topN);
|
|
389
|
+
baseline = { peers: new Map(current.peers.map((p) => [p.peerId, p])) };
|
|
390
|
+
return window;
|
|
391
|
+
} };
|
|
392
|
+
}
|
|
393
|
+
/** Bytes → whole kB. The plane moves 180–330 MB/min; raw bytes would put
|
|
394
|
+
* nine-digit numbers on a line that has to stay readable. */
|
|
395
|
+
function kb(bytes) {
|
|
396
|
+
return Math.round(bytes / 1024);
|
|
397
|
+
}
|
|
398
|
+
/** `req:<msgs>/<kB>,res:<msgs>/<kB>,evt:<msgs>/<kB>` — every kind, always,
|
|
399
|
+
* including at zero: a kind that went quiet is a reading, not a blank. */
|
|
400
|
+
function formatDirection(sample) {
|
|
401
|
+
return `req:${sample.reqMessages}/${kb(sample.reqBytes)}kB,res:${sample.resMessages}/${kb(sample.resBytes)}kB,evt:${sample.evtMessages}/${kb(sample.evtBytes)}kB`;
|
|
402
|
+
}
|
|
403
|
+
/** `<peer>:tx=<req>/<res>/<evt>:rx=<req>/<res>/<evt>:kB=<tx>/<rx>` — the whole
|
|
404
|
+
* peer×kind cross for one peer, which is the question in one token. */
|
|
405
|
+
function formatPeer(delta) {
|
|
406
|
+
return `${delta.peerId}:tx=${delta.tx.reqMessages}/${delta.tx.resMessages}/${delta.tx.evtMessages}:rx=${delta.rx.reqMessages}/${delta.rx.resMessages}/${delta.rx.evtMessages}:kB=${kb(socketDirectionBytes(delta.tx))}/${kb(socketDirectionBytes(delta.rx))}`;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The suffix appended to a `[mem]` line — leading space included, empty when
|
|
410
|
+
* this process has no local socket plane.
|
|
411
|
+
*
|
|
412
|
+
* Only the processes that OWN a child registry have one (hub-main, and an
|
|
413
|
+
* agent's main), so these fields never appear on the ~38 runner lines that
|
|
414
|
+
* share the same log stream. On the ones that do, every field is printed on
|
|
415
|
+
* every line including at zero — `socketPeers` because measured-below-connected
|
|
416
|
+
* is the only way to see an unmeasured channel, and the per-kind splits because
|
|
417
|
+
* "the plane went quiet for a minute" is a reading.
|
|
418
|
+
*
|
|
419
|
+
* `socketTop` is the one conditional field — a ranked list of nothing is noise —
|
|
420
|
+
* and it is a single comma-separated value so the field COUNT does not change
|
|
421
|
+
* from line to line.
|
|
422
|
+
*/
|
|
423
|
+
function formatSocketPlane(window) {
|
|
424
|
+
if (window === void 0) return "";
|
|
425
|
+
const top = window.top.map(formatPeer).join(",");
|
|
426
|
+
return ` socketPeers=${window.peersMeasured}/${window.peersConnected} socketTx=${formatDirection(window.tx)} socketRx=${formatDirection(window.rx)}` + (top.length === 0 ? "" : ` socketTop=${top}`);
|
|
427
|
+
}
|
|
428
|
+
//#endregion
|
|
153
429
|
//#region src/kernel/heap-spaces.ts
|
|
154
430
|
/**
|
|
155
431
|
* heap-spaces — WHERE in the V8 heap the bytes are, on the ordinary heartbeat.
|
|
@@ -931,6 +1207,7 @@ function format(label, s, loop, budgetMb, suffix) {
|
|
|
931
1207
|
function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true, rssBudget, probes) {
|
|
932
1208
|
const readSpaces = probes?.readSpaces ?? readHeapSpaces;
|
|
933
1209
|
const eventPlane = probes?.eventPlane === void 0 ? void 0 : createEventPlaneMeter(probes.eventPlane);
|
|
1210
|
+
const socketPlane = probes?.socketPlane === void 0 ? void 0 : createSocketPlaneMeter(probes.socketPlane);
|
|
934
1211
|
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
935
1212
|
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
936
1213
|
const triggerMb = reclaimOptions?.triggerMb ?? 1024;
|
|
@@ -968,7 +1245,7 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
968
1245
|
return;
|
|
969
1246
|
}
|
|
970
1247
|
const after = read();
|
|
971
|
-
sink.info(`[mem] reclaim ${label} stranded=${strandedMb(sample)}MB rss=${sample.rssMb}MB→${after.rssMb}MB freed=${sample.rssMb - after.rssMb}MB arrayBuffers=${sample.arrayBuffersMb}MB→${after.arrayBuffersMb}MB took=${now() - startedAt}ms`);
|
|
1248
|
+
sink.info(`[mem] reclaim ${label} stranded=${strandedMb(sample)}MB rss=${sample.rssMb}MB→${after.rssMb}MB freed=${sample.rssMb - after.rssMb}MB arrayBuffers=${sample.arrayBuffersMb}MB→${after.arrayBuffersMb}MB took=${now() - startedAt}ms heapUsed=${sample.heapUsedMb}MB→${after.heapUsedMb}MB`);
|
|
972
1249
|
if (passesAtFloor >= 6 && !steadyStateAnnounced) {
|
|
973
1250
|
steadyStateAnnounced = true;
|
|
974
1251
|
const nextFloorMs = reclaimIntervalMs(passesAtFloor, minIntervalMs, steadyStateIntervalMs);
|
|
@@ -1035,7 +1312,7 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
1035
1312
|
const due = at - lastLoggedAt >= intervalMs;
|
|
1036
1313
|
if (mode === "escalated" || due) {
|
|
1037
1314
|
lastLoggedAt = at;
|
|
1038
|
-
const line = format(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}`);
|
|
1315
|
+
const line = format(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}` + formatSocketPlane(socketPlane?.read()));
|
|
1039
1316
|
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
1040
1317
|
else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
|
|
1041
1318
|
else sink.info(line);
|
|
@@ -4906,6 +5183,33 @@ var FrameDecoder = class {
|
|
|
4906
5183
|
chunks = [];
|
|
4907
5184
|
/** Sum of `chunks[i].byteLength` — tracked so length checks cost nothing. */
|
|
4908
5185
|
buffered = 0;
|
|
5186
|
+
/** Backing store for {@link lastFrameBytes}; see that getter. */
|
|
5187
|
+
sizes = [];
|
|
5188
|
+
/**
|
|
5189
|
+
* On-the-wire byte length (4-byte prefix included) of each frame returned by
|
|
5190
|
+
* the most recent {@link push}, index-aligned with the returned array.
|
|
5191
|
+
*
|
|
5192
|
+
* It exists because the frames come back DECODED, and a decoded value has no
|
|
5193
|
+
* memory of how many bytes it cost — so a caller counting received bytes per
|
|
5194
|
+
* message kind (`SocketChannel`, feeding the `[mem]` line's per-peer socket
|
|
5195
|
+
* attribution) could otherwise only charge a whole `data` chunk, which may
|
|
5196
|
+
* hold several frames of different kinds, or half of one.
|
|
5197
|
+
*
|
|
5198
|
+
* **Only the first `frames.length` entries are meaningful**, and the array is
|
|
5199
|
+
* never truncated. That is deliberate and it was measured on Node 24.17:
|
|
5200
|
+
* `sizes.length = 0` + `push` costs **35.1–35.9 ns** per call — V8 shrinks the
|
|
5201
|
+
* backing store and the next call re-grows it — which is 3 % of
|
|
5202
|
+
* `FrameDecoder.push` itself and 15× the counter it feeds. Writing in place
|
|
5203
|
+
* costs **0.06–0.13 ns**. On a path that runs ~22 000 times a second, an
|
|
5204
|
+
* instrument may not be the most expensive thing on the line it measures.
|
|
5205
|
+
*
|
|
5206
|
+
* Read it immediately after the `push` that produced it — it is a window onto
|
|
5207
|
+
* the decoder, not a value. Safe because `push` is only ever driven by a
|
|
5208
|
+
* socket `data` event, which Node never delivers re-entrantly.
|
|
5209
|
+
*/
|
|
5210
|
+
get lastFrameBytes() {
|
|
5211
|
+
return this.sizes;
|
|
5212
|
+
}
|
|
4909
5213
|
push(chunk) {
|
|
4910
5214
|
if (chunk.byteLength > 0) {
|
|
4911
5215
|
this.chunks.push(chunk);
|
|
@@ -4923,6 +5227,7 @@ var FrameDecoder = class {
|
|
|
4923
5227
|
body = Buffer.allocUnsafeSlow(len);
|
|
4924
5228
|
frame.copy(body, 0, HEADER_BYTES);
|
|
4925
5229
|
} else body = frame.subarray(HEADER_BYTES);
|
|
5230
|
+
this.sizes[frames.length] = total;
|
|
4926
5231
|
frames.push(decode(body));
|
|
4927
5232
|
}
|
|
4928
5233
|
return frames;
|
|
@@ -5007,6 +5312,11 @@ var SocketChannel = class {
|
|
|
5007
5312
|
closeHandler = () => {};
|
|
5008
5313
|
closed = false;
|
|
5009
5314
|
closeFired = false;
|
|
5315
|
+
/** Frames written to the peer, cumulative, split by kind. See
|
|
5316
|
+
* {@link readTraffic} and `transport/socket-traffic.ts`. */
|
|
5317
|
+
txCounters = createSocketDirectionCounters();
|
|
5318
|
+
/** Frames read from the peer, cumulative, split by kind. */
|
|
5319
|
+
rxCounters = createSocketDirectionCounters();
|
|
5010
5320
|
constructor(socket) {
|
|
5011
5321
|
this.socket = socket;
|
|
5012
5322
|
socket.on("data", (chunk) => this.onData(chunk));
|
|
@@ -5073,14 +5383,41 @@ var SocketChannel = class {
|
|
|
5073
5383
|
this.closed = true;
|
|
5074
5384
|
this.socket.destroy();
|
|
5075
5385
|
}
|
|
5386
|
+
/**
|
|
5387
|
+
* This channel's cumulative traffic, both directions, split by frame kind.
|
|
5388
|
+
*
|
|
5389
|
+
* Cumulative for the life of the channel: the reporting layer subtracts the
|
|
5390
|
+
* previous reading, because the question is a RATE ("which peer receives the
|
|
5391
|
+
* 22 500 writes/s") and not a total since boot.
|
|
5392
|
+
*/
|
|
5393
|
+
readTraffic() {
|
|
5394
|
+
return {
|
|
5395
|
+
tx: sampleSocketDirection(this.txCounters),
|
|
5396
|
+
rx: sampleSocketDirection(this.rxCounters)
|
|
5397
|
+
};
|
|
5398
|
+
}
|
|
5076
5399
|
send(frame) {
|
|
5077
5400
|
if (this.closed) return;
|
|
5078
|
-
|
|
5401
|
+
const encoded = encodeFrame(frame);
|
|
5402
|
+
recordSocketFrame(this.txCounters, frame.k, encoded.byteLength);
|
|
5403
|
+
this.socket.write(encoded);
|
|
5079
5404
|
}
|
|
5080
5405
|
onData(chunk) {
|
|
5081
|
-
|
|
5406
|
+
const frames = this.decoder.push(chunk);
|
|
5407
|
+
const sizes = this.decoder.lastFrameBytes;
|
|
5408
|
+
for (let i = 0; i < frames.length; i += 1) this.handleFrame(frames[i], sizes[i] ?? 0);
|
|
5082
5409
|
}
|
|
5083
|
-
|
|
5410
|
+
/**
|
|
5411
|
+
* `wireBytes` is the frame's on-the-wire length, counted HERE rather than in
|
|
5412
|
+
* `onData` on purpose: a peer that sends a frame this decoder can parse but
|
|
5413
|
+
* whose shape is not a `Frame` throws on `frame.k`, and inside this async
|
|
5414
|
+
* method that stays the rejected promise it has always been. Reading `.k`
|
|
5415
|
+
* one frame earlier, synchronously in the socket's `data` handler, would
|
|
5416
|
+
* turn the same malformed frame into an uncaught exception on the transport
|
|
5417
|
+
* — a diagnostic that can crash the process it watches.
|
|
5418
|
+
*/
|
|
5419
|
+
async handleFrame(frame, wireBytes) {
|
|
5420
|
+
recordSocketFrame(this.rxCounters, frame.k, wireBytes);
|
|
5084
5421
|
if (frame.k === "req") {
|
|
5085
5422
|
try {
|
|
5086
5423
|
const result = await this.requestHandler(frame.body);
|
|
@@ -5954,6 +6291,22 @@ var LocalChildRegistry = class {
|
|
|
5954
6291
|
};
|
|
5955
6292
|
}
|
|
5956
6293
|
/**
|
|
6294
|
+
* This child's socket traffic — bytes and messages, both directions, split by
|
|
6295
|
+
* frame kind — or `null` when the child is gone or its channel keeps no
|
|
6296
|
+
* counters (an in-process or test transport).
|
|
6297
|
+
*
|
|
6298
|
+
* The event counters above cover ONE kind of frame and, once surfaced on
|
|
6299
|
+
* 2026-08-29, accounted for 4% of hub-main's ~44 000 socket syscalls/s. This
|
|
6300
|
+
* is the same question asked of every frame the channel moves, and it is the
|
|
6301
|
+
* only per-peer attribution that exists: `/proc` reports one `rchar`/`wchar`
|
|
6302
|
+
* pair for the whole process and has no per-socket breakdown.
|
|
6303
|
+
*/
|
|
6304
|
+
getChildSocketTraffic(childId) {
|
|
6305
|
+
const entry = this.children.get(childId);
|
|
6306
|
+
if (entry === void 0) return null;
|
|
6307
|
+
return entry.channel.readTraffic?.() ?? null;
|
|
6308
|
+
}
|
|
6309
|
+
/**
|
|
5957
6310
|
* The regime the counters above were produced under, read once from
|
|
5958
6311
|
* `CAMSTACK_UDS_EVENT_FANOUT` at construction.
|
|
5959
6312
|
*
|
|
@@ -9147,4 +9500,4 @@ async function installManifestPythonDeps(declaration, addonDir, deps, logger) {
|
|
|
9147
9500
|
await deps.installPythonRequirements(reqAbs);
|
|
9148
9501
|
}
|
|
9149
9502
|
//#endregion
|
|
9150
|
-
export { buildNativeCapProxy as $, RUNNER_HEAP_SNAPSHOT_ENV as $t, createParentUnownedCallHandler as A, HUB_MAIN_HEAP_WATCH_LABEL as At, AGENT_CAP_FWD_SERVICE as B, createLoopDelayMeter as Bt, brokerTransportLink as C, runNpm as Ct, HUB_CAP_FWD_ACTION as D, HEAP_RECLAIM_TRIGGER_MB as Dt, localProviderLink as E, HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS as Et, createUdsLoggerWithControl as F, RUNNER_HEAP_WATCH_INTERVAL_MS as Ft, readMoleculerFanoutMode as G, parseRssBudgetMb as Gt, CapRouteError as H, describeRss as Ht, LocalChildClient as I, RUNNER_RSS_BUDGET_ENV as It, UdsLocalTransportServer as J, startHeapWatch as Jt, createLocalTransport as K, reclaimIntervalMs as Kt, LocalChildRegistry as L, ZERO_LOOP_DELAY as Lt, createUdsEventBus as M, HUB_RSS_BUDGET_ENV as Mt, udsChildLogToWorkerEntry as N, RSS_BUDGET_REANNOUNCE_MIN_MS as Nt, HUB_CAP_FWD_SERVICE as O, HEAP_WATCH_INTERVAL_MS as Ot, createUdsLogger as P, RSS_BUDGET_RELEASE_RATIO as Pt, encodeFrame as Q, HEAP_SPACE_REPORT_MIN_MB as Qt, UDS_NO_ROUTE_PREFIX as R, buildHeapSample as Rt, brokerCallForCap as S, resolveNpmInvocation as St, ipcParentLink as T, HEAP_RECLAIM_MIN_INTERVAL_MS as Tt, classifyCapRoute as U, hubMainRssBudget as Ut, CapRouteResolver as V, createV8Reclaimer as Vt, callWithServiceDiscovery as W, nextRssBudgetState as Wt, localEndpointPath as X, strandedMb as Xt, SocketChannel as Y, startRunnerHeapWatch as Yt, FrameDecoder as Z, DECISIVE_HEAP_SPACES as Zt, createKernelHwAccel as _, CapabilityHandle as _t, getWorkerDeviceRegistry as a, selectReportedSpaces as an, setWorkerNativeCapsChangeListener as at, __resetCapUsageRegistryForTests as b, installManifestNativeDeps as bt, setHubConnected as c,
|
|
9503
|
+
export { buildNativeCapProxy as $, RUNNER_HEAP_SNAPSHOT_ENV as $t, createParentUnownedCallHandler as A, HUB_MAIN_HEAP_WATCH_LABEL as At, AGENT_CAP_FWD_SERVICE as B, createLoopDelayMeter as Bt, brokerTransportLink as C, runNpm as Ct, HUB_CAP_FWD_ACTION as D, HEAP_RECLAIM_TRIGGER_MB as Dt, localProviderLink as E, HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS as Et, createUdsLoggerWithControl as F, RUNNER_HEAP_WATCH_INTERVAL_MS as Ft, readMoleculerFanoutMode as G, parseRssBudgetMb as Gt, CapRouteError as H, describeRss as Ht, LocalChildClient as I, RUNNER_RSS_BUDGET_ENV as It, UdsLocalTransportServer as J, startHeapWatch as Jt, createLocalTransport as K, reclaimIntervalMs as Kt, LocalChildRegistry as L, ZERO_LOOP_DELAY as Lt, createUdsEventBus as M, HUB_RSS_BUDGET_ENV as Mt, udsChildLogToWorkerEntry as N, RSS_BUDGET_REANNOUNCE_MIN_MS as Nt, HUB_CAP_FWD_SERVICE as O, HEAP_WATCH_INTERVAL_MS as Ot, createUdsLogger as P, RSS_BUDGET_RELEASE_RATIO as Pt, encodeFrame as Q, HEAP_SPACE_REPORT_MIN_MB as Qt, UDS_NO_ROUTE_PREFIX as R, buildHeapSample as Rt, brokerCallForCap as S, formatEventPlane as Sn, resolveNpmInvocation as St, ipcParentLink as T, HEAP_RECLAIM_MIN_INTERVAL_MS as Tt, classifyCapRoute as U, hubMainRssBudget as Ut, CapRouteResolver as V, createV8Reclaimer as Vt, callWithServiceDiscovery as W, nextRssBudgetState as Wt, localEndpointPath as X, strandedMb as Xt, SocketChannel as Y, startRunnerHeapWatch as Yt, FrameDecoder as Z, DECISIVE_HEAP_SPACES as Zt, createKernelHwAccel as _, socketDirectionMessages as _n, CapabilityHandle as _t, getWorkerDeviceRegistry as a, selectReportedSpaces as an, setWorkerNativeCapsChangeListener as at, __resetCapUsageRegistryForTests as b, createEventPlaneReader as bn, installManifestNativeDeps as bt, setHubConnected as c, createSocketPlaneMeter as cn, NATIVE_PROVIDER_SERVICE_INFIX as ct, getBrokerEventBus as d, formatSocketPlane as dn, capBareAction as dt, emitHeapDiagnosticReport as en, buildUdsNativeCapProxy as et, getMoleculerEventStats as f, EMPTY_SOCKET_DIRECTION as fn, capServiceName as ft, AddonDepsManager as g, socketDirectionBytes as gn, DeviceRegistry as gt, subscribePassthrough as h, sampleSocketDirection as hn, serializeTypedArrays as ht, createUdsAddonContext as i, readHeapSpaces as in, mountNativeCapService as it, createUdsEventBridge as j, HUB_MAIN_RSS_BUDGET_MB as jt, createHubCapForwardService as k, HEAP_WATCH_WARN_RATIO as kt, EVENT_TOPIC_PREFIX as l, createSocketPlaneReader as ln, capActionName as lt, setNodeEventInterest as m, recordSocketFrame as mn, deserializeTypedArrays as mt, adaptBrokerToCluster as n, heapSnapshotAuthorised as nn, getWorkerNativeCapProvider as nt, getOrInitReadinessRegistry as o, EMPTY_SOCKET_PLANE_BASELINE as on, createAddonService as ot, registerEventBusService as p, createSocketDirectionCounters as pn, parseCapAction as pt, UdsLocalTransportClient as q, shouldReclaim as qt, createAddonContext as r, heapSpaceField as rn, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistryForClient as s, SOCKET_PLANE_TOP_N as sn, validateProviderRegistrations as st, installManifestPythonDeps as t, formatHeapSpaces as tn, createBrokerDeviceManagerApi as tt, clusterEventTopic as u, diffSocketPlane as un, capActionSuffix as ut, resolveHwAccel as v, EVENT_PLANE_TOP_N as vn, CapabilityUnavailableError as vt, buildLinkChain as w, createAddonDataPlaneFacility as wt, getCapUsageRegistry as x, diffEventPlane as xn, resolveAddonClass as xt, CapUsageRegistry as y, createEventPlaneMeter as yn, copyBundledNativeModules as yt, AGENT_CAP_FWD_ACTION as z, createDeferredHeapWatchSink as zt };
|