file-mover
Version:
Script to move files and update imports automatically
410 lines • 22.8 kB
JavaScript
// 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