appdynamics
Version:
Performance Profiler and Monitor
179 lines (163 loc) • 6.08 kB
JavaScript
/*
* 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
*/
;
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);
};