querysub 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.
- package/.dependency-cruiser.js +304 -0
- package/.eslintrc.js +51 -0
- package/.github/copilot-instructions.md +1 -0
- package/.vscode/settings.json +25 -0
- package/bin/deploy.js +4 -0
- package/bin/function.js +4 -0
- package/bin/server.js +4 -0
- package/costsBenefits.txt +112 -0
- package/deploy.ts +3 -0
- package/inject.ts +1 -0
- package/package.json +60 -0
- package/prompts.txt +54 -0
- package/spec.txt +820 -0
- package/src/-a-archives/archiveCache.ts +913 -0
- package/src/-a-archives/archives.ts +148 -0
- package/src/-a-archives/archivesBackBlaze.ts +792 -0
- package/src/-a-archives/archivesDisk.ts +418 -0
- package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
- package/src/-a-auth/certs.ts +517 -0
- package/src/-a-auth/der.ts +122 -0
- package/src/-a-auth/ed25519.ts +1015 -0
- package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
- package/src/-b-authorities/dnsAuthority.ts +203 -0
- package/src/-b-authorities/emailAuthority.ts +57 -0
- package/src/-c-identity/IdentityController.ts +200 -0
- package/src/-d-trust/NetworkTrust2.ts +150 -0
- package/src/-e-certs/EdgeCertController.ts +288 -0
- package/src/-e-certs/certAuthority.ts +192 -0
- package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
- package/src/-g-core-values/NodeCapabilities.ts +134 -0
- package/src/-g-core-values/oneTimeForward.ts +91 -0
- package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
- package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
- package/src/0-path-value-core/LoggingClient.tsx +24 -0
- package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
- package/src/0-path-value-core/PathController.ts +1 -0
- package/src/0-path-value-core/PathValueCommitter.ts +565 -0
- package/src/0-path-value-core/PathValueController.ts +231 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
- package/src/0-path-value-core/debugLogs.ts +90 -0
- package/src/0-path-value-core/pathValueArchives.ts +483 -0
- package/src/0-path-value-core/pathValueCore.ts +2217 -0
- package/src/1-path-client/RemoteWatcher.ts +558 -0
- package/src/1-path-client/pathValueClientWatcher.ts +702 -0
- package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
- package/src/2-proxy/archiveMoveHarness.ts +376 -0
- package/src/2-proxy/garbageCollection.ts +753 -0
- package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
- package/src/2-proxy/pathValueProxy.ts +139 -0
- package/src/2-proxy/schema2.ts +518 -0
- package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
- package/src/3-path-functions/PathFunctionRunner.ts +619 -0
- package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
- package/src/3-path-functions/deployBlock.ts +10 -0
- package/src/3-path-functions/deployCheck.ts +7 -0
- package/src/3-path-functions/deployMain.ts +160 -0
- package/src/3-path-functions/pathFunctionLoader.ts +282 -0
- package/src/3-path-functions/syncSchema.ts +475 -0
- package/src/3-path-functions/tests/functionsTest.ts +135 -0
- package/src/3-path-functions/tests/rejectTest.ts +77 -0
- package/src/4-dom/css.tsx +29 -0
- package/src/4-dom/cssTypes.d.ts +212 -0
- package/src/4-dom/qreact.tsx +2322 -0
- package/src/4-dom/qreactTest.tsx +417 -0
- package/src/4-querysub/Querysub.ts +877 -0
- package/src/4-querysub/QuerysubController.ts +620 -0
- package/src/4-querysub/copyEvent.ts +0 -0
- package/src/4-querysub/permissions.ts +289 -0
- package/src/4-querysub/permissionsShared.ts +1 -0
- package/src/4-querysub/querysubPrediction.ts +525 -0
- package/src/5-diagnostics/FullscreenModal.tsx +67 -0
- package/src/5-diagnostics/GenericFormat.tsx +165 -0
- package/src/5-diagnostics/Modal.tsx +79 -0
- package/src/5-diagnostics/Table.tsx +183 -0
- package/src/5-diagnostics/TimeGrouper.tsx +114 -0
- package/src/5-diagnostics/diskValueAudit.ts +216 -0
- package/src/5-diagnostics/memoryValueAudit.ts +442 -0
- package/src/5-diagnostics/nodeMetadata.ts +135 -0
- package/src/5-diagnostics/qreactDebug.tsx +309 -0
- package/src/5-diagnostics/shared.ts +26 -0
- package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
- package/src/TestController.ts +35 -0
- package/src/allowclient.flag +0 -0
- package/src/bits.ts +86 -0
- package/src/buffers.ts +69 -0
- package/src/config.ts +53 -0
- package/src/config2.ts +48 -0
- package/src/diagnostics/ActionsHistory.ts +56 -0
- package/src/diagnostics/NodeViewer.tsx +503 -0
- package/src/diagnostics/SizeLimiter.ts +62 -0
- package/src/diagnostics/TimeDebug.tsx +18 -0
- package/src/diagnostics/benchmark.ts +139 -0
- package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
- package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
- package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
- package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
- package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
- package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
- package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
- package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
- package/src/diagnostics/heapTag.ts +13 -0
- package/src/diagnostics/listenOnDebugger.ts +77 -0
- package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
- package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
- package/src/diagnostics/logs/ansiFormat.ts +108 -0
- package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
- package/src/diagnostics/logs/diskLogger.ts +305 -0
- package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
- package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
- package/src/diagnostics/logs/logGitHashes.ts +30 -0
- package/src/diagnostics/managementPages.tsx +289 -0
- package/src/diagnostics/periodic.ts +89 -0
- package/src/diagnostics/runSaturationTest.ts +416 -0
- package/src/diagnostics/satSchema.ts +64 -0
- package/src/diagnostics/trackResources.ts +82 -0
- package/src/diagnostics/watchdog.ts +55 -0
- package/src/errors.ts +132 -0
- package/src/forceProduction.ts +3 -0
- package/src/fs.ts +72 -0
- package/src/heapDumps.ts +666 -0
- package/src/https.ts +2 -0
- package/src/inject.ts +1 -0
- package/src/library-components/ATag.tsx +84 -0
- package/src/library-components/Button.tsx +344 -0
- package/src/library-components/ButtonSelector.tsx +64 -0
- package/src/library-components/DropdownCustom.tsx +151 -0
- package/src/library-components/DropdownSelector.tsx +32 -0
- package/src/library-components/Input.tsx +334 -0
- package/src/library-components/InputLabel.tsx +198 -0
- package/src/library-components/InputPicker.tsx +125 -0
- package/src/library-components/LazyComponent.tsx +62 -0
- package/src/library-components/MeasureHeightCSS.tsx +48 -0
- package/src/library-components/MeasuredDiv.tsx +47 -0
- package/src/library-components/ShowMore.tsx +51 -0
- package/src/library-components/SyncedController.ts +171 -0
- package/src/library-components/TimeRangeSelector.tsx +407 -0
- package/src/library-components/URLParam.ts +263 -0
- package/src/library-components/colors.tsx +14 -0
- package/src/library-components/drag.ts +114 -0
- package/src/library-components/icons.tsx +692 -0
- package/src/library-components/niceStringify.ts +50 -0
- package/src/library-components/renderToString.ts +52 -0
- package/src/misc/PromiseRace.ts +101 -0
- package/src/misc/color.ts +30 -0
- package/src/misc/getParentProcessId.cs +53 -0
- package/src/misc/getParentProcessId.ts +53 -0
- package/src/misc/hash.ts +83 -0
- package/src/misc/ipPong.js +13 -0
- package/src/misc/networking.ts +2 -0
- package/src/misc/random.ts +45 -0
- package/src/misc.ts +19 -0
- package/src/noserverhotreload.flag +0 -0
- package/src/path.ts +226 -0
- package/src/persistentLocalStore.ts +37 -0
- package/src/promise.ts +15 -0
- package/src/server.ts +73 -0
- package/src/src.d.ts +1 -0
- package/src/test/heapProcess.ts +36 -0
- package/src/test/mongoSatTest.tsx +55 -0
- package/src/test/satTest.ts +193 -0
- package/src/test/test.tsx +552 -0
- package/src/zip.ts +92 -0
- package/src/zipThreaded.ts +106 -0
- package/src/zipThreadedWorker.js +19 -0
- package/tsconfig.json +27 -0
- package/yarnSpec.txt +56 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { delay } from "socket-function/src/batching";
|
|
2
|
+
import { formatTime, formatNumber } from "socket-function/src/formatting/format";
|
|
3
|
+
import { blue, green, red, yellow } from "socket-function/src/formatting/logColors";
|
|
4
|
+
import { createStatsValue, addToStatsValue, StatsValue, getStatsTop } from "socket-function/src/profiling/stats";
|
|
5
|
+
import { percent, formatStats } from "socket-function/src/profiling/statsFormat";
|
|
6
|
+
import { proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
7
|
+
import { timeoutToError, logErrors } from "../errors";
|
|
8
|
+
import { PromiseObj } from "../promise";
|
|
9
|
+
import { Benchmark } from "./benchmark";
|
|
10
|
+
|
|
11
|
+
export class DriftValues {
|
|
12
|
+
constructor(public readonly config: {
|
|
13
|
+
count: number;
|
|
14
|
+
modulus: number;
|
|
15
|
+
driftFraction: number;
|
|
16
|
+
}) { }
|
|
17
|
+
seqNum = 0;
|
|
18
|
+
public getNextValues(): number[] {
|
|
19
|
+
const { count, modulus, driftFraction } = this.config;
|
|
20
|
+
let curSeqNum = this.seqNum++;
|
|
21
|
+
let drift = Math.ceil(driftFraction * count);
|
|
22
|
+
let startValue = curSeqNum * drift % modulus;
|
|
23
|
+
return Array(count).fill(0).map((_, i) => (startValue + i) % modulus);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BenchmarkFunctions {
|
|
28
|
+
setValues(indexes: number[], value?: number): Promise<void>;
|
|
29
|
+
getValues(indexes: number[]): Promise<number[]>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function runSingleSaturationTest(config: {
|
|
33
|
+
duration: number;
|
|
34
|
+
saturation: number;
|
|
35
|
+
writeSpec: DriftValues;
|
|
36
|
+
predictedValues?: number[];
|
|
37
|
+
functions: BenchmarkFunctions;
|
|
38
|
+
}) {
|
|
39
|
+
const { duration, saturation, writeSpec, predictedValues, functions } = config;
|
|
40
|
+
|
|
41
|
+
let finalEndTime = Date.now() + duration;
|
|
42
|
+
let done = new PromiseObj();
|
|
43
|
+
|
|
44
|
+
let baseStartTime = Date.now();
|
|
45
|
+
let totalOverlappedWaitTime = 0;
|
|
46
|
+
let errorCount = 0;
|
|
47
|
+
|
|
48
|
+
let lastActiveStartTime = 0;
|
|
49
|
+
let totalActiveTime = 0;
|
|
50
|
+
|
|
51
|
+
let runCount = 0;
|
|
52
|
+
|
|
53
|
+
let latencyStats = createStatsValue();
|
|
54
|
+
|
|
55
|
+
let currentParallelCount = 0;
|
|
56
|
+
async function spawnCall() {
|
|
57
|
+
if (currentParallelCount === 0) {
|
|
58
|
+
lastActiveStartTime = Date.now();
|
|
59
|
+
}
|
|
60
|
+
currentParallelCount++;
|
|
61
|
+
let time = Date.now();
|
|
62
|
+
let errored = true;
|
|
63
|
+
try {
|
|
64
|
+
let indexes = writeSpec.getNextValues();
|
|
65
|
+
if (predictedValues) {
|
|
66
|
+
for (let i of indexes) {
|
|
67
|
+
predictedValues[i]++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await timeoutToError(30 * 1000, functions.setValues(indexes), () => new Error(`Call timed out after 30 seconds`));
|
|
71
|
+
errored = false;
|
|
72
|
+
} finally {
|
|
73
|
+
let latency = Date.now() - time;
|
|
74
|
+
totalOverlappedWaitTime += latency;
|
|
75
|
+
addToStatsValue(latencyStats, latency);
|
|
76
|
+
if (errored) {
|
|
77
|
+
errorCount++;
|
|
78
|
+
}
|
|
79
|
+
runCount++;
|
|
80
|
+
currentParallelCount--;
|
|
81
|
+
if (currentParallelCount === 0) {
|
|
82
|
+
totalActiveTime += Date.now() - lastActiveStartTime;
|
|
83
|
+
}
|
|
84
|
+
trySpawnMore();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function getTargetParallelCount() {
|
|
88
|
+
let currentElapsedTime = Date.now() - baseStartTime;
|
|
89
|
+
let currentOverallSaturation = totalOverlappedWaitTime / currentElapsedTime;
|
|
90
|
+
|
|
91
|
+
// TODO: Use higher or lower saturation values if we are way off, BUT, only after the time is advanced enough,
|
|
92
|
+
// and only between 2X and 0.5X.
|
|
93
|
+
|
|
94
|
+
// Round down if we are oversatured, round up if we are undersaturated
|
|
95
|
+
if (currentOverallSaturation > saturation) {
|
|
96
|
+
return Math.floor(saturation);
|
|
97
|
+
} else {
|
|
98
|
+
return Math.ceil(saturation);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function trySpawnMore() {
|
|
103
|
+
if (Date.now() >= finalEndTime) {
|
|
104
|
+
if (currentParallelCount === 0) {
|
|
105
|
+
done.resolve();
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
let parallelCount = getTargetParallelCount();
|
|
110
|
+
// If we want to run 0 values, then just wait a bit, and check again
|
|
111
|
+
if (parallelCount === 0) {
|
|
112
|
+
delay(10).finally(trySpawnMore);
|
|
113
|
+
}
|
|
114
|
+
while (currentParallelCount < parallelCount) {
|
|
115
|
+
logErrors(spawnCall());
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
trySpawnMore();
|
|
120
|
+
|
|
121
|
+
await done.promise;
|
|
122
|
+
|
|
123
|
+
let effectiveRate = runCount / totalActiveTime * 1000;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
effectiveRate,
|
|
127
|
+
achievedSaturation: totalOverlappedWaitTime / (Date.now() - baseStartTime),
|
|
128
|
+
latencyStats,
|
|
129
|
+
runCount,
|
|
130
|
+
errorCount,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function runSaturationTests(config: {
|
|
135
|
+
durationPer: number;
|
|
136
|
+
writeSpec: DriftValues;
|
|
137
|
+
functions: BenchmarkFunctions;
|
|
138
|
+
}) {
|
|
139
|
+
const { functions, durationPer, writeSpec } = config;
|
|
140
|
+
|
|
141
|
+
function shouldStopIterating(config: {
|
|
142
|
+
rateIncrease: number;
|
|
143
|
+
latencyIncrease: number;
|
|
144
|
+
saturation: number;
|
|
145
|
+
latencyAverage: number;
|
|
146
|
+
}): boolean {
|
|
147
|
+
const { rateIncrease, latencyIncrease, saturation, latencyAverage } = config;
|
|
148
|
+
if (rateIncrease < 1.1 && saturation >= 1) {
|
|
149
|
+
console.log(`Stopping due to low rate increase ${rateIncrease}`);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (saturation >= 5 && rateIncrease / latencyIncrease < 0.1) {
|
|
153
|
+
console.log(`Stopping due to low rate/latency increase ${rateIncrease / latencyIncrease}`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (latencyAverage >= 500) {
|
|
157
|
+
console.log(`Stopping due to high latency ${latencyAverage}`);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let results: string[] = [];
|
|
164
|
+
function log(text = "") {
|
|
165
|
+
results.push(text);
|
|
166
|
+
console.log(text);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Run a test to warm things up
|
|
170
|
+
let initialResult = await runSingleSaturationTest({ duration: config.durationPer, saturation: 0.5, writeSpec, functions });
|
|
171
|
+
|
|
172
|
+
// Let our warmup settle
|
|
173
|
+
await delay(initialResult.latencyStats.sum * 0.5);
|
|
174
|
+
|
|
175
|
+
// Reset all of the values, both to fill up the server reservations, and so we can easily predict what
|
|
176
|
+
// the final values should be
|
|
177
|
+
console.log(`Start value fill`);
|
|
178
|
+
let fillTime = Date.now();
|
|
179
|
+
const batchCount = 1000;
|
|
180
|
+
for (let i = 0; i < writeSpec.config.modulus; i += batchCount) {
|
|
181
|
+
let currentCount = Math.min(batchCount, writeSpec.config.modulus - i);
|
|
182
|
+
await functions.setValues(Array(currentCount).fill(0).map((_, a) => i + a), 0);
|
|
183
|
+
}
|
|
184
|
+
fillTime = Date.now() - fillTime;
|
|
185
|
+
log(`Finished value fill in ${formatTime(fillTime)}`);
|
|
186
|
+
await delay(fillTime * 0.5 + 250);
|
|
187
|
+
|
|
188
|
+
type Result = {
|
|
189
|
+
actualSaturation: number;
|
|
190
|
+
effectiveRate: number;
|
|
191
|
+
latencyStats: StatsValue;
|
|
192
|
+
};
|
|
193
|
+
let allResults: Result[] = [];
|
|
194
|
+
|
|
195
|
+
let predictedValues = Array(writeSpec.config.modulus).fill(0);
|
|
196
|
+
|
|
197
|
+
let saturation = 1 / 16;
|
|
198
|
+
let lastRate = 0;
|
|
199
|
+
let lastLatency = Number.POSITIVE_INFINITY;
|
|
200
|
+
let stopCount = 0;
|
|
201
|
+
let totalErrorCount = 0;
|
|
202
|
+
while (true) {
|
|
203
|
+
console.log(`Start saturation ${percent(saturation)}`);
|
|
204
|
+
let actualTime = Date.now();
|
|
205
|
+
let result = await runSingleSaturationTest({
|
|
206
|
+
duration: durationPer,
|
|
207
|
+
saturation,
|
|
208
|
+
writeSpec,
|
|
209
|
+
predictedValues,
|
|
210
|
+
functions,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
actualTime = Date.now() - actualTime;
|
|
214
|
+
|
|
215
|
+
allResults.push({
|
|
216
|
+
actualSaturation: result.achievedSaturation,
|
|
217
|
+
effectiveRate: result.effectiveRate,
|
|
218
|
+
latencyStats: result.latencyStats,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
log(`In ${formatTime(actualTime).padEnd(8, " ")} target saturation ${percent(saturation).padEnd(8, " ")} achieved saturation ${percent(result.achievedSaturation).padEnd(8, " ")} effective rate ${(formatNumber(result.effectiveRate) + "/s").padEnd(12, " ")} latency ${formatStats(result.latencyStats, { noColor: true })}`);
|
|
222
|
+
if (result.errorCount > 0) {
|
|
223
|
+
log(` ERROR: ${result.errorCount} errors!`);
|
|
224
|
+
totalErrorCount += result.errorCount;
|
|
225
|
+
}
|
|
226
|
+
// Wait to let the server cleanup / recover
|
|
227
|
+
console.log(`Waiting for ${formatTime(actualTime * 0.5)}`);
|
|
228
|
+
await delay(actualTime * 0.5);
|
|
229
|
+
console.log(`End saturation ${percent(saturation)}`);
|
|
230
|
+
|
|
231
|
+
// Always stop if we are over 3X the target time
|
|
232
|
+
if (actualTime / durationPer > 2) {
|
|
233
|
+
console.log(`Stopping due to very high test time ${formatTime(actualTime)}`);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let rateIncrease = result.effectiveRate / lastRate;
|
|
238
|
+
let latencyIncrease = result.latencyStats.sum / lastLatency;
|
|
239
|
+
lastRate = result.effectiveRate;
|
|
240
|
+
lastLatency = result.latencyStats.sum;
|
|
241
|
+
if (shouldStopIterating({
|
|
242
|
+
rateIncrease,
|
|
243
|
+
latencyIncrease,
|
|
244
|
+
saturation: saturation,
|
|
245
|
+
latencyAverage: result.latencyStats.sum / result.latencyStats.count
|
|
246
|
+
})) {
|
|
247
|
+
stopCount++;
|
|
248
|
+
if (stopCount >= 3) {
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
stopCount = 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
saturation *= 2;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let afterResult = await runSingleSaturationTest({ duration: config.durationPer, saturation: 0.5, writeSpec, predictedValues, functions });
|
|
259
|
+
|
|
260
|
+
let initialAvgLatency = initialResult.latencyStats.sum / initialResult.latencyStats.count;
|
|
261
|
+
let afterAvgLatency = afterResult.latencyStats.sum / afterResult.latencyStats.count;
|
|
262
|
+
|
|
263
|
+
let latencyIncrease = afterAvgLatency / initialAvgLatency;
|
|
264
|
+
if (latencyIncrease > 1.2) {
|
|
265
|
+
console.log(red(`WARNING: Latency increased by ${percent(latencyIncrease - 1)}!`));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
log("");
|
|
270
|
+
log(`Rate chart (op/s)`);
|
|
271
|
+
logChart(allResults.map(result => ({
|
|
272
|
+
x: result.actualSaturation,
|
|
273
|
+
y: result.effectiveRate,
|
|
274
|
+
xStr: percent(result.actualSaturation),
|
|
275
|
+
yStr: formatNumber(result.effectiveRate) + "/s",
|
|
276
|
+
})));
|
|
277
|
+
|
|
278
|
+
log("");
|
|
279
|
+
log(`Latency chart (average)`);
|
|
280
|
+
logChart(allResults.map(result => ({
|
|
281
|
+
x: result.actualSaturation,
|
|
282
|
+
y: result.latencyStats.sum / result.latencyStats.count,
|
|
283
|
+
xStr: percent(result.actualSaturation),
|
|
284
|
+
yStr: formatTime(result.latencyStats.sum / result.latencyStats.count),
|
|
285
|
+
})));
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
log("");
|
|
289
|
+
log(`Suggestions`);
|
|
290
|
+
|
|
291
|
+
let maxRate = allResults.reduce((a, b) => Math.max(a, b.effectiveRate), 0);
|
|
292
|
+
let minLatency = allResults[2].latencyStats ?? allResults[0].latencyStats;
|
|
293
|
+
|
|
294
|
+
function findClosest(dist: (other: Result) => number): Result {
|
|
295
|
+
let closest: Result | undefined;
|
|
296
|
+
let closestDist = Number.POSITIVE_INFINITY;
|
|
297
|
+
for (let result of allResults) {
|
|
298
|
+
let currentDist = Math.abs(dist(result));
|
|
299
|
+
if (currentDist < closestDist) {
|
|
300
|
+
closest = result;
|
|
301
|
+
closestDist = currentDist;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return closest!;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const maxRateObj = findClosest(r => r.effectiveRate - maxRate * 0.8);
|
|
308
|
+
const heavySat = findClosest(r => r.actualSaturation - maxRateObj.actualSaturation * 0.5);
|
|
309
|
+
const minLatencyObj = findClosest(r => r.latencyStats.sum / r.latencyStats.count - minLatency.sum / minLatency.count);
|
|
310
|
+
const minLatency2XObj = findClosest(r => r.latencyStats.sum / r.latencyStats.count - minLatency.sum / minLatency.count * 2);
|
|
311
|
+
|
|
312
|
+
const normalLatency = findClosest(r => r.latencyStats.sum / r.latencyStats.count - 100);
|
|
313
|
+
const highLatency = findClosest(r => r.latencyStats.sum / r.latencyStats.count - 500);
|
|
314
|
+
|
|
315
|
+
function getSatObj(title: string, obj: Result) {
|
|
316
|
+
let output = "";
|
|
317
|
+
output += (title.padEnd(20, " ")) + " ";
|
|
318
|
+
output += (`${formatNumber(obj.effectiveRate)}/s`.padEnd(10, " ")) + " ";
|
|
319
|
+
|
|
320
|
+
let top = getStatsTop(obj.latencyStats);
|
|
321
|
+
if (top.topHeavy) {
|
|
322
|
+
output += `latency = ${formatStats(obj.latencyStats, { noColor: true, noSum: true })}`.padEnd(16, " ") + " ";
|
|
323
|
+
} else {
|
|
324
|
+
output += `latency = ${formatTime(obj.latencyStats.sum / obj.latencyStats.count)}`.padEnd(16, " ") + " ";
|
|
325
|
+
}
|
|
326
|
+
output += (`(${percent(obj.actualSaturation)} saturation)`.padEnd(25, " ")) + " ";
|
|
327
|
+
output += (`(${formatNumber(obj.latencyStats.count)} count)`.padEnd(12, " ")) + " ";
|
|
328
|
+
|
|
329
|
+
return output;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (maxRateObj.effectiveRate > highLatency.effectiveRate * 1.2) {
|
|
333
|
+
log(getSatObj("Max rate", maxRateObj));
|
|
334
|
+
}
|
|
335
|
+
if (heavySat.effectiveRate > highLatency.effectiveRate * 1.2) {
|
|
336
|
+
log(getSatObj("Heavy saturation", heavySat));
|
|
337
|
+
}
|
|
338
|
+
log(getSatObj("Min latency", minLatencyObj));
|
|
339
|
+
if (highLatency.effectiveRate > normalLatency.effectiveRate * 1.2) {
|
|
340
|
+
log(blue(getSatObj("500ms latency", highLatency)));
|
|
341
|
+
}
|
|
342
|
+
if (minLatency2XObj.effectiveRate > minLatencyObj.effectiveRate * 1.2) {
|
|
343
|
+
log(yellow(getSatObj("Min latency 2X", minLatency2XObj)));
|
|
344
|
+
}
|
|
345
|
+
if (
|
|
346
|
+
normalLatency.effectiveRate > minLatencyObj.effectiveRate * 1.2
|
|
347
|
+
&& (normalLatency.effectiveRate > minLatency2XObj.effectiveRate * 1.2)
|
|
348
|
+
) {
|
|
349
|
+
log(green(getSatObj("100ms latency", normalLatency)));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
let realValues: number[] = await functions.getValues(predictedValues.map((_, i) => i));
|
|
354
|
+
|
|
355
|
+
let wrongIndexes = 0;
|
|
356
|
+
let wrongSum = 0;
|
|
357
|
+
for (let i = 0; i < predictedValues.length; i++) {
|
|
358
|
+
if (predictedValues[i] !== realValues[i]) {
|
|
359
|
+
wrongIndexes++;
|
|
360
|
+
wrongSum += Math.abs(predictedValues[i] - realValues[i]);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
let wrongFraction = wrongSum / predictedValues.reduce((a, b) => a + b, 0);
|
|
364
|
+
if (wrongIndexes > 0) {
|
|
365
|
+
log(`!!!! ERROR !!!!: ${formatNumber(wrongIndexes)} indexes were wrong, ${percent(wrongFraction)} of operations were missed`);
|
|
366
|
+
}
|
|
367
|
+
if (totalErrorCount > 0) {
|
|
368
|
+
log(`!!!! ERROR !!!!: ${formatNumber(totalErrorCount)} errors were encountered`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
console.log();
|
|
373
|
+
console.log("Results");
|
|
374
|
+
for (let result of results) {
|
|
375
|
+
console.log(result);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return results;
|
|
379
|
+
|
|
380
|
+
function logChart(values: { x: number; y: number; xStr: string; yStr: string; }[]) {
|
|
381
|
+
const barWidth = 4;
|
|
382
|
+
let columnWidth = values.length * barWidth;
|
|
383
|
+
let columnHeight = 12;
|
|
384
|
+
function rightJustifyLog(text: string) {
|
|
385
|
+
let padding = " ".repeat(Math.max(columnWidth - text.length, 0));
|
|
386
|
+
log(padding + text);
|
|
387
|
+
}
|
|
388
|
+
function sidesLog(left: string, right: string) {
|
|
389
|
+
let padding = " ".repeat(Math.max(columnWidth - left.length - right.length, 0));
|
|
390
|
+
log(left + padding + right);
|
|
391
|
+
}
|
|
392
|
+
rightJustifyLog(values.at(-1)!.yStr);
|
|
393
|
+
log();
|
|
394
|
+
let maxY = values.reduce((a, b) => Math.max(a, b.y), 0);
|
|
395
|
+
let minY = values.reduce((a, b) => Math.min(a, b.y), Number.POSITIVE_INFINITY);
|
|
396
|
+
minY = 0;
|
|
397
|
+
let grid = Array(columnHeight).fill(0).map(() => Array(columnWidth).fill(" "));
|
|
398
|
+
let xIndex = 0;
|
|
399
|
+
for (let value of values) {
|
|
400
|
+
let barHeight = Math.floor((value.y - minY) / (maxY - minY) * columnHeight);
|
|
401
|
+
for (let y = 0; y < barHeight; y++) {
|
|
402
|
+
for (let xOff = 0; xOff < barWidth; xOff++) {
|
|
403
|
+
grid[y][xIndex + xOff] = "█";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
xIndex += barWidth;
|
|
407
|
+
}
|
|
408
|
+
grid.reverse();
|
|
409
|
+
for (let row of grid) {
|
|
410
|
+
log(row.join(""));
|
|
411
|
+
}
|
|
412
|
+
rightJustifyLog(values.at(0)!.yStr);
|
|
413
|
+
sidesLog(values.at(0)!.xStr, values.at(-1)!.xStr);
|
|
414
|
+
log();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { red } from "socket-function/src/formatting/logColors";
|
|
2
|
+
import { Querysub } from "../4-querysub/Querysub";
|
|
3
|
+
import { delay } from "socket-function/src/batching";
|
|
4
|
+
import { atomic, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
5
|
+
import { Benchmark } from "./benchmark";
|
|
6
|
+
import { BenchmarkFunctions } from "./runSaturationTest";
|
|
7
|
+
import debugbreak from "debugbreak";
|
|
8
|
+
|
|
9
|
+
let { data, functions } = Querysub.createSchema<{
|
|
10
|
+
values: {
|
|
11
|
+
[index: number]: number;
|
|
12
|
+
};
|
|
13
|
+
}>()({
|
|
14
|
+
domainName: "querysub.com",
|
|
15
|
+
functions: {
|
|
16
|
+
setValues(indexes: number[], value?: number) {
|
|
17
|
+
for (let index of indexes) {
|
|
18
|
+
let newValue = value ?? (data().values[index] + 1);
|
|
19
|
+
data().values[index] = newValue;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
module,
|
|
24
|
+
moduleId: "saturationTest",
|
|
25
|
+
functionMetadata: {},
|
|
26
|
+
permissions: {
|
|
27
|
+
PERMISSIONS: isRegisteredUserCheck
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function isRegisteredUserCheck(): boolean {
|
|
32
|
+
let allowed = Querysub.getCallerIP() === "127.0.0.1";
|
|
33
|
+
if (!allowed && Querysub.isAllSynced()) {
|
|
34
|
+
console.log(red(`Permission denied to IP ${JSON.stringify(Querysub.getCallerIP())}`));
|
|
35
|
+
}
|
|
36
|
+
return allowed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const sat_data = data;
|
|
40
|
+
export const sat_functions = functions;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
export function createBenchmarkFunctions(): BenchmarkFunctions {
|
|
44
|
+
return {
|
|
45
|
+
async setValues(indexes: number[], value?: number) {
|
|
46
|
+
let call = sat_functions.setValues(indexes, value);
|
|
47
|
+
await Benchmark.waitForCallToFinish(call);
|
|
48
|
+
},
|
|
49
|
+
async getValues(indexes: number[]): Promise<number[]> {
|
|
50
|
+
let realValues: number[] = [];
|
|
51
|
+
const watcher = proxyWatcher.createWatcher({
|
|
52
|
+
watchFunction() {
|
|
53
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
54
|
+
let index = indexes[i];
|
|
55
|
+
realValues[i] = atomic(sat_data().values[index]) ?? 0;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
await delay(10 * 1000);
|
|
60
|
+
watcher.dispose();
|
|
61
|
+
return realValues;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { runInfinitePoll } from "socket-function/src/batching";
|
|
2
|
+
import { lazy } from "socket-function/src/caching";
|
|
3
|
+
import { formatNumber } from "socket-function/src/formatting/format";
|
|
4
|
+
import { blue } from "socket-function/src/formatting/logColors";
|
|
5
|
+
import { isNode } from "socket-function/src/misc";
|
|
6
|
+
import { registerPeriodic } from "./periodic";
|
|
7
|
+
import { registerMeasureInfo } from "socket-function/src/profiling/measure";
|
|
8
|
+
import { logNodeStateStats } from "../5-diagnostics/nodeMetadata";
|
|
9
|
+
|
|
10
|
+
let resources: {
|
|
11
|
+
name: string;
|
|
12
|
+
getCount: () => number;
|
|
13
|
+
}[] = [];
|
|
14
|
+
|
|
15
|
+
export function registerResource<T extends Set<unknown> | Map<unknown, unknown> | unknown[]>(name: string, data: T): T {
|
|
16
|
+
let getCount: () => number;
|
|
17
|
+
if (Array.isArray(data)) {
|
|
18
|
+
getCount = () => data.length;
|
|
19
|
+
} else {
|
|
20
|
+
getCount = () => data.size;
|
|
21
|
+
}
|
|
22
|
+
resources.push({ name, getCount });
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
export function registerMapArrayResource<T extends Map<unknown, unknown[]>>(name: string, data: T): T {
|
|
26
|
+
let getCount = () => {
|
|
27
|
+
let sum = 0;
|
|
28
|
+
for (let value of data.values()) {
|
|
29
|
+
sum += value.length;
|
|
30
|
+
}
|
|
31
|
+
return sum;
|
|
32
|
+
};
|
|
33
|
+
resources.push({ name, getCount });
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
export function registerDynamicResource(name: string, getCount: () => number) {
|
|
37
|
+
resources.push({ name, getCount });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getHeapSize() {
|
|
41
|
+
if (isNode()) {
|
|
42
|
+
let usage = process.memoryUsage();
|
|
43
|
+
// Not really the heap size, but it is more what we are looking for
|
|
44
|
+
return usage.rss;
|
|
45
|
+
} else {
|
|
46
|
+
return (window.performance as any).memory.totalJSHeapSize;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function getUsedHeapSize() {
|
|
50
|
+
if (isNode()) {
|
|
51
|
+
let usage = process.memoryUsage();
|
|
52
|
+
return usage.heapUsed + usage.arrayBuffers;
|
|
53
|
+
} else {
|
|
54
|
+
if (!window.performance) return 0;
|
|
55
|
+
return (window.performance as any).memory.usedJSHeapSize;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function logResourcesNow() {
|
|
60
|
+
let resourcesWithCounts = resources.map(resource => ({ ...resource, count: resource.getCount() }));
|
|
61
|
+
resourcesWithCounts.sort((a, b) => b.count - a.count);
|
|
62
|
+
if (!isNode()) {
|
|
63
|
+
console.log(`Memory usage ${blue(formatNumber(getUsedHeapSize()) + "B")}/${blue(formatNumber(getHeapSize()) + "B")} at ${new Date().toISOString()}`);
|
|
64
|
+
for (let resource of resourcesWithCounts) {
|
|
65
|
+
if (resource.count === 0) continue;
|
|
66
|
+
console.log(` ${resource.name}: ${formatNumber(resource.count)}`);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
logNodeStateStats("Heap", formatNumber)(getUsedHeapSize());
|
|
70
|
+
logNodeStateStats("All Memory", formatNumber)(getHeapSize());
|
|
71
|
+
for (let resource of resourcesWithCounts) {
|
|
72
|
+
if (resource.count === 0) continue;
|
|
73
|
+
logNodeStateStats(resource.name, formatNumber)(resource.count);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
registerMeasureInfo(() => {
|
|
79
|
+
return formatNumber(getUsedHeapSize()) + "B";
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
registerPeriodic(logResourcesNow);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
2
|
+
import { FormattedMeasureTable, logMeasureTable, startMeasure } from "socket-function/src/profiling/measure";
|
|
3
|
+
import { logErrors } from "../errors";
|
|
4
|
+
import { isNode } from "socket-function/src/misc";
|
|
5
|
+
import debugbreak from "debugbreak";
|
|
6
|
+
import { registerPeriodic } from "./periodic";
|
|
7
|
+
import { getOwnMachineId } from "../-a-auth/certs";
|
|
8
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
9
|
+
import { logDisk } from "./logs/diskLogger";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
let measureObj = startMeasure();
|
|
13
|
+
function logProfileMeasuresTimingsNow() {
|
|
14
|
+
let profile = measureObj.finish();
|
|
15
|
+
measureObj = startMeasure();
|
|
16
|
+
function diskLogMeasureObj(table: FormattedMeasureTable | undefined) {
|
|
17
|
+
if (!table) return;
|
|
18
|
+
logDisk(table.title, table.entries);
|
|
19
|
+
}
|
|
20
|
+
diskLogMeasureObj(logMeasureTable(profile, {
|
|
21
|
+
name: `watchdog at ${new Date().toLocaleString()}`,
|
|
22
|
+
// NOTE: Much higher min log times, now that we are combining logs.
|
|
23
|
+
minTimeToLog: 250,
|
|
24
|
+
thresholdInTable: 0,
|
|
25
|
+
setTitle: true
|
|
26
|
+
}));
|
|
27
|
+
diskLogMeasureObj(logMeasureTable(profile, {
|
|
28
|
+
name: `watchdog at ${new Date().toLocaleString()}`,
|
|
29
|
+
mergeDepth: 1,
|
|
30
|
+
minTimeToLog: 250,
|
|
31
|
+
}));
|
|
32
|
+
// if (isNode()) {
|
|
33
|
+
// if (SocketFunction.mountedNodeId) {
|
|
34
|
+
// console.log("Mounted as " + SocketFunction.mountedNodeId);
|
|
35
|
+
// } else {
|
|
36
|
+
// console.log("Not mounted");
|
|
37
|
+
// }
|
|
38
|
+
// }
|
|
39
|
+
}
|
|
40
|
+
(globalThis as any).logProfileMeasuresNow = logProfileMeasuresTimingsNow;
|
|
41
|
+
|
|
42
|
+
(globalThis as any).logUnfiltered = function logUnfiltered(depth = 2) {
|
|
43
|
+
let profile = measureObj.finish();
|
|
44
|
+
measureObj = startMeasure();
|
|
45
|
+
logMeasureTable(profile, {
|
|
46
|
+
name: `all logs at ${new Date().toLocaleString()}`,
|
|
47
|
+
mergeDepth: depth,
|
|
48
|
+
minTimeToLog: 0,
|
|
49
|
+
maxTableEntries: 10000000,
|
|
50
|
+
thresholdInTable: 0
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
registerPeriodic(logProfileMeasuresTimingsNow);
|