/*****************************************************************************/
/*
                             HyperSpi$agent.c

This is the System Performance Information collection agent for the Hypertext 
SPI facility.  It executes on selected nodes, writing selected system 
performance information into a data file, once every minute.  The data 
contains per-minute average and total values (depending on the datum), and 
peak values based on the granularity of whatever is the collection period 
(currently 1 second).  These data files are kept on a per-day basis.  This 
data may then be processed and displayed by the Hypertext SPI facility. 

It utilizes the undocumented EXE$GETSPI interface for Alpha and VAX, the
documented and supported (as from V7.3) $GETRMI interface for Itanium. 
Original idea and details plagiarised (and extensively reworked) from a VMS
X Window System utility named SPI. 


BUILD DETAILS
-------------
See BUILD_HYPERSPI$AGENT.COM procedure.


COPYRIGHT
---------
Copyright (C) 1996-2020 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.
http://www.gnu.org/licenses/gpl.txt


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
14-OCT-2014  MGD  v2.1.0, bump version in line with HYPERSPI.C
21-MAY-2011  MGD  v2.0.0, port to Itanium (changed the size of some data)
                          add network interface data
                          data format version 3 (shared with HyperSPI++)
                          (There is a lot of work that *could* be done to this
                           older piece of code but I've spent the minimal time
                           necessary just to make it work on modern platforms.)
23-DEC-2003  MGD  v1.1.1, minor conditional mods to support IA64
09-SEP-2001  MGD  v1.1.0, provide PID logical name to allow $FORCEX
07-JUL-2001  MGD  v1.0.4, remove 'register' from storage declarations
15-FEB-2001  MGD  v1.0.3, bugfix; MBytes memory for systems >= 4096MB
28-OCT-2000  MGD  v1.0.2, make adjustments for RELEAXED_ANSI compilation
10-AUG-2000  MGD  v1.0.1, work around HPARITH exception with lib$cvtf()
13-AUG-1998  MGD  NOTE:   this application is not Y2K compliant (in the sense
                          data is stored in files named with a 2 digit year!)
20-JUN-1995  MGD  v1.0.0, initial development
*/
/*****************************************************************************/

#define SOFTWAREVN "2.1.0"
#define SOFTWARENM "HYPERSPI$AGENT"
#ifdef __ALPHA
   char SoftwareID [] = SOFTWARENM " AXP-" SOFTWAREVN;
#endif
#ifdef __ia64
   char SoftwareID [] = SOFTWARENM " IA64-" SOFTWAREVN;
#endif
#ifdef __VAX
   char SoftwareID [] = SOFTWARENM " VAX-" SOFTWAREVN;
#endif
#ifdef __x86_64
   char SoftwareID [] = SOFTWARENM " X86-" SOFTWAREVN;
#endif

/* standard C header files */
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

/* VMS-related header files */
#include <descrip.h>
#include <dvidef.h>
#include <dvsdef.h>
#include <jpidef.h>
#include <libdtdef.h>
#include <lnmdef.h>
#include <psldef.h>
#include <iodef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>
#include <rms.h>
#include <rmsdef.h>

/* Alpha and VAX will use the EXE$SPI interface, Itanium will use $GETRMI */
#ifdef __ia64
#include <rmidef.h>
#endif

/* application header file */
#include "hyperspi.h"
#include "spidef.h"

#ifndef __VAX
#   pragma nomember_alignment
#endif

#define boolean int
#define true 1
#define false 0
 
#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))
 
#define COLLECT_INTERVAL_SECONDS 1
#define RECORD_INTERVAL_SECONDS 60
#define CPU_MODE_COUNT HYPERSPI_CPU_MODE_COUNT 
#define NI_DEVICE_MAX 8

boolean  Debug = 0,
         DebugSpi = 0;

unsigned long  ActiveCpuCnt,
               AvailCpuCnt,
               EfnTimer,
               EfnSpi,
               SyiMemSize,
               SyiPageSize;

#ifdef __RMIDEF_LOADED
unsigned __int64   TotalCpuTicks,
                   TotalUserModeCpuTicks;
unsigned __int64  TotalModeCpuTicks[CPU_MODE_COUNT];
#else
unsigned int   TotalCpuTicks,
               TotalUserModeCpuTicks;
unsigned long  TotalModeCpuTicks[CPU_MODE_COUNT];
#endif

float  FloatNumberOfCPUs,
       TotalDeltaSeconds;

char  SyiNodeName [16];

unsigned long  CurrentBinTime [2],
               PreviousBinTime [2];
unsigned short  CurrentNumTime [7];

unsigned long  CollectSeconds [2] = { -10000000*COLLECT_INTERVAL_SECONDS, -1 };
float RecordSeconds = (float)(RECORD_INTERVAL_SECONDS-1);
                              
/* this structure declared in HyperSpi.h */
struct  HyperSpiData  SpiRecord;

/* function prototypes */
void AgentPidLogicalAtExit ();
int CollectNetInt();
int CollectSystemDynamic();
int CollectSystemStatic();
void DebugSpiRecord ();


/*****************************************************************************/
/*
*/

int main ()

