querysub 0.458.0 → 0.459.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
|
@@ -69,7 +69,7 @@ const renderChartPNG = cacheArgsEqual((
|
|
|
69
69
|
height: number,
|
|
70
70
|
nodeIds: readonly string[],
|
|
71
71
|
bucketsArrays: readonly GrossStatsBucket[][],
|
|
72
|
-
): string => {
|
|
72
|
+
): { pngUrl: string; maxTotal: number } => {
|
|
73
73
|
let canvas = document.createElement("canvas");
|
|
74
74
|
canvas.width = width;
|
|
75
75
|
canvas.height = height;
|
|
@@ -115,7 +115,7 @@ const renderChartPNG = cacheArgsEqual((
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
return canvas.toDataURL();
|
|
118
|
+
return { pngUrl: canvas.toDataURL(), maxTotal };
|
|
119
119
|
}, 5);
|
|
120
120
|
|
|
121
121
|
export class GrossStatsPage extends qreact.Component {
|
|
@@ -136,23 +136,31 @@ export class GrossStatsPage extends qreact.Component {
|
|
|
136
136
|
<div className={css.hbox(6)}>
|
|
137
137
|
<span>Range:</span>
|
|
138
138
|
{TIME_RANGES.map(r =>
|
|
139
|
-
<
|
|
140
|
-
className={css.
|
|
139
|
+
<button
|
|
140
|
+
className={css.pad2(8, 4) + (state.rangeMs === r.ms ? " " + css.hsl(210, 70, 60).hslcolor(0, 0, 100).bold : "")}
|
|
141
141
|
onClick={() => Querysub.commit(() => { state.rangeMs = r.ms; })}
|
|
142
|
-
>{r.label}</
|
|
142
|
+
>{r.label}</button>
|
|
143
143
|
)}
|
|
144
144
|
</div>
|
|
145
145
|
<div className={css.hbox(6).wrap}>
|
|
146
146
|
<span>Field:</span>
|
|
147
147
|
{GROSS_STATS_FIELDS.map(f =>
|
|
148
|
-
<
|
|
149
|
-
className={css.
|
|
148
|
+
<button
|
|
149
|
+
className={css.pad2(8, 4) + (state.selectedField === f ? " " + css.hsl(140, 60, 50).hslcolor(0, 0, 100).bold : "")}
|
|
150
150
|
onClick={() => Querysub.commit(() => { state.selectedField = f; })}
|
|
151
|
-
>{f}</
|
|
151
|
+
>{f}</button>
|
|
152
152
|
)}
|
|
153
153
|
</div>
|
|
154
154
|
<div className={css.hbox(8).wrap}>
|
|
155
155
|
<span>Servers:</span>
|
|
156
|
+
<button
|
|
157
|
+
className={css.pad2(8, 4)}
|
|
158
|
+
onClick={() => Querysub.commit(() => { state.excludedNodes = new Set(); })}
|
|
159
|
+
>Select all</button>
|
|
160
|
+
<button
|
|
161
|
+
className={css.pad2(8, 4)}
|
|
162
|
+
onClick={() => Querysub.commit(() => { state.excludedNodes = new Set(nodeIds); })}
|
|
163
|
+
>Select none</button>
|
|
156
164
|
{nodeIds.map((nodeId, i) =>
|
|
157
165
|
<label className={css.hbox(4)}>
|
|
158
166
|
<input
|
|
@@ -198,6 +206,15 @@ export class GrossStatsPage extends qreact.Component {
|
|
|
198
206
|
perNodeTotals.push(totals);
|
|
199
207
|
}
|
|
200
208
|
|
|
209
|
+
let maxPerField = {} as Record<GrossStatsField, number>;
|
|
210
|
+
for (let f of GROSS_STATS_FIELDS) {
|
|
211
|
+
maxPerField[f] = 0;
|
|
212
|
+
for (let totals of perNodeTotals) {
|
|
213
|
+
if (totals[f] > maxPerField[f]) maxPerField[f] = totals[f];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
let highlightStyle = css.hsl(60, 90, 75);
|
|
217
|
+
|
|
201
218
|
return <table className={css.fillWidth}>
|
|
202
219
|
<thead>
|
|
203
220
|
<tr>
|
|
@@ -215,9 +232,12 @@ export class GrossStatsPage extends qreact.Component {
|
|
|
215
232
|
<td className={css.pad2(4)} style={{ borderLeft: `4px solid ${colorForNode(i)}` }}>
|
|
216
233
|
{shortNodeId(nodeId)}
|
|
217
234
|
</td>
|
|
218
|
-
{GROSS_STATS_FIELDS.map(f =>
|
|
219
|
-
|
|
220
|
-
|
|
235
|
+
{GROSS_STATS_FIELDS.map(f => {
|
|
236
|
+
let isMax = perNodeTotals[i][f] > 0 && perNodeTotals[i][f] === maxPerField[f];
|
|
237
|
+
return <td className={css.textAlign("right").pad2(4) + (isMax ? " " + highlightStyle : "")}>
|
|
238
|
+
{formatNumber(perNodeTotals[i][f])}
|
|
239
|
+
</td>;
|
|
240
|
+
})}
|
|
221
241
|
</tr>
|
|
222
242
|
)}
|
|
223
243
|
</tbody>
|
|
@@ -234,7 +254,7 @@ export class GrossStatsPage extends qreact.Component {
|
|
|
234
254
|
let bucketsArrays: GrossStatsBucket[][] = selectedNodeIds.map(n => bucketsByNode.get(n) ?? []);
|
|
235
255
|
let anyLoading = !result;
|
|
236
256
|
|
|
237
|
-
let
|
|
257
|
+
let chart = renderChartPNG(
|
|
238
258
|
state.selectedField,
|
|
239
259
|
state.rangeMs,
|
|
240
260
|
CHART_WIDTH,
|
|
@@ -242,18 +262,36 @@ export class GrossStatsPage extends qreact.Component {
|
|
|
242
262
|
selectedNodeIds,
|
|
243
263
|
bucketsArrays,
|
|
244
264
|
);
|
|
265
|
+
let now = Date.now();
|
|
266
|
+
let windowStart = now - state.rangeMs;
|
|
245
267
|
|
|
246
268
|
return <div className={css.vbox(8).pad2(8).fillWidth}>
|
|
247
269
|
<h2>Cluster Stats</h2>
|
|
248
270
|
{this.renderControls(allNodeIds)}
|
|
249
|
-
<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
271
|
+
<div className={css.vbox(4)}>
|
|
272
|
+
<div className={css.hbox(8).bold}>
|
|
273
|
+
<span>peak: {formatNumber(chart.maxTotal)} {state.selectedField} / minute</span>
|
|
274
|
+
</div>
|
|
275
|
+
<img
|
|
276
|
+
src={chart.pngUrl}
|
|
277
|
+
width={CHART_WIDTH}
|
|
278
|
+
height={CHART_HEIGHT}
|
|
279
|
+
className={css.hsl(0, 0, 100)}
|
|
280
|
+
style={anyLoading ? { opacity: 0.5 } : undefined}
|
|
281
|
+
/>
|
|
282
|
+
<div className={css.hbox(0).fillWidth}>
|
|
283
|
+
<span className={css.flexShrink0}>{formatLocalTime(windowStart)}</span>
|
|
284
|
+
<span className={css.fillBoth}></span>
|
|
285
|
+
<span className={css.flexShrink0}>{formatLocalTime(now)}</span>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
256
288
|
{this.renderTable(selectedNodeIds, bucketsArrays)}
|
|
257
289
|
</div>;
|
|
258
290
|
}
|
|
259
291
|
}
|
|
292
|
+
|
|
293
|
+
function formatLocalTime(ms: number): string {
|
|
294
|
+
let d = new Date(ms);
|
|
295
|
+
let pad = (n: number) => String(n).padStart(2, "0");
|
|
296
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
297
|
+
}
|
|
@@ -393,7 +393,16 @@ export class BufferIndex {
|
|
|
393
393
|
const bufferStep = iterateForward ? 1 : -1;
|
|
394
394
|
|
|
395
395
|
for (let bufferIndex = bufferStartIdx; iterateForward ? bufferIndex < bufferEndIdx : bufferIndex > bufferEndIdx; bufferIndex += bufferStep) {
|
|
396
|
-
|
|
396
|
+
// No `matchCount >= params.limit` cap inside the block.
|
|
397
|
+
// Buffer order within a block is not guaranteed to follow
|
|
398
|
+
// the search direction (blocks are time-ordered, buffers
|
|
399
|
+
// inside them are not), so stopping mid-block on a match
|
|
400
|
+
// count would drop earlier-time buffers we haven't reached
|
|
401
|
+
// yet. The block-level cap above is the only safe stop;
|
|
402
|
+
// here we only honor cross-file `keepIterating` (which
|
|
403
|
+
// applies to the whole file at once, so it's safe at any
|
|
404
|
+
// granularity).
|
|
405
|
+
if (!config.keepIterating()) break;
|
|
397
406
|
await config.results.limitGroup?.wait();
|
|
398
407
|
|
|
399
408
|
let buffer = buffers[bufferIndex];
|
|
@@ -580,7 +580,16 @@ export class BufferUnitIndex {
|
|
|
580
580
|
const step = iterateForward ? 1 : -1;
|
|
581
581
|
|
|
582
582
|
for (let i = startIdx; iterateForward ? i < endIdx : i > endIdx; i += step) {
|
|
583
|
-
|
|
583
|
+
// No matchCount-based cap inside the block. Buffer
|
|
584
|
+
// order within a block is not guaranteed to follow the
|
|
585
|
+
// search direction (blocks are time-ordered, buffers
|
|
586
|
+
// inside them are not), so a mid-block stop on
|
|
587
|
+
// `relevantCount >= limit` would drop earlier-time
|
|
588
|
+
// buffers we haven't reached yet. Block-level
|
|
589
|
+
// `stopIterating` is the safe granularity; here we
|
|
590
|
+
// only honor cross-file `keepIterating`, which applies
|
|
591
|
+
// to the whole file at once.
|
|
592
|
+
if (!keepIterating()) break;
|
|
584
593
|
await results.limitGroup?.wait();
|
|
585
594
|
|
|
586
595
|
const buffer = await this.getBufferFromBlock(blockReader, i);
|