UNPKG

appdynamics

Version:

Performance Profiler and Monitor

390 lines (319 loc) 10.7 kB
/* Copyright (c) AppDynamics, Inc., and its affiliates 2015 All Rights Reserved */ 'use strict'; function ProtobufModel(agent) { this.agent = agent; this.detectErrors = undefined; this.errorThreshold = undefined; this.ignoredMessagesConfig = undefined; this.ignoredExceptionConfig = undefined; this.callGraphConfig = undefined; this.traceRegex = undefined; this.userCodeErrRegEx = undefined; this.classRegex = undefined; this.agentRegex = undefined; } exports.ProtobufModel = ProtobufModel; ProtobufModel.prototype.init = function() { var self = this; self.currentProcessSnapshot = undefined; self.btCallsStarted = null; self.btCallsCompleted = null; self.traceRegex = /at\s([^\(]+)\s\(([a-zA-Z]\:)?([^\:]+)\:(\d+)\:\d+\)$/; self.userCodeErrRegEx = /at\s([a-zA-Z]\:\\)?([^\:]+)\:(\d+)\:\d+$/; self.classRegex = /^(.+)\.([^\.]+)$/; self.agentRegex = /node_modules[\\\/]appdynamics/; }; ProtobufModel.prototype.createSnapshotInfo = function(transaction, errorInfo, exceptionInfo) { var self = this; var snapshotInfo = undefined; var snapshotTriggerObject = self.agent.backendConnector.createSnapshotTrigger(transaction); if (snapshotTriggerObject.attachSnapshot) { var processSnapshots = transaction.processSnapshots; var processSnapshotGUIDs = undefined; var callGraph = undefined; var totalTimeMS = undefined; if (processSnapshots) { processSnapshotGUIDs = Object.keys(processSnapshots); if (processSnapshotGUIDs.length) { callGraph = self.createCallGraph(transaction); } } if (!callGraph) { totalTimeMS = transaction.ms; } snapshotInfo = { trigger: snapshotTriggerObject.snapshotTrigger, snapshot: { snapshotGUID: transaction.guid, timestamp: transaction.ts, callGraph: callGraph, errorInfo: errorInfo, exceptionInfo: exceptionInfo, processID: process.pid, exitCalls: self.createSnapshotExitCalls(transaction), totalTimeMS: totalTimeMS, upstreamCrossAppSnapshotGUID: transaction.incomingCrossAppGUID } }; if (transaction.httpRequestSnapshotData) { snapshotInfo.snapshot.httpRequestData = transaction.httpRequestSnapshotData; snapshotInfo.snapshot.httpRequestData.requestMethod = transaction.method; snapshotInfo.snapshot.httpRequestData.responseCode = transaction.statusCode; } if(transaction.eumGuid) { snapshotInfo.snapshot.eumGUID = transaction.eumGuid; } processSnapshots = transaction.processSnapshots; if (processSnapshots) { snapshotInfo.snapshot.processSnapshotGUIDs = Object.keys(processSnapshots); } if(transaction.api) { if(transaction.api.onSnapshotCaptured) { transaction.api.onSnapshotCaptured(transaction.api); } if(transaction.api.snapshotData && transaction.api.snapshotData.length) { snapshotInfo.snapshot.methodInvocationData = (snapshotInfo.snapshot.methodInvocationData || []) .concat(transaction.api.snapshotData); } } } return snapshotInfo; }; ProtobufModel.prototype.createSnapshot = function(transaction) { var self = this; var errorInfo = self.createErrorInfo(transaction); var exceptionInfo = self.createExceptionInfo(transaction); var processSnapshots = transaction.processSnapshots; var processSnapshotGUIDs = undefined; var snapshotInfo = self.createSnapshotInfo(transaction, errorInfo, exceptionInfo); if (processSnapshots) { processSnapshotGUIDs = Object.keys(processSnapshots); if (processSnapshotGUIDs.length) { //callGraph = self.createCallGraph(transaction); } } if (transaction.httpRequestData) { snapshotInfo.snapshot.httpRequestData = transaction.httpRequestData; } if(transaction.eumGuid) { snapshotInfo.snapshot.eumGUID = transaction.eumGuid; } processSnapshots = transaction.processSnapshots; if (processSnapshots) { snapshotInfo.snapshot.processSnapshotGUIDs = Object.keys(processSnapshots); } return snapshotInfo; }; ProtobufModel.prototype.createErrorInfo = function() { // will be reused for console.log and console.error messages var self = this; if(!self.detectErrors) { return undefined; } var errorInfo = { errors: [] }; if(errorInfo.errors.length > 0) { return errorInfo; } return undefined; }; ProtobufModel.prototype.constructStackTrace = function(stackTraceStr) { var self = this; var stackTrace = { elements: [] }; if(stackTraceStr && typeof(stackTraceStr) === 'string') { stackTraceStr = stackTraceStr.replace(/\(anonymous\sfunction\)/, '<anonymous>'); var lines = stackTraceStr.split("\n"); lines.shift(); lines.forEach(function(line) { var traceMatch = self.traceRegex.exec(line); var userCodeErrMatch = self.userCodeErrRegEx.exec(line); if(traceMatch && traceMatch.length == 5) { var klass, method, classMatch, drivePrefix; classMatch = self.classRegex.exec(traceMatch[1]); if(classMatch && classMatch.length == 3) { klass = classMatch[1]; method = classMatch[2]; } else { klass = ''; method = traceMatch[1]; } if (!self.agentRegex.exec(traceMatch[3])) { drivePrefix = traceMatch[2] || ''; stackTrace.elements.push({ klass: klass, method: method, fileName: drivePrefix + traceMatch[3], lineNumber: parseInt(traceMatch[4]) }); } } else if (userCodeErrMatch && userCodeErrMatch.length == 4) { if (!self.agentRegex.exec(userCodeErrMatch[2])) { drivePrefix = userCodeErrMatch[1] || ''; stackTrace.elements.push({ klass: '', method: '', fileName: drivePrefix + userCodeErrMatch[2], lineNumber: parseInt(userCodeErrMatch[3]) }); } } }); } return stackTrace; }; ProtobufModel.prototype.createExceptionInfo = function(transaction, skipIgnored) { var self = this; var exceptionsMap = {}; var exceptionInfo = { exceptions: [], stackTraces: [] }; var errorSources = []; if (transaction.error) errorSources.push(transaction); if (transaction.exitCalls) errorSources = errorSources.concat(transaction.exitCalls); errorSources.forEach(function(errorSource) { if(!errorSource.error) return; var message = self.extractErrorMessage(errorSource.error); var rootException = exceptionsMap[message]; if(rootException) { rootException.count++; return; } var ignored = self.isExceptionIgnored(errorSource.error.name, message); if (!ignored) transaction.hasErrors = true; if (ignored && skipIgnored) { return; } var stackTrace = self.constructStackTrace(errorSource.error.stack); // exception rootException = { root: { klass: (errorSource.backendName && errorSource.backendName + ' Error') || errorSource.error.name || 'Unknown Error', message: message, stackTraceID: exceptionInfo.stackTraces.length }, count: 1 }; exceptionInfo.stackTraces.push(stackTrace); exceptionInfo.exceptions.push(rootException); exceptionsMap[message] = rootException; }); if(exceptionInfo.exceptions.length > 0) { return exceptionInfo; } return undefined; }; ProtobufModel.prototype.isErrorIgnored = function(message) { var self = this; if(!self.ignoredMessagesConfig) { return false; } var ignore = false; self.ignoredMessagesConfig.forEach(function(ignoredMessageConfig) { if(self.agent.stringMatcher.matchString(ignoredMessageConfig, message)) { ignore = true; } }); return ignore; }; ProtobufModel.prototype.isExceptionIgnored = function(name, message) { var self = this; if(!self.ignoredExceptionsConfig) { return false; } var ignore = false; self.ignoredExceptionsConfig.forEach(function(ignoredExceptionConfig) { var match = ignoredExceptionConfig.classNames[0]; if ((match == name || match == '*') && self.agent.stringMatcher.matchString( ignoredExceptionConfig.matchCondition, message)) { ignore = true; } }); return ignore; }; /* If there is one or more process snapshots linked to this transaction then make a fake call graph such that the UI will indicate the BT snapshot for this transaction has call graph data ( aka deep dive data ). */ ProtobufModel.prototype.createCallGraph = function(transaction) { var callGraph = { callElements: [ { timeTaken: transaction.ms, numOfChildren: 0, name: '{name}', type: 'JS', method: '{request}' } ] }; return callGraph; }; ProtobufModel.prototype.createSnapshotExitCalls = function(transaction) { var self = this; var exitCalls = []; var exitCallsMap = {}; if(transaction.exitCalls) { transaction.exitCalls.forEach(function(exitCall) { var snapshotExitCallId; var snapshotExitCall; if (exitCall.isSql) { snapshotExitCallId = exitCall.backendName + ':' + exitCall.command; snapshotExitCall = exitCallsMap[snapshotExitCallId]; if(snapshotExitCall) { snapshotExitCall.count++; snapshotExitCall.timeTaken += exitCall.ms; return; } } snapshotExitCall = { backendIdentifier: exitCall.backendIdentifier, sequenceInfo: exitCall.sequenceInfo, timeTaken: exitCall.ms, count: 1, errorDetails: self.extractErrorMessage(exitCall.error), detailString: exitCall.command, boundParameters: undefined }; if(exitCall.isSql && exitCall.commandArgs) { snapshotExitCall.boundParameters = { type: 'POSITIONAL', posParameters: exitCall.commandArgs }; } exitCalls.push(snapshotExitCall); if((!exitCall.commandArgs) && (exitCall.isSql)) { exitCallsMap[snapshotExitCallId] = snapshotExitCall; } }); } if(exitCalls.length > 0) { return exitCalls; } return undefined; }; ProtobufModel.prototype.extractErrorData = function(error, field) { if(typeof(error) == 'string') { return error; } else if(typeof(error) == 'object' && error !== null) { return error[field]; } return undefined; }; ProtobufModel.prototype.extractErrorMessage = function(error) { var self = this; return self.extractErrorData(error, 'message'); }; ProtobufModel.prototype.extractErrorName = function(error) { var self = this; return self.extractErrorData(error, 'name'); };