rigscope 0.1.7 → 0.2.0

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.
Files changed (86) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +4 -4
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_not-found.html +1 -1
  12. package/.next/server/app/_not-found.rsc +2 -2
  13. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  14. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  16. package/.next/server/app/administration/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/administration.html +1 -1
  18. package/.next/server/app/administration.rsc +2 -2
  19. package/.next/server/app/administration.segments/_full.segment.rsc +2 -2
  20. package/.next/server/app/administration.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/alerts/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/alerts.html +1 -1
  24. package/.next/server/app/alerts.rsc +2 -2
  25. package/.next/server/app/alerts.segments/_full.segment.rsc +2 -2
  26. package/.next/server/app/alerts.segments/_tree.segment.rsc +2 -2
  27. package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +2 -2
  28. package/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  29. package/.next/server/app/connections.html +1 -1
  30. package/.next/server/app/connections.rsc +2 -2
  31. package/.next/server/app/connections.segments/_full.segment.rsc +2 -2
  32. package/.next/server/app/connections.segments/_tree.segment.rsc +2 -2
  33. package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +2 -2
  34. package/.next/server/app/history/page.js +2 -2
  35. package/.next/server/app/history/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/history.html +1 -1
  37. package/.next/server/app/history.rsc +5 -5
  38. package/.next/server/app/history.segments/_full.segment.rsc +5 -5
  39. package/.next/server/app/history.segments/_tree.segment.rsc +2 -2
  40. package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +5 -5
  41. package/.next/server/app/index.html +1 -1
  42. package/.next/server/app/index.rsc +2 -2
  43. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  44. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  45. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  46. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  47. package/.next/server/app/login.html +1 -1
  48. package/.next/server/app/login.rsc +2 -2
  49. package/.next/server/app/login.segments/_full.segment.rsc +2 -2
  50. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  51. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  52. package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -1
  53. package/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/.next/server/app/processes/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/processes.html +1 -1
  56. package/.next/server/app/processes.rsc +2 -2
  57. package/.next/server/app/processes.segments/_full.segment.rsc +2 -2
  58. package/.next/server/app/processes.segments/_tree.segment.rsc +2 -2
  59. package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +2 -2
  60. package/.next/server/app-paths-manifest.json +4 -4
  61. package/.next/server/chunks/168.js +1 -1
  62. package/.next/server/middleware-build-manifest.js +1 -1
  63. package/.next/server/pages/404.html +1 -1
  64. package/.next/server/pages/500.html +1 -1
  65. package/.next/static/chunks/408-351fe4a1e9a2d556.js +1 -0
  66. package/.next/static/chunks/app/history/page-adc25194b30c689d.js +3 -0
  67. package/.next/static/css/{f5d3162842dcac31.css → 2abef6d49c1c2970.css} +1 -1
  68. package/ARCHITECTURE.md +25 -9
  69. package/CHANGELOG.md +35 -0
  70. package/README.md +10 -3
  71. package/dist/cli.cjs +106 -2
  72. package/dist/collector.cjs +23 -6
  73. package/dist/hub.cjs +3441 -1457
  74. package/dist/ops.cjs +1196 -896
  75. package/dist/system.cjs +65 -0
  76. package/docs/configuration.md +33 -1
  77. package/docs/installation.md +3 -3
  78. package/docs/monitoring.md +57 -17
  79. package/docs/operations.md +38 -9
  80. package/docs/releasing.md +12 -12
  81. package/docs/telemetry-storage.md +187 -0
  82. package/package.json +3 -1
  83. package/.next/static/chunks/408-9fdba9e4e956ef08.js +0 -1
  84. package/.next/static/chunks/app/history/page-a25ba8fab7bd7cb9.js +0 -3
  85. /package/.next/static/{9SHY6v87ulfunqR2e84n9 → Jlbibe2_iFCljUO4-BUK7}/_buildManifest.js +0 -0
  86. /package/.next/static/{9SHY6v87ulfunqR2e84n9 → Jlbibe2_iFCljUO4-BUK7}/_ssgManifest.js +0 -0
