shjung пре 2 недеља
родитељ
комит
fc5241c07e

+ 1 - 0
moct-utic-server/src/main/java/com/utic/its/moct/utic/server/config/ApplicationConfig.java

@@ -20,6 +20,7 @@ public class ApplicationConfig extends NettyServerConfig {
 
     private String processId = "70070";
     private String processName = "moct-utic-server";
+    private double cpuLimits = 75;
     private int errTimeGap = 120;
 
     private int packetQueueSize = 0;

+ 95 - 0
moct-utic-server/src/main/java/com/utic/its/moct/utic/server/scheduler/ApplicationScheduler.java

@@ -3,6 +3,7 @@ package com.utic.its.moct.utic.server.scheduler;
 import com.utic.its.common.dto.CenterDto;
 import com.utic.its.common.dto.NET;
 import com.utic.its.common.repository.ApplicationRepository;
+import com.utic.its.common.utils.SystemHealth;
 import com.utic.its.moct.utic.server.cluster.ClusterConfig;
 import com.utic.its.moct.utic.server.config.ApplicationConfig;
 import com.utic.its.moct.utic.server.config.SchedulingConfig;
@@ -10,12 +11,21 @@ import com.utic.its.moct.utic.server.config.TraceConfig;
 import com.utic.its.moct.utic.server.process.DbmsDataProcessService;
 import com.utic.its.moct.utic.server.service.MoctUticServerService;
 import com.utic.its.moct.utic.server.service.ProcessStateService;
+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 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
@@ -30,8 +40,29 @@ public class ApplicationScheduler {
     private final MoctUticServerService moctUticServerService;
     private final ProcessStateService processStateService;
 
+    private final HikariDataSource dataSource;
+    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
+    private final SystemHealth systemHealth = new SystemHealth();
+    private final DecimalFormat df = new DecimalFormat("#.##");
+
     @Scheduled(cron = "5 * * * * *")  // 1분주기 작업 실행
     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 {
             if (this.processStateService.isStateUpdateTimeExpired()) {
                 // 60초 이상 경과 시 프로세스 상태 업데이트
@@ -76,4 +107,68 @@ public class ApplicationScheduler {
         }
     }
 
+    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[{}, {} 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.getGcCount(),
+                this.systemHealth.getGcTime());
+    }
+
+    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<Thread.State, Integer> 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());
+        }
+    }
+
 }

+ 5 - 5
moct-utic-server/src/main/resources/logback-spring-appender.xml

@@ -17,7 +17,7 @@
             <appender name="FILE-${id}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                 <file>${LOG_PATH}packet/${id}.log</file>
                 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-                    <charset>${LOG_CHARSET}</charset>
+                    <charset>${FILE_LOG_CHARSET}</charset>
                     <Pattern>${LOG_PATTERN_PACKET}</Pattern>
                 </encoder>
 
@@ -33,7 +33,7 @@
     <appender name="FILE_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_FILE}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -46,7 +46,7 @@
     <appender name="FILE_ASPECT" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME_ASPECT}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_ASPECT}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -64,7 +64,7 @@
         </filter>
         <file>${LOG_PATH}${LOG_FILE_NAME_ERROR}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_ERROR}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -77,7 +77,7 @@
     <appender name="FILE_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME_SESSION}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_FILE}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

+ 1 - 3
moct-utic-server/src/main/resources/logback-spring.xml

@@ -7,14 +7,12 @@
     <property name="PROJECT_NAME"    value="${PROJECT_PREFIX}"/>
     <property name="ROOT_LOG_LEVEL"  value="INFO"/>
     <property name="LOG_CHARSET"     value="UTF-8" />
+    <property name="FILE_LOG_CHARSET" value="UTF-8" />
 
     <springProperty scope="context" name="LOGGING_PATH" source="logging.file.path"/>
     <property name="LOG_PATH" value="${LOGGING_PATH:-${user.dir}/logs/${PROJECT_NAME}}/"/>
     <property name="LOG_BACKUP_PATH" value="${LOG_PATH}/backup/"/>
 
-<!--    <property name="LOG_PATH"        value="${user.home}/logs/${PROJECT_NAME}/"/>-->
-<!--    <property name="LOG_BACKUP_PATH" value="${user.home}/logs/${PROJECT_NAME}/backup/"/>-->
-
     <property name="LOG_FILE_NAME"             value="${PROJECT_NAME}.log"/>
     <property name="LOG_FILE_NAME_ERROR"       value="${PROJECT_NAME}.err.log"/>
     <property name="LOG_FILE_NAME_PATTERN"     value="%d{yyyyMMdd}_%i.log.gz"/>

