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