text: /*
   * 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 "sldns/sbuffer.h"
  #include "sldns/wire2str.h"
  #include "sldns/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.local_alias = NULL;
  	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;
  }
  