* -------------------------------------------------------------------------
* 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, { useState, useEffect } from 'react';
import { observer } from 'mobx-react-lite';
import { runInAction } from 'mobx';
import { Spin, CollapsiblePanel, Select } from '@insight/lib/components';
import { StyledEmpty, customConsole as console } from '@insight/lib/utils';
import { useTranslation } from 'react-i18next';
import { Graph, MemoryCurve } from '../entity/memory';
import { LineChart } from './LineChart';
import { Session } from '../entity/session';
import { MemorySession, GroupBy, type RangeFlagList } from '../entity/memorySession';
import { memoryCurveGet } from '../utils/RequestUtils';
import { getTimelineOffsetByKey } from '../connection/handler';
import { Label } from './Common';
import { FlexDiv } from '../utils/styleUtils';
const DynamicLineChart = observer(({ session, memorySession, isDark }:
{ session: Session; memorySession: MemorySession; isDark: boolean }) => {
const isCompare: boolean = session.compareRank.isCompare;
const [memoryCurveData, setMemoryCurveData] = useState<MemoryCurve | undefined>(undefined);
const [lineChartData, setLineChartData] = useState<Graph | undefined>(undefined);
const [curveSpin, setCurveSpin] = useState<boolean>(false);
const [rangeFlagData, setRangeFlagData] = useState<RangeFlagList[]>([]);
const [rankOffsetNs, setRankOffsetNs] = useState<number>(0);
const { t } = useTranslation('memory');
const onSelectedRangeChanged = (start: number, end: number): void => {
runInAction(() => {
if (start > end || !memoryCurveData) {
memorySession.selectedRange = undefined;
return;
}
const allDataListSet = new Set(memoryCurveData.lines
.map(item => {
return parseFloat(item[0] as string);
}).sort((a, b) => a - b));
if (allDataListSet.size <= 1) {
memorySession.selectedRange = undefined;
return;
}
const allDataList = Array.from(allDataListSet);
if (end >= allDataList.length) {
memorySession.selectedRange = { startTs: allDataList[start], endTs: allDataList[allDataList.length - 1] };
} else {
memorySession.selectedRange = { startTs: allDataList[start], endTs: allDataList[end] };
}
memorySession.current = 1;
memorySession.pageSize = 10;
});
};
const onMemoryCurveGet = (): void => {
setCurveSpin(true);
const rankValue = memorySession.getSelectedRankValue();
const start = (memorySession.selectedRange?.startTs ?? '').toString();
const end = (memorySession.selectedRange?.endTs ?? '').toString();
memoryCurveGet({ rankId: rankValue.rankInfo.rankId, dbPath: rankValue.dbPath, type: memorySession.groupId, isCompare, start, end }).then((resp) => {
setMemoryCurveData(resp);
let columns: string[] = [];
if (memorySession.groupId === GroupBy.STREAM) {
columns = resp.legends?.map(legend => {
const index = legend.lastIndexOf(' ');
if (index > -1) {
return t(legend.slice(0, index), { stream: legend.slice(index + 1) });
} else {
return t(legend);
}
});
} else {
columns = resp.legends?.map(legend => t(legend));
}
if (columns?.length <= 0 || resp.lines?.length <= 0) {
return;
}
setLineChartData({
title: resp.title,
columns,
rows: resp.lines,
});
setRankOffsetNs(resp.rankOffsetNs);
}).catch(err => {
console.error(err);
}).finally(() => {
setCurveSpin(false);
});
};
useEffect(() => {
const timer = setTimeout(() => {
if (memorySession.selectedRankId === '') {
setLineChartData(undefined);
setMemoryCurveData(undefined);
return;
}
onMemoryCurveGet();
});
return () => clearTimeout(timer);
}, [memorySession.selectedRankId, memorySession.groupId, t, session.isAllMemoryCompletedSwitch, isCompare, memorySession.selectedRange]);
useEffect(() => {
runInAction(() => {
memorySession.searchEventOperatorName = '';
memorySession.current = 1;
memorySession.pageSize = 10;
});
}, [isCompare]);
return (
<div className="mb-30">
<CollapsiblePanel title={t('Memory Analysis')}>
<Spin spinning={curveSpin}>
{lineChartData
? <>
<div style={{ paddingBottom: 20 }}>
<RangeFlagSelect memorySession={memorySession} rankOffsetNs={rankOffsetNs} callback={setRangeFlagData}/>
</div>
<LineChart
hAxisTitle={t('Time (ms)')}
vAxisTitle={t('Memory Usage (MB)')}
graph={lineChartData}
onSelectionChanged={onSelectedRangeChanged}
record={memorySession.selectedRecord}
isDark={isDark}
isStatic={false}
rangeFlagData={rangeFlagData}
/>
</>
: <StyledEmpty style={{ marginTop: 160 }} />
}
</Spin>
</CollapsiblePanel>
</div>
);
});
const RangeFlagSelect = observer(({ memorySession, rankOffsetNs, callback }: { memorySession: MemorySession; rankOffsetNs: number; callback: (value: RangeFlagList[]) => void }): JSX.Element => {
const [selectValue, setSelectValue] = useState<string[]>([]);
const [selectOptions, setSelectOptions] = useState<Array<{ label: string; value: string }>>([]);
const { t } = useTranslation('memory');
const handleChange = (value: string[]): void => {
setSelectValue(value);
};
const updateSelectData = (): void => {
const selectOptionsValueListOld = selectOptions.map(item => item.value);
const selectOptionsValueListNew = memorySession.rangeFlagList.map(item => item.uid);
const selectValueNew = [...selectValue];
for (let i = 0; i < selectValueNew.length;) {
if (!selectOptionsValueListNew.includes(selectValueNew[i])) {
selectValueNew.splice(i, 1);
} else {
i++;
}
}
memorySession.rangeFlagList.forEach(item => {
if (!selectOptionsValueListOld.includes(item.uid)) {
selectValueNew.push(item.uid);
}
});
setSelectOptions(memorySession.rangeFlagList.map(item => ({ value: item.uid, label: item.description })));
setSelectValue(selectValueNew);
};
useEffect(() => {
updateSelectData();
}, [memorySession.rangeFlagList]);
useEffect(() => {
getTimelineOffsetByKey();
}, [memorySession.hostCondition.value, memorySession.rankCondition.value]);
useEffect(() => {
const selectedRangeFlag: RangeFlagList[] = [];
memorySession.rangeFlagList.forEach(item => {
if (selectValue.includes(item.uid)) {
selectedRangeFlag.push({
...item,
timeStamp: item.timeStamp + memorySession.timelineOffset - rankOffsetNs,
anotherTimeStamp: item.anotherTimeStamp + memorySession.timelineOffset - rankOffsetNs,
});
}
});
callback(selectedRangeFlag);
}, [selectValue.length, memorySession.rangeFlagList, memorySession.timelineOffset, rankOffsetNs]);
return <FlexDiv>
<Label name={t('AreaMark')} />
<Select
mode="multiple"
allowClear
style={{ width: 600 }}
value={selectValue}
onChange={handleChange}
options={selectOptions}
maxTagTextLength={10}
maxTagCount="responsive"
showSearch={true}
optionFilterProp="label"
/>
</FlexDiv>;
});
export default DynamicLineChart;