UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

287 lines (279 loc) • 12.2 kB
/** * Summary Report Generator for Performance Optimization * @description Generates comprehensive performance reports with detailed metrics, * SQL operation analysis, and optimization recommendations based on sync results. * * Features: * - Performance metrics visualization * - SQL operation reduction analysis * - Entity-specific performance breakdowns * - Time complexity analysis * - Optimization recommendations * - Export to various formats (console, JSON, markdown) * * @author Optimizely MCP Server * @version 2.0.0 */ import chalk from 'chalk'; import { writeFileSync } from 'fs'; /** * Summary Report Generator */ export class SummaryReporter { report; constructor() { this.report = this.initializeReport(); } /** * Initialize empty report structure */ initializeReport() { return { summary: { totalDuration: 0, totalEntities: 0, totalRecords: 0, totalSqlBefore: 0, totalSqlAfter: 0, overallReduction: 0, achievedTarget: false }, entityMetrics: [], phaseTimings: {}, recommendations: [], timestamp: new Date().toISOString(), configuration: { batchSize: 100, platform: 'Feature & Web Experimentation', projectCount: 0 } }; } /** * Add entity performance metrics */ addEntityMetrics(metrics) { this.report.entityMetrics.push(metrics); // Update summary this.report.summary.totalEntities++; this.report.summary.totalRecords += metrics.recordCount; this.report.summary.totalSqlBefore += metrics.sqlOperationsBefore; this.report.summary.totalSqlAfter += metrics.sqlOperationsAfter; } /** * Add phase timing */ addPhaseTiming(phase, duration) { this.report.phaseTimings[phase] = duration; } /** * Calculate final metrics and generate recommendations */ finalize(totalDuration, projectCount) { this.report.summary.totalDuration = totalDuration; this.report.configuration.projectCount = projectCount; // Calculate overall reduction if (this.report.summary.totalSqlBefore > 0) { this.report.summary.overallReduction = ((this.report.summary.totalSqlBefore - this.report.summary.totalSqlAfter) / this.report.summary.totalSqlBefore) * 100; this.report.summary.achievedTarget = this.report.summary.overallReduction >= 90; } // Generate recommendations this.generateRecommendations(); } /** * Generate optimization recommendations based on metrics */ generateRecommendations() { const { summary, entityMetrics } = this.report; // Achievement recognition if (summary.achievedTarget) { this.report.recommendations.push('šŸŽÆ Excellent! Achieved target of 90%+ SQL operation reduction.'); } else { this.report.recommendations.push(`āš ļø SQL reduction of ${summary.overallReduction.toFixed(1)}% is below 90% target.`); } // Find slowest entities const slowestEntity = entityMetrics.reduce((prev, curr) => curr.syncDuration > prev.syncDuration ? curr : prev, entityMetrics[0]); if (slowestEntity && slowestEntity.syncDuration > 5000) { this.report.recommendations.push(`🐌 ${slowestEntity.entityType} sync took ${(slowestEntity.syncDuration / 1000).toFixed(1)}s. Consider optimizing API pagination.`); } // Large dataset recommendations const largeEntities = entityMetrics.filter(e => e.recordCount > 100); if (largeEntities.length > 0) { this.report.recommendations.push(`šŸ“Š Large datasets detected (${largeEntities.map(e => e.entityType).join(', ')}). Consider increasing batch size for better performance.`); } // Low reduction entities const lowReductionEntities = entityMetrics.filter(e => e.reductionPercent < 90); if (lowReductionEntities.length > 0) { this.report.recommendations.push(`⚔ Entities with sub-optimal reduction: ${lowReductionEntities.map(e => `${e.entityType} (${e.reductionPercent.toFixed(1)}%)`).join(', ')}`); } // General recommendations if (summary.totalDuration > 60000) { this.report.recommendations.push('ā±ļø Consider implementing parallel project sync for multi-project scenarios.'); } if (this.report.configuration.projectCount > 5) { this.report.recommendations.push('šŸ”„ With multiple projects, incremental sync can significantly reduce sync time.'); } } /** * Generate console output */ toConsole() { const { summary, entityMetrics, phaseTimings, recommendations } = this.report; console.log('\n' + chalk.bold.blue('═'.repeat(80))); console.log(chalk.bold.white.bgBlue(' PERFORMANCE OPTIMIZATION SUMMARY REPORT ')); console.log(chalk.blue('═'.repeat(80))); // Summary Section console.log(chalk.bold('\nšŸ“Š OVERALL PERFORMANCE:')); console.log(chalk.white(` Total Duration: ${(summary.totalDuration / 1000).toFixed(2)}s`)); console.log(chalk.white(` Total Entities: ${summary.totalEntities}`)); console.log(chalk.white(` Total Records: ${summary.totalRecords.toLocaleString()}`)); console.log(chalk.bold.green(` SQL Operations: ${summary.totalSqlBefore.toLocaleString()} → ${summary.totalSqlAfter.toLocaleString()}`)); console.log(chalk.bold.green(` Reduction: ${summary.overallReduction.toFixed(1)}%`)); if (summary.achievedTarget) { console.log(chalk.bold.green(` šŸŽÆ TARGET ACHIEVED! (90%+ reduction)`)); } // Entity Breakdown console.log(chalk.bold('\nšŸ“¦ ENTITY PERFORMANCE BREAKDOWN:')); console.log(chalk.gray('─'.repeat(80))); console.log(chalk.bold('Entity Type'.padEnd(20) + 'Records'.padEnd(10) + 'SQL Before'.padEnd(12) + 'SQL After'.padEnd(12) + 'Reduction'.padEnd(12) + 'Time'.padEnd(10) + 'Avg/Record')); console.log(chalk.gray('─'.repeat(80))); entityMetrics .sort((a, b) => b.recordCount - a.recordCount) .forEach(metric => { const reductionColor = metric.reductionPercent >= 90 ? chalk.green : chalk.yellow; console.log(metric.entityType.padEnd(20) + metric.recordCount.toString().padEnd(10) + metric.sqlOperationsBefore.toString().padEnd(12) + metric.sqlOperationsAfter.toString().padEnd(12) + reductionColor(`${metric.reductionPercent.toFixed(1)}%`.padEnd(12)) + `${(metric.syncDuration / 1000).toFixed(2)}s`.padEnd(10) + `${metric.averageTimePerRecord.toFixed(0)}ms`); }); console.log(chalk.gray('─'.repeat(80))); // Phase Timings console.log(chalk.bold('\nā±ļø PHASE TIMINGS:')); Object.entries(phaseTimings) .sort(([, a], [, b]) => b - a) .forEach(([phase, duration]) => { const percentage = (duration / summary.totalDuration * 100).toFixed(1); const bar = 'ā–ˆ'.repeat(Math.floor(parseInt(percentage) / 2)); console.log(chalk.white(` ${phase.padEnd(30)} ${bar} ${(duration / 1000).toFixed(2)}s (${percentage}%)`)); }); // Recommendations if (recommendations.length > 0) { console.log(chalk.bold('\nšŸ’” RECOMMENDATIONS:')); recommendations.forEach(rec => { console.log(chalk.yellow(` ${rec}`)); }); } // Configuration console.log(chalk.bold('\nāš™ļø CONFIGURATION:')); console.log(chalk.gray(` Batch Size: ${this.report.configuration.batchSize}`)); console.log(chalk.gray(` Platform: ${this.report.configuration.platform}`)); console.log(chalk.gray(` Projects: ${this.report.configuration.projectCount}`)); console.log(chalk.gray(` Generated: ${new Date(this.report.timestamp).toLocaleString()}`)); console.log('\n' + chalk.blue('═'.repeat(80)) + '\n'); } /** * Export to JSON file */ toJSON(filepath) { const json = JSON.stringify(this.report, null, 2); if (filepath) { writeFileSync(filepath, json, 'utf-8'); console.log(chalk.green(`āœ“ Report saved to: ${filepath}`)); } return json; } /** * Export to Markdown format */ toMarkdown(filepath) { const { summary, entityMetrics, phaseTimings, recommendations } = this.report; let markdown = `# Performance Optimization Summary Report Generated: ${new Date(this.report.timestamp).toLocaleString()} ## šŸ“Š Overall Performance | Metric | Value | |--------|-------| | **Total Duration** | ${(summary.totalDuration / 1000).toFixed(2)}s | | **Total Entities** | ${summary.totalEntities} | | **Total Records** | ${summary.totalRecords.toLocaleString()} | | **SQL Operations** | ${summary.totalSqlBefore.toLocaleString()} → ${summary.totalSqlAfter.toLocaleString()} | | **Reduction** | ${summary.overallReduction.toFixed(1)}% | | **Target Achieved** | ${summary.achievedTarget ? 'āœ… Yes (90%+)' : 'āŒ No'} | ## šŸ“¦ Entity Performance Breakdown | Entity Type | Records | SQL Before | SQL After | Reduction | Sync Time | Avg/Record | |-------------|---------|------------|-----------|-----------|-----------|------------| `; entityMetrics .sort((a, b) => b.recordCount - a.recordCount) .forEach(metric => { markdown += `| ${metric.entityType} | ${metric.recordCount} | ${metric.sqlOperationsBefore} | ${metric.sqlOperationsAfter} | ${metric.reductionPercent.toFixed(1)}% | ${(metric.syncDuration / 1000).toFixed(2)}s | ${metric.averageTimePerRecord.toFixed(0)}ms |\n`; }); markdown += ` ## ā±ļø Phase Timings | Phase | Duration | Percentage | |-------|----------|------------| `; Object.entries(phaseTimings) .sort(([, a], [, b]) => b - a) .forEach(([phase, duration]) => { const percentage = (duration / summary.totalDuration * 100).toFixed(1); markdown += `| ${phase} | ${(duration / 1000).toFixed(2)}s | ${percentage}% |\n`; }); if (recommendations.length > 0) { markdown += ` ## šŸ’” Recommendations `; recommendations.forEach(rec => { markdown += `- ${rec}\n`; }); } markdown += ` ## āš™ļø Configuration - **Batch Size**: ${this.report.configuration.batchSize} - **Platform**: ${this.report.configuration.platform} - **Projects**: ${this.report.configuration.projectCount} `; if (filepath) { writeFileSync(filepath, markdown, 'utf-8'); console.log(chalk.green(`āœ“ Markdown report saved to: ${filepath}`)); } return markdown; } /** * Get raw report data */ getReport() { return this.report; } /** * Create entity metrics from progress data */ static createEntityMetrics(entityType, recordCount, syncDuration, batchOperations = 1) { const sqlOperationsBefore = recordCount; // Individual inserts const sqlOperationsAfter = batchOperations; // Batch inserts const reductionPercent = ((sqlOperationsBefore - sqlOperationsAfter) / sqlOperationsBefore) * 100; const averageTimePerRecord = recordCount > 0 ? syncDuration / recordCount : 0; return { entityType, recordCount, sqlOperationsBefore, sqlOperationsAfter, reductionPercent, syncDuration, averageTimePerRecord, batchSize: Math.ceil(recordCount / batchOperations) }; } } //# sourceMappingURL=SummaryReporter.js.map