chore: add code formatting config and pre-commit hooks (#450)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* Helps identify memory leaks and optimize resource usage
|
||||
*/
|
||||
|
||||
const v8 = require('v8');
|
||||
const v8 = require('node:v8');
|
||||
|
||||
class MemoryProfiler {
|
||||
constructor() {
|
||||
@@ -19,7 +19,7 @@ class MemoryProfiler {
|
||||
checkpoint(label) {
|
||||
const memUsage = process.memoryUsage();
|
||||
const heapStats = v8.getHeapStatistics();
|
||||
|
||||
|
||||
const checkpoint = {
|
||||
label,
|
||||
timestamp: Date.now() - this.startTime,
|
||||
@@ -28,18 +28,18 @@ class MemoryProfiler {
|
||||
heapTotal: this.formatBytes(memUsage.heapTotal),
|
||||
heapUsed: this.formatBytes(memUsage.heapUsed),
|
||||
external: this.formatBytes(memUsage.external),
|
||||
arrayBuffers: this.formatBytes(memUsage.arrayBuffers || 0)
|
||||
arrayBuffers: this.formatBytes(memUsage.arrayBuffers || 0),
|
||||
},
|
||||
heap: {
|
||||
totalHeapSize: this.formatBytes(heapStats.total_heap_size),
|
||||
usedHeapSize: this.formatBytes(heapStats.used_heap_size),
|
||||
heapSizeLimit: this.formatBytes(heapStats.heap_size_limit),
|
||||
mallocedMemory: this.formatBytes(heapStats.malloced_memory),
|
||||
externalMemory: this.formatBytes(heapStats.external_memory)
|
||||
externalMemory: this.formatBytes(heapStats.external_memory),
|
||||
},
|
||||
raw: {
|
||||
heapUsed: memUsage.heapUsed
|
||||
}
|
||||
heapUsed: memUsage.heapUsed,
|
||||
},
|
||||
};
|
||||
|
||||
// Track peak memory
|
||||
@@ -55,8 +55,8 @@ class MemoryProfiler {
|
||||
* Force garbage collection (requires --expose-gc flag)
|
||||
*/
|
||||
forceGC() {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
if (globalThis.gc) {
|
||||
globalThis.gc();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -67,16 +67,16 @@ class MemoryProfiler {
|
||||
*/
|
||||
getSummary() {
|
||||
const currentMemory = process.memoryUsage();
|
||||
|
||||
|
||||
return {
|
||||
currentUsage: {
|
||||
rss: this.formatBytes(currentMemory.rss),
|
||||
heapTotal: this.formatBytes(currentMemory.heapTotal),
|
||||
heapUsed: this.formatBytes(currentMemory.heapUsed)
|
||||
heapUsed: this.formatBytes(currentMemory.heapUsed),
|
||||
},
|
||||
peakMemory: this.formatBytes(this.peakMemory),
|
||||
totalCheckpoints: this.checkpoints.length,
|
||||
runTime: `${((Date.now() - this.startTime) / 1000).toFixed(2)}s`
|
||||
runTime: `${((Date.now() - this.startTime) / 1000).toFixed(2)}s`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,12 +86,12 @@ class MemoryProfiler {
|
||||
getDetailedReport() {
|
||||
const summary = this.getSummary();
|
||||
const memoryGrowth = this.calculateMemoryGrowth();
|
||||
|
||||
|
||||
return {
|
||||
summary,
|
||||
memoryGrowth,
|
||||
checkpoints: this.checkpoints,
|
||||
recommendations: this.getRecommendations(memoryGrowth)
|
||||
recommendations: this.getRecommendations(memoryGrowth),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -100,23 +100,23 @@ class MemoryProfiler {
|
||||
*/
|
||||
calculateMemoryGrowth() {
|
||||
if (this.checkpoints.length < 2) return [];
|
||||
|
||||
|
||||
const growth = [];
|
||||
for (let i = 1; i < this.checkpoints.length; i++) {
|
||||
const prev = this.checkpoints[i - 1];
|
||||
const curr = this.checkpoints[i];
|
||||
|
||||
const heapDiff = curr.raw.heapUsed - prev.raw.heapUsed;
|
||||
|
||||
for (let index = 1; index < this.checkpoints.length; index++) {
|
||||
const previous = this.checkpoints[index - 1];
|
||||
const current = this.checkpoints[index];
|
||||
|
||||
const heapDiff = current.raw.heapUsed - previous.raw.heapUsed;
|
||||
|
||||
growth.push({
|
||||
from: prev.label,
|
||||
to: curr.label,
|
||||
from: previous.label,
|
||||
to: current.label,
|
||||
heapGrowth: this.formatBytes(Math.abs(heapDiff)),
|
||||
isIncrease: heapDiff > 0,
|
||||
timeDiff: `${((curr.timestamp - prev.timestamp) / 1000).toFixed(2)}s`
|
||||
timeDiff: `${((current.timestamp - previous.timestamp) / 1000).toFixed(2)}s`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return growth;
|
||||
}
|
||||
|
||||
@@ -125,40 +125,41 @@ class MemoryProfiler {
|
||||
*/
|
||||
getRecommendations(memoryGrowth) {
|
||||
const recommendations = [];
|
||||
|
||||
|
||||
// Check for large memory growth
|
||||
const largeGrowths = memoryGrowth.filter(g => {
|
||||
const largeGrowths = memoryGrowth.filter((g) => {
|
||||
const bytes = this.parseBytes(g.heapGrowth);
|
||||
return bytes > 50 * 1024 * 1024; // 50MB
|
||||
});
|
||||
|
||||
|
||||
if (largeGrowths.length > 0) {
|
||||
recommendations.push({
|
||||
type: 'warning',
|
||||
message: `Large memory growth detected in ${largeGrowths.length} operations`,
|
||||
details: largeGrowths.map(g => `${g.from} → ${g.to}: ${g.heapGrowth}`)
|
||||
details: largeGrowths.map((g) => `${g.from} → ${g.to}: ${g.heapGrowth}`),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Check peak memory
|
||||
if (this.peakMemory > 500 * 1024 * 1024) { // 500MB
|
||||
if (this.peakMemory > 500 * 1024 * 1024) {
|
||||
// 500MB
|
||||
recommendations.push({
|
||||
type: 'warning',
|
||||
message: `High peak memory usage: ${this.formatBytes(this.peakMemory)}`,
|
||||
suggestion: 'Consider processing files in smaller batches'
|
||||
suggestion: 'Consider processing files in smaller batches',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Check for potential memory leaks
|
||||
const continuousGrowth = this.checkContinuousGrowth();
|
||||
if (continuousGrowth) {
|
||||
recommendations.push({
|
||||
type: 'error',
|
||||
message: 'Potential memory leak detected',
|
||||
details: 'Memory usage continuously increases without significant decreases'
|
||||
details: 'Memory usage continuously increases without significant decreases',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
@@ -167,14 +168,14 @@ class MemoryProfiler {
|
||||
*/
|
||||
checkContinuousGrowth() {
|
||||
if (this.checkpoints.length < 5) return false;
|
||||
|
||||
|
||||
let increasingCount = 0;
|
||||
for (let i = 1; i < this.checkpoints.length; i++) {
|
||||
if (this.checkpoints[i].raw.heapUsed > this.checkpoints[i - 1].raw.heapUsed) {
|
||||
for (let index = 1; index < this.checkpoints.length; index++) {
|
||||
if (this.checkpoints[index].raw.heapUsed > this.checkpoints[index - 1].raw.heapUsed) {
|
||||
increasingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If memory increases in more than 80% of checkpoints, might be a leak
|
||||
return increasingCount / (this.checkpoints.length - 1) > 0.8;
|
||||
}
|
||||
@@ -184,31 +185,31 @@ class MemoryProfiler {
|
||||
*/
|
||||
formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
const index = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return Number.parseFloat((bytes / Math.pow(k, index)).toFixed(2)) + ' ' + sizes[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse human-readable bytes back to number
|
||||
*/
|
||||
parseBytes(str) {
|
||||
const match = str.match(/^([\d.]+)\s*([KMGT]?B?)$/i);
|
||||
parseBytes(string_) {
|
||||
const match = string_.match(/^([\d.]+)\s*([KMGT]?B?)$/i);
|
||||
if (!match) return 0;
|
||||
|
||||
const value = parseFloat(match[1]);
|
||||
|
||||
const value = Number.parseFloat(match[1]);
|
||||
const unit = match[2].toUpperCase();
|
||||
|
||||
|
||||
const multipliers = {
|
||||
'B': 1,
|
||||
'KB': 1024,
|
||||
'MB': 1024 * 1024,
|
||||
'GB': 1024 * 1024 * 1024
|
||||
B: 1,
|
||||
KB: 1024,
|
||||
MB: 1024 * 1024,
|
||||
GB: 1024 * 1024 * 1024,
|
||||
};
|
||||
|
||||
|
||||
return value * (multipliers[unit] || 1);
|
||||
}
|
||||
|
||||
@@ -221,4 +222,4 @@ class MemoryProfiler {
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
module.exports = new MemoryProfiler();
|
||||
module.exports = new MemoryProfiler();
|
||||
|
||||
Reference in New Issue
Block a user