querysub 0.374.0 → 0.376.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.
Files changed (38) hide show
  1. package/package.json +2 -4
  2. package/src/deployManager/components/MachineDetailPage.tsx +2 -5
  3. package/src/deployManager/components/ServiceDetailPage.tsx +2 -5
  4. package/src/deployManager/machineApplyMainCode.ts +7 -0
  5. package/src/diagnostics/NodeViewer.tsx +4 -5
  6. package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +10 -5
  7. package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.cpp +20 -0
  8. package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +29 -2
  9. package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +61 -20
  10. package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +2 -2
  11. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +7 -7
  12. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +250 -243
  13. package/src/diagnostics/logs/IndexedLogs/LogViewerParams.ts +21 -0
  14. package/src/diagnostics/logs/IndexedLogs/{bufferMatcher.ts → bufferSearchFindMatcher.ts} +9 -4
  15. package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +3 -3
  16. package/src/diagnostics/logs/diskLogger.ts +0 -38
  17. package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +9 -0
  18. package/src/diagnostics/logs/injectFileLocationToConsole.ts +3 -0
  19. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +24 -22
  20. package/src/diagnostics/managementPages.tsx +0 -18
  21. package/test.ts +0 -5
  22. package/bin/error-email.js +0 -8
  23. package/bin/error-im.js +0 -8
  24. package/src/diagnostics/logs/FastArchiveAppendable.ts +0 -843
  25. package/src/diagnostics/logs/FastArchiveController.ts +0 -573
  26. package/src/diagnostics/logs/FastArchiveViewer.tsx +0 -1090
  27. package/src/diagnostics/logs/LogViewer2.tsx +0 -552
  28. package/src/diagnostics/logs/errorNotifications/ErrorDigestPage.tsx +0 -409
  29. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +0 -756
  30. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +0 -280
  31. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +0 -254
  32. package/src/diagnostics/logs/errorNotifications/errorDigestEmail.tsx +0 -233
  33. package/src/diagnostics/logs/errorNotifications/errorDigestEntry.tsx +0 -14
  34. package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +0 -292
  35. package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +0 -209
  36. package/src/diagnostics/logs/importLogsEntry.ts +0 -38
  37. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +0 -150
  38. package/src/diagnostics/logs/logViewerExtractField.ts +0 -36
