From ac303a09f665a2e2837d9f9fd157db303ebb3faa Mon Sep 17 00:00:00 2001 From: Yash Tibrewal Date: Fri, 24 May 2024 10:38:58 -0700 Subject: [PATCH] [OTel] Generate pkg-config file for grpcpp_otel_plugin (#36686) Public Changes - * Add a pkgconfig installer for `grpcpp_otel_plugin` Example Changes - * Add example of how to use the pkgconfig for `grpcpp_otel_plugin` with the existing OpenTelemetry example. * Add another OpenTelemetry example that uses OTel's OStream exporter. This makes it easier to test the pkgconfig file for `grpcpp_otel_plugin` since the OStream exporter does not require any additional dependencies, as opposed to the Prometheus exporter. Test changes - * Modify `run_distrib_test_cmake_pkgconfig.sh` test to install opentelemetry and build the example with the OStream exporter. Closes #36686 PiperOrigin-RevId: 636965475 --- CMakeLists.txt | 11 ++ examples/cpp/otel/BUILD | 10 +- examples/cpp/otel/CMakeLists.txt | 11 +- examples/cpp/otel/Makefile | 124 ++++++++++++++++ examples/cpp/otel/greeter_callback_client.cc | 88 ++--------- examples/cpp/otel/greeter_callback_server.cc | 56 +------ examples/cpp/otel/ostream/BUILD | 44 ++++++ examples/cpp/otel/ostream/CMakeLists.txt | 85 +++++++++++ examples/cpp/otel/ostream/Makefile | 127 ++++++++++++++++ .../otel/ostream/greeter_callback_client.cc | 79 ++++++++++ .../otel/ostream/greeter_callback_server.cc | 78 ++++++++++ examples/cpp/otel/util.cc | 139 +++++++++++++++++- examples/cpp/otel/util.h | 2 + templates/CMakeLists.txt.template | 16 +- .../cpp/run_distrib_test_cmake_pkgconfig.sh | 13 ++ 15 files changed, 741 insertions(+), 142 deletions(-) create mode 100644 examples/cpp/otel/Makefile create mode 100644 examples/cpp/otel/ostream/BUILD create mode 100644 examples/cpp/otel/ostream/CMakeLists.txt create mode 100644 examples/cpp/otel/ostream/Makefile create mode 100644 examples/cpp/otel/ostream/greeter_callback_client.cc create mode 100644 examples/cpp/otel/ostream/greeter_callback_server.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 32839763f43..2169f108009 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37015,3 +37015,14 @@ generate_pkgconfig( "-lgrpc++_unsecure" "-laddress_sorting -lupb_message_lib -lupb_mem_lib -lupb_base_lib -lutf8_range_lib" "grpc++_unsecure.pc") + +# grpcpp_otel_plugin .pc file +generate_pkgconfig( + "gRPC++ OpenTelemetry Plugin" + "OpenTelemetry Plugin for gRPC C++" + "${gRPC_CPP_VERSION}" + "absl_algorithm_container absl_any_invocable absl_base absl_bind_front absl_check absl_cleanup absl_config absl_cord absl_core_headers absl_flags absl_flags_marshalling absl_flat_hash_map absl_flat_hash_set absl_function_ref absl_hash absl_inlined_vector absl_log absl_log_globals absl_log_severity absl_memory absl_no_destructor absl_optional absl_random_bit_gen_ref absl_random_distributions absl_random_random absl_span absl_status absl_statusor absl_str_format absl_strings absl_synchronization absl_time absl_type_traits absl_utility absl_variant gpr grpc grpc++ opentelemetry_api" + "libcares openssl re2 zlib" + "-lgrpcpp_otel_plugin" + "-laddress_sorting -lupb_textformat_lib -lupb_json_lib -lupb_message_lib -lupb_mem_lib -lupb_base_lib -lutf8_range_lib" + "grpcpp_otel_plugin.pc") diff --git a/examples/cpp/otel/BUILD b/examples/cpp/otel/BUILD index 561ce270df2..ff1ec187a19 100644 --- a/examples/cpp/otel/BUILD +++ b/examples/cpp/otel/BUILD @@ -14,12 +14,19 @@ licenses(["notice"]) +package( + default_visibility = ["//examples/cpp/otel:__subpackages__"], +) + cc_library( name = "util", srcs = ["util.cc"], hdrs = ["util.h"], + defines = ["BAZEL_BUILD"], deps = [ "//:grpc++", + "//:grpc++_reflection", + "//examples/protos:helloworld_cc_grpc", "@io_opentelemetry_cpp//sdk/src/metrics", ], ) @@ -32,7 +39,6 @@ cc_binary( "util", "//:grpc++", "//:grpcpp_otel_plugin", - "//examples/protos:helloworld_cc_grpc", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/flags:parse", "@io_opentelemetry_cpp//exporters/prometheus:prometheus_exporter", @@ -47,9 +53,7 @@ cc_binary( deps = [ "util", "//:grpc++", - "//:grpc++_reflection", "//:grpcpp_otel_plugin", - "//examples/protos:helloworld_cc_grpc", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/flags:parse", "@com_google_absl//absl/strings:str_format", diff --git a/examples/cpp/otel/CMakeLists.txt b/examples/cpp/otel/CMakeLists.txt index e4028a214f9..b81a2538a89 100644 --- a/examples/cpp/otel/CMakeLists.txt +++ b/examples/cpp/otel/CMakeLists.txt @@ -67,22 +67,21 @@ target_link_libraries(hw_grpc_proto add_library(util "util.cc") target_link_libraries(util + hw_grpc_proto opentelemetry-cpp::metrics - ${_GRPC_GRPCPP}) + ${_GRPC_GRPCPP} + ${_REFLECTION} + ${_PROTOBUF_LIBPROTOBUF}) # Targets greeter_callback_(client|server) foreach(_target greeter_callback_client greeter_callback_server) add_executable(${_target} "${_target}.cc") target_link_libraries(${_target} - hw_grpc_proto absl::flags absl::flags_parse opentelemetry-cpp::metrics opentelemetry-cpp::prometheus_exporter - ${_REFLECTION} - ${_GRPC_GRPCPP} gRPC::grpcpp_otel_plugin - util - ${_PROTOBUF_LIBPROTOBUF}) + util) endforeach() diff --git a/examples/cpp/otel/Makefile b/examples/cpp/otel/Makefile new file mode 100644 index 00000000000..0286a777a6a --- /dev/null +++ b/examples/cpp/otel/Makefile @@ -0,0 +1,124 @@ +# +# Copyright 2015 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# TODO(jtattermusch): Remove the hack to workaround protobuf bug. See https://github.com/protocolbuffers/protobuf/issues/12439 +# Hack: protobuf currently doesn't declare it's absl dependencies when protobuf.pc pkgconfig file is used. +PROTOBUF_ABSL_DEPS = absl_absl_check absl_absl_log absl_algorithm absl_base absl_bind_front absl_bits absl_btree absl_cleanup absl_cord absl_core_headers absl_debugging absl_die_if_null absl_dynamic_annotations absl_flags absl_flat_hash_map absl_flat_hash_set absl_function_ref absl_hash absl_layout absl_log_initialize absl_log_severity absl_memory absl_node_hash_map absl_node_hash_set absl_optional absl_span absl_status absl_statusor absl_strings absl_synchronization absl_time absl_type_traits absl_utility absl_variant +# TODO(jtattermusch): Remove the hack to workaround protobuf/utf8_range bug. See https://github.com/protocolbuffers/utf8_range/issues/20 +# Hack: utf8_range (which is protobuf's dependency) currently doesn't have a pkgconfig file, so we need to explicitly +# tweak the list of libraries to link against to fix the build. +PROTOBUF_UTF8_RANGE_LINK_LIBS = -lutf8_validity +OPENTELEMETRY_LINK_LIBS = -lopentelemetry_metrics -lopentelemetry_exporter_prometheus -lopentelemetry_common -lopentelemetry_resources -lprometheus-cpp-pull -lprometheus-cpp-core + +HOST_SYSTEM = $(shell uname | cut -f 1 -d_) +SYSTEM ?= $(HOST_SYSTEM) +CXX = g++ +CPPFLAGS += `pkg-config --cflags protobuf grpc absl_flags absl_flags_parse` +ifeq ($(SYSTEM),Darwin) +LDFLAGS += -L/usr/local/lib `pkg-config --libs --static protobuf grpc++ grpcpp_otel_plugin absl_flags absl_flags_parse $(PROTOBUF_ABSL_DEPS)` \ + $(PROTOBUF_UTF8_RANGE_LINK_LIBS) \ + $(OPENTELEMETRY_LINK_LIBS) \ + -pthread \ + -lgrpc++_reflection \ + -ldl +else +LDFLAGS += -L/usr/local/lib `pkg-config --libs --static protobuf grpc++ grpcpp_otel_plugin absl_flags absl_flags_parse $(PROTOBUF_ABSL_DEPS)` \ + $(PROTOBUF_UTF8_RANGE_LINK_LIBS) \ + $(OPENTELEMETRY_LINK_LIBS) \ + -pthread \ + -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed \ + -ldl +endif +PROTOC = protoc +GRPC_CPP_PLUGIN = grpc_cpp_plugin +GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)` + +PROTOS_PATH = ../../protos + +vpath %.proto $(PROTOS_PATH) + +all: system-check greeter_callback_client greeter_callback_server + +greeter_callback_client: util.o helloworld.pb.o helloworld.grpc.pb.o greeter_callback_client.o + $(CXX) $^ $(LDFLAGS) -o $@ + +greeter_callback_server: util.o helloworld.pb.o helloworld.grpc.pb.o greeter_callback_server.o + $(CXX) $^ $(LDFLAGS) -o $@ + +.PRECIOUS: %.grpc.pb.cc +%.grpc.pb.cc: %.proto + $(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $< + +.PRECIOUS: %.pb.cc +%.pb.cc: %.proto + $(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $< + +clean: + rm -f *.o *.pb.cc *.pb.h greeter_callback_client greeter_callback_server + + +# The following is to test your system and ensure a smoother experience. +# They are by no means necessary to actually compile a grpc-enabled software. + +PROTOC_CMD = which $(PROTOC) +PROTOC_CHECK_CMD = $(PROTOC) --version | grep -q 'libprotoc.3\|libprotoc [0-9][0-9]\.' +PLUGIN_CHECK_CMD = which $(GRPC_CPP_PLUGIN) +HAS_PROTOC = $(shell $(PROTOC_CMD) > /dev/null && echo true || echo false) +ifeq ($(HAS_PROTOC),true) +HAS_VALID_PROTOC = $(shell $(PROTOC_CHECK_CMD) 2> /dev/null && echo true || echo false) +endif +HAS_PLUGIN = $(shell $(PLUGIN_CHECK_CMD) > /dev/null && echo true || echo false) + +SYSTEM_OK = false +ifeq ($(HAS_VALID_PROTOC),true) +ifeq ($(HAS_PLUGIN),true) +SYSTEM_OK = true +endif +endif + +system-check: +ifneq ($(HAS_VALID_PROTOC),true) + @echo " DEPENDENCY ERROR" + @echo + @echo "You don't have protoc 3.0.0 or newer installed in your path." + @echo "Please install an up-to-date version of Google protocol buffers." + @echo "You can find it here:" + @echo + @echo " https://github.com/protocolbuffers/protobuf/releases" + @echo + @echo "Here is what I get when trying to evaluate your version of protoc:" + @echo + -$(PROTOC) --version + @echo + @echo +endif +ifneq ($(HAS_PLUGIN),true) + @echo " DEPENDENCY ERROR" + @echo + @echo "You don't have the grpc c++ protobuf plugin installed in your path." + @echo "Please install grpc. You can find it here:" + @echo + @echo " https://github.com/grpc/grpc" + @echo + @echo "Here is what I get when trying to detect if you have the plugin:" + @echo + -which $(GRPC_CPP_PLUGIN) + @echo + @echo +endif +ifneq ($(SYSTEM_OK),true) + @false +endif diff --git a/examples/cpp/otel/greeter_callback_client.cc b/examples/cpp/otel/greeter_callback_client.cc index 32a2049b4cf..f166bd9f5e6 100644 --- a/examples/cpp/otel/greeter_callback_client.cc +++ b/examples/cpp/otel/greeter_callback_client.cc @@ -16,12 +16,14 @@ * */ -#include -#include -#include -#include +// Explicitly define HAVE_ABSEIL to avoid conflict with OTel's Abseil +// version. Refer +// https://github.com/open-telemetry/opentelemetry-cpp/issues/1042. +#ifndef HAVE_ABSEIL +#define HAVE_ABSEIL +#endif + #include -#include #include "absl/flags/flag.h" #include "absl/flags/parse.h" @@ -30,13 +32,10 @@ #include "opentelemetry/sdk/metrics/meter_provider.h" #include -#include #ifdef BAZEL_BUILD #include "examples/cpp/otel/util.h" -#include "examples/protos/helloworld.grpc.pb.h" #else -#include "helloworld.grpc.pb.h" #include "util.h" #endif @@ -44,64 +43,6 @@ ABSL_FLAG(std::string, target, "localhost:50051", "Server address"); ABSL_FLAG(std::string, prometheus_endpoint, "localhost:9465", "Prometheus exporter endpoint"); -using grpc::Channel; -using grpc::ClientContext; -using grpc::Status; -using helloworld::Greeter; -using helloworld::HelloReply; -using helloworld::HelloRequest; - -class GreeterClient { - public: - GreeterClient(std::shared_ptr channel) - : stub_(Greeter::NewStub(channel)) {} - - // Assembles the client's payload, sends it and presents the response back - // from the server. - std::string SayHello(const std::string& user) { - // Data we are sending to the server. - HelloRequest request; - request.set_name(user); - - // Container for the data we expect from the server. - HelloReply reply; - - // Context for the client. It could be used to convey extra information to - // the server and/or tweak certain RPC behaviors. - ClientContext context; - - // The actual RPC. - std::mutex mu; - std::condition_variable cv; - bool done = false; - Status status; - stub_->async()->SayHello(&context, &request, &reply, - [&mu, &cv, &done, &status](Status s) { - status = std::move(s); - std::lock_guard lock(mu); - done = true; - cv.notify_one(); - }); - - std::unique_lock lock(mu); - while (!done) { - cv.wait(lock); - } - - // Act upon its status. - if (status.ok()) { - return reply.message(); - } else { - std::cout << status.error_code() << ": " << status.error_message() - << std::endl; - return "RPC failed"; - } - } - - private: - std::unique_ptr stub_; -}; - int main(int argc, char** argv) { absl::ParseCommandLine(argc, argv); // Register a global gRPC OpenTelemetry plugin configured with a prometheus @@ -125,20 +66,9 @@ int main(int argc, char** argv) { << status.ToString() << std::endl; return static_cast(status.code()); } - // Instantiate the client. It requires a channel, out of which the actual RPCs - // are created. This channel models a connection to an endpoint specified by - // the argument "--target=" which is the only expected argument. - std::string target_str = absl::GetFlag(FLAGS_target); - grpc::ChannelArguments args; + // Continuously send RPCs every second. - while (true) { - GreeterClient greeter(grpc::CreateCustomChannel( - target_str, grpc::InsecureChannelCredentials(), args)); - std::string user("world"); - std::string reply = greeter.SayHello(user); - std::cout << "Greeter received: " << reply << std::endl; - std::this_thread::sleep_for(std::chrono::seconds(1)); - } + RunClient(absl::GetFlag(FLAGS_target)); return 0; } diff --git a/examples/cpp/otel/greeter_callback_server.cc b/examples/cpp/otel/greeter_callback_server.cc index 8e7e7cda052..408b52d685d 100644 --- a/examples/cpp/otel/greeter_callback_server.cc +++ b/examples/cpp/otel/greeter_callback_server.cc @@ -16,6 +16,13 @@ * */ +// Explicitly define HAVE_ABSEIL to avoid conflict with OTel's Abseil +// version. Refer +// https://github.com/open-telemetry/opentelemetry-cpp/issues/1042. +#ifndef HAVE_ABSEIL +#define HAVE_ABSEIL +#endif + #include #include #include @@ -28,15 +35,10 @@ #include "opentelemetry/sdk/metrics/meter_provider.h" #include -#include -#include -#include #ifdef BAZEL_BUILD #include "examples/cpp/otel/util.h" -#include "examples/protos/helloworld.grpc.pb.h" #else -#include "helloworld.grpc.pb.h" #include "util.h" #endif @@ -44,50 +46,6 @@ ABSL_FLAG(uint16_t, port, 50051, "Server port for the service"); ABSL_FLAG(std::string, prometheus_endpoint, "localhost:9464", "Prometheus exporter endpoint"); -using grpc::CallbackServerContext; -using grpc::Server; -using grpc::ServerBuilder; -using grpc::ServerUnaryReactor; -using grpc::Status; -using helloworld::Greeter; -using helloworld::HelloReply; -using helloworld::HelloRequest; - -// Logic and data behind the server's behavior. -class GreeterServiceImpl final : public Greeter::CallbackService { - ServerUnaryReactor* SayHello(CallbackServerContext* context, - const HelloRequest* request, - HelloReply* reply) override { - std::string prefix("Hello "); - reply->set_message(prefix + request->name()); - - ServerUnaryReactor* reactor = context->DefaultReactor(); - reactor->Finish(Status::OK); - return reactor; - } -}; - -void RunServer(uint16_t port) { - std::string server_address = absl::StrFormat("0.0.0.0:%d", port); - GreeterServiceImpl service; - - grpc::EnableDefaultHealthCheckService(true); - grpc::reflection::InitProtoReflectionServerBuilderPlugin(); - ServerBuilder builder; - // Listen on the given address without any authentication mechanism. - builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); - // Register "service" as the instance through which we'll communicate with - // clients. In this case it corresponds to an *synchronous* service. - builder.RegisterService(&service); - // Finally assemble the server. - std::unique_ptr server(builder.BuildAndStart()); - std::cout << "Server listening on " << server_address << std::endl; - - // Wait for the server to shutdown. Note that some other thread must be - // responsible for shutting down the server for this call to ever return. - server->Wait(); -} - int main(int argc, char** argv) { absl::ParseCommandLine(argc, argv); // Register a global gRPC OpenTelemetry plugin configured with a prometheus diff --git a/examples/cpp/otel/ostream/BUILD b/examples/cpp/otel/ostream/BUILD new file mode 100644 index 00000000000..3195902160e --- /dev/null +++ b/examples/cpp/otel/ostream/BUILD @@ -0,0 +1,44 @@ +# Copyright 2023 the gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["notice"]) + +cc_binary( + name = "greeter_callback_client", + srcs = ["greeter_callback_client.cc"], + defines = ["BAZEL_BUILD"], + deps = [ + "//:grpcpp_otel_plugin", + "//examples/cpp/otel:util", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + "@io_opentelemetry_cpp//exporters/ostream:ostream_metric_exporter", + "@io_opentelemetry_cpp//sdk/src/metrics", + ], +) + +cc_binary( + name = "greeter_callback_server", + srcs = ["greeter_callback_server.cc"], + defines = ["BAZEL_BUILD"], + deps = [ + "//:grpcpp_otel_plugin", + "//examples/cpp/otel:util", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + "@com_google_absl//absl/strings:str_format", + "@io_opentelemetry_cpp//exporters/ostream:ostream_metric_exporter", + "@io_opentelemetry_cpp//sdk/src/metrics", + ], +) diff --git a/examples/cpp/otel/ostream/CMakeLists.txt b/examples/cpp/otel/ostream/CMakeLists.txt new file mode 100644 index 00000000000..c0d9a58a988 --- /dev/null +++ b/examples/cpp/otel/ostream/CMakeLists.txt @@ -0,0 +1,85 @@ +# Copyright 2018 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# cmake build file for C++ gRPC OpenTelemetry example. +# Assumes absl, protobuf, prometheus-cpp, opentelemetry-cpp and gRPC (with -DgRPC_BUILD_OPENTELEMETRY_PLUGIN=ON) have been installed using cmake. +# See cmake_externalproject/CMakeLists.txt for all-in-one cmake build +# that automatically builds all the dependencies before building helloworld. + +cmake_minimum_required(VERSION 3.13) + +project(grpc_opentelemetry_example C CXX) + +include(../../cmake/common.cmake) + +# Find opentelemetry-cpp package +find_package(opentelemetry-cpp CONFIG REQUIRED) + +# Proto file +get_filename_component(hw_proto "../../../protos/helloworld.proto" ABSOLUTE) +get_filename_component(hw_proto_path "${hw_proto}" PATH) + +# Generated sources +set(hw_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.pb.cc") +set(hw_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.pb.h") +set(hw_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.grpc.pb.cc") +set(hw_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.grpc.pb.h") +add_custom_command( + OUTPUT "${hw_proto_srcs}" "${hw_proto_hdrs}" "${hw_grpc_srcs}" "${hw_grpc_hdrs}" + COMMAND ${_PROTOBUF_PROTOC} + ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" + --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" + -I "${hw_proto_path}" + --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}" + "${hw_proto}" + DEPENDS "${hw_proto}") + +# Include generated *.pb.h files +include_directories("${CMAKE_CURRENT_BINARY_DIR}") +include_directories("${CMAKE_SOURCE_DIR}") + +# hw_grpc_proto +add_library(hw_grpc_proto + ${hw_grpc_srcs} + ${hw_grpc_hdrs} + ${hw_proto_srcs} + ${hw_proto_hdrs}) +target_link_libraries(hw_grpc_proto + ${_REFLECTION} + ${_GRPC_GRPCPP} + ${_PROTOBUF_LIBPROTOBUF}) + +# util +add_library(util + "../util.cc") +target_link_libraries(util + hw_grpc_proto + opentelemetry-cpp::metrics + ${_GRPC_GRPCPP} + ${_REFLECTION} + ${_PROTOBUF_LIBPROTOBUF}) + +# Targets greeter_callback_(client|server) +foreach(_target + greeter_callback_client greeter_callback_server) + add_executable(${_target} "${_target}.cc") + target_link_libraries(${_target} + absl::flags + absl::flags_parse + opentelemetry-cpp::metrics + opentelemetry-cpp::ostream_metrics_exporter + gRPC::grpcpp_otel_plugin + util + ${_PROTOBUF_LIBPROTOBUF}) +endforeach() diff --git a/examples/cpp/otel/ostream/Makefile b/examples/cpp/otel/ostream/Makefile new file mode 100644 index 00000000000..9adf470da10 --- /dev/null +++ b/examples/cpp/otel/ostream/Makefile @@ -0,0 +1,127 @@ +# +# Copyright 2015 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# TODO(jtattermusch): Remove the hack to workaround protobuf bug. See https://github.com/protocolbuffers/protobuf/issues/12439 +# Hack: protobuf currently doesn't declare it's absl dependencies when protobuf.pc pkgconfig file is used. +PROTOBUF_ABSL_DEPS = absl_absl_check absl_absl_log absl_algorithm absl_base absl_bind_front absl_bits absl_btree absl_cleanup absl_cord absl_core_headers absl_debugging absl_die_if_null absl_dynamic_annotations absl_flags absl_flat_hash_map absl_flat_hash_set absl_function_ref absl_hash absl_layout absl_log_initialize absl_log_severity absl_memory absl_node_hash_map absl_node_hash_set absl_optional absl_span absl_status absl_statusor absl_strings absl_synchronization absl_time absl_type_traits absl_utility absl_variant +# TODO(jtattermusch): Remove the hack to workaround protobuf/utf8_range bug. See https://github.com/protocolbuffers/utf8_range/issues/20 +# Hack: utf8_range (which is protobuf's dependency) currently doesn't have a pkgconfig file, so we need to explicitly +# tweak the list of libraries to link against to fix the build. +PROTOBUF_UTF8_RANGE_LINK_LIBS = -lutf8_validity +OPENTELEMETRY_LINK_LIBS = -lopentelemetry_metrics -lopentelemetry_exporter_ostream_metrics -lopentelemetry_resources -lopentelemetry_common + +HOST_SYSTEM = $(shell uname | cut -f 1 -d_) +SYSTEM ?= $(HOST_SYSTEM) +CXX = g++ +CPPFLAGS += `pkg-config --cflags protobuf grpc absl_flags absl_flags_parse` +ifeq ($(SYSTEM),Darwin) +LDFLAGS += -L/usr/local/lib `pkg-config --libs --static protobuf grpc++ grpcpp_otel_plugin absl_flags absl_flags_parse $(PROTOBUF_ABSL_DEPS)` \ + $(PROTOBUF_UTF8_RANGE_LINK_LIBS) \ + $(OPENTELEMETRY_LINK_LIBS) \ + -pthread \ + -lgrpc++_reflection \ + -ldl +else +LDFLAGS += -L/usr/local/lib `pkg-config --libs --static protobuf grpc++ grpcpp_otel_plugin absl_flags absl_flags_parse $(PROTOBUF_ABSL_DEPS)` \ + $(PROTOBUF_UTF8_RANGE_LINK_LIBS) \ + $(OPENTELEMETRY_LINK_LIBS) \ + -pthread \ + -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed \ + -ldl +endif +PROTOC = protoc +GRPC_CPP_PLUGIN = grpc_cpp_plugin +GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)` + +PROTOS_PATH = ../../../protos + +vpath %.proto $(PROTOS_PATH) + +all: system-check greeter_callback_client greeter_callback_server + +greeter_callback_client: util.o helloworld.pb.o helloworld.grpc.pb.o greeter_callback_client.o + $(CXX) $^ $(LDFLAGS) -o $@ + +greeter_callback_server: util.o helloworld.pb.o helloworld.grpc.pb.o greeter_callback_server.o + $(CXX) $^ $(LDFLAGS) -o $@ + +.PRECIOUS: %.grpc.pb.cc +%.grpc.pb.cc: %.proto + $(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $< + +.PRECIOUS: %.pb.cc +%.pb.cc: %.proto + $(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $< + +util.o: ../util.cc + $(CXX) $^ -I . -I $(CPPFLAGS) -c -o $@ + +clean: + rm -f *.o *.pb.cc *.pb.h greeter_callback_client greeter_callback_server + + +# The following is to test your system and ensure a smoother experience. +# They are by no means necessary to actually compile a grpc-enabled software. + +PROTOC_CMD = which $(PROTOC) +PROTOC_CHECK_CMD = $(PROTOC) --version | grep -q 'libprotoc.3\|libprotoc [0-9][0-9]\.' +PLUGIN_CHECK_CMD = which $(GRPC_CPP_PLUGIN) +HAS_PROTOC = $(shell $(PROTOC_CMD) > /dev/null && echo true || echo false) +ifeq ($(HAS_PROTOC),true) +HAS_VALID_PROTOC = $(shell $(PROTOC_CHECK_CMD) 2> /dev/null && echo true || echo false) +endif +HAS_PLUGIN = $(shell $(PLUGIN_CHECK_CMD) > /dev/null && echo true || echo false) + +SYSTEM_OK = false +ifeq ($(HAS_VALID_PROTOC),true) +ifeq ($(HAS_PLUGIN),true) +SYSTEM_OK = true +endif +endif + +system-check: +ifneq ($(HAS_VALID_PROTOC),true) + @echo " DEPENDENCY ERROR" + @echo + @echo "You don't have protoc 3.0.0 or newer installed in your path." + @echo "Please install an up-to-date version of Google protocol buffers." + @echo "You can find it here:" + @echo + @echo " https://github.com/protocolbuffers/protobuf/releases" + @echo + @echo "Here is what I get when trying to evaluate your version of protoc:" + @echo + -$(PROTOC) --version + @echo + @echo +endif +ifneq ($(HAS_PLUGIN),true) + @echo " DEPENDENCY ERROR" + @echo + @echo "You don't have the grpc c++ protobuf plugin installed in your path." + @echo "Please install grpc. You can find it here:" + @echo + @echo " https://github.com/grpc/grpc" + @echo + @echo "Here is what I get when trying to detect if you have the plugin:" + @echo + -which $(GRPC_CPP_PLUGIN) + @echo + @echo +endif +ifneq ($(SYSTEM_OK),true) + @false +endif diff --git a/examples/cpp/otel/ostream/greeter_callback_client.cc b/examples/cpp/otel/ostream/greeter_callback_client.cc new file mode 100644 index 00000000000..24e141fd7b5 --- /dev/null +++ b/examples/cpp/otel/ostream/greeter_callback_client.cc @@ -0,0 +1,79 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Explicitly define HAVE_ABSEIL to avoid conflict with OTel's Abseil +// version. Refer +// https://github.com/open-telemetry/opentelemetry-cpp/issues/1042. +#ifndef HAVE_ABSEIL +#define HAVE_ABSEIL +#endif + +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "opentelemetry/exporters/ostream/metric_exporter.h" +#include "opentelemetry/exporters/ostream/metric_exporter_factory.h" +#include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader.h" +#include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h" +#include "opentelemetry/sdk/metrics/meter_provider.h" + +#include + +#ifdef BAZEL_BUILD +#include "examples/cpp/otel/util.h" +#else +#include "../util.h" +#endif + +ABSL_FLAG(std::string, target, "localhost:50051", "Server address"); + +int main(int argc, char** argv) { + absl::ParseCommandLine(argc, argv); + // Register a global gRPC OpenTelemetry plugin configured with an ostream + // exporter. + auto ostream_exporter = + opentelemetry::exporter::metrics::OStreamMetricExporterFactory::Create(); + opentelemetry::sdk::metrics::PeriodicExportingMetricReaderOptions + reader_options; + reader_options.export_interval_millis = std::chrono::milliseconds(1000); + reader_options.export_timeout_millis = std::chrono::milliseconds(500); + auto reader = + opentelemetry::sdk::metrics::PeriodicExportingMetricReaderFactory::Create( + std::move(ostream_exporter), reader_options); + auto meter_provider = + std::make_shared(); + // The default histogram boundaries are not granular enough for RPCs. Override + // the "grpc.client.attempt.duration" view as recommended by + // https://github.com/grpc/proposal/blob/master/A66-otel-stats.md. + AddLatencyView(meter_provider.get(), "grpc.client.attempt.duration", "s"); + meter_provider->AddMetricReader(std::move(reader)); + auto status = grpc::OpenTelemetryPluginBuilder() + .SetMeterProvider(std::move(meter_provider)) + .BuildAndRegisterGlobal(); + if (!status.ok()) { + std::cerr << "Failed to register gRPC OpenTelemetry Plugin: " + << status.ToString() << std::endl; + return static_cast(status.code()); + } + + // Continuously send RPCs every second. + RunClient(absl::GetFlag(FLAGS_target)); + + return 0; +} diff --git a/examples/cpp/otel/ostream/greeter_callback_server.cc b/examples/cpp/otel/ostream/greeter_callback_server.cc new file mode 100644 index 00000000000..ab539efd305 --- /dev/null +++ b/examples/cpp/otel/ostream/greeter_callback_server.cc @@ -0,0 +1,78 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Explicitly define HAVE_ABSEIL to avoid conflict with OTel's Abseil +// version. Refer +// https://github.com/open-telemetry/opentelemetry-cpp/issues/1042. +#ifndef HAVE_ABSEIL +#define HAVE_ABSEIL +#endif + +#include +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "opentelemetry/exporters/ostream/metric_exporter.h" +#include "opentelemetry/exporters/ostream/metric_exporter_factory.h" +#include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader.h" +#include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h" +#include "opentelemetry/sdk/metrics/meter_provider.h" + +#include + +#ifdef BAZEL_BUILD +#include "examples/cpp/otel/util.h" +#else +#include "../util.h" +#endif + +ABSL_FLAG(uint16_t, port, 50051, "Server port for the service"); + +int main(int argc, char** argv) { + absl::ParseCommandLine(argc, argv); + // Register a global gRPC OpenTelemetry plugin configured with an ostream + // exporter. + auto ostream_exporter = + opentelemetry::exporter::metrics::OStreamMetricExporterFactory::Create(); + opentelemetry::sdk::metrics::PeriodicExportingMetricReaderOptions + reader_options; + reader_options.export_interval_millis = std::chrono::milliseconds(1000); + reader_options.export_timeout_millis = std::chrono::milliseconds(500); + auto reader = + opentelemetry::sdk::metrics::PeriodicExportingMetricReaderFactory::Create( + std::move(ostream_exporter), reader_options); + auto meter_provider = + std::make_shared(); + // The default histogram boundaries are not granular enough for RPCs. Override + // the "grpc.server.call.duration" view as recommended by + // https://github.com/grpc/proposal/blob/master/A66-otel-stats.md. + AddLatencyView(meter_provider.get(), "grpc.server.call.duration", "s"); + meter_provider->AddMetricReader(std::move(reader)); + auto status = grpc::OpenTelemetryPluginBuilder() + .SetMeterProvider(std::move(meter_provider)) + .BuildAndRegisterGlobal(); + if (!status.ok()) { + std::cerr << "Failed to register gRPC OpenTelemetry Plugin: " + << status.ToString() << std::endl; + return static_cast(status.code()); + } + RunServer(absl::GetFlag(FLAGS_port)); + return 0; +} diff --git a/examples/cpp/otel/util.cc b/examples/cpp/otel/util.cc index 8777481ea00..cba83f736ad 100644 --- a/examples/cpp/otel/util.cc +++ b/examples/cpp/otel/util.cc @@ -16,17 +16,42 @@ // // -#ifdef BAZEL_BUILD -#include "examples/cpp/otel/util.h" -#else -#include "util.h" +// Explicitly define HAVE_ABSEIL to avoid conflict with OTel's Abseil +// version. Refer +// https://github.com/open-telemetry/opentelemetry-cpp/issues/1042. +#ifndef HAVE_ABSEIL +#define HAVE_ABSEIL #endif +#include +#include + #include "opentelemetry/sdk/metrics/view/instrument_selector_factory.h" #include "opentelemetry/sdk/metrics/view/meter_selector_factory.h" #include "opentelemetry/sdk/metrics/view/view_factory.h" +#include #include +#include + +#ifdef BAZEL_BUILD +#include "examples/cpp/otel/util.h" +#include "examples/protos/helloworld.grpc.pb.h" +#else +#include "helloworld.grpc.pb.h" +#include "util.h" +#endif + +using grpc::CallbackServerContext; +using grpc::Channel; +using grpc::ClientContext; +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerUnaryReactor; +using grpc::Status; +using helloworld::Greeter; +using helloworld::HelloReply; +using helloworld::HelloRequest; void AddLatencyView(opentelemetry::sdk::metrics::MeterProvider* provider, const std::string& name, const std::string& unit) { @@ -48,3 +73,109 @@ void AddLatencyView(opentelemetry::sdk::metrics::MeterProvider* provider, opentelemetry::sdk::metrics::AggregationType::kHistogram, std::move(histogram_config))); } + +namespace { + +class GreeterClient { + public: + GreeterClient(std::shared_ptr channel) + : stub_(Greeter::NewStub(channel)) {} + + // Assembles the client's payload, sends it and presents the response back + // from the server. + std::string SayHello(const std::string& user) { + // Data we are sending to the server. + HelloRequest request; + request.set_name(user); + + // Container for the data we expect from the server. + HelloReply reply; + + // Context for the client. It could be used to convey extra information to + // the server and/or tweak certain RPC behaviors. + ClientContext context; + + // The actual RPC. + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + stub_->async()->SayHello(&context, &request, &reply, + [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + + std::unique_lock lock(mu); + while (!done) { + cv.wait(lock); + } + + // Act upon its status. + if (status.ok()) { + return reply.message(); + } else { + std::cout << status.error_code() << ": " << status.error_message() + << std::endl; + return "RPC failed"; + } + } + + private: + std::unique_ptr stub_; +}; + +// Logic and data behind the server's behavior. +class GreeterServiceImpl final : public Greeter::CallbackService { + ServerUnaryReactor* SayHello(CallbackServerContext* context, + const HelloRequest* request, + HelloReply* reply) override { + std::string prefix("Hello "); + reply->set_message(prefix + request->name()); + + ServerUnaryReactor* reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + return reactor; + } +}; + +} // namespace + +void RunServer(uint16_t port) { + std::string server_address = absl::StrFormat("0.0.0.0:%d", port); + GreeterServiceImpl service; + + grpc::EnableDefaultHealthCheckService(true); + grpc::reflection::InitProtoReflectionServerBuilderPlugin(); + ServerBuilder builder; + // Listen on the given address without any authentication mechanism. + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + // Register "service" as the instance through which we'll communicate with + // clients. In this case it corresponds to an *synchronous* service. + builder.RegisterService(&service); + // Finally assemble the server. + std::unique_ptr server(builder.BuildAndStart()); + std::cout << "Server listening on " << server_address << std::endl; + + // Wait for the server to shutdown. Note that some other thread must be + // responsible for shutting down the server for this call to ever return. + server->Wait(); +} + +void RunClient(const std::string& target_str) { + // Instantiate the client. It requires a channel, out of which the actual RPCs + // are created. This channel models a connection to an endpoint specified by + // the argument "--target=" which is the only expected argument. + grpc::ChannelArguments args; + // Continuously send RPCs every second. + while (true) { + GreeterClient greeter(grpc::CreateCustomChannel( + target_str, grpc::InsecureChannelCredentials(), args)); + std::string user("world"); + std::string reply = greeter.SayHello(user); + std::cout << "Greeter received: " << reply << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} diff --git a/examples/cpp/otel/util.h b/examples/cpp/otel/util.h index 4fe1681b4fa..4c062a39bc1 100644 --- a/examples/cpp/otel/util.h +++ b/examples/cpp/otel/util.h @@ -28,4 +28,6 @@ void AddLatencyView(opentelemetry::sdk::metrics::MeterProvider* provider, const std::string& name, const std::string& unit); +void RunServer(uint16_t port); +void RunClient(const std::string& target_str); #endif // GRPCPP_EXAMPLES_CPP_OTEL_UTIL_H diff --git a/templates/CMakeLists.txt.template b/templates/CMakeLists.txt.template index 4b8ab51cac4..a3e7503f373 100644 --- a/templates/CMakeLists.txt.template +++ b/templates/CMakeLists.txt.template @@ -119,7 +119,10 @@ if lib_name == 'protobuf': # TODO(jtattermusch): add better way of excluding explicit protobuf dependency. continue - requires.add(lib_name) + if lib_name == 'opentelemetry-cpp::api': + requires.add('opentelemetry_api') + else: + requires.add(lib_name) return list(sorted(requires)) def get_pkgconfig_requires_private(lib): @@ -1142,3 +1145,14 @@ "${" ".join(get_pkgconfig_libs("grpc++_unsecure"))}" "${" ".join(get_pkgconfig_libs_private("grpc++_unsecure"))}" "grpc++_unsecure.pc") + + # grpcpp_otel_plugin .pc file + generate_pkgconfig( + "gRPC++ OpenTelemetry Plugin" + "OpenTelemetry Plugin for gRPC C++" + "<%text>${gRPC_CPP_VERSION}" + "${" ".join(get_pkgconfig_requires("grpcpp_otel_plugin"))}" + "${" ".join(get_pkgconfig_requires_private("grpcpp_otel_plugin"))}" + "${" ".join(get_pkgconfig_libs("grpcpp_otel_plugin"))}" + "${" ".join(get_pkgconfig_libs_private("grpcpp_otel_plugin"))}" + "grpcpp_otel_plugin.pc") diff --git a/test/distrib/cpp/run_distrib_test_cmake_pkgconfig.sh b/test/distrib/cpp/run_distrib_test_cmake_pkgconfig.sh index 48e2a4d25e2..3e3fb3ac57b 100755 --- a/test/distrib/cpp/run_distrib_test_cmake_pkgconfig.sh +++ b/test/distrib/cpp/run_distrib_test_cmake_pkgconfig.sh @@ -65,6 +65,13 @@ cmake -DCMAKE_BUILD_TYPE=Release ../.. make "-j${GRPC_CPP_DISTRIBTEST_BUILD_COMPILER_JOBS}" install popd +# Install OpenTelemetry +mkdir -p "third_party/opentelemetry-cpp/cmake/build" +pushd "third_party/opentelemetry-cpp/cmake/build" +cmake -DCMAKE_BUILD_TYPE=Release -DWITH_ABSEIL=ON -DBUILD_TESTING=OFF -DWITH_BENCHMARK=OFF ../.. +make "-j${GRPC_CPP_DISTRIBTEST_BUILD_COMPILER_JOBS}" install +popd + # Just before installing gRPC, wipe out contents of all the submodules to simulate # a standalone build from an archive. # Get list of submodules from the .gitmodules file since for "git submodule foreach" @@ -88,6 +95,7 @@ cmake \ -DgRPC_RE2_PROVIDER=package \ -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package \ + -DgRPC_BUILD_GRPCPP_OTEL_PLUGIN=ON \ ../.. make "-j${GRPC_CPP_DISTRIBTEST_BUILD_COMPILER_JOBS}" install popd @@ -111,3 +119,8 @@ popd pushd examples/cpp/route_guide make "-j${GRPC_CPP_DISTRIBTEST_BUILD_COMPILER_JOBS}" popd + +# Build otel example using Makefile and pkg-config +pushd examples/cpp/otel/ostream +make "-j${GRPC_CPP_DISTRIBTEST_BUILD_COMPILER_JOBS}" +popd