Skip to main content
Glama
metrics.ts8.06 kB
/** * Prometheus-compatible metrics collection * For Phase 1D observability implementation */ interface MetricValue { value: number; labels?: Record<string, string>; timestamp?: number; } interface HistogramBucket { le: string; count: number; } interface HistogramData { sum: number; count: number; buckets: HistogramBucket[]; } export class Counter { private values: MetricValue[] = []; constructor( public readonly _name: string, public readonly _help: string, public readonly _labels: string[] = [] ) {} increment(labels?: Record<string, string>): void { this.values.push({ value: 1, labels, timestamp: Date.now(), }); } add(value: number, labels?: Record<string, string>): void { this.values.push({ value, labels, timestamp: Date.now(), }); } getValues(): MetricValue[] { return [...this.values]; } reset(): void { this.values = []; } } export class Gauge { private value: number = 0; private labels?: Record<string, string>; constructor( public readonly _name: string, public readonly _help: string, public readonly _labelNames: string[] = [] ) {} set(value: number, labels?: Record<string, string>): void { this.value = value; this.labels = labels; } increment(labels?: Record<string, string>): void { this.value += 1; this.labels = labels; } decrement(labels?: Record<string, string>): void { this.value -= 1; this.labels = labels; } getValue(): MetricValue { return { value: this.value, labels: this.labels, timestamp: Date.now(), }; } reset(): void { this.value = 0; this.labels = undefined; } } export class Histogram { private data: HistogramData; private buckets: number[]; constructor( public readonly _name: string, public readonly _help: string, public readonly _labelNames: string[] = [], buckets: number[] = [0.1, 0.5, 1, 2.5, 5, 10] ) { this.buckets = buckets; this.data = { sum: 0, count: 0, buckets: buckets .map((le) => ({ le: le.toString(), count: 0 })) .concat({ le: "+Inf", count: 0 }), }; } observe(value: number, _labels?: Record<string, string>): void { this.data.sum += value; this.data.count += 1; // Update buckets - cumulative: each bucket counts observations <= its upper bound for (let i = 0; i < this.buckets.length; i++) { if (value <= this.buckets[i]) { // Increment this bucket and all higher buckets for (let j = i; j < this.data.buckets.length - 1; j++) { this.data.buckets[j].count += 1; } break; } } // +Inf bucket always gets incremented this.data.buckets[this.data.buckets.length - 1].count += 1; } getData(): HistogramData & { labels?: Record<string, string> } { return { ...this.data }; } reset(): void { this.data = { sum: 0, count: 0, buckets: this.buckets .map((le) => ({ le: le.toString(), count: 0 })) .concat({ le: "+Inf", count: 0 }), }; } } export class MetricsCollector { private counters: Map<string, Counter> = new Map(); private gauges: Map<string, Gauge> = new Map(); private histograms: Map<string, Histogram> = new Map(); createCounter(name: string, help: string, labels: string[] = []): Counter { const counter = new Counter(name, help, labels); this.counters.set(name, counter); return counter; } createGauge(name: string, help: string, labels: string[] = []): Gauge { const gauge = new Gauge(name, help, labels); this.gauges.set(name, gauge); return gauge; } createHistogram( name: string, help: string, labels: string[] = [], buckets?: number[] ): Histogram { const histogram = new Histogram(name, help, labels, buckets); this.histograms.set(name, histogram); return histogram; } recordLatency(operation: string, duration: number, labels?: Record<string, string>): void { const histogram = this.histograms.get(`mcp_${operation}_duration_seconds`); if (histogram) { histogram.observe(duration / 1000, labels); // Convert to seconds } } incrementCounter(name: string, labels?: Record<string, string>): void { const counter = this.counters.get(name); if (counter) { counter.increment(labels); } } setGauge(name: string, value: number, labels?: Record<string, string>): void { const gauge = this.gauges.get(name); if (gauge) { gauge.set(value, labels); } } exportPrometheus(): string { let output = ""; // Export counters for (const [name, counter] of this.counters) { output += `# HELP ${name} ${counter._help}\n`; output += `# TYPE ${name} counter\n`; const values = counter.getValues(); for (const value of values) { const labels = value.labels ? `{${Object.entries(value.labels) .map(([k, v]) => `${k}="${v}"`) .join(",")}}` : ""; output += `${name}${labels} ${value.value} ${value.timestamp}\n`; } } // Export gauges for (const [name, gauge] of this.gauges) { output += `# HELP ${name} ${gauge._help}\n`; output += `# TYPE ${name} gauge\n`; const value = gauge.getValue(); const labels = value.labels ? `{${Object.entries(value.labels) .map(([k, v]) => `${k}="${v}"`) .join(",")}}` : ""; output += `${name}${labels} ${value.value} ${value.timestamp}\n`; } // Export histograms for (const [name, histogram] of this.histograms) { output += `# HELP ${name} ${histogram._help}\n`; output += `# TYPE ${name} histogram\n`; const data = histogram.getData(); for (const bucket of data.buckets) { output += `${name}_bucket{le="${bucket.le}"} ${bucket.count}\n`; } output += `${name}_count ${data.count}\n`; output += `${name}_sum ${data.sum}\n`; } return output; } reset(): void { for (const counter of this.counters.values()) { counter.reset(); } for (const gauge of this.gauges.values()) { gauge.reset(); } for (const histogram of this.histograms.values()) { histogram.reset(); } } } // Global metrics instance export const metrics = new MetricsCollector(); // Pre-defined metrics for MCP server export const sessionCreateCounter = metrics.createCounter( "mcp_session_create_total", "Total number of session creations" ); export const sessionCreateDuration = metrics.createHistogram( "mcp_session_create_duration_seconds", "Duration of session creation operations", ["result"], [0.1, 0.5, 1, 2.5, 5, 10] ); export const sessionGetCounter = metrics.createCounter( "mcp_session_get_total", "Total number of session retrievals", ["result"] ); export const sessionGetDuration = metrics.createHistogram( "mcp_session_get_duration_seconds", "Duration of session get operations", ["result"], [0.01, 0.05, 0.1, 0.5, 1, 2.5] ); export const sessionUpdateCounter = metrics.createCounter( "mcp_session_update_total", "Total number of session updates", ["result"] ); export const sessionEndCounter = metrics.createCounter( "mcp_session_end_total", "Total number of session endings" ); export const cacheHitCounter = metrics.createCounter( "mcp_cache_hits_total", "Total number of cache hits", ["operation"] ); export const cacheMissCounter = metrics.createCounter( "mcp_cache_misses_total", "Total number of cache misses", ["operation"] ); export const activeSessionsGauge = metrics.createGauge( "mcp_active_sessions", "Number of currently active sessions" ); export const d1WriteCounter = metrics.createCounter( "mcp_d1_writes_total", "Total number of D1 database writes", ["table", "result"] ); export const d1ReadCounter = metrics.createCounter( "mcp_d1_reads_total", "Total number of D1 database reads", ["table", "result"] );

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hummbl-dev/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server