/*****************************************************************************/
/*
                                  sysPLUS.c

Show essential system data without using a scripting process as does
WatchShowSystem().  It is intended as a fallback should the creation of a
scripting process fail (for example; no remaining process slot, insufficient
paged dynamic memory), providing essential data useful for trouble-shooting. 
It can also be activated using the URI /httpd/-/admin/report/system+.  And also
via the CLI with |rqptr| NULL which FaoToEither() handles.  I have experienced
both.

See sysPlusNet() for usage of logical name WASD_SYSPLUS_NI.


EXE$GETSPI
----------
What a dog's breakfast $GETRMI seems to be.  With MonDeSi it just seemed too
difficult to get consistent results across versions and platforms so I went
back to EXE$GETSPI and have arrived at the same conclusion again this time.

RMI$_SCS on Alpha VMS V8.3 and IA64 VSI V8.4 returns %X00000014
(%SYSTEM-F-BADPARAM, bad parameter value) when using this item.  (Same on VAX
V7.3 - but absolutely anything that works on VAX too is a bonus).  Using this
item with $GETSPI no problems.


VERSION HISTORY
---------------
20-JUL-2019  MGD  expand to include network, MSCP and SCS data
                  dignified with a module of its very own
                  adapt to allow use via CLI /SYSPLUS
25-AUG-2018  MGD  initial as WatchSystemPlus() et.al.
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <descrip.h>
#include <dvidef.h>
#include <dvsdef.h>
#include <iodef.h>
#include <jpidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <lnmdef.h>
#include <rmidef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>

/* application related header files */
#include "wasd.h"

#define WASD_MODULE "SYSPLUS"

#define DBUG 0

/* last <whatever> for selected data */
#define NUM_MINS 10

/* 96 is (still) the SPD cluster node limit (we had 70+ in the '90s :-) */
#define SCS_NODES_MAX 16

#ifndef WASD_GETSPI
#define WASD_GETSPI 0
#endif

/******************/
/* global storage */
/******************/

static ulong  sysPlusRmiMemErrs;

/********************/
/* external storage */
/********************/

extern int  EfnWait,
            HttpdTickSecond;

extern ulong  HttpdTime64[],
              MailboxMask[];

extern SYS_INFO  SysInfo;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Report wrapper.
*/

void sysPlusReport (REQUEST_STRUCT *rqptr)

{
   static $DESCRIPTOR (SystemFaoDsc,
"!AZ, a !AZ with !UL CPU and !ULMB running VMS !AZ");

   static char UnderFao [] =
"!AZ\n!#*-\nCurrent: !20%D  Boot: !20%D  Up: !%D\n\n";

   static ulong  SyiBootTime [2],
                 GetTime64 [2],
                 UpTime64 [2];

   static VMS_ITEM_LIST3  SyiItem [] =
   {
     { sizeof(SyiBootTime), SYI$_BOOTTIME, &SyiBootTime, 0 },
     { 0,0,0,0 }
   };

   BOOL  Not200;
   int  status;
   ushort  slen;
   char  SystemBuffer [256];
   IO_SB  IOsb;
   $DESCRIPTOR (SystemBufferDsc, SystemBuffer);

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

   Not200 = true;
   if (rqptr)
   {
      if (Not200 = (rqptr->rqResponse.HttpStatus != 200))
      {
         rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
         ResponseHeader200 (rqptr, "text/plain", NULL);
      }
   }

   status = sys$getsyiw (EfnWait, 0, 0, &SyiItem, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      FaoToEither (rqptr, "$GETSYI() !&S\n",  status);
      return;
   }

   sys$gettim (&GetTime64);
   status = lib$sub_times (&GetTime64, &SyiBootTime, &UpTime64);
   if (VMSnok (status)) FaoToEither (rqptr, "$SUB_TIMES() !&S\n",  status);

   status = sys$fao (&SystemFaoDsc, &slen, &SystemBufferDsc,
                     SysInfo.NodeName,
                     SysInfo.HwName,
                     SysInfo.AvailCpuCnt,
                     SysInfo.MemoryMB,
                     SysInfo.Version);
   SystemBuffer[slen] = '\0';
   if (VMSnok (status)) FaoToEither (rqptr, "$FAO() !&S\n",  status);

   status = FaoToEither (rqptr, &UnderFao, SystemBuffer, slen,
                         &GetTime64, &SyiBootTime, &UpTime64);
   if (VMSnok (status)) FaoToEither (rqptr, "$FAO() !&S\n",  status);

   if (!rqptr)
   {
      /* at the command-line take a ten second sample of CPU consumption */
      sysPlusRmi ((REQUEST_STRUCT*)-1);
      sleep (10);
      /* force that sample up into the first "minute" buffer */
      sysPlusRmi ((REQUEST_STRUCT*)-1);
   }

   sysPlusRmi (rqptr);
   sysPlusSyi (rqptr);
   sysPlusScs (rqptr);
   sysPlusMscp (rqptr);
   sysPlusDvi (rqptr);
   sysPlusNet (rqptr);
   sysPlusJpi (rqptr);

   if (Not200)
   {
      FaoToEither (rqptr, "!77*-\n\n", SystemBuffer, slen);
      if (rqptr) AdminEnd (rqptr);
   }
}

/*****************************************************************************/
/*
Provide device data.
*/

void sysPlusDvi (REQUEST_STRUCT *rqptr)

