package com.evps.comm.server.scheduler; import com.evps.comm.server.config.ApplicationConfig; import com.evps.comm.server.config.TraceConfig; import com.evps.comm.server.repository.ApplicationRepository; import com.evps.comm.server.service.EvpsServiceManagerService; import com.evps.comm.server.service.UnitSystService; import com.evps.common.utils.SystemHealth; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariPoolMXBean; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.PreDestroy; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.text.DecimalFormat; import java.util.EnumMap; import java.util.Map; @Slf4j @RequiredArgsConstructor @EnableScheduling @Component public class ApplicationScheduler { private final TraceConfig traceConfig; private final ApplicationConfig config; private final UnitSystService unitSystService; private final ApplicationRepository applicationRepository; private final EvpsServiceManagerService evpsServiceManagerService; private final HikariDataSource dataSource; private final Map stateCountMap = new EnumMap<>(Thread.State.class); private final SystemHealth systemHealth = new SystemHealth(); private final DecimalFormat df = new DecimalFormat("#.##"); @PreDestroy public void onShutDown() { log.info("ApplicationScheduler.onShutDown: Shutting down..."); } @Scheduled(cron = "0 * * * * *") public void updateProcessState() { try { this.systemHealth.checkHealth(false); loggingHealthCheck(); double cpuUsage = this.systemHealth.getCpuUsage(); if (cpuUsage > this.config.getCpuLimits()) { // ApplicationRepository.processState.setErrDesc("CPU 사용율이 너무 높음: " + String.format("%.2f", cpuUsage)); log.warn("[SKIP] ApplicationScheduler.updateProcessState: High CPU Usage, Limit({} %), Current({} %), Schedule Job SKIP...", this.config.getCpuLimits(), String.format("%.2f", cpuUsage)); loggingThreads(); return; } } catch (Exception e) { log.error("ApplicationScheduler.updateProcessState: System Health Check Exception {}", e.getMessage()); } try { this.unitSystService.updateUnitSystStts(); } catch(Exception e) { log.error("ApplicationScheduler.updateProcessState: Exception {}", e.getMessage()); } try { this.traceConfig.loadTraceInfo(); } catch(Exception e) { log.error("ApplicationScheduler.loadTraceInfo: Exception {}", e.getMessage()); } } @Scheduled(cron = "0/30 * * * * *") public void serviceMangerSchedule() { try { this.evpsServiceManagerService.checkServiceTimeout(); } catch(Exception e) { log.error("ApplicationScheduler.serviceMangerSchedule: Exception {}", e.getMessage()); } } @Scheduled(cron = "20 * * * * *") // 1분주기 작업 실행 public void reportCenterSessions() { try { this.applicationRepository.reportCenterSessions(); } catch(Exception e) { log.error("ApplicationScheduler.reportCenterSessions: Exception {}", e.getMessage()); } } @Scheduled(cron = "10 0/5 * * * *") // 5분주기 작업 실행 public void loadBaseDatabase() { try { this.applicationRepository.loadDb(); } catch(Exception e) { log.error("ApplicationScheduler.loadBaseDatabase: Exception {}", e.getMessage()); } } private void loggingHealthCheck() { log.info("----------------------------------------------------------------------------------------------------------"); loggingSystemHealth(); logSessionStatus(); loggingThreadState(); log.info("----------------------------------------------------------------------------------------------------------"); } private void loggingSystemHealth() { double loadAvg = this.systemHealth.getLoadAverage(); String loadAvgStr = (loadAvg < 0) ? "N/A" : this.df.format(loadAvg); log.info("SYSTEM HEALTH: CPU {} Cores[{} %, LoadAvg({})], Memory[{} %, Used({} MB), Max({} MB)], Threads[{}, Peak({})], GC(T/R)[{}/{} ms]", this.systemHealth.getCpuCores(), this.df.format(this.systemHealth.getCpuUsage()), loadAvgStr, this.df.format(this.systemHealth.getMemUsage()), this.systemHealth.getUsedMemory(), this.systemHealth.getMaxMemory(), this.systemHealth.getThreadCount(), this.systemHealth.getPeakThreadCount(), this.systemHealth.getGcTotalAvgTime(), this.systemHealth.getGcRecentAvgTime()); } private void logSessionStatus() { HikariPoolMXBean poolStats = this.dataSource.getHikariPoolMXBean(); int totalConnections = poolStats.getTotalConnections(); int activeConnections = poolStats.getActiveConnections(); int idleConnections = poolStats.getIdleConnections(); int threadsAwaiting = poolStats.getThreadsAwaitingConnection(); log.info(" DB SESSION: Total: {}, Active: {}, Idle: {}, Waiting: {}", totalConnections, activeConnections, idleConnections, threadsAwaiting); } private void loggingThreadState() { ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); ThreadInfo[] infos = threadBean.dumpAllThreads(false, false); this.stateCountMap.clear(); for (ThreadInfo info : infos) { Thread.State state = info.getThreadState(); this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1); } StringBuilder sb = new StringBuilder(); for (Map.Entry entry : stateCountMap.entrySet()) { sb.append(String.format("%s(%d), ", entry.getKey().name(), entry.getValue())); } // 마지막 쉼표 제거 if (sb.length() > 0) { sb.setLength(sb.length() - 2); } log.info(" THREAD STATE: {}", sb.toString()); } private void loggingThreads() { ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); ThreadInfo[] infos = threadBean.dumpAllThreads(false, false); this.stateCountMap.clear(); for (ThreadInfo info : infos) { Thread.State state = info.getThreadState(); this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1); log.info(" [" + info.getThreadId() + "] " + info.getThreadName() + " - " + info.getThreadState()); } } }