querysub 0.46.0 → 0.50.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.
@@ -4,6 +4,8 @@ import { canHaveChildren } from "socket-function/src/types";
4
4
  import { qreact } from "../4-dom/qreact";
5
5
  import preact, { ContextType } from "preact";
6
6
  import { errorMessage, warnMessage } from "../library-components/colors";
7
+ import { URLParam } from "../library-components/URLParam";
8
+ import { Querysub } from "../4-querysub/QuerysubController";
7
9
 
8
10
  export { errorMessage, warnMessage };
9
11
 
@@ -26,6 +28,7 @@ type StringFormatters = (
26
28
  | "timeSpan" | "date"
27
29
  | "error" | "link"
28
30
  | "toSpaceCase"
31
+ | "<Selector>"
29
32
  );
30
33
 
31
34
  function d(value: unknown, formattedValue: preact.ComponentChild) {
@@ -44,6 +47,7 @@ let formatters: { [formatter in StringFormatters]: (value: unknown) => preact.Co
44
47
  date: (value) => d(value, formatVeryNiceDateTime(Number(value))),
45
48
  error: (value) => d(value, <span class={errorMessage}>{String(value)}</span>),
46
49
  toSpaceCase: (value) => d(value, toSpaceCase(String(value))),
50
+ "<Selector>": (value) => d(value, <Selector {...JSON.parse(String(value).slice("<Selector>".length))} />),
47
51
  link: (value) => {
48
52
  if (value === undefined || value === null) {
49
53
  return "";
@@ -102,6 +106,12 @@ let formatters: { [formatter in StringFormatters]: (value: unknown) => preact.Co
102
106
  if (typeof value === "string" && value.startsWith("https://")) {
103
107
  return formatters.link(value);
104
108
  }
109
+ if (typeof value === "string" && value.startsWith("<Selector>")) {
110
+ let configParts = value.slice("<Selector>".length);
111
+ if (isJSON(configParts)) {
112
+ return formatters["<Selector>"](value);
113
+ }
114
+ }
105
115
  // Don't render nested objects, etc, otherwise passing large arrays
106
116
  // could just in us blowing the output up, when their intention
107
117
  // is to just show "Array()"
@@ -111,6 +121,13 @@ let formatters: { [formatter in StringFormatters]: (value: unknown) => preact.Co
111
121
  return formatters.string(value);
112
122
  },
113
123
  };
124
+ function isJSON(value: unknown) {
125
+ try {
126
+ JSON.parse(value as string);
127
+ return true;
128
+ } catch { }
129
+ return false;
130
+ }
114
131
  function formatVArray(value: unknown[], formatter: StringFormatters) {
115
132
  if (!Array.isArray(value)) {
116
133
  return <span class={errorMessage}>Expected array, got {typeof value}</span>;
@@ -164,4 +181,78 @@ export function toSpaceCase(text: string) {
164
181
  // Convert multiple spaces to a single space
165
182
  .replace(/\s+/g, " ");
166
183
  ;
167
- }
184
+ }
185
+
186
+ export interface SelectorProps {
187
+ options: { value: string, urlValue: string, title: string }[];
188
+ urlKey: string;
189
+ }
190
+ class Selector extends qreact.Component<SelectorProps> {
191
+ state = {
192
+ expanded: false,
193
+ };
194
+ componentDidMount(): void {
195
+ document.body.addEventListener("click", this.onBodyClick);
196
+ qreact.onDispose(this.onBodyClick);
197
+ }
198
+
199
+ justSetExpanded = false;
200
+ onBodyClick = () => {
201
+ if (this.justSetExpanded) {
202
+ this.justSetExpanded = false;
203
+ return;
204
+ }
205
+ Querysub.commit(() => {
206
+ this.state.expanded = false;
207
+ });
208
+ };
209
+ render() {
210
+ const { options, urlKey } = this.props;
211
+ if (options.length === 0) {
212
+ return "";
213
+ }
214
+ let urlParam = new URLParam(urlKey, options[0].urlValue);
215
+ let selectedOption = options.find(x => x.urlValue === urlParam.value);
216
+ let component = qreact.getRenderingComponentAssert();
217
+ return (
218
+ <div
219
+ className={css.relative.zIndex(1)}
220
+ onClick={() => {
221
+ if (this.state.expanded) return;
222
+
223
+ this.state.expanded = true;
224
+ this.justSetExpanded = true;
225
+ }}
226
+ >
227
+ <div
228
+ className={css.hsla(0, 0, 50, 0.3).pad2(6, 2).button}
229
+ title={selectedOption?.title}
230
+ >
231
+ {formatValue(selectedOption?.value)}
232
+ </div>
233
+
234
+ {this.state.expanded && (
235
+ <div
236
+ className={css.absolute.pos(0, 0).width("100vw").vbox0}
237
+ >
238
+ <div className={css.vbox(2).hsla(0, 0, 80, 1).pad2(6)}>
239
+ {options.map(x =>
240
+ <div
241
+ className={css.pad2(6, 2).hsla(0, 0, 50, 0.5).button}
242
+ onClick={() => {
243
+ urlParam.value = x.urlValue;
244
+ }}
245
+ title={x.title}
246
+ >({x.title}) {formatValue(x.value)}</div>
247
+ )}
248
+ </div>
249
+ </div>
250
+ )}
251
+ </div>
252
+ );
253
+ }
254
+ }
255
+
256
+ export function encodeFormattedSelector(config: SelectorProps) {
257
+ return `<Selector>${JSON.stringify(config)}`;
258
+ }
@@ -102,7 +102,12 @@ export class Table<RowT extends RowType> extends qreact.Component<TableType<RowT
102
102
  }
103
103
  innerContent = props.children as any;
104
104
  }
105
- return <td {...attributes}>
105
+ return <td
106
+ {...attributes}
107
+ data-column={columnName}
108
+ // If it ever becomes non-table cell, it breaks the table
109
+ style={{ display: "table-cell" }}
110
+ >
106
111
  {innerContent}
107
112
  </td>;
108
113
  })}
