@ -0,0 +1,6 @@ | |||
.waf-* | |||
.lock-wscript | |||
.DS_Store | |||
*~ | |||
build | |||
weighttp |
@ -0,0 +1,23 @@ | |||
The MIT License | |||
Copyright (c) 2009 Thomas Porzelt | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in | |||
all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
THE SOFTWARE. | |||
@ -0,0 +1,46 @@ | |||
weighttp - a lightweight and simple webserver benchmarking tool | |||
----------------------------------------- | |||
Please see http://weighttp.lighttpd.net/ for current info. | |||
BUILD | |||
===== | |||
Make sure you have libev* and python (for waf) installed, then: | |||
$ ./waf configure | |||
$ ./waf build | |||
See ./waf --help for available configure options and other commands available. | |||
INSTALL | |||
======= | |||
$ ./waf install | |||
or | |||
$ sudo ./waf install | |||
USAGE | |||
===== | |||
$ weighttp -h | |||
UNINSTALL | |||
========= | |||
$ ./waf uninstall | |||
or | |||
$ sudo ./waf uninstall | |||
You can also chain commands: | |||
$ ./waf configure clean build install | |||
---- | |||
* libev can be found in your distro's repository or at http://software.schmorp.de/pkg/libev.html |
@ -0,0 +1,6 @@ | |||
- timing statistics | |||
- generally better statistics | |||
- chunked encoding support | |||
- ssl support | |||
- better error reporting | |||
- ipv6 support |
@ -0,0 +1,388 @@ | |||
/* | |||
* weighttp - a lightweight and simple webserver benchmarking tool | |||
* | |||
* Author: | |||
* Copyright (c) 2009 Thomas Porzelt | |||
* | |||
* License: | |||
* MIT, see COPYING file | |||
*/ | |||
#include "weighttp.h" | |||
static uint8_t client_parse(Client *client); | |||
static void client_io_cb(struct ev_loop *loop, ev_io *w, int revents); | |||
static void client_set_events(Client *client, int events); | |||
/* | |||
static void client_add_events(Client *client, int events); | |||
static void client_rem_events(Client *client, int events); | |||
static void client_add_events(Client *client, int events) { | |||
struct ev_loop *loop = client->worker->loop; | |||
ev_io *watcher = &client->sock_watcher; | |||
if ((watcher->events & events) == events) | |||
return; | |||
ev_io_stop(loop, watcher); | |||
ev_io_set(watcher, watcher->fd, watcher->events | events); | |||
ev_io_start(loop, watcher); | |||
} | |||
static void client_rem_events(Client *client, int events) { | |||
struct ev_loop *loop = client->worker->loop; | |||
ev_io *watcher = &client->sock_watcher; | |||
if (0 == (watcher->events & events)) | |||
return; | |||
ev_io_stop(loop, watcher); | |||
ev_io_set(watcher, watcher->fd, watcher->events & ~events); | |||
ev_io_start(loop, watcher); | |||
} | |||
*/ | |||
static void client_set_events(Client *client, int events) { | |||
struct ev_loop *loop = client->worker->loop; | |||
ev_io *watcher = &client->sock_watcher; | |||
if (events == (watcher->events & (EV_READ | EV_WRITE))) | |||
return; | |||
ev_io_stop(loop, watcher); | |||
ev_io_set(watcher, watcher->fd, (watcher->events & ~(EV_READ | EV_WRITE)) | events); | |||
ev_io_start(loop, watcher); | |||
} | |||
Client *client_new(Worker *worker) { | |||
Client *client; | |||
client = W_MALLOC(Client, 1); | |||
client->state = CLIENT_START; | |||
client->worker = worker; | |||
client->sock_watcher.fd = -1; | |||
client->sock_watcher.data = client; | |||
client->content_length = -1; | |||
client->buffer_offset = 0; | |||
client->request_offset = 0; | |||
client->keepalive = client->worker->config->keep_alive; | |||
return client; | |||
} | |||
void client_free(Client *client) { | |||
if (client->sock_watcher.fd != -1) { | |||
ev_io_stop(client->worker->loop, &client->sock_watcher); | |||
shutdown(client->sock_watcher.fd, SHUT_WR); | |||
close(client->sock_watcher.fd); | |||
} | |||
free(client); | |||
} | |||
static void client_reset(Client *client) { | |||
//printf("keep alive: %d\n", client->keepalive); | |||
if (!client->keepalive) { | |||
ev_io_stop(client->worker->loop, &client->sock_watcher); | |||
if (client->sock_watcher.fd != -1) { | |||
shutdown(client->sock_watcher.fd, SHUT_WR); | |||
close(client->sock_watcher.fd); | |||
} | |||
client->sock_watcher.fd = -1; | |||
client->state = CLIENT_START; | |||
} else { | |||
client_set_events(client, EV_WRITE); | |||
client->worker->stats.req_started++; | |||
client->state = CLIENT_WRITING; | |||
} | |||
client->parser_state = PARSER_START; | |||
client->buffer_offset = 0; | |||
client->parser_offset = 0; | |||
client->request_offset = 0; | |||
client->ts_start = 0; | |||
client->ts_end = 0; | |||
client->status_200 = 0; | |||
client->success = 0; | |||
client->content_length = -1; | |||
client->bytes_received = 0; | |||
client->header_size = 0; | |||
client->keepalive = client->worker->config->keep_alive; | |||
} | |||
static uint8_t client_connect(Client *client) { | |||
//printf("connecting...\n"); | |||
start: | |||
if (-1 == connect(client->sock_watcher.fd, client->worker->config->saddr->ai_addr, client->worker->config->saddr->ai_addrlen)) { | |||
switch (errno) { | |||
case EINPROGRESS: | |||
case EALREADY: | |||
/* async connect now in progress */ | |||
client->state = CLIENT_CONNECTING; | |||
return 1; | |||
case EISCONN: | |||
break; | |||
case EINTR: | |||
goto start; | |||
default: | |||
{ | |||
strerror_r(errno, client->buffer, sizeof(client->buffer)); | |||
W_ERROR("connect() failed: %s (%d)", client->buffer, errno); | |||
return 0; | |||
} | |||
} | |||
} | |||
/* successfully connected */ | |||
client->state = CLIENT_WRITING; | |||
return 1; | |||
} | |||
static void client_io_cb(struct ev_loop *loop, ev_io *w, int revents) { | |||
Client *client = w->data; | |||
UNUSED(loop); | |||
UNUSED(revents); | |||
client_state_machine(client); | |||
} | |||
void client_state_machine(Client *client) { | |||
int r; | |||
Config *config = client->worker->config; | |||
start: | |||
//printf("state: %d\n", client->state); | |||
switch (client->state) { | |||
case CLIENT_START: | |||
do { | |||
r = socket(config->saddr->ai_family, config->saddr->ai_socktype, config->saddr->ai_protocol); | |||
} while (-1 == r && errno == EINTR); | |||
if (-1 == r) { | |||
client->state = CLIENT_ERROR; | |||
goto start; | |||
} | |||
/* set non-blocking */ | |||
fcntl(r, F_SETFL, O_NONBLOCK | O_RDWR); | |||
ev_init(&client->sock_watcher, client_io_cb); | |||
ev_io_set(&client->sock_watcher, r, EV_WRITE); | |||
ev_io_start(client->worker->loop, &client->sock_watcher); | |||
client->worker->stats.req_started++; | |||
if (!client_connect(client)) { | |||
client->state = CLIENT_ERROR; | |||
goto start; | |||
} else { | |||
client_set_events(client, EV_WRITE); | |||
return; | |||
} | |||
case CLIENT_CONNECTING: | |||
if (!client_connect(client)) { | |||
client->state = CLIENT_ERROR; | |||
goto start; | |||
} | |||
case CLIENT_WRITING: | |||
while (1) { | |||
r = write(client->sock_watcher.fd, &config->request[client->request_offset], config->request_size - client->request_offset); | |||
//printf("write(%d - %d = %d): %d\n", config->request_size, client->request_offset, config->request_size - client->request_offset, r); | |||
if (r == -1) { | |||
/* error */ | |||
if (errno == EINTR) | |||
continue; | |||
strerror_r(errno, client->buffer, sizeof(client->buffer)); | |||
W_ERROR("write() failed: %s (%d)", client->buffer, errno); | |||
client->state = CLIENT_ERROR; | |||
goto start; | |||
} else if (r != 0) { | |||
/* success */ | |||
client->request_offset += r; | |||
if (client->request_offset == config->request_size) { | |||
/* whole request was sent, start reading */ | |||
client->state = CLIENT_READING; | |||
client_set_events(client, EV_READ); | |||
} | |||
return; | |||
} else { | |||
/* disconnect */ | |||
client->state = CLIENT_END; | |||
goto start; | |||
} | |||
} | |||
case CLIENT_READING: | |||
while (1) { | |||
r = read(client->sock_watcher.fd, &client->buffer[client->buffer_offset], sizeof(client->buffer) - client->buffer_offset); | |||
//printf("read(): %d\n", r); | |||
if (r == -1) { | |||
/* error */ | |||
if (errno == EINTR) | |||
continue; | |||
strerror_r(errno, client->buffer, sizeof(client->buffer)); | |||
W_ERROR("read() failed: %s (%d)", client->buffer, errno); | |||
client->state = CLIENT_ERROR; | |||
} else if (r != 0) { | |||
/* success */ | |||
client->bytes_received += r; | |||
client->buffer_offset += r; | |||
client->worker->stats.bytes_total += r; | |||
if (client->buffer_offset >= sizeof(client->buffer)) { | |||
/* too big response header */ | |||
client->state = CLIENT_ERROR; | |||
break; | |||
} | |||
client->buffer[client->buffer_offset] = '\0'; | |||
//printf("buffer:\n==========\n%s\n==========\n", client->buffer); | |||
if (!client_parse(client)) { | |||
client->state = CLIENT_ERROR; | |||
//printf("parser failed\n"); | |||
break; | |||
} else { | |||
if (client->state == CLIENT_END) | |||
goto start; | |||
else | |||
break; | |||
} | |||
} else { | |||
/* disconnect */ | |||
client->state = CLIENT_ERROR; | |||
break; | |||
} | |||
} | |||
break; | |||
case CLIENT_ERROR: | |||
//printf("client error\n"); | |||
client->worker->stats.req_error++; | |||
client->keepalive = 0; | |||
client->success = 0; | |||
client->state = CLIENT_END; | |||
case CLIENT_END: | |||
/* update worker stats */ | |||
client->worker->stats.req_done++; | |||
if (client->success) { | |||
client->worker->stats.req_success++; | |||
client->worker->stats.bytes_body += client->bytes_received - client->header_size; | |||
} else { | |||
client->worker->stats.req_failed++; | |||
} | |||
if (client->worker->stats.req_started == client->worker->stats.req_todo) { | |||
/* this worker has started all requests */ | |||
ev_io_stop(client->worker->loop, &client->sock_watcher); | |||
if (client->worker->stats.req_done == client->worker->stats.req_todo) { | |||
/* this worker has finished all requests */ | |||
ev_unref(client->worker->loop); | |||
} | |||
} else { | |||
client_reset(client); | |||
goto start; | |||
} | |||
} | |||
} | |||
static uint8_t client_parse(Client *client) { | |||
char *end, *str; | |||
switch (client->parser_state) { | |||
case PARSER_START: | |||
//printf("parse (START):\n%s\n", &client->buffer[client->parser_offset]); | |||
/* look for HTTP/1.1 200 OK */ | |||
if (client->buffer_offset < sizeof("HTTP/1.1 200 OK\r\n")) | |||
return 1; | |||
if (strncmp(client->buffer, "HTTP/1.1 200 OK\r\n", sizeof("HTTP/1.1 200 OK\r\n")-1) == 0) { | |||
client->status_200 = 1; | |||
client->parser_offset = sizeof("HTTP/1.1 200 ok\r\n") - 1; | |||
} else { | |||
client->status_200 = 0; | |||
end = strchr(client->buffer, '\r'); | |||
if (!end || *(end+1) != '\n') | |||
return 0; | |||
client->parser_offset = end + 2 - client->buffer; | |||
} | |||
client->parser_state = PARSER_HEADER; | |||
case PARSER_HEADER: | |||
//printf("parse (HEADER)\n"); | |||
/* look for Content-Length and Connection header */ | |||
while (NULL != (end = strchr(&client->buffer[client->parser_offset], '\r'))) { | |||
if (*(end+1) != '\n') | |||
return 0; | |||
if (end == &client->buffer[client->parser_offset]) { | |||
/* body reached */ | |||
client->parser_state = PARSER_BODY; | |||
client->header_size = end + 2 - client->buffer; | |||
//printf("body reached\n"); | |||
return client_parse(client); | |||
} | |||
*end = '\0'; | |||
str = &client->buffer[client->parser_offset]; | |||
//printf("checking header: '%s'\n", str); | |||
if (strncmp(str, "Content-Length: ", sizeof("Content-Length: ")-1) == 0) { | |||
/* content length header */ | |||
client->content_length = atoi(str + sizeof("Content-Length: ") - 1); | |||
} else if (strncmp(str, "Connection: ", sizeof("Connection: ")-1) == 0) { | |||
/* connection header */ | |||
str += sizeof("Connection: ") - 1; | |||
if (strncmp(str, "close", sizeof("close")-1) == 0) | |||
client->keepalive = 0; | |||
else if (strncmp(str, "Keep-Alive", sizeof("Keep-Alive")-1) == 0) | |||
client->keepalive = client->worker->config->keep_alive; | |||
else if (strncmp(str, "keep-alive", sizeof("keep-alive")-1) == 0) | |||
client->keepalive = client->worker->config->keep_alive; | |||
else | |||
return 0; | |||
} | |||
if (*(end+2) == '\r' && *(end+3) == '\n') { | |||
/* body reached */ | |||
client->parser_state = PARSER_BODY; | |||
client->header_size = end + 4 - client->buffer; | |||
//printf("body reached\n"); | |||
return client_parse(client); | |||
} | |||
client->parser_offset = end - client->buffer + 2; | |||
} | |||
return 1; | |||
case PARSER_BODY: | |||
//printf("parse (BODY)\n"); | |||
/* do nothing, just consume the data */ | |||
/*printf("content-l: %"PRIu64", header: %d, recevied: %"PRIu64"\n", | |||
client->content_length, client->header_size, client->bytes_received); | |||
client->buffer_offset = 0;*/ | |||
if (client->content_length == -1) | |||
return 0; | |||
if (client->bytes_received == (uint64_t) (client->header_size + client->content_length)) { | |||
/* full response received */ | |||
client->state = CLIENT_END; | |||
client->success = client->status_200 ? 1 : 0; | |||
} | |||
return 1; | |||
} | |||
return 1; | |||
} |
@ -0,0 +1,46 @@ | |||
/* | |||
* weighttp - a lightweight and simple webserver benchmarking tool | |||
* | |||
* Author: | |||
* Copyright (c) 2009 Thomas Porzelt | |||
* | |||
* License: | |||
* MIT, see COPYING file | |||
*/ | |||
struct Client { | |||
enum { | |||
CLIENT_START, | |||
CLIENT_CONNECTING, | |||
CLIENT_WRITING, | |||
CLIENT_READING, | |||
CLIENT_ERROR, | |||
CLIENT_END | |||
} state; | |||
enum { | |||
PARSER_START, | |||
PARSER_HEADER, | |||
PARSER_BODY | |||
} parser_state; | |||
Worker *worker; | |||
ev_io sock_watcher; | |||
uint32_t buffer_offset; | |||
uint32_t parser_offset; | |||
uint32_t request_offset; | |||
ev_tstamp ts_start; | |||
ev_tstamp ts_end; | |||
uint8_t keepalive; | |||
uint8_t success; | |||
uint8_t status_200; | |||
int64_t content_length; | |||
uint64_t bytes_received; /* including http header */ | |||
uint16_t header_size; | |||
char buffer[CLIENT_BUFFER_SIZE]; | |||
}; | |||
Client *client_new(Worker *worker); | |||
void client_free(Client *client); | |||
void client_state_machine(Client *client); |
@ -0,0 +1,342 @@ | |||
/* | |||
* weighttp - a lightweight and simple webserver benchmarking tool | |||
* | |||
* Author: | |||
* Copyright (c) 2009 Thomas Porzelt | |||
* | |||
* License: | |||
* MIT, see COPYING file | |||
*/ | |||
#define VERSION "0.1" | |||
#include "weighttp.h" | |||
extern int optind, optopt; /* getopt */ | |||
static void show_help(void) { | |||
printf("weighttp <options> <url>\n"); | |||
printf(" -n num number of requests (mandatory)\n"); | |||
printf(" -k keep alive (default: no)\n"); | |||
printf(" -t num threadcount (default: 1)\n"); | |||
printf(" -c num concurrent clients (default: 1)\n"); | |||
printf(" -h show help and exit\n"); | |||
printf(" -v show version and exit\n\n"); | |||
} | |||
static struct addrinfo *resolve_host(char *hostname, uint16_t port) { | |||
int err; | |||
char port_str[6]; | |||
struct addrinfo hints, *res, *res_first, *res_last; | |||
memset(&hints, 0, sizeof(hints)); | |||
hints.ai_family = PF_UNSPEC; | |||
hints.ai_socktype = SOCK_STREAM; | |||
sprintf(port_str, "%d", port); | |||
err = getaddrinfo(hostname, port_str, &hints, &res_first); | |||
if (err) { | |||
W_ERROR("could not resolve hostname: %s", hostname); | |||
return NULL; | |||
} | |||
/* search for an ipv4 address, no ipv6 yet */ | |||
res_last = NULL; | |||
for (res = res_first; res != NULL; res = res->ai_next) { | |||
if (res->ai_family == AF_INET) | |||
break; | |||
res_last = res; | |||
} | |||
if (!res) { | |||
freeaddrinfo(res_first); | |||
W_ERROR("could not resolve hostname: %s", hostname); | |||
return NULL; | |||
} | |||
if (res != res_first) { | |||
/* unlink from list and free rest */ | |||
res_last->ai_next = res->ai_next; | |||
freeaddrinfo(res_first); | |||
res->ai_next = NULL; | |||
} | |||
return res; | |||
} | |||
static char *forge_request(char *url, char keep_alive, char **host, uint16_t *port) { | |||
char *c, *end; | |||
char *req; | |||
uint32_t len; | |||
*host = NULL; | |||
*port = 0; | |||
if (strncmp(url, "http://", 7) == 0) | |||
url += 7; | |||
else if (strncmp(url, "https://", 8) == 0) { | |||
W_ERROR("%s", "no ssl support yet"); | |||
url += 8; | |||
return NULL; | |||
} | |||
len = strlen(url); | |||
if ((c = strchr(url, ':'))) { | |||
/* found ':' => host:port */ | |||
*host = W_MALLOC(char, c - url + 1); | |||
memcpy(*host, url, c - url); | |||
(*host)[c - url] = '\0'; | |||
if ((end = strchr(c+1, '/'))) { | |||
*end = '\0'; | |||
*port = atoi(c+1); | |||
*end = '/'; | |||
url = end; | |||
} else { | |||
*port = atoi(c+1); | |||
url += len; | |||
} | |||
} else { | |||
*port = 80; | |||
if ((c = strchr(url, '/'))) { | |||
*host = W_MALLOC(char, c - url + 1); | |||
memcpy(*host, url, c - url); | |||
(*host)[c - url] = '\0'; | |||
url = c; | |||
} else { | |||
*host = W_MALLOC(char, len + 1); | |||
memcpy(*host, url, len); | |||
(*host)[len] = '\0'; | |||
url += len; | |||
} | |||
} | |||
if (*port == 0) { | |||
W_ERROR("%s", "could not parse url"); | |||
free(*host); | |||
return NULL; | |||
} | |||
if (*url == '\0') | |||
url = "/"; | |||
req = W_MALLOC(char, sizeof("GET HTTP/1.1\r\nHost: :65536\r\nConnection: keep-alive\r\n\r\n") + strlen(*host) + strlen(url)); | |||
strcpy(req, "GET "); | |||
strcat(req, url); | |||
strcat(req, " HTTP/1.1\r\nHost: "); | |||
strcat(req, *host); | |||
if (*port != 80) | |||
sprintf(req + strlen(req), ":%"PRIu16, *port); | |||
if (keep_alive) | |||
strcat(req, "\r\nConnection: keep-alive\r\n\r\n"); | |||
else | |||
strcat(req, "\r\nConnection: close\r\n\r\n"); | |||
return req; | |||
} | |||
int main(int argc, char *argv[]) { | |||
Worker **workers; | |||
pthread_t *threads; | |||
int i; | |||
char c; | |||
int err; | |||
struct ev_loop *loop; | |||
Config config; | |||
Worker *worker; | |||
char *host; | |||
uint16_t port; | |||
uint16_t rest_concur, rest_req; | |||
Stats stats; | |||
ev_tstamp duration; | |||
int sec, millisec, microsec; | |||
uint64_t rps; | |||
uint64_t kbps; | |||
printf("weighttp - a lightweight and simple webserver benchmarking tool\n\n"); | |||
/* default settings */ | |||
config.thread_count = 1; | |||
config.concur_count = 1; | |||
config.req_count = 0; | |||
config.keep_alive = 0; | |||
while ((c = getopt(argc, argv, ":hvkn:t:c:")) != -1) { | |||
switch (c) { | |||
case 'h': | |||
show_help(); | |||
return 0; | |||
case 'v': | |||
printf("version: " VERSION "\n"); | |||
printf("build-date: " __DATE__ " " __TIME__ "\n\n"); | |||
return 0; | |||
case 'k': | |||
config.keep_alive = 1; | |||
break; | |||
case 'n': | |||
config.req_count = atoi(optarg); | |||
break; | |||
case 't': | |||
config.thread_count = atoi(optarg); | |||
break; | |||
case 'c': | |||
config.concur_count = atoi(optarg); | |||
break; | |||
case '?': | |||
W_ERROR("unkown option: -%c", optopt); | |||
show_help(); | |||
return 1; | |||
} | |||
} | |||
if ((argc - optind) < 1) { | |||
W_ERROR("%s", "missing url argument\n"); | |||
show_help(); | |||
return 1; | |||
} else if ((argc - optind) > 1) { | |||
W_ERROR("%s", "too many arguments\n"); | |||
show_help(); | |||
return 1; | |||
} | |||
/* check for sane arguments */ | |||
if (!config.thread_count) { | |||
W_ERROR("%s", "thread count has to be > 0\n"); | |||
show_help(); | |||
return 1; | |||
} | |||
if (!config.concur_count) { | |||
W_ERROR("%s", "number of concurrent clients has to be > 0\n"); | |||
show_help(); | |||
return 1; | |||
} | |||
if (!config.req_count) { | |||
W_ERROR("%s", "number of requests has to be > 0\n"); | |||
show_help(); | |||
return 1; | |||
} | |||
if (config.thread_count > config.req_count || config.thread_count > config.concur_count || config.concur_count > config.req_count) { | |||
W_ERROR("%s", "insane arguments\n"); | |||
show_help(); | |||
return 1; | |||
} | |||
loop = ev_default_loop(0); | |||
if (!loop) { | |||
W_ERROR("%s", "could not initialize libev\n"); | |||
return 2; | |||
} | |||
if (NULL == (config.request = forge_request(argv[optind], config.keep_alive, &host, &port)) { | |||
return 1; | |||
} | |||
config.request_size = strlen(config.request); | |||
//printf("Request (%d):\n==========\n%s==========\n", config.request_size, config.request); | |||
//printf("host: '%s', port: %d\n", host, port); | |||
/* resolve hostname */ | |||
if(!(config.saddr = resolve_host(host, port))) { | |||
return 1; | |||
} | |||
/* spawn threads */ | |||
threads = W_MALLOC(pthread_t, config.thread_count); | |||
workers = W_MALLOC(Worker*, config.thread_count); | |||
rest_concur = config.concur_count % config.thread_count; | |||
rest_req = config.req_count % config.thread_count; | |||
printf("starting benchmark...\n"); | |||
memset(&stats, 0, sizeof(stats)); | |||
stats.ts_start = ev_time(); | |||
for (i = 0; i < config.thread_count; i++) { | |||
uint16_t reqs = config.req_count / config.thread_count; | |||
uint16_t concur = config.concur_count / config.thread_count; | |||
uint16_t diff; | |||
if (rest_concur) { | |||
diff = (i == config.thread_count) ? rest_concur : (rest_concur / config.thread_count); | |||
diff = diff ? diff : 1; | |||
concur += diff; | |||
rest_concur -= diff; | |||
} | |||
if (rest_req) { | |||
diff = (i == config.thread_count) ? rest_req : (rest_req / config.thread_count); | |||
diff = diff ? diff : 1; | |||
reqs += diff; | |||
rest_req -= diff; | |||
} | |||
workers[i] = worker = worker_new(i+1, &config, concur, reqs); | |||
if (!worker) { | |||
W_ERROR("%s", "failed to allocate worker or client"); | |||
return 1; | |||
} | |||
err = pthread_create(&threads[i], NULL, worker_thread, (void*)worker); | |||
if (err != 0) { | |||
W_ERROR("failed spawning thread (%d)", err); | |||
return 2; | |||
} | |||
} | |||
for (i = 0; i < config.thread_count; i++) { | |||
err = pthread_join(threads[i], NULL); | |||
worker = workers[i]; | |||
if (err != 0) { | |||
W_ERROR("failed joining thread (%d)", err); | |||
return 3; | |||
} | |||
stats.req_started += worker->stats.req_started; | |||
stats.req_done += worker->stats.req_done; | |||
stats.req_success += worker->stats.req_success; | |||
stats.req_failed += worker->stats.req_failed; | |||
stats.bytes_total += worker->stats.bytes_total; | |||
stats.bytes_body += worker->stats.bytes_body; | |||
worker_free(worker); | |||
} | |||
stats.ts_end = ev_time(); | |||
duration = stats.ts_end - stats.ts_start; | |||
sec = duration; | |||
duration -= sec; | |||
duration = duration * 1000; | |||
millisec = duration; | |||
duration -= millisec; | |||
microsec = duration * 1000; | |||
rps = stats.req_done / (stats.ts_end - stats.ts_start); | |||
kbps = stats.bytes_total / (stats.ts_end - stats.ts_start) / 1024; | |||
printf("\nfinished in %d sec, %d millisec and %d microsec, %"PRIu64" req/s, %"PRIu64" kbyte/s\n", sec, millisec, microsec, rps, kbps); | |||
printf("requests: %"PRIu64" total, %"PRIu64" started, %"PRIu64" done, %"PRIu64" succeeded, %"PRIu64" failed, %"PRIu64" errored\n", | |||
config.req_count, stats.req_started, stats.req_done, stats.req_success, stats.req_failed, stats.req_error | |||
); | |||
printf("traffic: %"PRIu64" bytes total, %"PRIu64" bytes http, %"PRIu64" bytes data\n", | |||
stats.bytes_total, stats.bytes_total - stats.bytes_body, stats.bytes_body | |||
); | |||
ev_default_destroy(); | |||
free(threads); | |||
free(workers); | |||
free(config.request); | |||
freeaddrinfo(config.saddr); | |||
return 0; | |||
} |
@ -0,0 +1,60 @@ | |||
/* | |||
* weighttp - a lightweight and simple webserver benchmarking tool | |||
* | |||
* Author: | |||
* Copyright (c) 2009 Thomas Porzelt | |||
* | |||
* License: | |||
* MIT, see COPYING file | |||
*/ | |||
#ifndef WEIGHTTP_H | |||
#define WEIGHTTP_H 1 | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <time.h> | |||
#include <errno.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <stdint.h> | |||
#include <fcntl.h> | |||
#include <inttypes.h> | |||
#include <sys/socket.h> | |||
#include <netdb.h> | |||
#include <ev.h> | |||
#include <pthread.h> | |||
#define CLIENT_BUFFER_SIZE 32 * 1024 | |||
#define W_MALLOC(t, n) ((t*) calloc((n), sizeof(t))) | |||
#define W_ERROR(f, ...) fprintf(stderr, "error: " f "\n", __VA_ARGS__) | |||
#define UNUSED(x) ( (void)(x) ) | |||
struct Config; | |||
typedef struct Config Config; | |||
struct Stats; | |||
typedef struct Stats Stats; | |||
struct Worker; | |||
typedef struct Worker Worker; | |||
struct Client; | |||
typedef struct Client Client; | |||
#include "client.h" | |||
#include "worker.h" | |||
struct Config { | |||
uint64_t req_count; | |||
uint8_t thread_count; | |||
uint16_t concur_count; | |||
uint8_t keep_alive; | |||
char *request; | |||
uint32_t request_size; | |||
struct addrinfo *saddr; | |||
}; | |||
#endif |
@ -0,0 +1,58 @@ | |||
/* | |||
* weighttp - a lightweight and simple webserver benchmarking tool | |||
* | |||
* Author: | |||
* Copyright (c) 2009 Thomas Porzelt | |||
* | |||
* License: | |||
* MIT, see COPYING file | |||
*/ | |||
#include "weighttp.h" | |||
Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t num_requests) { | |||
Worker *worker; | |||
uint16_t i; | |||
worker = W_MALLOC(Worker, 1); | |||
worker->id = id; | |||
worker->loop = ev_loop_new(0); | |||
ev_ref(worker->loop); | |||
worker->config = config; | |||
worker->num_clients = num_clients; | |||
worker->stats.req_todo = num_requests; | |||
worker->clients = W_MALLOC(Client*, num_clients); | |||
for (i = 0; i < num_clients; i++) { | |||
if (NULL == (worker->clients[i] = client_new(worker))) | |||
return NULL; | |||
} | |||
return worker; | |||
} | |||
void worker_free(Worker *worker) { | |||
uint16_t i; | |||
for (i = 0; i < worker->num_clients; i++) | |||
client_free(worker->clients[i]); | |||
free(worker->clients); | |||
free(worker); | |||
} | |||
void *worker_thread(void* arg) { | |||
uint16_t i; | |||
Worker *worker = (Worker*)arg; | |||
/* start all clients */ | |||
for (i = 0; i < worker->num_clients; i++) { | |||
client_state_machine(worker->clients[i]); | |||
} | |||
ev_loop(worker->loop, 0); | |||
ev_loop_destroy(worker->loop); | |||
return NULL; | |||
} |
@ -0,0 +1,40 @@ | |||
/* | |||
* weighttp - a lightweight and simple webserver benchmarking tool | |||
* | |||
* Author: | |||
* Copyright (c) 2009 Thomas Porzelt | |||
* | |||
* License: | |||
* MIT, see COPYING file | |||
*/ | |||
struct Stats { | |||
ev_tstamp ts_start; /* start of requests */ | |||
ev_tstamp ts_end; /* end of requests */ | |||
ev_tstamp req_ts_min; /* minimum time taken for a request */ | |||
ev_tstamp req_ts_max; /* maximum time taken for a request */ | |||
ev_tstamp req_ts_total; /* total time taken for all requests (this is not ts_end - ts_start!) */ | |||
uint64_t req_todo; /* total number of requests to do */ | |||
uint64_t req_started; /* total number of requests started */ | |||
uint64_t req_done; /* total number of requests done */ | |||
uint64_t req_success; /* total number of successful requests */ | |||
uint64_t req_failed; /* total number of failed requests */ | |||
uint64_t req_error; /* total number of error'd requests */ | |||
uint64_t bytes_total; /* total number of bytes received (html+body) */ | |||
uint64_t bytes_body; /* total number of bytes received (body) */ | |||
}; | |||
struct Worker { | |||
uint8_t id; | |||
Config *config; | |||
struct ev_loop *loop; | |||
char *request; | |||
Client **clients; | |||
uint16_t num_clients; | |||
Stats stats; | |||
}; | |||
Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t num_requests); | |||
void worker_free(Worker *worker); | |||
void *worker_thread(void* arg); |
@ -0,0 +1,70 @@ | |||
#! /usr/bin/env python | |||
# encoding: utf-8 | |||
""" | |||
* weighttp - a lightweight and simple webserver benchmarking tool | |||
* | |||
* Author: | |||
* Copyright (c) 2009 Thomas Porzelt | |||
* | |||
* License: | |||
* MIT, see COPYING file | |||
""" | |||
import Options | |||
# the following two variables are used by the target "waf dist" | |||
VERSION='0.0.1' | |||
APPNAME='weighttp' | |||
# these variables are mandatory ('/' are converted automatically) | |||
srcdir = '.' | |||
blddir = 'build' | |||
def set_options(opt): | |||
opt.tool_options('compiler_cc') | |||
# ./waf configure options | |||
#opt.add_option('--with-xyz', action='store_true', help='with xyz', dest = 'xyz', default = False) | |||
def configure(conf): | |||
conf.env['CCFLAGS'] += [ | |||
'-std=gnu99', '-Wall', '-Wshadow', '-W', '-pedantic', '-g', '-g2', '-O2', '-Wmissing-declarations', | |||
'-Wdeclaration-after-statement', '-Wno-pointer-sign', '-Wcast-align', '-Winline', '-Wsign-compare', | |||
'-Wnested-externs', '-Wpointer-arith', '-Werror', '-Wbad-function-cast', '-Wmissing-prototypes', | |||
'-fPIC', '-D_GNU_SOURCE', '-D_FILE_OFFSET_BITS=64', '-D_LARGEFILE_SOURCE', | |||
'-D_LARGE_FILES', '-fno-strict-aliasing', | |||
] | |||
conf.check_tool('compiler_cc') | |||
# check for libev | |||
conf.check(lib='ev', uselib_store='ev', mandatory=True) | |||
conf.check(header_name='ev.h', uselib='ev', mandatory=True) | |||
# check for libpthread | |||
conf.check(lib='pthread', uselib_store='pthread', mandatory=True) | |||
conf.check(header_name='pthread.h', uselib='pthread', mandatory=True) | |||
# check for needed headers | |||
conf.check(header_name='unistd.h') | |||
conf.check(header_name='stdint.h') | |||
conf.check(header_name='fcntl.h') | |||
conf.check(header_name='inttypes.h') | |||
# check for needed functions | |||
#conf.check(function_name='writev', header_name='sys/uio.h', define_name='HAVE_WRITEV') | |||
def build(bld): | |||
bld.new_task_gen( | |||
features = 'cc cprogram', | |||
source = ['src/client.c', 'src/weighttp.c', 'src/worker.c'], | |||
defines = ['HAVE_CONFIG_H=1', 'VERSION=' % VERSION], | |||
includes = '.', | |||
uselib = 'ev pthread', | |||
target = 'weighttp' | |||
) | |||