Home | History | Annotate | Line # | Download | only in testcode
perf.c revision 1.1.1.6.2.1
      1 /*
      2  * testcode/perf.c - debug program to estimate name server performance.
      3  *
      4  * Copyright (c) 2008, NLnet Labs. All rights reserved.
      5  *
      6  * This software is open source.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  *
     12  * Redistributions of source code must retain the above copyright notice,
     13  * this list of conditions and the following disclaimer.
     14  *
     15  * Redistributions in binary form must reproduce the above copyright notice,
     16  * this list of conditions and the following disclaimer in the documentation
     17  * and/or other materials provided with the distribution.
     18  *
     19  * Neither the name of the NLNET LABS nor the names of its contributors may
     20  * be used to endorse or promote products derived from this software without
     21  * specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     26  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     27  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     28  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
     29  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     30  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     31  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     32  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     33  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     34  */
     35 
     36 /**
     37  * \file
     38  *
     39  * This program estimates DNS name server performance.
     40  */
     41 
     42 #include "config.h"
     43 #ifdef HAVE_GETOPT_H
     44 #include <getopt.h>
     45 #endif
     46 #include <signal.h>
     47 #include "util/log.h"
     48 #include "util/locks.h"
     49 #include "util/net_help.h"
     50 #include "util/data/msgencode.h"
     51 #include "util/data/msgreply.h"
     52 #include "util/data/msgparse.h"
     53 #include "sldns/sbuffer.h"
     54 #include "sldns/wire2str.h"
     55 #include "sldns/str2wire.h"
     56 #include <sys/time.h>
     57 
     58 /** usage information for perf */
     59 static void usage(char* nm)
     60 {
     61 	printf("usage: %s [options] server\n", nm);
     62 	printf("server: ip address of server, IP4 or IP6.\n");
     63 	printf("	If not on port %d add @port.\n", UNBOUND_DNS_PORT);
     64 	printf("-d sec	duration of test in whole seconds (0: wait for ^C)\n");
     65 	printf("-a str	query to ask, interpreted as a line from qfile\n");
     66 	printf("-f fnm	query list to read from file\n");
     67 	printf("	every line has format: qname qclass qtype [+-]{E}\n");
     68 	printf("	where + means RD set, E means EDNS enabled\n");
     69 	printf("-q 	quiet mode, print only final qps\n");
     70 	exit(1);
     71 }
     72 
     73 struct perfinfo;
     74 struct perfio;
     75 
     76 /** Global info for perf */
     77 struct perfinfo {
     78 	/** need to exit */
     79 	volatile int exit;
     80 	/** all purpose buffer (for UDP send and receive) */
     81 	sldns_buffer* buf;
     82 
     83 	/** destination */
     84 	struct sockaddr_storage dest;
     85 	/** length of dest socket addr */
     86 	socklen_t destlen;
     87 
     88 	/** when did this time slice start */
     89 	struct timeval since;
     90 	/** number of queries received in that time */
     91 	size_t numrecv;
     92 	/** number of queries sent out in that time */
     93 	size_t numsent;
     94 
     95 	/** duration of test in seconds */
     96 	int duration;
     97 	/** quiet mode? */
     98 	int quiet;
     99 
    100 	/** when did the total test start */
    101 	struct timeval start;
    102 	/** total number recvd */
    103 	size_t total_recv;
    104 	/** total number sent */
    105 	size_t total_sent;
    106 	/** numbers by rcode */
    107 	size_t by_rcode[32];
    108 
    109 	/** number of I/O ports */
    110 	size_t io_num;
    111 	/** I/O ports array */
    112 	struct perfio* io;
    113 	/** max fd value in io ports */
    114 	int maxfd;
    115 	/** readset */
    116 	fd_set rset;
    117 
    118 	/** size of querylist */
    119 	size_t qlist_size;
    120 	/** allocated size of qlist array */
    121 	size_t qlist_capacity;
    122 	/** list of query packets (data) */
    123 	uint8_t** qlist_data;
    124 	/** list of query packets (length of a packet) */
    125 	size_t* qlist_len;
    126 	/** index into querylist, for walking the list */
    127 	size_t qlist_idx;
    128 };
    129 
    130 /** I/O port for perf */
    131 struct perfio {
    132 	/** id number */
    133 	size_t id;
    134 	/** file descriptor of socket */
    135 	int fd;
    136 	/** timeout value */
    137 	struct timeval timeout;
    138 	/** ptr back to perfinfo */
    139 	struct perfinfo* info;
    140 };
    141 
    142 /** number of msec between starting io ports */
    143 #define START_IO_INTERVAL 10
    144 /** number of msec timeout on io ports */
    145 #define IO_TIMEOUT 10
    146 
    147 /** signal handler global info */
    148 static struct perfinfo* sig_info;
    149 
    150 /** signal handler for user quit */
    151 static RETSIGTYPE perf_sigh(int sig)
    152 {
    153 	log_assert(sig_info);
    154 	if(!sig_info->quiet)
    155 		printf("exit on signal %d\n", sig);
    156 	sig_info->exit = 1;
    157 }
    158 
    159 /** timeval compare, t1 < t2 */
    160 static int
    161 perf_tv_smaller(struct timeval* t1, struct timeval* t2)
    162 {
    163 #ifndef S_SPLINT_S
    164 	if(t1->tv_sec < t2->tv_sec)
    165 		return 1;
    166 	if(t1->tv_sec == t2->tv_sec &&
    167 		t1->tv_usec < t2->tv_usec)
    168 		return 1;
    169 #endif
    170 	return 0;
    171 }
    172 
    173 /** timeval add, t1 += t2 */
    174 static void
    175 perf_tv_add(struct timeval* t1, struct timeval* t2)
    176 {
    177 #ifndef S_SPLINT_S
    178 	t1->tv_sec += t2->tv_sec;
    179 	t1->tv_usec += t2->tv_usec;
    180 	while(t1->tv_usec >= 1000000) {
    181 		t1->tv_usec -= 1000000;
    182 		t1->tv_sec++;
    183 	}
    184 #endif
    185 }
    186 
    187 /** timeval subtract, t1 -= t2 */
    188 static void
    189 perf_tv_subtract(struct timeval* t1, struct timeval* t2)
    190 {
    191 #ifndef S_SPLINT_S
    192 	t1->tv_sec -= t2->tv_sec;
    193 	if(t1->tv_usec >= t2->tv_usec) {
    194 		t1->tv_usec -= t2->tv_usec;
    195 	} else {
    196 		t1->tv_sec--;
    197 		t1->tv_usec = 1000000-(t2->tv_usec-t1->tv_usec);
    198 	}
    199 #endif
    200 }
    201 
    202 
    203 /** setup perf test environment */
    204 static void
    205 perfsetup(struct perfinfo* info)
    206 {
    207 	size_t i;
    208 	if(gettimeofday(&info->start, NULL) < 0)
    209 		fatal_exit("gettimeofday: %s", strerror(errno));
    210 	sig_info = info;
    211 	if( signal(SIGINT, perf_sigh) == SIG_ERR ||
    212 #ifdef SIGQUIT
    213 		signal(SIGQUIT, perf_sigh) == SIG_ERR ||
    214 #endif
    215 #ifdef SIGHUP
    216 		signal(SIGHUP, perf_sigh) == SIG_ERR ||
    217 #endif
    218 #ifdef SIGBREAK
    219 		signal(SIGBREAK, perf_sigh) == SIG_ERR ||
    220 #endif
    221 		signal(SIGTERM, perf_sigh) == SIG_ERR)
    222 		fatal_exit("could not bind to signal");
    223 	info->io = (struct perfio*)calloc(sizeof(struct perfio), info->io_num);
    224 	if(!info->io) fatal_exit("out of memory");
    225 #ifndef S_SPLINT_S
    226 	FD_ZERO(&info->rset);
    227 #endif
    228 	info->since = info->start;
    229 	for(i=0; i<info->io_num; i++) {
    230 		info->io[i].id = i;
    231 		info->io[i].info = info;
    232 		info->io[i].fd = socket(
    233 			addr_is_ip6(&info->dest, info->destlen)?
    234 			AF_INET6:AF_INET, SOCK_DGRAM, 0);
    235 		if(info->io[i].fd == -1) {
    236 			fatal_exit("socket: %s", sock_strerror(errno));
    237 		}
    238 		if(info->io[i].fd > info->maxfd)
    239 			info->maxfd = info->io[i].fd;
    240 #ifndef S_SPLINT_S
    241 		FD_SET(FD_SET_T info->io[i].fd, &info->rset);
    242 		info->io[i].timeout.tv_usec = ((START_IO_INTERVAL*i)%1000)
    243 						*1000;
    244 		info->io[i].timeout.tv_sec = (START_IO_INTERVAL*i)/1000;
    245 		perf_tv_add(&info->io[i].timeout, &info->since);
    246 #endif
    247 	}
    248 }
    249 
    250 /** cleanup perf test environment */
    251 static void
    252 perffree(struct perfinfo* info)
    253 {
    254 	size_t i;
    255 	if(!info) return;
    256 	if(info->io) {
    257 		for(i=0; i<info->io_num; i++) {
    258 			sock_close(info->io[i].fd);
    259 		}
    260 		free(info->io);
    261 	}
    262 	for(i=0; i<info->qlist_size; i++)
    263 		free(info->qlist_data[i]);
    264 	free(info->qlist_data);
    265 	free(info->qlist_len);
    266 }
    267 
    268 /** send new query for io */
    269 static void
    270 perfsend(struct perfinfo* info, size_t n, struct timeval* now)
    271 {
    272 	ssize_t r;
    273 	r = sendto(info->io[n].fd, (void*)info->qlist_data[info->qlist_idx],
    274 		info->qlist_len[info->qlist_idx], 0,
    275 		(struct sockaddr*)&info->dest, info->destlen);
    276 	/*log_hex("send", info->qlist_data[info->qlist_idx],
    277 		info->qlist_len[info->qlist_idx]);*/
    278 	if(r == -1) {
    279 		log_err("sendto: %s", sock_strerror(errno));
    280 	} else if(r != (ssize_t)info->qlist_len[info->qlist_idx]) {
    281 		log_err("partial sendto");
    282 	}
    283 	info->qlist_idx = (info->qlist_idx+1) % info->qlist_size;
    284 	info->numsent++;
    285 
    286 	info->io[n].timeout.tv_sec = IO_TIMEOUT/1000;
    287 	info->io[n].timeout.tv_usec = (IO_TIMEOUT%1000)*1000;
    288 	perf_tv_add(&info->io[n].timeout, now);
    289 }
    290 
    291 /** got reply for io */
    292 static void
    293 perfreply(struct perfinfo* info, size_t n, struct timeval* now)
    294 {
    295 	ssize_t r;
    296 	r = recv(info->io[n].fd, (void*)sldns_buffer_begin(info->buf),
    297 		sldns_buffer_capacity(info->buf), 0);
    298 	if(r == -1) {
    299 		log_err("recv: %s", sock_strerror(errno));
    300 	} else {
    301 		info->by_rcode[LDNS_RCODE_WIRE(sldns_buffer_begin(
    302 			info->buf))]++;
    303 		info->numrecv++;
    304 	}
    305 	/*sldns_buffer_set_limit(info->buf, r);
    306 	log_buf(0, "reply", info->buf);*/
    307 	perfsend(info, n, now);
    308 }
    309 
    310 /** got timeout for io */
    311 static void
    312 perftimeout(struct perfinfo* info, size_t n, struct timeval* now)
    313 {
    314 	/* may not be a dropped packet, this is also used to start
    315 	 * up the sending IOs */
    316 	perfsend(info, n, now);
    317 }
    318 
    319 /** print nice stats about qps */
    320 static void
    321 stat_printout(struct perfinfo* info, struct timeval* now,
    322 	struct timeval* elapsed)
    323 {
    324 	/* calculate qps */
    325 	double dt, qps = 0;
    326 #ifndef S_SPLINT_S
    327 	dt = (double)(elapsed->tv_sec*1000000 + elapsed->tv_usec) / 1000000;
    328 #endif
    329 	if(dt > 0.001)
    330 		qps = (double)(info->numrecv) / dt;
    331 	if(!info->quiet)
    332 		printf("qps: %g\n", qps);
    333 	/* setup next slice */
    334 	info->since = *now;
    335 	info->total_sent += info->numsent;
    336 	info->total_recv += info->numrecv;
    337 	info->numrecv = 0;
    338 	info->numsent = 0;
    339 }
    340 
    341 /** wait for new events for performance test */
    342 static void
    343 perfselect(struct perfinfo* info)
    344 {
    345 	fd_set rset = info->rset;
    346 	struct timeval timeout, now;
    347 	int num;
    348 	size_t i;
    349 	if(gettimeofday(&now, NULL) < 0)
    350 		fatal_exit("gettimeofday: %s", strerror(errno));
    351 	/* time to exit? */
    352 	if(info->duration > 0) {
    353 		timeout = now;
    354 		perf_tv_subtract(&timeout, &info->start);
    355 		if((int)timeout.tv_sec >= info->duration) {
    356 			info->exit = 1;
    357 			return;
    358 		}
    359 	}
    360 	/* time for stats printout? */
    361 	timeout = now;
    362 	perf_tv_subtract(&timeout, &info->since);
    363 	if(timeout.tv_sec > 0) {
    364 		stat_printout(info, &now, &timeout);
    365 	}
    366 	/* see what is closest port to timeout; or if there is a timeout */
    367 	timeout = info->io[0].timeout;
    368 	for(i=0; i<info->io_num; i++) {
    369 		if(perf_tv_smaller(&info->io[i].timeout, &now)) {
    370 			perftimeout(info, i, &now);
    371 			return;
    372 		}
    373 		if(perf_tv_smaller(&info->io[i].timeout, &timeout)) {
    374 			timeout = info->io[i].timeout;
    375 		}
    376 	}
    377 	perf_tv_subtract(&timeout, &now);
    378 
    379 	num = select(info->maxfd+1, &rset, NULL, NULL, &timeout);
    380 	if(num == -1) {
    381 		if(errno == EAGAIN || errno == EINTR)
    382 			return;
    383 		log_err("select: %s", strerror(errno));
    384 	}
    385 
    386 	/* handle new events */
    387 	for(i=0; num && i<info->io_num; i++) {
    388 		if(FD_ISSET(info->io[i].fd, &rset)) {
    389 			perfreply(info, i, &now);
    390 			num--;
    391 		}
    392 	}
    393 }
    394 
    395 /** show end stats */
    396 static void
    397 perfendstats(struct perfinfo* info)
    398 {
    399 	double dt, qps;
    400 	struct timeval timeout, now;
    401 	int i, lost;
    402 	if(gettimeofday(&now, NULL) < 0)
    403 		fatal_exit("gettimeofday: %s", strerror(errno));
    404 	timeout = now;
    405 	perf_tv_subtract(&timeout, &info->since);
    406 	stat_printout(info, &now, &timeout);
    407 
    408 	timeout = now;
    409 	perf_tv_subtract(&timeout, &info->start);
    410 	dt = (double)(timeout.tv_sec*1000000 + timeout.tv_usec) / 1000000.0;
    411 	qps = (double)(info->total_recv) / dt;
    412 	lost = (int)(info->total_sent - info->total_recv) - (int)info->io_num;
    413 	if(!info->quiet) {
    414 		printf("overall time: 	%g sec\n",
    415 			(double)timeout.tv_sec +
    416 			(double)timeout.tv_usec/1000000.);
    417 		if(lost > 0)
    418 			printf("Packets lost: 	%d\n", (int)lost);
    419 
    420 		for(i=0; i<(int)(sizeof(info->by_rcode)/sizeof(size_t)); i++)
    421 		{
    422 			if(info->by_rcode[i] > 0) {
    423 				char rc[16];
    424 				sldns_wire2str_rcode_buf(i, rc, sizeof(rc));
    425 				printf("%d(%5s): 	%u replies\n",
    426 					i, rc, (unsigned)info->by_rcode[i]);
    427 			}
    428 		}
    429 	}
    430 	printf("average qps: 	%g\n", qps);
    431 }
    432 
    433 /** perform the performance test */
    434 static void
    435 perfmain(struct perfinfo* info)
    436 {
    437 	perfsetup(info);
    438 	while(!info->exit) {
    439 		perfselect(info);
    440 	}
    441 	perfendstats(info);
    442 	perffree(info);
    443 }
    444 
    445 /** parse a query line to a packet into buffer */
    446 static int
    447 qlist_parse_line(sldns_buffer* buf, char* p)
    448 {
    449 	char nm[1024], cl[1024], tp[1024], fl[1024];
    450 	int r;
    451 	int rec = 1, edns = 0;
    452 	struct query_info qinfo;
    453 	nm[0] = 0; cl[0] = 0; tp[0] = 0; fl[0] = 0;
    454 	r = sscanf(p, " %1023s %1023s %1023s %1023s", nm, cl, tp, fl);
    455 	if(r != 3 && r != 4)
    456 		return 0;
    457 	/*printf("nm='%s', cl='%s', tp='%s', fl='%s'\n", nm, cl, tp, fl);*/
    458 	if(strcmp(tp, "IN") == 0 || strcmp(tp, "CH") == 0) {
    459 		qinfo.qtype = sldns_get_rr_type_by_name(cl);
    460 		qinfo.qclass = sldns_get_rr_class_by_name(tp);
    461 		if((qinfo.qtype == 0 && strcmp(cl, "TYPE0") != 0) ||
    462 			(qinfo.qclass == 0 && strcmp(tp, "CLASS0") != 0)) {
    463 			return 0;
    464 		}
    465 	} else {
    466 		qinfo.qtype = sldns_get_rr_type_by_name(tp);
    467 		qinfo.qclass = sldns_get_rr_class_by_name(cl);
    468 		if((qinfo.qtype == 0 && strcmp(tp, "TYPE0") != 0) ||
    469 			(qinfo.qclass == 0 && strcmp(cl, "CLASS0") != 0)) {
    470 			return 0;
    471 		}
    472 	}
    473 	if(fl[0] == '+') rec = 1;
    474 	else if(fl[0] == '-') rec = 0;
    475 	else if(fl[0] == 'E') edns = 1;
    476 	if((fl[0] == '+' || fl[0] == '-') && fl[1] == 'E')
    477 		edns = 1;
    478 	qinfo.qname = sldns_str2wire_dname(nm, &qinfo.qname_len);
    479 	if(!qinfo.qname)
    480 		return 0;
    481 	qinfo.local_alias = NULL;
    482 	qinfo_query_encode(buf, &qinfo);
    483 	sldns_buffer_write_u16_at(buf, 0, 0); /* zero ID */
    484 	if(rec) LDNS_RD_SET(sldns_buffer_begin(buf));
    485 	if(edns) {
    486 		struct edns_data ed;
    487 		memset(&ed, 0, sizeof(ed));
    488 		ed.edns_present = 1;
    489 		ed.udp_size = EDNS_ADVERTISED_SIZE;
    490 		/* Set DO bit in all EDNS datagrams ... */
    491 		ed.bits = EDNS_DO;
    492 		attach_edns_record(buf, &ed);
    493 	}
    494 	free(qinfo.qname);
    495 	return 1;
    496 }
    497 
    498 /** grow query list capacity */
    499 static void
    500 qlist_grow_capacity(struct perfinfo* info)
    501 {
    502 	size_t newcap = (size_t)((info->qlist_capacity==0)?16:
    503 		info->qlist_capacity*2);
    504 	uint8_t** d = (uint8_t**)calloc(sizeof(uint8_t*), newcap);
    505 	size_t* l = (size_t*)calloc(sizeof(size_t), newcap);
    506 	if(!d || !l) fatal_exit("out of memory");
    507 	if(info->qlist_data && info->qlist_capacity)
    508 		memcpy(d, info->qlist_data, sizeof(uint8_t*)*
    509 			info->qlist_capacity);
    510 	if(info->qlist_len && info->qlist_capacity)
    511 		memcpy(l, info->qlist_len, sizeof(size_t)*
    512 			info->qlist_capacity);
    513 	free(info->qlist_data);
    514 	free(info->qlist_len);
    515 	info->qlist_data = d;
    516 	info->qlist_len = l;
    517 	info->qlist_capacity = newcap;
    518 }
    519 
    520 /** setup query list in info */
    521 static void
    522 qlist_add_line(struct perfinfo* info, char* line, int no)
    523 {
    524 	if(!qlist_parse_line(info->buf, line)) {
    525 		printf("error parsing query %d: %s\n", no, line);
    526 		exit(1);
    527 	}
    528 	sldns_buffer_write_u16_at(info->buf, 0, (uint16_t)info->qlist_size);
    529 	if(info->qlist_size + 1 > info->qlist_capacity) {
    530 		qlist_grow_capacity(info);
    531 	}
    532 	info->qlist_len[info->qlist_size] = sldns_buffer_limit(info->buf);
    533 	info->qlist_data[info->qlist_size] = memdup(
    534 		sldns_buffer_begin(info->buf), sldns_buffer_limit(info->buf));
    535 	if(!info->qlist_data[info->qlist_size])
    536 		fatal_exit("out of memory");
    537 	info->qlist_size ++;
    538 }
    539 
    540 /** setup query list in info */
    541 static void
    542 qlist_read_file(struct perfinfo* info, char* fname)
    543 {
    544 	char buf[1024];
    545 	char *p;
    546 	FILE* in = fopen(fname, "r");
    547 	int lineno = 0;
    548 	if(!in) {
    549 		perror(fname);
    550 		exit(1);
    551 	}
    552 	while(fgets(buf, (int)sizeof(buf), in)) {
    553 		lineno++;
    554 		buf[sizeof(buf)-1] = 0;
    555 		p = buf;
    556 		while(*p == ' ' || *p == '\t')
    557 			p++;
    558 		if(p[0] == 0 || p[0] == '\n' || p[0] == ';' || p[0] == '#')
    559 			continue;
    560 		qlist_add_line(info, p, lineno);
    561 	}
    562 	printf("Read %s, got %u queries\n", fname, (unsigned)info->qlist_size);
    563 	fclose(in);
    564 }
    565 
    566 /** getopt global, in case header files fail to declare it. */
    567 extern int optind;
    568 /** getopt global, in case header files fail to declare it. */
    569 extern char* optarg;
    570 
    571 /** main program for perf */
    572 int main(int argc, char* argv[])
    573 {
    574 	char* nm = argv[0];
    575 	int c;
    576 	struct perfinfo info;
    577 #ifdef USE_WINSOCK
    578 	int r;
    579 	WSADATA wsa_data;
    580 #endif
    581 
    582 	/* defaults */
    583 	memset(&info, 0, sizeof(info));
    584 	info.io_num = 16;
    585 
    586 	checklock_start();
    587 	log_init(NULL, 0, NULL);
    588 	log_ident_set("perf");
    589 #ifdef USE_WINSOCK
    590 	if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0)
    591 		fatal_exit("WSAStartup failed: %s", wsa_strerror(r));
    592 #endif
    593 
    594 	info.buf = sldns_buffer_new(65553);
    595 	if(!info.buf) fatal_exit("out of memory");
    596 
    597 	/* parse the options */
    598 	while( (c=getopt(argc, argv, "d:ha:f:q")) != -1) {
    599 		switch(c) {
    600 		case 'q':
    601 			info.quiet = 1;
    602 			break;
    603 		case 'd':
    604 			if(atoi(optarg)==0 && strcmp(optarg, "0")!=0) {
    605 				printf("-d not a number %s", optarg);
    606 				exit(1);
    607 			}
    608 			info.duration = atoi(optarg);
    609 			break;
    610 		case 'a':
    611 			qlist_add_line(&info, optarg, 0);
    612 			break;
    613 		case 'f':
    614 			qlist_read_file(&info, optarg);
    615 			break;
    616 		case '?':
    617 		case 'h':
    618 		default:
    619 			usage(nm);
    620 		}
    621 	}
    622 	argc -= optind;
    623 	argv += optind;
    624 
    625 	if(argc != 1) {
    626 		printf("error: pass server IP address on commandline.\n");
    627 		usage(nm);
    628 	}
    629 	if(!extstrtoaddr(argv[0], &info.dest, &info.destlen, UNBOUND_DNS_PORT)) {
    630 		printf("Could not parse ip: %s\n", argv[0]);
    631 		exit(1);
    632 	}
    633 	if(info.qlist_size == 0) {
    634 		printf("No queries to make, use -f or -a.\n");
    635 		exit(1);
    636 	}
    637 
    638 	/* do the performance test */
    639 	perfmain(&info);
    640 
    641 	sldns_buffer_free(info.buf);
    642 #ifdef USE_WINSOCK
    643 	WSACleanup();
    644 #endif
    645 	checklock_stop();
    646 	return 0;
    647 }
    648