/src/wireshark/epan/stats_tree.c
Line | Count | Source |
1 | | /* stats_tree.c |
2 | | * API for a counter tree for Wireshark |
3 | | * 2004, Luis E. G. Ontanon |
4 | | * |
5 | | * Wireshark - Network traffic analyzer |
6 | | * By Gerald Combs <gerald@wireshark.org> |
7 | | * Copyright 1998 Gerald Combs |
8 | | * |
9 | | * SPDX-License-Identifier: GPL-2.0-or-later |
10 | | */ |
11 | | |
12 | | /* stats_tree modifications by Deon van der Westhuysen, November 2013 |
13 | | * support for |
14 | | * - sorting by column, |
15 | | * - calculation of average values |
16 | | * - calculation of burst rate |
17 | | * - export to text, CSV or XML file |
18 | | */ |
19 | | |
20 | | #include "config.h" |
21 | | |
22 | | #include <glib.h> |
23 | | |
24 | | #include <stdlib.h> |
25 | | |
26 | | #include <epan/stats_tree_priv.h> |
27 | | #include <epan/prefs.h> |
28 | | #include <math.h> |
29 | | #include <string.h> |
30 | | |
31 | | #include "strutil.h" |
32 | | #include "stats_tree.h" |
33 | | #include <wsutil/ws_assert.h> |
34 | | |
35 | | enum _stat_tree_columns { |
36 | | COL_NAME, |
37 | | COL_COUNT, |
38 | | COL_AVERAGE, |
39 | | COL_MIN, |
40 | | COL_MAX, |
41 | | COL_RATE, |
42 | | COL_PERCENT, |
43 | | COL_BURSTRATE, |
44 | | COL_BURSTTIME, |
45 | | N_COLUMNS |
46 | | }; |
47 | | |
48 | | /* used to contain the registered stat trees */ |
49 | | static GHashTable *registry; |
50 | | |
51 | | /* a text representation of a node |
52 | | if buffer is NULL returns a newly allocated string */ |
53 | | char* |
54 | | stats_tree_node_to_str(const stat_node *node, char *buffer, unsigned len) |
55 | 0 | { |
56 | 0 | if (buffer) { |
57 | 0 | snprintf(buffer,len,"%s: %i",node->name, node->counter); |
58 | 0 | return buffer; |
59 | 0 | } else { |
60 | 0 | return ws_strdup_printf("%s: %i",node->name, node->counter); |
61 | 0 | } |
62 | 0 | } |
63 | | |
64 | | unsigned |
65 | | // NOLINTNEXTLINE(misc-no-recursion) |
66 | | stats_tree_branch_max_namelen(const stat_node *node, unsigned indent) |
67 | 0 | { |
68 | 0 | stat_node *child; |
69 | 0 | unsigned maxlen = 0; |
70 | 0 | unsigned len; |
71 | |
|
72 | 0 | indent = indent > INDENT_MAX ? INDENT_MAX : indent; |
73 | |
|
74 | 0 | if (node->children) { |
75 | 0 | for (child = node->children; child; child = child->next ) { |
76 | | // Recursion is limited by proto.c checks |
77 | 0 | len = stats_tree_branch_max_namelen(child,indent+1); |
78 | 0 | maxlen = len > maxlen ? len : maxlen; |
79 | 0 | } |
80 | 0 | } |
81 | |
|
82 | 0 | if (node->st_flags&ST_FLG_ROOTCHILD) { |
83 | 0 | char *display_name = stats_tree_get_displayname(node->name); |
84 | 0 | len = (unsigned) strlen(display_name) + indent; |
85 | 0 | g_free(display_name); |
86 | 0 | } |
87 | 0 | else { |
88 | 0 | len = (unsigned) strlen(node->name) + indent; |
89 | 0 | } |
90 | 0 | maxlen = len > maxlen ? len : maxlen; |
91 | |
|
92 | 0 | return maxlen; |
93 | 0 | } |
94 | | |
95 | | /* frees the resources allocated by a stat_tree node */ |
96 | | static void |
97 | | // NOLINTNEXTLINE(misc-no-recursion) |
98 | | free_stat_node(stat_node *node) |
99 | 0 | { |
100 | 0 | stat_node *child; |
101 | 0 | stat_node *next; |
102 | 0 | burst_bucket *bucket; |
103 | |
|
104 | 0 | if (node->children) { |
105 | 0 | for (child = node->children; child; child = next ) { |
106 | | /* child->next will be gone after free_stat_node, so cache it here */ |
107 | 0 | next = child->next; |
108 | | // Recursion is limited by proto.c checks |
109 | 0 | free_stat_node(child); |
110 | 0 | } |
111 | 0 | } |
112 | |
|
113 | 0 | if (node->hash) g_hash_table_destroy(node->hash); |
114 | |
|
115 | 0 | while (node->bh) { |
116 | 0 | bucket = node->bh; |
117 | 0 | node->bh = bucket->next; |
118 | 0 | g_free(bucket); |
119 | 0 | } |
120 | |
|
121 | 0 | g_free(node->rng); |
122 | 0 | g_free(node->name); |
123 | 0 | g_free(node); |
124 | 0 | } |
125 | | |
126 | | /* destroys the whole tree instance */ |
127 | | void |
128 | | stats_tree_free(stats_tree *st) |
129 | 0 | { |
130 | 0 | stat_node *child; |
131 | 0 | stat_node *next; |
132 | |
|
133 | 0 | if (!st) return; |
134 | | |
135 | 0 | g_free(st->filter); |
136 | 0 | g_hash_table_destroy(st->names); |
137 | 0 | g_ptr_array_free(st->parents,true); |
138 | 0 | g_free(st->display_name); |
139 | | |
140 | | /* st->root is not allocated with malloc, so we have to call |
141 | | * free_stat_node on each child and free the root's dynamically |
142 | | * allocated members instead of calling free_stat_node(&str->root) |
143 | | */ |
144 | 0 | for (child = st->root.children; child; child = next ) { |
145 | | /* child->next will be gone after free_stat_node, so cache it here */ |
146 | 0 | next = child->next; |
147 | 0 | free_stat_node(child); |
148 | 0 | } |
149 | 0 | g_free(st->root.name); |
150 | 0 | g_free(st->root.bh); |
151 | |
|
152 | 0 | if (st->cfg->free_tree_pr) |
153 | 0 | st->cfg->free_tree_pr(st); |
154 | |
|
155 | 0 | if (st->cfg->cleanup) |
156 | 0 | st->cfg->cleanup(st); |
157 | |
|
158 | 0 | g_free(st); |
159 | 0 | } |
160 | | |
161 | | |
162 | | /* reset a node to its original state */ |
163 | | static void |
164 | | // NOLINTNEXTLINE(misc-no-recursion) |
165 | | reset_stat_node(stat_node *node) |
166 | 0 | { |
167 | 0 | stat_node *child; |
168 | 0 | burst_bucket *bucket; |
169 | |
|
170 | 0 | node->counter = 0; |
171 | 0 | switch (node->datatype) |
172 | 0 | { |
173 | 0 | case STAT_DT_INT: |
174 | 0 | node->total.int_total = 0; |
175 | 0 | node->minvalue.int_min = INT_MAX; |
176 | 0 | node->maxvalue.int_max = INT_MIN; |
177 | 0 | break; |
178 | 0 | case STAT_DT_FLOAT: |
179 | 0 | node->total.float_total = 0; |
180 | 0 | node->minvalue.float_min = FLT_MAX; |
181 | 0 | node->maxvalue.float_max = -FLT_MAX; |
182 | 0 | break; |
183 | 0 | } |
184 | 0 | node->st_flags = 0; |
185 | |
|
186 | 0 | while (node->bh) { |
187 | 0 | bucket = node->bh; |
188 | 0 | node->bh = bucket->next; |
189 | 0 | g_free(bucket); |
190 | 0 | } |
191 | 0 | node->bh = g_new0(burst_bucket, 1); |
192 | 0 | node->bt = node->bh; |
193 | 0 | node->bcount = 0; |
194 | 0 | node->max_burst = 0; |
195 | 0 | node->burst_time = -1.0; |
196 | |
|
197 | 0 | if (node->children) { |
198 | 0 | for (child = node->children; child; child = child->next ) |
199 | | // Recursion is limited by proto.c checks |
200 | 0 | reset_stat_node(child); |
201 | 0 | } |
202 | 0 | } |
203 | | |
204 | | /* reset the whole stats_tree */ |
205 | | void |
206 | | stats_tree_reset(void *p) |
207 | 0 | { |
208 | 0 | stats_tree *st = (stats_tree *)p; |
209 | |
|
210 | 0 | st->start = -1.0; |
211 | 0 | st->elapsed = 0.0; |
212 | 0 | st->now = - 1.0; |
213 | |
|
214 | 0 | reset_stat_node(&st->root); |
215 | 0 | } |
216 | | |
217 | | void |
218 | | stats_tree_reinit(void *p) |
219 | 0 | { |
220 | 0 | stats_tree *st = (stats_tree *)p; |
221 | 0 | stat_node *child; |
222 | 0 | stat_node *next; |
223 | |
|
224 | 0 | for (child = st->root.children; child; child = next) { |
225 | | /* child->next will be gone after free_stat_node, so cache it here */ |
226 | 0 | next = child->next; |
227 | 0 | free_stat_node(child); |
228 | 0 | } |
229 | |
|
230 | 0 | st->root.children = NULL; |
231 | 0 | st->root.counter = 0; |
232 | 0 | switch (st->root.datatype) |
233 | 0 | { |
234 | 0 | case STAT_DT_INT: |
235 | 0 | st->root.total.int_total = 0; |
236 | 0 | st->root.minvalue.int_min = INT_MAX; |
237 | 0 | st->root.maxvalue.int_max = INT_MIN; |
238 | 0 | break; |
239 | 0 | case STAT_DT_FLOAT: |
240 | 0 | st->root.total.float_total = 0; |
241 | 0 | st->root.minvalue.float_min = FLT_MAX; |
242 | 0 | st->root.maxvalue.float_max = -FLT_MAX; |
243 | 0 | break; |
244 | 0 | } |
245 | 0 | st->root.st_flags = 0; |
246 | |
|
247 | 0 | g_free(st->root.bh); |
248 | 0 | st->root.bh = g_new0(burst_bucket, 1); |
249 | 0 | st->root.bt = st->root.bh; |
250 | 0 | st->root.bcount = 0; |
251 | 0 | st->root.max_burst = 0; |
252 | 0 | st->root.burst_time = -1.0; |
253 | | |
254 | | /* No more stat_nodes left in tree - clean out hash, array */ |
255 | 0 | g_hash_table_remove_all(st->names); |
256 | 0 | if (st->parents->len>1) { |
257 | 0 | g_ptr_array_remove_range(st->parents, 1, st->parents->len-1); |
258 | 0 | } |
259 | | |
260 | | /* Do not update st_flags for the tree (sorting) - leave as was */ |
261 | 0 | st->num_columns = N_COLUMNS; |
262 | | /* XXX - Do we really need to recreate the display name? */ |
263 | 0 | g_free(st->display_name); |
264 | 0 | st->display_name = stats_tree_get_displayname(st->cfg->path); |
265 | |
|
266 | 0 | if (st->cfg->init) { |
267 | 0 | st->cfg->init(st); |
268 | 0 | } |
269 | 0 | } |
270 | | |
271 | | static void |
272 | | stats_tree_free_configuration(void *p) |
273 | 0 | { |
274 | 0 | stats_tree_cfg* cfg = (stats_tree_cfg*)p; |
275 | 0 | g_free(cfg->tapname); |
276 | 0 | g_free(cfg->abbr); |
277 | 0 | g_free(cfg->path); |
278 | 0 | g_free(cfg->title); |
279 | 0 | g_free(cfg->first_column_name); |
280 | 0 | g_free(cfg); |
281 | 0 | } |
282 | | |
283 | | void |
284 | | stats_tree_init(void) |
285 | 15 | { |
286 | 15 | registry = g_hash_table_new_full(g_str_hash,g_str_equal,NULL,stats_tree_free_configuration); |
287 | 15 | } |
288 | | |
289 | | /* register a new stats_tree */ |
290 | | stats_tree_cfg * |
291 | | stats_tree_register(const char *tapname, const char *abbr, const char *path, |
292 | | unsigned flags, |
293 | | stat_tree_packet_cb packet, stat_tree_init_cb init, |
294 | | stat_tree_cleanup_cb cleanup) |
295 | 720 | { |
296 | 720 | stats_tree_cfg *cfg = g_new0(stats_tree_cfg, 1); |
297 | | |
298 | | /* at the very least the abbrev and the packet function should be given */ |
299 | 720 | ws_assert( tapname && abbr && packet ); |
300 | | |
301 | 720 | cfg->tapname = g_strdup(tapname); |
302 | 720 | cfg->abbr = g_strdup(abbr); |
303 | 720 | cfg->path = path ? g_strdup(path) : g_strdup(abbr); |
304 | 720 | cfg->stat_group = REGISTER_PACKET_STAT_GROUP_UNSORTED; |
305 | | |
306 | 720 | GString *title_str = g_string_new(""); |
307 | 720 | char **split = g_strsplit(path, STATS_TREE_MENU_SEPARATOR, 0); |
308 | 720 | const char *sep = ""; |
309 | 1.77k | for (size_t idx = 0; split[idx]; idx++) { |
310 | 1.05k | g_string_append_printf(title_str, "%s%s", sep, g_strstrip(split[idx])); |
311 | 1.05k | sep = " / "; |
312 | 1.05k | } |
313 | 720 | g_strfreev(split); |
314 | 720 | cfg->title = g_string_free(title_str, FALSE); |
315 | | |
316 | 720 | cfg->packet = packet; |
317 | 720 | cfg->init = init; |
318 | 720 | cfg->cleanup = cleanup; |
319 | | |
320 | 720 | cfg->flags = flags&~ST_FLG_MASK; |
321 | 720 | cfg->st_flags = flags&ST_FLG_MASK; |
322 | | |
323 | 720 | if (!registry) registry = g_hash_table_new_full(g_str_hash,g_str_equal,NULL,stats_tree_free_configuration); |
324 | | |
325 | 720 | g_hash_table_insert(registry,cfg->abbr,cfg); |
326 | | |
327 | 720 | return cfg; |
328 | 720 | } |
329 | | |
330 | | /* register a new stat_tree with default group REGISTER_PACKET_STAT_GROUP_UNSORTED from a plugin */ |
331 | | stats_tree_cfg * |
332 | | stats_tree_register_plugin(const char *tapname, const char *abbr, const char *path, |
333 | | unsigned flags, |
334 | | stat_tree_packet_cb packet, stat_tree_init_cb init, |
335 | | stat_tree_cleanup_cb cleanup) |
336 | 135 | { |
337 | 135 | stats_tree_cfg *cfg = stats_tree_register(tapname, abbr, path, |
338 | 135 | flags, packet, init, cleanup); |
339 | 135 | cfg->plugin = true; |
340 | | |
341 | 135 | return cfg; |
342 | 135 | } |
343 | | |
344 | | void |
345 | 45 | stats_tree_set_group(stats_tree_cfg *st_config, register_stat_group_t stat_group) { |
346 | 45 | if (st_config) { |
347 | 45 | st_config->stat_group = stat_group; |
348 | 45 | } |
349 | 45 | } |
350 | | |
351 | | void |
352 | 75 | stats_tree_set_first_column_name(stats_tree_cfg *st_config, const char *column_name) { |
353 | 75 | if (st_config) { |
354 | 75 | st_config->first_column_name = g_strdup(column_name); |
355 | 75 | } |
356 | 75 | } |
357 | | |
358 | | stats_tree* |
359 | | stats_tree_new(stats_tree_cfg *cfg, tree_pres *pr, const char *filter) |
360 | 0 | { |
361 | 0 | stats_tree *st = g_new0(stats_tree, 1); |
362 | |
|
363 | 0 | st->cfg = cfg; |
364 | 0 | st->pr = pr; |
365 | |
|
366 | 0 | st->names = g_hash_table_new(g_str_hash,g_str_equal); |
367 | 0 | st->parents = g_ptr_array_new(); |
368 | 0 | st->filter = g_strdup(filter); |
369 | |
|
370 | 0 | st->start = -1.0; |
371 | 0 | st->elapsed = 0.0; |
372 | |
|
373 | 0 | switch (st->root.datatype) |
374 | 0 | { |
375 | 0 | case STAT_DT_INT: |
376 | 0 | st->root.minvalue.int_min = INT_MAX; |
377 | 0 | st->root.maxvalue.int_max = INT_MIN; |
378 | 0 | break; |
379 | 0 | case STAT_DT_FLOAT: |
380 | 0 | st->root.minvalue.float_min = FLT_MAX; |
381 | 0 | st->root.maxvalue.float_max = -FLT_MAX; |
382 | 0 | break; |
383 | 0 | } |
384 | | |
385 | 0 | st->root.bh = g_new0(burst_bucket, 1); |
386 | 0 | st->root.bt = st->root.bh; |
387 | 0 | st->root.burst_time = -1.0; |
388 | |
|
389 | 0 | st->root.name = stats_tree_get_displayname(cfg->path); |
390 | 0 | st->root.st = st; |
391 | |
|
392 | 0 | st->st_flags = st->cfg->st_flags; |
393 | |
|
394 | 0 | if (!(st->st_flags&ST_FLG_SRTCOL_MASK)) { |
395 | | /* No default sort specified - use preferences */ |
396 | 0 | st->st_flags |= prefs.st_sort_defcolflag<<ST_FLG_SRTCOL_SHIFT; |
397 | 0 | if (prefs.st_sort_defdescending) { |
398 | 0 | st->st_flags |= ST_FLG_SORT_DESC; |
399 | 0 | } |
400 | 0 | } |
401 | 0 | st->num_columns = N_COLUMNS; |
402 | 0 | st->display_name = stats_tree_get_displayname(st->cfg->path); |
403 | |
|
404 | 0 | g_ptr_array_add(st->parents,&st->root); |
405 | |
|
406 | 0 | return st; |
407 | 0 | } |
408 | | |
409 | | /* will be the tap packet cb */ |
410 | | tap_packet_status |
411 | | stats_tree_packet(void *p, packet_info *pinfo, epan_dissect_t *edt, const void *pri, tap_flags_t flags) |
412 | 0 | { |
413 | 0 | stats_tree *st = (stats_tree *)p; |
414 | |
|
415 | 0 | st->now = nstime_to_msec(&pinfo->rel_ts); |
416 | 0 | if (st->start < 0.0) st->start = st->now; |
417 | |
|
418 | 0 | st->elapsed = st->now - st->start; |
419 | |
|
420 | 0 | if (st->cfg->packet) |
421 | 0 | return st->cfg->packet(st,pinfo,edt,pri, flags); |
422 | 0 | else |
423 | 0 | return TAP_PACKET_DONT_REDRAW; |
424 | 0 | } |
425 | | |
426 | | stats_tree_cfg* |
427 | | stats_tree_get_cfg_by_abbr(const char *abbr) |
428 | 0 | { |
429 | 0 | if (!abbr) return NULL; |
430 | 0 | return (stats_tree_cfg *)g_hash_table_lookup(registry,abbr); |
431 | 0 | } |
432 | | |
433 | | static int |
434 | | compare_stat_menu_item(const void *stat_a, const void *stat_b) |
435 | 0 | { |
436 | 0 | const stats_tree_cfg* stat_cfg_a = (const stats_tree_cfg*)stat_a; |
437 | 0 | const stats_tree_cfg* stat_cfg_b = (const stats_tree_cfg*)stat_b; |
438 | |
|
439 | 0 | return strcmp(stat_cfg_a->path, stat_cfg_b->path); |
440 | 0 | } |
441 | | |
442 | | GList* |
443 | | stats_tree_get_cfg_list(void) |
444 | 0 | { |
445 | 0 | GList* registry_list = g_hash_table_get_values(registry); |
446 | | /* Now sort the list so they can show up in the |
447 | | menu alphabetically */ |
448 | 0 | return g_list_sort(registry_list, compare_stat_menu_item); |
449 | |
|
450 | 0 | } |
451 | | |
452 | | struct _stats_tree_pres_cbs { |
453 | | void (*setup_node_pr)(stat_node*); |
454 | | void (*free_tree_pr)(stats_tree*); |
455 | | }; |
456 | | |
457 | | static void |
458 | | setup_tree_presentation(void *k _U_, void *v, void *p) |
459 | 0 | { |
460 | 0 | stats_tree_cfg *cfg = (stats_tree_cfg *)v; |
461 | 0 | struct _stats_tree_pres_cbs *d = (struct _stats_tree_pres_cbs *)p; |
462 | |
|
463 | 0 | cfg->setup_node_pr = d->setup_node_pr; |
464 | 0 | cfg->free_tree_pr = d->free_tree_pr; |
465 | |
|
466 | 0 | } |
467 | | |
468 | | void |
469 | | stats_tree_presentation(void (*registry_iterator)(void *,void *,void *), |
470 | | void (*setup_node_pr)(stat_node*), |
471 | | void (*free_tree_pr)(stats_tree*), |
472 | | void *data) |
473 | 0 | { |
474 | 0 | static struct _stats_tree_pres_cbs d; |
475 | |
|
476 | 0 | d.setup_node_pr = setup_node_pr; |
477 | 0 | d.free_tree_pr = free_tree_pr; |
478 | |
|
479 | 0 | if (registry) g_hash_table_foreach(registry,setup_tree_presentation,&d); |
480 | |
|
481 | 0 | if (registry_iterator && registry) |
482 | 0 | g_hash_table_foreach(registry,registry_iterator,data); |
483 | |
|
484 | 0 | } |
485 | | |
486 | | |
487 | | /* creates a stat_tree node |
488 | | * name: the name of the stats_tree node |
489 | | * parent_name: the name of the ALREADY REGISTERED parent |
490 | | * with_hash: whether or not it should keep a hash with its children names |
491 | | * as_named_node: whether or not it has to be registered in the root namespace |
492 | | */ |
493 | | static stat_node* |
494 | | new_stat_node(stats_tree *st, const char *name, int parent_id, stat_node_datatype datatype, |
495 | | bool with_hash, bool as_parent_node) |
496 | 0 | { |
497 | |
|
498 | 0 | stat_node *node = g_new0(stat_node, 1); |
499 | 0 | stat_node *last_chld = NULL; |
500 | |
|
501 | 0 | node->datatype = datatype; |
502 | 0 | switch (datatype) |
503 | 0 | { |
504 | 0 | case STAT_DT_INT: |
505 | 0 | node->minvalue.int_min = INT_MAX; |
506 | 0 | node->maxvalue.int_max = INT_MIN; |
507 | 0 | break; |
508 | 0 | case STAT_DT_FLOAT: |
509 | 0 | node->minvalue.float_min = FLT_MAX; |
510 | 0 | node->maxvalue.float_max = -FLT_MAX; |
511 | 0 | break; |
512 | 0 | } |
513 | 0 | node->st_flags = parent_id?0:ST_FLG_ROOTCHILD; |
514 | |
|
515 | 0 | node->bh = g_new0(burst_bucket, 1); |
516 | 0 | node->bt = node->bh; |
517 | 0 | node->burst_time = -1.0; |
518 | |
|
519 | 0 | node->name = g_strdup(name); |
520 | 0 | node->st = st; |
521 | 0 | node->hash = with_hash ? g_hash_table_new(g_str_hash,g_str_equal) : NULL; |
522 | |
|
523 | 0 | if (as_parent_node) { |
524 | 0 | g_hash_table_insert(st->names, |
525 | 0 | node->name, |
526 | 0 | node); |
527 | |
|
528 | 0 | g_ptr_array_add(st->parents,node); |
529 | |
|
530 | 0 | node->id = st->parents->len - 1; |
531 | 0 | } else { |
532 | 0 | node->id = -1; |
533 | 0 | } |
534 | |
|
535 | 0 | if (parent_id >= 0 && parent_id < (int) st->parents->len ) { |
536 | 0 | node->parent = (stat_node *)g_ptr_array_index(st->parents,parent_id); |
537 | 0 | } else { |
538 | | /* ??? should we set the parent to be root ??? */ |
539 | 0 | ws_assert_not_reached(); |
540 | 0 | } |
541 | |
|
542 | 0 | if (node->parent->children) { |
543 | | /* insert as last child */ |
544 | |
|
545 | 0 | for (last_chld = node->parent->children; |
546 | 0 | last_chld->next; |
547 | 0 | last_chld = last_chld->next ) ; |
548 | |
|
549 | 0 | last_chld->next = node; |
550 | |
|
551 | 0 | } else { |
552 | | /* insert as first child */ |
553 | 0 | node->parent->children = node; |
554 | 0 | } |
555 | |
|
556 | 0 | if(node->parent->hash) { |
557 | 0 | g_hash_table_replace(node->parent->hash,node->name,node); |
558 | 0 | } |
559 | |
|
560 | 0 | if (st->cfg->setup_node_pr) { |
561 | 0 | st->cfg->setup_node_pr(node); |
562 | 0 | } else { |
563 | 0 | node->pr = NULL; |
564 | 0 | } |
565 | |
|
566 | 0 | return node; |
567 | 0 | } |
568 | | /***/ |
569 | | |
570 | | int |
571 | | stats_tree_create_node(stats_tree *st, const char *name, int parent_id, stat_node_datatype datatype, bool with_hash) |
572 | 0 | { |
573 | 0 | stat_node *node = new_stat_node(st,name,parent_id,datatype,with_hash,true); |
574 | |
|
575 | 0 | if (node) |
576 | 0 | return node->id; |
577 | 0 | else |
578 | 0 | return 0; |
579 | 0 | } |
580 | | |
581 | | /* XXX: should this be a macro? */ |
582 | | int |
583 | | stats_tree_create_node_by_pname(stats_tree *st, const char *name, |
584 | | const char *parent_name, stat_node_datatype datatype, bool with_children) |
585 | 0 | { |
586 | 0 | return stats_tree_create_node(st,name,stats_tree_parent_id_by_name(st,parent_name),datatype,with_children); |
587 | 0 | } |
588 | | |
589 | | /* Internal function to update the burst calculation data - add entry to bucket */ |
590 | | static void |
591 | | update_burst_calc(stat_node *node, int value) |
592 | 0 | { |
593 | 0 | double current_bucket; |
594 | 0 | double burstwin; |
595 | |
|
596 | 0 | burst_bucket *bn; |
597 | |
|
598 | 0 | if (!prefs.st_enable_burstinfo) { |
599 | 0 | return; |
600 | 0 | } |
601 | | |
602 | | /* NB thebucket list should always contain at least one node - even if it is */ |
603 | | /* the dummy created at init time. Head and tail should never be NULL! */ |
604 | 0 | current_bucket = floor(node->st->now/prefs.st_burst_resolution); |
605 | 0 | burstwin = prefs.st_burst_windowlen/prefs.st_burst_resolution; |
606 | 0 | if (current_bucket>node->bt->bucket_no) { |
607 | | /* Must add a new bucket at the burst list tail */ |
608 | 0 | bn = g_new0(burst_bucket, 1); |
609 | 0 | bn->count = value; |
610 | 0 | bn->bucket_no = current_bucket; |
611 | 0 | bn->start_time = node->st->now; |
612 | 0 | bn->prev = node->bt; |
613 | 0 | node->bt->next = bn; |
614 | 0 | node->bt = bn; |
615 | | /* And add value to the current burst count for node */ |
616 | 0 | node->bcount += value; |
617 | | /* Check if bucket list head is now too old and must be removed */ |
618 | 0 | while (current_bucket>=(node->bh->bucket_no+burstwin)) { |
619 | | /* off with its head! */ |
620 | 0 | bn = node->bh; |
621 | 0 | node->bh = bn->next; |
622 | 0 | node->bh->prev = NULL; |
623 | 0 | node->bcount -= bn->count; |
624 | 0 | g_free(bn); |
625 | 0 | } |
626 | 0 | } |
627 | 0 | else if (current_bucket<node->bh->bucket_no) { |
628 | | /* Packet must be added at head of burst list - check if not too old */ |
629 | 0 | if ((current_bucket+burstwin)>node->bt->bucket_no) { |
630 | | /* packet still within the window */ |
631 | 0 | bn = g_new0(burst_bucket, 1); |
632 | 0 | bn->count = value; |
633 | 0 | bn->bucket_no = current_bucket; |
634 | 0 | bn->start_time = node->st->now; |
635 | 0 | bn->next = node->bh; |
636 | 0 | node->bh->prev = bn; |
637 | 0 | node->bh = bn; |
638 | | /* And add value to the current burst count for node */ |
639 | 0 | node->bcount += value; |
640 | 0 | } |
641 | 0 | } |
642 | 0 | else |
643 | 0 | { |
644 | | /* Somewhere in the middle... */ |
645 | 0 | burst_bucket *search = node->bt; |
646 | 0 | while (current_bucket<search->bucket_no) { |
647 | 0 | search = search->prev; |
648 | 0 | } |
649 | 0 | if (current_bucket==search->bucket_no) { |
650 | | /* found existing bucket, increase value */ |
651 | 0 | search->count += value; |
652 | 0 | if (search->start_time>node->st->now) { |
653 | 0 | search->start_time = node->st->now; |
654 | 0 | } |
655 | 0 | } |
656 | 0 | else { |
657 | | /* must add a new bucket after bn. */ |
658 | 0 | bn = g_new0(burst_bucket, 1); |
659 | 0 | bn->count = value; |
660 | 0 | bn->bucket_no = current_bucket; |
661 | 0 | bn->start_time = node->st->now; |
662 | 0 | bn->prev = search; |
663 | 0 | bn->next = search->next; |
664 | 0 | search->next = bn; |
665 | 0 | bn->next->prev = bn; |
666 | 0 | } |
667 | 0 | node->bcount += value; |
668 | 0 | } |
669 | 0 | if (node->bcount>node->max_burst) { |
670 | | /* new record burst */ |
671 | 0 | node->max_burst = node->bcount; |
672 | 0 | node->burst_time = node->bh->start_time; |
673 | 0 | } |
674 | 0 | } |
675 | | |
676 | | /* |
677 | | * Increases by delta the counter of the node whose name is given |
678 | | * if the node does not exist yet it's created (with counter=1) |
679 | | * using parent_name as parent node. |
680 | | * with_hash=true to indicate that the created node will have a parent |
681 | | */ |
682 | | int |
683 | | stats_tree_manip_node_int(manip_node_mode mode, stats_tree *st, const char *name, |
684 | | int parent_id, bool with_hash, int value) |
685 | 0 | { |
686 | 0 | stat_node *node = NULL; |
687 | 0 | stat_node *parent = NULL; |
688 | |
|
689 | 0 | ws_assert( parent_id >= 0 && parent_id < (int) st->parents->len ); |
690 | |
|
691 | 0 | parent = (stat_node *)g_ptr_array_index(st->parents,parent_id); |
692 | |
|
693 | 0 | if( parent->hash ) { |
694 | 0 | node = (stat_node *)g_hash_table_lookup(parent->hash,name); |
695 | 0 | } else { |
696 | 0 | node = (stat_node *)g_hash_table_lookup(st->names,name); |
697 | 0 | } |
698 | |
|
699 | 0 | if ( node == NULL ) |
700 | 0 | node = new_stat_node(st,name,parent_id,STAT_DT_INT,with_hash,with_hash); |
701 | |
|
702 | 0 | switch (mode) { |
703 | 0 | case MN_INCREASE: |
704 | 0 | node->counter += value; |
705 | 0 | update_burst_calc(node, value); |
706 | 0 | break; |
707 | 0 | case MN_SET: |
708 | 0 | node->counter = value; |
709 | 0 | break; |
710 | 0 | case MN_AVERAGE: |
711 | 0 | node->counter++; |
712 | 0 | update_burst_calc(node, 1); |
713 | | /* fall through */ /*to average code */ |
714 | 0 | case MN_AVERAGE_NOTICK: |
715 | 0 | node->total.int_total += value; |
716 | 0 | if (node->minvalue.int_min > value) { |
717 | 0 | node->minvalue.int_min = value; |
718 | 0 | } |
719 | 0 | if (node->maxvalue.int_max < value) { |
720 | 0 | node->maxvalue.int_max = value; |
721 | 0 | } |
722 | 0 | node->st_flags |= ST_FLG_AVERAGE; |
723 | 0 | break; |
724 | 0 | case MN_SET_FLAGS: |
725 | 0 | node->st_flags |= value; |
726 | 0 | break; |
727 | 0 | case MN_CLEAR_FLAGS: |
728 | 0 | node->st_flags &= ~value; |
729 | 0 | break; |
730 | 0 | } |
731 | | |
732 | 0 | if (node) |
733 | 0 | return node->id; |
734 | 0 | else |
735 | 0 | return -1; |
736 | 0 | } |
737 | | |
738 | | /* |
739 | | * Increases by delta the counter of the node whose name is given |
740 | | * if the node does not exist yet it's created (with counter=1) |
741 | | * using parent_name as parent node. |
742 | | * with_hash=true to indicate that the created node will have a parent |
743 | | */ |
744 | | int |
745 | | stats_tree_manip_node_float(manip_node_mode mode, stats_tree *st, const char *name, |
746 | | int parent_id, bool with_hash, float value) |
747 | 0 | { |
748 | 0 | stat_node *node = NULL; |
749 | 0 | stat_node *parent = NULL; |
750 | |
|
751 | 0 | ws_assert(parent_id >= 0 && parent_id < (int)st->parents->len); |
752 | |
|
753 | 0 | parent = (stat_node *)g_ptr_array_index(st->parents, parent_id); |
754 | |
|
755 | 0 | if (parent->hash) { |
756 | 0 | node = (stat_node *)g_hash_table_lookup(parent->hash, name); |
757 | 0 | } |
758 | 0 | else { |
759 | 0 | node = (stat_node *)g_hash_table_lookup(st->names, name); |
760 | 0 | } |
761 | |
|
762 | 0 | if (node == NULL) |
763 | 0 | node = new_stat_node(st, name, parent_id, STAT_DT_FLOAT, with_hash, with_hash); |
764 | |
|
765 | 0 | switch (mode) { |
766 | 0 | case MN_AVERAGE: |
767 | 0 | node->counter++; |
768 | 0 | update_burst_calc(node, 1); |
769 | | /* fall through */ /*to average code */ |
770 | 0 | case MN_AVERAGE_NOTICK: |
771 | 0 | node->total.float_total += value; |
772 | 0 | if (node->minvalue.float_min > value) { |
773 | 0 | node->minvalue.float_min = value; |
774 | 0 | } |
775 | 0 | if (node->maxvalue.float_max < value) { |
776 | 0 | node->maxvalue.float_max = value; |
777 | 0 | } |
778 | 0 | node->st_flags |= ST_FLG_AVERAGE; |
779 | 0 | break; |
780 | 0 | default: |
781 | | //only average is currently supported |
782 | 0 | ws_assert_not_reached(); |
783 | 0 | break; |
784 | 0 | } |
785 | | |
786 | 0 | if (node) |
787 | 0 | return node->id; |
788 | 0 | else |
789 | 0 | return -1; |
790 | 0 | } |
791 | | |
792 | | char* |
793 | | stats_tree_get_abbr(const char *opt_arg) |
794 | 0 | { |
795 | 0 | unsigned i; |
796 | | |
797 | | /* XXX: this fails when tshark is given any options |
798 | | after the -z */ |
799 | 0 | ws_assert(opt_arg != NULL); |
800 | |
|
801 | 0 | for (i=0; opt_arg[i] && opt_arg[i] != ','; i++); |
802 | |
|
803 | 0 | if (opt_arg[i] == ',') { |
804 | 0 | return g_strndup(opt_arg,i); |
805 | 0 | } else { |
806 | 0 | return NULL; |
807 | 0 | } |
808 | 0 | } |
809 | | |
810 | | |
811 | | /* |
812 | | * This function accepts an input string which should define a long integer range. |
813 | | * The normal result is a struct containing the floor and ceil value of this |
814 | | * range. |
815 | | * |
816 | | * It is allowed to define a range string in the following ways : |
817 | | * |
818 | | * "0-10" -> { 0, 10 } |
819 | | * "-0" -> { INT_MIN, 0 } |
820 | | * "0-" -> { 0, INT_MAX } |
821 | | * "-" -> { INT_MIN, INT_MAX } |
822 | | * |
823 | | * Note that this function is robust to buggy input string. If in some cases it |
824 | | * returns NULL, it but may also return a pair with undefined values. |
825 | | * |
826 | | */ |
827 | | static range_pair_t* |
828 | | get_range(char *rngstr) |
829 | 0 | { |
830 | 0 | char **split; |
831 | 0 | range_pair_t *rng; |
832 | |
|
833 | 0 | split = g_strsplit((char*)rngstr,"-",2); |
834 | | |
835 | | /* empty string */ |
836 | 0 | if (split[0] == NULL) { |
837 | 0 | g_strfreev(split); |
838 | 0 | return NULL; |
839 | 0 | } |
840 | | |
841 | 0 | rng = g_new(range_pair_t, 1); |
842 | |
|
843 | 0 | if (split[1] == NULL) { |
844 | | /* means we have a non empty string with no delimiter |
845 | | * so it must be a single number */ |
846 | 0 | rng->floor = (int)strtol(split[0],NULL,10); |
847 | 0 | rng->ceil = rng->floor; |
848 | 0 | } else { |
849 | | /* string == "X-?" */ |
850 | 0 | if (*(split[0]) != '\0') { |
851 | 0 | rng->floor = (int)strtol(split[0],NULL,10); |
852 | 0 | } else { |
853 | | /* string == "-?" */ |
854 | 0 | rng->floor = INT_MIN; |
855 | 0 | } |
856 | | |
857 | | /* string != "?-" */ |
858 | 0 | if (*(split[1]) != '\0') { |
859 | 0 | rng->ceil = (int)strtol(split[1],NULL,10); |
860 | 0 | } else { |
861 | | /* string == "?-" */ |
862 | 0 | rng->ceil = INT_MAX; |
863 | 0 | } |
864 | 0 | } |
865 | 0 | g_strfreev(split); |
866 | |
|
867 | 0 | return rng; |
868 | 0 | } |
869 | | |
870 | | |
871 | | int |
872 | | stats_tree_create_range_node(stats_tree *st, const char *name, int parent_id, ...) |
873 | 0 | { |
874 | 0 | va_list list; |
875 | 0 | char *curr_range; |
876 | 0 | stat_node *rng_root = new_stat_node(st, name, parent_id, STAT_DT_INT, false, true); |
877 | 0 | stat_node *range_node = NULL; |
878 | |
|
879 | 0 | va_start( list, parent_id ); |
880 | 0 | while (( curr_range = va_arg(list, char*) )) { |
881 | 0 | range_node = new_stat_node(st, curr_range, rng_root->id, STAT_DT_INT, false, false); |
882 | 0 | range_node->rng = get_range(curr_range); |
883 | 0 | } |
884 | 0 | va_end( list ); |
885 | |
|
886 | 0 | return rng_root->id; |
887 | 0 | } |
888 | | |
889 | | int |
890 | | stats_tree_create_range_node_string(stats_tree *st, const char *name, |
891 | | int parent_id, int num_str_ranges, |
892 | | char** str_ranges) |
893 | 0 | { |
894 | 0 | int i; |
895 | 0 | stat_node *rng_root = new_stat_node(st, name, parent_id, STAT_DT_INT, false, true); |
896 | 0 | stat_node *range_node = NULL; |
897 | |
|
898 | 0 | for (i = 0; i < num_str_ranges - 1; i++) { |
899 | 0 | range_node = new_stat_node(st, str_ranges[i], rng_root->id, STAT_DT_INT, false, false); |
900 | 0 | range_node->rng = get_range(str_ranges[i]); |
901 | 0 | } |
902 | 0 | range_node = new_stat_node(st, str_ranges[i], rng_root->id, STAT_DT_INT, false, false); |
903 | 0 | range_node->rng = get_range(str_ranges[i]); |
904 | 0 | if (range_node->rng->floor == range_node->rng->ceil) { |
905 | 0 | range_node->rng->ceil = INT_MAX; |
906 | 0 | } |
907 | |
|
908 | 0 | return rng_root->id; |
909 | 0 | } |
910 | | |
911 | | /****/ |
912 | | int |
913 | | stats_tree_parent_id_by_name(stats_tree *st, const char *parent_name) |
914 | 0 | { |
915 | 0 | stat_node *node = (stat_node *)g_hash_table_lookup(st->names,parent_name); |
916 | |
|
917 | 0 | if (node) |
918 | 0 | return node->id; |
919 | 0 | else |
920 | 0 | return 0; /* XXX: this is the root should we return -1 instead?*/ |
921 | 0 | } |
922 | | |
923 | | |
924 | | int |
925 | | stats_tree_range_node_with_pname(stats_tree *st, const char *name, |
926 | | const char *parent_name, ...) |
927 | 0 | { |
928 | 0 | va_list list; |
929 | 0 | char *curr_range; |
930 | 0 | stat_node *range_node = NULL; |
931 | 0 | int parent_id = stats_tree_parent_id_by_name(st,parent_name); |
932 | 0 | stat_node *rng_root = new_stat_node(st, name, parent_id, STAT_DT_INT, false, true); |
933 | |
|
934 | 0 | va_start( list, parent_name ); |
935 | 0 | while (( curr_range = va_arg(list, char*) )) { |
936 | 0 | range_node = new_stat_node(st, curr_range, rng_root->id, STAT_DT_INT, false, false); |
937 | 0 | range_node->rng = get_range(curr_range); |
938 | 0 | } |
939 | 0 | va_end( list ); |
940 | |
|
941 | 0 | return rng_root->id; |
942 | 0 | } |
943 | | |
944 | | |
945 | | int |
946 | | stats_tree_tick_range(stats_tree *st, const char *name, int parent_id, |
947 | | int value_in_range) |
948 | 0 | { |
949 | |
|
950 | 0 | stat_node *node = NULL; |
951 | 0 | stat_node *parent = NULL; |
952 | 0 | stat_node *child = NULL; |
953 | 0 | int stat_floor, stat_ceil; |
954 | |
|
955 | 0 | if (parent_id >= 0 && parent_id < (int) st->parents->len) { |
956 | 0 | parent = (stat_node *)g_ptr_array_index(st->parents,parent_id); |
957 | 0 | } else { |
958 | 0 | ws_assert_not_reached(); |
959 | 0 | } |
960 | |
|
961 | 0 | if( parent->hash ) { |
962 | 0 | node = (stat_node *)g_hash_table_lookup(parent->hash,name); |
963 | 0 | } else { |
964 | 0 | node = (stat_node *)g_hash_table_lookup(st->names,name); |
965 | 0 | } |
966 | |
|
967 | 0 | if ( node == NULL ) |
968 | 0 | ws_assert_not_reached(); |
969 | | |
970 | | /* update stats for container node. counter should already be ticked so we only update total and min/max */ |
971 | 0 | node->total.int_total += value_in_range; |
972 | 0 | if (node->minvalue.int_min > value_in_range) { |
973 | 0 | node->minvalue.int_min = value_in_range; |
974 | 0 | } |
975 | 0 | if (node->maxvalue.int_max < value_in_range) { |
976 | 0 | node->maxvalue.int_max = value_in_range; |
977 | 0 | } |
978 | 0 | node->st_flags |= ST_FLG_AVERAGE; |
979 | |
|
980 | 0 | for ( child = node->children; child; child = child->next) { |
981 | 0 | stat_floor = child->rng->floor; |
982 | 0 | stat_ceil = child->rng->ceil; |
983 | |
|
984 | 0 | if ( value_in_range >= stat_floor && value_in_range <= stat_ceil ) { |
985 | 0 | child->counter++; |
986 | 0 | child->total.int_total += value_in_range; |
987 | 0 | if (child->minvalue.int_min > value_in_range) { |
988 | 0 | child->minvalue.int_min = value_in_range; |
989 | 0 | } |
990 | 0 | if (child->maxvalue.int_max < value_in_range) { |
991 | 0 | child->maxvalue.int_max = value_in_range; |
992 | 0 | } |
993 | 0 | child->st_flags |= ST_FLG_AVERAGE; |
994 | 0 | update_burst_calc(child, 1); |
995 | 0 | return node->id; |
996 | 0 | } |
997 | 0 | } |
998 | | |
999 | 0 | return node->id; |
1000 | 0 | } |
1001 | | |
1002 | | int |
1003 | | stats_tree_create_pivot(stats_tree *st, const char *name, int parent_id) |
1004 | 0 | { |
1005 | 0 | stat_node *node = new_stat_node(st,name,parent_id,STAT_DT_INT,true,true); |
1006 | |
|
1007 | 0 | if (node) |
1008 | 0 | return node->id; |
1009 | 0 | else |
1010 | 0 | return 0; |
1011 | 0 | } |
1012 | | |
1013 | | int |
1014 | | stats_tree_create_pivot_by_pname(stats_tree *st, const char *name, |
1015 | | const char *parent_name) |
1016 | 0 | { |
1017 | 0 | int parent_id = stats_tree_parent_id_by_name(st,parent_name); |
1018 | 0 | stat_node *node; |
1019 | |
|
1020 | 0 | node = new_stat_node(st,name,parent_id,STAT_DT_INT,true,true); |
1021 | |
|
1022 | 0 | if (node) |
1023 | 0 | return node->id; |
1024 | 0 | else |
1025 | 0 | return 0; |
1026 | 0 | } |
1027 | | |
1028 | | int |
1029 | | stats_tree_tick_pivot(stats_tree *st, int pivot_id, const char *pivot_value) |
1030 | 0 | { |
1031 | 0 | stat_node *parent = (stat_node *)g_ptr_array_index(st->parents,pivot_id); |
1032 | |
|
1033 | 0 | parent->counter++; |
1034 | 0 | update_burst_calc(parent, 1); |
1035 | 0 | stats_tree_manip_node_int( MN_INCREASE, st, pivot_value, pivot_id, false, 1); |
1036 | |
|
1037 | 0 | return pivot_id; |
1038 | 0 | } |
1039 | | |
1040 | | char* |
1041 | | stats_tree_get_displayname (const char* fullname) |
1042 | 0 | { |
1043 | 0 | char *buf = g_strdup(fullname); |
1044 | 0 | char *sep; |
1045 | |
|
1046 | 0 | if (prefs.st_sort_showfullname) { |
1047 | 0 | return buf; /* unmodified */ |
1048 | 0 | } |
1049 | | |
1050 | 0 | sep = buf; |
1051 | 0 | while ((sep = strchr(sep,'/')) != NULL) { |
1052 | 0 | if (*(++sep)=='/') { /* escaped slash - two slash characters after each other */ |
1053 | 0 | memmove(sep,sep+1,strlen(sep)); |
1054 | 0 | } |
1055 | 0 | else { |
1056 | | /* we got a new path separator */ |
1057 | 0 | memmove(buf,sep,strlen(sep)+1); |
1058 | 0 | sep = buf; |
1059 | 0 | } |
1060 | 0 | } |
1061 | |
|
1062 | 0 | return buf; |
1063 | 0 | } |
1064 | | |
1065 | | int |
1066 | | stats_tree_get_default_sort_col (stats_tree *st) |
1067 | 0 | { |
1068 | 0 | switch ((st->st_flags&ST_FLG_SRTCOL_MASK)>>ST_FLG_SRTCOL_SHIFT) { |
1069 | 0 | case ST_SORT_COL_NAME: |
1070 | 0 | return COL_NAME; |
1071 | 0 | case ST_SORT_COL_COUNT: |
1072 | 0 | return COL_COUNT; |
1073 | 0 | case ST_SORT_COL_AVG: |
1074 | 0 | return COL_AVERAGE; |
1075 | 0 | case ST_SORT_COL_MIN: |
1076 | 0 | return COL_MIN; |
1077 | 0 | case ST_SORT_COL_MAX: |
1078 | 0 | return COL_MAX; |
1079 | 0 | case ST_SORT_COL_BURSTRATE: |
1080 | 0 | return COL_BURSTRATE; |
1081 | 0 | } |
1082 | 0 | return COL_COUNT; /* nothing specific set */ |
1083 | 0 | } |
1084 | | |
1085 | | bool |
1086 | | stats_tree_is_default_sort_DESC (stats_tree *st) |
1087 | 0 | { |
1088 | 0 | return st->st_flags&ST_FLG_SORT_DESC; |
1089 | 0 | } |
1090 | | |
1091 | | const char* |
1092 | | stats_tree_get_column_name (stats_tree_cfg *st_config, int col_index) |
1093 | 0 | { |
1094 | 0 | switch (col_index) { |
1095 | 0 | case COL_NAME: |
1096 | 0 | if (st_config->first_column_name) { |
1097 | 0 | return st_config->first_column_name; |
1098 | 0 | } |
1099 | 0 | return "Topic / Item"; |
1100 | 0 | case COL_COUNT: |
1101 | 0 | return "Count"; |
1102 | 0 | case COL_AVERAGE: |
1103 | 0 | return "Average"; |
1104 | 0 | case COL_MIN: |
1105 | 0 | return "Min Val"; |
1106 | 0 | case COL_MAX: |
1107 | 0 | return "Max Val"; |
1108 | 0 | case COL_RATE: |
1109 | 0 | return "Rate (ms)"; |
1110 | 0 | case COL_PERCENT: |
1111 | 0 | return "Percent"; |
1112 | 0 | case COL_BURSTRATE: |
1113 | 0 | return prefs.st_burst_showcount ? "Burst Count" : "Burst Rate"; |
1114 | 0 | case COL_BURSTTIME: |
1115 | 0 | return "Burst Start"; |
1116 | 0 | default: |
1117 | 0 | return "(Unknown)"; |
1118 | 0 | } |
1119 | 0 | } |
1120 | | |
1121 | | int |
1122 | | stats_tree_get_column_size (int col_index) |
1123 | 0 | { |
1124 | 0 | if (col_index==COL_NAME) { |
1125 | 0 | return 36; /* but caller should really call stats_tree_branch_max_namelen() */ |
1126 | 0 | } |
1127 | 0 | if (col_index<N_COLUMNS) { |
1128 | 0 | return 12; /* all numerical values are this size */ |
1129 | 0 | } |
1130 | 0 | return 0; /* invalid column */ |
1131 | 0 | } |
1132 | | |
1133 | | char** |
1134 | | stats_tree_get_values_from_node (const stat_node* node) |
1135 | 0 | { |
1136 | 0 | char **values = (char**) g_malloc0(sizeof(char*)*(node->st->num_columns)); |
1137 | |
|
1138 | 0 | values[COL_NAME] = (node->st_flags&ST_FLG_ROOTCHILD)?stats_tree_get_displayname(node->name):g_strdup(node->name); |
1139 | 0 | values[COL_COUNT] = ws_strdup_printf("%u",node->counter); |
1140 | 0 | if (((node->st_flags&ST_FLG_AVERAGE) || node->rng)) { |
1141 | 0 | if (node->counter) { |
1142 | 0 | switch (node->datatype) |
1143 | 0 | { |
1144 | 0 | case STAT_DT_INT: |
1145 | 0 | values[COL_AVERAGE] = ws_strdup_printf("%.2f", ((float)node->total.int_total) / node->counter); |
1146 | 0 | break; |
1147 | 0 | case STAT_DT_FLOAT: |
1148 | 0 | values[COL_AVERAGE] = ws_strdup_printf("%.2f", node->total.float_total / node->counter); |
1149 | 0 | break; |
1150 | 0 | } |
1151 | 0 | } else { |
1152 | 0 | values[COL_AVERAGE] = g_strdup("-"); |
1153 | 0 | } |
1154 | 0 | } else { |
1155 | 0 | values[COL_AVERAGE] = g_strdup(""); |
1156 | 0 | } |
1157 | | |
1158 | 0 | if (((node->st_flags&ST_FLG_AVERAGE) || node->rng)) { |
1159 | 0 | if (node->counter) { |
1160 | 0 | switch (node->datatype) |
1161 | 0 | { |
1162 | 0 | case STAT_DT_INT: |
1163 | 0 | values[COL_MIN] = ws_strdup_printf("%d", node->minvalue.int_min); |
1164 | 0 | break; |
1165 | 0 | case STAT_DT_FLOAT: |
1166 | 0 | values[COL_MIN] = ws_strdup_printf("%f", node->minvalue.float_min); |
1167 | 0 | break; |
1168 | 0 | } |
1169 | 0 | } |
1170 | 0 | else { |
1171 | 0 | values[COL_MIN] = g_strdup("-"); |
1172 | 0 | } |
1173 | 0 | } |
1174 | 0 | else { |
1175 | 0 | values[COL_MIN] = g_strdup(""); |
1176 | 0 | } |
1177 | | |
1178 | 0 | if (((node->st_flags&ST_FLG_AVERAGE) || node->rng)) { |
1179 | 0 | if (node->counter) { |
1180 | 0 | switch (node->datatype) |
1181 | 0 | { |
1182 | 0 | case STAT_DT_INT: |
1183 | 0 | values[COL_MAX] = ws_strdup_printf("%d", node->maxvalue.int_max); |
1184 | 0 | break; |
1185 | 0 | case STAT_DT_FLOAT: |
1186 | 0 | values[COL_MAX] = ws_strdup_printf("%f", node->maxvalue.float_max); |
1187 | 0 | break; |
1188 | 0 | } |
1189 | 0 | } |
1190 | 0 | else { |
1191 | 0 | values[COL_MAX] = g_strdup("-"); |
1192 | 0 | } |
1193 | 0 | } |
1194 | 0 | else { |
1195 | 0 | values[COL_MAX] = g_strdup(""); |
1196 | 0 | } |
1197 | | |
1198 | 0 | values[COL_RATE] = (node->st->elapsed)?ws_strdup_printf("%.4f",((float)node->counter)/node->st->elapsed):g_strdup(""); |
1199 | 0 | values[COL_PERCENT] = ((node->parent)&&(node->parent->counter))? |
1200 | 0 | ws_strdup_printf("%.2f%%",(node->counter*100.0)/node->parent->counter): |
1201 | 0 | (node->parent==&(node->st->root)?g_strdup("100%"):g_strdup("")); |
1202 | 0 | if (node->st->num_columns>COL_BURSTTIME) { |
1203 | 0 | values[COL_BURSTRATE] = (!prefs.st_enable_burstinfo)?g_strdup(""): |
1204 | 0 | (node->max_burst?(prefs.st_burst_showcount? |
1205 | 0 | ws_strdup_printf("%d",node->max_burst): |
1206 | 0 | ws_strdup_printf("%.4f",((double)node->max_burst)/prefs.st_burst_windowlen)): |
1207 | 0 | g_strdup("-")); |
1208 | 0 | values[COL_BURSTTIME] = (!prefs.st_enable_burstinfo)?g_strdup(""): |
1209 | 0 | (node->max_burst?ws_strdup_printf("%.3f",(node->burst_time/1000.0)):g_strdup("-")); |
1210 | 0 | } |
1211 | 0 | return values; |
1212 | 0 | } |
1213 | | |
1214 | | int |
1215 | | stats_tree_sort_compare (const stat_node *a, const stat_node *b, int sort_column, |
1216 | | bool sort_descending) |
1217 | 0 | { |
1218 | 0 | int result = 0; |
1219 | 0 | float avg_a = 0, avg_b = 0; |
1220 | |
|
1221 | 0 | if (prefs.st_sort_rng_nameonly&&(a->rng&&b->rng)) { |
1222 | | /* always sort ranges by range name */ |
1223 | 0 | result = a->rng->floor - b->rng->floor; |
1224 | 0 | if (sort_descending&&(!prefs.st_sort_rng_fixorder)) { |
1225 | 0 | result = -result; |
1226 | 0 | } |
1227 | 0 | return result; |
1228 | 0 | } |
1229 | | |
1230 | 0 | switch (sort_column) { |
1231 | 0 | case COL_NAME: |
1232 | 0 | if (a->rng&&b->rng) { |
1233 | 0 | result = a->rng->floor - b->rng->floor; |
1234 | 0 | } |
1235 | 0 | else if (prefs.st_sort_casesensitve) { |
1236 | 0 | result = strcmp(a->name,b->name); |
1237 | 0 | } |
1238 | 0 | else { |
1239 | 0 | result = g_ascii_strcasecmp(a->name,b->name); |
1240 | 0 | } |
1241 | 0 | break; |
1242 | | |
1243 | 0 | case COL_RATE: |
1244 | 0 | case COL_PERCENT: |
1245 | 0 | case COL_COUNT: |
1246 | 0 | result = a->counter - b->counter; |
1247 | 0 | break; |
1248 | | |
1249 | 0 | case COL_AVERAGE: |
1250 | 0 | switch (a->datatype) |
1251 | 0 | { |
1252 | 0 | case STAT_DT_INT: |
1253 | 0 | avg_a = a->counter ? ((float)a->total.int_total)/a->counter : 0; |
1254 | 0 | avg_b = b->counter ? ((float)b->total.int_total)/b->counter : 0; |
1255 | 0 | break; |
1256 | 0 | case STAT_DT_FLOAT: |
1257 | 0 | avg_a = a->counter ? ((float)a->total.float_total) / a->counter : 0; |
1258 | 0 | avg_b = b->counter ? ((float)b->total.float_total) / b->counter : 0; |
1259 | 0 | break; |
1260 | 0 | } |
1261 | 0 | result = (avg_a>avg_b) ? 1 : ( (avg_a<avg_b) ? -1 : 0); |
1262 | 0 | break; |
1263 | | |
1264 | 0 | case COL_MIN: |
1265 | 0 | switch (a->datatype) |
1266 | 0 | { |
1267 | 0 | case STAT_DT_INT: |
1268 | 0 | result = a->minvalue.int_min - b->minvalue.int_min; |
1269 | 0 | break; |
1270 | 0 | case STAT_DT_FLOAT: |
1271 | 0 | result = (a->minvalue.float_min>b->minvalue.int_min) ? 1 : ((a->minvalue.float_min<b->minvalue.int_min) ? -1 : 0); |
1272 | 0 | break; |
1273 | 0 | } |
1274 | 0 | break; |
1275 | | |
1276 | 0 | case COL_MAX: |
1277 | 0 | switch (a->datatype) |
1278 | 0 | { |
1279 | 0 | case STAT_DT_INT: |
1280 | 0 | result = a->maxvalue.int_max - b->maxvalue.int_max; |
1281 | 0 | break; |
1282 | 0 | case STAT_DT_FLOAT: |
1283 | 0 | result = (a->maxvalue.float_max>b->maxvalue.float_max) ? 1 : ((a->maxvalue.float_max<b->maxvalue.float_max) ? -1 : 0); |
1284 | 0 | break; |
1285 | 0 | } |
1286 | 0 | break; |
1287 | | |
1288 | 0 | case COL_BURSTRATE: |
1289 | 0 | result = a->max_burst - b->max_burst; |
1290 | 0 | break; |
1291 | | |
1292 | 0 | case COL_BURSTTIME: |
1293 | 0 | result = (a->burst_time>b->burst_time)?1:((a->burst_time<b->burst_time)?-1:0); |
1294 | 0 | break; |
1295 | | |
1296 | 0 | default: |
1297 | | /* no sort comparison found for column - must update this switch statement */ |
1298 | 0 | ws_assert_not_reached(); |
1299 | 0 | } |
1300 | | |
1301 | | /* break tie between items with same primary search result */ |
1302 | 0 | if (!result) { |
1303 | 0 | if (sort_column==COL_NAME) { |
1304 | 0 | result = a->counter - b->counter; |
1305 | 0 | } |
1306 | 0 | else { |
1307 | 0 | if (a->rng&&b->rng) { |
1308 | 0 | result = a->rng->floor - b->rng->floor; |
1309 | 0 | } |
1310 | 0 | else if (prefs.st_sort_casesensitve) { |
1311 | 0 | result = strcmp(a->name,b->name); |
1312 | 0 | } |
1313 | 0 | else { |
1314 | 0 | result = g_ascii_strcasecmp(a->name,b->name); |
1315 | 0 | } |
1316 | 0 | } |
1317 | 0 | } |
1318 | | |
1319 | | /* take into account sort order */ |
1320 | 0 | if (sort_descending) { |
1321 | 0 | result = -result; |
1322 | 0 | } |
1323 | |
|
1324 | 0 | if ((a->st_flags&ST_FLG_SORT_TOP)!=(b->st_flags&ST_FLG_SORT_TOP)) { |
1325 | | /* different sort groups top vs non-top */ |
1326 | 0 | result = (a->st_flags&ST_FLG_SORT_TOP)?-1:1; |
1327 | 0 | } |
1328 | 0 | return result; |
1329 | 0 | } |
1330 | | |
1331 | | GString* |
1332 | | stats_tree_format_as_str(const stats_tree* st, st_format_type format_type, |
1333 | | int sort_column, bool sort_descending) |
1334 | 0 | { |
1335 | 0 | int maxnamelen = stats_tree_branch_max_namelen(&st->root,0); |
1336 | 0 | stat_node *child; |
1337 | 0 | GString *s; |
1338 | 0 | int count; |
1339 | 0 | char *separator = NULL; |
1340 | |
|
1341 | 0 | switch(format_type) { |
1342 | 0 | case ST_FORMAT_YAML: |
1343 | 0 | s = g_string_new("---\n"); |
1344 | 0 | break; |
1345 | 0 | case ST_FORMAT_XML: |
1346 | 0 | s = g_string_new("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
1347 | 0 | break; |
1348 | 0 | case ST_FORMAT_CSV: |
1349 | 0 | s = g_string_new("\"level\",\"parent\","); |
1350 | 0 | for (count = 0; count<st->num_columns; count++) { |
1351 | 0 | g_string_append_printf(s,"\"%s\",",stats_tree_get_column_name(st->cfg, count)); |
1352 | 0 | } |
1353 | 0 | g_string_append (s,"\n"); |
1354 | 0 | break; |
1355 | 0 | case ST_FORMAT_PLAIN: |
1356 | 0 | { |
1357 | 0 | char fmt[16]; |
1358 | 0 | int sep_length; |
1359 | |
|
1360 | 0 | sep_length = maxnamelen; |
1361 | 0 | for (count = 1; count<st->num_columns; count++) { |
1362 | 0 | sep_length += stats_tree_get_column_size(count)+2; |
1363 | 0 | } |
1364 | 0 | separator = (char *)g_malloc(sep_length+1); |
1365 | 0 | memset (separator, '=', sep_length); |
1366 | 0 | separator[sep_length] = 0; |
1367 | |
|
1368 | 0 | s = g_string_new("\n"); |
1369 | 0 | g_string_append(s,separator); |
1370 | 0 | g_string_append_printf(s,"\n%s:\n",st->cfg->title); |
1371 | 0 | snprintf (fmt,sizeof(fmt),"%%-%us",maxnamelen); |
1372 | 0 | g_string_append_printf(s,fmt,stats_tree_get_column_name(st->cfg, 0)); |
1373 | 0 | for (count = 1; count<st->num_columns; count++) { |
1374 | 0 | snprintf (fmt,sizeof(fmt)," %%-%ds",stats_tree_get_column_size(count)+1); |
1375 | 0 | g_string_append_printf(s,fmt,stats_tree_get_column_name(st->cfg, count)); |
1376 | 0 | } |
1377 | 0 | memset (separator, '-', sep_length); |
1378 | 0 | g_string_append_printf(s,"\n%s\n",separator); |
1379 | 0 | break; |
1380 | 0 | } |
1381 | 0 | default: |
1382 | 0 | return g_string_new("unknown format for stats_tree\n"); |
1383 | 0 | } |
1384 | | |
1385 | 0 | for (child = st->root.children; child; child = child->next ) { |
1386 | 0 | stats_tree_format_node_as_str(child,s,format_type,0,"",maxnamelen,sort_column,sort_descending); |
1387 | |
|
1388 | 0 | } |
1389 | |
|
1390 | 0 | if (format_type==ST_FORMAT_PLAIN) { |
1391 | 0 | g_string_append_printf(s,"\n%s\n",separator); |
1392 | 0 | g_free(separator); |
1393 | 0 | } |
1394 | |
|
1395 | 0 | return s; |
1396 | 0 | } |
1397 | | |
1398 | | typedef struct { |
1399 | | int sort_column; |
1400 | | bool sort_descending; |
1401 | | } sortinfo; |
1402 | | |
1403 | | /* Function to compare elements for child array sort. a and b are children, user_data |
1404 | | points to a st_flags value */ |
1405 | | int |
1406 | | stat_node_array_sortcmp (const void *a, const void *b, void *user_data) |
1407 | 0 | { |
1408 | | /* user_data is *unsigned value to st_flags */ |
1409 | 0 | return stats_tree_sort_compare (*(const stat_node*const*)a,*(const stat_node*const*)b, |
1410 | 0 | ((sortinfo*)user_data)->sort_column,((sortinfo*)user_data)->sort_descending); |
1411 | 0 | } |
1412 | | |
1413 | | static char* |
1414 | | clean_for_xml_tag (char *str) |
1415 | 0 | { |
1416 | 0 | char *s = str; |
1417 | 0 | while ((s=strpbrk(s,"!\"#$%%&'()*+,/;<=>?@[\\]^`{|}~ ")) != NULL) { |
1418 | 0 | *(s++) = '-'; |
1419 | 0 | } |
1420 | 0 | return str; |
1421 | 0 | } |
1422 | | |
1423 | | /** helper function to add note to formatted stats_tree */ |
1424 | | // NOLINTNEXTLINE(misc-no-recursion) |
1425 | | void stats_tree_format_node_as_str(const stat_node *node, |
1426 | | GString *s, |
1427 | | st_format_type format_type, |
1428 | | unsigned indent, |
1429 | | const char *path, |
1430 | | int maxnamelen, |
1431 | | int sort_column, |
1432 | | bool sort_descending) |
1433 | 0 | { |
1434 | 0 | int count; |
1435 | 0 | int num_columns = node->st->num_columns; |
1436 | 0 | char **values = stats_tree_get_values_from_node(node); |
1437 | 0 | stat_node *child; |
1438 | 0 | sortinfo si; |
1439 | 0 | char *full_path; |
1440 | 0 | char fmt[16] = "%s%s%s"; |
1441 | |
|
1442 | 0 | switch(format_type) { |
1443 | 0 | case ST_FORMAT_YAML: |
1444 | 0 | if (indent) { |
1445 | 0 | snprintf(fmt, sizeof(fmt), "%%%ds%%s%%s", indent*4-2); |
1446 | 0 | } |
1447 | 0 | g_string_append_printf(s, fmt, "", indent?"- ":"", "Description"); |
1448 | 0 | g_string_append_printf(s, ": \"%s\"\n", values[0]); |
1449 | |
|
1450 | 0 | for (count = 1; count<num_columns; count++) { |
1451 | 0 | if (*values[count]) { |
1452 | 0 | g_string_append_printf(s, fmt, "", indent?" ":"", |
1453 | 0 | stats_tree_get_column_name(node->st->cfg, count)); |
1454 | 0 | g_string_append_printf(s, ": %s\n", values[count]); |
1455 | 0 | } |
1456 | 0 | } |
1457 | 0 | if (node->children) { |
1458 | 0 | g_string_append_printf(s, fmt, "", indent?" ":"", "Items:\n"); |
1459 | 0 | } |
1460 | 0 | break; |
1461 | 0 | case ST_FORMAT_XML: |
1462 | 0 | { |
1463 | 0 | char *itemname = xml_escape(values[0]); |
1464 | 0 | g_string_append_printf(s,"<stat-node name=\"%s\"%s>\n",itemname, |
1465 | 0 | node->rng?" isrange=\"true\"":""); |
1466 | 0 | g_free(itemname); |
1467 | 0 | for (count = 1; count<num_columns; count++) { |
1468 | 0 | char *colname = g_strdup(stats_tree_get_column_name(node->st->cfg, count)); |
1469 | 0 | g_string_append_printf(s,"<%s>",clean_for_xml_tag(colname)); |
1470 | 0 | g_string_append_printf(s,"%s</%s>\n",values[count],colname); |
1471 | 0 | g_free(colname); |
1472 | 0 | } |
1473 | 0 | break; |
1474 | 0 | } |
1475 | 0 | case ST_FORMAT_CSV: |
1476 | 0 | g_string_append_printf(s,"%d,\"%s\",\"%s\"",indent,path,values[0]); |
1477 | 0 | for (count = 1; count<num_columns; count++) { |
1478 | 0 | g_string_append_printf(s,",%s",values[count]); |
1479 | 0 | } |
1480 | 0 | g_string_append (s,"\n"); |
1481 | 0 | break; |
1482 | 0 | case ST_FORMAT_PLAIN: |
1483 | 0 | snprintf (fmt,sizeof(fmt),"%%%ds%%-%us",indent,maxnamelen-indent); |
1484 | 0 | g_string_append_printf(s,fmt,"",values[0]); |
1485 | 0 | for (count = 1; count<num_columns; count++) { |
1486 | 0 | snprintf (fmt,sizeof(fmt)," %%-%us",stats_tree_get_column_size(count)+1); |
1487 | 0 | g_string_append_printf(s,fmt,values[count]); |
1488 | 0 | } |
1489 | 0 | g_string_append (s,"\n"); |
1490 | 0 | break; |
1491 | 0 | } |
1492 | | |
1493 | 0 | indent++; |
1494 | 0 | indent = indent > INDENT_MAX ? INDENT_MAX : indent; |
1495 | 0 | full_path = ws_strdup_printf ("%s/%s",path,values[0]); |
1496 | |
|
1497 | 0 | for (count = 0; count<num_columns; count++) { |
1498 | 0 | g_free(values[count]); |
1499 | 0 | } |
1500 | 0 | g_free(values); |
1501 | |
|
1502 | 0 | if (node->children) { |
1503 | 0 | GArray *Children = g_array_new(false,false,sizeof(child)); |
1504 | 0 | for (child = node->children; child; child = child->next ) { |
1505 | 0 | g_array_append_val(Children,child); |
1506 | 0 | } |
1507 | 0 | si.sort_column = sort_column; |
1508 | 0 | si.sort_descending = sort_descending; |
1509 | 0 | g_array_sort_with_data(Children,stat_node_array_sortcmp,&si); |
1510 | 0 | for (count = 0; count<((int)Children->len); count++) { |
1511 | 0 | stats_tree_format_node_as_str(g_array_index(Children,stat_node*,count), s, format_type, |
1512 | 0 | indent, full_path, maxnamelen, sort_column, sort_descending); |
1513 | 0 | } |
1514 | 0 | g_array_free(Children, true); |
1515 | 0 | } |
1516 | 0 | g_free(full_path); |
1517 | |
|
1518 | 0 | if (format_type==ST_FORMAT_XML) { |
1519 | 0 | g_string_append(s,"</stat-node>\n"); |
1520 | 0 | } |
1521 | 0 | } |
1522 | | |
1523 | | void stats_tree_cleanup(void) |
1524 | 0 | { |
1525 | 0 | g_hash_table_destroy(registry); |
1526 | 0 | } |
1527 | | |
1528 | | /* |
1529 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
1530 | | * |
1531 | | * Local variables: |
1532 | | * c-basic-offset: 4 |
1533 | | * tab-width: 8 |
1534 | | * indent-tabs-mode: nil |
1535 | | * End: |
1536 | | * |
1537 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
1538 | | * :indentSize=4:tabSize=8:noTabs=true: |
1539 | | */ |