@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
417 lines ⢠15.8 kB
JavaScript
/**
* Verbose Metrics Reporter for Detailed Performance Analysis
* @description Provides comprehensive detailed metrics and debugging information
* for performance optimization analysis during cache sync operations.
*
* Features:
* - Granular timing breakdowns
* - Memory usage tracking
* - API call statistics
* - Database query analysis
* - Network latency measurements
* - Error rate tracking
*
* @author Optimizely MCP Server
* @version 1.0.0
*/
import chalk from 'chalk';
import { performance } from 'perf_hooks';
/**
* Verbose Metrics Reporter Class
*/
export class VerboseMetricsReporter {
metrics;
memoryInterval;
currentPhase;
constructor() {
this.metrics = {
startTime: performance.now(),
memorySnapshots: [],
apiCalls: [],
databaseQueries: [],
phases: new Map(),
errors: []
};
}
/**
* Start verbose metrics collection
*/
start() {
// Start memory monitoring
this.startMemoryMonitoring();
// Capture initial memory snapshot
this.captureMemorySnapshot();
console.log(chalk.dim('š Verbose metrics collection started'));
}
/**
* Stop metrics collection
*/
stop() {
this.metrics.endTime = performance.now();
if (this.memoryInterval) {
clearInterval(this.memoryInterval);
this.memoryInterval = undefined;
}
// Capture final memory snapshot
this.captureMemorySnapshot();
}
/**
* Start monitoring memory usage
*/
startMemoryMonitoring(intervalMs = 1000) {
this.memoryInterval = setInterval(() => {
this.captureMemorySnapshot();
}, intervalMs);
}
/**
* Capture memory snapshot
*/
captureMemorySnapshot() {
const memUsage = process.memoryUsage();
this.metrics.memorySnapshots.push({
timestamp: performance.now(),
heapUsed: memUsage.heapUsed,
heapTotal: memUsage.heapTotal,
external: memUsage.external,
rss: memUsage.rss
});
}
/**
* Start a new phase
*/
startPhase(phaseName) {
this.currentPhase = phaseName;
this.metrics.phases.set(phaseName, {
startTime: performance.now(),
metrics: []
});
}
/**
* End current phase
*/
endPhase(phaseName) {
const phase = phaseName || this.currentPhase;
if (!phase)
return;
const phaseData = this.metrics.phases.get(phase);
if (phaseData) {
phaseData.endTime = performance.now();
}
if (phase === this.currentPhase) {
this.currentPhase = undefined;
}
}
/**
* Record API call
*/
recordApiCall(endpoint, method, duration, statusCode, size) {
this.metrics.apiCalls.push({
endpoint,
method,
duration,
statusCode,
timestamp: performance.now(),
size
});
}
/**
* Record database query
*/
recordDatabaseQuery(query, duration, rowCount) {
this.metrics.databaseQueries.push({
query: query.substring(0, 100), // Truncate for display
duration,
rowCount,
timestamp: performance.now()
});
}
/**
* Record detailed metric
*/
recordMetric(metric) {
if (this.currentPhase) {
const phase = this.metrics.phases.get(this.currentPhase);
if (phase) {
phase.metrics.push({
...metric,
timestamp: performance.now()
});
}
}
}
/**
* Record error
*/
recordError(error, context) {
this.metrics.errors.push({
timestamp: performance.now(),
phase: this.currentPhase || 'unknown',
error,
context
});
}
/**
* Generate verbose report
*/
generateReport() {
const totalDuration = (this.metrics.endTime || performance.now()) - this.metrics.startTime;
console.log('\n' + chalk.bold.magenta('ā'.repeat(80)));
console.log(chalk.bold.white.bgMagenta(' VERBOSE PERFORMANCE METRICS '));
console.log(chalk.magenta('ā'.repeat(80)));
// Overall timing
console.log(chalk.bold('\nā±ļø TIMING ANALYSIS:'));
console.log(` Total Duration: ${(totalDuration / 1000).toFixed(3)}s`);
console.log(` Start Time: ${new Date(Date.now() - totalDuration).toLocaleTimeString()}`);
console.log(` End Time: ${new Date().toLocaleTimeString()}`);
// Phase breakdown
this.generatePhaseBreakdown();
// Memory analysis
this.generateMemoryAnalysis();
// API performance
this.generateApiAnalysis();
// Database performance
this.generateDatabaseAnalysis();
// Error analysis
this.generateErrorAnalysis();
// Summary statistics
this.generateSummaryStatistics();
console.log('\n' + chalk.magenta('ā'.repeat(80)) + '\n');
}
/**
* Generate phase breakdown
*/
generatePhaseBreakdown() {
console.log(chalk.bold('\nš PHASE BREAKDOWN:'));
console.log(chalk.gray('ā'.repeat(60)));
const phases = Array.from(this.metrics.phases.entries())
.map(([name, data]) => ({
name,
duration: (data.endTime || performance.now()) - data.startTime,
metricsCount: data.metrics.length
}))
.sort((a, b) => b.duration - a.duration);
phases.forEach(phase => {
const percentage = (phase.duration / phases.reduce((sum, p) => sum + p.duration, 0)) * 100;
const bar = 'ā'.repeat(Math.floor(percentage / 2));
console.log(` ${phase.name.padEnd(30)} ${bar} ${(phase.duration / 1000).toFixed(3)}s (${percentage.toFixed(1)}%)`);
if (phase.metricsCount > 0) {
console.log(chalk.dim(` āā ${phase.metricsCount} detailed metrics recorded`));
}
});
}
/**
* Generate memory analysis
*/
generateMemoryAnalysis() {
if (this.metrics.memorySnapshots.length === 0)
return;
console.log(chalk.bold('\nš¾ MEMORY ANALYSIS:'));
console.log(chalk.gray('ā'.repeat(60)));
const firstSnapshot = this.metrics.memorySnapshots[0];
const lastSnapshot = this.metrics.memorySnapshots[this.metrics.memorySnapshots.length - 1];
// Heap usage
const heapDelta = lastSnapshot.heapUsed - firstSnapshot.heapUsed;
const heapColor = heapDelta > 0 ? chalk.yellow : chalk.green;
console.log(' Heap Usage:');
console.log(` Initial: ${this.formatBytes(firstSnapshot.heapUsed)}`);
console.log(` Final: ${this.formatBytes(lastSnapshot.heapUsed)}`);
console.log(heapColor(` Change: ${heapDelta > 0 ? '+' : ''}${this.formatBytes(heapDelta)}`));
// Peak memory
const peakHeap = Math.max(...this.metrics.memorySnapshots.map(s => s.heapUsed));
const peakRSS = Math.max(...this.metrics.memorySnapshots.map(s => s.rss));
console.log(' Peak Usage:');
console.log(` Heap: ${this.formatBytes(peakHeap)}`);
console.log(` RSS: ${this.formatBytes(peakRSS)}`);
// Memory trend
this.generateMemoryTrend();
}
/**
* Generate memory trend visualization
*/
generateMemoryTrend() {
if (this.metrics.memorySnapshots.length < 2)
return;
console.log(' Memory Trend:');
const samples = 20;
const step = Math.max(1, Math.floor(this.metrics.memorySnapshots.length / samples));
const maxHeap = Math.max(...this.metrics.memorySnapshots.map(s => s.heapUsed));
for (let i = 0; i < this.metrics.memorySnapshots.length; i += step) {
const snapshot = this.metrics.memorySnapshots[i];
const barLength = Math.floor((snapshot.heapUsed / maxHeap) * 30);
const bar = 'ā'.repeat(barLength);
console.log(chalk.dim(` ${bar} ${this.formatBytes(snapshot.heapUsed)}`));
}
}
/**
* Generate API analysis
*/
generateApiAnalysis() {
if (this.metrics.apiCalls.length === 0)
return;
console.log(chalk.bold('\nš API PERFORMANCE:'));
console.log(chalk.gray('ā'.repeat(60)));
// Group by endpoint
const endpointStats = new Map();
this.metrics.apiCalls.forEach(call => {
const key = `${call.method} ${call.endpoint}`;
if (!endpointStats.has(key)) {
endpointStats.set(key, this.createEmptyAggregation());
}
this.updateAggregation(endpointStats.get(key), call.duration);
});
// Display stats
console.log(' Endpoint Statistics:');
endpointStats.forEach((stats, endpoint) => {
console.log(` ${endpoint}:`);
console.log(` Calls: ${stats.count}`);
console.log(` Avg: ${stats.avg.toFixed(0)}ms`);
console.log(` Min/Max: ${stats.min.toFixed(0)}ms / ${stats.max.toFixed(0)}ms`);
console.log(` P95: ${stats.p95.toFixed(0)}ms`);
});
// Total API metrics
const totalApiTime = this.metrics.apiCalls.reduce((sum, call) => sum + call.duration, 0);
const totalDataSize = this.metrics.apiCalls
.filter(call => call.size)
.reduce((sum, call) => sum + (call.size || 0), 0);
console.log(' Overall API Metrics:');
console.log(` Total Calls: ${this.metrics.apiCalls.length}`);
console.log(` Total Time: ${(totalApiTime / 1000).toFixed(2)}s`);
console.log(` Total Data: ${this.formatBytes(totalDataSize)}`);
// Error rate
const errorCalls = this.metrics.apiCalls.filter(call => call.statusCode >= 400);
if (errorCalls.length > 0) {
console.log(chalk.red(` Error Rate: ${((errorCalls.length / this.metrics.apiCalls.length) * 100).toFixed(1)}%`));
}
}
/**
* Generate database analysis
*/
generateDatabaseAnalysis() {
if (this.metrics.databaseQueries.length === 0)
return;
console.log(chalk.bold('\nšļø DATABASE PERFORMANCE:'));
console.log(chalk.gray('ā'.repeat(60)));
// Query statistics
const totalQueries = this.metrics.databaseQueries.length;
const totalQueryTime = this.metrics.databaseQueries.reduce((sum, q) => sum + q.duration, 0);
const totalRows = this.metrics.databaseQueries.reduce((sum, q) => sum + q.rowCount, 0);
console.log(' Query Statistics:');
console.log(` Total Queries: ${totalQueries}`);
console.log(` Total Time: ${(totalQueryTime / 1000).toFixed(3)}s`);
console.log(` Total Rows: ${totalRows.toLocaleString()}`);
console.log(` Avg Query Time: ${(totalQueryTime / totalQueries).toFixed(1)}ms`);
// Slowest queries
const slowestQueries = this.metrics.databaseQueries
.sort((a, b) => b.duration - a.duration)
.slice(0, 5);
if (slowestQueries.length > 0) {
console.log(' Slowest Queries:');
slowestQueries.forEach((query, index) => {
console.log(` ${index + 1}. ${query.query}`);
console.log(chalk.dim(` Duration: ${query.duration.toFixed(0)}ms, Rows: ${query.rowCount}`));
});
}
}
/**
* Generate error analysis
*/
generateErrorAnalysis() {
if (this.metrics.errors.length === 0) {
console.log(chalk.bold.green('\nā
ERROR ANALYSIS: No errors recorded'));
return;
}
console.log(chalk.bold.red('\nā ERROR ANALYSIS:'));
console.log(chalk.gray('ā'.repeat(60)));
// Group by phase
const errorsByPhase = new Map();
this.metrics.errors.forEach(error => {
errorsByPhase.set(error.phase, (errorsByPhase.get(error.phase) || 0) + 1);
});
console.log(' Errors by Phase:');
errorsByPhase.forEach((count, phase) => {
console.log(chalk.red(` ${phase}: ${count} error${count > 1 ? 's' : ''}`));
});
// Recent errors
console.log(' Recent Errors:');
this.metrics.errors.slice(-3).forEach(error => {
console.log(chalk.red(` [${error.phase}] ${error.error}`));
if (error.context) {
console.log(chalk.dim(` Context: ${JSON.stringify(error.context)}`));
}
});
}
/**
* Generate summary statistics
*/
generateSummaryStatistics() {
console.log(chalk.bold('\nš SUMMARY STATISTICS:'));
console.log(chalk.gray('ā'.repeat(60)));
const totalDuration = (this.metrics.endTime || performance.now()) - this.metrics.startTime;
const apiTime = this.metrics.apiCalls.reduce((sum, call) => sum + call.duration, 0);
const dbTime = this.metrics.databaseQueries.reduce((sum, q) => sum + q.duration, 0);
// Time distribution
console.log(' Time Distribution:');
console.log(` API Calls: ${((apiTime / totalDuration) * 100).toFixed(1)}%`);
console.log(` Database: ${((dbTime / totalDuration) * 100).toFixed(1)}%`);
console.log(` Other: ${(((totalDuration - apiTime - dbTime) / totalDuration) * 100).toFixed(1)}%`);
// Efficiency metrics
const throughput = this.metrics.databaseQueries.reduce((sum, q) => sum + q.rowCount, 0) / (totalDuration / 1000);
console.log(' Efficiency Metrics:');
console.log(` Throughput: ${throughput.toFixed(1)} records/second`);
console.log(` API Efficiency: ${(this.metrics.apiCalls.length / (apiTime / 1000)).toFixed(1)} calls/second`);
// Resource utilization
if (this.metrics.memorySnapshots.length > 0) {
const avgHeap = this.metrics.memorySnapshots.reduce((sum, s) => sum + s.heapUsed, 0) / this.metrics.memorySnapshots.length;
console.log(' Resource Utilization:');
console.log(` Avg Heap: ${this.formatBytes(avgHeap)}`);
console.log(` Memory Samples: ${this.metrics.memorySnapshots.length}`);
}
}
/**
* Helper: Format bytes
*/
formatBytes(bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size > 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
/**
* Helper: Create empty aggregation
*/
createEmptyAggregation() {
return {
min: Infinity,
max: -Infinity,
avg: 0,
median: 0,
p95: 0,
p99: 0,
count: 0
};
}
/**
* Helper: Update aggregation
*/
updateAggregation(agg, value) {
agg.min = Math.min(agg.min, value);
agg.max = Math.max(agg.max, value);
agg.avg = (agg.avg * agg.count + value) / (agg.count + 1);
agg.count++;
// Note: Proper percentile calculation would require storing all values
agg.median = agg.avg; // Simplified
agg.p95 = agg.max * 0.95; // Simplified
agg.p99 = agg.max * 0.99; // Simplified
}
/**
* Export metrics data
*/
exportMetrics() {
return this.metrics;
}
}
//# sourceMappingURL=VerboseMetricsReporter.js.map