* -------------------------------------------------------------------------
* This file is part of the MindStudio project.
* Copyright (c) 2025 Huawei Technologies Co.,Ltd.
*
* MindStudio is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*/
import { observer } from 'mobx-react-lite';
import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Spin, CollapsiblePanel } from '@insight/lib/components';
import { chartVisbilityListener, COLOR, commonEchartsOptions } from '../Common';
import type { Session } from '../../entity/session';
import i18n from '@insight/lib/i18n';
import { cloneDeep } from 'lodash';
import { chartColors, getAdaptiveEchart, getDefaultChartOptions, safeStr } from '@insight/lib/utils';
import type { LegendComponentOption, TooltipComponentOption } from 'echarts/components';
import { CompareData, FormatterParams } from '../../utils/interface';
export interface DataItem {
index: number;
rankId: string;
dbPath: string;
compareData: CompareData<Duration>;
}
export interface Duration {
elapseTime: number;
transitTime: number;
synchronizationTime: number;
waitTime: number;
synchronizationTimeRatio: number;
waitTimeRatio: number;
startTime: number;
idleTime: number;
sdmaBw: number;
rdmaBw: number;
}
export interface ChartData {
rankId: string[];
elapseTime?: number[];
transitTime?: number[];
synchronizationTime?: number[];
waitTime?: number[];
synchronizationTimeRatio?: number[];
waitTimeRatio?: number[];
}
function InitCharts(data: ChartData, isCompare: boolean): void {
const chartDom = document.getElementById('main');
if (chartDom === null || chartDom.offsetParent === null) {
return;
}
const myChart = getAdaptiveEchart(chartDom);
myChart.setOption(wrapData(data, isCompare), { replaceMerge: ['series', 'xAxis', 'yAxis', 'legend'] });
}
function wrapData(data: ChartData, isCompare: boolean): any {
const options = cloneDeep(baseOption);
options.xAxis[0].data = data.rankId;
options.legend = getLegend();
options.series = getSeries({ data });
options.tooltip = getTooltip(isCompare);
return options;
}
function getLegend(): LegendComponentOption {
const legend = baseOption.legend;
return {
...legend,
data: legend.data.map((legendDataItem: any) => ({
...legendDataItem,
name: i18n.t(`tableHead.${legendDataItem.name}`, { ns: 'communication' }),
}))
,
};
}
function getSeries({ data }: {data: ChartData}): any {
return baseOption.series.map((serie: any) => ({
...serie,
name: i18n.t(`tableHead.${serie.name}`, { ns: 'communication' }),
data: data[serie.id as keyof ChartData],
}));
}
function getTooltip(isCompare: boolean): TooltipComponentOption {
return {
...commonEchartsOptions.tooltip,
confine: true,
formatter: (params: FormatterParams[]): string => getTooltipFormatter(params, isCompare),
};
}
function getTooltipFormatter(params: FormatterParams[], isCompare: boolean): string {
let html = params[0].name;
params.forEach(serie => {
const { marker, seriesName, seriesType, value } = serie;
let valueClass = '';
if (isCompare) {
valueClass = value >= 0 ? 'positive-number' : 'negative-number';
}
html += `
<div>
<span>${marker}${safeStr(seriesName)}</span>
<span class="tooltip-value ${valueClass}">${safeStr(value)} ${seriesType === 'line' ? '' : 'ms'}</span>
</div>`;
});
return html;
}
const baseOption: any = {
textStyle: getDefaultChartOptions().textStyle,
color: chartColors,
tooltip: {
...commonEchartsOptions.tooltip,
confine: true,
},
toolbox: {
show: false,
},
legend: {
bottom: 0,
data: [
{ name: 'Elapse Time', textStyle: { color: COLOR.GREY_50 } },
{ name: 'Transit Time', textStyle: { color: COLOR.GREY_50 } },
{ name: 'Synchronization Time', textStyle: { color: COLOR.GREY_50 } },
{ name: 'Wait Time', textStyle: { color: COLOR.GREY_50 } },
{ name: 'Synchronization Time Ratio', textStyle: { color: COLOR.GREY_50 } },
{ name: 'Wait Time Ratio', textStyle: { color: COLOR.GREY_50 } }],
tooltip: {
show: true,
formatter: function () {
const div = document.createElement('div');
div.className = 'legend-tooltip';
div.append(i18n.t('chart:switchTooltip'));
return div;
},
},
},
xAxis: [
{
type: 'category',
data: [],
axisPointer: {
type: 'shadow',
},
axisLabel: {
color: COLOR.GREY_40,
},
},
],
yAxis: [
{
type: 'value',
name: 'Time(ms)',
axisLabel: {
formatter: '{value}',
color: COLOR.GREY_40,
},
},
{
type: 'value',
name: 'Ratio',
axisLabel: {
formatter: '{value}',
color: COLOR.GREY_40,
},
splitLine: commonEchartsOptions.splitLineY,
},
],
series: [
{
id: 'elapseTime',
name: 'Elapse Time',
type: 'bar',
tooltip: {
valueFormatter: (value: any): string => {
return `${value}ms`;
},
},
data: [],
},
{
id: 'transitTime',
name: 'Transit Time',
type: 'bar',
tooltip: {
valueFormatter: (value: any): string => {
return `${value}ms`;
},
},
data: [],
},
{
id: 'synchronizationTime',
name: 'Synchronization Time',
type: 'bar',
tooltip: {
valueFormatter: (value: any): string => {
return `${value}ms`;
},
},
data: [],
},
{
id: 'waitTime',
name: 'Wait Time',
type: 'bar',
tooltip: {
valueFormatter: (value: any): string => {
return `${value}ms`;
},
},
data: [],
},
{
id: 'synchronizationTimeRatio',
name: 'Synchronization Time Ratio',
type: 'line',
yAxisIndex: 1,
tooltip: {
valueFormatter: (value: any): string => {
return value;
},
},
data: [],
},
{
id: 'waitTimeRatio',
name: 'Wait Time Ratio',
type: 'line',
yAxisIndex: 1,
tooltip: {
valueFormatter: (value: any): string => {
return value;
},
},
data: [],
},
],
grid: {
left: 100,
right: 100,
},
};
const wrapChartData = (data: DataItem[], isCompare: boolean): ChartData => {
const chartData: ChartData = {} as ChartData;
chartData.rankId = data.map((item: DataItem) => item.rankId);
const fields: Array<keyof Duration & keyof ChartData> = ['elapseTime', 'transitTime', 'synchronizationTime',
'waitTime', 'synchronizationTimeRatio', 'waitTimeRatio'];
fields.forEach(field => {
chartData[field] = data.map((item: DataItem) => isCompare ? item.compareData.diff[field] : item.compareData.compare[field]);
});
return chartData;
};
const CommunicationTimeChart = observer(({ dataSource, session }: {dataSource: DataItem[]; session: Session}) => {
const { t } = useTranslation('communication');
const data = useMemo(() => wrapChartData(dataSource, session.isCompare), [dataSource, session.isCompare]);
chartVisbilityListener('main', () => {
InitCharts(data, session.isCompare);
});
useEffect(() => {
setTimeout(() => {
InitCharts(data, session.isCompare);
});
}, [dataSource, t, session.isCompare]);
return (
<CollapsiblePanel title={t('sessionTitle.VisualizedCommunicationTime')}>
<Spin spinning={session.clusterCompleted && !session.durationFileCompleted } tip="">
<div id={'main'} style={{ height: '400px' }} ></div>
</Spin>
</CollapsiblePanel>
);
});
export default CommunicationTimeChart;