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