apminsight
Version:
monitor nodejs applications
619 lines (561 loc) • 17.7 kB
JavaScript
var utils = require("./../util/utils");
var logger = require("./../util/logger");
var constants = require("./../constants");
var libUtil = require("util");
var errorinfo = require("./error");
function Tracker(trackerInfo) {
this._parent = trackerInfo.parent;
this._startTime = new Date().getTime();
this._name = trackerInfo.trackerName;
this._component = trackerInfo.component ? trackerInfo.component : "";
this._sync = trackerInfo.sync ? true : false;
this._byApi = trackerInfo.api ? true : false;
this._stale = trackerInfo.stale ? trackerInfo.stale : false;
this._compAppended = false;
this._endTime = 0;
this._time = 0;
this._childOverhead = 0;
this._info = {};
this._child = [];
this._error = null;
this._completed = false;
this._spanId = utils.randomIdGenerator(constants.spanIdBytes)();
this._methodOrder = trackerInfo.methodOrder;
this._markedForDrop = false;
}
Tracker.prototype.getInfo = function () {
return this._info;
};
Tracker.prototype.updateInfo = function (additionalInfo) {
this._info = Object.assign(this._info, additionalInfo);
};
Tracker.prototype.getStartTime = function () {
return this._startTime;
};
Tracker.prototype.getTrackerName = function () {
return this._name;
};
Tracker.prototype.getTrackerNameForTrace = function () {
var name = this._name.indexOf(".prototype.")
? this._name.replace(".prototype", "")
: this._name;
var info = this.getInfo();
//reqPath will be available for next.js framework
if (info.reqPath) {
name += " - " + info.reqPath;
}
if (info.opn) {
name = this._component + " - " + info.opn;
}
if (info.method) {
name += " - " + info.method;
}
if (info.statusCode) {
name += " - " + info.statusCode;
}
if (info.host && info.port) {
name += " - " + info.host + ":" + info.port;
}
if (info.pathname) {
name += " - " + info.pathname;
}
if (this.isError()) {
return name + " : " + this.getErrorInfo().getType();
}
return name;
};
Tracker.prototype.getComponent = function () {
return this._component;
};
Tracker.prototype.getTime = function () {
return this._time;
};
Tracker.prototype.getChildOverhead = function () {
return this._childOverhead;
};
Tracker.prototype.checkAndUpdateExclusiveTime = function (childTracker) {
if (this._sync && childTracker && childTracker.isSync()) {
var overHead = childTracker.getTime() + childTracker.getChildOverhead();
this._childOverhead += overHead;
}
};
Tracker.prototype.getChildTrackers = function () {
return this._child;
};
Tracker.prototype.isAllowedInTrace = function (txn) {
if (this._stale || !this.isCompleted()) {
return false;
}
if (this.isError() || this.isCustomTracker()) {
return true;
}
if (
!utils.isEmpty(this.getComponent()) &&
constants.externalComponents.indexOf(this.getComponent()) >= 0
) {
return true;
}
return (
this._time >=
utils.getGenericThreshold(txn.getUrl()).getTxnTrackerDropThreshold()
);
};
Tracker.prototype.getAdditionalInfo = function () {
var info = {};
if (this.isError()) {
info.exception_st = this.getErrorInfo().getErrorStackFrames();
}
if (this._info.dtdata) {
info.dtdata = this._info.dtdata;
}
return info;
};
Tracker.prototype.updateEndTime = function () {
this._endTime = new Date().getTime();
this._time = this._endTime - this._startTime - this._childOverhead;
};
Tracker.prototype.setError = function (error, txn) {
if (error && !error.apmAgentHandled) {
this._error = new errorinfo(error);
error.apmAgentHandled = true;
if(!apmInsightAgentInstance.getConfig().isDataExporterEnabled()) {
txn.checkAndAddError(this._error.getType());
}
}
};
Tracker.prototype.setActive = function () {
if (this._stale && !this._compAppended) {
apmInsightAgentInstance.checkAndAddComponent(
apmInsightAgentInstance.getCurTxn(),
this
);
this._compAppended = true;
}
this._stale = false;
};
Tracker.prototype.isError = function () {
if (this._error) {
return true;
}
return false;
};
Tracker.prototype.getErrorInfo = function () {
return this._error;
};
Tracker.prototype.endTracker = function (txn, err) {
if (this.isCompleted()) {
return false;
}
if (!txn || txn.isCompleted()) {
return false;
}
this._completed = true;
this.setError(err, txn);
this.updateEndTime();
if(apmInsightAgentInstance.getConfig().isDataExporterEnabled()) {
const shouldDrop = this.shouldDropTracker(txn);
if (shouldDrop) {
// Drop the tracker and reassign children to parent
this._markedForDrop = true;
this.dropAndReassignChildren();
}
return false; //This prevents db call getting stored in transaction
} else {
if (!this._stale && !this._compAppended) {
apmInsightAgentInstance.checkAndAddComponent(txn, this);
this._compAppended = true;
}
if (this._parent) {
this._parent.checkAndUpdateExclusiveTime(this);
}
}
return true;
};
Tracker.prototype.addChildTracker = function (child) {
this._child.push(child);
};
Tracker.prototype.isCompleted = function () {
return this._completed;
};
Tracker.prototype.isSync = function () {
return this._sync;
};
Tracker.prototype.isCustomTracker = function () {
return this._byApi;
};
Tracker.prototype.isDistributedTrace = function () {
return this._dtrace;
};
Tracker.prototype.setDistributedTrace = function () {
this._dtrace = true;
};
Tracker.prototype.updateDtInfo = function (dtInfo) {
this.updateInfo({ dtdata: dtInfo });
this.setDistributedTrace();
};
Tracker.prototype.getSpanId = function () {
return this._spanId;
}
Tracker.prototype.setMethodOrder = function (order) {
this._methodOrder = order;
};
Tracker.prototype.getMethodOrder = function () {
return this._methodOrder;
};
Tracker.prototype.getExporterFmtInfo = function (txn) {
if (this._markedForDrop || !this.isCompleted() || (this._methodOrder != 0 && !this._parent)) {
return null;
}
// Processing name
let name = this._name.indexOf(".prototype.")
? this._name.replace(".prototype", "")
: this._name;
//reqPath will be available for next.js framework
if (this.getInfo().reqPath) {
name += " - " + this.getInfo().reqPath;
}
const info = {
st: this._startTime,
et: this._endTime,
fn: name,
cn: this._component,
si: this._spanId
};
if (this._methodOrder !== 0 && this._parent) {
info.psi = this._parent.getSpanId();
}
// Set external component flag (1 if external, 0 if internal)
if ((!utils.isEmpty(this._component) &&
constants.externalComponents.indexOf(this._component) >= 0)) {
info.ex = 1;
if (!utils.isEmpty(this._info.host)) {
info.hn = this._info.host;
}
if (!utils.isEmpty(this._info.port)) {
info.pn = this._info.port;
}
if (!utils.isEmpty(this._info.statusCode)) {
info.esc = this._info.statusCode;
}
}
// Add error/exception information if available
if (this.isError()) {
const errorInfo = this.getErrorInfo();
if (errorInfo) {
info.if = info.ie = 1;
info.ei = [{
err_clz: errorInfo.getType(),
level: errorInfo.getLevel(),
time: errorInfo.getTime(),
str: errorInfo.getMessage ? errorInfo.getMessage() : ""
}];
// Add stacktrace if available
var stackFrames = errorInfo.getErrorStackFrames();
if (stackFrames) {
info.mst = stackFrames;
}
}
}
// Database Query construction
if (
(this.getComponent() === "MONGODB" || this.getComponent() === "ORACLEDB") &&
this._info.opn &&
this._info.object
) {
info.query = this._info.opn + " - " + this._info.object;
info.dn = this._info.object;
} else if (this._info.query){
info.qs = this._info.query;
}
// Add method order if available (for ordering spans with same timestamp)
if (utils.isNumber(this._methodOrder)) {
info.mo = this._methodOrder;
}
if (!this.isSync()) {
// Async operations are always async roots
info.ar = true;
} else if (this._parent && !this._parent.isSync()) {
// Sync callbacks of async parents are also async roots
info.ar = true;
}
// Add distributed tracing data if available and increment dt_count
if (this.isDistributedTrace()) {
info.dt = this._info.dtdata;
// Increment distributed tracing count in the passed transaction
if (txn && typeof txn.incrDtCount === 'function') {
txn.incrDtCount();
}
}
return info;
}
Tracker.prototype.shouldDropTracker = function (txn) {
const isWebTxn = txn.getTxnType() === "webtxn";
const duration = this._endTime - this._startTime;
const threshold = txn.getTrackerDropThreshold() || 10;
//Criteria 1: not a root tracker
if(this._methodOrder === 0) {
return false;
}
// Criteria 2: No error
if (this.isError()) {
return false;
}
// Criteria 3: Not external component (only for web transactions)
if (isWebTxn && !utils.isEmpty(this.getComponent()) &&
constants.externalComponents.indexOf(this.getComponent()) >= 0) {
return false;
}
// Criteria 4: Not custom tracker (API created)
if (this.isCustomTracker()) {
return false;
}
// Criteria 5: Not distributed trace
if (this.isDistributedTrace()) {
return false;
}
// Criteria 6: Duration less than threshold
if (duration <= threshold) {
return true;
}
return false;
};
Tracker.prototype.dropAndReassignChildren = function () {
if (!this._parent) {
// Orphan all children if no parent exists
this._child.forEach(child => child._parent = null);
} else {
const grandparent = this._parent;
this._child.forEach(child => {
child._parent = grandparent;
if (!grandparent._child.includes(child)) {
grandparent._child.push(child);
}
});
}
this._child.length = 0;
};
function DbTracker(trackerInfo) {
Tracker.call(this, trackerInfo);
this._minRt = 0;
this._maxRt = 0;
this._count = 0;
this._errorCount = 0;
this.isDbTracker = true;
}
libUtil.inherits(DbTracker, Tracker);
DbTracker.prototype.endTracker = function (txn) {
var processed = Tracker.prototype.endTracker.apply(this, arguments);
if (processed) {
txn.addDbCall(this);
}
};
DbTracker.prototype.isAllowedInTrace = function () {
return this.isCompleted() ? true : false;
};
DbTracker.prototype.getCount = function () {
return this._count;
};
DbTracker.prototype.getErrorCount = function () {
return this._errorCount;
};
DbTracker.prototype.getMinRt = function () {
return this._minRt;
};
DbTracker.prototype.getMaxRt = function () {
return this._maxRt;
};
DbTracker.prototype.getTrackerNameForTrace = function () {
if (!this._info.opn) {
return this._name.indexOf(".prototype.")
? this._name.replace(".prototype", "")
: this._name;
}
var trackerName = this._component + " - " + this._info.opn;
if (this._info.host && this._info.port) {
trackerName += " - " + this._info.host + ":" + this._info.port;
}
if (this.isError()) {
trackerName += " : " + this.getErrorInfo().getType();
}
return trackerName;
};
DbTracker.prototype.getAdditionalInfo = function (txn) {
var info = {};
if (this.isError()) {
info.stacktrace = this.getErrorInfo().getErrorStackFrames();
}
if (
(this.getComponent() === "MONGODB" || this.getComponent() === "ORACLEDB") &&
this._info.opn &&
this._info.object
) {
info.query = this._info.opn + " - " + this._info.object;
} else if (utils.getGenericThreshold(txn.getUrl()).isSqlParameterized()) {
info.query = getMaskedQuery(this._info.query);
} else {
info.query = this._info.query;
}
return info;
};
DbTracker.prototype.extractOperationInfo = function () {
if (this._info.opn && this._info.object) {
return this._info;
}
if (this._info.query) {
try {
var opnName = constants.firstSyllable.exec(this._info.query);
var regex = constants.dbOpnRegex[opnName[1].toLowerCase()];
if (regex) {
var groups = regex.exec(this._info.query);
if (groups) {
this._info.opn = groups[1].toUpperCase();
this._info.object = groups[2];
return this._info;
}
}
} catch (err) {
if (apmInsightAgentInstance.getConfig().isDebugModeEnabled()) {
logger.error(
"Error occured while processing the query - " +
this._info.query +
" :: " +
err
);
}
}
} else if (this.getComponent() === "MONGODB" || this.getComponent() === "ORACLEDB") {
var qualifiedName = this.getTrackerName().split(".");
this._info.opn = qualifiedName.pop().toUpperCase();
return this._info;
}
return {};
};
DbTracker.prototype.aggregate = function (dbtracker) {
var info = dbtracker.extractOperationInfo();
this._info.opn = info.opn;
this._info.object = info.object;
this._component = dbtracker.getComponent();
if (dbtracker.isError()) {
this._errorCount += 1;
return;
}
this._time += dbtracker.getTime();
this._count += 1;
if (this._minRt === 0 || dbtracker.getTime() < this._minRt) {
this._minRt = dbtracker.getTime();
}
if (this._maxRt === 0 || this._maxRt < dbtracker.getTime()) {
this._maxRt = dbtracker.getTime();
}
};
function getMaskedQuery(sql) {
if (!sql) {
return "";
}
var maskedQuery = "";
var length = sql.length;
for (var i = 0; i < length; i++) {
var c = sql.charAt(i);
if (c === "'" || c === '"' /*|| c == '`'*/) {
var c2 = c;
maskedQuery += "?";
for (i++; i < length; i++) {
c = sql.charAt(i);
if (c == "\\" && i < length - 1) {
i++;
} else {
if (c == c2) {
break;
}
}
}
if (i >= length) {
break;
}
} else {
if (
utils.isNumber(c) ||
(c === "." &&
i < length - 1 &&
utils.isNumber(sql.charAt(i + 1)))
) {
maskedQuery += "?";
while (i < length) {
c = sql.charAt(i);
if (!utils.isNumber(c) && c !== ".") {
break;
}
i++;
}
if (i >= length) {
break;
}
i--;
} else {
if (!utils.isLetter(c)) {
if (c !== "_") {
maskedQuery += c;
continue;
}
}
while (i < length) {
c = sql.charAt(i);
if (!utils.isLetter(c) && c != "_" && !utils.isNumber(c)) {
break;
}
maskedQuery += c;
i++;
}
if (i >= length) {
break;
}
i--;
}
}
}
return maskedQuery;
}
function EsTracker(trackerInfo) {
Tracker.call(this, trackerInfo);
this.isEsTracker = true;
}
libUtil.inherits(EsTracker, Tracker);
EsTracker.prototype.getTrackerNameForTrace = function () {
var trackerName = this._component;
if (
this._info.params &&
this._info.params[0] &&
this._info.params[0].method
) {
trackerName += " - " + this._info.params[0].method;
}
if (this._info.host && this._info.port) {
trackerName += " - " + this._info.host + ":" + this._info.port;
}
if (
this._info.params &&
this._info.params[0] &&
this._info.params[0].path
) {
trackerName += " - " + this._info.params[0].path;
}
if (this.isError()) {
trackerName += " : " + this.getErrorInfo().getType();
}
return trackerName;
};
function createRootTracker(rootListner, methodOrder) {
var trackerInfo = {
trackerName: rootListner,
sync: true,
component: "NODEJS-CORE",
methodOrder: methodOrder || 0
};
return new Tracker(trackerInfo);
}
module.exports = {
Tracker: Tracker,
DbTracker: DbTracker,
EsTracker: EsTracker,
createRootTracker: createRootTracker
};