querysub 0.356.0 → 0.358.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 +9 -0
- package/bin/movelogs.js +4 -0
- package/package.json +13 -6
- package/scripts/postinstall.js +23 -0
- package/src/-a-archives/archiveCache.ts +10 -12
- package/src/-a-archives/archives.ts +29 -0
- package/src/-a-archives/archivesBackBlaze.ts +60 -12
- package/src/-a-archives/archivesDisk.ts +39 -13
- package/src/-a-archives/archivesLimitedCache.ts +21 -0
- package/src/-a-archives/archivesMemoryCache.ts +374 -0
- package/src/-a-archives/archivesPrivateFileSystem.ts +22 -0
- package/src/-g-core-values/NodeCapabilities.ts +3 -0
- package/src/0-path-value-core/auditLogs.ts +5 -1
- package/src/0-path-value-core/pathValueCore.ts +7 -7
- package/src/4-dom/qreact.tsx +1 -0
- package/src/4-querysub/Querysub.ts +1 -5
- package/src/config.ts +5 -0
- package/src/deployManager/components/MachineDetailPage.tsx +43 -2
- package/src/deployManager/components/MachinesListPage.tsx +10 -2
- package/src/deployManager/machineApplyMainCode.ts +3 -3
- package/src/deployManager/machineSchema.ts +39 -0
- package/src/diagnostics/MachineThreadInfo.tsx +235 -0
- package/src/diagnostics/NodeViewer.tsx +5 -3
- package/src/diagnostics/logs/FastArchiveAppendable.ts +79 -42
- package/src/diagnostics/logs/FastArchiveController.ts +102 -63
- package/src/diagnostics/logs/FastArchiveViewer.tsx +36 -8
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +462 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.cpp +327 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.d.ts +18 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.js +1 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +222 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexLogsOptimizationConstants.ts +22 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat +1145 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat.d.ts +178 -0
- package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +208 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +716 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +146 -0
- package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +569 -0
- package/src/diagnostics/logs/IndexedLogs/FindProgressTracker.ts +45 -0
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +685 -0
- package/src/diagnostics/logs/IndexedLogs/LogStreamer.ts +47 -0
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +901 -0
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +236 -0
- package/src/diagnostics/logs/IndexedLogs/binding.gyp +23 -0
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +251 -0
- package/src/diagnostics/logs/IndexedLogs/moveLogsEntry.ts +10 -0
- package/src/diagnostics/logs/LogViewer2.tsx +120 -55
- package/src/diagnostics/logs/TimeRangeSelector.tsx +5 -2
- package/src/diagnostics/logs/diskLogger.ts +32 -48
- package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +3 -2
- package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +1 -0
- package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +150 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +150 -15
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat +106 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat.d.ts +2 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/testHoist.ts +5 -0
- package/src/diagnostics/logs/logViewerExtractField.ts +2 -3
- package/src/diagnostics/managementPages.tsx +10 -0
- package/src/diagnostics/trackResources.ts +1 -1
- package/src/functional/limitProcessing.ts +39 -0
- package/src/misc/lz4_wasm_nodejs.d.ts +34 -0
- package/src/misc/lz4_wasm_nodejs.js +178 -0
- package/src/misc/lz4_wasm_nodejs_bg.js +94 -0
- package/src/misc/lz4_wasm_nodejs_bg.wasm +0 -0
- package/src/misc/lz4_wasm_nodejs_bg.wasm.d.ts +15 -0
- package/src/storage/CompressedStream.ts +13 -0
- package/src/storage/LZ4.ts +32 -0
- package/src/storage/ZSTD.ts +10 -0
- package/src/wat/watCompiler.ts +1716 -0
- package/src/wat/watGrammar.pegjs +93 -0
- package/src/wat/watHandler.ts +179 -0
- package/src/wat/watInstructions.txt +707 -0
- package/src/zip.ts +3 -89
- package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +0 -125
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import { lazy } from "socket-function/src/caching";
|
|
2
|
+
import { Archives, nestArchives } from "../../../-a-archives/archives";
|
|
3
|
+
import { deepCloneJSON, keyByArray, nextId, sort, timeInHour, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
4
|
+
import { BufferIndex } from "./BufferIndex";
|
|
5
|
+
import { delay, runInParallel, runInSerial, runInfinitePoll } from "socket-function/src/batching";
|
|
6
|
+
import { IndexedLogResults, Reader, SearchParams, addReadToResults, createEmptyIndexedLogResults, INDEX_EXTENSION } from "./BufferIndexHelpers";
|
|
7
|
+
import { getDomain, isPublic } from "../../../config";
|
|
8
|
+
import { getArchivesLocal } from "../../../-a-archives/archivesDisk";
|
|
9
|
+
import { getArchivesBackblaze, getArchivesBackblazePrivateImmutable } from "../../../-a-archives/archivesBackBlaze";
|
|
10
|
+
import { getOwnThreadId } from "../../../-a-auth/certs";
|
|
11
|
+
import { ArchivesMemoryCacheStats, createArchivesMemoryCache } from "../../../-a-archives/archivesMemoryCache";
|
|
12
|
+
import { registerShutdownHandler } from "../../periodic";
|
|
13
|
+
import { measureBlock, measureFnc } from "socket-function/src/profiling/measure";
|
|
14
|
+
import { isNode } from "typesafecss";
|
|
15
|
+
import { getOwnMachineId } from "../../../-f-node-discovery/NodeDiscovery";
|
|
16
|
+
import { TimeFilePath, TimeFileTree } from "./TimeFileTree";
|
|
17
|
+
import { LogStreamer } from "./LogStreamer";
|
|
18
|
+
import { moveLogsToPublic } from "./moveIndexLogsToPublic";
|
|
19
|
+
import { MAX_SINGLE_FILE_DATA, MAX_COUNT_PER_FILE, DISK_FLUSH_INTERVAL, PUBLIC_MOVE_THRESHOLD, MOVING_TIMEOUT } from "./BufferIndexLogsOptimizationConstants";
|
|
20
|
+
import { FindProgressTracker } from "./FindProgressTracker";
|
|
21
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
22
|
+
import { assertIsManagementUser } from "../../managementPages";
|
|
23
|
+
import { ignoreErrors } from "../../../errors";
|
|
24
|
+
import { blue } from "socket-function/src/formatting/logColors";
|
|
25
|
+
import { LimitGroup } from "../../../functional/limitProcessing";
|
|
26
|
+
|
|
27
|
+
export type TimeFilePathWithSize = TimeFilePath & {
|
|
28
|
+
size: number;
|
|
29
|
+
indexSize: number;
|
|
30
|
+
sourceName: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let loggerByName = new Map<string, IndexedLogs<unknown>>();
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export class IndexedLogs<T> {
|
|
37
|
+
public constructor(public config: {
|
|
38
|
+
name: string,
|
|
39
|
+
maxSingleFileData?: number;
|
|
40
|
+
maxCountPerFile?: number;
|
|
41
|
+
forceUsePublicLogs?: boolean;
|
|
42
|
+
getTime: (result: T) => number | undefined;
|
|
43
|
+
}) {
|
|
44
|
+
loggerByName.set(this.config.name, this as any);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
private static shouldRunLoop = false;
|
|
49
|
+
public static runLogMoveLoop() {
|
|
50
|
+
IndexedLogs.shouldRunLoop = true;
|
|
51
|
+
for (let indexedLogs of loggerByName.values()) {
|
|
52
|
+
indexedLogs.runLogMoverLoop();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private findCallbacks = new Map<string, (match: T) => void>();
|
|
57
|
+
private resultsCallbacks = new Map<string, (results: IndexedLogResults) => void>();
|
|
58
|
+
public async clientFind(config: {
|
|
59
|
+
params: SearchParams;
|
|
60
|
+
onResult: (match: T) => void;
|
|
61
|
+
onResults?: (results: IndexedLogResults) => void;
|
|
62
|
+
}): Promise<IndexedLogResults> {
|
|
63
|
+
let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
|
|
64
|
+
let findId = nextId();
|
|
65
|
+
let callback = (match: T) => {
|
|
66
|
+
config.onResult(match);
|
|
67
|
+
};
|
|
68
|
+
this.findCallbacks.set(findId, callback);
|
|
69
|
+
if (config.onResults) {
|
|
70
|
+
this.resultsCallbacks.set(findId, config.onResults);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return await controller.find({
|
|
74
|
+
findId,
|
|
75
|
+
indexedLogsName: this.config.name,
|
|
76
|
+
params: config.params,
|
|
77
|
+
});
|
|
78
|
+
} finally {
|
|
79
|
+
// There's some trailing time after the controller call finishes when the results will be trickling back to us.
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
this.findCallbacks.delete(findId);
|
|
82
|
+
this.resultsCallbacks.delete(findId);
|
|
83
|
+
}, timeInMinute * 30);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
public clientCancelAllCallbacks() {
|
|
87
|
+
this.findCallbacks.clear();
|
|
88
|
+
this.resultsCallbacks.clear();
|
|
89
|
+
}
|
|
90
|
+
public async clientGetPaths(config: {
|
|
91
|
+
startTime: number;
|
|
92
|
+
endTime: number;
|
|
93
|
+
only?: "local" | "public";
|
|
94
|
+
}): Promise<TimeFilePathWithSize[]> {
|
|
95
|
+
let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
|
|
96
|
+
return await controller.getPaths({
|
|
97
|
+
indexedLogsName: this.config.name,
|
|
98
|
+
startTime: config.startTime,
|
|
99
|
+
endTime: config.endTime,
|
|
100
|
+
only: config.only,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
public onFindResult(config: {
|
|
104
|
+
findId: string;
|
|
105
|
+
result: unknown;
|
|
106
|
+
}) {
|
|
107
|
+
let callback = this.findCallbacks.get(config.findId);
|
|
108
|
+
if (!callback) throw new Error(`Find callback ${config.findId} not found`);
|
|
109
|
+
callback(config.result as T);
|
|
110
|
+
}
|
|
111
|
+
public async onResults(config: {
|
|
112
|
+
findId: string;
|
|
113
|
+
results: IndexedLogResults;
|
|
114
|
+
}): Promise<boolean> {
|
|
115
|
+
let callback = this.resultsCallbacks.get(config.findId);
|
|
116
|
+
if (!callback) throw new Error(`Results callback ${config.findId} not found`);
|
|
117
|
+
callback(config.results);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public async clientForceMoveLogsToPublic() {
|
|
122
|
+
let controller = IndexedLogShimController.nodes[SocketFunction.getBrowserNodeId()];
|
|
123
|
+
await controller.forceMoveLogsToPublic({
|
|
124
|
+
indexedLogsName: this.config.name,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private localLogsStats = {
|
|
129
|
+
cachedReads: 0,
|
|
130
|
+
uncachedReads: 0,
|
|
131
|
+
cachedReadSize: 0,
|
|
132
|
+
uncachedReadSize: 0,
|
|
133
|
+
totalCacheSize: 0,
|
|
134
|
+
totalCacheCount: 0,
|
|
135
|
+
};
|
|
136
|
+
private backblazeLogsStats = {
|
|
137
|
+
cachedReads: 0,
|
|
138
|
+
uncachedReads: 0,
|
|
139
|
+
cachedReadSize: 0,
|
|
140
|
+
uncachedReadSize: 0,
|
|
141
|
+
totalCacheSize: 0,
|
|
142
|
+
totalCacheCount: 0,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
private fileSizeCache = new Map<string, number>();
|
|
146
|
+
|
|
147
|
+
private getLocalLogs = lazy((): Archives => {
|
|
148
|
+
let baseDisk = getArchivesLocal(getDomain());
|
|
149
|
+
let archives = nestArchives("indexed-logs/" + this.config.name, baseDisk);
|
|
150
|
+
archives = createArchivesMemoryCache(archives, {
|
|
151
|
+
maxSize: 1024 * 1024 * 512,
|
|
152
|
+
maxCount: 1000 * 100,
|
|
153
|
+
stats: this.localLogsStats,
|
|
154
|
+
sizeCache: this.fileSizeCache,
|
|
155
|
+
// Local disk reads are fast, but even local reads can have high latency
|
|
156
|
+
extraReadSize: 1024 * 1024 * 1,
|
|
157
|
+
});
|
|
158
|
+
return archives;
|
|
159
|
+
});
|
|
160
|
+
private getPublicLogs = lazy((): Archives => {
|
|
161
|
+
let basePublic: Archives = getArchivesLocal(getDomain());
|
|
162
|
+
// 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.
|
|
163
|
+
let extraReadSize = 1024 * 1024 * 10;
|
|
164
|
+
if (this.config.forceUsePublicLogs || isPublic()) {
|
|
165
|
+
basePublic = getArchivesBackblaze(getDomain());
|
|
166
|
+
// 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.
|
|
167
|
+
extraReadSize = 1024 * 1024 * 1;
|
|
168
|
+
}
|
|
169
|
+
let archives = nestArchives("final-indexed-logs/" + this.config.name, basePublic);
|
|
170
|
+
archives = createArchivesMemoryCache(archives, {
|
|
171
|
+
maxSize: 1024 * 1024 * 1024 * 2,
|
|
172
|
+
maxCount: 1000 * 500,
|
|
173
|
+
fullyImmutable: true,
|
|
174
|
+
extraReadSize,
|
|
175
|
+
stats: this.backblazeLogsStats,
|
|
176
|
+
sizeCache: this.fileSizeCache,
|
|
177
|
+
});
|
|
178
|
+
return archives;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
private getTimeBlock(time: number): { startTime: number, endTime: number } {
|
|
182
|
+
let startTime = Math.floor(time / timeInHour) * timeInHour;
|
|
183
|
+
let endTime = startTime + timeInHour;
|
|
184
|
+
return { startTime, endTime };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private runLogMoverLoop = lazy(() => {
|
|
188
|
+
runInfinitePoll(timeInMinute * 5, async () => {
|
|
189
|
+
await this.moveLogsToPublic();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
private currentLogStream: LogStreamer<T> | undefined;
|
|
194
|
+
private forceFlushStream: (() => Promise<void>) | undefined;
|
|
195
|
+
|
|
196
|
+
private getCurrentLogStream(): LogStreamer<T> {
|
|
197
|
+
|
|
198
|
+
if (!this.currentLogStream) {
|
|
199
|
+
let { startTime, endTime } = this.getTimeBlock(Date.now());
|
|
200
|
+
let path = new TimeFileTree(this.getLocalLogs()).getNewPendingPath({ startTime, endTime });
|
|
201
|
+
let streamer = BufferIndex.createStreamer();
|
|
202
|
+
let currentLogStreamSize = 0;
|
|
203
|
+
let currentLogStreamCount = 0;
|
|
204
|
+
let newStreamer = async () => {
|
|
205
|
+
let result = streamer.close();
|
|
206
|
+
await this.getLocalLogs().append(path, result.data);
|
|
207
|
+
await this.getLocalLogs().append(path + INDEX_EXTENSION, result.index);
|
|
208
|
+
|
|
209
|
+
path = new TimeFileTree(this.getLocalLogs()).getNewPendingPath({ startTime, endTime });
|
|
210
|
+
streamer = BufferIndex.createStreamer();
|
|
211
|
+
currentLogStreamSize = 0;
|
|
212
|
+
currentLogStreamCount = 0;
|
|
213
|
+
};
|
|
214
|
+
this.forceFlushStream = async () => {
|
|
215
|
+
// FORCE the stream to finish and write to disk, by making it too large
|
|
216
|
+
currentLogStreamSize = Number.MAX_SAFE_INTEGER;
|
|
217
|
+
currentLogStreamCount = Number.MAX_SAFE_INTEGER;
|
|
218
|
+
await this.currentLogStream?.flushNow?.();
|
|
219
|
+
|
|
220
|
+
};
|
|
221
|
+
let writeBuffers = async (buffers: Buffer[]) => {
|
|
222
|
+
if (Date.now() > endTime) {
|
|
223
|
+
let timeBlockObj = this.getTimeBlock(Date.now());
|
|
224
|
+
startTime = timeBlockObj.startTime;
|
|
225
|
+
endTime = timeBlockObj.endTime;
|
|
226
|
+
path = new TimeFileTree(this.getLocalLogs()).getNewPendingPath(timeBlockObj);
|
|
227
|
+
await newStreamer();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let maxSize = this.config.maxSingleFileData || MAX_SINGLE_FILE_DATA;
|
|
231
|
+
let maxCount = this.config.maxCountPerFile || MAX_COUNT_PER_FILE;
|
|
232
|
+
|
|
233
|
+
let bufferGroups = this.splitBuffersIntoGroups(buffers, maxSize, maxCount, currentLogStreamSize, currentLogStreamCount);
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < bufferGroups.length; i++) {
|
|
236
|
+
let group = bufferGroups[i];
|
|
237
|
+
|
|
238
|
+
if (i > 0) {
|
|
239
|
+
await newStreamer();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let result = streamer.add(group);
|
|
243
|
+
await this.getLocalLogs().append(path, result.data);
|
|
244
|
+
if (result.index) {
|
|
245
|
+
await this.getLocalLogs().append(path + INDEX_EXTENSION, result.index);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
currentLogStreamSize += result.data.length;
|
|
249
|
+
currentLogStreamCount += group.length;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
this.currentLogStream = new LogStreamer<T>(DISK_FLUSH_INTERVAL, writeBuffers);
|
|
254
|
+
}
|
|
255
|
+
return this.currentLogStream;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private splitBuffersIntoGroups(buffers: Buffer[], maxSize: number, maxCount: number, currentLogStreamSize: number, currentLogStreamCount: number): Buffer[][] {
|
|
259
|
+
let groups: Buffer[][] = [];
|
|
260
|
+
let currentGroup: Buffer[] = [];
|
|
261
|
+
let currentGroupSize = 0;
|
|
262
|
+
let remainingSize = maxSize - currentLogStreamSize;
|
|
263
|
+
let remainingCount = maxCount - currentLogStreamCount;
|
|
264
|
+
|
|
265
|
+
for (let buffer of buffers) {
|
|
266
|
+
if (
|
|
267
|
+
// Allow empty at the start, so we create a new streamer
|
|
268
|
+
(currentGroup.length > 0 || groups.length === 0)
|
|
269
|
+
&&
|
|
270
|
+
(currentGroupSize + buffer.length > remainingSize || currentGroup.length >= remainingCount)
|
|
271
|
+
) {
|
|
272
|
+
groups.push(currentGroup);
|
|
273
|
+
currentGroup = [];
|
|
274
|
+
currentGroupSize = 0;
|
|
275
|
+
remainingSize = maxSize;
|
|
276
|
+
remainingCount = maxCount;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
currentGroup.push(buffer);
|
|
280
|
+
currentGroupSize += buffer.length;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (currentGroup.length > 0) {
|
|
284
|
+
groups.push(currentGroup);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return groups;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
public append(datum: T) {
|
|
291
|
+
this.getCurrentLogStream().append(datum);
|
|
292
|
+
if (IndexedLogs.shouldRunLoop) {
|
|
293
|
+
this.runLogMoverLoop();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@measureFnc
|
|
298
|
+
public async getPaths(config: {
|
|
299
|
+
startTime: number;
|
|
300
|
+
endTime: number;
|
|
301
|
+
only?: "local" | "public";
|
|
302
|
+
}): Promise<TimeFilePathWithSize[]> {
|
|
303
|
+
let localLogs = this.getLocalLogs();
|
|
304
|
+
let backblazeLogs = this.getPublicLogs();
|
|
305
|
+
|
|
306
|
+
let paths: TimeFilePath[] = [];
|
|
307
|
+
|
|
308
|
+
if (config.only !== "public") {
|
|
309
|
+
let localPaths = await new TimeFileTree(localLogs).findAllPaths({
|
|
310
|
+
startTime: config.startTime,
|
|
311
|
+
endTime: config.endTime,
|
|
312
|
+
});
|
|
313
|
+
paths.push(...localPaths);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (config.only !== "local") {
|
|
317
|
+
let backblazePaths = await new TimeFileTree(backblazeLogs).findAllPaths({
|
|
318
|
+
startTime: config.startTime,
|
|
319
|
+
endTime: config.endTime,
|
|
320
|
+
});
|
|
321
|
+
paths.push(...backblazePaths);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Bake in the size to ensure more cache hits (without this, every single access will always result in a file/remote call).
|
|
325
|
+
let pathsWithSize = await Promise.all(paths.map(async (path): Promise<TimeFilePathWithSize> => {
|
|
326
|
+
let archives = path.logCount !== undefined ? backblazeLogs : localLogs;
|
|
327
|
+
let info = await archives.getInfo(path.fullPath);
|
|
328
|
+
let size = info?.size || 0;
|
|
329
|
+
this.fileSizeCache.set(path.fullPath, size);
|
|
330
|
+
let indexPath = path.fullPath + INDEX_EXTENSION;
|
|
331
|
+
let indexInfo = await archives.getInfo(indexPath);
|
|
332
|
+
let indexSize = indexInfo?.size || 0;
|
|
333
|
+
this.fileSizeCache.set(indexPath, indexSize);
|
|
334
|
+
return { ...path, size, indexSize, sourceName: this.config.name };
|
|
335
|
+
}));
|
|
336
|
+
|
|
337
|
+
return pathsWithSize;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@measureFnc
|
|
341
|
+
public async find(config: {
|
|
342
|
+
params: SearchParams;
|
|
343
|
+
onResult: (match: T) => void;
|
|
344
|
+
onResults?: (results: IndexedLogResults) => Promise<boolean>;
|
|
345
|
+
}): Promise<IndexedLogResults> {
|
|
346
|
+
let startTime = Date.now();
|
|
347
|
+
let interval: NodeJS.Timeout | undefined;
|
|
348
|
+
|
|
349
|
+
let results: IndexedLogResults = createEmptyIndexedLogResults();
|
|
350
|
+
results.limitGroup = new LimitGroup({
|
|
351
|
+
maxTimePerBeforeWait: 500,
|
|
352
|
+
waitTime: 250,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
let initialLocalStats = deepCloneJSON(this.localLogsStats);
|
|
356
|
+
let initialBackblazeStats = deepCloneJSON(this.backblazeLogsStats);
|
|
357
|
+
|
|
358
|
+
let updateResultsStats = () => {
|
|
359
|
+
results.reads = [];
|
|
360
|
+
|
|
361
|
+
function addStatsRead(initial: ArchivesMemoryCacheStats, final: ArchivesMemoryCacheStats, remote: boolean) {
|
|
362
|
+
let uncached = addReadToResults(results, {
|
|
363
|
+
cached: false,
|
|
364
|
+
remote,
|
|
365
|
+
count: final.uncachedReads - initial.uncachedReads,
|
|
366
|
+
size: final.uncachedReadSize - initial.uncachedReadSize,
|
|
367
|
+
});
|
|
368
|
+
uncached.totalSize = final.totalCacheSize;
|
|
369
|
+
uncached.totalCount = final.totalCacheCount;
|
|
370
|
+
|
|
371
|
+
let cached = addReadToResults(results, {
|
|
372
|
+
cached: true,
|
|
373
|
+
remote,
|
|
374
|
+
count: final.cachedReads - initial.cachedReads,
|
|
375
|
+
size: final.cachedReadSize - initial.cachedReadSize,
|
|
376
|
+
});
|
|
377
|
+
cached.totalSize = final.totalCacheSize;
|
|
378
|
+
cached.totalCount = final.totalCacheCount;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
addStatsRead(initialLocalStats, this.localLogsStats, false);
|
|
382
|
+
addStatsRead(initialBackblazeStats, this.backblazeLogsStats, true);
|
|
383
|
+
|
|
384
|
+
results.totalSearchTime = Date.now() - startTime;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const onResultsCallback = config.onResults;
|
|
388
|
+
if (onResultsCallback) {
|
|
389
|
+
interval = setInterval(async () => {
|
|
390
|
+
updateResultsStats();
|
|
391
|
+
let shouldContinue = await onResultsCallback(deepCloneJSON(results));
|
|
392
|
+
if (!shouldContinue) {
|
|
393
|
+
if (!results.cancel) {
|
|
394
|
+
console.log(blue(`Cancelled search on ${this.config.name}`));
|
|
395
|
+
}
|
|
396
|
+
results.cancel = true;
|
|
397
|
+
}
|
|
398
|
+
}, timeInSecond);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
await this.findBase({
|
|
403
|
+
params: config.params,
|
|
404
|
+
onResult: config.onResult,
|
|
405
|
+
results,
|
|
406
|
+
});
|
|
407
|
+
updateResultsStats();
|
|
408
|
+
return results;
|
|
409
|
+
} finally {
|
|
410
|
+
if (interval) {
|
|
411
|
+
clearInterval(interval);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private async findBase(config: {
|
|
417
|
+
params: SearchParams;
|
|
418
|
+
onResult: (match: T) => void;
|
|
419
|
+
results: IndexedLogResults;
|
|
420
|
+
}): Promise<void> {
|
|
421
|
+
let startTime = Date.now();
|
|
422
|
+
let localLogs = this.getLocalLogs();
|
|
423
|
+
let backblazeLogs = this.getPublicLogs();
|
|
424
|
+
|
|
425
|
+
let fileFindTime = Date.now();
|
|
426
|
+
let paths = config.params.pathOverrides ?? await this.getPaths({
|
|
427
|
+
startTime: config.params.startTime,
|
|
428
|
+
endTime: config.params.endTime,
|
|
429
|
+
only: config.params.only,
|
|
430
|
+
});
|
|
431
|
+
paths = paths.filter(x => x.sourceName === this.config.name);
|
|
432
|
+
for (let path of paths) {
|
|
433
|
+
this.fileSizeCache.set(path.fullPath, path.size);
|
|
434
|
+
this.fileSizeCache.set(path.fullPath + INDEX_EXTENSION, path.indexSize);
|
|
435
|
+
}
|
|
436
|
+
fileFindTime = Date.now() - fileFindTime;
|
|
437
|
+
|
|
438
|
+
// Newest first
|
|
439
|
+
sort(paths, x => - x.startTime);
|
|
440
|
+
|
|
441
|
+
let results = config.results;
|
|
442
|
+
let localPaths = paths.filter(x => x.logCount === undefined);
|
|
443
|
+
let remotePaths = paths.filter(x => x.logCount !== undefined);
|
|
444
|
+
results.totalLocalFiles = localPaths.length;
|
|
445
|
+
results.totalBackblazeFiles = remotePaths.length;
|
|
446
|
+
results.fileFindTime = fileFindTime;
|
|
447
|
+
|
|
448
|
+
let progressTracker = new FindProgressTracker<T>({
|
|
449
|
+
params: config.params,
|
|
450
|
+
deserialize: (buffer: Buffer) => LogStreamer.deserialize<T>(buffer),
|
|
451
|
+
getTime: this.config.getTime,
|
|
452
|
+
onResult: (match: T) => {
|
|
453
|
+
if (results.timeToFirstMatch < 0) {
|
|
454
|
+
results.timeToFirstMatch = Date.now() - startTime;
|
|
455
|
+
}
|
|
456
|
+
results.matchCount++;
|
|
457
|
+
config.onResult(match);
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
const searchPath = async (path: TimeFilePathWithSize) => {
|
|
463
|
+
if (!progressTracker.isSourceRelevant(path)) return;
|
|
464
|
+
// Wait, so we don't lock up the main thread?
|
|
465
|
+
await delay(0);
|
|
466
|
+
|
|
467
|
+
let remote = path.logCount !== undefined;
|
|
468
|
+
let archives = remote ? backblazeLogs : localLogs;
|
|
469
|
+
if (remote) {
|
|
470
|
+
results.backblazeFilesSearched++;
|
|
471
|
+
} else {
|
|
472
|
+
results.localFilesSearched++;
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
let readIndexTime = Date.now();
|
|
476
|
+
let index = await archives.get(
|
|
477
|
+
path.fullPath + INDEX_EXTENSION,
|
|
478
|
+
{ range: { start: 0, end: path.indexSize } }
|
|
479
|
+
) || Buffer.alloc(0);
|
|
480
|
+
readIndexTime = Date.now() - readIndexTime;
|
|
481
|
+
results.indexSearchTime += readIndexTime;
|
|
482
|
+
let dataReader: Reader = {
|
|
483
|
+
getLength: async () => path.size,
|
|
484
|
+
read: async (offset, length) => {
|
|
485
|
+
let endOffset = Math.min(offset + length, path.size);
|
|
486
|
+
if (offset >= path.size) return Buffer.alloc(0);
|
|
487
|
+
let data = await archives.get(path.fullPath, { range: { start: offset, end: endOffset } });
|
|
488
|
+
if (!data) return Buffer.alloc(0);
|
|
489
|
+
return data;
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
let totalSearchTime = Date.now();
|
|
493
|
+
let blockSearchTimeBefore = results.blockSearchTime;
|
|
494
|
+
await BufferIndex.find({
|
|
495
|
+
index,
|
|
496
|
+
dataReader,
|
|
497
|
+
params: {
|
|
498
|
+
...config.params,
|
|
499
|
+
limit: config.params.limit - results.matchCount,
|
|
500
|
+
},
|
|
501
|
+
keepIterating: () => !results.cancel && progressTracker.isSourceRelevant(path),
|
|
502
|
+
onResult: (match: Buffer) => {
|
|
503
|
+
if (results.matchCount >= config.params.limit) return;
|
|
504
|
+
progressTracker.addResult(match, path);
|
|
505
|
+
},
|
|
506
|
+
results,
|
|
507
|
+
});
|
|
508
|
+
totalSearchTime = Date.now() - totalSearchTime;
|
|
509
|
+
let blockSearchTimeAdded = results.blockSearchTime - blockSearchTimeBefore;
|
|
510
|
+
results.indexSearchTime += totalSearchTime - blockSearchTimeAdded;
|
|
511
|
+
|
|
512
|
+
} catch (e: any) {
|
|
513
|
+
results.fileErrors.push({ error: String(e?.stack || e), path: path.fullPath });
|
|
514
|
+
console.warn(`Error in reading log file ${path.fullPath} logs: ${e}`);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
let localDone = (async () => {
|
|
518
|
+
for (let path of localPaths) {
|
|
519
|
+
await searchPath(path);
|
|
520
|
+
}
|
|
521
|
+
})();
|
|
522
|
+
// No parallel count when running locally, as running it in parallel makes timing more difficult.
|
|
523
|
+
let parallelCount = isPublic() ? 32 : 1;
|
|
524
|
+
let remoteParallel = runInParallel({ parallelCount }, searchPath);
|
|
525
|
+
await Promise.all(remotePaths.map(remoteParallel));
|
|
526
|
+
await localDone;
|
|
527
|
+
|
|
528
|
+
if (results.timeToFirstMatch < 0) {
|
|
529
|
+
results.timeToFirstMatch = Date.now() - startTime;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
public async moveLogsToPublic(forceAll = false) {
|
|
534
|
+
if (forceAll) {
|
|
535
|
+
await this.currentLogStream?.flushNow?.();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
await moveLogsToPublic({
|
|
539
|
+
forceAll,
|
|
540
|
+
localLogs: this.getLocalLogs(),
|
|
541
|
+
publicLogs: this.getPublicLogs(),
|
|
542
|
+
publicMoveThreshold: PUBLIC_MOVE_THRESHOLD,
|
|
543
|
+
maxSingleFileData: this.config.maxSingleFileData || MAX_SINGLE_FILE_DATA,
|
|
544
|
+
movingTimeout: MOVING_TIMEOUT,
|
|
545
|
+
indexExtension: INDEX_EXTENSION,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
// TEST functions
|
|
552
|
+
public async TEST_flushNow() {
|
|
553
|
+
await this.forceFlushStream?.();
|
|
554
|
+
}
|
|
555
|
+
public async TEST_deleteAllLocalLogs() {
|
|
556
|
+
let localLogs = this.getLocalLogs();
|
|
557
|
+
await new TimeFileTree(localLogs).deleteAll();
|
|
558
|
+
}
|
|
559
|
+
public async TEST_deleteAllBackblazeLogs() {
|
|
560
|
+
let backblazeLogs = this.getPublicLogs();
|
|
561
|
+
await new TimeFileTree(backblazeLogs).deleteAll();
|
|
562
|
+
}
|
|
563
|
+
public async TEST_deleteAllLogs() {
|
|
564
|
+
await this.TEST_deleteAllLocalLogs();
|
|
565
|
+
await this.TEST_deleteAllBackblazeLogs();
|
|
566
|
+
|
|
567
|
+
let localLogs = this.getLocalLogs();
|
|
568
|
+
// Also delete all .moving files
|
|
569
|
+
let movingFiles = await localLogs.find("", { shallow: true, type: "files" });
|
|
570
|
+
movingFiles = movingFiles.filter(x => x.endsWith(".moving"));
|
|
571
|
+
for (let movingFile of movingFiles) {
|
|
572
|
+
await localLogs.del(movingFile);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
class IndexedLogClient {
|
|
578
|
+
public async onFind(config: {
|
|
579
|
+
findId: string;
|
|
580
|
+
indexedLogsName: string;
|
|
581
|
+
result: unknown;
|
|
582
|
+
}) {
|
|
583
|
+
if (isNode()) return;
|
|
584
|
+
let indexedLogs = loggerByName.get(config.indexedLogsName);
|
|
585
|
+
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
586
|
+
indexedLogs.onFindResult({
|
|
587
|
+
findId: config.findId,
|
|
588
|
+
result: config.result,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
public async onResults(config: {
|
|
592
|
+
findId: string;
|
|
593
|
+
indexedLogsName: string;
|
|
594
|
+
results: IndexedLogResults;
|
|
595
|
+
}): Promise<boolean> {
|
|
596
|
+
if (isNode()) return true;
|
|
597
|
+
let indexedLogs = loggerByName.get(config.indexedLogsName);
|
|
598
|
+
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
599
|
+
return await indexedLogs.onResults({
|
|
600
|
+
findId: config.findId,
|
|
601
|
+
results: config.results,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
class IndexedLogShim {
|
|
606
|
+
public async find(config: {
|
|
607
|
+
findId: string;
|
|
608
|
+
indexedLogsName: string;
|
|
609
|
+
params: SearchParams;
|
|
610
|
+
}): Promise<IndexedLogResults> {
|
|
611
|
+
let caller = SocketFunction.getCaller();
|
|
612
|
+
let indexedLogs = loggerByName.get(config.indexedLogsName);
|
|
613
|
+
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
614
|
+
|
|
615
|
+
return indexedLogs.find({
|
|
616
|
+
params: config.params,
|
|
617
|
+
onResult: (match: unknown) => {
|
|
618
|
+
ignoreErrors(IndexedLogClientController.nodes[caller.nodeId].onFind({
|
|
619
|
+
findId: config.findId,
|
|
620
|
+
indexedLogsName: config.indexedLogsName,
|
|
621
|
+
result: match,
|
|
622
|
+
}));
|
|
623
|
+
},
|
|
624
|
+
onResults: async (results: IndexedLogResults) => {
|
|
625
|
+
try {
|
|
626
|
+
await IndexedLogClientController.nodes[caller.nodeId].onResults({
|
|
627
|
+
findId: config.findId,
|
|
628
|
+
indexedLogsName: config.indexedLogsName,
|
|
629
|
+
results: results,
|
|
630
|
+
});
|
|
631
|
+
return true;
|
|
632
|
+
} catch (e) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
public async getPaths(config: {
|
|
639
|
+
indexedLogsName: string;
|
|
640
|
+
startTime: number;
|
|
641
|
+
endTime: number;
|
|
642
|
+
only?: "local" | "public";
|
|
643
|
+
}): Promise<TimeFilePathWithSize[]> {
|
|
644
|
+
let indexedLogs = loggerByName.get(config.indexedLogsName);
|
|
645
|
+
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
646
|
+
return indexedLogs.getPaths({
|
|
647
|
+
startTime: config.startTime,
|
|
648
|
+
endTime: config.endTime,
|
|
649
|
+
only: config.only,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
public async forceMoveLogsToPublic(config: {
|
|
654
|
+
indexedLogsName: string;
|
|
655
|
+
}) {
|
|
656
|
+
let indexedLogs = loggerByName.get(config.indexedLogsName);
|
|
657
|
+
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
658
|
+
await indexedLogs.moveLogsToPublic(true);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const IndexedLogShimController = SocketFunction.register(
|
|
663
|
+
"IndexedLogShim-019c87b7-73ca-72ec-91b3-2d45ebb616cd",
|
|
664
|
+
new IndexedLogShim(),
|
|
665
|
+
() => ({
|
|
666
|
+
find: {
|
|
667
|
+
hooks: [assertIsManagementUser]
|
|
668
|
+
},
|
|
669
|
+
getPaths: {
|
|
670
|
+
hooks: [assertIsManagementUser]
|
|
671
|
+
},
|
|
672
|
+
forceMoveLogsToPublic: {
|
|
673
|
+
hooks: [assertIsManagementUser]
|
|
674
|
+
}
|
|
675
|
+
})
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
const IndexedLogClientController = SocketFunction.register(
|
|
679
|
+
"IndexedLogClient-019c87b9-1c6d-72ed-8bc9-d52451e2c1b9",
|
|
680
|
+
new IndexedLogClient(),
|
|
681
|
+
() => ({
|
|
682
|
+
onFind: {},
|
|
683
|
+
onResults: {}
|
|
684
|
+
})
|
|
685
|
+
);
|