통계 화면, drilldown 기능 구현

This commit is contained in:
pksup
2026-01-21 15:41:18 +09:00
parent 94f7bf8510
commit d4bdd39450
5 changed files with 448 additions and 27 deletions
@@ -10,7 +10,7 @@
%> %>
<html> <html>
<head> <head>
<title>API 시간별 통계</title> <title>시간별 통계 조회</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<jsp:include page="/jsp/common/include/css.jsp"/> <jsp:include page="/jsp/common/include/css.jsp"/>
<style> <style>
@@ -18,6 +18,10 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 15px; margin-bottom: 15px;
gap: 10px;
}
.chart-container.full-width {
display: block;
} }
.chart { .chart {
width: 49%; width: 49%;
@@ -25,6 +29,10 @@
border: 1px solid #ddd; border: 1px solid #ddd;
background: #fff; background: #fff;
} }
.chart.full-width {
width: 100%;
height: 250px;
}
</style> </style>
<jsp:include page="/jsp/common/include/script.jsp"/> <jsp:include page="/jsp/common/include/script.jsp"/>
<script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script> <script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script>
@@ -33,7 +41,7 @@
var url_view = '<c:url value="/onl/kjb/statistics/apiStatsHourMan.view"/>'; var url_view = '<c:url value="/onl/kjb/statistics/apiStatsHourMan.view"/>';
var url_minute_view = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.view"/>'; var url_minute_view = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.view"/>';
var callChart, respChart; var totalDonutChart, seq900DonutChart, callChart, respChart;
function numberFormatter(cellvalue, options, rowObject) { function numberFormatter(cellvalue, options, rowObject) {
if (cellvalue == null || cellvalue == '') return '0'; if (cellvalue == null || cellvalue == '') return '0';
@@ -46,9 +54,131 @@
} }
function initCharts() { function initCharts() {
totalDonutChart = echarts.init(document.getElementById('totalDonutChart'));
seq900DonutChart = echarts.init(document.getElementById('seq900DonutChart'));
callChart = echarts.init(document.getElementById('callChart')); callChart = echarts.init(document.getElementById('callChart'));
respChart = echarts.init(document.getElementById('respChart')); respChart = echarts.init(document.getElementById('respChart'));
// 총건수 도넛 차트
var totalDonutOption = {
title: {
text: '총 건수 분포',
left: 'center',
top: 10,
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'horizontal',
bottom: 10,
data: ['성공', 'Timeout', '시스템오류', '업무오류']
},
series: [{
name: '총건수',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: true,
label: {
show: true,
formatter: '{b}: {c}'
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
}
},
labelLine: {
show: true
},
data: [
{ value: 0, name: '성공', itemStyle: { color: '#91CC75' } },
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } },
{ value: 0, name: '업무오류', itemStyle: { color: '#FC8452' } }
]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}]
};
totalDonutChart.setOption(totalDonutOption);
// Seq900 도넛 차트
var seq900DonutOption = {
title: {
text: 'Seq900 에러 분포',
left: 'center',
top: 10,
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'horizontal',
bottom: 10,
data: ['Timeout', '시스템오류', '업무오류']
},
series: [{
name: 'Seq900 에러',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: true,
label: {
show: true,
formatter: '{b}: {c}'
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
}
},
labelLine: {
show: true
},
data: [
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } },
{ value: 0, name: '업무오류', itemStyle: { color: '#FC8452' } }
]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}]
};
seq900DonutChart.setOption(seq900DonutOption);
// 호출량 차트
var callOption = { var callOption = {
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } }, title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } },
tooltip: { tooltip: {
@@ -60,13 +190,16 @@
xAxis: { type: 'category', boundaryGap: false, data: [] }, xAxis: { type: 'category', boundaryGap: false, data: [] },
yAxis: { type: 'value', minInterval: 1 }, yAxis: { type: 'value', minInterval: 1 },
series: [ series: [
{ name: '성공', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#5470C6' }, data: [] }, { name: '성공', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#5470C6' }, data: [] },
{ name: 'Timeout', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FAC858' }, data: [] }, { name: 'Timeout', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FAC858' }, data: [] },
{ name: '시스템오류', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#EE6666' }, data: [] }, { name: '시스템오류', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#EE6666' }, data: [] },
{ name: '업무오류', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FC8452' }, data: [] } { name: '업무오류', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FC8452' }, data: [] }
] ]
}; };
callChart.setOption(callOption);
// 응답시간 차트
var respOption = { var respOption = {
title: { text: '응답시간 추이 (ms)', left: 'center', textStyle: { fontSize: 14 } }, title: { text: '응답시간 추이 (ms)', left: 'center', textStyle: { fontSize: 14 } },
tooltip: { tooltip: {
@@ -78,13 +211,12 @@
xAxis: { type: 'category', boundaryGap: false, data: [] }, xAxis: { type: 'category', boundaryGap: false, data: [] },
yAxis: { type: 'value' }, yAxis: { type: 'value' },
series: [ series: [
{ name: 'P95', type: 'line', itemStyle: { color: '#EE6666' }, lineStyle: { type: 'dashed' }, data: [] }, { name: 'P95', type: 'line', smooth: true, itemStyle: { color: '#EE6666' }, lineStyle: { type: 'dashed' }, data: [] },
{ name: 'P50', type: 'line', itemStyle: { color: '#5470C6' }, data: [] }, { name: 'P50', type: 'line', smooth: true, itemStyle: { color: '#5470C6' }, data: [] },
{ name: '평균', type: 'line', itemStyle: { color: '#91CC75' }, data: [] } { name: '평균', type: 'line', smooth: true, itemStyle: { color: '#91CC75' }, data: [] }
] ]
}; };
callChart.setOption(callOption);
respChart.setOption(respOption); respChart.setOption(respOption);
// 드릴다운 이벤트 (시간 클릭 시 분단위 화면으로 이동) // 드릴다운 이벤트 (시간 클릭 시 분단위 화면으로 이동)
@@ -101,18 +233,34 @@
// rawData에서 실제 statTime 가져오기 (yyyyMMddHH00 형식) // rawData에서 실제 statTime 가져오기 (yyyyMMddHH00 형식)
var statTime = chart.rawData[dataIndex].statTime; var statTime = chart.rawData[dataIndex].statTime;
var date = statTime.substring(0, 4) + '-' + statTime.substring(4, 6) + '-' + statTime.substring(6, 8); var year = statTime.substring(0, 4);
var hour = statTime.substring(8, 10); var month = statTime.substring(4, 6);
var startTime = hour + ':00'; var day = statTime.substring(6, 8);
var endTime = hour + ':59'; var hour = parseInt(statTime.substring(8, 10));
var params = '?searchStartDate=' + date + '&searchStartTime=' + startTime var startDate = year + '-' + month + '-' + day;
+ '&searchEndDate=' + date + '&searchEndTime=' + endTime; var startTime = (hour < 10 ? '0' + hour : hour) + ':00';
// 1시간 후 계산 (23시인 경우 다음날 00시로 자동 계산)
var d = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), hour, 0, 0);
d.setHours(d.getHours() + 1);
var endDate = d.getFullYear() + '-' +
(d.getMonth() + 1 < 10 ? '0' : '') + (d.getMonth() + 1) + '-' +
(d.getDate() < 10 ? '0' : '') + d.getDate();
var endTime = (d.getHours() < 10 ? '0' : '') + d.getHours() + ':00';
var params = '?searchStartDate=' + startDate + '&searchStartTime=' + startTime
+ '&searchEndDate=' + endDate + '&searchEndTime=' + endTime;
params += '&menuId='+'${param.menuId}';
params += '&cmd='+'LIST';
params += '&serviceType='+'APIGW';
location.href = url_minute_view + params; location.href = url_minute_view + params;
} }
function updateCharts(data) { function updateCharts(data) {
// 시계열 차트용 변수
var times = []; var times = [];
var successData = []; var successData = [];
var timeoutData = []; var timeoutData = [];
@@ -122,10 +270,19 @@
var p50RespData = []; var p50RespData = [];
var p95RespData = []; var p95RespData = [];
// 도넛 차트용 총계 변수
var totalSuccess = 0;
var totalTimeout = 0;
var totalSystemErr = 0;
var totalBizErr = 0;
var seq900Timeout = 0;
var seq900SystemErr = 0;
var seq900BizErr = 0;
data.forEach(function(item) { data.forEach(function(item) {
// statTime: yyyyMMddHH00 -> HH시 형식으로 표시
var hour = item.statTime.substring(8, 10);
times.push(item.statTime); times.push(item.statTime);
// 시계열 차트 데이터
successData.push(item.successCnt); successData.push(item.successCnt);
timeoutData.push(item.timeoutCnt); timeoutData.push(item.timeoutCnt);
systemErrData.push(item.systemErrCnt); systemErrData.push(item.systemErrCnt);
@@ -133,8 +290,53 @@
avgRespData.push(item.avgRespTime); avgRespData.push(item.avgRespTime);
p50RespData.push(item.p50RespTime); p50RespData.push(item.p50RespTime);
p95RespData.push(item.p95RespTime); p95RespData.push(item.p95RespTime);
// 도넛 차트용 합산
totalSuccess += (item.successCnt || 0);
totalTimeout += (item.timeoutCnt || 0);
totalSystemErr += (item.systemErrCnt || 0);
totalBizErr += (item.bizErrCnt || 0);
seq900Timeout += (item.seq900TimeoutCnt || 0);
seq900SystemErr += (item.seq900SystemErrCnt || 0);
seq900BizErr += (item.seq900BizErrCnt || 0);
}); });
// 총건수 도넛 차트 업데이트
var grandTotal = totalSuccess + totalTimeout + totalSystemErr + totalBizErr;
totalDonutChart.setOption({
series: [{
data: [
{ value: totalSuccess, name: '성공' },
{ value: totalTimeout, name: 'Timeout' },
{ value: totalSystemErr, name: '시스템오류' },
{ value: totalBizErr, name: '업무오류' }
]
}],
graphic: [{
style: {
text: grandTotal.toLocaleString()
}
}]
});
// Seq900 도넛 차트 업데이트
var seq900Total = seq900Timeout + seq900SystemErr + seq900BizErr;
seq900DonutChart.setOption({
series: [{
data: [
{ value: seq900Timeout, name: 'Timeout' },
{ value: seq900SystemErr, name: '시스템오류' },
{ value: seq900BizErr, name: '업무오류' }
]
}],
graphic: [{
style: {
text: seq900Total.toLocaleString()
}
}]
});
// 호출량 차트 업데이트
callChart.setOption({ callChart.setOption({
xAxis: { data: times.map(function(t) { return t.substring(8, 10) + '시'; }) }, xAxis: { data: times.map(function(t) { return t.substring(8, 10) + '시'; }) },
series: [ series: [
@@ -145,6 +347,7 @@
] ]
}); });
// 응답시간 차트 업데이트
respChart.setOption({ respChart.setOption({
xAxis: { data: times.map(function(t) { return t.substring(8, 10) + '시'; }) }, xAxis: { data: times.map(function(t) { return t.substring(8, 10) + '시'; }) },
series: [ series: [
@@ -214,6 +417,7 @@
'통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID', '통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID',
'Inbound Adapter', 'Outbound Adapter', 'Inbound Adapter', 'Outbound Adapter',
'총건수', '성공', 'Timeout', '시스템오류', '업무오류', '총건수', '성공', 'Timeout', '시스템오류', '업무오류',
'Seq900 Timeout', 'Seq900 시스템오류', 'Seq900 업무오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)', 'P50(ms)', 'P95(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)', 'P50(ms)', 'P95(ms)'
], ],
colModel: [ colModel: [
@@ -229,6 +433,9 @@
{ name: 'timeoutCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false }, { name: 'timeoutCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'systemErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false }, { name: 'systemErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'bizErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false }, { name: 'bizErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'seq900TimeoutCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'seq900SystemErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'seq900BizErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'avgRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false }, { name: 'avgRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
{ name: 'minRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false }, { name: 'minRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
{ name: 'maxRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false }, { name: 'maxRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
@@ -281,6 +488,8 @@
// 윈도우 리사이즈 시 차트 리사이즈 // 윈도우 리사이즈 시 차트 리사이즈
$(window).resize(function() { $(window).resize(function() {
if (totalDonutChart) totalDonutChart.resize();
if (seq900DonutChart) seq900DonutChart.resize();
if (callChart) callChart.resize(); if (callChart) callChart.resize();
if (respChart) respChart.resize(); if (respChart) respChart.resize();
}); });
@@ -344,6 +553,12 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<!-- 도넛 차트 (상단 50%씩) -->
<div class="chart-container">
<div id="totalDonutChart" class="chart"></div>
<div id="seq900DonutChart" class="chart"></div>
</div>
<!-- 호출량 및 응답시간 차트 (하단 50%씩) -->
<div class="chart-container"> <div class="chart-container">
<div id="callChart" class="chart"></div> <div id="callChart" class="chart"></div>
<div id="respChart" class="chart"></div> <div id="respChart" class="chart"></div>
@@ -10,7 +10,7 @@
%> %>
<html> <html>
<head> <head>
<title>API 분단위 통계</title> <title>분별 통계 조회</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<jsp:include page="/jsp/common/include/css.jsp"/> <jsp:include page="/jsp/common/include/css.jsp"/>
<style> <style>
@@ -18,6 +18,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 15px; margin-bottom: 15px;
gap: 10px;
} }
.chart { .chart {
width: 49%; width: 49%;
@@ -32,7 +33,7 @@
var url = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.json"/>'; var url = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.json"/>';
var url_view = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.view"/>'; var url_view = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.view"/>';
var callChart, respChart; var totalDonutChart, seq900DonutChart, callChart, respChart;
function numberFormatter(cellvalue, options, rowObject) { function numberFormatter(cellvalue, options, rowObject) {
if (cellvalue == null || cellvalue == '') return '0'; if (cellvalue == null || cellvalue == '') return '0';
@@ -45,9 +46,131 @@
} }
function initCharts() { function initCharts() {
totalDonutChart = echarts.init(document.getElementById('totalDonutChart'));
seq900DonutChart = echarts.init(document.getElementById('seq900DonutChart'));
callChart = echarts.init(document.getElementById('callChart')); callChart = echarts.init(document.getElementById('callChart'));
respChart = echarts.init(document.getElementById('respChart')); respChart = echarts.init(document.getElementById('respChart'));
// 총건수 도넛 차트
var totalDonutOption = {
title: {
text: '총 건수 분포',
left: 'center',
top: 10,
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'horizontal',
bottom: 10,
data: ['성공', 'Timeout', '시스템오류', '업무오류']
},
series: [{
name: '총건수',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: true,
label: {
show: true,
formatter: '{b}: {c}'
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
}
},
labelLine: {
show: true
},
data: [
{ value: 0, name: '성공', itemStyle: { color: '#91CC75' } },
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } },
{ value: 0, name: '업무오류', itemStyle: { color: '#FC8452' } }
]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}]
};
totalDonutChart.setOption(totalDonutOption);
// Seq900 도넛 차트
var seq900DonutOption = {
title: {
text: 'Seq900 에러 분포',
left: 'center',
top: 10,
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'horizontal',
bottom: 10,
data: ['Timeout', '시스템오류', '업무오류']
},
series: [{
name: 'Seq900 에러',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: true,
label: {
show: true,
formatter: '{b}: {c}'
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
}
},
labelLine: {
show: true
},
data: [
{ value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
{ value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } },
{ value: 0, name: '업무오류', itemStyle: { color: '#FC8452' } }
]
}],
graphic: [{
type: 'text',
left: 'center',
top: 'center',
style: {
text: '0',
textAlign: 'center',
fill: '#333',
fontSize: 24,
fontWeight: 'bold'
}
}]
};
seq900DonutChart.setOption(seq900DonutOption);
// 호출량 차트
var callOption = { var callOption = {
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } }, title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } },
tooltip: { tooltip: {
@@ -63,10 +186,10 @@
{ start: 0, end: 100 } { start: 0, end: 100 }
], ],
series: [ series: [
{ name: '성공', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#5470C6' }, data: [], showSymbol: false }, { name: '성공', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#5470C6' }, data: [], showSymbol: false },
{ name: 'Timeout', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FAC858' }, data: [], showSymbol: false }, { name: 'Timeout', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FAC858' }, data: [], showSymbol: false },
{ name: '시스템오류', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#EE6666' }, data: [], showSymbol: false }, { name: '시스템오류', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#EE6666' }, data: [], showSymbol: false },
{ name: '업무오류', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FC8452' }, data: [], showSymbol: false } { name: '업무오류', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FC8452' }, data: [], showSymbol: false }
] ]
}; };
@@ -85,9 +208,9 @@
{ start: 0, end: 100 } { start: 0, end: 100 }
], ],
series: [ series: [
{ name: 'P95', type: 'line', itemStyle: { color: '#EE6666' }, lineStyle: { type: 'dashed' }, data: [], showSymbol: false }, { name: 'P95', type: 'line', smooth: true, itemStyle: { color: '#EE6666' }, lineStyle: { type: 'dashed' }, data: [], showSymbol: false },
{ name: 'P50', type: 'line', itemStyle: { color: '#5470C6' }, data: [], showSymbol: false }, { name: 'P50', type: 'line', smooth: true, itemStyle: { color: '#5470C6' }, data: [], showSymbol: false },
{ name: '평균', type: 'line', itemStyle: { color: '#91CC75' }, data: [], showSymbol: false } { name: '평균', type: 'line', smooth: true, itemStyle: { color: '#91CC75' }, data: [], showSymbol: false }
] ]
}; };
@@ -96,6 +219,7 @@
} }
function updateCharts(data) { function updateCharts(data) {
// 시계열 차트용 변수
var times = []; var times = [];
var successData = []; var successData = [];
var timeoutData = []; var timeoutData = [];
@@ -105,10 +229,21 @@
var p50RespData = []; var p50RespData = [];
var p95RespData = []; var p95RespData = [];
// 도넛 차트용 총계 변수
var totalSuccess = 0;
var totalTimeout = 0;
var totalSystemErr = 0;
var totalBizErr = 0;
var seq900Timeout = 0;
var seq900SystemErr = 0;
var seq900BizErr = 0;
data.forEach(function(item) { data.forEach(function(item) {
// statTime: yyyyMMddHHmm -> HH:mm 형식으로 표시 // statTime: yyyyMMddHHmm -> HH:mm 형식으로 표시
var time = item.statTime.substring(8, 10) + ':' + item.statTime.substring(10, 12); var time = item.statTime.substring(8, 10) + ':' + item.statTime.substring(10, 12);
times.push(time); times.push(time);
// 시계열 차트 데이터
successData.push(item.successCnt); successData.push(item.successCnt);
timeoutData.push(item.timeoutCnt); timeoutData.push(item.timeoutCnt);
systemErrData.push(item.systemErrCnt); systemErrData.push(item.systemErrCnt);
@@ -116,8 +251,53 @@
avgRespData.push(item.avgRespTime); avgRespData.push(item.avgRespTime);
p50RespData.push(item.p50RespTime); p50RespData.push(item.p50RespTime);
p95RespData.push(item.p95RespTime); p95RespData.push(item.p95RespTime);
// 도넛 차트용 합산
totalSuccess += (item.successCnt || 0);
totalTimeout += (item.timeoutCnt || 0);
totalSystemErr += (item.systemErrCnt || 0);
totalBizErr += (item.bizErrCnt || 0);
seq900Timeout += (item.seq900TimeoutCnt || 0);
seq900SystemErr += (item.seq900SystemErrCnt || 0);
seq900BizErr += (item.seq900BizErrCnt || 0);
}); });
// 총건수 도넛 차트 업데이트
var grandTotal = totalSuccess + totalTimeout + totalSystemErr + totalBizErr;
totalDonutChart.setOption({
series: [{
data: [
{ value: totalSuccess, name: '성공' },
{ value: totalTimeout, name: 'Timeout' },
{ value: totalSystemErr, name: '시스템오류' },
{ value: totalBizErr, name: '업무오류' }
]
}],
graphic: [{
style: {
text: grandTotal.toLocaleString()
}
}]
});
// Seq900 도넛 차트 업데이트
var seq900Total = seq900Timeout + seq900SystemErr + seq900BizErr;
seq900DonutChart.setOption({
series: [{
data: [
{ value: seq900Timeout, name: 'Timeout' },
{ value: seq900SystemErr, name: '시스템오류' },
{ value: seq900BizErr, name: '업무오류' }
]
}],
graphic: [{
style: {
text: seq900Total.toLocaleString()
}
}]
});
// 호출량 차트 업데이트
callChart.setOption({ callChart.setOption({
xAxis: { data: times }, xAxis: { data: times },
series: [ series: [
@@ -128,6 +308,7 @@
] ]
}); });
// 응답시간 차트 업데이트
respChart.setOption({ respChart.setOption({
xAxis: { data: times }, xAxis: { data: times },
series: [ series: [
@@ -244,6 +425,7 @@
'통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID', '통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID',
'Inbound Adapter', 'Outbound Adapter', 'Inbound Adapter', 'Outbound Adapter',
'총건수', '성공', 'Timeout', '시스템오류', '업무오류', '총건수', '성공', 'Timeout', '시스템오류', '업무오류',
'Seq900 Timeout', 'Seq900 시스템오류', 'Seq900 업무오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)', 'P50(ms)', 'P95(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)', 'P50(ms)', 'P95(ms)'
], ],
colModel: [ colModel: [
@@ -259,6 +441,9 @@
{ name: 'timeoutCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false }, { name: 'timeoutCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'systemErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false }, { name: 'systemErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'bizErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false }, { name: 'bizErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'seq900TimeoutCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'seq900SystemErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'seq900BizErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'avgRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false }, { name: 'avgRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
{ name: 'minRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false }, { name: 'minRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
{ name: 'maxRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false }, { name: 'maxRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
@@ -342,6 +527,8 @@
// 윈도우 리사이즈 시 차트 리사이즈 // 윈도우 리사이즈 시 차트 리사이즈
$(window).resize(function() { $(window).resize(function() {
if (totalDonutChart) totalDonutChart.resize();
if (seq900DonutChart) seq900DonutChart.resize();
if (callChart) callChart.resize(); if (callChart) callChart.resize();
if (respChart) respChart.resize(); if (respChart) respChart.resize();
}); });
@@ -409,6 +596,12 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<!-- 도넛 차트 (상단 50%씩) -->
<div class="chart-container">
<div id="totalDonutChart" class="chart"></div>
<div id="seq900DonutChart" class="chart"></div>
</div>
<!-- 호출량 및 응답시간 차트 (하단 50%씩) -->
<div class="chart-container"> <div class="chart-container">
<div id="callChart" class="chart"></div> <div id="callChart" class="chart"></div>
<div id="respChart" class="chart"></div> <div id="respChart" class="chart"></div>
@@ -94,6 +94,9 @@ public class ApiStatsHourService
q.timeoutCnt.sum(), q.timeoutCnt.sum(),
q.systemErrCnt.sum(), q.systemErrCnt.sum(),
q.bizErrCnt.sum(), q.bizErrCnt.sum(),
q.seq900TimeoutCnt.sum(),
q.seq900SystemErrCnt.sum(),
q.seq900BizErrCnt.sum(),
q.avgRespTime.avg(), q.avgRespTime.avg(),
q.p50RespTime.avg(), q.p50RespTime.avg(),
q.p95RespTime.avg())) q.p95RespTime.avg()))
@@ -96,6 +96,9 @@ public class ApiStatsMinuteService
q.timeoutCnt.sum(), q.timeoutCnt.sum(),
q.systemErrCnt.sum(), q.systemErrCnt.sum(),
q.bizErrCnt.sum(), q.bizErrCnt.sum(),
q.seq900TimeoutCnt.sum(),
q.seq900SystemErrCnt.sum(),
q.seq900BizErrCnt.sum(),
q.avgRespTime.avg(), q.avgRespTime.avg(),
q.p50RespTime.avg(), q.p50RespTime.avg(),
q.p95RespTime.avg())) q.p95RespTime.avg()))
@@ -21,6 +21,9 @@ public class ApiStatsChartUI {
private Long timeoutCnt; private Long timeoutCnt;
private Long systemErrCnt; private Long systemErrCnt;
private Long bizErrCnt; private Long bizErrCnt;
private Long seq900TimeoutCnt;
private Long seq900SystemErrCnt;
private Long seq900BizErrCnt;
// 응답시간 메트릭 (AVG) // 응답시간 메트릭 (AVG)
private BigDecimal avgRespTime; private BigDecimal avgRespTime;
@@ -30,6 +33,7 @@ public class ApiStatsChartUI {
// QueryDSL Projections용 생성자 // QueryDSL Projections용 생성자
public ApiStatsChartUI(LocalDateTime statTime, Long totalCnt, Long successCnt, public ApiStatsChartUI(LocalDateTime statTime, Long totalCnt, Long successCnt,
Long timeoutCnt, Long systemErrCnt, Long bizErrCnt, Long timeoutCnt, Long systemErrCnt, Long bizErrCnt,
Long seq900TimeoutCnt, Long seq900SystemErrCnt, Long seq900BizErrCnt,
Double avgRespTime, Double p50RespTime, Double p95RespTime) { Double avgRespTime, Double p50RespTime, Double p95RespTime) {
this.statTime = statTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")); this.statTime = statTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
this.totalCnt = totalCnt != null ? totalCnt : 0L; this.totalCnt = totalCnt != null ? totalCnt : 0L;
@@ -37,6 +41,9 @@ public class ApiStatsChartUI {
this.timeoutCnt = timeoutCnt != null ? timeoutCnt : 0L; this.timeoutCnt = timeoutCnt != null ? timeoutCnt : 0L;
this.systemErrCnt = systemErrCnt != null ? systemErrCnt : 0L; this.systemErrCnt = systemErrCnt != null ? systemErrCnt : 0L;
this.bizErrCnt = bizErrCnt != null ? bizErrCnt : 0L; this.bizErrCnt = bizErrCnt != null ? bizErrCnt : 0L;
this.seq900TimeoutCnt = seq900TimeoutCnt != null ? seq900TimeoutCnt : 0L;
this.seq900SystemErrCnt = seq900SystemErrCnt != null ? seq900SystemErrCnt : 0L;
this.seq900BizErrCnt = seq900BizErrCnt != null ? seq900BizErrCnt : 0L;
this.avgRespTime = avgRespTime != null ? BigDecimal.valueOf(avgRespTime) : BigDecimal.ZERO; this.avgRespTime = avgRespTime != null ? BigDecimal.valueOf(avgRespTime) : BigDecimal.ZERO;
this.p50RespTime = p50RespTime != null ? BigDecimal.valueOf(p50RespTime) : BigDecimal.ZERO; this.p50RespTime = p50RespTime != null ? BigDecimal.valueOf(p50RespTime) : BigDecimal.ZERO;
this.p95RespTime = p95RespTime != null ? BigDecimal.valueOf(p95RespTime) : BigDecimal.ZERO; this.p95RespTime = p95RespTime != null ? BigDecimal.valueOf(p95RespTime) : BigDecimal.ZERO;