{
#define DC$_DISK 1
#define DEV$V_NET 0x2000

   static char  DviHeader [] =
"Device Name      Errs  Refs        Ops Status\n\
~~~~~~~~~~~~~~~ ~~~~~ ~~~~~ ~~~~~~~~~~ ~~~~~~\n";

   static char  DeviceFao [] =
"!#AZ !5UL !5UL !10UL !AZ\n\
!#*  !5AZ!17*  !AZ\n";

   static char  NetSpeedFao [] = "!#* !AZ !AZ !ULMbpS\n",
                NetNoSpeedFao [] = "!#* !AZ !AZ\n";

   static char  DiskFao [] = "!#* \"!AZ\" !AZ free of !AZ (!UL%)\n";

   static $DESCRIPTOR (SearchDeviceDsc, "*");

   static ulong  DviDevClass,
                 DviDevChar,
                 DviDevChar2,
                 DviErrCnt,
                 DviFreeBlocks,
                 DviLanLinkStateValid,
                 DviLanLinkUp,
                 DviLanSpeed,
                 DviMaxBlock,
                 DviOpCnt,
                 DviPid,
                 DviRefCnt,
                 DviSts;

   static ushort  DviDevTypeNameLen,
                  DviVolNamLen;

   static char  DviDevTypeName [64+1],
                DviVolNam [15+1];

   static struct
   {
      ushort  buf_len;
      ushort  item;
      uchar   *buf_addr;
      ushort  *short_ret_len;
   }
      DviItems [] =
   {
      { sizeof(DviDevClass), DVI$_DEVCLASS, &DviDevClass, 0 },
      { sizeof(DviDevChar), DVI$_DEVCHAR, &DviDevChar, 0 },
      { sizeof(DviDevChar2), DVI$_DEVCHAR2, &DviDevChar2, 0 },
      { sizeof(DviErrCnt), DVI$_ERRCNT, &DviErrCnt, 0 },
      { sizeof(DviFreeBlocks), DVI$_FREEBLOCKS, &DviFreeBlocks, 0 },
#if(__VMS_VER >= 80000000) && (!defined __VAX)
      { sizeof(DviLanLinkStateValid), DVI$_LAN_LINK_STATE_VALID,
        &DviLanLinkStateValid, 0 },
      { sizeof(DviLanLinkUp), DVI$_LAN_LINK_UP, &DviLanLinkUp, 0 },
      { sizeof(DviLanSpeed), DVI$_LAN_SPEED, &DviLanSpeed, 0 },
#endif
      { sizeof(DviMaxBlock), DVI$_MAXBLOCK, &DviMaxBlock, 0 },
      { sizeof(DviDevTypeName)-1, DVI$_DEVICE_TYPE_NAME, &DviDevTypeName,
        &DviDevTypeNameLen},
      { sizeof(DviOpCnt), DVI$_OPCNT, &DviOpCnt, 0 },
      { sizeof(DviPid), DVI$_PID, &DviPid, 0 },
      { sizeof(DviRefCnt), DVI$_REFCNT, &DviRefCnt, 0 },
      { sizeof(DviSts), DVI$_STS, &DviSts, 0 },
      { sizeof(DviVolNam)-1, DVI$_VOLNAM, &DviVolNam, &DviVolNamLen },
      { 0,0,0,0 }
   };

   int  status;
   ushort  slen;
   ulong  context64 [2];
   double  freefp, sizefp;
   char  DevCharBuf [256],
         DevName [64+1],
         DevStsBuf [256],
         FreeBuf [32],
         SizeBuf [32];
   $DESCRIPTOR (DevNameDsc, DevName);
   $DESCRIPTOR (ScanNameDsc, DevName);
   IO_SB  IOsb;
   
   /*********/
   /* begin */
   /*********/

   FaoToEither (rqptr, DviHeader);

   PUT_ZERO_QUAD (&context64);

   for (;;)
   {
      status = sys$device_scan (&ScanNameDsc, &slen,
                                &SearchDeviceDsc, 0, &context64);
      if (VMSnok (status))
      {
         if (status != SS$_NOMOREDEV)
            FaoToEither (rqptr, "$DEVICE_SCAN() !&S\n", status);
         break;
      }

      DevName[slen] = '\0';
      DevNameDsc.dsc$w_length = slen;

      if (MATCH3 (DevName, "_BG") && !MATCH5 (DevName, "_BG0:")) continue;
      if (strstr (DevName, "$BG")) continue;

      if (MATCH3 (DevName, "_MB")) continue;
      if (strstr (DevName, "$MB")) continue;

      status = sys$getdviw (EfnWait, 0, &DevNameDsc,
                            &DviItems, &IOsb, 0, 0, 0, 0);
      if (VMSok (status)) status = IOsb.Status;
      if (VMSnok (status))
      {
         if (status != SS$_NOMOREDEV)
            FaoToEither (rqptr, "$GETDVI() !&S\n", status);
         break;
      }

      DevStsBuf[0] = '\0';

      if (DviSts & 0x1) strcat (DevStsBuf, " tim");
      if (DviSts & 0x2) strcat (DevStsBuf, " int");
      if (DviSts & 0x4) strcat (DevStsBuf, " erlogip");
      if (DviSts & 0x8) strcat (DevStsBuf, " cancel");
      if (DviSts & 0x10) strcat (DevStsBuf, " ONLINE");
                    else strcat (DevStsBuf, " OFFLINE");
      if (DviSts & 0x20) strcat (DevStsBuf, " power");
      if (DviSts & 0x40) strcat (DevStsBuf, " TIMEOUT");
      if (DviSts & 0x80) strcat (DevStsBuf, " inttype");
      if (DviSts & 0x100) strcat (DevStsBuf, " busy");
      if (DviSts & 0x200) strcat (DevStsBuf, " mounting");
      if (DviSts & 0x400) strcat (DevStsBuf, " deadmo");
      if (DviSts & 0x800) strcat (DevStsBuf, " valid");
      if (DviSts & 0x1000) strcat (DevStsBuf, " unload");
      if (DviSts & 0x2000) strcat (DevStsBuf, " template");
      if (DviSts & 0x4000) strcat (DevStsBuf, " MNTVERIP");
      if (DviSts & 0x8000) strcat (DevStsBuf, " WRONGVOL");
      if (DviSts & 0x10000) strcat (DevStsBuf, " deleteucb");
      if (DviSts & 0x20000) strcat (DevStsBuf, " lclvalid");
      if (DviSts & 0x40000) strcat (DevStsBuf, " supmvmsg");
      if (DviSts & 0x80000) strcat (DevStsBuf, " MNTVERPND");
      if (DviSts & 0x100000) strcat (DevStsBuf, " dismount");
      if (DviSts & 0x200000) strcat (DevStsBuf, " clutran");
      if (DviSts & 0x400000) strcat (DevStsBuf, " wrtlockmv");
      if (DviSts & 0x800000) strcat (DevStsBuf, " svpnend");
      if (DviSts & 0x1000000) strcat (DevStsBuf, " altbsy");
      if (DviSts & 0x2000000) strcat (DevStsBuf, " snapshot");
      if (DviSts & 0x4000000) strcat (DevStsBuf, " noassign");
      if (DviSts & 0x8000000) strcat (DevStsBuf, " exfuncsupp");
      if (DviSts & 0x1000000) strcat (DevStsBuf, " fastpath");
      if (DviSts & 0x2000000) strcat (DevStsBuf, " pathverip");
      if (DviSts & 0x4000000) strcat (DevStsBuf, " fphwint");
      if (DviSts & 0x8000000) strcat (DevStsBuf, " iopostlocal");

      strncpy (DevCharBuf, DviDevTypeName, DviDevTypeNameLen);
      DevCharBuf[DviDevTypeNameLen] = ':';
      DevCharBuf[DviDevTypeNameLen+1] = '\0';

      if (DviDevChar & 0x1) strcat (DevCharBuf, " rec");
      if (DviDevChar & 0x2) strcat (DevCharBuf, " ccl");
      if (DviDevChar & 0x4) strcat (DevCharBuf, " TERMINAL");
      if (DviDevChar & 0x8) strcat (DevCharBuf, " dir");
      if (DviDevChar & 0x10) strcat (DevCharBuf, " sdi");
      if (DviDevChar & 0x20) strcat (DevCharBuf, " sqd");
      if (DviDevChar & 0x40) strcat (DevCharBuf, " spl");
      if (DviDevChar & 0x80) strcat (DevCharBuf, " opr");
      if (DviDevChar & 0x100) strcat (DevCharBuf, " rct");
      if (DviDevChar & 0x200) strcat (DevCharBuf, " qsvd");
      if (DviDevChar & 0x400) strcat (DevCharBuf, " qsvbl");
      if (DviDevChar & 0x800) strcat (DevCharBuf, " mpdev_sec");
      if (DviDevChar & 0x1000) strcat (DevCharBuf, " mpdev_mem");
      if (DviDevChar & 0x2000) strcat (DevCharBuf, " NETWORK");
      if (DviDevChar & 0x4000) strcat (DevCharBuf, " fod");
      if (DviDevChar & 0x8000) strcat (DevCharBuf, " dua");
      if (DviDevChar & 0x10000) strcat (DevCharBuf, " shr");
      if (DviDevChar & 0x20000) strcat (DevCharBuf, " gen");
      if (DviDevChar & 0x40000) strcat (DevCharBuf, " avl");
      if (DviDevChar & 0x80000) strcat (DevCharBuf, " MOUNTED");
      if (DviDevChar & 0x100000) strcat (DevCharBuf, " mbx");
      if (DviDevChar & 0x200000) strcat (DevCharBuf, " DMT");
      if (DviDevChar & 0x400000) strcat (DevCharBuf, " elg");
      if (DviDevChar & 0x800000) strcat (DevCharBuf, " all");
      if (DviDevChar & 0x1000000) strcat (DevCharBuf, " for");
      if (DviDevChar & 0x2000000) strcat (DevCharBuf, " swl");
      if (DviDevChar & 0x4000000) strcat (DevCharBuf, " idv");
      if (DviDevChar & 0x8000000) strcat (DevCharBuf, " odv");
      if (DviDevChar & 0x1000000) strcat (DevCharBuf, " rnd");
      if (DviDevChar & 0x2000000) strcat (DevCharBuf, " rtm");
      if (DviDevChar & 0x4000000) strcat (DevCharBuf, " rck");
      if (DviDevChar & 0x8000000) strcat (DevCharBuf, " wck");

      if (DviDevChar2 & 0x1) strcat (DevCharBuf, " clu");
      if (DviDevChar2 & 0x2) strcat (DevCharBuf, " det");
      if (DviDevChar2 & 0x4) strcat (DevCharBuf, " rtt");
      if (DviDevChar2 & 0x8) strcat (DevCharBuf, " cdp");
      if (DviDevChar2 & 0x10) strcat (DevCharBuf, " 2p");
      if (DviDevChar2 & 0x20) strcat (DevCharBuf, " mscp");
      if (DviDevChar2 & 0x40) strcat (DevCharBuf, " ssm");
      if (DviDevChar2 & 0x80) strcat (DevCharBuf, " srv");
      if (DviDevChar2 & 0x100) strcat (DevCharBuf, " red");
      if (DviDevChar2 & 0x200) strcat (DevCharBuf, " nnm");
      if (DviDevChar2 & 0x400) strcat (DevCharBuf, " wbc");
      if (DviDevChar2 & 0x800) strcat (DevCharBuf, " wtc");
      if (DviDevChar2 & 0x1000) strcat (DevCharBuf, " hoc");
      if (DviDevChar2 & 0x2000) strcat (DevCharBuf, " loc");
      if (DviDevChar2 & 0x4000) strcat (DevCharBuf, " dfs");
      if (DviDevChar2 & 0x8000) strcat (DevCharBuf, " dap");
      if (DviDevChar2 & 0x10000) strcat (DevCharBuf, " nlt");
      if (DviDevChar2 & 0x20000) strcat (DevCharBuf, " sex");
      if (DviDevChar2 & 0x40000) strcat (DevCharBuf, " shd");
      if (DviDevChar2 & 0x80000) strcat (DevCharBuf, " vrt");
      if (DviDevChar2 & 0x100000) strcat (DevCharBuf, " ldr");
      if (DviDevChar2 & 0x200000) strcat (DevCharBuf, " nolb");
      if (DviDevChar2 & 0x400000) strcat (DevCharBuf, " noclu");
      if (DviDevChar2 & 0x800000) strcat (DevCharBuf, " vmem");
      if (DviDevChar2 & 0x1000000) strcat (DevCharBuf, " scsi");
      if (DviDevChar2 & 0x2000000) strcat (DevCharBuf, " wlg");
      if (DviDevChar2 & 0x4000000) strcat (DevCharBuf, " nofe");
      if (DviDevChar2 & 0x8000000) strcat (DevCharBuf, " 0x8000000");
      if (DviDevChar2 & 0x1000000) strcat (DevCharBuf, " cramio");
      if (DviDevChar2 & 0x2000000) strcat (DevCharBuf, " dtn");
      if (DviDevChar2 & 0x4000000) strcat (DevCharBuf, " 0x40000000");
      if (DviDevChar2 & 0x8000000) strcat (DevCharBuf, " pool_mbr");

      FaoToEither (rqptr, DeviceFao,
                slen > 15 ? slen : 15,
                DevName+1,
                DviErrCnt,
                DviRefCnt,
                DviOpCnt,
                DevStsBuf+1,
                slen > 15 ? slen : 15,
                DviErrCnt ? "^^^^^" : "     ",
                DevCharBuf);

      if (DviDevClass == DC$_DISK)
      {
         if (DviVolNamLen)
         {
            DviVolNam[DviVolNamLen] = '\0';
            sizefp = (double)DviMaxBlock * 512.0 / 1073741824.0;
            freefp = (double)DviFreeBlocks * 512.0 / 1073741824.0;
            sprintf (SizeBuf, "%.2fGB", sizefp);
            sprintf (FreeBuf, "%.2fGB", freefp);

            FaoToEither (rqptr, DiskFao,
                      slen > 39 ? slen+20 : 39,
                      DviVolNam, FreeBuf, SizeBuf,
                      (int)(freefp * 100.0 / sizefp));
         }
      }
      else
      if (DviDevChar & DEV$V_NET)
      {
#ifdef __VAX
         DviLanSpeed = 0;
#endif
         if (DviLanSpeed)
            FaoToEither (rqptr, NetSpeedFao,
                      slen > 39 ? slen+20 : 39,
                      DviLanLinkStateValid ? "valid" : "invalid",
                      DviLanLinkUp ? "UP" : "DOWN",
                      DviLanSpeed);
         else
            FaoToEither (rqptr, NetNoSpeedFao,
                      slen > 39 ? slen+20 : 39,
                      DviLanLinkStateValid ? "valid" : "invalid",
                      DviLanLinkUp ? "UP" : "DOWN");
      }
   }

   FaoToEither (rqptr, "\n");
}

/*****************************************************************************/
/*
Provide process data.
*/

#define PSCAN$_GETJPI_BUFFER_SIZE 24

void sysPlusJpi (REQUEST_STRUCT *rqptr)

{
/* e.g. U-S-E-K- U+S-E-K- U^S-E-K- */
#define ASTFLG(bit,idx) { if (JpiAstEn & bit) if (JpiAstAct & bit) \
                          AstFlags[idx] = '^'; else AstFlags[idx] = '+'; \
                          else AstFlags[idx] = '-'; }

   static char  JpiHeader [] =
