APM(Application Performance Management),中文名稱應(yīng)用性能管理。百度百科上給了比較系統(tǒng)的介紹,有興趣的同學(xué)可以查看:應(yīng)用性能管理;知乎上也有對(duì)這個(gè)方向的簡單探討,鏈接:APM(應(yīng)用性能管理)在中國前景如何?。
本文從聽云的Nodejs監(jiān)控源碼入手,探索其實(shí)現(xiàn)的的一些原理和細(xì)節(jié)。源碼可以在聽云官網(wǎng)上找到。
整體結(jié)構(gòu)
從下圖可以看到整個(gè)源碼結(jié)構(gòu)。metrics主要處理性能指標(biāo),負(fù)責(zé)把收集到的指標(biāo)轉(zhuǎn)成服務(wù)器識(shí)別的格式;serve負(fù)責(zé)服務(wù)器的連接建立、數(shù)據(jù)傳送等和服務(wù)器交互的邏輯;options是處理配置信息,如服務(wù)器的地址、用戶的key等;util是方法集合,如日志處理等,它的功能比較復(fù)雜;parsers是程序的核心,對(duì)用戶使用到的框架或者API進(jìn)行封裝處理。

啟動(dòng)
index.js中給出來程序的啟動(dòng)流程:
var init = function init() {
...
// 1. 初始化配置信息
var config = require('./options/config.js').init();
var Agent = require('./agent.js');
// 2. 根據(jù)配置信息生成agent對(duì)象 agent對(duì)象會(huì)攜帶性能相關(guān)的信息
agent = new Agent(config);
// 3. shimmer提供函數(shù)的封裝模塊
var shimmer = require('./util/shimmer.js');
// 對(duì)Node中的加載做一層封裝
shimmer.patchModule(agent);
// 對(duì)http進(jìn)行封裝
shimmer.bootstrapInstrumentation(agent);
// 收集應(yīng)用基本信息 如物理內(nèi)存使用情況、事件隊(duì)列排隊(duì)
// 啟動(dòng)定時(shí)器 50s間隔發(fā)送運(yùn)行參數(shù)
return agent.start();
}
var start_message = init();
運(yùn)行
啟動(dòng)流程中,shimmer. patchModule會(huì)對(duì)Node模塊加載的_load方法進(jìn)行了處理:
patchModule : function patchModule(agent) {
logger.debug("Wrapping module loader.");
var Module = require('module');
shimmer.wrapMethod(Module, 'Module', '_load', function cb_wrapMethod(load) {
return function cls_wrapMethod(file) {
return _postLoad(agent, load.apply(this, arguments), file);
};
});
}
Node在模塊加載時(shí)都會(huì)調(diào)用到Module的_load方法。當(dāng)require一個(gè)模塊時(shí),程序會(huì)根據(jù)模塊的名字決定加載執(zhí)行哪一個(gè)封裝邏輯;如果沒有封裝邏輯,那么直接執(zhí)行原模塊:
function _postLoad(agent, nodule, name) {
var base = path.basename(name);
// 原生express的base為express,封裝的parsers/wrappers/express.js,其base為express.js。 依據(jù)此避免了循環(huán)依賴
var wrapper_module = (name === 'pg.js') ? 'pg': base;
// WRAPPERS是由express、redis、mysql等模塊名組成的數(shù)組
if (WRAPPERS.indexOf(wrapper_module) !== -1) {
logger.debug('wrap %s.', base);
var filename = path.join(__dirname, '../parsers/wrappers', wrapper_module + '.js');
instrument(agent, base, filename, nodule);
}
if (FUN_WRAPPERS.indexOf(wrapper_module) !== -1) {
logger.debug('wrap %s.', base);
var filename = path.join(__dirname, '../parsers/wrappers', wrapper_module + '.js');
return retInstrument(agent, base, filename, nodule);
}
return nodule;
}
instrument函數(shù)的邏輯就是把封裝模塊加載執(zhí)行:
function instrument(agent, shortName, fileName, nodule, param) {
try {
require(fileName)(agent, nodule, param);
}
catch (error) {
logger.verbose(error, "wrap module %s failed.", path.basename(shortName, ".js"));
}
}
Express探針
其對(duì)Express的封裝邏輯基本包括以下幾個(gè)步驟:1. 判斷應(yīng)用程序中的Express版本; 2. 依據(jù)版本執(zhí)行封裝邏輯。 以Express4.0為例,程序?qū)ο旅鎺讉€(gè)方法做了封裝處理:
shimmer.wrapMethodOnce(express.application, 'express.application', 'init', app_init);
shimmer.wrapMethodOnce(express.response, 'express.response', 'render', wrapRender.bind(null, 4));
shimmer.wrapMethodOnce(express.Router, 'express.Router', 'process_params', wrapProcessParams.bind(null, 4));
shimmer.wrapMethodOnce(express.Router, 'express.Router', 'use', wrapMiddlewareStack.bind(null, 'use'));
shimmer.wrapMethodOnce(express.Router, 'express.Router', 'route', wrapMiddlewareStack.bind(null, 'route'));
wrapMethodOnce的邏輯如下,主要是對(duì)原有的方法做一層封裝:
wrapMethodOnce : function wrapMethodOnce(nodule, noduleName, method, wrapper ) {
if (!noduleName) noduleName = '[unknown]';
var method_name = noduleName + '.' + method;
var original = nodule[method];
if (!original) {
return logger.debug("%s not defined, skip wrapping.", method_name);
}
if ( original.__TY_unwrap ) return;
var wrapped = wrapper(original);
wrapped.__TY_original = original;
wrapped.__TY_unwrap = function __TY_unwrap() {
nodule[method] = original;
logger.debug("Removed instrumentation from %s.", method_name);
};
nodule[method] = wrapped;
if (shimmer.debug) instrumented.push(wrapped);
}
以wrapRender為例,它在原來的方法上的基礎(chǔ)上了增加了指標(biāo)收集的邏輯:
function wrapRender(version, render) {
return function wp_Render(view, options, cb, parent, sub) {
if ( ! agent.config.enabled ) return render.apply(this, arguments);
if (!tracer.getAction()) return render.apply(this, arguments);
var classname = (version < 3)?'http.ServerResponse':'express.response';
// var name = "Express/" + view.replace(/\//g, "%2F") + '/' + classname + '.render';
var name = "Express/" + classname + '/render';
var segment_info = {
metric_name : name,
call_url:"",
call_count:1,
class_name: classname,
method_name: "render",
params : {}
}
var segment = tracer.addSegment(segment_info, record);
if ( typeof options === 'function' ) {
cb = options;
options = null;
}
var self = this;
var wrapped = tracer.callbackProxy(function render_cb(err, rendered){
segment.end();
if ( typeof cb === 'function' ) return cb.apply(this, arguments);
if (err) {
logger.debug(err, "Express%d %s Render failed @action %s:", version, name, segment.trace.action.id);
return self.req.next(err);
}
var returned = self.send(rendered);
logger.debug("Express%d %s Rendered @action %s.", version, name, segment.trace.action.id);
return returned;
});
return render.call(this, view, options, wrapped, parent, sub);
};
}
因?yàn)楹驼?qǐng)求有關(guān),上述邏輯執(zhí)行完會(huì)進(jìn)入到http的封裝邏輯中。當(dāng)一個(gè)請(qǐng)求發(fā)送后,會(huì)進(jìn)入到http.ServerResponse.prototype的end方法中:
shimmer.wrapMethod(response, 'http.ServerResponse.prototype', 'end', function wrspe(end) {
return wrapEnd(agent, end, action);
});
wrapWrite = wrapEnd = function(agent, original, action) {
return function(data, encoding, callback) {
...
// 性能跟蹤
if (!action.head_writed) {
setTraceData(action, this)
}
if (this.statusCode != 200) {
logger.debug('statusCode is %s, skip injecting code.', this.statusCode);
return original.call(this, resultData || data, encoding, callback);
}
if (data && _tingyun.needInject(agent, this._headers || this._header) && !_tingyun.injected) {
// 如果需要對(duì)前端模塊嵌入代碼 執(zhí)行...
}
return original.call(this, resultData || data, encoding, callback);
}
};
指標(biāo)收集
特定時(shí)間間隔發(fā)送一次性能指標(biāo),默認(rèn)為50s
// 啟動(dòng)定時(shí)器
Agent.prototype._startTimer = function _startTimer(interval) {
var agent = this;
this.hTimer = setInterval(function () { agent._on_timer(); }, interval * 1000);
if (this.hTimer.unref) this.hTimer.unref();
};
// 發(fā)送指標(biāo)
Agent.prototype._on_timer = function _on_timer() {
...
// 發(fā)送時(shí) 先收集 然后傳給服務(wù)器
this._send_metrics(on_upload_ret);
};
具體的指標(biāo)收集是個(gè)比較復(fù)雜的邏輯,類型分為action、sql等;和性能有關(guān)的類包括: trace、action、segment等。具體的分析后續(xù)加上。