|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import logger from './logger.js'; |
|
|
import { GC_COOLDOWN } from '../constants/index.js'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const MemoryPressure = { |
|
|
LOW: 'low', |
|
|
MEDIUM: 'medium', |
|
|
HIGH: 'high', |
|
|
CRITICAL: 'critical' |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateThresholds(thresholdMB) { |
|
|
const highBytes = thresholdMB * 1024 * 1024; |
|
|
return { |
|
|
LOW: Math.floor(highBytes * 0.3), |
|
|
MEDIUM: Math.floor(highBytes * 0.6), |
|
|
HIGH: highBytes, |
|
|
TARGET: Math.floor(highBytes * 0.5) |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
let THRESHOLDS = calculateThresholds(100); |
|
|
|
|
|
|
|
|
const POOL_SIZES = { |
|
|
[MemoryPressure.LOW]: { chunk: 30, toolCall: 15, lineBuffer: 5 }, |
|
|
[MemoryPressure.MEDIUM]: { chunk: 20, toolCall: 10, lineBuffer: 3 }, |
|
|
[MemoryPressure.HIGH]: { chunk: 10, toolCall: 5, lineBuffer: 2 }, |
|
|
[MemoryPressure.CRITICAL]: { chunk: 5, toolCall: 3, lineBuffer: 1 } |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MemoryManager { |
|
|
constructor() { |
|
|
|
|
|
this.currentPressure = MemoryPressure.LOW; |
|
|
|
|
|
this.cleanupCallbacks = new Set(); |
|
|
|
|
|
this.lastGCTime = 0; |
|
|
|
|
|
this.gcCooldown = GC_COOLDOWN; |
|
|
this.checkInterval = null; |
|
|
this.isShuttingDown = false; |
|
|
|
|
|
this.configuredThresholdMB = 100; |
|
|
|
|
|
|
|
|
this.stats = { |
|
|
gcCount: 0, |
|
|
cleanupCount: 0, |
|
|
peakMemory: 0 |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setThreshold(thresholdMB) { |
|
|
if (thresholdMB && thresholdMB > 0) { |
|
|
this.configuredThresholdMB = thresholdMB; |
|
|
THRESHOLDS = calculateThresholds(thresholdMB); |
|
|
logger.info(`内存阈值已设置: ${thresholdMB}MB (LOW: ${Math.floor(THRESHOLDS.LOW/1024/1024)}MB, MEDIUM: ${Math.floor(THRESHOLDS.MEDIUM/1024/1024)}MB, HIGH: ${Math.floor(THRESHOLDS.HIGH/1024/1024)}MB)`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getThresholds() { |
|
|
return { |
|
|
configuredMB: this.configuredThresholdMB, |
|
|
lowMB: Math.floor(THRESHOLDS.LOW / 1024 / 1024), |
|
|
mediumMB: Math.floor(THRESHOLDS.MEDIUM / 1024 / 1024), |
|
|
highMB: Math.floor(THRESHOLDS.HIGH / 1024 / 1024), |
|
|
targetMB: Math.floor(THRESHOLDS.TARGET / 1024 / 1024) |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start(interval = 30000) { |
|
|
if (this.checkInterval) return; |
|
|
|
|
|
this.checkInterval = setInterval(() => { |
|
|
if (!this.isShuttingDown) { |
|
|
this.check(); |
|
|
} |
|
|
}, interval); |
|
|
|
|
|
|
|
|
this.check(); |
|
|
logger.info(`内存管理器已启动 (检查间隔: ${interval/1000}秒)`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stop() { |
|
|
this.isShuttingDown = true; |
|
|
if (this.checkInterval) { |
|
|
clearInterval(this.checkInterval); |
|
|
this.checkInterval = null; |
|
|
} |
|
|
this.cleanupCallbacks.clear(); |
|
|
logger.info('内存管理器已停止'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
registerCleanup(callback) { |
|
|
this.cleanupCallbacks.add(callback); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unregisterCleanup(callback) { |
|
|
this.cleanupCallbacks.delete(callback); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getMemoryUsage() { |
|
|
const usage = process.memoryUsage(); |
|
|
return { |
|
|
heapUsed: usage.heapUsed, |
|
|
heapTotal: usage.heapTotal, |
|
|
rss: usage.rss, |
|
|
external: usage.external, |
|
|
heapUsedMB: Math.round(usage.heapUsed / 1024 / 1024 * 10) / 10 |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getPressureLevel(heapUsed) { |
|
|
if (heapUsed < THRESHOLDS.LOW) return MemoryPressure.LOW; |
|
|
if (heapUsed < THRESHOLDS.MEDIUM) return MemoryPressure.MEDIUM; |
|
|
if (heapUsed < THRESHOLDS.HIGH) return MemoryPressure.HIGH; |
|
|
return MemoryPressure.CRITICAL; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getPoolSizes() { |
|
|
return POOL_SIZES[this.currentPressure]; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getCurrentPressure() { |
|
|
return this.currentPressure; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
check() { |
|
|
const { heapUsed, heapUsedMB } = this.getMemoryUsage(); |
|
|
const newPressure = this.getPressureLevel(heapUsed); |
|
|
|
|
|
|
|
|
if (heapUsed > this.stats.peakMemory) { |
|
|
this.stats.peakMemory = heapUsed; |
|
|
} |
|
|
|
|
|
|
|
|
if (newPressure !== this.currentPressure) { |
|
|
logger.info(`内存压力变化: ${this.currentPressure} -> ${newPressure} (${heapUsedMB}MB)`); |
|
|
this.currentPressure = newPressure; |
|
|
} |
|
|
|
|
|
|
|
|
switch (newPressure) { |
|
|
case MemoryPressure.CRITICAL: |
|
|
this.handleCriticalPressure(heapUsedMB); |
|
|
break; |
|
|
case MemoryPressure.HIGH: |
|
|
this.handleHighPressure(heapUsedMB); |
|
|
break; |
|
|
case MemoryPressure.MEDIUM: |
|
|
this.handleMediumPressure(heapUsedMB); |
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
return newPressure; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleMediumPressure(heapUsedMB) { |
|
|
|
|
|
this.notifyCleanup(MemoryPressure.MEDIUM); |
|
|
this.stats.cleanupCount++; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleHighPressure(heapUsedMB) { |
|
|
logger.info(`内存较高 (${heapUsedMB}MB),执行积极清理`); |
|
|
this.notifyCleanup(MemoryPressure.HIGH); |
|
|
this.stats.cleanupCount++; |
|
|
|
|
|
|
|
|
this.tryGC(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleCriticalPressure(heapUsedMB) { |
|
|
logger.warn(`内存紧急 (${heapUsedMB}MB),执行紧急清理`); |
|
|
this.notifyCleanup(MemoryPressure.CRITICAL); |
|
|
this.stats.cleanupCount++; |
|
|
|
|
|
|
|
|
this.forceGC(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
notifyCleanup(pressure) { |
|
|
for (const callback of this.cleanupCallbacks) { |
|
|
try { |
|
|
callback(pressure); |
|
|
} catch (error) { |
|
|
logger.error('清理回调执行失败:', error.message); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tryGC() { |
|
|
const now = Date.now(); |
|
|
if (now - this.lastGCTime < this.gcCooldown) { |
|
|
return false; |
|
|
} |
|
|
return this.forceGC(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
forceGC() { |
|
|
if (global.gc) { |
|
|
const before = this.getMemoryUsage().heapUsedMB; |
|
|
global.gc(); |
|
|
this.lastGCTime = Date.now(); |
|
|
this.stats.gcCount++; |
|
|
const after = this.getMemoryUsage().heapUsedMB; |
|
|
logger.info(`GC 完成: ${before}MB -> ${after}MB (释放 ${(before - after).toFixed(1)}MB)`); |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cleanup() { |
|
|
return this.check(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getStats() { |
|
|
const memory = this.getMemoryUsage(); |
|
|
return { |
|
|
...this.stats, |
|
|
currentPressure: this.currentPressure, |
|
|
currentHeapMB: memory.heapUsedMB, |
|
|
peakMemoryMB: Math.round(this.stats.peakMemory / 1024 / 1024 * 10) / 10, |
|
|
poolSizes: this.getPoolSizes(), |
|
|
thresholds: this.getThresholds() |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const memoryManager = new MemoryManager(); |
|
|
export default memoryManager; |
|
|
|
|
|
|
|
|
export function registerMemoryPoolCleanup(pool, getMaxSize) { |
|
|
memoryManager.registerCleanup(() => { |
|
|
const maxSize = getMaxSize(); |
|
|
while (pool.length > maxSize) { |
|
|
pool.pop(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
export { MemoryPressure, THRESHOLDS }; |