querysub 0.355.0 → 0.357.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.cursorrules +8 -0
  2. package/bin/movelogs.js +4 -0
  3. package/package.json +12 -6
  4. package/scripts/postinstall.js +23 -0
  5. package/src/-a-archives/archiveCache.ts +10 -12
  6. package/src/-a-archives/archives.ts +29 -0
  7. package/src/-a-archives/archivesBackBlaze.ts +60 -12
  8. package/src/-a-archives/archivesDisk.ts +27 -8
  9. package/src/-a-archives/archivesLimitedCache.ts +21 -0
  10. package/src/-a-archives/archivesMemoryCache.ts +350 -0
  11. package/src/-a-archives/archivesPrivateFileSystem.ts +22 -0
  12. package/src/-g-core-values/NodeCapabilities.ts +3 -0
  13. package/src/0-path-value-core/auditLogs.ts +5 -1
  14. package/src/0-path-value-core/pathValueCore.ts +7 -7
  15. package/src/4-dom/qreact.tsx +1 -0
  16. package/src/4-querysub/Querysub.ts +1 -5
  17. package/src/config.ts +5 -0
  18. package/src/diagnostics/MachineThreadInfo.tsx +235 -0
  19. package/src/diagnostics/NodeViewer.tsx +3 -2
  20. package/src/diagnostics/logs/FastArchiveAppendable.ts +79 -42
  21. package/src/diagnostics/logs/FastArchiveController.ts +102 -63
  22. package/src/diagnostics/logs/FastArchiveViewer.tsx +36 -8
  23. package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +461 -0
  24. package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.cpp +327 -0
  25. package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.d.ts +18 -0
  26. package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.js +1 -0
  27. package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +140 -0
  28. package/src/diagnostics/logs/IndexedLogs/BufferIndexLogsOptimizationConstants.ts +22 -0
  29. package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat +1145 -0
  30. package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat.d.ts +178 -0
  31. package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +206 -0
  32. package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +719 -0
  33. package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +146 -0
  34. package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +408 -0
  35. package/src/diagnostics/logs/IndexedLogs/FindProgressTracker.ts +45 -0
  36. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +598 -0
  37. package/src/diagnostics/logs/IndexedLogs/LogStreamer.ts +47 -0
  38. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +702 -0
  39. package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +236 -0
  40. package/src/diagnostics/logs/IndexedLogs/binding.gyp +23 -0
  41. package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +221 -0
  42. package/src/diagnostics/logs/IndexedLogs/moveLogsEntry.ts +10 -0
  43. package/src/diagnostics/logs/LogViewer2.tsx +120 -55
  44. package/src/diagnostics/logs/TimeRangeSelector.tsx +5 -2
  45. package/src/diagnostics/logs/diskLogger.ts +32 -48
  46. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +3 -2
  47. package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +1 -0
  48. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +150 -0
  49. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +133 -0
  50. package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +180 -0
  51. package/src/diagnostics/logs/lifeCycleAnalysis/test.wat +106 -0
  52. package/src/diagnostics/logs/lifeCycleAnalysis/test.wat.d.ts +2 -0
  53. package/src/diagnostics/logs/lifeCycleAnalysis/testHoist.ts +5 -0
  54. package/src/diagnostics/logs/logViewerExtractField.ts +2 -3
  55. package/src/diagnostics/managementPages.tsx +11 -1
  56. package/src/diagnostics/trackResources.ts +1 -1
  57. package/src/misc/lz4_wasm_nodejs.d.ts +34 -0
  58. package/src/misc/lz4_wasm_nodejs.js +178 -0
  59. package/src/misc/lz4_wasm_nodejs_bg.js +94 -0
  60. package/src/misc/lz4_wasm_nodejs_bg.wasm +0 -0
  61. package/src/misc/lz4_wasm_nodejs_bg.wasm.d.ts +15 -0
  62. package/src/storage/CompressedStream.ts +13 -0
  63. package/src/storage/LZ4.ts +32 -0
  64. package/src/storage/ZSTD.ts +10 -0
  65. package/src/wat/watCompiler.ts +1716 -0
  66. package/src/wat/watGrammar.pegjs +93 -0
  67. package/src/wat/watHandler.ts +179 -0
  68. package/src/wat/watInstructions.txt +707 -0
  69. package/src/zip.ts +3 -89
  70. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +0 -125
