UNPKG

nextjs-memory-profiler

Version:

Memory leak detection and profiling tool for Next.js applications

259 lines (213 loc) 7.71 kB
const v8 = require("v8"); const fs = require("fs"); const path = require("path"); class MemoryProfiler { constructor(options = {}) { this.interval = options.interval || 5000; this.threshold = options.threshold || 100; this.samples = []; this.routeMemory = new Map(); this.isRunning = false; this.logFile = path.join(process.cwd(), "memory-profile.log"); this.isServer = typeof window === 'undefined'; } measureMemory(initialMemory = null) { if (!this.isServer) { console.warn('Memory profiling is only available on the server side'); return { timestamp: new Date().toISOString(), heapUsed: 0, heapTotal: 0, external: 0, }; } const currentMemory = process.memoryUsage(); if (!initialMemory) { return { timestamp: new Date().toISOString(), heapUsed: Math.round(currentMemory.heapUsed / 1024 / 1024), heapTotal: Math.round(currentMemory.heapTotal / 1024 / 1024), external: Math.round(currentMemory.external / 1024 / 1024), }; } return { timestamp: new Date().toISOString(), heapUsed: Math.round((currentMemory.heapUsed - initialMemory.heapUsed) / 1024 / 1024), heapTotal: Math.round((currentMemory.heapTotal - initialMemory.heapTotal) / 1024 / 1024), external: Math.round((currentMemory.external - initialMemory.external) / 1024 / 1024), }; } startRouteProfiler(route) { if (!this.isServer) { console.warn('Memory profiling is only available on the server side'); return { end: () => ({ heapUsed: 0, heapTotal: 0, external: 0, duration: 0 }) }; } const startTime = process.hrtime(); const initialMemory = process.memoryUsage(); return { end: () => { const diff = process.hrtime(startTime); const duration = diff[0] * 1000 + diff[1] / 1000000; const memoryDiff = this.measureMemory(initialMemory); this.recordRouteMemory(route, { ...memoryDiff, duration }); return { ...memoryDiff, duration }; } }; } recordRouteMemory(route, memoryDiff) { if (!this.isServer) return; if (!this.routeMemory.has(route)) { this.routeMemory.set(route, []); } const routeStats = this.routeMemory.get(route); routeStats.push({ timestamp: new Date().toISOString(), ...memoryDiff, }); if (routeStats.length > 1) { const lastTwo = routeStats.slice(-2); const increase = lastTwo[1].heapUsed - lastTwo[0].heapUsed; if (increase > this.threshold) { const warning = `⚠️ Route Memory Leak Uyarısı [${route}]: ${increase}MB artış tespit edildi`; this.writeLog(warning); console.warn(warning); } } } start() { if (!this.isServer) { console.warn('Memory profiling is only available on the server side'); return; } if (this.isRunning) return; this.isRunning = true; console.log("Memory profiling başlatıldı..."); this.writeLog("Memory profiling başlatıldı"); this.intervalId = setInterval(() => { const memoryStats = this.measureMemory(); const heapStats = v8.getHeapStatistics(); const sample = { ...memoryStats, heapSizeLimit: Math.round(heapStats.heap_size_limit / 1024 / 1024), }; this.samples.push(sample); this.analyzeMemory(sample); }, this.interval); // Uygulama kapanırken profiler'ı durdur process.on('SIGINT', () => { this.stop(); }); process.on('SIGTERM', () => { this.stop(); }); } stop() { if (!this.isServer || !this.isRunning) return; clearInterval(this.intervalId); this.isRunning = false; this.writeLog("Memory profiling durduruldu"); console.log("Memory profiling durduruldu"); this.generateReport(); } analyzeMemory(sample) { if (this.samples.length < 2) return; const previousSample = this.samples[this.samples.length - 2]; const memoryIncrease = sample.heapUsed - previousSample.heapUsed; if (memoryIncrease > this.threshold) { const warning = `⚠️ Memory Leak Uyarısı: ${memoryIncrease}MB artış tespit edildi`; this.writeLog(warning); console.warn(warning); } const heapUsagePercent = (sample.heapUsed / sample.heapSizeLimit) * 100; if (heapUsagePercent > 80) { const warning = `⚠️ Yüksek Heap Kullanımı: %${heapUsagePercent.toFixed(2)}`; this.writeLog(warning); console.warn(warning); } } generateReport() { if (!this.isServer || this.samples.length === 0) return; try { const report = { startTime: this.samples[0].timestamp, endTime: this.samples[this.samples.length - 1].timestamp, totalSamples: this.samples.length, memoryTrend: this.calculateMemoryTrend(), maxHeapUsed: Math.max(...this.samples.map((s) => s.heapUsed)), minHeapUsed: Math.min(...this.samples.map((s) => s.heapUsed)), averageHeapUsed: this.calculateAverage( this.samples.map((s) => s.heapUsed) ), routeAnalysis: this.generateRouteReport(), }; const reportPath = path.join(process.cwd(), "memory-report.json"); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); console.log(`Detaylı rapor oluşturuldu: ${reportPath}`); } catch (error) { console.error('Rapor oluşturulurken hata:', error); } } generateRouteReport() { const routeReport = {}; for (const [route, samples] of this.routeMemory.entries()) { try { routeReport[route] = { totalRequests: samples.length, averageHeapUsed: this.calculateAverage(samples.map((s) => s.heapUsed)), averageDuration: this.calculateAverage(samples.map((s) => s.duration)), maxHeapUsed: Math.max(...samples.map((s) => s.heapUsed)), minHeapUsed: Math.min(...samples.map((s) => s.heapUsed)), trend: this.calculateRouteTrend(samples), }; } catch (error) { console.error(`Route analizi oluşturulurken hata (${route}):`, error); } } return routeReport; } calculateRouteTrend(samples) { if (samples.length < 2) return "Yetersiz veri"; try { const firstSample = samples[0].heapUsed; const lastSample = samples[samples.length - 1].heapUsed; const trend = ((lastSample - firstSample) / firstSample) * 100; return `${trend.toFixed(2)}% ${trend > 0 ? "artış" : "azalış"}`; } catch (error) { console.error('Trend hesaplanırken hata:', error); return "Hesaplanamadı"; } } calculateMemoryTrend() { if (this.samples.length < 2) return "Yetersiz veri"; try { const firstSample = this.samples[0].heapUsed; const lastSample = this.samples[this.samples.length - 1].heapUsed; const trend = ((lastSample - firstSample) / firstSample) * 100; return `${trend.toFixed(2)}% ${trend > 0 ? "artış" : "azalış"}`; } catch (error) { console.error('Trend hesaplanırken hata:', error); return "Hesaplanamadı"; } } calculateAverage(numbers) { if (!numbers || numbers.length === 0) return 0; return Math.round(numbers.reduce((a, b) => a + b, 0) / numbers.length); } writeLog(message) { if (!this.isServer) return; try { const logMessage = `[${new Date().toISOString()}] ${message}\n`; fs.appendFileSync(this.logFile, logMessage); } catch (error) { console.error('Log yazılırken hata:', error); } } } module.exports = MemoryProfiler;