* -------------------------------------------------------------------------
* 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';
import { fetchColumnFilterProps, ResizeTable } from '@insight/lib/resize';
import type { ColumnsType } from 'antd/es/table';
import { DragDirection, useDraggableContainer } from '@insight/lib';
import React, { useEffect, useRef, useState } from 'react';
import { ChartErrorBoundary } from '../error/ChartErrorBoundary';
import { MoreContainer, StyledMoreCard } from '../BottomPanel';
import { store } from '../../store';
import { getOverallMetrics, getOverallMetricsMoreList } from '../../api/request';
import type {
GetOverallMetricsMoreListResultItem,
GetOverallMetricsResultItem,
} from '../../api/interface';
import type { FilterValue, SorterResult } from 'antd/lib/table/interface';
import { Session } from '../../entity/session';
import { getDefaultColumData, getPageData, PageType, queryOneKernel } from './Common';
import type { CardMetaData } from '../../entity/data';
import jumpToUnitOperator from '../../utils/jumpToUnitOperator';
import { getDetailTimeDisplay } from '../../insight/units/AscendUnit';
import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next';
import { StyledEmpty } from '@insight/lib/utils';
import type { SelectContentViewProps } from './SystemView';
import { getTimeOffset } from '../../insight/units/utils';
import { ResponseValidator } from '../../utils/response-validator';
export const overallMetricsColumns = (t: TFunction): ColumnsType<GetOverallMetricsResultItem> => [
{ title: t('Category'), dataIndex: 'name', ellipsis: true },
{ title: t('Total Time(us)'), dataIndex: 'totalTime' },
{
title: t('Time Ratio'),
dataIndex: 'ratio',
render: (text) => text !== null ? `${text}%` : text,
},
{ title: t('Number'), dataIndex: 'nums' },
{ title: t('Avg(us)'), dataIndex: 'avg' },
{ title: t('Min(us)'), dataIndex: 'min' },
{ title: t('Max(us)'), dataIndex: 'max' },
];
const overallMetricsMoreColumns = (t: TFunction): ColumnsType<GetOverallMetricsMoreListResultItem> => [
{
title: t('Name'),
dataIndex: 'name',
ellipsis: true,
...fetchColumnFilterProps('name', 'Name'),
onFilter: undefined,
},
{
title: t('Start Time'),
dataIndex: 'startTime',
...getDefaultColumData('startTime'),
},
{
title: t('Duration(ns)'),
dataIndex: 'duration',
...getDefaultColumData('duration'),
},
];
interface OverallMetricsTableProps extends SelectContentViewProps {
session: Session;
selectedRow?: GetOverallMetricsResultItem | null;
setSelectedRow: (row: GetOverallMetricsResultItem | null) => void;
}
const OverallMetricsTable = observer(({ bottomHeight, card, session, selectedRow, setSelectedRow }: OverallMetricsTableProps) => {
const defaultPage = { current: 1, pageSize: 10, total: 0 };
const [tableData, setTableData] = useState<GetOverallMetricsResultItem[]>([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(defaultPage);
const cardPhase = session.units.find((unit) => (unit.metadata as CardMetaData).cardId === card.cardId)?.phase;
const { t } = useTranslation('timeline', { keyPrefix: 'tableHead' });
async function getOverallMetricsData(): Promise<void> {
if (!card || card.cardId === '') {
return;
}
setLoading(true);
try {
const rankId = card.cardId;
const dbPath = card.dbPath;
let startTime = session.timeAnalysisRange?.[0] ?? 0;
startTime = startTime < 0 ? 0 : startTime;
let endTime = session.timeAnalysisRange?.[1] ?? 0;
endTime = endTime < 0 ? 0 : endTime;
const timestampoffset = getTimeOffset(session, card);
const { data, count: total } = await getOverallMetrics({
rankId,
dbPath,
pageSize: page.pageSize,
current: page.current,
startTime: Math.floor(startTime + timestampoffset),
endTime: Math.ceil(endTime + timestampoffset),
});
setPage({ ...page, total });
setTableData(data ?? []);
setLoading(false);
} catch (e) {
setLoading(false);
setTableData([]);
setPage(defaultPage);
}
}
useEffect(() => {
getOverallMetricsData();
setSelectedRow(null);
}, [card.cardId, session.timeAnalysisRange]);
useEffect(() => {
if (cardPhase === 'download') {
getOverallMetricsData();
}
}, [cardPhase, session.timeAnalysisRange]);
return <ResizeTable
rowKey={'id'}
dataSource={tableData}
columns={overallMetricsColumns(t)}
loading={loading}
scroll={{ y: bottomHeight - 146 }}
pagination={getPageData(page, setPage)}
rowHoverable={false}
expandable={{}}
onRow={(record): { onClick: () => void } => ({
onClick: (): void => {
if (record.level === 1) {
return;
}
setSelectedRow(record);
},
})}
// 给表格添加类名
rowClassName={(record): string => {
// 检查selectedRow的id是否与当前记录的id相同
return selectedRow?.id === record.id ? 'selected-row' : '';
}}
></ResizeTable>;
});
interface OverallMetricsMoreProps extends SelectContentViewProps {
selectedRow?: GetOverallMetricsResultItem | null;
}
const OverallMetricsMoreTable = observer(({ card, session, selectedRow, bottomHeight }: OverallMetricsMoreProps) => {
const [selectedRowId, setSelectedRowId] = useState<string>();
const { t } = useTranslation('timeline', { keyPrefix: 'tableHead' });
const { page, setPage, setSorter, setFilters, loading, tableData } = useMetricsMoreUpdater({
session,
card,
selectedRow,
});
const rowEvents = (record: GetOverallMetricsMoreListResultItem): React.HTMLAttributes<any> => {
const { id, name, duration, timestamp } = record;
return {
onClick: async (): Promise<void> => {
const res = await queryOneKernel({ rankId: card.cardId, dbPath: card.dbPath, name, timestamp, duration });
jumpToUnitOperator({
...record,
...res,
duration,
cardId: card.cardId,
dbPath: card.dbPath,
tid: res.threadId,
id: res.id,
});
setSelectedRowId(id);
},
};
};
return <ResizeTable
rowKey={'id'}
dataSource={tableData}
columns={overallMetricsMoreColumns(t)}
scroll={{ y: bottomHeight !== undefined ? bottomHeight - 200 : undefined }}
loading={loading}
pagination={getPageData(page, setPage)}
onRow={(record): React.HTMLAttributes<any> => rowEvents(record)}
rowClassName={(record): string => {
return record.id === selectedRowId ? 'selected-row' : 'click-able';
}}
onChange={(pagination, filters, newSorter, extra): void => {
if (extra.action === 'sort') {
setSorter(newSorter as SorterResult<GetOverallMetricsMoreListResultItem>);
}
if (extra.action === 'filter') {
setFilters(filters);
}
}}
></ResizeTable>;
});
export type MetricsMoreUpdaterType = ({ session, card, selectedRow }: Pick<OverallMetricsMoreProps, 'session' | 'card' | 'selectedRow'>) => ({
page: PageType;
setPage: (args: PageType) => void;
sorter: SorterResult<GetOverallMetricsMoreListResultItem>;
setSorter: React.Dispatch<React.SetStateAction<SorterResult<GetOverallMetricsMoreListResultItem>>>;
setFilters: React.Dispatch<React.SetStateAction<Record<string, FilterValue | null>>>;
loading: boolean;
tableData: GetOverallMetricsMoreListResultItem[];
});
const useMetricsMoreUpdater: MetricsMoreUpdaterType = ({ session, card, selectedRow }) => {
const defaultPage = { current: 1, pageSize: 10, total: 0 };
const defaultSorter: SorterResult<GetOverallMetricsMoreListResultItem> = { field: 'duration', order: 'descend' };
const [page, setPage] = useState<PageType>(defaultPage);
const [sorter, setSorter] = useState(defaultSorter);
const [filters, setFilters] = useState<Record<string, FilterValue | null>>({});
const [loading, setLoading] = useState(false);
const [tableData, setTableData] = useState<GetOverallMetricsMoreListResultItem[]>([]);
const validatorRef = useRef(new ResponseValidator());
const isMountedRef = useRef(true); // 防止卸载后 setState
const getMoreListData = React.useCallback(async (selectedRow: GetOverallMetricsResultItem | null): Promise<{
page: PageType; data: GetOverallMetricsMoreListResultItem[];
} | null> => {
if (!card || card.cardId === '') {
return null;
}
setLoading(true);
let startTime = session.timeAnalysisRange?.[0] ?? 0;
startTime = startTime < 0 ? 0 : startTime;
let endTime = session.timeAnalysisRange?.[1] ?? 0;
endTime = endTime < 0 ? 0 : endTime;
const timestampoffset = getTimeOffset(session, card);
const { sameOperatorsDetails, count: total, pageSize, currentPage: current } = await getOverallMetricsMoreList({
rankId: card.cardId,
dbPath: card.dbPath,
name: filters.name?.[0] as string | undefined,
categoryList: selectedRow?.categoryList ?? [],
order: sorter.order,
orderBy: sorter.field as keyof GetOverallMetricsMoreListResultItem,
pageSize: page.pageSize,
current: page.current,
startTime: Math.floor(startTime + timestampoffset),
endTime: Math.ceil(endTime + timestampoffset),
}).finally(() => {
setLoading(false);
});
return {
page: { ...page, pageSize, current, total },
data: sameOperatorsDetails.map(item => ({ ...item, startTime: getDetailTimeDisplay(item.timestamp) })) ?? [],
};
}, [card?.cardId, card?.dbPath, filters.name, sorter.order, sorter.field, page.pageSize, page.current, session.timeAnalysisRange]);
useEffect(() => {
isMountedRef.current = true;
const validator = validatorRef.current;
// 更新版本号
const requestVersion = validator.markUpdate();
setTableData([]);
// 情况1:selectedRow 为空 -> 立即清空数据
if (!selectedRow) {
return () => {
isMountedRef.current = false;
};
}
getMoreListData(selectedRow).then(res => {
if (res && isMountedRef.current && validator.isValid(requestVersion)) {
setPage(res.page);
setTableData(res.data);
}
});
return () => {
isMountedRef.current = false;
};
}, [getMoreListData, selectedRow]);
useEffect(() => {
setPage(defaultPage);
}, [card?.cardId, selectedRow?.id]);
useEffect(() => () => {
validatorRef.current.reset(); // 组件卸载时重置版本号,确保后续请求无效
}, []);
return { page, setPage, sorter, setSorter, setFilters, loading, tableData };
};
/**
* OverallMetrics组件,用于展示总体指标信息。
*
* @param {SelectContentViewProps} props - 组件的属性,包含卡片信息等
* @returns {JSX.Element} 返回一个React元素,根据条件渲染总体指标表格或空状态
*/
export const OverallMetrics = observer((props: SelectContentViewProps) => {
const { sessionStore } = store;
// 获取当前活跃的session
const session = sessionStore.activeSession;
// 使用拖拽容器,设置拖拽方向为向右,宽度为400
const [view] = useDraggableContainer({ dragDirection: DragDirection.RIGHT, draggableWH: 400 });
// 定义并初始化selectedRow状态,用于存储选中的行数据
const [selectedRow, setSelectedRow] = useState<GetOverallMetricsResultItem | null>();
const { t } = useTranslation();
return props.card !== undefined && props.card.cardId !== ''
? view({
mainContainer: session !== undefined
? <OverallMetricsTable {...props} selectedRow={selectedRow} setSelectedRow={setSelectedRow}
session={session}/>
: <></>,
draggableContainer: <StyledMoreCard
className="moreContainer"
title={t('Details')}
bordered={false}>
<ChartErrorBoundary className={'more-error'}>
<MoreContainer>
{session && <OverallMetricsMoreTable {...props} session={session} selectedRow={selectedRow}/>}
</MoreContainer>
</ChartErrorBoundary>
</StyledMoreCard>,
id: 'overallMetrics',
})
: <div style={{ display: 'flex', height: '100%' }}>
<StyledEmpty style={{ margin: 'auto' }}/>
</div>;
});