initial commit

This commit is contained in:
Thomas Porzelt 2009-09-10 20:09:56 +02:00
commit 68b711e66b
12 changed files with 1085 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.waf-*
.lock-wscript
.DS_Store
*~
build
weighttp

23
COPYING Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);

BIN
waf vendored Executable file

Binary file not shown.

70
wscript Normal file
View 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'
)