UNPKG

@tehreet/conduit

Version:

LLM API gateway with intelligent routing, robust process management, and health monitoring

272 lines 8.45 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MetricsCollector = void 0; const events_1 = require("events"); const log_1 = require("../utils/log"); /** * Metrics collection system for usage tracking */ class MetricsCollector extends events_1.EventEmitter { constructor(config = {}) { super(); this.metrics = new Map(); this.flushInterval = null; this.isInitialized = false; this.config = { enabled: false, provider: 'custom', prefix: 'conduit', flushInterval: 60, ...config }; } /** * Initialize metrics collection */ async initialize() { if (this.isInitialized || !this.config.enabled) { return; } (0, log_1.log)('Initializing metrics collector...'); // Set up periodic flush if (this.config.flushInterval && this.config.flushInterval > 0) { this.flushInterval = setInterval(() => this.flush(), this.config.flushInterval * 1000); } this.isInitialized = true; this.emit('initialized'); (0, log_1.log)(`Metrics collector initialized (provider: ${this.config.provider})`); } /** * Record usage data as metrics */ async record(usage) { if (!this.isInitialized || !this.config.enabled) { return; } try { // Record request count this.incrementCounter('requests_total', { model: usage.model, project_id: usage.projectId || 'unknown', agent_id: usage.agentId || 'unknown' }); // Record token count this.recordGauge('tokens_total', usage.tokenCount, { model: usage.model, project_id: usage.projectId || 'unknown' }); // Record cost if (usage.cost) { this.recordGauge('cost_total', usage.cost, { model: usage.model, project_id: usage.projectId || 'unknown' }); } // Record routing decision this.incrementCounter('routing_decisions_total', { source: usage.routingSource || 'unknown', model: usage.model }); // Record response time if available if (usage.metadata?.duration) { this.recordHistogram('response_time_seconds', usage.metadata.duration, { model: usage.model }); } this.emit('recorded', usage); } catch (error) { (0, log_1.log)('Error recording metrics:', error); this.emit('error', error); } } /** * Increment a counter metric */ incrementCounter(name, tags = {}) { const metricKey = this.getMetricKey(name, tags); const existing = this.metrics.get(metricKey); const metric = { name: `${this.config.prefix}_${name}`, value: existing ? existing.value + 1 : 1, type: 'counter', tags, timestamp: new Date() }; this.metrics.set(metricKey, metric); } /** * Record a gauge metric */ recordGauge(name, value, tags = {}) { const metricKey = this.getMetricKey(name, tags); const metric = { name: `${this.config.prefix}_${name}`, value, type: 'gauge', tags, timestamp: new Date() }; this.metrics.set(metricKey, metric); } /** * Record a histogram metric */ recordHistogram(name, value, tags = {}) { const metricKey = this.getMetricKey(name, tags); const metric = { name: `${this.config.prefix}_${name}`, value, type: 'histogram', tags, timestamp: new Date() }; this.metrics.set(metricKey, metric); } /** * Get current metrics */ getMetrics() { return Array.from(this.metrics.values()); } /** * Get metrics summary */ getMetricsSummary() { const metrics = this.getMetrics(); const summary = { totalMetrics: metrics.length, byType: { counter: 0, gauge: 0, histogram: 0 }, byName: {} }; for (const metric of metrics) { summary.byType[metric.type]++; if (!summary.byName[metric.name]) { summary.byName[metric.name] = 0; } summary.byName[metric.name]++; } return summary; } /** * Clear all metrics */ clearMetrics() { this.metrics.clear(); this.emit('cleared'); } /** * Flush metrics to configured provider */ async flush() { if (!this.isInitialized || !this.config.enabled || this.metrics.size === 0) { return; } try { const metrics = this.getMetrics(); switch (this.config.provider) { case 'prometheus': await this.flushToPrometheus(metrics); break; case 'statsd': await this.flushToStatsd(metrics); break; case 'custom': await this.flushToCustom(metrics); break; } this.emit('flushed', { count: metrics.length }); } catch (error) { (0, log_1.log)('Error flushing metrics:', error); this.emit('error', error); } } /** * Update configuration */ async updateConfig(config) { this.config = { ...this.config, ...config }; // Restart flush interval if changed if (this.flushInterval) { clearInterval(this.flushInterval); this.flushInterval = null; } if (this.config.enabled && this.config.flushInterval && this.config.flushInterval > 0) { this.flushInterval = setInterval(() => this.flush(), this.config.flushInterval * 1000); } this.emit('config-updated', this.config); } /** * Get collector status */ getStatus() { return { initialized: this.isInitialized, enabled: this.config.enabled, provider: this.config.provider, metricsCount: this.metrics.size, flushInterval: this.config.flushInterval }; } /** * Generate metric key for deduplication */ getMetricKey(name, tags) { const tagString = Object.entries(tags) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${key}=${value}`) .join(','); return `${name}:${tagString}`; } /** * Flush metrics to Prometheus */ async flushToPrometheus(metrics) { // TODO: Implement Prometheus push gateway integration (0, log_1.log)(`Would flush ${metrics.length} metrics to Prometheus`); } /** * Flush metrics to StatsD */ async flushToStatsd(metrics) { // TODO: Implement StatsD client integration (0, log_1.log)(`Would flush ${metrics.length} metrics to StatsD`); } /** * Flush metrics to custom endpoint */ async flushToCustom(metrics) { // Emit metrics as events for custom handling this.emit('metrics', metrics); if (this.config.endpoint) { // TODO: Implement HTTP POST to custom endpoint (0, log_1.log)(`Would flush ${metrics.length} metrics to ${this.config.endpoint}`); } } /** * Cleanup resources */ async cleanup() { if (!this.isInitialized) { return; } (0, log_1.log)('Cleaning up metrics collector...'); if (this.flushInterval) { clearInterval(this.flushInterval); this.flushInterval = null; } // Final flush await this.flush(); this.metrics.clear(); this.isInitialized = false; this.emit('cleanup'); (0, log_1.log)('Metrics collector cleaned up'); } } exports.MetricsCollector = MetricsCollector; //# sourceMappingURL=MetricsCollector.js.map