Easy-Monitor 2.0: 开启你的 Node.js 内核性能监控
发布于 7 年前 作者 hyj1991 31144 次浏览 最后一次编辑是 4 年前 来自 分享

前言:Easy-Monitor 已经更新到 3.0 啦

Easy-Monitor 3.0 介绍参见:https://cnodejs.org/topic/5ee1ee83b703280f0bcb922a


I. 写在最前面

Easy-Monitor 1.x 的版本在今年三月的时候发布了,陆续从最初的仅仅为了做一个短时间 ( 5s ) 的 cpu-profiling 查看函数瓶颈,到后面加入了针对疑似内存泄漏点的排查功能。

整个 1.x 版本主要还是试验一下自己的做内核监控的可行性和想法,所以在使用上和设计架构上没有做太多的考量,导致易用性和兼容性上存在一些问题,因此从 5 月中旬开始到现在,对这个项目做了一个彻底的重构,也是现在这个 2.0 版本的初衷~

II. 功能

  • 服务器状态概览信息展示
  • 实时 CPU 函数性能分析,帮助定位程序的性能瓶颈点
  • 实时 Memory 堆内内存结构分析,帮助定位到内存疑似泄漏点

2.0 的一些新特性

  • 基于 vue.js 和 iview 组件全新设计的 UI
  • 全面兼容 v4.x ~ v8.x
  • 新增概览 Overview 展示页
  • 支持 动态更新配置,无需重启一键生效
  • 支持 Stream 流式解析更大的 HeapSnapshot
  • 支持 Cluster 集群部署,支持定制 私有协议

III. 快速开始

- 安装模块

执行如下命令安装 Easy-Monitor:

npm install easy-monitor

- 项目中引入

在你的项目入口文件中按照如下方式引入,当然请传入你的项目名称:

'use strict';
const easyMonitor = require('easy-monitor');
easyMonitor('你的项目名称');

好了,此时你所需要做的一切都已就绪,接下来以你喜欢的方式运行项目即可,不管是 nohup 还是 pm2,亦或是直接 node 启动均可。

- 访问监控页面

打开你的浏览器,访问 http://localhost:12333 ,即可看到进程界面。

- 完整样例 & Demo

为了帮助大家更好的理解使用,下面编写一个 Easy-Monitor 嵌入 Express 应用的完整例子

'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);

将上述的内容保存成一个 js 文件,启动后访问 http://127.0.0.1:12333 即进入 Easy-Monitor 的首页,就是这样的简单!

这里有一个在线真实的 Demo 地址:Easy-Monitor Demo,可以点击进入自行尝试一番。

更多关于 集群部署、深度定制化开发、通用配置以及动态更新配置的说明可以查看 Easy-Monitor 详细文档

IV. 设计架构

这个项目旨在帮助大家更加深入地理解自己的 Node 项目运行时状态,以便能够在想做性能优化时更有针对性,也考虑到使用的难易程度,所以对于 快速部署集群 C/S 模式部署 两种都做了支持,整体设计架构如下:

快速部署

快速部署模式下,web 页面展示的进程会以 fork 的形式自动启动,采集内核数据的客户端则会绑定到业务进程,两者间采用默认的 tcp 通信模式:

jiagou1.png

如上图所示,这种部署模式下,优点是仅需要 require 即可,无任何额外操作,开箱即用。缺点也比较明显,如果项目是分布在不同的服务器上,则需要每次都输入对应的项目所在服务器地址才能访问。

集群 C/S 部署

这种部署模式其实就是独立部署 dashboard 进程,采集内核数据的客户端则分散到各个业务进程,数据统一上报给独立部署的 dashboard 进程,整体设计架构如下:

jiagou2.png

这种部署模式下,在业务项目本身集群化部署的情况下非常易于统一查看,省去了快速部署模式下每次访问不同地址的烦恼。

V. 图文使用说明

动态更新配置