"PID      Process Name    Prior       I/O             CPU \
Page flts file State\n\
~~~~~~~~ Username ~~~~~~ Image !46*~\n";

   static char  ProcessFao [] =
"!8XL !15AZ !5<!2UL/!UL!> !9UL !3UL !2ZL:!2ZL:!2ZL.!2ZL !9UL !3UL% !AZ\n\\
         !15AZ !AZ\n";

   static ulong  GetJpiControlFlags = JPI$M_IGNORE_TARGET_STATUS;

   static ulong  JpiAstAct,
                 JpiAstEn,
                 JpiBufIo,
                 JpiCpuTim,
                 JpiDirIo,
                 JpiJobType,
                 JpiMode,
                 JpiPagFilCnt,
                 JpiPageFlts,
                 JpiPgFlQuota,
                 JpiPid,
                 JpiPri,
                 JpiPrib,
                 JpiState,
                 JpiSts,
                 JpiSts2;

   static char  JpiImagName [256],
                JpiNodeName [32],
                JpiPrcNam [16],
                JpiUserName [13];

   static struct
   {
      ushort  buf_len;
      ushort  item;
      uchar   *buf_addr;
      ushort  *short_ret_len;
   }
      JpiItems [] =
   {
      { sizeof(GetJpiControlFlags), JPI$_GETJPI_CONTROL_FLAGS,
        &GetJpiControlFlags, 0 },
      { sizeof(JpiAstAct), JPI$_ASTACT, &JpiAstAct, 0 },
      { sizeof(JpiAstEn),  JPI$_ASTEN,  &JpiAstEn, 0 },
      { sizeof(JpiBufIo), JPI$_BUFIO, &JpiBufIo, 0 },
      { sizeof(JpiCpuTim), JPI$_CPUTIM, &JpiCpuTim, 0 },
      { sizeof(JpiDirIo), JPI$_DIRIO, &JpiDirIo, 0 },
      { sizeof(JpiImagName), JPI$_IMAGNAME, &JpiImagName, 0 },
      { sizeof(JpiJobType), JPI$_JOBTYPE, &JpiJobType, 0 },
      { sizeof(JpiMode), JPI$_MODE, &JpiMode, 0 },
      { sizeof(JpiPageFlts), JPI$_PAGEFLTS, &JpiPageFlts, 0 },
      { sizeof(JpiPgFlQuota), JPI$_PGFLQUOTA, &JpiPgFlQuota, 0 },
      { sizeof(JpiPagFilCnt), JPI$_PAGFILCNT, &JpiPagFilCnt, 0 },
      { sizeof(JpiPrcNam), JPI$_PRCNAM, &JpiPrcNam, 0 },
      { sizeof(JpiPid), JPI$_PID, &JpiPid, 0 },
      { sizeof(JpiPri), JPI$_PRI, &JpiPri, 0 },
      { sizeof(JpiPrib), JPI$_PRIB, &JpiPrib, 0 },
      { sizeof(JpiState), JPI$_STATE, &JpiState, 0 },
      { sizeof(JpiSts), JPI$_STS, &JpiSts, 0 },
      { sizeof(JpiSts2), JPI$_STS, &JpiSts2, 0 },
      { sizeof(JpiUserName), JPI$_USERNAME, &JpiUserName, 0 },
      { 0,0,0,0 }
   },
   ScanItems [] =
   {
      { 0, PSCAN$_GETJPI_BUFFER_SIZE, 2048, 0},
      { 0,0,0,0 }
   };

   int  idx, status,
        IdentCount,
        ProcessCount,
        SetPrvStatus;
   ulong  *vecptr;
   ulong  ProcessContext;
   ulong  FaoVector [32];
   char  *cptr, *sptr;
   char  AstFlags [16],
         ProcFlags [512];
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "sysPlusJpi()");

   ProcessContext = 0;
   status = sys$process_scan (&ProcessContext, &ScanItems);
   if (VMSnok (status))
   {
      FaoToEither (rqptr, "$PROCESS_SCAN() !&S\n", status);
      return;
   }

   if (VMSnok (SetPrvStatus = sys$setprv (1, &MailboxMask, 0, 0)))
      ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);

   ProcessCount = 0;

   FaoToEither (rqptr, JpiHeader);

   for (;;)
   {
      status = sys$getjpiw (EfnWait, &ProcessContext, 0,
                            &JpiItems, &IOsb, 0, 0);
      if (VMSok (status)) status = IOsb.Status;
      if (VMSnok (status))
      {
         if (status != SS$_NOMOREPROC)
            FaoToEither (rqptr, "$GETJPI() !&S\n", status);
         break;
      }

      JpiPrcNam[15] = '\0';
      for (cptr = JpiPrcNam; *cptr && *cptr != ' '; cptr++);
      *cptr = '\0';

      JpiUserName[12] = '\0';
      for (cptr = JpiUserName; *cptr && *cptr != ' '; cptr++);
      *cptr = '\0';

      ProcFlags[0] = '\0';

      switch (JpiState)
      {
         case  1 : strcat (ProcFlags, "COLPG"); break;
         case  2 : strcat (ProcFlags, "MWAIT"); break;
         case  3 : strcat (ProcFlags, "CEF"); break;
         case  4 : strcat (ProcFlags, "PFW"); break;
         case  5 : strcat (ProcFlags, "LEF"); break;
         case  6 : strcat (ProcFlags, "LEFO"); break;
         case  7 : strcat (ProcFlags, "HIB"); break;
         case  8 : strcat (ProcFlags, "HIBO"); break;
         case  9 : strcat (ProcFlags, "SUSP"); break;
         case 10 : strcat (ProcFlags, "SUSPO"); break;
         case 11 : strcat (ProcFlags, "FPG"); break;
         case 12 : strcat (ProcFlags, "COM"); break;
         case 13 : strcat (ProcFlags, "COMO"); break;
         case 14 : strcat (ProcFlags, "CUR"); break;
         default : strcat (ProcFlags, "?");
      }

      strcat (ProcFlags, "\n         ");

      switch (JpiMode)
      {
         case  0 : strcat (ProcFlags, "other"); break;
         case  1 : strcat (ProcFlags, "network"); break;
         case  2 : strcat (ProcFlags, "batch"); break;
         case  3 : strcat (ProcFlags, "interactive"); break;
         default : strcat (ProcFlags, "?");
      }

      switch (JpiJobType)
      {
         case 0 : strcat (ProcFlags, ":detached"); break;
         case 1 : strcat (ProcFlags, ":network"); break;
         case 2 : strcat (ProcFlags, ":batch"); break;
         case 3 : strcat (ProcFlags, ":local"); break;
         case 4 : strcat (ProcFlags, ":dialup"); break;
         case 5 : strcat (ProcFlags, ":remote"); break;
         default : strcat (ProcFlags, ":?");
      }

      /* AST flags */
      AstFlags[0] = ' ';
      AstFlags[1] = 'U'; ASTFLG(1,2)
      AstFlags[3] = 'S'; ASTFLG(2,4)
      AstFlags[5] = 'E'; ASTFLG(4,6)
      AstFlags[7] = 'K'; ASTFLG(8,8)
      AstFlags[9] = '\0';
      strcat (ProcFlags, AstFlags);

      if (JpiSts & 0x1) strcat (ProcFlags, " res");
      if (JpiSts & 0x2) strcat (ProcFlags, " delpen");
      if (JpiSts & 0x4) strcat (ProcFlags, " forcpen");
      if (JpiSts & 0x8) strcat (ProcFlags, " inquan");
      if (JpiSts & 0x10) strcat (ProcFlags, " pswapm");
      if (JpiSts & 0x20) strcat (ProcFlags, " respen");
      if (JpiSts & 0x40) strcat (ProcFlags, " ssfexc");
      if (JpiSts & 0x80) strcat (ProcFlags, " ssfexce");
      if (JpiSts & 0x100) strcat (ProcFlags, " ssfexcs");
      if (JpiSts & 0x200) strcat (ProcFlags, " ssfexcu");
      if (JpiSts & 0x400) strcat (ProcFlags, " ssrwait");
      if (JpiSts & 0x800) strcat (ProcFlags, " suspen");
      if (JpiSts & 0x2000) strcat (ProcFlags, " wall");
      if (JpiSts & 0x4000) strcat (ProcFlags, " batch");
      if (JpiSts & 0x8000) strcat (ProcFlags, " noacnt");
      if (JpiSts & 0x10000) strcat (ProcFlags, " nosuspend");
      if (JpiSts & 0x20000) strcat (ProcFlags, " astpen");
      if (JpiSts & 0x40000) strcat (ProcFlags, " phdres");
      if (JpiSts & 0x80000) strcat (ProcFlags, " hiber");
      if (JpiSts & 0x100000) strcat (ProcFlags, " login");
      if (JpiSts & 0x200000) strcat (ProcFlags, " netwrk");
      if (JpiSts & 0x400000) strcat (ProcFlags, " pwrast");
      if (JpiSts & 0x800000) strcat (ProcFlags, " nodelet");
      if (JpiSts & 0x1000000) strcat (ProcFlags, " disaws");
      if (JpiSts & 0x2000000) strcat (ProcFlags, " inter");
      if (JpiSts & 0x4000000) strcat (ProcFlags, " recover");
      if (JpiSts & 0x8000000) strcat (ProcFlags, " 0x8000000");
      if (JpiSts & 0x1000000) strcat (ProcFlags, " hardaff");
      if (JpiSts & 0x2000000) strcat (ProcFlags, " erdact");
      if (JpiSts & 0x4000000) strcat (ProcFlags, " softsusp");
      if (JpiSts & 0x8000000) strcat (ProcFlags, " preempted");

      strcat (ProcFlags, "\n        ");

      if (JpiSts2 & 0x1) strcat (ProcFlags, " qresched");
      if (JpiSts2 & 0x2) strcat (ProcFlags, " deplock");
      if (JpiSts2 & 0x3) strcat (ProcFlags, " fredlock");
      if (JpiSts2 & 0x4) strcat (ProcFlags, " phdlock");
      if (JpiSts2 & 0x10) strcat (ProcFlags, " tcb");
      if (JpiSts2 & 0x20) strcat (ProcFlags, " tbspend");
      if (JpiSts2 & 0x40) strcat (ProcFlags, " sslogenab");
      if (JpiSts2 & 0x80) strcat (ProcFlags, " sslogperm");
      if (JpiSts2 & 0x100) strcat (ProcFlags, " brkrunload");
      if (JpiSts2 & 0x8000) strcat (ProcFlags, " classschedperm");
      if (JpiSts2 & 0x10000) strcat (ProcFlags, " termnotify");
      if (JpiSts2 & 0x20000) strcat (ProcFlags, " bytlmloan");
      if (JpiSts2 & 0x40000) strcat (ProcFlags, " dispreempt");
      if (JpiSts2 & 0x80000) strcat (ProcFlags, " nounshelv");
      if (JpiSts2 & 0x100000) strcat (ProcFlags, " shelvres");
      if (JpiSts2 & 0x200000) strcat (ProcFlags, " classched");
      if (JpiSts2 & 0x400000) strcat (ProcFlags, " classupp");
      if (JpiSts2 & 0x800000) strcat (ProcFlags, " intbsstate");
      if (JpiSts2 & 0x1000000) strcat (ProcFlags, " windfall");
      if (JpiSts2 & 0x2000000) strcat (ProcFlags, " notify");
      if (JpiSts2 & 0x3C000000) strcat (ProcFlags, " sinthread");
      if (JpiSts2 & 0x40000000) strcat (ProcFlags, " rwast");
      if (JpiSts2 & 0x80000000) strcat (ProcFlags, " softsinthr");

      ProcessCount++;

      vecptr = FaoVector;

      *vecptr++ = JpiPid;
      *vecptr++ = JpiPrcNam;
      *vecptr++ = JpiPri;
      *vecptr++ = JpiPrib;
      *vecptr++ = JpiBufIo + JpiDirIo;
      *vecptr++ = JpiCpuTim / 8640000;             /* CPU day */
      *vecptr++ = (JpiCpuTim % 8640000) / 360000;  /* CPU hour */
      *vecptr++ = (JpiCpuTim % 360000) / 6000;     /* CPU minute */
      *vecptr++ = (JpiCpuTim % 6000 ) / 100;       /* CPU second */
      *vecptr++ = JpiCpuTim % 100;                 /* CPU 10 milliseconds */
      *vecptr++ = JpiPageFlts;
      if (JpiPagFilCnt)
         *vecptr++ = 100 - PercentOf (JpiPagFilCnt,JpiPgFlQuota);
      else
         *vecptr++ = 0;
      *vecptr++ = ProcFlags;
      *vecptr++ = JpiUserName;
      if (JpiImagName[0])
         *vecptr++ = JpiImagName;
      else
         *vecptr++ = "[none]";

      if (rqptr)
         status = FaolToNet (rqptr, ProcessFao, &FaoVector);
      else
      {
         char  Buffer [32767];
         status = FaolToBuffer (Buffer, sizeof(Buffer), NULL,
                                ProcessFao, &FaoVector);
         fputs (Buffer, stdout);
      }
      if (VMSnok (status)) FaoToEither (rqptr, "$FAO() !&S\n",  status);
   }

   if (VMSnok (SetPrvStatus = sys$setprv (0, &MailboxMask, 0, 0)))
      ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);
}