+ 1 - 0
rota-utic-client/src/main/java/com/utic/its/rota/utic/client/config/ApplicationConfig.java

@@ -20,6 +20,7 @@ public class ApplicationConfig extends NettyServerConfig {
 
     private String processId = "70060";
     private String processName = "rota-utic-client";
+    private double cpuLimits = 75;
     private int errTimeGap = 120;
 
     private int packetQueueSize = 0;

+ 95 - 0
rota-utic-client/src/main/java/com/utic/its/rota/utic/client/scheduler/ApplicationScheduler.java

@@ -3,6 +3,7 @@ package com.utic.its.rota.utic.client.scheduler;
 import com.utic.its.common.dto.CenterDto;
 import com.utic.its.common.dto.NET;
 import com.utic.its.common.repository.ApplicationRepository;
+import com.utic.its.common.utils.SystemHealth;
 import com.utic.its.common.xnet.process.dbms.DbmsData;
 import com.utic.its.rota.utic.client.cluster.ClusterConfig;
 import com.utic.its.rota.utic.client.config.ApplicationConfig;
@@ -10,12 +11,21 @@ import com.utic.its.rota.utic.client.config.SchedulingConfig;
 import com.utic.its.rota.utic.client.config.TraceConfig;
 import com.utic.its.rota.utic.client.process.DbmsDataProcessService;
 import com.utic.its.rota.utic.client.service.ProcessStateService;
+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 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
@@ -32,8 +42,29 @@ public class ApplicationScheduler {
     private final DbmsData delRcvLinkTraffic = new DbmsData(0, null, DbmsData.DBMS_DELETE_RCV_LINK_TRAFFIC, null, null, 0);
     private final DbmsData delRcvIncident = new DbmsData(0, null, DbmsData.DBMS_DELETE_RCV_INCIDENT, null, null, 0);
 
+    private final HikariDataSource dataSource;
+    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
+    private final SystemHealth systemHealth = new SystemHealth();
+    private final DecimalFormat df = new DecimalFormat("#.##");
+
     @Scheduled(cron = "5 * * * * *")  // 1분주기 작업 실행
     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 {
             if (this.processStateService.isStateUpdateTimeExpired()) {
                 // 60초 이상 경과 시 프로세스 상태 업데이트
@@ -113,4 +144,68 @@ public class ApplicationScheduler {
         }
     }
 
+    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[{}, {} 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.getGcCount(),
+                this.systemHealth.getGcTime());
+    }
+
+    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<Thread.State, Integer> 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());
+        }
+    }
+
 }

+ 5 - 5
rota-utic-client/src/main/resources/logback-spring-appender.xml

@@ -17,7 +17,7 @@
             <appender name="FILE-${id}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                 <file>${LOG_PATH}packet/${id}.log</file>
                 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-                    <charset>${LOG_CHARSET}</charset>
+                    <charset>${FILE_LOG_CHARSET}</charset>
                     <Pattern>${LOG_PATTERN_PACKET}</Pattern>
                 </encoder>
 
@@ -33,7 +33,7 @@
     <appender name="FILE_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_FILE}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -46,7 +46,7 @@
     <appender name="FILE_ASPECT" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME_ASPECT}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_ASPECT}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -64,7 +64,7 @@
         </filter>
         <file>${LOG_PATH}${LOG_FILE_NAME_ERROR}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_ERROR}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -77,7 +77,7 @@
     <appender name="FILE_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME_SESSION}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_FILE}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

+ 1 - 3
rota-utic-client/src/main/resources/logback-spring.xml

@@ -7,14 +7,12 @@
     <property name="PROJECT_NAME"    value="${PROJECT_PREFIX}"/>
     <property name="ROOT_LOG_LEVEL"  value="INFO"/>
     <property name="LOG_CHARSET"     value="UTF-8" />
+    <property name="FILE_LOG_CHARSET" value="UTF-8" />
 
     <springProperty scope="context" name="LOGGING_PATH" source="logging.file.path"/>
     <property name="LOG_PATH" value="${LOGGING_PATH:-${user.dir}/logs/${PROJECT_NAME}}/"/>
     <property name="LOG_BACKUP_PATH" value="${LOG_PATH}/backup/"/>
 
