commit 68b711e66b5fa3f76745e5b0d28808fb63eb6416 Author: Thomas Porzelt Date: Thu Sep 10 20:09:56 2009 +0200 initial commit 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 \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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 Binary files /dev/null and b/waf 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' + ) +