@@ -170,14 +175,4 @@ function renderTrimmed(config: {
170
175
  let close = showModal({
171
176
  content: <FullscreenModal onCancel={() => {
172
177
  close.close();
173
- }}>
174
- <div class={css.whiteSpace("pre-wrap")}>
175
- {content}
176
- </div>
177
- </FullscreenModal>
178
- });
179
- }
180
- },
181
- innerContent: contentStr
182
- };
183
- }
178
+
@@ -1,6 +1,7 @@
1
1
  import debugbreak from "debugbreak";
2
2
  import { formatTime } from "socket-function/src/formatting/format";
3
3
  import { isDefined } from "../misc";
4
+ import { encodeFormattedSelector } from "./GenericFormat";
4
5
 
5
6
  const SUB_BUCKET_COUNT = 100;
6
7
 
@@ -71,13 +72,14 @@ export class TimeGrouper {
71
72
  }
72
73
  }
73
74
  public getSummary() {
75
+
74
76
  let buckets = this.buckets.map(bucket => {
75
77
  // Reduce subBuckets within each bucket
76
78
  let sub = bucket.subBuckets;
77
79
  return {
78
80
  name: bucket.name,
79
81
  duration: bucket.duration,
80
- recordedDuration: sub.at(-1)!.startTime - sub[0].endTime,
82
+ recordedDuration: (sub.at(-1)?.startTime ?? 0) - (sub.at(0)?.endTime ?? 0),
81
83
  min: sub.map(x => x.min).reduce((a, b) => Math.min(a, b), Number.POSITIVE_INFINITY),
82
84
  max: sub.map(x => x.max).reduce((a, b) => Math.max(a, b), Number.NEGATIVE_INFINITY),
83
85
  sum: sub.map(x => x.sum).reduce((a, b) => a + b, 0),
@@ -95,22 +97,29 @@ export class TimeGrouper {
95
97
 
96
98
  /** For sampled values, ex, % CPU usage */
97
99
  public getStateSummary(format: (value: number) => string): string {
98
- return this.getSummary().map(bucket => {
99
- if (bucket.duration === 0 || bucket.min === bucket.max) {
100
- if (bucket.min === 0) return undefined;
101
- return `(${bucket.name}) ${format(bucket.min)}`;
102
- }
103
- return `(${bucket.name}) ${format(bucket.min)} to ${format(bucket.max)}`;
104
- }).filter(isDefined).join("\n");
100
+ return encodeFormattedSelector({
101
+ // Always use the same urlKey, so we can change all displays at once
102
+ urlKey: "timegroupergroup",
103
+ options: this.getSummary().map(bucket => {
104
+ if (bucket.duration === 0 || bucket.min === bucket.max) {
105
+ if (bucket.min === 0) return undefined;
106
+ return { value: `${format(bucket.min)}`, title: bucket.name, urlValue: bucket.name };
107
+ }
108
+ return { value: `${format(bucket.min)} to ${format(bucket.max)}`, title: bucket.name, urlValue: bucket.name };
109
+ }).filter(isDefined)
110
+ });
105
111
  }
106
112
  /** Sums up the values. Ex, for time waiting. */
107
113
  public getEventSummary(format: (value: number) => string): string {
108
- return this.getSummary().map(bucket => {
109
- if (bucket.duration === 0) {
110
- if (bucket.sum === 0) return undefined;
111
- return `(${bucket.name}) ${format(bucket.sum)}`;
112
- }
113
- return `(${bucket.name}) ${format(bucket.sum)}`;
114
- }).filter(isDefined).join("\n");
114
+ return encodeFormattedSelector({
115
+ urlKey: "timegroupergroup",
116
+ options: this.getSummary().map(bucket => {
117
+ if (bucket.duration === 0) {
118
+ if (bucket.sum === 0) return undefined;
119
+ return { value: `${format(bucket.sum)}`, title: bucket.name, urlValue: bucket.name };
120
+ }
121
+ return { value: `${format(bucket.sum)}`, title: bucket.name, urlValue: bucket.name };
122
+ }).filter(isDefined)
123
+ });
115
124
  }
116
125
  }
@@ -3,7 +3,7 @@ import { PathValueController } from "../0-path-value-core/PathValueController";
3
3
  import { PathValue, authorityStorage, pathWatcher } from "../0-path-value-core/pathValueCore";
4
4
  import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
5
5
  import { measureFnc } from "socket-function/src/profiling/measure";
6
- import { isNode, list, sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
6
+ import { QueueLimited, isNode, list, sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
7
7
  import { getSingleSizeEstimate } from "./shared";
8
8
  import { devDebugbreak, isDevDebugbreak } from "../config";
9
9
  import { nodePathAuthority } from "../0-path-value-core/NodePathAuthorities";
@@ -172,12 +172,11 @@ function sampleRandom(config: {
172
172
  }
173
173
  }
174
174
 
175
- const AUDIT_WINDOW_SIZE = 10;
176
- let lastAuditsSizes: {
175
+ let lastAuditsSizes = new QueueLimited<{
177
176
  count: number;
178
177
  size: number;
179
178
  time: number;
180
- }[] = [];
179
+ }>(10);
181
180
 
182
181
  async function auditSpecificPaths(config: {
183
182
  paths: string[];
@@ -211,9 +210,10 @@ async function auditSpecificPathsBase(config: {
211
210
  // audit fewer paths, up to just auditing a single path).
212
211
  // Use getSingleSizeEstimate to get sizes
213
212
  // If forceAudit, don't limit the paths
214
- if (lastAuditsSizes.length > 0 && !config.forceAudit) {
215
- let auditWindowTotalSize = lastAuditsSizes.map(v => v.size).reduce((a, b) => a + b, 0);
216
- let auditWindowTime = Date.now() - lastAuditsSizes[0].time;
213
+ let oldest = lastAuditsSizes.getOldest();
214
+ if (oldest && !config.forceAudit) {
215
+ let auditWindowTotalSize = lastAuditsSizes.getAllUnordered().map(v => v.size).reduce((a, b) => a + b, 0);
216
+ let auditWindowTime = Date.now() - oldest.time;
217
217
  let auditWindowRate = auditWindowTotalSize / auditWindowTime;
218
218
  let fractionToReduce = Math.min(1, auditWindowRate / AUDIT_MAX_BYTES_PER_SECOND);
219
219
  let count = Math.max(1, Math.floor(paths.length * fractionToReduce));
@@ -229,9 +229,6 @@ async function auditSpecificPathsBase(config: {
229
229
  + v.path.length * 2
230
230
  ).reduce((a, b) => a + b, 0);
231
231
  lastAuditsSizes.push({ count: paths.length, size, time: Date.now() });
232
- while (lastAuditsSizes.length > AUDIT_WINDOW_SIZE) {
233
- lastAuditsSizes.shift();
234
- }
235
232
  }
236
233
 
237
234
  const THRESHOLD_TIME = Date.now() - MAX_ASSUMED_PROPAGATION_DELAY;
@@ -11,9 +11,21 @@ import { formatNumber, formatPercent, formatTime } from "socket-function/src/for
11
11
  import os from "os";
12
12
  import { isNode } from "typesafecss";
13
13
  import { diskLog } from "../diagnostics/logs/diskLogger";
14
+ import { devDebugbreak } from "../config";
15
+ import { addStatPeriodic, addStatSumPeriodic, addTimeProfileDistribution, onTimeProfile, logNodeStats, logNodeStateStats, registerNodeMetadata } from "../-0-hooks/hooks";
16
+ import { magenta } from "socket-function/src/formatting/logColors";
14
17
 
15
18
 
16
- const POLL_INTERVAL = timeInMinute * 5;
19
+ let POLL_INTERVAL = timeInMinute * 5;
20
+
21
+ //todonext
22
+ // Updating didn't work?
23
+ // - Reboot, then try do-update again, then if it still isn't up, ssh in and see what's wrong
24
+
25
+ //todonext;
26
+ // Testing
27
+ POLL_INTERVAL = 10 * 1000;
28
+
17
29
 
18
30
  // Undefined means we infer the column
19
31
  // Null means the column is removed
@@ -37,10 +49,10 @@ export type ExtraMetadata = {
37
49
  getValue: () => MaybePromise<unknown>;
38
50
  };
39
51
  let extraMetadatas: ExtraMetadata[] = [];
40
- export function registerNodeMetadata(getter: ExtraMetadata) {
52
+ registerNodeMetadata.declare(function registerNodeMetadata(getter: ExtraMetadata) {
41
53
  //console.log(`Registering metadata: ${getter.columnName}`);
42
54
  extraMetadatas.push(getter);
43
- }
55
+ });
44
56
 
45
57
  // IMPORTANT! We no longer diskLog here! diskLogging is not ideal for metrics like these. You almost only
46
58
  // care about the current metrics OR past trends. A single value in the logs from the past is just
@@ -76,21 +88,24 @@ let eventValues = cache((title: string, format: (value: number) => string): { (v
76
88
  });
77
89
 
78
90
  /** For values that should be summed to get the value. */
79
- export function logNodeStats(title: string, format: (value: number) => string): { (value: number): void } {
80
- return eventValues(title, format);
81
- }
91
+ logNodeStats.declare(function registerLogNodeStats(title: string, format: (value: number) => string, value: number) {
92
+ eventValues(title, format)(value);
93
+ });
82
94
  /** For state values (ex, % CPU usage), for which summing wouldn't make sense. */
83
- export function logNodeStateStats(title: string, format: (value: number) => string): { (value: number): void } {
84
- return stateValues(title, format);
85
- }
95
+ logNodeStateStats.declare(function registerLogNodeStateStats(title: string, format: (value: number) => string, value: number) {
96
+ stateValues(title, format)(value);
97
+ });
86
98
 
87
99
  let existingPeriodic = new Set<string>();
88
100
  /** For state values (ex, % CPU usage), for which summing wouldn't make sense. */
89
- export function addStatPeriodic(
90
- title: string,
91
- getValue: () => number,
92
- format: (value: number) => string = formatNumber,
101
+ addStatPeriodic.declare(function addStatPeriodic(
102
+ config: {
103
+ title: string,
104
+ getValue: () => number,
105
+ format?: (value: number) => string,
106
+ }
93
107
  ) {
108
+ const { title, getValue, format = formatNumber } = config;
94
109
  let hash = JSON.stringify({ title, getValue: getValue.toString(), format: format.toString() });
95
110
  // Ignoring duplicates, otherwise we end up logging way too much
96
111
  if (globalThis.isHotReloading?.() && existingPeriodic.has(hash)) return;
@@ -106,13 +121,16 @@ export function addStatPeriodic(
106
121
  return timeGrouper.getStateSummary(format);
107
122
  },
108
123
  });
109
- }
124
+ });
110
125
  /** For values that should be summed to get the value. */
111
- export function addStatSumPeriodic(
112
- title: string,
113
- getValue: () => number,
114
- format: (value: number) => string = formatNumber,
126
+ addStatSumPeriodic.declare(function addStatSumPeriodic(
127
+ config: {
128
+ title: string,
129
+ getValue: () => number,
130
+ format?: ((value: number) => string),
131
+ }
115
132
  ) {
133
+ const { title, getValue, format = formatNumber } = config;
116
134
  let hash = JSON.stringify({ title, getValue: getValue.toString(), format: format.toString() });
117
135
  // Ignoring duplicates, otherwise we end up logging way too much
118
136
  if (globalThis.isHotReloading?.() && existingPeriodic.has(hash)) return;
@@ -128,10 +146,10 @@ export function addStatSumPeriodic(
128
146
  return timeGrouper.getEventSummary(format);
129
147
  },
130
148
  });
131
- }
149
+ });
132
150
 
133
151
  let pendingTimeProfiles = new Map<string, { start: number; end: number }[]>();
134
- export function onTimeProfile(title: string, startTime: number) {
152
+ onTimeProfile.declare(function onTimeProfile(title: string, startTime: number) {
135
153
  let profiles = pendingTimeProfiles.get(title);
136
154
  if (!profiles) {
137
155
  profiles = [];
@@ -143,7 +161,7 @@ export function onTimeProfile(title: string, startTime: number) {
143
161
  });
144
162
  }
145
163
  profiles.push({ start: startTime, end: Date.now() });
146
- }
164
+ });
147
165
 
148
166
  /** Ex, for tracking the time distribution of API calls, or watchFunction evaluation, etc.
149
167
  * - Specifically for time, so we can handle overlap cases (such as waiting for 10 calls at once),
@@ -155,7 +173,7 @@ export function onTimeProfile(title: string, startTime: number) {
155
173
  * you need to harvest values from an external library, in which case, you will need
156
174
  * this function.
157
175
  */
158
- export function addTimeProfileDistribution(
176
+ addTimeProfileDistribution.declare(function addTimeProfileDistribution(
159
177
  title: string,
160
178
  /** It's expected that this will return all values since the last call to harvestValues
161
179
  * (basically, copy your value buffer and clear the old one).
@@ -166,8 +184,16 @@ export function addTimeProfileDistribution(
166
184
  // Ignoring duplicates, otherwise we end up logging way too much
167
185
  if (globalThis.isHotReloading?.() && existingPeriodic.has(hash)) return;
168
186
 
187
+ // TODO: Because we know the time ranges, we can actually provide better state values. As in,
188
+ // within a single call we can determine the maximum and minimum instantaneous rates. However...
189
+ // this is more difficult, and might not be useful (as one really fast time group shouldn't result
190
+ // in a spike of an infinite rate, it's better to smooth it out a bit).
191
+
169
192
  let fractionTimeWaitingGroup = new TimeGrouper();
170
193
  let ratePerSecondGroup = new TimeGrouper();
194
+ let timePerValueGroup = new TimeGrouper();
195
+ let parallelFactorGroup = new TimeGrouper();
196
+ let maxEstRateGroup = new TimeGrouper();
171
197
  let lastPollTime = Date.now();
172
198
  runInfinitePoll(POLL_INTERVAL, () => {
173
199
  let originalStartTime = lastPollTime;
@@ -175,39 +201,61 @@ export function addTimeProfileDistribution(
175
201
  values = values.slice();
176
202
  sort(values, x => x.start);
177
203
  let freeTime = 0;
178
- for (let { start, end } of values) {
204
+ let totalTime = 0;
205
+
206
+ console.log(magenta(`${title} Time: ${values.length}`));
207
+
208
+ for (let obj of values) {
209
+ if (!obj) {
210
+ devDebugbreak();
211
+ }
212
+ let { start, end } = obj;
179
213
  if (start > lastPollTime) {
180
214
  freeTime += start - lastPollTime;
181
215
  }
182
216
  lastPollTime = end;
217
+ totalTime += end - start;
183
218
  }
184
219
  let now = Date.now();
185
220
  freeTime += now - lastPollTime;
186
221
  lastPollTime = now;
187
222
 
188
223
  let duration = lastPollTime - originalStartTime;
189
- let waitingTime = duration - freeTime;
224
+ let activeTime = duration - freeTime;
190
225
  let ratePerSecond = values.length / (duration / 1000);
191
-
192
- fractionTimeWaitingGroup.onValueChanged(waitingTime / duration);
226
+ let averageTimePerValue = totalTime / values.length;
227
+ let fracActive = activeTime / duration;
228
+ fractionTimeWaitingGroup.onValueChanged(fracActive);
193
229
  ratePerSecondGroup.onValueChanged(ratePerSecond);
194
- });
230
+ if (values.length > 0) {
231
+ timePerValueGroup.onValueChanged(averageTimePerValue);
232
+ }
195
233
 
196
- registerNodeMetadata({
197
- columnName: `% Time in ${title}`,
198
- column: {},
199
- getValue() {
200
- return fractionTimeWaitingGroup.getStateSummary(formatPercent);
201
- },
234
+ let parallelFactor = 0;
235
+ let totalTimeOverlapped = values.map(x => x.end - x.start).reduce((a, b) => a + b, 0);
236
+ if (totalTimeOverlapped > 0) {
237
+ parallelFactor = totalTimeOverlapped / activeTime;
238
+ }
239
+ parallelFactorGroup.onValueChanged(parallelFactor);
240
+
241
+ let maxEstRate = fracActive === 0 ? 0 : ratePerSecond / fracActive;
242
+ maxEstRateGroup.onValueChanged(maxEstRate);
202
243
  });
244
+
203
245
  registerNodeMetadata({
204
- columnName: `${title}/S`,
246
+ columnName: `${title} Time`,
205
247
  column: {},
206
248
  getValue() {
207
- return ratePerSecondGroup.getStateSummary(formatNumber);
249
+ return [
250
+ fractionTimeWaitingGroup.getStateSummary(formatPercent),
251
+ timePerValueGroup.getStateSummary(x => formatTime(x) + "/per"),
252
+ ratePerSecondGroup.getStateSummary(x => formatNumber(x) + "/s"),
253
+ parallelFactorGroup.getStateSummary(x => formatNumber(x) + "X"),
254
+ maxEstRateGroup.getStateSummary(x => `est max(${formatNumber(x)}/s)`),
255
+ ];
208
256
  },
209
257
  });
210
- }
258
+ });
211
259
 
212
260
  if (isNode()) {
213
261
  void runInfinitePollCallAtStart(POLL_INTERVAL, async () => {
@@ -216,24 +264,24 @@ if (isNode()) {
216
264
  const free = os.freemem();
217
265
  const total = os.totalmem();
218
266
 
219
- logNodeStateStats("System|Memory Free", x => formatNumber(x) + "B")(free);
220
- logNodeStateStats("System|Memory Free %", formatPercent)(free / total);
267
+ logNodeStateStats("System|Memory Free", x => formatNumber(x) + "B", free);
268
+ logNodeStateStats("System|Memory Free %", formatPercent, free / total);
221
269
 
222
270
  let constrainedMemory = process.constrainedMemory();
223
271
  if (constrainedMemory !== undefined && constrainedMemory < 2 ** 48) {
224
- logNodeStateStats("System|Constrained Memory", x => formatNumber(x) + "B")(constrainedMemory);
272
+ logNodeStateStats("System|Constrained Memory", x => formatNumber(x) + "B", constrainedMemory);
225
273
  }
226
274
 
227
275
  let usedCPU = process.cpuUsage();
228
- logNodeStateStats("System|CPU User", formatTime)(usedCPU.user * 1000);
229
- logNodeStateStats("System|CPU System", formatTime)(usedCPU.system * 1000);
276
+ logNodeStateStats("System|CPU User", formatTime, usedCPU.user * 1000);
277
+ logNodeStateStats("System|CPU System", formatTime, usedCPU.system * 1000);
230
278
 
231
279
  let cpuCount = os.cpus().length;
232
- logNodeStateStats("System|CPU Count", formatNumber)(cpuCount);
280
+ logNodeStateStats("System|CPU Count", formatNumber, cpuCount);
233
281
 
234
282
  let load = os.loadavg();
235
- logNodeStateStats("System|Average Load", formatPercent)(load[0]);
236
- logNodeStateStats("System|Average Free Cores", formatPercent)((os.cpus().length - load[0]));
283
+ logNodeStateStats("System|Average Load", formatPercent, load[0]);
284
+ logNodeStateStats("System|Average Free Cores", formatPercent, (os.cpus().length - load[0]));
237
285
 
238
286
  });
239
287
  }
@@ -3,7 +3,7 @@ import { formatTime } from "socket-function/src/formatting/format";
3
3
  import { registerMeasureInfo } from "socket-function/src/profiling/measure";
4
4
  import { isNode } from "typesafecss";
5
5
  import { monitorEventLoopDelay } from "perf_hooks";
6
- import { registerNodeMetadata } from "./nodeMetadata";
6
+ import { registerNodeMetadata } from "../-0-hooks/hooks";
7
7
  import { getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
8
8
 
9
9
  const POLL_INTERVAL = 350;
@@ -18,7 +18,7 @@ import { requiresNetworkTrustHook } from "../../src/-d-trust/NetworkTrust2";
18
18
  import { getDomain, isNoNetwork } from "../../src/config";
19
19
  import { NodeMetadataController } from "../../src/5-diagnostics/nodeMetadata";
20
20
  import { isDefined } from "../../src/misc";
21
- import { errorMessage, formatValue, toSpaceCase } from "../../src/5-diagnostics/GenericFormat";
21
+ import { encodeFormattedSelector, errorMessage, formatValue, toSpaceCase } from "../../src/5-diagnostics/GenericFormat";
22
22
  import { ValueAuditController } from "../../src/5-diagnostics/memoryValueAudit";
23
23
  import { getExternalIP } from "../../src/misc/networking";
24
24
  import dns from "dns";
@@ -458,7 +458,6 @@ class NodeViewerControllerBase {
458
458
  wrapAddTableValue("port", {}, async () => {
459
459
  return nodeId.split(":").at(-1);
460
460
  }),
461
- //
462
461
  wrapAddTableValue("threadId", {
463
462
  formatter: "link:/?page=user&showingmanagement&managementpage=LogViewer&filterThreadIds=$VALUE$<Thread Errors $VALUE$>"
464
463
  }, async () => {
@@ -493,8 +492,8 @@ class NodeViewerControllerBase {
493
492
  }
494
493
  } catch (e: any) {
495
494
  // Any errors almost certainly mean they aren't exposing any extra metadata, which is fine.
496
- // columns["extraMetadatasError"] = { formatter: "error" };
497
- // row["extraMetadatasError"] = "Error: " + e.stack;
495
+ columns["extraMetadatasError"] = { formatter: "error" };
496
+ row["extraMetadatasError"] = "Error: " + e.stack;
498
497
  }
499
498
  return {
500
499
  columns,