UNPKG

appdynamics

Version:

Performance Profiler and Monitor

788 lines (624 loc) 21.2 kB
/* Copyright (c) AppDynamics, Inc., and its affiliates 2015 All Rights Reserved */ var { v4: uuid } = require('uuid'); var APPD_INTERNAL_RE = /\/appdynamics\//; function ProcessScanner(agent) { this.agent = agent; this.autoSnapshotDurationSeconds = 10; this.snapshotCount = 0; this.maxSnapshotsPerPeriod = 2; this.snapshotCountResetPeriodMS = 1 * 60 * 1000; this.snapshotsResetIntervalId = undefined; this.currentProcessSnapshot = undefined; this.lastProcessRequestId = undefined; this.lastSnapshotRequestID = undefined; this.btCalls = null; } exports.ProcessScanner = ProcessScanner; ProcessScanner.prototype.init = function () { var self = this; self.lastSnapshotRequestID = 0; self.agent.timers.setInterval(function() { self.lastSnapshotRequestID = 0; }, 300000); var snapshotsPerPeriod = self.agent.opts.maxProcessSnapshotsPerPeriod; if (snapshotsPerPeriod !== undefined) { snapshotsPerPeriod = +snapshotsPerPeriod; if (snapshotsPerPeriod >= 0) { self.maxSnapshotsPerPeriod = snapshotsPerPeriod; } } self.agent.logger.info("ProcessScanner - maxSnapshotsPerPeriod: " + self.maxSnapshotsPerPeriod); var resetPeriod = +(self.agent.opts.processSnapshotCountResetPeriodSeconds); if (resetPeriod && (resetPeriod > 0)) { self.snapshotCountResetPeriodMS = (+resetPeriod) * 1000; } self.agent.logger.info("ProcessScanner - snapshotCountResetPeriodMS: " + self.snapshotCountResetPeriodMS); var snapshotDuration = +(self.agent.opts.autoSnapshotDurationSeconds); if (snapshotDuration && (snapshotDuration > 0) && (snapshotDuration < 300)) { self.autoSnapshotDurationSeconds = snapshotDuration; } self.agent.logger.info("ProcessScanner - autoSnapshotDurationSeconds: " + self.autoSnapshotDurationSeconds); self.agent.on('transactionStarted', function(transaction) { if (!self.capturingProcessSnapshot) return; var threadId = transaction.threadId; var btCallInfo = self.btCalls[threadId] || (self.btCalls[threadId] = {}); btCallInfo.startTime = self.agent.system.hrtime(); btCallInfo.registrationId = transaction.registrationId; transaction.addProcessSnapshotGUID(self.currentProcessSnapshot.guid); }); self.agent.on('transaction', function(transaction) { if (!self.capturingProcessSnapshot) return; transaction.addProcessSnapshotGUID(self.currentProcessSnapshot.guid); var threadId = transaction.threadId; var btCallInfo = self.btCalls[threadId] || (self.btCalls[threadId] = { startTime: undefined }); btCallInfo.registrationId = btCallInfo.registrationId || transaction.registrationId; btCallInfo.endTime = self.agent.system.hrtime(); }); }; Object.defineProperty(ProcessScanner.prototype, 'capturingProcessSnapshot', { get: function(){ return !!this.currentProcessSnapshot; } }); function ensureInterval(self) { if (self.snapshotsResetIntervalId) return; self.snapshotsResetIntervalId = self.agent.timers.setInterval(function () { self.snapshotCount = 0; }, self.snapshotCountResetPeriodMS); } ProcessScanner.prototype.doErrorCallback = function(msg, callback) { if (!callback) { return; } var err = new Error(msg); process.nextTick(function() { callback(err, null); }); }; ProcessScanner.prototype.startAutoSnapshotIfPossible = function (callback) { var self = this, msg; if (self.capturingProcessSnapshot) { msg = 'Process snapshot already in progress, not starting another one!'; self.agent.logger.info(msg); self.doErrorCallback(msg, callback); return; } if (self.snapshotCount >= self.maxSnapshotsPerPeriod) { msg = 'Already did ' + self.snapshotCount + ' process snapshots in ' + self.snapshotCountResetPeriodMS + 'ms.'; self.agent.logger.info(msg); self.doErrorCallback(msg, callback); return; } self.startSnapshot(true, self.autoSnapshotDurationSeconds, -1, callback); }; ProcessScanner.prototype.startManualSnapshot = function(processCallGraphReq, callback) { var self = this, requestID, msg; requestID = +processCallGraphReq.snapshotRequestID; if(requestID <= self.lastSnapshotRequestID) { msg = 'snapshotRequestID ' + processCallGraphReq.snapshotRequestID + ' was already processed, ignoring.'; self.agent.logger.info(msg); self.doErrorCallback(msg, callback); return; } self.lastSnapshotRequestID = requestID; if (self.capturingProcessSnapshot) { msg = 'Process snapshot capture already in progress; snapshotRequestID ' + processCallGraphReq.snapshotRequestID + ' ignored.'; self.agent.logger.info(msg); self.doErrorCallback(msg, callback); return; } self.startSnapshot(false, processCallGraphReq.captureTime, requestID, callback); }; ProcessScanner.prototype.__populatePendingTransactions = function() { var self = this; var startTime = self.agent.system.hrtime(); var transactionsMap = self.agent.profiler.transactions; self.btCalls = {}; for(var threadId in transactionsMap) { var btCallInfo = self.btCalls[threadId] || ( self.btCalls[threadId] = {} ); btCallInfo.startTime = startTime; btCallInfo.registrationId = btCallInfo.registrationId || transactionsMap[threadId].registrationId; } }; ProcessScanner.prototype.__btCallsToProto = function(btCalls) { var self = this; var result = []; var registrationIDToIndex = {}; var now = self.agent.system.hrtime(); for (var threadId in btCalls) { var btCallInfo = btCalls[threadId]; if (btCallInfo.startTime === undefined) continue; if (!btCallInfo.registrationId) continue; if (btCallInfo.endTime === undefined) btCallInfo.endTime = now; var index = registrationIDToIndex[btCallInfo.registrationId]; if (index === undefined) { index = result.length; registrationIDToIndex[btCallInfo.registrationId] = index; } var protoBTCallInfo = result[index] || ( result[index] = { btID: btCallInfo.registrationId, count: 0, totalTimeTakenMS: 0 } ); protoBTCallInfo.count++; protoBTCallInfo.totalTimeTakenMS += Math.max(0, (btCallInfo.endTime - btCallInfo.startTime) / 1000.0); } return result; }; ProcessScanner.prototype.startSnapshot = function (auto, durationSeconds, requestID, callback) { var self = this, msg; if(process.version.match(/^v0\.12\.[1234]$/) && !self.agent.opts.ignoreV8SamplerBug) { msg = "CPU profiling is not supported in Node.js v0.12.x due to V8 bug."; self.doErrorCallback(msg, callback); return; } if (self.capturingProcessSnapshot) { msg = "Process snapshot already started, not starting another."; self.doErrorCallback(msg, callback); return; } self.agent.backendConnector.startProcessSnapshot(); self.currentProcessSnapshot = { snapshotRequestID: requestID, guid: uuid(), timestamp: undefined, processCallGraph: undefined, processAllocationGraph: undefined, processID: process.pid, btCalls: undefined }; self.__populatePendingTransactions(); self.snapshotCount++; ensureInterval(self); self.agent.proxy.enableCallContext(); if (self.agent.nsolidEnabled && self.agent.asyncActivity) { self.agent.asyncActivity(function(err, activity) { if (err) { self.logger.warn("Unable to fetch N|Solid metadata: " + err); } activity = activity.filter(function(entry) { return !APPD_INTERNAL_RE.exec(entry.location.file); }); self.currentProcessSnapshot.asyncActivity = JSON.stringify(activity); collect(); }); } else { process.nextTick(collect); } function collect() { try { var threadId = self.agent.thread.nextId - 1; self.agent.cpuProfiler.startCpuProfiler(durationSeconds, function(err, processCallGraph) { try { if(err) { callback(err, null); return; } var btCalls = self.__btCallsToProto(self.btCalls); var processSnapshot = self.currentProcessSnapshot; processSnapshot.timestamp = Date.now(); processSnapshot.processCallGraph = processCallGraph; processSnapshot.btCalls = btCalls; self.lastSnapshotRequestID = requestID; self.postProcessCallGraph(processSnapshot, self.agent.proxy.getCallContextMap(), durationSeconds); callback(null, processSnapshot); } finally { self.btCalls = null; self.currentProcessSnapshot = undefined; self.agent.proxy.disableCallContext(); } }, threadId); if(!auto) { try { if(self.agent.heapProfiler.isObjectTrackingSupported()) { self.agent.logger.info("ProcessScanner - Starting heap allocation tracking..."); self.agent.heapProfiler.trackAllocations(durationSeconds - 1, function(err, processAllocationGraph) { if(err) { return callback(err, null); } if(self.currentProcessSnapshot) { self.currentProcessSnapshot.processAllocationGraph = processAllocationGraph; self.postProcessAllocationGraph(self.currentProcessSnapshot, self.agent.proxy.getCallContextMap()); } }); } else { self.agent.logger.warn("ProcessScanner - Heap allocation tracking is not supported."); } } catch(err) { self.agent.logger.warn("ProcessScanner - Heap allocation tracking failed."); self.agent.logger.warn(err); } } self.agent.profiler.processSnapshotStarted(self.currentProcessSnapshot); } catch(err) { self.btCalls = null; self.currentProcessSnapshot = undefined; self.agent.proxy.disableCallContext(); callback(err, null); return; } } }; ProcessScanner.prototype.convertListToTree = function(elements) { var nodeQueue = []; var rootNode = elements.shift(); nodeQueue.push(rootNode); while(nodeQueue.length > 0) { var node = nodeQueue.shift(); node.children = []; for(var i = 0; i < node.numChildren; i++) { var childNode = elements.shift(); node.children.push(childNode); nodeQueue.push(childNode); } delete node.numChildren; } return rootNode; }; ProcessScanner.prototype.convertTreeToList = function(rootNode) { var elements = []; var nodeQueue = []; nodeQueue.push(rootNode); while(nodeQueue.length > 0) { var node = nodeQueue.shift(); node.children.forEach(function(childNode) { nodeQueue.push(childNode); }); node.numChildren = node.children.length; delete node.children; elements.push(node); } return elements; }; ProcessScanner.prototype.calculateCpuTime = function(rootNode) { var id = 1; var parentNodeMap = {}; var nodeQueue = []; var reverseQueue = []; rootNode._id = id++; nodeQueue.push(rootNode); while(nodeQueue.length > 0) { var node = nodeQueue.shift(); reverseQueue.unshift(node); for(var i = node.children.length - 1; i >= 0; i--) { node.children[i]._id = id++; parentNodeMap[node.children[i]._id] = node; nodeQueue.unshift(node.children[i]); } } reverseQueue.forEach(function(node) { var parentNode = parentNodeMap[node._id]; if(parentNode) { parentNode.samplesCount += node.samplesCount; } }); var samplingRate = 1; //ms reverseQueue.forEach(function(node) { node.timeTaken = node.samplesCount * samplingRate; }); reverseQueue.forEach(function(node) { delete node._id; delete node.samplesCount; }); }; ProcessScanner.prototype.populateCallContext = function(rootNode, callContextMap) { var i; var nodeQueue = [rootNode]; var appdCallbackPrefix = /appd_proxy_/; while(nodeQueue.length > 0) { var node = nodeQueue.shift(); if(node.method) { var callContext = callContextMap[node.method]; if(callContext) { node.btId = callContext.btId; if(callContext.snapshotGuid) { node.snapshotGuid = callContext.snapshotGuid; } } } if(appdCallbackPrefix.exec(node.method)) { // don't need thread specific nodes anymore node.method = 'appd_proxy_x'; node.lineNumber = 1; } for(i = node.children.length - 1; i >= 0; i--) { var childNode = node.children[i]; if(node.btId) { childNode.btId = node.btId; } if(node.snapshotGuid) { childNode.snapshotGuid = node.snapshotGuid; } nodeQueue.unshift(childNode); } } }; ProcessScanner.prototype.addCallBtData = function(firstNode, childNode) { var btData; if(childNode.btId) { if(!firstNode.procCallElemBTData) { firstNode.procCallElemBTData = []; } else { firstNode.procCallElemBTData.forEach(function(existingBtData) { if(existingBtData.btID === childNode.btId && existingBtData.btSnapGUID === childNode.snapshotGuid) { btData = existingBtData; } }); } if(btData) { btData.totalTimeTaken += childNode.timeTaken; btData.count++; } else { btData = { btID: childNode.btId, totalTimeTaken: childNode.timeTaken, count: 1 }; if(childNode.snapshotGuid) { btData.btSnapGUID = childNode.snapshotGuid; } firstNode.procCallElemBTData.push(btData); } delete childNode.btId; delete childNode.snapshotGuid; } }; ProcessScanner.prototype.addAllocationBtData = function(firstNode, childNode) { var btData; if(childNode.btId) { if(!firstNode.procAllocationElemBTData) { firstNode.procAllocationElemBTData = []; } else { firstNode.procAllocationElemBTData.forEach(function(existingBtData) { if(existingBtData.btID === childNode.btId && existingBtData.btSnapGUID === childNode.snapshotGuid) { btData = existingBtData; } }); } if(btData) { btData.totalSize += childNode.size; btData.count += childNode.count; } else { btData = { btID: childNode.btId, totalSize: childNode.size, count: childNode.count }; if(childNode.snapshotGuid) { btData.btSnapGUID = childNode.snapshotGuid; } firstNode.procAllocationElemBTData.push(btData); } delete childNode.btId; delete childNode.snapshotGuid; } }; ProcessScanner.prototype.mergeThreadPaths = function(rootNode, heapMode) { var self = this; var i; var nodeQueue = []; nodeQueue.push(rootNode); while(nodeQueue.length > 0) { var node = nodeQueue.shift(); var nodeMap = {}; for(i = node.children.length - 1; i >= 0; i--) { var childNode = node.children[i]; var sig = [ childNode.klass, childNode.method, childNode.lineNumber, childNode.fileName ].join(':'); var firstNode = nodeMap[sig]; if(!firstNode) { nodeMap[sig] = childNode; if(!heapMode) { self.addCallBtData(childNode, childNode); } else { self.addAllocationBtData(childNode, childNode); } nodeQueue.unshift(childNode); } else { node.children.splice(i, 1); if(!heapMode) { firstNode.timeTaken += childNode.timeTaken; self.addCallBtData(firstNode, childNode); } else { firstNode.size += childNode.size; firstNode.count += childNode.count; self.addAllocationBtData(firstNode, childNode); } childNode.children.forEach(function(childChildNode) { firstNode.children.push(childChildNode); }); } } } }; ProcessScanner.prototype.removeIdleNode = function(rootNode) { for(var i = 0; i < rootNode.children.length; i++) { if(rootNode.children[i].method === '(program)') { rootNode.children.splice(i, 1); break; } } }; ProcessScanner.prototype.removeAgentNodes = function(rootNode) { var i; var nodeQueue = []; nodeQueue.push(rootNode); while(nodeQueue.length > 0) { var node = nodeQueue.shift(); var childNodeQueue = node.children; node.children = []; while(childNodeQueue.length > 0) { var childNode = childNodeQueue.shift(); if(APPD_INTERNAL_RE.exec(childNode.fileName)) { for(i = childNode.children.length - 1; i >= 0; i--) { childNodeQueue.unshift(childNode.children[i]); } } else { node.children.push(childNode); } } for(i = node.children.length - 1; i >= 0; i--) { nodeQueue.unshift(node.children[i]); } } }; ProcessScanner.prototype.backpropagateCallContext = function(rootNode, heapMode) { var procProp; if(!heapMode) { procProp = 'procCallElemBTData'; } else { procProp = 'procAllocationElemBTData'; } function createBtDataUid(btData) { var btDataKey = btData.btID; if(btData.btSnapGUID) { btDataKey += ':' + btData.btSnapGUID; } return btDataKey; } function cloneBtData(btData) { var btDataCopy = { btID: btData.btID, count: btData.count, totalTimeTaken: btData.totalTimeTaken }; if(!heapMode) { btDataCopy.totalTimeTaken = btData.totalTimeTaken; } else { btDataCopy.totalSize = btData.totalSize; } if(btData.btSnapGUID) { btDataCopy.btSnapGUID = btData.btSnapGUID; } return btDataCopy; } var i; var id = 1; var parentNodeMap = {}; var btDataMap = {}; var nodeQueue = []; var reverseQueue = []; rootNode._id = id++; nodeQueue.push(rootNode); while(nodeQueue.length > 0) { var node = nodeQueue.shift(); reverseQueue.unshift(node); if(node[procProp]) { node[procProp].forEach(function(btData) { btDataMap[node._id + ':' + createBtDataUid(btData)] = btData; }); } for(i = node.children.length - 1; i >= 0; i--) { var childNode = node.children[i]; childNode._id = id++; parentNodeMap[childNode._id] = node; nodeQueue.unshift(childNode); } } reverseQueue.forEach(function(node) { var parentNode = parentNodeMap[node._id]; if(node[procProp] && parentNode) { if(!parentNode[procProp]) { parentNode[procProp] = []; } node[procProp].forEach(function(btData) { var parentBtDataKey = parentNode._id + ':' + createBtDataUid(btData); var parentBtData = btDataMap[parentBtDataKey]; if(!parentBtData) { parentBtData = cloneBtData(btData); parentBtData._synthetic = true; btDataMap[parentBtDataKey] = parentBtData; parentNode[procProp].push(parentBtData); } else if (parentBtData._synthetic) { parentBtData.count += btData.count; parentBtData.totalTimeTaken += btData.totalTimeTaken; } }); } }); reverseQueue.forEach(function(node) { delete node._id; if(node[procProp]) { node[procProp].forEach(function(btData) { delete btData._synthetic; }); } }); }; ProcessScanner.prototype.postProcessCallGraph = function(processSnapshot, callContextMap, durationSeconds) { var self = this; var callElements = processSnapshot.processCallGraph.callElements; if(callElements && callElements.length > 0) { var rootNode = self.convertListToTree(callElements); self.calculateCpuTime(rootNode, durationSeconds); self.populateCallContext(rootNode, callContextMap); self.mergeThreadPaths(rootNode); self.removeIdleNode(rootNode); if(self.agent.opts.excludeAgentFromCallGraph) { self.removeAgentNodes(rootNode); } self.backpropagateCallContext(rootNode); callElements = self.convertTreeToList(rootNode); // remove root node var root = callElements[0]; if (root && root.klass == "(global)" && root.method == "(root)") { processSnapshot.processCallGraph.numOfRootElements = root.numChildren; callElements.splice(0, 1); } processSnapshot.processCallGraph.callElements = callElements; } }; ProcessScanner.prototype.postProcessAllocationGraph = function(processSnapshot, callContextMap) { var self = this; var allocationElements = processSnapshot.processAllocationGraph.allocationElements; if(allocationElements && allocationElements.length > 0) { var rootNode = self.convertListToTree(allocationElements); self.populateCallContext(rootNode, callContextMap); self.mergeThreadPaths(rootNode, true); if(self.agent.opts.excludeAgentFromCallGraph) { self.removeAgentNodes(rootNode); } self.backpropagateCallContext(rootNode, true); allocationElements = self.convertTreeToList(rootNode); processSnapshot.processAllocationGraph.allocationElements = allocationElements; processSnapshot.processAllocationGraph.numOfRootElements = 1; } };