@layr-labs/hourglass-performer
Version:
TypeScript SDK for building Hourglass AVS performers
382 lines ⢠12.6 kB
JavaScript
"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