/*****************************************************************************/
/*
                             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 2 seconds).  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.  Interface details 
plagiarized (and extensively reworked) from a VMS X Window System utility 
named SPI. 


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


COPYRIGHT
---------
Copyright (C) 1996-2003 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 2.


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
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 "1.1.1"
#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

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

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

/* application header file */
#include "HyperSpi.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 CollectIntervalSeconds 1
#define RecordIntervalSeconds 60

boolean  Debug = 0,
         DebugSpi = 0;

unsigned long  SyiMemSize,
               SyiPageSize,
               TotalCpuTicks,
               TotalUserModeCpuTicks;

char  SyiNodeName [16];

float  FloatNumberOfCPUs,
       TotalDeltaSeconds;

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

unsigned long  CollectSeconds [2] = { -10000000 * CollectIntervalSeconds, -1 };
                              
/* this structure declared in HyperSpi.h */
struct  HyperSpiData  SpiRecord;

/* function prototypes */
int SPI_AST ();
int CollectTimer_AST ();
void AgentPidLogicalAtExit ();

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

int main ()

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

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

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

   AgentPidLogical (true);

   /* 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 (CollectSystemInfo ()))
      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];

   /* initialize the System Performance Information */
   CollectSPI ();

   if (VMSnok (status =
       sys$setimr (0, &CollectSeconds, &CollectTimer_AST, 0, 0)))
      exit (status);

   sys$hiber ();

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

   for (;;)
   {
      /* get an update of the system time */
      sys$gettim (&CurrentBinTime);
      sys$numtim (&CurrentNumTime, &CurrentBinTime);

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

         if (TotalDeltaSeconds >= 59.0)
         {
            if (Debug)
               fprintf (stdout, "TotalCpuTicks: %d TotalUserModeCpuTicks: %d\n",
                        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++;

            CollectPageSpace ();

            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.Minute = PrevMinute = CurrentNumTime[4];
         SpiRecord.Hour = CurrentNumTime[3];
         SpiRecord.Day = CurrentNumTime[2];
         SpiRecord.Month = CurrentNumTime[1];
         SpiRecord.Year = CurrentNumTime[0];

         TotalDeltaSeconds = 0.0;
      }

      /* collect System Performance Information */
      CollectSPI ();

      if (Debug) DebugSpiRecord ();

      if (VMSnok (status =
          sys$setimr (0, &CollectSeconds, &CollectTimer_AST, 0, 0)))
         exit (status);

      sys$hiber ();
   }

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

/*****************************************************************************/
/*
Is executed whenever the "collection" timer expires.  Initiate another timer 
and wake the hibernating main().
*/ 

CollectTimer_AST ()

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

   sys$wake (0, 0);
} 

/*****************************************************************************/
/*
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",
         HyperSpiDataDirectory, HyperSpiDataVersion, 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.

Uses undocument system call EXE$GETSPI().  The code in this function is largely 
derived from a VMS system performance DECwindows utility named SPI.
*/ 

#define SPI$_MODES      0x1000
#define SPI$_FRLIST     0x101C
#define SPI$_MOLIST     0x101D
#define SPI$_FAULTS     0x101E
#define SPI$_PREADS     0x101F
#define SPI$_PWRITES    0x1020
#define SPI$_PWRITEIO   0x1021
#define SPI$_PREADIO    0x1022
#define SPI$_DIRIO      0x1036
#define SPI$_BUFIO      0x1037
#define SPI$_PROCS      0x101A
#define SPI$_PROC       0x101B
#define SPI$_MSCP_ALL   0x108B

#define ModeCounters 8

#define InterruptMode 0
#define MultiProcMode 1
#define KernelMode 2
#define ExecutiveMode 3
#define SupervisorMode 4
#define UserMode 5
#define CompatibilityMode 6
#define NullMode 7

int CollectSPI ()

