Created
January 25, 2018 06:39
-
-
Save agreatfool/024d11c54c1a8e474ded5269d54f5c99 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
// $ node -v | |
// v8.4.0 | |
/** | |
Debug输出信息的查看,使用: | |
DEBUG=MEM:* node xxx.js | |
查看内存输出日志: | |
DEBUG=MEM:ReportMemory node ... | |
查看正常内存使用日志: | |
DEBUG=MEM:NormalLogic node ... | |
查看内存泄露报告: | |
DEBUG=MEM:LeakLogic node ... | |
查看组合: | |
DEBUG=MEM:ReportMemory,MEM:LeakLogic node ... | |
查看所有: | |
DEBUG=MEM:* node ... | |
使用范例: | |
正常内存使用: | |
DEBUG=MEM:* node --expose-gc cases/leak-and-gc.js \ | |
--normal --report-memory \ | |
--new-gc-trigger=500 \ | |
--large-gc-trigger=200 \ | |
--new-increase-max=30 \ | |
--large-increase-max=10 | |
内存泄露使用: | |
DEBUG=MEM:* node cases/leak-and-gc.js \ | |
--leak --report-memory \ | |
--leak-size=50 | |
*/ | |
"use strict"; | |
const v8 = require("v8"); | |
const debug = require("debug"); | |
const program = require("commander"); | |
const pkg = require('../package.json'); | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
//-* Const | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
const POOL_LEAK = []; // 内存泄露对象池 | |
let POOL_NORMAL_NEW = []; // 新生代对象池 | |
let POOL_NORMAL_LARGE = []; // 大对象对象池 | |
let CHAR_MEMORY_BYTE = 8; // 单个字符占用字节数 | |
const NEW_GC_TRIGGER = 600; // 默认新生代内存回收阈值 | |
const LARGE_GC_TRIGGER = 300; // 默认大对象内存回收阈值 | |
const MEM_NEW_COUNT_MIN = 20; // 默认新生代每次添加对象数量的最小值 | |
const MEM_NEW_COUNT_MAX = 50; // 默认新生代每次添加对象数量的最大值 | |
const MEM_LARGE_COUNT_MIN = 5; // 默认大对象每次添加对象数量的最小值 | |
const MEM_LARGE_COUNT_MAX = 20; // 默认大对象每次添加对象数量的最大值 | |
const MEM_LEAK_SIZE = 40; // 默认单次泄露内存量 MB | |
const RUN_INTERVAL = 1000; // 默认运行间隔 ms | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
//-* Command line | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
program.version(pkg.version) | |
.option("--normal", "是否运行正常内存使用逻辑") | |
.option("--new-gc-trigger <n>", "新生代内存空间释放阈值,当新生代内存数组数量达到该值即会在代码中进行释放,默认600") | |
.option("--large-gc-trigger <n>", "大对象内存空间释放阈值,当大对象内存数组数量达到该值即会在代码中进行释放,默认300") | |
.option("--new-increase-max <n>", "新生代内存对象增长幅度最大值,必须大于20,默认50") | |
.option("--large-increase-max <n>", "大对象内存对象增长幅度最大值,必须大于5,默认20") | |
.option("--leak", "是否运行内存泄露逻辑") | |
.option("--leak-size <n>", "单次泄露内存量 MB") | |
.option("--report-memory", "是否输出内存使用信息") | |
.option("--report-heap", "是否输出堆内存使用信息") | |
.option("--report-space", "是否输出堆空间使用信息") | |
.option("--interval <n>", "逻辑执行间隔,默认1000ms,即1s") | |
.parse(process.argv); | |
const NORMAL = program.normal !== undefined; | |
const NORMAL_NEW_GC_TRIGGER = program.newGcTrigger === undefined ? NEW_GC_TRIGGER : program.newGcTrigger; | |
const NORMAL_LARGE_GC_TRIGGER = program.largeGcTrigger === undefined ? LARGE_GC_TRIGGER : program.largeGcTrigger; | |
const NORMAL_NEW_INCREASE_MAX = (program.newIncreaseMax === undefined || program.newIncreaseMax <= MEM_NEW_COUNT_MIN) | |
? MEM_NEW_COUNT_MAX : program.newIncreaseMax; | |
const NORMAL_LARGE_INCREASE_MAX = (program.largeIncreaseMax === undefined || program.largeIncreaseMax <= MEM_LARGE_COUNT_MIN) | |
? MEM_LARGE_COUNT_MAX : program.largeIncreaseMax; | |
const LEAK = program.leak !== undefined; | |
const LEAK_SIZE = program.leakSize === undefined ? MEM_LEAK_SIZE : program.leakSize; | |
const REPORT_MEMORY = program.reportMemory !== undefined; | |
const REPORT_HEAP = program.reportHeap !== undefined; | |
const REPORT_SPACE = program.reportSpace !== undefined; | |
const INTERVAL = program.interval === undefined ? RUN_INTERVAL : program.interval; | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
//-* Memory leak | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
class LeakLogic { | |
constructor(util, log) { | |
this._util = util; | |
this._log = log; | |
this._blockSize = parseInt(LEAK_SIZE / 8) * 1024 * 1024; // n * 8 MB | |
} | |
makeLeak() { | |
let buff = new Array(this._blockSize); | |
for (let i = 0; i < buff.length; i++) { | |
buff[i] = "X"; | |
} | |
POOL_LEAK.push(buff); | |
this._log.reportLeak(`Leak size: ${this._util.formatReadable(this._blockSize * CHAR_MEMORY_BYTE)}`); | |
} | |
} | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
//-* Normal logic | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
class NormalLogic { | |
constructor(util, log) { | |
this._util = util; | |
this._log = log; | |
this._blockSizeNew = 8 * 1024; // 8 * 8 = 64 KB | |
this._blockSizeLargeObj = parseInt(0.2 * 1024 * 1024); // 0.2 * 8 = 1.6 MB | |
this._poolNewTrigger = NORMAL_NEW_GC_TRIGGER; // POOL_NORMAL_NEW.length | |
this._poolLargeTrigger = NORMAL_LARGE_GC_TRIGGER; // POOL_NORMAL_LARGE.length | |
} | |
useNewSpace() { | |
const randLoopCount = this._util.getRandomInt(MEM_NEW_COUNT_MIN, NORMAL_NEW_INCREASE_MAX); | |
for (let i = 0; i < randLoopCount; i++) { | |
let buff = new Array(this._blockSizeNew); | |
for (let i = 0; i < buff.length; i++) { | |
buff[i] = "N"; | |
} | |
POOL_NORMAL_NEW.push(buff); | |
} | |
this._log.reportNormal( | |
`Memory NEW: count: ${randLoopCount}, size: ${this._util.formatReadable(this._blockSizeNew * CHAR_MEMORY_BYTE * randLoopCount, 3)}` | |
); | |
this.recycleNewSpace(); | |
} | |
useLargeObjectSpace() { | |
const randLoopCount = this._util.getRandomInt(MEM_LARGE_COUNT_MIN, NORMAL_LARGE_INCREASE_MAX); | |
for (let i = 0; i < randLoopCount; i++) { | |
let buff = new Array(this._blockSizeLargeObj); | |
for (let i = 0; i < buff.length; i++) { | |
buff[i] = "L"; | |
} | |
POOL_NORMAL_LARGE.push(buff); | |
} | |
this._log.reportNormal( | |
`Memory LARGE: count: ${randLoopCount}, size: ${this._util.formatReadable(this._blockSizeLargeObj * CHAR_MEMORY_BYTE * randLoopCount, 3)}` | |
); | |
this.recycleLargeObjectSpace(); | |
} | |
recycleNewSpace() { | |
let hasClear = false; | |
let hasGc = false; | |
if (POOL_NORMAL_NEW.length > this._poolNewTrigger) { | |
POOL_NORMAL_NEW = []; | |
hasClear = true; | |
hasGc = this.toggleGc(); | |
} | |
if (hasClear || hasGc) { | |
this._log.reportNormal(`Clear new pool: ${hasClear}, gc: ${hasGc}`); | |
} | |
} | |
recycleLargeObjectSpace() { | |
let hasClear = false; | |
let hasGc = false; | |
if (POOL_NORMAL_LARGE.length > this._poolLargeTrigger) { | |
POOL_NORMAL_LARGE = []; | |
hasClear = true; | |
hasGc = this.toggleGc(); | |
} | |
if (hasClear || hasGc) { | |
this._log.reportNormal(`Clear large pool: ${hasClear}, gc: ${hasGc}`); | |
} | |
} | |
toggleGc() { | |
if (global.gc) { | |
global.gc(); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
} | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
//-* Tools | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
class Utility { | |
constructor(log) { | |
this._log = log; | |
} | |
formatReadable(bytes, precision) { | |
precision = precision | 2; | |
return (bytes / 1024 / 1024).toFixed(precision) + ' MB'; | |
} | |
getRandomInt(min, max) { | |
min = Math.ceil(min); | |
max = Math.floor(max); | |
return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive | |
} | |
reportMemory() { | |
const mem = process.memoryUsage(); | |
this._log.reportMem(`Process: heapTotal ${this.formatReadable(mem.heapTotal)}, heapUsed ${this.formatReadable(mem.heapUsed)}, rss ${this.formatReadable(mem.rss)}`); | |
} | |
reportHeap() { | |
const heapInfo = v8.getHeapStatistics(); | |
for (let k in heapInfo) { | |
if (!heapInfo.hasOwnProperty(k)) { | |
continue; | |
} | |
heapInfo[k] = this.formatReadable(heapInfo[k]); | |
} | |
this._log.reportMem(heapInfo); | |
} | |
reportSpace() { | |
const spaceInfo = v8.getHeapSpaceStatistics(); | |
spaceInfo.forEach((space, index) => { | |
Object.keys(space).forEach((spaceKey) => { | |
if (spaceKey === "space_name") { | |
return; // do nothing with name | |
} | |
spaceInfo[index][spaceKey] = this.formatReadable(spaceInfo[index][spaceKey]); | |
}); | |
}); | |
this._log.reportMem(spaceInfo); | |
} | |
} | |
class Log { | |
constructor() { | |
this._reportMem = debug("MEM:ReportMemory"); | |
this._reportNormalLogic = debug("MEM:NormalLogic"); | |
this._reportLeak = debug("MEM:LeakLogic"); | |
} | |
reportMem() { | |
this._reportMem.apply(this, arguments); | |
} | |
reportNormal() { | |
this._reportNormalLogic.apply(this, arguments); | |
} | |
reportLeak() { | |
this._reportLeak.apply(this, arguments); | |
} | |
} | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
//-* Run | |
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- | |
const log = new Log(); | |
const util = new Utility(log); | |
const leak = new LeakLogic(util, log); | |
const normal = new NormalLogic(util, log); | |
setInterval(() => { | |
if (NORMAL) { | |
normal.useNewSpace(); | |
normal.useLargeObjectSpace(); | |
} | |
if (LEAK) { | |
leak.makeLeak(); | |
} | |
if (REPORT_MEMORY) { | |
util.reportMemory(); | |
} | |
if (REPORT_HEAP) { | |
util.reportHeap(); | |
} | |
if (REPORT_SPACE) { | |
util.reportSpace(); | |
} | |
}, INTERVAL); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment