h2load: Add -d option to upload data to server
This commit is contained in:
parent
faa2c4467a
commit
ed79637737
|
@ -28,6 +28,8 @@
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
@ -57,16 +59,27 @@
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
|
||||||
|
#ifndef O_BINARY
|
||||||
|
#define O_BINARY (0)
|
||||||
|
#endif // O_BINARY
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace h2load {
|
namespace h2load {
|
||||||
|
|
||||||
Config::Config()
|
Config::Config()
|
||||||
: addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
|
: data_length(-1), addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
|
||||||
max_concurrent_streams(-1), window_bits(16), connection_window_bits(16),
|
max_concurrent_streams(-1), window_bits(16), connection_window_bits(16),
|
||||||
no_tls_proto(PROTO_HTTP2), port(0), default_port(0), verbose(false) {}
|
no_tls_proto(PROTO_HTTP2), data_fd(-1), port(0), default_port(0),
|
||||||
|
verbose(false) {}
|
||||||
|
|
||||||
Config::~Config() { freeaddrinfo(addrs); }
|
Config::~Config() {
|
||||||
|
freeaddrinfo(addrs);
|
||||||
|
|
||||||
|
if (data_fd != -1) {
|
||||||
|
close(data_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
|
@ -95,7 +108,7 @@ void debug_nextproto_error() {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
RequestStat::RequestStat() : completed(false) {}
|
RequestStat::RequestStat() : data_offset(0), completed(false) {}
|
||||||
|
|
||||||
Stats::Stats(size_t req_todo)
|
Stats::Stats(size_t req_todo)
|
||||||
: req_todo(0), req_started(0), req_done(0), req_success(0),
|
: req_todo(0), req_started(0), req_done(0), req_success(0),
|
||||||
|
@ -987,6 +1000,9 @@ Options:
|
||||||
#endif // !HAVE_SPDYLAY
|
#endif // !HAVE_SPDYLAY
|
||||||
out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
|
out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
|
||||||
Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
|
Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
|
||||||
|
-d, --data=<FILE>
|
||||||
|
Post FILE to server. The request method is changed to
|
||||||
|
POST.
|
||||||
-v, --verbose
|
-v, --verbose
|
||||||
Output debug information.
|
Output debug information.
|
||||||
--version Display version information and exit.
|
--version Display version information and exit.
|
||||||
|
@ -995,11 +1011,13 @@ Options:
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
std::string datafile;
|
||||||
while (1) {
|
while (1) {
|
||||||
static int flag = 0;
|
static int flag = 0;
|
||||||
static option long_options[] = {
|
static option long_options[] = {
|
||||||
{"requests", required_argument, nullptr, 'n'},
|
{"requests", required_argument, nullptr, 'n'},
|
||||||
{"clients", required_argument, nullptr, 'c'},
|
{"clients", required_argument, nullptr, 'c'},
|
||||||
|
{"data", required_argument, nullptr, 'd'},
|
||||||
{"threads", required_argument, nullptr, 't'},
|
{"threads", required_argument, nullptr, 't'},
|
||||||
{"max-concurrent-streams", required_argument, nullptr, 'm'},
|
{"max-concurrent-streams", required_argument, nullptr, 'm'},
|
||||||
{"window-bits", required_argument, nullptr, 'w'},
|
{"window-bits", required_argument, nullptr, 'w'},
|
||||||
|
@ -1012,7 +1030,7 @@ int main(int argc, char **argv) {
|
||||||
{"version", no_argument, &flag, 1},
|
{"version", no_argument, &flag, 1},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
auto c = getopt_long(argc, argv, "hvW:c:m:n:p:t:w:H:i:", long_options,
|
auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:", long_options,
|
||||||
&option_index);
|
&option_index);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
|
@ -1024,6 +1042,9 @@ int main(int argc, char **argv) {
|
||||||
case 'c':
|
case 'c':
|
||||||
config.nclients = strtoul(optarg, nullptr, 10);
|
config.nclients = strtoul(optarg, nullptr, 10);
|
||||||
break;
|
break;
|
||||||
|
case 'd':
|
||||||
|
datafile = optarg;
|
||||||
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
#ifdef NOTHREADS
|
#ifdef NOTHREADS
|
||||||
std::cerr << "-t: WARNING: Threading disabled at build time, "
|
std::cerr << "-t: WARNING: Threading disabled at build time, "
|
||||||
|
@ -1162,6 +1183,20 @@ int main(int argc, char **argv) {
|
||||||
<< "cores." << std::endl;
|
<< "cores." << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!datafile.empty()) {
|
||||||
|
config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
|
||||||
|
if (config.data_fd == -1) {
|
||||||
|
std::cerr << "-d: Could not open file " << datafile << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
struct stat data_stat;
|
||||||
|
if (fstat(config.data_fd, &data_stat) == -1) {
|
||||||
|
std::cerr << "-d: Could not stat file " << datafile << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
config.data_length = data_stat.st_size;
|
||||||
|
}
|
||||||
|
|
||||||
struct sigaction act;
|
struct sigaction act;
|
||||||
memset(&act, 0, sizeof(struct sigaction));
|
memset(&act, 0, sizeof(struct sigaction));
|
||||||
act.sa_handler = SIG_IGN;
|
act.sa_handler = SIG_IGN;
|
||||||
|
@ -1243,7 +1278,7 @@ int main(int argc, char **argv) {
|
||||||
} else {
|
} else {
|
||||||
shared_nva.emplace_back(":authority", config.host);
|
shared_nva.emplace_back(":authority", config.host);
|
||||||
}
|
}
|
||||||
shared_nva.emplace_back(":method", "GET");
|
shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
|
||||||
|
|
||||||
// list overridalbe headers
|
// list overridalbe headers
|
||||||
auto override_hdrs =
|
auto override_hdrs =
|
||||||
|
|
|
@ -60,6 +60,8 @@ struct Config {
|
||||||
std::string scheme;
|
std::string scheme;
|
||||||
std::string host;
|
std::string host;
|
||||||
std::string ifile;
|
std::string ifile;
|
||||||
|
// length of upload data
|
||||||
|
int64_t data_length;
|
||||||
addrinfo *addrs;
|
addrinfo *addrs;
|
||||||
size_t nreqs;
|
size_t nreqs;
|
||||||
size_t nclients;
|
size_t nclients;
|
||||||
|
@ -69,6 +71,8 @@ struct Config {
|
||||||
size_t window_bits;
|
size_t window_bits;
|
||||||
size_t connection_window_bits;
|
size_t connection_window_bits;
|
||||||
enum { PROTO_HTTP2, PROTO_SPDY2, PROTO_SPDY3, PROTO_SPDY3_1 } no_tls_proto;
|
enum { PROTO_HTTP2, PROTO_SPDY2, PROTO_SPDY3, PROTO_SPDY3_1 } no_tls_proto;
|
||||||
|
// file descriptor for upload data
|
||||||
|
int data_fd;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
uint16_t default_port;
|
uint16_t default_port;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
|
@ -83,6 +87,8 @@ struct RequestStat {
|
||||||
std::chrono::steady_clock::time_point request_time;
|
std::chrono::steady_clock::time_point request_time;
|
||||||
// time point when stream was closed
|
// time point when stream was closed
|
||||||
std::chrono::steady_clock::time_point stream_close_time;
|
std::chrono::steady_clock::time_point stream_close_time;
|
||||||
|
// upload data length sent so far
|
||||||
|
int64_t data_offset;
|
||||||
// true if stream was successfully closed. This means stream was
|
// true if stream was successfully closed. This means stream was
|
||||||
// not reset, but it does not mean HTTP level error (e.g., 404).
|
// not reset, but it does not mean HTTP level error (e.g., 404).
|
||||||
bool completed;
|
bool completed;
|
||||||
|
|
|
@ -110,6 +110,36 @@ int before_frame_send_callback(nghttp2_session *session,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
|
uint8_t *buf, size_t length, uint32_t *data_flags,
|
||||||
|
nghttp2_data_source *source, void *user_data) {
|
||||||
|
auto client = static_cast<Client *>(user_data);
|
||||||
|
auto config = client->worker->config;
|
||||||
|
auto req_stat = static_cast<RequestStat *>(
|
||||||
|
nghttp2_session_get_stream_user_data(session, stream_id));
|
||||||
|
|
||||||
|
ssize_t nread;
|
||||||
|
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
|
||||||
|
-1 &&
|
||||||
|
errno == EINTR)
|
||||||
|
;
|
||||||
|
|
||||||
|
if (nread == -1) {
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
req_stat->data_offset += nread;
|
||||||
|
|
||||||
|
if (nread == 0 || req_stat->data_offset == config->data_length) {
|
||||||
|
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
|
ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
|
||||||
size_t length, int flags, void *user_data) {
|
size_t length, int flags, void *user_data) {
|
||||||
|
@ -188,8 +218,11 @@ void Http2Session::submit_request(RequestStat *req_stat) {
|
||||||
client_->reqidx = 0;
|
client_->reqidx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto stream_id = nghttp2_submit_request(session_, nullptr, nva.data(),
|
nghttp2_data_provider prd{{0}, file_read_callback};
|
||||||
nva.size(), nullptr, req_stat);
|
|
||||||
|
auto stream_id =
|
||||||
|
nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(),
|
||||||
|
config->data_fd == -1 ? nullptr : &prd, req_stat);
|
||||||
assert(stream_id > 0);
|
assert(stream_id > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,35 @@ ssize_t send_callback(spdylay_session *session, const uint8_t *data,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
ssize_t file_read_callback(spdylay_session *session, int32_t stream_id,
|
||||||
|
uint8_t *buf, size_t length, int *eof,
|
||||||
|
spdylay_data_source *source, void *user_data) {
|
||||||
|
auto client = static_cast<Client *>(user_data);
|
||||||
|
auto config = client->worker->config;
|
||||||
|
auto req_stat = static_cast<RequestStat *>(
|
||||||
|
spdylay_session_get_stream_user_data(session, stream_id));
|
||||||
|
|
||||||
|
ssize_t nread;
|
||||||
|
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
|
||||||
|
-1 &&
|
||||||
|
errno == EINTR)
|
||||||
|
;
|
||||||
|
|
||||||
|
if (nread == -1) {
|
||||||
|
return SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
req_stat->data_offset += nread;
|
||||||
|
|
||||||
|
if (nread == 0 || req_stat->data_offset == config->data_length) {
|
||||||
|
*eof = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void SpdySession::on_connect() {
|
void SpdySession::on_connect() {
|
||||||
spdylay_session_callbacks callbacks = {0};
|
spdylay_session_callbacks callbacks = {0};
|
||||||
callbacks.send_callback = send_callback;
|
callbacks.send_callback = send_callback;
|
||||||
|
@ -150,7 +179,10 @@ void SpdySession::submit_request(RequestStat *req_stat) {
|
||||||
client_->reqidx = 0;
|
client_->reqidx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
spdylay_submit_request(session_, 0, nv.data(), nullptr, req_stat);
|
spdylay_data_provider prd{{0}, file_read_callback};
|
||||||
|
|
||||||
|
spdylay_submit_request(session_, 0, nv.data(),
|
||||||
|
config->data_fd == -1 ? nullptr : &prd, req_stat);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpdySession::on_read(const uint8_t *data, size_t len) {
|
int SpdySession::on_read(const uint8_t *data, size_t len) {
|
||||||
|
|
Loading…
Reference in New Issue