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.
- package/.cursorrules +1 -0
- package/package.json +2 -1
- package/src/-a-archives/archivesDisk.ts +24 -6
- package/src/-a-archives/archivesMemoryCache.ts +41 -17
- package/src/deployManager/components/MachineDetailPage.tsx +45 -4
- package/src/deployManager/components/MachinesListPage.tsx +10 -2
- package/src/deployManager/components/ServiceDetailPage.tsx +13 -3
- package/src/deployManager/components/ServicesListPage.tsx +18 -6
- package/src/deployManager/machineApplyMainCode.ts +3 -3
- package/src/deployManager/machineSchema.ts +39 -0
- package/src/diagnostics/NodeViewer.tsx +2 -1
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +124 -123
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +83 -1
- package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +2 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +21 -24
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +1 -1
- package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +186 -25
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +284 -195
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +312 -108
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +1 -1
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +37 -7
- package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +62 -35
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +0 -180
- 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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
168
|
-
|
|
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
|
-
//
|
|
172
|
-
extraReadSize = 1024 * 1024 *
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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 (
|
|
381
|
-
|
|
381
|
+
if (results.timeToFirstMatch < 0) {
|
|
382
|
+
results.timeToFirstMatch = Date.now() - startTime;
|
|
382
383
|
}
|
|
383
|
-
|
|
384
|
+
results.matchCount++;
|
|
384
385
|
config.onResult(match);
|
|
385
386
|
},
|
|
386
387
|
});
|
|
387
388
|
|
|
388
389
|
|
|
389
|
-
|
|
390
|
-
if (!progressTracker.isSourceRelevant(path))
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 -
|
|
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 (
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
);
|