UNPKG

ctrlshiftleft

Version:

AI-powered toolkit for embedding QA and security testing into development workflows

274 lines (237 loc) 8.89 kB
/** * Performance Tracker (JavaScript wrapper) * * This is a JavaScript wrapper for the TypeScript performanceTracker module. * It provides performance tracking capabilities for ctrl.shift.left operations. */ const fs = require('fs'); const path = require('path'); const os = require('os'); // Import platform utilities const { PathUtils, FileUtils } = require('./platformUtils'); /** * Performance tracker for measuring and reporting operation durations */ class PerformanceTracker { /** * Create a new performance tracker * @param {Object} options Configuration options * @param {string} options.projectName Project name for reporting * @param {string} options.version Project version for reporting * @param {string} options.outputDir Directory to save performance reports * @param {boolean} options.enabled Whether tracking is enabled */ constructor(options = {}) { this.projectName = options.projectName || path.basename(process.cwd()); this.version = options.version || '1.0.0'; this.outputDir = options.outputDir || './performance-reports'; this.enabled = options.enabled !== false; this.entries = []; this.activeTimers = new Map(); // Create output directory if enabled if (this.enabled && !fs.existsSync(this.outputDir)) { PathUtils.ensureDir(this.outputDir); } } /** * Enable or disable performance tracking * @param {boolean} enabled Whether tracking should be enabled */ setEnabled(enabled) { this.enabled = enabled; } /** * Start a performance timer for an operation * @param {string} type Operation type (e.g., 'generation', 'execution') * @param {string} target Target of the operation (e.g., filename) * @returns {string} Timer ID for stopping the timer later */ startTimer(type, target) { if (!this.enabled) { return ''; } const timerId = `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; this.activeTimers.set(timerId, { startTime: Date.now(), type, target }); return timerId; } /** * Stop a performance timer and record the operation * @param {string} timerId Timer ID from startTimer * @param {Object} metadata Additional metadata about the operation * @returns {number} Duration in milliseconds */ stopTimer(timerId, metadata = {}) { if (!this.enabled || !timerId || !this.activeTimers.has(timerId)) { return -1; } const endTime = Date.now(); const timer = this.activeTimers.get(timerId); const duration = endTime - timer.startTime; this.activeTimers.delete(timerId); this.recordOperation(timer.type, timer.target, duration, metadata); return duration; } /** * Record an operation directly with a known duration * @param {string} type Operation type * @param {string} target Target of the operation * @param {number} duration Duration in milliseconds * @param {Object} metadata Additional metadata */ recordOperation(type, target, duration, metadata = {}) { if (!this.enabled) { return; } this.entries.push({ type, target, duration, timestamp: new Date().toISOString(), metadata: { ...metadata, platform: os.platform(), arch: os.arch(), nodeVersion: process.version } }); } /** * Get all recorded performance entries * @returns {Array} Array of performance entries */ getEntries() { return this.entries; } /** * Generate a performance report * @param {string} label Optional label for the report * @returns {Object} Performance report object */ generateReport(label = '') { const totalDuration = this.entries.reduce((sum, entry) => sum + entry.duration, 0); const averageDuration = this.entries.length ? totalDuration / this.entries.length : 0; const successCount = this.entries.filter(entry => entry.metadata?.success !== false).length; const successRate = this.entries.length ? (successCount / this.entries.length) * 100 : 100; // Group by type const byType = {}; this.entries.forEach(entry => { if (!byType[entry.type]) { byType[entry.type] = { count: 0, totalDuration: 0, averageDuration: 0, successCount: 0, successRate: 0 }; } byType[entry.type].count++; byType[entry.type].totalDuration += entry.duration; if (entry.metadata?.success !== false) { byType[entry.type].successCount++; } }); // Calculate averages and success rates Object.keys(byType).forEach(type => { byType[type].averageDuration = byType[type].totalDuration / byType[type].count; byType[type].successRate = (byType[type].successCount / byType[type].count) * 100; }); return { metadata: { project: this.projectName, version: this.version, timestamp: new Date().toISOString(), label, platform: os.platform(), arch: os.arch(), nodeVersion: process.version }, entries: this.entries, summary: { operationCount: this.entries.length, totalDuration, averageDuration, successCount, successRate, byType } }; } /** * Save performance report to file * @param {string} format Output format ('json' or 'markdown') * @param {string} label Optional label for the report * @returns {string} Path to the saved report */ saveReport(format = 'json', label = '') { if (!this.enabled || this.entries.length === 0) { return ''; } // Create output directory if it doesn't exist PathUtils.ensureDir(this.outputDir); const timestamp = new Date().toISOString().replace(/:/g, '-'); const labelSuffix = label ? `-${label}` : ''; const report = this.generateReport(label); if (format === 'json') { const filePath = path.join(this.outputDir, `perf-report${labelSuffix}-${timestamp}.json`); fs.writeFileSync(filePath, JSON.stringify(report, null, 2)); return filePath; } else if (format === 'markdown') { const filePath = path.join(this.outputDir, `perf-report${labelSuffix}-${timestamp}.md`); const markdown = this.formatMarkdownReport(report); fs.writeFileSync(filePath, markdown); return filePath; } return ''; } /** * Format a performance report as Markdown * @param {Object} report Performance report object * @returns {string} Markdown formatted report */ formatMarkdownReport(report) { const { metadata, summary, entries } = report; let markdown = `# Performance Report\n\n`; // Metadata section markdown += `## Metadata\n\n`; markdown += `- **Project**: ${metadata.project}\n`; markdown += `- **Version**: ${metadata.version}\n`; markdown += `- **Timestamp**: ${metadata.timestamp}\n`; if (metadata.label) { markdown += `- **Label**: ${metadata.label}\n`; } markdown += `- **Platform**: ${metadata.platform} (${metadata.arch})\n`; markdown += `- **Node.js**: ${metadata.nodeVersion}\n\n`; // Summary section markdown += `## Summary\n\n`; markdown += `- **Total Operations**: ${summary.operationCount}\n`; markdown += `- **Total Duration**: ${summary.totalDuration.toFixed(2)}ms\n`; markdown += `- **Average Duration**: ${summary.averageDuration.toFixed(2)}ms\n`; markdown += `- **Success Rate**: ${summary.successRate.toFixed(2)}%\n\n`; // Operations by type markdown += `## Operations by Type\n\n`; markdown += `| Type | Count | Total Duration | Average Duration | Success Rate |\n`; markdown += `| ---- | ----- | -------------- | ---------------- | ------------ |\n`; Object.keys(summary.byType).forEach(type => { const stats = summary.byType[type]; markdown += `| ${type} | ${stats.count} | ${stats.totalDuration.toFixed(2)}ms | ${stats.averageDuration.toFixed(2)}ms | ${stats.successRate.toFixed(2)}% |\n`; }); markdown += `\n## Operation Details\n\n`; markdown += `| Type | Target | Duration | Timestamp | Status |\n`; markdown += `| ---- | ------ | -------- | --------- | ------ |\n`; // Show the first 20 operations for brevity const displayEntries = entries.slice(0, 20); displayEntries.forEach(entry => { const status = entry.metadata?.success === false ? '❌ Failed' : '✅ Success'; markdown += `| ${entry.type} | ${entry.target} | ${entry.duration.toFixed(2)}ms | ${entry.timestamp} | ${status} |\n`; }); if (entries.length > 20) { markdown += `\n*...and ${entries.length - 20} more operations*\n`; } return markdown; } } module.exports = { PerformanceTracker };