这是我参与「掘金日新计划 · 4 月更文挑战」的第 5 天,点击查看活动详情
说明
原仓库已经五年多没有更新了,可能也根本没有更新的必要,但还是将原仓库地址放在这里 Memeye
因为原仓库比较久远,所以笔者打算重写一套node内存监控的系统,以Memeye原仓库为蓝本,起名ShowMemor,欢迎👏🏻共建🏹。
memeye上手
memeye上手非常简单,我们直接来看:
const http = require('http')
const memeye = require('memeye')
memeye()
let s = '';
for (let i=0;i<1024 * 10;i++){
s += 'a';
}
const str = s;
const bufStr = Buffer.from(s);
const server = http.createServer((req,res) => {
if (req.url === '/buffer'){
res.end(bufStr)
}else if(req.url === '/string'){
res.end(str)
}
})
server.listen(3000);
wrk压测
wrk -t12 -c400 -d30s http://192.168.20.186:3000/string
压测结果
我们可以看可视化的效果还是很不错的,接下来就来分析一下它的原理。
memeye架构
图上分为三部分,左侧就是性能数据的获取部分,右侧是客户端可视化的部分,而中间是进行IPC通信获取实时数据,并通过事件监听的方式触发数据推送,通过websocket发送数据到客户端。
很精简,但却有很多值得我们学习的地方。很多时候并不是我们写不出来什么库或者框架,而是我们不知道底层给我们提供了什么功能,就例如这个内存监控的实现,首先采集数据所用到的node原生提供的库就是我们平时不会用到的。因为业务不会用到,所以🙅♂️去看,所以不知道,所以连node提供给我们了哪些能力都不知道,何谈创新?
node子进程及进程间IPC通信
我们来看一下关于node开启子进程,以及如何进行IPC通信。
父进程 parent.js
const child = require('child_process');
const childProcess = child.fork(__dirname + '/child.js');
// 默认去找路径下的 index.js
// const childProcess = child.fork(__dirname);
// 向子进程发送数据
childProcess.send({'name':'flten'});
// 监听子进程发送过来的数据
childProcess.on("message",function (message) {
console.log('parent received message',message)
})
子进程 child.js
// 监听父进程发送过来的数据
process.on("message",function(message){
console.log('child received message',message)
})
// 子进程向父进程发送数据
process.send({'age':24})
其实比较简单,开启进程使用的是child_process,而进程间通信,涉及两者之间,父进程可以向子进程发送数据,也可以监听子进程发来的数据,子进程亦是如此,直接看代码更为直观。
socketio双向通信
主要是服务器主动向客户端发送
服务端
const express = require('express');
const app = express();
app.use(express.static(__dirname));
app.listen(8000);
const ws = require('ws').Server;
const server = new ws({port:8888});
server.on('connection',function(socket){
console.log('服务端建立连接成功')
socket.on('message',function(message){
console.log('监听到客户端传来的消息',message)
socket.send('服务端向客户端发送的消息')
})
})
客户端
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="clock"></div>
<script type="text/javascript">
const socket = new WebSocket('ws://localhost:8888');
socket.onopen = function(){
console.log('监听到服务打开')
socket.send('客户端向服务端发送消息')
}
socket.onmessage = function(event){
console.log('客户端监听到服务端传来的消息',event.data)
}
</script>
</body>
</html>
我们用express开启了一个后端服务,而在客户端直接使用webstorm为静态文件打开一个静态服务。服务端引入ws,客户端调用原生的WebSocket构造函数,根据所提供的监听事件就可以进行通信了。
是不是很简单?只是我们可能遇不到双向通信的业务,所以不会去关注node有ws,使用那么久javascript,却也不知道有个WebSocket构造函数。
node流与管道
定时器轮询
如何获取实时数据呢?其实最简单的一种方式,就是使用轮询,简单粗暴但却非常有效。
const v8 = require('v8');
const os = require('os');
setInterval(() => {
let v8Stat = {
heap: v8.getHeapStatistics(),
heapSpace: v8.getHeapSpaceStatistics(),
}
let osStat = {
freeMem: os.freemem(),
totalMem: os.totalmem(),
cpus: os.cpus(),
}
console.log(v8Stat,osStat)
}, 1000);
其中的node的v8模块我相信很少被我们到,但这就是核心呀,因为它就是重要的数据来源。而通过node原生提供的os模块我们就可以获取到cpu数、空闲内存、总内存等系统的情况。但这些都只是node原生提供给我们的能力而已,获取到了这些其实已经解决核心。
事件监听
内存数据收集
那么获取的数据是什么样子呢?我们可以来看一下。
const v8 = require('v8');
const os = require('os');
let v8Stat = {
heap: v8.getHeapStatistics(),
heapSpace: v8.getHeapSpaceStatistics(), // V8 堆空间的统计信息
}
let osStat = {
freeMem: os.freemem(),
totalMem: os.totalmem(),
cpus: os.cpus(),
}
console.log('v8Stat',v8Stat);
console.log('osStat',osStat);
结果
v8Stat {
heap: {
total_heap_size: 4468736,
total_heap_size_executable: 524288,
total_physical_size: 4468736,
total_available_size: 4342017560,
used_heap_size: 2885536,
heap_size_limit: 4345298944,
malloced_memory: 8192,
peak_malloced_memory: 123168,
does_zap_garbage: 0,
number_of_native_contexts: 1,//native_context 的值是当前活动的顶层上下文的数量。 随着时间的推移,此数字的增加表示内存泄漏。
number_of_detached_contexts: 0 //detached_context 的值是已分离但尚未回收垃圾的上下文数。 该数字不为零表示潜在的内存泄漏。
},
heapSpace: [
{
space_name: 'read_only_space',
space_size: 151552,
space_used_size: 150392,
space_available_size: 0,
physical_space_size: 151552
},
{
space_name: 'new_space',
space_size: 2097152,
space_used_size: 915672,
space_available_size: 131752,
physical_space_size: 2097152
},
{
space_name: 'old_space',
space_size: 1458176,
space_used_size: 1348792,
space_available_size: 184,
physical_space_size: 1458176
},
{
space_name: 'code_space',
space_size: 360448,
space_used_size: 87456,
space_available_size: 0,
physical_space_size: 360448
},
{
space_name: 'map_space',
space_size: 528384,
space_used_size: 267768,
space_available_size: 0,
physical_space_size: 528384
},
{
space_name: 'large_object_space',
space_size: 135168,
space_used_size: 131112,
space_available_size: 0,
physical_space_size: 135168
},
{
space_name: 'code_large_object_space',
space_size: 0,
space_used_size: 0,
space_available_size: 0,
physical_space_size: 0
},
{
space_name: 'new_large_object_space',
space_size: 0,
space_used_size: 0,
space_available_size: 1047424,
physical_space_size: 0
}
]
}
osStat {
freeMem: 6661525504,
totalMem: 17067167744,
cpus: [
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
},
{
model: 'Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz',
speed: 3192,
times: [Object]
}
]
}
收集子进程内存信息
我们的主进程需要和客户端进行双向通信,所以可以将信息采集的工作交给子进程去做。
const child = require('child_process');
const childProcess = child.fork(__dirname + '/child.js');
// 收集子进程内存信息
let processStat = process.memoryUsage();
console.log(processStat);
结果
{
rss: 26828800,
heapTotal: 4993024,
heapUsed: 3163440,
external: 1122743,
arrayBuffers: 10059
}
colors.js 控制台输入日志样式美化
这个不那么重要,但是很有趣,并且很可能你知道了以后就会去使用。让你的打印信息五彩缤纷,能够识别不同的信息类型。
const colors = require('colors/safe');
colors.setTheme({
info: 'green',
warn: 'yellow',
debug: 'blue',
error: 'red'
});
function Logger(prefix = '') {
this.prefix = prefix;
}
Logger.prototype.error = function (msg) {
console.log(this.prefix + colors['error'](msg))
}
Logger.prototype.warn = function (msg) {
console.log(this.prefix + colors['warn'](msg))
}
Logger.prototype.info = function (msg) {
console.log(this.prefix + colors['info'](msg))
}
Logger.prototype.debug = function (msg) {
console.log(this.prefix + colors['debug'](msg))
}
const log = new Logger('flten:');
log.error('error');
log.warn('warn');
log.info('info');
log.debug('debug');
结果