UNPKG

@layr-labs/hourglass-performer

Version:

TypeScript SDK for building Hourglass AVS performers

382 lines • 12.6 kB
"use strict"; // Comprehensive metrics collection and monitoring system var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MetricsCollector = exports.FileMetricsExporter = exports.ConsoleMetricsExporter = exports.MetricType = void 0; /** * Metric types */ var MetricType; (function (MetricType) { MetricType["COUNTER"] = "counter"; MetricType["GAUGE"] = "gauge"; MetricType["HISTOGRAM"] = "histogram"; MetricType["SUMMARY"] = "summary"; })(MetricType || (exports.MetricType = MetricType = {})); /** * Console metrics exporter */ class ConsoleMetricsExporter { constructor() { this.name = 'console'; } async export(metrics) { console.log('\nšŸ“Š Metrics Report:'); console.log('═'.repeat(50)); const grouped = this.groupMetricsByName(metrics); for (const [name, points] of grouped) { const latest = points[points.length - 1]; const agg = this.calculateAggregation(name, points); console.log(`\n${name} (${latest.type})`); console.log(` Current: ${latest.value}`); if (points.length > 1) { console.log(` Count: ${agg.count}, Avg: ${agg.avg.toFixed(2)}, Min: ${agg.min}, Max: ${agg.max}`); } if (latest.labels && Object.keys(latest.labels).length > 0) { console.log(` Labels: ${JSON.stringify(latest.labels)}`); } } console.log('═'.repeat(50)); } groupMetricsByName(metrics) { const grouped = new Map(); for (const metric of metrics) { const existing = grouped.get(metric.name) || []; existing.push(metric); grouped.set(metric.name, existing); } return grouped; } calculateAggregation(name, points) { const values = points.map(p => p.value).sort((a, b) => a - b); const count = values.length; const sum = values.reduce((a, b) => a + b, 0); const avg = sum / count; const min = values[0]; const max = values[count - 1]; // Calculate standard deviation const variance = values.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / count; const stdDev = Math.sqrt(variance); // Calculate percentiles const getPercentile = (p) => { const index = Math.ceil((p / 100) * count) - 1; return values[Math.max(0, index)]; }; return { name, count, sum, avg, min, max, stdDev, percentiles: { p50: getPercentile(50), p90: getPercentile(90), p95: getPercentile(95), p99: getPercentile(99), }, }; } } exports.ConsoleMetricsExporter = ConsoleMetricsExporter; /** * JSON file metrics exporter */ class FileMetricsExporter { constructor(filePath) { this.filePath = filePath; this.name = 'file'; } async export(metrics) { const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const data = { timestamp: new Date().toISOString(), metrics, }; await fs.writeFile(this.filePath, JSON.stringify(data, null, 2)); } } exports.FileMetricsExporter = FileMetricsExporter; /** * Comprehensive metrics collector */ class MetricsCollector { constructor(logger, config = {}) { this.logger = logger; this.metrics = []; this.exporters = []; this.counters = new Map(); this.gauges = new Map(); this.histograms = new Map(); this.config = { enabled: config.enabled ?? true, maxPoints: config.maxPoints ?? 10000, exportInterval: config.exportInterval ?? 60000, // 1 minute retentionPeriod: config.retentionPeriod ?? 3600000, // 1 hour defaultLabels: config.defaultLabels ?? {}, }; // Add default console exporter this.addExporter(new ConsoleMetricsExporter()); } /** * Start the metrics collector */ start() { if (!this.config.enabled) { this.logger.debug('Metrics collection is disabled'); return; } this.logger.info('Metrics collector started'); // Start periodic export if (this.config.exportInterval > 0) { this.exportInterval = setInterval(() => { this.exportMetrics().catch(error => { this.logger.error('Failed to export metrics', { error: error.message }); }); }, this.config.exportInterval); } // Start periodic cleanup setInterval(() => { this.cleanup(); }, this.config.retentionPeriod / 4); // Cleanup every quarter of retention period } /** * Stop the metrics collector */ stop() { if (this.exportInterval) { clearInterval(this.exportInterval); this.exportInterval = undefined; } this.logger.info('Metrics collector stopped'); } /** * Add metrics exporter */ addExporter(exporter) { this.exporters.push(exporter); this.logger.debug(`Added metrics exporter: ${exporter.name}`); } /** * Remove metrics exporter */ removeExporter(name) { this.exporters = this.exporters.filter(e => e.name !== name); this.logger.debug(`Removed metrics exporter: ${name}`); } /** * Record a counter metric */ counter(name, value = 1, labels) { if (!this.config.enabled) return; const current = this.counters.get(name) || 0; const newValue = current + value; this.counters.set(name, newValue); this.recordMetric({ name, type: MetricType.COUNTER, value: newValue, timestamp: Date.now(), labels: { ...this.config.defaultLabels, ...labels }, }); } /** * Record a gauge metric */ gauge(name, value, labels) { if (!this.config.enabled) return; this.gauges.set(name, value); this.recordMetric({ name, type: MetricType.GAUGE, value, timestamp: Date.now(), labels: { ...this.config.defaultLabels, ...labels }, }); } /** * Record a histogram metric */ histogram(name, value, labels) { if (!this.config.enabled) return; const existing = this.histograms.get(name) || []; existing.push(value); this.histograms.set(name, existing); this.recordMetric({ name, type: MetricType.HISTOGRAM, value, timestamp: Date.now(), labels: { ...this.config.defaultLabels, ...labels }, }); } /** * Record timing metric (convenience method for histogram) */ timing(name, duration, labels) { this.histogram(`${name}_duration_ms`, duration, labels); } /** * Create a timer function */ timer(name, labels) { const startTime = Date.now(); return () => { const duration = Date.now() - startTime; this.timing(name, duration, labels); }; } /** * Record custom metric */ recordMetric(metric) { if (!this.config.enabled) return; const point = { ...metric, timestamp: metric.timestamp ?? Date.now(), labels: { ...this.config.defaultLabels, ...metric.labels }, }; this.metrics.push(point); // Enforce max points limit if (this.metrics.length > this.config.maxPoints) { this.metrics = this.metrics.slice(-this.config.maxPoints); } } /** * Get all recorded metrics */ getMetrics() { return [...this.metrics]; } /** * Get metrics by name */ getMetricsByName(name) { return this.metrics.filter(m => m.name === name); } /** * Get metric aggregation */ getAggregation(name) { const points = this.getMetricsByName(name); if (points.length === 0) return null; const values = points.map(p => p.value).sort((a, b) => a - b); const count = values.length; const sum = values.reduce((a, b) => a + b, 0); const avg = sum / count; const min = values[0]; const max = values[count - 1]; const variance = values.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / count; const stdDev = Math.sqrt(variance); const getPercentile = (p) => { const index = Math.ceil((p / 100) * count) - 1; return values[Math.max(0, index)]; }; return { name, count, sum, avg, min, max, stdDev, percentiles: { p50: getPercentile(50), p90: getPercentile(90), p95: getPercentile(95), p99: getPercentile(99), }, }; } /** * Export metrics using all configured exporters */ async exportMetrics() { if (this.metrics.length === 0) return; const promises = this.exporters.map(async (exporter) => { try { await exporter.export([...this.metrics]); } catch (error) { this.logger.error(`Failed to export metrics with ${exporter.name}`, { error: error instanceof Error ? error.message : 'Unknown error' }); } }); await Promise.allSettled(promises); } /** * Clean up old metrics */ cleanup() { const cutoff = Date.now() - this.config.retentionPeriod; const before = this.metrics.length; this.metrics = this.metrics.filter(m => m.timestamp >= cutoff); const removed = before - this.metrics.length; if (removed > 0) { this.logger.debug(`Cleaned up ${removed} old metrics`); } } /** * Get current metrics summary */ getSummary() { const byType = new Map(); let oldest = Number.MAX_SAFE_INTEGER; let newest = 0; for (const metric of this.metrics) { byType.set(metric.type, (byType.get(metric.type) || 0) + 1); oldest = Math.min(oldest, metric.timestamp); newest = Math.max(newest, metric.timestamp); } return { totalMetrics: this.metrics.length, counters: byType.get(MetricType.COUNTER) || 0, gauges: byType.get(MetricType.GAUGE) || 0, histograms: byType.get(MetricType.HISTOGRAM) || 0, oldestTimestamp: oldest === Number.MAX_SAFE_INTEGER ? undefined : oldest, newestTimestamp: newest === 0 ? undefined : newest, }; } } exports.MetricsCollector = MetricsCollector; //# sourceMappingURL=metrics.js.map