rigscope 0.1.0 → 0.1.2
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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +2 -2
- 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 +2 -2
- package/.next/server/app/connections.segments/_full.segment.rsc +2 -2
- package/.next/server/app/connections.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/history/page.js +2 -2
- 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 +5 -5
- package/.next/server/app/history.segments/_full.segment.rsc +5 -5
- package/.next/server/app/history.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +5 -5
- 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 +2 -2
- 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 +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/machines/[machineId]/page.js +1 -1
- 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 +2 -2
- 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/168.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/408-4fe7147e38981ee1.js +1 -0
- package/.next/static/chunks/app/history/page-22be927a4c3a9c76.js +3 -0
- package/.next/static/chunks/app/machines/[machineId]/page-916ee73de5eb067d.js +1 -0
- package/.next/static/css/3fbf75e4c471c6a8.css +1 -0
- package/CHANGELOG.md +27 -0
- package/README.md +53 -136
- package/dist/collector.cjs +9 -1
- package/dist/hub.cjs +14 -5
- package/dist/ops.cjs +8 -4
- package/docs/bottlenecks.md +1 -1
- package/docs/configuration.md +85 -0
- package/docs/development.md +1 -1
- package/docs/installation.md +205 -376
- package/docs/monitoring.md +112 -0
- package/docs/operations.md +103 -68
- package/docs/releasing.md +16 -14
- package/docs/scale-validation.md +34 -3
- package/docs/stress-2026-11-04/180301-probe-m100-u1-s1-20000ms.json +412 -0
- package/docs/stress-2026-11-04/180335-probe-m200-u1-s1-20000ms.json +412 -0
- package/docs/stress-2026-11-04/180409-probe-m400-u1-s1-20000ms.json +412 -0
- package/docs/stress-2026-11-04/180443-probe-m800-u1-s1-20000ms.json +412 -0
- package/docs/stress-2026-11-04/180518-probe-m600-u1-s1-20000ms.json +412 -0
- package/docs/stress-2026-11-04/180553-probe-m500-u1-s1-20000ms.json +412 -0
- package/docs/stress-2026-11-04/180628-probe-m550-u1-s1-20000ms.json +412 -0
- package/docs/stress-2026-11-04/180716-probe-m800-u1-s0-20000ms.json +415 -0
- package/docs/stress-2026-11-04/180751-probe-m1600-u1-s0-20000ms.json +415 -0
- package/docs/stress-2026-11-04/180828-probe-m3200-u1-s0-20000ms.json +416 -0
- package/docs/stress-2026-11-04/180941-probe-m2400-u1-s0-20000ms.json +416 -0
- package/docs/stress-2026-11-04/181031-probe-m2000-u1-s0-20000ms.json +416 -0
- package/docs/stress-2026-11-04/181114-probe-m1800-u1-s0-20000ms.json +416 -0
- package/docs/stress-2026-11-04/181203-probe-m100-u64-s64-20000ms.json +418 -0
- package/docs/stress-2026-11-04/181304-probe-m200-u64-s64-20000ms.json +418 -0
- package/docs/stress-2026-11-04/181405-probe-m400-u64-s64-20000ms.json +418 -0
- package/docs/stress-2026-11-04/181507-probe-m500-u64-s64-20000ms.json +418 -0
- package/docs/stress-2026-11-04/181609-probe-m450-u64-s64-20000ms.json +418 -0
- package/docs/stress-2026-11-04/181845-probe-m2-u2-s2-3000ms.json +419 -0
- package/docs/stress-2026-11-04/181931-probe-m500-u64-s64-30000ms.json +420 -0
- package/docs/stress-2026-11-04/182108-probe-m1800-u1-s0-120000ms.json +418 -0
- package/docs/stress-2026-11-04/182354-probe-m1200-u1-s0-120000ms.json +418 -0
- package/docs/stress-2026-11-04/182625-probe-m900-u1-s0-120000ms.json +418 -0
- package/docs/stress-2026-11-04/182847-probe-m1050-u1-s0-120000ms.json +418 -0
- package/docs/stress-2026-11-04/183112-probe-m1125-u1-s0-120000ms.json +418 -0
- package/docs/stress-2026-11-04/193608-probe-m500-u64-s64-120000ms.json +420 -0
- package/docs/stress-2026-11-04/193909-probe-m529-u1-s1-15000ms.json +420 -0
- package/docs/stress-2026-11-04/193940-probe-m530-u1-s1-15000ms.json +420 -0
- package/docs/stress-2026-11-04/194011-probe-m100-u1-s65-10000ms.json +421 -0
- package/docs/stress-2026-11-04/README.md +205 -0
- package/docs/stress-2026-11-04/index.jsonl +29 -0
- package/docs/stress-2026-11-04/manifest.json +13 -0
- package/docs/stress-2026-11-04/results.html +22 -0
- package/docs/system-installation.md +195 -0
- package/package.json +9 -1
- package/packaging/system/README.md +1 -1
- package/.next/static/chunks/408-18819c6490a5f8d3.js +0 -1
- package/.next/static/chunks/app/history/page-3fa5846eca26b7f7.js +0 -3
- package/.next/static/chunks/app/machines/[machineId]/page-a5aa1d8676272f38.js +0 -1
- package/.next/static/css/e41ccbccc3afaae5.css +0 -1
- /package/.next/static/{DZKWF9ur7H5wI0c7RAhdq → npHoP6NSY976Yaye-xi86}/_buildManifest.js +0 -0
- /package/.next/static/{DZKWF9ur7H5wI0c7RAhdq → npHoP6NSY976Yaye-xi86}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tool": "rigscope-scale-harness",
|
|
3
|
+
"synthetic": true,
|
|
4
|
+
"disclaimer": "Synthetic bounded workload on an isolated temporary hub/database, not a capacity, latency, or hardware guarantee. Values depend on this host, disk, Node version, payload, cadence, viewer count and concurrent activity.",
|
|
5
|
+
"generatedAt": "2026-11-04T19:40:30.382Z",
|
|
6
|
+
"node": "v26.13.0",
|
|
7
|
+
"host": {
|
|
8
|
+
"platform": "linux",
|
|
9
|
+
"release": "7.3.0-9-generic",
|
|
10
|
+
"arch": "x64",
|
|
11
|
+
"cpuModel": "AMD EPYC 7402P 24-Core Processor",
|
|
12
|
+
"logicalCores": 48,
|
|
13
|
+
"totalMemoryBytes": 130351214592,
|
|
14
|
+
"loadAverage": [
|
|
15
|
+
0.92,
|
|
16
|
+
0.93,
|
|
17
|
+
0.57
|
|
18
|
+
],
|
|
19
|
+
"node": "v26.13.0"
|
|
20
|
+
},
|
|
21
|
+
"command": "npx tsx scripts/scale-harness.ts --case fleet-75m --machines 100 --viewers 1 --sse-clients 65 --sse-workers 4 --duration-ms 10000 --history-interval-ms 1000 --database-budget-mb 1024 --json-out /home/triet/Project/rigscope/docs/stress-2026-11-04/194011-probe-m100-u1-s65-10000ms.json",
|
|
22
|
+
"suite": [
|
|
23
|
+
{
|
|
24
|
+
"name": "fleet-75m",
|
|
25
|
+
"description": "75 machines with eight concurrent History viewers and four SSE viewers.",
|
|
26
|
+
"machines": 100,
|
|
27
|
+
"durationMs": 10000,
|
|
28
|
+
"sampleIntervalMs": 1000,
|
|
29
|
+
"recordsPerBatch": 1,
|
|
30
|
+
"viewers": 1,
|
|
31
|
+
"sseClients": 65,
|
|
32
|
+
"slowSseClients": 0,
|
|
33
|
+
"slowSsePauseAfterMs": 0,
|
|
34
|
+
"slowSsePauseMs": 0,
|
|
35
|
+
"historyIntervalMs": 1000,
|
|
36
|
+
"historyMetricSet": "standard",
|
|
37
|
+
"churnEveryMs": null,
|
|
38
|
+
"churnMachines": 0,
|
|
39
|
+
"replayRecords": 0
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"cases": [
|
|
43
|
+
{
|
|
44
|
+
"name": "fleet-75m",
|
|
45
|
+
"description": "75 machines with eight concurrent History viewers and four SSE viewers.",
|
|
46
|
+
"config": {
|
|
47
|
+
"name": "fleet-75m",
|
|
48
|
+
"description": "75 machines with eight concurrent History viewers and four SSE viewers.",
|
|
49
|
+
"machines": 100,
|
|
50
|
+
"durationMs": 10000,
|
|
51
|
+
"sampleIntervalMs": 1000,
|
|
52
|
+
"recordsPerBatch": 1,
|
|
53
|
+
"viewers": 1,
|
|
54
|
+
"sseClients": 65,
|
|
55
|
+
"slowSseClients": 0,
|
|
56
|
+
"slowSsePauseAfterMs": 0,
|
|
57
|
+
"slowSsePauseMs": 0,
|
|
58
|
+
"historyIntervalMs": 1000,
|
|
59
|
+
"historyMetricSet": "standard",
|
|
60
|
+
"churnEveryMs": null,
|
|
61
|
+
"churnMachines": 0,
|
|
62
|
+
"replayRecords": 0,
|
|
63
|
+
"warmupMs": 1500,
|
|
64
|
+
"clientWarmupMs": 1000,
|
|
65
|
+
"sseWorkers": 4,
|
|
66
|
+
"maxCollectorConnections": 110,
|
|
67
|
+
"databaseBudgetBytes": 1073741824
|
|
68
|
+
},
|
|
69
|
+
"host": {
|
|
70
|
+
"platform": "linux",
|
|
71
|
+
"release": "7.3.0-9-generic",
|
|
72
|
+
"arch": "x64",
|
|
73
|
+
"cpuModel": "AMD EPYC 7402P 24-Core Processor",
|
|
74
|
+
"logicalCores": 48,
|
|
75
|
+
"totalMemoryBytes": 130351214592,
|
|
76
|
+
"loadAverage": [
|
|
77
|
+
1,
|
|
78
|
+
0.94,
|
|
79
|
+
0.57
|
|
80
|
+
],
|
|
81
|
+
"node": "v26.13.0"
|
|
82
|
+
},
|
|
83
|
+
"authorization": {
|
|
84
|
+
"anonymousDenied": true,
|
|
85
|
+
"scopedHistory": true,
|
|
86
|
+
"scopedSnapshot": true,
|
|
87
|
+
"scopedReset": true
|
|
88
|
+
},
|
|
89
|
+
"ingestion": {
|
|
90
|
+
"recordsSent": 1500,
|
|
91
|
+
"recordsAccepted": 1500,
|
|
92
|
+
"recordsRejected": 0,
|
|
93
|
+
"replayRecordsSent": 0,
|
|
94
|
+
"replayRecordsAccepted": 0,
|
|
95
|
+
"retryAcks": 0,
|
|
96
|
+
"unackedLiveRecords": 0,
|
|
97
|
+
"sendErrors": 0,
|
|
98
|
+
"ackLatencyMs": {
|
|
99
|
+
"count": 1500,
|
|
100
|
+
"min": 3.32,
|
|
101
|
+
"avg": 39.4,
|
|
102
|
+
"p50": 38.02,
|
|
103
|
+
"p95": 63.78,
|
|
104
|
+
"p99": 67.24,
|
|
105
|
+
"max": 71.89
|
|
106
|
+
},
|
|
107
|
+
"sampleToCommitLagMs": {
|
|
108
|
+
"count": 1500,
|
|
109
|
+
"min": 3,
|
|
110
|
+
"avg": 39.39,
|
|
111
|
+
"p50": 38,
|
|
112
|
+
"p95": 64,
|
|
113
|
+
"p99": 67,
|
|
114
|
+
"max": 72
|
|
115
|
+
},
|
|
116
|
+
"messagesSent": 1600,
|
|
117
|
+
"backpressureSkips": 0,
|
|
118
|
+
"connects": 100,
|
|
119
|
+
"reconnects": 0,
|
|
120
|
+
"reconnectFailures": 0,
|
|
121
|
+
"collectorRecoveryMs": {
|
|
122
|
+
"count": 0,
|
|
123
|
+
"min": 0,
|
|
124
|
+
"avg": 0,
|
|
125
|
+
"p50": 0,
|
|
126
|
+
"p95": 0,
|
|
127
|
+
"p99": 0,
|
|
128
|
+
"max": 0
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"history": {
|
|
132
|
+
"queries": 10,
|
|
133
|
+
"ok": 10,
|
|
134
|
+
"rateLimited": 0,
|
|
135
|
+
"errors": 0,
|
|
136
|
+
"latencyMs": {
|
|
137
|
+
"count": 10,
|
|
138
|
+
"min": 3.83,
|
|
139
|
+
"avg": 4.49,
|
|
140
|
+
"p50": 4.66,
|
|
141
|
+
"p95": 5.28,
|
|
142
|
+
"p99": 5.28,
|
|
143
|
+
"max": 5.28
|
|
144
|
+
},
|
|
145
|
+
"series": {
|
|
146
|
+
"count": 10,
|
|
147
|
+
"min": 4,
|
|
148
|
+
"avg": 4,
|
|
149
|
+
"p50": 4,
|
|
150
|
+
"p95": 4,
|
|
151
|
+
"p99": 4,
|
|
152
|
+
"max": 4
|
|
153
|
+
},
|
|
154
|
+
"points": {
|
|
155
|
+
"count": 10,
|
|
156
|
+
"min": 12,
|
|
157
|
+
"avg": 30,
|
|
158
|
+
"p50": 32,
|
|
159
|
+
"p95": 48,
|
|
160
|
+
"p99": 48,
|
|
161
|
+
"max": 48
|
|
162
|
+
},
|
|
163
|
+
"truncated": 0,
|
|
164
|
+
"tiers": {
|
|
165
|
+
"raw": 10
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"stream": {
|
|
169
|
+
"clients": 65,
|
|
170
|
+
"httpStatuses": {
|
|
171
|
+
"200": 64,
|
|
172
|
+
"503": 1
|
|
173
|
+
},
|
|
174
|
+
"slowClients": 0,
|
|
175
|
+
"events": 896,
|
|
176
|
+
"snapshots": 64,
|
|
177
|
+
"updates": 832,
|
|
178
|
+
"resets": 0,
|
|
179
|
+
"bytes": 176808198,
|
|
180
|
+
"eventLagMs": {
|
|
181
|
+
"count": 896,
|
|
182
|
+
"min": 24,
|
|
183
|
+
"avg": 428.26,
|
|
184
|
+
"p50": 440,
|
|
185
|
+
"p95": 481,
|
|
186
|
+
"p99": 847,
|
|
187
|
+
"max": 874
|
|
188
|
+
},
|
|
189
|
+
"normalEventLagMs": {
|
|
190
|
+
"count": 896,
|
|
191
|
+
"min": 24,
|
|
192
|
+
"avg": 428.26,
|
|
193
|
+
"p50": 440,
|
|
194
|
+
"p95": 481,
|
|
195
|
+
"p99": 847,
|
|
196
|
+
"max": 874
|
|
197
|
+
},
|
|
198
|
+
"slowEventLagMs": {
|
|
199
|
+
"count": 0,
|
|
200
|
+
"min": 0,
|
|
201
|
+
"avg": 0,
|
|
202
|
+
"p50": 0,
|
|
203
|
+
"p95": 0,
|
|
204
|
+
"p99": 0,
|
|
205
|
+
"max": 0
|
|
206
|
+
},
|
|
207
|
+
"firstEventMs": {
|
|
208
|
+
"count": 64,
|
|
209
|
+
"min": 28.8,
|
|
210
|
+
"avg": 100.63,
|
|
211
|
+
"p50": 108,
|
|
212
|
+
"p95": 145.31,
|
|
213
|
+
"p99": 151.84,
|
|
214
|
+
"max": 151.84
|
|
215
|
+
},
|
|
216
|
+
"reconnects": 0,
|
|
217
|
+
"resetsAfterReconnect": 0,
|
|
218
|
+
"slowPauses": 0,
|
|
219
|
+
"slowDrops": 1,
|
|
220
|
+
"sseRecoveryMs": {
|
|
221
|
+
"count": 0,
|
|
222
|
+
"min": 0,
|
|
223
|
+
"avg": 0,
|
|
224
|
+
"p50": 0,
|
|
225
|
+
"p95": 0,
|
|
226
|
+
"p99": 0,
|
|
227
|
+
"max": 0
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"hub": {
|
|
231
|
+
"baseline": {
|
|
232
|
+
"pid": 204091,
|
|
233
|
+
"uptimeMs": 3300.52,
|
|
234
|
+
"hubRunId": "bf98eaa6-f0f6-4b1b-b64c-3d92f712bdcf",
|
|
235
|
+
"eventLoopDelayMs": {
|
|
236
|
+
"p50": 10.11,
|
|
237
|
+
"p95": 13.43,
|
|
238
|
+
"p99": 24.13,
|
|
239
|
+
"max": 25.85
|
|
240
|
+
},
|
|
241
|
+
"eventLoopUtilization": 0.1791,
|
|
242
|
+
"cpuPercent": 22.05,
|
|
243
|
+
"cpuMs": {
|
|
244
|
+
"user": 219.44,
|
|
245
|
+
"system": 50.09
|
|
246
|
+
},
|
|
247
|
+
"rssBytes": 151478272,
|
|
248
|
+
"rssPeakBytes": 151478272,
|
|
249
|
+
"heapUsedBytes": 42367504,
|
|
250
|
+
"database": {
|
|
251
|
+
"bytes": 6317192,
|
|
252
|
+
"walBytes": 4412552,
|
|
253
|
+
"mainBytes": 1904640
|
|
254
|
+
},
|
|
255
|
+
"store": {
|
|
256
|
+
"queuedRecords": 0,
|
|
257
|
+
"queuedBatches": 0,
|
|
258
|
+
"lastFlushMs": 1793821214429,
|
|
259
|
+
"lastFlushDurationMs": 7,
|
|
260
|
+
"committedRecords": 300,
|
|
261
|
+
"duplicateRecords": 0,
|
|
262
|
+
"rejectedRecords": 0,
|
|
263
|
+
"writeFailures": 0,
|
|
264
|
+
"lastWriteError": null,
|
|
265
|
+
"diskFullFailures": 0,
|
|
266
|
+
"dbBytes": 6317192,
|
|
267
|
+
"dbWalBytes": 4412552,
|
|
268
|
+
"dbBytesLimit": 1073741824,
|
|
269
|
+
"retention": {
|
|
270
|
+
"lastRunAtMs": 0,
|
|
271
|
+
"lastRunDurationMs": 0,
|
|
272
|
+
"deletedRows": 0,
|
|
273
|
+
"pressure": false,
|
|
274
|
+
"effectiveRawCutoffMs": null,
|
|
275
|
+
"physicalBytes": 6317192,
|
|
276
|
+
"physicalLimitBytes": 1073741824,
|
|
277
|
+
"physicalPressure": false,
|
|
278
|
+
"walBytes": 4412552
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"liveClients": 64,
|
|
282
|
+
"sessions": 2,
|
|
283
|
+
"connectedMachines": 100,
|
|
284
|
+
"configuredMachines": 100,
|
|
285
|
+
"snapshotBytes": 197185
|
|
286
|
+
},
|
|
287
|
+
"window": {
|
|
288
|
+
"pid": 204091,
|
|
289
|
+
"uptimeMs": 13306.01,
|
|
290
|
+
"hubRunId": "bf98eaa6-f0f6-4b1b-b64c-3d92f712bdcf",
|
|
291
|
+
"eventLoopDelayMs": {
|
|
292
|
+
"p50": 10.11,
|
|
293
|
+
"p95": 17.2,
|
|
294
|
+
"p99": 37.03,
|
|
295
|
+
"max": 48.04
|
|
296
|
+
},
|
|
297
|
+
"eventLoopUtilization": 0.1304,
|
|
298
|
+
"cpuPercent": 15.65,
|
|
299
|
+
"cpuMs": {
|
|
300
|
+
"user": 1233.71,
|
|
301
|
+
"system": 331.49
|
|
302
|
+
},
|
|
303
|
+
"rssBytes": 170266624,
|
|
304
|
+
"rssPeakBytes": 170266624,
|
|
305
|
+
"heapUsedBytes": 53632832,
|
|
306
|
+
"database": {
|
|
307
|
+
"bytes": 8240520,
|
|
308
|
+
"walBytes": 4808072,
|
|
309
|
+
"mainBytes": 3432448
|
|
310
|
+
},
|
|
311
|
+
"store": {
|
|
312
|
+
"queuedRecords": 0,
|
|
313
|
+
"queuedBatches": 0,
|
|
314
|
+
"lastFlushMs": 1793821224449,
|
|
315
|
+
"lastFlushDurationMs": 14,
|
|
316
|
+
"committedRecords": 1300,
|
|
317
|
+
"duplicateRecords": 0,
|
|
318
|
+
"rejectedRecords": 0,
|
|
319
|
+
"writeFailures": 0,
|
|
320
|
+
"lastWriteError": null,
|
|
321
|
+
"diskFullFailures": 0,
|
|
322
|
+
"dbBytes": 8240520,
|
|
323
|
+
"dbWalBytes": 4808072,
|
|
324
|
+
"dbBytesLimit": 1073741824,
|
|
325
|
+
"retention": {
|
|
326
|
+
"lastRunAtMs": 0,
|
|
327
|
+
"lastRunDurationMs": 0,
|
|
328
|
+
"deletedRows": 0,
|
|
329
|
+
"pressure": false,
|
|
330
|
+
"effectiveRawCutoffMs": null,
|
|
331
|
+
"physicalBytes": 8240520,
|
|
332
|
+
"physicalLimitBytes": 1073741824,
|
|
333
|
+
"physicalPressure": false,
|
|
334
|
+
"walBytes": 4808072
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
"liveClients": 64,
|
|
338
|
+
"sessions": 2,
|
|
339
|
+
"connectedMachines": 100,
|
|
340
|
+
"configuredMachines": 100,
|
|
341
|
+
"snapshotBytes": 197285
|
|
342
|
+
},
|
|
343
|
+
"final": {
|
|
344
|
+
"pid": 204091,
|
|
345
|
+
"uptimeMs": 16312.78,
|
|
346
|
+
"hubRunId": "bf98eaa6-f0f6-4b1b-b64c-3d92f712bdcf",
|
|
347
|
+
"eventLoopDelayMs": {
|
|
348
|
+
"p50": 10.11,
|
|
349
|
+
"p95": 11.39,
|
|
350
|
+
"p99": 36.01,
|
|
351
|
+
"max": 59.15
|
|
352
|
+
},
|
|
353
|
+
"eventLoopUtilization": 0.1049,
|
|
354
|
+
"cpuPercent": 12.59,
|
|
355
|
+
"cpuMs": {
|
|
356
|
+
"user": 278.31,
|
|
357
|
+
"system": 99.94
|
|
358
|
+
},
|
|
359
|
+
"rssBytes": 157626368,
|
|
360
|
+
"rssPeakBytes": 170659840,
|
|
361
|
+
"heapUsedBytes": 33033944,
|
|
362
|
+
"database": {
|
|
363
|
+
"bytes": 8678792,
|
|
364
|
+
"walBytes": 4808072,
|
|
365
|
+
"mainBytes": 3870720
|
|
366
|
+
},
|
|
367
|
+
"store": {
|
|
368
|
+
"queuedRecords": 0,
|
|
369
|
+
"queuedBatches": 0,
|
|
370
|
+
"lastFlushMs": 1793821226444,
|
|
371
|
+
"lastFlushDurationMs": 1,
|
|
372
|
+
"committedRecords": 1500,
|
|
373
|
+
"duplicateRecords": 0,
|
|
374
|
+
"rejectedRecords": 0,
|
|
375
|
+
"writeFailures": 0,
|
|
376
|
+
"lastWriteError": null,
|
|
377
|
+
"diskFullFailures": 0,
|
|
378
|
+
"dbBytes": 8678792,
|
|
379
|
+
"dbWalBytes": 4808072,
|
|
380
|
+
"dbBytesLimit": 1073741824,
|
|
381
|
+
"retention": {
|
|
382
|
+
"lastRunAtMs": 0,
|
|
383
|
+
"lastRunDurationMs": 0,
|
|
384
|
+
"deletedRows": 0,
|
|
385
|
+
"pressure": false,
|
|
386
|
+
"effectiveRawCutoffMs": null,
|
|
387
|
+
"physicalBytes": 8678792,
|
|
388
|
+
"physicalLimitBytes": 1073741824,
|
|
389
|
+
"physicalPressure": false,
|
|
390
|
+
"walBytes": 4808072
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
"liveClients": 64,
|
|
394
|
+
"sessions": 2,
|
|
395
|
+
"connectedMachines": 100,
|
|
396
|
+
"configuredMachines": 100,
|
|
397
|
+
"snapshotBytes": 197285
|
|
398
|
+
},
|
|
399
|
+
"databaseGrowth": {
|
|
400
|
+
"bytes": 2361600,
|
|
401
|
+
"walBytes": 395520,
|
|
402
|
+
"mainBytes": 1966080
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
"elapsedMs": 10000.82,
|
|
406
|
+
"artifacts": {
|
|
407
|
+
"directory": "/tmp/rigscope-scale-8QIQV0",
|
|
408
|
+
"kept": false,
|
|
409
|
+
"configPath": "/tmp/rigscope-scale-8QIQV0/hub-config.json",
|
|
410
|
+
"databasePath": "/tmp/rigscope-scale-8QIQV0/scale.sqlite"
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
],
|
|
414
|
+
"limitations": [
|
|
415
|
+
"Collector payload values and cadence are synthetic; the hub, database, auth, SSE and History paths are real modules.",
|
|
416
|
+
"The hub child and all load generators share one host; the hub event loop is single-threaded, while load processes can use other cores. This is not distributed load.",
|
|
417
|
+
"History queries are synchronous on the hub event loop, so 'concurrent' viewers model overlapping HTTP requests, not parallel SQLite readers.",
|
|
418
|
+
"The measurement never contacts the live service; it uses a fresh temporary database and loopback ephemeral port.",
|
|
419
|
+
"A passing case is evidence for this host and workload, not an unlimited-capacity or latency guarantee."
|
|
420
|
+
]
|
|
421
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Hub capacity stress test — 2026-11-04
|
|
2
|
+
|
|
3
|
+
On this AMD EPYC 7402P host (24 cores / 48 threads, 121 GiB RAM), the isolated
|
|
4
|
+
hub passed **500 collectors plus 64 concurrent live browser sessions** for two
|
|
5
|
+
minutes. Every collector sent one representative sample per second; every
|
|
6
|
+
session also polled History once per second. Ack p95 was **223 ms**, History p95
|
|
7
|
+
was **181 ms**, hub CPU averaged **75% of one core**, and sampled hub RSS peaked
|
|
8
|
+
at **560 MiB**. All 64 streams stayed connected. All 7,744 History requests
|
|
9
|
+
succeeded, all submitted live records were acknowledged, and the final queue
|
|
10
|
+
was empty.
|
|
11
|
+
|
|
12
|
+
These are synthetic measurements on this machine, using production hub modules.
|
|
13
|
+
They are **not a long-term capacity guarantee or a test of the deployed HTTPS
|
|
14
|
+
service, real browser rendering, or a mature multi-day history database**.
|
|
15
|
+
|
|
16
|
+
## Three different limits
|
|
17
|
+
|
|
18
|
+
- **Application limits:** 100 collector sockets by default, configurable with
|
|
19
|
+
`HUB_MAX_COLLECTOR_CONNECTIONS`; 50 user accounts; 64 simultaneous live browser
|
|
20
|
+
streams; 256 stored browser sessions. Accounts, logged-in sessions, and open
|
|
21
|
+
streams are different counts. The tests used independent sessions on one
|
|
22
|
+
shared all-machines account, plus a scoped account for authorization checks.
|
|
23
|
+
- **Ingestion without live streams:** 1,050 collectors passed the two-minute
|
|
24
|
+
target; 1,125 exceeded the chosen 500 ms ack p95 target (538 ms). Thus the
|
|
25
|
+
observed two-minute latency boundary is **between 1,050 and 1,125** at 1 Hz.
|
|
26
|
+
At 1,200, ack p95 rose to 2.15 seconds, retries appeared, and the final queue
|
|
27
|
+
held 2,321 records. This is an ingestion measurement with one light History
|
|
28
|
+
session, not a usable full-fleet dashboard capacity.
|
|
29
|
+
- **Full-fleet snapshot delivery:** 529 collectors passed, but 530 disconnected
|
|
30
|
+
even one fast loopback viewer after its initial snapshot. The 529-machine
|
|
31
|
+
snapshot was 1,047,111 bytes; at 530 it was 1,049,092 bytes. The live feed calls
|
|
32
|
+
`res.write()` and then destroys the connection when `writableLength` exceeds
|
|
33
|
+
its **1,048,576-byte guard**. A single sufficiently large snapshot triggers
|
|
34
|
+
that guard. The 530-machine run kept ingestion healthy (~532 commits/s,
|
|
35
|
+
58 ms ack p95) but delivered **zero live updates**, confirming that the failure
|
|
36
|
+
is in streaming. The exact machine count depends on payload size and access
|
|
37
|
+
scope; the 1 MiB guard is the relevant constraint.
|
|
38
|
+
|
|
39
|
+
A separate admission probe opened 65 streams: **64 returned HTTP 200, and one
|
|
40
|
+
returned HTTP 503**. That is the configured stream cap, not exhausted hardware.
|
|
41
|
+
|
|
42
|
+
Source references: [collector socket budget](https://github.com/Triet1402/rigscope/blob/main/src/hub/collector-ws.ts),
|
|
43
|
+
[collector configuration](https://github.com/Triet1402/rigscope/blob/main/src/hub/config.ts),
|
|
44
|
+
[user account limit](https://github.com/Triet1402/rigscope/blob/main/src/hub/auth/config.ts),
|
|
45
|
+
[session capacity](https://github.com/Triet1402/rigscope/blob/main/src/hub/auth/sessions.ts), and
|
|
46
|
+
[live feed admission and buffer guard](https://github.com/Triet1402/rigscope/blob/main/src/hub/sse.ts).
|
|
47
|
+
|
|
48
|
+
## Measurements
|
|
49
|
+
|
|
50
|
+
| Collectors | History / live sessions | Seconds | Ack p95 | History p95 | Hub CPU¹ | Hub RSS peak | Result | Evidence |
|
|
51
|
+
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- |
|
|
52
|
+
| 900 | 1 / 0 | 120 | 72 ms | 27 ms | 75.1% | 225 MiB | Pass | [182625-probe-m900-u1-s0-120000ms.json](182625-probe-m900-u1-s0-120000ms.json) |
|
|
53
|
+
| 1,050 | 1 / 0 | 120 | 140 ms | 28 ms | 82.2% | 260 MiB | Pass | [182847-probe-m1050-u1-s0-120000ms.json](182847-probe-m1050-u1-s0-120000ms.json) |
|
|
54
|
+
| 1,125 | 1 / 0 | 120 | 538 ms | 31 ms | 86.7% | 259 MiB | Outside target | [183112-probe-m1125-u1-s0-120000ms.json](183112-probe-m1125-u1-s0-120000ms.json) |
|
|
55
|
+
| 1,200 | 1 / 0 | 120 | 2,154 ms | 30 ms | 92.4% | 262 MiB | Outside target | [182354-probe-m1200-u1-s0-120000ms.json](182354-probe-m1200-u1-s0-120000ms.json) |
|
|
56
|
+
| 1,800 | 1 / 0 | 20 | 88 ms | 24 ms | 103.4% | 291 MiB | Pass | [181114-probe-m1800-u1-s0-20000ms.json](181114-probe-m1800-u1-s0-20000ms.json) |
|
|
57
|
+
| 1,800 | 1 / 0 | 120 | 3,872 ms | 34 ms | 108.3% | 311 MiB | Outside target | [182108-probe-m1800-u1-s0-120000ms.json](182108-probe-m1800-u1-s0-120000ms.json) |
|
|
58
|
+
| 500 | 64 / 64 | 120 | 223 ms | 181 ms | 74.7% | 560 MiB | Pass | [193608-probe-m500-u64-s64-120000ms.json](193608-probe-m500-u64-s64-120000ms.json) |
|
|
59
|
+
| 529 | 1 / 1 | 15 | 58 ms | 9 ms | 45.7% | 200 MiB | Pass | [193909-probe-m529-u1-s1-15000ms.json](193909-probe-m529-u1-s1-15000ms.json) |
|
|
60
|
+
| 530 | 1 / 1 | 15 | 58 ms | 18 ms | 45.0% | 189 MiB | Outside target | [193940-probe-m530-u1-s1-15000ms.json](193940-probe-m530-u1-s1-15000ms.json) |
|
|
61
|
+
| 100 | 1 / 65 | 10 | 64 ms | 5 ms | 15.7% | 162 MiB | Outside target | [194011-probe-m100-u1-s65-10000ms.json](194011-probe-m100-u1-s65-10000ms.json) |
|
|
62
|
+
|
|
63
|
+
¹ Hub CPU is process CPU as a percentage of one core, including its helper
|
|
64
|
+
threads. It is not the percentage of the 48-thread machine. Windows and
|
|
65
|
+
percentiles have the boundaries described below. “Outside target” includes
|
|
66
|
+
intentionally exceeding admission limits; it does not mean a hub crash.
|
|
67
|
+
|
|
68
|
+
The 1,800-collector result is included to show why a short burst is insufficient:
|
|
69
|
+
it passed for 20 seconds, then failed over 120 seconds with 3.87-second ack p95,
|
|
70
|
+
retry acknowledgments and a growing queue. The 1,125 run crossed only the ack
|
|
71
|
+
latency target; it did not lose submitted records or finish with backlog.
|
|
72
|
+
|
|
73
|
+
## Method and pass criteria
|
|
74
|
+
|
|
75
|
+
The existing `scripts/scale-harness.ts` creates a fresh private temporary
|
|
76
|
+
configuration and SQLite database for every run and binds a throwaway child to
|
|
77
|
+
an ephemeral loopback port. It drives real authenticated collector WebSockets,
|
|
78
|
+
SSE and History HTTP requests through production modules, including durable
|
|
79
|
+
commit-before-ack, WAL and `synchronous=FULL`. The installed service and its
|
|
80
|
+
configuration/database are never used as load targets.
|
|
81
|
+
|
|
82
|
+
The payload represents eight CPU cores, two GPUs, two filesystems, two disk
|
|
83
|
+
devices and two network interfaces, with optional bottleneck counters. Each
|
|
84
|
+
collector sends one record every 1,000 ms. History requests select one machine,
|
|
85
|
+
four standard series and a five-minute range, though the fresh database only
|
|
86
|
+
contains the run's data. All live sessions receive the full fleet. Enrollment,
|
|
87
|
+
process detail, active alert rules/webhooks, static page delivery and TLS are
|
|
88
|
+
outside this workload.
|
|
89
|
+
|
|
90
|
+
The isolated collector admission budget is raised automatically above the
|
|
91
|
+
requested fleet size. This does not change a deployed hub's 100-connection
|
|
92
|
+
default. The queue was aligned with production at 4,096 records and transactions
|
|
93
|
+
at 32 records. The session store uses the production 256-session default. The
|
|
94
|
+
live-stream cap remains 64. Database main+WAL was capped at 1 GiB for these
|
|
95
|
+
experiments; no run reached the cap or reported SQLite write failures.
|
|
96
|
+
|
|
97
|
+
An initial exponential search doubled load, then bisected the passing/failing
|
|
98
|
+
interval to about 12.5%. Follow-up two-minute runs narrowed the ingestion
|
|
99
|
+
boundary, and single-machine probes narrowed the snapshot boundary. Targets
|
|
100
|
+
were chosen for this experiment, not taken from a published product SLA:
|
|
101
|
+
|
|
102
|
+
- ack p95 ≤500 ms; History p95 ≤1,000 ms;
|
|
103
|
+
- hub event-loop p99 ≤200 ms;
|
|
104
|
+
- normal SSE newest-sample age p95 ≤2,000 ms;
|
|
105
|
+
- measured durable commit rate ≥95% of requested cadence;
|
|
106
|
+
- successful History polls ≥90% of requested cadence;
|
|
107
|
+
- no retries, rejected/unacked records, collector send errors, or cadence skips;
|
|
108
|
+
- no History errors, HTTP 429s or truncated queries;
|
|
109
|
+
- all requested live streams remain present and receive ongoing updates;
|
|
110
|
+
- final queue empty, with no write failures or physical-budget pressure.
|
|
111
|
+
|
|
112
|
+
Durable throughput is computed from the difference in the hub's commit counters
|
|
113
|
+
across the measured window. Ack latency is send-to-durable-ack round-trip time,
|
|
114
|
+
including startup and the drain tail. SSE lag is the age of the newest machine
|
|
115
|
+
sample in each received snapshot; it does not establish freshness of every
|
|
116
|
+
machine. History/SSE totals and percentile samples include the bounded tail.
|
|
117
|
+
|
|
118
|
+
### Correcting the load generator
|
|
119
|
+
|
|
120
|
+
The first mixed-load search used a single parent event loop for collectors,
|
|
121
|
+
History polling and parsing all 64 streams. It appeared to stop around 400–450
|
|
122
|
+
collectors, but it was under-producing samples while every submitted record
|
|
123
|
+
was still accepted. Those cases remain in the raw index as diagnostic evidence;
|
|
124
|
+
they are **not** the mixed-load capacity conclusion.
|
|
125
|
+
|
|
126
|
+
The harness now supports `--sse-workers N`. The final mixed runs use four
|
|
127
|
+
independent stream-reader processes, with full JSON parsing, and a one-second
|
|
128
|
+
client warmup before resetting the measured window. The collector driver and
|
|
129
|
+
History polling stay in the parent process. The final 500/64 run achieved
|
|
130
|
+
99.96% of requested durable sampling throughput. Single-stream boundary probes
|
|
131
|
+
use one reader worker. Older results without workers include stream startup
|
|
132
|
+
in the measured window. A three-second, two-machine worker smoke is also in
|
|
133
|
+
the index; its cadence classification is not a capacity result.
|
|
134
|
+
|
|
135
|
+
### Resource protection
|
|
136
|
+
|
|
137
|
+
`scripts/stress-hub.py` supervises the entire test process group at one-second
|
|
138
|
+
intervals, including the hub, driver and stream readers, at reduced scheduling
|
|
139
|
+
priority. It stops the group for:
|
|
140
|
+
|
|
141
|
+
- less than 16 GiB available host RAM;
|
|
142
|
+
- more than 4 GiB summed test-process RSS;
|
|
143
|
+
- less than 10 GiB free disk;
|
|
144
|
+
- more than 256 MiB of new host swap-out;
|
|
145
|
+
- over 80% host CPU for five consecutive samples;
|
|
146
|
+
- excessive setup/measurement time.
|
|
147
|
+
|
|
148
|
+
Across the 29 measured attempts, sampled test-process RSS peaked at **1,475 MiB**,
|
|
149
|
+
host CPU peaked at **9.69%**, available RAM stayed above **108.2 GiB**, and new
|
|
150
|
+
swap-out was **zero**. No resource cutoff fired. Existing used swap was about
|
|
151
|
+
7.6 GiB before testing; it was not new pressure from these tests.
|
|
152
|
+
|
|
153
|
+
The hub's CPU and synchronous storage work become limiting while most host
|
|
154
|
+
cores and RAM remain available. The final mixed run received about 11.7 GB of
|
|
155
|
+
SSE data including warmup/drain; loopback does not establish capacity over a
|
|
156
|
+
real network link. Different CPU-core counts, devices, scopes, History breadth,
|
|
157
|
+
retention age or network conditions can change these results substantially.
|
|
158
|
+
|
|
159
|
+
## Reproduce
|
|
160
|
+
|
|
161
|
+
Requires Linux, Python 3, Node 26+ and the checkout's development dependencies.
|
|
162
|
+
Run from the repository root. Each attempt saves raw JSON, a log and a resource
|
|
163
|
+
trace under the dated output directory; `--out PATH` overrides it. Logs and
|
|
164
|
+
per-process resource traces are ignored by Git and stay local. Measurement JSON
|
|
165
|
+
can be committed with a reviewed report.
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
# Quick exponential/binary ingestion search; follow with longer confirmation
|
|
169
|
+
python3 scripts/stress-hub.py --search collectors --machines 100 --sse 0 --duration-ms 20000
|
|
170
|
+
|
|
171
|
+
# Passing two-minute ingestion load and its nearby failing target
|
|
172
|
+
python3 scripts/stress-hub.py --machines 1050 --users 1 --sse 0 --duration-ms 120000
|
|
173
|
+
python3 scripts/stress-hub.py --machines 1125 --users 1 --sse 0 --duration-ms 120000
|
|
174
|
+
|
|
175
|
+
# Confirmed two-minute mixed workload; stream workers default to four
|
|
176
|
+
python3 scripts/stress-hub.py --machines 500 --users 64 --sse 64 --duration-ms 120000
|
|
177
|
+
|
|
178
|
+
# Snapshot-size and stream-admission probes (expected failures at 530 and 65)
|
|
179
|
+
python3 scripts/stress-hub.py --machines 529 --users 1 --sse 1 --sse-workers 1 --duration-ms 15000
|
|
180
|
+
python3 scripts/stress-hub.py --machines 530 --users 1 --sse 1 --sse-workers 1 --duration-ms 15000
|
|
181
|
+
python3 scripts/stress-hub.py --machines 100 --users 1 --sse 65 --duration-ms 10000
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The runner records a failed target as `"pass": false` in its report and continues
|
|
185
|
+
a search; intentional over-limit probes are ordinary successful runner
|
|
186
|
+
executions. Read the report rather than using the Python exit status as a
|
|
187
|
+
performance assertion.
|
|
188
|
+
|
|
189
|
+
## Artifacts and verification
|
|
190
|
+
|
|
191
|
+
[All 29 attempts](index.jsonl), [chart](results.html), and
|
|
192
|
+
[source hashes and cleanup manifest](manifest.json). Each index entry identifies
|
|
193
|
+
its raw report and resource trace; a sibling `.log` holds runner output. Raw
|
|
194
|
+
measurement reports are versioned, including failed and superseded generator
|
|
195
|
+
configurations. Detailed resource traces and logs are retained locally and
|
|
196
|
+
excluded from Git; regenerate them with the commands above when needed.
|
|
197
|
+
|
|
198
|
+
Verification: `npm run typecheck`, `git diff --check`, and both
|
|
199
|
+
`node --import tsx --test tests/scale-harness.test.ts` cases passed. The tests
|
|
200
|
+
exercise authenticated scope checks, collector reconnect/replay, SSE reset and
|
|
201
|
+
cleanup with zero and two independent reader workers. Python syntax was checked.
|
|
202
|
+
All recorded temporary hub/database directories were removed, and the installed
|
|
203
|
+
hub returned HTTP 200 on its existing Unix-socket health endpoint afterward.
|
|
204
|
+
The installed hub and collector processes were left running. Only harness,
|
|
205
|
+
supervisor, tests and documentation files changed.
|