* -------------------------------------------------------------------------
* 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 React, { useEffect, useState, useRef } from 'react';
import * as echarts from 'echarts';
import { safeStr } from '@insight/lib/utils';
import { MIChart } from '@insight/lib/components';
import type { ChartsHandle } from '@insight/lib';
import { type EChartsOption } from 'echarts';
import { Session } from '../entity/session';
import { observer } from 'mobx-react';
import { getFuncNewData } from './dataHandler';
import { chartResize } from '../utils/utils';
import { useTheme, type Theme } from '@emotion/react';
import { Loading, MarkLineStack } from './leaks/tools';
import { runInAction } from 'mobx';
const colorTypes: string[] = ['#8fd3e8', '#d95850', '#eb8146', '#ffb248', '#f2d643', '#ebdba4', '#fcce10', '#b5c334', '#1bca93'];
const transData = (data: any, min: number, max: number): any => {
return data.map((item: any, index: number) => ({
name: item.func,
value: [item.depth, Math.max(item.startTimestamp, min), Math.min(item.endTimestamp, max), item.func],
itemStyle: {
color: colorTypes[index % 9],
},
}));
};
const getRenderItem = (session: Session, theme: Theme, api: any): any => {
const level = api.value(0);
const start = api.coord([api.value(1), level]);
const end = api.coord([api.value(2), level]);
const height = ((api.size([0, 1]) || [0, 20]) as number[])[1];
const width = end[0] - start[0];
const customRes = {
type: 'rect',
transition: ['shape'],
shape: {
x: start[0],
y: start[1],
width,
height: height - 2,
},
emphasis: { style: { stroke: '#000' } },
textContent: {
type: 'text',
style: {
text: api.value(3),
fill: '#000',
overflow: 'truncate',
width: width - 4,
fontSize: 11,
},
},
textConfig: {
position: 'inside',
inside: true,
local: true,
},
style: {
fill: api.visual('color'),
stroke: '',
lineWidth: 0,
},
};
if (session.searchFunc.includes(api.value(3))) {
customRes.style.stroke = theme.mode === 'dark' ? '#fff' : '#000';
customRes.style.lineWidth = 3;
}
return customRes;
};
const getSeries = (session: Session, theme: Theme): any => {
return [
{
type: 'custom',
data: transData(session.funcData.traces, session.minTime, session.maxTime),
renderItem: (params: any, api: any): any => getRenderItem(session, theme, api),
encode: {
x: [0, 1, 2],
y: 0,
},
clip: true,
},
];
};
const getTooltip = (session: Session): echarts.TooltipComponentOption => {
return {
trigger: 'item',
formatter: function (params: any): string {
const info = session.funcData.traces[params.dataIndex];
if (!info) {
return '';
}
return safeStr(info.func);
},
textStyle: {
fontSize: 12,
},
};
};
const getOptions = (session: Session, theme: Theme): EChartsOption => {
return {
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
gradientColor: ['#f6efa6', '#d88273', '#bf444c'],
xAxis: {
axisTick: {
show: false,
},
min: session.minTime,
max: session.maxTime,
axisLine: {
show: false,
},
axisLabel: {
formatter: function (value: number): string {
return `${(value / 1000 / 1000).toFixed(3)}`;
},
},
splitLine: {
show: false,
},
},
yAxis: {
show: false,
inverse: true,
axisPointer: {
show: false,
snap: true,
},
max: session.maxDepth + 1,
},
tooltip: getTooltip(session),
series: getSeries(session, theme),
grid: {
left: 80,
right: 60,
},
};
};
const MemoryFunctionCall = observer(({ session }: { session: Session }): React.ReactElement => {
const chartRef = useRef<ChartsHandle>(null);
const markLineRef = useRef<HTMLDivElement>(null);
const [chartOptions, setChartOptions] = useState<EChartsOption>({});
const { funcData, deviceId, eventType, threadId, searchFunc, threadFlag } = session;
const [markLineContainerWidth, setMarkLineContainerWidth] = useState(0);
const theme: Theme = useTheme();
const handleResize = (): void => {
if (markLineRef.current === null) {
return;
}
setMarkLineContainerWidth(markLineRef.current.getBoundingClientRect().width);
const instance = chartRef.current?.getInstance();
if (instance) {
instance.clear();
setChartOptions(getOptions(session, theme));
}
};
const handleMouseMove = (ev: React.MouseEvent<HTMLDivElement>): void => {
if (markLineRef.current === null) {
return;
}
const rect = markLineRef.current.getBoundingClientRect();
const x = ev.clientX - rect.left;
const y = ev.clientY - rect.top;
if (x < 0 || x > rect.width || y < 0 || y > rect.height) {
runInAction(() => {
session.markLineInfo.stack = { x: -1, y: -1 };
session.markLineInfo.block = { x: -1, y: -1 };
});
return;
}
runInAction(() => {
session.markLineInfo.stack = { x, y };
});
};
useEffect(() => {
if (deviceId === '' || threadFlag) return;
getFuncNewData(session);
}, [deviceId, eventType, threadId]);
useEffect(() => {
setChartOptions(getOptions(session, theme));
chartResize(chartRef?.current?.getInstance());
}, [deviceId, eventType, threadId, funcData, searchFunc, theme.mode, session.minTime, session.maxTime]);
useEffect(() => {
if (markLineRef.current === null) {
return;
}
setMarkLineContainerWidth(markLineRef.current.getBoundingClientRect().width);
}, [chartOptions, theme.mode]);
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div style={{ position: 'relative', width: 'calc(100vw - 120px)', height: 500 }}
onMouseMove={handleMouseMove}>
<MIChart
ref={chartRef}
height="100%"
loading={false}
options={chartOptions}
/>
<Loading style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} loading={session.loadingFunc} />
<div ref={markLineRef}
style={{ position: 'absolute', top: 60, left: 80, width: 'calc(100% - 140px)', height: 'calc(100% - 130px)', pointerEvents: 'none' }}
>
<MarkLineStack session={session} width={markLineContainerWidth} />
</div>
</div>;
});
export default MemoryFunctionCall;