ca-apm-probe
Advanced tools
@@ -0,0 +0,0 @@ |
@@ -24,2 +24,3 @@ | ||
var fs = require('fs'); | ||
var util = require('util'); | ||
var logger = require("./logger.js"); | ||
@@ -32,2 +33,3 @@ var config = require('../config.json'); | ||
var pid = process.pid; | ||
var instanceId = util.format('%s-%s', os.hostname(), pid); | ||
var maxPingDelay = config.maxPingDelay || 15000; | ||
@@ -70,3 +72,3 @@ var lastPingResponseTime = new Date().getTime(); | ||
pid: pid, | ||
instid: pid | ||
instid: instanceId | ||
}; | ||
@@ -81,3 +83,3 @@ return object; | ||
ver: 1, | ||
instid: pid, | ||
instid: instanceId, | ||
pgm: currentProbeName | ||
@@ -84,0 +86,0 @@ }; |
@@ -0,0 +0,0 @@ |
@@ -42,3 +42,4 @@ /** | ||
var path = obj.path; | ||
var method = (obj.stack[0].method).toUpperCase(); | ||
var req = args[0]; | ||
var res = args[1]; | ||
@@ -51,3 +52,3 @@ var eventNameFormatted = 'route.dispatch'; | ||
route: path, | ||
http_method: method | ||
http_method: req.method | ||
}); | ||
@@ -57,4 +58,2 @@ storage.set('ctx', ctx); | ||
var req = args[0]; | ||
var res = args[1]; | ||
var errorObject = new Object(); | ||
@@ -61,0 +60,0 @@ |
@@ -51,4 +51,3 @@ | ||
// CA code end | ||
proxy.callback(args, -1, | ||
function(obj, args, extra, graph, currentNode, storage) { | ||
proxy.callback(args, -1, function(obj, args, storage) { | ||
//var errorObject = agent.checkAndSetErrorObject(args, 'FSError'); | ||
@@ -63,3 +62,3 @@ | ||
}, | ||
function(obj, args, extra, graph, currentNode) { | ||
function(obj, args) { | ||
if (ctx != null) agent.asynchEventFinish(ctx); | ||
@@ -66,0 +65,0 @@ }); // CA code end |
@@ -40,294 +40,274 @@ | ||
module.exports = function(http) { | ||
targetModule.http = http; | ||
targetModule.methodMap = getMethodsWithProbes(); | ||
var methodMap = targetModule.methodMap; | ||
// server probe | ||
targetModule.http = http; | ||
targetModule.methodMap = getMethodsWithProbes(); | ||
var methodMap = targetModule.methodMap; | ||
// server probe | ||
proxy.after(http, 'createServer', function(module, args, server) { | ||
server['__CA_id'] = serverNum++; | ||
logger.debug('HTTP server %d created', server['__CA_id']); | ||
var stackTrace = (new Error('Location')).stack.substring(6); | ||
logger.debug(stackTrace); | ||
}); | ||
proxy.after(http, 'createServer', function(module, args, server) { | ||
server['__CA_id'] = serverNum++; | ||
logger.debug('HTTP server %d created', server['__CA_id']); | ||
var stackTrace = (new Error('Location')).stack.substring(6); | ||
logger.debug(stackTrace); | ||
}); | ||
proxy.before(http.Server.prototype, ['on', 'addListener'], | ||
function caHTTPBeforeHook(server, args) { | ||
proxy.before(http.Server.prototype, ['on', 'addListener'], | ||
function caHTTPBeforeHook(server, args) { | ||
if (logger.isDebug) { | ||
logger.debug('HTTP server ' + server['__CA_id'] + ': ' + args[0] + ' listener added.'); | ||
logger.debug((new Error('Location')).stack.substring(6)); | ||
} | ||
if (logger.isDebug()) { | ||
logger.debug('HTTP server ' + server['__CA_id'] + ': ' + args[0] + ' listener added.'); | ||
logger.debug((new Error('Location')).stack.substring(6)); | ||
} | ||
if (methodMap[0] == "skip_instrument") { | ||
return; | ||
} | ||
if (methodMap[0] == "skip_instrument") { | ||
return; | ||
} | ||
// store ref to server so we can pull current connections | ||
agent.httpServer = server; | ||
// store ref to server so we can pull current connections | ||
agent.httpServer = server; | ||
// agent provides data such as protocol, default port | ||
// it differs across http vs https | ||
var httpAgent = http.globalAgent; | ||
// agent provides data such as protocol, default port | ||
// it differs across http vs https | ||
var httpAgent = http.globalAgent; | ||
var kContextPropertyName = '__CA_APM_PROBE_HTTP_CONTEXT__'; | ||
var context = server[kContextPropertyName]; | ||
if (context == null) { | ||
// Index [0] is current connection counter, [1] is the previous one. | ||
context = {connectionCounts: [0, 0]}; | ||
Object.defineProperty(server, kContextPropertyName, {value: context}); | ||
} | ||
var kContextPropertyName = '__CA_APM_PROBE_HTTP_CONTEXT__'; | ||
var context = server[kContextPropertyName]; | ||
if (context == null) { | ||
// Index [0] is current connection counter, [1] is the previous one. | ||
context = {connectionCounts: [0, 0]}; | ||
Object.defineProperty(server, kContextPropertyName, {value: context}); | ||
} | ||
function getEventNameFormatted(req) { | ||
var name = 'http.' + req.method; | ||
return name; | ||
function getEventNameFormatted(req) { | ||
var name = 'http.' + req.method; | ||
return name; | ||
} | ||
function getEventArgs(req) { | ||
var eargs = { | ||
url: req.url | ||
}; | ||
var corHeaderValue = req.headers[CORRELATION_HEADER_KEY_LOWERCASE]; | ||
if (corHeaderValue) { | ||
if (logger.isDebug()) { | ||
logger.debug('Noticed correlation header in incoming http request, corid: %s' | ||
, corHeaderValue); | ||
} | ||
eargs.corId = corHeaderValue; | ||
} | ||
function getEventArgs(req) { | ||
var eargs = { | ||
url: req.url | ||
}; | ||
var corHeaderValue = req.headers[CORRELATION_HEADER_KEY_LOWERCASE]; | ||
if (corHeaderValue) { | ||
if (logger.isDebug()) { | ||
logger.debug('Noticed correlation header in incoming http request, corid: %s' | ||
, corHeaderValue); | ||
} | ||
eargs.corId = corHeaderValue; | ||
} | ||
var hostHeader = req.headers['host']; | ||
var hostHeader = req.headers['host']; | ||
if(hostHeader) { | ||
var hostPort = hostHeader.split(':'); | ||
eargs.hostName = hostPort[0] || DEFAULT_HOST; | ||
eargs.hostPort = hostPort[1] || (httpAgent.defaultPort && httpAgent.defaultPort.toString()) || DEFAULT_PORT; | ||
} else { | ||
eargs.hostName = DEFAULT_HOST; | ||
eargs.hostPort = (httpAgent.defaultPort && httpAgent.defaultPort.toString()) || DEFAULT_PORT; | ||
} | ||
if(hostHeader) { | ||
var hostPort = hostHeader.split(':'); | ||
eargs.hostName = hostPort[0] || DEFAULT_HOST; | ||
eargs.hostPort = hostPort[1] || (httpAgent.defaultPort && httpAgent.defaultPort.toString()) || DEFAULT_PORT; | ||
} else { | ||
eargs.hostName = DEFAULT_HOST; | ||
eargs.hostPort = (httpAgent.defaultPort && httpAgent.defaultPort.toString()) || DEFAULT_PORT; | ||
} | ||
return eargs; | ||
} | ||
return eargs; | ||
} | ||
function logHttpUrl(req, eargs) { | ||
if (logger.isDebug()) { | ||
var protocol = httpAgent.protocol || DEFAULT_PROTOCOL; | ||
var url = protocol + '//' + eargs.hostName + ':' + eargs.hostPort + req.url; | ||
logger.debug('http requested url: %s', url); | ||
} | ||
} | ||
function logHttpUrl(req, eargs) { | ||
if (logger.isDebug()) { | ||
var protocol = httpAgent.protocol || DEFAULT_PROTOCOL; | ||
var url = protocol + '//' + eargs.hostName + ':' + eargs.hostPort + req.url; | ||
logger.debug('http requested url: %s', url); | ||
} | ||
} | ||
function processRequest(req, storage) { | ||
var eventNameFormatted = getEventNameFormatted(req); | ||
function processRequest(req, storage) { | ||
var eventNameFormatted = getEventNameFormatted(req); | ||
var eventArgs = getEventArgs(req); | ||
var ctx = agent.asynchEventStart(null, eventNameFormatted, eventArgs); | ||
storage.set('ctx', ctx); | ||
logHttpUrl(req, eventArgs); | ||
ctx.eventNameFormatted = eventNameFormatted; | ||
return ctx; | ||
} | ||
var eventArgs = getEventArgs(req); | ||
var ctx = agent.asynchEventStart(null, eventNameFormatted, eventArgs); | ||
storage.set('ctx', ctx); | ||
logHttpUrl(req, eventArgs); | ||
ctx.eventNameFormatted = eventNameFormatted; | ||
return ctx; | ||
} | ||
if (args[0] !== 'request' && args[0] !== 'upgrade') return; | ||
if (args[0] === 'connection') { | ||
proxy.callback(args, -1, function caHTTPConnectionHook(obj, args, _, _, _, storage) { | ||
logger.debug('TCP connection.'); | ||
}, function (obj, args, _, _, _, storage) { | ||
var socket = args[0]; | ||
if (socket.parser.onIncoming) { | ||
if (socket.parser.onIncoming.__CA_patched) { | ||
return; | ||
} | ||
logger.debug('HTTP parser attached to connection has been instrumented.'); | ||
var onOnIncoming_save = socket.parser.onIncoming; | ||
socket.parser.onIncoming = function() { | ||
logger.debug('HTTP request incoming.'); | ||
if (agent.paused) return; | ||
var request = arguments[0]; | ||
request.__CA_ctx = processRequest(request, storage); | ||
onOnIncoming_save.apply(socket.parser, arguments); | ||
}; | ||
socket.parser.onIncoming.__CA_patched = true; | ||
} | ||
}); | ||
return; | ||
} | ||
if (args[0] !== 'request' && args[0] !== 'upgrade') return; | ||
proxy.callback(args, -1, function caHTTPCallbackHook(obj, args, storage) { | ||
context.connectionCounts[0] += 1; | ||
proxy.callback(args, -1, function caHTTPCallbackHook(obj, args, _, _, _, storage) { | ||
context.connectionCounts[0] += 1; | ||
if (agent.paused) return; | ||
if (agent.paused) return; | ||
var request = args[0]; | ||
var res = args[1]; | ||
var ctx = null; | ||
var eventNameFormatted = ''; | ||
var request = args[0]; | ||
var res = args[1]; | ||
var ctx = null; | ||
var eventNameFormatted = ''; | ||
if (request.__CA_ctx) { | ||
ctx = request.__CA_ctx; | ||
eventNameFormatted = ctx.eventNameFormatted; | ||
} | ||
else { | ||
ctx = processRequest(request, storage); | ||
eventNameFormatted = ctx.eventNameFormatted; | ||
} | ||
// handle multiple request listeners scenario | ||
// process request only once to send start event | ||
if (request.__CA_ctx) { | ||
ctx = request.__CA_ctx; | ||
storage.set('ctx', ctx); | ||
} else { | ||
ctx = processRequest(request, storage); | ||
request.__CA_ctx = ctx; | ||
} | ||
if (res.__CA_patched) { | ||
return; | ||
} | ||
// response already patched to send finish event | ||
if (res.__CA_patched) { | ||
return; | ||
} | ||
var errorObject = null; | ||
eventNameFormatted = ctx.eventNameFormatted; | ||
var errorObject = null; | ||
res.__CA_patched = true; | ||
res.__CA_patched = true; | ||
var end_save = res.end; | ||
res.end = function () { | ||
var isFinished = res.finished; | ||
end_save.apply(res, arguments); | ||
var end_save = res.end; | ||
res.end = function() { | ||
var isFinished = res.finished; | ||
end_save.apply(res, arguments); | ||
if (isFinished === false) { | ||
if (res.statusCode >= 400) { | ||
errorObject = new Object(); | ||
errorObject['class'] = "Http " + res.statusCode; | ||
errorObject['msg'] = res.statusMessage; | ||
} | ||
if (isFinished === false) { | ||
if (res.statusCode >= 400) { | ||
errorObject = new Object(); | ||
errorObject['class'] = "Http " + res.statusCode; | ||
errorObject['msg'] = res.statusMessage; | ||
} | ||
ctx = agent.asynchEventDone(ctx, eventNameFormatted, null, errorObject); | ||
if (ctx != null) { | ||
agent.asynchEventFinish(ctx); | ||
} | ||
} | ||
}; | ||
ctx = agent.asynchEventDone(ctx, eventNameFormatted, null, errorObject); | ||
if (ctx != null) { | ||
agent.asynchEventFinish(ctx); | ||
} | ||
} | ||
}; | ||
}); | ||
}); | ||
}); | ||
// probe into outgoing client request instance | ||
proxy.after(http, 'request', function(obj, args, rval, storage) { | ||
if (agent.paused) return; | ||
// probe into outgoing client request instance | ||
proxy.after(http, 'request', function(obj, args, rval, storage) { | ||
if (agent.paused) return; | ||
if (methodMap[0] == "skip_instrument") { | ||
return; | ||
} | ||
if (methodMap[0] == "skip_instrument") { | ||
return; | ||
} | ||
var clientReq = rval; | ||
var clientReq = rval; | ||
if (isHttpReqDecEnabled && clientReq && typeof clientReq === 'object') { | ||
var ctx = storage.get('ctx'); | ||
if (isHttpReqDecEnabled && clientReq && typeof clientReq === 'object') { | ||
var ctx = storage.get('ctx'); | ||
if (ctx && ctx.corIdObj) { | ||
var corid = ctx.corIdObj.getOutgoingCorId(); | ||
clientReq.setHeader(CORRELATION_HEADER_KEY, corid); | ||
logger.debug('Decorated outgoing http request with correlation header %s: %s', | ||
CORRELATION_HEADER_KEY, corid); | ||
} | ||
} | ||
}); | ||
if (ctx && ctx.corIdObj) { | ||
var corid = ctx.corIdObj.getOutgoingCorId(); | ||
clientReq.setHeader(CORRELATION_HEADER_KEY, corid); | ||
logger.debug('Decorated outgoing http request with correlation header %s: %s', | ||
CORRELATION_HEADER_KEY, corid); | ||
} | ||
} | ||
}); | ||
if (http.ClientRequest && http.ClientRequest.prototype) { | ||
// request.end() is used to signify that one is done with making request out | ||
proxy.after(http.ClientRequest.prototype, 'end', function caHTTPClientReqHook(obj, args, rval, storage) { | ||
if (http.ClientRequest && http.ClientRequest.prototype) { | ||
// request.end() is used to signify that one is done with making request out | ||
proxy.after(http.ClientRequest.prototype, 'end', function caHTTPClientReqHook(obj, args, rval, storage) { | ||
var clientReq = obj; | ||
var hostHeader = clientReq.getHeader('host'); | ||
var host; | ||
var port; | ||
var protocol = (clientReq.agent && clientReq.agent.protocol) || DEFAULT_PROTOCOL; | ||
var path = clientReq.path; | ||
var url = path; | ||
if(hostHeader) { | ||
var hostPort = hostHeader.split(':'); | ||
host = hostPort[0] || DEFAULT_HOST; | ||
port = hostPort[1] || (clientReq.agent && clientReq.agent.defaultPort && clientReq.agent.defaultPort.toString()) || DEFAULT_PORT; | ||
} else { | ||
host = DEFAULT_HOST; | ||
port = (clientReq.agent && clientReq.agent.defaultPort && clientReq.agent.defaultPort.toString()) || DEFAULT_PORT; | ||
} | ||
var clientReq = obj; | ||
var hostHeader = clientReq.getHeader('host'); | ||
var host; | ||
var port; | ||
var protocol = (clientReq.agent && clientReq.agent.protocol) || DEFAULT_PROTOCOL; | ||
var path = clientReq.path; | ||
var url = path; | ||
if(hostHeader) { | ||
var hostPort = hostHeader.split(':'); | ||
host = hostPort[0] || DEFAULT_HOST; | ||
port = hostPort[1] || (clientReq.agent && clientReq.agent.defaultPort && clientReq.agent.defaultPort.toString()) || DEFAULT_PORT; | ||
} else { | ||
host = DEFAULT_HOST; | ||
port = (clientReq.agent && clientReq.agent.defaultPort && clientReq.agent.defaultPort.toString()) || DEFAULT_PORT; | ||
} | ||
if (!path.startsWith(protocol + '//')) { | ||
url = protocol + '//' + host + ':' + port + path; | ||
} | ||
if (!path.startsWith(protocol + '//')) { | ||
url = protocol + '//' + host + ':' + port + path; | ||
} | ||
var eventArgs = { | ||
host: host, | ||
port: port, | ||
method: clientReq.method, | ||
path: path, | ||
url: url | ||
}; | ||
var eventArgs = { | ||
host: host, | ||
port: port, | ||
method: clientReq.method, | ||
path: path, | ||
url: url | ||
}; | ||
var eventNameFormatted = 'httpclient.request'; | ||
var ctx = storage.get('ctx'); | ||
if (logger.isDebug()) { | ||
if (ctx) { | ||
logger.debug('%s[%d %d %d]: $s', eventNameFormatted, ctx.txid, ctx.lane, ctx.evtid, eventArgs.url); | ||
} | ||
else { | ||
logger.debug('%s - no context', eventNameFormatted); | ||
logger.debug((new Error('No context')).stack); | ||
} | ||
} | ||
ctx = agent.asynchEventStart(storage.get('ctx'), eventNameFormatted, eventArgs); | ||
storage.set('ctx', ctx); | ||
var eventNameFormatted = 'httpclient.request'; | ||
var ctx = storage.get('ctx'); | ||
if (logger.isDebug()) { | ||
if (ctx) { | ||
logger.debug('%s[%d %d %d]: $s', eventNameFormatted, ctx.txid, ctx.lane, ctx.evtid, eventArgs.url); | ||
} | ||
else { | ||
logger.debug('%s - no context', eventNameFormatted); | ||
logger.debug((new Error('No context')).stack); | ||
} | ||
} | ||
ctx = agent.asynchEventStart(storage.get('ctx'), eventNameFormatted, eventArgs); | ||
storage.set('ctx', ctx); | ||
// patch emit method | ||
var originalEmit = clientReq.emit; | ||
// patch emit method | ||
var originalEmit = clientReq.emit; | ||
clientReq.emit = function() { | ||
beforeClientResponseHook(clientReq, arguments, ctx); | ||
var returnValue = originalEmit.apply(this, arguments); | ||
if (ctx != null) agent.asynchEventFinish(ctx); | ||
return returnValue; | ||
}; | ||
clientReq.emit = function() { | ||
beforeClientResponseHook(clientReq, arguments, ctx); | ||
var returnValue = originalEmit.apply(this, arguments); | ||
if (ctx != null) agent.asynchEventFinish(ctx); | ||
return returnValue; | ||
}; | ||
}); | ||
}; | ||
}); | ||
}; | ||
// one time response event is emitted by ClientRequest instance when it is | ||
// gets response back from server | ||
var beforeClientResponseHook = function(obj, args, ctx) { | ||
if (methodMap[0] == "skip_instrument") { | ||
return; | ||
} | ||
// one time response event is emitted by ClientRequest instance when it is | ||
// gets response back from server | ||
var beforeClientResponseHook = function(obj, args, ctx) { | ||
if (methodMap[0] == "skip_instrument") { | ||
return; | ||
} | ||
if (args[0] !== 'response') { | ||
return; | ||
} | ||
if (args[0] !== 'response') { | ||
return; | ||
} | ||
var res = args[1]; | ||
var res = args[1]; | ||
if (res) { | ||
var clientReq = obj; | ||
var resCode = res.statusCode; | ||
var resMsg = res.statusMessage; | ||
if (res) { | ||
var clientReq = obj; | ||
var resCode = res.statusCode; | ||
var resMsg = res.statusMessage; | ||
var eventArgs = { | ||
resCode: resCode, | ||
resMsg: resMsg | ||
}; | ||
var eventArgs = { | ||
resCode: resCode, | ||
resMsg: resMsg | ||
}; | ||
var errorObject = null; | ||
if (res.statusCode >= 400 && res.statusCode <= 599) { | ||
errorObject = {}; | ||
errorObject['class'] = "Http " + resCode; | ||
errorObject['msg'] = resMsg; | ||
} | ||
var errorObject = null; | ||
if (res.statusCode >= 400 && res.statusCode <= 599) { | ||
errorObject = {}; | ||
errorObject['class'] = "Http " + resCode; | ||
errorObject['msg'] = resMsg; | ||
} | ||
var eventNameFormatted = 'httpclient.request'; | ||
var eventNameFormatted = 'httpclient.request'; | ||
if (ctx) { | ||
if (logger.isDebug()) { | ||
logger.debug('%s[%d %d %d]: callback', eventNameFormatted, ctx.txid, ctx.lane, ctx.evti); | ||
} | ||
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, errorObject); | ||
} | ||
else { | ||
if (logger.isDebug()) { | ||
logger.debug('%s - no context: callback', eventNameFormatted); | ||
logger.debug((new Error('No context')).stack); | ||
} | ||
} | ||
if (ctx) { | ||
if (logger.isDebug()) { | ||
logger.debug('%s[%d %d %d]: callback', eventNameFormatted, ctx.txid, ctx.lane, ctx.evti); | ||
} | ||
}; | ||
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, errorObject); | ||
} | ||
else { | ||
if (logger.isDebug()) { | ||
logger.debug('%s - no context: callback', eventNameFormatted); | ||
logger.debug((new Error('No context')).stack); | ||
} | ||
} | ||
} | ||
}; | ||
@@ -338,12 +318,12 @@ }; | ||
function getMethodsWithProbes() { | ||
if (!targetModule.methodMap) { | ||
var mt = new Object; | ||
mt[0] = 'http#all'; | ||
targetModule.methodMap = mt; | ||
} | ||
return targetModule.methodMap; | ||
if (!targetModule.methodMap) { | ||
var mt = new Object; | ||
mt[0] = 'http#all'; | ||
targetModule.methodMap = mt; | ||
} | ||
return targetModule.methodMap; | ||
} | ||
function instrument(methodMap) { | ||
targetModule.methodMap = methodMap; | ||
targetModule.methodMap = methodMap; | ||
} | ||
@@ -350,0 +330,0 @@ |
@@ -76,4 +76,3 @@ | ||
storage.set('ctx', ctx); | ||
proxy.callback(args, -1, | ||
function(obj, args, _, _, _, storage) { | ||
proxy.callback(args, -1, function(obj, args, storage) { | ||
var errorObject = agent.checkAndSetErrorObject(args, 'MySQLError'); | ||
@@ -80,0 +79,0 @@ // CA code start |
@@ -142,4 +142,3 @@ /** | ||
if (callbackIndex !== -1) { | ||
proxy.callback(args, callbackIndex, | ||
function(obj, args, extra, graph, currentNode, storage) { | ||
proxy.callback(args, callbackIndex, function(obj, args, storage) { | ||
@@ -146,0 +145,0 @@ //var errorObject = agent.checkAndSetErrorObject(args, 'MongodbError'); |
@@ -81,3 +81,3 @@ | ||
proxy.callback(args, -1, function(obj, args, _, _, _, storage) { | ||
proxy.callback(args, -1, function(obj, args, storage) { | ||
var errorObject = agent.checkAndSetErrorObject(args, 'MySQLError'); | ||
@@ -124,3 +124,3 @@ if (ctx != null) { | ||
var newArgs = [args[0]._callback]; | ||
proxy.callback(newArgs, -1, function(obj, args, extra, graph, currentNode, storage) { | ||
proxy.callback(newArgs, -1, function(obj, args, storage) { | ||
storage.set('ctx', ctx); | ||
@@ -127,0 +127,0 @@ if (debug) { |
@@ -55,3 +55,3 @@ | ||
function handle(obj, args, extra, _, _, storage) { | ||
function handle(obj, args, storage) { | ||
// var errorObject = agent.checkAndSetErrorObject(args, 'RedisError'); | ||
@@ -68,3 +68,3 @@ | ||
} | ||
function afterHandle(obj, args,extra) { | ||
function afterHandle(obj, args) { | ||
// if (ctx != null) agent.asynchEventFinish(ctx); | ||
@@ -71,0 +71,0 @@ } |
@@ -86,14 +86,8 @@ | ||
} | ||
var target = args[index]; | ||
var extra = agent.extra; | ||
var graph = agent.graph; | ||
var currentNode = agent.currentNode; | ||
var proxy = wrap(target, function(recv, args, context) { | ||
// TODO(bnoordhuis) Get rid of these globals. | ||
if (extra) agent.extra = extra; | ||
if (graph) agent.graph = graph; | ||
if (currentNode) agent.currentNode = currentNode; | ||
if (before) { | ||
try { | ||
before(recv, args, extra, graph, currentNode, context.storage); | ||
before(recv, args, context.storage); | ||
} catch (e) { | ||
@@ -103,6 +97,7 @@ logProbeErrorMessage(e, recv, context.target, true); | ||
} | ||
var rval = target.apply(recv, args); | ||
if (after) { | ||
try { | ||
after(recv, args, extra, graph, currentNode, context.storage); | ||
after(recv, args, rval, context.storage); | ||
} catch (e) { | ||
@@ -112,6 +107,2 @@ logProbeErrorMessage(e, recv, context.target, null, true); | ||
} | ||
// TODO(bnoordhuis) Get rid of these globals. | ||
if (currentNode) agent.currentNode = undefined; | ||
if (graph) agent.graph = undefined; | ||
if (extra) agent.extra = undefined; | ||
return rval; | ||
@@ -118,0 +109,0 @@ }, true); |
@@ -21,3 +21,17 @@ /** | ||
module.exports.findCallbackIndex = function (args) { | ||
var agent = require('../agent'); | ||
var proxy = require('../proxy'); | ||
var logger = require("../logger.js"); | ||
var traceUtil = require("./trace-util"); | ||
var util = require('util'); | ||
var DEFAULT_EVENT_CLASS_NAME = 'Object'; | ||
/** | ||
* Find index of callback function in arguments array. | ||
* @method | ||
* @param {array} args array of arguments | ||
* @returns {number} | ||
*/ | ||
var findCallbackIndex = function (args) { | ||
var callbackIndex = args.length - 1; | ||
@@ -29,2 +43,233 @@ | ||
return callbackIndex; | ||
} | ||
} | ||
/** | ||
* Instrument callback based asynchronous non-blocking functions | ||
* @method | ||
* @param {object} object Object selected for instrumentation | ||
* @param {array} fnNames Names of functions to instrument | ||
* @param {string} eventClassName Class name used for creating trace events | ||
* @param {function} startEventParamsCallback Callback function to fetch parameters for start trace event. | ||
* Arguments passed to this callback are: | ||
* -obj object being traced | ||
* -args arguments passed to function under execution | ||
* @param finishEventParamsCallback Callback function to fetch parameters for finish trace event | ||
* Arguments passed to this callback are: | ||
* -obj callback object being traced | ||
* -args arguments passed to callback. Usually non null args[0] indicates error, args[1] refers to returned value | ||
*/ | ||
var instrumentAsync = function (object, fnNames, eventClassName, startEventParamsCallback, finishEventParamsCallback) { | ||
var eventClassName = eventClassName || (object.constructor.name && object.constructor.name.toLowerCase()) || DEFAULT_EVENT_CLASS_NAME; | ||
var fnNames = fnNames || []; | ||
if (!(fnNames instanceof Array)) { | ||
fnNames = [fnNames]; | ||
} | ||
fnNames.forEach(function (fnName) { | ||
var eventNameFormatted = util.format('%s.%s', eventClassName, fnName); | ||
proxy.before(object, fnName, function (obj, args, storage) { | ||
var callbackIndex = findCallbackIndex(args); | ||
// if callback in not available, skip tracing | ||
if (callbackIndex == -1) { | ||
return; | ||
} | ||
var ctx = storage.get('ctx'); | ||
var eventArgs = startEventParamsCallback ? startEventParamsCallback(obj, args) : {}; | ||
if (logger.isDebug()) { | ||
logger.debug('%s - execution started', eventNameFormatted); | ||
if (!ctx) { | ||
logger.debug('%s - no context', eventNameFormatted); | ||
} | ||
} | ||
//on start of execution, send 'start-trace' event | ||
ctx = agent.asynchEventStart(ctx, eventNameFormatted, eventArgs); | ||
storage.set('ctx', ctx); | ||
proxy.callback(args, -1, function (obj, args, storage) { | ||
if (ctx != null) { | ||
if (logger.isDebug()) { | ||
logger.debug('%s - execution finished', eventNameFormatted); | ||
} | ||
var eventArgs = finishEventParamsCallback ? finishEventParamsCallback(obj, args) : {}; | ||
var errorObject = traceUtil.checkCallbackArgsForError(args); | ||
//on callback invocation, send 'end-trace' event | ||
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, errorObject); | ||
storage.set('ctx', ctx); | ||
} | ||
}, function (obj, args) { | ||
agent.asynchEventFinish(ctx); | ||
}); | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Instrument promise based asynchronous non-blocking functions | ||
* @method | ||
* @param {object} object Object selected for instrumentation | ||
* @param {array} fnNames Names of functions to instrument | ||
* @param {string} eventClassName Class name used for creating trace events | ||
* @param {function} startEventParamsCallback Callback function to fetch parameters for start trace event. | ||
* Arguments passed to this callback are: | ||
* -obj object being traced | ||
* -args arguments passed to function under execution | ||
* @param finishEventParamsCallback Callback function to fetch parameters for finish trace event | ||
* Arguments passed to this callback are: | ||
* -val result of async operation when promise is successfully resolved | ||
*/ | ||
var instrumentAsyncPromise = function (object, fnNames, eventClassName, startEventParamsCallback, finishEventParamsCallback) { | ||
var eventClassName = eventClassName || (object.constructor.name && object.constructor.name.toLowerCase()) || DEFAULT_EVENT_CLASS_NAME; | ||
var fnNames = fnNames || []; | ||
if (!(fnNames instanceof Array)) { | ||
fnNames = [fnNames]; | ||
} | ||
fnNames.forEach(function (fnName) { | ||
var eventNameFormatted = util.format('%s.%s', eventClassName, fnName); | ||
proxy.around(object, fnName, function (obj, args, storage) { | ||
var ctx = storage.get('ctx'); | ||
var eventArgs = startEventParamsCallback ? startEventParamsCallback(obj, args) : {}; | ||
if (logger.isDebug()) { | ||
logger.debug('%s - execution started', eventNameFormatted); | ||
if (!ctx) { | ||
logger.debug('%s - no context', eventNameFormatted); | ||
} | ||
} | ||
//on start of execution, send 'start-trace' event | ||
ctx = agent.asynchEventStart(ctx, eventNameFormatted, eventArgs); | ||
storage.set('ctx', ctx); | ||
}, | ||
function (obj, args, rval, storage) { | ||
var promise = rval; | ||
var ctx = storage.get('ctx'); | ||
if (ctx) { | ||
// register promise handlers | ||
promise.then(getPromiseSuccessHandler(storage, ctx, eventNameFormatted, finishEventParamsCallback), | ||
getPromiseRejectionHandler(storage, ctx, eventNameFormatted, finishEventParamsCallback)); | ||
} | ||
}); | ||
}); | ||
}; | ||
function getPromiseSuccessHandler(storage, ctx, eventNameFormatted, finishEventParamsCallback) { | ||
return function caMongodbPromiseSuccessHandler(value) { | ||
if (logger.isDebug()) { | ||
logger.debug('%s - execution finished - promise resolved successfully', eventNameFormatted); | ||
} | ||
if (ctx) { | ||
var eventArgs = finishEventParamsCallback ? finishEventParamsCallback(value) : {}; | ||
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs); | ||
storage.set('ctx', ctx); | ||
agent.asynchEventFinish(ctx); | ||
} | ||
}; | ||
} | ||
function getPromiseRejectionHandler(storage, ctx, eventNameFormatted, finishEventParamsCallback) { | ||
return function caMongodbPromiseRejectionHandler(value) { | ||
if (logger.isDebug()) { | ||
logger.debug('%s - execution finished - promise rejected', eventNameFormatted); | ||
} | ||
if (ctx) { | ||
var eventArgs = {}; | ||
var errorObject = traceUtil.getFormattedErrorObject(value); | ||
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, errorObject); | ||
storage.set('ctx', ctx); | ||
agent.asynchEventFinish(ctx); | ||
} | ||
}; | ||
} | ||
/** | ||
* Instrument synchronous blocking functions | ||
* @method | ||
* @param {object} object selected for instrumentation | ||
* @param {array} fnNames Names of functions to instrument | ||
* @param {string} eventClassName Class name used for creating trace events | ||
* @param {function} startEventParamsCallback Callback function to fetch parameters for start trace event. | ||
* Arguments passed to this callback are: | ||
* -obj object being traced | ||
* -args arguments passed to function under execution | ||
* @param finishEventParamsCallback Callback function to fetch parameters for finish trace event | ||
* Arguments passed to this callback are: | ||
* -obj callback object being traced | ||
* -args arguments passed to function under execution | ||
* -rval value returned by function | ||
*/ | ||
var instrumentSync = function (object, fnNames, eventClassName, startEventParamsCallback, finishEventParamsCallback) { | ||
var eventClassName = eventClassName || (object.constructor.name && object.constructor.name.toLowerCase()) || DEFAULT_EVENT_CLASS_NAME; | ||
var fnNames = fnNames || []; | ||
if (!(fnNames instanceof Array)) { | ||
fnNames = [fnNames]; | ||
} | ||
fnNames.forEach(function (fnName) { | ||
var eventNameFormatted = util.format('%s.%s', eventClassName, fnName); | ||
proxy.around(object, fnName, function (obj, args, storage) { | ||
var ctx = storage.get('ctx'); | ||
var eventArgs = startEventParamsCallback ? startEventParamsCallback(obj, args) : {}; | ||
if (logger.isDebug()) { | ||
logger.debug('%s - execution started', eventNameFormatted); | ||
if (!ctx) { | ||
logger.debug('%s - no context', eventNameFormatted); | ||
} | ||
} | ||
//on start of execution, send 'start-trace' event | ||
ctx = agent.asynchEventStart(ctx, eventNameFormatted, eventArgs); | ||
storage.set('ctx', ctx); | ||
}, | ||
function (obj, args, rval, storage) { | ||
var ctx = storage.get('ctx'); | ||
if (ctx != null) { | ||
if (logger.isDebug()) { | ||
logger.debug('%s - execution finished', eventNameFormatted); | ||
} | ||
var eventArgs = finishEventParamsCallback ? finishEventParamsCallback(obj, args, rval) : {}; | ||
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs); | ||
agent.asynchEventFinish(ctx); | ||
} | ||
}); | ||
}); | ||
}; | ||
module.exports = { | ||
findCallbackIndex: findCallbackIndex, | ||
instrumentAsync: instrumentAsync, | ||
instrumentAsyncPromise: instrumentAsyncPromise, | ||
instrumentSync: instrumentSync | ||
}; |
@@ -36,3 +36,4 @@ /** | ||
function formatEventArgValueObject (object) { | ||
// always returns a string value | ||
function formatEventArgValueObject(object) { | ||
if (object) { | ||
@@ -42,11 +43,9 @@ return truncateEventArgValue((JSON.stringify(object))); | ||
return null; | ||
return ''; | ||
} | ||
function createErrorObject(err, errorClass) { | ||
var errorObject = {}; | ||
errorObject = { | ||
class : errorClass, | ||
msg: formatEventArgValueObject(err) | ||
function createErrorObject(errorClass, errMsg) { | ||
var errorObject = { | ||
class: errorClass, | ||
msg: formatEventArgValueObject(errMsg) | ||
} | ||
@@ -57,7 +56,22 @@ | ||
function checkAndCreateErrorObject(args, errorClass) { | ||
function getFormattedErrorObject(err) { | ||
return (err instanceof Error) ? { | ||
class: err.name, | ||
msg: truncateEventArgValue(err.message) | ||
} : null; | ||
} | ||
function checkCallbackArgsForError(args, defaultErrorClass) { | ||
// based on callback calling conventions, first argument to callback is error | ||
if (args[0] != null) { | ||
return createErrorObject(args[0], errorClass); | ||
var err = args[0]; | ||
if (err != null) { | ||
var object = getFormattedErrorObject(err); | ||
if (!object && defaultErrorClass) { | ||
object = createErrorObject(defaultErrorClass, err); | ||
} | ||
return object; | ||
} | ||
@@ -72,2 +86,3 @@ | ||
module.exports.createErrorObject = createErrorObject; | ||
module.exports.checkAndCreateErrorObject = checkAndCreateErrorObject; | ||
module.exports.getFormattedErrorObject = getFormattedErrorObject; | ||
module.exports.checkCallbackArgsForError = checkCallbackArgsForError; |
@@ -1,2 +0,2 @@ | ||
/** | ||
@@ -34,3 +34,3 @@ * Copyright (c) 2015 CA. All rights reserved. | ||
function VirtualStack() { | ||
events.EventEmitter.call(this); | ||
events.EventEmitter.call(this); | ||
} | ||
@@ -58,9 +58,11 @@ | ||
var VirtualStackContext = function (parent, evtTypeNum, evtName, evtArgs) { | ||
var VirtualStackContext = function (previous, evtTypeNum, evtName, evtArgs) { | ||
this.evtTypeNum = evtTypeNum; | ||
this.parent = parent; | ||
this.previous = previous; | ||
this.childLanes = 0; | ||
this.name = evtName; | ||
this.args = evtArgs; | ||
if (parent == null) { | ||
if (previous == null) { | ||
this.parent = null; | ||
this.txid = nextTxId++; | ||
@@ -71,22 +73,22 @@ this.evtid = nextEventId++; | ||
this.seq = 0; | ||
this.corIdObj = coridgenerator.generateCorIdObj(); | ||
if (!this.args) { | ||
this.args = {}; | ||
} | ||
// in case, we have seen correlation header in request | ||
// parse and reset the correlation instance | ||
this.corIdObj = coridgenerator.generateCorIdObj(); | ||
if (!this.args) { | ||
this.args = {}; | ||
} | ||
// in case, we have seen correlation header in request | ||
// parse and reset the correlation instance | ||
if (this.args.corId) { | ||
this.corIdObj.parseAndSet(this.args.corId); | ||
} | ||
this.corIdObj.parseAndSet(this.args.corId); | ||
} | ||
// create new Correlation id instance | ||
this.args.corId = this.corIdObj.getCurrentCorId(); | ||
this.args.traceId = this.corIdObj.getTransactionTraceId(); | ||
this.args.corId = this.corIdObj.getCurrentCorId(); | ||
this.args.traceId = this.corIdObj.getTransactionTraceId(); | ||
return; | ||
} | ||
this.txid = parent.txid; | ||
this.evtid = parent.evtid; | ||
this.level = parent.level; | ||
this.lane = parent.lane; | ||
this.seq = parent.seq; | ||
this.corIdObj = parent.corIdObj; | ||
this.txid = previous.txid; | ||
this.evtid = previous.evtid; | ||
this.level = previous.level; | ||
this.lane = previous.lane; | ||
this.seq = previous.seq; | ||
this.corIdObj = previous.corIdObj; | ||
@@ -96,34 +98,55 @@ if (evtTypeNum == 0) { | ||
this.evtid = nextEventId++; | ||
if (parent.evtTypeNum == 0) { | ||
if (previous.evtTypeNum == 0) { | ||
// previous component is still executing, | ||
// so previous context is parent for this context | ||
this.parent = previous; | ||
// Parent is start event, we go to next level and start new lane | ||
this.level = parent.level + 1; | ||
this.lane = parent.lane; | ||
this.level = this.parent.level + 1; | ||
this.lane = previous.lane; | ||
this.seq = 0; | ||
} | ||
else { | ||
// previous component has finished. | ||
// so previous start context's parent is parent for this context | ||
// startCtx == previous.parent | ||
this.parent = previous.parent.parent; | ||
// parent is finish event, so we just start new component in same lane and level | ||
this.level = parent.level; | ||
this.lane = parent.lane; | ||
this.seq = parent.seq + 1; | ||
this.level = previous.level; | ||
this.lane = previous.lane; | ||
this.seq = previous.seq + 1; | ||
} | ||
// If this not first child pick another lane and start a new fragment | ||
if (parent.childLanes > 0) { | ||
this.lane = nextLaneId++; | ||
this.seq = 0; | ||
//create fragment start event | ||
//logger.debug("Parent: %s", util.inspect(parent, { showHidden: true, depth: 0 })); | ||
logger.debug('create new lane [%d, %d]', this.txid, this.lane); | ||
startNewLane(this); | ||
if (this.parent) { | ||
// If this not first child pick another lane and start a new fragment | ||
if (this.parent.childLanes > 0) { | ||
this.lane = nextLaneId++; | ||
this.seq = 0; | ||
//create fragment start event | ||
//logger.debug("Parent: %s", util.inspect(parent, { showHidden: true, depth: 0 })); | ||
if (logger.isDebug()) { | ||
logger.debug('create new lane: %s', this.toString()); | ||
} | ||
startNewLane(this); | ||
} | ||
this.parent.childLanes++; | ||
} | ||
parent.childLanes++; | ||
} else | ||
{ | ||
this.parent = previous; | ||
} | ||
}; | ||
VirtualStackContext.prototype.toString = function () { | ||
return util.format('{txid: %d, lane: %d, level: %d, eventName: %s, eventType: %d}', this.txid, this.lane, this.level, this.name, this.evtTypeNum); | ||
} | ||
function getLogPrefix(ctx) { | ||
var levelstr = ''; | ||
var levelstr = ''; | ||
for (var i = 0; i < ctx.level;i++) { | ||
levelstr=levelstr + ' '; | ||
} | ||
} | ||
var colorstr = '\x1b[1;3' + (ctx.lane%6 + 1) + 'm'; | ||
@@ -145,7 +168,7 @@ return levelstr + colorstr + '[' + ctx.txid + ',' + ctx.lane + ','+ ctx.seq + ','+ ctx.evtid + '] '; | ||
if (!virtualStackMap.hasOwnProperty(txid)) { | ||
// Create new transaction structure | ||
// Create new transaction structure | ||
virtualStackMap[txid] = { | ||
fragments: { | ||
count: 0 | ||
} | ||
fragments: { | ||
count: 0 | ||
} | ||
}; | ||
@@ -155,12 +178,12 @@ } | ||
if (ctx.parent) { | ||
fragName = ctx.parent.name.replace("\.","_"); | ||
ctx.corIdObj = ctx.parent.corIdObj; | ||
if (ctx.previous) { | ||
fragName = ctx.previous.name.replace("\.","_"); | ||
ctx.corIdObj = ctx.previous.corIdObj; | ||
}; | ||
virtualStackMap[txid].fragments[lane] = { | ||
name: fragName, | ||
evtid: fragmentCtx.evtid, | ||
startlevel: fragmentCtx.level, | ||
lane: fragmentCtx.lane | ||
name: fragName, | ||
evtid: fragmentCtx.evtid, | ||
startlevel: fragmentCtx.level, | ||
lane: fragmentCtx.lane | ||
}; | ||
@@ -176,3 +199,3 @@ virtualStackMap[txid].fragments.count++; | ||
ctx.args.corId = ctx.corIdObj.getCurrentCorId(); | ||
} | ||
} | ||
@@ -184,2 +207,3 @@ // Send out fragment start event to collector | ||
ctx.evtid = nextEventId++; | ||
ctx.parent = fragmentCtx; | ||
return ctx; | ||
@@ -202,2 +226,3 @@ } | ||
virtualStackMap[txid].fragments.count--; | ||
if (virtualStackMap[txid].fragments.count === 0) { | ||
@@ -209,4 +234,4 @@ logger.debug("Transaction %d finished.", txid); | ||
function pushAsyncTracerEvent(parentCtx, evtName, evtArgs) { | ||
var ctx = new VirtualStackContext(parentCtx, 0, evtName, evtArgs); | ||
function pushAsyncTracerEvent(previousCtx, evtName, evtArgs) { | ||
var ctx = new VirtualStackContext(previousCtx, 0, evtName, evtArgs); | ||
var txid = ctx.txid; | ||
@@ -216,15 +241,15 @@ var lane = ctx.lane; | ||
if (!virtualStackMap.hasOwnProperty(txid)) { | ||
// Create new transaction structure | ||
// Create new transaction structure | ||
virtualStackMap[txid] = { | ||
fragments: { | ||
count: 0 | ||
} | ||
fragments: { | ||
count: 0 | ||
} | ||
}; | ||
if (parentCtx == null && evtName.startsWith('http.')) { | ||
if (previousCtx == null && evtName.startsWith('http.')) { | ||
// This is brand new transaction, create new main fragment | ||
virtualStackMap[txid].fragments.count = 1; | ||
virtualStackMap[txid].fragments[lane] = { | ||
evtid: ctx.evtid, | ||
startlevel: ctx.level, | ||
lane: ctx.lane | ||
virtualStackMap[txid].fragments[lane] = { | ||
evtid: ctx.evtid, | ||
startlevel: ctx.level, | ||
lane: ctx.lane | ||
}; | ||
@@ -238,3 +263,3 @@ logger.debug('main fragment [%d, %d]', txid, lane); | ||
} | ||
} | ||
} | ||
logger.debug('fragment resume [%d, %d]', txid, lane); | ||
@@ -249,3 +274,3 @@ if (!virtualStackMap[txid].fragments[lane]) { | ||
virtualStackMap[txid].fragments[lane].active = true; | ||
createTracerEvent(ctx, 'tracer-start', evtName, evtTs, ctx.args); | ||
@@ -260,6 +285,6 @@ return ctx; | ||
if (virtualStackMap[txid] == undefined) { | ||
// This can happen for events that are not detected | ||
// This can happen for events that are not detected | ||
// as parallel fragments, but outlive the transaction | ||
logger.debug('pop %s with unknown txid [%d]', evtName, txid); | ||
// Send event to collector anyway, so that it can | ||
// Send event to collector anyway, so that it can | ||
createTracerEvent(ctx, 'tracer-finish', evtName, evtTs, evtArgs, errorObj); | ||
@@ -273,4 +298,11 @@ return null; | ||
} | ||
virtualStackMap[txid].fragments[lane].active = false; | ||
} | ||
// on pop, update child lanes count of parent ctx | ||
var parentCtx = startCtx.parent; | ||
if (parentCtx) { | ||
parentCtx.childLanes--; | ||
} | ||
} | ||
createTracerEvent(ctx, 'tracer-finish', evtName, evtTs, evtArgs, errorObj); | ||
@@ -288,9 +320,9 @@ return ctx; | ||
if (virtualStackMap.hasOwnProperty(txid) && | ||
virtualStackMap[txid].fragments.hasOwnProperty(lane) && | ||
virtualStackMap[txid].fragments[lane].startlevel === level && | ||
virtualStackMap[txid].fragments[lane].active === false) { | ||
if (virtualStackMap.hasOwnProperty(txid) && | ||
virtualStackMap[txid].fragments.hasOwnProperty(lane) && | ||
virtualStackMap[txid].fragments[lane].startlevel === level && | ||
virtualStackMap[txid].fragments[lane].active === false) { | ||
finishLane(finishCtx); | ||
} | ||
} | ||
} | ||
@@ -304,3 +336,3 @@ | ||
if (logger.isDebug()) { | ||
logger.debug('start: %s[%d %d %d] - ts:%d',evtName, ctx.txid, ctx.lane, ctx.evtid, evtTs); | ||
logger.debug('start: %s[%d %d %d] - ts:%d',evtName, ctx.txid, ctx.lane, ctx.evtid, evtTs); | ||
} | ||
@@ -311,7 +343,7 @@ startTraceHandler(evtName, evtTs, txid, ctx.evtid, evtArgs); | ||
if (evtType === 'tracer-finish' && endTraceHandler) { | ||
// Fancy logging: | ||
// Fancy logging: | ||
//logger.info(getLogPrefix(ctx) + 'finish: ' + evtName + ' '+ durStr + '- ts:' + evtTs + getLogSuffix()); | ||
// More efficient logging | ||
if (logger.isDebug()) { | ||
logger.debug('finish: %s [%d %d %d] - ts:%d', evtName, ctx.txid, ctx.lane, ctx.evtid, evtTs); | ||
logger.debug('finish: %s [%d %d %d] - ts:%d', evtName, ctx.txid, ctx.lane, ctx.evtid, evtTs); | ||
} | ||
@@ -321,3 +353,3 @@ endTraceHandler(evtName, evtTs, txid, ctx.evtid, evtArgs, errorObj); | ||
} | ||
} | ||
} | ||
@@ -351,3 +383,3 @@ function setTraceHandlers(startTrace, endTrace) { | ||
if (logger.isDebug()) { | ||
logger.debug('Deleting %d %s', txKeys[i], | ||
logger.debug('Deleting %d %s', txKeys[i], | ||
util.inspect(virtualStackMap[txKeys[i]], { showHidden: true, depth: 5 }) ); | ||
@@ -354,0 +386,0 @@ } |
{ | ||
"name": "ca-apm-probe", | ||
"version": "1.10.13", | ||
"version": "1.10.20", | ||
"description": "CA APM Node.js Agent monitors real-time health and performance of Node.js applications", | ||
@@ -34,3 +34,3 @@ "keywords": "ca-apm-probe, apm, agent, instrumentation, monitoring, performance", | ||
}, | ||
"engines" : { "node" : ">=0.10.x <=0.12.x" } | ||
"engines" : { "node" : ">=0.10.x <=4.3.x" } | ||
} |
@@ -95,2 +95,5 @@ ## APM Node.js Agent | ||
### Version History | ||
- 1.10.20 | ||
- Published on: 04/25/2016 | ||
- Added support for node 4.3.x runtime | ||
- 1.10.13 | ||
@@ -97,0 +100,0 @@ - Published on: 02/17/2016 |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
495570
60.03%70
6.06%9239
98.86%115
2.68%