diff options
Diffstat (limited to 'epan/stats_tree.c')
-rw-r--r-- | epan/stats_tree.c | 654 |
1 files changed, 639 insertions, 15 deletions
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 <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,"<"); + 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,"<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"); + } +} + + + |