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
This commit is contained in:
Michael Mann 2013-11-29 22:47:59 +00:00
parent eaaf4437ab
commit 60d6b05e23
14 changed files with 1399 additions and 118 deletions

View File

@ -3792,7 +3792,8 @@ Masayuki Takemura <masayuki.takemura[AT]gmail.com>
Ed Beroset <beroset[AT]mindspring.com>
e.yimjia <jy.m12.0[AT]gmail.com>
Jonathon Jongsma <jjongsma[AT]redhat.com>
zeljko <zancimer[AT]gmail.com>
Zeljko Ancimer <zancimer[AT]gmail.com>
Deon van der Westhuysen <deonvdw[AT]gmail.com>
Dan Lasley <dlasley[AT]promus.com> gave permission for his
dumpit() hex-dump routine to be used.

View File

@ -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

View File

@ -58,6 +58,7 @@
#include "epan/filter_expressions.h"
#include "epan/wmem/wmem.h"
#include <epan/stats_tree.h>
/* 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;

View File

@ -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;

View File

@ -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 <glib.h>
#include <glib/gprintf.h>
#include <stdlib.h>
#include <epan/stats_tree_priv.h>
#include <epan/prefs.h>
#include <math.h>
#include <string.h>
#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_FLG_SRTCOL_SHIFT;
if (prefs.st_sort_defdescending) {
st->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_bucket<node->bh->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_bucket<search->bucket_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 (index<N_COLUMNS) {
return 12; /* all numerica values this size */
}
return 0; /* invalid column */
}
extern gboolean
stats_tree_is_sortable_column (gint index)
{
switch (index) {
case COL_NAME:
case COL_COUNT:
case COL_AVERAGE:
case COL_MIN:
case COL_MAX:
case COL_BURSTRATE: return TRUE;
default: return FALSE;
}
}
extern gchar**
stats_tree_get_values_from_node (const stat_node* node)
{
gchar **values = (gchar**) g_malloc0(sizeof(gchar*)*(node->st->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_a<avg_b)?-1:0);
}
}
else {
result= -1; /* let b>a */
}
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("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n");
}
else if (format==ST_FORMAT_CSV) {
s = g_string_new("\"level\",\"parent\",");
for (count = 0; count<st->num_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; count<st->num_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; count<st->num_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,"&lt;");
break;
case '>': g_string_append(s,"&gt;");
break;
case '&': g_string_append(s,"&amp;");
break;
case '\'': g_string_append(s,"&apos;");
break;
case '"': g_string_append(s,"&quot;");
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,"<stat-node name=\"%s\"%s>\n",itemname->str,
node->rng?" isrange=\"true\"":"");
g_string_free(itemname,TRUE);
g_string_append(s,"");
for (count = 1; count<num_columns; count++) {
gchar *colname= g_strdup(stats_tree_get_column_name(count));
g_string_append_printf(s,"<%s>",clean_for_xml_tag(colname));
g_string_append_printf(s,"%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<num_columns; count++) {
g_string_append_printf(s,",%s",values[count]);
}
g_string_append (s,"\n");
}
else if (format==ST_FORMAT_PLAIN) {
char fmt[16];
g_sprintf (fmt,"%%%ds%%-%us",indent,maxnamelen-indent);
g_string_append_printf(s,fmt,"",values[0]);
for (count = 1; count<num_columns; count++) {
g_sprintf (fmt," %%-%us",stats_tree_get_column_size(count)+1);
g_string_append_printf(s,fmt,values[count]);
}
g_string_append (s,"\n");
}
indent++;
indent = indent > INDENT_MAX ? INDENT_MAX : indent;
path= g_strdup_printf ("%s/%s",path,values[0]);
for (count = 0; count<num_columns; count++) {
g_free(values[count]);
}
g_free(values);
if (node->children) {
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,"</stat-node>\n");
}
}

View File

@ -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 */

View File

@ -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 */

View File

@ -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"

View File

@ -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 );

View File

@ -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

View File

@ -1253,6 +1253,7 @@ static const char *ui_desc_menubar =
" </menu>\n"
" <menuitem name='UDPMulticastStreams' action='/Statistics/UDPMulticastStreams'/>\n"
" <menuitem name='WLANTraffic' action='/Statistics/WLANTraffic'/>\n"
" <separator/>\n"
" </menu>\n"
" <menu name= 'TelephonyMenu' action='/Telephony'>\n"
" <menu name= 'ANSI' action='/Telephony/ANSI'>\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);
}

View File

@ -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 <string.h>
@ -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 <gdk/gdkwin32.h>
#include <windows.h>
#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; count<num_columns; count++) {
columns[count]= count;
g_value_init(values+count, G_TYPE_STRING);
g_value_take_string (values+count,valstrs[count-N_RESERVED_COL]);
}
if (!node->pr) {
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; count<num_columns; count++) {
g_value_unset(values+count);
}
g_free(columns);
g_free(values);
g_free(valstrs);
if (node->children) {
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; count<st->num_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; count<st->num_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; count<st->num_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,

View File

@ -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) {

View File

@ -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 */