249 lines
7.3 KiB
C++
249 lines
7.3 KiB
C++
#include <stdio.h>
|
|
|
|
#include <fstream>
|
|
#include <numeric>
|
|
#include <unordered_map>
|
|
|
|
#include "examples/sudoku/percentile.h"
|
|
#include "examples/sudoku/sudoku.h"
|
|
#include "muduo/base/FileUtil.h"
|
|
#include "muduo/base/Logging.h"
|
|
#include "muduo/net/EventLoop.h"
|
|
#include "muduo/net/TcpClient.h"
|
|
|
|
using namespace muduo;
|
|
using namespace muduo::net;
|
|
|
|
typedef std::vector<string> Input;
|
|
typedef std::shared_ptr<const Input> InputPtr;
|
|
|
|
InputPtr readInput(std::istream& in)
|
|
{
|
|
std::shared_ptr<Input> input(new Input);
|
|
std::string line;
|
|
while (getline(in, line)) {
|
|
if (line.size() == implicit_cast<size_t>(kCells)) {
|
|
input->push_back(line.c_str());
|
|
}
|
|
}
|
|
return input;
|
|
}
|
|
|
|
class SudokuClient : noncopyable {
|
|
public:
|
|
SudokuClient(EventLoop* loop, const InetAddress& serverAddr,
|
|
const InputPtr& input, const string& name, bool nodelay)
|
|
: name_(name),
|
|
tcpNoDelay_(nodelay),
|
|
client_(loop, serverAddr, name_),
|
|
input_(input),
|
|
count_(0)
|
|
{
|
|
client_.setConnectionCallback(
|
|
std::bind(&SudokuClient::onConnection, this, _1));
|
|
client_.setMessageCallback(
|
|
std::bind(&SudokuClient::onMessage, this, _1, _2, _3));
|
|
}
|
|
|
|
void connect() { client_.connect(); }
|
|
|
|
void send(int n)
|
|
{
|
|
assert(n > 0);
|
|
if (!conn_) return;
|
|
|
|
Timestamp now(Timestamp::now());
|
|
for (int i = 0; i < n; ++i) {
|
|
char buf[256];
|
|
const string& req = (*input_)[count_ % input_->size()];
|
|
int len = snprintf(buf, sizeof buf, "%s-%08d:%s\r\n", name_.c_str(),
|
|
count_, req.c_str());
|
|
requests_.append(buf, len);
|
|
sendTime_[count_] = now;
|
|
++count_;
|
|
}
|
|
|
|
conn_->send(&requests_);
|
|
}
|
|
|
|
void report(std::vector<int>* latency, int* infly)
|
|
{
|
|
latency->insert(latency->end(), latencies_.begin(), latencies_.end());
|
|
latencies_.clear();
|
|
*infly += static_cast<int>(sendTime_.size());
|
|
}
|
|
|
|
private:
|
|
void onConnection(const TcpConnectionPtr& conn)
|
|
{
|
|
if (conn->connected()) {
|
|
LOG_INFO << name_ << " connected";
|
|
if (tcpNoDelay_) conn->setTcpNoDelay(true);
|
|
conn_ = conn;
|
|
} else {
|
|
LOG_INFO << name_ << " disconnected";
|
|
conn_.reset();
|
|
// FIXME: exit
|
|
}
|
|
}
|
|
|
|
void onMessage(const TcpConnectionPtr& conn, Buffer* buf,
|
|
Timestamp recvTime)
|
|
{
|
|
size_t len = buf->readableBytes();
|
|
while (len >= kCells + 2) {
|
|
const char* crlf = buf->findCRLF();
|
|
if (crlf) {
|
|
string response(buf->peek(), crlf);
|
|
buf->retrieveUntil(crlf + 2);
|
|
len = buf->readableBytes();
|
|
if (!verify(response, recvTime)) {
|
|
LOG_ERROR << "Bad response:" << response;
|
|
conn->shutdown();
|
|
break;
|
|
}
|
|
} else if (len > 100) // id + ":" + kCells + "\r\n"
|
|
{
|
|
LOG_ERROR << "Line is too long!";
|
|
conn->shutdown();
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool verify(const string& response, Timestamp recvTime)
|
|
{
|
|
size_t colon = response.find(':');
|
|
if (colon != string::npos) {
|
|
size_t dash = response.find('-');
|
|
if (dash != string::npos && dash < colon) {
|
|
int id = atoi(response.c_str() + dash + 1);
|
|
std::unordered_map<int, Timestamp>::iterator sendTime =
|
|
sendTime_.find(id);
|
|
if (sendTime != sendTime_.end()) {
|
|
int64_t latency_us =
|
|
recvTime.microSecondsSinceEpoch() -
|
|
sendTime->second.microSecondsSinceEpoch();
|
|
latencies_.push_back(static_cast<int>(latency_us));
|
|
sendTime_.erase(sendTime);
|
|
} else {
|
|
LOG_ERROR << "Unknown id " << id << " of " << name_;
|
|
}
|
|
}
|
|
}
|
|
// FIXME
|
|
return true;
|
|
}
|
|
|
|
const string name_;
|
|
const bool tcpNoDelay_;
|
|
TcpClient client_;
|
|
TcpConnectionPtr conn_;
|
|
Buffer requests_;
|
|
const InputPtr input_;
|
|
int count_;
|
|
std::unordered_map<int, Timestamp> sendTime_;
|
|
std::vector<int> latencies_;
|
|
};
|
|
|
|
class SudokuLoadtest : noncopyable {
|
|
public:
|
|
SudokuLoadtest() : count_(0), ticks_(0), sofar_(0) {}
|
|
|
|
void runClient(const InputPtr& input, const InetAddress& serverAddr,
|
|
int rps, int conn, bool nodelay)
|
|
{
|
|
EventLoop loop;
|
|
|
|
for (int i = 0; i < conn; ++i) {
|
|
Fmt f("c%04d", i + 1);
|
|
string name(f.data(), f.length());
|
|
clients_.emplace_back(
|
|
new SudokuClient(&loop, serverAddr, input, name, nodelay));
|
|
clients_.back()->connect();
|
|
}
|
|
|
|
loop.runEvery(1.0 / kHz, std::bind(&SudokuLoadtest::tick, this, rps));
|
|
loop.runEvery(1.0, std::bind(&SudokuLoadtest::tock, this));
|
|
loop.loop();
|
|
}
|
|
|
|
private:
|
|
void tick(int rps)
|
|
{
|
|
++ticks_;
|
|
int64_t reqs = rps * ticks_ / kHz - sofar_;
|
|
sofar_ += reqs;
|
|
|
|
if (reqs > 0) {
|
|
for (const auto& client : clients_) {
|
|
client->send(static_cast<int>(reqs));
|
|
}
|
|
}
|
|
}
|
|
|
|
void tock()
|
|
{
|
|
std::vector<int> latencies;
|
|
int infly = 0;
|
|
for (const auto& client : clients_) {
|
|
client->report(&latencies, &infly);
|
|
}
|
|
|
|
Percentile p(latencies, infly);
|
|
LOG_INFO << p.report();
|
|
char buf[64];
|
|
snprintf(buf, sizeof buf, "r%04d", count_);
|
|
p.save(latencies, buf);
|
|
++count_;
|
|
}
|
|
|
|
std::vector<std::unique_ptr<SudokuClient>> clients_;
|
|
int count_;
|
|
int64_t ticks_;
|
|
int64_t sofar_;
|
|
static const int kHz = 100;
|
|
};
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
int conn = 1;
|
|
int rps = 100;
|
|
bool nodelay = false;
|
|
InetAddress serverAddr("127.0.0.1", 9981);
|
|
switch (argc) {
|
|
case 6:
|
|
nodelay = string(argv[5]) == "-n";
|
|
// FALL THROUGH
|
|
case 5:
|
|
conn = atoi(argv[4]);
|
|
// FALL THROUGH
|
|
case 4:
|
|
rps = atoi(argv[3]);
|
|
// FALL THROUGH
|
|
case 3:
|
|
serverAddr = InetAddress(argv[2], 9981);
|
|
// FALL THROUGH
|
|
case 2:
|
|
break;
|
|
default:
|
|
printf(
|
|
"Usage: %s input server_ip [requests_per_second] [connections] "
|
|
"[-n]\n",
|
|
argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
std::ifstream in(argv[1]);
|
|
if (in) {
|
|
InputPtr input(readInput(in));
|
|
printf("%zd requests from %s\n", input->size(), argv[1]);
|
|
SudokuLoadtest test;
|
|
test.runClient(input, serverAddr, rps, conn, nodelay);
|
|
} else {
|
|
printf("Cannot open %s\n", argv[1]);
|
|
}
|
|
}
|