querysub 0.608.0 → 0.610.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.
|
|
3
|
+
"version": "0.610.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",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
76
76
|
"pako": "^2.1.0",
|
|
77
77
|
"peggy": "^5.0.6",
|
|
78
|
-
"sliftutils": "^1.7.
|
|
78
|
+
"sliftutils": "^1.7.84",
|
|
79
79
|
"socket-function": "^1.2.30",
|
|
80
80
|
"terser": "^5.31.0",
|
|
81
81
|
"typenode": "^6.6.1",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
-
import { getArchives2 } from "../-a-archives/archives2";
|
|
2
|
+
import { getArchives2, getArchives2Buffered } from "../-a-archives/archives2";
|
|
3
3
|
import { getDomain, isDevDebugbreak, isNoNetwork, isPublic } from "../config";
|
|
4
4
|
import { measureBlock } from "socket-function/src/profiling/measure";
|
|
5
5
|
import { isNode, keyByArray, sha256Hash, throttleFunction, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
@@ -56,7 +56,7 @@ let DEAD_NODE_POLL_COOLDOWN = timeInMinute * 5;
|
|
|
56
56
|
|
|
57
57
|
let shutdown = false;
|
|
58
58
|
|
|
59
|
-
const archives = lazy(() =>
|
|
59
|
+
const archives = lazy(() => getArchives2Buffered("nodes/"));
|
|
60
60
|
|
|
61
61
|
let logging = true;
|
|
62
62
|
export function enableNodeDiscoveryLogging() {
|
|
@@ -128,6 +128,7 @@ class OperationSummaryTable extends qreact.Component<{ serverUrl: string; operat
|
|
|
128
128
|
}
|
|
129
129
|
if (!summaries) return <div>Loading...</div>;
|
|
130
130
|
if (!summaries.length) return <div className={css.colorhsl(OP_METRIC_COLOR.h, OP_METRIC_COLOR.s, OP_METRIC_COLOR.l)}>No accesses recorded.</div>;
|
|
131
|
+
sort(summaries, entry => -entry.weight);
|
|
131
132
|
let rows = summaries.map(entry => ({
|
|
132
133
|
path: entry.path,
|
|
133
134
|
value: formatAccessMetric(entry.weight, asBytes),
|
|
@@ -54,6 +54,48 @@ export async function getSuppressionEntries(): Promise<SuppressionEntry[]> {
|
|
|
54
54
|
|
|
55
55
|
let getMatcher = cacheLimited(10000, (text: string) => createMatchesPattern(Buffer.from(text), false));
|
|
56
56
|
|
|
57
|
+
// The watch loop checks every incoming error against the suppression list. Reading the suppression archive once per error produced thousands of storage listings per minute, so the hot path is served from a cached copy: the first pass tolerates 15 minute staleness, and only errors that pass unsuppressed force a fresher (30 second) re-read.
|
|
58
|
+
const SUPPRESSION_CACHE_TTL = timeInMinute * 15;
|
|
59
|
+
const SUPPRESSION_FRESH_CACHE_TTL = 1000 * 30;
|
|
60
|
+
let cachedSuppressionEntries: SuppressionEntry[] = [];
|
|
61
|
+
let suppressionCacheTime = 0;
|
|
62
|
+
let suppressionFreshCacheTime = 0;
|
|
63
|
+
|
|
64
|
+
async function refreshSuppressionEntriesCache() {
|
|
65
|
+
cachedSuppressionEntries = await getSuppressionEntries();
|
|
66
|
+
suppressionCacheTime = Date.now();
|
|
67
|
+
suppressionFreshCacheTime = Date.now();
|
|
68
|
+
return cachedSuppressionEntries;
|
|
69
|
+
}
|
|
70
|
+
async function getSuppressionEntriesCached() {
|
|
71
|
+
if (Date.now() - suppressionCacheTime > SUPPRESSION_CACHE_TTL) {
|
|
72
|
+
return await refreshSuppressionEntriesCache();
|
|
73
|
+
}
|
|
74
|
+
return cachedSuppressionEntries;
|
|
75
|
+
}
|
|
76
|
+
async function getSuppressionEntriesFresh() {
|
|
77
|
+
if (Date.now() - suppressionFreshCacheTime > SUPPRESSION_FRESH_CACHE_TTL) {
|
|
78
|
+
return await refreshSuppressionEntriesCache();
|
|
79
|
+
}
|
|
80
|
+
return cachedSuppressionEntries;
|
|
81
|
+
}
|
|
82
|
+
function invalidateSuppressionEntriesCaches() {
|
|
83
|
+
suppressionCacheTime = 0;
|
|
84
|
+
suppressionFreshCacheTime = 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Side-effect-free version of applySuppression's "suppressed" verdict, for deciding which entry set the watch loop should apply against.
|
|
88
|
+
function isSuppressedByAny(error: LogDatum, suppressionEntries: SuppressionEntry[]): boolean {
|
|
89
|
+
let errorBuffer = Buffer.from(JSON.stringify(error));
|
|
90
|
+
for (let suppressionEntry of suppressionEntries) {
|
|
91
|
+
let isMatch = getMatcher(suppressionEntry.pattern);
|
|
92
|
+
if (isMatch(errorBuffer) && error.time <= suppressionEntry.timeout) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
57
99
|
const MAX_UNMATCHED = 10000;
|
|
58
100
|
const MAX_EXAMPLES = 100;
|
|
59
101
|
let unmatchedIndex = 0;
|
|
@@ -299,7 +341,11 @@ export async function exposeErrorWatchService() {
|
|
|
299
341
|
let errorLogs = await getErrorLogs();
|
|
300
342
|
for await (let error of watchAllValues(errorLogs)) {
|
|
301
343
|
try {
|
|
302
|
-
|
|
344
|
+
// First pass: a long-lived cached list is enough to confirm an already-known suppression (the common case). Only if it doesn't already suppress this error do we re-read from a fresher cache, before treating it as unmatched and potentially spending an AI pattern generation on it.
|
|
345
|
+
let suppressionEntries = await getSuppressionEntriesCached();
|
|
346
|
+
if (!isSuppressedByAny(error, suppressionEntries)) {
|
|
347
|
+
suppressionEntries = await getSuppressionEntriesFresh();
|
|
348
|
+
}
|
|
303
349
|
let suppressed = false;
|
|
304
350
|
let matchedSuppressionId: string | undefined = undefined;
|
|
305
351
|
|
|
@@ -336,6 +382,7 @@ export async function exposeErrorWatchService() {
|
|
|
336
382
|
};
|
|
337
383
|
|
|
338
384
|
await suppression.set(newEntry.id, newEntry);
|
|
385
|
+
invalidateSuppressionEntriesCaches();
|
|
339
386
|
applySuppression(error, newEntry);
|
|
340
387
|
queueDiscordNotification(newEntry.id, 1, true);
|
|
341
388
|
|
|
@@ -413,11 +460,13 @@ class ErrorNotificationService {
|
|
|
413
460
|
|
|
414
461
|
public async setSuppressionEntry(entry: SuppressionEntry) {
|
|
415
462
|
await suppression.set(entry.id, entry);
|
|
463
|
+
invalidateSuppressionEntriesCaches();
|
|
416
464
|
await reapplySuppressions();
|
|
417
465
|
}
|
|
418
466
|
|
|
419
467
|
public async deleteSuppressionEntry(id: string) {
|
|
420
468
|
await suppression.delete(id);
|
|
469
|
+
invalidateSuppressionEntriesCaches();
|
|
421
470
|
await reapplySuppressions();
|
|
422
471
|
}
|
|
423
472
|
|
|
@@ -428,6 +477,7 @@ class ErrorNotificationService {
|
|
|
428
477
|
throw new Error(`Suppression entry ${id} not found`);
|
|
429
478
|
}
|
|
430
479
|
await suppression.set(id, { ...entry, notes, lastUpdatedTime: Date.now() });
|
|
480
|
+
invalidateSuppressionEntriesCaches();
|
|
431
481
|
}
|
|
432
482
|
}
|
|
433
483
|
|