From 3b482f4ce99b42a2bfdbe94de261af1e6e67680f Mon Sep 17 00:00:00 2001 From: "Kelly, Niall" Date: Thu, 4 Jun 2020 12:09:42 +0100 Subject: [PATCH] chore: initial commit of grpc-census-example Signed-off-by: Kelly, Niall --- examples/grpc-census-prop/README.md | 130 ++++++++++++++++++ .../grpc-census-prop/capitalize_client.js | 73 ++++++++++ .../grpc-census-prop/capitalize_server.js | 93 +++++++++++++ examples/grpc-census-prop/combination2.md | 95 +++++++++++++ examples/grpc-census-prop/combination4.md | 98 +++++++++++++ examples/grpc-census-prop/package.json | 50 +++++++ examples/grpc-census-prop/protos/defs.proto | 19 +++ examples/grpc-census-prop/tracer.js | 36 +++++ examples/grpc-census-prop/tracer_census.js | 23 ++++ 9 files changed, 617 insertions(+) create mode 100644 examples/grpc-census-prop/README.md create mode 100644 examples/grpc-census-prop/capitalize_client.js create mode 100644 examples/grpc-census-prop/capitalize_server.js create mode 100644 examples/grpc-census-prop/combination2.md create mode 100644 examples/grpc-census-prop/combination4.md create mode 100644 examples/grpc-census-prop/package.json create mode 100644 examples/grpc-census-prop/protos/defs.proto create mode 100644 examples/grpc-census-prop/tracer.js create mode 100644 examples/grpc-census-prop/tracer_census.js diff --git a/examples/grpc-census-prop/README.md b/examples/grpc-census-prop/README.md new file mode 100644 index 0000000000..4d0d465f29 --- /dev/null +++ b/examples/grpc-census-prop/README.md @@ -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: +- For more information on OpenTelemetry for Node.js, visit: + +## LICENSE + +Apache License 2.0 diff --git a/examples/grpc-census-prop/capitalize_client.js b/examples/grpc-census-prop/capitalize_client.js new file mode 100644 index 0000000000..0768d6fe9c --- /dev/null +++ b/examples/grpc-census-prop/capitalize_client.js @@ -0,0 +1,73 @@ +'use strict'; + +const binaryPropagator = process.env.BINARY_PROPAGATOR === 'true' ? true : false; +const censusTracer = process.env.CENSUS_TRACER === 'true' ? true : false; +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.Fetch; + +function main() { + const client = new Fetch('localhost:50051', + grpc.credentials.createInsecure()); + const data = process.argv[2] || 'opentelemetry'; + console.log('> ', data); + + if (censusTracer) { + capitalizeWithCensusTracing(tracer, client, data); + } + else { + capitalizeWithOTelTracing(tracer, 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); +} + +function capitalizeWithCensusTracing(tracer, 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(); + }); + }); +} + +function capitalizeWithOTelTracing(tracer, 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(); diff --git a/examples/grpc-census-prop/capitalize_server.js b/examples/grpc-census-prop/capitalize_server.js new file mode 100644 index 0000000000..817912df7c --- /dev/null +++ b/examples/grpc-census-prop/capitalize_server.js @@ -0,0 +1,93 @@ +'use strict'; + +const binaryPropagator = process.env.BINARY_PROPAGATOR === 'true' ? true : false; +const censusTracer = process.env.CENSUS_TRACER === 'true' ? true : false; + +let tracer; +let SpanKind; +if (censusTracer) { + tracer = require('./tracer_census')(); + SpanKind = require('@opencensus/core').SpanKind; +} else { + tracer = require('./tracer')('example-grpc-capitalize-server', binaryPropagator); + SpanKind = require('@opentelemetry/api').SpanKind; +} + +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) }); +} + +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; +} + +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(); diff --git a/examples/grpc-census-prop/combination2.md b/examples/grpc-census-prop/combination2.md new file mode 100644 index 0000000000..446552135c --- /dev/null +++ b/examples/grpc-census-prop/combination2.md @@ -0,0 +1,95 @@ +# Example Output from Combination 2 + +OpenTelemetry (with **binary propagator**) used on server, OpenCensus used +on client. Propagation through `grpc-trace-bin` header. + +## Client + +Note: traceId **1565fbb4d6f042d8880bedb509bf6f2e** + +``` +$ npm run client:census + +> grpc-census-prop-example@0.8.0 client:census /opentelemetry-js-contrib/examples/grpc-census-prop +> cross-env CENSUS_TRACER=true node ./capitalize_client.js + +> opentelemetry +Sleeping 5 seconds before shutdown to ensure all records are flushed. +(node:14866) DeprecationWarning: grpc.load: Use the @grpc/proto-loader module with grpc.loadPackageDefinition instead +< OPENTELEMETRY +RootSpan: {traceId: 1565fbb4d6f042d8880bedb509bf6f2e, spanId: a5ccb3c920a18ace, name: tutorialsClient.capitalize } + ChildSpans: + {spanId: c92c3b3f955cdce1, name: grpc.rpc.Fetch/Capitalize} +Completed. +``` + +## Server + +Note: traceId **1565fbb4d6f042d8880bedb509bf6f2e** + +``` +$ npm run server:otel:binprop + +> grpc-census-prop-example@0.8.0 server:otel:binprop /opentelemetry-js-contrib/examples/grpc-census-prop +> cross-env BINARY_PROPAGATOR=true node ./capitalize_server.js + +PluginLoader#load: trying to load grpc@1.24.2 +Metadata { + _internal_repr: + { 'grpc-trace-bin': + [ Buffer [Uint8Array] [ + 0, + 0, + 21, + 101, + 251, + 180, + 214, + 240, + 66, + 216, + 136, + 11, + 237, + 181, + 9, + 191, + 111, + 46, + 1, + 201, + 44, + 59, + 63, + 149, + 92, + 220, + 225, + 2, + 1 ] ], + 'user-agent': [ 'grpc-node/1.24.2 grpc-c/8.0.0 (linux; chttp2; ganges)' ] }, + flags: 0 } +traceid: 1565fbb4d6f042d8880bedb509bf6f2e +{ traceId: '1565fbb4d6f042d8880bedb509bf6f2e', + parentId: '891bd1ebb5e44ec8', + name: 'tutorials.FetchImpl.capitalize', + id: '61a1c3bce0364fe2', + kind: 1, + timestamp: 1591295467649290, + duration: 101304, + attributes: {}, + status: { code: 0 }, + events: [] } +{ traceId: '1565fbb4d6f042d8880bedb509bf6f2e', + parentId: 'c92c3b3f955cdce1', + name: 'grpc.rpc.Fetch/Capitalize', + id: '891bd1ebb5e44ec8', + kind: 1, + timestamp: 1591295467645184, + duration: 106431, + attributes: + { 'grpc.kind': 1, component: 'grpc', 'grpc.status_code': '0' }, + status: { code: 0 }, + events: + [ { name: 'received', attributes: undefined, time: [Array] } ] } +``` diff --git a/examples/grpc-census-prop/combination4.md b/examples/grpc-census-prop/combination4.md new file mode 100644 index 0000000000..908e55990b --- /dev/null +++ b/examples/grpc-census-prop/combination4.md @@ -0,0 +1,98 @@ +# Example Output from Combination 4 + +OpenCensus used on server, OpenTelemetry (with **binary propagator**) used on +client. Propagation through `grpc-trace-bin` header. + +## Client + +Note: traceId **901c68f681e5a85a125b3dad82e51498** + +``` +$ npm run client:otel:binprop + +> grpc-census-prop-example@0.8.0 client:otel:binprop /opentelemetry-js-contrib/examples/grpc-census-prop +> cross-env BINARY_PROPAGATOR=true node ./capitalize_client.js + +PluginLoader#load: trying to load grpc@1.24.2 +> opentelemetry +Sleeping 5 seconds before shutdown to ensure all records are flushed. +(node:29834) DeprecationWarning: grpc.load: Use the @grpc/proto-loader module with grpc.loadPackageDefinition instead +{ traceId: '901c68f681e5a85a125b3dad82e51498', + parentId: '070c69c837bbbd1e', + name: 'grpc.rpc.Fetch/Capitalize', + id: 'ecdf319bce919fde', + kind: 2, + timestamp: 1591295728961209, + duration: 159530, + attributes: + { component: 'grpc', + 'grpc.method': '/rpc.Fetch/Capitalize', + 'grpc.kind': 2, + 'grpc.status_code': '0' }, + status: { code: 0 }, + events: [ { name: 'sent', attributes: undefined, time: [Array] } ] } +< OPENTELEMETRY +traceid: 901c68f681e5a85a125b3dad82e51498 +{ traceId: '901c68f681e5a85a125b3dad82e51498', + parentId: undefined, + name: 'tutorialsClient.capitalize', + id: '070c69c837bbbd1e', + kind: 0, + timestamp: 1591295728960326, + duration: 163145, + attributes: {}, + status: { code: 0 }, + events: [] } +Completed. +``` + +## Server + +Note: traceId **901c68f681e5a85a125b3dad82e51498** + +``` +$ npm run server:census + +> grpc-census-prop-example@0.8.0 server:census /opentelemetry-js-contrib/examples/grpc-census-prop +> cross-env CENSUS_TRACER=true node ./capitalize_server.js + +Metadata { + _internal_repr: + { 'grpc-trace-bin': + [ Buffer [Uint8Array] [ + 0, + 0, + 144, + 28, + 104, + 246, + 129, + 229, + 168, + 90, + 18, + 91, + 61, + 173, + 130, + 229, + 20, + 152, + 1, + 236, + 223, + 49, + 155, + 206, + 145, + 159, + 222, + 2, + 1 ] ], + 'user-agent': [ 'grpc-node/1.24.2 grpc-c/8.0.0 (linux; chttp2; ganges)' ] }, + flags: 0 } +traceid: 901c68f681e5a85a125b3dad82e51498 +RootSpan: {traceId: 901c68f681e5a85a125b3dad82e51498, spanId: 63028b5ce96caec6, name: grpc.rpc.Fetch/Capitalize } + ChildSpans: + {spanId: d70a03f18955e762, name: tutorials.FetchImpl.capitalize} +``` diff --git a/examples/grpc-census-prop/package.json b/examples/grpc-census-prop/package.json new file mode 100644 index 0000000000..3c904503af --- /dev/null +++ b/examples/grpc-census-prop/package.json @@ -0,0 +1,50 @@ +{ + "name": "grpc-census-prop-example", + "version": "0.8.0", + "description": "Example of using propagator-grpc-census-binary", + "main": "index.js", + "scripts": { + "server:otel:defprop": "cross-env node ./capitalize_server.js", + "server:otel:binprop": "cross-env BINARY_PROPAGATOR=true node ./capitalize_server.js", + "server:census": "cross-env CENSUS_TRACER=true node ./capitalize_server.js", + "client:otel:defprop": "cross-env node ./capitalize_client.js", + "client:otel:binprop": "cross-env BINARY_PROPAGATOR=true node ./capitalize_client.js", + "client:census": "cross-env CENSUS_TRACER=true node ./capitalize_client.js" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/open-telemetry/opentelemetry-js.git" + }, + "keywords": [ + "opentelemetry", + "grpc", + "tracing" + ], + "engines": { + "node": ">=8" + }, + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/open-telemetry/opentelemetry-js/issues" + }, + "dependencies": { + "@grpc/proto-loader": "^0.4.0", + "@opencensus/core": "0.0.21", + "@opencensus/instrumentation-grpc": "0.0.21", + "@opencensus/nodejs": "0.0.21", + "@opencensus/propagation-binaryformat": "0.0.21", + "@opentelemetry/api": "^0.8.3", + "@opentelemetry/core": "^0.8.3", + "@opentelemetry/node": "^0.8.3", + "@opentelemetry/plugin-grpc": "^0.8.3", + "@opentelemetry/propagator-grpc-census-binary": "^0.8.0", + "@opentelemetry/tracing": "^0.8.3", + "grpc": "^1.24.2", + "node-pre-gyp": "0.12.0" + }, + "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme", + "devDependencies": { + "cross-env": "^6.0.0" + } +} diff --git a/examples/grpc-census-prop/protos/defs.proto b/examples/grpc-census-prop/protos/defs.proto new file mode 100644 index 0000000000..cc4a40334c --- /dev/null +++ b/examples/grpc-census-prop/protos/defs.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package rpc; + +service Fetch { + // Sends a capitalizes payload + rpc Capitalize(Payload) returns (Payload) {} +} + +// The request and response payload containing the id and data. +message Payload { + int32 id = 1; + bytes data = 2; +} diff --git a/examples/grpc-census-prop/tracer.js b/examples/grpc-census-prop/tracer.js new file mode 100644 index 0000000000..d1b8c5e206 --- /dev/null +++ b/examples/grpc-census-prop/tracer.js @@ -0,0 +1,36 @@ +'use strict'; + +const opentelemetry = require('@opentelemetry/api'); +const { NodeTracerProvider } = require('@opentelemetry/node'); +const { SimpleSpanProcessor, ConsoleSpanExporter } = require('@opentelemetry/tracing'); +const { HttpTraceContext } = require('@opentelemetry/core'); +const { GrpcCensusPropagator } = require('@opentelemetry/propagator-grpc-census-binary'); + +module.exports = (serviceName, binaryPropagator) => { + const provider = new NodeTracerProvider({ + plugins: { + grpc: { + enabled: true, + // You may use a package name or absolute path to the file. + path: '@opentelemetry/plugin-grpc', + }, + }, + }); + + // It is recommended to use this `BatchSpanProcessor` for better performance + // and optimization, especially in production. + provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + + if (binaryPropagator) { + // Initialize the OpenTelemetry APIs to use the NodeTracerProvider bindings + provider.register({ + propagator: new GrpcCensusPropagator() + }); + } else { + provider.register({ + propagator: new HttpTraceContext() + }); + } + + return opentelemetry.trace.getTracer(serviceName); +}; diff --git a/examples/grpc-census-prop/tracer_census.js b/examples/grpc-census-prop/tracer_census.js new file mode 100644 index 0000000000..32788ffc99 --- /dev/null +++ b/examples/grpc-census-prop/tracer_census.js @@ -0,0 +1,23 @@ +'use strict'; + +const tracing = require('@opencensus/nodejs'); +const { ConsoleExporter } = require('@opencensus/core'); + +const defaultBufferConfig = { + bufferSize: 1, + bufferTimeout: 2000 +}; + +module.exports = () => { + let tracer = tracing.start({ + + samplingRate: 1, + plugins: { + 'grpc': '@opencensus/instrumentation-grpc' + } + }).tracer; + + tracer.registerSpanEventListener(new ConsoleExporter(defaultBufferConfig)); + + return tracer; +};