@tehreet/conduit
Version:
LLM API gateway with intelligent routing, robust process management, and health monitoring
272 lines • 8.45 kB
JavaScript
"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