/* * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, * 2002, 2003, 2004 * Ohio University. * * --- * * Starting with the release of tcptrace version 6 in 2001, tcptrace * is licensed under the GNU General Public License (GPL). We believe * that, among the available licenses, the GPL will do the best job of * allowing tcptrace to continue to be a valuable, freely-available * and well-maintained tool for the networking community. * * Previous versions of tcptrace were released under a license that * was much less restrictive with respect to how tcptrace could be * used in commercial products. Because of this, I am willing to * consider alternate license arrangements as allowed in Section 10 of * the GNU GPL. Before I would consider licensing tcptrace under an * alternate agreement with a particular individual or company, * however, I would have to be convinced that such an alternative * would be to the greater benefit of the networking community. * * --- * * This file is part of Tcptrace. * * Tcptrace was originally written and continues to be maintained by * Shawn Ostermann with the help of a group of devoted students and * users (see the file 'THANKS'). The work on tcptrace has been made * possible over the years through the generous support of NASA GRC, * the National Science Foundation, and Sun Microsystems. * * Tcptrace is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Tcptrace is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tcptrace (in the file 'COPYING'); if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * Author: Shawn Ostermann * School of Electrical Engineering and Computer Science * Ohio University * Athens, OH * ostermann@cs.ohiou.edu * http://www.tcptrace.org/ */ #include "tcptrace.h" static char const GCC_UNUSED copyright[] = "@(#)Copyright (c) 2004 -- Ohio University.\n"; static char const GCC_UNUSED rcsid[] = "@(#)$Header$"; /* info that I keep about each plotter */ struct plotter_info { MFILE *fplot; /* the file that hold the plot */ tcb *p2plast; /* the TCB that this goes with (if any) */ timeval zerotime; /* first time stamp in this plot (see -z) */ char *filename; /* redundant copy of name for debugging */ Bool header_done; /* Flag indicating plotter header written to file */ Bool axis_switched; /* Switch x & y axis types. * (Needed for Time Line Charts, * Default = FALSE) */ char *title; /* Plotter title */ char *xlabel; /* Plotter x-axis label */ char *ylabel; /* Plotter y-axis label */ }; /* locally global parameters */ static int max_plotters; static PLOTTER plotter_ix = NO_PLOTTER; static char *temp_color = NULL; static struct plotter_info *pplotters; /* local routine declarations */ static char *xp_timestamp(PLOTTER pl, struct timeval time); static char *TSGPlotName(tcb *plast, PLOTTER, char *suffix); static void DoPlot(PLOTTER pl, char *fmt, ...); static void WritePlotHeader(PLOTTER pl); static void CallDoPlot(PLOTTER pl, char *plot_cmd, int plot_argc, ...); /* * Return a string suitable for use as a timestamp in the xplot output. * sdo fix: originally, we were just plotting to the accuracy of 1/10 ms * this was mostly to help keep the .xpl files from being too large. However, * on faster networks this just isn't enough, so we'll now use all 6 digits * of the microsecond counter. Note that there's no guarantee that the * timestamps are really all that accurate! */ static char * xp_timestamp( PLOTTER pl, struct timeval time) { #define NUM_BUFS 4 static char bufs[NUM_BUFS][20]; /* several of them for multiple calls in one printf */ static int bufix = 0; unsigned secs; unsigned usecs; unsigned decimal; char *pbuf; struct plotter_info *ppi; ppi = &pplotters[pl]; /* see if we're graphing from "0" OR if the axis type is switched */ if (graph_time_zero || ppi->axis_switched) { if (ZERO_TIME(&ppi->zerotime)) { /* set "zero point" */ ppi->zerotime = time; } /* (in)sanity check */ if (tv_lt(time,ppi->zerotime)) { fprintf(stderr,"Internal error in plotting (plot file '%s')...\n\ ZERO-based X-axis plotting requested and elements are not plotted in\n\ increasing time order. Try without the '-z' flag\n", ppi->filename); /* exit(-5); */ time.tv_sec = time.tv_usec = 0; } else { /* computer offset from first plotter point */ tv_sub(&time, ppi->zerotime); } } /* calculate time components */ secs = time.tv_sec; usecs = time.tv_usec; decimal = usecs; /* use one of 4 rotating static buffers (for multiple calls per printf) */ bufix = (bufix+1)%NUM_BUFS; pbuf = bufs[bufix]; snprintf(pbuf,sizeof(bufs[bufix]),"%u.%06u",secs,decimal); return(pbuf); } void plot_init(void) { max_plotters = 256; /* just a default, make more on the fly */ pplotters = MallocZ(max_plotters * sizeof(struct plotter_info)); } static void plotter_makemore(void) { int new_max_plotters = max_plotters * 4; if (debug) fprintf(stderr,"plotter: making more space for %d total plotters\n", new_max_plotters); /* reallocate the memory to make more space */ pplotters = ReallocZ(pplotters, max_plotters * sizeof(struct plotter_info), new_max_plotters * sizeof(struct plotter_info)); max_plotters = new_max_plotters; } /* max number of letters in endpoint name */ /* (8 allows 26**8 different endpoints (209,000,000,000) probably plenty for now!!!!!) */ /* #define MAX_HOSTLETTER_LEN 8 Moving this definition to tcptrace.h so other modules can use it. */ char * HostLetter( llong ix) { static char name[MAX_HOSTLETTER_LEN+1]; static char *pname; /* basically, just convert to base 26 */ pname = &name[sizeof(name)-1]; *pname-- = '\00'; while (pname >= name) { unsigned digit = ix % 26; *pname-- = 'a'+digit; ix = (ix / 26) - 1; if (ix == -1) return(pname+1); } fprintf(stderr,"Fatal, too many hosts to name (max length %d)\n\nNOTE:\nIf you are using gcc version 2.95.3, then this may be a compiler bug. This particular version\nis known to generate incorrect assembly code when used with CCOPT=-O2.\nSuggested fixes are:\n 1. Update gcc to the latest version and recompile tcptrace.\n 2. Use the same version of gcc, but edit the tcptrace Makefile, setting CCOPT=-O instead of\n CCOPT=-O2, and then recompile tcptrace.\nEither of these steps should hopefully fix the problem.\n\n", MAX_HOSTLETTER_LEN); exit(-1); return(NULL); /* NOTREACHED */ } char * NextHostLetter(void) { static llong count = 0; return(HostLetter(count++)); } static char * TSGPlotName( tcb *plast, PLOTTER pl, char *suffix) { static char filename[25]; snprintf(filename,sizeof(filename),"%s2%s%s", plast->host_letter, plast->ptwin->host_letter, suffix); return(filename); } static void DoPlot( PLOTTER pl, char *fmt, ...) { va_list ap; MFILE *f = NULL; struct plotter_info *ppi; va_start(ap,fmt); /* if (!graph_tsg) */ /* return; */ if (pl == NO_PLOTTER) { va_end(ap); return; } if (pl > plotter_ix) { fprintf(stderr,"Illegal plotter: %d\n", pl); exit(-1); } ppi = &pplotters[pl]; if ((f = ppi->fplot) == NULL) { va_end(ap); return; } /* Write the plotter header if not already written */ if(!ppi->header_done) WritePlotHeader(pl); Mvfprintf(f,fmt,ap); if (temp_color) { Mfprintf(f," %s",temp_color); temp_color = NULL; } Mfprintf (f,"\n"); va_end(ap); return; } PLOTTER new_plotter( tcb *plast, char *filename, /* if NULL, use default name from plast */ char *title, char *xlabel, char *ylabel, char *suffix) { PLOTTER pl; MFILE *f; struct plotter_info *ppi; ++plotter_ix; if (plotter_ix >= max_plotters) { plotter_makemore(); } pl = plotter_ix; ppi = &pplotters[pl]; if (filename == NULL) filename = TSGPlotName(plast,pl,suffix); else if (suffix != NULL) { char buf[100]; snprintf(buf,sizeof(buf),"%s%s", filename, suffix); filename = buf; } if (debug) fprintf(stderr,"Plotter %d file is '%s'\n", pl, filename); if ((f = Mfopen(filename,"w")) == NULL) { perror(filename); return(NO_PLOTTER); } ppi->fplot = f; ppi->p2plast = plast; ppi->filename = strdup(filename); ppi->axis_switched = FALSE; ppi->header_done = FALSE; /* Save these fields to be writtn to the plotter header later in DoPlot() */ ppi->title = strdup(title); ppi->xlabel = strdup(xlabel); ppi->ylabel = strdup(ylabel); return(pl); } void plotter_done(void) { PLOTTER pl; MFILE *f; char *fname; static struct dstring *xplot_cmd_buff=NULL; if(plotter_ix>0) { if(xplot_all_files) { xplot_cmd_buff=DSNew(); DSAppendString(xplot_cmd_buff,"xplot"); DSAppendString(xplot_cmd_buff," "); if(xplot_args!=NULL) { DSAppendString(xplot_cmd_buff,xplot_args); DSAppendString(xplot_cmd_buff," "); } } } for (pl = 0; pl <= plotter_ix; ++pl) { struct plotter_info *ppi = &pplotters[pl]; if ((f = ppi->fplot) == NULL) continue; /* Write the plotter header if not already written */ if(!ppi->header_done) WritePlotHeader(pl); if (!ignore_non_comp || ((ppi->p2plast != NULL) && (ConnComplete(ppi->p2plast->ptp)))) { Mfprintf(f,"go\n"); Mfclose(f); } else { fname = ppi->p2plast->tsg_plotfile; if (debug) fprintf(stderr,"Removing incomplete plot file '%s'\n", fname); Mfclose(f); if (unlink(fname) != 0) perror(fname); } if(xplot_all_files){ if(output_file_dir!=NULL) { DSAppendString(xplot_cmd_buff,output_file_dir); DSAppendString(xplot_cmd_buff,"/"); } DSAppendString(xplot_cmd_buff,ppi->filename); DSAppendString(xplot_cmd_buff," "); } } if(plotter_ix>0) { if(xplot_all_files) { fprintf(stdout,"%s\n",DSVal(xplot_cmd_buff)); system(DSVal(xplot_cmd_buff)); DSDestroy(&xplot_cmd_buff); } } } void plotter_temp_color( PLOTTER pl, char *color) { if (colorplot) temp_color = color; } void plotter_perm_color( PLOTTER pl, char *color) { if (colorplot) CallDoPlot(pl, color, 0); } void plotter_line( PLOTTER pl, struct timeval t1, u_long x1, struct timeval t2, u_long x2) { CallDoPlot(pl,"line", 4, t1, x1, t2, x2); } void plotter_dline( PLOTTER pl, struct timeval t1, u_long x1, struct timeval t2, u_long x2) { CallDoPlot(pl,"dline", 4, t1, x1, t2, x2); } void plotter_diamond( PLOTTER pl, struct timeval t, u_long x) { CallDoPlot(pl,"diamond", 2, t, x); } void plotter_dot( PLOTTER pl, struct timeval t, u_long x) { CallDoPlot(pl,"dot", 2, t, x); } void plotter_plus( PLOTTER pl, struct timeval t, u_long x) { CallDoPlot(pl,"plus", 2, t, x); } void plotter_box( PLOTTER pl, struct timeval t, u_long x) { CallDoPlot(pl,"box", 2, t, x); } void plotter_arrow( PLOTTER pl, struct timeval t, u_long x, char dir) { char arrow_type[7]; snprintf(arrow_type, sizeof(arrow_type), "%carrow", dir); CallDoPlot(pl, arrow_type, 2, t, x); } void plotter_uarrow( PLOTTER pl, struct timeval t, u_long x) { plotter_arrow(pl,t,x,'u'); } void plotter_darrow( PLOTTER pl, struct timeval t, u_long x) { plotter_arrow(pl,t,x,'d'); } void plotter_rarrow( PLOTTER pl, struct timeval t, u_long x) { plotter_arrow(pl,t,x,'r'); } void plotter_larrow( PLOTTER pl, struct timeval t, u_long x) { plotter_arrow(pl,t,x,'l'); } void plotter_tick( PLOTTER pl, struct timeval t, u_long x, char dir) { char tick_type[6]; snprintf(tick_type, sizeof(tick_type), "%ctick", dir); CallDoPlot(pl, tick_type, 2, t, x); } void plotter_dtick( PLOTTER pl, struct timeval t, u_long x) { plotter_tick(pl,t,x,'d'); } void plotter_utick( PLOTTER pl, struct timeval t, u_long x) { plotter_tick(pl,t,x,'u'); } void plotter_ltick( PLOTTER pl, struct timeval t, u_long x) { plotter_tick(pl,t,x,'l'); } void plotter_rtick( PLOTTER pl, struct timeval t, u_long x) { plotter_tick(pl,t,x,'r'); } void plotter_htick( PLOTTER pl, struct timeval t, u_long x) { plotter_tick(pl,t,x,'h'); } void plotter_vtick( PLOTTER pl, struct timeval t, u_long x) { plotter_tick(pl,t,x,'v'); } /* don't plot ANYTHING, just make sure ZERO point is set! */ void plotter_nothing( PLOTTER pl, struct timeval t) { char *ret; ret = xp_timestamp(pl,t); if (debug > 10) printf("plotter_nothing(%s) gets '%s'\n", ts2ascii(&t), ret); } void plotter_text( PLOTTER pl, struct timeval t, u_long x, char *where, char *str) { char text_type[6]; snprintf(text_type, sizeof(text_type), "%stext", where); CallDoPlot(pl, text_type, 2, t, x); /* fix by Bill Fenner - Wed Feb 5, 1997, thanks */ /* This is a little ugly. Text commands take 2 lines. */ /* A temporary color could have been */ /* inserted after that line, but would NOT be inserted after */ /* the next line, so we'll be OK. I can't think of a better */ /* way right now, and this works fine (famous last words) */ CallDoPlot(pl, str, 0); } void plotter_invisible( PLOTTER pl, struct timeval t, u_long x) { CallDoPlot(pl,"invisible", 2, t, x); } /* high-level line-drawing package */ struct pl_line { char *color; char *label; int last_y; timeval last_time; PLOTTER plotter; Bool labelled; }; PLINE new_line( PLOTTER plotter, char *label, char *color) { struct pl_line *pl; pl = MallocZ(sizeof(struct pl_line)); pl->plotter = plotter; pl->label = label; pl->color = color; return(pl); } void extend_line( PLINE pline, timeval xval, int yval) { PLOTTER p; if (!pline) return; p = pline->plotter; #ifdef OLD /* attach a label to the first non-zero point */ if (!pline->labelled) { if (yval != 0) { plotter_temp_color(p, pline->color); plotter_text(p, xval, yval, "l", pline->label); pline->labelled = 1; } } #endif /* attach a label midway on the first line segment above 0 */ /* for whom the second point is NOT 0 */ if (!pline->labelled) { if ((yval != 0) && (!ZERO_TIME(&pline->last_time))) { timeval tv_elapsed; timeval tv_avg; int avg_yval; /* computer elapsed time for these 2 points */ tv_elapsed = xval; tv_sub(&tv_elapsed,pline->last_time); /* divide elapsed time by 2 */ tv_elapsed.tv_sec /= 2; tv_elapsed.tv_usec /= 2; /* add 1/2 of the elapsed time to the oldest point */ /* (giving us the average time) */ tv_avg = pline->last_time; tv_add(&tv_avg, tv_elapsed); /* average the Y values */ avg_yval = (1 + pline->last_y+yval)/2; /* (rounding UP, please) */ /* draw the label */ plotter_temp_color(p, pline->color); plotter_text(p, tv_avg, avg_yval, "l", pline->label); /* remember that it's been done */ pline->labelled = 1; } } /* draw a dot at the current point */ plotter_perm_color(p, pline->color); plotter_dot(p, xval, yval); /* if this isn't the FIRST point, connect with a line */ if (!ZERO_TIME(&pline->last_time)) { plotter_line(p, xval, yval, pline->last_time, pline->last_y); } /* remember this point for the next line segment */ pline->last_time = xval; pline->last_y = yval; } /* This function may be called with 0, 2 or 4 arguments depending on plot command. */ static void CallDoPlot( PLOTTER pl, char *plot_cmd, int plot_argc, ...) { struct timeval t1; u_long x1 = 0; struct timeval t2; u_long x2 = 0; va_list ap; struct plotter_info *ppi; char fmt[200]; if (pl == NO_PLOTTER) return; if (pl > plotter_ix) { fprintf(stderr,"Illegal plotter: %d\n", pl); exit(-1); } ppi = &pplotters[pl]; /* Get the arguments from the variable list */ va_start(ap, plot_argc); if(plot_argc > 0) { t1 = va_arg(ap, struct timeval); x1 = va_arg(ap, u_long); } if(plot_argc > 2) { t2 = va_arg(ap, struct timeval); x2 = va_arg(ap, u_long); } va_end(ap); memset(fmt, 0, sizeof(fmt)); if(ppi->axis_switched) { switch(plot_argc) { case 0: snprintf(fmt, sizeof(fmt), "%s", plot_cmd); DoPlot(pl, fmt); break; case 2: snprintf(fmt, sizeof(fmt), "%s %%u -%%s", plot_cmd); DoPlot(pl, fmt, x1, xp_timestamp(pl,t1)); break; case 4: snprintf(fmt, sizeof(fmt), "%s %%u -%%s %%u -%%s", plot_cmd); DoPlot(pl, fmt, x1, xp_timestamp(pl,t1), x2, xp_timestamp(pl,t2)); break; default: fprintf(stderr, "CallDoPlot: Illegal number of arguments (%d)\n", plot_argc); } } else { switch(plot_argc) { case 0: snprintf(fmt, sizeof(fmt), "%s", plot_cmd); DoPlot(pl, fmt); break; case 2: snprintf(fmt, sizeof(fmt), "%s %%s %%u", plot_cmd); DoPlot(pl, fmt, xp_timestamp(pl,t1), x1); break; case 4: snprintf(fmt, sizeof(fmt), "%s %%s %%u %%s %%u", plot_cmd); DoPlot(pl, fmt, xp_timestamp(pl,t1), x1, xp_timestamp(pl,t2), x2); break; default: fprintf(stderr, "CallDoPlot: Illegal number of arguments (%d)\n", plot_argc); } } return; } static void WritePlotHeader( PLOTTER pl) { MFILE *f = NULL; struct plotter_info *ppi; if (pl == NO_PLOTTER) return; if (pl > plotter_ix) { fprintf(stderr,"Illegal plotter: %d\n", pl); exit(-1); } ppi = &pplotters[pl]; if ((f = ppi->fplot) == NULL) return; if(ppi->axis_switched) { /* Header for the Time Line Charts */ Mfprintf(f,"%s %s\n", "unsigned", "dtime"); } else { /* Header for all other plots */ /* graph coordinates... */ /* X coord is timeval unless graph_time_zero is true */ /* Y is signed except when it's a sequence number */ /* ugly hack -- unsigned makes the graphs hard to work with and is only needed for the time sequence graphs */ /* suggestion by Michele Clark at UNC - make them double instead */ Mfprintf(f,"%s %s\n", graph_time_zero?"dtime":"timeval", ((strcmp(ppi->ylabel,"sequence number") == 0)&&(!graph_seq_zero))? "double":"signed"); } if (show_title) { if (xplot_title_prefix) Mfprintf(f,"title\n%s %s\n", ExpandFormat(xplot_title_prefix), ppi->title); else Mfprintf(f,"title\n%s\n", ppi->title); } Mfprintf(f,"xlabel\n%s\n", ppi->xlabel); Mfprintf(f,"ylabel\n%s\n", ppi->ylabel); /* Indicate that the header has now been written to the plotter file */ ppi->header_done = TRUE; return; } /* Switch the x and y axis type (Needed for Time Line Charts. Default = FLASE) */ void plotter_switch_axis( PLOTTER pl, Bool flag) { struct plotter_info *ppi = &pplotters[pl]; ppi->axis_switched = flag; }