/*
* 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 - 2006 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 74726 2006-09-28 16:52:58Z rsr $
***/
#include "common.h"
/*
* Global stuff
*/
/* The target traffic level at the end of the ramp-up */
double max_qps = 100000.0;
/* The time period over which we ramp up traffic */
double ramp_time = 60.0;
/* How long to wait for responses after sending traffic */
double wait_time = 40.0;
/* When to stop waiting for respnoses (valid during wait phase only) */
double end_time;
/* Interval between plot data points, in seconds */
double bucket_interval = 0.5;
/* The number of plot data points */
int n_buckets;
/* The plot data file */
const char *plotfile = "resperf.gnuplot";
/* The largest acceptable query loss when reporting max throughput */
double max_loss_percent = 100.0;
/*
* 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
*/
int last_bucket_used;
/*
* The statistics for queries sent during one bucket_interval
* of the traffic sending phase.
*/
struct ramp_bucket {
int queries;
int responses;
int failures;
double latency_sum;
};
/* Pointer to array of n_buckets ramp_bucket structures */
struct ramp_bucket *buckets;
/*
* If true, we are in the wait phase, that is, we have stopped sending
* queries and are just waiting for any remaining responses.
*/
isc_boolean_t wait_phase = ISC_FALSE;
/* The time when the wait phase began */
struct timeval wait_phase_began;
/*
* show_startup_info:
* Show name/version
*/
static void
show_startup_info(void) {
printf("\n"
"DNS Resolution Performance Testing Tool\n\n"
"Nominum Version " VERSION "\n\n");
}
/* Print program-specific usage */
void
show_usage_prog(void) {
fprintf(stderr,
" [-i plot_interval] [-m max_qps] [-P plotfile]\n"
" [-r rampup_time] [-L max_loss]\n");
}
/* Print program-specific option descriptions */
void
show_options_prog(void) {
fprintf(stderr,
" -i specifies the time interval between plot data points, "
"in seconds (default: %.1f)\n"
" -m specifies the maximum number of queries per second (default: %.0f)\n"
" -P specifies the name of the plot data file (default: %s)\n"
" -r specifies the ramp-up time in seconds (default: %.0f)\n"
" -L specifies the maximum acceptable query loss, "
"in percent (default: %.0f)\n",
bucket_interval, max_qps, plotfile, ramp_time,
max_loss_percent);
}
/* Handle program-specific options */
isc_result_t
handle_prog_opt(int c) {
isc_result_t result;
switch (c) {
case 'i':
result = parse_udouble(&bucket_interval, optarg);
if (result != ISC_R_SUCCESS) {
fprintf(stderr, "Invalid plot interval "
"(positive floating point value "
"expected): %s\n", optarg);
return (ISC_R_FAILURE);
}
break;
case 'm':
result = parse_udouble(&max_qps, optarg);
if (result != ISC_R_SUCCESS) {
fprintf(stderr, "Invalid max qps "
"(positive floating point value "
"expected): %s\n", optarg);
return (ISC_R_FAILURE);
}
break;
case 'P':
plotfile = optarg;
break;
case 'r':
result = parse_udouble(&ramp_time, optarg);
if (result != ISC_R_SUCCESS) {
fprintf(stderr, "Invalid ramp time "
"(positive floating point value "
"expected): %s\n", optarg);
return (ISC_R_FAILURE);
}
break;
case 'L':
result = parse_udouble(&max_loss_percent, optarg);
if (result != ISC_R_SUCCESS) {
fprintf(stderr, "Invalid max query loss "
"(positive floating point value "
"expected): %s\n", optarg);
return (ISC_R_FAILURE);
}
break;
default:
return (ISC_R_NOTFOUND);
}
return (ISC_R_SUCCESS);
}
/* Find the struct ramp_bucket for queries sent at time "when" */
static struct ramp_bucket *
find_bucket(struct timeval *when) {
double sent_at = difftv(when, &time_of_program_start);
int i = (int) floor(n_buckets * sent_at / ramp_time);
/*
* 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];
}
/*
* register_response:
* Register receipt of a query
*
* Removes the record for the given query id in status[] if any exists.
*
* Returns ISC_TRUE if the response was for an outstanding query
* Returns ISC_FALSE otherwise.
*/
isc_boolean_t
register_response(unsigned int id, unsigned int rcode) {
struct query_status *s = &status[id];
if (s->list != &outstanding_list) {
fprintf(stderr, "Warning: Received a response with an "
"unexpected id: %u\n", id);
return (ISC_FALSE);
}
ISC_LIST_UNLINK(outstanding_list, s, link);
ISC_LIST_APPEND(instanding_list, s, link);
s->list = &instanding_list;
num_queries_outstanding--;
if (s->desc != NULL) {
printf("> %s %s\n", rcode_strings[rcode], s->desc);
free(s->desc);
s->desc = NULL;
}
{
double latency = difftv(&time_now, &s->sent_timestamp);
struct ramp_bucket *b = find_bucket(&s->sent_timestamp);
b->responses++;
if (!(rcode == dns_rcode_noerror ||
rcode == dns_rcode_nxdomain))
b->failures++;
b->latency_sum += latency;
}
return (ISC_TRUE);
}
/*
* 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;
double run_time = difftv(&time_of_end_of_run, &time_of_program_start);
printf("\nStatistics:\n\n");
printf(" Queries sent: %u\n", num_queries_sent);
printf(" Queries completed: %u\n",
num_queries_sent - num_queries_outstanding);
printf(" Queries lost: %u\n", num_queries_outstanding);
printf(" Ran for: %.6lf seconds\n", run_time);
/* 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++) {
struct ramp_bucket *b = &buckets[i];
double responses_per_sec =
(double) b->responses / bucket_interval;
double loss = b->queries ?
(b->queries - b->responses) / (double) b->queries : 0.0;
double loss_percent = loss * 100.0;
if (loss_percent > max_loss_percent)
break;
if (responses_per_sec > max_throughput) {
max_throughput = responses_per_sec;
loss_at_max_throughput = loss_percent;
}
}
printf(" Maximum throughput: %.6lf qps\n", max_throughput);
printf(" Lost at that point: %.2f%%\n", loss_at_max_throughput);
}
static struct ramp_bucket *
init_buckets(int n) {
struct ramp_bucket *p = malloc(n * sizeof(*p));
int i;
for (i = 0; i < n; i++) {
p[i].queries = p[i].responses = p[i].failures = 0;
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
do_one_line() {
char line[MAX_INPUT_LEN + 1];
if (get_input_line(line, sizeof(line)) != ISC_R_SUCCESS) {
fprintf(stderr, "ran out of query data\n");
exit(1);
}
return process_line(line);
}
static void
enter_wait_phase() {
wait_phase = ISC_TRUE;
printf("[Status] Waiting for more responses\n");
wait_phase_began = time_now;
end_time = difftv(&time_now, &time_of_program_start) + wait_time;
}
int
main(int argc, char **argv) {
int i;
FILE *plotf;
show_startup_info();
if (setup(argc, argv, "i:m:P:r:L:") != ISC_R_SUCCESS)
exit(1);
n_buckets = ceil(ramp_time / bucket_interval);
buckets = init_buckets(n_buckets);
update_current_time();
time_of_program_start = time_now;
printf("[Status] Sending\n");
for (;;) {
int scheduled_n, should_send;
double time_since_start =
difftv(&time_now, &time_of_program_start);
if (wait_phase) {
if (time_since_start >= end_time)
break;
} else {
if (time_since_start >= ramp_time)
enter_wait_phase();
}
if (! wait_phase) {
/* How many queries should we have sent by now? */
scheduled_n = 0.5 * max_qps *
time_since_start * time_since_start / ramp_time;
should_send = scheduled_n - num_queries_sent;
if (should_send >= 1000) {
printf("[Status] Fell behind by %d queries, "
"ending test at %.0f qps\n",
should_send,
max_qps * time_since_start / ramp_time);
enter_wait_phase();
}
if (should_send > 0) {
isc_result_t result = do_one_line();
if (result == ISC_R_SUCCESS)
find_bucket(&time_now)->queries++;
if (result == ISC_R_NOMORE) {
printf("[Status] Reached 65536 outstanding queries\n");
enter_wait_phase();
}
}
}
(void) try_process_response(query_socket);
update_current_time();
}
update_current_time();
time_of_end_of_run = time_now;
printf("[Status] Testing complete\n");
maybe_print_args(argc, argv);
plotf = fopen(plotfile, "w");
if (! plotf) {
fprintf(stderr, "could not open %s: %s", plotfile,
strerror(errno));
exit(1);
}
/* Print column headers */
fprintf(plotf, "# time target_qps actual_qps "
"responses_per_sec failures_per_sec "
"avg_latency\n");
/* Don't print unused buckets */
last_bucket_used = find_bucket(&wait_phase_began) - buckets;
/* Don't print a partial bucket at the end */
if (last_bucket_used > 0)
--last_bucket_used;
for (i = 0; i <= last_bucket_used; i++) {
double bucket_qps = (i + 0.5) * max_qps / n_buckets;
double latency = buckets[i].responses ?
buckets[i].latency_sum / buckets[i].responses : 0;
fprintf(plotf, "%7.3f %8.2f %8.2f %8.2f %8.2f %8.6f\n",
(i + 0.5) * ramp_time / n_buckets,
bucket_qps,
(double) buckets[i].queries / bucket_interval,
(double) buckets[i].responses / bucket_interval,
(double) buckets[i].failures / bucket_interval,
latency);
}
fclose(plotf);
print_statistics();
cleanup();
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1