UNPKG

appdynamics

Version:

Performance Profiler and Monitor

179 lines (163 loc) 6.08 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 GrpcExitProbe(agent) { this.agent = agent; } exports.GrpcExitProbe = GrpcExitProbe; GrpcExitProbe.prototype.init = function () { }; GrpcExitProbe.prototype.attach = function(obj) { var self = this; var proxy = self.agent.proxy; self.module = obj; function invokeInterception(client, method, serviceName, type, data) { var threadId = self.agent.thread.current(); self.agent.proxy.around(client.prototype, method, function(obj, args, locals) { var supportedProperties = { 'SERVICE': serviceName, 'VENDOR': 'gRPC', 'OPERATION': method, 'SOAP_ACTION': type, 'URL': data.address }; self.createExitCall(supportedProperties, method, args, locals); if (locals.exitCall) { var correlationHeaderValue = self.agent.backendConnector.getCorrelationHeader(locals.exitCall); if (correlationHeaderValue) { var argsArray = Array.prototype.slice.call(args); self.injectCorrelationHeader(argsArray, type, correlationHeaderValue); return argsArray; } } }, after, false, threadId); function after(obj, args, ret, locals) { let finished = false; if(ret.constructor.name == 'ClientReadableStream' || 'ClientDuplexStream'){ ret.on('status', () => { finished = true; self.complete(locals); }); ret.on('error', (err) => { if(!finished) { finished = true; if (err) { var errorObject = { message: err.message, name: "GRPC Error code " + err.code }; locals.error = errorObject; } self.complete(locals); } }); } } } function methodsToIntercept (client, methods, data) { function methodType(requestStream, responseStream) { if(!requestStream && !responseStream) { return 'unary'; } else if (requestStream && !responseStream) { return 'client_stream'; } else if (!requestStream && responseStream) { return 'server_stream'; } else { return 'bidi'; } } /* If the .proto defines UnaryMethod there could be two methods on the client * UnaryMethod (name field) and unaryMethod (originalName) */ Object.entries(methods).forEach(([name, {path, requestStream, responseStream, originalName}]) => { var serviceName = path.split('/')[1]; var type = methodType(requestStream, responseStream); invokeInterception(client, name, serviceName, type, data); if(originalName && client.prototype.hasOwnProperty(originalName) && name != originalName) { invokeInterception(client, originalName, serviceName, type, data); } }); } /* Recursively search through client classes so we intercept * all functions, this is to match parsing done by * https://github.com/grpc/grpc-node/blob/1d14203c382509c3f36132bd0244c99792cb6601/packages/grpc-js/src/make-client.ts#L200-L217 */ function interceptLoadedPackage (client, ret) { Object.entries(ret).forEach(([key, service]) => { if(typeof(service) === 'function') { var data = {}; ret[key] = proxy.extendFunctionConstructor(ret[key], function(obj, args) { data.address = args[0]; }, true); methodsToIntercept(ret[key], ret[key].service, data); } else if(typeof(service.format) != 'string') { interceptLoadedPackage(client, service); } }); } proxy.after(obj, 'loadPackageDefinition' , function(obj,args, ret) { interceptLoadedPackage(obj, ret); }, false); }; GrpcExitProbe.prototype.injectCorrelationHeader = function(args, type, corrHeader) { var self = this; // This finds an instance of Metadata among the arguments. var metadataIndex = args.findIndex((arg) => { return (arg && typeof arg === 'object' && arg.internalRepr && typeof arg.getMap === 'function' ); }); var metadata; if (metadataIndex == -1) { metadata = new self.module.Metadata(); if (type == 'unary' || type == 'server_stream') { if (args.length == 0) { // No argument (for the gRPC call) was provided, so we will have to // provide one, since metadata cannot be the first argument. // The internal representation of argument defaults to undefined // in its non-presence. args.push(undefined); } metadataIndex = 1; } else { metadataIndex = 0; } args.splice(metadataIndex, 0, metadata); } else { metadata = args[metadataIndex]; } metadata.set(self.agent.correlation.HEADER_NAME, corrHeader.toString()); }; GrpcExitProbe.prototype.createExitCall = function(supportedProperties, command, commandArgs, locals) { var self = this, profiler = self.agent.profiler; locals.time = profiler.time(); locals.exitCall = profiler.createExitCall(locals.time, { exitType: 'WEB_SERVICE', supportedProperties: supportedProperties, command: command, commandArgs: commandArgs, stackTrace: profiler.stackTrace() }); if (!locals.exitCall) return; if(supportedProperties['SOAP_ACTION'] == 'client_stream' || supportedProperties['SOAP_ACTION'] == 'unary') { locals.methodHasCb = self.agent.proxy.callback(commandArgs, -1, function(obj, args) { if (args.length > 0 && args[0]) { var err = args[0]; var errorObject = { message: err.message, name: "GRPC Error code " + err.code }; locals.error = errorObject; } self.complete(locals); }); } }; GrpcExitProbe.prototype.complete = function(locals) { if (!locals.exitCall) return; if (!locals.time.done()) return; this.agent.profiler.addExitCall(locals.time, locals.exitCall, locals.error); };