@@ -1,1090 +0,0 @@
1
- import { measureFnc } from "socket-function/src/profiling/measure";
2
- import { t } from "../../2-proxy/schema2";
3
- import { qreact } from "../../4-dom/qreact";
4
- import { DatumStats, FastArchiveAppendable } from "./FastArchiveAppendable";
5
- import { Querysub } from "../../4-querysub/Querysub";
6
- import { URLParam } from "../../library-components/URLParam";
7
- import { TimeRangeSelector, getTimeRange } from "./TimeRangeSelector";
8
- import { formatNumber, formatTime, formatVeryNiceDateTime, formatDateTime } from "socket-function/src/formatting/format";
9
- import { list, sort, throttleFunction, timeInHour } from "socket-function/src/misc";
10
- import { css } from "typesafecss";
11
- import { logErrors } from "../../errors";
12
- import { InputLabelURL } from "../../library-components/InputLabel";
13
- import { clamp } from "../../misc";
14
- import { ButtonSelector } from "../../library-components/ButtonSelector";
15
- import { Button } from "../../library-components/Button";
16
- import { lazy } from "socket-function/src/caching";
17
- import { LOG_LIMIT_FLAG } from "./diskLogger";
18
- import { MaybePromise, canHaveChildren } from "socket-function/src/types";
19
- import { niceParse } from "../../niceStringify";
20
- import { FileMetadata } from "./FastArchiveController";
21
- import { throttleRender } from "../../functional/throttleRender";
22
-
23
- const RENDER_INTERVAL = 1000;
24
-
25
- const HISTOGRAM_RERENDER_INTERVAL = 10000;
26
-
27
- export const filterParam = new URLParam("filter", "");
28
- // If filter2 is set, we also need it. Not great (why not support arbitrary counts of these), but for now... this should be fine (and support arbitrary counts might slow down our incredibly optimized scan function, where most of our time is spent).
29
- // NOTE: This supports less than filterParam (no object parsing, it only does text contains)
30
- export const filterParam2 = new URLParam("filter2", "");
31
-
32
- export const cacheBustParam = new URLParam("cacheBust", 0);
33
- const caseInsensitiveParam = new URLParam("caseInsensitive", false);
34
- const hideAllDataParam = new URLParam("hideAllData", false);
35
- export type ScanFnc = (posStart: number, posEnd: number, data: Buffer) => boolean;
36
-
37
- export class FastArchiveViewer<T> extends qreact.Component<{
38
- fastArchives: () => FastArchiveAppendable<T>[];
39
- forceGetPublic?: boolean;
40
- runOnLoad?: boolean;
41
- onStart: () => MaybePromise<void>;
42
- getScanFnc?: () => ScanFnc;
43
- getWantData?: (file: FileMetadata) => Promise<ScanFnc | undefined>;
44
- onDatums: (source: FastArchiveAppendable<T>, datums: T[], metadata: FileMetadata) => void;
45
- // Called after onData
46
- onStats?: (source: FastArchiveAppendable<T>, stats: DatumStats, metadata: FileMetadata) => void;
47
- onFinish?: () => void;
48
- }> {
49
- state = t.state({
50
- runCount: t.atomic<number>(0),
51
- // rootPath =>
52
- fileMetadata: t.atomic<({
53
- files: FileMetadata[];
54
- createTime?: number;
55
- } | undefined)[]>([]),
56
- finished: t.atomic(true),
57
- error: t.atomic<string | undefined>(undefined),
58
- pendingSyncInitializations: t.atomic<number>(0),
59
-
60
- // Current sync parameters - set before synchronizeData is called
61
- currentSyncParams: t.atomic<{
62
- filterString: string;
63
- startTime: number;
64
- endTime: number;
65
- fastArchivePaths: string[];
66
- } | undefined>(undefined),
67
-
68
- renderSeqNum: t.atomic<number>(0),
69
- });
70
-
71
- // Histogram data arrays
72
- private histogramAllDataCounts: Float64Array = new Float64Array(0);
73
- private histogramSelectedDataCounts: Float64Array = new Float64Array(0);
74
- private histogramAllDataSizes: Float64Array = new Float64Array(0);
75
- private histogramSelectedDataSizes: Float64Array = new Float64Array(0);
76
- private histogramStartTime: number = 0;
77
- private histogramEndTime: number = 0;
78
- private histogramBucketTime: number = timeInHour;
79
-
80
- private allSize = 0;
81
- private matchedSize = 0;
82
-
83
- private limitedScanCount = 0;
84
- private limitedMatchCount = 0;
85
-
86
- private fileErrors: Array<{ file: FileMetadata; error: Error }> = [];
87
-
88
- private progressBars: Record<string, {
89
- section: string,
90
- value: number,
91
- max: number,
92
- initialTime: number,
93
- lastSetTime: number,
94
- }> = {};
95
-
96
- private lastRenderTime = 0;
97
-
98
- private getHistogramBucketIndex(startTime: number): number {
99
- const index = Math.floor((startTime - this.histogramStartTime) / this.histogramBucketTime);
100
- return clamp(index, 0, this.histogramAllDataCounts.length - 1);
101
- }
102
-
103
- componentWillMount(): void {
104
- qreact.onDispose(() => {
105
- this.cancelAllSynchronizes();
106
- });
107
- }
108
-
109
- cancelAllSynchronizes() {
110
- for (let fastArchive of this.props.fastArchives()) {
111
- fastArchive.cancelAllSynchronizes();
112
- }
113
- }
114
-
115
- private currentSequenceNumber = 0;
116
- private latestSequenceNumber = 0;
117
-
118
- @measureFnc
119
- private async synchronizeData() {
120
- console.log("synchronizeData");
121
- let onStart = Querysub.fastRead(() => this.props.onStart);
122
- let onDatums = Querysub.fastRead(() => this.props.onDatums);
123
- let onStats = Querysub.fastRead(() => this.props.onStats);
124
- let fastArchives = Querysub.fastRead(() => this.props.fastArchives);
125
- let onFinish = Querysub.fastRead(() => this.props.onFinish);
126
- let getWantData = Querysub.fastRead(() => this.props.getWantData);
127
- // Increment sequence number for this new sync attempt
128
- this.currentSequenceNumber++;
129
- this.latestSequenceNumber = this.currentSequenceNumber;
130
- const mySequenceNumber = this.currentSequenceNumber;
131
-
132
- // Increment run count for each new run
133
- Querysub.commit(() => {
134
- this.state.runCount++;
135
- });
136
-
137
- // Helper function to check if this sequence number is still the latest
138
- const isLatestSync = () => mySequenceNumber === this.latestSequenceNumber;
139
-
140
- // Helper function to safely update state only if we're the latest sync
141
- const ifLatest = (updater: () => void) => {
142
- if (isLatestSync()) {
143
- Querysub.commit(updater);
144
- } else {
145
- console.log(`Ignoring state update from sequence ${mySequenceNumber}, latest is ${this.latestSequenceNumber}`);
146
- }
147
- };
148
-
149
- // Increment sync count before making the call
150
- Querysub.commit(() => {
151
- this.state.pendingSyncInitializations++;
152
- });
153
-
154
-
155
- const timeRange = getTimeRange();
156
- let filterString = filterParam.value;
157
- let filterString2 = filterParam2.value;
158
- let scannedValueCount = 0;
159
-
160
- // Store current sync parameters BEFORE calling synchronizeData
161
- ifLatest(() => {
162
- this.state.currentSyncParams = {
163
- filterString,
164
- startTime: timeRange.startTime,
165
- endTime: timeRange.endTime,
166
- fastArchivePaths: fastArchives().map(archive => archive.rootPath),
167
- };
168
- });
169
-
170
- ifLatest(() => {
171
- this.state.error = undefined;
172
- this.state.finished = false;
173
-
174
- // Clear existing progress bars
175
- for (let key of Object.keys(this.progressBars)) {
176
- delete this.progressBars[key];
177
- }
178
- this.state.fileMetadata = [];
179
- this.fileErrors = [];
180
- this.limitedScanCount = 0;
181
- this.limitedMatchCount = 0;
182
- this.allSize = 0;
183
- this.matchedSize = 0;
184
-
185
- this.histogramBucketTime = Math.max(timeInHour, (timeRange.endTime - timeRange.startTime) / 100);
186
-
187
- this.histogramStartTime = timeRange.startTime - this.histogramBucketTime * 2;
188
- this.histogramEndTime = timeRange.endTime + this.histogramBucketTime * 2;
189
- const bucketCount = clamp(Math.ceil((this.histogramEndTime - this.histogramStartTime) / this.histogramBucketTime), 1, 10000);
190
-
191
- this.histogramAllDataCounts = new Float64Array(bucketCount);
192
- this.histogramSelectedDataCounts = new Float64Array(bucketCount);
193
- this.histogramAllDataSizes = new Float64Array(bucketCount);
194
- this.histogramSelectedDataSizes = new Float64Array(bucketCount);
195
- });
196
-
197
- const onProgress = (progress: { section: string; value: number; max: number }) => {
198
- if (!isLatestSync()) return;
199
- const key = progress.section;
200
-
201
- // Add or update progress bar
202
- let existingBar = this.progressBars[key];
203
- this.progressBars[key] = {
204
- section: progress.section,
205
- value: progress.value,
206
- max: progress.max,
207
- initialTime: existingBar?.initialTime || Date.now(),
208
- lastSetTime: Date.now(),
209
- };
210
-
211
- let now = Date.now();
212
- if (now - this.lastRenderTime > RENDER_INTERVAL) {
213
- this.lastRenderTime = now;
214
- ifLatest(() => {
215
- this.state.renderSeqNum++;
216
- });
217
- }
218
- };
219
- try {
220
- await onStart();
221
-
222
- const caseInsensitive = Querysub.fastRead(() => caseInsensitiveParam.value);
223
- let caseInsensitiveMapping = new Uint8Array(256);
224
- for (let i = 0; i < 256; i++) {
225
- caseInsensitiveMapping[i] = String.fromCharCode(i).toLowerCase().charCodeAt(0);
226
- }
227
-
228
- type ScanMatch = (posStart: number, posEnd: number, buffer: Buffer) => boolean;
229
- type DatumMatch = (datum: unknown) => boolean;
230
- type MatchObj = {
231
- scanMatch: ScanMatch,
232
- datumMatch: DatumMatch,
233
- };
234
-
235
- function parsePart(filterString: string): MatchObj {
236
- if (!filterString.trim()) {
237
- return {
238
- scanMatch: () => true,
239
- datumMatch: () => true,
240
- };
241
- }
242
- let datumMatch = (datum: unknown) => true;
243
- if (filterString.includes("=")) {
244
- let equalIndex = filterString.indexOf("=");
245
- let key = filterString.slice(0, equalIndex).trim();
246
- let value = niceParse(filterString.slice(equalIndex + 1).trim());
247
- filterString = `${JSON.stringify(key)}:${JSON.stringify(value)}`;
248
- datumMatch = (datum: unknown) => {
249
- if (!canHaveChildren(datum)) return false;
250
- let curKey = key;
251
- if (!(curKey in datum)) {
252
- curKey = curKey.toLowerCase();
253
- let keys = Object.keys(datum);
254
- curKey = keys.find(x => x.toLowerCase() === curKey) || "";
255
- if (!curKey) return false;
256
- }
257
- return datum[curKey] === value;
258
- };
259
- }
260
- if (caseInsensitive) {
261
- filterString = filterString.toLowerCase();
262
- }
263
- const filterBuffer = Buffer.from(filterString);
264
- const char0 = filterBuffer[0];
265
- return {
266
- scanMatch: (posStart: number, posEnd: number, buffer: Buffer) => {
267
- for (let i = posStart; i < posEnd; i++) {
268
- // NOTE: This is slow for repeated prefixes, but... in practice, even if we have prefixes, they won't repeat, so... this should be quite fast.
269
- let ch = buffer[i];
270
- if (caseInsensitive) {
271
- ch = caseInsensitiveMapping[ch];
272
- }
273
- if (ch === char0) {
274
- if (filterBuffer.length === 1) return true;
275
- for (let j = 1; j < filterBuffer.length; j++) {
276
- let ch2 = buffer[i + j];
277
- if (caseInsensitive) {
278
- ch2 = caseInsensitiveMapping[ch2];
279
- }
280
- if (ch2 !== filterBuffer[j]) {
281
- break;
282
- }
283
- if (j === filterBuffer.length - 1) {
284
- return true;
285
- }
286
- }
287
- }
288
- }
289
- return false;
290
- },
291
- datumMatch,
292
- };
293
- }
294
-
295
- function joinMatches(matches: MatchObj[], condition: "||" | "&&"): MatchObj {
296
- if (condition === "||") {
297
- return {
298
- scanMatch: (posStart: number, posEnd: number, buffer: Buffer) => {
299
- return matches.some(match => match.scanMatch(posStart, posEnd, buffer));
300
- },
301
- datumMatch: (datum: unknown) => {
302
- return matches.some(match => match.datumMatch(datum));
303
- },
304
- };
305
- }
306
- return {
307
- scanMatch: (posStart: number, posEnd: number, buffer: Buffer) => {
308
- return matches.every(match => match.scanMatch(posStart, posEnd, buffer));
309
- },
310
- datumMatch: (datum: unknown) => {
311
- return matches.every(match => match.datumMatch(datum));
312
- },
313
- };
314
- }
315
-
316
-
317
- let andMatches = filterString.split("|").map(orParts => {
318
- let andParts = orParts.split("&");
319
- let andMatches = andParts.map(part => parsePart(part.trim()));
320
- return joinMatches(andMatches, "&&");
321
- });
322
- let { scanMatch, datumMatch } = joinMatches(andMatches, "||");
323
-
324
- let andMatches2 = filterString2.split("|").map(orParts => {
325
- let andParts = orParts.split("&");
326
- let andMatches = andParts.map(part => parsePart(part.trim()));
327
- return joinMatches(andMatches, "&&");
328
- });
329
- let scanObj2 = joinMatches(andMatches2, "||");
330
- let scanMatch2 = scanObj2.scanMatch;
331
- let datumMatch2 = scanObj2.datumMatch;
332
- let useScan2 = !!filterString2.trim();
333
-
334
- const limitedBuffer = Buffer.from(LOG_LIMIT_FLAG);
335
-
336
-
337
- await Promise.all(fastArchives().map(async (fastArchive, index) => {
338
- const result = await fastArchive.synchronizeData({
339
- range: timeRange,
340
- cacheBust: Querysub.fastRead(() => cacheBustParam.value),
341
- scanFnc: Querysub.fastRead(() => this.props.getScanFnc?.()),
342
- getWantData: async (file) => {
343
- let wantData = await getWantData?.(file);
344
- return (posStart: number, posEnd: number, data: Buffer) => {
345
- let isLimited = false;
346
- for (let i = posStart; i < posEnd && !isLimited; i++) {
347
- if (data[i] === limitedBuffer[0]) {
348
- for (let j = 1; j < limitedBuffer.length; j++) {
349
- if (data[i + j] !== limitedBuffer[j]) {
350
- break;
351
- }
352
- if (j === limitedBuffer.length - 1) {
353
- isLimited = true;
354
- break;
355
- }
356
- }
357
- }
358
- }
359
- if (isLimited) {
360
- this.limitedScanCount++;
361
- }
362
-
363
- // scanMatch is faster than wantData (generally), so run it first
364
- let matched = scanMatch(posStart, posEnd, data);
365
- if (matched && useScan2) {
366
- matched = scanMatch2(posStart, posEnd, data);
367
- }
368
- if (matched && wantData) {
369
- matched = wantData(posStart, posEnd, data);
370
- }
371
- if (isLimited && matched) {
372
- this.limitedMatchCount++;
373
- }
374
- return matched;
375
- };
376
- },
377
- onData: (datums, file) => {
378
- if (!isLatestSync()) return;
379
- let keptDatums: T[] = [];
380
- for (let i = 0; i < datums.length; i++) {
381
- let datum = datums[i];
382
- if (datumMatch(datum)) {
383
- keptDatums.push(datum);
384
- }
385
- }
386
- onDatums(fastArchive, keptDatums, file);
387
- },
388
- onStats: onStats && ((stats, file) => {
389
- if (!isLatestSync()) return;
390
-
391
- onStats!(fastArchive, stats, file);
392
- this.allSize += stats.matchedSize + stats.notMatchedSize;
393
- this.matchedSize += stats.matchedSize;
394
- // Update scanned count and sequence number for rendering
395
- scannedValueCount += stats.notMatchedCount + stats.matchedCount;
396
- // Update all data histogram
397
- const bucketIndex = this.getHistogramBucketIndex(file.startTime);
398
- this.histogramAllDataCounts[bucketIndex] += stats.notMatchedCount + stats.matchedCount;
399
- this.histogramSelectedDataCounts[bucketIndex] += stats.matchedCount;
400
- this.histogramAllDataSizes[bucketIndex] += stats.matchedSize + stats.notMatchedSize;
401
- this.histogramSelectedDataSizes[bucketIndex] += stats.matchedSize;
402
- if (scannedValueCount >= HISTOGRAM_RERENDER_INTERVAL) {
403
- scannedValueCount = 0;
404
- let now = Date.now();
405
- if (now - this.lastRenderTime > RENDER_INTERVAL) {
406
- this.lastRenderTime = now;
407
- ifLatest(() => {
408
- this.state.renderSeqNum++;
409
- });
410
- }
411
- }
412
- }),
413
- onError: (error, file) => {
414
- if (!isLatestSync()) return;
415
- this.fileErrors.push({ file, error });
416
- },
417
- onProgress,
418
- onFinish: () => {
419
- ifLatest(() => {
420
- this.state.renderSeqNum++;
421
- });
422
- setTimeout(() => {
423
- ifLatest(() => {
424
- this.state.finished = true;
425
- });
426
- }, 2500);
427
- onFinish?.();
428
- },
429
- });
430
- if (result !== "cancelled") {
431
- ifLatest(() => {
432
- this.state.fileMetadata[index] = result.metadata;
433
- });
434
- }
435
- return result;
436
- }));
437
-
438
- } catch (error: any) {
439
- ifLatest(() => {
440
- this.state.error = error.stack || String(error);
441
- });
442
- } finally {
443
- // Decrement sync count when call finishes (regardless of sequence number)
444
- Querysub.commit(() => {
445
- this.state.pendingSyncInitializations--;
446
- });
447
- }
448
- }
449
- private renderProgressBars() {
450
- let progressEntries = Object.entries(this.progressBars);
451
-
452
- if (progressEntries.length === 0) {
453
- return null;
454
- }
455
-
456
- const getNowTime = lazy(() => Querysub.timeDelayed(1000));
457
- if (progressEntries.some(x => x[0].startsWith("Error"))) {
458
- progressEntries = progressEntries.filter(x => x[0].startsWith("Error"));
459
- }
460
- sort(progressEntries, x => x[1].initialTime);
461
-
462
- // Group progress bars by the text after the pipe sign
463
- const groupedProgress = new Map<string, {
464
- displayName: string;
465
- entries: Array<[string, { section: string, value: number, max: number, initialTime: number, lastSetTime: number }]>;
466
- totalValue: number;
467
- totalMax: number;
468
- earliestTime: number;
469
- latestTime: number;
470
- }>();
471
-
472
- for (const [key, progress] of progressEntries) {
473
- const pipeIndex = progress.section.indexOf("|");
474
- const displayName = pipeIndex >= 0 ? progress.section.substring(pipeIndex + 1) : progress.section;
475
-
476
- if (!groupedProgress.has(displayName)) {
477
- groupedProgress.set(displayName, {
478
- displayName,
479
- entries: [],
480
- totalValue: 0,
481
- totalMax: 0,
482
- earliestTime: progress.initialTime,
483
- latestTime: progress.lastSetTime,
484
- });
485
- }
486
-
487
- const group = groupedProgress.get(displayName)!;
488
- group.entries.push([key, progress]);
489
- group.totalValue += progress.value;
490
- group.totalMax += progress.max;
491
- group.earliestTime = Math.min(group.earliestTime, progress.initialTime);
492
- group.latestTime = Math.max(group.latestTime, progress.lastSetTime);
493
- }
494
-
495
- return (
496
- <div className={css.hbox(10).wrap.alignItems("start").fillWidth}>
497
- {!this.state.finished && (
498
- <div
499
- onClick={() => {
500
- this.cancelAllSynchronizes();
501
- }}
502
- className={css.pad2(12).bord2(200, 50, 50).hsl(200, 50, 95).colorhsl(200, 50, 40).button}
503
- >
504
- Cancel
505
- </div>
506
- )}
507
- {Array.from(groupedProgress.values()).map((group) => {
508
- const fraction = clamp(group.totalMax > 0 ? (group.totalValue / group.totalMax) * 1 : 1, 0, 1);
509
- let progressTime = 0;
510
- if (group.totalValue >= group.totalMax) {
511
- progressTime = group.latestTime;
512
- } else {
513
- progressTime = getNowTime();
514
- }
515
- const elapsedTime = progressTime - group.earliestTime;
516
-
517
- // Create tooltip showing breakdown by original prefix
518
- const tooltipContent = group.entries.map(([key, progress]) => {
519
- const pipeIndex = progress.section.indexOf("|");
520
- const prefix = pipeIndex >= 0 ? progress.section.substring(0, pipeIndex) : key;
521
- return `${prefix}: ${formatNumber(progress.value)}/${formatNumber(progress.max)}`;
522
- }).join("\n");
523
-
524
- return (
525
- <div key={group.displayName} className={
526
- // NOTE: There is no point making this proportionally to the time, because we stream the data, so the time overlaps, making everything take most of the time.
527
- css.vbox(4)
528
- }
529
- title={tooltipContent}
530
- >
531
- <div className={css.vbox(2)}>
532
- <div className={css.fontSize(14).colorhsl(0, 0, 20)}>
533
- {group.displayName}
534
- </div>
535
- <div className={css.fontSize(12).colorhsl(0, 0, 40).width(150)}>
536
- {formatNumber(group.totalValue)} {group.totalValue < group.totalMax && `/ ${formatNumber(group.totalMax)}`} ({formatTime(elapsedTime)})
537
- </div>
538
- </div>
539
- <div className={css.fillWidth.height(8).bord2(200, 20, 80).hsl(200, 10, 95)}>
540
- <div
541
- className={css.height(8).hsl(200, 50, 70).width(`${fraction * 100}%`).transition("width 200ms")}
542
- />
543
- </div>
544
- </div>
545
- );
546
- })}
547
- </div>
548
- );
549
- }
550
-
551
- private renderHistogram() {
552
- this.state.renderSeqNum;
553
-
554
- if (this.histogramAllDataCounts.length === 0) {
555
- return null;
556
- }
557
-
558
- let countsArray = Array.from(this.histogramAllDataCounts);
559
- let selectedArray = Array.from(this.histogramSelectedDataCounts);
560
- const maxAllCount = countsArray.reduce((a, b) => Math.max(a, b), 0);
561
- const maxSelectedCount = selectedArray.reduce((a, b) => Math.max(a, b), 0);
562
- const maxCountForScale = hideAllDataParam.value ? maxSelectedCount : maxAllCount;
563
- const selected = css.hsla(120, 40, 50, 1);
564
- const all = css.hsla(200, 40, 70, 1);
565
-
566
- return (
567
- <div className={css.vbox(10).fillWidth}>
568
- <div
569
- className={css.relative.fillWidth.height(100).bord2(0, 0, 40).hbox0.overflowHidden}
570
- >
571
- {Array.from(this.histogramAllDataCounts).map((count, index) => {
572
- const selectedCount = this.histogramSelectedDataCounts[index] || 0;
573
- const allSize = this.histogramAllDataSizes[index] || 0;
574
- const selectedSize = this.histogramSelectedDataSizes[index] || 0;
575
- return (
576
- <div
577
- key={index}
578
- className={css.relative.fillHeight.fillWidth.filter("brightness(1.1)", "hover")}
579
- title={`${formatNumber(selectedCount)} filtered (${formatNumber(selectedSize)}B)\n${formatNumber(count)} all (${formatNumber(allSize)}B)\n${formatVeryNiceDateTime(this.histogramStartTime + index * this.histogramBucketTime)}`}
580
- >
581
- {count > 0 && !hideAllDataParam.value && <div
582
- className={
583
- all.absolute.bottom(0).left(0).right(1)
584
- .transition("height 200ms")
585
- .height(`calc(max(3px, ${(count / maxCountForScale) * 100}%))`)
586
- }
587
- />}
588
-
589
- {selectedCount > 0 && <div
590
- className={
591
- selected.absolute.bottom(0).left(0).right(1)
592
- .transition("height 200ms")
593
- .height(`calc(max(3px, ${(selectedCount / maxCountForScale) * 100}%))`)
594
- }
595
- />}
596
- </div>
597
- );
598
- })}
599
- </div>
600
- <div className={css.hbox(20).fontSize(12)}>
601
- {!hideAllDataParam.value && (
602
- <div className={css.hbox(5)}>
603
- <div className={all.size(16, 16)} />
604
- <span>All data (max {formatNumber(maxAllCount)} | {formatNumber(countsArray.reduce((a, b) => a + b, 0))} | {formatNumber(this.allSize)}B)</span>
605
- </div>
606
- )}
607
- <div className={css.hbox(5)}>
608
- <div className={selected.size(16, 16)} />
609
- <span>Filtered data (max {formatNumber(maxSelectedCount)} | {formatNumber(selectedArray.reduce((a, b) => a + b, 0))} | {formatNumber(this.matchedSize)}B)</span>
610
- </div>
611
- <InputLabelURL
612
- label="Only show filtered data"
613
- checkbox
614
- url={hideAllDataParam}
615
- flavor="small"
616
- />
617
- </div>
618
- </div>
619
- );
620
- }
621
-
622
- private getOutdatedInfo(): string[] {
623
- const currentTimeRange = getTimeRange();
624
- const currentFilterString = filterParam.value;
625
- const currentArchivePaths = this.props.fastArchives().map(archive => archive.rootPath);
626
-
627
- // Get stored sync parameters
628
- const storedParams = this.state.currentSyncParams;
629
-
630
- // If we have no stored params yet, not outdated (first run or loading)
631
- if (!storedParams) {
632
- return [];
633
- }
634
-
635
- let warnings: string[] = [];
636
-
637
- // Check for parameter differences
638
- if (storedParams.filterString !== currentFilterString) {
639
- warnings.push("filter");
640
- }
641
- if (storedParams.startTime !== currentTimeRange.startTime) {
642
- warnings.push("start time");
643
- }
644
- if (storedParams.endTime !== currentTimeRange.endTime) {
645
- warnings.push("end time");
646
- }
647
-
648
- // Check for missing and extra archives
649
- const currentArchivePathsSet = new Set(currentArchivePaths);
650
- const storedArchivePathsSet = new Set(storedParams.fastArchivePaths);
651
-
652
- for (let storedPath of storedParams.fastArchivePaths) {
653
- if (!currentArchivePathsSet.has(storedPath)) {
654
- warnings.push(`Missing ${storedPath}`);
655
- }
656
- }
657
- for (let currentPath of currentArchivePaths) {
658
- if (!storedArchivePathsSet.has(currentPath)) {
659
- warnings.push(`Extra ${currentPath}`);
660
- }
661
- }
662
-
663
- return warnings;
664
- }
665
-
666
- public synchronizeDataThrottled = throttleFunction(500, () => {
667
- Querysub.onCommitFinished(() => {
668
- logErrors(this.synchronizeData());
669
- });
670
- });
671
- render() {
672
- if (throttleRender({ key: "FastArchiveViewer", frameDelay: 30 })) return undefined;
673
-
674
- let totalFileCount = 0;
675
- let totalBackblazeByteCount = 0;
676
- let totalLocalByteCount = 0;
677
- let totalLocalFileCount = 0;
678
-
679
- let readLocalTimes: number[] = [];
680
-
681
-
682
- for (let fileMetadata of this.state.fileMetadata) {
683
- if (!fileMetadata) continue;
684
- totalFileCount += fileMetadata.files.length;
685
- let backblazeFiles = fileMetadata.files.filter(file => !file.nodeId);
686
- let localFiles = fileMetadata.files.filter(file => file.nodeId);
687
- if (localFiles.length > 0) {
688
- readLocalTimes.push(fileMetadata.createTime || 0);
689
- }
690
- totalBackblazeByteCount += backblazeFiles.reduce((acc, file) => acc + file.size, 0);
691
- totalLocalByteCount += localFiles.reduce((acc, file) => acc + file.size, 0);
692
- totalLocalFileCount += localFiles.length;
693
- }
694
-
695
- const infoDisplay = (hue: number) => css.pad2(12).bord2(hue, 50, 50).hsl(hue, 50, 95).colorhsl(hue, 50, 40);
696
-
697
-
698
-
699
- return (
700
- <div className={css.vbox(20).pad2(20).fillBoth}>
701
- <div className={css.hbox(20).fillWidth}>
702
- <TimeRangeSelector />
703
- <div className={infoDisplay(280)}>
704
- TODO: Filtering for machineId / threadId, if we find the log loading is too slow
705
- </div>
706
- </div>
707
-
708
- <div className={css.vbox(12).fillWidth.paddingBottom(20)}>
709
- <InputLabelURL
710
- label={
711
- <div className={css.hbox(10)}>
712
- <Button onClick={() => {
713
- void this.synchronizeDataThrottled();
714
- }}>
715
- Run
716
- </Button>
717
- <span>Filter</span>
718
- </div>
719
- }
720
- url={filterParam}
721
- hot
722
- flavor="large"
723
- fillWidth
724
- onKeyUp={this.synchronizeDataThrottled}
725
- ref2={() => {
726
- if (this.props.runOnLoad) {
727
- void this.synchronizeDataThrottled();
728
- }
729
- }}
730
- noEnterKeyBlur
731
- placeholder="Filter terms, ex x | y & z"
732
- />
733
- <InputLabelURL
734
- label="Case Insensitive"
735
- checkbox
736
- url={caseInsensitiveParam}
737
- onChange={() => {
738
- void this.synchronizeDataThrottled();
739
- }}
740
- flavor="small"
741
- />
742
- <div className={css.vbox(10)}>
743
- {this.state.runCount > 0 && (() => {
744
- const outdatedWarnings = this.getOutdatedInfo();
745
- const errorCount = this.fileErrors.length;
746
-
747
- // Group errors by file path
748
- const errorsByFile = new Map<string, number>();
749
- for (let errorInfo of this.fileErrors) {
750
- const path = errorInfo.file.path;
751
- errorsByFile.set(path, (errorsByFile.get(path) || 0) + 1);
752
- }
753
-
754
- const errorTooltip = Array.from(errorsByFile.entries())
755
- .map(([path, count]) => `${path}: ${count} errors`)
756
- .join("\n");
757
-
758
- return (
759
- <div className={css.hbox(10).wrap}>
760
- <div
761
- className={infoDisplay(120)}
762
- title={this.state.fileMetadata.map(x => x?.files || []).flat().map(x =>
763
- `${x.path} (${formatNumber(x.size)})`
764
- ).join("\n")}
765
- >
766
- File count: {formatNumber(totalFileCount)}{errorCount > 0 && ` (${errorCount} erred)`}, Backblaze size: {formatNumber(totalBackblazeByteCount)}B (compressed), Disk size: {formatNumber(totalLocalByteCount)}B (uncompressed)
767
- </div>
768
- {errorCount > 0 && (
769
- <div
770
- className={infoDisplay(45)}
771
- title={errorTooltip}
772
- >
773
- {errorCount} file{errorCount > 1 ? "s" : ""} failed to load
774
- </div>
775
- )}
776
- {outdatedWarnings.length > 0 && (
777
- <div
778
- className={infoDisplay(30).button}
779
- onClick={() => {
780
- void this.synchronizeDataThrottled();
781
- }}
782
- title={outdatedWarnings.join(", ")}
783
- >
784
- <div className={css.vbox(4)}>
785
- <div className={css.boldStyle}>Search parameters outdated - Click to run updated search. Outdated:</div>
786
- <div className={css.fontSize(12)}>
787
- {outdatedWarnings.join(" | ")}
788
- </div>
789
- </div>
790
- </div>
791
- )}
792
- </div>
793
- );
794
- })()}
795
- {this.state.runCount === 0 && (
796
- <div className={infoDisplay(200).button} onClick={() => {
797
- void this.synchronizeDataThrottled();
798
- }}>
799
- No data downloaded yet. Click here to download data.
800
- </div>
801
- )}
802
- {this.state.finished && (() => {
803
-
804
- const timeRange = getTimeRange();
805
- if (timeRange.endTime && readLocalTimes.length === 0) return null;
806
- const earliestTime = Math.min(...readLocalTimes);
807
-
808
- if (earliestTime >= timeRange.endTime) return null;
809
-
810
- return (
811
- <div
812
- className={infoDisplay(0).button}
813
- onClick={() => {
814
- Querysub.commit(() => {
815
- cacheBustParam.value = Date.now();
816
- });
817
- void this.synchronizeDataThrottled();
818
- }}
819
- >
820
- <div className={css.hbox(8).alignItems("center").wrap}>
821
- <span>Future data has been read</span>
822
- <span className={css.fontSize(18).boldStyle}>{formatTime(timeRange.endTime - earliestTime)} into the future</span>
823
- <span>({formatDateTime(earliestTime)})</span>
824
- <span>Clear here to load possibly missing data data.</span>
825
- </div>
826
- </div>
827
- );
828
- })()}
829
- {!this.state.finished && <LoaderAurora />}
830
- {this.limitedScanCount > 0 && (
831
- <div className={infoDisplay(60).boldStyle.button}
832
- onClick={() => {
833
- filterParam.value = LOG_LIMIT_FLAG;
834
- void this.synchronizeDataThrottled();
835
- }}
836
- >
837
- Click here to see {formatNumber(this.limitedScanCount)} scanned logs were rate limited at a file level. This means lines might be missing. If this happens a lot, use pass {`{ [LOG_LINE_LIMIT_ID]: "INSERT RANDOM GUID HERE" }`} in the error/warn message to limit only the spamming line.
838
- </div>
839
- )}
840
- {this.limitedMatchCount > 0 && filterParam.value && (
841
- <div className={infoDisplay(0).boldStyle.button}
842
- onClick={() => {
843
- filterParam.value += " & " + LOG_LIMIT_FLAG;
844
- void this.synchronizeDataThrottled();
845
- }}
846
- >
847
- Click here to see {formatNumber(this.limitedMatchCount)} matched logs were rate limited at a file level. This means lines might be missing. If this happens a lot, use pass {`{ [LOG_LINE_LIMIT_ID]: "INSERT RANDOM GUID HERE" }`} in the error/warn message to limit only the spamming line.
848
- </div>
849
- )}
850
- {this.state.pendingSyncInitializations > 0 && (
851
- <div className={infoDisplay(200)}>
852
- <strong>Pending sync initializations:</strong> {formatNumber(this.state.pendingSyncInitializations)}
853
- </div>
854
- )}
855
- </div>
856
-
857
- {this.state.error && (
858
- <div className={infoDisplay(0).whiteSpace("pre-wrap")}>
859
- <strong>Error:</strong> {this.state.error}
860
- </div>
861
- )}
862
-
863
- {this.renderProgressBars()}
864
-
865
- {this.renderHistogram()}
866
-
867
- {this.props.children}
868
- </div>
869
- </div>
870
- );
871
- }
872
- }
873
-
874
- class LoaderAurora extends qreact.Component {
875
- render() {
876
- return (
877
- <>
878
- <style>{`
879
- .aurora-container-2 {
880
- position: relative;
881
- width: 800px;
882
- height: 40px;
883
- overflow: hidden;
884
- background: linear-gradient(180deg, hsl(280, 30%, 12%), hsl(320, 35%, 15%));
885
- }
886
- .aurora-ribbon-2 {
887
- position: absolute;
888
- width: 1420px;
889
- height: 40px;
890
- filter: blur(12px);
891
- opacity: 0.9;
892
- }
893
-
894
- /* Different gradient patterns for variety */
895
- .aurora-ribbon-2.gradient1 {
896
- background: linear-gradient(90deg,
897
- hsla(160, 80%, 50%, 0) 0%,
898
- hsla(160, 80%, 50%, 0.7) 30%,
899
- hsla(280, 80%, 60%, 0.7) 60%,
900
- hsla(40, 80%, 55%, 0) 100%);
901
- }
902
- .aurora-ribbon-2.gradient2 {
903
- background: linear-gradient(90deg,
904
- hsla(200, 70%, 60%, 0) 0%,
905
- hsla(200, 70%, 60%, 0.6) 40%,
906
- hsla(320, 70%, 55%, 0.6) 70%,
907
- hsla(60, 70%, 50%, 0) 100%);
908
- }
909
- .aurora-ribbon-2.gradient3 {
910
- background: linear-gradient(90deg,
911
- hsla(300, 80%, 65%, 0) 0%,
912
- hsla(300, 80%, 65%, 0.8) 25%,
913
- hsla(180, 80%, 55%, 0.8) 65%,
914
- hsla(240, 80%, 60%, 0) 100%);
915
- }
916
-
917
- /* Left to right movement */
918
- .aurora-ribbon-2.r1 {
919
- top: 0;
920
- transform: rotate(-10deg);
921
- animation: auroraSlideLeft-2 3s ease-in-out infinite, auroraRotate1-2 6s linear infinite;
922
- opacity: 0.7;
923
- }
924
- .aurora-ribbon-2.r2 {
925
- top: 20px;
926
- transform: rotate(12deg);
927
- animation: auroraSlideRight-2 2.5s ease-in-out infinite, auroraRotate2-2 8s linear infinite reverse;
928
- opacity: 0.8;
929
- }
930
- .aurora-ribbon-2.r3 {
931
- top: 40px;
932
- transform: rotate(18deg);
933
- animation: auroraSlideDiagonal1-2 4s ease-in-out infinite;
934
- opacity: 0.6;
935
- }
936
- .aurora-ribbon-2.r4 {
937
- top: -10px;
938
- transform: rotate(-15deg);
939
- animation: auroraSlideLeft-2 3.5s ease-in-out infinite, auroraRotate3-2 10s linear infinite;
940
- opacity: 0.5;
941
- }
942
- .aurora-ribbon-2.r5 {
943
- top: 30px;
944
- transform: rotate(8deg);
945
- animation: auroraSlideDiagonal2-2 2.8s ease-in-out infinite;
946
- opacity: 0.9;
947
- }
948
- .aurora-ribbon-2.r6 {
949
- top: 10px;
950
- transform: rotate(-5deg);
951
- animation: auroraSlideRight-2 4.2s ease-in-out infinite, auroraRotate1-2 7s linear infinite;
952
- opacity: 0.4;
953
- }
954
- .aurora-ribbon-2.r7 {
955
- top: 50px;
956
- transform: rotate(22deg);
957
- animation: auroraSlideLeft-2 2.2s ease-in-out infinite;
958
- opacity: 0.8;
959
- }
960
- .aurora-ribbon-2.r8 {
961
- top: 15px;
962
- transform: rotate(-8deg);
963
- animation: auroraSlideDiagonal3-2 3.7s ease-in-out infinite, auroraRotate2-2 9s linear infinite;
964
- opacity: 0.6;
965
- }
966
- .aurora-ribbon-2.r9 {
967
- top: 35px;
968
- transform: rotate(15deg);
969
- animation: auroraSlideRight-2 3.1s ease-in-out infinite, auroraRotate1-2 8.5s linear infinite;
970
- opacity: 0.7;
971
- }
972
- .aurora-ribbon-2.r10 {
973
- top: -5px;
974
- transform: rotate(-12deg);
975
- animation: auroraSlideDiagonal1-2 3.8s ease-in-out infinite;
976
- opacity: 0.5;
977
- }
978
- .aurora-ribbon-2.r11 {
979
- top: 25px;
980
- transform: rotate(6deg);
981
- animation: auroraSlideLeft-2 2.9s ease-in-out infinite, auroraRotate3-2 11s linear infinite;
982
- opacity: 0.8;
983
- }
984
- .aurora-ribbon-2.r12 {
985
- top: 45px;
986
- transform: rotate(-20deg);
987
- animation: auroraSlideDiagonal2-2 4.1s ease-in-out infinite, auroraRotate2-2 7.5s linear infinite reverse;
988
- opacity: 0.6;
989
- }
990
- .aurora-ribbon-2.r13 {
991
- top: 5px;
992
- transform: rotate(10deg);
993
- animation: auroraSlideRight-2 2.7s ease-in-out infinite;
994
- opacity: 0.9;
995
- }
996
- .aurora-ribbon-2.r14 {
997
- top: 38px;
998
- transform: rotate(-7deg);
999
- animation: auroraSlideDiagonal3-2 3.3s ease-in-out infinite, auroraRotate1-2 9.5s linear infinite;
1000
- opacity: 0.4;
1001
- }
1002
- .aurora-ribbon-2.r15 {
1003
- top: 18px;
1004
- transform: rotate(14deg);
1005
- animation: auroraSlideLeft-2 4.3s ease-in-out infinite, auroraRotate3-2 8.2s linear infinite reverse;
1006
- opacity: 0.7;
1007
- }
1008
- .aurora-ribbon-2.r16 {
1009
- top: 12px;
1010
- transform: rotate(-18deg);
1011
- animation: auroraSlideDiagonal1-2 2.6s ease-in-out infinite, auroraRotate2-2 10.5s linear infinite;
1012
- opacity: 0.8;
1013
- }
1014
-
1015
- /* Left to right slide */
1016
- @keyframes auroraSlideLeft-2 {
1017
- 0% { left: -920px; }
1018
- 100% { left: 900px; }
1019
- }
1020
-
1021
- /* Right to left slide */
1022
- @keyframes auroraSlideRight-2 {
1023
- 0% { left: 900px; }
1024
- 100% { left: -920px; }
1025
- }
1026
-
1027
- /* Diagonal movements */
1028
- @keyframes auroraSlideDiagonal1-2 {
1029
- 0% { left: -920px; top: 40px; }
1030
- 50% { left: 400px; top: -5px; }
1031
- 100% { left: 900px; top: 40px; }
1032
- }
1033
-
1034
- @keyframes auroraSlideDiagonal2-2 {
1035
- 0% { left: 900px; top: 30px; }
1036
- 50% { left: 400px; top: 45px; }
1037
- 100% { left: -920px; top: 30px; }
1038
- }
1039
-
1040
- @keyframes auroraSlideDiagonal3-2 {
1041
- 0% { left: -920px; top: 15px; }
1042
- 30% { left: -200px; top: 5px; }
1043
- 70% { left: 600px; top: 25px; }
1044
- 100% { left: 900px; top: 15px; }
1045
- }
1046
-
1047
- /* Rotation animations */
1048
- @keyframes auroraRotate1-2 {
1049
- 0% { transform: rotate(-10deg); }
1050
- 25% { transform: rotate(-5deg); }
1051
- 50% { transform: rotate(-15deg); }
1052
- 75% { transform: rotate(-8deg); }
1053
- 100% { transform: rotate(-10deg); }
1054
- }
1055
-
1056
- @keyframes auroraRotate2-2 {
1057
- 0% { transform: rotate(12deg); }
1058
- 33% { transform: rotate(18deg); }
1059
- 66% { transform: rotate(8deg); }
1060
- 100% { transform: rotate(12deg); }
1061
- }
1062
-
1063
- @keyframes auroraRotate3-2 {
1064
- 0% { transform: rotate(-15deg); }
1065
- 50% { transform: rotate(-20deg); }
1066
- 100% { transform: rotate(-15deg); }
1067
- }
1068
- `}</style>
1069
- <div className="aurora-container-2">
1070
- <div className="aurora-ribbon-2 r1 gradient1"></div>
1071
- <div className="aurora-ribbon-2 r2 gradient2"></div>
1072
- <div className="aurora-ribbon-2 r3 gradient3"></div>
1073
- <div className="aurora-ribbon-2 r4 gradient1"></div>
1074
- <div className="aurora-ribbon-2 r5 gradient3"></div>
1075
- <div className="aurora-ribbon-2 r6 gradient2"></div>
1076
- <div className="aurora-ribbon-2 r7 gradient1"></div>
1077
- <div className="aurora-ribbon-2 r8 gradient3"></div>
1078
- <div className="aurora-ribbon-2 r9 gradient2"></div>
1079
- <div className="aurora-ribbon-2 r10 gradient1"></div>
1080
- <div className="aurora-ribbon-2 r11 gradient3"></div>
1081
- <div className="aurora-ribbon-2 r12 gradient2"></div>
1082
- <div className="aurora-ribbon-2 r13 gradient1"></div>
1083
- <div className="aurora-ribbon-2 r14 gradient3"></div>
1084
- <div className="aurora-ribbon-2 r15 gradient2"></div>
1085
- <div className="aurora-ribbon-2 r16 gradient1"></div>
1086
- </div>
1087
- </>
1088
- );
1089
- }
1090
- }