]> git.saurik.com Git - apple/network_cmds.git/blob - unbound/testcode/perf.c
c51eee4b161d4533528b4638491ce0301f9495a8
[apple/network_cmds.git] / unbound / testcode / perf.c
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 "ldns/sbuffer.h"
54 #include "ldns/wire2str.h"
55 #include "ldns/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 #ifndef USE_WINSOCK
237 fatal_exit("socket: %s", strerror(errno));
238 #else
239 fatal_exit("socket: %s",
240 wsa_strerror(WSAGetLastError()));
241 #endif
242 }
243 if(info->io[i].fd > info->maxfd)
244 info->maxfd = info->io[i].fd;
245 #ifndef S_SPLINT_S
246 FD_SET(FD_SET_T info->io[i].fd, &info->rset);
247 info->io[i].timeout.tv_usec = ((START_IO_INTERVAL*i)%1000)
248 *1000;
249 info->io[i].timeout.tv_sec = (START_IO_INTERVAL*i)/1000;
250 perf_tv_add(&info->io[i].timeout, &info->since);
251 #endif
252 }
253 }
254
255 /** cleanup perf test environment */
256 static void
257 perffree(struct perfinfo* info)
258 {
259 size_t i;
260 if(!info) return;
261 if(info->io) {
262 for(i=0; i<info->io_num; i++) {
263 #ifndef USE_WINSOCK
264 close(info->io[i].fd);
265 #else
266 closesocket(info->io[i].fd);
267 #endif
268 }
269 free(info->io);
270 }
271 for(i=0; i<info->qlist_size; i++)
272 free(info->qlist_data[i]);
273 free(info->qlist_data);
274 free(info->qlist_len);
275 }
276
277 /** send new query for io */
278 static void
279 perfsend(struct perfinfo* info, size_t n, struct timeval* now)
280 {
281 ssize_t r;
282 r = sendto(info->io[n].fd, (void*)info->qlist_data[info->qlist_idx],
283 info->qlist_len[info->qlist_idx], 0,
284 (struct sockaddr*)&info->dest, info->destlen);
285 /*log_hex("send", info->qlist_data[info->qlist_idx],
286 info->qlist_len[info->qlist_idx]);*/
287 if(r == -1) {
288 #ifndef USE_WINSOCK
289 log_err("sendto: %s", strerror(errno));
290 #else
291 log_err("sendto: %s", wsa_strerror(WSAGetLastError()));
292 #endif
293 } else if(r != (ssize_t)info->qlist_len[info->qlist_idx]) {
294 log_err("partial sendto");
295 }
296 info->qlist_idx = (info->qlist_idx+1) % info->qlist_size;
297 info->numsent++;
298
299 info->io[n].timeout.tv_sec = IO_TIMEOUT/1000;
300 info->io[n].timeout.tv_usec = (IO_TIMEOUT%1000)*1000;
301 perf_tv_add(&info->io[n].timeout, now);
302 }
303
304 /** got reply for io */
305 static void
306 perfreply(struct perfinfo* info, size_t n, struct timeval* now)
307 {
308 ssize_t r;
309 r = recv(info->io[n].fd, (void*)sldns_buffer_begin(info->buf),
310 sldns_buffer_capacity(info->buf), 0);
311 if(r == -1) {
312 #ifndef USE_WINSOCK
313 log_err("recv: %s", strerror(errno));
314 #else
315 log_err("recv: %s", wsa_strerror(WSAGetLastError()));
316 #endif
317 } else {
318 info->by_rcode[LDNS_RCODE_WIRE(sldns_buffer_begin(
319 info->buf))]++;
320 info->numrecv++;
321 }
322 /*sldns_buffer_set_limit(info->buf, r);
323 log_buf(0, "reply", info->buf);*/
324 perfsend(info, n, now);
325 }
326
327 /** got timeout for io */
328 static void
329 perftimeout(struct perfinfo* info, size_t n, struct timeval* now)
330 {
331 /* may not be a dropped packet, this is also used to start
332 * up the sending IOs */
333 perfsend(info, n, now);
334 }
335
336 /** print nice stats about qps */
337 static void
338 stat_printout(struct perfinfo* info, struct timeval* now,
339 struct timeval* elapsed)
340 {
341 /* calculate qps */
342 double dt, qps = 0;
343 #ifndef S_SPLINT_S
344 dt = (double)(elapsed->tv_sec*1000000 + elapsed->tv_usec) / 1000000;
345 #endif
346 if(dt > 0.001)
347 qps = (double)(info->numrecv) / dt;
348 if(!info->quiet)
349 printf("qps: %g\n", qps);
350 /* setup next slice */
351 info->since = *now;
352 info->total_sent += info->numsent;
353 info->total_recv += info->numrecv;
354 info->numrecv = 0;
355 info->numsent = 0;
356 }
357
358 /** wait for new events for performance test */
359 static void
360 perfselect(struct perfinfo* info)
361 {
362 fd_set rset = info->rset;
363 struct timeval timeout, now;
364 int num;
365 size_t i;
366 if(gettimeofday(&now, NULL) < 0)
367 fatal_exit("gettimeofday: %s", strerror(errno));
368 /* time to exit? */
369 if(info->duration > 0) {
370 timeout = now;
371 perf_tv_subtract(&timeout, &info->start);
372 if((int)timeout.tv_sec >= info->duration) {
373 info->exit = 1;
374 return;
375 }
376 }
377 /* time for stats printout? */
378 timeout = now;
379 perf_tv_subtract(&timeout, &info->since);
380 if(timeout.tv_sec > 0) {
381 stat_printout(info, &now, &timeout);
382 }
383 /* see what is closest port to timeout; or if there is a timeout */
384 timeout = info->io[0].timeout;
385 for(i=0; i<info->io_num; i++) {
386 if(perf_tv_smaller(&info->io[i].timeout, &now)) {
387 perftimeout(info, i, &now);
388 return;
389 }
390 if(perf_tv_smaller(&info->io[i].timeout, &timeout)) {
391 timeout = info->io[i].timeout;
392 }
393 }
394 perf_tv_subtract(&timeout, &now);
395
396 num = select(info->maxfd+1, &rset, NULL, NULL, &timeout);
397 if(num == -1) {
398 if(errno == EAGAIN || errno == EINTR)
399 return;
400 log_err("select: %s", strerror(errno));
401 }
402
403 /* handle new events */
404 for(i=0; num && i<info->io_num; i++) {
405 if(FD_ISSET(info->io[i].fd, &rset)) {
406 perfreply(info, i, &now);
407 num--;
408 }
409 }
410 }
411
412 /** show end stats */
413 static void
414 perfendstats(struct perfinfo* info)
415 {
416 double dt, qps;
417 struct timeval timeout, now;
418 int i, lost;
419 if(gettimeofday(&now, NULL) < 0)
420 fatal_exit("gettimeofday: %s", strerror(errno));
421 timeout = now;
422 perf_tv_subtract(&timeout, &info->since);
423 stat_printout(info, &now, &timeout);
424
425 timeout = now;
426 perf_tv_subtract(&timeout, &info->start);
427 dt = (double)(timeout.tv_sec*1000000 + timeout.tv_usec) / 1000000.0;
428 qps = (double)(info->total_recv) / dt;
429 lost = (int)(info->total_sent - info->total_recv) - (int)info->io_num;
430 if(!info->quiet) {
431 printf("overall time: %g sec\n",
432 (double)timeout.tv_sec +
433 (double)timeout.tv_usec/1000000.);
434 if(lost > 0)
435 printf("Packets lost: %d\n", (int)lost);
436
437 for(i=0; i<(int)(sizeof(info->by_rcode)/sizeof(size_t)); i++)
438 {
439 if(info->by_rcode[i] > 0) {
440 char rc[16];
441 sldns_wire2str_rcode_buf(i, rc, sizeof(rc));
442 printf("%d(%5s): %u replies\n",
443 i, rc, (unsigned)info->by_rcode[i]);
444 }
445 }
446 }
447 printf("average qps: %g\n", qps);
448 }
449
450 /** perform the performance test */
451 static void
452 perfmain(struct perfinfo* info)
453 {
454 perfsetup(info);
455 while(!info->exit) {
456 perfselect(info);
457 }
458 perfendstats(info);
459 perffree(info);
460 }
461
462 /** parse a query line to a packet into buffer */
463 static int
464 qlist_parse_line(sldns_buffer* buf, char* p)
465 {
466 char nm[1024], cl[1024], tp[1024], fl[1024];
467 int r;
468 int rec = 1, edns = 0;
469 struct query_info qinfo;
470 nm[0] = 0; cl[0] = 0; tp[0] = 0; fl[0] = 0;
471 r = sscanf(p, " %1023s %1023s %1023s %1023s", nm, cl, tp, fl);
472 if(r != 3 && r != 4)
473 return 0;
474 /*printf("nm='%s', cl='%s', tp='%s', fl='%s'\n", nm, cl, tp, fl);*/
475 if(strcmp(tp, "IN") == 0 || strcmp(tp, "CH") == 0) {
476 qinfo.qtype = sldns_get_rr_type_by_name(cl);
477 qinfo.qclass = sldns_get_rr_class_by_name(tp);
478 } else {
479 qinfo.qtype = sldns_get_rr_type_by_name(tp);
480 qinfo.qclass = sldns_get_rr_class_by_name(cl);
481 }
482 if(fl[0] == '+') rec = 1;
483 else if(fl[0] == '-') rec = 0;
484 else if(fl[0] == 'E') edns = 1;
485 if((fl[0] == '+' || fl[0] == '-') && fl[1] == 'E')
486 edns = 1;
487 qinfo.qname = sldns_str2wire_dname(nm, &qinfo.qname_len);
488 if(!qinfo.qname)
489 return 0;
490 qinfo_query_encode(buf, &qinfo);
491 sldns_buffer_write_u16_at(buf, 0, 0); /* zero ID */
492 if(rec) LDNS_RD_SET(sldns_buffer_begin(buf));
493 if(edns) {
494 struct edns_data ed;
495 memset(&ed, 0, sizeof(ed));
496 ed.edns_present = 1;
497 ed.udp_size = EDNS_ADVERTISED_SIZE;
498 /* Set DO bit in all EDNS datagrams ... */
499 ed.bits = EDNS_DO;
500 attach_edns_record(buf, &ed);
501 }
502 free(qinfo.qname);
503 return 1;
504 }
505
506 /** grow query list capacity */
507 static void
508 qlist_grow_capacity(struct perfinfo* info)
509 {
510 size_t newcap = (size_t)((info->qlist_capacity==0)?16:
511 info->qlist_capacity*2);
512 uint8_t** d = (uint8_t**)calloc(sizeof(uint8_t*), newcap);
513 size_t* l = (size_t*)calloc(sizeof(size_t), newcap);
514 if(!d || !l) fatal_exit("out of memory");
515 memcpy(d, info->qlist_data, sizeof(uint8_t*)*
516 info->qlist_capacity);
517 memcpy(l, info->qlist_len, sizeof(size_t)*
518 info->qlist_capacity);
519 free(info->qlist_data);
520 free(info->qlist_len);
521 info->qlist_data = d;
522 info->qlist_len = l;
523 info->qlist_capacity = newcap;
524 }
525
526 /** setup query list in info */
527 static void
528 qlist_add_line(struct perfinfo* info, char* line, int no)
529 {
530 if(!qlist_parse_line(info->buf, line)) {
531 printf("error parsing query %d: %s\n", no, line);
532 exit(1);
533 }
534 sldns_buffer_write_u16_at(info->buf, 0, (uint16_t)info->qlist_size);
535 if(info->qlist_size + 1 > info->qlist_capacity) {
536 qlist_grow_capacity(info);
537 }
538 info->qlist_len[info->qlist_size] = sldns_buffer_limit(info->buf);
539 info->qlist_data[info->qlist_size] = memdup(
540 sldns_buffer_begin(info->buf), sldns_buffer_limit(info->buf));
541 if(!info->qlist_data[info->qlist_size])
542 fatal_exit("out of memory");
543 info->qlist_size ++;
544 }
545
546 /** setup query list in info */
547 static void
548 qlist_read_file(struct perfinfo* info, char* fname)
549 {
550 char buf[1024];
551 char *p;
552 FILE* in = fopen(fname, "r");
553 int lineno = 0;
554 if(!in) {
555 perror(fname);
556 exit(1);
557 }
558 while(fgets(buf, (int)sizeof(buf), in)) {
559 lineno++;
560 buf[sizeof(buf)-1] = 0;
561 p = buf;
562 while(*p == ' ' || *p == '\t')
563 p++;
564 if(p[0] == 0 || p[0] == '\n' || p[0] == ';' || p[0] == '#')
565 continue;
566 qlist_add_line(info, p, lineno);
567 }
568 printf("Read %s, got %u queries\n", fname, (unsigned)info->qlist_size);
569 fclose(in);
570 }
571
572 /** getopt global, in case header files fail to declare it. */
573 extern int optind;
574 /** getopt global, in case header files fail to declare it. */
575 extern char* optarg;
576
577 /** main program for perf */
578 int main(int argc, char* argv[])
579 {
580 char* nm = argv[0];
581 int c;
582 struct perfinfo info;
583 #ifdef USE_WINSOCK
584 int r;
585 WSADATA wsa_data;
586 #endif
587
588 /* defaults */
589 memset(&info, 0, sizeof(info));
590 info.io_num = 16;
591
592 log_init(NULL, 0, NULL);
593 log_ident_set("perf");
594 checklock_start();
595 #ifdef USE_WINSOCK
596 if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0)
597 fatal_exit("WSAStartup failed: %s", wsa_strerror(r));
598 #endif
599
600 info.buf = sldns_buffer_new(65553);
601 if(!info.buf) fatal_exit("out of memory");
602
603 /* parse the options */
604 while( (c=getopt(argc, argv, "d:ha:f:q")) != -1) {
605 switch(c) {
606 case 'q':
607 info.quiet = 1;
608 break;
609 case 'd':
610 if(atoi(optarg)==0 && strcmp(optarg, "0")!=0) {
611 printf("-d not a number %s", optarg);
612 return 1;
613 }
614 info.duration = atoi(optarg);
615 break;
616 case 'a':
617 qlist_add_line(&info, optarg, 0);
618 break;
619 case 'f':
620 qlist_read_file(&info, optarg);
621 break;
622 case '?':
623 case 'h':
624 default:
625 usage(nm);
626 }
627 }
628 argc -= optind;
629 argv += optind;
630
631 if(argc != 1) {
632 printf("error: pass server IP address on commandline.\n");
633 usage(nm);
634 }
635 if(!extstrtoaddr(argv[0], &info.dest, &info.destlen)) {
636 printf("Could not parse ip: %s\n", argv[0]);
637 return 1;
638 }
639 if(info.qlist_size == 0) {
640 printf("No queries to make, use -f or -a.\n");
641 return 1;
642 }
643
644 /* do the performance test */
645 perfmain(&info);
646
647 sldns_buffer_free(info.buf);
648 #ifdef USE_WINSOCK
649 WSACleanup();
650 #endif
651 checklock_stop();
652 return 0;
653 }