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已經發布。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容