Skip to content
This repository has been archived by the owner on Oct 3, 2023. It is now read-only.

Commit

Permalink
Use binary format for grpc plugin (#354)
Browse files Browse the repository at this point in the history
* Use binary format for grpc plugin

* fix indentations

* add tests for metadata headers
  • Loading branch information
mayurkale22 committed Feb 21, 2019
1 parent fc5f4d1 commit 5b8dced
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 59 deletions.
2 changes: 1 addition & 1 deletion packages/opencensus-instrumentation-grpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"access": "public"
},
"devDependencies": {
"@opencensus/propagation-b3": "^0.0.9",
"@opencensus/propagation-binaryformat": "^0.0.1",
"@types/end-of-stream": "^1.4.0",
"@types/lodash": "^4.14.109",
"@types/mocha": "^5.2.5",
Expand Down
119 changes: 68 additions & 51 deletions packages/opencensus-instrumentation-grpc/src/grpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
* limitations under the License.
*/

import {BasePlugin, CanonicalCode, HeaderGetter, HeaderSetter, PluginInternalFiles, RootSpan, Span, SpanKind} from '@opencensus/core';
import {BasePlugin, CanonicalCode, PluginInternalFiles, RootSpan, Span, SpanContext, SpanKind, TraceOptions} from '@opencensus/core';
import {deserializeSpanContext, serializeSpanContext} from '@opencensus/propagation-binaryformat';
import {EventEmitter} from 'events';
import * as grpcTypes from 'grpc';
import * as lodash from 'lodash';
import * as path from 'path';
import * as semver from 'semver';
import * as shimmer from 'shimmer';

/** The metadata key under which span context is stored as a binary value. */
export const GRPC_TRACE_KEY = 'grpc-trace-bin';
const findIndex = lodash.findIndex;

//
Expand All @@ -48,7 +49,7 @@ type ServerCallWithMeta = ServerCall&{
&EventEmitter;

export type SendUnaryDataCallback =
(error: grpcTypes.ServiceError,
(error: grpcTypes.ServiceError|null,
// tslint:disable-next-line:no-any
value?: any, trailer?: grpcTypes.Metadata, flags?: grpcTypes.writeFlags) =>
void;
Expand All @@ -59,17 +60,6 @@ type GrpcClientFunc = typeof Function&{
responseStream: boolean;
};


type HandlerSet = {
// tslint:disable-next-line:no-any
func: grpcTypes.handleCall<any, any>;
// tslint:disable-next-line:no-any
serialize: grpcTypes.serialize<any>;
// tslint:disable-next-line:no-any
deserialize: grpcTypes.deserialize<any>;
type: string;
};

// tslint:disable:variable-name
// tslint:disable-next-line:no-any
let Metadata: any;
Expand Down Expand Up @@ -162,29 +152,29 @@ export class GrpcPlugin extends BasePlugin {
this: typeof handlerSet, call: ServerCallWithMeta,
callback: SendUnaryDataCallback) {
const self = this;
const propagation = plugin.tracer.propagation;
const headers = call.metadata.getMap();
const getter: HeaderGetter = {
getHeader(name: string) {
return headers[name] as string;
}
};

const traceOptions = {
const traceOptions: TraceOptions = {
name: `grpc.${name.replace('/', '')}`,
kind: SpanKind.SERVER,
spanContext: propagation ? propagation.extract(getter) : null
kind: SpanKind.SERVER
};
plugin.logger.debug('path func: %s', traceOptions.name);

const spanContext = GrpcPlugin.getSpanContext(call.metadata);
if (spanContext) {
traceOptions.spanContext = spanContext;
}
plugin.logger.debug(
'path func: %s', JSON.stringify(traceOptions));

return plugin.tracer.startRootSpan(traceOptions, rootSpan => {
if (!rootSpan) {
return originalFunc.call(self, call, callback);
}

rootSpan.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_METHOD, name);
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_KIND, traceOptions.kind);
if (traceOptions.kind) {
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_KIND, traceOptions.kind);
}

switch (type) {
case 'unary':
Expand All @@ -206,7 +196,6 @@ export class GrpcPlugin extends BasePlugin {
};
}


/**
* Handler Unary and Client Stream Calls
*/
Expand All @@ -219,10 +208,12 @@ export class GrpcPlugin extends BasePlugin {
// tslint:disable-next-line:no-any
value: any, trailer: grpcTypes.Metadata, flags: grpcTypes.writeFlags) {
if (err) {
rootSpan.setStatus(
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
if (err.code) {
rootSpan.setStatus(
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
}
rootSpan.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_ERROR_NAME, err.name);
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_ERROR_MESSAGE, err.message);
Expand Down Expand Up @@ -351,10 +342,13 @@ export class GrpcPlugin extends BasePlugin {
// tslint:disable-next-line:no-any
const wrappedFn = (err: grpcTypes.ServiceError, res: any) => {
if (err) {
span.setStatus(
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
span.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
if (err.code) {
span.setStatus(
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code),
err.message);
span.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
}
span.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_ERROR_NAME, err.name);
span.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_ERROR_MESSAGE, err.message);
Expand Down Expand Up @@ -386,24 +380,13 @@ export class GrpcPlugin extends BasePlugin {
}
}

const metadata = this.getMetadata(original, args, span);

const setter: HeaderSetter = {
setHeader(name: string, value: string) {
metadata.set(name, value);
}
};

const propagation = plugin.tracer.propagation;
if (propagation) {
propagation.inject(setter, span.spanContext);
}
const metadata = this.getMetadata(original, args);
GrpcPlugin.setSpanContext(metadata, span.spanContext);

span.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_METHOD, original.path);
span.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_KIND, span.kind);

const call = original.apply(self, args);

plugin.tracer.wrapEmitter(call);

// if server stream or bidi
Expand Down Expand Up @@ -444,7 +427,7 @@ export class GrpcPlugin extends BasePlugin {
* https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/blob/src/plugins/plugin-grpc.ts#L96)
*/
// tslint:disable-next-line:no-any
private getMetadata(original: GrpcClientFunc, args: any[], span: Span):
private getMetadata(original: GrpcClientFunc, args: any[]):
grpcTypes.Metadata {
let metadata: grpcTypes.Metadata;

Expand Down Expand Up @@ -490,6 +473,40 @@ export class GrpcPlugin extends BasePlugin {
static convertGrpcStatusToSpanStatus(statusCode: grpcTypes.status): number {
return statusCode;
}

/**
* Returns a span context on a Metadata object if it exists and is
* well-formed, or null otherwise.
* @param metadata The Metadata object from which span context should be
* retrieved.
*/
static getSpanContext(metadata: grpcTypes.Metadata): SpanContext|null {
const metadataValue = metadata.getMap()[GRPC_TRACE_KEY] as Buffer;
// Entry doesn't exist.
if (!metadataValue) {
return null;
}
const spanContext = deserializeSpanContext(metadataValue);
// Value is malformed.
if (!spanContext) {
return null;
}
return spanContext;
}

/**
* Set span context on a Metadata object if it exists.
* @param metadata The Metadata object to which a span context should be
* added.
* @param spanContext The span context.
*/
static setSpanContext(metadata: grpcTypes.Metadata, spanContext: SpanContext):
void {
const serializedSpanContext = serializeSpanContext(spanContext);
if (serializedSpanContext) {
metadata.set(GRPC_TRACE_KEY, serializedSpanContext);
}
}
}

const plugin = new GrpcPlugin();
Expand Down
97 changes: 92 additions & 5 deletions packages/opencensus-instrumentation-grpc/test/test-grpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@

import {CoreTracer, RootSpan, Span, SpanEventListener, SpanKind} from '@opencensus/core';
import {logger} from '@opencensus/core';
import {B3Format} from '@opencensus/propagation-b3';
import * as assert from 'assert';
import * as grpcModule from 'grpc';
import * as path from 'path';

import {GrpcModule, GrpcPlugin, plugin, SendUnaryDataCallback} from '../src/';

import {GRPC_TRACE_KEY, GrpcModule, GrpcPlugin, plugin, SendUnaryDataCallback} from '../src/';

const PROTO_PATH = __dirname + '/fixtures/grpc-instrumentation-test.proto';
const grpcPort = 50051;
const MAX_ERROR_STATUS = grpcModule.status.UNAUTHENTICATED;
const log = logger.logger();


const replicate = (request: TestRequestResponse) => {
const result: TestRequestResponse[] = [];
for (let i = 0; i < request.num; i++) {
Expand Down Expand Up @@ -244,7 +241,7 @@ describe('GrpcPlugin() ', function() {
let client: TestGrpcClient;
const tracer = new CoreTracer();
const rootSpanVerifier = new RootSpanVerifier();
tracer.start({samplingRate: 1, propagation: new B3Format(), logger: log});
tracer.start({samplingRate: 1, logger: log});

it('should return a plugin', () => {
assert.ok(plugin instanceof GrpcPlugin);
Expand Down Expand Up @@ -495,4 +492,94 @@ describe('GrpcPlugin() ', function() {
});
});
});
describe('setSpanContext', () => {
const metadata = new grpcModule.Metadata();
const spanContext = {
traceId: '3ad17e665f514aabb896341f670179ed',
spanId: '3aaeb440a89d9e82',
options: 0x1
};

it('should set span context', () => {
GrpcPlugin.setSpanContext(metadata, spanContext);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, spanContext);
});
});

describe('getSpanContext', () => {
const metadata = new grpcModule.Metadata();
it('should return null when span context is not set', () => {
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.equal(actualSpanContext, null);
});

it('should return valid span context', () => {
const buffer = new Buffer([
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
]);
const expectedSpanContext = {
traceId: 'df6a2038fa78c4cd42209126249c31c7',
spanId: 'c2b7ce7a572a37c6',
options: 1
};
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, expectedSpanContext);
});

it('should return null for unsupported version', () => {
const buffer = new Buffer([
0x66, 0x64, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});

it('should return null when unexpected trace ID offset', () => {
const buffer = new Buffer([
0x00, 0x04, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});

it('should return null when unexpected span ID offset', () => {
const buffer = new Buffer([
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x03, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});

it('should return null when unexpected options offset', () => {
const buffer = new Buffer([
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x03, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x00, 0x01
]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});

it('should return null when invalid input i.e. truncated', () => {
const buffer =
new Buffer([0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});
});
});
4 changes: 2 additions & 2 deletions packages/opencensus-instrumentation-grpc/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"pretty": true,
"module": "commonjs",
"target": "es6",
"strictNullChecks": false
"strictNullChecks": true,
"noUnusedLocals": true
},
"include": [
"src/**/*.ts",
Expand All @@ -17,4 +18,3 @@
"node_modules"
]
}

2 changes: 2 additions & 0 deletions packages/opencensus-propagation-binaryformat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export * from './binary-format';

0 comments on commit 5b8dced

Please sign in to comment.