]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - testcode/perf.c
Vendor import of Unbound 1.9.0.
[FreeBSD/FreeBSD.git] / 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 "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 #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.local_alias = NULL;
491         qinfo_query_encode(buf, &qinfo);
492         sldns_buffer_write_u16_at(buf, 0, 0); /* zero ID */
493         if(rec) LDNS_RD_SET(sldns_buffer_begin(buf));
494         if(edns) {
495                 struct edns_data ed;
496                 memset(&ed, 0, sizeof(ed));
497                 ed.edns_present = 1;
498                 ed.udp_size = EDNS_ADVERTISED_SIZE;
499                 /* Set DO bit in all EDNS datagrams ... */
500                 ed.bits = EDNS_DO;
501                 attach_edns_record(buf, &ed);
502         }
503         free(qinfo.qname);
504         return 1;
505 }
506
507 /** grow query list capacity */
508 static void
509 qlist_grow_capacity(struct perfinfo* info)
510 {
511         size_t newcap = (size_t)((info->qlist_capacity==0)?16:
512                 info->qlist_capacity*2);
513         uint8_t** d = (uint8_t**)calloc(sizeof(uint8_t*), newcap);
514         size_t* l = (size_t*)calloc(sizeof(size_t), newcap);
515         if(!d || !l) fatal_exit("out of memory");
516         if(info->qlist_data && info->qlist_capacity)
517                 memcpy(d, info->qlist_data, sizeof(uint8_t*)*
518                         info->qlist_capacity);
519         if(info->qlist_len && info->qlist_capacity)
520                 memcpy(l, info->qlist_len, sizeof(size_t)*
521                         info->qlist_capacity);
522         free(info->qlist_data);
523         free(info->qlist_len);
524         info->qlist_data = d;
525         info->qlist_len = l;
526         info->qlist_capacity = newcap;
527 }
528
529 /** setup query list in info */
530 static void
531 qlist_add_line(struct perfinfo* info, char* line, int no)
532 {
533         if(!qlist_parse_line(info->buf, line)) {
534                 printf("error parsing query %d: %s\n", no, line);
535                 exit(1);
536         }
537         sldns_buffer_write_u16_at(info->buf, 0, (uint16_t)info->qlist_size); 
538         if(info->qlist_size + 1 > info->qlist_capacity) {
539                 qlist_grow_capacity(info);
540         }
541         info->qlist_len[info->qlist_size] = sldns_buffer_limit(info->buf);
542         info->qlist_data[info->qlist_size] = memdup(
543                 sldns_buffer_begin(info->buf), sldns_buffer_limit(info->buf));
544         if(!info->qlist_data[info->qlist_size])
545                 fatal_exit("out of memory");
546         info->qlist_size ++;
547 }
548
549 /** setup query list in info */
550 static void
551 qlist_read_file(struct perfinfo* info, char* fname)
552 {
553         char buf[1024];
554         char *p;
555         FILE* in = fopen(fname, "r");
556         int lineno = 0;
557         if(!in) {
558                 perror(fname);
559                 exit(1);
560         }
561         while(fgets(buf, (int)sizeof(buf), in)) {
562                 lineno++;
563                 buf[sizeof(buf)-1] = 0;
564                 p = buf;
565                 while(*p == ' ' || *p == '\t')
566                         p++;
567                 if(p[0] == 0 || p[0] == '\n' || p[0] == ';' || p[0] == '#')
568                         continue;
569                 qlist_add_line(info, p, lineno);
570         }
571         printf("Read %s, got %u queries\n", fname, (unsigned)info->qlist_size);
572         fclose(in);
573 }
574
575 /** getopt global, in case header files fail to declare it. */
576 extern int optind;
577 /** getopt global, in case header files fail to declare it. */
578 extern char* optarg;
579
580 /** main program for perf */
581 int main(int argc, char* argv[]) 
582 {
583         char* nm = argv[0];
584         int c;
585         struct perfinfo info;
586 #ifdef USE_WINSOCK
587         int r;
588         WSADATA wsa_data;
589 #endif
590
591         /* defaults */
592         memset(&info, 0, sizeof(info));
593         info.io_num = 16;
594
595         log_init(NULL, 0, NULL);
596         log_ident_set("perf");
597         checklock_start();
598 #ifdef USE_WINSOCK
599         if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0)
600                 fatal_exit("WSAStartup failed: %s", wsa_strerror(r));
601 #endif
602
603         info.buf = sldns_buffer_new(65553);
604         if(!info.buf) fatal_exit("out of memory");
605
606         /* parse the options */
607         while( (c=getopt(argc, argv, "d:ha:f:q")) != -1) {
608                 switch(c) {
609                 case 'q':
610                         info.quiet = 1;
611                         break;
612                 case 'd':
613                         if(atoi(optarg)==0 && strcmp(optarg, "0")!=0) {
614                                 printf("-d not a number %s", optarg);
615                                 exit(1);
616                         }
617                         info.duration = atoi(optarg);
618                         break;
619                 case 'a':
620                         qlist_add_line(&info, optarg, 0);
621                         break;
622                 case 'f':
623                         qlist_read_file(&info, optarg);
624                         break;
625                 case '?':
626                 case 'h':
627                 default:
628                         usage(nm);
629                 }
630         }
631         argc -= optind;
632         argv += optind;
633
634         if(argc != 1) {
635                 printf("error: pass server IP address on commandline.\n");
636                 usage(nm);
637         }
638         if(!extstrtoaddr(argv[0], &info.dest, &info.destlen)) {
639                 printf("Could not parse ip: %s\n", argv[0]);
640                 exit(1);
641         }
642         if(info.qlist_size == 0) {
643                 printf("No queries to make, use -f or -a.\n");
644                 exit(1);
645         }
646         
647         /* do the performance test */
648         perfmain(&info);
649
650         sldns_buffer_free(info.buf);
651 #ifdef USE_WINSOCK
652         WSACleanup();
653 #endif
654         checklock_stop();
655         return 0;
656 }