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 } 640