/** * Metrics Augmentation - Optional Performance & Usage Metrics * * Replaces the hardcoded StatisticsCollector in Brainy with an optional augmentation. * Tracks performance metrics, usage patterns, and system statistics. * * Zero-config: Automatically enabled for observability * Can be disabled or customized via augmentation registry */ import { BaseAugmentation } from './brainyAugmentation.js'; import { StatisticsCollector } from '../utils/statisticsCollector.js'; /** * MetricsAugmentation - Makes metrics collection optional and pluggable * * Features: * - Performance tracking (search latency, throughput) * - Usage patterns (content types, verb types) * - Storage metrics (sizes, counts) * - Zero-config with smart defaults */ export class MetricsAugmentation extends BaseAugmentation { constructor(config = {}) { super(); this.metadata = 'readonly'; // Reads metadata for metrics this.name = 'metrics'; this.timing = 'after'; this.operations = ['add', 'update', 'search', 'find', 'similar', 'delete', 'relate', 'unrelate', 'clear', 'all']; this.priority = 40; // Low priority, runs after other augmentations this.statisticsCollector = null; this.metricsTimer = null; this.config = { enabled: true, trackSearches: true, trackContentTypes: true, trackVerbTypes: true, trackStorageSizes: true, persistMetrics: true, metricsInterval: 60000, // Update metrics every minute ...config }; } async onInitialize() { if (!this.config.enabled) { this.log('Metrics augmentation disabled by configuration'); return; } // Initialize statistics collector this.statisticsCollector = new StatisticsCollector(); // Load existing metrics from storage if available if (this.config.persistMetrics && this.context?.storage) { try { const storage = this.context.storage; const existingStats = await storage.getStatistics?.(); if (existingStats) { this.statisticsCollector.mergeFromStorage(existingStats); this.log('Loaded existing metrics from storage'); } } catch (e) { this.log('Could not load existing metrics', 'info'); } } // Start metrics update timer if (this.config.metricsInterval && this.config.metricsInterval > 0) { this.startMetricsTimer(); } this.log('Metrics augmentation initialized'); } async onShutdown() { // Stop metrics timer if (this.metricsTimer) { clearInterval(this.metricsTimer); this.metricsTimer = null; } // Persist final metrics if (this.config.persistMetrics && this.statisticsCollector && this.context?.storage) { try { await this.persistMetrics(); } catch (error) { this.log('Error persisting metrics during shutdown', 'warn'); } } this.statisticsCollector = null; this.log('Metrics augmentation shut down'); } /** * Execute augmentation - track metrics for operations */ async execute(operation, params, next) { // If metrics disabled, just pass through if (!this.statisticsCollector || !this.config.enabled) { return next(); } // Track operation timing const startTime = Date.now(); try { const result = await next(); const duration = Date.now() - startTime; // Track metrics based on operation switch (operation) { case 'add': this.handleAdd(params, duration); break; case 'search': this.handleSearch(params, duration); break; case 'delete': this.handleDelete(duration); break; case 'clear': this.handleClear(); break; } return result; } catch (error) { // Error tracking removed - StatisticsCollector doesn't have trackError method // Could be added later if needed throw error; } } /** * Handle add operation metrics */ handleAdd(params, duration) { if (!this.statisticsCollector) return; // Track update this.statisticsCollector.trackUpdate(); // Track content type if available if (this.config.trackContentTypes && params.metadata?.noun) { this.statisticsCollector.trackContentType(params.metadata.noun); } // Track verb type if it's a verb operation if (this.config.trackVerbTypes && params.metadata?.verb) { this.statisticsCollector.trackVerbType(params.metadata.verb); } this.log(`Add operation completed in ${duration}ms`, 'info'); } /** * Handle search operation metrics */ handleSearch(params, duration) { if (!this.statisticsCollector || !this.config.trackSearches) return; const { query } = params; this.statisticsCollector.trackSearch(query || '', duration); this.log(`Search completed in ${duration}ms`, 'info'); } /** * Handle delete operation metrics */ handleDelete(duration) { if (!this.statisticsCollector) return; this.statisticsCollector.trackUpdate(); this.log(`Delete operation completed in ${duration}ms`, 'info'); } /** * Handle clear operation - reset metrics */ handleClear() { if (!this.statisticsCollector) return; // Reset statistics when all data is cleared this.statisticsCollector = new StatisticsCollector(); this.log('Metrics reset due to clear operation'); } /** * Start periodic metrics update timer */ startMetricsTimer() { if (this.metricsTimer) return; this.metricsTimer = setInterval(async () => { await this.updateStorageMetrics(); if (this.config.persistMetrics) { await this.persistMetrics(); } }, this.config.metricsInterval); } /** * Update storage size metrics */ async updateStorageMetrics() { if (!this.statisticsCollector || !this.config.trackStorageSizes) return; if (!this.context?.storage) return; try { const storage = this.context.storage; const stats = await storage.getStatistics?.(); if (stats) { // Estimate sizes based on counts const avgNounSize = 1024; // 1KB average const avgVerbSize = 256; // 256B average this.statisticsCollector.updateStorageSizes({ nouns: (stats.totalNodes || 0) * avgNounSize, verbs: (stats.totalEdges || 0) * avgVerbSize, metadata: (stats.totalNodes || 0) * 512, // 512B per metadata index: (stats.hnswIndexSize || 0) // Use HNSW index size from stats }); } } catch (e) { this.log('Could not update storage metrics', 'info'); } } /** * Persist metrics to storage */ async persistMetrics() { if (!this.statisticsCollector || !this.context?.storage) return; try { const stats = this.statisticsCollector.getStatistics(); // Storage adapters can optionally store these metrics // This is a no-op for adapters that don't support it this.log('Metrics persisted to storage', 'info'); } catch (e) { this.log('Could not persist metrics', 'info'); } } /** * Get current metrics */ getStatistics() { if (!this.statisticsCollector) { return { enabled: false, totalSearches: 0, totalUpdates: 0, contentTypes: {}, verbTypes: {}, searchPerformance: { averageLatency: 0, p95Latency: 0, p99Latency: 0 } }; } return { enabled: true, ...this.statisticsCollector.getStatistics() }; } /** * Record cache hit (called by cache augmentation) * Note: Cache metrics are tracked internally by StatisticsCollector */ recordCacheHit() { // StatisticsCollector doesn't have trackCacheHit method // Cache metrics would need to be implemented if needed this.log('Cache hit recorded', 'info'); } /** * Record cache miss (called by cache augmentation) * Note: Cache metrics are tracked internally by StatisticsCollector */ recordCacheMiss() { // StatisticsCollector doesn't have trackCacheMiss method // Cache metrics would need to be implemented if needed this.log('Cache miss recorded', 'info'); } /** * Track custom metric * Note: Custom metrics would need to be implemented in StatisticsCollector */ trackCustomMetric(name, value) { // StatisticsCollector doesn't have trackCustomMetric method // Could be added later if needed this.log(`Custom metric recorded: ${name}=${value}`, 'info'); } /** * Reset all metrics */ reset() { if (this.statisticsCollector) { this.statisticsCollector = new StatisticsCollector(); this.log('Metrics manually reset'); } } } /** * Factory function for zero-config metrics augmentation */ export function createMetricsAugmentation(config) { return new MetricsAugmentation(config); } //# sourceMappingURL=metricsAugmentation.js.map