UNPKG

@codai/memorai-core

Version:

Simplified advanced memory engine - no tiers, just powerful semantic search with persistence

239 lines (238 loc) 8.99 kB
/** * Performance monitoring and metrics collection for Memorai * Tracks query performance, memory usage, and system health */ export class PerformanceMonitor { constructor(windowSizeMs = 60000, maxHistorySize = 10000) { this.metrics = []; this.activeConnections = 0; this.totalConnections = 0; this.maxConcurrentConnections = 0; this.windowSizeMs = windowSizeMs; this.maxHistorySize = maxHistorySize; } /** * Record a query operation for metrics tracking */ recordQuery(metrics) { this.metrics.push(metrics); // Cleanup old metrics to prevent memory leaks if (this.metrics.length > this.maxHistorySize) { this.metrics = this.metrics.slice(-this.maxHistorySize); } } /** * Start timing a query operation */ startQuery(operation, tenantId, agentId) { const startTime = Date.now(); return { finish: (success, error, resultCount, cacheHit) => { const endTime = Date.now(); const queryMetrics = { operation, startTime, endTime, duration: endTime - startTime, success, tenantId, }; if (agentId !== undefined) { queryMetrics.agentId = agentId; } if (error !== undefined) { queryMetrics.error = error; } if (resultCount !== undefined) { queryMetrics.resultCount = resultCount; } if (cacheHit !== undefined) { queryMetrics.cacheHit = cacheHit; } this.recordQuery(queryMetrics); }, }; } /** * Get performance metrics for the current time window */ getMetrics() { const now = Date.now(); const windowStart = new Date(now - this.windowSizeMs); const windowEnd = new Date(now); // Filter metrics to current window const windowMetrics = this.metrics.filter(m => m.endTime >= now - this.windowSizeMs); const totalQueries = windowMetrics.length; const successfulQueries = windowMetrics.filter(m => m.success).length; const queryErrors = totalQueries - successfulQueries; // Calculate averages const avgQueryTime = totalQueries > 0 ? windowMetrics.reduce((sum, m) => sum + m.duration, 0) / totalQueries : 0; // Operation-specific metrics const rememberOps = windowMetrics.filter(m => m.operation === 'remember'); const recallOps = windowMetrics.filter(m => m.operation === 'recall'); const forgetOps = windowMetrics.filter(m => m.operation === 'forget'); const contextOps = windowMetrics.filter(m => m.operation === 'context'); const avgTime = (ops) => ops.length > 0 ? ops.reduce((sum, m) => sum + m.duration, 0) / ops.length : 0; // Cache metrics const cacheableOps = windowMetrics.filter(m => m.cacheHit !== undefined); const cacheHits = cacheableOps.filter(m => m.cacheHit === true).length; const cacheMisses = cacheableOps.filter(m => m.cacheHit === false).length; const cacheHitRate = cacheableOps.length > 0 ? cacheHits / cacheableOps.length : 0; return { queryCount: totalQueries, avgQueryTime, queryErrors, querySuccessRate: totalQueries > 0 ? successfulQueries / totalQueries : 0, rememberCount: rememberOps.length, recallCount: recallOps.length, forgetCount: forgetOps.length, contextCount: contextOps.length, avgRememberTime: avgTime(rememberOps), avgRecallTime: avgTime(recallOps), avgForgetTime: avgTime(forgetOps), avgContextTime: avgTime(contextOps), cacheHits, cacheMisses, cacheHitRate, memoryUsage: process.memoryUsage().heapUsed / 1024 / 1024, // MB activeConnections: this.activeConnections, windowStart, windowEnd, }; } /** * Get detailed query history for debugging */ getQueryHistory(count = 100) { return this.metrics.slice(-count).reverse(); } /** * Get metrics for a specific tenant */ getTenantMetrics(tenantId) { const tenantMetrics = this.metrics.filter(m => m.tenantId === tenantId); // Create a temporary monitor with only tenant metrics const tempMonitor = new PerformanceMonitor(this.windowSizeMs, this.maxHistorySize); tempMonitor.metrics = tenantMetrics; return tempMonitor.getMetrics(); } /** * Get slow query analysis */ getSlowQueries(thresholdMs = 1000, count = 10) { return this.metrics .filter(m => m.duration > thresholdMs) .sort((a, b) => b.duration - a.duration) .slice(0, count); } /** * Get error analysis */ getErrorAnalysis() { const errorCounts = new Map(); this.metrics .filter(m => !m.success && m.error) .forEach(m => { const existing = errorCounts.get(m.error) || { count: 0, lastOccurrence: 0, }; errorCounts.set(m.error, { count: existing.count + 1, lastOccurrence: Math.max(existing.lastOccurrence, m.endTime), }); }); return Array.from(errorCounts.entries()) .map(([error, data]) => ({ error, count: data.count, lastOccurrence: new Date(data.lastOccurrence), })) .sort((a, b) => b.count - a.count); } /** * Clear metrics history (useful for testing) */ clearMetrics() { this.metrics = []; } /** * Track a new connection */ recordConnection(type) { if (type === 'open') { this.activeConnections++; this.totalConnections++; this.maxConcurrentConnections = Math.max(this.maxConcurrentConnections, this.activeConnections); } else if (type === 'close') { this.activeConnections = Math.max(0, this.activeConnections - 1); } } /** * Get connection statistics */ getConnectionStats() { return { active: this.activeConnections, total: this.totalConnections, maxConcurrent: this.maxConcurrentConnections, }; } /** * Reset connection counters (useful for testing) */ resetConnections() { this.activeConnections = 0; this.totalConnections = 0; this.maxConcurrentConnections = 0; } /** * Export metrics for external monitoring systems */ exportMetrics() { const metrics = this.getMetrics(); // Prometheus format const prometheus = [ `# HELP memorai_query_total Total number of queries`, `# TYPE memorai_query_total counter`, `memorai_query_total ${metrics.queryCount}`, ``, `# HELP memorai_query_duration_avg Average query duration in milliseconds`, `# TYPE memorai_query_duration_avg gauge`, `memorai_query_duration_avg ${metrics.avgQueryTime}`, ``, `# HELP memorai_query_success_rate Query success rate (0-1)`, `# TYPE memorai_query_success_rate gauge`, `memorai_query_success_rate ${metrics.querySuccessRate}`, ``, `# HELP memorai_cache_hit_rate Cache hit rate (0-1)`, `# TYPE memorai_cache_hit_rate gauge`, `memorai_cache_hit_rate ${metrics.cacheHitRate}`, ``, `# HELP memorai_memory_usage_mb Memory usage in megabytes`, `# TYPE memorai_memory_usage_mb gauge`, `memorai_memory_usage_mb ${metrics.memoryUsage}`, ].join('\n'); // CSV format const csv = [ 'metric,value,timestamp', `query_count,${metrics.queryCount},${metrics.windowEnd.toISOString()}`, `avg_query_time,${metrics.avgQueryTime},${metrics.windowEnd.toISOString()}`, `query_errors,${metrics.queryErrors},${metrics.windowEnd.toISOString()}`, `success_rate,${metrics.querySuccessRate},${metrics.windowEnd.toISOString()}`, `cache_hit_rate,${metrics.cacheHitRate},${metrics.windowEnd.toISOString()}`, `memory_usage_mb,${metrics.memoryUsage},${metrics.windowEnd.toISOString()}`, ].join('\n'); return { prometheus, json: metrics, csv, }; } } // Global singleton instance export const performanceMonitor = new PerformanceMonitor();