package/dist/cli.cjs CHANGED
@@ -661,6 +661,13 @@ function verifyPassword(password, hash2) {
661
661
  // src/hub/config.ts
662
662
  var import_node_net = require("node:net");
663
663
  var import_node_path3 = require("node:path");
664
+
665
+ // src/contracts/telemetry.ts
666
+ var MAX_PERSISTENT_FLUSH_MS = 5e3;
667
+ var MIN_EPOCH = Date.parse("2000-01-01T00:00:00Z");
668
+ var MAX_EPOCH = Date.parse("2200-01-01T00:00:00Z");
669
+
670
+ // src/hub/config.ts
664
671
  var MAX_HUB_COLLECTORS_BYTES = 1024 * 1024;
665
672
  var MAX_COLLECTOR_ID_LENGTH = 128;
666
673
  var MAX_MACHINE_ID_LENGTH = 128;
@@ -688,6 +695,7 @@ function parseHubConfig(env) {
688
695
  maxCollectorConnections = parsed;
689
696
  }
690
697
  const connectionBudget = maxCollectorConnections === void 0 ? {} : { maxCollectorConnections };
698
+ const history = parseHistoryConfig(env);
691
699
  if (publicOrigin === void 0 !== (socketPath === void 0)) {
692
700
  throw new HubConfigError(
693
701
  "HUB_PUBLIC_ORIGIN and HUB_SOCKET_PATH must be set together: remote mode needs both the exact HTTPS origin and the Unix socket path"
@@ -707,6 +715,7 @@ function parseHubConfig(env) {
707
715
  collectors,
708
716
  development,
709
717
  remote: parseRemoteConfig(publicOrigin, socketPath),
718
+ history,
710
719
  ...connectionBudget
711
720
  };
712
721
  }
@@ -716,9 +725,65 @@ function parseHubConfig(env) {
716
725
  collectors,
717
726
  development,
718
727
  remote: null,
728
+ history,
719
729
  ...connectionBudget
720
730
  };
721
731
  }
732
+ var DEFAULT_MEMORY_BUDGET_BYTES = 128 * 1024 * 1024;
733
+ var DEFAULT_PERSISTENT_FLUSH_MS = MAX_PERSISTENT_FLUSH_MS;
734
+ var DEFAULT_RECORDING_MAX_DURATION_MS = 60 * 6e4;
735
+ var DEFAULT_RECORDING_MAX_BYTES = 64 * 1024 * 1024;
736
+ var DEFAULT_RECORDING_BACKLOG_MS = 10 * 6e4;
737
+ var DEFAULT_MAX_ACTIVE_RECORDINGS = 4;
738
+ var HOUR_MS = 36e5;
739
+ var DAY_MS = 864e5;
740
+ function parseHistoryConfig(env) {
741
+ const modeValue = env.HUB_TELEMETRY_MODE ?? "memory";
742
+ if (modeValue !== "memory" && modeValue !== "persistent") {
743
+ throw new HubConfigError("HUB_TELEMETRY_MODE must be memory or persistent");
744
+ }
745
+ const memoryBudgetBytes = envInteger(
746
+ env.HUB_HISTORY_MEMORY_BYTES,
747
+ DEFAULT_MEMORY_BUDGET_BYTES,
748
+ 1 * 1024 * 1024,
749
+ 16 * 1024 * 1024 * 1024,
750
+ "HUB_HISTORY_MEMORY_BYTES"
751
+ );
752
+ const flushIntervalMs = envInteger(
753
+ env.HUB_TELEMETRY_FLUSH_MS,
754
+ DEFAULT_PERSISTENT_FLUSH_MS,
755
+ 1e3,
756
+ MAX_PERSISTENT_FLUSH_MS,
757
+ "HUB_TELEMETRY_FLUSH_MS"
758
+ );
759
+ const retention = {
760
+ rawMs: envInteger(env.HUB_RETENTION_RAW_MS, 2 * DAY_MS, 0, 400 * DAY_MS, "HUB_RETENTION_RAW_MS"),
761
+ tenSecondMs: envInteger(env.HUB_RETENTION_10S_MS, 2 * HOUR_MS, 0, 30 * DAY_MS, "HUB_RETENTION_10S_MS"),
762
+ oneMinuteMs: envInteger(env.HUB_RETENTION_1M_MS, 30 * DAY_MS, 0, 400 * DAY_MS, "HUB_RETENTION_1M_MS"),
763
+ oneHourMs: envInteger(env.HUB_RETENTION_1H_MS, 400 * DAY_MS, 0, 800 * DAY_MS, "HUB_RETENTION_1H_MS"),
764
+ receiptsMs: envInteger(env.HUB_RECEIPT_RETENTION_MS, 7 * DAY_MS, HOUR_MS, 400 * DAY_MS, "HUB_RECEIPT_RETENTION_MS"),
765
+ gapsMs: envInteger(env.HUB_GAP_RETENTION_MS, 400 * DAY_MS, 0, 800 * DAY_MS, "HUB_GAP_RETENTION_MS")
766
+ };
767
+ const recordings = {
768
+ directory: nonEmpty(env.HUB_RECORDINGS_DIR) ?? null,
769
+ maxDurationMs: envInteger(env.HUB_RECORDING_MAX_DURATION_MS, DEFAULT_RECORDING_MAX_DURATION_MS, 1e3, 24 * 60 * 6e4, "HUB_RECORDING_MAX_DURATION_MS"),
770
+ maxBytes: envInteger(env.HUB_RECORDING_MAX_BYTES, DEFAULT_RECORDING_MAX_BYTES, 64 * 1024, 1024 * 1024 * 1024, "HUB_RECORDING_MAX_BYTES"),
771
+ backlogMs: envInteger(env.HUB_RECORDING_BACKLOG_MS, DEFAULT_RECORDING_BACKLOG_MS, 0, 24 * 60 * 6e4, "HUB_RECORDING_BACKLOG_MS"),
772
+ maxActive: envInteger(env.HUB_RECORDING_MAX_ACTIVE, DEFAULT_MAX_ACTIVE_RECORDINGS, 1, 32, "HUB_RECORDING_MAX_ACTIVE")
773
+ };
774
+ return { mode: modeValue, memoryBudgetBytes, flushIntervalMs, retention, recordings };
775
+ }
776
+ function envInteger(raw, fallback, min, max, name) {
777
+ if (raw === void 0 || raw === "") return fallback;
778
+ if (!/^\d+$/.test(raw)) {
779
+ throw new HubConfigError(`${name} must be a nonnegative integer`);
780
+ }
781
+ const value = Number(raw);
782
+ if (!Number.isSafeInteger(value) || value < min || value > max) {
783
+ throw new HubConfigError(`${name} must be between ${min} and ${max}`);
784
+ }
785
+ return value;
786
+ }
722
787
  function nonEmpty(value) {
723
788
  return value === void 0 || value === "" ? void 0 : value;
724
789
  }
@@ -1446,8 +1511,45 @@ var MIGRATIONS = [
1446
1511
  },
1447
1512
  { version: 3, name: "admin", up: adminSchema },
1448
1513
  { version: 4, name: "alerts", up: installAlertSchema },
1449
- { version: 5, name: "collector_registry", up: installRegistrySchema }
1514
+ { version: 5, name: "collector_registry", up: installRegistrySchema },
1515
+ { version: 6, name: "telemetry_modes_and_recordings", up: telemetryModesAndRecordings }
1450
1516
  ];