进入监控 web 页面首页后,点击项目标题,即可打开动态更新配置的 Modal 框 (需要版本 >= v2.1.0),如图:

dynimic1.png

弹出框具体每一项的内容和含义详见 开启动态更新配置

OS 信息概览

进入监控 web 页面首页后,找到对应的项目,所在服务器选择你想查看的服务器,进程一项选择你想查看的进程,解析类型一项选择 OS,最后点击项目标题旁边的 start 按钮,如下图所示:

gailan2.png

即可进入当前选中进程的概览信息页面,此页面可以看到服务器的 CPU 使用率,以及选中的进程的 Memory 占用情况,其中内存占用展示了三类:

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

如下图所示:

gailan3.png

这个 line-charts 展示模块的设计实现参考了 @JerryC8080Memeye 项目,本文末和源码中做了特别标注。

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

使用方法

进入监控 web 页面首页后,找到对应的项目,所在服务器选择你想查看的服务器,进程一项选择你想查看的进程,解析类型一项选择 CPU,最后点击项目标题旁边的 start 按钮,如下图所示:

cpu1.png

如果你没有更改过启动参数,那么会进行 5s(默认值) 的 CPU-Profiling,然后进行分析,展示结果包含:

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

如图所示:

cpu2.png

这里可以看到,我们将鼠标移至 系统路径 一栏后,会在气泡框内展示出对应函数的完整的在服务器上的路径。下面解释下图表中的元素含义:

函数运算耗费时长相关

  • 函数名称: 即为编写的 js 函数名称
  • 执行时长: 该函数的运算时长,注意如果函数内包含异步操作,那么异步操作花费的时间是不统计在内的。这个时长就是指纯函数本身占据CPU运算的时间,所以可以定位到 CPU 运算瓶颈点。
  • 占据调用者百分比: 这里指的是本函数执行的时长占据调用者函数的总执行时长的百分比
  • 系统路径: 函数在服务器的具体文件路径

引擎逆优化相关

  • 函数名称: 即为编写的 js 函数名称
  • 逆优化原因: 此函数被 V8 引擎逆优化的原因
  • 系统路径: 函数在服务器的具体文件路径

动态更改 CPU 配置,获得更多数据

上面使用方法一节中看到很多数值被标注为 默认值,这表示这些值我们可以通过动态修改 CPU-Profiling 的配置来进行调整,这是一个动态调整的例子:

想对部署的项目进行压测 10 分钟,期间采集 CPU 数据,过滤出执行耗费大于 100ms 的函数,并且分析的结果每一项展示 50 条。

那么可以按照上面提到的方法进入动态更新配置的 Modal 框,修改参数如下图所示:

cpu3.png

修改完毕后,点击 Modal 弹出框右下角的 确定 按钮,即可通知服务器更改配置,保存成功后再按照上面的使用方法进行 CPU 数据采集和分析,等待约 10 分钟就可以看到结果。

关于CPU 动态配置中的自定义过滤

在上面的截图中可以看到,CPU 动态配置项中还有个按钮是 自定义过滤,这个是用来对 CPU 采集数据分析结过做过滤用的。

项目默认会过滤掉路径中包含 node_modulesanonymous 等系统函数,这样可以保证结果中只包含开发者编写的函数,那么关闭自定义过滤的话会将系统函数也展示在内。

自定义过滤函数大家也可以根据项目需求而自行注入,具体可以看开发文档中的介绍: 自定义过滤

Memory 数据采集分析定位疑似内存泄漏点

使用方法

进入监控 web 页面首页后,找到对应的项目,所在服务器选择你想查看的服务器,进程一项选择你想查看的进程,解析类型一项选择 MEM,最后点击项目标题旁边的 start 按钮,如下图所示:

mem1.png

页面会对堆内内存分析中间过程进行一些展示,提高体验,等分析完毕后,即会展示出对应的结果,如下图所示:

