apminsight
Version:
monitor nodejs applications
466 lines (400 loc) • 14.1 kB
JavaScript
var tracker = require("./tracker");
var utils = require("./../util/utils");
var logger = require("./../util/logger");
var metricstore = require("./metricstore");
var errorInfo = require("./error");
function Transaction(reqInfo) {
this._startTime = new Date().getTime();
(this._responseTime = 0), (this._minRt = 0), (this._maxRt = 0);
(this._errorRt = 0), (this._count = 0), (this._endTime = 0);
(this._errorCount = 0), (this._completed = false);
(this._dbCalls = []), (this._intCompInfo = {});
(this._extCompInfo = {}), (this._extCallCount = 0), (this._errInfo = {});
(this._ocurrence = -1), (this._traceReason = 0);
this._byApi = reqInfo.api ? true : false;
this._ignore = false;
this._rootTracker = tracker.createRootTracker(reqInfo.rootListner);
(this._trackersCount = 0), (this._sqlTrackersCount = 0);
this._customParams = {};
this._error = null;
}
Transaction.prototype.updateRtMetric = function () {
this._endTime = new Date().getTime();
this._responseTime = this._endTime - this._startTime;
this._minRt = this._responseTime;
this._maxRt = this._responseTime;
};
Transaction.prototype.updateReqCountByErr = function (err) {
if (err instanceof Error) {
this._errorCount = 1;
this._error = new errorInfo(err);
this.getRootTracker().setError(err, this);
} else {
this._count = 1;
}
};
Transaction.prototype.aggregateNonErrorTxn = function (txn) {
this._responseTime += txn.getRt();
this._count += txn.getCount();
if (this._minRt === 0 || txn.getMinRt() < this._minRt) {
this._minRt = txn.getMinRt();
}
if (this._maxRt === 0 || this._maxRt < txn.getMaxRt()) {
this._maxRt = txn.getMaxRt();
}
};
Transaction.prototype.aggregateTxnSubResources = function (txn) {
this._dbCalls = this._dbCalls.concat(txn.getDbCalls());
aggregateComponents(this._intCompInfo, txn.getInternalCompInfo());
aggregateComponents(this._extCompInfo, txn.getExtCompInfo());
this.aggregateErrors(txn.getErrorsInfo());
};
Transaction.prototype.aggregateComponent = function (component) {
if (!component) {
return;
}
if (component.isExt() > 0) {
var compNameInfo =
component.getName() + component.getHost() + component.getPort();
checkAndAggregateComp(this._extCompInfo, component, compNameInfo);
this._extCallCount += component.getCount() + component.getErrorCount();
} else {
checkAndAggregateComp(
this._intCompInfo,
component,
component.getName()
);
}
};
function checkAndAggregateComp(collectedComp, newComp, compNameIndex) {
var compInfo = collectedComp[compNameIndex];
if (compInfo) {
collectedComp[compNameIndex].aggregate(newComp);
} else {
collectedComp[compNameIndex] = newComp;
}
}
Transaction.prototype.checkAndAddError = function (errName) {
if (utils.isEmpty(errName)) {
return;
}
if (this._errInfo[errName]) {
this._errInfo[errName] += 1;
} else {
this._errInfo[errName] = 1;
}
};
function aggregateComponents(baseCompInfo, newCompInfo) {
var allUniqueComp = Object.keys(newCompInfo);
if (allUniqueComp.length == 0) {
return baseCompInfo;
}
allUniqueComp.forEach(function (eachUniqueComp) {
var comp = baseCompInfo[eachUniqueComp];
if (comp) {
var combineComp = newCompInfo[eachUniqueComp];
baseCompInfo[eachUniqueComp] =
comp.aggregateAsNewComponent(combineComp);
return;
}
baseCompInfo[eachUniqueComp] = newCompInfo[eachUniqueComp];
});
}
Transaction.prototype.aggregateErrors = function (errorInfo) {
var allUniqueError = Object.keys(errorInfo);
var curTxn = this;
allUniqueError.forEach(function (eachUniqueError) {
if (curTxn._errInfo[eachUniqueError]) {
curTxn._errInfo[eachUniqueError] += errorInfo[eachUniqueError];
return;
}
curTxn._errInfo[eachUniqueError] = errorInfo[eachUniqueError];
});
};
Transaction.prototype.getComponentDetails = function () {
var components = [];
var intCompInfo = this._intCompInfo;
var extCompInfo = this._extCompInfo;
Object.keys(intCompInfo).forEach(function (eachComp) {
components.push(intCompInfo[eachComp].getInfoAsObj());
});
Object.keys(extCompInfo).forEach(function (eachComp) {
components.push(extCompInfo[eachComp].getInfoAsObj());
});
return components;
};
Transaction.prototype.getErrorDetails = function () {
var logmetric = {};
var warningCount = 0,
fatalCount = 0;
var errorInfo = this._errInfo;
Object.keys(errorInfo).forEach(function (eachError) {
var errorCount = errorInfo[eachError];
logmetric[eachError] = errorCount;
fatalCount += errorCount;
});
logmetric.warning = warningCount;
logmetric.fatal = fatalCount;
return logmetric;
};
Transaction.prototype.appendCompInfoInTrace = function (traceInfo) {
traceInfo.ext_components = extractCompDetailsForTrace(this._extCompInfo);
traceInfo.int_components = extractCompDetailsForTrace(this._intCompInfo);
};
function extractCompDetailsForTrace(allCompInfo) {
var allComp = Object.keys(allCompInfo);
var compDetails = { success: {}, fail: {} };
if (allComp.length <= 0) {
return compDetails;
}
allComp.forEach(function (eachComp) {
var compInfo = allCompInfo[eachComp];
if (
compDetails.success[compInfo.getName()] ||
compDetails.fail[compInfo.getName()]
) {
compDetails.success[compInfo.getName()] +=
allCompInfo[eachComp].getCount();
compDetails.fail[compInfo.getName()] +=
allCompInfo[eachComp].getErrorCount();
} else {
compDetails.success[compInfo.getName()] =
allCompInfo[eachComp].getCount();
compDetails.fail[compInfo.getName()] =
allCompInfo[eachComp].getErrorCount();
}
});
return compDetails;
}
Transaction.prototype.checkAndupdateInDataStore = function (
txnMetric,
txnType,
samplingFac,
metricLimit
) {
var txnExist = false;
var matchedTxn;
if (txnMetric[this.getUrl()]) {
txnExist = true;
matchedTxn = txnMetric[this.getUrl()];
} else {
matchedTxn = new txnType({});
}
if (matchedTxn.incrementAndGetOcurrence() % samplingFac !== 0) {
logger.critical("txn dropped due to sampling factor");
return;
}
if (txnExist || Object.keys(txnMetric).length <= metricLimit) {
var dbCallCount = metricstore.updateAndGetDbCount(
this.getDbCalls().length
);
if (
dbCallCount >
utils.getGenericThreshold(this.getUrl()).getDbMetricSize()
) {
this.clearDbCalls();
}
matchedTxn.aggregate(this);
txnMetric[this.getUrl()] = matchedTxn;
}
metricstore.checkAndIncludeInTrace(this);
};
Transaction.prototype.isTxnIgnored = function (resCode) {
let shouldIgnore = this._ignore; // Default to current ignore status
try {
if (this.isCompleted()) {
logger.critical("End txn called again", this.getUrl());
return true;
}
// If no response code is provided, return the current ignore status or if already ignored
if (shouldIgnore || utils.isEmpty(resCode)) {
return shouldIgnore;
}
// Convert resCode to string safely
const resCodeStr = String(resCode);
// Get include list
let httpErrorCodesIncludeList = apmInsightAgentInstance
.getThreshold()
.getHttpErrorCodesTrackList();
if (httpErrorCodesIncludeList.length == 0) {
httpErrorCodesIncludeList = apmInsightAgentInstance.getConfig().getHttpErrorCodesTrack();
}
// Check include list logic
if (httpErrorCodesIncludeList.length > 0) {
const isValidResponse = httpErrorCodesIncludeList.includes(resCodeStr) ||
(resCode >= 200 && resCode < 300);
this._ignore = shouldIgnore = !isValidResponse; // Update shouldIgnore based on validation
return shouldIgnore;
}
// Check ignore list if include list approach isn't used
const httpErrorCodesIgnoreList = apmInsightAgentInstance
.getThreshold()
.getHttpErrorCodesIgnoreList();
if (httpErrorCodesIgnoreList.includes(resCodeStr)) {
logger.critical(
`${this.getUrl()} (${resCode}) transaction ignored. This response code is configured in ignore http error code list: ${httpErrorCodesIgnoreList}`
);
shouldIgnore = true;
}
} catch (err) {
logger.error(
`Error while evaluating transaction ignore status for ${this.getUrl() || 'unknown'}: ${err}`
);
// We'll return the current value of shouldIgnore via the finally block
} finally {
return shouldIgnore;
}
};
Transaction.prototype.setTransactionName = function (name) {
this._url = name;
};
Transaction.prototype.setRumTraceId = function (traceId) {
this._rumTraceId = traceId;
};
Transaction.prototype.getRumTraceId = function () {
return this._rumTraceId;
};
Transaction.prototype.setCustomParams = function (key, value) {
var cusParams = this._customParams;
if (cusParams) {
var values = cusParams[key];
if (utils.isEmpty(values)) {
if (Object.keys(cusParams).length < 10) {
values = [];
values.push(value);
cusParams[key] = values;
}
} else if (values.length < 10) {
values.push(value);
}
}
};
Transaction.prototype.endRootTracker = function (err) {
this._rootTracker.endTracker(this, err);
};
Transaction.prototype.getRootTracker = function () {
return this._rootTracker;
};
Transaction.prototype.getDbCalls = function () {
return this._dbCalls;
};
Transaction.prototype.getInternalCompInfo = function () {
return this._intCompInfo;
};
Transaction.prototype.getExtCompInfo = function () {
return this._extCompInfo;
};
Transaction.prototype.getErrorsInfo = function () {
return this._errInfo;
};
Transaction.prototype.addDbCall = function (dbTracker) {
this._dbCalls.push(dbTracker);
this.incrSqlTrackersCount();
};
Transaction.prototype.clearDbCalls = function () {
this._dbCalls = [];
};
Transaction.prototype.isCompleted = function () {
return this._completed;
};
Transaction.prototype.isErrorTxn = function () {
return this._errorCount > 0;
};
Transaction.prototype.markCompleted = function () {
this._completed = true;
};
Transaction.prototype.getUrl = function () {
return this._url;
};
Transaction.prototype.getStartTime = function () {
return this._startTime;
};
Transaction.prototype.getRt = function () {
return this._responseTime;
};
Transaction.prototype.getErrorRt = function () {
return this._errorRt;
};
Transaction.prototype.getError = function () {
return this._error;
};
Transaction.prototype.getMinRt = function () {
return this._minRt;
};
Transaction.prototype.getMaxRt = function () {
return this._maxRt;
};
Transaction.prototype.getCount = function () {
return this._count;
};
Transaction.prototype.getErrorCount = function () {
return this._errorCount;
};
Transaction.prototype.getExtCallCount = function () {
return this._extCallCount;
};
Transaction.prototype.getCustomParams = function () {
return this._customParams;
};
Transaction.prototype.getCustomParamsAsStr = function () {
var cusParams = this.getCustomParams();
Object.keys(cusParams).forEach(function (eachKey) {
var eachValue = cusParams[eachKey];
for (var index = 0; index < eachValue.length; index++) {
var valueAtIndex = eachValue[index];
try {
if (utils.isObject(valueAtIndex)) {
eachValue[index] = JSON.stringify(valueAtIndex);
} else {
eachValue[index] = String(valueAtIndex);
}
} catch (err) {
logger.error(
"error while converting the value " +
valueAtIndex +
" to string"
);
}
}
});
return cusParams;
};
Transaction.prototype.incrementAndGetOcurrence = function () {
this.incrementOcurrence();
return this._ocurrence;
};
Transaction.prototype.incrementOcurrence = function () {
this._ocurrence += 1;
};
Transaction.prototype.isCreatedByApi = function () {
return this._byApi;
};
Transaction.prototype.setIgnore = function () {
this._ignore = true;
logger.info(this.getUrl() + " txn ignored by api");
};
Transaction.prototype.getTrackerCountInfo = function () {
return {
method_trackers_count: this.getMethdCallsCount(),
sql_trackers_count: this.getSqlTrackersCount(),
ext_trackers_count: this.getExtTrackersCountExcludingSql()
};
};
Transaction.prototype.getMethdCallsCount = function () {
var internalCount = this._trackersCount - this._extCallCount;
return internalCount < 0 ? 0 : internalCount;
};
Transaction.prototype.getExtTrackersCountExcludingSql = function () {
var count = this._extCallCount - this._sqlTrackersCount;
return count < 0 ? 0 : count;
};
Transaction.prototype.getSqlTrackersCount = function () {
return this._sqlTrackersCount;
};
Transaction.prototype.incrSqlTrackersCount = function () {
this._sqlTrackersCount++;
};
Transaction.prototype.incrTrackersCount = function () {
this._trackersCount++;
};
module.exports = Transaction;