시스템 모니터
This commit is contained in:
141
backend/api/anomaly/short-term.get.ts
Normal file
141
backend/api/anomaly/short-term.get.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { getDb } from '../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = getDb()
|
||||
const THRESHOLD = 30 // 30% 이상 변화 시 이상 감지
|
||||
|
||||
// 활성 서버 목록
|
||||
const servers = db.prepare(`
|
||||
SELECT target_id, server_name
|
||||
FROM server_targets
|
||||
WHERE is_active = 1
|
||||
ORDER BY server_name
|
||||
`).all() as any[]
|
||||
|
||||
const anomalies: any[] = []
|
||||
const serverResults: any[] = []
|
||||
|
||||
// 로그 저장용 prepared statement
|
||||
const insertLog = db.prepare(`
|
||||
INSERT INTO anomaly_logs (target_id, server_name, detect_type, metric, level, current_value, threshold_value, message)
|
||||
VALUES (?, ?, 'short-term', ?, ?, ?, ?, ?)
|
||||
`)
|
||||
|
||||
// 최근 1분 내 동일 로그 존재 여부 확인 (중복 방지)
|
||||
const recentLogExists = db.prepare(`
|
||||
SELECT 1 FROM anomaly_logs
|
||||
WHERE target_id = ? AND detect_type = 'short-term' AND metric = ?
|
||||
AND detected_at > datetime('now', '-1 minute', 'localtime')
|
||||
LIMIT 1
|
||||
`)
|
||||
|
||||
for (const server of servers) {
|
||||
const snapshots = db.prepare(`
|
||||
SELECT cpu_percent, memory_percent, collected_at
|
||||
FROM server_snapshots
|
||||
WHERE target_id = ?
|
||||
ORDER BY collected_at DESC
|
||||
LIMIT 20
|
||||
`).all(server.target_id) as any[]
|
||||
|
||||
if (snapshots.length < 4) {
|
||||
serverResults.push({
|
||||
target_id: server.target_id,
|
||||
server_name: server.server_name,
|
||||
cpu_change: null,
|
||||
mem_change: null,
|
||||
status: 'normal'
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const half = Math.floor(snapshots.length / 2)
|
||||
const currSnapshots = snapshots.slice(0, half)
|
||||
const prevSnapshots = snapshots.slice(half)
|
||||
|
||||
const currCpuAvg = currSnapshots.reduce((sum, s) => sum + (s.cpu_percent || 0), 0) / currSnapshots.length
|
||||
const prevCpuAvg = prevSnapshots.reduce((sum, s) => sum + (s.cpu_percent || 0), 0) / prevSnapshots.length
|
||||
const currMemAvg = currSnapshots.reduce((sum, s) => sum + (s.memory_percent || 0), 0) / currSnapshots.length
|
||||
const prevMemAvg = prevSnapshots.reduce((sum, s) => sum + (s.memory_percent || 0), 0) / prevSnapshots.length
|
||||
|
||||
let cpuChange: number | null = null
|
||||
let memChange: number | null = null
|
||||
|
||||
if (prevCpuAvg > 1) {
|
||||
cpuChange = ((currCpuAvg - prevCpuAvg) / prevCpuAvg) * 100
|
||||
} else {
|
||||
cpuChange = currCpuAvg - prevCpuAvg
|
||||
}
|
||||
|
||||
if (prevMemAvg > 1) {
|
||||
memChange = ((currMemAvg - prevMemAvg) / prevMemAvg) * 100
|
||||
} else {
|
||||
memChange = currMemAvg - prevMemAvg
|
||||
}
|
||||
|
||||
let status = 'normal'
|
||||
const maxChange = Math.max(Math.abs(cpuChange || 0), Math.abs(memChange || 0))
|
||||
if (maxChange >= 100) status = 'danger'
|
||||
else if (maxChange >= THRESHOLD) status = 'warning'
|
||||
|
||||
serverResults.push({
|
||||
target_id: server.target_id,
|
||||
server_name: server.server_name,
|
||||
cpu_change: cpuChange,
|
||||
mem_change: memChange,
|
||||
status
|
||||
})
|
||||
|
||||
// CPU 이상 감지 + 로그 저장
|
||||
if (cpuChange !== null && Math.abs(cpuChange) >= THRESHOLD) {
|
||||
const level = Math.abs(cpuChange) >= 100 ? 'danger' : 'warning'
|
||||
const direction = cpuChange >= 0 ? '증가' : '감소'
|
||||
const message = `CPU ${direction} 감지 (${prevCpuAvg.toFixed(1)}% → ${currCpuAvg.toFixed(1)}%)`
|
||||
|
||||
anomalies.push({
|
||||
target_id: server.target_id,
|
||||
server_name: server.server_name,
|
||||
metric: 'CPU',
|
||||
prev_avg: prevCpuAvg,
|
||||
curr_avg: currCpuAvg,
|
||||
change_rate: cpuChange,
|
||||
direction: cpuChange >= 0 ? 'up' : 'down'
|
||||
})
|
||||
|
||||
// 중복 아니면 로그 저장
|
||||
if (!recentLogExists.get(server.target_id, 'CPU')) {
|
||||
insertLog.run(server.target_id, server.server_name, 'CPU', level, currCpuAvg, cpuChange, message)
|
||||
}
|
||||
}
|
||||
|
||||
// Memory 이상 감지 + 로그 저장
|
||||
if (memChange !== null && Math.abs(memChange) >= THRESHOLD) {
|
||||
const level = Math.abs(memChange) >= 100 ? 'danger' : 'warning'
|
||||
const direction = memChange >= 0 ? '증가' : '감소'
|
||||
const message = `Memory ${direction} 감지 (${prevMemAvg.toFixed(1)}% → ${currMemAvg.toFixed(1)}%)`
|
||||
|
||||
anomalies.push({
|
||||
target_id: server.target_id,
|
||||
server_name: server.server_name,
|
||||
metric: 'Memory',
|
||||
prev_avg: prevMemAvg,
|
||||
curr_avg: currMemAvg,
|
||||
change_rate: memChange,
|
||||
direction: memChange >= 0 ? 'up' : 'down'
|
||||
})
|
||||
|
||||
if (!recentLogExists.get(server.target_id, 'Memory')) {
|
||||
insertLog.run(server.target_id, server.server_name, 'Memory', level, currMemAvg, memChange, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anomalies.sort((a, b) => Math.abs(b.change_rate) - Math.abs(a.change_rate))
|
||||
|
||||
return {
|
||||
anomalies,
|
||||
servers: serverResults,
|
||||
threshold: THRESHOLD,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user