          <<< NOTED::DISK$NOTES7:[NOTES$LIBRARY_7OF4]HACKERS.NOTE;1 >>>
                               -< ** Hackers ** >-
================================================================================
Note 394.36  $getspi: Monitoring % CPU Utilization and Process Count    36 of 37
XDELTA::HOFFMAN "Steve, OpenVMS Engineering"        285 lines   1-AUG-1996 17:09
                             -< I_SPI_INTERVAL.C >-
--------------------------------------------------------------------------------

  The phrase "half by half" does not have a clear meaning in American
  English.  (It is closest to a bovine-derived substance sometimes
  consumed in coffee. :-)  But I'll venture on and try to answer...

  The "magic" here -- why the interrupt and idle counts appear to be
  incorrect -- is that the "idle" tick value is "double-counted".  In
  other words, the idle loop runs on the interrupt stack, and is counted
  against the idle count *and* against the interrupt count.

  From module [SYS]TIMESCHDL.LIS:

      INCL    CPU$L_NULLCPU(R1)       ;Accumulate NULL CPU time this processor
 48$: INCL    CPU$L_KERNEL(R1)[R0]    ;Account for tick on interrupt stack

  I've attached an update to the program that polls the system at
  an interval, and displays the difference between the two polls.
  It also accounts for the idle-vs-interrupt counting, which is what
  was apparently confusing you.  (And me, for that matter. :-)

  --

/*
//  Demonstration of the undocumented GET SYSTEM PERFORMANCE INFORMATION
//  call EXE$GETSPI used by MONITOR.  The call first appeared in VMS V4.4.
//  Note the calling interface to this particular routine is not documented,
//  and the interface has changed in the past, and may change in the future.
//
//  Use the following LINK command to pick up the definition of EXE$GETSPI.
//
//	$
//	$ CC/DECC I_SPI
//	$ LINK I_SPI,SYS$INPUT/OPTIONS
//	SYS$SHARE:SPISHR/SHARE	! the SPI shareable
//	$
//
//  Note that the SPISHR.EXE shareable image MUST be installed.  (But
//  note MONITOR requires that the shareable be installed, too.)  This
//  program was tested under OpenVMS V6.2.
//
//  Note certain itemcodes are known to have changed at/hear the T5.0
//  OpenVMS release.
//
//  Note the spidef.h module present on recent versions of OpenVMS is
//  not associated and not related to the spidef.h module used here.
//
//  17-Feb-1988	Stephen (XDELTA::) Hoffman
//	Program twisted into a demonstration suitable for public
//	consumption.  I can't be sure any of this really works.
//	(Though I did seem to get the SPI$_DISKS call working...)
//  22-Feb-1988	Stephen (XDELTA::) Hoffman
//	Updated for T5.0 VMS.
//  10-Sep-1992 Peter (RANGER::) Vatne
//	Updated for VMS V5.5.
//  02-Apr-1996 Stephen (XDELTA::) Hoffman
//	Updated for OpenVMS VAX and Alpha V6.2, DEC C.
//  01-Jul-1996 Stephen (XDELTA::) Hoffman
//	Now displays interval; updated to display interrupt time alone
//
*/
#include <lib$routines.h>
#include <ssdef.h>
#include <starlet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stsdef.h>
#include <syidef.h>
#include "spidef.h"
#define  IL3_MAX	10
#define  DELAY_INTERVAL_IN_SECONDS	10
/*
//  The following is a vanilla three longword itemlist structure used
//  by the $GETSPI call...  The same format itemlist structure used by
//  the standard/documented SYS$GETxxI calls.
*/
struct item_list_3
    {
    unsigned short item_size;
    unsigned short item_code;
    void *address;
    void *return_length_address;
    };
int exe$getspi( 
	int,	    /* event flag */
	void *,	    /* VMScluster CSID */
	void *,	    /* address of node name descriptor */
	void *,	    /* itemlist address */
	void *,	    /* IOSB address */
	void *,	    /* AST Address */
	void * );   /* AST parameter */
