Skip to content

Instantly share code, notes, and snippets.

@agreatfool
Created January 25, 2018 06:39
Show Gist options
  • Save agreatfool/024d11c54c1a8e474ded5269d54f5c99 to your computer and use it in GitHub Desktop.
Save agreatfool/024d11c54c1a8e474ded5269d54f5c99 to your computer and use it in GitHub Desktop.
#!/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