@camstack/system 1.1.13 → 1.1.15

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.
@@ -1,4 +1,4 @@
1
- import { InferProvider, deviceManagerCapability, AddonContext } from '@camstack/types';
1
+ import { AddonContext, deviceManagerCapability, InferProvider } from '@camstack/types';
2
2
  import { DeviceManagerSettings, DeviceMetaStore } from './device-meta-store.js';
3
3
  import { ProviderContext } from './device-provider-context.js';
4
4
  type IDeviceManagerProvider = InferProvider<typeof deviceManagerCapability>;
package/dist/index.js CHANGED
@@ -92343,6 +92343,76 @@ async function getPidStats$1(pids) {
92343
92343
  });
92344
92344
  });
92345
92345
  }
92346
+ /**
92347
+ * Resolve the declared heap profile for one addon by reading its package.json manifest
92348
+ * (`camstack.addons[].execution.heapProfile`). `addonDir` points at the addon's built `dist`, so the
92349
+ * manifest is one level up; we also try `addonDir` itself for layouts where it IS the package root.
92350
+ * Result is cached per (dir,id) — spawns are infrequent but a runner may re-spawn on crash/restart.
92351
+ * Returns `undefined` when the manifest is unreadable or declares no profile → treated as light.
92352
+ */
92353
+ var heapProfileCache = /* @__PURE__ */ new Map();
92354
+ function readAddonHeapProfile(spec) {
92355
+ const cacheKey = `${spec.addonDir}::${spec.addonId}`;
92356
+ const cached = heapProfileCache.get(cacheKey);
92357
+ if (cached !== void 0 || heapProfileCache.has(cacheKey)) return cached;
92358
+ let profile;
92359
+ for (const manifestPath of [node_path.join(spec.addonDir, "package.json"), node_path.join(node_path.dirname(spec.addonDir), "package.json")]) try {
92360
+ const raw = node_fs.readFileSync(manifestPath, "utf8");
92361
+ const parsed = JSON.parse(raw);
92362
+ profile = extractHeapProfile(parsed, spec.addonId);
92363
+ if (profile !== void 0) break;
92364
+ if (manifestHasAddon(parsed, spec.addonId)) break;
92365
+ } catch {}
92366
+ heapProfileCache.set(cacheKey, profile);
92367
+ return profile;
92368
+ }
92369
+ function manifestHasAddon(parsed, addonId) {
92370
+ return readManifestAddons(parsed).some((a) => a.id === addonId);
92371
+ }
92372
+ function extractHeapProfile(parsed, addonId) {
92373
+ const value = readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.heapProfile;
92374
+ return value === "heavy" || value === "light" ? value : void 0;
92375
+ }
92376
+ function readManifestAddons(parsed) {
92377
+ if (typeof parsed !== "object" || parsed === null) return [];
92378
+ const camstack = parsed.camstack;
92379
+ if (typeof camstack !== "object" || camstack === null) return [];
92380
+ const addons = camstack.addons;
92381
+ return Array.isArray(addons) ? addons : [];
92382
+ }
92383
+ /**
92384
+ * A runner is heavy if ANY co-located addon declares `heapProfile: 'heavy'` in its manifest.
92385
+ * Everything else (the un-annotated default) is a light control-plane runner. No hard-coded names.
92386
+ */
92387
+ function isHeavyRunner(addons) {
92388
+ return addons.some((a) => readAddonHeapProfile(a) === "heavy");
92389
+ }
92390
+ /**
92391
+ * Per-placement V8 heap flags (Block D). LIGHT runners cap their young generation
92392
+ * (`--max-semi-space-size`) to reclaim V8's over-reservation — a measured ~15 MB/runner win.
92393
+ * HEAVY runners keep V8 defaults. The old-space cap is opt-in via env (unset = uncapped = no OOM
92394
+ * risk) so it can be enabled after before/after measurement on the real cluster.
92395
+ *
92396
+ * Kill-switch: `CAMSTACK_RUNNER_HEAP_TUNING=off` disables all flags (recover without a rebuild).
92397
+ * Overrides: `CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB` (default 2), `CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`
92398
+ * (default unset = no old-space cap).
92399
+ */
92400
+ function runnerHeapFlags(addons) {
92401
+ if (process.env["CAMSTACK_RUNNER_HEAP_TUNING"] === "off") return [];
92402
+ if (isHeavyRunner(addons)) return [];
92403
+ const semiMb = positiveIntEnv("CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB", 2);
92404
+ const oldMb = positiveIntEnv("CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB", 0);
92405
+ const flags = [];
92406
+ if (semiMb > 0) flags.push(`--max-semi-space-size=${semiMb}`);
92407
+ if (oldMb > 0) flags.push(`--max-old-space-size=${oldMb}`);
92408
+ return flags;
92409
+ }
92410
+ function positiveIntEnv(name, fallback) {
92411
+ const raw = process.env[name];
92412
+ if (raw === void 0) return fallback;
92413
+ const n = Number.parseInt(raw, 10);
92414
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
92415
+ }
92346
92416
  var MAX_BACKOFF_MS = 3e4;
92347
92417
  var CRASH_WINDOW_MS = 6e4;
92348
92418
  var MAX_CRASHES_IN_WINDOW = 5;
@@ -92421,7 +92491,8 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
92421
92491
  ...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
92422
92492
  ...env
92423
92493
  };
