APM(Application Performance Management),中文名称应用性能管理。百度百科上给了比较系统的介绍,有兴趣的同学可以查看:应用性能管理;知乎上也有对这个方向的简单探讨,链接:APM(应用性能管理)在中国前景如何?。
本文从听云的Nodejs监控源码入手,探索其实现的的一些原理和细节。源码可以在听云官网上找到。
整体结构
从下图可以看到整个源码结构。metrics主要处理性能指标,负责把收集到的指标转成服务器识别的格式;serve负责服务器的连接建立、数据传送等和服务器交互的逻辑;options是处理配置信息,如服务器的地址、用户的key等;util是方法集合,如日志处理等,它的功能比较复杂;parsers是程序的核心,对用户使用到的框架或者API进行封装处理。
![](https://img.haomeiwen.com/i1975863/438d5905be6e923e.png)
启动
index.js中给出来程序的启动流程:
var init = function init() {
...
// 1. 初始化配置信息
var config = require('./options/config.js').init();
var Agent = require('./agent.js');
// 2. 根据配置信息生成agent对象 agent对象会携带性能相关的信息
agent = new Agent(config);
// 3. shimmer提供函数的封装模块
var shimmer = require('./util/shimmer.js');
// 对Node中的加载做一层封装
shimmer.patchModule(agent);
// 对http进行封装
shimmer.bootstrapInstrumentation(agent);
// 收集应用基本信息 如物理内存使用情况、事件队列排队
// 启动定时器 50s间隔发送运行参数
return agent.start();
}
var start_message = init();
运行
启动流程中,shimmer. patchModule
会对Node模块加载的_load方法进行了处理:
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在模块加载时都会调用到Module的_load方法。当require一个模块时,程序会根据模块的名字决定加载执行哪一个封装逻辑;如果没有封装逻辑,那么直接执行原模块:
function _postLoad(agent, nodule, name) {
var base = path.basename(name);
// 原生express的base为express,封装的parsers/wrappers/express.js,其base为express.js。 依据此避免了循环依赖
var wrapper_module = (name === 'pg.js') ? 'pg': base;
// WRAPPERS是由express、redis、mysql等模块名组成的数组
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函数的逻辑就是把封装模块加载执行:
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探针
其对Express的封装逻辑基本包括以下几个步骤:1. 判断应用程序中的Express版本; 2. 依据版本执行封装逻辑。 以Express4.0为例,程序对下面几个方法做了封装处理:
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的逻辑如下,主要是对原有的方法做一层封装:
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为例,它在原来的方法上的基础上了增加了指标收集的逻辑:
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);
};
}
因为和请求有关,上述逻辑执行完会进入到http的封装逻辑中。当一个请求发送后,会进入到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) {
// 如果需要对前端模块嵌入代码 执行...
}
return original.call(this, resultData || data, encoding, callback);
}
};
指标收集
特定时间间隔发送一次性能指标,默认为50s
// 启动定时器
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();
};
// 发送指标
Agent.prototype._on_timer = function _on_timer() {
...
// 发送时 先收集 然后传给服务器
this._send_metrics(on_upload_ret);
};
具体的指标收集是个比较复杂的逻辑,类型分为action、sql等;和性能有关的类包括: trace
、action
、segment
等。具体的分析后续加上。
网友评论