-<!--    <property name="LOG_PATH"        value="${user.home}/logs/${PROJECT_NAME}/"/>-->
-<!--    <property name="LOG_BACKUP_PATH" value="${user.home}/logs/${PROJECT_NAME}/backup/"/>-->
-
     <property name="LOG_FILE_NAME"             value="${PROJECT_NAME}.log"/>
     <property name="LOG_FILE_NAME_ERROR"       value="${PROJECT_NAME}.err.log"/>
     <property name="LOG_FILE_NAME_PATTERN"     value="%d{yyyyMMdd}_%i.log.gz"/>

+ 1 - 0
rota-utic-server/src/main/java/com/utic/its/rota/utic/server/config/ApplicationConfig.java

@@ -20,6 +20,7 @@ public class ApplicationConfig extends NettyServerConfig {
 
     private String processId = "70050";
     private String processName = "rota-utic-server";
+    private double cpuLimits = 75;
     private int errTimeGap = 120;
 
     private int packetQueueSize = 0;

+ 95 - 0
rota-utic-server/src/main/java/com/utic/its/rota/utic/server/scheduler/ApplicationScheduler.java

@@ -2,6 +2,7 @@ package com.utic.its.rota.utic.server.scheduler;
 
 import com.utic.its.common.dto.CenterDto;
 import com.utic.its.common.dto.NET;
+import com.utic.its.common.utils.SystemHealth;
 import com.utic.its.common.repository.ApplicationRepository;
 import com.utic.its.common.xnet.process.dbms.DbmsData;
 import com.utic.its.rota.utic.server.cluster.ClusterConfig;
@@ -11,12 +12,21 @@ import com.utic.its.rota.utic.server.config.TraceConfig;
 import com.utic.its.rota.utic.server.process.DbmsDataProcessService;
 import com.utic.its.rota.utic.server.service.ProcessStateService;
 import com.utic.its.rota.utic.server.service.RotaUticServerService;
+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 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
@@ -33,8 +43,29 @@ public class ApplicationScheduler {
 
     private final DbmsData delSndIncident = new DbmsData(0, ApplicationRepository.CENTER, DbmsData.DBMS_DATA_DELETE_SND_INCIDENT, "", null, 0);
 
+    private final HikariDataSource dataSource;
+    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
+    private final SystemHealth systemHealth = new SystemHealth();
+    private final DecimalFormat df = new DecimalFormat("#.##");
+
     @Scheduled(cron = "5 * * * * *")  // 1분주기 작업 실행
     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 {
             if (this.processStateService.isStateUpdateTimeExpired()) {
                 // 60초 이상 경과 시 프로세스 상태 업데이트
@@ -117,4 +148,68 @@ public class ApplicationScheduler {
         }
     }
 
+    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[{}, {} 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.getGcCount(),
+                this.systemHealth.getGcTime());
+    }
+
+    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<Thread.State, Integer> 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());
+        }
+    }
+
 }

+ 5 - 5
rota-utic-server/src/main/resources/logback-spring-appender.xml

@@ -17,7 +17,7 @@
             <appender name="FILE-${id}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                 <file>${LOG_PATH}packet/${id}.log</file>
                 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-                    <charset>${LOG_CHARSET}</charset>
+                    <charset>${FILE_LOG_CHARSET}</charset>
                     <Pattern>${LOG_PATTERN_PACKET}</Pattern>
                 </encoder>
 
@@ -33,7 +33,7 @@
     <appender name="FILE_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_FILE}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -46,7 +46,7 @@
     <appender name="FILE_ASPECT" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME_ASPECT}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_ASPECT}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -64,7 +64,7 @@
         </filter>
         <file>${LOG_PATH}${LOG_FILE_NAME_ERROR}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_ERROR}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@@ -77,7 +77,7 @@
     <appender name="FILE_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${LOG_PATH}${LOG_FILE_NAME_SESSION}</file>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <charset>${LOG_CHARSET}</charset>
+            <charset>${FILE_LOG_CHARSET}</charset>
             <pattern>${LOG_PATTERN_FILE}</pattern>
         </encoder>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

+ 1 - 3
rota-utic-server/src/main/resources/logback-spring.xml

@@ -7,14 +7,12 @@
     <property name="PROJECT_NAME"    value="${PROJECT_PREFIX}"/>
     <property name="ROOT_LOG_LEVEL"  value="INFO"/>
     <property name="LOG_CHARSET"     value="UTF-8" />
+    <property name="FILE_LOG_CHARSET" value="UTF-8" />
 
     <springProperty scope="context" name="LOGGING_PATH" source="logging.file.path"/>
     <property name="LOG_PATH" value="${LOGGING_PATH:-${user.dir}/logs/${PROJECT_NAME}}/"/>
     <property name="LOG_BACKUP_PATH" value="${LOG_PATH}/backup/"/>
 
