2009-09-10 18:09:56 +00:00
|
|
|
/*
|
|
|
|
* weighttp - a lightweight and simple webserver benchmarking tool
|
|
|
|
*
|
|
|
|
* Author:
|
|
|
|
* Copyright (c) 2009 Thomas Porzelt
|
|
|
|
*
|
|
|
|
* License:
|
|
|
|
* MIT, see COPYING file
|
|
|
|
*/
|
|
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2009-09-11 16:27:43 +00:00
|
|
|
uint64_t str_to_uint64(char *str) {
|
2009-09-11 10:09:54 +00:00
|
|
|
uint64_t i;
|
|
|
|
|
|
|
|
for (i = 0; *str; str++) {
|
|
|
|
if (*str < '0' || *str > '9')
|
|
|
|
return UINT64_MAX;
|
|
|
|
|
|
|
|
i *= 10;
|
|
|
|
i += *str - '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2009-09-10 18:09:56 +00:00
|
|
|
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':
|
2009-09-11 10:09:54 +00:00
|
|
|
config.req_count = str_to_uint64(optarg);
|
2009-09-10 18:09:56 +00:00
|
|
|
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;
|
|
|
|
}
|
2009-09-11 10:09:54 +00:00
|
|
|
if (config.req_count == UINT64_MAX || config.thread_count > config.req_count || config.thread_count > config.concur_count || config.concur_count > config.req_count) {
|
2009-09-10 18:09:56 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-09-10 20:26:58 +00:00
|
|
|
if (NULL == (config.request = forge_request(argv[optind], config.keep_alive, &host, &port))) {
|
2009-09-10 18:09:56 +00:00
|
|
|
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++) {
|
2009-09-11 10:09:54 +00:00
|
|
|
uint64_t reqs = config.req_count / config.thread_count;
|
2009-09-10 18:09:56 +00:00
|
|
|
uint16_t concur = config.concur_count / config.thread_count;
|
2009-09-11 10:09:54 +00:00
|
|
|
uint64_t diff;
|
2009-09-10 18:09:56 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2009-09-11 10:09:54 +00:00
|
|
|
printf("spawning thread #%d: %"PRIu16" concurrent requests, %"PRIu64" total requests\n", i+1, concur, reqs);
|
|
|
|
workers[i] = worker_new(i+1, &config, concur, reqs);
|
2009-09-10 18:09:56 +00:00
|
|
|
|
2009-09-11 10:09:54 +00:00
|
|
|
if (!(workers[i])) {
|
2009-09-10 18:09:56 +00:00
|
|
|
W_ERROR("%s", "failed to allocate worker or client");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2009-09-11 10:09:54 +00:00
|
|
|
err = pthread_create(&threads[i], NULL, worker_thread, (void*)workers[i]);
|
2009-09-10 18:09:56 +00:00
|
|
|
|
|
|
|
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;
|
2009-09-10 21:45:34 +00:00
|
|
|
}
|