UNPKG

appdynamics

Version:

Performance Profiler and Monitor

200 lines (166 loc) 6.66 kB
/* Copyright (c) AppDynamics, Inc., and its affiliates 2015 All Rights Reserved */ 'use strict'; var HttpCommon = require('./http-common'); function Http2EntryProbe(agent) { this.agent = agent; this.statusCodesConfig = undefined; this.delayedCallbackQueue = []; } exports.Http2EntryProbe = Http2EntryProbe; Http2EntryProbe.prototype.init = function () { }; Http2EntryProbe.prototype.attach = function (obj) { var self = this; self.agent.timers.startTimer(100, true, function () { var now = Date.now(); while (self.delayedCallbackQueue.length > 0) { if (self.delayedCallbackQueue[0].ts < now - 10) { var delayedCallbackInfo = self.delayedCallbackQueue.shift(); delayedCallbackInfo.func.call(this); } else { break; } } }); self.agent.proxy.after(obj, ['createSecureServer', 'createServer'], function (obj, args, ret) { self.agent.proxy.before(ret, ['on', 'addListener'], function(obj, args) { if (args[0] !== 'stream') return; var cbIndex = args.length - 1; args[cbIndex] = self.__createRequestHandler(args[cbIndex]); }); }); }; function createBTCallback(agent, profiler, time, transaction, thread, callback, self, origSelf, req, stream, headers) { var didRun = false; var threadId = thread.current(); return self.agent.context.bind(function () { if (didRun) return; didRun = true; var oldThreadId = thread.current(); thread.resume(threadId); try { callback = agent.proxy.wrapWithThreadProxyIfEnabled(callback); callback.call(origSelf, stream, headers); } catch (e) { self.finalizeTransaction(e, profiler, time, transaction, req, stream); throw e; } finally { thread.resume(oldThreadId); } }); } function buildEumCookie(agent, transaction, responseHeaders, request) { var response = {headers: responseHeaders, setHeader: function(key, value) { this.headers[key] = value; }, getHeader: function(key) { return this.headers[key]; }}; var eumCookie = agent.eum.newEumCookie(transaction, request, response, request.headers[":scheme"] == 'https'); eumCookie.build(); } Http2EntryProbe.prototype.__createRequestHandler = function (callback) { var self = this; return function (stream, headers) { self.agent.context.run(requestHandler, stream); function requestHandler(stream) { var proxy = self.agent.proxy; var profiler = self.agent.profiler; var time = profiler.time(true); self.agent.metricsManager.addMetric(self.agent.metricsManager.HTTP_INCOMING_COUNT, 1); var req = {url: headers[":path"], method: headers[":method"], headers: headers}; var transaction = profiler.startTransaction(time, req, 'NODEJS_WEB'); self.agent.context.set('threadId', transaction.threadId); req.__appdThreadId = transaction.threadId; transaction.url = req.url; transaction.method = req.method; var eumEnabled = (transaction.eumEnabled && !transaction.skip); if (!transaction.corrHeader && eumEnabled) { proxy.before(stream, ['respond'], function (obj, args) { if(!transaction.isFinished) { if (!args.length || !args[0] || stream.headersSent) { return; } var responseHeaders = args[0]; req["EumHeadersSet"] = true; buildEumCookie(self.agent, transaction, responseHeaders, req); } }); proxy.before(stream, ['respondWithFile', 'respondWithFD'], function (obj, args) { if(!transaction.isFinished) { if (args.length < 2 || !args[1] || stream.headersSent) { return; } var responseHeaders = args[1]; req["EumHeadersSet"] = true; buildEumCookie(self.agent, transaction, responseHeaders, req); } }); var handle = proxy.getSymbolProperty(stream, 'handle'); if (handle) { proxy.before(handle, 'respond', function(obj, args) { if (!args[0] || args[0].length != 2 || req["EumHeadersSet"]) { return; } var headersList = args[0]; var headers = headersList[0]; var count = headersList[1]; var responseHeaders = {}; buildEumCookie(self.agent, transaction, responseHeaders, req); // we need to perform the same conversion that the http2 module does // the following conversion is based on lib/http2/util.js mapToHeaders() const keys = Object.keys(responseHeaders); for (var i = 0; i < keys.length; ++i) { var key = keys[i]; var value = responseHeaders[key]; headers += `${key}\0${value}\0`; count++; } headersList[0] = headers; headersList[1] = count; }); } } proxy.after(stream, 'end', function (obj, args, ret) { self.finalizeTransaction(null, profiler, time, transaction, req, ret); transaction = null; }); if (self.agent.opts.btEntryPointDelayDisabled) { try { return callback.call(this, stream, headers); } catch (e) { self.finalizeTransaction(e, profiler, time, transaction, req, null); throw e; } } var delayedCallback = createBTCallback(self.agent, profiler, time, transaction, self.agent.thread, callback, self, this, req, stream, headers); transaction.once('ignoreTransactionCbExecute', delayedCallback); transaction.emit('delayedCallbackReady'); transaction.once('btInfoResponse', delayedCallback); self.delayedCallbackQueue.push({ ts: Date.now(), func: delayedCallback }); } }; }; Http2EntryProbe.prototype.finalizeTransaction = function (err, profiler, time, transaction, req, stream) { if (req['done'] || !time.done()) return; req['done'] = true; transaction.error = transaction.error || err; transaction.statusCode = transaction.statusCode || transaction.error && transaction.error.statusCode || stream && stream.sentHeaders && stream.sentHeaders[":status"] || 200; transaction.stackTrace = transaction.stackTrace || profiler.formatStackTrace(transaction.error); var error = HttpCommon.generateError(transaction.error, transaction.statusCode, this.statusCodesConfig); if (error) { transaction.error = error; } if (transaction.api && transaction.api.onResponseComplete) { transaction.api.onResponseComplete.apply(transaction.api, [stream]); } profiler.endTransaction(time, transaction); };