From 2cacb959c7d442102d157178405ab07a9f74bb84 Mon Sep 17 00:00:00 2001 From: Eugene Ostroukhov Date: Mon, 4 Dec 2023 15:38:07 -0800 Subject: [PATCH] [examples] Add custom metrics example --- examples/cpp/cmake/common.cmake | 1 + examples/cpp/orca/BUILD | 29 +++++++++ examples/cpp/orca/CMakeLists.txt | 70 +++++++++++++++++++++ examples/cpp/orca/README.md | 46 ++++++++++++++ examples/cpp/orca/orca_server.cc | 105 +++++++++++++++++++++++++++++++ 5 files changed, 251 insertions(+) create mode 100644 examples/cpp/orca/BUILD create mode 100644 examples/cpp/orca/CMakeLists.txt create mode 100644 examples/cpp/orca/README.md create mode 100644 examples/cpp/orca/orca_server.cc diff --git a/examples/cpp/cmake/common.cmake b/examples/cpp/cmake/common.cmake index 5c3d1053182d17..df9bc4db0f87ff 100644 --- a/examples/cpp/cmake/common.cmake +++ b/examples/cpp/cmake/common.cmake @@ -51,6 +51,7 @@ if(GRPC_AS_SUBMODULE) # this build. set(_PROTOBUF_LIBPROTOBUF libprotobuf) set(_REFLECTION grpc++_reflection) + set(_ORCA_SERVICE grpcpp_orca_service) if(CMAKE_CROSSCOMPILING) find_program(_PROTOBUF_PROTOC protoc) else() diff --git a/examples/cpp/orca/BUILD b/examples/cpp/orca/BUILD new file mode 100644 index 00000000000000..f6641932a91b2d --- /dev/null +++ b/examples/cpp/orca/BUILD @@ -0,0 +1,29 @@ +# 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 = "orca_server", + srcs = ["orca_server.cc"], + defines = ["BAZEL_BUILD"], + deps = [ + "//:grpc++", + "//:grpcpp_orca_service", + "//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/orca/CMakeLists.txt b/examples/cpp/orca/CMakeLists.txt new file mode 100644 index 00000000000000..e8a069316b8b37 --- /dev/null +++ b/examples/cpp/orca/CMakeLists.txt @@ -0,0 +1,70 @@ +# 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++ helloworld example. +# Assumes protobuf and gRPC 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.8) + +project(HelloWorld C CXX) + +include(../cmake/common.cmake) + +# 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}") + +# 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}) + +add_executable(orca_server + "orca_server.cc" + PRIVATE "../../../src/cpp/server/orca/orca_service.cc") +target_include_directories(orca_server "../../../src/core/ext/upb-gen/") +target_link_libraries(orca_server + hw_grpc_proto + absl::flags + absl::flags_parse + ${_GRPC_GRPCPP} + ${_ORCA_SERVICE} + ${_PROTOBUF_LIBPROTOBUF}) + diff --git a/examples/cpp/orca/README.md b/examples/cpp/orca/README.md new file mode 100644 index 00000000000000..680a4970e59c70 --- /dev/null +++ b/examples/cpp/orca/README.md @@ -0,0 +1,46 @@ +# gRPC Custom Metrics Example + +You can find a complete set of instructions for building gRPC and running the +examples in the [C++ Quick Start][]. + +This example shows how to implement a server that provides custom metrics usable +by custom load balancing policies. + +Server needs to be setup with metrics recorder and Orca service for sending +these metrics to a client: + +```c++ +GreeterServiceImpl service; +// Setup custom metrics recording +auto server_metric_recorder = + grpc::experimental::ServerMetricRecorder::Create(); +grpc::experimental::OrcaService orca_service( + server_metric_recorder.get(), + grpc::experimental::OrcaService::Options().set_min_report_duration( + absl::Seconds(0.1))); +builder.RegisterService(&orca_service); +grpc::ServerBuilder::experimental_type(&builder).EnableCallMetricRecording( + nullptr); +``` + +Afterwards per-request metrics can be reported from the gRPC service +implementation using the metric recorder from the request context: + +```c++ +auto recorder = context->ExperimentalGetCallMetricRecorder(); +if (recorder == nullptr) { + return Status(grpc::StatusCode::INTERNAL, + "Unable to access metrics recorder. Make sure " + "EnableCallMetricRecording had been called."); +} +recorder->RecordCpuUtilizationMetric(0.5); +``` + +Out of band metrics can be reported using the `server_metric_recorder` +directly: + +```c++ +server_metric_recorder->SetCpuUtilization(0.75); +``` + +[C++ Quick Start]: https://grpc.io/docs/languages/cpp/quickstart diff --git a/examples/cpp/orca/orca_server.cc b/examples/cpp/orca/orca_server.cc new file mode 100644 index 00000000000000..a1171b149f364f --- /dev/null +++ b/examples/cpp/orca/orca_server.cc @@ -0,0 +1,105 @@ +/* + * + * 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. + * + */ + +#include +#include +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/strings/str_format.h" + +#include +#include +#include + +#ifdef BAZEL_BUILD +#include "examples/protos/helloworld.grpc.pb.h" + +#include +#include +#else +#include "../../../include/grpcpp/ext/call_metric_recorder.h" +#include "../../../include/grpcpp/ext/orca_service.h" +#include "helloworld.grpc.pb.h" +#endif + +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::Status; +using helloworld::Greeter; +using helloworld::HelloReply; +using helloworld::HelloRequest; + +ABSL_FLAG(uint16_t, port, 50051, "Server port for the service"); + +// Logic and data behind the server's behavior. +class GreeterServiceImpl final : public Greeter::Service { + Status SayHello(ServerContext* context, const HelloRequest* request, + HelloReply* reply) override { + auto recorder = context->ExperimentalGetCallMetricRecorder(); + if (recorder == nullptr) { + return Status(grpc::StatusCode::INTERNAL, + "Unable to access metrics recorder. Make sure " + "EnableCallMetricRecording had been called."); + } + recorder->RecordRequestCostMetric("db_queries", 10); + recorder->RecordCpuUtilizationMetric(0.5); + std::string prefix("Hello "); + reply->set_message(prefix + request->name()); + return Status::OK; + } +}; + +void RunServer(uint16_t port) { + std::string server_address = absl::StrFormat("0.0.0.0:%d", port); + ServerBuilder builder; + GreeterServiceImpl service; + // Setup custom metrics recording + auto server_metric_recorder = + grpc::experimental::ServerMetricRecorder::Create(); + grpc::experimental::OrcaService orca_service( + server_metric_recorder.get(), + grpc::experimental::OrcaService::Options().set_min_report_duration( + absl::Seconds(0.1))); + builder.RegisterService(&orca_service); + grpc::ServerBuilder::experimental_type(&builder).EnableCallMetricRecording( + server_metric_recorder.get()); + // Resume setting up gRPC server as usual + grpc::EnableDefaultHealthCheckService(true); + // 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); + RunServer(absl::GetFlag(FLAGS_port)); + return 0; +}