mausezahn: use getopt_long instead of getopt
[netsniff-ng.git] / staging / rcv_rtp.c
blobf43b92cd826cf79bed474ef46beb3b41a6c735cc
1 /*
2 * Mausezahn - A fast versatile traffic generator
3 * Copyright (C) 2008-2010 Herbert Haas
4 *
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License version 2 as published by the
7 * Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12 * details.
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, see http://www.gnu.org/licenses/gpl-2.0.html
20 ///////////////////////////////////////////////////
21 //
22 // Table of contents:
23 //
24 // rcv_rtp_init()
25 // rcv_rtp()
26 // compare4B()
27 // got_rtp_packet()
28 // print_jitterbar()
29 //
31 ///////////////////////////////////////////////////
32 //
33 // Documentation about RTP traffic analysis
34 //
35 // See http://wiki.wireshark.org/RTP_statistics
37 //
39 #include "mz.h"
40 #include "mops.h"
42 static enum rtp_display_mode {
43 BAR, NCURSES, TEXT
44 } rtp_dm;
46 static int32_t
47 time0,
48 jitter_rfc;
50 static struct mz_timestamp
51 timeTX[TIME_COUNT_MAX],
52 timeRX[TIME_COUNT_MAX];
54 static u_int32_t
55 drop, // packet drop count
56 dis, // packet disorder count
57 gtotal; // counts number of file write cycles (see "got_rtp_packet()")
59 static char rtp_filter_str[64];
61 // Initialize the rcv_rtp process: Read user parameters and initialize globals
62 int rcv_rtp_init(void)
64 char argval[MAX_PAYLOAD_SIZE];
65 char dummy[512];
66 int len;
67 u_int32_t port = 30000; // 4-byte variable to catch errors, see below
69 int ssrc_s = 0;
71 // Help text
73 if (getarg(tx.arg_string,"help", NULL)==1) {
74 fprintf(stderr,"\n"
75 MAUSEZAHN_VERSION
76 "\n"
77 "| RTP reception for jitter measurements.\n"
78 "|\n"
79 "| Parameters:\n"
80 "|\n"
81 "| bar ...... Display modes: By default 'bar' is used and shows the RFC 3550 jitter as\n"
82 "| ASCII-based waterfall diagram.\n"
83 "| txt ...... The 'txt' mode prints all measurement values numerically upon each\n"
84 "| measurement interval.\n"
85 // "| curse ...... Shows all values and a diagram within an resizesable ncurses window.\n"
86 "|\n"
87 "| ssrc ....... Listen to the stream with the specified SSRC. You must specify this\n"
88 "| when there are concurrent streams, e. g. one in each direction.\n"
89 "|\n"
90 "| log ....... Write moving average also in a datafile (not only on terminal).\n"
91 "| logg ....... Like log but additionally write detailed real-time statistics in a data file\n"
92 "| path = <path> ....... Path to directory where datafiles can be stored (default: local directory).\n"
93 "| num = <10-%d> ...... number of packets to be received for averaging (default: %d).\n"
94 "| port = <0-65535> ....... Change if RTP packets are sent to a different port than 30000 (default).\n"
95 "|\n"
96 "| Note:\n"
97 "|\n"
98 "| Mausezahn can log actual realtime measurement data in data files (in the specified path or\n"
99 "| current directory) but always prints the moving average on the command line (this can be disabled\n"
100 "| using the 'quiet' option (-q)).\n"
101 "|\n"
102 "| The realtime data file(s) consist of two columns:\n"
103 "|\n"
104 "| 1. relative timestamp in usec\n"
105 "| 2. 'true' jitter in usec\n"
106 "|\n"
107 "| where the 'true' jitter is calculated using the (relative) timestamps inside the received\n"
108 "| packets t(i) and the (relative) timestamps T(i) observed locally when packets are received using\n"
109 "| the formula:\n"
110 "|\n"
111 "| jitter(i) = [T(i) - T(i-1)] - [t(i) - t(i-1)] + jitter(i-1) .\n"
112 "|\n"
113 "| This method has two advantages: (i) we do not need to synchronize the clocks of sender and\n"
114 "| receiver, and (ii) the TX-side jitter (mainly caused by the kernel-scheduler) is subtracted\n"
115 "| so that we primarily measure the jitter caused by the network.\n"
116 "| \n"
117 "| The data files consist of seven columns:\n"
118 "| \n"
119 "| 1. relative timestamp in seconds\n"
120 "| 2. minimum jitter\n"
121 "| 3. average jitter\n"
122 "| 4. minimum jitter\n"
123 "| 5. estimated jitter variance according RFC-3550\n"
124 "| 6. packet drop count (total)\n"
125 "| 7. packet disorder count (total)\n"
126 "| \n"
127 "| All measurement values are done in usec and refer to the current set of samples (see parameter 'num').\n"
128 "| Note that an RFC-conform jitter (smoothed mean deviation) is calculated and collected in column five.\n"
129 "| The drop value refers to the current measurement window, while the total drop and disorder values are\n"
130 "| calculated using some weird estimation functions; the goal was to provide a 'time-less' estimation\n"
131 "| while being able to automatically resynchronize to a re-started RTP measurement stream.\n"
132 "| \n"
133 "| EXAMPLE USAGE:\n"
134 "|\n"
135 "| At the TX-station enter:\n"
136 "|\n"
137 "| # mz eth0 -t rtp -B 10.3.3.42 (optionally change rate via -d option, payload size via pld command)\n"
138 "|\n"
139 "| At the RX-station (10.3.3.42) enter:\n"
140 "|\n"
141 "| # mz eth0 -T rtp \"log, path=/tmp/mz/\"\n"
142 "|\n"
143 "\n", TIME_COUNT_MAX, TIME_COUNT);
144 exit(0);
148 // check argstring for arguments
150 if (getarg(tx.arg_string,"bar", NULL)==1) {
151 rtp_dm = BAR;
154 if (getarg(tx.arg_string,"txt", NULL)==1) {
155 rtp_dm = TEXT;
158 if (getarg(tx.arg_string,"curses", NULL)==1) {
159 rtp_dm = BAR; //NCURSES;
160 fprintf(stderr, " XXX This Mausezahn version does not support ncurses windows.\n");
163 if (getarg(tx.arg_string,"width", argval)==1) {
164 if (rtp_dm != BAR) {
165 fprintf(stderr, " mz/rcv_rtp: The 'width' parameter requires the display mode 'bar'\n");
166 return -1;
168 bwidth = (int) str2int(argval); // [TODO] bwidth is currently not used
169 if (bwidth>RCV_RTP_MAX_BAR_WIDTH) {
170 fprintf(stderr, "The width must not exceed %i\n",
171 RCV_RTP_MAX_BAR_WIDTH);
172 return -1;
174 } else bwidth=80;
176 if (getarg(tx.arg_string,"ssrc", argval)==1) {
177 ssrc_s = str2hex(argval, mz_ssrc, 4);
178 if (ssrc_s<0) {
179 fprintf(stderr, " mz/rtp_rcv: invalid ssrc!\n");
180 return -1;
184 if (getarg(tx.arg_string,"log", NULL)==1) {
185 rtp_log = 1;
188 if (getarg(tx.arg_string,"logg", NULL)==1) {
189 rtp_log = 2;
193 if (getarg(tx.arg_string,"path", argval)==1) {
194 len = strlen(argval);
195 if (len>128) {
196 fprintf(stderr, " mz/Error: path must not exceed 128 characters!\n");
197 exit (-1);
199 if (argval[len-1]!='/') {
200 strncat(argval, "/",1); // ensure that all paths end with "/"
202 strncpy(path, argval, 128);
206 if (getarg(tx.arg_string,"num", argval)==1) {
207 gind_max = (u_int32_t) str2int(argval);
208 if (gind_max > TIME_COUNT_MAX) {
209 gind_max = TIME_COUNT_MAX;
210 fprintf(stderr, " mz/Warning: num range is 10..%d. Will reset to %d.\n",
211 TIME_COUNT_MAX, TIME_COUNT_MAX);
213 else if (gind_max < 10) {
214 gind_max = 10;
215 fprintf(stderr, " mz/Warning: num range is 10..%d. Will reset to 10.\n",
216 TIME_COUNT_MAX);
221 // initialize global filter string
222 strncpy (rtp_filter_str, "udp dst port 30000", 64);
224 if (getarg(tx.arg_string,"port", argval)==1) {
225 port = (u_int32_t) str2int(argval);
226 if (port>65535) {
227 port = 30000;
228 fprintf(stderr, " mz: Too large port number! Reset to default port (30000).\n");
231 sprintf(rtp_filter_str, "udp dst port %u", (unsigned int) port);
235 if (ssrc_s==0) str2hex("ca:fe:fe:ed", mz_ssrc, 4);
237 // open file
239 if (rtp_log) {
240 // get a new filename
241 timestamp_human(filename, "rtp_avg_");
242 strncpy(dummy, path, 128);
243 strncat(dummy, filename, 64);
244 if (verbose) fprintf(stderr, " mz: Will open %s\n", dummy);
246 fp = fopen (dummy, "w+");
248 if (fp == NULL) {
249 perror("fopen");
250 exit (-1);
253 gtotal=0; // counts written data blocks
254 fprintf(fp, "# Average jitter measurements made by Mausezahn " MAUSEZAHN_VERSION_SHORT ".\n");
255 fprintf(fp, "# Timestamp is in seconds, all other values in microseconds.\n");
256 fprintf(fp, "# Column values (from left to right):\n");
257 fprintf(fp, "# 1. Timestamp\n"
258 "# 2. min_jitter\n"
259 "# 3. avg_jitter\n"
260 "# 4. max_jitter\n"
261 "# 5. estimated jitter according RFC-3550\n"
262 "# 6. packet drop count (total)\n"
263 "# 7. packet disorder count (total)\n");
266 ///////////// also detailed log required /////////////
267 if (rtp_log==2) {
268 // get a new filename
269 timestamp_human(filename, "rtp_rt_");
270 strncpy(dummy, path, 128);
271 strncat(dummy, filename, 64);
272 if (verbose) fprintf(stderr, " mz: Will open %s\n", dummy);
274 fp2 = fopen (dummy, "w+");
276 if (fp2 == NULL) {
277 perror("fopen");
278 exit (-1);
281 fprintf(fp2, "# Jitter measurements by Mausezahn " MAUSEZAHN_VERSION_SHORT ".\n");
282 fprintf(fp2, "# Timestamp (usec) , true jitter (nsec)\n");
287 drop=0;
288 dis=0;
289 jitter_rfc=0;
291 return 0;
300 ////////////////////////////////////////////////////////////////////////////////////////////
302 // Defines the pcap handler and the callback function
303 int rcv_rtp(void)
305 char errbuf[PCAP_ERRBUF_SIZE];
307 pcap_t *p;
309 struct bpf_program filter;
313 p = pcap_open_live (tx.device,
314 MAXBYTES_TO_READ, // max num of bytes to read
315 0, // 1 if promiscuous mode
316 PCAP_READ_TIMEOUT_MSEC, // read timeout in msec
317 errbuf);
319 if (p == NULL)
321 fprintf(stderr," mz/rcv_rtp: %s\n",errbuf);
322 exit(1);
326 if ( pcap_compile(p,
327 &filter, // the compiled version of the filter
328 rtp_filter_str, // text version of filter
329 0, // 1 = optimize
330 0) // netmask
331 == -1)
333 fprintf(stderr," mz/rcv_rtp: Error calling pcap_compile\n");
334 exit(1);
339 if ( pcap_setfilter(p, &filter) == -1)
341 fprintf(stderr," mz/rcv_rtp: Error setting filter\n");
342 pcap_geterr(p);
343 exit(1);
346 again:
349 pcap_loop (p,
350 1, // number of packets to wait
351 got_rtp_packet, // name of callback function
352 NULL); // optional additional arguments for callback function
355 goto again;
358 // TODO: Currently we never reach this point!
359 fprintf(stderr, " mz: receiving of RTP finished.\n");
360 pcap_close(p);
362 return 0;
368 // Compares two 4-byte variables byte by byte
369 // returns 0 if identical, 1 if different
370 inline int compare4B (u_int8_t *ip1, u_int8_t *ip2)
372 if (*ip1 != *ip2) return 1;
373 if (*(ip1+1) != *(ip2+1)) return 1;
374 if (*(ip1+2) != *(ip2+2)) return 1;
375 if (*(ip1+3) != *(ip2+3)) return 1;
377 return 0;
384 // Handler function to do something when RTP messages are received
385 void got_rtp_packet(u_char *args,
386 const struct pcap_pkthdr *header, // statistics about the packet (see 'struct pcap_pkthdr')
387 const u_char *packet) // the bytestring sniffed
389 const struct struct_ethernet *ethernet;
390 const struct struct_ip *ip;
391 const struct struct_udp *udp;
392 const struct struct_rtp *rtp;
394 int size_ethernet = sizeof(struct struct_ethernet);
395 int size_ip = sizeof(struct struct_ip);
396 int size_udp = sizeof(struct struct_udp);
397 // int size_rtp = sizeof(struct struct_rtp);
399 ethernet = (struct struct_ethernet*)(packet);
400 ip = (struct struct_ip*)(packet+size_ethernet);
401 udp = (struct struct_udp*)(packet+size_ethernet+size_ip);
402 rtp = (struct struct_rtp*)(packet+size_ethernet+size_ip+size_udp);
404 struct mz_timestamp
405 deltaTX,
406 deltaRX;
408 u_int32_t
410 jitter_abs,
411 jitter_avg,
412 jitter_max,
413 jitter_min,
414 curtime=0;
416 int32_t ltemp;
418 u_int8_t *x,*y;
420 char dummy[256];
421 char ts_hms[10];
422 unsigned char *dum;
423 static u_int32_t drop_last=0, drop_prev=0;
424 int s1, s2;
426 // check if the RTP packet is really from a Mausezahn instance:
427 if (compare4B((u_int8_t*) &rtp->ssrc, mz_ssrc)==0) {
428 // we got a valid RTP packet from a Mausezahn instance
429 // Get current SQNR and store it in 'sqnr_cur' in host byte order
430 x = (u_int8_t*) &rtp->sqnr;
431 y = (u_int8_t*) &sqnr_cur;
433 *y = *(x+1);
434 y++;
435 *y = *x;
437 /////////////////////////////////////////////////////////////////////
438 // Packet drop and disorder detection:
439 if (sqnr0_flag) {
440 if (sqnr_next==sqnr_cur) { // correct SQNR received
441 sqnr_next++;
442 sqnr_last++;
443 } else if (sqnr_last>sqnr_cur) { // disordered sequence
444 dis++;
445 if (drop) drop--; // don't get below 0
446 else { // drop reached zero: resync (restarted RTP stream?)
447 sqnr_last = sqnr_cur;
448 sqnr_next = (++sqnr_last);
449 dis=0;
451 } else { // packet drop
452 drop += (sqnr_cur-sqnr_next);
453 sqnr_last = sqnr_cur;
454 sqnr_next = (++sqnr_last);
456 } else {
457 // initial synchronization with observed SQNR:
458 sqnr_last = sqnr_cur;
459 sqnr_next = (++sqnr_last);
460 sqnr0_flag++;
463 /////////////////////////////////////////////////////////////////////
466 // Get RX timestamp from pcap header
467 timeRX[gind].sec = header->ts.tv_sec;
468 timeRX[gind].nsec = header->ts.tv_usec *1000;
470 // Get TX timestamp from the packet
471 mops_hton4((u_int32_t*) &rtp->time_sec, (u_int8_t*) &timeTX[gind].sec);
472 mops_hton4((u_int32_t*) &rtp->time_nsec, (u_int8_t*) &timeTX[gind].nsec);
474 // printf("%li %li\n", (long int) timeTX[gind].sec, (long int) timeTX[gind].nsec);
476 gind++;
478 ////////////////////////////////////////////////////////////////
479 if (gind == gind_max) { // array full, now calculate statistics
480 gind=0;
481 gtotal++;
483 jitter_avg = 0;
484 jitter_min = 0xffffffff;
485 jitter_max = 0;
488 ///////////////////////////////////////////////////////
489 // calculate deltas and jitters
490 for (i=2; i<gind_max; i++) { // omit the first 2 data
491 // entries because of
492 // artificial high TX-delta!
494 ///////////////////////////////////////////////
495 // calculate deltaTX and deltaRX
497 s1=timestamp_subtract (&timeTX[i], &timeTX[i-1], &deltaTX);
498 s2=timestamp_subtract (&timeRX[i], &timeRX[i-1], &deltaRX);
499 if (s1) fprintf(stderr, " *** ***\n");
501 // Then calculate the precise jitter by considering
502 // also TX-jitter: (pseudo)jitter = deltaRX - deltaTX,
503 // hence we have positive and negative jitter (delay
504 // deviations) jitter entries are in +/- nanoseconds
505 jitter[i] = (deltaRX.sec*1000000000L + deltaRX.nsec)
506 - (deltaTX.sec*1000000000L + deltaTX.nsec);
507 // Calculate RFC 3550 jitter estimation. According to
508 // that RFC the jitter should be measured in timestamp
509 // units; however currently Mausezahn uses nanoseconds.
510 // (If we want to solve this: G.711 timestamp units are
511 // 125 usec, so jitter/=125 would be sufficient, AFAIK)
512 ltemp = labs(jitter[i]) - jitter_rfc;
513 jitter_rfc += (ltemp>>4);
514 // Add previous pseudojitter to get the true jitter
515 // (See Documentation!)
516 jitter[i] += jitter[i-1];
518 ////////////////////////////////////////////////
523 ////////////////////////////////////////////////
524 // Determine avg, min, and max jitter within this time frame:
525 jitter_abs = labs(jitter[i]);
526 jitter_avg += jitter_abs;
527 if (jitter_abs < jitter_min) jitter_min = jitter_abs;
528 if (jitter_abs > jitter_max) jitter_max = jitter_abs;
530 ////////////////////////////////
532 /// PRINT IN FILE_2: Detailed jitter data ///
533 if (rtp_log==2) {
534 // Calculate relative timestamp for column 1 of the datafile
535 curtime = timeRX[i].sec*1000000+timeRX[i].nsec/1000;
536 if (time0_flag) {
537 curtime = curtime - time0;
538 } else { // this is only done once during the Mausezahn process
539 time0 = curtime;
540 time0_flag=1;
541 curtime = curtime - time0;
543 fprintf(fp2, "%lu, %li\n",
544 (long unsigned int) curtime,
545 (long int) jitter[i]);
546 fflush(fp2); // save everything immediately
547 // (CHECK if fsync() is additionally needed)
549 } // end for (i=2; i<gind_max; i++)
551 ////////////////////////////////////////////////////////
554 jitter_avg = jitter_avg / (gind_max-2); // average true jitter, always positive
556 if (drop>=drop_prev) { // because the total drop count may decrease(!) if disordered packets appear lately
557 drop_last = drop - drop_prev;
558 drop_prev=drop;
559 } else drop_last=0;
561 // PRINT ON CLI: statistics data
562 switch (rtp_dm) {
563 case TEXT:
564 dum = (unsigned char*) &ip->src;
565 fprintf(stdout,
566 "Got %u packets from host %u.%u.%u.%u: %lu lost (%lu absolute lost, %lu out of order)\n"
567 " Jitter_RFC (low pass filtered) = %li usec\n"
568 " Samples jitter (min/avg/max) = %lu/%lu/%lu usec\n",
569 gind_max,
570 *(dum),*(dum+1),*(dum+2),*(dum+3),
571 (long unsigned int) drop_last,
572 (long unsigned int) drop,
573 (long unsigned int) dis,
574 (long int) jitter_rfc/1000,
575 (long unsigned int) jitter_min/1000,
576 (long unsigned int) jitter_avg/1000,
577 (long unsigned int) jitter_max/1000);
578 break;
580 case BAR:
581 print_jitterbar(jitter_rfc/1000, drop_last);
582 break;
584 case NCURSES: // would be nice...?
585 break;
587 default:
588 break;
591 // Determine whether some packets got lost:
599 /// PRINT IN FILE_1: statistics only ///
600 if (rtp_log) {
601 ts_hms[0]=0x00;
602 timestamp_hms (ts_hms);
603 fprintf(fp,
604 "%s, %lu, %lu, %lu, %li, %u, %u\n",
605 ts_hms,
606 (long unsigned int) jitter_min/1000,
607 (long unsigned int) jitter_avg/1000,
608 (long unsigned int) jitter_max/1000,
609 (long int) jitter_rfc/1000,
610 drop,
611 dis);
612 fflush(fp);
617 // Open another file if current file reaches a limit
619 if ((rtp_log==2) && (gtotal>MAX_DATA_BLOCKS)) { // file big enough,
620 gtotal=0;
621 if (fclose(fp2) == EOF) {
622 perror("fclose");
623 exit(1);
626 if (verbose)
627 fprintf(stderr, " mz: %s written.\n",filename);
629 timestamp_human(filename, "rtp_"); // get a new filename
630 strncpy(dummy, path, 128);
631 strncat(dummy, filename, 64);
633 if (verbose) fprintf(stderr, " mz: Will open %s\n", dummy);
635 if ( (fp2 = fopen (dummy, "w+")) == NULL) {
636 if (errno != EAGAIN) {
637 perror("fopen");
638 exit (-1);
641 fprintf(fp2, "# Jitter measurements by Mausezahn "
642 MAUSEZAHN_VERSION_SHORT ".\n");
643 fprintf(fp2, "# Timestamp (usec) , true jitter (nsec)\n");
645 } // statistics end *********************************************************************
652 void print_jitterbar (long int j, u_int32_t d)
654 // Determine actual data window by considering two events:
656 // 1) window move (j exceeds lower or upper limit)
657 // 2) window rescale (window moves happen too often or the variance
658 // of successive data points is too small)
660 // The most critical value is the chosen resolution (window range),
661 // especially the _initial_ resolution.
663 static long int range=0, min=0, max=0, minvar=0, j0=0, dj=0;
664 static int moved=0, varcount=0, barcount=0;
665 char str[128], bar[150],
666 str1[8], str2[8], str3[8], str4[8];
667 int event=0, anz;
668 long int tmp;
670 // Initialize vars (start with an opened window)
671 // Note that 'range' is actually half of the window
672 if (!range) {
673 range=j;
674 if (range<500) range=500;
675 max = j+range;
676 min = 0;
677 minvar=range/40;
678 event++;
679 } else {
680 dj = labs(j-j0); // no initialization: calculate jitter delta
683 // Move window when borders crossed:
684 if ((j<min) || (j>max)) {
685 max = j + range;
686 min = max-2*range;
687 if (min<0) {
688 min=0;
689 range=(max-min)/2;
690 fprintf(stdout, "\nNOTE: +- Rescaled window to %4.2f msec\n", (double) range/500);
692 moved++;
693 event++;
694 fprintf(stdout,"\n");
695 // printf("move event: min=%li max=%li\n", min, max);
696 } else {
697 if (moved) moved--;
698 // printf("normal event: min=%li max=%li\n", min, max);
702 // Increase range when window moved 5 times in a row
703 if (moved>2) {
704 range*=3;
705 if (range>10000000L) range=10000000L;
706 minvar=range/40;
707 if (minvar<1000) minvar=1000;
708 max=j+range;
709 min=j-range;
710 if (min<0) {
711 min=0;
712 range=(max-min)/2;
714 moved=0;
715 event++;
716 // printf("scale up event: min=%li max=%li\n", min, max);
717 fprintf(stdout, "\nNOTE: ++ Rescaled window to %4.2f msec\n", (double) range/500);
721 // Decrease range when jitter deltas are smaller than minvar
722 // 5 times in a row
723 if (dj<minvar)
724 varcount++;
725 else
726 varcount=0;
728 if (varcount>5) {
729 range*=0.75;
730 if (range>j) range=j;
731 if (range<500) {
732 range=500;
734 minvar=range/40;
735 if (minvar<1000) minvar=1000;
736 max=j+range;
737 min=j-range;
738 if (min<0) {
739 min=0;
740 range=(max-min)/2;
742 fprintf(stdout, "\nNOTE: -- Rescaled window to %4.2f msec\n", (double) range/500);
743 varcount=0;
744 event++;
745 // printf("scale down event: min=%li max=%li\n", min, max);
748 j0=j;
750 barcount++;
751 if (barcount==24) {
752 event=1;
753 barcount=0;
756 if (event) {
757 tmp=range*0.667;
758 sprintf(str1,"%4.2f", (double) min/1000);
759 sprintf(str2,"%4.2f", (double) (min+tmp)/1000);
760 sprintf(str3,"%4.2f", (double) (max-tmp)/1000);
761 sprintf(str4,"%4.2f", (double) max/1000);
763 fprintf(stdout,
764 "%-6s %-6s %-6s %-6s\n"
765 "|-------------------------|-------------------------|-------------------------|\n",
766 str1, str2, str3, str4);
767 barcount=0;
770 anz = 80*(j-min)/(2*range);
771 if (anz) {
772 memset((void*) str, '#', anz);
773 memset((void*) str+anz, ' ', 80-anz);
774 str[80]='\0';
776 else {
777 memset((void*) str, ' ', 80);
778 str[0]='#';
779 str[80]='\0';
781 if (d)
782 sprintf(bar, "%s%4.2f msec !%lu dropped!", str, (double) j/1000, (unsigned long int) d);
783 else
784 sprintf(bar, "%s%4.2f msec", str, (double) j/1000);
786 fprintf(stdout,"%s\n", bar);