1517
+ function telemetryModesAndRecordings(db) {
1518
+ db.exec(`
1519
+ CREATE TABLE IF NOT EXISTS points_10s (
1520
+ series_id INTEGER NOT NULL,
1521
+ bucket_ms INTEGER NOT NULL,
1522
+ min REAL,
1523
+ max REAL,
1524
+ sum REAL NOT NULL DEFAULT 0,
1525
+ count INTEGER NOT NULL DEFAULT 0,
1526
+ null_count INTEGER NOT NULL DEFAULT 0,
1527
+ PRIMARY KEY (series_id, bucket_ms)
1528
+ ) WITHOUT ROWID;
1529
+
1530
+ CREATE TABLE IF NOT EXISTS recordings (
1531
+ id TEXT PRIMARY KEY,
1532
+ created_by TEXT NOT NULL,
1533
+ status TEXT NOT NULL,
1534
+ machines_json TEXT NOT NULL,
1535
+ metrics_json TEXT NOT NULL,
1536
+ resolution_ms INTEGER NOT NULL,
1537
+ created_at_ms INTEGER NOT NULL,
1538
+ started_at_ms INTEGER NOT NULL,
1539
+ stopped_at_ms INTEGER,
1540
+ deadline_ms INTEGER NOT NULL,
1541
+ max_bytes INTEGER NOT NULL,
1542
+ bytes_written INTEGER NOT NULL DEFAULT 0,
1543
+ file_name TEXT NOT NULL,
1544
+ include_backlog INTEGER NOT NULL DEFAULT 0,
1545
+ reason TEXT,
1546
+ error TEXT
1547
+ );
1548
+
1549
+ CREATE INDEX IF NOT EXISTS recordings_created
1550
+ ON recordings (created_at_ms DESC);
1551
+ `);
1552
+ }
1451
1553
 
