This is an automated email from the ASF dual-hosted git repository.
vogievetsky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new cf84e5f1b09 fix: Web console: compaction of PB scale datasource
returning a bigint that crashes the Datasources view and get Claude to fix all
other issues like it. (#20384)
cf84e5f1b09 is described below
commit cf84e5f1b09ce3b05c2f5c015e5259bf66b7a7e6
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Mon Sep 21 23:31:58 2026 -0700
fix: Web console: compaction of PB scale datasource returning a bigint that
crashes the Datasources view and get Claude to fix all other issues like it.
(#20384)
* fix compaction status
* fix the rest of the issues
* address review feedback
Only coerce measure columns in bigIntsToNumbers so BIGINT dimension
values above Number.MAX_SAFE_INTEGER keep their exact value, instead of
rounding and merging distinct chart categories / breaking click-to-filter.
Render the Availability cell for BigInt segment counts rather than
returning '-'. Widen the count fields on DatasourceQueryResultRow to
NumberLike and coerce once, so a BigInt count keeps its percentage,
segment link, and zero-replica status.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../src/components/segment-timeline/interval.ts | 17 ++-
.../segment-timeline/segment-bar-chart-render.tsx | 11 +-
.../compaction-status/compaction-status.ts | 19 ++--
web-console/src/druid-models/stages/stages.ts | 126 ++++++++++++---------
.../supervisor-status/supervisor-status.ts | 59 +++++-----
web-console/src/utils/general.tsx | 25 ++++
.../__snapshots__/datasources-view.spec.tsx.snap | 4 +-
.../views/datasources-view/datasources-view.tsx | 79 +++++++------
.../modules/bar-chart-module/bar-chart-module.tsx | 5 +-
.../multi-axis-chart-module.tsx | 6 +-
.../modules/pie-chart-module/pie-chart-module.tsx | 10 +-
.../time-chart-module/time-chart-module.tsx | 2 +-
.../src/views/segments-view/segments-view.tsx | 2 +-
.../src/views/services-view/services-view.tsx | 14 +--
.../execution-stages-pane.tsx | 14 ++-
15 files changed, 242 insertions(+), 151 deletions(-)
diff --git a/web-console/src/components/segment-timeline/interval.ts
b/web-console/src/components/segment-timeline/interval.ts
index 833f01251cf..67f4684383c 100644
--- a/web-console/src/components/segment-timeline/interval.ts
+++ b/web-console/src/components/segment-timeline/interval.ts
@@ -19,6 +19,7 @@
import type { Duration } from 'chronoshift';
import { sum } from 'd3-array';
+import type { NumberLike } from '../../utils';
import { formatBytes, formatInteger } from '../../utils';
export type IntervalStat = 'segments' | 'size' | 'rows';
@@ -42,16 +43,16 @@ export function getIntervalStatTitle(intervalStat:
IntervalStat): string {
}
export function aggregateSegmentStats(
- xs: readonly Record<IntervalStat, number>[],
+ xs: readonly Record<IntervalStat, NumberLike>[],
): Record<IntervalStat, number> {
return {
- segments: sum(xs, s => s.segments),
- size: sum(xs, s => s.size),
- rows: sum(xs, s => s.rows),
+ segments: sum(xs, s => Number(s.segments)),
+ size: sum(xs, s => Number(s.size)),
+ rows: sum(xs, s => Number(s.rows)),
};
}
-export function formatIntervalStat(stat: IntervalStat, n: number) {
+export function formatIntervalStat(stat: IntervalStat, n: NumberLike) {
switch (stat) {
case 'segments':
case 'rows':
@@ -65,7 +66,7 @@ export function formatIntervalStat(stat: IntervalStat, n:
number) {
}
}
-export interface IntervalRow extends Record<IntervalStat, number> {
+export interface IntervalRow extends Record<IntervalStat, NumberLike> {
start: Date;
end: Date;
datasource: string;
@@ -73,7 +74,11 @@ export interface IntervalRow extends Record<IntervalStat,
number> {
originalTimeSpan: Duration;
}
+// Unlike IntervalRow, the stats here have been through aggregateSegmentStats
so they are plain numbers
export interface TrimmedIntervalRow extends IntervalRow {
+ segments: number;
+ size: number;
+ rows: number;
shownDays: number;
normalized: Record<IntervalStat, number>;
}
diff --git
a/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
b/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
index 945b8703fc3..0cd9cdfa820 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
@@ -271,12 +271,17 @@ export const SegmentBarChartRender = function
SegmentBarChartRender(
const averageRowSizeByDatasource = groupByAsMap(
shownIntervalRows.filter(intervalRow => intervalRow.size > 0 &&
intervalRow.rows > 0),
intervalRow => intervalRow.datasource,
- intervalRows => sum(intervalRows, d => d.size) / sum(intervalRows, d =>
d.rows),
+ intervalRows =>
+ sum(intervalRows, d => Number(d.size)) / sum(intervalRows, d =>
Number(d.rows)),
);
const trimDuration = new Duration(trimGranularity);
const trimmedIntervalRows = shownIntervalRows.map(intervalRow => {
- const { start, end, segments, size, rows } = intervalRow;
+ const { start, end } = intervalRow;
+ // These come off the JSON parser, so they can be BigInt on large
clusters
+ const segments = Number(intervalRow.segments);
+ const size = Number(intervalRow.size);
+ const rows = Number(intervalRow.rows);
const startTrimmed = trimDuration.floor(start, Timezone.UTC);
let endTrimmed = trimDuration.ceil(end, Timezone.UTC);
@@ -293,6 +298,8 @@ export const SegmentBarChartRender = function
SegmentBarChartRender(
start: startTrimmed,
end: endTrimmed,
shownDays,
+ segments,
+ rows,
size: shownSize,
normalized: {
size: shownSize / shownDays,
diff --git
a/web-console/src/druid-models/compaction-status/compaction-status.ts
b/web-console/src/druid-models/compaction-status/compaction-status.ts
index 5712eb958b0..785ff753c50 100644
--- a/web-console/src/druid-models/compaction-status/compaction-status.ts
+++ b/web-console/src/druid-models/compaction-status/compaction-status.ts
@@ -16,6 +16,7 @@
* limitations under the License.
*/
+import type { NumberLike } from '../../utils';
import { formatBytesCompact, pluralIfNeeded } from '../../utils';
import type { CompactionConfig } from '../compaction-config/compaction-config';
import { compactionConfigHasLegacyInputSegmentSizeBytesSet } from
'../compaction-config/compaction-config';
@@ -27,15 +28,15 @@ function capitalizeFirst(str: string): string {
export interface CompactionStatus {
dataSource: string;
scheduleStatus: string;
- bytesAwaitingCompaction: number;
- bytesCompacted: number;
- bytesSkipped: number;
- segmentCountAwaitingCompaction: number;
- segmentCountCompacted: number;
- segmentCountSkipped: number;
- intervalCountAwaitingCompaction: number;
- intervalCountCompacted: number;
- intervalCountSkipped: number;
+ bytesAwaitingCompaction: NumberLike;
+ bytesCompacted: NumberLike;
+ bytesSkipped: NumberLike;
+ segmentCountAwaitingCompaction: NumberLike;
+ segmentCountCompacted: NumberLike;
+ segmentCountSkipped: NumberLike;
+ intervalCountAwaitingCompaction: NumberLike;
+ intervalCountCompacted: NumberLike;
+ intervalCountSkipped: NumberLike;
}
export function zeroCompactionStatus(compactionStatus: CompactionStatus):
boolean {
diff --git a/web-console/src/druid-models/stages/stages.ts
b/web-console/src/druid-models/stages/stages.ts
index c1e2e18096d..9c3a6a3e700 100644
--- a/web-console/src/druid-models/stages/stages.ts
+++ b/web-console/src/druid-models/stages/stages.ts
@@ -19,7 +19,16 @@
import { max, sum } from 'd3-array';
import { AutoForm } from '../../components';
-import { countBy, deleteKeys, filterMap, groupByAsMap, oneOf, zeroDivide }
from '../../utils';
+import type { NumberLike } from '../../utils';
+import {
+ countBy,
+ deleteKeys,
+ filterMap,
+ groupByAsMap,
+ isNumberLike,
+ oneOf,
+ zeroDivide,
+} from '../../utils';
import type { InputFormat } from '../input-format/input-format';
import type { InputSource } from '../input-source/input-source';
@@ -31,8 +40,8 @@ const READING_INPUT_WITH_SHUFFLE_WEIGHT = 1 - SHUFFLE_WEIGHT;
export type InOut = 'in' | 'out';
-function simpleSum(xs: number[]) {
- return sum(xs);
+function simpleSum(xs: NumberLike[]): number {
+ return sum(xs, x => Number(x));
}
function aggregateThings<T>(
@@ -155,7 +164,7 @@ export type ChannelCounterName = `input${number}` |
'output' | 'shuffle';
export type CounterName = keyof StageWorkerCounter;
function tallyWarningCount(warningCounter: WarningCounter): number {
- return sum(Object.values(warningCounter), v => (typeof v === 'number' ? v :
0));
+ return sum(Object.values(warningCounter), v => (isNumberLike(v) ? Number(v)
: 0));
}
function sumByKey(objs: Record<string, number>[]): Record<string, number> {
@@ -172,17 +181,17 @@ function sumByKey(objs: Record<string, number>[]):
Record<string, number> {
export interface ChannelCounter {
type: 'channel';
- rows?: number[];
- bytes?: number[];
- frames?: number[];
- files?: number[];
- totalFiles?: number[];
- loadBytes?: number[];
- loadTime?: number[];
- loadWait?: number[];
- loadFiles?: number[];
- queries?: number[];
- totalQueries?: number[];
+ rows?: NumberLike[];
+ bytes?: NumberLike[];
+ frames?: NumberLike[];
+ files?: NumberLike[];
+ totalFiles?: NumberLike[];
+ loadBytes?: NumberLike[];
+ loadTime?: NumberLike[];
+ loadWait?: NumberLike[];
+ loadFiles?: NumberLike[];
+ queries?: NumberLike[];
+ totalQueries?: NumberLike[];
}
export type ChannelFields =
@@ -236,10 +245,10 @@ export function aggregateSortProgressCounters(
export interface SegmentGenerationProgressCounter {
type: 'segmentGenerationProgress';
- rowsProcessed: number;
- rowsPersisted: number;
- rowsMerged: number;
- rowsPushed: number;
+ rowsProcessed: NumberLike;
+ rowsPersisted: NumberLike;
+ rowsMerged: NumberLike;
+ rowsPushed: NumberLike;
}
export type SegmentGenerationProgressFields =
@@ -298,11 +307,22 @@ export interface CpusCounter {
export interface CpuCounter {
type: 'cpu';
- cpu: number;
- wall: number;
+ cpu: NumberLike;
+ wall: NumberLike;
}
export interface StorageCounter {
+ type: 'storage';
+ localBytesMax?: NumberLike;
+ localBytesReserved: NumberLike;
+ localFilesWritten: NumberLike;
+ localBytesWritten: NumberLike;
+ durableFileCount: NumberLike;
+ durableBytesWritten: NumberLike;
+}
+
+// normalizeStorageCounter() has coerced these, so they are safe to do
arithmetic on
+export interface NormalizedStorageCounter {
type: 'storage';
localBytesMax?: number;
localBytesReserved: number;
@@ -312,7 +332,9 @@ export interface StorageCounter {
durableBytesWritten: number;
}
-function normalizeStorageCounter(s: StorageCounter | undefined):
StorageCounter | undefined {
+function normalizeStorageCounter(
+ s: StorageCounter | undefined,
+): NormalizedStorageCounter | undefined {
if (!s) return;
return {
type: 'storage',
@@ -340,7 +362,7 @@ export interface SimpleWideCounter {
shuffle?: Record<ChannelFields, number>;
segmentGenerationProgress?: SegmentGenerationProgressCounter;
cpu?: CpusCounter;
- storage?: StorageCounter;
+ storage?: NormalizedStorageCounter;
}
function zeroChannelFields(): Record<ChannelFields, number> {
@@ -585,7 +607,7 @@ export class Stages {
return sum(this.getCountersForStage(stage), c => {
const counter = c[counterName];
if (counter?.type !== 'channel') return 0;
- return sum(counter[field] || []);
+ return simpleSum(counter[field] || []);
});
}
@@ -608,7 +630,7 @@ export class Stages {
if (!counters) return 0;
return zeroDivide(
sum(this.getCountersForStage(stage), c => {
- const rowsToSort = c.output ? sum(c.output.rows || []) : 0;
+ const rowsToSort = c.output ? simpleSum(c.output.rows || []) : 0;
const progressDigest = c.sortProgress?.progressDigest || 0;
return Math.floor(rowsToSort * progressDigest);
}),
@@ -622,7 +644,9 @@ export class Stages {
): number {
const { counters } = this;
if (!counters) return 0;
- return sum(this.getCountersForStage(stage), c =>
c.segmentGenerationProgress?.[field] || 0);
+ return sum(this.getCountersForStage(stage), c =>
+ Number(c.segmentGenerationProgress?.[field] || 0),
+ );
}
getChannelCounterNamesForStage(stage: StageDefinition): ChannelCounterName[]
{
@@ -649,17 +673,17 @@ export class Stages {
const c = stageCounters[channel];
newWideCounter[channel] = c
? {
- rows: sum(c.rows || []),
- bytes: sum(c.bytes || []),
- frames: sum(c.frames || []),
- files: sum(c.files || []),
- totalFiles: sum(c.totalFiles || []),
- loadBytes: sum(c.loadBytes || []),
- loadTime: sum(c.loadTime || []),
- loadWait: sum(c.loadWait || []),
- loadFiles: sum(c.loadFiles || []),
- queries: sum(c.queries || []),
- totalQueries: sum(c.totalQueries || []),
+ rows: simpleSum(c.rows || []),
+ bytes: simpleSum(c.bytes || []),
+ frames: simpleSum(c.frames || []),
+ files: simpleSum(c.files || []),
+ totalFiles: simpleSum(c.totalFiles || []),
+ loadBytes: simpleSum(c.loadBytes || []),
+ loadTime: simpleSum(c.loadTime || []),
+ loadWait: simpleSum(c.loadWait || []),
+ loadFiles: simpleSum(c.loadFiles || []),
+ queries: simpleSum(c.queries || []),
+ totalQueries: simpleSum(c.totalQueries || []),
}
: zeroChannelFields();
}
@@ -684,7 +708,7 @@ export class Stages {
// Check if the worker has any wall time recorded
const { cpu } = stageCounters;
if (cpu) {
- const totalWall = sum(CPUS_COUNTER_FIELDS, field => cpu[field]?.wall
|| 0);
+ const totalWall = sum(CPUS_COUNTER_FIELDS, field =>
Number(cpu[field]?.wall || 0));
if (totalWall > 0) return 0;
}
@@ -694,10 +718,10 @@ export class Stages {
const c = stageCounters[channel];
if (!c) return true;
return (
- sum(c.rows || []) === 0 &&
- sum(c.files || []) === 0 &&
- sum(c.bytes || []) === 0 &&
- sum(c.frames || []) === 0
+ simpleSum(c.rows || []) === 0 &&
+ simpleSum(c.files || []) === 0 &&
+ simpleSum(c.bytes || []) === 0 &&
+ simpleSum(c.frames || []) === 0
);
}),
);
@@ -760,15 +784,15 @@ export class Stages {
for (let i = 0; i < n; i++) {
const c = simpleCounters[i][counterName]!; // This must be defined
as we initialized all the counters above
- c.rows += channelCounter.rows?.[i] || 0;
- c.bytes += channelCounter.bytes?.[i] || 0;
- c.frames += channelCounter.frames?.[i] || 0;
- c.files += channelCounter.files?.[i] || 0;
- c.totalFiles += channelCounter.totalFiles?.[i] || 0;
- c.loadBytes += channelCounter.loadBytes?.[i] || 0;
- c.loadTime += channelCounter.loadTime?.[i] || 0;
- c.loadWait += channelCounter.loadWait?.[i] || 0;
- c.loadFiles += channelCounter.loadFiles?.[i] || 0;
+ c.rows += Number(channelCounter.rows?.[i] || 0);
+ c.bytes += Number(channelCounter.bytes?.[i] || 0);
+ c.frames += Number(channelCounter.frames?.[i] || 0);
+ c.files += Number(channelCounter.files?.[i] || 0);
+ c.totalFiles += Number(channelCounter.totalFiles?.[i] || 0);
+ c.loadBytes += Number(channelCounter.loadBytes?.[i] || 0);
+ c.loadTime += Number(channelCounter.loadTime?.[i] || 0);
+ c.loadWait += Number(channelCounter.loadWait?.[i] || 0);
+ c.loadFiles += Number(channelCounter.loadFiles?.[i] || 0);
}
}
}
diff --git
a/web-console/src/druid-models/supervisor-status/supervisor-status.ts
b/web-console/src/druid-models/supervisor-status/supervisor-status.ts
index feb42a0a7f7..f5f1269629d 100644
--- a/web-console/src/druid-models/supervisor-status/supervisor-status.ts
+++ b/web-console/src/druid-models/supervisor-status/supervisor-status.ts
@@ -36,7 +36,7 @@ export interface SupervisorStatus {
publishingTasks: SupervisorStatusTask[];
latestOffsets?: SupervisorOffsetMap;
minimumLag?: SupervisorOffsetMap;
- aggregateLag: number;
+ aggregateLag: NumberLike;
offsetsLastUpdated: string;
suspended: boolean;
healthy: boolean;
@@ -46,15 +46,15 @@ export interface SupervisorStatus {
// autocompact
scheduleStatus: string;
- bytesAwaitingCompaction: number;
- bytesCompacted: number;
- bytesSkipped: number;
- segmentCountAwaitingCompaction: number;
- segmentCountCompacted: number;
- segmentCountSkipped: number;
- intervalCountAwaitingCompaction: number;
- intervalCountCompacted: number;
- intervalCountSkipped: number;
+ bytesAwaitingCompaction: NumberLike;
+ bytesCompacted: NumberLike;
+ bytesSkipped: NumberLike;
+ segmentCountAwaitingCompaction: NumberLike;
+ segmentCountCompacted: NumberLike;
+ segmentCountSkipped: NumberLike;
+ intervalCountAwaitingCompaction: NumberLike;
+ intervalCountCompacted: NumberLike;
+ intervalCountSkipped: NumberLike;
};
}
@@ -93,30 +93,33 @@ export interface RowStats {
}
export interface RowStatsCounter {
- processed: number;
- processedBytes: number;
- processedWithError: number;
- thrownAway: number;
- unparseable: number;
+ processed: NumberLike;
+ processedBytes: NumberLike;
+ processedWithError: NumberLike;
+ thrownAway: NumberLike;
+ unparseable: NumberLike;
}
-function sumRowStatsCounter(rowStats: RowStatsCounter[]): RowStatsCounter {
+// Aggregating coerces away any BigInt, so the result is safe to do arithmetic
on
+export type AggregatedRowStatsCounter = Record<keyof RowStatsCounter, number>;
+
+function sumRowStatsCounter(rowStats: RowStatsCounter[]):
AggregatedRowStatsCounter {
return {
- processed: sum(rowStats, d => d.processed),
- processedBytes: sum(rowStats, d => d.processedBytes),
- processedWithError: sum(rowStats, d => d.processedWithError),
- thrownAway: sum(rowStats, d => d.thrownAway),
- unparseable: sum(rowStats, d => d.unparseable),
+ processed: sum(rowStats, d => Number(d.processed)),
+ processedBytes: sum(rowStats, d => Number(d.processedBytes)),
+ processedWithError: sum(rowStats, d => Number(d.processedWithError)),
+ thrownAway: sum(rowStats, d => Number(d.thrownAway)),
+ unparseable: sum(rowStats, d => Number(d.unparseable)),
};
}
-function maxRowStatsCounter(rowStats: RowStatsCounter[]): RowStatsCounter {
+function maxRowStatsCounter(rowStats: RowStatsCounter[]):
AggregatedRowStatsCounter {
return {
- processed: max(rowStats, d => d.processed) ?? 0,
- processedBytes: max(rowStats, d => d.processedBytes) ?? 0,
- processedWithError: max(rowStats, d => d.processedWithError) ?? 0,
- thrownAway: max(rowStats, d => d.thrownAway) ?? 0,
- unparseable: max(rowStats, d => d.unparseable) ?? 0,
+ processed: max(rowStats, d => Number(d.processed)) ?? 0,
+ processedBytes: max(rowStats, d => Number(d.processedBytes)) ?? 0,
+ processedWithError: max(rowStats, d => Number(d.processedWithError)) ?? 0,
+ thrownAway: max(rowStats, d => Number(d.thrownAway)) ?? 0,
+ unparseable: max(rowStats, d => Number(d.unparseable)) ?? 0,
};
}
@@ -132,7 +135,7 @@ export function getTotalSupervisorStats(
stats: SupervisorStats,
key: RowStatsKey,
activeTaskIds: string[] | undefined,
-): RowStatsCounter {
+): AggregatedRowStatsCounter {
return sumRowStatsCounter(
Object.values(stats).map(s =>
maxRowStatsCounter(
diff --git a/web-console/src/utils/general.tsx
b/web-console/src/utils/general.tsx
index d719df030ce..8565d2afe9b 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -41,6 +41,31 @@ export function isNumberLikeNaN(x: NumberLike): boolean {
return isNaN(Number(x));
}
+/**
+ * Query results and API responses are parsed with JSONBig, so any integer
with 16+ digits arrives
+ * as a BigInt. Charting libraries can not do arithmetic on those, so flatten
them back to numbers.
+ *
+ * Only the named (measure) columns are converted. Dimension values are left
alone because a BigInt
+ * above Number.MAX_SAFE_INTEGER would round, merging distinct categories into
one bucket and making
+ * click-to-filter target a value that is not in the data.
+ */
+export function bigIntsToNumbers<T extends Record<string, any>>(
+ rows: T[],
+ measureKeys: string[],
+): T[] {
+ return rows.map(row => {
+ let changed = false;
+ const res: Record<string, any> = { ...row };
+ for (const k of measureKeys) {
+ if (typeof res[k] === 'bigint') {
+ res[k] = Number(res[k]);
+ changed = true;
+ }
+ }
+ return changed ? (res as T) : row;
+ });
+}
+
export function nonEmptyString(s: unknown): s is string {
return typeof s === 'string' && s !== '';
}
diff --git
a/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
b/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
index 5f471fa1e39..5c79521c819 100644
---
a/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
+++
b/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
@@ -188,11 +188,11 @@ exports[`DatasourcesView matches snapshot 1`] = `
{
"Cell": [Function],
"Header": "Availability",
- "accessor": "num_segments",
+ "accessor": [Function],
"className": "padded",
"filterable": false,
+ "id": "num_segments",
"show": true,
- "sortMethod": [Function],
"width": 220,
},
{
diff --git a/web-console/src/views/datasources-view/datasources-view.tsx
b/web-console/src/views/datasources-view/datasources-view.tsx
index 49e0f0e2996..e5196c85bb1 100644
--- a/web-console/src/views/datasources-view/datasources-view.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.tsx
@@ -81,6 +81,7 @@ import {
getDruidErrorMessage,
groupByAsMap,
hasOverlayOpen,
+ isNumberLike,
isNumberLikeNaN,
LocalStorageBackedVisibility,
LocalStorageKeys,
@@ -161,26 +162,27 @@ const formatAvgRowSize = formatInteger;
const formatReplicatedSize = formatBytes;
const formatLeftToBeCompacted = formatBytes;
-function progress(done: number, awaiting: number): number {
- const d = done + awaiting;
- if (!d) return 0;
- return done / d;
+function progress(done: NumberLike, awaiting: NumberLike): number {
+ const doneNumber = Number(done);
+ const total = doneNumber + Number(awaiting);
+ if (!total) return 0;
+ return doneNumber / total;
}
const PERCENT_BRACES = [formatPercent(1)];
interface DatasourceQueryResultRow {
readonly datasource: string;
- readonly num_segments: number;
- readonly num_zero_replica_segments: number;
- readonly num_segments_to_load: number;
- readonly num_segments_to_drop: number;
- readonly minute_aligned_segments: number;
- readonly hour_aligned_segments: number;
- readonly day_aligned_segments: number;
- readonly month_aligned_segments: number;
- readonly year_aligned_segments: number;
- readonly all_granularity_segments: number;
+ readonly num_segments: NumberLike;
+ readonly num_zero_replica_segments: NumberLike;
+ readonly num_segments_to_load: NumberLike;
+ readonly num_segments_to_drop: NumberLike;
+ readonly minute_aligned_segments: NumberLike;
+ readonly hour_aligned_segments: NumberLike;
+ readonly day_aligned_segments: NumberLike;
+ readonly month_aligned_segments: NumberLike;
+ readonly year_aligned_segments: NumberLike;
+ readonly all_granularity_segments: NumberLike;
readonly total_data_size: NumberLike;
readonly replicated_size: NumberLike;
readonly min_segment_rows: NumberLike;
@@ -1247,11 +1249,22 @@ GROUP BY 1, 2`;
show: visibleColumns.shown('Availability'),
filterable: false,
width: 220,
- accessor: 'num_segments',
+ id: 'num_segments',
+ accessor: ({ num_segments, num_segments_to_load }) => {
+ const total = Number(num_segments);
+ if (!total) return 0;
+ return (total - Number(num_segments_to_load)) / total;
+ },
className: 'padded',
- Cell: ({ value: num_segments, original }) => {
- const { datasource, unused, num_segments_to_load,
num_zero_replica_segments, rules } =
- original as Datasource;
+ Cell: ({ original }) => {
+ const {
+ datasource,
+ unused,
+ num_segments,
+ num_segments_to_load,
+ num_zero_replica_segments,
+ rules,
+ } = original as Datasource;
if (unused) {
return (
<span>
@@ -1273,27 +1286,31 @@ GROUP BY 1, 2`;
{pluralIfNeeded(num_segments, 'segment')}
</a>
);
+ if (!isNumberLike(num_segments) ||
!isNumberLike(num_segments_to_load)) {
+ return '-';
+ }
+
+ const numSegments = Number(num_segments);
+ const numSegmentsToLoad = Number(num_segments_to_load);
const percentZeroReplica = (
- Math.floor((num_zero_replica_segments / num_segments) * 1000)
/ 10
+ Math.floor((Number(num_zero_replica_segments) / numSegments) *
1000) / 10
).toFixed(1);
- if (typeof num_segments_to_load !== 'number' || typeof
num_segments !== 'number') {
- return '-';
- } else if (num_segments === 0) {
+ if (numSegments === 0) {
return (
<span>
<span style={{ color: DatasourcesView.EMPTY_COLOR
}}>● </span>
Empty
</span>
);
- } else if (num_segments_to_load === 0) {
+ } else if (numSegmentsToLoad === 0) {
return (
<span>
<span style={{ color:
DatasourcesView.FULLY_AVAILABLE_COLOR }}>
●
</span>
{assemble(
- num_segments !== num_zero_replica_segments
+ numSegments !== Number(num_zero_replica_segments)
? `Fully ${descriptor}`
: undefined,
hasZeroReplicationRule ? `${percentZeroReplica}% deep
storage only` : '',
@@ -1302,9 +1319,9 @@ GROUP BY 1, 2`;
</span>
);
} else {
- const numAvailableSegments = num_segments -
num_segments_to_load;
+ const numAvailableSegments = numSegments - numSegmentsToLoad;
const percentAvailable = (
- Math.floor((numAvailableSegments / num_segments) * 1000) / 10
+ Math.floor((numAvailableSegments / numSegments) * 1000) / 10
).toFixed(1);
return (
<span>
@@ -1319,11 +1336,6 @@ GROUP BY 1, 2`;
);
}
},
- sortMethod: (d1, d2) => {
- const percentAvailable1 = d1.num_available / d1.num_total;
- const percentAvailable2 = d2.num_available / d2.num_total;
- return percentAvailable1 - percentAvailable2 || d1.num_total -
d2.num_total;
- },
},
{
Header: twoLines('Historical', 'load/drop queues'),
@@ -1569,9 +1581,8 @@ GROUP BY 1, 2`;
width: 200,
accessor: ({ compaction }) => {
const status = compaction?.status;
- return status?.bytesCompacted
- ? status.bytesCompacted / (status.bytesAwaitingCompaction +
status.bytesCompacted)
- : 0;
+ if (!status) return 0;
+ return progress(status.bytesCompacted,
status.bytesAwaitingCompaction);
},
filterable: false,
className: 'padded',
diff --git
a/web-console/src/views/explore-view/modules/bar-chart-module/bar-chart-module.tsx
b/web-console/src/views/explore-view/modules/bar-chart-module/bar-chart-module.tsx
index dac24da809e..21d1134d374 100644
---
a/web-console/src/views/explore-view/modules/bar-chart-module/bar-chart-module.tsx
+++
b/web-console/src/views/explore-view/modules/bar-chart-module/bar-chart-module.tsx
@@ -25,7 +25,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { Loader, PortalBubble, type PortalBubbleOpenOn } from
'../../../../components';
import { useQueryManager } from '../../../../hooks';
-import { formatEmpty } from '../../../../utils';
+import { bigIntsToNumbers, formatEmpty } from '../../../../utils';
import { Issue } from '../../components';
import type { ExpressionMeta } from '../../models';
import { ModuleRepository } from '../../module-repository/module-repository';
@@ -151,7 +151,8 @@ ModuleRepository.registerModule<BarChartParameterValues>({
const [sourceDataState, queryManager] = useQueryManager({
query: dataQuery,
processQuery: async (query, signal) => {
- return (await runSqlQuery(query, signal)).toObjectArray();
+ // Only 'met' is coerced, 'dim' is a dimension value that must keep
its exact value
+ return bigIntsToNumbers((await runSqlQuery(query,
signal)).toObjectArray(), ['met']);
},
});
diff --git
a/web-console/src/views/explore-view/modules/multi-axis-chart-module/multi-axis-chart-module.tsx
b/web-console/src/views/explore-view/modules/multi-axis-chart-module/multi-axis-chart-module.tsx
index f945e028345..073065b58fa 100644
---
a/web-console/src/views/explore-view/modules/multi-axis-chart-module/multi-axis-chart-module.tsx
+++
b/web-console/src/views/explore-view/modules/multi-axis-chart-module/multi-axis-chart-module.tsx
@@ -27,6 +27,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { Loader, PortalBubble, type PortalBubbleOpenOn } from
'../../../../components';
import { useQueryManager } from '../../../../hooks';
import {
+ bigIntsToNumbers,
formatInteger,
formatIsoDateRange,
formatNumber,
@@ -114,7 +115,10 @@
ModuleRepository.registerModule<MultiAxisChartParameterValues>({
throw new Error(`Must have a column of type TIMESTAMP for the
multi-axis chart to work`);
}
- return (await runSqlQuery(query, signal)).toObjectArray();
+ return bigIntsToNumbers(
+ (await runSqlQuery(query, signal)).toObjectArray(),
+ measures.map(measure => measure.name),
+ );
},
});
diff --git
a/web-console/src/views/explore-view/modules/pie-chart-module/pie-chart-module.tsx
b/web-console/src/views/explore-view/modules/pie-chart-module/pie-chart-module.tsx
index 81873f0d87a..987ceb96a89 100644
---
a/web-console/src/views/explore-view/modules/pie-chart-module/pie-chart-module.tsx
+++
b/web-console/src/views/explore-view/modules/pie-chart-module/pie-chart-module.tsx
@@ -26,7 +26,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { Loader, PortalBubble, type PortalBubbleOpenOn } from
'../../../../components';
import { useQueryManager } from '../../../../hooks';
import { ColorAssigner } from '../../../../singletons';
-import { formatEmpty, formatNumber } from '../../../../utils';
+import { bigIntsToNumbers, formatEmpty, formatNumber } from
'../../../../utils';
import { Issue } from '../../components';
import type { ExpressionMeta } from '../../models';
import { ModuleRepository } from '../../module-repository/module-repository';
@@ -131,7 +131,7 @@ ModuleRepository.registerModule<PieChartParameterValues>({
query: dataQueries,
processQuery: async ({ mainQuery, limit, splitExpression,
othersPartialQuery }, signal) => {
const result = await runSqlQuery({ query: mainQuery }, signal);
- const data = result.toObjectArray();
+ const data = bigIntsToNumbers(result.toObjectArray(), ['value']);
if (splitExpression && othersPartialQuery) {
const pieValues = result.getColumnByIndex(0)!;
@@ -140,7 +140,11 @@ ModuleRepository.registerModule<PieChartParameterValues>({
const othersResult = await runSqlQuery({
query:
othersPartialQuery.addWhere(splitExpression.notIn(pieValues.slice(0, limit))),
});
- data.push({ name: 'Others', value: othersResult.rows[0][0],
__isOthers: true });
+ data.push({
+ name: 'Others',
+ value: Number(othersResult.rows[0][0]),
+ __isOthers: true,
+ });
}
}
diff --git
a/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx
b/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx
index c6c70dccf15..49689bd5572 100644
---
a/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx
+++
b/web-console/src/views/explore-view/modules/time-chart-module/time-chart-module.tsx
@@ -345,7 +345,7 @@ ModuleRepository.registerModule<TimeChartParameterValues>({
(b): RangeDatum => ({
start: b[TIME_NAME].valueOf(),
end: granularity.shift(b[TIME_NAME], Timezone.UTC, 1).valueOf(),
- measures: measures.map((_, i) => b[getMeasureName(i)]),
+ measures: measures.map((_, i) => Number(b[getMeasureName(i)])),
facet: b[FACET_NAME],
}),
);
diff --git a/web-console/src/views/segments-view/segments-view.tsx
b/web-console/src/views/segments-view/segments-view.tsx
index 7be81490255..90b96a6d621 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -216,7 +216,7 @@ interface SegmentQueryResultRow {
version: string;
shard_spec: ShardSpec;
partition_num: number;
- size: number;
+ size: NumberLike;
num_rows: NumberLike;
avg_row_size: NumberLike;
num_replicas: number;
diff --git a/web-console/src/views/services-view/services-view.tsx
b/web-console/src/views/services-view/services-view.tsx
index 606c1d58dfa..b02487ce9d1 100644
--- a/web-console/src/views/services-view/services-view.tsx
+++ b/web-console/src/views/services-view/services-view.tsx
@@ -164,8 +164,8 @@ interface ServiceResultRow {
readonly version: string;
readonly build_revision: string;
readonly labels: string | null;
- readonly available_processors: number;
- readonly total_memory: number;
+ readonly available_processors: NumberLike;
+ readonly total_memory: NumberLike;
}
interface CloneStatusInfo {
@@ -174,7 +174,7 @@ interface CloneStatusInfo {
readonly state: string;
readonly segmentLoadsRemaining: number;
readonly segmentDropsRemaining: number;
- readonly bytesToLoad: number;
+ readonly bytesToLoad: NumberLike;
}
interface ServerModeInfo {
@@ -810,7 +810,7 @@ ORDER BY
Aggregated: ({ subRows }) => {
const originalRows = subRows.map(r => r._original);
if (!originalRows.some(r => r.service_type === 'historical'))
return '';
- const totalCurr = sum(originalRows, s => s.curr_size);
+ const totalCurr = sum(originalRows, s => Number(s.curr_size));
return formatBytes(totalCurr);
},
Cell: ({ value, aggregated, original }) => {
@@ -830,7 +830,7 @@ ORDER BY
Aggregated: ({ subRows }) => {
const originalRows = subRows.map(r => r._original);
if (!originalRows.some(r => r.service_type === 'historical'))
return '';
- const totalEffectiveSize = sum(originalRows, s =>
s.effective_size);
+ const totalEffectiveSize = sum(originalRows, s =>
Number(s.effective_size));
return formatBytes(totalEffectiveSize);
},
Cell: ({ value, aggregated, original }) => {
@@ -988,7 +988,7 @@ ORDER BY
Cell: ({ value }) => (value === null ? '' : formatInteger(value)),
Aggregated: ({ subRows }) => {
const originalRows: ServiceResultRow[] = subRows.map(r =>
r._original);
- const totalAvailableProcessors = sum(originalRows, s =>
s.available_processors);
+ const totalAvailableProcessors = sum(originalRows, s =>
Number(s.available_processors));
return totalAvailableProcessors;
},
},
@@ -1005,7 +1005,7 @@ ORDER BY
},
Aggregated: ({ subRows }) => {
const originalRows: ServiceResultRow[] = subRows.map(r =>
r._original);
- const totalMemory = sum(originalRows, s => s.total_memory);
+ const totalMemory = sum(originalRows, s => Number(s.total_memory));
return formatBytes(totalMemory, true);
},
},
diff --git
a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
index 5a4cb1932f5..888e718a258 100644
---
a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
+++
b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
@@ -310,11 +310,13 @@ export const ExecutionStagesPane = React.memo(function
ExecutionStagesPane(
<div
key={k}
data-tooltip={`${fieldTitle}\nCPU time:
${formatDurationWithMs(
- v.cpu / 1e6,
+ Number(v.cpu) / 1e6,
)}`}
>
<span
className="cpu-label">{cpusCounterFieldTitle(k)}</span>
- <span
className="cpu-counter">{formatDurationWithMs(v.wall / 1e6)}</span>
+ <span className="cpu-counter">
+ {formatDurationWithMs(Number(v.wall) / 1e6)}
+ </span>
</div>
);
})}
@@ -1036,10 +1038,14 @@ ${title} uncompressed size: ${formatBytesCompact(
return (
<div
key={k}
- data-tooltip={`${fieldTitle}\nCPU time:
${formatDurationWithMs(v.cpu / 1e6)}`}
+ data-tooltip={`${fieldTitle}\nCPU time:
${formatDurationWithMs(
+ Number(v.cpu) / 1e6,
+ )}`}
>
<span className="cpu-label">{fieldTitle}</span>
- <span
className="cpu-counter">{formatDurationWithMs(v.wall / 1e6)}</span>
+ <span className="cpu-counter">
+ {formatDurationWithMs(Number(v.wall) / 1e6)}
+ </span>
</div>
);
})}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]