155 lines
4.9 KiB
TypeScript
155 lines
4.9 KiB
TypeScript
import { query } from '../../utils/db'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const DEVIATION_THRESHOLD = 2.0
|
|
|
|
const servers = await query<any>(`
|
|
SELECT target_id, name as server_name
|
|
FROM server_targets
|
|
WHERE is_active = 1
|
|
ORDER BY 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<any>(`
|
|
SELECT cpu_usage as cpu_percent, memory_usage as memory_percent, checked_at as collected_at
|
|
FROM server_logs
|
|
WHERE target_id = $1
|
|
AND checked_at >= NOW() - INTERVAL '14 days'
|
|
AND EXTRACT(HOUR FROM checked_at) = $2
|
|
AND (
|
|
($3 = 'weekend' AND EXTRACT(DOW FROM checked_at) IN (0, 6))
|
|
OR
|
|
($3 = 'weekday' AND EXTRACT(DOW FROM checked_at) NOT IN (0, 6))
|
|
)
|
|
ORDER BY checked_at DESC
|
|
`, [server.target_id, currentHour, dayType])
|
|
|
|
// 현재 값
|
|
const currentRows = await query<any>(`
|
|
SELECT cpu_usage as cpu_percent, memory_usage as memory_percent
|
|
FROM server_logs
|
|
WHERE target_id = $1
|
|
ORDER BY checked_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()
|
|
}
|
|
})
|