@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,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
require("./dist-
|
|
2
|
+
require("./dist-CDgIzo82.js");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
4
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
5
5
|
let _camstack_types_node = require("@camstack/types/node");
|
|
@@ -154,6 +154,282 @@ function formatEventPlane(window) {
|
|
|
154
154
|
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}`);
|
|
155
155
|
}
|
|
156
156
|
//#endregion
|
|
157
|
+
//#region src/kernel/transport/socket-traffic.ts
|
|
158
|
+
function createSocketDirectionCounters() {
|
|
159
|
+
return {
|
|
160
|
+
reqMessages: 0,
|
|
161
|
+
reqBytes: 0,
|
|
162
|
+
resMessages: 0,
|
|
163
|
+
resBytes: 0,
|
|
164
|
+
evtMessages: 0,
|
|
165
|
+
evtBytes: 0
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The hot-path write. Two additions and at most two string comparisons against
|
|
170
|
+
* interned literals — no allocation, no branch on anything the caller has to
|
|
171
|
+
* compute, and nothing that can throw.
|
|
172
|
+
*
|
|
173
|
+
* Measured on Node 24.17, 20 M iterations, three runs: **2.37–2.41 ns/call**
|
|
174
|
+
* against an empty loop. Its neighbours on the same path, measured in the same
|
|
175
|
+
* run: `encodeFrame` on a 0.33 KB body 1948–1975 ns, `FrameDecoder.push`
|
|
176
|
+
* 1156–1166 ns, `process.memoryUsage()` 920–945 ns. The counter is **0.12 % of
|
|
177
|
+
* `encodeFrame`** — and `encodeFrame` is followed by a syscall.
|
|
178
|
+
*
|
|
179
|
+
* Together with the decoder's per-frame size write (0.06–0.13 ns, see
|
|
180
|
+
* `frame-codec.ts`) the whole instrument adds **~2.5 ns per frame**: at 44 000
|
|
181
|
+
* frames/s that is 107–111 µs/s, i.e. **0.011 % of one core**. An instrument
|
|
182
|
+
* that changes what it measures is not an instrument, and this one cannot.
|
|
183
|
+
*/
|
|
184
|
+
function recordSocketFrame(counters, kind, bytes) {
|
|
185
|
+
if (kind === "evt") {
|
|
186
|
+
counters.evtMessages += 1;
|
|
187
|
+
counters.evtBytes += bytes;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (kind === "req") {
|
|
191
|
+
counters.reqMessages += 1;
|
|
192
|
+
counters.reqBytes += bytes;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
counters.resMessages += 1;
|
|
196
|
+
counters.resBytes += bytes;
|
|
197
|
+
}
|
|
198
|
+
/** Copy out. Called once per heartbeat line, never on the frame path. */
|
|
199
|
+
function sampleSocketDirection(counters) {
|
|
200
|
+
return {
|
|
201
|
+
reqMessages: counters.reqMessages,
|
|
202
|
+
reqBytes: counters.reqBytes,
|
|
203
|
+
resMessages: counters.resMessages,
|
|
204
|
+
resBytes: counters.resBytes,
|
|
205
|
+
evtMessages: counters.evtMessages,
|
|
206
|
+
evtBytes: counters.evtBytes
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/** A direction that has seen nothing. */
|
|
210
|
+
var EMPTY_SOCKET_DIRECTION = {
|
|
211
|
+
reqMessages: 0,
|
|
212
|
+
reqBytes: 0,
|
|
213
|
+
resMessages: 0,
|
|
214
|
+
resBytes: 0,
|
|
215
|
+
evtMessages: 0,
|
|
216
|
+
evtBytes: 0
|
|
217
|
+
};
|
|
218
|
+
/** Total messages across the three kinds. */
|
|
219
|
+
function socketDirectionMessages(sample) {
|
|
220
|
+
return sample.reqMessages + sample.resMessages + sample.evtMessages;
|
|
221
|
+
}
|
|
222
|
+
/** Total on-the-wire bytes across the three kinds. */
|
|
223
|
+
function socketDirectionBytes(sample) {
|
|
224
|
+
return sample.reqBytes + sample.resBytes + sample.evtBytes;
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/kernel/socket-plane-report.ts
|
|
228
|
+
/**
|
|
229
|
+
* socket-plane-report — which PEER, exchanging what KIND of message, accounts
|
|
230
|
+
* for hub-main's ~44 000 socket syscalls per second.
|
|
231
|
+
*
|
|
232
|
+
* ## Why this exists, and why it is not the fan-out report
|
|
233
|
+
*
|
|
234
|
+
* `event-plane-report.ts` surfaced the counters the cluster had always kept and
|
|
235
|
+
* never read. It answered its own question and, in answering it, sharpened this
|
|
236
|
+
* one: measured 2026-08-29, **the event plane is 4 % of hub-main's socket
|
|
237
|
+
* traffic**. The remaining 96 % — ~22 000 reads/s and ~22 500 writes/s at a
|
|
238
|
+
* 0.33 KB mean, on the process that mediates every cross-runner call — had no
|
|
239
|
+
* instrument at all, and no instrument was obtainable:
|
|
240
|
+
*
|
|
241
|
+
* - `/proc/<pid>/io` gives ONE `rchar`/`wchar` pair for the whole process.
|
|
242
|
+
* There is no per-socket breakdown anywhere in `/proc`.
|
|
243
|
+
* - The strongest attribution available was a correlation — 78–82 % of
|
|
244
|
+
* hub-main's outbound bytes moving with `hub/pipeline-analytics`, in both
|
|
245
|
+
* measured windows — computed from that runner's own `rchar`, which also
|
|
246
|
+
* counts page-cache file reads on a runner that owns a vector store. A
|
|
247
|
+
* correlation this stable is worth one counter to settle; it is not itself
|
|
248
|
+
* an answer.
|
|
249
|
+
* - Three separate inferences about this traffic were closed early on
|
|
250
|
+
* 2026-08-29 and were wrong. The point of this module is that the next pass
|
|
251
|
+
* is evidence.
|
|
252
|
+
*
|
|
253
|
+
* ## What it reports, and why each field is load-bearing
|
|
254
|
+
*
|
|
255
|
+
* Per kind, per direction, cluster-wide, plus the top peers:
|
|
256
|
+
*
|
|
257
|
+
* - **`evt`** scales with the number of RUNNERS, not with anything an operator
|
|
258
|
+
* asked for — an idle `hub/ml` still reads 82 messages/s. If `evt` dominates,
|
|
259
|
+
* the cost is the plane's own shape and the fix is the fan-out gate.
|
|
260
|
+
* - **`req`** scales with work. If `req` dominates, some caller is chatty and
|
|
261
|
+
* the fix is that caller.
|
|
262
|
+
* - **`res`** is where a large payload can hide (a GOP crosses hub-main as a
|
|
263
|
+
* msgpack `Uint8Array` inside a `res`). **This is the falsifier**: the
|
|
264
|
+
* "message-RATE, not media" conclusion of
|
|
265
|
+
* `docs/design/2026-08-29-hub-main-memory-what-crosses-it.md` §4 rests on a
|
|
266
|
+
* 0.33/0.40 KB mean, and `res` bytes dominating at a large mean breaks it and
|
|
267
|
+
* re-opens the D9/D18 question. Bytes are reported beside messages on every
|
|
268
|
+
* kind precisely so the mean can be read off the line.
|
|
269
|
+
*
|
|
270
|
+
* ## Deltas, never totals
|
|
271
|
+
*
|
|
272
|
+
* The channel counters are cumulative for the life of the channel. A cumulative
|
|
273
|
+
* number on a heartbeat answers "how much since boot", which nobody asked; the
|
|
274
|
+
* question is a rate. So the meter is read ONCE PER LINE and reports the window
|
|
275
|
+
* since the previous read — the same discipline the loop-delay histogram and
|
|
276
|
+
* the fan-out counters already follow, for the same reason.
|
|
277
|
+
*
|
|
278
|
+
* A peer whose runner RESPAWNED reconnects under the same id with a fresh
|
|
279
|
+
* channel and counters that went backwards. Clamping at 0 reports that window
|
|
280
|
+
* as quiet rather than negative: its traffic is genuinely unknown, and a
|
|
281
|
+
* negative number on a rate reads as a broken meter.
|
|
282
|
+
*
|
|
283
|
+
* ## What this is NOT
|
|
284
|
+
*
|
|
285
|
+
* It is not a bound, a budget or a throttle. Nothing here changes a default,
|
|
286
|
+
* drops a frame or tunes a socket. It counts what already crosses, so that the
|
|
287
|
+
* thing that eventually gets fixed is chosen from evidence.
|
|
288
|
+
*/
|
|
289
|
+
/**
|
|
290
|
+
* Build a reader over the child registry.
|
|
291
|
+
*
|
|
292
|
+
* Returns `undefined` until the registry exists — the heartbeat starts as the
|
|
293
|
+
* FIRST statement of `bootstrap()` and `LocalChildRegistry` is built hundreds
|
|
294
|
+
* of lines later. Printing zeros in that window would report a silent plane
|
|
295
|
+
* where there is no plane at all.
|
|
296
|
+
*
|
|
297
|
+
* Wrapped: a registry mid-teardown may throw, and a diagnostic that can crash
|
|
298
|
+
* the process it watches is worse than none.
|
|
299
|
+
*/
|
|
300
|
+
function createSocketPlaneReader(sources) {
|
|
301
|
+
return () => {
|
|
302
|
+
try {
|
|
303
|
+
const registry = sources.registry();
|
|
304
|
+
if (registry === null) return void 0;
|
|
305
|
+
const listed = registry.listChildren();
|
|
306
|
+
const peers = [];
|
|
307
|
+
for (const child of listed) {
|
|
308
|
+
const traffic = registry.getChildSocketTraffic(child.childId);
|
|
309
|
+
if (traffic === null) continue;
|
|
310
|
+
peers.push({
|
|
311
|
+
peerId: child.childId,
|
|
312
|
+
tx: traffic.tx,
|
|
313
|
+
rx: traffic.rx
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
peers,
|
|
318
|
+
peersConnected: listed.length
|
|
319
|
+
};
|
|
320
|
+
} catch {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* How many peers get named on the line.
|
|
327
|
+
*
|
|
328
|
+
* Five, matching `EVENT_PLANE_TOP_N` for the same reason: the measured
|
|
329
|
+
* distribution is not flat — one peer accounted for 78–82 % of outbound bytes
|
|
330
|
+
* and the runner-up for ~6 % — so five names the dominant peer, its challenger
|
|
331
|
+
* and enough of the tail to see the shape change. Naming all ~38 would put a
|
|
332
|
+
* kilobyte-long line in the hub's shared log every minute, which is how a log
|
|
333
|
+
* stops being read.
|
|
334
|
+
*/
|
|
335
|
+
var SOCKET_PLANE_TOP_N = 5;
|
|
336
|
+
/** A process that has never read the plane. */
|
|
337
|
+
var EMPTY_SOCKET_PLANE_BASELINE = { peers: /* @__PURE__ */ new Map() };
|
|
338
|
+
function diffDirection(current, before) {
|
|
339
|
+
const previous = before ?? EMPTY_SOCKET_DIRECTION;
|
|
340
|
+
return {
|
|
341
|
+
reqMessages: Math.max(0, current.reqMessages - previous.reqMessages),
|
|
342
|
+
reqBytes: Math.max(0, current.reqBytes - previous.reqBytes),
|
|
343
|
+
resMessages: Math.max(0, current.resMessages - previous.resMessages),
|
|
344
|
+
resBytes: Math.max(0, current.resBytes - previous.resBytes),
|
|
345
|
+
evtMessages: Math.max(0, current.evtMessages - previous.evtMessages),
|
|
346
|
+
evtBytes: Math.max(0, current.evtBytes - previous.evtBytes)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function addDirection(into, add) {
|
|
350
|
+
return {
|
|
351
|
+
reqMessages: into.reqMessages + add.reqMessages,
|
|
352
|
+
reqBytes: into.reqBytes + add.reqBytes,
|
|
353
|
+
resMessages: into.resMessages + add.resMessages,
|
|
354
|
+
resBytes: into.resBytes + add.resBytes,
|
|
355
|
+
evtMessages: into.evtMessages + add.evtMessages,
|
|
356
|
+
evtBytes: into.evtBytes + add.evtBytes
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/** Total messages a peer moved in the window, both directions. */
|
|
360
|
+
function peerMessages(delta) {
|
|
361
|
+
return socketDirectionMessages(delta.tx) + socketDirectionMessages(delta.rx);
|
|
362
|
+
}
|
|
363
|
+
/** Subtract the previous cumulative reading from the current one. */
|
|
364
|
+
function diffSocketPlane(baseline, current, topN = 5) {
|
|
365
|
+
const deltas = [];
|
|
366
|
+
let tx = EMPTY_SOCKET_DIRECTION;
|
|
367
|
+
let rx = EMPTY_SOCKET_DIRECTION;
|
|
368
|
+
for (const sample of current.peers) {
|
|
369
|
+
const before = baseline.peers.get(sample.peerId);
|
|
370
|
+
const delta = {
|
|
371
|
+
peerId: sample.peerId,
|
|
372
|
+
tx: diffDirection(sample.tx, before?.tx),
|
|
373
|
+
rx: diffDirection(sample.rx, before?.rx)
|
|
374
|
+
};
|
|
375
|
+
tx = addDirection(tx, delta.tx);
|
|
376
|
+
rx = addDirection(rx, delta.rx);
|
|
377
|
+
if (peerMessages(delta) > 0) deltas.push(delta);
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
peersConnected: current.peersConnected,
|
|
381
|
+
peersMeasured: current.peers.length,
|
|
382
|
+
tx,
|
|
383
|
+
rx,
|
|
384
|
+
top: deltas.toSorted((a, b) => peerMessages(b) - peerMessages(a) || a.peerId.localeCompare(b.peerId)).slice(0, topN)
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function createSocketPlaneMeter(reader, topN = 5) {
|
|
388
|
+
let baseline = EMPTY_SOCKET_PLANE_BASELINE;
|
|
389
|
+
return { read: () => {
|
|
390
|
+
const current = reader();
|
|
391
|
+
if (current === void 0) return void 0;
|
|
392
|
+
const window = diffSocketPlane(baseline, current, topN);
|
|
393
|
+
baseline = { peers: new Map(current.peers.map((p) => [p.peerId, p])) };
|
|
394
|
+
return window;
|
|
395
|
+
} };
|
|
396
|
+
}
|
|
397
|
+
/** Bytes → whole kB. The plane moves 180–330 MB/min; raw bytes would put
|
|
398
|
+
* nine-digit numbers on a line that has to stay readable. */
|
|
399
|
+
function kb(bytes) {
|
|
400
|
+
return Math.round(bytes / 1024);
|
|
401
|
+
}
|
|
402
|
+
/** `req:<msgs>/<kB>,res:<msgs>/<kB>,evt:<msgs>/<kB>` — every kind, always,
|
|
403
|
+
* including at zero: a kind that went quiet is a reading, not a blank. */
|
|
404
|
+
function formatDirection(sample) {
|
|
405
|
+
return `req:${sample.reqMessages}/${kb(sample.reqBytes)}kB,res:${sample.resMessages}/${kb(sample.resBytes)}kB,evt:${sample.evtMessages}/${kb(sample.evtBytes)}kB`;
|
|
406
|
+
}
|
|
407
|
+
/** `<peer>:tx=<req>/<res>/<evt>:rx=<req>/<res>/<evt>:kB=<tx>/<rx>` — the whole
|
|
408
|
+
* peer×kind cross for one peer, which is the question in one token. */
|
|
409
|
+
function formatPeer(delta) {
|
|
410
|
+
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))}`;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* The suffix appended to a `[mem]` line — leading space included, empty when
|
|
414
|
+
* this process has no local socket plane.
|
|
415
|
+
*
|
|
416
|
+
* Only the processes that OWN a child registry have one (hub-main, and an
|
|
417
|
+
* agent's main), so these fields never appear on the ~38 runner lines that
|
|
418
|
+
* share the same log stream. On the ones that do, every field is printed on
|
|
419
|
+
* every line including at zero — `socketPeers` because measured-below-connected
|
|
420
|
+
* is the only way to see an unmeasured channel, and the per-kind splits because
|
|
421
|
+
* "the plane went quiet for a minute" is a reading.
|
|
422
|
+
*
|
|
423
|
+
* `socketTop` is the one conditional field — a ranked list of nothing is noise —
|
|
424
|
+
* and it is a single comma-separated value so the field COUNT does not change
|
|
425
|
+
* from line to line.
|
|
426
|
+
*/
|
|
427
|
+
function formatSocketPlane(window) {
|
|
428
|
+
if (window === void 0) return "";
|
|
429
|
+
const top = window.top.map(formatPeer).join(",");
|
|
430
|
+
return ` socketPeers=${window.peersMeasured}/${window.peersConnected} socketTx=${formatDirection(window.tx)} socketRx=${formatDirection(window.rx)}` + (top.length === 0 ? "" : ` socketTop=${top}`);
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
157
433
|
//#region src/kernel/heap-spaces.ts
|
|
158
434
|
/**
|
|
159
435
|
* heap-spaces — WHERE in the V8 heap the bytes are, on the ordinary heartbeat.
|
|
@@ -935,6 +1211,7 @@ function format(label, s, loop, budgetMb, suffix) {
|
|
|
935
1211
|
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) {
|
|
936
1212
|
const readSpaces = probes?.readSpaces ?? readHeapSpaces;
|
|
937
1213
|
const eventPlane = probes?.eventPlane === void 0 ? void 0 : createEventPlaneMeter(probes.eventPlane);
|
|
1214
|
+
const socketPlane = probes?.socketPlane === void 0 ? void 0 : createSocketPlaneMeter(probes.socketPlane);
|
|
938
1215
|
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
939
1216
|
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
940
1217
|
const triggerMb = reclaimOptions?.triggerMb ?? 1024;
|
|
@@ -972,7 +1249,7 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
972
1249
|
return;
|
|
973
1250
|
}
|
|
974
1251
|
const after = read();
|
|
975
|
-
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`);
|
|
1252
|
+
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`);
|
|
976
1253
|
if (passesAtFloor >= 6 && !steadyStateAnnounced) {
|
|
977
1254
|
steadyStateAnnounced = true;
|
|
978
1255
|
const nextFloorMs = reclaimIntervalMs(passesAtFloor, minIntervalMs, steadyStateIntervalMs);
|
|
@@ -1039,7 +1316,7 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
1039
1316
|
const due = at - lastLoggedAt >= intervalMs;
|
|
1040
1317
|
if (mode === "escalated" || due) {
|
|
1041
1318
|
lastLoggedAt = at;
|
|
1042
|
-
const line = format(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}`);
|
|
1319
|
+
const line = format(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}` + formatSocketPlane(socketPlane?.read()));
|
|
1043
1320
|
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
1044
1321
|
else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
|
|
1045
1322
|
else sink.info(line);
|
|
@@ -4910,6 +5187,33 @@ var FrameDecoder = class {
|
|
|
4910
5187
|
chunks = [];
|
|
4911
5188
|
/** Sum of `chunks[i].byteLength` — tracked so length checks cost nothing. */
|
|
4912
5189
|
buffered = 0;
|
|
5190
|
+
/** Backing store for {@link lastFrameBytes}; see that getter. */
|
|
5191
|
+
sizes = [];
|
|
5192
|
+
/**
|
|
5193
|
+
* On-the-wire byte length (4-byte prefix included) of each frame returned by
|
|
5194
|
+
* the most recent {@link push}, index-aligned with the returned array.
|
|
5195
|
+
*
|
|
5196
|
+
* It exists because the frames come back DECODED, and a decoded value has no
|
|
5197
|
+
* memory of how many bytes it cost — so a caller counting received bytes per
|
|
5198
|
+
* message kind (`SocketChannel`, feeding the `[mem]` line's per-peer socket
|
|
5199
|
+
* attribution) could otherwise only charge a whole `data` chunk, which may
|
|
5200
|
+
* hold several frames of different kinds, or half of one.
|
|
5201
|
+
*
|
|
5202
|
+
* **Only the first `frames.length` entries are meaningful**, and the array is
|
|
5203
|
+
* never truncated. That is deliberate and it was measured on Node 24.17:
|
|
5204
|
+
* `sizes.length = 0` + `push` costs **35.1–35.9 ns** per call — V8 shrinks the
|
|
5205
|
+
* backing store and the next call re-grows it — which is 3 % of
|
|
5206
|
+
* `FrameDecoder.push` itself and 15× the counter it feeds. Writing in place
|
|
5207
|
+
* costs **0.06–0.13 ns**. On a path that runs ~22 000 times a second, an
|
|
5208
|
+
* instrument may not be the most expensive thing on the line it measures.
|
|
5209
|
+
*
|
|
5210
|
+
* Read it immediately after the `push` that produced it — it is a window onto
|
|
5211
|
+
* the decoder, not a value. Safe because `push` is only ever driven by a
|
|
5212
|
+
* socket `data` event, which Node never delivers re-entrantly.
|
|
5213
|
+
*/
|
|
5214
|
+
get lastFrameBytes() {
|
|
5215
|
+
return this.sizes;
|
|
5216
|
+
}
|
|
4913
5217
|
push(chunk) {
|
|
4914
5218
|
if (chunk.byteLength > 0) {
|
|
4915
5219
|
this.chunks.push(chunk);
|
|
@@ -4927,6 +5231,7 @@ var FrameDecoder = class {
|
|
|
4927
5231
|
body = Buffer.allocUnsafeSlow(len);
|
|
4928
5232
|
frame.copy(body, 0, HEADER_BYTES);
|
|
4929
5233
|
} else body = frame.subarray(HEADER_BYTES);
|
|
5234
|
+
this.sizes[frames.length] = total;
|
|
4930
5235
|
frames.push(decode(body));
|
|
4931
5236
|
}
|
|
4932
5237
|
return frames;
|
|
@@ -5011,6 +5316,11 @@ var SocketChannel = class {
|
|
|
5011
5316
|
closeHandler = () => {};
|
|
5012
5317
|
closed = false;
|
|
5013
5318
|
closeFired = false;
|
|
5319
|
+
/** Frames written to the peer, cumulative, split by kind. See
|
|
5320
|
+
* {@link readTraffic} and `transport/socket-traffic.ts`. */
|
|
5321
|
+
txCounters = createSocketDirectionCounters();
|
|
5322
|
+
/** Frames read from the peer, cumulative, split by kind. */
|
|
5323
|
+
rxCounters = createSocketDirectionCounters();
|
|
5014
5324
|
constructor(socket) {
|
|
5015
5325
|
this.socket = socket;
|
|
5016
5326
|
socket.on("data", (chunk) => this.onData(chunk));
|
|
@@ -5077,14 +5387,41 @@ var SocketChannel = class {
|
|
|
5077
5387
|
this.closed = true;
|
|
5078
5388
|
this.socket.destroy();
|
|
5079
5389
|
}
|
|
5390
|
+
/**
|
|
5391
|
+
* This channel's cumulative traffic, both directions, split by frame kind.
|
|
5392
|
+
*
|
|
5393
|
+
* Cumulative for the life of the channel: the reporting layer subtracts the
|
|
5394
|
+
* previous reading, because the question is a RATE ("which peer receives the
|
|
5395
|
+
* 22 500 writes/s") and not a total since boot.
|
|
5396
|
+
*/
|
|
5397
|
+
readTraffic() {
|
|
5398
|
+
return {
|
|
5399
|
+
tx: sampleSocketDirection(this.txCounters),
|
|
5400
|
+
rx: sampleSocketDirection(this.rxCounters)
|
|
5401
|
+
};
|
|
5402
|
+
}
|
|
5080
5403
|
send(frame) {
|
|
5081
5404
|
if (this.closed) return;
|
|
5082
|
-
|
|
5405
|
+
const encoded = encodeFrame(frame);
|
|
5406
|
+
recordSocketFrame(this.txCounters, frame.k, encoded.byteLength);
|
|
5407
|
+
this.socket.write(encoded);
|
|
5083
5408
|
}
|
|
5084
5409
|
onData(chunk) {
|
|
5085
|
-
|
|
5410
|
+
const frames = this.decoder.push(chunk);
|
|
5411
|
+
const sizes = this.decoder.lastFrameBytes;
|
|
5412
|
+
for (let i = 0; i < frames.length; i += 1) this.handleFrame(frames[i], sizes[i] ?? 0);
|
|
5086
5413
|
}
|
|
5087
|
-
|
|
5414
|
+
/**
|
|
5415
|
+
* `wireBytes` is the frame's on-the-wire length, counted HERE rather than in
|
|
5416
|
+
* `onData` on purpose: a peer that sends a frame this decoder can parse but
|
|
5417
|
+
* whose shape is not a `Frame` throws on `frame.k`, and inside this async
|
|
5418
|
+
* method that stays the rejected promise it has always been. Reading `.k`
|
|
5419
|
+
* one frame earlier, synchronously in the socket's `data` handler, would
|
|
5420
|
+
* turn the same malformed frame into an uncaught exception on the transport
|
|
5421
|
+
* — a diagnostic that can crash the process it watches.
|
|
5422
|
+
*/
|
|
5423
|
+
async handleFrame(frame, wireBytes) {
|
|
5424
|
+
recordSocketFrame(this.rxCounters, frame.k, wireBytes);
|
|
5088
5425
|
if (frame.k === "req") {
|
|
5089
5426
|
try {
|
|
5090
5427
|
const result = await this.requestHandler(frame.body);
|
|
@@ -5958,6 +6295,22 @@ var LocalChildRegistry = class {
|
|
|
5958
6295
|
};
|
|
5959
6296
|
}
|
|
5960
6297
|
/**
|
|
6298
|
+
* This child's socket traffic — bytes and messages, both directions, split by
|
|
6299
|
+
* frame kind — or `null` when the child is gone or its channel keeps no
|
|
6300
|
+
* counters (an in-process or test transport).
|
|
6301
|
+
*
|
|
6302
|
+
* The event counters above cover ONE kind of frame and, once surfaced on
|
|
6303
|
+
* 2026-08-29, accounted for 4% of hub-main's ~44 000 socket syscalls/s. This
|
|
6304
|
+
* is the same question asked of every frame the channel moves, and it is the
|
|
6305
|
+
* only per-peer attribution that exists: `/proc` reports one `rchar`/`wchar`
|
|
6306
|
+
* pair for the whole process and has no per-socket breakdown.
|
|
6307
|
+
*/
|
|
6308
|
+
getChildSocketTraffic(childId) {
|
|
6309
|
+
const entry = this.children.get(childId);
|
|
6310
|
+
if (entry === void 0) return null;
|
|
6311
|
+
return entry.channel.readTraffic?.() ?? null;
|
|
6312
|
+
}
|
|
6313
|
+
/**
|
|
5961
6314
|
* The regime the counters above were produced under, read once from
|
|
5962
6315
|
* `CAMSTACK_UDS_EVENT_FANOUT` at construction.
|
|
5963
6316
|
*
|
|
@@ -9211,6 +9564,18 @@ Object.defineProperty(exports, "DeviceRegistry", {
|
|
|
9211
9564
|
return DeviceRegistry;
|
|
9212
9565
|
}
|
|
9213
9566
|
});
|
|
9567
|
+
Object.defineProperty(exports, "EMPTY_SOCKET_DIRECTION", {
|
|
9568
|
+
enumerable: true,
|
|
9569
|
+
get: function() {
|
|
9570
|
+
return EMPTY_SOCKET_DIRECTION;
|
|
9571
|
+
}
|
|
9572
|
+
});
|
|
9573
|
+
Object.defineProperty(exports, "EMPTY_SOCKET_PLANE_BASELINE", {
|
|
9574
|
+
enumerable: true,
|
|
9575
|
+
get: function() {
|
|
9576
|
+
return EMPTY_SOCKET_PLANE_BASELINE;
|
|
9577
|
+
}
|
|
9578
|
+
});
|
|
9214
9579
|
Object.defineProperty(exports, "EVENT_PLANE_TOP_N", {
|
|
9215
9580
|
enumerable: true,
|
|
9216
9581
|
get: function() {
|
|
@@ -9343,6 +9708,12 @@ Object.defineProperty(exports, "RUNNER_RSS_BUDGET_ENV", {
|
|
|
9343
9708
|
return RUNNER_RSS_BUDGET_ENV;
|
|
9344
9709
|
}
|
|
9345
9710
|
});
|
|
9711
|
+
Object.defineProperty(exports, "SOCKET_PLANE_TOP_N", {
|
|
9712
|
+
enumerable: true,
|
|
9713
|
+
get: function() {
|
|
9714
|
+
return SOCKET_PLANE_TOP_N;
|
|
9715
|
+
}
|
|
9716
|
+
});
|
|
9346
9717
|
Object.defineProperty(exports, "SocketChannel", {
|
|
9347
9718
|
enumerable: true,
|
|
9348
9719
|
get: function() {
|
|
@@ -9541,6 +9912,24 @@ Object.defineProperty(exports, "createParentUnownedCallHandler", {
|
|
|
9541
9912
|
return createParentUnownedCallHandler;
|
|
9542
9913
|
}
|
|
9543
9914
|
});
|
|
9915
|
+
Object.defineProperty(exports, "createSocketDirectionCounters", {
|
|
9916
|
+
enumerable: true,
|
|
9917
|
+
get: function() {
|
|
9918
|
+
return createSocketDirectionCounters;
|
|
9919
|
+
}
|
|
9920
|
+
});
|
|
9921
|
+
Object.defineProperty(exports, "createSocketPlaneMeter", {
|
|
9922
|
+
enumerable: true,
|
|
9923
|
+
get: function() {
|
|
9924
|
+
return createSocketPlaneMeter;
|
|
9925
|
+
}
|
|
9926
|
+
});
|
|
9927
|
+
Object.defineProperty(exports, "createSocketPlaneReader", {
|
|
9928
|
+
enumerable: true,
|
|
9929
|
+
get: function() {
|
|
9930
|
+
return createSocketPlaneReader;
|
|
9931
|
+
}
|
|
9932
|
+
});
|
|
9544
9933
|
Object.defineProperty(exports, "createUdsAddonContext", {
|
|
9545
9934
|
enumerable: true,
|
|
9546
9935
|
get: function() {
|
|
@@ -9595,6 +9984,12 @@ Object.defineProperty(exports, "diffEventPlane", {
|
|
|
9595
9984
|
return diffEventPlane;
|
|
9596
9985
|
}
|
|
9597
9986
|
});
|
|
9987
|
+
Object.defineProperty(exports, "diffSocketPlane", {
|
|
9988
|
+
enumerable: true,
|
|
9989
|
+
get: function() {
|
|
9990
|
+
return diffSocketPlane;
|
|
9991
|
+
}
|
|
9992
|
+
});
|
|
9598
9993
|
Object.defineProperty(exports, "emitHeapDiagnosticReport", {
|
|
9599
9994
|
enumerable: true,
|
|
9600
9995
|
get: function() {
|
|
@@ -9619,6 +10014,12 @@ Object.defineProperty(exports, "formatHeapSpaces", {
|
|
|
9619
10014
|
return formatHeapSpaces;
|
|
9620
10015
|
}
|
|
9621
10016
|
});
|
|
10017
|
+
Object.defineProperty(exports, "formatSocketPlane", {
|
|
10018
|
+
enumerable: true,
|
|
10019
|
+
get: function() {
|
|
10020
|
+
return formatSocketPlane;
|
|
10021
|
+
}
|
|
10022
|
+
});
|
|
9622
10023
|
Object.defineProperty(exports, "getBrokerEventBus", {
|
|
9623
10024
|
enumerable: true,
|
|
9624
10025
|
get: function() {
|
|
@@ -9757,6 +10158,12 @@ Object.defineProperty(exports, "reclaimIntervalMs", {
|
|
|
9757
10158
|
return reclaimIntervalMs;
|
|
9758
10159
|
}
|
|
9759
10160
|
});
|
|
10161
|
+
Object.defineProperty(exports, "recordSocketFrame", {
|
|
10162
|
+
enumerable: true,
|
|
10163
|
+
get: function() {
|
|
10164
|
+
return recordSocketFrame;
|
|
10165
|
+
}
|
|
10166
|
+
});
|
|
9760
10167
|
Object.defineProperty(exports, "registerEventBusService", {
|
|
9761
10168
|
enumerable: true,
|
|
9762
10169
|
get: function() {
|
|
@@ -9787,6 +10194,12 @@ Object.defineProperty(exports, "runNpm", {
|
|
|
9787
10194
|
return runNpm;
|
|
9788
10195
|
}
|
|
9789
10196
|
});
|
|
10197
|
+
Object.defineProperty(exports, "sampleSocketDirection", {
|
|
10198
|
+
enumerable: true,
|
|
10199
|
+
get: function() {
|
|
10200
|
+
return sampleSocketDirection;
|
|
10201
|
+
}
|
|
10202
|
+
});
|
|
9790
10203
|
Object.defineProperty(exports, "selectReportedSpaces", {
|
|
9791
10204
|
enumerable: true,
|
|
9792
10205
|
get: function() {
|
|
@@ -9823,6 +10236,18 @@ Object.defineProperty(exports, "shouldReclaim", {
|
|
|
9823
10236
|
return shouldReclaim;
|
|
9824
10237
|
}
|
|
9825
10238
|
});
|
|
10239
|
+
Object.defineProperty(exports, "socketDirectionBytes", {
|
|
10240
|
+
enumerable: true,
|
|
10241
|
+
get: function() {
|
|
10242
|
+
return socketDirectionBytes;
|
|
10243
|
+
}
|
|
10244
|
+
});
|
|
10245
|
+
Object.defineProperty(exports, "socketDirectionMessages", {
|
|
10246
|
+
enumerable: true,
|
|
10247
|
+
get: function() {
|
|
10248
|
+
return socketDirectionMessages;
|
|
10249
|
+
}
|
|
10250
|
+
});
|
|
9826
10251
|
Object.defineProperty(exports, "startHeapWatch", {
|
|
9827
10252
|
enumerable: true,
|
|
9828
10253
|
get: function() {
|