| // Copyright (c) 2014 baidu-rpc 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. |
| |
| // A performance test. |
| |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <vector> |
| #include <butil/atomicops.h> |
| #include <butil/fast_rand.h> |
| #include <butil/logging.h> |
| #include <brpc/channel.h> |
| #include <brpc/rdma/rdma_fallback_channel.h> |
| #include <brpc/rdma/rdma_helper.h> |
| #include <brpc/server.h> |
| #include <bthread/bthread.h> |
| #include <bvar/latency_recorder.h> |
| #include <bvar/variable.h> |
| #include <gflags/gflags.h> |
| #include "test.pb.h" |
| |
| DEFINE_int32(thread_num, 0, "How many threads are used"); |
| DEFINE_int32(max_thread_num, 16, "The max number of threads are used"); |
| DEFINE_int32(attachment_size, -1, "Attachment size is used (in KB)"); |
| DEFINE_bool(specify_attachment_addr, false, "Specify the address of attachment"); |
| DEFINE_bool(echo_attachment, false, "Select whether attachment should be echo"); |
| DEFINE_string(connection_type, "single", "Connection type of the channel"); |
| DEFINE_string(protocol, "baidu_std", "Protocol type."); |
| DEFINE_string(servers, "0.0.0.0:8002+0.0.0.0:8002", "IP Address of servers"); |
| DEFINE_bool(use_rdma, true, "Use RDMA or not"); |
| DEFINE_int32(rpc_timeout_ms, 2000, "RPC call timeout"); |
| DEFINE_int32(test_seconds, 20, "Test running time"); |
| DEFINE_int32(test_iterations, 0, "Test iterations"); |
| DEFINE_bool(use_fallback_channel, false, "Use RdmaFallbackChannel"); |
| |
| bvar::LatencyRecorder g_latency_recorder("client"); |
| bvar::LatencyRecorder g_server_cpu_recorder("server_cpu"); |
| bvar::LatencyRecorder g_client_cpu_recorder("client_cpu"); |
| butil::atomic<uint64_t> g_last_time(0); |
| butil::atomic<uint64_t> g_total_bytes; |
| butil::atomic<uint64_t> g_total_cnt; |
| std::vector<std::string> g_servers; |
| int rr_index = 0; |
| |
| class PerformanceTest { |
| public: |
| PerformanceTest(int attachment_size, bool echo_attachment) |
| : _addr(NULL) |
| , _fchannel(NULL) |
| , _channel(NULL) |
| , _cntl(NULL) |
| , _response(NULL) |
| , _start_time(0) |
| , _iterations(0) |
| , _stop(false) |
| { |
| attachment_size *= 1024; |
| |
| if (attachment_size > 0) { |
| _addr = malloc(attachment_size); |
| butil::fast_rand_bytes(_addr, attachment_size); |
| if (FLAGS_specify_attachment_addr) { |
| brpc::rdma::RegisterMemoryForRdma(_addr, attachment_size); |
| _attachment.append_user_data(_addr, attachment_size, NULL, NULL); |
| } else { |
| _attachment.append(_addr, attachment_size); |
| } |
| } |
| _echo_attachment = echo_attachment; |
| } |
| |
| ~PerformanceTest() { |
| if (_addr) { |
| if (FLAGS_specify_attachment_addr) { |
| brpc::rdma::DeregisterMemoryForRdma(_addr); |
| } |
| free(_addr); |
| } |
| delete _fchannel; |
| delete _channel; |
| } |
| |
| inline bool IsStop() { return _stop; } |
| |
| int Init() { |
| brpc::ChannelOptions options; |
| options.use_rdma = FLAGS_use_rdma; |
| options.protocol = FLAGS_protocol; |
| options.connection_type = FLAGS_connection_type; |
| options.timeout_ms = FLAGS_rpc_timeout_ms; |
| options.max_retry = 0; |
| std::string server = g_servers[(rr_index++) % g_servers.size()]; |
| if (FLAGS_use_fallback_channel) { |
| _fchannel = new brpc::rdma::RdmaFallbackChannel(); |
| if (_fchannel->Init(server.c_str(), &options) != 0) { |
| LOG(ERROR) << "Fail to initialize channel"; |
| return -1; |
| } |
| } else { |
| _channel = new brpc::Channel(); |
| if (_channel->Init(server.c_str(), &options) != 0) { |
| LOG(ERROR) << "Fail to initialize channel"; |
| return -1; |
| } |
| } |
| brpc::Controller cntl; |
| test::PerfTestResponse response; |
| test::PerfTestRequest request; |
| request.set_echo_attachment(_echo_attachment); |
| if (FLAGS_use_fallback_channel) { |
| test::PerfTestService_Stub stub(_fchannel); |
| stub.Test(&cntl, &request, &response, NULL); |
| } else { |
| test::PerfTestService_Stub stub(_channel); |
| stub.Test(&cntl, &request, &response, NULL); |
| } |
| if (cntl.Failed()) { |
| LOG(ERROR) << "RPC call failed: " << cntl.ErrorText(); |
| return -1; |
| } |
| return 0; |
| } |
| |
| void SendRequest() { |
| test::PerfTestRequest request; |
| _response = new test::PerfTestResponse(); |
| _cntl = new brpc::Controller(); |
| request.set_echo_attachment(_echo_attachment); |
| _cntl->request_attachment().append(_attachment); |
| google::protobuf::Closure* done = brpc::NewCallback(&HandleResponse, this); |
| if (FLAGS_use_fallback_channel) { |
| test::PerfTestService_Stub stub(_fchannel); |
| stub.Test(_cntl, &request, _response, done); |
| } else { |
| test::PerfTestService_Stub stub(_channel); |
| stub.Test(_cntl, &request, _response, done); |
| } |
| } |
| |
| static void HandleResponse(PerformanceTest* test) { |
| std::unique_ptr<brpc::Controller> cntl_guard(test->_cntl); |
| std::unique_ptr<test::PerfTestResponse> response_guard(test->_response); |
| if (test->_cntl->Failed()) { |
| LOG(ERROR) << "RPC call failed: " << test->_cntl->ErrorText(); |
| test->_stop = true; |
| return; |
| } |
| |
| g_latency_recorder << test->_cntl->latency_us(); |
| if (test->_response->cpu_usage().size() > 0) { |
| g_server_cpu_recorder << atof(test->_response->cpu_usage().c_str()) * 100; |
| } |
| g_total_bytes.fetch_add(test->_attachment.size(), |
| butil::memory_order_relaxed); |
| g_total_cnt.fetch_add(1, butil::memory_order_relaxed); |
| |
| cntl_guard.reset(NULL); |
| response_guard.reset(NULL); |
| |
| if (test->_iterations == 0 && FLAGS_test_iterations > 0) { |
| test->_stop = true; |
| return; |
| } |
| --test->_iterations; |
| uint64_t last = g_last_time.load(butil::memory_order_relaxed); |
| uint64_t now = butil::gettimeofday_us(); |
| if (now > last && now - last > 100000) { |
| if (g_last_time.exchange(now, butil::memory_order_relaxed) == last) { |
| g_client_cpu_recorder << |
| atof(bvar::Variable::describe_exposed("process_cpu_usage").c_str()) * 100; |
| } |
| } |
| if (now - test->_start_time > FLAGS_test_seconds * 1000000u) { |
| test->_stop = true; |
| return; |
| } |
| test->SendRequest(); |
| } |
| |
| static void* RunTest(void* arg) { |
| PerformanceTest* test = (PerformanceTest*)arg; |
| test->_start_time = butil::gettimeofday_us(); |
| test->_iterations = FLAGS_test_iterations; |
| |
| test->SendRequest(); |
| |
| return NULL; |
| } |
| |
| private: |
| void* _addr; |
| brpc::rdma::RdmaFallbackChannel* _fchannel; |
| brpc::Channel* _channel; |
| brpc::Controller* _cntl; |
| test::PerfTestResponse* _response; |
| uint64_t _start_time; |
| uint32_t _iterations; |
| volatile bool _stop; |
| butil::IOBuf _attachment; |
| bool _echo_attachment; |
| }; |
| |
| static void* DeleteTest(void* arg) { |
| PerformanceTest* test = (PerformanceTest*)arg; |
| delete test; |
| return NULL; |
| } |
| |
| void Test(int thread_num, int attachment_size) { |
| std::cout << "[Threads: " << thread_num |
| << ", Attachment: " << attachment_size << "KB" |
| << ", Echo: " << (FLAGS_echo_attachment ? "yes]" : "no]") << std::flush; |
| g_total_bytes.store(0, butil::memory_order_relaxed); |
| g_total_cnt.store(0, butil::memory_order_relaxed); |
| std::vector<PerformanceTest*> tests; |
| for (int k = 0; k < thread_num; ++k) { |
| PerformanceTest* t = new PerformanceTest(attachment_size, FLAGS_echo_attachment); |
| if (t->Init() < 0) { |
| exit(1); |
| } |
| tests.push_back(t); |
| } |
| uint64_t start_time = butil::gettimeofday_us(); |
| bthread_t tid[thread_num]; |
| for (int k = 0; k < thread_num; ++k) { |
| bthread_start_background(&tid[k], &BTHREAD_ATTR_NORMAL, |
| PerformanceTest::RunTest, tests[k]); |
| } |
| for (int k = 0; k < thread_num; ++k) { |
| while (!tests[k]->IsStop()) { |
| bthread_usleep(10000); |
| } |
| } |
| uint64_t end_time = butil::gettimeofday_us(); |
| double throughput = g_total_bytes / 1.048576 / (end_time - start_time); |
| if (FLAGS_test_iterations == 0) { |
| std::cout << " Avg-Latency: " << g_latency_recorder.latency(10) << "us" |
| << ", 90th-Latency: " << g_latency_recorder.latency_percentile(0.9) << "us" |
| << ", 99th-Latency: " << g_latency_recorder.latency_percentile(0.99) << "us" |
| << ", 99.9th-Latency: " << g_latency_recorder.latency_percentile(0.999) << "us" |
| << ", Throughput: " << throughput << "MB/s" |
| << ", QPS: " << g_total_cnt.load(butil::memory_order_relaxed) * 1000 / (end_time - start_time) << "k" |
| << ", Server CPU-utilization: " << g_server_cpu_recorder.latency(10) << "\%" |
| << ", Client CPU-utilization: " << g_client_cpu_recorder.latency(10) << "\%" |
| << std::endl; |
| } else { |
| std::cout << " Throughput: " << throughput << "MB/s" << std::endl; |
| } |
| for (int k = 0; k < thread_num; ++k) { |
| bthread_start_background(&tid[k], &BTHREAD_ATTR_NORMAL, DeleteTest, tests[k]); |
| } |
| } |
| |
| int main(int argc, char* argv[]) { |
| google::ParseCommandLineFlags(&argc, &argv, true); |
| |
| // Initialize RDMA environment in advance. |
| if (FLAGS_use_rdma) { |
| brpc::rdma::GlobalRdmaInitializeOrDie(); |
| } |
| |
| brpc::StartDummyServerAt(8001); |
| |
| std::string::size_type pos1 = 0; |
| std::string::size_type pos2 = FLAGS_servers.find('+'); |
| while (pos2 != std::string::npos) { |
| g_servers.push_back(FLAGS_servers.substr(pos1, pos2 - pos1)); |
| pos1 = pos2 + 1; |
| pos2 = FLAGS_servers.find('+', pos1); |
| } |
| g_servers.push_back(FLAGS_servers.substr(pos1)); |
| |
| if (FLAGS_thread_num > 0 && FLAGS_attachment_size >= 0) { |
| Test(FLAGS_thread_num, FLAGS_attachment_size); |
| } else if (FLAGS_thread_num <= 0 && FLAGS_attachment_size >= 0) { |
| for (int i = 1; i <= FLAGS_max_thread_num; i *= 2) { |
| Test(i, FLAGS_attachment_size); |
| } |
| } else if (FLAGS_thread_num > 0 && FLAGS_attachment_size < 0) { |
| for (int i = 1; i <= 1024; i *= 4) { |
| Test(FLAGS_thread_num, i); |
| } |
| } else { |
| for (int j = 1; j <= 1024; j *= 4) { |
| for (int i = 1; i <= FLAGS_max_thread_num; i *= 2) { |
| Test(i, j); |
| } |
| } |
| } |
| |
| return 0; |
| } |