| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you 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 <gflags/gflags.h> | |
| #include <chrono> | |
| #include <iostream> | |
| #include <thread> | |
| #include "butil/config.h" | |
| #include "butil/endpoint.h" | |
| #include "brpc/closure_guard.h" | |
| #include "brpc/controller.h" | |
| #include "brpc/errno.pb.h" | |
| #include "brpc/server.h" | |
| #include "bthread/bthread.h" | |
| #include "echo.brpc.fb.h" | |
| #include "echo_pb.pb.h" | |
| #if !BRPC_WITH_FLATBUFFERS | |
| #error "benchmark_fb requires a FlatBuffers-enabled brpc library" | |
| #endif | |
| DEFINE_string(listen_addr, "127.0.0.1:0", "IPv4 address; port 0 selects a free port. " | |
| "A non-loopback address also needs --allow_remote"); | |
| DEFINE_bool(allow_remote, false, "Allow a non-loopback --listen_addr for cross-host benchmarks"); | |
| DEFINE_int32(duration_s, 60, "Exit after 1..86400 seconds, or earlier on SIGINT/SIGTERM"); | |
| DEFINE_int32(max_concurrency, 32, "Maximum concurrent requests, 1..4096"); | |
| DEFINE_int32(reply_delay_ms, 0, "Test aid: delay every reply by 0..10000 milliseconds"); | |
| namespace { | |
| const size_t kMaxBytes = 1024 * 1024; | |
| void DelayReply() { | |
| if (FLAGS_reply_delay_ms > 0) { | |
| // Sleep the handling bthread, not a worker pthread. | |
| bthread_usleep(static_cast<uint64_t>(FLAGS_reply_delay_ms) * 1000); | |
| } | |
| } | |
| // Reserve the echoed string plus table overhead up front so a large reply | |
| // does not grow the builder by repeated reallocate-and-copy. | |
| size_t BuilderSize(const benchmark_fb::Request* input) { | |
| const size_t text = input->message() ? input->message()->size() : 0; | |
| return text + 256; | |
| } | |
| class BenchmarkServiceImpl : public benchmark_fb::BenchmarkService { | |
| public: | |
| void Echo(google::protobuf::RpcController* controller, | |
| const brpc::flatbuffers::Message* request, | |
| brpc::flatbuffers::Message* response, | |
| google::protobuf::Closure* done) override { | |
| brpc::ClosureGuard done_guard(done); | |
| if (!controller) { | |
| return; | |
| } | |
| auto* cntl = static_cast<brpc::Controller*>(controller); | |
| // The generated dispatcher has already verified the request schema, | |
| // so verifying it again here would only add cost to the fb_rpc side. | |
| if (!request || !response) { | |
| cntl->SetFailed(brpc::EREQUEST, "Invalid FlatBuffers Request"); | |
| return; | |
| } | |
| const auto* input = request->GetRoot<benchmark_fb::Request>(); | |
| if ((input->message() && input->message()->size() > kMaxBytes) || | |
| cntl->request_attachment().size() > kMaxBytes) { | |
| cntl->SetFailed(brpc::EREQUEST, "Example payload limit is 1 MiB per field"); | |
| return; | |
| } | |
| brpc::flatbuffers::MessageBuilder builder(BuilderSize(input)); | |
| // Preserve absent versus empty strings, including embedded zero bytes. | |
| const auto text = input->message() ? | |
| builder.CreateString(input->message()->data(), input->message()->size()) : | |
| ::flatbuffers::Offset<::flatbuffers::String>(); | |
| builder.Finish(benchmark_fb::CreateResponse( | |
| builder, input->request_id(), text, cntl->request_attachment().size())); | |
| *response = builder.ReleaseMessage(); | |
| cntl->response_attachment().append(cntl->request_attachment()); | |
| DelayReply(); | |
| } | |
| }; | |
| // Protobuf control service over baidu_std with the same echo semantics. | |
| class PbBenchmarkServiceImpl : public benchmark_fb_pb::BenchmarkService { | |
| public: | |
| void Echo(google::protobuf::RpcController* controller, | |
| const benchmark_fb_pb::Request* request, | |
| benchmark_fb_pb::Response* response, | |
| google::protobuf::Closure* done) override { | |
| brpc::ClosureGuard done_guard(done); | |
| auto* cntl = static_cast<brpc::Controller*>(controller); | |
| if (request->message().size() > kMaxBytes || | |
| cntl->request_attachment().size() > kMaxBytes) { | |
| cntl->SetFailed(brpc::EREQUEST, "Example payload limit is 1 MiB per field"); | |
| return; | |
| } | |
| response->set_request_id(request->request_id()); | |
| if (request->has_message()) { | |
| response->set_message(request->message()); | |
| } | |
| response->set_attachment_size(cntl->request_attachment().size()); | |
| cntl->response_attachment().append(cntl->request_attachment()); | |
| DelayReply(); | |
| } | |
| }; | |
| } // namespace | |
| int main(int argc, char* argv[]) { | |
| GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); | |
| butil::EndPoint endpoint; | |
| if ((FLAGS_listen_addr.compare(0, 10, "127.0.0.1:") != 0 && !FLAGS_allow_remote) || | |
| butil::str2endpoint(FLAGS_listen_addr.c_str(), &endpoint) != 0 || | |
| FLAGS_duration_s < 1 || FLAGS_duration_s > 86400 || | |
| FLAGS_max_concurrency < 1 || FLAGS_max_concurrency > 4096 || | |
| FLAGS_reply_delay_ms < 0 || FLAGS_reply_delay_ms > 10000) { | |
| std::cerr << "Use --listen_addr=127.0.0.1:PORT (or --allow_remote), " | |
| "--duration_s=1..86400, --max_concurrency=1..4096 " | |
| "and --reply_delay_ms=0..10000\n"; | |
| return 2; | |
| } | |
| // brpc defaults SIGTERM to immediate termination. This example always | |
| // enables graceful shutdown, including the smoke's owned-process cleanup. | |
| if (GFLAGS_NAMESPACE::SetCommandLineOption( | |
| "graceful_quit_on_sigterm", "true").empty()) { | |
| std::cerr << "Cannot enable graceful SIGTERM handling\n"; | |
| return 1; | |
| } | |
| BenchmarkServiceImpl service; | |
| brpc::Server server; | |
| if (server.AddFlatBuffersService(&service, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { | |
| std::cerr << "Cannot register FlatBuffers service\n"; | |
| return 1; | |
| } | |
| PbBenchmarkServiceImpl pb_service; | |
| if (server.AddService(&pb_service, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { | |
| std::cerr << "Cannot register protobuf control service\n"; | |
| return 1; | |
| } | |
| brpc::ServerOptions options; | |
| options.has_builtin_services = false; | |
| options.enabled_protocols = "fb_rpc baidu_std"; | |
| options.max_concurrency = FLAGS_max_concurrency; | |
| if (server.Start(endpoint, &options) != 0) { | |
| std::cerr << "Cannot start FlatBuffers server\n"; | |
| return 1; | |
| } | |
| // Install brpc's termination handlers before publishing readiness. | |
| brpc::IsAskedToQuit(); | |
| std::cout << "BRPC_FB_READY " << server.listen_address() << std::endl; | |
| const auto deadline = std::chrono::steady_clock::now() + | |
| std::chrono::seconds(FLAGS_duration_s); | |
| while (!brpc::IsAskedToQuit() && std::chrono::steady_clock::now() < deadline) { | |
| std::this_thread::sleep_for(std::chrono::milliseconds(50)); | |
| } | |
| if (!brpc::IsAskedToQuit()) { | |
| std::cerr << "--duration_s=" << FLAGS_duration_s << " elapsed, stopping\n"; | |
| } | |
| const int stop_result = server.Stop(0); | |
| const int join_result = server.Join(); | |
| return stop_result == 0 && join_result == 0 ? 0 : 1; | |
| } |