h2load: Add -d option to upload data to server

This commit is contained in:
Tatsuhiro Tsujikawa 2015-03-26 19:53:42 +09:00
parent faa2c4467a
commit ed79637737
4 changed files with 115 additions and 9 deletions

View File

@ -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 =

View File

@ -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;

View File

@ -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);
} }

View File

@ -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) {