/* io_stat.c * io_stat 2002 Ronnie Sahlberg * * $Id: io_stat.c,v 1.58 2004/01/21 03:54:30 ulfl Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #include #include #include "gtkglobals.h" #include "epan/epan_dissect.h" #include "epan/packet_info.h" #include "menu.h" #include "../tap.h" #include "../register.h" #include "simple_dialog.h" #include "../globals.h" #include "../color.h" #include "compat_macros.h" /* filter prefs dialog */ #include "filter_prefs.h" /* filter prefs dialog */ void protect_thread_critical_region(void); void unprotect_thread_critical_region(void); #define MAX_GRAPHS 5 #define MAX_YSCALE 22 #define AUTO_MAX_YSCALE 0 static guint32 yscale_max[MAX_YSCALE] = {AUTO_MAX_YSCALE, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000, 5000000, 10000000, 20000000, 50000000}; #define MAX_PIXELS_PER_TICK 4 #define DEFAULT_PIXELS_PER_TICK 2 static guint32 pixels_per_tick[MAX_PIXELS_PER_TICK] = {1, 2, 5, 10}; #define DEFAULT_PLOT_STYLE 0 #define PLOT_STYLE_LINE 0 #define PLOT_STYLE_IMPULSE 1 #define PLOT_STYLE_FILLED_BAR 2 #define MAX_PLOT_STYLES 3 static char *plot_style_name[MAX_PLOT_STYLES] = { "Line", "Impulse", "FBar", }; #define COUNT_TYPE_FRAMES 0 #define COUNT_TYPE_BYTES 1 #define COUNT_TYPE_ADVANCED 2 #define MAX_COUNT_TYPES 3 static char *count_type_names[MAX_COUNT_TYPES] = {"frames/tick", "bytes/tick", "advanced..."}; /* unit is in ms */ #define MAX_TICK_VALUES 5 #define DEFAULT_TICK_VALUE 3 static guint tick_interval_values[MAX_TICK_VALUES] = { 1, 10, 100, 1000, 10000 }; #define CALC_TYPE_SUM 0 #define CALC_TYPE_COUNT 1 #define CALC_TYPE_MAX 2 #define CALC_TYPE_MIN 3 #define CALC_TYPE_AVG 4 #define CALC_TYPE_LOAD 5 #define MAX_CALC_TYPES 6 static char *calc_type_names[MAX_CALC_TYPES] = {"SUM(*)", "COUNT(*)", "MAX(*)", "MIN(*)", "AVG(*)", "LOAD(*)"}; typedef struct _io_stat_calc_type_t { struct _io_stat_graph_t *gio; int calc_type; } io_stat_calc_type_t; #define NUM_IO_ITEMS 100000 typedef struct _io_item_t { guint32 frames; /* always calculated, will hold number of frames*/ guint32 bytes; /* always calculated, will hold number of bytes*/ gint32 int_max; gint32 int_min; gint32 int_tot; nstime_t time_max; nstime_t time_min; nstime_t time_tot; } io_item_t; typedef struct _io_stat_graph_t { struct _io_stat_t *io; io_item_t items[NUM_IO_ITEMS]; int plot_style; gboolean display; GtkWidget *display_button; GtkWidget *color_button; GtkWidget *filter_field; GtkWidget *advanced_buttons; int calc_type; io_stat_calc_type_t calc_types[MAX_CALC_TYPES]; int hf_index; GtkWidget *calc_field; GdkColor color; GdkGC *gc; construct_args_t *args; GtkWidget *filter_bt; } io_stat_graph_t; typedef struct _io_stat_t { gboolean needs_redraw; gint32 interval; /* measurement interval in ms */ guint32 last_interval; guint32 max_interval; /* XXX max_interval and num_items are redundant */ guint32 num_items; struct _io_stat_graph_t graphs[MAX_GRAPHS]; GtkWidget *window; GtkWidget *draw_area; GdkPixmap *pixmap; GtkAdjustment *scrollbar_adjustment; GtkWidget *scrollbar; int pixmap_width; int pixmap_height; int pixels_per_tick; int max_y_units; int count_type; } io_stat_t; #if GTK_MAJOR_VERSION < 2 GtkRcStyle *rc_style; GdkColormap *colormap; #endif static void init_io_stat_window(io_stat_t *io); static void io_stat_set_title(io_stat_t *io) { char *title; if(!io->window){ return; } title = g_strdup_printf("IO-Stat: %s", cf_get_display_name(&cfile)); gtk_window_set_title(GTK_WINDOW(io->window), title); g_free(title); } static void io_stat_reset(io_stat_t *io) { int i, j; io->needs_redraw=TRUE; for(i=0;igraphs[i].items[j]; ioi->frames=0; ioi->bytes=0; ioi->int_max=0; ioi->int_min=0; ioi->int_tot=0; ioi->time_max.secs=0; ioi->time_max.nsecs=0; ioi->time_min.secs=0; ioi->time_min.nsecs=0; ioi->time_tot.secs=0; ioi->time_tot.nsecs=0; } } io->last_interval=0xffffffff; io->max_interval=0; io->num_items=0; io_stat_set_title(io); } static void gtk_iostat_reset(void *g) { io_stat_graph_t *gio=g; io_stat_reset(gio->io); } static int gtk_iostat_packet(void *g, packet_info *pinfo, epan_dissect_t *edt, void *dummy _U_) { io_stat_graph_t *git=g; io_item_t *it; nstime_t time_delta; int idx; /* we sometimes get called when git is disabled. this is a bug since the tap listener should be removed first */ if(!git->display){ return 0; } git->io->needs_redraw=TRUE; /* * Find which interval this is supposed to to in and store the * interval index as idx */ time_delta.secs=pinfo->fd->rel_secs; time_delta.nsecs=pinfo->fd->rel_usecs*1000; if(time_delta.nsecs<0){ time_delta.secs--; time_delta.nsecs+=1000000000; } if(time_delta.secs<0){ return FALSE; } idx=(time_delta.secs*1000+time_delta.nsecs/1000000)/git->io->interval; /* some sanity checks */ if((idx<0)||(idx>=NUM_IO_ITEMS)){ return FALSE; } /* update num_items */ if((guint32)idx > git->io->num_items){ git->io->num_items=idx; git->io->max_interval=idx*git->io->interval; } /* * Find the appropriate io_item_t structure */ it=&git->items[idx]; /* * For ADVANCED mode we need to keep track of some more stuff * than just frame and byte counts */ if(git->io->count_type==COUNT_TYPE_ADVANCED){ GPtrArray *gp; guint i; gp=proto_get_finfo_ptr_array(edt->tree, git->hf_index); if(!gp){ return FALSE; } /* update the appropriate counters, make sure that if * frames==0 then this is the first seen value so * set any min/max values accordingly */ for(i=0;ilen;i++){ int new_int; nstime_t *new_time; switch(proto_registrar_get_ftype(git->hf_index)){ case FT_UINT8: case FT_UINT16: case FT_UINT24: case FT_UINT32: case FT_INT8: case FT_INT16: case FT_INT24: case FT_INT32: new_int=fvalue_get_integer(&((field_info *)gp->pdata[i])->value); if((new_int>it->int_max)||(it->frames==0)){ it->int_max=new_int; } if((new_intint_min)||(it->frames==0)){ it->int_min=new_int; } it->int_tot+=new_int; break; case FT_RELATIVE_TIME: new_time=fvalue_get(&((field_info *)gp->pdata[0])->value); switch(git->calc_type){ #ifdef G_HAVE_UINT64 guint64 t, pt; /* time in us */ #else guint32 t, pt; #endif int i; case CALC_TYPE_LOAD: /* it is a LOAD calculation of a relative time field. * add the time this call spanned to each * interval it spanned according to its contribution * to that interval. */ t=new_time->secs; t=t*1000000+new_time->nsecs/1000; i=idx; /* handle current interval */ pt=pinfo->fd->rel_secs*1000000+pinfo->fd->rel_usecs; pt=pt%(git->io->interval*1000); if(pt>t){ pt=t; } while(t){ git->items[i].time_tot.nsecs+=pt*1000; if(git->items[i].time_tot.nsecs>1000000000){ git->items[i].time_tot.secs++; git->items[i].time_tot.nsecs-=1000000000; } if(i==0){ break; } i--; t-=pt; if(t > (guint32) (git->io->interval*1000)){ pt=git->io->interval*1000; } else { pt=t; } } break; default: if( (new_time->secs>it->time_max.secs) ||( (new_time->secs==it->time_max.secs) &&(new_time->nsecs>it->time_max.nsecs)) ||(it->frames==0)){ it->time_max.secs=new_time->secs; it->time_max.nsecs=new_time->nsecs; } if( (new_time->secstime_min.secs) ||( (new_time->secs==it->time_min.secs) &&(new_time->nsecstime_min.nsecs)) ||(it->frames==0)){ it->time_min.secs=new_time->secs; it->time_min.nsecs=new_time->nsecs; } it->time_tot.secs+=new_time->secs; it->time_tot.nsecs+=new_time->nsecs; if(it->time_tot.nsecs>=1000000000){ it->time_tot.nsecs-=1000000000; it->time_tot.secs++; } } } } } it->frames++; it->bytes+=pinfo->fd->pkt_len; return TRUE; } static guint32 get_it_value(io_stat_t *io, int graph_id, int idx) { guint32 value=0; int adv_type; io_item_t *it; it=&io->graphs[graph_id].items[idx]; switch(io->count_type){ case COUNT_TYPE_FRAMES: return it->frames; case COUNT_TYPE_BYTES: return it->bytes; } adv_type=proto_registrar_get_ftype(io->graphs[graph_id].hf_index); switch(adv_type){ case FT_NONE: switch(io->graphs[graph_id].calc_type){ case CALC_TYPE_COUNT: value=it->frames; break; default: break; } break; case FT_UINT8: case FT_UINT16: case FT_UINT24: case FT_UINT32: case FT_INT8: case FT_INT16: case FT_INT24: case FT_INT32: switch(io->graphs[graph_id].calc_type){ case CALC_TYPE_SUM: value=it->int_tot; break; case CALC_TYPE_COUNT: value=it->frames; break; case CALC_TYPE_MAX: value=it->int_max; break; case CALC_TYPE_MIN: value=it->int_min; break; case CALC_TYPE_AVG: if(it->frames){ value=it->int_tot/it->frames; } else { value=0; } break; default: break; } break; case FT_RELATIVE_TIME: switch(io->graphs[graph_id].calc_type){ case CALC_TYPE_COUNT: value=it->frames; break; case CALC_TYPE_MAX: value=it->time_max.secs*1000000+it->time_max.nsecs/1000; break; case CALC_TYPE_MIN: value=it->time_min.secs*1000000+it->time_min.nsecs/1000; break; case CALC_TYPE_AVG: if(it->frames){ #ifdef G_HAVE_UINT64 guint64 t; /* time in us */ #else guint32 t; #endif t=it->time_tot.secs; t=t*1000000+it->time_tot.nsecs/1000; value=t/it->frames; } else { value=0; } break; case CALC_TYPE_LOAD: value=(it->time_tot.secs*1000000+it->time_tot.nsecs/1000)/io->interval; break; default: break; } break; default: break; } return value; } static void print_time_scale_string(char *buf, guint32 t) { if(t>=10000000){ sprintf(buf, "%ds",t/1000000); } else if(t>=1000000){ sprintf(buf, "%d.%03ds",t/1000000,(t%1000000)/1000); } else if(t>=10000){ sprintf(buf, "%dms",t/1000); } else if(t>=1000){ sprintf(buf, "%d.%03dms",t/1000,t%1000); } else { sprintf(buf, "%dus",t); } } static void io_stat_draw(io_stat_t *io) { int i; guint32 last_interval, first_interval, interval_delta, delta_multiplier; gint32 current_interval; guint32 left_x_border; guint32 right_x_border; guint32 top_y_border; guint32 bottom_y_border; #if GTK_MAJOR_VERSION < 2 GdkFont *font; #else PangoLayout *layout; #endif guint32 label_width, label_height; guint32 draw_width, draw_height; char label_string[15]; /* new variables */ guint32 num_time_intervals; guint32 max_value; /* max value of seen data */ guint32 max_y; /* max value of the Y scale */ gboolean draw_y_as_time; #if GTK_MAJOR_VERSION <2 font = io->draw_area->style->font; #endif if(!io->needs_redraw){ return; } io->needs_redraw=FALSE; /* * Find the length of the intervals we have data for * so we know how large arrays we need to malloc() */ num_time_intervals=io->num_items; /* if there isnt anything to do, just return */ if(num_time_intervals==0){ return; } num_time_intervals+=1; /* XXX move this check to _packet() */ if(num_time_intervals>NUM_IO_ITEMS){ simple_dialog(ESD_TYPE_WARN, NULL, "IO-Stat error. There are too many entries, bailing out"); return; } /* * find the max value so we can autoscale the y axis */ max_value=0; for(i=0;igraphs[i].display){ continue; } for(idx=0;(guint32) (idx) < num_time_intervals;idx++){ guint32 val; val=get_it_value(io, i, idx); /* keep track of the max value we have encountered */ if(val>max_value){ max_value=val; } } } /* * Clear out old plot */ gdk_draw_rectangle(io->pixmap, io->draw_area->style->white_gc, TRUE, 0, 0, io->draw_area->allocation.width, io->draw_area->allocation.height); /* * Calculate the y scale we should use */ if(io->max_y_units==AUTO_MAX_YSCALE){ max_y=yscale_max[MAX_YSCALE-1]; for(i=MAX_YSCALE-1;i>0;i--){ if(max_valuemax_y_units; } /* * If we use ADVANCED and all the graphs are plotting * either MIN/MAX/AVG of an FT_RELATIVE_TIME field * then we will do some some special processing for the * labels for the Y axis below: * we will append the time unit " s" " ms" or " us" * and we will present the unit in decimal */ draw_y_as_time=FALSE; if(io->count_type==COUNT_TYPE_ADVANCED){ draw_y_as_time=TRUE; for(i=0;igraphs[i].display){ continue; } adv_type=proto_registrar_get_ftype(io->graphs[i].hf_index); switch(adv_type){ case FT_RELATIVE_TIME: switch(io->graphs[i].calc_type){ case CALC_TYPE_MAX: case CALC_TYPE_MIN: case CALC_TYPE_AVG: break; default: draw_y_as_time=FALSE; } break; default: draw_y_as_time=FALSE; } } } /* * Calculate size of borders surrounding the plot * The border on the right side needs to be adjusted depending * on the width of the text labels. For simplicity we assume that the * top y scale label will be the widest one */ if(draw_y_as_time){ print_time_scale_string(label_string, max_y); } else { sprintf(label_string,"%d", max_y); } #if GTK_MAJOR_VERSION < 2 label_width=gdk_string_width(font, label_string); label_height=gdk_string_height(font, label_string); #else layout = gtk_widget_create_pango_layout(io->draw_area, label_string); pango_layout_get_pixel_size(layout, &label_width, &label_height); #endif left_x_border=10; right_x_border=label_width+20; top_y_border=10; bottom_y_border=label_height+20; /* * Calculate the size of the drawing area for the actual plot */ draw_width=io->pixmap_width-right_x_border-left_x_border; draw_height=io->pixmap_height-top_y_border-bottom_y_border; /* * Draw the y axis and labels * (we always draw the y scale with 11 ticks along the axis) */ gdk_draw_line(io->pixmap, io->draw_area->style->black_gc, io->pixmap_width-right_x_border+1, top_y_border, io->pixmap_width-right_x_border+1, io->pixmap_height-bottom_y_border); for(i=0;i<=10;i++){ int xwidth, lwidth; xwidth=5; if(!(i%5)){ /* first, middle and last tick are slightly longer */ xwidth=10; } /* draw the tick */ gdk_draw_line(io->pixmap, io->draw_area->style->black_gc, io->pixmap_width-right_x_border+1, io->pixmap_height-bottom_y_border-draw_height*i/10, io->pixmap_width-right_x_border+1+xwidth, io->pixmap_height-bottom_y_border-draw_height*i/10); /* draw the label */ if(i==10){ if(draw_y_as_time){ print_time_scale_string(label_string, (max_y*i/10)); } else { sprintf(label_string,"%d", max_y*i/10); } #if GTK_MAJOR_VERSION < 2 lwidth=gdk_string_width(font, label_string); gdk_draw_string(io->pixmap, font, io->draw_area->style->black_gc, io->pixmap_width-right_x_border+15+label_width-lwidth, io->pixmap_height-bottom_y_border-draw_height*i/10+label_height/2, label_string); #else pango_layout_set_text(layout, label_string, -1); pango_layout_get_pixel_size(layout, &lwidth, NULL); gdk_draw_layout(io->pixmap, io->draw_area->style->black_gc, io->pixmap_width-right_x_border+15+label_width-lwidth, io->pixmap_height-bottom_y_border-draw_height*i/10-label_height/2, layout); #endif } } /* * if we have not specified the last_interval via the gui, * then just pick the current end of the capture so that is scrolls * nicely when doing live captures */ if(io->last_interval==0xffffffff){ last_interval=io->max_interval; } else { last_interval=io->last_interval; } /*XXX*/ /* plot the x-scale */ gdk_draw_line(io->pixmap, io->draw_area->style->black_gc, left_x_border, io->pixmap_height-bottom_y_border+1, io->pixmap_width-right_x_border+1, io->pixmap_height-bottom_y_border+1); if((last_interval/io->interval)>draw_width/io->pixels_per_tick+1){ first_interval=(last_interval/io->interval)-draw_width/io->pixels_per_tick+1; first_interval*=io->interval; } else { first_interval=0; } interval_delta=1; delta_multiplier=5; while(interval_delta<((last_interval-first_interval)/10)){ interval_delta*=delta_multiplier; if(delta_multiplier==5){ delta_multiplier=2; } else { delta_multiplier=5; } } for(current_interval=last_interval;current_interval>(gint32)first_interval;current_interval=current_interval-io->interval){ int x, xlen; /* if pixels_per_tick is <5, only draw every 10 ticks */ if((io->pixels_per_tick<10) && (current_interval%(10*io->interval))){ continue; } if(current_interval%interval_delta){ xlen=5; } else { xlen=10; } x=draw_width+left_x_border-((last_interval-current_interval)/io->interval)*io->pixels_per_tick; gdk_draw_line(io->pixmap, io->draw_area->style->black_gc, x-1-io->pixels_per_tick/2, io->pixmap_height-bottom_y_border+1, x-1-io->pixels_per_tick/2, io->pixmap_height-bottom_y_border+xlen+1); if(xlen==10){ int lwidth; if(io->interval>=1000){ sprintf(label_string,"%d", current_interval/1000); } else if(io->interval>=100){ sprintf(label_string,"%d.%1d", current_interval/1000,(current_interval/100)%10); } else if(io->interval>=10){ sprintf(label_string,"%d.%2d", current_interval/1000,(current_interval/10)%100); } else { sprintf(label_string,"%d.%3d", current_interval/1000,current_interval%1000); } #if GTK_MAJOR_VERSION < 2 lwidth=gdk_string_width(font, label_string); gdk_draw_string(io->pixmap, font, io->draw_area->style->black_gc, x-1-io->pixels_per_tick/2-lwidth/2, io->pixmap_height-bottom_y_border+15+label_height, label_string); #else pango_layout_set_text(layout, label_string, -1); pango_layout_get_pixel_size(layout, &lwidth, NULL); gdk_draw_layout(io->pixmap, io->draw_area->style->black_gc, x-1-io->pixels_per_tick/2-lwidth/2, io->pixmap_height-bottom_y_border+15, layout); #endif } } #if GTK_MAJOR_VERSION >= 2 g_object_unref(G_OBJECT(layout)); #endif /* * Loop over all graphs and draw them */ for(i=MAX_GRAPHS-1;i>=0;i--){ guint32 interval; guint32 x_pos, y_pos, prev_x_pos, prev_y_pos; if(!io->graphs[i].display){ continue; } /* initialize prev x/y to the low left corner of the graph */ prev_x_pos=draw_width-1-io->pixels_per_tick*((last_interval-first_interval)/io->interval+1)+left_x_border; prev_y_pos=draw_height-1+top_y_border; for(interval=first_interval+io->interval;interval<=last_interval;interval+=io->interval){ guint32 val; x_pos=draw_width-1-io->pixels_per_tick*((last_interval-interval)/io->interval+1)+left_x_border; val=get_it_value(io, i, interval/io->interval); if(val>max_y){ y_pos=0; } else { y_pos=draw_height-1-(val*draw_height)/max_y+top_y_border; } /* dont need to draw anything if the segment * is entirely above the top of the graph */ if( (prev_y_pos==0) && (y_pos==0) ){ prev_y_pos=y_pos; prev_x_pos=x_pos; continue; } switch(io->graphs[i].plot_style){ case PLOT_STYLE_LINE: gdk_draw_line(io->pixmap, io->graphs[i].gc, prev_x_pos, prev_y_pos, x_pos, y_pos); break; case PLOT_STYLE_IMPULSE: if(val){ gdk_draw_line(io->pixmap, io->graphs[i].gc, x_pos, draw_height-1+top_y_border, x_pos, y_pos); } break; case PLOT_STYLE_FILLED_BAR: if(val){ gdk_draw_rectangle(io->pixmap, io->graphs[i].gc, TRUE, x_pos-io->pixels_per_tick/2, draw_height-1-(val*draw_height)/max_y+top_y_border, io->pixels_per_tick, (val*draw_height)/max_y); } break; } prev_y_pos=y_pos; prev_x_pos=x_pos; } } gdk_draw_pixmap(io->draw_area->window, io->draw_area->style->fg_gc[GTK_WIDGET_STATE(io->draw_area)], io->pixmap, 0, 0, 0, 0, io->pixmap_width, io->pixmap_height); /* update the scrollbar */ io->scrollbar_adjustment->upper=(gfloat) io->max_interval; io->scrollbar_adjustment->step_increment=(gfloat) ((last_interval-first_interval)/10); io->scrollbar_adjustment->page_increment=(gfloat) (last_interval-first_interval); if((last_interval-first_interval)*100 < io->max_interval){ io->scrollbar_adjustment->page_size=(gfloat) (io->max_interval/100); } else { io->scrollbar_adjustment->page_size=(gfloat) (last_interval-first_interval); } io->scrollbar_adjustment->value=last_interval-io->scrollbar_adjustment->page_size; gtk_adjustment_changed(io->scrollbar_adjustment); gtk_adjustment_value_changed(io->scrollbar_adjustment); } static void io_stat_redraw(io_stat_t *io) { io->needs_redraw=TRUE; io_stat_draw(io); } static void gtk_iostat_draw(void *g) { io_stat_graph_t *git=g; io_stat_draw(git->io); } static GString * enable_graph(io_stat_graph_t *gio, char *filter) { gio->display=TRUE; return register_tap_listener("frame", gio, filter, gtk_iostat_reset, gtk_iostat_packet, gtk_iostat_draw); } static void disable_graph(io_stat_graph_t *gio) { if (gio->display) { gio->display=FALSE; protect_thread_critical_region(); remove_tap_listener(gio); unprotect_thread_critical_region(); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gio->display_button), FALSE); } } static void gtk_iostat_init(char *optarg _U_) { io_stat_t *io; int i=0; static color_t col[MAX_GRAPHS] = { {0, 0x0000, 0x0000, 0x0000}, {0, 0xffff, 0x0000, 0x0000}, {0, 0x0000, 0xffff, 0x0000}, {0, 0x0000, 0x0000, 0xffff}, {0, 0xffff, 0x5000, 0xffff} }; GString *error_string; io=g_malloc(sizeof(io_stat_t)); io->needs_redraw=TRUE; io->interval=1000; io->window=NULL; io->draw_area=NULL; io->pixmap=NULL; io->scrollbar=NULL; io->scrollbar_adjustment=NULL; io->pixmap_width=500; io->pixmap_height=200; io->pixels_per_tick=5; io->max_y_units=AUTO_MAX_YSCALE; io->count_type=0; io->last_interval=0xffffffff; io->max_interval=0; io->num_items=0; for(i=0;igraphs[i].gc=NULL; io->graphs[i].color.pixel=col[i].pixel; io->graphs[i].color.red=col[i].red; io->graphs[i].color.green=col[i].green; io->graphs[i].color.blue=col[i].blue; io->graphs[i].display=0; io->graphs[i].display_button=NULL; io->graphs[i].color_button=NULL; io->graphs[i].filter_field=NULL; io->graphs[i].advanced_buttons=NULL; io->graphs[i].io=io; io->graphs[i].args=g_malloc(sizeof(construct_args_t)); io->graphs[i].args->title = NULL; io->graphs[i].args->wants_apply_button=TRUE; io->graphs[i].args->activate_on_ok=TRUE; io->graphs[i].filter_bt=NULL; } io_stat_reset(io); error_string=enable_graph(&io->graphs[0], NULL); if(error_string){ fprintf(stderr, "ethereal: Can't attach io_stat tap: %s\n", error_string->str); g_string_free(error_string, TRUE); io->graphs[0].display=0; io->graphs[0].display_button=NULL; io->graphs[0].filter_field=NULL; io->graphs[0].advanced_buttons=NULL; exit(10); } /* build the GUI */ init_io_stat_window(io); retap_packets(&cfile); io_stat_redraw(io); } static gint quit(GtkWidget *widget, GdkEventExpose *event _U_) { int i; io_stat_t *io; io=(io_stat_t *)OBJECT_GET_DATA(widget, "io_stat_t"); for(i=0;igraphs[i].display){ protect_thread_critical_region(); remove_tap_listener(&io->graphs[i]); unprotect_thread_critical_region(); free(io->graphs[i].args->title); io->graphs[i].args->title=NULL; g_free(io->graphs[i].args); io->graphs[i].args=NULL; } } g_free(io); return TRUE; } /* create a new backing pixmap of the appropriate size */ static gint configure_event(GtkWidget *widget, GdkEventConfigure *event _U_) { int i; io_stat_t *io; io=(io_stat_t *)OBJECT_GET_DATA(widget, "io_stat_t"); if(!io){ exit(10); } if(io->pixmap){ gdk_pixmap_unref(io->pixmap); io->pixmap=NULL; } io->pixmap=gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1); io->pixmap_width=widget->allocation.width; io->pixmap_height=widget->allocation.height; gdk_draw_rectangle(io->pixmap, widget->style->white_gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height); /* set up the colors and the GC structs for this pixmap */ for(i=0;igraphs[i].gc=gdk_gc_new(io->pixmap); #if GTK_MAJOR_VERSION < 2 colormap = gtk_widget_get_colormap (widget); if (!gdk_color_alloc (colormap, &io->graphs[i].color)){ g_warning ("Couldn't allocate color"); } gdk_gc_set_foreground(io->graphs[i].gc, &io->graphs[i].color); #else gdk_gc_set_rgb_fg_color(io->graphs[i].gc, &io->graphs[i].color); #endif } io_stat_redraw(io); return TRUE; } static gint scrollbar_changed(GtkWidget *widget _U_, gpointer data) { io_stat_t *io=(io_stat_t *)data; guint32 mi; mi=(guint32) (io->scrollbar_adjustment->value+io->scrollbar_adjustment->page_size); if(io->last_interval==mi){ return TRUE; } if( (io->last_interval==0xffffffff) && (mi==io->max_interval) ){ return TRUE; } io->last_interval=(mi/io->interval)*io->interval; io_stat_redraw(io); return TRUE; } /* redraw the screen from the backing pixmap */ static gint expose_event(GtkWidget *widget, GdkEventExpose *event) { io_stat_t *io; io=(io_stat_t *)OBJECT_GET_DATA(widget, "io_stat_t"); if(!io){ exit(10); } gdk_draw_pixmap(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], io->pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } static void create_draw_area(io_stat_t *io, GtkWidget *box) { io->draw_area=gtk_drawing_area_new(); SIGNAL_CONNECT(io->draw_area, "destroy", quit, io); OBJECT_SET_DATA(io->draw_area, "io_stat_t", io); WIDGET_SET_SIZE(io->draw_area, io->pixmap_width, io->pixmap_height); /* signals needed to handle backing pixmap */ SIGNAL_CONNECT(io->draw_area, "expose_event", expose_event, NULL); SIGNAL_CONNECT(io->draw_area, "configure_event", configure_event, io); gtk_widget_show(io->draw_area); gtk_box_pack_start(GTK_BOX(box), io->draw_area, TRUE, TRUE, 0); /* create the associated scrollbar */ io->scrollbar_adjustment=(GtkAdjustment *)gtk_adjustment_new(0,0,0,0,0,0); io->scrollbar=gtk_hscrollbar_new(io->scrollbar_adjustment); gtk_widget_show(io->scrollbar); gtk_box_pack_start(GTK_BOX(box), io->scrollbar, FALSE, FALSE, 0); SIGNAL_CONNECT(io->scrollbar_adjustment, "value_changed", scrollbar_changed, io); } static void tick_interval_select(GtkWidget *item, gpointer key) { int val; io_stat_t *io; io=(io_stat_t *)key; val=(int)OBJECT_GET_DATA(item, "tick_interval"); io->interval=val; retap_packets(&cfile); io_stat_redraw(io); } static void pixels_per_tick_select(GtkWidget *item, gpointer key) { int val; io_stat_t *io; io=(io_stat_t *)key; val=(int)OBJECT_GET_DATA(item, "pixels_per_tick"); io->pixels_per_tick=val; io_stat_redraw(io); } static void plot_style_select(GtkWidget *item, gpointer key) { int val; io_stat_graph_t *ppt; ppt=(io_stat_graph_t *)key; val=(int)OBJECT_GET_DATA(item, "plot_style"); ppt->plot_style=val; io_stat_redraw(ppt->io); } static void create_pixels_per_tick_menu_items(io_stat_t *io, GtkWidget *menu) { char str[5]; GtkWidget *menu_item; int i; for(i=0;ipixels_per_tick=DEFAULT_PIXELS_PER_TICK; OBJECT_SET_DATA(menu_item, "pixels_per_tick", pixels_per_tick[i]); SIGNAL_CONNECT(menu_item, "activate", pixels_per_tick_select, io); gtk_widget_show(menu_item); gtk_menu_append(GTK_MENU(menu), menu_item); } gtk_menu_set_active(GTK_MENU(menu), DEFAULT_PIXELS_PER_TICK); return; } static void yscale_select(GtkWidget *item, gpointer key) { int val; io_stat_t *io; io=(io_stat_t *)key; val=(int)OBJECT_GET_DATA(item, "yscale_max"); io->max_y_units=val; io_stat_redraw(io); } static void create_tick_interval_menu_items(io_stat_t *io, GtkWidget *menu) { char str[15]; GtkWidget *menu_item; int i; for(i=0;i=1000){ sprintf(str,"%d sec", tick_interval_values[i]/1000); } else if(tick_interval_values[i]>=100){ sprintf(str,"0.%1d sec", (tick_interval_values[i]/100)%10); } else if(tick_interval_values[i]>=10){ sprintf(str,"0.%02d sec", (tick_interval_values[i]/10)%10); } else { sprintf(str,"0.%03d sec", (tick_interval_values[i])%10); } menu_item=gtk_menu_item_new_with_label(str); OBJECT_SET_DATA(menu_item, "tick_interval", tick_interval_values[i]); SIGNAL_CONNECT(menu_item, "activate", tick_interval_select, (gpointer)io); gtk_widget_show(menu_item); gtk_menu_append(GTK_MENU(menu), menu_item); } gtk_menu_set_active(GTK_MENU(menu), DEFAULT_TICK_VALUE); return; } static void create_yscale_max_menu_items(io_stat_t *io, GtkWidget *menu) { char str[15]; GtkWidget *menu_item; int i; for(i=0;icount_type=val; if(io->count_type==COUNT_TYPE_ADVANCED){ int i; for(i=0;igraphs[i]); gtk_widget_show(io->graphs[i].advanced_buttons); /* redraw the entire window so the unhidden widgets show up, hopefully */ {GdkRectangle update_rect; update_rect.x=0; update_rect.y=0; update_rect.width=io->window->allocation.width; update_rect.height=io->window->allocation.height; gtk_widget_draw(io->window, &update_rect); } } } else { int i; for(i=0;igraphs[i].advanced_buttons); } } io_stat_redraw(io); } static void create_frames_or_bytes_menu_items(io_stat_t *io, GtkWidget *menu) { GtkWidget *menu_item; int i; for(i=0;iio->count_type==COUNT_TYPE_ADVANCED){ char *field; field=(char *)gtk_entry_get_text(GTK_ENTRY(gio->calc_field)); /* warn and bail out if there was no field specified */ if(field==NULL || field[0]==0){ simple_dialog(ESD_TYPE_WARN, NULL, "You did not specify a field name."); disable_graph(gio); io_stat_redraw(gio->io); return 0; } /* warn and bail out if the field could not be found */ hfi=proto_registrar_get_byname(field); if(hfi==NULL){ simple_dialog(ESD_TYPE_WARN, NULL, "'%s' is not a valid field name.", field); disable_graph(gio); io_stat_redraw(gio->io); return 0; } gio->hf_index=hfi->id; /* check that the type is compatible */ switch(hfi->type){ case FT_UINT8: case FT_UINT16: case FT_UINT24: case FT_UINT32: case FT_INT8: case FT_INT16: case FT_INT24: case FT_INT32: /* these values support all calculations except LOAD */ switch(gio->calc_type){ case CALC_TYPE_LOAD: simple_dialog(ESD_TYPE_WARN, NULL, "LOAD(*) is only supported for relative-time fields."); disable_graph(gio); io_stat_redraw(gio->io); return 0; } /* these types support all calculations */ break; case FT_RELATIVE_TIME: /* this type only supports COUNT, MAX, MIN, AVG */ switch(gio->calc_type){ case CALC_TYPE_COUNT: case CALC_TYPE_MAX: case CALC_TYPE_MIN: case CALC_TYPE_AVG: case CALC_TYPE_LOAD: break; default: simple_dialog(ESD_TYPE_WARN, NULL, "%s is a relative-time field, so %s calculations are not supported on it.", field, calc_type_names[gio->calc_type]); disable_graph(gio); io_stat_redraw(gio->io); return 0; } break; case FT_UINT64: case FT_INT64: /* * XXX - support this if gint64/guint64 are * available? */ if(gio->calc_type!=CALC_TYPE_COUNT){ simple_dialog(ESD_TYPE_WARN, NULL, "%s is a 64-bit integer, so %s calculations are not supported on it.", field, calc_type_names[gio->calc_type]); disable_graph(gio); io_stat_redraw(gio->io); return 0; } break; default: /* * XXX - support all operations on floating-point * numbers? */ if(gio->calc_type!=CALC_TYPE_COUNT){ simple_dialog(ESD_TYPE_WARN, NULL, "%s doesn't have integral values, so %s calculations are not supported on it.", field, calc_type_names[gio->calc_type]); disable_graph(gio); io_stat_redraw(gio->io); return 0; } break; } } /* first check if the filter string is valid. */ filter=(char *)gtk_entry_get_text(GTK_ENTRY(gio->filter_field)); if(!dfilter_compile(filter, &dfilter)) { simple_dialog(ESD_TYPE_WARN, NULL, "Filter \"%s\" is invalid - %s", filter, dfilter_error_msg); disable_graph(gio); io_stat_redraw(gio->io); return 0; } if (dfilter != NULL) dfilter_free(dfilter); /* this graph is not active, just update display and redraw */ if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gio->display_button))){ disable_graph(gio); io_stat_redraw(gio->io); return 0; } /* ok, we have a valid filter and the graph is active. first just try to delete any previous settings and then apply the new ones. */ protect_thread_critical_region(); remove_tap_listener(gio); unprotect_thread_critical_region(); io_stat_reset(gio->io); enable_graph(gio, filter); retap_packets(&cfile); io_stat_redraw(gio->io); return 0; } static void calc_type_select(GtkWidget *item _U_, gpointer key) { io_stat_calc_type_t *ct=(io_stat_calc_type_t *)key; ct->gio->calc_type=ct->calc_type; /* disable the graph */ disable_graph(ct->gio); io_stat_redraw(ct->gio->io); } static void create_calc_types_menu_items(io_stat_graph_t *gio, GtkWidget *menu) { GtkWidget *menu_item; int i; for(i=0;icalc_types[i].gio=gio; gio->calc_types[i].calc_type=i; menu_item=gtk_menu_item_new_with_label(calc_type_names[i]); SIGNAL_CONNECT(menu_item, "activate", calc_type_select, &gio->calc_types[i]); gtk_widget_show(menu_item); gtk_menu_append(GTK_MENU(menu), menu_item); } return; } static void create_advanced_menu(io_stat_graph_t *gio, GtkWidget *box, char *name, void (*func)(io_stat_graph_t *io, GtkWidget *menu)) { GtkWidget *hbox; GtkWidget *label; GtkWidget *option_menu; GtkWidget *menu; hbox=gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(box), hbox); gtk_box_set_child_packing(GTK_BOX(box), hbox, FALSE, FALSE, 0, GTK_PACK_START); gtk_widget_show(hbox); label=gtk_label_new(name); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); option_menu=gtk_option_menu_new(); menu=gtk_menu_new(); (*func)(gio, menu); gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu); gtk_box_pack_end(GTK_BOX(hbox), option_menu, FALSE, FALSE, 0); gtk_widget_show(option_menu); } static void create_advanced_field(io_stat_graph_t *gio, GtkWidget *box) { gio->calc_field=gtk_entry_new_with_max_length(30); gtk_box_pack_start(GTK_BOX(box), gio->calc_field, FALSE, FALSE, 0); gtk_widget_show(gio->calc_field); SIGNAL_CONNECT(gio->calc_field, "activate", filter_callback, gio); } static void create_advanced_box(io_stat_graph_t *gio, GtkWidget *box) { GtkWidget *hbox; hbox=gtk_hbox_new(FALSE, 0); gio->advanced_buttons=hbox; gtk_container_add(GTK_CONTAINER(box), hbox); gtk_box_set_child_packing(GTK_BOX(box), hbox, FALSE, FALSE, 0, GTK_PACK_START); gtk_widget_hide(hbox); gio->calc_type=CALC_TYPE_SUM; create_advanced_menu(gio, hbox, "Calc:", create_calc_types_menu_items); create_advanced_field(gio, hbox); } static void filter_button_clicked(GtkWidget *w, gpointer uio) { io_stat_graph_t *gio=(io_stat_graph_t *)uio; display_filter_construct_cb(w, gio->args); return; } static void create_filter_box(io_stat_graph_t *gio, GtkWidget *box, int num) { GtkWidget *option_menu; GtkWidget *menu; GtkWidget *menu_item; GtkWidget *hbox; GtkWidget *label; char str[256]; int i; hbox=gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(box), hbox); gtk_box_set_child_packing(GTK_BOX(box), hbox, FALSE, FALSE, 0, GTK_PACK_START); gtk_widget_show(hbox); sprintf(str, "Filter:%d", num); label=gtk_label_new(str); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); gio->display_button=gtk_toggle_button_new(); gtk_box_pack_start(GTK_BOX(hbox), gio->display_button, FALSE, FALSE, 0); gtk_widget_show(gio->display_button); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gio->display_button), gio->display); SIGNAL_CONNECT(gio->display_button, "toggled", filter_callback, gio); gio->color_button=gtk_toggle_button_new(); gtk_box_pack_start(GTK_BOX(hbox), gio->color_button, FALSE, FALSE, 0); gtk_widget_show(gio->color_button); #if GTK_MAJOR_VERSION < 2 rc_style = gtk_rc_style_new (); rc_style->bg[GTK_STATE_NORMAL] = gio->color; rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_BG; rc_style->bg[GTK_STATE_ACTIVE] = gio->color; rc_style->color_flags[GTK_STATE_ACTIVE] |= GTK_RC_BG; rc_style->bg[GTK_STATE_PRELIGHT] = gio->color; rc_style->color_flags[GTK_STATE_PRELIGHT] |= GTK_RC_BG; rc_style->bg[GTK_STATE_SELECTED] = gio->color; rc_style->color_flags[GTK_STATE_SELECTED] |= GTK_RC_BG; rc_style->bg[GTK_STATE_INSENSITIVE] = gio->color; rc_style->color_flags[GTK_STATE_INSENSITIVE] |= GTK_RC_BG; gtk_widget_modify_style (gio->color_button, rc_style); gtk_rc_style_unref (rc_style); #else gtk_widget_modify_bg(gio->color_button, GTK_STATE_NORMAL, &gio->color); gtk_widget_modify_bg(gio->color_button, GTK_STATE_ACTIVE, &gio->color); gtk_widget_modify_bg(gio->color_button, GTK_STATE_PRELIGHT, &gio->color); gtk_widget_modify_bg(gio->color_button, GTK_STATE_SELECTED, &gio->color); gtk_widget_modify_bg(gio->color_button, GTK_STATE_INSENSITIVE, &gio->color); #endif /* gtk_signal_connect(GTK_OBJECT(gio->display_button), "toggled", GTK_SIGNAL_FUNC(filter_callback), gio);*/ /* filter prefs dialog */ label=gtk_label_new(" "); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); gio->filter_bt=BUTTON_NEW_FROM_STOCK(ETHEREAL_STOCK_DISPLAY_FILTER_ENTRY); sprintf(str, "Ethereal: Display Filter IO-Stat (Filter:%d)", num); if(gio->args->title){ free(gio->args->title); } gio->args->title=strdup(str); SIGNAL_CONNECT(gio->filter_bt, "clicked", filter_button_clicked, gio); SIGNAL_CONNECT(gio->filter_bt, "destroy", filter_button_destroy_cb, NULL); gtk_box_pack_start(GTK_BOX(hbox), gio->filter_bt, FALSE, TRUE, 0); gtk_widget_show(gio->filter_bt); gio->filter_field=gtk_entry_new_with_max_length(256); /* filter prefs dialog */ OBJECT_SET_DATA(gio->filter_bt, E_FILT_TE_PTR_KEY, gio->filter_field); /* filter prefs dialog */ gtk_box_pack_start(GTK_BOX(hbox), gio->filter_field, FALSE, FALSE, 0); gtk_widget_show(gio->filter_field); SIGNAL_CONNECT(gio->filter_field, "activate", filter_callback, gio); create_advanced_box(gio, hbox); /* * create PlotStyle menu */ sprintf(str, " Style:"); label=gtk_label_new(str); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); option_menu=gtk_option_menu_new(); menu=gtk_menu_new(); for(i=0;iio->graphs[num-1]); gtk_widget_show(menu_item); gtk_menu_append(GTK_MENU(menu), menu_item); } gtk_menu_set_active(GTK_MENU(menu), DEFAULT_PLOT_STYLE); gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu); gtk_box_pack_end(GTK_BOX(hbox), option_menu, FALSE, FALSE, 0); gtk_widget_show(option_menu); return; } static void create_filter_area(io_stat_t *io, GtkWidget *box) { GtkWidget *vbox; int i; vbox=gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(box), vbox); gtk_box_set_child_packing(GTK_BOX(box), vbox, FALSE, FALSE, 0, GTK_PACK_START); gtk_widget_show(vbox); for(i=0;igraphs[i], vbox, i+1); } return; } static void init_io_stat_window(io_stat_t *io) { GtkWidget *vbox; GtkWidget *hbox; /* create the main window */ io->window=gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_widget_set_name(io->window, "I/O Statistics"); vbox=gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(io->window), vbox); gtk_widget_show(vbox); create_draw_area(io, vbox); hbox=gtk_hbox_new(FALSE, 0); gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); gtk_box_set_child_packing(GTK_BOX(vbox), hbox, FALSE, FALSE, 0, GTK_PACK_START); gtk_widget_show(hbox); create_filter_area(io, hbox); create_ctrl_area(io, hbox); io_stat_set_title(io); gtk_widget_show(io->window); } static void gtk_iostat_cb(GtkWidget *w _U_, gpointer d _U_) { gtk_iostat_init(NULL); } void register_tap_listener_gtk_iostat(void) { register_ethereal_tap("io,stat", gtk_iostat_init); } void register_tap_menu_gtkiostat(void) { register_tap_menu_item("_Statistics/IO/IO-Stat", gtk_iostat_cb, NULL, NULL, NULL); }