/*****************************************************************************/ /* HyperSpi$agent.c This is the System Performance Information collection agent for the Hypertext SPI facility. It executes on selected nodes, writing selected system performance information into a data file, once every minute. The data contains per-minute average and total values (depending on the datum), and peak values based on the granularity of whatever is the collection period (currently 1 second). These data files are kept on a per-day basis. This data may then be processed and displayed by the Hypertext SPI facility. It utilizes the undocumented EXE$GETSPI interface for Alpha and VAX, the documented and supported (as from V7.3) $GETRMI interface for Itanium. Original idea and details plagiarised (and extensively reworked) from a VMS X Window System utility named SPI. BUILD DETAILS ------------- See BUILD_HYPERSPI$AGENT.COM procedure. COPYRIGHT --------- Copyright (C) 1995-2021 Mark G.Daniel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. VERSION HISTORY (update SOFTWAREVN as well!) --------------- 14-OCT-2014 MGD v2.1.0, bump version in line with HYPERSPI.C 21-MAY-2011 MGD v2.0.0, port to Itanium (changed the size of some data) add network interface data data format version 3 (shared with HyperSPI++) (There is a lot of work that *could* be done to this older piece of code but I've spent the minimal time necessary just to make it work on modern platforms.) 23-DEC-2003 MGD v1.1.1, minor conditional mods to support IA64 09-SEP-2001 MGD v1.1.0, provide PID logical name to allow $FORCEX 07-JUL-2001 MGD v1.0.4, remove 'register' from storage declarations 15-FEB-2001 MGD v1.0.3, bugfix; MBytes memory for systems >= 4096MB 28-OCT-2000 MGD v1.0.2, make adjustments for RELEAXED_ANSI compilation 10-AUG-2000 MGD v1.0.1, work around HPARITH exception with lib$cvtf() 13-AUG-1998 MGD NOTE: this application is not Y2K compliant (in the sense data is stored in files named with a 2 digit year!) 20-JUN-1995 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "2.1.0" #define SOFTWARENM "HYPERSPI$AGENT" #ifdef __ALPHA char SoftwareID [] = SOFTWARENM " AXP-" SOFTWAREVN; #endif #ifdef __ia64 char SoftwareID [] = SOFTWARENM " IA64-" SOFTWAREVN; #endif #ifdef __VAX char SoftwareID [] = SOFTWARENM " VAX-" SOFTWAREVN; #endif #ifdef __x86_64 char SoftwareID [] = SOFTWARENM " X86-" SOFTWAREVN; #endif /* standard C header files */ #include #include #include #include /* VMS-related header files */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* Alpha and VAX will use the EXE$SPI interface, Itanium will use $GETRMI */ #ifdef __ia64 #include #endif /* application header file */ #include "hyperspi.h" #include "spidef.h" #ifndef __VAX # pragma nomember_alignment #endif #define boolean int #define true 1 #define false 0 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define COLLECT_INTERVAL_SECONDS 1 #define RECORD_INTERVAL_SECONDS 60 #define CPU_MODE_COUNT HYPERSPI_CPU_MODE_COUNT #define NI_DEVICE_MAX 8 boolean Debug = 0, DebugSpi = 0; unsigned long ActiveCpuCnt, AvailCpuCnt, EfnTimer, EfnSpi, SyiMemSize, SyiPageSize; #ifdef __RMIDEF_LOADED unsigned __int64 TotalCpuTicks, TotalUserModeCpuTicks; unsigned __int64 TotalModeCpuTicks[CPU_MODE_COUNT]; #else unsigned int TotalCpuTicks, TotalUserModeCpuTicks; unsigned long TotalModeCpuTicks[CPU_MODE_COUNT]; #endif float FloatNumberOfCPUs, TotalDeltaSeconds; char SyiNodeName [16]; unsigned long CurrentBinTime [2], PreviousBinTime [2]; unsigned short CurrentNumTime [7]; unsigned long CollectSeconds [2] = { -10000000*COLLECT_INTERVAL_SECONDS, -1 }; float RecordSeconds = (float)(RECORD_INTERVAL_SECONDS-1); /* this structure declared in HyperSpi.h */ struct HyperSpiData SpiRecord; /* function prototypes */ void AgentPidLogicalAtExit (); int CollectNetInt(); int CollectSystemDynamic(); int CollectSystemStatic(); void DebugSpiRecord (); /*****************************************************************************/ /* */ int main () { int idx, status; unsigned short PrevMinute = 0; float ftmp; /*********/ /* begin */ /*********/ if (getenv ("HYPERSPI$DBUG") != NULL) Debug = true; fprintf(stdout, "%s\n", SoftwareID); AgentPidLogical (true); if (VMSnok (status = lib$get_ef (&EfnTimer))) exit (status); if (VMSnok (status = lib$get_ef (&EfnSpi))) exit (status); /* set the process priority sufficiently high to have an edge */ if (VMSnok (status = sys$setpri (0, 0, 6, 0, 0, 0))) exit (status); /* get system name, physical memory size */ if (VMSnok (CollectSystemStatic ())) exit (status); SpiRecord.SystemMemoryMBytes = (SyiMemSize * (SyiPageSize / 512)) / 2048; /* initialize the time in the data record, etc */ sys$gettim (&CurrentBinTime); sys$numtim (&CurrentNumTime, &CurrentBinTime); SpiRecord.Minute = PrevMinute = CurrentNumTime[4]; SpiRecord.Hour = CurrentNumTime[3]; SpiRecord.Day = CurrentNumTime[2]; SpiRecord.Month = CurrentNumTime[1]; SpiRecord.Year = CurrentNumTime[0]; /* initialise */ CollectSPI (); /*****************/ /* infinite loop */ /*****************/ for (;;) { if (VMSnok (status = sys$setimr (EfnTimer, &CollectSeconds, 0, 0, 0))) exit (status); if (VMSnok (status = sys$waitfr (EfnTimer))) exit (status); /* get an update of the system time */ PreviousBinTime[0] = CurrentBinTime[0]; PreviousBinTime[1] = CurrentBinTime[1]; sys$gettim (&CurrentBinTime); sys$numtim (&CurrentNumTime, &CurrentBinTime); CollectSystemDynamic (); CollectSPI (); CollectNetInt (); if (Debug) DebugSpiRecord (); if (CurrentNumTime[4] != PrevMinute) { if (Debug) fprintf (stdout, "TotalDeltaSeconds: %f\n", TotalDeltaSeconds); if (TotalDeltaSeconds >= RecordSeconds) { if (Debug) fprintf (stdout, #ifdef __RMIDEF_LOADED "TotalCpuTicks: %Lu TotalUserModeCpuTicks: %Lu\n", #else "TotalCpuTicks: %u TotalUserModeCpuTicks: %u\n", #endif TotalCpuTicks, TotalUserModeCpuTicks); ftmp = (float)TotalCpuTicks / TotalDeltaSeconds / FloatNumberOfCPUs; SpiRecord.PercentCPU = (unsigned char)ftmp; if (ftmp - floor(ftmp) >= 0.5) SpiRecord.PercentCPU++; ftmp = (float)TotalUserModeCpuTicks / TotalDeltaSeconds / FloatNumberOfCPUs; SpiRecord.PercentUserModeCPU = (unsigned char)ftmp; if (ftmp - floor(ftmp) >= 0.5) SpiRecord.PercentUserModeCPU++; WriteSpiRecord (); } TotalCpuTicks = TotalUserModeCpuTicks = SpiRecord.PercentCPU = SpiRecord.PeakPercentCPU = SpiRecord.PercentUserModeCPU = SpiRecord.PeakPercentUserModeCPU = SpiRecord.PageFaults = SpiRecord.PeakPageFaults = SpiRecord.HardPageFaults = SpiRecord.PeakHardPageFaults = SpiRecord.BufferedIO = SpiRecord.PeakBufferedIO = SpiRecord.DirectIO = SpiRecord.PeakDirectIO = SpiRecord.MscpIO = SpiRecord.PeakMscpIO = 0; SpiRecord.NetIntTx[0] = SpiRecord.NetIntTx[1] = SpiRecord.PeakNetIntTx = SpiRecord.NetIntRx[0] = SpiRecord.NetIntRx[1] = SpiRecord.PeakNetIntRx = SpiRecord.PeakNetIntRxTx = SpiRecord.LckLoc = SpiRecord.LckIn = SpiRecord.LckOut = 0; for (idx = 0; idx < CPU_MODE_COUNT; ++idx) TotalModeCpuTicks[idx] = 0; for (idx = 0; idx < sizeof(SpiRecord.Rfu)/sizeof(SpiRecord.Rfu[0]); idx++) SpiRecord.Rfu[idx] = 0; for (idx = 0; idx < HYPERSPI_CPU_MODE_COUNT; idx++) SpiRecord.PercentModeCPU[idx] = 0; SpiRecord.Minute = PrevMinute = CurrentNumTime[4]; SpiRecord.Hour = CurrentNumTime[3]; SpiRecord.Day = CurrentNumTime[2]; SpiRecord.Month = CurrentNumTime[1]; SpiRecord.Year = CurrentNumTime[0]; TotalDeltaSeconds = 0.0; } } /* should never get to here! */ exit (STS$K_ERROR); } /*****************************************************************************/ /* Open the per-day data file, write the SPI record into it, close the file. */ WriteSpiRecord () { static unsigned short PrevDay = 0; static int DataFileNameLength; static char DataFileName [256]; static struct FAB DataFileFab; static struct RAB DataFileRab; static struct XABPRO DataFileXabPro; int status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "WriteSpiRecord()\n"); if (Debug) DebugSpiRecord (); if (SpiRecord.Day != PrevDay) { PrevDay = SpiRecord.Day; DataFileNameLength = sprintf (DataFileName, "%sHYPERSPI_%s_%s_%02.02d%02.02d%02.02d.DAT", HYPERSPI_DATA_DIRECTORY, HYPERSPI_DATA_VERSION, SyiNodeName, SpiRecord.Day, SpiRecord.Month, SpiRecord.Year % 100); if (Debug) fprintf (stdout, "DataFileName |%s|\n", DataFileName); DataFileFab = cc$rms_fab; DataFileFab.fab$l_fop = FAB$M_CIF | FAB$M_SQO; DataFileFab.fab$b_fac = FAB$M_PUT; DataFileFab.fab$l_fna = DataFileName; DataFileFab.fab$b_fns = DataFileNameLength; DataFileFab.fab$w_mrs = sizeof(struct HyperSpiData); DataFileFab.fab$b_org = FAB$C_SEQ; DataFileFab.fab$b_rfm = FAB$C_FIX; DataFileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT; DataFileFab.fab$l_xab = &DataFileXabPro; DataFileRab = cc$rms_rab; DataFileRab.rab$l_fab = &DataFileFab; DataFileRab.rab$b_mbf = 2; DataFileRab.rab$l_rop = RAB$M_EOF; DataFileRab.rab$w_rsz = sizeof(struct HyperSpiData); DataFileRab.rab$l_rbf = (char*)&SpiRecord; /* initialize the extended access block (XAB) as a protection block */ DataFileXabPro.xab$b_bln = sizeof(DataFileXabPro); DataFileXabPro.xab$b_cod = XAB$C_PRO; DataFileXabPro.xab$l_nxt = 0; DataFileXabPro.xab$w_pro = 0xee00; /* w:r,g:r,o:rwed,s:rwed */ } if (VMSnok (status = sys$create (&DataFileFab, 0, 0))) { if (Debug) fprintf (stdout, "sys$create() %%X%08.08X\n", status); exit (status); } if (VMSnok (status = sys$connect (&DataFileRab, 0, 0))) { if (Debug) fprintf (stdout, "sys$connect() %%X%08.08X\n", status); sys$close (&DataFileFab, 0, 0); exit (status); } if (VMSnok (status = sys$put (&DataFileRab, 0, 0))) { if (Debug) fprintf (stdout, "sys$put() %%X%08.08X\n", status); sys$close (&DataFileFab, 0, 0); exit (status); } sys$close (&DataFileFab, 0, 0); } /*****************************************************************************/ /* Collect System Performance Information. It utilizes the undocumented EXE$GETSPI interface for Alpha and VAX, the documented and supported (as from V7.3) $GETRMI interface for Itanium. */ #define MODE_INTERRUPT 0 #define MODE_MULTIPROC 1 #define MODE_KERNEL 2 #define MODE_EXECUTIVE 3 #define MODE_SUPERVISOR 4 #define MODE_USER 5 #define MODE_COMPAT 6 #define MODE_NULL 7 int CollectSPI () { const long cvtf_mode = LIB$K_DELTA_SECONDS_F; static unsigned long ProcessCount, CpuTicks, UserModeCpuTicks, PageFaults, PrevPageFaults = 0, HardPageFaults, PrevHardPageFaults = 0, PageReadIO, PrevPageReadIO = 0, PageWriteIO, PrevPageWriteIO = 0, BufferedIO, PrevBufferedIO = 0, DirectIO, PrevDirectIO = 0, MscpIO, PrevMscpIO = 0, LckLoc[2], PrevLckLoc[2] = {0, 0}, LckIn[2], PrevLckIn[2] = {0, 0}, LckOut[2], PrevLckOut[2] = {0, 0}, Com, ComO; /* storage for VMS binary time */ static unsigned long DiffBinTime [2]; #ifdef __RMIDEF_LOADED static unsigned __int64 CpuExec, CpuIntStk, CpuKernel, CpuMpSynch, CpuSuper, CpuUser, FreeList; static unsigned __int64 ModeCpuTicks[HYPERSPI_CPU_MODE_COUNT]; static unsigned long MscpData [35]; #else /* __RMIDEF_LOADED */ static unsigned long FreeList; static unsigned long MscpData[13], ModeCpuTicks[HYPERSPI_CPU_MODE_COUNT]; #endif /* __RMIDEF_LOADED */ #ifndef __VAX # pragma member_alignment __save # pragma nomember_alignment #endif /* structure for a single CPU's mode ticks */ static struct SingleCPU { unsigned char CPUid; unsigned long Count[8]; }; /* a structure all CPU's mode ticks for a system with up to 32 CPUs */ static struct { unsigned long NumberOfCPUs; struct SingleCPU CPU[32]; } ModeTicks; #ifdef __RMIDEF_LOADED /* initialize structure for RMI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemList[] = { /* for 7.3-2(?) and later which has the RMI$_CPU_... items */ { sizeof(CpuExec), RMI$_CPUEXEC, &CpuExec, 0 }, { sizeof(CpuIntStk), RMI$_CPUINTSTK, &CpuIntStk, 0 }, { sizeof(CpuKernel), RMI$_CPUKERNEL, &CpuKernel, 0 }, { sizeof(CpuMpSynch), RMI$_CPUMPSYNCH, &CpuMpSynch, 0 }, { sizeof(CpuSuper), RMI$_CPUSUPER, &CpuSuper, 0 }, { sizeof(CpuUser), RMI$_CPUUSER, &CpuUser, 0 }, { sizeof(FreeList), RMI$_FRLIST, &FreeList, 0 }, { sizeof(PageFaults), RMI$_FAULTS, &PageFaults, 0 }, { sizeof(PageReadIO), RMI$_PREADIO, &PageReadIO, 0 }, { sizeof(PageWriteIO), RMI$_PWRITIO, &PageWriteIO, 0 }, { sizeof(BufferedIO), RMI$_BUFIO, &BufferedIO, 0 }, { sizeof(DirectIO), RMI$_DIRIO, &DirectIO, 0 }, { sizeof(Com), RMI$_COM, &Com, 0 }, { sizeof(ComO), RMI$_COMO, &ComO, 0 }, { sizeof(ProcessCount), RMI$_PROCS, &ProcessCount, 0 }, { sizeof(MscpData), RMI$_MSCP_EVERYTHING, &MscpData, 0 }, { sizeof(LckLoc[0]), RMI$_ENQNEWLOC, &(LckLoc[0]), 0 }, { sizeof(LckIn[0]), RMI$_ENQNEWIN, &(LckIn[0]), 0 }, { sizeof(LckOut[0]), RMI$_ENQNEWOUT, &(LckOut[0]), 0 }, { sizeof(LckLoc[1]), RMI$_ENQCVTLOC, &(LckLoc[1]), 0 }, { sizeof(LckIn[1]), RMI$_ENQCVTIN, &(LckIn[1]), 0 }, { sizeof(LckOut[1]), RMI$_ENQCVTOUT, &(LckOut[1]), 0 }, {0,0,0,0} }; #else /* __RMIDEF_LOADED */ /* initialize structure for SPI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemList[] = { { sizeof(ModeTicks), SPI$_MODES, &ModeTicks, 0 }, { sizeof(unsigned long), SPI$_FRLIST, &FreeList, 0 }, { sizeof(unsigned long), SPI$_FAULTS, &PageFaults, 0 }, { sizeof(unsigned long), SPI$_PREADIO, &PageReadIO, 0 }, { sizeof(unsigned long), SPI$_PWRITIO, &PageWriteIO, 0 }, { sizeof(unsigned long), SPI$_BUFIO, &BufferedIO, 0 }, { sizeof(unsigned long), SPI$_DIRIO, &DirectIO, 0 }, { sizeof(unsigned long), SPI$_COM, &Com, 0 }, { sizeof(unsigned long), SPI$_COMO, &ComO, 0 }, { sizeof(unsigned long), SPI$_PROCS, &ProcessCount, 0 }, { sizeof(MscpData), SPI$_MSCP_ALL, &MscpData, 0 }, { sizeof(unsigned long), SPI$_ENQNEWLOC, &(LckLoc[0]), 0 }, { sizeof(unsigned long), SPI$_ENQNEWIN, &(LckIn[0]), 0 }, { sizeof(unsigned long), SPI$_ENQNEWOUT, &(LckOut[0]), 0 }, { sizeof(unsigned long), SPI$_ENQCVTLOC, &(LckLoc[1]), 0 }, { sizeof(unsigned long), SPI$_ENQCVTIN, &(LckIn[1]), 0 }, { sizeof(unsigned long), SPI$_ENQCVTOUT, &(LckOut[1]), 0 }, {0,0,0,0} }; #endif /* __RMIDEF_LOADED */ #ifndef __VAX # pragma member_alignment __restore #endif #ifdef __RMIDEF_LOADED static unsigned __int64 PrevModes[8]; #else static unsigned long PrevModes[8]; #endif int status; #ifdef __RMIDEF_LOADED unsigned __int64 tmp; unsigned __int64 CurrentModes[8]; #else unsigned long tmp; unsigned long CurrentModes[8]; #endif unsigned long cidx, midx; float DeltaSeconds; IOSBLK IOsb; /*********/ /* begin */ /*********/ /* collect System Performance Information */ #ifdef __RMIDEF_LOADED status = sys$getrmi ( EfnSpi, /* efn */ 0, /* csiaddr */ 0, /* nodename */ &ItemList, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (Debug) fprintf (stdout, "sys$getrmi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); #else /* __RMIDEF_LOADED */ status = exe$getspi ( EfnSpi, /* efn */ 0, /* csiaddr */ 0, /* nodename */ &ItemList, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (Debug) fprintf (stdout, "exe$getspi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); #endif /* __RMIDEF_LOADED */ if (VMSnok (status)) return (status); if (VMSnok (status = sys$waitfr (EfnSpi))) exit (status); /**************************/ /* calculate elapsed time */ /**************************/ lib$sub_times (&CurrentBinTime, &PreviousBinTime, &DiffBinTime); #ifdef __RMIDEF_LOADED status = lib$cvts_from_internal_time (&cvtf_mode, &DeltaSeconds, &DiffBinTime); #else status = lib$cvtf_from_internal_time (&cvtf_mode, &DeltaSeconds, &DiffBinTime); #endif TotalDeltaSeconds += DeltaSeconds; if (Debug) fprintf (stdout, "DeltaSeconds: %f TotalDeltaSeconds: %f\n", DeltaSeconds, TotalDeltaSeconds); /**********************/ /* calculate CPU time */ /**********************/ #ifdef __RMIDEF_LOADED CurrentModes[MODE_INTERRUPT] = CpuIntStk; CurrentModes[MODE_EXECUTIVE] = CpuExec; CurrentModes[MODE_KERNEL] = CpuKernel; CurrentModes[MODE_MULTIPROC] = CpuMpSynch; CurrentModes[MODE_USER] = CpuUser; CurrentModes[MODE_SUPERVISOR] = CpuSuper; CurrentModes[MODE_COMPAT] = CurrentModes[MODE_NULL] = 0; #else /* __RMIDEF_LOADED */ if (Debug) { for (cidx = 0; cidx < ModeTicks.NumberOfCPUs; cidx++) for (midx = MODE_INTERRUPT; midx <= MODE_NULL; midx++) fprintf (stdout, "[%d][%d] = %u\n", cidx, midx, ModeTicks.CPU[cidx].Count[midx]); } /* accumulate mode counters for all CPUs into CPU 0's area */ for (cidx = 1; cidx < ModeTicks.NumberOfCPUs; cidx++) for (midx = MODE_INTERRUPT; midx <= MODE_NULL; midx++) ModeTicks.CPU[0].Count[midx] += ModeTicks.CPU[cidx].Count[midx]; /* current raw CPU ticks */ CurrentModes[MODE_INTERRUPT] = ModeTicks.CPU[0].Count[MODE_INTERRUPT] - ModeTicks.CPU[0].Count[MODE_NULL]; CurrentModes[MODE_MULTIPROC] = ModeTicks.CPU[0].Count[MODE_MULTIPROC]; CurrentModes[MODE_KERNEL] = ModeTicks.CPU[0].Count[MODE_KERNEL]; CurrentModes[MODE_EXECUTIVE] = ModeTicks.CPU[0].Count[MODE_EXECUTIVE]; CurrentModes[MODE_SUPERVISOR] = ModeTicks.CPU[0].Count[MODE_SUPERVISOR]; CurrentModes[MODE_USER] = ModeTicks.CPU[0].Count[MODE_USER]; CurrentModes[MODE_COMPAT] = ModeTicks.CPU[0].Count[MODE_COMPAT]; CurrentModes[MODE_NULL] = ModeTicks.CPU[0].Count[MODE_NULL]; #endif /* __RMIDEF_LOADED */ /* calculate the number of CPU ticks for this period */ CpuTicks = 0; for (midx = MODE_INTERRUPT; midx <= MODE_COMPAT; midx++) { tmp = CurrentModes[midx]; #ifndef __VAX /* It has been OBSERVED that in this application Interrupt mode counters can actually go backwards (only ever observed by 1). Don't know if this is an artifact of the code, or what? This kludge doesn't seem to cause any problems, but will look for a better solution/explanation when time permits. */ if (CurrentModes[midx] >= PrevModes[midx]) CurrentModes[midx] = tmp - PrevModes[midx]; else CurrentModes[midx] = 0; #else CurrentModes[midx] = tmp - PrevModes[midx]; #endif /* __VAX */ PrevModes[midx] = tmp; /* keep track of the total ticks */ CpuTicks += CurrentModes[midx]; } UserModeCpuTicks = CurrentModes[MODE_USER] + CurrentModes[MODE_COMPAT]; ModeCpuTicks[HYPERSPI_MODE_USER] = UserModeCpuTicks; ModeCpuTicks[HYPERSPI_MODE_INTERRUPT] = CurrentModes[MODE_INTERRUPT]; ModeCpuTicks[HYPERSPI_MODE_MULTIPROC] = CurrentModes[MODE_MULTIPROC]; ModeCpuTicks[HYPERSPI_MODE_KERNEL] = CurrentModes[MODE_KERNEL]; ModeCpuTicks[HYPERSPI_MODE_EXECUTIVE] = CurrentModes[MODE_EXECUTIVE]; ModeCpuTicks[HYPERSPI_MODE_SUPERVISOR] = CurrentModes[MODE_SUPERVISOR]; if (Debug) { fprintf (stdout, #ifdef __RMIDEF_LOADED "ticks CPU\nI: %Lu\nMP: %Lu\nK: %Lu\nE: %Lu\nS: %Lu\nU: %Lu\nC: %Lu\n?: %Lu\n", #else "ticks CPU\nI: %u\nMP: %u\nK: %u\nE: %u\nS: %u\nU: %u\nC: %u\n?: %u\n", #endif CurrentModes[0], CurrentModes[1], CurrentModes[2], CurrentModes[3], CurrentModes[4], CurrentModes[5], CurrentModes[6], CurrentModes[7]); } /*******************/ /* calculate other */ /*******************/ if (Debug) { fprintf (stdout, "CPU ticks %u\n\ CPU user mode ticks %u\n\ Free list: %u\n\ Page faults: %u %u hard\n\ Com: %u\n\ ComO: %u\n\ Buffered I/O: %u\n\ Direct I/O: %u\n\ Processes: %u\n\ MSCP: %u %u %u %u %u %u %u %u %u %u %u %u %u\n\ Locks: %u %u %u %u %u %u\n", CpuTicks, UserModeCpuTicks, (unsigned long)FreeList, PageFaults, PageReadIO+PageWriteIO, Com, ComO, BufferedIO, DirectIO, ProcessCount, MscpData[0], MscpData[1], MscpData[2], MscpData[3], MscpData[4], MscpData[5], MscpData[6], MscpData[7], MscpData[8], MscpData[9], MscpData[10], MscpData[11], MscpData[12], LckLoc[0], LckIn[0], LckOut[0], LckLoc[1], LckIn[1], LckOut[1]); } /***********************/ /* update the counters */ /***********************/ FloatNumberOfCPUs = (float)(SpiRecord.NumberOfCPUs = ActiveCpuCnt); SpiRecord.SystemMemoryPercentInUse = ((SyiMemSize - FreeList) * 100) / SyiMemSize; SpiRecord.NumberOfProcesses = ProcessCount; /* 'PrevPageFaults' will only be zero first time function is called */ if (PrevPageFaults) { int i = 0; TotalCpuTicks += CpuTicks; TotalUserModeCpuTicks += UserModeCpuTicks; for (i = 0; i < CPU_MODE_COUNT; ++i) TotalModeCpuTicks[i] += ModeCpuTicks[i]; if (DeltaSeconds < 1.0) DeltaSeconds = 1.0; tmp = (int)((float)CpuTicks / DeltaSeconds / FloatNumberOfCPUs); if (tmp > SpiRecord.PeakPercentCPU) SpiRecord.PeakPercentCPU = tmp; tmp = (int)((float)UserModeCpuTicks / DeltaSeconds / FloatNumberOfCPUs); if (tmp > SpiRecord.PeakPercentUserModeCPU) SpiRecord.PeakPercentUserModeCPU = tmp; } if (PageFaults > PrevPageFaults) { if (PrevPageFaults) { SpiRecord.PageFaults += (tmp = PageFaults - PrevPageFaults); if (tmp > SpiRecord.PeakPageFaults) SpiRecord.PeakPageFaults = tmp; SpiRecord.HardPageFaults += (tmp = PageReadIO - PrevPageReadIO + PageWriteIO - PrevPageWriteIO); if (tmp > SpiRecord.PeakHardPageFaults) SpiRecord.PeakHardPageFaults = tmp; } PrevPageReadIO = PageReadIO; PrevPageWriteIO = PageWriteIO; } /* Page faults counter can overflow */ PrevPageFaults = PageFaults; SpiRecord.Computable = Com + ComO; if (BufferedIO > PrevBufferedIO) { if (PrevBufferedIO) { SpiRecord.BufferedIO += (tmp = BufferedIO - PrevBufferedIO); if (tmp > SpiRecord.PeakBufferedIO) SpiRecord.PeakBufferedIO = tmp; } PrevBufferedIO = BufferedIO; } if (DirectIO > PrevDirectIO) { if (PrevDirectIO) { SpiRecord.DirectIO += (tmp = DirectIO - PrevDirectIO); if (tmp > SpiRecord.PeakDirectIO) SpiRecord.PeakDirectIO = tmp; } PrevDirectIO = DirectIO; } #ifdef __RMIDEF_LOADED MscpIO = MscpData[23] + MscpData[24]; #else /* __RMIDEF_LOADED */ MscpIO = MscpData[0]; #endif /* __RMIDEF_LOADED */ if (MscpIO > PrevMscpIO) { if (PrevMscpIO) { SpiRecord.MscpIO += (tmp = MscpIO - PrevMscpIO); if (tmp > SpiRecord.PeakMscpIO) SpiRecord.PeakMscpIO = tmp; } PrevMscpIO = MscpIO; } /* lock counters can overflow */ if (LckLoc[0] > PrevLckLoc[0]) { SpiRecord.LckLoc += LckLoc[0] - PrevLckLoc[0]; } PrevLckLoc[0] = LckLoc[0]; if (LckLoc[1] > PrevLckLoc[1]) { SpiRecord.LckLoc += LckLoc[1] - PrevLckLoc[1]; } PrevLckLoc[1] = LckLoc[1]; if (LckIn[0] > PrevLckIn[0]) { SpiRecord.LckIn += LckIn[0] - PrevLckIn[0]; } PrevLckIn[0] = LckIn[0]; if (LckIn[1] > PrevLckIn[1]) { SpiRecord.LckIn += LckIn[1] - PrevLckIn[1]; } PrevLckIn[1] = LckIn[1]; if (LckOut[0] > PrevLckOut[0]) { SpiRecord.LckOut += LckOut[0] - PrevLckOut[0]; } PrevLckOut[0] = LckOut[0]; if (LckOut[1] > PrevLckOut[1]) { SpiRecord.LckOut += LckOut[1] - PrevLckOut[1]; } PrevLckOut[1] = LckOut[1]; return (SS$_NORMAL); } /****************************************************************************/ /* Get the size and amount of free space in the installed page file(s). */ int CollectSystemDynamic () { static unsigned long PageFileSize, PageFileFree; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } SyiItems [] = { { sizeof(ActiveCpuCnt), SYI$_ACTIVECPU_CNT, &ActiveCpuCnt, 0 }, { sizeof(PageFileSize), SYI$_PAGEFILE_PAGE, &PageFileSize, 0 }, { sizeof(PageFileFree), SYI$_PAGEFILE_FREE, &PageFileFree, 0 }, {0,0,0,0}, }; int status; struct { unsigned long Status; unsigned long Reserved; } IOsb; /*********/ /* begin */ /*********/ status = sys$getsyiw (0, 0, 0, &SyiItems, &IOsb, 0, 0); if (Debug) fprintf (stdout, "sys$getsyiw() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.Status); if (VMSnok (status)) return (status); if (VMSnok (IOsb.Status)) return (IOsb.Status); SpiRecord.PageSpaceMBytes = ((float)PageFileSize * SyiPageSize) / 1048576; SpiRecord.PageSpacePercentInUse = 100 - (((float)PageFileFree * 100) / (float)PageFileSize); if (Debug) fprintf (stdout, "AvailCpuCnt: %d ActiveCpuCnt: %d \ PageSpaceMBytes: %d PageSpacePercentInUse: %d\n", AvailCpuCnt, ActiveCpuCnt, SpiRecord.PageSpaceMBytes, SpiRecord.PageSpacePercentInUse); return (SS$_NORMAL); } /****************************************************************************/ /* Get node name, amount of physical memory, and memory page size. */ /* these are currently not in header files for VAX C */ /* discovered empirically on an AXP system using DEC C */ #define SYI$_PAGE_SIZE 4452 #define SYI$_MEMSIZE 4459 int CollectSystemStatic () { int status; unsigned short SyiNodeNameLength; struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } SyiItems [] = { { 15, SYI$_NODENAME, &SyiNodeName, &SyiNodeNameLength }, { 4, SYI$_MEMSIZE, &SyiMemSize, 0 }, { 4, SYI$_PAGE_SIZE, &SyiPageSize, 0 }, { sizeof(AvailCpuCnt), SYI$_AVAILCPU_CNT, &AvailCpuCnt, 0 }, { 0,0,0,0 } }; struct { unsigned long Status; unsigned long Reserved; } IOsb; /*********/ /* begin */ /*********/ status = sys$getsyiw (0, 0, 0, &SyiItems, &IOsb, 0, 0); if (Debug) fprintf (stdout, "sys$getsyiw() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.Status); if (VMSok (IOsb.Status)) SyiNodeName[SyiNodeNameLength] = '\0'; else SyiNodeName[0] = '\0'; if (VMSnok (IOsb.Status)) return (IOsb.Status); return (SS$_NORMAL); } /*****************************************************************************/ /* This function uses undocumented sense-mode functionality to load counters from the network interface (commonly ethernet device). Multiple devices are supported using a multi-valued logical name HYPERSPI$AGENT_NI and the frame-rate and byte-rate and soft/hard error-rate statistics are accumlated to represent total system data. On Alpha and Itanium uses native 64 bit (quadword) accumulators. Essential concepts have come from "EMON: moniteur ethernet (V2.1-2) Gerard Guillaume" and found in DECUSLIB freeware, and from some peeks at the T4 source code. The implementation is mine. Adapted from the MONDESI.C development tree! */ int CollectNetInt () { static int NetIntCount; static unsigned short NetIntChan [NI_DEVICE_MAX]; static unsigned long DcSCOM = 32, /* DC$_SCOM */ DtNI = 13; /* DT$_NI */ #ifndef __VAX static unsigned __int64 NetIntBlocksRx, NetIntBlocksTx, NetIntBytesRx, NetIntBytesTx, PrevBlocksRx, PrevBlocksTx, PrevBytesRx, PrevBytesTx, PrevErrorsHard, PrevErrorsSoft; #else static unsigned int NetIntBlocksRx, NetIntBlocksTx, NetIntBytesRx, NetIntBytesTx, PrevBlocksRx, PrevBlocksTx, PrevBytesRx, PrevBytesTx, PrevErrorsHard, PrevErrorsSoft; #endif static long DeviceCount = -1, DviUnit, LnmIndex = -1, LnmAttributes; static unsigned short LogValueLength; static char LogValue [32], NetIntDev [NI_DEVICE_MAX*24]; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } LnmItems [] = { { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 }, { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 }, { sizeof(LogValue)-1, LNM$_STRING, &LogValue, &LogValueLength }, { 0,0,0,0 } }, DevScanItems [] = { { sizeof(DcSCOM), DVS$_DEVCLASS, &DcSCOM, 0 }, { 0,0,0,0 } }, GetDviItems [] = { { sizeof(DviUnit), DVI$_UNIT, &DviUnit, 0 }, { 0,0,0,0 } }; static $DESCRIPTOR (NetIntDsc, ""); static $DESCRIPTOR (LogNameDsc, "HYPERSPI$AGENT_NI"); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); int ecnt, status; unsigned short pword, retlen; unsigned long LnmCount, NetIntErrorsHard, NetIntErrorsSoft; unsigned long DevScanContext [2]; float tfloat; char *vptr; char DevScanBuffer [64], CountBuffer [512]; struct STRUCT_IOSBLK IOsb; $DESCRIPTOR (CountDsc, CountBuffer); $DESCRIPTOR (DevScanDsc, DevScanBuffer); #ifndef __VAX #define CASTIT (unsigned __int64) unsigned __int64 value, BytesRx, BytesTx; #else #define CASTIT (unsigned long) unsigned long value, BytesRx, BytesTx; unsigned long QuadWord [2] = { 0, 0 }; #endif /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "CollectNetInt() %s\n", NetIntDev); if (LnmIndex < 0) { /* first call, check for logical name defined network devices */ for (LnmIndex = 0; LnmIndex < NI_DEVICE_MAX; LnmIndex++) { status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems); if (VMSnok (status) || !(LnmAttributes & LNM$M_EXISTS)) break; LogValue[LogValueLength] = '\0'; NetIntDsc.dsc$a_pointer = LogValue; NetIntDsc.dsc$w_length = LogValueLength; status = sys$assign (&NetIntDsc, &NetIntChan[LnmIndex], 0, 0); if (VMSnok (status)) { fprintf (stdout, "[%d] %s %%X%08.08X\n", __LINE__, LogValue, status); NetIntCount = 0; break; } if (NetIntDev[0]) strcat (NetIntDev, ","); strcat (NetIntDev, LogValue); NetIntCount++; } } if (DeviceCount < 0) { /* first call */ DeviceCount = 0; /* if logical name defined and found devices */ if (NetIntCount) return (NetIntCount); /* scan system for network devices */ DevScanContext[0] = DevScanContext[1] = 0; while (NetIntCount < NI_DEVICE_MAX) { status = sys$device_scan (&DevScanDsc, &retlen, 0, &DevScanItems, &DevScanContext); if (VMSnok (status)) { if (status == SS$_NOMOREDEV || status == SS$_NOSUCHDEV) break; fprintf (stdout, "[%d] %%X%08.08X\n", __LINE__, status); NetIntCount = 0; break; } status = sys$getdviw (0, 0, &DevScanDsc, &GetDviItems, &IOsb, 0, 0, 0, 0); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) { fprintf (stdout, "[%d] %%X%08.08X\n", __LINE__, status); NetIntCount = 0; break; } /* if not a template device then continue */ if (DviUnit != 0) continue; status = sys$assign (&DevScanDsc, &NetIntChan[DeviceCount++], 0, 0); if (VMSnok (status)) { if (status == SS$_NOPRIV) continue; fprintf (stdout, "[%d] %s %%X%08.08X\n", __LINE__, LogValue, status); NetIntCount = 0; break; } if (NetIntDev[0]) strcat (NetIntDev, ","); DevScanBuffer[retlen] = '\0'; strcat (NetIntDev, DevScanBuffer+1); NetIntCount++; } /* first call, just return having assigned channels (perhaps) */ return (NetIntCount); } /****************/ /* collect data */ /****************/ NetIntBlocksRx = NetIntBlocksTx = NetIntBytesRx = NetIntBytesTx = NetIntErrorsHard = NetIntErrorsSoft = 0; /* if no devices then just return from here */ if (!NetIntCount) return (NetIntCount); /* accumulate counts from each device */ for (ecnt = 0; ecnt < NetIntCount; ecnt++) { /* if the channel has been deassigned */ if (!NetIntChan[ecnt]) continue; memset (CountBuffer, 0, sizeof(CountBuffer)); status = sys$qiow (0, NetIntChan[ecnt], #ifndef __VAX #define IO$M_RD_64COUNT 0x4000 IO$_SENSEMODE | IO$M_RD_COUNT | IO$M_RD_64COUNT | IO$M_CTRL, #else IO$_SENSEMODE | IO$M_RD_COUNT | IO$M_CTRL, #endif &IOsb, 0, 0, 0, &CountDsc, 0, 0, 0, 0); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) { /* if the interface give problems just disable reporting */ static int ReportCount = 0; if (ReportCount++ < 64) fprintf (stdout, "[%d] %%X%08.08X \"%s\"%d\n", __LINE__, status, NetIntDev, ecnt); else { sys$dassgn (NetIntChan[ecnt]); NetIntChan[ecnt] = 0; ReportCount = 0; } continue; } vptr = CountBuffer; while (vptr < CountBuffer + IOsb.iosb$w_bcnt) { pword = *(unsigned short*)vptr; vptr += 2; /* end of list */ if (!pword) break; /* MSB should be set! */ if (!(pword & 0x8000)) { fprintf (stdout, "[%d] %04.04X\n", __LINE__, pword); break; } value = 0; #ifndef __VAX if ((pword & 0x0fff) < 200) { /* quadword value (item number less than 200) */ value = *(unsigned __int64*)vptr; vptr += 8; } else #endif if ((pword & 0x6000) == 0x6000) { /* longword value */ value = *(unsigned long*)vptr; vptr += 4; } else if ((pword & 0x6000) == 0x4000) { /* word value */ value = *(unsigned short*)vptr; vptr += 2; } else if ((pword & 0x6000) == 0x2000) { /* byte value */ value = *(unsigned char*)vptr; vptr++; } if (pword & 0x1000) { /* hw map (?) */ value = *(unsigned long*)vptr; vptr += 2; continue; } switch (pword & 0x0fff) { #ifndef __VAX /* these look suspiciously like "LANCP SHOW DEVICE /COUNT"! */ case 1 : /* seconds since last zeroed */ break; case 2 : case 6 : NetIntBytesRx += value; break; case 3 : case 7 : NetIntBytesTx += value; break; case 4 : case 8 : NetIntBlocksRx += value; break; case 5 : case 9 : NetIntBlocksTx += value; break; case 12 : case 13 : case 14 : case 15 : case 16 : case 17 : case 18 : case 19 : case 30 : case 21 : case 22 : case 23 : case 24 : case 27 : case 28 : case 29 : NetIntErrorsSoft += value; break; case 25 : case 26 : case 33 : case 34 : NetIntErrorsHard += value; break; #endif /* more "traditional" counters */ case 1000 /* NMA$C_CTLIN_BRC */ : case 1002 /* NMA$C_CTLIN_MBY */ : NetIntBytesRx += value; break; case 1001 /* NMA$C_CTLIN_BSN */ : NetIntBytesTx += value; break; case 1010 /* NMA$C_CTLIN_DBR */ : case 1012 /* NMA$C_CTLIN_MBL */ : NetIntBlocksRx += value; break; case 1011 /* NMA$C_CTLIN_DBS */ : NetIntBlocksTx += value; break; case 1013 /* NMA$C_CTLIN_BID */ : case 1014 /* NMA$C_CTLIN_BS1 */ : case 1015 /* NMA$C_CTLIN_BSM */ : case 1040 /* NMA$C_CTLIN_RBE */ : case 1041 /* NMA$C_CTLIN_LBE */ : case 1064 /* NMA$C_CTLIN_OVR */ : case 1066 /* NMA$C_CTLIN_UBU */ : NetIntErrorsSoft += value; break; case 1060 /* NMA$C_CTLIN_SFL */ : case 1061 /* NMA$C_CTLIN_CDC */ : case 1063 /* NMA$C_CTLIN_UFD */ : case 1062 /* NMA$C_CTLIN_RFL */ : case 1065 /* NMA$C_CTLIN_SBU */ : NetIntErrorsHard += value; break; default : } } } if (!PrevBytesRx) PrevBytesRx = NetIntBytesRx; BytesRx = NetIntBytesRx - PrevBytesRx; PrevBytesRx = NetIntBytesRx; if (!PrevBytesTx) PrevBytesTx = NetIntBytesTx; BytesTx = NetIntBytesTx - PrevBytesTx; PrevBytesTx = NetIntBytesTx; PrevErrorsSoft = NetIntErrorsSoft; PrevErrorsHard = NetIntErrorsHard; #ifndef __VAX *(unsigned __int64*)SpiRecord.NetIntRx = *(unsigned __int64*)SpiRecord.NetIntRx + BytesRx; if ((unsigned long)BytesRx > SpiRecord.PeakNetIntRx) SpiRecord.PeakNetIntRx = (unsigned long)BytesRx; *(unsigned __int64*)SpiRecord.NetIntTx = *(unsigned __int64*)SpiRecord.NetIntTx + BytesTx; if ((unsigned long)BytesTx > SpiRecord.PeakNetIntTx) SpiRecord.PeakNetIntTx = (unsigned long)BytesTx; if ((unsigned long)(BytesRx + BytesTx) > SpiRecord.PeakNetIntRxTx) SpiRecord.PeakNetIntRxTx = (unsigned long)(BytesRx + BytesTx); #else QuadWord[0] = BytesRx; status = lib$addx (&QuadWord, &SpiRecord.NetIntRx, &SpiRecord.NetIntRx, 0); if (VMSnok(status)) exit (status); if (BytesRx > SpiRecord.PeakNetIntRx) SpiRecord.PeakNetIntRx = BytesRx; QuadWord[0] = BytesTx; status = lib$addx (&QuadWord, &SpiRecord.NetIntTx, &SpiRecord.NetIntTx, 0); if (VMSnok(status)) exit (status); if (BytesTx > SpiRecord.PeakNetIntTx) SpiRecord.PeakNetIntTx = BytesTx; if (BytesRx + BytesTx > SpiRecord.PeakNetIntRxTx) SpiRecord.PeakNetIntRxTx = BytesRx + BytesTx; #endif return (NetIntCount); } /*****************************************************************************/ /* */ void DebugSpiRecord () { int idx; /*********/ /* begin */ /*********/ fprintf (stdout, "%02.02d:%02.02d CPU %d %d %d, %d %d MEM %dMb %d%% PAG %dMb %d%%\ PRC %d FLT %d %d, %d %d IO %d %d %d, %d %d %d COM %d MODE", SpiRecord.Hour, SpiRecord.Minute, SpiRecord.NumberOfCPUs, SpiRecord.PercentCPU, SpiRecord.PercentUserModeCPU, SpiRecord.PeakPercentCPU, SpiRecord.PeakPercentUserModeCPU, SpiRecord.SystemMemoryMBytes, SpiRecord.SystemMemoryPercentInUse, SpiRecord.PageSpaceMBytes, SpiRecord.PageSpacePercentInUse, SpiRecord.NumberOfProcesses, SpiRecord.PageFaults, SpiRecord.HardPageFaults, SpiRecord.PeakPageFaults, SpiRecord.PeakHardPageFaults, SpiRecord.BufferedIO, SpiRecord.DirectIO, SpiRecord.MscpIO, SpiRecord.PeakBufferedIO, SpiRecord.PeakDirectIO, SpiRecord.PeakMscpIO, SpiRecord.Computable); for (idx = 0; idx < HYPERSPI_CPU_MODE_COUNT; ++idx) fprintf (stdout, " %d", SpiRecord.PercentModeCPU[idx]); fprintf (stdout, "\n"); #ifndef __VAX fprintf (stdout, "NI %12Lu %12u %12Lu %12u %12u\n", *(__int64*)SpiRecord.NetIntRx, SpiRecord.PeakNetIntRx, *(__int64*)SpiRecord.NetIntTx, SpiRecord.PeakNetIntTx, SpiRecord.PeakNetIntRxTx); #else fprintf (stdout, "NI %12u %12u %12u %12u %12u\n", SpiRecord.NetIntRx, SpiRecord.PeakNetIntRx, SpiRecord.NetIntTx, SpiRecord.PeakNetIntTx, SpiRecord.PeakNetIntRxTx); #endif } /*****************************************************************************/ /* Create a logical name HYPERSPI_AGENT_PID containing the process PID (binary). */ AgentPidLogical (boolean CreateLogical) { static unsigned long PslUser = PSL$C_USER; static $DESCRIPTOR (LnmSystemDsc, "LNM$SYSTEM"); static $DESCRIPTOR (PidLogNameDsc, "HYPERSPI_AGENT_PID"); static unsigned long Pid; static struct { unsigned short buf_len; unsigned short item; void *buf_addr; unsigned short *short_ret_len; } JpiItems [] = { { sizeof(Pid), JPI$_PID, &Pid, 0 }, { 0,0,0,0 } }, LnmPidItem [] = { { sizeof(Pid), LNM$_STRING, &Pid, 0 }, { 0,0,0,0 } }; int status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AgentPidLogical()\n"); if (CreateLogical) { atexit (&AgentPidLogicalAtExit); status = sys$getjpiw (0, 0, 0, &JpiItems, 0, 0, 0); if (VMSok (status)) status = sys$crelnm (0, &LnmSystemDsc, &PidLogNameDsc, &PslUser, &LnmPidItem); if (VMSnok (status)) exit (status); } else sys$dellnm (&LnmSystemDsc, &PidLogNameDsc, &PslUser); } /*****************************************************************************/ /* Executed during image exit (in particular $FORCEX). */ void AgentPidLogicalAtExit () { if (Debug) fprintf (stdout, "AgentPidLogicalAtExit()\n"); AgentPidLogical (false); } /****************************************************************************/ /* Does a case-insensitive character-by-character string compare and returns true if two strings are the same, or false if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ boolean strsame ( char *sptr1, char *sptr2, int count ) { while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (false); if (count > 0) if (--count == 0) return (true); } if (*sptr1 || *sptr2) return (false); else return (true); } /****************************************************************************/