UNPKG

appdynamics

Version:

Performance Profiler and Monitor

177 lines (156 loc) 5.98 kB
/* * Copyright (c) AppDynamics, Inc., and its affiliates * 2016 * All Rights Reserved * THIS IS UNPUBLISHED PROPRIETARY CODE OF APPDYNAMICS, INC. * The copyright notice above does not evidence any actual or intended publication of such source code */ 'use strict'; function CassandraProbe(agent) { this.agent = agent; this.packages = ['cassandra-driver']; } exports.CassandraProbe = CassandraProbe; CassandraProbe.prototype.attach = function(obj) { var self = this; var proxy = self.agent.proxy; var profiler = self.agent.profiler; if(process.env.APPDYNAMICS_CASSANDRA_PROBE_DISABLE == true || process.env.APPDYNAMICS_CASSANDRA_PROBE_DISABLE == 'true') { return; } if(obj.__appdynamicsProbeAttached__) return; obj.__appdynamicsProbeAttached__ = true; self.agent.on('destroy', function() { if(obj.__appdynamicsProbeAttached__) { delete obj.__appdynamicsProbeAttached__; proxy.release(obj.Client); proxy.release(obj.Client.prototype.batch); proxy.release(obj.Client.prototype.eachRow); proxy.release(obj.Client.prototype.execute); } }); // only way to determine cluster name is to query the database, // so we need to hook the (undocumented) 'connected' event once // the driver is instantiated. NOTE: Client is a constructor // so we must fixup the prototype chain after hooking it. var Client = obj.Client; proxy.after(obj, 'Client', function(obj) { obj.once('connected', onconnected); function onconnected(err) { if (err) { // connection was unsuccessful; try again on next connect return obj.once('connected', onconnected); } // we have a connection; get the cluster name obj.execute("SELECT cluster_name FROM system.local WHERE key = 'local'", function (err, results) { if (!err && results && results.rows && results.rows[0]) { obj.cluster = results.rows[0].cluster_name; } if (!obj.cluster) { self.agent.logger.warn( 'unable to determine Cassandra cluster name; ' + 'Cassandra exit call discovery will be disabled'); } } ); } }); obj.Client.prototype = Client.prototype; // execute() and batch() return a promise if they don't // receive a callback so we need both before and after // advice to handle both cases proxy.around(obj.Client.prototype, ['execute', 'batch'], before, after); // eachRow() doesn't return a promise if no callback is given, // so we don't need after advice to handle that; but we also // have to be careful about detecting the optional completion // callback as it's preceded by another (non-optional) // function argument proxy.before(obj.Client.prototype, 'eachRow', function(obj, args, locals) { var missingCallback = false; var cb1 = args[args.length - 2]; var cb2 = args[args.length - 1]; if (!(typeof(cb1) == 'function' && typeof(cb2) == 'function')) { missingCallback = true; } // handle the exit call before.call(this, obj, args, locals, missingCallback); } ); // capture exit call start and, if an operation completion // callback is available, hook it to end the exit call // when the operation completes. for operations that can // return a promise, see after() function before(obj, args, locals, noCallback) { var client = obj; var host = client.hosts && client.hosts.values()[0]; var command = args[0]; if (!(host && client.cluster)) return; var supportedProperties = { 'CLUSTER NAME': client.cluster, 'KEYSPACE': client.keyspace, 'HOST': host.address.split(':')[0], 'PORT': host.address.split(':')[1], 'DATA CENTER': host.datacenter, 'RACK': host.rack }; locals = locals || {}; locals.time = profiler.time(); locals.exitCall = createExitCall(client, profiler, locals.time, command, supportedProperties); if (noCallback) { // special case for beforeEach() without callback; // no promise is returned and there is no event we // can hook or other means to get called back when // the query completes, so we just complete the exit // call right away complete(args[0], locals); } else { locals.methodHasCb = self.agent.proxy.callback(args, -1, function(obj, args) { // the operation completed via a callback; end the exit call complete(args[0], locals); }, null, self.agent.thread.current()); } } // end the exit call via promise resolution for operations that // can return a promise; ret is assumed to be a promise if it is // 'promise-like', i.e. it has a `then` property that is callable. function after(obj, args, ret, locals) { if (locals.methodHasCb) return; if (!ret || !ret.__appdynamicsIsPromiseResult__) return; 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 = self.agent.proxy.getErrorObject(err); profiler.addExitCall(locals.time, locals.exitCall, error); } }; function createExitCall(client, profiler, time, command, props) { var stringifiedCommand = command; if (typeof command != 'string' && command.length) { stringifiedCommand = []; for(var i = 0; i < command.length; i++) { stringifiedCommand[i] = command[i].query; if (typeof command[i] === 'string') { stringifiedCommand[i] = command[i]; } } } return profiler.createExitCall(time, { exitType: 'EXIT_CUSTOM', exitSubType: 'Cassandra CQL', configType: 'Cassandra', supportedProperties: props, command: profiler.sanitize(stringifiedCommand), user: client.options.authProvider && client.options.authProvider.username, stackTrace: profiler.stackTrace(), isSql: false }); }