UNPKG

file-mover

Version:

Script to move files and update imports automatically

410 lines 22.8 kB
// Performance tracking and metrics module import { promises as fs } from "fs"; import path from "path"; // No-op timer for when performance tracking is disabled class NoOpTimer { end() { return 0; } } class Performance { metrics; verbose; timerStack; runId; logDir; constructor(verbose = false) { this.verbose = verbose; this.metrics = this.getInitialMetrics(); this.timerStack = new Map(); this.runId = this.generateRunId(); this.logDir = path.join(process.cwd(), 'performance'); this.ensureLogDirectory(); } generateRunId() { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}-${String(now.getMinutes()).padStart(2, '0')}-${String(now.getSeconds()).padStart(2, '0')}`; } async ensureLogDirectory() { try { await fs.mkdir(this.logDir, { recursive: true }); } catch (error) { // Directory might already exist, ignore error } } getInitialMetrics() { return { totalTime: 0, fileDiscovery: { time: 0, fileCount: 0 }, validation: { time: 0, moveCount: 0 }, importAnalysis: { time: 0, totalFiles: 0, filesWithImports: 0, fileReadTime: 0, astParseTime: 0, importMatchingTime: 0, individualFileTimes: [], }, fileOperations: { time: 0, moves: 0, updates: 0, writeTime: 0, moveTime: 0, }, cachePerformance: { astCacheHits: 0, fileCacheHits: 0, totalCacheLookups: 0 }, individualMoves: [], }; } calculateSummary() { const totalFiles = this.metrics.importAnalysis.totalFiles; const totalMoves = this.metrics.individualMoves.length; const totalUpdates = this.metrics.fileOperations.updates; const avgAnalysisTime = totalMoves > 0 ? this.metrics.individualMoves.reduce((sum, move) => sum + move.analysisTime, 0) / totalMoves : 0; const avgMoveTime = totalMoves > 0 ? this.metrics.individualMoves.reduce((sum, move) => sum + move.moveTime, 0) / totalMoves : 0; const avgUpdateTime = totalMoves > 0 ? this.metrics.individualMoves.reduce((sum, move) => sum + move.updateTime, 0) / totalMoves : 0; const avgFileAnalysisTime = this.metrics.importAnalysis.individualFileTimes.length > 0 ? this.metrics.importAnalysis.individualFileTimes.reduce((sum, file) => sum + file.totalTime, 0) / this.metrics.importAnalysis.individualFileTimes.length : 0; const cacheHitRate = this.metrics.cachePerformance.totalCacheLookups > 0 ? (this.metrics.cachePerformance.astCacheHits + this.metrics.cachePerformance.fileCacheHits) / this.metrics.cachePerformance.totalCacheLookups : 0; return { totalFiles, totalMoves, totalUpdates, avgAnalysisTime, avgMoveTime, avgUpdateTime, avgFileAnalysisTime, cacheHitRate, }; } async savePerformanceLog() { if (!this.verbose) return; const logEntry = { timestamp: new Date().toISOString(), runId: this.runId, metrics: this.metrics, summary: this.calculateSummary(), }; const logFile = path.join(this.logDir, `performance_${this.runId}.json`); await fs.writeFile(logFile, JSON.stringify(logEntry, null, 2)); } async updatePerformanceHistory() { if (!this.verbose) return; const historyFile = path.join(this.logDir, 'performance_history.json'); let history = []; try { const existingData = await fs.readFile(historyFile, 'utf8'); history = JSON.parse(existingData); } catch (error) { // File doesn't exist or is invalid, start with empty array } const logEntry = { timestamp: new Date().toISOString(), runId: this.runId, metrics: this.metrics, summary: this.calculateSummary(), }; history.push(logEntry); // Keep only last 50 runs to prevent file from growing too large if (history.length > 50) { history = history.slice(-50); } await fs.writeFile(historyFile, JSON.stringify(history, null, 2)); } generatePerformanceTable() { const summary = this.calculateSummary(); return ` ┌─────────────────────────────────────────────────────────────────────────────┐ │ PERFORMANCE SUMMARY TABLE │ ├─────────────────────────────────────────────────────────────────────────────┤ │ Run ID: ${this.runId.padEnd(58)} │ │ Timestamp: ${new Date().toISOString().padEnd(52)} │ ├─────────────────────────────────────────────────────────────────────────────┤ │ METRICS │ VALUE │ PERCENTAGE │ ├─────────────────────────────────────────────────────────────────────────────┤ │ Total Execution Time │ ${this.metrics.totalTime.toFixed(2).padStart(8)}ms │ ${'100.0%'.padStart(10)} │ │ File Discovery │ ${this.metrics.fileDiscovery.time.toFixed(2).padStart(8)}ms │ ${((this.metrics.fileDiscovery.time / this.metrics.totalTime) * 100).toFixed(1).padStart(6)}% │ │ Validation │ ${this.metrics.validation.time.toFixed(2).padStart(8)}ms │ ${((this.metrics.validation.time / this.metrics.totalTime) * 100).toFixed(1).padStart(6)}% │ │ Import Analysis │ ${this.metrics.importAnalysis.time.toFixed(2).padStart(8)}ms │ ${((this.metrics.importAnalysis.time / this.metrics.totalTime) * 100).toFixed(1).padStart(6)}% │ │ File Operations │ ${this.metrics.fileOperations.time.toFixed(2).padStart(8)}ms │ ${((this.metrics.fileOperations.time / this.metrics.totalTime) * 100).toFixed(1).padStart(6)}% │ ├─────────────────────────────────────────────────────────────────────────────┤ │ BREAKDOWN │ VALUE │ DETAILS │ ├─────────────────────────────────────────────────────────────────────────────┤ │ Files Processed │ ${summary.totalFiles.toString().padStart(8)} │ ${this.metrics.fileDiscovery.fileCount} discovered │ │ Files Moved │ ${summary.totalMoves.toString().padStart(8)} │ ${this.metrics.fileOperations.moves} operations │ │ Files Updated │ ${summary.totalUpdates.toString().padStart(8)} │ ${this.metrics.fileOperations.updates} imports │ │ Files with Imports │ ${this.metrics.importAnalysis.filesWithImports.toString().padStart(8)} │ ${((this.metrics.importAnalysis.filesWithImports / this.metrics.importAnalysis.totalFiles) * 100).toFixed(1)}% of total │ ├─────────────────────────────────────────────────────────────────────────────┤ │ AVERAGE TIMES │ VALUE │ DETAILS │ ├─────────────────────────────────────────────────────────────────────────────┤ │ Analysis per Move │ ${summary.avgAnalysisTime.toFixed(2).padStart(8)}ms │ ${this.metrics.individualMoves.length} moves │ │ Physical Move │ ${summary.avgMoveTime.toFixed(2).padStart(8)}ms │ ${this.metrics.fileOperations.moves} operations │ │ Import Updates │ ${summary.avgUpdateTime.toFixed(2).padStart(8)}ms │ ${this.metrics.fileOperations.updates} updates │ │ File Analysis │ ${summary.avgFileAnalysisTime.toFixed(2).padStart(8)}ms │ ${this.metrics.importAnalysis.individualFileTimes.length} files │ ├─────────────────────────────────────────────────────────────────────────────┤ │ CACHE PERFORMANCE │ VALUE │ DETAILS │ ├─────────────────────────────────────────────────────────────────────────────┤ │ Cache Hit Rate │ ${(summary.cacheHitRate * 100).toFixed(1).padStart(6)}% │ ${this.metrics.cachePerformance.astCacheHits + this.metrics.cachePerformance.fileCacheHits}/${this.metrics.cachePerformance.totalCacheLookups} hits │ │ AST Cache Hits │ ${this.metrics.cachePerformance.astCacheHits.toString().padStart(8)} │ ${this.metrics.cachePerformance.totalCacheLookups} lookups │ │ File Cache Hits │ ${this.metrics.cachePerformance.fileCacheHits.toString().padStart(8)} │ ${this.metrics.cachePerformance.totalCacheLookups} lookups │ └─────────────────────────────────────────────────────────────────────────────┘ `; } startTimer(label) { // If not verbose, return no-op timer immediately if (!this.verbose) { return new NoOpTimer(); } const start = performance.now(); this.timerStack.set(label, start); return { end: () => { const end = performance.now(); const duration = end - start; console.log(`⏱️ ${label}: ${duration.toFixed(2)}ms`); this.timerStack.delete(label); return duration; }, }; } trackFileAnalysis(file, readTime, parseTime, matchTime, importCount) { // Only track if verbose mode is enabled if (!this.verbose) return; const totalTime = readTime + parseTime + matchTime; this.metrics.importAnalysis.individualFileTimes.push({ file, readTime, parseTime, matchTime, totalTime, importCount, }); } addFileOpTime(type, time) { // Only track if verbose mode is enabled if (!this.verbose) return; if (type === 'move') this.metrics.fileOperations.moveTime += time; if (type === 'write') this.metrics.fileOperations.writeTime += time; } addFileOpUpdates(count) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.fileOperations.updates += count; } addFileOpMoves(count) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.fileOperations.moves += count; } setFileOpTotalTime(time) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.fileOperations.time = time; } addValidationTime(time, moveCount) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.validation.time = time; this.metrics.validation.moveCount = moveCount; } addDiscoveryTime(time, fileCount) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.fileDiscovery.time = time; this.metrics.fileDiscovery.fileCount = fileCount; } setImportAnalysisTime(time, totalFiles, filesWithImports) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.importAnalysis.time = time; this.metrics.importAnalysis.totalFiles = totalFiles; this.metrics.importAnalysis.filesWithImports = filesWithImports; } setImportAnalysisBreakdown(read, parse, match) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.importAnalysis.fileReadTime = read; this.metrics.importAnalysis.astParseTime = parse; this.metrics.importAnalysis.importMatchingTime = match; } clearFileAnalysisTimes() { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.importAnalysis.individualFileTimes = []; } addMoveMetrics(move) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.individualMoves.push(move); } setTotalTime(time) { // Only track if verbose mode is enabled if (!this.verbose) return; this.metrics.totalTime = time; } async printSummary() { // Only print if verbose mode is enabled if (!this.verbose) return; // Save performance data await this.savePerformanceLog(); await this.updatePerformanceHistory(); // Print detailed summary console.log(this.generatePerformanceTable()); // Print detailed breakdown console.log("\n📊 DETAILED PERFORMANCE BREAKDOWN"); console.log("=".repeat(50)); console.log(`Total execution time: ${this.metrics.totalTime.toFixed(2)}ms`); console.log(`\nBreakdown:`); console.log(` File discovery: ${this.metrics.fileDiscovery.time.toFixed(2)}ms (${this.metrics.fileDiscovery.fileCount} files)`); console.log(` Validation: ${this.metrics.validation.time.toFixed(2)}ms (${this.metrics.validation.moveCount} moves)`); console.log(` File operations: ${this.metrics.fileOperations.time.toFixed(2)}ms (${this.metrics.fileOperations.moves} moves, ${this.metrics.fileOperations.updates} updates)`); // Detailed import analysis breakdown if (this.metrics.importAnalysis.time > 0) { console.log(`\n📈 Import Analysis Details:`); console.log(` Total analysis time: ${this.metrics.importAnalysis.time.toFixed(2)}ms`); console.log(` Files processed: ${this.metrics.importAnalysis.totalFiles}`); console.log(` Files with imports: ${this.metrics.importAnalysis.filesWithImports}`); console.log(` File reading: ${this.metrics.importAnalysis.fileReadTime.toFixed(2)}ms (${((this.metrics.importAnalysis.fileReadTime / this.metrics.importAnalysis.time) * 100).toFixed(1)}%)`); console.log(` AST parsing: ${this.metrics.importAnalysis.astParseTime.toFixed(2)}ms (${((this.metrics.importAnalysis.astParseTime / this.metrics.importAnalysis.time) * 100).toFixed(1)}%)`); console.log(` Import matching: ${this.metrics.importAnalysis.importMatchingTime.toFixed(2)}ms (${((this.metrics.importAnalysis.importMatchingTime / this.metrics.importAnalysis.time) * 100).toFixed(1)}%)`); // Show top 10 slowest files if (this.metrics.importAnalysis.individualFileTimes.length > 0) { const sortedFiles = [...this.metrics.importAnalysis.individualFileTimes] .sort((a, b) => b.totalTime - a.totalTime) .slice(0, 10); console.log(`\n🐌 Top 10 slowest files to analyze:`); sortedFiles.forEach((file, index) => { const fileName = file.file.split('/').pop() || file.file; console.log(` ${index + 1}. ${fileName}: ${file.totalTime.toFixed(2)}ms (read: ${file.readTime.toFixed(2)}ms, parse: ${file.parseTime.toFixed(2)}ms, match: ${file.matchTime.toFixed(2)}ms, imports: ${file.importCount})`); }); } } // Detailed file operations breakdown if (this.metrics.fileOperations.time > 0) { console.log(`\n📁 File Operations Details:`); console.log(` Physical moves: ${this.metrics.fileOperations.moveTime.toFixed(2)}ms`); console.log(` File writes: ${this.metrics.fileOperations.writeTime.toFixed(2)}ms`); } // Cache performance if (this.metrics.cachePerformance.totalCacheLookups > 0) { const astHitRate = (this.metrics.cachePerformance.astCacheHits / this.metrics.cachePerformance.totalCacheLookups * 100).toFixed(1); const fileHitRate = (this.metrics.cachePerformance.fileCacheHits / this.metrics.cachePerformance.totalCacheLookups * 100).toFixed(1); console.log(`\nCache Performance:`); console.log(` AST cache hit rate: ${astHitRate}% (${this.metrics.cachePerformance.astCacheHits}/${this.metrics.cachePerformance.totalCacheLookups})`); console.log(` File cache hit rate: ${fileHitRate}% (${this.metrics.cachePerformance.fileCacheHits}/${this.metrics.cachePerformance.totalCacheLookups})`); } if (this.metrics.individualMoves.length > 0) { console.log(`\nIndividual move performance:`); this.metrics.individualMoves.forEach((move, index) => { const totalMoveTime = move.analysisTime + move.moveTime + move.updateTime; console.log(` Move ${index + 1}: ${totalMoveTime.toFixed(2)}ms total`); console.log(` Analysis: ${move.analysisTime.toFixed(2)}ms`); if (move.detailedAnalysis) { console.log(` File reading: ${move.detailedAnalysis.fileReadTime.toFixed(2)}ms`); console.log(` AST parsing: ${move.detailedAnalysis.astParseTime.toFixed(2)}ms`); console.log(` Import matching: ${move.detailedAnalysis.importMatchingTime.toFixed(2)}ms`); console.log(` Files processed: ${move.detailedAnalysis.filesProcessed}, with imports: ${move.detailedAnalysis.filesWithImports}`); } console.log(` Physical move: ${move.moveTime.toFixed(2)}ms`); console.log(` Import updates: ${move.updateTime.toFixed(2)}ms (${move.filesUpdated} files updated)`); }); } // Performance insights console.log(`\n💡 Performance Insights:`); const avgAnalysisTime = this.metrics.individualMoves.reduce((sum, move) => sum + move.analysisTime, 0) / this.metrics.individualMoves.length; const avgUpdateTime = this.metrics.individualMoves.reduce((sum, move) => sum + move.updateTime, 0) / this.metrics.individualMoves.length; console.log(` Average analysis time per move: ${avgAnalysisTime.toFixed(2)}ms`); console.log(` Average update time per move: ${avgUpdateTime.toFixed(2)}ms`); console.log(` File discovery efficiency: ${(this.metrics.fileDiscovery.fileCount / this.metrics.fileDiscovery.time * 1000).toFixed(1)} files/ms`); if (this.metrics.importAnalysis.individualFileTimes.length > 0) { const avgFileAnalysisTime = this.metrics.importAnalysis.individualFileTimes.reduce((sum, file) => sum + file.totalTime, 0) / this.metrics.importAnalysis.individualFileTimes.length; console.log(` Average file analysis time: ${avgFileAnalysisTime.toFixed(2)}ms`); const filesWithImports = this.metrics.importAnalysis.individualFileTimes.filter(f => f.importCount > 0); if (filesWithImports.length > 0) { const avgImportsPerFile = filesWithImports.reduce((sum, file) => sum + file.importCount, 0) / filesWithImports.length; console.log(` Average imports per file (files with imports): ${avgImportsPerFile.toFixed(1)}`); } } // Clear caches to free memory this.clearCaches(); } clearCaches() { // Only clear if verbose mode is enabled if (!this.verbose) return; if (typeof globalThis !== 'undefined' && globalThis.astCache) { globalThis.astCache.clear(); } if (typeof globalThis !== 'undefined' && globalThis.fileContentCache) { globalThis.fileContentCache.clear(); } console.log('🧹 Caches cleared'); } reset() { // Only reset if verbose mode is enabled if (!this.verbose) return; this.metrics = this.getInitialMetrics(); } getMetrics() { // Return empty metrics if not verbose to avoid memory allocation if (!this.verbose) { return this.getInitialMetrics(); } return this.metrics; } // Cache tracking - no-op when not verbose trackCacheHit(type) { if (!this.verbose) return; this.metrics.cachePerformance.totalCacheLookups++; if (type === 'ast') this.metrics.cachePerformance.astCacheHits++; else this.metrics.cachePerformance.fileCacheHits++; } trackCacheLookup() { if (!this.verbose) return; this.metrics.cachePerformance.totalCacheLookups++; } } let perfInstance = null; export function getPerformance(verbose = false) { if (!perfInstance) perfInstance = new Performance(verbose); return perfInstance; } export function resetPerformance() { perfInstance = null; } //# sourceMappingURL=performance.js.map