@layr-labs/hourglass-performer
Version:
TypeScript SDK for building Hourglass AVS performers
389 lines • 13.7 kB
JavaScript
"use strict";
// Debugging and troubleshooting utilities for the performer
Object.defineProperty(exports, "__esModule", { value: true });
exports.DiagnosticTool = exports.RequestTracer = exports.MemoryLeakDetector = void 0;
/**
* Memory leak detector
*/
class MemoryLeakDetector {
constructor(logger, config) {
this.snapshots = [];
this.logger = logger;
this.config = config;
}
start() {
if (!this.config.enableMemoryLeakDetection)
return;
this.checkInterval = setInterval(() => {
this.checkMemoryUsage();
}, this.config.performanceInterval);
this.logger.debug('Memory leak detector started');
}
stop() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = undefined;
}
this.logger.debug('Memory leak detector stopped');
}
checkMemoryUsage() {
const snapshot = this.createPerformanceSnapshot();
this.snapshots.push(snapshot);
// Keep only last 10 snapshots
if (this.snapshots.length > 10) {
this.snapshots = this.snapshots.slice(-10);
}
const memoryMB = snapshot.memory.heapUsed / 1024 / 1024;
// Check for memory threshold
if (memoryMB > this.config.memoryThreshold) {
this.logger.warn('Memory usage exceeds threshold', {
currentMB: Math.round(memoryMB),
thresholdMB: this.config.memoryThreshold,
});
}
// Check for memory growth trend
if (this.snapshots.length >= 5) {
const trend = this.calculateMemoryTrend();
if (trend > 10) { // Growing by more than 10MB
this.logger.warn('Potential memory leak detected', {
trendMB: Math.round(trend),
currentMB: Math.round(memoryMB),
});
}
}
}
calculateMemoryTrend() {
const recent = this.snapshots.slice(-5);
const first = recent[0].memory.heapUsed / 1024 / 1024;
const last = recent[recent.length - 1].memory.heapUsed / 1024 / 1024;
return last - first;
}
createPerformanceSnapshot() {
const hrTime = process.hrtime();
const start = hrTime[0] * 1000 + hrTime[1] / 1000000;
// Measure event loop lag
setImmediate(() => {
const end = process.hrtime()[0] * 1000 + process.hrtime()[1] / 1000000;
const lag = end - start;
return {
timestamp: Date.now(),
memory: process.memoryUsage(),
eventLoopLag: lag,
handles: process._getActiveHandles().length,
requests: process._getActiveRequests().length,
};
});
return {
timestamp: Date.now(),
memory: process.memoryUsage(),
eventLoopLag: 0, // Will be updated asynchronously
handles: process._getActiveHandles?.().length || 0,
requests: process._getActiveRequests?.().length || 0,
};
}
}
exports.MemoryLeakDetector = MemoryLeakDetector;
/**
* Request tracer for debugging
*/
class RequestTracer {
constructor(logger, config) {
this.traces = new Map();
this.logger = logger;
this.config = config;
}
startTrace(id, operation, metadata) {
if (!this.config.enableRequestTracing)
return;
this.traces.set(id, {
id,
operation,
startTime: Date.now(),
metadata: metadata || {},
steps: [],
});
this.logger.debug(`Started trace: ${operation}`, { traceId: id });
}
addStep(id, step, metadata) {
if (!this.config.enableRequestTracing)
return;
const trace = this.traces.get(id);
if (trace) {
trace.steps.push({
step,
timestamp: Date.now(),
metadata: metadata || {},
});
}
}
endTrace(id, result) {
if (!this.config.enableRequestTracing)
return;
const trace = this.traces.get(id);
if (trace) {
trace.endTime = Date.now();
trace.duration = trace.endTime - trace.startTime;
trace.result = result;
this.logger.debug(`Completed trace: ${trace.operation}`, {
traceId: id,
duration: trace.duration,
steps: trace.steps.length,
});
// Clean up old traces
this.traces.delete(id);
}
}
getActiveTraces() {
return Array.from(this.traces.values());
}
}
exports.RequestTracer = RequestTracer;
/**
* Comprehensive diagnostics tool
*/
class DiagnosticTool {
constructor(logger, config = {}) {
this.recentErrors = [];
this.config = {
enabled: config.enabled ?? false,
enablePerformanceMonitoring: config.enablePerformanceMonitoring ?? true,
enableMemoryLeakDetection: config.enableMemoryLeakDetection ?? true,
enableRequestTracing: config.enableRequestTracing ?? false,
performanceInterval: config.performanceInterval ?? 30000,
memoryThreshold: config.memoryThreshold ?? 512,
};
this.logger = logger;
this.memoryLeakDetector = new MemoryLeakDetector(logger, this.config);
this.requestTracer = new RequestTracer(logger, this.config);
}
/**
* Start diagnostic monitoring
*/
start() {
if (!this.config.enabled) {
this.logger.debug('Diagnostics disabled');
return;
}
this.memoryLeakDetector.start();
this.logger.info('Diagnostic monitoring started');
}
/**
* Stop diagnostic monitoring
*/
stop() {
this.memoryLeakDetector.stop();
this.logger.info('Diagnostic monitoring stopped');
}
/**
* Set metrics collector
*/
setMetricsCollector(collector) {
this.metricsCollector = collector;
}
/**
* Record error for diagnostics
*/
recordError(error) {
this.recentErrors.push(`${new Date().toISOString()}: ${error}`);
// Keep only last 20 errors
if (this.recentErrors.length > 20) {
this.recentErrors = this.recentErrors.slice(-20);
}
}
/**
* Get system information
*/
getSystemInfo() {
const os = require('os');
return {
nodeVersion: process.version,
platform: os.platform(),
arch: os.arch(),
cpus: os.cpus().length,
totalMemory: os.totalmem(),
freeMemory: os.freemem(),
uptime: os.uptime(),
loadAverage: os.loadavg(),
};
}
/**
* Get current performance snapshot
*/
getPerformanceSnapshot() {
return {
timestamp: Date.now(),
memory: process.memoryUsage(),
eventLoopLag: 0, // Would need async measurement
handles: process._getActiveHandles?.().length || 0,
requests: process._getActiveRequests?.().length || 0,
};
}
/**
* Get comprehensive diagnostic information
*/
getDiagnosticInfo(status, config) {
return {
system: this.getSystemInfo(),
performance: this.getPerformanceSnapshot(),
status,
config: this.sanitizeConfig(config),
environment: this.getFilteredEnvironment(),
recentErrors: [...this.recentErrors],
metricsSummary: this.metricsCollector?.getSummary(),
};
}
/**
* Start request trace
*/
startTrace(id, operation, metadata) {
this.requestTracer.startTrace(id, operation, metadata);
}
/**
* Add step to trace
*/
addTraceStep(id, step, metadata) {
this.requestTracer.addStep(id, step, metadata);
}
/**
* End request trace
*/
endTrace(id, result) {
this.requestTracer.endTrace(id, result);
}
/**
* Get active traces
*/
getActiveTraces() {
return this.requestTracer.getActiveTraces();
}
/**
* Perform health check on the system
*/
performHealthCheck() {
const issues = [];
const recommendations = [];
const perf = this.getPerformanceSnapshot();
const system = this.getSystemInfo();
// Check memory usage
const memoryUsageMB = perf.memory.heapUsed / 1024 / 1024;
if (memoryUsageMB > this.config.memoryThreshold) {
issues.push(`High memory usage: ${Math.round(memoryUsageMB)}MB`);
recommendations.push('Consider optimizing memory usage or increasing memory limits');
}
// Check system memory
const systemMemoryUsage = (system.totalMemory - system.freeMemory) / system.totalMemory;
if (systemMemoryUsage > 0.9) {
issues.push('System memory usage is very high');
recommendations.push('Consider scaling or optimizing system resources');
}
// Check load average
const loadAvg = system.loadAverage[0];
if (loadAvg > system.cpus * 2) {
issues.push(`High system load: ${loadAvg.toFixed(2)}`);
recommendations.push('System may be overloaded, consider scaling');
}
// Check active handles
if (perf.handles > 1000) {
issues.push(`High number of active handles: ${perf.handles}`);
recommendations.push('Check for resource leaks or connection pooling issues');
}
return {
healthy: issues.length === 0,
issues,
recommendations,
};
}
/**
* Generate diagnostic report
*/
generateReport(status, config) {
const info = this.getDiagnosticInfo(status, config);
const healthCheck = this.performHealthCheck();
let report = '# Performer Diagnostic Report\n\n';
report += `Generated: ${new Date().toISOString()}\n\n`;
report += '## System Information\n';
report += `- Node.js: ${info.system.nodeVersion}\n`;
report += `- Platform: ${info.system.platform} ${info.system.arch}\n`;
report += `- CPUs: ${info.system.cpus}\n`;
report += `- Memory: ${Math.round(info.system.totalMemory / 1024 / 1024 / 1024)}GB total, ${Math.round(info.system.freeMemory / 1024 / 1024 / 1024)}GB free\n`;
report += `- Uptime: ${Math.round(info.system.uptime / 3600)}h\n\n`;
report += '## Performance\n';
report += `- Status: ${info.status}\n`;
report += `- Memory Usage: ${Math.round(info.performance.memory.heapUsed / 1024 / 1024)}MB\n`;
report += `- Active Handles: ${info.performance.handles}\n`;
report += `- Active Requests: ${info.performance.requests}\n\n`;
if (info.metricsSummary) {
report += '## Metrics Summary\n';
report += `- Total Metrics: ${info.metricsSummary.totalMetrics}\n`;
report += `- Counters: ${info.metricsSummary.counters}\n`;
report += `- Gauges: ${info.metricsSummary.gauges}\n`;
report += `- Histograms: ${info.metricsSummary.histograms}\n\n`;
}
report += '## Health Check\n';
report += `- Overall Health: ${healthCheck.healthy ? '✅ Healthy' : '❌ Issues Detected'}\n`;
if (healthCheck.issues.length > 0) {
report += '- Issues:\n';
healthCheck.issues.forEach(issue => {
report += ` - ${issue}\n`;
});
}
if (healthCheck.recommendations.length > 0) {
report += '- Recommendations:\n';
healthCheck.recommendations.forEach(rec => {
report += ` - ${rec}\n`;
});
}
if (info.recentErrors.length > 0) {
report += '\n## Recent Errors\n';
info.recentErrors.slice(-5).forEach(error => {
report += `- ${error}\n`;
});
}
return report;
}
/**
* Sanitize configuration for diagnostics
*/
sanitizeConfig(config) {
const sensitive = ['password', 'secret', 'key', 'token'];
const sanitized = { ...config };
const sanitizeObject = (obj) => {
if (typeof obj !== 'object' || obj === null)
return obj;
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (sensitive.some(s => key.toLowerCase().includes(s))) {
result[key] = '[REDACTED]';
}
else if (typeof value === 'object') {
result[key] = sanitizeObject(value);
}
else {
result[key] = value;
}
}
return result;
};
return sanitizeObject(sanitized);
}
/**
* Get filtered environment variables
*/
getFilteredEnvironment() {
const sensitive = ['password', 'secret', 'key', 'token'];
const filtered = {};
for (const [key, value] of Object.entries(process.env)) {
if (value === undefined)
continue;
if (sensitive.some(s => key.toLowerCase().includes(s))) {
filtered[key] = '[REDACTED]';
}
else {
filtered[key] = value;
}
}
return filtered;
}
}
exports.DiagnosticTool = DiagnosticTool;
//# sourceMappingURL=diagnostics.js.map