{
   int  idx, status;
   unsigned short  PrevMinute = 0;
   float  ftmp;

   /*********/
   /* begin */
   /*********/

   if (getenv ("HYPERSPI$DBUG") != NULL) Debug = true;

   fprintf(stdout, "%s\n", SoftwareID);

   AgentPidLogical (true);

   if (VMSnok (status = lib$get_ef (&EfnTimer)))
      exit (status);

   if (VMSnok (status = lib$get_ef (&EfnSpi)))
      exit (status);

   /* set the process priority sufficiently high to have an edge */
   if (VMSnok (status = sys$setpri (0, 0, 6, 0, 0, 0)))
      exit (status);

   /* get system name, physical memory size */
   if (VMSnok (CollectSystemStatic ()))
      exit (status);

   SpiRecord.SystemMemoryMBytes = (SyiMemSize * (SyiPageSize / 512)) / 2048;

   /* initialize the time in the data record, etc */
   sys$gettim (&CurrentBinTime);
   sys$numtim (&CurrentNumTime, &CurrentBinTime);
   SpiRecord.Minute = PrevMinute = CurrentNumTime[4];
   SpiRecord.Hour = CurrentNumTime[3];
   SpiRecord.Day = CurrentNumTime[2];
   SpiRecord.Month = CurrentNumTime[1];
   SpiRecord.Year = CurrentNumTime[0];

   /* initialise */
   CollectSPI ();

   /*****************/
   /* infinite loop */
   /*****************/

   for (;;)
   {
      if (VMSnok (status = sys$setimr (EfnTimer, &CollectSeconds, 0, 0, 0)))
         exit (status);

      if (VMSnok (status = sys$waitfr (EfnTimer)))
         exit (status);      

      /* get an update of the system time */
      PreviousBinTime[0] = CurrentBinTime[0];
      PreviousBinTime[1] = CurrentBinTime[1];
      sys$gettim (&CurrentBinTime);
      sys$numtim (&CurrentNumTime, &CurrentBinTime);

      CollectSystemDynamic ();

      CollectSPI ();

      CollectNetInt ();

      if (Debug) DebugSpiRecord ();

      if (CurrentNumTime[4] != PrevMinute)
      {
         if (Debug)
            fprintf (stdout, "TotalDeltaSeconds: %f\n", TotalDeltaSeconds);

         if (TotalDeltaSeconds >= RecordSeconds)
         {
            if (Debug)
               fprintf (stdout,
#ifdef __RMIDEF_LOADED
"TotalCpuTicks: %Lu TotalUserModeCpuTicks: %Lu\n",
#else
"TotalCpuTicks: %u TotalUserModeCpuTicks: %u\n",
#endif
                        TotalCpuTicks, TotalUserModeCpuTicks);

            ftmp = (float)TotalCpuTicks /
                   TotalDeltaSeconds /
                   FloatNumberOfCPUs;
            SpiRecord.PercentCPU = (unsigned char)ftmp;
            if (ftmp - floor(ftmp) >= 0.5)
               SpiRecord.PercentCPU++;

            ftmp = (float)TotalUserModeCpuTicks /
                   TotalDeltaSeconds /
                   FloatNumberOfCPUs;
            SpiRecord.PercentUserModeCPU = (unsigned char)ftmp;
            if (ftmp - floor(ftmp) >= 0.5)
               SpiRecord.PercentUserModeCPU++;

            WriteSpiRecord ();
         }

         TotalCpuTicks = TotalUserModeCpuTicks =
         SpiRecord.PercentCPU = SpiRecord.PeakPercentCPU =
         SpiRecord.PercentUserModeCPU = SpiRecord.PeakPercentUserModeCPU =
         SpiRecord.PageFaults = SpiRecord.PeakPageFaults =
         SpiRecord.HardPageFaults = SpiRecord.PeakHardPageFaults =
         SpiRecord.BufferedIO = SpiRecord.PeakBufferedIO =
         SpiRecord.DirectIO = SpiRecord.PeakDirectIO =
         SpiRecord.MscpIO = SpiRecord.PeakMscpIO = 0;

         SpiRecord.NetIntTx[0] = SpiRecord.NetIntTx[1] =
         SpiRecord.PeakNetIntTx =
         SpiRecord.NetIntRx[0] = SpiRecord.NetIntRx[1] =
         SpiRecord.PeakNetIntRx = 
         SpiRecord.PeakNetIntRxTx = 
         SpiRecord.LckLoc = SpiRecord.LckIn = SpiRecord.LckOut = 0;

	 for (idx = 0; idx < CPU_MODE_COUNT; ++idx)
	    TotalModeCpuTicks[idx] = 0;

	 for (idx = 0;
              idx < sizeof(SpiRecord.Rfu)/sizeof(SpiRecord.Rfu[0]);
              idx++)
	    SpiRecord.Rfu[idx] = 0;

	 for (idx = 0; idx < HYPERSPI_CPU_MODE_COUNT; idx++)
	    SpiRecord.PercentModeCPU[idx] = 0;

         SpiRecord.Minute = PrevMinute = CurrentNumTime[4];
         SpiRecord.Hour = CurrentNumTime[3];
         SpiRecord.Day = CurrentNumTime[2];
         SpiRecord.Month = CurrentNumTime[1];
         SpiRecord.Year = CurrentNumTime[0];

         TotalDeltaSeconds = 0.0;
      }
   }

   /* should never get to here! */
   exit (STS$K_ERROR);
} 

/*****************************************************************************/
/*
Open the per-day data file, write the SPI record into it, close the file.
*/

WriteSpiRecord ()

{
   static unsigned short  PrevDay = 0;
   static int  DataFileNameLength;
   static char  DataFileName [256];
   static struct FAB  DataFileFab;
   static struct RAB  DataFileRab;
   static struct XABPRO  DataFileXabPro;

   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "WriteSpiRecord()\n");

   if (Debug) DebugSpiRecord ();

   if (SpiRecord.Day != PrevDay)
   {
      PrevDay = SpiRecord.Day;

      DataFileNameLength =
         sprintf (DataFileName,
         "%sHYPERSPI_%s_%s_%02.02d%02.02d%02.02d.DAT",
         HYPERSPI_DATA_DIRECTORY, HYPERSPI_DATA_VERSION, SyiNodeName,
         SpiRecord.Day, SpiRecord.Month, SpiRecord.Year % 100);
      if (Debug) fprintf (stdout, "DataFileName |%s|\n", DataFileName);

      DataFileFab = cc$rms_fab;
      DataFileFab.fab$l_fop = FAB$M_CIF | FAB$M_SQO;
      DataFileFab.fab$b_fac = FAB$M_PUT;
      DataFileFab.fab$l_fna = DataFileName;  
      DataFileFab.fab$b_fns = DataFileNameLength;  
      DataFileFab.fab$w_mrs = sizeof(struct HyperSpiData);
      DataFileFab.fab$b_org = FAB$C_SEQ;
      DataFileFab.fab$b_rfm = FAB$C_FIX;
      DataFileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT;
      DataFileFab.fab$l_xab = &DataFileXabPro;

      DataFileRab = cc$rms_rab;
      DataFileRab.rab$l_fab = &DataFileFab;
      DataFileRab.rab$b_mbf = 2;
      DataFileRab.rab$l_rop = RAB$M_EOF;
      DataFileRab.rab$w_rsz = sizeof(struct HyperSpiData);
      DataFileRab.rab$l_rbf = (char*)&SpiRecord;

      /* initialize the extended access block (XAB) as a protection block */
      DataFileXabPro.xab$b_bln = sizeof(DataFileXabPro);
      DataFileXabPro.xab$b_cod = XAB$C_PRO;
      DataFileXabPro.xab$l_nxt = 0;
      DataFileXabPro.xab$w_pro = 0xee00;  /* w:r,g:r,o:rwed,s:rwed */
   }

   if (VMSnok (status = sys$create (&DataFileFab, 0, 0)))
   {
      if (Debug) fprintf (stdout, "sys$create() %%X%08.08X\n", status);
      exit (status);
   }

   if (VMSnok (status = sys$connect (&DataFileRab, 0, 0)))
   {
      if (Debug) fprintf (stdout, "sys$connect() %%X%08.08X\n", status);
      sys$close (&DataFileFab, 0, 0);
      exit (status);
   }

   if (VMSnok (status = sys$put (&DataFileRab, 0, 0)))
   {
      if (Debug) fprintf (stdout, "sys$put() %%X%08.08X\n", status);
      sys$close (&DataFileFab, 0, 0);
      exit (status);
   }

   sys$close (&DataFileFab, 0, 0);
} 

/*****************************************************************************/
/*
Collect System Performance Information.

It utilizes the undocumented EXE$GETSPI interface for Alpha and VAX, the
documented and supported (as from V7.3) $GETRMI interface for Itanium. 
*/ 