/*****************************************************************************/
/*
Provide resource consumption data.  This called with |rqptr| non-NULL to report
the data.  It is also called by HttpdTick() once a minute to update the
per-minute and buffered data.
*/

void sysPlusRmi (REQUEST_STRUCT *rqptr)

{
#define CPU_KERNEL   1
#define CPU_EXEC     2
#define CPU_SUPER    3
#define CPU_USER     4
#define CPU_IDLE     5
#define CPU_int_mps  6
#define CPU_INTSTK   7
#define CPU_MPSYNCH  8
#define CPU_total    9

#define NUM_STATE 9

#define STATE_CEF    1
#define STATE_COLPG  2
#define STATE_COM    3
#define STATE_COMO   4
#define STATE_CUR    5
#define STATE_FPG    6
#define STATE_HIB    7
#define STATE_HIBO   8
#define STATE_LEF    9
#define STATE_LEFO  10
#define STATE_SUSP  11
#define STATE_MWAIT 12
#define STATE_SUSPO 13
#define STATE_total 14
#define MEM_ERRS    15
#define RESMASK     16
#define ULONG_LAST  17

#ifdef __VAX

#pragma member_alignment save 
#pragma _nomember_alignment 

#  define VAX_MAX_CPUS 8

   typedef struct CPU_struct 
   { 
       uchar  cpu_id;
       ulong  interrupt;
       ulong  mpsynch;
       ulong  kernel;
       ulong  exec;
       ulong  super;
       ulong  user;
       ulong  reserved;
       ulong  idle;
   } CPU_STRUCT; 

   static struct CPU_array_struct 
   { 
      ulong  NumberOfCPUs;
      CPU_STRUCT  SingleCpu[VAX_MAX_CPUS];
   } CpuArray;

#pragma member_alignment restore 

   /* CPU modes ([0]..[6]) by minutes ([1]..) plus current read ([0]) */
   static  ulong  RmiCpu [NUM_MINS+2][NUM_STATE+1];

#else /* not __VAX */

   /* CPU modes ([0]..[6]) by minutes ([1]..) plus current read ([0]) */
   static  __int64  RmiCpu [NUM_MINS+2][NUM_STATE+1];

#endif /* __VAX */

   /* various unsigned longs by minutes ([1]..) plus current read [0] */
   static ulong  RmiUlong [NUM_MINS+2][ULONG_LAST+1];

   static VMS_ITEM_LIST3  RmiItem [] =
   {
#ifdef __VAX
     { sizeof(CpuArray), RMI$_MODES, &CpuArray, 0 },
#else /* not __VAX */
     { sizeof(RmiCpu[0][CPU_EXEC]),   RMI$_CPUEXEC, &RmiCpu[0][CPU_EXEC], 0 },
     { sizeof(RmiCpu[0][CPU_IDLE]),   RMI$_CPUIDLE, &RmiCpu[0][CPU_IDLE], 0 },
     { sizeof(RmiCpu[0][CPU_INTSTK]), RMI$_CPUINTSTK, &RmiCpu[0][CPU_INTSTK], 0 },
     { sizeof(RmiCpu[0][CPU_KERNEL]), RMI$_CPUKERNEL, &RmiCpu[0][CPU_KERNEL], 0 },
     { sizeof(RmiCpu[0][CPU_MPSYNCH]), RMI$_CPUMPSYNCH, &RmiCpu[0][CPU_MPSYNCH], 0 },
     { sizeof(RmiCpu[0][CPU_SUPER]),  RMI$_CPUSUPER, &RmiCpu[0][CPU_SUPER], 0 },
     { sizeof(RmiCpu[0][CPU_USER]),   RMI$_CPUUSER, &RmiCpu[0][CPU_USER], 0 },
     { sizeof(RmiUlong[0][MEM_ERRS]), RMI$_MEMERRS, &RmiUlong[0][MEM_ERRS], 0 },
     { sizeof(RmiUlong[0][RESMASK]),  RMI$_RESMASK, &RmiUlong[0][RESMASK], 0 },
#endif /* __VAX */
     { sizeof(RmiUlong[0][STATE_CEF]),  RMI$_CEF, &RmiUlong[0][STATE_CEF], 0 },
     { sizeof(RmiUlong[0][STATE_COLPG]), RMI$_COLPG, &RmiUlong[0][STATE_COLPG], 0 },
     { sizeof(RmiUlong[0][STATE_COM]),  RMI$_COM, &RmiUlong[0][STATE_COM], 0 },
     { sizeof(RmiUlong[0][STATE_COMO]), RMI$_COMO, &RmiUlong[0][STATE_COMO], 0 },
     { sizeof(RmiUlong[0][STATE_CUR]),  RMI$_CUR, &RmiUlong[0][STATE_CUR], 0 },
     { sizeof(RmiUlong[0][STATE_FPG]),  RMI$_FPG, &RmiUlong[0][STATE_FPG], 0 },
     { sizeof(RmiUlong[0][STATE_HIB]),  RMI$_HIB, &RmiUlong[0][STATE_HIB], 0 },
     { sizeof(RmiUlong[0][STATE_HIBO]), RMI$_HIBO, &RmiUlong[0][STATE_HIBO], 0 },
     { sizeof(RmiUlong[0][STATE_LEF]),  RMI$_LEF, &RmiUlong[0][STATE_LEF], 0 },
     { sizeof(RmiUlong[0][STATE_LEFO]), RMI$_LEFO, &RmiUlong[0][STATE_LEFO], 0 },
     { sizeof(RmiUlong[0][STATE_MWAIT]), RMI$_MWAIT, &RmiUlong[0][STATE_MWAIT], 0 },
     { sizeof(RmiUlong[0][STATE_SUSP]), RMI$_SUSP, &RmiUlong[0][STATE_SUSP], 0 },
     { sizeof(RmiUlong[0][STATE_SUSPO]), RMI$_SUSPO, &RmiUlong[0][STATE_SUSPO], 0 },
     { 0,0,0,0 }
   };

   int  min, mode, status;
   ulong  cpuTotal,
          modeTotal;
   ulong  PercentCpu [NUM_STATE+1];
   char  when [16];
   IO_SB  IOsb;

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

   if (rqptr == (REQUEST_STRUCT*)-1)
   {
      /*******************/
      /* per-minute data */
      /*******************/

      /* move all values into one minute further in the past */
      for (min = NUM_MINS+1; min > 0; min--)
      {
         memcpy (RmiCpu[min], RmiCpu[min-1], sizeof(RmiCpu[min]));
         memcpy (RmiUlong[min], RmiUlong[min-1], sizeof(RmiUlong[min]));
      }
   }

#ifdef __VAX
   if (SysInfo.AvailCpuCnt > VAX_MAX_CPUS)
   {
      FaoToEither (rqptr, "SysInfo.AvailCpuCnt > VAX_MAX_CPUS\n");
      return;
   }
#endif /* __VAX */

   /* collect System Performance Information */
   status = sys$getrmi (EfnWait, 0, 0, &RmiItem, &IOsb, 0, 0);
   if (VMSok (status)) sys$synch (EfnWait, &IOsb);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      FaoToEither (rqptr, "$GETRMI() !&S\n",  status);
      return;
   }

