/* * * Copyright 2015, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test/cpp/util/create_test_channel.h" #include "test/cpp/qps/qpstest.grpc.pb.h" #include "test/cpp/qps/timer.h" #include "test/cpp/qps/client.h" namespace grpc { namespace testing { class ClientRpcContext { public: ClientRpcContext() {} virtual ~ClientRpcContext() {} // next state, return false if done. Collect stats when appropriate virtual bool RunNextState(bool, Histogram* hist) = 0; virtual ClientRpcContext* StartNewClone() = 0; static void* tag(ClientRpcContext* c) { return reinterpret_cast(c); } static ClientRpcContext* detag(void* t) { return reinterpret_cast(t); } deadline_list::iterator deadline_posn() const {return deadline_posn_;} void set_deadline_posn(deadline_list::iterator&& it) {deadline_posn_ = it;} virtual void Start() = 0; private: deadline_list::iterator deadline_posn_; }; template class ClientRpcContextUnaryImpl : public ClientRpcContext { public: ClientRpcContextUnaryImpl( TestService::Stub* stub, const RequestType& req, std::function< std::unique_ptr>( TestService::Stub*, grpc::ClientContext*, const RequestType&)> start_req, std::function on_done) : context_(), stub_(stub), req_(req), response_(), next_state_(&ClientRpcContextUnaryImpl::RespDone), callback_(on_done), start_req_(start_req) { } void Start() GRPC_OVERRIDE { start_ = Timer::Now(); response_reader_ = start_req_(stub_, &context_, req_); response_reader_->Finish(&response_, &status_, ClientRpcContext::tag(this)); } ~ClientRpcContextUnaryImpl() GRPC_OVERRIDE {} bool RunNextState(bool ok, Histogram* hist) GRPC_OVERRIDE { bool ret = (this->*next_state_)(ok); if (!ret) { hist->Add((Timer::Now() - start_) * 1e9); } return ret; } ClientRpcContext* StartNewClone() GRPC_OVERRIDE { return new ClientRpcContextUnaryImpl(stub_, req_, start_req_, callback_); } private: bool RespDone(bool) { next_state_ = &ClientRpcContextUnaryImpl::DoCallBack; return false; } bool DoCallBack(bool) { callback_(status_, &response_); return false; } grpc::ClientContext context_; TestService::Stub* stub_; RequestType req_; ResponseType response_; bool (ClientRpcContextUnaryImpl::*next_state_)(bool); std::function callback_; std::function>( TestService::Stub*, grpc::ClientContext*, const RequestType&)> start_req_; grpc::Status status_; double start_; std::unique_ptr> response_reader_; }; typedef std::forward_list deadline_list; typedef std::forward_list context_list; class AsyncClient : public Client { public: explicit AsyncClient(const ClientConfig& config, std::function setup_ctx) : Client(config), channel_rpc_lock_(config.client_channels()), max_outstanding_per_channel_(config.outstanding_rpcs_per_channel()), channel_count_(config.client_channels()) { SetupLoadTest(config, config.async_client_threads()); for (int i = 0; i < config.async_client_threads(); i++) { cli_cqs_.emplace_back(new CompletionQueue); if (!closed_loop_) { rpc_deadlines_.emplace_back(); next_channel_.push_back(i % channel_count_); issue_allowed_.push_back(true); grpc_time next_issue; NextIssueTime(i, &next_issue); next_issue_.push_back(next_issue); } } if (!closed_loop_) { for (auto channel = channels_.begin(); channel != channels_.end(); channel++) { rpcs_outstanding_.push_back(0); } } int t = 0; for (int i = 0; i < config.outstanding_rpcs_per_channel(); i++) { for (int ch = 0; ch < channel_count_; ch++) { auto channel = channels_[ch]; auto* cq = cli_cqs_[t].get(); t = (t + 1) % cli_cqs_.size(); ClientRpcContext *ctx = setup_ctx(cq, channel->get_stub(), request_); if (closed_loop_) { // only relevant for closed_loop unary, but harmless for // closed_loop streaming ctx->Start(); } else { contexts_[ch].push_front(ctx); } } } } virtual ~AsyncClient() { for (auto cq = cli_cqs_.begin(); cq != cli_cqs_.end(); cq++) { (*cq)->Shutdown(); void* got_tag; bool ok; while ((*cq)->Next(&got_tag, &ok)) { delete ClientRpcContext::detag(got_tag); } } } bool ThreadFunc(Histogram* histogram, size_t thread_idx) GRPC_OVERRIDE GRPC_FINAL { void* got_tag; bool ok; grpc_time deadline, short_deadline; if (closed_loop_) { deadline = grpc_time_source::now() + std::chrono::seconds(1); short_deadline = deadline; } else { if (rpc_deadlines_[thread_idx].empty()) { deadline = grpc_time_source::now() + std::chrono::seconds(1); } else { deadline = *(rpc_deadlines_[thread_idx].begin()); } short_deadline = issue_allowed_[thread_idx] ? next_issue_[thread_idx] : deadline; } bool got_event; switch (cli_cqs_[thread_idx]->AsyncNext(&got_tag, &ok, short_deadline)) { case CompletionQueue::SHUTDOWN: return false; case CompletionQueue::TIMEOUT: got_event = false; break; case CompletionQueue::GOT_EVENT: got_event = true; break; default: GPR_ASSERT(false); break; } if ((closed_loop_ || !rpc_deadlines_[thread_idx].empty()) && grpc_time_source::now() > deadline) { // we have missed some 1-second deadline, which is too much gpr_log(GPR_INFO, "Missed an RPC deadline, giving up"); return false; } if (got_event) { ClientRpcContext* ctx = ClientRpcContext::detag(got_tag); if (ctx->RunNextState(ok, histogram) == false) { // call the callback and then delete it rpc_deadlines_[thread_idx].erase_after(ctx->deadline_posn()); ctx->RunNextState(ok, histogram); ClientRpcContext *clone_ctx = ctx->StartNewClone(); delete ctx; if (!closed_loop_) { // Put this in the list of idle contexts for this channel } } issue_allowed_[thread_idx] = true; // may be ok now even if it hadn't been } if (issue_allowed_[thread_idx] && grpc_time_source::now() >= next_issue_[thread_idx]) { // Attempt to issue bool issued = false; for (int num_attempts = 0; num_attempts < channel_count_ && !issued; num_attempts++, next_channel_[thread_idx] = (next_channel_[thread_idx]+1)%channel_count_) { std::lock_guard g(channel_rpc_lock_[next_channel_[thread_idx]]); if ((rpcs_outstanding_[next_channel_[thread_idx]] < max_outstanding_per_channel_) && !contexts_[next_channel_[thread_idx]].empty()) { // Get an idle context from the front of the list auto ctx = contexts_[next_channel_[thread_idx]].begin(); contexts_[next_channel_[thread_idx]].pop_front(); // do the work to issue ctx->Start(); rpcs_outstanding_[next_channel_[thread_idx]]++; issued = true; } } if (!issued) issue_allowed_[thread_idx] = false; } return true; } private: std::vector> cli_cqs_; std::vector rpc_deadlines_; // per thread deadlines std::vector next_channel_; // per thread round-robin channel ctr std::vector issue_allowed_; // may this thread attempt to issue std::vector next_issue_; // when should it issue? std::vector channel_rpc_lock_; std::vector rpcs_outstanding_; // per-channel vector std::vector contexts_; // per-channel list of idle contexts int max_outstanding_per_channel_; int channel_count_; }; class AsyncUnaryClient GRPC_FINAL : public AsyncClient { public: explicit AsyncUnaryClient(const ClientConfig& config) : AsyncClient(config, SetupCtx) { StartThreads(config.async_client_threads()); } ~AsyncUnaryClient() GRPC_OVERRIDE { EndThreads(); } private: static ClientRpcContext *SetupCtx(CompletionQueue* cq, TestService::Stub* stub, const SimpleRequest& req) { auto check_done = [](grpc::Status s, SimpleResponse* response) {}; auto start_req = [cq](TestService::Stub* stub, grpc::ClientContext* ctx, const SimpleRequest& request) { return stub->AsyncUnaryCall(ctx, request, cq); }; return new ClientRpcContextUnaryImpl( stub, req, start_req, check_done); } }; template class ClientRpcContextStreamingImpl : public ClientRpcContext { public: ClientRpcContextStreamingImpl( TestService::Stub* stub, const RequestType& req, std::function>( TestService::Stub*, grpc::ClientContext*, void*)> start_req, std::function on_done) : context_(), stub_(stub), req_(req), response_(), next_state_(&ClientRpcContextStreamingImpl::ReqSent), callback_(on_done), start_req_(start_req), start_(Timer::Now()), stream_(start_req_(stub_, &context_, ClientRpcContext::tag(this))) {} ~ClientRpcContextStreamingImpl() GRPC_OVERRIDE {} bool RunNextState(bool ok, Histogram* hist) GRPC_OVERRIDE { return (this->*next_state_)(ok, hist); } ClientRpcContext* StartNewClone() GRPC_OVERRIDE { return new ClientRpcContextStreamingImpl(stub_, req_, start_req_, callback_); } void Start() GRPC_OVERRIDE {} private: bool ReqSent(bool ok, Histogram*) { return StartWrite(ok); } bool StartWrite(bool ok) { if (!ok) { return (false); } start_ = Timer::Now(); next_state_ = &ClientRpcContextStreamingImpl::WriteDone; stream_->Write(req_, ClientRpcContext::tag(this)); return true; } bool WriteDone(bool ok, Histogram*) { if (!ok) { return (false); } next_state_ = &ClientRpcContextStreamingImpl::ReadDone; stream_->Read(&response_, ClientRpcContext::tag(this)); return true; } bool ReadDone(bool ok, Histogram* hist) { hist->Add((Timer::Now() - start_) * 1e9); return StartWrite(ok); } grpc::ClientContext context_; TestService::Stub* stub_; RequestType req_; ResponseType response_; bool (ClientRpcContextStreamingImpl::*next_state_)(bool, Histogram*); std::function callback_; std::function< std::unique_ptr>( TestService::Stub*, grpc::ClientContext*, void*)> start_req_; grpc::Status status_; double start_; std::unique_ptr> stream_; }; class AsyncStreamingClient GRPC_FINAL : public AsyncClient { public: explicit AsyncStreamingClient(const ClientConfig& config) : AsyncClient(config, SetupCtx) { StartThreads(config.async_client_threads()); } ~AsyncStreamingClient() GRPC_OVERRIDE { EndThreads(); } private: static ClientRpcContext *SetupCtx(CompletionQueue* cq, TestService::Stub* stub, const SimpleRequest& req) { auto check_done = [](grpc::Status s, SimpleResponse* response) {}; auto start_req = [cq](TestService::Stub* stub, grpc::ClientContext* ctx, void* tag) { auto stream = stub->AsyncStreamingCall(ctx, cq, tag); return stream; }; return new ClientRpcContextStreamingImpl( stub, req, start_req, check_done); } }; std::unique_ptr CreateAsyncUnaryClient(const ClientConfig& args) { return std::unique_ptr(new AsyncUnaryClient(args)); } std::unique_ptr CreateAsyncStreamingClient(const ClientConfig& args) { return std::unique_ptr(new AsyncStreamingClient(args)); } } // namespace testing } // namespace grpc