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.
|
|
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.
|
|
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 {
|
|
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 =
|
|
14
|
-
const
|
|
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
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
53
|
+
return <div className={css.vbox(1).alignItems("stretch")}>
|
|
65
54
|
{this.props.serviceNodeIds.map(nodeId => {
|
|
66
|
-
let
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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(
|
|
85
|
-
.
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
103
|
-
let
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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 {
|