#ifdef __VAX
   {
      /* accumulate mode counts into something resembling CPU_.. */
      int  idx;
      memset (&RmiCpu[0], 0, sizeof(RmiCpu[0]));
      for (idx = 0; idx < VAX_MAX_CPUS; idx++)
      {
         RmiCpu[0][CPU_INTSTK] += CpuArray.SingleCpu[idx].interrupt;
         RmiCpu[0][CPU_MPSYNCH] += CpuArray.SingleCpu[idx].mpsynch;
         RmiCpu[0][CPU_KERNEL] += CpuArray.SingleCpu[idx].kernel;
         RmiCpu[0][CPU_EXEC] += CpuArray.SingleCpu[idx].exec;
         RmiCpu[0][CPU_SUPER] += CpuArray.SingleCpu[idx].super;
         RmiCpu[0][CPU_USER] += CpuArray.SingleCpu[idx].user;
         RmiCpu[0][CPU_IDLE] += CpuArray.SingleCpu[idx].idle;
      }
      RmiCpu[0][CPU_INTSTK] -= RmiCpu[0][CPU_IDLE];
   }
#else
   sysPlusRmiMemErrs = RmiUlong[0][MEM_ERRS];
#endif /* __VAX */

   RmiCpu[0][CPU_total] = RmiCpu[0][CPU_INTSTK] +
                          RmiCpu[0][CPU_MPSYNCH] +
                          RmiCpu[0][CPU_KERNEL] +
                          RmiCpu[0][CPU_EXEC] +
                          RmiCpu[0][CPU_SUPER] +
                          RmiCpu[0][CPU_USER] +
                          RmiCpu[0][CPU_IDLE];

   RmiCpu[0][CPU_int_mps] = RmiCpu[0][CPU_INTSTK] +
                            RmiCpu[0][CPU_MPSYNCH];

   RmiUlong[0][STATE_total] = RmiUlong[0][STATE_CEF] +
                              RmiUlong[0][STATE_COLPG] +
                              RmiUlong[0][STATE_COM] +
                              RmiUlong[0][STATE_COMO] +
                              RmiUlong[0][STATE_CUR] +
                              RmiUlong[0][STATE_FPG] +
                              RmiUlong[0][STATE_HIB] +
                              RmiUlong[0][STATE_HIBO] +
                              RmiUlong[0][STATE_LEF] +
                              RmiUlong[0][STATE_LEFO] +
                              RmiUlong[0][STATE_MWAIT] +
                              RmiUlong[0][STATE_SUSP] +
                              RmiUlong[0][STATE_SUSPO];

   if (rqptr == (REQUEST_STRUCT*)-1) return;

   /**********/
   /* report */
   /**********/

   FaoToEither (rqptr,
"CPU%     Kern  Exec  Supr  User  Idle  Othr\n\
         ~~~~  ~~~~  ~~~~  ~~~~  ~~~~  ~~~~\n");

   for (min = 0; min <= NUM_MINS; min++)
   {
      if (!RmiCpu[min+1][CPU_total]) break;

      if (min)
         sprintf (when, "%d min", min);
      else
         strcpy (when, "now");

      for (mode = CPU_KERNEL; mode <= CPU_int_mps; mode++)
      {
         if (RmiCpu[min+1][CPU_total])
            cpuTotal = (ulong)(RmiCpu[min][CPU_total] -
                               RmiCpu[min+1][CPU_total]);
         else
            cpuTotal = 0;

         if (RmiCpu[min+1][mode])
            modeTotal = (ulong)(RmiCpu[min][mode] -
                                RmiCpu[min+1][mode]);
         else
            modeTotal = 0;

         PercentCpu[mode] = PercentOf (modeTotal, cpuTotal);
      }

      FaoToEither (rqptr,
"!6&>AZ    !3UL   !3UL   !3UL   !3UL   !3UL   !3UL\n",
                when,
                PercentCpu[CPU_KERNEL], PercentCpu[CPU_EXEC],
                PercentCpu[CPU_SUPER], PercentCpu[CPU_USER],
                PercentCpu[CPU_IDLE], PercentCpu[CPU_int_mps]);
   }

   FaoToEither (rqptr,
"\nStates    CEF COLPG   COM  COMO   CUR   FPG   HIB  HIBO   \
LEF  LEFO MWAIT resource  SUSP SUSPO Total\n\
        ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ \
~~~~~ ~~~~~ ~~~~~~~~~~~~~~ ~~~~~ ~~~~~ ~~~~~\n");

   for (min = 0; min <= NUM_MINS; min++)
   {
      if (!RmiUlong[min+1][STATE_total]) break;

      if (min)
         sprintf (when, "%d min", min);
      else
         strcpy (when, "now");

      FaoToEither (rqptr,
"!6&>AZ  !5UL !5UL !5UL !5UL !5UL !5UL !5UL !5UL \
!5UL !5UL !5UL !8XL !5UL !5UL !5UL\n",
                when,
                RmiUlong[min][STATE_CEF],
                RmiUlong[min][STATE_COLPG],
                RmiUlong[min][STATE_COM],
                RmiUlong[min][STATE_COMO],
                RmiUlong[min][STATE_CUR],
                RmiUlong[min][STATE_FPG],
                RmiUlong[min][STATE_HIB],
                RmiUlong[min][STATE_HIBO],
                RmiUlong[min][STATE_LEF],
                RmiUlong[min][STATE_LEFO],
                RmiUlong[min][STATE_MWAIT],
                RmiUlong[min][RESMASK],
                RmiUlong[min][STATE_SUSP],
                RmiUlong[min][STATE_SUSPO],
                RmiUlong[min][STATE_total]);
   }

   FaoToEither (rqptr, "\n");
}

/*****************************************************************************/
/*
Provide Mass Storage Control Protocol (MSCP) data.
*/

void sysPlusMscp (REQUEST_STRUCT *rqptr)

{
#ifdef __VAX
#define MSCP_ALL 1
#else
#define MSCP_ALL 0
#endif

   static BOOL  nope;
   static int  status;
   static ulong  MscpData [35];

   /* by minutes ([1]..) plus current read ([0]) */
   static ulong  MscpBufAvail [NUM_MINS+2],
                 MscpBufWait [NUM_MINS+2],
                 MscpOpCount [NUM_MINS+2],
                 MscpRead [NUM_MINS+2],
                 MscpSplitXfer [NUM_MINS+2],
                 MscpVcFail [NUM_MINS+2],
                 MscpWrite [NUM_MINS+2];

   static VMS_ITEM_LIST3  RmiItem [] =
   {
#if MSCP_ALL
       { sizeof(MscpData), RMI$_MSCP_ALL, &MscpData, 0 },
#else
       { sizeof(MscpData), RMI$_MSCP_EVERYTHING, &MscpData, 0 },
#endif
       {0,0,0,0}
   };

   int  min;
   ulong  ThisMscpBufAvail,
          ThisMscpBufWait,
          ThisMscpOpCount,
          ThisMscpRead,
          ThisMscpSplitXfer,
          ThisMscpVcFail,
          ThisMscpWrite;
   char  when [16];
   IO_SB  IOsb;

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

   if (nope)
   {
      FaoToEither (rqptr, "MSCP data not available !&S\n\n", status);
      return;
   }

   if (rqptr == (REQUEST_STRUCT*)-1)
   {
      /*******************/
      /* per-minute data */
      /*******************/

      /* move all values into one minute into the past */
      for (min = NUM_MINS+1; min > 0; min--)
      {
         MscpBufAvail[min] = MscpBufAvail[min-1];
         MscpOpCount[min] = MscpOpCount[min-1];
         MscpRead[min] = MscpRead[min-1];
         MscpWrite[min] = MscpWrite[min-1];
         MscpVcFail[min] = MscpVcFail[min-1];
         MscpSplitXfer[min] = MscpSplitXfer[min-1];
         MscpBufWait[min] = MscpBufWait[min-1];
      }
   }

   status = sys$getrmi (EfnWait, 0, 0, &RmiItem, &IOsb, 0, 0);
   if (VMSok (status)) sys$synch (EfnWait, &IOsb);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      FaoToEither (rqptr, "MSCP data not available !&S\n\n",  status);
      nope = true;
      return;
   }

#if MSCP_ALL
   MscpBufAvail[0] = 0;
   MscpOpCount[0] = MscpData[0];
   MscpVcFail[0] = 0;
   MscpRead[0] = MscpData[1];
   MscpWrite[0] = MscpData[2];
   MscpSplitXfer[0] = MscpData[3];
   MscpBufWait[0] = MscpData[4];
#else
   MscpBufAvail[0] = MscpData[0];
   MscpOpCount[0] = MscpData[21];
   MscpVcFail[0] = MscpData[22];
   MscpRead[0] = MscpData[23];
   MscpWrite[0] = MscpData[24];
   MscpSplitXfer[0] = MscpData[25];
   MscpBufWait[0] = MscpData[26];
#endif

   if (rqptr == (REQUEST_STRUCT*)-1) return;

   /***********/
   /* report */
   /**********/

#if MSCP_ALL
   FaoToEither (rqptr, "\
MSCP          Op Cnt          Read         Write  \
       Split          Wait\n\
        ~~~~~~~~~~~~  ~~~~~~~~~~~~  ~~~~~~~~~~~~  \
~~~~~~~~~~~~  ~~~~~~~~~~~~\n");
#else
   FaoToEither (rqptr, "\
MSCP         Buf Avl        Op Cnt          Read         Write  \
     VC Fail         Split          Wait\n\
        ~~~~~~~~~~~~  ~~~~~~~~~~~~  ~~~~~~~~~~~~  ~~~~~~~~~~~~  \
~~~~~~~~~~~~  ~~~~~~~~~~~~  ~~~~~~~~~~~~\n");
#endif

   for (min = 0; min <= NUM_MINS; min++)
   {
      if (min)
      {
         if (!MscpOpCount[min+1]) continue;
         ThisMscpBufAvail = MscpBufAvail[min];
         ThisMscpOpCount = MscpOpCount[min] - MscpOpCount[min+1];
         ThisMscpRead = MscpRead[min] - MscpRead[min+1];
         ThisMscpWrite = MscpWrite[min] - MscpWrite[min+1];
         ThisMscpVcFail = MscpVcFail[min] - MscpVcFail[min+1];
         ThisMscpSplitXfer = MscpSplitXfer[min] - MscpSplitXfer[min+1];
         ThisMscpBufWait = MscpBufWait[min] - MscpBufWait[min+1];
         sprintf (when, "%d min", min);
      }
      else
      {
         ThisMscpBufAvail = MscpBufAvail[0];
         ThisMscpOpCount = MscpOpCount[0];
         ThisMscpRead = MscpRead[0];
         ThisMscpWrite = MscpWrite[0];
         ThisMscpVcFail = MscpVcFail[0];
         ThisMscpSplitXfer = MscpSplitXfer[0];
         ThisMscpBufWait = MscpBufWait[0];
         strcpy (when, "total");
      }

#if MSCP_ALL
      FaoToEither (rqptr,
"!6&>AZ  !12UL  !12UL  !12UL  !12UL  !12UL\n",
                   when,
                   ThisMscpOpCount,
                   ThisMscpRead, ThisMscpWrite,
                   ThisMscpSplitXfer, ThisMscpBufWait);
#else
      FaoToEither (rqptr,
"!6&>AZ  !12UL  !12UL  !12UL  !12UL  !12UL  !12UL  !12UL\n",
                   when,
                   ThisMscpBufAvail, ThisMscpOpCount,
                   ThisMscpRead, ThisMscpWrite,
                   ThisMscpVcFail, ThisMscpSplitXfer, ThisMscpBufWait);
#endif
   }

   FaoToEither (rqptr, "\n");
}