{
   const long cvtf_mode = LIB$K_DELTA_SECONDS_F;

   static unsigned long  MscpAll[13],
                         Processes,
                         FreeList,
                         CpuTicks,
                         UserModeCpuTicks,
                         PageFaults,
                         PrevPageFaults = 0,
                         HardPageFaults,
                         PrevHardPageFaults = 0,
                         PageReadIO,
                         PrevPageReadIO = 0,
                         PageWriteIO,
                         PrevPageWriteIO = 0,
                         BufferedIO,
                         PrevBufferedIO = 0,
                         DirectIO,
                         PrevDirectIO = 0,
                         MscpIO,
                         PrevMscpIO = 0;

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

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

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

   /* 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;

   /* 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$_PWRITEIO, &PageWriteIO, 0 },
       { sizeof(unsigned long), SPI$_BUFIO, &BufferedIO, 0 },
       { sizeof(unsigned long), SPI$_DIRIO, &DirectIO, 0 },
       { sizeof(unsigned long), SPI$_PROCS, &Processes, 0 },
       { sizeof(MscpAll), SPI$_MSCP_ALL, &MscpAll, 0 },
       {0,0,0,0}
   };

#ifndef __VAX
#   pragma member_alignment __restore
#endif

   static struct {
      unsigned long  Status;
      unsigned long  Reserved;
   } IOsb;

   static unsigned long  PreviousModes[ModeCounters];

   int  status;
   unsigned long  tmp, cidx, midx;
   unsigned long CurrentModes[ModeCounters];
   float  DeltaSeconds;

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

   /* get time immediately before getting system performance information */
   sys$gettim (&BinTime);

   /* collect System Performance Information */
   status = exe$getspi (
            0,  /* efn */
            0,  /* csiaddr */
            0,  /* nodename */
            &ItemList,  /* item list */
            &IOsb,  /* iosb */
            &SPI_AST,  /* astaddr */
            0 );  /* astprm */

   sys$hiber ();

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

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

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

   /*
      If zero (first previous) was subtracted from the current time *some*
      Alpha lib$cvtf...() spat with a HPARITH (high performance arithmetic
      trap) FLTINV (floating invalid operation).  Not sure why!
   */
   if (!PrevBinTime[0] && !PrevBinTime[1])
   {
      PrevBinTime[0] = BinTime[0];
      PrevBinTime[1] = BinTime[1];
   }
   lib$sub_times (&BinTime, &PrevBinTime, &DiffBinTime);
   PrevBinTime[0] = BinTime[0];
   PrevBinTime[1] = BinTime[1];
   lib$cvtf_from_internal_time (&cvtf_mode, &DeltaSeconds, &DiffBinTime);
   TotalDeltaSeconds += DeltaSeconds;
   if (Debug)
      fprintf (stdout, "DeltaSeconds: %f TotalDeltaSeconds: %f\n",
               DeltaSeconds, TotalDeltaSeconds);

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

   if (DebugSpi)
   {
      fprintf (stdout, "CPUs: %d\n", ModeTicks.NumberOfCPUs);
      for (cidx = 0; cidx < ModeTicks.NumberOfCPUs; cidx++)
      {
         for (midx = InterruptMode; midx <= NullMode; 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 = InterruptMode; midx <= NullMode; midx++)
           ModeTicks.CPU[0].Count[midx] += ModeTicks.CPU[cidx].Count[midx];

   /* current raw CPU ticks */
   CurrentModes[InterruptMode] = ModeTicks.CPU[0].Count[InterruptMode] -
                                 ModeTicks.CPU[0].Count[NullMode];
   CurrentModes[MultiProcMode] = ModeTicks.CPU[0].Count[MultiProcMode];
   CurrentModes[KernelMode] = ModeTicks.CPU[0].Count[KernelMode];
   CurrentModes[ExecutiveMode] = ModeTicks.CPU[0].Count[ExecutiveMode];
   CurrentModes[SupervisorMode] = ModeTicks.CPU[0].Count[SupervisorMode];
   CurrentModes[UserMode] = ModeTicks.CPU[0].Count[UserMode];
   CurrentModes[CompatibilityMode] = ModeTicks.CPU[0].Count[CompatibilityMode];
   CurrentModes[NullMode] = ModeTicks.CPU[0].Count[NullMode];

   if (DebugSpi)
   {
      fprintf (stdout,
      "raw CPU\nI: %u\nMP: %u\nK: %u\nE: %u\nS: %u\nU: %u\nC: %u\n?: %u\n",
      CurrentModes[0], CurrentModes[1], CurrentModes[2], CurrentModes[3],
      CurrentModes[4], CurrentModes[5], CurrentModes[6], CurrentModes[7]);
   }

   /* calculate the number of CPU ticks for this period */
   CpuTicks = 0;
   for (midx = InterruptMode; midx <= CompatibilityMode; 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] >= PreviousModes[midx])
          CurrentModes[midx] = tmp - PreviousModes[midx];
       else
          CurrentModes[midx] = 0;
#else /* VAX */
       CurrentModes[midx] = tmp - PreviousModes[midx];
#endif /* #ifndef __VAX */
       PreviousModes[midx] = tmp;
       /* keep track of the total ticks */
       CpuTicks += CurrentModes[midx];
   }
   UserModeCpuTicks = CurrentModes[UserMode] + CurrentModes[CompatibilityMode];

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

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

   if (DebugSpi)
   {
       fprintf (stdout,
"CPU ticks %u\n\
CPU user mode ticks %u\n\
Free list: %u\n\
Page faults: %u  %u hard\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",
       CpuTicks, UserModeCpuTicks,
       FreeList, PageFaults, PageReadIO+PageWriteIO,
       BufferedIO, DirectIO, Processes,
       MscpAll[0], MscpAll[1], MscpAll[2], MscpAll[3], MscpAll[4],
       MscpAll[5], MscpAll[6], MscpAll[7], MscpAll[8], MscpAll[9],
       MscpAll[10], MscpAll[11], MscpAll[12]);
   }

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

   if (!SpiRecord.NumberOfCPUs)
      FloatNumberOfCPUs = (float)(SpiRecord.NumberOfCPUs =
                                  ModeTicks.NumberOfCPUs);

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

   SpiRecord.NumberOfProcesses = Processes;

   /* 'PrevPageFaults' will only be zero first time function is called */
   if (PrevPageFaults)
   {
      TotalCpuTicks += CpuTicks;
      TotalUserModeCpuTicks += UserModeCpuTicks;

      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;
      }
      PrevPageFaults = PageFaults;
      PrevPageReadIO = PageReadIO;
      PrevPageWriteIO = PageWriteIO;
   }

   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;
   }

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


   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
AST function executed when exe$getspi() completes.  Just wake the process.
*/ 

SPI_AST ()

{
   sys$wake (0, 0);
}

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

CollectPageSpace ()

{
   static unsigned long  PageFileSize,
                         PageFileFree;
   static struct {
      short BufferLength;
      short ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   } SyiItems [] =
   {
      { 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 = (PageFileSize * (SyiPageSize / 512)) / 2048;
   SpiRecord.PageSpacePercentInUse =
      100 - ((PageFileFree * 100) / PageFileSize);
   if (Debug)
      fprintf (stdout,
              "PageSpaceMBytes: %d PageSpacePercentInUse: %d\n",
              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 CollectSystemInfo ()

{
   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 },
      { 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);
}

/*****************************************************************************/
/*
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);
}

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

DebugSpiRecord ()

{
   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\n",
   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);
} 

/****************************************************************************/
/*
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);
}

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