-<!--    <property name="LOG_PATH"        value="${user.dir}/logs/${PROJECT_NAME}/"/>-->
-<!--    <property name="LOG_BACKUP_PATH" value="${user.dir}/logs/${PROJECT_NAME}/backup/"/>-->
-
     <property name="LOG_FILE_NAME"             value="${PROJECT_NAME}.log"/>
     <property name="LOG_FILE_NAME_ERROR"       value="${PROJECT_NAME}.err.log"/>
     <property name="LOG_FILE_NAME_PATTERN"     value="%d{yyyyMMdd}_%i.log.gz"/>

+ 1 - 1
utic-its-asn1/install.sh

@@ -11,4 +11,4 @@ mvn install:install-file \
   -DartifactId=asn1 \
   -Dversion=0.0.1 \
   -Dpackaging=jar \
-  -DlocalRepositoryPath=C:\java\repository\
+  -DlocalRepositoryPath=/Users/openvalue/Projects/java/repository/

+ 14 - 0
utic-its-common/src/main/java/com/utic/its/common/utils/GcStats.java

@@ -0,0 +1,14 @@
+package com.utic.its.common.utils;
+
+import lombok.Getter;
+
+@Getter
+public class GcStats {
+    private final long count;
+    private final long time;
+
+    public GcStats(long count, long time) {
+        this.count = count;
+        this.time = time;
+    }
+}

+ 2 - 2
utic-its-common/src/main/java/com/utic/its/common/utils/LogUtils.java

