2 * Routines for calculating statistics based on protocol.
4 * Wireshark - Network traffic analyzer
5 * By Gerald Combs <gerald@wireshark.org>
6 * Copyright 1998 Gerald Combs
8 * SPDX-License-Identifier: GPL-2.0-or-later
16 #include "frame_tvbuff.h"
17 #include "ui/proto_hier_stats.h"
18 #include "ui/progress_dlg.h"
19 #include "epan/epan_dissect.h"
20 #include "epan/proto.h"
21 #include <wsutil/ws_assert.h>
23 /* Update the progress bar this many times when scanning the packet list. */
24 #define N_PROGBAR_UPDATES 100
26 #define STAT_NODE_STATS(n) ((ph_stats_node_t*)(n)->data)
27 #define STAT_NODE_HFINFO(n) (STAT_NODE_STATS(n)->hfinfo)
29 static int pc_proto_id
= -1;
32 find_stat_node(GNode
*parent_stat_node
, const header_field_info
*needle_hfinfo
)
34 GNode
*needle_stat_node
, *up_parent_stat_node
;
35 const header_field_info
*hfinfo
;
36 ph_stats_node_t
*stats
;
38 /* Look down the tree */
39 needle_stat_node
= g_node_first_child(parent_stat_node
);
41 while (needle_stat_node
) {
42 hfinfo
= STAT_NODE_HFINFO(needle_stat_node
);
43 if (hfinfo
&& hfinfo
->id
== needle_hfinfo
->id
) {
44 return needle_stat_node
;
46 needle_stat_node
= g_node_next_sibling(needle_stat_node
);
49 /* Look up the tree */
50 up_parent_stat_node
= parent_stat_node
;
51 while (up_parent_stat_node
&& up_parent_stat_node
->parent
)
53 needle_stat_node
= g_node_first_child(up_parent_stat_node
->parent
);
54 while (needle_stat_node
) {
55 hfinfo
= STAT_NODE_HFINFO(needle_stat_node
);
56 if (hfinfo
&& hfinfo
->id
== needle_hfinfo
->id
) {
57 return needle_stat_node
;
59 needle_stat_node
= g_node_next_sibling(needle_stat_node
);
62 up_parent_stat_node
= up_parent_stat_node
->parent
;
65 /* None found. Create one. */
66 stats
= g_new(ph_stats_node_t
, 1);
68 /* Initialize counters */
69 stats
->hfinfo
= needle_hfinfo
;
70 stats
->num_pkts_total
= 0;
71 stats
->num_pdus_total
= 0;
72 stats
->num_pkts_last
= 0;
73 stats
->num_bytes_total
= 0;
74 stats
->num_bytes_last
= 0;
77 needle_stat_node
= g_node_new(stats
);
78 g_node_append(parent_stat_node
, needle_stat_node
);
79 return needle_stat_node
;
84 process_node(proto_node
*ptree_node
, GNode
*parent_stat_node
, ph_stats_t
*ps
)
87 ph_stats_node_t
*stats
;
88 proto_node
*proto_sibling_node
;
91 finfo
= PNODE_FINFO(ptree_node
);
92 /* We don't fake protocol nodes we expect them to have a field_info.
93 * Even with a faked proto tree, we don't fake nodes when PTREE_FINFO(tree)
94 * is NULL in order to avoid crashes here and elsewhere. (See epan/proto.c)
98 stat_node
= find_stat_node(parent_stat_node
, finfo
->hfinfo
);
100 stats
= STAT_NODE_STATS(stat_node
);
101 /* Only increment the total packet count once per packet for a given
102 * node, since there could be multiple PDUs in a frame.
103 * (All the other statistics should be incremented every time,
104 * including the count for how often a protocol was the last
105 * protocol in a packet.)
107 if (stats
->last_pkt
!= ps
->tot_packets
) {
108 stats
->num_pkts_total
++;
109 stats
->last_pkt
= ps
->tot_packets
;
111 stats
->num_pdus_total
++;
112 stats
->num_bytes_total
+= finfo
->length
+ finfo
->appendix_length
;
114 proto_sibling_node
= ptree_node
->next
;
116 /* Skip entries that are not protocols, e.g.
117 * toplevel tree item of desegmentation "[Reassembled TCP Segments]")
118 * XXX: We should probably skip PINOs with field_type FT_BYTES too.
120 * XXX: We look at siblings not children, and thus don't descend into
121 * the tree to pick up embedded protocols not added to the toplevel of
124 while (proto_sibling_node
&& !proto_registrar_is_protocol(PNODE_FINFO(proto_sibling_node
)->hfinfo
->id
)) {
125 proto_sibling_node
= proto_sibling_node
->next
;
128 if (proto_sibling_node
) {
129 process_node(proto_sibling_node
, stat_node
, ps
);
131 stats
->num_pkts_last
++;
132 stats
->num_bytes_last
+= finfo
->length
+ finfo
->appendix_length
;
139 process_tree(proto_tree
*protocol_tree
, ph_stats_t
* ps
)
141 proto_node
*ptree_node
;
144 * Skip over non-protocols and comments. (Packet comments are a PINO
145 * with FT_PROTOCOL field type). This keeps us from having a top-level
146 * "Packet comments" item that steals items from "Frame".
148 ptree_node
= ((proto_node
*)protocol_tree
)->first_child
;
149 while (ptree_node
&& (ptree_node
->finfo
->hfinfo
->id
== pc_proto_id
|| !proto_registrar_is_protocol(ptree_node
->finfo
->hfinfo
->id
))) {
150 ptree_node
= ptree_node
->next
;
157 process_node(ptree_node
, ps
->stats_tree
, ps
);
161 process_record(capture_file
*cf
, frame_data
*frame
, column_info
*cinfo
,
162 wtap_rec
*rec
, Buffer
*buf
, ph_stats_t
* ps
)
167 /* Load the record from the capture file */
168 if (!cf_read_record(cf
, frame
, rec
, buf
))
169 return false; /* failure */
171 /* Dissect the record tree not visible */
172 epan_dissect_init(&edt
, cf
->epan
, true, false);
173 /* Don't fake protocols. We need them for the protocol hierarchy */
174 epan_dissect_fake_protocols(&edt
, false);
175 epan_dissect_run(&edt
, cf
->cd_t
, rec
,
176 frame_tvbuff_new_buffer(&cf
->provider
, frame
, buf
),
179 /* Get stats from this protocol tree */
180 process_tree(edt
.tree
, ps
);
184 cur_time
= nstime_to_sec(&frame
->abs_ts
);
185 if (cur_time
< ps
->first_time
)
186 ps
->first_time
= cur_time
;
187 if (cur_time
> ps
->last_time
)
188 ps
->last_time
= cur_time
;
191 /* Free our memory. */
192 epan_dissect_cleanup(&edt
);
194 return true; /* success */
198 ph_stats_new(capture_file
*cf
)
203 progdlg_t
*progbar
= NULL
;
208 char status_str
[100];
209 int progbar_nextstep
;
212 if (!cf
) return NULL
;
215 ws_warning("Failing to compute protocol hierarchy stats on \"%s\" since a read is in progress", cf
->filename
);
218 cf
->read_lock
= true;
220 cf
->stop_flag
= false;
222 pc_proto_id
= proto_registrar_get_id_byname("pkt_comment");
224 /* Initialize the data */
225 ps
= g_new(ph_stats_t
, 1);
228 ps
->stats_tree
= g_node_new(NULL
);
229 ps
->first_time
= 0.0;
232 /* Update the progress bar when it gets to this value. */
233 progbar_nextstep
= 0;
234 /* When we reach the value that triggers a progress bar update,
235 bump that value by this amount. */
236 progbar_quantum
= cf
->count
/N_PROGBAR_UPDATES
;
237 /* Count of packets at which we've looked. */
239 /* Progress so far. */
243 ws_buffer_init(&buf
, 1514);
245 for (framenum
= 1; framenum
<= cf
->count
; framenum
++) {
246 frame
= frame_data_sequence_find(cf
->provider
.frames
, framenum
);
248 /* Create the progress bar if necessary.
249 We check on every iteration of the loop, so that
250 it takes no longer than the standard time to create
251 it (otherwise, for a large file, we might take
252 considerably longer than that standard time in order
253 to get to the next progress bar step). */
255 progbar
= delayed_create_progress_dlg(
256 cf
->window
, "Computing",
257 "protocol hierarchy statistics",
258 true, &cf
->stop_flag
, progbar_val
);
260 /* Update the progress bar, but do it only N_PROGBAR_UPDATES
261 times; when we update it, we have to run the GTK+ main
262 loop to get it to repaint what's pending, and doing so
263 may involve an "ioctl()" to see if there's any pending
264 input from an X server, and doing that for every packet
265 can be costly, especially on a big file. */
266 if (count
>= progbar_nextstep
) {
267 /* let's not divide by zero. I should never be started
268 * with count == 0, so let's assert that
270 ws_assert(cf
->count
> 0);
272 progbar_val
= (float) count
/ cf
->count
;
274 if (progbar
!= NULL
) {
275 snprintf(status_str
, sizeof(status_str
),
276 "%4u of %u frames", count
, cf
->count
);
277 update_progress_dlg(progbar
, progbar_val
, status_str
);
280 progbar_nextstep
+= progbar_quantum
;
284 /* Well, the user decided to abort the statistics.
285 computation process Just stop. */
289 /* Skip frames that are hidden due to the display filter.
290 XXX - should the progress bar count only packets that
291 passed the display filter? If so, it should
292 probably do so for other loops (see "file.c") that
293 look only at those packets. */
294 if (frame
->passed_dfilter
) {
297 if (ps
->tot_packets
== 0) {
298 double cur_time
= nstime_to_sec(&frame
->abs_ts
);
299 ps
->first_time
= cur_time
;
300 ps
->last_time
= cur_time
;
304 /* We throw away the statistics if we quit in the middle,
305 * so increment this first so that the count starts at 1
306 * when processing records, since we initialize the stat
307 * nodes' last_pkt to 0.
311 /* we don't care about colinfo */
312 if (!process_record(cf
, frame
, NULL
, &rec
, &buf
, ps
)) {
314 * Give up, and set "stop_flag" so we
315 * just abort rather than popping up
316 * the statistics window.
318 cf
->stop_flag
= true;
322 ps
->tot_bytes
+= frame
->pkt_len
;
328 wtap_rec_cleanup(&rec
);
329 ws_buffer_free(&buf
);
331 /* We're done calculating the statistics; destroy the progress bar
332 if it was created. */
334 destroy_progress_dlg(progbar
);
338 * We quit in the middle; throw away the statistics
339 * and return NULL, so our caller doesn't pop up a
340 * window with the incomplete statistics.
346 ws_assert(cf
->read_lock
);
347 cf
->read_lock
= false;
353 stat_node_free(GNode
*node
, void *data _U_
)
355 ph_stats_node_t
*stats
= (ph_stats_node_t
*)node
->data
;
361 ph_stats_free(ph_stats_t
*ps
)
363 if (ps
->stats_tree
) {
364 g_node_traverse(ps
->stats_tree
, G_IN_ORDER
,
366 stat_node_free
, NULL
);
367 g_node_destroy(ps
->stats_tree
);