]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
1919. [contrib] queryperf: a set of new features: collecting/printing
authorTatuya JINMEI 神明達哉 <jinmei@isc.org>
Sat, 29 Oct 2005 00:21:12 +0000 (00:21 +0000)
committerTatuya JINMEI 神明達哉 <jinmei@isc.org>
Sat, 29 Oct 2005 00:21:12 +0000 (00:21 +0000)
response delays, printing intermediate results, and
adjusting query rate for the "target" qps.

(pull down to 9.4)

CHANGES
contrib/queryperf/queryperf.c

diff --git a/CHANGES b/CHANGES
index 0edf4e6d9de38d9b1e0ef9dff2f4bdd3ed775834..e6e6a7f50dade026b4edb58ef8e551394f7f6ae9 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,6 @@
+1919.  [contrib]       queryperf: a set of new features: collecting/printing
+                       response delays, printing intermediate results, and
+                       adjusting query rate for the "target" qps.
 
        --- 9.4.0a2 released ---
 
index b6371797183976fcb685a9905d5421b72b7e6da0..376409cee2aca8ddd307c4e70ffe23494bc4edc1 100644 (file)
@@ -18,7 +18,7 @@
 /***
  ***   DNS Query Performance Testing Tool  (queryperf.c)
  ***
- ***   Version $Id: queryperf.c,v 1.8.192.2 2004/06/21 00:46:17 marka Exp $
+ ***   Version $Id: queryperf.c,v 1.8.192.3 2005/10/29 00:21:12 jinmei Exp $
  ***
  ***   Stephen Jacob <sj@nominum.com>
  ***/
@@ -53,6 +53,9 @@
 #define DEF_SERVER_PORT                        "53"
 #define DEF_BUFFER_SIZE                        32              /* in k */
 
+#define DEF_RTTARRAY_SIZE              50000
+#define DEF_RTTARRAY_UNIT              100             /* in usec */
+
 /*
  * Other constants / definitions
  */
@@ -131,6 +134,9 @@ struct addrinfo *server_ai;                         /* init NULL */
 int run_only_once = FALSE;
 int use_timelimit = FALSE;
 unsigned int run_timelimit;                            /* init 0 */
+unsigned int print_interval;                           /* init 0 */
+
+unsigned int target_qps;                               /* init 0 */
 
 int serverset = FALSE, portset = FALSE;
 int queriesset = FALSE, timeoutset = FALSE;
@@ -150,12 +156,37 @@ FILE *datafile_ptr;                                       /* init NULL */
 unsigned int runs_through_file;                                /* init 0 */
 
 unsigned int num_queries_sent;                         /* init 0 */
+unsigned int num_queries_sent_interval;
 unsigned int num_queries_outstanding;                  /* init 0 */
 unsigned int num_queries_timed_out;                    /* init 0 */
+unsigned int num_queries_possiblydelayed;              /* init 0 */
+unsigned int num_queries_timed_out_interval;
+unsigned int num_queries_possiblydelayed_interval;
 
 struct timeval time_of_program_start;
 struct timeval time_of_first_query;
+double time_of_first_query_sec;
+struct timeval time_of_first_query_interval;
 struct timeval time_of_end_of_run;
+struct timeval time_of_stop_sending;
+
+struct timeval time_of_queryset_start;
+double query_interval;
+struct timeval time_of_next_queryset;
+
+double rtt_max = -1;
+double rtt_max_interval = -1;
+double rtt_min = -1;
+double rtt_min_interval = -1;
+double rtt_total;
+double rtt_total_interval;
+int rttarray_size = DEF_RTTARRAY_SIZE;
+int rttarray_unit = DEF_RTTARRAY_UNIT;
+unsigned int *rttarray = NULL;
+unsigned int *rttarray_interval = NULL;
+unsigned int rtt_overflows;
+unsigned int rtt_overflows_interval;
+char *rtt_histogram_file = NULL;
 
 struct query_status *status;                           /* init NULL */
 unsigned int query_status_allocated;                   /* init 0 */
