Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add gRPC Census Example #46

Merged
merged 5 commits into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions examples/grpc-census-prop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Introduction

This example uses the same gRPC [defs.proto](./protos/defs.proto) as the
[grpc_dynamic_codegen](../grpc_dynamic_codegen)
example in which a server takes a payload containing bytes and capitalizes them.
In this case we are demonstrating the use of the
[propagator-grpc-census-binary](../../propagators/opentelemetry-propagator-grpc-census-binary)
propagator. The propagator can be useful when communicating with another service
that is already instrumented using OpenCensus.

If both sides of gRPC communication are using OpenTelemetry instrumentation then
the `propagator-grpc-census-binary` propagator isn't required. Context will be
propagated using the `traceparent` header (thanks to the
[HttpTraceContext](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-core/src/context/propagation/HttpTraceContext.ts)
propagator from opentelemetry-core). If there is a mix of OpenCensus and OpenTelemetry
instrumentation then the `propagator-grpc-census-binary` propagator allows OpenTelemetry
to propagate context through the `grpc-trace-bin` binary header.

The same source code is used to run various versions of the client and server. Environment
variables (set up through `scripts` in [package.json](./package.json)) determine the various
combinations. This table shows what to expect:

| Combination | Client Instrumentation | Server Instrumentation | Propagation Header |
| :---------- | :--------------------- | :--------------------- | :----------------- |
| 1 | OpenTelemetry (default propagator) | OpenTelemetry (default propagator) | `traceparent` |
| 2 | OpenCensus | OpenTelemetry (**binary propagator**) | `grpc-trace-bin` |
| 3 | OpenCensus | OpenCensus | `grpc-trace-bin` |
| 4 | OpenTelemetry (**binary propagator**) | OpenCensus | `grpc-trace-bin` |

If context propagation is working correctly we should see consistent values
for `traceId` in the output of both the client and server. (Note: the example
uses simple Console Exporters rather than Jaeger or Zipkin). The servers also
output the contents of `grpc.Metadata` which allows us to see the values of
`traceparent` or `grpc-trace-bin` where applicable.

## Installation

```sh
$ # from this directory
$ npm install
```

## Running the Client and Server combinations

### Combination 1

OpenTelemetry (with default propagator) used on both client and server.
Propagation through `traceparent` header.

- Run the server

```sh
$ # from this directory
$ npm run server:otel:defprop
```

- Run the client

```sh
$ # from this directory
$ npm run client:otel:defprop
```

### Combination 2

OpenTelemetry (with **binary propagator**) used on server, OpenCensus used
on client. Propagation through `grpc-trace-bin` header.

- Run the server

```sh
$ # from this directory
$ npm run server:otel:binprop
```

- Run the client

```sh
$ # from this directory
$ npm run client:census
```

See [combination2](./combination2.md) for example output

### Combination 3

OpenCensus used on both client and server. Propagation through `grpc-trace-bin` header.

- Run the server

```sh
$ # from this directory
$ npm run server:census
```

- Run the client

```sh
$ # from this directory
$ npm run client:census
```

### Combination 4

OpenCensus used on server, OpenTelemetry (with **binary propagator**) used on
client. Propagation through `grpc-trace-bin` header.

- Run the server

```sh
$ # from this directory
$ npm run server:census
```

- Run the client

```sh
$ # from this directory
$ npm run client:otel:binprop
```

See [combination4](./combination4.md) for example output

## Useful links
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
- For more information on OpenTelemetry for Node.js, visit: <https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node-sdk>

## LICENSE

Apache License 2.0
82 changes: 82 additions & 0 deletions examples/grpc-census-prop/capitalize_client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';

/* eslint-disable global-require */
const binaryPropagator = process.env.BINARY_PROPAGATOR === 'true';
const censusTracer = process.env.CENSUS_TRACER === 'true';
let tracer;
if (censusTracer) {
tracer = require('./tracer_census')();
} else {
tracer = require('./tracer')('example-grpc-capitalize-client', binaryPropagator);
}

const path = require('path');
const grpc = require('grpc');

const PROTO_PATH = path.join(__dirname, 'protos/defs.proto');

// Even though grpc.load is deprecated in favor of @grpc/proto-loader, it
// appears @opencensus/instrumentation-grpc only gets to set the
// grpc-trace-bin header if we use grpc.load
const { Fetch } = grpc.load(PROTO_PATH).rpc;

