querysub 0.349.0 → 0.350.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.349.0",
3
+ "version": "0.350.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",
@@ -35,6 +35,7 @@ import { assertIsNetworkTrusted } from "../../-d-trust/NetworkTrust2";
35
35
  import { blue, magenta } from "socket-function/src/formatting/logColors";
36
36
  import { FileMetadata, FastArchiveAppendableControllerBase, FastArchiveAppendableController, getFileMetadataHash } from "./FastArchiveController";
37
37
  import { fsExistsAsync } from "../../fs";
38
+ import { ScanFnc } from "./FastArchiveViewer";
38
39
 
39
40
  // NOTE: In a single command line micro-test it looks like we can write about 40K writes of 500 per once, when using 10X parallel, on a fairly potato server. We should probably batch though, and only do 1X parallel.
40
41
  /*
@@ -360,6 +361,7 @@ export class FastArchiveAppendable<Datum> {
360
361
  endTime: number;
361
362
  };
362
363
  cacheBust: number;
364
+ scanFnc?: ScanFnc;
363
365
  getWantData?: (file: FileMetadata) => Promise<((posStart: number, posEnd: number, data: Buffer) => boolean) | undefined>;
364
366
  onData: (datum: Datum[], file: FileMetadata) => void;
365
367
  // Called after onData
@@ -519,7 +521,7 @@ export class FastArchiveAppendable<Datum> {
519
521
  let processProgress = await createProgress("Processing (datums)", 0);
520
522
  let corruptDatumsProgress = await createProgress("Corrupt Datums (datums)", 0);
521
523
 
522
- const self = this;
524
+ let scanFnc = config.scanFnc;
523
525
 
524
526
 
525
527
  async function downloadAndParseFile(file: FileMetadata, runInner: (code: () => Promise<void>) => Promise<void>) {
@@ -546,6 +548,9 @@ export class FastArchiveAppendable<Datum> {
546
548
  scanProgressCount++;
547
549
  }
548
550
  if (buffer !== "done") {
551
+ if (scanFnc) {
552
+ scanFnc(posStart, posEnd, buffer);
553
+ }
549
554
  if (wantData && !wantData(posStart, posEnd, buffer)) {
550
555
  notMatchedSize += (posEnd - posStart);
551
556
  notMatchedCount++;
@@ -25,15 +25,22 @@ const RENDER_INTERVAL = 1000;
25
25
  const HISTOGRAM_RERENDER_INTERVAL = 10000;
26
26
 
27
27
  export const filterParam = new URLParam("filter", "");
28
+ // If filter2 is set, we also need it. Not great (why not support arbitrary counts of these), but for now... this should be fine (and support arbitrary counts might slow down our incredibly optimized scan function, where most of our time is spent).
29
+ // NOTE: This supports less than filterParam (no object parsing, it only does text contains)
30
+ export const filterParam2 = new URLParam("filter2", "");
31
+
28
32
  export const cacheBustParam = new URLParam("cacheBust", 0);
29
33
  const caseInsensitiveParam = new URLParam("caseInsensitive", false);
30
34
  const hideAllDataParam = new URLParam("hideAllData", false);
31
35
 
36
+ export type ScanFnc = (posStart: number, posEnd: number, data: Buffer) => boolean;
37
+
32
38
  export class FastArchiveViewer<T> extends qreact.Component<{
33
39
  fastArchives: FastArchiveAppendable<T>[];
34
40
  runOnLoad?: boolean;
35
41
  onStart: () => MaybePromise<void>;
36
- getWantData?: (file: FileMetadata) => Promise<((posStart: number, posEnd: number, data: Buffer) => boolean) | undefined>;
42
+ getScanFnc?: () => ScanFnc;
43
+ getWantData?: (file: FileMetadata) => Promise<ScanFnc | undefined>;
37
44
  onDatums: (source: FastArchiveAppendable<T>, datums: T[], metadata: FileMetadata) => void;
38
45
  // Called after onData
39
46
  onStats?: (source: FastArchiveAppendable<T>, stats: DatumStats, metadata: FileMetadata) => void;
@@ -108,6 +115,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
108
115
 
109
116
  @measureFnc
110
117
  private async synchronizeData() {
118
+ console.log("synchronizeData");
111
119
  let onStart = Querysub.fastRead(() => this.props.onStart);
112
120
  let onDatums = Querysub.fastRead(() => this.props.onDatums);
113
121
  let onStats = Querysub.fastRead(() => this.props.onStats);
@@ -144,7 +152,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
144
152
 
145
153
  const timeRange = getTimeRange();
146
154
  let filterString = filterParam.value;
147
-
155
+ let filterString2 = filterParam2.value;
148
156
  let scannedValueCount = 0;
149
157
 
150
158
  // Store current sync parameters BEFORE calling synchronizeData
@@ -310,6 +318,15 @@ export class FastArchiveViewer<T> extends qreact.Component<{
310
318
  });
311
319
  let { scanMatch, datumMatch } = joinMatches(andMatches, "||");
312
320
 
321
+ let andMatches2 = filterString2.split("|").map(orParts => {
322
+ let andParts = orParts.split("&");
323
+ let andMatches = andParts.map(part => parsePart(part.trim()));
324
+ return joinMatches(andMatches, "&&");
325
+ });
326
+ let scanObj2 = joinMatches(andMatches2, "||");
327
+ let scanMatch2 = scanObj2.scanMatch;
328
+ let datumMatch2 = scanObj2.datumMatch;
329
+ let useScan2 = !!filterString2.trim();
313
330
 
314
331
  const limitedBuffer = Buffer.from(LOG_LIMIT_FLAG);
315
332
 
@@ -318,6 +335,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
318
335
  const result = await fastArchive.synchronizeData({
319
336
  range: timeRange,
320
337
  cacheBust: Querysub.fastRead(() => cacheBustParam.value),
338
+ scanFnc: Querysub.fastRead(() => this.props.getScanFnc?.()),
321
339
  getWantData: async (file) => {
322
340
  let wantData = await getWantData?.(file);
323
341
  return (posStart: number, posEnd: number, data: Buffer) => {
@@ -341,6 +359,9 @@ export class FastArchiveViewer<T> extends qreact.Component<{
341
359
 
342
360
  // scanMatch is faster than wantData (generally), so run it first
343
361
  let matched = scanMatch(posStart, posEnd, data);
362
+ if (matched && useScan2) {
363
+ matched = scanMatch2(posStart, posEnd, data);
364
+ }
344
365
  if (matched && wantData) {
345
366
  matched = wantData(posStart, posEnd, data);
346
367
  }
@@ -635,8 +656,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
635
656
  return warnings;
636
657
  }
637
658
 
638
- public handleDownload = throttleFunction(500, () => {
639
- console.log("handleDownload");
659
+ public synchronizeDataThrottled = throttleFunction(500, () => {
640
660
  Querysub.onCommitFinished(() => {
641
661
  logErrors(this.synchronizeData());
642
662
  });
@@ -683,7 +703,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
683
703
  label={
684
704
  <div className={css.hbox(10)}>
685
705
  <Button onClick={() => {
686
- void this.handleDownload();
706
+ void this.synchronizeDataThrottled();
687
707
  }}>
688
708
  Run
689
709
  </Button>
@@ -694,10 +714,10 @@ export class FastArchiveViewer<T> extends qreact.Component<{
694
714
  hot
695
715
  flavor="large"
696
716
  fillWidth
697
- onKeyUp={this.handleDownload}
717
+ onKeyUp={this.synchronizeDataThrottled}
698
718
  ref2={() => {
699
719
  if (this.props.runOnLoad) {
700
- void this.handleDownload();
720
+ void this.synchronizeDataThrottled();
701
721
  }
702
722
  }}
703
723
  noEnterKeyBlur
@@ -708,7 +728,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
708
728
  checkbox
709
729
  url={caseInsensitiveParam}
710
730
  onChange={() => {
711
- void this.handleDownload();
731
+ void this.synchronizeDataThrottled();
712
732
  }}
713
733
  flavor="small"
714
734
  />
@@ -729,7 +749,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
729
749
  <div
730
750
  className={infoDisplay(30).button}
731
751
  onClick={() => {
732
- void this.handleDownload();
752
+ void this.synchronizeDataThrottled();
733
753
  }}
734
754
  title={outdatedWarnings.join(", ")}
735
755
  >
@@ -746,7 +766,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
746
766
  })()}
747
767
  {this.state.runCount === 0 && (
748
768
  <div className={infoDisplay(200).button} onClick={() => {
749
- void this.handleDownload();
769
+ void this.synchronizeDataThrottled();
750
770
  }}>
751
771
  No data downloaded yet. Click here to download data.
752
772
  </div>
@@ -766,7 +786,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
766
786
  Querysub.commit(() => {
767
787
  cacheBustParam.value = Date.now();
768
788
  });
769
- void this.handleDownload();
789
+ void this.synchronizeDataThrottled();
770
790
  }}
771
791
  >
772
792
  <div className={css.hbox(8).alignItems("center").wrap}>
@@ -783,7 +803,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
783
803
  <div className={infoDisplay(60).boldStyle.button}
784
804
  onClick={() => {
785
805
  filterParam.value = LOG_LIMIT_FLAG;
786
- void this.handleDownload();
806
+ void this.synchronizeDataThrottled();
787
807
  }}
788
808
  >
789
809
  Click here to see {formatNumber(this.limitedScanCount)} scanned logs were rate limited at a file level. This means lines might be missing. If this happens a lot, use pass {`{ [LOG_LINE_LIMIT_ID]: "INSERT RANDOM GUID HERE" }`} in the error/warn message to limit only the spamming line.
@@ -793,7 +813,7 @@ export class FastArchiveViewer<T> extends qreact.Component<{
793
813
  <div className={infoDisplay(0).boldStyle.button}
794
814
  onClick={() => {
795
815
  filterParam.value += " & " + LOG_LIMIT_FLAG;
796
- void this.handleDownload();
816
+ void this.synchronizeDataThrottled();
797
817
  }}
798
818
  >
799
819
  Click here to see {formatNumber(this.limitedMatchCount)} matched logs were rate limited at a file level. This means lines might be missing. If this happens a lot, use pass {`{ [LOG_LINE_LIMIT_ID]: "INSERT RANDOM GUID HERE" }`} in the error/warn message to limit only the spamming line.
@@ -13,7 +13,7 @@ import { logErrors } from "../../errors";
13
13
  import { batchFunction, runInSerial } from "socket-function/src/batching";
14
14
  import { Querysub } from "../../4-querysub/QuerysubController";
15
15
  import { sort, timeInDay, timeInHour } from "socket-function/src/misc";
16
- import { FastArchiveViewer, cacheBustParam, filterParam } from "./FastArchiveViewer";
16
+ import { FastArchiveViewer, cacheBustParam, filterParam, filterParam2 } from "./FastArchiveViewer";
17
17
  import { LogDatum, getLoggers, LOG_LIMIT_FLAG, LOG_LINE_LIMIT_FLAG } from "./diskLogger";
18
18
  import { ColumnType, Table, TableType } from "../../5-diagnostics/Table";
19
19
  import { formatDateJSX } from "../../misc/formatJSX";
@@ -28,6 +28,7 @@ import { RecentErrorsController, SuppressionListController, getSuppressEntryChec
28
28
  import { SocketFunction } from "socket-function/SocketFunction";
29
29
  import { throttleRender } from "../../functional/throttleRender";
30
30
  import { isNode } from "typesafecss";
31
+ import { createLogViewerExtractField } from "./logViewerExtractField";
31
32
 
32
33
  const RENDER_INTERVAL = 1000;
33
34
 
@@ -56,6 +57,7 @@ const defaultSelectedFields = {
56
57
  export class LogViewer2 extends qreact.Component {
57
58
  state = t.state({
58
59
  datumsSeqNum: t.atomic<number>(0),
60
+ showCountByFile: t.boolean,
59
61
  });
60
62
 
61
63
  private example: string | undefined = undefined;
@@ -66,6 +68,7 @@ export class LogViewer2 extends qreact.Component {
66
68
  private errors = 0;
67
69
  private notMatchedCount = 0;
68
70
  private datums: LogDatum[] = [];
71
+ private countsPerName = new Map<string, number>();
69
72
 
70
73
  private lastRenderTime = 0;
71
74
  private suppressionCounts = new Map<string, number>();
@@ -74,7 +77,7 @@ export class LogViewer2 extends qreact.Component {
74
77
 
75
78
  rerun() {
76
79
  cacheBustParam.value = Date.now();
77
- void this.fastArchiveViewer?.handleDownload();
80
+ void this.fastArchiveViewer?.synchronizeDataThrottled();
78
81
  }
79
82
 
80
83
  render() {
@@ -166,6 +169,7 @@ export class LogViewer2 extends qreact.Component {
166
169
  this.datums = [];
167
170
  this.suppressionCounts = new Map();
168
171
  this.expiredSuppressionCounts = new Map();
172
+ this.countsPerName = new Map();
169
173
  this.operationSequenceNum++;
170
174
  void (async () => {
171
175
  const currentSequenceNum = this.operationSequenceNum;
@@ -183,10 +187,21 @@ export class LogViewer2 extends qreact.Component {
183
187
  suppressionList = await suppressionController.getSuppressionList.promise();
184
188
 
185
189
  }}
190
+ getScanFnc={() => {
191
+ if (!this.state.showCountByFile) {
192
+ return () => false;
193
+ }
194
+ return createLogViewerExtractField("name", (value) => {
195
+ let prevCount = this.countsPerName.get(value) ?? 0;
196
+ prevCount++;
197
+ this.countsPerName.set(value, prevCount);
198
+ });
199
+ }}
186
200
  getWantData={async (file) => {
187
201
  if (!hasErrorNotifyToggle) return undefined;
188
202
  // By defaulting to the synchronous one, the list will be updated if there's any changes. However, we will also just get it asynchronously if the list hasn't been updated by the time we call get1data. And because we assign it back to the variable, it'll be cached.
189
203
  suppressionList = suppressionList || await suppressionController.getSuppressionList.promise();
204
+
190
205
  let suppressionFull = getSuppressionFull({
191
206
  entries: suppressionList,
192
207
  blockTimeRange: {
@@ -329,6 +344,7 @@ export class LogViewer2 extends qreact.Component {
329
344
  </div>;
330
345
  }
331
346
  };
347
+ let includedFiles = filterParam2.value.split("|").map(x => x.trim());
332
348
  return <>
333
349
  <div className={css.hbox(10)}>
334
350
  <InputPicker
@@ -373,6 +389,79 @@ export class LogViewer2 extends qreact.Component {
373
389
  {this.datums.length === 0 && <div className={css.hsl(40, 50, 50).colorhsl(60, 50, 100).boldStyle.pad2(10).ellipsis}>
374
390
  No logs matched, either increase the time range or decrease the filter specificity.
375
391
  </div>}
392
+ {includedFiles.length > 0 && includedFiles[0] && <div className={css.hbox(20)}>
393
+ <div className={css.hbox(10)}>
394
+ <div className={css.fontSize(18).hsl(0, 50, 50).colorhsl(0, 50, 95).pad2(8, 4)}>
395
+ Only showing {includedFiles.length} files:
396
+ </div>
397
+ <Button onClick={() => {
398
+ filterParam2.value = "";
399
+ this.rerun();
400
+ }}>
401
+ Clear All Filters
402
+ </Button>
403
+ </div>
404
+ <div className={css.hbox(10, 10).wrap}>
405
+ {includedFiles.filter(x => x).map((fileName) => (
406
+ <div
407
+ key={fileName}
408
+ className={css.pad2(8, 4).hsl(120, 40, 80).bord2(120, 40, 60, 1).button}
409
+ onClick={() => {
410
+ let newFiles = includedFiles.filter(x => x !== fileName);
411
+ filterParam2.value = newFiles.join("|");
412
+ }}
413
+ title={`Click to remove ${fileName} from filter`}
414
+ >
415
+ {fileName} ✕
416
+ </div>
417
+ ))}
418
+ </div>
419
+ </div>}
420
+
421
+ {this.state.showCountByFile && (() => {
422
+ let counts = Array.from(this.countsPerName.entries());
423
+ sort(counts, x => -x[1]);
424
+ let topCounts = counts.slice(0, 20);
425
+ let totalCount = counts.reduce((sum, [, count]) => sum + count, 0);
426
+
427
+ return <div className={css.vbox(10)}>
428
+ <div className={css.hbox(10, 10).wrap}>
429
+ {topCounts.map(([name, count]) => {
430
+ let percentage = totalCount > 0 ? (count / totalCount) * 100 : 0;
431
+ let isFiltered = includedFiles.includes(name);
432
+
433
+ return <div
434
+ key={name}
435
+ className={css.relative.pad2(8, 4).bord2(210, 30, 60, 1).ellipsis.button}
436
+ title={isFiltered ? `Click to remove ${name} from filter` : `Click to add ${name} to filter`}
437
+ onClick={() => {
438
+ if (isFiltered) {
439
+ // Remove from filter
440
+ let newFiles = includedFiles.filter(x => x !== name);
441
+ filterParam2.value = newFiles.join("|");
442
+ } else {
443
+ // Add to filter
444
+ let newFiles = [...includedFiles.filter(x => x), name];
445
+ filterParam2.value = newFiles.join("|");
446
+ }
447
+ }}
448
+ >
449
+ <div
450
+ className={css.absolute.pos(0, 0).height("100%").width(`${percentage}%`).hsl(isFiltered ? 120 : 210, 40, 80)}
451
+ />
452
+ <div className={css.relative.maxWidth(200).ellipsis}>
453
+ {formatNumber(count)} | {name || "(no name present)"} {isFiltered && "✓" || ""}
454
+ </div>
455
+ </div>;
456
+ })}
457
+ </div>
458
+ </div>;
459
+ })() || <Button onClick={() => {
460
+ this.state.showCountByFile = true;
461
+ this.rerun();
462
+ }}>
463
+ Show Count by File
464
+ </Button>}
376
465
  <Table
377
466
  rows={this.datums}
378
467
  columns={columns}
@@ -121,7 +121,12 @@ const sendIMs = batchFunction(({ delay: BATCH_TIME }), async (logsAll: LogDatum[
121
121
  return;
122
122
  }
123
123
  let url = createLink(getErrorLogsLink());
124
- let message = Object.values(info.perFile).flat().map(
124
+ let flat = Object.values(info.perFile).flat();
125
+ if (flat.length === 0) {
126
+ console.log(`All messages have hit their IM limit, so not sending any message at all.`);
127
+ return;
128
+ }
129
+ let message = flat.map(
125
130
  x => `[${formatDateTime(x.time)}](${url}) | ${ellipsisMiddle(String(x.param0), 100)} (${x.__NAME__})`
126
131
  ).join("\n");
127
132
  message = ellipsisMiddle(message, 900);
@@ -0,0 +1,37 @@
1
+ import { ScanFnc } from "./FastArchiveViewer";
2
+
3
+ export function createLogViewerExtractField(
4
+ fieldName: string,
5
+ onFieldValue: (value: string) => void,
6
+ ): ScanFnc {
7
+ let fieldNameBuffer = Buffer.from(`${JSON.stringify(fieldName)}:"`);
8
+ let quoteChar = Buffer.from(`"`)[0];
9
+ let escapeChar = Buffer.from(`\\`)[0];
10
+ return (posStart: number, posEnd: number, data: Buffer) => {
11
+ for (let i = posStart; i < posEnd; i++) {
12
+ if (data[i] === fieldNameBuffer[0]) {
13
+ for (let j = 1; j < fieldNameBuffer.length; j++) {
14
+ if (data[i + j] !== fieldNameBuffer[j]) {
15
+ break;
16
+ }
17
+ if (j === fieldNameBuffer.length - 1) {
18
+ // Parse until the string ends, ignoring escaped characters
19
+ let start = i + j + 1;
20
+ for (let k = start; k < posEnd; k++) {
21
+ if (data[k] === quoteChar) {
22
+ onFieldValue(data.slice(start, k).toString());
23
+ break;
24
+ }
25
+ if (data[k] === escapeChar) {
26
+ k++;
27
+ }
28
+ }
29
+ return true;
30
+ }
31
+ }
32
+ }
33
+ }
34
+ onFieldValue("");
35
+ return false;
36
+ };
37
+ }
@@ -35,7 +35,9 @@ export class InputPicker<T> extends qreact.Component<{
35
35
  state = {
36
36
  pendingText: "",
37
37
  focused: false,
38
+ showLimit: this.props.matchLimit ?? 10,
38
39
  };
40
+ inputElem: HTMLInputElement | null = null;
39
41
  render() {
40
42
  const { singleOption, fillWidth } = this.props;
41
43
  // Input, and beside it the picked values
@@ -63,7 +65,7 @@ export class InputPicker<T> extends qreact.Component<{
63
65
  pendingMatches = resolvedOptions;
64
66
  }
65
67
  let extra = pendingMatches.length;
66
- pendingMatches = pendingMatches.slice(0, this.props.matchLimit ?? 10);
68
+ pendingMatches = pendingMatches.slice(0, this.state.showLimit);
67
69
  extra -= pendingMatches.length;
68
70
  return (
69
71
  <div class={
@@ -78,6 +80,7 @@ export class InputPicker<T> extends qreact.Component<{
78
80
  {this.props.label}
79
81
  </div>
80
82
  <Input
83
+ inputRef={x => this.inputElem = x}
81
84
  value={this.state.pendingText}
82
85
  hot
83
86
  alwaysUseLatestValueWhenFocused
@@ -119,7 +122,17 @@ export class InputPicker<T> extends qreact.Component<{
119
122
  </Button>
120
123
  ))}
121
124
  {extra > 0 && (
122
- <Button class={css.hbox(5).button} disabled>
125
+ <Button class={css.hbox(5).button + " keepModalsOpen"}
126
+ onMouseDown={e => {
127
+ this.state.showLimit *= 2;
128
+ // HACK: Trying to prevent the event from causing a blur doesn't seem to work, so we'll just refocus it. And also waiting for 0 milliseconds doesn't work, so we'll wait for 100 milliseconds.
129
+ // - If the user clicks too fast this doesn't work, but the on-mouse out handler also won't work, Because we'll blur before on mouse up and then will be removed.
130
+ // TODO: Instead of keeping track of the focus state, we should make it a palette and when the palette goes away we should hide the open state.
131
+ setTimeout(() => {
132
+ this.inputElem?.focus();
133
+ }, 1000);
134
+ }}
135
+ >
123
136
  + {extra} more...
124
137
  </Button>
125
138
  )}