@@ -186,7 +217,7 @@ void
 show_startup_info(void) {
        printf("\n"
 "DNS Query Performance Testing Tool\n"
-"Version: $Id: queryperf.c,v 1.8.192.2 2004/06/21 00:46:17 marka Exp $\n"
+"Version: $Id: queryperf.c,v 1.8.192.3 2005/10/29 00:21:12 jinmei Exp $\n"
 "\n");
 }
 
@@ -200,7 +231,8 @@ show_usage(void) {
 "\n"
 "Usage: queryperf [-d datafile] [-s server_addr] [-p port] [-q num_queries]\n"
 "                 [-b bufsize] [-t timeout] [-n] [-l limit] [-f family] [-1]\n"
-"                 [-e] [-D] [-c] [-v] [-h]\n"
+"                 [-i interval] [-r arraysize] [-u unit] [-H histfile]\n"
+"                 [-T qps] [-e] [-D] [-c] [-v] [-h]\n"
 "  -d specifies the input data file (default: stdin)\n"
 "  -s sets the server to query (default: %s)\n"
 "  -p sets the port on which to query the server (default: %s)\n"
@@ -210,7 +242,12 @@ show_usage(void) {
 "  -l specifies how a limit for how long to run tests in seconds (no default)\n"
 "  -1 run through input only once (default: multiple iff limit given)\n"
 "  -b set input/output buffer size in kilobytes (default: %d k)\n"
+"  -i specifies interval of intermediate outputs in seconds (default: 0=none)\n"
 "  -f specify address family of DNS transport, inet or inet6 (default: any)\n"
+"  -r set RTT statistics array size (default: %d)\n"
+"  -u set RTT statistics time unit in usec (default: %d)\n"
+"  -H specifies RTT histogram data file (default: none)\n"
+"  -T specify the target qps (default: 0=unspecified)\n"
 "  -e enable EDNS 0\n"
 "  -D set the DNSSEC OK bit (implies EDNS)\n"
 "  -c print the number of packets with each rcode\n"
@@ -219,7 +256,7 @@ show_usage(void) {
 "\n",
                DEF_SERVER_TO_QUERY, DEF_SERVER_PORT,
                DEF_MAX_QUERIES_OUTSTANDING, DEF_QUERY_TIMEOUT,
-               DEF_BUFFER_SIZE);
+               DEF_BUFFER_SIZE, DEF_RTTARRAY_SIZE, DEF_RTTARRAY_UNIT);
 }
 
 /*
@@ -473,7 +510,8 @@ parse_args(int argc, char **argv) {
        int c;
        unsigned int uint_arg_val;
 
-       while ((c = getopt(argc, argv, "f:q:t:nd:s:p:1l:b:eDcvh")) != -1) {
+       while ((c = getopt(argc, argv,
+                          "f:q:t:i:nd:s:p:1l:b:eDcvr:T::u:H:h")) != -1) {
                switch (c) {
                case 'f':
                        if (strcmp(optarg, "inet") == 0)
@@ -534,7 +572,7 @@ parse_args(int argc, char **argv) {
                        }
                        serverset = TRUE;
                        break;
-                       
+
                case 'p':
                        if (is_uint(optarg, &uint_arg_val) == TRUE &&
                            uint_arg_val < MAX_PORT)
@@ -588,6 +626,45 @@ parse_args(int argc, char **argv) {
                case 'v':
                        verbose = 1;
                        break;
+               case 'i':
+                       if (is_uint(optarg, &uint_arg_val) == TRUE)
+                               print_interval = uint_arg_val;
+                       else {
+                               fprintf(stderr, "Invalid interval: %s\n",
+                                       optarg);
+                               return (-1);
+                       }
+                       break;
+               case 'r':
+                       if (is_uint(optarg, &uint_arg_val) == TRUE)
+                               rttarray_size = uint_arg_val;
+                       else {
+                               fprintf(stderr, "Invalid RTT array size: %s\n",
+                                       optarg);
+                               return (-1);
+                       }
+                       break;
+               case 'u':
+                       if (is_uint(optarg, &uint_arg_val) == TRUE)
+                               rttarray_unit = uint_arg_val;
+                       else {
+                               fprintf(stderr, "Invalid RTT unit: %s\n",
+                                       optarg);
+                               return (-1);
+                       }
+                       break;
+               case 'H':
+                       rtt_histogram_file = optarg;
+                       break;
+               case 'T':
+                       if (is_uint(optarg, &uint_arg_val) == TRUE)
+                               target_qps = uint_arg_val;
+                       else {
+                               fprintf(stderr, "Invalid target qps: %s\n",
+                                       optarg);
+                               return (-1);
+                       }
+                       break;
                case 'h':
                        return (-1);
                default:
@@ -780,6 +857,65 @@ change_socket(void) {
        return (*sockp);
 }
 
+/*
+ * reset_rttarray:
+ *   (re)allocate RTT array and zero-clear the whole buffer.
+ *   if array is being used, it is freed.
+ *   Returns -1 on failure
+ *   Returns a non-negative integer otherwise
+ */
+int
+reset_rttarray(int size) {
+       if (rttarray != NULL)
+               free(rttarray);
+       if (rttarray_interval != NULL)
+               free(rttarray_interval);
+
+       rttarray = NULL;
+       rttarray_interval = NULL;
+       rtt_max = -1;
+       rtt_min = -1;
+
+       if (size > 0) {
+               rttarray = malloc(size * sizeof(rttarray[0]));
+               if (rttarray == NULL) {
+                       fprintf(stderr,
+                               "Error: allocating memory for RTT array\n");
+                       return (-1);
+               }
+               memset(rttarray, 0, size * sizeof(rttarray[0]));
+
+               rttarray_interval = malloc(size *
+                                          sizeof(rttarray_interval[0]));
+               if (rttarray_interval == NULL) {
+                       fprintf(stderr,
+                               "Error: allocating memory for RTT array\n");
+                       return (-1);
+               }
+
+               memset(rttarray_interval, 0,
+                      size * sizeof(rttarray_interval[0]));
+       }
+
+       return (0);
+}
+
+/*
+ * set_query_interval:
+ *   set the interval of consecutive queries if the target qps are specified.
+ *   Returns -1 on failure
+ *   Returns a non-negative integer otherwise
+ */
+int
+set_query_interval(unsigned int qps) {
+       if (qps == 0)
+               return (0);
+
+       query_interval = (1.0 / (double)qps);
+
+       return (0);
+}
+
 /*
  * setup:
  *   Set configuration options from command line arguments
@@ -824,6 +960,12 @@ setup(int argc, char **argv) {
        if ((query_socket = change_socket()) == -1)
                return (-1);
 
+       if (reset_rttarray(rttarray_size) == -1)
+               return (-1);
+
+       if (set_query_interval(target_qps) == -1)
+               return (-1);
+
        return (0);
 }
 
@@ -841,6 +983,20 @@ set_timenow(struct timeval *tv) {
        }
 }
 
+/*
+ * addtv:
+ *   add tv1 and tv2, store the result in tv_result.
+ */
+void
+addtv(struct timeval *tv1, struct timeval *tv2, struct timeval *tv_result) {
+       tv_result->tv_sec = tv1->tv_sec + tv2->tv_sec;
+       tv_result->tv_usec = tv1->tv_usec + tv2->tv_usec;
+       if (tv_result->tv_usec > 1000000) {
+               tv_result->tv_sec++;
+               tv_result->tv_usec -= 1000000;
+       }
+}
+
 /*
  * difftv:
  *   Find the difference in seconds between two timeval structs.
@@ -921,6 +1077,7 @@ keep_sending(int *reached_end_input) {
        } else {
                if (*reached_end_input == TRUE)
                        runs_through_file++;
+               set_timenow(&time_of_stop_sending);
                stop = TRUE;
                return (FALSE);
        }
@@ -1299,6 +1456,8 @@ send_query(char *query_desc) {
 
        if (setup_phase == TRUE) {
                set_timenow(&time_of_first_query);
+               time_of_first_query_sec = (double)time_of_first_query.tv_sec +
+                       ((double)time_of_first_query.tv_usec / 1000000.0);
                setup_phase = FALSE;
                if (getnameinfo(server_ai->ai_addr, server_ai->ai_addrlen,
                                serveraddr, sizeof(serveraddr), NULL, 0,
@@ -1311,12 +1470,12 @@ send_query(char *query_desc) {
        }
 
        /* Find the first slot in status[] that is not in use */
-       for(count = 0; (status[count].in_use == TRUE)
-           && (count < max_queries_outstanding); count++);
+       for (count = 0; (status[count].in_use == TRUE)
+            && (count < max_queries_outstanding); count++);
 
        if (status[count].in_use == TRUE) {
                fprintf(stderr, "Unexpected error: We have run out of "
-                       "status[] space!\n");
+                       "status[] space!\n");
                return;
        }
 
@@ -1327,10 +1486,62 @@ send_query(char *query_desc) {
                status[count].desc = strdup(query_desc);
        set_timenow(&status[count].sent_timestamp);
 
+       if (num_queries_sent_interval == 0)
+               set_timenow(&time_of_first_query_interval);
+
        num_queries_sent++;
+       num_queries_sent_interval++;
        num_queries_outstanding++;
 }
 
+void
+register_rtt(struct timeval *timestamp) {
+       int i;
+       int oldquery = FALSE;
+       struct timeval now;
+       double rtt;
+
+       set_timenow(&now);
+       rtt = difftv(now, *timestamp);
+
+       if (difftv(*timestamp, time_of_first_query_interval) < 0)
+               oldquery = TRUE;
+
+       if (rtt_max < 0 || rtt_max < rtt)
+               rtt_max = rtt;
+
+       if (rtt_min < 0 || rtt_min > rtt)
+               rtt_min = rtt;
+
+       rtt_total += rtt;
+
+       if (!oldquery) {
+               if (rtt_max_interval < 0 || rtt_max_interval < rtt)
+                       rtt_max_interval = rtt;
+
+               if (rtt_min_interval < 0 || rtt_min_interval > rtt)
+                       rtt_min_interval = rtt;
+
+               rtt_total_interval += rtt;
+       }
+
+       if (rttarray == NULL)
+               return;
+
+       i = (int)(rtt * (1000000.0 / rttarray_unit));
+       if (i < rttarray_size) {
+               rttarray[i]++;
+               if (!oldquery)
+                       rttarray_interval[i]++;
+       } else {
+               fprintf(stderr, "Warning: RTT is out of range: %.6lf\n",
+                       rtt);
+               rtt_overflows++;
+               if (!oldquery)
+                       rtt_overflows_interval++;
+       }
+}
+
 /*
  * register_response:
  *   Register receipt of a query
@@ -1342,12 +1553,17 @@ void
 register_response(unsigned short int id, unsigned int rcode) {
        unsigned int ct = 0;
        int found = FALSE;
+       struct timeval now;
+       double rtt;
 
-       for(; (ct < query_status_allocated) && (found == FALSE); ct++) {
+       for (; (ct < query_status_allocated) && (found == FALSE); ct++) {
                if ((status[ct].in_use == TRUE) && (status[ct].id == id)) {
                        status[ct].in_use = FALSE;
                        num_queries_outstanding--;
                        found = TRUE;
+
+                       register_rtt(&status[ct].sent_timestamp);
+
                        if (status[ct].desc) {
                                printf("> %s %s\n", rcode_strings[rcode],
                                       status[ct].desc);
@@ -1358,9 +1574,15 @@ register_response(unsigned short int id, unsigned int rcode) {
                }
        }
 
-       if (found == FALSE)
-               fprintf(stderr, "Warning: Received a response with an "
-                       "unexpected (maybe timed out) id: %u\n", id);
+       if (found == FALSE) {
+               if (target_qps > 0) {
+                       num_queries_possiblydelayed++;
+                       num_queries_possiblydelayed_interval++;
+               } else {
+                       fprintf(stderr, "Warning: Received a response with an "
+                               "unexpected (maybe timed out) id: %u\n", id);
+               }
+       }
 }
 
 /*
@@ -1449,21 +1671,57 @@ data_available(double wait) {
  *   decrementing the number of outstanding queries.
  */
 void
