Skip to content

Commit

Permalink
Simple instrumented gRPC example (#729)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hablapatabla authored Jun 3, 2021
1 parent 1c70f0e commit 0f6199f
Show file tree
Hide file tree
Showing 7 changed files with 477 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
if(WITH_OTLP)
add_subdirectory(otlp)
add_subdirectory(grpc)
endif()
if(WITH_JAEGER)
add_subdirectory(jaeger)
Expand Down
48 changes: 48 additions & 0 deletions examples/grpc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Proto file
get_filename_component(proto_file "./protos/messages.proto" ABSOLUTE)
get_filename_component(proto_file_path "${proto_file}" PATH)

message("PATH:${proto_file_path}:${proto_file}")
# Generated sources
set(example_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/messages.pb.cc")
set(example_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/messages.pb.h")
set(example_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/messages.grpc.pb.cc")
set(example_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/messages.grpc.pb.h")

add_custom_command(
OUTPUT "${example_proto_srcs}" "${example_proto_hdrs}" "${example_grpc_srcs}"
"${example_grpc_hdrs}"
COMMAND
${PROTOBUF_PROTOC_EXECUTABLE} ARGS "--grpc_out=${CMAKE_CURRENT_BINARY_DIR}"
"--cpp_out=${CMAKE_CURRENT_BINARY_DIR}" "--proto_path=${proto_file_path}"
--plugin=protoc-gen-grpc="${gRPC_CPP_PLUGIN_EXECUTABLE}" "${proto_file}")
# DEPENDS "${proto_file}")

# hw_grpc_proto
add_library(example_grpc_proto ${example_grpc_srcs} ${example_grpc_hdrs}
${example_proto_srcs} ${example_proto_hdrs})

include_directories(
${CMAKE_SOURCE_DIR}/exporters/ostream/include ${CMAKE_SOURCE_DIR}/ext/include
${CMAKE_SOURCE_DIR}/api/include/ ${CMAKE_SOURCE_DIR/})

include_directories(${CMAKE_CURRENT_BINARY_DIR})

if(TARGET protobuf::libprotobuf)
target_link_libraries(example_grpc_proto gRPC::grpc++ protobuf::libprotobuf)
else()
target_include_directories(example_grpc_proto ${Protobuf_INCLUDE_DIRS})
target_link_libraries(example_grpc_proto ${Protobuf_LIBRARIES})
endif()

foreach(_target client server)
add_executable(${_target} "${_target}.cpp")
target_link_libraries(
${_target}
example_grpc_proto
protobuf::libprotobuf
gRPC::grpc++
gRPC::grpc++_reflection
opentelemetry_trace
opentelemetry_exporter_ostream_span)
endforeach()
99 changes: 99 additions & 0 deletions examples/grpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# OpenTelemetry C++ Example

## gRPC

This is a simple example that demonstrates tracing a gRPC request from client to server. There is an experimental directory in this example - the code within has been commented out to prevent any conflicts. The example shows several aspects of tracing such as:

* Using the `TracerProvider`
* Implementing the TextMapCarrier
* Context injection/extraction
* Span Attributes
* Span Semantic Conventions
* Using the ostream exporter
* Nested spans
* W3c Trace Context Propagation (Very soon!)

### Running the example

1. The example uses gRPC C++ as well as Google's protocol buffers. Make sure you have installed both
of these packages on your system, in such a way that CMake would know how to find them with this command:

``find_package(gRPC)``

2. Build and Deploy the opentelementry-cpp as described in [INSTALL.md](../../INSTALL.md). Building the project will build all of the examples
and create new folders containing their executables within the 'build' directory NOT the 'examples' directory.

3. Start the server from your `build/examples/grpc` directory. Both the server and client are configured to use 8800 as the default port,
but if you would like to use another port, you can specify that as an argument.

```console
$ ./server [port_num]
Server listening on port: 0.0.0.0:8800
```

4. In a separate terminal window, run the client to make a single request:

```console
$ ./client [port_num]
...
```

5. You should see console exporter output for both the client and server sessions.
* Client console

```console
{
name : GreeterClient/Greet
trace_id : f5d16f8399be0d2c6b39d992634ffdbb
span_id : 9c79a2dd744d7d2d
tracestate :
parent_span_id: 0000000000000000
start : 1622603339918985700
duration : 4960500
description :
span kind : Client
status : Ok
attributes :
rpc.grpc.status_code: 0
net.peer.port: 8080
net.peer.ip: 0.0.0.0
rpc.method: Greet
rpc.service: grpc-example.GreetService
rpc.system: grpc
events :
}
```

* Server console

```console
{
name : GreeterService/Greet
trace_id : f5d16f8399be0d2c6b39d992634ffdbb
span_id : 1e8a7d2d46e08573
tracestate :
parent_span_id: 9c79a2dd744d7d2d
start : 1622603339923163800
duration : 76400
description :
span kind : Server
status : Ok
attributes :
rpc.grpc.status_code: 0
rpc.method: Greet
rpc.service: GreeterService
rpc.system: grpc
events :
{
name : Processing client attributes
timestamp : 1622603339923180800
attributes :
}
{
name : Response sent to client
timestamp : 1622603339923233700
attributes :
}
links :
}
```
109 changes: 109 additions & 0 deletions examples/grpc/client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include "tracer_common.h"
#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>

#include "messages.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientReader;
using grpc::Status;

using grpc_example::Greeter;
using grpc_example::GreetRequest;
using grpc_example::GreetResponse;


namespace
{

class GreeterClient
{
public:
GreeterClient(std::shared_ptr<Channel> channel) : stub_(Greeter::NewStub(channel)) {}

std::string Greet(std::string ip, uint16_t port)
{
// Build gRPC Context objects and protobuf message containers
GreetRequest request;
GreetResponse response;
ClientContext context;
request.set_request("Nice to meet you!");

opentelemetry::trace::StartSpanOptions options;
options.kind = opentelemetry::trace::SpanKind::kClient;

std::string span_name = "GreeterClient/Greet";
auto span = get_tracer("grpc")->StartSpan(span_name,
{{"rpc.system", "grpc"},
{"rpc.service", "grpc-example.GreetService"},
{"rpc.method", "Greet"},
{"net.peer.ip", ip},
{"net.peer.port", port}},
options);

auto scope = get_tracer("grpc-client")->WithActiveSpan(span);

// inject current context to grpc metadata
auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent();
GrpcClientCarrier carrier(&context);
auto prop = opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();
prop->Inject(carrier, current_ctx);

// Send request to server
Status status = stub_->Greet(&context, request, &response);
if (status.ok())
{
span->SetStatus(opentelemetry::trace::StatusCode::kOk);
span->SetAttribute("rpc.grpc.status_code", status.error_code());
// Make sure to end your spans!
span->End();
return response.response();
}
else
{
std::cout << status.error_code() << ": " << status.error_message() << std::endl;
span->SetStatus(opentelemetry::trace::StatusCode::kError);
span->SetAttribute("rpc.grpc.status_code", status.error_code());
// Make sure to end your spans!
span->End();
return "RPC failed";
}
}

private:
std::unique_ptr<Greeter::Stub> stub_;
}; // GreeterClient class

void RunClient(uint16_t port)
{
GreeterClient greeter(
grpc::CreateChannel("0.0.0.0:" + std::to_string(port), grpc::InsecureChannelCredentials()));
std::string response = greeter.Greet("0.0.0.0", port);
std::cout << "grpc_server says: " << response << std::endl;
}
} // namespace

int main(int argc, char **argv)
{
initTracer();
// set global propagator
opentelemetry::context::propagation::GlobalTextMapPropagator::SetGlobalPropagator(
nostd::shared_ptr<opentelemetry::context::propagation::TextMapPropagator>(
new opentelemetry::trace::propagation::HttpTraceContext()));
constexpr uint16_t default_port = 8800;
uint16_t port;
if (argc > 1)
{
port = atoi(argv[1]);
}
else
{
port = default_port;
}
RunClient(port);
return 0;
}
15 changes: 15 additions & 0 deletions examples/grpc/protos/messages.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
syntax = "proto3";

package grpc_example;

service Greeter {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}

message GreetRequest {
string request = 1;
}

message GreetResponse {
string response = 1;
}
114 changes: 114 additions & 0 deletions examples/grpc/server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "messages.grpc.pb.h"
#include "tracer_common.h"
#include "opentelemetry/trace/span_context_kv_iterable_view.h"

#include <grpcpp/grpcpp.h>
#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h>

#include <chrono>
#include <fstream>
#include <sstream>
#include <string>
#include <thread>
#include <map>

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::ServerWriter;
using grpc::Status;

using grpc_example::Greeter;
using grpc_example::GreetRequest;
using grpc_example::GreetResponse;

using Span = opentelemetry::trace::Span;
using SpanContext = opentelemetry::trace::SpanContext;

namespace
{
class GreeterServer final : public Greeter::Service
{
public:
Status Greet(ServerContext *context,
const GreetRequest *request,
GreetResponse *response) override
{
for( auto elem: context->client_metadata()) {
std::cout << "ELEM: " << elem.first << " " << elem.second << "\n";
}

// Create a SpanOptions object and set the kind to Server to inform OpenTel.
opentelemetry::trace::StartSpanOptions options;
options.kind = opentelemetry::trace::SpanKind::kServer;

// extract context from grpc metadata
GrpcServerCarrier carrier(context);

auto prop = opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();
auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent();
auto new_context = prop->Extract(carrier, current_ctx);
options.parent = opentelemetry::trace::propagation::GetSpan(new_context)->GetContext();

std::string span_name = "GreeterService/Greet";
auto span = get_tracer("grpc")
->StartSpan(span_name,
{{"rpc.system", "grpc"},
{"rpc.service", "GreeterService"},
{"rpc.method", "Greet"},
{"rpc.grpc.status_code", 0}},
options);
auto scope = get_tracer("grpc")->WithActiveSpan(span);

// Fetch and parse whatever HTTP headers we can from the gRPC request.
span->AddEvent("Processing client attributes");

std::string req = request->request();
std::cout << std::endl << "grpc_client says: " << req << std::endl;
std::string message = "The pleasure is mine.";
// Send response to client
response->set_response(message);
span->AddEvent("Response sent to client");

span->SetStatus(opentelemetry::trace::StatusCode::kOk);
// Make sure to end your spans!
span->End();
return Status::OK;
}
}; // GreeterServer class

void RunServer(uint16_t port)
{
std::string address("0.0.0.0:" + std::to_string(port));
GreeterServer service;
ServerBuilder builder;

builder.RegisterService(&service);
builder.AddListeningPort(address, grpc::InsecureServerCredentials());

std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on port: " << address << std::endl;
server->Wait();
server->Shutdown();
}
} // namespace

int main(int argc, char **argv)
{
initTracer();
constexpr uint16_t default_port = 8800;
uint16_t port;
if (argc > 1)
{
port = atoi(argv[1]);
}
else
{
port = default_port;
}

RunServer(port);
return 0;
}
Loading

0 comments on commit 0f6199f

Please sign in to comment.