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