* -------------------------------------------------------------------------
* 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 { Button, CollapsiblePanel } from '@insight/lib/components';
import type { ColumnsType } from 'antd/es/table';
import { DownOutlined } from '@ant-design/icons';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next';
import type { StringMap } from '../../utils/interface';
import { notNull, getPageConfigWithPageData } from '../Common';
import { queryCommunicationDetail, queryComputeDetail, querySummaryStatistics } from '../../utils/RequestUtils';
import { ResizeTable } from '@insight/lib/resize';
import type { Session } from '../../entity/session';
import { observer } from 'mobx-react-lite';
const useComputingStatisticsColumns = (): ColumnsType => {
const { t } = useTranslation('summary');
return [
{
title: t('AcceleratorCore'),
dataIndex: 'acceleratorCore',
key: 'acceleratorCore',
ellipsis: true,
},
{
title: `${t('AcceleratorCoreDurations')}(μs)`,
dataIndex: 'duration',
key: 'duration',
ellipsis: true,
},
];
};
const useComputingDetailColumns = (): ColumnsType => {
const { t } = useTranslation('operator', { keyPrefix: 'tableHead' });
return [
{ title: t('Name'), dataIndex: 'name', sorter: true, ellipsis: true },
{ title: t('Type'), dataIndex: 'type', sorter: true, ellipsis: true },
{ title: `${t('StartTime')}(ms)`, dataIndex: 'startTime', sorter: true, ellipsis: true },
{ title: `${t('Duration')}(μs)`, dataIndex: 'duration', sorter: true, ellipsis: true },
{ title: `${t('WaitTime')}(μs)`, dataIndex: 'waitTime', sorter: true, ellipsis: true },
{ title: t('BlockNum'), dataIndex: 'blockNum', sorter: true, ellipsis: true },
{
title: t('InputShapes'),
dataIndex: 'inputShapes',
sorter: true,
ellipsis: true,
},
{
title: t('InputDataTypes'),
dataIndex: 'inputDataTypes',
sorter: true,
ellipsis: true,
},
{
title: t('InputFormats'),
dataIndex: 'inputFormats',
sorter: true,
ellipsis: true,
},
{
title: t('OutputShapes'),
dataIndex: 'outputShapes',
sorter: true,
ellipsis: true,
},
{
title: t('OutputDataTypes'),
dataIndex: 'outputDataTypes',
sorter: true,
ellipsis: true,
},
{
title: t('OutputFormats'),
dataIndex: 'outputFormats',
sorter: true,
ellipsis: true,
},
];
};
const useCommunicationStatisticsColumns = (): ColumnsType => {
const { t } = useTranslation('summary');
return [
{
title: t('AcceleratorCore'),
dataIndex: 'acceleratorCore',
key: 'acceleratorCore',
ellipsis: true,
},
{
title: `${t('CommunicationDurationsNotOverlapped')}(μs)`,
dataIndex: 'notOverlapped',
key: 'notOverlapped',
ellipsis: true,
},
{
title: `${t('CommunicationDurationsOverlapped')}(μs)`,
dataIndex: 'overlapped',
key: 'overlapped',
ellipsis: true,
},
];
};
const useCommunicationDetailColumns = (): ColumnsType => {
const { t } = useTranslation('operator', { keyPrefix: 'tableHead' });
return [
{
title: t('Name'),
dataIndex: 'name',
sorter: true,
ellipsis: true,
},
{
title: t('Type'),
dataIndex: 'type',
sorter: true,
ellipsis: true,
},
{
title: `${t('StartTime')}(ms)`,
dataIndex: 'startTime',
sorter: true,
ellipsis: true,
},
{
title: `${t('Duration')}(μs)`,
dataIndex: 'duration',
sorter: true,
ellipsis: true,
},
{
title: `${t('WaitTime')}(μs)`,
dataIndex: 'waitTime',
sorter: true,
ellipsis: true,
},
];
};
const useCommunicationOverlappedDetailColumns = (): ColumnsType => {
const { t } = useTranslation('summary');
return [
...useCommunicationDetailColumns(),
{
title: `${t('Overlapped Durations')}(μs)`,
dataIndex: 'overlapDuration',
key: 'overlapDuration',
sorter: true,
ellipsis: true,
},
];
};
const useCommunicationNotOverlappedDetailColumns = (): ColumnsType => {
const { t } = useTranslation('summary');
return [
...useCommunicationDetailColumns(),
{
title: `${t('Not Overlapped Durations')}(μs)`,
dataIndex: 'notOverlapDuration',
key: 'notOverlapDuration',
sorter: true,
ellipsis: true,
},
];
};
const rowKeyMap: any = {
compute: 'acceleratorCore',
communication: 'acceleratorCore',
};
const useColMap = (): any => ({
compute: useComputingStatisticsColumns(),
computeDetail: useComputingDetailColumns(),
communication: useCommunicationStatisticsColumns(),
communicationDetail: useCommunicationDetailColumns(),
communicationOverlappedDetail: useCommunicationOverlappedDetailColumns(),
communicationNotOverlappedDetail: useCommunicationNotOverlappedDetailColumns(),
});
const useTableSet = (timeFlag: string, setExpandedKeys?: any): any => {
const rowKey = rowKeyMap[timeFlag];
const colMap = useColMap();
const columns = notNull(colMap[timeFlag]) ? [...colMap[timeFlag]] : [];
const { t } = useTranslation('summary');
if (['compute', 'communication'].includes(timeFlag)) {
const btnCol = {
title: t('Details'),
ellipsis: true,
minWidth: 85,
render: (_: any, record: any) => (<Button type="link"
onClick={(): void => {
setExpandedKeys((pre: string[]) => {
const list = [...pre];
const keyIndex = list.indexOf(record[rowKey]);
if (keyIndex === -1) {
list.push(record[rowKey]);
} else {
list.splice(keyIndex, 1);
}
return list;
});
}}>{t('Details')}<DownOutlined/></Button>),
};
columns.push(btnCol);
}
return { rowKey, columns };
};
const searchData = async({ rankId, dbPath, record, page, sorter, name, step, clusterPath }: any): Promise<{total: number;data: object[]}> => {
let data;
let total;
const param = {
rankId,
dbPath,
timeFlag: record.acceleratorCore,
currentPage: page.current,
pageSize: page.pageSize,
orderBy: sorter.field,
order: sorter.order,
step: step === 'All' ? '' : step,
clusterPath,
};
if (name === 'computeDetail') {
try {
const res = await queryComputeDetail(param);
data = res?.computeDetails ?? [];
total = res.totalNum;
} catch (e) {
data = [];
total = 0;
}
data = data.map((item: any) => ({
...item,
duration: Number(item.duration?.toFixed(4)),
waitTime: Number(item.waitTime?.toFixed(4)),
}));
} else {
try {
const res = await queryCommunicationDetail(param);
data = res?.communicationDetails ?? [];
total = res?.totalNum ?? 0;
} catch (e) {
data = [];
total = 0;
}
}
return { data, total };
};
const defaultPage = { current: 1, pageSize: 10, total: 0 };
const defaultSorter = { field: '', order: 'descend' };
const DetailTable = ({ rankId, dbPath, record, name, step, clusterPath }: any): JSX.Element => {
const [dataSource, setDataSource] = useState<any[]>([]);
const [page, setPage] = useState(defaultPage);
const [sorter, setSorter] = useState(defaultSorter);
const { columns, rowKey } = useTableSet(name);
useEffect(() => {
updateData(page, sorter);
}, [page.current, page.pageSize, sorter.field, sorter.order, record.acceleratorCore, rankId]);
const updateData = async(_page: any, _sorter: any): Promise<void> => {
const { data, total } = await searchData({ rankId, dbPath, record, page: _page, sorter: _sorter, name, step, clusterPath });
setDataSource(data);
setPage({ ..._page, total });
};
return <div>
<ResizeTable
dataSource={dataSource}
allowCopy
columns={columns}
rowKey={rowKey}
size="small"
pagination={getPageConfigWithPageData(page, setPage)}
onChange={(pagination: any, filters: any, mySorter: any, extra: any): void => {
if (extra.action === 'sort') {
setSorter(mySorter);
}
}
}
/>
</div>;
};
const timeFlagMap: StringMap = {
compute: 'Computing',
communicationNotOverLappedTime: 'Communication(Not OverLapped)',
communicationOverLappedTime: 'Communication(OverLapped)',
freeTime: 'Free',
};
function getTitle(timeFlag: string, t: TFunction): string {
return t(`${timeFlagMap[timeFlag]}Detail`);
}
export const ComputeStatisticsTable = (props: any): JSX.Element => {
const timeFlag = 'compute';
const { rankId = '', dbPath = '', step = '', session } = props;
const [dataSource, setDataSource] = useState<any[]>([]);
const [expandedRowKeys, setExpandedKeys] = useState<string[]>([]);
const { columns, rowKey } = useTableSet(timeFlag, setExpandedKeys);
useEffect(() => {
updateData();
setExpandedKeys([]);
}, [props.rankId, props.dbPath, props.step, session.selectedClusterPath]);
const updateData = async (): Promise<void> => {
try {
const res = await querySummaryStatistics(
{ timeFlag, rankId, dbPath, stepId: step === 'All' ? '' : step, clusterPath: session.selectedClusterPath });
let data = res?.summaryStatisticsItemList ?? [];
data = data.map((item: any) => ({
...item,
duration: Number(item.duration.toFixed(4)),
utilization: Number(item.utilization.toFixed(4)),
}));
setDataSource(data);
} catch (e) {
setDataSource([]);
}
};
return <ResizeTable
dataSource={ dataSource}
columns={columns}
expandable={{
expandedRowRender: (record: any) => <DetailTable record={record}
name={`${timeFlag}Detail`} rankId={rankId} dbPath={dbPath} step={step} clusterPath={session.selectedClusterPath} />,
expandedRowKeys,
expandIcon: () => (<></>),
}}
rowKey={rowKey}
pagination={false}
/>;
};
export const CommunicationStatisticsTable = (props: any): JSX.Element => {
const timeFlag = 'communication';
const { rankId = '', dbPath = '', step = '', session } = props;
const [dataSource, setDataSource] = useState<any[]>([]);
const [expandedRowKeys, setExpandedKeys] = useState<string[]>([]);
const { columns, rowKey } = useTableSet(timeFlag, setExpandedKeys);
useEffect(() => {
updateData();
setExpandedKeys([]);
}, [props.rankId, props.step, session.selectedClusterPath]);
const updateData = async (): Promise<void> => {
let list: any[] = [];
try {
const res = await querySummaryStatistics(
{ timeFlag, rankId, dbPath, stepId: step === 'All' ? '' : step, clusterPath: session.selectedClusterPath });
list = res?.summaryStatisticsItemList ?? [];
} catch (e) {
list = [];
}
const data = {
acceleratorCore: 'Communication',
overlapped: list.find(item => item.overlapType === 'Communication(Overlapped)')?.duration,
notOverlapped: list.find(item => item.overlapType === 'Communication(Not Overlapped)')?.duration,
};
setDataSource([data]);
};
return <ResizeTable
dataSource={ dataSource}
columns={columns}
expandable={{
expandedRowRender: (record: any) => <DetailTable record={record}
name={'communicationDetail' } rankId={rankId} dbPath={dbPath} step={step} clusterPath={session.selectedClusterPath} />,
expandedRowKeys,
expandIcon: () => (<></>),
}}
rowKey={rowKey}
pagination={false}
/>;
};
export const StatisticsTable = observer(({ rankId, dbPath, step, session }: {step: string; rankId: string; dbPath: string; session: Session }): JSX.Element => {
const { t } = useTranslation('summary');
return notNull(rankId) && session.unitcount > 0
? (
<div data-testid="statistics-table-container">
<div data-testid="computing-detail" style={{ marginBottom: '20px' }}>
<CollapsiblePanel
secondary
title={`${getTitle('compute', t)} ( Rank ${rankId} )`}
style={{ marginBottom: !session.isFullDb ? 0 : -26 }}
headerStyle={{ padding: 0, marginTop: 24, marginBottom: 16 }}
contentStyle={{ padding: 0 }}>
{session.parseCompleted
? (<ComputeStatisticsTable rankId={rankId} dbPath={dbPath} step={step} session={session}/>)
: <div style={{ textAlign: 'center' }}>{ t('Timeline not fully parsed') }</div>
}
</CollapsiblePanel>
</div>
{
!session.isFullDb
? (
<div data-testid="communication-detail" style={{ marginBottom: '20px' }}>
<CollapsiblePanel
secondary
style={{ marginBottom: -26 }}
title={`${t('CommunicationDetail')} ( Rank ${rankId} )`}
headerStyle={{ padding: 0, marginTop: 24, marginBottom: 16 }}
contentStyle={{ padding: 0 }}>
{session.parseCompleted
? <CommunicationStatisticsTable rankId={rankId} dbPath={dbPath} step={step} session={session}/>
: <div style={{ textAlign: 'center' }}>{ t('Timeline not fully parsed') }</div>
}
</CollapsiblePanel>
</div>
)
: <></>
}
</div>)
: <></>
;
});