querysub 0.366.0 → 0.367.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.366.0",
3
+ "version": "0.367.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",
@@ -1,4 +1,4 @@
1
- import "querysub/inject";
1
+ import "../inject";
2
2
 
3
3
  import { Querysub } from "../4-querysub/QuerysubController";
4
4
  import { logErrors } from "../errors";
@@ -1,4 +1,4 @@
1
- import "querysub/inject";
1
+ import "../inject";
2
2
 
3
3
  import { Querysub } from "../4-querysub/QuerysubController";
4
4
  import { logErrors } from "../errors";
@@ -9,7 +9,8 @@ import { cacheArgsEqual, cacheLimited, cacheWeak, lazy } from "socket-function/s
9
9
  import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
10
10
  import { formatNumber, formatTime } from "socket-function/src/formatting/format";
11
11
  import { magenta, yellow } from "socket-function/src/formatting/logColors";
12
- import { Unit, getAllUnits, Reader, createMatchesPattern, createOffsetReader, splitOnWildcard, SearchParams, IndexedLogResults } from "./BufferIndexHelpers";
12
+ import { Unit, getAllUnits, Reader, createOffsetReader, SearchParams, IndexedLogResults } from "./BufferIndexHelpers";
13
+ import { createMatchesPattern, getSearchUnits } from "./bufferMatcher";
13
14
  import { UnitSet } from "./BufferUnitSet";
14
15
  import { BufferUnitIndex } from "./BufferUnitIndex";
15
16
  import { BufferListStreamer } from "./BufferListStreamer";
