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