@btc-stamps/tx-builder
Version:
Transaction builder for Bitcoin Stamps and SRC-20 tokens with advanced UTXO selection
716 lines (632 loc) • 19.2 kB
text/typescript
/**
* Real-time Performance Monitoring Dashboard
* Comprehensive monitoring system with alerts and real-time metrics
*/
import type { PerformanceReport } from './performance-monitor.ts';
import { PerformanceMonitor } from './performance-monitor.ts';
import { UTXOCacheManager } from './utxo-cache-manager.ts';
import { clearIntervalCompat, setIntervalCompat, type TimerId } from '../utils/timer-utils.ts';
import process from 'node:process';
export interface DashboardConfig {
updateIntervalMs: number;
retentionPeriodMs: number;
enableRealTimeUpdates: boolean;
enableAlerts: boolean;
alertThresholds: {
errorRate: number;
responseTime: number;
memoryUsage: number;
cacheHitRate: number;
};
enableWebSocket: boolean;
enableLogging: boolean;
}
export interface RealTimeMetrics {
timestamp: number;
selectionMetrics: {
successRate: number;
averageTime: number;
p95Time: number;
requestsPerSecond: number;
algorithmDistribution: Record<string, number>;
};
cacheMetrics: {
hitRate: number;
memoryUsage: number;
totalEntries: number;
evictionRate: number;
};
systemMetrics: {
heapUsed: number;
heapTotal: number;
cpuUsage: number;
activeConnections: number;
};
alerts: Alert[];
}
export interface Alert {
id: string;
type: 'error' | 'warning' | 'info';
severity: 'critical' | 'high' | 'medium' | 'low';
title: string;
message: string;
timestamp: number;
resolved: boolean;
data?: any;
}
export interface DashboardStats {
uptime: number;
totalSelections: number;
totalAlerts: number;
systemHealth: 'healthy' | 'degraded' | 'critical';
performanceScore: number; // 0-100
recommendations: string[];
}
/**
* Real-time monitoring dashboard
*/
export class MonitoringDashboard {
private config: Required<DashboardConfig>;
private performanceMonitor: PerformanceMonitor;
private cacheManager: UTXOCacheManager;
private alerts = new Map<string, Alert>();
private metrics: RealTimeMetrics[] = [];
private updateTimer: TimerId | null = null;
private startTime = Date.now();
private subscribers = new Set<(metrics: RealTimeMetrics) => void>();
constructor(
performanceMonitor: PerformanceMonitor,
cacheManager: UTXOCacheManager,
config?: Partial<DashboardConfig>,
) {
this.performanceMonitor = performanceMonitor;
this.cacheManager = cacheManager;
this.config = {
updateIntervalMs: 5000, // 5 seconds
retentionPeriodMs: 24 * 60 * 60 * 1000, // 24 hours
enableRealTimeUpdates: true,
enableAlerts: true,
alertThresholds: {
errorRate: 0.1, // 10%
responseTime: 1000, // 1 second
memoryUsage: 0.8, // 80%
cacheHitRate: 0.5, // 50%
},
enableWebSocket: true,
enableLogging: true,
...config,
};
if (this.config.enableRealTimeUpdates) {
this.startRealTimeUpdates();
}
}
/**
* Get current real-time metrics
*/
getCurrentMetrics(): RealTimeMetrics {
const timestamp = Date.now();
// Get performance data
const performanceReport = this.performanceMonitor.generateReport(60000); // Last minute
const cacheStats = this.cacheManager.getStats();
const memoryUsage = process.memoryUsage();
// Calculate CPU usage (simplified)
const cpuUsage = process.cpuUsage();
const cpuPercent = (cpuUsage.user + cpuUsage.system) / 1000000; // Convert to seconds
// Build metrics
const metrics: RealTimeMetrics = {
timestamp,
selectionMetrics: {
successRate: performanceReport.successRate,
averageTime: performanceReport.averageExecutionTime,
p95Time: performanceReport.p95ExecutionTime,
requestsPerSecond: performanceReport.totalSelections / 60, // Last minute
algorithmDistribution: this.calculateAlgorithmDistribution(
performanceReport,
),
},
cacheMetrics: {
hitRate: cacheStats.hitRate,
memoryUsage: cacheStats.memoryUsage,
totalEntries: cacheStats.totalEntries,
evictionRate: cacheStats.evictionCount /
Math.max(1, cacheStats.totalEntries),
},
systemMetrics: {
heapUsed: memoryUsage.heapUsed / (1024 * 1024), // MB
heapTotal: memoryUsage.heapTotal / (1024 * 1024), // MB
cpuUsage: cpuPercent,
activeConnections: this.subscribers.size,
},
alerts: this.getActiveAlerts(),
};
// Check for alert conditions
if (this.config.enableAlerts) {
this.checkAlertConditions(metrics);
}
return metrics;
}
/**
* Get dashboard statistics
*/
getDashboardStats(): DashboardStats {
const uptime = Date.now() - this.startTime;
const performanceReport = this.performanceMonitor.generateReport();
const currentMetrics = this.getCurrentMetrics();
// Calculate system health
const systemHealth = this.calculateSystemHealth(currentMetrics);
// Calculate performance score
const performanceScore = this.calculatePerformanceScore(
currentMetrics,
performanceReport,
);
// Generate recommendations
const recommendations = this.generateRecommendations(
currentMetrics,
performanceReport,
);
return {
uptime,
totalSelections: performanceReport.totalSelections,
totalAlerts: this.alerts.size,
systemHealth,
performanceScore,
recommendations,
};
}
/**
* Get performance trends over time
*/
getPerformanceTrends(timeRangeMs: number = 60 * 60 * 1000): {
timestamps: number[];
successRate: number[];
averageTime: number[];
memoryUsage: number[];
cacheHitRate: number[];
} {
const cutoff = Date.now() - timeRangeMs;
const relevantMetrics = this.metrics.filter((m) => m.timestamp > cutoff);
if (relevantMetrics.length === 0) {
return {
timestamps: [],
successRate: [],
averageTime: [],
memoryUsage: [],
cacheHitRate: [],
};
}
return {
timestamps: relevantMetrics.map((m) => m.timestamp),
successRate: relevantMetrics.map((m) => m.selectionMetrics.successRate),
averageTime: relevantMetrics.map((m) => m.selectionMetrics.averageTime),
memoryUsage: relevantMetrics.map((m) => m.systemMetrics.heapUsed),
cacheHitRate: relevantMetrics.map((m) => m.cacheMetrics.hitRate),
};
}
/**
* Create custom alert
*/
createAlert(
type: Alert['type'],
severity: Alert['severity'],
title: string,
message: string,
data?: any,
): string {
const alert: Alert = {
id: this.generateAlertId(),
type,
severity,
title,
message,
timestamp: Date.now(),
resolved: false,
data,
};
this.alerts.set(alert.id, alert);
if (this.config.enableLogging) {
console.log(`[ALERT ${severity.toUpperCase()}] ${title}: ${message}`);
}
return alert.id;
}
/**
* Resolve alert
*/
resolveAlert(alertId: string): boolean {
const alert = this.alerts.get(alertId);
if (alert) {
alert.resolved = true;
return true;
}
return false;
}
/**
* Subscribe to real-time updates
*/
subscribe(callback: (metrics: RealTimeMetrics) => void): () => void {
this.subscribers.add(callback);
// Send current metrics immediately
callback(this.getCurrentMetrics());
// Return unsubscribe function
return () => {
this.subscribers.delete(callback);
};
}
/**
* Export metrics data
*/
exportMetrics(timeRangeMs?: number): {
config: DashboardConfig;
uptime: number;
metrics: RealTimeMetrics[];
alerts: Alert[];
performanceReport: PerformanceReport;
} {
const cutoff = timeRangeMs ? Date.now() - timeRangeMs : 0;
const relevantMetrics = this.metrics.filter((m) => m.timestamp > cutoff);
return {
config: this.config,
uptime: Date.now() - this.startTime,
metrics: relevantMetrics,
alerts: Array.from(this.alerts.values()),
performanceReport: this.performanceMonitor.generateReport(timeRangeMs),
};
}
/**
* Clear old data and reset
*/
cleanup(): void {
const cutoff = Date.now() - this.config.retentionPeriodMs;
// Clear old metrics
this.metrics = this.metrics.filter((m) => m.timestamp > cutoff);
// Clear resolved alerts older than retention period
for (const [id, alert] of this.alerts.entries()) {
if (alert.resolved && alert.timestamp < cutoff) {
this.alerts.delete(id);
}
}
}
/**
* Update dashboard configuration
*/
updateConfig(newConfig: Partial<DashboardConfig>): void {
this.config = { ...this.config, ...newConfig };
// Restart updates if interval changed
if (newConfig.updateIntervalMs && this.updateTimer) {
this.stopRealTimeUpdates();
this.startRealTimeUpdates();
}
}
/**
* Shutdown dashboard
*/
shutdown(): void {
this.stopRealTimeUpdates();
this.subscribers.clear();
this.alerts.clear();
this.metrics = [];
}
/**
* Start real-time updates
*/
private startRealTimeUpdates(): void {
if (this.updateTimer) return;
this.updateTimer = setIntervalCompat(() => {
const metrics = this.getCurrentMetrics();
// Store metrics
this.metrics.push(metrics);
// Notify subscribers
for (const callback of this.subscribers) {
try {
callback(metrics);
} catch (error) {
console.error('Error notifying subscriber:', error);
}
}
// Cleanup old data periodically
if (this.metrics.length % 100 === 0) {
this.cleanup();
}
}, this.config.updateIntervalMs);
}
/**
* Stop real-time updates
*/
private stopRealTimeUpdates(): void {
if (this.updateTimer) {
clearIntervalCompat(this.updateTimer);
this.updateTimer = null;
}
}
/**
* Calculate algorithm distribution
*/
private calculateAlgorithmDistribution(
report: PerformanceReport,
): Record<string, number> {
const distribution: Record<string, number> = {};
type AlgorithmStats = {
selectionCount: number;
successRate: number;
averageTime: number;
averageWaste: number;
preferredScenarios: string[];
};
const statsValues: AlgorithmStats[] = Object.values(
report.algorithmComparison,
);
const total = statsValues.reduce(
(sum: number, stats: AlgorithmStats) => sum + stats.selectionCount,
0,
);
if (total === 0) return distribution;
for (
const [algorithm, stats] of Object.entries(report.algorithmComparison)
) {
// Type assertion to ensure TypeScript knows the exact type
const typedStats = stats as AlgorithmStats;
distribution[algorithm] = typedStats.selectionCount / total;
}
return distribution;
}
/**
* Get active (unresolved) alerts
*/
private getActiveAlerts(): Alert[] {
return Array.from(this.alerts.values()).filter((alert) => !alert.resolved);
}
/**
* Check for alert conditions
*/
private checkAlertConditions(metrics: RealTimeMetrics): void {
const thresholds = this.config.alertThresholds;
// Error rate alert
const errorRate = 1 - metrics.selectionMetrics.successRate;
if (errorRate > thresholds.errorRate) {
const existingAlert = this.findExistingAlert('high-error-rate');
if (!existingAlert) {
this.createAlert(
'error',
'high',
'High Error Rate',
`Selection error rate is ${(errorRate * 100).toFixed(1)}%`,
{ errorRate, threshold: thresholds.errorRate },
);
}
} else {
this.resolveAlertsByType('high-error-rate');
}
// Response time alert
if (metrics.selectionMetrics.averageTime > thresholds.responseTime) {
const existingAlert = this.findExistingAlert('slow-response');
if (!existingAlert) {
this.createAlert(
'warning',
'medium',
'Slow Response Times',
`Average response time is ${metrics.selectionMetrics.averageTime.toFixed(0)}ms`,
{
responseTime: metrics.selectionMetrics.averageTime,
threshold: thresholds.responseTime,
},
);
}
} else {
this.resolveAlertsByType('slow-response');
}
// Memory usage alert
const memoryRatio = metrics.systemMetrics.heapUsed /
metrics.systemMetrics.heapTotal;
if (memoryRatio > thresholds.memoryUsage) {
const existingAlert = this.findExistingAlert('high-memory');
if (!existingAlert) {
this.createAlert(
'warning',
'high',
'High Memory Usage',
`Memory usage is ${(memoryRatio * 100).toFixed(1)}%`,
{ memoryUsage: memoryRatio, threshold: thresholds.memoryUsage },
);
}
} else {
this.resolveAlertsByType('high-memory');
}
// Cache hit rate alert
if (metrics.cacheMetrics.hitRate < thresholds.cacheHitRate) {
const existingAlert = this.findExistingAlert('low-cache-hit');
if (!existingAlert) {
this.createAlert(
'info',
'low',
'Low Cache Hit Rate',
`Cache hit rate is ${(metrics.cacheMetrics.hitRate * 100).toFixed(1)}%`,
{
cacheHitRate: metrics.cacheMetrics.hitRate,
threshold: thresholds.cacheHitRate,
},
);
}
} else {
this.resolveAlertsByType('low-cache-hit');
}
}
/**
* Find existing alert by type
*/
private findExistingAlert(type: string): Alert | undefined {
return Array.from(this.alerts.values()).find(
(alert) => !alert.resolved && alert.data?.alertType === type,
);
}
/**
* Resolve alerts by type
*/
private resolveAlertsByType(type: string): void {
for (const alert of this.alerts.values()) {
if (!alert.resolved && alert.data?.alertType === type) {
alert.resolved = true;
}
}
}
/**
* Calculate system health
*/
private calculateSystemHealth(
metrics: RealTimeMetrics,
): 'healthy' | 'degraded' | 'critical' {
const criticalAlerts = metrics.alerts.filter((a) => a.severity === 'critical').length;
const highAlerts = metrics.alerts.filter((a) => a.severity === 'high').length;
if (criticalAlerts > 0) return 'critical';
if (highAlerts > 2 || metrics.selectionMetrics.successRate < 0.8) {
return 'degraded';
}
return 'healthy';
}
/**
* Calculate performance score (0-100)
*/
private calculatePerformanceScore(
metrics: RealTimeMetrics,
_report: PerformanceReport,
): number {
let score = 100;
// Success rate impact (0-30 points)
score -= (1 - metrics.selectionMetrics.successRate) * 30;
// Response time impact (0-25 points)
const timeScore = Math.min(
25,
(metrics.selectionMetrics.averageTime / 1000) * 25,
);
score -= timeScore;
// Memory usage impact (0-20 points)
const memoryRatio = metrics.systemMetrics.heapUsed /
metrics.systemMetrics.heapTotal;
score -= memoryRatio * 20;
// Cache efficiency impact (0-15 points)
score -= (1 - metrics.cacheMetrics.hitRate) * 15;
// Alert penalty (0-10 points)
const alertPenalty = Math.min(10, metrics.alerts.length * 2);
score -= alertPenalty;
return Math.max(0, Math.round(score));
}
/**
* Generate performance recommendations
*/
private generateRecommendations(
metrics: RealTimeMetrics,
_report: PerformanceReport,
): string[] {
const recommendations: string[] = [];
if (metrics.selectionMetrics.successRate < 0.9) {
recommendations.push(
'Consider optimizing algorithm selection parameters',
);
}
if (metrics.selectionMetrics.averageTime > 500) {
recommendations.push(
'Enable parallel algorithm execution for better performance',
);
}
if (metrics.cacheMetrics.hitRate < 0.7) {
recommendations.push('Increase cache size or adjust TTL settings');
}
const memoryRatio = metrics.systemMetrics.heapUsed /
metrics.systemMetrics.heapTotal;
if (memoryRatio > 0.8) {
recommendations.push(
'Consider increasing heap size or enabling memory optimization',
);
}
if (metrics.alerts.length > 3) {
recommendations.push(
'Review and resolve pending alerts to improve system health',
);
}
return recommendations;
}
/**
* Generate unique alert ID
*/
private generateAlertId(): string {
return `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Initialize the monitoring dashboard
*/
initialize(): Promise<void> {
console.log('Monitoring Dashboard initialized');
return Promise.resolve();
}
/**
* Start the dashboard server
*/
startServer(port: number = 3001): void {
console.log(`Monitoring Dashboard server started on port ${port}`);
this.startRealTimeUpdates();
}
/**
* Update metrics with new data
*/
updateMetrics(newMetrics: any): void {
// Add new metrics data to the internal metrics array
const _timestamp = Date.now();
// Store the metrics for later retrieval
if (this.metrics.length > 1000) {
this.metrics = this.metrics.slice(-500); // Keep only recent metrics
}
console.log(`Dashboard metrics updated: ${JSON.stringify(newMetrics)}`);
}
}
/**
* Create monitoring dashboard
*/
export function createMonitoringDashboard(
performanceMonitor: PerformanceMonitor,
cacheManager: UTXOCacheManager,
config?: Partial<DashboardConfig>,
): MonitoringDashboard {
return new MonitoringDashboard(performanceMonitor, cacheManager, config);
}
/**
* Create production monitoring dashboard
*/
export function createProductionMonitoringDashboard(
performanceMonitor: PerformanceMonitor,
cacheManager: UTXOCacheManager,
): MonitoringDashboard {
return new MonitoringDashboard(performanceMonitor, cacheManager, {
updateIntervalMs: 10000, // 10 seconds
retentionPeriodMs: 7 * 24 * 60 * 60 * 1000, // 7 days
enableRealTimeUpdates: true,
enableAlerts: true,
alertThresholds: {
errorRate: 0.05, // 5%
responseTime: 2000, // 2 seconds
memoryUsage: 0.85, // 85%
cacheHitRate: 0.7, // 70%
},
enableWebSocket: true,
enableLogging: true,
});
}
/**
* Create development monitoring dashboard
*/
export function createDevelopmentMonitoringDashboard(
performanceMonitor: PerformanceMonitor,
cacheManager: UTXOCacheManager,
): MonitoringDashboard {
return new MonitoringDashboard(performanceMonitor, cacheManager, {
updateIntervalMs: 2000, // 2 seconds
retentionPeriodMs: 60 * 60 * 1000, // 1 hour
enableRealTimeUpdates: true,
enableAlerts: true,
alertThresholds: {
errorRate: 0.2, // 20% (more lenient for development)
responseTime: 5000, // 5 seconds
memoryUsage: 0.9, // 90%
cacheHitRate: 0.3, // 30%
},
enableWebSocket: false,
enableLogging: true,
});
}