blob: a21f2318fa7546d357b577f38e606efe7b4900d4 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import {
parseCounter,
parseProfileText,
ProfileParserError,
} from './profileParser';
import tpcdsQuery41MergedProfile from '../../test/fixtures/profiles/tpcds-query41-merged-profile.txt?raw';
function representativeProfile(version = '4.1.0') {
return [
'Summary:',
` - Doris Version: doris-${version}-abcdef`,
'MergedProfile:',
' Fragment 0:',
' Pipeline 0(instance_num=1):',
' RESULT_SINK_OPERATOR(id=2147483647):',
' CommonCounters:',
' - ExecTime: avg 2ms, max 3ms, min 1ms',
' EXCHANGE_OPERATOR(id=35):',
' CommonCounters:',
' - ExecTime: avg 1ms, max 1ms, min 1ms',
' Fragment 1:',
' Pipeline 0(instance_num=2):',
' DATA_STREAM_SINK_OPERATOR(dest_id=35):',
' CommonCounters:',
' - ExecTime: avg 1ms, max 2ms, min 1ms',
'DetailProfile(test):',
].join('\n');
}
describe('MergedProfile parser', () => {
it('parses a graph with pipeline and exchange edges', () => {
const graph = parseProfileText(representativeProfile());
expect(graph.summary).toMatchObject({ fragmentCount: 2, pipelineCount: 2, nodeCount: 3, edgeCount: 2 });
expect(graph.graph.edges.map((edge) => edge.kind)).toEqual(['PIPELINE_DATA', 'EXCHANGE']);
expect(graph.summary.criticalNodeId).toBe('fragment:0/pipeline:0/operator:0');
});
it.each(['4.0.6', '4.1.0', '5.0.0'])('parses a structurally compatible Doris %s Profile', (version) => {
expect(parseProfileText(representativeProfile(version)).summary.nodeCount).toBe(3);
});
it('does not require a Doris version marker when MergedProfile is compatible', () => {
const profile = representativeProfile().replace(/^ {2}- Doris Version:.*\n/m, '');
expect(parseProfileText(profile).summary.nodeCount).toBe(3);
});
it('reports Visual as unavailable when MergedProfile is absent', () => {
expect(() => parseProfileText('Summary:\n - Task Type: QUERY')).toThrowError(
expect.objectContaining({ code: 'DAG_UNAVAILABLE' }),
);
});
it('reports an incompatible MergedProfile structure as a parse failure with a reason', () => {
try {
parseProfileText('MergedProfile:\n incompatible content');
throw new Error('Expected parsing to fail.');
} catch (error) {
expect(error).toBeInstanceOf(ProfileParserError);
if (!(error instanceof ProfileParserError)) return;
expect(error.code).toBe('DAG_PARSE_FAILED');
expect(error.message).toContain('Fragment, Pipeline, and Operator');
}
});
it('keeps compound duration and exact count parsing from the website baseline', () => {
expect(parseCounter('WaitForDependencyTime', 'avg 13sec796ms, max 13sec796ms, min 1us')).toEqual({
sum: undefined,
avg: 13_796_000_000,
max: 13_796_000_000,
min: 1_000,
});
expect(parseCounter('RowsProduced', 'sum 2.232K (2232), avg 279, max 309, min 253')).toEqual({
sum: 2232,
avg: 279,
max: 309,
min: 253,
});
});
it('maps current Doris operator counters to their actual semantics', () => {
// Captured MergedProfile shape and counter names emitted by Doris BE.
const graph = parseProfileText([
'MergedProfile:',
' Fragment 0:',
' Pipeline 0(instance_num=1):',
' AGGREGATION_SINK_OPERATOR(nereids_id=842)(id=3):',
' CommonCounters:',
' - InputRows: sum 6, avg 3, max 4, min 2',
' - RowsProduced: sum 2, avg 1, max 1, min 1',
' - MemoryUsage: sum 1.25 MB, avg 640.00 KB, max 1.00 MB, min 256.00 KB',
' - PeakMemoryUsage: sum 2.00 MB, avg 1.00 MB, max 1.50 MB, min 512.00 KB',
'DetailProfile(test):',
].join('\n'));
expect(graph.graph.nodes[0].metrics).toMatchObject({
inputRows: { sum: 6, avg: 3, max: 4, min: 2 },
outputRows: { sum: 2, avg: 1, max: 1, min: 1 },
memoryUsageBytes: { sum: 1_310_720, avg: 655_360, max: 1_048_576, min: 262_144 },
memoryPeakBytes: { sum: 2_097_152, avg: 1_048_576, max: 1_572_864, min: 524_288 },
});
});
it('keeps the Doris 4.0 MemoryUsagePeak spelling as a compatibility fallback', () => {
const profile = representativeProfile().replace(
' - ExecTime: avg 2ms, max 3ms, min 1ms',
' - MemoryUsagePeak: sum 2.00 MB, avg 2.00 MB, max 2.00 MB, min 2.00 MB',
);
expect(parseProfileText(profile).graph.nodes[0].metrics?.memoryPeakBytes?.max).toBe(2_097_152);
});
it('parses row and memory metrics from a captured Doris TPC-DS Profile', () => {
const graph = parseProfileText(tpcdsQuery41MergedProfile);
const aggregationSink = graph.graph.nodes.find(
node => node.operatorType === 'AGGREGATION_SINK_OPERATOR' && node.nereidsId === 842,
);
expect(graph.summary).toMatchObject({ fragmentCount: 5, pipelineCount: 13 });
expect(aggregationSink?.metrics).toMatchObject({
inputRows: { sum: 6, max: 3 },
outputRows: null,
memoryPeakBytes: { sum: 1_310_720, max: 436_511 },
});
});
it('pairs the BE partitioned hash join operator names', () => {
const graph = parseProfileText([
'MergedProfile:',
' Fragment 0:',
' Pipeline 0(instance_num=1):',
' PARTITIONED_HASH_JOIN_PROBE_OPERATOR(nereids_id=12)(id=4):',
' Pipeline 1(instance_num=1):',
' PARTITIONED_HASH_JOIN_SINK_OPERATOR(nereids_id=12)(id=4):',
'DetailProfile(test):',
].join('\n'));
expect(graph.graph.edges).toEqual([
expect.objectContaining({ kind: 'BUILD_DEPENDENCY', relationId: '12' }),
]);
expect(graph.unresolvedReferences).toHaveLength(0);
});
it('parses multicast dest_ids lists and reports unresolved branches', () => {
const graph = parseProfileText([
'MergedProfile:',
' Fragment 0:',
' Pipeline 0(instance_num=1):',
' MULTI_CAST_DATA_STREAM_SINK_OPERATOR(id=9, dest_ids=[7,8]):',
' Fragment 1:',
' Pipeline 0(instance_num=1):',
' MULTI_CAST_DATA_STREAM_SOURCE_OPERATOR(id=7):',
' Fragment 2:',
' Pipeline 0(instance_num=1):',
' MULTI_CAST_DATA_STREAM_SOURCE_OPERATOR(id=8):',
'DetailProfile(test):',
].join('\n'));
expect(graph.graph.nodes[0].destIds).toEqual([7, 8]);
expect(graph.graph.edges.filter(edge => edge.kind === 'MULTICAST')).toHaveLength(2);
expect(graph.unresolvedReferences).toHaveLength(0);
});
});