/* * 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; }