1452
1554
  // src/hub/storage/db.ts
1453
1555
  var BUSY_TIMEOUT_MS = 5e3;
@@ -1524,7 +1626,7 @@ var SqliteHubDatabase = class {
1524
1626
  const walBudgetBytes = Math.max(64 * 1024, Math.min(HUB_WAL_RESERVE_BYTES, Math.floor(limitBytes / 16)));
1525
1627
  const mainBudgetBytes = Math.max(pageSize, limitBytes - walBudgetBytes);
1526
1628
  const requestedPages = Math.max(1, Math.floor(mainBudgetBytes / pageSize));
1527
- const checkpointPages = Math.max(16, Math.min(1e3, Math.floor(walBudgetBytes / pageSize)));
1629
+ const checkpointPages = Math.max(16, Math.min(16384, Math.floor(walBudgetBytes / pageSize)));
1528
1630
  this.db.exec(`PRAGMA journal_size_limit = ${walBudgetBytes}`);
1529
1631
  this.db.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);
1530
1632
  let maxPageCount = requestedPages;
@@ -3555,7 +3657,9 @@ var COUNTED_TABLES = /* @__PURE__ */ new Set([
3555
3657
  "machine_meta",
3556
3658
  "machine_latest",
3557
3659
  "inventory",
3660
+ "points_10s",
3558
3661
  "gaps",
3662
+ "recordings",
3559
3663
  "alert_rules",
3560
3664
  "alert_incidents",
3561
3665
  "alert_jobs",
@@ -1550,6 +1550,8 @@ var DestinationOutbox = class _DestinationOutbox {
1550
1550
  var MAX_MESSAGE_BYTES = 262144;
1551
1551
  var MAX_DETAIL_BYTES = 1048576;
1552
1552
  var MAX_BATCH_RECORDS = 32;
1553
+ var MAX_IN_FLIGHT_BATCHES = 64;
1554
+ var MAX_PERSISTENT_FLUSH_MS = 5e3;
1553
1555
  var MAX_CORES = 1024;
1554
1556
  var MAX_GPUS = 16;
1555
1557
  var MAX_FILESYSTEMS = 64;
@@ -1572,8 +1574,16 @@ function parseHubMessage(value) {
1572
1574
  switch (o.type) {
1573
1575
  case "welcome": {
1574
1576
  keys(o, ["type", "hubRunId", "machineId", "delivery", "maxInFlightBatches"], "welcome");
1575
- if (o.delivery !== "durable") throw new Error(`invalid welcome: unexpected delivery ${desc(o.delivery)}`);
1576
- return { type: "welcome", hubRunId: id(o.hubRunId, "hubRunId"), machineId: id(o.machineId, "machineId"), delivery: "durable", maxInFlightBatches: int(o.maxInFlightBatches, "maxInFlightBatches", 1, 64) };
1577
+ if (o.delivery !== "durable" && o.delivery !== "memory") {
1578
+ throw new Error(`invalid welcome: unexpected delivery ${desc(o.delivery)}`);
1579
+ }
1580
+ return {
1581
+ type: "welcome",
1582
+ hubRunId: id(o.hubRunId, "hubRunId"),
1583
+ machineId: id(o.machineId, "machineId"),
1584
+ delivery: o.delivery,
1585
+ maxInFlightBatches: int(o.maxInFlightBatches, "maxInFlightBatches", 1, MAX_IN_FLIGHT_BATCHES)
1586
+ };
1577
1587
  }
1578
1588
  case "ack": {
1579
1589
  keys(o, ["type", "accepted", "rejected"], "ack", ["retryAfterMs"]);
@@ -1673,7 +1683,7 @@ var MAX_SOCKET_INBOUND_BYTES = MAX_MESSAGE_BYTES;
1673
1683
  var MAX_UNACKED_SEND_BYTES = 256 * 1024;
1674
1684
  var DEFAULT_ACK_TIMEOUT_MS = 3e4;
1675
1685
  var DEFAULT_REPLAY_BATCHES_PER_SECOND = 4;
1676
- var DEFAULT_MAX_LOCAL_IN_FLIGHT_BATCHES = 8;
1686
+ var DEFAULT_MAX_LOCAL_IN_FLIGHT_BATCHES = 64;
1677
1687
  var DEFAULT_MAX_BATCH_BYTES = 2e5;
1678
1688
  var OUTBOX_ENFORCE_INTERVAL_MS = 1e4;
1679
1689
  var MAX_OUTBOUND_BUFFERED_BYTES = MAX_UNACKED_SEND_BYTES;
@@ -2154,9 +2164,12 @@ var DestinationSession = class {
2154
2164
  // -------------------------------------------------------------------------
2155
2165
  // Delivery
2156
2166
  // -------------------------------------------------------------------------
2157
- /** Window slots reserved for live delivery (one when the window is > 1). */
2167
+ /** Reserve a flush window of live samples so replay cannot starve fresh delivery. */
2158
2168
  liveReservation() {
2159
- return this.inFlightWindow > 1 ? 1 : 0;
2169
+ if (this.inFlightWindow <= 1) return 0;
2170
+ if (this.inFlightWindow < MAX_IN_FLIGHT_BATCHES) return 1;
2171
+ const liveSlots = Math.ceil(MAX_PERSISTENT_FLUSH_MS / this.expectedIntervalMs) + 2;
2172
+ return Math.min(this.inFlightWindow - 1, liveSlots);
2160
2173
  }
2161
2174
  replayCapacity() {
2162
2175
  return Math.max(0, this.inFlightWindow - this.liveReservation());
@@ -2258,6 +2271,7 @@ var DestinationSession = class {
2258
2271
  this.inFlight.delete(batchId);
2259
2272
  this.outbox.release(batch.refs);
2260
2273
  this.log(`ack timed out for ${batch.refs.length} record(s); will resend`);
2274
+ this.sendLiveBatch();
2261
2275
  this.pumpReplay();
2262
2276
  }
2263
2277
  handleAck(message) {
@@ -2293,6 +2307,7 @@ var DestinationSession = class {
2293
2307
  }
2294
2308
  this.log(`hub asked to retry after ${delay}ms; replay paused`);
2295
2309
  }
2310
+ this.sendLiveBatch();
2296
2311
  this.pumpReplay();
2297
2312
  }
2298
2313
  batchIdOf(target) {
@@ -5217,7 +5232,7 @@ function budget(value, fallback) {
5217
5232
  // package.json
5218
5233
  var package_default = {
5219
5234
  name: "rigscope",
5220
- version: "0.1.7",
5235
+ version: "0.2.0",
5221
5236
  private: false,
5222
5237
  engines: {
5223
5238
  node: ">=26.0.0"
@@ -5239,6 +5254,7 @@ var package_default = {
5239
5254
  test: "node scripts/test.cjs",
5240
5255
  "test:runtime": "npm run build:runtime && node scripts/test-runtime.cjs",
5241
5256
  "test:dev": "npm run build:runtime && node scripts/test.cjs --suite development",
5257
+ "benchmark:telemetry": "tsx scripts/telemetry-benchmark.ts",
5242
5258
  "validate:https": "node scripts/validate-https.cjs",
5243
5259
  "build:ui": "next build --webpack",
5244
5260
  rigscope: "node bin/rigscope.cjs",
@@ -5287,6 +5303,7 @@ var package_default = {
5287
5303
  "docs/system-installation.md",
5288
5304
  "docs/configuration.md",
5289
5305
  "docs/monitoring.md",
5306
+ "docs/telemetry-storage.md",
5290
5307
  "docs/operations.md",
5291
5308
  "docs/bottlenecks.md",
5292
5309
  "docs/exports.md",