import { query } from '../../utils/db' export default defineEventHandler(async (event) => { const SLOPE_THRESHOLD = 0.5 const MIN_SAMPLES = 10 const WINDOW_MINUTES = 30 const servers = await query(` SELECT target_id, name as server_name FROM server_targets WHERE is_active = 1 ORDER BY name `) const anomalies: any[] = [] const serverResults: any[] = [] for (const server of servers) { const snapshots = await query(` SELECT cpu_usage as cpu_percent, memory_usage as memory_percent, checked_at as collected_at, EXTRACT(EPOCH FROM (NOW() - checked_at)) / 60 as minutes_ago FROM server_logs WHERE target_id = $1 AND is_success = 1 AND checked_at >= NOW() - INTERVAL '${WINDOW_MINUTES} minutes' ORDER BY checked_at ASC `, [server.target_id]) if (snapshots.length < MIN_SAMPLES) { serverResults.push({ target_id: server.target_id, server_name: server.server_name, cpu_current: snapshots.length > 0 ? snapshots[snapshots.length - 1].cpu_percent : null, mem_current: snapshots.length > 0 ? snapshots[snapshots.length - 1].memory_percent : null, cpu_slope: null, mem_slope: null, cpu_trend: null, mem_trend: null, sample_count: snapshots.length, status: 'insufficient' }) continue } const n = snapshots.length const current = snapshots[n - 1] const currCpu = Number(current.cpu_percent) || 0 const currMem = Number(current.memory_percent) || 0 const cpuPoints = snapshots.map((s: any, i: number) => ({ x: i, y: Number(s.cpu_percent) || 0 })) const memPoints = snapshots.map((s: any, i: number) => ({ x: i, y: Number(s.memory_percent) || 0 })) function linearRegression(points: { x: number, y: number }[]): { slope: number, intercept: number, r2: number } { const n = points.length let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0 for (const p of points) { sumX += p.x sumY += p.y sumXY += p.x * p.y sumX2 += p.x * p.x } const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX) const intercept = (sumY - slope * sumX) / n const yMean = sumY / n let ssTotal = 0, ssResidual = 0 for (const p of points) { const yPred = slope * p.x + intercept ssTotal += Math.pow(p.y - yMean, 2) ssResidual += Math.pow(p.y - yPred, 2) } const r2 = ssTotal > 0 ? 1 - (ssResidual / ssTotal) : 0 return { slope, intercept, r2 } } const cpuReg = linearRegression(cpuPoints) const memReg = linearRegression(memPoints) const cpuSlopePerMin = (cpuReg.slope * n) / WINDOW_MINUTES const memSlopePerMin = (memReg.slope * n) / WINDOW_MINUTES function getTrend(slope: number, r2: number): string { if (r2 < 0.3) return 'unstable' if (slope >= SLOPE_THRESHOLD) return 'rising' if (slope <= -SLOPE_THRESHOLD) return 'falling' return 'stable' } const cpuTrend = getTrend(cpuSlopePerMin, cpuReg.r2) const memTrend = getTrend(memSlopePerMin, memReg.r2) let status = 'normal' if (cpuTrend === 'rising' || memTrend === 'rising') status = 'warning' if (cpuSlopePerMin >= 1.0 || memSlopePerMin >= 1.0) status = 'danger' serverResults.push({ target_id: server.target_id, server_name: server.server_name, cpu_current: currCpu, mem_current: currMem, cpu_slope: cpuSlopePerMin, mem_slope: memSlopePerMin, cpu_trend: cpuTrend, mem_trend: memTrend, cpu_r2: cpuReg.r2, mem_r2: memReg.r2, sample_count: snapshots.length, status }) if (cpuTrend === 'rising' && cpuReg.r2 >= 0.3) { const level = cpuSlopePerMin >= 1.0 ? 'danger' : 'warning' anomalies.push({ target_id: server.target_id, server_name: server.server_name, metric: 'CPU', current: currCpu, slope: cpuSlopePerMin, r2: cpuReg.r2, trend: cpuTrend, level }) } if (memTrend === 'rising' && memReg.r2 >= 0.3) { const level = memSlopePerMin >= 1.0 ? 'danger' : 'warning' anomalies.push({ target_id: server.target_id, server_name: server.server_name, metric: 'Memory', current: currMem, slope: memSlopePerMin, r2: memReg.r2, trend: memTrend, level }) } } anomalies.sort((a, b) => b.slope - a.slope) return { anomalies, servers: serverResults, config: { slope_threshold: SLOPE_THRESHOLD, window_minutes: WINDOW_MINUTES, min_samples: MIN_SAMPLES }, timestamp: new Date().toISOString() } })