From 60d6b05e2340ae90c09fbdd2f25b6513131a0bd1 Mon Sep 17 00:00:00 2001 From: Michael Mann Date: Fri, 29 Nov 2013 22:47:59 +0000 Subject: [PATCH] Stats_tree enhancements for sorting, averages and burst rate. Bug 9452 (https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=9452) From Deon van der Westhuysen - Bug fix: object leak in stats_tree after a tap reset (for example apply statistics preferences with a stats_tree window open) - Bug fix: correct sample code in README.stats_tree - Add: slash in plug-in name now creates submenu as docs describe (was a bug?) - Add: menu separator before the stat_tree registered plug-ins - Add: stats_tree can now calculate averages for nodes; automatically calculated for range nodes. Add section in README.stats_tree describing averages. - Add: stats_tree can now calculate burst rate of each node (like rate but with a shorter, sliding time window) - Add: sorting for stats_tree plug-ins. Can sort on node name, count, average, min, max values and burst rate. - Add: preferences for stats_tree system (default sort column, burst calc params) - Add: stats_tree window copy to clipboard and export and plain text, csv and XML. - Added sample of new functionality in $srcdir/plugins/stats_tree/pinfo_stats_tree.c - Moved all stats_tree sample plug-ins to "IP Statistics" submenu. svn path=/trunk/; revision=53657 --- AUTHORS | 3 +- doc/README.stats_tree | 37 +- epan/prefs.c | 105 ++++- epan/prefs.h | 14 + epan/stats_tree.c | 654 +++++++++++++++++++++++++- epan/stats_tree.h | 49 +- epan/stats_tree_priv.h | 84 +++- image/file_dlg_win32.rc | 6 + plugins/stats_tree/pinfo_stats_tree.c | 43 +- ui/cli/tap-stats_tree.c | 19 +- ui/gtk/main_menubar.c | 50 +- ui/gtk/stats_tree_stat.c | 355 +++++++++++--- ui/win32/file_dlg_win32.c | 87 ++++ ui/win32/file_dlg_win32.h | 11 + 14 files changed, 1399 insertions(+), 118 deletions(-) diff --git a/AUTHORS b/AUTHORS index 561ba4b6ea..e17435fd49 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3792,7 +3792,8 @@ Masayuki Takemura Ed Beroset e.yimjia Jonathon Jongsma -zeljko +Zeljko Ancimer +Deon van der Westhuysen Dan Lasley gave permission for his dumpit() hex-dump routine to be used. diff --git a/doc/README.stats_tree b/doc/README.stats_tree index b5b24e42e5..dcaf91cf6a 100644 --- a/doc/README.stats_tree +++ b/doc/README.stats_tree @@ -95,7 +95,7 @@ extern int udp_term_stats_tree_packet(stats_tree *st, /* st as it was passed to e_udphdr* udphdr = (e_udphdr*) p; /* we increment by one (tick) the root node */ - stats_tree_tick_node(st, st_udp_term, 0, FALSE); + tick_stat_node(st, st_str_udp_term, 0, FALSE); /* we then tick a node for this src_addr:src_port if the node doesn't exists it will be created */ @@ -192,6 +192,41 @@ sets the value of a stat_node zero_stat_node(st,name,parent_id,with_children) resets to zero a stat_node +Averages work by tracking both the number of items added to node (the ticking +action) and the value of each item added to the node. This is done +automatically for ranged nodes; for other node types you need to call one of +the functions below to associate item values with each tick. + +avg_stat_node_add_value_notick(st,name,parent_id,with_children,value) +avg_stat_node_add_value(st,name,parent_id,with_children,value) + +The difference between the above functions is whether the item count is +increased or not. To properly compute the average you need to either call +avg_stat_node_add_value or avg_stat_node_add_value_notick combined +tick_stat_node. The later sequence allows for plug-ins which are compatible +with older wireshark versions which ignores avg_stat_node_add_value because +it does not understand the command. This would result in 0 counts for all +nodes. It is preferred to use avg_stat_node_add_value if you are not writing +a plug-in. + +avg_stat_node_add_value is used the same way as tick_stat_node with the +exception that you now specify an additional value associated with the tick. + +Do not mix increase_stat_node, set_stat_node or zero_stat_node +with avg_stat_node_add_value as this will lead to incorrect results for the +average value. + +stats_tree now also support setting flags per node to control the behaviour +of these nodes. This can be done using the stat_node_set_flags and +stat_node_clear_flags functions. Currently these flags are defined: + + ST_FLG_DEF_NOEXPAND: By default the top-level nodes in a tree are + automatically expanded in the GUI. Setting this flag on + such a node prevents the node from automatically expanding. + ST_FLG_SORT_TOP: Nodes with this flag is sorted separately from nodes + without this flag (in effect partitioning tree into a top and + bottom half. Each half is sorted normally. Top always appear + first :) You can find more examples of these in $srcdir/plugins/stats_tree/pinfo_stats_tree.c diff --git a/epan/prefs.c b/epan/prefs.c index ab55672406..ea4c40b0b2 100644 --- a/epan/prefs.c +++ b/epan/prefs.c @@ -58,6 +58,7 @@ #include "epan/filter_expressions.h" #include "epan/wmem/wmem.h" +#include /* Internal functions */ static module_t *find_subtree(module_t *parent, const char *tilte); @@ -1256,6 +1257,16 @@ static const enum_val_t gui_qt_language[] = { {NULL, NULL, -1} }; +static const enum_val_t st_sort_col_vals[] = { + { "name", "Node name (topic/item)", ST_SORT_COL_NAME }, + { "count", "Item count", ST_SORT_COL_COUNT }, + { "average", "Average value of the node", ST_SORT_COL_AVG }, + { "min", "Minimum value of the node", ST_SORT_COL_MIN }, + { "max", "Maximum value of the node", ST_SORT_COL_MAX }, + { "burst", "Burst rate of the node", ST_SORT_COL_BURSTRATE }, + { NULL, NULL, 0 } +}; + static void stats_callback(void) { @@ -1269,6 +1280,22 @@ stats_callback(void) prefs.rtp_player_max_visible = RTP_PLAYER_DEFAULT_VISIBLE; #endif + /* burst resolution can't be less than 1 (ms) */ + if (prefs.st_burst_resolution < 1) { + prefs.st_burst_resolution = 1; + } + else if (prefs.st_burst_resolution > ST_MAX_BURSTRES) { + prefs.st_burst_resolution = ST_MAX_BURSTRES; + } + /* make sure burst window value makes sense */ + if (prefs.st_burst_windowlen < prefs.st_burst_resolution) { + prefs.st_burst_windowlen = prefs.st_burst_resolution; + } + /* round burst window down to multiple of resolution */ + prefs.st_burst_windowlen -= prefs.st_burst_windowlen%prefs.st_burst_resolution; + if ((prefs.st_burst_windowlen/prefs.st_burst_resolution) > ST_MAX_BURSTBUCKETS) { + prefs.st_burst_windowlen = prefs.st_burst_resolution*ST_MAX_BURSTBUCKETS; + } } static void @@ -2461,6 +2488,73 @@ prefs_register_modules(void) &prefs.rtp_player_max_visible); #endif + prefs_register_bool_preference(stats_module, "st_enable_burstinfo", + "Enable the calculation of burst information", + "If enabled burst rates will be calcuted for statistics that use the stats_tree system. " + "Burst rates are calculated over a much shorter time interval than the rate column.", + &prefs.st_enable_burstinfo); + + prefs_register_bool_preference(stats_module, "st_burst_showcount", + "Show burst count for item rather than rate", + "If selected the stats_tree statistics nodes will show the count of events " + "within the burst window instead of a burst rate. Burst rate is calculated " + "as number of events within burst window divided by the burst windown length.", + &prefs.st_burst_showcount); + + prefs_register_uint_preference(stats_module, "st_burst_resolution", + "Burst rate resolution (ms)", + "Sets the duration of the time interval into which events are grouped when calculating " + "the burst rate. Higher resolution (smaller number) increases processing overhead.", + 10,&prefs.st_burst_resolution); + + prefs_register_uint_preference(stats_module, "st_burst_windowlen", + "Burst rate window size (ms)", + "Sets the duration of the sliding window during which the burst rate is " + "measured. Longer window relative to burst rate resolution increases " + "processing overhead. Will be truncated to a multiple of burst resolution.", + 10,&prefs.st_burst_windowlen); + + prefs_register_enum_preference(stats_module, "st_sort_defcolflag", + "Default sort column for stats_tree stats", + "Sets the default column by which stats based on the stats_tree " + "system is sorted.", + &prefs.st_sort_defcolflag, st_sort_col_vals, FALSE); + + prefs_register_bool_preference(stats_module, "st_sort_defdescending", + "Default stats_tree sort order is descending", + "When selected, statistics based on the stats_tree system will by default " + "be sorted in descending order.", + &prefs.st_sort_defdescending); + + prefs_register_bool_preference(stats_module, "st_sort_casesensitve", + "Case sensitive sort of stats_tree item names", + "When selected, the item/node names of statistics based on the stats_tree " + "system will be sorted taking case into account. Else the case of the name " + "will be ignored.", + &prefs.st_sort_casesensitve); + + prefs_register_bool_preference(stats_module, "st_sort_rng_nameonly", + "Always sort 'range' nodes by name", + "When selected, the stats_tree nodes representing a range of values " + "(0-49, 50-100, etc.) will always be sorted by name (the range of the " + "node). Else range nodes are sorted by the same column as the rest of " + " the tree.", + &prefs.st_sort_rng_nameonly); + + prefs_register_bool_preference(stats_module, "st_sort_rng_fixorder", + "Always sort 'range' nodes in ascending order", + "When selected, the stats_tree nodes representing a range of values " + "(0-49, 50-100, etc.) will always be sorted ascending; else it follows " + "the sort direction of the tree. Only effective if \"Always sort " + "'range' nodes by name\" is also selected.", + &prefs.st_sort_rng_fixorder); + + prefs_register_bool_preference(stats_module, "st_sort_showfullname", + "Display the full stats_tree plug-in name", + "When selected, the full name (including menu path) of the stats_tree " + "plug-in is show in windows. If cleared the plug-in name is shown " + "without menu path (only the part of the name after last '/' character.)", + &prefs.st_sort_showfullname); /* Protocols */ protocols_module = prefs_register_module(NULL, "protocols", "Protocols", @@ -2903,7 +2997,16 @@ pre_init_prefs(void) /* set the default values for the tap/statistics dialog box */ prefs.tap_update_interval = TAP_UPDATE_DEFAULT_INTERVAL; prefs.rtp_player_max_visible = RTP_PLAYER_DEFAULT_VISIBLE; - + prefs.st_enable_burstinfo = TRUE; + prefs.st_burst_showcount = FALSE; + prefs.st_burst_resolution = ST_DEF_BURSTRES; + prefs.st_burst_windowlen = ST_DEF_BURSTLEN; + prefs.st_sort_casesensitve = TRUE; + prefs.st_sort_rng_fixorder = TRUE; + prefs.st_sort_rng_nameonly = TRUE; + prefs.st_sort_defcolflag = ST_SORT_COL_COUNT; + prefs.st_sort_defdescending = TRUE; + prefs.st_sort_showfullname = FALSE; prefs.display_hidden_proto_items = FALSE; prefs_pre_initialized = TRUE; diff --git a/epan/prefs.h b/epan/prefs.h index 9b9b1a1000..729ece2a4c 100644 --- a/epan/prefs.h +++ b/epan/prefs.h @@ -50,6 +50,10 @@ extern "C" { #define RTP_PLAYER_DEFAULT_VISIBLE 4 #define TAP_UPDATE_DEFAULT_INTERVAL 3000 +#define ST_DEF_BURSTRES 5 +#define ST_DEF_BURSTLEN 100 +#define ST_MAX_BURSTRES 600000 /* somewhat arbirary limit of 10 minutes */ +#define ST_MAX_BURSTBUCKETS 100 /* somewhat arbirary limit - more buckets degrade performance */ /* * Convert a string listing name resolution types to a bitmask of @@ -208,6 +212,16 @@ typedef struct _e_prefs { gboolean unknown_colorfilters; /* unknown or obsolete color filter(s) */ guint gui_qt_language; /* Qt Translation language selection */ gboolean gui_packet_editor; /* Enable Packet Editor */ + gboolean st_enable_burstinfo; + gboolean st_burst_showcount; + gint st_burst_resolution; + gint st_burst_windowlen; + gboolean st_sort_casesensitve; + gboolean st_sort_rng_fixorder; + gboolean st_sort_rng_nameonly; + gint st_sort_defcolflag; + gboolean st_sort_defdescending; + gboolean st_sort_showfullname; } e_prefs; WS_DLL_PUBLIC e_prefs prefs; diff --git a/epan/stats_tree.c b/epan/stats_tree.c index a49f33357d..d1db527485 100644 --- a/epan/stats_tree.c +++ b/epan/stats_tree.c @@ -23,28 +23,47 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + /* stats_tree modifications by Deon van der Westhuysen, November 2013 + * support for + * - sorting by column, + * - calculation of average values + * - calculation of burst rate + * - export to text, CSV or XML file + */ + #include "config.h" #include +#include #include #include +#include +#include #include #include "stats_tree.h" -/* -TODO: - - sort out the sorting issue - - */ +enum _stat_tree_columns { + COL_NAME, + COL_COUNT, + COL_AVERAGE, + COL_MIN, + COL_MAX, + COL_RATE, + COL_PERCENT, + COL_BURSTRATE, + COL_BURSTTIME, + N_COLUMNS +}; /* used to contain the registered stat trees */ static GHashTable *registry = NULL; /* writes into the buffers pointed by value, rate and percent the string representations of a node*/ +/*** DEPRECIATED ***/ extern void stats_tree_get_strs_from_node(const stat_node *node, gchar *value, gchar *rate, gchar *percent) { @@ -99,7 +118,14 @@ stats_tree_branch_max_namelen(const stat_node *node, guint indent) } } + if (node->st_flags&ST_FLG_ROOTCHILD) { + gchar *display_name= stats_tree_get_displayname(node->name); + len = (guint) strlen(display_name) + indent; + g_free(display_name); + } + else { len = (guint) strlen(node->name) + indent; + } maxlen = len > maxlen ? len : maxlen; return maxlen; @@ -109,6 +135,7 @@ static gchar *format; /* populates the given GString with a tree representation of a branch given by node, using indent spaces as initial indentation */ +/*** DEPRECIATED ***/ extern void stats_tree_branch_to_str(const stat_node *node, GString *s, guint indent) { @@ -157,6 +184,7 @@ free_stat_node(stat_node *node) { stat_node *child; stat_node *next; + burst_bucket *bucket; if (node->children) { for (child = node->children; child; child = next ) { @@ -170,6 +198,12 @@ free_stat_node(stat_node *node) if (node->hash) g_hash_table_destroy(node->hash); + while (node->bh) { + bucket = node->bh; + node->bh = bucket->next; + g_free(bucket); + } + g_free(node->rng); g_free(node->name); g_free(node); @@ -185,6 +219,7 @@ stats_tree_free(stats_tree *st) g_free(st->filter); g_hash_table_destroy(st->names); g_ptr_array_free(st->parents,TRUE); + g_free(st->display_name); for (child = st->root.children; child; child = next ) { /* child->next will be gone after free_stat_node, so cache it here */ @@ -207,14 +242,30 @@ static void reset_stat_node(stat_node *node) { stat_node *child; + burst_bucket *bucket; + + node->counter = 0; + node->total = 0; + node->minvalue = G_MAXINT; + node->maxvalue = G_MININT; + node->st_flags = 0; + + while (node->bh) { + bucket = node->bh; + node->bh = bucket->next; + g_free(bucket); + } + node->bh = (burst_bucket*)g_malloc0(sizeof(burst_bucket)); + node->bt = node->bh; + node->bcount = 0; + node->max_burst = 0; + node->burst_time = -1.0; if (node->children) { for (child = node->children; child; child = child->next ) reset_stat_node(child); } - node->counter = 0; - if(node->st->cfg->reset_node) { node->st->cfg->reset_node(node); } @@ -229,6 +280,7 @@ stats_tree_reset(void *p) st->start = -1.0; st->elapsed = 0.0; + st->now = - 1.0; reset_stat_node(&st->root); @@ -252,6 +304,27 @@ stats_tree_reinit(void *p) st->root.children = NULL; st->root.counter = 0; + st->root.total = 0; + st->root.minvalue = G_MAXINT; + st->root.maxvalue = G_MININT; + st->root.st_flags = 0; + + st->root.bh = (burst_bucket*)g_malloc0(sizeof(burst_bucket)); + st->root.bt = st->root.bh; + st->root.bcount = 0; + st->root.max_burst = 0; + st->root.burst_time = -1.0; + + /* No more stat_nodes left in tree - clean out hash, array */ + g_hash_table_remove_all(st->names); + if (st->parents->len>1) { + g_ptr_array_remove_range(st->parents, 1, st->parents->len-1); + } + + /* Do not update st_flags for the tree (sorting) - leave as was */ + st->num_columns = N_COLUMNS; + g_free(st->display_name); + st->display_name= stats_tree_get_displayname(st->cfg->name); if (st->cfg->init) { st->cfg->init(st); @@ -265,7 +338,6 @@ stats_tree_register_with_group(const char *tapname, const char *abbr, const char stat_tree_packet_cb packet, stat_tree_init_cb init, stat_tree_cleanup_cb cleanup, register_stat_group_t stat_group) { - stats_tree_cfg *cfg = (stats_tree_cfg *)g_malloc( sizeof(stats_tree_cfg) ); /* at the very least the abbrev and the packet function should be given */ @@ -281,7 +353,8 @@ stats_tree_register_with_group(const char *tapname, const char *abbr, const char cfg->init = init; cfg->cleanup = cleanup; - cfg->flags = flags; + cfg->flags = flags&~ST_FLG_MASK; + cfg->st_flags = flags&ST_FLG_MASK; /* these have to be filled in by implementations */ cfg->setup_node_pr = NULL; @@ -344,7 +417,18 @@ stats_tree_new(stats_tree_cfg *cfg, tree_pres *pr, const char *filter) st->elapsed = 0.0; st->root.counter = 0; - st->root.name = g_strdup(cfg->name); + st->root.total = 0; + st->root.minvalue = G_MAXINT; + st->root.maxvalue = G_MININT; + st->root.st_flags = 0; + + st->root.bh = (burst_bucket*)g_malloc0(sizeof(burst_bucket)); + st->root.bt = st->root.bh; + st->root.bcount = 0; + st->root.max_burst = 0; + st->root.burst_time = -1.0; + + st->root.name = stats_tree_get_displayname(cfg->name); st->root.st = st; st->root.parent = NULL; st->root.children = NULL; @@ -352,6 +436,18 @@ stats_tree_new(stats_tree_cfg *cfg, tree_pres *pr, const char *filter) st->root.hash = NULL; st->root.pr = NULL; + st->st_flags = st->cfg->st_flags; + + if (!(st->st_flags&ST_FLG_SRTCOL_MASK)) { + /* No default sort specified - use preferences */ + st->st_flags |= prefs.st_sort_defcolflag<st_flags |= ST_FLG_SORT_DESC; + } + } + st->num_columns = N_COLUMNS; + st->display_name= stats_tree_get_displayname(st->cfg->name); + g_ptr_array_add(st->parents,&st->root); return st; @@ -362,11 +458,11 @@ extern int stats_tree_packet(void *p, packet_info *pinfo, epan_dissect_t *edt, const void *pri) { stats_tree *st = (stats_tree *)p; - double now = nstime_to_msec(&pinfo->rel_ts); - if (st->start < 0.0) st->start = now; + st->now = nstime_to_msec(&pinfo->rel_ts); + if (st->start < 0.0) st->start = st->now; - st->elapsed = now - st->start; + st->elapsed = st->now - st->start; if (st->cfg->packet) return st->cfg->packet(st,pinfo,edt,pri); @@ -461,6 +557,17 @@ new_stat_node(stats_tree *st, const gchar *name, int parent_id, stat_node *last_chld = NULL; node->counter = 0; + node->total = 0; + node->minvalue = G_MAXINT; + node->maxvalue = G_MININT; + node->st_flags = parent_id?0:ST_FLG_ROOTCHILD; + + node->bh = (burst_bucket*)g_malloc0(sizeof(burst_bucket)); + node->bt = node->bh; + node->bcount = 0; + node->max_burst = 0; + node->burst_time = -1; + node->name = g_strdup(name); node->children = NULL; node->next = NULL; @@ -535,7 +642,92 @@ stats_tree_create_node_by_pname(stats_tree *st, const gchar *name, return stats_tree_create_node(st,name,stats_tree_parent_id_by_name(st,parent_name),with_children); } +/* Internal function to update the burst calculation data - add entry to bucket */ +static void +update_burst_calc(stat_node *node, gint value) +{ + double current_bucket; + double burstwin; + burst_bucket *bn; + + if (!prefs.st_enable_burstinfo) { + return; + } + + /* NB thebucket list should always contain at least one node - even if it is */ + /* the dummy created at init time. Head and tail should never be NULL! */ + current_bucket= floor(node->st->now/prefs.st_burst_resolution); + burstwin= prefs.st_burst_windowlen/prefs.st_burst_resolution; + if (current_bucket>node->bt->bucket_no) { + /* Must add a new bucket at the burst list tail */ + bn = (burst_bucket*)g_malloc0(sizeof(burst_bucket)); + bn->count = value; + bn->bucket_no = current_bucket; + bn->start_time = node->st->now; + bn->prev = node->bt; + node->bt->next = bn; + node->bt = bn; + /* And add value to the current burst count for node */ + node->bcount += value; + /* Check if bucket list head is now too old and must be removed */ + while (current_bucket>=(node->bh->bucket_no+burstwin)) { + /* off with its head! */ + bn = node->bh; + node->bh = bn->next; + node->bh->prev = NULL; + node->bcount -= bn->count; + g_free(bn); + } + } + else if (current_bucketbh->bucket_no) { + /* Packet must be added at head of burst list - check if not too old */ + if ((current_bucket+burstwin)>node->bt->bucket_no) { + /* packet still within the window */ + bn = (burst_bucket*)g_malloc0(sizeof(burst_bucket)); + bn->count = value; + bn->bucket_no = current_bucket; + bn->start_time = node->st->now; + bn->next = node->bh; + node->bh->prev = bn; + node->bh = bn; + /* And add value to the current burst count for node */ + node->bcount += value; + } + } + else + { + /* Somewhere in the middle... */ + burst_bucket *search = node->bt; + while (current_bucketbucket_no) { + search = search->prev; + } + if (current_bucket==search->bucket_no) { + /* found existing bucket, increase value */ + search->count += value; + if (search->start_time>node->st->now) { + search->start_time = node->st->now; + } + } + else { + /* must add a new bucket after bn. */ + bn = (burst_bucket*)g_malloc0(sizeof(burst_bucket)); + bn->count = value; + bn->bucket_no = current_bucket; + bn->start_time = node->st->now; + bn->prev = search; + bn->next = search->next; + search->next = bn; + bn->next->prev = bn; + } + node->bcount += value; + } + if (node->bcount>node->max_burst) { + /* new record burst */ + node->max_burst = node->bcount; + node->burst_time = node->bh->start_time; + } +} /* * Increases by delta the counter of the node whose name is given @@ -564,8 +756,31 @@ stats_tree_manip_node(manip_node_mode mode, stats_tree *st, const char *name, node = new_stat_node(st,name,parent_id,with_hash,with_hash); switch (mode) { - case MN_INCREASE: node->counter += value; break; + case MN_INCREASE: + node->counter += value; + update_burst_calc(node, value); + break; case MN_SET: node->counter = value; break; + case MN_AVERAGE: + node->counter++; + update_burst_calc(node, 1); + /* fall through to average code */ + case MN_AVERAGE_NOTICK: + node->total += value; + if (node->minvalue > value) { + node->minvalue = value; + } + if (node->maxvalue < value) { + node->maxvalue = value; + } + node->st_flags |= ST_FLG_AVERAGE; + break; + case MN_SET_FLAGS: + node->st_flags |= value; + break; + case MN_CLEAR_FLAGS: + node->st_flags &= ~value; + break; } if (node) @@ -746,12 +961,31 @@ stats_tree_tick_range(stats_tree *st, const gchar *name, int parent_id, if ( node == NULL ) g_assert_not_reached(); + /* update stats for container node. counter should already be ticked so we only update total and min/max */ + node->total += value_in_range; + if (node->minvalue > value_in_range) { + node->minvalue = value_in_range; + } + if (node->maxvalue < value_in_range) { + node->maxvalue = value_in_range; + } + node->st_flags |= ST_FLG_AVERAGE; + for ( child = node->children; child; child = child->next) { floor = child->rng->floor; ceil = child->rng->ceil; if ( value_in_range >= floor && value_in_range <= ceil ) { child->counter++; + child->total += value_in_range; + if (child->minvalue > value_in_range) { + child->minvalue = value_in_range; + } + if (child->maxvalue < value_in_range) { + child->maxvalue = value_in_range; + } + child->st_flags |= ST_FLG_AVERAGE; + update_burst_calc(child, 1); return node->id; } } @@ -788,12 +1022,402 @@ stats_tree_create_pivot_by_pname(stats_tree *st, const gchar *name, extern int stats_tree_tick_pivot(stats_tree *st, int pivot_id, const gchar *pivot_value) { - stat_node *parent = (stat_node *)g_ptr_array_index(st->parents,pivot_id); parent->counter++; + update_burst_calc(parent, 1); stats_tree_manip_node( MN_INCREASE, st, pivot_value, pivot_id, FALSE, 1); return pivot_id; } +extern gchar* +stats_tree_get_displayname (gchar* fullname) +{ + gchar *buf = g_strdup(fullname); + gchar *sep; + + if (prefs.st_sort_showfullname) { + return buf; /* unmodifed */ + } + + sep = buf; + while (sep= strchr(sep,'/')) { + if (*(++sep)=='/') { /* escapeded slash - two slash characters after each other */ + memmove(sep,sep+1,strlen(sep)); + } + else { + /* we got a new path separator */ + memmove(buf,sep,strlen(sep)+1); + sep = buf; + } + } + + return buf; +} + +extern gint +stats_tree_get_default_sort_col (stats_tree *st) +{ + switch ((st->st_flags&ST_FLG_SRTCOL_MASK)>>ST_FLG_SRTCOL_SHIFT) { + case ST_SORT_COL_NAME: return COL_NAME; + case ST_SORT_COL_COUNT: return COL_COUNT; + case ST_SORT_COL_AVG: return COL_AVERAGE; + case ST_SORT_COL_MIN: return COL_MIN; + case ST_SORT_COL_MAX: return COL_MAX; + case ST_SORT_COL_BURSTRATE: return COL_BURSTRATE; + } + return COL_COUNT; /* nothing specific set */ +} + +extern gboolean +stats_tree_is_default_sort_DESC (stats_tree *st) +{ + return st->st_flags&ST_FLG_SORT_DESC; +} + +extern gchar* +stats_tree_get_column_name (gint index) +{ + switch (index) { + case COL_NAME: return "Topic / Item"; + case COL_COUNT: return "Count"; + case COL_AVERAGE: return "Average"; + case COL_MIN: return "Min val"; + case COL_MAX: return "Max val"; + case COL_RATE: return "Rate (ms)"; + case COL_PERCENT: return "Percent"; + case COL_BURSTRATE: return prefs.st_burst_showcount?"Burst count":"Burst rate"; + case COL_BURSTTIME: return "Burst start"; + default: return "(Unknown)"; + } +} + +extern gint +stats_tree_get_column_size (gint index) +{ + if (index==COL_NAME) { + return 36; /* but caller should really call stats_tree_branch_max_namelen() */ + } + if (indexst->num_columns)); + + values[COL_NAME]= (node->st_flags&ST_FLG_ROOTCHILD)?stats_tree_get_displayname(node->name):g_strdup(node->name); + values[COL_COUNT]= g_strdup_printf("%u",node->counter); + values[COL_AVERAGE]= ((node->st_flags&ST_FLG_AVERAGE)||node->rng)? + (node->counter?g_strdup_printf("%.2f",((float)node->total)/node->counter):g_strdup("-")): + g_strdup(""); + values[COL_MIN]= ((node->st_flags&ST_FLG_AVERAGE)||node->rng)? + (node->counter?g_strdup_printf("%u",node->minvalue):g_strdup("-")): + g_strdup(""); + values[COL_MAX]= ((node->st_flags&ST_FLG_AVERAGE)||node->rng)? + (node->counter?g_strdup_printf("%u",node->maxvalue):g_strdup("-")): + g_strdup(""); + values[COL_RATE]= (node->st->elapsed)?g_strdup_printf("%.4f",((float)node->counter)/node->st->elapsed):g_strdup(""); + values[COL_PERCENT]= ((node->parent)&&(node->parent->counter))? + g_strdup_printf("%.2f%%",(node->counter*100.0)/node->parent->counter): + (node->parent==&(node->st->root)?g_strdup("100%"):g_strdup("")); + if (node->st->num_columns>COL_BURSTTIME) { + values[COL_BURSTRATE]= (!prefs.st_enable_burstinfo)?g_strdup(""): + (node->max_burst?(prefs.st_burst_showcount? + g_strdup_printf("%d",node->max_burst): + g_strdup_printf("%.4f",((double)node->max_burst)/prefs.st_burst_windowlen)): + g_strdup("-")); + values[COL_BURSTTIME]= (!prefs.st_enable_burstinfo)?g_strdup(""): + (node->max_burst?g_strdup_printf("%.3f",((double)node->burst_time/1000.0)):g_strdup("-")); + } + return values; +} + +extern gint +stats_tree_sort_compare (const stat_node *a, const stat_node *b, gint sort_column, + gboolean sort_descending) +{ + int result; + float avg_a, avg_b; + + if (prefs.st_sort_rng_nameonly&&(a->rng&&b->rng)) { + /* always sort ranges by range name */ + result = a->rng->floor - b->rng->floor; + if (sort_descending&&(!prefs.st_sort_rng_fixorder)) { + result= -result; + } + return result; + } + + switch (sort_column) + { + case COL_NAME: if (a->rng&&b->rng) { + result = a->rng->floor - b->rng->floor; + } + else if (prefs.st_sort_casesensitve) { + result = strcmp(a->name,b->name); + } + else { + result = g_ascii_strcasecmp(a->name,b->name); + } + break; + + case COL_COUNT: result = a->counter - b->counter; + break; + + case COL_AVERAGE: if (a->counter) { + result= 1; /* assume a>b */ + if (b->counter) { + avg_a= ((float)a->total)/a->counter; + avg_b= ((float)b->total)/b->counter; + result= (avg_a>avg_b)?1:((avg_aa */ + } + break; + + case COL_MIN: result = a->minvalue - b->minvalue; + break; + + case COL_MAX: result = a->maxvalue - b->maxvalue; + break; + + case COL_BURSTRATE: result = a->max_burst - b->max_burst; + } + + /* break tie between items with same primary search result */ + if (!result) { + if (sort_column==COL_NAME) { + result = a->counter - b->counter; + } + else { + if (a->rng&&b->rng) { + result = a->rng->floor - b->rng->floor; + } + else if (prefs.st_sort_casesensitve) { + result = strcmp(a->name,b->name); + } + else { + result = g_ascii_strcasecmp(a->name,b->name); + } + } + } + + /* take into account sort order */ + if (sort_descending) { + result= -result; + } + + if ((a->st_flags&ST_FLG_SORT_TOP)!=(b->st_flags&ST_FLG_SORT_TOP)) { + /* different sort groups top vs non-top */ + result= (a->st_flags&ST_FLG_SORT_TOP)?-1:1; + } + return result; +} + +extern GString* +stats_tree_format_as_str(const stats_tree* st, guint format, + gint sort_column, gboolean sort_descending) +{ + int maxnamelen= stats_tree_branch_max_namelen(&st->root,0); + stat_node *child; + GString *s; + int count; + gchar *separator; + + if (format==ST_FORMAT_XML) { + s = g_string_new("\n"); + } + else if (format==ST_FORMAT_CSV) { + s = g_string_new("\"level\",\"parent\","); + for (count = 0; countnum_columns; count++) { + g_string_append_printf(s,"\"%s\",",stats_tree_get_column_name(count)); + } + g_string_append (s,"\n"); + } + else if (format==ST_FORMAT_PLAIN) { + char fmt[16]; + int sep_length; + + sep_length= maxnamelen; + for (count = 1; countnum_columns; count++) { + sep_length += stats_tree_get_column_size(count)+2; + } + separator = g_malloc(sep_length+1); + memset (separator, '=', sep_length); + separator[sep_length] = 0; + + s = g_string_new("\n"); + g_string_append(s,separator); + g_string_append_printf(s,"\n%s:\n",st->cfg->name); + g_sprintf (fmt,"%%-%us",maxnamelen); + g_string_append_printf(s,fmt,stats_tree_get_column_name(0)); + for (count = 1; countnum_columns; count++) { + g_sprintf (fmt," %%-%us",stats_tree_get_column_size(count)+1); + g_string_append_printf(s,fmt,stats_tree_get_column_name(count)); + } + memset (separator, '-', sep_length); + g_string_append_printf(s,"\n%s\n",separator); + } + else { + return g_string_new("unknown format for stats_tree\n"); + } + + for (child = st->root.children; child; child = child->next ) { + stats_tree_format_node_as_str(child,s,format,0,"",maxnamelen,sort_column,sort_descending); + + } + + if (format==ST_FORMAT_PLAIN) { + g_string_append_printf(s,"\n%s\n",separator); + g_free(separator); + } + + return s; +} + +typedef struct { + gint sort_column; + gboolean sort_descending; +} sortinfo; + +/* Function to compare elements for child array sort. a and b are children, user_data +points to a st_flags value */ +extern gint +stat_node_array_sortcmp (gconstpointer a, gconstpointer b, gpointer user_data) +{ + /* user_data is *guint value to st_flags */ + return stats_tree_sort_compare (*(stat_node**)a,*(stat_node**)b, + ((sortinfo*)user_data)->sort_column,((sortinfo*)user_data)->sort_descending); +}; + +static gchar* +clean_for_xml_tag (gchar *str) +{ + gchar *s = str; + while (s=strpbrk(s,"!\"#$%%&'()*+,/;<=>?@[\\]^`{|}~ ")) { + *(s++) = '-'; + } + return str; +} + +static GString* +escape_xml_chars (gchar *str) +{ + GString *s= g_string_new(""); + while (1) { + switch (*str) { + case 0: return s; + case '<': g_string_append(s,"<"); + break; + case '>': g_string_append(s,">"); + break; + case '&': g_string_append(s,"&"); + break; + case '\'': g_string_append(s,"'"); + break; + case '"': g_string_append(s,"""); + break; + default: g_string_append_c(s,*str); + break; + } + str++; + } + + return s; +} +/** helper funcation to add note to formatted stats_tree */ +WS_DLL_PUBLIC void stats_tree_format_node_as_str(const stat_node *node, GString *s, + guint format, guint indent, gchar *path, gint maxnamelen, gint sort_column, + gboolean sort_descending) +{ + int count; + int num_columns= node->st->num_columns; + gchar **values= stats_tree_get_values_from_node(node); + stat_node *child; + sortinfo si; + + if (format==ST_FORMAT_XML) { + GString *itemname= escape_xml_chars(values[0]); + g_string_append_printf(s,"\n",itemname->str, + node->rng?" isrange=\"true\"":""); + g_string_free(itemname,TRUE); + g_string_append(s,""); + for (count = 1; count",clean_for_xml_tag(colname)); + g_string_append_printf(s,"%s\n",values[count],colname); + g_free(colname); + } + } + else if (format==ST_FORMAT_CSV) { + g_string_append_printf(s,"%d,\"%s\",\"%s\"",indent,path,values[0]); + for (count = 1; count INDENT_MAX ? INDENT_MAX : indent; + path= g_strdup_printf ("%s/%s",path,values[0]); + + for (count = 0; countchildren) { + GArray *Children= g_array_new(FALSE,FALSE,sizeof(child)); + for (child = node->children; child; child = child->next ) { + g_array_append_val(Children,child); + } + si.sort_column = sort_column; + si.sort_descending = sort_descending; + g_array_sort_with_data(Children,stat_node_array_sortcmp,&si); + for (count = 0; count<((int)Children->len); count++) { + stats_tree_format_node_as_str(g_array_index(Children,stat_node*,count), s, format, + indent, path, maxnamelen, sort_column, sort_descending); + } + g_array_free(Children,FALSE); + } + g_free(path); + + if (format==ST_FORMAT_XML) { + g_string_append(s,"\n"); + } +} + + + diff --git a/epan/stats_tree.h b/epan/stats_tree.h index 06db45f1dc..23e2c8175b 100644 --- a/epan/stats_tree.h +++ b/epan/stats_tree.h @@ -35,6 +35,24 @@ #define STAT_TREE_ROOT "root" +#define ST_FLG_AVERAGE 0x10000000 /* Calculate overages for nodes, rather than totals */ +#define ST_FLG_ROOTCHILD 0x20000000 /* This node is a direct child of the root node */ +#define ST_FLG_DEF_NOEXPAND 0x01000000 /* This node should not be expanded by default */ +#define ST_FLG_SORT_DESC 0x00800000 /* When sorting, sort ascending instead of decending */ +#define ST_FLG_SORT_TOP 0x00400000 /* When sorting always keep these lines on of list */ +#define ST_FLG_SRTCOL_MASK 0x000F0000 /* Mask for sort column ID */ +#define ST_FLG_SRTCOL_SHIFT 16 /* Number of bits to shift masked result */ + +#define ST_FLG_MASK (ST_FLG_AVERAGE|ST_FLG_ROOTCHILD|ST_FLG_DEF_NOEXPAND|\ + ST_FLG_SORT_TOP|ST_FLG_SORT_DESC|ST_FLG_SRTCOL_MASK) + +#define ST_SORT_COL_NAME 1 /* Sort nodes by node names */ +#define ST_SORT_COL_COUNT 2 /* Sort nodes by node count */ +#define ST_SORT_COL_AVG 3 /* Sort nodes by node average */ +#define ST_SORT_COL_MIN 4 /* Sort nodes by minimum node value */ +#define ST_SORT_COL_MAX 5 /* Sort nodes by maximum node value */ +#define ST_SORT_COL_BURSTRATE 6 /* Sort nodes by burst rate */ + /* obscure information regarding the stats_tree */ typedef struct _stats_tree stats_tree; @@ -168,7 +186,14 @@ WS_DLL_PUBLIC int stats_tree_tick_pivot(stats_tree *st, * using parent_name as parent node (NULL for root). * with_children=TRUE to indicate that the created node will be a parent */ -typedef enum _manip_node_mode { MN_INCREASE, MN_SET } manip_node_mode; +typedef enum _manip_node_mode { + MN_INCREASE, + MN_SET, + MN_AVERAGE, + MN_AVERAGE_NOTICK, + MN_SET_FLAGS, + MN_CLEAR_FLAGS +} manip_node_mode; WS_DLL_PUBLIC int stats_tree_manip_node(manip_node_mode mode, stats_tree *st, const gchar *name, @@ -188,4 +213,26 @@ WS_DLL_PUBLIC int stats_tree_manip_node(manip_node_mode mode, #define zero_stat_node(st,name,parent_id,with_children) \ (stats_tree_manip_node(MN_SET,(st),(name),(parent_id),(with_children),0)) +/* + * Add value to average calculation WITHOUT ticking node. Node MUST be ticked separately! + * + * Intention is to allow code to separately tick node (backward compatibility for plugin) + * and set value to use for averages. Older versions without average support will then at + * least show a count instead of 0. + */ +#define avg_stat_node_add_value_notick(st,name,parent_id,with_children,value) \ +(stats_tree_manip_node(MN_AVERAGE_NOTICK,(st),(name),(parent_id),(with_children),value)) + +/* Tick node and add a new value to the average calculation for this stats node. */ +#define avg_stat_node_add_value(st,name,parent_id,with_children,value) \ +(stats_tree_manip_node(MN_AVERAGE,(st),(name),(parent_id),(with_children),value)) + +/* Set flags for this node. Node created if it does not yet exist. */ +#define stat_node_set_flags(st,name,parent_id,with_children,flags) \ +(stats_tree_manip_node(MN_SET_FLAGS,(st),(name),(parent_id),(with_children),flags)) + +/* Clear flags for this node. Node created if it does not yet exist. */ +#define stat_node_clear_flags(st,name,parent_id,with_children,flags) \ +(stats_tree_manip_node(MN_CLEAR_FLAGS,(st),(name),(parent_id),(with_children),flags)) + #endif /* __STATS_TREE_H */ diff --git a/epan/stats_tree_priv.h b/epan/stats_tree_priv.h index ce817e6634..a6790e495d 100644 --- a/epan/stats_tree_priv.h +++ b/epan/stats_tree_priv.h @@ -57,12 +57,32 @@ typedef struct _range_pair { gint ceil; } range_pair_t; +typedef struct _burst_bucket burst_bucket; +struct _burst_bucket { + burst_bucket *next; + burst_bucket *prev; + gint count; + double bucket_no; + double start_time; +}; + struct _stat_node { gchar* name; int id; /** the counter it keeps */ gint counter; + /** total of all values submitted - for computing averages */ + gint64 total; + gint minvalue; + gint maxvalue; + int st_flags; + + /** fields for burst rate calculation */ + gint bcount; + burst_bucket *bh, *bt; + gint max_burst; + double burst_time; /** children nodes by name */ GHashTable *hash; @@ -91,6 +111,11 @@ struct _stats_tree { /* times */ double start; double elapsed; + double now; + + int st_flags; + gint num_columns; + gchar *display_name; /** used to lookup named parents: * key: parent node name @@ -152,6 +177,9 @@ struct _stats_tree_cfg { void (*free_tree_pr)(stats_tree*); void (*draw_tree)(stats_tree*); void (*reset_tree)(stats_tree*); + + /** flags for the stats tree (sorting etc.) default values to new trees */ + guint st_flags; }; /* guess what, this is it! */ @@ -193,7 +221,9 @@ WS_DLL_PUBLIC GList *stats_tree_get_cfg_list(void); /** extracts node data as strings from a stat_node into the buffers given by value, rate and precent - if NULL they are ignored */ + if NULL they are ignored + + DO NOT USE FOR NEW CODE. Use stats_tree_get_values_from_node() instead */ WS_DLL_PUBLIC void stats_tree_get_strs_from_node(const stat_node *node, gchar *value, gchar *rate, @@ -217,4 +247,56 @@ WS_DLL_PUBLIC gchar *stats_tree_node_to_str(const stat_node *node, } #endif /* __cplusplus */ +/** get the display name for the stats_tree (or node name) based on the + st_sort_showfullname preference. If not set remove everything before + last unescaped backslash. Caller must free the result */ +WS_DLL_PUBLIC gchar* stats_tree_get_displayname (gchar* fullname); + +/** returns the column number of the default column to sort on */ +WS_DLL_PUBLIC gint stats_tree_get_default_sort_col (stats_tree *st); + +/** returns the default sort order to use */ +WS_DLL_PUBLIC gboolean stats_tree_is_default_sort_DESC (stats_tree *st); + +/** returns the column name for a given column index */ +WS_DLL_PUBLIC gchar* stats_tree_get_column_name (gint index); + +/** returns the maximum number of characters in the value of a column */ +WS_DLL_PUBLIC gint stats_tree_get_column_size (gint index); + +/** returns TRUE is the the column name for a given column index can be sorted*/ +WS_DLL_PUBLIC gboolean stats_tree_is_sortable_column (gint index); + +/** returns the formatted column values for the current node + as array of gchar*. Caller must free entries and free array */ +WS_DLL_PUBLIC gchar** stats_tree_get_values_from_node (const stat_node* node); + +/** function to compare two nodes for sort, based on sort_column. */ +WS_DLL_PUBLIC gint stats_tree_sort_compare (const stat_node *a, + const stat_node *b, + gint sort_column, + gboolean sort_descending); + +/** wrapper for stats_tree_sort_compare() function that can be called from array sort. */ +WS_DLL_PUBLIC gint stat_node_array_sortcmp (gconstpointer a, + gconstpointer b, + gpointer user_data); + +/** function to copy stats_tree into GString. format deternmines output format */ +typedef enum _st_format_type { ST_FORMAT_PLAIN, ST_FORMAT_CSV, ST_FORMAT_XML}; +WS_DLL_PUBLIC GString* stats_tree_format_as_str(const stats_tree* st, + guint format, + gint sort_column, + gboolean sort_descending); + +/** helper funcation to add note to formatted stats_tree */ +WS_DLL_PUBLIC void stats_tree_format_node_as_str(const stat_node *node, + GString *s, + guint format, + guint indent, + gchar *path, + gint maxnamelen, + gint sort_column, + gboolean sort_descending); + #endif /* __STATS_TREE_PRIV_H */ diff --git a/image/file_dlg_win32.rc b/image/file_dlg_win32.rc index 32320cae06..f500718c27 100644 --- a/image/file_dlg_win32.rc +++ b/image/file_dlg_win32.rc @@ -37,6 +37,12 @@ FONT 8, "MS Shell Dlg" CHECKBOX "Compress with gzip", EWFD_GZIP_CB, 67, 0, 100, 8, BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP } +WIRESHARK_SAVEASSTATSTREENAME_TEMPLATE DIALOGEX 0, 0, 167, 0 +STYLE WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | DS_3DLOOK | DS_CONTROL +FONT 8, "MS Shell Dlg" +{ +} + WIRESHARK_EXPORT_SPECIFIED_PACKETS_FILENAME_TEMPLATE DIALOGEX 0, 0, 453, 109 STYLE WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | DS_3DLOOK | DS_CONTROL FONT 8, "MS Shell Dlg" diff --git a/plugins/stats_tree/pinfo_stats_tree.c b/plugins/stats_tree/pinfo_stats_tree.c index 97948fff75..1ee31eb0ad 100644 --- a/plugins/stats_tree/pinfo_stats_tree.c +++ b/plugins/stats_tree/pinfo_stats_tree.c @@ -81,7 +81,7 @@ uat_plen_record_update_cb(void *r, const char **err) } static void uat_plen_record_free_cb(void*r) { - uat_plen_record_t* record = (uat_plen_record_t*)r; + uat_plen_record_t* record = (uat_plen_record_t*)r; if (record->packet_range) g_free(record->packet_range); @@ -108,7 +108,7 @@ UAT_RANGE_CB_DEF(uat_plen_records, packet_range, uat_plen_record_t) /* ip host stats_tree -- basic test */ static int st_node_ip = -1; -static const gchar* st_str_ip = "IP Addresses"; +static const gchar* st_str_ip = "IP Statistics/IP Addresses"; static void ip_hosts_stats_tree_init(stats_tree* st) { st_node_ip = stats_tree_create_node(st, st_str_ip, 0, TRUE); @@ -122,9 +122,38 @@ static int ip_hosts_stats_tree_packet(stats_tree *st , packet_info *pinfo, epan return 1; } +/* ip host stats_tree -- separate source and dest, test stats_tree flags */ +static int st_node_ip_src = -1; +static int st_node_ip_dst = -1; +static const gchar* st_str_ip_srcdst = "IP Statistics/Source and Dest IP Addresses"; +static const gchar* st_str_ip_src = "Source IP Addresses"; +static const gchar* st_str_ip_dst = "Destination IP Addresses"; + +static void ip_srcdst_stats_tree_init(stats_tree* st) { + /* create one tree branch for source */ + st_node_ip_src = stats_tree_create_node(st, st_str_ip_src, 0, TRUE); + /* set flag so this branch will always be sorted to top of tree */ + stat_node_set_flags(st, st_str_ip_src, 0, FALSE, ST_FLG_SORT_TOP); + /* creat another top level node for destination branch */ + st_node_ip_dst = stats_tree_create_node(st, st_str_ip_dst, 0, TRUE); + /* set flag so this branch will not be expanded by default */ + stat_node_set_flags(st, st_str_ip_dst, 0, FALSE, ST_FLG_DEF_NOEXPAND); +} + +static int ip_srcdst_stats_tree_packet(stats_tree *st , packet_info *pinfo, epan_dissect_t *edt _U_, const void *p _U_) { + /* update source branch */ + tick_stat_node(st, st_str_ip_src, 0, FALSE); + tick_stat_node(st, ep_address_to_str(&pinfo->net_src), st_node_ip_src, FALSE); + /* update destination branch */ + tick_stat_node(st, st_str_ip_dst, 0, FALSE); + tick_stat_node(st, ep_address_to_str(&pinfo->net_dst), st_node_ip_dst, FALSE); + + return 1; +} + /* packet type stats_tree -- test pivot node */ static int st_node_ptype = -1; -static const gchar* st_str_ptype = "IP Protocol Types"; +static const gchar* st_str_ptype = "IP Statistics/IP Protocol Types"; static void ptype_stats_tree_init(stats_tree* st) { st_node_ptype = stats_tree_create_pivot(st, st_str_ptype, 0); @@ -158,6 +187,11 @@ static void plen_stats_tree_init(stats_tree* st) { static int plen_stats_tree_packet(stats_tree* st, packet_info* pinfo, epan_dissect_t *edt _U_, const void *p _U_) { tick_stat_node(st, st_str_plen, 0, FALSE); + /* also add value for averages calculation. we call the notick version of */ + /* avg_stat_node_add_value and call tick_stat_node separately. this allows */ + /* compatiblity with older wireshark versions with no average support. */ + avg_stat_node_add_value_notick(st, st_str_plen, 0, FALSE, pinfo->fd->pkt_len); + stats_tree_tick_range(st, st_str_plen, 0, pinfo->fd->pkt_len); return 1; @@ -170,7 +204,7 @@ static int plen_stats_tree_packet(stats_tree* st, packet_info* pinfo, epan_disse */ static int st_node_dsts = -1; -static const gchar* st_str_dsts = "IP Destinations"; +static const gchar* st_str_dsts = "IP Statistics/IP Destinations"; static void dsts_stats_tree_init(stats_tree* st) { st_node_dsts = stats_tree_create_node(st, st_str_dsts, 0, TRUE); @@ -203,6 +237,7 @@ void register_pinfo_stat_trees(void) { }; stats_tree_register_plugin("ip","ip_hosts",st_str_ip, 0, ip_hosts_stats_tree_packet, ip_hosts_stats_tree_init, NULL ); + stats_tree_register_plugin("ip","ip_srcdst",st_str_ip_srcdst, 0, ip_srcdst_stats_tree_packet, ip_srcdst_stats_tree_init, NULL ); stats_tree_register_plugin("ip","ptype",st_str_ptype, 0, ptype_stats_tree_packet, ptype_stats_tree_init, NULL ); stats_tree_register_with_group("frame","plen",st_str_plen, 0, plen_stats_tree_packet, plen_stats_tree_init, NULL, REGISTER_STAT_GROUP_GENERIC ); stats_tree_register_plugin("ip","dests",st_str_dsts, 0, dsts_stats_tree_packet, dsts_stats_tree_init, NULL ); diff --git a/ui/cli/tap-stats_tree.c b/ui/cli/tap-stats_tree.c index 7e893c39af..345e898579 100644 --- a/ui/cli/tap-stats_tree.c +++ b/ui/cli/tap-stats_tree.c @@ -54,23 +54,12 @@ draw_stats_tree(void *psp) { stats_tree *st = (stats_tree *)psp; GString *s; - gchar *fmt; - stat_node *child; - - s = g_string_new("\n===================================================================\n"); - fmt = g_strdup_printf(" %%s%%-%us%%12s\t%%12s\t%%12s\n",stats_tree_branch_max_namelen(&st->root,0)); - g_string_append_printf(s,fmt,"",st->cfg->name,"value","rate","percent"); - g_free(fmt); - g_string_append_printf(s,"-------------------------------------------------------------------\n"); - - for (child = st->root.children; child; child = child->next ) { - stats_tree_branch_to_str(child,s,0); - } - - s = g_string_append(s,"\n===================================================================\n"); + s= stats_tree_format_as_str(st, ST_FORMAT_PLAIN, stats_tree_get_default_sort_col(st), + stats_tree_is_default_sort_DESC(st)); + printf("%s",s->str); - + g_string_free(s,TRUE); } static void diff --git a/ui/gtk/main_menubar.c b/ui/gtk/main_menubar.c index b10cf12964..7d3bf54402 100644 --- a/ui/gtk/main_menubar.c +++ b/ui/gtk/main_menubar.c @@ -1253,6 +1253,7 @@ static const char *ui_desc_menubar = " \n" " \n" " \n" +" \n" " \n" " \n" " \n" @@ -4221,6 +4222,11 @@ add_tap_plugins (guint merge_id, GtkUIManager *ui_manager) GList *iter; gchar *action_name; + gchar *submenu_path; + gchar *stat_name_buf; + gchar *stat_name; + gchar *sep; + action_group = gtk_action_group_new ("tap-plugins-group"); submenu_statistics = gtk_ui_manager_get_widget(ui_manager_main_menubar, MENU_STATISTICS_PATH); @@ -4237,23 +4243,61 @@ add_tap_plugins (guint merge_id, GtkUIManager *ui_manager) while (iter) { stats_tree_cfg *cfg = (stats_tree_cfg*)iter->data; if (cfg->plugin) { - action_name = g_strdup_printf(MENU_STATISTICS_PATH "/%s", cfg->abbr); + stat_name_buf = g_strdup(cfg->name); + submenu_path = g_malloc(strlen(MENU_STATISTICS_PATH)+strlen(cfg->name)+strlen(cfg->abbr)+1); /* worst case length */ + strcpy(submenu_path, MENU_STATISTICS_PATH); + + sep= stat_name= stat_name_buf; + while (sep= strchr(sep,'/')) { + if (*(++sep)=='/') { /* escapeded slash - two slash characters after each other */ + memmove(sep,sep+1,strlen(sep)); + } + else { + /* we got a new submenu name - ignore the edge case where there is no text following this slash */ + *(sep-1)= 0; + action_name = g_strdup_printf("%s/%s", submenu_path,stat_name); + if (!gtk_ui_manager_get_widget(ui_manager, action_name)) { + action = (GtkAction *)g_object_new (GTK_TYPE_ACTION, + "name", action_name, + "label", stat_name, + NULL); + gtk_action_group_add_action (action_group, action); + g_object_unref (action); + + gtk_ui_manager_add_ui (ui_manager, merge_id, + submenu_path, + stat_name, + action_name, + GTK_UI_MANAGER_MENU, + FALSE); + } + g_free (action_name); + + strcat(submenu_path,"/"); + strcat(submenu_path,stat_name); + stat_name= sep; + } + } + + action_name = g_strdup_printf("%s/%s", submenu_path, cfg->abbr); action = (GtkAction *)g_object_new (GTK_TYPE_ACTION, "name", action_name, - "label", cfg->name, + "label", stat_name, NULL); g_signal_connect (action, "activate", G_CALLBACK (gtk_stats_tree_cb), NULL); gtk_action_group_add_action (action_group, action); g_object_unref (action); gtk_ui_manager_add_ui (ui_manager, merge_id, - MENU_STATISTICS_PATH, + submenu_path, action_name, action_name, GTK_UI_MANAGER_MENUITEM, FALSE); g_free (action_name); + g_free (stat_name_buf); + g_free (submenu_path); } iter = g_list_next(iter); } diff --git a/ui/gtk/stats_tree_stat.c b/ui/gtk/stats_tree_stat.c index c0df8bc58d..ccf9517881 100644 --- a/ui/gtk/stats_tree_stat.c +++ b/ui/gtk/stats_tree_stat.c @@ -23,6 +23,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + /* stats_tree modifications by Deon van der Westhuysen, November 2013 + * support for + * - sorting by column, + * - display a generic number of columns(driven by stats_tree.c + * - copy to clipboard + * - export to text, CSV or XML file + */ + #include "config.h" #include @@ -38,11 +46,22 @@ #include "ui/gtk/gui_utils.h" #include "ui/gtk/dlg_utils.h" +#include "ui/gtk/file_dlg.h" #include "ui/gtk/tap_param_dlg.h" #include "ui/gtk/main.h" #include "ui/gtk/old-gtk-compat.h" +#ifdef _WIN32 +#define USE_WIN32_FILE_DIALOGS +#endif + +#ifdef USE_WIN32_FILE_DIALOGS +#include +#include +#include "ui/win32/file_dlg_win32.h" +#endif + struct _st_node_pres { GtkTreeIter* iter; }; @@ -58,26 +77,32 @@ struct _tree_pres { GtkWidget* tree; }; -/* the columns of the tree pane */ -enum _stat_tree_columns { - TITLE_COLUMN, - COUNT_COLUMN, - RATE_COLUMN, - PERCENT_COLUMN, - N_COLUMNS -}; +/* Define fixed column indexes */ +#define NODEPTR_COLUMN 0 /* Always first column */ +#define N_RESERVED_COL 1 /* Number of columns for internal use - added before visable cols */ -/* used for converting numbers */ -#define NUM_BUF_SIZE 32 -/* creates the gtk representation for a stat_node - * node: the node - */ static void -setup_gtk_node_pr(stat_node* node) +draw_gtk_node(stat_node* node) { GtkTreeIter* parent = NULL; + stat_node* child; + int num_columns= node->st->num_columns+N_RESERVED_COL; + gint *columns = (gint*) g_malloc(sizeof(gint)*num_columns); + GValue *values = (GValue*) g_malloc0(sizeof(GValue)*num_columns); + gchar **valstrs = stats_tree_get_values_from_node(node); + int count; + columns[0]= 0; + g_value_init(values, G_TYPE_POINTER); + g_value_set_pointer(values, node); + for (count = N_RESERVED_COL; countpr) { node->pr = (st_node_pres *)g_malloc(sizeof(st_node_pres)); if (node->st->pr->store) { @@ -87,35 +112,30 @@ setup_gtk_node_pr(stat_node* node) parent = node->parent->pr->iter; } gtk_tree_store_append (node->st->pr->store, node->pr->iter, parent); - gtk_tree_store_set(node->st->pr->store, node->pr->iter, - TITLE_COLUMN, node->name, RATE_COLUMN, "", COUNT_COLUMN, "", -1); + gtk_tree_store_set_valuesv(node->st->pr->store, node->pr->iter, + columns, values, num_columns); + } } -} - - -static void -draw_gtk_node(stat_node* node) -{ - static gchar value[NUM_BUF_SIZE]; - static gchar rate[NUM_BUF_SIZE]; - static gchar percent[NUM_BUF_SIZE]; - stat_node* child; - - stats_tree_get_strs_from_node(node, value, rate, - percent); - if (node->st->pr->store && node->pr->iter) { - gtk_tree_store_set(node->st->pr->store, node->pr->iter, - RATE_COLUMN, rate, - COUNT_COLUMN, value, - PERCENT_COLUMN, percent, - -1); + /* skip reserved columns and first entry in the stats_tree values */ + /* list (the node name). These should already be set and static. */ + gtk_tree_store_set_valuesv(node->st->pr->store, node->pr->iter, + columns+N_RESERVED_COL+1, values+N_RESERVED_COL+1, + num_columns-N_RESERVED_COL-1); } + for (count = 0; countchildren) { for (child = node->children; child; child = child->next ) draw_gtk_node(child); } + } static void @@ -123,18 +143,175 @@ draw_gtk_tree(void *psp) { stats_tree *st = (stats_tree *)psp; stat_node* child; + int count; + gint sort_column= GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID; + GtkSortType order= GTK_SORT_DESCENDING; + + for (count = 0; countnum_columns; count++) { + gtk_tree_view_column_set_title(gtk_tree_view_get_column(GTK_TREE_VIEW(st->pr->tree),count), + stats_tree_get_column_name(count)); + } + + gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), &sort_column, &order); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), + GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_DESCENDING); for (child = st->root.children; child; child = child->next ) { draw_gtk_node(child); - if (child->pr->iter && st->pr->store) { + if ( (!(child->st_flags&ST_FLG_DEF_NOEXPAND)) && child->pr->iter && st->pr->store ) { gtk_tree_view_expand_row(GTK_TREE_VIEW(st->pr->tree), - gtk_tree_model_get_path(GTK_TREE_MODEL(st->pr->store), - child->pr->iter), + gtk_tree_model_get_path(GTK_TREE_MODEL(st->pr->store),child->pr->iter), FALSE); } } + if ((sort_column==GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID)|| + (sort_column==GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID)) { + sort_column= stats_tree_get_default_sort_col(st)+N_RESERVED_COL; + order= stats_tree_is_default_sort_DESC(st)?GTK_SORT_DESCENDING:GTK_SORT_ASCENDING; + } + + /* Only call this once the entire list is drawn - else Gtk seems */ + /* to get sorting order wrong (sorting broken when new nodes are */ + /* added after setting sort column.) Also for performance. */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), sort_column, order); +} + +static gboolean +copy_tree_to_clipboard +(GtkWidget *win _U_, stats_tree *st) +{ + gint sort_column= N_RESERVED_COL; /* default */ + GtkSortType order= GTK_SORT_DESCENDING; + GString *s; + + gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), &sort_column, &order); + s= stats_tree_format_as_str(st,ST_FORMAT_PLAIN,sort_column-N_RESERVED_COL,order==GTK_SORT_DESCENDING); + copy_to_clipboard(s); + g_string_free (s,TRUE); + + return TRUE; +} + + +#ifndef USE_WIN32_FILE_DIALOGS +static gboolean +gtk_save_as_statstree(GtkWidget *win, GString *file_name, int *file_type) +{ + GtkWidget *saveas_w; + GtkWidget *main_vb; + GtkWidget *ft_hb, *ft_lb, *ft_combo_box; + char *st_name; + gpointer ptr; + + saveas_w = file_selection_new("Wireshark: Save stats tree as ...", + GTK_WINDOW(win), FILE_SELECTION_SAVE); + + main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 5, FALSE); + gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5); + file_selection_set_extra_widget(saveas_w, main_vb); + gtk_widget_show(main_vb); + + /* File type row */ + ft_hb = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3, FALSE); + gtk_box_pack_start(GTK_BOX(main_vb), ft_hb, FALSE, FALSE, 0); + gtk_widget_show(ft_hb); + + ft_lb = gtk_label_new("Save as format:"); + gtk_box_pack_start(GTK_BOX(ft_hb), ft_lb, FALSE, FALSE, 0); + gtk_widget_show(ft_lb); + + ft_combo_box = ws_combo_box_new_text_and_pointer(); + ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(ft_combo_box), "Plain text file (.txt)", GINT_TO_POINTER(ST_FORMAT_PLAIN)); + ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(ft_combo_box), "Comma separated values (.csv)", GINT_TO_POINTER(ST_FORMAT_CSV)); + ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(ft_combo_box), "XML document (.xml)", GINT_TO_POINTER(ST_FORMAT_XML)); + + gtk_box_pack_start(GTK_BOX(ft_hb), ft_combo_box, FALSE, FALSE, 0); + gtk_widget_show(ft_combo_box); + ws_combo_box_set_active(GTK_COMBO_BOX(ft_combo_box), 0); + + st_name = file_selection_run(saveas_w); + if (st_name == NULL) { + /* User cancelled or closed the dialog. */ + return FALSE; + } + + if (! ws_combo_box_get_active_pointer(GTK_COMBO_BOX(ft_combo_box), &ptr)) { + g_assert_not_reached(); /* Programming error: somehow nothing is active */ + } + + /* Save result from dialog box */ + *file_type = GPOINTER_TO_INT(ptr); + g_string_printf(file_name, "%s", st_name); + + /* We've crossed the Rubicon; get rid of the file save-as box. */ + window_destroy(GTK_WIDGET(saveas_w)); + g_free(st_name); + return TRUE; +} +#endif /* USE_WIN32_FILE_DIALOGS */ + +static gboolean +save_as_dialog(GtkWidget *win _U_, stats_tree *st) +{ + gint sort_column= 1; /* default */ + GtkSortType order= GTK_SORT_DESCENDING; + GString *str_tree; + GString *file_name = g_string_new(""); + int file_type; + gchar *file_name_lower; + gchar *file_ext; + FILE *f; + gboolean success= FALSE; + int last_errno; + +#ifdef USE_WIN32_FILE_DIALOGS + if (win32_save_as_statstree(GDK_WINDOW_HWND(gtk_widget_get_window(st->pr->win)), + file_name, &file_type)) { +#else /* USE_WIN32_FILE_DIALOGS */ + if (gtk_save_as_statstree(st->pr->win,file_name,&file_type)) { +#endif /* USE_WIN32_FILE_DIALOGS */ + + /* add file extension as required */ + file_name_lower = g_utf8_strdown(file_name->str, -1); + file_ext= file_type==ST_FORMAT_XML?".xml":(file_type==ST_FORMAT_CSV?".csv":".txt"); + if (!g_str_has_suffix(file_name_lower, file_ext)) { + /* Must add extenstion */ + g_string_append(file_name,file_ext); + } + g_free(file_name_lower); + + gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), &sort_column, &order); + str_tree=stats_tree_format_as_str(st,file_type,sort_column-N_RESERVED_COL,order==GTK_SORT_DESCENDING); + + /* actually save the file */ + f= fopen (file_name->str,"w"); + last_errno= errno; + if (f) { + if (fputs(str_tree->str, f)!=EOF) { + success= TRUE; + } + last_errno= errno; + fclose(f); + } + if (!success) { + GtkWidget *dialog = gtk_message_dialog_new (GTK_WINDOW(st->pr->win), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Error saving file '%s': %s", + file_name->str, g_strerror (last_errno)); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + } + + g_string_free(str_tree, TRUE); + } + + g_string_free(file_name, TRUE); + + return TRUE; } static void @@ -169,11 +346,37 @@ reset_tap(void* p) { stats_tree* st = (stats_tree *)p; stat_node* c; + for (c = st->root.children; c; c = c->next) { clear_node_pr(c); } - st->cfg->init(st); + stats_tree_reinit(st); +/* st->cfg->init(st); doesn't properly delete nodes */ +} + +static gint +st_sort_func(GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + gint sort_column= 1; /* default */ + GtkSortType order= GTK_SORT_DESCENDING; + stat_node *node_a; + stat_node *node_b; + gint result; + + gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (user_data), &sort_column, &order); + + gtk_tree_model_get(model, a, NODEPTR_COLUMN, &node_a, -1); + gtk_tree_model_get(model, b, NODEPTR_COLUMN, &node_b, -1); + + result= stats_tree_sort_compare(node_a,node_b,sort_column-N_RESERVED_COL,order==GTK_SORT_DESCENDING); + if (order==GTK_SORT_DESCENDING) { + result= -result; + } + return result; } /* initializes the stats_tree window */ @@ -189,9 +392,12 @@ init_gtk_tree(const char* opt_arg, void *userdata _U_) GString* error_string; GtkWidget *scr_win; size_t init_strlen; - GtkWidget *main_vb, *bbox, *bt_close; + GtkWidget *main_vb, *bbox, *bt_close, *bt_copy, *bt_saveas; GtkTreeViewColumn* column; GtkCellRenderer* renderer; + GtkTreeSortable *sortable; + GType *col_types; + int count; if (abbr) { cfg = stats_tree_get_cfg_by_abbr(abbr); @@ -230,17 +436,17 @@ init_gtk_tree(const char* opt_arg, void *userdata _U_) cfg->in_use = TRUE; - window_name = g_strdup_printf("%s Stats Tree", cfg->name); + window_name = g_strdup_printf("%s Stats Tree", st->display_name); st->pr->win = window_new_with_geom(GTK_WINDOW_TOPLEVEL,window_name,window_name); - gtk_window_set_default_size(GTK_WINDOW(st->pr->win), 400, 400); + gtk_window_set_default_size(GTK_WINDOW(st->pr->win), st->num_columns*80+80, 400); g_free(window_name); if(st->filter){ - title=g_strdup_printf("%s with filter: %s",cfg->name,st->filter); + title=g_strdup_printf("%s with filter: %s",st->display_name,st->filter); } else { st->filter=NULL; - title=g_strdup_printf("%s", cfg->name); + title=g_strdup_printf("%s", st->display_name); } gtk_window_set_title(GTK_WINDOW(st->pr->win), title); @@ -252,47 +458,36 @@ init_gtk_tree(const char* opt_arg, void *userdata _U_) scr_win = scrolled_window_new(NULL, NULL); - st->pr->store = gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, - G_TYPE_STRING, G_TYPE_STRING); + col_types= (GType*)g_malloc(sizeof(GType)*(st->num_columns+N_RESERVED_COL)); + col_types[0] = G_TYPE_POINTER; + for (count = 0; countnum_columns; count++) { + col_types[count+N_RESERVED_COL] = G_TYPE_STRING; + } + st->pr->store = gtk_tree_store_newv (st->num_columns+N_RESERVED_COL,col_types); + g_free (col_types); + sortable= GTK_TREE_SORTABLE (st->pr->store); st->pr->tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (st->pr->store)); + gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(st->pr->tree), FALSE); g_object_unref(G_OBJECT(st->pr->store)); gtk_container_add( GTK_CONTAINER(scr_win), st->pr->tree); /* the columns */ + for (count = 0; countnum_columns; count++) { renderer = gtk_cell_renderer_text_new (); - column = gtk_tree_view_column_new_with_attributes ("Topic / Item", renderer, - "text", TITLE_COLUMN, - NULL); + column = gtk_tree_view_column_new_with_attributes (stats_tree_get_column_name(count), + renderer, "text", count+N_RESERVED_COL, NULL); + if (stats_tree_is_sortable_column(count)) { + gtk_tree_view_column_set_sort_column_id(column, count+N_RESERVED_COL); + gtk_tree_sortable_set_sort_func(sortable,count+N_RESERVED_COL, st_sort_func, sortable, NULL); + } gtk_tree_view_column_set_resizable (column,TRUE); gtk_tree_view_column_set_sizing(column,GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column (GTK_TREE_VIEW (st->pr->tree), column); + } - renderer = gtk_cell_renderer_text_new (); - column = gtk_tree_view_column_new_with_attributes ("Count", renderer, - "text", COUNT_COLUMN, - NULL); - - gtk_tree_view_column_set_resizable (column,TRUE); - gtk_tree_view_column_set_sizing(column,GTK_TREE_VIEW_COLUMN_AUTOSIZE); - gtk_tree_view_append_column (GTK_TREE_VIEW (st->pr->tree), column); - - renderer = gtk_cell_renderer_text_new (); - column = gtk_tree_view_column_new_with_attributes ("Rate (ms)", renderer, - "text", RATE_COLUMN, - NULL); - gtk_tree_view_column_set_resizable (column,TRUE); - gtk_tree_view_column_set_sizing(column,GTK_TREE_VIEW_COLUMN_AUTOSIZE); - gtk_tree_view_append_column (GTK_TREE_VIEW (st->pr->tree), column); - - renderer = gtk_cell_renderer_text_new (); - column = gtk_tree_view_column_new_with_attributes ("Percent", renderer, - "text", PERCENT_COLUMN, - NULL); - gtk_tree_view_column_set_resizable(column,TRUE); - gtk_tree_view_column_set_sizing(column,GTK_TREE_VIEW_COLUMN_AUTOSIZE); - gtk_tree_view_append_column (GTK_TREE_VIEW (st->pr->tree), column); + gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL); gtk_box_pack_start(GTK_BOX(main_vb), scr_win, TRUE, TRUE, 0); @@ -312,7 +507,7 @@ init_gtk_tree(const char* opt_arg, void *userdata _U_) } /* Button row. */ - bbox = dlg_button_row_new(GTK_STOCK_CLOSE, NULL); + bbox = dlg_button_row_new(GTK_STOCK_COPY, GTK_STOCK_SAVE_AS, GTK_STOCK_CLOSE, NULL); gtk_box_pack_start(GTK_BOX(main_vb), bbox, FALSE, FALSE, 0); bt_close = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CLOSE); @@ -321,6 +516,12 @@ init_gtk_tree(const char* opt_arg, void *userdata _U_) g_signal_connect(GTK_WINDOW(st->pr->win), "delete_event", G_CALLBACK(window_delete_event_cb), NULL); g_signal_connect(GTK_WINDOW(st->pr->win), "destroy", G_CALLBACK(free_gtk_tree), st); + bt_copy = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_COPY); + g_signal_connect(GTK_WINDOW (bt_copy), "clicked", G_CALLBACK(copy_tree_to_clipboard), st); + + bt_saveas = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_SAVE_AS); + g_signal_connect(GTK_WINDOW (bt_saveas), "clicked", G_CALLBACK(save_as_dialog), st); + gtk_widget_show_all(st->pr->win); window_present(st->pr->win); @@ -336,17 +537,19 @@ static void register_gtk_stats_tree_tap (gpointer k _U_, gpointer v, gpointer p _U_) { stats_tree_cfg* cfg = (stats_tree_cfg *)v; + gchar* display_name= stats_tree_get_displayname(cfg->name); cfg->pr = (tree_cfg_pres *)g_malloc(sizeof(tree_cfg_pres)); cfg->pr->stat_dlg = (tap_param_dlg *)g_malloc(sizeof(tap_param_dlg)); - cfg->pr->stat_dlg->win_title = g_strdup_printf("%s Stats Tree",cfg->name); + cfg->pr->stat_dlg->win_title = g_strdup_printf("%s Stats Tree",display_name); cfg->pr->stat_dlg->init_string = g_strdup_printf("%s,tree",cfg->abbr); cfg->pr->stat_dlg->tap_init_cb = init_gtk_tree; cfg->pr->stat_dlg->index = -1; cfg->pr->stat_dlg->nparams = G_N_ELEMENTS(tree_stat_params); cfg->pr->stat_dlg->params = tree_stat_params; + g_free(display_name); } static void @@ -360,7 +563,7 @@ register_tap_listener_stats_tree_stat(void) { stats_tree_presentation(register_gtk_stats_tree_tap, - setup_gtk_node_pr, + NULL, NULL, NULL, NULL, diff --git a/ui/win32/file_dlg_win32.c b/ui/win32/file_dlg_win32.c index 7b4d829ed2..187e38eff8 100644 --- a/ui/win32/file_dlg_win32.c +++ b/ui/win32/file_dlg_win32.c @@ -95,6 +95,7 @@ static UINT_PTR CALLBACK open_file_hook_proc(HWND of_hwnd, UINT ui_msg, WPARAM w_param, LPARAM l_param); static UINT_PTR CALLBACK save_as_file_hook_proc(HWND of_hwnd, UINT ui_msg, WPARAM w_param, LPARAM l_param); +static UINT_PTR CALLBACK save_as_statstree_hook_proc(HWND of_hwnd, UINT ui_msg, WPARAM w_param, LPARAM l_param); static UINT_PTR CALLBACK export_specified_packets_file_hook_proc(HWND of_hwnd, UINT ui_msg, WPARAM w_param, LPARAM l_param); static UINT_PTR CALLBACK merge_file_hook_proc(HWND mf_hwnd, UINT ui_msg, WPARAM w_param, LPARAM l_param); static UINT_PTR CALLBACK export_file_hook_proc(HWND of_hwnd, UINT ui_msg, WPARAM w_param, LPARAM l_param); @@ -426,6 +427,72 @@ win32_save_as_file(HWND h_wnd, capture_file *cf, GString *file_name, int *file_t return gsfn_ok; } +gboolean win32_save_as_statstree(HWND h_wnd, GString *file_name, int *file_type) +{ + OPENFILENAME *ofn; + TCHAR file_name16[MAX_PATH] = _T(""); + int ofnsize; + gboolean gsfn_ok; +#if (_MSC_VER >= 1500) + OSVERSIONINFO osvi; +#endif + + if (!file_name || !file_type) + return FALSE; + + if (file_name->len > 0) { + StringCchCopy(file_name16, MAX_PATH, utf_8to16(file_name->str)); + } + + /* see OPENFILENAME comment in win32_open_file */ +#if (_MSC_VER >= 1500) + SecureZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + if (osvi.dwMajorVersion >= 5) { + ofnsize = sizeof(OPENFILENAME); + } else { + ofnsize = OPENFILENAME_SIZE_VERSION_400; + } +#else + ofnsize = sizeof(OPENFILENAME) + 12; +#endif + ofn = g_malloc0(ofnsize); + + ofn->lStructSize = ofnsize; + ofn->hwndOwner = h_wnd; + ofn->hInstance = (HINSTANCE) GetWindowLongPtr(h_wnd, GWLP_HINSTANCE); + ofn->lpstrFilter = _T("Plain text file (.txt)\0*.txt\0Comma separated values (.csv)\0*.csv\0XML document (.xml)\0*.xml\0"); + ofn->lpstrCustomFilter = NULL; + ofn->nMaxCustFilter = 0; + ofn->nFilterIndex = 1; /* the first entry is the best match; 1-origin indexing */ + ofn->lpstrFile = file_name16; + ofn->nMaxFile = MAX_PATH; + ofn->lpstrFileTitle = NULL; + ofn->nMaxFileTitle = 0; + ofn->lpstrInitialDir = utf_8to16(get_last_open_dir()); + ofn->lpstrTitle = _T("Wireshark: Save stats tree as ..."); + ofn->Flags = OFN_ENABLESIZING | OFN_ENABLETEMPLATE | OFN_EXPLORER | + OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | + OFN_PATHMUSTEXIST | OFN_ENABLEHOOK; + ofn->lpstrDefExt = NULL; + ofn->lpfnHook = save_as_statstree_hook_proc; + ofn->lpTemplateName = _T("WIRESHARK_SAVEASSTATSTREENAME_TEMPLATE"); + + gsfn_ok = GetSaveFileName(ofn); + + if (gsfn_ok) { + g_string_printf(file_name, "%s", utf_16to8(file_name16)); + /* What file format was specified? */ + *file_type = ofn->nFilterIndex - 1; + } + + g_sf_hwnd = NULL; + g_free( (void *) ofn); + return gsfn_ok; +} + + gboolean win32_export_specified_packets_file(HWND h_wnd, capture_file *cf, GString *file_name, @@ -1705,6 +1772,26 @@ save_as_file_hook_proc(HWND sf_hwnd, UINT msg, WPARAM w_param, LPARAM l_param) { return 0; } +static UINT_PTR CALLBACK +save_as_statstree_hook_proc(HWND sf_hwnd, UINT msg, WPARAM w_param, LPARAM l_param) { + + switch(msg) { + case WM_INITDIALOG: + g_sf_hwnd = sf_hwnd; + break; + + case WM_COMMAND: + break; + + case WM_NOTIFY: + break; + + default: + break; + } + return 0; +} + #define RANGE_TEXT_MAX 128 static UINT_PTR CALLBACK export_specified_packets_file_hook_proc(HWND sf_hwnd, UINT msg, WPARAM w_param, LPARAM l_param) { diff --git a/ui/win32/file_dlg_win32.h b/ui/win32/file_dlg_win32.h index a317f49d9e..94662ee6e6 100644 --- a/ui/win32/file_dlg_win32.h +++ b/ui/win32/file_dlg_win32.h @@ -122,6 +122,17 @@ void win32_export_color_file(HWND h_wnd, capture_file *cf, gpointer filter_list) */ void win32_import_color_file(HWND h_wnd, gpointer color_filters); +/** Open the "Save As" dialog box for stats_tree statistics window. + * + * @param h_wnd HWND of the parent window. + * @param file_name File name. May be empty. + * @param file_type stats_tree file type. + * + * @return FALSE if the dialog was cancelled + */ +gboolean win32_save_as_statstree(HWND h_wnd, GString *file_name, + int *file_type); + void file_set_save_marked_sensitive(); /* Open dialog defines */