camstack 1.2.58 → 1.2.59
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.
|
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
|
|
|
23633
23633
|
}
|
|
23634
23634
|
});
|
|
23635
23635
|
|
|
23636
|
-
// ../system/dist/dist-
|
|
23637
|
-
var
|
|
23638
|
-
"../system/dist/dist-
|
|
23636
|
+
// ../system/dist/dist-C7uZ_fOb.js
|
|
23637
|
+
var require_dist_C7uZ_fOb = __commonJS({
|
|
23638
|
+
"../system/dist/dist-C7uZ_fOb.js"(exports) {
|
|
23639
23639
|
"use strict";
|
|
23640
23640
|
var zod = require_zod();
|
|
23641
23641
|
var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
@@ -32718,6 +32718,38 @@ var require_dist_CxgTulEG = __commonJS({
|
|
|
32718
32718
|
/** Wall-clock uptime (seconds). Parsed from `ps etime`. */
|
|
32719
32719
|
uptimeSec: zod.z.number()
|
|
32720
32720
|
});
|
|
32721
|
+
var ContainerMemoryPointSchema = zod.z.object({
|
|
32722
|
+
/** Which hierarchy answered, so a reading is never ambiguous. */
|
|
32723
|
+
source: zod.z.enum(["cgroup-v2", "cgroup-v1"]),
|
|
32724
|
+
/** `memory.current` (v2) / `memory.usage_in_bytes` (v1). Always known. */
|
|
32725
|
+
currentBytes: zod.z.number(),
|
|
32726
|
+
/** The cgroup's ceiling. `null` = NO LIMIT — never a sentinel, never zero. */
|
|
32727
|
+
limitBytes: zod.z.number().nullable(),
|
|
32728
|
+
/** Anonymous pages: the closest thing to "what the processes allocated". */
|
|
32729
|
+
anonBytes: zod.z.number().nullable(),
|
|
32730
|
+
/** Page cache. Charged to the cgroup, owned by no process. */
|
|
32731
|
+
fileBytes: zod.z.number().nullable(),
|
|
32732
|
+
/**
|
|
32733
|
+
* Shared memory — and the field that explained the largest single surprise.
|
|
32734
|
+
* The i915 driver backs GPU buffers with shmem, so an inference pool or a
|
|
32735
|
+
* hardware-decode session holding DRM objects is charged HERE and appears
|
|
32736
|
+
* nowhere in a `ps` scan.
|
|
32737
|
+
*/
|
|
32738
|
+
shmemBytes: zod.z.number().nullable(),
|
|
32739
|
+
/** Kernel slab charged to this cgroup. `null` on v1, which never publishes it. */
|
|
32740
|
+
slabBytes: zod.z.number().nullable(),
|
|
32741
|
+
/**
|
|
32742
|
+
* Shrinkable i915 GEM object bytes, from debugfs.
|
|
32743
|
+
*
|
|
32744
|
+
* **Host-wide across every DRM client, NOT cgroup-scoped.** It is not a
|
|
32745
|
+
* component of `currentBytes` and must not be subtracted from it; it says
|
|
32746
|
+
* what put the shmem there, where `shmemBytes` only says how much.
|
|
32747
|
+
*
|
|
32748
|
+
* `null` wherever debugfs is not mounted — which is inside every camstack
|
|
32749
|
+
* container today — and on any node with no Intel GPU.
|
|
32750
|
+
*/
|
|
32751
|
+
gpuShmemBytes: zod.z.number().nullable()
|
|
32752
|
+
}).extend({ atMs: zod.z.number() });
|
|
32721
32753
|
var DumpHeapSnapshotInputSchema = zod.z.object({
|
|
32722
32754
|
/** The addon whose runner should dump a heap snapshot. */
|
|
32723
32755
|
addonId: zod.z.string()
|
|
@@ -32767,6 +32799,21 @@ var require_dist_CxgTulEG = __commonJS({
|
|
|
32767
32799
|
/** One entry per function seen in the window, heaviest-first. */
|
|
32768
32800
|
series: zod.z.array(LoadFunctionSeriesSchema).readonly(),
|
|
32769
32801
|
/**
|
|
32802
|
+
* The CONTAINER's memory over the same window, oldest-first.
|
|
32803
|
+
*
|
|
32804
|
+
* Sits next to `series` rather than in a method of its own because the whole
|
|
32805
|
+
* question is a subtraction: the per-process rows in `series` sum to one
|
|
32806
|
+
* number and this one is another, and an operator who has to issue two calls
|
|
32807
|
+
* to compare them will compare two different instants. Same reader, same
|
|
32808
|
+
* `sinceMs`, same `bucketMs`, same timestamps.
|
|
32809
|
+
*
|
|
32810
|
+
* **EMPTY means ABSENT, never zero.** A node with no cgroup — a developer
|
|
32811
|
+
* Mac, a bare-metal host, a container with the hierarchy hidden — reports no
|
|
32812
|
+
* points at all. A zero here would be indistinguishable from a healthy
|
|
32813
|
+
* container and is precisely the lie this field exists to avoid.
|
|
32814
|
+
*/
|
|
32815
|
+
containerMemory: zod.z.array(ContainerMemoryPointSchema).readonly(),
|
|
32816
|
+
/**
|
|
32770
32817
|
* Width of one returned bucket, in ms. Equals the sampling cadence when no
|
|
32771
32818
|
* reduction was needed — so a caller can always say what one point covers
|
|
32772
32819
|
* without having to know whether it was reduced.
|
|
@@ -58639,7 +58686,7 @@ var require_alerts_addon = __commonJS({
|
|
|
58639
58686
|
[Symbol.toStringTag]: { value: "Module" }
|
|
58640
58687
|
});
|
|
58641
58688
|
require_chunk_Cek0wNdY();
|
|
58642
|
-
var require_dist10 =
|
|
58689
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
58643
58690
|
function selectExpired(alerts, cutoffMs) {
|
|
58644
58691
|
return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
|
|
58645
58692
|
}
|
|
@@ -59458,7 +59505,7 @@ var require_console_logging = __commonJS({
|
|
|
59458
59505
|
[Symbol.toStringTag]: { value: "Module" }
|
|
59459
59506
|
});
|
|
59460
59507
|
require_chunk_Cek0wNdY();
|
|
59461
|
-
var require_dist10 =
|
|
59508
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
59462
59509
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
59463
59510
|
var LEVEL_RANK = {
|
|
59464
59511
|
debug: 0,
|
|
@@ -59552,7 +59599,7 @@ var require_core_blocks_addon = __commonJS({
|
|
|
59552
59599
|
"use strict";
|
|
59553
59600
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
59554
59601
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
59555
|
-
var require_dist10 =
|
|
59602
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
59556
59603
|
var node_crypto = __require("crypto");
|
|
59557
59604
|
var node_fs_promises = __require("fs/promises");
|
|
59558
59605
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -60449,11 +60496,11 @@ var require_core_blocks = __commonJS({
|
|
|
60449
60496
|
}
|
|
60450
60497
|
});
|
|
60451
60498
|
|
|
60452
|
-
// ../system/dist/retired-settings-keys-
|
|
60453
|
-
var
|
|
60454
|
-
"../system/dist/retired-settings-keys-
|
|
60499
|
+
// ../system/dist/retired-settings-keys-CBU4-TJO.js
|
|
60500
|
+
var require_retired_settings_keys_CBU4_TJO = __commonJS({
|
|
60501
|
+
"../system/dist/retired-settings-keys-CBU4-TJO.js"(exports) {
|
|
60455
60502
|
"use strict";
|
|
60456
|
-
var require_dist10 =
|
|
60503
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
60457
60504
|
function settingsStoreIsAuthoritativeHere(env) {
|
|
60458
60505
|
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
60459
60506
|
return raw === "" || raw === "hub";
|
|
@@ -62667,8 +62714,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
62667
62714
|
[Symbol.toStringTag]: { value: "Module" }
|
|
62668
62715
|
});
|
|
62669
62716
|
require_chunk_Cek0wNdY();
|
|
62670
|
-
var require_dist10 =
|
|
62671
|
-
var require_retired_settings_keys =
|
|
62717
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
62718
|
+
var require_retired_settings_keys = require_retired_settings_keys_CBU4_TJO();
|
|
62672
62719
|
var node_crypto = __require("crypto");
|
|
62673
62720
|
var _camstack_types_node = require_node();
|
|
62674
62721
|
var JOB_HISTORY = 20;
|
|
@@ -67417,7 +67464,7 @@ var require_hub_forwarder = __commonJS({
|
|
|
67417
67464
|
[Symbol.toStringTag]: { value: "Module" }
|
|
67418
67465
|
});
|
|
67419
67466
|
require_chunk_Cek0wNdY();
|
|
67420
|
-
var require_dist10 =
|
|
67467
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
67421
67468
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
67422
67469
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
67423
67470
|
var HubForwarderDestination = class {
|
|
@@ -67554,7 +67601,7 @@ var require_liveness_monitor_addon = __commonJS({
|
|
|
67554
67601
|
"use strict";
|
|
67555
67602
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
67556
67603
|
require_chunk_Cek0wNdY();
|
|
67557
|
-
var require_dist10 =
|
|
67604
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
67558
67605
|
var NO_DEVICES = "liveness:no-devices";
|
|
67559
67606
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
67560
67607
|
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
@@ -67744,7 +67791,7 @@ var require_local_auth_addon = __commonJS({
|
|
|
67744
67791
|
[Symbol.toStringTag]: { value: "Module" }
|
|
67745
67792
|
});
|
|
67746
67793
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
67747
|
-
var require_dist10 =
|
|
67794
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
67748
67795
|
var node_crypto = __require("crypto");
|
|
67749
67796
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
67750
67797
|
var crypto$1 = __require("crypto");
|
|
@@ -75557,7 +75604,7 @@ var require_loki_logging = __commonJS({
|
|
|
75557
75604
|
[Symbol.toStringTag]: { value: "Module" }
|
|
75558
75605
|
});
|
|
75559
75606
|
require_chunk_Cek0wNdY();
|
|
75560
|
-
var require_dist10 =
|
|
75607
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
75561
75608
|
function sanitizeLabelName(raw) {
|
|
75562
75609
|
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
75563
75610
|
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
@@ -76122,14 +76169,859 @@ var require_native_metrics_addon = __commonJS({
|
|
|
76122
76169
|
[Symbol.toStringTag]: { value: "Module" }
|
|
76123
76170
|
});
|
|
76124
76171
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
76125
|
-
var require_dist10 =
|
|
76172
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
76126
76173
|
var node_fs_promises = __require("fs/promises");
|
|
76127
76174
|
var node_child_process = __require("child_process");
|
|
76128
76175
|
var node_util = __require("util");
|
|
76129
|
-
var node_os = __require("os");
|
|
76130
|
-
node_os = require_chunk.__toESM(node_os);
|
|
76131
76176
|
var node_fs = __require("fs");
|
|
76132
76177
|
node_fs = require_chunk.__toESM(node_fs);
|
|
76178
|
+
var node_os = __require("os");
|
|
76179
|
+
node_os = require_chunk.__toESM(node_os);
|
|
76180
|
+
var nodeCgroupFileReader = { read(path) {
|
|
76181
|
+
try {
|
|
76182
|
+
return (0, node_fs.readFileSync)(path, "utf8");
|
|
76183
|
+
} catch {
|
|
76184
|
+
return null;
|
|
76185
|
+
}
|
|
76186
|
+
} };
|
|
76187
|
+
var CGROUP_MOUNT_ROOT = "/sys/fs/cgroup";
|
|
76188
|
+
var I915_GEM_OBJECTS_PATH = "/sys/kernel/debug/dri/0/i915_gem_objects";
|
|
76189
|
+
var V1_UNLIMITED_SENTINEL = 9223372036854772e3;
|
|
76190
|
+
function parseCgroupSelf(text) {
|
|
76191
|
+
let v1 = null;
|
|
76192
|
+
for (const rawLine of text.split("\n")) {
|
|
76193
|
+
const line = rawLine.trim();
|
|
76194
|
+
if (line.length === 0) continue;
|
|
76195
|
+
const first = line.indexOf(":");
|
|
76196
|
+
if (first < 0) continue;
|
|
76197
|
+
const second = line.indexOf(":", first + 1);
|
|
76198
|
+
if (second < 0) continue;
|
|
76199
|
+
const controllers = line.slice(first + 1, second);
|
|
76200
|
+
const path = line.slice(second + 1);
|
|
76201
|
+
if (path.length === 0) continue;
|
|
76202
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
76203
|
+
if (controllers.length === 0) return {
|
|
76204
|
+
version: "v2",
|
|
76205
|
+
path: normalized
|
|
76206
|
+
};
|
|
76207
|
+
if (v1 === null && controllers.split(",").includes("memory")) v1 = {
|
|
76208
|
+
version: "v1",
|
|
76209
|
+
path: normalized
|
|
76210
|
+
};
|
|
76211
|
+
}
|
|
76212
|
+
return v1;
|
|
76213
|
+
}
|
|
76214
|
+
function parseCgroupStat(text) {
|
|
76215
|
+
const out = /* @__PURE__ */ new Map();
|
|
76216
|
+
for (const rawLine of text.split("\n")) {
|
|
76217
|
+
const line = rawLine.trim();
|
|
76218
|
+
if (line.length === 0) continue;
|
|
76219
|
+
const space = line.indexOf(" ");
|
|
76220
|
+
if (space <= 0) continue;
|
|
76221
|
+
const key = line.slice(0, space);
|
|
76222
|
+
const value = Number(line.slice(space + 1).trim());
|
|
76223
|
+
if (!Number.isFinite(value)) continue;
|
|
76224
|
+
out.set(key, value);
|
|
76225
|
+
}
|
|
76226
|
+
return out;
|
|
76227
|
+
}
|
|
76228
|
+
function parseCgroupScalar(text) {
|
|
76229
|
+
const trimmed = text.trim();
|
|
76230
|
+
if (trimmed.length === 0) return null;
|
|
76231
|
+
if (trimmed === "max") return null;
|
|
76232
|
+
const value = Number(trimmed);
|
|
76233
|
+
if (!Number.isFinite(value) || value < 0) return null;
|
|
76234
|
+
if (value >= V1_UNLIMITED_SENTINEL) return null;
|
|
76235
|
+
return value;
|
|
76236
|
+
}
|
|
76237
|
+
function parseI915GemObjects(text) {
|
|
76238
|
+
const match = text.match(/^\s*\d+\s+shrinkable\b[^,\n]*,\s*(\d+)\s+bytes/m);
|
|
76239
|
+
if (match === null) return null;
|
|
76240
|
+
const bytes = Number(match[1]);
|
|
76241
|
+
return Number.isFinite(bytes) ? bytes : null;
|
|
76242
|
+
}
|
|
76243
|
+
function resolveCgroupMemoryLayout(fs) {
|
|
76244
|
+
const selfText = fs.read("/proc/self/cgroup");
|
|
76245
|
+
if (selfText === null) return null;
|
|
76246
|
+
const entry = parseCgroupSelf(selfText);
|
|
76247
|
+
if (entry === null) return null;
|
|
76248
|
+
const suffix = entry.path === "/" ? "" : entry.path;
|
|
76249
|
+
const candidates = entry.version === "v2" ? [`${CGROUP_MOUNT_ROOT}${suffix}`, CGROUP_MOUNT_ROOT] : [`${CGROUP_MOUNT_ROOT}/memory${suffix}`, `${CGROUP_MOUNT_ROOT}/memory`];
|
|
76250
|
+
const usageFile = entry.version === "v2" ? "memory.current" : "memory.usage_in_bytes";
|
|
76251
|
+
const limitFile = entry.version === "v2" ? "memory.max" : "memory.limit_in_bytes";
|
|
76252
|
+
for (const directory of candidates) {
|
|
76253
|
+
if (fs.read(`${directory}/${usageFile}`) === null) continue;
|
|
76254
|
+
return {
|
|
76255
|
+
source: entry.version === "v2" ? "cgroup-v2" : "cgroup-v1",
|
|
76256
|
+
directory,
|
|
76257
|
+
currentPath: `${directory}/${usageFile}`,
|
|
76258
|
+
limitPath: `${directory}/${limitFile}`,
|
|
76259
|
+
statPath: `${directory}/memory.stat`
|
|
76260
|
+
};
|
|
76261
|
+
}
|
|
76262
|
+
return null;
|
|
76263
|
+
}
|
|
76264
|
+
function statValue(stat, key) {
|
|
76265
|
+
const value = stat.get(key);
|
|
76266
|
+
return value === void 0 ? null : value;
|
|
76267
|
+
}
|
|
76268
|
+
function resolveSlab(stat) {
|
|
76269
|
+
const direct = statValue(stat, "slab");
|
|
76270
|
+
if (direct !== null) return direct;
|
|
76271
|
+
const reclaimable = stat.get("slab_reclaimable");
|
|
76272
|
+
const unreclaimable = stat.get("slab_unreclaimable");
|
|
76273
|
+
if (reclaimable === void 0 || unreclaimable === void 0) return null;
|
|
76274
|
+
return reclaimable + unreclaimable;
|
|
76275
|
+
}
|
|
76276
|
+
function buildContainerMemorySnapshot(input) {
|
|
76277
|
+
const currentBytes = parseCgroupScalar(input.currentText);
|
|
76278
|
+
if (currentBytes === null) return null;
|
|
76279
|
+
const stat = input.statText === null ? /* @__PURE__ */ new Map() : parseCgroupStat(input.statText);
|
|
76280
|
+
const isV2 = input.source === "cgroup-v2";
|
|
76281
|
+
return {
|
|
76282
|
+
source: input.source,
|
|
76283
|
+
currentBytes,
|
|
76284
|
+
limitBytes: input.limitText === null ? null : parseCgroupScalar(input.limitText),
|
|
76285
|
+
anonBytes: statValue(stat, isV2 ? "anon" : "rss"),
|
|
76286
|
+
fileBytes: statValue(stat, isV2 ? "file" : "cache"),
|
|
76287
|
+
shmemBytes: statValue(stat, "shmem"),
|
|
76288
|
+
slabBytes: isV2 ? resolveSlab(stat) : null,
|
|
76289
|
+
gpuShmemBytes: input.gpuShmemBytes
|
|
76290
|
+
};
|
|
76291
|
+
}
|
|
76292
|
+
var ContainerMemoryProbe = class {
|
|
76293
|
+
logger;
|
|
76294
|
+
fs;
|
|
76295
|
+
wantGpu;
|
|
76296
|
+
resolved = false;
|
|
76297
|
+
layout = null;
|
|
76298
|
+
gpuProbed = false;
|
|
76299
|
+
gpuReadable = false;
|
|
76300
|
+
/** `null` until the first sample — so the FIRST outcome is a transition too. */
|
|
76301
|
+
lastReadOk = null;
|
|
76302
|
+
constructor(deps) {
|
|
76303
|
+
this.logger = deps.logger;
|
|
76304
|
+
this.fs = deps.fs ?? nodeCgroupFileReader;
|
|
76305
|
+
this.wantGpu = deps.readGpu ?? true;
|
|
76306
|
+
}
|
|
76307
|
+
/** Did this node resolve a cgroup at all? Absent is a reportable fact. */
|
|
76308
|
+
get available() {
|
|
76309
|
+
return this.layout !== null;
|
|
76310
|
+
}
|
|
76311
|
+
/**
|
|
76312
|
+
* One reading, or `null` when this node has no cgroup or the cgroup stopped
|
|
76313
|
+
* answering. `null` is ABSENT — the caller contributes no point, and no
|
|
76314
|
+
* point is what a chart must show, never a zero.
|
|
76315
|
+
*/
|
|
76316
|
+
sample() {
|
|
76317
|
+
const layout = this.resolveOnce();
|
|
76318
|
+
if (layout === null) return null;
|
|
76319
|
+
const currentText = this.fs.read(layout.currentPath);
|
|
76320
|
+
if (currentText === null) {
|
|
76321
|
+
this.noteRead(false, `unreadable: ${layout.currentPath}`);
|
|
76322
|
+
return null;
|
|
76323
|
+
}
|
|
76324
|
+
const snapshot = buildContainerMemorySnapshot({
|
|
76325
|
+
source: layout.source,
|
|
76326
|
+
currentText,
|
|
76327
|
+
limitText: this.fs.read(layout.limitPath),
|
|
76328
|
+
statText: this.fs.read(layout.statPath),
|
|
76329
|
+
gpuShmemBytes: this.readGpuShmem()
|
|
76330
|
+
});
|
|
76331
|
+
if (snapshot === null) {
|
|
76332
|
+
this.noteRead(false, `unparseable: ${layout.currentPath}`);
|
|
76333
|
+
return null;
|
|
76334
|
+
}
|
|
76335
|
+
this.noteRead(true, null);
|
|
76336
|
+
return snapshot;
|
|
76337
|
+
}
|
|
76338
|
+
/** Resolve on first use and never again — a cgroup does not move under a pid. */
|
|
76339
|
+
resolveOnce() {
|
|
76340
|
+
if (this.resolved) return this.layout;
|
|
76341
|
+
this.resolved = true;
|
|
76342
|
+
try {
|
|
76343
|
+
this.layout = resolveCgroupMemoryLayout(this.fs);
|
|
76344
|
+
} catch (err) {
|
|
76345
|
+
this.layout = null;
|
|
76346
|
+
this.logger.warn("cgroup memory resolution failed \u2014 container memory will be ABSENT", { meta: { error: require_dist10.errMsg(err) } });
|
|
76347
|
+
return null;
|
|
76348
|
+
}
|
|
76349
|
+
if (this.layout === null) {
|
|
76350
|
+
this.logger.info("no cgroup on this node \u2014 container memory is ABSENT, not zero, for every sample", { meta: {
|
|
76351
|
+
platform: process.platform,
|
|
76352
|
+
probed: "/proc/self/cgroup"
|
|
76353
|
+
} });
|
|
76354
|
+
return null;
|
|
76355
|
+
}
|
|
76356
|
+
this.logger.info("container memory resolved from the cgroup", { meta: {
|
|
76357
|
+
source: this.layout.source,
|
|
76358
|
+
directory: this.layout.directory
|
|
76359
|
+
} });
|
|
76360
|
+
return this.layout;
|
|
76361
|
+
}
|
|
76362
|
+
/**
|
|
76363
|
+
* The GPU number, probed once.
|
|
76364
|
+
*
|
|
76365
|
+
* It is host-wide and lives in debugfs, so it is absent far more often than
|
|
76366
|
+
* it is present — inside every camstack container today. One `open` decides
|
|
76367
|
+
* it for the life of the process; after that an unreadable file costs
|
|
76368
|
+
* nothing at all, not even a syscall.
|
|
76369
|
+
*/
|
|
76370
|
+
readGpuShmem() {
|
|
76371
|
+
if (!this.wantGpu) return null;
|
|
76372
|
+
if (!this.gpuProbed) {
|
|
76373
|
+
this.gpuProbed = true;
|
|
76374
|
+
const text2 = this.fs.read(I915_GEM_OBJECTS_PATH);
|
|
76375
|
+
this.gpuReadable = text2 !== null && parseI915GemObjects(text2) !== null;
|
|
76376
|
+
this.logger.info(this.gpuReadable ? "i915 GEM accounting is readable \u2014 GPU-backed shmem will be reported" : "i915 GEM accounting is NOT readable \u2014 gpuShmemBytes stays UNKNOWN (this is normal: debugfs is not mounted in the container, and a node with no Intel GPU has no such file)", { meta: { path: I915_GEM_OBJECTS_PATH } });
|
|
76377
|
+
if (!this.gpuReadable) return null;
|
|
76378
|
+
return text2 === null ? null : parseI915GemObjects(text2);
|
|
76379
|
+
}
|
|
76380
|
+
if (!this.gpuReadable) return null;
|
|
76381
|
+
const text = this.fs.read(I915_GEM_OBJECTS_PATH);
|
|
76382
|
+
return text === null ? null : parseI915GemObjects(text);
|
|
76383
|
+
}
|
|
76384
|
+
/** Log a read outcome only when it CHANGES. Never once per tick. */
|
|
76385
|
+
noteRead(ok, reason) {
|
|
76386
|
+
if (this.lastReadOk === ok) return;
|
|
76387
|
+
const first = this.lastReadOk === null;
|
|
76388
|
+
this.lastReadOk = ok;
|
|
76389
|
+
if (!ok) {
|
|
76390
|
+
this.logger.warn("cgroup memory stopped answering \u2014 container memory is ABSENT until it returns", { meta: { reason: reason ?? "unknown" } });
|
|
76391
|
+
return;
|
|
76392
|
+
}
|
|
76393
|
+
if (!first) this.logger.info("cgroup memory is answering again");
|
|
76394
|
+
}
|
|
76395
|
+
};
|
|
76396
|
+
var LoadPartition = class {
|
|
76397
|
+
capacity;
|
|
76398
|
+
slots;
|
|
76399
|
+
head = 0;
|
|
76400
|
+
count = 0;
|
|
76401
|
+
rows = 0;
|
|
76402
|
+
/** Newest retained sample's timestamp — the monotonic gate for `push`. */
|
|
76403
|
+
newestAtMs = null;
|
|
76404
|
+
constructor(capacity) {
|
|
76405
|
+
this.capacity = capacity;
|
|
76406
|
+
this.slots = Array.from({ length: capacity });
|
|
76407
|
+
}
|
|
76408
|
+
/**
|
|
76409
|
+
* Accept a sample.
|
|
76410
|
+
*
|
|
76411
|
+
* A sample at or before the newest one already held is REFUSED. The bus drops
|
|
76412
|
+
* a node's own broadcast echo, but a cross-node redelivery or a replayed
|
|
76413
|
+
* subscription must not be able to double a point — and idempotence here is
|
|
76414
|
+
* what lets every reader above treat the series as a set.
|
|
76415
|
+
*
|
|
76416
|
+
* `accepted` is reported separately from `rowDelta` on purpose: a full ring
|
|
76417
|
+
* that evicts a sample of the same size has a delta of zero and is not a
|
|
76418
|
+
* refusal, and conflating the two would silently stop advancing the write
|
|
76419
|
+
* ordinal on a steady-state cluster.
|
|
76420
|
+
*/
|
|
76421
|
+
push(sample) {
|
|
76422
|
+
if (this.newestAtMs !== null && sample.atMs <= this.newestAtMs) return {
|
|
76423
|
+
accepted: false,
|
|
76424
|
+
rowDelta: 0
|
|
76425
|
+
};
|
|
76426
|
+
const evicted = this.count === this.capacity ? this.slots[this.head]?.processes.length ?? 0 : 0;
|
|
76427
|
+
this.slots[this.head] = sample;
|
|
76428
|
+
this.head = (this.head + 1) % this.capacity;
|
|
76429
|
+
if (this.count < this.capacity) this.count++;
|
|
76430
|
+
this.newestAtMs = sample.atMs;
|
|
76431
|
+
const rowDelta = sample.processes.length - evicted;
|
|
76432
|
+
this.rows += rowDelta;
|
|
76433
|
+
return {
|
|
76434
|
+
accepted: true,
|
|
76435
|
+
rowDelta
|
|
76436
|
+
};
|
|
76437
|
+
}
|
|
76438
|
+
/** Drop the oldest sample. Returns the rows reclaimed (0 when empty). */
|
|
76439
|
+
dropOldest() {
|
|
76440
|
+
if (this.count === 0) return 0;
|
|
76441
|
+
const index = (this.head - this.count + this.capacity) % this.capacity;
|
|
76442
|
+
const victim = this.slots[index];
|
|
76443
|
+
this.slots[index] = void 0;
|
|
76444
|
+
this.count--;
|
|
76445
|
+
const reclaimed = victim?.processes.length ?? 0;
|
|
76446
|
+
this.rows -= reclaimed;
|
|
76447
|
+
if (this.count === 0) this.newestAtMs = null;
|
|
76448
|
+
return reclaimed;
|
|
76449
|
+
}
|
|
76450
|
+
/** Oldest-first, optionally only what is strictly newer than `sinceMs`. */
|
|
76451
|
+
list(sinceMs) {
|
|
76452
|
+
const out = [];
|
|
76453
|
+
for (let i = 0; i < this.count; i++) {
|
|
76454
|
+
const index = (this.head - this.count + i + this.capacity) % this.capacity;
|
|
76455
|
+
const sample = this.slots[index];
|
|
76456
|
+
if (sample === void 0) continue;
|
|
76457
|
+
if (sinceMs !== void 0 && sample.atMs <= sinceMs) continue;
|
|
76458
|
+
out.push(sample);
|
|
76459
|
+
}
|
|
76460
|
+
return out;
|
|
76461
|
+
}
|
|
76462
|
+
size() {
|
|
76463
|
+
return this.count;
|
|
76464
|
+
}
|
|
76465
|
+
rowCount() {
|
|
76466
|
+
return this.rows;
|
|
76467
|
+
}
|
|
76468
|
+
oldestAtMs() {
|
|
76469
|
+
if (this.count === 0) return null;
|
|
76470
|
+
const index = (this.head - this.count + this.capacity) % this.capacity;
|
|
76471
|
+
return this.slots[index]?.atMs ?? null;
|
|
76472
|
+
}
|
|
76473
|
+
lastWriteAtMs() {
|
|
76474
|
+
return this.newestAtMs;
|
|
76475
|
+
}
|
|
76476
|
+
};
|
|
76477
|
+
var NodeLoadRing = class {
|
|
76478
|
+
partitions = /* @__PURE__ */ new Map();
|
|
76479
|
+
/**
|
|
76480
|
+
* Node id → write ordinal of its last accepted sample. A monotonic counter,
|
|
76481
|
+
* not a clock: partition eviction must follow the order writes actually
|
|
76482
|
+
* happened, and node clocks disagree.
|
|
76483
|
+
*/
|
|
76484
|
+
lastWriteSeq = /* @__PURE__ */ new Map();
|
|
76485
|
+
writeSeq = 0;
|
|
76486
|
+
totalRows = 0;
|
|
76487
|
+
samplesPerNode;
|
|
76488
|
+
maxTotalProcessRows;
|
|
76489
|
+
maxNodes;
|
|
76490
|
+
idleEvictionMs;
|
|
76491
|
+
now;
|
|
76492
|
+
constructor(options = {}) {
|
|
76493
|
+
this.samplesPerNode = options.samplesPerNode ?? 180;
|
|
76494
|
+
this.maxTotalProcessRows = options.maxTotalProcessRows ?? 32e3;
|
|
76495
|
+
this.maxNodes = options.maxNodes ?? 16;
|
|
76496
|
+
this.idleEvictionMs = options.idleEvictionMs ?? 36e5;
|
|
76497
|
+
this.now = options.now ?? Date.now;
|
|
76498
|
+
}
|
|
76499
|
+
/**
|
|
76500
|
+
* Retain one snapshot. `processes` is stored by reference — the payload is
|
|
76501
|
+
* already an immutable arrival off the bus, and copying it would double the
|
|
76502
|
+
* measured cost for nothing.
|
|
76503
|
+
*
|
|
76504
|
+
* Returns whether the sample was ACCEPTED — that is, whether it was new
|
|
76505
|
+
* rather than a replay of a timestamp this node has already delivered.
|
|
76506
|
+
*
|
|
76507
|
+
* The return value is not diagnostics. It is the idempotence gate the
|
|
76508
|
+
* DURABLE tier rides on (`load-series-store.ts`): only an accepted sample is
|
|
76509
|
+
* appended to the table, which is what lets that table carry an `INTEGER`
|
|
76510
|
+
* rowid key instead of a composite unique index over two million rows. The
|
|
76511
|
+
* gate is one monotonic comparison in memory; the index it replaces was
|
|
76512
|
+
* measured at ~40 bytes per row.
|
|
76513
|
+
*/
|
|
76514
|
+
record(nodeId, atMs, processes) {
|
|
76515
|
+
if (nodeId.length === 0) return false;
|
|
76516
|
+
this.sweepIdle();
|
|
76517
|
+
let partition = this.partitions.get(nodeId);
|
|
76518
|
+
if (partition === void 0) {
|
|
76519
|
+
partition = new LoadPartition(this.samplesPerNode);
|
|
76520
|
+
this.partitions.set(nodeId, partition);
|
|
76521
|
+
}
|
|
76522
|
+
const outcome = partition.push({
|
|
76523
|
+
atMs,
|
|
76524
|
+
processes
|
|
76525
|
+
});
|
|
76526
|
+
if (!outcome.accepted) {
|
|
76527
|
+
if (partition.size() === 0) this.partitions.delete(nodeId);
|
|
76528
|
+
return false;
|
|
76529
|
+
}
|
|
76530
|
+
this.totalRows += outcome.rowDelta;
|
|
76531
|
+
this.lastWriteSeq.set(nodeId, ++this.writeSeq);
|
|
76532
|
+
if (this.partitions.size > this.maxNodes) this.evictPartitions(this.partitions.size - this.maxNodes, nodeId);
|
|
76533
|
+
this.enforceRowBudget();
|
|
76534
|
+
return true;
|
|
76535
|
+
}
|
|
76536
|
+
/**
|
|
76537
|
+
* Read one node's retained series, oldest-first.
|
|
76538
|
+
*
|
|
76539
|
+
* `sinceMs` is EXCLUSIVE: a caller passes the newest timestamp it already
|
|
76540
|
+
* holds and gets back only what it is missing. That is the whole contract
|
|
76541
|
+
* that lets the admin UI seed from here and then continue live without
|
|
76542
|
+
* doubling a point it already drew.
|
|
76543
|
+
*
|
|
76544
|
+
* A node nobody has recorded answers with an empty series, not an error —
|
|
76545
|
+
* unknown is the truth about a node that has not reported.
|
|
76546
|
+
*/
|
|
76547
|
+
read(nodeId, sinceMs) {
|
|
76548
|
+
this.sweepIdle();
|
|
76549
|
+
const partition = this.partitions.get(nodeId);
|
|
76550
|
+
if (partition === void 0) return {
|
|
76551
|
+
nodeId,
|
|
76552
|
+
samples: [],
|
|
76553
|
+
retainedSamples: 0,
|
|
76554
|
+
oldestAtMs: null,
|
|
76555
|
+
capacity: this.samplesPerNode
|
|
76556
|
+
};
|
|
76557
|
+
return {
|
|
76558
|
+
nodeId,
|
|
76559
|
+
samples: partition.list(sinceMs),
|
|
76560
|
+
retainedSamples: partition.size(),
|
|
76561
|
+
oldestAtMs: partition.oldestAtMs(),
|
|
76562
|
+
capacity: this.samplesPerNode
|
|
76563
|
+
};
|
|
76564
|
+
}
|
|
76565
|
+
/** Node ids with a live partition. Observability for the fleet bound. */
|
|
76566
|
+
nodeIds() {
|
|
76567
|
+
return [...this.partitions.keys()];
|
|
76568
|
+
}
|
|
76569
|
+
/** Process rows retained across every partition — the number that IS memory. */
|
|
76570
|
+
rowCount() {
|
|
76571
|
+
return this.totalRows;
|
|
76572
|
+
}
|
|
76573
|
+
/** Samples retained across every partition. */
|
|
76574
|
+
sampleCount() {
|
|
76575
|
+
let total = 0;
|
|
76576
|
+
for (const partition of this.partitions.values()) total += partition.size();
|
|
76577
|
+
return total;
|
|
76578
|
+
}
|
|
76579
|
+
/**
|
|
76580
|
+
* Drop partitions whose newest sample is older than the retention window.
|
|
76581
|
+
*
|
|
76582
|
+
* Lazy, on write and on read — never a timer. A timer would be a new
|
|
76583
|
+
* periodic cost in a subsystem whose entire premise is that it adds none,
|
|
76584
|
+
* and a ring that nobody writes to and nobody reads is not growing either.
|
|
76585
|
+
*/
|
|
76586
|
+
sweepIdle() {
|
|
76587
|
+
const cutoff = this.now() - this.idleEvictionMs;
|
|
76588
|
+
for (const [nodeId, partition] of [...this.partitions.entries()]) {
|
|
76589
|
+
const lastWrite = partition.lastWriteAtMs();
|
|
76590
|
+
if (lastWrite !== null && lastWrite > cutoff) continue;
|
|
76591
|
+
this.dropPartition(nodeId);
|
|
76592
|
+
}
|
|
76593
|
+
}
|
|
76594
|
+
/**
|
|
76595
|
+
* Bring the fleet back under the row budget by dropping the OLDEST sample of
|
|
76596
|
+
* the HEAVIEST partition, repeatedly.
|
|
76597
|
+
*
|
|
76598
|
+
* Terminates: every iteration removes one sample from a non-empty partition,
|
|
76599
|
+
* and the population of samples is finite and strictly decreasing.
|
|
76600
|
+
*/
|
|
76601
|
+
enforceRowBudget() {
|
|
76602
|
+
while (this.totalRows > this.maxTotalProcessRows) {
|
|
76603
|
+
const victim = this.heaviestPartition();
|
|
76604
|
+
if (victim === null) return;
|
|
76605
|
+
const [nodeId, partition] = victim;
|
|
76606
|
+
this.totalRows -= partition.dropOldest();
|
|
76607
|
+
if (partition.size() === 0) this.dropPartition(nodeId);
|
|
76608
|
+
}
|
|
76609
|
+
}
|
|
76610
|
+
heaviestPartition() {
|
|
76611
|
+
let best = null;
|
|
76612
|
+
for (const entry of this.partitions.entries()) {
|
|
76613
|
+
if (entry[1].size() === 0) continue;
|
|
76614
|
+
if (best === null || entry[1].rowCount() > best[1].rowCount()) best = entry;
|
|
76615
|
+
}
|
|
76616
|
+
return best;
|
|
76617
|
+
}
|
|
76618
|
+
/** Drop `count` whole partitions, least-recently-written first. */
|
|
76619
|
+
evictPartitions(count, protectedNodeId) {
|
|
76620
|
+
const order = [...this.partitions.keys()].filter((nodeId) => nodeId !== protectedNodeId).toSorted((a, b) => (this.lastWriteSeq.get(a) ?? 0) - (this.lastWriteSeq.get(b) ?? 0));
|
|
76621
|
+
let remaining = count;
|
|
76622
|
+
for (const nodeId of order) {
|
|
76623
|
+
if (remaining <= 0) return;
|
|
76624
|
+
this.dropPartition(nodeId);
|
|
76625
|
+
remaining -= 1;
|
|
76626
|
+
}
|
|
76627
|
+
}
|
|
76628
|
+
/** Remove a partition and everything that indexes it. */
|
|
76629
|
+
dropPartition(nodeId) {
|
|
76630
|
+
const partition = this.partitions.get(nodeId);
|
|
76631
|
+
if (partition === void 0) return;
|
|
76632
|
+
this.totalRows -= partition.rowCount();
|
|
76633
|
+
this.partitions.delete(nodeId);
|
|
76634
|
+
this.lastWriteSeq.delete(nodeId);
|
|
76635
|
+
}
|
|
76636
|
+
};
|
|
76637
|
+
var CONTAINER_MEMORY_COLLECTION = "metrics:node-memory-samples";
|
|
76638
|
+
var CONTAINER_MEMORY_MAX_TOTAL_ROWS = 2880;
|
|
76639
|
+
var CONTAINER_MEMORY_MAX_ROWS = 5e4;
|
|
76640
|
+
var CONTAINER_MEMORY_COLUMNS = [
|
|
76641
|
+
{
|
|
76642
|
+
name: "id",
|
|
76643
|
+
type: "INTEGER",
|
|
76644
|
+
primaryKey: true,
|
|
76645
|
+
notNull: true
|
|
76646
|
+
},
|
|
76647
|
+
{
|
|
76648
|
+
name: "nodeId",
|
|
76649
|
+
type: "TEXT",
|
|
76650
|
+
notNull: true
|
|
76651
|
+
},
|
|
76652
|
+
/** Shared with the process rows of the same tick — that is the whole point. */
|
|
76653
|
+
{
|
|
76654
|
+
name: "atMs",
|
|
76655
|
+
type: "INTEGER",
|
|
76656
|
+
notNull: true
|
|
76657
|
+
},
|
|
76658
|
+
{
|
|
76659
|
+
name: "source",
|
|
76660
|
+
type: "TEXT",
|
|
76661
|
+
notNull: true
|
|
76662
|
+
},
|
|
76663
|
+
/**
|
|
76664
|
+
* Whole MiB, like `rssMib` on the process row and for the same reason: a
|
|
76665
|
+
* byte-exact residency on a value in the tens of gigabytes is precision no
|
|
76666
|
+
* chart can draw, and it costs bytes on every row forever.
|
|
76667
|
+
*/
|
|
76668
|
+
{
|
|
76669
|
+
name: "currentMib",
|
|
76670
|
+
type: "INTEGER",
|
|
76671
|
+
notNull: true
|
|
76672
|
+
},
|
|
76673
|
+
/** `NULL` = NO LIMIT. Never a sentinel. */
|
|
76674
|
+
{
|
|
76675
|
+
name: "limitMib",
|
|
76676
|
+
type: "INTEGER"
|
|
76677
|
+
},
|
|
76678
|
+
/** `NULL` = UNKNOWN on all four. Never zero. */
|
|
76679
|
+
{
|
|
76680
|
+
name: "anonMib",
|
|
76681
|
+
type: "INTEGER"
|
|
76682
|
+
},
|
|
76683
|
+
{
|
|
76684
|
+
name: "fileMib",
|
|
76685
|
+
type: "INTEGER"
|
|
76686
|
+
},
|
|
76687
|
+
{
|
|
76688
|
+
name: "shmemMib",
|
|
76689
|
+
type: "INTEGER"
|
|
76690
|
+
},
|
|
76691
|
+
{
|
|
76692
|
+
name: "slabMib",
|
|
76693
|
+
type: "INTEGER"
|
|
76694
|
+
},
|
|
76695
|
+
/** Host-wide i915 GEM bytes. `NULL` wherever debugfs is not mounted. */
|
|
76696
|
+
{
|
|
76697
|
+
name: "gpuShmemMib",
|
|
76698
|
+
type: "INTEGER"
|
|
76699
|
+
}
|
|
76700
|
+
];
|
|
76701
|
+
var CONTAINER_MEMORY_INDEXES = [
|
|
76702
|
+
/** The one index, and it serves both the read and the prune — see the
|
|
76703
|
+
* process table's `idx_load_samples_node_at` for the full reasoning. */
|
|
76704
|
+
{
|
|
76705
|
+
name: "idx_node_memory_node_at",
|
|
76706
|
+
columns: ["nodeId", "atMs"]
|
|
76707
|
+
}
|
|
76708
|
+
];
|
|
76709
|
+
var BYTES_PER_MIB$1 = 1048576;
|
|
76710
|
+
function toMib(value) {
|
|
76711
|
+
return value === null ? null : Math.round(value / BYTES_PER_MIB$1);
|
|
76712
|
+
}
|
|
76713
|
+
function fromMib(value) {
|
|
76714
|
+
return value === null ? null : value * BYTES_PER_MIB$1;
|
|
76715
|
+
}
|
|
76716
|
+
function snapshotToRow(nodeId, atMs, snapshot) {
|
|
76717
|
+
return {
|
|
76718
|
+
nodeId,
|
|
76719
|
+
atMs,
|
|
76720
|
+
source: snapshot.source,
|
|
76721
|
+
currentMib: Math.round(snapshot.currentBytes / BYTES_PER_MIB$1),
|
|
76722
|
+
limitMib: toMib(snapshot.limitBytes),
|
|
76723
|
+
anonMib: toMib(snapshot.anonBytes),
|
|
76724
|
+
fileMib: toMib(snapshot.fileBytes),
|
|
76725
|
+
shmemMib: toMib(snapshot.shmemBytes),
|
|
76726
|
+
slabMib: toMib(snapshot.slabBytes),
|
|
76727
|
+
gpuShmemMib: toMib(snapshot.gpuShmemBytes)
|
|
76728
|
+
};
|
|
76729
|
+
}
|
|
76730
|
+
function rowToPoint(row) {
|
|
76731
|
+
if (row.source !== "cgroup-v2" && row.source !== "cgroup-v1") return null;
|
|
76732
|
+
return {
|
|
76733
|
+
atMs: row.atMs,
|
|
76734
|
+
source: row.source,
|
|
76735
|
+
currentBytes: row.currentMib * BYTES_PER_MIB$1,
|
|
76736
|
+
limitBytes: fromMib(row.limitMib),
|
|
76737
|
+
anonBytes: fromMib(row.anonMib),
|
|
76738
|
+
fileBytes: fromMib(row.fileMib),
|
|
76739
|
+
shmemBytes: fromMib(row.shmemMib),
|
|
76740
|
+
slabBytes: fromMib(row.slabMib),
|
|
76741
|
+
gpuShmemBytes: fromMib(row.gpuShmemMib)
|
|
76742
|
+
};
|
|
76743
|
+
}
|
|
76744
|
+
function recordToMemoryRow(data) {
|
|
76745
|
+
const nodeId = data["nodeId"];
|
|
76746
|
+
const source = data["source"];
|
|
76747
|
+
if (typeof nodeId !== "string" || typeof source !== "string") return null;
|
|
76748
|
+
const atMs = Number(data["atMs"]);
|
|
76749
|
+
const currentMib = Number(data["currentMib"]);
|
|
76750
|
+
if (!Number.isFinite(atMs) || !Number.isFinite(currentMib)) return null;
|
|
76751
|
+
const optional = (raw) => {
|
|
76752
|
+
if (raw === null || raw === void 0) return null;
|
|
76753
|
+
const n = Number(raw);
|
|
76754
|
+
return Number.isFinite(n) ? n : null;
|
|
76755
|
+
};
|
|
76756
|
+
return {
|
|
76757
|
+
nodeId,
|
|
76758
|
+
atMs,
|
|
76759
|
+
source,
|
|
76760
|
+
currentMib,
|
|
76761
|
+
limitMib: optional(data["limitMib"]),
|
|
76762
|
+
anonMib: optional(data["anonMib"]),
|
|
76763
|
+
fileMib: optional(data["fileMib"]),
|
|
76764
|
+
shmemMib: optional(data["shmemMib"]),
|
|
76765
|
+
slabMib: optional(data["slabMib"]),
|
|
76766
|
+
gpuShmemMib: optional(data["gpuShmemMib"])
|
|
76767
|
+
};
|
|
76768
|
+
}
|
|
76769
|
+
var ContainerMemoryRing = class {
|
|
76770
|
+
ring;
|
|
76771
|
+
constructor(options = {}) {
|
|
76772
|
+
this.ring = new NodeLoadRing({
|
|
76773
|
+
samplesPerNode: options.samplesPerNode ?? 180,
|
|
76774
|
+
maxTotalProcessRows: CONTAINER_MEMORY_MAX_TOTAL_ROWS,
|
|
76775
|
+
...options.now !== void 0 ? { now: options.now } : {}
|
|
76776
|
+
});
|
|
76777
|
+
}
|
|
76778
|
+
/** Retain one reading. `false` = a timestamp this node already delivered. */
|
|
76779
|
+
record(nodeId, atMs, snapshot) {
|
|
76780
|
+
return this.ring.record(nodeId, atMs, [snapshot]);
|
|
76781
|
+
}
|
|
76782
|
+
/**
|
|
76783
|
+
* Read one node's retained readings, oldest-first. `sinceMs` is EXCLUSIVE,
|
|
76784
|
+
* matching every other reader in this subsystem.
|
|
76785
|
+
*
|
|
76786
|
+
* A node nobody has recorded answers EMPTY — which is what "this node has no
|
|
76787
|
+
* cgroup" looks like, and it must stay distinguishable from a zero.
|
|
76788
|
+
*/
|
|
76789
|
+
read(nodeId, sinceMs) {
|
|
76790
|
+
const series = this.ring.read(nodeId, sinceMs);
|
|
76791
|
+
const out = [];
|
|
76792
|
+
for (const sample of series.samples) {
|
|
76793
|
+
const snapshot = sample.processes[0];
|
|
76794
|
+
if (snapshot === void 0) continue;
|
|
76795
|
+
out.push({
|
|
76796
|
+
...snapshot,
|
|
76797
|
+
atMs: sample.atMs
|
|
76798
|
+
});
|
|
76799
|
+
}
|
|
76800
|
+
return out;
|
|
76801
|
+
}
|
|
76802
|
+
};
|
|
76803
|
+
function reduceContainerMemory(points, bucketMs, origin) {
|
|
76804
|
+
if (bucketMs <= 0 || points.length === 0) return points;
|
|
76805
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
76806
|
+
for (const point of points) {
|
|
76807
|
+
const start = origin + Math.floor((point.atMs - origin) / bucketMs) * bucketMs;
|
|
76808
|
+
const held = buckets.get(start);
|
|
76809
|
+
if (held === void 0 || point.currentBytes > held.currentBytes) buckets.set(start, {
|
|
76810
|
+
...point,
|
|
76811
|
+
atMs: start
|
|
76812
|
+
});
|
|
76813
|
+
}
|
|
76814
|
+
return [...buckets.values()].toSorted((a, b) => a.atMs - b.atMs);
|
|
76815
|
+
}
|
|
76816
|
+
function mergeContainerMemory(cold, hot) {
|
|
76817
|
+
const byAt = /* @__PURE__ */ new Map();
|
|
76818
|
+
for (const point of cold) byAt.set(point.atMs, point);
|
|
76819
|
+
for (const point of hot) byAt.set(point.atMs, point);
|
|
76820
|
+
return [...byAt.values()].toSorted((a, b) => a.atMs - b.atMs);
|
|
76821
|
+
}
|
|
76822
|
+
var ContainerMemoryStore = class {
|
|
76823
|
+
declared = false;
|
|
76824
|
+
store;
|
|
76825
|
+
logger;
|
|
76826
|
+
nowFn;
|
|
76827
|
+
prunePageRows;
|
|
76828
|
+
constructor(deps) {
|
|
76829
|
+
this.store = deps.store;
|
|
76830
|
+
this.logger = deps.logger;
|
|
76831
|
+
this.nowFn = deps.now ?? (() => Date.now());
|
|
76832
|
+
this.prunePageRows = deps.prunePageRows ?? 2e4;
|
|
76833
|
+
}
|
|
76834
|
+
/** Idempotently declare the collection. `false` when the store refused. */
|
|
76835
|
+
async declare() {
|
|
76836
|
+
if (this.declared) return true;
|
|
76837
|
+
try {
|
|
76838
|
+
await this.store.declareCollection.mutate({
|
|
76839
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76840
|
+
columns: [...CONTAINER_MEMORY_COLUMNS],
|
|
76841
|
+
indexes: CONTAINER_MEMORY_INDEXES.map((i) => ({
|
|
76842
|
+
name: i.name,
|
|
76843
|
+
columns: [...i.columns]
|
|
76844
|
+
}))
|
|
76845
|
+
});
|
|
76846
|
+
this.declared = true;
|
|
76847
|
+
return true;
|
|
76848
|
+
} catch (err) {
|
|
76849
|
+
this.logger.warn("container memory declareCollection failed \u2014 nothing will be retained on disk", { meta: {
|
|
76850
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76851
|
+
error: require_dist10.errMsg(err)
|
|
76852
|
+
} });
|
|
76853
|
+
return false;
|
|
76854
|
+
}
|
|
76855
|
+
}
|
|
76856
|
+
/** Append ONE reading. */
|
|
76857
|
+
async append(nodeId, atMs, snapshot) {
|
|
76858
|
+
if (!await this.declare()) return 0;
|
|
76859
|
+
const record = { ...snapshotToRow(nodeId, atMs, snapshot) };
|
|
76860
|
+
try {
|
|
76861
|
+
const { inserted } = await this.store.insertMany.mutate({
|
|
76862
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76863
|
+
records: [{ data: record }]
|
|
76864
|
+
});
|
|
76865
|
+
return inserted;
|
|
76866
|
+
} catch (err) {
|
|
76867
|
+
this.logger.warn("container memory reading not retained \u2014 this interval will be missing", { meta: {
|
|
76868
|
+
nodeId,
|
|
76869
|
+
atMs,
|
|
76870
|
+
error: require_dist10.errMsg(err)
|
|
76871
|
+
} });
|
|
76872
|
+
return 0;
|
|
76873
|
+
}
|
|
76874
|
+
}
|
|
76875
|
+
/** Read one node's cold readings, oldest-first. `sinceMs` is EXCLUSIVE. */
|
|
76876
|
+
async read(nodeId, sinceMs, limitRows) {
|
|
76877
|
+
if (!await this.declare()) return [];
|
|
76878
|
+
try {
|
|
76879
|
+
const records = await this.store.query.query({
|
|
76880
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76881
|
+
filter: {
|
|
76882
|
+
where: { nodeId },
|
|
76883
|
+
whereBetween: { atMs: [sinceMs + 1, Number.MAX_SAFE_INTEGER] },
|
|
76884
|
+
orderBy: {
|
|
76885
|
+
field: "atMs",
|
|
76886
|
+
direction: "asc"
|
|
76887
|
+
},
|
|
76888
|
+
limit: limitRows
|
|
76889
|
+
}
|
|
76890
|
+
});
|
|
76891
|
+
const points = [];
|
|
76892
|
+
for (const record of records) {
|
|
76893
|
+
const row = recordToMemoryRow(record.data);
|
|
76894
|
+
if (row === null) continue;
|
|
76895
|
+
const point = rowToPoint(row);
|
|
76896
|
+
if (point !== null) points.push(point);
|
|
76897
|
+
}
|
|
76898
|
+
return points;
|
|
76899
|
+
} catch (err) {
|
|
76900
|
+
this.logger.warn("container memory cold read failed \u2014 answering from the hot window only", { meta: {
|
|
76901
|
+
nodeId,
|
|
76902
|
+
sinceMs,
|
|
76903
|
+
error: require_dist10.errMsg(err)
|
|
76904
|
+
} });
|
|
76905
|
+
return [];
|
|
76906
|
+
}
|
|
76907
|
+
}
|
|
76908
|
+
/**
|
|
76909
|
+
* Enforce BOTH bounds, oldest-first, through a bounded page each.
|
|
76910
|
+
*
|
|
76911
|
+
* Not rate-limited here: it is driven by the process store's own prune
|
|
76912
|
+
* cadence, so it runs at most once a minute by construction and a second
|
|
76913
|
+
* timer would be a second thing to get wrong.
|
|
76914
|
+
*/
|
|
76915
|
+
async prune(nodeIds, retention) {
|
|
76916
|
+
if (!await this.declare()) return null;
|
|
76917
|
+
const ageCutoff = this.nowFn() - retention.retentionHours * 36e5;
|
|
76918
|
+
let deletedByAge = 0;
|
|
76919
|
+
let rowsExamined = 0;
|
|
76920
|
+
for (const nodeId of nodeIds) {
|
|
76921
|
+
const outcome = await this.deleteOldestPage(nodeId, ageCutoff, this.prunePageRows);
|
|
76922
|
+
deletedByAge += outcome.deleted;
|
|
76923
|
+
rowsExamined += outcome.examined;
|
|
76924
|
+
}
|
|
76925
|
+
const total = await this.count();
|
|
76926
|
+
const excess = total === null ? 0 : total - CONTAINER_MEMORY_MAX_ROWS;
|
|
76927
|
+
let deletedByCap = 0;
|
|
76928
|
+
if (excess > 0) {
|
|
76929
|
+
let remaining = Math.min(excess, this.prunePageRows);
|
|
76930
|
+
for (const nodeId of nodeIds) {
|
|
76931
|
+
if (remaining <= 0) break;
|
|
76932
|
+
const outcome = await this.deleteOldestPage(nodeId, null, remaining);
|
|
76933
|
+
deletedByCap += outcome.deleted;
|
|
76934
|
+
remaining -= outcome.deleted;
|
|
76935
|
+
rowsExamined += outcome.examined;
|
|
76936
|
+
}
|
|
76937
|
+
this.logger.warn("container memory ROW CAP bit \u2014 evicting the oldest readings", { meta: {
|
|
76938
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76939
|
+
rows: total,
|
|
76940
|
+
cap: CONTAINER_MEMORY_MAX_ROWS,
|
|
76941
|
+
over: excess,
|
|
76942
|
+
deleted: deletedByCap,
|
|
76943
|
+
hint: "lower the retention or the sampling cadence \u2014 the cap is the guarantee, not the intention"
|
|
76944
|
+
} });
|
|
76945
|
+
}
|
|
76946
|
+
return {
|
|
76947
|
+
deletedByAge,
|
|
76948
|
+
deletedByCap,
|
|
76949
|
+
rowsExamined,
|
|
76950
|
+
capBit: excess > 0
|
|
76951
|
+
};
|
|
76952
|
+
}
|
|
76953
|
+
/** Total rows, or `null` when the store could not answer. */
|
|
76954
|
+
async count() {
|
|
76955
|
+
if (!await this.declare()) return null;
|
|
76956
|
+
try {
|
|
76957
|
+
return await this.store.count.query({ collection: CONTAINER_MEMORY_COLLECTION });
|
|
76958
|
+
} catch (err) {
|
|
76959
|
+
this.logger.warn("container memory count failed \u2014 the row cap is not enforced this pass", { meta: { error: require_dist10.errMsg(err) } });
|
|
76960
|
+
return null;
|
|
76961
|
+
}
|
|
76962
|
+
}
|
|
76963
|
+
/**
|
|
76964
|
+
* Delete this node's oldest rows, at most one page's worth.
|
|
76965
|
+
*
|
|
76966
|
+
* `cutoff === null` means "the oldest rows, whatever their age" — the
|
|
76967
|
+
* row-cap path, which is bounded by `limitRows` instead. Either way the page
|
|
76968
|
+
* is READ FIRST, ordered by the one index, and its last `atMs` becomes the
|
|
76969
|
+
* effective cutoff for a single bounded delete. An unbounded `deleteWhere`
|
|
76970
|
+
* would be one statement and one stalling transaction the first time a
|
|
76971
|
+
* retention is lowered.
|
|
76972
|
+
*/
|
|
76973
|
+
async deleteOldestPage(nodeId, cutoff, limitRows) {
|
|
76974
|
+
if (limitRows <= 0) return {
|
|
76975
|
+
deleted: 0,
|
|
76976
|
+
examined: 0
|
|
76977
|
+
};
|
|
76978
|
+
try {
|
|
76979
|
+
const page = await this.store.query.query({
|
|
76980
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76981
|
+
filter: {
|
|
76982
|
+
where: { nodeId },
|
|
76983
|
+
...cutoff === null ? {} : { whereBetween: { atMs: [0, cutoff] } },
|
|
76984
|
+
orderBy: {
|
|
76985
|
+
field: "atMs",
|
|
76986
|
+
direction: "asc"
|
|
76987
|
+
},
|
|
76988
|
+
limit: Math.min(limitRows, this.prunePageRows)
|
|
76989
|
+
},
|
|
76990
|
+
columns: ["atMs"]
|
|
76991
|
+
});
|
|
76992
|
+
if (page.length === 0) return {
|
|
76993
|
+
deleted: 0,
|
|
76994
|
+
examined: 0
|
|
76995
|
+
};
|
|
76996
|
+
const effectiveCutoff = Number(page.at(-1)?.data["atMs"]);
|
|
76997
|
+
if (!Number.isFinite(effectiveCutoff)) return {
|
|
76998
|
+
deleted: 0,
|
|
76999
|
+
examined: page.length
|
|
77000
|
+
};
|
|
77001
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
77002
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
77003
|
+
filter: {
|
|
77004
|
+
where: { nodeId },
|
|
77005
|
+
whereBetween: { atMs: [0, effectiveCutoff] }
|
|
77006
|
+
}
|
|
77007
|
+
});
|
|
77008
|
+
return {
|
|
77009
|
+
deleted,
|
|
77010
|
+
examined: page.length
|
|
77011
|
+
};
|
|
77012
|
+
} catch (err) {
|
|
77013
|
+
this.logger.warn("container memory prune failed \u2014 the table keeps growing this pass", { meta: {
|
|
77014
|
+
nodeId,
|
|
77015
|
+
cutoff,
|
|
77016
|
+
error: require_dist10.errMsg(err)
|
|
77017
|
+
} });
|
|
77018
|
+
return {
|
|
77019
|
+
deleted: 0,
|
|
77020
|
+
examined: 0
|
|
77021
|
+
};
|
|
77022
|
+
}
|
|
77023
|
+
}
|
|
77024
|
+
};
|
|
76133
77025
|
var IS_DARWIN = node_os.platform() === "darwin";
|
|
76134
77026
|
var IS_LINUX = node_os.platform() === "linux";
|
|
76135
77027
|
var NativeMetricsProvider = class {
|
|
@@ -76594,247 +77486,6 @@ var require_native_metrics_addon = __commonJS({
|
|
|
76594
77486
|
});
|
|
76595
77487
|
});
|
|
76596
77488
|
}
|
|
76597
|
-
var LoadPartition = class {
|
|
76598
|
-
capacity;
|
|
76599
|
-
slots;
|
|
76600
|
-
head = 0;
|
|
76601
|
-
count = 0;
|
|
76602
|
-
rows = 0;
|
|
76603
|
-
/** Newest retained sample's timestamp — the monotonic gate for `push`. */
|
|
76604
|
-
newestAtMs = null;
|
|
76605
|
-
constructor(capacity) {
|
|
76606
|
-
this.capacity = capacity;
|
|
76607
|
-
this.slots = Array.from({ length: capacity });
|
|
76608
|
-
}
|
|
76609
|
-
/**
|
|
76610
|
-
* Accept a sample.
|
|
76611
|
-
*
|
|
76612
|
-
* A sample at or before the newest one already held is REFUSED. The bus drops
|
|
76613
|
-
* a node's own broadcast echo, but a cross-node redelivery or a replayed
|
|
76614
|
-
* subscription must not be able to double a point — and idempotence here is
|
|
76615
|
-
* what lets every reader above treat the series as a set.
|
|
76616
|
-
*
|
|
76617
|
-
* `accepted` is reported separately from `rowDelta` on purpose: a full ring
|
|
76618
|
-
* that evicts a sample of the same size has a delta of zero and is not a
|
|
76619
|
-
* refusal, and conflating the two would silently stop advancing the write
|
|
76620
|
-
* ordinal on a steady-state cluster.
|
|
76621
|
-
*/
|
|
76622
|
-
push(sample) {
|
|
76623
|
-
if (this.newestAtMs !== null && sample.atMs <= this.newestAtMs) return {
|
|
76624
|
-
accepted: false,
|
|
76625
|
-
rowDelta: 0
|
|
76626
|
-
};
|
|
76627
|
-
const evicted = this.count === this.capacity ? this.slots[this.head]?.processes.length ?? 0 : 0;
|
|
76628
|
-
this.slots[this.head] = sample;
|
|
76629
|
-
this.head = (this.head + 1) % this.capacity;
|
|
76630
|
-
if (this.count < this.capacity) this.count++;
|
|
76631
|
-
this.newestAtMs = sample.atMs;
|
|
76632
|
-
const rowDelta = sample.processes.length - evicted;
|
|
76633
|
-
this.rows += rowDelta;
|
|
76634
|
-
return {
|
|
76635
|
-
accepted: true,
|
|
76636
|
-
rowDelta
|
|
76637
|
-
};
|
|
76638
|
-
}
|
|
76639
|
-
/** Drop the oldest sample. Returns the rows reclaimed (0 when empty). */
|
|
76640
|
-
dropOldest() {
|
|
76641
|
-
if (this.count === 0) return 0;
|
|
76642
|
-
const index = (this.head - this.count + this.capacity) % this.capacity;
|
|
76643
|
-
const victim = this.slots[index];
|
|
76644
|
-
this.slots[index] = void 0;
|
|
76645
|
-
this.count--;
|
|
76646
|
-
const reclaimed = victim?.processes.length ?? 0;
|
|
76647
|
-
this.rows -= reclaimed;
|
|
76648
|
-
if (this.count === 0) this.newestAtMs = null;
|
|
76649
|
-
return reclaimed;
|
|
76650
|
-
}
|
|
76651
|
-
/** Oldest-first, optionally only what is strictly newer than `sinceMs`. */
|
|
76652
|
-
list(sinceMs) {
|
|
76653
|
-
const out = [];
|
|
76654
|
-
for (let i = 0; i < this.count; i++) {
|
|
76655
|
-
const index = (this.head - this.count + i + this.capacity) % this.capacity;
|
|
76656
|
-
const sample = this.slots[index];
|
|
76657
|
-
if (sample === void 0) continue;
|
|
76658
|
-
if (sinceMs !== void 0 && sample.atMs <= sinceMs) continue;
|
|
76659
|
-
out.push(sample);
|
|
76660
|
-
}
|
|
76661
|
-
return out;
|
|
76662
|
-
}
|
|
76663
|
-
size() {
|
|
76664
|
-
return this.count;
|
|
76665
|
-
}
|
|
76666
|
-
rowCount() {
|
|
76667
|
-
return this.rows;
|
|
76668
|
-
}
|
|
76669
|
-
oldestAtMs() {
|
|
76670
|
-
if (this.count === 0) return null;
|
|
76671
|
-
const index = (this.head - this.count + this.capacity) % this.capacity;
|
|
76672
|
-
return this.slots[index]?.atMs ?? null;
|
|
76673
|
-
}
|
|
76674
|
-
lastWriteAtMs() {
|
|
76675
|
-
return this.newestAtMs;
|
|
76676
|
-
}
|
|
76677
|
-
};
|
|
76678
|
-
var NodeLoadRing = class {
|
|
76679
|
-
partitions = /* @__PURE__ */ new Map();
|
|
76680
|
-
/**
|
|
76681
|
-
* Node id → write ordinal of its last accepted sample. A monotonic counter,
|
|
76682
|
-
* not a clock: partition eviction must follow the order writes actually
|
|
76683
|
-
* happened, and node clocks disagree.
|
|
76684
|
-
*/
|
|
76685
|
-
lastWriteSeq = /* @__PURE__ */ new Map();
|
|
76686
|
-
writeSeq = 0;
|
|
76687
|
-
totalRows = 0;
|
|
76688
|
-
samplesPerNode;
|
|
76689
|
-
maxTotalProcessRows;
|
|
76690
|
-
maxNodes;
|
|
76691
|
-
idleEvictionMs;
|
|
76692
|
-
now;
|
|
76693
|
-
constructor(options = {}) {
|
|
76694
|
-
this.samplesPerNode = options.samplesPerNode ?? 180;
|
|
76695
|
-
this.maxTotalProcessRows = options.maxTotalProcessRows ?? 32e3;
|
|
76696
|
-
this.maxNodes = options.maxNodes ?? 16;
|
|
76697
|
-
this.idleEvictionMs = options.idleEvictionMs ?? 36e5;
|
|
76698
|
-
this.now = options.now ?? Date.now;
|
|
76699
|
-
}
|
|
76700
|
-
/**
|
|
76701
|
-
* Retain one snapshot. `processes` is stored by reference — the payload is
|
|
76702
|
-
* already an immutable arrival off the bus, and copying it would double the
|
|
76703
|
-
* measured cost for nothing.
|
|
76704
|
-
*
|
|
76705
|
-
* Returns whether the sample was ACCEPTED — that is, whether it was new
|
|
76706
|
-
* rather than a replay of a timestamp this node has already delivered.
|
|
76707
|
-
*
|
|
76708
|
-
* The return value is not diagnostics. It is the idempotence gate the
|
|
76709
|
-
* DURABLE tier rides on (`load-series-store.ts`): only an accepted sample is
|
|
76710
|
-
* appended to the table, which is what lets that table carry an `INTEGER`
|
|
76711
|
-
* rowid key instead of a composite unique index over two million rows. The
|
|
76712
|
-
* gate is one monotonic comparison in memory; the index it replaces was
|
|
76713
|
-
* measured at ~40 bytes per row.
|
|
76714
|
-
*/
|
|
76715
|
-
record(nodeId, atMs, processes) {
|
|
76716
|
-
if (nodeId.length === 0) return false;
|
|
76717
|
-
this.sweepIdle();
|
|
76718
|
-
let partition = this.partitions.get(nodeId);
|
|
76719
|
-
if (partition === void 0) {
|
|
76720
|
-
partition = new LoadPartition(this.samplesPerNode);
|
|
76721
|
-
this.partitions.set(nodeId, partition);
|
|
76722
|
-
}
|
|
76723
|
-
const outcome = partition.push({
|
|
76724
|
-
atMs,
|
|
76725
|
-
processes
|
|
76726
|
-
});
|
|
76727
|
-
if (!outcome.accepted) {
|
|
76728
|
-
if (partition.size() === 0) this.partitions.delete(nodeId);
|
|
76729
|
-
return false;
|
|
76730
|
-
}
|
|
76731
|
-
this.totalRows += outcome.rowDelta;
|
|
76732
|
-
this.lastWriteSeq.set(nodeId, ++this.writeSeq);
|
|
76733
|
-
if (this.partitions.size > this.maxNodes) this.evictPartitions(this.partitions.size - this.maxNodes, nodeId);
|
|
76734
|
-
this.enforceRowBudget();
|
|
76735
|
-
return true;
|
|
76736
|
-
}
|
|
76737
|
-
/**
|
|
76738
|
-
* Read one node's retained series, oldest-first.
|
|
76739
|
-
*
|
|
76740
|
-
* `sinceMs` is EXCLUSIVE: a caller passes the newest timestamp it already
|
|
76741
|
-
* holds and gets back only what it is missing. That is the whole contract
|
|
76742
|
-
* that lets the admin UI seed from here and then continue live without
|
|
76743
|
-
* doubling a point it already drew.
|
|
76744
|
-
*
|
|
76745
|
-
* A node nobody has recorded answers with an empty series, not an error —
|
|
76746
|
-
* unknown is the truth about a node that has not reported.
|
|
76747
|
-
*/
|
|
76748
|
-
read(nodeId, sinceMs) {
|
|
76749
|
-
this.sweepIdle();
|
|
76750
|
-
const partition = this.partitions.get(nodeId);
|
|
76751
|
-
if (partition === void 0) return {
|
|
76752
|
-
nodeId,
|
|
76753
|
-
samples: [],
|
|
76754
|
-
retainedSamples: 0,
|
|
76755
|
-
oldestAtMs: null,
|
|
76756
|
-
capacity: this.samplesPerNode
|
|
76757
|
-
};
|
|
76758
|
-
return {
|
|
76759
|
-
nodeId,
|
|
76760
|
-
samples: partition.list(sinceMs),
|
|
76761
|
-
retainedSamples: partition.size(),
|
|
76762
|
-
oldestAtMs: partition.oldestAtMs(),
|
|
76763
|
-
capacity: this.samplesPerNode
|
|
76764
|
-
};
|
|
76765
|
-
}
|
|
76766
|
-
/** Node ids with a live partition. Observability for the fleet bound. */
|
|
76767
|
-
nodeIds() {
|
|
76768
|
-
return [...this.partitions.keys()];
|
|
76769
|
-
}
|
|
76770
|
-
/** Process rows retained across every partition — the number that IS memory. */
|
|
76771
|
-
rowCount() {
|
|
76772
|
-
return this.totalRows;
|
|
76773
|
-
}
|
|
76774
|
-
/** Samples retained across every partition. */
|
|
76775
|
-
sampleCount() {
|
|
76776
|
-
let total = 0;
|
|
76777
|
-
for (const partition of this.partitions.values()) total += partition.size();
|
|
76778
|
-
return total;
|
|
76779
|
-
}
|
|
76780
|
-
/**
|
|
76781
|
-
* Drop partitions whose newest sample is older than the retention window.
|
|
76782
|
-
*
|
|
76783
|
-
* Lazy, on write and on read — never a timer. A timer would be a new
|
|
76784
|
-
* periodic cost in a subsystem whose entire premise is that it adds none,
|
|
76785
|
-
* and a ring that nobody writes to and nobody reads is not growing either.
|
|
76786
|
-
*/
|
|
76787
|
-
sweepIdle() {
|
|
76788
|
-
const cutoff = this.now() - this.idleEvictionMs;
|
|
76789
|
-
for (const [nodeId, partition] of [...this.partitions.entries()]) {
|
|
76790
|
-
const lastWrite = partition.lastWriteAtMs();
|
|
76791
|
-
if (lastWrite !== null && lastWrite > cutoff) continue;
|
|
76792
|
-
this.dropPartition(nodeId);
|
|
76793
|
-
}
|
|
76794
|
-
}
|
|
76795
|
-
/**
|
|
76796
|
-
* Bring the fleet back under the row budget by dropping the OLDEST sample of
|
|
76797
|
-
* the HEAVIEST partition, repeatedly.
|
|
76798
|
-
*
|
|
76799
|
-
* Terminates: every iteration removes one sample from a non-empty partition,
|
|
76800
|
-
* and the population of samples is finite and strictly decreasing.
|
|
76801
|
-
*/
|
|
76802
|
-
enforceRowBudget() {
|
|
76803
|
-
while (this.totalRows > this.maxTotalProcessRows) {
|
|
76804
|
-
const victim = this.heaviestPartition();
|
|
76805
|
-
if (victim === null) return;
|
|
76806
|
-
const [nodeId, partition] = victim;
|
|
76807
|
-
this.totalRows -= partition.dropOldest();
|
|
76808
|
-
if (partition.size() === 0) this.dropPartition(nodeId);
|
|
76809
|
-
}
|
|
76810
|
-
}
|
|
76811
|
-
heaviestPartition() {
|
|
76812
|
-
let best = null;
|
|
76813
|
-
for (const entry of this.partitions.entries()) {
|
|
76814
|
-
if (entry[1].size() === 0) continue;
|
|
76815
|
-
if (best === null || entry[1].rowCount() > best[1].rowCount()) best = entry;
|
|
76816
|
-
}
|
|
76817
|
-
return best;
|
|
76818
|
-
}
|
|
76819
|
-
/** Drop `count` whole partitions, least-recently-written first. */
|
|
76820
|
-
evictPartitions(count, protectedNodeId) {
|
|
76821
|
-
const order = [...this.partitions.keys()].filter((nodeId) => nodeId !== protectedNodeId).toSorted((a, b) => (this.lastWriteSeq.get(a) ?? 0) - (this.lastWriteSeq.get(b) ?? 0));
|
|
76822
|
-
let remaining = count;
|
|
76823
|
-
for (const nodeId of order) {
|
|
76824
|
-
if (remaining <= 0) return;
|
|
76825
|
-
this.dropPartition(nodeId);
|
|
76826
|
-
remaining -= 1;
|
|
76827
|
-
}
|
|
76828
|
-
}
|
|
76829
|
-
/** Remove a partition and everything that indexes it. */
|
|
76830
|
-
dropPartition(nodeId) {
|
|
76831
|
-
const partition = this.partitions.get(nodeId);
|
|
76832
|
-
if (partition === void 0) return;
|
|
76833
|
-
this.totalRows -= partition.rowCount();
|
|
76834
|
-
this.partitions.delete(nodeId);
|
|
76835
|
-
this.lastWriteSeq.delete(nodeId);
|
|
76836
|
-
}
|
|
76837
|
-
};
|
|
76838
77489
|
var LOAD_SERIES_COLLECTION = "metrics:node-load-samples";
|
|
76839
77490
|
var DEFAULT_MAX_ROWS = 5e5;
|
|
76840
77491
|
var LOAD_SERIES_COLUMNS = [
|
|
@@ -77545,6 +78196,20 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77545
78196
|
* per sample, to reach that very same table.
|
|
77546
78197
|
*/
|
|
77547
78198
|
loadStore = null;
|
|
78199
|
+
/**
|
|
78200
|
+
* The container's OWN memory, sampled on the process-snapshot tick.
|
|
78201
|
+
*
|
|
78202
|
+
* It rides that tick rather than a timer of its own so a memory reading and
|
|
78203
|
+
* the process rows it must be compared against carry the SAME timestamp —
|
|
78204
|
+
* the whole question is a subtraction, and two clocks make it an estimate.
|
|
78205
|
+
* Constructed in `onInitialize`, where the logger exists; it resolves the
|
|
78206
|
+
* cgroup once and latches.
|
|
78207
|
+
*/
|
|
78208
|
+
memoryProbe = null;
|
|
78209
|
+
/** Hot tier for the container reading. Bounds in `container-memory-series.ts`. */
|
|
78210
|
+
memoryRing = new ContainerMemoryRing();
|
|
78211
|
+
/** Cold tier, HUB only — same reasoning as `loadStore`. */
|
|
78212
|
+
memoryStore = null;
|
|
77548
78213
|
/** The resolved knobs. Re-resolved on every settings write. */
|
|
77549
78214
|
loadConfig = {
|
|
77550
78215
|
cadenceSec: 10,
|
|
@@ -77617,13 +78282,20 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77617
78282
|
dumpHeapSnapshot: (params) => this.dumpHeapSnapshot(params)
|
|
77618
78283
|
};
|
|
77619
78284
|
this.applyLoadSeriesConfig();
|
|
77620
|
-
if (this.isHub)
|
|
77621
|
-
|
|
77622
|
-
|
|
77623
|
-
|
|
78285
|
+
if (this.isHub) {
|
|
78286
|
+
this.loadStore = new LoadSeriesStore({
|
|
78287
|
+
store: this.ctx.api.settingsStore,
|
|
78288
|
+
logger: this.ctx.logger.child("LoadSeries")
|
|
78289
|
+
});
|
|
78290
|
+
this.memoryStore = new ContainerMemoryStore({
|
|
78291
|
+
store: this.ctx.api.settingsStore,
|
|
78292
|
+
logger: this.ctx.logger.child("ContainerMemory")
|
|
78293
|
+
});
|
|
78294
|
+
}
|
|
78295
|
+
this.memoryProbe = new ContainerMemoryProbe({ logger: this.ctx.logger.child("ContainerMemory") });
|
|
77624
78296
|
this.snapshotTimer = setInterval(() => this.emitResourcesSnapshot(), METRICS_SNAPSHOT_INTERVAL_MS);
|
|
77625
78297
|
this.startProcessSnapshotTimer();
|
|
77626
|
-
this.ctx.addDisposer(this.ctx.eventBus.subscribe({ category: require_dist10.EventCategory.MetricsNodeProcessesSnapshot }, (event) => this.retainSnapshot(event.data.nodeId, event.data.timestamp, event.data.processes)));
|
|
78298
|
+
this.ctx.addDisposer(this.ctx.eventBus.subscribe({ category: require_dist10.EventCategory.MetricsNodeProcessesSnapshot }, (event) => this.retainSnapshot(event.data.nodeId, event.data.timestamp, event.data.processes, event.data.containerMemory)));
|
|
77627
78299
|
return [{
|
|
77628
78300
|
capability: require_dist10.metricsProviderCapability,
|
|
77629
78301
|
provider: composed
|
|
@@ -77641,19 +78313,31 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77641
78313
|
* The durable append is fire-and-forget: a storage stall must cost a gap in
|
|
77642
78314
|
* the cold window, never a blocked event-bus handler. Every failure logs.
|
|
77643
78315
|
*/
|
|
77644
|
-
retainSnapshot(nodeId, atMs, processes) {
|
|
78316
|
+
retainSnapshot(nodeId, atMs, processes, containerMemory) {
|
|
77645
78317
|
const retained = processes.map((p) => NativeMetricsAddon2.toRetained(p));
|
|
77646
78318
|
if (!this.loadRing.record(nodeId, atMs, retained)) return;
|
|
77647
78319
|
this.observedRowsByNode.set(nodeId, retained.length);
|
|
78320
|
+
if (containerMemory !== null) this.memoryRing.record(nodeId, atMs, containerMemory);
|
|
77648
78321
|
const store = this.loadStore;
|
|
77649
78322
|
if (store === null) return;
|
|
77650
|
-
store.append(nodeId, atMs, retained).then(() => store.prune([...this.observedRowsByNode.keys()], this.loadConfig)).
|
|
78323
|
+
store.append(nodeId, atMs, retained).then(() => store.prune([...this.observedRowsByNode.keys()], this.loadConfig)).then(async (outcome) => {
|
|
78324
|
+
if (outcome === null) return;
|
|
78325
|
+
await this.memoryStore?.prune([...this.observedRowsByNode.keys()], this.loadConfig);
|
|
78326
|
+
}).catch((err) => {
|
|
77651
78327
|
this.ctx.logger.warn("durable load series write failed", { meta: {
|
|
77652
78328
|
nodeId,
|
|
77653
78329
|
atMs,
|
|
77654
78330
|
error: err instanceof Error ? err.message : String(err)
|
|
77655
78331
|
} });
|
|
77656
78332
|
});
|
|
78333
|
+
if (containerMemory === null) return;
|
|
78334
|
+
this.memoryStore?.append(nodeId, atMs, containerMemory).catch((err) => {
|
|
78335
|
+
this.ctx.logger.warn("durable container memory write failed", { meta: {
|
|
78336
|
+
nodeId,
|
|
78337
|
+
atMs,
|
|
78338
|
+
error: err instanceof Error ? err.message : String(err)
|
|
78339
|
+
} });
|
|
78340
|
+
});
|
|
77657
78341
|
}
|
|
77658
78342
|
/**
|
|
77659
78343
|
* The ONE reader, over both tiers.
|
|
@@ -77682,6 +78366,14 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77682
78366
|
return {
|
|
77683
78367
|
nodeId: params.forNodeId,
|
|
77684
78368
|
series: merged.series,
|
|
78369
|
+
containerMemory: await this.readContainerMemory({
|
|
78370
|
+
forNodeId: params.forNodeId,
|
|
78371
|
+
sinceMs,
|
|
78372
|
+
...params.sinceMs !== void 0 ? { exclusiveSinceMs: params.sinceMs } : {},
|
|
78373
|
+
bucketMs: merged.bucketMs,
|
|
78374
|
+
cadenceMs,
|
|
78375
|
+
origin: merged.oldestAtMs
|
|
78376
|
+
}),
|
|
77685
78377
|
bucketMs: merged.bucketMs,
|
|
77686
78378
|
retainedSamples: merged.retainedSamples,
|
|
77687
78379
|
oldestAtMs: merged.oldestAtMs,
|
|
@@ -77690,6 +78382,24 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77690
78382
|
};
|
|
77691
78383
|
}
|
|
77692
78384
|
/**
|
|
78385
|
+
* The container's memory over the same window, on the same x-axis.
|
|
78386
|
+
*
|
|
78387
|
+
* Both tiers, merged and deduped on `atMs` like the process series, then
|
|
78388
|
+
* reduced against the SAME `bucketMs` and the SAME origin — otherwise the
|
|
78389
|
+
* two series in one answer would sit on two different axes and the
|
|
78390
|
+
* subtraction they exist for would be an eyeball.
|
|
78391
|
+
*
|
|
78392
|
+
* An EMPTY answer means the node has no cgroup, or nothing has been retained
|
|
78393
|
+
* for it yet. It never means zero.
|
|
78394
|
+
*/
|
|
78395
|
+
async readContainerMemory(params) {
|
|
78396
|
+
const hot = this.memoryRing.read(params.forNodeId, params.exclusiveSinceMs);
|
|
78397
|
+
const store = this.memoryStore;
|
|
78398
|
+
const merged = mergeContainerMemory(store === null ? [] : await store.read(params.forNodeId, params.sinceMs, CONTAINER_MEMORY_MAX_ROWS), hot);
|
|
78399
|
+
if (params.bucketMs <= params.cadenceMs) return merged;
|
|
78400
|
+
return reduceContainerMemory(merged, params.bucketMs, params.origin ?? 0);
|
|
78401
|
+
}
|
|
78402
|
+
/**
|
|
77693
78403
|
* Re-resolve the knobs and restate the fixed cadence.
|
|
77694
78404
|
*
|
|
77695
78405
|
* A REFUSED value (out of 5-60 s, or out of 1-72 h) leaves the previous
|
|
@@ -77806,6 +78516,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77806
78516
|
const timestamp = Date.now();
|
|
77807
78517
|
try {
|
|
77808
78518
|
const processes = await this.listNodeProcesses();
|
|
78519
|
+
const containerMemory = this.memoryProbe?.sample() ?? null;
|
|
77809
78520
|
eventBus.emit(require_dist10.createEvent(require_dist10.EventCategory.MetricsNodeProcessesSnapshot, {
|
|
77810
78521
|
type: "node",
|
|
77811
78522
|
id: nodeId,
|
|
@@ -77813,7 +78524,8 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77813
78524
|
}, {
|
|
77814
78525
|
nodeId,
|
|
77815
78526
|
processes,
|
|
77816
|
-
timestamp
|
|
78527
|
+
timestamp,
|
|
78528
|
+
containerMemory
|
|
77817
78529
|
}));
|
|
77818
78530
|
} catch (err) {
|
|
77819
78531
|
this.ctx.logger.warn("process snapshot skipped \u2014 this interval will be missing", { meta: {
|
|
@@ -78079,14 +78791,14 @@ var require_filesystem_storage_addon = __commonJS({
|
|
|
78079
78791
|
[Symbol.toStringTag]: { value: "Module" }
|
|
78080
78792
|
});
|
|
78081
78793
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
78082
|
-
var require_dist10 =
|
|
78794
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
78083
78795
|
var node_crypto = __require("crypto");
|
|
78084
78796
|
var node_fs_promises = __require("fs/promises");
|
|
78085
78797
|
var node_path = __require("path");
|
|
78086
78798
|
node_path = require_chunk.__toESM(node_path);
|
|
78087
|
-
var node_os = __require("os");
|
|
78088
78799
|
var node_fs = __require("fs");
|
|
78089
78800
|
node_fs = require_chunk.__toESM(node_fs);
|
|
78801
|
+
var node_os = __require("os");
|
|
78090
78802
|
function isWithinAllowedRoots(candidate, allowedRoots) {
|
|
78091
78803
|
const norm = (0, node_path.resolve)(candidate);
|
|
78092
78804
|
return allowedRoots.some((root) => {
|
|
@@ -79195,8 +79907,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
79195
79907
|
[Symbol.toStringTag]: { value: "Module" }
|
|
79196
79908
|
});
|
|
79197
79909
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
79198
|
-
var require_dist10 =
|
|
79199
|
-
var require_retired_settings_keys =
|
|
79910
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
79911
|
+
var require_retired_settings_keys = require_retired_settings_keys_CBU4_TJO();
|
|
79200
79912
|
var node_crypto = __require("crypto");
|
|
79201
79913
|
var node_fs = __require("fs");
|
|
79202
79914
|
var node_module = __require("module");
|
|
@@ -81475,7 +82187,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
81475
82187
|
[Symbol.toStringTag]: { value: "Module" }
|
|
81476
82188
|
});
|
|
81477
82189
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
81478
|
-
var require_dist10 =
|
|
82190
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
81479
82191
|
var node_crypto = __require("crypto");
|
|
81480
82192
|
var node_fs_promises = __require("fs/promises");
|
|
81481
82193
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -83356,7 +84068,7 @@ var require_system_config_addon = __commonJS({
|
|
|
83356
84068
|
[Symbol.toStringTag]: { value: "Module" }
|
|
83357
84069
|
});
|
|
83358
84070
|
require_chunk_Cek0wNdY();
|
|
83359
|
-
var require_dist10 =
|
|
84071
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
83360
84072
|
var SECTION_TITLES = {
|
|
83361
84073
|
server: "Server",
|
|
83362
84074
|
auth: "Authentication"
|
|
@@ -101417,7 +102129,7 @@ var require_winston_logging = __commonJS({
|
|
|
101417
102129
|
[Symbol.toStringTag]: { value: "Module" }
|
|
101418
102130
|
});
|
|
101419
102131
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
101420
|
-
var require_dist10 =
|
|
102132
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
101421
102133
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
101422
102134
|
var node_path = __require("path");
|
|
101423
102135
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -102447,9 +103159,9 @@ var require_file_data_plane_DO8KbxCe = __commonJS({
|
|
|
102447
103159
|
}
|
|
102448
103160
|
});
|
|
102449
103161
|
|
|
102450
|
-
// ../system/dist/tls-
|
|
102451
|
-
var
|
|
102452
|
-
"../system/dist/tls-
|
|
103162
|
+
// ../system/dist/tls-BxQlomxd.js
|
|
103163
|
+
var require_tls_BxQlomxd = __commonJS({
|
|
103164
|
+
"../system/dist/tls-BxQlomxd.js"(exports) {
|
|
102453
103165
|
"use strict";
|
|
102454
103166
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
102455
103167
|
var node_crypto = __require("crypto");
|
|
@@ -102457,9 +103169,9 @@ var require_tls_u8QCJCFE = __commonJS({
|
|
|
102457
103169
|
var node_path = __require("path");
|
|
102458
103170
|
var node_child_process = __require("child_process");
|
|
102459
103171
|
var node_util = __require("util");
|
|
103172
|
+
var node_fs = __require("fs");
|
|
102460
103173
|
var node_os = __require("os");
|
|
102461
103174
|
node_os = require_chunk.__toESM(node_os);
|
|
102462
|
-
var node_fs = __require("fs");
|
|
102463
103175
|
var node_http = __require("http");
|
|
102464
103176
|
var node_net = __require("net");
|
|
102465
103177
|
var DEFAULT_HTTP_PORT = 4480;
|
|
@@ -113905,12 +114617,12 @@ var require_dist2 = __commonJS({
|
|
|
113905
114617
|
}
|
|
113906
114618
|
});
|
|
113907
114619
|
|
|
113908
|
-
// ../system/dist/manifest-python-deps-
|
|
113909
|
-
var
|
|
113910
|
-
"../system/dist/manifest-python-deps-
|
|
114620
|
+
// ../system/dist/manifest-python-deps-CRTx_j4k.js
|
|
114621
|
+
var require_manifest_python_deps_CRTx_j4k = __commonJS({
|
|
114622
|
+
"../system/dist/manifest-python-deps-CRTx_j4k.js"(exports) {
|
|
113911
114623
|
"use strict";
|
|
113912
114624
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
113913
|
-
|
|
114625
|
+
require_dist_C7uZ_fOb();
|
|
113914
114626
|
var node_crypto = __require("crypto");
|
|
113915
114627
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
113916
114628
|
var _camstack_types_node = require_node();
|
|
@@ -113919,10 +114631,10 @@ var require_manifest_python_deps_CcDe4S2Z = __commonJS({
|
|
|
113919
114631
|
node_path = require_chunk.__toESM(node_path);
|
|
113920
114632
|
var node_child_process = __require("child_process");
|
|
113921
114633
|
var node_util = __require("util");
|
|
113922
|
-
var node_os = __require("os");
|
|
113923
|
-
node_os = require_chunk.__toESM(node_os);
|
|
113924
114634
|
var node_fs = __require("fs");
|
|
113925
114635
|
node_fs = require_chunk.__toESM(node_fs);
|
|
114636
|
+
var node_os = __require("os");
|
|
114637
|
+
node_os = require_chunk.__toESM(node_os);
|
|
113926
114638
|
var node_http = __require("http");
|
|
113927
114639
|
var node_v8 = __require("v8");
|
|
113928
114640
|
node_v8 = require_chunk.__toESM(node_v8);
|
|
@@ -125356,7 +126068,7 @@ var require_dist3 = __commonJS({
|
|
|
125356
126068
|
"use strict";
|
|
125357
126069
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
125358
126070
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
125359
|
-
var require_dist10 =
|
|
126071
|
+
var require_dist10 = require_dist_C7uZ_fOb();
|
|
125360
126072
|
var require_builtins_alerts_alerts_addon = require_alerts_addon();
|
|
125361
126073
|
require_alerts();
|
|
125362
126074
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
@@ -125381,8 +126093,8 @@ var require_dist3 = __commonJS({
|
|
|
125381
126093
|
require_system_config();
|
|
125382
126094
|
var require_builtins_winston_logging_index = require_winston_logging();
|
|
125383
126095
|
var require_file_data_plane = require_file_data_plane_DO8KbxCe();
|
|
125384
|
-
var require_tls$1 =
|
|
125385
|
-
var require_manifest_python_deps =
|
|
126096
|
+
var require_tls$1 = require_tls_BxQlomxd();
|
|
126097
|
+
var require_manifest_python_deps = require_manifest_python_deps_CRTx_j4k();
|
|
125386
126098
|
var require_resource_monitor = require_resource_monitor_CdnzxBLP();
|
|
125387
126099
|
var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
|
|
125388
126100
|
var zod = require_zod();
|
|
@@ -125394,11 +126106,11 @@ var require_dist3 = __commonJS({
|
|
|
125394
126106
|
var node_child_process = __require("child_process");
|
|
125395
126107
|
var node_util = __require("util");
|
|
125396
126108
|
node_util = require_chunk.__toESM(node_util);
|
|
125397
|
-
var node_os = __require("os");
|
|
125398
|
-
node_os = require_chunk.__toESM(node_os);
|
|
125399
126109
|
var node_fs = __require("fs");
|
|
125400
126110
|
var node_fs$1 = require_chunk.__toESM(node_fs, 1);
|
|
125401
126111
|
node_fs = require_chunk.__toESM(node_fs);
|
|
126112
|
+
var node_os = __require("os");
|
|
126113
|
+
node_os = require_chunk.__toESM(node_os);
|
|
125402
126114
|
var node_http = __require("http");
|
|
125403
126115
|
var node_vm = __require("vm");
|
|
125404
126116
|
node_vm = require_chunk.__toESM(node_vm);
|
|
@@ -215080,6 +215792,38 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
215080
215792
|
/** Wall-clock uptime (seconds). Parsed from `ps etime`. */
|
|
215081
215793
|
uptimeSec: zod.z.number()
|
|
215082
215794
|
});
|
|
215795
|
+
var ContainerMemoryPointSchema = zod.z.object({
|
|
215796
|
+
/** Which hierarchy answered, so a reading is never ambiguous. */
|
|
215797
|
+
source: zod.z.enum(["cgroup-v2", "cgroup-v1"]),
|
|
215798
|
+
/** `memory.current` (v2) / `memory.usage_in_bytes` (v1). Always known. */
|
|
215799
|
+
currentBytes: zod.z.number(),
|
|
215800
|
+
/** The cgroup's ceiling. `null` = NO LIMIT — never a sentinel, never zero. */
|
|
215801
|
+
limitBytes: zod.z.number().nullable(),
|
|
215802
|
+
/** Anonymous pages: the closest thing to "what the processes allocated". */
|
|
215803
|
+
anonBytes: zod.z.number().nullable(),
|
|
215804
|
+
/** Page cache. Charged to the cgroup, owned by no process. */
|
|
215805
|
+
fileBytes: zod.z.number().nullable(),
|
|
215806
|
+
/**
|
|
215807
|
+
* Shared memory — and the field that explained the largest single surprise.
|
|
215808
|
+
* The i915 driver backs GPU buffers with shmem, so an inference pool or a
|
|
215809
|
+
* hardware-decode session holding DRM objects is charged HERE and appears
|
|
215810
|
+
* nowhere in a `ps` scan.
|
|
215811
|
+
*/
|
|
215812
|
+
shmemBytes: zod.z.number().nullable(),
|
|
215813
|
+
/** Kernel slab charged to this cgroup. `null` on v1, which never publishes it. */
|
|
215814
|
+
slabBytes: zod.z.number().nullable(),
|
|
215815
|
+
/**
|
|
215816
|
+
* Shrinkable i915 GEM object bytes, from debugfs.
|
|
215817
|
+
*
|
|
215818
|
+
* **Host-wide across every DRM client, NOT cgroup-scoped.** It is not a
|
|
215819
|
+
* component of `currentBytes` and must not be subtracted from it; it says
|
|
215820
|
+
* what put the shmem there, where `shmemBytes` only says how much.
|
|
215821
|
+
*
|
|
215822
|
+
* `null` wherever debugfs is not mounted — which is inside every camstack
|
|
215823
|
+
* container today — and on any node with no Intel GPU.
|
|
215824
|
+
*/
|
|
215825
|
+
gpuShmemBytes: zod.z.number().nullable()
|
|
215826
|
+
}).extend({ atMs: zod.z.number() });
|
|
215083
215827
|
var DumpHeapSnapshotInputSchema = zod.z.object({
|
|
215084
215828
|
/** The addon whose runner should dump a heap snapshot. */
|
|
215085
215829
|
addonId: zod.z.string()
|
|
@@ -215129,6 +215873,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
215129
215873
|
/** One entry per function seen in the window, heaviest-first. */
|
|
215130
215874
|
series: zod.z.array(LoadFunctionSeriesSchema).readonly(),
|
|
215131
215875
|
/**
|
|
215876
|
+
* The CONTAINER's memory over the same window, oldest-first.
|
|
215877
|
+
*
|
|
215878
|
+
* Sits next to `series` rather than in a method of its own because the whole
|
|
215879
|
+
* question is a subtraction: the per-process rows in `series` sum to one
|
|
215880
|
+
* number and this one is another, and an operator who has to issue two calls
|
|
215881
|
+
* to compare them will compare two different instants. Same reader, same
|
|
215882
|
+
* `sinceMs`, same `bucketMs`, same timestamps.
|
|
215883
|
+
*
|
|
215884
|
+
* **EMPTY means ABSENT, never zero.** A node with no cgroup — a developer
|
|
215885
|
+
* Mac, a bare-metal host, a container with the hierarchy hidden — reports no
|
|
215886
|
+
* points at all. A zero here would be indistinguishable from a healthy
|
|
215887
|
+
* container and is precisely the lie this field exists to avoid.
|
|
215888
|
+
*/
|
|
215889
|
+
containerMemory: zod.z.array(ContainerMemoryPointSchema).readonly(),
|
|
215890
|
+
/**
|
|
215132
215891
|
* Width of one returned bucket, in ms. Equals the sampling cadence when no
|
|
215133
215892
|
* reduction was needed — so a caller can always say what one point covers
|
|
215134
215893
|
* without having to know whether it was reduced.
|