/*****************************************************************************/
/*
                                  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  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 !AZ running VMS !AZ");

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

   static int64  SyiBootTime64,
                 Time64,
                 UpTime64;

   static VMS_ITEM_LIST3  SyiItem [] =
   {
     { sizeof(SyiBootTime64), SYI$_BOOTTIME, &SyiBootTime64, 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 (&Time64);
   UpTime64 = SyiBootTime64 - Time64;

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

   status = FaoToEither (rqptr, &UnderFao, SystemBuffer, slen,
                         &Time64, &SyiBootTime64, &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))
      { 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  size, status;
   uint64  context64;
   ushort  slen;
   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);

   context64 = 0;

   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';
      size = sizeof(DevStsBuf);

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

      size = sizeof(DevCharBuf);
      strzcpy (DevCharBuf, DviDevTypeName, size);
      strzcat (DevCharBuf, ":", size);

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

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

      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)
      {
         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, size, 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';
      size = sizeof(ProcFlags);

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

      strzcat (ProcFlags, "\n         ", size);

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

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

      /* 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';
      strzcat (ProcFlags, AstFlags, size);

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

      strzcat (ProcFlags, "\n        ", size);

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

      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 - PercentOf32 (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

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

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

   static VMS_ITEM_LIST3  RmiItem [] =
   {
     { 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 },
     { 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]));
      }
   }

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

   sysPlusRmiMemErrs = RmiUlong[0][MEM_ERRS];

   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
         strzcpy (when, "now", sizeof(when));

      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] = PercentOf32 (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
         strzcpy (when, "now", sizeof(when));

      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)

{
#define MSCP_ALL 0

   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];
         strzcpy (when, "total", sizeof(when));
      }

#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 uint64  NetIntBlocksRx64   [NI_DEVICE_MAX][NUM_MINS+3],
                  NetIntBlocksTx64   [NI_DEVICE_MAX][NUM_MINS+3],
                  NetIntBytesRx64    [NI_DEVICE_MAX][NUM_MINS+3],
                  NetIntBytesTx64    [NI_DEVICE_MAX][NUM_MINS+3],
                  NetIntErrorsHard64 [NI_DEVICE_MAX][NUM_MINS+3],
                  NetIntErrorsSoft64 [NI_DEVICE_MAX][NUM_MINS+3];
   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;
   uint64  value64,
           BlocksRx64,
           BlocksTx64,
           BytesRx64,
           BytesTx64,
           DevScanContext64,
           ErrorsHard64,
           ErrorsSoft64;
   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 */
      DevScanContext64 = 0;
      while (NetIntCount < NI_DEVICE_MAX)
      {
         status = sys$device_scan (&DevScanDsc, &retlen, 0,
                                   &DevScanItems, &DevScanContext64);
         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--)
         {
            NetIntBlocksRx64[ecnt][min] = NetIntBlocksRx64[ecnt][min-1];
            NetIntBlocksTx64[ecnt][min] = NetIntBlocksTx64[ecnt][min-1];
            NetIntBytesRx64[ecnt][min] = NetIntBytesRx64[ecnt][min-1];
            NetIntBytesTx64[ecnt][min] = NetIntBytesTx64[ecnt][min-1];
            NetIntErrorsHard64[ecnt][min] = NetIntErrorsHard64[ecnt][min-1];
            NetIntErrorsSoft64[ecnt][min] = NetIntErrorsSoft64[ecnt][min-1];
         }
      }
   }

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

   IoClrCount = 0;

   /* accumulate counts from each device */
   for (ecnt = 0; ecnt < NetIntCount; ecnt++)
   {
      NetIntBlocksRx64[ecnt][ACCUM] = 0;
      NetIntBlocksTx64[ecnt][ACCUM] = 0;
      NetIntBytesRx64[ecnt][ACCUM] = 0;
      NetIntBytesTx64[ecnt][ACCUM] = 0;
      NetIntErrorsHard64[ecnt][ACCUM] = 0;
      NetIntErrorsSoft64[ecnt][ACCUM] = 0;
      memset (CountBuffer, 0, sizeof(CountBuffer));

      status = sys$qiow (0, NetIntChan[ecnt],
                         IO$_SENSEMODE | IO$M_RD_COUNT | IoClrCount |
#define IO$M_RD_64COUNT 0x4000
                         IO$M_RD_64COUNT | 
                         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 = *(USHORTPTR)vptr;
         vptr += 2;

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

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

         value64 = 0;
         if ((pword & 0x0fff) < 200)
         {
            /* quadword value (item number less than 200) */
            value64 = *(INT64PTR)vptr;
            vptr += 8;
         }
         else
         if ((pword & 0x6000) == 0x6000)
         {
            /* longword value */
            value64 = *(ULONGPTR)vptr;
            vptr += 4;
         } 
         else
         if ((pword & 0x6000) == 0x4000)
         {
            /* word value */
            value64 = *(USHORTPTR)vptr;
            vptr += 2;
         } 
         else
         if ((pword & 0x6000) == 0x2000)
         {
            /* byte value */
            value64 = *(uchar*)vptr;
            vptr++;
         } 

         if (pword & 0x1000)
         {
            /* hw map (?) */
            value64 = *(USHORTPTR)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 :
                 NetIntBytesRx64[ecnt][ACCUM] += value64;
                 break;

            case 3 :
            case 7 :
                 NetIntBytesTx64[ecnt][ACCUM] += value64;
                 break;

            case 4 :
            case 8 :
                 NetIntBlocksRx64[ecnt][ACCUM] += value64;
                 break;

            case 5 :
            case 9 :
                 NetIntBlocksTx64[ecnt][ACCUM] += value64;
                 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 :
                 NetIntErrorsSoft64[ecnt][ACCUM] += value64;
                 break;

            case 25 :
            case 26 :
            case 33 :
            case 34 :
                 NetIntErrorsHard64[ecnt][ACCUM] += value64;
                 break;

            /* more "traditional" counters */

            case 1000 /* NMA$C_CTLIN_BRC bytes received */ :
            case 1002 /* NMA$C_CTLIN_MBY multicast bytes received */ :
                 NetIntBytesRx64[ecnt][ACCUM] += value64;
                 break;

            case 1001 /* NMA$C_CTLIN_BSN bytes sent */ :
                 NetIntBytesTx64[ecnt][ACCUM] += value64;
                 break;

            case 1010 /* NMA$C_CTLIN_DBR data blocks received */ :
            case 1012 /* NMA$C_CTLIN_MBL multicast blocks received */ :
                 NetIntBlocksRx64[ecnt][ACCUM] += value64;
                 break;

            case 1011 /* NMA$C_CTLIN_DBS data blocks sent */ :
                 NetIntBlocksTx64[ecnt][ACCUM] += value64;
                 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 */ :

                 NetIntErrorsSoft64[ecnt][ACCUM] += value64;
                 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 */ :

                 NetIntErrorsHard64[ecnt][ACCUM] += value64;
                 break;

            default :
         }
      }

      NetIntBlocksRx64[ecnt][0] = NetIntBlocksRx64[ecnt][ACCUM];
      NetIntBlocksTx64[ecnt][0] = NetIntBlocksTx64[ecnt][ACCUM];
      NetIntBytesRx64[ecnt][0] = NetIntBytesRx64[ecnt][ACCUM];
      NetIntBytesTx64[ecnt][0] = NetIntBytesTx64[ecnt][ACCUM];
      NetIntErrorsHard64[ecnt][0] = NetIntErrorsHard64[ecnt][ACCUM];
      NetIntErrorsSoft64[ecnt][0] = NetIntErrorsSoft64[ecnt][ACCUM];
   }

   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 (!NetIntBlocksRx64[ecnt][min+1]) continue;

            BlocksRx64 = NetIntBlocksRx64[ecnt][min] -
                         NetIntBlocksRx64[ecnt][min+1];
            BlocksTx64 = NetIntBlocksTx64[ecnt][min] -
                         NetIntBlocksTx64[ecnt][min+1];
            BytesRx64 = NetIntBytesRx64[ecnt][min] -
                        NetIntBytesRx64[ecnt][min+1];
            BytesTx64 = NetIntBytesTx64[ecnt][min] -
                        NetIntBytesTx64[ecnt][min+1];
            ErrorsHard64 = NetIntErrorsHard64[ecnt][min] -
                           NetIntErrorsHard64[ecnt][min+1];
            ErrorsSoft64 = NetIntErrorsSoft64[ecnt][min] -
                           NetIntErrorsSoft64[ecnt][min+1];
            sprintf (when, "%d min", min);
         }
         else
         {
            BlocksRx64 = NetIntBlocksRx64[ecnt][ACCUM];
            BlocksTx64 = NetIntBlocksTx64[ecnt][ACCUM];
            BytesRx64 = NetIntBytesRx64[ecnt][ACCUM];
            BytesTx64 = NetIntBytesTx64[ecnt][ACCUM];
            ErrorsHard64 = NetIntErrorsHard64[ecnt][ACCUM];
            ErrorsSoft64 = NetIntErrorsSoft64[ecnt][ACCUM];
            strzcpy (when, "total", sizeof(when));
         }

         status = FaoToEither (rqptr,
"!6&>AZ  !13@UQ  !13@UQ  !18@UQ !8AZ  !18@UQ !8AZ  !6@UQ  !6@UQ\n",
                               when,
                               &BlocksRx64, &BlocksTx64,
                               &BytesRx64, sysPlusNetVal(BytesRx64,byrx),
                               &BytesTx64, sysPlusNetVal(BytesTx64,bytx),
                               &ErrorsHard64, &ErrorsSoft64);
      }
   }

   FaoToEither (rqptr, "\n");
}

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

char* sysPlusNetVal
(
int64 bytes64,
char *buf
)
{
#define AT_ONCE_MAX 2

   char  tmp [32];
   float  qfloat;

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

   qfloat = (float)bytes64;
   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
         strzcpy (when, "total", sizeof(when));

      /* 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,
               PercentOf32 (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,
               PercentOf32 (SyiPageFilePage - SyiPageFileFree, SyiPageFilePage));
   }
   else
      PageFileBuf[0] = '\0';

   sprintf (MainMemErrBuf, "  Errors: %u\n", sysPlusRmiMemErrs);

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

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

