From 2504a22cab4f716542c3891986b920be37be1c25 Mon Sep 17 00:00:00 2001 From: Liang XU Date: Sun, 22 Apr 2012 13:02:31 +0800 Subject: [PATCH 1/2] Add time statistics like AB. --- src/client.c | 19 ++++- src/client.h | 7 +- src/weighttp.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++- src/worker.c | 3 +- src/worker.h | 14 +++- 5 files changed, 240 insertions(+), 8 deletions(-) diff --git a/src/client.c b/src/client.c index f8875e4..b06dc04 100644 --- a/src/client.c +++ b/src/client.c @@ -105,6 +105,9 @@ static void client_reset(Client *client) { client->parser_offset = 0; client->request_offset = 0; client->ts_start = 0; + client->ts_connect = 0; + client->ts_endwrite = 0; + client->ts_beginread = 0; client->ts_end = 0; client->status_success = 0; client->success = 0; @@ -143,6 +146,7 @@ static uint8_t client_connect(Client *client) { /* successfully connected */ client->state = CLIENT_WRITING; + client->ts_connect = ev_now(client->worker->loop); return 1; } @@ -183,6 +187,7 @@ void client_state_machine(Client *client) { ev_io_set(&client->sock_watcher, r, EV_WRITE); ev_io_start(client->worker->loop, &client->sock_watcher); + client->ts_start = ev_now(client->worker->loop); if (!client_connect(client)) { client->state = CLIENT_ERROR; goto start; @@ -213,6 +218,7 @@ void client_state_machine(Client *client) { if (client->request_offset == config->request_size) { /* whole request was sent, start reading */ client->state = CLIENT_READING; + client->ts_endwrite = ev_now(client->worker->loop); client_set_events(client, EV_READ); } @@ -236,6 +242,8 @@ void client_state_machine(Client *client) { client->state = CLIENT_ERROR; } else if (r != 0) { /* success */ + if(0 == client->ts_beginread) + client->ts_beginread = ev_now(client->worker->loop); client->bytes_received += r; client->buffer_offset += r; client->worker->stats.bytes_total += r; @@ -277,9 +285,15 @@ void client_state_machine(Client *client) { client->keepalive = 0; client->success = 0; client->state = CLIENT_END; - case CLIENT_END: + case CLIENT_END: { /* update worker stats */ - client->worker->stats.req_done++; + uint64_t index = client->worker->stats.req_done++; + struct Times *s = &client->worker->stats.times[index]; + client->ts_end = ev_now(client->worker->loop); + s->starttime = client->ts_start; + s->ctime = ap_max(0, client->ts_connect - client->ts_start); + s->time = ap_max(0, client->ts_end - client->ts_start); + s->waittime = ap_max(0, client->ts_beginread - client->ts_endwrite); if (client->success) { client->worker->stats.req_success++; @@ -308,6 +322,7 @@ void client_state_machine(Client *client) { client_reset(client); goto start; } + } } } diff --git a/src/client.h b/src/client.h index bb0a8b0..ed3dd4a 100644 --- a/src/client.h +++ b/src/client.h @@ -29,8 +29,11 @@ struct Client { uint32_t buffer_offset; uint32_t parser_offset; uint32_t request_offset; - ev_tstamp ts_start; - ev_tstamp ts_end; + ev_tstamp ts_start; /* Start of connection */ + ev_tstamp ts_connect; /* Connected, start writing */ + ev_tstamp ts_endwrite; /* Request written */ + ev_tstamp ts_beginread; /* First byte of input */ + ev_tstamp ts_end; /* Connection closed */ uint8_t keepalive; uint8_t success; uint8_t status_success; diff --git a/src/weighttp.c b/src/weighttp.c index 77504f1..674fb44 100644 --- a/src/weighttp.c +++ b/src/weighttp.c @@ -7,7 +7,8 @@ * License: * MIT, see COPYING file */ - +#include +#include #include "weighttp.h" extern int optind, optopt; /* getopt */ @@ -186,6 +187,199 @@ uint64_t str_to_uint64(char *str) { return i; } +static int compradre(struct Times * a, struct Times * b) +{ + if ((a->ctime) < (b->ctime)) + return -1; + if ((a->ctime) > (b->ctime)) + return +1; + return 0; +} + +static int comprando(struct Times * a, struct Times * b) +{ + if ((a->time) < (b->time)) + return -1; + if ((a->time) > (b->time)) + return +1; + return 0; +} + +static int compri(struct Times * a, struct Times * b) +{ + ev_tstamp p = a->time - a->ctime; + ev_tstamp q = b->time - b->ctime; + if (p < q) + return -1; + if (p > q) + return +1; + return 0; +} + +static int compwait(struct Times * a, struct Times * b) +{ + if ((a->waittime) < (b->waittime)) + return -1; + if ((a->waittime) > (b->waittime)) + return 1; + return 0; +} + +static void output_results(uint64_t done, struct Times * stats) { + /* work out connection times */ + int i; + ev_tstamp totalcon = 0, total = 0, totald = 0, totalwait = 0; + ev_tstamp meancon, meantot, meand, meanwait; + ev_tstamp mincon = ULONG_MAX, mintot = ULONG_MAX, mind = ULONG_MAX, + minwait = ULONG_MAX; + ev_tstamp maxcon = 0, maxtot = 0, maxd = 0, maxwait = 0; + ev_tstamp mediancon = 0, mediantot = 0, mediand = 0, medianwait = 0; + ev_tstamp sdtot = 0, sdcon = 0, sdd = 0, sdwait = 0; + + for (i = 0; i < done; i++) { + struct Times *s = &stats[i]; + mincon = ap_min(mincon, s->ctime); + mintot = ap_min(mintot, s->time); + mind = ap_min(mind, s->time - s->ctime); + minwait = ap_min(minwait, s->waittime); + + maxcon = ap_max(maxcon, s->ctime); + maxtot = ap_max(maxtot, s->time); + maxd = ap_max(maxd, s->time - s->ctime); + maxwait = ap_max(maxwait, s->waittime); + + totalcon += s->ctime; + total += s->time; + totald += s->time - s->ctime; + totalwait += s->waittime; + } + meancon = totalcon / done; + meantot = total / done; + meand = totald / done; + meanwait = totalwait / done; + + /* calculating the sample variance: the sum of the squared deviations, divided by n-1 */ + for (i = 0; i < done; i++) { + struct Times *s = &stats[i]; + ev_tstamp a; + a = (s->time - meantot); + sdtot += a * a; + a = (s->ctime - meancon); + sdcon += a * a; + a = (s->time - s->ctime - meand); + sdd += a * a; + a = (s->waittime - meanwait); + sdwait += a * a; + } + + sdtot = (done > 1) ? sqrt(sdtot / (done - 1)) : 0; + sdcon = (done > 1) ? sqrt(sdcon / (done - 1)) : 0; + sdd = (done > 1) ? sqrt(sdd / (done - 1)) : 0; + sdwait = (done > 1) ? sqrt(sdwait / (done - 1)) : 0; + + /* + * XXX: what is better; this hideous cast of the compradre function; or + * the four warnings during compile ? dirkx just does not know and + * hates both/ + */ + qsort(stats, done, sizeof(struct Times), + (int (*)(const void *, const void *))compradre); + if ((done > 1) && (done % 2)) + mediancon = (stats[done / 2].ctime + stats[done / 2 + 1].ctime) / 2; + else + mediancon = stats[done / 2].ctime; + + qsort(stats, done, sizeof(struct Times), + (int (*)(const void *, const void *))compri); + if ((done > 1) && (done % 2)) + mediand = (stats[done / 2].time + stats[done / 2 + 1].time + - stats[done / 2].ctime - stats[done / 2 + 1].ctime) / 2; + else + mediand = stats[done / 2].time - stats[done / 2].ctime; + + qsort(stats, done, sizeof(struct Times), + (int (*)(const void *, const void *))compwait); + if ((done > 1) && (done % 2)) + medianwait = (stats[done / 2].waittime + stats[done / 2 + 1].waittime) / 2; + else + medianwait = stats[done / 2].waittime; + + qsort(stats, done, sizeof(struct Times), + (int (*)(const void *, const void *))comprando); + if ((done > 1) && (done % 2)) + mediantot = (stats[done / 2].time + stats[done / 2 + 1].time) / 2; + else + mediantot = stats[done / 2].time; + + printf("\nConnection Times (ms)\n"); + /* + * Reduce stats from apr time to milliseconds + */ + mincon = ap_double_ms(mincon); + mind = ap_double_ms(mind); + minwait = ap_double_ms(minwait); + mintot = ap_double_ms(mintot); + meancon = ap_double_ms(meancon); + meand = ap_double_ms(meand); + meanwait = ap_double_ms(meanwait); + meantot = ap_double_ms(meantot); + mediancon = ap_double_ms(mediancon); + mediand = ap_double_ms(mediand); + medianwait = ap_double_ms(medianwait); + mediantot = ap_double_ms(mediantot); + maxcon = ap_double_ms(maxcon); + maxd = ap_double_ms(maxd); + maxwait = ap_double_ms(maxwait); + maxtot = ap_double_ms(maxtot); + sdcon = ap_double_ms(sdcon); + sdd = ap_double_ms(sdd); + sdwait = ap_double_ms(sdwait); + sdtot = ap_double_ms(sdtot); + +#define CONF_FMT_STRING "%5.3f %4.3f %5.1f %6.3f %7.3f\n" +#define SANE(what,mean,median,sd) \ + { \ + double d = (double)mean - median; \ + if (d < 0) d = -d; \ + if (d > 2 * sd ) \ + printf("ERROR: The median and mean for " what " are more than twice the standard\n" \ + " deviation apart. These results are NOT reliable.\n"); \ + else if (d > sd ) \ + printf("WARNING: The median and mean for " what " are not within a normal deviation\n" \ + " These results are probably not that reliable.\n"); \ + } + + printf(" min mean[+/-sd] median max\n"); + printf("Connect: " CONF_FMT_STRING, + mincon, meancon, sdcon, mediancon, maxcon); + printf("Processing: " CONF_FMT_STRING, + mind, meand, sdd, mediand, maxd); + printf("Waiting: " CONF_FMT_STRING, + minwait, meanwait, sdwait, medianwait, maxwait); + printf("Total: " CONF_FMT_STRING, + mintot, meantot, sdtot, mediantot, maxtot); + SANE("the initial connection time", meancon, mediancon, sdcon); + SANE("the processing time", meand, mediand, sdd); + SANE("the waiting time", meanwait, medianwait, sdwait); + SANE("the total time", meantot, mediantot, sdtot); + + /* Sorted on total connect times */ + if ((done > 1)) { + int percs[] = {50, 66, 75, 80, 90, 95, 98, 99, 100}; + printf("\nPercentage of the requests served within a certain time (ms)\n"); + for (i = 0; i < sizeof(percs) / sizeof(int); i++) { + if (percs[i] <= 0) + printf(" 0%% <0> (never)\n"); + else if (percs[i] >= 100) + printf(" 100%% %5.3f (longest request)\n", + ap_double_ms(stats[done - 1].time)); + else + printf(" %d%% %5.3f\n", percs[i], + ap_double_ms(stats[(int)(done * percs[i] / 100)].time)); + } + } +} + int main(int argc, char *argv[]) { Worker **workers; pthread_t *threads; @@ -207,6 +401,8 @@ int main(int argc, char *argv[]) { uint64_t kbps; char **headers; uint8_t headers_num; + struct Times * times; + uint64_t times_offet; printf("weighttp - a lightweight and simple webserver benchmarking tool\n\n"); @@ -308,6 +504,7 @@ int main(int argc, char *argv[]) { return 1; } + times = W_MALLOC(struct Times, config.req_count); /* spawn threads */ threads = W_MALLOC(pthread_t, config.thread_count); workers = W_MALLOC(Worker*, config.thread_count); @@ -320,6 +517,7 @@ int main(int argc, char *argv[]) { memset(&stats, 0, sizeof(stats)); ts_start = ev_time(); + times_offet = 0; for (i = 0; i < config.thread_count; i++) { uint64_t reqs = config.req_count / config.thread_count; uint16_t concur = config.concur_count / config.thread_count; @@ -339,7 +537,8 @@ int main(int argc, char *argv[]) { rest_req -= diff; } printf("spawning thread #%d: %"PRIu16" concurrent requests, %"PRIu64" total requests\n", i+1, concur, reqs); - workers[i] = worker_new(i+1, &config, concur, reqs); + workers[i] = worker_new(i+1, &config, concur, reqs, times + times_offet); + times_offet += reqs; if (!(workers[i])) { W_ERROR("%s", "failed to allocate worker or client"); @@ -397,6 +596,8 @@ int main(int argc, char *argv[]) { printf("traffic: %"PRIu64" bytes total, %"PRIu64" bytes http, %"PRIu64" bytes data\n", stats.bytes_total, stats.bytes_total - stats.bytes_body, stats.bytes_body ); + if (stats.req_done > 0) + output_results(stats.req_done, times); ev_default_destroy(); diff --git a/src/worker.c b/src/worker.c index 8cadc9b..1ad5aab 100644 --- a/src/worker.c +++ b/src/worker.c @@ -10,7 +10,7 @@ #include "weighttp.h" -Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t num_requests) { +Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t num_requests, struct Times * times) { Worker *worker; uint16_t i; @@ -21,6 +21,7 @@ Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t nu worker->config = config; worker->num_clients = num_clients; worker->stats.req_todo = num_requests; + worker->stats.times = times; worker->progress_interval = num_requests / 10; if (worker->progress_interval == 0) diff --git a/src/worker.h b/src/worker.h index f87476b..db4018b 100644 --- a/src/worker.h +++ b/src/worker.h @@ -8,6 +8,17 @@ * MIT, see COPYING file */ +struct Times { + ev_tstamp starttime;/* start time of connection */ + ev_tstamp waittime; /* between request and reading response */ + ev_tstamp ctime; /* time to connect */ + ev_tstamp time; /* time for connection */ +}; +#define ap_min(a,b) (((a)<(b))?(a):(b)) +#define ap_max(a,b) (((a)>(b))?(a):(b)) +#define ap_round_ms(a) ((uint32_t)((a) + 0.500)*1000) +#define ap_double_ms(a) ((a)*1000.0) + struct Stats { ev_tstamp req_ts_min; /* minimum time taken for a request */ ev_tstamp req_ts_max; /* maximum time taken for a request */ @@ -25,6 +36,7 @@ struct Stats { uint64_t req_3xx; uint64_t req_4xx; uint64_t req_5xx; + struct Times * times; }; struct Worker { @@ -39,6 +51,6 @@ struct Worker { }; -Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t num_requests); +Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t num_requests, struct Times * times); void worker_free(Worker *worker); void *worker_thread(void* arg); From 7901f2f4d6fece84a5a5dbfc30bb35d60a45317b Mon Sep 17 00:00:00 2001 From: lxu4net Date: Fri, 26 Jul 2013 09:12:33 +0800 Subject: [PATCH 2/2] add -L -M parameters for binding to many local ip and port. Avoid fast ip and port reuse. --- src/client.c | 39 +++++++++++++++++++++------ src/weighttp.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/weighttp.h | 4 +++ src/worker.c | 12 +++++++++ src/worker.h | 9 +++++++ 5 files changed, 125 insertions(+), 10 deletions(-) diff --git a/src/client.c b/src/client.c index b06dc04..0305f63 100644 --- a/src/client.c +++ b/src/client.c @@ -9,6 +9,7 @@ */ #include "weighttp.h" +#include static uint8_t client_parse(Client *client, int size); static void client_io_cb(struct ev_loop *loop, ev_io *w, int revents); @@ -160,7 +161,7 @@ static void client_io_cb(struct ev_loop *loop, ev_io *w, int revents) { } void client_state_machine(Client *client) { - int r; + int r, ret; Config *config = client->worker->config; start: @@ -180,6 +181,26 @@ void client_state_machine(Client *client) { goto start; } + /* bind to local address */ + if (0 != config->laddr_size) { + do { + struct sockaddr_in laddr; + uint16_t port = client->worker->current_port++; + if (client->worker->current_port >= LADDR_PORT_END) { + client->worker->current_port = LADDR_PORT_BEG; + client->worker->current_laddr++; + } + if (client->worker->current_laddr >= client->worker->laddrs_ip_end) { + client->worker->current_laddr = client->worker->laddrs_ip_beg; + } + + /* select local address */ + laddr = config->laddres[client->worker->current_laddr]; + laddr.sin_port = htons(port); + ret = bind(r, (struct sockaddr *) &laddr, sizeof(laddr)); + } while (-1 == ret); + } + /* set non-blocking */ fcntl(r, F_SETFL, O_NONBLOCK | O_RDWR); @@ -287,13 +308,15 @@ void client_state_machine(Client *client) { client->state = CLIENT_END; case CLIENT_END: { /* update worker stats */ - uint64_t index = client->worker->stats.req_done++; - struct Times *s = &client->worker->stats.times[index]; - client->ts_end = ev_now(client->worker->loop); - s->starttime = client->ts_start; - s->ctime = ap_max(0, client->ts_connect - client->ts_start); - s->time = ap_max(0, client->ts_end - client->ts_start); - s->waittime = ap_max(0, client->ts_beginread - client->ts_endwrite); + uint64_t times_index; + struct Times * s; + times_index = client->worker->stats.req_done++; + s= &client->worker->stats.times[times_index]; + client->ts_end = ev_now(client->worker->loop); + s->starttime = client->ts_start; + s->ctime = ap_max(0, client->ts_connect - client->ts_start); + s->time = ap_max(0, client->ts_end - client->ts_start); + s->waittime = ap_max(0, client->ts_beginread - client->ts_endwrite); if (client->success) { client->worker->stats.req_success++; diff --git a/src/weighttp.c b/src/weighttp.c index 674fb44..d759848 100644 --- a/src/weighttp.c +++ b/src/weighttp.c @@ -11,6 +11,14 @@ #include #include "weighttp.h" +#include +#include +#include +#include +#include + + + extern int optind, optopt; /* getopt */ static void show_help(void) { @@ -21,6 +29,8 @@ static void show_help(void) { printf(" -k keep alive (default: no)\n"); printf(" -6 use ipv6 (default: no)\n"); printf(" -H str add header to request\n"); + printf(" -L ip local ip address\n"); + printf(" -M mask local network mask\n"); printf(" -h show help and exit\n"); printf(" -v show version and exit\n\n"); printf("example: weighttpd -n 10000 -c 10 -t 2 -k -H \"User-Agent: foo\" localhost/index.html\n\n"); @@ -227,7 +237,7 @@ static int compwait(struct Times * a, struct Times * b) static void output_results(uint64_t done, struct Times * stats) { /* work out connection times */ - int i; + uint16_t i; ev_tstamp totalcon = 0, total = 0, totald = 0, totalwait = 0; ev_tstamp meancon, meantot, meand, meanwait; ev_tstamp mincon = ULONG_MAX, mintot = ULONG_MAX, mind = ULONG_MAX, @@ -380,6 +390,41 @@ static void output_results(uint64_t done, struct Times * stats) { } } + +static int setup_local_addresses(in_addr_t laddr, in_addr_t lmask, + Config * config) { + int count, i; + struct ifreq interfaces[LOCAL_ADDRESS_MAX]; + struct ifconf req; + + int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (-1 == fd) { + fprintf(stderr, "Failed to create socket. error = %s", strerror(errno)); + return -1; + } + + bzero(&interfaces, sizeof(interfaces)); + req.ifc_len = sizeof(interfaces); + req.ifc_req = interfaces; + + if (ioctl(fd, SIOCGIFCONF, &req)) { + fprintf(stderr, "Failed to list interfaces. error = %s", strerror(errno)); + close(fd); + return errno; + } + + count = req.ifc_len / sizeof(interfaces[0]); + config->laddr_size = 0; + for (i = 0; i < count; ++i) { + struct sockaddr_in * in = (struct sockaddr_in *) (&interfaces[i].ifr_addr); + if (laddr == (in->sin_addr.s_addr & lmask)) + config->laddres[config->laddr_size++] = *in; + } + + close(fd); + return 0; +} + int main(int argc, char *argv[]) { Worker **workers; pthread_t *threads; @@ -403,6 +448,8 @@ int main(int argc, char *argv[]) { uint8_t headers_num; struct Times * times; uint64_t times_offet; + in_addr_t laddr; + in_addr_t lmask; printf("weighttp - a lightweight and simple webserver benchmarking tool\n\n"); @@ -416,7 +463,11 @@ int main(int argc, char *argv[]) { config.req_count = 0; config.keep_alive = 0; - while ((c = getopt(argc, argv, ":hv6kn:t:c:H:")) != -1) { + laddr = 0; + lmask = 0; + config.laddr_size = 0; + + while ((c = getopt(argc, argv, ":hv6kn:t:c:H:L:M:")) != -1) { switch (c) { case 'h': show_help(); @@ -440,6 +491,12 @@ int main(int argc, char *argv[]) { case 'c': config.concur_count = atoi(optarg); break; + case 'L': + laddr = inet_addr(optarg); + break; + case 'M': + lmask = inet_addr(optarg); + break; case 'H': headers = W_REALLOC(headers, char*, headers_num+1); headers[headers_num] = optarg; @@ -484,6 +541,16 @@ int main(int argc, char *argv[]) { return 1; } + if (0 != laddr) { + if (0 == lmask) { + config.laddr_size = 1; + config.laddres[0].sin_family = AF_INET; + config.laddres[0].sin_port = 0; + config.laddres[0].sin_addr.s_addr = laddr; + } else { + setup_local_addresses(laddr, lmask, &config); + } + } loop = ev_default_loop(0); if (!loop) { diff --git a/src/weighttp.h b/src/weighttp.h index 4d98fba..626899d 100644 --- a/src/weighttp.h +++ b/src/weighttp.h @@ -56,6 +56,10 @@ struct Config { char *request; uint32_t request_size; struct addrinfo *saddr; + +#define LOCAL_ADDRESS_MAX 4096 + uint16_t laddr_size; + struct sockaddr_in laddres[LOCAL_ADDRESS_MAX]; }; uint64_t str_to_uint64(char *str); diff --git a/src/worker.c b/src/worker.c index 1ad5aab..d6fe212 100644 --- a/src/worker.c +++ b/src/worker.c @@ -9,6 +9,7 @@ */ #include "weighttp.h" +#include Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t num_requests, struct Times * times) { Worker *worker; @@ -23,6 +24,17 @@ Worker *worker_new(uint8_t id, Config *config, uint16_t num_clients, uint64_t nu worker->stats.req_todo = num_requests; worker->stats.times = times; worker->progress_interval = num_requests / 10; + if(0 < config->laddr_size) { + uint16_t num_laddrs = config->laddr_size / config->thread_count; + worker->laddrs_ip_beg = (id - 1) * num_laddrs; + worker->laddrs_ip_end = worker->laddrs_ip_beg + num_laddrs; + printf("worker-%u will use local addresses:\n", id); + for(i = worker->laddrs_ip_beg; i < worker->laddrs_ip_end; ++i) { + printf("\t%s\n", inet_ntoa(config->laddres[i].sin_addr)); + } + worker->current_laddr = worker->laddrs_ip_beg; + worker->current_port = LADDR_PORT_BEG; + } if (worker->progress_interval == 0) worker->progress_interval = 1; diff --git a/src/worker.h b/src/worker.h index db4018b..8d81953 100644 --- a/src/worker.h +++ b/src/worker.h @@ -48,6 +48,15 @@ struct Worker { uint16_t num_clients; Stats stats; uint64_t progress_interval; + +#define LADDR_PORT_BEG 10000 +#define LADDR_PORT_END 60000 +#define LADDR_PORT_RANGE LADDR_PORT_END - LADDR_PORT_BEG; + + uint16_t laddrs_ip_beg; + uint16_t laddrs_ip_end; + uint16_t current_laddr; + uint16_t current_port; };