import { query } from '../../utils/db' export default defineEventHandler(async (event) => { const WARNING_Z = 2.0 const DANGER_Z = 3.0 const servers = await query(` SELECT target_id, server_name FROM server_targets WHERE is_active = 1 ORDER BY server_name `) const anomalies: any[] = [] const serverResults: any[] = [] for (const server of servers) { const snapshots = await query(` SELECT cpu_percent, memory_percent, collected_at FROM server_snapshots WHERE target_id = $1 AND collected_at::timestamp >= NOW() - INTERVAL '1 hour' ORDER BY collected_at DESC `, [server.target_id]) if (snapshots.length < 10) { serverResults.push({ target_id: server.target_id, server_name: server.server_name, cpu_zscore: null, mem_zscore: null, cpu_avg: null, cpu_std: null, mem_avg: null, mem_std: null, sample_count: snapshots.length, status: 'insufficient' }) continue } const current = snapshots[0] const currCpu = Number(current.cpu_percent) || 0 const currMem = Number(current.memory_percent) || 0 const cpuValues = snapshots.map((s: any) => Number(s.cpu_percent) || 0) const memValues = snapshots.map((s: any) => Number(s.memory_percent) || 0) const cpuAvg = cpuValues.reduce((a: number, b: number) => a + b, 0) / cpuValues.length const memAvg = memValues.reduce((a: number, b: number) => a + b, 0) / memValues.length const cpuVariance = cpuValues.reduce((sum: number, val: number) => sum + Math.pow(val - cpuAvg, 2), 0) / cpuValues.length const memVariance = memValues.reduce((sum: number, val: number) => sum + Math.pow(val - memAvg, 2), 0) / memValues.length const cpuStd = Math.sqrt(cpuVariance) const memStd = Math.sqrt(memVariance) const cpuZscore = cpuStd > 0.1 ? (currCpu - cpuAvg) / cpuStd : 0 const memZscore = memStd > 0.1 ? (currMem - memAvg) / memStd : 0 let status = 'normal' const maxZ = Math.max(Math.abs(cpuZscore), Math.abs(memZscore)) if (maxZ >= DANGER_Z) status = 'danger' else if (maxZ >= WARNING_Z) status = 'warning' serverResults.push({ target_id: server.target_id, server_name: server.server_name, cpu_current: currCpu, mem_current: currMem, cpu_zscore: cpuZscore, mem_zscore: memZscore, cpu_avg: cpuAvg, cpu_std: cpuStd, mem_avg: memAvg, mem_std: memStd, sample_count: snapshots.length, status }) // CPU 이상 감지 if (Math.abs(cpuZscore) >= WARNING_Z) { const level = Math.abs(cpuZscore) >= DANGER_Z ? 'danger' : 'warning' anomalies.push({ target_id: server.target_id, server_name: server.server_name, metric: 'CPU', current: currCpu, avg: cpuAvg, std: cpuStd, zscore: cpuZscore, direction: cpuZscore >= 0 ? 'up' : 'down', level }) } // Memory 이상 감지 if (Math.abs(memZscore) >= WARNING_Z) { const level = Math.abs(memZscore) >= DANGER_Z ? 'danger' : 'warning' anomalies.push({ target_id: server.target_id, server_name: server.server_name, metric: 'Memory', current: currMem, avg: memAvg, std: memStd, zscore: memZscore, direction: memZscore >= 0 ? 'up' : 'down', level }) } } anomalies.sort((a, b) => Math.abs(b.zscore) - Math.abs(a.zscore)) return { anomalies, servers: serverResults, thresholds: { warning: WARNING_Z, danger: DANGER_Z }, timestamp: new Date().toISOString() } })