mirror of https://github.com/apache/druid.git
Web console: segment writing progress indication (#13929)
* add segment writing progress indication * update with more metrics * add push metric
This commit is contained in:
parent
d81d13b9ba
commit
8d125b7c7f
|
@ -102,6 +102,7 @@ export interface StageWorkerCounter {
|
||||||
output?: ChannelCounter;
|
output?: ChannelCounter;
|
||||||
shuffle?: ChannelCounter;
|
shuffle?: ChannelCounter;
|
||||||
sortProgress?: SortProgressCounter;
|
sortProgress?: SortProgressCounter;
|
||||||
|
segmentGenerationProgress?: SegmentGenerationProgressCounter;
|
||||||
warnings?: WarningCounter;
|
warnings?: WarningCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +147,20 @@ export interface SortProgressCounter {
|
||||||
triviallyComplete?: boolean;
|
triviallyComplete?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SegmentGenerationProgressCounter {
|
||||||
|
type: 'segmentGenerationProgress';
|
||||||
|
rowsProcessed: number;
|
||||||
|
rowsPersisted: number;
|
||||||
|
rowsMerged: number;
|
||||||
|
rowsPushed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SegmentGenerationProgressFields =
|
||||||
|
| 'rowsProcessed'
|
||||||
|
| 'rowsPersisted'
|
||||||
|
| 'rowsMerged'
|
||||||
|
| 'rowsPushed';
|
||||||
|
|
||||||
export interface WarningCounter {
|
export interface WarningCounter {
|
||||||
type: 'warning';
|
type: 'warning';
|
||||||
CannotParseExternalData?: number;
|
CannotParseExternalData?: number;
|
||||||
|
@ -157,6 +172,7 @@ export interface SimpleWideCounter {
|
||||||
[k: `input${number}`]: Record<ChannelFields, number> | undefined;
|
[k: `input${number}`]: Record<ChannelFields, number> | undefined;
|
||||||
output?: Record<ChannelFields, number>;
|
output?: Record<ChannelFields, number>;
|
||||||
shuffle?: Record<ChannelFields, number>;
|
shuffle?: Record<ChannelFields, number>;
|
||||||
|
segmentGenerationProgress?: SegmentGenerationProgressCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
function zeroChannelFields(): Record<ChannelFields, number> {
|
function zeroChannelFields(): Record<ChannelFields, number> {
|
||||||
|
@ -173,8 +189,12 @@ export class Stages {
|
||||||
static readonly QUERY_START_FACTOR = 0.05;
|
static readonly QUERY_START_FACTOR = 0.05;
|
||||||
static readonly QUERY_END_FACTOR = 0.05;
|
static readonly QUERY_END_FACTOR = 0.05;
|
||||||
|
|
||||||
|
static stageType(stage: StageDefinition): string {
|
||||||
|
return stage.definition.processor.type;
|
||||||
|
}
|
||||||
|
|
||||||
static stageWeight(stage: StageDefinition): number {
|
static stageWeight(stage: StageDefinition): number {
|
||||||
return stage.definition.processor.type === 'limit' ? 0.1 : 1;
|
return Stages.stageType(stage) === 'limit' ? 0.1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly stages: StageDefinition[];
|
public readonly stages: StageDefinition[];
|
||||||
|
@ -214,6 +234,9 @@ export class Stages {
|
||||||
case 'shuffle':
|
case 'shuffle':
|
||||||
return 'Shuffle output';
|
return 'Shuffle output';
|
||||||
|
|
||||||
|
case 'segmentGenerationProgress':
|
||||||
|
return 'Segment generation';
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (counterName.startsWith('input')) {
|
if (counterName.startsWith('input')) {
|
||||||
const inputIndex = Number(counterName.replace('input', ''));
|
const inputIndex = Number(counterName.replace('input', ''));
|
||||||
|
@ -230,7 +253,7 @@ export class Stages {
|
||||||
}
|
}
|
||||||
|
|
||||||
stageHasOutput(stage: StageDefinition): boolean {
|
stageHasOutput(stage: StageDefinition): boolean {
|
||||||
return stage.definition.processor.type !== 'segmentGenerator';
|
return Stages.stageType(stage) !== 'segmentGenerator';
|
||||||
}
|
}
|
||||||
|
|
||||||
stageHasSort(stage: StageDefinition): boolean {
|
stageHasSort(stage: StageDefinition): boolean {
|
||||||
|
@ -287,9 +310,12 @@ export class Stages {
|
||||||
) / inputFileCount
|
) / inputFileCount
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, base it on the stage input divided by the output of all non-broadcast input stages
|
// Otherwise, base it on the stage input divided by the output of all non-broadcast input stages,
|
||||||
|
// use the segment generation counter in the special case of a segmentGenerator stage
|
||||||
return zeroDivide(
|
return zeroDivide(
|
||||||
sum(input, (inputSource, i) =>
|
Stages.stageType(stage) === 'segmentGenerator'
|
||||||
|
? this.getTotalSegmentGenerationProgressForStage(stage, 'rowsPushed')
|
||||||
|
: sum(input, (inputSource, i) =>
|
||||||
inputSource.type === 'stage' && !broadcast?.includes(i)
|
inputSource.type === 'stage' && !broadcast?.includes(i)
|
||||||
? this.getTotalCounterForStage(stage, `input${i}`, 'rows')
|
? this.getTotalCounterForStage(stage, `input${i}`, 'rows')
|
||||||
: 0,
|
: 0,
|
||||||
|
@ -400,6 +426,15 @@ export class Stages {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTotalSegmentGenerationProgressForStage(
|
||||||
|
stage: StageDefinition,
|
||||||
|
field: SegmentGenerationProgressFields,
|
||||||
|
): number {
|
||||||
|
const { counters } = this;
|
||||||
|
if (!counters) return 0;
|
||||||
|
return sum(this.getCountersForStage(stage), c => c.segmentGenerationProgress?.[field] || 0);
|
||||||
|
}
|
||||||
|
|
||||||
getChannelCounterNamesForStage(stage: StageDefinition): ChannelCounterName[] {
|
getChannelCounterNamesForStage(stage: StageDefinition): ChannelCounterName[] {
|
||||||
const { definition } = stage;
|
const { definition } = stage;
|
||||||
|
|
||||||
|
@ -416,8 +451,7 @@ export class Stages {
|
||||||
const channelCounters = this.getChannelCounterNamesForStage(stage);
|
const channelCounters = this.getChannelCounterNamesForStage(stage);
|
||||||
|
|
||||||
const forStageCounters = counters?.[stageNumber] || {};
|
const forStageCounters = counters?.[stageNumber] || {};
|
||||||
return Object.keys(forStageCounters).map(key => {
|
return Object.entries(forStageCounters).map(([key, stageCounters]) => {
|
||||||
const stageCounters = forStageCounters[key];
|
|
||||||
const newWideCounter: SimpleWideCounter = {
|
const newWideCounter: SimpleWideCounter = {
|
||||||
index: Number(key),
|
index: Number(key),
|
||||||
};
|
};
|
||||||
|
@ -433,6 +467,7 @@ export class Stages {
|
||||||
}
|
}
|
||||||
: zeroChannelFields();
|
: zeroChannelFields();
|
||||||
}
|
}
|
||||||
|
newWideCounter.segmentGenerationProgress = stageCounters.segmentGenerationProgress;
|
||||||
return newWideCounter;
|
return newWideCounter;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import { RefName } from 'druid-query-toolkit';
|
import { T } from 'druid-query-toolkit';
|
||||||
import * as JSONBig from 'json-bigint-native';
|
import * as JSONBig from 'json-bigint-native';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export const ExecutionDetailsPane = React.memo(function ExecutionDetailsPane(
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{`General info for ${execution.id}${
|
<p>{`General info for ${execution.id}${
|
||||||
ingestDatasource ? ` ingesting into ${RefName.create(ingestDatasource, true)}` : ''
|
ingestDatasource ? ` ingesting into ${T(ingestDatasource)}` : ''
|
||||||
}`}</p>
|
}`}</p>
|
||||||
{execution.error && <ExecutionErrorPane execution={execution} />}
|
{execution.error && <ExecutionErrorPane execution={execution} />}
|
||||||
{execution.stages ? (
|
{execution.stages ? (
|
||||||
|
|
|
@ -31,6 +31,7 @@ import type {
|
||||||
ClusterBy,
|
ClusterBy,
|
||||||
CounterName,
|
CounterName,
|
||||||
Execution,
|
Execution,
|
||||||
|
SegmentGenerationProgressFields,
|
||||||
SimpleWideCounter,
|
SimpleWideCounter,
|
||||||
StageDefinition,
|
StageDefinition,
|
||||||
} from '../../../druid-models';
|
} from '../../../druid-models';
|
||||||
|
@ -125,6 +126,8 @@ export const ExecutionStagesPane = React.memo(function ExecutionStagesPane(
|
||||||
...stages.getInputCountersForStage(stage, 'rows').map(formatRows),
|
...stages.getInputCountersForStage(stage, 'rows').map(formatRows),
|
||||||
formatRows(stages.getTotalCounterForStage(stage, 'output', 'rows')),
|
formatRows(stages.getTotalCounterForStage(stage, 'output', 'rows')),
|
||||||
formatRows(stages.getTotalCounterForStage(stage, 'shuffle', 'rows')),
|
formatRows(stages.getTotalCounterForStage(stage, 'shuffle', 'rows')),
|
||||||
|
formatRows(stages.getTotalSegmentGenerationProgressForStage(stage, 'rowsMerged')),
|
||||||
|
formatRows(stages.getTotalSegmentGenerationProgressForStage(stage, 'rowsPushed')),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const filesValues = filterMap(stages.stages, stage => {
|
const filesValues = filterMap(stages.stages, stage => {
|
||||||
|
@ -164,6 +167,18 @@ export const ExecutionStagesPane = React.memo(function ExecutionStagesPane(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSegmentGenerator = Stages.stageType(stage) === 'segmentGenerator';
|
||||||
|
let bracesSegmentRowsMerged: string[] = [];
|
||||||
|
let bracesSegmentRowsPushed: string[] = [];
|
||||||
|
if (isSegmentGenerator) {
|
||||||
|
bracesSegmentRowsMerged = wideCounters.map(wideCounter =>
|
||||||
|
formatRows(wideCounter.segmentGenerationProgress?.rowsMerged || 0),
|
||||||
|
);
|
||||||
|
bracesSegmentRowsPushed = wideCounters.map(wideCounter =>
|
||||||
|
formatRows(wideCounter.segmentGenerationProgress?.rowsPushed || 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactTable
|
<ReactTable
|
||||||
className="detail-counters-for-workers"
|
className="detail-counters-for-workers"
|
||||||
|
@ -236,6 +251,30 @@ export const ExecutionStagesPane = React.memo(function ExecutionStagesPane(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
Stages.stageType(stage) === 'segmentGenerator'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
Header: twoLines('Merged', <i>rows</i>),
|
||||||
|
id: 'segmentGeneration_rowsMerged',
|
||||||
|
accessor: d => d.segmentGenerationProgress?.rowsMerged || 0,
|
||||||
|
className: 'padded',
|
||||||
|
width: 180,
|
||||||
|
Cell({ value }) {
|
||||||
|
return <BracedText text={formatRows(value)} braces={bracesSegmentRowsMerged} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: twoLines('Pushed', <i>rows</i>),
|
||||||
|
id: 'segmentGeneration_rowsPushed',
|
||||||
|
accessor: d => d.segmentGenerationProgress?.rowsPushed || 0,
|
||||||
|
className: 'padded',
|
||||||
|
width: 180,
|
||||||
|
Cell({ value }) {
|
||||||
|
return <BracedText text={formatRows(value)} braces={bracesSegmentRowsPushed} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -420,6 +459,22 @@ ${title} uncompressed size: ${formatBytesCompact(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dataProcessedSegmentGeneration(
|
||||||
|
stage: StageDefinition,
|
||||||
|
field: SegmentGenerationProgressFields,
|
||||||
|
) {
|
||||||
|
if (!stages.hasCounterForStage(stage, 'segmentGenerationProgress')) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="data-transfer">
|
||||||
|
<BracedText
|
||||||
|
text={formatRows(stages.getTotalSegmentGenerationProgressForStage(stage, field))}
|
||||||
|
braces={rowsValues}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactTable
|
<ReactTable
|
||||||
className={classNames('execution-stages-pane', DEFAULT_TABLE_CLASS_NAME)}
|
className={classNames('execution-stages-pane', DEFAULT_TABLE_CLASS_NAME)}
|
||||||
|
@ -510,12 +565,19 @@ ${title} uncompressed size: ${formatBytesCompact(
|
||||||
<>
|
<>
|
||||||
<div className="counter-spacer extend-right" />
|
<div className="counter-spacer extend-right" />
|
||||||
<div>{stages.getStageCounterTitle(stage, 'output')}</div>
|
<div>{stages.getStageCounterTitle(stage, 'output')}</div>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{stages.hasCounterForStage(stage, 'shuffle') && (
|
{stages.hasCounterForStage(stage, 'shuffle') && (
|
||||||
<div>{stages.getStageCounterTitle(stage, 'shuffle')}</div>
|
<div>{stages.getStageCounterTitle(stage, 'shuffle')}</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
{stages.hasCounterForStage(stage, 'segmentGenerationProgress') && (
|
||||||
|
<>
|
||||||
|
<div className="counter-spacer extend-right" />
|
||||||
|
<div>Merged</div>
|
||||||
|
<div>Pushed</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -536,11 +598,20 @@ ${title} uncompressed size: ${formatBytesCompact(
|
||||||
: dataProcessedInput(stage, i),
|
: dataProcessedInput(stage, i),
|
||||||
)}
|
)}
|
||||||
{stages.hasCounterForStage(stage, 'output') && (
|
{stages.hasCounterForStage(stage, 'output') && (
|
||||||
|
<>
|
||||||
<div className="counter-spacer extend-left" />
|
<div className="counter-spacer extend-left" />
|
||||||
)}
|
|
||||||
{dataProcessedOutput(stage)}
|
{dataProcessedOutput(stage)}
|
||||||
{dataProcessedShuffle(stage)}
|
{dataProcessedShuffle(stage)}
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
{stages.hasCounterForStage(stage, 'segmentGenerationProgress') && (
|
||||||
|
<>
|
||||||
|
<div className="counter-spacer extend-left" />
|
||||||
|
{dataProcessedSegmentGeneration(stage, 'rowsMerged')}
|
||||||
|
{dataProcessedSegmentGeneration(stage, 'rowsPushed')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@ exports[`IngestSuccessPane matches snapshot 1`] = `
|
||||||
className="ingest-success-pane"
|
className="ingest-success-pane"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
465,346 rows inserted into 'kttm_simple'.
|
465,346 rows inserted into "kttm_simple".
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Insert query took 0:00:09.
|
Insert query took 0:00:09.
|
||||||
|
|
|
@ -56,7 +56,7 @@ export const IngestSuccessPane = React.memo(function IngestSuccessPane(
|
||||||
return (
|
return (
|
||||||
<div className="ingest-success-pane">
|
<div className="ingest-success-pane">
|
||||||
<p>
|
<p>
|
||||||
{`${rows < 0 ? 'Data' : pluralIfNeeded(rows, 'row')} inserted into '${datasource}'.`}
|
{`${rows < 0 ? 'Data' : pluralIfNeeded(rows, 'row')} inserted into ${T(datasource)}.`}
|
||||||
{warnings > 0 && (
|
{warnings > 0 && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
|
|
Loading…
Reference in New Issue