main()
    {
    unsigned long int ef;
    unsigned long int retstat;
    unsigned short int iosb[4];
    int i;
    unsigned long int ModeEntrySize;
#pragma __member_alignment __save
#pragma __nomember_alignment
    /*
    //	The pragma member alignment is used to prevent DEC C
    //	from padding the structure fields, and thus throwing
    //	off the arrays.
    */
    struct ModeEntry
	{
	unsigned char number;		/* cpu number */
	unsigned long int interrupt;    /* kernel on i-stack *and* idle ticks */
	unsigned long int mpsynch;	/* spinlock/kernel ticks */
	unsigned long int kernel;	/* kernel mode ticks */
	unsigned long int exec;		/* exec mode ticks */
	unsigned long int super;	/* supervisor mode ticks */
	unsigned long int user;		/* user mode ticks */
	unsigned long int compat;	/* compatibility mode ticks */
	unsigned long int idle;		/* idle ticks */
        };
#pragma __member_alignment __restore
    struct ModeEntry *ModeEntryPtr0, *ModeEntryPtr1;
    char *ModeEntryArray0, *ModeEntryArray1;
    unsigned long CurStateProcs;
    unsigned long int TotalTicks0, TotalTicks1, IdleTicks0, IdleTicks1;
    unsigned long int ActiveCPUs;
    struct item_list_3 spi_itmlst[IL3_MAX];
    struct item_list_3 syi_itmlst[IL3_MAX];


    i = 0;
    syi_itmlst[i].item_size			= sizeof( int );
    syi_itmlst[i].item_code			= SYI$_ACTIVECPU_CNT;
    syi_itmlst[i].address			= &ActiveCPUs;
    syi_itmlst[i++].return_length_address	= NULL;
    syi_itmlst[i].item_size			= 0;
    syi_itmlst[i].item_code			= 0;
    syi_itmlst[i].address			= NULL;
    syi_itmlst[i++].return_length_address	= NULL;

    /*
    //	Get the number of processors.
    */
    retstat = lib$get_ef( &ef );
    if (!$VMS_STATUS_SUCCESS( retstat )) return retstat;
    retstat = sys$getsyi( ef, 0, 0, &syi_itmlst, &iosb, 0, 0);
    if (!$VMS_STATUS_SUCCESS( retstat )) return retstat;
    retstat = sys$synch( ef, &iosb);
    if (!$VMS_STATUS_SUCCESS( retstat )) return retstat;

    /*
    //	Calculate the Size Of The Array Needed For The Data:
    //	((NumberOfCPUs * ModeArrayEntrySize) + LongwordForActiveCPUs
    //	+ LongwordFudgeFactor)
    */
    ModeEntrySize = (sizeof( struct ModeEntry ) * ActiveCPUs) + (2 * sizeof( int ));
    ModeEntryArray0 = malloc( ModeEntrySize );
    memset( ModeEntryArray0, 0, ModeEntrySize );
    ModeEntryArray1 = malloc( ModeEntrySize );
    memset( ModeEntryArray1, 0, ModeEntrySize );

    /*
    //	Create the itemlist.  Note that this itemlist gets reused below, so
    //	beware of re-ordering _any_ of the itemcode entries in this list...
    */
    i = 0;
    spi_itmlst[i].item_size			= ModeEntrySize;
    spi_itmlst[i].item_code			= SPI$_MODES;
    spi_itmlst[i].address			= ModeEntryArray0;
    spi_itmlst[i++].return_length_address	= NULL;
    spi_itmlst[i].item_size			= sizeof( CurStateProcs);
    spi_itmlst[i].item_code			= SPI$_CUR;
    spi_itmlst[i].address			= &CurStateProcs;
    spi_itmlst[i++].return_length_address	= NULL;
    spi_itmlst[i].item_size			= 0;
    spi_itmlst[i].item_code			= 0;
    spi_itmlst[i].address			= NULL;
    spi_itmlst[i++].return_length_address	= NULL;

    /*
    //	Collect a sample...
    */
    printf("Collecting a sample...\n");
    retstat = exe$getspi( ef, 0, 0, &spi_itmlst, &iosb, 0, 0);
    if (!$VMS_STATUS_SUCCESS( retstat )) return retstat;
    retstat = sys$synch( ef, &iosb);
    if (!$VMS_STATUS_SUCCESS( retstat )) return retstat;
    printf("...Sample collected.\n");

    /*
    //	Accumulate the ticks from all processors from the first sample...
    //	(->idle is *not* counted here, as it's also part of ->interrupt.)
    */
    ModeEntryPtr0 = (void *)((char *) ModeEntryArray0 + sizeof( int ));
    IdleTicks0 = 0;
    TotalTicks0 = 0;
    for ( i = 0; i < ActiveCPUs; i++ )
	{
	TotalTicks0 +=
	    ModeEntryPtr0->interrupt +
	    ModeEntryPtr0->mpsynch   +
	    ModeEntryPtr0->kernel    +
            ModeEntryPtr0->exec      +
            ModeEntryPtr0->super     +
            ModeEntryPtr0->user      +
            ModeEntryPtr0->compat;
	IdleTicks0 += ModeEntryPtr0->interrupt - ModeEntryPtr0->idle;
	ModeEntryPtr0 += 1;
	};

    /*
    //	Reset the itemlist -- note we basically reuse the original list...
    */
    i = 0;
    spi_itmlst[i].address			= ModeEntryArray1;

    /*
    //	Delay by some number of seconds...
    */
    printf("Waiting 0x0%x seconds...\n", DELAY_INTERVAL_IN_SECONDS );
    sleep( DELAY_INTERVAL_IN_SECONDS );

    /*
    //	Collect another sample...
    */
    printf("Collecting a sample...\n");
    retstat = exe$getspi( ef, 0, 0, &spi_itmlst, &iosb, 0, 0);
    if (!$VMS_STATUS_SUCCESS( retstat )) return retstat;
    retstat = sys$synch( ef, &iosb);
    if (!$VMS_STATUS_SUCCESS( retstat )) return retstat;
    printf("...Sample collected.\n");

    /*
    //	Accumulate the ticks from all processors from the second sample...
    //	(->idle is *not* counted here, as it's also part of ->interrupt.)
    */
    ModeEntryPtr1 = (void *)((char *) ModeEntryArray1 + sizeof( int ));
    IdleTicks1 = 0;
    TotalTicks1 = 0;
    for ( i = 0; i < ActiveCPUs; i++ )
	{
	TotalTicks1 +=
	    ModeEntryPtr1->interrupt +
	    ModeEntryPtr1->mpsynch  + 
	    ModeEntryPtr1->kernel    +
            ModeEntryPtr1->exec      +
            ModeEntryPtr1->super     +
            ModeEntryPtr1->user      +
            ModeEntryPtr1->compat;
	IdleTicks1 += ModeEntryPtr1->interrupt - ModeEntryPtr1->idle;
	ModeEntryPtr1 += 1;
	};

    /*
    //	Now display the results...
    */
    printf("Number of CPUs present:   0x0%08.8x\n", *(int *) ModeEntryArray0 );
    printf("number of active CPUs:    0x0%08.8x\n", ActiveCPUs );

    printf("\n");

    printf("Total Ticks (all CPUs):   0x0%08.8x\n", TotalTicks1 - TotalTicks0 );

    ModeEntryPtr0 = (void *)((char *) ModeEntryArray0 + sizeof( int ));
    ModeEntryPtr1 = (void *)((char *) ModeEntryArray1 + sizeof( int ));
    printf("\n");
    printf("Accumulated (per-CPU) counts:\n");
    for ( i = 0; i < ActiveCPUs; i++ )
	{
	printf("0x0%02.2x:interrupt:         0x0%08.8x\n", ModeEntryPtr1->number, IdleTicks1 - IdleTicks0 );
	printf("0x0%02.2x:mpsynch:           0x0%08.8x\n", ModeEntryPtr1->number, ModeEntryPtr1->mpsynch - ModeEntryPtr0->mpsynch);
	printf("0x0%02.2x:kernel:            0x0%08.8x\n", ModeEntryPtr1->number, ModeEntryPtr1->kernel - ModeEntryPtr0->kernel);
	printf("0x0%02.2x:executive:         0x0%08.8x\n", ModeEntryPtr1->number, ModeEntryPtr1->exec - ModeEntryPtr0->exec);
	printf("0x0%02.2x:supervisor:        0x0%08.8x\n", ModeEntryPtr1->number, ModeEntryPtr1->super - ModeEntryPtr0->super);
	printf("0x0%02.2x:user:              0x0%08.8x\n", ModeEntryPtr1->number, ModeEntryPtr1->user - ModeEntryPtr0->user);
	printf("0x0%02.2x:compatibility:     0x0%08.8x\n", ModeEntryPtr1->number, ModeEntryPtr1->compat - ModeEntryPtr0->compat);
	printf("0x0%02.2x:idle               0x0%08.8x\n", ModeEntryPtr1->number, ModeEntryPtr1->idle - ModeEntryPtr0->idle);
	printf("\n");
	ModeEntryPtr0 += 1;
	ModeEntryPtr1 += 1;
	};

    printf(" number of processes in CUR state: %d\n", CurStateProcs );

    return SS$_NORMAL;
    }
    

