@camstack/system 1.2.122 → 1.2.124
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-bindings-store.d.ts +20 -0
- package/dist/builtins/device-manager/device-linked-devices.d.ts +16 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +6 -0
- package/dist/builtins/device-manager/device-manager.addon.js +73 -7
- package/dist/builtins/device-manager/device-manager.addon.mjs +73 -7
- 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 +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -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-DnhGRFEn.js → dist-CIDgcxPk.js} +97 -21
- package/dist/{dist-CEhrHp3u.mjs → dist-CvJrtLkw.mjs} +97 -21
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/kernel/heap-watch.d.ts +80 -1
- package/dist/{manifest-python-deps-DZSNlHjU.mjs → manifest-python-deps-D1Ydw7J0.mjs} +93 -8
- package/dist/{manifest-python-deps-76dZLxD0.js → manifest-python-deps-DB_4H3tR.js} +93 -8
- package/dist/{retired-settings-keys-VAyOhu_a.mjs → retired-settings-keys-B1aZcoS0.mjs} +1 -1
- package/dist/{retired-settings-keys-D6Jy_SsO.js → retired-settings-keys-D7c0n9jO.js} +1 -1
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ import * as os from "node:os";
|
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
12
|
import { createServer } from "node:http";
|
|
13
|
+
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
13
14
|
import * as v8 from "node:v8";
|
|
14
15
|
import * as vm from "node:vm";
|
|
15
16
|
import { BOOT_RECOVERY_BACKOFF_MS, DATAPLANE_SECRET_HEADER, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, createEventBusSliceSource, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep, withCapInputDefaults } from "@camstack/types/addon";
|
|
@@ -166,6 +167,72 @@ var HEAP_RECLAIM_TRIGGER_MB = 1024;
|
|
|
166
167
|
* router's time, against a ~1.5GiB peak it removes each pass.
|
|
167
168
|
*/
|
|
168
169
|
var HEAP_RECLAIM_MIN_INTERVAL_MS = 12e4;
|
|
170
|
+
/** What a loop with nothing recorded yet reports. */
|
|
171
|
+
var ZERO_LOOP_DELAY = {
|
|
172
|
+
p50Ms: 0,
|
|
173
|
+
p99Ms: 0,
|
|
174
|
+
maxMs: 0
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* A `perf_hooks` interval histogram, read once per LOGGED line.
|
|
178
|
+
*
|
|
179
|
+
* The histogram samples in libuv at `resolutionMs` and costs nothing to keep
|
|
180
|
+
* enabled; the expensive part would be reading it on every 2s probe, which
|
|
181
|
+
* would also destroy the window the line claims to describe (probe and log
|
|
182
|
+
* cadences are deliberately different — see {@link startHeapWatch}).
|
|
183
|
+
*
|
|
184
|
+
* An EMPTY histogram is the trap: Node answers `min = 9223372036854776000` and
|
|
185
|
+
* `percentile()` throws on some versions, so a first heartbeat that read it
|
|
186
|
+
* naively would print `loopMax=9223372036854ms` and be read as a catastrophe on
|
|
187
|
+
* every boot. `count === 0` short-circuits to {@link ZERO_LOOP_DELAY}.
|
|
188
|
+
*
|
|
189
|
+
* Returns undefined if the platform declines, so the caller keeps a heartbeat
|
|
190
|
+
* either way.
|
|
191
|
+
*/
|
|
192
|
+
function createLoopDelayMeter(resolutionMs = 20) {
|
|
193
|
+
try {
|
|
194
|
+
const histogram = monitorEventLoopDelay({ resolution: resolutionMs });
|
|
195
|
+
histogram.enable();
|
|
196
|
+
return {
|
|
197
|
+
read: () => {
|
|
198
|
+
try {
|
|
199
|
+
if (histogram.count === 0) return ZERO_LOOP_DELAY;
|
|
200
|
+
const sample = {
|
|
201
|
+
p50Ms: Math.round(histogram.percentile(50) / 1e6),
|
|
202
|
+
p99Ms: Math.round(histogram.percentile(99) / 1e6),
|
|
203
|
+
maxMs: Math.round(histogram.max / 1e6)
|
|
204
|
+
};
|
|
205
|
+
histogram.reset();
|
|
206
|
+
return sample;
|
|
207
|
+
} catch {
|
|
208
|
+
return ZERO_LOOP_DELAY;
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
stop: () => {
|
|
212
|
+
histogram.disable();
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
} catch {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Read the origin of the old-space ceiling off node's own options.
|
|
221
|
+
*
|
|
222
|
+
* hub-main is the only process in the cluster whose ceiling nobody chose. Every
|
|
223
|
+
* forked runner is given V8 heap flags by `process-service.ts`; hub-main gets
|
|
224
|
+
* `--max-old-space-size` from `docker-entrypoint.sh` ONLY when the operator sets
|
|
225
|
+
* `CAMSTACK_HUB_HEAP_MB`, and on the live container it is unset — verified
|
|
226
|
+
* 2026-08-25 by reading pid 7's argv, a bare `node .../starter.js`. So the
|
|
227
|
+
* ceiling in force is whatever V8 derives from the host (4288MB there), and the
|
|
228
|
+
* only record of that was a comment inside an image nobody rebuilds to read.
|
|
229
|
+
*
|
|
230
|
+
* A fact about the running process belongs in the running process's log, and
|
|
231
|
+
* `execArgv` answers it for free at boot.
|
|
232
|
+
*/
|
|
233
|
+
function heapCeilingOrigin(execArgv = process.execArgv) {
|
|
234
|
+
return execArgv.some((arg) => /^--max[-_]old[-_]space[-_]size(=|$)/.test(arg)) ? "explicit" : "v8-default";
|
|
235
|
+
}
|
|
169
236
|
var MB = (bytes) => Math.round(bytes / 1048576);
|
|
170
237
|
/**
|
|
171
238
|
* Build a sample from raw readings. Pure so the thresholds are testable without
|
|
@@ -229,8 +296,10 @@ function createV8Reclaimer() {
|
|
|
229
296
|
return;
|
|
230
297
|
}
|
|
231
298
|
}
|
|
232
|
-
function format(label, s) {
|
|
233
|
-
|
|
299
|
+
function format(label, s, loop) {
|
|
300
|
+
const line = `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
|
|
301
|
+
if (loop === void 0) return line;
|
|
302
|
+
return `${line} loopP50=${loop.p50Ms}ms loopP99=${loop.p99Ms}ms loopMax=${loop.maxMs}ms`;
|
|
234
303
|
}
|
|
235
304
|
/**
|
|
236
305
|
* Start the heartbeat. Returns a stop function.
|
|
@@ -238,7 +307,7 @@ function format(label, s) {
|
|
|
238
307
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
239
308
|
* a diagnostic that can crash the process it watches is worse than none.
|
|
240
309
|
*/
|
|
241
|
-
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions) {
|
|
310
|
+
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true) {
|
|
242
311
|
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
243
312
|
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
244
313
|
const triggerMb = reclaimOptions?.triggerMb ?? 1024;
|
|
@@ -247,14 +316,24 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
247
316
|
const escalateRatio = reclaimOptions?.escalateRatio ?? .7;
|
|
248
317
|
const deescalateRatio = reclaimOptions?.deescalateRatio ?? .6;
|
|
249
318
|
let lastReclaimAt = Number.NEGATIVE_INFINITY;
|
|
319
|
+
/** Consecutive passes that fired the instant the floor elapsed. See
|
|
320
|
+
* {@link RECLAIM_STEADY_STATE_PASSES}. */
|
|
321
|
+
let passesAtFloor = 0;
|
|
322
|
+
let steadyStateAnnounced = false;
|
|
250
323
|
const read = () => {
|
|
251
324
|
const limit = reclaimOptions?.heapLimitBytes ?? v8.getHeapStatistics().heap_size_limit;
|
|
252
325
|
return buildHeapSample(readMemory(), limit);
|
|
253
326
|
};
|
|
254
327
|
const maybeReclaim = (sample) => {
|
|
255
328
|
if (reclaimOptions === void 0) return;
|
|
256
|
-
if (!shouldReclaim(sample, triggerMb))
|
|
257
|
-
|
|
329
|
+
if (!shouldReclaim(sample, triggerMb)) {
|
|
330
|
+
passesAtFloor = 0;
|
|
331
|
+
steadyStateAnnounced = false;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const sinceLast = now() - lastReclaimAt;
|
|
335
|
+
if (sinceLast < minIntervalMs) return;
|
|
336
|
+
passesAtFloor = sinceLast <= minIntervalMs * 2 ? passesAtFloor + 1 : 1;
|
|
258
337
|
lastReclaimAt = now();
|
|
259
338
|
const startedAt = now();
|
|
260
339
|
try {
|
|
@@ -265,6 +344,10 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
265
344
|
}
|
|
266
345
|
const after = read();
|
|
267
346
|
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`);
|
|
347
|
+
if (passesAtFloor >= 6 && !steadyStateAnnounced) {
|
|
348
|
+
steadyStateAnnounced = true;
|
|
349
|
+
sink.warn(`[mem] reclaim ${label} has run at the floor for ${passesAtFloor} consecutive passes (every ${minIntervalMs}ms) — the stop-the-world is now STEADY STATE, not a rescue. The allocation source is what needs the fix; this pass only bounds its peak.`);
|
|
350
|
+
}
|
|
268
351
|
};
|
|
269
352
|
let mode = "steady";
|
|
270
353
|
let lastLoggedAt = Number.NEGATIVE_INFINITY;
|
|
@@ -293,7 +376,7 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
293
376
|
const due = at - lastLoggedAt >= intervalMs;
|
|
294
377
|
if (mode === "escalated" || due) {
|
|
295
378
|
lastLoggedAt = at;
|
|
296
|
-
const line = format(label, sample);
|
|
379
|
+
const line = format(label, sample, loopDelay?.read());
|
|
297
380
|
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
298
381
|
else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
|
|
299
382
|
else sink.info(line);
|
|
@@ -305,11 +388,13 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
305
388
|
const timer = setInterval(tick, probeIntervalMs);
|
|
306
389
|
timer.unref?.();
|
|
307
390
|
tick();
|
|
391
|
+
if (announceCeilingOrigin && heapCeilingOrigin(execArgv) === "v8-default") sink.info(`[mem] ${label} heap ceiling is V8's DEFAULT (${read().heapLimitMb}MB) — no --max-old-space-size on argv. Nothing here CHOSE that number; it is derived from host RAM and moves with it.`);
|
|
308
392
|
let stopped = false;
|
|
309
393
|
return () => {
|
|
310
394
|
if (stopped) return;
|
|
311
395
|
stopped = true;
|
|
312
396
|
clearInterval(timer);
|
|
397
|
+
loopDelay?.stop();
|
|
313
398
|
};
|
|
314
399
|
}
|
|
315
400
|
/**
|
|
@@ -358,13 +443,13 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
|
|
|
358
443
|
function startRunnerHeapWatch(options) {
|
|
359
444
|
if (options.heapProfile !== "heavy") return void 0;
|
|
360
445
|
const intervalMs = options.intervalMs ?? 3e5;
|
|
361
|
-
if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs);
|
|
446
|
+
if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs, void 0, void 0, void 0, false);
|
|
362
447
|
let reclaimOptions = options.reclaimOptions;
|
|
363
448
|
if (reclaimOptions === void 0) {
|
|
364
449
|
const reclaimer = createV8Reclaimer();
|
|
365
450
|
reclaimOptions = reclaimer === void 0 ? void 0 : { reclaim: reclaimer };
|
|
366
451
|
}
|
|
367
|
-
return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions);
|
|
452
|
+
return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions, void 0, void 0, false);
|
|
368
453
|
}
|
|
369
454
|
//#endregion
|
|
370
455
|
//#region src/kernel/moleculer/addon-data-plane-facility.ts
|
|
@@ -12,6 +12,7 @@ node_os = require_chunk.__toESM(node_os);
|
|
|
12
12
|
let node_fs = require("node:fs");
|
|
13
13
|
node_fs = require_chunk.__toESM(node_fs);
|
|
14
14
|
let node_http = require("node:http");
|
|
15
|
+
let node_perf_hooks = require("node:perf_hooks");
|
|
15
16
|
let node_v8 = require("node:v8");
|
|
16
17
|
node_v8 = require_chunk.__toESM(node_v8);
|
|
17
18
|
let node_vm = require("node:vm");
|
|
@@ -170,6 +171,72 @@ var HEAP_RECLAIM_TRIGGER_MB = 1024;
|
|
|
170
171
|
* router's time, against a ~1.5GiB peak it removes each pass.
|
|
171
172
|
*/
|
|
172
173
|
var HEAP_RECLAIM_MIN_INTERVAL_MS = 12e4;
|
|
174
|
+
/** What a loop with nothing recorded yet reports. */
|
|
175
|
+
var ZERO_LOOP_DELAY = {
|
|
176
|
+
p50Ms: 0,
|
|
177
|
+
p99Ms: 0,
|
|
178
|
+
maxMs: 0
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* A `perf_hooks` interval histogram, read once per LOGGED line.
|
|
182
|
+
*
|
|
183
|
+
* The histogram samples in libuv at `resolutionMs` and costs nothing to keep
|
|
184
|
+
* enabled; the expensive part would be reading it on every 2s probe, which
|
|
185
|
+
* would also destroy the window the line claims to describe (probe and log
|
|
186
|
+
* cadences are deliberately different — see {@link startHeapWatch}).
|
|
187
|
+
*
|
|
188
|
+
* An EMPTY histogram is the trap: Node answers `min = 9223372036854776000` and
|
|
189
|
+
* `percentile()` throws on some versions, so a first heartbeat that read it
|
|
190
|
+
* naively would print `loopMax=9223372036854ms` and be read as a catastrophe on
|
|
191
|
+
* every boot. `count === 0` short-circuits to {@link ZERO_LOOP_DELAY}.
|
|
192
|
+
*
|
|
193
|
+
* Returns undefined if the platform declines, so the caller keeps a heartbeat
|
|
194
|
+
* either way.
|
|
195
|
+
*/
|
|
196
|
+
function createLoopDelayMeter(resolutionMs = 20) {
|
|
197
|
+
try {
|
|
198
|
+
const histogram = (0, node_perf_hooks.monitorEventLoopDelay)({ resolution: resolutionMs });
|
|
199
|
+
histogram.enable();
|
|
200
|
+
return {
|
|
201
|
+
read: () => {
|
|
202
|
+
try {
|
|
203
|
+
if (histogram.count === 0) return ZERO_LOOP_DELAY;
|
|
204
|
+
const sample = {
|
|
205
|
+
p50Ms: Math.round(histogram.percentile(50) / 1e6),
|
|
206
|
+
p99Ms: Math.round(histogram.percentile(99) / 1e6),
|
|
207
|
+
maxMs: Math.round(histogram.max / 1e6)
|
|
208
|
+
};
|
|
209
|
+
histogram.reset();
|
|
210
|
+
return sample;
|
|
211
|
+
} catch {
|
|
212
|
+
return ZERO_LOOP_DELAY;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
stop: () => {
|
|
216
|
+
histogram.disable();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
} catch {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Read the origin of the old-space ceiling off node's own options.
|
|
225
|
+
*
|
|
226
|
+
* hub-main is the only process in the cluster whose ceiling nobody chose. Every
|
|
227
|
+
* forked runner is given V8 heap flags by `process-service.ts`; hub-main gets
|
|
228
|
+
* `--max-old-space-size` from `docker-entrypoint.sh` ONLY when the operator sets
|
|
229
|
+
* `CAMSTACK_HUB_HEAP_MB`, and on the live container it is unset — verified
|
|
230
|
+
* 2026-08-25 by reading pid 7's argv, a bare `node .../starter.js`. So the
|
|
231
|
+
* ceiling in force is whatever V8 derives from the host (4288MB there), and the
|
|
232
|
+
* only record of that was a comment inside an image nobody rebuilds to read.
|
|
233
|
+
*
|
|
234
|
+
* A fact about the running process belongs in the running process's log, and
|
|
235
|
+
* `execArgv` answers it for free at boot.
|
|
236
|
+
*/
|
|
237
|
+
function heapCeilingOrigin(execArgv = process.execArgv) {
|
|
238
|
+
return execArgv.some((arg) => /^--max[-_]old[-_]space[-_]size(=|$)/.test(arg)) ? "explicit" : "v8-default";
|
|
239
|
+
}
|
|
173
240
|
var MB = (bytes) => Math.round(bytes / 1048576);
|
|
174
241
|
/**
|
|
175
242
|
* Build a sample from raw readings. Pure so the thresholds are testable without
|
|
@@ -233,8 +300,10 @@ function createV8Reclaimer() {
|
|
|
233
300
|
return;
|
|
234
301
|
}
|
|
235
302
|
}
|
|
236
|
-
function format(label, s) {
|
|
237
|
-
|
|
303
|
+
function format(label, s, loop) {
|
|
304
|
+
const line = `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
|
|
305
|
+
if (loop === void 0) return line;
|
|
306
|
+
return `${line} loopP50=${loop.p50Ms}ms loopP99=${loop.p99Ms}ms loopMax=${loop.maxMs}ms`;
|
|
238
307
|
}
|
|
239
308
|
/**
|
|
240
309
|
* Start the heartbeat. Returns a stop function.
|
|
@@ -242,7 +311,7 @@ function format(label, s) {
|
|
|
242
311
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
243
312
|
* a diagnostic that can crash the process it watches is worse than none.
|
|
244
313
|
*/
|
|
245
|
-
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions) {
|
|
314
|
+
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true) {
|
|
246
315
|
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
247
316
|
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
248
317
|
const triggerMb = reclaimOptions?.triggerMb ?? 1024;
|
|
@@ -251,14 +320,24 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
251
320
|
const escalateRatio = reclaimOptions?.escalateRatio ?? .7;
|
|
252
321
|
const deescalateRatio = reclaimOptions?.deescalateRatio ?? .6;
|
|
253
322
|
let lastReclaimAt = Number.NEGATIVE_INFINITY;
|
|
323
|
+
/** Consecutive passes that fired the instant the floor elapsed. See
|
|
324
|
+
* {@link RECLAIM_STEADY_STATE_PASSES}. */
|
|
325
|
+
let passesAtFloor = 0;
|
|
326
|
+
let steadyStateAnnounced = false;
|
|
254
327
|
const read = () => {
|
|
255
328
|
const limit = reclaimOptions?.heapLimitBytes ?? node_v8.getHeapStatistics().heap_size_limit;
|
|
256
329
|
return buildHeapSample(readMemory(), limit);
|
|
257
330
|
};
|
|
258
331
|
const maybeReclaim = (sample) => {
|
|
259
332
|
if (reclaimOptions === void 0) return;
|
|
260
|
-
if (!shouldReclaim(sample, triggerMb))
|
|
261
|
-
|
|
333
|
+
if (!shouldReclaim(sample, triggerMb)) {
|
|
334
|
+
passesAtFloor = 0;
|
|
335
|
+
steadyStateAnnounced = false;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const sinceLast = now() - lastReclaimAt;
|
|
339
|
+
if (sinceLast < minIntervalMs) return;
|
|
340
|
+
passesAtFloor = sinceLast <= minIntervalMs * 2 ? passesAtFloor + 1 : 1;
|
|
262
341
|
lastReclaimAt = now();
|
|
263
342
|
const startedAt = now();
|
|
264
343
|
try {
|
|
@@ -269,6 +348,10 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
269
348
|
}
|
|
270
349
|
const after = read();
|
|
271
350
|
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`);
|
|
351
|
+
if (passesAtFloor >= 6 && !steadyStateAnnounced) {
|
|
352
|
+
steadyStateAnnounced = true;
|
|
353
|
+
sink.warn(`[mem] reclaim ${label} has run at the floor for ${passesAtFloor} consecutive passes (every ${minIntervalMs}ms) — the stop-the-world is now STEADY STATE, not a rescue. The allocation source is what needs the fix; this pass only bounds its peak.`);
|
|
354
|
+
}
|
|
272
355
|
};
|
|
273
356
|
let mode = "steady";
|
|
274
357
|
let lastLoggedAt = Number.NEGATIVE_INFINITY;
|
|
@@ -297,7 +380,7 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
297
380
|
const due = at - lastLoggedAt >= intervalMs;
|
|
298
381
|
if (mode === "escalated" || due) {
|
|
299
382
|
lastLoggedAt = at;
|
|
300
|
-
const line = format(label, sample);
|
|
383
|
+
const line = format(label, sample, loopDelay?.read());
|
|
301
384
|
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
302
385
|
else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
|
|
303
386
|
else sink.info(line);
|
|
@@ -309,11 +392,13 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
309
392
|
const timer = setInterval(tick, probeIntervalMs);
|
|
310
393
|
timer.unref?.();
|
|
311
394
|
tick();
|
|
395
|
+
if (announceCeilingOrigin && heapCeilingOrigin(execArgv) === "v8-default") sink.info(`[mem] ${label} heap ceiling is V8's DEFAULT (${read().heapLimitMb}MB) — no --max-old-space-size on argv. Nothing here CHOSE that number; it is derived from host RAM and moves with it.`);
|
|
312
396
|
let stopped = false;
|
|
313
397
|
return () => {
|
|
314
398
|
if (stopped) return;
|
|
315
399
|
stopped = true;
|
|
316
400
|
clearInterval(timer);
|
|
401
|
+
loopDelay?.stop();
|
|
317
402
|
};
|
|
318
403
|
}
|
|
319
404
|
/**
|
|
@@ -362,13 +447,13 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
|
|
|
362
447
|
function startRunnerHeapWatch(options) {
|
|
363
448
|
if (options.heapProfile !== "heavy") return void 0;
|
|
364
449
|
const intervalMs = options.intervalMs ?? 3e5;
|
|
365
|
-
if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs);
|
|
450
|
+
if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs, void 0, void 0, void 0, false);
|
|
366
451
|
let reclaimOptions = options.reclaimOptions;
|
|
367
452
|
if (reclaimOptions === void 0) {
|
|
368
453
|
const reclaimer = createV8Reclaimer();
|
|
369
454
|
reclaimOptions = reclaimer === void 0 ? void 0 : { reclaim: reclaimer };
|
|
370
455
|
}
|
|
371
|
-
return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions);
|
|
456
|
+
return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions, void 0, void 0, false);
|
|
372
457
|
}
|
|
373
458
|
//#endregion
|
|
374
459
|
//#region src/kernel/moleculer/addon-data-plane-facility.ts
|