querysub 0.357.0 → 0.359.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.
Files changed (25) hide show
  1. package/.cursorrules +1 -0
  2. package/package.json +2 -1
  3. package/src/-a-archives/archivesDisk.ts +24 -6
  4. package/src/-a-archives/archivesMemoryCache.ts +41 -17
  5. package/src/deployManager/components/MachineDetailPage.tsx +45 -4
  6. package/src/deployManager/components/MachinesListPage.tsx +10 -2
  7. package/src/deployManager/components/ServiceDetailPage.tsx +13 -3
  8. package/src/deployManager/components/ServicesListPage.tsx +18 -6
  9. package/src/deployManager/machineApplyMainCode.ts +3 -3
  10. package/src/deployManager/machineSchema.ts +39 -0
  11. package/src/diagnostics/NodeViewer.tsx +2 -1
  12. package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +124 -123
  13. package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +83 -1
  14. package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +2 -0
  15. package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +21 -24
  16. package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +1 -1
  17. package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +186 -25
  18. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +284 -195
  19. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +312 -108
  20. package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +1 -1
  21. package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +37 -7
  22. package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +0 -0
  23. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +62 -35
  24. package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +0 -180
  25. package/src/functional/limitProcessing.ts +39 -0
@@ -2,10 +2,10 @@ import { lazy } from "socket-function/src/caching";
2
2
  import { Archives, nestArchives } from "../../../-a-archives/archives";
3
3
  import { deepCloneJSON, keyByArray, nextId, sort, timeInHour, timeInMinute, timeInSecond } from "socket-function/src/misc";
4
4
  import { BufferIndex } from "./BufferIndex";
5
- import { delay, runInSerial, runInfinitePoll } from "socket-function/src/batching";
6
- import { Reader, SearchParams } from "./BufferIndexHelpers";
5
+ import { delay, runInParallel, runInSerial, runInfinitePoll } from "socket-function/src/batching";
6
+ import { IndexedLogResults, Reader, SearchParams, addReadToResults, createEmptyIndexedLogResults, INDEX_EXTENSION } from "./BufferIndexHelpers";
7
7
  import { getDomain, isPublic } from "../../../config";
8
- import { getArchivesLocal } from "../../../-a-archives/archivesDisk";
8
+ import { getArchivesHome, getArchivesLocal } from "../../../-a-archives/archivesDisk";
9
9
  import { getArchivesBackblaze, getArchivesBackblazePrivateImmutable } from "../../../-a-archives/archivesBackBlaze";
10
10
  import { getOwnThreadId } from "../../../-a-auth/certs";
11
11
  import { ArchivesMemoryCacheStats, createArchivesMemoryCache } from "../../../-a-archives/archivesMemoryCache";
@@ -21,49 +21,16 @@ import { FindProgressTracker } from "./FindProgressTracker";
21
21
  import { SocketFunction } from "socket-function/SocketFunction";
22
22
  import { assertIsManagementUser } from "../../managementPages";
23
23
  import { ignoreErrors } from "../../../errors";
24
+ import { blue } from "socket-function/src/formatting/logColors";
25
+ import { LimitGroup } from "../../../functional/limitProcessing";
24
26
 
25
27
  export type TimeFilePathWithSize = TimeFilePath & {
26
28
  size: number;
27
- sourceName: string;
28
- };
29
-
30
- export type IndexedLogResults = {
31
- matchCount: number;
32
-
33
- // NOTE: A lot of the metadata won't be accurate if multiple searches happen at the same time. However, for debugging, it should be sufficient.
34
- reads: {
35
- cached: boolean;
36
- remote: boolean;
37
- count: number;
38
- size: number;
39
-
40
- totalSize: number;
41
- totalCount: number;
42
- }[];
43
-
44
- localFilesSearched: number;
45
- backblazeFilesSearched: number;
46
-
47
- totalBlockCount: number;
48
- blockCheckedCount: number;
49
- blocksCheckedCompressedSize: number;
50
- blocksCheckedDecompressedSize: number;
51
- blockErrors: string[];
52
-
53
- fileErrors: string[];
54
-
55
- indexesSearched: number;
56
29
  indexSize: number;
57
-
58
- timeToFirstMatch: number;
59
- fileFindTime: number;
60
- indexSearchTime: number;
61
- blockSearchTime: number;
62
-
63
- totalSearchTime: number;
30
+ sourceName: string;
64
31
  };
