- Add comprehensive database migrations (001-024) for schema evolution - Enhance API schema with expanded type definitions and resolvers - Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth - Implement new services: AI optimization, billing, blockchain, compliance, marketplace - Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage) - Update Crossplane provider with enhanced VM management capabilities - Add comprehensive test suite for API endpoints and services - Update frontend components with improved GraphQL subscriptions and real-time updates - Enhance security configurations and headers (CSP, CORS, etc.) - Update documentation and configuration files - Add new CI/CD workflows and validation scripts - Implement design system improvements and UI enhancements
318 lines
8.9 KiB
TypeScript
318 lines
8.9 KiB
TypeScript
/**
|
|
* Anomaly Detection Service
|
|
* Detects anomalies in resource metrics and behavior patterns
|
|
*/
|
|
|
|
import { Context } from '../types/context'
|
|
import { getDb } from '../db'
|
|
|
|
export interface Anomaly {
|
|
id: string
|
|
resourceId: string
|
|
metricType: string
|
|
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
|
|
anomalyType: 'SPIKE' | 'DROP' | 'PATTERN' | 'THRESHOLD'
|
|
value: number
|
|
expectedValue?: number
|
|
deviation: number
|
|
timestamp: Date
|
|
description: string
|
|
recommendation?: string
|
|
}
|
|
|
|
export interface AnomalyDetectionConfig {
|
|
resourceId: string
|
|
metricType: string
|
|
threshold?: number
|
|
windowSize?: number // Time window in minutes
|
|
sensitivity?: 'LOW' | 'MEDIUM' | 'HIGH'
|
|
}
|
|
|
|
/**
|
|
* Detect anomalies in resource metrics using statistical methods
|
|
*/
|
|
export async function detectAnomalies(
|
|
context: Context,
|
|
config: AnomalyDetectionConfig
|
|
): Promise<Anomaly[]> {
|
|
const db = getDb()
|
|
const anomalies: Anomaly[] = []
|
|
|
|
// Get historical metrics for the resource
|
|
const windowSize = config.windowSize || 60 // Default 1 hour
|
|
const endTime = new Date()
|
|
const startTime = new Date(endTime.getTime() - windowSize * 60 * 1000)
|
|
|
|
// Query metrics from database
|
|
const metricsQuery = `
|
|
SELECT timestamp, value, labels
|
|
FROM metrics
|
|
WHERE resource_id = $1
|
|
AND metric_type = $2
|
|
AND timestamp >= $3
|
|
AND timestamp <= $4
|
|
ORDER BY timestamp ASC
|
|
`
|
|
|
|
const result = await db.query(metricsQuery, [
|
|
config.resourceId,
|
|
config.metricType,
|
|
startTime,
|
|
endTime,
|
|
])
|
|
|
|
if (result.rows.length < 10) {
|
|
// Not enough data for anomaly detection
|
|
return []
|
|
}
|
|
|
|
const values = result.rows.map((row) => parseFloat(row.value))
|
|
const timestamps = result.rows.map((row) => new Date(row.timestamp))
|
|
|
|
// Calculate statistical baseline
|
|
const mean = values.reduce((a, b) => a + b, 0) / values.length
|
|
const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length
|
|
const stdDev = Math.sqrt(variance)
|
|
|
|
// Determine threshold based on sensitivity
|
|
const sensitivityMultiplier = {
|
|
LOW: 3.0,
|
|
MEDIUM: 2.5,
|
|
HIGH: 2.0,
|
|
}[config.sensitivity || 'MEDIUM']
|
|
|
|
const threshold = config.threshold || mean + (sensitivityMultiplier * stdDev)
|
|
const lowerThreshold = mean - (sensitivityMultiplier * stdDev)
|
|
|
|
// Detect anomalies
|
|
for (let i = 0; i < values.length; i++) {
|
|
const value = values[i]
|
|
const timestamp = timestamps[i]
|
|
|
|
// Check for threshold violations
|
|
if (value > threshold) {
|
|
const deviation = ((value - mean) / stdDev) * 100
|
|
const severity = determineSeverity(deviation, 'SPIKE')
|
|
|
|
anomalies.push({
|
|
id: `anomaly-${config.resourceId}-${timestamp.getTime()}`,
|
|
resourceId: config.resourceId,
|
|
metricType: config.metricType,
|
|
severity,
|
|
anomalyType: 'SPIKE',
|
|
value,
|
|
expectedValue: mean,
|
|
deviation,
|
|
timestamp,
|
|
description: `${config.metricType} spike detected: ${value.toFixed(2)} (expected: ${mean.toFixed(2)})`,
|
|
recommendation: getRecommendation(config.metricType, 'SPIKE', value, mean),
|
|
})
|
|
} else if (value < lowerThreshold) {
|
|
const deviation = ((mean - value) / stdDev) * 100
|
|
const severity = determineSeverity(deviation, 'DROP')
|
|
|
|
anomalies.push({
|
|
id: `anomaly-${config.resourceId}-${timestamp.getTime()}`,
|
|
resourceId: config.resourceId,
|
|
metricType: config.metricType,
|
|
severity,
|
|
anomalyType: 'DROP',
|
|
value,
|
|
expectedValue: mean,
|
|
deviation,
|
|
timestamp,
|
|
description: `${config.metricType} drop detected: ${value.toFixed(2)} (expected: ${mean.toFixed(2)})`,
|
|
recommendation: getRecommendation(config.metricType, 'DROP', value, mean),
|
|
})
|
|
}
|
|
|
|
// Detect pattern anomalies (sudden changes)
|
|
if (i > 0) {
|
|
const previousValue = values[i - 1]
|
|
const change = Math.abs(value - previousValue)
|
|
const changePercent = (change / previousValue) * 100
|
|
|
|
if (changePercent > 50) {
|
|
// Significant change detected
|
|
const deviation = changePercent
|
|
const severity = determineSeverity(deviation, 'PATTERN')
|
|
|
|
anomalies.push({
|
|
id: `anomaly-pattern-${config.resourceId}-${timestamp.getTime()}`,
|
|
resourceId: config.resourceId,
|
|
metricType: config.metricType,
|
|
severity,
|
|
anomalyType: 'PATTERN',
|
|
value,
|
|
expectedValue: previousValue,
|
|
deviation,
|
|
timestamp,
|
|
description: `Sudden ${config.metricType} change: ${changePercent.toFixed(2)}% change from ${previousValue.toFixed(2)} to ${value.toFixed(2)}`,
|
|
recommendation: getRecommendation(config.metricType, 'PATTERN', value, previousValue),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store anomalies in database
|
|
if (anomalies.length > 0) {
|
|
await storeAnomalies(context, anomalies)
|
|
}
|
|
|
|
return anomalies
|
|
}
|
|
|
|
/**
|
|
* Detect anomalies across all resources
|
|
*/
|
|
export async function detectAnomaliesForAllResources(
|
|
context: Context,
|
|
metricTypes: string[] = ['CPU_USAGE', 'MEMORY_USAGE', 'ERROR_RATE']
|
|
): Promise<Anomaly[]> {
|
|
const db = getDb()
|
|
const allAnomalies: Anomaly[] = []
|
|
|
|
// Get all active resources
|
|
const resourcesQuery = `
|
|
SELECT DISTINCT resource_id, metric_type
|
|
FROM metrics
|
|
WHERE timestamp >= NOW() - INTERVAL '1 hour'
|
|
`
|
|
|
|
const result = await db.query(resourcesQuery)
|
|
|
|
const resourceMetricPairs = new Set<string>()
|
|
for (const row of result.rows) {
|
|
const key = `${row.resource_id}:${row.metric_type}`
|
|
if (!resourceMetricPairs.has(key) && metricTypes.includes(row.metric_type)) {
|
|
resourceMetricPairs.add(key)
|
|
|
|
const anomalies = await detectAnomalies(context, {
|
|
resourceId: row.resource_id,
|
|
metricType: row.metric_type,
|
|
})
|
|
|
|
allAnomalies.push(...anomalies)
|
|
}
|
|
}
|
|
|
|
return allAnomalies
|
|
}
|
|
|
|
/**
|
|
* Store anomalies in database
|
|
*/
|
|
async function storeAnomalies(context: Context, anomalies: Anomaly[]): Promise<void> {
|
|
const db = getDb()
|
|
|
|
for (const anomaly of anomalies) {
|
|
await db.query(
|
|
`INSERT INTO anomalies (
|
|
id, resource_id, metric_type, severity, anomaly_type,
|
|
value, expected_value, deviation, timestamp, description, recommendation
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
severity = EXCLUDED.severity,
|
|
value = EXCLUDED.value,
|
|
deviation = EXCLUDED.deviation,
|
|
updated_at = NOW()`,
|
|
[
|
|
anomaly.id,
|
|
anomaly.resourceId,
|
|
anomaly.metricType,
|
|
anomaly.severity,
|
|
anomaly.anomalyType,
|
|
anomaly.value,
|
|
anomaly.expectedValue,
|
|
anomaly.deviation,
|
|
anomaly.timestamp,
|
|
anomaly.description,
|
|
anomaly.recommendation,
|
|
]
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine severity based on deviation
|
|
*/
|
|
function determineSeverity(deviation: number, type: string): Anomaly['severity'] {
|
|
const absDeviation = Math.abs(deviation)
|
|
|
|
if (absDeviation > 200) return 'CRITICAL'
|
|
if (absDeviation > 100) return 'HIGH'
|
|
if (absDeviation > 50) return 'MEDIUM'
|
|
return 'LOW'
|
|
}
|
|
|
|
/**
|
|
* Get recommendation based on anomaly type
|
|
*/
|
|
function getRecommendation(
|
|
metricType: string,
|
|
anomalyType: string,
|
|
currentValue: number,
|
|
expectedValue: number
|
|
): string {
|
|
if (metricType === 'CPU_USAGE' && anomalyType === 'SPIKE') {
|
|
return 'Consider scaling up resources or optimizing application performance'
|
|
}
|
|
if (metricType === 'MEMORY_USAGE' && anomalyType === 'SPIKE') {
|
|
return 'Check for memory leaks or increase memory allocation'
|
|
}
|
|
if (metricType === 'ERROR_RATE' && anomalyType === 'SPIKE') {
|
|
return 'Investigate application errors and check logs for issues'
|
|
}
|
|
if (metricType === 'NETWORK_THROUGHPUT' && anomalyType === 'SPIKE') {
|
|
return 'Monitor network capacity and consider load balancing'
|
|
}
|
|
if (anomalyType === 'DROP') {
|
|
return 'Verify resource is functioning correctly and check for connectivity issues'
|
|
}
|
|
return 'Review resource configuration and monitor for continued anomalies'
|
|
}
|
|
|
|
/**
|
|
* Get recent anomalies for a resource
|
|
*/
|
|
export async function getAnomalies(
|
|
context: Context,
|
|
resourceId?: string,
|
|
limit: number = 100
|
|
): Promise<Anomaly[]> {
|
|
const db = getDb()
|
|
|
|
let query = `
|
|
SELECT * FROM anomalies
|
|
WHERE 1=1
|
|
`
|
|
const params: any[] = []
|
|
let paramCount = 1
|
|
|
|
if (resourceId) {
|
|
query += ` AND resource_id = $${paramCount}`
|
|
params.push(resourceId)
|
|
paramCount++
|
|
}
|
|
|
|
query += ` ORDER BY timestamp DESC LIMIT $${paramCount}`
|
|
params.push(limit)
|
|
|
|
const result = await db.query(query, params)
|
|
|
|
return result.rows.map((row) => ({
|
|
id: row.id,
|
|
resourceId: row.resource_id,
|
|
metricType: row.metric_type,
|
|
severity: row.severity,
|
|
anomalyType: row.anomaly_type,
|
|
value: parseFloat(row.value),
|
|
expectedValue: row.expected_value ? parseFloat(row.expected_value) : undefined,
|
|
deviation: parseFloat(row.deviation),
|
|
timestamp: new Date(row.timestamp),
|
|
description: row.description,
|
|
recommendation: row.recommendation,
|
|
}))
|
|
}
|
|
|