Docker 파일
This commit is contained in:
@@ -1,44 +1,29 @@
|
||||
import { getDb } from '../../utils/db'
|
||||
import { query } from '../../utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = getDb()
|
||||
const SLOPE_THRESHOLD = 0.5 // 분당 0.5% 이상 증가/감소 시 이상
|
||||
const MIN_SAMPLES = 10 // 최소 10개 샘플 필요
|
||||
const WINDOW_MINUTES = 30 // 30분 윈도우
|
||||
const SLOPE_THRESHOLD = 0.5
|
||||
const MIN_SAMPLES = 10
|
||||
const WINDOW_MINUTES = 30
|
||||
|
||||
const servers = db.prepare(`
|
||||
SELECT target_id, server_name
|
||||
const servers = await query<any>(`
|
||||
SELECT target_id, name as server_name
|
||||
FROM server_targets
|
||||
WHERE is_active = 1
|
||||
ORDER BY server_name
|
||||
`).all() as any[]
|
||||
ORDER BY name
|
||||
`)
|
||||
|
||||
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 (?, ?, 'trend', ?, ?, ?, ?, ?)
|
||||
`)
|
||||
|
||||
const recentLogExists = db.prepare(`
|
||||
SELECT 1 FROM anomaly_logs
|
||||
WHERE target_id = ? AND detect_type = 'trend' AND metric = ?
|
||||
AND detected_at > datetime('now', '-1 minute', 'localtime')
|
||||
LIMIT 1
|
||||
`)
|
||||
|
||||
for (const server of servers) {
|
||||
// 최근 30분 데이터 조회
|
||||
const snapshots = db.prepare(`
|
||||
SELECT cpu_percent, memory_percent, collected_at,
|
||||
(julianday('now', 'localtime') - julianday(collected_at)) * 24 * 60 as minutes_ago
|
||||
FROM server_snapshots
|
||||
WHERE target_id = ? AND is_online = 1
|
||||
AND collected_at >= datetime('now', '-${WINDOW_MINUTES} minutes', 'localtime')
|
||||
ORDER BY collected_at ASC
|
||||
`).all(server.target_id) as any[]
|
||||
const snapshots = await query<any>(`
|
||||
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({
|
||||
@@ -56,33 +41,28 @@ export default defineEventHandler(async (event) => {
|
||||
continue
|
||||
}
|
||||
|
||||
// 선형 회귀 계산 (최소제곱법)
|
||||
// y = ax + b, a = slope (기울기)
|
||||
const n = snapshots.length
|
||||
const current = snapshots[n - 1]
|
||||
const currCpu = current.cpu_percent ?? 0
|
||||
const currMem = current.memory_percent ?? 0
|
||||
const currCpu = Number(current.cpu_percent) || 0
|
||||
const currMem = Number(current.memory_percent) || 0
|
||||
|
||||
// x = 시간 (분), y = 값
|
||||
const cpuPoints = snapshots.map((s, i) => ({ x: i, y: s.cpu_percent ?? 0 }))
|
||||
const memPoints = snapshots.map((s, i) => ({ x: i, y: s.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, sumY2 = 0
|
||||
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
|
||||
sumY2 += p.y * p.y
|
||||
}
|
||||
|
||||
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)
|
||||
const intercept = (sumY - slope * sumX) / n
|
||||
|
||||
// R² (결정계수) 계산
|
||||
const yMean = sumY / n
|
||||
let ssTotal = 0, ssResidual = 0
|
||||
for (const p of points) {
|
||||
@@ -98,14 +78,11 @@ export default defineEventHandler(async (event) => {
|
||||
const cpuReg = linearRegression(cpuPoints)
|
||||
const memReg = linearRegression(memPoints)
|
||||
|
||||
// 분당 기울기로 환산 (수집 간격 고려)
|
||||
const totalMinutes = WINDOW_MINUTES
|
||||
const cpuSlopePerMin = (cpuReg.slope * n) / totalMinutes
|
||||
const memSlopePerMin = (memReg.slope * n) / totalMinutes
|
||||
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 (r2 < 0.3) return 'unstable'
|
||||
if (slope >= SLOPE_THRESHOLD) return 'rising'
|
||||
if (slope <= -SLOPE_THRESHOLD) return 'falling'
|
||||
return 'stable'
|
||||
@@ -114,10 +91,9 @@ export default defineEventHandler(async (event) => {
|
||||
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' // 분당 1% 이상
|
||||
if (cpuSlopePerMin >= 1.0 || memSlopePerMin >= 1.0) status = 'danger'
|
||||
|
||||
serverResults.push({
|
||||
target_id: server.target_id,
|
||||
@@ -134,11 +110,8 @@ export default defineEventHandler(async (event) => {
|
||||
status
|
||||
})
|
||||
|
||||
// CPU 이상감지 + 로그 저장
|
||||
if (cpuTrend === 'rising' && cpuReg.r2 >= 0.3) {
|
||||
const level = cpuSlopePerMin >= 1.0 ? 'danger' : 'warning'
|
||||
const message = `CPU 지속 상승 중 (분당 +${cpuSlopePerMin.toFixed(2)}%, R²=${cpuReg.r2.toFixed(2)})`
|
||||
|
||||
anomalies.push({
|
||||
target_id: server.target_id,
|
||||
server_name: server.server_name,
|
||||
@@ -149,17 +122,10 @@ export default defineEventHandler(async (event) => {
|
||||
trend: cpuTrend,
|
||||
level
|
||||
})
|
||||
|
||||
if (!recentLogExists.get(server.target_id, 'CPU')) {
|
||||
insertLog.run(server.target_id, server.server_name, 'CPU', level, currCpu, cpuSlopePerMin, message)
|
||||
}
|
||||
}
|
||||
|
||||
// Memory 이상감지 + 로그 저장
|
||||
if (memTrend === 'rising' && memReg.r2 >= 0.3) {
|
||||
const level = memSlopePerMin >= 1.0 ? 'danger' : 'warning'
|
||||
const message = `Memory 지속 상승 중 (분당 +${memSlopePerMin.toFixed(2)}%, R²=${memReg.r2.toFixed(2)})`
|
||||
|
||||
anomalies.push({
|
||||
target_id: server.target_id,
|
||||
server_name: server.server_name,
|
||||
@@ -170,10 +136,6 @@ export default defineEventHandler(async (event) => {
|
||||
trend: memTrend,
|
||||
level
|
||||
})
|
||||
|
||||
if (!recentLogExists.get(server.target_id, 'Memory')) {
|
||||
insertLog.run(server.target_id, server.server_name, 'Memory', level, currMem, memSlopePerMin, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user