@@ -10,7 +10,7 @@ import { FastArchiveAppendable } from "./FastArchiveAppendable";
10
10
  import { t } from "../../2-proxy/schema2";
11
11
  import { measureFnc } from "socket-function/src/profiling/measure";
12
12
  import { logErrors } from "../../errors";
13
- import { batchFunction, runInSerial } from "socket-function/src/batching";
13
+ import { batchFunction, delay, runInSerial } from "socket-function/src/batching";
14
14
  import { Querysub } from "../../4-querysub/QuerysubController";
15
15
  import { sort, timeInDay, timeInHour } from "socket-function/src/misc";
16
16
  import { FastArchiveViewer, cacheBustParam, filterParam, filterParam2 } from "./FastArchiveViewer";
@@ -29,6 +29,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
29
29
  import { throttleRender } from "../../functional/throttleRender";
30
30
  import { isNode } from "typesafecss";
31
31
  import { createLogViewerExtractField } from "./logViewerExtractField";
32
+ import { isPublic } from "../../config";
32
33
 
33
34
  const RENDER_INTERVAL = 1000;
34
35
 
@@ -42,6 +43,7 @@ const selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, bo
42
43
  const useRelativeTimeURL = new URLParam("useRelativeTime", true);
43
44
 
44
45
  export const errorNotifyToggleURL = new URLParam("errorNotifyToggle", false);
46
+ const forceGetPublicURL = new URLParam("forceGetPublic", false);
45
47
 
