querysub 0.608.0 → 0.609.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.608.0",
3
+ "version": "0.609.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.83",
78
+ "sliftutils": "^1.7.84",
79
79
  "socket-function": "^1.2.30",
80
80
  "terser": "^5.31.0",
81
81
  "typenode": "^6.6.1",
@@ -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
- let suppressionEntries = await getSuppressionEntries();
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