@@ -43,7 +43,7 @@ public class LogUtils {
     public static String elapsedLog(String logMsg, int count, long elapsedTime) {
         String adjustedMsg = adjustLogMsg(logMsg, 36);
         DecimalFormat df = new DecimalFormat("#,###");
-        return String.format("%s: [%8s ms, %6s EA]", adjustedMsg, df.format(elapsedTime), df.format(count));
+        return String.format("%s: [%8s ms, %8s EA]", adjustedMsg, df.format(elapsedTime), df.format(count));
     }
     public static String elapsedLog(String logMsg) {
         String adjustedMsg = adjustLogMsg(logMsg, 36);
@@ -60,7 +60,7 @@ public class LogUtils {
     public static String elapsedLog(String type, String logMsg, int count, long elapsedTime) {
         String adjustedMsg = adjustLogMsg(logMsg, 30);
         DecimalFormat df = new DecimalFormat("#,###");
-        return String.format("(%s) %s: [%8s ms, %6s EA]", type, adjustedMsg, df.format(elapsedTime), df.format(count));
+        return String.format("(%s) %s: [%8s ms, %8s EA]", type, adjustedMsg, df.format(elapsedTime), df.format(count));
     }
     public static String elapsedLog(String type, String logMsg) {
         String adjustedMsg = adjustLogMsg(logMsg, 30);

+ 144 - 0
utic-its-common/src/main/java/com/utic/its/common/utils/SystemHealth.java

@@ -0,0 +1,144 @@
+package com.utic.its.common.utils;
+
+import com.sun.management.OperatingSystemMXBean;
+import lombok.Getter;
+
+import java.io.File;
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.HashMap;
+import java.util.Map;
+
+@Getter
+public class SystemHealth {
+
+    private String osName = System.getProperty("os.name");
+    private String osVersion = System.getProperty("os.version");
+    private int cpuCores = Runtime.getRuntime().availableProcessors();
+    private long maxMemory = Runtime.getRuntime().maxMemory();
+
+    private long totalMemory = 0;   // MB
+    private long freeMemory = 0;    // MB
+    private long usedMemory = 0;    // MB
+    private long heapUsed;
+    private long nonHeapUsed;
+
+    private double memUsage = 0.;
+    private double cpuUsage = 0.;
+    private double loadAverage;         // CPU 부하
+
+    private int threadCount = 0;    // 현재스레드 수
+    private int peakThreadCount;    // 어플리케이션 운영중 최대 스레드 수
+
+    private long jvmUptime;             // milliseconds, - 예: uptimeMillis = 3600000 → JVM이 1시간(60분) 동안 실행 중이라는 뜻
+    private long loadedClassCount;
+
+//    private final Map<String, Double> diskUsageMap = new HashMap<>();
+    private long diskTotal;
+    private long diskFree;
+    private double diskUsage;
+
+    private final Map<String, GcStats> gcStatsMap = new HashMap<>();
+    private String gcName;
+    private long gcCount = 0;
+    private long gcTime = 0;
+    private String jvmStartTime;
+
+    public SystemHealth() {
+        this.jvmStartTime = Instant.ofEpochMilli(
+                ManagementFactory.getRuntimeMXBean().getStartTime()
+        ).atZone(ZoneId.systemDefault()).toString();
+
+        Runtime runtime = Runtime.getRuntime();
+        this.maxMemory = runtime.maxMemory() / 1024 / 1024;
+        this.cpuCores = runtime.availableProcessors();
+        this.osName = System.getProperty("os.name");
+        this.osVersion = System.getProperty("os.version");
+    }
+
+    public void checkHealth(boolean useDisk) {
+        Runtime runtime = Runtime.getRuntime();
+        this.totalMemory = runtime.totalMemory();   // Byte
+        this.freeMemory = runtime.freeMemory();     // Byte
+        this.usedMemory = this.totalMemory  - this.freeMemory;  // Byte
+
+        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
+        this.heapUsed = memoryMXBean.getHeapMemoryUsage().getUsed() / 1024 / 1024;
+        this.nonHeapUsed = memoryMXBean.getNonHeapMemoryUsage().getUsed() / 1024 / 1024;
+
+        this.totalMemory = this.totalMemory / 1024 / 1024;
+        this.freeMemory = this.freeMemory / 1024 / 1024;
+        this.usedMemory = this.usedMemory / 1024 / 1024;
+
+//        System.out.println("사용 중인 메모리: " + (usedMemory / 1024 / 1024) + " MB");
+//        System.out.println("최대 메모리: " + (maxMemory / 1024 / 1024) + " MB");
+
+        this.memUsage = (double) this.usedMemory / this.maxMemory * 100;
+        // For Linux/Unix, Windows(Perhaps)
+        OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
+        this.cpuUsage = osBean.getSystemCpuLoad() * 100;
+        this.loadAverage = osBean.getSystemLoadAverage(); // 1분 평균
+
+        this.threadCount = ManagementFactory.getThreadMXBean().getThreadCount();
+        this.peakThreadCount = ManagementFactory.getThreadMXBean().getPeakThreadCount();
+
+        this.jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+//        long uptimeSeconds = this.jvmUptime / 1000;
+//        long uptimeMinutes = uptimeSeconds / 60;
+//        long uptimeHours = uptimeMinutes / 60;
+//        long uptimeDays = uptimeHours / 24;
+//        System.out.println("JVM Uptime: "
+//                + uptimeDays + "d "
+//                + (uptimeHours % 24) + "h "
+//                + (uptimeMinutes % 60) + "m "
+//                + (uptimeSeconds % 60) + "s");
+
+
+        this.loadedClassCount = ManagementFactory.getClassLoadingMXBean().getLoadedClassCount();
+
+        if (useDisk) {
+            File root = new File("/");
+            if (root.exists() && root.canRead()) {
+                this.diskTotal = root.getTotalSpace();
+                this.diskFree = root.getFreeSpace();
+                long used = this.diskTotal - this.diskFree;
+                this.diskUsage = (double) used / this.diskTotal * 100;
+            }
+        }
+
+        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
+            this.gcName = gc.getName();
+            this.gcCount += gc.getCollectionCount();
+            this.gcTime += gc.getCollectionTime();
+            gcStatsMap.put(gc.getName(), new GcStats(gc.getCollectionCount(), gc.getCollectionTime()));
+        }
+
+//        for (File root : File.listRoots()) {
+//            if (root.exists() && root.canRead()) {
+//                long total = root.getTotalSpace();
+//                long free = root.getFreeSpace();
+//                double usage = (double) (total - free) / total * 100;
+//                diskUsageMap.put(root.getAbsolutePath(), usage);
+//            }
+//        }
+
+        // GC가 전체 실행 시간의 몇 %를 차지했는지 확인 가능
+//        long uptime = ManagementFactory.getRuntimeMXBean().getUptime(); // ms
+//        long gcTime = this.gcTime;
+//        double gcRatio = (double) gcTime / uptime * 100;
+//        log.info("GC Time: {}ms, JVM Uptime: {}ms, GC 비중: {:.2f}%", gcTime, uptime, gcRatio);
+    }
+
+    public String getFormattedUptime() {
+        long uptimeSeconds = this.jvmUptime / 1000;
+        long uptimeMinutes = uptimeSeconds / 60;
+        long uptimeHours = uptimeMinutes / 60;
+        long uptimeDays = uptimeHours / 24;
+        return uptimeDays + "d " + (uptimeHours % 24) + "h " + (uptimeMinutes % 60) + "m " + (uptimeSeconds % 60) + "s";
+    }
+
+
+}