mem2.png

这里根据分析当前进程堆内分配内存的结构,计算得出 RetainedSize 最大的节点占据总的堆内分配内存占比来判断是否有内存泄漏风险,以及对应的疑似泄漏节点引力图。

这一部分引力图详细定位内存泄漏点的逻辑和 Easy-Monitor 1.x 没有变化,可以参见上一篇文章来获取详情: 轻松排查线上Node内存泄漏问题

动态更改 Memory 配置,获得更多数据

Memory 分析同样也提供了一些可以动态更改的配置,试看如下场景:

Node 项目发生内存泄漏时,想获取更多的疑似内存泄漏点,以及需要更多的子节点信息和引力图深度来排查定位内存泄漏点,那么我们可以通过修改提供的动态配置来达到不重启服务器即可生效的目的,如下图所示:

mem3.png

这里将默认的展示 5 个疑似内存泄漏点更改为 10 个,将引力图的每一个节点的展开子节点数从默认的 5 个更改为 8 个,将引力图深度从默认的 25 更改为 50,这样就能获取到更多的内存信息。

关于 Memory 动态配置中的 Root 节点配置

Root 节点信息的展示是帮助大家更深入地理解当前的堆内内存结构,对于排查内存泄漏并没有太大帮助,但是这一项打开后会增加浏览器下载的 HeapSnapshot 分析结果数据大小,可以将其关闭掉。

VI. 特别感谢

重构过程中思路参考了一些开源项目,在最后特别感谢下 @JerryC8080

  • Memeye: 作者为 JerryC8080,2.0 版本的 overview 页面实现和 logger 模块实现参考了 Memeye 项目

源代码中对应位置也做了标注,感谢作者们的开源精神。

VII. 结语

Easy-Monitor 项目我个人会长期维护下去,下一阶段的计划大致如下:

    1. 研究下 Node 8 提供的 N-API,以将计算量巨大的 CPU 和 HeapsnapSnapshot 运算解析部分移植成扩展,放入单独的计算线程中来进一步提升性能。
    1. 研究下 Chrome 最新提供的 Record Allocation Profiler 和 Record Allocation Timeline 的实现,看看能不能合并入 Easy-Monitor 的 Memory 分析部分实现线上 Node 项目的实时分析。

最后本项目地址在 Easy-Monitor ,如果使用中遇到问题可以提 issue ,保证快速响应。

如果本项目对您有用欢迎 star,如果想一起开发或者有更多想法也欢迎联系我~

46 回复

@i5ting 嘿嘿…狼叔带我飞哇

来自酷炫的 CNodeMD

还是你比较屌,深感惭愧。

@JerryC8080 不不不,最初架构的灵感确实是你的项目给我的,当时你文章说不支持 cluster 我就在想怎么弄,后来索性就用 tcp 替换 ipc,嘿嘿

来自酷炫的 CNodeMD

摩拜。。。

@i5ting 看到推送的时候好开心哇,谢谢狼叔~

好看 支持!!!

请问楼主是 egg 的开发者么

@jkjk77 我是打酱油的,嘿嘿,感谢关注哇

做的东西越来越酷炫了

@zy445566 老酒装在了新瓶子(vue)里,嘿嘿

我用chrome打开你的DEMO, 点击Star之后,页面不停抖动,我的显示器分辨率2048*1152,缩小浏览器窗口就正常了。

@merrynode 没有这个分辨率的显示器,有空找下看看为啥

必须关注一下

@hyj1991 今天上午我还在构思这么个玩意,想自己做动手的。 其实既然你已经支持集群式, 我个人认为可以增加更多功能去提供那些服务器管理的 例如:cronjob bash的支持,以及整个集群服务器的各种状态。 当然如果添加我说的功能可能就超出了Ez-Monitor的本身命名意义。

@AberGLJ 感谢关注 @danielsss 确实,我目前专注把目前提供的功能能做的更好,而且个人的精力还是有限,只能看看有没有人扩展了~

