Remove static that was added to appease Coverity 753. 2016 bytes isn't too much to...
[obnox/wireshark/wip.git] / tap-rpcstat.c
1 /* tap-rpcstat.c
2  * rpcstat   2002 Ronnie Sahlberg
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  */
24
25 /* This module provides rpc call/reply SRT statistics to tshark.
26  * It is only used by tshark and not wireshark
27  *
28  * It serves as an example on how to use the tap api.
29  */
30
31 #ifdef HAVE_CONFIG_H
32 # include "config.h"
33 #endif
34
35 #include <stdio.h>
36
37 #ifdef HAVE_SYS_TYPES_H
38 # include <sys/types.h>
39 #endif
40
41 #include <string.h>
42 #include "epan/packet_info.h"
43 #include <epan/tap.h>
44 #include <epan/stat_cmd_args.h>
45 #include <epan/dissectors/packet-rpc.h>
46
47 #define MICROSECS_PER_SEC   1000000
48 #define NANOSECS_PER_SEC    1000000000
49
50 /* used to keep track of statistics for a specific procedure */
51 typedef struct _rpc_procedure_t {
52         const char *proc;
53         int num;
54         nstime_t min;
55         nstime_t max;
56         nstime_t tot;
57 } rpc_procedure_t;
58
59 /* used to keep track of the statistics for an entire program interface */
60 typedef struct _rpcstat_t {
61         const char *prog;
62         char *filter;
63         guint32 program;
64         guint32 version;
65         guint32 num_procedures;
66         rpc_procedure_t *procedures;
67 } rpcstat_t;
68
69
70
71 /* This callback is never used by tshark but it is here for completeness.
72  * When registering below, we could just have left this function as NULL.
73  *
74  * When used by wireshark, this function will be called whenever we would need
75  * to reset all state. Such as when wireshark opens a new file, when it
76  * starts a new capture, when it rescans the packetlist after some prefs have
77  * changed etc.
78  * So if your application has some state it needs to clean up in those
79  * situations, here is a good place to put that code.
80  */
81 static void
82 rpcstat_reset(void *prs)
83 {
84         rpcstat_t *rs=prs;
85         guint32 i;
86
87         for(i=0;i<rs->num_procedures;i++){
88                 rs->procedures[i].num=0;
89                 rs->procedures[i].min.secs=0;
90                 rs->procedures[i].min.nsecs=0;
91                 rs->procedures[i].max.secs=0;
92                 rs->procedures[i].max.nsecs=0;
93                 rs->procedures[i].tot.secs=0;
94                 rs->procedures[i].tot.nsecs=0;
95         }
96 }
97
98
99 /* This callback is invoked whenever the tap system has seen a packet
100  * we might be interested in.
101  * The function is to be used to only update internal state information
102  * in the *tapdata structure, and if there were state changes which requires
103  * the window to be redrawn, return 1 and (*draw) will be called sometime
104  * later.
105  *
106  * This function should be as lightweight as possible since it executes together
107  * with the normal wireshark dissectors. Try to push as much processing as
108  * possible into (*draw) instead since that function executes asynchronously
109  * and does not affect the main threads performance.
110  *
111  * If it is possible, try to do all "filtering" explicitely as we do below in
112  * this example since you will get MUCH better performance than applying
113  * a similar display-filter in the register call.
114  *
115  * The third parameter is tap dependant. Since we register this one to the "rpc"
116  * tap the third parameters type is rpc_call_info_value.
117  *
118  *
119  * The filtering we do is just to check the rpc_call_info_value struct that
120  * we were called for the proper program and version. We didnt apply a filter
121  * when we registered so we will be called for ALL rpc packets and not just
122  * the ones we are collecting stats for.
123  *
124  *
125  * function returns :
126  *  0: no updates, no need to call (*draw) later
127  * !0: state has changed, call (*draw) sometime later
128  */
129 static int
130 rpcstat_packet(void *prs, packet_info *pinfo, epan_dissect_t *edt _U_, const void *pri)
131 {
132         rpcstat_t *rs=prs;
133         const rpc_call_info_value *ri=pri;
134         nstime_t delta;
135         rpc_procedure_t *rp;
136
137         if(ri->proc>=rs->num_procedures){
138                 /* dont handle this since its outside of known table */
139                 return 0;
140         }
141         /* we are only interested in reply packets */
142         if(ri->request){
143                 return 0;
144         }
145         /* we are only interested in certain program/versions */
146         if( (ri->prog!=rs->program) || (ri->vers!=rs->version) ){
147                 return 0;
148         }
149
150         rp=&(rs->procedures[ri->proc]);
151
152         /* calculate time delta between request and reply */
153         nstime_delta(&delta, &pinfo->fd->abs_ts, &ri->req_time);
154
155         if(rp->num==0){
156                 rp->max.secs=delta.secs;
157                 rp->max.nsecs=delta.nsecs;
158         }
159
160         if(rp->num==0){
161                 rp->min.secs=delta.secs;
162                 rp->min.nsecs=delta.nsecs;
163         }
164
165         if( (delta.secs<rp->min.secs)
166         ||( (delta.secs==rp->min.secs)
167           &&(delta.nsecs<rp->min.nsecs) ) ){
168                 rp->min.secs=delta.secs;
169                 rp->min.nsecs=delta.nsecs;
170         }
171
172         if( (delta.secs>rp->max.secs)
173         ||( (delta.secs==rp->max.secs)
174           &&(delta.nsecs>rp->max.nsecs) ) ){
175                 rp->max.secs=delta.secs;
176                 rp->max.nsecs=delta.nsecs;
177         }
178
179         rp->tot.secs += delta.secs;
180         rp->tot.nsecs += delta.nsecs;
181         if(rp->tot.nsecs > NANOSECS_PER_SEC){
182                 rp->tot.nsecs -= NANOSECS_PER_SEC;
183                 rp->tot.secs++;
184         }
185
186         rp->num++;
187
188         return 1;
189 }
190
191 /* This callback is used when tshark wants us to draw/update our
192  * data to the output device. Since this is tshark only output is
193  * stdout.
194  * TShark will only call this callback once, which is when tshark has
195  * finished reading all packets and exists.
196  * If used with wireshark this may be called any time, perhaps once every 3
197  * seconds or so.
198  * This function may even be called in parallell with (*reset) or (*draw)
199  * so make sure there are no races. The data in the rpcstat_t can thus change
200  * beneath us. Beware.
201  */
202 static void
203 rpcstat_draw(void *prs)
204 {
205         rpcstat_t *rs=prs;
206         guint32 i;
207         guint64 td;
208         printf("\n");
209         printf("=======================================================\n");
210         printf("%s Version %d SRT Statistics:\n", rs->prog, rs->version);
211         printf("Filter: %s\n",rs->filter?rs->filter:"");
212         printf("Procedure        Calls    Min SRT    Max SRT    Avg SRT\n");
213         for(i=0;i<rs->num_procedures;i++){
214                 if(rs->procedures[i].num==0){
215                         continue;
216                 }
217                 /* Scale the average SRT in units of 1us and round to the nearest us. */
218                 td = ((guint64)(rs->procedures[i].tot.secs)) * NANOSECS_PER_SEC + rs->procedures[i].tot.nsecs;
219                 td = ((td / rs->procedures[i].num) + 500) / 1000;
220
221                 printf("%-15s %6d %3d.%06d %3d.%06d %3" G_GINT64_MODIFIER "u.%06" G_GINT64_MODIFIER "u\n",
222                         rs->procedures[i].proc,
223                         rs->procedures[i].num,
224                         (int)(rs->procedures[i].min.secs),(rs->procedures[i].min.nsecs+500)/1000,
225                         (int)(rs->procedures[i].max.secs),(rs->procedures[i].max.nsecs+500)/1000,
226                         td/MICROSECS_PER_SEC, td%MICROSECS_PER_SEC
227                 );
228         }
229         printf("=======================================================\n");
230 }
231
232 static guint32 rpc_program=0;
233 static guint32 rpc_version=0;
234 static gint32 rpc_min_proc=-1;
235 static gint32 rpc_max_proc=-1;
236
237 static void *
238 rpcstat_find_procs(gpointer *key, gpointer *value _U_, gpointer *user_data _U_)
239 {
240         rpc_proc_info_key *k=(rpc_proc_info_key *)key;
241
242         if(k->prog!=rpc_program){
243                 return NULL;
244         }
245         if(k->vers!=rpc_version){
246                 return NULL;
247         }
248         if(rpc_min_proc==-1){
249                 rpc_min_proc=k->proc;
250                 rpc_max_proc=k->proc;
251         }
252         if((gint32)k->proc<rpc_min_proc){
253                 rpc_min_proc=k->proc;
254         }
255         if((gint32)k->proc>rpc_max_proc){
256                 rpc_max_proc=k->proc;
257         }
258
259         return NULL;
260 }
261
262
263 /* When called, this function will create a new instance of rpcstat.
264  * program and version are whick onc-rpc program/version we want to
265  * collect statistics for.
266  * This function is called from tshark when it parses the -z rpc, arguments
267  * and it creates a new instance to store statistics in and registers this
268  * new instance for the rpc tap.
269  */
270 static void
271 rpcstat_init(const char *optarg, void* userdata _U_)
272 {
273         rpcstat_t *rs;
274         guint32 i;
275         int program, version;
276         int pos=0;
277         const char *filter=NULL;
278         GString *error_string;
279
280         if(sscanf(optarg,"rpc,rtt,%d,%d,%n",&program,&version,&pos)==2){
281                 if(pos){
282                         filter=optarg+pos;
283                 } else {
284                         filter=NULL;
285                 }
286         } else {
287                 fprintf(stderr, "tshark: invalid \"-z rpc,rtt,<program>,<version>[,<filter>]\" argument\n");
288                 exit(1);
289         }
290
291         rs=g_malloc(sizeof(rpcstat_t));
292         rs->prog=rpc_prog_name(program);
293         rs->program=program;
294         rs->version=version;
295         if(filter){
296                 rs->filter=g_strdup(filter);
297         } else {
298                 rs->filter=NULL;
299         }
300         rpc_program=program;
301         rpc_version=version;
302         rpc_min_proc=-1;
303         rpc_max_proc=-1;
304         g_hash_table_foreach(rpc_procs, (GHFunc)rpcstat_find_procs, NULL);
305         if(rpc_min_proc==-1){
306                 fprintf(stderr,"tshark: Invalid -z rpc,rrt,%d,%d\n",rpc_program,rpc_version);
307                 fprintf(stderr,"   Program:%d version:%d isn't supported by tshark.\n", rpc_program, rpc_version);
308                 exit(1);
309         }
310
311
312         rs->num_procedures=rpc_max_proc+1;
313         rs->procedures=g_malloc(sizeof(rpc_procedure_t)*(rs->num_procedures+1));
314         for(i=0;i<rs->num_procedures;i++){
315                 rs->procedures[i].proc=rpc_proc_name(program, version, i);
316                 rs->procedures[i].num=0;
317                 rs->procedures[i].min.secs=0;
318                 rs->procedures[i].min.nsecs=0;
319                 rs->procedures[i].max.secs=0;
320                 rs->procedures[i].max.nsecs=0;
321                 rs->procedures[i].tot.secs=0;
322                 rs->procedures[i].tot.nsecs=0;
323         }
324
325 /* It is possible to create a filter and attach it to the callbacks. Then the
326  * callbacks would only be invoked if the filter matched.
327  * Evaluating filters is expensive and if we can avoid it and not use them
328  * we gain performance.
329  * In this case we do the filtering for protocol and version inside the
330  * callback itself but use whatever filter the user provided.
331  * (Perhaps the user only want the stats for nis+ traffic for certain objects?)
332  *
333  */
334
335         error_string=register_tap_listener("rpc", rs, filter, 0, rpcstat_reset, rpcstat_packet, rpcstat_draw);
336         if(error_string){
337                 /* error, we failed to attach to the tap. clean up */
338                 g_free(rs->procedures);
339                 g_free(rs->filter);
340                 g_free(rs);
341
342                 fprintf(stderr, "tshark: Couldn't register rpc,rtt tap: %s\n",
343                     error_string->str);
344                 g_string_free(error_string, TRUE);
345                 exit(1);
346         }
347 }
348
349
350 void
351 register_tap_listener_rpcstat(void)
352 {
353         register_stat_cmd_arg("rpc,rtt,", rpcstat_init,NULL);
354 }
355