-process_responses(void) {
+process_responses(int adjust_rate) {
+       double wait;
+       struct timeval now, waituntil;
        double first_packet_wait = RESPONSE_BLOCKING_WAIT_TIME;
        unsigned int outstanding = queries_outstanding();
 
-       /*
-        * Don't block waiting for packets at all if we aren't looking for
-        * any responses or if we are now able to send new queries.
-        */
-       if ((outstanding == 0) || (outstanding < max_queries_outstanding)) {
-               first_packet_wait = 0.0;
-       }
+       if (adjust_rate == TRUE) {
+               double u;
+
+               u = time_of_first_query_sec +
+                       query_interval * num_queries_sent;
+               waituntil.tv_sec = (long)floor(u);
+               waituntil.tv_usec = (long)(1000000.0 * (u - waituntil.tv_sec));
+
+               /*
+                * Wait until a response arrives or the specified limit is
+                * reached.
+                */
+               while (1) {
+                       set_timenow(&now);
+                       wait = difftv(waituntil, now);
+                       if (wait <= 0)
+                               wait = 0.0;
+                       if (data_available(wait) != TRUE)
+                               break;
 
-       if (data_available(first_packet_wait) == TRUE) {
-               while (data_available(0.0) == TRUE)
-                       ;
+                       /*
+                        * We have reached the limit.  Read as many responses
+                        * as possible without waiting, and exit.
+                        */
+                       if (wait == 0) {
+                               while (data_available(0.0) == TRUE)
+                                       ;
+                               break;
+                       }
+               }
+       } else {
+               /*
+                * Don't block waiting for packets at all if we aren't
+                * looking for any responses or if we are now able to send new
+                * queries.
+                */
+               if ((outstanding == 0) ||
+                   (outstanding < max_queries_outstanding)) {
+                       first_packet_wait = 0.0;
+               }
+
+               if (data_available(first_packet_wait) == TRUE) {
+                       while (data_available(0.0) == TRUE)
+                               ;
+               }
        }
 }
 
@@ -1474,90 +1732,207 @@ process_responses(void) {
  *   the number of queries outstanding for each one removed.
  */
 void
-retire_old_queries(void) {
+retire_old_queries(int sending) {
        unsigned int count = 0;
        struct timeval curr_time;
+       double timeout = query_timeout;
+       int timeout_reduced = FALSE;
+
+       /*
+        * If we have target qps and would not be able to send any packets
+        * due to buffer full, check whether we are behind the schedule.
+        * If we are, purge some queries more aggressively.
+        */
+       if (target_qps > 0 && sending == TRUE && count == 0 &&
+           queries_outstanding() == max_queries_outstanding) {
+               struct timeval next, now;
+               double n;
+
+               n = time_of_first_query_sec +
+                       query_interval * num_queries_sent;
+               next.tv_sec = (long)floor(n);
+               next.tv_usec = (long)(1000000.0 * (n - next.tv_sec));
+
+               set_timenow(&now);
+               if (difftv(next, now) <= 0) {
+                       timeout_reduced = TRUE;
+                       timeout = 0.001; /* XXX: ad-hoc value */
+               }
+       }
 
        set_timenow(&curr_time);
 
-       for(; count < query_status_allocated; count++) {
+       for (; count < query_status_allocated; count++) {
 
                if ((status[count].in_use == TRUE)
                    && (difftv(curr_time, status[count].sent_timestamp)
-                   >= (double)query_timeout)) {
+                   >= (double)timeout)) {
 
                        status[count].in_use = FALSE;
                        num_queries_outstanding--;
                        num_queries_timed_out++;
-
-                       if (status[count].desc) {
-                               printf("> T %s\n", status[count].desc);
-                               free(status[count].desc);
-                       } else {
-                               printf("[Timeout] Query timed out: msg id %u\n",
-                                      status[count].id);
+                       num_queries_timed_out_interval++;
+
+                       if (timeout_reduced == FALSE) {
+                               if (status[count].desc) {
+                                       printf("> T %s\n", status[count].desc);
+                                       free(status[count].desc);
+                               } else {
+                                       printf("[Timeout] Query timed out: "
+                                              "msg id %u\n",
+                                              status[count].id);
+                               }
                        }
                }
        }
 }
 
+/*
+ * print_histogram
+ *   Print RTT histogram to the specified file in the gnuplot format
+ */
+void
+print_histogram(unsigned int total) {
+       int i;
+       double ratio;
+       FILE *fp;
+
+       if (rtt_histogram_file == NULL || rttarray == NULL)
+               return;
+
+       fp = fopen((const char *)rtt_histogram_file, "w+");
+       if (fp == NULL) {
+               fprintf(stderr, "Error opening RTT histogram file: %s\n",
+                       rtt_histogram_file);
+               return;
+       }
+
+       for (i = 0; i < rttarray_size; i++) {
+               ratio = ((double)rttarray[i] / (double)total) * 100;
+               fprintf(fp, "%.6lf %.3lf\n",
+                       (double)(i * rttarray_unit) +
+                       (double)rttarray_unit / 2,
+                       ratio);
+       }
+
+       (void)fclose(fp);
+}
+
 /*
  * print_statistics:
  *   Print out statistics based on the results of the test
  */
 void
-print_statistics(void) {
+print_statistics(int intermediate, unsigned int sent, unsigned int timed_out,
+                unsigned int possibly_delayed,
+                struct timeval *first_query,
+                struct timeval *program_start,
+                struct timeval *end_perf, struct timeval *end_query,
+                double rmax, double rmin, double rtotal,
+                unsigned int roverflows, unsigned int *rarray)
+{
        unsigned int num_queries_completed;
-       double per_lost, per_completed;
-       double run_time, queries_per_sec;
+       double per_lost, per_completed, per_lost2, per_completed2; 
+       double run_time, queries_per_sec, queries_per_sec2;
+       double queries_per_sec_total;
+       double rtt_average, rtt_stddev;
        struct timeval start_time;
 
-       num_queries_completed = num_queries_sent - num_queries_timed_out;
+       num_queries_completed = sent - timed_out;
 
        if (num_queries_completed == 0) {
                per_lost = 0.0;
                per_completed = 0.0;
+
+               per_lost2 = 0.0;
+               per_completed2 = 0.0;
        } else {
-               per_lost = 100.0 * (double)num_queries_timed_out
-                          / (double)num_queries_sent;
+               per_lost = (100.0 * (double)timed_out) / (double)sent;
                per_completed = 100.0 - per_lost;
+
+               per_lost2 = (100.0 * (double)(timed_out - possibly_delayed))
+                       / (double)sent;
+               per_completed2 = 100 - per_lost2;
        }
 
-       if (num_queries_sent == 0) {
-               start_time.tv_sec = time_of_program_start.tv_sec;
-               start_time.tv_usec = time_of_program_start.tv_usec;
+       if (sent == 0) {
+               start_time.tv_sec = program_start->tv_sec;
+               start_time.tv_usec = program_start->tv_usec;
                run_time = 0.0;
                queries_per_sec = 0.0;
+               queries_per_sec2 = 0.0;
+               queries_per_sec_total = 0.0;
        } else {
-               start_time.tv_sec = time_of_first_query.tv_sec;
-               start_time.tv_usec = time_of_first_query.tv_usec;
-               run_time = difftv(time_of_end_of_run, time_of_first_query);
+               start_time.tv_sec = first_query->tv_sec;
+               start_time.tv_usec = first_query->tv_usec;
+               run_time = difftv(*end_perf, *first_query);
                queries_per_sec = (double)num_queries_completed / run_time;
+               queries_per_sec2 = (double)(num_queries_completed +
+                                           possibly_delayed) / run_time;
+
+               queries_per_sec_total = (double)sent /
+                       difftv(*end_query, *first_query);
        }
 
-       printf("\n");
+       if (num_queries_completed > 0) {
+               int i;
+               double sum = 0;
 
-       printf("Statistics:\n");
+               rtt_average = rtt_total / (double)num_queries_completed;
+               for (i = 0; i < rttarray_size; i++) {
+                       if (rarray[i] != 0) {
+                               double mean, diff;
+
+                               mean = (double)(i * rttarray_unit) +
+                               (double)rttarray_unit / 2;
+                               diff = rtt_average - (mean / 1000000.0);
+                               sum += (diff * diff) * rarray[i];
+                       }
+               }
+               rtt_stddev = sqrt(sum / (double)num_queries_completed);
+       } else {
+               rtt_average = 0.0;
+               rtt_stddev = 0.0;
+       }
 
        printf("\n");
 
-       printf("  Parse input file:     %s\n",
-              ((run_only_once == TRUE) ? "once" : "multiple times"));
-       if (use_timelimit)
-               printf("  Run time limit:       %u seconds\n", run_timelimit);
-       if (run_only_once == FALSE)
-               printf("  Ran through file:     %u times\n",
-                      runs_through_file);
-       else
-               printf("  Ended due to:         reaching %s\n",
-                      ((runs_through_file == 0) ? "time limit"
-                      : "end of file"));
+       printf("%sStatistics:\n", intermediate ? "Intermediate " : "");
 
        printf("\n");
 
-       printf("  Queries sent:         %u queries\n", num_queries_sent);
+       if (!intermediate) {
+               printf("  Parse input file:     %s\n",
+                      ((run_only_once == TRUE) ? "once" : "multiple times"));
+               if (use_timelimit)
+                       printf("  Run time limit:       %u seconds\n",
+                              run_timelimit);
+               if (run_only_once == FALSE)
+                       printf("  Ran through file:     %u times\n",
+                              runs_through_file);
+               else
+                       printf("  Ended due to:         reaching %s\n",
+                              ((runs_through_file == 0) ? "time limit"
+                               : "end of file"));
+
+               printf("\n");
+       }
+
+       printf("  Queries sent:         %u queries\n", sent);
        printf("  Queries completed:    %u queries\n", num_queries_completed);
-       printf("  Queries lost:         %u queries\n", num_queries_timed_out);
+       printf("  Queries lost:         %u queries\n", timed_out);
+       printf("  Queries delayed(?):   %u queries\n", possibly_delayed);
+
+       printf("\n");
+
+       printf("  RTT max:              %3.6lf sec\n", rmax);
+       printf("  RTT min:              %3.6lf sec\n", rmin);
+       printf("  RTT average:          %3.6lf sec\n", rtt_average);
+       printf("  RTT std deviation:    %3.6lf sec\n", rtt_stddev);
+       printf("  RTT out of range:     %u queries\n", roverflows);
+
+       if (!intermediate)      /* XXX should we print this case also? */
+               print_histogram(num_queries_completed);
 
        printf("\n");
 
@@ -1574,28 +1949,88 @@ print_statistics(void) {
        }
 
        printf("  Percentage completed: %6.2lf%%\n", per_completed);
+       if (possibly_delayed > 0)
+               printf("     (w/ delayed qrys): %6.2lf%%\n", per_completed2);
        printf("  Percentage lost:      %6.2lf%%\n", per_lost);
+       if (possibly_delayed > 0)
+               printf("    (w/o delayed qrys): %6.2lf%%\n", per_lost2);
 
        printf("\n");
 
        printf("  Started at:           %s",
               ctime((const time_t *)&start_time.tv_sec));
        printf("  Finished at:          %s",
-              ctime((const time_t *)&time_of_end_of_run.tv_sec));
+              ctime((const time_t *)&end_perf->tv_sec));
        printf("  Ran for:              %.6lf seconds\n", run_time);
 
        printf("\n");
 
        printf("  Queries per second:   %.6lf qps\n", queries_per_sec);
+       if (possibly_delayed > 0) {
+               printf("   (w/ delayed qrys):   %.6lf qps\n",
+                      queries_per_sec2);
+       }
+       if (target_qps > 0) {
+               printf("  Total QPS/target:     %.6lf/%d qps\n",
+                      queries_per_sec_total, target_qps);              
+       }
 
        printf("\n");
 }
 
+void
+print_interval_statistics() {
+       struct timeval time_now;
+
+       if (use_timelimit == FALSE)
+               return;
+
+       if (setup_phase == TRUE)
+               return;
+
+       if (print_interval == 0)
+               return;
+
+       if (timelimit_reached() == TRUE)
+               return;
+
+       set_timenow(&time_now);
+       if (difftv(time_now, time_of_first_query_interval)
+           <= (double)print_interval)
+               return;
+
+       /* Don't count currently outstanding queries */
+       num_queries_sent_interval -= queries_outstanding();
+       print_statistics(TRUE, num_queries_sent_interval,
+                        num_queries_timed_out_interval,
+                        num_queries_possiblydelayed_interval,
+                        &time_of_first_query_interval,
+                        &time_of_first_query_interval, &time_now, &time_now,
+                        rtt_max_interval, rtt_min_interval,
+                        rtt_total_interval, rtt_overflows_interval,
+                        rttarray_interval);
+
+       /* Reset intermediate counters */
+       num_queries_sent_interval = 0;
+       num_queries_timed_out_interval = 0;
+       num_queries_possiblydelayed_interval = 0;
+       rtt_max_interval = -1;
+       rtt_min_interval = -1;
+       rtt_total_interval = 0.0;
+       rtt_overflows_interval = 0;
+       if (rttarray_interval != NULL) {
+               memset(rttarray_interval, 0,
+                      sizeof(rttarray_interval[0]) * rttarray_size);
+       }
+}
+
 /*
- * dnsqtest Program Mainline
+ * queryperf Program Mainline
  */
 int
 main(int argc, char **argv) {
+       int adjust_rate;
+       int sending = FALSE;
        int got_eof = FALSE;
        int input_length = MAX_INPUT_LEN;
        char input_line[MAX_INPUT_LEN + 1];
@@ -1603,6 +2038,8 @@ main(int argc, char **argv) {
        set_timenow(&time_of_program_start);
        time_of_first_query.tv_sec = 0;
        time_of_first_query.tv_usec = 0;
+       time_of_first_query_interval.tv_sec = 0;
+       time_of_first_query_interval.tv_usec = 0;
        time_of_end_of_run.tv_sec = 0;
        time_of_end_of_run.tv_usec = 0;
 
@@ -1615,9 +2052,13 @@ main(int argc, char **argv) {
 
        printf("[Status] Processing input data\n");
 
-       while (keep_sending(&got_eof) == TRUE || queries_outstanding() > 0) {
-               while (keep_sending(&got_eof) == TRUE
-                      && queries_outstanding() < max_queries_outstanding) {
+       while ((sending = keep_sending(&got_eof)) == TRUE ||
+              queries_outstanding() > 0) {
+               print_interval_statistics();
+               adjust_rate = FALSE;
+
+               while ((sending = keep_sending(&got_eof)) == TRUE &&
+                      queries_outstanding() < max_queries_outstanding) {
                        int len = next_input_line(input_line, input_length);
                        if (len == 0) {
                                got_eof = TRUE;
@@ -1639,12 +2080,17 @@ main(int argc, char **argv) {
                                        update_config(input_line);
                                else {
                                        send_query(input_line);
+                                       if (target_qps > 0 &&
+                                           (num_queries_sent %
+                                            max_queries_outstanding) == 0) {
+                                               adjust_rate = TRUE;
+                                       }
                                }
                        }
                }
 
-               retire_old_queries();
-               process_responses();
+               process_responses(adjust_rate);
+               retire_old_queries(sending);
        }
 
        set_timenow(&time_of_end_of_run);
@@ -1654,7 +2100,11 @@ main(int argc, char **argv) {
        close_socket();
        close_datafile();
 
-       print_statistics();
+       print_statistics(FALSE, num_queries_sent, num_queries_timed_out,
+                        num_queries_possiblydelayed,
+                        &time_of_first_query, &time_of_program_start,
+                        &time_of_end_of_run, &time_of_stop_sending,
+                        rtt_max, rtt_min, rtt_total, rtt_overflows, rttarray);
 
        return (0);
 }