* -------------------------------------------------------------------------
* 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 type { Session } from '../../entity/session';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { observer } from 'mobx-react';
import { Button } from '@insight/lib/components';
import { getDefaultColumData, getPageData, queryOneKernel, searchAllSlices } from './Common';
import { ResizeTable, fetchColumnFilterProps } from '@insight/lib/resize';
import { ChartErrorBoundary } from '../error/ChartErrorBoundary';
import styled from '@emotion/styled';
import { DEFAULT_CARD_VALUE, RankFilter, SelectedCardInfo } from './SystemView';
import { getDetailTimeDisplay } from '../../insight/units/AscendUnit';
import type { InsightUnit } from '../../entity/insight';
import { getTimeOffset } from '../../insight/units/utils';
import type { ThreadMetaData } from '../../entity/data';
import { ProcessMetaData } from '../../entity/data';
import jumpToUnitOperator from '../../utils/jumpToUnitOperator';
const CONTAINER = styled.div`
height: calc(100% - 50px);
.ant-table-wrapper {
height: 100%;
}
`;
const FindDetailContainer = styled.div`
padding: 8px 16px;
.rank-filter {
display: flex;
align-items: center;
gap: 24px;
.content {
flex: none;
width: 160px;
}
}
`;
export interface SearchTableData {
*
* @type SearchAllSlicesDetails[]
* @memberof searchAllSlicesDetails
*/
searchAllSlicesDetails: SearchAllSlicesDetails[];
*
* @type {number}
* @memberof count
*/
count: number;
}
export interface SearchAllSlicesDetails {
startTime: string;
*
* @type {string}
* @memberof name
*/
name: string;
*
* @type {number}
* @memberof timestamp
*/
timestamp: number;
*
* @type {number}
* @memberof duration
*/
duration: number;
*
* @type {string}
* @memberof id
*/
id: string;
*
* @type {string}
* @memberof tid
*/
tid: string;
*
* @type {string}
* @memberof pid
*/
pid: string;
*
* @type {string}
* @memberof deviceId
*/
deviceId: string;
*
* @type {number}
* @memberof depth
*/
depth: number;
*
* @type {string}
* @memberof originOptimizer
*/
originOptimizer: string;
* 实际是 card: `{host} {rankId}`
* @type {string}
* @memberof rankId
*/
rankId: string;
* 卡的数据库路径
* @type {string}
* @memberof dbPath
*/
dbPath: string;
}
export function useFindDetail(session: Session, bottomHeight: number): any {
const { t } = useTranslation('timeline');
return {
label: <div className={'title'}><span>{t('Find')}</span></div>,
key: 'Find',
children: <FindDetailView session={session} bottomHeight={bottomHeight}/>,
};
}
const useColumns = (onFilter: (value: string) => void): any => {
const { t } = useTranslation('timeline', { keyPrefix: 'tableHead' });
return [
{
title: t('Name'),
dataIndex: 'name',
...getDefaultColumData('name'),
...fetchColumnFilterProps('name', 'Name', false, undefined, onFilter),
},
{ title: t('Start Time'), dataIndex: 'startTime', ...getDefaultColumData('startTime') },
{ title: t('Duration(ns)'), dataIndex: 'duration', ...getDefaultColumData('duration') },
];
};
export const FindDetailView = observer((props: { session: Session; bottomHeight: number }) => {
const [conditions, setConditions] = useState<SelectedCardInfo>(DEFAULT_CARD_VALUE);
const handleChange = (card: SelectedCardInfo): void => {
setConditions(card);
};
return (
<FindDetailContainer>
<RankFilter session={props.session} handleChange={handleChange} defaultRankId={props.session.searchData?.rankId}></RankFilter>
<ChartErrorBoundary>
<FindDetail card={conditions} session={props.session} bottomHeight={props.bottomHeight}></FindDetail>
</ChartErrorBoundary>
</FindDetailContainer>
);
});
const defaultPage = { current: 1, pageSize: 10, total: 0 };
const defaultSorter = { field: 'duration', order: 'descend' };
export interface FindDetailProps {
card: SelectedCardInfo;
session: Session;
bottomHeight: number;
}
interface AllConditionType {
doContextSearch?: boolean;
page: typeof defaultPage;
sorter: typeof defaultSorter;
selectCard: SelectedCardInfo;
nameFilter: string;
}
const FindDetail = observer((props: FindDetailProps) => {
const [dataSource, setDataSource] = useState<SearchAllSlicesDetails[]>([]);
const [page, setPage] = useState(defaultPage);
const [sorter, setSorter] = useState(defaultSorter);
const [isLoading, setLoading] = useState(false);
const [rowData, setRowData] = useState<Partial<SearchAllSlicesDetails>>({});
const [nameFilter, setNameFilter] = useState<string>('');
const [allCondition, setAllCondition] = useState<AllConditionType>(
{ doContextSearch: props.session.doContextSearch, page, sorter, selectCard: props.card, nameFilter: '' });
const { t } = useTranslation('timeline', { keyPrefix: 'tableHead' });
const handleNameFilter = (value: string): void => {
setNameFilter(value);
};
const columns = [...useColumns(handleNameFilter), {
title: t('Click To Timeline'),
dataIndex: 'click',
ellipsis: true,
render: (_: any, record: any): React.ReactElement => (<Button type="link"
onClick={(): void => {
setRowData({ name: record.name ?? record.originOptimizer, ...record });
}}>{t('Click')}</Button>),
}];
useEffect(() => {
setAllCondition({ ...allCondition, page, sorter });
}, [sorter, page.current, page.pageSize]);
useEffect(() => {
setAllCondition({ ...allCondition, doContextSearch: props.session.doContextSearch, page: defaultPage, selectCard: props.card });
setPage(defaultPage);
}, [props.session.doContextSearch, props.card.cardId]);
useEffect(() => {
setAllCondition({ ...allCondition, nameFilter, page: defaultPage });
setPage(defaultPage);
}, [nameFilter]);
useEffect(() => {
updateData(allCondition.page, allCondition.sorter, props);
}, [allCondition.sorter, allCondition.selectCard.cardId, allCondition.page.current,
allCondition.page.pageSize, allCondition.doContextSearch, allCondition.nameFilter, props.session.doReset]);
useEffect(() => {
if (rowData.name === null || rowData.name === undefined) {
return;
}
handleFindSelected(rowData as SearchAllSlicesDetails, props);
}, [rowData]);
const updateData = async(pages: any, sorters: {field: string;order: string}, prop: FindDetailProps): Promise<void> => {
if (props.card === undefined || props.card.cardId === '') {
setDataSource([]);
setPage(defaultPage);
setSorter(defaultSorter);
setAllCondition({ ...allCondition, page: defaultPage, sorter: defaultSorter });
return;
}
if (prop.session.searchData === undefined || prop.session.searchData?.content === '') {
setDataSource([]);
setPage(defaultPage);
setSorter(defaultSorter);
setAllCondition({ ...allCondition, page: defaultPage, sorter: defaultSorter });
return;
}
setLoading(true);
const res = await searchData(pages, sorters, prop, allCondition.nameFilter).finally(() => setLoading(false));
const timestampoffset = getTimeOffset(props.session, props.card);
const data = res.searchAllSlicesDetails.map(item => {
item.startTime = getDetailTimeDisplay(item.timestamp - timestampoffset);
return item;
});
setDataSource(data);
setPage({ ...page, total: res.count });
};
return <CONTAINER>
<ResizeTable
onChange={(pagination: unknown, filters: unknown, newsorter: unknown, extra: {action: string}): void => {
if (extra.action === 'sort') {
setSorter(newsorter as typeof sorter);
}
}}
rowClassName={(record: any): string => {
return record.id === rowData.id ? 'selected-row' : 'click-able';
}}
pagination={getPageData(page, setPage)}
dataSource={dataSource}
columns={columns}
scroll={{ y: props.bottomHeight - 180 }}
size="small"
loading = {isLoading}
/>
</CONTAINER>;
});
const searchData = async(pages: any, sorters: {field: string;order: string}, prop: FindDetailProps, nameFilter?: string): Promise<SearchTableData> => {
const metadataList = (prop.session.lockUnit as InsightUnit[]).map(selectUnit => {
const { threadId, processId, metaType, cardId } = selectUnit?.metadata as ThreadMetaData ?? {};
const timestampOffset = getTimeOffset(prop.session, selectUnit?.metadata as ProcessMetaData);
const lockStartTime = prop.session.lockRange === undefined ? 0 : Math.floor(prop.session.lockRange[0] + timestampOffset);
const lockEndTime = prop.session.lockRange === undefined ? 0 : Math.ceil(prop.session.lockRange[1] + timestampOffset);
return {
tid: threadId,
pid: processId,
metaType,
rankId: cardId,
lockStartTime,
lockEndTime,
};
});
const res = await searchAllSlices({
rankId: prop.card.cardId,
dbPath: prop.card.dbPath,
pageSize: pages.pageSize,
current: pages.current,
orderBy: sorters.field === 'startTime' ? 'timestamp' : sorters.field ?? defaultSorter.field,
order: sorters.order ?? defaultSorter.order,
searchContent: prop.session.searchData?.content,
isMatchCase: prop.session.searchData?.isMatchCase,
isMatchExact: prop.session.searchData?.isMatchExact,
metadataList,
nameFilter,
});
return res;
};
const handleFindSelected = async(rowData: SearchAllSlicesDetails & { originOptimizer: string }, props: FindDetailProps): Promise<void> => {
const queryName = rowData.name ?? rowData.originOptimizer;
const res = await queryOneKernel({
rankId: rowData.rankId,
dbPath: rowData.dbPath,
name: queryName,
timestamp: rowData.timestamp,
duration: rowData.duration,
});
const depth = rowData.depth > res.depth ? rowData.depth : res.depth;
jumpToUnitOperator({
...rowData,
name: queryName,
depth,
cardId: rowData.rankId,
dbPath: rowData.dbPath,
});
};