@camstack/system 1.2.153 → 1.2.155

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.
Files changed (64) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  19. package/dist/builtins/hub-forwarder/index.js +1 -1
  20. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  22. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  24. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  25. package/dist/builtins/local-network/local-network.addon.js +1 -1
  26. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  27. package/dist/builtins/loki-logging/index.js +1 -1
  28. package/dist/builtins/loki-logging/index.mjs +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  31. package/dist/builtins/platform-probe/index.js +1 -1
  32. package/dist/builtins/platform-probe/index.mjs +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/snapshot/index.js +1 -1
  36. package/dist/builtins/snapshot/index.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/system-config/system-config.addon.js +1 -1
  44. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  45. package/dist/builtins/winston-logging/index.js +1 -1
  46. package/dist/builtins/winston-logging/index.mjs +1 -1
  47. package/dist/{dist-C7uZ_fOb.js → dist-BPlfW-CG.js} +33 -6
  48. package/dist/{dist-BLhrrxIs.mjs → dist-D_NyUV6Y.mjs} +33 -6
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +14 -2
  51. package/dist/index.mjs +3 -3
  52. package/dist/kernel/heap-watch.d.ts +8 -0
  53. package/dist/kernel/socket-plane-report.d.ts +99 -0
  54. package/dist/kernel/transport/frame-codec.d.ts +25 -0
  55. package/dist/kernel/transport/index.d.ts +2 -0
  56. package/dist/kernel/transport/local-child-registry.d.ts +13 -0
  57. package/dist/kernel/transport/local-transport.d.ts +11 -0
  58. package/dist/kernel/transport/socket-channel.d.ts +23 -0
  59. package/dist/kernel/transport/socket-traffic.d.ts +118 -0
  60. package/dist/{manifest-python-deps-QompQxhG.mjs → manifest-python-deps-BiWtkFGY.mjs} +360 -7
  61. package/dist/{manifest-python-deps-CRTx_j4k.js → manifest-python-deps-w48vOhGR.js} +431 -6
  62. package/dist/{retired-settings-keys-q9mt_DLX.mjs → retired-settings-keys-Bq1Tb84I.mjs} +1 -1
  63. package/dist/{retired-settings-keys-CBU4-TJO.js → retired-settings-keys-DBY6ebwV.js} +1 -1
  64. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import "./dist-BLhrrxIs.mjs";
1
+ import "./dist-D_NyUV6Y.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
- this.socket.write(encodeFrame(frame));
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
- for (const f of this.decoder.push(chunk)) this.handleFrame(f);
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
- async handleFrame(frame) {
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, createEventPlaneReader as cn, NATIVE_PROVIDER_SERVICE_INFIX as ct, getBrokerEventBus as d, capBareAction as dt, emitHeapDiagnosticReport as en, buildUdsNativeCapProxy as et, getMoleculerEventStats as f, capServiceName as ft, AddonDepsManager as g, DeviceRegistry as gt, subscribePassthrough as h, 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, diffEventPlane as ln, capActionName as lt, setNodeEventInterest as m, deserializeTypedArrays as mt, adaptBrokerToCluster as n, heapSnapshotAuthorised as nn, getWorkerNativeCapProvider as nt, getOrInitReadinessRegistry as o, EVENT_PLANE_TOP_N as on, createAddonService as ot, registerEventBusService as p, parseCapAction as pt, UdsLocalTransportClient as q, shouldReclaim as qt, createAddonContext as r, heapSpaceField as rn, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistryForClient as s, createEventPlaneMeter as sn, validateProviderRegistrations as st, installManifestPythonDeps as t, formatHeapSpaces as tn, createBrokerDeviceManagerApi as tt, clusterEventTopic as u, formatEventPlane as un, capActionSuffix as ut, resolveHwAccel as v, CapabilityUnavailableError as vt, buildLinkChain as w, createAddonDataPlaneFacility as wt, getCapUsageRegistry as x, resolveAddonClass as xt, CapUsageRegistry as y, copyBundledNativeModules as yt, AGENT_CAP_FWD_ACTION as z, createDeferredHeapWatchSink as zt };
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 };