/*****************************************************************************/
/*
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 WASD_SYSPLUS_NI and the frame-rate
and byte-rate and soft/hard error-rate statistics are accumlated to represent
total system data.

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

void sysPlusNet (REQUEST_STRUCT *rqptr)

{
#define NI_DEVICE_MAX 16
#define NI_DEVNAM_MAX 31
/* the ACCUM index is used to accumulate the current device counters */
#define ACCUM (NUM_MINS+2)

   static int  NetIntCount,
               IoClrCount;
   static ushort  NetIntChan [NI_DEVICE_MAX];
   static ulong  DcSCOM = 32,  /* DC$_SCOM */
                 DtNI = 13;    /* DT$_NI */
   static ulong  NetIntBlocksRx   [NI_DEVICE_MAX][NUM_MINS+3][2],
                 NetIntBlocksTx   [NI_DEVICE_MAX][NUM_MINS+3][2],
                 NetIntBytesRx    [NI_DEVICE_MAX][NUM_MINS+3][2],
                 NetIntBytesTx    [NI_DEVICE_MAX][NUM_MINS+3][2],
                 NetIntErrorsHard [NI_DEVICE_MAX][NUM_MINS+3][2],
                 NetIntErrorsSoft [NI_DEVICE_MAX][NUM_MINS+3][2];
   static long  DeviceCount = -1,
                DviUnit,
                LnmIndex = -1,
                LnmAttributes;
   static ushort  LogValueLen;
   static char  under [] = "~~~~~~~~~~~~~~~~~~~~";
   static char  LogValue [64];
   static char  NetIntDevName [NI_DEVICE_MAX][NI_DEVNAM_MAX+1];
   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, &LogValueLen },
      { 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, "WASD_SYSPLUS_NI");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");

   int  ecnt, min, status;
   ushort  pword,
           retlen;
   ulong  LnmCount;
   ulong  value [2],
          BlocksRx [2],
          BlocksTx [2],
          BytesRx [2],
          BytesTx [2],
          DevScanContext [2],
          ErrorsHard [2],
          ErrorsSoft [2];
   char  *vptr;
   char  byrx [16],
         bytx [16],
         when [16],
         DevScanBuffer [64],
         CountBuffer [512];
   IO_SB  IOsb;
   $DESCRIPTOR (CountDsc, CountBuffer);
   $DESCRIPTOR (DevScanDsc, DevScanBuffer);

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "sysPlusNet()");

   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[LogValueLen] = '\0';

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

         status = sys$assign (&NetIntDsc, &NetIntChan[LnmIndex], 0, 0);
         if (VMSnok (status))
         {
            FaoToEither (rqptr, "$ASSIGN() !AZ !&S\n", LogValue, status);
            continue;
         }

         strncpy (NetIntDevName[NetIntCount], LogValue, NI_DEVNAM_MAX);
         NetIntDevName[NetIntCount][NI_DEVNAM_MAX] = '\0';
         NetIntCount++;
      }
   }

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

      /* 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;
            if (VMSnok (status))
               FaoToEither (rqptr, "$DEVICE_SCAN() !&S\n", status);
         }

         status = sys$getdviw (0, 0, &DevScanDsc, &GetDviItems, &IOsb, 
                               0, 0, 0, 0);
         if (VMSok (status)) status = IOsb.Status;
         if (VMSnok (status)) FaoToEither (rqptr, "$GETDVIW() !&S\n", status);

         /* if not a template device then continue */
         if (DviUnit != 0) continue;

         DevScanBuffer[retlen] = '\0';

         /* RMDRIVER gives %SYSTEM-F-SYSVERDIF */
         if (!strcmp (DevScanBuffer, "_RMA0:")) continue;

         status = sys$assign (&DevScanDsc, &NetIntChan[DeviceCount++], 0, 0);
         if (VMSnok (status)) FaoToEither (rqptr, "$ASSIGN() !&S\n", status);

         strncpy (NetIntDevName[NetIntCount], DevScanBuffer+1, NI_DEVNAM_MAX);
         NetIntDevName[NetIntCount][NI_DEVNAM_MAX] = '\0';
         NetIntCount++;
      }
   }

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

   if (rqptr == (REQUEST_STRUCT*)-1)
   {
      /* once a minute store previous minute's accumulated data */
      for (ecnt = 0; ecnt < NetIntCount; ecnt++)
      {
         for (min = NUM_MINS+1; min > 0; min--)
         {
            PUT_QUAD_QUAD (&NetIntBlocksRx[ecnt][min-1],
                           &NetIntBlocksRx[ecnt][min]);
            PUT_QUAD_QUAD (&NetIntBlocksTx[ecnt][min-1],
                           &NetIntBlocksTx[ecnt][min]);
            PUT_QUAD_QUAD (&NetIntBytesRx[ecnt][min-1],
                           &NetIntBytesRx[ecnt][min]);
            PUT_QUAD_QUAD (&NetIntBytesTx[ecnt][min-1],
                           &NetIntBytesTx[ecnt][min]);
            PUT_QUAD_QUAD (&NetIntErrorsHard[ecnt][min-1],
                           &NetIntErrorsHard[ecnt][min]);
            PUT_QUAD_QUAD (&NetIntErrorsSoft[ecnt][min-1],
                           &NetIntErrorsSoft[ecnt][min]);
         }
      }
   }

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

   IoClrCount = 0;

   /* accumulate counts from each device */
   for (ecnt = 0; ecnt < NetIntCount; ecnt++)
   {
      PUT_ZERO_QUAD (&NetIntBlocksRx[ecnt][ACCUM]);
      PUT_ZERO_QUAD (&NetIntBlocksTx[ecnt][ACCUM]);
      PUT_ZERO_QUAD (&NetIntBytesRx[ecnt][ACCUM]);
      PUT_ZERO_QUAD (&NetIntBytesTx[ecnt][ACCUM]);
      PUT_ZERO_QUAD (&NetIntErrorsHard[ecnt][ACCUM]);
      PUT_ZERO_QUAD (&NetIntErrorsSoft[ecnt][ACCUM]);
      memset (CountBuffer, 0, sizeof(CountBuffer));

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

      if (VMSok (status)) status = IOsb.Status;
      if (VMSnok (status)) FaoToEither (rqptr, "$QIOW() !AZ !&S\n",
                                        NetIntDevName[ecnt], status);

      vptr = CountBuffer;
      while (vptr < CountBuffer + IOsb.Count)
      {
         pword = *(ushort*)vptr;
         vptr += 2;

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

         /* MSB should be set! */
         if (!(pword & 0x8000)) break;

         PUT_ZERO_QUAD (&value);
         if ((pword & 0x0fff) < 200)
         {
            /* quadword value (item number less than 200) */
            PUT_QUAD_QUAD (vptr, &value);
            vptr += 8;
         }
         else
         if ((pword & 0x6000) == 0x6000)
         {
            /* longword value */
            value[0] = *(ulong*)vptr;
            vptr += 4;
         } 
         else
         if ((pword & 0x6000) == 0x4000)
         {
            /* word value */
            value[0] = *(ushort*)vptr;
            vptr += 2;
         } 
         else
         if ((pword & 0x6000) == 0x2000)
         {
            /* byte value */
            value[0] = *(uchar*)vptr;
            vptr++;
         } 

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

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

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

            case 2 :
            case 6 :
                 ADD_QUAD_QUAD (&value, &NetIntBytesRx[ecnt][ACCUM]);
                 break;

            case 3 :
            case 7 :
                 ADD_QUAD_QUAD (&value, &NetIntBytesTx[ecnt][ACCUM]);
                 break;

            case 4 :
            case 8 :
                 ADD_QUAD_QUAD (&value, &NetIntBlocksRx[ecnt][ACCUM]);
                 break;

            case 5 :
            case 9 :
                 ADD_QUAD_QUAD (&value, &NetIntBlocksTx[ecnt][ACCUM]);
                 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 :
                 ADD_QUAD_QUAD (&value, &NetIntErrorsSoft[ecnt][ACCUM]);
                 break;

            case 25 :
            case 26 :
            case 33 :
            case 34 :
                 ADD_QUAD_QUAD (&value, &NetIntErrorsHard[ecnt][ACCUM]);
                 break;

            /* more "traditional" counters */

            case 1000 /* NMA$C_CTLIN_BRC bytes received */ :
            case 1002 /* NMA$C_CTLIN_MBY multicast bytes received */ :
                 ADD_QUAD_QUAD (&value, &NetIntBytesRx[ecnt][ACCUM]);
                 break;

            case 1001 /* NMA$C_CTLIN_BSN bytes sent */ :
                 ADD_QUAD_QUAD (&value, &NetIntBytesTx[ecnt][ACCUM]);
                 break;

            case 1010 /* NMA$C_CTLIN_DBR data blocks received */ :
            case 1012 /* NMA$C_CTLIN_MBL multicast blocks received */ :
                 ADD_QUAD_QUAD (&value, &NetIntBlocksRx[ecnt][ACCUM]);
                 break;

            case 1011 /* NMA$C_CTLIN_DBS data blocks sent */ :
                 ADD_QUAD_QUAD (&value, &NetIntBlocksTx[ecnt][ACCUM]);
                 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 */ :

                 ADD_QUAD_QUAD (&value, &NetIntErrorsSoft[ecnt][ACCUM]);
                 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 */ :

                 ADD_QUAD_QUAD (&value, &NetIntErrorsHard[ecnt][ACCUM]);
                 break;

            default :
         }
      }

      PUT_QUAD_QUAD (&NetIntBlocksRx[ecnt][ACCUM],
                     &NetIntBlocksRx[ecnt][0]);
      PUT_QUAD_QUAD (&NetIntBlocksTx[ecnt][ACCUM],
                     &NetIntBlocksTx[ecnt][0]);
      PUT_QUAD_QUAD (&NetIntBytesRx[ecnt][ACCUM],
                     &NetIntBytesRx[ecnt][0]);
      PUT_QUAD_QUAD (&NetIntBytesTx[ecnt][ACCUM],
                     &NetIntBytesTx[ecnt][0]);
      PUT_QUAD_QUAD (&NetIntErrorsHard[ecnt][ACCUM],
                     &NetIntErrorsHard[ecnt][0]);
      PUT_QUAD_QUAD (&NetIntErrorsSoft[ecnt][ACCUM],
                     &NetIntErrorsSoft[ecnt][0]);

