querysub 0.355.0 → 0.357.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 +8 -0
- package/bin/movelogs.js +4 -0
- package/package.json +12 -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 +27 -8
- package/src/-a-archives/archivesLimitedCache.ts +21 -0
- package/src/-a-archives/archivesMemoryCache.ts +350 -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/diagnostics/MachineThreadInfo.tsx +235 -0
- package/src/diagnostics/NodeViewer.tsx +3 -2
- 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 +461 -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 +140 -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 +206 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +719 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +146 -0
- package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +408 -0
- package/src/diagnostics/logs/IndexedLogs/FindProgressTracker.ts +45 -0
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +598 -0
- package/src/diagnostics/logs/IndexedLogs/LogStreamer.ts +47 -0
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +702 -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 +221 -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/lifeCycleAnalysis/LifeCyclePages.tsx +150 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +133 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +180 -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 +11 -1
- package/src/diagnostics/trackResources.ts +1 -1
- 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,598 @@
|
|
|
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, runInSerial, runInfinitePoll } from "socket-function/src/batching";
|
|
6
|
+
import { Reader, SearchParams } 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
|
+
|
|
25
|
+
export type TimeFilePathWithSize = TimeFilePath & {
|
|
26
|
+
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
|
+
indexSize: number;
|
|
57
|
+
|
|
58
|
+
timeToFirstMatch: number;
|
|
59
|
+
fileFindTime: number;
|
|
60
|
+
indexSearchTime: number;
|
|
61
|
+
blockSearchTime: number;
|
|
62
|
+
|
|
63
|
+
totalSearchTime: number;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
let logByName = new Map<string, IndexedLogs<unknown>>();
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export class IndexedLogs<T> {
|
|
70
|
+
public constructor(public config: {
|
|
71
|
+
name: string,
|
|
72
|
+
maxSingleFileData?: number;
|
|
73
|
+
maxCountPerFile?: number;
|
|
74
|
+
forceUsePublicLogs?: boolean;
|
|
75
|
+
getTime: (result: T) => number | undefined;
|
|
76
|
+
}) {
|
|
77
|
+
logByName.set(this.config.name, this as any);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
private static shouldRunLoop = false;
|
|
82
|
+
public static runLogMoveLoop() {
|
|
83
|
+
IndexedLogs.shouldRunLoop = true;
|
|
84
|
+
for (let indexedLogs of logByName.values()) {
|
|
85
|
+
indexedLogs.runLogMoverLoop();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
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
|
+
private localLogsStats = {
|
|
140
|
+
cachedReads: 0,
|
|
141
|
+
uncachedReads: 0,
|
|
142
|
+
cachedReadSize: 0,
|
|
143
|
+
uncachedReadSize: 0,
|
|
144
|
+
totalCacheSize: 0,
|
|
145
|
+
totalCacheCount: 0,
|
|
146
|
+
};
|
|
147
|
+
private backblazeLogsStats = {
|
|
148
|
+
cachedReads: 0,
|
|
149
|
+
uncachedReads: 0,
|
|
150
|
+
cachedReadSize: 0,
|
|
151
|
+
uncachedReadSize: 0,
|
|
152
|
+
totalCacheSize: 0,
|
|
153
|
+
totalCacheCount: 0,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
private getLocalLogs = lazy((): Archives => {
|
|
157
|
+
let baseDisk = getArchivesLocal(getDomain());
|
|
158
|
+
let archives = nestArchives("indexed-logs/" + this.config.name, baseDisk);
|
|
159
|
+
archives = createArchivesMemoryCache(archives, {
|
|
160
|
+
maxSize: 1024 * 1024 * 512,
|
|
161
|
+
maxCount: 1000 * 100,
|
|
162
|
+
stats: this.localLogsStats
|
|
163
|
+
});
|
|
164
|
+
return archives;
|
|
165
|
+
});
|
|
166
|
+
private getPublicLogs = lazy((): Archives => {
|
|
167
|
+
let basePublic: Archives = getArchivesLocal(getDomain());
|
|
168
|
+
let extraReadSize = 1024;
|
|
169
|
+
if (this.config.forceUsePublicLogs || isPublic()) {
|
|
170
|
+
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;
|
|
173
|
+
}
|
|
174
|
+
let archives = nestArchives("final-indexed-logs/" + this.config.name, basePublic);
|
|
175
|
+
archives = createArchivesMemoryCache(archives, {
|
|
176
|
+
maxSize: 1024 * 1024 * 1024 * 2,
|
|
177
|
+
maxCount: 1000 * 500,
|
|
178
|
+
fullyImmutable: true,
|
|
179
|
+
extraReadSize,
|
|
180
|
+
stats: this.backblazeLogsStats,
|
|
181
|
+
});
|
|
182
|
+
return archives;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
private getTimeBlock(time: number): { startTime: number, endTime: number } {
|
|
186
|
+
let startTime = Math.floor(time / timeInHour) * timeInHour;
|
|
187
|
+
let endTime = startTime + timeInHour;
|
|
188
|
+
return { startTime, endTime };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private runLogMoverLoop = lazy(() => {
|
|
192
|
+
runInfinitePoll(timeInMinute * 5, async () => {
|
|
193
|
+
await this.moveLogsToPublic();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
private currentLogStream: LogStreamer<T> | undefined;
|
|
198
|
+
private forceFlushStream: (() => Promise<void>) | undefined;
|
|
199
|
+
|
|
200
|
+
private getCurrentLogStream(): LogStreamer<T> {
|
|
201
|
+
if (!this.currentLogStream) {
|
|
202
|
+
let { startTime, endTime } = this.getTimeBlock(Date.now());
|
|
203
|
+
let path = new TimeFileTree(this.getLocalLogs()).getNewPendingPath({ startTime, endTime });
|
|
204
|
+
let indexPath = this.getIndexPath(path);
|
|
205
|
+
let streamer = BufferIndex.createStreamer();
|
|
206
|
+
let currentLogStreamSize = 0;
|
|
207
|
+
let currentLogStreamCount = 0;
|
|
208
|
+
let newStreamer = async () => {
|
|
209
|
+
let result = streamer.close();
|
|
210
|
+
await this.getLocalLogs().append(path, result.data);
|
|
211
|
+
await this.getLocalLogs().append(indexPath, result.index);
|
|
212
|
+
|
|
213
|
+
path = new TimeFileTree(this.getLocalLogs()).getNewPendingPath({ startTime, endTime });
|
|
214
|
+
indexPath = this.getIndexPath(path);
|
|
215
|
+
streamer = BufferIndex.createStreamer();
|
|
216
|
+
currentLogStreamSize = 0;
|
|
217
|
+
currentLogStreamCount = 0;
|
|
218
|
+
};
|
|
219
|
+
this.forceFlushStream = async () => {
|
|
220
|
+
// FORCE the stream to finish and write to disk, by making it too large
|
|
221
|
+
currentLogStreamSize = Number.MAX_SAFE_INTEGER;
|
|
222
|
+
currentLogStreamCount = Number.MAX_SAFE_INTEGER;
|
|
223
|
+
await this.currentLogStream?.flushNow?.();
|
|
224
|
+
|
|
225
|
+
};
|
|
226
|
+
let writeBuffers = async (buffers: Buffer[]) => {
|
|
227
|
+
let maxSize = this.config.maxSingleFileData || MAX_SINGLE_FILE_DATA;
|
|
228
|
+
let maxCount = this.config.maxCountPerFile || MAX_COUNT_PER_FILE;
|
|
229
|
+
|
|
230
|
+
let bufferGroups = this.splitBuffersIntoGroups(buffers, maxSize, maxCount, currentLogStreamSize, currentLogStreamCount);
|
|
231
|
+
|
|
232
|
+
for (let i = 0; i < bufferGroups.length; i++) {
|
|
233
|
+
let group = bufferGroups[i];
|
|
234
|
+
|
|
235
|
+
if (i > 0) {
|
|
236
|
+
await newStreamer();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let result = streamer.add(group);
|
|
240
|
+
await this.getLocalLogs().append(path, result.data);
|
|
241
|
+
if (result.index) {
|
|
242
|
+
await this.getLocalLogs().append(indexPath, result.index);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
currentLogStreamSize += result.data.length;
|
|
246
|
+
currentLogStreamCount += group.length;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
this.currentLogStream = new LogStreamer<T>(DISK_FLUSH_INTERVAL, writeBuffers);
|
|
251
|
+
}
|
|
252
|
+
return this.currentLogStream;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private splitBuffersIntoGroups(buffers: Buffer[], maxSize: number, maxCount: number, currentLogStreamSize: number, currentLogStreamCount: number): Buffer[][] {
|
|
256
|
+
let groups: Buffer[][] = [];
|
|
257
|
+
let currentGroup: Buffer[] = [];
|
|
258
|
+
let currentGroupSize = 0;
|
|
259
|
+
let remainingSize = maxSize - currentLogStreamSize;
|
|
260
|
+
let remainingCount = maxCount - currentLogStreamCount;
|
|
261
|
+
|
|
262
|
+
for (let buffer of buffers) {
|
|
263
|
+
if (
|
|
264
|
+
// Allow empty at the start, so we create a new streamer
|
|
265
|
+
(currentGroup.length > 0 || groups.length === 0)
|
|
266
|
+
&&
|
|
267
|
+
(currentGroupSize + buffer.length > remainingSize || currentGroup.length >= remainingCount)
|
|
268
|
+
) {
|
|
269
|
+
groups.push(currentGroup);
|
|
270
|
+
currentGroup = [];
|
|
271
|
+
currentGroupSize = 0;
|
|
272
|
+
remainingSize = maxSize;
|
|
273
|
+
remainingCount = maxCount;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
currentGroup.push(buffer);
|
|
277
|
+
currentGroupSize += buffer.length;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (currentGroup.length > 0) {
|
|
281
|
+
groups.push(currentGroup);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return groups;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private getIndexPath(dataPath: string): string {
|
|
288
|
+
return dataPath + ".index";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public append(datum: T) {
|
|
292
|
+
this.getCurrentLogStream().append(datum);
|
|
293
|
+
if (IndexedLogs.shouldRunLoop) {
|
|
294
|
+
this.runLogMoverLoop();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
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
|
+
return { ...path, size, sourceName: this.config.name };
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
return pathsWithSize;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public async find(config: {
|
|
336
|
+
params: SearchParams;
|
|
337
|
+
onResult: (match: T) => void;
|
|
338
|
+
}): Promise<IndexedLogResults> {
|
|
339
|
+
let startTime = Date.now();
|
|
340
|
+
let localLogs = this.getLocalLogs();
|
|
341
|
+
let backblazeLogs = this.getPublicLogs();
|
|
342
|
+
|
|
343
|
+
let fileFindTime = Date.now();
|
|
344
|
+
let paths = config.params.pathOverrides ?? await this.getPaths({
|
|
345
|
+
startTime: config.params.startTime,
|
|
346
|
+
endTime: config.params.endTime,
|
|
347
|
+
only: config.params.only,
|
|
348
|
+
});
|
|
349
|
+
paths = paths.filter(x => x.sourceName === this.config.name);
|
|
350
|
+
fileFindTime = Date.now() - fileFindTime;
|
|
351
|
+
|
|
352
|
+
// Newest first
|
|
353
|
+
sort(paths, x => - x.startTime);
|
|
354
|
+
|
|
355
|
+
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[] = [];
|
|
374
|
+
|
|
375
|
+
let progressTracker = new FindProgressTracker<T>({
|
|
376
|
+
params: config.params,
|
|
377
|
+
deserialize: (buffer: Buffer) => LogStreamer.deserialize<T>(buffer),
|
|
378
|
+
getTime: this.config.getTime,
|
|
379
|
+
onResult: (match: T) => {
|
|
380
|
+
if (stats.timeToFirstMatch < 0) {
|
|
381
|
+
stats.timeToFirstMatch = Date.now() - startTime;
|
|
382
|
+
}
|
|
383
|
+
stats.matchCount++;
|
|
384
|
+
config.onResult(match);
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
for (let path of paths) {
|
|
390
|
+
if (!progressTracker.isSourceRelevant(path)) continue;
|
|
391
|
+
|
|
392
|
+
let archives = path.logCount !== undefined ? backblazeLogs : localLogs;
|
|
393
|
+
try {
|
|
394
|
+
let readIndexTime = Date.now();
|
|
395
|
+
let index = await archives.get(this.getIndexPath(path.fullPath)) || Buffer.alloc(0);
|
|
396
|
+
readIndexTime = Date.now() - readIndexTime;
|
|
397
|
+
stats.indexSearchTime += readIndexTime;
|
|
398
|
+
let dataReader: Reader = {
|
|
399
|
+
getLength: async () => path.size,
|
|
400
|
+
read: async (offset, length) => {
|
|
401
|
+
let endOffset = Math.min(offset + length, path.size);
|
|
402
|
+
if (offset >= path.size) return Buffer.alloc(0);
|
|
403
|
+
let data = await archives.get(path.fullPath, { range: { start: offset, end: endOffset } });
|
|
404
|
+
if (!data) return Buffer.alloc(0);
|
|
405
|
+
return data;
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
let totalSearchTime = Date.now();
|
|
409
|
+
let findObj = await BufferIndex.find({
|
|
410
|
+
index,
|
|
411
|
+
dataReader,
|
|
412
|
+
params: {
|
|
413
|
+
...config.params,
|
|
414
|
+
limit: config.params.limit - stats.matchCount,
|
|
415
|
+
},
|
|
416
|
+
keepIterating: () => progressTracker.isSourceRelevant(path),
|
|
417
|
+
onResult: (match: Buffer) => {
|
|
418
|
+
if (stats.matchCount >= config.params.limit) return;
|
|
419
|
+
progressTracker.addResult(match, path);
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
blockErrors.push(...findObj.blocksWithErrors);
|
|
423
|
+
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;
|
|
434
|
+
} catch (e: any) {
|
|
435
|
+
fileErrors.push(String(e?.stack || e));
|
|
436
|
+
console.warn(`Error in reading log file ${path.fullPath} logs: ${e}`);
|
|
437
|
+
}
|
|
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
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
public async moveLogsToPublic(forceAll = false) {
|
|
482
|
+
if (forceAll) {
|
|
483
|
+
await this.currentLogStream?.flushNow?.();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
await moveLogsToPublic({
|
|
487
|
+
forceAll,
|
|
488
|
+
localLogs: this.getLocalLogs(),
|
|
489
|
+
publicLogs: this.getPublicLogs(),
|
|
490
|
+
publicMoveThreshold: PUBLIC_MOVE_THRESHOLD,
|
|
491
|
+
maxSingleFileData: this.config.maxSingleFileData || MAX_SINGLE_FILE_DATA,
|
|
492
|
+
movingTimeout: MOVING_TIMEOUT,
|
|
493
|
+
getIndexPath: (path: string) => this.getIndexPath(path),
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
// TEST functions
|
|
500
|
+
public async TEST_flushNow() {
|
|
501
|
+
await this.forceFlushStream?.();
|
|
502
|
+
}
|
|
503
|
+
public async TEST_deleteAllLocalLogs() {
|
|
504
|
+
let localLogs = this.getLocalLogs();
|
|
505
|
+
await new TimeFileTree(localLogs).deleteAll();
|
|
506
|
+
}
|
|
507
|
+
public async TEST_deleteAllBackblazeLogs() {
|
|
508
|
+
let backblazeLogs = this.getPublicLogs();
|
|
509
|
+
await new TimeFileTree(backblazeLogs).deleteAll();
|
|
510
|
+
}
|
|
511
|
+
public async TEST_deleteAllLogs() {
|
|
512
|
+
await this.TEST_deleteAllLocalLogs();
|
|
513
|
+
await this.TEST_deleteAllBackblazeLogs();
|
|
514
|
+
|
|
515
|
+
let localLogs = this.getLocalLogs();
|
|
516
|
+
// Also delete all .moving files
|
|
517
|
+
let movingFiles = await localLogs.find("", { shallow: true, type: "files" });
|
|
518
|
+
movingFiles = movingFiles.filter(x => x.endsWith(".moving"));
|
|
519
|
+
for (let movingFile of movingFiles) {
|
|
520
|
+
await localLogs.del(movingFile);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
class IndexedLogClient {
|
|
526
|
+
public async onFind(config: {
|
|
527
|
+
findId: string;
|
|
528
|
+
indexedLogsName: string;
|
|
529
|
+
result: unknown;
|
|
530
|
+
}) {
|
|
531
|
+
if (isNode()) return;
|
|
532
|
+
let indexedLogs = logByName.get(config.indexedLogsName);
|
|
533
|
+
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
534
|
+
indexedLogs.onFindResult({
|
|
535
|
+
findId: config.findId,
|
|
536
|
+
result: config.result,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
class IndexedLogShim {
|
|
541
|
+
public async find(config: {
|
|
542
|
+
findId: string;
|
|
543
|
+
indexedLogsName: string;
|
|
544
|
+
params: SearchParams;
|
|
545
|
+
}): Promise<IndexedLogResults> {
|
|
546
|
+
let caller = SocketFunction.getCaller();
|
|
547
|
+
let indexedLogs = logByName.get(config.indexedLogsName);
|
|
548
|
+
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
549
|
+
return indexedLogs.find({
|
|
550
|
+
params: config.params,
|
|
551
|
+
onResult: (match: unknown) => {
|
|
552
|
+
ignoreErrors(IndexedLogClientController.nodes[caller.nodeId].onFind({
|
|
553
|
+
findId: config.findId,
|
|
554
|
+
indexedLogsName: config.indexedLogsName,
|
|
555
|
+
result: match,
|
|
556
|
+
}));
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
public async getPaths(config: {
|
|
561
|
+
indexedLogsName: string;
|
|
562
|
+
startTime: number;
|
|
563
|
+
endTime: number;
|
|
564
|
+
only?: "local" | "public";
|
|
565
|
+
}): Promise<TimeFilePathWithSize[]> {
|
|
566
|
+
let indexedLogs = logByName.get(config.indexedLogsName);
|
|
567
|
+
if (!indexedLogs) throw new Error(`Indexed logs ${config.indexedLogsName} not found`);
|
|
568
|
+
return indexedLogs.getPaths({
|
|
569
|
+
startTime: config.startTime,
|
|
570
|
+
endTime: config.endTime,
|
|
571
|
+
only: config.only,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const IndexedLogShimController = SocketFunction.register(
|
|
577
|
+
"IndexedLogShim-019c87b7-73ca-72ec-91b3-2d45ebb616cd",
|
|
578
|
+
new IndexedLogShim(),
|
|
579
|
+
() => ({
|
|
580
|
+
find: {
|
|
581
|
+
hooks: [assertIsManagementUser]
|
|
582
|
+
},
|
|
583
|
+
getPaths: {
|
|
584
|
+
hooks: [assertIsManagementUser]
|
|
585
|
+
},
|
|
586
|
+
clientForceMoveLogsToPublic: {
|
|
587
|
+
hooks: [assertIsManagementUser]
|
|
588
|
+
}
|
|
589
|
+
})
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
const IndexedLogClientController = SocketFunction.register(
|
|
593
|
+
"IndexedLogClient-019c87b9-1c6d-72ed-8bc9-d52451e2c1b9",
|
|
594
|
+
new IndexedLogClient(),
|
|
595
|
+
() => ({
|
|
596
|
+
onFind: {}
|
|
597
|
+
})
|
|
598
|
+
);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { runInfinitePoll, runInSerial } from "socket-function/src/batching";
|
|
2
|
+
import { measureBlock } from "socket-function/src/profiling/measure";
|
|
3
|
+
import { isNode } from "typesafecss";
|
|
4
|
+
import { registerShutdownHandler } from "../../periodic";
|
|
5
|
+
|
|
6
|
+
export class LogStreamer<T> {
|
|
7
|
+
public constructor(diskFlushInterval: number, public writeBuffers: (buffer: Buffer[]) => Promise<void>) {
|
|
8
|
+
registerShutdownHandler(async () => {
|
|
9
|
+
await this.flushNow();
|
|
10
|
+
});
|
|
11
|
+
runInfinitePoll(diskFlushInterval, async () => {
|
|
12
|
+
await this.flushNow();
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private pendingWriteQueue: T[] = [];
|
|
17
|
+
|
|
18
|
+
private insideAppend = false;
|
|
19
|
+
public append(datum: T) {
|
|
20
|
+
if (!isNode()) return;
|
|
21
|
+
if (this.insideAppend) return;
|
|
22
|
+
this.insideAppend = true;
|
|
23
|
+
try {
|
|
24
|
+
this.pendingWriteQueue.push(datum);
|
|
25
|
+
} finally {
|
|
26
|
+
this.insideAppend = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
insideFlush = false;
|
|
30
|
+
public flushNow = runInSerial(async () => {
|
|
31
|
+
this.insideFlush = true;
|
|
32
|
+
try {
|
|
33
|
+
// Remember to add timing inside of this to the serialization parts?
|
|
34
|
+
let writeParts = measureBlock(() => {
|
|
35
|
+
return this.pendingWriteQueue.map(x => Buffer.from(JSON.stringify(x)));
|
|
36
|
+
}, `LogStreamer|serialize log data`);
|
|
37
|
+
this.pendingWriteQueue = [];
|
|
38
|
+
await this.writeBuffers(writeParts);
|
|
39
|
+
} finally {
|
|
40
|
+
this.insideFlush = false;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
public static deserialize<T>(data: Buffer): T {
|
|
44
|
+
return JSON.parse(data.toString());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|