65
32
 
66
- let logByName = new Map<string, IndexedLogs<unknown>>();
33
+ let loggerByName = new Map<string, IndexedLogs<unknown>>();
67
34
 
68
35
 
69
36
  export class IndexedLogs<T> {
@@ -74,68 +41,18 @@ export class IndexedLogs<T> {
74
41
  forceUsePublicLogs?: boolean;
75
42
  getTime: (result: T) => number | undefined;
76
43
  }) {
77
- logByName.set(this.config.name, this as any);
44
+ loggerByName.set(this.config.name, this as any);
78
45
  }
79
46
 
80
47
 
81
48
  private static shouldRunLoop = false;
82
49
  public static runLogMoveLoop() {
83
50
  IndexedLogs.shouldRunLoop = true;
84
- for (let indexedLogs of logByName.values()) {
51
+ for (let indexedLogs of loggerByName.values()) {
85
52
  indexedLogs.runLogMoverLoop();
86
53
  }
87
54
  }
88
55
 
89
- private findCallbacks = new Map<string, (match: T) => void>();
90
- public async clientFind(config: {
91
- params: SearchParams;
92
- onResult: (match: T) => void;
93
- }): Promise<IndexedLogResults> {
94
- let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
95
- let findId = nextId();
96
- let callback = (match: T) => {
97
- config.onResult(match);
98
- };
99
- this.findCallbacks.set(findId, callback);
100
- try {
101
- return await controller.find({
102
- findId,
103
- indexedLogsName: this.config.name,
104
- params: config.params,
105
- });
106
- } finally {
107
- // There's some trailing time after the controller call finishes when the results will be trickling back to us.
108
- setTimeout(() => {
109
- this.findCallbacks.delete(findId);
110
- }, timeInMinute * 30);
111
- }
112
- }
113
- public async clientGetPaths(config: {
114
- startTime: number;
115
- endTime: number;
116
- only?: "local" | "public";
117
- }): Promise<TimeFilePathWithSize[]> {
118
- let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
119
- return await controller.getPaths({
120
- indexedLogsName: this.config.name,
121
- startTime: config.startTime,
122
- endTime: config.endTime,
123
- only: config.only,
124
- });
125
- }
126
- public onFindResult(config: {
127
- findId: string;
128
- result: unknown;
129
- }) {
130
- let callback = this.findCallbacks.get(config.findId);
131
- if (!callback) return;
132
- callback(config.result as T);
133
- }
134
-
135
- public async clientForceMoveLogsToPublic() {
136
- await this.moveLogsToPublic(true);
137
- }
138
-
139
56
  private localLogsStats = {
140
57
  cachedReads: 0,
141
58
  uncachedReads: 0,
@@ -153,23 +70,29 @@ export class IndexedLogs<T> {
153
70
  totalCacheCount: 0,
154
71
  };
155
72
 
73
+ private fileSizeCache = new Map<string, number>();
74
+
156
75
  private getLocalLogs = lazy((): Archives => {
157
- let baseDisk = getArchivesLocal(getDomain());
76
+ let baseDisk = getArchivesHome(getDomain());
158
77
  let archives = nestArchives("indexed-logs/" + this.config.name, baseDisk);
159
78
  archives = createArchivesMemoryCache(archives, {
160
79
  maxSize: 1024 * 1024 * 512,
161
80
  maxCount: 1000 * 100,
162
- stats: this.localLogsStats
81
+ stats: this.localLogsStats,
82
+ sizeCache: this.fileSizeCache,
83
+ // Local disk reads are fast, but even local reads can have high latency
84
+ extraReadSize: 1024 * 1024 * 1,
163
85
  });
164
86
  return archives;
165
87
  });
166
88
  private getPublicLogs = lazy((): Archives => {
167
- let basePublic: Archives = getArchivesLocal(getDomain());
168
- let extraReadSize = 1024;
89
+ let basePublic: Archives = getArchivesHome(getDomain());
90
+ // NOTE: The local disk is so fast that reading in 10 megabytes is nothing, And if we read in too small of a value, the overhead per read ends up making this take forever.
91
+ let extraReadSize = 1024 * 1024 * 10;
169
92
  if (this.config.forceUsePublicLogs || isPublic()) {
170
93
  basePublic = getArchivesBackblaze(getDomain());
171
- // The latency on Backblaze is so high that if we're going to do a read, we might as well read a lot. As in, minimum 2s latency. The bandwidth is good though.
172
- extraReadSize = 1024 * 1024 * 10;
94
+ // NOTE: While the latency to back plays is high, now we're reading in parallel, so it shouldn't be as big of an issue.
95
+ extraReadSize = 1024 * 1024 * 1;
173
96
  }
174
97
  let archives = nestArchives("final-indexed-logs/" + this.config.name, basePublic);
175
98
  archives = createArchivesMemoryCache(archives, {
@@ -178,6 +101,7 @@ export class IndexedLogs<T> {
178
101
  fullyImmutable: true,
179
102
  extraReadSize,
180
103
  stats: this.backblazeLogsStats,
104
+ sizeCache: this.fileSizeCache,
181
105
  });
182
106
  return archives;
183
107
  });
@@ -198,20 +122,19 @@ export class IndexedLogs<T> {
198
122
  private forceFlushStream: (() => Promise<void>) | undefined;
199
123
 
200
124
  private getCurrentLogStream(): LogStreamer<T> {
125
+
201
126
  if (!this.currentLogStream) {
202
127
  let { startTime, endTime } = this.getTimeBlock(Date.now());
203
128
  let path = new TimeFileTree(this.getLocalLogs()).getNewPendingPath({ startTime, endTime });
204
- let indexPath = this.getIndexPath(path);
205
129
  let streamer = BufferIndex.createStreamer();
206
130
  let currentLogStreamSize = 0;
207
131
  let currentLogStreamCount = 0;
208
132
  let newStreamer = async () => {
209
133
  let result = streamer.close();
210
134
  await this.getLocalLogs().append(path, result.data);
211
- await this.getLocalLogs().append(indexPath, result.index);
135
+ await this.getLocalLogs().append(path + INDEX_EXTENSION, result.index);
212
136
 
213
137
  path = new TimeFileTree(this.getLocalLogs()).getNewPendingPath({ startTime, endTime });
214
- indexPath = this.getIndexPath(path);
215
138
  streamer = BufferIndex.createStreamer();
216
139
  currentLogStreamSize = 0;
217
140
  currentLogStreamCount = 0;
@@ -224,6 +147,14 @@ export class IndexedLogs<T> {
224
147
 
225
148
  };
226
149
  let writeBuffers = async (buffers: Buffer[]) => {
150
+ if (Date.now() > endTime) {
151
+ let timeBlockObj = this.getTimeBlock(Date.now());
152
+ startTime = timeBlockObj.startTime;
153
+ endTime = timeBlockObj.endTime;
154
+ path = new TimeFileTree(this.getLocalLogs()).getNewPendingPath(timeBlockObj);
155
+ await newStreamer();
156
+ }
157
+
227
158
  let maxSize = this.config.maxSingleFileData || MAX_SINGLE_FILE_DATA;
228
159
  let maxCount = this.config.maxCountPerFile || MAX_COUNT_PER_FILE;
229
160
 
@@ -239,7 +170,7 @@ export class IndexedLogs<T> {
239
170
  let result = streamer.add(group);
240
171
  await this.getLocalLogs().append(path, result.data);
241
172
  if (result.index) {
242
- await this.getLocalLogs().append(indexPath, result.index);
173
+ await this.getLocalLogs().append(path + INDEX_EXTENSION, result.index);
243
174
  }
244
175
 
245
176
  currentLogStreamSize += result.data.length;
@@ -284,10 +215,6 @@ export class IndexedLogs<T> {
284
215
  return groups;
285
216
  }
286
217
 
287
- private getIndexPath(dataPath: string): string {
288
- return dataPath + ".index";
289
- }
290
-
291
218
  public append(datum: T) {
292
219
  this.getCurrentLogStream().append(datum);
293
220
  if (IndexedLogs.shouldRunLoop) {
@@ -295,6 +222,7 @@ export class IndexedLogs<T> {
295
222
  }
296
223
  }
297
224
 
225
+ @measureFnc
298
226
  public async getPaths(config: {
299
227
  startTime: number;
300
228
  endTime: number;
@@ -326,16 +254,98 @@ export class IndexedLogs<T> {
326
254
  let archives = path.logCount !== undefined ? backblazeLogs : localLogs;
327
255
  let info = await archives.getInfo(path.fullPath);
328
256
  let size = info?.size || 0;
329
- return { ...path, size, sourceName: this.config.name };
257
+ this.fileSizeCache.set(path.fullPath, size);
258
+ let indexPath = path.fullPath + INDEX_EXTENSION;
259
+ let indexInfo = await archives.getInfo(indexPath);
260
+ let indexSize = indexInfo?.size || 0;
261
+ this.fileSizeCache.set(indexPath, indexSize);
262
+ return { ...path, size, indexSize, sourceName: this.config.name };
330
263
  }));
331
264
 
332
265
  return pathsWithSize;
333
266
  }
334
267
 
268
+ @measureFnc
335
269
  public async find(config: {
336
270
  params: SearchParams;
337
271
  onResult: (match: T) => void;
272
+ onResults?: (results: IndexedLogResults) => Promise<boolean>;
338
273
  }): Promise<IndexedLogResults> {
274
+ let startTime = Date.now();
275
+ let interval: NodeJS.Timeout | undefined;
276
+
277
+ let results: IndexedLogResults = createEmptyIndexedLogResults();
278
+ results.limitGroup = new LimitGroup({
279
+ maxTimePerBeforeWait: 500,
280
+ waitTime: 250,
281
+ });
282
+
283
+ let initialLocalStats = deepCloneJSON(this.localLogsStats);
284
+ let initialBackblazeStats = deepCloneJSON(this.backblazeLogsStats);
285
+
286
+ let updateResultsStats = () => {
287
+ results.reads = [];
288
+
289
+ function addStatsRead(initial: ArchivesMemoryCacheStats, final: ArchivesMemoryCacheStats, remote: boolean) {
290
+ let uncached = addReadToResults(results, {
291
+ cached: false,
292
+ remote,
293
+ count: final.uncachedReads - initial.uncachedReads,
294
+ size: final.uncachedReadSize - initial.uncachedReadSize,
295
+ });
296
+ uncached.totalSize = final.totalCacheSize;
297
+ uncached.totalCount = final.totalCacheCount;
298
+
299
+ let cached = addReadToResults(results, {
300
+ cached: true,
301
+ remote,
302
+ count: final.cachedReads - initial.cachedReads,
303
+ size: final.cachedReadSize - initial.cachedReadSize,
304
+ });
305
+ cached.totalSize = final.totalCacheSize;
306
+ cached.totalCount = final.totalCacheCount;
307
+ }
308
+
309
+ addStatsRead(initialLocalStats, this.localLogsStats, false);
310
+ addStatsRead(initialBackblazeStats, this.backblazeLogsStats, true);
311
+
312
+ results.totalSearchTime = Date.now() - startTime;
313
+ };
314
+
315
+ const onResultsCallback = config.onResults;
316
+ if (onResultsCallback) {
317
+ interval = setInterval(async () => {
318
+ updateResultsStats();
319
+ let shouldContinue = await onResultsCallback(deepCloneJSON(results));
320
+ if (!shouldContinue) {
321
+ if (!results.cancel) {
322
+ console.log(blue(`Cancelled search on ${this.config.name}`));
323
+ }
324
+ results.cancel = true;
325
+ }
326
+ }, timeInSecond);
327
+ }
328
+
329
+ try {
330
+ await this.findBase({
331
+ params: config.params,
332
+ onResult: config.onResult,
333
+ results,
334
+ });
335
+ updateResultsStats();
336
+ return results;
337
+ } finally {
338
+ if (interval) {
339
+ clearInterval(interval);
340
+ }
341
+ }
342
+ }
343
+
344
+ private async findBase(config: {
345
+ params: SearchParams;
346
+ onResult: (match: T) => void;
347
+ results: IndexedLogResults;
348
+ }): Promise<void> {
339
349
  let startTime = Date.now();
340
350
  let localLogs = this.getLocalLogs();
341
351
  let backblazeLogs = this.getPublicLogs();
@@ -347,54 +357,56 @@ export class IndexedLogs<T> {
347
357
  only: config.params.only,
348
358
  });
349
359
  paths = paths.filter(x => x.sourceName === this.config.name);
360
+ for (let path of paths) {
361
+ this.fileSizeCache.set(path.fullPath, path.size);
362
+ this.fileSizeCache.set(path.fullPath + INDEX_EXTENSION, path.indexSize);
363
+ }
350
364
  fileFindTime = Date.now() - fileFindTime;
351
365
 
352
366
  // Newest first
353
367
  sort(paths, x => - x.startTime);
354
368
 
369
+ let results = config.results;
355
370
  let localPaths = paths.filter(x => x.logCount === undefined);
356
- let backblazePaths = paths.filter(x => x.logCount !== undefined);
357
-
358
- let initialLocalStats = deepCloneJSON(this.localLogsStats);
359
- let initialBackblazeStats = deepCloneJSON(this.backblazeLogsStats);
360
- let stats = {
361
- blockCheckedCount: 0,
362
- blocksCheckedCompressedSize: 0,
363
- blocksCheckedDecompressedSize: 0,
364
- totalBlockCount: 0,
365
- indexSearchTime: 0,
366
- blockSearchTime: 0,
367
- indexesSearched: 0,
368
- indexSize: 0,
369
- matchCount: 0,
370
- timeToFirstMatch: -1,
371
- };
372
- let fileErrors: string[] = [];
373
- let blockErrors: string[] = [];
371
+ let remotePaths = paths.filter(x => x.logCount !== undefined);
372
+ results.totalLocalFiles = localPaths.length;
373
+ results.totalBackblazeFiles = remotePaths.length;
374
+ results.fileFindTime = fileFindTime;
374
375
 
375
376
  let progressTracker = new FindProgressTracker<T>({
376
377
  params: config.params,
377
378
  deserialize: (buffer: Buffer) => LogStreamer.deserialize<T>(buffer),
378
379
  getTime: this.config.getTime,
379
380
  onResult: (match: T) => {
380
- if (stats.timeToFirstMatch < 0) {
381
- stats.timeToFirstMatch = Date.now() - startTime;
381
+ if (results.timeToFirstMatch < 0) {
382
+ results.timeToFirstMatch = Date.now() - startTime;
382
383
  }
383
- stats.matchCount++;
384
+ results.matchCount++;
384
385
  config.onResult(match);
385
386
  },
386
387
  });
387
388
 
388
389
 
389
- for (let path of paths) {
390
- if (!progressTracker.isSourceRelevant(path)) continue;
390
+ const searchPath = async (path: TimeFilePathWithSize) => {
391
+ if (!progressTracker.isSourceRelevant(path)) return;
392
+ // Wait, so we don't lock up the main thread?
393
+ await delay(0);
391
394
 
392
- let archives = path.logCount !== undefined ? backblazeLogs : localLogs;
395
+ let remote = path.logCount !== undefined;
396
+ let archives = remote ? backblazeLogs : localLogs;
397
+ if (remote) {
398
+ results.backblazeFilesSearched++;
399
+ } else {
400
+ results.localFilesSearched++;
401
+ }
393
402
  try {
394
403
  let readIndexTime = Date.now();
395
- let index = await archives.get(this.getIndexPath(path.fullPath)) || Buffer.alloc(0);
404
+ let index = await archives.get(
405
+ path.fullPath + INDEX_EXTENSION,
406
+ { range: { start: 0, end: path.indexSize } }
407
+ ) || Buffer.alloc(0);
396
408
  readIndexTime = Date.now() - readIndexTime;
397
- stats.indexSearchTime += readIndexTime;
409
+ results.indexSearchTime += readIndexTime;
398
410
  let dataReader: Reader = {
399
411
  getLength: async () => path.size,
400
412
  read: async (offset, length) => {
@@ -406,76 +418,44 @@ export class IndexedLogs<T> {
406
418
  },
407
419
  };
408
420
  let totalSearchTime = Date.now();
409
- let findObj = await BufferIndex.find({
421
+ let blockSearchTimeBefore = results.blockSearchTime;
422
+ await BufferIndex.find({
410
423
  index,
411
424
  dataReader,
412
425
  params: {
413
426
  ...config.params,
414
- limit: config.params.limit - stats.matchCount,
427
+ limit: config.params.limit - results.matchCount,
415
428
  },
416
- keepIterating: () => progressTracker.isSourceRelevant(path),
429
+ keepIterating: () => !results.cancel && progressTracker.isSourceRelevant(path),
417
430
  onResult: (match: Buffer) => {
418
- if (stats.matchCount >= config.params.limit) return;
431
+ if (results.matchCount >= config.params.limit) return;
419
432
  progressTracker.addResult(match, path);
420
433
  },
434
+ results,
421
435
  });
422
- blockErrors.push(...findObj.blocksWithErrors);
423
436
  totalSearchTime = Date.now() - totalSearchTime;
424
- stats.indexSearchTime += totalSearchTime - findObj.blockSearchTime;
425
- stats.blockSearchTime += findObj.blockSearchTime;
426
- stats.indexesSearched += findObj.indexCount;
427
- stats.indexSize += findObj.indexSize;
428
- stats.blockCheckedCount += findObj.blocksChecked;
429
- stats.blocksCheckedCompressedSize += findObj.blocksCheckedCompressedSize;
430
- stats.blocksCheckedDecompressedSize += findObj.blocksCheckedDecompressedSize;
431
- stats.totalBlockCount += findObj.totalBlockCount;
432
-
433
- if (stats.matchCount >= config.params.limit) break;
437
+ let blockSearchTimeAdded = results.blockSearchTime - blockSearchTimeBefore;
438
+ results.indexSearchTime += totalSearchTime - blockSearchTimeAdded;
439
+
434
440
  } catch (e: any) {
435
- fileErrors.push(String(e?.stack || e));
441
+ results.fileErrors.push({ error: String(e?.stack || e), path: path.fullPath });
436
442
  console.warn(`Error in reading log file ${path.fullPath} logs: ${e}`);
437
443
  }
438
- }
439
-
440
- if (stats.timeToFirstMatch < 0) {
441
- stats.timeToFirstMatch = Date.now() - startTime;
442
- }
443
-
444
-
445
- let reads: { cached: boolean; remote: boolean; count: number; size: number; totalSize: number; totalCount: number; }[] = [];
446
- function addStatsRead(initial: ArchivesMemoryCacheStats, final: ArchivesMemoryCacheStats, remote: boolean) {
447
- reads.push({
448
- cached: false,
449
- remote,
450
- count: final.uncachedReads - initial.uncachedReads,
451
- size: final.uncachedReadSize - initial.uncachedReadSize,
452
- totalSize: final.totalCacheSize,
453
- totalCount: final.totalCacheCount,
454
- });
455
- reads.push({
456
- cached: true,
457
- remote,
458
- count: final.cachedReads - initial.cachedReads,
459
- size: final.cachedReadSize - initial.cachedReadSize,
460
- totalSize: final.totalCacheSize,
461
- totalCount: final.totalCacheCount,
462
- });
463
- }
464
-
465
- addStatsRead(initialLocalStats, this.localLogsStats, false);
466
- addStatsRead(initialBackblazeStats, this.backblazeLogsStats, true);
467
-
468
-
469
- return {
470
- reads,
471
- localFilesSearched: localPaths.length,
472
- backblazeFilesSearched: backblazePaths.length,
473
- fileFindTime,
474
- fileErrors,
475
- blockErrors,
476
- totalSearchTime: Date.now() - startTime,
477
- ...stats,
478
444
  };
445
+ let localDone = (async () => {
446
+ for (let path of localPaths) {
447
+ await searchPath(path);
448
+ }
449
+ })();
450
+ // No parallel count when running locally, as running it in parallel makes timing more difficult.
451
+ let parallelCount = isPublic() ? 32 : 1;
452
+ let remoteParallel = runInParallel({ parallelCount }, searchPath);
453
+ await Promise.all(remotePaths.map(remoteParallel));
454
+ await localDone;
455
+
456
+ if (results.timeToFirstMatch < 0) {
457
+ results.timeToFirstMatch = Date.now() - startTime;
458
+ }
479
459
  }
480
460
 
481
461
  public async moveLogsToPublic(forceAll = false) {
@@ -490,11 +470,85 @@ export class IndexedLogs<T> {
490
470
  publicMoveThreshold: PUBLIC_MOVE_THRESHOLD,
491
471
  maxSingleFileData: this.config.maxSingleFileData || MAX_SINGLE_FILE_DATA,
492
472
  movingTimeout: MOVING_TIMEOUT,
493
- getIndexPath: (path: string) => this.getIndexPath(path),
473
+ indexExtension: INDEX_EXTENSION,
494
474
  });
495
475
  }
496
476
 
497
477
 
478
+ // #region Client Calls
479
+ private findCallbacks = new Map<string, (match: T) => void>();
480
+ private resultsCallbacks = new Map<string, (results: IndexedLogResults) => void>();
481
+ public async clientFind(config: {
482
+ params: SearchParams;
483
+ onResult: (match: T) => void;
484
+ onResults?: (results: IndexedLogResults) => void;
485
+ }): Promise<IndexedLogResults> {
486
+ let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
487
+ let findId = nextId();
488
+ let callback = (match: T) => {
489
+ config.onResult(match);
490
+ };
491
+ this.findCallbacks.set(findId, callback);
492
+ if (config.onResults) {
493
+ this.resultsCallbacks.set(findId, config.onResults);
494
+ }
495
+ try {
496
+ return await controller.find({
497
+ findId,
498
+ indexedLogsName: this.config.name,
499
+ params: config.params,
500
+ });
501
+ } finally {
502
+ // There's some trailing time after the controller call finishes when the results will be trickling back to us.
503
+ setTimeout(() => {
504
+ this.findCallbacks.delete(findId);
505
+ this.resultsCallbacks.delete(findId);
506
+ }, timeInMinute * 30);
507
+ }
508
+ }
509
+ public clientCancelAllCallbacks() {
510
+ this.findCallbacks.clear();
511
+ this.resultsCallbacks.clear();
512
+ }
513
+ public async clientGetPaths(config: {
514
+ startTime: number;
515
+ endTime: number;
516
+ only?: "local" | "public";
517
+ }): Promise<TimeFilePathWithSize[]> {
518
+ let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
519
+ return await controller.getPaths({
520
+ indexedLogsName: this.config.name,
521
+ startTime: config.startTime,
522
+ endTime: config.endTime,
523
+ only: config.only,
524
+ });
525
+ }
526
+ public onFindResult(config: {
527
+ findId: string;
528
+ result: unknown;
529
+ }) {
530
+ let callback = this.findCallbacks.get(config.findId);
531
+ if (!callback) throw new Error(`Find callback ${config.findId} not found`);
532
+ callback(config.result as T);
533
+ }
534
+ public async onResults(config: {
535
+ findId: string;
536
+ results: IndexedLogResults;
537
+ }): Promise<boolean> {
538
+ let callback = this.resultsCallbacks.get(config.findId);
539
+ if (!callback) throw new Error(`Results callback ${config.findId} not found`);
540
+ callback(config.results);
541
+ return true;
542
+ }
543
+
544
+ public async clientForceMoveLogsToPublic() {
545
+ let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
546
+ await controller.forceMoveLogsToPublic({
547
+ indexedLogsName: this.config.name,
548
+ });
549
+ }
550
+ // #endregion
551
+
498
552
 
499
553
  // TEST functions
500
554
  public async TEST_flushNow() {
@@ -529,13 +583,26 @@ class IndexedLogClient {
529
583
  result: unknown;
530
584
  }) {
531
585
  if (isNode()) return;
532
- let indexedLogs = logByName.get(config.indexedLogsName);
586
+ let indexedLogs = loggerByName.get(config.indexedLogsName);
533
587
  if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
534
588
  indexedLogs.onFindResult({
535
589
  findId: config.findId,
536
590
  result: config.result,
537
591
  });
538
592
  }
593
+ public async onResults(config: {
594
+ findId: string;
595
+ indexedLogsName: string;
596
+ results: IndexedLogResults;
597
+ }): Promise<boolean> {
598
+ if (isNode()) return true;
599
+ let indexedLogs = loggerByName.get(config.indexedLogsName);
600
+ if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
601
+ return await indexedLogs.onResults({
602
+ findId: config.findId,
603
+ results: config.results,
604
+ });
605
+ }
539
606
  }
540
607
  class IndexedLogShim {
541
608
  public async find(config: {
@@ -544,8 +611,9 @@ class IndexedLogShim {
544
611
  params: SearchParams;
545
612
  }): Promise<IndexedLogResults> {
546
613
  let caller = SocketFunction.getCaller();
547
- let indexedLogs = logByName.get(config.indexedLogsName);
614
+ let indexedLogs = loggerByName.get(config.indexedLogsName);
548
615
  if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
616
+
549
617
  return indexedLogs.find({
550
618
  params: config.params,
551
619
  onResult: (match: unknown) => {
@@ -555,6 +623,18 @@ class IndexedLogShim {
555
623
  result: match,
556
624
  }));
557
625
  },
626
+ onResults: async (results: IndexedLogResults) => {
627
+ try {
628
+ await IndexedLogClientController.nodes[caller.nodeId].onResults({
629
+ findId: config.findId,
630
+ indexedLogsName: config.indexedLogsName,
631
+ results: results,
632
+ });
633
+ return true;
634
+ } catch (e) {
635
+ return false;
636
+ }
637
+ },
558
638
  });
559
639
  }
560
640
  public async getPaths(config: {
@@ -563,7 +643,7 @@ class IndexedLogShim {
563
643
  endTime: number;
564
644
  only?: "local" | "public";
565
645
  }): Promise<TimeFilePathWithSize[]> {
566
- let indexedLogs = logByName.get(config.indexedLogsName);
646
+ let indexedLogs = loggerByName.get(config.indexedLogsName);
567
647
  if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
568
648
  return indexedLogs.getPaths({
569
649
  startTime: config.startTime,
@@ -571,6 +651,14 @@ class IndexedLogShim {
571
651
  only: config.only,
572
652
  });
573
653
  }
654
+
655
+ public async forceMoveLogsToPublic(config: {
656
+ indexedLogsName: string;
657
+ }) {
658
+ let indexedLogs = loggerByName.get(config.indexedLogsName);
659
+ if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
660
+ await indexedLogs.moveLogsToPublic(true);
661
+ }
574
662
  }
575
663
 
576
664
  const IndexedLogShimController = SocketFunction.register(
@@ -583,7 +671,7 @@ const IndexedLogShimController = SocketFunction.register(
583
671
  getPaths: {
584
672
  hooks: [assertIsManagementUser]
585
673
  },
586
- clientForceMoveLogsToPublic: {
674
+ forceMoveLogsToPublic: {
587
675
  hooks: [assertIsManagementUser]
588
676
  }
589
677
  })
@@ -593,6 +681,7 @@ const IndexedLogClientController = SocketFunction.register(
593
681
  "IndexedLogClient-019c87b9-1c6d-72ed-8bc9-d52451e2c1b9",
594
682
  new IndexedLogClient(),
595
683
  () => ({
596
- onFind: {}
684
+ onFind: {},
685
+ onResults: {}
597
686
  })
598
687
  );