/**
* Creates a gRPC client, makes a gRPC call and waits before shutting down
*/
function main() {
const client = new Fetch('localhost:50051',
grpc.credentials.createInsecure());
const data = process.argv[2] || 'opentelemetry';
console.log('> ', data);

if (censusTracer) {
capitalizeWithCensusTracing(client, data);
} else {
capitalizeWithOTelTracing(client, data);
}

// The process must live for at least the interval past any traces that
// must be exported, or some risk being lost if they are recorded after the
// last export.
console.log('Sleeping 5 seconds before shutdown to ensure all records are flushed.');
setTimeout(() => { console.log('Completed.'); }, 5000);
}

/**
* Makes the gRPC call wrapped in an OpenCensus-style span
*/
function capitalizeWithCensusTracing(client, data) {
tracer.startRootSpan({ name: 'tutorialsClient.capitalize' }, (rootSpan) => {
client.capitalize({ data: Buffer.from(data) }, (err, response) => {
if (err) {
console.log('could not get grpc response');
rootSpan.end();
return;
}
console.log('< ', response.data.toString('utf8'));

rootSpan.end();
});
});
}

/**
* Makes the gRPC call wrapped in an OpenTelemetry-style span
*/
function capitalizeWithOTelTracing(client, data) {
const span = tracer.startSpan('tutorialsClient.capitalize');
tracer.withSpan(span, () => {
client.capitalize({ data: Buffer.from(data) }, (err, response) => {
if (err) {
console.log('could not get grpc response');
return;
}
console.log('< ', response.data.toString('utf8'));
// display traceid in the terminal
console.log(`traceid: ${span.context().traceId}`);
span.end();
});
});
}

main();
102 changes: 102 additions & 0 deletions examples/grpc-census-prop/capitalize_server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use strict';

/* eslint-disable global-require */
const binaryPropagator = process.env.BINARY_PROPAGATOR === 'true';
const censusTracer = process.env.CENSUS_TRACER === 'true';

let tracer;
let SpanKind;
if (censusTracer) {
tracer = require('./tracer_census')();
({ SpanKind } = require('@opencensus/core'));
} else {
tracer = require('./tracer')('example-grpc-capitalize-server', binaryPropagator);
({ SpanKind } = require('@opentelemetry/api'));
}

const path = require('path');
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');

const PROTO_PATH = path.join(__dirname, 'protos/defs.proto');
const PROTO_OPTIONS = {
keepCase: true, enums: String, defaults: true, oneofs: true,
};
const definition = protoLoader.loadSync(PROTO_PATH, PROTO_OPTIONS);
const rpcProto = grpc.loadPackageDefinition(definition).rpc;

/**
* Implements the Capitalize RPC method.
*/
function capitalize(call, callback) {
if (call.metadata) {
// output the gRPC metadata to see headers e.g. traceparent or grpc-trace-bin
console.dir(call.metadata, { depth: null });
}

let capitalized;
if (censusTracer) {
capitalized = capitalizeWithCensusTracing(call);
} else {
capitalized = capitalizeWithOTelTracing(call);
}

callback(null, { data: Buffer.from(capitalized) });
}

/**
* Capitalize wrapped with Census tracing
*/
function capitalizeWithCensusTracing(call) {
const currentSpan = tracer.currentRootSpan;
// display traceid in the terminal
console.log(`traceid: ${currentSpan.traceId}`);

const span = tracer.startChildSpan({
name: 'tutorials.FetchImpl.capitalize',
kind: SpanKind.SERVER,
});

const data = call.request.data.toString('utf8');
const capitalized = data.toUpperCase();
for (let i = 0; i < 100000000; i += 1) {
// empty
}
span.end();
return capitalized;
}

/**
* Capitalize wrapped with OpenTelemetry tracing
*/
function capitalizeWithOTelTracing(call) {
const currentSpan = tracer.getCurrentSpan();
// display traceid in the terminal
console.log(`traceid: ${currentSpan.context().traceId}`);

const span = tracer.startSpan('tutorials.FetchImpl.capitalize', {
parent: currentSpan,
kind: SpanKind.SERVER,
});

const data = call.request.data.toString('utf8');
const capitalized = data.toUpperCase();
for (let i = 0; i < 100000000; i += 1) {
// empty
}
span.end();
return capitalized;
}

/**
* Starts an RPC server that receives requests for the Fetch service at the
* sample server port.
*/
function main() {
const server = new grpc.Server();
server.addService(rpcProto.Fetch.service, { capitalize });
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();
}

main();
Loading