Files
Sankofa/api/src/services/anomaly-detection.ts
T
defiQUG 9daf1fd378 Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements
- 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
2025-12-12 18:01:35 -08:00

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,
}))
}