import { query } from '../../utils/db' export default defineEventHandler(async (event) => { const DEVIATION_THRESHOLD = 2.0 const servers = await query(` SELECT target_id, server_name FROM server_targets WHERE is_active = 1 ORDER BY server_name `) const now = new Date() const currentHour = now.getHours() const currentDayOfWeek = now.getDay() const isWeekend = currentDayOfWeek === 0 || currentDayOfWeek === 6 const dayType = isWeekend ? 'weekend' : 'weekday' const anomalies: any[] = [] const serverResults: any[] = [] for (const server of servers) { // 최근 14일 동일 시간대 데이터 const historicalData = await query(` SELECT cpu_percent, memory_percent, collected_at FROM server_snapshots WHERE target_id = $1 AND collected_at::timestamp >= NOW() - INTERVAL '14 days' AND EXTRACT(HOUR FROM collected_at::timestamp) = $2 AND ( ($3 = 'weekend' AND EXTRACT(DOW FROM collected_at::timestamp) IN (0, 6)) OR ($3 = 'weekday' AND EXTRACT(DOW FROM collected_at::timestamp) NOT IN (0, 6)) ) ORDER BY collected_at DESC `, [server.target_id, currentHour, dayType]) // 현재 값 const currentRows = await query(` SELECT cpu_percent, memory_percent FROM server_snapshots WHERE target_id = $1 ORDER BY collected_at DESC LIMIT 1 `, [server.target_id]) const current = currentRows[0] if (!current || historicalData.length < 5) { serverResults.push({ target_id: server.target_id, server_name: server.server_name, current_hour: currentHour, day_type: dayType, cpu_current: current?.cpu_percent ?? null, mem_current: current?.memory_percent ?? null, cpu_baseline: null, mem_baseline: null, cpu_deviation: null, mem_deviation: null, sample_count: historicalData.length, status: 'insufficient' }) continue } const currCpu = Number(current.cpu_percent) || 0 const currMem = Number(current.memory_percent) || 0 const cpuValues = historicalData.map((s: any) => Number(s.cpu_percent) || 0) const memValues = historicalData.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 cpuDeviation = cpuStd > 0.1 ? (currCpu - cpuAvg) / cpuStd : 0 const memDeviation = memStd > 0.1 ? (currMem - memAvg) / memStd : 0 let status = 'normal' const maxDev = Math.max(Math.abs(cpuDeviation), Math.abs(memDeviation)) if (maxDev >= 3.0) status = 'danger' else if (maxDev >= DEVIATION_THRESHOLD) status = 'warning' serverResults.push({ target_id: server.target_id, server_name: server.server_name, current_hour: currentHour, day_type: dayType, cpu_current: currCpu, mem_current: currMem, cpu_baseline: { avg: cpuAvg, std: cpuStd }, mem_baseline: { avg: memAvg, std: memStd }, cpu_deviation: cpuDeviation, mem_deviation: memDeviation, sample_count: historicalData.length, status }) // CPU 이상감지 if (Math.abs(cpuDeviation) >= DEVIATION_THRESHOLD) { const level = Math.abs(cpuDeviation) >= 3.0 ? 'danger' : 'warning' anomalies.push({ target_id: server.target_id, server_name: server.server_name, metric: 'CPU', current: currCpu, baseline_avg: cpuAvg, deviation: cpuDeviation, direction: cpuDeviation >= 0 ? 'up' : 'down', level, hour: currentHour, day_type: dayType }) } // Memory 이상감지 if (Math.abs(memDeviation) >= DEVIATION_THRESHOLD) { const level = Math.abs(memDeviation) >= 3.0 ? 'danger' : 'warning' anomalies.push({ target_id: server.target_id, server_name: server.server_name, metric: 'Memory', current: currMem, baseline_avg: memAvg, deviation: memDeviation, direction: memDeviation >= 0 ? 'up' : 'down', level, hour: currentHour, day_type: dayType }) } } anomalies.sort((a, b) => Math.abs(b.deviation) - Math.abs(a.deviation)) return { anomalies, servers: serverResults, context: { current_hour: currentHour, day_type: dayType, day_type_label: isWeekend ? '주말' : '평일', threshold: DEVIATION_THRESHOLD }, timestamp: new Date().toISOString() } })