@@ -301,7 +302,7 @@ export class BufferIndex {
301
302
  keepIterating: () => boolean;
302
303
  onResult: (match: Buffer) => void;
303
304
  results: IndexedLogResults;
304
- allSearchUnits: Set<Unit>;
305
+ allSearchUnits: Unit[][];
305
306
  matchesPattern: (buffer: Buffer) => boolean;
306
307
  }) {
307
308
  let { index, dataReader, params, keepIterating, onResult, results, allSearchUnits, matchesPattern } = config;
@@ -343,10 +344,17 @@ export class BufferIndex {
343
344
  let blockIndexData = indexEntries[i];
344
345
 
345
346
  // Check if this block contains all search units
346
- let hasAllUnits = true;
347
- for (let unit of allSearchUnits) {
348
- if (!UnitSet.has(blockIndexData, unit)) {
349
- hasAllUnits = false;
347
+ let hasAnyOr = false;
348
+ for (let or of allSearchUnits) {
349
+ let hasAllUnits = true;
350
+ for (let unit of or) {
351
+ if (!UnitSet.has(blockIndexData, unit)) {
352
+ hasAllUnits = false;
353
+ break;
354
+ }
355
+ }
356
+ if (hasAllUnits) {
357
+ hasAnyOr = true;
350
358
  break;
351
359
  }
352
360
  }
@@ -354,7 +362,7 @@ export class BufferIndex {
354
362
  results.localIndexesSearched += 1;
355
363
  results.localIndexSize += blockIndexData.length;
356
364
 
357
- if (!hasAllUnits) continue;
365
+ if (!hasAnyOr) continue;
358
366
 
359
367
  const dataBlocks = await getDataBlocks();
360
368
 
@@ -405,23 +413,11 @@ export class BufferIndex {
405
413
  }): Promise<void> {
406
414
  let { index, dataReader, params, results } = config;
407
415
 
408
- // Create the pattern matcher once with pre-calculated segments
409
416
  const matchesPattern = createMatchesPattern(params.findBuffer, !!params.disableWildCards);
410
417
 
411
- // Compute search units once — shared by both index types
412
- let allSearchUnits = new Set<Unit>();
413
- {
414
- let segments = params.disableWildCards && [params.findBuffer] || splitOnWildcard(params.findBuffer).filter(s => s.length > 0);
415
- for (let seg of segments) {
416
- if (seg.length < 4) continue;
417
- for (let ref of getAllUnits({ buffer: seg, bufferIndex: 0, block: 0 })) {
418
- allSearchUnits.add(ref.unit);
419
- }
420
- }
421
- if (allSearchUnits.size === 0) {
422
- // Search pattern too short to use index, return empty results
423
- return;
424
- }
418
+ let allSearchUnits = getSearchUnits(params.findBuffer, !!params.disableWildCards);
419
+ if (allSearchUnits.length === 0) {
420
+ return;
425
421
  }
426
422
 
427
423
  let type = index[0];
@@ -447,6 +443,7 @@ export class BufferIndex {
447
443
  } else if (type === BULK_TYPE) {
448
444
  await BufferUnitIndex.find({
449
445
  params,
446
+ allSearchUnits,
450
447
  index,
451
448
  reader: dataReader,
452
449
  keepIterating: config.keepIterating,
@@ -16,6 +16,7 @@ export type SearchParams = {
16
16
  findBuffer: Buffer;
17
17
  pathOverrides?: TimeFilePathWithSize[];
18
18
  only?: "local" | "public";
19
+ forceReadPublic?: boolean;
19
20
  };
20
21
 
21
22
  export type Unit = number;
@@ -134,10 +135,72 @@ export type IndexedLogResults = {
134
135
  };
135
136
  export function createEmptyIndexedLogResults(): IndexedLogResults {
136
137
  return {
137
- matchCount: 0, reads: [], totalLocalFiles: 0, totalBackblazeFiles: 0, localFilesSearched: 0, backblazeFilesSearched: 0, totalBlockCount: 0, blockCheckedCount: 0, remoteBlockCount: 0, localBlockCount: 0, remoteBlockCheckedCount: 0, localBlockCheckedCount: 0, blocksCheckedCompressedSize: 0, blocksCheckedDecompressedSize: 0, blockErrors: [], fileErrors: [], remoteIndexesSearched: 0, remoteIndexSize: 0, localIndexesSearched: 0, localIndexSize: 0, timeToFirstMatch: 0, fileFindTime: 0, indexSearchTime: 0, blockSearchTime: 0, totalSearchTime: 0, cancel: undefined, limitGroup: undefined,
138
+ matchCount: 0, reads: [], totalLocalFiles: 0, totalBackblazeFiles: 0, localFilesSearched: 0, backblazeFilesSearched: 0, totalBlockCount: 0, blockCheckedCount: 0, remoteBlockCount: 0, localBlockCount: 0, remoteBlockCheckedCount: 0, localBlockCheckedCount: 0, blocksCheckedCompressedSize: 0, blocksCheckedDecompressedSize: 0, blockErrors: [], fileErrors: [], remoteIndexesSearched: 0, remoteIndexSize: 0, localIndexesSearched: 0, localIndexSize: 0, timeToFirstMatch: -1, fileFindTime: 0, indexSearchTime: 0, blockSearchTime: 0, totalSearchTime: 0, cancel: undefined, limitGroup: undefined,
138
139
  };
139
140
  }
140
141
 
142
+ export function mergeIndexedLogResults(existing: IndexedLogResults, incoming: IndexedLogResults): IndexedLogResults {
143
+ let readsByKey = new Map<string, typeof existing.reads[0]>();
144
+
145
+ for (let read of existing.reads) {
146
+ let key = `${read.cached}-${read.remote}`;
147
+ let existingRead = readsByKey.get(key);
148
+ if (existingRead) {
149
+ existingRead.count += read.count;
150
+ existingRead.size += read.size;
151
+ existingRead.totalSize = Math.max(existingRead.totalSize, read.totalSize);
152
+ existingRead.totalCount = Math.max(existingRead.totalCount, read.totalCount);
153
+ } else {
154
+ readsByKey.set(key, { ...read });
155
+ }
156
+ }
157
+
158
+ for (let read of incoming.reads) {
159
+ let key = `${read.cached}-${read.remote}`;
160
+ let existingRead = readsByKey.get(key);
161
+ if (existingRead) {
162
+ existingRead.count += read.count;
163
+ existingRead.size += read.size;
164
+ existingRead.totalSize = Math.max(existingRead.totalSize, read.totalSize);
165
+ existingRead.totalCount = Math.max(existingRead.totalCount, read.totalCount);
166
+ } else {
167
+ readsByKey.set(key, { ...read });
168
+ }
169
+ }
170
+
171
+ return {
172
+ matchCount: existing.matchCount + incoming.matchCount,
173
+ totalLocalFiles: existing.totalLocalFiles + incoming.totalLocalFiles,
174
+ totalBackblazeFiles: existing.totalBackblazeFiles + incoming.totalBackblazeFiles,
175
+ reads: Array.from(readsByKey.values()),
176
+ localFilesSearched: existing.localFilesSearched + incoming.localFilesSearched,
177
+ backblazeFilesSearched: existing.backblazeFilesSearched + incoming.backblazeFilesSearched,
178
+ totalBlockCount: existing.totalBlockCount + incoming.totalBlockCount,
179
+ blockCheckedCount: existing.blockCheckedCount + incoming.blockCheckedCount,
180
+ blocksCheckedCompressedSize: existing.blocksCheckedCompressedSize + incoming.blocksCheckedCompressedSize,
181
+ blocksCheckedDecompressedSize: existing.blocksCheckedDecompressedSize + incoming.blocksCheckedDecompressedSize,
182
+ blockErrors: [...existing.blockErrors, ...incoming.blockErrors],
183
+ fileErrors: [...existing.fileErrors, ...incoming.fileErrors],
184
+ remoteIndexesSearched: existing.remoteIndexesSearched + incoming.remoteIndexesSearched,
185
+ remoteIndexSize: existing.remoteIndexSize + incoming.remoteIndexSize,
186
+ localIndexesSearched: existing.localIndexesSearched + incoming.localIndexesSearched,
187
+ localIndexSize: existing.localIndexSize + incoming.localIndexSize,
188
+ timeToFirstMatch: Math.min(
189
+ existing.timeToFirstMatch === -1 ? Infinity : existing.timeToFirstMatch,
190
+ incoming.timeToFirstMatch === -1 ? Infinity : incoming.timeToFirstMatch
191
+ ),
192
+ fileFindTime: existing.fileFindTime + incoming.fileFindTime,
193
+ indexSearchTime: existing.indexSearchTime + incoming.indexSearchTime,
194
+ blockSearchTime: existing.blockSearchTime + incoming.blockSearchTime,
195
+ totalSearchTime: Math.max(existing.totalSearchTime, incoming.totalSearchTime),
196
+ remoteBlockCount: existing.remoteBlockCount + incoming.remoteBlockCount,
197
+ localBlockCount: existing.localBlockCount + incoming.localBlockCount,
198
+ remoteBlockCheckedCount: existing.remoteBlockCheckedCount + incoming.remoteBlockCheckedCount,
199
+ localBlockCheckedCount: existing.localBlockCheckedCount + incoming.localBlockCheckedCount,
200
+ };
201
+ }
202
+
203
+
141
204
  export function addReadToResults(results: IndexedLogResults, read: {
142
205
  cached: boolean;
143
206
  remote: boolean;
@@ -161,62 +224,3 @@ export function addReadToResults(results: IndexedLogResults, read: {
161
224
  return existingRead;
162
225
  }
163
226
 
164
-
165
- export const WILD_CARD_BYTE = 42;
166
- export function splitOnWildcard(buffer: Buffer): Buffer[] {
167
- let segments: Buffer[] = [];
168
- let start = 0;
169
- for (let i = 0; i <= buffer.length; i++) {
170
- if (i === buffer.length || buffer[i] === WILD_CARD_BYTE) {
171
- segments.push(buffer.slice(start, i));
172
- start = i + 1;
173
- }
174
- }
175
- return segments;
176
- }
177
-
178
- // Each WILD_CARD_BYTE in pattern acts as a multi-byte wildcard: the segments on either
179
- // side must appear in order somewhere within buffer.
180
- // Returns a function that matches buffers against the pre-processed pattern.
181
- export function createMatchesPattern(pattern: Buffer, disableWildCards: boolean): (buffer: Buffer) => boolean {
182
- let segments = disableWildCards && [pattern] || splitOnWildcard(pattern).filter(s => s.length > 0);
183
-
184
- return measureWrap(function matchesPattern(buffer: Buffer): boolean {
185
- // Fast path: check if all segments exist anywhere in the buffer using indexOf
186
- for (let seg of segments) {
187
- if (buffer.indexOf(seg) === -1) {
188
- return false;
189
- }
190
- }
191
-
192
- // Tries to match all segments in order starting from bufferPos, returning the end
193
- // position after the last match, or -1 if not all segments could be found.
194
- function matchSegmentsFrom(bufferPos: number): number {
195
- for (let seg of segments) {
196
- function segMatchesAt(pos: number): boolean {
197
- for (let i = 0; i < seg.length; i++) {
198
- if (buffer[pos + i] !== seg[i]) return false;
199
- }
200
- return true;
201
- }
202
- let found = false;
203
- for (let searchPos = bufferPos; searchPos <= buffer.length - seg.length; searchPos++) {
204
- if (segMatchesAt(searchPos)) {
205
- // NOTE: I think this is safe because every segment has a wildcard after it. So we can never have a case where we didn't skip far enough because the wild card will just skip farther. And we won't have a partial match as we're matching the whole chunk. So we won't match a prefix and then get stuck. I think... it does seem weird though...
206
- bufferPos = searchPos + seg.length;
207
- found = true;
208
- break;
209
- }
210
- }
211
- if (!found) return -1;
212
- }
213
- return bufferPos;
214
- }
215
-
216
- for (let startPos = 0; startPos <= buffer.length; startPos++) {
217
- if (matchSegmentsFrom(startPos) >= 0) return true;
218
- }
219
- return false;
220
- }, "BufferIndex|matchesPattern");
221
- }
222
-
@@ -3,7 +3,7 @@
3
3
  import { LZ4 } from "../../../storage/LZ4";
4
4
  import { measureBlock, measureFnc } from "socket-function/src/profiling/measure";
5
5
  import { Zip } from "../../../zip";
6
- import { BufferReader, Reader, WILD_CARD_BYTE, createMatchesPattern, SearchParams, IndexedLogResults } from "./BufferIndexHelpers";
6
+ import { BufferReader, Reader, SearchParams, IndexedLogResults, Unit } from "./BufferIndexHelpers";
7
7
  import { formatNumber, formatPercent } from "socket-function/src/formatting/format";
8
8
  import { lazy } from "socket-function/src/caching";
9
9
  import { list, sort } from "socket-function/src/misc";
@@ -11,6 +11,7 @@ import { testDisableCache } from "../../../-a-archives/archivesMemoryCache";
11
11
  import { devDebugbreak } from "../../../config";
12
12
  import { BufferUnitIndexParallelSearchCount, DEFAULT_BLOCK_SIZE, DEFAULT_TARGET_UNITS_PER_BUCKET } from "./BufferIndexLogsOptimizationConstants";
13
13
  import { runInParallel } from "socket-function/src/batching";
14
+ import { createMatchesPattern } from "./bufferMatcher";
14
15
 
15
16
  const USE_COMPRESSION = true;
16
17
 
@@ -454,51 +455,17 @@ export class BufferUnitIndex {
454
455
  @measureFnc
455
456
  public static async find(config: {
456
457
  params: SearchParams;
458
+ allSearchUnits: Unit[][];
457
459
  keepIterating: () => boolean;
458
460
  onResult: (match: Buffer) => void;
459
461
  index: Buffer;
460
462
  reader: Reader;
461
463
  results: IndexedLogResults;
462
464
  }): Promise<void> {
463
- const { params, index, reader, keepIterating, results } = config;
464
-
465
- // Split on wildcards if present
466
- function splitOnWildcard(buffer: Buffer): Buffer[] {
467
- const segments: Buffer[] = [];
468
- let start = 0;
469
- for (let i = 0; i <= buffer.length; i++) {
470
- if (i === buffer.length || buffer[i] === WILD_CARD_BYTE) {
471
- segments.push(buffer.slice(start, i));
472
- start = i + 1;
473
- }
474
- }
475
- return segments;
476
- }
477
-
478
- const segments = params.disableWildCards && [params.findBuffer] || splitOnWildcard(params.findBuffer).filter(s => s.length > 0);
479
-
480
- // Find blocks for each segment >= 4 bytes
481
- const candidateBlocks = measureBlock(() => {
482
- const candidateBlocksPerSegment: number[][] = [];
483
- for (const segment of segments) {
484
- if (segment.length < 4) continue;
485
- const blockIndices = this.findBlocks({ findBuffer: segment, index });
486
- candidateBlocksPerSegment.push(blockIndices);
487
- }
488
-
489
- if (candidateBlocksPerSegment.length === 0) {
490
- throw new Error("Search pattern too short: all segments are fewer than 4 bytes, cannot use index");
491
- }
492
-
493
- let intersectionSet = new Set<number>(candidateBlocksPerSegment[0]);
494
- for (let i = 1; i < candidateBlocksPerSegment.length; i++) {
495
- const currentSet = new Set(candidateBlocksPerSegment[i]);
496
- intersectionSet = new Set([...intersectionSet].filter(x => currentSet.has(x)));
497
- }
465
+ const { params, index, reader, keepIterating, results, allSearchUnits } = config;
498
466
 
499
-
500
- return intersectionSet;
501
- }, `findCandidateBlocks`);
467
+ let candidateBlocksList = allSearchUnits.map(units => this.findBlocks({ units, index })).flat();
468
+ let candidateBlocksSet = new Set(candidateBlocksList);
502
469
 
503
470
 
504
471
  const matchesPattern = createMatchesPattern(params.findBuffer, !!params.disableWildCards);
@@ -516,7 +483,7 @@ export class BufferUnitIndex {
516
483
  let matchCounts = list(blockCount).fill(0);
517
484
 
518
485
  const searchBlock = async (blockIndex: number) => {
519
- if (!candidateBlocks.has(blockIndex)) return;
486
+ if (!candidateBlocksSet.has(blockIndex)) return;
520
487
  // This is kind of a weird thing. Basically, because we search in parallel, we might search out of order. So we can only look at the counts before or at us, as if we match a whole bunch after us, but we should still keep going as our matches are going to take precedence.
521
488
  let stopIterating = () => {
522
489
  let countBefore = 0;
@@ -568,7 +535,7 @@ export class BufferUnitIndex {
568
535
  searchBlock
569
536
  );
570
537
  // Search first first, as moveLogsToPublic should have made it so this is the newest.
571
- let searchOrder = Array.from(candidateBlocks);
538
+ let searchOrder = Array.from(candidateBlocksSet);
572
539
  sort(searchOrder, x => x);
573
540
  await Promise.all(searchOrder.map(runSearchBlock));
574
541
 
@@ -576,23 +543,12 @@ export class BufferUnitIndex {
576
543
  results.blockSearchTime += Date.now() - blockSearchTimeStart;
577
544
  }
578
545
 
546
+ @measureFnc
579
547
  private static findBlocks(config: {
580
- findBuffer: Buffer;
548
+ units: number[];
581
549
  index: Buffer;
582
550
  }): number[] {
583
- const { findBuffer, index } = config;
584
-
585
- // Extract all unique units from findBuffer
586
- const units = measureBlock(() => {
587
- const units: number[] = [];
588
- for (let i = 0; i <= findBuffer.length - 4; i++) {
589
- const unit = findBuffer.readUint32LE(i);
590
- if (unit !== 0 && !units.includes(unit)) {
591
- units.push(unit);
592
- }
593
- }
594
- return units;
595
- }, `extractUnits`);
551
+ const { units, index } = config;
596
552
 
597
553
  if (units.length === 0) {
598
554
  return [];
@@ -600,25 +556,12 @@ export class BufferUnitIndex {
600
556
 
601
557
  // Get blocks for each unit and intersect
602
558
  return measureBlock(() => {
603
- const candidateBlocksPerUnit: number[][] = [];
604
- for (const unit of units) {
605
- const blockIndices = this.getBlocksForUnit(index, unit);
606
- candidateBlocksPerUnit.push(blockIndices);
559
+ let candidateBlocks = this.getBlocksForUnit(index, units[0]);
560
+ for (let i = 1; i < units.length; i++) {
561
+ let nextBlocks = new Set(this.getBlocksForUnit(index, units[i]));
562
+ candidateBlocks = candidateBlocks.filter(b => nextBlocks.has(b));
607
563
  }
608
-
609
- // Intersect all block sets
610
- let intersectionSet = new Set<number>(candidateBlocksPerUnit[0]);
611
- for (let i = 1; i < candidateBlocksPerUnit.length; i++) {
612
- const currentSet = new Set(candidateBlocksPerUnit[i]);
613
- intersectionSet = new Set([...intersectionSet].filter(x => currentSet.has(x)));
614
- }
615
-
616
- let allCounts = candidateBlocksPerUnit.map(b => b.length);
617
- sort(allCounts, x => x);
618
-
619
- //console.log(`Candidate blocks ${intersectionSet.size}, minimum: ${Math.min(...candidateBlocksPerUnit.map(b => b.length))}, best 4 counts: ${allCounts.slice(0, 4).join(", ")}`);
620
-
621
- return Array.from(intersectionSet);
564
+ return candidateBlocks;
622
565
  }, `intersectBlocks`);
623
566
  }
624
567
 
@@ -1,13 +1,13 @@
1
1
  import { lazy } from "socket-function/src/caching";
2
2
  import { Archives, nestArchives } from "../../../-a-archives/archives";
3
- import { deepCloneJSON, keyByArray, nextId, sort, timeInHour, timeInMinute, timeInSecond } from "socket-function/src/misc";
3
+ import { deepCloneJSON, keyByArray, nextId, sort, timeInHour, timeInMinute, timeInSecond, timeoutToUndefinedSilent } from "socket-function/src/misc";
4
4
  import { BufferIndex } from "./BufferIndex";
5
5
  import { delay, runInParallel, runInSerial, runInfinitePoll } from "socket-function/src/batching";
6
- import { IndexedLogResults, Reader, SearchParams, addReadToResults, createEmptyIndexedLogResults, INDEX_EXTENSION } from "./BufferIndexHelpers";
6
+ import { IndexedLogResults, Reader, SearchParams, addReadToResults, createEmptyIndexedLogResults, INDEX_EXTENSION, mergeIndexedLogResults } from "./BufferIndexHelpers";
7
7
  import { getDomain, isPublic } from "../../../config";
8
8
  import { getArchivesHome, getArchivesLocal } from "../../../-a-archives/archivesDisk";
9
9
  import { getArchivesBackblaze, getArchivesBackblazePrivateImmutable } from "../../../-a-archives/archivesBackBlaze";
10
- import { getOwnThreadId } from "../../../-a-auth/certs";
10
+ import { getMachineId, getOwnThreadId } from "../../../-a-auth/certs";
11
11
  import { ArchivesMemoryCacheStats, createArchivesMemoryCache } from "../../../-a-archives/archivesMemoryCache";
12
12
  import { registerShutdownHandler } from "../../periodic";
13
13
  import { measureBlock, measureFnc } from "socket-function/src/profiling/measure";
@@ -23,6 +23,8 @@ import { assertIsManagementUser } from "../../managementPages";
23
23
  import { ignoreErrors } from "../../../errors";
24
24
  import { blue } from "socket-function/src/formatting/logColors";
25
25
  import { LimitGroup } from "../../../functional/limitProcessing";
26
+ import { getAllNodeIds } from "../../../-f-node-discovery/NodeDiscovery";
27
+ import { NodeCapabilitiesController } from "../../../-g-core-values/NodeCapabilities";
26
28
 
27
29
  export type TimeFilePathWithSize = TimeFilePath & {
28
30
  size: number;
@@ -222,15 +224,78 @@ export class IndexedLogs<T> {
222
224
  }
223
225
  }
224
226
 
227
+ private machineNodeCache = new Map<string, string>();
228
+ private async getMachineNodes(): Promise<string[]> {
229
+ let ownMachineId = getOwnMachineId();
230
+ let nodeIds = await getAllNodeIds();
231
+ let byMachineId = keyByArray(nodeIds, x => getMachineId(x));
232
+ byMachineId.delete(ownMachineId);
233
+
234
+ await Promise.all(Array.from(byMachineId.entries()).map(async ([machineId, nodeIds]) => {
235
+ let added = false;
236
+ const tryAdd = async (nodeId: string, preferredOnly = false) => {
237
+ if (added) return;
238
+ let hasLogger = await timeoutToUndefinedSilent(2500, IndexedLogShimController.nodes[nodeId].hasLogger(this.config.name));
239
+ if (!hasLogger) return false;
240
+ // NOTE: Prefer to do the searching on the move logs service. However, if it's not available, any service can do searching. It just might lag that server...
241
+ if (preferredOnly) {
242
+ let entryPoint = await NodeCapabilitiesController.nodes[nodeId].getEntryPoint();
243
+ if (!entryPoint.includes("movelogs")) return false;
244
+ }
245
+ added = true;
246
+
247
+ this.machineNodeCache.set(machineId, nodeId);
248
+ return true;
249
+ };
250
+ let cached = this.machineNodeCache.get(machineId);
251
+ if (cached) {
252
+ if (await tryAdd(cached)) return;
253
+ }
254
+ // NOTE: I don't think we need to try a few nodes at a time. How many nodes are going to exist for one machine id anyways? The more machines we add, it won't add more nodes per machine id.
255
+ await Promise.all(nodeIds.map(nodeId => tryAdd(nodeId, true)));
256
+ if (!added) {
257
+ await Promise.all(nodeIds.map(nodeId => tryAdd(nodeId, false)));
258
+ }
259
+ if (!added) {
260
+ console.warn(`No machine node found for machine ${machineId}`, { machineId, nodeIds });
261
+ }
262
+ }));
263
+
264
+ return Array.from(this.machineNodeCache.values());
265
+ }
266
+
225
267
  @measureFnc
226
268
  public async getPaths(config: {
227
269
  startTime: number;
228
270
  endTime: number;
229
271
  only?: "local" | "public";
272
+ forceReadPublic?: boolean;
230
273
  }): Promise<TimeFilePathWithSize[]> {
274
+ let finalPaths: TimeFilePathWithSize[] = [];
275
+
276
+ if (!config.only && (config.forceReadPublic || isPublic())) {
277
+ let machineNodes = await this.getMachineNodes();
278
+ await Promise.all(machineNodes.map(async (machineNode) => {
279
+ try {
280
+ let paths = await IndexedLogShimController.nodes[machineNode].getPaths({
281
+ ...config,
282
+ only: "local",
283
+ indexedLogsName: this.config.name,
284
+ });
285
+ finalPaths.push(...paths);
286
+ } catch (e) {
287
+ console.error(`Error in getting paths on machine node ${machineNode}: ${e}`);
288
+ }
289
+ }));
290
+ }
291
+
292
+ // If we're forcefully reading from the public server, but we're not public, the code above will be the only code which adds to results
293
+ if (!isPublic() && config.forceReadPublic) {
294
+ return finalPaths;
295
+ }
296
+
231
297
  let localLogs = this.getLocalLogs();
232
298
  let backblazeLogs = this.getPublicLogs();
233
-
234
299
  let paths: TimeFilePath[] = [];
235
300
 
236
301
  if (config.only !== "public") {
@@ -262,7 +327,9 @@ export class IndexedLogs<T> {
262
327
  return { ...path, size, indexSize, sourceName: this.config.name };
263
328
  }));
264
329
 
265
- return pathsWithSize;
330
+ finalPaths.push(...pathsWithSize);
331
+
332
+ return finalPaths;
266
333
  }
267
334
 
268
335
  @measureFnc
@@ -274,7 +341,47 @@ export class IndexedLogs<T> {
274
341
  let startTime = Date.now();
275
342
  let interval: NodeJS.Timeout | undefined;
276
343
 
344
+ let getFinalResults = (): IndexedLogResults => {
345
+ let results = createEmptyIndexedLogResults();
346
+ for (let result of allResults.values()) {
347
+ results = mergeIndexedLogResults(results, result);
348
+ }
349
+ return results;
350
+ };
351
+ let allResults = new Map<string, IndexedLogResults>();
352
+ let allDone: Promise<void>[] = [];
353
+
354
+ if (!config.params.only && (config.params.forceReadPublic || isPublic())) {
355
+ let machineNodes = await this.getMachineNodes();
356
+ allDone.push(...machineNodes.map(async (machineNode) => {
357
+ try {
358
+ let resultObj = await this.clientFind({
359
+ ...config,
360
+ nodeId: machineNode,
361
+ onResult(match) {
362
+ config.onResult(match);
363
+ },
364
+ onResults(results) {
365
+ allResults.set(machineNode, results);
366
+ },
367
+ });
368
+ allResults.set(machineNode, resultObj);
369
+ } catch (e) {
370
+ console.error(`Error in finding logs on machine node ${machineNode}: ${e}`);
371
+ }
372
+ }));
373
+ }
374
+
375
+ // If we're forcefully reading from the public server, but we're not public, the code above will be the only code which adds to results
376
+ if (!isPublic() && config.params.forceReadPublic) {
377
+ return getFinalResults();
378
+ }
379
+
380
+
277
381
  let results: IndexedLogResults = createEmptyIndexedLogResults();
382
+ allResults.set("", results);
383
+
384
+
278
385
  results.limitGroup = new LimitGroup({
279
386
  maxTimePerBeforeWait: 500,
280
387
  waitTime: 250,
@@ -316,7 +423,8 @@ export class IndexedLogs<T> {
316
423
  if (onResultsCallback) {
317
424
  interval = setInterval(async () => {
318
425
  updateResultsStats();
319
- let shouldContinue = await onResultsCallback(deepCloneJSON(results));
426
+ let results = getFinalResults();
427
+ let shouldContinue = await onResultsCallback(results);
320
428
  if (!shouldContinue) {
321
429
  if (!results.cancel) {
322
430
  console.log(blue(`Cancelled search on ${this.config.name}`));
@@ -327,13 +435,15 @@ export class IndexedLogs<T> {
327
435
  }
328
436
 
329
437
  try {
330
- await this.findBase({
438
+ let promise = this.findBase({
331
439
  params: config.params,
332
440
  onResult: config.onResult,
333
441
  results,
334
442
  });
443
+ allDone.push(promise);
444
+ await Promise.allSettled(allDone);
335
445
  updateResultsStats();
336
- return results;
446
+ return getFinalResults();
337
447
  } finally {
338
448
  if (interval) {
339
449
  clearInterval(interval);
@@ -482,8 +592,9 @@ export class IndexedLogs<T> {
482
592
  params: SearchParams;
483
593
  onResult: (match: T) => void;
484
594
  onResults?: (results: IndexedLogResults) => void;
595
+ nodeId?: string;
485
596
  }): Promise<IndexedLogResults> {
486
- let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
597
+ let controller = IndexedLogShimController.nodes[config.nodeId || SocketFunction.getBrowserNodeId()];
487
598
  let findId = nextId();
488
599
  let callback = (match: T) => {
489
600
  config.onResult(match);
@@ -514,6 +625,8 @@ export class IndexedLogs<T> {
514
625
  startTime: number;
515
626
  endTime: number;
516
627
  only?: "local" | "public";
628
+ forceReadPublic?: boolean;
629
+
517
630
  }): Promise<TimeFilePathWithSize[]> {
518
631
  let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
519
632
  return await controller.getPaths({
@@ -521,6 +634,7 @@ export class IndexedLogs<T> {
521
634
  startTime: config.startTime,
522
635
  endTime: config.endTime,
523
636
  only: config.only,
637
+ forceReadPublic: config.forceReadPublic,
524
638
  });
525
639
  }
526
640
  public onFindResult(config: {
@@ -642,6 +756,7 @@ class IndexedLogShim {
642
756
  startTime: number;
643
757
  endTime: number;
644
758
  only?: "local" | "public";
759
+ forceReadPublic?: boolean;
645
760
  }): Promise<TimeFilePathWithSize[]> {
646
761
  let indexedLogs = loggerByName.get(config.indexedLogsName);
647
762
  if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
@@ -649,6 +764,7 @@ class IndexedLogShim {
649
764
  startTime: config.startTime,
650
765
  endTime: config.endTime,
651
766
  only: config.only,
767
+ forceReadPublic: config.forceReadPublic,
652
768
  });
653
769
  }
654
770
 
@@ -659,6 +775,10 @@ class IndexedLogShim {
659
775
  if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
660
776
  await indexedLogs.moveLogsToPublic(true);
661
777
  }
778
+
779
+ public async hasLogger(name: string) {
780
+ return loggerByName.has(name);
781
+ }
662
782
  }
663
783
 
664
784
  const IndexedLogShimController = SocketFunction.register(
@@ -673,6 +793,9 @@ const IndexedLogShimController = SocketFunction.register(
673
793
  },
674
794
  forceMoveLogsToPublic: {
675
795
  hooks: [assertIsManagementUser]
796
+ },
797
+ hasLogger: {
798
+ hooks: [assertIsManagementUser]
676
799
  }
677
800
  })
678
801
  );
@@ -21,14 +21,15 @@ import { ObjectDisplay } from "../ObjectDisplay";
21
21
  import { formatDateJSX } from "../../../misc/formatJSX";
22
22
  import { PUBLIC_MOVE_THRESHOLD } from "./BufferIndexLogsOptimizationConstants";
23
23
  import { atomic } from "../../../2-proxy/PathValueProxyWatcher";
24
- import { errorToUndefined } from "querysub/src/errors";
25
- import { IndexedLogResults, createEmptyIndexedLogResults } from "./BufferIndexHelpers";
24
+ import { errorToUndefined } from "../../../errors";
25
+ import { IndexedLogResults, createEmptyIndexedLogResults, mergeIndexedLogResults } from "./BufferIndexHelpers";
26
26
  import { TimeFilePath } from "./TimeFileTree";
27
+ import { isPublic } from "../../../config";
27
28
 
28
29
  let searchText = new URLParam("searchText", "");
29
- let readLiveData = new URLParam("readLiveData", false);
30
30
  let excludePendingResults = new URLParam("excludePendingResults", false);
31
31
  let limitURL = new URLParam("limit", 100);
32
+ let readPublicLogs = new URLParam("readPublicLogs", false);
32
33
 
33
34
  let savedPathsURL = new URLParam("savedPaths", "");
34
35
  let selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, boolean>);
@@ -43,63 +44,6 @@ const defaultSelectedFields = {
43
44
  __entry: true,
44
45
  };
45
46
 
46
- function mergeIndexedLogResults(existing: IndexedLogResults, incoming: IndexedLogResults): IndexedLogResults {
47
- let readsByKey = new Map<string, typeof existing.reads[0]>();
48
-
49
- for (let read of existing.reads) {
50
- let key = `${read.cached}-${read.remote}`;
51
- let existingRead = readsByKey.get(key);
52
- if (existingRead) {
53
- existingRead.count += read.count;
54
- existingRead.size += read.size;
55
- existingRead.totalSize = Math.max(existingRead.totalSize, read.totalSize);
56
- existingRead.totalCount = Math.max(existingRead.totalCount, read.totalCount);
57
- } else {
58
- readsByKey.set(key, { ...read });
59
- }
60
- }
61
-
62
- for (let read of incoming.reads) {
63
- let key = `${read.cached}-${read.remote}`;
64
- let existingRead = readsByKey.get(key);
65
- if (existingRead) {
66
- existingRead.count += read.count;
67
- existingRead.size += read.size;
68
- existingRead.totalSize = Math.max(existingRead.totalSize, read.totalSize);
69
- existingRead.totalCount = Math.max(existingRead.totalCount, read.totalCount);
70
- } else {
71
- readsByKey.set(key, { ...read });
72
- }
73
- }
74
-
75
- return {
76
- matchCount: existing.matchCount + incoming.matchCount,
77
- totalLocalFiles: existing.totalLocalFiles + incoming.totalLocalFiles,
78
- totalBackblazeFiles: existing.totalBackblazeFiles + incoming.totalBackblazeFiles,
79
- reads: Array.from(readsByKey.values()),
80
- localFilesSearched: existing.localFilesSearched + incoming.localFilesSearched,
81
- backblazeFilesSearched: existing.backblazeFilesSearched + incoming.backblazeFilesSearched,
82
- totalBlockCount: existing.totalBlockCount + incoming.totalBlockCount,
83
- blockCheckedCount: existing.blockCheckedCount + incoming.blockCheckedCount,
84
- blocksCheckedCompressedSize: existing.blocksCheckedCompressedSize + incoming.blocksCheckedCompressedSize,
85
- blocksCheckedDecompressedSize: existing.blocksCheckedDecompressedSize + incoming.blocksCheckedDecompressedSize,
86
- blockErrors: [...existing.blockErrors, ...incoming.blockErrors],
87
- fileErrors: [...existing.fileErrors, ...incoming.fileErrors],
88
- remoteIndexesSearched: existing.remoteIndexesSearched + incoming.remoteIndexesSearched,
89
- remoteIndexSize: existing.remoteIndexSize + incoming.remoteIndexSize,
90
- localIndexesSearched: existing.localIndexesSearched + incoming.localIndexesSearched,
91
- localIndexSize: existing.localIndexSize + incoming.localIndexSize,
92
- timeToFirstMatch: Math.min(existing.timeToFirstMatch === 0 ? Infinity : existing.timeToFirstMatch, incoming.timeToFirstMatch === 0 ? Infinity : incoming.timeToFirstMatch),
93
- fileFindTime: existing.fileFindTime + incoming.fileFindTime,
94
- indexSearchTime: existing.indexSearchTime + incoming.indexSearchTime,
95
- blockSearchTime: existing.blockSearchTime + incoming.blockSearchTime,
96
- totalSearchTime: Math.max(existing.totalSearchTime, incoming.totalSearchTime),
97
- remoteBlockCount: existing.remoteBlockCount + incoming.remoteBlockCount,
98
- localBlockCount: existing.localBlockCount + incoming.localBlockCount,
99
- remoteBlockCheckedCount: existing.remoteBlockCheckedCount + incoming.remoteBlockCheckedCount,
100
- localBlockCheckedCount: existing.localBlockCheckedCount + incoming.localBlockCheckedCount,
101
- };
102
- }
103
47
 
104
48
  export class LogViewer3 extends qreact.Component {
105
49
  static renderInProgress = true;
@@ -137,9 +81,6 @@ export class LogViewer3 extends qreact.Component {
137
81
  } else {
138
82
  paths = this.state.paths;
139
83
  }
140
- if (excludePendingResults.value) {
141
- paths = paths.filter(x => x.logCount !== undefined);
142
- }
143
84
  return paths;
144
85
  }
145
86
 
@@ -202,8 +143,8 @@ export class LogViewer3 extends qreact.Component {
202
143
  const renderStage = (title: string, items: preact.ComponentChild[], hue: number | undefined, isFirst: boolean, isLast: boolean) => {
203
144
  const triangleWidth = 20;
204
145
 
205
- let color = hue ? css.hsl(hue, 70, 80) : css.hsl(0, 0, 100);
206
- let colorhsl = hue ? css.hslcolor(hue, 80, 30) : css;
146
+ let color = hue !== undefined ? css.hsl(hue, 70, 80) : css.hsl(0, 0, 100);
147
+ let colorhsl = hue !== undefined ? css.hslcolor(hue, 80, 30) : css;
207
148
 
208
149
  return (
209
150
  <div className={css.vbox(2).relative.minWidth(200).paddingTop(8).paddingBottom(8).paddingRight(10).paddingLeft(isFirst ? 10 : 10 + triangleWidth) + color}>
@@ -211,7 +152,7 @@ export class LogViewer3 extends qreact.Component {
211
152
  {items.map((item, i) => (
212
153
  <div key={i}>{item}</div>
213
154
  ))}
214
- {!isLast && (
155
+ {!isLast && hue !== undefined && (
215
156
  <svg width={triangleWidth} height="100" viewBox="0 0 20 100" preserveAspectRatio="none" className={css.absolute.right(-triangleWidth).top(0).fillHeight.zIndex(1)}>
216
157
  <polygon points="0,0 20,50 0,100" fill={`hsl(${hue}, 70%, 80%)`} />
217
158
  </svg>
@@ -262,8 +203,8 @@ export class LogViewer3 extends qreact.Component {
262
203
  {renderStage("Indexes", indexItems, 250, false, false)}
263
204
  {renderStage("Blocks", blockItems, 210, false, false)}
264
205
  {renderStage("Reads", cacheItems, 160, false, false)}
265
- {renderStage("Results", resultItems, 120, false, false)}
266
- {renderStage("Done", doneItems, undefined, false, !hasErrors)}
206
+ {renderStage("Results", resultItems, 120, false, this.state.searching && !hasErrors)}
207
+ {!this.state.searching && renderStage("Done", doneItems, undefined, false, !hasErrors)}
267
208
  {(() => {
268
209
  if (!hasErrors) return undefined;
269
210
  let errorItems: preact.ComponentChild[] = [];
@@ -339,7 +280,7 @@ export class LogViewer3 extends qreact.Component {
339
280
  )}
340
281
  {!isFrozen && (
341
282
  <>
342
- <div className={css.fontWeight(600)}>FIX pending logs not being merged!</div>
283
+ <div className={css.fontWeight(600)}>Pending logs not being merged!</div>
343
284
  {warnings.map((warning) => (
344
285
  <div key={warning.machineId} className={css.hbox(10)}>
345
286
  <MachineThreadInfo machineId={warning.machineId} />
@@ -427,6 +368,8 @@ export class LogViewer3 extends qreact.Component {
427
368
  let paths = await logger.clientGetPaths({
428
369
  startTime: range.startTime,
429
370
  endTime: range.endTime,
371
+ only: excludePendingResults.value ? "local" : undefined,
372
+ forceReadPublic: readPublicLogs.value,
430
373
  });
431
374
  allPaths.push(...paths);
432
375
  }
@@ -439,7 +382,7 @@ export class LogViewer3 extends qreact.Component {
439
382
  return allPaths;
440
383
  }
441
384
 
442
- async updatePaths() {
385
+ async updateToLatestResults() {
443
386
  let prevPaths = this.getPaths();
444
387
  let newPaths = await this.loadPaths();
445
388
  Querysub.commitLocal(() => {
@@ -449,6 +392,7 @@ export class LogViewer3 extends qreact.Component {
449
392
  let keep = new Set(prevPaths.map(getHash));
450
393
  this.state.paths = newPaths?.filter(x => keep.has(getHash(x))) || [];
451
394
  });
395
+ await this.search();
452
396
  }
453
397
 
454
398
  cancel = async () => {
@@ -506,7 +450,7 @@ export class LogViewer3 extends qreact.Component {
506
450
 
507
451
  let hasPaths = Querysub.localRead(() => this.getPaths().length > 0);
508
452
  let getFilesTime = 0;
509
- if (readLiveData.value || !hasPaths) {
453
+ if (!hasPaths) {
510
454
  let startTime = Date.now();
511
455
  await this.loadPaths();
512
456
  getFilesTime = Date.now() - startTime;
@@ -564,6 +508,8 @@ export class LogViewer3 extends qreact.Component {
564
508
  limit: limitURL.value,
565
509
  findBuffer: searchBuffer,
566
510
  pathOverrides: paths,
511
+ only: excludePendingResults.value ? "local" : undefined,
512
+ forceReadPublic: readPublicLogs.value,
567
513
  },
568
514
  onResult: (match: LogDatum) => {
569
515
  console.log("onResult", match);
@@ -734,16 +680,16 @@ export class LogViewer3 extends qreact.Component {
734
680
  <div className={css.vbox(20).pad2(20).fillBoth}>
735
681
  <div className={css.hbox(20)}>
736
682
  <div>Log Viewer 3</div>
737
- <InputLabelURL
738
- checkbox
739
- label="Always Read Live Data"
740
- url={readLiveData}
741
- />
742
683
  <InputLabelURL
743
684
  checkbox
744
685
  label="Exclude Pending Results"
745
686
  url={excludePendingResults}
746
687
  />
688
+ {!isPublic() && <InputLabelURL
689
+ checkbox
690
+ label="Read Public Logs"
691
+ url={readPublicLogs}
692
+ />}
747
693
  <InputLabelURL
748
694
  label="Limit"
749
695
  number
@@ -794,6 +740,7 @@ export class LogViewer3 extends qreact.Component {
794
740
  </Button>
795
741
  <TimeRangeSelector />
796
742
  </div>
743
+ {!isPublic() && readPublicLogs.value && <h1>Reading public logs</h1>}
797
744
 
798
745
  <div className={css.hbox(10).fillWidth}>
799
746
  <InputLabelURL
@@ -814,12 +761,12 @@ export class LogViewer3 extends qreact.Component {
814
761
  onClick={() => void this.loadPaths()}
815
762
  hue={this.state.paths.length ? 200 : 120}
816
763
  >
817
- {this.state.paths.length && "Reset Files" || "Preview Files"}
764
+ {this.getPaths().length && "Reset Files" || "Preview Files"}
818
765
  </Button>
819
766
  )}
820
767
  {this.getPaths().length > 0 &&
821
- <Button flavor="large" hue={120} onClick={() => void this.updatePaths()}>
822
- Update Files
768
+ <Button flavor="large" hue={120} onClick={() => void this.updateToLatestResults()}>
769
+ Search Latest
823
770
  </Button>
824
771
  }
825
772
  {savedPathsURL.value && (
@@ -0,0 +1,215 @@
1
+ import { measureWrap } from "socket-function/src/profiling/measure";
2
+ import { Unit, getAllUnits } from "./BufferIndexHelpers";
3
+
4
+ const WILD_CARD_BYTE = 42;
5
+ const WILD_CARD_CHAR = "*";
6
+ const SEARCH_OR_BYTE = 124;
7
+ const SEARCH_OR_CHAR = "|";
8
+ const SEARCH_AND_BYTE = 38;
9
+ const SEARCH_AND_CHAR = "&";
10
+
11
+ export type MatchStructure = {
12
+ type: "or";
13
+ parts: MatchStructure[];
14
+ } | {
15
+ type: "and";
16
+ parts: MatchStructure[];
17
+ } | {
18
+ // An AND, but the order must be subsequentially, and there can't be overlapping ("cat*ate" matches "cat ate my bird", but not "cate", where as "cat&ate" matches "cate")
19
+ type: "wildcard";
20
+ parts: MatchStructure[];
21
+ } | {
22
+ type: "exact";
23
+ value: Buffer;
24
+ };
25
+
26
+ function parseMatchStructure(pattern: Buffer): MatchStructure {
27
+ let text = pattern.toString("utf-8");
28
+
29
+ let orParts = text.split(SEARCH_OR_CHAR);
30
+ if (orParts.length > 1) {
31
+ return {
32
+ type: "or",
33
+ parts: orParts.map(part => parseMatchStructure(Buffer.from(part)))
34
+ };
35
+ }
36
+
37
+ let andParts = text.split(SEARCH_AND_CHAR);
38
+ if (andParts.length > 1) {
39
+ return {
40
+ type: "and",
41
+ parts: andParts.map(part => parseMatchStructure(Buffer.from(part)))
42
+ };
43
+ }
44
+
45
+ let wildcardParts = text.split(WILD_CARD_CHAR).filter(p => p.length > 0);
46
+ if (wildcardParts.length > 1) {
47
+ return {
48
+ type: "wildcard",
49
+ parts: wildcardParts.map(part => ({ type: "exact" as const, value: Buffer.from(part) }))
50
+ };
51
+ }
52
+
53
+ return { type: "exact", value: pattern };
54
+ }
55
+ // inside is and, outside is outer
56
+ export function getSearchUnits(pattern: Buffer, disableSpecialCharacters: boolean): Unit[][] {
57
+ if (disableSpecialCharacters || !pattern.includes(SEARCH_OR_BYTE) && !pattern.includes(SEARCH_AND_BYTE) && !pattern.includes(WILD_CARD_BYTE)) {
58
+ return [getAllUnits({ buffer: pattern, bufferIndex: 0, block: 0 }).map(u => u.unit)];
59
+ }
60
+ let simpler = getSimplerStructure(parseMatchStructure(pattern));
61
+ let mapped = simpler.map(inner =>
62
+ inner.map(buffer =>
63
+ getAllUnits({ buffer, bufferIndex: 0, block: 0 }).map(u => u.unit)
64
+ ).flat()
65
+ ).filter(part => part.length > 0);
66
+ return mapped;
67
+ }
68
+
69
+ // inside is and, outside is outer
70
+ function getSimplerStructure(structure: MatchStructure): Buffer[][] {
71
+ const type = structure.type;
72
+ if (type === "exact") {
73
+ return [[structure.value]];
74
+ }
75
+
76
+ if (type === "or") {
77
+ let result: Buffer[][] = [];
78
+ for (let part of structure.parts) {
79
+ result.push(...getSimplerStructure(part));
80
+ }
81
+ return result;
82
+ }
83
+
84
+ if (type === "and" || type === "wildcard") {
85
+ let result: Buffer[][] = [[]];
86
+
87
+ for (let part of structure.parts) {
88
+ let partResult = getSimplerStructure(part);
89
+ let newResult: Buffer[][] = [];
90
+
91
+ for (let existingClause of result) {
92
+ for (let partClause of partResult) {
93
+ newResult.push([...existingClause, ...partClause]);
94
+ }
95
+ }
96
+
97
+ result = newResult;
98
+ }
99
+
100
+ return result;
101
+ }
102
+ let unhandled: never = type;
103
+
104
+ throw new Error(`Unknown structure type: ${type}`);
105
+ }
106
+ // Each WILD_CARD_BYTE in pattern acts as a multi-byte wildcard: the segments on either
107
+ // side must appear in order somewhere within buffer.
108
+ // Returns a function that matches buffers against the pre-processed pattern.
109
+ export function createMatchesPattern(pattern: Buffer, disableSpecialCharacters: boolean): (buffer: Buffer) => boolean {
110
+
111
+ if (disableSpecialCharacters || !pattern.includes(SEARCH_OR_BYTE) && !pattern.includes(SEARCH_AND_BYTE) && !pattern.includes(WILD_CARD_BYTE)) {
112
+ return measureWrap(function matchesPattern(buffer: Buffer): boolean {
113
+ return buffer.indexOf(pattern) !== -1;
114
+ }, "BufferIndex|matchesPatternExact");
115
+ }
116
+
117
+
118
+ let structure = parseMatchStructure(pattern);
119
+ let simplerStructure = getSimplerStructure(structure);
120
+ let hasAnyWildcard = pattern.includes(WILD_CARD_BYTE);
121
+
122
+ return measureWrap(function matchesPattern(buffer: Buffer): boolean {
123
+ // 1) First, use simplerStructure to exclude any false cases
124
+ // simplerStructure is Buffer[][] where outer array is OR, inner array is AND
125
+ let anyOrClausePassed = false;
126
+ for (let andClause of simplerStructure) {
127
+ let allAndTermsFound = true;
128
+ for (let seg of andClause) {
129
+ if (buffer.indexOf(seg) === -1) {
130
+ allAndTermsFound = false;
131
+ break;
132
+ }
133
+ }
134
+ if (allAndTermsFound) {
135
+ anyOrClausePassed = true;
136
+ break;
137
+ }
138
+ }
139
+
140
+ if (!anyOrClausePassed) {
141
+ return false;
142
+ }
143
+
144
+ // 2) If !hasAnyWildcard, return true
145
+ if (!hasAnyWildcard) {
146
+ return true;
147
+ }
148
+
149
+ // 3) Do the expensive wildcard check on the full structure.
150
+ return matchesStructure(buffer, structure);
151
+
152
+ function matchesStructure(buf: Buffer, struct: MatchStructure): boolean {
153
+ const type = struct.type;
154
+
155
+ if (type === "exact") {
156
+ return buf.indexOf(struct.value) !== -1;
157
+ }
158
+
159
+ if (type === "or") {
160
+ for (let part of struct.parts) {
161
+ if (matchesStructure(buf, part)) {
162
+ return true;
163
+ }
164
+ }
165
+ return false;
166
+ }
167
+
168
+ if (type === "and") {
169
+ for (let part of struct.parts) {
170
+ if (!matchesStructure(buf, part)) {
171
+ return false;
172
+ }
173
+ }
174
+ return true;
175
+ }
176
+
177
+ if (type === "wildcard") {
178
+ let segments = struct.parts.map(p => {
179
+ if (p.type !== "exact") throw new Error("Wildcard parts must be exact");
180
+ return p.value;
181
+ });
182
+
183
+ function matchSegmentsFrom(bufferPos: number): number {
184
+ for (let seg of segments) {
185
+ function segMatchesAt(pos: number): boolean {
186
+ for (let i = 0; i < seg.length; i++) {
187
+ if (buf[pos + i] !== seg[i]) return false;
188
+ }
189
+ return true;
190
+ }
191
+ let found = false;
192
+ for (let searchPos = bufferPos; searchPos <= buf.length - seg.length; searchPos++) {
193
+ if (segMatchesAt(searchPos)) {
194
+ bufferPos = searchPos + seg.length;
195
+ found = true;
196
+ break;
197
+ }
198
+ }
199
+ if (!found) return -1;
200
+ }
201
+ return bufferPos;
202
+ }
203
+
204
+ for (let startPos = 0; startPos <= buf.length; startPos++) {
205
+ if (matchSegmentsFrom(startPos) >= 0) return true;
206
+ }
207
+ return false;
208
+ }
209
+
210
+ let unhandled: never = type;
211
+ throw new Error(`Unknown structure type: ${type}`);
212
+ }
213
+ }, "BufferIndex|matchesPattern");
214
+ }
215
+
@@ -19,35 +19,18 @@ IMPORTANT! Now I am properly calling shutdown, so none of the streamed logs shou
19
19
 
20
20
 
21
21
 
22
- Make sure audits are appearing.
23
- new non-local WATCH
22
+ 0) Deploy again
23
+ 1) Verify isPublic searching works (from our local machine)
24
24
 
25
25
 
26
- 2.0) SUPPORT reading pending from multiple servers
27
- - The main controller has to find a node on each other machine, and call it. Only one node per machine though, so it shouldn't be too difficult.
28
- - We'll cache the last node per machine that we picked.
29
- - If the cache value doesn't exist, or if it doesn't work, if it throws an error when we try to verify it works, then we'll call a function to get the entry point on all of the nodes for that machine
30
- - After we receive the first result, we'll wait at least a second so we get some more results, and then we'll prioritize the one that's the function endpoint, which will end in function.js.
31
-
32
- 2) Add a UI toggle to read public logs (only shows up on a non-public server though, as otherwise it wouldn't make sense)
33
- - Basically, just changes the code we're reading from multiple servers to select public servers instead, and then, of course, skip ourselves.
34
-
35
- BUG: UGH... live logs for the remote server isn't working...
36
- new non-local WATCH
37
- - UGH... it being pending logs is annoying, as that's hard to debug locally...
38
- AH! Why do we have so few logs?
39
-
40
-
41
- 1) Support & / | syntax in find
42
- - Add note above search for *, &, | syntax
43
-
44
- NOTE: The reason we're not going to add the ability to pin to specific threads is because The files are going to be combined cross-thread anyway, and that should be mostly the files we're searching eventually.
45
26
 
46
27
  2) Create lot of remote server logs
47
28
  - Via our refresh loop
48
29
 
49
30
 
50
- 3) Deploy service for movelogs
31
+ 3) Create and deploy service for movelogs
32
+ 3.0) Update preferred entrypoint to be the movelogs entrypoint
33
+
51
34
  0) Run move logs in function runner, in development, just so we don't get too far behind
52
35
 
53
36