#define MODE_INTERRUPT 0
#define MODE_MULTIPROC 1
#define MODE_KERNEL 2
#define MODE_EXECUTIVE 3
#define MODE_SUPERVISOR 4
#define MODE_USER 5
#define MODE_COMPAT 6
#define MODE_NULL 7

int CollectSPI ()

{
   const long cvtf_mode = LIB$K_DELTA_SECONDS_F;

   static unsigned long  ProcessCount,
                         CpuTicks,
                         UserModeCpuTicks,
                         PageFaults,
                         PrevPageFaults = 0,
                         HardPageFaults,
                         PrevHardPageFaults = 0,
                         PageReadIO,
                         PrevPageReadIO = 0,
                         PageWriteIO,
                         PrevPageWriteIO = 0,
                         BufferedIO,
                         PrevBufferedIO = 0,
                         DirectIO,
                         PrevDirectIO = 0,
                         MscpIO,
                         PrevMscpIO = 0,
			 LckLoc[2],
			 PrevLckLoc[2] = {0, 0},
			 LckIn[2],
			 PrevLckIn[2] = {0, 0},
			 LckOut[2],
			 PrevLckOut[2] = {0, 0},
                         Com,
                         ComO;

   /* storage for VMS binary time */
   static unsigned long  DiffBinTime [2];

#ifdef __RMIDEF_LOADED

   static unsigned __int64  CpuExec,
                            CpuIntStk,
                            CpuKernel,
                            CpuMpSynch,
                            CpuSuper,
                            CpuUser,
                            FreeList;

   static unsigned __int64  ModeCpuTicks[HYPERSPI_CPU_MODE_COUNT];

   static unsigned long  MscpData [35];

#else /* __RMIDEF_LOADED */

   static unsigned long  FreeList;

   static unsigned long  MscpData[13],
			 ModeCpuTicks[HYPERSPI_CPU_MODE_COUNT];

#endif /* __RMIDEF_LOADED */

#ifndef __VAX
#   pragma member_alignment __save
#   pragma nomember_alignment
#endif

   /* structure for a single CPU's mode ticks */
   static struct SingleCPU {
       unsigned char  CPUid;
       unsigned long  Count[8];
   };

   /* a structure all CPU's mode ticks for a system with up to 32 CPUs */
   static struct {
       unsigned long  NumberOfCPUs;
       struct SingleCPU  CPU[32];
   } ModeTicks;

#ifdef __RMIDEF_LOADED

   /* initialize structure for RMI items */
   static struct {
      short  BufferLength;
      short  ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   } ItemList[] =
   {
       /* for 7.3-2(?) and later which has the RMI$_CPU_... items */
       { sizeof(CpuExec), RMI$_CPUEXEC, &CpuExec, 0 },
       { sizeof(CpuIntStk), RMI$_CPUINTSTK, &CpuIntStk, 0 },
       { sizeof(CpuKernel), RMI$_CPUKERNEL, &CpuKernel, 0 },
       { sizeof(CpuMpSynch), RMI$_CPUMPSYNCH, &CpuMpSynch, 0 },
       { sizeof(CpuSuper), RMI$_CPUSUPER, &CpuSuper, 0 },
       { sizeof(CpuUser), RMI$_CPUUSER, &CpuUser, 0 },
       { sizeof(FreeList), RMI$_FRLIST, &FreeList, 0 }, 
       { sizeof(PageFaults), RMI$_FAULTS, &PageFaults, 0 },
       { sizeof(PageReadIO), RMI$_PREADIO, &PageReadIO, 0 },
       { sizeof(PageWriteIO), RMI$_PWRITIO, &PageWriteIO, 0 },
       { sizeof(BufferedIO), RMI$_BUFIO, &BufferedIO, 0 },
       { sizeof(DirectIO), RMI$_DIRIO, &DirectIO, 0 },
       { sizeof(Com), RMI$_COM, &Com, 0 },
       { sizeof(ComO), RMI$_COMO, &ComO, 0 },
       { sizeof(ProcessCount), RMI$_PROCS, &ProcessCount, 0 },
       { sizeof(MscpData), RMI$_MSCP_EVERYTHING, &MscpData, 0 },
       { sizeof(LckLoc[0]), RMI$_ENQNEWLOC, &(LckLoc[0]), 0 },
       { sizeof(LckIn[0]), RMI$_ENQNEWIN, &(LckIn[0]), 0 },
       { sizeof(LckOut[0]), RMI$_ENQNEWOUT, &(LckOut[0]), 0 },
       { sizeof(LckLoc[1]), RMI$_ENQCVTLOC, &(LckLoc[1]), 0 },
       { sizeof(LckIn[1]), RMI$_ENQCVTIN, &(LckIn[1]), 0 },
       { sizeof(LckOut[1]), RMI$_ENQCVTOUT, &(LckOut[1]), 0 },
       {0,0,0,0}
   };

#else /* __RMIDEF_LOADED */

   /* initialize structure for SPI items */
   static struct {
      short  BufferLength;
      short  ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   } ItemList[] =
   {
       { sizeof(ModeTicks), SPI$_MODES, &ModeTicks, 0 },
       { sizeof(unsigned long), SPI$_FRLIST, &FreeList, 0 },
       { sizeof(unsigned long), SPI$_FAULTS, &PageFaults, 0 },
       { sizeof(unsigned long), SPI$_PREADIO, &PageReadIO, 0 },
       { sizeof(unsigned long), SPI$_PWRITIO, &PageWriteIO, 0 },
       { sizeof(unsigned long), SPI$_BUFIO, &BufferedIO, 0 },
       { sizeof(unsigned long), SPI$_DIRIO, &DirectIO, 0 },
       { sizeof(unsigned long), SPI$_COM, &Com, 0 },
       { sizeof(unsigned long), SPI$_COMO, &ComO, 0 },
       { sizeof(unsigned long), SPI$_PROCS, &ProcessCount, 0 },
       { sizeof(MscpData), SPI$_MSCP_ALL, &MscpData, 0 },
       { sizeof(unsigned long), SPI$_ENQNEWLOC, &(LckLoc[0]), 0 },
       { sizeof(unsigned long), SPI$_ENQNEWIN, &(LckIn[0]), 0 },
       { sizeof(unsigned long), SPI$_ENQNEWOUT, &(LckOut[0]), 0 },
       { sizeof(unsigned long), SPI$_ENQCVTLOC, &(LckLoc[1]), 0 },
       { sizeof(unsigned long), SPI$_ENQCVTIN, &(LckIn[1]), 0 },
       { sizeof(unsigned long), SPI$_ENQCVTOUT, &(LckOut[1]), 0 },
       {0,0,0,0}
   };

#endif /* __RMIDEF_LOADED */

#ifndef __VAX
#   pragma member_alignment __restore
#endif

#ifdef __RMIDEF_LOADED
   static unsigned __int64  PrevModes[8];
#else
   static unsigned long  PrevModes[8];
#endif

   int  status;
#ifdef __RMIDEF_LOADED
   unsigned __int64  tmp;
   unsigned __int64  CurrentModes[8];
#else
   unsigned long  tmp;
   unsigned long CurrentModes[8];
#endif
   unsigned long  cidx, midx;
   float  DeltaSeconds;
   IOSBLK  IOsb;

   /*********/
   /* begin */
   /*********/

   /* collect System Performance Information */

#ifdef __RMIDEF_LOADED

   status = sys$getrmi (
            EfnSpi,  /* efn */
            0,  /* csiaddr */
            0,  /* nodename */
            &ItemList,  /* item list */
            &IOsb,  /* iosb */
            0,      /* astaddr */
            0 );    /* astprm */

   if (Debug)
      fprintf (stdout, "sys$getrmi() %%X%08.08X  IOsb: %%X%08.08X\n",
               status, IOsb.iosb$w_status);

#else /* __RMIDEF_LOADED */

   status = exe$getspi (
            EfnSpi,  /* efn */
            0,  /* csiaddr */
            0,  /* nodename */
            &ItemList,  /* item list */
            &IOsb,  /* iosb */
            0,  /* astaddr */
            0 );  /* astprm */

   if (Debug)
      fprintf (stdout, "exe$getspi() %%X%08.08X  IOsb: %%X%08.08X\n",
               status, IOsb.iosb$w_status);

#endif /* __RMIDEF_LOADED */

   if (VMSnok (status)) return (status);

   if (VMSnok (status = sys$waitfr (EfnSpi))) exit (status);      

   /**************************/
   /* calculate elapsed time */
   /**************************/

   lib$sub_times (&CurrentBinTime, &PreviousBinTime, &DiffBinTime);

#ifdef __RMIDEF_LOADED
   status = lib$cvts_from_internal_time (&cvtf_mode, &DeltaSeconds,
                                         &DiffBinTime);
#else
   status = lib$cvtf_from_internal_time (&cvtf_mode, &DeltaSeconds,
                                         &DiffBinTime);
#endif

   TotalDeltaSeconds += DeltaSeconds;
   if (Debug)
      fprintf (stdout, "DeltaSeconds: %f TotalDeltaSeconds: %f\n",
               DeltaSeconds, TotalDeltaSeconds);

   /**********************/
   /* calculate CPU time */
   /**********************/

#ifdef __RMIDEF_LOADED

   CurrentModes[MODE_INTERRUPT] = CpuIntStk;
   CurrentModes[MODE_EXECUTIVE] = CpuExec;
   CurrentModes[MODE_KERNEL] = CpuKernel;
   CurrentModes[MODE_MULTIPROC] = CpuMpSynch;
   CurrentModes[MODE_USER] = CpuUser;
   CurrentModes[MODE_SUPERVISOR] = CpuSuper;
   CurrentModes[MODE_COMPAT] = CurrentModes[MODE_NULL] = 0;

#else /* __RMIDEF_LOADED */

   if (Debug)
   {
      for (cidx = 0; cidx < ModeTicks.NumberOfCPUs; cidx++)
         for (midx = MODE_INTERRUPT; midx <= MODE_NULL; midx++)
            fprintf (stdout, "[%d][%d] = %u\n",
                     cidx, midx, ModeTicks.CPU[cidx].Count[midx]);
   }

   /* accumulate mode counters for all CPUs into CPU 0's area */
   for (cidx = 1; cidx < ModeTicks.NumberOfCPUs; cidx++)
       for (midx = MODE_INTERRUPT; midx <= MODE_NULL; midx++)
           ModeTicks.CPU[0].Count[midx] += ModeTicks.CPU[cidx].Count[midx];

   /* current raw CPU ticks */
   CurrentModes[MODE_INTERRUPT] = ModeTicks.CPU[0].Count[MODE_INTERRUPT] -
                                  ModeTicks.CPU[0].Count[MODE_NULL];
   CurrentModes[MODE_MULTIPROC] = ModeTicks.CPU[0].Count[MODE_MULTIPROC];
   CurrentModes[MODE_KERNEL] = ModeTicks.CPU[0].Count[MODE_KERNEL];
   CurrentModes[MODE_EXECUTIVE] = ModeTicks.CPU[0].Count[MODE_EXECUTIVE];
   CurrentModes[MODE_SUPERVISOR] = ModeTicks.CPU[0].Count[MODE_SUPERVISOR];
   CurrentModes[MODE_USER] = ModeTicks.CPU[0].Count[MODE_USER];
   CurrentModes[MODE_COMPAT] = ModeTicks.CPU[0].Count[MODE_COMPAT];
   CurrentModes[MODE_NULL] = ModeTicks.CPU[0].Count[MODE_NULL];

#endif /* __RMIDEF_LOADED */

   /* calculate the number of CPU ticks for this period */
   CpuTicks = 0;
   for (midx = MODE_INTERRUPT; midx <= MODE_COMPAT; midx++)
   {
       tmp = CurrentModes[midx];
#ifndef __VAX
       /*
          It has been OBSERVED that in this application Interrupt mode
          counters can actually go backwards (only ever observed by 1).
          Don't know if this is an artifact of the code, or what?
          This kludge doesn't seem to cause any problems, but will look
          for a better solution/explanation when time permits.
       */
       if (CurrentModes[midx] >= PrevModes[midx])
          CurrentModes[midx] = tmp - PrevModes[midx];
       else
          CurrentModes[midx] = 0;
#else
       CurrentModes[midx] = tmp - PrevModes[midx];
#endif /* __VAX */
       PrevModes[midx] = tmp;
       /* keep track of the total ticks */
       CpuTicks += CurrentModes[midx];
   }

   UserModeCpuTicks = CurrentModes[MODE_USER] + CurrentModes[MODE_COMPAT];
   ModeCpuTicks[HYPERSPI_MODE_USER] = UserModeCpuTicks;
   ModeCpuTicks[HYPERSPI_MODE_INTERRUPT] = CurrentModes[MODE_INTERRUPT];
   ModeCpuTicks[HYPERSPI_MODE_MULTIPROC] = CurrentModes[MODE_MULTIPROC];
   ModeCpuTicks[HYPERSPI_MODE_KERNEL] = CurrentModes[MODE_KERNEL];
   ModeCpuTicks[HYPERSPI_MODE_EXECUTIVE] = CurrentModes[MODE_EXECUTIVE];
   ModeCpuTicks[HYPERSPI_MODE_SUPERVISOR] = CurrentModes[MODE_SUPERVISOR];

   if (Debug)
   {
      fprintf (stdout,
#ifdef __RMIDEF_LOADED
"ticks CPU\nI: %Lu\nMP: %Lu\nK: %Lu\nE: %Lu\nS: %Lu\nU: %Lu\nC: %Lu\n?: %Lu\n",
#else
"ticks CPU\nI: %u\nMP: %u\nK: %u\nE: %u\nS: %u\nU: %u\nC: %u\n?: %u\n",
#endif
      CurrentModes[0], CurrentModes[1], CurrentModes[2], CurrentModes[3],
      CurrentModes[4], CurrentModes[5], CurrentModes[6], CurrentModes[7]);
   }

   /*******************/
   /* calculate other */
   /*******************/

   if (Debug)
   {
       fprintf (stdout,
"CPU ticks %u\n\
CPU user mode ticks %u\n\
Free list: %u\n\
Page faults: %u  %u hard\n\
Com: %u\n\
ComO: %u\n\
Buffered I/O: %u\n\
Direct I/O: %u\n\
Processes: %u\n\
MSCP: %u %u %u %u %u %u %u %u %u %u %u %u %u\n\
Locks: %u %u %u %u %u %u\n",
       CpuTicks, UserModeCpuTicks,
       (unsigned long)FreeList, PageFaults, PageReadIO+PageWriteIO,
       Com, ComO,
       BufferedIO, DirectIO, ProcessCount,
       MscpData[0], MscpData[1], MscpData[2], MscpData[3], MscpData[4],
       MscpData[5], MscpData[6], MscpData[7], MscpData[8], MscpData[9],
       MscpData[10], MscpData[11], MscpData[12], 
       LckLoc[0], LckIn[0], LckOut[0], LckLoc[1], LckIn[1], LckOut[1]);
   }

   /***********************/
   /* update the counters */
   /***********************/

   FloatNumberOfCPUs = (float)(SpiRecord.NumberOfCPUs = ActiveCpuCnt);

   SpiRecord.SystemMemoryPercentInUse = 
      ((SyiMemSize - FreeList) * 100) / SyiMemSize;

   SpiRecord.NumberOfProcesses = ProcessCount;

   /* 'PrevPageFaults' will only be zero first time function is called */
   if (PrevPageFaults)
   {
      int i = 0;
      TotalCpuTicks += CpuTicks;
      TotalUserModeCpuTicks += UserModeCpuTicks;
      for (i = 0; i < CPU_MODE_COUNT; ++i)
         TotalModeCpuTicks[i] += ModeCpuTicks[i];

      if (DeltaSeconds < 1.0) DeltaSeconds = 1.0;
      tmp = (int)((float)CpuTicks / DeltaSeconds / FloatNumberOfCPUs);
      if (tmp > SpiRecord.PeakPercentCPU)
         SpiRecord.PeakPercentCPU = tmp;
      tmp = (int)((float)UserModeCpuTicks / DeltaSeconds / FloatNumberOfCPUs);
      if (tmp > SpiRecord.PeakPercentUserModeCPU)
         SpiRecord.PeakPercentUserModeCPU = tmp;
   }

   if (PageFaults > PrevPageFaults)
   {
      if (PrevPageFaults)
      {
         SpiRecord.PageFaults += (tmp = PageFaults - PrevPageFaults);
         if (tmp > SpiRecord.PeakPageFaults) SpiRecord.PeakPageFaults = tmp;
         SpiRecord.HardPageFaults += 
            (tmp = PageReadIO - PrevPageReadIO + PageWriteIO - PrevPageWriteIO);
         if (tmp > SpiRecord.PeakHardPageFaults)
             SpiRecord.PeakHardPageFaults = tmp;
      }
      PrevPageReadIO = PageReadIO;
      PrevPageWriteIO = PageWriteIO;
   }
   /* Page faults counter can overflow */
   PrevPageFaults = PageFaults;

   SpiRecord.Computable = Com + ComO;

   if (BufferedIO > PrevBufferedIO)
   {
      if (PrevBufferedIO)
      {
         SpiRecord.BufferedIO += (tmp = BufferedIO - PrevBufferedIO);
         if (tmp > SpiRecord.PeakBufferedIO) SpiRecord.PeakBufferedIO = tmp;
      }
      PrevBufferedIO = BufferedIO;
   }

   if (DirectIO > PrevDirectIO)
   {
      if (PrevDirectIO)
      {
         SpiRecord.DirectIO += (tmp = DirectIO - PrevDirectIO);
         if (tmp > SpiRecord.PeakDirectIO) SpiRecord.PeakDirectIO = tmp;
      }
      PrevDirectIO = DirectIO;
   }

#ifdef __RMIDEF_LOADED

   MscpIO = MscpData[23] + MscpData[24];

#else /* __RMIDEF_LOADED */

   MscpIO = MscpData[0];

#endif /* __RMIDEF_LOADED */

   if (MscpIO > PrevMscpIO)
   {
      if (PrevMscpIO)
      {
         SpiRecord.MscpIO += (tmp = MscpIO - PrevMscpIO);
         if (tmp > SpiRecord.PeakMscpIO) SpiRecord.PeakMscpIO = tmp;
      }
      PrevMscpIO = MscpIO;
   }

   /* lock counters can overflow */

   if (LckLoc[0] > PrevLckLoc[0]) {
      SpiRecord.LckLoc += LckLoc[0] - PrevLckLoc[0];
   }
   PrevLckLoc[0] = LckLoc[0];

   if (LckLoc[1] > PrevLckLoc[1]) {
      SpiRecord.LckLoc += LckLoc[1] - PrevLckLoc[1];
   }
   PrevLckLoc[1] = LckLoc[1];

   if (LckIn[0] > PrevLckIn[0]) {
      SpiRecord.LckIn += LckIn[0] - PrevLckIn[0];
   }
   PrevLckIn[0] = LckIn[0];

   if (LckIn[1] > PrevLckIn[1]) {
      SpiRecord.LckIn += LckIn[1] - PrevLckIn[1];
   }
   PrevLckIn[1] = LckIn[1];

   if (LckOut[0] > PrevLckOut[0]) {
      SpiRecord.LckOut += LckOut[0] - PrevLckOut[0];
   }
   PrevLckOut[0] = LckOut[0];

   if (LckOut[1] > PrevLckOut[1]) {
      SpiRecord.LckOut += LckOut[1] - PrevLckOut[1];
   }
   PrevLckOut[1] = LckOut[1];

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Get the size and amount of free space in the installed page file(s).
*/ 

int CollectSystemDynamic ()

{
   static unsigned long  PageFileSize,
                         PageFileFree;
   static struct {
      short BufferLength;
      short ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   } SyiItems [] =
   {
      { sizeof(ActiveCpuCnt), SYI$_ACTIVECPU_CNT, &ActiveCpuCnt, 0 },
      { sizeof(PageFileSize), SYI$_PAGEFILE_PAGE, &PageFileSize, 0 },
      { sizeof(PageFileFree), SYI$_PAGEFILE_FREE, &PageFileFree, 0 },
      {0,0,0,0},
   };

   int  status;
   struct {
      unsigned long  Status;
      unsigned long  Reserved;
   } IOsb;

   /*********/
   /* begin */
   /*********/

   status = sys$getsyiw (0, 0, 0, &SyiItems, &IOsb, 0, 0);

   if (Debug)
      fprintf (stdout, "sys$getsyiw() %%X%08.08X IOsb: %%X%08.08X\n",
               status, IOsb.Status);

   if (VMSnok (status)) return (status);
   if (VMSnok (IOsb.Status)) return (IOsb.Status);

   SpiRecord.PageSpaceMBytes = ((float)PageFileSize * SyiPageSize) / 1048576;
   SpiRecord.PageSpacePercentInUse =
      100 - (((float)PageFileFree * 100) / (float)PageFileSize);
   if (Debug)
      fprintf (stdout,
"AvailCpuCnt: %d ActiveCpuCnt: %d \
PageSpaceMBytes: %d PageSpacePercentInUse: %d\n",
              AvailCpuCnt, ActiveCpuCnt,
              SpiRecord.PageSpaceMBytes, SpiRecord.PageSpacePercentInUse);
                                                               
   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Get node name, amount of physical memory, and memory page size.
*/

/* these are currently not in header files for VAX C */
/* discovered empirically on an AXP system using DEC C */
#define SYI$_PAGE_SIZE 4452
#define SYI$_MEMSIZE 4459

int CollectSystemStatic ()

{
   int  status;
   unsigned short  SyiNodeNameLength;
   struct {
      short BufferLength;
      short ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   }
   SyiItems [] =
   {
      { 15, SYI$_NODENAME, &SyiNodeName, &SyiNodeNameLength },
      { 4, SYI$_MEMSIZE, &SyiMemSize, 0 },
      { 4, SYI$_PAGE_SIZE, &SyiPageSize, 0 },
      { sizeof(AvailCpuCnt), SYI$_AVAILCPU_CNT, &AvailCpuCnt, 0 },
      { 0,0,0,0 }
   };
   struct {
      unsigned long  Status;
      unsigned long  Reserved;
   } IOsb;

   /*********/
   /* begin */
   /*********/

   status = sys$getsyiw (0, 0, 0, &SyiItems, &IOsb, 0, 0);

   if (Debug)
      fprintf (stdout, "sys$getsyiw() %%X%08.08X IOsb: %%X%08.08X\n",
               status, IOsb.Status);

   if (VMSok (IOsb.Status))
      SyiNodeName[SyiNodeNameLength] = '\0';
   else
      SyiNodeName[0] = '\0';

   if (VMSnok (IOsb.Status)) return (IOsb.Status);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
This function uses undocumented sense-mode functionality to load counters from
the network interface (commonly ethernet device).  Multiple devices are
supported using a multi-valued logical name HYPERSPI$AGENT_NI and the
frame-rate and byte-rate and soft/hard error-rate statistics are accumlated to
represent total system data.

On Alpha and Itanium uses native 64 bit (quadword) accumulators.

Essential concepts have come from "EMON: moniteur ethernet (V2.1-2) Gerard
Guillaume" and found in DECUSLIB freeware, and from some peeks at the T4 source
code.  The implementation is mine.

Adapted from the MONDESI.C development tree!
*/

int CollectNetInt ()

{
   static int  NetIntCount;
   static unsigned short  NetIntChan [NI_DEVICE_MAX];
   static unsigned long  DcSCOM = 32,  /* DC$_SCOM */
                         DtNI = 13;    /* DT$_NI */

#ifndef __VAX
   static unsigned __int64  NetIntBlocksRx,
                            NetIntBlocksTx,
                            NetIntBytesRx,
                            NetIntBytesTx,
                            PrevBlocksRx,
                            PrevBlocksTx,
                            PrevBytesRx,
                            PrevBytesTx,
                            PrevErrorsHard,
                            PrevErrorsSoft;
#else
   static unsigned int   NetIntBlocksRx,
                         NetIntBlocksTx,
                         NetIntBytesRx,
                         NetIntBytesTx,
                         PrevBlocksRx,
                         PrevBlocksTx,
                         PrevBytesRx,
                         PrevBytesTx,
                         PrevErrorsHard,
                         PrevErrorsSoft;
#endif

   static long  DeviceCount = -1,
                DviUnit,
                LnmIndex = -1,
                LnmAttributes;
   static unsigned short  LogValueLength;
   static char  LogValue [32],
                NetIntDev [NI_DEVICE_MAX*24];
   static struct {
      short  BufferLength;
      short  ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   }
   LnmItems [] =
   {
      { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 },
      { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 },
      { sizeof(LogValue)-1, LNM$_STRING, &LogValue, &LogValueLength },
      { 0,0,0,0 }
   },
   DevScanItems [] =
   {
      { sizeof(DcSCOM), DVS$_DEVCLASS, &DcSCOM, 0 },
      { 0,0,0,0 }
   },
   GetDviItems [] =
   {
      { sizeof(DviUnit), DVI$_UNIT, &DviUnit, 0 },
      { 0,0,0,0 }
   };
   static $DESCRIPTOR (NetIntDsc, "");
   static $DESCRIPTOR (LogNameDsc, "HYPERSPI$AGENT_NI");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");

   int  ecnt, status;
   unsigned short  pword,
                   retlen;
   unsigned long  LnmCount,
                  NetIntErrorsHard,
                  NetIntErrorsSoft;
   unsigned long  DevScanContext [2];
   float  tfloat;
   char  *vptr;
   char  DevScanBuffer [64],
         CountBuffer [512];
   struct STRUCT_IOSBLK  IOsb;
   $DESCRIPTOR (CountDsc, CountBuffer);
   $DESCRIPTOR (DevScanDsc, DevScanBuffer);

#ifndef __VAX
#define CASTIT (unsigned __int64)
   unsigned __int64  value,
                     BytesRx,
                     BytesTx;
#else
#define CASTIT (unsigned long)
   unsigned long  value,
                  BytesRx,
                  BytesTx;
   unsigned long  QuadWord [2] = { 0, 0 };
#endif

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CollectNetInt() %s\n", NetIntDev);

   if (LnmIndex < 0)
   {
      /* first call, check for logical name defined network devices */
      for (LnmIndex = 0; LnmIndex < NI_DEVICE_MAX; LnmIndex++)
      {
         status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems);
         if (VMSnok (status) || !(LnmAttributes & LNM$M_EXISTS)) break;
         LogValue[LogValueLength] = '\0';

         NetIntDsc.dsc$a_pointer = LogValue;
         NetIntDsc.dsc$w_length = LogValueLength;

         status = sys$assign (&NetIntDsc, &NetIntChan[LnmIndex], 0, 0);
         if (VMSnok (status))
         {
            fprintf (stdout, "[%d] %s %%X%08.08X\n",
                     __LINE__, LogValue, status);
            NetIntCount = 0;
            break;
         }
         if (NetIntDev[0]) strcat (NetIntDev, ",");
         strcat (NetIntDev, LogValue);
         NetIntCount++;
      }
   }

   if (DeviceCount < 0)
   {
      /* first call */
      DeviceCount = 0;

      /* if logical name defined and found devices */
      if (NetIntCount) return (NetIntCount);

      /* scan system for network devices */
      DevScanContext[0] = DevScanContext[1] = 0;
      while (NetIntCount < NI_DEVICE_MAX)
      {
         status = sys$device_scan (&DevScanDsc, &retlen, 0,
                                   &DevScanItems, &DevScanContext);
         if (VMSnok (status))
         {
            if (status == SS$_NOMOREDEV || status == SS$_NOSUCHDEV) break;
            fprintf (stdout, "[%d] %%X%08.08X\n", __LINE__, status);
            NetIntCount = 0;
            break;
         }

         status = sys$getdviw (0, 0, &DevScanDsc, &GetDviItems, &IOsb, 
                               0, 0, 0, 0);
         if (VMSok (status)) status = IOsb.iosb$w_status;
         if (VMSnok (status))
         {
            fprintf (stdout, "[%d] %%X%08.08X\n", __LINE__, status);
            NetIntCount = 0;
            break;
         }
         /* if not a template device then continue */
         if (DviUnit != 0) continue;

         status = sys$assign (&DevScanDsc, &NetIntChan[DeviceCount++], 0, 0);
         if (VMSnok (status))
         {
            if (status == SS$_NOPRIV) continue;
            fprintf (stdout, "[%d] %s %%X%08.08X\n",
                     __LINE__, LogValue, status);
            NetIntCount = 0;
            break;
         }

         if (NetIntDev[0]) strcat (NetIntDev, ",");
         DevScanBuffer[retlen] = '\0';
         strcat (NetIntDev, DevScanBuffer+1);
         NetIntCount++;
      }

      /* first call, just return having assigned channels (perhaps) */
      return (NetIntCount);
   }

   /****************/
   /* collect data */
   /****************/

   NetIntBlocksRx = NetIntBlocksTx =
      NetIntBytesRx = NetIntBytesTx =
      NetIntErrorsHard = NetIntErrorsSoft = 0;

   /* if no devices then just return from here */
   if (!NetIntCount) return (NetIntCount);

   /* accumulate counts from each device */
   for (ecnt = 0; ecnt < NetIntCount; ecnt++)
   {
      /* if the channel has been deassigned */
      if (!NetIntChan[ecnt]) continue;

      memset (CountBuffer, 0, sizeof(CountBuffer));

      status = sys$qiow (0, NetIntChan[ecnt],
#ifndef __VAX
#define IO$M_RD_64COUNT 0x4000
                         IO$_SENSEMODE | IO$M_RD_COUNT |
                         IO$M_RD_64COUNT | IO$M_CTRL,
#else
                         IO$_SENSEMODE | IO$M_RD_COUNT | IO$M_CTRL,
#endif
                         &IOsb, 0, 0,
                         0, &CountDsc, 0, 0, 0, 0);

      if (VMSok (status)) status = IOsb.iosb$w_status;
      if (VMSnok (status))
      {
         /* if the interface give problems just disable reporting */
         static  int  ReportCount = 0;
         if (ReportCount++ < 64)
            fprintf (stdout, "[%d] %%X%08.08X  \"%s\"%d\n",
                     __LINE__, status, NetIntDev, ecnt);
         else
         {
            sys$dassgn (NetIntChan[ecnt]);
            NetIntChan[ecnt] = 0;
            ReportCount = 0;
         }
         continue;
      }

      vptr = CountBuffer;
      while (vptr < CountBuffer + IOsb.iosb$w_bcnt)
      {
         pword = *(unsigned short*)vptr;
         vptr += 2;

         /* end of list */
         if (!pword) break;

         /* MSB should be set! */
         if (!(pword & 0x8000))
         {
            fprintf (stdout, "[%d] %04.04X\n", __LINE__, pword);
            break;
         }

         value = 0;
#ifndef __VAX
         if ((pword & 0x0fff) < 200)
         {
            /* quadword value (item number less than 200) */
            value = *(unsigned __int64*)vptr;
            vptr += 8;
         }
         else
#endif
         if ((pword & 0x6000) == 0x6000)
         {
            /* longword value */
            value = *(unsigned long*)vptr;
            vptr += 4;
         } 
         else
         if ((pword & 0x6000) == 0x4000)
         {
            /* word value */
            value = *(unsigned short*)vptr;
            vptr += 2;
         } 
         else
         if ((pword & 0x6000) == 0x2000)
         {
            /* byte value */
            value = *(unsigned char*)vptr;
            vptr++;
         } 

         if (pword & 0x1000)
         {
            /* hw map (?) */
            value = *(unsigned long*)vptr;
            vptr += 2;
            continue;
         } 

         switch (pword & 0x0fff)
         {
#ifndef __VAX
            /* these look suspiciously like "LANCP SHOW DEVICE /COUNT"! */

            case 1 :
                 /* seconds since last zeroed */
                 break;

            case 2 :
            case 6 :
                 NetIntBytesRx += value;
                 break;

            case 3 :
            case 7 :
                 NetIntBytesTx += value;
                 break;

            case 4 :
            case 8 :
                 NetIntBlocksRx += value;
                 break;

            case 5 :
            case 9 :
                 NetIntBlocksTx += value;
                 break;

            case 12 :
            case 13 :
            case 14 :
            case 15 :
            case 16 :
            case 17 :
            case 18 :
            case 19 :
            case 30 :
            case 21 :
            case 22 :
            case 23 :
            case 24 :
            case 27 :
            case 28 :
            case 29 :
                 NetIntErrorsSoft += value;
                 break;

            case 25 :
            case 26 :
            case 33 :
            case 34 :
                 NetIntErrorsHard += value;
                 break;
#endif

            /* more "traditional" counters */

            case 1000 /* NMA$C_CTLIN_BRC */ :
            case 1002 /* NMA$C_CTLIN_MBY */ :
                 NetIntBytesRx += value;
                 break;

            case 1001 /* NMA$C_CTLIN_BSN */ :
                 NetIntBytesTx += value;
                 break;

            case 1010 /* NMA$C_CTLIN_DBR */ :
            case 1012 /* NMA$C_CTLIN_MBL */ :
                 NetIntBlocksRx += value;
                 break;

            case 1011 /* NMA$C_CTLIN_DBS */ :
                 NetIntBlocksTx += value;
                 break;

            case 1013 /* NMA$C_CTLIN_BID */ :
            case 1014 /* NMA$C_CTLIN_BS1 */ :
            case 1015 /* NMA$C_CTLIN_BSM */ :
            case 1040 /* NMA$C_CTLIN_RBE */ :
            case 1041 /* NMA$C_CTLIN_LBE */ :
            case 1064 /* NMA$C_CTLIN_OVR */ :
            case 1066 /* NMA$C_CTLIN_UBU */ :

                 NetIntErrorsSoft += value;
                 break;

            case 1060 /* NMA$C_CTLIN_SFL */ :
            case 1061 /* NMA$C_CTLIN_CDC */ :
            case 1063 /* NMA$C_CTLIN_UFD */ :
            case 1062 /* NMA$C_CTLIN_RFL */ :
            case 1065 /* NMA$C_CTLIN_SBU */ :

                 NetIntErrorsHard += value;
                 break;

            default :
         }
      }
   }

   if (!PrevBytesRx) PrevBytesRx = NetIntBytesRx;
   BytesRx = NetIntBytesRx - PrevBytesRx;
   PrevBytesRx = NetIntBytesRx;

   if (!PrevBytesTx) PrevBytesTx = NetIntBytesTx;
   BytesTx = NetIntBytesTx - PrevBytesTx;
   PrevBytesTx = NetIntBytesTx;

   PrevErrorsSoft = NetIntErrorsSoft;
   PrevErrorsHard = NetIntErrorsHard;

#ifndef __VAX

   *(unsigned __int64*)SpiRecord.NetIntRx =
     *(unsigned __int64*)SpiRecord.NetIntRx + BytesRx;
   if ((unsigned long)BytesRx > SpiRecord.PeakNetIntRx)
      SpiRecord.PeakNetIntRx = (unsigned long)BytesRx;

   *(unsigned __int64*)SpiRecord.NetIntTx =
     *(unsigned __int64*)SpiRecord.NetIntTx + BytesTx;
   if ((unsigned long)BytesTx > SpiRecord.PeakNetIntTx)
      SpiRecord.PeakNetIntTx = (unsigned long)BytesTx;

   if ((unsigned long)(BytesRx + BytesTx) > SpiRecord.PeakNetIntRxTx)
      SpiRecord.PeakNetIntRxTx = (unsigned long)(BytesRx + BytesTx);

#else

   QuadWord[0] = BytesRx;
   status = lib$addx (&QuadWord, &SpiRecord.NetIntRx, &SpiRecord.NetIntRx, 0);
   if (VMSnok(status)) exit (status);
   if (BytesRx > SpiRecord.PeakNetIntRx) SpiRecord.PeakNetIntRx = BytesRx;

   QuadWord[0] = BytesTx;
   status = lib$addx (&QuadWord, &SpiRecord.NetIntTx, &SpiRecord.NetIntTx, 0);
   if (VMSnok(status)) exit (status);
   if (BytesTx > SpiRecord.PeakNetIntTx) SpiRecord.PeakNetIntTx = BytesTx;

   if (BytesRx + BytesTx > SpiRecord.PeakNetIntRxTx)
      SpiRecord.PeakNetIntRxTx = BytesRx + BytesTx;

#endif

   return (NetIntCount);
}

/*****************************************************************************/
/*
*/

void DebugSpiRecord ()

{
   int idx;

   /*********/
   /* begin */
   /*********/

   fprintf (stdout,
"%02.02d:%02.02d  CPU %d %d %d, %d %d  MEM %dMb %d%%  PAG %dMb %d%%\
       PRC %d  FLT %d %d, %d %d  IO %d %d %d, %d %d %d COM %d MODE",
   SpiRecord.Hour,
   SpiRecord.Minute,
   SpiRecord.NumberOfCPUs,
   SpiRecord.PercentCPU,
   SpiRecord.PercentUserModeCPU,
   SpiRecord.PeakPercentCPU,
   SpiRecord.PeakPercentUserModeCPU,
   SpiRecord.SystemMemoryMBytes,
   SpiRecord.SystemMemoryPercentInUse,
   SpiRecord.PageSpaceMBytes,
   SpiRecord.PageSpacePercentInUse,
   SpiRecord.NumberOfProcesses,
   SpiRecord.PageFaults,
   SpiRecord.HardPageFaults,
   SpiRecord.PeakPageFaults,
   SpiRecord.PeakHardPageFaults,
   SpiRecord.BufferedIO,
   SpiRecord.DirectIO,
   SpiRecord.MscpIO,
   SpiRecord.PeakBufferedIO,
   SpiRecord.PeakDirectIO,
   SpiRecord.PeakMscpIO,
   SpiRecord.Computable);

   for (idx = 0; idx < HYPERSPI_CPU_MODE_COUNT; ++idx)
      fprintf (stdout, " %d", SpiRecord.PercentModeCPU[idx]);

   fprintf (stdout, "\n");

#ifndef __VAX
   fprintf (stdout, "NI %12Lu %12u %12Lu %12u %12u\n",
            *(__int64*)SpiRecord.NetIntRx, SpiRecord.PeakNetIntRx,
            *(__int64*)SpiRecord.NetIntTx, SpiRecord.PeakNetIntTx,
            SpiRecord.PeakNetIntRxTx);
#else
   fprintf (stdout, "NI %12u %12u %12u %12u %12u\n",
            SpiRecord.NetIntRx, SpiRecord.PeakNetIntRx,
            SpiRecord.NetIntTx, SpiRecord.PeakNetIntTx,
            SpiRecord.PeakNetIntRxTx);
#endif
} 

/*****************************************************************************/
/*
Create a logical name HYPERSPI_AGENT_PID containing the process PID (binary).
*/

AgentPidLogical (boolean CreateLogical)

{
   static unsigned long  PslUser = PSL$C_USER;
   static $DESCRIPTOR (LnmSystemDsc, "LNM$SYSTEM");
   static $DESCRIPTOR (PidLogNameDsc, "HYPERSPI_AGENT_PID");

   static unsigned long  Pid;
   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      void   *buf_addr;
      unsigned short  *short_ret_len;
   }
   JpiItems [] =
   {
     { sizeof(Pid), JPI$_PID, &Pid, 0 },
     { 0,0,0,0 }
   },
   LnmPidItem [] =
   {
      { sizeof(Pid), LNM$_STRING, &Pid, 0 },
      { 0,0,0,0 }
   };

   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "AgentPidLogical()\n");

   if (CreateLogical)
   {
      atexit (&AgentPidLogicalAtExit);
      status = sys$getjpiw (0, 0, 0, &JpiItems, 0, 0, 0);
      if (VMSok (status))
         status = sys$crelnm (0, &LnmSystemDsc, &PidLogNameDsc, &PslUser,
                              &LnmPidItem);
      if (VMSnok (status)) exit (status);
   }
   else
      sys$dellnm (&LnmSystemDsc, &PidLogNameDsc, &PslUser);
}

/*****************************************************************************/
/*
Executed during image exit (in particular $FORCEX).
*/

void AgentPidLogicalAtExit ()

{
   if (Debug) fprintf (stdout, "AgentPidLogicalAtExit()\n");

   AgentPidLogical (false);
}

/****************************************************************************/
/*
Does a case-insensitive character-by-character string compare and returns true 
if two strings are the same, or false if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 

boolean strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (false);
      if (count > 0) 
         if (--count == 0) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}

/****************************************************************************/

