/** * * press.cc * An Tao * * Copyright 2018, An Tao. All rights reserved. * https://github.com/an-tao/drogon * Use of this source code is governed by the MIT license * that can be found in the License file. * * Drogon * */ #include "press.h" #include "cmd.h" #include #include #include #include #include #ifndef _WIN32 #include #endif using namespace drogon_ctl; std::string press::detail() { return "Use press command to do stress testing\n" "Usage:drogon_ctl press \n" " -n num number of requests(default : 1)\n" " -t num number of threads(default : 1)\n" " -c num concurrent connections(default : 1)\n" // " -k keep alive(default: no)\n" " -q no progress indication(default: no)\n\n" "example: drogon_ctl press -n 10000 -c 100 -t 4 -q " "http://localhost:8080/index.html\n"; } void outputErrorAndExit(const string_view &err) { std::cout << err << std::endl; exit(1); } void press::handleCommand(std::vector ¶meters) { for (auto iter = parameters.begin(); iter != parameters.end(); iter++) { auto ¶m = *iter; if (param.find("-n") == 0) { if (param == "-n") { ++iter; if (iter == parameters.end()) { outputErrorAndExit("No number of requests!"); } auto &num = *iter; try { numOfRequests_ = std::stoll(num); } catch (...) { outputErrorAndExit("Invalid number of requests!"); } continue; } else { auto num = param.substr(2); try { numOfRequests_ = std::stoll(num); } catch (...) { outputErrorAndExit("Invalid number of requests!"); } continue; } } else if (param.find("-t") == 0) { if (param == "-t") { ++iter; if (iter == parameters.end()) { outputErrorAndExit("No number of threads!"); } auto &num = *iter; try { numOfThreads_ = std::stoll(num); } catch (...) { outputErrorAndExit("Invalid number of threads!"); } continue; } else { auto num = param.substr(2); try { numOfThreads_ = std::stoll(num); } catch (...) { outputErrorAndExit("Invalid number of threads!"); } continue; } } else if (param.find("-c") == 0) { if (param == "-c") { ++iter; if (iter == parameters.end()) { outputErrorAndExit("No number of connections!"); } auto &num = *iter; try { numOfConnections_ = std::stoll(num); } catch (...) { outputErrorAndExit("Invalid number of connections!"); } continue; } else { auto num = param.substr(2); try { numOfConnections_ = std::stoll(num); } catch (...) { outputErrorAndExit("Invalid number of connections!"); } continue; } } // else if (param == "-k") // { // keepAlive_ = true; // continue; // } else if (param == "-q") { processIndication_ = false; } else if (param[0] != '-') { url_ = param; } } // std::cout << "n=" << numOfRequests_ << std::endl; // std::cout << "t=" << numOfThreads_ << std::endl; // std::cout << "c=" << numOfConnections_ << std::endl; // std::cout << "q=" << processIndication_ << std::endl; // std::cout << "url=" << url_ << std::endl; if (url_.empty() || url_.compare(0, 4, "http") != 0 || (url_.compare(4, 3, "://") != 0 && url_.compare(4, 4, "s://") != 0)) { outputErrorAndExit("Invalid URL"); } else { auto pos = url_.find("://"); auto posOfPath = url_.find("/", pos + 3); if (posOfPath == std::string::npos) { host_ = url_; path_ = "/"; } else { host_ = url_.substr(0, posOfPath); path_ = url_.substr(posOfPath); } } // std::cout << "host=" << host_ << std::endl; // std::cout << "path=" << path_ << std::endl; doTesting(); } void press::doTesting() { createRequestAndClients(); if (clients_.empty()) { outputErrorAndExit("No connection!"); } statistics_.startDate_ = trantor::Date::now(); for (auto &client : clients_) { sendRequest(client); } loopPool_->wait(); } void press::createRequestAndClients() { loopPool_ = std::make_unique(numOfThreads_); loopPool_->start(); for (size_t i = 0; i < numOfConnections_; ++i) { auto client = HttpClient::newHttpClient(host_, loopPool_->getNextLoop()); client->enableCookies(); clients_.push_back(client); } } void press::sendRequest(const HttpClientPtr &client) { auto numOfRequest = statistics_.numOfRequestsSent_++; if (numOfRequest >= numOfRequests_) { return; } auto request = HttpRequest::newHttpRequest(); request->setPath(path_); request->setMethod(Get); // std::cout << "send!" << std::endl; client->sendRequest( request, [this, client, request](ReqResult r, const HttpResponsePtr &resp) { size_t goodNum, badNum; if (r == ReqResult::Ok) { // std::cout << "OK" << std::endl; goodNum = ++statistics_.numOfGoodResponse_; badNum = statistics_.numOfBadResponse_; statistics_.bytesRecieved_ += resp->body().length(); auto delay = trantor::Date::now().microSecondsSinceEpoch() - request->creationDate().microSecondsSinceEpoch(); statistics_.totalDelay_ += delay; } else { goodNum = statistics_.numOfGoodResponse_; badNum = ++statistics_.numOfBadResponse_; if (badNum > numOfRequests_ / 10) { outputErrorAndExit("Too many errors"); } } if (goodNum + badNum >= numOfRequests_) { outputResults(); } if (r == ReqResult::Ok) sendRequest(client); else { client->getLoop()->runAfter(1, [this, client]() { sendRequest(client); }); } if (processIndication_) { auto rec = goodNum + badNum; if (rec % 100000 == 0) { std::cout << rec << " responses are received" << std::endl << std::endl; } } }); } void press::outputResults() { static std::mutex mtx; size_t totalSent = 0; size_t totalRecv = 0; for (auto &client : clients_) { totalSent += client->bytesSent(); totalRecv += client->bytesReceived(); } auto now = trantor::Date::now(); auto microSecs = now.microSecondsSinceEpoch() - statistics_.startDate_.microSecondsSinceEpoch(); double seconds = (double)microSecs / 1000000.0; size_t rps = static_cast(statistics_.numOfGoodResponse_ / seconds); std::cout << std::endl; std::cout << "TOTALS: " << numOfConnections_ << " connect, " << numOfRequests_ << " requests, " << statistics_.numOfGoodResponse_ << " success, " << statistics_.numOfBadResponse_ << " fail" << std::endl; std::cout << "TRAFFIC: " << statistics_.bytesRecieved_ / statistics_.numOfGoodResponse_ << " avg bytes, " << (totalRecv - statistics_.bytesRecieved_) / statistics_.numOfGoodResponse_ << " avg overhead, " << statistics_.bytesRecieved_ << " bytes, " << totalRecv - statistics_.bytesRecieved_ << " overhead" << std::endl; std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(3) << "TIMING: " << seconds << " seconds, " << rps << " rps, " << (double)(statistics_.totalDelay_) / statistics_.numOfGoodResponse_ / 1000 << " ms avg req time" << std::endl; std::cout << "SPEED: download " << totalRecv / seconds / 1000 << " kBps, upload " << totalSent / seconds / 1000 << " kBps" << std::endl << std::endl; exit(0); }