Skip to content

Commit 93ac83f

Browse files
committed
[pinpoint-apm#12240] Heatmap > realtime
1 parent 816412c commit 93ac83f

File tree

13 files changed

+318
-64
lines changed

13 files changed

+318
-64
lines changed

web-frontend/src/main/v3/packages/ui/src/components/Heatmap/Heatmap.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { ErrorBoundary, ChartSkeleton } from '@pinpoint-fe/ui';
33
import { HeatmapFetcher, HeatmapFetcherProps } from './HeatmapFetcher';
4+
import { HeatmapRealtimeFetcher } from './HeatmapRealtimeFetcher';
45

56
export interface HeatmapProps extends HeatmapFetcherProps {
67
realtime?: boolean;
@@ -10,7 +11,7 @@ export const Heatmap = ({ realtime = false, ...props }: HeatmapProps) => {
1011
return (
1112
<ErrorBoundary>
1213
<React.Suspense fallback={<ChartSkeleton />}>
13-
{realtime ? <div>Realtime...</div> : <HeatmapFetcher {...props} />}
14+
{realtime ? <HeatmapRealtimeFetcher {...props} /> : <HeatmapFetcher {...props} />}
1415
</React.Suspense>
1516
</ErrorBoundary>
1617
);

web-frontend/src/main/v3/packages/ui/src/components/Heatmap/HeatmapFetcher.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,16 @@ import {
55
useStoragedAxisY,
66
useGetHeatmapAppData,
77
} from '@pinpoint-fe/ui/src/hooks';
8-
import {
9-
APP_SETTING_KEYS,
10-
ApplicationType,
11-
GetHeatmapAppData,
12-
GetServerMap,
13-
} from '@pinpoint-fe/ui/src/constants';
14-
15-
export interface HeatmapFetcherHandle {
16-
handleCaptureImage: () => Promise<void>;
17-
}
8+
import { APP_SETTING_KEYS, GetHeatmapAppData } from '@pinpoint-fe/ui/src/constants';
189

1910
const DefaultAxisY = [0, 10000];
2011

2112
export type HeatmapFetcherProps = {
22-
nodeData: GetServerMap.NodeData | ApplicationType;
2313
agentId?: string;
24-
} & Pick<HeatmapChartCoreProps, 'toolbarOption'>;
14+
} & Pick<HeatmapChartCoreProps, 'toolbarOption' | 'nodeData'>;
2515

2616
export const HeatmapFetcher = ({ nodeData, agentId, ...props }: HeatmapFetcherProps) => {
2717
const { dateRange } = useServerMapSearchParameters();
28-
2918
const [y] = useStoragedAxisY(APP_SETTING_KEYS.HEATMAP_Y_AXIS_MIN_MAX, DefaultAxisY);
3019

3120
const [parameters, setParameters] = React.useState<GetHeatmapAppData.Parameters>({
@@ -56,5 +45,5 @@ export const HeatmapFetcher = ({ nodeData, agentId, ...props }: HeatmapFetcherPr
5645
agentId,
5746
]);
5847

59-
return <HeatmapChartCore isLoading={isLoading} data={data} {...props} />;
48+
return <HeatmapChartCore isLoading={isLoading} data={data} nodeData={nodeData} {...props} />;
6049
};
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import React from 'react';
2+
import HeatmapChartCore, { HeatmapChartCoreProps } from './core/HeatmapChartCore';
3+
import {
4+
useServerMapSearchParameters,
5+
useStoragedAxisY,
6+
useGetHeatmapAppData,
7+
} from '@pinpoint-fe/ui/src/hooks';
8+
import {
9+
APP_SETTING_KEYS,
10+
ApplicationType,
11+
GetHeatmapAppData,
12+
GetServerMap,
13+
} from '@pinpoint-fe/ui/src/constants';
14+
import { subMinutes, subSeconds, addSeconds } from 'date-fns';
15+
16+
const DefaultAxisY = [0, 10000];
17+
18+
// (from 계산을 위해) 가장 가까운 10초 단위로 올림한 뒤에 30초를 빼는 함수
19+
function ceilTo10SecAndSubtract30(date: Date): Date {
20+
const seconds = date.getSeconds();
21+
const remainder = seconds % 10;
22+
const toAdd = remainder === 0 ? 0 : 10 - remainder;
23+
24+
const roundedUp = addSeconds(date, toAdd);
25+
const result = subSeconds(roundedUp, 30);
26+
27+
// milliseconds 제거
28+
result.setMilliseconds(0);
29+
return result;
30+
}
31+
32+
export type HeatmapRealtimeFetcherProps = {
33+
nodeData: GetServerMap.NodeData | ApplicationType;
34+
agentId?: string;
35+
} & Pick<HeatmapChartCoreProps, 'toolbarOption'>;
36+
37+
export const HeatmapRealtimeFetcher = ({
38+
nodeData,
39+
agentId,
40+
...props
41+
}: HeatmapRealtimeFetcherProps) => {
42+
const now = new Date();
43+
const lastToDate = now; // 마지막 호출 시간을 기억해서 from~to 보다 전이면 "lastToDate ~ new Date()" 로 호출
44+
const [realtimeDateRange, setRealtimeDateRange] = React.useState({
45+
from: subMinutes(now, 5),
46+
to: now,
47+
});
48+
const [realtimeData, setRealtimeData] = React.useState<GetHeatmapAppData.Response>();
49+
50+
React.useEffect(() => {
51+
const interval = setInterval(() => {
52+
const now = new Date();
53+
setRealtimeDateRange({
54+
from: ceilTo10SecAndSubtract30(now),
55+
to: now,
56+
});
57+
}, 5000);
58+
59+
return () => {
60+
clearInterval(interval);
61+
};
62+
}, []);
63+
64+
React.useEffect(() => {
65+
const now = new Date();
66+
setRealtimeDateRange({
67+
from: subMinutes(now, 5),
68+
to: now,
69+
});
70+
}, [nodeData?.applicationName]);
71+
72+
const [y] = useStoragedAxisY(APP_SETTING_KEYS.HEATMAP_Y_AXIS_MIN_MAX, DefaultAxisY);
73+
74+
const [parameters, setParameters] = React.useState<GetHeatmapAppData.Parameters>({
75+
applicationName: nodeData?.applicationName,
76+
from: realtimeDateRange.from.getTime(),
77+
to: realtimeDateRange.to.getTime(),
78+
minElapsedTime: Number(y?.[0]) || DefaultAxisY[0],
79+
maxElapsedTime: Number(y?.[1]) || DefaultAxisY[1],
80+
agentId: agentId,
81+
});
82+
const { data, isLoading } = useGetHeatmapAppData(parameters);
83+
84+
React.useEffect(() => {
85+
setParameters({
86+
applicationName: nodeData?.applicationName,
87+
from: realtimeDateRange.from.getTime(),
88+
to: realtimeDateRange.to.getTime(),
89+
minElapsedTime: Number(y?.[0]) || DefaultAxisY[0],
90+
maxElapsedTime: Number(y?.[1]) || DefaultAxisY[1],
91+
agentId: agentId,
92+
});
93+
}, [
94+
realtimeDateRange.from.getTime(),
95+
realtimeDateRange.to.getTime(),
96+
y?.[0],
97+
y?.[1],
98+
nodeData?.applicationName,
99+
agentId,
100+
]);
101+
102+
React.useEffect(() => {
103+
setRealtimeData((prevData) => {
104+
if (!prevData) {
105+
return data;
106+
} else {
107+
let newHeatmapData: GetHeatmapAppData.HeatmapData[] = [];
108+
const preHeatmapData = prevData.heatmapData?.sort((a, b) => a.timestamp - b.timestamp);
109+
const indexOfFirstData = preHeatmapData.findIndex(
110+
(item) => item.timestamp === data?.heatmapData[data?.heatmapData?.length - 1].timestamp,
111+
);
112+
113+
if (indexOfFirstData === -1) {
114+
newHeatmapData = preHeatmapData
115+
.slice(data?.size?.width)
116+
.concat(data?.heatmapData?.sort((a, b) => a.timestamp - b.timestamp) || []);
117+
} else {
118+
const n = preHeatmapData.length - indexOfFirstData; // 뒤에서 n번째 데이터부터 새로 받았음;
119+
newHeatmapData = preHeatmapData
120+
.slice((data?.size?.width || 3) - n, indexOfFirstData)
121+
.concat(data?.heatmapData?.sort((a, b) => a.timestamp - b.timestamp) || []);
122+
}
123+
124+
const totalSuccessCount = newHeatmapData?.reduce((total, heatmapItem) => {
125+
const sumInCellList = heatmapItem?.cellDataList?.reduce((cellTotal, cell) => {
126+
return cellTotal + cell?.successCount;
127+
}, 0);
128+
return total + sumInCellList;
129+
}, 0);
130+
131+
const totalFailCount = newHeatmapData?.reduce((total, heatmapItem) => {
132+
const sumInCellList = heatmapItem?.cellDataList?.reduce((cellTotal, cell) => {
133+
return cellTotal + cell?.failCount;
134+
}, 0);
135+
return total + sumInCellList;
136+
}, 0);
137+
138+
return {
139+
...prevData,
140+
heatmapData: newHeatmapData,
141+
summary: {
142+
totalSuccessCount,
143+
totalFailCount,
144+
},
145+
};
146+
}
147+
});
148+
}, [data]);
149+
150+
return (
151+
<HeatmapChartCore
152+
isRealtime={true}
153+
isLoading={isLoading && !realtimeData}
154+
nodeData={nodeData}
155+
data={realtimeData}
156+
{...props}
157+
/>
158+
);
159+
};

web-frontend/src/main/v3/packages/ui/src/components/Heatmap/core/HeatmapChart.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const HeatmapColor = {
3838
};
3939

4040
type HeatmapChartProps = {
41+
isRealtime?: boolean;
4142
data?: GetHeatmapAppData.Response;
4243
setting: HeatmapSettingType;
4344
onDragEnd?: (
@@ -74,7 +75,7 @@ function visualMapFormatter(value: string, range: [number, number] | undefined)
7475
}
7576

7677
const HeatmapChart = React.forwardRef(
77-
({ data, setting, onDragEnd }: HeatmapChartProps, ref: React.Ref<HTMLDivElement>) => {
78+
({ isRealtime, data, setting, onDragEnd }: HeatmapChartProps, ref: React.Ref<HTMLDivElement>) => {
7879
const chartRef = React.useRef<ReactEChartsCore>(null);
7980

8081
const [isMouseDown, setIsMouseDown] = React.useState(false);
@@ -100,6 +101,7 @@ const HeatmapChart = React.forwardRef(
100101
};
101102
}, []);
102103

104+
// realtime일 경우 사용하지 않음
103105
const maxCount = React.useMemo(() => {
104106
let success = 0;
105107
let fail = 0;
@@ -340,7 +342,7 @@ const HeatmapChart = React.forwardRef(
340342
{
341343
id: 'success',
342344
min: 0,
343-
max: maxCount.success,
345+
max: isRealtime ? 5000 : maxCount.success,
344346
calculable: true,
345347
seriesIndex: 0,
346348
orient: 'horizontal',
@@ -360,7 +362,7 @@ const HeatmapChart = React.forwardRef(
360362
{
361363
id: 'fail',
362364
min: 0,
363-
max: maxCount.fail,
365+
max: isRealtime ? 100 : maxCount.fail,
364366
calculable: true,
365367
seriesIndex: 1,
366368
orient: 'horizontal',

web-frontend/src/main/v3/packages/ui/src/components/Heatmap/core/HeatmapChartCore.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ import {
55
BASE_PATH,
66
colors,
77
GetHeatmapAppData,
8+
GetServerMap,
9+
ApplicationType,
810
} from '@pinpoint-fe/ui/src/constants';
911
import HeatmapChart from './HeatmapChart';
1012
import {
1113
HelpPopover,
1214
cn,
1315
convertParamsToQueryString,
16+
getFormattedDateRange,
1417
getHeatmapFullScreenPath,
18+
getHeatmapFullScreenRealtimePath,
19+
getScatterFullScreenRealtimePath,
1520
getTransactionListPath,
1621
getTranscationListQueryString,
1722
useServerMapSearchParameters,
@@ -35,7 +40,9 @@ export const HeatmapColor = {
3540
};
3641

3742
export type HeatmapChartCoreProps = {
43+
isRealtime?: boolean;
3844
isLoading?: boolean;
45+
nodeData: GetServerMap.NodeData | ApplicationType;
3946
data?: GetHeatmapAppData.Response;
4047
agentId?: string;
4148
toolbarOption?: {
@@ -45,21 +52,34 @@ export type HeatmapChartCoreProps = {
4552
};
4653
};
4754

48-
const HeatmapChartCore = ({ isLoading, data, agentId, toolbarOption }: HeatmapChartCoreProps) => {
55+
const HeatmapChartCore = ({
56+
isRealtime,
57+
isLoading,
58+
nodeData,
59+
data,
60+
agentId,
61+
toolbarOption,
62+
}: HeatmapChartCoreProps) => {
4963
const chartContainerRef = React.useRef<HTMLDivElement>(null);
5064

51-
const { searchParameters, application } = useServerMapSearchParameters();
65+
const { dateRange, searchParameters, application } = useServerMapSearchParameters();
5266
const [showSetting, setShowSetting] = React.useState(false);
5367
const [isCapturingImage, setIsCapturingImage] = React.useState(false);
5468

5569
const [y, setY] = useStoragedAxisY(APP_SETTING_KEYS.HEATMAP_Y_AXIS_MIN_MAX, [0, 10000]);
5670

5771
const handleExpand = () => {
58-
// if (realtime) {
59-
// return;
60-
// }
72+
if (isRealtime) {
73+
window.open(
74+
`${BASE_PATH}${getHeatmapFullScreenRealtimePath(nodeData)}?${convertParamsToQueryString({
75+
agentId,
76+
})}`,
77+
'_blank',
78+
);
79+
return;
80+
}
6181
window.open(
62-
`${BASE_PATH}${getHeatmapFullScreenPath(application)}?${convertParamsToQueryString({
82+
`${BASE_PATH}${getHeatmapFullScreenPath(nodeData)}?${convertParamsToQueryString({
6383
from: searchParameters.from,
6484
to: searchParameters.to,
6585
})}`,
@@ -97,7 +117,7 @@ const HeatmapChartCore = ({ isLoading, data, agentId, toolbarOption }: HeatmapCh
97117
window.open(
98118
`${BASE_PATH}${getTransactionListPath(
99119
application,
100-
searchParameters,
120+
isRealtime ? getFormattedDateRange(dateRange) : searchParameters,
101121
)}&${getTranscationListQueryString({
102122
...data,
103123
checkedLegends,
@@ -129,6 +149,7 @@ const HeatmapChartCore = ({ isLoading, data, agentId, toolbarOption }: HeatmapCh
129149
<HeatmapChart
130150
ref={chartContainerRef}
131151
data={data}
152+
isRealtime={isRealtime}
132153
setting={{
133154
yMin: y[0],
134155
yMax: y[1],

0 commit comments

Comments
 (0)