summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Porzelt <tp@cryosphere.de>2009-09-10 20:09:56 +0200
committerThomas Porzelt <tp@cryosphere.de>2009-09-10 20:09:56 +0200
commit68b711e66b5fa3f76745e5b0d28808fb63eb6416 (patch)
tree14bef4455ccf8c356646d8ac70d1806b66645748
downloadweighttp-68b711e66b5fa3f76745e5b0d28808fb63eb6416.tar.gz
weighttp-68b711e66b5fa3f76745e5b0d28808fb63eb6416.zip
initial commit
-rw-r--r--.gitignore6
-rw-r--r--COPYING23
-rw-r--r--README46
-rw-r--r--TODO6
-rw-r--r--src/client.c388
-rw-r--r--src/client.h46
-rw-r--r--src/weighttp.c342
-rw-r--r--src/weighttp.h60
-rw-r--r--src/worker.c58
-rw-r--r--src/worker.h40
-rwxr-xr-xwafbin0 -> 86338 bytes
-rw-r--r--wscript70
12 files changed, 1085 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..343001e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.waf-*
+.lock-wscript
+.DS_Store
+*~
+build
+weighttp
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..01daff8
--- /dev/null
+++ b/COPYING
@@ -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.
+
diff --git a/README b/README
new file mode 100644
index 0000000..e116e9e
--- /dev/null
+++ b/README
@@ -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 \ No newline at end of file
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..84d08a3
--- /dev/null
+++ b/TODO
@@ -0,0 +1,6 @@
+- timing statistics
+- generally better statistics
+- chunked encoding support
+- ssl support
+- better error reporting
+- ipv6 support \ No newline at end of file
diff --git a/src/client.c b/src/client.c
new file mode 100644
index 0000000..864013a
--- /dev/null
+++ b/src/client.c
@@ -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;
+} \ No newline at end of file
diff --git a/src/client.h b/src/client.h
new file mode 100644
index 0000000..4b83b13
--- /dev/null
+++ b/src/client.h
@@ -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); \ No newline at end of file
diff --git a/src/weighttp.c b/src/weighttp.c
new file mode 100644
index 0000000..1e56e40
--- /dev/null
+++ b/src/weighttp.c
@@ -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;
+} \ No newline at end of file
diff --git a/src/weighttp.h b/src/weighttp.h
new file mode 100644
index 0000000..ad47465
--- /dev/null
+++ b/src/weighttp.h
@@ -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
diff --git a/src/worker.c b/src/worker.c
new file mode 100644
index 0000000..255deeb
--- /dev/null
+++ b/src/worker.c
@@ -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;
+} \ No newline at end of file
diff --git a/src/worker.h b/src/worker.h
new file mode 100644
index 0000000..add16ba
--- /dev/null
+++ b/src/worker.h
@@ -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); \ No newline at end of file
diff --git a/waf b/waf
new file mode 100755
index 0000000..65ab0a8
--- /dev/null
+++ b/waf
Binary files differ
diff --git a/wscript b/wscript
new file mode 100644
index 0000000..157c771
--- /dev/null
+++ b/wscript
@@ -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'
+ )
+