camstack 1.2.58 → 1.2.60
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-BPlfW-CG.js
|
|
23637
|
+
var require_dist_BPlfW_CG = __commonJS({
|
|
23638
|
+
"../system/dist/dist-BPlfW-CG.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.
|
|
@@ -43598,6 +43645,33 @@ var require_dist_CxgTulEG = __commonJS({
|
|
|
43598
43645
|
deviceNative: true,
|
|
43599
43646
|
mode: "singleton",
|
|
43600
43647
|
deviceTypes: [DeviceType.Camera],
|
|
43648
|
+
/**
|
|
43649
|
+
* **Auth tier: `protected` on every method — deliberate, and load-bearing.**
|
|
43650
|
+
*
|
|
43651
|
+
* Talking through a camera is an OPERATE action, not a CONFIGURE one. This
|
|
43652
|
+
* cap has no configuration surface at all: all six methods open, feed and
|
|
43653
|
+
* close one live audio session against one `deviceId`. That is the same
|
|
43654
|
+
* authority as `ptz.move` or `snapshot.getSnapshot`, both `protected` — and
|
|
43655
|
+
* the opposite of `ptz.savePreset` / `snapshot.invalidateCache`, which are
|
|
43656
|
+
* `admin` because they change what the device IS.
|
|
43657
|
+
*
|
|
43658
|
+
* `protected` does not mean ungated: `protectedProcedure` runs the
|
|
43659
|
+
* `METHOD_ACCESS_MAP` scope check, and every method here is `scope: 'device'`
|
|
43660
|
+
* with `access: 'create'` and a `deviceId` in its input. So a caller needs a
|
|
43661
|
+
* grant that covers THAT camera at `create` — a `camera-viewer` (`view`
|
|
43662
|
+
* only) still cannot talk, and a grant on camera 5 cannot talk through
|
|
43663
|
+
* camera 7.
|
|
43664
|
+
*
|
|
43665
|
+
* Every method was `auth: 'admin'` from the initial commit, which made the
|
|
43666
|
+
* cap unreachable by every non-admin principal — `adminProcedure` throws
|
|
43667
|
+
* `FORBIDDEN: Admin required` BEFORE the scope check runs, so the scope
|
|
43668
|
+
* machinery generated for this cap (`METHOD_ACCESS_MAP`,
|
|
43669
|
+
* `DEVICE_SCOPED_CAPS`, `METHOD_DEVICE_SELECTORS`) was complete and dead. The
|
|
43670
|
+
* `camera-operator` scope preset has promised "PTZ control, intercom,
|
|
43671
|
+
* snapshots" since that same commit; the promise could not be kept. Recorded
|
|
43672
|
+
* as D289; `scripts/check-scope-preset-promises.ts` now fails the build if a
|
|
43673
|
+
* preset promises a cap no row of that preset can reach.
|
|
43674
|
+
*/
|
|
43601
43675
|
methods: {
|
|
43602
43676
|
/**
|
|
43603
43677
|
* Open a server-side WebRTC audio-only session. Returns an SDP
|
|
@@ -43610,7 +43684,7 @@ var require_dist_CxgTulEG = __commonJS({
|
|
|
43610
43684
|
sdpOffer: zod.z.string()
|
|
43611
43685
|
}), {
|
|
43612
43686
|
kind: "mutation",
|
|
43613
|
-
auth: "
|
|
43687
|
+
auth: "protected"
|
|
43614
43688
|
}),
|
|
43615
43689
|
handleAnswer: method(zod.z.object({
|
|
43616
43690
|
deviceId: zod.z.number(),
|
|
@@ -43618,7 +43692,7 @@ var require_dist_CxgTulEG = __commonJS({
|
|
|
43618
43692
|
sdpAnswer: zod.z.string()
|
|
43619
43693
|
}), zod.z.void(), {
|
|
43620
43694
|
kind: "mutation",
|
|
43621
|
-
auth: "
|
|
43695
|
+
auth: "protected"
|
|
43622
43696
|
}),
|
|
43623
43697
|
/** Close explicitly. Server also auto-closes on 30s idle. */
|
|
43624
43698
|
stopSession: method(zod.z.object({
|
|
@@ -43626,7 +43700,7 @@ var require_dist_CxgTulEG = __commonJS({
|
|
|
43626
43700
|
sessionId: zod.z.string()
|
|
43627
43701
|
}), zod.z.void(), {
|
|
43628
43702
|
kind: "mutation",
|
|
43629
|
-
auth: "
|
|
43703
|
+
auth: "protected"
|
|
43630
43704
|
}),
|
|
43631
43705
|
/**
|
|
43632
43706
|
* Open a raw-PCM talk session (no WebRTC SDP plumbing). Used by
|
|
@@ -43639,7 +43713,7 @@ var require_dist_CxgTulEG = __commonJS({
|
|
|
43639
43713
|
*/
|
|
43640
43714
|
startTalkSession: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.object({ sessionId: zod.z.string() }), {
|
|
43641
43715
|
kind: "mutation",
|
|
43642
|
-
auth: "
|
|
43716
|
+
auth: "protected"
|
|
43643
43717
|
}),
|
|
43644
43718
|
/**
|
|
43645
43719
|
* Push one chunk of talk-back audio onto the active talk session.
|
|
@@ -43674,12 +43748,12 @@ var require_dist_CxgTulEG = __commonJS({
|
|
|
43674
43748
|
sequenceNumber: zod.z.number().int()
|
|
43675
43749
|
}), zod.z.object({ accepted: zod.z.boolean() }), {
|
|
43676
43750
|
kind: "mutation",
|
|
43677
|
-
auth: "
|
|
43751
|
+
auth: "protected"
|
|
43678
43752
|
}),
|
|
43679
43753
|
/** Close the raw-PCM talk session. Idempotent. */
|
|
43680
43754
|
endTalkSession: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), {
|
|
43681
43755
|
kind: "mutation",
|
|
43682
|
-
auth: "
|
|
43756
|
+
auth: "protected"
|
|
43683
43757
|
})
|
|
43684
43758
|
},
|
|
43685
43759
|
events: { onStatusChanged: { data: zod.z.object({
|
|
@@ -58639,7 +58713,7 @@ var require_alerts_addon = __commonJS({
|
|
|
58639
58713
|
[Symbol.toStringTag]: { value: "Module" }
|
|
58640
58714
|
});
|
|
58641
58715
|
require_chunk_Cek0wNdY();
|
|
58642
|
-
var require_dist10 =
|
|
58716
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
58643
58717
|
function selectExpired(alerts, cutoffMs) {
|
|
58644
58718
|
return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
|
|
58645
58719
|
}
|
|
@@ -59458,7 +59532,7 @@ var require_console_logging = __commonJS({
|
|
|
59458
59532
|
[Symbol.toStringTag]: { value: "Module" }
|
|
59459
59533
|
});
|
|
59460
59534
|
require_chunk_Cek0wNdY();
|
|
59461
|
-
var require_dist10 =
|
|
59535
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
59462
59536
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
59463
59537
|
var LEVEL_RANK = {
|
|
59464
59538
|
debug: 0,
|
|
@@ -59552,7 +59626,7 @@ var require_core_blocks_addon = __commonJS({
|
|
|
59552
59626
|
"use strict";
|
|
59553
59627
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
59554
59628
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
59555
|
-
var require_dist10 =
|
|
59629
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
59556
59630
|
var node_crypto = __require("crypto");
|
|
59557
59631
|
var node_fs_promises = __require("fs/promises");
|
|
59558
59632
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -60449,11 +60523,11 @@ var require_core_blocks = __commonJS({
|
|
|
60449
60523
|
}
|
|
60450
60524
|
});
|
|
60451
60525
|
|
|
60452
|
-
// ../system/dist/retired-settings-keys-
|
|
60453
|
-
var
|
|
60454
|
-
"../system/dist/retired-settings-keys-
|
|
60526
|
+
// ../system/dist/retired-settings-keys-DBY6ebwV.js
|
|
60527
|
+
var require_retired_settings_keys_DBY6ebwV = __commonJS({
|
|
60528
|
+
"../system/dist/retired-settings-keys-DBY6ebwV.js"(exports) {
|
|
60455
60529
|
"use strict";
|
|
60456
|
-
var require_dist10 =
|
|
60530
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
60457
60531
|
function settingsStoreIsAuthoritativeHere(env) {
|
|
60458
60532
|
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
60459
60533
|
return raw === "" || raw === "hub";
|
|
@@ -62667,8 +62741,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
62667
62741
|
[Symbol.toStringTag]: { value: "Module" }
|
|
62668
62742
|
});
|
|
62669
62743
|
require_chunk_Cek0wNdY();
|
|
62670
|
-
var require_dist10 =
|
|
62671
|
-
var require_retired_settings_keys =
|
|
62744
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
62745
|
+
var require_retired_settings_keys = require_retired_settings_keys_DBY6ebwV();
|
|
62672
62746
|
var node_crypto = __require("crypto");
|
|
62673
62747
|
var _camstack_types_node = require_node();
|
|
62674
62748
|
var JOB_HISTORY = 20;
|
|
@@ -67417,7 +67491,7 @@ var require_hub_forwarder = __commonJS({
|
|
|
67417
67491
|
[Symbol.toStringTag]: { value: "Module" }
|
|
67418
67492
|
});
|
|
67419
67493
|
require_chunk_Cek0wNdY();
|
|
67420
|
-
var require_dist10 =
|
|
67494
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
67421
67495
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
67422
67496
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
67423
67497
|
var HubForwarderDestination = class {
|
|
@@ -67554,7 +67628,7 @@ var require_liveness_monitor_addon = __commonJS({
|
|
|
67554
67628
|
"use strict";
|
|
67555
67629
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
67556
67630
|
require_chunk_Cek0wNdY();
|
|
67557
|
-
var require_dist10 =
|
|
67631
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
67558
67632
|
var NO_DEVICES = "liveness:no-devices";
|
|
67559
67633
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
67560
67634
|
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
@@ -67744,7 +67818,7 @@ var require_local_auth_addon = __commonJS({
|
|
|
67744
67818
|
[Symbol.toStringTag]: { value: "Module" }
|
|
67745
67819
|
});
|
|
67746
67820
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
67747
|
-
var require_dist10 =
|
|
67821
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
67748
67822
|
var node_crypto = __require("crypto");
|
|
67749
67823
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
67750
67824
|
var crypto$1 = __require("crypto");
|
|
@@ -75557,7 +75631,7 @@ var require_loki_logging = __commonJS({
|
|
|
75557
75631
|
[Symbol.toStringTag]: { value: "Module" }
|
|
75558
75632
|
});
|
|
75559
75633
|
require_chunk_Cek0wNdY();
|
|
75560
|
-
var require_dist10 =
|
|
75634
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
75561
75635
|
function sanitizeLabelName(raw) {
|
|
75562
75636
|
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
75563
75637
|
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
@@ -76122,14 +76196,859 @@ var require_native_metrics_addon = __commonJS({
|
|
|
76122
76196
|
[Symbol.toStringTag]: { value: "Module" }
|
|
76123
76197
|
});
|
|
76124
76198
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
76125
|
-
var require_dist10 =
|
|
76199
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
76126
76200
|
var node_fs_promises = __require("fs/promises");
|
|
76127
76201
|
var node_child_process = __require("child_process");
|
|
76128
76202
|
var node_util = __require("util");
|
|
76129
|
-
var node_os = __require("os");
|
|
76130
|
-
node_os = require_chunk.__toESM(node_os);
|
|
76131
76203
|
var node_fs = __require("fs");
|
|
76132
76204
|
node_fs = require_chunk.__toESM(node_fs);
|
|
76205
|
+
var node_os = __require("os");
|
|
76206
|
+
node_os = require_chunk.__toESM(node_os);
|
|
76207
|
+
var nodeCgroupFileReader = { read(path) {
|
|
76208
|
+
try {
|
|
76209
|
+
return (0, node_fs.readFileSync)(path, "utf8");
|
|
76210
|
+
} catch {
|
|
76211
|
+
return null;
|
|
76212
|
+
}
|
|
76213
|
+
} };
|
|
76214
|
+
var CGROUP_MOUNT_ROOT = "/sys/fs/cgroup";
|
|
76215
|
+
var I915_GEM_OBJECTS_PATH = "/sys/kernel/debug/dri/0/i915_gem_objects";
|
|
76216
|
+
var V1_UNLIMITED_SENTINEL = 9223372036854772e3;
|
|
76217
|
+
function parseCgroupSelf(text) {
|
|
76218
|
+
let v1 = null;
|
|
76219
|
+
for (const rawLine of text.split("\n")) {
|
|
76220
|
+
const line = rawLine.trim();
|
|
76221
|
+
if (line.length === 0) continue;
|
|
76222
|
+
const first = line.indexOf(":");
|
|
76223
|
+
if (first < 0) continue;
|
|
76224
|
+
const second = line.indexOf(":", first + 1);
|
|
76225
|
+
if (second < 0) continue;
|
|
76226
|
+
const controllers = line.slice(first + 1, second);
|
|
76227
|
+
const path = line.slice(second + 1);
|
|
76228
|
+
if (path.length === 0) continue;
|
|
76229
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
76230
|
+
if (controllers.length === 0) return {
|
|
76231
|
+
version: "v2",
|
|
76232
|
+
path: normalized
|
|
76233
|
+
};
|
|
76234
|
+
if (v1 === null && controllers.split(",").includes("memory")) v1 = {
|
|
76235
|
+
version: "v1",
|
|
76236
|
+
path: normalized
|
|
76237
|
+
};
|
|
76238
|
+
}
|
|
76239
|
+
return v1;
|
|
76240
|
+
}
|
|
76241
|
+
function parseCgroupStat(text) {
|
|
76242
|
+
const out = /* @__PURE__ */ new Map();
|
|
76243
|
+
for (const rawLine of text.split("\n")) {
|
|
76244
|
+
const line = rawLine.trim();
|
|
76245
|
+
if (line.length === 0) continue;
|
|
76246
|
+
const space = line.indexOf(" ");
|
|
76247
|
+
if (space <= 0) continue;
|
|
76248
|
+
const key = line.slice(0, space);
|
|
76249
|
+
const value = Number(line.slice(space + 1).trim());
|
|
76250
|
+
if (!Number.isFinite(value)) continue;
|
|
76251
|
+
out.set(key, value);
|
|
76252
|
+
}
|
|
76253
|
+
return out;
|
|
76254
|
+
}
|
|
76255
|
+
function parseCgroupScalar(text) {
|
|
76256
|
+
const trimmed = text.trim();
|
|
76257
|
+
if (trimmed.length === 0) return null;
|
|
76258
|
+
if (trimmed === "max") return null;
|
|
76259
|
+
const value = Number(trimmed);
|
|
76260
|
+
if (!Number.isFinite(value) || value < 0) return null;
|
|
76261
|
+
if (value >= V1_UNLIMITED_SENTINEL) return null;
|
|
76262
|
+
return value;
|
|
76263
|
+
}
|
|
76264
|
+
function parseI915GemObjects(text) {
|
|
76265
|
+
const match = text.match(/^\s*\d+\s+shrinkable\b[^,\n]*,\s*(\d+)\s+bytes/m);
|
|
76266
|
+
if (match === null) return null;
|
|
76267
|
+
const bytes = Number(match[1]);
|
|
76268
|
+
return Number.isFinite(bytes) ? bytes : null;
|
|
76269
|
+
}
|
|
76270
|
+
function resolveCgroupMemoryLayout(fs) {
|
|
76271
|
+
const selfText = fs.read("/proc/self/cgroup");
|
|
76272
|
+
if (selfText === null) return null;
|
|
76273
|
+
const entry = parseCgroupSelf(selfText);
|
|
76274
|
+
if (entry === null) return null;
|
|
76275
|
+
const suffix = entry.path === "/" ? "" : entry.path;
|
|
76276
|
+
const candidates = entry.version === "v2" ? [`${CGROUP_MOUNT_ROOT}${suffix}`, CGROUP_MOUNT_ROOT] : [`${CGROUP_MOUNT_ROOT}/memory${suffix}`, `${CGROUP_MOUNT_ROOT}/memory`];
|
|
76277
|
+
const usageFile = entry.version === "v2" ? "memory.current" : "memory.usage_in_bytes";
|
|
76278
|
+
const limitFile = entry.version === "v2" ? "memory.max" : "memory.limit_in_bytes";
|
|
76279
|
+
for (const directory of candidates) {
|
|
76280
|
+
if (fs.read(`${directory}/${usageFile}`) === null) continue;
|
|
76281
|
+
return {
|
|
76282
|
+
source: entry.version === "v2" ? "cgroup-v2" : "cgroup-v1",
|
|
76283
|
+
directory,
|
|
76284
|
+
currentPath: `${directory}/${usageFile}`,
|
|
76285
|
+
limitPath: `${directory}/${limitFile}`,
|
|
76286
|
+
statPath: `${directory}/memory.stat`
|
|
76287
|
+
};
|
|
76288
|
+
}
|
|
76289
|
+
return null;
|
|
76290
|
+
}
|
|
76291
|
+
function statValue(stat, key) {
|
|
76292
|
+
const value = stat.get(key);
|
|
76293
|
+
return value === void 0 ? null : value;
|
|
76294
|
+
}
|
|
76295
|
+
function resolveSlab(stat) {
|
|
76296
|
+
const direct = statValue(stat, "slab");
|
|
76297
|
+
if (direct !== null) return direct;
|
|
76298
|
+
const reclaimable = stat.get("slab_reclaimable");
|
|
76299
|
+
const unreclaimable = stat.get("slab_unreclaimable");
|
|
76300
|
+
if (reclaimable === void 0 || unreclaimable === void 0) return null;
|
|
76301
|
+
return reclaimable + unreclaimable;
|
|
76302
|
+
}
|
|
76303
|
+
function buildContainerMemorySnapshot(input) {
|
|
76304
|
+
const currentBytes = parseCgroupScalar(input.currentText);
|
|
76305
|
+
if (currentBytes === null) return null;
|
|
76306
|
+
const stat = input.statText === null ? /* @__PURE__ */ new Map() : parseCgroupStat(input.statText);
|
|
76307
|
+
const isV2 = input.source === "cgroup-v2";
|
|
76308
|
+
return {
|
|
76309
|
+
source: input.source,
|
|
76310
|
+
currentBytes,
|
|
76311
|
+
limitBytes: input.limitText === null ? null : parseCgroupScalar(input.limitText),
|
|
76312
|
+
anonBytes: statValue(stat, isV2 ? "anon" : "rss"),
|
|
76313
|
+
fileBytes: statValue(stat, isV2 ? "file" : "cache"),
|
|
76314
|
+
shmemBytes: statValue(stat, "shmem"),
|
|
76315
|
+
slabBytes: isV2 ? resolveSlab(stat) : null,
|
|
76316
|
+
gpuShmemBytes: input.gpuShmemBytes
|
|
76317
|
+
};
|
|
76318
|
+
}
|
|
76319
|
+
var ContainerMemoryProbe = class {
|
|
76320
|
+
logger;
|
|
76321
|
+
fs;
|
|
76322
|
+
wantGpu;
|
|
76323
|
+
resolved = false;
|
|
76324
|
+
layout = null;
|
|
76325
|
+
gpuProbed = false;
|
|
76326
|
+
gpuReadable = false;
|
|
76327
|
+
/** `null` until the first sample — so the FIRST outcome is a transition too. */
|
|
76328
|
+
lastReadOk = null;
|
|
76329
|
+
constructor(deps) {
|
|
76330
|
+
this.logger = deps.logger;
|
|
76331
|
+
this.fs = deps.fs ?? nodeCgroupFileReader;
|
|
76332
|
+
this.wantGpu = deps.readGpu ?? true;
|
|
76333
|
+
}
|
|
76334
|
+
/** Did this node resolve a cgroup at all? Absent is a reportable fact. */
|
|
76335
|
+
get available() {
|
|
76336
|
+
return this.layout !== null;
|
|
76337
|
+
}
|
|
76338
|
+
/**
|
|
76339
|
+
* One reading, or `null` when this node has no cgroup or the cgroup stopped
|
|
76340
|
+
* answering. `null` is ABSENT — the caller contributes no point, and no
|
|
76341
|
+
* point is what a chart must show, never a zero.
|
|
76342
|
+
*/
|
|
76343
|
+
sample() {
|
|
76344
|
+
const layout = this.resolveOnce();
|
|
76345
|
+
if (layout === null) return null;
|
|
76346
|
+
const currentText = this.fs.read(layout.currentPath);
|
|
76347
|
+
if (currentText === null) {
|
|
76348
|
+
this.noteRead(false, `unreadable: ${layout.currentPath}`);
|
|
76349
|
+
return null;
|
|
76350
|
+
}
|
|
76351
|
+
const snapshot = buildContainerMemorySnapshot({
|
|
76352
|
+
source: layout.source,
|
|
76353
|
+
currentText,
|
|
76354
|
+
limitText: this.fs.read(layout.limitPath),
|
|
76355
|
+
statText: this.fs.read(layout.statPath),
|
|
76356
|
+
gpuShmemBytes: this.readGpuShmem()
|
|
76357
|
+
});
|
|
76358
|
+
if (snapshot === null) {
|
|
76359
|
+
this.noteRead(false, `unparseable: ${layout.currentPath}`);
|
|
76360
|
+
return null;
|
|
76361
|
+
}
|
|
76362
|
+
this.noteRead(true, null);
|
|
76363
|
+
return snapshot;
|
|
76364
|
+
}
|
|
76365
|
+
/** Resolve on first use and never again — a cgroup does not move under a pid. */
|
|
76366
|
+
resolveOnce() {
|
|
76367
|
+
if (this.resolved) return this.layout;
|
|
76368
|
+
this.resolved = true;
|
|
76369
|
+
try {
|
|
76370
|
+
this.layout = resolveCgroupMemoryLayout(this.fs);
|
|
76371
|
+
} catch (err) {
|
|
76372
|
+
this.layout = null;
|
|
76373
|
+
this.logger.warn("cgroup memory resolution failed \u2014 container memory will be ABSENT", { meta: { error: require_dist10.errMsg(err) } });
|
|
76374
|
+
return null;
|
|
76375
|
+
}
|
|
76376
|
+
if (this.layout === null) {
|
|
76377
|
+
this.logger.info("no cgroup on this node \u2014 container memory is ABSENT, not zero, for every sample", { meta: {
|
|
76378
|
+
platform: process.platform,
|
|
76379
|
+
probed: "/proc/self/cgroup"
|
|
76380
|
+
} });
|
|
76381
|
+
return null;
|
|
76382
|
+
}
|
|
76383
|
+
this.logger.info("container memory resolved from the cgroup", { meta: {
|
|
76384
|
+
source: this.layout.source,
|
|
76385
|
+
directory: this.layout.directory
|
|
76386
|
+
} });
|
|
76387
|
+
return this.layout;
|
|
76388
|
+
}
|
|
76389
|
+
/**
|
|
76390
|
+
* The GPU number, probed once.
|
|
76391
|
+
*
|
|
76392
|
+
* It is host-wide and lives in debugfs, so it is absent far more often than
|
|
76393
|
+
* it is present — inside every camstack container today. One `open` decides
|
|
76394
|
+
* it for the life of the process; after that an unreadable file costs
|
|
76395
|
+
* nothing at all, not even a syscall.
|
|
76396
|
+
*/
|
|
76397
|
+
readGpuShmem() {
|
|
76398
|
+
if (!this.wantGpu) return null;
|
|
76399
|
+
if (!this.gpuProbed) {
|
|
76400
|
+
this.gpuProbed = true;
|
|
76401
|
+
const text2 = this.fs.read(I915_GEM_OBJECTS_PATH);
|
|
76402
|
+
this.gpuReadable = text2 !== null && parseI915GemObjects(text2) !== null;
|
|
76403
|
+
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 } });
|
|
76404
|
+
if (!this.gpuReadable) return null;
|
|
76405
|
+
return text2 === null ? null : parseI915GemObjects(text2);
|
|
76406
|
+
}
|
|
76407
|
+
if (!this.gpuReadable) return null;
|
|
76408
|
+
const text = this.fs.read(I915_GEM_OBJECTS_PATH);
|
|
76409
|
+
return text === null ? null : parseI915GemObjects(text);
|
|
76410
|
+
}
|
|
76411
|
+
/** Log a read outcome only when it CHANGES. Never once per tick. */
|
|
76412
|
+
noteRead(ok, reason) {
|
|
76413
|
+
if (this.lastReadOk === ok) return;
|
|
76414
|
+
const first = this.lastReadOk === null;
|
|
76415
|
+
this.lastReadOk = ok;
|
|
76416
|
+
if (!ok) {
|
|
76417
|
+
this.logger.warn("cgroup memory stopped answering \u2014 container memory is ABSENT until it returns", { meta: { reason: reason ?? "unknown" } });
|
|
76418
|
+
return;
|
|
76419
|
+
}
|
|
76420
|
+
if (!first) this.logger.info("cgroup memory is answering again");
|
|
76421
|
+
}
|
|
76422
|
+
};
|
|
76423
|
+
var LoadPartition = class {
|
|
76424
|
+
capacity;
|
|
76425
|
+
slots;
|
|
76426
|
+
head = 0;
|
|
76427
|
+
count = 0;
|
|
76428
|
+
rows = 0;
|
|
76429
|
+
/** Newest retained sample's timestamp — the monotonic gate for `push`. */
|
|
76430
|
+
newestAtMs = null;
|
|
76431
|
+
constructor(capacity) {
|
|
76432
|
+
this.capacity = capacity;
|
|
76433
|
+
this.slots = Array.from({ length: capacity });
|
|
76434
|
+
}
|
|
76435
|
+
/**
|
|
76436
|
+
* Accept a sample.
|
|
76437
|
+
*
|
|
76438
|
+
* A sample at or before the newest one already held is REFUSED. The bus drops
|
|
76439
|
+
* a node's own broadcast echo, but a cross-node redelivery or a replayed
|
|
76440
|
+
* subscription must not be able to double a point — and idempotence here is
|
|
76441
|
+
* what lets every reader above treat the series as a set.
|
|
76442
|
+
*
|
|
76443
|
+
* `accepted` is reported separately from `rowDelta` on purpose: a full ring
|
|
76444
|
+
* that evicts a sample of the same size has a delta of zero and is not a
|
|
76445
|
+
* refusal, and conflating the two would silently stop advancing the write
|
|
76446
|
+
* ordinal on a steady-state cluster.
|
|
76447
|
+
*/
|
|
76448
|
+
push(sample) {
|
|
76449
|
+
if (this.newestAtMs !== null && sample.atMs <= this.newestAtMs) return {
|
|
76450
|
+
accepted: false,
|
|
76451
|
+
rowDelta: 0
|
|
76452
|
+
};
|
|
76453
|
+
const evicted = this.count === this.capacity ? this.slots[this.head]?.processes.length ?? 0 : 0;
|
|
76454
|
+
this.slots[this.head] = sample;
|
|
76455
|
+
this.head = (this.head + 1) % this.capacity;
|
|
76456
|
+
if (this.count < this.capacity) this.count++;
|
|
76457
|
+
this.newestAtMs = sample.atMs;
|
|
76458
|
+
const rowDelta = sample.processes.length - evicted;
|
|
76459
|
+
this.rows += rowDelta;
|
|
76460
|
+
return {
|
|
76461
|
+
accepted: true,
|
|
76462
|
+
rowDelta
|
|
76463
|
+
};
|
|
76464
|
+
}
|
|
76465
|
+
/** Drop the oldest sample. Returns the rows reclaimed (0 when empty). */
|
|
76466
|
+
dropOldest() {
|
|
76467
|
+
if (this.count === 0) return 0;
|
|
76468
|
+
const index = (this.head - this.count + this.capacity) % this.capacity;
|
|
76469
|
+
const victim = this.slots[index];
|
|
76470
|
+
this.slots[index] = void 0;
|
|
76471
|
+
this.count--;
|
|
76472
|
+
const reclaimed = victim?.processes.length ?? 0;
|
|
76473
|
+
this.rows -= reclaimed;
|
|
76474
|
+
if (this.count === 0) this.newestAtMs = null;
|
|
76475
|
+
return reclaimed;
|
|
76476
|
+
}
|
|
76477
|
+
/** Oldest-first, optionally only what is strictly newer than `sinceMs`. */
|
|
76478
|
+
list(sinceMs) {
|
|
76479
|
+
const out = [];
|
|
76480
|
+
for (let i = 0; i < this.count; i++) {
|
|
76481
|
+
const index = (this.head - this.count + i + this.capacity) % this.capacity;
|
|
76482
|
+
const sample = this.slots[index];
|
|
76483
|
+
if (sample === void 0) continue;
|
|
76484
|
+
if (sinceMs !== void 0 && sample.atMs <= sinceMs) continue;
|
|
76485
|
+
out.push(sample);
|
|
76486
|
+
}
|
|
76487
|
+
return out;
|
|
76488
|
+
}
|
|
76489
|
+
size() {
|
|
76490
|
+
return this.count;
|
|
76491
|
+
}
|
|
76492
|
+
rowCount() {
|
|
76493
|
+
return this.rows;
|
|
76494
|
+
}
|
|
76495
|
+
oldestAtMs() {
|
|
76496
|
+
if (this.count === 0) return null;
|
|
76497
|
+
const index = (this.head - this.count + this.capacity) % this.capacity;
|
|
76498
|
+
return this.slots[index]?.atMs ?? null;
|
|
76499
|
+
}
|
|
76500
|
+
lastWriteAtMs() {
|
|
76501
|
+
return this.newestAtMs;
|
|
76502
|
+
}
|
|
76503
|
+
};
|
|
76504
|
+
var NodeLoadRing = class {
|
|
76505
|
+
partitions = /* @__PURE__ */ new Map();
|
|
76506
|
+
/**
|
|
76507
|
+
* Node id → write ordinal of its last accepted sample. A monotonic counter,
|
|
76508
|
+
* not a clock: partition eviction must follow the order writes actually
|
|
76509
|
+
* happened, and node clocks disagree.
|
|
76510
|
+
*/
|
|
76511
|
+
lastWriteSeq = /* @__PURE__ */ new Map();
|
|
76512
|
+
writeSeq = 0;
|
|
76513
|
+
totalRows = 0;
|
|
76514
|
+
samplesPerNode;
|
|
76515
|
+
maxTotalProcessRows;
|
|
76516
|
+
maxNodes;
|
|
76517
|
+
idleEvictionMs;
|
|
76518
|
+
now;
|
|
76519
|
+
constructor(options = {}) {
|
|
76520
|
+
this.samplesPerNode = options.samplesPerNode ?? 180;
|
|
76521
|
+
this.maxTotalProcessRows = options.maxTotalProcessRows ?? 32e3;
|
|
76522
|
+
this.maxNodes = options.maxNodes ?? 16;
|
|
76523
|
+
this.idleEvictionMs = options.idleEvictionMs ?? 36e5;
|
|
76524
|
+
this.now = options.now ?? Date.now;
|
|
76525
|
+
}
|
|
76526
|
+
/**
|
|
76527
|
+
* Retain one snapshot. `processes` is stored by reference — the payload is
|
|
76528
|
+
* already an immutable arrival off the bus, and copying it would double the
|
|
76529
|
+
* measured cost for nothing.
|
|
76530
|
+
*
|
|
76531
|
+
* Returns whether the sample was ACCEPTED — that is, whether it was new
|
|
76532
|
+
* rather than a replay of a timestamp this node has already delivered.
|
|
76533
|
+
*
|
|
76534
|
+
* The return value is not diagnostics. It is the idempotence gate the
|
|
76535
|
+
* DURABLE tier rides on (`load-series-store.ts`): only an accepted sample is
|
|
76536
|
+
* appended to the table, which is what lets that table carry an `INTEGER`
|
|
76537
|
+
* rowid key instead of a composite unique index over two million rows. The
|
|
76538
|
+
* gate is one monotonic comparison in memory; the index it replaces was
|
|
76539
|
+
* measured at ~40 bytes per row.
|
|
76540
|
+
*/
|
|
76541
|
+
record(nodeId, atMs, processes) {
|
|
76542
|
+
if (nodeId.length === 0) return false;
|
|
76543
|
+
this.sweepIdle();
|
|
76544
|
+
let partition = this.partitions.get(nodeId);
|
|
76545
|
+
if (partition === void 0) {
|
|
76546
|
+
partition = new LoadPartition(this.samplesPerNode);
|
|
76547
|
+
this.partitions.set(nodeId, partition);
|
|
76548
|
+
}
|
|
76549
|
+
const outcome = partition.push({
|
|
76550
|
+
atMs,
|
|
76551
|
+
processes
|
|
76552
|
+
});
|
|
76553
|
+
if (!outcome.accepted) {
|
|
76554
|
+
if (partition.size() === 0) this.partitions.delete(nodeId);
|
|
76555
|
+
return false;
|
|
76556
|
+
}
|
|
76557
|
+
this.totalRows += outcome.rowDelta;
|
|
76558
|
+
this.lastWriteSeq.set(nodeId, ++this.writeSeq);
|
|
76559
|
+
if (this.partitions.size > this.maxNodes) this.evictPartitions(this.partitions.size - this.maxNodes, nodeId);
|
|
76560
|
+
this.enforceRowBudget();
|
|
76561
|
+
return true;
|
|
76562
|
+
}
|
|
76563
|
+
/**
|
|
76564
|
+
* Read one node's retained series, oldest-first.
|
|
76565
|
+
*
|
|
76566
|
+
* `sinceMs` is EXCLUSIVE: a caller passes the newest timestamp it already
|
|
76567
|
+
* holds and gets back only what it is missing. That is the whole contract
|
|
76568
|
+
* that lets the admin UI seed from here and then continue live without
|
|
76569
|
+
* doubling a point it already drew.
|
|
76570
|
+
*
|
|
76571
|
+
* A node nobody has recorded answers with an empty series, not an error —
|
|
76572
|
+
* unknown is the truth about a node that has not reported.
|
|
76573
|
+
*/
|
|
76574
|
+
read(nodeId, sinceMs) {
|
|
76575
|
+
this.sweepIdle();
|
|
76576
|
+
const partition = this.partitions.get(nodeId);
|
|
76577
|
+
if (partition === void 0) return {
|
|
76578
|
+
nodeId,
|
|
76579
|
+
samples: [],
|
|
76580
|
+
retainedSamples: 0,
|
|
76581
|
+
oldestAtMs: null,
|
|
76582
|
+
capacity: this.samplesPerNode
|
|
76583
|
+
};
|
|
76584
|
+
return {
|
|
76585
|
+
nodeId,
|
|
76586
|
+
samples: partition.list(sinceMs),
|
|
76587
|
+
retainedSamples: partition.size(),
|
|
76588
|
+
oldestAtMs: partition.oldestAtMs(),
|
|
76589
|
+
capacity: this.samplesPerNode
|
|
76590
|
+
};
|
|
76591
|
+
}
|
|
76592
|
+
/** Node ids with a live partition. Observability for the fleet bound. */
|
|
76593
|
+
nodeIds() {
|
|
76594
|
+
return [...this.partitions.keys()];
|
|
76595
|
+
}
|
|
76596
|
+
/** Process rows retained across every partition — the number that IS memory. */
|
|
76597
|
+
rowCount() {
|
|
76598
|
+
return this.totalRows;
|
|
76599
|
+
}
|
|
76600
|
+
/** Samples retained across every partition. */
|
|
76601
|
+
sampleCount() {
|
|
76602
|
+
let total = 0;
|
|
76603
|
+
for (const partition of this.partitions.values()) total += partition.size();
|
|
76604
|
+
return total;
|
|
76605
|
+
}
|
|
76606
|
+
/**
|
|
76607
|
+
* Drop partitions whose newest sample is older than the retention window.
|
|
76608
|
+
*
|
|
76609
|
+
* Lazy, on write and on read — never a timer. A timer would be a new
|
|
76610
|
+
* periodic cost in a subsystem whose entire premise is that it adds none,
|
|
76611
|
+
* and a ring that nobody writes to and nobody reads is not growing either.
|
|
76612
|
+
*/
|
|
76613
|
+
sweepIdle() {
|
|
76614
|
+
const cutoff = this.now() - this.idleEvictionMs;
|
|
76615
|
+
for (const [nodeId, partition] of [...this.partitions.entries()]) {
|
|
76616
|
+
const lastWrite = partition.lastWriteAtMs();
|
|
76617
|
+
if (lastWrite !== null && lastWrite > cutoff) continue;
|
|
76618
|
+
this.dropPartition(nodeId);
|
|
76619
|
+
}
|
|
76620
|
+
}
|
|
76621
|
+
/**
|
|
76622
|
+
* Bring the fleet back under the row budget by dropping the OLDEST sample of
|
|
76623
|
+
* the HEAVIEST partition, repeatedly.
|
|
76624
|
+
*
|
|
76625
|
+
* Terminates: every iteration removes one sample from a non-empty partition,
|
|
76626
|
+
* and the population of samples is finite and strictly decreasing.
|
|
76627
|
+
*/
|
|
76628
|
+
enforceRowBudget() {
|
|
76629
|
+
while (this.totalRows > this.maxTotalProcessRows) {
|
|
76630
|
+
const victim = this.heaviestPartition();
|
|
76631
|
+
if (victim === null) return;
|
|
76632
|
+
const [nodeId, partition] = victim;
|
|
76633
|
+
this.totalRows -= partition.dropOldest();
|
|
76634
|
+
if (partition.size() === 0) this.dropPartition(nodeId);
|
|
76635
|
+
}
|
|
76636
|
+
}
|
|
76637
|
+
heaviestPartition() {
|
|
76638
|
+
let best = null;
|
|
76639
|
+
for (const entry of this.partitions.entries()) {
|
|
76640
|
+
if (entry[1].size() === 0) continue;
|
|
76641
|
+
if (best === null || entry[1].rowCount() > best[1].rowCount()) best = entry;
|
|
76642
|
+
}
|
|
76643
|
+
return best;
|
|
76644
|
+
}
|
|
76645
|
+
/** Drop `count` whole partitions, least-recently-written first. */
|
|
76646
|
+
evictPartitions(count, protectedNodeId) {
|
|
76647
|
+
const order = [...this.partitions.keys()].filter((nodeId) => nodeId !== protectedNodeId).toSorted((a, b) => (this.lastWriteSeq.get(a) ?? 0) - (this.lastWriteSeq.get(b) ?? 0));
|
|
76648
|
+
let remaining = count;
|
|
76649
|
+
for (const nodeId of order) {
|
|
76650
|
+
if (remaining <= 0) return;
|
|
76651
|
+
this.dropPartition(nodeId);
|
|
76652
|
+
remaining -= 1;
|
|
76653
|
+
}
|
|
76654
|
+
}
|
|
76655
|
+
/** Remove a partition and everything that indexes it. */
|
|
76656
|
+
dropPartition(nodeId) {
|
|
76657
|
+
const partition = this.partitions.get(nodeId);
|
|
76658
|
+
if (partition === void 0) return;
|
|
76659
|
+
this.totalRows -= partition.rowCount();
|
|
76660
|
+
this.partitions.delete(nodeId);
|
|
76661
|
+
this.lastWriteSeq.delete(nodeId);
|
|
76662
|
+
}
|
|
76663
|
+
};
|
|
76664
|
+
var CONTAINER_MEMORY_COLLECTION = "metrics:node-memory-samples";
|
|
76665
|
+
var CONTAINER_MEMORY_MAX_TOTAL_ROWS = 2880;
|
|
76666
|
+
var CONTAINER_MEMORY_MAX_ROWS = 5e4;
|
|
76667
|
+
var CONTAINER_MEMORY_COLUMNS = [
|
|
76668
|
+
{
|
|
76669
|
+
name: "id",
|
|
76670
|
+
type: "INTEGER",
|
|
76671
|
+
primaryKey: true,
|
|
76672
|
+
notNull: true
|
|
76673
|
+
},
|
|
76674
|
+
{
|
|
76675
|
+
name: "nodeId",
|
|
76676
|
+
type: "TEXT",
|
|
76677
|
+
notNull: true
|
|
76678
|
+
},
|
|
76679
|
+
/** Shared with the process rows of the same tick — that is the whole point. */
|
|
76680
|
+
{
|
|
76681
|
+
name: "atMs",
|
|
76682
|
+
type: "INTEGER",
|
|
76683
|
+
notNull: true
|
|
76684
|
+
},
|
|
76685
|
+
{
|
|
76686
|
+
name: "source",
|
|
76687
|
+
type: "TEXT",
|
|
76688
|
+
notNull: true
|
|
76689
|
+
},
|
|
76690
|
+
/**
|
|
76691
|
+
* Whole MiB, like `rssMib` on the process row and for the same reason: a
|
|
76692
|
+
* byte-exact residency on a value in the tens of gigabytes is precision no
|
|
76693
|
+
* chart can draw, and it costs bytes on every row forever.
|
|
76694
|
+
*/
|
|
76695
|
+
{
|
|
76696
|
+
name: "currentMib",
|
|
76697
|
+
type: "INTEGER",
|
|
76698
|
+
notNull: true
|
|
76699
|
+
},
|
|
76700
|
+
/** `NULL` = NO LIMIT. Never a sentinel. */
|
|
76701
|
+
{
|
|
76702
|
+
name: "limitMib",
|
|
76703
|
+
type: "INTEGER"
|
|
76704
|
+
},
|
|
76705
|
+
/** `NULL` = UNKNOWN on all four. Never zero. */
|
|
76706
|
+
{
|
|
76707
|
+
name: "anonMib",
|
|
76708
|
+
type: "INTEGER"
|
|
76709
|
+
},
|
|
76710
|
+
{
|
|
76711
|
+
name: "fileMib",
|
|
76712
|
+
type: "INTEGER"
|
|
76713
|
+
},
|
|
76714
|
+
{
|
|
76715
|
+
name: "shmemMib",
|
|
76716
|
+
type: "INTEGER"
|
|
76717
|
+
},
|
|
76718
|
+
{
|
|
76719
|
+
name: "slabMib",
|
|
76720
|
+
type: "INTEGER"
|
|
76721
|
+
},
|
|
76722
|
+
/** Host-wide i915 GEM bytes. `NULL` wherever debugfs is not mounted. */
|
|
76723
|
+
{
|
|
76724
|
+
name: "gpuShmemMib",
|
|
76725
|
+
type: "INTEGER"
|
|
76726
|
+
}
|
|
76727
|
+
];
|
|
76728
|
+
var CONTAINER_MEMORY_INDEXES = [
|
|
76729
|
+
/** The one index, and it serves both the read and the prune — see the
|
|
76730
|
+
* process table's `idx_load_samples_node_at` for the full reasoning. */
|
|
76731
|
+
{
|
|
76732
|
+
name: "idx_node_memory_node_at",
|
|
76733
|
+
columns: ["nodeId", "atMs"]
|
|
76734
|
+
}
|
|
76735
|
+
];
|
|
76736
|
+
var BYTES_PER_MIB$1 = 1048576;
|
|
76737
|
+
function toMib(value) {
|
|
76738
|
+
return value === null ? null : Math.round(value / BYTES_PER_MIB$1);
|
|
76739
|
+
}
|
|
76740
|
+
function fromMib(value) {
|
|
76741
|
+
return value === null ? null : value * BYTES_PER_MIB$1;
|
|
76742
|
+
}
|
|
76743
|
+
function snapshotToRow(nodeId, atMs, snapshot) {
|
|
76744
|
+
return {
|
|
76745
|
+
nodeId,
|
|
76746
|
+
atMs,
|
|
76747
|
+
source: snapshot.source,
|
|
76748
|
+
currentMib: Math.round(snapshot.currentBytes / BYTES_PER_MIB$1),
|
|
76749
|
+
limitMib: toMib(snapshot.limitBytes),
|
|
76750
|
+
anonMib: toMib(snapshot.anonBytes),
|
|
76751
|
+
fileMib: toMib(snapshot.fileBytes),
|
|
76752
|
+
shmemMib: toMib(snapshot.shmemBytes),
|
|
76753
|
+
slabMib: toMib(snapshot.slabBytes),
|
|
76754
|
+
gpuShmemMib: toMib(snapshot.gpuShmemBytes)
|
|
76755
|
+
};
|
|
76756
|
+
}
|
|
76757
|
+
function rowToPoint(row) {
|
|
76758
|
+
if (row.source !== "cgroup-v2" && row.source !== "cgroup-v1") return null;
|
|
76759
|
+
return {
|
|
76760
|
+
atMs: row.atMs,
|
|
76761
|
+
source: row.source,
|
|
76762
|
+
currentBytes: row.currentMib * BYTES_PER_MIB$1,
|
|
76763
|
+
limitBytes: fromMib(row.limitMib),
|
|
76764
|
+
anonBytes: fromMib(row.anonMib),
|
|
76765
|
+
fileBytes: fromMib(row.fileMib),
|
|
76766
|
+
shmemBytes: fromMib(row.shmemMib),
|
|
76767
|
+
slabBytes: fromMib(row.slabMib),
|
|
76768
|
+
gpuShmemBytes: fromMib(row.gpuShmemMib)
|
|
76769
|
+
};
|
|
76770
|
+
}
|
|
76771
|
+
function recordToMemoryRow(data) {
|
|
76772
|
+
const nodeId = data["nodeId"];
|
|
76773
|
+
const source = data["source"];
|
|
76774
|
+
if (typeof nodeId !== "string" || typeof source !== "string") return null;
|
|
76775
|
+
const atMs = Number(data["atMs"]);
|
|
76776
|
+
const currentMib = Number(data["currentMib"]);
|
|
76777
|
+
if (!Number.isFinite(atMs) || !Number.isFinite(currentMib)) return null;
|
|
76778
|
+
const optional = (raw) => {
|
|
76779
|
+
if (raw === null || raw === void 0) return null;
|
|
76780
|
+
const n = Number(raw);
|
|
76781
|
+
return Number.isFinite(n) ? n : null;
|
|
76782
|
+
};
|
|
76783
|
+
return {
|
|
76784
|
+
nodeId,
|
|
76785
|
+
atMs,
|
|
76786
|
+
source,
|
|
76787
|
+
currentMib,
|
|
76788
|
+
limitMib: optional(data["limitMib"]),
|
|
76789
|
+
anonMib: optional(data["anonMib"]),
|
|
76790
|
+
fileMib: optional(data["fileMib"]),
|
|
76791
|
+
shmemMib: optional(data["shmemMib"]),
|
|
76792
|
+
slabMib: optional(data["slabMib"]),
|
|
76793
|
+
gpuShmemMib: optional(data["gpuShmemMib"])
|
|
76794
|
+
};
|
|
76795
|
+
}
|
|
76796
|
+
var ContainerMemoryRing = class {
|
|
76797
|
+
ring;
|
|
76798
|
+
constructor(options = {}) {
|
|
76799
|
+
this.ring = new NodeLoadRing({
|
|
76800
|
+
samplesPerNode: options.samplesPerNode ?? 180,
|
|
76801
|
+
maxTotalProcessRows: CONTAINER_MEMORY_MAX_TOTAL_ROWS,
|
|
76802
|
+
...options.now !== void 0 ? { now: options.now } : {}
|
|
76803
|
+
});
|
|
76804
|
+
}
|
|
76805
|
+
/** Retain one reading. `false` = a timestamp this node already delivered. */
|
|
76806
|
+
record(nodeId, atMs, snapshot) {
|
|
76807
|
+
return this.ring.record(nodeId, atMs, [snapshot]);
|
|
76808
|
+
}
|
|
76809
|
+
/**
|
|
76810
|
+
* Read one node's retained readings, oldest-first. `sinceMs` is EXCLUSIVE,
|
|
76811
|
+
* matching every other reader in this subsystem.
|
|
76812
|
+
*
|
|
76813
|
+
* A node nobody has recorded answers EMPTY — which is what "this node has no
|
|
76814
|
+
* cgroup" looks like, and it must stay distinguishable from a zero.
|
|
76815
|
+
*/
|
|
76816
|
+
read(nodeId, sinceMs) {
|
|
76817
|
+
const series = this.ring.read(nodeId, sinceMs);
|
|
76818
|
+
const out = [];
|
|
76819
|
+
for (const sample of series.samples) {
|
|
76820
|
+
const snapshot = sample.processes[0];
|
|
76821
|
+
if (snapshot === void 0) continue;
|
|
76822
|
+
out.push({
|
|
76823
|
+
...snapshot,
|
|
76824
|
+
atMs: sample.atMs
|
|
76825
|
+
});
|
|
76826
|
+
}
|
|
76827
|
+
return out;
|
|
76828
|
+
}
|
|
76829
|
+
};
|
|
76830
|
+
function reduceContainerMemory(points, bucketMs, origin) {
|
|
76831
|
+
if (bucketMs <= 0 || points.length === 0) return points;
|
|
76832
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
76833
|
+
for (const point of points) {
|
|
76834
|
+
const start = origin + Math.floor((point.atMs - origin) / bucketMs) * bucketMs;
|
|
76835
|
+
const held = buckets.get(start);
|
|
76836
|
+
if (held === void 0 || point.currentBytes > held.currentBytes) buckets.set(start, {
|
|
76837
|
+
...point,
|
|
76838
|
+
atMs: start
|
|
76839
|
+
});
|
|
76840
|
+
}
|
|
76841
|
+
return [...buckets.values()].toSorted((a, b) => a.atMs - b.atMs);
|
|
76842
|
+
}
|
|
76843
|
+
function mergeContainerMemory(cold, hot) {
|
|
76844
|
+
const byAt = /* @__PURE__ */ new Map();
|
|
76845
|
+
for (const point of cold) byAt.set(point.atMs, point);
|
|
76846
|
+
for (const point of hot) byAt.set(point.atMs, point);
|
|
76847
|
+
return [...byAt.values()].toSorted((a, b) => a.atMs - b.atMs);
|
|
76848
|
+
}
|
|
76849
|
+
var ContainerMemoryStore = class {
|
|
76850
|
+
declared = false;
|
|
76851
|
+
store;
|
|
76852
|
+
logger;
|
|
76853
|
+
nowFn;
|
|
76854
|
+
prunePageRows;
|
|
76855
|
+
constructor(deps) {
|
|
76856
|
+
this.store = deps.store;
|
|
76857
|
+
this.logger = deps.logger;
|
|
76858
|
+
this.nowFn = deps.now ?? (() => Date.now());
|
|
76859
|
+
this.prunePageRows = deps.prunePageRows ?? 2e4;
|
|
76860
|
+
}
|
|
76861
|
+
/** Idempotently declare the collection. `false` when the store refused. */
|
|
76862
|
+
async declare() {
|
|
76863
|
+
if (this.declared) return true;
|
|
76864
|
+
try {
|
|
76865
|
+
await this.store.declareCollection.mutate({
|
|
76866
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76867
|
+
columns: [...CONTAINER_MEMORY_COLUMNS],
|
|
76868
|
+
indexes: CONTAINER_MEMORY_INDEXES.map((i) => ({
|
|
76869
|
+
name: i.name,
|
|
76870
|
+
columns: [...i.columns]
|
|
76871
|
+
}))
|
|
76872
|
+
});
|
|
76873
|
+
this.declared = true;
|
|
76874
|
+
return true;
|
|
76875
|
+
} catch (err) {
|
|
76876
|
+
this.logger.warn("container memory declareCollection failed \u2014 nothing will be retained on disk", { meta: {
|
|
76877
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76878
|
+
error: require_dist10.errMsg(err)
|
|
76879
|
+
} });
|
|
76880
|
+
return false;
|
|
76881
|
+
}
|
|
76882
|
+
}
|
|
76883
|
+
/** Append ONE reading. */
|
|
76884
|
+
async append(nodeId, atMs, snapshot) {
|
|
76885
|
+
if (!await this.declare()) return 0;
|
|
76886
|
+
const record = { ...snapshotToRow(nodeId, atMs, snapshot) };
|
|
76887
|
+
try {
|
|
76888
|
+
const { inserted } = await this.store.insertMany.mutate({
|
|
76889
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76890
|
+
records: [{ data: record }]
|
|
76891
|
+
});
|
|
76892
|
+
return inserted;
|
|
76893
|
+
} catch (err) {
|
|
76894
|
+
this.logger.warn("container memory reading not retained \u2014 this interval will be missing", { meta: {
|
|
76895
|
+
nodeId,
|
|
76896
|
+
atMs,
|
|
76897
|
+
error: require_dist10.errMsg(err)
|
|
76898
|
+
} });
|
|
76899
|
+
return 0;
|
|
76900
|
+
}
|
|
76901
|
+
}
|
|
76902
|
+
/** Read one node's cold readings, oldest-first. `sinceMs` is EXCLUSIVE. */
|
|
76903
|
+
async read(nodeId, sinceMs, limitRows) {
|
|
76904
|
+
if (!await this.declare()) return [];
|
|
76905
|
+
try {
|
|
76906
|
+
const records = await this.store.query.query({
|
|
76907
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76908
|
+
filter: {
|
|
76909
|
+
where: { nodeId },
|
|
76910
|
+
whereBetween: { atMs: [sinceMs + 1, Number.MAX_SAFE_INTEGER] },
|
|
76911
|
+
orderBy: {
|
|
76912
|
+
field: "atMs",
|
|
76913
|
+
direction: "asc"
|
|
76914
|
+
},
|
|
76915
|
+
limit: limitRows
|
|
76916
|
+
}
|
|
76917
|
+
});
|
|
76918
|
+
const points = [];
|
|
76919
|
+
for (const record of records) {
|
|
76920
|
+
const row = recordToMemoryRow(record.data);
|
|
76921
|
+
if (row === null) continue;
|
|
76922
|
+
const point = rowToPoint(row);
|
|
76923
|
+
if (point !== null) points.push(point);
|
|
76924
|
+
}
|
|
76925
|
+
return points;
|
|
76926
|
+
} catch (err) {
|
|
76927
|
+
this.logger.warn("container memory cold read failed \u2014 answering from the hot window only", { meta: {
|
|
76928
|
+
nodeId,
|
|
76929
|
+
sinceMs,
|
|
76930
|
+
error: require_dist10.errMsg(err)
|
|
76931
|
+
} });
|
|
76932
|
+
return [];
|
|
76933
|
+
}
|
|
76934
|
+
}
|
|
76935
|
+
/**
|
|
76936
|
+
* Enforce BOTH bounds, oldest-first, through a bounded page each.
|
|
76937
|
+
*
|
|
76938
|
+
* Not rate-limited here: it is driven by the process store's own prune
|
|
76939
|
+
* cadence, so it runs at most once a minute by construction and a second
|
|
76940
|
+
* timer would be a second thing to get wrong.
|
|
76941
|
+
*/
|
|
76942
|
+
async prune(nodeIds, retention) {
|
|
76943
|
+
if (!await this.declare()) return null;
|
|
76944
|
+
const ageCutoff = this.nowFn() - retention.retentionHours * 36e5;
|
|
76945
|
+
let deletedByAge = 0;
|
|
76946
|
+
let rowsExamined = 0;
|
|
76947
|
+
for (const nodeId of nodeIds) {
|
|
76948
|
+
const outcome = await this.deleteOldestPage(nodeId, ageCutoff, this.prunePageRows);
|
|
76949
|
+
deletedByAge += outcome.deleted;
|
|
76950
|
+
rowsExamined += outcome.examined;
|
|
76951
|
+
}
|
|
76952
|
+
const total = await this.count();
|
|
76953
|
+
const excess = total === null ? 0 : total - CONTAINER_MEMORY_MAX_ROWS;
|
|
76954
|
+
let deletedByCap = 0;
|
|
76955
|
+
if (excess > 0) {
|
|
76956
|
+
let remaining = Math.min(excess, this.prunePageRows);
|
|
76957
|
+
for (const nodeId of nodeIds) {
|
|
76958
|
+
if (remaining <= 0) break;
|
|
76959
|
+
const outcome = await this.deleteOldestPage(nodeId, null, remaining);
|
|
76960
|
+
deletedByCap += outcome.deleted;
|
|
76961
|
+
remaining -= outcome.deleted;
|
|
76962
|
+
rowsExamined += outcome.examined;
|
|
76963
|
+
}
|
|
76964
|
+
this.logger.warn("container memory ROW CAP bit \u2014 evicting the oldest readings", { meta: {
|
|
76965
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
76966
|
+
rows: total,
|
|
76967
|
+
cap: CONTAINER_MEMORY_MAX_ROWS,
|
|
76968
|
+
over: excess,
|
|
76969
|
+
deleted: deletedByCap,
|
|
76970
|
+
hint: "lower the retention or the sampling cadence \u2014 the cap is the guarantee, not the intention"
|
|
76971
|
+
} });
|
|
76972
|
+
}
|
|
76973
|
+
return {
|
|
76974
|
+
deletedByAge,
|
|
76975
|
+
deletedByCap,
|
|
76976
|
+
rowsExamined,
|
|
76977
|
+
capBit: excess > 0
|
|
76978
|
+
};
|
|
76979
|
+
}
|
|
76980
|
+
/** Total rows, or `null` when the store could not answer. */
|
|
76981
|
+
async count() {
|
|
76982
|
+
if (!await this.declare()) return null;
|
|
76983
|
+
try {
|
|
76984
|
+
return await this.store.count.query({ collection: CONTAINER_MEMORY_COLLECTION });
|
|
76985
|
+
} catch (err) {
|
|
76986
|
+
this.logger.warn("container memory count failed \u2014 the row cap is not enforced this pass", { meta: { error: require_dist10.errMsg(err) } });
|
|
76987
|
+
return null;
|
|
76988
|
+
}
|
|
76989
|
+
}
|
|
76990
|
+
/**
|
|
76991
|
+
* Delete this node's oldest rows, at most one page's worth.
|
|
76992
|
+
*
|
|
76993
|
+
* `cutoff === null` means "the oldest rows, whatever their age" — the
|
|
76994
|
+
* row-cap path, which is bounded by `limitRows` instead. Either way the page
|
|
76995
|
+
* is READ FIRST, ordered by the one index, and its last `atMs` becomes the
|
|
76996
|
+
* effective cutoff for a single bounded delete. An unbounded `deleteWhere`
|
|
76997
|
+
* would be one statement and one stalling transaction the first time a
|
|
76998
|
+
* retention is lowered.
|
|
76999
|
+
*/
|
|
77000
|
+
async deleteOldestPage(nodeId, cutoff, limitRows) {
|
|
77001
|
+
if (limitRows <= 0) return {
|
|
77002
|
+
deleted: 0,
|
|
77003
|
+
examined: 0
|
|
77004
|
+
};
|
|
77005
|
+
try {
|
|
77006
|
+
const page = await this.store.query.query({
|
|
77007
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
77008
|
+
filter: {
|
|
77009
|
+
where: { nodeId },
|
|
77010
|
+
...cutoff === null ? {} : { whereBetween: { atMs: [0, cutoff] } },
|
|
77011
|
+
orderBy: {
|
|
77012
|
+
field: "atMs",
|
|
77013
|
+
direction: "asc"
|
|
77014
|
+
},
|
|
77015
|
+
limit: Math.min(limitRows, this.prunePageRows)
|
|
77016
|
+
},
|
|
77017
|
+
columns: ["atMs"]
|
|
77018
|
+
});
|
|
77019
|
+
if (page.length === 0) return {
|
|
77020
|
+
deleted: 0,
|
|
77021
|
+
examined: 0
|
|
77022
|
+
};
|
|
77023
|
+
const effectiveCutoff = Number(page.at(-1)?.data["atMs"]);
|
|
77024
|
+
if (!Number.isFinite(effectiveCutoff)) return {
|
|
77025
|
+
deleted: 0,
|
|
77026
|
+
examined: page.length
|
|
77027
|
+
};
|
|
77028
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
77029
|
+
collection: CONTAINER_MEMORY_COLLECTION,
|
|
77030
|
+
filter: {
|
|
77031
|
+
where: { nodeId },
|
|
77032
|
+
whereBetween: { atMs: [0, effectiveCutoff] }
|
|
77033
|
+
}
|
|
77034
|
+
});
|
|
77035
|
+
return {
|
|
77036
|
+
deleted,
|
|
77037
|
+
examined: page.length
|
|
77038
|
+
};
|
|
77039
|
+
} catch (err) {
|
|
77040
|
+
this.logger.warn("container memory prune failed \u2014 the table keeps growing this pass", { meta: {
|
|
77041
|
+
nodeId,
|
|
77042
|
+
cutoff,
|
|
77043
|
+
error: require_dist10.errMsg(err)
|
|
77044
|
+
} });
|
|
77045
|
+
return {
|
|
77046
|
+
deleted: 0,
|
|
77047
|
+
examined: 0
|
|
77048
|
+
};
|
|
77049
|
+
}
|
|
77050
|
+
}
|
|
77051
|
+
};
|
|
76133
77052
|
var IS_DARWIN = node_os.platform() === "darwin";
|
|
76134
77053
|
var IS_LINUX = node_os.platform() === "linux";
|
|
76135
77054
|
var NativeMetricsProvider = class {
|
|
@@ -76594,247 +77513,6 @@ var require_native_metrics_addon = __commonJS({
|
|
|
76594
77513
|
});
|
|
76595
77514
|
});
|
|
76596
77515
|
}
|
|
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
77516
|
var LOAD_SERIES_COLLECTION = "metrics:node-load-samples";
|
|
76839
77517
|
var DEFAULT_MAX_ROWS = 5e5;
|
|
76840
77518
|
var LOAD_SERIES_COLUMNS = [
|
|
@@ -77545,6 +78223,20 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77545
78223
|
* per sample, to reach that very same table.
|
|
77546
78224
|
*/
|
|
77547
78225
|
loadStore = null;
|
|
78226
|
+
/**
|
|
78227
|
+
* The container's OWN memory, sampled on the process-snapshot tick.
|
|
78228
|
+
*
|
|
78229
|
+
* It rides that tick rather than a timer of its own so a memory reading and
|
|
78230
|
+
* the process rows it must be compared against carry the SAME timestamp —
|
|
78231
|
+
* the whole question is a subtraction, and two clocks make it an estimate.
|
|
78232
|
+
* Constructed in `onInitialize`, where the logger exists; it resolves the
|
|
78233
|
+
* cgroup once and latches.
|
|
78234
|
+
*/
|
|
78235
|
+
memoryProbe = null;
|
|
78236
|
+
/** Hot tier for the container reading. Bounds in `container-memory-series.ts`. */
|
|
78237
|
+
memoryRing = new ContainerMemoryRing();
|
|
78238
|
+
/** Cold tier, HUB only — same reasoning as `loadStore`. */
|
|
78239
|
+
memoryStore = null;
|
|
77548
78240
|
/** The resolved knobs. Re-resolved on every settings write. */
|
|
77549
78241
|
loadConfig = {
|
|
77550
78242
|
cadenceSec: 10,
|
|
@@ -77617,13 +78309,20 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77617
78309
|
dumpHeapSnapshot: (params) => this.dumpHeapSnapshot(params)
|
|
77618
78310
|
};
|
|
77619
78311
|
this.applyLoadSeriesConfig();
|
|
77620
|
-
if (this.isHub)
|
|
77621
|
-
|
|
77622
|
-
|
|
77623
|
-
|
|
78312
|
+
if (this.isHub) {
|
|
78313
|
+
this.loadStore = new LoadSeriesStore({
|
|
78314
|
+
store: this.ctx.api.settingsStore,
|
|
78315
|
+
logger: this.ctx.logger.child("LoadSeries")
|
|
78316
|
+
});
|
|
78317
|
+
this.memoryStore = new ContainerMemoryStore({
|
|
78318
|
+
store: this.ctx.api.settingsStore,
|
|
78319
|
+
logger: this.ctx.logger.child("ContainerMemory")
|
|
78320
|
+
});
|
|
78321
|
+
}
|
|
78322
|
+
this.memoryProbe = new ContainerMemoryProbe({ logger: this.ctx.logger.child("ContainerMemory") });
|
|
77624
78323
|
this.snapshotTimer = setInterval(() => this.emitResourcesSnapshot(), METRICS_SNAPSHOT_INTERVAL_MS);
|
|
77625
78324
|
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)));
|
|
78325
|
+
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
78326
|
return [{
|
|
77628
78327
|
capability: require_dist10.metricsProviderCapability,
|
|
77629
78328
|
provider: composed
|
|
@@ -77641,19 +78340,31 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77641
78340
|
* The durable append is fire-and-forget: a storage stall must cost a gap in
|
|
77642
78341
|
* the cold window, never a blocked event-bus handler. Every failure logs.
|
|
77643
78342
|
*/
|
|
77644
|
-
retainSnapshot(nodeId, atMs, processes) {
|
|
78343
|
+
retainSnapshot(nodeId, atMs, processes, containerMemory) {
|
|
77645
78344
|
const retained = processes.map((p) => NativeMetricsAddon2.toRetained(p));
|
|
77646
78345
|
if (!this.loadRing.record(nodeId, atMs, retained)) return;
|
|
77647
78346
|
this.observedRowsByNode.set(nodeId, retained.length);
|
|
78347
|
+
if (containerMemory !== null) this.memoryRing.record(nodeId, atMs, containerMemory);
|
|
77648
78348
|
const store = this.loadStore;
|
|
77649
78349
|
if (store === null) return;
|
|
77650
|
-
store.append(nodeId, atMs, retained).then(() => store.prune([...this.observedRowsByNode.keys()], this.loadConfig)).
|
|
78350
|
+
store.append(nodeId, atMs, retained).then(() => store.prune([...this.observedRowsByNode.keys()], this.loadConfig)).then(async (outcome) => {
|
|
78351
|
+
if (outcome === null) return;
|
|
78352
|
+
await this.memoryStore?.prune([...this.observedRowsByNode.keys()], this.loadConfig);
|
|
78353
|
+
}).catch((err) => {
|
|
77651
78354
|
this.ctx.logger.warn("durable load series write failed", { meta: {
|
|
77652
78355
|
nodeId,
|
|
77653
78356
|
atMs,
|
|
77654
78357
|
error: err instanceof Error ? err.message : String(err)
|
|
77655
78358
|
} });
|
|
77656
78359
|
});
|
|
78360
|
+
if (containerMemory === null) return;
|
|
78361
|
+
this.memoryStore?.append(nodeId, atMs, containerMemory).catch((err) => {
|
|
78362
|
+
this.ctx.logger.warn("durable container memory write failed", { meta: {
|
|
78363
|
+
nodeId,
|
|
78364
|
+
atMs,
|
|
78365
|
+
error: err instanceof Error ? err.message : String(err)
|
|
78366
|
+
} });
|
|
78367
|
+
});
|
|
77657
78368
|
}
|
|
77658
78369
|
/**
|
|
77659
78370
|
* The ONE reader, over both tiers.
|
|
@@ -77682,6 +78393,14 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77682
78393
|
return {
|
|
77683
78394
|
nodeId: params.forNodeId,
|
|
77684
78395
|
series: merged.series,
|
|
78396
|
+
containerMemory: await this.readContainerMemory({
|
|
78397
|
+
forNodeId: params.forNodeId,
|
|
78398
|
+
sinceMs,
|
|
78399
|
+
...params.sinceMs !== void 0 ? { exclusiveSinceMs: params.sinceMs } : {},
|
|
78400
|
+
bucketMs: merged.bucketMs,
|
|
78401
|
+
cadenceMs,
|
|
78402
|
+
origin: merged.oldestAtMs
|
|
78403
|
+
}),
|
|
77685
78404
|
bucketMs: merged.bucketMs,
|
|
77686
78405
|
retainedSamples: merged.retainedSamples,
|
|
77687
78406
|
oldestAtMs: merged.oldestAtMs,
|
|
@@ -77690,6 +78409,24 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77690
78409
|
};
|
|
77691
78410
|
}
|
|
77692
78411
|
/**
|
|
78412
|
+
* The container's memory over the same window, on the same x-axis.
|
|
78413
|
+
*
|
|
78414
|
+
* Both tiers, merged and deduped on `atMs` like the process series, then
|
|
78415
|
+
* reduced against the SAME `bucketMs` and the SAME origin — otherwise the
|
|
78416
|
+
* two series in one answer would sit on two different axes and the
|
|
78417
|
+
* subtraction they exist for would be an eyeball.
|
|
78418
|
+
*
|
|
78419
|
+
* An EMPTY answer means the node has no cgroup, or nothing has been retained
|
|
78420
|
+
* for it yet. It never means zero.
|
|
78421
|
+
*/
|
|
78422
|
+
async readContainerMemory(params) {
|
|
78423
|
+
const hot = this.memoryRing.read(params.forNodeId, params.exclusiveSinceMs);
|
|
78424
|
+
const store = this.memoryStore;
|
|
78425
|
+
const merged = mergeContainerMemory(store === null ? [] : await store.read(params.forNodeId, params.sinceMs, CONTAINER_MEMORY_MAX_ROWS), hot);
|
|
78426
|
+
if (params.bucketMs <= params.cadenceMs) return merged;
|
|
78427
|
+
return reduceContainerMemory(merged, params.bucketMs, params.origin ?? 0);
|
|
78428
|
+
}
|
|
78429
|
+
/**
|
|
77693
78430
|
* Re-resolve the knobs and restate the fixed cadence.
|
|
77694
78431
|
*
|
|
77695
78432
|
* A REFUSED value (out of 5-60 s, or out of 1-72 h) leaves the previous
|
|
@@ -77806,6 +78543,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77806
78543
|
const timestamp = Date.now();
|
|
77807
78544
|
try {
|
|
77808
78545
|
const processes = await this.listNodeProcesses();
|
|
78546
|
+
const containerMemory = this.memoryProbe?.sample() ?? null;
|
|
77809
78547
|
eventBus.emit(require_dist10.createEvent(require_dist10.EventCategory.MetricsNodeProcessesSnapshot, {
|
|
77810
78548
|
type: "node",
|
|
77811
78549
|
id: nodeId,
|
|
@@ -77813,7 +78551,8 @@ var require_native_metrics_addon = __commonJS({
|
|
|
77813
78551
|
}, {
|
|
77814
78552
|
nodeId,
|
|
77815
78553
|
processes,
|
|
77816
|
-
timestamp
|
|
78554
|
+
timestamp,
|
|
78555
|
+
containerMemory
|
|
77817
78556
|
}));
|
|
77818
78557
|
} catch (err) {
|
|
77819
78558
|
this.ctx.logger.warn("process snapshot skipped \u2014 this interval will be missing", { meta: {
|
|
@@ -78079,14 +78818,14 @@ var require_filesystem_storage_addon = __commonJS({
|
|
|
78079
78818
|
[Symbol.toStringTag]: { value: "Module" }
|
|
78080
78819
|
});
|
|
78081
78820
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
78082
|
-
var require_dist10 =
|
|
78821
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
78083
78822
|
var node_crypto = __require("crypto");
|
|
78084
78823
|
var node_fs_promises = __require("fs/promises");
|
|
78085
78824
|
var node_path = __require("path");
|
|
78086
78825
|
node_path = require_chunk.__toESM(node_path);
|
|
78087
|
-
var node_os = __require("os");
|
|
78088
78826
|
var node_fs = __require("fs");
|
|
78089
78827
|
node_fs = require_chunk.__toESM(node_fs);
|
|
78828
|
+
var node_os = __require("os");
|
|
78090
78829
|
function isWithinAllowedRoots(candidate, allowedRoots) {
|
|
78091
78830
|
const norm = (0, node_path.resolve)(candidate);
|
|
78092
78831
|
return allowedRoots.some((root) => {
|
|
@@ -79195,8 +79934,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
79195
79934
|
[Symbol.toStringTag]: { value: "Module" }
|
|
79196
79935
|
});
|
|
79197
79936
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
79198
|
-
var require_dist10 =
|
|
79199
|
-
var require_retired_settings_keys =
|
|
79937
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
79938
|
+
var require_retired_settings_keys = require_retired_settings_keys_DBY6ebwV();
|
|
79200
79939
|
var node_crypto = __require("crypto");
|
|
79201
79940
|
var node_fs = __require("fs");
|
|
79202
79941
|
var node_module = __require("module");
|
|
@@ -81475,7 +82214,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
81475
82214
|
[Symbol.toStringTag]: { value: "Module" }
|
|
81476
82215
|
});
|
|
81477
82216
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
81478
|
-
var require_dist10 =
|
|
82217
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
81479
82218
|
var node_crypto = __require("crypto");
|
|
81480
82219
|
var node_fs_promises = __require("fs/promises");
|
|
81481
82220
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -83356,7 +84095,7 @@ var require_system_config_addon = __commonJS({
|
|
|
83356
84095
|
[Symbol.toStringTag]: { value: "Module" }
|
|
83357
84096
|
});
|
|
83358
84097
|
require_chunk_Cek0wNdY();
|
|
83359
|
-
var require_dist10 =
|
|
84098
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
83360
84099
|
var SECTION_TITLES = {
|
|
83361
84100
|
server: "Server",
|
|
83362
84101
|
auth: "Authentication"
|
|
@@ -101417,7 +102156,7 @@ var require_winston_logging = __commonJS({
|
|
|
101417
102156
|
[Symbol.toStringTag]: { value: "Module" }
|
|
101418
102157
|
});
|
|
101419
102158
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
101420
|
-
var require_dist10 =
|
|
102159
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
101421
102160
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
101422
102161
|
var node_path = __require("path");
|
|
101423
102162
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -102447,9 +103186,9 @@ var require_file_data_plane_DO8KbxCe = __commonJS({
|
|
|
102447
103186
|
}
|
|
102448
103187
|
});
|
|
102449
103188
|
|
|
102450
|
-
// ../system/dist/tls-
|
|
102451
|
-
var
|
|
102452
|
-
"../system/dist/tls-
|
|
103189
|
+
// ../system/dist/tls-BxQlomxd.js
|
|
103190
|
+
var require_tls_BxQlomxd = __commonJS({
|
|
103191
|
+
"../system/dist/tls-BxQlomxd.js"(exports) {
|
|
102453
103192
|
"use strict";
|
|
102454
103193
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
102455
103194
|
var node_crypto = __require("crypto");
|
|
@@ -102457,9 +103196,9 @@ var require_tls_u8QCJCFE = __commonJS({
|
|
|
102457
103196
|
var node_path = __require("path");
|
|
102458
103197
|
var node_child_process = __require("child_process");
|
|
102459
103198
|
var node_util = __require("util");
|
|
103199
|
+
var node_fs = __require("fs");
|
|
102460
103200
|
var node_os = __require("os");
|
|
102461
103201
|
node_os = require_chunk.__toESM(node_os);
|
|
102462
|
-
var node_fs = __require("fs");
|
|
102463
103202
|
var node_http = __require("http");
|
|
102464
103203
|
var node_net = __require("net");
|
|
102465
103204
|
var DEFAULT_HTTP_PORT = 4480;
|
|
@@ -113905,12 +114644,12 @@ var require_dist2 = __commonJS({
|
|
|
113905
114644
|
}
|
|
113906
114645
|
});
|
|
113907
114646
|
|
|
113908
|
-
// ../system/dist/manifest-python-deps-
|
|
113909
|
-
var
|
|
113910
|
-
"../system/dist/manifest-python-deps-
|
|
114647
|
+
// ../system/dist/manifest-python-deps-COeSr7el.js
|
|
114648
|
+
var require_manifest_python_deps_COeSr7el = __commonJS({
|
|
114649
|
+
"../system/dist/manifest-python-deps-COeSr7el.js"(exports) {
|
|
113911
114650
|
"use strict";
|
|
113912
114651
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
113913
|
-
|
|
114652
|
+
require_dist_BPlfW_CG();
|
|
113914
114653
|
var node_crypto = __require("crypto");
|
|
113915
114654
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
113916
114655
|
var _camstack_types_node = require_node();
|
|
@@ -113919,10 +114658,10 @@ var require_manifest_python_deps_CcDe4S2Z = __commonJS({
|
|
|
113919
114658
|
node_path = require_chunk.__toESM(node_path);
|
|
113920
114659
|
var node_child_process = __require("child_process");
|
|
113921
114660
|
var node_util = __require("util");
|
|
113922
|
-
var node_os = __require("os");
|
|
113923
|
-
node_os = require_chunk.__toESM(node_os);
|
|
113924
114661
|
var node_fs = __require("fs");
|
|
113925
114662
|
node_fs = require_chunk.__toESM(node_fs);
|
|
114663
|
+
var node_os = __require("os");
|
|
114664
|
+
node_os = require_chunk.__toESM(node_os);
|
|
113926
114665
|
var node_http = __require("http");
|
|
113927
114666
|
var node_v8 = __require("v8");
|
|
113928
114667
|
node_v8 = require_chunk.__toESM(node_v8);
|
|
@@ -125356,7 +126095,7 @@ var require_dist3 = __commonJS({
|
|
|
125356
126095
|
"use strict";
|
|
125357
126096
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
125358
126097
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
125359
|
-
var require_dist10 =
|
|
126098
|
+
var require_dist10 = require_dist_BPlfW_CG();
|
|
125360
126099
|
var require_builtins_alerts_alerts_addon = require_alerts_addon();
|
|
125361
126100
|
require_alerts();
|
|
125362
126101
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
@@ -125381,8 +126120,8 @@ var require_dist3 = __commonJS({
|
|
|
125381
126120
|
require_system_config();
|
|
125382
126121
|
var require_builtins_winston_logging_index = require_winston_logging();
|
|
125383
126122
|
var require_file_data_plane = require_file_data_plane_DO8KbxCe();
|
|
125384
|
-
var require_tls$1 =
|
|
125385
|
-
var require_manifest_python_deps =
|
|
126123
|
+
var require_tls$1 = require_tls_BxQlomxd();
|
|
126124
|
+
var require_manifest_python_deps = require_manifest_python_deps_COeSr7el();
|
|
125386
126125
|
var require_resource_monitor = require_resource_monitor_CdnzxBLP();
|
|
125387
126126
|
var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
|
|
125388
126127
|
var zod = require_zod();
|
|
@@ -125394,11 +126133,11 @@ var require_dist3 = __commonJS({
|
|
|
125394
126133
|
var node_child_process = __require("child_process");
|
|
125395
126134
|
var node_util = __require("util");
|
|
125396
126135
|
node_util = require_chunk.__toESM(node_util);
|
|
125397
|
-
var node_os = __require("os");
|
|
125398
|
-
node_os = require_chunk.__toESM(node_os);
|
|
125399
126136
|
var node_fs = __require("fs");
|
|
125400
126137
|
var node_fs$1 = require_chunk.__toESM(node_fs, 1);
|
|
125401
126138
|
node_fs = require_chunk.__toESM(node_fs);
|
|
126139
|
+
var node_os = __require("os");
|
|
126140
|
+
node_os = require_chunk.__toESM(node_os);
|
|
125402
126141
|
var node_http = __require("http");
|
|
125403
126142
|
var node_vm = __require("vm");
|
|
125404
126143
|
node_vm = require_chunk.__toESM(node_vm);
|
|
@@ -215080,6 +215819,38 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
215080
215819
|
/** Wall-clock uptime (seconds). Parsed from `ps etime`. */
|
|
215081
215820
|
uptimeSec: zod.z.number()
|
|
215082
215821
|
});
|
|
215822
|
+
var ContainerMemoryPointSchema = zod.z.object({
|
|
215823
|
+
/** Which hierarchy answered, so a reading is never ambiguous. */
|
|
215824
|
+
source: zod.z.enum(["cgroup-v2", "cgroup-v1"]),
|
|
215825
|
+
/** `memory.current` (v2) / `memory.usage_in_bytes` (v1). Always known. */
|
|
215826
|
+
currentBytes: zod.z.number(),
|
|
215827
|
+
/** The cgroup's ceiling. `null` = NO LIMIT — never a sentinel, never zero. */
|
|
215828
|
+
limitBytes: zod.z.number().nullable(),
|
|
215829
|
+
/** Anonymous pages: the closest thing to "what the processes allocated". */
|
|
215830
|
+
anonBytes: zod.z.number().nullable(),
|
|
215831
|
+
/** Page cache. Charged to the cgroup, owned by no process. */
|
|
215832
|
+
fileBytes: zod.z.number().nullable(),
|
|
215833
|
+
/**
|
|
215834
|
+
* Shared memory — and the field that explained the largest single surprise.
|
|
215835
|
+
* The i915 driver backs GPU buffers with shmem, so an inference pool or a
|
|
215836
|
+
* hardware-decode session holding DRM objects is charged HERE and appears
|
|
215837
|
+
* nowhere in a `ps` scan.
|
|
215838
|
+
*/
|
|
215839
|
+
shmemBytes: zod.z.number().nullable(),
|
|
215840
|
+
/** Kernel slab charged to this cgroup. `null` on v1, which never publishes it. */
|
|
215841
|
+
slabBytes: zod.z.number().nullable(),
|
|
215842
|
+
/**
|
|
215843
|
+
* Shrinkable i915 GEM object bytes, from debugfs.
|
|
215844
|
+
*
|
|
215845
|
+
* **Host-wide across every DRM client, NOT cgroup-scoped.** It is not a
|
|
215846
|
+
* component of `currentBytes` and must not be subtracted from it; it says
|
|
215847
|
+
* what put the shmem there, where `shmemBytes` only says how much.
|
|
215848
|
+
*
|
|
215849
|
+
* `null` wherever debugfs is not mounted — which is inside every camstack
|
|
215850
|
+
* container today — and on any node with no Intel GPU.
|
|
215851
|
+
*/
|
|
215852
|
+
gpuShmemBytes: zod.z.number().nullable()
|
|
215853
|
+
}).extend({ atMs: zod.z.number() });
|
|
215083
215854
|
var DumpHeapSnapshotInputSchema = zod.z.object({
|
|
215084
215855
|
/** The addon whose runner should dump a heap snapshot. */
|
|
215085
215856
|
addonId: zod.z.string()
|
|
@@ -215129,6 +215900,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
215129
215900
|
/** One entry per function seen in the window, heaviest-first. */
|
|
215130
215901
|
series: zod.z.array(LoadFunctionSeriesSchema).readonly(),
|
|
215131
215902
|
/**
|
|
215903
|
+
* The CONTAINER's memory over the same window, oldest-first.
|
|
215904
|
+
*
|
|
215905
|
+
* Sits next to `series` rather than in a method of its own because the whole
|
|
215906
|
+
* question is a subtraction: the per-process rows in `series` sum to one
|
|
215907
|
+
* number and this one is another, and an operator who has to issue two calls
|
|
215908
|
+
* to compare them will compare two different instants. Same reader, same
|
|
215909
|
+
* `sinceMs`, same `bucketMs`, same timestamps.
|
|
215910
|
+
*
|
|
215911
|
+
* **EMPTY means ABSENT, never zero.** A node with no cgroup — a developer
|
|
215912
|
+
* Mac, a bare-metal host, a container with the hierarchy hidden — reports no
|
|
215913
|
+
* points at all. A zero here would be indistinguishable from a healthy
|
|
215914
|
+
* container and is precisely the lie this field exists to avoid.
|
|
215915
|
+
*/
|
|
215916
|
+
containerMemory: zod.z.array(ContainerMemoryPointSchema).readonly(),
|
|
215917
|
+
/**
|
|
215132
215918
|
* Width of one returned bucket, in ms. Equals the sampling cadence when no
|
|
215133
215919
|
* reduction was needed — so a caller can always say what one point covers
|
|
215134
215920
|
* without having to know whether it was reduced.
|
|
@@ -226930,6 +227716,33 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
226930
227716
|
deviceNative: true,
|
|
226931
227717
|
mode: "singleton",
|
|
226932
227718
|
deviceTypes: [require_sleep.DeviceType.Camera],
|
|
227719
|
+
/**
|
|
227720
|
+
* **Auth tier: `protected` on every method — deliberate, and load-bearing.**
|
|
227721
|
+
*
|
|
227722
|
+
* Talking through a camera is an OPERATE action, not a CONFIGURE one. This
|
|
227723
|
+
* cap has no configuration surface at all: all six methods open, feed and
|
|
227724
|
+
* close one live audio session against one `deviceId`. That is the same
|
|
227725
|
+
* authority as `ptz.move` or `snapshot.getSnapshot`, both `protected` — and
|
|
227726
|
+
* the opposite of `ptz.savePreset` / `snapshot.invalidateCache`, which are
|
|
227727
|
+
* `admin` because they change what the device IS.
|
|
227728
|
+
*
|
|
227729
|
+
* `protected` does not mean ungated: `protectedProcedure` runs the
|
|
227730
|
+
* `METHOD_ACCESS_MAP` scope check, and every method here is `scope: 'device'`
|
|
227731
|
+
* with `access: 'create'` and a `deviceId` in its input. So a caller needs a
|
|
227732
|
+
* grant that covers THAT camera at `create` — a `camera-viewer` (`view`
|
|
227733
|
+
* only) still cannot talk, and a grant on camera 5 cannot talk through
|
|
227734
|
+
* camera 7.
|
|
227735
|
+
*
|
|
227736
|
+
* Every method was `auth: 'admin'` from the initial commit, which made the
|
|
227737
|
+
* cap unreachable by every non-admin principal — `adminProcedure` throws
|
|
227738
|
+
* `FORBIDDEN: Admin required` BEFORE the scope check runs, so the scope
|
|
227739
|
+
* machinery generated for this cap (`METHOD_ACCESS_MAP`,
|
|
227740
|
+
* `DEVICE_SCOPED_CAPS`, `METHOD_DEVICE_SELECTORS`) was complete and dead. The
|
|
227741
|
+
* `camera-operator` scope preset has promised "PTZ control, intercom,
|
|
227742
|
+
* snapshots" since that same commit; the promise could not be kept. Recorded
|
|
227743
|
+
* as D289; `scripts/check-scope-preset-promises.ts` now fails the build if a
|
|
227744
|
+
* preset promises a cap no row of that preset can reach.
|
|
227745
|
+
*/
|
|
226933
227746
|
methods: {
|
|
226934
227747
|
/**
|
|
226935
227748
|
* Open a server-side WebRTC audio-only session. Returns an SDP
|
|
@@ -226942,7 +227755,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
226942
227755
|
sdpOffer: zod.z.string()
|
|
226943
227756
|
}), {
|
|
226944
227757
|
kind: "mutation",
|
|
226945
|
-
auth: "
|
|
227758
|
+
auth: "protected"
|
|
226946
227759
|
}),
|
|
226947
227760
|
handleAnswer: require_sleep.method(zod.z.object({
|
|
226948
227761
|
deviceId: zod.z.number(),
|
|
@@ -226950,7 +227763,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
226950
227763
|
sdpAnswer: zod.z.string()
|
|
226951
227764
|
}), zod.z.void(), {
|
|
226952
227765
|
kind: "mutation",
|
|
226953
|
-
auth: "
|
|
227766
|
+
auth: "protected"
|
|
226954
227767
|
}),
|
|
226955
227768
|
/** Close explicitly. Server also auto-closes on 30s idle. */
|
|
226956
227769
|
stopSession: require_sleep.method(zod.z.object({
|
|
@@ -226958,7 +227771,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
226958
227771
|
sessionId: zod.z.string()
|
|
226959
227772
|
}), zod.z.void(), {
|
|
226960
227773
|
kind: "mutation",
|
|
226961
|
-
auth: "
|
|
227774
|
+
auth: "protected"
|
|
226962
227775
|
}),
|
|
226963
227776
|
/**
|
|
226964
227777
|
* Open a raw-PCM talk session (no WebRTC SDP plumbing). Used by
|
|
@@ -226971,7 +227784,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
226971
227784
|
*/
|
|
226972
227785
|
startTalkSession: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.object({ sessionId: zod.z.string() }), {
|
|
226973
227786
|
kind: "mutation",
|
|
226974
|
-
auth: "
|
|
227787
|
+
auth: "protected"
|
|
226975
227788
|
}),
|
|
226976
227789
|
/**
|
|
226977
227790
|
* Push one chunk of talk-back audio onto the active talk session.
|
|
@@ -227006,12 +227819,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
227006
227819
|
sequenceNumber: zod.z.number().int()
|
|
227007
227820
|
}), zod.z.object({ accepted: zod.z.boolean() }), {
|
|
227008
227821
|
kind: "mutation",
|
|
227009
|
-
auth: "
|
|
227822
|
+
auth: "protected"
|
|
227010
227823
|
}),
|
|
227011
227824
|
/** Close the raw-PCM talk session. Idempotent. */
|
|
227012
227825
|
endTalkSession: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), {
|
|
227013
227826
|
kind: "mutation",
|
|
227014
|
-
auth: "
|
|
227827
|
+
auth: "protected"
|
|
227015
227828
|
})
|
|
227016
227829
|
},
|
|
227017
227830
|
events: { onStatusChanged: { data: zod.z.object({
|
|
@@ -244269,7 +245082,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244269
245082
|
{
|
|
244270
245083
|
id: "camera-operator",
|
|
244271
245084
|
label: "Camera operator",
|
|
244272
|
-
description: "Everything `camera-viewer` does plus PTZ
|
|
245085
|
+
description: "Everything `camera-viewer` does plus live control of every granted camera \u2014 PTZ movement, intercom talk-back and on-demand snapshots. Cannot delete devices, and grants nothing on system-scope caps (recording control, users, addons).",
|
|
244273
245086
|
rows: [{
|
|
244274
245087
|
type: "category",
|
|
244275
245088
|
target: "device",
|
|
@@ -400869,6 +401682,7 @@ var require_scope_access = __commonJS({
|
|
|
400869
401682
|
"use strict";
|
|
400870
401683
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
400871
401684
|
exports.DEVICE_ENUMERATION_METHODS = void 0;
|
|
401685
|
+
exports.referencedDeviceIds = referencedDeviceIds;
|
|
400872
401686
|
exports.checkScopeAccess = checkScopeAccess;
|
|
400873
401687
|
var system_1 = require_dist3();
|
|
400874
401688
|
var types_1 = require_dist4();
|
|
@@ -412794,6 +413608,25 @@ var require_trpc_router = __commonJS({
|
|
|
412794
413608
|
}
|
|
412795
413609
|
});
|
|
412796
413610
|
|
|
413611
|
+
// ../../server/backend/dist/api/trpc/trpc-error-device-tags.js
|
|
413612
|
+
var require_trpc_error_device_tags = __commonJS({
|
|
413613
|
+
"../../server/backend/dist/api/trpc/trpc-error-device-tags.js"(exports) {
|
|
413614
|
+
"use strict";
|
|
413615
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
413616
|
+
exports.trpcErrorDeviceTags = trpcErrorDeviceTags;
|
|
413617
|
+
var scope_access_js_1 = require_scope_access();
|
|
413618
|
+
function trpcErrorDeviceTags(path, input) {
|
|
413619
|
+
if (path === void 0)
|
|
413620
|
+
return {};
|
|
413621
|
+
const ids = (0, scope_access_js_1.referencedDeviceIds)(path, input);
|
|
413622
|
+
const first = ids[0];
|
|
413623
|
+
if (first === void 0)
|
|
413624
|
+
return {};
|
|
413625
|
+
return ids.length > 1 ? { deviceId: first, deviceCount: ids.length } : { deviceId: first };
|
|
413626
|
+
}
|
|
413627
|
+
}
|
|
413628
|
+
});
|
|
413629
|
+
|
|
412797
413630
|
// ../../server/backend/dist/api/trpc/ws-request-census.js
|
|
412798
413631
|
var require_ws_request_census = __commonJS({
|
|
412799
413632
|
"../../server/backend/dist/api/trpc/ws-request-census.js"(exports) {
|
|
@@ -420876,6 +421709,7 @@ var require_main4 = __commonJS({
|
|
|
420876
421709
|
var trpc_context_1 = require_trpc_context();
|
|
420877
421710
|
var trpc_router_1 = require_trpc_router();
|
|
420878
421711
|
var trpc_error_principal_1 = require_trpc_error_principal();
|
|
421712
|
+
var trpc_error_device_tags_1 = require_trpc_error_device_tags();
|
|
420879
421713
|
var ws_request_census_1 = require_ws_request_census();
|
|
420880
421714
|
var addon_route_jwt_gate_js_1 = require_addon_route_jwt_gate();
|
|
420881
421715
|
var session_cookie_js_1 = require_session_cookie();
|
|
@@ -421174,9 +422008,13 @@ var require_main4 = __commonJS({
|
|
|
421174
422008
|
(0, http_request_census_hook_1.recordTrpcHttpRequest)(httpRequestCensus, req, trpcCtx.user);
|
|
421175
422009
|
return trpcCtx;
|
|
421176
422010
|
},
|
|
421177
|
-
onError: ({ path: trpcPath, error, ctx }) => {
|
|
422011
|
+
onError: ({ path: trpcPath, error, ctx, input }) => {
|
|
421178
422012
|
const trpcLogger = app.get(logging_service_1.LoggingService).createLogger("tRPC");
|
|
421179
422013
|
trpcLogger.warn("tRPC error", {
|
|
422014
|
+
// WHICH camera. A refusal on a device-scoped path that names no
|
|
422015
|
+
// deviceId cannot answer "one camera or all of them?" — the
|
|
422016
|
+
// question always asked first (2026-08-29, `intercom.startSession`).
|
|
422017
|
+
tags: (0, trpc_error_device_tags_1.trpcErrorDeviceTags)(trpcPath, input),
|
|
421180
422018
|
meta: {
|
|
421181
422019
|
code: error.code,
|
|
421182
422020
|
path: trpcPath ?? "?",
|
|
@@ -421575,9 +422413,12 @@ var require_main4 = __commonJS({
|
|
|
421575
422413
|
(0, ws_request_census_1.identifyWsCensusSession)(opts.res, (0, trpc_error_principal_1.describeTrpcPrincipal)(wsCtx.user));
|
|
421576
422414
|
return wsCtx;
|
|
421577
422415
|
},
|
|
421578
|
-
onError: ({ path: trpcPath, error, ctx }) => {
|
|
422416
|
+
onError: ({ path: trpcPath, error, ctx, input }) => {
|
|
421579
422417
|
const trpcLogger = app.get(logging_service_1.LoggingService).createLogger("tRPC:ws");
|
|
421580
422418
|
trpcLogger.warn("tRPC error", {
|
|
422419
|
+
// The socket the viewer uses — this is the transport that logged the
|
|
422420
|
+
// four `intercom.startSession` refusals with no camera on them.
|
|
422421
|
+
tags: (0, trpc_error_device_tags_1.trpcErrorDeviceTags)(trpcPath, input),
|
|
421581
422422
|
meta: {
|
|
421582
422423
|
code: error.code,
|
|
421583
422424
|
path: trpcPath ?? "?",
|