#ifdef __VAX
      /* 32 bit accumulators tend to fill rather rapidly */
      if (NetIntBytesRx[ecnt][0] == 0xffffffff ||
          NetIntBytesTx[ecnt][0] == 0xffffffff)
         IoClrCount = IO$M_CLR_COUNT;
#endif
   }

   if (rqptr == (REQUEST_STRUCT*)-1) return;

   /**********/
   /* report */
   /**********/

   for (ecnt = 0; ecnt < NetIntCount; ecnt++)
   {
      status = FaoToEither (rqptr,
"!AZ\
!8AZ!13&>AZ  !13&>AZ  !18&>AZ !8&>AZ  !18&>AZ !8&>AZ  !6&>AZ  !6&>AZ\n\
!6AZ  !13AZ  !13AZ  !18AZ !8AZ  !18AZ !8AZ  !6AZ  !6AZ\n",
               ecnt ? "\n" : "", NetIntDevName[ecnt],
"Packet Rx", "Packet Tx", "Bytes Rx", "", "Bytes Tx", "", "Hard", "Soft",
"", under, under, under, "", under, "", under, under);

      if (VMSnok (status)) FaoToEither (rqptr, "$FAO() !&S\n", status);

      for (min = 0; min <= NUM_MINS; min++)
      {
         if (min)
         {
            if (QUAD_ZERO(&NetIntBlocksRx[ecnt][min+1])) continue;

            MINUS_QUAD_QUAD (&NetIntBlocksRx[ecnt][min+1],
                             &NetIntBlocksRx[ecnt][min],
                             &BlocksRx);
            MINUS_QUAD_QUAD (&NetIntBlocksTx[ecnt][min+1],
                             &NetIntBlocksTx[ecnt][min],
                             &BlocksTx);
            MINUS_QUAD_QUAD (&NetIntBytesRx[ecnt][min+1],
                             &NetIntBytesRx[ecnt][min],
                             &BytesRx);
            MINUS_QUAD_QUAD (&NetIntBytesTx[ecnt][min+1],
                             &NetIntBytesTx[ecnt][min],
                             &BytesTx);
            MINUS_QUAD_QUAD (&NetIntErrorsHard[ecnt][min+1],
                             &NetIntErrorsHard[ecnt][min],
                             &ErrorsHard);
            MINUS_QUAD_QUAD (&NetIntErrorsSoft[ecnt][min+1],
                             &NetIntErrorsSoft[ecnt][min],
                             &ErrorsSoft);
            sprintf (when, "%d min", min);
         }
         else
         {
            PUT_QUAD_QUAD (&NetIntBlocksRx[ecnt][ACCUM], &BlocksRx);
            PUT_QUAD_QUAD (&NetIntBlocksTx[ecnt][ACCUM], &BlocksTx);
            PUT_QUAD_QUAD (&NetIntBytesRx[ecnt][ACCUM], &BytesRx);
            PUT_QUAD_QUAD (&NetIntBytesTx[ecnt][ACCUM], &BytesTx);
            PUT_QUAD_QUAD (&NetIntErrorsHard[ecnt][ACCUM], &ErrorsHard);
            PUT_QUAD_QUAD (&NetIntErrorsSoft[ecnt][ACCUM], &ErrorsSoft);
            strcpy (when, "total");
         }

         status = FaoToEither (rqptr,
"!6&>AZ  !13@SQ  !13@SQ  !18@SQ !8AZ  !18@SQ !8AZ  !6@SQ  !6@SQ\n",
                               when,
                               &BlocksRx, &BlocksTx,
                               &BytesRx, sysPlusNetVal(&BytesRx,byrx),
                               &BytesTx, sysPlusNetVal(&BytesTx,bytx),
                               &ErrorsHard, &ErrorsSoft);
      }
   }

   FaoToEither (rqptr, "\n");
}

/*****************************************************************************/
/*
Quadword accumulator to real number Tbytes, Gbytes, Mbytes or kbytes.
*/

char* sysPlusNetVal
(
ulong *quad,
char *buf
)
{
#define AT_ONCE_MAX 2

   char  tmp [32];
   float  qfloat;

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

   qfloat = FLOAT_FROM_QUAD (quad);
   if (qfloat >= 1024.0 * 1024.0 * 1024.0 * 1024.0)
      sprintf (tmp, "%3.2fTB", qfloat / (1024.0 * 1024.0 * 1024.0 * 1024.0));
   else
   if (qfloat >= 1024.0 * 1024.0 * 1024.0)
      sprintf (tmp, "%3.2fGB", qfloat / (1024.0 * 1024.0 * 1024.0));
   else
   if (qfloat >= 1024.0 * 1024.0)
      sprintf (tmp, "%3.2fMB", qfloat / (1024.0 * 1024.0));
   else
   if (qfloat >= 1024.0)
      sprintf (tmp, "%3.2fkB", qfloat / 1024.0);
   else
      tmp[0] = '\0';
   /* right justify */
   sprintf (buf, "%8.8s", tmp);
   return (buf);
}

/*****************************************************************************/
/*
Provide cluster resource consumption data.  This called with |rqptr| non-NULL
to report the data.  It is also called by HttpdTick() once a minute to update
the per-minute and buffered data.
*/

void sysPlusScs (REQUEST_STRUCT *rqptr)

{
   static ulong  ScsRead [1+(SCS_NODES_MAX*14)];
   static ulong  ScsData [NUM_MINS+2][SCS_NODES_MAX][14];
   static VMS_ITEM_LIST3  RmiItem [] =
   {
      { sizeof(ScsRead), RMI$_SCS, &ScsRead, 0 },
      {0,0,0,0}
   };

   /* hmmm; on VAX V7.3 this gets clobbered if placed before ScsRead[] !! */
   static int  status;

   int  cnt, min, node;
   ulong DgramDisc,
         DgramRcvd,
         DgramSent,
         KbyteMap,
         KbyteSent,
         KbyteRcvd,
         MsgSent,
         MsgRcvd,
         QcrCnt,
         QbdtCnt,
         ReqDats,
         SendDats;
   ulong  *data;
   char *cptr, *sptr;
   char  name [16],
         when [16];
   IO_SB  IOsb;

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

   if (status)
   {
      FaoToEither (rqptr, "SCS data not available !&S\n\n", status);
      return;
   }

   if (rqptr == (REQUEST_STRUCT*)-1)
   {
      /*******************/
      /* per-minute data */
      /*******************/

      /* move all values into one minute into the past */
      for (min = NUM_MINS+1; min > 0; min--)
         /* first node is always this one */
         for (node = 1; node < SCS_NODES_MAX; node++)
            if (ScsData[min-1][node][0])
               memcpy (&ScsData[min][node],
                       &ScsData[min-1][node],
                       sizeof(ScsData[0][0]));
   }

#if WASD_GETSPI
   status = exe$getspi (EfnWait, 0, 0, &RmiItem, &IOsb, 0, 0);
#else
   status = sys$getrmi (EfnWait, 0, 0, &RmiItem, &IOsb, 0, 0);
#endif
   if (VMSok (status)) sys$synch (EfnWait, &IOsb);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      FaoToEither (rqptr, "SCS data not available !&S\n\n",  status);
      return;
   }
   status = 0;

   memcpy (&ScsData[0], &ScsRead[1], sizeof(ScsData[0]));

   if (rqptr == (REQUEST_STRUCT*)-1) return;

   /**********/
   /* report */
   /**********/

   FaoToEither (rqptr, "\
SCS    Node    dTx dRx dDp       kB Map        kB Rx        kB Tx \
       Msg Rx        Msg Tx   Qbdt     Qcr    Req Dats    Snd Dats\n\
       ~~~~~~~ ~~~ ~~~ ~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~ \
~~~~~~~~~~~~~ ~~~~~~~~~~~~~ ~~~~~~ ~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~\n");

   for (min = 0; min <= NUM_MINS; min++)
   {
      if (min)
         sprintf (when, "%d min", min);
      else
         strcpy (when, "total");

      /* first node is always this one */
      for (node = 1; node < SCS_NODES_MAX; node++)
      {
         /* node name is a counted string */
         cptr = (char*)&ScsData[min][node][0];
         cnt = *cptr++ & 0x7;
         /* zero-length node name */
         if (!cnt) continue;
         for (sptr = name; cnt--; *sptr++ = *cptr++);
         *sptr = '\0';
         
         DgramSent = ScsData[min][node][2];
         DgramRcvd = ScsData[min][node][3];
         DgramDisc = ScsData[min][node][4];
         MsgSent   = ScsData[min][node][5];
         MsgRcvd   = ScsData[min][node][6];
         SendDats  = ScsData[min][node][7];
         KbyteSent = ScsData[min][node][8];
         ReqDats   = ScsData[min][node][9];
         KbyteRcvd = ScsData[min][node][10];
         KbyteMap  = ScsData[min][node][11];
         QcrCnt    = ScsData[min][node][12];
         QbdtCnt   = ScsData[min][node][13];

         if (min)
         {
            /* if no previous minute data */
            if (!ScsData[min+1][node][0]) continue;
            /* otherwise delta with previous minute */
            DgramSent -= ScsData[min+1][node][2];
            DgramRcvd -= ScsData[min+1][node][3];
            DgramDisc -= ScsData[min+1][node][4];
            MsgSent   -= ScsData[min+1][node][5];
            MsgRcvd   -= ScsData[min+1][node][6];  
            SendDats  -= ScsData[min+1][node][7];  
            KbyteSent -= ScsData[min+1][node][8];
            ReqDats   -= ScsData[min+1][node][9];  
            KbyteRcvd -= ScsData[min+1][node][10];
            KbyteMap  -= ScsData[min+1][node][11];
            QcrCnt    -= ScsData[min+1][node][12];  
            QbdtCnt   -= ScsData[min+1][node][13];  
         }

         FaoToEither (rqptr,
"!6&>AZ !7AZ !3UL !3UL !3UL !12UL !12UL !12UL !13UL !13UL \
!6UL !7UL !11UL !11UL\n",
                      when, name,
                      DgramSent, DgramRcvd, DgramDisc,
                      KbyteMap, KbyteSent, KbyteRcvd,
                      MsgSent, MsgRcvd,
                      QbdtCnt, QcrCnt,
                      ReqDats, SendDats);  
          when[0] = '\0';
      }
   }

   FaoToEither (rqptr, "\n");
}

