rigscope 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +2 -2
- package/.next/build-manifest.json +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/administration/page_client-reference-manifest.js +1 -1
- package/.next/server/app/administration.html +1 -1
- package/.next/server/app/administration.rsc +2 -2
- package/.next/server/app/administration.segments/_full.segment.rsc +2 -2
- package/.next/server/app/administration.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/alerts/page_client-reference-manifest.js +1 -1
- package/.next/server/app/alerts.html +1 -1
- package/.next/server/app/alerts.rsc +2 -2
- package/.next/server/app/alerts.segments/_full.segment.rsc +2 -2
- package/.next/server/app/alerts.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/connections/page.js +1 -1
- package/.next/server/app/connections/page_client-reference-manifest.js +1 -1
- package/.next/server/app/connections.html +1 -1
- package/.next/server/app/connections.rsc +3 -3
- package/.next/server/app/connections.segments/_full.segment.rsc +3 -3
- package/.next/server/app/connections.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/history/page_client-reference-manifest.js +1 -1
- package/.next/server/app/history.html +1 -1
- package/.next/server/app/history.rsc +2 -2
- package/.next/server/app/history.segments/_full.segment.rsc +2 -2
- package/.next/server/app/history.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/processes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/processes.html +1 -1
- package/.next/server/app/processes.rsc +2 -2
- package/.next/server/app/processes.segments/_full.segment.rsc +2 -2
- package/.next/server/app/processes.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +2 -2
- package/.next/server/app-paths-manifest.json +2 -2
- package/.next/server/chunks/161.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/939-92930fde48061e20.js +1 -0
- package/.next/static/chunks/app/connections/page-7d18b901f2338f20.js +1 -0
- package/ARCHITECTURE.md +18 -6
- package/CHANGELOG.md +35 -0
- package/dist/cli.cjs +762 -509
- package/dist/collector.cjs +581 -115
- package/dist/hub.cjs +7 -3
- package/dist/system.cjs +1257 -363
- package/docs/configuration.md +1 -0
- package/docs/installation.md +9 -3
- package/docs/monitoring.md +6 -3
- package/docs/operations.md +40 -13
- package/docs/system-installation.md +11 -4
- package/docs/telemetry-storage.md +80 -9
- package/package.json +1 -1
- package/packaging/collector.env.example +6 -0
- package/packaging/system/rigscope-collector.service.example +3 -0
- package/.next/static/chunks/939-514e554087419c71.js +0 -1
- package/.next/static/chunks/app/connections/page-75abd16fc495fcbd.js +0 -1
- /package/.next/static/{Jlbibe2_iFCljUO4-BUK7 → JbaLjudQSxrMUD_VHPrnD}/_buildManifest.js +0 -0
- /package/.next/static/{Jlbibe2_iFCljUO4-BUK7 → JbaLjudQSxrMUD_VHPrnD}/_ssgManifest.js +0 -0
package/dist/collector.cjs
CHANGED
|
@@ -108,13 +108,16 @@ function filterInventory(inv, sharing) {
|
|
|
108
108
|
swapTotalBytes: policy.swap ? inv.memory.swapTotalBytes : null
|
|
109
109
|
},
|
|
110
110
|
gpus: policy.gpus ? inv.gpus.map((gpu) => ({ ...gpu })) : [],
|
|
111
|
-
filesystems: policy.disks ? inv.filesystems.map((
|
|
111
|
+
filesystems: policy.disks ? inv.filesystems.map((fs3) => ({ ...fs3 })) : [],
|
|
112
112
|
disks: policy.disks ? inv.disks.map((disk) => ({ ...disk })) : [],
|
|
113
113
|
network,
|
|
114
114
|
capabilities
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// collector/destinations/queue.ts
|
|
119
|
+
var DEFAULT_COLLECTOR_QUEUE_MODE = "memory";
|
|
120
|
+
|
|
118
121
|
// collector/config.ts
|
|
119
122
|
var ConfigError = class extends Error {
|
|
120
123
|
issues;
|
|
@@ -320,6 +323,18 @@ function parseDestinations(env, issues, destinations) {
|
|
|
320
323
|
}
|
|
321
324
|
}
|
|
322
325
|
}
|
|
326
|
+
var QUEUE_MODES = ["memory", "durable"];
|
|
327
|
+
function parseQueueMode(env, issues) {
|
|
328
|
+
const raw = env["COLLECTOR_QUEUE_MODE"]?.trim();
|
|
329
|
+
if (raw === void 0 || raw === "") {
|
|
330
|
+
return DEFAULT_COLLECTOR_QUEUE_MODE;
|
|
331
|
+
}
|
|
332
|
+
if (!QUEUE_MODES.includes(raw)) {
|
|
333
|
+
issues.push(`COLLECTOR_QUEUE_MODE must be one of ${QUEUE_MODES.join(" or ")}`);
|
|
334
|
+
return DEFAULT_COLLECTOR_QUEUE_MODE;
|
|
335
|
+
}
|
|
336
|
+
return raw;
|
|
337
|
+
}
|
|
323
338
|
function parseIntervalMs(env, issues) {
|
|
324
339
|
const raw = env["COLLECTOR_INTERVAL_MS"]?.trim();
|
|
325
340
|
if (raw === void 0 || raw === "") {
|
|
@@ -350,6 +365,7 @@ function parseCollectorConfig(env) {
|
|
|
350
365
|
}
|
|
351
366
|
const destinations = [];
|
|
352
367
|
const intervalMs = parseIntervalMs(env, issues);
|
|
368
|
+
const queueMode = parseQueueMode(env, issues);
|
|
353
369
|
parseDestinations(env, issues, destinations);
|
|
354
370
|
if (issues.length > 0) {
|
|
355
371
|
throw new ConfigError(issues);
|
|
@@ -359,7 +375,8 @@ function parseCollectorConfig(env) {
|
|
|
359
375
|
hostname,
|
|
360
376
|
hostnameIsMockDefault: rawHostname === "",
|
|
361
377
|
intervalMs,
|
|
362
|
-
destinations
|
|
378
|
+
destinations,
|
|
379
|
+
queueMode
|
|
363
380
|
};
|
|
364
381
|
}
|
|
365
382
|
|
|
@@ -377,8 +394,8 @@ var ProcessLeaseError = class extends Error {
|
|
|
377
394
|
this.code = code;
|
|
378
395
|
}
|
|
379
396
|
};
|
|
380
|
-
function acquireProcessLease(
|
|
381
|
-
const absolute = (0, import_node_path.resolve)(
|
|
397
|
+
function acquireProcessLease(path3) {
|
|
398
|
+
const absolute = (0, import_node_path.resolve)(path3);
|
|
382
399
|
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(absolute), { recursive: true, mode: 448 });
|
|
383
400
|
const parent = (0, import_node_fs.lstatSync)((0, import_node_path.dirname)(absolute));
|
|
384
401
|
if (!parent.isDirectory() || (parent.mode & 63) !== 0) {
|
|
@@ -432,10 +449,10 @@ function acquireProcessLease(path2) {
|
|
|
432
449
|
"runtime lease could not be acquired after repeated stale-lock recovery"
|
|
433
450
|
);
|
|
434
451
|
}
|
|
435
|
-
function inspectLock(
|
|
452
|
+
function inspectLock(path3) {
|
|
436
453
|
let stats;
|
|
437
454
|
try {
|
|
438
|
-
stats = (0, import_node_fs.lstatSync)(
|
|
455
|
+
stats = (0, import_node_fs.lstatSync)(path3);
|
|
439
456
|
} catch (error) {
|
|
440
457
|
if (error.code === "ENOENT") return null;
|
|
441
458
|
throw new ProcessLeaseError(
|
|
@@ -448,7 +465,7 @@ function inspectLock(path2) {
|
|
|
448
465
|
}
|
|
449
466
|
let raw;
|
|
450
467
|
try {
|
|
451
|
-
raw = (0, import_node_fs.readFileSync)(
|
|
468
|
+
raw = (0, import_node_fs.readFileSync)(path3, "utf8");
|
|
452
469
|
} catch (error) {
|
|
453
470
|
if (error.code === "ENOENT") return null;
|
|
454
471
|
throw new ProcessLeaseError(
|
|
@@ -485,10 +502,10 @@ function inspectLock(path2) {
|
|
|
485
502
|
);
|
|
486
503
|
}
|
|
487
504
|
}
|
|
488
|
-
function createRelease(
|
|
505
|
+
function createRelease(path3, inode) {
|
|
489
506
|
let released = false;
|
|
490
507
|
const cleanup = () => {
|
|
491
|
-
removeIfSameInode(
|
|
508
|
+
removeIfSameInode(path3, inode);
|
|
492
509
|
};
|
|
493
510
|
const onExit = () => {
|
|
494
511
|
if (released) return;
|
|
@@ -503,10 +520,10 @@ function createRelease(path2, inode) {
|
|
|
503
520
|
cleanup();
|
|
504
521
|
};
|
|
505
522
|
}
|
|
506
|
-
function removeIfSameInode(
|
|
523
|
+
function removeIfSameInode(path3, inode) {
|
|
507
524
|
try {
|
|
508
|
-
const stat = (0, import_node_fs.lstatSync)(
|
|
509
|
-
if (stat.ino === inode.ino && stat.dev === inode.dev) (0, import_node_fs.unlinkSync)(
|
|
525
|
+
const stat = (0, import_node_fs.lstatSync)(path3);
|
|
526
|
+
if (stat.ino === inode.ino && stat.dev === inode.dev) (0, import_node_fs.unlinkSync)(path3);
|
|
510
527
|
} catch {
|
|
511
528
|
}
|
|
512
529
|
}
|
|
@@ -515,10 +532,10 @@ function removeIfSameInode(path2, inode) {
|
|
|
515
532
|
function defaultStatePath() {
|
|
516
533
|
return (0, import_node_path2.join)(process.env.XDG_STATE_HOME || (0, import_node_path2.join)((0, import_node_os.homedir)(), ".local", "state"), "rigscope", "collector.json");
|
|
517
534
|
}
|
|
518
|
-
function readState(
|
|
519
|
-
const stat = (0, import_node_fs2.lstatSync)(
|
|
535
|
+
function readState(path3) {
|
|
536
|
+
const stat = (0, import_node_fs2.lstatSync)(path3);
|
|
520
537
|
if (!stat.isFile() || stat.size > 64 * 1024 || (stat.mode & 63) !== 0) throw new Error("collector state must be a private regular file");
|
|
521
|
-
const parsed = JSON.parse((0, import_node_fs2.readFileSync)(
|
|
538
|
+
const parsed = JSON.parse((0, import_node_fs2.readFileSync)(path3, "utf8"));
|
|
522
539
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid collector state");
|
|
523
540
|
const raw = parsed;
|
|
524
541
|
if (Object.hasOwn(raw, "version") || Object.hasOwn(raw, "formatVersion")) {
|
|
@@ -543,13 +560,13 @@ function readState(path2) {
|
|
|
543
560
|
...pendingEnrollments === void 0 ? {} : { pendingEnrollments }
|
|
544
561
|
};
|
|
545
562
|
}
|
|
546
|
-
function updateInstallation(
|
|
547
|
-
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(
|
|
548
|
-
const directoryStat = (0, import_node_fs2.lstatSync)((0, import_node_path2.dirname)(
|
|
563
|
+
function updateInstallation(path3, change = (s) => s) {
|
|
564
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(path3), { recursive: true, mode: 448 });
|
|
565
|
+
const directoryStat = (0, import_node_fs2.lstatSync)((0, import_node_path2.dirname)(path3));
|
|
549
566
|
if (!directoryStat.isDirectory() || (directoryStat.mode & 63) !== 0) {
|
|
550
567
|
throw new ConfigError(["collector state requires a private directory (0700)"]);
|
|
551
568
|
}
|
|
552
|
-
const lock = `${
|
|
569
|
+
const lock = `${path3}.lock`;
|
|
553
570
|
let releaseLock;
|
|
554
571
|
try {
|
|
555
572
|
releaseLock = acquireProcessLease(lock);
|
|
@@ -566,8 +583,8 @@ function updateInstallation(path2, change = (s) => s) {
|
|
|
566
583
|
try {
|
|
567
584
|
let state;
|
|
568
585
|
try {
|
|
569
|
-
(0, import_node_fs2.lstatSync)(
|
|
570
|
-
state = readState(
|
|
586
|
+
(0, import_node_fs2.lstatSync)(path3);
|
|
587
|
+
state = readState(path3);
|
|
571
588
|
} catch (error) {
|
|
572
589
|
if (error.code !== "ENOENT") throw error;
|
|
573
590
|
state = { collectorId: (0, import_node_crypto2.randomUUID)(), destinations: [] };
|
|
@@ -578,7 +595,7 @@ function updateInstallation(path2, change = (s) => s) {
|
|
|
578
595
|
destinations: changed.destinations,
|
|
579
596
|
...changed.pendingEnrollments === void 0 ? {} : { pendingEnrollments: changed.pendingEnrollments }
|
|
580
597
|
};
|
|
581
|
-
temporary = `${
|
|
598
|
+
temporary = `${path3}.${(0, import_node_crypto2.randomUUID)()}.tmp`;
|
|
582
599
|
const out = (0, import_node_fs2.openSync)(temporary, "wx", 384);
|
|
583
600
|
try {
|
|
584
601
|
(0, import_node_fs2.writeFileSync)(out, JSON.stringify(next) + "\n");
|
|
@@ -586,9 +603,9 @@ function updateInstallation(path2, change = (s) => s) {
|
|
|
586
603
|
} finally {
|
|
587
604
|
(0, import_node_fs2.closeSync)(out);
|
|
588
605
|
}
|
|
589
|
-
(0, import_node_fs2.renameSync)(temporary,
|
|
606
|
+
(0, import_node_fs2.renameSync)(temporary, path3);
|
|
590
607
|
temporary = void 0;
|
|
591
|
-
const directory = (0, import_node_fs2.openSync)((0, import_node_path2.dirname)(
|
|
608
|
+
const directory = (0, import_node_fs2.openSync)((0, import_node_path2.dirname)(path3), "r");
|
|
592
609
|
try {
|
|
593
610
|
(0, import_node_fs2.fsyncSync)(directory);
|
|
594
611
|
} finally {
|
|
@@ -606,14 +623,14 @@ function updateInstallation(path2, change = (s) => s) {
|
|
|
606
623
|
|
|
607
624
|
// collector/enroll.ts
|
|
608
625
|
var import_node_crypto3 = require("node:crypto");
|
|
609
|
-
async function enrollCollector(
|
|
626
|
+
async function enrollCollector(path3, connectUrl, enrollmentToken, sharing, replace = false) {
|
|
610
627
|
const issues = [];
|
|
611
628
|
const url = validateHubUrl(connectUrl, "enrollment destination", issues);
|
|
612
629
|
if (!url || issues.length) throw new Error("invalid enrollment destination");
|
|
613
630
|
if (!/^[\x21-\x7e]{1,256}$/.test(enrollmentToken)) throw new Error("invalid enrollment token");
|
|
614
|
-
const state = updateInstallation(
|
|
631
|
+
const state = updateInstallation(path3);
|
|
615
632
|
if (!replace && state.destinations.some((d) => d.url === url.href) || !state.destinations.some((d) => d.url === url.href) && state.destinations.length >= 8) throw new Error("destination already enrolled or destination budget exhausted");
|
|
616
|
-
const prepared = updateInstallation(
|
|
633
|
+
const prepared = updateInstallation(path3, (current) => {
|
|
617
634
|
if (current.pendingEnrollments?.some((p) => p.url === url.href)) return current;
|
|
618
635
|
return { ...current, pendingEnrollments: [
|
|
619
636
|
...current.pendingEnrollments ?? [],
|
|
@@ -659,7 +676,7 @@ async function enrollCollector(path2, connectUrl, enrollmentToken, sharing, repl
|
|
|
659
676
|
if (result.collectorId !== state.collectorId || typeof result.machineId !== "string" || !result.machineId || typeof result.token !== "string") throw new Error("invalid enrollment response");
|
|
660
677
|
const destination = parseCollectorConfig({ COLLECTOR_HUBS: JSON.stringify([{ url: url.href, token: result.token, sharing }]) }).destinations[0];
|
|
661
678
|
try {
|
|
662
|
-
updateInstallation(
|
|
679
|
+
updateInstallation(path3, (current) => {
|
|
663
680
|
if (current.collectorId !== state.collectorId || !replace && current.destinations.some((d) => d.url === url.href) || !current.destinations.some((d) => d.url === url.href) && current.destinations.length >= 8) throw new Error("collector state changed during enrollment");
|
|
664
681
|
return {
|
|
665
682
|
...current,
|
|
@@ -678,9 +695,9 @@ var nodeFs = __toESM(require("node:fs"));
|
|
|
678
695
|
var BOOT_ID_UNAVAILABLE = "unavailable";
|
|
679
696
|
var BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id";
|
|
680
697
|
var BOOT_ID_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
681
|
-
function readBootIdentity(
|
|
698
|
+
function readBootIdentity(fs3 = nodeFs) {
|
|
682
699
|
try {
|
|
683
|
-
const raw = String(
|
|
700
|
+
const raw = String(fs3.readFileSync(BOOT_ID_PATH, "utf8")).trim();
|
|
684
701
|
if (BOOT_ID_PATTERN.test(raw)) {
|
|
685
702
|
return { bootId: raw.toLowerCase(), available: true };
|
|
686
703
|
}
|
|
@@ -758,13 +775,17 @@ var OutboxBudget = class {
|
|
|
758
775
|
}
|
|
759
776
|
return largest;
|
|
760
777
|
}
|
|
761
|
-
/**
|
|
778
|
+
/**
|
|
779
|
+
* Physical counterpart of `largest`, using measured main+WAL bytes. A memory
|
|
780
|
+
* queue owns no file, so it is never a physical-budget victim.
|
|
781
|
+
*/
|
|
762
782
|
largestByDisk(exclude) {
|
|
763
783
|
let largest = null;
|
|
764
784
|
let largestBytes = -1;
|
|
765
785
|
for (const outbox of this.outboxes) {
|
|
766
786
|
if (exclude?.has(outbox)) continue;
|
|
767
787
|
const bytes = outbox.diskBytes();
|
|
788
|
+
if (bytes === 0) continue;
|
|
768
789
|
if (bytes > largestBytes) {
|
|
769
790
|
largest = outbox;
|
|
770
791
|
largestBytes = bytes;
|
|
@@ -779,6 +800,10 @@ var OutboxBudget = class {
|
|
|
779
800
|
}
|
|
780
801
|
};
|
|
781
802
|
|
|
803
|
+
// collector/destinations/connection.ts
|
|
804
|
+
var fs2 = __toESM(require("node:fs"));
|
|
805
|
+
var path2 = __toESM(require("node:path"));
|
|
806
|
+
|
|
782
807
|
// collector/destinations/outbox.ts
|
|
783
808
|
var import_node_crypto5 = require("node:crypto");
|
|
784
809
|
var fs = __toESM(require("node:fs"));
|
|
@@ -853,6 +878,7 @@ function asRow(value) {
|
|
|
853
878
|
};
|
|
854
879
|
}
|
|
855
880
|
var DestinationOutbox = class _DestinationOutbox {
|
|
881
|
+
queueMode = "durable";
|
|
856
882
|
db;
|
|
857
883
|
limits;
|
|
858
884
|
budget;
|
|
@@ -1101,7 +1127,8 @@ var DestinationOutbox = class _DestinationOutbox {
|
|
|
1101
1127
|
inFlightRecords: 0,
|
|
1102
1128
|
droppedRecords: this.droppedSinceOpen,
|
|
1103
1129
|
replaying: false,
|
|
1104
|
-
lastAckAt: this.lastAckAt
|
|
1130
|
+
lastAckAt: this.lastAckAt,
|
|
1131
|
+
queueMode: this.queueMode
|
|
1105
1132
|
};
|
|
1106
1133
|
}
|
|
1107
1134
|
const pending = this.db.prepare("SELECT captured_at, bytes FROM outbox_records WHERE state = 'pending'").all();
|
|
@@ -1122,7 +1149,8 @@ var DestinationOutbox = class _DestinationOutbox {
|
|
|
1122
1149
|
inFlightRecords: inFlight,
|
|
1123
1150
|
droppedRecords: this.droppedSinceOpen,
|
|
1124
1151
|
replaying: pending.length > 0,
|
|
1125
|
-
lastAckAt: this.lastAckAt
|
|
1152
|
+
lastAckAt: this.lastAckAt,
|
|
1153
|
+
queueMode: this.queueMode
|
|
1126
1154
|
};
|
|
1127
1155
|
}
|
|
1128
1156
|
/** Logical bytes of stored record JSON (pending plus in-flight). */
|
|
@@ -1330,8 +1358,13 @@ var DestinationOutbox = class _DestinationOutbox {
|
|
|
1330
1358
|
let guard = 0;
|
|
1331
1359
|
while (budget2.diskOverBy() > 0 && guard < 256) {
|
|
1332
1360
|
guard += 1;
|
|
1333
|
-
const
|
|
1334
|
-
if (
|
|
1361
|
+
const candidate = budget2.largestByDisk(tried);
|
|
1362
|
+
if (candidate === null) break;
|
|
1363
|
+
if (!(candidate instanceof _DestinationOutbox)) {
|
|
1364
|
+
tried.add(candidate);
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
const target = candidate;
|
|
1335
1368
|
if (!target.canReclaimPhysical()) {
|
|
1336
1369
|
tried.add(target);
|
|
1337
1370
|
continue;
|
|
@@ -1546,6 +1579,414 @@ var DestinationOutbox = class _DestinationOutbox {
|
|
|
1546
1579
|
}
|
|
1547
1580
|
};
|
|
1548
1581
|
|
|
1582
|
+
// collector/destinations/memory-queue.ts
|
|
1583
|
+
var refKey = ({ runId, sequence }) => `${runId}\0${sequence}`;
|
|
1584
|
+
var NO_DISK = {
|
|
1585
|
+
bytes: 0,
|
|
1586
|
+
mainBytes: 0,
|
|
1587
|
+
walBytes: 0,
|
|
1588
|
+
logicalBytes: 0,
|
|
1589
|
+
limitBytes: 0,
|
|
1590
|
+
overBy: 0,
|
|
1591
|
+
pressure: false,
|
|
1592
|
+
walBudgetBytes: 0,
|
|
1593
|
+
checkpointPages: 0,
|
|
1594
|
+
pageCount: 0,
|
|
1595
|
+
maxPageCount: 0,
|
|
1596
|
+
freePages: 0,
|
|
1597
|
+
autoVacuum: 0
|
|
1598
|
+
};
|
|
1599
|
+
var MemoryDestinationQueue = class {
|
|
1600
|
+
queueMode = "memory";
|
|
1601
|
+
limits;
|
|
1602
|
+
budget;
|
|
1603
|
+
allocateSequence;
|
|
1604
|
+
identity;
|
|
1605
|
+
now;
|
|
1606
|
+
entries = [];
|
|
1607
|
+
byRef = /* @__PURE__ */ new Map();
|
|
1608
|
+
nextId = 1;
|
|
1609
|
+
totalBytes = 0;
|
|
1610
|
+
/** id of the newest metrics record not yet offered to `takeLive`. */
|
|
1611
|
+
liveId = null;
|
|
1612
|
+
droppedSinceOpen = 0;
|
|
1613
|
+
lastAckAt = null;
|
|
1614
|
+
closed = false;
|
|
1615
|
+
constructor(opts) {
|
|
1616
|
+
this.limits = { ...DEFAULT_OUTBOX_LIMITS, ...opts.limits };
|
|
1617
|
+
this.budget = opts.budget ?? null;
|
|
1618
|
+
this.allocateSequence = opts.allocateSequence;
|
|
1619
|
+
this.identity = opts.identity;
|
|
1620
|
+
this.now = opts.now ?? Date.now;
|
|
1621
|
+
this.budget?.register(this);
|
|
1622
|
+
}
|
|
1623
|
+
// -------------------------------------------------------------------------
|
|
1624
|
+
// Writes
|
|
1625
|
+
// -------------------------------------------------------------------------
|
|
1626
|
+
/**
|
|
1627
|
+
* Admit one filtered metrics record. Age and byte caps are applied first so
|
|
1628
|
+
* the newest sample always wins its slot, exactly as the durable queue sheds
|
|
1629
|
+
* the oldest pending telemetry under pressure. The shared budget and gap cap
|
|
1630
|
+
* are then settled so admission itself keeps retained data bounded, even if
|
|
1631
|
+
* the maintenance tick never runs. Never throws.
|
|
1632
|
+
*/
|
|
1633
|
+
append(record) {
|
|
1634
|
+
if (this.closed) return;
|
|
1635
|
+
try {
|
|
1636
|
+
const json = JSON.stringify(record);
|
|
1637
|
+
const bytes = Buffer.byteLength(json, "utf8");
|
|
1638
|
+
if (bytes > this.limits.maxBytes) {
|
|
1639
|
+
this.droppedSinceOpen += 1;
|
|
1640
|
+
this.writeGap("outbox-bytes", record.capturedAt, record.capturedAt, 1);
|
|
1641
|
+
this.enforceAdmission();
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
this.enforceAge();
|
|
1645
|
+
this.enforceBytes(this.limits.maxBytes - bytes);
|
|
1646
|
+
this.insertJson(record.kind, record, json, bytes);
|
|
1647
|
+
this.enforceAdmission();
|
|
1648
|
+
} catch {
|
|
1649
|
+
this.droppedSinceOpen += 1;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
/** Reapply the current privacy policy to every retained metrics record. */
|
|
1653
|
+
filterPending(transform) {
|
|
1654
|
+
if (this.closed) return;
|
|
1655
|
+
for (const entry of [...this.entries]) {
|
|
1656
|
+
if (entry.kind !== "metrics") continue;
|
|
1657
|
+
const parsed = this.parse(entry);
|
|
1658
|
+
if (parsed === null) continue;
|
|
1659
|
+
const json = JSON.stringify(transform(parsed));
|
|
1660
|
+
const bytes = Buffer.byteLength(json, "utf8");
|
|
1661
|
+
this.totalBytes += bytes - entry.bytes;
|
|
1662
|
+
entry.json = json;
|
|
1663
|
+
entry.bytes = bytes;
|
|
1664
|
+
}
|
|
1665
|
+
this.totalBytes = Math.max(0, this.totalBytes);
|
|
1666
|
+
}
|
|
1667
|
+
// -------------------------------------------------------------------------
|
|
1668
|
+
// Reads
|
|
1669
|
+
// -------------------------------------------------------------------------
|
|
1670
|
+
/**
|
|
1671
|
+
* Newest metrics record appended since the last `takeLive`. It stays pending
|
|
1672
|
+
* so a lost live send is still replayed later, matching the durable queue.
|
|
1673
|
+
*/
|
|
1674
|
+
takeLive() {
|
|
1675
|
+
if (this.closed || this.liveId === null) return null;
|
|
1676
|
+
const id2 = this.liveId;
|
|
1677
|
+
this.liveId = null;
|
|
1678
|
+
const entry = this.entries.find((candidate) => candidate.id === id2);
|
|
1679
|
+
if (entry === void 0 || entry.state !== "pending" || entry.kind !== "metrics") return null;
|
|
1680
|
+
return this.parse(entry);
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Oldest pending records, oldest first, excluding the record currently held
|
|
1684
|
+
* for the live slot. Bounded by both count and bytes, but always at least one
|
|
1685
|
+
* record so a single large sample cannot stall the queue.
|
|
1686
|
+
*/
|
|
1687
|
+
takeReplay(maxRecords, maxBytes) {
|
|
1688
|
+
if (this.closed || !Number.isFinite(maxRecords) || maxRecords < 1) return [];
|
|
1689
|
+
const records = [];
|
|
1690
|
+
let bytes = 0;
|
|
1691
|
+
for (const entry of this.entries) {
|
|
1692
|
+
if (records.length >= Math.floor(maxRecords)) break;
|
|
1693
|
+
if (entry.state !== "pending" || entry.id === this.liveId) continue;
|
|
1694
|
+
if (records.length > 0 && bytes + entry.bytes > maxBytes) break;
|
|
1695
|
+
const parsed = this.parse(entry);
|
|
1696
|
+
if (parsed === null) continue;
|
|
1697
|
+
records.push(parsed);
|
|
1698
|
+
bytes += entry.bytes;
|
|
1699
|
+
}
|
|
1700
|
+
return records;
|
|
1701
|
+
}
|
|
1702
|
+
// -------------------------------------------------------------------------
|
|
1703
|
+
// Delivery state
|
|
1704
|
+
// -------------------------------------------------------------------------
|
|
1705
|
+
markInFlight(refs) {
|
|
1706
|
+
if (this.closed) return;
|
|
1707
|
+
for (const ref of refs) {
|
|
1708
|
+
const entry = this.byRef.get(refKey(ref));
|
|
1709
|
+
if (entry !== void 0 && entry.state === "pending") {
|
|
1710
|
+
entry.state = "inflight";
|
|
1711
|
+
entry.offered = true;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
release(refs) {
|
|
1716
|
+
if (this.closed) return;
|
|
1717
|
+
for (const ref of refs) {
|
|
1718
|
+
const entry = this.byRef.get(refKey(ref));
|
|
1719
|
+
if (entry !== void 0 && entry.state === "inflight") entry.state = "pending";
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
releaseAll() {
|
|
1723
|
+
if (this.closed) return;
|
|
1724
|
+
for (const entry of this.entries) if (entry.state === "inflight") entry.state = "pending";
|
|
1725
|
+
}
|
|
1726
|
+
/** Accepted and rejected records both leave the queue. Unknown refs are ignored. */
|
|
1727
|
+
acknowledge(accepted, rejected) {
|
|
1728
|
+
if (this.closed) return;
|
|
1729
|
+
if (accepted.length === 0 && rejected.length === 0) return;
|
|
1730
|
+
for (const ref of [...accepted, ...rejected]) {
|
|
1731
|
+
const key = refKey(ref);
|
|
1732
|
+
const entry = this.byRef.get(key);
|
|
1733
|
+
if (entry === void 0) continue;
|
|
1734
|
+
this.byRef.delete(key);
|
|
1735
|
+
const index = this.entries.indexOf(entry);
|
|
1736
|
+
if (index >= 0) this.entries.splice(index, 1);
|
|
1737
|
+
if (this.liveId === entry.id) this.liveId = null;
|
|
1738
|
+
this.totalBytes = Math.max(0, this.totalBytes - entry.bytes);
|
|
1739
|
+
}
|
|
1740
|
+
this.lastAckAt = this.isoNow();
|
|
1741
|
+
}
|
|
1742
|
+
// -------------------------------------------------------------------------
|
|
1743
|
+
// Retention
|
|
1744
|
+
// -------------------------------------------------------------------------
|
|
1745
|
+
enforceLimits() {
|
|
1746
|
+
if (this.closed) return;
|
|
1747
|
+
this.enforceAge();
|
|
1748
|
+
this.enforceBytes(this.limits.maxBytes);
|
|
1749
|
+
this.enforceAdmission();
|
|
1750
|
+
}
|
|
1751
|
+
/** Shared-budget victim: shed this queue's oldest pending metrics records. */
|
|
1752
|
+
evictForBudget(reason) {
|
|
1753
|
+
if (this.closed) return 0;
|
|
1754
|
+
const victims = this.entries.filter((entry) => entry.state === "pending" && entry.kind === "metrics").slice(0, 16);
|
|
1755
|
+
if (victims.length === 0) return 0;
|
|
1756
|
+
const evicted = this.evict(victims, reason);
|
|
1757
|
+
this.enforceGapCap();
|
|
1758
|
+
return evicted;
|
|
1759
|
+
}
|
|
1760
|
+
status() {
|
|
1761
|
+
let pendingRecords = 0;
|
|
1762
|
+
let pendingBytes = 0;
|
|
1763
|
+
let inFlightRecords = 0;
|
|
1764
|
+
let oldest = null;
|
|
1765
|
+
for (const entry of this.entries) {
|
|
1766
|
+
if (entry.state === "inflight") {
|
|
1767
|
+
inFlightRecords += 1;
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
pendingRecords += 1;
|
|
1771
|
+
pendingBytes += entry.bytes;
|
|
1772
|
+
if (oldest === null || entry.capturedAtMs < Date.parse(oldest)) oldest = entry.capturedAt;
|
|
1773
|
+
}
|
|
1774
|
+
return {
|
|
1775
|
+
pendingRecords,
|
|
1776
|
+
pendingBytes,
|
|
1777
|
+
oldestPendingCapturedAt: oldest,
|
|
1778
|
+
inFlightRecords,
|
|
1779
|
+
droppedRecords: this.droppedSinceOpen,
|
|
1780
|
+
replaying: pendingRecords > 0,
|
|
1781
|
+
lastAckAt: this.lastAckAt,
|
|
1782
|
+
queueMode: this.queueMode
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
bytes() {
|
|
1786
|
+
return this.totalBytes;
|
|
1787
|
+
}
|
|
1788
|
+
/** A RAM queue owns no file: this is what makes the mode observable. */
|
|
1789
|
+
diskBytes() {
|
|
1790
|
+
return 0;
|
|
1791
|
+
}
|
|
1792
|
+
diskStatus() {
|
|
1793
|
+
return { ...NO_DISK, logicalBytes: this.totalBytes };
|
|
1794
|
+
}
|
|
1795
|
+
checkpoint() {
|
|
1796
|
+
}
|
|
1797
|
+
attachBudget(budget2) {
|
|
1798
|
+
this.budget = budget2;
|
|
1799
|
+
}
|
|
1800
|
+
close() {
|
|
1801
|
+
if (this.closed) return;
|
|
1802
|
+
this.closed = true;
|
|
1803
|
+
this.budget?.unregister(this);
|
|
1804
|
+
this.entries.length = 0;
|
|
1805
|
+
this.byRef.clear();
|
|
1806
|
+
this.totalBytes = 0;
|
|
1807
|
+
this.liveId = null;
|
|
1808
|
+
}
|
|
1809
|
+
// -------------------------------------------------------------------------
|
|
1810
|
+
// Internals
|
|
1811
|
+
// -------------------------------------------------------------------------
|
|
1812
|
+
isoNow() {
|
|
1813
|
+
return new Date(this.now()).toISOString();
|
|
1814
|
+
}
|
|
1815
|
+
insertJson(kind, record, json, bytes) {
|
|
1816
|
+
const entry = {
|
|
1817
|
+
id: this.nextId++,
|
|
1818
|
+
runId: record.runId,
|
|
1819
|
+
sequence: record.sequence,
|
|
1820
|
+
capturedAt: record.capturedAt,
|
|
1821
|
+
capturedAtMs: Date.parse(record.capturedAt),
|
|
1822
|
+
kind,
|
|
1823
|
+
gapReason: record.kind === "gap" ? record.payload.reason : null,
|
|
1824
|
+
state: "pending",
|
|
1825
|
+
offered: false,
|
|
1826
|
+
bytes,
|
|
1827
|
+
json
|
|
1828
|
+
};
|
|
1829
|
+
this.entries.push(entry);
|
|
1830
|
+
this.byRef.set(refKey(entry), entry);
|
|
1831
|
+
this.totalBytes += bytes;
|
|
1832
|
+
if (record.kind === "metrics") this.liveId = entry.id;
|
|
1833
|
+
return entry;
|
|
1834
|
+
}
|
|
1835
|
+
remove(entry) {
|
|
1836
|
+
this.byRef.delete(refKey(entry));
|
|
1837
|
+
const index = this.entries.indexOf(entry);
|
|
1838
|
+
if (index >= 0) this.entries.splice(index, 1);
|
|
1839
|
+
if (this.liveId === entry.id) this.liveId = null;
|
|
1840
|
+
this.totalBytes = Math.max(0, this.totalBytes - entry.bytes);
|
|
1841
|
+
}
|
|
1842
|
+
/** Fold evicted records into one coalesced gap so the hub sees the hole. */
|
|
1843
|
+
evict(victims, reason) {
|
|
1844
|
+
if (victims.length === 0) return 0;
|
|
1845
|
+
let from = victims[0].capturedAt;
|
|
1846
|
+
let to = victims[0].capturedAt;
|
|
1847
|
+
let fromMs = victims[0].capturedAtMs;
|
|
1848
|
+
let toMs = victims[0].capturedAtMs;
|
|
1849
|
+
for (const entry of victims) {
|
|
1850
|
+
this.remove(entry);
|
|
1851
|
+
if (entry.capturedAtMs < fromMs) {
|
|
1852
|
+
fromMs = entry.capturedAtMs;
|
|
1853
|
+
from = entry.capturedAt;
|
|
1854
|
+
}
|
|
1855
|
+
if (entry.capturedAtMs > toMs) {
|
|
1856
|
+
toMs = entry.capturedAtMs;
|
|
1857
|
+
to = entry.capturedAt;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
this.droppedSinceOpen += victims.length;
|
|
1861
|
+
this.writeGap(reason, from, to, victims.length);
|
|
1862
|
+
return victims.length;
|
|
1863
|
+
}
|
|
1864
|
+
/** Fold one dropped interval into a gap payload, keeping the widest range. */
|
|
1865
|
+
extendGapPayload(target, source) {
|
|
1866
|
+
if (Date.parse(source.fromCapturedAt) < Date.parse(target.fromCapturedAt)) {
|
|
1867
|
+
target.fromCapturedAt = source.fromCapturedAt;
|
|
1868
|
+
}
|
|
1869
|
+
if (Date.parse(source.toCapturedAt) > Date.parse(target.toCapturedAt)) {
|
|
1870
|
+
target.toCapturedAt = source.toCapturedAt;
|
|
1871
|
+
}
|
|
1872
|
+
target.droppedRecords += source.droppedRecords;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Coalesce into the newest pending gap of the same reason that has not been
|
|
1876
|
+
* offered yet, so per-reason evidence stays one record while gaps queue.
|
|
1877
|
+
*/
|
|
1878
|
+
writeGap(reason, fromCapturedAt, toCapturedAt, droppedRecords) {
|
|
1879
|
+
for (let index = this.entries.length - 1; index >= 0; index -= 1) {
|
|
1880
|
+
const candidate = this.entries[index];
|
|
1881
|
+
if (candidate.kind !== "gap" || candidate.state !== "pending" || candidate.offered) continue;
|
|
1882
|
+
if (candidate.runId !== this.identity.runId || candidate.gapReason !== reason) continue;
|
|
1883
|
+
const record = this.parse(candidate);
|
|
1884
|
+
if (record === null) break;
|
|
1885
|
+
this.extendGapPayload(record.payload, { fromCapturedAt, toCapturedAt, droppedRecords });
|
|
1886
|
+
candidate.capturedAt = record.capturedAt;
|
|
1887
|
+
candidate.capturedAtMs = Date.parse(record.capturedAt);
|
|
1888
|
+
const json2 = JSON.stringify(record);
|
|
1889
|
+
const bytes = Buffer.byteLength(json2, "utf8");
|
|
1890
|
+
this.totalBytes += bytes - candidate.bytes;
|
|
1891
|
+
candidate.json = json2;
|
|
1892
|
+
candidate.bytes = bytes;
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
const gap = {
|
|
1896
|
+
collectorId: this.identity.collectorId,
|
|
1897
|
+
runId: this.identity.runId,
|
|
1898
|
+
bootId: this.identity.bootId,
|
|
1899
|
+
sequence: this.allocateSequence(),
|
|
1900
|
+
capturedAt: this.isoNow(),
|
|
1901
|
+
kind: "gap",
|
|
1902
|
+
payload: { fromCapturedAt, toCapturedAt, droppedRecords, reason }
|
|
1903
|
+
};
|
|
1904
|
+
const json = JSON.stringify(gap);
|
|
1905
|
+
this.insertJson("gap", gap, json, Buffer.byteLength(json, "utf8"));
|
|
1906
|
+
}
|
|
1907
|
+
pendingMetrics() {
|
|
1908
|
+
return this.entries.filter((entry) => entry.state === "pending" && entry.kind === "metrics");
|
|
1909
|
+
}
|
|
1910
|
+
enforceAge() {
|
|
1911
|
+
const cutoff = this.now() - this.limits.maxAgeMs;
|
|
1912
|
+
const expired = this.pendingMetrics().filter((entry) => entry.capturedAtMs < cutoff);
|
|
1913
|
+
if (expired.length > 0) this.evict(expired, "outbox-age");
|
|
1914
|
+
}
|
|
1915
|
+
/** Evict oldest pending metrics until the queue fits `targetBytes`. */
|
|
1916
|
+
enforceBytes(targetBytes) {
|
|
1917
|
+
if (this.totalBytes <= targetBytes) return;
|
|
1918
|
+
const pending = this.pendingMetrics();
|
|
1919
|
+
const victims = [];
|
|
1920
|
+
for (const entry of pending) {
|
|
1921
|
+
if (this.totalBytes - sumBytes(victims) <= targetBytes) break;
|
|
1922
|
+
victims.push(entry);
|
|
1923
|
+
}
|
|
1924
|
+
if (victims.length === 0 && pending.length > 0) victims.push(pending[0]);
|
|
1925
|
+
if (victims.length > 0) this.evict(victims, "outbox-bytes");
|
|
1926
|
+
}
|
|
1927
|
+
/** Settle the caps that can change after any admission or eviction. */
|
|
1928
|
+
enforceAdmission() {
|
|
1929
|
+
this.enforceBudget();
|
|
1930
|
+
this.enforceGapCap();
|
|
1931
|
+
}
|
|
1932
|
+
enforceBudget() {
|
|
1933
|
+
const budget2 = this.budget;
|
|
1934
|
+
if (budget2 === null) return;
|
|
1935
|
+
const tried = /* @__PURE__ */ new Set();
|
|
1936
|
+
let guard = 0;
|
|
1937
|
+
while (budget2.overBy() > 0 && guard < 1e4) {
|
|
1938
|
+
guard += 1;
|
|
1939
|
+
const target = budget2.largest(tried);
|
|
1940
|
+
if (target === null) break;
|
|
1941
|
+
if (target.evictForBudget("disk-budget") === 0) tried.add(target);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
/** Hard ceiling on retained gap records: merge the oldest pending gaps. */
|
|
1945
|
+
enforceGapCap() {
|
|
1946
|
+
let guard = 0;
|
|
1947
|
+
while (this.gapCount() > this.limits.maxGapRecords && guard < 1e4) {
|
|
1948
|
+
guard += 1;
|
|
1949
|
+
const gaps = this.entries.filter((entry) => entry.kind === "gap" && entry.state === "pending");
|
|
1950
|
+
if (gaps.length < 2) return;
|
|
1951
|
+
const older = gaps[0];
|
|
1952
|
+
const newer = gaps[1];
|
|
1953
|
+
const olderRecord = this.parse(older);
|
|
1954
|
+
const merged = this.parse(newer);
|
|
1955
|
+
if (olderRecord === null || merged === null) return;
|
|
1956
|
+
this.extendGapPayload(merged.payload, olderRecord.payload);
|
|
1957
|
+
merged.collectorId = this.identity.collectorId;
|
|
1958
|
+
merged.runId = this.identity.runId;
|
|
1959
|
+
merged.bootId = this.identity.bootId;
|
|
1960
|
+
merged.sequence = this.allocateSequence();
|
|
1961
|
+
merged.capturedAt = this.isoNow();
|
|
1962
|
+
this.remove(newer);
|
|
1963
|
+
this.remove(older);
|
|
1964
|
+
const json = JSON.stringify(merged);
|
|
1965
|
+
this.insertJson("gap", merged, json, Buffer.byteLength(json, "utf8"));
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Read one entry as a record. Ours is always self-written, so a parse failure
|
|
1970
|
+
* means an internal bug: count it as dropped and forget the row rather than
|
|
1971
|
+
* stalling every later record, matching the durable queue's corrupt-row path.
|
|
1972
|
+
*/
|
|
1973
|
+
parse(entry) {
|
|
1974
|
+
try {
|
|
1975
|
+
return JSON.parse(entry.json);
|
|
1976
|
+
} catch {
|
|
1977
|
+
this.remove(entry);
|
|
1978
|
+
this.droppedSinceOpen += 1;
|
|
1979
|
+
return null;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
gapCount() {
|
|
1983
|
+
return this.entries.reduce((count, entry) => entry.kind === "gap" ? count + 1 : count, 0);
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
function sumBytes(entries2) {
|
|
1987
|
+
return entries2.reduce((total, entry) => total + entry.bytes, 0);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1549
1990
|
// src/contracts/telemetry.ts
|
|
1550
1991
|
var MAX_MESSAGE_BYTES = 262144;
|
|
1551
1992
|
var MAX_DETAIL_BYTES = 1048576;
|
|
@@ -1702,7 +2143,7 @@ function inboundText(data) {
|
|
|
1702
2143
|
}
|
|
1703
2144
|
return "";
|
|
1704
2145
|
}
|
|
1705
|
-
function
|
|
2146
|
+
function refKey2(ref) {
|
|
1706
2147
|
return `${ref.runId}\0${ref.sequence}`;
|
|
1707
2148
|
}
|
|
1708
2149
|
function toRef(record) {
|
|
@@ -2245,7 +2686,7 @@ var DestinationSession = class {
|
|
|
2245
2686
|
const id2 = this.nextBatchId++;
|
|
2246
2687
|
const entry = {
|
|
2247
2688
|
refs,
|
|
2248
|
-
keys: new Set(refs.map(
|
|
2689
|
+
keys: new Set(refs.map(refKey2)),
|
|
2249
2690
|
mode,
|
|
2250
2691
|
timer: setTimeout(() => this.handleAckTimeout(id2), this.ackTimeoutMs)
|
|
2251
2692
|
};
|
|
@@ -2283,7 +2724,7 @@ var DestinationSession = class {
|
|
|
2283
2724
|
`hub rejected ${message.rejected.length} record(s): ${message.rejected[0]?.reason ?? "no reason given"}`
|
|
2284
2725
|
);
|
|
2285
2726
|
}
|
|
2286
|
-
const ackedKeys = new Set([...accepted, ...rejected].map(
|
|
2727
|
+
const ackedKeys = new Set([...accepted, ...rejected].map(refKey2));
|
|
2287
2728
|
const touched = [];
|
|
2288
2729
|
for (const [id2, batch] of [...this.inFlight.entries()]) {
|
|
2289
2730
|
let intersects = false;
|
|
@@ -2303,7 +2744,7 @@ var DestinationSession = class {
|
|
|
2303
2744
|
for (const batch of toRelease) {
|
|
2304
2745
|
this.inFlight.delete(this.batchIdOf(batch));
|
|
2305
2746
|
clearTimeout(batch.timer);
|
|
2306
|
-
this.outbox.release(batch.refs.filter((ref) => batch.keys.has(
|
|
2747
|
+
this.outbox.release(batch.refs.filter((ref) => batch.keys.has(refKey2(ref))));
|
|
2307
2748
|
}
|
|
2308
2749
|
this.log(`hub asked to retry after ${delay}ms; replay paused`);
|
|
2309
2750
|
}
|
|
@@ -2353,6 +2794,13 @@ var DestinationSession = class {
|
|
|
2353
2794
|
};
|
|
2354
2795
|
|
|
2355
2796
|
// collector/destinations/connection.ts
|
|
2797
|
+
function leftoverDurableQueue(dir) {
|
|
2798
|
+
try {
|
|
2799
|
+
return fs2.existsSync(path2.join(dir, "outbox.sqlite"));
|
|
2800
|
+
} catch {
|
|
2801
|
+
return false;
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2356
2804
|
var HubConnection = class _HubConnection {
|
|
2357
2805
|
index;
|
|
2358
2806
|
policy;
|
|
@@ -2394,22 +2842,29 @@ var HubConnection = class _HubConnection {
|
|
|
2394
2842
|
});
|
|
2395
2843
|
}
|
|
2396
2844
|
/**
|
|
2397
|
-
* Opens this connection's
|
|
2398
|
-
*
|
|
2399
|
-
*
|
|
2400
|
-
*
|
|
2845
|
+
* Opens this connection's delivery queue in the configured mode and re-applies
|
|
2846
|
+
* the privacy policy to any recovered pending record so none of them can
|
|
2847
|
+
* leave with forbidden fields. Sanitation never cadence-filters: retained
|
|
2848
|
+
* samples were already cadence-selected when they were captured.
|
|
2849
|
+
*
|
|
2850
|
+
* In `memory` mode no queue file is created at all. A durable queue left over
|
|
2851
|
+
* from an earlier `durable` install is not opened or replayed; it is reported
|
|
2852
|
+
* once so the operator knows unacknowledged samples are sitting on disk and
|
|
2853
|
+
* how to discard them.
|
|
2401
2854
|
*/
|
|
2402
2855
|
static open(options) {
|
|
2403
|
-
const
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2856
|
+
const identity = {
|
|
2857
|
+
collectorId: options.identity.collectorId,
|
|
2858
|
+
runId: options.identity.runId,
|
|
2859
|
+
bootId: options.identity.bootId
|
|
2860
|
+
};
|
|
2861
|
+
const dir = outboxDirFor(options.stateDir, options.endpoint.url);
|
|
2862
|
+
const outbox = options.queueMode === "durable" ? DestinationOutbox.open({ dir, budget: options.budget, allocateSequence: options.allocateSequence, identity }) : new MemoryDestinationQueue({ budget: options.budget, allocateSequence: options.allocateSequence, identity });
|
|
2863
|
+
if (options.queueMode === "memory" && leftoverDurableQueue(dir)) {
|
|
2864
|
+
options.onEvent?.(
|
|
2865
|
+
`hub${options.index}: a durable outbox from an earlier COLLECTOR_QUEUE_MODE=durable install is unused; run rigscope upgrade, or delete it, to discard those unacknowledged samples`
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2413
2868
|
const connection = new _HubConnection(options, outbox);
|
|
2414
2869
|
try {
|
|
2415
2870
|
outbox.filterPending((record) => applySharing(record, connection.policy));
|
|
@@ -2523,9 +2978,9 @@ var nodeFs2 = __toESM(require("node:fs"));
|
|
|
2523
2978
|
var nodeOs = __toESM(require("node:os"));
|
|
2524
2979
|
|
|
2525
2980
|
// collector/sampling/procfs.ts
|
|
2526
|
-
function readTextOrNull(read,
|
|
2981
|
+
function readTextOrNull(read, path3) {
|
|
2527
2982
|
try {
|
|
2528
|
-
return read(
|
|
2983
|
+
return read(path3);
|
|
2529
2984
|
} catch {
|
|
2530
2985
|
return null;
|
|
2531
2986
|
}
|
|
@@ -2751,8 +3206,8 @@ var CPU_HWMON_NAMES = /* @__PURE__ */ new Set([
|
|
|
2751
3206
|
"cpu_thermal",
|
|
2752
3207
|
"soc_thermal"
|
|
2753
3208
|
]);
|
|
2754
|
-
function readCounter(read,
|
|
2755
|
-
const text2 = readTextOrNull(read,
|
|
3209
|
+
function readCounter(read, path3) {
|
|
3210
|
+
const text2 = readTextOrNull(read, path3);
|
|
2756
3211
|
if (text2 === null || text2.trim() === "") return null;
|
|
2757
3212
|
const value = Number(text2.trim());
|
|
2758
3213
|
if (!Number.isSafeInteger(value) || value < 0) return null;
|
|
@@ -2773,8 +3228,8 @@ function hwmonOrder(name) {
|
|
|
2773
3228
|
const match = /^hwmon(\d+)$/.exec(name);
|
|
2774
3229
|
return match === null ? Number.MAX_SAFE_INTEGER : Number(match[1]);
|
|
2775
3230
|
}
|
|
2776
|
-
function readCandidateTemperature(read,
|
|
2777
|
-
const text2 = readTextOrNull(read,
|
|
3231
|
+
function readCandidateTemperature(read, path3) {
|
|
3232
|
+
const text2 = readTextOrNull(read, path3);
|
|
2778
3233
|
if (text2 === null || text2.trim() === "") return null;
|
|
2779
3234
|
const millidegrees = Number(text2.trim());
|
|
2780
3235
|
if (!Number.isFinite(millidegrees)) return null;
|
|
@@ -2905,21 +3360,21 @@ function parsePressure(text2) {
|
|
|
2905
3360
|
}
|
|
2906
3361
|
return sawLine ? { someAvg10, fullAvg10 } : null;
|
|
2907
3362
|
}
|
|
2908
|
-
function readPressure(read,
|
|
2909
|
-
const text2 = readTextOrNull(read,
|
|
3363
|
+
function readPressure(read, path3) {
|
|
3364
|
+
const text2 = readTextOrNull(read, path3);
|
|
2910
3365
|
return text2 === null ? null : parsePressure(text2);
|
|
2911
3366
|
}
|
|
2912
3367
|
|
|
2913
3368
|
// collector/sampling/sysfs-gpu.ts
|
|
2914
|
-
function number(read,
|
|
2915
|
-
const raw = readTextOrNull(read,
|
|
3369
|
+
function number(read, path3, max = Number.MAX_SAFE_INTEGER, min = 0) {
|
|
3370
|
+
const raw = readTextOrNull(read, path3)?.trim();
|
|
2916
3371
|
if (!raw || !/^[-]?\d+$/.test(raw)) return null;
|
|
2917
3372
|
const value = Number(raw);
|
|
2918
3373
|
return Number.isSafeInteger(value) && value >= min && value <= max ? value : null;
|
|
2919
3374
|
}
|
|
2920
|
-
function entries(readers,
|
|
3375
|
+
function entries(readers, path3) {
|
|
2921
3376
|
try {
|
|
2922
|
-
return readers.readdir(
|
|
3377
|
+
return readers.readdir(path3).filter((name) => /^hwmon\d+$/.test(name)).sort().slice(0, 8);
|
|
2923
3378
|
} catch {
|
|
2924
3379
|
return [];
|
|
2925
3380
|
}
|
|
@@ -2942,9 +3397,9 @@ function readSysfsGpu(readers, address) {
|
|
|
2942
3397
|
if (values.memoryTotalBytes === 0) values.memoryTotalBytes = null;
|
|
2943
3398
|
if (values.memoryTotalBytes === null || values.memoryUsedBytes !== null && values.memoryUsedBytes > values.memoryTotalBytes) values.memoryUsedBytes = null;
|
|
2944
3399
|
for (const hwmon of entries(readers, `${base}/hwmon`)) {
|
|
2945
|
-
const
|
|
2946
|
-
const temp = number(read, `${
|
|
2947
|
-
const power = number(read, `${
|
|
3400
|
+
const path3 = `${base}/hwmon/${hwmon}`;
|
|
3401
|
+
const temp = number(read, `${path3}/temp1_input`, 2e5, -5e4);
|
|
3402
|
+
const power = number(read, `${path3}/power1_average`, 1e10) ?? number(read, `${path3}/power1_input`, 1e10);
|
|
2948
3403
|
if (temp !== null && values.temperatureC === null) values.temperatureC = temp / 1e3;
|
|
2949
3404
|
if (power !== null && values.powerWatts === null) values.powerWatts = power / 1e6;
|
|
2950
3405
|
}
|
|
@@ -2955,9 +3410,9 @@ function readSysfsGpu(readers, address) {
|
|
|
2955
3410
|
}
|
|
2956
3411
|
|
|
2957
3412
|
// collector/sampling/network.ts
|
|
2958
|
-
function readOptional(read,
|
|
3413
|
+
function readOptional(read, path3) {
|
|
2959
3414
|
try {
|
|
2960
|
-
return read(
|
|
3415
|
+
return read(path3);
|
|
2961
3416
|
} catch {
|
|
2962
3417
|
return null;
|
|
2963
3418
|
}
|
|
@@ -3476,10 +3931,10 @@ var USER_HZ = 100;
|
|
|
3476
3931
|
var CPU_TOLERANCE_FRACTION = 0.05;
|
|
3477
3932
|
var MAX_RATE_BYTES_PER_SEC = 1e13;
|
|
3478
3933
|
var defaultReaders = {
|
|
3479
|
-
readFile: (
|
|
3480
|
-
readdir: (
|
|
3481
|
-
statfs: (
|
|
3482
|
-
const stats = nodeFs2.statfsSync(
|
|
3934
|
+
readFile: (path3) => String(nodeFs2.readFileSync(path3, "utf8")),
|
|
3935
|
+
readdir: (path3) => nodeFs2.readdirSync(path3),
|
|
3936
|
+
statfs: (path3) => {
|
|
3937
|
+
const stats = nodeFs2.statfsSync(path3);
|
|
3483
3938
|
return {
|
|
3484
3939
|
bsize: Number(stats.bsize),
|
|
3485
3940
|
blocks: Number(stats.blocks),
|
|
@@ -3527,7 +3982,7 @@ function meanLatencyMs(operations, timeMs) {
|
|
|
3527
3982
|
function inventorySignature(inventory) {
|
|
3528
3983
|
return JSON.stringify({
|
|
3529
3984
|
gpus: inventory.gpus.map((gpu) => [gpu.id, gpu.vendor, gpu.telemetry, gpu.memoryTotalBytes, gpu.pciBusId]),
|
|
3530
|
-
filesystems: inventory.filesystems.map((
|
|
3985
|
+
filesystems: inventory.filesystems.map((fs3) => [fs3.id, fs3.totalBytes]),
|
|
3531
3986
|
disks: inventory.disks.map((disk) => [disk.id, disk.sizeBytes]),
|
|
3532
3987
|
network: inventory.network.map((iface) => [
|
|
3533
3988
|
iface.id,
|
|
@@ -4694,9 +5149,9 @@ var ProcessScanner = class {
|
|
|
4694
5149
|
}
|
|
4695
5150
|
};
|
|
4696
5151
|
}
|
|
4697
|
-
async readText(
|
|
5152
|
+
async readText(path3) {
|
|
4698
5153
|
try {
|
|
4699
|
-
return { ok: true, text: await import_node_fs3.promises.readFile(
|
|
5154
|
+
return { ok: true, text: await import_node_fs3.promises.readFile(path3, "utf8") };
|
|
4700
5155
|
} catch (error) {
|
|
4701
5156
|
return { ok: false, code: errorCode(error) };
|
|
4702
5157
|
}
|
|
@@ -4781,8 +5236,8 @@ function parseIo(text2) {
|
|
|
4781
5236
|
function parseCgroupUnit(text2) {
|
|
4782
5237
|
for (const line of text2.split("\n")) {
|
|
4783
5238
|
if (!line.startsWith("0::")) continue;
|
|
4784
|
-
const
|
|
4785
|
-
const components =
|
|
5239
|
+
const path3 = line.slice(3);
|
|
5240
|
+
const components = path3.split("/").filter((part) => part.length > 0);
|
|
4786
5241
|
const last = components[components.length - 1];
|
|
4787
5242
|
if (!last) return null;
|
|
4788
5243
|
if (!last.endsWith(".service") && !last.endsWith(".scope")) return null;
|
|
@@ -5232,7 +5687,7 @@ function budget(value, fallback) {
|
|
|
5232
5687
|
// package.json
|
|
5233
5688
|
var package_default = {
|
|
5234
5689
|
name: "rigscope",
|
|
5235
|
-
version: "0.2.
|
|
5690
|
+
version: "0.2.1",
|
|
5236
5691
|
private: false,
|
|
5237
5692
|
engines: {
|
|
5238
5693
|
node: ">=26.0.0"
|
|
@@ -5371,8 +5826,8 @@ var OperationsError = class extends Error {
|
|
|
5371
5826
|
function isPrivateMode(mode) {
|
|
5372
5827
|
return (mode & 63) === 0;
|
|
5373
5828
|
}
|
|
5374
|
-
function ensurePrivateDir(
|
|
5375
|
-
const absolute = (0, import_node_path3.resolve)(
|
|
5829
|
+
function ensurePrivateDir(path3) {
|
|
5830
|
+
const absolute = (0, import_node_path3.resolve)(path3);
|
|
5376
5831
|
(0, import_node_fs4.mkdirSync)(absolute, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
5377
5832
|
const stat = (0, import_node_fs4.lstatSync)(absolute);
|
|
5378
5833
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
@@ -5385,23 +5840,48 @@ function ensurePrivateDir(path2) {
|
|
|
5385
5840
|
);
|
|
5386
5841
|
}
|
|
5387
5842
|
}
|
|
5843
|
+
function assertPrivateRegularFile(path3, label) {
|
|
5844
|
+
const absolute = (0, import_node_path3.resolve)(path3);
|
|
5845
|
+
let stat;
|
|
5846
|
+
try {
|
|
5847
|
+
stat = (0, import_node_fs4.lstatSync)(absolute);
|
|
5848
|
+
} catch (error) {
|
|
5849
|
+
throw new OperationsError("missing-file", `${label} does not exist: ${absolute}`, { cause: error });
|
|
5850
|
+
}
|
|
5851
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
5852
|
+
throw new OperationsError("unsafe-path", `${label} must be a regular file, not a symlink: ${absolute}`);
|
|
5853
|
+
}
|
|
5854
|
+
if (!isPrivateMode(stat.mode)) {
|
|
5855
|
+
throw new OperationsError(
|
|
5856
|
+
"insecure-permissions",
|
|
5857
|
+
`${label} must be private (0600/0400); found ${formatMode(stat.mode)}: ${absolute}`
|
|
5858
|
+
);
|
|
5859
|
+
}
|
|
5860
|
+
return stat;
|
|
5861
|
+
}
|
|
5388
5862
|
function formatMode(mode) {
|
|
5389
5863
|
return `0${(mode & 511).toString(8).padStart(3, "0")}`;
|
|
5390
5864
|
}
|
|
5391
|
-
function
|
|
5392
|
-
const absolute = (0, import_node_path3.resolve)(
|
|
5865
|
+
function writePrivateFileBuffered(path3, content) {
|
|
5866
|
+
const absolute = (0, import_node_path3.resolve)(path3);
|
|
5393
5867
|
const parent = (0, import_node_path3.dirname)(absolute);
|
|
5868
|
+
ensurePrivateDir(parent);
|
|
5869
|
+
try {
|
|
5870
|
+
assertPrivateRegularFile(absolute, "private file");
|
|
5871
|
+
} catch (error) {
|
|
5872
|
+
if (!(error instanceof OperationsError) || error.code !== "missing-file") throw error;
|
|
5873
|
+
}
|
|
5394
5874
|
const temporary = (0, import_node_path3.join)(parent, `.${(0, import_node_path3.basename)(absolute)}.tmp-${(0, import_node_crypto6.randomUUID)()}`);
|
|
5395
5875
|
let fd = null;
|
|
5876
|
+
let temporaryCreated = false;
|
|
5396
5877
|
try {
|
|
5397
|
-
fd = (0, import_node_fs4.openSync)(temporary, "wx",
|
|
5878
|
+
fd = (0, import_node_fs4.openSync)(temporary, "wx", PRIVATE_FILE_MODE);
|
|
5879
|
+
temporaryCreated = true;
|
|
5398
5880
|
(0, import_node_fs4.writeFileSync)(fd, content);
|
|
5399
|
-
(0, import_node_fs4.fsyncSync)(fd);
|
|
5400
5881
|
(0, import_node_fs4.closeSync)(fd);
|
|
5401
5882
|
fd = null;
|
|
5402
|
-
(0, import_node_fs4.chmodSync)(temporary,
|
|
5883
|
+
(0, import_node_fs4.chmodSync)(temporary, PRIVATE_FILE_MODE);
|
|
5403
5884
|
(0, import_node_fs4.renameSync)(temporary, absolute);
|
|
5404
|
-
fsyncDir(parent);
|
|
5405
5885
|
} catch (error) {
|
|
5406
5886
|
if (fd !== null) {
|
|
5407
5887
|
try {
|
|
@@ -5409,39 +5889,24 @@ function writeFileAtomic(path2, content, mode) {
|
|
|
5409
5889
|
} catch {
|
|
5410
5890
|
}
|
|
5411
5891
|
}
|
|
5412
|
-
removeIfExists(temporary);
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
}
|
|
5416
|
-
function writePrivateFile(path2, content) {
|
|
5417
|
-
ensurePrivateDir((0, import_node_path3.dirname)((0, import_node_path3.resolve)(path2)));
|
|
5418
|
-
writeFileAtomic(path2, content, PRIVATE_FILE_MODE);
|
|
5419
|
-
}
|
|
5420
|
-
function fsyncDir(path2) {
|
|
5421
|
-
let fd = null;
|
|
5422
|
-
try {
|
|
5423
|
-
fd = (0, import_node_fs4.openSync)(path2, "r");
|
|
5424
|
-
(0, import_node_fs4.fsyncSync)(fd);
|
|
5425
|
-
} catch {
|
|
5426
|
-
} finally {
|
|
5427
|
-
if (fd !== null) {
|
|
5428
|
-
try {
|
|
5429
|
-
(0, import_node_fs4.closeSync)(fd);
|
|
5430
|
-
} catch {
|
|
5431
|
-
}
|
|
5892
|
+
if (temporaryCreated) removeIfExists(temporary);
|
|
5893
|
+
if (error.code === "ENOENT") {
|
|
5894
|
+
throw new OperationsError("missing-file", `directory does not exist: ${parent}`, { cause: error });
|
|
5432
5895
|
}
|
|
5896
|
+
throw error;
|
|
5433
5897
|
}
|
|
5434
5898
|
}
|
|
5435
|
-
function removeIfExists(
|
|
5899
|
+
function removeIfExists(path3) {
|
|
5436
5900
|
try {
|
|
5437
|
-
(0, import_node_fs4.unlinkSync)(
|
|
5901
|
+
(0, import_node_fs4.unlinkSync)(path3);
|
|
5438
5902
|
} catch (error) {
|
|
5439
5903
|
if (error.code !== "ENOENT") throw error;
|
|
5440
5904
|
}
|
|
5441
5905
|
}
|
|
5442
5906
|
|
|
5443
5907
|
// collector/runtime.ts
|
|
5444
|
-
|
|
5908
|
+
var HEALTH_WRITE_INTERVAL_MS = 5e3;
|
|
5909
|
+
async function runCollectorLoop(config, identity, statePath, mock) {
|
|
5445
5910
|
const temporary = mock && !process.env.COLLECTOR_STATE_FILE ? (0, import_node_fs5.mkdtempSync)((0, import_node_path4.join)((0, import_node_os2.tmpdir)(), "rigscope-mock-")) : null;
|
|
5446
5911
|
const stateDir = temporary ?? (0, import_node_path4.dirname)(statePath);
|
|
5447
5912
|
const releaseLease = acquireProcessLease((0, import_node_path4.join)(stateDir, "collector-runtime.lock"));
|
|
@@ -5458,6 +5923,7 @@ async function runDurableCollector(config, identity, statePath, mock) {
|
|
|
5458
5923
|
stateDir,
|
|
5459
5924
|
baseIntervalMs: config.intervalMs,
|
|
5460
5925
|
identity,
|
|
5926
|
+
queueMode: config.queueMode,
|
|
5461
5927
|
budget: budget2,
|
|
5462
5928
|
allocateSequence: () => sequence.next(),
|
|
5463
5929
|
sampler,
|
|
@@ -5492,8 +5958,8 @@ async function runDurableCollector(config, identity, statePath, mock) {
|
|
|
5492
5958
|
const at = performance.now();
|
|
5493
5959
|
for (const connection of connections) connection.acceptSample(record, at);
|
|
5494
5960
|
const healthFile = process.env.COLLECTOR_HEALTH_FILE;
|
|
5495
|
-
if (healthFile && Date.now() - lastHealthWrite >=
|
|
5496
|
-
|
|
5961
|
+
if (healthFile && Date.now() - lastHealthWrite >= HEALTH_WRITE_INTERVAL_MS) {
|
|
5962
|
+
writePrivateFileBuffered(healthFile, JSON.stringify({
|
|
5497
5963
|
version: APPLICATION_VERSION,
|
|
5498
5964
|
pid: process.pid,
|
|
5499
5965
|
sampledAt: Date.now()
|
|
@@ -5520,7 +5986,7 @@ async function runDurableCollector(config, identity, statePath, mock) {
|
|
|
5520
5986
|
for (const connection of connections) connection.stop();
|
|
5521
5987
|
releaseLease();
|
|
5522
5988
|
if (temporary) (0, import_node_fs5.rmSync)(temporary, { recursive: true, force: true });
|
|
5523
|
-
console.log("collector stopping; durable pending delivery saved");
|
|
5989
|
+
console.log(config.queueMode === "durable" ? "collector stopping; durable pending delivery saved" : "collector stopping; unacknowledged samples in RAM are discarded");
|
|
5524
5990
|
setTimeout(() => process.exit(0), 250).unref();
|
|
5525
5991
|
setTimeout(() => process.exit(0), 3e3).unref();
|
|
5526
5992
|
};
|
|
@@ -5599,7 +6065,7 @@ async function run() {
|
|
|
5599
6065
|
runId,
|
|
5600
6066
|
bootId: boot.bootId
|
|
5601
6067
|
};
|
|
5602
|
-
await
|
|
6068
|
+
await runCollectorLoop(config, identity, process.env.COLLECTOR_STATE_FILE || defaultStatePath(), isMock);
|
|
5603
6069
|
return 0;
|
|
5604
6070
|
}
|
|
5605
6071
|
function isMainModule() {
|