92424
- const child = spawnFn(process.execPath, [runnerPath], {
92494
+ const heapFlags = runnerHeapFlags(addons);
92495
+ const child = spawnFn(process.execPath, [...heapFlags, runnerPath], {
92425
92496
  env: childEnv,
92426
92497
  stdio: [
92427
92498
  "ignore",
package/dist/index.mjs CHANGED
@@ -92335,6 +92335,76 @@ async function getPidStats$1(pids) {
92335
92335
  });
92336
92336
  });
92337
92337
  }
92338
+ /**
92339
+ * Resolve the declared heap profile for one addon by reading its package.json manifest
92340
+ * (`camstack.addons[].execution.heapProfile`). `addonDir` points at the addon's built `dist`, so the
92341
+ * manifest is one level up; we also try `addonDir` itself for layouts where it IS the package root.
92342
+ * Result is cached per (dir,id) — spawns are infrequent but a runner may re-spawn on crash/restart.
92343
+ * Returns `undefined` when the manifest is unreadable or declares no profile → treated as light.
92344
+ */
92345
+ var heapProfileCache = /* @__PURE__ */ new Map();
92346
+ function readAddonHeapProfile(spec) {
92347
+ const cacheKey = `${spec.addonDir}::${spec.addonId}`;
92348
+ const cached = heapProfileCache.get(cacheKey);
92349
+ if (cached !== void 0 || heapProfileCache.has(cacheKey)) return cached;
92350
+ let profile;
92351
+ for (const manifestPath of [path$39.join(spec.addonDir, "package.json"), path$39.join(path$39.dirname(spec.addonDir), "package.json")]) try {
92352
+ const raw = fs$17.readFileSync(manifestPath, "utf8");
92353
+ const parsed = JSON.parse(raw);
92354
+ profile = extractHeapProfile(parsed, spec.addonId);
92355
+ if (profile !== void 0) break;
92356
+ if (manifestHasAddon(parsed, spec.addonId)) break;
92357
+ } catch {}
92358
+ heapProfileCache.set(cacheKey, profile);
92359
+ return profile;
92360
+ }
92361
+ function manifestHasAddon(parsed, addonId) {
92362
+ return readManifestAddons(parsed).some((a) => a.id === addonId);
92363
+ }
92364
+ function extractHeapProfile(parsed, addonId) {
92365
+ const value = readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.heapProfile;
92366
+ return value === "heavy" || value === "light" ? value : void 0;
92367
+ }
92368
+ function readManifestAddons(parsed) {
92369
+ if (typeof parsed !== "object" || parsed === null) return [];
92370
+ const camstack = parsed.camstack;
92371
+ if (typeof camstack !== "object" || camstack === null) return [];
92372
+ const addons = camstack.addons;
92373
+ return Array.isArray(addons) ? addons : [];
92374
+ }
92375
+ /**
92376
+ * A runner is heavy if ANY co-located addon declares `heapProfile: 'heavy'` in its manifest.
92377
+ * Everything else (the un-annotated default) is a light control-plane runner. No hard-coded names.
92378
+ */
92379
+ function isHeavyRunner(addons) {
92380
+ return addons.some((a) => readAddonHeapProfile(a) === "heavy");
92381
+ }
92382
+ /**
92383
+ * Per-placement V8 heap flags (Block D). LIGHT runners cap their young generation
92384
+ * (`--max-semi-space-size`) to reclaim V8's over-reservation — a measured ~15 MB/runner win.
92385
+ * HEAVY runners keep V8 defaults. The old-space cap is opt-in via env (unset = uncapped = no OOM
92386
+ * risk) so it can be enabled after before/after measurement on the real cluster.
92387
+ *
92388
+ * Kill-switch: `CAMSTACK_RUNNER_HEAP_TUNING=off` disables all flags (recover without a rebuild).
92389
+ * Overrides: `CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB` (default 2), `CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB`
92390
+ * (default unset = no old-space cap).
92391
+ */
92392
+ function runnerHeapFlags(addons) {
92393
+ if (process.env["CAMSTACK_RUNNER_HEAP_TUNING"] === "off") return [];
92394
+ if (isHeavyRunner(addons)) return [];
92395
+ const semiMb = positiveIntEnv("CAMSTACK_RUNNER_LIGHT_SEMI_SPACE_MB", 2);
92396
+ const oldMb = positiveIntEnv("CAMSTACK_RUNNER_LIGHT_MAX_OLD_MB", 0);
92397
+ const flags = [];
92398
+ if (semiMb > 0) flags.push(`--max-semi-space-size=${semiMb}`);
92399
+ if (oldMb > 0) flags.push(`--max-old-space-size=${oldMb}`);
92400
+ return flags;
92401
+ }
92402
+ function positiveIntEnv(name, fallback) {
92403
+ const raw = process.env[name];
92404
+ if (raw === void 0) return fallback;
92405
+ const n = Number.parseInt(raw, 10);
92406
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
92407
+ }
92338
92408
  var MAX_BACKOFF_MS = 3e4;
92339
92409
  var CRASH_WINDOW_MS = 6e4;
92340
92410
  var MAX_CRASHES_IN_WINDOW = 5;
@@ -92413,7 +92483,8 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
92413
92483
  ...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
92414
92484
  ...env
92415
92485
  };
92416
- const child = spawnFn(process.execPath, [runnerPath], {
92486
+ const heapFlags = runnerHeapFlags(addons);
92487
+ const child = spawnFn(process.execPath, [...heapFlags, runnerPath], {
92417
92488
  env: childEnv,
92418
92489
  stdio: [
92419
92490
  "ignore",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",