@hyj1991 如果有需要我可以帮忙扩展,我对这个项目也比较感兴趣. Q:404637026 我们可以聊聊

@danielsss 加了~

来自酷炫的 CNodeMD

@hyj1991 大家反馈,比较吃内存,要不要进微信群讨论一下

@hyj1991 推荐别人用的时候,他们说有代码侵入。他们更愿意,像pm2,指定路径,通过监听子进程的方式来使用。是否能推出这种模式?

来自酷炫的 CNodeMD

@zy445566 代码侵入是指?这里代码没有做侵入哇,这个工具完全依赖于 v8 引擎暴露的接口,没有对 node 的代码做任何形式的侵入

来自酷炫的 CNodeMD

@hyj1991 不是要在项目启动文件里面加这个么?

const easyMonitor = require('easy-monitor');
easyMonitor(app_name);

毕竟很多人不希望项目中存有其他代码片段,更希望想pm2一样,直接启动监控。 发现alinode和easy-monitor高度相似,但alinode也没有进行代码侵入。

@zy445566 alinode,大致是实现了修改 node 内核代码,重新编译了 node 源文件,而pm2 的 监控可以直接启动那是因为这东西就是 pm2 的开发组做的,所以可以嵌入 pm2 的启动代码里,这样子当然不需要额外的操作了。。。 easy-monitor 虽然启动方式有点像 oneAPM 这种,但是确实就是一个单独的包,没有对 http 等模块做运行时侵入,既然是一个独立的 npm 包,肯定需要你在项目入口文件启动一下,但是你花费的所有额外操作就仅仅这些了,不会要你改任何的业务代码。 可以把 Easy-Monitor 当做 lodash 等模块,应该会好理解一些吧

@hyj1991 666,不愧是一君大佬 可能更多人喜欢pm2的实现方式吧。

@zy445566 请问你是哪位哇。。。感觉一脸懵逼

来自酷炫的 CNodeMD

谢谢分享!!!

请问浅引用大小和保留引用大小分别指的是什么

这才是应该学习的node、项目

@wuyafeiJS 谢谢你的肯定,但是现阶段我在见识到大量的内存泄漏 case 后,感觉线上情况有变化太多,相对来说稳定性和大堆的分析(例如超过 300M 的 snapshot,由于 v8 引擎对 string 的长度限制导致没法读取)目前纯 js + addon 的形式是没有办法分析和做到稳定性的保障的

当时做这个工具的最初衷是因为 alinode 收费,前东家又不给钱接 alinode 哈哈哈,然而现在已经免费了,我建议你有线上应用部署还是接 alinode 吧,easy-monitor 这个项目我也会继续维护,给有兴趣的同学在内核分析这一块做开源的交流学习使用嘿嘿

这个和进程管理的有什么区别吗

@hyj1991 阿里云也不是免费的啊,当然指的是面向企业版的监控。你这个项目我老早就在关注,没想到升级到2.0后就变大了许多,不过可以稍作定制化给不同的公司使用。

@royalrover alinode 和阿里云不绑定哦,你可以部署到自己的机器上

来自酷炫的 CNodeMD

@hyj1991 请教下,关于内存泄漏的疑似算法,有参考模型吗?这一块的数据可视化为什么选用力导引呢

还有我之前一直挺担心这个采集的性能问题,如果heapdump达到了500mb左右,就算采用stream进行跨机器的传输,这块带宽以及性能方面的影响也不小,不过这也是个trade off。我更想知道项目接入easy-monitor前后他们的吞吐量如何?

@hyj1991 我们docker部署的 我们不想将dashboard端口暴露出来 在不对外暴露端口如何访问dashboard

@yanghanyu198 可以看文档的这个部分 虚拟路径转发

Anyway,可以尝试下更强大的 Node.js 性能平台

回到顶部