easy-monitor源码分析

前言

笔者之前有使用轻量级的easy-monitor2.0对项目进行内存泄漏的排查;

本文主要是对2.0版本的源码学习和笔记整理。目的是为了个人的技术提升,想去了解一个nodejs监控的整体实现。如果哪里有理解不对的地方欢迎读者指出。

本文底部会有原作者在cnode原文章的链接;

整体架构

这里引用一下原文中的架构图


image.png

整体上分为了三个模块:

  • 业务进程中运行的embrace模块
  • dashboard看板服务模块
  • web页面

下面从源码层面对各模块进行分析;

初始化

这个库对外暴露的是一个方法。

'use strict';
const easyMonitor = require('easy-monitor');
easyMonitor('Mercury');
const express = require('express');
const app = express();

app.get('/hello', function (req, res, next) {
    res.send('hello');
});

app.listen(8082);

入口文件index.js引入根目录的dispatch.js

dispatch.png

这个文件主要做了以下工作:

  • src_logic/common目录的js文件进行初始化,导出一个对象;收敛了common目录下的所有模块方法。并且遍历该对象属性值,如果包含initP方法就进行调用;
//获取基础配置, pre 表示预先加载的文件,params 表示对应的参数
const common = _common({ pre: ['config', 'logger', 'utils', 'cache'], param: { config: options } });
yield common.utils.commonInitP(common);
  • src_logic/common目录下的每一个模块暴露的都是一个初始化的方法,接受的参数是一致的,其实对应的就是common目录被前置加载的模块;
function (common, config, logger, utils, cache) { ... }
  • 在初始化上述config模块时,使用了类似的初始化过程对src_logic/config目录下的所有配置文件进行了初始化;并导出了一个对象,收敛了所有配置选项;
    详细过程阅读src_logic/common/common.config.js模块

  • 初始话完毕后便是运行embrace模块的start方法
    通过fork子进程形式运行dashboard模块
    (没有分析集群部署模式)

//非 cluster 模式下,embrace 嵌入业务进程,dashboard 以 fork 形式启动
embrace.start(config, common);
common.utils.forkNode(path.join(rootPath, 'dashboard/_fork.js'), [JSON.stringify(options)]);

关于RPC

这里插入一下RPC的概念,方便理解下面的embracedashboard之间的通信

这里引用一段网络上的解释:原文
RPC (Remote Procedure Call:远程过程调用):一种进程间通信方式。允许像调用本地服务一样调用远程服务

RPC架构:
包含四个核心组件

  • 客户端(client):服务的调用方
  • 服务端(server):服务提供方
  • 客户端存根(client stub):将客户端请求参数打包成网络消息,再发给服务方
  • 服务端存根(server stub):接收客户端发来的消息,将消息解包,并调用本地方法
image.png

其实源码中embrace模块对应的就是Clientdashboard模块对应的就是Server,基于TCP链接实现的通信;

embrace模块

embrace模块暴露出的start方法将上文中初始化后完整的config对象和common对象作为了参数传入;

embrace.png

这里主要进行的工作是:

  • 加载embrace/dispatch模块
  • 加载所有embrace/controller目录下的逻辑处理方法 ,集成到一个controller对象上
//获取 embrace 的 dispatch 信息
const dispatch = _require('embrace/dispatch');
const controller = dispatch.controller(config, common, dbl);
  • 启动tcp客户端服务;(注:与dashboard模块通信的客户端)
    并设置this指向 { controller }
//启动 tcp 客户端
const tcpClient = dispatch.tcp;
tcpClient.apply({ controller }, [config, common, dbl]);

embrace/dispatch模块

上文中引入的embrace/dispatch模块对外暴露了上文中用到的两个方法

  • createTcpClient:启动tcp客户端服务
    这里使用了net.Socket类创建了socket实例,并且调用了实例方法connect,以tcp模式进行链接。
//和服务器建立链接
const client = new net.Socket();
client.connect(config.embrace.tcp_port, config.embrace.tcp_host, _callbackListener);
//处理 tcp 数据
client.on('data', socketUtils.onData.bind(ctx, client));

这里的socketUtils.onDatacommon/common.scoket.js模块中暴露出的方法,目的是为了统一处理tcp句柄中的 data 事件;
dashboard模块中创建的tcp服务端的回调函数中会再次用到该方法;目的是对相同格式的消息数据格式统一处理;

  • createController:集成controller方法
    controller有4种类型: auth、fetch、overview、profiler,对应四种逻辑处理;

dashboard模块

dashboard模块初始化和启动的逻辑与embrace模块异曲同工,稍有不同的是区分为了两部分:

  • HTTP服务:处理web端用户的操作,对TCP服务下发指令
  • TCP服务端: 处理embrace客户端发来的消息内容,通知embrace客户端开始对应的操作

overview

这里以overview为例子走一遍完整的流程

首页

image.png

overview页面:此页面可以看到服务器的 CPU 使用率,以及被选中进程的 Memory 占用情况,其中内存占用展示了三类:

  • heapUsed: 正在使用的堆内内存大小
  • heapTotal: 申请的总堆内内存大小
  • rss: 堆外分配的内存大小
image.png

整个过程笔者使用了一个简易的流程图来示意;(如果不容易理解,请还参考原作者的架构图)


image.png
  • web发送 fetchOverview请求
  • dashboard http服务 接收到请求后,检查缓存是否有内容;并且会通知 TCP server 发送消息给 TCP client;最后将消息响应发送回web;
  • TCP client 解析消息调用对应controller获取当前进程cpu占用率以及内存使用情况并写入缓存,供下次dashboard http服务去读取信息

上述过程在web端是启动了setInterval定时器每一秒中请求一次,而且在客户端每一次都将上一次的数据做了缓存,绘制出了实时的折线图效果

具体获取cpumem数据是通过下面的方法:

//获取本进程的 cpu 使用率和 memory 占用信息
const memoryUsage = common.overview.computeMemoryUsage();
const cpuUsage = common.overview.computeCpuUsage();

computeMemoryUsage的计算方法比较直接:process_memoryusage

mem.png

computeMemoryUsage使用到的Nodejs API:os_cpus并进行了简单计算

cpu.png

CPU 数据采集分析函数运算瓶颈

image.png

这个功能是对CPU-Profiling,然后进行分析,展示结果包含:

  • 执行耗费时间大于 500ms(默认值) 的函数列表
  • 执行耗费时间最长的 5个(默认值) 函数
  • V8 引擎逆优化最频繁的 5个(默认值) 函数

感谢大佬的开源精神
easy-monitor作者原文链接
easy-monitor3.0已经发布。

你可能感兴趣的:(easy-monitor源码分析)