From 5277c79e8ceccc507099bfaf6c5cbb2357fbb56b Mon Sep 17 00:00:00 2001 From: daekuk Date: Thu, 12 Feb 2026 09:15:00 +0900 Subject: [PATCH] =?UTF-8?q?-=20=EB=B0=95=EA=B8=B0=EC=84=AD=20=EC=9D=B4?= =?UTF-8?q?=EC=82=AC=EB=8B=98=20=EC=88=98=EC=A0=95=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/ApiStatsMonitorImpl.java | 6 + .../collector/ApiStatsCollector.java | 103 ++++++++---------- .../collector/HourStatsAggregator.java | 6 +- .../collector/MinuteStatsAggregator.java | 6 +- 4 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/eactive/eai/custom/statistics/ApiStatsMonitorImpl.java b/src/main/java/com/eactive/eai/custom/statistics/ApiStatsMonitorImpl.java index a82f099..6eb2929 100644 --- a/src/main/java/com/eactive/eai/custom/statistics/ApiStatsMonitorImpl.java +++ b/src/main/java/com/eactive/eai/custom/statistics/ApiStatsMonitorImpl.java @@ -1,5 +1,7 @@ package com.eactive.eai.custom.statistics; +import java.time.LocalDateTime; + import org.springframework.stereotype.Component; import com.eactive.eai.common.message.EAIMessage; @@ -120,6 +122,10 @@ public class ApiStatsMonitorImpl implements StatisticsMonitor { private long extractResponseTime(EAIMessage msg) { long msgPssTm = msg.getMsgPssTm(); // 메시지 처리시각 long msgRcvTm = msg.getMsgRcvTm(); // 메시지 수신시각 + + if (msgPssTm == 0) { + msgPssTm = System.currentTimeMillis(); + } if (msgPssTm > 0 && msgRcvTm > 0) { return msgPssTm - msgRcvTm; diff --git a/src/main/java/com/eactive/eai/custom/statistics/collector/ApiStatsCollector.java b/src/main/java/com/eactive/eai/custom/statistics/collector/ApiStatsCollector.java index 43a23fb..4ef6080 100644 --- a/src/main/java/com/eactive/eai/custom/statistics/collector/ApiStatsCollector.java +++ b/src/main/java/com/eactive/eai/custom/statistics/collector/ApiStatsCollector.java @@ -17,7 +17,6 @@ import com.eactive.eai.data.entity.custom.statistics.ApiStatsHour; import com.eactive.eai.data.entity.custom.statistics.ApiStatsMinute; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; /** * API 통계 수집기 @@ -60,80 +59,74 @@ public class ApiStatsCollector { } /** - * 거래 이벤트 기록 (시간 지정 - 테스트용) + * 거래 이벤트 기록 (시간 지정) * * @param event 거래 이벤트 * @param eventTime 이벤트 발생 시간 */ public void recordEvent(TransactionEvent event, LocalDateTime eventTime) { - // 통계 수집 비활성화 체크 (분단위 또는 시간별 중 하나라도 활성화되어야 함) boolean minuteEnabled = apiStatsConfig.isMinuteEnabled(); boolean hourEnabled = apiStatsConfig.isHourEnabled(); if (!minuteEnabled && !hourEnabled) { - if (log.isDebugEnabled()) { - log.debug("API stats collection is disabled (both minute and hour)"); - } + log.debug("API stats collection is disabled (both minute and hour)"); return; } + if (minuteEnabled) { + recordMinuteEvent(event, eventTime); + } + + if (hourEnabled) { + recordHourEvent(event, eventTime); + } + } + + private void recordMinuteEvent(TransactionEvent event, LocalDateTime eventTime) { try { - MinuteStatsKey minuteKey = null; - MinuteStatsAggregator minuteAggregator = null; - HourStatsKey hourKey = null; - HourStatsAggregator hourAggregator = null; + if (minuteAggregators.size() >= MAX_MINUTE_AGGREGATORS) { + log.warn("Minute aggregator limit reached ({}), forcing flush of completed minutes", MAX_MINUTE_AGGREGATORS); + flushCompletedMinutes(); - // 분단위 집계 (활성화된 경우만) - if (minuteEnabled) { - // Aggregator 개수 제한 체크 (메모리 보호) if (minuteAggregators.size() >= MAX_MINUTE_AGGREGATORS) { - log.warn("Minute aggregator limit reached ({}), forcing flush of completed minutes", MAX_MINUTE_AGGREGATORS); - flushCompletedMinutes(); - - // flush 후에도 여전히 제한 초과 시 경고 - if (minuteAggregators.size() >= MAX_MINUTE_AGGREGATORS) { - log.error("Minute aggregator count still exceeds limit after flush: {}", minuteAggregators.size()); - } + log.error("Minute aggregator count still exceeds limit after flush: {}", minuteAggregators.size()); } - - minuteKey = MinuteStatsKey.from(event, eventTime); - minuteAggregator = minuteAggregators.computeIfAbsent( - minuteKey, - MinuteStatsAggregator::new - ); - minuteAggregator.recordEvent(event); } - // 시간별 집계 (활성화된 경우만) - if (hourEnabled) { - // Aggregator 개수 제한 체크 (메모리 보호) - if (hourAggregators.size() >= MAX_HOUR_AGGREGATORS) { - log.warn("Hour aggregator limit reached ({}), forcing flush of completed hours", MAX_HOUR_AGGREGATORS); - flushCompletedHours(); - - // flush 후에도 여전히 제한 초과 시 경고 - if (hourAggregators.size() >= MAX_HOUR_AGGREGATORS) { - log.error("Hour aggregator count still exceeds limit after flush: {}", hourAggregators.size()); - } - } - - hourKey = HourStatsKey.from(event, eventTime); - hourAggregator = hourAggregators.computeIfAbsent( - hourKey, - HourStatsAggregator::new - ); - hourAggregator.recordEvent(event); - } - - if (log.isDebugEnabled()) { - log.debug("Recorded event: minute={}, hour={}, minute_total={}, hour_total={}", - minuteKey, hourKey, - minuteAggregator != null ? minuteAggregator.getTotalCount() : 0, - hourAggregator != null ? hourAggregator.getTotalCount() : 0); - } + MinuteStatsKey minuteKey = MinuteStatsKey.from(event, eventTime); + MinuteStatsAggregator minuteAggregator = minuteAggregators.computeIfAbsent( + minuteKey, + MinuteStatsAggregator::new + ); + minuteAggregator.recordEvent(event); + log.debug("Recorded minute event: key={}, total={}", minuteKey, minuteAggregator.getTotalCount()); } catch (Exception e) { - log.error("Failed to record event: {}", event, e); + log.error("Failed to record minute event: {}", event, e); + } + } + + private void recordHourEvent(TransactionEvent event, LocalDateTime eventTime) { + try { + if (hourAggregators.size() >= MAX_HOUR_AGGREGATORS) { + log.warn("Hour aggregator limit reached ({}), forcing flush of completed hours", MAX_HOUR_AGGREGATORS); + flushCompletedHours(); + + if (hourAggregators.size() >= MAX_HOUR_AGGREGATORS) { + log.error("Hour aggregator count still exceeds limit after flush: {}", hourAggregators.size()); + } + } + + HourStatsKey hourKey = HourStatsKey.from(event, eventTime); + HourStatsAggregator hourAggregator = hourAggregators.computeIfAbsent( + hourKey, + HourStatsAggregator::new + ); + hourAggregator.recordEvent(event); + + log.debug("Recorded hour event: key={}, total={}", hourKey, hourAggregator.getTotalCount()); + } catch (Exception e) { + log.error("Failed to record hour event: {}", event, e); } } diff --git a/src/main/java/com/eactive/eai/custom/statistics/collector/HourStatsAggregator.java b/src/main/java/com/eactive/eai/custom/statistics/collector/HourStatsAggregator.java index b8acfff..3e8afed 100644 --- a/src/main/java/com/eactive/eai/custom/statistics/collector/HourStatsAggregator.java +++ b/src/main/java/com/eactive/eai/custom/statistics/collector/HourStatsAggregator.java @@ -57,7 +57,6 @@ public class HourStatsAggregator { /** * 거래 이벤트 기록 - * - AtomicLong 사용으로 synchronized 불필요 (성능 최적화) */ public void recordEvent(TransactionEvent event) { int logSeq = event.getLogSequence(); @@ -194,6 +193,7 @@ public class HourStatsAggregator { long timeout = timeoutCnt.get(); long systemErr = systemErrCnt.get(); long bizErr = bizErrCnt.get(); + long avgCount = success + timeout + systemErr + bizErr; stats.setTotalCnt(total); stats.setSuccessCnt(success); @@ -207,9 +207,9 @@ public class HourStatsAggregator { stats.setSeq900BizErrCnt(seq900BizErrCnt.get()); // 응답시간 메트릭 - if (total > 0 && totalRespTime.get() > 0) { + if (avgCount > 0 && totalRespTime.get() > 0) { // 응답시간이 실제로 기록된 경우만 처리 - double avg = (double) totalRespTime.get() / total; + double avg = (double) totalRespTime.get() / avgCount; stats.setAvgRespTime(BigDecimal.valueOf(avg).setScale(3, RoundingMode.HALF_UP)); // Min/Max (totalRespTime > 0이면 반드시 갱신됨) diff --git a/src/main/java/com/eactive/eai/custom/statistics/collector/MinuteStatsAggregator.java b/src/main/java/com/eactive/eai/custom/statistics/collector/MinuteStatsAggregator.java index d2eb524..4a5c1cb 100644 --- a/src/main/java/com/eactive/eai/custom/statistics/collector/MinuteStatsAggregator.java +++ b/src/main/java/com/eactive/eai/custom/statistics/collector/MinuteStatsAggregator.java @@ -57,7 +57,6 @@ public class MinuteStatsAggregator { /** * 거래 이벤트 기록 - * - AtomicLong 사용으로 synchronized 불필요 (성능 최적화) */ public void recordEvent(TransactionEvent event) { int logSeq = event.getLogSequence(); @@ -194,6 +193,7 @@ public class MinuteStatsAggregator { long timeout = timeoutCnt.get(); long systemErr = systemErrCnt.get(); long bizErr = bizErrCnt.get(); + long avgCount = success + timeout + systemErr + bizErr; stats.setTotalCnt(total); stats.setSuccessCnt(success); @@ -207,9 +207,9 @@ public class MinuteStatsAggregator { stats.setSeq900BizErrCnt(seq900BizErrCnt.get()); // 응답시간 메트릭 - if (total > 0 && totalRespTime.get() > 0) { + if (avgCount > 0 && totalRespTime.get() > 0) { // 응답시간이 실제로 기록된 경우만 처리 - double avg = (double) totalRespTime.get() / total; + double avg = (double) totalRespTime.get() / avgCount; stats.setAvgRespTime(BigDecimal.valueOf(avg).setScale(3, RoundingMode.HALF_UP)); // Min/Max (totalRespTime > 0이면 반드시 갱신됨)