mirror of
https://gitee.com/an-tao/drogon.git
synced 2024-12-06 05:37:37 +08:00
Merge pull request #176 from an-tao/dev
Add stress testing command to drogon_ctl
This commit is contained in:
commit
ff9c05e088
318
drogon_ctl/press.cc
Normal file
318
drogon_ctl/press.cc
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 <drogon/DrClassMap.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
using namespace drogon_ctl;
|
||||||
|
std::string press::detail()
|
||||||
|
{
|
||||||
|
return "Use press command to do stress testing\n"
|
||||||
|
"Usage:drogon_ctl press <options> <url>\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<std::string> ¶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.find("http") != 0 ||
|
||||||
|
_url.find("://") == std::string::npos)
|
||||||
|
{
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
_stat._startDate = trantor::Date::now();
|
||||||
|
for (auto &client : _clients)
|
||||||
|
{
|
||||||
|
sendRequest(client);
|
||||||
|
}
|
||||||
|
_loopPool->wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void press::createRequestAndClients()
|
||||||
|
{
|
||||||
|
_loopPool = std::make_unique<trantor::EventLoopThreadPool>(_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 = _stat._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 = ++_stat._numOfGoodResponse;
|
||||||
|
badNum = _stat._numOfBadResponse;
|
||||||
|
_stat._bytesRecieved += resp->body().length();
|
||||||
|
auto delay = trantor::Date::now().microSecondsSinceEpoch() -
|
||||||
|
request->creationDate().microSecondsSinceEpoch();
|
||||||
|
_stat._totalDelay += delay;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
goodNum = _stat._numOfGoodResponse;
|
||||||
|
badNum = ++_stat._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() -
|
||||||
|
_stat._startDate.microSecondsSinceEpoch();
|
||||||
|
double seconds = (double)microSecs / 1000000.0;
|
||||||
|
size_t rps = _stat._numOfGoodResponse / seconds;
|
||||||
|
std::cout << std::endl;
|
||||||
|
std::cout << "TOTALS: " << _numOfConnections << " connect, "
|
||||||
|
<< _numOfRequests << " requests, " << _stat._numOfGoodResponse
|
||||||
|
<< " success, " << _stat._numOfBadResponse << " fail"
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
std::cout << "TRAFFIC: " << _stat._bytesRecieved / _stat._numOfGoodResponse
|
||||||
|
<< " avg bytes, "
|
||||||
|
<< (totalRecv - _stat._bytesRecieved) / _stat._numOfGoodResponse
|
||||||
|
<< " avg overhead, " << _stat._bytesRecieved << " bytes, "
|
||||||
|
<< totalRecv - _stat._bytesRecieved << " overhead" << std::endl;
|
||||||
|
|
||||||
|
std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(3)
|
||||||
|
<< "TIMING: " << seconds << " seconds, " << rps << " rps, "
|
||||||
|
<< (double)(_stat._totalDelay) / _stat._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);
|
||||||
|
}
|
75
drogon_ctl/press.h
Normal file
75
drogon_ctl/press.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* press.h
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CommandHandler.h"
|
||||||
|
#include <drogon/DrObject.h>
|
||||||
|
#include <drogon/HttpAppFramework.h>
|
||||||
|
#include <drogon/HttpClient.h>
|
||||||
|
#include <trantor/utils/Date.h>
|
||||||
|
#include <trantor/net/EventLoopThreadPool.h>
|
||||||
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace drogon;
|
||||||
|
|
||||||
|
namespace drogon_ctl
|
||||||
|
{
|
||||||
|
struct Statistics
|
||||||
|
{
|
||||||
|
std::atomic_size_t _numOfRequestsSent = ATOMIC_VAR_INIT(0);
|
||||||
|
std::atomic_size_t _bytesRecieved = ATOMIC_VAR_INIT(0);
|
||||||
|
std::atomic_size_t _numOfGoodResponse = ATOMIC_VAR_INIT(0);
|
||||||
|
std::atomic_size_t _numOfBadResponse = ATOMIC_VAR_INIT(0);
|
||||||
|
std::atomic_size_t _totalDelay = ATOMIC_VAR_INIT(0);
|
||||||
|
trantor::Date _startDate;
|
||||||
|
trantor::Date _endDate;
|
||||||
|
};
|
||||||
|
class press : public DrObject<press>, public CommandHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void handleCommand(std::vector<std::string> ¶meters) override;
|
||||||
|
virtual std::string script() override
|
||||||
|
{
|
||||||
|
return "Do stress testing(Use 'drogon_ctl help press' for more "
|
||||||
|
"information)";
|
||||||
|
}
|
||||||
|
virtual bool isTopCommand() override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
virtual std::string detail() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t _numOfThreads = 1;
|
||||||
|
size_t _numOfRequests = 1;
|
||||||
|
size_t _numOfConnections = 1;
|
||||||
|
//bool _keepAlive = false;
|
||||||
|
bool _processIndication = true;
|
||||||
|
std::string _url;
|
||||||
|
std::string _host;
|
||||||
|
std::string _path;
|
||||||
|
void doTesting();
|
||||||
|
void createRequestAndClients();
|
||||||
|
void sendRequest(const HttpClientPtr &client);
|
||||||
|
void outputResults();
|
||||||
|
std::unique_ptr<trantor::EventLoopThreadPool> _loopPool;
|
||||||
|
std::vector<HttpClientPtr> _clients;
|
||||||
|
Statistics _stat;
|
||||||
|
|
||||||
|
};
|
||||||
|
} // namespace drogon_ctl
|
@ -6,7 +6,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright 2018, An Tao. All rights reserved.
|
* Copyright 2018, An Tao. All rights reserved.
|
||||||
* https://github.com/an-tao/drogon
|
* https://github.com/an-tao/drogon
|
||||||
* Use of this source code is governed by a MIT license
|
* Use of this source code is governed by the MIT license
|
||||||
* that can be found in the License file.
|
* that can be found in the License file.
|
||||||
*
|
*
|
||||||
* Drogon
|
* Drogon
|
||||||
@ -49,6 +49,10 @@ class HttpClient : public trantor::NonCopyable
|
|||||||
/**
|
/**
|
||||||
* The response from the http server is obtained
|
* The response from the http server is obtained
|
||||||
* in the callback function.
|
* in the callback function.
|
||||||
|
* Note:
|
||||||
|
* The request object is altered(some headers are added to it) before it is
|
||||||
|
* sent, so calling this method with a same request object in different
|
||||||
|
* thread is dangerous.
|
||||||
*/
|
*/
|
||||||
virtual void sendRequest(const HttpRequestPtr &req,
|
virtual void sendRequest(const HttpRequestPtr &req,
|
||||||
const HttpReqCallback &callback) = 0;
|
const HttpReqCallback &callback) = 0;
|
||||||
@ -100,6 +104,10 @@ class HttpClient : public trantor::NonCopyable
|
|||||||
/// Get the event loop of the client;
|
/// Get the event loop of the client;
|
||||||
virtual trantor::EventLoop *getLoop() = 0;
|
virtual trantor::EventLoop *getLoop() = 0;
|
||||||
|
|
||||||
|
/// Get the number of bytes sent or received
|
||||||
|
virtual size_t bytesSent() const = 0;
|
||||||
|
virtual size_t bytesReceived() const = 0;
|
||||||
|
|
||||||
/// Use hostString to connect to server
|
/// Use hostString to connect to server
|
||||||
/**
|
/**
|
||||||
* Examples for hostString:
|
* Examples for hostString:
|
||||||
|
@ -319,6 +319,7 @@ void HttpClientImpl::sendReq(const trantor::TcpConnectionPtr &connPtr,
|
|||||||
implPtr->appendToBuffer(&buffer);
|
implPtr->appendToBuffer(&buffer);
|
||||||
LOG_TRACE << "Send request:"
|
LOG_TRACE << "Send request:"
|
||||||
<< std::string(buffer.peek(), buffer.readableBytes());
|
<< std::string(buffer.peek(), buffer.readableBytes());
|
||||||
|
_bytesSent += buffer.readableBytes();
|
||||||
connPtr->send(std::move(buffer));
|
connPtr->send(std::move(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,12 +330,14 @@ void HttpClientImpl::onRecvMessage(const trantor::TcpConnectionPtr &connPtr,
|
|||||||
any_cast<HttpResponseParser>(connPtr->getMutableContext());
|
any_cast<HttpResponseParser>(connPtr->getMutableContext());
|
||||||
|
|
||||||
// LOG_TRACE << "###:" << msg->readableBytes();
|
// LOG_TRACE << "###:" << msg->readableBytes();
|
||||||
|
auto msgSize = msg->readableBytes();
|
||||||
while (msg->readableBytes() > 0)
|
while (msg->readableBytes() > 0)
|
||||||
{
|
{
|
||||||
if (!responseParser->parseResponse(msg))
|
if (!responseParser->parseResponse(msg))
|
||||||
{
|
{
|
||||||
assert(!_pipeliningCallbacks.empty());
|
assert(!_pipeliningCallbacks.empty());
|
||||||
onError(ReqResult::BadResponse);
|
onError(ReqResult::BadResponse);
|
||||||
|
_bytesReceived += (msgSize - msg->readableBytes());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (responseParser->gotAll())
|
if (responseParser->gotAll())
|
||||||
@ -354,6 +357,8 @@ void HttpClientImpl::onRecvMessage(const trantor::TcpConnectionPtr &connPtr,
|
|||||||
auto cb = std::move(_pipeliningCallbacks.front());
|
auto cb = std::move(_pipeliningCallbacks.front());
|
||||||
_pipeliningCallbacks.pop();
|
_pipeliningCallbacks.pop();
|
||||||
handleCookies(resp);
|
handleCookies(resp);
|
||||||
|
_bytesReceived += (msgSize - msg->readableBytes());
|
||||||
|
msgSize = msg->readableBytes();
|
||||||
cb(ReqResult::Ok, resp);
|
cb(ReqResult::Ok, resp);
|
||||||
|
|
||||||
// LOG_TRACE << "pipelining buffer size=" <<
|
// LOG_TRACE << "pipelining buffer size=" <<
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright 2018, An Tao. All rights reserved.
|
* Copyright 2018, An Tao. All rights reserved.
|
||||||
* https://github.com/an-tao/drogon
|
* https://github.com/an-tao/drogon
|
||||||
* Use of this source code is governed by a MIT license
|
* Use of this source code is governed by the MIT license
|
||||||
* that can be found in the License file.
|
* that can be found in the License file.
|
||||||
*
|
*
|
||||||
* Drogon
|
* Drogon
|
||||||
@ -63,6 +63,15 @@ class HttpClientImpl : public HttpClient,
|
|||||||
_validCookies.emplace_back(cookie);
|
_validCookies.emplace_back(cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual size_t bytesSent() const override
|
||||||
|
{
|
||||||
|
return _bytesSent;
|
||||||
|
}
|
||||||
|
virtual size_t bytesReceived() const override
|
||||||
|
{
|
||||||
|
return _bytesReceived;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<trantor::TcpClient> _tcpClient;
|
std::shared_ptr<trantor::TcpClient> _tcpClient;
|
||||||
trantor::EventLoop *_loop;
|
trantor::EventLoop *_loop;
|
||||||
@ -81,6 +90,8 @@ class HttpClientImpl : public HttpClient,
|
|||||||
size_t _pipeliningDepth = 0;
|
size_t _pipeliningDepth = 0;
|
||||||
bool _enableCookies = false;
|
bool _enableCookies = false;
|
||||||
std::vector<Cookie> _validCookies;
|
std::vector<Cookie> _validCookies;
|
||||||
|
size_t _bytesSent = 0;
|
||||||
|
size_t _bytesReceived = 0;
|
||||||
};
|
};
|
||||||
typedef std::shared_ptr<HttpClientImpl> HttpClientImplPtr;
|
typedef std::shared_ptr<HttpClientImpl> HttpClientImplPtr;
|
||||||
} // namespace drogon
|
} // namespace drogon
|
||||||
|
Loading…
Reference in New Issue
Block a user