시스템 모니터
This commit is contained in:
149
backend/api/anomaly/zscore.get.ts
Normal file
149
backend/api/anomaly/zscore.get.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { getDb } from '../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = getDb()
|
||||
const WARNING_Z = 2.0
|
||||
const DANGER_Z = 3.0
|
||||
|
||||
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[] = []
|
||||
|
||||
// 로그 저장용
|
||||
const insertLog = db.prepare(`
|
||||
INSERT INTO anomaly_logs (target_id, server_name, detect_type, metric, level, current_value, threshold_value, message)
|
||||
VALUES (?, ?, 'zscore', ?, ?, ?, ?, ?)
|
||||
`)
|
||||
|
||||
const recentLogExists = db.prepare(`
|
||||
SELECT 1 FROM anomaly_logs
|
||||
WHERE target_id = ? AND detect_type = 'zscore' 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 = ?
|
||||
AND collected_at >= datetime('now', '-1 hour', 'localtime')
|
||||
ORDER BY collected_at DESC
|
||||
`).all(server.target_id) as any[]
|
||||
|
||||
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 = current.cpu_percent ?? 0
|
||||
const currMem = current.memory_percent ?? 0
|
||||
|
||||
const cpuValues = snapshots.map(s => s.cpu_percent ?? 0)
|
||||
const memValues = snapshots.map(s => s.memory_percent ?? 0)
|
||||
|
||||
const cpuAvg = cpuValues.reduce((a, b) => a + b, 0) / cpuValues.length
|
||||
const memAvg = memValues.reduce((a, b) => a + b, 0) / memValues.length
|
||||
|
||||
const cpuVariance = cpuValues.reduce((sum, val) => sum + Math.pow(val - cpuAvg, 2), 0) / cpuValues.length
|
||||
const memVariance = memValues.reduce((sum, val) => 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'
|
||||
const direction = cpuZscore >= 0 ? '높음' : '낮음'
|
||||
const message = `CPU 평균 대비 ${Math.abs(cpuZscore).toFixed(1)}σ ${direction} (평균: ${cpuAvg.toFixed(1)}%, 현재: ${currCpu.toFixed(1)}%)`
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
if (!recentLogExists.get(server.target_id, 'CPU')) {
|
||||
insertLog.run(server.target_id, server.server_name, 'CPU', level, currCpu, cpuZscore, message)
|
||||
}
|
||||
}
|
||||
|
||||
// Memory 이상 감지 + 로그 저장
|
||||
if (Math.abs(memZscore) >= WARNING_Z) {
|
||||
const level = Math.abs(memZscore) >= DANGER_Z ? 'danger' : 'warning'
|
||||
const direction = memZscore >= 0 ? '높음' : '낮음'
|
||||
const message = `Memory 평균 대비 ${Math.abs(memZscore).toFixed(1)}σ ${direction} (평균: ${memAvg.toFixed(1)}%, 현재: ${currMem.toFixed(1)}%)`
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
if (!recentLogExists.get(server.target_id, 'Memory')) {
|
||||
insertLog.run(server.target_id, server.server_name, 'Memory', level, currMem, memZscore, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user