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
|
@@ -7,7 +7,7 @@ import { InputLabel, InputLabelURL } from "../../../library-components/InputLabe
|
|
|
7
7
|
import { getLoggers2, getLoggers2Async, LogDatum } from "../diskLogger";
|
|
8
8
|
import { list, timeInDay, keyByArray, sort, throttleFunction } from "socket-function/src/misc";
|
|
9
9
|
import { formatDateTime, formatDateTimeDetailed, formatNumber, formatTime, formatPercent } from "socket-function/src/formatting/format";
|
|
10
|
-
import {
|
|
10
|
+
import { IndexedLogs, TimeFilePathWithSize } from "./IndexedLogs";
|
|
11
11
|
import { Querysub } from "../../../4-querysub/QuerysubController";
|
|
12
12
|
import { URLParam } from "../../../library-components/URLParam";
|
|
13
13
|
import { getOwnMachineId, getOwnThreadId } from "../../../-f-node-discovery/NodeDiscovery";
|
|
@@ -21,6 +21,8 @@ import { ObjectDisplay } from "../ObjectDisplay";
|
|
|
21
21
|
import { formatDateJSX } from "../../../misc/formatJSX";
|
|
22
22
|
import { PUBLIC_MOVE_THRESHOLD } from "./BufferIndexLogsOptimizationConstants";
|
|
23
23
|
import { atomic } from "../../../2-proxy/PathValueProxyWatcher";
|
|
24
|
+
import { errorToUndefined } from "querysub/src/errors";
|
|
25
|
+
import { IndexedLogResults, createEmptyIndexedLogResults } from "./BufferIndexHelpers";
|
|
24
26
|
|
|
25
27
|
let searchText = new URLParam("searchText", "");
|
|
26
28
|
let readLiveData = new URLParam("readLiveData", false);
|
|
@@ -31,9 +33,6 @@ let savedPathsURL = new URLParam("savedPaths", "");
|
|
|
31
33
|
let selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, boolean>);
|
|
32
34
|
let useRelativeTimeURL = new URLParam("useRelativeTime", true);
|
|
33
35
|
|
|
34
|
-
// Only auto-search if the string is long enough. Otherwise, we're going to get way too many results, which could slow down the server and won't be useful to us.
|
|
35
|
-
const MIN_AUTO_SEARCH_LENGTH = 7;
|
|
36
|
-
|
|
37
36
|
const defaultSelectedFields = {
|
|
38
37
|
param0: true,
|
|
39
38
|
time: true,
|
|
@@ -43,13 +42,61 @@ const defaultSelectedFields = {
|
|
|
43
42
|
__entry: true,
|
|
44
43
|
};
|
|
45
44
|
|
|
46
|
-
function
|
|
45
|
+
function mergeIndexedLogResults(existing: IndexedLogResults, incoming: IndexedLogResults): IndexedLogResults {
|
|
46
|
+
let readsByKey = new Map<string, typeof existing.reads[0]>();
|
|
47
|
+
|
|
48
|
+
for (let read of existing.reads) {
|
|
49
|
+
let key = `${read.cached}-${read.remote}`;
|
|
50
|
+
let existingRead = readsByKey.get(key);
|
|
51
|
+
if (existingRead) {
|
|
52
|
+
existingRead.count += read.count;
|
|
53
|
+
existingRead.size += read.size;
|
|
54
|
+
existingRead.totalSize = Math.max(existingRead.totalSize, read.totalSize);
|
|
55
|
+
existingRead.totalCount = Math.max(existingRead.totalCount, read.totalCount);
|
|
56
|
+
} else {
|
|
57
|
+
readsByKey.set(key, { ...read });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (let read of incoming.reads) {
|
|
62
|
+
let key = `${read.cached}-${read.remote}`;
|
|
63
|
+
let existingRead = readsByKey.get(key);
|
|
64
|
+
if (existingRead) {
|
|
65
|
+
existingRead.count += read.count;
|
|
66
|
+
existingRead.size += read.size;
|
|
67
|
+
existingRead.totalSize = Math.max(existingRead.totalSize, read.totalSize);
|
|
68
|
+
existingRead.totalCount = Math.max(existingRead.totalCount, read.totalCount);
|
|
69
|
+
} else {
|
|
70
|
+
readsByKey.set(key, { ...read });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
matchCount: existing.matchCount + incoming.matchCount,
|
|
76
|
+
totalLocalFiles: existing.totalLocalFiles + incoming.totalLocalFiles,
|
|
77
|
+
totalBackblazeFiles: existing.totalBackblazeFiles + incoming.totalBackblazeFiles,
|
|
78
|
+
reads: Array.from(readsByKey.values()),
|
|
79
|
+
localFilesSearched: existing.localFilesSearched + incoming.localFilesSearched,
|
|
80
|
+
backblazeFilesSearched: existing.backblazeFilesSearched + incoming.backblazeFilesSearched,
|
|
81
|
+
totalBlockCount: existing.totalBlockCount + incoming.totalBlockCount,
|
|
82
|
+
blockCheckedCount: existing.blockCheckedCount + incoming.blockCheckedCount,
|
|
83
|
+
blocksCheckedCompressedSize: existing.blocksCheckedCompressedSize + incoming.blocksCheckedCompressedSize,
|
|
84
|
+
blocksCheckedDecompressedSize: existing.blocksCheckedDecompressedSize + incoming.blocksCheckedDecompressedSize,
|
|
85
|
+
blockErrors: [...existing.blockErrors, ...incoming.blockErrors],
|
|
86
|
+
fileErrors: [...existing.fileErrors, ...incoming.fileErrors],
|
|
87
|
+
remoteIndexesSearched: existing.remoteIndexesSearched + incoming.remoteIndexesSearched,
|
|
88
|
+
remoteIndexSize: existing.remoteIndexSize + incoming.remoteIndexSize,
|
|
89
|
+
localIndexesSearched: existing.localIndexesSearched + incoming.localIndexesSearched,
|
|
90
|
+
localIndexSize: existing.localIndexSize + incoming.localIndexSize,
|
|
91
|
+
timeToFirstMatch: Math.min(existing.timeToFirstMatch === 0 ? Infinity : existing.timeToFirstMatch, incoming.timeToFirstMatch === 0 ? Infinity : incoming.timeToFirstMatch),
|
|
92
|
+
fileFindTime: existing.fileFindTime + incoming.fileFindTime,
|
|
93
|
+
indexSearchTime: existing.indexSearchTime + incoming.indexSearchTime,
|
|
94
|
+
blockSearchTime: existing.blockSearchTime + incoming.blockSearchTime,
|
|
95
|
+
totalSearchTime: Math.max(existing.totalSearchTime, incoming.totalSearchTime),
|
|
96
|
+
remoteBlockCount: existing.remoteBlockCount + incoming.remoteBlockCount,
|
|
97
|
+
localBlockCount: existing.localBlockCount + incoming.localBlockCount,
|
|
98
|
+
remoteBlockCheckedCount: existing.remoteBlockCheckedCount + incoming.remoteBlockCheckedCount,
|
|
99
|
+
localBlockCheckedCount: existing.localBlockCheckedCount + incoming.localBlockCheckedCount,
|
|
53
100
|
};
|
|
54
101
|
}
|
|
55
102
|
|
|
@@ -60,10 +107,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
60
107
|
enableInfos: t.boolean(true),
|
|
61
108
|
enableWarnings: t.boolean(true),
|
|
62
109
|
enableErrors: t.boolean(true),
|
|
63
|
-
results: t.atomic<LogDatum[]>([
|
|
64
|
-
{ "time": 1771825252649.44, "__LOG_TYPE": "log", "__machineId": "1ed7a7340a015680", "__mountId": "1d590f0919b1f438.1ed7a7340a015680.querysubtest.com:7007", "__threadId": "1d590f0919b1f438", "__port": 7007, "__nodeId": "1d590f0919b1f438.1ed7a7340a015680.querysubtest.com:7007", "__entry": "D:\\repos\\qs-cyoa\\src\\server.ts", "__hostName": "DESKTOP-05MP449", "__accountName": "desktop-05mp449\\quent", "__externalIP": "99.250.124.91", "__os": "win32", "__pid": 75888, "param0": "new non-local WATCH", "path": ".,querysubtest._com.,PathFunctionRunner.,book.,Data.,edittingBooks.,hkwnmoiwhuyqenbi.,books.,bf0713806bf1c4338._querysubtest._com_1755390476654._5208_1.,nodes.,1770378259589._5535.,aiCalls.,", "watcher": "client:127.0.0.1:1771825250819.3994:0.634106584461968", "diskAudit": true },
|
|
65
|
-
{ "time": 1771825252649.4397, "__LOG_TYPE": "log", "__machineId": "1ed7a7340a015680", "__mountId": "1d590f0919b1f438.1ed7a7340a015680.querysubtest.com:7007", "__threadId": "x1d590f0919b1f438", "__port": 7007, "__nodeId": "1d590f0919b1f438.1ed7a7340a015680.querysubtest.com:7007", "__entry": "D:\\repos\\qs-cyoa\\src\\server.ts", "__hostName": "DESKTOP-05MP449", "__accountName": "desktop-05mp449\\quent", "__externalIP": "99.250.124.91", "__os": "win32", "__pid": 75888, "param0": "new non-local WATCH", "path": ".,querysubtest._com.,PathFunctionRunner.,book.,Data.,edittingBooks.,hkwnmoiwhuyqenbi.,books.,bf0713806bf1c4338._querysubtest._com_1755390476654._5208_1.,nodes.,1770378259589._5535.,totalTime.,", "watcher": "client:127.0.0.1:1771825250819.3994:0.634106584461968", "diskAudit": true },
|
|
66
|
-
]),
|
|
110
|
+
results: t.atomic<LogDatum[]>([]),
|
|
67
111
|
searching: t.boolean,
|
|
68
112
|
searchingLogs: t.boolean,
|
|
69
113
|
searchingInfos: t.boolean,
|
|
@@ -72,8 +116,13 @@ export class LogViewer3 extends qreact.Component {
|
|
|
72
116
|
paths: t.atomic<TimeFilePathWithSize[]>([]),
|
|
73
117
|
loadingPaths: t.boolean,
|
|
74
118
|
stats: t.atomic<IndexedLogResults | undefined>(undefined),
|
|
119
|
+
hasSearched: t.boolean(false),
|
|
120
|
+
forceMoveStartTime: t.atomic<number | undefined>(undefined),
|
|
121
|
+
forceMoveEndTime: t.atomic<number | undefined>(undefined),
|
|
75
122
|
});
|
|
76
123
|
|
|
124
|
+
private searchSequenceNumber = 0;
|
|
125
|
+
|
|
77
126
|
componentDidMount(): void {
|
|
78
127
|
void this.loadPaths();
|
|
79
128
|
}
|
|
@@ -118,20 +167,29 @@ export class LogViewer3 extends qreact.Component {
|
|
|
118
167
|
|
|
119
168
|
let totalSizeRead = 0;
|
|
120
169
|
let cachedSize = 0;
|
|
170
|
+
let cachedCount = 0;
|
|
121
171
|
let uncachedSize = 0;
|
|
122
172
|
let uncachedCount = 0;
|
|
123
173
|
let uncachedRemoteSize = 0;
|
|
124
174
|
let uncachedRemoteCount = 0;
|
|
125
175
|
let totalSize = 0;
|
|
176
|
+
let remoteTotalSize = 0;
|
|
177
|
+
let localTotalSize = 0;
|
|
126
178
|
|
|
127
179
|
for (let read of stats.reads) {
|
|
128
180
|
totalSizeRead += read.size;
|
|
129
181
|
if (read.cached) {
|
|
130
182
|
cachedSize += read.size;
|
|
183
|
+
cachedCount += read.count;
|
|
131
184
|
} else {
|
|
132
185
|
uncachedSize += read.size;
|
|
133
186
|
uncachedCount += read.count;
|
|
134
187
|
totalSize += read.totalSize;
|
|
188
|
+
if (read.remote) {
|
|
189
|
+
remoteTotalSize += read.totalSize;
|
|
190
|
+
} else {
|
|
191
|
+
localTotalSize += read.totalSize;
|
|
192
|
+
}
|
|
135
193
|
}
|
|
136
194
|
if (read.remote && !read.cached) {
|
|
137
195
|
uncachedRemoteSize += read.size;
|
|
@@ -161,21 +219,28 @@ export class LogViewer3 extends qreact.Component {
|
|
|
161
219
|
};
|
|
162
220
|
|
|
163
221
|
const fileItems = [
|
|
164
|
-
`${formatTime(stats.fileFindTime)} | ${formatNumber(stats.localFilesSearched + stats.backblazeFilesSearched)} | ${formatNumber(totalSize)}B`,
|
|
165
|
-
`
|
|
222
|
+
`${formatTime(stats.fileFindTime)} | ${formatNumber(stats.localFilesSearched + stats.backblazeFilesSearched)} / ${formatNumber(stats.totalLocalFiles + stats.totalBackblazeFiles)} | ${formatNumber(totalSize)}B`,
|
|
223
|
+
`remote ${formatNumber(stats.backblazeFilesSearched)} / ${formatNumber(stats.totalBackblazeFiles)} | ${formatNumber(remoteTotalSize)}B`,
|
|
224
|
+
`pending ${formatNumber(stats.localFilesSearched)} / ${formatNumber(stats.totalLocalFiles)} | ${formatNumber(localTotalSize)}B`,
|
|
166
225
|
];
|
|
167
226
|
|
|
227
|
+
const totalIndexesSearched = stats.remoteIndexesSearched + stats.localIndexesSearched;
|
|
228
|
+
const totalIndexSize = stats.remoteIndexSize + stats.localIndexSize;
|
|
168
229
|
const indexItems = [
|
|
169
|
-
`${formatTime(stats.indexSearchTime)} | ${formatNumber(
|
|
230
|
+
`${formatTime(stats.indexSearchTime)} | ${formatNumber(totalIndexesSearched)} | ${formatNumber(totalIndexSize)}B | ${formatNumber(totalSize / totalIndexSize)}X`,
|
|
231
|
+
`remote ${formatNumber(stats.remoteIndexesSearched)} | ${formatNumber(stats.remoteIndexSize)}B | ${formatNumber(remoteTotalSize / stats.remoteIndexSize)}X`,
|
|
232
|
+
`local ${formatNumber(stats.localIndexesSearched)} | ${formatNumber(stats.localIndexSize)}B | ${formatNumber(localTotalSize / stats.localIndexSize)}X`,
|
|
170
233
|
];
|
|
171
234
|
|
|
172
235
|
const blockItems = [
|
|
173
|
-
|
|
174
|
-
`total
|
|
236
|
+
<div title={`Scanned size = ${formatNumber(stats.blocksCheckedCompressedSize)}B, Decompressed size = ${formatNumber(stats.blocksCheckedDecompressedSize)}B, Compression ratio = ${formatNumber(stats.blocksCheckedDecompressedSize / stats.blocksCheckedCompressedSize)}X`}>{formatTime(stats.blockSearchTime)} | {formatNumber(stats.blockCheckedCount)} | {formatNumber(stats.blocksCheckedCompressedSize)}B | {formatNumber(stats.blocksCheckedDecompressedSize / stats.blocksCheckedCompressedSize)}X</div>,
|
|
237
|
+
`total scanned ${formatNumber(stats.totalBlockCount)} (${formatPercent(stats.blockCheckedCount / stats.totalBlockCount)})`,
|
|
238
|
+
`remote ${formatNumber(stats.remoteBlockCheckedCount)} / ${formatNumber(stats.remoteBlockCount)} (${formatPercent(stats.remoteBlockCheckedCount / stats.remoteBlockCount)}) | local ${formatNumber(stats.localBlockCheckedCount)} / ${formatNumber(stats.localBlockCount)} (${formatPercent(stats.localBlockCheckedCount / stats.localBlockCount)})`,
|
|
175
239
|
];
|
|
176
240
|
let cacheItems = [
|
|
177
|
-
`disk read ${formatNumber(uncachedSize)}B`,
|
|
241
|
+
`disk read ${formatNumber(uncachedSize)}B (${formatNumber(uncachedCount)})`,
|
|
178
242
|
`remote ${formatPercent(uncachedRemoteSize / totalSizeRead)} (${formatNumber(uncachedRemoteCount)})`,
|
|
243
|
+
`cached ${formatNumber(cachedSize)} (${formatNumber(cachedCount)})`,
|
|
179
244
|
];
|
|
180
245
|
|
|
181
246
|
const resultItems = [
|
|
@@ -194,14 +259,15 @@ export class LogViewer3 extends qreact.Component {
|
|
|
194
259
|
{renderStage("Files", fileItems, 290, true, false)}
|
|
195
260
|
{renderStage("Indexes", indexItems, 250, false, false)}
|
|
196
261
|
{renderStage("Blocks", blockItems, 210, false, false)}
|
|
197
|
-
{renderStage("
|
|
262
|
+
{renderStage("Reads", cacheItems, 160, false, false)}
|
|
198
263
|
{renderStage("Results", resultItems, 120, false, false)}
|
|
199
264
|
{renderStage("Done", doneItems, undefined, false, !hasErrors)}
|
|
200
265
|
{(() => {
|
|
201
266
|
if (!hasErrors) return undefined;
|
|
202
267
|
let errorItems: preact.ComponentChild[] = [];
|
|
203
268
|
if (stats.fileErrors.length > 0) {
|
|
204
|
-
|
|
269
|
+
const errorTitle = stats.fileErrors.map(e => `${e.path}:\n${e.error}`).join("\n\n");
|
|
270
|
+
errorItems.push(<div title={errorTitle}>{stats.fileErrors.length} files failed</div>);
|
|
205
271
|
}
|
|
206
272
|
if (stats.blockErrors.length > 0) {
|
|
207
273
|
errorItems.push(<div title={stats.blockErrors.join("\n")}>{stats.blockErrors.length} blocks failed</div>);
|
|
@@ -217,7 +283,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
217
283
|
const now = Date.now();
|
|
218
284
|
const threshold = now - (2 * PUBLIC_MOVE_THRESHOLD);
|
|
219
285
|
|
|
220
|
-
const machineWarnings: Record<string, { machineId: string; oldestTime: number }> = {};
|
|
286
|
+
const machineWarnings: Record<string, { machineId: string; oldestTime: number; count: number; totalSize: number }> = {};
|
|
221
287
|
|
|
222
288
|
for (const path of paths) {
|
|
223
289
|
if (path.logCount !== undefined) continue;
|
|
@@ -227,26 +293,102 @@ export class LogViewer3 extends qreact.Component {
|
|
|
227
293
|
if (!path.machineId) continue;
|
|
228
294
|
|
|
229
295
|
const existing = machineWarnings[path.machineId];
|
|
230
|
-
if (!existing
|
|
296
|
+
if (!existing) {
|
|
231
297
|
machineWarnings[path.machineId] = {
|
|
232
298
|
machineId: path.machineId,
|
|
233
299
|
oldestTime: path.startTime,
|
|
300
|
+
count: 1,
|
|
301
|
+
totalSize: path.size || 0,
|
|
234
302
|
};
|
|
303
|
+
} else {
|
|
304
|
+
if (path.startTime < existing.oldestTime) {
|
|
305
|
+
existing.oldestTime = path.startTime;
|
|
306
|
+
}
|
|
307
|
+
existing.count++;
|
|
308
|
+
existing.totalSize += path.size || 0;
|
|
235
309
|
}
|
|
236
310
|
}
|
|
237
311
|
|
|
238
312
|
const warnings = Object.values(machineWarnings);
|
|
239
313
|
if (warnings.length === 0) return undefined;
|
|
240
314
|
|
|
315
|
+
const isFrozen = !!savedPathsURL.value;
|
|
316
|
+
|
|
241
317
|
return (
|
|
242
|
-
<div className={css.vbox(10).pad2(10).hsl(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
318
|
+
<div className={css.vbox(10).pad2(10).hsl(0, 50, 50).colorhsl(60, 50, 100)}>
|
|
319
|
+
{isFrozen && (
|
|
320
|
+
<>
|
|
321
|
+
<div className={css.fontWeight(600)}>Frozen files are too old:</div>
|
|
322
|
+
{warnings.map((warning) => (
|
|
323
|
+
<div key={warning.machineId} className={css.hbox(10)}>
|
|
324
|
+
<MachineThreadInfo machineId={warning.machineId} />
|
|
325
|
+
<span>has {formatNumber(warning.count)} pending files ({formatNumber(warning.totalSize)}B) that are {formatTime(now - warning.oldestTime)} old</span>
|
|
326
|
+
</div>
|
|
327
|
+
))}
|
|
328
|
+
<Button
|
|
329
|
+
onClick={() => {
|
|
330
|
+
this.clearFrozenPaths();
|
|
331
|
+
void this.loadPaths();
|
|
332
|
+
}}
|
|
333
|
+
hue={180}
|
|
334
|
+
>
|
|
335
|
+
Clear Frozen Files and Reload
|
|
336
|
+
</Button>
|
|
337
|
+
</>
|
|
338
|
+
)}
|
|
339
|
+
{!isFrozen && (
|
|
340
|
+
<>
|
|
341
|
+
<div className={css.fontWeight(600)}>FIX pending logs not being merged!</div>
|
|
342
|
+
{warnings.map((warning) => (
|
|
343
|
+
<div key={warning.machineId} className={css.hbox(10)}>
|
|
344
|
+
<MachineThreadInfo machineId={warning.machineId} />
|
|
345
|
+
<span>is not moving logs to remote storage. {formatNumber(warning.count)} files ({formatNumber(warning.totalSize)}B) are {formatTime(now - warning.oldestTime)} old</span>
|
|
346
|
+
</div>
|
|
347
|
+
))}
|
|
348
|
+
<Button
|
|
349
|
+
onClick={async () => {
|
|
350
|
+
Querysub.commitLocal(() => {
|
|
351
|
+
this.state.forceMoveStartTime = Date.now();
|
|
352
|
+
this.state.forceMoveEndTime = undefined;
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
let forceMoveInterval = setInterval(() => {
|
|
356
|
+
this.forceUpdate();
|
|
357
|
+
}, 100);
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
let loggers = await getLoggers2Async();
|
|
361
|
+
for (let logger of Object.values(loggers)) {
|
|
362
|
+
await logger.clientForceMoveLogsToPublic();
|
|
363
|
+
}
|
|
364
|
+
await this.loadPaths();
|
|
365
|
+
} finally {
|
|
366
|
+
if (forceMoveInterval) {
|
|
367
|
+
clearInterval(forceMoveInterval);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
Querysub.commitLocal(() => {
|
|
371
|
+
this.state.forceMoveEndTime = Date.now();
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}}
|
|
375
|
+
hue={180}
|
|
376
|
+
>
|
|
377
|
+
Force Move Logs to Public
|
|
378
|
+
</Button>
|
|
379
|
+
{(() => {
|
|
380
|
+
if (this.state.forceMoveStartTime !== undefined && this.state.forceMoveEndTime === undefined) {
|
|
381
|
+
const elapsed = Date.now() - this.state.forceMoveStartTime;
|
|
382
|
+
return <div>Moving logs... {formatTime(elapsed)}</div>;
|
|
383
|
+
}
|
|
384
|
+
if (this.state.forceMoveStartTime !== undefined && this.state.forceMoveEndTime !== undefined) {
|
|
385
|
+
const duration = this.state.forceMoveEndTime - this.state.forceMoveStartTime;
|
|
386
|
+
return <div>Moved logs in {formatTime(duration)}</div>;
|
|
387
|
+
}
|
|
388
|
+
return undefined;
|
|
389
|
+
})()}
|
|
390
|
+
</>
|
|
391
|
+
)}
|
|
250
392
|
</div>
|
|
251
393
|
);
|
|
252
394
|
}
|
|
@@ -256,7 +398,9 @@ export class LogViewer3 extends qreact.Component {
|
|
|
256
398
|
return;
|
|
257
399
|
}
|
|
258
400
|
|
|
259
|
-
|
|
401
|
+
Querysub.commitLocal(() => {
|
|
402
|
+
this.state.loadingPaths = true;
|
|
403
|
+
});
|
|
260
404
|
|
|
261
405
|
let loggers = await getLoggers2Async();
|
|
262
406
|
let selectedLoggers: typeof loggers.logLogs[] = [];
|
|
@@ -293,8 +437,21 @@ export class LogViewer3 extends qreact.Component {
|
|
|
293
437
|
});
|
|
294
438
|
}
|
|
295
439
|
|
|
440
|
+
cancel = async () => {
|
|
441
|
+
this.searchSequenceNumber++;
|
|
442
|
+
let loggers = await getLoggers2Async();
|
|
443
|
+
for (let logger of Object.values(loggers)) {
|
|
444
|
+
logger.clientCancelAllCallbacks();
|
|
445
|
+
}
|
|
446
|
+
Querysub.commitLocal(() => {
|
|
447
|
+
this.state.searching = false;
|
|
448
|
+
this.state.hasSearched = false;
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
|
|
296
452
|
|
|
297
|
-
search =
|
|
453
|
+
search = async () => {
|
|
454
|
+
await this.cancel();
|
|
298
455
|
Querysub.commitLocal(() => {
|
|
299
456
|
this.state.searching = true;
|
|
300
457
|
this.state.results = [];
|
|
@@ -305,15 +462,6 @@ export class LogViewer3 extends qreact.Component {
|
|
|
305
462
|
this.state.searchingErrors = false;
|
|
306
463
|
});
|
|
307
464
|
|
|
308
|
-
let startTime = Date.now();
|
|
309
|
-
|
|
310
|
-
let hasPaths = Querysub.localRead(() => this.getPaths().length > 0);
|
|
311
|
-
let getFilesTime = 0;
|
|
312
|
-
if (readLiveData.value || !hasPaths) {
|
|
313
|
-
let startTime = Date.now();
|
|
314
|
-
await this.loadPaths();
|
|
315
|
-
getFilesTime = Date.now() - startTime;
|
|
316
|
-
}
|
|
317
465
|
|
|
318
466
|
let loggers = await getLoggers2Async();
|
|
319
467
|
|
|
@@ -337,6 +485,20 @@ export class LogViewer3 extends qreact.Component {
|
|
|
337
485
|
}
|
|
338
486
|
});
|
|
339
487
|
|
|
488
|
+
this.searchSequenceNumber++;
|
|
489
|
+
let currentSequenceNumber = this.searchSequenceNumber;
|
|
490
|
+
|
|
491
|
+
let startTime = Date.now();
|
|
492
|
+
|
|
493
|
+
let hasPaths = Querysub.localRead(() => this.getPaths().length > 0);
|
|
494
|
+
let getFilesTime = 0;
|
|
495
|
+
if (readLiveData.value || !hasPaths) {
|
|
496
|
+
let startTime = Date.now();
|
|
497
|
+
await this.loadPaths();
|
|
498
|
+
getFilesTime = Date.now() - startTime;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
340
502
|
if (selectedLoggers.length === 0) {
|
|
341
503
|
console.error("No log sources selected");
|
|
342
504
|
Querysub.commitLocal(() => {
|
|
@@ -347,25 +509,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
347
509
|
|
|
348
510
|
let searchBuffer = Querysub.localRead(() => Buffer.from(searchText.value, "utf8"));
|
|
349
511
|
let results: LogDatum[] = [];
|
|
350
|
-
let stats: IndexedLogResults =
|
|
351
|
-
matchCount: 0,
|
|
352
|
-
reads: [],
|
|
353
|
-
localFilesSearched: 0,
|
|
354
|
-
backblazeFilesSearched: 0,
|
|
355
|
-
totalBlockCount: 0,
|
|
356
|
-
blockCheckedCount: 0,
|
|
357
|
-
blocksCheckedCompressedSize: 0,
|
|
358
|
-
blocksCheckedDecompressedSize: 0,
|
|
359
|
-
blockErrors: [],
|
|
360
|
-
fileErrors: [],
|
|
361
|
-
indexesSearched: 0,
|
|
362
|
-
indexSize: 0,
|
|
363
|
-
timeToFirstMatch: 0,
|
|
364
|
-
fileFindTime: 0,
|
|
365
|
-
indexSearchTime: 0,
|
|
366
|
-
blockSearchTime: 0,
|
|
367
|
-
totalSearchTime: 0,
|
|
368
|
-
};
|
|
512
|
+
let stats: IndexedLogResults = createEmptyIndexedLogResults();
|
|
369
513
|
stats.fileFindTime += getFilesTime;
|
|
370
514
|
|
|
371
515
|
let paths = Querysub.localRead(() => this.getPaths());
|
|
@@ -373,13 +517,33 @@ export class LogViewer3 extends qreact.Component {
|
|
|
373
517
|
let range = Querysub.localRead(() => getTimeRange());
|
|
374
518
|
|
|
375
519
|
let updateResults = throttleFunction(100, () => {
|
|
520
|
+
if (this.searchSequenceNumber !== currentSequenceNumber) return;
|
|
376
521
|
Querysub.commitLocal(() => {
|
|
377
522
|
this.state.results = results;
|
|
378
523
|
});
|
|
379
524
|
});
|
|
380
525
|
|
|
526
|
+
let loggerResults = new Map<typeof loggers.logLogs, IndexedLogResults>();
|
|
527
|
+
|
|
528
|
+
let updateStats = () => {
|
|
529
|
+
if (this.searchSequenceNumber !== currentSequenceNumber) return;
|
|
530
|
+
|
|
531
|
+
let mergedStats: IndexedLogResults = createEmptyIndexedLogResults();
|
|
532
|
+
|
|
533
|
+
for (let loggerResult of loggerResults.values()) {
|
|
534
|
+
mergedStats = mergeIndexedLogResults(mergedStats, loggerResult);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
mergedStats.totalSearchTime = Date.now() - startTime;
|
|
538
|
+
|
|
539
|
+
Querysub.commitLocal(() => {
|
|
540
|
+
this.state.stats = mergedStats;
|
|
541
|
+
});
|
|
542
|
+
};
|
|
543
|
+
|
|
381
544
|
await Promise.all(selectedLoggers.map(async (logger) => {
|
|
382
|
-
let
|
|
545
|
+
let done = false;
|
|
546
|
+
let result = await errorToUndefined(logger.clientFind({
|
|
383
547
|
params: {
|
|
384
548
|
startTime: range.startTime,
|
|
385
549
|
endTime: range.endTime,
|
|
@@ -392,25 +556,20 @@ export class LogViewer3 extends qreact.Component {
|
|
|
392
556
|
results.push(match);
|
|
393
557
|
void updateResults();
|
|
394
558
|
},
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
stats.indexSearchTime += result.indexSearchTime;
|
|
409
|
-
stats.blockSearchTime += result.blockSearchTime;
|
|
410
|
-
stats.totalSearchTime = Date.now() - startTime;
|
|
559
|
+
onResults: (loggerStats: IndexedLogResults) => {
|
|
560
|
+
if (done) return;
|
|
561
|
+
loggerResults.set(logger, loggerStats);
|
|
562
|
+
updateStats();
|
|
563
|
+
},
|
|
564
|
+
}));
|
|
565
|
+
done = true;
|
|
566
|
+
|
|
567
|
+
if (result) {
|
|
568
|
+
loggerResults.set(logger, result);
|
|
569
|
+
updateStats();
|
|
570
|
+
}
|
|
571
|
+
|
|
411
572
|
Querysub.commitLocal(() => {
|
|
412
|
-
this.state.results = results;
|
|
413
|
-
this.state.stats = stats;
|
|
414
573
|
if (logger === loggers.logLogs) this.state.searchingLogs = false;
|
|
415
574
|
if (logger === loggers.infoLogs) this.state.searchingInfos = false;
|
|
416
575
|
if (logger === loggers.warnLogs) this.state.searchingWarnings = false;
|
|
@@ -418,14 +577,13 @@ export class LogViewer3 extends qreact.Component {
|
|
|
418
577
|
});
|
|
419
578
|
}));
|
|
420
579
|
|
|
421
|
-
stats.totalSearchTime = Date.now() - startTime;
|
|
422
580
|
|
|
423
581
|
Querysub.commitLocal(() => {
|
|
424
582
|
this.state.results = results;
|
|
425
583
|
this.state.searching = false;
|
|
426
|
-
this.state.
|
|
584
|
+
this.state.hasSearched = true;
|
|
427
585
|
});
|
|
428
|
-
}
|
|
586
|
+
};
|
|
429
587
|
|
|
430
588
|
renderResults() {
|
|
431
589
|
let fieldNames = new Set<string>();
|
|
@@ -534,7 +692,7 @@ export class LogViewer3 extends qreact.Component {
|
|
|
534
692
|
/>
|
|
535
693
|
</div>
|
|
536
694
|
|
|
537
|
-
{this.state.results.length === 0 && !this.state.searching && <div className={css.hsl(40, 50, 50).colorhsl(60, 50, 100).boldStyle.pad2(10).ellipsis}>
|
|
695
|
+
{this.state.results.length === 0 && !this.state.searching && this.state.hasSearched && <div className={css.hsl(40, 50, 50).colorhsl(60, 50, 100).boldStyle.pad2(10).ellipsis}>
|
|
538
696
|
No logs matched, try adjusting your search or time range.
|
|
539
697
|
</div>}
|
|
540
698
|
|
|
@@ -629,11 +787,6 @@ export class LogViewer3 extends qreact.Component {
|
|
|
629
787
|
fillWidth
|
|
630
788
|
focusOnMount
|
|
631
789
|
url={searchText}
|
|
632
|
-
onChangeValue={(value) => {
|
|
633
|
-
if (value.length >= MIN_AUTO_SEARCH_LENGTH) {
|
|
634
|
-
void this.search();
|
|
635
|
-
}
|
|
636
|
-
}}
|
|
637
790
|
onKeyDown={(e) => {
|
|
638
791
|
if (e.key === "Enter") {
|
|
639
792
|
searchText.value = e.currentTarget.value;
|
|
@@ -668,27 +821,78 @@ export class LogViewer3 extends qreact.Component {
|
|
|
668
821
|
>
|
|
669
822
|
Search
|
|
670
823
|
</Button>
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
{this.state.loadingPaths && <div>Loading paths...</div>}
|
|
674
|
-
{this.state.searching && <div>Searching...</div>}
|
|
675
|
-
|
|
676
|
-
{this.getPaths().length > 0 && (
|
|
677
|
-
<div className={css.hbox(10).fillWidth}>
|
|
678
|
-
<FilePathSelector
|
|
679
|
-
paths={this.getPaths()}
|
|
680
|
-
onChange={(paths) => {
|
|
681
|
-
this.state.paths = paths;
|
|
682
|
-
}}
|
|
683
|
-
/>
|
|
824
|
+
{this.state.searching && (
|
|
684
825
|
<Button
|
|
685
|
-
|
|
686
|
-
|
|
826
|
+
flavor="large"
|
|
827
|
+
onClick={() => void this.cancel()}
|
|
828
|
+
hue={60}
|
|
687
829
|
>
|
|
688
|
-
|
|
830
|
+
Cancel
|
|
689
831
|
</Button>
|
|
690
|
-
|
|
691
|
-
|
|
832
|
+
)}
|
|
833
|
+
</div>
|
|
834
|
+
|
|
835
|
+
<div className={css.hbox(10).fillWidth}>
|
|
836
|
+
<FilePathSelector
|
|
837
|
+
paths={this.getPaths()}
|
|
838
|
+
onChange={(paths) => {
|
|
839
|
+
this.state.paths = paths;
|
|
840
|
+
this.freezePaths();
|
|
841
|
+
}}
|
|
842
|
+
fileErrors={this.state.stats?.fileErrors}
|
|
843
|
+
/>
|
|
844
|
+
<Button
|
|
845
|
+
onClick={() => {
|
|
846
|
+
if (savedPathsURL.value) {
|
|
847
|
+
this.clearFrozenPaths();
|
|
848
|
+
void this.loadPaths();
|
|
849
|
+
} else {
|
|
850
|
+
this.freezePaths();
|
|
851
|
+
}
|
|
852
|
+
}}
|
|
853
|
+
hue={savedPathsURL.value ? 0 : 180}
|
|
854
|
+
>
|
|
855
|
+
{savedPathsURL.value ? `Clear Frozen Files (${this.getPaths().length})` : `Freeze Files (${this.state.paths.length})`}
|
|
856
|
+
</Button>
|
|
857
|
+
{!savedPathsURL.value && (
|
|
858
|
+
<div className={css.pad2(8, 4).hsl(40, 60, 50).colorhsl(40, 0, 100)}>
|
|
859
|
+
Files frozen in memory. Click Preview Files to refresh.
|
|
860
|
+
</div>
|
|
861
|
+
)}
|
|
862
|
+
{(() => {
|
|
863
|
+
if (!savedPathsURL.value) return undefined;
|
|
864
|
+
const paths = this.getPaths();
|
|
865
|
+
const pendingCount = paths.filter(x => x.logCount === undefined).length;
|
|
866
|
+
if (pendingCount === 0) return undefined;
|
|
867
|
+
return (
|
|
868
|
+
<Button
|
|
869
|
+
onClick={() => {
|
|
870
|
+
const paths = this.getPaths();
|
|
871
|
+
const nonPendingPaths = paths.filter(x => x.logCount !== undefined);
|
|
872
|
+
const json = JSON.stringify(nonPendingPaths);
|
|
873
|
+
const buffer = Buffer.from(json, "utf8");
|
|
874
|
+
const compressed = LZ4.compress(buffer);
|
|
875
|
+
savedPathsURL.value = compressed.toString("base64");
|
|
876
|
+
}}
|
|
877
|
+
hue={40}
|
|
878
|
+
>
|
|
879
|
+
Remove Pending ({pendingCount})
|
|
880
|
+
</Button>
|
|
881
|
+
);
|
|
882
|
+
})()}
|
|
883
|
+
|
|
884
|
+
{this.state.loadingPaths && <div>Loading paths...</div>}
|
|
885
|
+
{this.state.searching && (() => {
|
|
886
|
+
let searchingSources: string[] = [];
|
|
887
|
+
if (this.state.searchingLogs) searchingSources.push("Logs");
|
|
888
|
+
if (this.state.searchingInfos) searchingSources.push("Infos");
|
|
889
|
+
if (this.state.searchingWarnings) searchingSources.push("Warnings");
|
|
890
|
+
if (this.state.searchingErrors) searchingSources.push("Errors");
|
|
891
|
+
return <div className={css.pad2(4, 2).hsl(120, 40, 50).colorhsl(120, 0, 100)}>
|
|
892
|
+
Searching: {searchingSources.join(", ")}
|
|
893
|
+
</div>;
|
|
894
|
+
})()}
|
|
895
|
+
</div>
|
|
692
896
|
|
|
693
897
|
{this.renderPendingLogWarnings()}
|
|
694
898
|
|
|
@@ -70,7 +70,7 @@ const KEY_MAPPING: Record<string, { field: keyof Omit<TimeFilePath, "fullPath">;
|
|
|
70
70
|
"compressed": { field: "compressedSize", parseAsNumber: true },
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
function decodeLogFilePath(path: string): TimeFilePath | undefined {
|
|
73
|
+
export function decodeLogFilePath(path: string): TimeFilePath | undefined {
|
|
74
74
|
if (!path.endsWith(LOG_FILE_EXTENSION)) {
|
|
75
75
|
return undefined;
|
|
76
76
|
}
|