2 * API for a counter tree for Wireshark
3 * 2004, Luis E. G. Ontanon
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
12 /* stats_tree modifications by Deon van der Westhuysen, November 2013
14 * - sorting by column,
15 * - calculation of average values
16 * - calculation of burst rate
17 * - export to text, CSV or XML file
26 #include <epan/stats_tree_priv.h>
27 #include <epan/prefs.h>
32 #include "stats_tree.h"
33 #include <wsutil/ws_assert.h>
35 enum _stat_tree_columns
{
48 /* used to contain the registered stat trees */
49 static GHashTable
*registry
;
51 /* a text representation of a node
52 if buffer is NULL returns a newly allocated string */
54 stats_tree_node_to_str(const stat_node
*node
, char *buffer
, unsigned len
)
57 snprintf(buffer
,len
,"%s: %i",node
->name
, node
->counter
);
60 return ws_strdup_printf("%s: %i",node
->name
, node
->counter
);
65 // NOLINTNEXTLINE(misc-no-recursion)
66 stats_tree_branch_max_namelen(const stat_node
*node
, unsigned indent
)
72 indent
= indent
> INDENT_MAX
? INDENT_MAX
: indent
;
75 for (child
= node
->children
; child
; child
= child
->next
) {
76 // Recursion is limited by proto.c checks
77 len
= stats_tree_branch_max_namelen(child
,indent
+1);
78 maxlen
= len
> maxlen
? len
: maxlen
;
82 if (node
->st_flags
&ST_FLG_ROOTCHILD
) {
83 char *display_name
= stats_tree_get_displayname(node
->name
);
84 len
= (unsigned) strlen(display_name
) + indent
;
88 len
= (unsigned) strlen(node
->name
) + indent
;
90 maxlen
= len
> maxlen
? len
: maxlen
;
95 /* frees the resources allocated by a stat_tree node */
97 // NOLINTNEXTLINE(misc-no-recursion)
98 free_stat_node(stat_node
*node
)
102 burst_bucket
*bucket
;
104 if (node
->children
) {
105 for (child
= node
->children
; child
; child
= next
) {
106 /* child->next will be gone after free_stat_node, so cache it here */
108 // Recursion is limited by proto.c checks
109 free_stat_node(child
);
113 if (node
->hash
) g_hash_table_destroy(node
->hash
);
117 node
->bh
= bucket
->next
;
126 /* destroys the whole tree instance */
128 stats_tree_free(stats_tree
*st
)
136 g_hash_table_destroy(st
->names
);
137 g_ptr_array_free(st
->parents
,true);
138 g_free(st
->display_name
);
140 for (child
= st
->root
.children
; child
; child
= next
) {
141 /* child->next will be gone after free_stat_node, so cache it here */
143 free_stat_node(child
);
146 if (st
->cfg
->free_tree_pr
)
147 st
->cfg
->free_tree_pr(st
);
149 if (st
->cfg
->cleanup
)
150 st
->cfg
->cleanup(st
);
156 /* reset a node to its original state */
158 // NOLINTNEXTLINE(misc-no-recursion)
159 reset_stat_node(stat_node
*node
)
162 burst_bucket
*bucket
;
165 switch (node
->datatype
)
168 node
->total
.int_total
= 0;
169 node
->minvalue
.int_min
= INT_MAX
;
170 node
->maxvalue
.int_max
= INT_MIN
;
173 node
->total
.float_total
= 0;
174 node
->minvalue
.float_min
= FLT_MAX
;
175 node
->maxvalue
.float_max
= FLT_MIN
;
182 node
->bh
= bucket
->next
;
185 node
->bh
= g_new0(burst_bucket
, 1);
189 node
->burst_time
= -1.0;
191 if (node
->children
) {
192 for (child
= node
->children
; child
; child
= child
->next
)
193 // Recursion is limited by proto.c checks
194 reset_stat_node(child
);
198 /* reset the whole stats_tree */
200 stats_tree_reset(void *p
)
202 stats_tree
*st
= (stats_tree
*)p
;
208 reset_stat_node(&st
->root
);
212 stats_tree_reinit(void *p
)
214 stats_tree
*st
= (stats_tree
*)p
;
218 for (child
= st
->root
.children
; child
; child
= next
) {
219 /* child->next will be gone after free_stat_node, so cache it here */
221 free_stat_node(child
);
224 st
->root
.children
= NULL
;
225 st
->root
.counter
= 0;
226 switch (st
->root
.datatype
)
229 st
->root
.total
.int_total
= 0;
230 st
->root
.minvalue
.int_min
= INT_MAX
;
231 st
->root
.maxvalue
.int_max
= INT_MIN
;
234 st
->root
.total
.float_total
= 0;
235 st
->root
.minvalue
.float_min
= FLT_MAX
;
236 st
->root
.maxvalue
.float_max
= FLT_MIN
;
239 st
->root
.st_flags
= 0;
241 st
->root
.bh
= g_new0(burst_bucket
, 1);
242 st
->root
.bt
= st
->root
.bh
;
244 st
->root
.max_burst
= 0;
245 st
->root
.burst_time
= -1.0;
247 /* No more stat_nodes left in tree - clean out hash, array */
248 g_hash_table_remove_all(st
->names
);
249 if (st
->parents
->len
>1) {
250 g_ptr_array_remove_range(st
->parents
, 1, st
->parents
->len
-1);
253 /* Do not update st_flags for the tree (sorting) - leave as was */
254 st
->num_columns
= N_COLUMNS
;
255 g_free(st
->display_name
);
256 st
->display_name
= stats_tree_get_displayname(st
->cfg
->path
);
264 stats_tree_free_configuration(void *p
)
266 stats_tree_cfg
* cfg
= (stats_tree_cfg
*)p
;
267 g_free(cfg
->tapname
);
271 g_free(cfg
->first_column_name
);
275 /* register a new stats_tree */
276 extern stats_tree_cfg
*
277 stats_tree_register(const char *tapname
, const char *abbr
, const char *path
,
279 stat_tree_packet_cb packet
, stat_tree_init_cb init
,
280 stat_tree_cleanup_cb cleanup
)
282 stats_tree_cfg
*cfg
= g_new0(stats_tree_cfg
, 1);
284 /* at the very least the abbrev and the packet function should be given */
285 ws_assert( tapname
&& abbr
&& packet
);
287 cfg
->tapname
= g_strdup(tapname
);
288 cfg
->abbr
= g_strdup(abbr
);
289 cfg
->path
= path
? g_strdup(path
) : g_strdup(abbr
);
290 cfg
->stat_group
= REGISTER_PACKET_STAT_GROUP_UNSORTED
;
292 GString
*title_str
= g_string_new("");
293 char **split
= g_strsplit(path
, STATS_TREE_MENU_SEPARATOR
, 0);
294 const char *sep
= "";
295 for (size_t idx
= 0; split
[idx
]; idx
++) {
296 g_string_append_printf(title_str
, "%s%s", sep
, g_strstrip(split
[idx
]));
300 cfg
->title
= g_string_free(title_str
, FALSE
);
302 cfg
->packet
= packet
;
304 cfg
->cleanup
= cleanup
;
306 cfg
->flags
= flags
&~ST_FLG_MASK
;
307 cfg
->st_flags
= flags
&ST_FLG_MASK
;
309 if (!registry
) registry
= g_hash_table_new_full(g_str_hash
,g_str_equal
,NULL
,stats_tree_free_configuration
);
311 g_hash_table_insert(registry
,cfg
->abbr
,cfg
);
316 /* register a new stat_tree with default group REGISTER_PACKET_STAT_GROUP_UNSORTED from a plugin */
317 extern stats_tree_cfg
*
318 stats_tree_register_plugin(const char *tapname
, const char *abbr
, const char *path
,
320 stat_tree_packet_cb packet
, stat_tree_init_cb init
,
321 stat_tree_cleanup_cb cleanup
)
323 stats_tree_cfg
*cfg
= stats_tree_register(tapname
, abbr
, path
,
324 flags
, packet
, init
, cleanup
);
331 stats_tree_set_group(stats_tree_cfg
*st_config
, register_stat_group_t stat_group
) {
333 st_config
->stat_group
= stat_group
;
338 stats_tree_set_first_column_name(stats_tree_cfg
*st_config
, const char *column_name
) {
340 st_config
->first_column_name
= g_strdup(column_name
);
345 stats_tree_new(stats_tree_cfg
*cfg
, tree_pres
*pr
, const char *filter
)
347 stats_tree
*st
= g_new0(stats_tree
, 1);
352 st
->names
= g_hash_table_new(g_str_hash
,g_str_equal
);
353 st
->parents
= g_ptr_array_new();
354 st
->filter
= g_strdup(filter
);
359 switch (st
->root
.datatype
)
362 st
->root
.minvalue
.int_min
= INT_MAX
;
363 st
->root
.maxvalue
.int_max
= INT_MIN
;
366 st
->root
.minvalue
.float_min
= FLT_MAX
;
367 st
->root
.maxvalue
.float_max
= FLT_MIN
;
371 st
->root
.bh
= g_new0(burst_bucket
, 1);
372 st
->root
.bt
= st
->root
.bh
;
373 st
->root
.burst_time
= -1.0;
375 st
->root
.name
= stats_tree_get_displayname(cfg
->path
);
378 st
->st_flags
= st
->cfg
->st_flags
;
380 if (!(st
->st_flags
&ST_FLG_SRTCOL_MASK
)) {
381 /* No default sort specified - use preferences */
382 st
->st_flags
|= prefs
.st_sort_defcolflag
<<ST_FLG_SRTCOL_SHIFT
;
383 if (prefs
.st_sort_defdescending
) {
384 st
->st_flags
|= ST_FLG_SORT_DESC
;
387 st
->num_columns
= N_COLUMNS
;
388 st
->display_name
= stats_tree_get_displayname(st
->cfg
->path
);
390 g_ptr_array_add(st
->parents
,&st
->root
);
395 /* will be the tap packet cb */
396 extern tap_packet_status
397 stats_tree_packet(void *p
, packet_info
*pinfo
, epan_dissect_t
*edt
, const void *pri
, tap_flags_t flags
)
399 stats_tree
*st
= (stats_tree
*)p
;
401 st
->now
= nstime_to_msec(&pinfo
->rel_ts
);
402 if (st
->start
< 0.0) st
->start
= st
->now
;
404 st
->elapsed
= st
->now
- st
->start
;
407 return st
->cfg
->packet(st
,pinfo
,edt
,pri
, flags
);
409 return TAP_PACKET_DONT_REDRAW
;
412 extern stats_tree_cfg
*
413 stats_tree_get_cfg_by_abbr(const char *abbr
)
415 if (!abbr
) return NULL
;
416 return (stats_tree_cfg
*)g_hash_table_lookup(registry
,abbr
);
420 compare_stat_menu_item(const void *stat_a
, const void *stat_b
)
422 const stats_tree_cfg
* stat_cfg_a
= (const stats_tree_cfg
*)stat_a
;
423 const stats_tree_cfg
* stat_cfg_b
= (const stats_tree_cfg
*)stat_b
;
425 return strcmp(stat_cfg_a
->path
, stat_cfg_b
->path
);
429 stats_tree_get_cfg_list(void)
431 GList
* registry_list
= g_hash_table_get_values(registry
);
432 /* Now sort the list so they can show up in the
433 menu alphabetically */
434 return g_list_sort(registry_list
, compare_stat_menu_item
);
438 struct _stats_tree_pres_cbs
{
439 void (*setup_node_pr
)(stat_node
*);
440 void (*free_tree_pr
)(stats_tree
*);
444 setup_tree_presentation(void *k _U_
, void *v
, void *p
)
446 stats_tree_cfg
*cfg
= (stats_tree_cfg
*)v
;
447 struct _stats_tree_pres_cbs
*d
= (struct _stats_tree_pres_cbs
*)p
;
449 cfg
->setup_node_pr
= d
->setup_node_pr
;
450 cfg
->free_tree_pr
= d
->free_tree_pr
;
455 stats_tree_presentation(void (*registry_iterator
)(void *,void *,void *),
456 void (*setup_node_pr
)(stat_node
*),
457 void (*free_tree_pr
)(stats_tree
*),
460 static struct _stats_tree_pres_cbs d
;
462 d
.setup_node_pr
= setup_node_pr
;
463 d
.free_tree_pr
= free_tree_pr
;
465 if (registry
) g_hash_table_foreach(registry
,setup_tree_presentation
,&d
);
467 if (registry_iterator
&& registry
)
468 g_hash_table_foreach(registry
,registry_iterator
,data
);
473 /* creates a stat_tree node
474 * name: the name of the stats_tree node
475 * parent_name: the name of the ALREADY REGISTERED parent
476 * with_hash: whether or not it should keep a hash with its children names
477 * as_named_node: whether or not it has to be registered in the root namespace
480 new_stat_node(stats_tree
*st
, const char *name
, int parent_id
, stat_node_datatype datatype
,
481 bool with_hash
, bool as_parent_node
)
484 stat_node
*node
= g_new0(stat_node
, 1);
485 stat_node
*last_chld
= NULL
;
487 node
->datatype
= datatype
;
491 node
->minvalue
.int_min
= INT_MAX
;
492 node
->maxvalue
.int_max
= INT_MIN
;
495 node
->minvalue
.float_min
= FLT_MAX
;
496 node
->maxvalue
.float_max
= FLT_MIN
;
499 node
->st_flags
= parent_id
?0:ST_FLG_ROOTCHILD
;
501 node
->bh
= g_new0(burst_bucket
, 1);
503 node
->burst_time
= -1.0;
505 node
->name
= g_strdup(name
);
507 node
->hash
= with_hash
? g_hash_table_new(g_str_hash
,g_str_equal
) : NULL
;
509 if (as_parent_node
) {
510 g_hash_table_insert(st
->names
,
514 g_ptr_array_add(st
->parents
,node
);
516 node
->id
= st
->parents
->len
- 1;
521 if (parent_id
>= 0 && parent_id
< (int) st
->parents
->len
) {
522 node
->parent
= (stat_node
*)g_ptr_array_index(st
->parents
,parent_id
);
524 /* ??? should we set the parent to be root ??? */
525 ws_assert_not_reached();
528 if (node
->parent
->children
) {
529 /* insert as last child */
531 for (last_chld
= node
->parent
->children
;
533 last_chld
= last_chld
->next
) ;
535 last_chld
->next
= node
;
538 /* insert as first child */
539 node
->parent
->children
= node
;
542 if(node
->parent
->hash
) {
543 g_hash_table_replace(node
->parent
->hash
,node
->name
,node
);
546 if (st
->cfg
->setup_node_pr
) {
547 st
->cfg
->setup_node_pr(node
);
557 stats_tree_create_node(stats_tree
*st
, const char *name
, int parent_id
, stat_node_datatype datatype
, bool with_hash
)
559 stat_node
*node
= new_stat_node(st
,name
,parent_id
,datatype
,with_hash
,true);
567 /* XXX: should this be a macro? */
569 stats_tree_create_node_by_pname(stats_tree
*st
, const char *name
,
570 const char *parent_name
, stat_node_datatype datatype
, bool with_children
)
572 return stats_tree_create_node(st
,name
,stats_tree_parent_id_by_name(st
,parent_name
),datatype
,with_children
);
575 /* Internal function to update the burst calculation data - add entry to bucket */
577 update_burst_calc(stat_node
*node
, int value
)
579 double current_bucket
;
584 if (!prefs
.st_enable_burstinfo
) {
588 /* NB thebucket list should always contain at least one node - even if it is */
589 /* the dummy created at init time. Head and tail should never be NULL! */
590 current_bucket
= floor(node
->st
->now
/prefs
.st_burst_resolution
);
591 burstwin
= prefs
.st_burst_windowlen
/prefs
.st_burst_resolution
;
592 if (current_bucket
>node
->bt
->bucket_no
) {
593 /* Must add a new bucket at the burst list tail */
594 bn
= g_new0(burst_bucket
, 1);
596 bn
->bucket_no
= current_bucket
;
597 bn
->start_time
= node
->st
->now
;
601 /* And add value to the current burst count for node */
602 node
->bcount
+= value
;
603 /* Check if bucket list head is now too old and must be removed */
604 while (current_bucket
>=(node
->bh
->bucket_no
+burstwin
)) {
605 /* off with its head! */
608 node
->bh
->prev
= NULL
;
609 node
->bcount
-= bn
->count
;
613 else if (current_bucket
<node
->bh
->bucket_no
) {
614 /* Packet must be added at head of burst list - check if not too old */
615 if ((current_bucket
+burstwin
)>node
->bt
->bucket_no
) {
616 /* packet still within the window */
617 bn
= g_new0(burst_bucket
, 1);
619 bn
->bucket_no
= current_bucket
;
620 bn
->start_time
= node
->st
->now
;
624 /* And add value to the current burst count for node */
625 node
->bcount
+= value
;
630 /* Somewhere in the middle... */
631 burst_bucket
*search
= node
->bt
;
632 while (current_bucket
<search
->bucket_no
) {
633 search
= search
->prev
;
635 if (current_bucket
==search
->bucket_no
) {
636 /* found existing bucket, increase value */
637 search
->count
+= value
;
638 if (search
->start_time
>node
->st
->now
) {
639 search
->start_time
= node
->st
->now
;
643 /* must add a new bucket after bn. */
644 bn
= g_new0(burst_bucket
, 1);
646 bn
->bucket_no
= current_bucket
;
647 bn
->start_time
= node
->st
->now
;
649 bn
->next
= search
->next
;
653 node
->bcount
+= value
;
655 if (node
->bcount
>node
->max_burst
) {
656 /* new record burst */
657 node
->max_burst
= node
->bcount
;
658 node
->burst_time
= node
->bh
->start_time
;
663 * Increases by delta the counter of the node whose name is given
664 * if the node does not exist yet it's created (with counter=1)
665 * using parent_name as parent node.
666 * with_hash=true to indicate that the created node will have a parent
669 stats_tree_manip_node_int(manip_node_mode mode
, stats_tree
*st
, const char *name
,
670 int parent_id
, bool with_hash
, int value
)
672 stat_node
*node
= NULL
;
673 stat_node
*parent
= NULL
;
675 ws_assert( parent_id
>= 0 && parent_id
< (int) st
->parents
->len
);
677 parent
= (stat_node
*)g_ptr_array_index(st
->parents
,parent_id
);
680 node
= (stat_node
*)g_hash_table_lookup(parent
->hash
,name
);
682 node
= (stat_node
*)g_hash_table_lookup(st
->names
,name
);
686 node
= new_stat_node(st
,name
,parent_id
,STAT_DT_INT
,with_hash
,with_hash
);
690 node
->counter
+= value
;
691 update_burst_calc(node
, value
);
694 node
->counter
= value
;
698 update_burst_calc(node
, 1);
699 /* fall through */ /*to average code */
700 case MN_AVERAGE_NOTICK
:
701 node
->total
.int_total
+= value
;
702 if (node
->minvalue
.int_min
> value
) {
703 node
->minvalue
.int_min
= value
;
705 if (node
->maxvalue
.int_max
< value
) {
706 node
->maxvalue
.int_max
= value
;
708 node
->st_flags
|= ST_FLG_AVERAGE
;
711 node
->st_flags
|= value
;
714 node
->st_flags
&= ~value
;
725 * Increases by delta the counter of the node whose name is given
726 * if the node does not exist yet it's created (with counter=1)
727 * using parent_name as parent node.
728 * with_hash=true to indicate that the created node will have a parent
731 stats_tree_manip_node_float(manip_node_mode mode
, stats_tree
*st
, const char *name
,
732 int parent_id
, bool with_hash
, float value
)
734 stat_node
*node
= NULL
;
735 stat_node
*parent
= NULL
;
737 ws_assert(parent_id
>= 0 && parent_id
< (int)st
->parents
->len
);
739 parent
= (stat_node
*)g_ptr_array_index(st
->parents
, parent_id
);
742 node
= (stat_node
*)g_hash_table_lookup(parent
->hash
, name
);
745 node
= (stat_node
*)g_hash_table_lookup(st
->names
, name
);
749 node
= new_stat_node(st
, name
, parent_id
, STAT_DT_FLOAT
, with_hash
, with_hash
);
754 update_burst_calc(node
, 1);
755 /* fall through */ /*to average code */
756 case MN_AVERAGE_NOTICK
:
757 node
->total
.float_total
+= value
;
758 if (node
->minvalue
.float_min
> value
) {
759 node
->minvalue
.float_min
= value
;
761 if (node
->maxvalue
.float_max
< value
) {
762 node
->maxvalue
.float_max
= value
;
764 node
->st_flags
|= ST_FLG_AVERAGE
;
767 //only average is currently supported
768 ws_assert_not_reached();
779 stats_tree_get_abbr(const char *opt_arg
)
783 /* XXX: this fails when tshark is given any options
785 ws_assert(opt_arg
!= NULL
);
787 for (i
=0; opt_arg
[i
] && opt_arg
[i
] != ','; i
++);
789 if (opt_arg
[i
] == ',') {
790 return g_strndup(opt_arg
,i
);
798 * This function accepts an input string which should define a long integer range.
799 * The normal result is a struct containing the floor and ceil value of this
802 * It is allowed to define a range string in the following ways :
804 * "0-10" -> { 0, 10 }
805 * "-0" -> { INT_MIN, 0 }
806 * "0-" -> { 0, INT_MAX }
807 * "-" -> { INT_MIN, INT_MAX }
809 * Note that this function is robust to buggy input string. If in some cases it
810 * returns NULL, it but may also return a pair with undefined values.
814 get_range(char *rngstr
)
819 split
= g_strsplit((char*)rngstr
,"-",2);
822 if (split
[0] == NULL
) {
827 rng
= g_new(range_pair_t
, 1);
829 if (split
[1] == NULL
) {
830 /* means we have a non empty string with no delimiter
831 * so it must be a single number */
832 rng
->floor
= (int)strtol(split
[0],NULL
,10);
833 rng
->ceil
= rng
->floor
;
835 /* string == "X-?" */
836 if (*(split
[0]) != '\0') {
837 rng
->floor
= (int)strtol(split
[0],NULL
,10);
840 rng
->floor
= INT_MIN
;
844 if (*(split
[1]) != '\0') {
845 rng
->ceil
= (int)strtol(split
[1],NULL
,10);
858 stats_tree_create_range_node(stats_tree
*st
, const char *name
, int parent_id
, ...)
862 stat_node
*rng_root
= new_stat_node(st
, name
, parent_id
, STAT_DT_INT
, false, true);
863 stat_node
*range_node
= NULL
;
865 va_start( list
, parent_id
);
866 while (( curr_range
= va_arg(list
, char*) )) {
867 range_node
= new_stat_node(st
, curr_range
, rng_root
->id
, STAT_DT_INT
, false, false);
868 range_node
->rng
= get_range(curr_range
);
876 stats_tree_create_range_node_string(stats_tree
*st
, const char *name
,
877 int parent_id
, int num_str_ranges
,
881 stat_node
*rng_root
= new_stat_node(st
, name
, parent_id
, STAT_DT_INT
, false, true);
882 stat_node
*range_node
= NULL
;
884 for (i
= 0; i
< num_str_ranges
- 1; i
++) {
885 range_node
= new_stat_node(st
, str_ranges
[i
], rng_root
->id
, STAT_DT_INT
, false, false);
886 range_node
->rng
= get_range(str_ranges
[i
]);
888 range_node
= new_stat_node(st
, str_ranges
[i
], rng_root
->id
, STAT_DT_INT
, false, false);
889 range_node
->rng
= get_range(str_ranges
[i
]);
890 if (range_node
->rng
->floor
== range_node
->rng
->ceil
) {
891 range_node
->rng
->ceil
= INT_MAX
;
899 stats_tree_parent_id_by_name(stats_tree
*st
, const char *parent_name
)
901 stat_node
*node
= (stat_node
*)g_hash_table_lookup(st
->names
,parent_name
);
906 return 0; /* XXX: this is the root should we return -1 instead?*/
911 stats_tree_range_node_with_pname(stats_tree
*st
, const char *name
,
912 const char *parent_name
, ...)
916 stat_node
*range_node
= NULL
;
917 int parent_id
= stats_tree_parent_id_by_name(st
,parent_name
);
918 stat_node
*rng_root
= new_stat_node(st
, name
, parent_id
, STAT_DT_INT
, false, true);
920 va_start( list
, parent_name
);
921 while (( curr_range
= va_arg(list
, char*) )) {
922 range_node
= new_stat_node(st
, curr_range
, rng_root
->id
, STAT_DT_INT
, false, false);
923 range_node
->rng
= get_range(curr_range
);
932 stats_tree_tick_range(stats_tree
*st
, const char *name
, int parent_id
,
936 stat_node
*node
= NULL
;
937 stat_node
*parent
= NULL
;
938 stat_node
*child
= NULL
;
939 int stat_floor
, stat_ceil
;
941 if (parent_id
>= 0 && parent_id
< (int) st
->parents
->len
) {
942 parent
= (stat_node
*)g_ptr_array_index(st
->parents
,parent_id
);
944 ws_assert_not_reached();
948 node
= (stat_node
*)g_hash_table_lookup(parent
->hash
,name
);
950 node
= (stat_node
*)g_hash_table_lookup(st
->names
,name
);
954 ws_assert_not_reached();
956 /* update stats for container node. counter should already be ticked so we only update total and min/max */
957 node
->total
.int_total
+= value_in_range
;
958 if (node
->minvalue
.int_min
> value_in_range
) {
959 node
->minvalue
.int_min
= value_in_range
;
961 if (node
->maxvalue
.int_max
< value_in_range
) {
962 node
->maxvalue
.int_max
= value_in_range
;
964 node
->st_flags
|= ST_FLG_AVERAGE
;
966 for ( child
= node
->children
; child
; child
= child
->next
) {
967 stat_floor
= child
->rng
->floor
;
968 stat_ceil
= child
->rng
->ceil
;
970 if ( value_in_range
>= stat_floor
&& value_in_range
<= stat_ceil
) {
972 child
->total
.int_total
+= value_in_range
;
973 if (child
->minvalue
.int_min
> value_in_range
) {
974 child
->minvalue
.int_min
= value_in_range
;
976 if (child
->maxvalue
.int_max
< value_in_range
) {
977 child
->maxvalue
.int_max
= value_in_range
;
979 child
->st_flags
|= ST_FLG_AVERAGE
;
980 update_burst_calc(child
, 1);
989 stats_tree_create_pivot(stats_tree
*st
, const char *name
, int parent_id
)
991 stat_node
*node
= new_stat_node(st
,name
,parent_id
,STAT_DT_INT
,true,true);
1000 stats_tree_create_pivot_by_pname(stats_tree
*st
, const char *name
,
1001 const char *parent_name
)
1003 int parent_id
= stats_tree_parent_id_by_name(st
,parent_name
);
1006 node
= new_stat_node(st
,name
,parent_id
,STAT_DT_INT
,true,true);
1015 stats_tree_tick_pivot(stats_tree
*st
, int pivot_id
, const char *pivot_value
)
1017 stat_node
*parent
= (stat_node
*)g_ptr_array_index(st
->parents
,pivot_id
);
1020 update_burst_calc(parent
, 1);
1021 stats_tree_manip_node_int( MN_INCREASE
, st
, pivot_value
, pivot_id
, false, 1);
1027 stats_tree_get_displayname (char* fullname
)
1029 char *buf
= g_strdup(fullname
);
1032 if (prefs
.st_sort_showfullname
) {
1033 return buf
; /* unmodified */
1037 while ((sep
= strchr(sep
,'/')) != NULL
) {
1038 if (*(++sep
)=='/') { /* escaped slash - two slash characters after each other */
1039 memmove(sep
,sep
+1,strlen(sep
));
1042 /* we got a new path separator */
1043 memmove(buf
,sep
,strlen(sep
)+1);
1052 stats_tree_get_default_sort_col (stats_tree
*st
)
1054 switch ((st
->st_flags
&ST_FLG_SRTCOL_MASK
)>>ST_FLG_SRTCOL_SHIFT
) {
1055 case ST_SORT_COL_NAME
:
1057 case ST_SORT_COL_COUNT
:
1059 case ST_SORT_COL_AVG
:
1061 case ST_SORT_COL_MIN
:
1063 case ST_SORT_COL_MAX
:
1065 case ST_SORT_COL_BURSTRATE
:
1066 return COL_BURSTRATE
;
1068 return COL_COUNT
; /* nothing specific set */
1072 stats_tree_is_default_sort_DESC (stats_tree
*st
)
1074 return st
->st_flags
&ST_FLG_SORT_DESC
;
1078 stats_tree_get_column_name (stats_tree_cfg
*st_config
, int col_index
)
1080 switch (col_index
) {
1082 if (st_config
->first_column_name
) {
1083 return st_config
->first_column_name
;
1085 return "Topic / Item";
1099 return prefs
.st_burst_showcount
? "Burst Count" : "Burst Rate";
1101 return "Burst Start";
1108 stats_tree_get_column_size (int col_index
)
1110 if (col_index
==COL_NAME
) {
1111 return 36; /* but caller should really call stats_tree_branch_max_namelen() */
1113 if (col_index
<N_COLUMNS
) {
1114 return 12; /* all numerical values are this size */
1116 return 0; /* invalid column */
1120 stats_tree_get_values_from_node (const stat_node
* node
)
1122 char **values
= (char**) g_malloc0(sizeof(char*)*(node
->st
->num_columns
));
1124 values
[COL_NAME
] = (node
->st_flags
&ST_FLG_ROOTCHILD
)?stats_tree_get_displayname(node
->name
):g_strdup(node
->name
);
1125 values
[COL_COUNT
] = ws_strdup_printf("%u",node
->counter
);
1126 if (((node
->st_flags
&ST_FLG_AVERAGE
) || node
->rng
)) {
1127 if (node
->counter
) {
1128 switch (node
->datatype
)
1131 values
[COL_AVERAGE
] = ws_strdup_printf("%.2f", ((float)node
->total
.int_total
) / node
->counter
);
1134 values
[COL_AVERAGE
] = ws_strdup_printf("%.2f", node
->total
.float_total
/ node
->counter
);
1138 values
[COL_AVERAGE
] = g_strdup("-");
1141 values
[COL_AVERAGE
] = g_strdup("");
1144 if (((node
->st_flags
&ST_FLG_AVERAGE
) || node
->rng
)) {
1145 if (node
->counter
) {
1146 switch (node
->datatype
)
1149 values
[COL_MIN
] = ws_strdup_printf("%d", node
->minvalue
.int_min
);
1152 values
[COL_MIN
] = ws_strdup_printf("%f", node
->minvalue
.float_min
);
1157 values
[COL_MIN
] = g_strdup("-");
1161 values
[COL_MIN
] = g_strdup("");
1164 if (((node
->st_flags
&ST_FLG_AVERAGE
) || node
->rng
)) {
1165 if (node
->counter
) {
1166 switch (node
->datatype
)
1169 values
[COL_MAX
] = ws_strdup_printf("%d", node
->maxvalue
.int_max
);
1172 values
[COL_MAX
] = ws_strdup_printf("%f", node
->maxvalue
.float_max
);
1177 values
[COL_MAX
] = g_strdup("-");
1181 values
[COL_MAX
] = g_strdup("");
1184 values
[COL_RATE
] = (node
->st
->elapsed
)?ws_strdup_printf("%.4f",((float)node
->counter
)/node
->st
->elapsed
):g_strdup("");
1185 values
[COL_PERCENT
] = ((node
->parent
)&&(node
->parent
->counter
))?
1186 ws_strdup_printf("%.2f%%",(node
->counter
*100.0)/node
->parent
->counter
):
1187 (node
->parent
==&(node
->st
->root
)?g_strdup("100%"):g_strdup(""));
1188 if (node
->st
->num_columns
>COL_BURSTTIME
) {
1189 values
[COL_BURSTRATE
] = (!prefs
.st_enable_burstinfo
)?g_strdup(""):
1190 (node
->max_burst
?(prefs
.st_burst_showcount
?
1191 ws_strdup_printf("%d",node
->max_burst
):
1192 ws_strdup_printf("%.4f",((double)node
->max_burst
)/prefs
.st_burst_windowlen
)):
1194 values
[COL_BURSTTIME
] = (!prefs
.st_enable_burstinfo
)?g_strdup(""):
1195 (node
->max_burst
?ws_strdup_printf("%.3f",(node
->burst_time
/1000.0)):g_strdup("-"));
1201 stats_tree_sort_compare (const stat_node
*a
, const stat_node
*b
, int sort_column
,
1202 bool sort_descending
)
1205 float avg_a
= 0, avg_b
= 0;
1207 if (prefs
.st_sort_rng_nameonly
&&(a
->rng
&&b
->rng
)) {
1208 /* always sort ranges by range name */
1209 result
= a
->rng
->floor
- b
->rng
->floor
;
1210 if (sort_descending
&&(!prefs
.st_sort_rng_fixorder
)) {
1216 switch (sort_column
) {
1218 if (a
->rng
&&b
->rng
) {
1219 result
= a
->rng
->floor
- b
->rng
->floor
;
1221 else if (prefs
.st_sort_casesensitve
) {
1222 result
= strcmp(a
->name
,b
->name
);
1225 result
= g_ascii_strcasecmp(a
->name
,b
->name
);
1232 result
= a
->counter
- b
->counter
;
1236 switch (a
->datatype
)
1239 avg_a
= a
->counter
? ((float)a
->total
.int_total
)/a
->counter
: 0;
1240 avg_b
= b
->counter
? ((float)b
->total
.int_total
)/b
->counter
: 0;
1243 avg_a
= a
->counter
? ((float)a
->total
.float_total
) / a
->counter
: 0;
1244 avg_b
= b
->counter
? ((float)b
->total
.float_total
) / b
->counter
: 0;
1247 result
= (avg_a
>avg_b
) ? 1 : ( (avg_a
<avg_b
) ? -1 : 0);
1251 switch (a
->datatype
)
1254 result
= a
->minvalue
.int_min
- b
->minvalue
.int_min
;
1257 result
= (a
->minvalue
.float_min
>b
->minvalue
.int_min
) ? 1 : ((a
->minvalue
.float_min
<b
->minvalue
.int_min
) ? -1 : 0);
1263 switch (a
->datatype
)
1266 result
= a
->maxvalue
.int_max
- b
->maxvalue
.int_max
;
1269 result
= (a
->maxvalue
.float_max
>b
->maxvalue
.float_max
) ? 1 : ((a
->maxvalue
.float_max
<b
->maxvalue
.float_max
) ? -1 : 0);
1275 result
= a
->max_burst
- b
->max_burst
;
1279 result
= (a
->burst_time
>b
->burst_time
)?1:((a
->burst_time
<b
->burst_time
)?-1:0);
1283 /* no sort comparison found for column - must update this switch statement */
1284 ws_assert_not_reached();
1287 /* break tie between items with same primary search result */
1289 if (sort_column
==COL_NAME
) {
1290 result
= a
->counter
- b
->counter
;
1293 if (a
->rng
&&b
->rng
) {
1294 result
= a
->rng
->floor
- b
->rng
->floor
;
1296 else if (prefs
.st_sort_casesensitve
) {
1297 result
= strcmp(a
->name
,b
->name
);
1300 result
= g_ascii_strcasecmp(a
->name
,b
->name
);
1305 /* take into account sort order */
1306 if (sort_descending
) {
1310 if ((a
->st_flags
&ST_FLG_SORT_TOP
)!=(b
->st_flags
&ST_FLG_SORT_TOP
)) {
1311 /* different sort groups top vs non-top */
1312 result
= (a
->st_flags
&ST_FLG_SORT_TOP
)?-1:1;
1318 stats_tree_format_as_str(const stats_tree
* st
, st_format_type format_type
,
1319 int sort_column
, bool sort_descending
)
1321 int maxnamelen
= stats_tree_branch_max_namelen(&st
->root
,0);
1325 char *separator
= NULL
;
1327 switch(format_type
) {
1328 case ST_FORMAT_YAML
:
1329 s
= g_string_new("---\n");
1332 s
= g_string_new("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1335 s
= g_string_new("\"level\",\"parent\",");
1336 for (count
= 0; count
<st
->num_columns
; count
++) {
1337 g_string_append_printf(s
,"\"%s\",",stats_tree_get_column_name(st
->cfg
, count
));
1339 g_string_append (s
,"\n");
1341 case ST_FORMAT_PLAIN
:
1346 sep_length
= maxnamelen
;
1347 for (count
= 1; count
<st
->num_columns
; count
++) {
1348 sep_length
+= stats_tree_get_column_size(count
)+2;
1350 separator
= (char *)g_malloc(sep_length
+1);
1351 memset (separator
, '=', sep_length
);
1352 separator
[sep_length
] = 0;
1354 s
= g_string_new("\n");
1355 g_string_append(s
,separator
);
1356 g_string_append_printf(s
,"\n%s:\n",st
->cfg
->title
);
1357 snprintf (fmt
,sizeof(fmt
),"%%-%us",maxnamelen
);
1358 g_string_append_printf(s
,fmt
,stats_tree_get_column_name(st
->cfg
, 0));
1359 for (count
= 1; count
<st
->num_columns
; count
++) {
1360 snprintf (fmt
,sizeof(fmt
)," %%-%ds",stats_tree_get_column_size(count
)+1);
1361 g_string_append_printf(s
,fmt
,stats_tree_get_column_name(st
->cfg
, count
));
1363 memset (separator
, '-', sep_length
);
1364 g_string_append_printf(s
,"\n%s\n",separator
);
1368 return g_string_new("unknown format for stats_tree\n");
1371 for (child
= st
->root
.children
; child
; child
= child
->next
) {
1372 stats_tree_format_node_as_str(child
,s
,format_type
,0,"",maxnamelen
,sort_column
,sort_descending
);
1376 if (format_type
==ST_FORMAT_PLAIN
) {
1377 g_string_append_printf(s
,"\n%s\n",separator
);
1386 bool sort_descending
;
1389 /* Function to compare elements for child array sort. a and b are children, user_data
1390 points to a st_flags value */
1392 stat_node_array_sortcmp (const void *a
, const void *b
, void *user_data
)
1394 /* user_data is *unsigned value to st_flags */
1395 return stats_tree_sort_compare (*(const stat_node
*const*)a
,*(const stat_node
*const*)b
,
1396 ((sortinfo
*)user_data
)->sort_column
,((sortinfo
*)user_data
)->sort_descending
);
1400 clean_for_xml_tag (char *str
)
1403 while ((s
=strpbrk(s
,"!\"#$%%&'()*+,/;<=>?@[\\]^`{|}~ ")) != NULL
) {
1409 /** helper function to add note to formatted stats_tree */
1410 // NOLINTNEXTLINE(misc-no-recursion)
1411 WS_DLL_PUBLIC
void stats_tree_format_node_as_str(const stat_node
*node
,
1413 st_format_type format_type
,
1418 bool sort_descending
)
1421 int num_columns
= node
->st
->num_columns
;
1422 char **values
= stats_tree_get_values_from_node(node
);
1426 char fmt
[16] = "%s%s%s";
1428 switch(format_type
) {
1429 case ST_FORMAT_YAML
:
1431 snprintf(fmt
, sizeof(fmt
), "%%%ds%%s%%s", indent
*4-2);
1433 g_string_append_printf(s
, fmt
, "", indent
?"- ":"", "Description");
1434 g_string_append_printf(s
, ": \"%s\"\n", values
[0]);
1436 for (count
= 1; count
<num_columns
; count
++) {
1437 if (*values
[count
]) {
1438 g_string_append_printf(s
, fmt
, "", indent
?" ":"",
1439 stats_tree_get_column_name(node
->st
->cfg
, count
));
1440 g_string_append_printf(s
, ": %s\n", values
[count
]);
1443 if (node
->children
) {
1444 g_string_append_printf(s
, fmt
, "", indent
?" ":"", "Items:\n");
1449 char *itemname
= xml_escape(values
[0]);
1450 g_string_append_printf(s
,"<stat-node name=\"%s\"%s>\n",itemname
,
1451 node
->rng
?" isrange=\"true\"":"");
1453 for (count
= 1; count
<num_columns
; count
++) {
1454 char *colname
= g_strdup(stats_tree_get_column_name(node
->st
->cfg
, count
));
1455 g_string_append_printf(s
,"<%s>",clean_for_xml_tag(colname
));
1456 g_string_append_printf(s
,"%s</%s>\n",values
[count
],colname
);
1462 g_string_append_printf(s
,"%d,\"%s\",\"%s\"",indent
,path
,values
[0]);
1463 for (count
= 1; count
<num_columns
; count
++) {
1464 g_string_append_printf(s
,",%s",values
[count
]);
1466 g_string_append (s
,"\n");
1468 case ST_FORMAT_PLAIN
:
1469 snprintf (fmt
,sizeof(fmt
),"%%%ds%%-%us",indent
,maxnamelen
-indent
);
1470 g_string_append_printf(s
,fmt
,"",values
[0]);
1471 for (count
= 1; count
<num_columns
; count
++) {
1472 snprintf (fmt
,sizeof(fmt
)," %%-%us",stats_tree_get_column_size(count
)+1);
1473 g_string_append_printf(s
,fmt
,values
[count
]);
1475 g_string_append (s
,"\n");
1480 indent
= indent
> INDENT_MAX
? INDENT_MAX
: indent
;
1481 full_path
= ws_strdup_printf ("%s/%s",path
,values
[0]);
1483 for (count
= 0; count
<num_columns
; count
++) {
1484 g_free(values
[count
]);
1488 if (node
->children
) {
1489 GArray
*Children
= g_array_new(false,false,sizeof(child
));
1490 for (child
= node
->children
; child
; child
= child
->next
) {
1491 g_array_append_val(Children
,child
);
1493 si
.sort_column
= sort_column
;
1494 si
.sort_descending
= sort_descending
;
1495 g_array_sort_with_data(Children
,stat_node_array_sortcmp
,&si
);
1496 for (count
= 0; count
<((int)Children
->len
); count
++) {
1497 stats_tree_format_node_as_str(g_array_index(Children
,stat_node
*,count
), s
, format_type
,
1498 indent
, full_path
, maxnamelen
, sort_column
, sort_descending
);
1500 g_array_free(Children
, true);
1504 if (format_type
==ST_FORMAT_XML
) {
1505 g_string_append(s
,"</stat-node>\n");
1509 void stats_tree_cleanup(void)
1511 g_hash_table_destroy(registry
);
1515 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1520 * indent-tabs-mode: nil
1523 * vi: set shiftwidth=4 tabstop=8 expandtab:
1524 * :indentSize=4:tabSize=8:noTabs=true: