camstack 1.2.50 → 1.2.52
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-B-mBrEz9.js
|
|
23637
|
+
var require_dist_B_mBrEz9 = __commonJS({
|
|
23638
|
+
"../system/dist/dist-B-mBrEz9.js"(exports) {
|
|
23639
23639
|
"use strict";
|
|
23640
23640
|
var zod = require_zod();
|
|
23641
23641
|
var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
@@ -26367,6 +26367,116 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
26367
26367
|
*/
|
|
26368
26368
|
debug: zod.z.boolean().optional()
|
|
26369
26369
|
});
|
|
26370
|
+
var UNATTRIBUTED_BUCKET_KEY = "__unattributed__";
|
|
26371
|
+
var ROOT_BUCKET_KEY = "__root__";
|
|
26372
|
+
function bucketFor(row) {
|
|
26373
|
+
if (row.addonId !== null) return {
|
|
26374
|
+
key: row.addonId,
|
|
26375
|
+
kind: "addon"
|
|
26376
|
+
};
|
|
26377
|
+
if (row.classification === "root") return {
|
|
26378
|
+
key: ROOT_BUCKET_KEY,
|
|
26379
|
+
kind: "root"
|
|
26380
|
+
};
|
|
26381
|
+
return {
|
|
26382
|
+
key: UNATTRIBUTED_BUCKET_KEY,
|
|
26383
|
+
kind: "unattributed"
|
|
26384
|
+
};
|
|
26385
|
+
}
|
|
26386
|
+
function deci(value) {
|
|
26387
|
+
return Math.round(value * 10) / 10;
|
|
26388
|
+
}
|
|
26389
|
+
function foldSnapshotByFunction(rows, atMs) {
|
|
26390
|
+
const acc = /* @__PURE__ */ new Map();
|
|
26391
|
+
for (const row of rows) {
|
|
26392
|
+
const { key, kind } = bucketFor(row);
|
|
26393
|
+
const cur = acc.get(key) ?? {
|
|
26394
|
+
kind,
|
|
26395
|
+
main: 0,
|
|
26396
|
+
gc: 0,
|
|
26397
|
+
lifetime: 0,
|
|
26398
|
+
memory: 0,
|
|
26399
|
+
count: 0,
|
|
26400
|
+
splitKnown: true
|
|
26401
|
+
};
|
|
26402
|
+
const known = row.cpuMainPercent !== null && row.cpuGcPercent !== null;
|
|
26403
|
+
acc.set(key, {
|
|
26404
|
+
kind: cur.kind,
|
|
26405
|
+
main: cur.main + (row.cpuMainPercent ?? 0),
|
|
26406
|
+
gc: cur.gc + (row.cpuGcPercent ?? 0),
|
|
26407
|
+
lifetime: cur.lifetime + row.cpuPercent,
|
|
26408
|
+
memory: cur.memory + row.memoryRssBytes,
|
|
26409
|
+
count: cur.count + 1,
|
|
26410
|
+
splitKnown: cur.splitKnown && known
|
|
26411
|
+
});
|
|
26412
|
+
}
|
|
26413
|
+
return [...acc.entries()].map(([key, a]) => {
|
|
26414
|
+
const main = a.splitKnown ? deci(a.main) : null;
|
|
26415
|
+
const gc = a.splitKnown ? deci(a.gc) : null;
|
|
26416
|
+
const lifetime = deci(a.lifetime);
|
|
26417
|
+
return {
|
|
26418
|
+
key,
|
|
26419
|
+
kind: a.kind,
|
|
26420
|
+
point: {
|
|
26421
|
+
atMs,
|
|
26422
|
+
samples: 1,
|
|
26423
|
+
cpuMainPercent: main,
|
|
26424
|
+
cpuMainPercentMin: main,
|
|
26425
|
+
cpuGcPercent: gc,
|
|
26426
|
+
cpuGcPercentMin: gc,
|
|
26427
|
+
cpuLifetimePercent: lifetime,
|
|
26428
|
+
cpuLifetimePercentMin: lifetime,
|
|
26429
|
+
memoryRssBytes: a.memory,
|
|
26430
|
+
memoryRssBytesMin: a.memory,
|
|
26431
|
+
processCount: a.count,
|
|
26432
|
+
processCountMin: a.count
|
|
26433
|
+
}
|
|
26434
|
+
};
|
|
26435
|
+
});
|
|
26436
|
+
}
|
|
26437
|
+
function minNullable(a, b) {
|
|
26438
|
+
if (a === null || b === null) return null;
|
|
26439
|
+
return a < b ? a : b;
|
|
26440
|
+
}
|
|
26441
|
+
function maxNullable(a, b) {
|
|
26442
|
+
if (a === null || b === null) return null;
|
|
26443
|
+
return a > b ? a : b;
|
|
26444
|
+
}
|
|
26445
|
+
function mergePoints(held, next, atMs) {
|
|
26446
|
+
return {
|
|
26447
|
+
atMs,
|
|
26448
|
+
samples: held.samples + next.samples,
|
|
26449
|
+
cpuMainPercent: maxNullable(held.cpuMainPercent, next.cpuMainPercent),
|
|
26450
|
+
cpuMainPercentMin: minNullable(held.cpuMainPercentMin, next.cpuMainPercentMin),
|
|
26451
|
+
cpuGcPercent: maxNullable(held.cpuGcPercent, next.cpuGcPercent),
|
|
26452
|
+
cpuGcPercentMin: minNullable(held.cpuGcPercentMin, next.cpuGcPercentMin),
|
|
26453
|
+
cpuLifetimePercent: Math.max(held.cpuLifetimePercent, next.cpuLifetimePercent),
|
|
26454
|
+
cpuLifetimePercentMin: Math.min(held.cpuLifetimePercentMin, next.cpuLifetimePercentMin),
|
|
26455
|
+
memoryRssBytes: Math.max(held.memoryRssBytes, next.memoryRssBytes),
|
|
26456
|
+
memoryRssBytesMin: Math.min(held.memoryRssBytesMin, next.memoryRssBytesMin),
|
|
26457
|
+
processCount: Math.max(held.processCount, next.processCount),
|
|
26458
|
+
processCountMin: Math.min(held.processCountMin, next.processCountMin)
|
|
26459
|
+
};
|
|
26460
|
+
}
|
|
26461
|
+
function resolveBucketMs(spanMs, cadenceMs, maxPoints) {
|
|
26462
|
+
if (maxPoints <= 0 || cadenceMs <= 0 || spanMs <= 0) return Math.max(cadenceMs, 1);
|
|
26463
|
+
const wanted = spanMs / maxPoints;
|
|
26464
|
+
if (wanted <= cadenceMs) return cadenceMs;
|
|
26465
|
+
return Math.ceil(wanted / cadenceMs) * cadenceMs;
|
|
26466
|
+
}
|
|
26467
|
+
function reducePoints(points, bucketMs, origin) {
|
|
26468
|
+
if (bucketMs <= 0 || points.length === 0) return points;
|
|
26469
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
26470
|
+
for (const point of points) {
|
|
26471
|
+
const start = origin + Math.floor((point.atMs - origin) / bucketMs) * bucketMs;
|
|
26472
|
+
const held = buckets.get(start);
|
|
26473
|
+
buckets.set(start, held === void 0 ? {
|
|
26474
|
+
...point,
|
|
26475
|
+
atMs: start
|
|
26476
|
+
} : mergePoints(held, point, start));
|
|
26477
|
+
}
|
|
26478
|
+
return [...buckets.values()].toSorted((a, b) => a.atMs - b.atMs);
|
|
26479
|
+
}
|
|
26370
26480
|
var MODEL_FORMATS = [
|
|
26371
26481
|
"onnx",
|
|
26372
26482
|
"coreml",
|
|
@@ -29520,6 +29630,10 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
29520
29630
|
id: zod.z.string(),
|
|
29521
29631
|
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
29522
29632
|
});
|
|
29633
|
+
var BulkRecordSchema = zod.z.object({
|
|
29634
|
+
id: zod.z.string().optional(),
|
|
29635
|
+
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
29636
|
+
});
|
|
29523
29637
|
var CollectionColumnSchema = zod.z.object({
|
|
29524
29638
|
name: zod.z.string(),
|
|
29525
29639
|
type: zod.z.enum([
|
|
@@ -29594,6 +29708,34 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
29594
29708
|
collection: zod.z.string(),
|
|
29595
29709
|
record: SettingsRecordSchema
|
|
29596
29710
|
}), zod.z.void(), { kind: "mutation" }),
|
|
29711
|
+
/**
|
|
29712
|
+
* Insert MANY records in ONE transaction, returning how many landed.
|
|
29713
|
+
*
|
|
29714
|
+
* The write-side twin of {@link deleteWhere}, and it exists for the same
|
|
29715
|
+
* reason: without it, appending a batch is N round trips and N COMMITs on
|
|
29716
|
+
* the single shared connection that also serves every cluster-wide
|
|
29717
|
+
* configuration read. The durable load series writes one process row per
|
|
29718
|
+
* process per sample — 76 rows every 10 s on the live fleet — and the
|
|
29719
|
+
* operator's rule for it is *one transaction per sample, never one per
|
|
29720
|
+
* row*. `insert` cannot express that; nothing else could.
|
|
29721
|
+
*
|
|
29722
|
+
* **All or nothing.** A batch that fails on its fifth row leaves none of
|
|
29723
|
+
* the five behind. A half-written sample is worse than a missing one: the
|
|
29724
|
+
* missing one reads as "nobody reported", which is true, while the half
|
|
29725
|
+
* one reads as "these were the only processes running", which is not.
|
|
29726
|
+
*
|
|
29727
|
+
* `id` is OPTIONAL per record, and that is the difference from
|
|
29728
|
+
* {@link insert}. A collection whose primary key is an `INTEGER` rowid
|
|
29729
|
+
* alias has no id to supply — SQLite assigns it, for free, and inventing a
|
|
29730
|
+
* `randomUUID()` for such a column would write a 36-character string into
|
|
29731
|
+
* an integer key. Omitted on a TEXT key, a uuid is generated exactly as
|
|
29732
|
+
* `insert` does.
|
|
29733
|
+
*/
|
|
29734
|
+
insertMany: method(zod.z.object({
|
|
29735
|
+
namespace: zod.z.string().optional(),
|
|
29736
|
+
collection: zod.z.string(),
|
|
29737
|
+
records: zod.z.array(BulkRecordSchema).readonly()
|
|
29738
|
+
}), zod.z.object({ inserted: zod.z.number().int() }), { kind: "mutation" }),
|
|
29597
29739
|
/** Update an existing record by ID. */
|
|
29598
29740
|
update: method(zod.z.object({
|
|
29599
29741
|
namespace: zod.z.string().optional(),
|
|
@@ -29783,6 +29925,15 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
29783
29925
|
kind: "mutation",
|
|
29784
29926
|
auth: "admin"
|
|
29785
29927
|
}),
|
|
29928
|
+
/** Insert many records in ONE transaction. All or nothing. */
|
|
29929
|
+
insertMany: method(zod.z.object({
|
|
29930
|
+
namespace: zod.z.string().optional(),
|
|
29931
|
+
collection: zod.z.string(),
|
|
29932
|
+
records: zod.z.array(BulkRecordSchema).readonly()
|
|
29933
|
+
}), zod.z.object({ inserted: zod.z.number().int() }), {
|
|
29934
|
+
kind: "mutation",
|
|
29935
|
+
auth: "admin"
|
|
29936
|
+
}),
|
|
29786
29937
|
/** Update an existing record by ID. */
|
|
29787
29938
|
update: method(zod.z.object({
|
|
29788
29939
|
namespace: zod.z.string().optional(),
|
|
@@ -32089,6 +32240,83 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
32089
32240
|
/** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
|
|
32090
32241
|
mount: { kind: "skip" }
|
|
32091
32242
|
};
|
|
32243
|
+
var LoadContributionSchema = zod.z.object({
|
|
32244
|
+
role: zod.z.enum([
|
|
32245
|
+
"decode",
|
|
32246
|
+
"transcode",
|
|
32247
|
+
"recording",
|
|
32248
|
+
"streaming",
|
|
32249
|
+
"detection"
|
|
32250
|
+
]),
|
|
32251
|
+
/**
|
|
32252
|
+
* The NUMERIC device id — the same value every log line carries as
|
|
32253
|
+
* `tags.deviceId`. `null` means this cost genuinely belongs to no single
|
|
32254
|
+
* camera (a shared pool), NOT that the contributor forgot to look it up: a
|
|
32255
|
+
* contributor that cannot name its camera must not emit the entry at all,
|
|
32256
|
+
* because an unnamed per-camera entry is indistinguishable from a shared one
|
|
32257
|
+
* and would quietly turn one camera's cost into everybody's.
|
|
32258
|
+
*/
|
|
32259
|
+
deviceId: zod.z.number().int().positive().nullable(),
|
|
32260
|
+
attribution: zod.z.enum([
|
|
32261
|
+
"measured",
|
|
32262
|
+
"accounted",
|
|
32263
|
+
"unattributable"
|
|
32264
|
+
]),
|
|
32265
|
+
/**
|
|
32266
|
+
* What ONE entry is, in the contributor's own words — `615/high`,
|
|
32267
|
+
* `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
|
|
32268
|
+
* family and inventing a common one would lose the only information that
|
|
32269
|
+
* makes two entries for the same camera distinguishable.
|
|
32270
|
+
*/
|
|
32271
|
+
unit: zod.z.string(),
|
|
32272
|
+
/**
|
|
32273
|
+
* The OS process this cost lives in, when there is one. Present so a
|
|
32274
|
+
* consumer can (a) tell two generations of the same unit apart across a
|
|
32275
|
+
* restart, and (b) subtract claimed processes from the node's process
|
|
32276
|
+
* snapshot to see what NOBODY claimed. Absent for an entry that owns no
|
|
32277
|
+
* process of its own.
|
|
32278
|
+
*/
|
|
32279
|
+
pid: zod.z.number().int().positive().optional(),
|
|
32280
|
+
/**
|
|
32281
|
+
* When this generation started. The pid's incarnation marker: a consumer
|
|
32282
|
+
* differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
|
|
32283
|
+
* window when this changes, because the counter restarted from zero in a new
|
|
32284
|
+
* process.
|
|
32285
|
+
*/
|
|
32286
|
+
startedAtMs: zod.z.number().optional(),
|
|
32287
|
+
/**
|
|
32288
|
+
* CUMULATIVE CPU seconds this unit has consumed since it started — user +
|
|
32289
|
+
* system, read from the child's own `/proc/<pid>/stat` at the moment the
|
|
32290
|
+
* contribution is asked for.
|
|
32291
|
+
*
|
|
32292
|
+
* Cumulative and not a rate on purpose: a rate needs a window, a window
|
|
32293
|
+
* needs a sampler, and a new per-node sampler is the defect half of
|
|
32294
|
+
* `docs/architecture/load-ledger.md` documents. A counter can be differenced
|
|
32295
|
+
* by whoever already keeps a history; a rate cannot be un-averaged.
|
|
32296
|
+
*
|
|
32297
|
+
* Absent — never zero — on a node with no `/proc`, on a read failure, and on
|
|
32298
|
+
* an entry with no process.
|
|
32299
|
+
*/
|
|
32300
|
+
cpuSeconds: zod.z.number().optional(),
|
|
32301
|
+
/** Resident bytes of this unit's process, same source and same rules. */
|
|
32302
|
+
rssBytes: zod.z.number().optional()
|
|
32303
|
+
});
|
|
32304
|
+
var loadContributionCapability = {
|
|
32305
|
+
name: "load-contribution",
|
|
32306
|
+
scope: "system",
|
|
32307
|
+
mode: "collection",
|
|
32308
|
+
internal: true,
|
|
32309
|
+
methods: {
|
|
32310
|
+
/**
|
|
32311
|
+
* This addon's own cost entries, computed live from state it already
|
|
32312
|
+
* holds. Inert: no persistence, no sampling, no timer. It is answered on
|
|
32313
|
+
* whatever beat the caller already has.
|
|
32314
|
+
*/
|
|
32315
|
+
list: method(zod.z.void(), zod.z.array(LoadContributionSchema).readonly())
|
|
32316
|
+
},
|
|
32317
|
+
/** In-process only — enumerated through `addons.listCapabilityProviders`. */
|
|
32318
|
+
mount: { kind: "skip" }
|
|
32319
|
+
};
|
|
32092
32320
|
var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
|
|
32093
32321
|
var RedirectLoginMethodSchema = zod.z.object({
|
|
32094
32322
|
kind: zod.z.literal("redirect"),
|
|
@@ -32260,8 +32488,7 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
32260
32488
|
classification: zod.z.enum([
|
|
32261
32489
|
"root",
|
|
32262
32490
|
"managed",
|
|
32263
|
-
"system"
|
|
32264
|
-
"ghost"
|
|
32491
|
+
"system"
|
|
32265
32492
|
]),
|
|
32266
32493
|
/** `$process` addon binding when `managed`, else null. */
|
|
32267
32494
|
addonId: zod.z.string().nullable(),
|
|
@@ -32269,22 +32496,39 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
32269
32496
|
nodeId: zod.z.string().nullable(),
|
|
32270
32497
|
/** Truncated command line. */
|
|
32271
32498
|
command: zod.z.string(),
|
|
32499
|
+
/**
|
|
32500
|
+
* `ps pcpu` — CPU averaged over the process's WHOLE LIFETIME, not a rate.
|
|
32501
|
+
* On a runner up for days it barely moves. Fine as a column, useless as a
|
|
32502
|
+
* series: use `cpuMainPercent + cpuGcPercent` for anything time-varying.
|
|
32503
|
+
*/
|
|
32272
32504
|
cpuPercent: zod.z.number(),
|
|
32273
32505
|
memoryRssBytes: zod.z.number(),
|
|
32506
|
+
/**
|
|
32507
|
+
* Instantaneous CPU% of the process's own threads over the last
|
|
32508
|
+
* process-snapshot window, from a `/proc/<pid>/task/*` tick delta.
|
|
32509
|
+
*
|
|
32510
|
+
* `null` = UNKNOWN, never zero: no previous sample yet (first tick after
|
|
32511
|
+
* boot), the pid was recycled, or this node is not Linux.
|
|
32512
|
+
*/
|
|
32513
|
+
cpuMainPercent: zod.z.number().nullable(),
|
|
32514
|
+
/**
|
|
32515
|
+
* Instantaneous CPU% of V8's `V8Worker` platform pool over the same window.
|
|
32516
|
+
*
|
|
32517
|
+
* This is the number that rewrote the 2026-08-27 diagnosis — hub-main 73%,
|
|
32518
|
+
* `stream-broker` 61% (`docs/architecture/load-ledger.md`). A CPU chart that
|
|
32519
|
+
* does not separate it from `cpuMainPercent` shows "busy" where the truth is
|
|
32520
|
+
* "allocating too much".
|
|
32521
|
+
*
|
|
32522
|
+
* Concurrent GC is the dominant tenant of that pool but not the only one
|
|
32523
|
+
* (background compilation runs there too), so it is reported as
|
|
32524
|
+
* "GC / V8 helpers" rather than as pure collection time. `null` has the same
|
|
32525
|
+
* meaning as on `cpuMainPercent`.
|
|
32526
|
+
*/
|
|
32527
|
+
cpuGcPercent: zod.z.number().nullable(),
|
|
32528
|
+
/** Threads seen in the tick scan. `null` under the same conditions. */
|
|
32529
|
+
threadCount: zod.z.number().nullable(),
|
|
32274
32530
|
/** Wall-clock uptime (seconds). Parsed from `ps etime`. */
|
|
32275
|
-
uptimeSec: zod.z.number()
|
|
32276
|
-
/** True when ancestor walk reaches `ppid=1` (reparented to init/launchd). */
|
|
32277
|
-
orphaned: zod.z.boolean()
|
|
32278
|
-
});
|
|
32279
|
-
var KillProcessInputSchema = zod.z.object({
|
|
32280
|
-
pid: zod.z.number(),
|
|
32281
|
-
/** Force = SIGKILL. Default is SIGTERM. */
|
|
32282
|
-
force: zod.z.boolean().optional()
|
|
32283
|
-
});
|
|
32284
|
-
var KillProcessResultSchema = zod.z.object({
|
|
32285
|
-
success: zod.z.boolean(),
|
|
32286
|
-
reason: zod.z.string().optional(),
|
|
32287
|
-
signal: zod.z.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
32531
|
+
uptimeSec: zod.z.number()
|
|
32288
32532
|
});
|
|
32289
32533
|
var DumpHeapSnapshotInputSchema = zod.z.object({
|
|
32290
32534
|
/** The addon whose runner should dump a heap snapshot. */
|
|
@@ -32298,6 +32542,89 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
32298
32542
|
pid: zod.z.number().optional(),
|
|
32299
32543
|
reason: zod.z.string().optional()
|
|
32300
32544
|
});
|
|
32545
|
+
var LoadPointSchema = zod.z.object({
|
|
32546
|
+
/** Bucket START, or the snapshot's own timestamp when unreduced. */
|
|
32547
|
+
atMs: zod.z.number(),
|
|
32548
|
+
/** Raw snapshots in this bucket. Never 0 — AN EMPTY BUCKET IS ABSENT. */
|
|
32549
|
+
samples: zod.z.number().int(),
|
|
32550
|
+
/**
|
|
32551
|
+
* `null` = UNKNOWN and it PROPAGATES: a bucket is null unless every process
|
|
32552
|
+
* of every snapshot in it reported a thread split. A partial sum is a
|
|
32553
|
+
* smaller number that looks exactly as real as a complete one.
|
|
32554
|
+
*/
|
|
32555
|
+
cpuMainPercent: zod.z.number().nullable(),
|
|
32556
|
+
cpuMainPercentMin: zod.z.number().nullable(),
|
|
32557
|
+
cpuGcPercent: zod.z.number().nullable(),
|
|
32558
|
+
cpuGcPercentMin: zod.z.number().nullable(),
|
|
32559
|
+
/** Lifetime-average CPU%, summed. Always known — and never a rate. */
|
|
32560
|
+
cpuLifetimePercent: zod.z.number(),
|
|
32561
|
+
cpuLifetimePercentMin: zod.z.number(),
|
|
32562
|
+
memoryRssBytes: zod.z.number(),
|
|
32563
|
+
memoryRssBytesMin: zod.z.number(),
|
|
32564
|
+
processCount: zod.z.number().int(),
|
|
32565
|
+
processCountMin: zod.z.number().int()
|
|
32566
|
+
});
|
|
32567
|
+
var LoadFunctionSeriesSchema = zod.z.object({
|
|
32568
|
+
key: zod.z.string(),
|
|
32569
|
+
kind: zod.z.enum([
|
|
32570
|
+
"addon",
|
|
32571
|
+
"root",
|
|
32572
|
+
"unattributed"
|
|
32573
|
+
]),
|
|
32574
|
+
/** Oldest-first. A missing interval is MISSING — never zero-filled. */
|
|
32575
|
+
points: zod.z.array(LoadPointSchema).readonly()
|
|
32576
|
+
});
|
|
32577
|
+
var NodeLoadSeriesSchema = zod.z.object({
|
|
32578
|
+
nodeId: zod.z.string(),
|
|
32579
|
+
/** One entry per function seen in the window, heaviest-first. */
|
|
32580
|
+
series: zod.z.array(LoadFunctionSeriesSchema).readonly(),
|
|
32581
|
+
/**
|
|
32582
|
+
* Width of one returned bucket, in ms. Equals the sampling cadence when no
|
|
32583
|
+
* reduction was needed — so a caller can always say what one point covers
|
|
32584
|
+
* without having to know whether it was reduced.
|
|
32585
|
+
*/
|
|
32586
|
+
bucketMs: zod.z.number(),
|
|
32587
|
+
/** Raw snapshots that went into this answer, across both tiers. */
|
|
32588
|
+
retainedSamples: zod.z.number(),
|
|
32589
|
+
/** Oldest snapshot represented, or `null` when nothing is retained. */
|
|
32590
|
+
oldestAtMs: zod.z.number().nullable(),
|
|
32591
|
+
/** The fixed sampling cadence in force on the cluster, in ms. */
|
|
32592
|
+
cadenceMs: zod.z.number(),
|
|
32593
|
+
/**
|
|
32594
|
+
* Did the DURABLE tier contribute? `false` means the answer is the hot ring
|
|
32595
|
+
* alone — an agent (which holds no table), or a store that refused.
|
|
32596
|
+
* Reported because "the last hour" and "the last six hours" are different
|
|
32597
|
+
* questions and an operator must not have to guess which was answered.
|
|
32598
|
+
*/
|
|
32599
|
+
durable: zod.z.boolean()
|
|
32600
|
+
});
|
|
32601
|
+
var GetLoadSeriesInputSchema = zod.z.object({
|
|
32602
|
+
/**
|
|
32603
|
+
* The node whose series is wanted.
|
|
32604
|
+
*
|
|
32605
|
+
* NOT named `nodeId`: the generated cap router strips a top-level
|
|
32606
|
+
* `nodeId` from every method input and uses it to ROUTE the call to
|
|
32607
|
+
* that node's provider (`generated-cap-routers.ts`). A series target
|
|
32608
|
+
* called `nodeId` would silently become a routing pin and never reach
|
|
32609
|
+
* the provider. The hub holds every node it hears from, so the
|
|
32610
|
+
* ordinary call is unpinned — answered by the hub, for any node.
|
|
32611
|
+
*/
|
|
32612
|
+
forNodeId: zod.z.string(),
|
|
32613
|
+
/**
|
|
32614
|
+
* EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
|
|
32615
|
+
* holds and receives only what it is missing, so seeding a live chart
|
|
32616
|
+
* from this method cannot double a point already drawn.
|
|
32617
|
+
*/
|
|
32618
|
+
sinceMs: zod.z.number().optional(),
|
|
32619
|
+
/**
|
|
32620
|
+
* Most points the caller wants PER FUNCTION. The window is reduced to fit,
|
|
32621
|
+
* preserving min and max per bucket.
|
|
32622
|
+
*
|
|
32623
|
+
* Absent means NO reduction — legitimate for a short window and a trap for a
|
|
32624
|
+
* long one, which is why a chart passes its own pixel width.
|
|
32625
|
+
*/
|
|
32626
|
+
maxPoints: zod.z.number().int().positive().optional()
|
|
32627
|
+
});
|
|
32301
32628
|
var SystemMetricsSchema = zod.z.object({
|
|
32302
32629
|
cpuPercent: zod.z.number(),
|
|
32303
32630
|
memoryPercent: zod.z.number(),
|
|
@@ -32343,28 +32670,44 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
32343
32670
|
getAddonStats: method(zod.z.object({ addonId: zod.z.string() }), PidResourceStatsSchema.nullable()),
|
|
32344
32671
|
/**
|
|
32345
32672
|
* Snapshot of every camstack-related process on this node with a
|
|
32346
|
-
*
|
|
32347
|
-
* Processes tab: cross-references `$process.list` against a `ps` scan
|
|
32348
|
-
*
|
|
32349
|
-
*
|
|
32673
|
+
* root/managed/system classification. Powers the Cluster → Agent →
|
|
32674
|
+
* Processes tab: cross-references `$process.list` against a `ps` scan so
|
|
32675
|
+
* per-addon CPU and RSS can be attributed, and so a process the cluster
|
|
32676
|
+
* does not manage is still visible.
|
|
32677
|
+
*
|
|
32678
|
+
* **Read-only, by design.** This cap once carried a `killProcess`
|
|
32679
|
+
* mutation; it was deleted on 2026-08-27. A runner's lifecycle belongs to
|
|
32680
|
+
* `CrashSupervisor` and is driven through `addons.restartAddon` /
|
|
32681
|
+
* `$process.restart` — signalling a raw pid went around the supervisor
|
|
32682
|
+
* (D6), and the one class it was willing to signal turned out to be the
|
|
32683
|
+
* container's own init and the operator's desktop app.
|
|
32350
32684
|
*/
|
|
32351
32685
|
listNodeProcesses: method(zod.z.void(), zod.z.array(NodeProcessSchema).readonly()),
|
|
32352
32686
|
/**
|
|
32353
|
-
*
|
|
32354
|
-
*
|
|
32355
|
-
*
|
|
32356
|
-
*
|
|
32687
|
+
* The retained per-node load series — the ONE reader over BOTH tiers.
|
|
32688
|
+
*
|
|
32689
|
+
* The in-memory ring is the HOT window (the last 180 snapshots, held by
|
|
32690
|
+
* every node's `native-metrics`); the hub's `metrics:node-load-samples`
|
|
32691
|
+
* table is the COLD one (the operator's retention, six hours by default).
|
|
32692
|
+
* This method merges them and DEDUPES on `atMs`, so a snapshot present in
|
|
32693
|
+
* both contributes once and the caller never learns which tier a point
|
|
32694
|
+
* came from. There is deliberately no second read surface: two readers is
|
|
32695
|
+
* how two charts start disagreeing about the same node.
|
|
32696
|
+
*
|
|
32697
|
+
* Reads only; nothing is sampled to answer it. Normally called UNPINNED —
|
|
32698
|
+
* the hub hears every node's snapshot and holds every node's rows — and
|
|
32699
|
+
* answers for any `forNodeId`. Pinned to an agent it answers from that
|
|
32700
|
+
* agent's ring alone (`durable: false`). Empty is a legitimate answer: a
|
|
32701
|
+
* node nobody has heard from has no series, and saying so is the truth.
|
|
32357
32702
|
*/
|
|
32358
|
-
|
|
32359
|
-
kind: "mutation",
|
|
32360
|
-
auth: "admin"
|
|
32361
|
-
}),
|
|
32703
|
+
getLoadSeries: method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema),
|
|
32362
32704
|
/**
|
|
32363
32705
|
* Tell the addon's forked runner to write a V8 heap snapshot to disk (via
|
|
32364
32706
|
* SIGUSR2 — the runner's diagnostic handler). Also logs its
|
|
32365
|
-
* `process.memoryUsage()` + heap-space breakdown.
|
|
32366
|
-
*
|
|
32367
|
-
* attribution; copy the returned path off the
|
|
32707
|
+
* `process.memoryUsage()` + heap-space breakdown. Resolves the pid from
|
|
32708
|
+
* `$process.list`, so it can only reach a runner this node spawned. Use
|
|
32709
|
+
* for deep per-addon memory attribution; copy the returned path off the
|
|
32710
|
+
* node to analyze.
|
|
32368
32711
|
*/
|
|
32369
32712
|
dumpHeapSnapshot: method(DumpHeapSnapshotInputSchema, DumpHeapSnapshotResultSchema, {
|
|
32370
32713
|
kind: "mutation",
|
|
@@ -47465,6 +47808,7 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
47465
47808
|
*/
|
|
47466
47809
|
channels: zod.z.array(LogChannelWindowPatchSchema).readonly().optional()
|
|
47467
47810
|
});
|
|
47811
|
+
var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: zod.z.string() });
|
|
47468
47812
|
var GetLoggingSettingsInputSchema = zod.z.object({
|
|
47469
47813
|
scopeNodeId: zod.z.string().optional(),
|
|
47470
47814
|
/**
|
|
@@ -47558,6 +47902,22 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
47558
47902
|
*/
|
|
47559
47903
|
getRequestCensus: method(zod.z.void(), RequestCensusStatusSchema, { auth: "admin" }),
|
|
47560
47904
|
/**
|
|
47905
|
+
* Every `load-contribution` an addon on this cluster reports — each
|
|
47906
|
+
* addon's OWN cost, already attributed by the addon that owns it.
|
|
47907
|
+
*
|
|
47908
|
+
* There is no central list of what costs what: an addon that spawns a
|
|
47909
|
+
* per-camera child declares it, and one that cannot attribute its cost
|
|
47910
|
+
* (the shared inference pool) declares THAT. So a new cost family appears
|
|
47911
|
+
* here the moment its addon is redeployed, with nobody editing anything.
|
|
47912
|
+
*
|
|
47913
|
+
* What this does NOT do is measure the node. `metrics.node-processes-
|
|
47914
|
+
* snapshot` still does that, and the difference between the two is the
|
|
47915
|
+
* finding: a process no contribution claims is either a leak or a family
|
|
47916
|
+
* nobody has taught to report. Both belong in the unattributed bucket, and
|
|
47917
|
+
* neither may be folded into a camera.
|
|
47918
|
+
*/
|
|
47919
|
+
getLoadContributions: method(zod.z.void(), zod.z.array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }),
|
|
47920
|
+
/**
|
|
47561
47921
|
* The logging settings document — levels and armed diagnostics — resolved
|
|
47562
47922
|
* for `nodeId`, or for the cluster when `nodeId` is absent.
|
|
47563
47923
|
*
|
|
@@ -48542,7 +48902,7 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
48542
48902
|
"ffmpeg.binaryPath": "ffmpeg",
|
|
48543
48903
|
"ffmpeg.hwAccel": "auto",
|
|
48544
48904
|
"ffmpeg.threadCount": 0,
|
|
48545
|
-
"auth.tokenExpiry": "
|
|
48905
|
+
"auth.tokenExpiry": "30d"
|
|
48546
48906
|
};
|
|
48547
48907
|
var AccessoryKind = {
|
|
48548
48908
|
Siren: DeviceRole.Siren,
|
|
@@ -48976,6 +49336,7 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
48976
49336
|
lawnMowerControlCapability,
|
|
48977
49337
|
llmCapability,
|
|
48978
49338
|
llmRuntimeCapability,
|
|
49339
|
+
loadContributionCapability,
|
|
48979
49340
|
localNetworkCapability,
|
|
48980
49341
|
lockControlCapability,
|
|
48981
49342
|
logChannelsCapability,
|
|
@@ -49999,6 +50360,12 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
49999
50360
|
addonId: null,
|
|
50000
50361
|
access: "create"
|
|
50001
50362
|
},
|
|
50363
|
+
"dataStoreProvider.insertMany": {
|
|
50364
|
+
capName: "data-store-provider",
|
|
50365
|
+
capScope: "system",
|
|
50366
|
+
addonId: null,
|
|
50367
|
+
access: "create"
|
|
50368
|
+
},
|
|
50002
50369
|
"dataStoreProvider.isEmpty": {
|
|
50003
50370
|
capName: "data-store-provider",
|
|
50004
50371
|
capScope: "system",
|
|
@@ -51313,6 +51680,12 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
51313
51680
|
addonId: null,
|
|
51314
51681
|
access: "create"
|
|
51315
51682
|
},
|
|
51683
|
+
"loadContribution.list": {
|
|
51684
|
+
capName: "load-contribution",
|
|
51685
|
+
capScope: "system",
|
|
51686
|
+
addonId: null,
|
|
51687
|
+
access: "view"
|
|
51688
|
+
},
|
|
51316
51689
|
"localNetwork.downloadCa": {
|
|
51317
51690
|
capName: "local-network",
|
|
51318
51691
|
capScope: "system",
|
|
@@ -51613,17 +51986,17 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
51613
51986
|
addonId: null,
|
|
51614
51987
|
access: "view"
|
|
51615
51988
|
},
|
|
51616
|
-
"metricsProvider.
|
|
51989
|
+
"metricsProvider.getLoadSeries": {
|
|
51617
51990
|
capName: "metrics-provider",
|
|
51618
51991
|
capScope: "system",
|
|
51619
51992
|
addonId: null,
|
|
51620
51993
|
access: "view"
|
|
51621
51994
|
},
|
|
51622
|
-
"metricsProvider.
|
|
51995
|
+
"metricsProvider.getProcessStats": {
|
|
51623
51996
|
capName: "metrics-provider",
|
|
51624
51997
|
capScope: "system",
|
|
51625
51998
|
addonId: null,
|
|
51626
|
-
access: "
|
|
51999
|
+
access: "view"
|
|
51627
52000
|
},
|
|
51628
52001
|
"metricsProvider.listAddonInstances": {
|
|
51629
52002
|
capName: "metrics-provider",
|
|
@@ -53635,6 +54008,12 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
53635
54008
|
addonId: null,
|
|
53636
54009
|
access: "create"
|
|
53637
54010
|
},
|
|
54011
|
+
"settingsStore.insertMany": {
|
|
54012
|
+
capName: "settings-store",
|
|
54013
|
+
capScope: "system",
|
|
54014
|
+
addonId: null,
|
|
54015
|
+
access: "create"
|
|
54016
|
+
},
|
|
53638
54017
|
"settingsStore.isEmpty": {
|
|
53639
54018
|
capName: "settings-store",
|
|
53640
54019
|
capScope: "system",
|
|
@@ -54247,6 +54626,12 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
54247
54626
|
addonId: null,
|
|
54248
54627
|
access: "create"
|
|
54249
54628
|
},
|
|
54629
|
+
"system.getLoadContributions": {
|
|
54630
|
+
capName: "system",
|
|
54631
|
+
capScope: "system",
|
|
54632
|
+
addonId: null,
|
|
54633
|
+
access: "view"
|
|
54634
|
+
},
|
|
54250
54635
|
"system.getLoggingSettings": {
|
|
54251
54636
|
capName: "system",
|
|
54252
54637
|
capScope: "system",
|
|
@@ -57734,6 +58119,12 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
57734
58119
|
return filesystemBrowseCapability;
|
|
57735
58120
|
}
|
|
57736
58121
|
});
|
|
58122
|
+
Object.defineProperty(exports, "foldSnapshotByFunction", {
|
|
58123
|
+
enumerable: true,
|
|
58124
|
+
get: function() {
|
|
58125
|
+
return foldSnapshotByFunction;
|
|
58126
|
+
}
|
|
58127
|
+
});
|
|
57737
58128
|
Object.defineProperty(exports, "hydrateSchema", {
|
|
57738
58129
|
enumerable: true,
|
|
57739
58130
|
get: function() {
|
|
@@ -57878,6 +58269,18 @@ var require_dist_BVU5JADq = __commonJS({
|
|
|
57878
58269
|
return readinessKey;
|
|
57879
58270
|
}
|
|
57880
58271
|
});
|
|
58272
|
+
Object.defineProperty(exports, "reducePoints", {
|
|
58273
|
+
enumerable: true,
|
|
58274
|
+
get: function() {
|
|
58275
|
+
return reducePoints;
|
|
58276
|
+
}
|
|
58277
|
+
});
|
|
58278
|
+
Object.defineProperty(exports, "resolveBucketMs", {
|
|
58279
|
+
enumerable: true,
|
|
58280
|
+
get: function() {
|
|
58281
|
+
return resolveBucketMs;
|
|
58282
|
+
}
|
|
58283
|
+
});
|
|
57881
58284
|
Object.defineProperty(exports, "resolveCapMount", {
|
|
57882
58285
|
enumerable: true,
|
|
57883
58286
|
get: function() {
|
|
@@ -57992,7 +58395,7 @@ var require_alerts_addon = __commonJS({
|
|
|
57992
58395
|
[Symbol.toStringTag]: { value: "Module" }
|
|
57993
58396
|
});
|
|
57994
58397
|
require_chunk_Cek0wNdY();
|
|
57995
|
-
var require_dist10 =
|
|
58398
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
57996
58399
|
function selectExpired(alerts, cutoffMs) {
|
|
57997
58400
|
return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
|
|
57998
58401
|
}
|
|
@@ -58811,7 +59214,7 @@ var require_console_logging = __commonJS({
|
|
|
58811
59214
|
[Symbol.toStringTag]: { value: "Module" }
|
|
58812
59215
|
});
|
|
58813
59216
|
require_chunk_Cek0wNdY();
|
|
58814
|
-
var require_dist10 =
|
|
59217
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
58815
59218
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
58816
59219
|
var LEVEL_RANK = {
|
|
58817
59220
|
debug: 0,
|
|
@@ -58905,7 +59308,7 @@ var require_core_blocks_addon = __commonJS({
|
|
|
58905
59308
|
"use strict";
|
|
58906
59309
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
58907
59310
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
58908
|
-
var require_dist10 =
|
|
59311
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
58909
59312
|
var node_crypto = __require("crypto");
|
|
58910
59313
|
var node_fs_promises = __require("fs/promises");
|
|
58911
59314
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -59802,11 +60205,11 @@ var require_core_blocks = __commonJS({
|
|
|
59802
60205
|
}
|
|
59803
60206
|
});
|
|
59804
60207
|
|
|
59805
|
-
// ../system/dist/retired-settings-keys-
|
|
59806
|
-
var
|
|
59807
|
-
"../system/dist/retired-settings-keys-
|
|
60208
|
+
// ../system/dist/retired-settings-keys-Davtjo5p.js
|
|
60209
|
+
var require_retired_settings_keys_Davtjo5p = __commonJS({
|
|
60210
|
+
"../system/dist/retired-settings-keys-Davtjo5p.js"(exports) {
|
|
59808
60211
|
"use strict";
|
|
59809
|
-
var require_dist10 =
|
|
60212
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
59810
60213
|
function settingsStoreIsAuthoritativeHere(env) {
|
|
59811
60214
|
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
59812
60215
|
return raw === "" || raw === "hub";
|
|
@@ -60638,6 +61041,7 @@ var require_node = __commonJS({
|
|
|
60638
61041
|
var node_stream_promises = __require("stream/promises");
|
|
60639
61042
|
var node_stream = __require("stream");
|
|
60640
61043
|
var node_child_process = __require("child_process");
|
|
61044
|
+
var node_fs_promises = __require("fs/promises");
|
|
60641
61045
|
function findFileByName(dir, name) {
|
|
60642
61046
|
try {
|
|
60643
61047
|
for (const entry of (0, node_fs.readdirSync)(dir, { withFileTypes: true })) {
|
|
@@ -60998,6 +61402,110 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
60998
61402
|
hash
|
|
60999
61403
|
} });
|
|
61000
61404
|
}
|
|
61405
|
+
var CLOCK_TICKS_PER_SEC = 100;
|
|
61406
|
+
var PAGE_BYTES = 4096;
|
|
61407
|
+
var NO_COST_CLAIM = { release: () => void 0 };
|
|
61408
|
+
var nodeProcStatReader = {
|
|
61409
|
+
readStat: (pid) => (0, node_fs_promises.readFile)(`/proc/${pid}/stat`, "utf8"),
|
|
61410
|
+
readStatm: (pid) => (0, node_fs_promises.readFile)(`/proc/${pid}/statm`, "utf8")
|
|
61411
|
+
};
|
|
61412
|
+
function parseProcCpuSeconds(line) {
|
|
61413
|
+
const close = line.lastIndexOf(")");
|
|
61414
|
+
if (close < 0) return null;
|
|
61415
|
+
const rest = line.slice(close + 1).trim().split(/\s+/);
|
|
61416
|
+
const utime = Number(rest[11]);
|
|
61417
|
+
const stime = Number(rest[12]);
|
|
61418
|
+
if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;
|
|
61419
|
+
return (utime + stime) / CLOCK_TICKS_PER_SEC;
|
|
61420
|
+
}
|
|
61421
|
+
function parseProcRssBytes(line) {
|
|
61422
|
+
const fields = line.trim().split(/\s+/);
|
|
61423
|
+
const residentPages = Number(fields[1]);
|
|
61424
|
+
if (!Number.isFinite(residentPages)) return null;
|
|
61425
|
+
return residentPages * PAGE_BYTES;
|
|
61426
|
+
}
|
|
61427
|
+
async function readProcessCost(pid, reader = nodeProcStatReader) {
|
|
61428
|
+
let cpuSeconds = null;
|
|
61429
|
+
let rssBytes = null;
|
|
61430
|
+
try {
|
|
61431
|
+
cpuSeconds = parseProcCpuSeconds(await reader.readStat(pid));
|
|
61432
|
+
} catch {
|
|
61433
|
+
cpuSeconds = null;
|
|
61434
|
+
}
|
|
61435
|
+
try {
|
|
61436
|
+
rssBytes = parseProcRssBytes(await reader.readStatm(pid));
|
|
61437
|
+
} catch {
|
|
61438
|
+
rssBytes = null;
|
|
61439
|
+
}
|
|
61440
|
+
return {
|
|
61441
|
+
...cpuSeconds === null ? {} : { cpuSeconds },
|
|
61442
|
+
...rssBytes === null ? {} : { rssBytes }
|
|
61443
|
+
};
|
|
61444
|
+
}
|
|
61445
|
+
var ChildCostRegistry = class {
|
|
61446
|
+
reader;
|
|
61447
|
+
now;
|
|
61448
|
+
claims = /* @__PURE__ */ new Map();
|
|
61449
|
+
constructor(reader = nodeProcStatReader, now = Date.now) {
|
|
61450
|
+
this.reader = reader;
|
|
61451
|
+
this.now = now;
|
|
61452
|
+
}
|
|
61453
|
+
/**
|
|
61454
|
+
* Record one child. The returned handle is the only way to remove it.
|
|
61455
|
+
*
|
|
61456
|
+
* A claim with no pid is dropped rather than stored: it could carry no
|
|
61457
|
+
* measurement, and an entry with a camera and no numbers reads on a chart as
|
|
61458
|
+
* a camera that cost nothing.
|
|
61459
|
+
*/
|
|
61460
|
+
claim(input) {
|
|
61461
|
+
const pid = input.pid;
|
|
61462
|
+
if (pid === void 0 || !Number.isInteger(pid) || pid <= 0) return NO_COST_CLAIM;
|
|
61463
|
+
const key = /* @__PURE__ */ Symbol("child-cost-claim");
|
|
61464
|
+
this.claims.set(key, {
|
|
61465
|
+
role: input.role,
|
|
61466
|
+
deviceId: input.deviceId,
|
|
61467
|
+
unit: input.unit,
|
|
61468
|
+
attribution: input.attribution ?? "measured",
|
|
61469
|
+
pid,
|
|
61470
|
+
startedAtMs: this.now()
|
|
61471
|
+
});
|
|
61472
|
+
return { release: () => {
|
|
61473
|
+
this.claims.delete(key);
|
|
61474
|
+
} };
|
|
61475
|
+
}
|
|
61476
|
+
/** Live claims, in claim order. Diagnostics and tests; not a contribution. */
|
|
61477
|
+
list() {
|
|
61478
|
+
return [...this.claims.values()].map((c) => ({
|
|
61479
|
+
role: c.role,
|
|
61480
|
+
deviceId: c.deviceId,
|
|
61481
|
+
unit: c.unit,
|
|
61482
|
+
attribution: c.attribution,
|
|
61483
|
+
pid: c.pid
|
|
61484
|
+
}));
|
|
61485
|
+
}
|
|
61486
|
+
/**
|
|
61487
|
+
* This addon's contribution: one entry per live claim, with whatever the OS
|
|
61488
|
+
* will tell us about that child right now.
|
|
61489
|
+
*
|
|
61490
|
+
* A child that has exited between the claim and this read contributes an
|
|
61491
|
+
* entry with no numbers rather than no entry — the unit exists, the addon
|
|
61492
|
+
* believes it is running, and hiding it would make a dying writer look like
|
|
61493
|
+
* a writer that was never started.
|
|
61494
|
+
*/
|
|
61495
|
+
async contributions() {
|
|
61496
|
+
const out = [];
|
|
61497
|
+
for (const claim of this.claims.values()) out.push({
|
|
61498
|
+
role: claim.role,
|
|
61499
|
+
deviceId: claim.deviceId,
|
|
61500
|
+
attribution: claim.attribution,
|
|
61501
|
+
unit: claim.unit,
|
|
61502
|
+
pid: claim.pid,
|
|
61503
|
+
startedAtMs: claim.startedAtMs,
|
|
61504
|
+
...await readProcessCost(claim.pid, this.reader)
|
|
61505
|
+
});
|
|
61506
|
+
return out;
|
|
61507
|
+
}
|
|
61508
|
+
};
|
|
61001
61509
|
var STORAGE_LOCATION_TYPES = [
|
|
61002
61510
|
"data",
|
|
61003
61511
|
"media",
|
|
@@ -61815,10 +62323,12 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
61815
62323
|
}
|
|
61816
62324
|
}
|
|
61817
62325
|
};
|
|
62326
|
+
exports.ChildCostRegistry = ChildCostRegistry;
|
|
61818
62327
|
exports.FfmpegProcess = FfmpegProcess;
|
|
61819
62328
|
exports.FilesystemStorageProvider = FilesystemStorageProvider;
|
|
61820
62329
|
exports.Fmp4FragmentChild = Fmp4FragmentChild;
|
|
61821
62330
|
exports.Fmp4FragmentPlane = Fmp4FragmentPlane;
|
|
62331
|
+
exports.NO_COST_CLAIM = NO_COST_CLAIM;
|
|
61822
62332
|
exports.PYTHON_VERSION = PYTHON_VERSION;
|
|
61823
62333
|
exports.buildBinaryPath = buildBinaryPath;
|
|
61824
62334
|
exports.canonicalDeviceFingerprint = canonicalDeviceFingerprint;
|
|
@@ -61834,6 +62344,10 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
61834
62344
|
exports.getPythonDownloadUrl = getPythonDownloadUrl;
|
|
61835
62345
|
exports.installPythonPackages = installPythonPackages;
|
|
61836
62346
|
exports.installPythonRequirements = installPythonRequirements;
|
|
62347
|
+
exports.nodeProcStatReader = nodeProcStatReader;
|
|
62348
|
+
exports.parseProcCpuSeconds = parseProcCpuSeconds;
|
|
62349
|
+
exports.parseProcRssBytes = parseProcRssBytes;
|
|
62350
|
+
exports.readProcessCost = readProcessCost;
|
|
61837
62351
|
exports.resolveExportFingerprint = resolveExportFingerprint;
|
|
61838
62352
|
exports.signExpiringUrl = signExpiringUrl;
|
|
61839
62353
|
exports.verifyExpiringUrl = verifyExpiringUrl;
|
|
@@ -61849,8 +62363,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
61849
62363
|
[Symbol.toStringTag]: { value: "Module" }
|
|
61850
62364
|
});
|
|
61851
62365
|
require_chunk_Cek0wNdY();
|
|
61852
|
-
var require_dist10 =
|
|
61853
|
-
var require_retired_settings_keys =
|
|
62366
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
62367
|
+
var require_retired_settings_keys = require_retired_settings_keys_Davtjo5p();
|
|
61854
62368
|
var node_crypto = __require("crypto");
|
|
61855
62369
|
var _camstack_types_node = require_node();
|
|
61856
62370
|
var JOB_HISTORY = 20;
|
|
@@ -66599,7 +67113,7 @@ var require_hub_forwarder = __commonJS({
|
|
|
66599
67113
|
[Symbol.toStringTag]: { value: "Module" }
|
|
66600
67114
|
});
|
|
66601
67115
|
require_chunk_Cek0wNdY();
|
|
66602
|
-
var require_dist10 =
|
|
67116
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
66603
67117
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
66604
67118
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
66605
67119
|
var HubForwarderDestination = class {
|
|
@@ -66736,7 +67250,7 @@ var require_liveness_monitor_addon = __commonJS({
|
|
|
66736
67250
|
"use strict";
|
|
66737
67251
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
66738
67252
|
require_chunk_Cek0wNdY();
|
|
66739
|
-
var require_dist10 =
|
|
67253
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
66740
67254
|
var NO_DEVICES = "liveness:no-devices";
|
|
66741
67255
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
66742
67256
|
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
@@ -66926,7 +67440,7 @@ var require_local_auth_addon = __commonJS({
|
|
|
66926
67440
|
[Symbol.toStringTag]: { value: "Module" }
|
|
66927
67441
|
});
|
|
66928
67442
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
66929
|
-
var require_dist10 =
|
|
67443
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
66930
67444
|
var node_crypto = __require("crypto");
|
|
66931
67445
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
66932
67446
|
var crypto$1 = __require("crypto");
|
|
@@ -71440,6 +71954,99 @@ var require_local_auth_addon = __commonJS({
|
|
|
71440
71954
|
};
|
|
71441
71955
|
}));
|
|
71442
71956
|
var import_jsonwebtoken = /* @__PURE__ */ require_chunk.__toESM(require_jsonwebtoken());
|
|
71957
|
+
var DURATION_PATTERN = /^(\d+(?:\.\d+)?)\s*(ms|milliseconds?|msecs?|s|secs?|seconds?|m|mins?|minutes?|h|hrs?|hours?|d|days?|w|weeks?|y|yrs?|years?)$/i;
|
|
71958
|
+
var UNIT_MS = {
|
|
71959
|
+
ms: 1,
|
|
71960
|
+
msec: 1,
|
|
71961
|
+
msecs: 1,
|
|
71962
|
+
millisecond: 1,
|
|
71963
|
+
milliseconds: 1,
|
|
71964
|
+
s: 1e3,
|
|
71965
|
+
sec: 1e3,
|
|
71966
|
+
secs: 1e3,
|
|
71967
|
+
second: 1e3,
|
|
71968
|
+
seconds: 1e3,
|
|
71969
|
+
m: 6e4,
|
|
71970
|
+
min: 6e4,
|
|
71971
|
+
mins: 6e4,
|
|
71972
|
+
minute: 6e4,
|
|
71973
|
+
minutes: 6e4,
|
|
71974
|
+
h: 36e5,
|
|
71975
|
+
hr: 36e5,
|
|
71976
|
+
hrs: 36e5,
|
|
71977
|
+
hour: 36e5,
|
|
71978
|
+
hours: 36e5,
|
|
71979
|
+
d: 864e5,
|
|
71980
|
+
day: 864e5,
|
|
71981
|
+
days: 864e5,
|
|
71982
|
+
w: 6048e5,
|
|
71983
|
+
week: 6048e5,
|
|
71984
|
+
weeks: 6048e5,
|
|
71985
|
+
y: 315576e5,
|
|
71986
|
+
yr: 315576e5,
|
|
71987
|
+
yrs: 315576e5,
|
|
71988
|
+
year: 315576e5,
|
|
71989
|
+
years: 315576e5
|
|
71990
|
+
};
|
|
71991
|
+
var MIN_EXPIRY_MS = 6e4;
|
|
71992
|
+
var MAX_EXPIRY_MS = 365 * 864e5;
|
|
71993
|
+
function durationMs(candidate) {
|
|
71994
|
+
const match = DURATION_PATTERN.exec(candidate);
|
|
71995
|
+
if (match === null) return null;
|
|
71996
|
+
const amount = Number(match[1]);
|
|
71997
|
+
const unitMs = UNIT_MS[match[2].toLowerCase()];
|
|
71998
|
+
if (!Number.isFinite(amount) || unitMs === void 0) return null;
|
|
71999
|
+
return amount * unitMs;
|
|
72000
|
+
}
|
|
72001
|
+
var DEFAULT_SECONDS = (() => {
|
|
72002
|
+
const ms = durationMs("30d");
|
|
72003
|
+
if (ms === null) throw new Error(`DEFAULT_TOKEN_EXPIRY is not a duration: 30d`);
|
|
72004
|
+
return ms / 1e3;
|
|
72005
|
+
})();
|
|
72006
|
+
function refuse(reason) {
|
|
72007
|
+
return {
|
|
72008
|
+
ok: false,
|
|
72009
|
+
reason,
|
|
72010
|
+
seconds: DEFAULT_SECONDS,
|
|
72011
|
+
value: "30d"
|
|
72012
|
+
};
|
|
72013
|
+
}
|
|
72014
|
+
function parseTokenExpiry(configured) {
|
|
72015
|
+
if (configured === void 0 || configured === null) return {
|
|
72016
|
+
ok: true,
|
|
72017
|
+
seconds: DEFAULT_SECONDS,
|
|
72018
|
+
value: "30d"
|
|
72019
|
+
};
|
|
72020
|
+
if (typeof configured !== "string") return refuse("not-a-string");
|
|
72021
|
+
const trimmed = configured.trim();
|
|
72022
|
+
if (trimmed === "") return {
|
|
72023
|
+
ok: true,
|
|
72024
|
+
seconds: DEFAULT_SECONDS,
|
|
72025
|
+
value: "30d"
|
|
72026
|
+
};
|
|
72027
|
+
const totalMs = durationMs(trimmed);
|
|
72028
|
+
if (totalMs === null) return refuse("malformed");
|
|
72029
|
+
if (totalMs < MIN_EXPIRY_MS) return refuse("too-short");
|
|
72030
|
+
if (totalMs > MAX_EXPIRY_MS) return refuse("too-long");
|
|
72031
|
+
return {
|
|
72032
|
+
ok: true,
|
|
72033
|
+
seconds: totalMs / 1e3,
|
|
72034
|
+
value: trimmed
|
|
72035
|
+
};
|
|
72036
|
+
}
|
|
72037
|
+
var REJECTION_DETAIL = {
|
|
72038
|
+
"not-a-string": "value is not a string",
|
|
72039
|
+
malformed: "not a duration string (expected e.g. 24h, 7d, 30d \u2014 a bare number is NOT seconds)",
|
|
72040
|
+
"too-short": `shorter than the ${MIN_EXPIRY_MS / 1e3}s minimum`,
|
|
72041
|
+
"too-long": `longer than the ${MAX_EXPIRY_MS / 864e5}d maximum`
|
|
72042
|
+
};
|
|
72043
|
+
function logTokenExpiryRejection(configured, rejection, logger) {
|
|
72044
|
+
logger.warn(`auth.tokenExpiry REJECTED \u2014 ${REJECTION_DETAIL[rejection.reason]}; signing with the default 30d instead`, { meta: {
|
|
72045
|
+
configured,
|
|
72046
|
+
reason: rejection.reason,
|
|
72047
|
+
applied: rejection.value
|
|
72048
|
+
} });
|
|
72049
|
+
}
|
|
71443
72050
|
var noopLogger = {
|
|
71444
72051
|
debug() {
|
|
71445
72052
|
},
|
|
@@ -71460,6 +72067,16 @@ var require_local_auth_addon = __commonJS({
|
|
|
71460
72067
|
config;
|
|
71461
72068
|
jwtSecret;
|
|
71462
72069
|
logger;
|
|
72070
|
+
/**
|
|
72071
|
+
* Offending `auth.tokenExpiry` values already reported by this instance.
|
|
72072
|
+
*
|
|
72073
|
+
* `signToken` re-reads the setting on EVERY mint so an operator's change
|
|
72074
|
+
* takes effect without a restart — which also means a bad value would log
|
|
72075
|
+
* once per login and once per silent refresh from every viewer. The refusal
|
|
72076
|
+
* must be visible, not repeated: the first mint after a bad save logs, the
|
|
72077
|
+
* rest resolve silently to the same default until the value changes.
|
|
72078
|
+
*/
|
|
72079
|
+
reportedInvalidExpiries = /* @__PURE__ */ new Set();
|
|
71463
72080
|
constructor(config, logger = noopLogger) {
|
|
71464
72081
|
this.config = config;
|
|
71465
72082
|
this.logger = logger;
|
|
@@ -71474,8 +72091,15 @@ var require_local_auth_addon = __commonJS({
|
|
|
71474
72091
|
}
|
|
71475
72092
|
signToken(payload) {
|
|
71476
72093
|
const configured = this.config.get("auth.tokenExpiry");
|
|
71477
|
-
const
|
|
71478
|
-
|
|
72094
|
+
const parsed = parseTokenExpiry(configured);
|
|
72095
|
+
if (!parsed.ok) {
|
|
72096
|
+
const seen = typeof configured === "string" ? configured : JSON.stringify(configured);
|
|
72097
|
+
if (!this.reportedInvalidExpiries.has(seen)) {
|
|
72098
|
+
this.reportedInvalidExpiries.add(seen);
|
|
72099
|
+
logTokenExpiryRejection(configured, parsed, this.logger);
|
|
72100
|
+
}
|
|
72101
|
+
}
|
|
72102
|
+
return import_jsonwebtoken.sign({ ...payload }, this.jwtSecret, { expiresIn: parsed.seconds });
|
|
71479
72103
|
}
|
|
71480
72104
|
verifyToken(token2) {
|
|
71481
72105
|
return import_jsonwebtoken.verify(token2, this.jwtSecret);
|
|
@@ -73762,6 +74386,30 @@ var require_local_auth_addon = __commonJS({
|
|
|
73762
74386
|
return parseRow(results[0].data);
|
|
73763
74387
|
}
|
|
73764
74388
|
};
|
|
74389
|
+
function createAuthConfigReader(resolved, writeAuthSection) {
|
|
74390
|
+
return {
|
|
74391
|
+
get(path) {
|
|
74392
|
+
switch (path) {
|
|
74393
|
+
case "auth.jwtSecret":
|
|
74394
|
+
return narrow(resolved.jwtSecret);
|
|
74395
|
+
case "auth.adminUsername":
|
|
74396
|
+
return narrow(resolved.adminUsername);
|
|
74397
|
+
case "auth.adminPassword":
|
|
74398
|
+
return narrow(resolved.adminPassword);
|
|
74399
|
+
case "auth.tokenExpiry":
|
|
74400
|
+
return narrow(resolved.tokenExpiry);
|
|
74401
|
+
default:
|
|
74402
|
+
return;
|
|
74403
|
+
}
|
|
74404
|
+
},
|
|
74405
|
+
update(_section, data) {
|
|
74406
|
+
if (typeof data["jwtSecret"] === "string") writeAuthSection({ jwtSecret: data["jwtSecret"] });
|
|
74407
|
+
}
|
|
74408
|
+
};
|
|
74409
|
+
}
|
|
74410
|
+
function narrow(value) {
|
|
74411
|
+
return value;
|
|
74412
|
+
}
|
|
73765
74413
|
var USERS_COLLECTION = "users";
|
|
73766
74414
|
var API_KEYS_COLLECTION = "api_keys";
|
|
73767
74415
|
var SCOPED_TOKENS_COLLECTION = "scoped_tokens";
|
|
@@ -74292,19 +74940,14 @@ var require_local_auth_addon = __commonJS({
|
|
|
74292
74940
|
}
|
|
74293
74941
|
async onInitialize() {
|
|
74294
74942
|
const authSection = await this.ctx.settings?.getSection("auth") ?? {};
|
|
74295
|
-
const
|
|
74296
|
-
|
|
74297
|
-
|
|
74298
|
-
|
|
74299
|
-
|
|
74300
|
-
|
|
74301
|
-
|
|
74302
|
-
|
|
74303
|
-
},
|
|
74304
|
-
update: (_section, data) => {
|
|
74305
|
-
if (typeof data["jwtSecret"] === "string") this.ctx.settings?.setSection("auth", { jwtSecret: data["jwtSecret"] });
|
|
74306
|
-
}
|
|
74307
|
-
};
|
|
74943
|
+
const reader = createAuthConfigReader({
|
|
74944
|
+
jwtSecret: typeof authSection["jwtSecret"] === "string" ? authSection["jwtSecret"] : this.config.jwtSecret ?? "",
|
|
74945
|
+
adminUsername: typeof authSection["adminUsername"] === "string" && authSection["adminUsername"] ? authSection["adminUsername"] : this.config.adminUsername ?? "",
|
|
74946
|
+
adminPassword: typeof authSection["adminPassword"] === "string" && authSection["adminPassword"] ? authSection["adminPassword"] : this.config.adminPassword ?? "",
|
|
74947
|
+
tokenExpiry: typeof authSection["tokenExpiry"] === "string" && authSection["tokenExpiry"] ? authSection["tokenExpiry"] : void 0
|
|
74948
|
+
}, (patch) => {
|
|
74949
|
+
this.ctx.settings?.setSection("auth", patch);
|
|
74950
|
+
});
|
|
74308
74951
|
this.authManager = new AuthManager(reader, this.ctx.logger);
|
|
74309
74952
|
const store = this.ctx.api?.settingsStore;
|
|
74310
74953
|
if (store) {
|
|
@@ -74610,7 +75253,7 @@ var require_loki_logging = __commonJS({
|
|
|
74610
75253
|
[Symbol.toStringTag]: { value: "Module" }
|
|
74611
75254
|
});
|
|
74612
75255
|
require_chunk_Cek0wNdY();
|
|
74613
|
-
var require_dist10 =
|
|
75256
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
74614
75257
|
function sanitizeLabelName(raw) {
|
|
74615
75258
|
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
74616
75259
|
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
@@ -75175,7 +75818,8 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75175
75818
|
[Symbol.toStringTag]: { value: "Module" }
|
|
75176
75819
|
});
|
|
75177
75820
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
75178
|
-
var require_dist10 =
|
|
75821
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
75822
|
+
var node_fs_promises = __require("fs/promises");
|
|
75179
75823
|
var node_child_process = __require("child_process");
|
|
75180
75824
|
var node_util = __require("util");
|
|
75181
75825
|
var node_os = __require("os");
|
|
@@ -75646,11 +76290,893 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75646
76290
|
});
|
|
75647
76291
|
});
|
|
75648
76292
|
}
|
|
75649
|
-
var
|
|
76293
|
+
var LoadPartition = class {
|
|
76294
|
+
capacity;
|
|
76295
|
+
slots;
|
|
76296
|
+
head = 0;
|
|
76297
|
+
count = 0;
|
|
76298
|
+
rows = 0;
|
|
76299
|
+
/** Newest retained sample's timestamp — the monotonic gate for `push`. */
|
|
76300
|
+
newestAtMs = null;
|
|
76301
|
+
constructor(capacity) {
|
|
76302
|
+
this.capacity = capacity;
|
|
76303
|
+
this.slots = Array.from({ length: capacity });
|
|
76304
|
+
}
|
|
76305
|
+
/**
|
|
76306
|
+
* Accept a sample.
|
|
76307
|
+
*
|
|
76308
|
+
* A sample at or before the newest one already held is REFUSED. The bus drops
|
|
76309
|
+
* a node's own broadcast echo, but a cross-node redelivery or a replayed
|
|
76310
|
+
* subscription must not be able to double a point — and idempotence here is
|
|
76311
|
+
* what lets every reader above treat the series as a set.
|
|
76312
|
+
*
|
|
76313
|
+
* `accepted` is reported separately from `rowDelta` on purpose: a full ring
|
|
76314
|
+
* that evicts a sample of the same size has a delta of zero and is not a
|
|
76315
|
+
* refusal, and conflating the two would silently stop advancing the write
|
|
76316
|
+
* ordinal on a steady-state cluster.
|
|
76317
|
+
*/
|
|
76318
|
+
push(sample) {
|
|
76319
|
+
if (this.newestAtMs !== null && sample.atMs <= this.newestAtMs) return {
|
|
76320
|
+
accepted: false,
|
|
76321
|
+
rowDelta: 0
|
|
76322
|
+
};
|
|
76323
|
+
const evicted = this.count === this.capacity ? this.slots[this.head]?.processes.length ?? 0 : 0;
|
|
76324
|
+
this.slots[this.head] = sample;
|
|
76325
|
+
this.head = (this.head + 1) % this.capacity;
|
|
76326
|
+
if (this.count < this.capacity) this.count++;
|
|
76327
|
+
this.newestAtMs = sample.atMs;
|
|
76328
|
+
const rowDelta = sample.processes.length - evicted;
|
|
76329
|
+
this.rows += rowDelta;
|
|
76330
|
+
return {
|
|
76331
|
+
accepted: true,
|
|
76332
|
+
rowDelta
|
|
76333
|
+
};
|
|
76334
|
+
}
|
|
76335
|
+
/** Drop the oldest sample. Returns the rows reclaimed (0 when empty). */
|
|
76336
|
+
dropOldest() {
|
|
76337
|
+
if (this.count === 0) return 0;
|
|
76338
|
+
const index = (this.head - this.count + this.capacity) % this.capacity;
|
|
76339
|
+
const victim = this.slots[index];
|
|
76340
|
+
this.slots[index] = void 0;
|
|
76341
|
+
this.count--;
|
|
76342
|
+
const reclaimed = victim?.processes.length ?? 0;
|
|
76343
|
+
this.rows -= reclaimed;
|
|
76344
|
+
if (this.count === 0) this.newestAtMs = null;
|
|
76345
|
+
return reclaimed;
|
|
76346
|
+
}
|
|
76347
|
+
/** Oldest-first, optionally only what is strictly newer than `sinceMs`. */
|
|
76348
|
+
list(sinceMs) {
|
|
76349
|
+
const out = [];
|
|
76350
|
+
for (let i = 0; i < this.count; i++) {
|
|
76351
|
+
const index = (this.head - this.count + i + this.capacity) % this.capacity;
|
|
76352
|
+
const sample = this.slots[index];
|
|
76353
|
+
if (sample === void 0) continue;
|
|
76354
|
+
if (sinceMs !== void 0 && sample.atMs <= sinceMs) continue;
|
|
76355
|
+
out.push(sample);
|
|
76356
|
+
}
|
|
76357
|
+
return out;
|
|
76358
|
+
}
|
|
76359
|
+
size() {
|
|
76360
|
+
return this.count;
|
|
76361
|
+
}
|
|
76362
|
+
rowCount() {
|
|
76363
|
+
return this.rows;
|
|
76364
|
+
}
|
|
76365
|
+
oldestAtMs() {
|
|
76366
|
+
if (this.count === 0) return null;
|
|
76367
|
+
const index = (this.head - this.count + this.capacity) % this.capacity;
|
|
76368
|
+
return this.slots[index]?.atMs ?? null;
|
|
76369
|
+
}
|
|
76370
|
+
lastWriteAtMs() {
|
|
76371
|
+
return this.newestAtMs;
|
|
76372
|
+
}
|
|
76373
|
+
};
|
|
76374
|
+
var NodeLoadRing = class {
|
|
76375
|
+
partitions = /* @__PURE__ */ new Map();
|
|
76376
|
+
/**
|
|
76377
|
+
* Node id → write ordinal of its last accepted sample. A monotonic counter,
|
|
76378
|
+
* not a clock: partition eviction must follow the order writes actually
|
|
76379
|
+
* happened, and node clocks disagree.
|
|
76380
|
+
*/
|
|
76381
|
+
lastWriteSeq = /* @__PURE__ */ new Map();
|
|
76382
|
+
writeSeq = 0;
|
|
76383
|
+
totalRows = 0;
|
|
76384
|
+
samplesPerNode;
|
|
76385
|
+
maxTotalProcessRows;
|
|
76386
|
+
maxNodes;
|
|
76387
|
+
idleEvictionMs;
|
|
76388
|
+
now;
|
|
76389
|
+
constructor(options = {}) {
|
|
76390
|
+
this.samplesPerNode = options.samplesPerNode ?? 180;
|
|
76391
|
+
this.maxTotalProcessRows = options.maxTotalProcessRows ?? 32e3;
|
|
76392
|
+
this.maxNodes = options.maxNodes ?? 16;
|
|
76393
|
+
this.idleEvictionMs = options.idleEvictionMs ?? 36e5;
|
|
76394
|
+
this.now = options.now ?? Date.now;
|
|
76395
|
+
}
|
|
76396
|
+
/**
|
|
76397
|
+
* Retain one snapshot. `processes` is stored by reference — the payload is
|
|
76398
|
+
* already an immutable arrival off the bus, and copying it would double the
|
|
76399
|
+
* measured cost for nothing.
|
|
76400
|
+
*
|
|
76401
|
+
* Returns whether the sample was ACCEPTED — that is, whether it was new
|
|
76402
|
+
* rather than a replay of a timestamp this node has already delivered.
|
|
76403
|
+
*
|
|
76404
|
+
* The return value is not diagnostics. It is the idempotence gate the
|
|
76405
|
+
* DURABLE tier rides on (`load-series-store.ts`): only an accepted sample is
|
|
76406
|
+
* appended to the table, which is what lets that table carry an `INTEGER`
|
|
76407
|
+
* rowid key instead of a composite unique index over two million rows. The
|
|
76408
|
+
* gate is one monotonic comparison in memory; the index it replaces was
|
|
76409
|
+
* measured at ~40 bytes per row.
|
|
76410
|
+
*/
|
|
76411
|
+
record(nodeId, atMs, processes) {
|
|
76412
|
+
if (nodeId.length === 0) return false;
|
|
76413
|
+
this.sweepIdle();
|
|
76414
|
+
let partition = this.partitions.get(nodeId);
|
|
76415
|
+
if (partition === void 0) {
|
|
76416
|
+
partition = new LoadPartition(this.samplesPerNode);
|
|
76417
|
+
this.partitions.set(nodeId, partition);
|
|
76418
|
+
}
|
|
76419
|
+
const outcome = partition.push({
|
|
76420
|
+
atMs,
|
|
76421
|
+
processes
|
|
76422
|
+
});
|
|
76423
|
+
if (!outcome.accepted) {
|
|
76424
|
+
if (partition.size() === 0) this.partitions.delete(nodeId);
|
|
76425
|
+
return false;
|
|
76426
|
+
}
|
|
76427
|
+
this.totalRows += outcome.rowDelta;
|
|
76428
|
+
this.lastWriteSeq.set(nodeId, ++this.writeSeq);
|
|
76429
|
+
if (this.partitions.size > this.maxNodes) this.evictPartitions(this.partitions.size - this.maxNodes, nodeId);
|
|
76430
|
+
this.enforceRowBudget();
|
|
76431
|
+
return true;
|
|
76432
|
+
}
|
|
76433
|
+
/**
|
|
76434
|
+
* Read one node's retained series, oldest-first.
|
|
76435
|
+
*
|
|
76436
|
+
* `sinceMs` is EXCLUSIVE: a caller passes the newest timestamp it already
|
|
76437
|
+
* holds and gets back only what it is missing. That is the whole contract
|
|
76438
|
+
* that lets the admin UI seed from here and then continue live without
|
|
76439
|
+
* doubling a point it already drew.
|
|
76440
|
+
*
|
|
76441
|
+
* A node nobody has recorded answers with an empty series, not an error —
|
|
76442
|
+
* unknown is the truth about a node that has not reported.
|
|
76443
|
+
*/
|
|
76444
|
+
read(nodeId, sinceMs) {
|
|
76445
|
+
this.sweepIdle();
|
|
76446
|
+
const partition = this.partitions.get(nodeId);
|
|
76447
|
+
if (partition === void 0) return {
|
|
76448
|
+
nodeId,
|
|
76449
|
+
samples: [],
|
|
76450
|
+
retainedSamples: 0,
|
|
76451
|
+
oldestAtMs: null,
|
|
76452
|
+
capacity: this.samplesPerNode
|
|
76453
|
+
};
|
|
76454
|
+
return {
|
|
76455
|
+
nodeId,
|
|
76456
|
+
samples: partition.list(sinceMs),
|
|
76457
|
+
retainedSamples: partition.size(),
|
|
76458
|
+
oldestAtMs: partition.oldestAtMs(),
|
|
76459
|
+
capacity: this.samplesPerNode
|
|
76460
|
+
};
|
|
76461
|
+
}
|
|
76462
|
+
/** Node ids with a live partition. Observability for the fleet bound. */
|
|
76463
|
+
nodeIds() {
|
|
76464
|
+
return [...this.partitions.keys()];
|
|
76465
|
+
}
|
|
76466
|
+
/** Process rows retained across every partition — the number that IS memory. */
|
|
76467
|
+
rowCount() {
|
|
76468
|
+
return this.totalRows;
|
|
76469
|
+
}
|
|
76470
|
+
/** Samples retained across every partition. */
|
|
76471
|
+
sampleCount() {
|
|
76472
|
+
let total = 0;
|
|
76473
|
+
for (const partition of this.partitions.values()) total += partition.size();
|
|
76474
|
+
return total;
|
|
76475
|
+
}
|
|
76476
|
+
/**
|
|
76477
|
+
* Drop partitions whose newest sample is older than the retention window.
|
|
76478
|
+
*
|
|
76479
|
+
* Lazy, on write and on read — never a timer. A timer would be a new
|
|
76480
|
+
* periodic cost in a subsystem whose entire premise is that it adds none,
|
|
76481
|
+
* and a ring that nobody writes to and nobody reads is not growing either.
|
|
76482
|
+
*/
|
|
76483
|
+
sweepIdle() {
|
|
76484
|
+
const cutoff = this.now() - this.idleEvictionMs;
|
|
76485
|
+
for (const [nodeId, partition] of [...this.partitions.entries()]) {
|
|
76486
|
+
const lastWrite = partition.lastWriteAtMs();
|
|
76487
|
+
if (lastWrite !== null && lastWrite > cutoff) continue;
|
|
76488
|
+
this.dropPartition(nodeId);
|
|
76489
|
+
}
|
|
76490
|
+
}
|
|
76491
|
+
/**
|
|
76492
|
+
* Bring the fleet back under the row budget by dropping the OLDEST sample of
|
|
76493
|
+
* the HEAVIEST partition, repeatedly.
|
|
76494
|
+
*
|
|
76495
|
+
* Terminates: every iteration removes one sample from a non-empty partition,
|
|
76496
|
+
* and the population of samples is finite and strictly decreasing.
|
|
76497
|
+
*/
|
|
76498
|
+
enforceRowBudget() {
|
|
76499
|
+
while (this.totalRows > this.maxTotalProcessRows) {
|
|
76500
|
+
const victim = this.heaviestPartition();
|
|
76501
|
+
if (victim === null) return;
|
|
76502
|
+
const [nodeId, partition] = victim;
|
|
76503
|
+
this.totalRows -= partition.dropOldest();
|
|
76504
|
+
if (partition.size() === 0) this.dropPartition(nodeId);
|
|
76505
|
+
}
|
|
76506
|
+
}
|
|
76507
|
+
heaviestPartition() {
|
|
76508
|
+
let best = null;
|
|
76509
|
+
for (const entry of this.partitions.entries()) {
|
|
76510
|
+
if (entry[1].size() === 0) continue;
|
|
76511
|
+
if (best === null || entry[1].rowCount() > best[1].rowCount()) best = entry;
|
|
76512
|
+
}
|
|
76513
|
+
return best;
|
|
76514
|
+
}
|
|
76515
|
+
/** Drop `count` whole partitions, least-recently-written first. */
|
|
76516
|
+
evictPartitions(count, protectedNodeId) {
|
|
76517
|
+
const order = [...this.partitions.keys()].filter((nodeId) => nodeId !== protectedNodeId).toSorted((a, b) => (this.lastWriteSeq.get(a) ?? 0) - (this.lastWriteSeq.get(b) ?? 0));
|
|
76518
|
+
let remaining = count;
|
|
76519
|
+
for (const nodeId of order) {
|
|
76520
|
+
if (remaining <= 0) return;
|
|
76521
|
+
this.dropPartition(nodeId);
|
|
76522
|
+
remaining -= 1;
|
|
76523
|
+
}
|
|
76524
|
+
}
|
|
76525
|
+
/** Remove a partition and everything that indexes it. */
|
|
76526
|
+
dropPartition(nodeId) {
|
|
76527
|
+
const partition = this.partitions.get(nodeId);
|
|
76528
|
+
if (partition === void 0) return;
|
|
76529
|
+
this.totalRows -= partition.rowCount();
|
|
76530
|
+
this.partitions.delete(nodeId);
|
|
76531
|
+
this.lastWriteSeq.delete(nodeId);
|
|
76532
|
+
}
|
|
76533
|
+
};
|
|
76534
|
+
var LOAD_SERIES_COLLECTION = "metrics:node-load-samples";
|
|
76535
|
+
var DEFAULT_MAX_ROWS = 5e5;
|
|
76536
|
+
var LOAD_SERIES_COLUMNS = [
|
|
76537
|
+
/** `INTEGER PRIMARY KEY` = SQLite rowid alias: the key IS the row's address,
|
|
76538
|
+
* so it costs no separate index and no stored string. */
|
|
76539
|
+
{
|
|
76540
|
+
name: "id",
|
|
76541
|
+
type: "INTEGER",
|
|
76542
|
+
primaryKey: true,
|
|
76543
|
+
notNull: true
|
|
76544
|
+
},
|
|
76545
|
+
{
|
|
76546
|
+
name: "nodeId",
|
|
76547
|
+
type: "TEXT",
|
|
76548
|
+
notNull: true
|
|
76549
|
+
},
|
|
76550
|
+
/** The EMITTING node's timestamp for the sample this row belongs to. Every
|
|
76551
|
+
* row of one sample shares it — that is what makes a sample reassemblable. */
|
|
76552
|
+
{
|
|
76553
|
+
name: "atMs",
|
|
76554
|
+
type: "INTEGER",
|
|
76555
|
+
notNull: true
|
|
76556
|
+
},
|
|
76557
|
+
{
|
|
76558
|
+
name: "pid",
|
|
76559
|
+
type: "INTEGER",
|
|
76560
|
+
notNull: true
|
|
76561
|
+
},
|
|
76562
|
+
/** `NULL` for a process no addon owns — `root` and `system` both. */
|
|
76563
|
+
{
|
|
76564
|
+
name: "addonId",
|
|
76565
|
+
type: "TEXT"
|
|
76566
|
+
},
|
|
76567
|
+
{
|
|
76568
|
+
name: "classification",
|
|
76569
|
+
type: "TEXT",
|
|
76570
|
+
notNull: true
|
|
76571
|
+
},
|
|
76572
|
+
/** `ps pcpu` x 10. A LIFETIME average, not a rate — see `NodeProcess`. */
|
|
76573
|
+
{
|
|
76574
|
+
name: "cpuDeci",
|
|
76575
|
+
type: "INTEGER",
|
|
76576
|
+
notNull: true
|
|
76577
|
+
},
|
|
76578
|
+
{
|
|
76579
|
+
name: "rssMib",
|
|
76580
|
+
type: "INTEGER",
|
|
76581
|
+
notNull: true
|
|
76582
|
+
},
|
|
76583
|
+
/** Instantaneous main-thread CPU% x 10. `NULL` = UNKNOWN, never zero. */
|
|
76584
|
+
{
|
|
76585
|
+
name: "cpuMainDeci",
|
|
76586
|
+
type: "INTEGER"
|
|
76587
|
+
},
|
|
76588
|
+
/** Instantaneous V8-helper-pool CPU% x 10. `NULL` = UNKNOWN, never zero. */
|
|
76589
|
+
{
|
|
76590
|
+
name: "cpuGcDeci",
|
|
76591
|
+
type: "INTEGER"
|
|
76592
|
+
}
|
|
76593
|
+
];
|
|
76594
|
+
var LOAD_SERIES_INDEXES = [
|
|
76595
|
+
/**
|
|
76596
|
+
* The ONE index, and it serves both jobs.
|
|
76597
|
+
*
|
|
76598
|
+
* Reads are always "this node, newer than T" — `nodeId` leads so the
|
|
76599
|
+
* equality is a range scan and `atMs` supplies the order without a sort.
|
|
76600
|
+
* Prunes are always "this node, oldest first" — the same index, walked from
|
|
76601
|
+
* the other end. A second index on `atMs` alone was measured and rejected:
|
|
76602
|
+
* it cost 23 B/row (196.0 → 172.8 with the two column drops) to serve a
|
|
76603
|
+
* cross-node prune that a per-node loop over three nodes already serves.
|
|
76604
|
+
*/
|
|
76605
|
+
{
|
|
76606
|
+
name: "idx_load_samples_node_at",
|
|
76607
|
+
columns: ["nodeId", "atMs"]
|
|
76608
|
+
}
|
|
76609
|
+
];
|
|
76610
|
+
var BYTES_PER_MIB = 1048576;
|
|
76611
|
+
function fromDeci(value) {
|
|
76612
|
+
return value === null ? null : Math.round(value) / 10;
|
|
76613
|
+
}
|
|
76614
|
+
function toDeci(value) {
|
|
76615
|
+
return value === null ? null : Math.round(value * 10);
|
|
76616
|
+
}
|
|
76617
|
+
function rowToProcess(row) {
|
|
76618
|
+
return {
|
|
76619
|
+
pid: row.pid,
|
|
76620
|
+
addonId: row.addonId,
|
|
76621
|
+
classification: row.classification,
|
|
76622
|
+
cpuPercent: fromDeci(row.cpuDeci) ?? 0,
|
|
76623
|
+
memoryRssBytes: row.rssMib * BYTES_PER_MIB,
|
|
76624
|
+
cpuMainPercent: fromDeci(row.cpuMainDeci),
|
|
76625
|
+
cpuGcPercent: fromDeci(row.cpuGcDeci)
|
|
76626
|
+
};
|
|
76627
|
+
}
|
|
76628
|
+
function processToRow(nodeId, atMs, process2) {
|
|
76629
|
+
return {
|
|
76630
|
+
nodeId,
|
|
76631
|
+
atMs,
|
|
76632
|
+
pid: process2.pid,
|
|
76633
|
+
addonId: process2.addonId,
|
|
76634
|
+
classification: process2.classification,
|
|
76635
|
+
cpuDeci: toDeci(process2.cpuPercent) ?? 0,
|
|
76636
|
+
rssMib: Math.round(process2.memoryRssBytes / BYTES_PER_MIB),
|
|
76637
|
+
cpuMainDeci: toDeci(process2.cpuMainPercent),
|
|
76638
|
+
cpuGcDeci: toDeci(process2.cpuGcPercent)
|
|
76639
|
+
};
|
|
76640
|
+
}
|
|
76641
|
+
function rowsToSamples(rows) {
|
|
76642
|
+
const byAt = /* @__PURE__ */ new Map();
|
|
76643
|
+
for (const row of rows) {
|
|
76644
|
+
const held = byAt.get(row.atMs);
|
|
76645
|
+
if (held === void 0) byAt.set(row.atMs, [rowToProcess(row)]);
|
|
76646
|
+
else held.push(rowToProcess(row));
|
|
76647
|
+
}
|
|
76648
|
+
return [...byAt.entries()].toSorted((a, b) => a[0] - b[0]).map(([atMs, processes]) => ({
|
|
76649
|
+
atMs,
|
|
76650
|
+
processes
|
|
76651
|
+
}));
|
|
76652
|
+
}
|
|
76653
|
+
var LoadSeriesStore = class {
|
|
76654
|
+
declared = false;
|
|
76655
|
+
lastPruneAtMs = 0;
|
|
76656
|
+
store;
|
|
76657
|
+
logger;
|
|
76658
|
+
nowFn;
|
|
76659
|
+
prunePageRows;
|
|
76660
|
+
pruneIntervalMs;
|
|
76661
|
+
constructor(deps) {
|
|
76662
|
+
this.store = deps.store;
|
|
76663
|
+
this.logger = deps.logger;
|
|
76664
|
+
this.nowFn = deps.now ?? (() => Date.now());
|
|
76665
|
+
this.prunePageRows = deps.prunePageRows ?? 2e4;
|
|
76666
|
+
this.pruneIntervalMs = deps.pruneIntervalMs ?? 6e4;
|
|
76667
|
+
}
|
|
76668
|
+
/** Idempotently declare the collection. `false` when the store refused. */
|
|
76669
|
+
async declare() {
|
|
76670
|
+
if (this.declared) return true;
|
|
76671
|
+
try {
|
|
76672
|
+
await this.store.declareCollection.mutate({
|
|
76673
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76674
|
+
columns: [...LOAD_SERIES_COLUMNS],
|
|
76675
|
+
indexes: LOAD_SERIES_INDEXES.map((i) => ({
|
|
76676
|
+
name: i.name,
|
|
76677
|
+
columns: [...i.columns]
|
|
76678
|
+
}))
|
|
76679
|
+
});
|
|
76680
|
+
this.declared = true;
|
|
76681
|
+
return true;
|
|
76682
|
+
} catch (err) {
|
|
76683
|
+
this.logger.warn("load series declareCollection failed \u2014 nothing will be retained on disk", { meta: {
|
|
76684
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76685
|
+
error: require_dist10.errMsg(err)
|
|
76686
|
+
} });
|
|
76687
|
+
return false;
|
|
76688
|
+
}
|
|
76689
|
+
}
|
|
76690
|
+
/**
|
|
76691
|
+
* Append ONE sample — every process row of it — in ONE transaction.
|
|
76692
|
+
*
|
|
76693
|
+
* Never a write per row. `insertMany` exists for exactly this: at the 10 s
|
|
76694
|
+
* default the fleet produces 7.6 rows/s, and 7.6 separate commits per second
|
|
76695
|
+
* on the connection that also serves every cluster-wide configuration read
|
|
76696
|
+
* is a constant load nobody asked for.
|
|
76697
|
+
*/
|
|
76698
|
+
async append(nodeId, atMs, processes) {
|
|
76699
|
+
if (processes.length === 0) return 0;
|
|
76700
|
+
if (!await this.declare()) return 0;
|
|
76701
|
+
const records = processes.map((p) => ({ data: { ...processToRow(nodeId, atMs, p) } }));
|
|
76702
|
+
try {
|
|
76703
|
+
const { inserted } = await this.store.insertMany.mutate({
|
|
76704
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76705
|
+
records
|
|
76706
|
+
});
|
|
76707
|
+
return inserted;
|
|
76708
|
+
} catch (err) {
|
|
76709
|
+
this.logger.warn("load series sample not retained \u2014 this interval will be missing", { meta: {
|
|
76710
|
+
nodeId,
|
|
76711
|
+
atMs,
|
|
76712
|
+
rows: processes.length,
|
|
76713
|
+
error: require_dist10.errMsg(err)
|
|
76714
|
+
} });
|
|
76715
|
+
return 0;
|
|
76716
|
+
}
|
|
76717
|
+
}
|
|
76718
|
+
/**
|
|
76719
|
+
* Read one node's cold samples, oldest-first.
|
|
76720
|
+
*
|
|
76721
|
+
* `sinceMs` is EXCLUSIVE, matching the ring, so a caller passing the newest
|
|
76722
|
+
* timestamp it holds gets only what it is missing. `limitRows` bounds the
|
|
76723
|
+
* read in ROWS (not samples) because rows are what the query costs.
|
|
76724
|
+
*/
|
|
76725
|
+
async read(nodeId, sinceMs, limitRows) {
|
|
76726
|
+
if (!await this.declare()) return [];
|
|
76727
|
+
try {
|
|
76728
|
+
const records = await this.store.query.query({
|
|
76729
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76730
|
+
filter: {
|
|
76731
|
+
where: { nodeId },
|
|
76732
|
+
whereBetween: { atMs: [sinceMs + 1, Number.MAX_SAFE_INTEGER] },
|
|
76733
|
+
orderBy: {
|
|
76734
|
+
field: "atMs",
|
|
76735
|
+
direction: "asc"
|
|
76736
|
+
},
|
|
76737
|
+
limit: limitRows
|
|
76738
|
+
}
|
|
76739
|
+
});
|
|
76740
|
+
const rows = [];
|
|
76741
|
+
for (const record of records) {
|
|
76742
|
+
const row = recordToRow(record.data);
|
|
76743
|
+
if (row !== null) rows.push(row);
|
|
76744
|
+
}
|
|
76745
|
+
return rowsToSamples(rows);
|
|
76746
|
+
} catch (err) {
|
|
76747
|
+
this.logger.warn("load series cold read failed \u2014 answering from the hot window only", { meta: {
|
|
76748
|
+
nodeId,
|
|
76749
|
+
sinceMs,
|
|
76750
|
+
error: require_dist10.errMsg(err)
|
|
76751
|
+
} });
|
|
76752
|
+
return [];
|
|
76753
|
+
}
|
|
76754
|
+
}
|
|
76755
|
+
/**
|
|
76756
|
+
* Enforce BOTH bounds, oldest-first, through a bounded page each.
|
|
76757
|
+
*
|
|
76758
|
+
* Rate-limited to {@link pruneIntervalMs}: a bound is not a deadline, and the
|
|
76759
|
+
* append path must not pay a sweep on every sample.
|
|
76760
|
+
*/
|
|
76761
|
+
async prune(nodeIds, retention, force = false) {
|
|
76762
|
+
const now = this.nowFn();
|
|
76763
|
+
if (!force && now - this.lastPruneAtMs < this.pruneIntervalMs) return null;
|
|
76764
|
+
this.lastPruneAtMs = now;
|
|
76765
|
+
if (!await this.declare()) return null;
|
|
76766
|
+
let deletedByAge = 0;
|
|
76767
|
+
let deletedByCap = 0;
|
|
76768
|
+
let rowsExamined = 0;
|
|
76769
|
+
const ageCutoff = now - retention.retentionHours * 36e5;
|
|
76770
|
+
for (const nodeId of nodeIds) {
|
|
76771
|
+
const outcome = await this.pruneNodeToCutoff(nodeId, ageCutoff);
|
|
76772
|
+
deletedByAge += outcome.deleted;
|
|
76773
|
+
rowsExamined += outcome.examined;
|
|
76774
|
+
}
|
|
76775
|
+
const total = await this.count();
|
|
76776
|
+
const excess = total === null ? 0 : total - retention.maxRows;
|
|
76777
|
+
if (excess > 0) {
|
|
76778
|
+
const outcome = await this.pruneOldestRows(nodeIds, excess);
|
|
76779
|
+
deletedByCap = outcome.deleted;
|
|
76780
|
+
rowsExamined += outcome.examined;
|
|
76781
|
+
this.logger.warn("load series ROW CAP bit \u2014 evicting the oldest samples", { meta: {
|
|
76782
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76783
|
+
rows: total,
|
|
76784
|
+
cap: retention.maxRows,
|
|
76785
|
+
over: excess,
|
|
76786
|
+
deleted: outcome.deleted,
|
|
76787
|
+
retentionHours: retention.retentionHours,
|
|
76788
|
+
hint: "lower the retention or the sampling cadence \u2014 the cap is the guarantee, not the intention"
|
|
76789
|
+
} });
|
|
76790
|
+
}
|
|
76791
|
+
return {
|
|
76792
|
+
deletedByAge,
|
|
76793
|
+
deletedByCap,
|
|
76794
|
+
rowsExamined,
|
|
76795
|
+
capBit: excess > 0
|
|
76796
|
+
};
|
|
76797
|
+
}
|
|
76798
|
+
/** Total rows, or `null` when the store could not answer. */
|
|
76799
|
+
async count() {
|
|
76800
|
+
if (!await this.declare()) return null;
|
|
76801
|
+
try {
|
|
76802
|
+
return await this.store.count.query({ collection: LOAD_SERIES_COLLECTION });
|
|
76803
|
+
} catch (err) {
|
|
76804
|
+
this.logger.warn("load series count failed \u2014 the row cap is not enforced this pass", { meta: { error: require_dist10.errMsg(err) } });
|
|
76805
|
+
return null;
|
|
76806
|
+
}
|
|
76807
|
+
}
|
|
76808
|
+
/**
|
|
76809
|
+
* Delete this node's rows older than `cutoff`, at most one page's worth.
|
|
76810
|
+
*
|
|
76811
|
+
* The page is the whole point. `deleteWhere({ atMs: [0, cutoff] })` on its
|
|
76812
|
+
* own is one statement but an UNBOUNDED one — a first pass after a retention
|
|
76813
|
+
* change would delete millions of rows inside a single stalling transaction.
|
|
76814
|
+
* So the page is read first (keys only, ordered by the index), its last
|
|
76815
|
+
* `atMs` becomes the EFFECTIVE cutoff, and the delete is bounded by it.
|
|
76816
|
+
*/
|
|
76817
|
+
async pruneNodeToCutoff(nodeId, cutoff) {
|
|
76818
|
+
try {
|
|
76819
|
+
const page = await this.store.query.query({
|
|
76820
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76821
|
+
filter: {
|
|
76822
|
+
where: { nodeId },
|
|
76823
|
+
whereBetween: { atMs: [0, cutoff] },
|
|
76824
|
+
orderBy: {
|
|
76825
|
+
field: "atMs",
|
|
76826
|
+
direction: "asc"
|
|
76827
|
+
},
|
|
76828
|
+
limit: this.prunePageRows
|
|
76829
|
+
},
|
|
76830
|
+
columns: ["atMs"]
|
|
76831
|
+
});
|
|
76832
|
+
if (page.length === 0) return {
|
|
76833
|
+
deleted: 0,
|
|
76834
|
+
examined: 0
|
|
76835
|
+
};
|
|
76836
|
+
const effectiveCutoff = Number(page.at(-1)?.data["atMs"]);
|
|
76837
|
+
if (!Number.isFinite(effectiveCutoff)) return {
|
|
76838
|
+
deleted: 0,
|
|
76839
|
+
examined: page.length
|
|
76840
|
+
};
|
|
76841
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
76842
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76843
|
+
filter: {
|
|
76844
|
+
where: { nodeId },
|
|
76845
|
+
whereBetween: { atMs: [0, effectiveCutoff] }
|
|
76846
|
+
}
|
|
76847
|
+
});
|
|
76848
|
+
return {
|
|
76849
|
+
deleted,
|
|
76850
|
+
examined: page.length
|
|
76851
|
+
};
|
|
76852
|
+
} catch (err) {
|
|
76853
|
+
this.logger.warn("load series age prune failed \u2014 the table keeps growing this pass", { meta: {
|
|
76854
|
+
nodeId,
|
|
76855
|
+
cutoff,
|
|
76856
|
+
error: require_dist10.errMsg(err)
|
|
76857
|
+
} });
|
|
76858
|
+
return {
|
|
76859
|
+
deleted: 0,
|
|
76860
|
+
examined: 0
|
|
76861
|
+
};
|
|
76862
|
+
}
|
|
76863
|
+
}
|
|
76864
|
+
/**
|
|
76865
|
+
* Drop the oldest rows across the known nodes until `excess` is covered.
|
|
76866
|
+
*
|
|
76867
|
+
* Same bounded-page technique, walked per node so the one index serves it.
|
|
76868
|
+
* The node holding the oldest rows pays first, which is also the node
|
|
76869
|
+
* producing the pressure when a runaway process count is the cause.
|
|
76870
|
+
*/
|
|
76871
|
+
async pruneOldestRows(nodeIds, excess) {
|
|
76872
|
+
let remaining = Math.min(excess, this.prunePageRows);
|
|
76873
|
+
let deleted = 0;
|
|
76874
|
+
let examined = 0;
|
|
76875
|
+
for (const nodeId of nodeIds) {
|
|
76876
|
+
if (remaining <= 0) break;
|
|
76877
|
+
try {
|
|
76878
|
+
const page = await this.store.query.query({
|
|
76879
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76880
|
+
filter: {
|
|
76881
|
+
where: { nodeId },
|
|
76882
|
+
orderBy: {
|
|
76883
|
+
field: "atMs",
|
|
76884
|
+
direction: "asc"
|
|
76885
|
+
},
|
|
76886
|
+
limit: remaining
|
|
76887
|
+
},
|
|
76888
|
+
columns: ["atMs"]
|
|
76889
|
+
});
|
|
76890
|
+
examined += page.length;
|
|
76891
|
+
if (page.length === 0) continue;
|
|
76892
|
+
const cutoff = Number(page.at(-1)?.data["atMs"]);
|
|
76893
|
+
if (!Number.isFinite(cutoff)) continue;
|
|
76894
|
+
const result = await this.store.deleteWhere.mutate({
|
|
76895
|
+
collection: LOAD_SERIES_COLLECTION,
|
|
76896
|
+
filter: {
|
|
76897
|
+
where: { nodeId },
|
|
76898
|
+
whereBetween: { atMs: [0, cutoff] }
|
|
76899
|
+
}
|
|
76900
|
+
});
|
|
76901
|
+
deleted += result.deleted;
|
|
76902
|
+
remaining -= result.deleted;
|
|
76903
|
+
} catch (err) {
|
|
76904
|
+
this.logger.warn("load series cap prune failed \u2014 the cap is not enforced this pass", { meta: {
|
|
76905
|
+
nodeId,
|
|
76906
|
+
error: require_dist10.errMsg(err)
|
|
76907
|
+
} });
|
|
76908
|
+
}
|
|
76909
|
+
}
|
|
76910
|
+
return {
|
|
76911
|
+
deleted,
|
|
76912
|
+
examined
|
|
76913
|
+
};
|
|
76914
|
+
}
|
|
76915
|
+
};
|
|
76916
|
+
function recordToRow(data) {
|
|
76917
|
+
const nodeId = data["nodeId"];
|
|
76918
|
+
const classification = data["classification"];
|
|
76919
|
+
if (typeof nodeId !== "string" || typeof classification !== "string") return null;
|
|
76920
|
+
const atMs = Number(data["atMs"]);
|
|
76921
|
+
const pid = Number(data["pid"]);
|
|
76922
|
+
const cpuDeci = Number(data["cpuDeci"]);
|
|
76923
|
+
const rssMib = Number(data["rssMib"]);
|
|
76924
|
+
if (![
|
|
76925
|
+
atMs,
|
|
76926
|
+
pid,
|
|
76927
|
+
cpuDeci,
|
|
76928
|
+
rssMib
|
|
76929
|
+
].every((n) => Number.isFinite(n))) return null;
|
|
76930
|
+
const rawAddon = data["addonId"];
|
|
76931
|
+
const optional = (raw) => {
|
|
76932
|
+
if (raw === null || raw === void 0) return null;
|
|
76933
|
+
const n = Number(raw);
|
|
76934
|
+
return Number.isFinite(n) ? n : null;
|
|
76935
|
+
};
|
|
76936
|
+
return {
|
|
76937
|
+
nodeId,
|
|
76938
|
+
atMs,
|
|
76939
|
+
pid,
|
|
76940
|
+
addonId: typeof rawAddon === "string" ? rawAddon : null,
|
|
76941
|
+
classification,
|
|
76942
|
+
cpuDeci,
|
|
76943
|
+
rssMib,
|
|
76944
|
+
cpuMainDeci: optional(data["cpuMainDeci"]),
|
|
76945
|
+
cpuGcDeci: optional(data["cpuGcDeci"])
|
|
76946
|
+
};
|
|
76947
|
+
}
|
|
76948
|
+
var LoadSeriesConfigError = class extends Error {
|
|
76949
|
+
constructor(message) {
|
|
76950
|
+
super(message);
|
|
76951
|
+
this.name = "LoadSeriesConfigError";
|
|
76952
|
+
}
|
|
76953
|
+
};
|
|
76954
|
+
function requireInteger(value, field) {
|
|
76955
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
76956
|
+
if (!Number.isFinite(n)) throw new LoadSeriesConfigError(`${field} must be a number, received ${String(value)}`);
|
|
76957
|
+
return Math.round(n);
|
|
76958
|
+
}
|
|
76959
|
+
function resolveLoadSeriesConfig(raw) {
|
|
76960
|
+
let cadenceSec = 10;
|
|
76961
|
+
if (raw.loadSeriesCadenceSec !== void 0 && raw.loadSeriesCadenceSec !== null) {
|
|
76962
|
+
cadenceSec = requireInteger(raw.loadSeriesCadenceSec, "loadSeriesCadenceSec");
|
|
76963
|
+
if (cadenceSec < 5 || cadenceSec > 60) throw new LoadSeriesConfigError(`load series cadence must be between 5 and 60 seconds \u2014 refused ${cadenceSec}`);
|
|
76964
|
+
}
|
|
76965
|
+
let retentionHours = 6;
|
|
76966
|
+
if (raw.loadSeriesRetentionHours !== void 0 && raw.loadSeriesRetentionHours !== null) {
|
|
76967
|
+
retentionHours = requireInteger(raw.loadSeriesRetentionHours, "loadSeriesRetentionHours");
|
|
76968
|
+
if (retentionHours < 1 || retentionHours > 72) throw new LoadSeriesConfigError(`load series retention must be between 1 and 72 hours \u2014 refused ${retentionHours}`);
|
|
76969
|
+
}
|
|
76970
|
+
return {
|
|
76971
|
+
cadenceSec,
|
|
76972
|
+
retentionHours,
|
|
76973
|
+
maxRows: DEFAULT_MAX_ROWS
|
|
76974
|
+
};
|
|
76975
|
+
}
|
|
76976
|
+
function projectLoadSeriesCost(input) {
|
|
76977
|
+
const samplesPerNodeWindow = input.config.retentionHours * 3600 / input.config.cadenceSec;
|
|
76978
|
+
const intendedRows = Math.round(samplesPerNodeWindow * Math.max(input.observedProcessRows, 0));
|
|
76979
|
+
const boundedRows = Math.min(intendedRows, input.config.maxRows);
|
|
76980
|
+
return {
|
|
76981
|
+
intendedRows,
|
|
76982
|
+
boundedRows,
|
|
76983
|
+
estimatedBytes: boundedRows * 87,
|
|
76984
|
+
capBites: intendedRows > input.config.maxRows
|
|
76985
|
+
};
|
|
76986
|
+
}
|
|
76987
|
+
function describeLoadSeriesCost(projection) {
|
|
76988
|
+
const mib = (projection.estimatedBytes / 1048576).toFixed(1);
|
|
76989
|
+
const rows = projection.intendedRows.toLocaleString("en-US");
|
|
76990
|
+
const capped = projection.boundedRows.toLocaleString("en-US");
|
|
76991
|
+
if (!projection.capBites) return `This configuration retains ~${rows} rows \u2248 ${mib} MiB on the hub database (NVMe cache), measured at 87 bytes per row.`;
|
|
76992
|
+
return `This configuration WANTS ~${rows} rows, above the ${projection.boundedRows.toLocaleString("en-US")}-row hard cap. The table will hold ~${capped} rows \u2248 ${mib} MiB and the OLDEST samples will be evicted, so the effective window is shorter than the retention you asked for.`;
|
|
76993
|
+
}
|
|
76994
|
+
function mergeSamples(cold, hot) {
|
|
76995
|
+
const byAt = /* @__PURE__ */ new Map();
|
|
76996
|
+
for (const sample of cold) byAt.set(sample.atMs, sample);
|
|
76997
|
+
for (const sample of hot) byAt.set(sample.atMs, sample);
|
|
76998
|
+
return [...byAt.values()].toSorted((a, b) => a.atMs - b.atMs);
|
|
76999
|
+
}
|
|
77000
|
+
function toFoldRows(processes) {
|
|
77001
|
+
return processes;
|
|
77002
|
+
}
|
|
77003
|
+
function mergeLoadSeries(input) {
|
|
77004
|
+
const samples = mergeSamples(input.cold, input.hot);
|
|
77005
|
+
if (samples.length === 0) return {
|
|
77006
|
+
series: [],
|
|
77007
|
+
bucketMs: input.cadenceMs,
|
|
77008
|
+
retainedSamples: 0,
|
|
77009
|
+
oldestAtMs: null
|
|
77010
|
+
};
|
|
77011
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
77012
|
+
for (const sample of samples) for (const bucket of require_dist10.foldSnapshotByFunction(toFoldRows(sample.processes), sample.atMs)) {
|
|
77013
|
+
const held = byKey.get(bucket.key);
|
|
77014
|
+
if (held === void 0) byKey.set(bucket.key, {
|
|
77015
|
+
kind: bucket.kind,
|
|
77016
|
+
points: [bucket.point]
|
|
77017
|
+
});
|
|
77018
|
+
else held.points.push(bucket.point);
|
|
77019
|
+
}
|
|
77020
|
+
const oldestAtMs = samples[0]?.atMs ?? null;
|
|
77021
|
+
const newestAtMs = samples.at(-1)?.atMs ?? oldestAtMs;
|
|
77022
|
+
const spanMs = oldestAtMs === null || newestAtMs === null ? 0 : newestAtMs - oldestAtMs + input.cadenceMs;
|
|
77023
|
+
const bucketMs = input.maxPoints === void 0 ? input.cadenceMs : require_dist10.resolveBucketMs(spanMs, input.cadenceMs, input.maxPoints);
|
|
77024
|
+
const origin = oldestAtMs ?? 0;
|
|
77025
|
+
const series = [...byKey.entries()].map(([key, held]) => ({
|
|
77026
|
+
key,
|
|
77027
|
+
kind: held.kind,
|
|
77028
|
+
points: bucketMs > input.cadenceMs ? require_dist10.reducePoints(held.points, bucketMs, origin) : held.points
|
|
77029
|
+
}));
|
|
77030
|
+
const weight = (s) => {
|
|
77031
|
+
const last = s.points.at(-1);
|
|
77032
|
+
if (last === void 0) return -1;
|
|
77033
|
+
if (last.cpuMainPercent !== null && last.cpuGcPercent !== null) return last.cpuMainPercent + last.cpuGcPercent;
|
|
77034
|
+
return last.cpuLifetimePercent;
|
|
77035
|
+
};
|
|
77036
|
+
return {
|
|
77037
|
+
series: series.toSorted((a, b) => {
|
|
77038
|
+
const d = weight(b) - weight(a);
|
|
77039
|
+
return d !== 0 ? d : a.key.localeCompare(b.key);
|
|
77040
|
+
}),
|
|
77041
|
+
bucketMs,
|
|
77042
|
+
retainedSamples: samples.length,
|
|
77043
|
+
oldestAtMs
|
|
77044
|
+
};
|
|
77045
|
+
}
|
|
75650
77046
|
var CAMSTACK_CMD_RE = /(camstack|tsx\s+watch\s.*launcher\.ts|packages\/agent\/dist\/cli\.js|inference_pool\.py|bench-(inference-pool|nodeav)|node .*\/packages\/)/;
|
|
75651
|
-
|
|
77047
|
+
function classifyProcess(pid, selfPid, managed) {
|
|
77048
|
+
if (pid === selfPid) return "root";
|
|
77049
|
+
if (managed.has(pid)) return "managed";
|
|
77050
|
+
return "system";
|
|
77051
|
+
}
|
|
77052
|
+
function buildNodeProcesses(input) {
|
|
77053
|
+
const out = [];
|
|
77054
|
+
for (const p of input.psRows) {
|
|
77055
|
+
if (!CAMSTACK_CMD_RE.test(p.command)) continue;
|
|
77056
|
+
const managed = input.managed.get(p.pid);
|
|
77057
|
+
const split = input.cpuSplits.get(p.pid);
|
|
77058
|
+
const classification = classifyProcess(p.pid, input.selfPid, input.managed);
|
|
77059
|
+
out.push({
|
|
77060
|
+
pid: p.pid,
|
|
77061
|
+
ppid: p.ppid,
|
|
77062
|
+
pgid: p.pgid,
|
|
77063
|
+
classification,
|
|
77064
|
+
addonId: managed?.addonId ?? null,
|
|
77065
|
+
nodeId: managed?.nodeId ?? (p.pid === input.selfPid ? input.selfNodeId : null),
|
|
77066
|
+
command: p.command,
|
|
77067
|
+
cpuPercent: p.cpuPercent,
|
|
77068
|
+
memoryRssBytes: p.memoryRssBytes,
|
|
77069
|
+
cpuMainPercent: split?.mainPercent ?? null,
|
|
77070
|
+
cpuGcPercent: split?.gcPercent ?? null,
|
|
77071
|
+
threadCount: split?.threadCount ?? null,
|
|
77072
|
+
uptimeSec: p.uptimeSec
|
|
77073
|
+
});
|
|
77074
|
+
}
|
|
77075
|
+
return out;
|
|
77076
|
+
}
|
|
77077
|
+
var CLOCK_TICKS_PER_SEC = 100;
|
|
77078
|
+
var V8_HELPER_THREAD_RE = /^V8Worker/;
|
|
77079
|
+
function parseThreadStat(line) {
|
|
77080
|
+
const close = line.lastIndexOf(")");
|
|
77081
|
+
const open = line.indexOf("(");
|
|
77082
|
+
if (close < 0 || open < 0 || close < open) return null;
|
|
77083
|
+
const comm = line.slice(open + 1, close);
|
|
77084
|
+
const rest = line.slice(close + 1).trim().split(/\s+/);
|
|
77085
|
+
const utime = Number(rest[11]);
|
|
77086
|
+
const stime = Number(rest[12]);
|
|
77087
|
+
const startTicks = Number(rest[19]);
|
|
77088
|
+
if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;
|
|
77089
|
+
if (!Number.isFinite(startTicks)) return null;
|
|
77090
|
+
return {
|
|
77091
|
+
comm,
|
|
77092
|
+
ticks: utime + stime,
|
|
77093
|
+
startTicks
|
|
77094
|
+
};
|
|
77095
|
+
}
|
|
77096
|
+
var nodeProcFsReader = {
|
|
77097
|
+
listTaskIds: (pid) => (0, node_fs_promises.readdir)(`/proc/${pid}/task`),
|
|
77098
|
+
readTaskStat: (pid, tid) => (0, node_fs_promises.readFile)(`/proc/${pid}/task/${tid}/stat`, "utf8")
|
|
77099
|
+
};
|
|
77100
|
+
async function readThreadTicks(pid, reader = nodeProcFsReader) {
|
|
77101
|
+
let tids;
|
|
77102
|
+
try {
|
|
77103
|
+
tids = await reader.listTaskIds(pid);
|
|
77104
|
+
} catch {
|
|
77105
|
+
return null;
|
|
77106
|
+
}
|
|
77107
|
+
let mainTicks = 0;
|
|
77108
|
+
let gcTicks = 0;
|
|
77109
|
+
let threadCount = 0;
|
|
77110
|
+
let startTicks = null;
|
|
77111
|
+
const mainThreadTid = String(pid);
|
|
77112
|
+
for (const tid of tids) {
|
|
77113
|
+
let line;
|
|
77114
|
+
try {
|
|
77115
|
+
line = await reader.readTaskStat(pid, tid);
|
|
77116
|
+
} catch {
|
|
77117
|
+
continue;
|
|
77118
|
+
}
|
|
77119
|
+
const parsed = parseThreadStat(line);
|
|
77120
|
+
if (parsed === null) continue;
|
|
77121
|
+
threadCount += 1;
|
|
77122
|
+
if (tid === mainThreadTid) startTicks = parsed.startTicks;
|
|
77123
|
+
if (V8_HELPER_THREAD_RE.test(parsed.comm)) gcTicks += parsed.ticks;
|
|
77124
|
+
else mainTicks += parsed.ticks;
|
|
77125
|
+
}
|
|
77126
|
+
if (threadCount === 0) return null;
|
|
77127
|
+
return {
|
|
77128
|
+
mainTicks,
|
|
77129
|
+
gcTicks,
|
|
77130
|
+
threadCount,
|
|
77131
|
+
atMs: Date.now(),
|
|
77132
|
+
startTicks
|
|
77133
|
+
};
|
|
77134
|
+
}
|
|
77135
|
+
function cpuSplitBetween(prev, next) {
|
|
77136
|
+
const windowMs = next.atMs - prev.atMs;
|
|
77137
|
+
if (windowMs <= 0) return null;
|
|
77138
|
+
if (prev.startTicks === null || next.startTicks === null) return null;
|
|
77139
|
+
if (prev.startTicks !== next.startTicks) return null;
|
|
77140
|
+
const mainDelta = next.mainTicks - prev.mainTicks;
|
|
77141
|
+
const gcDelta = next.gcTicks - prev.gcTicks;
|
|
77142
|
+
if (mainDelta < 0 || gcDelta < 0) return null;
|
|
77143
|
+
const windowTicks = windowMs / 1e3 * CLOCK_TICKS_PER_SEC;
|
|
77144
|
+
const pct = (delta) => Math.round(delta / windowTicks * 1e3) / 10;
|
|
77145
|
+
return {
|
|
77146
|
+
mainPercent: pct(mainDelta),
|
|
77147
|
+
gcPercent: pct(gcDelta),
|
|
77148
|
+
threadCount: next.threadCount
|
|
77149
|
+
};
|
|
77150
|
+
}
|
|
77151
|
+
var ThreadCpuTracker = class {
|
|
77152
|
+
reader;
|
|
77153
|
+
previous = /* @__PURE__ */ new Map();
|
|
77154
|
+
constructor(reader = nodeProcFsReader) {
|
|
77155
|
+
this.reader = reader;
|
|
77156
|
+
}
|
|
77157
|
+
/**
|
|
77158
|
+
* Sample `pids` and return the split for each one that HAS a usable previous
|
|
77159
|
+
* sample. A pid absent from the result has no answer yet — the caller must
|
|
77160
|
+
* report `null`, not `0`.
|
|
77161
|
+
*/
|
|
77162
|
+
async sample(pids) {
|
|
77163
|
+
const out = /* @__PURE__ */ new Map();
|
|
77164
|
+
const nextPrevious = /* @__PURE__ */ new Map();
|
|
77165
|
+
for (const pid of pids) {
|
|
77166
|
+
const next = await readThreadTicks(pid, this.reader);
|
|
77167
|
+
if (next === null) continue;
|
|
77168
|
+
nextPrevious.set(pid, next);
|
|
77169
|
+
const prev = this.previous.get(pid);
|
|
77170
|
+
if (prev === void 0) continue;
|
|
77171
|
+
const split = cpuSplitBetween(prev, next);
|
|
77172
|
+
if (split !== null) out.set(pid, split);
|
|
77173
|
+
}
|
|
77174
|
+
this.previous = nextPrevious;
|
|
77175
|
+
return out;
|
|
77176
|
+
}
|
|
77177
|
+
};
|
|
77178
|
+
var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
|
|
75652
77179
|
var METRICS_SNAPSHOT_INTERVAL_MS = 5e3;
|
|
75653
|
-
var PROCESS_SNAPSHOT_INTERVAL_MS = 2e4;
|
|
75654
77180
|
var METRICS_SNAPSHOT_HEARTBEAT_MS = 6e4;
|
|
75655
77181
|
function coarsenResourcesSnapshot(snapshot) {
|
|
75656
77182
|
if (!snapshot || typeof snapshot !== "object") return JSON.stringify(snapshot);
|
|
@@ -75669,18 +77195,6 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75669
77195
|
};
|
|
75670
77196
|
return JSON.stringify(round(snapshot));
|
|
75671
77197
|
}
|
|
75672
|
-
function coarsenProcessList(processes) {
|
|
75673
|
-
const summary = processes.filter((p) => !!p && typeof p === "object").map((p) => {
|
|
75674
|
-
return [
|
|
75675
|
-
p["pid"],
|
|
75676
|
-
p["addonId"],
|
|
75677
|
-
p["state"],
|
|
75678
|
-
typeof p["cpuPercent"] === "number" ? Math.round(p["cpuPercent"] / 5) * 5 : null,
|
|
75679
|
-
typeof p["memoryRss"] === "number" ? Math.round(p["memoryRss"] / (50 * 1024 * 1024)) : null
|
|
75680
|
-
];
|
|
75681
|
-
});
|
|
75682
|
-
return JSON.stringify(summary);
|
|
75683
|
-
}
|
|
75684
77198
|
function narrowWorkerState(state) {
|
|
75685
77199
|
switch (state) {
|
|
75686
77200
|
case "starting":
|
|
@@ -75693,7 +77207,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75693
77207
|
return "running";
|
|
75694
77208
|
}
|
|
75695
77209
|
}
|
|
75696
|
-
var NativeMetricsAddon = class extends require_dist10.BaseAddon {
|
|
77210
|
+
var NativeMetricsAddon = class NativeMetricsAddon2 extends require_dist10.BaseAddon {
|
|
75697
77211
|
provider = null;
|
|
75698
77212
|
startedAtMs = Date.now();
|
|
75699
77213
|
snapshotTimer = null;
|
|
@@ -75705,9 +77219,77 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75705
77219
|
* elapsed) is skipped.
|
|
75706
77220
|
*/
|
|
75707
77221
|
lastResourcesEmit = null;
|
|
75708
|
-
|
|
77222
|
+
/**
|
|
77223
|
+
* Holds the previous `/proc/<pid>/task/*` tick counts so each process scan
|
|
77224
|
+
* can turn them into an instantaneous CPU rate split between the process's
|
|
77225
|
+
* own threads and V8's helper pool. Self-bounding — see `ThreadCpuTracker`.
|
|
77226
|
+
*/
|
|
77227
|
+
threadCpu = new ThreadCpuTracker();
|
|
77228
|
+
/**
|
|
77229
|
+
* Retention for the snapshots this addon emits. Fed by SUBSCRIBING to
|
|
77230
|
+
* `metrics.node-processes-snapshot`, never by a second sampler — on the hub
|
|
77231
|
+
* that bus carries every node, so the hub's ring is the cluster's. Bounds and
|
|
77232
|
+
* measured cost live in `node-load-ring.ts`.
|
|
77233
|
+
*/
|
|
77234
|
+
loadRing = new NodeLoadRing();
|
|
77235
|
+
/**
|
|
77236
|
+
* The COLD tier, and it exists only on the HUB.
|
|
77237
|
+
*
|
|
77238
|
+
* The table is hub-resident and the hub already hears every node's snapshot
|
|
77239
|
+
* on its bus, so the hub's writer is the cluster's. An agent writing through
|
|
77240
|
+
* the `settings-store` singleton would ship its rows over Moleculer, once
|
|
77241
|
+
* per sample, to reach that very same table.
|
|
77242
|
+
*/
|
|
77243
|
+
loadStore = null;
|
|
77244
|
+
/** The resolved knobs. Re-resolved on every settings write. */
|
|
77245
|
+
loadConfig = {
|
|
77246
|
+
cadenceSec: 10,
|
|
77247
|
+
retentionHours: 6,
|
|
77248
|
+
maxRows: DEFAULT_MAX_ROWS
|
|
77249
|
+
};
|
|
77250
|
+
/**
|
|
77251
|
+
* Process rows seen in each node's most recent snapshot — the OBSERVED
|
|
77252
|
+
* numbers the settings form projects its cost from. A constant here would be
|
|
77253
|
+
* a projection that stops being true the first time the fleet changes.
|
|
77254
|
+
*/
|
|
77255
|
+
observedRowsByNode = /* @__PURE__ */ new Map();
|
|
75709
77256
|
constructor() {
|
|
75710
|
-
super({
|
|
77257
|
+
super({
|
|
77258
|
+
samplingIntervalMs: 5e3,
|
|
77259
|
+
loadSeriesCadenceSec: 10,
|
|
77260
|
+
loadSeriesRetentionHours: 6
|
|
77261
|
+
});
|
|
77262
|
+
}
|
|
77263
|
+
/** Is this the hub? The same test every other addon uses (`addon-ai`). */
|
|
77264
|
+
get isHub() {
|
|
77265
|
+
return (this.ctx.kernel.cluster?.broker?.nodeID ?? "hub") === "hub";
|
|
77266
|
+
}
|
|
77267
|
+
/** Process rows observed across the whole fleet, for the cost projection. */
|
|
77268
|
+
observedFleetRows() {
|
|
77269
|
+
let total = 0;
|
|
77270
|
+
for (const rows of this.observedRowsByNode.values()) total += rows;
|
|
77271
|
+
return total;
|
|
77272
|
+
}
|
|
77273
|
+
/**
|
|
77274
|
+
* Project a `NodeProcess` onto the series' own row.
|
|
77275
|
+
*
|
|
77276
|
+
* `command`, `ppid`, `pgid`, `nodeId`, `threadCount` and `uptimeSec` are
|
|
77277
|
+
* dropped here, at the single point both tiers pass through, so the hot ring
|
|
77278
|
+
* and the cold table carry the SAME shape and the merged read cannot tell
|
|
77279
|
+
* them apart. `command` in particular is the fattest field in a snapshot and
|
|
77280
|
+
* the same identical string on every runner — the runner id travels in the
|
|
77281
|
+
* environment, not in argv — and nothing that draws this series reads it.
|
|
77282
|
+
*/
|
|
77283
|
+
static toRetained(process2) {
|
|
77284
|
+
return {
|
|
77285
|
+
pid: process2.pid,
|
|
77286
|
+
addonId: process2.addonId,
|
|
77287
|
+
classification: process2.classification,
|
|
77288
|
+
cpuPercent: process2.cpuPercent,
|
|
77289
|
+
memoryRssBytes: process2.memoryRssBytes,
|
|
77290
|
+
cpuMainPercent: process2.cpuMainPercent,
|
|
77291
|
+
cpuGcPercent: process2.cpuGcPercent
|
|
77292
|
+
};
|
|
75711
77293
|
}
|
|
75712
77294
|
async onInitialize() {
|
|
75713
77295
|
const provider = new NativeMetricsProvider();
|
|
@@ -75727,16 +77309,127 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75727
77309
|
listAddonInstances: () => this.listAddonInstances(),
|
|
75728
77310
|
getAddonStats: (params) => this.getAddonStats(params.addonId),
|
|
75729
77311
|
listNodeProcesses: () => this.listNodeProcesses(),
|
|
75730
|
-
|
|
77312
|
+
getLoadSeries: (params) => this.readLoadSeries(params),
|
|
75731
77313
|
dumpHeapSnapshot: (params) => this.dumpHeapSnapshot(params)
|
|
75732
77314
|
};
|
|
77315
|
+
this.applyLoadSeriesConfig();
|
|
77316
|
+
if (this.isHub) this.loadStore = new LoadSeriesStore({
|
|
77317
|
+
store: this.ctx.api.settingsStore,
|
|
77318
|
+
logger: this.ctx.logger.child("LoadSeries")
|
|
77319
|
+
});
|
|
75733
77320
|
this.snapshotTimer = setInterval(() => this.emitResourcesSnapshot(), METRICS_SNAPSHOT_INTERVAL_MS);
|
|
75734
|
-
this.
|
|
77321
|
+
this.startProcessSnapshotTimer();
|
|
77322
|
+
this.ctx.addDisposer(this.ctx.eventBus.subscribe({ category: require_dist10.EventCategory.MetricsNodeProcessesSnapshot }, (event) => this.retainSnapshot(event.data.nodeId, event.data.timestamp, event.data.processes)));
|
|
75735
77323
|
return [{
|
|
75736
77324
|
capability: require_dist10.metricsProviderCapability,
|
|
75737
77325
|
provider: composed
|
|
75738
77326
|
}];
|
|
75739
77327
|
}
|
|
77328
|
+
/**
|
|
77329
|
+
* Retain one arriving snapshot in BOTH tiers.
|
|
77330
|
+
*
|
|
77331
|
+
* The ring is written FIRST and it is the gate: `record` refuses a timestamp
|
|
77332
|
+
* this node has already delivered, and only an accepted sample reaches the
|
|
77333
|
+
* table. That is what makes the append idempotent without a unique index
|
|
77334
|
+
* over two million rows — the composite key it replaces was measured at ~40
|
|
77335
|
+
* bytes per row (`load-series-store.ts`).
|
|
77336
|
+
*
|
|
77337
|
+
* The durable append is fire-and-forget: a storage stall must cost a gap in
|
|
77338
|
+
* the cold window, never a blocked event-bus handler. Every failure logs.
|
|
77339
|
+
*/
|
|
77340
|
+
retainSnapshot(nodeId, atMs, processes) {
|
|
77341
|
+
const retained = processes.map((p) => NativeMetricsAddon2.toRetained(p));
|
|
77342
|
+
if (!this.loadRing.record(nodeId, atMs, retained)) return;
|
|
77343
|
+
this.observedRowsByNode.set(nodeId, retained.length);
|
|
77344
|
+
const store = this.loadStore;
|
|
77345
|
+
if (store === null) return;
|
|
77346
|
+
store.append(nodeId, atMs, retained).then(() => store.prune([...this.observedRowsByNode.keys()], this.loadConfig)).catch((err) => {
|
|
77347
|
+
this.ctx.logger.warn("durable load series write failed", { meta: {
|
|
77348
|
+
nodeId,
|
|
77349
|
+
atMs,
|
|
77350
|
+
error: err instanceof Error ? err.message : String(err)
|
|
77351
|
+
} });
|
|
77352
|
+
});
|
|
77353
|
+
}
|
|
77354
|
+
/**
|
|
77355
|
+
* The ONE reader, over both tiers.
|
|
77356
|
+
*
|
|
77357
|
+
* Cold first, hot second, merged and deduped on `atMs` — see
|
|
77358
|
+
* `load-series-reader.ts`. The cold read is bounded in ROWS, because rows
|
|
77359
|
+
* are what a query costs, and the fold + reduction happen HERE rather than
|
|
77360
|
+
* in the browser: six hours at the 10 s cadence is 2 160 snapshots, and
|
|
77361
|
+
* shipping them to a page that will discard most of them is precisely the
|
|
77362
|
+
* cost this subsystem exists to avoid.
|
|
77363
|
+
*/
|
|
77364
|
+
async readLoadSeries(params) {
|
|
77365
|
+
const cadenceMs = this.loadConfig.cadenceSec * 1e3;
|
|
77366
|
+
const sinceMs = params.sinceMs ?? 0;
|
|
77367
|
+
const hotSamples = this.loadRing.read(params.forNodeId, params.sinceMs).samples.map((sample) => ({
|
|
77368
|
+
atMs: sample.atMs,
|
|
77369
|
+
processes: sample.processes
|
|
77370
|
+
}));
|
|
77371
|
+
const store = this.loadStore;
|
|
77372
|
+
const merged = mergeLoadSeries({
|
|
77373
|
+
cold: store === null ? [] : await store.read(params.forNodeId, sinceMs, this.loadConfig.maxRows),
|
|
77374
|
+
hot: hotSamples,
|
|
77375
|
+
cadenceMs,
|
|
77376
|
+
...params.maxPoints !== void 0 ? { maxPoints: params.maxPoints } : {}
|
|
77377
|
+
});
|
|
77378
|
+
return {
|
|
77379
|
+
nodeId: params.forNodeId,
|
|
77380
|
+
series: merged.series,
|
|
77381
|
+
bucketMs: merged.bucketMs,
|
|
77382
|
+
retainedSamples: merged.retainedSamples,
|
|
77383
|
+
oldestAtMs: merged.oldestAtMs,
|
|
77384
|
+
cadenceMs,
|
|
77385
|
+
durable: store !== null
|
|
77386
|
+
};
|
|
77387
|
+
}
|
|
77388
|
+
/**
|
|
77389
|
+
* Re-resolve the knobs and restate the fixed cadence.
|
|
77390
|
+
*
|
|
77391
|
+
* A REFUSED value (out of 5-60 s, or out of 1-72 h) leaves the previous
|
|
77392
|
+
* configuration in force and says so. Refused, never clamped: storing 10
|
|
77393
|
+
* when the operator typed 2 and reading 10 back is a knob and a readback
|
|
77394
|
+
* agreeing on a value nobody chose.
|
|
77395
|
+
*/
|
|
77396
|
+
applyLoadSeriesConfig() {
|
|
77397
|
+
try {
|
|
77398
|
+
const next = resolveLoadSeriesConfig({
|
|
77399
|
+
loadSeriesCadenceSec: this.config.loadSeriesCadenceSec,
|
|
77400
|
+
loadSeriesRetentionHours: this.config.loadSeriesRetentionHours
|
|
77401
|
+
});
|
|
77402
|
+
const changed = next.cadenceSec !== this.loadConfig.cadenceSec || next.retentionHours !== this.loadConfig.retentionHours;
|
|
77403
|
+
this.loadConfig = next;
|
|
77404
|
+
if (changed) {
|
|
77405
|
+
const projection = projectLoadSeriesCost({
|
|
77406
|
+
config: next,
|
|
77407
|
+
observedProcessRows: this.observedFleetRows()
|
|
77408
|
+
});
|
|
77409
|
+
this.ctx.logger.info("load series configuration applied", { meta: {
|
|
77410
|
+
nodeId: this.ctx.kernel.localNodeId ?? this.ctx.id,
|
|
77411
|
+
cadenceSec: next.cadenceSec,
|
|
77412
|
+
retentionHours: next.retentionHours,
|
|
77413
|
+
maxRows: next.maxRows,
|
|
77414
|
+
projectedRows: projection.intendedRows,
|
|
77415
|
+
projectedMib: Math.round(projection.estimatedBytes / 1048576 * 10) / 10,
|
|
77416
|
+
capBites: projection.capBites
|
|
77417
|
+
} });
|
|
77418
|
+
}
|
|
77419
|
+
} catch (err) {
|
|
77420
|
+
this.ctx.logger.warn("load series configuration REFUSED \u2014 keeping the previous values", { meta: {
|
|
77421
|
+
nodeId: this.ctx.kernel.localNodeId ?? this.ctx.id,
|
|
77422
|
+
cadenceSec: this.loadConfig.cadenceSec,
|
|
77423
|
+
retentionHours: this.loadConfig.retentionHours,
|
|
77424
|
+
error: err instanceof Error ? err.message : String(err)
|
|
77425
|
+
} });
|
|
77426
|
+
}
|
|
77427
|
+
}
|
|
77428
|
+
/** (Re)arm the fixed-cadence process-tree timer at the configured interval. */
|
|
77429
|
+
startProcessSnapshotTimer() {
|
|
77430
|
+
if (this.processSnapshotTimer) clearInterval(this.processSnapshotTimer);
|
|
77431
|
+
this.processSnapshotTimer = setInterval(() => this.emitProcessesSnapshot(), this.loadConfig.cadenceSec * 1e3);
|
|
77432
|
+
}
|
|
75740
77433
|
async onShutdown() {
|
|
75741
77434
|
if (this.snapshotTimer) {
|
|
75742
77435
|
clearInterval(this.snapshotTimer);
|
|
@@ -75793,10 +77486,14 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75793
77486
|
}
|
|
75794
77487
|
}
|
|
75795
77488
|
/**
|
|
75796
|
-
* Emit one `metrics.node-processes-snapshot` for this node.
|
|
75797
|
-
*
|
|
75798
|
-
*
|
|
75799
|
-
*
|
|
77489
|
+
* Emit one `metrics.node-processes-snapshot` for this node.
|
|
77490
|
+
*
|
|
77491
|
+
* Heavy — a full OS `ps -eo` scan plus a `$process.list` broker call — and
|
|
77492
|
+
* UNCONDITIONAL. The change-detection that used to guard it is gone: with a
|
|
77493
|
+
* fixed cadence a missing interval means exactly one thing, and that is the
|
|
77494
|
+
* property an operator investigating a spike is actually looking for.
|
|
77495
|
+
*
|
|
77496
|
+
* A failed scan emits nothing, which is the same signal: nobody reported.
|
|
75800
77497
|
*/
|
|
75801
77498
|
async emitProcessesSnapshot() {
|
|
75802
77499
|
const eventBus = this.ctx.eventBus;
|
|
@@ -75805,30 +77502,27 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75805
77502
|
const timestamp = Date.now();
|
|
75806
77503
|
try {
|
|
75807
77504
|
const processes = await this.listNodeProcesses();
|
|
75808
|
-
|
|
75809
|
-
|
|
75810
|
-
|
|
75811
|
-
|
|
75812
|
-
|
|
75813
|
-
|
|
75814
|
-
|
|
75815
|
-
|
|
75816
|
-
|
|
75817
|
-
|
|
75818
|
-
|
|
75819
|
-
|
|
75820
|
-
|
|
75821
|
-
|
|
75822
|
-
processes,
|
|
75823
|
-
timestamp
|
|
75824
|
-
}));
|
|
75825
|
-
}
|
|
75826
|
-
} catch {
|
|
77505
|
+
eventBus.emit(require_dist10.createEvent(require_dist10.EventCategory.MetricsNodeProcessesSnapshot, {
|
|
77506
|
+
type: "node",
|
|
77507
|
+
id: nodeId,
|
|
77508
|
+
nodeId
|
|
77509
|
+
}, {
|
|
77510
|
+
nodeId,
|
|
77511
|
+
processes,
|
|
77512
|
+
timestamp
|
|
77513
|
+
}));
|
|
77514
|
+
} catch (err) {
|
|
77515
|
+
this.ctx.logger.warn("process snapshot skipped \u2014 this interval will be missing", { meta: {
|
|
77516
|
+
nodeId,
|
|
77517
|
+
error: err instanceof Error ? err.message : String(err)
|
|
77518
|
+
} });
|
|
75827
77519
|
}
|
|
75828
77520
|
}
|
|
75829
77521
|
async onConfigChanged() {
|
|
75830
77522
|
this.provider?.stopSampling();
|
|
75831
77523
|
this.provider?.startSampling(this.config.samplingIntervalMs);
|
|
77524
|
+
this.applyLoadSeriesConfig();
|
|
77525
|
+
this.startProcessSnapshotTimer();
|
|
75832
77526
|
}
|
|
75833
77527
|
async listWorkerInstances() {
|
|
75834
77528
|
const broker = this.ctx.kernel.cluster?.broker;
|
|
@@ -75872,128 +77566,29 @@ var require_native_metrics_addon = __commonJS({
|
|
|
75872
77566
|
/**
|
|
75873
77567
|
* Walk the OS process table and classify each camstack-shaped process.
|
|
75874
77568
|
*
|
|
75875
|
-
* Classification
|
|
75876
|
-
*
|
|
75877
|
-
*
|
|
75878
|
-
*
|
|
75879
|
-
* - system — ancestry walk crosses a SUPERVISOR_BOUNDARY_RE match
|
|
75880
|
-
* (tsx-watch launcher, agent CLI, concurrently, vite,
|
|
75881
|
-
* npm exec wrapper). The process belongs to the dev
|
|
75882
|
-
* tree even if not in `$process.list`. NEVER killable.
|
|
75883
|
-
* - ghost — ancestry walk reaches `ppid=1` without crossing any
|
|
75884
|
-
* supervisor boundary AND the parent isn't visible in
|
|
75885
|
-
* `ps`. A truly orphaned camstack-shaped process. The
|
|
75886
|
-
* ONLY classification that's eligible for kill.
|
|
75887
|
-
*
|
|
75888
|
-
* Old pattern-only ghost detection produced false positives: every
|
|
75889
|
-
* monorepo-path process matched CAMSTACK_CMD_RE, ancestry walk
|
|
75890
|
-
* stopping at ppid=hub returned false-positive ghosts whenever a
|
|
75891
|
-
* concurrently sibling sat above hub. Ancestry-driven classification
|
|
75892
|
-
* fixes that.
|
|
77569
|
+
* Classification is IDENTITY-driven: `root` is this pid, `managed` is a pid
|
|
77570
|
+
* the kernel's `$process.list` names, and every other camstack-shaped
|
|
77571
|
+
* process is `system`. The rules — and why there is no longer an ancestry
|
|
77572
|
+
* walk behind them — live in `process-classification.ts`.
|
|
75893
77573
|
*/
|
|
75894
77574
|
async listNodeProcesses() {
|
|
75895
77575
|
const ps = await this.runPs();
|
|
75896
77576
|
if (ps.length === 0) return [];
|
|
75897
|
-
const
|
|
77577
|
+
const managed = /* @__PURE__ */ new Map();
|
|
75898
77578
|
const workers = await this.listWorkerInstances();
|
|
75899
|
-
for (const w of workers)
|
|
77579
|
+
for (const w of workers) managed.set(w.pid, {
|
|
75900
77580
|
addonId: w.addonId,
|
|
75901
77581
|
nodeId: w.nodeId
|
|
75902
77582
|
});
|
|
75903
|
-
const
|
|
75904
|
-
const
|
|
75905
|
-
|
|
75906
|
-
|
|
75907
|
-
|
|
75908
|
-
|
|
77583
|
+
const camstackPids = ps.filter((p) => CAMSTACK_CMD_RE.test(p.command)).map((p) => p.pid);
|
|
77584
|
+
const cpuSplits = await this.threadCpu.sample(camstackPids);
|
|
77585
|
+
return buildNodeProcesses({
|
|
77586
|
+
psRows: ps,
|
|
77587
|
+
selfPid: process.pid,
|
|
77588
|
+
selfNodeId: this.ctx.kernel.cluster?.broker?.nodeID ?? "hub",
|
|
77589
|
+
managed,
|
|
77590
|
+
cpuSplits
|
|
75909
77591
|
});
|
|
75910
|
-
const classifyByAncestry = (startPid) => {
|
|
75911
|
-
let cur = startPid;
|
|
75912
|
-
for (let depth = 0; depth < 32; depth++) {
|
|
75913
|
-
const node = psIndex.get(cur);
|
|
75914
|
-
if (!node) return "ghost";
|
|
75915
|
-
if (cur === selfPid) return "system";
|
|
75916
|
-
if (SUPERVISOR_BOUNDARY_RE.test(node.command)) return "system";
|
|
75917
|
-
if (node.ppid === 1) return "ghost";
|
|
75918
|
-
if (node.ppid === selfPid) return "system";
|
|
75919
|
-
cur = node.ppid;
|
|
75920
|
-
}
|
|
75921
|
-
return "system";
|
|
75922
|
-
};
|
|
75923
|
-
const out = [];
|
|
75924
|
-
for (const p of ps) {
|
|
75925
|
-
if (!CAMSTACK_CMD_RE.test(p.command)) continue;
|
|
75926
|
-
const managed = managedPids.get(p.pid);
|
|
75927
|
-
let classification;
|
|
75928
|
-
if (p.pid === selfPid) classification = "root";
|
|
75929
|
-
else if (managed) classification = "managed";
|
|
75930
|
-
else classification = classifyByAncestry(p.pid);
|
|
75931
|
-
const orphaned = classification === "ghost";
|
|
75932
|
-
out.push({
|
|
75933
|
-
pid: p.pid,
|
|
75934
|
-
ppid: p.ppid,
|
|
75935
|
-
pgid: p.pgid,
|
|
75936
|
-
classification,
|
|
75937
|
-
addonId: managed?.addonId ?? null,
|
|
75938
|
-
nodeId: managed?.nodeId ?? (p.pid === selfPid ? hubNodeId : null),
|
|
75939
|
-
command: p.command,
|
|
75940
|
-
cpuPercent: p.cpuPercent,
|
|
75941
|
-
memoryRssBytes: p.memoryRssBytes,
|
|
75942
|
-
uptimeSec: p.uptimeSec,
|
|
75943
|
-
orphaned
|
|
75944
|
-
});
|
|
75945
|
-
}
|
|
75946
|
-
return out;
|
|
75947
|
-
}
|
|
75948
|
-
/**
|
|
75949
|
-
* Send SIGTERM / SIGKILL to a pid. Refuses pids that don't appear in
|
|
75950
|
-
* `listNodeProcesses()` to prevent arbitrary system kills — a dedicated
|
|
75951
|
-
* admin-path for resurrected zombies, not a generic shell replacement.
|
|
75952
|
-
*
|
|
75953
|
-
* `root`-classified pids (the running launcher / agent CLI / hub itself)
|
|
75954
|
-
* are also refused: killing them tears down the whole node and the
|
|
75955
|
-
* operator's intent is almost always to nuke a leaked child, not the
|
|
75956
|
-
* supervisor that keeps the rest alive. Process restart goes through
|
|
75957
|
-
* the dedicated `$process.restart` action, not this kill API.
|
|
75958
|
-
*/
|
|
75959
|
-
async killProcess(input) {
|
|
75960
|
-
const match = (await this.listNodeProcesses()).find((p) => p.pid === input.pid);
|
|
75961
|
-
if (!match) return {
|
|
75962
|
-
success: false,
|
|
75963
|
-
reason: "pid not in node process table"
|
|
75964
|
-
};
|
|
75965
|
-
if (match.classification === "root" || match.classification === "system") {
|
|
75966
|
-
this.ctx.logger.warn("Refused to kill protected process", { meta: {
|
|
75967
|
-
pid: input.pid,
|
|
75968
|
-
classification: match.classification,
|
|
75969
|
-
addonId: match.addonId,
|
|
75970
|
-
command: match.command
|
|
75971
|
-
} });
|
|
75972
|
-
return {
|
|
75973
|
-
success: false,
|
|
75974
|
-
reason: match.classification === "root" ? "cannot kill root (current node supervisor)" : "cannot kill system (intentional dev-tree ancestor \u2014 vite, concurrently, npm, etc.)"
|
|
75975
|
-
};
|
|
75976
|
-
}
|
|
75977
|
-
const signal = input.force ? "SIGKILL" : "SIGTERM";
|
|
75978
|
-
try {
|
|
75979
|
-
process.kill(input.pid, signal);
|
|
75980
|
-
this.ctx.logger.info("Killed node process", { meta: {
|
|
75981
|
-
pid: input.pid,
|
|
75982
|
-
signal,
|
|
75983
|
-
classification: match.classification,
|
|
75984
|
-
addonId: match.addonId
|
|
75985
|
-
} });
|
|
75986
|
-
return {
|
|
75987
|
-
success: true,
|
|
75988
|
-
signal
|
|
75989
|
-
};
|
|
75990
|
-
} catch (err) {
|
|
75991
|
-
return {
|
|
75992
|
-
success: false,
|
|
75993
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
75994
|
-
signal
|
|
75995
|
-
};
|
|
75996
|
-
}
|
|
75997
77592
|
}
|
|
75998
77593
|
/**
|
|
75999
77594
|
* Ask the addon's forked runner to write a V8 heap snapshot (SIGUSR2 → the
|
|
@@ -76070,7 +77665,29 @@ var require_native_metrics_addon = __commonJS({
|
|
|
76070
77665
|
return [];
|
|
76071
77666
|
}
|
|
76072
77667
|
}
|
|
77668
|
+
/**
|
|
77669
|
+
* The knobs live HERE, on the document this addon already owns.
|
|
77670
|
+
*
|
|
77671
|
+
* Fields on an existing document, never a method per knob —
|
|
77672
|
+
* `system.getLoggingSettings` / `setLoggingSettings` set that precedent.
|
|
77673
|
+
* They are deliberately NOT in the logging document: that one is about
|
|
77674
|
+
* levels and diagnostic windows, and two documents both claiming a knob is
|
|
77675
|
+
* how this repo has already shipped a switch nobody read.
|
|
77676
|
+
*
|
|
77677
|
+
* Cluster-wide, not per-node: every node must emit on the same cadence or
|
|
77678
|
+
* the fleet's series cannot be laid over each other, and the table is single
|
|
77679
|
+
* and hub-resident, so a per-node retention would be a promise nothing could
|
|
77680
|
+
* keep.
|
|
77681
|
+
*
|
|
77682
|
+
* The cost line is computed from OBSERVED numbers — the nodes and process
|
|
77683
|
+
* counts the cluster is actually reporting — so an operator raising the
|
|
77684
|
+
* retention sees what it costs BEFORE applying it, not afterwards.
|
|
77685
|
+
*/
|
|
76073
77686
|
globalSettingsSchema() {
|
|
77687
|
+
const projection = projectLoadSeriesCost({
|
|
77688
|
+
config: this.loadConfig,
|
|
77689
|
+
observedProcessRows: this.observedFleetRows()
|
|
77690
|
+
});
|
|
76074
77691
|
return this.schema({ sections: [{
|
|
76075
77692
|
id: "native-metrics-settings",
|
|
76076
77693
|
title: "System Metrics",
|
|
@@ -76085,6 +77702,47 @@ var require_native_metrics_addon = __commonJS({
|
|
|
76085
77702
|
default: 5e3,
|
|
76086
77703
|
unit: "ms"
|
|
76087
77704
|
})]
|
|
77705
|
+
}, {
|
|
77706
|
+
id: "native-metrics-load-series",
|
|
77707
|
+
title: "Load history",
|
|
77708
|
+
fields: [
|
|
77709
|
+
this.field({
|
|
77710
|
+
type: "number",
|
|
77711
|
+
key: "loadSeriesCadenceSec",
|
|
77712
|
+
label: "Sampling cadence",
|
|
77713
|
+
description: "How often every node reports its process tree. FIXED \u2014 a sample is emitted on every interval whether or not anything changed, so a gap in the chart means one thing only: nobody reported. Outside 5-60 s the value is refused.",
|
|
77714
|
+
min: 5,
|
|
77715
|
+
max: 60,
|
|
77716
|
+
step: 1,
|
|
77717
|
+
default: 10,
|
|
77718
|
+
unit: "s"
|
|
77719
|
+
}),
|
|
77720
|
+
this.field({
|
|
77721
|
+
type: "number",
|
|
77722
|
+
key: "loadSeriesRetentionHours",
|
|
77723
|
+
label: "Retention",
|
|
77724
|
+
description: "How long the load history is kept on disk (the hub database, on the NVMe cache \u2014 never the recordings disk). This is the INTENTION; the row cap below is the guarantee.",
|
|
77725
|
+
min: 1,
|
|
77726
|
+
max: 72,
|
|
77727
|
+
step: 1,
|
|
77728
|
+
default: 6,
|
|
77729
|
+
unit: "h"
|
|
77730
|
+
}),
|
|
77731
|
+
{
|
|
77732
|
+
type: "info",
|
|
77733
|
+
key: "load-series-cost",
|
|
77734
|
+
label: "What this configuration costs",
|
|
77735
|
+
content: describeLoadSeriesCost(projection),
|
|
77736
|
+
variant: projection.capBites ? "warning" : "info"
|
|
77737
|
+
},
|
|
77738
|
+
{
|
|
77739
|
+
type: "info",
|
|
77740
|
+
key: "load-series-cap",
|
|
77741
|
+
label: "Hard row cap",
|
|
77742
|
+
content: `The table never exceeds ${this.loadConfig.maxRows.toLocaleString("en-US")} rows. When it would, the OLDEST samples are evicted and a warning is logged \u2014 so raising the retention can never turn into an incident, only into a shorter effective window.`,
|
|
77743
|
+
variant: "info"
|
|
77744
|
+
}
|
|
77745
|
+
]
|
|
76088
77746
|
}] });
|
|
76089
77747
|
}
|
|
76090
77748
|
};
|
|
@@ -76117,7 +77775,7 @@ var require_filesystem_storage_addon = __commonJS({
|
|
|
76117
77775
|
[Symbol.toStringTag]: { value: "Module" }
|
|
76118
77776
|
});
|
|
76119
77777
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
76120
|
-
var require_dist10 =
|
|
77778
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
76121
77779
|
var node_crypto = __require("crypto");
|
|
76122
77780
|
var node_fs_promises = __require("fs/promises");
|
|
76123
77781
|
var node_path = __require("path");
|
|
@@ -77233,8 +78891,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
77233
78891
|
[Symbol.toStringTag]: { value: "Module" }
|
|
77234
78892
|
});
|
|
77235
78893
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
77236
|
-
var require_dist10 =
|
|
77237
|
-
var require_retired_settings_keys =
|
|
78894
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
78895
|
+
var require_retired_settings_keys = require_retired_settings_keys_Davtjo5p();
|
|
77238
78896
|
var node_crypto = __require("crypto");
|
|
77239
78897
|
var node_fs = __require("fs");
|
|
77240
78898
|
var node_module = __require("module");
|
|
@@ -77861,7 +79519,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
77861
79519
|
this.declaredCollections.set(collection, {
|
|
77862
79520
|
primaryKey: "id",
|
|
77863
79521
|
columns: /* @__PURE__ */ new Set(["data"]),
|
|
77864
|
-
booleanColumns: /* @__PURE__ */ new Set()
|
|
79522
|
+
booleanColumns: /* @__PURE__ */ new Set(),
|
|
79523
|
+
autoPrimaryKey: false
|
|
77865
79524
|
});
|
|
77866
79525
|
}
|
|
77867
79526
|
if (await this.isEmpty({ collection: "system-settings" })) await this.seedDefaults();
|
|
@@ -77877,7 +79536,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
77877
79536
|
const decl = {
|
|
77878
79537
|
primaryKey: "id",
|
|
77879
79538
|
columns: /* @__PURE__ */ new Set(["data"]),
|
|
77880
|
-
booleanColumns: /* @__PURE__ */ new Set()
|
|
79539
|
+
booleanColumns: /* @__PURE__ */ new Set(),
|
|
79540
|
+
autoPrimaryKey: false
|
|
77881
79541
|
};
|
|
77882
79542
|
this.declaredCollections.set(scoped, decl);
|
|
77883
79543
|
return decl;
|
|
@@ -77981,6 +79641,48 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
77981
79641
|
else for (const [k, v] of Object.entries(record.data)) if (decl.columns.has(k)) row[k] = this.serializeColumnValue(v);
|
|
77982
79642
|
await this.tableInsert(scoped, row);
|
|
77983
79643
|
}
|
|
79644
|
+
/**
|
|
79645
|
+
* Insert a batch in ONE transaction, on ONE prepared statement.
|
|
79646
|
+
*
|
|
79647
|
+
* The write-side twin of {@link deleteWhere}. Every row in the batch shares
|
|
79648
|
+
* a single column list, which is what makes one `prepare` legal: rows are
|
|
79649
|
+
* normalised to the collection's DECLARED column set, so a row that omits a
|
|
79650
|
+
* column binds `null` for it rather than producing a second statement shape.
|
|
79651
|
+
*
|
|
79652
|
+
* All or nothing — `better-sqlite3`'s `transaction()` rolls the whole batch
|
|
79653
|
+
* back on any throw. A caller writing one process sample gets one COMMIT,
|
|
79654
|
+
* which is the entire point: 76 rows every 10 s is 7.6 rows/s, and 7.6
|
|
79655
|
+
* separate commits per second on the connection that also serves every
|
|
79656
|
+
* cluster-wide configuration read is a constant load nobody asked for.
|
|
79657
|
+
*/
|
|
79658
|
+
async insertMany({ namespace, collection, records }) {
|
|
79659
|
+
if (records.length === 0) return { inserted: 0 };
|
|
79660
|
+
const scoped = this.scopedName(namespace, collection);
|
|
79661
|
+
const decl = this.requireDeclared(scoped);
|
|
79662
|
+
const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
|
|
79663
|
+
const dataColumns = [...decl.columns];
|
|
79664
|
+
const keys = decl.autoPrimaryKey ? dataColumns : [decl.primaryKey, ...dataColumns];
|
|
79665
|
+
const sql = `INSERT INTO "${scoped}" (${keys.map((k) => `"${k}"`).join(", ")}) VALUES (${keys.map(() => "?").join(", ")})`;
|
|
79666
|
+
const batch = records.map((record) => {
|
|
79667
|
+
const row = {};
|
|
79668
|
+
if (isKvShape) row["data"] = JSON.stringify(record.data);
|
|
79669
|
+
else for (const [k, v] of Object.entries(record.data)) if (decl.columns.has(k)) row[k] = this.serializeColumnValue(v);
|
|
79670
|
+
if (!decl.autoPrimaryKey) row[decl.primaryKey] = record.id || (0, node_crypto.randomUUID)();
|
|
79671
|
+
return keys.map((k) => row[k] ?? null);
|
|
79672
|
+
});
|
|
79673
|
+
const db = this.getDb();
|
|
79674
|
+
const stmt = db.prepare(sql);
|
|
79675
|
+
const run = db.transaction((rows) => {
|
|
79676
|
+
for (const row of rows) stmt.run(...row);
|
|
79677
|
+
});
|
|
79678
|
+
this.measured({
|
|
79679
|
+
op: "insertMany",
|
|
79680
|
+
collection: scoped,
|
|
79681
|
+
sql,
|
|
79682
|
+
params: batch[0] ?? []
|
|
79683
|
+
}, () => run(batch));
|
|
79684
|
+
return { inserted: batch.length };
|
|
79685
|
+
}
|
|
77984
79686
|
async update({ namespace, collection, id, data }) {
|
|
77985
79687
|
const scoped = this.scopedName(namespace, collection);
|
|
77986
79688
|
const decl = this.requireDeclared(scoped);
|
|
@@ -78655,7 +80357,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
78655
80357
|
this.declaredCollections.set(table, {
|
|
78656
80358
|
primaryKey,
|
|
78657
80359
|
columns: columnNames,
|
|
78658
|
-
booleanColumns
|
|
80360
|
+
booleanColumns,
|
|
80361
|
+
autoPrimaryKey: pkCol?.type === "INTEGER"
|
|
78659
80362
|
});
|
|
78660
80363
|
}
|
|
78661
80364
|
/** Serialise per-column values for SQL binding: objects → JSON, booleans → 0/1. */
|
|
@@ -79468,7 +81171,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
79468
81171
|
[Symbol.toStringTag]: { value: "Module" }
|
|
79469
81172
|
});
|
|
79470
81173
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
79471
|
-
var require_dist10 =
|
|
81174
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
79472
81175
|
var node_crypto = __require("crypto");
|
|
79473
81176
|
var node_fs_promises = __require("fs/promises");
|
|
79474
81177
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -80032,6 +81735,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
80032
81735
|
set: async (input) => (await engine()).set(input),
|
|
80033
81736
|
query: async (input) => (await engine()).query(input),
|
|
80034
81737
|
insert: async (input) => (await engine()).insert(input),
|
|
81738
|
+
insertMany: async (input) => (await engine()).insertMany(input),
|
|
80035
81739
|
update: async (input) => (await engine()).update(input),
|
|
80036
81740
|
delete: async (input) => (await engine()).delete(input),
|
|
80037
81741
|
deleteWhere: async (input) => (await engine()).deleteWhere(input),
|
|
@@ -81348,7 +83052,7 @@ var require_system_config_addon = __commonJS({
|
|
|
81348
83052
|
[Symbol.toStringTag]: { value: "Module" }
|
|
81349
83053
|
});
|
|
81350
83054
|
require_chunk_Cek0wNdY();
|
|
81351
|
-
var require_dist10 =
|
|
83055
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
81352
83056
|
var SECTION_TITLES = {
|
|
81353
83057
|
server: "Server",
|
|
81354
83058
|
auth: "Authentication"
|
|
@@ -81413,7 +83117,7 @@ var require_system_config_addon = __commonJS({
|
|
|
81413
83117
|
type: "text",
|
|
81414
83118
|
key: "tokenExpiry",
|
|
81415
83119
|
label: "Token Expiry",
|
|
81416
|
-
description: "
|
|
83120
|
+
description: "Lifetime of a session token, as a duration string (e.g. 24h, 7d, 30d). A bare number is rejected \u2014 it would be read as milliseconds. Applies to tokens issued from the next login or silent refresh; tokens already handed out keep the lifetime they were signed with.",
|
|
81417
83121
|
placeholder: "30d",
|
|
81418
83122
|
default: "30d"
|
|
81419
83123
|
}]
|
|
@@ -99409,7 +101113,7 @@ var require_winston_logging = __commonJS({
|
|
|
99409
101113
|
[Symbol.toStringTag]: { value: "Module" }
|
|
99410
101114
|
});
|
|
99411
101115
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
99412
|
-
var require_dist10 =
|
|
101116
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
99413
101117
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
99414
101118
|
var node_path = __require("path");
|
|
99415
101119
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -101185,9 +102889,9 @@ ${(0, node_fs.readFileSync)(ca.caCertPath, "utf-8").trimEnd()}
|
|
|
101185
102889
|
}
|
|
101186
102890
|
});
|
|
101187
102891
|
|
|
101188
|
-
// ../types/dist/event-category-
|
|
101189
|
-
var
|
|
101190
|
-
"../types/dist/event-category-
|
|
102892
|
+
// ../types/dist/event-category-BaEgqJNv.js
|
|
102893
|
+
var require_event_category_BaEgqJNv = __commonJS({
|
|
102894
|
+
"../types/dist/event-category-BaEgqJNv.js"(exports) {
|
|
101191
102895
|
"use strict";
|
|
101192
102896
|
var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
101193
102897
|
EventCategory2["SystemBoot"] = "system.boot";
|
|
@@ -101352,11 +103056,11 @@ var require_event_category_EY0GNjV9 = __commonJS({
|
|
|
101352
103056
|
}
|
|
101353
103057
|
});
|
|
101354
103058
|
|
|
101355
|
-
// ../types/dist/sleep-
|
|
101356
|
-
var
|
|
101357
|
-
"../types/dist/sleep-
|
|
103059
|
+
// ../types/dist/sleep-9d8tJRbO.js
|
|
103060
|
+
var require_sleep_9d8tJRbO = __commonJS({
|
|
103061
|
+
"../types/dist/sleep-9d8tJRbO.js"(exports) {
|
|
101358
103062
|
"use strict";
|
|
101359
|
-
var require_event_category =
|
|
103063
|
+
var require_event_category = require_event_category_BaEgqJNv();
|
|
101360
103064
|
var zod = require_zod();
|
|
101361
103065
|
var WELL_KNOWN_TABS = [
|
|
101362
103066
|
{
|
|
@@ -104907,8 +106611,8 @@ var require_addon = __commonJS({
|
|
|
104907
106611
|
"../types/dist/addon.js"(exports) {
|
|
104908
106612
|
"use strict";
|
|
104909
106613
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
104910
|
-
var require_event_category =
|
|
104911
|
-
var require_sleep =
|
|
106614
|
+
var require_event_category = require_event_category_BaEgqJNv();
|
|
106615
|
+
var require_sleep = require_sleep_9d8tJRbO();
|
|
104912
106616
|
var require_err_msg = require_err_msg_COpsHMw2();
|
|
104913
106617
|
var CAP_INPUT_DEFAULTS = Object.freeze({
|
|
104914
106618
|
"addons": { "getLogs": { "limit": 100 } },
|
|
@@ -105197,6 +106901,7 @@ var require_addon = __commonJS({
|
|
|
105197
106901
|
"listProfiles",
|
|
105198
106902
|
"listRuntimeNodes"
|
|
105199
106903
|
],
|
|
106904
|
+
"load-contribution": ["list"],
|
|
105200
106905
|
"log-channels": ["list"],
|
|
105201
106906
|
"log-destination": ["query"],
|
|
105202
106907
|
"login-method": ["getLoginMethods"],
|
|
@@ -111785,12 +113490,12 @@ var require_dist2 = __commonJS({
|
|
|
111785
113490
|
}
|
|
111786
113491
|
});
|
|
111787
113492
|
|
|
111788
|
-
// ../system/dist/manifest-python-deps-
|
|
111789
|
-
var
|
|
111790
|
-
"../system/dist/manifest-python-deps-
|
|
113493
|
+
// ../system/dist/manifest-python-deps-BV_Cy99l.js
|
|
113494
|
+
var require_manifest_python_deps_BV_Cy99l = __commonJS({
|
|
113495
|
+
"../system/dist/manifest-python-deps-BV_Cy99l.js"(exports) {
|
|
111791
113496
|
"use strict";
|
|
111792
113497
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
111793
|
-
|
|
113498
|
+
require_dist_B_mBrEz9();
|
|
111794
113499
|
var node_crypto = __require("crypto");
|
|
111795
113500
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
111796
113501
|
var _camstack_types_node = require_node();
|
|
@@ -122905,7 +124610,7 @@ var require_dist3 = __commonJS({
|
|
|
122905
124610
|
"use strict";
|
|
122906
124611
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
122907
124612
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
122908
|
-
var require_dist10 =
|
|
124613
|
+
var require_dist10 = require_dist_B_mBrEz9();
|
|
122909
124614
|
var require_builtins_alerts_alerts_addon = require_alerts_addon();
|
|
122910
124615
|
require_alerts();
|
|
122911
124616
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
@@ -122931,7 +124636,7 @@ var require_dist3 = __commonJS({
|
|
|
122931
124636
|
var require_builtins_winston_logging_index = require_winston_logging();
|
|
122932
124637
|
var require_file_data_plane = require_file_data_plane_DO8KbxCe();
|
|
122933
124638
|
var require_tls$1 = require_tls_u8QCJCFE();
|
|
122934
|
-
var require_manifest_python_deps =
|
|
124639
|
+
var require_manifest_python_deps = require_manifest_python_deps_BV_Cy99l();
|
|
122935
124640
|
var require_resource_monitor = require_resource_monitor_CdnzxBLP();
|
|
122936
124641
|
var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
|
|
122937
124642
|
var zod = require_zod();
|
|
@@ -203626,7 +205331,7 @@ var require_enums = __commonJS({
|
|
|
203626
205331
|
"../types/dist/enums.js"(exports) {
|
|
203627
205332
|
"use strict";
|
|
203628
205333
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
203629
|
-
var require_event_category =
|
|
205334
|
+
var require_event_category = require_event_category_BaEgqJNv();
|
|
203630
205335
|
var EventSourceType = /* @__PURE__ */ (function(EventSourceType2) {
|
|
203631
205336
|
EventSourceType2["Addon"] = "addon";
|
|
203632
205337
|
EventSourceType2["Core"] = "core";
|
|
@@ -203646,8 +205351,8 @@ var require_dist4 = __commonJS({
|
|
|
203646
205351
|
"../types/dist/index.js"(exports) {
|
|
203647
205352
|
"use strict";
|
|
203648
205353
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
203649
|
-
var require_event_category =
|
|
203650
|
-
var require_sleep =
|
|
205354
|
+
var require_event_category = require_event_category_BaEgqJNv();
|
|
205355
|
+
var require_sleep = require_sleep_9d8tJRbO();
|
|
203651
205356
|
var require_canonical_hash = require_canonical_hash_DNV8S5ET();
|
|
203652
205357
|
var require_enums2 = require_enums();
|
|
203653
205358
|
var require_err_msg = require_err_msg_COpsHMw2();
|
|
@@ -205413,6 +207118,116 @@ var require_dist4 = __commonJS({
|
|
|
205413
207118
|
}
|
|
205414
207119
|
return bestAbove?.entry ?? bestBelow?.entry;
|
|
205415
207120
|
}
|
|
207121
|
+
var UNATTRIBUTED_BUCKET_KEY = "__unattributed__";
|
|
207122
|
+
var ROOT_BUCKET_KEY = "__root__";
|
|
207123
|
+
function bucketFor(row) {
|
|
207124
|
+
if (row.addonId !== null) return {
|
|
207125
|
+
key: row.addonId,
|
|
207126
|
+
kind: "addon"
|
|
207127
|
+
};
|
|
207128
|
+
if (row.classification === "root") return {
|
|
207129
|
+
key: ROOT_BUCKET_KEY,
|
|
207130
|
+
kind: "root"
|
|
207131
|
+
};
|
|
207132
|
+
return {
|
|
207133
|
+
key: UNATTRIBUTED_BUCKET_KEY,
|
|
207134
|
+
kind: "unattributed"
|
|
207135
|
+
};
|
|
207136
|
+
}
|
|
207137
|
+
function deci(value) {
|
|
207138
|
+
return Math.round(value * 10) / 10;
|
|
207139
|
+
}
|
|
207140
|
+
function foldSnapshotByFunction(rows, atMs) {
|
|
207141
|
+
const acc = /* @__PURE__ */ new Map();
|
|
207142
|
+
for (const row of rows) {
|
|
207143
|
+
const { key, kind } = bucketFor(row);
|
|
207144
|
+
const cur = acc.get(key) ?? {
|
|
207145
|
+
kind,
|
|
207146
|
+
main: 0,
|
|
207147
|
+
gc: 0,
|
|
207148
|
+
lifetime: 0,
|
|
207149
|
+
memory: 0,
|
|
207150
|
+
count: 0,
|
|
207151
|
+
splitKnown: true
|
|
207152
|
+
};
|
|
207153
|
+
const known = row.cpuMainPercent !== null && row.cpuGcPercent !== null;
|
|
207154
|
+
acc.set(key, {
|
|
207155
|
+
kind: cur.kind,
|
|
207156
|
+
main: cur.main + (row.cpuMainPercent ?? 0),
|
|
207157
|
+
gc: cur.gc + (row.cpuGcPercent ?? 0),
|
|
207158
|
+
lifetime: cur.lifetime + row.cpuPercent,
|
|
207159
|
+
memory: cur.memory + row.memoryRssBytes,
|
|
207160
|
+
count: cur.count + 1,
|
|
207161
|
+
splitKnown: cur.splitKnown && known
|
|
207162
|
+
});
|
|
207163
|
+
}
|
|
207164
|
+
return [...acc.entries()].map(([key, a]) => {
|
|
207165
|
+
const main = a.splitKnown ? deci(a.main) : null;
|
|
207166
|
+
const gc = a.splitKnown ? deci(a.gc) : null;
|
|
207167
|
+
const lifetime = deci(a.lifetime);
|
|
207168
|
+
return {
|
|
207169
|
+
key,
|
|
207170
|
+
kind: a.kind,
|
|
207171
|
+
point: {
|
|
207172
|
+
atMs,
|
|
207173
|
+
samples: 1,
|
|
207174
|
+
cpuMainPercent: main,
|
|
207175
|
+
cpuMainPercentMin: main,
|
|
207176
|
+
cpuGcPercent: gc,
|
|
207177
|
+
cpuGcPercentMin: gc,
|
|
207178
|
+
cpuLifetimePercent: lifetime,
|
|
207179
|
+
cpuLifetimePercentMin: lifetime,
|
|
207180
|
+
memoryRssBytes: a.memory,
|
|
207181
|
+
memoryRssBytesMin: a.memory,
|
|
207182
|
+
processCount: a.count,
|
|
207183
|
+
processCountMin: a.count
|
|
207184
|
+
}
|
|
207185
|
+
};
|
|
207186
|
+
});
|
|
207187
|
+
}
|
|
207188
|
+
function minNullable(a, b) {
|
|
207189
|
+
if (a === null || b === null) return null;
|
|
207190
|
+
return a < b ? a : b;
|
|
207191
|
+
}
|
|
207192
|
+
function maxNullable(a, b) {
|
|
207193
|
+
if (a === null || b === null) return null;
|
|
207194
|
+
return a > b ? a : b;
|
|
207195
|
+
}
|
|
207196
|
+
function mergePoints(held, next, atMs) {
|
|
207197
|
+
return {
|
|
207198
|
+
atMs,
|
|
207199
|
+
samples: held.samples + next.samples,
|
|
207200
|
+
cpuMainPercent: maxNullable(held.cpuMainPercent, next.cpuMainPercent),
|
|
207201
|
+
cpuMainPercentMin: minNullable(held.cpuMainPercentMin, next.cpuMainPercentMin),
|
|
207202
|
+
cpuGcPercent: maxNullable(held.cpuGcPercent, next.cpuGcPercent),
|
|
207203
|
+
cpuGcPercentMin: minNullable(held.cpuGcPercentMin, next.cpuGcPercentMin),
|
|
207204
|
+
cpuLifetimePercent: Math.max(held.cpuLifetimePercent, next.cpuLifetimePercent),
|
|
207205
|
+
cpuLifetimePercentMin: Math.min(held.cpuLifetimePercentMin, next.cpuLifetimePercentMin),
|
|
207206
|
+
memoryRssBytes: Math.max(held.memoryRssBytes, next.memoryRssBytes),
|
|
207207
|
+
memoryRssBytesMin: Math.min(held.memoryRssBytesMin, next.memoryRssBytesMin),
|
|
207208
|
+
processCount: Math.max(held.processCount, next.processCount),
|
|
207209
|
+
processCountMin: Math.min(held.processCountMin, next.processCountMin)
|
|
207210
|
+
};
|
|
207211
|
+
}
|
|
207212
|
+
function resolveBucketMs(spanMs, cadenceMs, maxPoints) {
|
|
207213
|
+
if (maxPoints <= 0 || cadenceMs <= 0 || spanMs <= 0) return Math.max(cadenceMs, 1);
|
|
207214
|
+
const wanted = spanMs / maxPoints;
|
|
207215
|
+
if (wanted <= cadenceMs) return cadenceMs;
|
|
207216
|
+
return Math.ceil(wanted / cadenceMs) * cadenceMs;
|
|
207217
|
+
}
|
|
207218
|
+
function reducePoints(points, bucketMs, origin) {
|
|
207219
|
+
if (bucketMs <= 0 || points.length === 0) return points;
|
|
207220
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
207221
|
+
for (const point of points) {
|
|
207222
|
+
const start = origin + Math.floor((point.atMs - origin) / bucketMs) * bucketMs;
|
|
207223
|
+
const held = buckets.get(start);
|
|
207224
|
+
buckets.set(start, held === void 0 ? {
|
|
207225
|
+
...point,
|
|
207226
|
+
atMs: start
|
|
207227
|
+
} : mergePoints(held, point, start));
|
|
207228
|
+
}
|
|
207229
|
+
return [...buckets.values()].toSorted((a, b) => a.atMs - b.atMs);
|
|
207230
|
+
}
|
|
205416
207231
|
var FORMAT_KEYS = [
|
|
205417
207232
|
"onnx",
|
|
205418
207233
|
"coreml",
|
|
@@ -209336,6 +211151,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
209336
211151
|
id: zod.z.string(),
|
|
209337
211152
|
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
209338
211153
|
});
|
|
211154
|
+
var BulkRecordSchema = zod.z.object({
|
|
211155
|
+
id: zod.z.string().optional(),
|
|
211156
|
+
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
211157
|
+
});
|
|
209339
211158
|
var CollectionColumnSchema = zod.z.object({
|
|
209340
211159
|
name: zod.z.string(),
|
|
209341
211160
|
type: zod.z.enum([
|
|
@@ -209410,6 +211229,34 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
209410
211229
|
collection: zod.z.string(),
|
|
209411
211230
|
record: SettingsRecordSchema
|
|
209412
211231
|
}), zod.z.void(), { kind: "mutation" }),
|
|
211232
|
+
/**
|
|
211233
|
+
* Insert MANY records in ONE transaction, returning how many landed.
|
|
211234
|
+
*
|
|
211235
|
+
* The write-side twin of {@link deleteWhere}, and it exists for the same
|
|
211236
|
+
* reason: without it, appending a batch is N round trips and N COMMITs on
|
|
211237
|
+
* the single shared connection that also serves every cluster-wide
|
|
211238
|
+
* configuration read. The durable load series writes one process row per
|
|
211239
|
+
* process per sample — 76 rows every 10 s on the live fleet — and the
|
|
211240
|
+
* operator's rule for it is *one transaction per sample, never one per
|
|
211241
|
+
* row*. `insert` cannot express that; nothing else could.
|
|
211242
|
+
*
|
|
211243
|
+
* **All or nothing.** A batch that fails on its fifth row leaves none of
|
|
211244
|
+
* the five behind. A half-written sample is worse than a missing one: the
|
|
211245
|
+
* missing one reads as "nobody reported", which is true, while the half
|
|
211246
|
+
* one reads as "these were the only processes running", which is not.
|
|
211247
|
+
*
|
|
211248
|
+
* `id` is OPTIONAL per record, and that is the difference from
|
|
211249
|
+
* {@link insert}. A collection whose primary key is an `INTEGER` rowid
|
|
211250
|
+
* alias has no id to supply — SQLite assigns it, for free, and inventing a
|
|
211251
|
+
* `randomUUID()` for such a column would write a 36-character string into
|
|
211252
|
+
* an integer key. Omitted on a TEXT key, a uuid is generated exactly as
|
|
211253
|
+
* `insert` does.
|
|
211254
|
+
*/
|
|
211255
|
+
insertMany: require_sleep.method(zod.z.object({
|
|
211256
|
+
namespace: zod.z.string().optional(),
|
|
211257
|
+
collection: zod.z.string(),
|
|
211258
|
+
records: zod.z.array(BulkRecordSchema).readonly()
|
|
211259
|
+
}), zod.z.object({ inserted: zod.z.number().int() }), { kind: "mutation" }),
|
|
209413
211260
|
/** Update an existing record by ID. */
|
|
209414
211261
|
update: require_sleep.method(zod.z.object({
|
|
209415
211262
|
namespace: zod.z.string().optional(),
|
|
@@ -209599,6 +211446,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
209599
211446
|
kind: "mutation",
|
|
209600
211447
|
auth: "admin"
|
|
209601
211448
|
}),
|
|
211449
|
+
/** Insert many records in ONE transaction. All or nothing. */
|
|
211450
|
+
insertMany: require_sleep.method(zod.z.object({
|
|
211451
|
+
namespace: zod.z.string().optional(),
|
|
211452
|
+
collection: zod.z.string(),
|
|
211453
|
+
records: zod.z.array(BulkRecordSchema).readonly()
|
|
211454
|
+
}), zod.z.object({ inserted: zod.z.number().int() }), {
|
|
211455
|
+
kind: "mutation",
|
|
211456
|
+
auth: "admin"
|
|
211457
|
+
}),
|
|
209602
211458
|
/** Update an existing record by ID. */
|
|
209603
211459
|
update: require_sleep.method(zod.z.object({
|
|
209604
211460
|
namespace: zod.z.string().optional(),
|
|
@@ -211997,6 +213853,85 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
211997
213853
|
/** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
|
|
211998
213854
|
mount: { kind: "skip" }
|
|
211999
213855
|
};
|
|
213856
|
+
var LOAD_CONTRIBUTION_ROLES = [
|
|
213857
|
+
"decode",
|
|
213858
|
+
"transcode",
|
|
213859
|
+
"recording",
|
|
213860
|
+
"streaming",
|
|
213861
|
+
"detection"
|
|
213862
|
+
];
|
|
213863
|
+
var LOAD_CONTRIBUTION_ATTRIBUTIONS = [
|
|
213864
|
+
"measured",
|
|
213865
|
+
"accounted",
|
|
213866
|
+
"unattributable"
|
|
213867
|
+
];
|
|
213868
|
+
var LoadContributionSchema = zod.z.object({
|
|
213869
|
+
role: zod.z.enum(LOAD_CONTRIBUTION_ROLES),
|
|
213870
|
+
/**
|
|
213871
|
+
* The NUMERIC device id — the same value every log line carries as
|
|
213872
|
+
* `tags.deviceId`. `null` means this cost genuinely belongs to no single
|
|
213873
|
+
* camera (a shared pool), NOT that the contributor forgot to look it up: a
|
|
213874
|
+
* contributor that cannot name its camera must not emit the entry at all,
|
|
213875
|
+
* because an unnamed per-camera entry is indistinguishable from a shared one
|
|
213876
|
+
* and would quietly turn one camera's cost into everybody's.
|
|
213877
|
+
*/
|
|
213878
|
+
deviceId: zod.z.number().int().positive().nullable(),
|
|
213879
|
+
attribution: zod.z.enum(LOAD_CONTRIBUTION_ATTRIBUTIONS),
|
|
213880
|
+
/**
|
|
213881
|
+
* What ONE entry is, in the contributor's own words — `615/high`,
|
|
213882
|
+
* `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
|
|
213883
|
+
* family and inventing a common one would lose the only information that
|
|
213884
|
+
* makes two entries for the same camera distinguishable.
|
|
213885
|
+
*/
|
|
213886
|
+
unit: zod.z.string(),
|
|
213887
|
+
/**
|
|
213888
|
+
* The OS process this cost lives in, when there is one. Present so a
|
|
213889
|
+
* consumer can (a) tell two generations of the same unit apart across a
|
|
213890
|
+
* restart, and (b) subtract claimed processes from the node's process
|
|
213891
|
+
* snapshot to see what NOBODY claimed. Absent for an entry that owns no
|
|
213892
|
+
* process of its own.
|
|
213893
|
+
*/
|
|
213894
|
+
pid: zod.z.number().int().positive().optional(),
|
|
213895
|
+
/**
|
|
213896
|
+
* When this generation started. The pid's incarnation marker: a consumer
|
|
213897
|
+
* differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
|
|
213898
|
+
* window when this changes, because the counter restarted from zero in a new
|
|
213899
|
+
* process.
|
|
213900
|
+
*/
|
|
213901
|
+
startedAtMs: zod.z.number().optional(),
|
|
213902
|
+
/**
|
|
213903
|
+
* CUMULATIVE CPU seconds this unit has consumed since it started — user +
|
|
213904
|
+
* system, read from the child's own `/proc/<pid>/stat` at the moment the
|
|
213905
|
+
* contribution is asked for.
|
|
213906
|
+
*
|
|
213907
|
+
* Cumulative and not a rate on purpose: a rate needs a window, a window
|
|
213908
|
+
* needs a sampler, and a new per-node sampler is the defect half of
|
|
213909
|
+
* `docs/architecture/load-ledger.md` documents. A counter can be differenced
|
|
213910
|
+
* by whoever already keeps a history; a rate cannot be un-averaged.
|
|
213911
|
+
*
|
|
213912
|
+
* Absent — never zero — on a node with no `/proc`, on a read failure, and on
|
|
213913
|
+
* an entry with no process.
|
|
213914
|
+
*/
|
|
213915
|
+
cpuSeconds: zod.z.number().optional(),
|
|
213916
|
+
/** Resident bytes of this unit's process, same source and same rules. */
|
|
213917
|
+
rssBytes: zod.z.number().optional()
|
|
213918
|
+
});
|
|
213919
|
+
var loadContributionCapability = {
|
|
213920
|
+
name: "load-contribution",
|
|
213921
|
+
scope: "system",
|
|
213922
|
+
mode: "collection",
|
|
213923
|
+
internal: true,
|
|
213924
|
+
methods: {
|
|
213925
|
+
/**
|
|
213926
|
+
* This addon's own cost entries, computed live from state it already
|
|
213927
|
+
* holds. Inert: no persistence, no sampling, no timer. It is answered on
|
|
213928
|
+
* whatever beat the caller already has.
|
|
213929
|
+
*/
|
|
213930
|
+
list: require_sleep.method(zod.z.void(), zod.z.array(LoadContributionSchema).readonly())
|
|
213931
|
+
},
|
|
213932
|
+
/** In-process only — enumerated through `addons.listCapabilityProviders`. */
|
|
213933
|
+
mount: { kind: "skip" }
|
|
213934
|
+
};
|
|
212000
213935
|
var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
|
|
212001
213936
|
var RedirectLoginMethodSchema = zod.z.object({
|
|
212002
213937
|
kind: zod.z.literal("redirect"),
|
|
@@ -212168,8 +214103,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
212168
214103
|
classification: zod.z.enum([
|
|
212169
214104
|
"root",
|
|
212170
214105
|
"managed",
|
|
212171
|
-
"system"
|
|
212172
|
-
"ghost"
|
|
214106
|
+
"system"
|
|
212173
214107
|
]),
|
|
212174
214108
|
/** `$process` addon binding when `managed`, else null. */
|
|
212175
214109
|
addonId: zod.z.string().nullable(),
|
|
@@ -212177,22 +214111,39 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
212177
214111
|
nodeId: zod.z.string().nullable(),
|
|
212178
214112
|
/** Truncated command line. */
|
|
212179
214113
|
command: zod.z.string(),
|
|
214114
|
+
/**
|
|
214115
|
+
* `ps pcpu` — CPU averaged over the process's WHOLE LIFETIME, not a rate.
|
|
214116
|
+
* On a runner up for days it barely moves. Fine as a column, useless as a
|
|
214117
|
+
* series: use `cpuMainPercent + cpuGcPercent` for anything time-varying.
|
|
214118
|
+
*/
|
|
212180
214119
|
cpuPercent: zod.z.number(),
|
|
212181
214120
|
memoryRssBytes: zod.z.number(),
|
|
214121
|
+
/**
|
|
214122
|
+
* Instantaneous CPU% of the process's own threads over the last
|
|
214123
|
+
* process-snapshot window, from a `/proc/<pid>/task/*` tick delta.
|
|
214124
|
+
*
|
|
214125
|
+
* `null` = UNKNOWN, never zero: no previous sample yet (first tick after
|
|
214126
|
+
* boot), the pid was recycled, or this node is not Linux.
|
|
214127
|
+
*/
|
|
214128
|
+
cpuMainPercent: zod.z.number().nullable(),
|
|
214129
|
+
/**
|
|
214130
|
+
* Instantaneous CPU% of V8's `V8Worker` platform pool over the same window.
|
|
214131
|
+
*
|
|
214132
|
+
* This is the number that rewrote the 2026-08-27 diagnosis — hub-main 73%,
|
|
214133
|
+
* `stream-broker` 61% (`docs/architecture/load-ledger.md`). A CPU chart that
|
|
214134
|
+
* does not separate it from `cpuMainPercent` shows "busy" where the truth is
|
|
214135
|
+
* "allocating too much".
|
|
214136
|
+
*
|
|
214137
|
+
* Concurrent GC is the dominant tenant of that pool but not the only one
|
|
214138
|
+
* (background compilation runs there too), so it is reported as
|
|
214139
|
+
* "GC / V8 helpers" rather than as pure collection time. `null` has the same
|
|
214140
|
+
* meaning as on `cpuMainPercent`.
|
|
214141
|
+
*/
|
|
214142
|
+
cpuGcPercent: zod.z.number().nullable(),
|
|
214143
|
+
/** Threads seen in the tick scan. `null` under the same conditions. */
|
|
214144
|
+
threadCount: zod.z.number().nullable(),
|
|
212182
214145
|
/** Wall-clock uptime (seconds). Parsed from `ps etime`. */
|
|
212183
|
-
uptimeSec: zod.z.number()
|
|
212184
|
-
/** True when ancestor walk reaches `ppid=1` (reparented to init/launchd). */
|
|
212185
|
-
orphaned: zod.z.boolean()
|
|
212186
|
-
});
|
|
212187
|
-
var KillProcessInputSchema = zod.z.object({
|
|
212188
|
-
pid: zod.z.number(),
|
|
212189
|
-
/** Force = SIGKILL. Default is SIGTERM. */
|
|
212190
|
-
force: zod.z.boolean().optional()
|
|
212191
|
-
});
|
|
212192
|
-
var KillProcessResultSchema = zod.z.object({
|
|
212193
|
-
success: zod.z.boolean(),
|
|
212194
|
-
reason: zod.z.string().optional(),
|
|
212195
|
-
signal: zod.z.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
214146
|
+
uptimeSec: zod.z.number()
|
|
212196
214147
|
});
|
|
212197
214148
|
var DumpHeapSnapshotInputSchema = zod.z.object({
|
|
212198
214149
|
/** The addon whose runner should dump a heap snapshot. */
|
|
@@ -212206,6 +214157,89 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
212206
214157
|
pid: zod.z.number().optional(),
|
|
212207
214158
|
reason: zod.z.string().optional()
|
|
212208
214159
|
});
|
|
214160
|
+
var LoadPointSchema = zod.z.object({
|
|
214161
|
+
/** Bucket START, or the snapshot's own timestamp when unreduced. */
|
|
214162
|
+
atMs: zod.z.number(),
|
|
214163
|
+
/** Raw snapshots in this bucket. Never 0 — AN EMPTY BUCKET IS ABSENT. */
|
|
214164
|
+
samples: zod.z.number().int(),
|
|
214165
|
+
/**
|
|
214166
|
+
* `null` = UNKNOWN and it PROPAGATES: a bucket is null unless every process
|
|
214167
|
+
* of every snapshot in it reported a thread split. A partial sum is a
|
|
214168
|
+
* smaller number that looks exactly as real as a complete one.
|
|
214169
|
+
*/
|
|
214170
|
+
cpuMainPercent: zod.z.number().nullable(),
|
|
214171
|
+
cpuMainPercentMin: zod.z.number().nullable(),
|
|
214172
|
+
cpuGcPercent: zod.z.number().nullable(),
|
|
214173
|
+
cpuGcPercentMin: zod.z.number().nullable(),
|
|
214174
|
+
/** Lifetime-average CPU%, summed. Always known — and never a rate. */
|
|
214175
|
+
cpuLifetimePercent: zod.z.number(),
|
|
214176
|
+
cpuLifetimePercentMin: zod.z.number(),
|
|
214177
|
+
memoryRssBytes: zod.z.number(),
|
|
214178
|
+
memoryRssBytesMin: zod.z.number(),
|
|
214179
|
+
processCount: zod.z.number().int(),
|
|
214180
|
+
processCountMin: zod.z.number().int()
|
|
214181
|
+
});
|
|
214182
|
+
var LoadFunctionSeriesSchema = zod.z.object({
|
|
214183
|
+
key: zod.z.string(),
|
|
214184
|
+
kind: zod.z.enum([
|
|
214185
|
+
"addon",
|
|
214186
|
+
"root",
|
|
214187
|
+
"unattributed"
|
|
214188
|
+
]),
|
|
214189
|
+
/** Oldest-first. A missing interval is MISSING — never zero-filled. */
|
|
214190
|
+
points: zod.z.array(LoadPointSchema).readonly()
|
|
214191
|
+
});
|
|
214192
|
+
var NodeLoadSeriesSchema = zod.z.object({
|
|
214193
|
+
nodeId: zod.z.string(),
|
|
214194
|
+
/** One entry per function seen in the window, heaviest-first. */
|
|
214195
|
+
series: zod.z.array(LoadFunctionSeriesSchema).readonly(),
|
|
214196
|
+
/**
|
|
214197
|
+
* Width of one returned bucket, in ms. Equals the sampling cadence when no
|
|
214198
|
+
* reduction was needed — so a caller can always say what one point covers
|
|
214199
|
+
* without having to know whether it was reduced.
|
|
214200
|
+
*/
|
|
214201
|
+
bucketMs: zod.z.number(),
|
|
214202
|
+
/** Raw snapshots that went into this answer, across both tiers. */
|
|
214203
|
+
retainedSamples: zod.z.number(),
|
|
214204
|
+
/** Oldest snapshot represented, or `null` when nothing is retained. */
|
|
214205
|
+
oldestAtMs: zod.z.number().nullable(),
|
|
214206
|
+
/** The fixed sampling cadence in force on the cluster, in ms. */
|
|
214207
|
+
cadenceMs: zod.z.number(),
|
|
214208
|
+
/**
|
|
214209
|
+
* Did the DURABLE tier contribute? `false` means the answer is the hot ring
|
|
214210
|
+
* alone — an agent (which holds no table), or a store that refused.
|
|
214211
|
+
* Reported because "the last hour" and "the last six hours" are different
|
|
214212
|
+
* questions and an operator must not have to guess which was answered.
|
|
214213
|
+
*/
|
|
214214
|
+
durable: zod.z.boolean()
|
|
214215
|
+
});
|
|
214216
|
+
var GetLoadSeriesInputSchema = zod.z.object({
|
|
214217
|
+
/**
|
|
214218
|
+
* The node whose series is wanted.
|
|
214219
|
+
*
|
|
214220
|
+
* NOT named `nodeId`: the generated cap router strips a top-level
|
|
214221
|
+
* `nodeId` from every method input and uses it to ROUTE the call to
|
|
214222
|
+
* that node's provider (`generated-cap-routers.ts`). A series target
|
|
214223
|
+
* called `nodeId` would silently become a routing pin and never reach
|
|
214224
|
+
* the provider. The hub holds every node it hears from, so the
|
|
214225
|
+
* ordinary call is unpinned — answered by the hub, for any node.
|
|
214226
|
+
*/
|
|
214227
|
+
forNodeId: zod.z.string(),
|
|
214228
|
+
/**
|
|
214229
|
+
* EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
|
|
214230
|
+
* holds and receives only what it is missing, so seeding a live chart
|
|
214231
|
+
* from this method cannot double a point already drawn.
|
|
214232
|
+
*/
|
|
214233
|
+
sinceMs: zod.z.number().optional(),
|
|
214234
|
+
/**
|
|
214235
|
+
* Most points the caller wants PER FUNCTION. The window is reduced to fit,
|
|
214236
|
+
* preserving min and max per bucket.
|
|
214237
|
+
*
|
|
214238
|
+
* Absent means NO reduction — legitimate for a short window and a trap for a
|
|
214239
|
+
* long one, which is why a chart passes its own pixel width.
|
|
214240
|
+
*/
|
|
214241
|
+
maxPoints: zod.z.number().int().positive().optional()
|
|
214242
|
+
});
|
|
212209
214243
|
var SystemMetricsSchema = zod.z.object({
|
|
212210
214244
|
cpuPercent: zod.z.number(),
|
|
212211
214245
|
memoryPercent: zod.z.number(),
|
|
@@ -212251,28 +214285,44 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
212251
214285
|
getAddonStats: require_sleep.method(zod.z.object({ addonId: zod.z.string() }), PidResourceStatsSchema.nullable()),
|
|
212252
214286
|
/**
|
|
212253
214287
|
* Snapshot of every camstack-related process on this node with a
|
|
212254
|
-
*
|
|
212255
|
-
* Processes tab: cross-references `$process.list` against a `ps` scan
|
|
212256
|
-
*
|
|
212257
|
-
*
|
|
214288
|
+
* root/managed/system classification. Powers the Cluster → Agent →
|
|
214289
|
+
* Processes tab: cross-references `$process.list` against a `ps` scan so
|
|
214290
|
+
* per-addon CPU and RSS can be attributed, and so a process the cluster
|
|
214291
|
+
* does not manage is still visible.
|
|
214292
|
+
*
|
|
214293
|
+
* **Read-only, by design.** This cap once carried a `killProcess`
|
|
214294
|
+
* mutation; it was deleted on 2026-08-27. A runner's lifecycle belongs to
|
|
214295
|
+
* `CrashSupervisor` and is driven through `addons.restartAddon` /
|
|
214296
|
+
* `$process.restart` — signalling a raw pid went around the supervisor
|
|
214297
|
+
* (D6), and the one class it was willing to signal turned out to be the
|
|
214298
|
+
* container's own init and the operator's desktop app.
|
|
212258
214299
|
*/
|
|
212259
214300
|
listNodeProcesses: require_sleep.method(zod.z.void(), zod.z.array(NodeProcessSchema).readonly()),
|
|
212260
214301
|
/**
|
|
212261
|
-
*
|
|
212262
|
-
*
|
|
212263
|
-
*
|
|
212264
|
-
*
|
|
214302
|
+
* The retained per-node load series — the ONE reader over BOTH tiers.
|
|
214303
|
+
*
|
|
214304
|
+
* The in-memory ring is the HOT window (the last 180 snapshots, held by
|
|
214305
|
+
* every node's `native-metrics`); the hub's `metrics:node-load-samples`
|
|
214306
|
+
* table is the COLD one (the operator's retention, six hours by default).
|
|
214307
|
+
* This method merges them and DEDUPES on `atMs`, so a snapshot present in
|
|
214308
|
+
* both contributes once and the caller never learns which tier a point
|
|
214309
|
+
* came from. There is deliberately no second read surface: two readers is
|
|
214310
|
+
* how two charts start disagreeing about the same node.
|
|
214311
|
+
*
|
|
214312
|
+
* Reads only; nothing is sampled to answer it. Normally called UNPINNED —
|
|
214313
|
+
* the hub hears every node's snapshot and holds every node's rows — and
|
|
214314
|
+
* answers for any `forNodeId`. Pinned to an agent it answers from that
|
|
214315
|
+
* agent's ring alone (`durable: false`). Empty is a legitimate answer: a
|
|
214316
|
+
* node nobody has heard from has no series, and saying so is the truth.
|
|
212265
214317
|
*/
|
|
212266
|
-
|
|
212267
|
-
kind: "mutation",
|
|
212268
|
-
auth: "admin"
|
|
212269
|
-
}),
|
|
214318
|
+
getLoadSeries: require_sleep.method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema),
|
|
212270
214319
|
/**
|
|
212271
214320
|
* Tell the addon's forked runner to write a V8 heap snapshot to disk (via
|
|
212272
214321
|
* SIGUSR2 — the runner's diagnostic handler). Also logs its
|
|
212273
|
-
* `process.memoryUsage()` + heap-space breakdown.
|
|
212274
|
-
*
|
|
212275
|
-
* attribution; copy the returned path off the
|
|
214322
|
+
* `process.memoryUsage()` + heap-space breakdown. Resolves the pid from
|
|
214323
|
+
* `$process.list`, so it can only reach a runner this node spawned. Use
|
|
214324
|
+
* for deep per-addon memory attribution; copy the returned path off the
|
|
214325
|
+
* node to analyze.
|
|
212276
214326
|
*/
|
|
212277
214327
|
dumpHeapSnapshot: require_sleep.method(DumpHeapSnapshotInputSchema, DumpHeapSnapshotResultSchema, {
|
|
212278
214328
|
kind: "mutation",
|
|
@@ -228367,6 +230417,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
228367
230417
|
*/
|
|
228368
230418
|
channels: zod.z.array(LogChannelWindowPatchSchema).readonly().optional()
|
|
228369
230419
|
});
|
|
230420
|
+
var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: zod.z.string() });
|
|
228370
230421
|
var GetLoggingSettingsInputSchema = zod.z.object({
|
|
228371
230422
|
scopeNodeId: zod.z.string().optional(),
|
|
228372
230423
|
/**
|
|
@@ -228460,6 +230511,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
228460
230511
|
*/
|
|
228461
230512
|
getRequestCensus: require_sleep.method(zod.z.void(), RequestCensusStatusSchema, { auth: "admin" }),
|
|
228462
230513
|
/**
|
|
230514
|
+
* Every `load-contribution` an addon on this cluster reports — each
|
|
230515
|
+
* addon's OWN cost, already attributed by the addon that owns it.
|
|
230516
|
+
*
|
|
230517
|
+
* There is no central list of what costs what: an addon that spawns a
|
|
230518
|
+
* per-camera child declares it, and one that cannot attribute its cost
|
|
230519
|
+
* (the shared inference pool) declares THAT. So a new cost family appears
|
|
230520
|
+
* here the moment its addon is redeployed, with nobody editing anything.
|
|
230521
|
+
*
|
|
230522
|
+
* What this does NOT do is measure the node. `metrics.node-processes-
|
|
230523
|
+
* snapshot` still does that, and the difference between the two is the
|
|
230524
|
+
* finding: a process no contribution claims is either a leak or a family
|
|
230525
|
+
* nobody has taught to report. Both belong in the unattributed bucket, and
|
|
230526
|
+
* neither may be folded into a camera.
|
|
230527
|
+
*/
|
|
230528
|
+
getLoadContributions: require_sleep.method(zod.z.void(), zod.z.array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }),
|
|
230529
|
+
/**
|
|
228463
230530
|
* The logging settings document — levels and armed diagnostics — resolved
|
|
228464
230531
|
* for `nodeId`, or for the cluster when `nodeId` is absent.
|
|
228465
230532
|
*
|
|
@@ -229485,6 +231552,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
229485
231552
|
}
|
|
229486
231553
|
var HF_REPO = "camstack/camstack-models";
|
|
229487
231554
|
var HF_BASE_URL = `https://huggingface.co/${HF_REPO}/resolve/main`;
|
|
231555
|
+
var DEFAULT_TOKEN_EXPIRY = "30d";
|
|
229488
231556
|
var RUNTIME_DEFAULTS = {
|
|
229489
231557
|
"features.streaming": true,
|
|
229490
231558
|
"features.notifications": true,
|
|
@@ -229513,7 +231581,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
229513
231581
|
"ffmpeg.binaryPath": "ffmpeg",
|
|
229514
231582
|
"ffmpeg.hwAccel": "auto",
|
|
229515
231583
|
"ffmpeg.threadCount": 0,
|
|
229516
|
-
"auth.tokenExpiry": "
|
|
231584
|
+
"auth.tokenExpiry": "30d"
|
|
229517
231585
|
};
|
|
229518
231586
|
var AccessoryKind = {
|
|
229519
231587
|
Siren: require_sleep.DeviceRole.Siren,
|
|
@@ -232031,6 +234099,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
232031
234099
|
lawnMowerControl: "lawn-mower-control",
|
|
232032
234100
|
llm: "llm",
|
|
232033
234101
|
llmRuntime: "llm-runtime",
|
|
234102
|
+
loadContribution: "load-contribution",
|
|
232034
234103
|
localNetwork: "local-network",
|
|
232035
234104
|
lockControl: "lock-control",
|
|
232036
234105
|
logChannels: "log-channels",
|
|
@@ -232396,6 +234465,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
232396
234465
|
key: "llmRuntime",
|
|
232397
234466
|
name: "llm-runtime"
|
|
232398
234467
|
},
|
|
234468
|
+
{
|
|
234469
|
+
key: "loadContribution",
|
|
234470
|
+
name: "load-contribution"
|
|
234471
|
+
},
|
|
232399
234472
|
{
|
|
232400
234473
|
key: "localNetwork",
|
|
232401
234474
|
name: "local-network"
|
|
@@ -232785,6 +234858,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
232785
234858
|
lawnMowerControlCapability,
|
|
232786
234859
|
llmCapability,
|
|
232787
234860
|
llmRuntimeCapability,
|
|
234861
|
+
loadContributionCapability,
|
|
232788
234862
|
localNetworkCapability,
|
|
232789
234863
|
lockControlCapability,
|
|
232790
234864
|
logChannelsCapability,
|
|
@@ -233808,6 +235882,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
233808
235882
|
addonId: null,
|
|
233809
235883
|
access: "create"
|
|
233810
235884
|
},
|
|
235885
|
+
"dataStoreProvider.insertMany": {
|
|
235886
|
+
capName: "data-store-provider",
|
|
235887
|
+
capScope: "system",
|
|
235888
|
+
addonId: null,
|
|
235889
|
+
access: "create"
|
|
235890
|
+
},
|
|
233811
235891
|
"dataStoreProvider.isEmpty": {
|
|
233812
235892
|
capName: "data-store-provider",
|
|
233813
235893
|
capScope: "system",
|
|
@@ -235122,6 +237202,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
235122
237202
|
addonId: null,
|
|
235123
237203
|
access: "create"
|
|
235124
237204
|
},
|
|
237205
|
+
"loadContribution.list": {
|
|
237206
|
+
capName: "load-contribution",
|
|
237207
|
+
capScope: "system",
|
|
237208
|
+
addonId: null,
|
|
237209
|
+
access: "view"
|
|
237210
|
+
},
|
|
235125
237211
|
"localNetwork.downloadCa": {
|
|
235126
237212
|
capName: "local-network",
|
|
235127
237213
|
capScope: "system",
|
|
@@ -235422,17 +237508,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
235422
237508
|
addonId: null,
|
|
235423
237509
|
access: "view"
|
|
235424
237510
|
},
|
|
235425
|
-
"metricsProvider.
|
|
237511
|
+
"metricsProvider.getLoadSeries": {
|
|
235426
237512
|
capName: "metrics-provider",
|
|
235427
237513
|
capScope: "system",
|
|
235428
237514
|
addonId: null,
|
|
235429
237515
|
access: "view"
|
|
235430
237516
|
},
|
|
235431
|
-
"metricsProvider.
|
|
237517
|
+
"metricsProvider.getProcessStats": {
|
|
235432
237518
|
capName: "metrics-provider",
|
|
235433
237519
|
capScope: "system",
|
|
235434
237520
|
addonId: null,
|
|
235435
|
-
access: "
|
|
237521
|
+
access: "view"
|
|
235436
237522
|
},
|
|
235437
237523
|
"metricsProvider.listAddonInstances": {
|
|
235438
237524
|
capName: "metrics-provider",
|
|
@@ -237444,6 +239530,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
237444
239530
|
addonId: null,
|
|
237445
239531
|
access: "create"
|
|
237446
239532
|
},
|
|
239533
|
+
"settingsStore.insertMany": {
|
|
239534
|
+
capName: "settings-store",
|
|
239535
|
+
capScope: "system",
|
|
239536
|
+
addonId: null,
|
|
239537
|
+
access: "create"
|
|
239538
|
+
},
|
|
237447
239539
|
"settingsStore.isEmpty": {
|
|
237448
239540
|
capName: "settings-store",
|
|
237449
239541
|
capScope: "system",
|
|
@@ -238056,6 +240148,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
238056
240148
|
addonId: null,
|
|
238057
240149
|
access: "create"
|
|
238058
240150
|
},
|
|
240151
|
+
"system.getLoadContributions": {
|
|
240152
|
+
capName: "system",
|
|
240153
|
+
capScope: "system",
|
|
240154
|
+
addonId: null,
|
|
240155
|
+
access: "view"
|
|
240156
|
+
},
|
|
238059
240157
|
"system.getLoggingSettings": {
|
|
238060
240158
|
capName: "system",
|
|
238061
240159
|
capScope: "system",
|
|
@@ -238748,6 +240846,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
238748
240846
|
"lawn-mower-control",
|
|
238749
240847
|
"llm",
|
|
238750
240848
|
"llm-runtime",
|
|
240849
|
+
"load-contribution",
|
|
238751
240850
|
"local-network",
|
|
238752
240851
|
"lock-control",
|
|
238753
240852
|
"log-channels",
|
|
@@ -238906,6 +241005,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
238906
241005
|
"integrations",
|
|
238907
241006
|
"llm",
|
|
238908
241007
|
"llm-runtime",
|
|
241008
|
+
"load-contribution",
|
|
238909
241009
|
"local-network",
|
|
238910
241010
|
"log-channels",
|
|
238911
241011
|
"log-destination",
|
|
@@ -241538,7 +243638,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
241538
243638
|
listAddonInstances: (input) => dispatch("metricsProvider", "listAddonInstances", "query", input),
|
|
241539
243639
|
getAddonStats: (input) => dispatch("metricsProvider", "getAddonStats", "query", input),
|
|
241540
243640
|
listNodeProcesses: (input) => dispatch("metricsProvider", "listNodeProcesses", "query", input),
|
|
241541
|
-
|
|
243641
|
+
getLoadSeries: (input) => dispatch("metricsProvider", "getLoadSeries", "query", input),
|
|
241542
243642
|
dumpHeapSnapshot: (input) => dispatch("metricsProvider", "dumpHeapSnapshot", "mutation", input)
|
|
241543
243643
|
},
|
|
241544
243644
|
mqttBroker: {
|
|
@@ -241717,6 +243817,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
241717
243817
|
set: (input) => dispatch("settingsStore", "set", "mutation", input),
|
|
241718
243818
|
query: (input) => dispatch("settingsStore", "query", "query", input),
|
|
241719
243819
|
insert: (input) => dispatch("settingsStore", "insert", "mutation", input),
|
|
243820
|
+
insertMany: (input) => dispatch("settingsStore", "insertMany", "mutation", input),
|
|
241720
243821
|
update: (input) => dispatch("settingsStore", "update", "mutation", input),
|
|
241721
243822
|
delete: (input) => dispatch("settingsStore", "delete", "mutation", input),
|
|
241722
243823
|
deleteWhere: (input) => dispatch("settingsStore", "deleteWhere", "mutation", input),
|
|
@@ -241797,6 +243898,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
241797
243898
|
setSiteLocation: (input) => dispatch("system", "setSiteLocation", "mutation", input),
|
|
241798
243899
|
detectSiteLocation: (input) => dispatch("system", "detectSiteLocation", "mutation", input),
|
|
241799
243900
|
getRequestCensus: (input) => dispatch("system", "getRequestCensus", "query", input),
|
|
243901
|
+
getLoadContributions: (input) => dispatch("system", "getLoadContributions", "query", input),
|
|
241800
243902
|
getLoggingSettings: (input) => dispatch("system", "getLoggingSettings", "query", input),
|
|
241801
243903
|
setLoggingSettings: (input) => dispatch("system", "setLoggingSettings", "mutation", input)
|
|
241802
243904
|
},
|
|
@@ -244610,6 +246712,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244610
246712
|
exports.BrokerSubscribeResultSchema = SubscribeResultSchema;
|
|
244611
246713
|
exports.BrokerTestConnectionResultSchema = TestConnectionResultSchema;
|
|
244612
246714
|
exports.BrokerUnsubscribeInputSchema = UnsubscribeInputSchema;
|
|
246715
|
+
exports.BulkRecordSchema = BulkRecordSchema;
|
|
244613
246716
|
exports.CAMERA_SWITCH_CATALOG = CAMERA_SWITCH_CATALOG;
|
|
244614
246717
|
exports.CAMERA_SWITCH_ORDER = CAMERA_SWITCH_ORDER;
|
|
244615
246718
|
exports.CAM_PROFILE_ORDER = require_sleep.CAM_PROFILE_ORDER;
|
|
@@ -244724,6 +246827,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244724
246827
|
exports.DEFAULT_RETENTION = DEFAULT_RETENTION;
|
|
244725
246828
|
exports.DEFAULT_RUNTIME_STATE_DURABILITY = require_sleep.DEFAULT_RUNTIME_STATE_DURABILITY;
|
|
244726
246829
|
exports.DEFAULT_TIMELAPSE_PREVIEW_TEXT = DEFAULT_TIMELAPSE_PREVIEW_TEXT;
|
|
246830
|
+
exports.DEFAULT_TOKEN_EXPIRY = DEFAULT_TOKEN_EXPIRY;
|
|
244727
246831
|
exports.DETAIL_CROP_PADDING_FIELD = DETAIL_CROP_PADDING_FIELD;
|
|
244728
246832
|
exports.DETAIL_CROP_PADDING_KEY = DETAIL_CROP_PADDING_KEY;
|
|
244729
246833
|
exports.DETAIL_CROP_SECTION_ID = DETAIL_CROP_SECTION_ID;
|
|
@@ -244883,6 +246987,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244883
246987
|
exports.IntercomStatusSchema = IntercomStatusSchema;
|
|
244884
246988
|
exports.KNOWN_CAP_NAMES = KNOWN_CAP_NAMES;
|
|
244885
246989
|
exports.KeyEventSchema = KeyEventSchema;
|
|
246990
|
+
exports.LOAD_CONTRIBUTION_ATTRIBUTIONS = LOAD_CONTRIBUTION_ATTRIBUTIONS;
|
|
246991
|
+
exports.LOAD_CONTRIBUTION_ROLES = LOAD_CONTRIBUTION_ROLES;
|
|
244886
246992
|
exports.LOG_CHANNEL_TICK_MS = LOG_CHANNEL_TICK_MS;
|
|
244887
246993
|
exports.LOG_LEVEL_RANK = LOG_LEVEL_RANK;
|
|
244888
246994
|
exports.LabelAttributionSchema = LabelAttributionSchema;
|
|
@@ -244915,6 +247021,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244915
247021
|
exports.LlmTimeoutDefaults = LlmTimeoutDefaults;
|
|
244916
247022
|
exports.LlmUsageRollupSchema = LlmUsageRollupSchema;
|
|
244917
247023
|
exports.LlmUsageSchema = LlmUsageSchema;
|
|
247024
|
+
exports.LoadContributionSchema = LoadContributionSchema;
|
|
244918
247025
|
exports.LocateSegmentResultSchema = LocateSegmentResultSchema;
|
|
244919
247026
|
exports.LocationStatSchema = LocationStatSchema;
|
|
244920
247027
|
exports.LockControlStatusSchema = LockControlStatusSchema;
|
|
@@ -245197,6 +247304,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
245197
247304
|
exports.RECORDING_EXPORT_MAX_READ_BYTES = RECORDING_EXPORT_MAX_READ_BYTES;
|
|
245198
247305
|
exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
|
|
245199
247306
|
exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
|
|
247307
|
+
exports.ROOT_BUCKET_KEY = ROOT_BUCKET_KEY;
|
|
245200
247308
|
exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
|
|
245201
247309
|
exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
|
|
245202
247310
|
exports.RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS = RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS;
|
|
@@ -245239,6 +247347,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
245239
247347
|
exports.RelocateMediaInputSchema = RelocateMediaInputSchema;
|
|
245240
247348
|
exports.RenderedAsSchema = RenderedAsSchema;
|
|
245241
247349
|
exports.ReportMotionInputSchema = ReportMotionInputSchema;
|
|
247350
|
+
exports.ReportedLoadContributionSchema = ReportedLoadContributionSchema;
|
|
245242
247351
|
exports.RequestCensusGroupSchema = RequestCensusGroupSchema;
|
|
245243
247352
|
exports.RequestCensusProcedureSchema = RequestCensusProcedureSchema;
|
|
245244
247353
|
exports.RequestCensusSnapshotSchema = RequestCensusSnapshotSchema;
|
|
@@ -245422,6 +247531,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
245422
247531
|
exports.TransportPlaneCountsSchema = TransportPlaneCountsSchema;
|
|
245423
247532
|
exports.TransportPlaneSchema = TransportPlaneSchema;
|
|
245424
247533
|
exports.TurnServerSchema = TurnServerSchema;
|
|
247534
|
+
exports.UNATTRIBUTED_BUCKET_KEY = UNATTRIBUTED_BUCKET_KEY;
|
|
245425
247535
|
exports.UNIT_TABLE = UNIT_TABLE;
|
|
245426
247536
|
exports.UnifiedBrokerInfoSchema = BrokerInfoSchema$1;
|
|
245427
247537
|
exports.UnitConversionError = UnitConversionError;
|
|
@@ -245640,6 +247750,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
245640
247750
|
exports.filesystemBrowseCapability = filesystemBrowseCapability;
|
|
245641
247751
|
exports.findTimezone = findTimezone;
|
|
245642
247752
|
exports.floodCapability = floodCapability;
|
|
247753
|
+
exports.foldSnapshotByFunction = foldSnapshotByFunction;
|
|
245643
247754
|
exports.formatForBackend = formatForBackend;
|
|
245644
247755
|
exports.formatForRuntime = formatForRuntime;
|
|
245645
247756
|
exports.gasCapability = gasCapability;
|
|
@@ -245697,6 +247808,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
245697
247808
|
exports.lifecycleTaskSchema = lifecycleTaskSchema;
|
|
245698
247809
|
exports.llmCapability = llmCapability;
|
|
245699
247810
|
exports.llmRuntimeCapability = llmRuntimeCapability;
|
|
247811
|
+
exports.loadContributionCapability = loadContributionCapability;
|
|
245700
247812
|
exports.localNetworkCapability = localNetworkCapability;
|
|
245701
247813
|
exports.locationSimilarity = locationSimilarity;
|
|
245702
247814
|
exports.lockControlCapability = lockControlCapability;
|
|
@@ -245793,12 +247905,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
245793
247905
|
exports.recordingCapability = recordingCapability;
|
|
245794
247906
|
exports.recordingExportCapability = recordingExportCapability;
|
|
245795
247907
|
exports.rectsToCells = rectsToCells;
|
|
247908
|
+
exports.reducePoints = reducePoints;
|
|
245796
247909
|
exports.requiresPython = requiresPython;
|
|
245797
247910
|
exports.resetPoolBaseline = resetPoolBaseline;
|
|
245798
247911
|
exports.resolveAddonExecution = resolveAddonExecution;
|
|
245799
247912
|
exports.resolveAddonGroup = resolveAddonGroup;
|
|
245800
247913
|
exports.resolveAddonPlacement = resolveAddonPlacement;
|
|
245801
247914
|
exports.resolveAddonRuntime = resolveAddonRuntime;
|
|
247915
|
+
exports.resolveBucketMs = resolveBucketMs;
|
|
245802
247916
|
exports.resolveCapMount = require_sleep.resolveCapMount;
|
|
245803
247917
|
exports.resolveClusterStepModelId = resolveClusterStepModelId;
|
|
245804
247918
|
exports.resolveDetectionRuntime = resolveDetectionRuntime;
|
|
@@ -402988,6 +405102,37 @@ var require_collection_preference = __commonJS({
|
|
|
402988
405102
|
}
|
|
402989
405103
|
});
|
|
402990
405104
|
|
|
405105
|
+
// ../../server/backend/dist/api/core/load-contributions.js
|
|
405106
|
+
var require_load_contributions = __commonJS({
|
|
405107
|
+
"../../server/backend/dist/api/core/load-contributions.js"(exports) {
|
|
405108
|
+
"use strict";
|
|
405109
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
405110
|
+
exports.EMPTY_LOAD_CONTRIBUTION_PLANE = void 0;
|
|
405111
|
+
exports.buildLoadContributionPlane = buildLoadContributionPlane;
|
|
405112
|
+
var types_1 = require_dist4();
|
|
405113
|
+
exports.EMPTY_LOAD_CONTRIBUTION_PLANE = {
|
|
405114
|
+
contributions: async () => []
|
|
405115
|
+
};
|
|
405116
|
+
function buildLoadContributionPlane(source, onProviderError) {
|
|
405117
|
+
return {
|
|
405118
|
+
contributions: async () => {
|
|
405119
|
+
const out = [];
|
|
405120
|
+
for (const [addonId, provider] of source.entries()) {
|
|
405121
|
+
try {
|
|
405122
|
+
for (const entry of await provider.list()) {
|
|
405123
|
+
out.push({ ...entry, addonId });
|
|
405124
|
+
}
|
|
405125
|
+
} catch (err) {
|
|
405126
|
+
onProviderError?.(addonId, (0, types_1.errMsg)(err));
|
|
405127
|
+
}
|
|
405128
|
+
}
|
|
405129
|
+
return out;
|
|
405130
|
+
}
|
|
405131
|
+
};
|
|
405132
|
+
}
|
|
405133
|
+
}
|
|
405134
|
+
});
|
|
405135
|
+
|
|
402991
405136
|
// ../../server/backend/dist/api/core/logging-settings.js
|
|
402992
405137
|
var require_logging_settings = __commonJS({
|
|
402993
405138
|
"../../server/backend/dist/api/core/logging-settings.js"(exports) {
|
|
@@ -404029,6 +406174,7 @@ var require_cap_providers = __commonJS({
|
|
|
404029
406174
|
var agent_installed_packages_js_1 = require_agent_installed_packages();
|
|
404030
406175
|
var http_request_census_singleton_js_1 = require_http_request_census_singleton();
|
|
404031
406176
|
var collection_preference_js_1 = require_collection_preference();
|
|
406177
|
+
var load_contributions_js_1 = require_load_contributions();
|
|
404032
406178
|
var logging_settings_js_1 = require_logging_settings();
|
|
404033
406179
|
var request_census_settings_js_1 = require_request_census_settings();
|
|
404034
406180
|
var site_location_js_1 = require_site_location();
|
|
@@ -404046,6 +406192,9 @@ var require_cap_providers = __commonJS({
|
|
|
404046
406192
|
const channels = (0, logging_settings_js_1.buildLogChannelPlane)({ entries: () => registry?.getCollectionEntries("log-channels") ?? [] }, (addonId, phase, error) => {
|
|
404047
406193
|
logger?.warn("log-channels provider unreachable", { meta: { addonId, phase, error } });
|
|
404048
406194
|
});
|
|
406195
|
+
const loadContributions = (0, load_contributions_js_1.buildLoadContributionPlane)({ entries: () => registry?.getCollectionEntries("load-contribution") ?? [] }, (addonId, error) => {
|
|
406196
|
+
logger?.warn("load-contribution provider unreachable", { meta: { addonId, error } });
|
|
406197
|
+
});
|
|
404049
406198
|
const loggingSettings = new logging_settings_js_1.LoggingSettingsService({
|
|
404050
406199
|
store,
|
|
404051
406200
|
gate: (0, system_1.getLoggingGate)(),
|
|
@@ -404072,6 +406221,7 @@ var require_cap_providers = __commonJS({
|
|
|
404072
406221
|
}
|
|
404073
406222
|
return result;
|
|
404074
406223
|
},
|
|
406224
|
+
getLoadContributions: async () => loadContributions.contributions(),
|
|
404075
406225
|
getRetentionConfig: async () => getRetention(registry)?.getConfig() ?? null,
|
|
404076
406226
|
setRetentionConfig: async (input) => {
|
|
404077
406227
|
getRetention(registry)?.setConfig(input);
|