/*
* Copyright (C) 2000, 2001 Nominum, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
* INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Copyright (C) 2004 - 2015 Nominum, Inc.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose with or without fee is hereby granted,
* provided that the above copyright notice and this permission notice
* appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/***
*** DNS Resolution Performance Testing Tool
***
*** Version $Id: resperf.c 263304 2015-12-15 01:14:10Z bwelling $
***/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <isc/sockaddr.h>
#include "datafile.h"
#include "dns.h"
#include "log.h"
#include "net.h"
#include "opt.h"
#include "util.h"
#include "version.h"
/*
* Global stuff
*/
#define DEFAULT_LOCAL_PORT 0
struct query_info;
typedef struct query_info {
/*
* This link links the query into the list of outstanding
* queries or the list of available query IDs.
*/
/*
* The list this query is on.
*/
} query_info;
static unsigned int nsocks;
static int *socks;
/* The target traffic level at the end of the ramp-up */
/* The time period over which we ramp up traffic */
/* How long to send constant traffic after the initial ramp-up */
#define DEFAULT_SUSTAIN_TIME 0
/* How long to wait for responses after sending traffic */
/* Total duration of the traffic-sending part of the test */
/* Total duration of the test */
/* Interval between plot data points, in microseconds */
/* The number of plot data points */
static int n_buckets;
/* The plot data file */
/* The largest acceptable query loss when reporting max throughput */
/* The maximum number of outstanding queries */
static unsigned int max_outstanding;
/*
* The last plot data point containing actual data; this can
* be less than than (n_buckets - 1) if the traffic sending
* phase is cut short
*/
static int last_bucket_used;
/*
* The statistics for queries sent during one bucket_interval
* of the traffic sending phase.
*/
typedef struct {
int queries;
int responses;
int failures;
double latency_sum;
} ramp_bucket;
/* Pointer to array of n_buckets ramp_bucket structures */
enum phase {
/*
* The ramp-up phase: we are steadily increasing traffic.
*/
/*
* The sustain phase: we are sending traffic at a constant
* rate.
*/
/*
* The wait phase: we have stopped sending queries and are
* just waiting for any remaining responses.
*/
};
static char *
{
return buf;
}
static void
{
int sock_family;
unsigned int bufsize;
unsigned int i;
if (result != ISC_R_SUCCESS)
perf_log_fatal("creating memory context: %s",
nsocks = 1;
"address family of DNS transport, inet or inet6", "any",
&family);
"the port on which to query the server",
"the local address from which to send queries", NULL,
&local_name);
"the local port from which to send queries",
"the timeout for query completion in seconds",
&bufsize);
"the TSIG algorithm, name and secret", NULL,
&tsigkey_str);
"the time interval between plot data points, in seconds",
"the maximum number of queries per second",
"the ramp-up time in seconds",
"how long to send constant traffic, in seconds",
"the maximum acceptable query loss, in percent",
"the number of clients to act as", NULL,
&nsocks);
"the maximum number of queries outstanding",
perf_log_fatal("number of outstanding packets (%u) must not "
"be more than 64K per client", max_outstanding);
if (ramp_time + sustain_time == 0)
perf_log_fatal("rampup_time and constant_traffic_time must not "
"both be 0");
perf_log_fatal("out of memory");
for (i = 0; i < max_outstanding; i++) {
}
&server_addr);
if (dnssec)
if (tsigkey_str != NULL)
perf_log_fatal("out of memory");
for (i = 0; i < nsocks; i++)
bufsize);
}
static void
cleanup(void)
{
unsigned int i;
for (i = 0; i < nsocks; i++)
}
/* Find the ramp_bucket for queries sent at time "when" */
static ramp_bucket *
/*
* Guard against array bounds violations due to roundoff
* errors or scheduling jitter
*/
if (i < 0)
i = 0;
if (i > n_buckets - 1)
i = n_buckets - 1;
return &buckets[i];
}
/*
* print_statistics:
* Print out statistics based on the results of the test
*/
static void
print_statistics(void) {
int i;
double max_throughput;
double loss_at_max_throughput;
printf("\nStatistics:\n\n");
printf(" Response codes: ");
for (i = 0; i < 16; i++) {
if (rcodecounts[i] == 0)
continue;
if (first_rcode)
else
printf(", ");
perf_dns_rcode_strings[i], rcodecounts[i],
}
printf("\n");
printf(" Run time (s): %u.%06u\n",
/* Find the maximum throughput, subject to the -L option */
max_throughput = 0.0;
loss_at_max_throughput = 0.0;
for (i = 0; i <= last_bucket_used; i++) {
ramp_bucket *b = &buckets[i];
double responses_per_sec =
if (loss_percent > max_loss_percent)
break;
if (responses_per_sec > max_throughput) {
}
}
}
static ramp_bucket *
init_buckets(int n) {
ramp_bucket *p;
int i;
p = isc_mem_get(mctx, n * sizeof(*p));
if (p == NULL)
perf_log_fatal("out of memory");
for (i = 0; i < n; i++) {
p[i].latency_sum = 0.0;
}
return p;
}
/*
* Send a query based on a line of input.
* Return ISC_R_NOMORE if we ran out of query IDs.
*/
static isc_result_t
query_info *q;
unsigned int qid;
unsigned int sock;
unsigned char *base;
unsigned int length;
if (result != ISC_R_SUCCESS)
perf_log_fatal("ran out of query data");
q = ISC_LIST_HEAD(instanding_list);
if (! q)
return (ISC_R_NOMORE);
if (result != ISC_R_SUCCESS)
return (result);
q->sent_timestamp = time_now;
{
perf_log_warning("failed to send packet: %s",
return (ISC_R_FAILURE);
}
q->list = &outstanding_list;
return ISC_R_SUCCESS;
}
static void
enter_sustain_phase(void) {
if (sustain_time != 0.0)
printf("[Status] Ramp-up done, sending constant traffic\n");
}
static void
enter_wait_phase(void) {
phase = PHASE_WAIT;
printf("[Status] Waiting for more responses\n");
}
/*
* try_process_response:
*
* Receive from the given socket & process an individual response packet.
* Remove it from the list of open queries (status[]) and decrement the
* number of outstanding queries if it matches an open query.
*/
static void
query_info *q;
double latency;
ramp_bucket *b;
int n;
if (n < 0) {
return;
} else {
perf_log_fatal("failed to receive packet: %s",
}
} else if (n < 4) {
perf_log_warning("received short response");
return;
}
if (q->list != &outstanding_list) {
perf_log_warning("received a response with an "
"unexpected id: %u", qid);
return;
}
q->list = &instanding_list;
b = find_bucket(q->sent_timestamp);
b->responses++;
b->failures++;
b->latency_sum += latency;
rcodecounts[rcode]++;
}
static void
retire_old_queries(void)
{
query_info *q;
while (ISC_TRUE) {
if (q == NULL ||
break;
q->list = &instanding_list;
}
}
static inline int
{
if (phase == PHASE_RAMP) {
return 0.5 * max_qps *
(double)time_since_start * time_since_start /
} else { /* PHASE_SUSTAIN */
max_qps *
}
}
int
int i;
unsigned int max_packet_size;
unsigned int current_sock;
printf("DNS Resolution Performance Testing Tool\n"
for (i = 1; i < argc; i++) {
}
printf("\n");
printf("[Status] Sending\n");
current_sock = 0;
for (;;) {
int should_send;
switch (phase) {
case PHASE_RAMP:
if (time_since_start >= ramp_time)
break;
case PHASE_SUSTAIN:
if (time_since_start >= traffic_time)
break;
case PHASE_WAIT:
if (time_since_start >= end_time ||
goto end_loop;
break;
}
if (phase != PHASE_WAIT) {
if (should_send >= 1000) {
printf("[Status] Fell behind by %d queries, "
"ending test at %.0f qps\n",
(max_qps * time_since_start) /
}
if (should_send > 0) {
if (result == ISC_R_SUCCESS)
if (result == ISC_R_NOMORE) {
printf("[Status] Reached %u outstanding queries\n",
}
}
}
}
printf("[Status] Testing complete\n");
if (! plotf) {
}
/* Print column headers */
"responses_per_sec failures_per_sec "
"avg_latency\n");
/* Don't print unused buckets */
/* Don't print a partial bucket at the end */
if (last_bucket_used > 0)
for (i = 0; i <= last_bucket_used; i++) {
double t = (i + 0.5) * traffic_time /
double target_qps =
t,
latency);
}
cleanup();
return 0;
}