querysub 0.45.0 → 0.50.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/package.json +2 -2
- package/src/-0-hooks/hooks.ts +83 -0
- package/src/-a-archives/archivesBackBlaze.ts +1 -1
- package/src/0-path-value-core/NodePathAuthorities.ts +5 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +12 -12
- package/src/0-path-value-core/debugLogs.ts +6 -15
- package/src/0-path-value-core/pathValueCore.ts +53 -17
- package/src/2-proxy/PathValueProxyWatcher.ts +18 -10
- package/src/2-proxy/archiveMoveHarness.ts +7 -7
- package/src/2-proxy/garbageCollection.ts +6 -6
- package/src/4-dom/qreact.tsx +8 -1
- package/src/4-querysub/Querysub.ts +9 -4
- package/src/5-diagnostics/GenericFormat.tsx +92 -1
- package/src/5-diagnostics/Table.tsx +7 -12
- package/src/5-diagnostics/TimeGrouper.tsx +24 -15
- package/src/5-diagnostics/memoryValueAudit.ts +7 -10
- package/src/5-diagnostics/nodeMetadata.ts +92 -44
- package/src/5-diagnostics/synchronousLagTracking.ts +1 -1
- package/src/diagnostics/NodeViewer.tsx +3 -4
- package/src/diagnostics/logs/DiskLoggerPage.tsx +28 -12
- package/src/diagnostics/logs/diskLogger.ts +9 -0
- package/src/diagnostics/trackResources.ts +2 -2
- package/src/diagnostics/watchdog.ts +19 -8
- package/src/library-components/TimeRangeSelector.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
25
25
|
"pako": "^2.1.0",
|
|
26
26
|
"preact": "^10.11.3",
|
|
27
|
-
"socket-function": "^0.
|
|
27
|
+
"socket-function": "^0.47.0",
|
|
28
28
|
"terser": "^5.31.0",
|
|
29
29
|
"typesafecss": "^0.6.3",
|
|
30
30
|
"yaml": "^2.5.0",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Hooks, to allow function implementations to be declared after their first call
|
|
2
|
+
// (so static calls work).
|
|
3
|
+
|
|
4
|
+
function createHookFunction<Fnc extends (...args: any[]) => void>(debugName: string): {
|
|
5
|
+
(...args: Parameters<Fnc>): void;
|
|
6
|
+
declare: (fnc: Fnc) => void;
|
|
7
|
+
} {
|
|
8
|
+
let queuedCalls = [] as Parameters<Fnc>[];
|
|
9
|
+
let declaration: Fnc | undefined;
|
|
10
|
+
setImmediate(() => {
|
|
11
|
+
if (!declaration) {
|
|
12
|
+
throw new Error(`Hook function ${debugName} not declared`);
|
|
13
|
+
}
|
|
14
|
+
for (let call of queuedCalls) {
|
|
15
|
+
declaration(...call);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
function fnc(...args: Parameters<Fnc>): void {
|
|
19
|
+
queuedCalls.push(args);
|
|
20
|
+
};
|
|
21
|
+
fnc.declare = (fnc: Fnc) => {
|
|
22
|
+
declaration = fnc;
|
|
23
|
+
};
|
|
24
|
+
return fnc;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const addStatPeriodic = createHookFunction<
|
|
28
|
+
(
|
|
29
|
+
config: {
|
|
30
|
+
title: string,
|
|
31
|
+
getValue: () => number,
|
|
32
|
+
format?: (value: number) => string,
|
|
33
|
+
}
|
|
34
|
+
) => void
|
|
35
|
+
>("addStatPeriodic");
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export const addStatSumPeriodic = createHookFunction<
|
|
39
|
+
(
|
|
40
|
+
config: {
|
|
41
|
+
title: string,
|
|
42
|
+
getValue: () => number,
|
|
43
|
+
format?: (value: number) => string,
|
|
44
|
+
}
|
|
45
|
+
) => void
|
|
46
|
+
>("addStatSumPeriodic");
|
|
47
|
+
|
|
48
|
+
export const addTimeProfileDistribution = createHookFunction<
|
|
49
|
+
(
|
|
50
|
+
title: string,
|
|
51
|
+
harvestTimeRanges: () => { start: number; end: number; }[],
|
|
52
|
+
) => void
|
|
53
|
+
>("addTimeProfileDistribution");
|
|
54
|
+
|
|
55
|
+
export const onTimeProfile = createHookFunction<
|
|
56
|
+
(
|
|
57
|
+
title: string,
|
|
58
|
+
startTime: number,
|
|
59
|
+
) => void
|
|
60
|
+
>("onTimeProfile");
|
|
61
|
+
|
|
62
|
+
import type { ExtraMetadata } from "../5-diagnostics/nodeMetadata";
|
|
63
|
+
export const registerNodeMetadata = createHookFunction<
|
|
64
|
+
(
|
|
65
|
+
metadata: ExtraMetadata
|
|
66
|
+
) => void
|
|
67
|
+
>("registerNodeMetadata");
|
|
68
|
+
|
|
69
|
+
export const logNodeStats = createHookFunction<
|
|
70
|
+
(
|
|
71
|
+
title: string,
|
|
72
|
+
format: (value: number) => string,
|
|
73
|
+
value: number,
|
|
74
|
+
) => void
|
|
75
|
+
>("logNodeStats");
|
|
76
|
+
|
|
77
|
+
export const logNodeStateStats = createHookFunction<
|
|
78
|
+
(
|
|
79
|
+
title: string,
|
|
80
|
+
format: (value: number) => string,
|
|
81
|
+
value: number,
|
|
82
|
+
) => void
|
|
83
|
+
>("logNodeStateStats");
|
|
@@ -10,7 +10,7 @@ import { devDebugbreak } from "../config";
|
|
|
10
10
|
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
11
11
|
import { blue, green } from "socket-function/src/formatting/logColors";
|
|
12
12
|
import debugbreak from "debugbreak";
|
|
13
|
-
import {
|
|
13
|
+
import { onTimeProfile } from "../-0-hooks/hooks";
|
|
14
14
|
|
|
15
15
|
export function hasBackblazePermissions() {
|
|
16
16
|
return isNode() && fs.existsSync(getBackblazePath());
|
|
@@ -26,6 +26,11 @@ import { diskLog } from "../diagnostics/logs/diskLogger";
|
|
|
26
26
|
export const LOCAL_DOMAIN = "LOCAL";
|
|
27
27
|
export const LOCAL_DOMAIN_PATH = getPathStr1(LOCAL_DOMAIN);
|
|
28
28
|
|
|
29
|
+
if (!getOwnNodeId) {
|
|
30
|
+
devDebugbreak();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
const POLL_RATE = 5000;
|
|
30
35
|
const MAX_RECONNECT_TIME = timeInMinute * 15;
|
|
31
36
|
const RECONNECT_POLL_INTERVAL = 10000;
|
|
@@ -8,12 +8,12 @@ import { Archives } from "../../-a-archives/archives";
|
|
|
8
8
|
import debugbreak from "debugbreak";
|
|
9
9
|
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
10
10
|
import { blue, green, magenta, red } from "socket-function/src/formatting/logColors";
|
|
11
|
-
import { logNodeStateStats, logNodeStats } from "../../5-diagnostics/nodeMetadata";
|
|
12
11
|
import { devDebugbreak } from "../../config";
|
|
13
12
|
import { logErrors } from "../../errors";
|
|
14
13
|
import { saveSnapshot } from "./archiveSnapshots";
|
|
15
14
|
import { getNodeId } from "socket-function/src/nodeCache";
|
|
16
15
|
import { diskLog } from "../../diagnostics/logs/diskLogger";
|
|
16
|
+
import { logNodeStateStats, logNodeStats } from "../../-0-hooks/hooks";
|
|
17
17
|
|
|
18
18
|
/** Clean up old files after a while */
|
|
19
19
|
const DEAD_CREATE_THRESHOLD = timeInHour * 12;
|
|
@@ -57,7 +57,7 @@ export function createArchiveLocker2(config: {
|
|
|
57
57
|
|
|
58
58
|
async setValue(key, value) {
|
|
59
59
|
await getArchives(key).set(key, value);
|
|
60
|
-
logNodeStats(`archives|Created TΔ`, formatNumber
|
|
60
|
+
logNodeStats(`archives|Created TΔ`, formatNumber, 1);
|
|
61
61
|
},
|
|
62
62
|
async getValue(key) {
|
|
63
63
|
return getArchives(key).get(key);
|
|
@@ -84,7 +84,7 @@ export function createArchiveLocker2(config: {
|
|
|
84
84
|
targetPath: key,
|
|
85
85
|
target: archiveRecycleBin,
|
|
86
86
|
});
|
|
87
|
-
logNodeStats(`archives|Deleted TΔ`, formatNumber
|
|
87
|
+
logNodeStats(`archives|Deleted TΔ`, formatNumber, 1);
|
|
88
88
|
} catch {
|
|
89
89
|
// It was probably just moved by another process
|
|
90
90
|
}
|
|
@@ -446,7 +446,7 @@ class TransactionLocker {
|
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
448
|
console.warn(message);
|
|
449
|
-
logNodeStats(`archives|TΔ Atomic Retry`, formatNumber
|
|
449
|
+
logNodeStats(`archives|TΔ Atomic Retry`, formatNumber, 1);
|
|
450
450
|
return false;
|
|
451
451
|
}
|
|
452
452
|
}
|
|
@@ -557,7 +557,7 @@ class TransactionLocker {
|
|
|
557
557
|
if (LOG) {
|
|
558
558
|
console.log(`Applying transaction with ${createCount} creates and ${deleteCount} deletes. ${lockedFiles !== undefined && `Lock state depends on ${lockedFiles} files` || ""}`);
|
|
559
559
|
}
|
|
560
|
-
logNodeStats(`archives|TΔ Apply`, formatNumber
|
|
560
|
+
logNodeStats(`archives|TΔ Apply`, formatNumber, 1);
|
|
561
561
|
let opsRemaining = transaction.ops.slice();
|
|
562
562
|
// NOTE: Order doesn't matter here. If anything is reading the values
|
|
563
563
|
// 1) If it runs after we start, it will see our transaction and apply it
|
|
@@ -590,7 +590,7 @@ class TransactionLocker {
|
|
|
590
590
|
*/
|
|
591
591
|
public async getFiles(): Promise<FileInfo[]> {
|
|
592
592
|
let obj = await this.getFilesBase();
|
|
593
|
-
logNodeStateStats(`ArchiveLock Data File`, formatNumber
|
|
593
|
+
logNodeStateStats(`ArchiveLock Data File`, formatNumber, obj.dataFiles.length);
|
|
594
594
|
return obj.dataFiles;
|
|
595
595
|
}
|
|
596
596
|
private transactionAppliedCount = new Map<number, number>();
|
|
@@ -619,7 +619,7 @@ class TransactionLocker {
|
|
|
619
619
|
});
|
|
620
620
|
if (transactions.map(a => a.seqNum).join(",") !== transationsTest.map(a => a.seqNum).join(",")) {
|
|
621
621
|
console.warn("Transaction order is different when sorting by writeTime. This is likely due to a hanging writes.");
|
|
622
|
-
logNodeStats(`archives|TΔ Possible Hanging Write`, formatNumber
|
|
622
|
+
logNodeStats(`archives|TΔ Possible Hanging Write`, formatNumber, 1);
|
|
623
623
|
}
|
|
624
624
|
}
|
|
625
625
|
|
|
@@ -689,7 +689,7 @@ class TransactionLocker {
|
|
|
689
689
|
let unconfirmedOldFiles2 = veryOldFiles.filter(a => !doubleCheckLookup.has(a) && doubleCheckDataFiles.has(a.file));
|
|
690
690
|
console.warn(red(`Deleted ${unconfirmedOldFiles2.length} very old unconfirmed files`));
|
|
691
691
|
if (LOG) {
|
|
692
|
-
logNodeStats(`archives|TΔ Delete Old Rejected File`, formatNumber
|
|
692
|
+
logNodeStats(`archives|TΔ Delete Old Rejected File`, formatNumber, unconfirmedOldFiles2.length);
|
|
693
693
|
}
|
|
694
694
|
// At the point the file was very old when we started reading, not part of the active transaction.
|
|
695
695
|
for (let file of unconfirmedOldFiles2) {
|
|
@@ -708,7 +708,7 @@ class TransactionLocker {
|
|
|
708
708
|
if (deprecatedFiles.length > 0) {
|
|
709
709
|
if (LOG) {
|
|
710
710
|
console.warn(red(`Deleted ${deprecatedFiles.length} / ${oldEnoughConfirms.length} confirmations, for not having corresponding data files`));
|
|
711
|
-
logNodeStats(`archives|TΔ Delete Deprecated Confirm`, formatNumber
|
|
711
|
+
logNodeStats(`archives|TΔ Delete Deprecated Confirm`, formatNumber, deprecatedFiles.length);
|
|
712
712
|
}
|
|
713
713
|
for (let file of deprecatedFiles) {
|
|
714
714
|
await this.storage.deleteKey(file.file);
|
|
@@ -768,13 +768,13 @@ class TransactionLocker {
|
|
|
768
768
|
// which will cause our transaction to never apply. Otherwise... we MUST still
|
|
769
769
|
// be valid!
|
|
770
770
|
|
|
771
|
-
logNodeStats(`archives|TΔ Create File`, formatNumber
|
|
771
|
+
logNodeStats(`archives|TΔ Create File`, formatNumber, 1);
|
|
772
772
|
await this.prepareTransaction(transaction);
|
|
773
773
|
|
|
774
774
|
while (true) {
|
|
775
775
|
let beforeData = await this.getFilesBase();
|
|
776
776
|
if (!this.isTransactionValid(transaction, beforeData.dataFiles, beforeData.rawDataFiles)) {
|
|
777
|
-
logNodeStats(`archives|TΔ Rejected`, formatNumber
|
|
777
|
+
logNodeStats(`archives|TΔ Rejected`, formatNumber, 1);
|
|
778
778
|
if (LOG) {
|
|
779
779
|
console.log(red(`Finished transaction with rejection, ${transaction.ops.length} ops`));
|
|
780
780
|
}
|
|
@@ -785,7 +785,7 @@ class TransactionLocker {
|
|
|
785
785
|
|
|
786
786
|
let afterData = await this.getFilesBase();
|
|
787
787
|
if (this.wasTransactionApplied(transaction, afterData.dataFiles, afterData.rawDataFiles)) {
|
|
788
|
-
logNodeStats(`archives|TΔ Accepted`, formatNumber
|
|
788
|
+
logNodeStats(`archives|TΔ Accepted`, formatNumber, 1);
|
|
789
789
|
if (LOG) {
|
|
790
790
|
console.log(green(`Finished transaction with ${transaction.ops.length} ops`));
|
|
791
791
|
}
|
|
@@ -2,6 +2,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
|
|
|
2
2
|
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
3
3
|
import { isDevDebugbreak } from "../config";
|
|
4
4
|
import { measureWrap } from "socket-function/src/profiling/measure";
|
|
5
|
+
import { QueueLimited } from "socket-function/src/misc";
|
|
5
6
|
|
|
6
7
|
export interface DebugLog {
|
|
7
8
|
type: string;
|
|
@@ -9,7 +10,6 @@ export interface DebugLog {
|
|
|
9
10
|
values: { [key: string]: unknown };
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const MAX_LOG_HISTORY = 1000 * 1000 * 10;
|
|
13
13
|
let ENABLED_LOGGING = isDevDebugbreak();
|
|
14
14
|
export function enableDebugLogging() {
|
|
15
15
|
ENABLED_LOGGING = true;
|
|
@@ -23,18 +23,17 @@ export function isDebugLogEnabled() {
|
|
|
23
23
|
return ENABLED_LOGGING;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
let logHistory
|
|
27
|
-
let nextIndex = 0;
|
|
26
|
+
let logHistory = new QueueLimited<DebugLog>(1000 * 1000 * 10);
|
|
28
27
|
export function getFullLogHistory() {
|
|
29
28
|
return logHistory;
|
|
30
29
|
}
|
|
31
30
|
export function getLogHistoryIncludes(includes: string) {
|
|
32
|
-
return logHistory.filter(x => {
|
|
31
|
+
return logHistory.getAllUnordered().filter(x => {
|
|
33
32
|
return Object.values(x.values).some(y => String(y).includes(includes));
|
|
34
33
|
});
|
|
35
34
|
}
|
|
36
35
|
export function getLogHistoryEquals(value: string) {
|
|
37
|
-
return logHistory.filter(x => {
|
|
36
|
+
return logHistory.getAllUnordered().filter(x => {
|
|
38
37
|
return Object.values(x.values).some(y => y === value);
|
|
39
38
|
});
|
|
40
39
|
}
|
|
@@ -50,15 +49,7 @@ function debugLogBase(type: string, values: { [key: string]: unknown }) {
|
|
|
50
49
|
return;
|
|
51
50
|
}
|
|
52
51
|
let newEntry: DebugLog = { type, time: Date.now(), values };
|
|
53
|
-
|
|
54
|
-
// ALSO, .shift to make a queue lags (it was making this function profile 90% of the time,
|
|
55
|
-
// taking multiple seconds).
|
|
56
|
-
if (logHistory.length < MAX_LOG_HISTORY) {
|
|
57
|
-
logHistory.push(newEntry);
|
|
58
|
-
} else {
|
|
59
|
-
logHistory[nextIndex] = newEntry;
|
|
60
|
-
nextIndex = (nextIndex + 1) % MAX_LOG_HISTORY;
|
|
61
|
-
}
|
|
52
|
+
logHistory.push(newEntry);
|
|
62
53
|
};
|
|
63
54
|
|
|
64
55
|
class DebugLogControllerBase {
|
|
@@ -70,7 +61,7 @@ class DebugLogControllerBase {
|
|
|
70
61
|
}
|
|
71
62
|
public async getLogHistorySize() {
|
|
72
63
|
return {
|
|
73
|
-
"logHistory.length": logHistory.length,
|
|
64
|
+
"logHistory.length": logHistory.getAllUnordered().length,
|
|
74
65
|
};
|
|
75
66
|
}
|
|
76
67
|
}
|
|
@@ -13,7 +13,7 @@ import { batchFunction, delay, runInfinitePoll } from "socket-function/src/batch
|
|
|
13
13
|
import { ActionsHistory } from "../diagnostics/ActionsHistory";
|
|
14
14
|
import { markArrayAsSplitable } from "socket-function/src/fixLargeNetworkCalls";
|
|
15
15
|
import { registerDynamicResource, registerMapArrayResource, registerResource } from "../diagnostics/trackResources";
|
|
16
|
-
import { binarySearchIndex, isNode, isNodeTrue, promiseObj, timeInHour, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
16
|
+
import { binarySearchIndex, isNode, isNodeTrue, last, promiseObj, sort, timeInHour, timeInMinute, timeInSecond, QueueLimited } from "socket-function/src/misc";
|
|
17
17
|
import { isNodeTrusted, isTrusted, isTrustedByNode } from "../-d-trust/NetworkTrust2";
|
|
18
18
|
import { AuthorityPath, LOCAL_DOMAIN_PATH, pathValueAuthority2 } from "./NodePathAuthorities";
|
|
19
19
|
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
@@ -1115,15 +1115,22 @@ export const authorityStorage = new AuthorityPathValueStorage();
|
|
|
1115
1115
|
// If PathValue, it is a local watch
|
|
1116
1116
|
export type WriteCallback = NodeId | PathValue[];
|
|
1117
1117
|
|
|
1118
|
+
|
|
1118
1119
|
// isOwnNodeId(nodeId) means localOnValueCallback is called (instead of PathValueController.forwardWrites)
|
|
1119
1120
|
export type PathWatcherCallback = NodeId;
|
|
1120
1121
|
class PathWatcher {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1122
|
+
private watchers = registerResource("paths|PathWatcher.watchers", new Map<string, {
|
|
1123
|
+
watchers: Set<PathWatcherCallback>;
|
|
1124
|
+
requestedTime: number;
|
|
1125
|
+
receivedTime: number;
|
|
1126
|
+
}>());
|
|
1127
|
+
// If we don't limit the length, then this will memory leak. And if we only track
|
|
1128
|
+
// the values in watchers, then rolling key syncs will be lost.
|
|
1129
|
+
private syncHistoryForHarvest = new QueueLimited<{ start: number; end: number; path: string; }>(1_000_000);
|
|
1130
|
+
private syncHistoryForDebug = new QueueLimited<{ start: number; end: number; path: string; }>(1_000_000);
|
|
1131
|
+
|
|
1132
|
+
//private watchers = registerResource("paths|PathWatcher.watchers", new Map<string, Set<PathWatcherCallback>>());
|
|
1133
|
+
|
|
1127
1134
|
// realPath => packedPath => watcher => { depth, start, end }[]
|
|
1128
1135
|
private parentWatchers = registerResource("paths|parentWatchers", new Map<string,
|
|
1129
1136
|
// packedPath
|
|
@@ -1154,15 +1161,21 @@ class PathWatcher {
|
|
|
1154
1161
|
let newPathsWatched = new Set<string>();
|
|
1155
1162
|
let newParentsWatched = new Set<string>();
|
|
1156
1163
|
|
|
1164
|
+
let time = Date.now();
|
|
1165
|
+
|
|
1157
1166
|
for (let path of config.paths) {
|
|
1158
1167
|
let watchers = this.watchers.get(path);
|
|
1159
1168
|
if (!watchers) {
|
|
1160
|
-
watchers =
|
|
1169
|
+
watchers = {
|
|
1170
|
+
watchers: new Set(),
|
|
1171
|
+
requestedTime: time,
|
|
1172
|
+
receivedTime: 0,
|
|
1173
|
+
};
|
|
1161
1174
|
this.watchers.set(path, watchers);
|
|
1162
1175
|
}
|
|
1163
|
-
if (watchers.has(config.callback)) continue;
|
|
1176
|
+
if (watchers.watchers.has(config.callback)) continue;
|
|
1164
1177
|
newPathsWatched.add(path);
|
|
1165
|
-
watchers.add(config.callback);
|
|
1178
|
+
watchers.watchers.add(config.callback);
|
|
1166
1179
|
|
|
1167
1180
|
let watchObj = this.watchersToPaths.get(config.callback);
|
|
1168
1181
|
if (!watchObj) {
|
|
@@ -1264,8 +1277,8 @@ class PathWatcher {
|
|
|
1264
1277
|
|
|
1265
1278
|
let watchers = this.watchers.get(path);
|
|
1266
1279
|
if (!watchers) continue;
|
|
1267
|
-
watchers.delete(callback);
|
|
1268
|
-
if (watchers.size === 0) {
|
|
1280
|
+
watchers.watchers.delete(callback);
|
|
1281
|
+
if (watchers.watchers.size === 0) {
|
|
1269
1282
|
this.watchers.delete(path);
|
|
1270
1283
|
fullyUnwatched.paths.push(path);
|
|
1271
1284
|
authorityStorage.markPathAsUnwatched(path);
|
|
@@ -1312,7 +1325,7 @@ class PathWatcher {
|
|
|
1312
1325
|
let watchers = this.watchers.get(path);
|
|
1313
1326
|
if (watchers) {
|
|
1314
1327
|
this.watchers.delete(path);
|
|
1315
|
-
for (let watcher of watchers) {
|
|
1328
|
+
for (let watcher of watchers.watchers) {
|
|
1316
1329
|
let watchObj = this.watchersToPaths.get(watcher);
|
|
1317
1330
|
if (watchObj) {
|
|
1318
1331
|
watchObj.paths.delete(path);
|
|
@@ -1335,7 +1348,7 @@ class PathWatcher {
|
|
|
1335
1348
|
}
|
|
1336
1349
|
|
|
1337
1350
|
public triggerValuesChanged(valuesChanged: Set<PathValue>, parentsSynced?: string[], initialTriggers?: { values: Set<PathValue>; parentPaths: Set<string> }) {
|
|
1338
|
-
let changedPerCallbacks = this.getWatchers(valuesChanged, parentsSynced);
|
|
1351
|
+
let changedPerCallbacks = this.getWatchers(valuesChanged, { parentsSynced, pathsAreSynced: true });
|
|
1339
1352
|
for (let [watch, changes] of changedPerCallbacks) {
|
|
1340
1353
|
if (initialTriggers) {
|
|
1341
1354
|
let valuesFromInitialTrigger = new Set<PathValue>();
|
|
@@ -1371,11 +1384,24 @@ class PathWatcher {
|
|
|
1371
1384
|
}
|
|
1372
1385
|
}
|
|
1373
1386
|
}
|
|
1374
|
-
public getWatchers<T extends { path: string }>(
|
|
1387
|
+
public getWatchers<T extends { path: string }>(
|
|
1388
|
+
valuesChanged: Set<T>,
|
|
1389
|
+
config?: {
|
|
1390
|
+
parentsSynced?: string[];
|
|
1391
|
+
pathsAreSynced?: boolean;
|
|
1392
|
+
}
|
|
1393
|
+
) {
|
|
1375
1394
|
let changedPerCallbacks: Map<PathWatcherCallback, Set<T>> = new Map();
|
|
1395
|
+
let time = Date.now();
|
|
1376
1396
|
for (let value of valuesChanged) {
|
|
1377
1397
|
let path = value.path;
|
|
1378
1398
|
let latestWatches = this.watchers.get(path);
|
|
1399
|
+
if (config?.pathsAreSynced && latestWatches && !latestWatches.receivedTime) {
|
|
1400
|
+
latestWatches.receivedTime = time;
|
|
1401
|
+
let obj = { start: latestWatches.requestedTime, end: time, path };
|
|
1402
|
+
this.syncHistoryForHarvest.push(obj);
|
|
1403
|
+
this.syncHistoryForDebug.push(obj);
|
|
1404
|
+
}
|
|
1379
1405
|
|
|
1380
1406
|
function triggerNodeChanged(watcher: NodeId) {
|
|
1381
1407
|
let changes = changedPerCallbacks.get(watcher);
|
|
@@ -1387,7 +1413,7 @@ class PathWatcher {
|
|
|
1387
1413
|
}
|
|
1388
1414
|
|
|
1389
1415
|
if (latestWatches) {
|
|
1390
|
-
for (let watch of latestWatches) {
|
|
1416
|
+
for (let watch of latestWatches.watchers) {
|
|
1391
1417
|
triggerNodeChanged(watch);
|
|
1392
1418
|
}
|
|
1393
1419
|
}
|
|
@@ -1411,7 +1437,7 @@ class PathWatcher {
|
|
|
1411
1437
|
}
|
|
1412
1438
|
}
|
|
1413
1439
|
}
|
|
1414
|
-
for (let parentPath of parentsSynced ?? []) {
|
|
1440
|
+
for (let parentPath of config?.parentsSynced ?? []) {
|
|
1415
1441
|
let latestParentWatches = this.parentWatchers.get(parentPath);
|
|
1416
1442
|
if (latestParentWatches) {
|
|
1417
1443
|
for (let { watchers } of latestParentWatches.values()) {
|
|
@@ -1504,6 +1530,16 @@ class PathWatcher {
|
|
|
1504
1530
|
public getAllParentWatches() {
|
|
1505
1531
|
return Array.from(this.parentWatchers.keys());
|
|
1506
1532
|
}
|
|
1533
|
+
|
|
1534
|
+
public debug_harvestSyncTimes(): { start: number; end: number; path: string; }[] {
|
|
1535
|
+
let times = this.syncHistoryForHarvest.getAllUnordered();
|
|
1536
|
+
this.syncHistoryForHarvest.reset();
|
|
1537
|
+
return times;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
public debug_getSyncHistory(): { start: number; end: number; path: string; }[] {
|
|
1541
|
+
return this.syncHistoryForDebug.getAllUnordered();
|
|
1542
|
+
}
|
|
1507
1543
|
}
|
|
1508
1544
|
export const pathWatcher = new PathWatcher();
|
|
1509
1545
|
|
|
@@ -32,8 +32,8 @@ import { DEPTH_TO_DATA, MODULE_INDEX, getCurrentCall, getCurrentCallObj } from "
|
|
|
32
32
|
import { inlineNestedCalls } from "../3-path-functions/syncSchema";
|
|
33
33
|
import { interceptCalls, runCall } from "../3-path-functions/PathFunctionHelpers";
|
|
34
34
|
import { deepCloneCborx } from "../misc/cloneHelpers";
|
|
35
|
-
import { addStatPeriodic, addTimeProfileDistribution, onTimeProfile } from "../5-diagnostics/nodeMetadata";
|
|
36
35
|
import { formatPercent } from "socket-function/src/formatting/format";
|
|
36
|
+
import { addStatPeriodic, onTimeProfile } from "../-0-hooks/hooks";
|
|
37
37
|
|
|
38
38
|
// TODO: Break this into two parts:
|
|
39
39
|
// 1) Run and get accesses
|
|
@@ -164,15 +164,23 @@ let harvestableReadyLoopCount = 0;
|
|
|
164
164
|
let harvestableWaitingLoopCount = 0;
|
|
165
165
|
let lastZombieCount = 0;
|
|
166
166
|
// NOTE: Only the % waiting for active watchers. Which... is fine
|
|
167
|
-
addStatPeriodic(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
addStatPeriodic({
|
|
168
|
+
title: "Watcher % Waiting",
|
|
169
|
+
getValue: () => {
|
|
170
|
+
let totalLoop = harvestableReadyLoopCount + harvestableWaitingLoopCount;
|
|
171
|
+
if (totalLoop === 0) return 0;
|
|
172
|
+
let frac = harvestableWaitingLoopCount / totalLoop;
|
|
173
|
+
harvestableWaitingLoopCount = 0;
|
|
174
|
+
harvestableReadyLoopCount = 0;
|
|
175
|
+
return frac;
|
|
176
|
+
},
|
|
177
|
+
format: formatPercent,
|
|
178
|
+
});
|
|
179
|
+
addStatPeriodic({
|
|
180
|
+
title: "Stalled Watchers",
|
|
181
|
+
getValue: () => lastZombieCount,
|
|
182
|
+
});
|
|
183
|
+
|
|
176
184
|
|
|
177
185
|
// Used to prevent local rejections on remote values (as local functions aren't rerun)
|
|
178
186
|
// Also used for security on servers, so they can read from untrusted domains, but can't have values
|
|
@@ -9,9 +9,9 @@ import { AuthorityPath, pathValueAuthority2 } from "../0-path-value-core/NodePat
|
|
|
9
9
|
import { FileInfo, ArchiveTransaction } from "../0-path-value-core/archiveLocks/ArchiveLocks";
|
|
10
10
|
import { DecodedValuePath, pathValueArchives, PathValueArchives } from "../0-path-value-core/pathValueArchives";
|
|
11
11
|
import { PathValue, VALUE_GC_THRESHOLD, FILE_VALUE_COUNT_LIMIT, FILE_SIZE_LIMIT, compareTime } from "../0-path-value-core/pathValueCore";
|
|
12
|
-
import { logNodeStats } from "../5-diagnostics/nodeMetadata";
|
|
13
12
|
import { getSingleSizeEstimate } from "../5-diagnostics/shared";
|
|
14
13
|
import { getOurAuthorities } from "../config2";
|
|
14
|
+
import { logNodeStats } from "../-0-hooks/hooks";
|
|
15
15
|
import debugbreak from "debugbreak";
|
|
16
16
|
|
|
17
17
|
// NOTE: We probably COULD load values with skipStrings+skipValues, for some GCers, as some
|
|
@@ -314,12 +314,12 @@ export async function runArchiveMover(config: {
|
|
|
314
314
|
+ "\n=>\n"
|
|
315
315
|
+ ` { files: ${formatNumber(outputFiles)}, values: ${formatNumber(outputValueCount)}, bytes: ${formatNumber(outputBytes)} }`
|
|
316
316
|
);
|
|
317
|
-
logNodeStats("archives|Input Files", formatNumber
|
|
318
|
-
logNodeStats("archives|Input Values", formatNumber
|
|
319
|
-
logNodeStats("archives|Input Bytes", formatNumber
|
|
320
|
-
logNodeStats("archives|Output Files", formatNumber
|
|
321
|
-
logNodeStats("archives|Output Values", formatNumber
|
|
322
|
-
logNodeStats("archives|Output Bytes", formatNumber
|
|
317
|
+
logNodeStats("archives|Input Files", formatNumber, time);
|
|
318
|
+
logNodeStats("archives|Input Values", formatNumber, time);
|
|
319
|
+
logNodeStats("archives|Input Bytes", formatNumber, time);
|
|
320
|
+
logNodeStats("archives|Output Files", formatNumber, time);
|
|
321
|
+
logNodeStats("archives|Output Values", formatNumber, time);
|
|
322
|
+
logNodeStats("archives|Output Bytes", formatNumber, time);
|
|
323
323
|
}
|
|
324
324
|
console.log(" ");
|
|
325
325
|
}
|
|
@@ -9,7 +9,7 @@ import { AuthorityPath, pathValueAuthority2 } from "../0-path-value-core/NodePat
|
|
|
9
9
|
import { pathValueCommitter } from "../0-path-value-core/PathValueCommitter";
|
|
10
10
|
import { ReadLock, epochTime, PathValue, authorityStorage, compareTime, getNextTime } from "../0-path-value-core/pathValueCore";
|
|
11
11
|
import { WatchSpec, clientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
12
|
-
import { logNodeStats } from "
|
|
12
|
+
import { logNodeStats } from "../-0-hooks/hooks";
|
|
13
13
|
import { getPathFromStr, getPathStr } from "../path";
|
|
14
14
|
import { PromiseObj } from "../promise";
|
|
15
15
|
import { proxyWatcher, atomicRaw, atomic } from "./PathValueProxyWatcher";
|
|
@@ -634,9 +634,9 @@ export async function runAliveCheckerIteration(config?: {
|
|
|
634
634
|
console.log(` currently unsynced paths ${formatNumber(paths.size)}, unsynced parent paths ${formatNumber(parentPaths.size)}`);
|
|
635
635
|
console.log(` Total path watches ${formatNumber(watchSpec.paths.size)}, total parent path watches ${formatNumber(watchSpec.parentPaths.size)}`);
|
|
636
636
|
console.log(` Total out of shard values ${formatNumber(outOfShardValues.size)}`);
|
|
637
|
-
logNodeStats("archives|Unsynced Checkers", formatNumber
|
|
638
|
-
logNodeStats("archives|Unsynced Paths", formatNumber
|
|
639
|
-
logNodeStats("archives|Unsynced Parent Paths", formatNumber
|
|
637
|
+
logNodeStats("archives|Unsynced Checkers", formatNumber, paths.size);
|
|
638
|
+
logNodeStats("archives|Unsynced Paths", formatNumber, unsyncedPaths.size);
|
|
639
|
+
logNodeStats("archives|Unsynced Parent Paths", formatNumber, parentPaths.size);
|
|
640
640
|
|
|
641
641
|
clientWatcher.setWatches(watchSpec);
|
|
642
642
|
|
|
@@ -656,7 +656,7 @@ export async function runAliveCheckerIteration(config?: {
|
|
|
656
656
|
}
|
|
657
657
|
}
|
|
658
658
|
console.log(green(`Free now: ${formatNumber(totalFreeCount)} / ${formatNumber(values.length)}`));
|
|
659
|
-
logNodeStats("archives|Free now", formatNumber
|
|
659
|
+
logNodeStats("archives|Free now", formatNumber, totalFreeCount);
|
|
660
660
|
}
|
|
661
661
|
{
|
|
662
662
|
let pendingFrees = gcRequests.size;
|
|
@@ -676,7 +676,7 @@ export async function runAliveCheckerIteration(config?: {
|
|
|
676
676
|
console.log(` < ${day} days: ${formatNumber(count)}`);
|
|
677
677
|
}
|
|
678
678
|
|
|
679
|
-
logNodeStats("archives|Stored future frees", formatNumber
|
|
679
|
+
logNodeStats("archives|Stored future frees", formatNumber, pendingFrees);
|
|
680
680
|
}
|
|
681
681
|
|
|
682
682
|
if (example.lockBasedDelete) {
|
package/src/4-dom/qreact.tsx
CHANGED
|
@@ -184,6 +184,7 @@ export class qreact {
|
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
public static getRenderingComponent = getRenderingComponent;
|
|
187
|
+
public static getRenderingComponentAssert = getRenderingComponentAssert;
|
|
187
188
|
public static getAncestor = getAncestor;
|
|
188
189
|
public static getAncestorProps = getAncestorProps;
|
|
189
190
|
public static watchDispose = watchDispose;
|
|
@@ -329,9 +330,15 @@ function getCurrentComponentId() {
|
|
|
329
330
|
function getRenderingComponent() {
|
|
330
331
|
return QRenderClass.getInstanceAllowedUndefined(QRenderClass.renderingComponentId ?? -1) as ExternalRenderClass | undefined;
|
|
331
332
|
}
|
|
333
|
+
function getRenderingComponentAssert() {
|
|
334
|
+
let component = getRenderingComponent();
|
|
335
|
+
if (!component) throw new Error(`Can only call getRenderingComponentAssert if inside of a render function.`);
|
|
336
|
+
return component;
|
|
337
|
+
}
|
|
332
338
|
function getAncestorProps<Props>(
|
|
333
339
|
componentClass: { new(...args: any[]): { props: Props } },
|
|
334
340
|
component?: unknown
|
|
341
|
+
|
|
335
342
|
): Props | undefined {
|
|
336
343
|
let id = (component as any)?.[idSymbol];
|
|
337
344
|
let ancestor = getAncestor(componentClass, id);
|
|
@@ -351,7 +358,7 @@ function getAncestor(
|
|
|
351
358
|
}
|
|
352
359
|
function onDispose(fnc: () => void) {
|
|
353
360
|
let component = getRenderingComponent();
|
|
354
|
-
if (!component) throw new Error(`Can
|
|
361
|
+
if (!component) throw new Error(`Can only call onDispose if inside of a render (or componentDidMount) function. If an an event callback, call qreact.getRenderingComponent in render, and then call qreact.watchDispose(component, callback).`);
|
|
355
362
|
watchDispose(component, fnc);
|
|
356
363
|
}
|
|
357
364
|
function watchDispose(renderClass: ExternalRenderClass, fnc: () => void) {
|
|
@@ -26,9 +26,6 @@ import { PermissionsCheck } from "./permissions";
|
|
|
26
26
|
import { inlineNestedCalls, syncSchema } from "../3-path-functions/syncSchema";
|
|
27
27
|
import type { identityStorageKey, IdentityStorageType } from "../-a-auth/certs";
|
|
28
28
|
|
|
29
|
-
import "../diagnostics/watchdog";
|
|
30
|
-
import "../diagnostics/trackResources";
|
|
31
|
-
import "../diagnostics/benchmark";
|
|
32
29
|
import { qreact } from "../4-dom/qreact";
|
|
33
30
|
import { configRootDiscoveryLocation, getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
34
31
|
import { pathValueCommitter } from "../0-path-value-core/PathValueCommitter";
|
|
@@ -53,6 +50,10 @@ import { isDynamicModule } from "../3-path-functions/pathFunctionLoader";
|
|
|
53
50
|
|
|
54
51
|
export { t };
|
|
55
52
|
|
|
53
|
+
if (!registerGetCompressNetwork) {
|
|
54
|
+
devDebugbreak();
|
|
55
|
+
}
|
|
56
|
+
|
|
56
57
|
|
|
57
58
|
export type MachineSourceCheck<T = unknown> = {
|
|
58
59
|
/** NOTE: The source for this is added inline, and so it cannot use any external variables. */
|
|
@@ -873,4 +874,8 @@ setImmediate(() => {
|
|
|
873
874
|
registerGetCompressNetwork(() => Querysub.COMPRESS_NETWORK);
|
|
874
875
|
registerGetCompressDisk(() => Querysub.COMPRESS_DISK);
|
|
875
876
|
|
|
876
|
-
(globalThis as any).Querysub = Querysub;
|
|
877
|
+
(globalThis as any).Querysub = Querysub;
|
|
878
|
+
|
|
879
|
+
import "../diagnostics/watchdog";
|
|
880
|
+
import "../diagnostics/trackResources";
|
|
881
|
+
import "../diagnostics/benchmark";
|