/*****************************************************************************/
/*
Supports sysPlusPlus().  Provide system data.
*/

void sysPlusSyi (REQUEST_STRUCT *rqptr)

{
   static ushort  SyiClusterEVotes,
                  SyiClusterQuorum,
                  SyiNodeVotes,
                  SyiClusterVotes,
                  SyiNodeQuorum;

   static ulong  SyiClusterNodes,
                 SyiContigGblPages,
                 SyiFreeGblPages,
                 SyiMaxProcessCnt,
                 SyiPageFileFree,
                 SyiPageFilePage,
                 SyiSwapFileFree,
                 SyiSwapFilePage;

   static ulong  SyiBalSlots [4],
                 SyiMainMemory [4],
                 SyiNPagedPool [4],
                 SyiNPageVir [4],
                 SyiPagedPool [4],
                 SyiProcSlots [4];

   static VMS_ITEM_LIST3  SyiItem [] =
   {
     { sizeof(SyiBalSlots), SYI$_BAL_SLOTS, &SyiBalSlots, 0 },
     { sizeof(SyiContigGblPages), SYI$_CONTIG_GBLPAGES, &SyiContigGblPages, 0 },
     { sizeof(SyiClusterEVotes), SYI$_CLUSTER_EVOTES, &SyiClusterEVotes, 0 },
     { sizeof(SyiClusterNodes), SYI$_CLUSTER_NODES, &SyiClusterNodes, 0 },
     { sizeof(SyiClusterQuorum), SYI$_CLUSTER_QUORUM, &SyiClusterQuorum, 0 },
     { sizeof(SyiClusterVotes), SYI$_CLUSTER_VOTES, &SyiClusterVotes, 0 },
     { sizeof(SyiFreeGblPages), SYI$_FREE_GBLPAGES, &SyiFreeGblPages, 0 },
     { sizeof(SyiMaxProcessCnt), SYI$_MAXPROCESSCNT, &SyiMaxProcessCnt, 0 },
     { sizeof(SyiMainMemory), SYI$_MAIN_MEMORY, &SyiMainMemory, 0 },
     { sizeof(SyiNodeVotes), SYI$_NODE_VOTES, &SyiNodeVotes, 0 },
     { sizeof(SyiNPagedPool), SYI$_NPAGED_POOL, &SyiNPagedPool, 0 },
     { sizeof(SyiNPageVir), SYI$_NPAGEVIR, &SyiNPageVir, 0 },
     { sizeof(SyiNodeQuorum), SYI$_NODE_QUORUM, &SyiNodeQuorum, 0 },
     { sizeof(SyiPagedPool), SYI$_PAGED_POOL, &SyiPagedPool, 0 },
     { sizeof(SyiPageFileFree), SYI$_PAGEFILE_FREE, &SyiPageFileFree, 0 },
     { sizeof(SyiPageFilePage), SYI$_PAGEFILE_PAGE, &SyiPageFilePage, 0 },
     { sizeof(SyiProcSlots), SYI$_PROC_SLOTS, &SyiProcSlots, 0 },
     { sizeof(SyiSwapFileFree), SYI$_SWAPFILE_FREE, &SyiSwapFileFree, 0 },
     { sizeof(SyiSwapFilePage), SYI$_SWAPFILE_PAGE, &SyiSwapFilePage, 0 },
     { 0,0,0,0 }
   };

   static double  factor, fGB, fMB, fkB, fPS = 0.0;

   int  status;
   char  *cptr, *mmptr, *npptr, *pptr;
   char  MainMemErrBuf [32],
         PageFileBuf [256],
         SwapFileBuf [256];
   char  MainMemBuf [4][32],
         NonPagedPoolBuf [4][32],
         PagedPoolBuf [4][32];
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "sysPlusSyi()");

   status = sys$getsyiw (EfnWait, 0, 0, &SyiItem, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      FaoToEither (rqptr, "$GETSYI() !&S\n",  status);
      return;
   }

   if (fGB < 0.1)
   {
      fPS = (double)SysInfo.PageSize;
      fGB = 1073741824.0;
      fMB = 1048576.0;
      fkB = 1024.0;
   }

   if ((double)SyiMainMemory[0] * fPS / fGB < 1.0)
   {
      mmptr = "MB";
      factor = fMB;
   }
   else
   {
      mmptr = "GB";
      factor = fGB;
   }

   sprintf (MainMemBuf[0], "%6.2f", (double)SyiMainMemory[0] * fPS / factor);
   sprintf (MainMemBuf[1], "%6.2f", (double)SyiMainMemory[1] * fPS / factor);
   sprintf (MainMemBuf[2], "%6.2f", (double)SyiMainMemory[2] * fPS / factor);
   sprintf (MainMemBuf[3], "%6.2f", (double)SyiMainMemory[3] * fPS / factor);

   if ((double)SyiNPagedPool[0] / fGB < 1.0)
   {
      npptr = "MB";
      factor = fMB;
   }
   else
   {
      npptr = "GB";
      factor = fGB;
   }

   sprintf (NonPagedPoolBuf[0], "%6.2f", (double)SyiNPagedPool[0] / factor);
   sprintf (NonPagedPoolBuf[1], "%6.2f", (double)SyiNPagedPool[1] / factor);
   sprintf (NonPagedPoolBuf[2], "%6.2f", (double)SyiNPagedPool[2] / factor);
   sprintf (NonPagedPoolBuf[3], "%6.2f", (double)SyiNPagedPool[3] / factor);

   if ((double)SyiPagedPool[0] / fGB < 1.0)
   {
      pptr = "MB";
      factor = fMB;
   }
   else
   {
      pptr = "GB";
      factor = fGB;
   }

   sprintf (PagedPoolBuf[0], "%6.2f", (double)SyiPagedPool[0] / factor);
   sprintf (PagedPoolBuf[1], "%6.2f", (double)SyiPagedPool[1] / factor);
   sprintf (PagedPoolBuf[2], "%6.2f", (double)SyiPagedPool[2] / factor);
   sprintf (PagedPoolBuf[3], "%6.2f", (double)SyiPagedPool[3] / factor);

   if (SyiSwapFilePage)
   {
      if ((double)SyiSwapFilePage * fPS / fGB < 1.0)
      {
         cptr = "MB";
         factor = fMB;
      }
      else
      {
         cptr = "GB";
         factor = fGB;
      }
      sprintf (SwapFileBuf,
"Swap File Usage (bytes):                        Free        Size      In use\n\
                                    (%s)      %6.2f      %6.2f        %3d%%\n\n",
               cptr,
               (double)SyiSwapFileFree * fPS / factor,
               (double)SyiSwapFilePage * fPS / factor,
               PercentOf (SyiSwapFilePage - SyiSwapFileFree, SyiSwapFilePage));
   }
   else
      SwapFileBuf[0] = '\0';

   if (SyiPageFilePage)
   {
      if ((double)SyiPageFilePage * fPS / fGB < 1.0)
      {
         cptr = "MB";
         factor = fMB;
      }
      else
      {
         cptr = "GB";
         factor = fGB;
      }
      sprintf (PageFileBuf,
"Paging File Usage (bytes):                      Free        Size      In use\n\
                                    (%s)      %6.2f      %6.2f        %3d%%\n\n",
               cptr,
               (double)SyiPageFileFree * fPS / factor,
               (double)SyiPageFilePage * fPS / factor,
               PercentOf (SyiPageFilePage - SyiPageFileFree, SyiPageFilePage));
   }
   else
      PageFileBuf[0] = '\0';

#ifdef __VAX
   MainMemErrBuf[0] = '\0';
#else /* __VAX */ 
   sprintf (MainMemErrBuf, "  Errors: %u\n", sysPlusRmiMemErrs);
#endif /* __VAX */ 

   status = FaoToEither (rqptr,
"Physical Memory Usage (bytes):     Total        Free      In Use    Modified\n\
  Main Memory             (!AZ)    !9AZ   !9AZ   !9AZ   !9AZ\n\
!AZ\
\n\
Slot Usage (slots):                Total        Free    Resident     Swapped\n\
  Process Entry Slots               !4UL        !4UL        !4UL        !4UL\n\
  Balance Set Slots                 !4UL        !4UL        !4UL        !4UL\n\
\n\
Dynamic Memory Usage:              Total        Free      In Use     Largest\n\
  Nonpaged Dynamic Memory (!AZ)    !9AZ   !9AZ   !9AZ   !9AZ\n\
  Paged Dynamic Memory    (!AZ)    !9AZ   !9AZ   !9AZ   !9AZ\n\
\n\
!AZ\
!AZ\
Cluster      Nodes    Member  Expected     Votes    Quorum\n"
#if WASD_GETSPI
"(SPI)"
#else
"(RMI)"
#endif
"         !4UL      !4UL      !4UL      !4UL      !4UL\n\
\n",
      mmptr,
      MainMemBuf[0], MainMemBuf[1], MainMemBuf[2], MainMemBuf[3],
      MainMemErrBuf,
      SyiProcSlots[0], SyiProcSlots[1], SyiProcSlots[2], SyiProcSlots[3],
      SyiBalSlots[0], SyiBalSlots[1], SyiBalSlots[2], SyiBalSlots[3],
      npptr,
      NonPagedPoolBuf[0], NonPagedPoolBuf[1],
         NonPagedPoolBuf[2], NonPagedPoolBuf[3],
      pptr,
      PagedPoolBuf[0], PagedPoolBuf[1], PagedPoolBuf[2], PagedPoolBuf[3],
      SwapFileBuf, PageFileBuf,
      SyiClusterNodes, SysInfo.ClusterMember, SyiClusterEVotes,
      SyiClusterVotes, SyiClusterQuorum);

   if (VMSnok (status)) FaoToEither (rqptr, "$FAO() !&S\n",  status);
}

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