46
48
  const defaultSelectedFields = {
47
49
  param0: true,
@@ -58,6 +60,7 @@ export class LogViewer2 extends qreact.Component {
58
60
  state = t.state({
59
61
  datumsSeqNum: t.atomic<number>(0),
60
62
  showCountByFile: t.boolean,
63
+ showSizeByFile: t.boolean,
61
64
  });
62
65
 
63
66
  private example: string | undefined = undefined;
@@ -69,6 +72,7 @@ export class LogViewer2 extends qreact.Component {
69
72
  private notMatchedCount = 0;
70
73
  private datums: LogDatum[] = [];
71
74
  private countsPerName = new Map<string, number>();
75
+ private sizePerName = new Map<string, number>();
72
76
 
73
77
  private lastRenderTime = 0;
74
78
  private suppressionCounts = new Map<string, number>();
@@ -76,19 +80,15 @@ export class LogViewer2 extends qreact.Component {
76
80
  private fastArchiveViewer: FastArchiveViewer<LogDatum> | undefined = undefined;
77
81
 
78
82
  rerun() {
79
- cacheBustParam.value = Date.now();
80
- void this.fastArchiveViewer?.synchronizeDataThrottled();
83
+ Querysub.onCommitFinished(async () => {
84
+ void this.fastArchiveViewer?.synchronizeDataThrottled();
85
+ });
81
86
  }
82
-
83
- render() {
84
- if (throttleRender({ key: "LogViewer2", frameDelay: 30 })) return undefined;
85
-
86
- this.state.datumsSeqNum;
87
-
87
+ getLoggers = () => {
88
88
  let logs: FastArchiveAppendable<LogDatum>[] = [];
89
89
  let loggers = getLoggers();
90
90
  if (!loggers) {
91
- return `Loggers not available?`;
91
+ throw new Error("Loggers not available?");
92
92
  }
93
93
  let { logLogs, warnLogs, infoLogs, errorLogs } = loggers;
94
94
  if (errorNotifyToggleURL.value) {
@@ -108,6 +108,13 @@ export class LogViewer2 extends qreact.Component {
108
108
  logs.push(errorLogs);
109
109
  }
110
110
  }
111
+ return logs;
112
+ };
113
+
114
+ render() {
115
+ //if (throttleRender({ key: "LogViewer2", frameDelay: 30 })) return undefined;
116
+
117
+ this.state.datumsSeqNum;
111
118
 
112
119
  let suppressionController = SuppressionListController(SocketFunction.browserNodeId());
113
120
  let suppressionList = suppressionController.getSuppressionList();
@@ -138,6 +145,12 @@ export class LogViewer2 extends qreact.Component {
138
145
  Test Error Notification (won't rerun search, but it should show up in the title)
139
146
  </Button>
140
147
  </div>}
148
+ {!isPublic() && <InputLabelURL
149
+ label="Force Public Logs" checkbox url={forceGetPublicURL}
150
+ onChangeValue={(newValue) => {
151
+ this.rerun();
152
+ }}
153
+ />}
141
154
  </div>
142
155
 
143
156
  {!errorNotifyToggleURL.value && <div className={css.hbox(10)}>
@@ -157,8 +170,9 @@ export class LogViewer2 extends qreact.Component {
157
170
 
158
171
  <FastArchiveViewer
159
172
  ref2={x => this.fastArchiveViewer = x}
160
- fastArchives={logs}
173
+ fastArchives={this.getLoggers}
161
174
  runOnLoad={errorNotifyToggleURL.value}
175
+ forceGetPublic={forceGetPublicURL.value}
162
176
  onStart={async () => {
163
177
  this.datumCount = 0;
164
178
  this.notMatchedCount = 0;
@@ -188,13 +202,15 @@ export class LogViewer2 extends qreact.Component {
188
202
 
189
203
  }}
190
204
  getScanFnc={() => {
191
- if (!this.state.showCountByFile) {
205
+ if (!this.state.showCountByFile && !this.state.showSizeByFile) {
192
206
  return () => false;
193
207
  }
194
- return createLogViewerExtractField("name", (value) => {
208
+ return createLogViewerExtractField("__NAME__", (value, size) => {
195
209
  let prevCount = this.countsPerName.get(value) ?? 0;
196
210
  prevCount++;
197
211
  this.countsPerName.set(value, prevCount);
212
+ let prevSize = this.sizePerName.get(value) ?? 0;
213
+ this.sizePerName.set(value, prevSize + size);
198
214
  });
199
215
  }}
200
216
  getWantData={async (file) => {
@@ -418,50 +434,99 @@ export class LogViewer2 extends qreact.Component {
418
434
  </div>
419
435
  </div>}
420
436
 
421
- {this.state.showCountByFile && (() => {
422
- let counts = Array.from(this.countsPerName.entries());
423
- sort(counts, x => -x[1]);
424
- let topCounts = counts.slice(0, 20);
425
- let totalCount = counts.reduce((sum, [, count]) => sum + count, 0);
437
+ <div className={css.hbox(10)}>
438
+ {this.state.showCountByFile && (() => {
439
+ let counts = Array.from(this.countsPerName.entries());
440
+ sort(counts, x => -x[1]);
441
+ let topCounts = counts.slice(0, 20);
442
+ let totalCount = counts.reduce((sum, [, count]) => sum + count, 0);
426
443
 
427
- return <div className={css.vbox(10)}>
428
- <div className={css.hbox(10, 10).wrap}>
429
- {topCounts.map(([name, count]) => {
430
- let percentage = totalCount > 0 ? (count / totalCount) * 100 : 0;
431
- let isFiltered = includedFiles.includes(name);
444
+ return <div className={css.vbox(10)}>
445
+ <div className={css.hbox(10, 10).wrap}>
446
+ {topCounts.map(([name, count]) => {
447
+ let percentage = totalCount > 0 ? (count / totalCount) * 100 : 0;
448
+ let isFiltered = includedFiles.includes(name);
449
+ let size = this.sizePerName.get(name) ?? 0;
432
450
 
433
- return <div
434
- key={name}
435
- className={css.relative.pad2(8, 4).bord2(210, 30, 60, 1).ellipsis.button}
436
- title={isFiltered ? `Click to remove ${name} from filter` : `Click to add ${name} to filter`}
437
- onClick={() => {
438
- if (isFiltered) {
439
- // Remove from filter
440
- let newFiles = includedFiles.filter(x => x !== name);
441
- filterParam2.value = newFiles.join("|");
442
- } else {
443
- // Add to filter
444
- let newFiles = [...includedFiles.filter(x => x), name];
445
- filterParam2.value = newFiles.join("|");
446
- }
447
- }}
448
- >
449
- <div
450
- className={css.absolute.pos(0, 0).height("100%").width(`${percentage}%`).hsl(isFiltered ? 120 : 210, 40, 80)}
451
- />
452
- <div className={css.relative.maxWidth(200).ellipsis}>
453
- {formatNumber(count)} | {name || "(no name present)"} {isFiltered && "✓" || ""}
454
- </div>
455
- </div>;
456
- })}
457
- </div>
458
- </div>;
459
- })() || <Button onClick={() => {
460
- this.state.showCountByFile = true;
461
- this.rerun();
462
- }}>
463
- Show Count by File
464
- </Button>}
451
+ return <div
452
+ key={name}
453
+ className={css.relative.pad2(8, 4).bord2(210, 30, 60, 1).ellipsis.button}
454
+ title={isFiltered ? `Click to remove ${name} from filter` : `Click to add ${name} to filter`}
455
+ onClick={() => {
456
+ if (isFiltered) {
457
+ // Remove from filter
458
+ let newFiles = includedFiles.filter(x => x !== name);
459
+ filterParam2.value = newFiles.join("|");
460
+ } else {
461
+ // Add to filter
462
+ let newFiles = [...includedFiles.filter(x => x), name];
463
+ filterParam2.value = newFiles.join("|");
464
+ }
465
+ }}
466
+ >
467
+ <div
468
+ className={css.absolute.pos(0, 0).height("100%").width(`${percentage}%`).hsl(isFiltered ? 120 : 210, 40, 80)}
469
+ />
470
+ <div className={css.relative.maxWidth(200).ellipsis}>
471
+ {formatNumber(count)} ({formatNumber(size)}B) | {name || "(no name present)"} {isFiltered && "✓" || ""}
472
+ </div>
473
+ </div>;
474
+ })}
475
+ </div>
476
+ </div>;
477
+ })() || <Button onClick={() => {
478
+ this.state.showCountByFile = true;
479
+ this.rerun();
480
+ }}>
481
+ Show Count by File
482
+ </Button>}
483
+
484
+ {this.state.showSizeByFile && (() => {
485
+ let sizes = Array.from(this.sizePerName.entries());
486
+ sort(sizes, x => -x[1]);
487
+ let topSizes = sizes.slice(0, 20);
488
+ let totalSize = sizes.reduce((sum, [, size]) => sum + size, 0);
489
+
490
+ return <div className={css.vbox(10)}>
491
+ <div className={css.hbox(10, 10).wrap}>
492
+ {topSizes.map(([name, size]) => {
493
+ let percentage = totalSize > 0 ? (size / totalSize) * 100 : 0;
494
+ let isFiltered = includedFiles.includes(name);
495
+ let count = this.countsPerName.get(name) ?? 0;
496
+
497
+ return <div
498
+ key={name}
499
+ className={css.relative.pad2(8, 4).bord2(280, 30, 60, 1).ellipsis.button}
500
+ title={isFiltered ? `Click to remove ${name} from filter` : `Click to add ${name} to filter`}
501
+ onClick={() => {
502
+ if (isFiltered) {
503
+ // Remove from filter
504
+ let newFiles = includedFiles.filter(x => x !== name);
505
+ filterParam2.value = newFiles.join("|");
506
+ } else {
507
+ // Add to filter
508
+ let newFiles = [...includedFiles.filter(x => x), name];
509
+ filterParam2.value = newFiles.join("|");
510
+ }
511
+ }}
512
+ >
513
+ <div
514
+ className={css.absolute.pos(0, 0).height("100%").width(`${percentage}%`).hsl(isFiltered ? 120 : 280, 40, 80)}
515
+ />
516
+ <div className={css.relative.maxWidth(200).ellipsis}>
517
+ {formatNumber(size)}B ({formatNumber(count)}) | {name || "(no name present)"} {isFiltered && "✓" || ""}
518
+ </div>
519
+ </div>;
520
+ })}
521
+ </div>
522
+ </div>;
523
+ })() || <Button onClick={() => {
524
+ this.state.showSizeByFile = true;
525
+ this.rerun();
526
+ }}>
527
+ Show Size by File
528
+ </Button>}
529
+ </div>
465
530
  <Table
466
531
  rows={this.datums}
467
532
  columns={columns}
@@ -20,8 +20,8 @@ export function getTimeRange(): { startTime: number; endTime: number } {
20
20
  const defaultEnd = now + timeInHour;
21
21
 
22
22
  return {
23
- startTime: startTimeParam.value ?? defaultStart,
24
- endTime: endTimeParam.value ?? defaultEnd,
23
+ startTime: startTimeParam.value || defaultStart,
24
+ endTime: endTimeParam.value || defaultEnd,
25
25
  };
26
26
  }
27
27
 
@@ -73,6 +73,9 @@ export class TimeRangeSelector extends qreact.Component {
73
73
  }}
74
74
  outerClass={!endTimeParam.value && css.opacity(0.5) || ""}
75
75
  />
76
+ </div>
77
+ <div className={css.hbox(12).wrap}>
78
+
76
79
  <Button
77
80
  hue={110} onClick={() => {
78
81
  let now = Date.now();
@@ -98,6 +98,33 @@ const getNotifyErrors = lazy(function () {
98
98
  return notifyErrors;
99
99
  });
100
100
 
101
+ export const getLoggers2 = lazy(function () {
102
+ const { IndexedLogs } = require("./IndexedLogs/IndexedLogs") as typeof import("./IndexedLogs/IndexedLogs");
103
+
104
+ if (!IndexedLogs) {
105
+ setImmediate(() => {
106
+ getLoggers2.reset();
107
+ });
108
+ return undefined;
109
+ }
110
+
111
+ return {
112
+ logLogs: new IndexedLogs<LogDatum>({ name: "logs/log", getTime: x => x.time }),
113
+ warnLogs: new IndexedLogs<LogDatum>({ name: "logs/warn", getTime: x => x.time }),
114
+ infoLogs: new IndexedLogs<LogDatum>({ name: "logs/info", getTime: x => x.time }),
115
+ errorLogs: new IndexedLogs<LogDatum>({ name: "logs/error", getTime: x => x.time }),
116
+ };
117
+ });
118
+ export const getLoggers2Async = lazy(async () => {
119
+ const { IndexedLogs } = await import("./IndexedLogs/IndexedLogs");
120
+ return {
121
+ logLogs: new IndexedLogs<LogDatum>({ name: "logs/log", getTime: x => x.time }),
122
+ warnLogs: new IndexedLogs<LogDatum>({ name: "logs/warn", getTime: x => x.time }),
123
+ infoLogs: new IndexedLogs<LogDatum>({ name: "logs/info", getTime: x => x.time }),
124
+ errorLogs: new IndexedLogs<LogDatum>({ name: "logs/error", getTime: x => x.time }),
125
+ };
126
+ });
127
+
101
128
  // NOTE: If any message (first param) starts with this, we don't log it to the disk. VERY useful for multi-line logging where it wouldn't make sense in the logs
102
129
  // NOTE: This is visible, otherwise it's easy to accidentally copy it, and not know why the text is behaving strangely (not === other seemingly equal text, etc).
103
130
  // NOTE: Also hardcoded in measure.ts (in socket-function)
@@ -116,15 +143,7 @@ void Promise.resolve().then(() => {
116
143
  startupDone = true;
117
144
  });
118
145
 
119
- let logLimitLookup: {
120
- resetTime: number;
121
- counts: Map<string, number>;
122
- } | undefined;
123
146
 
124
- const LIMIT_PERIOD = timeInMinute * 15;
125
- const LIMIT_THRESHOLD = 1000;
126
- const WARN_LIMIT = 100;
127
- const ERROR_LIMIT = 100;
128
147
 
129
148
  const logDiskDontShim = logDisk;
130
149
  /** NOTE: Calling this directly means we lose __FILE__ tracking. But... that's probably fine... */
@@ -147,47 +166,12 @@ export function logDisk(type: "log" | "warn" | "info" | "error", ...args: unknow
147
166
  hasLineLimit = true;
148
167
  }
149
168
 
150
- if (logLimitLookup) {
151
- if (logObj.time > logLimitLookup.resetTime) {
152
- logLimitLookup = undefined;
153
- }
154
- }
155
- if (!logLimitLookup) {
156
- logLimitLookup = {
157
- resetTime: logObj.time + LIMIT_PERIOD,
158
- counts: new Map(),
159
- };
160
- }
161
-
162
- let count = logLimitLookup.counts.get(logType) || 0;
163
- count++;
164
- logLimitLookup.counts.set(logType, count);
165
- let limit = LIMIT_THRESHOLD;
166
- if (type === "warn") {
167
- limit = WARN_LIMIT;
168
- } else if (type === "error") {
169
- limit = ERROR_LIMIT;
170
- }
171
- if (count > limit) {
172
- let timeUntilReset = logLimitLookup.resetTime - logObj.time;
173
- if (hasLineLimit) {
174
- process.stdout.write(`Log type hit limit, not writing log type to disk for ~${formatTime(timeUntilReset)}: ${logType}\n`);
175
- }
176
- return;
177
- }
178
- if (count >= limit) {
179
- if (hasLineLimit) {
180
- logObj[LOG_LINE_LIMIT_FLAG] = true;
181
- } else {
182
- logObj[LOG_LIMIT_FLAG] = true;
183
- }
184
- }
185
-
186
- // We don't want developer errors clogging up the error logs. However, they can still notify errors, Because this will only notify nodes that are able to access us (It uses a reverse connection scheme, so instead of talking to nodes that we can access, we only talk to nodes that can access us), Which will mean it will only notify for local services, so the developer still gets error notifications, But our errors won't be spread to all developers. BUT, we will still watch global errors, because we can contact the global server, so developers will still get errors about production issues, even while developing!
187
- if (isPublic()) {
188
- let loggers = startupDone ? getLoggers() : undefined;
169
+ // NOTE: Local logs now log to their local disk instead of backblaze, so we can log even if in development (they just won't show up on the remote server).
170
+ //if (isPublic())
171
+ {
172
+ let loggers = startupDone ? getLoggers2() : undefined;
189
173
  if (!loggers) {
190
- getLoggers.reset();
174
+ getLoggers2.reset();
191
175
  setImmediate(() => {
192
176
  logDiskDontShim(type, ...args);
193
177
  });
@@ -505,7 +505,7 @@ class URLCache {
505
505
  }
506
506
  });
507
507
  }
508
- const urlCache = new URLCache();
508
+ export const urlCache = new URLCache();
509
509
 
510
510
  const limitRecentErrors = measureWrap(function limitRecentErrors(objs: LogDatum[]) {
511
511
  sort(objs, x => x.time);
@@ -575,7 +575,7 @@ export class RecentErrors {
575
575
  void this.broadcastUpdate(undefined);
576
576
  }
577
577
  });
578
- private broadcastUpdate = batchFunction({ delay: NOTIFICATION_BROADCAST_BATCH }, () => {
578
+ private broadcastUpdate = batchFunction({ delay: NOTIFICATION_BROADCAST_BATCH, noMeasure: true }, () => {
579
579
  recentErrorsChannel.broadcast(true);
580
580
  });
581
581
 
@@ -637,6 +637,7 @@ export class RecentErrors {
637
637
  },
638
638
  rootPath: appendable.rootPath,
639
639
  noLocalFiles: config.noLocalFiles,
640
+ forceGetPublic: true,
640
641
  });
641
642
  // Filter again, as new suppressions change the errors
642
643
  await this.updateRecentErrors();
@@ -195,6 +195,7 @@ export async function runDigest() {
195
195
  endTime,
196
196
  },
197
197
  rootPath: appendable.rootPath,
198
+ forceGetPublic: true,
198
199
  });
199
200
  let filesLeft = result.files.slice();
200
201
  await Promise.all(list(32).map(() => runThread()));
@@ -0,0 +1,150 @@
1
+ module.hotreload = true;
2
+ import { qreact } from "../../../4-dom/qreact";
3
+ import { css } from "../../../4-dom/css";
4
+ import { t } from "../../../2-proxy/schema2";
5
+ import { Querysub } from "../../../4-querysub/QuerysubController";
6
+ import { sort } from "socket-function/src/misc";
7
+ import { FastArchiveViewer } from "../FastArchiveViewer";
8
+ import { LogDatum, getLoggers } from "../diskLogger";
9
+ import { Table } from "../../../5-diagnostics/Table";
10
+ import { formatDateTime } from "socket-function/src/formatting/format";
11
+ import { FastArchiveAppendable } from "../FastArchiveAppendable";
12
+ import { getTimeRange } from "../TimeRangeSelector";
13
+ import { URLParam } from "../../../library-components/URLParam";
14
+ import { InputLabelURL } from "../../../library-components/InputLabel";
15
+ import { isPublic } from "../../../config";
16
+
17
+ const forceGetPublicURL = new URLParam("forceGetPublic", false);
18
+
19
+ export class LifeCyclePages extends qreact.Component {
20
+ state = t.state({
21
+ dataSeqNum: t.atomic<number>(0),
22
+ });
23
+
24
+ private datums: LogDatum[] = [];
25
+ private latestByName = new Map<string, LogDatum>();
26
+ private fastArchiveViewer: FastArchiveViewer<LogDatum> | undefined = undefined;
27
+
28
+ rerun() {
29
+ Querysub.onCommitFinished(async () => {
30
+ void this.fastArchiveViewer?.synchronizeDataThrottled();
31
+ });
32
+ }
33
+
34
+ getLoggers = () => {
35
+ let logs: FastArchiveAppendable<LogDatum>[] = [];
36
+ let loggers = getLoggers();
37
+ if (!loggers) {
38
+ throw new Error("Loggers not available?");
39
+ }
40
+ let { logLogs, warnLogs, infoLogs, errorLogs } = loggers;
41
+ logs.push(logLogs);
42
+ logs.push(infoLogs);
43
+ logs.push(warnLogs);
44
+ logs.push(errorLogs);
45
+ return logs;
46
+ };
47
+
48
+ renderContent() {
49
+ let latestEntries = Array.from(this.latestByName.entries());
50
+ sort(latestEntries, x => -(x[1].time || 0));
51
+
52
+ let rows = latestEntries.map(([name, datum]) => ({
53
+ name,
54
+ time: datum.time,
55
+ datum,
56
+ }));
57
+
58
+ return <>
59
+ <div className={css.hbox(10)}>
60
+ <div>
61
+ Total unique __NAME__ values: {this.latestByName.size}
62
+ </div>
63
+ <div>
64
+ Total logs processed: {this.datums.length}
65
+ </div>
66
+ </div>
67
+
68
+ {rows.length === 0 && <div className={css.hsl(40, 50, 50).colorhsl(60, 50, 100).boldStyle.pad2(10).ellipsis}>
69
+ No logs matched, either increase the time range or run the search.
70
+ </div>}
71
+
72
+ <Table
73
+ rows={rows}
74
+ columns={{
75
+ name: {
76
+ title: "Name",
77
+ formatter: x => <div>{String(x)}</div>
78
+ },
79
+ time: {
80
+ title: "Last Time",
81
+ formatter: x => <div>{formatDateTime(Number(x))}</div>
82
+ },
83
+ }}
84
+ lineLimit={4}
85
+ initialLimit={20}
86
+ characterLimit={400}
87
+ />
88
+ </>;
89
+ }
90
+
91
+ render() {
92
+ this.state.dataSeqNum;
93
+
94
+ const timeRange = getTimeRange();
95
+ return (
96
+ <div className={css.vbox(20).pad2(20).fillBoth}>
97
+ <div className={css.hbox(10)}>
98
+ <div className={css.fontSize(14)}>
99
+ Life Cycle Analysis
100
+ </div>
101
+ {!isPublic() && <InputLabelURL
102
+ label="Force Public Logs" checkbox url={forceGetPublicURL}
103
+ onChangeValue={(newValue) => {
104
+ this.rerun();
105
+ }}
106
+ />}
107
+ </div>
108
+
109
+ <FastArchiveViewer
110
+ ref2={x => this.fastArchiveViewer = x}
111
+ fastArchives={this.getLoggers}
112
+ runOnLoad={false}
113
+ forceGetPublic={forceGetPublicURL.value}
114
+ onStart={async () => {
115
+ this.datums = [];
116
+ this.latestByName = new Map();
117
+ }}
118
+ onDatums={(source, datums, file) => {
119
+ for (let datum of datums) {
120
+ let time = datum.time;
121
+ if (time && (time < timeRange.startTime || time > timeRange.endTime)) {
122
+ continue;
123
+ }
124
+ this.datums.push(datum);
125
+ }
126
+ }}
127
+ onFinish={() => {
128
+ // Sort all datums by time
129
+ sort(this.datums, x => x.time || 0);
130
+
131
+ // Get the latest value (last time) for every __NAME__
132
+ this.latestByName.clear();
133
+ for (let datum of this.datums) {
134
+ const name = datum.__NAME__;
135
+ if (name) {
136
+ this.latestByName.set(name, datum);
137
+ }
138
+ }
139
+
140
+ Querysub.commit(() => {
141
+ this.state.dataSeqNum++;
142
+ });
143
+ }}
144
+ >
145
+ {this.renderContent()}
146
+ </FastArchiveViewer>
147
+ </div>
148
+ );
149
+ }
150
+ }