@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
563 lines (562 loc) • 21.6 kB
JavaScript
"use strict";
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.PerformanceMonitor = void 0;
exports.createPerformanceMonitor = createPerformanceMonitor;
exports.getGlobalPerformanceMonitor = getGlobalPerformanceMonitor;
exports.setGlobalPerformanceMonitor = setGlobalPerformanceMonitor;
const events_1 = require("events");
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
class PerformanceMonitor extends events_1.EventEmitter {
constructor(options = {}) {
super();
this.metrics = [];
this.alerts = [];
this.thresholds = new Map();
this.lastAlerts = new Map();
this.operationCounters = new Map();
this.startTime = new Date();
this.options = {
enabled: true,
collectInterval: 5000, // 5 seconds
retentionPeriod: 24 * 60 * 60 * 1000, // 24 hours
storageDir: path.join(process.cwd(), '.re-shell', 'performance'),
dashboard: {
enabled: false,
port: 3030,
updateInterval: 1000
},
thresholds: [
{ metric: 'cpu.usage', warning: 70, critical: 90, unit: '%' },
{ metric: 'memory.usage', warning: 80, critical: 95, unit: '%' },
{ metric: 'operation.duration', warning: 1000, critical: 5000, unit: 'ms' },
{ metric: 'error.rate', warning: 5, critical: 10, unit: '%' }
],
alerting: {
enabled: true,
cooldown: 60000 // 1 minute
},
metrics: {
system: true,
operations: true,
memory: true,
network: false
},
...options
};
this.setupThresholds();
if (this.options.enabled) {
this.start();
}
}
setupThresholds() {
for (const threshold of this.options.thresholds) {
this.thresholds.set(threshold.metric, threshold);
}
}
start() {
if (this.collectTimer)
return;
// Start metric collection
this.collectTimer = setInterval(() => {
this.collectMetrics();
}, this.options.collectInterval);
// Start dashboard if enabled
if (this.options.dashboard.enabled) {
this.startDashboard();
}
// Clean up old metrics periodically
setInterval(() => {
this.cleanupMetrics();
}, 60000); // Every minute
this.emit('started');
}
stop() {
if (this.collectTimer) {
clearInterval(this.collectTimer);
this.collectTimer = undefined;
}
if (this.dashboardTimer) {
clearInterval(this.dashboardTimer);
this.dashboardTimer = undefined;
}
this.emit('stopped');
}
async collectMetrics() {
try {
if (this.options.metrics.system) {
const systemMetrics = await this.collectSystemMetrics();
this.recordSystemMetrics(systemMetrics);
}
// Clean up old metrics
this.cleanupMetrics();
this.emit('metrics:collected');
}
catch (error) {
this.emit('error', error);
}
}
async collectSystemMetrics() {
const cpus = os.cpus();
const totalMemory = os.totalmem();
const freeMemory = os.freemem();
const usedMemory = totalMemory - freeMemory;
const processMemory = process.memoryUsage();
return {
timestamp: new Date(),
cpu: {
usage: await this.getCpuUsage(),
loadAverage: os.loadavg(),
model: cpus[0]?.model || 'Unknown',
cores: cpus.length
},
memory: {
total: totalMemory,
free: freeMemory,
used: usedMemory,
usage: (usedMemory / totalMemory) * 100,
heap: processMemory
},
process: {
pid: process.pid,
uptime: process.uptime(),
cpu: await this.getProcessCpuUsage(),
memory: processMemory
},
disk: await this.getDiskUsage(),
network: this.options.metrics.network ? await this.getNetworkStats() : undefined
};
}
async getCpuUsage() {
return new Promise((resolve) => {
const startUsage = process.cpuUsage();
const startTime = Date.now();
setTimeout(() => {
const endUsage = process.cpuUsage(startUsage);
const endTime = Date.now();
const totalTime = (endTime - startTime) * 1000; // Convert to microseconds
const usage = ((endUsage.user + endUsage.system) / totalTime) * 100;
resolve(Math.min(usage, 100));
}, 100);
});
}
async getProcessCpuUsage() {
// Simple approximation - in real implementation, you'd track this over time
const usage = process.cpuUsage();
return (usage.user + usage.system) / 1000000; // Convert to seconds
}
async getDiskUsage() {
try {
const stats = await fs.stat(process.cwd());
// This is a simplified version - real implementation would use statvfs or similar
return {
available: 0,
used: 0,
total: 0,
usage: 0
};
}
catch {
return {
available: 0,
used: 0,
total: 0,
usage: 0
};
}
}
async getNetworkStats() {
// Placeholder - real implementation would read from /proc/net/dev or use system APIs
return {
connections: 0,
bytesReceived: 0,
bytesSent: 0
};
}
recordSystemMetrics(systemMetrics) {
// CPU metrics
this.recordMetric('cpu', 'usage', systemMetrics.cpu.usage, '%', {
model: systemMetrics.cpu.model,
cores: systemMetrics.cpu.cores.toString()
});
this.recordMetric('cpu', 'load_average_1m', systemMetrics.cpu.loadAverage[0], 'load');
this.recordMetric('cpu', 'load_average_5m', systemMetrics.cpu.loadAverage[1], 'load');
this.recordMetric('cpu', 'load_average_15m', systemMetrics.cpu.loadAverage[2], 'load');
// Memory metrics
this.recordMetric('memory', 'usage', systemMetrics.memory.usage, '%');
this.recordMetric('memory', 'total', systemMetrics.memory.total, 'bytes');
this.recordMetric('memory', 'used', systemMetrics.memory.used, 'bytes');
this.recordMetric('memory', 'free', systemMetrics.memory.free, 'bytes');
// Process metrics
this.recordMetric('process', 'heap_used', systemMetrics.process.memory.heapUsed, 'bytes');
this.recordMetric('process', 'heap_total', systemMetrics.process.memory.heapTotal, 'bytes');
this.recordMetric('process', 'external', systemMetrics.process.memory.external, 'bytes');
this.recordMetric('process', 'rss', systemMetrics.process.memory.rss, 'bytes');
this.recordMetric('process', 'uptime', systemMetrics.process.uptime, 'seconds');
// Check thresholds
this.checkThresholds(systemMetrics);
}
recordMetric(category, name, value, unit, tags, metadata) {
const metric = {
id: `${category}.${name}_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
timestamp: new Date(),
category,
name,
value,
unit,
tags,
metadata
};
this.metrics.push(metric);
// Check thresholds
const thresholdKey = `${category}.${name}`;
this.checkMetricThreshold(thresholdKey, metric);
this.emit('metric:recorded', metric);
}
recordOperation(name, duration, success = true, metadata) {
// Update operation counters
const counter = this.operationCounters.get(name) || { count: 0, total: 0, errors: 0 };
counter.count++;
counter.total += duration;
if (!success)
counter.errors++;
this.operationCounters.set(name, counter);
// Record metrics
this.recordMetric('operation', 'duration', duration, 'ms', { operation: name }, metadata);
this.recordMetric('operation', 'count', 1, 'count', { operation: name, success: success.toString() });
if (!success) {
this.recordMetric('operation', 'error', 1, 'count', { operation: name });
}
}
checkThresholds(systemMetrics) {
this.checkMetricThreshold('cpu.usage', {
id: 'cpu_usage_check',
timestamp: new Date(),
category: 'cpu',
name: 'usage',
value: systemMetrics.cpu.usage,
unit: '%'
});
this.checkMetricThreshold('memory.usage', {
id: 'memory_usage_check',
timestamp: new Date(),
category: 'memory',
name: 'usage',
value: systemMetrics.memory.usage,
unit: '%'
});
}
checkMetricThreshold(metricKey, metric) {
const threshold = this.thresholds.get(metricKey);
if (!threshold || !this.options.alerting.enabled)
return;
let level = null;
if (metric.value >= threshold.critical) {
level = 'critical';
}
else if (metric.value >= threshold.warning) {
level = 'warning';
}
if (level) {
this.triggerAlert(level, metric, threshold);
}
}
triggerAlert(level, metric, threshold) {
const alertKey = `${metric.category}.${metric.name}_${level}`;
const lastAlert = this.lastAlerts.get(alertKey);
// Check cooldown
if (lastAlert && Date.now() - lastAlert.getTime() < this.options.alerting.cooldown) {
return;
}
const alert = {
id: `alert_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
timestamp: new Date(),
level,
metric,
threshold,
message: `${metric.category}.${metric.name} is ${metric.value}${metric.unit} (${level}: ${threshold[level]}${threshold.unit})`
};
this.alerts.push(alert);
this.lastAlerts.set(alertKey, new Date());
// Keep only recent alerts
if (this.alerts.length > 1000) {
this.alerts = this.alerts.slice(-1000);
}
this.emit('alert', alert);
}
cleanupMetrics() {
const cutoff = Date.now() - this.options.retentionPeriod;
this.metrics = this.metrics.filter(metric => metric.timestamp.getTime() > cutoff);
this.alerts = this.alerts.filter(alert => alert.timestamp.getTime() > cutoff);
}
getDashboard() {
const now = Date.now();
const hour = 60 * 60 * 1000;
const activeAlerts = this.alerts.filter(a => now - a.timestamp.getTime() < hour);
// Calculate system health
const criticalAlerts = activeAlerts.filter(a => a.level === 'critical').length;
const warningAlerts = activeAlerts.filter(a => a.level === 'warning').length;
let systemHealth = 'good';
if (criticalAlerts > 0)
systemHealth = 'critical';
else if (warningAlerts > 0)
systemHealth = 'warning';
// Get recent metrics
const recentMetrics = this.metrics.filter(m => now - m.timestamp.getTime() < hour);
// Calculate operation stats
const slowestOperations = Array.from(this.operationCounters.entries())
.map(([name, stats]) => ({
name,
duration: stats.total / stats.count,
count: stats.count
}))
.sort((a, b) => b.duration - a.duration)
.slice(0, 10);
return {
overview: {
totalMetrics: this.metrics.length,
activeAlerts: activeAlerts.length,
systemHealth,
uptime: Date.now() - this.startTime.getTime()
},
realtime: {
cpu: this.getLatestMetricValue('cpu', 'usage') || 0,
memory: this.getLatestMetricValue('memory', 'usage') || 0,
operations: recentMetrics.filter(m => m.category === 'operation').length,
errors: recentMetrics.filter(m => m.category === 'operation' && m.name === 'error').length
},
trends: {
period: '1h',
metrics: this.calculateTrends(recentMetrics)
},
alerts: activeAlerts.slice(-20), // Latest 20 alerts
top: {
slowestOperations,
memoryConsumers: [], // TODO: Implement
errorRate: [] // TODO: Implement
}
};
}
getLatestMetricValue(category, name) {
const metric = this.metrics
.filter(m => m.category === category && m.name === name)
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())[0];
return metric ? metric.value : null;
}
calculateTrends(metrics) {
const metricGroups = new Map();
// Group metrics by category.name
for (const metric of metrics) {
const key = `${metric.category}.${metric.name}`;
if (!metricGroups.has(key)) {
metricGroups.set(key, []);
}
metricGroups.get(key).push(metric);
}
const trends = [];
for (const [name, metricList] of metricGroups) {
if (metricList.length < 2)
continue;
const sorted = metricList.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
const current = sorted[sorted.length - 1].value;
const average = sorted.reduce((sum, m) => sum + m.value, 0) / sorted.length;
const previous = sorted[sorted.length - 2].value;
const change = current - previous;
let trend = 'stable';
if (Math.abs(change) > average * 0.1) { // 10% change threshold
trend = change > 0 ? 'up' : 'down';
}
trends.push({
name,
current,
average,
trend,
change: (change / previous) * 100
});
}
return trends;
}
startDashboard() {
// Simple console-based dashboard for now
// In a real implementation, this would start an HTTP server
this.dashboardTimer = setInterval(() => {
if (process.env.RESHELL_PERFORMANCE_DASHBOARD === 'console') {
this.printConsoleDashboard();
}
}, this.options.dashboard.updateInterval);
}
printConsoleDashboard() {
const dashboard = this.getDashboard();
console.clear();
console.log('📊 Re-Shell CLI Performance Dashboard');
console.log('='.repeat(50));
console.log(`System Health: ${this.getHealthEmoji(dashboard.overview.systemHealth)} ${dashboard.overview.systemHealth.toUpperCase()}`);
console.log(`Uptime: ${Math.round(dashboard.overview.uptime / 1000)}s`);
console.log(`Active Alerts: ${dashboard.overview.activeAlerts}`);
console.log('');
console.log('Real-time Metrics:');
console.log(`CPU: ${dashboard.realtime.cpu.toFixed(1)}%`);
console.log(`Memory: ${dashboard.realtime.memory.toFixed(1)}%`);
console.log(`Operations: ${dashboard.realtime.operations}`);
console.log(`Errors: ${dashboard.realtime.errors}`);
console.log('');
if (dashboard.alerts.length > 0) {
console.log('Recent Alerts:');
dashboard.alerts.slice(-5).forEach(alert => {
const emoji = alert.level === 'critical' ? '🔴' : '🟡';
console.log(`${emoji} ${alert.message}`);
});
}
}
getHealthEmoji(health) {
switch (health) {
case 'good': return '🟢';
case 'warning': return '🟡';
case 'critical': return '🔴';
default: return '⚪';
}
}
// Export and persistence
async saveMetrics(filename) {
const saveFile = filename || path.join(this.options.storageDir, `metrics-${Date.now()}.json`);
await fs.ensureDir(path.dirname(saveFile));
const data = {
timestamp: new Date(),
metrics: this.metrics,
alerts: this.alerts,
operations: Object.fromEntries(this.operationCounters)
};
await fs.writeJson(saveFile, data, { spaces: 2 });
}
async loadMetrics(filename) {
const data = await fs.readJson(filename);
this.metrics = data.metrics || [];
this.alerts = data.alerts || [];
this.operationCounters = new Map(Object.entries(data.operations || {}));
}
getMetrics(filter) {
let filtered = this.metrics;
if (filter) {
if (filter.category) {
filtered = filtered.filter(m => m.category === filter.category);
}
if (filter.name) {
filtered = filtered.filter(m => m.name === filter.name);
}
if (filter.startTime) {
filtered = filtered.filter(m => m.timestamp >= filter.startTime);
}
if (filter.endTime) {
filtered = filtered.filter(m => m.timestamp <= filter.endTime);
}
}
return filtered;
}
getAlerts(filter) {
let filtered = this.alerts;
if (filter) {
if (filter.level) {
filtered = filtered.filter(a => a.level === filter.level);
}
if (filter.startTime) {
filtered = filtered.filter(a => a.timestamp >= filter.startTime);
}
if (filter.endTime) {
filtered = filtered.filter(a => a.timestamp <= filter.endTime);
}
}
return filtered;
}
// Configuration
setThreshold(metric, warning, critical, unit) {
const threshold = { metric, warning, critical, unit };
this.thresholds.set(metric, threshold);
// Update options
const existingIndex = this.options.thresholds.findIndex(t => t.metric === metric);
if (existingIndex >= 0) {
this.options.thresholds[existingIndex] = threshold;
}
else {
this.options.thresholds.push(threshold);
}
}
removeThreshold(metric) {
this.thresholds.delete(metric);
this.options.thresholds = this.options.thresholds.filter(t => t.metric !== metric);
}
getThresholds() {
return [...this.options.thresholds];
}
isEnabled() {
return this.options.enabled;
}
enable() {
this.options.enabled = true;
this.start();
}
disable() {
this.options.enabled = false;
this.stop();
}
}
exports.PerformanceMonitor = PerformanceMonitor;
// Global performance monitor
let globalPerformanceMonitor = null;
function createPerformanceMonitor(options) {
return new PerformanceMonitor(options);
}
function getGlobalPerformanceMonitor() {
if (!globalPerformanceMonitor) {
const enabled = process.env.RESHELL_PERFORMANCE === 'true' ||
process.argv.includes('--profile');
globalPerformanceMonitor = new PerformanceMonitor({
enabled,
dashboard: {
enabled: process.env.RESHELL_PERFORMANCE_DASHBOARD === 'console',
port: 3030,
updateInterval: 2000
}
});
}
return globalPerformanceMonitor;
}
function setGlobalPerformanceMonitor(monitor) {
if (globalPerformanceMonitor) {
globalPerformanceMonitor.stop();
}
globalPerformanceMonitor = monitor;
}