@camstack/system 1.0.5 → 1.0.7
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 +38 -21
- package/dist/addon-runner.mjs +18 -2
- package/dist/addon-utils.d.ts +20 -0
- package/dist/addon-utils.js +11 -0
- package/dist/addon-utils.mjs +3 -0
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +8 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +50 -3
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +50 -3
- package/dist/custom-action-registry-BEXwC-oo.mjs +38 -0
- package/dist/custom-action-registry-vLYEFTtv.js +43 -0
- package/dist/index.js +48 -706
- package/dist/index.mjs +18 -676
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -1
- package/dist/kernel/moleculer/readiness-context.d.ts +2 -1
- package/dist/{manifest-python-deps-CXbKrOdk.mjs → manifest-python-deps-CoJXeb9u.mjs} +2 -39
- package/dist/{manifest-python-deps-B4BmMoGT.js → manifest-python-deps-eBDj5HEY.js} +29 -72
- package/dist/model-download-service-C7AjBsX9.mjs +668 -0
- package/dist/model-download-service-JtVQtbb6.js +752 -0
- package/dist/process/resource-monitor.d.ts +9 -0
- package/dist/{resource-monitor-ClDGFyf6.mjs → resource-monitor-BkP504Vq.mjs} +20 -1
- package/dist/{resource-monitor-IIEanuJt.js → resource-monitor-DNNomR-i.js} +21 -1
- package/package.json +6 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
2
|
+
const require_manifest_python_deps = require("./manifest-python-deps-eBDj5HEY.js");
|
|
3
|
+
const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
|
|
3
4
|
let node_fs = require("node:fs");
|
|
4
5
|
node_fs = require_chunk.__toESM(node_fs);
|
|
5
6
|
let node_path = require("node:path");
|
|
6
7
|
node_path = require_chunk.__toESM(node_path);
|
|
7
|
-
let
|
|
8
|
+
let _camstack_types_addon = require("@camstack/types/addon");
|
|
8
9
|
let node_url = require("node:url");
|
|
10
|
+
let node_v8 = require("node:v8");
|
|
11
|
+
node_v8 = require_chunk.__toESM(node_v8);
|
|
9
12
|
let node_module = require("node:module");
|
|
10
13
|
//#region src/kernel/moleculer/worker-device-restore.ts
|
|
11
14
|
/**
|
|
@@ -41,7 +44,7 @@ async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNo
|
|
|
41
44
|
return;
|
|
42
45
|
}
|
|
43
46
|
const shared = context.kernel?.readinessRegistry ?? null;
|
|
44
|
-
const registry = shared ?? new
|
|
47
|
+
const registry = shared ?? new _camstack_types_addon.ReadinessRegistry({
|
|
45
48
|
eventBus: bus,
|
|
46
49
|
sourceNodeId,
|
|
47
50
|
logger: context.logger
|
|
@@ -54,13 +57,13 @@ async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNo
|
|
|
54
57
|
}, { timeoutMs: 6e4 });
|
|
55
58
|
log?.debug?.(`[worker-restore] "${addonId}": device-manager READY`);
|
|
56
59
|
} catch (err) {
|
|
57
|
-
if (err instanceof
|
|
60
|
+
if (err instanceof _camstack_types_addon.ReadinessTimeoutError) {
|
|
58
61
|
context.logger?.warn?.(`[worker-restore] device-manager not ready within ${err.waitedMs}ms — skipping for "${addonId}"`);
|
|
59
62
|
return;
|
|
60
63
|
}
|
|
61
64
|
throw err;
|
|
62
65
|
} finally {
|
|
63
|
-
if (!shared && registry instanceof
|
|
66
|
+
if (!shared && registry instanceof _camstack_types_addon.ReadinessRegistry) registry.close();
|
|
64
67
|
}
|
|
65
68
|
try {
|
|
66
69
|
const deviceManager = Reflect.get(api, "deviceManager");
|
|
@@ -86,7 +89,7 @@ async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNo
|
|
|
86
89
|
await restoreFn.call(addon, savedDevices);
|
|
87
90
|
log?.info?.(`[worker-restore] "${addonId}": restored ${savedDevices.length} device(s)`);
|
|
88
91
|
} catch (err) {
|
|
89
|
-
log?.warn?.(`[worker-restore] "${addonId}": restoreDevices threw: ${(0,
|
|
92
|
+
log?.warn?.(`[worker-restore] "${addonId}": restoreDevices threw: ${(0, _camstack_types_addon.errMsg)(err)}`);
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
//#endregion
|
|
@@ -272,11 +275,11 @@ function createChildAddonCallDispatch(deps) {
|
|
|
272
275
|
* every loaded addon before closing the UDS channel.
|
|
273
276
|
*/
|
|
274
277
|
process.on("uncaughtException", (err) => {
|
|
275
|
-
console.error(`[addon-runner CRASH] uncaughtException: ${(0,
|
|
278
|
+
console.error(`[addon-runner CRASH] uncaughtException: ${(0, _camstack_types_addon.errMsg)(err)}\n${err instanceof Error ? err.stack ?? "" : ""}`);
|
|
276
279
|
process.exit(1);
|
|
277
280
|
});
|
|
278
281
|
process.on("unhandledRejection", (reason) => {
|
|
279
|
-
console.error(`[addon-runner CRASH] unhandledRejection: ${(0,
|
|
282
|
+
console.error(`[addon-runner CRASH] unhandledRejection: ${(0, _camstack_types_addon.errMsg)(reason)}\n${reason instanceof Error ? reason.stack ?? "" : ""}`);
|
|
280
283
|
});
|
|
281
284
|
process.on("exit", (code) => {
|
|
282
285
|
console.error(`[addon-runner EXIT] code=${String(code)} nodeId=${process.env["CAMSTACK_RUNNER_ID"] ?? "?"}`);
|
|
@@ -297,7 +300,7 @@ function parseAddonsSpec(raw) {
|
|
|
297
300
|
try {
|
|
298
301
|
parsed = JSON.parse(raw);
|
|
299
302
|
} catch (err) {
|
|
300
|
-
throw new Error(`[addon-runner] CAMSTACK_RUNNER_ADDONS is not valid JSON: ${(0,
|
|
303
|
+
throw new Error(`[addon-runner] CAMSTACK_RUNNER_ADDONS is not valid JSON: ${(0, _camstack_types_addon.errMsg)(err)}`, { cause: err });
|
|
301
304
|
}
|
|
302
305
|
if (!Array.isArray(parsed)) throw new Error(`[addon-runner] CAMSTACK_RUNNER_ADDONS must be a JSON array`);
|
|
303
306
|
const specs = [];
|
|
@@ -370,7 +373,7 @@ async function main() {
|
|
|
370
373
|
try {
|
|
371
374
|
specs = parseAddonsSpec(addonsJson);
|
|
372
375
|
} catch (err) {
|
|
373
|
-
console.error((0,
|
|
376
|
+
console.error((0, _camstack_types_addon.errMsg)(err));
|
|
374
377
|
process.exit(1);
|
|
375
378
|
}
|
|
376
379
|
if (specs.length === 0) {
|
|
@@ -393,7 +396,7 @@ async function main() {
|
|
|
393
396
|
AddonClass
|
|
394
397
|
});
|
|
395
398
|
} catch (err) {
|
|
396
|
-
console.error(`[addon-runner] Failed to load "${spec.addonId}": ${(0,
|
|
399
|
+
console.error(`[addon-runner] Failed to load "${spec.addonId}": ${(0, _camstack_types_addon.errMsg)(err)}`);
|
|
397
400
|
process.exit(1);
|
|
398
401
|
}
|
|
399
402
|
let storageProvider;
|
|
@@ -463,7 +466,7 @@ async function main() {
|
|
|
463
466
|
* removed `custom.<action>` Moleculer action). Constructed up front so the
|
|
464
467
|
* addon-call handler can close over it at client-construction time.
|
|
465
468
|
*/
|
|
466
|
-
const customActionRegistry = new
|
|
469
|
+
const customActionRegistry = new require_custom_action_registry.CustomActionRegistry();
|
|
467
470
|
let shuttingDown = false;
|
|
468
471
|
const udsChildClient = new require_manifest_python_deps.LocalChildClient({
|
|
469
472
|
nodeId: parentNodeId,
|
|
@@ -488,12 +491,12 @@ async function main() {
|
|
|
488
491
|
try {
|
|
489
492
|
await udsChildClient.start();
|
|
490
493
|
} catch (err) {
|
|
491
|
-
console.error(`[addon-runner] "${nodeId}" UDS connect failed — exiting: ${(0,
|
|
494
|
+
console.error(`[addon-runner] "${nodeId}" UDS connect failed — exiting: ${(0, _camstack_types_addon.errMsg)(err)}`);
|
|
492
495
|
process.exit(1);
|
|
493
496
|
}
|
|
494
497
|
runnerLog.info("UDS child transport connected", { meta: { childId: runnerId } });
|
|
495
498
|
if (storageProvider) runnerLog.info(`"${nodeId}" storage provider rooted at ${dataDir}`);
|
|
496
|
-
else runnerLog.warn(`"${nodeId}" failed to load FilesystemStorageProvider`, { meta: { error: (0,
|
|
499
|
+
else runnerLog.warn(`"${nodeId}" failed to load FilesystemStorageProvider`, { meta: { error: (0, _camstack_types_addon.errMsg)(storageProviderError) } });
|
|
497
500
|
const refreshChildCaps = (reason) => {
|
|
498
501
|
const freshDescriptors = buildChildCapDescriptors(runnerManifest, require_manifest_python_deps.getWorkerNativeCapSnapshot());
|
|
499
502
|
udsChildClient.updateCaps(freshDescriptors).catch((err) => {
|
|
@@ -523,12 +526,12 @@ async function main() {
|
|
|
523
526
|
const pkgRaw = JSON.parse(node_fs.readFileSync(node_path.join(spec.addonDir, "package.json"), "utf-8"));
|
|
524
527
|
await require_manifest_python_deps.installManifestNativeDeps(spec.addonDir, pkgRaw, context.logger);
|
|
525
528
|
} catch (nativeErr) {
|
|
526
|
-
console.error(`[addon-runner] native deps install failed for "${spec.addonId}": ${(0,
|
|
529
|
+
console.error(`[addon-runner] native deps install failed for "${spec.addonId}": ${(0, _camstack_types_addon.errMsg)(nativeErr)}`);
|
|
527
530
|
process.exit(1);
|
|
528
531
|
}
|
|
529
532
|
await require_manifest_python_deps.installManifestPythonDeps(declaration, spec.addonDir, context.deps, context.logger);
|
|
530
533
|
const registeredProviders = /* @__PURE__ */ new Map();
|
|
531
|
-
const initResult = (0,
|
|
534
|
+
const initResult = (0, _camstack_types_addon.normalizeAddonInitResult)(await addon.initialize(context));
|
|
532
535
|
for (const reg of initResult?.providers ?? []) {
|
|
533
536
|
const capName = reg.capability.name;
|
|
534
537
|
registeredProviders.set(capName, reg.provider);
|
|
@@ -564,7 +567,7 @@ async function main() {
|
|
|
564
567
|
initResult: initResult ?? null
|
|
565
568
|
});
|
|
566
569
|
} catch (err) {
|
|
567
|
-
console.error(`[addon-runner] Init failed for "${spec.addonId}": ${(0,
|
|
570
|
+
console.error(`[addon-runner] Init failed for "${spec.addonId}": ${(0, _camstack_types_addon.errMsg)(err)}`);
|
|
568
571
|
process.exit(1);
|
|
569
572
|
}
|
|
570
573
|
if (deviceRestorePromises.length > 0 && !shuttingDown) await Promise.allSettled(deviceRestorePromises);
|
|
@@ -574,7 +577,7 @@ async function main() {
|
|
|
574
577
|
if (hubReachableFired) return;
|
|
575
578
|
hubReachableFired = true;
|
|
576
579
|
for (const entry of loaded) if (typeof entry.addon.onHubReachable === "function") Promise.resolve(entry.addon.onHubReachable()).catch((err) => {
|
|
577
|
-
runnerLog.error(`"${entry.addonId}" onHubReachable() threw`, { meta: { error: (0,
|
|
580
|
+
runnerLog.error(`"${entry.addonId}" onHubReachable() threw`, { meta: { error: (0, _camstack_types_addon.errMsg)(err) } });
|
|
578
581
|
});
|
|
579
582
|
};
|
|
580
583
|
udsChildClient.onConnected(() => {
|
|
@@ -584,6 +587,20 @@ async function main() {
|
|
|
584
587
|
fireHubReachableOnce();
|
|
585
588
|
runnerLog.info(`"${nodeId}" started — ${loaded.length} addon(s) ready (UDS connected)`);
|
|
586
589
|
process.stdout.write(`READY:${nodeId}\n`);
|
|
590
|
+
process.on("SIGUSR2", () => {
|
|
591
|
+
const MB = (x) => Math.round(x / 1048576 * 10) / 10;
|
|
592
|
+
try {
|
|
593
|
+
const mu = process.memoryUsage();
|
|
594
|
+
const spaces = node_v8.getHeapSpaceStatistics().filter((s) => s.space_used_size > 0).map((s) => `${s.space_name}=${MB(s.space_used_size)}`).join(" ");
|
|
595
|
+
const addons = loaded.map((e) => e.addonId).join(",");
|
|
596
|
+
runnerLog.info(`[mem] node="${nodeId}" addons=[${addons}] rss=${MB(mu.rss)} heapUsed=${MB(mu.heapUsed)} heapTotal=${MB(mu.heapTotal)} external=${MB(mu.external)} arrayBuffers=${MB(mu.arrayBuffers)} | ${spaces}`);
|
|
597
|
+
const snapPath = `/tmp/heap-${nodeId.replace(/[^\w.-]/g, "_")}.heapsnapshot`;
|
|
598
|
+
node_v8.writeHeapSnapshot(snapPath);
|
|
599
|
+
runnerLog.info(`[mem] node="${nodeId}" heap snapshot written -> ${snapPath}`);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
runnerLog.warn(`[mem] snapshot failed: ${(0, _camstack_types_addon.errMsg)(err)}`);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
587
604
|
const shutdown = async () => {
|
|
588
605
|
shuttingDown = true;
|
|
589
606
|
require_manifest_python_deps.setWorkerNativeCapsChangeListener(null);
|
|
@@ -595,12 +612,12 @@ async function main() {
|
|
|
595
612
|
for (const entry of loaded) try {
|
|
596
613
|
await entry.addon.shutdown();
|
|
597
614
|
} catch (err) {
|
|
598
|
-
runnerLog.error(`"${entry.addonId}" shutdown threw`, { meta: { error: (0,
|
|
615
|
+
runnerLog.error(`"${entry.addonId}" shutdown threw`, { meta: { error: (0, _camstack_types_addon.errMsg)(err) } });
|
|
599
616
|
}
|
|
600
617
|
try {
|
|
601
618
|
await udsChildClient.close();
|
|
602
619
|
} catch (err) {
|
|
603
|
-
runnerLog.warn("UDS child transport close threw during shutdown", { meta: { error: (0,
|
|
620
|
+
runnerLog.warn("UDS child transport close threw during shutdown", { meta: { error: (0, _camstack_types_addon.errMsg)(err) } });
|
|
604
621
|
}
|
|
605
622
|
process.exit(0);
|
|
606
623
|
};
|
|
@@ -619,7 +636,7 @@ async function main() {
|
|
|
619
636
|
}, 5e3);
|
|
620
637
|
}
|
|
621
638
|
main().catch((err) => {
|
|
622
|
-
console.error(`[addon-runner] Fatal error: ${(0,
|
|
639
|
+
console.error(`[addon-runner] Fatal error: ${(0, _camstack_types_addon.errMsg)(err)}`);
|
|
623
640
|
process.exit(1);
|
|
624
641
|
});
|
|
625
642
|
//#endregion
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { $ as setWorkerNativeCapsChangeListener, A as createUdsLoggerWithControl, L as LocalChildClient, X as getWorkerNativeCapProvider, Z as getWorkerNativeCapSnapshot,
|
|
1
|
+
import { $ as setWorkerNativeCapsChangeListener, A as createUdsLoggerWithControl, L as LocalChildClient, X as getWorkerNativeCapProvider, Z as getWorkerNativeCapSnapshot, i as createUdsAddonContext, mt as resolveAddonClass, pt as installManifestNativeDeps, t as installManifestPythonDeps, tt as validateProviderRegistrations } from "./manifest-python-deps-CoJXeb9u.mjs";
|
|
2
|
+
import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
|
|
2
3
|
import { register } from "node:module";
|
|
3
4
|
import * as fs from "node:fs";
|
|
4
5
|
import * as path$1 from "node:path";
|
|
5
|
-
import { ReadinessRegistry, ReadinessTimeoutError, errMsg, normalizeAddonInitResult } from "@camstack/types";
|
|
6
|
+
import { ReadinessRegistry, ReadinessTimeoutError, errMsg, normalizeAddonInitResult } from "@camstack/types/addon";
|
|
6
7
|
import { pathToFileURL } from "node:url";
|
|
8
|
+
import * as v8 from "node:v8";
|
|
7
9
|
//#region src/kernel/moleculer/worker-device-restore.ts
|
|
8
10
|
/**
|
|
9
11
|
* Worker-side device restore helper.
|
|
@@ -581,6 +583,20 @@ async function main() {
|
|
|
581
583
|
fireHubReachableOnce();
|
|
582
584
|
runnerLog.info(`"${nodeId}" started — ${loaded.length} addon(s) ready (UDS connected)`);
|
|
583
585
|
process.stdout.write(`READY:${nodeId}\n`);
|
|
586
|
+
process.on("SIGUSR2", () => {
|
|
587
|
+
const MB = (x) => Math.round(x / 1048576 * 10) / 10;
|
|
588
|
+
try {
|
|
589
|
+
const mu = process.memoryUsage();
|
|
590
|
+
const spaces = v8.getHeapSpaceStatistics().filter((s) => s.space_used_size > 0).map((s) => `${s.space_name}=${MB(s.space_used_size)}`).join(" ");
|
|
591
|
+
const addons = loaded.map((e) => e.addonId).join(",");
|
|
592
|
+
runnerLog.info(`[mem] node="${nodeId}" addons=[${addons}] rss=${MB(mu.rss)} heapUsed=${MB(mu.heapUsed)} heapTotal=${MB(mu.heapTotal)} external=${MB(mu.external)} arrayBuffers=${MB(mu.arrayBuffers)} | ${spaces}`);
|
|
593
|
+
const snapPath = `/tmp/heap-${nodeId.replace(/[^\w.-]/g, "_")}.heapsnapshot`;
|
|
594
|
+
v8.writeHeapSnapshot(snapPath);
|
|
595
|
+
runnerLog.info(`[mem] node="${nodeId}" heap snapshot written -> ${snapPath}`);
|
|
596
|
+
} catch (err) {
|
|
597
|
+
runnerLog.warn(`[mem] snapshot failed: ${errMsg(err)}`);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
584
600
|
const shutdown = async () => {
|
|
585
601
|
shuttingDown = true;
|
|
586
602
|
setWorkerNativeCapsChangeListener(null);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight addon-facing utility surface for `@camstack/system`.
|
|
3
|
+
*
|
|
4
|
+
* Importing the root barrel (`@camstack/system`) loads the ENTIRE framework —
|
|
5
|
+
* the kernel (moleculer) + every builtin addon (sqlite-storage, winston-logging,
|
|
6
|
+
* device-manager, …) — ~64MB of V8 heap PER process, even when an addon only
|
|
7
|
+
* needs a model-download or data-plane helper. Node has no runtime tree-shaking
|
|
8
|
+
* and the package is host-provided (loaded as the prebuilt barrel), so every
|
|
9
|
+
* addon that imported one util from the barrel paid the full ~64MB — multiplied
|
|
10
|
+
* across every forked pipeline runner.
|
|
11
|
+
*
|
|
12
|
+
* This subpath re-exports ONLY the addon-facing helpers, each from its DEEP,
|
|
13
|
+
* kernel-free source module, so importing it costs a few MB instead of ~64MB.
|
|
14
|
+
*
|
|
15
|
+
* See memory: camstack_runner_ram_and_build_rootcause.
|
|
16
|
+
*/
|
|
17
|
+
export { downloadFile, ensureModel, isModelDownloaded, deleteModelFromDisk, } from './download/model-downloader.js';
|
|
18
|
+
export { ModelDownloadService } from './download/model-download-service.js';
|
|
19
|
+
export { createFileDataPlaneHandler } from './http/file-data-plane.js';
|
|
20
|
+
export { CustomActionRegistry } from './kernel/custom-action-registry.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("./chunk-Cek0wNdY.js");
|
|
3
|
+
const require_model_download_service = require("./model-download-service-JtVQtbb6.js");
|
|
4
|
+
const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
|
|
5
|
+
exports.CustomActionRegistry = require_custom_action_registry.CustomActionRegistry;
|
|
6
|
+
exports.ModelDownloadService = require_model_download_service.ModelDownloadService;
|
|
7
|
+
exports.createFileDataPlaneHandler = require_model_download_service.createFileDataPlaneHandler;
|
|
8
|
+
exports.deleteModelFromDisk = require_model_download_service.deleteModelFromDisk;
|
|
9
|
+
exports.downloadFile = require_model_download_service.downloadFile;
|
|
10
|
+
exports.ensureModel = require_model_download_service.ensureModel;
|
|
11
|
+
exports.isModelDownloaded = require_model_download_service.isModelDownloaded;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as ensureModel, c as isModelDownloaded, l as createFileDataPlaneHandler, n as deleteModelFromDisk, r as downloadFile, t as ModelDownloadService } from "./model-download-service-C7AjBsX9.mjs";
|
|
2
|
+
import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
|
|
3
|
+
export { CustomActionRegistry, ModelDownloadService, createFileDataPlaneHandler, deleteModelFromDisk, downloadFile, ensureModel, isModelDownloaded };
|
|
@@ -66,6 +66,14 @@ export default class NativeMetricsAddon extends BaseAddon<NativeMetricsConfig> {
|
|
|
66
66
|
* the dedicated `$process.restart` action, not this kill API.
|
|
67
67
|
*/
|
|
68
68
|
private killProcess;
|
|
69
|
+
/**
|
|
70
|
+
* Ask the addon's forked runner to write a V8 heap snapshot (SIGUSR2 → the
|
|
71
|
+
* runner's diagnostic handler in addon-runner.ts), for deep per-addon memory
|
|
72
|
+
* attribution. Resolves the addon's worker pid from the kernel `$process.list`
|
|
73
|
+
* and signals it; the runner writes `/tmp/heap-<sanitized nodeId>.heapsnapshot`
|
|
74
|
+
* and logs its memoryUsage + heap-space breakdown. '$hub' targets this process.
|
|
75
|
+
*/
|
|
76
|
+
private dumpHeapSnapshot;
|
|
69
77
|
/** Raw `ps` scan returning every pid + command + resource stats. */
|
|
70
78
|
private runPs;
|
|
71
79
|
protected globalSettingsSchema(): import('@camstack/types').ConfigUISchema;
|
|
@@ -123,13 +123,15 @@ var NativeMetricsProvider = class {
|
|
|
123
123
|
return getCpuTemperatureInternal();
|
|
124
124
|
}
|
|
125
125
|
async getProcessStats(input) {
|
|
126
|
-
const { getPidStats } = await Promise.resolve().then(() => require("../../resource-monitor-
|
|
126
|
+
const { getPidStats } = await Promise.resolve().then(() => require("../../resource-monitor-DNNomR-i.js")).then((n) => n.resource_monitor_exports);
|
|
127
127
|
const raw = await getPidStats(input.pids);
|
|
128
128
|
const result = [];
|
|
129
129
|
for (const [pid, s] of raw) result.push({
|
|
130
130
|
pid,
|
|
131
131
|
cpu: s.cpu,
|
|
132
|
-
memory: s.memory
|
|
132
|
+
memory: s.memory,
|
|
133
|
+
privateBytes: s.privateBytes,
|
|
134
|
+
sharedBytes: s.sharedBytes
|
|
133
135
|
});
|
|
134
136
|
return result;
|
|
135
137
|
}
|
|
@@ -596,7 +598,8 @@ var NativeMetricsAddon = class extends _camstack_types.BaseAddon {
|
|
|
596
598
|
listAddonInstances: () => this.listAddonInstances(),
|
|
597
599
|
getAddonStats: (params) => this.getAddonStats(params.addonId),
|
|
598
600
|
listNodeProcesses: () => this.listNodeProcesses(),
|
|
599
|
-
killProcess: (params) => this.killProcess(params)
|
|
601
|
+
killProcess: (params) => this.killProcess(params),
|
|
602
|
+
dumpHeapSnapshot: (params) => this.dumpHeapSnapshot(params)
|
|
600
603
|
};
|
|
601
604
|
this.snapshotTimer = setInterval(() => this.emitMetricsSnapshots(), METRICS_SNAPSHOT_INTERVAL_MS);
|
|
602
605
|
return [{
|
|
@@ -845,6 +848,50 @@ var NativeMetricsAddon = class extends _camstack_types.BaseAddon {
|
|
|
845
848
|
};
|
|
846
849
|
}
|
|
847
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* Ask the addon's forked runner to write a V8 heap snapshot (SIGUSR2 → the
|
|
853
|
+
* runner's diagnostic handler in addon-runner.ts), for deep per-addon memory
|
|
854
|
+
* attribution. Resolves the addon's worker pid from the kernel `$process.list`
|
|
855
|
+
* and signals it; the runner writes `/tmp/heap-<sanitized nodeId>.heapsnapshot`
|
|
856
|
+
* and logs its memoryUsage + heap-space breakdown. '$hub' targets this process.
|
|
857
|
+
*/
|
|
858
|
+
async dumpHeapSnapshot(input) {
|
|
859
|
+
const sanitize = (nodeId) => nodeId.replace(/[^\w.-]/g, "_");
|
|
860
|
+
let pid;
|
|
861
|
+
let nodeId;
|
|
862
|
+
if (input.addonId === "$hub") {
|
|
863
|
+
pid = process.pid;
|
|
864
|
+
nodeId = this.ctx.kernel.cluster?.broker?.nodeID ?? "$hub";
|
|
865
|
+
} else {
|
|
866
|
+
const target = (await this.listWorkerInstances()).find((w) => w.addonId === input.addonId);
|
|
867
|
+
if (!target) return {
|
|
868
|
+
success: false,
|
|
869
|
+
reason: `no runner process for addon '${input.addonId}'`
|
|
870
|
+
};
|
|
871
|
+
pid = target.pid;
|
|
872
|
+
nodeId = target.nodeId;
|
|
873
|
+
}
|
|
874
|
+
const path = `/tmp/heap-${sanitize(nodeId)}.heapsnapshot`;
|
|
875
|
+
try {
|
|
876
|
+
process.kill(pid, "SIGUSR2");
|
|
877
|
+
this.ctx.logger.info("Requested heap snapshot from runner", { meta: {
|
|
878
|
+
addonId: input.addonId,
|
|
879
|
+
pid,
|
|
880
|
+
path
|
|
881
|
+
} });
|
|
882
|
+
return {
|
|
883
|
+
success: true,
|
|
884
|
+
pid,
|
|
885
|
+
path
|
|
886
|
+
};
|
|
887
|
+
} catch (err) {
|
|
888
|
+
return {
|
|
889
|
+
success: false,
|
|
890
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
891
|
+
pid
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
}
|
|
848
895
|
/** Raw `ps` scan returning every pid + command + resource stats. */
|
|
849
896
|
async runPs() {
|
|
850
897
|
try {
|
|
@@ -116,13 +116,15 @@ var NativeMetricsProvider = class {
|
|
|
116
116
|
return getCpuTemperatureInternal();
|
|
117
117
|
}
|
|
118
118
|
async getProcessStats(input) {
|
|
119
|
-
const { getPidStats } = await import("../../resource-monitor-
|
|
119
|
+
const { getPidStats } = await import("../../resource-monitor-BkP504Vq.mjs").then((n) => n.r);
|
|
120
120
|
const raw = await getPidStats(input.pids);
|
|
121
121
|
const result = [];
|
|
122
122
|
for (const [pid, s] of raw) result.push({
|
|
123
123
|
pid,
|
|
124
124
|
cpu: s.cpu,
|
|
125
|
-
memory: s.memory
|
|
125
|
+
memory: s.memory,
|
|
126
|
+
privateBytes: s.privateBytes,
|
|
127
|
+
sharedBytes: s.sharedBytes
|
|
126
128
|
});
|
|
127
129
|
return result;
|
|
128
130
|
}
|
|
@@ -589,7 +591,8 @@ var NativeMetricsAddon = class extends BaseAddon {
|
|
|
589
591
|
listAddonInstances: () => this.listAddonInstances(),
|
|
590
592
|
getAddonStats: (params) => this.getAddonStats(params.addonId),
|
|
591
593
|
listNodeProcesses: () => this.listNodeProcesses(),
|
|
592
|
-
killProcess: (params) => this.killProcess(params)
|
|
594
|
+
killProcess: (params) => this.killProcess(params),
|
|
595
|
+
dumpHeapSnapshot: (params) => this.dumpHeapSnapshot(params)
|
|
593
596
|
};
|
|
594
597
|
this.snapshotTimer = setInterval(() => this.emitMetricsSnapshots(), METRICS_SNAPSHOT_INTERVAL_MS);
|
|
595
598
|
return [{
|
|
@@ -838,6 +841,50 @@ var NativeMetricsAddon = class extends BaseAddon {
|
|
|
838
841
|
};
|
|
839
842
|
}
|
|
840
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Ask the addon's forked runner to write a V8 heap snapshot (SIGUSR2 → the
|
|
846
|
+
* runner's diagnostic handler in addon-runner.ts), for deep per-addon memory
|
|
847
|
+
* attribution. Resolves the addon's worker pid from the kernel `$process.list`
|
|
848
|
+
* and signals it; the runner writes `/tmp/heap-<sanitized nodeId>.heapsnapshot`
|
|
849
|
+
* and logs its memoryUsage + heap-space breakdown. '$hub' targets this process.
|
|
850
|
+
*/
|
|
851
|
+
async dumpHeapSnapshot(input) {
|
|
852
|
+
const sanitize = (nodeId) => nodeId.replace(/[^\w.-]/g, "_");
|
|
853
|
+
let pid;
|
|
854
|
+
let nodeId;
|
|
855
|
+
if (input.addonId === "$hub") {
|
|
856
|
+
pid = process.pid;
|
|
857
|
+
nodeId = this.ctx.kernel.cluster?.broker?.nodeID ?? "$hub";
|
|
858
|
+
} else {
|
|
859
|
+
const target = (await this.listWorkerInstances()).find((w) => w.addonId === input.addonId);
|
|
860
|
+
if (!target) return {
|
|
861
|
+
success: false,
|
|
862
|
+
reason: `no runner process for addon '${input.addonId}'`
|
|
863
|
+
};
|
|
864
|
+
pid = target.pid;
|
|
865
|
+
nodeId = target.nodeId;
|
|
866
|
+
}
|
|
867
|
+
const path = `/tmp/heap-${sanitize(nodeId)}.heapsnapshot`;
|
|
868
|
+
try {
|
|
869
|
+
process.kill(pid, "SIGUSR2");
|
|
870
|
+
this.ctx.logger.info("Requested heap snapshot from runner", { meta: {
|
|
871
|
+
addonId: input.addonId,
|
|
872
|
+
pid,
|
|
873
|
+
path
|
|
874
|
+
} });
|
|
875
|
+
return {
|
|
876
|
+
success: true,
|
|
877
|
+
pid,
|
|
878
|
+
path
|
|
879
|
+
};
|
|
880
|
+
} catch (err) {
|
|
881
|
+
return {
|
|
882
|
+
success: false,
|
|
883
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
884
|
+
pid
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
}
|
|
841
888
|
/** Raw `ps` scan returning every pid + command + resource stats. */
|
|
842
889
|
async runPs() {
|
|
843
890
|
try {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/kernel/custom-action-registry.ts
|
|
2
|
+
/**
|
|
3
|
+
* CustomActionRegistry — per-process registry of addon custom actions.
|
|
4
|
+
*
|
|
5
|
+
* Populated at boot from each addon's `AddonInitResult.customActions` +
|
|
6
|
+
* `handleCustomAction` handler. Rejects actions declared with scope other
|
|
7
|
+
* than 'system' (today only 'system' is runtime-supported; the descriptor
|
|
8
|
+
* allows future scopes for forward compat).
|
|
9
|
+
*/
|
|
10
|
+
var CustomActionRegistry = class {
|
|
11
|
+
byAddon = /* @__PURE__ */ new Map();
|
|
12
|
+
registerAddon(addonId, catalog, handler) {
|
|
13
|
+
const actions = /* @__PURE__ */ new Map();
|
|
14
|
+
for (const [name, spec] of Object.entries(catalog)) {
|
|
15
|
+
const scope = spec.scope ?? { kind: "system" };
|
|
16
|
+
if (scope.kind !== "system") throw new Error(`custom action '${addonId}.${name}' declared scope '${scope.kind}' — not yet implemented`);
|
|
17
|
+
actions.set(name, {
|
|
18
|
+
spec,
|
|
19
|
+
handler: (input) => handler(name, input)
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
this.byAddon.set(addonId, actions);
|
|
23
|
+
}
|
|
24
|
+
unregisterAddon(addonId) {
|
|
25
|
+
this.byAddon.delete(addonId);
|
|
26
|
+
}
|
|
27
|
+
resolve(addonId, action) {
|
|
28
|
+
return this.byAddon.get(addonId)?.get(action) ?? null;
|
|
29
|
+
}
|
|
30
|
+
listActions(addonId) {
|
|
31
|
+
return [...this.byAddon.get(addonId)?.keys() ?? []];
|
|
32
|
+
}
|
|
33
|
+
listAddons() {
|
|
34
|
+
return [...this.byAddon.keys()];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { CustomActionRegistry as t };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/kernel/custom-action-registry.ts
|
|
2
|
+
/**
|
|
3
|
+
* CustomActionRegistry — per-process registry of addon custom actions.
|
|
4
|
+
*
|
|
5
|
+
* Populated at boot from each addon's `AddonInitResult.customActions` +
|
|
6
|
+
* `handleCustomAction` handler. Rejects actions declared with scope other
|
|
7
|
+
* than 'system' (today only 'system' is runtime-supported; the descriptor
|
|
8
|
+
* allows future scopes for forward compat).
|
|
9
|
+
*/
|
|
10
|
+
var CustomActionRegistry = class {
|
|
11
|
+
byAddon = /* @__PURE__ */ new Map();
|
|
12
|
+
registerAddon(addonId, catalog, handler) {
|
|
13
|
+
const actions = /* @__PURE__ */ new Map();
|
|
14
|
+
for (const [name, spec] of Object.entries(catalog)) {
|
|
15
|
+
const scope = spec.scope ?? { kind: "system" };
|
|
16
|
+
if (scope.kind !== "system") throw new Error(`custom action '${addonId}.${name}' declared scope '${scope.kind}' — not yet implemented`);
|
|
17
|
+
actions.set(name, {
|
|
18
|
+
spec,
|
|
19
|
+
handler: (input) => handler(name, input)
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
this.byAddon.set(addonId, actions);
|
|
23
|
+
}
|
|
24
|
+
unregisterAddon(addonId) {
|
|
25
|
+
this.byAddon.delete(addonId);
|
|
26
|
+
}
|
|
27
|
+
resolve(addonId, action) {
|
|
28
|
+
return this.byAddon.get(addonId)?.get(action) ?? null;
|
|
29
|
+
}
|
|
30
|
+
listActions(addonId) {
|
|
31
|
+
return [...this.byAddon.get(addonId)?.keys() ?? []];
|
|
32
|
+
}
|
|
33
|
+
listAddons() {
|
|
34
|
+
return [...this.byAddon.keys()];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
Object.defineProperty(exports, "CustomActionRegistry", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function() {
|
|
41
|
+
return CustomActionRegistry;
|
|
42
|
+
}
|
|
43
|
+
});
|