대시보드 시스템 상태 카드에 메모리·디스크 신호등 추가
- Backend: PipelineMonitorService에 system 항목 추가 (OS 메모리, 디스크 사용률) - Frontend: SystemMonitorDto에 system 필드 추가 - DashboardPage: 시스템/메모리/디스크 3열 신호등 레이아웃 적용 - dashboardCommand.css: gc-dash-sys-status, gc-sys-tl-item 스타일 추가 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import com.goldenchart.websocket.UpbitWebSocketClient;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.lang.management.MemoryMXBean;
|
import java.lang.management.MemoryMXBean;
|
||||||
import java.lang.management.ThreadInfo;
|
import java.lang.management.ThreadInfo;
|
||||||
@@ -62,6 +63,7 @@ public class PipelineMonitorService {
|
|||||||
"gapBackfillCron", props.getGapBackfillCron()
|
"gapBackfillCron", props.getGapBackfillCron()
|
||||||
));
|
));
|
||||||
out.put("jvm", jvmMetrics());
|
out.put("jvm", jvmMetrics());
|
||||||
|
out.put("system", systemMetrics());
|
||||||
out.put("traffic", computeTrafficRates());
|
out.put("traffic", computeTrafficRates());
|
||||||
out.put("memory", ta4jStorage.storageMetrics());
|
out.put("memory", ta4jStorage.storageMetrics());
|
||||||
out.put("queue", eventQueue.stats());
|
out.put("queue", eventQueue.stats());
|
||||||
@@ -91,6 +93,40 @@ public class PipelineMonitorService {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> systemMetrics() {
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
// OS 물리 메모리
|
||||||
|
try {
|
||||||
|
com.sun.management.OperatingSystemMXBean osMx =
|
||||||
|
(com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
|
||||||
|
long totalPhys = osMx.getTotalMemorySize();
|
||||||
|
long freePhys = osMx.getFreeMemorySize();
|
||||||
|
double memPct = totalPhys > 0 ? (totalPhys - freePhys) * 100.0 / totalPhys : 0;
|
||||||
|
m.put("osMemUsedMb", roundMb(totalPhys - freePhys));
|
||||||
|
m.put("osMemTotalMb", roundMb(totalPhys));
|
||||||
|
m.put("osMemPct", Math.round(memPct * 10) / 10.0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
m.put("osMemUsedMb", 0);
|
||||||
|
m.put("osMemTotalMb", 0);
|
||||||
|
m.put("osMemPct", 0.0);
|
||||||
|
}
|
||||||
|
// 디스크 (루트 파티션)
|
||||||
|
try {
|
||||||
|
File root = new File("/");
|
||||||
|
long total = root.getTotalSpace();
|
||||||
|
long free = root.getFreeSpace();
|
||||||
|
double diskPct = total > 0 ? (total - free) * 100.0 / total : 0;
|
||||||
|
m.put("diskUsedGb", Math.round((total - free) / 1073741824.0 * 10) / 10.0);
|
||||||
|
m.put("diskTotalGb", Math.round(total / 1073741824.0 * 10) / 10.0);
|
||||||
|
m.put("diskPct", Math.round(diskPct * 10) / 10.0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
m.put("diskUsedGb", 0.0);
|
||||||
|
m.put("diskTotalGb", 0.0);
|
||||||
|
m.put("diskPct", 0.0);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Object> computeTrafficRates() {
|
private Map<String, Object> computeTrafficRates() {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
var q = eventQueue.stats();
|
var q = eventQueue.stats();
|
||||||
|
|||||||
@@ -182,7 +182,9 @@ const DashboardPage: React.FC<Props> = ({ theme, onGoChart }) => {
|
|||||||
|
|
||||||
<section className="gc-dash-card">
|
<section className="gc-dash-card">
|
||||||
<h2 className="gc-dash-card-title">시스템 상태</h2>
|
<h2 className="gc-dash-card-title">시스템 상태</h2>
|
||||||
<div className="gc-dash-card-inner gc-dash-card-inner--center">
|
<div className="gc-dash-card-inner gc-dash-sys-status">
|
||||||
|
{/* 전체 시스템 */}
|
||||||
|
<div className="gc-sys-tl-item">
|
||||||
<div className={`gc-traffic-light gc-traffic-light--${health}`} aria-label="시스템 상태">
|
<div className={`gc-traffic-light gc-traffic-light--${health}`} aria-label="시스템 상태">
|
||||||
<span className="gc-traffic-bulb gc-traffic-bulb--r" />
|
<span className="gc-traffic-bulb gc-traffic-bulb--r" />
|
||||||
<span className="gc-traffic-bulb gc-traffic-bulb--y" />
|
<span className="gc-traffic-bulb gc-traffic-bulb--y" />
|
||||||
@@ -191,6 +193,54 @@ const DashboardPage: React.FC<Props> = ({ theme, onGoChart }) => {
|
|||||||
<p className="gc-traffic-caption">
|
<p className="gc-traffic-caption">
|
||||||
{health === 'green' ? '정상 운영' : health === 'yellow' ? '주의 필요' : '점검 필요'}
|
{health === 'green' ? '정상 운영' : health === 'yellow' ? '주의 필요' : '점검 필요'}
|
||||||
</p>
|
</p>
|
||||||
|
<span className="gc-sys-tl-label">시스템</span>
|
||||||
|
</div>
|
||||||
|
{/* 메모리 */}
|
||||||
|
{(() => {
|
||||||
|
const memPct = mon.system?.osMemPct ?? mon.jvm.heapPct;
|
||||||
|
const memLevel: 'green' | 'yellow' | 'red' =
|
||||||
|
memPct >= 90 ? 'red' : memPct >= 70 ? 'yellow' : 'green';
|
||||||
|
const memUsed = mon.system?.osMemUsedMb ?? mon.jvm.heapUsedMb;
|
||||||
|
const memTotal = mon.system?.osMemTotalMb ?? mon.jvm.heapMaxMb;
|
||||||
|
return (
|
||||||
|
<div className="gc-sys-tl-item">
|
||||||
|
<div className={`gc-traffic-light gc-traffic-light--${memLevel}`} aria-label="메모리 사용량">
|
||||||
|
<span className="gc-traffic-bulb gc-traffic-bulb--r" />
|
||||||
|
<span className="gc-traffic-bulb gc-traffic-bulb--y" />
|
||||||
|
<span className="gc-traffic-bulb gc-traffic-bulb--g" />
|
||||||
|
</div>
|
||||||
|
<p className="gc-traffic-caption">{memPct.toFixed(0)}%</p>
|
||||||
|
<p className="gc-traffic-caption gc-traffic-caption--sub">
|
||||||
|
{memUsed >= 1024 ? `${(memUsed/1024).toFixed(1)}GB` : `${memUsed}MB`}
|
||||||
|
{' / '}
|
||||||
|
{memTotal >= 1024 ? `${(memTotal/1024).toFixed(1)}GB` : `${memTotal}MB`}
|
||||||
|
</p>
|
||||||
|
<span className="gc-sys-tl-label">메모리</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
{/* 디스크 */}
|
||||||
|
{(() => {
|
||||||
|
const diskPct = mon.system?.diskPct ?? 0;
|
||||||
|
const diskLevel: 'green' | 'yellow' | 'red' =
|
||||||
|
diskPct >= 90 ? 'red' : diskPct >= 70 ? 'yellow' : 'green';
|
||||||
|
const diskUsed = mon.system?.diskUsedGb ?? 0;
|
||||||
|
const diskTotal = mon.system?.diskTotalGb ?? 0;
|
||||||
|
return (
|
||||||
|
<div className="gc-sys-tl-item">
|
||||||
|
<div className={`gc-traffic-light gc-traffic-light--${diskLevel}`} aria-label="디스크 사용량">
|
||||||
|
<span className="gc-traffic-bulb gc-traffic-bulb--r" />
|
||||||
|
<span className="gc-traffic-bulb gc-traffic-bulb--y" />
|
||||||
|
<span className="gc-traffic-bulb gc-traffic-bulb--g" />
|
||||||
|
</div>
|
||||||
|
<p className="gc-traffic-caption">{diskPct.toFixed(0)}%</p>
|
||||||
|
<p className="gc-traffic-caption gc-traffic-caption--sub">
|
||||||
|
{diskUsed.toFixed(1)}GB / {diskTotal.toFixed(1)}GB
|
||||||
|
</p>
|
||||||
|
<span className="gc-sys-tl-label">디스크</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -321,6 +321,36 @@
|
|||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
color: var(--se-text-muted);
|
color: var(--se-text-muted);
|
||||||
}
|
}
|
||||||
|
.gc-traffic-caption--sub {
|
||||||
|
font-size: 0.62rem;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 시스템 상태 — 3열 신호등 ── */
|
||||||
|
.gc-dash-sys-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-around;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-sys-tl-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-sys-tl-label {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--se-text-muted);
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Indicator bars ── */
|
/* ── Indicator bars ── */
|
||||||
|
|
||||||
|
|||||||
@@ -576,6 +576,14 @@ export interface SystemMonitorDto {
|
|||||||
};
|
};
|
||||||
traffic: Record<string, number>;
|
traffic: Record<string, number>;
|
||||||
memory: { markets: number; series: number; totalBars: number; maxBarsPerSeries: number };
|
memory: { markets: number; series: number; totalBars: number; maxBarsPerSeries: number };
|
||||||
|
system?: {
|
||||||
|
osMemUsedMb: number;
|
||||||
|
osMemTotalMb: number;
|
||||||
|
osMemPct: number;
|
||||||
|
diskUsedGb: number;
|
||||||
|
diskTotalGb: number;
|
||||||
|
diskPct: number;
|
||||||
|
};
|
||||||
queue: { pending: number; capacity: number; fillRatio: number; enqueued: number; dequeued: number; dropped: number };
|
queue: { pending: number; capacity: number; fillRatio: number; enqueued: number; dequeued: number; dropped: number };
|
||||||
workers: { processed: number; errors: number; active: boolean; workerThreads: number };
|
workers: { processed: number; errors: number; active: boolean; workerThreads: number };
|
||||||
orders: {
|
orders: {
|
||||||
|
|||||||
Reference in New Issue
Block a user