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() } })