@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
287 lines (279 loc) ⢠12.2 kB
JavaScript
/**
* 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