querysub 0.366.0 → 0.368.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 +1 -1
- package/src/archiveapps/archiveGCEntry.tsx +1 -1
- package/src/archiveapps/archiveMergeEntry.tsx +1 -1
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +19 -22
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +64 -60
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +16 -73
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +153 -19
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +35 -83
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +4 -4
- package/src/diagnostics/logs/IndexedLogs/bufferMatcher.ts +215 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +5 -22
package/package.json
CHANGED
|
@@ -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,
|
|
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:
|
|
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
|
|
347
|
-
for (let
|
|
348
|
-
|
|
349
|
-
|
|
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 (!
|
|
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
|
-
|
|
412
|
-
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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 (!
|
|
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(
|
|
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
|
-
|
|
548
|
+
units: number[];
|
|
581
549
|
index: Buffer;
|
|
582
550
|
}): number[] {
|
|
583
|
-
const {
|
|
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
|
-
|
|
604
|
-
for (
|
|
605
|
-
|
|
606
|
-
|
|
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,32 +224,102 @@ 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 timeoutToUndefinedSilent(2500, 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
|
|
|
301
|
+
let promises: Promise<void>[] = [];
|
|
302
|
+
|
|
236
303
|
if (config.only !== "public") {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
304
|
+
promises.push((async () => {
|
|
305
|
+
let localPaths = await new TimeFileTree(localLogs).findAllPaths({
|
|
306
|
+
startTime: config.startTime,
|
|
307
|
+
endTime: config.endTime,
|
|
308
|
+
});
|
|
309
|
+
paths.push(...localPaths);
|
|
310
|
+
})());
|
|
242
311
|
}
|
|
243
312
|
|
|
244
313
|
if (config.only !== "local") {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
314
|
+
promises.push((async () => {
|
|
315
|
+
let backblazePaths = await new TimeFileTree(backblazeLogs).findAllPaths({
|
|
316
|
+
startTime: config.startTime,
|
|
317
|
+
endTime: config.endTime,
|
|
318
|
+
});
|
|
319
|
+
paths.push(...backblazePaths);
|
|
320
|
+
})());
|
|
250
321
|
}
|
|
322
|
+
await Promise.all(promises);
|
|
251
323
|
|
|
252
324
|
// Bake in the size to ensure more cache hits (without this, every single access will always result in a file/remote call).
|
|
253
325
|
let pathsWithSize = await Promise.all(paths.map(async (path): Promise<TimeFilePathWithSize> => {
|
|
@@ -262,7 +334,9 @@ export class IndexedLogs<T> {
|
|
|
262
334
|
return { ...path, size, indexSize, sourceName: this.config.name };
|
|
263
335
|
}));
|
|
264
336
|
|
|
265
|
-
|
|
337
|
+
finalPaths.push(...pathsWithSize);
|
|
338
|
+
|
|
339
|
+
return finalPaths;
|
|
266
340
|
}
|
|
267
341
|
|
|
268
342
|
@measureFnc
|
|
@@ -274,7 +348,51 @@ export class IndexedLogs<T> {
|
|
|
274
348
|
let startTime = Date.now();
|
|
275
349
|
let interval: NodeJS.Timeout | undefined;
|
|
276
350
|
|
|
351
|
+
let getFinalResults = (): IndexedLogResults => {
|
|
352
|
+
let results = createEmptyIndexedLogResults();
|
|
353
|
+
for (let result of allResults.values()) {
|
|
354
|
+
results = mergeIndexedLogResults(results, result);
|
|
355
|
+
}
|
|
356
|
+
return results;
|
|
357
|
+
};
|
|
358
|
+
let allResults = new Map<string, IndexedLogResults>();
|
|
359
|
+
let allDone: Promise<void>[] = [];
|
|
360
|
+
|
|
361
|
+
if (!config.params.only && (config.params.forceReadPublic || isPublic())) {
|
|
362
|
+
let machineNodes = await this.getMachineNodes();
|
|
363
|
+
allDone.push(...machineNodes.map(async (machineNode) => {
|
|
364
|
+
try {
|
|
365
|
+
let resultObj = await this.clientFind({
|
|
366
|
+
...config,
|
|
367
|
+
params: {
|
|
368
|
+
...config.params,
|
|
369
|
+
only: "local",
|
|
370
|
+
},
|
|
371
|
+
nodeId: machineNode,
|
|
372
|
+
onResult(match) {
|
|
373
|
+
config.onResult(match);
|
|
374
|
+
},
|
|
375
|
+
onResults(results) {
|
|
376
|
+
allResults.set(machineNode, results);
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
allResults.set(machineNode, resultObj);
|
|
380
|
+
} catch (e) {
|
|
381
|
+
console.error(`Error in finding logs on machine node ${machineNode}: ${e}`);
|
|
382
|
+
}
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 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
|
|
387
|
+
if (!isPublic() && config.params.forceReadPublic) {
|
|
388
|
+
return getFinalResults();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
277
392
|
let results: IndexedLogResults = createEmptyIndexedLogResults();
|
|
393
|
+
allResults.set("", results);
|
|
394
|
+
|
|
395
|
+
|
|
278
396
|
results.limitGroup = new LimitGroup({
|
|
279
397
|
maxTimePerBeforeWait: 500,
|
|
280
398
|
waitTime: 250,
|
|
@@ -316,7 +434,8 @@ export class IndexedLogs<T> {
|
|
|
316
434
|
if (onResultsCallback) {
|
|
317
435
|
interval = setInterval(async () => {
|
|
318
436
|
updateResultsStats();
|
|
319
|
-
let
|
|
437
|
+
let results = getFinalResults();
|
|
438
|
+
let shouldContinue = await onResultsCallback(results);
|
|
320
439
|
if (!shouldContinue) {
|
|
321
440
|
if (!results.cancel) {
|
|
322
441
|
console.log(blue(`Cancelled search on ${this.config.name}`));
|
|
@@ -327,13 +446,15 @@ export class IndexedLogs<T> {
|
|
|
327
446
|
}
|
|
328
447
|
|
|
329
448
|
try {
|
|
330
|
-
|
|
449
|
+
let promise = this.findBase({
|
|
331
450
|
params: config.params,
|
|
332
451
|
onResult: config.onResult,
|
|
333
452
|
results,
|
|
334
453
|
});
|
|
454
|
+
allDone.push(promise);
|
|
455
|
+
await Promise.allSettled(allDone);
|
|
335
456
|
updateResultsStats();
|
|
336
|
-
return
|
|
457
|
+
return getFinalResults();
|
|
337
458
|
} finally {
|
|
338
459
|
if (interval) {
|
|
339
460
|
clearInterval(interval);
|
|
@@ -482,8 +603,9 @@ export class IndexedLogs<T> {
|
|
|
482
603
|
params: SearchParams;
|
|
483
604
|
onResult: (match: T) => void;
|
|
484
605
|
onResults?: (results: IndexedLogResults) => void;
|
|
606
|
+
nodeId?: string;
|
|
485
607
|
}): Promise<IndexedLogResults> {
|
|
486
|
-
let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
|
|
608
|
+
let controller = IndexedLogShimController.nodes[config.nodeId || SocketFunction.getBrowserNodeId()];
|
|
487
609
|
let findId = nextId();
|
|
488
610
|
let callback = (match: T) => {
|
|
489
611
|
config.onResult(match);
|
|
@@ -514,6 +636,8 @@ export class IndexedLogs<T> {
|
|
|
514
636
|
startTime: number;
|
|
515
637
|
endTime: number;
|
|
516
638
|
only?: "local" | "public";
|
|
639
|
+
forceReadPublic?: boolean;
|
|
640
|
+
|
|
517
641
|
}): Promise<TimeFilePathWithSize[]> {
|
|
518
642
|
let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
|
|
519
643
|
return await controller.getPaths({
|
|
@@ -521,6 +645,7 @@ export class IndexedLogs<T> {
|
|
|
521
645
|
startTime: config.startTime,
|
|
522
646
|
endTime: config.endTime,
|
|
523
647
|
only: config.only,
|
|
648
|
+
forceReadPublic: config.forceReadPublic,
|
|
524
649
|
});
|
|
525
650
|
}
|
|
526
651
|
public onFindResult(config: {
|
|
@@ -642,6 +767,7 @@ class IndexedLogShim {
|
|
|
642
767
|
startTime: number;
|
|
643
768
|
endTime: number;
|
|
644
769
|
only?: "local" | "public";
|
|
770
|
+
forceReadPublic?: boolean;
|
|
645
771
|
}): Promise<TimeFilePathWithSize[]> {
|
|
646
772
|
let indexedLogs = loggerByName.get(config.indexedLogsName);
|
|
647
773
|
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
@@ -649,6 +775,7 @@ class IndexedLogShim {
|
|
|
649
775
|
startTime: config.startTime,
|
|
650
776
|
endTime: config.endTime,
|
|
651
777
|
only: config.only,
|
|
778
|
+
forceReadPublic: config.forceReadPublic,
|
|
652
779
|
});
|
|
653
780
|
}
|
|
654
781
|
|
|
@@ -659,6 +786,10 @@ class IndexedLogShim {
|
|
|
659
786
|
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
660
787
|
await indexedLogs.moveLogsToPublic(true);
|
|
661
788
|
}
|
|
789
|
+
|
|
790
|
+
public async hasLogger(name: string) {
|
|
791
|
+
return loggerByName.has(name);
|
|
792
|
+
}
|
|
662
793
|
}
|
|
663
794
|
|
|
664
795
|
const IndexedLogShimController = SocketFunction.register(
|
|
@@ -673,6 +804,9 @@ const IndexedLogShimController = SocketFunction.register(
|
|
|
673
804
|
},
|
|
674
805
|
forceMoveLogsToPublic: {
|
|
675
806
|
hooks: [assertIsManagementUser]
|
|
807
|
+
},
|
|
808
|
+
hasLogger: {
|
|
809
|
+
hooks: [assertIsManagementUser]
|
|
676
810
|
}
|
|
677
811
|
})
|
|
678
812
|
);
|
|
@@ -21,14 +21,16 @@ 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 "
|
|
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";
|
|
28
|
+
import { Zip } from "../../../zip";
|
|
27
29
|
|
|
28
30
|
let searchText = new URLParam("searchText", "");
|
|
29
|
-
let readLiveData = new URLParam("readLiveData", false);
|
|
30
31
|
let excludePendingResults = new URLParam("excludePendingResults", false);
|
|
31
32
|
let limitURL = new URLParam("limit", 100);
|
|
33
|
+
let readPublicLogs = new URLParam("readPublicLogs", false);
|
|
32
34
|
|
|
33
35
|
let savedPathsURL = new URLParam("savedPaths", "");
|
|
34
36
|
let selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, boolean>);
|
|
@@ -43,63 +45,6 @@ const defaultSelectedFields = {
|
|
|
43
45
|
__entry: true,
|
|
44
46
|
};
|
|
45
47
|
|
|
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
48
|
|
|
104
49
|
export class LogViewer3 extends qreact.Component {
|
|
105
50
|
static renderInProgress = true;
|
|
@@ -132,14 +77,11 @@ export class LogViewer3 extends qreact.Component {
|
|
|
132
77
|
let paths: TimeFilePathWithSize[] = [];
|
|
133
78
|
if (savedPathsURL.value) {
|
|
134
79
|
const compressed = Buffer.from(savedPathsURL.value, "base64");
|
|
135
|
-
const decompressed =
|
|
80
|
+
const decompressed = Zip.gunzipSync(compressed);
|
|
136
81
|
paths = JSON.parse(decompressed.toString("utf8"));
|
|
137
82
|
} else {
|
|
138
83
|
paths = this.state.paths;
|
|
139
84
|
}
|
|
140
|
-
if (excludePendingResults.value) {
|
|
141
|
-
paths = paths.filter(x => x.logCount !== undefined);
|
|
142
|
-
}
|
|
143
85
|
return paths;
|
|
144
86
|
}
|
|
145
87
|
|
|
@@ -154,7 +96,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
154
96
|
freezePaths() {
|
|
155
97
|
const json = JSON.stringify(this.state.paths);
|
|
156
98
|
const buffer = Buffer.from(json, "utf8");
|
|
157
|
-
const compressed =
|
|
99
|
+
const compressed = Zip.gzipSync(buffer, 9);
|
|
158
100
|
savedPathsURL.value = compressed.toString("base64");
|
|
159
101
|
}
|
|
160
102
|
|
|
@@ -202,8 +144,8 @@ export class LogViewer3 extends qreact.Component {
|
|
|
202
144
|
const renderStage = (title: string, items: preact.ComponentChild[], hue: number | undefined, isFirst: boolean, isLast: boolean) => {
|
|
203
145
|
const triangleWidth = 20;
|
|
204
146
|
|
|
205
|
-
let color = hue ? css.hsl(hue, 70, 80) : css.hsl(0, 0, 100);
|
|
206
|
-
let colorhsl = hue ? css.hslcolor(hue, 80, 30) : css;
|
|
147
|
+
let color = hue !== undefined ? css.hsl(hue, 70, 80) : css.hsl(0, 0, 100);
|
|
148
|
+
let colorhsl = hue !== undefined ? css.hslcolor(hue, 80, 30) : css;
|
|
207
149
|
|
|
208
150
|
return (
|
|
209
151
|
<div className={css.vbox(2).relative.minWidth(200).paddingTop(8).paddingBottom(8).paddingRight(10).paddingLeft(isFirst ? 10 : 10 + triangleWidth) + color}>
|
|
@@ -211,7 +153,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
211
153
|
{items.map((item, i) => (
|
|
212
154
|
<div key={i}>{item}</div>
|
|
213
155
|
))}
|
|
214
|
-
{!isLast && (
|
|
156
|
+
{!isLast && hue !== undefined && (
|
|
215
157
|
<svg width={triangleWidth} height="100" viewBox="0 0 20 100" preserveAspectRatio="none" className={css.absolute.right(-triangleWidth).top(0).fillHeight.zIndex(1)}>
|
|
216
158
|
<polygon points="0,0 20,50 0,100" fill={`hsl(${hue}, 70%, 80%)`} />
|
|
217
159
|
</svg>
|
|
@@ -262,8 +204,8 @@ export class LogViewer3 extends qreact.Component {
|
|
|
262
204
|
{renderStage("Indexes", indexItems, 250, false, false)}
|
|
263
205
|
{renderStage("Blocks", blockItems, 210, false, false)}
|
|
264
206
|
{renderStage("Reads", cacheItems, 160, false, false)}
|
|
265
|
-
{renderStage("Results", resultItems, 120, false,
|
|
266
|
-
{renderStage("Done", doneItems, undefined, false, !hasErrors)}
|
|
207
|
+
{renderStage("Results", resultItems, 120, false, this.state.searching && !hasErrors)}
|
|
208
|
+
{!this.state.searching && renderStage("Done", doneItems, undefined, false, !hasErrors)}
|
|
267
209
|
{(() => {
|
|
268
210
|
if (!hasErrors) return undefined;
|
|
269
211
|
let errorItems: preact.ComponentChild[] = [];
|
|
@@ -339,7 +281,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
339
281
|
)}
|
|
340
282
|
{!isFrozen && (
|
|
341
283
|
<>
|
|
342
|
-
<div className={css.fontWeight(600)}>
|
|
284
|
+
<div className={css.fontWeight(600)}>Pending logs not being merged!</div>
|
|
343
285
|
{warnings.map((warning) => (
|
|
344
286
|
<div key={warning.machineId} className={css.hbox(10)}>
|
|
345
287
|
<MachineThreadInfo machineId={warning.machineId} />
|
|
@@ -401,6 +343,8 @@ export class LogViewer3 extends qreact.Component {
|
|
|
401
343
|
|
|
402
344
|
Querysub.commitLocal(() => {
|
|
403
345
|
this.state.loadingPaths = true;
|
|
346
|
+
this.state.results = [];
|
|
347
|
+
this.state.stats = undefined;
|
|
404
348
|
});
|
|
405
349
|
|
|
406
350
|
let loggers = await getLoggers2Async();
|
|
@@ -423,13 +367,15 @@ export class LogViewer3 extends qreact.Component {
|
|
|
423
367
|
let allPaths: TimeFilePathWithSize[] = [];
|
|
424
368
|
let range = Querysub.localRead(() => getTimeRange());
|
|
425
369
|
|
|
426
|
-
|
|
370
|
+
await Promise.all(selectedLoggers.map(async (logger) => {
|
|
427
371
|
let paths = await logger.clientGetPaths({
|
|
428
372
|
startTime: range.startTime,
|
|
429
373
|
endTime: range.endTime,
|
|
374
|
+
only: excludePendingResults.value ? "local" : undefined,
|
|
375
|
+
forceReadPublic: readPublicLogs.value,
|
|
430
376
|
});
|
|
431
377
|
allPaths.push(...paths);
|
|
432
|
-
}
|
|
378
|
+
}));
|
|
433
379
|
sort(allPaths, x => -x.startTime);
|
|
434
380
|
|
|
435
381
|
Querysub.commitLocal(() => {
|
|
@@ -439,7 +385,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
439
385
|
return allPaths;
|
|
440
386
|
}
|
|
441
387
|
|
|
442
|
-
async
|
|
388
|
+
async updateToLatestResults() {
|
|
443
389
|
let prevPaths = this.getPaths();
|
|
444
390
|
let newPaths = await this.loadPaths();
|
|
445
391
|
Querysub.commitLocal(() => {
|
|
@@ -449,6 +395,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
449
395
|
let keep = new Set(prevPaths.map(getHash));
|
|
450
396
|
this.state.paths = newPaths?.filter(x => keep.has(getHash(x))) || [];
|
|
451
397
|
});
|
|
398
|
+
await this.search();
|
|
452
399
|
}
|
|
453
400
|
|
|
454
401
|
cancel = async () => {
|
|
@@ -506,7 +453,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
506
453
|
|
|
507
454
|
let hasPaths = Querysub.localRead(() => this.getPaths().length > 0);
|
|
508
455
|
let getFilesTime = 0;
|
|
509
|
-
if (
|
|
456
|
+
if (!hasPaths) {
|
|
510
457
|
let startTime = Date.now();
|
|
511
458
|
await this.loadPaths();
|
|
512
459
|
getFilesTime = Date.now() - startTime;
|
|
@@ -564,6 +511,8 @@ export class LogViewer3 extends qreact.Component {
|
|
|
564
511
|
limit: limitURL.value,
|
|
565
512
|
findBuffer: searchBuffer,
|
|
566
513
|
pathOverrides: paths,
|
|
514
|
+
only: excludePendingResults.value ? "local" : undefined,
|
|
515
|
+
forceReadPublic: readPublicLogs.value,
|
|
567
516
|
},
|
|
568
517
|
onResult: (match: LogDatum) => {
|
|
569
518
|
console.log("onResult", match);
|
|
@@ -734,16 +683,16 @@ export class LogViewer3 extends qreact.Component {
|
|
|
734
683
|
<div className={css.vbox(20).pad2(20).fillBoth}>
|
|
735
684
|
<div className={css.hbox(20)}>
|
|
736
685
|
<div>Log Viewer 3</div>
|
|
737
|
-
<InputLabelURL
|
|
738
|
-
checkbox
|
|
739
|
-
label="Always Read Live Data"
|
|
740
|
-
url={readLiveData}
|
|
741
|
-
/>
|
|
742
686
|
<InputLabelURL
|
|
743
687
|
checkbox
|
|
744
688
|
label="Exclude Pending Results"
|
|
745
689
|
url={excludePendingResults}
|
|
746
690
|
/>
|
|
691
|
+
{!isPublic() && <InputLabelURL
|
|
692
|
+
checkbox
|
|
693
|
+
label="Read Public Logs"
|
|
694
|
+
url={readPublicLogs}
|
|
695
|
+
/>}
|
|
747
696
|
<InputLabelURL
|
|
748
697
|
label="Limit"
|
|
749
698
|
number
|
|
@@ -794,6 +743,9 @@ export class LogViewer3 extends qreact.Component {
|
|
|
794
743
|
</Button>
|
|
795
744
|
<TimeRangeSelector />
|
|
796
745
|
</div>
|
|
746
|
+
{!isPublic() && readPublicLogs.value && <div className={css.fontSize(40).pad2(12, 6).hsl(280, 40, 50).colorhsl(0, 0, 100).boldStyle}>
|
|
747
|
+
Reading public logs
|
|
748
|
+
</div>}
|
|
797
749
|
|
|
798
750
|
<div className={css.hbox(10).fillWidth}>
|
|
799
751
|
<InputLabelURL
|
|
@@ -814,12 +766,12 @@ export class LogViewer3 extends qreact.Component {
|
|
|
814
766
|
onClick={() => void this.loadPaths()}
|
|
815
767
|
hue={this.state.paths.length ? 200 : 120}
|
|
816
768
|
>
|
|
817
|
-
{this.
|
|
769
|
+
{this.getPaths().length && "Reset Files" || "Preview Files"}
|
|
818
770
|
</Button>
|
|
819
771
|
)}
|
|
820
772
|
{this.getPaths().length > 0 &&
|
|
821
|
-
<Button flavor="large" hue={120} onClick={() => void this.
|
|
822
|
-
|
|
773
|
+
<Button flavor="large" hue={120} onClick={() => void this.updateToLatestResults()}>
|
|
774
|
+
Search Latest
|
|
823
775
|
</Button>
|
|
824
776
|
}
|
|
825
777
|
{savedPathsURL.value && (
|
|
@@ -156,12 +156,12 @@ export class TimeFileTree {
|
|
|
156
156
|
// Find all days in this month
|
|
157
157
|
const days = await this.archives.find(monthFolder + "/", { shallow: true, type: "folders" });
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
await Promise.all(days.map(async (dayFolder) => {
|
|
160
160
|
// dayFolder is a full path like "2024/01/15"
|
|
161
161
|
const day = parseInt(dayFolder.split("/").at(-1) || "", 10);
|
|
162
162
|
|
|
163
163
|
if (isNaN(day) || day < 1 || day > 31) {
|
|
164
|
-
|
|
164
|
+
return;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
// Check if this day is in range
|
|
@@ -169,7 +169,7 @@ export class TimeFileTree {
|
|
|
169
169
|
const dayEndDate = new Date(Date.UTC(year, month - 1, day, 23, 59, 59, 999));
|
|
170
170
|
|
|
171
171
|
if (dayEndDate.getTime() < config.startTime || dayDate.getTime() > config.endTime) {
|
|
172
|
-
|
|
172
|
+
return;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// Find all log files in this day
|
|
@@ -187,7 +187,7 @@ export class TimeFileTree {
|
|
|
187
187
|
results.push(decoded);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
}
|
|
190
|
+
}));
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
|
|
@@ -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
|
-
|
|
23
|
-
|
|
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)
|
|
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
|
|