UNPKG

appdynamics

Version:

Performance Profiler and Monitor

195 lines (174 loc) 6.62 kB
/* Copyright (c) AppDynamics, Inc., and its affiliates 2015 All Rights Reserved */ 'use strict'; // Interceptor(Capture threadID) ---> Async Handler(Execute in the threadID context of the interceptor) // In MySql probe. Query method is the main interceptor // In Pool mode: // Query method interceptor on poolClient ---> aync query handling on mysql connection // ---> async handling of the poolClient query cb/promise // In Non-pool mode: // Query method interceptor on mysqlConnection ---> async handling of the query's cb/promise function MysqlProbe(agent) { this.agent = agent; this.packages = ['mysql', 'mysql2']; } exports.MysqlProbe = MysqlProbe; function prepareExitCall(config, client, agent) { var context = agent.context; var profiler = agent.profiler; var proxy = agent.proxy; var thread = agent.thread; var supportedProperties = { 'HOST': config.host, 'PORT': config.port, 'DATABASE': config.database, 'VENDOR': 'MYSQL' }; if (client.query.__appdynamicsProbeAttached__) return; proxy.around(client, 'query', function(obj, args, locals) { var command, params; var trace = profiler.stackTrace(); function processQueryCb(obj, args) { complete(args, locals); } function createExitCall() { locals.exitCall = profiler.createExitCall(locals.time, { exitType: 'EXIT_DB', supportedProperties: supportedProperties, command: profiler.sanitize(command), commandArgs: profiler.sanitize(params), user: config.user, stackTrace: trace, isSql: true }); } if (args[0] && args[0].__appdCbFnHolder) { thread.resume(args[0].__appdCbFnHolder.queryThreadId); locals.time = profiler.time(); command = args[0].sql; params = args[0].values; locals.methodHasCb = true; createExitCall(); proxy.before(args[0].__appdCbFnHolder, 'triggerOnCb', processQueryCb, false, false, args[0].__appdCbFnHolder.queryThreadId); } else { locals.time = profiler.time(); command = args.length > 0 ? args[0] : undefined; params = args.length > 1 && Array.isArray(args[1]) ? args[1] : undefined; createExitCall(); // mysql driver breaks CLS continuity so we must explicitly bind callback: args[args.length - 1] = context.bind(args[args.length - 1]); locals.methodHasCb = proxy.callback(args, -1, processQueryCb, null, thread.current()); } }, after, false); client.query.__appdynamicsProbeAttached__ = true; function after(obj, args, ret, locals) { if (locals.methodHasCb) return; if (!ret || !ret.__appdynamicsIsPromiseResult__) complete(null, locals); else if (ret.error) complete(ret.error, locals); else { complete(null, locals); } } function complete(err, locals) { if (!locals.exitCall) return; if (!locals.time.done()) return; var error = proxy.getErrorObject(err); profiler.addExitCall(locals.time, locals.exitCall, error); } } MysqlProbe.prototype.attach = function(obj, name) { var self = this; var proxy = self.agent.proxy; var profiler = self.agent.profiler; var cmds = ['createClient', 'createConnection', 'createPool']; var mysqlConnection; if(process.env.APPDYNAMICS_MYSQL_PROBE_DISABLE == true || process.env.APPDYNAMICS_MYSQL_PROBE_DISABLE == 'true') { return; } if(obj.__appdynamicsProbeAttached__) return; obj.__appdynamicsProbeAttached__ = true; self.agent.on('destroy', function() { if(obj.__appdynamicsProbeAttached__) { delete obj.__appdynamicsProbeAttached__; cmds.forEach(function(createCmd) { if (obj[createCmd]) proxy.release(obj[createCmd]); }); if (mysqlConnection) delete mysqlConnection.query.__appdynamicsProbeAttached__; } }); if (name === 'mysql2' && obj.Connection && obj.Connection.createQuery) { proxy.after(obj.Connection, 'createQuery', function(obj, args, ret) { if (typeof (ret.then) === 'function') { // Disable mysql2's error logging when trying to treat // non-promise query as promise ret.then = ret.catch = null; } }); } function queryCbFnThreadCtxtSwitch(queryArgs, cbIndex, queryLocals) { // mysql driver breaks CLS continuity so we must explicitly bind callback: queryArgs[cbIndex] = self.agent.context.bind(queryArgs[cbIndex]); proxy.before(queryArgs, cbIndex, function() { queryLocals.triggerOnCb(); }, false, false); } cmds.forEach(function(createCmd) { proxy.after(obj, createCmd, function(obj, args, ret) { if (createCmd === 'createPool') { proxy.around(ret, 'getConnection', function(obj, args, locals) { if (!args) return; var cbIndex = args.length -1; if (!(typeof args[cbIndex] === 'function')) return; args[cbIndex] = self.agent.context.bind(args[cbIndex]); var callback = self.agent.context.bind(function(obj, args) { if (args[0]) return; if (args[1]) { mysqlConnection = args[1]; var config = mysqlConnection.config; prepareExitCall(config, mysqlConnection, self.agent); } }); locals.methodHasCb = proxy.callback(args, -1, callback); }, function(obj, args, ret, locals) { if (locals.methodHasCb) return; if (!ret || !ret.__appdynamicsIsPromiseResult__) return; if (ret.error) return; else { mysqlConnection = ret.data; var config = mysqlConnection.config; prepareExitCall(config, mysqlConnection, self.agent); } }); proxy.around(ret, 'query', function(obj, args, locals) { locals.triggerOnCb = function() { self.agent.logger.debug('Placeholder function'); }; locals.queryThreadId = profiler.time().threadId; if (typeof args[0] === 'function') { queryCbFnThreadCtxtSwitch(args, '0', locals); } else if (typeof args[1] === 'function') { queryCbFnThreadCtxtSwitch(args, '1', locals); } else { queryCbFnThreadCtxtSwitch(args, '2', locals); } }, function(obj, args, query, locals) { query.__appdCbFnHolder = locals; }); } else { mysqlConnection = ret; var config = (createCmd === 'createClient' ? mysqlConnection : mysqlConnection.config); if (!config) { return; } prepareExitCall(config, mysqlConnection, self.agent); } }); }); };