initial commit
This commit is contained in:
commit
68b711e66b
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.waf-*
|
||||
.lock-wscript
|
||||
.DS_Store
|
||||
*~
|
||||
build
|
||||
weighttp
|
23
COPYING
Normal file
23
COPYING
Normal file
@ -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.
|
||||
|
46
README
Normal file
46
README
Normal file
@ -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
|
6
TODO
Normal file
6
TODO
Normal file
@ -0,0 +1,6 @@
|
||||
- timing statistics
|
||||
- generally better statistics
|
||||
- chunked encoding support
|
||||
- ssl support
|
||||
- better error reporting
|
||||
- ipv6 support
|
388
src/client.c
Normal file
388
src/client.c
Normal file
@ -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;
|
||||
}
|
46
src/client.h
Normal file
46
src/client.h
Normal file
@ -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);
|
342
src/weighttp.c
Normal file
342
src/weighttp.c
Normal file
@ -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;
|
||||
}
|
60
src/weighttp.h
Normal file
60
src/weighttp.h
Normal file
@ -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
|
58
src/worker.c
Normal file
58
src/worker.c
Normal file
@ -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;
|
||||
}
|
40
src/worker.h
Normal file
40
src/worker.h
Normal file
@ -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);
|
70
wscript
Normal file
70
wscript
Normal file
@ -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'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user