ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
319 lines • 12 kB
JavaScript
;
/**
* Performance Tracker
*
* Utility for tracking performance metrics across the ctrl.shift.left toolkit.
* Collects timing information for test generation, execution, and analysis operations.
*/
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.performanceTracker = exports.PerformanceTracker = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class PerformanceTracker {
constructor(options = {}) {
this.entries = [];
this.activeTimers = new Map();
this.projectName = options.projectName || path.basename(process.cwd());
this.version = options.version || '1.4.0';
this.outputDir = options.outputDir || path.join(process.cwd(), 'performance-reports');
this.enabled = options.enabled !== undefined ? options.enabled : true;
// Create output directory if it doesn't exist
if (this.enabled && !fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
}
/**
* Start tracking an operation
* @param type Operation type
* @param target Component or file being processed
* @param id Optional custom identifier (defaults to auto-generated)
* @returns Timer ID for stopping the timer later
*/
startTimer(type, target, id) {
if (!this.enabled)
return '';
const timerId = id || `${type}-${target}-${Date.now()}`;
this.activeTimers.set(timerId, {
startTime: Date.now(),
type,
target
});
return timerId;
}
/**
* Stop tracking an operation and record the performance data
* @param timerId Timer ID from startTimer
* @param metadata Additional data about the operation
* @returns Duration in milliseconds or -1 if timer not found
*/
stopTimer(timerId, metadata) {
if (!this.enabled || !timerId)
return -1;
const timer = this.activeTimers.get(timerId);
if (!timer) {
console.warn(`PerformanceTracker: Timer ${timerId} not found`);
return -1;
}
const endTime = Date.now();
const duration = endTime - timer.startTime;
this.activeTimers.delete(timerId);
const entry = {
id: timerId,
type: timer.type,
target: timer.target,
startTime: timer.startTime,
endTime,
duration,
metadata: metadata || { success: true },
system: {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
cpuUsage: process.cpuUsage(),
memoryUsage: process.memoryUsage()
}
};
this.entries.push(entry);
return duration;
}
/**
* Record a completed operation without using timers
* @param type Operation type
* @param target Component or file being processed
* @param duration Duration in milliseconds
* @param metadata Additional data about the operation
*/
recordOperation(type, target, duration, metadata) {
if (!this.enabled)
return;
const now = Date.now();
const entry = {
id: `${type}-${target}-${now}`,
type,
target,
startTime: now - duration,
endTime: now,
duration,
metadata: metadata || { success: true },
system: {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version
}
};
this.entries.push(entry);
}
/**
* Get performance data for all recorded operations
*/
getEntries() {
return [...this.entries];
}
/**
* Generate a comprehensive performance report
* @param label Optional label for the report
*/
generateReport(label) {
// Calculate summary statistics
let totalDuration = 0;
const typeStats = {};
let successCount = 0;
this.entries.forEach(entry => {
totalDuration += entry.duration;
if (!typeStats[entry.type]) {
typeStats[entry.type] = { count: 0, totalDuration: 0 };
}
typeStats[entry.type].count += 1;
typeStats[entry.type].totalDuration += entry.duration;
if (entry.metadata?.success) {
successCount += 1;
}
});
// Calculate averages and format the report
const byType = {};
Object.entries(typeStats).forEach(([type, stats]) => {
byType[type] = {
count: stats.count,
totalDuration: stats.totalDuration,
averageDuration: stats.totalDuration / stats.count
};
});
const report = {
metadata: {
timestamp: Date.now(),
project: this.projectName,
label,
version: this.version
},
entries: this.entries,
summary: {
totalDuration,
averageDuration: totalDuration / this.entries.length || 0,
byType,
successRate: (successCount / this.entries.length) * 100 || 0,
totalOperations: this.entries.length
}
};
return report;
}
/**
* Save the performance report to a file
* @param format Output format (json, markdown)
* @param label Optional label for the report file
* @returns Path to the saved report file
*/
saveReport(format = 'json', label) {
if (!this.enabled || this.entries.length === 0) {
console.warn('PerformanceTracker: No performance data to save');
return '';
}
const report = this.generateReport(label);
const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
const filename = `perf-report-${label || this.projectName}-${timestamp}`;
const filePath = path.join(this.outputDir, `${filename}.${format === 'json' ? 'json' : 'md'}`);
if (format === 'json') {
fs.writeFileSync(filePath, JSON.stringify(report, null, 2));
}
else {
const mdContent = this.generateMarkdownReport(report);
fs.writeFileSync(filePath, mdContent);
}
return filePath;
}
/**
* Generate a markdown representation of the performance report
* @param report Performance report object
* @returns Markdown string
*/
generateMarkdownReport(report) {
const { metadata, summary, entries } = report;
let md = `# Performance Report: ${metadata.project}\n\n`;
md += `*Generated by ctrl.shift.left v${metadata.version} on ${new Date(metadata.timestamp).toLocaleString()}*\n\n`;
if (metadata.label) {
md += `**Label:** ${metadata.label}\n\n`;
}
// Summary section
md += `## Summary\n\n`;
md += `- **Total Operations:** ${summary.totalOperations}\n`;
md += `- **Total Duration:** ${formatDuration(summary.totalDuration)}\n`;
md += `- **Average Duration:** ${formatDuration(summary.averageDuration)}\n`;
md += `- **Success Rate:** ${summary.successRate.toFixed(1)}%\n\n`;
// Operations by type
md += `## Operations by Type\n\n`;
md += `| Type | Count | Total Duration | Average Duration |\n`;
md += `|------|-------|----------------|------------------|\n`;
Object.entries(summary.byType).forEach(([type, stats]) => {
md += `| ${type} | ${stats.count} | ${formatDuration(stats.totalDuration)} | ${formatDuration(stats.averageDuration)} |\n`;
});
md += `\n`;
// Top 10 slowest operations
const slowestOps = [...entries].sort((a, b) => b.duration - a.duration).slice(0, 10);
md += `## Top 10 Slowest Operations\n\n`;
md += `| Type | Target | Duration | Success |\n`;
md += `|------|--------|----------|--------|\n`;
slowestOps.forEach(entry => {
const success = entry.metadata?.success ? '✅' : '❌';
md += `| ${entry.type} | ${entry.target} | ${formatDuration(entry.duration)} | ${success} |\n`;
});
md += `\n`;
// System info from the latest entry
if (entries.length > 0 && entries[entries.length - 1].system) {
const system = entries[entries.length - 1].system;
md += `## System Information\n\n`;
md += `- **Platform:** ${system.platform}\n`;
md += `- **Architecture:** ${system.arch}\n`;
md += `- **Node.js Version:** ${system.nodeVersion}\n`;
if (system.memoryUsage) {
md += `- **Memory Usage:**\n`;
md += ` - RSS: ${formatBytes(system.memoryUsage.rss)}\n`;
md += ` - Heap Total: ${formatBytes(system.memoryUsage.heapTotal)}\n`;
md += ` - Heap Used: ${formatBytes(system.memoryUsage.heapUsed)}\n`;
md += ` - External: ${formatBytes(system.memoryUsage.external)}\n`;
}
}
return md;
}
/**
* Reset the performance tracker, clearing all entries
*/
reset() {
this.entries = [];
this.activeTimers.clear();
}
/**
* Enable or disable the performance tracker
*/
setEnabled(enabled) {
this.enabled = enabled;
}
}
exports.PerformanceTracker = PerformanceTracker;
/**
* Format duration in milliseconds to a human-readable string
*/
function formatDuration(ms) {
if (ms < 1000) {
return `${ms.toFixed(1)}ms`;
}
else if (ms < 60000) {
return `${(ms / 1000).toFixed(2)}s`;
}
else {
const minutes = Math.floor(ms / 60000);
const seconds = ((ms % 60000) / 1000).toFixed(1);
return `${minutes}m ${seconds}s`;
}
}
/**
* Format bytes to a human-readable string
*/
function formatBytes(bytes) {
if (bytes < 1024) {
return `${bytes} bytes`;
}
else if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(2)} KB`;
}
else if (bytes < 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
}
else {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
}
// Export a singleton instance for shared usage
exports.performanceTracker = new PerformanceTracker();
//# sourceMappingURL=performanceTracker.js.map