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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.45.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.41.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 { addTimeProfileDistribution, onTimeProfile } from "../5-diagnostics/nodeMetadata";
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)(1);
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)(1);
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)(1);
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)(1);
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)(obj.dataFiles.length);
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)(1);
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)(unconfirmedOldFiles2.length);
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)(deprecatedFiles.length);
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)(1);
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)(1);
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)(1);
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: DebugLog[] = [];
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
- // NOTE: Use a rolling index. We don't promise our results are sorted, so this is fine.
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
- //todonext;
1122
- // So... also record the watch start time, and the sync received time. So we can tell how long we waited
1123
- // to sync each path. It's a bit annoying to do this at such a low level, but... how long it takes
1124
- // to synchronize a path is probably the most important metric in the entire application. So... it's
1125
- // fair to put it here.
1126
- private watchers = registerResource("paths|PathWatcher.watchers", new Map<string, Set<PathWatcherCallback>>());
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 = new Set();
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 }>(valuesChanged: Set<T>, parentsSynced?: 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("Watcher % Waiting", () => {
168
- let totalLoop = harvestableReadyLoopCount + harvestableWaitingLoopCount;
169
- if (totalLoop === 0) return 0;
170
- let frac = harvestableWaitingLoopCount / totalLoop;
171
- harvestableWaitingLoopCount = 0;
172
- harvestableReadyLoopCount = 0;
173
- return frac;
174
- }, formatPercent);
175
- addStatPeriodic("Stalled Watchers", () => lastZombieCount);
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)(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);
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 "../5-diagnostics/nodeMetadata";
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)(paths.size);
638
- logNodeStats("archives|Unsynced Paths", formatNumber)(unsyncedPaths.size);
639
- logNodeStats("archives|Unsynced Parent Paths", formatNumber)(parentPaths.size);
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)(totalFreeCount);
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)(pendingFrees);
679
+ logNodeStats("archives|Stored future frees", formatNumber, pendingFrees);
680
680
  }
681
681
 
682
682
  if (example.lockBasedDelete) {
@@ -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 watch dispose if not inside of a `);
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";