diff options
Diffstat (limited to '')
-rw-r--r-- | external/unbound/testcode/perf.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/external/unbound/testcode/perf.c b/external/unbound/testcode/perf.c new file mode 100644 index 000000000..c51eee4b1 --- /dev/null +++ b/external/unbound/testcode/perf.c @@ -0,0 +1,653 @@ +/* + * testcode/perf.c - debug program to estimate name server performance. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This program estimates DNS name server performance. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif +#include <signal.h> +#include "util/log.h" +#include "util/locks.h" +#include "util/net_help.h" +#include "util/data/msgencode.h" +#include "util/data/msgreply.h" +#include "util/data/msgparse.h" +#include "ldns/sbuffer.h" +#include "ldns/wire2str.h" +#include "ldns/str2wire.h" +#include <sys/time.h> + +/** usage information for perf */ +static void usage(char* nm) +{ + printf("usage: %s [options] server\n", nm); + printf("server: ip address of server, IP4 or IP6.\n"); + printf(" If not on port %d add @port.\n", UNBOUND_DNS_PORT); + printf("-d sec duration of test in whole seconds (0: wait for ^C)\n"); + printf("-a str query to ask, interpreted as a line from qfile\n"); + printf("-f fnm query list to read from file\n"); + printf(" every line has format: qname qclass qtype [+-]{E}\n"); + printf(" where + means RD set, E means EDNS enabled\n"); + printf("-q quiet mode, print only final qps\n"); + exit(1); +} + +struct perfinfo; +struct perfio; + +/** Global info for perf */ +struct perfinfo { + /** need to exit */ + volatile int exit; + /** all purpose buffer (for UDP send and receive) */ + sldns_buffer* buf; + + /** destination */ + struct sockaddr_storage dest; + /** length of dest socket addr */ + socklen_t destlen; + + /** when did this time slice start */ + struct timeval since; + /** number of queries received in that time */ + size_t numrecv; + /** number of queries sent out in that time */ + size_t numsent; + + /** duration of test in seconds */ + int duration; + /** quiet mode? */ + int quiet; + + /** when did the total test start */ + struct timeval start; + /** total number recvd */ + size_t total_recv; + /** total number sent */ + size_t total_sent; + /** numbers by rcode */ + size_t by_rcode[32]; + + /** number of I/O ports */ + size_t io_num; + /** I/O ports array */ + struct perfio* io; + /** max fd value in io ports */ + int maxfd; + /** readset */ + fd_set rset; + + /** size of querylist */ + size_t qlist_size; + /** allocated size of qlist array */ + size_t qlist_capacity; + /** list of query packets (data) */ + uint8_t** qlist_data; + /** list of query packets (length of a packet) */ + size_t* qlist_len; + /** index into querylist, for walking the list */ + size_t qlist_idx; +}; + +/** I/O port for perf */ +struct perfio { + /** id number */ + size_t id; + /** file descriptor of socket */ + int fd; + /** timeout value */ + struct timeval timeout; + /** ptr back to perfinfo */ + struct perfinfo* info; +}; + +/** number of msec between starting io ports */ +#define START_IO_INTERVAL 10 +/** number of msec timeout on io ports */ +#define IO_TIMEOUT 10 + +/** signal handler global info */ +static struct perfinfo* sig_info; + +/** signal handler for user quit */ +static RETSIGTYPE perf_sigh(int sig) +{ + log_assert(sig_info); + if(!sig_info->quiet) + printf("exit on signal %d\n", sig); + sig_info->exit = 1; +} + +/** timeval compare, t1 < t2 */ +static int +perf_tv_smaller(struct timeval* t1, struct timeval* t2) +{ +#ifndef S_SPLINT_S + if(t1->tv_sec < t2->tv_sec) + return 1; + if(t1->tv_sec == t2->tv_sec && + t1->tv_usec < t2->tv_usec) + return 1; +#endif + return 0; +} + +/** timeval add, t1 += t2 */ +static void +perf_tv_add(struct timeval* t1, struct timeval* t2) +{ +#ifndef S_SPLINT_S + t1->tv_sec += t2->tv_sec; + t1->tv_usec += t2->tv_usec; + while(t1->tv_usec > 1000000) { + t1->tv_usec -= 1000000; + t1->tv_sec++; + } +#endif +} + +/** timeval subtract, t1 -= t2 */ +static void +perf_tv_subtract(struct timeval* t1, struct timeval* t2) +{ +#ifndef S_SPLINT_S + t1->tv_sec -= t2->tv_sec; + if(t1->tv_usec >= t2->tv_usec) { + t1->tv_usec -= t2->tv_usec; + } else { + t1->tv_sec--; + t1->tv_usec = 1000000-(t2->tv_usec-t1->tv_usec); + } +#endif +} + + +/** setup perf test environment */ +static void +perfsetup(struct perfinfo* info) +{ + size_t i; + if(gettimeofday(&info->start, NULL) < 0) + fatal_exit("gettimeofday: %s", strerror(errno)); + sig_info = info; + if( signal(SIGINT, perf_sigh) == SIG_ERR || +#ifdef SIGQUIT + signal(SIGQUIT, perf_sigh) == SIG_ERR || +#endif +#ifdef SIGHUP + signal(SIGHUP, perf_sigh) == SIG_ERR || +#endif +#ifdef SIGBREAK + signal(SIGBREAK, perf_sigh) == SIG_ERR || +#endif + signal(SIGTERM, perf_sigh) == SIG_ERR) + fatal_exit("could not bind to signal"); + info->io = (struct perfio*)calloc(sizeof(struct perfio), info->io_num); + if(!info->io) fatal_exit("out of memory"); +#ifndef S_SPLINT_S + FD_ZERO(&info->rset); +#endif + info->since = info->start; + for(i=0; i<info->io_num; i++) { + info->io[i].id = i; + info->io[i].info = info; + info->io[i].fd = socket( + addr_is_ip6(&info->dest, info->destlen)? + AF_INET6:AF_INET, SOCK_DGRAM, 0); + if(info->io[i].fd == -1) { +#ifndef USE_WINSOCK + fatal_exit("socket: %s", strerror(errno)); +#else + fatal_exit("socket: %s", + wsa_strerror(WSAGetLastError())); +#endif + } + if(info->io[i].fd > info->maxfd) + info->maxfd = info->io[i].fd; +#ifndef S_SPLINT_S + FD_SET(FD_SET_T info->io[i].fd, &info->rset); + info->io[i].timeout.tv_usec = ((START_IO_INTERVAL*i)%1000) + *1000; + info->io[i].timeout.tv_sec = (START_IO_INTERVAL*i)/1000; + perf_tv_add(&info->io[i].timeout, &info->since); +#endif + } +} + +/** cleanup perf test environment */ +static void +perffree(struct perfinfo* info) +{ + size_t i; + if(!info) return; + if(info->io) { + for(i=0; i<info->io_num; i++) { +#ifndef USE_WINSOCK + close(info->io[i].fd); +#else + closesocket(info->io[i].fd); +#endif + } + free(info->io); + } + for(i=0; i<info->qlist_size; i++) + free(info->qlist_data[i]); + free(info->qlist_data); + free(info->qlist_len); +} + +/** send new query for io */ +static void +perfsend(struct perfinfo* info, size_t n, struct timeval* now) +{ + ssize_t r; + r = sendto(info->io[n].fd, (void*)info->qlist_data[info->qlist_idx], + info->qlist_len[info->qlist_idx], 0, + (struct sockaddr*)&info->dest, info->destlen); + /*log_hex("send", info->qlist_data[info->qlist_idx], + info->qlist_len[info->qlist_idx]);*/ + if(r == -1) { +#ifndef USE_WINSOCK + log_err("sendto: %s", strerror(errno)); +#else + log_err("sendto: %s", wsa_strerror(WSAGetLastError())); +#endif + } else if(r != (ssize_t)info->qlist_len[info->qlist_idx]) { + log_err("partial sendto"); + } + info->qlist_idx = (info->qlist_idx+1) % info->qlist_size; + info->numsent++; + + info->io[n].timeout.tv_sec = IO_TIMEOUT/1000; + info->io[n].timeout.tv_usec = (IO_TIMEOUT%1000)*1000; + perf_tv_add(&info->io[n].timeout, now); +} + +/** got reply for io */ +static void +perfreply(struct perfinfo* info, size_t n, struct timeval* now) +{ + ssize_t r; + r = recv(info->io[n].fd, (void*)sldns_buffer_begin(info->buf), + sldns_buffer_capacity(info->buf), 0); + if(r == -1) { +#ifndef USE_WINSOCK + log_err("recv: %s", strerror(errno)); +#else + log_err("recv: %s", wsa_strerror(WSAGetLastError())); +#endif + } else { + info->by_rcode[LDNS_RCODE_WIRE(sldns_buffer_begin( + info->buf))]++; + info->numrecv++; + } + /*sldns_buffer_set_limit(info->buf, r); + log_buf(0, "reply", info->buf);*/ + perfsend(info, n, now); +} + +/** got timeout for io */ +static void +perftimeout(struct perfinfo* info, size_t n, struct timeval* now) +{ + /* may not be a dropped packet, this is also used to start + * up the sending IOs */ + perfsend(info, n, now); +} + +/** print nice stats about qps */ +static void +stat_printout(struct perfinfo* info, struct timeval* now, + struct timeval* elapsed) +{ + /* calculate qps */ + double dt, qps = 0; +#ifndef S_SPLINT_S + dt = (double)(elapsed->tv_sec*1000000 + elapsed->tv_usec) / 1000000; +#endif + if(dt > 0.001) + qps = (double)(info->numrecv) / dt; + if(!info->quiet) + printf("qps: %g\n", qps); + /* setup next slice */ + info->since = *now; + info->total_sent += info->numsent; + info->total_recv += info->numrecv; + info->numrecv = 0; + info->numsent = 0; +} + +/** wait for new events for performance test */ +static void +perfselect(struct perfinfo* info) +{ + fd_set rset = info->rset; + struct timeval timeout, now; + int num; + size_t i; + if(gettimeofday(&now, NULL) < 0) + fatal_exit("gettimeofday: %s", strerror(errno)); + /* time to exit? */ + if(info->duration > 0) { + timeout = now; + perf_tv_subtract(&timeout, &info->start); + if((int)timeout.tv_sec >= info->duration) { + info->exit = 1; + return; + } + } + /* time for stats printout? */ + timeout = now; + perf_tv_subtract(&timeout, &info->since); + if(timeout.tv_sec > 0) { + stat_printout(info, &now, &timeout); + } + /* see what is closest port to timeout; or if there is a timeout */ + timeout = info->io[0].timeout; + for(i=0; i<info->io_num; i++) { + if(perf_tv_smaller(&info->io[i].timeout, &now)) { + perftimeout(info, i, &now); + return; + } + if(perf_tv_smaller(&info->io[i].timeout, &timeout)) { + timeout = info->io[i].timeout; + } + } + perf_tv_subtract(&timeout, &now); + + num = select(info->maxfd+1, &rset, NULL, NULL, &timeout); + if(num == -1) { + if(errno == EAGAIN || errno == EINTR) + return; + log_err("select: %s", strerror(errno)); + } + + /* handle new events */ + for(i=0; num && i<info->io_num; i++) { + if(FD_ISSET(info->io[i].fd, &rset)) { + perfreply(info, i, &now); + num--; + } + } +} + +/** show end stats */ +static void +perfendstats(struct perfinfo* info) +{ + double dt, qps; + struct timeval timeout, now; + int i, lost; + if(gettimeofday(&now, NULL) < 0) + fatal_exit("gettimeofday: %s", strerror(errno)); + timeout = now; + perf_tv_subtract(&timeout, &info->since); + stat_printout(info, &now, &timeout); + + timeout = now; + perf_tv_subtract(&timeout, &info->start); + dt = (double)(timeout.tv_sec*1000000 + timeout.tv_usec) / 1000000.0; + qps = (double)(info->total_recv) / dt; + lost = (int)(info->total_sent - info->total_recv) - (int)info->io_num; + if(!info->quiet) { + printf("overall time: %g sec\n", + (double)timeout.tv_sec + + (double)timeout.tv_usec/1000000.); + if(lost > 0) + printf("Packets lost: %d\n", (int)lost); + + for(i=0; i<(int)(sizeof(info->by_rcode)/sizeof(size_t)); i++) + { + if(info->by_rcode[i] > 0) { + char rc[16]; + sldns_wire2str_rcode_buf(i, rc, sizeof(rc)); + printf("%d(%5s): %u replies\n", + i, rc, (unsigned)info->by_rcode[i]); + } + } + } + printf("average qps: %g\n", qps); +} + +/** perform the performance test */ +static void +perfmain(struct perfinfo* info) +{ + perfsetup(info); + while(!info->exit) { + perfselect(info); + } + perfendstats(info); + perffree(info); +} + +/** parse a query line to a packet into buffer */ +static int +qlist_parse_line(sldns_buffer* buf, char* p) +{ + char nm[1024], cl[1024], tp[1024], fl[1024]; + int r; + int rec = 1, edns = 0; + struct query_info qinfo; + nm[0] = 0; cl[0] = 0; tp[0] = 0; fl[0] = 0; + r = sscanf(p, " %1023s %1023s %1023s %1023s", nm, cl, tp, fl); + if(r != 3 && r != 4) + return 0; + /*printf("nm='%s', cl='%s', tp='%s', fl='%s'\n", nm, cl, tp, fl);*/ + if(strcmp(tp, "IN") == 0 || strcmp(tp, "CH") == 0) { + qinfo.qtype = sldns_get_rr_type_by_name(cl); + qinfo.qclass = sldns_get_rr_class_by_name(tp); + } else { + qinfo.qtype = sldns_get_rr_type_by_name(tp); + qinfo.qclass = sldns_get_rr_class_by_name(cl); + } + if(fl[0] == '+') rec = 1; + else if(fl[0] == '-') rec = 0; + else if(fl[0] == 'E') edns = 1; + if((fl[0] == '+' || fl[0] == '-') && fl[1] == 'E') + edns = 1; + qinfo.qname = sldns_str2wire_dname(nm, &qinfo.qname_len); + if(!qinfo.qname) + return 0; + qinfo_query_encode(buf, &qinfo); + sldns_buffer_write_u16_at(buf, 0, 0); /* zero ID */ + if(rec) LDNS_RD_SET(sldns_buffer_begin(buf)); + if(edns) { + struct edns_data ed; + memset(&ed, 0, sizeof(ed)); + ed.edns_present = 1; + ed.udp_size = EDNS_ADVERTISED_SIZE; + /* Set DO bit in all EDNS datagrams ... */ + ed.bits = EDNS_DO; + attach_edns_record(buf, &ed); + } + free(qinfo.qname); + return 1; +} + +/** grow query list capacity */ +static void +qlist_grow_capacity(struct perfinfo* info) +{ + size_t newcap = (size_t)((info->qlist_capacity==0)?16: + info->qlist_capacity*2); + uint8_t** d = (uint8_t**)calloc(sizeof(uint8_t*), newcap); + size_t* l = (size_t*)calloc(sizeof(size_t), newcap); + if(!d || !l) fatal_exit("out of memory"); + memcpy(d, info->qlist_data, sizeof(uint8_t*)* + info->qlist_capacity); + memcpy(l, info->qlist_len, sizeof(size_t)* + info->qlist_capacity); + free(info->qlist_data); + free(info->qlist_len); + info->qlist_data = d; + info->qlist_len = l; + info->qlist_capacity = newcap; +} + +/** setup query list in info */ +static void +qlist_add_line(struct perfinfo* info, char* line, int no) +{ + if(!qlist_parse_line(info->buf, line)) { + printf("error parsing query %d: %s\n", no, line); + exit(1); + } + sldns_buffer_write_u16_at(info->buf, 0, (uint16_t)info->qlist_size); + if(info->qlist_size + 1 > info->qlist_capacity) { + qlist_grow_capacity(info); + } + info->qlist_len[info->qlist_size] = sldns_buffer_limit(info->buf); + info->qlist_data[info->qlist_size] = memdup( + sldns_buffer_begin(info->buf), sldns_buffer_limit(info->buf)); + if(!info->qlist_data[info->qlist_size]) + fatal_exit("out of memory"); + info->qlist_size ++; +} + +/** setup query list in info */ +static void +qlist_read_file(struct perfinfo* info, char* fname) +{ + char buf[1024]; + char *p; + FILE* in = fopen(fname, "r"); + int lineno = 0; + if(!in) { + perror(fname); + exit(1); + } + while(fgets(buf, (int)sizeof(buf), in)) { + lineno++; + buf[sizeof(buf)-1] = 0; + p = buf; + while(*p == ' ' || *p == '\t') + p++; + if(p[0] == 0 || p[0] == '\n' || p[0] == ';' || p[0] == '#') + continue; + qlist_add_line(info, p, lineno); + } + printf("Read %s, got %u queries\n", fname, (unsigned)info->qlist_size); + fclose(in); +} + +/** getopt global, in case header files fail to declare it. */ +extern int optind; +/** getopt global, in case header files fail to declare it. */ +extern char* optarg; + +/** main program for perf */ +int main(int argc, char* argv[]) +{ + char* nm = argv[0]; + int c; + struct perfinfo info; +#ifdef USE_WINSOCK + int r; + WSADATA wsa_data; +#endif + + /* defaults */ + memset(&info, 0, sizeof(info)); + info.io_num = 16; + + log_init(NULL, 0, NULL); + log_ident_set("perf"); + checklock_start(); +#ifdef USE_WINSOCK + if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) + fatal_exit("WSAStartup failed: %s", wsa_strerror(r)); +#endif + + info.buf = sldns_buffer_new(65553); + if(!info.buf) fatal_exit("out of memory"); + + /* parse the options */ + while( (c=getopt(argc, argv, "d:ha:f:q")) != -1) { + switch(c) { + case 'q': + info.quiet = 1; + break; + case 'd': + if(atoi(optarg)==0 && strcmp(optarg, "0")!=0) { + printf("-d not a number %s", optarg); + return 1; + } + info.duration = atoi(optarg); + break; + case 'a': + qlist_add_line(&info, optarg, 0); + break; + case 'f': + qlist_read_file(&info, optarg); + break; + case '?': + case 'h': + default: + usage(nm); + } + } + argc -= optind; + argv += optind; + + if(argc != 1) { + printf("error: pass server IP address on commandline.\n"); + usage(nm); + } + if(!extstrtoaddr(argv[0], &info.dest, &info.destlen)) { + printf("Could not parse ip: %s\n", argv[0]); + return 1; + } + if(info.qlist_size == 0) { + printf("No queries to make, use -f or -a.\n"); + return 1; + } + + /* do the performance test */ + perfmain(&info); + + sldns_buffer_free(info.buf); +#ifdef USE_WINSOCK + WSACleanup(); +#endif + checklock_stop(); + return 0; +} |