querysub 0.590.0 → 0.591.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.590.0",
3
+ "version": "0.591.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -71,7 +71,7 @@
71
71
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
72
72
  "pako": "^2.1.0",
73
73
  "peggy": "^5.0.6",
74
- "sliftutils": "^1.7.65",
74
+ "sliftutils": "^1.7.66",
75
75
  "socket-function": "^1.2.27",
76
76
  "terser": "^5.31.0",
77
77
  "typenode": "^6.6.1",
@@ -116,7 +116,7 @@ export function wrapArchivesWithCache2(archives: IArchives, rootConfig?: {
116
116
  get2: async (fileName: string, config?: GetConfig) => {
117
117
  let result = await archives.get2(fileName, config);
118
118
  // Range reads are partial, so they can't populate the cache.
119
- if (result && !config?.range) {
119
+ if (result?.data && !config?.range) {
120
120
  await cacheBuffer(fileName, result.data);
121
121
  }
122
122
  return result;
@@ -2,7 +2,8 @@ import { SocketFunction } from "socket-function/SocketFunction";
2
2
  import { qreact } from "../../4-dom/qreact";
3
3
  import { css } from "typesafecss";
4
4
  import { formatNumber, formatTime, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
5
- import { timeoutToUndefinedSilent, timeInSecond } from "socket-function/src/misc";
5
+ import { timeInSecond } from "socket-function/src/misc";
6
+ import { timeoutToError } from "../../errors";
6
7
  import { StatsValue, getStatsTop } from "socket-function/src/profiling/stats";
7
8
  import type { MeasureChunk, MeasureSummary } from "../../diagnostics/watchdog";
8
9
  import { NodeCapabilitiesController } from "../../-g-core-values/NodeCapabilities";
@@ -10,19 +11,19 @@ import { getSyncedController } from "../../library-components/SyncedController";
10
11
  import { assertIsManagementUser } from "../../diagnostics/managementPages";
11
12
 
12
13
  const NODE_CALL_TIMEOUT = timeInSecond * 10;
13
- const BAR_HEIGHT_PX = 10;
14
- const MIN_SEGMENT_WIDTH_PX = 30;
14
+ const BAR_HEIGHT_PX = 6;
15
+ const MIN_BAR_WIDTH_PX = 30;
16
+ // Green at idle, red at fully busy.
17
+ const BAR_IDLE_HUE = 120;
18
+ const BAR_FLASH_THRESHOLD = 0.6;
19
+ const FLASH_CLASS_NAME = "ServiceMeasureBars-flash";
15
20
 
16
21
  class ServiceMeasureControllerBase {
17
- public async getMeasureSummaries(nodeIds: string[]): Promise<Record<string, MeasureSummary | undefined>> {
18
- let result: Record<string, MeasureSummary | undefined> = {};
19
- await Promise.all(nodeIds.map(async nodeId => {
20
- result[nodeId] = await timeoutToUndefinedSilent(NODE_CALL_TIMEOUT, NodeCapabilitiesController.nodes[nodeId].getMeasureSummary());
21
- }));
22
- return result;
22
+ public async getMeasureSummary(nodeId: string): Promise<MeasureSummary | undefined> {
23
+ return await timeoutToError(NODE_CALL_TIMEOUT, NodeCapabilitiesController.nodes[nodeId].getMeasureSummary(), () => new Error(`Timed out asking ${nodeId} for its measure summary`));
23
24
  }
24
25
  public async getMeasureBreakdown(nodeId: string, range: { startTime: number; endTime: number }): Promise<{ chunk?: MeasureChunk }> {
25
- let chunk = await timeoutToUndefinedSilent(NODE_CALL_TIMEOUT, NodeCapabilitiesController.nodes[nodeId].getMeasureBreakdown(range));
26
+ let chunk = await timeoutToError(NODE_CALL_TIMEOUT, NodeCapabilitiesController.nodes[nodeId].getMeasureBreakdown(range), () => new Error(`Timed out asking ${nodeId} for its measure breakdown`));
26
27
  return { chunk };
27
28
  }
28
29
  }
@@ -30,7 +31,7 @@ export const ServiceMeasureController = getSyncedController(SocketFunction.regis
30
31
  "ServiceMeasureController-3f8c1de2-5b74-4e0f-a6c9-8d21f47b90a5",
31
32
  new ServiceMeasureControllerBase(),
32
33
  () => ({
33
- getMeasureSummaries: { hooks: [assertIsManagementUser], compress: true },
34
+ getMeasureSummary: { hooks: [assertIsManagementUser], compress: true },
34
35
  getMeasureBreakdown: { hooks: [assertIsManagementUser], compress: true },
35
36
  }),
36
37
  () => ({
@@ -41,52 +42,55 @@ function percent(value: number) {
41
42
  return `${(value * 100).toFixed(2)}%`;
42
43
  }
43
44
 
44
- function nameHue(name: string) {
45
- let hash = 0;
46
- for (let i = 0; i < name.length; i++) {
47
- hash = (hash * 31 + name.charCodeAt(i)) | 0;
48
- }
49
- return Math.abs(hash) % 360;
50
- }
51
-
52
45
  export class ServiceMeasureBars extends qreact.Component<{
53
46
  serviceNodeIds: string[];
54
- /** Every node id on the page, so all rows share one cached call. */
55
- allNodeIds: string[];
56
47
  expandedNodeId: string;
57
48
  onToggleNode: (nodeId: string) => void;
58
49
  }> {
59
50
  render() {
60
51
  let controller = ServiceMeasureController(SocketFunction.browserNodeId());
61
- let summaries = controller.getMeasureSummaries(this.props.allNodeIds);
62
- if (!summaries) return undefined;
63
52
 
64
- return <div className={css.vbox(2)}>
53
+ return <div className={css.vbox(1).alignItems("stretch")}>
65
54
  {this.props.serviceNodeIds.map(nodeId => {
66
- let summary = summaries[nodeId];
55
+ let isExpanded = this.props.expandedNodeId === nodeId;
56
+ let summary: MeasureSummary | undefined;
57
+ try {
58
+ summary = controller.getMeasureSummary(nodeId);
59
+ } catch (err) {
60
+ return <div key={nodeId}
61
+ className={css.height(BAR_HEIGHT_PX).hsl(0, 70, 55)}
62
+ title={`Node did not respond (it likely doesn't have the measure API yet)\n${nodeId}\n${(err as Error).stack ?? err}`}
63
+ />;
64
+ }
67
65
  if (!summary) return undefined;
68
66
  let timeRunFor = summary.endTime - summary.startTime;
69
67
  if (timeRunFor <= 0) return undefined;
70
- let isExpanded = this.props.expandedNodeId === nodeId;
71
- return <div key={nodeId}
72
- className={
73
- css.hbox(1).wrap.button
74
- + (isExpanded && css.outline("1px solid hsl(210, 60%, 50%)") || "")
75
- }
76
- onClick={() => this.props.onToggleNode(nodeId)}
77
- >
78
- {summary.entries.map(entry => {
79
- let fraction = entry.ownTime / timeRunFor;
80
- return <div key={entry.name}
81
- className={css
82
- .height(BAR_HEIGHT_PX)
68
+ let fraction = summary.profiledTime / timeRunFor;
69
+ let clampedFraction = Math.min(1, Math.max(0, fraction));
70
+ let hue = BAR_IDLE_HUE * (1 - clampedFraction);
71
+ let isFlashing = fraction > BAR_FLASH_THRESHOLD;
72
+ return <div key={nodeId} className={css.hbox(0)}>
73
+ <div
74
+ className={
75
+ css.height(BAR_HEIGHT_PX)
83
76
  .width(`${fraction * 100}%`)
84
- .minWidth(MIN_SEGMENT_WIDTH_PX)
85
- .background(`hsl(${nameHue(entry.name)}, 60%, 60%)`)
86
- }
87
- title={`${entry.name}\n${formatTime(entry.ownTime)} (${percent(fraction)} of ${formatTime(timeRunFor)} profiled)\n${nodeId}`}
88
- />;
89
- })}
77
+ .minWidth(MIN_BAR_WIDTH_PX)
78
+ .button
79
+ .hsl(hue, 70, isExpanded ? 40 : 55)
80
+ + (isFlashing && (" " + FLASH_CLASS_NAME) || "")
81
+ }
82
+ title={`Profiled ${formatTime(summary.profiledTime)} of ${formatTime(timeRunFor)} (${percent(fraction)} CPU)\n${nodeId}`}
83
+ onClick={() => this.props.onToggleNode(nodeId)}
84
+ />
85
+ {isFlashing && <style>{`
86
+ @keyframes ${FLASH_CLASS_NAME}-anim {
87
+ 0%, 100% { opacity: 1; }
88
+ 50% { opacity: 0.3; }
89
+ }
90
+ .${FLASH_CLASS_NAME} {
91
+ animation: ${FLASH_CLASS_NAME}-anim 0.8s infinite;
92
+ }
93
+ `}</style>}
90
94
  </div>;
91
95
  })}
92
96
  </div>;
@@ -95,15 +99,21 @@ export class ServiceMeasureBars extends qreact.Component<{
95
99
 
96
100
  export class ServiceMeasureBreakdown extends qreact.Component<{
97
101
  nodeId: string;
98
- allNodeIds: string[];
99
102
  }> {
100
103
  render() {
101
104
  let controller = ServiceMeasureController(SocketFunction.browserNodeId());
102
- let summaries = controller.getMeasureSummaries(this.props.allNodeIds);
103
- let summary = summaries?.[this.props.nodeId];
104
- if (!summary) return undefined;
105
-
106
- let result = controller.getMeasureBreakdown(this.props.nodeId, { startTime: summary.startTime, endTime: summary.endTime });
105
+ let summary: MeasureSummary | undefined;
106
+ let result: { chunk?: MeasureChunk } | undefined;
107
+ try {
108
+ summary = controller.getMeasureSummary(this.props.nodeId);
109
+ if (!summary) return undefined;
110
+ result = controller.getMeasureBreakdown(this.props.nodeId, { startTime: summary.startTime, endTime: summary.endTime });
111
+ } catch (err) {
112
+ return <div className={css.pad2(10).hsl(0, 70, 92).bord2(0, 0, 20)}>
113
+ <div className={css.boldStyle.colorhsl(0, 70, 35)}>Node did not respond (it likely doesn't have the measure API yet)</div>
114
+ <pre className={css.whiteSpace("pre-wrap").fontSize(12)}>{(err as Error).stack ?? String(err)}</pre>
115
+ </div>;
116
+ }
107
117
  if (!result) {
108
118
  return <div className={css.pad2(10).hsl(0, 0, 98).bord2(0, 0, 20)}>Loading breakdown...</div>;
109
119
  }
@@ -81,7 +81,6 @@ export class ServicesListPage extends qreact.Component {
81
81
  serviceNodeIds.set(serviceId, nodeIds);
82
82
  }
83
83
  }
84
- let allNodeIds = sort(Array.from(new Set(Array.from(serviceNodeIds.values()).flat())), x => x);
85
84
 
86
85
  return <div className={css.vbox(16)}>
87
86
  <div className={css.hbox(12).wrap}>
@@ -168,10 +167,10 @@ export class ServicesListPage extends qreact.Component {
168
167
  let duplicateKey = (keyCounts.get(config.parameters.key || "") || 0) > 1;
169
168
  let measureNodeIds = serviceNodeIds.get(serviceId) || [];
170
169
  let expandedMeasureNodeId = this.state.expandedMeasureNodes[serviceId] || "";
171
- return <div className={css.vbox(4)} key={serviceId}>
170
+ return <div className={css.vbox(4).alignItems("stretch")} key={serviceId}>
172
171
  <div className={css.hbox(10)}>
173
172
  <ServiceCategoryBadge config={config} />
174
- <div className={css.vbox(4)}>
173
+ <div className={css.vbox(4).alignItems("stretch")}>
175
174
  <Anchor noStyles
176
175
  values={[currentViewParam.getOverride("service-detail"), selectedServiceIdParam.getOverride(serviceId)]}
177
176
  className={
@@ -240,7 +239,6 @@ export class ServicesListPage extends qreact.Component {
240
239
  </Anchor>
241
240
  {this.state.measurementsEnabled && measureNodeIds.length > 0 && <ServiceMeasureBars
242
241
  serviceNodeIds={measureNodeIds}
243
- allNodeIds={allNodeIds}
244
242
  expandedNodeId={expandedMeasureNodeId}
245
243
  onToggleNode={nodeId => {
246
244
  if (this.state.expandedMeasureNodes[serviceId] === nodeId) {
@@ -256,7 +254,6 @@ export class ServicesListPage extends qreact.Component {
256
254
  </div>
257
255
  {this.state.measurementsEnabled && expandedMeasureNodeId && <ServiceMeasureBreakdown
258
256
  nodeId={expandedMeasureNodeId}
259
- allNodeIds={allNodeIds}
260
257
  />}
261
258
  </div>
262
259
  ;
@@ -38,10 +38,6 @@ export type MeasureSummary = {
38
38
  startTime: number;
39
39
  endTime: number;
40
40
  profiledTime: number;
41
- entries: {
42
- name: string;
43
- ownTime: number;
44
- }[];
45
41
  };
46
42
 
47
43
  let measureChunks: MeasureChunk[] = [];
@@ -78,7 +74,6 @@ export function getLastMeasureSummary(): MeasureSummary | undefined {
78
74
  startTime: chunk.startTime,
79
75
  endTime: chunk.endTime,
80
76
  profiledTime: chunk.profiledTime,
81
- entries: chunk.entries.map(x => ({ name: x.name, ownTime: x.ownTime.sum })),
82
77
  };
83
78
  }
84
79
  export function getMeasureBreakdown(range: { startTime: number; endTime: number }): MeasureChunk | undefined {