@codai/memorai-core
Version:
Simplified advanced memory engine - no tiers, just powerful semantic search with persistence
239 lines (238 loc) • 8.99 kB
JavaScript
/**
* 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();