querysub 0.356.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 +132 -15
- 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 +10 -0
- 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,236 @@
|
|
|
1
|
+
import { Archives } from "../../../-a-archives/archives";
|
|
2
|
+
import { getOwnThreadId, getOwnMachineId } from "../../../-f-node-discovery/NodeDiscovery";
|
|
3
|
+
|
|
4
|
+
export type TimeFilePath = {
|
|
5
|
+
fullPath: string;
|
|
6
|
+
|
|
7
|
+
// If not pending, this isn't the source, this is just the writer.
|
|
8
|
+
threadId?: string;
|
|
9
|
+
// Presently machine should always be set. But it might not if we start merging logs in backblaze.
|
|
10
|
+
machineId?: string;
|
|
11
|
+
|
|
12
|
+
// If logCount is undefined it means it's a pending file
|
|
13
|
+
logCount?: number;
|
|
14
|
+
uncompressedSize?: number;
|
|
15
|
+
compressedSize?: number;
|
|
16
|
+
|
|
17
|
+
startTime: number;
|
|
18
|
+
endTime: number;
|
|
19
|
+
|
|
20
|
+
// Probably just random?
|
|
21
|
+
dedupe: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const LOG_FILE_EXTENSION = ".logfile";
|
|
25
|
+
|
|
26
|
+
function encodeLogFilePath(path: Omit<TimeFilePath, "fullPath">): string {
|
|
27
|
+
// Create folder structure: year/month/day/
|
|
28
|
+
const date = new Date(path.startTime);
|
|
29
|
+
const year = date.getUTCFullYear();
|
|
30
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
31
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
32
|
+
|
|
33
|
+
const folderPath = `${year}/${month}/${day}/`;
|
|
34
|
+
|
|
35
|
+
// Build file name with dashed parts
|
|
36
|
+
const parts: string[] = [];
|
|
37
|
+
|
|
38
|
+
if (path.threadId !== undefined) {
|
|
39
|
+
parts.push("threadid", path.threadId);
|
|
40
|
+
}
|
|
41
|
+
if (path.machineId !== undefined) {
|
|
42
|
+
parts.push("machineid", path.machineId);
|
|
43
|
+
}
|
|
44
|
+
parts.push("start", String(path.startTime));
|
|
45
|
+
parts.push("end", String(path.endTime));
|
|
46
|
+
parts.push("dedupe", String(path.dedupe));
|
|
47
|
+
if (path.logCount !== undefined) {
|
|
48
|
+
parts.push("logcount", String(path.logCount));
|
|
49
|
+
}
|
|
50
|
+
if (path.uncompressedSize !== undefined) {
|
|
51
|
+
parts.push("uncompressed", String(path.uncompressedSize));
|
|
52
|
+
}
|
|
53
|
+
if (path.compressedSize !== undefined) {
|
|
54
|
+
parts.push("compressed", String(path.compressedSize));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const fileName = parts.join("-") + LOG_FILE_EXTENSION;
|
|
58
|
+
|
|
59
|
+
return folderPath + fileName;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const KEY_MAPPING: Record<string, { field: keyof Omit<TimeFilePath, "fullPath">; parseAsNumber: boolean }> = {
|
|
63
|
+
"threadid": { field: "threadId", parseAsNumber: false },
|
|
64
|
+
"machineid": { field: "machineId", parseAsNumber: false },
|
|
65
|
+
"start": { field: "startTime", parseAsNumber: true },
|
|
66
|
+
"end": { field: "endTime", parseAsNumber: true },
|
|
67
|
+
"dedupe": { field: "dedupe", parseAsNumber: true },
|
|
68
|
+
"logcount": { field: "logCount", parseAsNumber: true },
|
|
69
|
+
"uncompressed": { field: "uncompressedSize", parseAsNumber: true },
|
|
70
|
+
"compressed": { field: "compressedSize", parseAsNumber: true },
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function decodeLogFilePath(path: string): TimeFilePath | undefined {
|
|
74
|
+
if (!path.endsWith(LOG_FILE_EXTENSION)) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Extract the file name from the path
|
|
79
|
+
const lastSlash = path.lastIndexOf("/");
|
|
80
|
+
const fileName = lastSlash >= 0 ? path.substring(lastSlash + 1) : path;
|
|
81
|
+
|
|
82
|
+
// Remove extension
|
|
83
|
+
const nameWithoutExt = fileName.substring(0, fileName.length - LOG_FILE_EXTENSION.length);
|
|
84
|
+
|
|
85
|
+
// Parse the dashed parts
|
|
86
|
+
const parts = nameWithoutExt.split("-");
|
|
87
|
+
|
|
88
|
+
const result: TimeFilePath = {
|
|
89
|
+
fullPath: path,
|
|
90
|
+
startTime: 0,
|
|
91
|
+
endTime: 0,
|
|
92
|
+
dedupe: 0,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < parts.length; i++) {
|
|
96
|
+
const key = parts[i];
|
|
97
|
+
const mapping = KEY_MAPPING[key];
|
|
98
|
+
|
|
99
|
+
if (mapping && i + 1 < parts.length) {
|
|
100
|
+
const value = parts[i + 1];
|
|
101
|
+
(result as any)[mapping.field] = mapping.parseAsNumber ? parseInt(value, 10) : value;
|
|
102
|
+
i++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// NOTE: This class will add its own extensions to the files, and it'll use it to detect these files. You can append your own extension and then we will ignore those files.
|
|
110
|
+
export class TimeFileTree {
|
|
111
|
+
public constructor(public archives: Archives) { }
|
|
112
|
+
|
|
113
|
+
public async findAllPaths(config: {
|
|
114
|
+
startTime: number;
|
|
115
|
+
endTime: number;
|
|
116
|
+
}): Promise<TimeFilePath[]> {
|
|
117
|
+
const results: TimeFilePath[] = [];
|
|
118
|
+
|
|
119
|
+
// Generate all year/month/day combinations in the range
|
|
120
|
+
const startDate = new Date(config.startTime);
|
|
121
|
+
const endDate = new Date(config.endTime);
|
|
122
|
+
|
|
123
|
+
const startYear = startDate.getUTCFullYear();
|
|
124
|
+
const endYear = endDate.getUTCFullYear();
|
|
125
|
+
|
|
126
|
+
// Find all years in range
|
|
127
|
+
const years = await this.archives.find("", { shallow: true, type: "folders" });
|
|
128
|
+
|
|
129
|
+
for (const yearFolder of years) {
|
|
130
|
+
// yearFolder is a full path like "2024"
|
|
131
|
+
const year = parseInt(yearFolder.split("/").at(-1) || "", 10);
|
|
132
|
+
|
|
133
|
+
if (isNaN(year) || year < startYear || year > endYear) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Find all months in this year
|
|
138
|
+
const months = await this.archives.find(yearFolder + "/", { shallow: true, type: "folders" });
|
|
139
|
+
|
|
140
|
+
for (const monthFolder of months) {
|
|
141
|
+
// monthFolder is a full path like "2024/01"
|
|
142
|
+
const month = parseInt(monthFolder.split("/").at(-1) || "", 10);
|
|
143
|
+
|
|
144
|
+
if (isNaN(month) || month < 1 || month > 12) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check if this month is in range
|
|
149
|
+
const monthDate = new Date(Date.UTC(year, month - 1, 1));
|
|
150
|
+
const monthEndDate = new Date(Date.UTC(year, month, 0, 23, 59, 59, 999));
|
|
151
|
+
|
|
152
|
+
if (monthEndDate.getTime() < config.startTime || monthDate.getTime() > config.endTime) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Find all days in this month
|
|
157
|
+
const days = await this.archives.find(monthFolder + "/", { shallow: true, type: "folders" });
|
|
158
|
+
|
|
159
|
+
for (const dayFolder of days) {
|
|
160
|
+
// dayFolder is a full path like "2024/01/15"
|
|
161
|
+
const day = parseInt(dayFolder.split("/").at(-1) || "", 10);
|
|
162
|
+
|
|
163
|
+
if (isNaN(day) || day < 1 || day > 31) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check if this day is in range
|
|
168
|
+
const dayDate = new Date(Date.UTC(year, month - 1, day, 0, 0, 0, 0));
|
|
169
|
+
const dayEndDate = new Date(Date.UTC(year, month - 1, day, 23, 59, 59, 999));
|
|
170
|
+
|
|
171
|
+
if (dayEndDate.getTime() < config.startTime || dayDate.getTime() > config.endTime) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Find all log files in this day
|
|
176
|
+
const files = await this.archives.find(dayFolder + "/", { shallow: true, type: "files" });
|
|
177
|
+
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
const decoded = decodeLogFilePath(file);
|
|
180
|
+
|
|
181
|
+
if (decoded === undefined) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check if file's time range overlaps with requested range
|
|
186
|
+
if (decoded.endTime >= config.startTime && decoded.startTime <= config.endTime) {
|
|
187
|
+
results.push(decoded);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return results;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public async deleteAll() {
|
|
198
|
+
let rootFolders = await this.archives.find("", { shallow: true, type: "folders" });
|
|
199
|
+
for (let rootFolder of rootFolders) {
|
|
200
|
+
await this.archives.del(rootFolder);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public getNewPendingPath(config: {
|
|
205
|
+
startTime: number;
|
|
206
|
+
endTime: number;
|
|
207
|
+
}): string {
|
|
208
|
+
return encodeLogFilePath({
|
|
209
|
+
startTime: config.startTime,
|
|
210
|
+
endTime: config.endTime,
|
|
211
|
+
threadId: getOwnThreadId(),
|
|
212
|
+
machineId: getOwnMachineId(),
|
|
213
|
+
dedupe: dedupeSeqNum++,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
public getNewFinalPath(config: {
|
|
218
|
+
logCount: number;
|
|
219
|
+
uncompressedSize: number;
|
|
220
|
+
compressedSize: number;
|
|
221
|
+
startTime: number;
|
|
222
|
+
endTime: number;
|
|
223
|
+
}): string {
|
|
224
|
+
return encodeLogFilePath({
|
|
225
|
+
startTime: config.startTime,
|
|
226
|
+
endTime: config.endTime,
|
|
227
|
+
machineId: getOwnMachineId(),
|
|
228
|
+
threadId: getOwnThreadId(),
|
|
229
|
+
dedupe: dedupeSeqNum++,
|
|
230
|
+
logCount: config.logCount,
|
|
231
|
+
uncompressedSize: config.uncompressedSize,
|
|
232
|
+
compressedSize: config.compressedSize,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
let dedupeSeqNum = 0;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "BufferIndexCPP",
|
|
5
|
+
"sources": [ "BufferIndexCPP.cpp" ],
|
|
6
|
+
"cflags": [ "-O3", "-march=native" ],
|
|
7
|
+
"cflags_cc": [ "-O3", "-march=native" ],
|
|
8
|
+
"msvs_settings": {
|
|
9
|
+
"VCCLCompilerTool": {
|
|
10
|
+
"Optimization": 3,
|
|
11
|
+
"FavorSizeOrSpeed": 1,
|
|
12
|
+
"InlineFunctionExpansion": 2,
|
|
13
|
+
"EnableIntrinsicFunctions": "true",
|
|
14
|
+
"RuntimeLibrary": 0
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"xcode_settings": {
|
|
18
|
+
"GCC_OPTIMIZATION_LEVEL": "3",
|
|
19
|
+
"OTHER_CFLAGS": [ "-march=native" ]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { delay } from "socket-function/src/batching";
|
|
2
|
+
import { sort, keyByArray } from "socket-function/src/misc";
|
|
3
|
+
import { Archives } from "../../../-a-archives/archives";
|
|
4
|
+
import { getOwnThreadId } from "../../../-f-node-discovery/NodeDiscovery";
|
|
5
|
+
import { BufferIndex } from "./BufferIndex";
|
|
6
|
+
import { LogStreamer } from "./LogStreamer";
|
|
7
|
+
import { TimeFileTree } from "./TimeFileTree";
|
|
8
|
+
import { green, magenta } from "socket-function/src/formatting/logColors";
|
|
9
|
+
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export async function moveLogsToPublic(config: {
|
|
14
|
+
publicMoveThreshold: number;
|
|
15
|
+
maxSingleFileData: number;
|
|
16
|
+
movingTimeout: number;
|
|
17
|
+
// Unsafe, but useful for testing
|
|
18
|
+
forceAll: boolean;
|
|
19
|
+
localLogs: Archives;
|
|
20
|
+
publicLogs: Archives;
|
|
21
|
+
getIndexPath: (path: string) => string;
|
|
22
|
+
}) {
|
|
23
|
+
let { forceAll, localLogs, publicLogs, publicMoveThreshold, maxSingleFileData, getIndexPath, movingTimeout } = config;
|
|
24
|
+
let now = Date.now();
|
|
25
|
+
let threadId = getOwnThreadId();
|
|
26
|
+
let ourMovingFileName = `${now}-${threadId}.moving`;
|
|
27
|
+
|
|
28
|
+
async function tryToGetMoveLock(): Promise<boolean> {
|
|
29
|
+
|
|
30
|
+
// Helper function to parse moving file names and extract timestamp
|
|
31
|
+
const parseMovingFileName = (fileName: string): number | undefined => {
|
|
32
|
+
if (!fileName.endsWith(".moving")) return undefined;
|
|
33
|
+
let parts = fileName.split("-");
|
|
34
|
+
if (parts.length < 2) return undefined;
|
|
35
|
+
let timestamp = parseInt(parts[0]);
|
|
36
|
+
if (isNaN(timestamp)) return undefined;
|
|
37
|
+
return timestamp;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Helper function to check and clean up stale moving files
|
|
41
|
+
const checkAndCleanMovingFiles = async (): Promise<string[]> => {
|
|
42
|
+
// 1) We do a shallow read of the local logs directory and then find any files that have the moving extension and then we see when they've been written.
|
|
43
|
+
let allFiles = await localLogs.find("", { shallow: true, type: "files" });
|
|
44
|
+
let movingFiles = allFiles.filter(x => x.endsWith(".moving"));
|
|
45
|
+
|
|
46
|
+
let now = Date.now();
|
|
47
|
+
let validMovingFiles: string[] = [];
|
|
48
|
+
let stalePaths: string[] = [];
|
|
49
|
+
|
|
50
|
+
for (let movingFile of movingFiles) {
|
|
51
|
+
let timestamp = parseMovingFileName(movingFile);
|
|
52
|
+
if (timestamp === undefined) continue;
|
|
53
|
+
|
|
54
|
+
let age = now - timestamp;
|
|
55
|
+
// 2) If they're not past our timeout threshold at the top of the file, Then we just return (NOOP).
|
|
56
|
+
// 3) If any are past their timeout threshold, we delete them.
|
|
57
|
+
if (age > movingTimeout) {
|
|
58
|
+
stalePaths.push(movingFile);
|
|
59
|
+
} else {
|
|
60
|
+
validMovingFiles.push(movingFile);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Delete stale moving files
|
|
65
|
+
for (let stalePath of stalePaths) {
|
|
66
|
+
await localLogs.del(stalePath);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Sort by timestamp (oldest first)
|
|
70
|
+
sort(validMovingFiles, x => parseMovingFileName(x) || Infinity);
|
|
71
|
+
|
|
72
|
+
return validMovingFiles;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// Initial check for existing moving files
|
|
77
|
+
let validMovingFiles = await checkAndCleanMovingFiles();
|
|
78
|
+
// 2) If they're not past our timeout threshold at the top of the file, Then we just return (NOOP).
|
|
79
|
+
if (validMovingFiles.length > 0) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 4) We then write our own moving file flag
|
|
84
|
+
await localLogs.set(ourMovingFileName, Buffer.from("moving"));
|
|
85
|
+
|
|
86
|
+
if (!forceAll) {
|
|
87
|
+
// 5) Wait 15 seconds, to let things settle, so we aren't racing
|
|
88
|
+
await delay(15000);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 6) Check again if anything is moving and it's past the threshold (This code should be in a function that's local to this function, so we can easily run it again)
|
|
92
|
+
validMovingFiles = await checkAndCleanMovingFiles();
|
|
93
|
+
|
|
94
|
+
// 7) Also, out of all of the moving files which aren't beyond the timeout, we need to make sure ours is the oldest, but not older than half the timeout. If this isn't true, then we just return.
|
|
95
|
+
let oldestMovingFile = validMovingFiles[0];
|
|
96
|
+
if (oldestMovingFile !== ourMovingFileName) {
|
|
97
|
+
// Clean up our moving file since we didn't get the lock
|
|
98
|
+
await localLogs.del(ourMovingFileName);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let ourTimestamp = parseMovingFileName(ourMovingFileName);
|
|
103
|
+
if (ourTimestamp !== undefined) {
|
|
104
|
+
let ourAge = Date.now() - ourTimestamp;
|
|
105
|
+
// If we got even close to the threshold, we need to abort. Because if we're close to the threshold, someone else might have thought we exceeded the threshold, and starting moving themselves.
|
|
106
|
+
if (ourAge > movingTimeout / 2) {
|
|
107
|
+
// Clean up our moving file since it's too old
|
|
108
|
+
await localLogs.del(ourMovingFileName);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 8) Then, if all this passes, return true
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let localPaths = await new TimeFileTree(localLogs).findAllPaths({
|
|
118
|
+
startTime: 0,
|
|
119
|
+
endTime: Date.now(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!forceAll) {
|
|
123
|
+
let threshold = Date.now() - publicMoveThreshold;
|
|
124
|
+
localPaths = localPaths.filter(x => x.startTime < threshold);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!localPaths.length) return;
|
|
128
|
+
|
|
129
|
+
if (!await tryToGetMoveLock()) return;
|
|
130
|
+
|
|
131
|
+
let byStartTime = keyByArray(localPaths, x => x.startTime);
|
|
132
|
+
|
|
133
|
+
for (let group of byStartTime.values()) {
|
|
134
|
+
let time = Date.now();
|
|
135
|
+
let buffers: Buffer[] = [];
|
|
136
|
+
await Promise.all(group.map(async x => {
|
|
137
|
+
let buffer = await localLogs.get(x.fullPath);
|
|
138
|
+
if (buffer) {
|
|
139
|
+
buffers.push(buffer);
|
|
140
|
+
}
|
|
141
|
+
}));
|
|
142
|
+
if (buffers.length === 0) continue;
|
|
143
|
+
|
|
144
|
+
console.log(magenta(`Merging and moving ${group.length} log files backblaze`));
|
|
145
|
+
|
|
146
|
+
let allBuffers: Buffer[][] = [];
|
|
147
|
+
|
|
148
|
+
for (let buffer of buffers) {
|
|
149
|
+
let decoded = await BufferIndex.decodeAll(buffer);
|
|
150
|
+
allBuffers.push(decoded);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let buffersFlat = allBuffers.flat();
|
|
154
|
+
// Reverse them, so the newest come first.
|
|
155
|
+
buffersFlat.reverse();
|
|
156
|
+
// Group buffersFlat so we are under MAX_SINGLE_FILE_DATA
|
|
157
|
+
let groupedBuffers: Buffer[][] = [];
|
|
158
|
+
let currentGroup: Buffer[] = [];
|
|
159
|
+
let currentGroupSize = maxSingleFileData + 1;
|
|
160
|
+
for (let buffer of buffersFlat) {
|
|
161
|
+
if (currentGroupSize > maxSingleFileData) {
|
|
162
|
+
currentGroup = [];
|
|
163
|
+
groupedBuffers.push(currentGroup);
|
|
164
|
+
currentGroupSize = 0;
|
|
165
|
+
}
|
|
166
|
+
currentGroup.push(buffer);
|
|
167
|
+
currentGroupSize += buffer.length;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let startTime = Math.min(...group.map(x => x.startTime));
|
|
171
|
+
let endTime = Math.max(...group.map(x => x.endTime));
|
|
172
|
+
|
|
173
|
+
let encoded: {
|
|
174
|
+
data: Buffer;
|
|
175
|
+
index: Buffer;
|
|
176
|
+
compressedSize: number;
|
|
177
|
+
uncompressedSize: number;
|
|
178
|
+
logCount: number;
|
|
179
|
+
}[] = [];
|
|
180
|
+
for (let group of groupedBuffers) {
|
|
181
|
+
let uncompressedSize = 0;
|
|
182
|
+
for (let buffer of group) {
|
|
183
|
+
uncompressedSize += buffer.length;
|
|
184
|
+
}
|
|
185
|
+
let obj = BufferIndex.encodeAll({
|
|
186
|
+
data: group,
|
|
187
|
+
});
|
|
188
|
+
encoded.push({
|
|
189
|
+
data: obj.data,
|
|
190
|
+
index: obj.index,
|
|
191
|
+
compressedSize: obj.data.length,
|
|
192
|
+
uncompressedSize,
|
|
193
|
+
logCount: group.length,
|
|
194
|
+
});
|
|
195
|
+
// Wait, so if we have to encode a lot of data, we don't block for too long?
|
|
196
|
+
await delay(0);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let backblazeManager = new TimeFileTree(publicLogs);
|
|
200
|
+
for (let obj of encoded) {
|
|
201
|
+
let path = backblazeManager.getNewFinalPath({
|
|
202
|
+
logCount: obj.logCount,
|
|
203
|
+
uncompressedSize: obj.uncompressedSize,
|
|
204
|
+
compressedSize: obj.compressedSize,
|
|
205
|
+
startTime,
|
|
206
|
+
endTime,
|
|
207
|
+
});
|
|
208
|
+
let indexPath = getIndexPath(path);
|
|
209
|
+
await publicLogs.set(indexPath, obj.index);
|
|
210
|
+
await publicLogs.set(path, obj.data);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log(green(`Wrote ${encoded.length} log files to backblaze (${formatNumber(encoded.reduce((acc, x) => acc + x.uncompressedSize, 0))}B compressed to ${formatNumber(encoded.reduce((acc, x) => acc + x.compressedSize, 0))}B + ${formatNumber(encoded.reduce((acc, x) => acc + x.index.length, 0))}B index) in ${formatTime(Date.now() - time)}`));
|
|
214
|
+
|
|
215
|
+
for (let path of group) {
|
|
216
|
+
await localLogs.del(path.fullPath);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await localLogs.del(ourMovingFileName);
|
|
221
|
+
}
|