#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>
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;
struct perfinfo {
volatile int exit;
sldns_buffer* buf;
struct sockaddr_storage dest;
socklen_t destlen;
struct timeval since;
size_t numrecv;
size_t numsent;
int duration;
int quiet;
struct timeval start;
size_t total_recv;
size_t total_sent;
size_t by_rcode[32];
size_t io_num;
struct perfio* io;
int maxfd;
fd_set rset;
size_t qlist_size;
size_t qlist_capacity;
uint8_t** qlist_data;
size_t* qlist_len;
size_t qlist_idx;
};
struct perfio {
size_t id;
int fd;
struct timeval timeout;
struct perfinfo* info;
};
#define START_IO_INTERVAL 10
#define IO_TIMEOUT 10
static struct perfinfo* sig_info;
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;
}
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;
}
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
}
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
}
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
}
}
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);
}
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);
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);
}
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++;
}
perfsend(info, n, now);
}
static void
perftimeout(struct perfinfo* info, size_t n, struct timeval* now)
{
perfsend(info, n, now);
}
static void
stat_printout(struct perfinfo* info, struct timeval* now,
struct timeval* elapsed)
{
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);
info->since = *now;
info->total_sent += info->numsent;
info->total_recv += info->numrecv;
info->numrecv = 0;
info->numsent = 0;
}
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));
if(info->duration > 0) {
timeout = now;
perf_tv_subtract(&timeout, &info->start);
if((int)timeout.tv_sec >= info->duration) {
info->exit = 1;
return;
}
}
timeout = now;
perf_tv_subtract(&timeout, &info->since);
if(timeout.tv_sec > 0) {
stat_printout(info, &now, &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));
}
for(i=0; num && i<info->io_num; i++) {
if(FD_ISSET(info->io[i].fd, &rset)) {
perfreply(info, i, &now);
num--;
}
}
}
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);
}
static void
perfmain(struct perfinfo* info)
{
perfsetup(info);
while(!info->exit) {
perfselect(info);
}
perfendstats(info);
perffree(info);
}
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;
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);
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;
ed.bits = EDNS_DO;
attach_edns_record(buf, &ed);
}
free(qinfo.qname);
return 1;
}
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;
}
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 ++;
}
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);
}
extern int optind;
extern char* optarg;
int main(int argc, char* argv[])
{
char* nm = argv[0];
int c;
struct perfinfo info;
#ifdef USE_WINSOCK
int r;
WSADATA wsa_data;
#endif
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");
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;
}
perfmain(&info);
sldns_buffer_free(info.buf);
#ifdef USE_WINSOCK
WSACleanup();
#endif
checklock_stop();
return 0;
}