25cfca480f30e5129e9d90ac948116fe6dd74570
[openafs.git] / src / xstat / xstat_cm.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * Description:
12  *      Implementation of the client side of the AFS Cache Manager
13  *      extended statistics facility.
14  *
15  *------------------------------------------------------------------------*/
16
17 #include <afsconfig.h>
18 #include <afs/param.h>
19
20 RCSID("$Header$");
21
22 #include "xstat_cm.h"                   /*Interface for this module*/
23 #include <lwp.h>                        /*Lightweight process package*/
24
25 #ifdef HAVE_STRING_H
26 #include <string.h>
27 #else
28 #ifdef HAVE_STRINGS_H
29 #include <strings.h>
30 #endif
31 #endif
32
33 #define LWP_STACK_SIZE  (16 * 1024)
34
35 /*
36  * Routines we need that don't have explicit include file definitions.
37  */
38 extern char *hostutil_GetNameByINet();  /*Host parsing utility*/
39
40 /*
41  * Exported variables.
42  */
43 int xstat_cm_numServers;                        /*Num connected servers*/
44 struct xstat_cm_ConnectionInfo
45     *xstat_cm_ConnInfo;                         /*Ptr to connection array*/
46 int numCollections;                             /*Number of data collections*/
47 struct xstat_cm_ProbeResults xstat_cm_Results;  /*Latest probe results*/
48 char terminationEvent;                          /*One-shot termination event*/
49
50 afs_int32 xstat_cmData[AFSCB_MAX_XSTAT_LONGS];          /*Buffer for collected data*/
51
52 /*
53  * Private globals.
54  */
55 static int xstat_cm_ProbeFreqInSecs;            /*Probe freq. in seconds*/
56 static int xstat_cm_initflag = 0;               /*Was init routine called?*/
57 static int xstat_cm_debug = 0;                  /*Debugging output enabled?*/
58 static int xstat_cm_oneShot = 0;                /*One-shot operation?*/
59 static int (*xstat_cm_Handler)();               /*Probe handler routine*/
60 static PROCESS probeLWP_ID;                     /*Probe LWP process ID*/
61 static int xstat_cm_numCollections;             /*Number of desired collections*/
62 static afs_int32 *xstat_cm_collIDP;                     /*Ptr to collection IDs desired*/
63
64
65 /*------------------------------------------------------------------------
66  * [private] xstat_cm_CleanupInit
67  *
68  * Description:
69  *      Set up for recovery after an error in initialization (i.e.,
70  *      during a call to xstat_cm_Init.
71  *
72  * Arguments:
73  *      None.
74  *
75  * Returns:
76  *      0 on success,
77  *      Error value otherwise.
78  *
79  * Environment:
80  *      This routine is private to the module.
81  *
82  * Side Effects:
83  *      Zeros out basic data structures.
84  *------------------------------------------------------------------------*/
85
86 static int xstat_cm_CleanupInit()
87
88 { /*xstat_cm_CleanupInit*/
89
90     xstat_cm_ConnInfo = (struct xstat_cm_ConnectionInfo *)0;
91     xstat_cm_Results.probeNum = 0;
92     xstat_cm_Results.probeTime = 0;
93     xstat_cm_Results.connP = (struct xstat_cm_ConnectionInfo *)0;
94     xstat_cm_Results.collectionNumber = 0;
95     xstat_cm_Results.data.AFSCB_CollData_len = AFSCB_MAX_XSTAT_LONGS;
96     xstat_cm_Results.data.AFSCB_CollData_val = (afs_int32 *)xstat_cmData;
97     xstat_cm_Results.probeOK = 0;
98
99     return(0);
100
101 } /*xstat_cm_CleanupInit*/
102
103
104 /*------------------------------------------------------------------------
105  * [exported] xstat_cm_Cleanup
106  *
107  * Description:
108  *      Clean up our memory and connection state.
109  *
110  * Arguments:
111  *      int a_releaseMem : Should we free up malloc'ed areas?
112  *
113  * Returns:
114  *      0 on total success,
115  *      -1 if the module was never initialized, or there was a problem
116  *              with the xstat_cm connection array.
117  *
118  * Environment:
119  *      xstat_cm_numServers should be properly set.  We don't do anything
120  *      unless xstat_cm_Init() has already been called.
121  *
122  * Side Effects:
123  *      Shuts down Rx connections gracefully, frees allocated space
124  *      (if so directed).
125  *------------------------------------------------------------------------*/
126
127 int xstat_cm_Cleanup(a_releaseMem)
128     int a_releaseMem;
129
130 { /*xstat_cm_Cleanup*/
131
132     static char rn[] = "xstat_cm_Cleanup";      /*Routine name*/
133     int code;                                   /*Return code*/
134     int conn_idx;                               /*Current connection index*/
135     struct xstat_cm_ConnectionInfo *curr_conn;  /*Ptr to xstat_cm connection*/
136
137     /*
138      * Assume the best, but check the worst.
139      */
140     if (!xstat_cm_initflag) {
141         fprintf(stderr, "[%s] Refused; module not initialized\n", rn);
142         return(-1);
143     }
144     else
145         code = 0;
146
147     /*
148      * Take care of all Rx connections first.  Check to see that the
149      * server count is a legal value.
150      */
151     if (xstat_cm_numServers <= 0) {
152         fprintf(stderr,
153                 "[%s] Illegal number of servers (xstat_cm_numServers = %d)\n",
154                 rn, xstat_cm_numServers);
155         code = -1;
156     }
157     else {
158         if (xstat_cm_ConnInfo != (struct xstat_cm_ConnectionInfo *)0) {
159             /*
160              * The xstat_cm connection structure array exists.  Go through
161              * it and close up any Rx connections it holds.
162              */
163             curr_conn = xstat_cm_ConnInfo;
164             for (conn_idx = 0; conn_idx < xstat_cm_numServers; conn_idx++) {
165                 if (curr_conn->rxconn != (struct rx_connection *)0) {
166                     rx_DestroyConnection(curr_conn->rxconn);
167                     curr_conn->rxconn = (struct rx_connection *)0;
168                 }
169                 curr_conn++;
170             } /*for each xstat_cm connection*/
171         } /*xstat_cm connection structure exists*/
172     } /*Legal number of servers*/
173
174     /*
175      * If asked to, release the space we've allocated.
176      */
177     if (a_releaseMem) {
178         if (xstat_cm_ConnInfo != (struct xstat_cm_ConnectionInfo *)0)
179             free(xstat_cm_ConnInfo);
180     }
181
182     /*
183      * Return the news, whatever it is.
184      */
185     return(code);
186
187 } /*xstat_cm_Cleanup*/
188
189
190 /*------------------------------------------------------------------------
191  * [private] xstat_cm_LWP
192  *
193  * Description:
194  *      This LWP iterates over the server connections and gathers up
195  *      the desired statistics from each one on a regular basis, for
196  *      all known data collections.  The associated handler function
197  *      is called each time a new data collection is received.
198  *
199  * Arguments:
200  *      None.
201  *
202  * Returns:
203  *      Nothing.
204  *
205  * Environment:
206  *      Started by xstat_cm_Init(), uses global structures and the
207  *      global private xstat_cm_oneShot variable.
208  *
209  * Side Effects:
210  *      As advertised.
211  *------------------------------------------------------------------------*/
212
213 static void xstat_cm_LWP()
214
215 { /*xstat_cm_LWP*/
216
217     static char rn[] = "xstat_cm_LWP";          /*Routine name*/
218     register afs_int32 code;                            /*Results of calls*/
219     int oneShotCode;                            /*Result of one-shot signal*/
220     struct timeval tv;                          /*Time structure*/
221     int conn_idx;                               /*Connection index*/
222     struct xstat_cm_ConnectionInfo *curr_conn;  /*Current connection*/
223     afs_int32 srvVersionNumber;                 /*Xstat version #*/
224     afs_int32 clientVersionNumber;                      /*Client xstat version*/
225     afs_int32 numColls;                         /*Number of collections to get*/
226     afs_int32 *currCollIDP;                             /*Curr collection ID desired*/
227
228     /*
229      * Set up some numbers we'll need.
230      */
231     clientVersionNumber = AFSCB_XSTAT_VERSION;
232
233     while (1) { /*Service loop*/
234         /*
235          * Iterate through the server connections, gathering data.
236          * Don't forget to bump the probe count and zero the statistics
237          * areas before calling the servers.
238          */
239         if (xstat_cm_debug)
240             printf("[%s] Waking up, getting data from %d server(s)\n",
241                    rn, xstat_cm_numServers);
242         curr_conn = xstat_cm_ConnInfo;
243         xstat_cm_Results.probeNum++;
244
245         for (conn_idx = 0; conn_idx < xstat_cm_numServers; conn_idx++) {
246             /*
247              * Grab the statistics for the current Cache Manager, if the
248              * connection is valid.
249              */
250             if (xstat_cm_debug)
251                 printf("[%s] Getting collections from Cache Manager '%s'\n",
252                        rn, curr_conn->hostName);
253             if (curr_conn->rxconn != (struct rx_connection *)0) {
254                 if (xstat_cm_debug)
255                     printf("[%s] Connection OK, calling RXAFSCB_GetXStats\n",
256                            rn);
257
258                 /*
259                  * Probe the given CM for each desired collection.
260                  */
261                 currCollIDP = xstat_cm_collIDP;
262                 for (numColls = 0;
263                      numColls < xstat_cm_numCollections;
264                      numColls++, currCollIDP++) {
265                     /*
266                      * Initialize the per-probe values.
267                      */
268                     if (xstat_cm_debug)
269                         printf("[%s] Asking for data collection %d\n",
270                                rn, *currCollIDP);
271                     xstat_cm_Results.collectionNumber = *currCollIDP;
272                     xstat_cm_Results.data.AFSCB_CollData_len =
273                         AFSCB_MAX_XSTAT_LONGS;
274                     memset(xstat_cm_Results.data.AFSCB_CollData_val, 0, AFSCB_MAX_XSTAT_LONGS * 4);
275
276                     xstat_cm_Results.connP = curr_conn;
277
278                     if (xstat_cm_debug) {
279                         printf("%s: Calling RXAFSCB_GetXStats, conn=0x%x, clientVersionNumber=%d, collectionNumber=%d, srvVersionNumberP=0x%x, timeP=0x%x, dataP=0x%x\n",
280                                rn, curr_conn->rxconn,
281                                clientVersionNumber,
282                                *currCollIDP,
283                                &srvVersionNumber,
284                                &(xstat_cm_Results.probeTime),
285                                &(xstat_cm_Results.data));
286                         printf("%s: [bufflen=%d, buffer at 0x%x]\n",
287                                rn,
288                                xstat_cm_Results.data.AFSCB_CollData_len,
289                                xstat_cm_Results.data.AFSCB_CollData_val);
290                     }
291
292                     xstat_cm_Results.probeOK =
293                         RXAFSCB_GetXStats(curr_conn->rxconn,
294                                           clientVersionNumber,
295                                           *currCollIDP,
296                                           &srvVersionNumber,
297                                           &(xstat_cm_Results.probeTime),
298                                           &(xstat_cm_Results.data));
299                     
300                     /*
301                      * Now that we (may) have the data for this connection,
302                      * call the associated handler function.  The handler
303                      * does not take any explicit parameters, but rather
304                      * gets to the goodies via some of the objects exported
305                      * by this module.
306                      */
307                     if (xstat_cm_debug)
308                         printf("[%s] Calling handler routine.\n",
309                                rn);
310                     code = xstat_cm_Handler();
311                     if (code)
312                         fprintf(stderr,
313                                 "[%s] Handler routine got error code %d\n",
314                                 rn, code);
315                 } /*For each collection*/
316             } /*Valid Rx connection*/
317             
318             /*
319              * Advance the xstat_cm connection pointer.
320              */
321             curr_conn++;
322
323         } /*For each xstat_cm connection*/
324
325         /*
326          * All (valid) connections have been probed.  Fall asleep for the
327          * prescribed number of seconds, unless we're a one-shot.  In
328          * that case, we need to signal our caller that we're done.
329          */
330         if (xstat_cm_debug)
331             printf("[%s] Polling complete for probe round %d.\n",
332                    rn, xstat_cm_Results.probeNum);
333
334         if (xstat_cm_oneShot) {
335             /*
336              * One-shot execution desired.  Signal our main procedure
337              * that we've finished our collection round.
338              */
339             if (xstat_cm_debug)
340                 printf("[%s] Signalling main process at 0x%x\n",
341                        rn, &terminationEvent);
342             oneShotCode = LWP_SignalProcess(&terminationEvent);
343             if (oneShotCode)
344                 fprintf(stderr,
345                         "[%s] Error %d from LWP_SignalProcess()",
346                         rn, oneShotCode);
347             break; /*from the perpetual while loop*/
348         } /*One-shot execution*/
349         else {
350             /*
351              * Continuous execution desired.  Sleep for the required
352              * number of seconds.
353              */
354             tv.tv_sec  = xstat_cm_ProbeFreqInSecs;
355             tv.tv_usec = 0;
356             if (xstat_cm_debug)
357                 printf("[%s] Falling asleep for %d seconds\n",
358                        rn, xstat_cm_ProbeFreqInSecs);
359             code = IOMGR_Select(0,      /*Num fids*/
360                                 0,      /*Descs ready for reading*/
361                                 0,      /*Descs ready for writing*/
362                                 0,      /*Descs w/exceptional conditions*/
363                                 &tv);   /*Ptr to timeout structure*/
364             if (code)
365                 fprintf(stderr,
366                         "[%s] IOMGR_Select returned code %d\n",
367                         rn, code);
368         } /*Continuous execution*/
369     } /*Service loop*/
370
371 } /*xstat_cm_LWP*/
372
373
374 /*------------------------------------------------------------------------
375  * [exported] xstat_cm_Init
376  *
377  * Description:
378  *      Initialize the xstat_cm module: set up Rx connections to the
379  *      given set of Cache Managers, start up the probe LWP, and
380  *      associate the routine to be called when a probe completes.
381  *      Also, let it know which collections you're interested in.
382  *
383  * Arguments:
384  *      int a_numServers                  : Num. servers to connect to.
385  *      struct sockaddr_in *a_socketArray : Array of server sockets.
386  *      int a_ProbeFreqInSecs             : Probe frequency in seconds.
387  *      int (*a_ProbeHandler)()           : Ptr to probe handler fcn.
388  *      int a_flags;                      : Various flags.
389  *      int a_numCollections              : Number of collections desired.
390  *      afs_int32 *a_collIDP                      : Ptr to collection IDs.
391  *
392  * Returns:
393  *      0 on success,
394  *      -2 for (at least one) connection error,
395  *      LWP process creation code, if it failed,
396  *      -1 for other fatal errors.
397  *
398  * Environment:
399  *      *** MUST BE THE FIRST ROUTINE CALLED FROM THIS PACKAGE ***
400  *
401  * Side Effects:
402  *      Sets up just about everything.
403  *------------------------------------------------------------------------*/
404
405 int xstat_cm_Init(a_numServers, a_socketArray, a_ProbeFreqInSecs,
406                   a_ProbeHandler, a_flags, a_numCollections, a_collIDP)
407     int a_numServers;
408     struct sockaddr_in *a_socketArray;
409     int a_ProbeFreqInSecs;
410     int (*a_ProbeHandler)();
411     int a_flags;
412     int a_numCollections;
413     afs_int32 *a_collIDP;
414
415 { /*xstat_cm_Init*/
416
417     static char rn[] = "xstat_cm_Init";         /*Routine name*/
418     register afs_int32 code;                            /*Return value*/
419     struct rx_securityClass *secobj;            /*Client security object*/
420     int arg_errfound;                           /*Argument error found?*/
421     int curr_srv;                               /*Current server idx*/
422     struct xstat_cm_ConnectionInfo *curr_conn;  /*Ptr to current conn*/
423     char *hostNameFound;                        /*Ptr to returned host name*/
424     int conn_err;                               /*Connection error?*/
425     int collIDBytes;                            /*Num bytes in coll ID array*/
426
427     /*
428      * If we've already been called, snicker at the bozo, gently
429      * remind him of his doubtful heritage, and return success.
430      */
431     if (xstat_cm_initflag) {
432         fprintf(stderr, "[%s] Called multiple times!\n", rn);
433         return(0);
434     }
435     else
436         xstat_cm_initflag = 1; 
437
438     /*
439      * Check the parameters for bogosities.
440      */
441     arg_errfound = 0;
442     if (a_numServers <= 0) {
443         fprintf(stderr, "[%s] Illegal number of servers: %d\n",
444                 rn, a_numServers);
445         arg_errfound = 1;
446     }
447     if (a_socketArray == (struct sockaddr_in *)0) {
448         fprintf(stderr, "[%s] Null server socket array argument\n", rn);
449         arg_errfound = 1;
450     }
451     if (a_ProbeFreqInSecs <= 0) {
452         fprintf(stderr, "[%s] Illegal probe frequency: %d\n",
453                 rn, a_ProbeFreqInSecs);
454         arg_errfound = 1;
455     }
456     if (a_ProbeHandler == (int (*)())0) {
457         fprintf(stderr, "[%s] Null probe handler function argument\n",
458                 rn);
459         arg_errfound = 1;
460     }
461     if (a_numCollections <= 0) {
462         fprintf(stderr, "[%s] Illegal collection count argument: %d\n",
463                 rn, a_numServers);
464         arg_errfound = 1;
465     }
466     if (a_collIDP == NULL) {
467         fprintf(stderr, "[%s] Null collection ID array argument\n", rn);
468         arg_errfound = 1;
469     }
470     if (arg_errfound)
471         return(-1);
472
473     /*
474      * Record our passed-in info.
475      */
476     xstat_cm_debug           = (a_flags & XSTAT_CM_INITFLAG_DEBUGGING);
477     xstat_cm_oneShot         = (a_flags & XSTAT_CM_INITFLAG_ONE_SHOT);
478     xstat_cm_numServers      = a_numServers;
479     xstat_cm_Handler         = a_ProbeHandler;
480     xstat_cm_ProbeFreqInSecs = a_ProbeFreqInSecs;
481     xstat_cm_numCollections  = a_numCollections;
482     collIDBytes = xstat_cm_numCollections * sizeof(afs_int32);
483     xstat_cm_collIDP         = (afs_int32 *)(malloc(collIDBytes));
484     memcpy(xstat_cm_collIDP, a_collIDP, collIDBytes);
485     if (xstat_cm_debug) {
486         printf("[%s] Asking for %d collection(s): ", rn, xstat_cm_numCollections);
487         for (curr_srv = 0; curr_srv < xstat_cm_numCollections; curr_srv++)
488             printf("%d ", *(xstat_cm_collIDP+curr_srv));
489         printf("\n");
490     }
491
492     /*
493      * Get ready in case we have to do a cleanup - basically, zero
494      * everything out.
495      */
496     code = xstat_cm_CleanupInit();
497     if (code)
498         return(code);
499
500     /*
501      * Allocate the necessary data structures and initialize everything
502      * else.
503      */
504     xstat_cm_ConnInfo =
505         (struct xstat_cm_ConnectionInfo *)
506             malloc(a_numServers * sizeof(struct xstat_cm_ConnectionInfo));
507     if (xstat_cm_ConnInfo == (struct xstat_cm_ConnectionInfo *)0) {
508         fprintf(stderr,
509                 "[%s] Can't allocate %d connection info structs (%d bytes)\n",
510                 rn, a_numServers,
511                 (a_numServers * sizeof(struct xstat_cm_ConnectionInfo)));
512         return(-1);     /*No cleanup needs to be done yet*/
513     }
514
515     /*
516      * Initialize the Rx subsystem, just in case nobody's done it.
517      */
518     if (xstat_cm_debug)
519         printf("[%s] Initializing Rx on port 0\n", rn);
520     code = rx_Init(htons(0));
521     if (code) {
522         fprintf(stderr, "[%s] Fatal error in rx_Init(), error=%d\n",
523                 rn, code);
524         return(-1);
525     }
526
527     if (xstat_cm_debug)
528         printf("[%s] Rx initialized on port 0\n", rn);
529
530     /*
531      * Create a null Rx client security object, to be used by the
532      * probe LWP.
533      */
534     secobj = rxnull_NewClientSecurityObject();
535     if (secobj == (struct rx_securityClass *)0) {
536         fprintf(stderr,
537                 "[%s] Can't create probe LWP client security object.\n",
538                 rn);
539         xstat_cm_Cleanup(1); /*Delete already-malloc'ed areas*/
540         return(-1);
541     }
542     if (xstat_cm_debug)
543         printf("[%s] Probe LWP client security object created\n",
544                rn);
545
546     curr_conn = xstat_cm_ConnInfo;
547     conn_err = 0;
548     for (curr_srv = 0; curr_srv < a_numServers; curr_srv++) {
549         /*
550          * Copy in the socket info for the current server, resolve its
551          * printable name if possible.
552          */
553         if (xstat_cm_debug) {
554             printf("[%s] Copying in the following socket info:\n",
555                    rn);
556             printf("[%s] IP addr 0x%lx, port %d\n", rn,
557                    (a_socketArray + curr_srv)->sin_addr.s_addr,
558                    (a_socketArray + curr_srv)->sin_port);
559         }
560         memcpy(&(curr_conn->skt), a_socketArray + curr_srv, sizeof(struct sockaddr_in));
561         
562         hostNameFound =
563             hostutil_GetNameByINet(curr_conn->skt.sin_addr.s_addr);
564         if (hostNameFound == NULL) {
565             fprintf(stderr,
566                     "[%s] Can't map Internet address %lu to a string name\n",
567                     rn, curr_conn->skt.sin_addr.s_addr);
568             curr_conn->hostName[0] = '\0';
569         }
570         else {
571             strcpy(curr_conn->hostName, hostNameFound);
572             if (xstat_cm_debug)
573                 printf("[%s] Host name for server index %d is %s\n",
574                        rn, curr_srv, curr_conn->hostName);
575         }
576         
577         /*
578          * Make an Rx connection to the current server.
579          */
580         if (xstat_cm_debug)
581             printf("[%s] Connecting to srv idx %d, IP addr 0x%lx, port %d, service 1\n",
582                    rn, curr_srv, curr_conn->skt.sin_addr.s_addr,
583                    curr_conn->skt.sin_port);
584         curr_conn->rxconn =
585             rx_NewConnection(curr_conn->skt.sin_addr.s_addr, /*Server addr*/
586                              curr_conn->skt.sin_port,        /*Server port*/
587                              1,                              /*AFS service #*/
588                              secobj,                         /*Security obj*/
589                              0);                             /*# of above*/
590         if (curr_conn->rxconn == (struct rx_connection *)0) {
591             fprintf(stderr,
592                     "[%s] Can't create Rx connection to server '%s' (%lu)\n",
593                     rn, curr_conn->hostName, curr_conn->skt.sin_addr.s_addr);
594             conn_err = 1;
595         }
596         if (xstat_cm_debug)
597             printf("[%s] New connection at 0x%lx\n",
598                    rn, curr_conn->rxconn);
599
600         /*
601          * Bump the current xstat_cm connection to set up.
602          */
603         curr_conn++;
604
605     } /*for curr_srv*/
606
607     /*
608      * Start up the probe LWP.
609      */
610     if (xstat_cm_debug)
611         printf("[%s] Creating the probe LWP\n", rn);
612     code =
613         LWP_CreateProcess(xstat_cm_LWP,         /*Function to start up*/
614                           LWP_STACK_SIZE,       /*Stack size in bytes*/
615                           1,                    /*Priority*/
616                           0,                    /*Parameters*/
617                           "xstat_cm Worker",    /*Name to use*/
618                           &probeLWP_ID);        /*Returned LWP process ID*/
619     if (code) {
620         fprintf(stderr,
621                 "[%s] Can't create xstat_cm LWP!  Error is %d\n",
622                 rn, code);
623         xstat_cm_Cleanup(1); /*Delete already-malloc'ed areas*/
624         return(code);
625     }
626     if (xstat_cm_debug)
627         printf("[%s] Probe LWP process structure located at 0x%x\n",
628                rn, probeLWP_ID);
629
630     /*
631      * Return the final results.
632      */
633     if (conn_err)
634         return(-2);
635     else
636         return(0);
637
638 } /*xstat_cm_Init*/
639
640
641 /*------------------------------------------------------------------------
642  * [exported] xstat_cm_ForceProbeNow
643  *
644  * Description:
645  *      Wake up the probe LWP, forcing it to execute a probe immediately.
646  *
647  * Arguments:
648  *      None.
649  *
650  * Returns:
651  *      0 on success,
652  *      Error value otherwise.
653  *
654  * Environment:
655  *      The module must have been initialized.
656  *
657  * Side Effects:
658  *      As advertised.
659  *------------------------------------------------------------------------*/
660
661 int xstat_cm_ForceProbeNow()
662
663 { /*xstat_cm_ForceProbeNow*/
664
665     static char rn[] = "xstat_cm_ForceProbeNow";        /*Routine name*/
666
667     /*
668      * There isn't a prayer unless we've been initialized.
669      */
670     if (!xstat_cm_initflag) {
671         fprintf(stderr, "[%s] Must call xstat_cm_Init first!\n", rn);
672         return(-1);
673     }
674
675     /*
676      * Kick the sucker in the side.
677      */
678     IOMGR_Cancel(probeLWP_ID);
679
680     /*
681      * We did it, so report the happy news.
682      */
683     return(0);
684
685 } /*xstat_cm_ForceProbeNow*/