/*****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* mondesi.c Monitor de Sistema (espaņol) MONDESI (pronounced "mon-deh-sih") is a browser-based VMS system monitor with a sufficiently small resource footprint as not to impact system performance (minimal CPU consumption and only one or two buffered IO per client update). No, the name's etiology has little significance at all (a small homage at most). I was just looking for a short (and cute) name. This utility is intended as a general tool for system observation. It is not a troubleshooting tool as such, especially when a system is beginning to suffer from resource exhaustion. Not only does the data collection script need to continue processing effectively (see 'SUFFICIENT PRIORITY' below) there is also a Web server in the data transfer path. MONDESI should work with all relatively modern browsers. Developed against Chrome 38+, Firefox 34+, MSIE 10 & 11, Opera 12+, Safari 7 & 8. Will NOT work with older browsers, especially MSIE 9. $GETRMI seems to have been intended as the 'modern' and documented version of the $GETSPI functionality. Unfortunately, after dealing with it for a while (MonDeSi versions 1 thru 5), it's difficult to shake the impression that someone lost interest halfway through the project! MonDeSi v6.0.0 moved functionality from $GETRMI to $GETSPI, in part to allow execution on VAXen. Displayed values have been validated using the interactive MONITOR utility. As much of the functionality is pushed off to the browser MONDESI relies heavily on JavaScript with server<->browser data exchanged as JSON. As MONDESI provides a readily accessable insight into a system (albeit, fairly basic resource consumption information only) perhaps it might be best available only under authorization. If MonDeSi stops updating the browser for any reason (e.g. server shutdown, network connection interrupted) for a period of ninety seconds or more, the node name is shown in red. MonDeSi periodically attempts to re-establish lost network connections automatically. NOTE: Originally MONDESI was a vehicle for experimentation rather than an application designed and built to address a specific need. Over the years it has received some use in anger and undergone some major cycles of enhancement. It is now - for what it tries to provide - a capable and useful tool to snapshot resource utilisation on all VMS platforms. WASD-isms --------- MonDeSi uses WebSocket on the WASD server and must be activated using CGIplus and so a single script may handle multiple clients. If not CGIplus, it itself redirects to using the /cgi-plus/ path which must be mapped. Normally an unused CGIplus script has a lifetime before deletion. Defining the logical name MONDESI_LIFETIME to an integer lets the script inform the server what that lifetime should be. If the value is an asterisk the lifetime is completely disabled. Not required for proctored instantiation. Examples: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_LIFETIME 0 !immediate deletion $ DEFINE /SYSTEM /EXECUTIVE MONDESI_LIFETIME 60 !deletion after 60 mins $ DEFINE /SYSTEM /EXECUTIVE MONDESI_LIFETIME * !infinite lifetime MonDeSi may be proctored into existence. # WASD_CONFIG_GLOBAL [DclScriptProctor] 1 /cgiplus-bin/mondesi /cgiplus-bin/mondesi NETWORK STATISTICS ------------------ MONDESI will scan the system for network devices (commonly ethernet) and use any found for NI statistics. If the system admin wishes to tailor the source of the statistics the multi-valued logical name MONDESI_NI can be used to specify one or more network interfaces. Define this logical name system-wide (or in a script wrapper procedure) as required. For example: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_NI EWA0:,EWB0: This logical name must exist as MONDESI initialises. If undefined or the specified device(s) cannot be accessed the ethernet data are greyed-out. DISK STATISTICS --------------- MONDESI will scan selected disk devices displaying the operations per second for mounted devices. To enable this element the system admin needs to define a multi-valued logical name containing the devices to be monitored. Those selected for display must be defined using the device names as shown by a $ SHOW DEVICE command. For example: $ SHOW DEVICE DKA Device Device Error Volume Free Trans Mnt Name Status Count Label Space Count Cnt $1$DKA0: (KLAATU) Mounted 0 ALPHASYS 90.22GB 597 1 $1$DKA100: (KLAATU) Mounted 0 BACKUP 208.86GB 10 1 For example: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_DISK $1$DKA0:,$1$DKA100:,$1$MDA0: This logical name must exist as MONDESI initialises. PROCESS DISPLAY --------------- MONDESI can be configured to display the top five processes for each of the categories; CPU time, super+exec+kernel time, page faults, buffered and direct I/O. It is displayed as a table immediately below the bar graph area and above the history area. Generating and then transmitting the data is RELATIVELY EXPENSIVE compared to other MonDeSi data. The collection code automatically switches between newer and older SPI data and data structure (coincident with VMS V8.3?). The S+E+K data item is unavailable with the older. For example, to enable this for all clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,*,* This logical name must exist as MONDESI initialises. To enable top-process only for web server authenticated clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,$* In addition, MonDeSi can be configured to display per-process data, by clicking on a displayed top-process. This requires the MONDESI.EXE image to be installed with WORLD privilege (see below). This is relatively QUITE EXPENSIVE in terms of data quanitiy transferred (though still noes not register in MONITOR SYSTEM). This displayed data should be relatively self-explanatory for the experienced VMS users. Per process quotas are displayed in red when they reach or exceed a minimum availability, by default 15%. The following logical name can be defined to change this threshold. For example: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_PROC_ALERT 25 OPCOM MONITORING ---------------- MonDeSi will spawn a subprocess attached to a pseudo-terminal with OPCOM enabled and parse the stream of OPCOM messages providing a count of messages and timestamp of the most recent. Where configured it will also provide a buffer of the most recent messages. The MONDESI.EXE image must be INSTALLed with OPER privilege. To disable this OPCOM monitoring completely define the following logical name with an exclamation point. $ DEFINE /SYSTEM /EXECUTIVE MONDESI_OPCOM "!" OPCOM messages are considered sensitive information and so by default are NOT available for general viewing. To view the message buffer the browser request must have been authorised (i.e. the CGI variable REMOTE_USER contain a string). Alternatively, to authorise all accessors to view the OPCOM message buffer use an asterisk. $ DEFINE /SYSTEM /EXECUTIVE MONDESI_OPCOM "*" To forbid any and all viewing of the OPCOM message buffer use a hash/pound symbol. OPCOM message counting still operates. $ DEFINE /SYSTEM /EXECUTIVE MONDESI_OPCOM "#" These are in addition to MONDESI_ACCESS controls (see below). By default the OPCOM enables all categories. To restrict the range of categories define the logical name to contain the exact REPLY command qualifier(s) required. For example, to enable just cluster and network messages: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_OPCOM "*/ENABLE=(CLUSTER,NETWORK)" To enable all and then disable security reporting: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_OPCOM "*/DISABLE=SECURITY" NOTE: To have security category enabled the MONDESI.EXE image must be INSTALLed with SECURITY privilege. If CGI (XHR) is used the long-pull response of the XHR request will generate a series of OPCOM messages each time the subprocess is established (with each long-pull response timeout or client change of collection parameters). Some may consider this noise so CGI access has OPCOM monitoring disabled by default. To enable OPCOM for CGI prefix the logical name value with a plus symbol. For example: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_OPCOM "+*/ENABLE=(CLUSTER,NETWORK)" ACCESS CONTROL -------------- As the displayed data can be considered sensitive in most circumstances access control on the MonDeSi script should be applied. The access control allows any web server authentication scheme and does not have to represent or correspond to SYSUAF authentiation. Access control may be effectively exercised using Web authorisation. Important note for WASD WebSocket users: Currently (early 2013) not all browsers fully support all HTTP semantics prior to WebSocket protocol upgrade. For example, of Chrome, Firefox, MSIE, Opera and Safari, only Firefox supports the standard HTTP request and proxy authentication mechanisms, which can be an issue if negotiating WebSockets through a proxy server requiring credentials, and for the authenticated access described in this section. At the time of writing (early 2013) not all browsers (only Firefox in fact) implement HTTP authorisation over WebSocket. WASD users employing WebSocket and authorisation are required to use FireFox! Disable WebSocket Authorisation for all but selected user-agents. As noted above, not all browsers support HTTP authorisation with WebSocket. MonDeSi allows WebSocket access (only available with WASD 10.1 and later) to be automatically disabled for all but selected browsers. Just define a multi-valued logical name with zero followed by values containing unique strings found in the cabable user-agent strings. For example, to disable WebSocket for all but Firefox: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_WEBSOCKET 0,"Firefox" Disabling WebSocket adds a little latency to some actions, such as per-process display, but does not affect overall functionality. Additional access control can be exercised in various combinations using a multi-valued logical name. The first value controls access to the general monitor display, the second access to the top-process display, the third to the per-process display. Access to top-process is a necessary pre-requisite for access to per-process display. An underscore can be used to disable access. An empty string (" ") must be specified where a lower index is to be ignored. For example, to enable top-process and per-process for all clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,*,* To enable top-process for all clients and per-process for all authenticated clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,*,$* Top-process for all clients and per-process for specified authenticated clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,*,"$,$" Top-process for authenticated clients and per-process for specified authenticated clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,$*,"$=," Top-process and per-process for all authenticated clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,"$*","$*" Top-process and per-process only for specified authenticated clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS "$,$","$*" OPCOM only for specified authenticated clients: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS " "," "," ","$,$" Alternatively provide one or more comma-separated, dotted-decimal IP address to specify one or more hosts allowed to use the script, and/or one or more comman-separated IP addresses and CIDR subnet mask to specify a range of hosts. IPv4 only! For example, the specified host(s) can access top-proceess and per-process: $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,"#192.168.1.2",* $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS *,"#192.168.1.2,192.168.1.3",* And, the specified host range (using CIDR notation): $ DEFINE /SYSTEM /EXECUTIVE MONDESI_ACCESS "#192.168.1.0/24",*,* SUFFICIENT PRIORITY ------------------- For a monitor such as this to be effective on a busy system it needs to have a bit of an edge! If INSTALLed with ALTPRI it will bump it's process priority up to 6 when starting and reduce it back to previous when exiting (just in case it's WASD and a persistent scripting process :-) $ INSTALL REPLACE MONDESI.EXE /PRIV=(ALTPRI) Don't forget to reINSTALL any subsequently built or deployed executable. WEBSOCKET --------- For the WASD server MonDeSi uses HTML5 WebSocket http://en.wikipedia.org/wiki/Websockets to perform the data transfer from server to client. The WASD server v10.1 and later supports scripting using WebSockets as the IPC. A single CGIplus scripting process can support multiple WebSocket clients making it particularly efficient. The code must support multiple asynchonous threads of processing. MONDESI provides this using ASTs. The WsLib..() functions from the WASD wsLIB library handle the concurrent WebSocket processing. See the WASD documentation for detailed information on WebSocket support. For multi-client WebSocket usage MonDeSi needs to be activated as a CGIplus script. This activation will happily support a non-WebSocket client as well. CGIplus activation can be ensured using a mapping rule providing redirection, for example redirect /cgi-bin/mondesi ///cgiplus-bin/mondesi pass /mondesi/-/* /www_root/mondesi/* MONDESI detects the underlying request type and adjusts its behaviour between standard HTTP clients (using the "Web Push" techology described below) and WebSocket clients. In fact, under WASD, a single (CGIplus) process with multiple WebSocket-connected clients can then be seconded to service a CGI(plus) (i.e. non-WebSocket) request. When the CGI(plus) client disconnects WASD will run-down the process disconnecting the WebSocket clients in the process (so-to-speak). Not much can be done about this without having a WebSocket specific script (perhaps wrapper). Just an interesting wrinkle! NON-WEBSOCKET ------------- With Apache and OSU the browser establishes a XMLHttpRequest() request to the script and receives the JSON data via that (long-pull) connection. When it eventually is disconnected (request timeout, or other) the browser immediately restablishes the connection. Checking additional data such as disk, process, per-process is clunkier in non-WebSocket environments as the current (long-pull) request needs to be terminated and an entirely new request established and so disk and top-process data is always included and is available immediately to the CGI client, in preference to re-requesting, at the trade-off of a baseline greater network traffic and executable footprint. It's all quite insignificant though for all but a completely quiescent site. INTERFRAME COMMUNICATION (IFC) ------------------------------ The node(s) displayed in the parent page are not necessarily the same as the parent page (indeed, multiple nodes could not all be) and of course these have differing host names and/or IP addresses. This incurs browser cross-domain restrictions with communication between the node display HTML (i)frame and the primary, top-level page, specifically - when needing to open/close the history graph portion of the display associated with each node. The HTML5 postMessage() IPC is used to communicate between pages (frames). BUILD DETAILS ------------- See BUILD_MONDESI.COM procedure. $ @BUILD_MONDESI [LINK] INSTALLED IMAGE --------------- To get job modes using a wildcard $GETJPI scan SYSPRV is required. $ INSTALL ADD MONDESI.EXE /PRIV=(SYSPRV) Running this with other utilities (e.g. WASDMON) with channels allocated to a network device requres SHARE privilege. $ INSTALL ADD MONDESI.EXE /PRIV=(SHARE) Per-process display requires the image to be installed with WORLD privilege. System startup requires something like $ INSTALL ADD MONDESI.EXE /PRIV=(WORLD) possibly (see above) $ INSTALL ADD MONDESI.EXE /PRIV=(ALTRI,SHARE,SYSPRV,WORLD) and on executable image update $ INSTALL REPLACE MONDESI.EXE APACHE INSTALLATION ------------------- Copy the executable to the APACHE$ROOT:[CGI-BIN] directory. Map the JavaScript resources into web-space using (assumes the application is located under APACHE$COMMON:[000000]): Alias /mondesi/-/ "/apache$common/mondesi/runtime/" OSU ACCOMODATIONS ----------------- OSU requires the use of the MONDESI.COM DCL wrapper to invoke the OSU CGI environment utility before executing MONDESI. The wrapper and the MONDESI.EXE executable both should be placed in WWW_ROOT:[BIN]. Rather than introduce the bulk of WASD CGILIB this deliberately slimline monitor utility checks for an indication of the OSU environment and overtly handles the indiosyncracies. MONDESI does not reopen in binary mode. OSU requires two CGI response headers (MONDESI supplies "Status:" and "Content-Type:"). OSU also dislikes a response header with carriage-control, so MONDESI uses only an . Map the JavaScript resources into web-space using (assumes the application is located under WWW_ROOT:[000000]): pass /mondesi/-/* /www_root/mondesi/runtime/* WASD INSTALLATION ----------------- Copy the executable to the CGI_EXE: directory. Map the JavaScript resources into web-space using (assumes the application is located in the source tree): # HTTPD$MAP pass /mondesi/-/* /wasd_root/src/mondesi/runtime/* COPYRIGHT --------- Copyright (C) 2008-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!) --------------- 23-SEP-2023 MGD v7.3.0, bump version for JavaScript double height history expand .NetBytesPerSec to .NetBytesPerSec64 04-MAR-2023 MGD v7.2.0, add CPU modes alternate display 25-OCT-2021 MGD v7.1.0, builds under x86-64 VMS inline with WASD, moved to Apache 2.0 license 05-FEB-2021 MGD v7.0.0, VAX no longer implemented move to native 64 bit data storage add OPCOM monitoring may now be proctored 20-JUL-2019 MGD v6.2.2, "One small step ..." bugfix; WebSockAvailable() 03-NOV-2018 MGD v6.2.1, bugfix; JavaScript query string processing 20-MAR-2018 MGD v6.2.0, MONDESI_QUERY logical name 19-MAR-2018 MGD v6.1.1, use DVI$_VOLSIZE for disk capacity (except on VAX) 15-AUG-2015 MGD v6.1.0, collect and display disk I/O queue length simplify JsonAlert() using vax_vsnprintf() 20-APR-2015 MGD v6.0.1, add volume mount/dismount alerts extended refesh period alert (e.g. suspended) bugfix; ensure JsonAlert()s do not occur prematurely bugfix; CollectDiskSPI() and CollectDiskDvi() in the detection of mount verify in progress 16-MAR-2015 MGD v6.0.0, significant front- and back- end rework JavaScript front-end now fully generates display front- and back- ends exchange data via JSON WZ_GRAPHICS.JS obsolete in favour of HTML5 SVG non-HTML5 (older) browsers no longer supported dependency on $GETRMI removed in favour of $GETSPI (allowing deployment to VAX) SIMPLER64.H simplifies cross-platform 64 bit (on VAX) at some inconvenience on Alpha and Itanium 01-DEC-2013 MGD v5.0.1, CollectDiskDVI() use lround() on size bugfix; CollectSPI() use fixed size SCS buffer bugfix; CollectDiskDVI() test max not free 24-NOV-2013 MGD v5.0.0, migrate functionality from executable to JavaScript add process state count to top process display CollectDiskDVI() collects disk size/used CollectProcSPI1/2() process count >= max plus +10 CollectSPI() see 16-NOV-2013 quirk VAX-specifics removed from code after failing to get it working (simple things like $GETRMI FAULTS and BUFIO just refused to return data) 31-MAR-2013 MGD v4.0.0, if rapid major version jumps are good enough for Chrome and the like :-) per-process monitoring compile Alpha and Itanium using same IEEE float logical name MONDESI_PROC obsoleted 22-JAN-2013 MGD v3.0.1, JavaScript update; finally discovered what garbles printable WZ graphics on MSIE (or at least how to prevent it) - reset "jg_fast". few minor HTML and CSS tweaks Happy birthday Emily! 06-JAN-2013 MGD v3.0.0, significant rework top process monitoring individual disk monitoring redesign data delivery for consistency between concurrent WebSocket clients note the WebSocket required REDIRECT described above! Happy birthday Gnomie! 08-APR-2012 MGD v2.1.1, nudge MSIE 10.0 (Windows 8 preview) to do its best! 28-JAN-2012 MGD v2.1.0, migrate to WASD WebSocket wsLIB environment (after significant changes to WebSocket drafts) 23-OCT-2010 MGD v2.0.0, happy birthday Raupu add network interface (NI) data add processes add lock waits to LOCK display add SCS messages PGFL data is now shown by hovering over MEM data are now presented as units, kilo, mega, giga support WASD WebSocket environment bugfix; nodes across 03-JUN-2009 MGD v1.0.2, bumped the dot-point merely to identify the 3.05 version of wz_jsgraphics.js 13-DEC-2008 MGD v1.0.1, CollectSPI() allow for 7.3-1 and earlier (sigh!) thanks Graham Burley for suggesting sys$gl_*jobcnt 29-NOV-2008 MGD v1.0.0, initial */ #endif /* COMMENTS_WITH_COMMENTS */ /*****************************************************************************/ #define SOFTWAREVN "7.3.0" /* ^^^^^ don't forget to update MONDESI.JS compliance! */ #define SOFTWARENM "MONDESI" #define SOFTWARECR "Copyright (C) 2008-2023 Mark G.Daniel" #ifdef __ALPHA char SoftwareID [] = SOFTWARENM " AXP-" SOFTWAREVN; #endif #ifdef __ia64 char SoftwareID [] = SOFTWARENM " IA64-" SOFTWAREVN; #endif #ifdef __VAX # error VAX no longer implemented #endif #ifdef __x86_64 char SoftwareID [] = SOFTWARENM " X86-" SOFTWAREVN; #endif #ifndef OPCOM_BUFFER_SIZE #error build procedure must define OPCOM_BUFFER_SIZE #endif /* standard C header files */ #include #include #include #include #include #include #include #include #include #include /* VMS-related header files */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wslib.h" #ifndef UNT64PTR /* mainly to allow easy use of the __unaligned directive */ #define UINTPTR __unaligned unsigned int* #define ULONGPTR __unaligned unsigned long* #define USHORTPTR __unaligned unsigned short* #define UINT64PTR __unaligned uint64* #define INT64PTR __unaligned int64* #endif /* by default all structures on non-VAX are not member aligned */ #pragma __nomember_alignment /* build more detailed debug */ #define DEBUG 0 #define BOOL int #define TRUE 1 #define FALSE 0 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) /* VAX doesn't have round() */ #define ROUND(value) (floor((float)(value) + 0.5)) #define FI_LI "MONDESI", __LINE__ #define EXIT_FI_LI(status) \ { printf ("Status:500\r\n\r\n[%s:%d]\r\n", FI_LI); exit(status); } #define AMBIT_PRIORITY 6 #define PER_PROC_ALERT 15 /* percent remaining */ #define PAGE_FILE_ALERT 15 /* percent remaining */ #define REFRESH_INTERVAL 2 /* seconds */ #define REFRESH_ALERT 5 /* alert when five times longer than should be */ /* exit after fifteen minutes without a WebSocket connection */ #define EXIT_AFTER_SECONDS 15 * 60; /* maximum number of network devices */ #define NI_DEVICE_MAX 8 /* maximum number of disks that can be monitored */ #define DISK_DEVICE_MAX 32 #define DISK_NAME_SIZE 64 #define DISK_USAGE_SIZE 31 #define DISK_VOLNAM_SIZE 12 /* maximim number of disks readable from system */ #define DISK_OFFSETS_MAX 100 /* maximum number of processes that can be reported */ #define PROC_MAX 5 #define PROC_NAME_SIZE 15 /* maximum number of processes readable from system */ #define PROC_CLASS_MAX 1000 #define PROC_CLASS_INC 100 /* puts three successive quotations into the stream - impossible JSON data */ #define JSON_SENTINAL "\"\"\"" /* effectively disable WsLib's own timeout mechanism */ #define ALONGTIME 365*24*60*60 /* one year in seconds */ /* enable some test-bench behaviours */ #define TEST_BENCH 0 /* logical names */ #define LOGNAM_ACCESS "MONDESI_ACCESS" #define LOGNAM_DISK "MONDESI_DISK" #define LOGNAM_LIFETIME "MONDESI_LIFETIME" #define LOGNAM_NI "MONDESI_NI" #define LOGNAM_OPCOM "MONDESI_OPCOM" #define LOGNAM_PROC_ALERT "MONDESI_PROC_ALERT" #define LOGNAM_QUERY "MONDESI_QUERY" #define LOGNAM_REFRESH "MONDESI_REFRESH" #define LOGNAM_WEBSOCKET "MONDESI_WEBSOCKET" /*******************/ /* data structures */ /*******************/ /* required for VMS V7.3-1 and earlier */ #ifndef INTSTK_MODE # define INTSTK_MODE 0 # define MPSYNCH_MODE 1 # define KERNEL_MODE 2 # define EXEC_MODE 3 # define SUPER_MODE 4 # define USER_MODE 5 # define COMPAT_MODE 6 # define IDLE_MODE 7 #endif #define MODE_COUNT IDLE_MODE+1 #define CPU_MAX 32 typedef struct STRUCT_IOSBLK { ushort iosb$w_status; ushort iosb$w_bcnt; uint iosb$l_reserved; } IOSBLK; /* from SPIDEF.H REMIDEF.H */ /* these members are aligned on non-VAX */ # pragma __member_alignment __save # pragma __member_alignment # ifdef __INITIAL_POINTER_SIZE /* and 32 bit pointers */ # pragma __required_pointer_size __save # pragma __required_pointer_size __short # endif typedef struct STRUCT_SYSTEM_DATA { int ClusterSummaryCount, CpuBusyPercent, CpuTicksBusy, CpuTicksUser, CpuUserPercent, DiskBufferLength, DynamicBufferLength, MonValLength, NodeInfoLength, NodeInfoTimeStamp, PrevInit, PrevMinute, ProcBufferLength, ProcBufferStatus, ProvideProcessData, StaticBufferLength, RuntimeBufferLength, SystemDelta, UsageCount; int ModePercent [MODE_COUNT]; ulong ActiveCpuCount, AvailCpuCount, BufferedIO, ClusterMsgSent, ClusterMsgRcvd, ClusterKbyteMap, ClusterKbyteSent, ClusterKbyteRcvd, ClusterMember, ClusterEVotes, ClusterNodes, ClusterQuorum, ClusterVotes, CpuCount, DirectIO, FcpRead, FcpWrite, FreeList, HardPageFaults, JpiPid, LockActivity, LockWait, MemInUse, MemInUseMax, MemSize, ModList, MscpRead, MscpWrite, NetDatagramRx, NetDatagramTx, PageFaults, PageFaultsHard, PageFaultsSoft, PageFileFree, PageFilePage, PageFileUsed, PageFilePercent, PageReadIO, PageSize, PageWriteIO, PrevBufferedIO, PrevBusyCpu, PrevClusterMsgSent, PrevClusterMsgRcvd, PrevClusterKbyteMap, PrevClusterKbyteSent, PrevClusterKbyteRcvd, PrevClusterNodes, PrevDirectIO, PrevFcpRead, PrevFcpWrite, PrevHardPageFaults, PrevLockActivity, PrevLockWait, PrevMemInUse, PrevMscpRead, PrevMscpWrite, PrevPageFaults, PrevPageFilePercent, PrevProcessCount, PrevProcessIntCount, PrevUserCpu, ProcessCount, ProcessBatCount, ProcessIntCount, ProcessNetCount, ProcessOthCount, Processes; int64 NetBytesPerSec64, NetBytesRxPerSec64, NetBytesTxPerSec64, NetIntBlocksRx64, NetIntBlocksTx64, NetIntBytesRx64, NetIntBytesRxTx64, NetIntBytesTx64, NetIntErrorsHard64, NetIntErrorsSoft64, PrevNetIntBlocksRx64, PrevNetIntBlocksTx64, PrevNetIntBytesRx64, PrevNetIntBytesTx64, PrevNetIntErrorsHard64; char *HttpdVersion; char MonDeSiPrcNam [15+1], ArchName [15+1], HwName [60+1], NodeName [15+1], VmsVersion [8+1]; int64 BootTime64, PageFileFreeBytes64, PageFilePageBytes64, PageFileUsedBytes64, UpTime64; char ClusterSummary [2048], DiskBuffer [2048], DynamicBuffer [2048], ProcBuffer [512+(48*6*PROC_MAX)], RuntimeBuffer [256], StaticBuffer [1024]; int DiskCount, MountCount; int DiskNameLen [DISK_DEVICE_MAX]; char DiskName [DISK_DEVICE_MAX][DISK_NAME_SIZE+1], DiskVolName [DISK_DEVICE_MAX][DISK_VOLNAM_SIZE+1]; ulong DiskErrCnt [DISK_DEVICE_MAX], DiskFreeBlocks [DISK_DEVICE_MAX], DiskIOPs [DISK_DEVICE_MAX], DiskMntVerIP [DISK_DEVICE_MAX], DiskOptCnt [DISK_DEVICE_MAX], DiskPrevErrCnt [DISK_DEVICE_MAX], DiskPrevMntVerIP [DISK_DEVICE_MAX], DiskPrevOptCnt [DISK_DEVICE_MAX], DiskPrevQueCnt [DISK_DEVICE_MAX], DiskQueCnt [DISK_DEVICE_MAX], DiskQueLen [DISK_DEVICE_MAX], DiskVolSize [DISK_DEVICE_MAX]; int ProcSanityCheck; char ProcName [PROC_MAX][PROC_NAME_SIZE+1]; ulong ProcBioCnt [PROC_MAX], ProcCpuTim [PROC_MAX], ProcDioCnt [PROC_MAX], ProcEpid [PROC_MAX], ProcPageFlts [PROC_MAX]; void (*ProcSpiFunction)(); } SYSTEM_DATA; #define ACCESS_GENERAL 0 #define ACCESS_TOP_PROCESS 1 #define ACCESS_PER_PROCESS 2 #define ACCESS_OPCOM 3 #define ACCESS_SIZE 4 /* used for multiple per-WebSocket client and CGI client (for convenience) */ typedef struct STRUCT_CLIENT_DATA { BOOL Initialised, OpcomAccess; BOOL AccessAllowed [ACCESS_SIZE]; int NodeInfoTimeStamp, OpcomCount, PerProcBufferLength, PerProcDatum, PerProcPid, PrevPerProcPid, PrevClusterNodes, PrevClusterSummaryCount, ProvideAuditData, ProvideDiskData, ProvideOpcomData, ProvideProcessData; ulong PrevJpiBufIo, PrevJpiDirIo, PrevJpiPageFlts, PrevCpuTime, PrevCpuSekTime; char InputBuffer [256], /* general plus OPCOM */ OutputBuffer [2048+OPCOM_BUFFER_SIZE], PerProcBuffer [2048]; struct WsLibStruct *WsLibPtr; } CLIENT_DATA; #pragma __member_alignment __restore /******************/ /* global storage */ /******************/ BOOL dbug, Clidbug, DataViaCGI, HttpdApache, HttpdOSU, HttpdWASD; int CgiNodeInfoTimeStamp, ConnectedCount, IsCgiPlus, PerProcAlert = PER_PROC_ALERT, PrevPrio, ProctoredScript = -1, RefreshInterval, UsageCount; uint ProvideDataSeconds, ExitTimeStamp, InitTimeStamp, EfnSpi, TimeStamp; int64 CollectTime64, CurrentTime64, StartTime64, ProvideTime64; uint EfnWait; ushort CurrentNumTime [7]; float DeltaSeconds = 999999.0; char *CgiHttpRefererPtr, *CgiHttpUserAgentPtr, *CgiRemoteAddrPtr, *CgiRemoteUserPtr, *CgiRequestUriPtr, *CgiServerSoftwarePtr, *CgiQueryStringPtr, *OpcomEnable; char HttpdVersion [32], MondesiQuery [255+1], NetIntDev [NI_DEVICE_MAX*24], RefererHost [128]; CLIENT_DATA CgiClientData; SYSTEM_DATA SystemData; /* sys$gettim_prec() arrived with IA64 V8.4 */ #if defined(sys$gettim_prec) /* may produce a "%LINK-I-UDFSYM, GETTIM_PREC" which can safely be ignored */ int (*GetTimFunction)() = sys$gettim_prec; #else int (*GetTimFunction)() = sys$gettim; #endif /* PRV$M_WORLD is optional */ ulong NeedPrivMask [2] = { PRV$M_SHARE | PRV$M_SYSPRV, 0 }; extern uint OpaAuditCount, OpaBufferLength, OpaOpcomCount; extern ulong OpaJpiPid, OpaSpawnPid, OpaStatus, OpaSpawnStatus; extern char *OpaBufferPtr, *OpaNonAuditPtr; extern char OpaTime[]; /**************/ /* prototypes */ /**************/ void AccessControl (CLIENT_DATA*); void ProvideData (); void CollectDiskSPI (); void CollectDiskDVI (); void CollectJobModes(); void CollectNetIntData (); int CollectPerProc (CLIENT_DATA*); void CollectProcSPI1 (); void CollectProcSPI2 (); int CollectSPI (); int CollectSyiDynamic (); int CollectSyiStatic (); void JsonAlert (CLIENT_DATA*, char*, ...); void JsonDiskData (); void JsonDynamicData (CLIENT_DATA*); char* JsonOpcomData (CLIENT_DATA*); void JsonRuntime (); void JsonServerSoftware(); void JsonStaticData (); char* MungeProcName (char*); char* MungeString (char*); char* MungeTime (int64*); int ProvideOpcomData (CLIENT_DATA*); void ReturnPrio (); void Setdbug (int); char* SysTrnLnm (char*, char*, int); void AddClient (); int HavePriv (ulong*); int MinimumWASD (char*); void ReadClient (struct WsLibStruct*); void RemoveClient (struct WsLibStruct*); void ScriptCallout (char *fmt, ...); int WebSockAvailable (); void XhrResponse (); int OpaSpawn (char*); extern int exe$getspi (__unknown_params); /*****************************************************************************/ /* */ int main (int argc, char *argv[]) { int status, NodesAcross, ViewWidth; char *cptr, *sptr, *zptr; char IframeId [64], NodeList [512], MonDeSiPrcNam [32]; $DESCRIPTOR (PrcNamDsc, ""); /*********/ /* begin */ /*********/ if (argc > 1) { if (!strcasecmp (argv[1], "/VERSION")) { fprintf (stdout, "%%MONDESI-I-VERSION, %s %s\n", SoftwareID, WsLibVersion()); exit (SS$_NORMAL); } if (!strcasecmp (argv[1], "/DBUG")) Clidbug = TRUE; } #if !defined(__ALPHA) /* cater for "%LINK-I-UDFSYM, GETTIM_PREC" on unsupported platform */ if (GetTimFunction == NULL) GetTimFunction = sys$gettim; #endif if (VMSnok (status = lib$get_ef (&EfnWait))) EXIT_FI_LI (status); if (VMSnok (status = lib$get_ef (&EfnSpi))) EXIT_FI_LI (status); GetTimFunction (&StartTime64); if (!HavePriv (NeedPrivMask)) EXIT_FI_LI (SS$_NOPRIV); /* ambit attempt to bump the process priority (if INSTALLed with ALTPRI) */ atexit (&ReturnPrio); sys$setpri (0, 0, AMBIT_PRIORITY, &PrevPrio, 0, 0); HttpdApache = (getenv ("APACHE$SHARED_SOCKET") != NULL || getenv ("APACHE$PARENT_PID") != NULL || getenv ("APACHE$SERVER_TAG") != NULL); HttpdOSU = (getenv("WWWEXEC_RUNDOWN_STRING") != NULL); HttpdWASD = (getenv("HTTP$INPUT") != NULL); Setdbug (1); IsCgiPlus = WsLibIsCgiPlus (); cptr = SysTrnLnm (LOGNAM_PROC_ALERT, NULL, 0); if (cptr) PerProcAlert = atoi(cptr); if (PerProcAlert <= 0 || PerProcAlert > 100) PerProcAlert = PER_PROC_ALERT; cptr = SysTrnLnm (LOGNAM_QUERY, NULL, 0); if (cptr) strcpy (MondesiQuery, cptr); cptr = SysTrnLnm (LOGNAM_REFRESH, NULL, 0); if (cptr) RefreshInterval = atoi(cptr); if (RefreshInterval <= 0 || RefreshInterval > 60) RefreshInterval = REFRESH_INTERVAL; GetTimFunction (&CurrentTime64); InitTimeStamp = decc$fix_time (&CurrentTime64); memset (&SystemData, 0, sizeof(SystemData)); CollectSyiStatic (&SystemData); if (SystemData.MonDeSiPrcNam[0] != '/') { /* not v12.0 or later process naming */ sprintf (MonDeSiPrcNam, "MonDeSi_%4.4X", SystemData.JpiPid & 0xffff); PrcNamDsc.dsc$a_pointer = MonDeSiPrcNam; PrcNamDsc.dsc$w_length = strlen(MonDeSiPrcNam); if (!(status = sys$setprn (&PrcNamDsc) & 1)) EXIT_FI_LI (status); } CollectNetIntData (); CollectDiskSPI (&SystemData); SystemData.ProcSpiFunction = &CollectProcSPI1; JsonStaticData (); if (dbug = Clidbug) { DataViaCGI = CgiClientData.Initialised = CgiClientData.ProvideProcessData = TRUE; CgiClientData.AccessAllowed[ACCESS_TOP_PROCESS] = TRUE; CgiClientData.AccessAllowed[ACCESS_PER_PROCESS] = TRUE; CgiClientData.AccessAllowed[ACCESS_OPCOM] = TRUE; ProvideData (); for(;;) sys$hiber(); } ProvideData (); for (;;) { /* with CGIplus this call will block waiting for the next request */ WsLibCgiVar (""); Setdbug (1); if (HttpdWASD) { if (ProctoredScript < 0) { /* check for proctor instantiation */ ProctoredScript = !*WsLibCgiVar ("REMOTE_ADDR") && !*WsLibCgiVar ("SERVER_ADDR"); if (ProctoredScript) { ScriptCallout ("!LIFETIME: DO-NOT-DISTURB\n"); fprintf (stdout, "Status: 204\n\n"); WsLibCgiPlusEof (); continue; } } /* start by disabling the WsLib..() idle timeout */ WsLibSetLifeSecs (ALONGTIME); if ((cptr = SysTrnLnm (LOGNAM_LIFETIME, NULL, 0)) != NULL) { /* the op wants me to live this long (non-integer means DND) */ ScriptCallout ("!LIFETIME: DO-NOT-DISTURB\n"); if (isdigit(*cptr)) { /* this many minutes after the most recent request */ WsLibSetLifeSecs ((atol(cptr)*60)+1); } } } /********************/ /* request received */ /********************/ UsageCount++; CgiHttpRefererPtr = WsLibCgiVar("HTTP_REFERER"); CgiHttpUserAgentPtr = WsLibCgiVar("HTTP_USER_AGENT"); CgiQueryStringPtr = WsLibCgiVar("QUERY_STRING"); CgiRemoteAddrPtr = WsLibCgiVar("REMOTE_ADDR"); CgiRemoteUserPtr = WsLibCgiVar("REMOTE_USER"); CgiRequestUriPtr = WsLibCgiVar("REQUEST_URI"); if (!CgiServerSoftwarePtr) JsonServerSoftware (); /* derive the URL scheme and host from the HTTP referrer field */ zptr = (sptr = RefererHost) + sizeof(RefererHost)-1; cptr = CgiHttpRefererPtr; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; AccessControl (&CgiClientData); if (!CgiClientData.AccessAllowed[ACCESS_GENERAL]) fprintf (stdout, "Status: 403\r\n\r\nAccess forbidden!\n"); else if (!strncmp (CgiQueryStringPtr, "data=1", 6)) { /********/ /* data */ /********/ if (DataViaCGI = !WsLibCgiVarNull ("WEBSOCKET_INPUT")) { XhrResponse (); /* ProvideData() now drives it and should never ... */ for(;;) sys$hiber(); } if (!WebSockAvailable()) { fprintf (stdout, "Status: 500\r\n\r\nWebSocket not available!\n"); exit (SS$_NORMAL); } if (!IsCgiPlus) { fprintf (stdout, "Status: 500\r\n\r\nMust be CGIplus!\n"); exit (SS$_NORMAL); } AddClient (); } else { /********/ /* page */ /********/ if (WebSockAvailable()) { if (!IsCgiPlus) { if (!strncasecmp (cptr = CgiRequestUriPtr, "/cgi-bin/", 9)) { /* redirect to "standard" CGIplus path */ fprintf (stdout, "Status: 302\r\n\ Location: /cgiplus-bin/%s\r\n\ \r\n\ Must be CGIplus!\n", cptr+9); } else { /* not the "standard" path */ fprintf (stdout, "Status: 500\r\n\r\nMust be CGIplus!\n"); } exit (SS$_NORMAL); } } fprintf (stdout, "Content-Type: text/html\r\n\ \r\n\ \n\ \n\ \n\ MonDeSi\n\ \n\ \n\ \n\ \n\ \n\ \n\ MonDeSi v%s \n\ \n\ \n", SOFTWARECR, SOFTWAREVN, WsLibCgiVar("SCRIPT_NAME"), WebSockAvailable() ? "true" : "false", SOFTWAREVN); } Setdbug (0); if (!IsCgiPlus) break; memset (&CgiClientData, 0, sizeof(CgiClientData)); WsLibCgiPlusEof (); } exit (SS$_NORMAL); } /*****************************************************************************/ /* Return to previous process priority (in case ambit raise was successful). */ void ReturnPrio () { int status; /*********/ /* begin */ /*********/ if (!PrevPrio) return; status = sys$setpri (0, 0, PrevPrio, 0, 0, 0); if (dbug) fprintf (stdout, "sys$setpri() %%X%08.08X\n", status); } /*****************************************************************************/ /* Allocate a client structure and add it to the head of the list. Establish the WebSocket IPC and begin processing. */ void AddClient () { int status; char *sptr; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (!(clptr = calloc (1, sizeof(CLIENT_DATA)))) EXIT_FI_LI (vaxc$errno); /* create a WebSocket library structure for the client */ if (!(clptr->WsLibPtr = WsLibCreate (clptr, RemoveClient))) { /* failed, commonly on some WebSocket protocol issue */ return; } /* open the IPC to the WebSocket (mailboxes) */ status = WsLibOpen (clptr->WsLibPtr); if (VMSnok (status)) EXIT_FI_LI (status); WsLibWatchScript (clptr->WsLibPtr, FI_LI, "!AZ", SoftwareID); /* provide the static system data */ WsLibWrite (clptr->WsLibPtr, SystemData.StaticBuffer, SystemData.StaticBufferLength, WSLIB_ASYNCH); AccessControl (clptr); if (clptr->AccessAllowed[ACCESS_TOP_PROCESS]) { if (clptr->AccessAllowed[ACCESS_PER_PROCESS]) sptr = "{\"$data\":\"access\",\"process\":2}"; else sptr = "{\"$data\":\"access\",\"process\":1}"; } else sptr = "{\"$data\":\"access\",\"process\":0}"; WsLibWrite (clptr->WsLibPtr, sptr, strlen(sptr), WSLIB_ASYNCH); clptr->Initialised = TRUE; /* queue an asynchronous read from the client */ WsLibRead (clptr->WsLibPtr, clptr->InputBuffer, sizeof(clptr->InputBuffer)-1, ReadClient); ConnectedCount++; } /*****************************************************************************/ /* Remove the client structure from the list and free the memory. */ void RemoveClient (struct WsLibStruct *wsptr) { struct MonClient *clptr; /*********/ /* begin */ /*********/ clptr = WsLibGetUserData(wsptr); if (ConnectedCount) ConnectedCount--; } /*****************************************************************************/ /* Read WebSocket write from the browser. */ void ReadClient (struct WsLibStruct *wsptr) { char *cptr; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "ReadClient() %%X%08.08X\n", WsLibReadStatus (wsptr)); if (VMSnok (WsLibReadStatus (wsptr))) { WsLibClose (wsptr, 0, NULL); return; } clptr = WsLibGetUserData (wsptr); /* ensure it's null-terminated */ clptr->InputBuffer[WsLibReadCount(wsptr)] = '\0'; if (strstr (clptr->InputBuffer, "audit=1")) clptr->ProvideAuditData = 1; else if (strstr (clptr->InputBuffer, "audit=0")) clptr->ProvideAuditData = 0; if (strstr (clptr->InputBuffer, "disk=1")) { clptr->ProvideDiskData = TRUE; if (SystemData.DiskBufferLength) WsLibWrite (wsptr, SystemData.DiskBuffer, SystemData.DiskBufferLength, WSLIB_ASYNCH); } else if (strstr (clptr->InputBuffer, "disk=0")) clptr->ProvideDiskData = FALSE; if (strstr (clptr->InputBuffer, "opcom=1")) clptr->ProvideOpcomData = 1; else if (strstr (clptr->InputBuffer, "opcom=0")) clptr->ProvideOpcomData = 0; if (cptr = strstr (clptr->InputBuffer, "pid=")) { if (clptr->AccessAllowed[ACCESS_PER_PROCESS]) if (clptr->PerProcPid = strtol(cptr+4,NULL,16)) { CollectPerProc (clptr); if (clptr->PerProcBufferLength) WsLibWrite (wsptr, clptr->PerProcBuffer, clptr->PerProcBufferLength, WSLIB_ASYNCH); } } if (strstr (clptr->InputBuffer, "process=1")) { if (clptr->AccessAllowed[ACCESS_TOP_PROCESS]) { clptr->ProvideProcessData = TRUE; if (SystemData.ProcBufferLength) WsLibWrite (wsptr, SystemData.ProcBuffer, SystemData.ProcBufferLength, WSLIB_ASYNCH); } } else if (strstr (clptr->InputBuffer, "process=0")) clptr->ProvideProcessData = FALSE; /* read next */ WsLibRead (wsptr, clptr->InputBuffer, sizeof(clptr->InputBuffer)-1, ReadClient); } /*****************************************************************************/ /* For an XMLHttpRequest() HTTP request. The response body will stream chunks of JSON data to be parsed by the browser JavaScript receiving it. Do the best we can to disable proxy caching ensuring a timely flow of response data. */ void XhrResponse () { char *cptr; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "XhrResponse()\n"); clptr = &CgiClientData; if (cptr = strstr (CgiQueryStringPtr, "pid=")) if (clptr->PerProcPid = strtol(cptr+4,NULL,16)) CollectPerProc (clptr); /* with CGI just include (if authorised) the OPCOM data */ clptr->ProvideOpcomData = 1; fprintf (stdout, "Status: 200\n\ Content-Type: text/html\n\ Script-Control: X-stream-mode=1\n\ Script-Control: X-content-encoding-gzip=0\n\ Pragma: no-cache\n\ Cache-Control: no-cache, no-store, private\n\ \n"); fputs (SystemData.StaticBuffer, stdout); fputs (JSON_SENTINAL, stdout); if (clptr->AccessAllowed[ACCESS_TOP_PROCESS]) { if (clptr->AccessAllowed[ACCESS_PER_PROCESS]) fputs ("{\"$data\":\"access\",\"process\":2}", stdout); else fputs ("{\"$data\":\"access\",\"process\":1}", stdout); } else fputs ("{\"$data\":\"access\",\"process\":0}", stdout); fputs (JSON_SENTINAL, stdout); fflush (stdout); CgiClientData.Initialised = TRUE; ConnectedCount++; } /*****************************************************************************/ /* Send a JSON data structure containing a message to be displayed as an alert. */ void JsonAlert ( CLIENT_DATA *ClientPtr, char *FormatString, ... ) { static char CurTime [24]; static $DESCRIPTOR (CurTimeDsc, CurTime); static $DESCRIPTOR (CurTimeFaoDsc, "!20%D\0"); int len; char jsonbuf [512], msgbuf [256]; va_list argptr; CLIENT_DATA *clptr; struct WsLibStruct *wsctx, *wsptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "JsonAlert()\n"); va_start (argptr, FormatString); len = vsnprintf (msgbuf, sizeof(msgbuf), FormatString, argptr); va_end (argptr); if (len >= sizeof(msgbuf)) EXIT_FI_LI (SS$_RESULTOVF); sys$fao (&CurTimeFaoDsc, 0, &CurTimeDsc, &CurrentTime64); if (CurTime[0] == ' ') CurTime[0] = '0'; len = snprintf (jsonbuf, sizeof(jsonbuf), "{\"$data\":\"alert\",\"time\":\"%s\",\"message\":\"%s\"}", CurTime, msgbuf); if (len >= sizeof(jsonbuf)) EXIT_FI_LI (SS$_RESULTOVF); if (DataViaCGI) { if (CgiClientData.Initialised) { fputs (jsonbuf, stdout); fputs (JSON_SENTINAL, stdout); fflush (stdout); } } wsctx = NULL; while (wsptr = WsLibNext(&wsctx)) { clptr = WsLibGetUserData (wsptr); if (!clptr->Initialised) continue; if (ClientPtr && clptr != ClientPtr) continue; /* set and forget; buffer each message internally */ WsLibSetBuffer (clptr->WsLibPtr); WsLibWrite (wsptr, jsonbuf, len, WSLIB_ASYNCH); /* revert to requiring an external, persistent buffer */ WsLibSetNoBuffer (clptr->WsLibPtr); } } /*****************************************************************************/ /* Indepdendent, timer AST driven update of the system performance data. This data is then written to connected clients. */ void ProvideData () { static const long cvtf_mode = LIB$K_DELTA_SECONDS_F; static ulong PrevTimeStamp, EfnTmr; static int64 PrevTime64, SecondsDelta64; int status; ulong fltf; /* generic 32 bits for the VAX float */ int64 DiffTime64; char *aptr, *cptr, *sptr, *zptr; struct WsLibStruct *wsctx, *wsptr; CLIENT_DATA *clptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "\nProvideData()\n"); if (!EfnTmr) { status = lib$get_ef (&EfnTmr); if (VMSnok (status)) EXIT_FI_LI (status); } GetTimFunction (&CurrentTime64); sys$numtim (&CurrentNumTime, &CurrentTime64); TimeStamp = decc$fix_time (&CurrentTime64); /* alert if the refresh period has been overly long */ if (TimeStamp > PrevTimeStamp + (RefreshInterval * REFRESH_ALERT)) if (PrevTimeStamp) JsonAlert (NULL, "!Refresh lasted %d seconds!", TimeStamp - PrevTimeStamp); PrevTimeStamp = TimeStamp; if (!PrevTime64) { PrevTime64 = CurrentTime64; if (dbug) fprintf (stdout, "%d %s\n", SystemData.StaticBufferLength, SystemData.StaticBuffer); } /* calculate a floating point delta time */ status = lib$sub_times (&CurrentTime64, &PrevTime64, &DiffTime64); if (VMSnok (status)) EXIT_FI_LI (status); PrevTime64 = CurrentTime64; #ifdef __ALPHA /* Alpha 7.3-1 does not have lib$cvts_..() */ status = lib$cvtf_from_internal_time (&cvtf_mode, &fltf, &DiffTime64); /* convert VAX to IEEE float */ if (VMSok (status)) status = cvt$convert_float (&fltf, CVT$K_VAX_F, &DeltaSeconds, CVT$K_IEEE_S, CVT$M_ROUND_TO_NEAREST); #else status = lib$cvts_from_internal_time (&cvtf_mode, &DeltaSeconds, &DiffTime64); #endif if (VMSnok (status)) EXIT_FI_LI (status); if (dbug) fprintf (stdout, "DeltaSeconds: %f\n", DeltaSeconds); if (!OpaSpawnPid) { /* there appears to be no OPCOM monitor */ if (OpcomEnable) free (OpcomEnable); if (DataViaCGI) OpcomEnable = "!"; else OpcomEnable = ""; if (cptr = SysTrnLnm (LOGNAM_OPCOM, NULL, 0)) { if (DataViaCGI) if (*cptr == '+') /* enable OPCOM over CGI */ OpcomEnable = strdup(cptr+1); else OpcomEnable = "!"; /* disable OPCOM completely */ else OpcomEnable = strdup(cptr); } if (OpcomEnable[0] != '!') { status = OpaSpawn (OpcomEnable); if (VMSnok (status)) { if (!OpaOpcomCount) OpaOpcomCount++; sprintf (OpaTime, "%%X%08.08X", status); } } } clptr = &CgiClientData; /* determine whether process data should be collected (expensive!) */ if (DataViaCGI) SystemData.ProvideProcessData = 1; else { /* WebSocket access */ SystemData.ProvideProcessData = 0; wsctx = NULL; while (wsptr = WsLibNext(&wsctx)) { clptr = WsLibGetUserData (wsptr); if (clptr->ProvideProcessData) SystemData.ProvideProcessData = clptr->ProvideProcessData; } } CollectNetIntData (); #if TEST_BENCH { static int ErrCnt; if (!(TimeStamp % 13)) ErrCnt += TimeStamp % 4; PUT_LONG_QUAD(ErrCnt,SystemData.NetIntErrorsHard); } #endif if (SystemData.PrevNetIntErrorsHard64 && SystemData.NetIntErrorsHard64 > SystemData.PrevNetIntErrorsHard64) { /* surely there will never be more than 4 billion errors :-{ */ JsonAlert (NULL, "!NI error count from %llu to %llu", SystemData.PrevNetIntErrorsHard64, SystemData.NetIntErrorsHard64); } SystemData.PrevNetIntErrorsHard64 = SystemData.NetIntErrorsHard64; CollectSyiDynamic (); CollectSPI (); CollectDiskSPI (); (*SystemData.ProcSpiFunction)(); JsonDynamicData (clptr); JsonDiskData (); #if TEST_BENCH if (!(TimeStamp % 9)) SystemData.PageFilePercent = TimeStamp % 100; else SystemData.PageFilePercent = SystemData.PrevPageFilePercent; #endif if (SystemData.PageFilePercent < SystemData.PrevPageFilePercent && SystemData.PageFilePercent >= 100 - PAGE_FILE_ALERT) JsonAlert (NULL, "!Page file free space %d%%", SystemData.PageFilePercent); SystemData.PrevPageFilePercent = SystemData.PageFilePercent; GetTimFunction (&CollectTime64); if (DataViaCGI) { /*******/ /* CGI */ /*******/ if (SystemData.DynamicBufferLength) { fputs (SystemData.DynamicBuffer, stdout); fputs (JSON_SENTINAL, stdout); } if (SystemData.ClusterSummaryCount > clptr->PrevClusterSummaryCount) { fputs (SystemData.ClusterSummary, stdout); fputs (JSON_SENTINAL, stdout); } if (SystemData.DiskBufferLength) { fputs (SystemData.DiskBuffer, stdout); fputs (JSON_SENTINAL, stdout); } if (clptr->AccessAllowed[ACCESS_TOP_PROCESS]) { if (SystemData.ProcBufferLength) { fputs (SystemData.ProcBuffer, stdout); fputs (JSON_SENTINAL, stdout); } if (clptr->AccessAllowed[ACCESS_PER_PROCESS]) if (clptr->PerProcPid) { CollectPerProc (clptr); if (clptr->PerProcBufferLength) { fputs (clptr->PerProcBuffer, stdout); fputs (JSON_SENTINAL, stdout); } } } if (clptr->ProvideOpcomData) { /* non-WebSocket always provides OPCOM buffer if permitted */ clptr->OpcomCount = OpaOpcomCount; cptr = JsonOpcomData (clptr); if (cptr) fprintf (stdout, "{\"$data\":\"opcom\",\"opcomBuffer\":\"%s\"}", cptr); } if (SystemData.RuntimeBufferLength) { if (HttpdWASD) fprintf (stdout, "%s,\"CGIplus\":%s}", SystemData.RuntimeBuffer, IsCgiPlus ? "true" : "false"); else fprintf (stdout, "%s}", SystemData.RuntimeBuffer); fputs (JSON_SENTINAL, stdout); } fflush (stdout); } wsctx = NULL; while (wsptr = WsLibNext(&wsctx)) { /*************/ /* WebSocket */ /*************/ clptr = WsLibGetUserData (wsptr); /* place into persistent buffer */ zptr = (sptr = clptr->OutputBuffer) + sizeof(clptr->OutputBuffer)-1; if (SystemData.DynamicBufferLength) { for (cptr = SystemData.DynamicBuffer; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (SystemData.ClusterSummaryCount > clptr->PrevClusterSummaryCount) { clptr->PrevClusterSummaryCount = SystemData.ClusterSummaryCount; for (cptr = SystemData.ClusterSummary; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (clptr->ProvideDiskData) if (SystemData.DiskBufferLength) { for (cptr = SystemData.DiskBuffer; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (clptr->ProvideProcessData) { if (clptr->AccessAllowed[ACCESS_TOP_PROCESS]) { if (SystemData.ProcBufferLength) { for (cptr = SystemData.ProcBuffer; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (clptr->PerProcPid) { if (clptr->AccessAllowed[ACCESS_PER_PROCESS]) { CollectPerProc (clptr); if (clptr->PerProcBufferLength) { for (cptr = clptr->PerProcBuffer; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } } } } } if (clptr->ProvideOpcomData) if (clptr->ProvideOpcomData == 1 || clptr->ProvideAuditData == 1 || clptr->OpcomCount != OpaOpcomCount) { clptr->OpcomCount = OpaOpcomCount; aptr = JsonOpcomData (clptr); if (aptr) { for (cptr = "{\"$data\":\"opcom\",\"opcomBuffer\":\""; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = "\"}"; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } } if (SystemData.RuntimeBufferLength) { for (cptr = SystemData.RuntimeBuffer; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = ",\"WebSocket\":true}"; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = JSON_SENTINAL; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (sptr > zptr) EXIT_FI_LI (SS$_RESULTOVF); WsLibWrite (wsptr, clptr->OutputBuffer, sptr - clptr->OutputBuffer, WSLIB_ASYNCH); } /*************/ /* and again */ /*************/ SystemData.PrevMinute = CurrentNumTime[4]; /* one to sixty seconds, just multiply by interval */ if (!SecondsDelta64) SecondsDelta64 = RefreshInterval * -10000000; status = sys$setimr (EfnTmr, &SecondsDelta64, ProvideData, NULL, 0); if (VMSnok(status)) EXIT_FI_LI (status); ProvideDataSeconds += RefreshInterval; GetTimFunction (&ProvideTime64); JsonRuntime (); } /*****************************************************************************/ /* Provide some runtime data. Average duration of each data collection and total monitor cycle (to differentiate the time required to write the data to the client(s)), average CPU consumed, average user and non-user CPU modes. All in seconds. With VAX clock granularity, durations may fail to register if sub-10mS. This data is not available to the end-user, is for developer and technical insight purposes, and can be seen from the data stream. */ void JsonRuntime () { static int64 CollectTotalTime64, ProvideTotalTime64; static long CpuSekInit, CpuTimInit, CpuUsrInit, JpiCpuTim, JpiExecTim, JpiKrnlTim, JpiSuprTim, JpiUserTim, StatCount; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemListJpi[] = { { sizeof(JpiCpuTim), JPI$_CPUTIM, &JpiCpuTim, 0 }, { sizeof(JpiExecTim), JPI$_EXECTIM, &JpiExecTim, 0 }, { sizeof(JpiKrnlTim), JPI$_KRNLTIM, &JpiKrnlTim, 0 }, { sizeof(JpiSuprTim), JPI$_SUPRTIM, &JpiSuprTim, 0 }, { sizeof(JpiUserTim), JPI$_USERTIM, &JpiUserTim, 0 }, {0,0,0,0} }; int status; ulong CpuUSR, CpuSEK, CpuTIM; int64 RunTime64, DiffTime64; char *sptr, *zptr; float fcount; IOSBLK IOsb; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "\nJsonRuntime()\n"); status = sys$getjpiw ( EfnWait, /* efn */ 0, /* pidaddr */ 0, /* prcnam */ &ItemListJpi, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) EXIT_FI_LI (status); if ((fcount = (float)(StatCount++)) == 0.0) { /* track and offset initial setup overheads */ CpuTimInit = JpiCpuTim; CpuUsrInit = JpiUserTim; CpuSekInit = JpiSuprTim + JpiExecTim + JpiKrnlTim; return; } CollectTime64 -= CurrentTime64; CollectTotalTime64 += CollectTime64; ProvideTime64 -= CurrentTime64; ProvideTotalTime64 += ProvideTime64; lib$sub_times (&CurrentTime64, &StartTime64, &RunTime64); CpuTIM = JpiCpuTim - CpuTimInit; CpuUSR = JpiUserTim - CpuUsrInit; CpuSEK = JpiSuprTim + JpiExecTim + JpiKrnlTim - CpuSekInit; zptr = (sptr = SystemData.RuntimeBuffer) + sizeof(SystemData.RuntimeBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"runtime\",\ \"cpu\":%f,\ \"cpuTIM\":%f,", (float)JpiCpuTim / 100.0, (float)CpuTIM / 100.0 / fcount); sptr += snprintf (sptr, zptr-sptr, "\"cpuUSR\":%f,\ \"cpuSEK\":%f,", (float)CpuUSR / 100.0 / fcount, (float)CpuSEK / 100.0 / fcount); sptr += snprintf (sptr, zptr-sptr, "\"run\":\"%s\",\ \"collect\":%f,\ \"provide\":%f", MungeTime(&RunTime64), (float)CollectTotalTime64 / 10000000.0 / fcount, (float)ProvideTotalTime64 / 10000000.0 / fcount); if (HttpdWASD) { sptr += snprintf (sptr, zptr-sptr, ",\"usage\":%d,\ \"connected\":%d", UsageCount, ConnectedCount); } /* bit shonky but JSON structure will be completed in ProvideData() */ if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.RuntimeBufferLength = sptr - SystemData.RuntimeBuffer; } /*****************************************************************************/ /* Create a JSON data structure in a buffer representing the per-boot characteristics of the system. */ void JsonStaticData () { static char BootTime [24]; static $DESCRIPTOR (BootTimeFaoDsc, "!17%D\0"); static $DESCRIPTOR (BootTimeDsc, BootTime); uint64 size64; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "JsonStaticData()\n"); size64 = (uint64)SystemData.MemSize * SystemData.PageSize; sys$fao (&BootTimeFaoDsc, 0, &BootTimeDsc, &SystemData.BootTime64); if (BootTime[0] == ' ') BootTime[0] = '0'; zptr = (sptr = SystemData.StaticBuffer) + sizeof(SystemData.StaticBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"static\""); sptr += snprintf (sptr, zptr-sptr, ",\"clusterMember\":%s", SystemData.ClusterMember ? "true" : "false"); sptr += snprintf (sptr, zptr-sptr, ",\"clusterMember\":%s", SystemData.ClusterMember ? "true" : "false"); sptr += snprintf (sptr, zptr-sptr, ",\"cpuAvail\":%d", SystemData.AvailCpuCount); sptr += snprintf (sptr, zptr-sptr, ",\"archName\":\"%s\"", SystemData.ArchName); sptr += snprintf (sptr, zptr-sptr, ",\"hwName\":\"%s\"", SystemData.HwName); sptr += snprintf (sptr, zptr-sptr, ",\"interval\":%d", RefreshInterval); sptr += snprintf (sptr, zptr-sptr, ",\"jpiPID\":\"%08.08X\"", SystemData.JpiPid); sptr += snprintf (sptr, zptr-sptr, ",\"nodeName\":\"%s\"", SystemData.NodeName); sptr += snprintf (sptr, zptr-sptr, ",\"vmsVersion\":\"%s\"", SystemData.VmsVersion); sptr += snprintf (sptr, zptr-sptr, ",\"memSize\":%llu", size64); sptr += snprintf (sptr, zptr-sptr, ",\"bootTime\":\"%s\"", BootTime); sptr += snprintf (sptr, zptr-sptr, ",\"diskCount\":%d", SystemData.DiskCount); sptr += snprintf (sptr, zptr-sptr, ",\"MONDESI_QUERY\":\"%s\"", MondesiQuery); sptr += snprintf (sptr, zptr-sptr, "}"); if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.StaticBufferLength = sptr - SystemData.StaticBuffer; } /*****************************************************************************/ /* Append the server and version to the static system buffer. This can't be done until the CGI variables are first available. */ void JsonServerSoftware () { char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "JsonServerSoftware()\n"); sptr = SystemData.StaticBuffer + SystemData.StaticBufferLength; zptr = SystemData.StaticBuffer + sizeof(SystemData.StaticBuffer); if (*(--sptr) != '}') EXIT_FI_LI (SS$_BUGCHECK); cptr = WsLibCgiVar("SERVER_SOFTWARE"); CgiServerSoftwarePtr = calloc (1, strlen(cptr)+1); strcpy (CgiServerSoftwarePtr, cptr); if (HttpdApache) { if (cptr = strstr (cptr, "Apache/")) cptr += 7; sptr += snprintf (sptr, zptr-sptr, ",\"HTTPd\":\"Apache v"); } else if (HttpdOSU) { if (cptr = strstr (cptr, "OSU/")) cptr += 4; sptr += snprintf (sptr, zptr-sptr, ",\"HTTPd\":\"OSU v"); } else if (HttpdWASD) { if (cptr = strstr (cptr, "WASD/")) cptr += 5; sptr += snprintf (sptr, zptr-sptr, ",\"HTTPd\":\"WASD v"); } else sptr += snprintf (sptr, zptr-sptr, ",\"HTTPd\":\""); if (cptr) while (*cptr && !isspace(*cptr) && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++; else if (sptr < zptr) *sptr++ = '?'; for (cptr = "\"}"; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.StaticBufferLength = sptr - SystemData.StaticBuffer; } /*****************************************************************************/ /* Create a JSON data structure in a buffer representing the core dynamic characteristics of the system. */ void JsonDynamicData (CLIENT_DATA *clptr) { static char CurTime [24], UpTime [16]; static $DESCRIPTOR (CurTimeDsc, CurTime); static $DESCRIPTOR (CurTimeFaoDsc, "!20%D\0"); static $DESCRIPTOR (UpTimeDsc, UpTime); static $DESCRIPTOR (UpTimeFaoDsc, "!10%D\0"); uint64 using64; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "JsonDynamicData()\n"); using64 = (uint64)SystemData.MemInUse * SystemData.PageSize; zptr = (sptr = SystemData.DynamicBuffer) + sizeof(SystemData.DynamicBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"dynamic\""); sptr += snprintf (sptr, zptr-sptr, ",\"timestamp\":%u", TimeStamp); if (SystemData.ClusterMember) { sptr += snprintf (sptr, zptr-sptr, ",\"clusterEVotes\":%d", SystemData.ClusterEVotes); if (SystemData.ClusterKbyteMap) sptr += snprintf (sptr, zptr-sptr, ",\"clusterKbyteMap\":%d", SystemData.ClusterKbyteMap); if (SystemData.ClusterKbyteRcvd) sptr += snprintf (sptr, zptr-sptr, ",\"clusterKbyteRcvd\":%d", SystemData.ClusterKbyteRcvd); if (SystemData.ClusterKbyteSent) sptr += snprintf (sptr, zptr-sptr, ",\"clusterKbyteSent\":%d", SystemData.ClusterKbyteSent); if (SystemData.ClusterMsgRcvd) sptr += snprintf (sptr, zptr-sptr, ",\"clusterMsgRcvd\":%d", SystemData.ClusterMsgRcvd); if (SystemData.ClusterMsgSent) sptr += snprintf (sptr, zptr-sptr, ",\"clusterMsgSent\":%d", SystemData.ClusterMsgSent); sptr += snprintf (sptr, zptr-sptr, ",\"clusterNodes\":%d", SystemData.ClusterNodes); sptr += snprintf (sptr, zptr-sptr, ",\"clusterQuorum\":%d", SystemData.ClusterQuorum); sptr += snprintf (sptr, zptr-sptr, ",\"clusterVotes\":%d", SystemData.ClusterVotes); } sptr += snprintf (sptr, zptr-sptr, ",\"cpuActive\":%d", SystemData.ActiveCpuCount); if (SystemData.CpuBusyPercent) sptr += snprintf (sptr, zptr-sptr, ",\"cpuBusy\":%d", SystemData.CpuBusyPercent); if (SystemData.CpuBusyPercent - SystemData.CpuUserPercent) sptr += snprintf (sptr, zptr-sptr, ",\"cpuSEK\":%d", SystemData.CpuBusyPercent - SystemData.CpuUserPercent); if (SystemData.ModePercent[INTSTK_MODE]) sptr += snprintf (sptr, zptr-sptr, ",\"modeINT\":%d", SystemData.ModePercent[INTSTK_MODE]); if (SystemData.ModePercent[MPSYNCH_MODE]) sptr += snprintf (sptr, zptr-sptr, ",\"modeMPS\":%d", SystemData.ModePercent[MPSYNCH_MODE]); if (SystemData.ModePercent[KERNEL_MODE]) sptr += snprintf (sptr, zptr-sptr, ",\"modeKER\":%d", SystemData.ModePercent[KERNEL_MODE]); if (SystemData.ModePercent[EXEC_MODE]) sptr += snprintf (sptr, zptr-sptr, ",\"modeEXE\":%d", SystemData.ModePercent[EXEC_MODE]); if (SystemData.ModePercent[SUPER_MODE]) sptr += snprintf (sptr, zptr-sptr, ",\"modeSUP\":%d", SystemData.ModePercent[SUPER_MODE]); if (SystemData.ModePercent[USER_MODE]) sptr += snprintf (sptr, zptr-sptr, ",\"modeUSE\":%d", SystemData.ModePercent[USER_MODE]); if (SystemData.BufferedIO) sptr += snprintf (sptr, zptr-sptr, ",\"bio\":%u", SystemData.BufferedIO); if (SystemData.DirectIO) sptr += snprintf (sptr, zptr-sptr, ",\"dio\":%u", SystemData.DirectIO); if (SystemData.DiskCount) sptr += snprintf (sptr, zptr-sptr, ",\"diskCount\":%d", SystemData.DiskCount); if (SystemData.MountCount) sptr += snprintf (sptr, zptr-sptr, ",\"diskMount\":%d", SystemData.MountCount); if (SystemData.FcpRead + SystemData.FcpWrite) sptr += snprintf (sptr, zptr-sptr, ",\"fcpRW\":%u", SystemData.FcpRead + SystemData.FcpWrite); if (SystemData.FcpWrite) sptr += snprintf (sptr, zptr-sptr, ",\"fcpWrite\":%u", SystemData.FcpWrite); if (SystemData.LockActivity) sptr += snprintf (sptr, zptr-sptr, ",\"lockActivity\":%u", SystemData.LockActivity); if (SystemData.LockWait) sptr += snprintf (sptr, zptr-sptr, ",\"lockWait\":%u", SystemData.LockWait); sptr += snprintf (sptr, zptr-sptr, ",\"memInUse\":%llu", using64); if (SystemData.ClusterMember) { if (SystemData.MscpRead) sptr += snprintf (sptr, zptr-sptr, ",\"mscpRead\":%d", SystemData.MscpRead); if (SystemData.MscpWrite) sptr += snprintf (sptr, zptr-sptr, ",\"mscpWrite\":%d", SystemData.MscpWrite); } if (SystemData.NetBytesPerSec64) sptr += snprintf (sptr, zptr-sptr, ",\"netPerSec\":%llu", SystemData.NetBytesPerSec64); if (SystemData.NetBytesRxPerSec64) sptr += snprintf (sptr, zptr-sptr, ",\"netRxPerSec\":%llu", SystemData.NetBytesRxPerSec64); if (SystemData.NetDatagramRx) sptr += snprintf (sptr, zptr-sptr, ",\"netDgramRx\":%u", SystemData.NetDatagramRx); if (SystemData.NetDatagramTx) sptr += snprintf (sptr, zptr-sptr, ",\"netDgramTx\":%u", SystemData.NetDatagramTx); if (SystemData.NetIntErrorsHard64) sptr += snprintf (sptr, zptr-sptr, ",\"netErrors\":%llu", SystemData.NetIntErrorsHard64); if (OpaOpcomCount) { sptr += snprintf (sptr, zptr-sptr, ",\"opcomAudit\":%u", OpaAuditCount); sptr += snprintf (sptr, zptr-sptr, ",\"opcomCount\":%u", OpaOpcomCount); if (ProvideOpcomData (clptr)) if (OpaBufferLength) sptr += snprintf (sptr, zptr-sptr, ",\"opcomLength\":%u", OpaBufferLength); sptr += snprintf (sptr, zptr-sptr, ",\"opcomTime\":\"%s\"", OpaTime); } if (OpaStatus) sptr += snprintf (sptr, zptr-sptr, ",\"opcomStatus\":\"%%X%08.08X\"", OpaStatus); if (OpaSpawnStatus) sptr += snprintf (sptr, zptr-sptr, ",\"opcomSpawn\":\"%%X%08.08X\"", OpaSpawnStatus); if (SystemData.PageFaults) sptr += snprintf (sptr, zptr-sptr, ",\"pageFaults\":%u", SystemData.PageFaults); if (SystemData.PageFaultsHard) sptr += snprintf (sptr, zptr-sptr, ",\"pageFaultsHard\":%u", SystemData.PageFaultsHard); if (SystemData.PageFilePageBytes64) sptr += snprintf (sptr, zptr-sptr, ",\"pageFileSize\":%llu", SystemData.PageFilePageBytes64); if (SystemData.PageFileUsedBytes64) sptr += snprintf (sptr, zptr-sptr, ",\"pageFileUsed\":%llu", SystemData.PageFileUsedBytes64); if (SystemData.ProcessCount) sptr += snprintf (sptr, zptr-sptr, ",\"procTot\":%u", SystemData.ProcessCount); if (SystemData.ProcessBatCount) sptr += snprintf (sptr, zptr-sptr, ",\"procBat\":%u", SystemData.ProcessBatCount); if (SystemData.ProcessIntCount) sptr += snprintf (sptr, zptr-sptr, ",\"procInt\":%u", SystemData.ProcessIntCount); if (SystemData.ProcessNetCount) sptr += snprintf (sptr, zptr-sptr, ",\"procNet\":%u", SystemData.ProcessNetCount); sys$fao (&UpTimeFaoDsc, 0, &UpTimeDsc, &SystemData.UpTime64); for (cptr = UpTime; *cptr && *cptr == ' '; cptr++); sptr += snprintf (sptr, zptr-sptr, ",\"upTime\":\"%s\"", cptr); sys$fao (&CurTimeFaoDsc, 0, &CurTimeDsc, &CurrentTime64); if (CurTime[0] == ' ') CurTime[0] = '0'; sptr += snprintf (sptr, zptr-sptr, ",\"vmsTime\":\"%s\"", CurTime); sptr += snprintf (sptr, zptr-sptr, "}"); if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.DynamicBufferLength = sptr - SystemData.DynamicBuffer; } /*****************************************************************************/ /* Create a JSON data structure in a buffer representing the monitored disks. */ void JsonDiskData () { int cnt; uint64 free64, size64; char *sptr, *zptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "JsonDiskData()\n"); if (!SystemData.DiskCount) return; zptr = (sptr = SystemData.DiskBuffer) + sizeof(SystemData.DiskBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"disk\""); sptr += snprintf (sptr, zptr-sptr, ",\"devices\":["); for (cnt = 0; cnt < SystemData.DiskCount; cnt++) { size64 = (uint64)SystemData.DiskVolSize[cnt] * 512; free64 = (uint64)SystemData.DiskFreeBlocks[cnt] * 512; sptr += snprintf (sptr, zptr-sptr, "%s{\"dev\":\"%s\",\"vol\":\"%s\",\"size\":%llu,\"free\":%llu,\ \"iops\":%u,\"qlen\":%u,\"err\":%u,\"mvip\":%s}", cnt ? "," : "", SystemData.DiskName[cnt], SystemData.DiskVolName[cnt], size64, free64, SystemData.DiskIOPs[cnt], SystemData.DiskQueLen[cnt], SystemData.DiskErrCnt[cnt], SystemData.DiskMntVerIP[cnt] ? "true" : "false"); } sptr += snprintf (sptr, zptr-sptr, "]}"); if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); SystemData.DiskBufferLength = sptr - SystemData.DiskBuffer; } /*****************************************************************************/ /* OPCOM messages are not for general consumption. */ int ProvideOpcomData (CLIENT_DATA *clptr) { char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "ProvideOpcomData()\n"); if (OpcomEnable[0] == '!') return (FALSE); if (OpcomEnable[0] == '#') return (FALSE); if (OpcomEnable[0] == '*') return (TRUE); if (clptr && clptr->OpcomAccess) return (clptr->AccessAllowed[ACCESS_OPCOM]); return (FALSE); } /*****************************************************************************/ /* Create a JSON data structure in a buffer containing OPCOM data. */ char* JsonOpcomData (CLIENT_DATA *clptr) { static int PrevAuditCount, PrevOpcomCount; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "JsonOpcomData()\n"); if (OpcomEnable[0] == '!') return (NULL); if (OpcomEnable[0] == '-') return (NULL); if (!OpaBufferPtr) return (NULL); if (!ProvideOpcomData (clptr)) return (NULL); if (OpaAuditCount) { if (clptr->ProvideAuditData) { /* client wants to see audit data so from main buffer */ if (clptr->ProvideAuditData++ == 1) return (OpaBufferPtr); if (OpaOpcomCount == PrevOpcomCount) return (NULL); PrevOpcomCount = OpaOpcomCount; return (OpaBufferPtr); } else { /* client does NOT want to see audit data so from redacted buffer */ if (clptr->ProvideOpcomData++ == 1) return (OpaNonAuditPtr); if (OpaAuditCount == PrevAuditCount) return (NULL); PrevAuditCount = OpaAuditCount; return (OpaNonAuditPtr); } } else { /* no audit data so from main buffer */ if (clptr->ProvideOpcomData++ == 1) return (OpaBufferPtr); if (OpaOpcomCount == PrevOpcomCount) return (NULL); PrevOpcomCount = OpaOpcomCount; return (OpaBufferPtr); } } /*****************************************************************************/ /* Per-boot system data. */ int CollectSyiStatic () { static ulong ActiveCpuCnt, AvailCpuCount, JpiPid, MemSize, PageSize; static int64 BootTime64; static ushort HwNameLength, PrcNamLength; static unsigned char ClusterMember; static char ArchName [15+1], HwName [1+60], JpiPrcNam [15+1], NodeName [15+1], VmsVersion [8+1]; /* initialize structure for SYI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } SyiItemList[] = { { sizeof(AvailCpuCount), SYI$_AVAILCPU_CNT, &AvailCpuCount, 0 }, { sizeof(ActiveCpuCnt), SYI$_ACTIVECPU_CNT, &ActiveCpuCnt, 0 }, { sizeof(ArchName), SYI$_ARCH_NAME, &ArchName, 0 }, { sizeof(BootTime64), SYI$_BOOTTIME, &BootTime64, 0 }, { sizeof(ClusterMember), SYI$_CLUSTER_MEMBER, &ClusterMember, 0 }, { sizeof(HwName), SYI$_HW_NAME, &HwName, &HwNameLength }, { sizeof(MemSize), SYI$_MEMSIZE, &MemSize, 0 }, { sizeof(NodeName), SYI$_SCSNODE, &NodeName, 0 }, { sizeof(PageSize), SYI$_PAGE_SIZE, &PageSize, 0 }, { sizeof(VmsVersion), SYI$_VERSION, &VmsVersion, 0 }, {0,0,0,0} }, JpiItemList[] = { { sizeof(JpiPid), JPI$_PID, &JpiPid, 0 }, { sizeof(JpiPrcNam), JPI$_PRCNAM, &JpiPrcNam, &PrcNamLength }, {0,0,0,0} }; int status; char *cptr, *sptr, *zptr; IOSBLK IOsb; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectSyiStatic()\n"); status = sys$getsyiw ( EfnWait, /* efn */ 0, /* csiaddr */ 0, /* nodename */ &SyiItemList, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (dbug) fprintf (stdout, "sys$getsyi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) EXIT_FI_LI (status); zptr = (sptr = SystemData.ArchName) + sizeof(SystemData.ArchName)-1; for (cptr = ArchName; *cptr && *cptr != ' ' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; HwName[HwNameLength] = '\0'; zptr = (sptr = SystemData.HwName) + sizeof(SystemData.HwName)-1; for (cptr = HwName; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; NodeName[sizeof(NodeName)-1] = '\0'; zptr = (sptr = SystemData.NodeName) + sizeof(SystemData.NodeName)-1; for (cptr = NodeName; *cptr && *cptr != ' ' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; zptr = (sptr = SystemData.VmsVersion) + sizeof(SystemData.VmsVersion)-1; for (cptr = VmsVersion; *cptr && *cptr != ' ' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; SystemData.MemSize = MemSize; SystemData.PageSize = PageSize; SystemData.BootTime64 = BootTime64; SystemData.AvailCpuCount = AvailCpuCount; SystemData.ActiveCpuCount = ActiveCpuCnt; SystemData.ClusterMember = ClusterMember; if (TEST_BENCH) SystemData.ClusterMember = 2; if (dbug) fprintf (stdout, "|%s|%s|%s|%s| %d %d\n", SystemData.ArchName, SystemData.HwName, SystemData.NodeName, SystemData.VmsVersion, SystemData.AvailCpuCount, ClusterMember); status = sys$getjpiw ( EfnWait, /* efn */ 0, /* csiaddr */ 0, /* nodename */ &JpiItemList, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (dbug) fprintf (stdout, "sys$getjpi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) EXIT_FI_LI (status); SystemData.JpiPid = OpaJpiPid = JpiPid; JpiPrcNam[PrcNamLength] = '\0'; strcpy (SystemData.MonDeSiPrcNam, JpiPrcNam); return (status); } /*****************************************************************************/ /* Dynamic system data. */ int CollectSyiDynamic () { static ushort ClusterEVotes, ClusterNodes, ClusterQuorum, ClusterVotes, HwNameLength, PrevClusterNodes; static ulong ActiveCpuCnt, AvailCpuCount, JpiPid, MemSize, PageFileFree, PageFilePage, PageSize; static int64 BootTime64; static char ArchName [15+1], HwName [1+60], NodeName [15+1], UpTime [15+1], VmsVersion [8+1]; static $DESCRIPTOR (UpTimeDsc, UpTime); static $DESCRIPTOR (UpTimeFaoDsc, "!10%D\0"); /* initialize structure for SYI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ClusterItemList[] = { { sizeof(ClusterNodes), SYI$_CLUSTER_NODES, &ClusterNodes, 0 }, { sizeof(ClusterQuorum), SYI$_CLUSTER_QUORUM, &ClusterQuorum, 0 }, { sizeof(ClusterVotes), SYI$_CLUSTER_VOTES, &ClusterVotes, 0 }, { sizeof(ClusterEVotes), SYI$_CLUSTER_EVOTES, &ClusterEVotes, 0 }, { sizeof(ActiveCpuCnt), SYI$_ACTIVECPU_CNT, &ActiveCpuCnt, 0 }, { sizeof(PageFileFree), SYI$_PAGEFILE_FREE, &PageFileFree, 0 }, { sizeof(PageFilePage), SYI$_PAGEFILE_PAGE, &PageFilePage, 0 }, {0,0,0,0} }, MemberItemList[] = { { sizeof(AvailCpuCount), SYI$_AVAILCPU_CNT, &AvailCpuCount, 0 }, { sizeof(ArchName), SYI$_ARCH_NAME, &ArchName, 0 }, { sizeof(BootTime64), SYI$_BOOTTIME, &BootTime64, 0 }, { sizeof(HwName), SYI$_HW_NAME, &HwName, &HwNameLength }, { sizeof(MemSize), SYI$_MEMSIZE, &MemSize, 0 }, { sizeof(PageSize), SYI$_PAGE_SIZE, &PageSize, 0 }, { sizeof(NodeName), SYI$_SCSNODE, &NodeName, 0 }, { sizeof(VmsVersion), SYI$_VERSION, &VmsVersion, 0 }, {0,0,0,0} }, NonItemList[] = { { sizeof(ActiveCpuCnt), SYI$_ACTIVECPU_CNT, &ActiveCpuCnt, 0 }, { sizeof(PageFileFree), SYI$_PAGEFILE_FREE, &PageFileFree, 0 }, { sizeof(PageFilePage), SYI$_PAGEFILE_PAGE, &PageFilePage, 0 }, {0,0,0,0} }; int cnt, csi, status; int64 UpTime64; float fMemSize; char *cptr, *sptr, *zptr; char sMemSize [32]; IOSBLK IOsb; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectSyiDynamic()\n"); /* collect System Information */ status = sys$getsyiw ( EfnWait, /* efn */ 0, /* csiaddr */ 0, /* nodename */ SystemData.ClusterMember ? (void*)&ClusterItemList : (void*)&NonItemList, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (dbug) fprintf (stdout, "sys$getsyi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) EXIT_FI_LI (status); SystemData.ActiveCpuCount = ActiveCpuCnt; SystemData.PageFilePercent = (int)(100.0 - ((float)PageFileFree * 100.0 / (float)PageFilePage)); SystemData.PageFileFree = PageFileFree; SystemData.PageFilePage = PageFilePage; SystemData.PageFileUsed = PageFilePage - PageFileFree; SystemData.PageFileFreeBytes64 = (int64)SystemData.PageFileFree * SystemData.PageSize; SystemData.PageFilePageBytes64 = (int64)SystemData.PageFilePage * SystemData.PageSize; SystemData.PageFileUsedBytes64 = (int64)SystemData.PageFileUsed * SystemData.PageSize; if (SystemData.ClusterMember) { if (TEST_BENCH) { ClusterEVotes = SystemData.ClusterMember / 4; if (!ClusterEVotes) ClusterEVotes = 1; ClusterNodes = SystemData.ClusterMember; if (TimeStamp % 115 > 50) ClusterNodes++; ClusterVotes = ClusterQuorum = ClusterEVotes / 2 + 1; if (ClusterVotes < ClusterEVotes) ClusterVotes++; } SystemData.ClusterEVotes = ClusterEVotes; SystemData.ClusterNodes = ClusterNodes; SystemData.ClusterQuorum = ClusterQuorum; SystemData.ClusterVotes = ClusterVotes; if (ClusterNodes != PrevClusterNodes || SystemData.PrevMinute != CurrentNumTime[4]) { if (ClusterNodes != PrevClusterNodes) JsonAlert (NULL, "!Cluster membership changed from %d to %d nodes", PrevClusterNodes, ClusterNodes); /* collect clustered systems information */ PrevClusterNodes = ClusterNodes; SystemData.ClusterSummaryCount++; zptr = (sptr = SystemData.ClusterSummary) + sizeof(SystemData.ClusterSummary); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"cluster\",\"summary\":\""); for (cnt = csi = -1;;) { status = sys$getsyiw ( EfnWait, /* efn */ &csi, /* csiaddr */ 0, /* nodename */ (void*)&MemberItemList, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (dbug) fprintf (stdout, "sys$getsyi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) { if (status == SS$_NOMORENODE) break; return (status); } HwName[HwNameLength] = '\0'; NodeName[sizeof(NodeName)-1] = '\0'; for (cptr = NodeName; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; fMemSize = (float)MemSize * (float)PageSize; if (fMemSize > 1073741824.0) sprintf (sMemSize, "%.3gGB", fMemSize / 1073741824.0); else sprintf (sMemSize, "%.3gMB", fMemSize / 1048576.0); status = lib$sub_times (&CurrentTime64, &BootTime64, &UpTime64); if (VMSnok (status)) EXIT_FI_LI (status); sys$fao (&UpTimeFaoDsc, 0, &UpTimeDsc, &UpTime64); for (cptr = UpTime; *cptr && *cptr == ' '; cptr++); sptr += snprintf (sptr, zptr-sptr, "%s%s:: %s %dxCPU%s %s %s %s up %s", ++cnt ? "
" : "", NodeName, HwName, AvailCpuCount, AvailCpuCount > 1 ? "s" : "", sMemSize, ArchName, VmsVersion, UpTime); if (sptr > zptr) EXIT_FI_LI (SS$_RESULTOVF); *sptr = '\0'; } if (TEST_BENCH) { sptr += snprintf (sptr, zptr-sptr, "%s:: %s %dxCPU%s %.3gGB %s VMS %s up 0 23:59", SystemData.NodeName, SystemData.HwName, SystemData.AvailCpuCount, SystemData.AvailCpuCount > 1 ? "s" : "", (float)SystemData.MemSize * (float)SystemData.PageSize / 1073741824.0, SystemData.ArchName, SystemData.VmsVersion); } sptr += snprintf (sptr, zptr-sptr, "\"}"); if (sptr >= zptr) EXIT_FI_LI (SS$_RESULTOVF); } } return (status); } /*****************************************************************************/ /* Collect System Performance Information. */ int CollectSPI () { /* 96 is the SPD cluster node limit (we had 70+ in the '90s :-) */ #define SCS_NODES_MAX 96 /* structure for a single CPU's mode ticks */ static struct SingleCPU { unsigned char CPUid; ulong Count [MODE_COUNT]; }; /* a structure all CPU's mode ticks for a system with up to 32 CPUs */ static struct { ulong NumberOfCPUs; struct SingleCPU CPU [CPU_MAX]; } ModeTicks; static int64 ModeTickCount [MODE_COUNT], PrevModeTickCount [MODE_COUNT]; static ulong BufferedIO, ClusterMsgSent, ClusterMsgRcvd, ClusterKbyteMap, ClusterKbyteSent, ClusterKbyteRcvd, DirectIO, FcpRead, FcpWrite, FreeList, LockConvert, LockDequeue, LockEnqueue, LockWait, ModList, MscpRead, MscpWrite, PageFaults, PageReadIO, PageWriteIO, ProcessCount; static ulong PrevBufferedIO, PrevClusterMsgSent, PrevClusterMsgRcvd, PrevClusterKbyteMap, PrevClusterKbyteSent, PrevClusterKbyteRcvd, PrevDirectIO, PrevFcpRead, PrevFcpWrite, PrevLockActivity, PrevLockWait, PrevMscpRead, PrevMscpWrite, PrevPageFaults, PrevPageReadIO, PrevPageWriteIO, PrevMscpIO; static ulong CpuExe, CpuIntStk, CpuKernel, CpuMpSynch, CpuSuper, CpuUser; static ulong MscpData [35]; static int ScsDataCount; static ulong ScsData [(14*SCS_NODES_MAX)+1]; static ulong PrevCpuTicksBusy, PrevCpuTicksUser; /* initialize structure for RMI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemList1[] = { /* for 7.3-2(?) and later which has the GETSPI$_CPU_... items */ { sizeof(BufferedIO), GETSPI$_BUFIO, &BufferedIO, 0 }, { sizeof(ModeTicks), GETSPI$_MODES, &ModeTicks, 0 }, { sizeof(LockDequeue), GETSPI$_DEQ, &LockDequeue, 0 }, { sizeof(DirectIO), GETSPI$_DIRIO, &DirectIO, 0 }, { sizeof(LockConvert), GETSPI$_ENQCVT, &LockConvert, 0 }, { sizeof(LockEnqueue), GETSPI$_ENQNEW, &LockEnqueue, 0 }, { sizeof(LockWait), GETSPI$_ENQWAIT, &LockWait, 0 }, { sizeof(FcpRead), GETSPI$_FCPREAD, &FcpRead, 0 }, { sizeof(FcpWrite), GETSPI$_FCPWRITE, &FcpWrite, 0 }, { sizeof(FreeList), GETSPI$_FRLIST, &FreeList, 0 }, { sizeof(ModList), GETSPI$_MODLIST, &ModList, 0 }, { sizeof(PageFaults), GETSPI$_FAULTS, &PageFaults, 0 }, { sizeof(PageReadIO), GETSPI$_PREADIO, &PageReadIO, 0 }, { sizeof(PageWriteIO), GETSPI$_PWRITIO, &PageWriteIO, 0 }, { sizeof(ProcessCount), GETSPI$_PROCS, &ProcessCount, 0 }, {0,0,0,0} }, ItemList2[] = { { sizeof(ScsData), GETSPI$_SCS, &ScsData, 0 }, { sizeof(MscpRead), GETSPI$_MSCP_READ, &MscpRead, 0 }, { sizeof(MscpWrite), GETSPI$_MSCP_WRITE, &MscpWrite, 0 }, {0,0,0,0} }; int cidx, midx, status, tlong; ulong datsize, CpuTicksBusy; ulong *datptr; IOSBLK IOsb, SynchIOsb; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectSPI()\n"); status = lib$sub_times (&CurrentTime64, &SystemData.BootTime64, &SystemData.UpTime64); if (VMSnok (status)) EXIT_FI_LI (status); /* collect System Performance Information */ status = exe$getspi ( EfnSpi, /* efn */ 0, /* csiaddr */ 0, /* nodename */ &ItemList1, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (dbug) fprintf (stdout, "exe$getspi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) return (status); if (SystemData.ClusterMember) { memset (&ScsData, 0, (ScsDataCount*(sizeof(ulong)*14)+1)); status = exe$getspi ( EfnSpi, /* efn */ 0, /* csiaddr */ 0, /* nodename */ &ItemList2, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (dbug) fprintf (stdout, "exe$getspi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) return (status); /* this count data is unreliable with at least OpenVMS V8.4 */ if (dbug) fprintf (stdout, "%d\n", ScsData[0]); for (cidx = ScsDataCount = 0; cidx < SCS_NODES_MAX; cidx++) { datptr = &(ScsData[(cidx*14)+1]); if (dbug) fprintf (stdout, "%d |%s| %d %d %d %d %d %d %d %d %d %d %d\n", cidx, (char*)(&datptr[0])+1, datptr[2], datptr[3], datptr[4], datptr[5], datptr[6], datptr[7], datptr[8], datptr[9], datptr[10], datptr[11], datptr[12]); /* as count data is unreliable use the first zero-length node name */ if (!*(char*)(&datptr[0])) break; ScsDataCount++; ClusterMsgSent = datptr[5]; ClusterMsgRcvd = datptr[6]; ClusterKbyteSent = datptr[8]; ClusterKbyteRcvd = datptr[10]; ClusterKbyteMap = datptr[11]; } } /*******/ /* CPU */ /*******/ /* accumulate all modes for all CPUs into CPU zero */ for (cidx = 1; cidx < ModeTicks.NumberOfCPUs; cidx++) for (midx = INTSTK_MODE; midx <= IDLE_MODE; midx++) ModeTicks.CPU[0].Count[midx] += ModeTicks.CPU[cidx].Count[midx]; /* calculate total ticks busy */ CpuTicksBusy = CpuUser = 0; CpuTicksBusy = ModeTicks.CPU[0].Count[INTSTK_MODE]; /* interrupt item is actually the sum of interrupt and idle! */ CpuTicksBusy -= ModeTicks.CPU[0].Count[IDLE_MODE]; CpuTicksBusy += ModeTicks.CPU[0].Count[KERNEL_MODE] + ModeTicks.CPU[0].Count[MPSYNCH_MODE] + ModeTicks.CPU[0].Count[EXEC_MODE] + ModeTicks.CPU[0].Count[SUPER_MODE] + ModeTicks.CPU[0].Count[USER_MODE]; CpuUser = ModeTicks.CPU[0].Count[USER_MODE]; if (PrevCpuTicksBusy && CpuTicksBusy > PrevCpuTicksBusy) { /* calculate percent busy */ SystemData.CpuTicksBusy = CpuTicksBusy - PrevCpuTicksBusy; SystemData.CpuBusyPercent = (uint)((float)SystemData.CpuTicksBusy / DeltaSeconds); if (SystemData.CpuBusyPercent > 100 * SystemData.ActiveCpuCount) SystemData.CpuBusyPercent = 100 * SystemData.ActiveCpuCount; } else SystemData.CpuBusyPercent = 0; PrevCpuTicksBusy = CpuTicksBusy; if (PrevCpuTicksUser) { SystemData.CpuTicksUser = CpuUser - PrevCpuTicksUser; SystemData.CpuUserPercent = (uint)((float)SystemData.CpuTicksUser / DeltaSeconds); if (SystemData.CpuUserPercent > 100 * SystemData.ActiveCpuCount) SystemData.CpuUserPercent = 100 * SystemData.ActiveCpuCount; } else SystemData.CpuUserPercent = 0; PrevCpuTicksUser = CpuUser; /* transfer the CPU tick count to the system data */ for (midx = INTSTK_MODE; midx <= IDLE_MODE; midx++) ModeTickCount[midx] = ModeTicks.CPU[0].Count[midx]; ModeTickCount[INTSTK_MODE] -= ModeTickCount[IDLE_MODE]; for (midx = INTSTK_MODE; midx <= IDLE_MODE; midx++) { if (PrevModeTickCount[midx]) { if (midx == COMPAT_MODE) continue; SystemData.ModePercent[midx] = (int)((float)((ModeTickCount[midx] - PrevModeTickCount[midx]) / DeltaSeconds)); if (SystemData.ModePercent[midx] > 100 * SystemData.ActiveCpuCount) SystemData.ModePercent[midx] = 100 * SystemData.ActiveCpuCount; } else SystemData.ModePercent[midx] = 0; PrevModeTickCount[midx] = ModeTickCount[midx]; } if (dbug) fprintf (stdout, "CPU: %d%% USER: %d%%\n", SystemData.CpuBusyPercent, SystemData.CpuUserPercent); /***************/ /* page faults */ /***************/ if (PrevPageFaults && PageFaults > PrevPageFaults) SystemData.PageFaults = PageFaults - PrevPageFaults; else SystemData.PageFaults = 0; PrevPageFaults = PageFaults; if (PrevPageReadIO && PageReadIO > PrevPageReadIO) SystemData.PageReadIO = PageReadIO - PrevPageReadIO; else SystemData.PageReadIO = 0; PrevPageReadIO = PageReadIO; if (PrevPageWriteIO && PageWriteIO > PrevPageWriteIO) SystemData.PageWriteIO = PageWriteIO - PrevPageWriteIO; else SystemData.PageWriteIO = 0; PrevPageWriteIO = PageWriteIO; SystemData.PageFaultsHard = SystemData.PageReadIO + SystemData.PageWriteIO; SystemData.PageFaultsHard = (uint)((float)SystemData.PageFaultsHard / DeltaSeconds); SystemData.PageFaultsSoft = SystemData.PageFaults - SystemData.PageFaultsHard; SystemData.PageFaultsSoft = (uint)((float)SystemData.PageFaultsSoft / DeltaSeconds); /********/ /* MSCP */ /********/ if (SystemData.ClusterMember) { if (TEST_BENCH) { MscpRead = ModeTicks.CPU[0].Count[KERNEL_MODE] & 0xff; MscpWrite = ModeTicks.CPU[0].Count[USER_MODE] & 0xff; } if (PrevMscpRead && MscpRead > PrevMscpRead) SystemData.MscpRead = MscpRead - PrevMscpRead; else SystemData.MscpRead = 0; PrevMscpRead = MscpRead; SystemData.MscpRead = (uint)((float)SystemData.MscpRead / DeltaSeconds); if (PrevMscpWrite > MscpWrite > PrevMscpWrite) SystemData.MscpWrite = MscpWrite - PrevMscpWrite; else SystemData.MscpWrite = 0; PrevMscpWrite = MscpWrite; SystemData.MscpWrite = (uint)((float)SystemData.MscpWrite / DeltaSeconds); } /********/ /* lock */ /********/ if (PrevLockActivity && LockEnqueue + LockDequeue + LockWait > PrevLockActivity) SystemData.LockActivity = LockEnqueue + LockDequeue + LockWait - PrevLockActivity; else SystemData.LockActivity = 0; PrevLockActivity = LockEnqueue + LockDequeue + LockWait; SystemData.LockActivity = (uint)((float)SystemData.LockActivity / DeltaSeconds); SystemData.LockWait = (uint)((float)SystemData.LockWait / DeltaSeconds); if (PrevLockWait && LockWait > PrevLockWait) SystemData.LockWait = LockWait - PrevLockWait; else SystemData.LockWait = 0; PrevLockWait = LockWait; /********/ /* disk */ /********/ if (PrevFcpRead && FcpRead > PrevFcpRead) SystemData.FcpRead = FcpRead - PrevFcpRead; else SystemData.FcpRead = 0; PrevFcpRead = FcpRead; SystemData.FcpRead = (uint)((float)SystemData.FcpRead / DeltaSeconds); if (PrevFcpWrite && FcpWrite > PrevFcpWrite) SystemData.FcpWrite = FcpWrite - PrevFcpWrite; else SystemData.FcpWrite = 0; PrevFcpWrite = FcpWrite; SystemData.FcpWrite = (uint)((float)SystemData.FcpWrite / DeltaSeconds); /*********/ /* other */ /*********/ if (PrevDirectIO && DirectIO > PrevDirectIO) SystemData.DirectIO = DirectIO - PrevDirectIO; else SystemData.DirectIO = 0; PrevDirectIO = DirectIO; SystemData.DirectIO = (uint)((float)SystemData.DirectIO / DeltaSeconds); if (PrevBufferedIO && BufferedIO > PrevBufferedIO) SystemData.BufferedIO = BufferedIO - PrevBufferedIO; else SystemData.BufferedIO = 0; PrevBufferedIO = BufferedIO; SystemData.BufferedIO = (uint)((float)SystemData.BufferedIO / DeltaSeconds); SystemData.MemInUse = SystemData.MemSize - FreeList - ModList; if (SystemData.MemInUse > SystemData.MemInUseMax) SystemData.MemInUseMax = SystemData.MemInUse; SystemData.ProcessCount = ProcessCount; CollectJobModes (); if (SystemData.ClusterMember) { /*******/ /* SCS */ /*******/ if (TEST_BENCH) { ClusterMsgSent = ModeTicks.CPU[0].Count[USER_MODE] & 0xf; ClusterMsgRcvd = ModeTicks.CPU[0].Count[SUPER_MODE] & 0xff; ClusterKbyteMap = ModeTicks.CPU[0].Count[KERNEL_MODE] & 0xf; ClusterKbyteSent = ModeTicks.CPU[0].Count[KERNEL_MODE] & 0xfff; ClusterKbyteRcvd = ModeTicks.CPU[0].Count[EXEC_MODE] & 0xff; } if (PrevClusterMsgSent && ClusterMsgSent > PrevClusterMsgSent) SystemData.ClusterMsgSent = ClusterMsgSent - PrevClusterMsgSent; else SystemData.ClusterMsgSent = 0; PrevClusterMsgSent = ClusterMsgSent; SystemData.ClusterMsgSent = (uint)((float)SystemData.ClusterMsgSent / DeltaSeconds); if (PrevClusterMsgRcvd && ClusterMsgRcvd > PrevClusterMsgRcvd) SystemData.ClusterMsgRcvd = ClusterMsgRcvd - PrevClusterMsgRcvd; else SystemData.ClusterMsgRcvd = 0; PrevClusterMsgRcvd = ClusterMsgRcvd; SystemData.ClusterMsgRcvd = (uint)((float)SystemData.ClusterMsgRcvd / DeltaSeconds); if (PrevClusterKbyteMap && ClusterKbyteMap > PrevClusterKbyteMap) SystemData.ClusterKbyteMap = ClusterKbyteMap - PrevClusterKbyteMap; else SystemData.ClusterKbyteMap = 0; PrevClusterKbyteMap = ClusterKbyteMap; SystemData.ClusterKbyteMap = (uint)((float)SystemData.ClusterKbyteMap / DeltaSeconds); if (PrevClusterKbyteSent && ClusterKbyteSent > PrevClusterKbyteSent) SystemData.ClusterKbyteSent = ClusterKbyteSent - PrevClusterKbyteSent; else SystemData.ClusterKbyteSent = 0; PrevClusterKbyteSent = ClusterKbyteSent; SystemData.ClusterKbyteSent = (uint)((float)SystemData.ClusterKbyteSent / DeltaSeconds); if (PrevClusterKbyteRcvd && ClusterKbyteRcvd > PrevClusterKbyteRcvd) SystemData.ClusterKbyteRcvd = ClusterKbyteRcvd - PrevClusterKbyteRcvd; else SystemData.ClusterKbyteRcvd = 0; PrevClusterKbyteRcvd = ClusterKbyteRcvd; SystemData.ClusterKbyteRcvd = (uint)((float)SystemData.ClusterKbyteRcvd / DeltaSeconds); } 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 ALAMODE_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 CollectNetIntData () { static int NetIntCount, IoClrCount; static ushort NetIntChan [NI_DEVICE_MAX]; static ulong DcSCOM = 32, /* DC$_SCOM */ DtNI = 13; /* DT$_NI */ static int64 PrevBlocksRx64, PrevBlocksTx64, PrevBytesRx64, PrevBytesRxTx64, PrevBytesTx64, PrevErrorsHard64, PrevErrorsSoft64; static long DeviceCount = -1, DviUnit, LnmIndex = -1; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } 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, "ALAMODE_NI"); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); int ecnt, status; ushort pword, retlen; ulong LnmCount; int64 value64, DevScanContext64, NetIntBlocksRx64, NetIntBlocksTx64, NetIntBytesRx64, NetIntBytesRxTx64, NetIntBytesTx64, NetIntErrorsHard64, NetIntErrorsSoft64; char *cptr, *vptr; char DevScanBuffer [64], CountBuffer [512]; struct STRUCT_IOSBLK IOsb; $DESCRIPTOR (CountDsc, CountBuffer); $DESCRIPTOR (DevScanDsc, DevScanBuffer); /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectNetIntData() %s\n", NetIntDev); if (LnmIndex < 0) { /* first call, check for logical name defined network devices */ for (LnmIndex = 0; LnmIndex < NI_DEVICE_MAX; LnmIndex++) { cptr = SysTrnLnm (LOGNAM_NI, NULL, LnmIndex); if (!cptr) break; NetIntDsc.dsc$a_pointer = cptr; NetIntDsc.dsc$w_length = strlen(cptr); status = sys$assign (&NetIntDsc, &NetIntChan[LnmIndex], 0, 0); if (VMSnok (status)) EXIT_FI_LI (status); if (NetIntDev[0]) strcat (NetIntDev, ","); strcat (NetIntDev, cptr); NetIntCount++; } } if (DeviceCount < 0) { /* first call */ DeviceCount = 0; /* if logical name defined and found devices */ if (NetIntCount) return; /* 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)) EXIT_FI_LI (status); } status = sys$getdviw (EfnWait, 0, &DevScanDsc, &GetDviItems, &IOsb, 0, 0, 0, 0); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) EXIT_FI_LI (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)) EXIT_FI_LI (status); if (NetIntDev[0]) strcat (NetIntDev, ","); strcat (NetIntDev, DevScanBuffer+1); NetIntCount++; } /* first call, just return having assigned channels (perhaps) */ return; } /****************/ /* collect data */ /****************/ /* initialise structure and local storage */ NetIntBlocksRx64 = NetIntBlocksTx64 = NetIntBytesRx64 = NetIntBytesTx64 = NetIntBytesRxTx64 = NetIntErrorsHard64 = NetIntErrorsSoft64 = 0; /* if no devices then just return from here */ if (!NetIntCount) return; /* accumulate counts from each device */ for (ecnt = 0; ecnt < NetIntCount; ecnt++) { memset (CountBuffer, 0, sizeof(CountBuffer)); #ifndef IO$M_RD_64COUNT #define IO$M_RD_64COUNT 0x4000 #endif status = sys$qiow (0, NetIntChan[ecnt], IO$_SENSEMODE | IO$M_RD_COUNT | IoClrCount | IO$M_RD_64COUNT | IO$M_CTRL, &IOsb, 0, 0, 0, &CountDsc, 0, 0, 0, 0); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) EXIT_FI_LI (status); vptr = CountBuffer; while (vptr < CountBuffer + IOsb.iosb$w_bcnt) { 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 = *(UINT64PTR)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 = *(ULONGPTR)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 += value64; break; case 3 : case 7 : NetIntBytesTx64 += value64; break; case 4 : case 8 : NetIntBlocksRx64 += value64; break; case 5 : case 9 : NetIntBlocksTx64 += 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 += value64; break; case 25 : case 26 : case 33 : case 34 : NetIntErrorsHard64 += value64; break; /* more "traditional" counters */ case 1000 /* NMA$C_CTLIN_BRC bytes received */ : case 1002 /* NMA$C_CTLIN_MBY multicast bytes received */ : NetIntBytesRx64 += value64; break; case 1001 /* NMA$C_CTLIN_BSN bytes sent */ : NetIntBytesTx64 += value64; break; case 1010 /* NMA$C_CTLIN_DBR data blocks received */ : case 1012 /* NMA$C_CTLIN_MBL multicast blocks received */ : NetIntBlocksRx64 += value64; break; case 1011 /* NMA$C_CTLIN_DBS data blocks sent */ : NetIntBlocksTx64 += 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 += 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 += value64; break; default : } } } if (PrevBytesRx64 && NetIntBytesRx64 > PrevBytesRx64) SystemData.NetIntBytesRx64 = NetIntBytesRx64 - PrevBytesRx64; else SystemData.NetIntBytesRx64 = 0; PrevBytesRx64 = NetIntBytesRx64; if (PrevBytesTx64 && NetIntBytesTx64 > PrevBytesTx64) SystemData.NetIntBytesTx64 = NetIntBytesTx64 - PrevBytesTx64; else SystemData.NetIntBytesTx64 = 0; PrevBytesTx64 = NetIntBytesTx64; NetIntBytesRxTx64 = NetIntBytesRx64 + NetIntBytesTx64; if (PrevBytesRxTx64 && NetIntBytesRxTx64 > PrevBytesRxTx64) SystemData.NetIntBytesRxTx64 = NetIntBytesRxTx64 - PrevBytesRxTx64; else SystemData.NetIntBytesRxTx64 = 0; PrevBytesRxTx64 = NetIntBytesRxTx64; SystemData.NetBytesRxPerSec64 = (uint64)(float)SystemData.NetIntBytesRx64 / (float)DeltaSeconds; SystemData.NetBytesTxPerSec64 = (uint64)(float)SystemData.NetIntBytesTx64 / (float)DeltaSeconds; SystemData.NetBytesPerSec64 = (uint64)(float)SystemData.NetIntBytesRxTx64 / (float)DeltaSeconds; if (PrevBlocksRx64 && NetIntBlocksRx64 > PrevBlocksRx64) SystemData.NetIntBlocksRx64 = NetIntBlocksRx64 - PrevBlocksRx64; else SystemData.NetIntBlocksRx64 = 0; PrevBlocksRx64 = NetIntBlocksRx64; if (PrevBlocksTx64 && NetIntBlocksTx64 > PrevBlocksTx64) SystemData.NetIntBlocksTx64 = NetIntBlocksTx64 - PrevBlocksTx64; else SystemData.NetIntBlocksTx64 = 0; PrevBlocksTx64 = NetIntBlocksTx64; if (SystemData.NetIntBlocksRx64) SystemData.NetDatagramRx = (ulong)(float)SystemData.NetIntBytesRx64 / (float)SystemData.NetIntBlocksRx64 / (float)DeltaSeconds; else SystemData.NetDatagramRx = 0; if (SystemData.NetIntBlocksTx64) SystemData.NetDatagramTx = (ulong)(float)SystemData.NetIntBytesTx64 / (float)SystemData.NetIntBlocksTx64 / (float)DeltaSeconds; else SystemData.NetDatagramTx = 0; if (PrevErrorsSoft64 && NetIntErrorsSoft64 > PrevErrorsSoft64) SystemData.NetIntErrorsSoft64 = NetIntErrorsSoft64 - PrevErrorsSoft64; PrevErrorsSoft64 = NetIntErrorsSoft64; if (PrevErrorsHard64 && NetIntErrorsHard64 > PrevErrorsHard64) SystemData.NetIntErrorsHard64 = NetIntErrorsHard64 - PrevErrorsHard64; PrevErrorsHard64 = NetIntErrorsHard64; } /*****************************************************************************/ /* Collect disk System Performance Information. This does not return data for devices in mount verify (perhaps unsurprisingly). */ void CollectDiskSPI () { #define GETSPI$_DISKS 4203 typedef struct _getspi$disk_offsets { short int getspi$w_disk_alloclass; int getspi$l_disk_devname; short int getspi$w_disk_unitnum; char getspi$b_disk_flags; int getspi$q_disk_nodename [2]; int getspi$q_disk_volnamel [2]; int getspi$l_disk_volnameh; int getspi$l_disk_optcnt; int getspi$l_disk_qcount; } GETSPI$DISK_OFFSETS; #define DISK_DATA_SIZE sizeof(ulong) + \ (sizeof(GETSPI$DISK_OFFSETS)*DISK_OFFSETS_MAX) static int CallCount; static char DiskData [DISK_DATA_SIZE]; /* initialize structure for SPI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemList1[] = { { sizeof(DiskData), GETSPI$_DISKS, &DiskData, 0 }, {0,0,0,0} }; static int DiskCount = 0; static long LnmIndex = -1; int dcnt, ncnt, status; char ch; char *cptr, *sptr; char DiskName [128]; struct STRUCT_IOSBLK IOsb; GETSPI$DISK_OFFSETS *doptr; SYSTEM_DATA DummyData; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectDiskSPI()\n"); if (LnmIndex < 0) { for (LnmIndex = 0; LnmIndex < DISK_DEVICE_MAX; LnmIndex++) { cptr = SysTrnLnm (LOGNAM_DISK, NULL, LnmIndex); if (!cptr) break; strcpy (SystemData.DiskName[LnmIndex], cptr); SystemData.DiskNameLen[LnmIndex] = strlen(cptr); } if ((SystemData.DiskCount = LnmIndex) == DISK_DEVICE_MAX) SystemData.DiskCount--; } /* if no disks being monitored */ if (!SystemData.DiskCount) return; /* collect System Performance Information */ status = exe$getspi (EfnSpi, 0, 0, &ItemList1, &IOsb, 0, 0); if (dbug) fprintf (stdout, "exe$getspi() %%X%08.08X IOsb: %%X%08.08X disk: %d\n", status, IOsb.iosb$w_status, *(ulong*)&DiskData); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) return; DiskCount = *(ulong*)&DiskData; if (DiskCount == DISK_OFFSETS_MAX) EXIT_FI_LI (SS$_BUGCHECK); doptr = (GETSPI$DISK_OFFSETS*)(DiskData + sizeof(ulong)); SystemData.MountCount = 0; /* transfer from SPI into system data structure */ for (dcnt = 0; dcnt < DiskCount; dcnt++, doptr++) { if (doptr->getspi$w_disk_alloclass) snprintf (DiskName, sizeof(DiskName), "$%d$%*.*s%d:", doptr->getspi$w_disk_alloclass, (int)(((char*)(&doptr->getspi$l_disk_devname))[0]), (int)(((char*)(&doptr->getspi$l_disk_devname))[0]), (char*)&doptr->getspi$l_disk_devname+1, doptr->getspi$w_disk_unitnum); else if (((char*)(&doptr->getspi$q_disk_nodename))[0]) snprintf (DiskName, sizeof(DiskName), "%*.*s$%*.*s%d:", (int)(((char*)(&doptr->getspi$q_disk_nodename))[0]), (int)(((char*)(&doptr->getspi$q_disk_nodename))[0]), (char*)doptr->getspi$q_disk_nodename+1, (int)(((char*)(&doptr->getspi$l_disk_devname))[0]), (int)(((char*)(&doptr->getspi$l_disk_devname))[0]), (char*)&doptr->getspi$l_disk_devname+1, doptr->getspi$w_disk_unitnum); else snprintf (DiskName, sizeof(DiskName), "%*.*s%d:", (int)(((char*)(&doptr->getspi$l_disk_devname))[0]), (int)(((char*)(&doptr->getspi$l_disk_devname))[0]), (char*)&doptr->getspi$l_disk_devname+1, doptr->getspi$w_disk_unitnum); /* locate the particular device in the system data */ for (ncnt = 0; ncnt < SystemData.DiskCount; ncnt++) if (!strcmp (SystemData.DiskName[ncnt], DiskName)) break; /* if it's not a required disk device */ if (ncnt >= SystemData.DiskCount) continue; ch = SystemData.DiskVolName[ncnt][0]; /* volume name terminating on first space */ sptr = SystemData.DiskVolName[ncnt]; for (cptr = (char*)doptr->getspi$q_disk_volnamel; cptr < (char*)(doptr->getspi$q_disk_volnamel)+12 && *cptr != ' '; *sptr++ = *cptr++); *sptr = '\0'; if (SystemData.DiskVolName[ncnt][0]) SystemData.MountCount++; if (CallCount && !ch) JsonAlert (NULL, "#%s mounted volume %s", SystemData.DiskName[ncnt], SystemData.DiskVolName[ncnt]); SystemData.DiskOptCnt[ncnt] = doptr->getspi$l_disk_optcnt; if (SystemData.DiskPrevOptCnt[ncnt] && SystemData.DiskOptCnt[ncnt] > SystemData.DiskPrevOptCnt[ncnt]) SystemData.DiskIOPs[ncnt] = (ulong)((float)(SystemData.DiskOptCnt[ncnt] - (float)SystemData.DiskPrevOptCnt[ncnt]) / DeltaSeconds); else SystemData.DiskIOPs[ncnt] = 0; SystemData.DiskPrevOptCnt[ncnt] = SystemData.DiskOptCnt[ncnt]; /* small values here make ROUNDing significant */ SystemData.DiskQueCnt[ncnt] = doptr->getspi$l_disk_qcount; if (SystemData.DiskPrevQueCnt[ncnt] && SystemData.DiskQueCnt[ncnt] > SystemData.DiskPrevQueCnt[ncnt]) SystemData.DiskQueLen[ncnt] = (ulong)ROUND(((float)(SystemData.DiskQueCnt[ncnt] - (float)SystemData.DiskPrevQueCnt[ncnt]) / DeltaSeconds)); else SystemData.DiskQueLen[ncnt] = 0; SystemData.DiskPrevQueCnt[ncnt] = SystemData.DiskQueCnt[ncnt]; } CollectDiskDVI (); CallCount++; } /*****************************************************************************/ /* Collect disk device total and free block (usage). */ void CollectDiskDVI () { #define UCB$M_MNTVERIP 0x4000 #define UCB$M_MSCP_MNTVERIP 0x100 static int DviInit = 1; static ulong DviDevSts, DviErrCnt, DviFreeBlocks, DviVolSize, DviSts; static char DviVolNam [DISK_VOLNAM_SIZE+1]; static $DESCRIPTOR (DevNameDsc, ""); static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } GetDviItems [] = { { sizeof(DviDevSts), DVI$_DEVSTS, &DviDevSts, 0 }, { sizeof(DviErrCnt), DVI$_ERRCNT, &DviErrCnt, 0 }, { sizeof(DviFreeBlocks), DVI$_FREEBLOCKS, &DviFreeBlocks, 0 }, { sizeof(DviSts), DVI$_STS, &DviSts, 0 }, { sizeof(DviVolNam)-1, DVI$_VOLNAM, &DviVolNam, 0 }, { sizeof(DviVolSize), DVI$_VOLSIZE, &DviVolSize, 0 }, { 0,0,0,0 } }; int cnt, dcnt, len, status; struct STRUCT_IOSBLK IOsb; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectDiskDVI()\n"); if (DviInit) { DviInit = 0; for (dcnt = 0; dcnt < SystemData.DiskCount; dcnt++) SystemData.DiskPrevMntVerIP[dcnt] = (ulong)-1; } for (dcnt = 0; dcnt < SystemData.DiskCount; dcnt++) { if (!SystemData.DiskVolName[dcnt][0]) { SystemData.DiskVolSize[dcnt] = SystemData.DiskErrCnt[dcnt] = SystemData.DiskPrevErrCnt[dcnt] = SystemData.DiskFreeBlocks[dcnt] = SystemData.DiskMntVerIP[dcnt] = 0; SystemData.DiskPrevMntVerIP[dcnt] = (ulong)-1; continue; } DevNameDsc.dsc$a_pointer = SystemData.DiskName[dcnt]; DevNameDsc.dsc$w_length = SystemData.DiskNameLen[dcnt]; status = sys$getdviw (EfnWait, 0, &DevNameDsc, &GetDviItems, &IOsb, 0, 0, 0, 0); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) { SystemData.DiskVolSize[dcnt] = SystemData.DiskErrCnt[dcnt] = SystemData.DiskPrevErrCnt[dcnt] = SystemData.DiskFreeBlocks[dcnt] = SystemData.DiskMntVerIP[dcnt] = 0; SystemData.DiskPrevMntVerIP[dcnt] = (ulong)-1; JsonAlert (NULL, "!%s %%X%08.08X", SystemData.DiskName[dcnt], status); continue; } if (dbug) fprintf (stdout, "%s sts: %%X%08.08X devsts: %%X%08.08X volnam: %s\n", SystemData.DiskName[dcnt], DviSts, DviDevSts, DviVolNam); if (!DviVolNam[0]) { /* if the volume name is no longer present it has been dismounted */ if (SystemData.DiskVolName[dcnt][0]) JsonAlert (NULL, "#%s dismounted volume %s", SystemData.DiskName[dcnt], SystemData.DiskVolName[dcnt]); SystemData.DiskVolName[dcnt][0] = '\0'; SystemData.DiskVolSize[dcnt] = SystemData.DiskErrCnt[dcnt] = SystemData.DiskPrevErrCnt[dcnt] = SystemData.DiskFreeBlocks[dcnt] = SystemData.DiskMntVerIP[dcnt] = 0; SystemData.DiskPrevMntVerIP[dcnt] = (ulong)-1; continue; } #if TEST_BENCH { static int ErrCnt; if (!(TimeStamp % 27)) ErrCnt += TimeStamp % 4; DviErrCnt = ErrCnt; if (TimeStamp % (SystemData.DiskCount * 3) == dcnt) DviSts |= UCB$M_MNTVERIP; if (TimeStamp % (SystemData.DiskCount * 4) == dcnt) DviDevSts |= UCB$M_MSCP_MNTVERIP; } #endif SystemData.DiskVolSize[dcnt] = DviVolSize; SystemData.DiskFreeBlocks[dcnt] = DviFreeBlocks; SystemData.DiskErrCnt[dcnt] = DviErrCnt; SystemData.DiskMntVerIP[dcnt] = ((DviSts & UCB$M_MNTVERIP) || (DviDevSts & UCB$M_MSCP_MNTVERIP)); if (SystemData.DiskPrevErrCnt[dcnt] && SystemData.DiskErrCnt[dcnt] > SystemData.DiskPrevErrCnt[dcnt]) JsonAlert (NULL, "!%s error count from %d to %d", SystemData.DiskName[dcnt], SystemData.DiskPrevErrCnt[dcnt], SystemData.DiskErrCnt[dcnt]); SystemData.DiskPrevErrCnt[dcnt] = SystemData.DiskErrCnt[dcnt]; if (SystemData.DiskMntVerIP[dcnt] && !SystemData.DiskPrevMntVerIP[dcnt]) JsonAlert (NULL, "!%s %s into MOUNT VERIFY", SystemData.DiskName[dcnt], SystemData.DiskVolName[dcnt]); else if (!SystemData.DiskMntVerIP[dcnt] && (int)SystemData.DiskPrevMntVerIP[dcnt] > 0) JsonAlert (NULL, "#%s %s out of MOUNT VERIFY", SystemData.DiskName[dcnt], SystemData.DiskVolName[dcnt]); SystemData.DiskPrevMntVerIP[dcnt] = SystemData.DiskMntVerIP[dcnt]; } } /*****************************************************************************/ /* Collect top-process System Performance Information for younger VMSes. Rather than generate the JSON data separately do it here directly using the function-local storage. This function collects process SPI and sorts the top five consumers each of CPU time, super+exec+kernel time, pages faults, buffered and direct I/O. These five are then built into a JSON sturcture for display purposes. This function uses a data structure derived in part from VMS sources and contains items and alignment (seemingly) not documented elsewhere. Not being completely obvious when the new structure started to be used (VMS V8.3?) this function switches between this and the older (documented and header-file described) data structure by sanity-checking the data returned. If any of the item values appear out-of-range then it's likely the offsets are wrong for this (older) system. Switch to using an equivalent function that employs that older structure. */ void CollectProcSPI1 () { #define GETSPI$_PROC 4123 /* LOTS of trial-and-error in this structure! */ typedef struct _getspi$proc_class { int getspi$l_proc_ipid; int getspi$l_proc_uic; short int getspi$w_proc_state; char getspi$b_proc_pri; /* OCTAWORD basealign octaword? */ char filler_to_octaword_alignment [5]; char getspi$o_proc_lname [16]; int getspi$l_proc_gpgcnt; int getspi$l_proc_ppgcnt; int getspi$l_proc_sts; int getspi$l_proc_diocnt; int getspi$l_proc_pageflts; int getspi$l_proc_cputim; int getspi$l_proc_biocnt; int getspi$l_proc_epid; int getspi$l_proc_efwm; int getspi$l_proc_rbstran; /* from V8.3 IA64 SPIDEF.SDL */ int getspi$l_proc_kernel_counter; int getspi$l_proc_executive_counter; int getspi$l_proc_supervisor_counter; int getspi$l_proc_user_counter; int getspi$l_proc_reserved1; /* use the second reserve to accumulate non-user ticks */ /** int getspi$l_proc_reserved2; **/ int getspi$l_proc_SEK; } GETSPI$PROC_CLASS; static long BufferNumber, StateCefCount, StateColpgCount, StateComCount, StateComoCount, StateCurCount, StateFpgCount, StateHibCount, StateHiboCount, StateLefCount, StateLefoCount, StateMwaitCount, StatePfwCount, StateSuspCount, StateSuspoCount; static int ProcDataSize, ProcMax; static char *ProcData1, *ProcData2; /* initialize structure for SPI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemList1[] = { { 0, GETSPI$_PROC, 0, 0 }, { sizeof(StateCefCount), GETSPI$_CEF, &StateCefCount, 0 }, { sizeof(StateColpgCount), GETSPI$_COLPG, &StateColpgCount, 0 }, { sizeof(StateComCount), GETSPI$_COM, &StateComCount, 0 }, { sizeof(StateComoCount), GETSPI$_COMO, &StateComoCount, 0 }, { sizeof(StateCurCount), GETSPI$_CUR, &StateCurCount, 0 }, { sizeof(StateFpgCount), GETSPI$_FPG, &StateFpgCount, 0 }, { sizeof(StateHibCount), GETSPI$_HIB, &StateHibCount, 0 }, { sizeof(StateHiboCount), GETSPI$_HIBO, &StateHiboCount, 0 }, { sizeof(StateLefCount), GETSPI$_LEF, &StateLefCount, 0 }, { sizeof(StateLefoCount), GETSPI$_LEFO, &StateLefoCount, 0 }, { sizeof(StateMwaitCount), GETSPI$_MWAIT, &StateMwaitCount, 0 }, { sizeof(StatePfwCount), GETSPI$_PFW, &StatePfwCount, 0 }, { sizeof(StateSuspCount), GETSPI$_SUSP, &StateSuspCount, 0 }, { sizeof(StateSuspoCount), GETSPI$_SUSPO, &StateSuspoCount, 0 }, {0,0,0,0} }; int cnt, spicnt, sprcnt, status, topcnt, PrevProcCount, ProcCount, RowCount; char *sptr, *zptr; struct STRUCT_IOSBLK IOsb; GETSPI$PROC_CLASS *prvptr, *spiptr, *sprptr; GETSPI$PROC_CLASS *TopCpuTim [PROC_MAX], *TopBioCnt [PROC_MAX], *TopDioCnt [PROC_MAX], *TopPageFlts [PROC_MAX], *TopSekTim [PROC_MAX]; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectProcSPI1()\n"); /* if process not being monitored */ if (!SystemData.ProvideProcessData) return; /***************/ /* collect SPI */ /***************/ if (SystemData.ProcessCount >= ProcMax) { /* grow the buffer space within limits */ while (SystemData.ProcessCount >= ProcMax && ProcMax < PROC_CLASS_MAX) ProcMax += PROC_CLASS_INC; /* plus ten gives a little margin for process growth since count */ ProcDataSize = sizeof(long) + (sizeof(GETSPI$PROC_CLASS) * (ProcMax+10)); if (ProcData1) free (ProcData1); ProcData1 = calloc (1, ProcDataSize); if (!ProcData1) EXIT_FI_LI (vaxc$errno); if (ProcData2) free (ProcData2); ProcData2 = calloc (1, ProcDataSize); if (!ProcData2) EXIT_FI_LI (vaxc$errno); ItemList1[0].BufferLength = ProcDataSize; BufferNumber = 0; } /* swap back and forth between the two buffers */ if (BufferNumber++ % 2) { spiptr = (GETSPI$PROC_CLASS*)ProcData2; prvptr = (GETSPI$PROC_CLASS*)ProcData1; } else { spiptr = (GETSPI$PROC_CLASS*)ProcData1; prvptr = (GETSPI$PROC_CLASS*)ProcData2; } ItemList1[0].BufferPtr = spiptr; /* collect System Performance Information */ status = exe$getspi (EfnSpi, 0, 0, &ItemList1, &IOsb, 0, 0); if (dbug) fprintf (stdout, "exe$getspi() %%X%08.08X IOsb: %%X%08.08X proc: %d\n", status, IOsb.iosb$w_status, *(ulong*)spiptr); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) { SystemData.ProcBufferStatus = status; return; } if (SystemData.ProcSanityCheck == 0) { /* sanity check the data structure */ SystemData.ProcSanityCheck = 1; ProcCount = *(ulong*)spiptr; spiptr = (GETSPI$PROC_CLASS*)((char*)spiptr + sizeof(ulong)); for (spicnt = 0; spicnt < ProcCount; spicnt++, spiptr++) { /* if any seem out-of-range the offsets are probably wrong */ if (spiptr->getspi$l_proc_epid == 0 || spiptr->getspi$w_proc_state < 0 || spiptr->getspi$w_proc_state > 255 || *spiptr->getspi$o_proc_lname < 0 || *spiptr->getspi$o_proc_lname > 15) { /* try using the older version of the structure */ free (ProcData1); free (ProcData2); SystemData.ProcSpiFunction = &CollectProcSPI2; CollectProcSPI2 (); return; } } } /**********************/ /* get process counts */ /**********************/ /* number of processes read by SPI */ ProcCount = *(ulong*)spiptr; if (ProcCount == PROC_CLASS_MAX) EXIT_FI_LI (SS$_BUGCHECK); /* now step over the process count */ spiptr = (GETSPI$PROC_CLASS*)((char*)spiptr + sizeof(ulong)); /* number of process read into the previous SPI */ PrevProcCount = *(ulong*)prvptr; /* now step over the process count */ prvptr = (GETSPI$PROC_CLASS*)((char*)prvptr + sizeof(ulong)); /* if there is no previous data then just skip this initial call */ if (!PrevProcCount) return; /**************************/ /* loop through processes */ /**************************/ for (cnt = 0; cnt < PROC_MAX; cnt++) TopCpuTim[cnt] = TopBioCnt[cnt] = TopDioCnt[cnt] = TopPageFlts[cnt] = TopSekTim[cnt] = NULL; for (spicnt = 0; spicnt < ProcCount; spicnt++, spiptr++) { if (dbug && DEBUG) fprintf (stdout, "SPI %2d. %8.8X |%*.*s| cpu:%u pgflt:%u dio:%u bio:%u\n", spicnt+1, spiptr->getspi$l_proc_epid, *spiptr->getspi$o_proc_lname, *spiptr->getspi$o_proc_lname, spiptr->getspi$o_proc_lname+1, spiptr->getspi$l_proc_cputim, spiptr->getspi$l_proc_pageflts, spiptr->getspi$l_proc_diocnt, spiptr->getspi$l_proc_biocnt); /*************************/ /* find in previous data */ /*************************/ sprptr = prvptr; for (sprcnt = 0; sprcnt < PrevProcCount; sprcnt++, sprptr++) if (spiptr->getspi$l_proc_epid == sprptr->getspi$l_proc_epid) break; /* if process not found in previous data */ if (sprcnt >= PrevProcCount) continue; if (dbug && DEBUG) fprintf (stdout, "PRV %2d. %8.8X |%*.*s| cpu:%u pgflt:%u dio:%u bio:%u\n", sprcnt+1, sprptr->getspi$l_proc_epid, *spiptr->getspi$o_proc_lname, *spiptr->getspi$o_proc_lname, sprptr->getspi$o_proc_lname+1, sprptr->getspi$l_proc_cputim, sprptr->getspi$l_proc_pageflts, sprptr->getspi$l_proc_diocnt, sprptr->getspi$l_proc_biocnt); /********************/ /* calculate deltas */ /********************/ sprptr->getspi$l_proc_cputim = (int)((float)(spiptr->getspi$l_proc_cputim - sprptr->getspi$l_proc_cputim) / DeltaSeconds); sprptr->getspi$l_proc_biocnt = (int)((float)(spiptr->getspi$l_proc_biocnt - sprptr->getspi$l_proc_biocnt) / DeltaSeconds); sprptr->getspi$l_proc_diocnt = (int)((float)(spiptr->getspi$l_proc_diocnt - sprptr->getspi$l_proc_diocnt) / DeltaSeconds); sprptr->getspi$l_proc_pageflts = (int)((float)(spiptr->getspi$l_proc_pageflts - sprptr->getspi$l_proc_pageflts) / DeltaSeconds); sprptr->getspi$l_proc_SEK = spiptr->getspi$l_proc_kernel_counter - sprptr->getspi$l_proc_kernel_counter; sprptr->getspi$l_proc_SEK += spiptr->getspi$l_proc_executive_counter - sprptr->getspi$l_proc_executive_counter; sprptr->getspi$l_proc_SEK += spiptr->getspi$l_proc_supervisor_counter - sprptr->getspi$l_proc_supervisor_counter; sprptr->getspi$l_proc_SEK = (int)((float)sprptr->getspi$l_proc_SEK / DeltaSeconds); /**************************/ /* populate sorted values */ /**************************/ if (sprptr->getspi$l_proc_cputim) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopCpuTim[topcnt]) { if (sprptr->getspi$l_proc_cputim > TopCpuTim[topcnt]->getspi$l_proc_cputim) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopCpuTim[cnt] = TopCpuTim[cnt-1]; TopCpuTim[topcnt] = sprptr; break; } } else { TopCpuTim[topcnt] = sprptr; break; } } if (sprptr->getspi$l_proc_biocnt) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopBioCnt[topcnt]) { if (sprptr->getspi$l_proc_biocnt > TopBioCnt[topcnt]->getspi$l_proc_biocnt) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopBioCnt[cnt] = TopBioCnt[cnt-1]; TopBioCnt[topcnt] = sprptr; break; } } else { TopBioCnt[topcnt] = sprptr; break; } } if (sprptr->getspi$l_proc_diocnt) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopDioCnt[topcnt]) { if (sprptr->getspi$l_proc_diocnt > TopDioCnt[topcnt]->getspi$l_proc_diocnt) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopDioCnt[cnt] = TopDioCnt[cnt-1]; TopDioCnt[topcnt] = sprptr; break; } } else { TopDioCnt[topcnt] = sprptr; break; } } if (sprptr->getspi$l_proc_pageflts) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopPageFlts[topcnt]) { if (sprptr->getspi$l_proc_pageflts > TopPageFlts[topcnt]->getspi$l_proc_pageflts) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopPageFlts[cnt] = TopPageFlts[cnt-1]; TopPageFlts[topcnt] = sprptr; break; } } else { TopPageFlts[topcnt] = sprptr; break; } } if (sprptr->getspi$l_proc_SEK) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopSekTim[topcnt]) { if (sprptr->getspi$l_proc_SEK > TopSekTim[topcnt]->getspi$l_proc_SEK) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopSekTim[cnt] = TopSekTim[cnt-1]; TopSekTim[topcnt] = sprptr; break; } } else { TopSekTim[topcnt] = sprptr; break; } } /********************/ /* end process loop */ /********************/ } /*******************/ /* build JSON data */ /*******************/ zptr = (sptr = SystemData.ProcBuffer) + sizeof(SystemData.ProcBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"process\""); sptr += snprintf (sptr, zptr-sptr, ",\"spi\":1"); sptr += snprintf (sptr, zptr-sptr, ",\"COM\":%d", StateComCount); sptr += snprintf (sptr, zptr-sptr, ",\"CUR\":%d", StateCurCount); sptr += snprintf (sptr, zptr-sptr, ",\"COMO\":%d", StateComoCount); sptr += snprintf (sptr, zptr-sptr, ",\"HIB\":%d", StateHibCount); sptr += snprintf (sptr, zptr-sptr, ",\"HIBO\":%d", StateHiboCount); sptr += snprintf (sptr, zptr-sptr, ",\"LEF\":%d", StateLefCount); sptr += snprintf (sptr, zptr-sptr, ",\"LEFO\":%d", StateLefoCount); sptr += snprintf (sptr, zptr-sptr, ",\"MWAIT\":%d", StateMwaitCount); sptr += snprintf (sptr, zptr-sptr, ",\"SUSP\":%d", StateSuspCount); sptr += snprintf (sptr, zptr-sptr, ",\"SUSPO\":%d", StateSuspoCount); sptr += snprintf (sptr, zptr-sptr, ",\"PFW\":%d", StatePfwCount); sptr += snprintf (sptr, zptr-sptr, ",\"swapped\":%d", StateComoCount + StateHiboCount + StateLefoCount + StateSuspoCount); sptr += snprintf (sptr, zptr-sptr, ",\"waiting\":%d", StateCefCount + StateColpgCount + StateFpgCount + StateMwaitCount + StatePfwCount); sptr += snprintf (sptr, zptr-sptr, ",\"top\":{"); sptr += snprintf (sptr, zptr-sptr, "\"cpu\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopCpuTim[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %1.2f\"", cnt ? "," : "", TopCpuTim[cnt]->getspi$l_proc_epid, MungeProcName(TopCpuTim[cnt]->getspi$o_proc_lname), (float)TopCpuTim[cnt]->getspi$l_proc_cputim/100.0); } sptr += snprintf (sptr, zptr-sptr, "],\"sek\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopSekTim[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %1.2f\"", cnt ? "," : "", TopSekTim[cnt]->getspi$l_proc_epid, MungeProcName(TopSekTim[cnt]->getspi$o_proc_lname), (float)TopSekTim[cnt]->getspi$l_proc_SEK/100.0); } sptr += snprintf (sptr, zptr-sptr, "],\"flts\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopPageFlts[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %4u\"", cnt ? "," : "", TopPageFlts[cnt]->getspi$l_proc_epid, MungeProcName(TopPageFlts[cnt]->getspi$o_proc_lname), TopPageFlts[cnt]->getspi$l_proc_pageflts); } sptr += snprintf (sptr, zptr-sptr, "],\"bio\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopBioCnt[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %4u\"", cnt ? "," : "", TopBioCnt[cnt]->getspi$l_proc_epid, MungeProcName(TopBioCnt[cnt]->getspi$o_proc_lname), TopBioCnt[cnt]->getspi$l_proc_biocnt); } sptr += snprintf (sptr, zptr-sptr, "],\"dio\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopDioCnt[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %4u\"", cnt ? "," : "", TopDioCnt[cnt]->getspi$l_proc_epid, MungeProcName(TopDioCnt[cnt]->getspi$o_proc_lname), TopDioCnt[cnt]->getspi$l_proc_diocnt); } sptr += snprintf (sptr, zptr-sptr, "]}}"); if (sptr >= zptr) EXIT_FI_LI (SS$_BUGCHECK); SystemData.ProcBufferLength = sptr - SystemData.ProcBuffer; if (dbug && DEBUG) fprintf (stdout, "\n%d\n%s\n", SystemData.ProcBufferLength, SystemData.ProcBuffer); } /*****************************************************************************/ /* Collect top-process System Performance Information for older VMSes. Functionality is as per CollectProcSPI1(). */ void CollectProcSPI2 () { #define GETSPI$_PROC 4123 /* structure according to the book! */ typedef struct _getspi$proc_class { int getspi$l_proc_ipid; int getspi$l_proc_uic; short int getspi$w_proc_state; char getspi$b_proc_pri; char getspi$o_proc_lname [16]; int getspi$l_proc_gpgcnt; int getspi$l_proc_ppgcnt; int getspi$l_proc_sts; int getspi$l_proc_diocnt; int getspi$l_proc_pageflts; int getspi$l_proc_cputim; int getspi$l_proc_biocnt; int getspi$l_proc_epid; int getspi$l_proc_efwm; int getspi$l_proc_rbstran; } GETSPI$PROC_CLASS; static long BufferNumber, StateCefCount, StateColpgCount, StateComCount, StateComoCount, StateCurCount, StateFpgCount, StateHibCount, StateHiboCount, StateLefCount, StateLefoCount, StateMwaitCount, StatePfwCount, StateSuspCount, StateSuspoCount; static int ProcDataSize, ProcMax; static ushort PrevMinute = -1; static char *ProcData1, *ProcData2; /* initialize structure for SPI items */ static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemList1[] = { { 0, GETSPI$_PROC, 0, 0 }, { sizeof(StateCefCount), GETSPI$_CEF, &StateCefCount, 0 }, { sizeof(StateColpgCount), GETSPI$_COLPG, &StateColpgCount, 0 }, { sizeof(StateComCount), GETSPI$_COM, &StateComCount, 0 }, { sizeof(StateComoCount), GETSPI$_COMO, &StateComoCount, 0 }, { sizeof(StateCurCount), GETSPI$_CUR, &StateCurCount, 0 }, { sizeof(StateFpgCount), GETSPI$_FPG, &StateFpgCount, 0 }, { sizeof(StateHibCount), GETSPI$_HIB, &StateHibCount, 0 }, { sizeof(StateHiboCount), GETSPI$_HIBO, &StateHiboCount, 0 }, { sizeof(StateLefCount), GETSPI$_LEF, &StateLefCount, 0 }, { sizeof(StateLefoCount), GETSPI$_LEFO, &StateLefoCount, 0 }, { sizeof(StateMwaitCount), GETSPI$_MWAIT, &StateMwaitCount, 0 }, { sizeof(StatePfwCount), GETSPI$_PFW, &StatePfwCount, 0 }, { sizeof(StateSuspCount), GETSPI$_SUSP, &StateSuspCount, 0 }, { sizeof(StateSuspoCount), GETSPI$_SUSPO, &StateSuspoCount, 0 }, {0,0,0,0} }; int cnt, spicnt, sprcnt, status, topcnt, PrevProcCount, ProcCount, RowCount; char *sptr, *zptr; struct STRUCT_IOSBLK IOsb; GETSPI$PROC_CLASS *prvptr, *spiptr, *sprptr; GETSPI$PROC_CLASS *TopCpuTim [PROC_MAX], *TopBioCnt [PROC_MAX], *TopDioCnt [PROC_MAX], *TopPageFlts [PROC_MAX]; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectProcSPI2()\n"); SystemData.ProcBuffer[SystemData.ProcBufferLength = 0] = '\0'; /* if process not being monitored */ if (!SystemData.ProvideProcessData) return; /***************/ /* collect SPI */ /***************/ if (SystemData.ProcessCount >= ProcMax) { /* grow the buffer space within limits */ while (SystemData.ProcessCount >= ProcMax && ProcMax < PROC_CLASS_MAX) ProcMax += PROC_CLASS_INC; /* plus ten gives a little margin for process growth since count */ ProcDataSize = sizeof(long) + (sizeof(GETSPI$PROC_CLASS) * (ProcMax+10)); if (ProcData1) free (ProcData1); ProcData1 = calloc (1, ProcDataSize); if (!ProcData1) EXIT_FI_LI (vaxc$errno); if (ProcData2) free (ProcData2); ProcData2 = calloc (1, ProcDataSize); if (!ProcData2) EXIT_FI_LI (vaxc$errno); ItemList1[0].BufferLength = ProcDataSize; BufferNumber = 0; } /* swap back and forth between the two buffers */ if (BufferNumber++ % 2) { spiptr = (GETSPI$PROC_CLASS*)ProcData2; prvptr = (GETSPI$PROC_CLASS*)ProcData1; } else { spiptr = (GETSPI$PROC_CLASS*)ProcData1; prvptr = (GETSPI$PROC_CLASS*)ProcData2; } ItemList1[0].BufferPtr = spiptr; /* collect System Performance Information */ status = exe$getspi (EfnSpi, 0, 0, &ItemList1, &IOsb, 0, 0); if (dbug) fprintf (stdout, "exe$getspi() %%X%08.08X IOsb: %%X%08.08X proc: %d\n", status, IOsb.iosb$w_status, *(ulong*)spiptr); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) { SystemData.ProcBufferStatus = status; return; } if (SystemData.ProcSanityCheck == 1) { /* sanity check the data structure */ SystemData.ProcSanityCheck = 2; ProcCount = *(ulong*)ItemList1[0].BufferPtr; spiptr = (GETSPI$PROC_CLASS*)((char*)spiptr + sizeof(ulong)); for (spicnt = 0; spicnt < ProcCount; spicnt++, spiptr++) { /* if any seem out-of-range the offsets are probably wrong - hmmm! */ if (spiptr->getspi$l_proc_epid == 0 || spiptr->getspi$w_proc_state < 0 || spiptr->getspi$w_proc_state > 255 || *spiptr->getspi$o_proc_lname < 0 || *spiptr->getspi$o_proc_lname > 15) EXIT_FI_LI (SS$_BUGCHECK); } } /**********************/ /* get process counts */ /**********************/ /* number of processes read by SPI */ ProcCount = *(ulong*)ItemList1[0].BufferPtr; if (ProcCount == PROC_CLASS_MAX) EXIT_FI_LI (SS$_BUGCHECK); /* now step over the process count */ spiptr = (GETSPI$PROC_CLASS*)((char*)spiptr + sizeof(ulong)); /* number of process read into the previous SPI */ PrevProcCount = *(ulong*)prvptr; /* now step over the process count */ prvptr = (GETSPI$PROC_CLASS*)((char*)prvptr + sizeof(ulong)); /* if there is no previous data then just skip this initial call */ if (!PrevProcCount) return; /**************************/ /* loop through processes */ /**************************/ for (cnt = 0; cnt < PROC_MAX; cnt++) TopCpuTim[cnt] = TopBioCnt[cnt] = TopDioCnt[cnt] = TopPageFlts[cnt] = NULL; for (spicnt = 0; spicnt < ProcCount; spicnt++, spiptr++) { if (dbug && DEBUG) fprintf (stdout, "SPI %2d. %8.8X |%*.*s| cpu:%u pgflt:%u dio:%u bio:%u\n", spicnt+1, spiptr->getspi$l_proc_epid, *spiptr->getspi$o_proc_lname, *spiptr->getspi$o_proc_lname, spiptr->getspi$o_proc_lname+1, spiptr->getspi$l_proc_cputim, spiptr->getspi$l_proc_pageflts, spiptr->getspi$l_proc_diocnt, spiptr->getspi$l_proc_biocnt); /*************************/ /* find in previous data */ /*************************/ sprptr = prvptr; for (sprcnt = 0; sprcnt < PrevProcCount; sprcnt++, sprptr++) if (spiptr->getspi$l_proc_epid == sprptr->getspi$l_proc_epid) break; /* if process not found in previous data */ if (sprcnt >= PrevProcCount) continue; if (dbug && DEBUG) fprintf (stdout, "PRV %2d. %8.8X |%*.*s| cpu:%u pgflt:%u dio:%u bio:%u\n", sprcnt+1, sprptr->getspi$l_proc_epid, *spiptr->getspi$o_proc_lname, *spiptr->getspi$o_proc_lname, sprptr->getspi$o_proc_lname+1, sprptr->getspi$l_proc_cputim, sprptr->getspi$l_proc_pageflts, sprptr->getspi$l_proc_diocnt, sprptr->getspi$l_proc_biocnt); /********************/ /* calculate deltas */ /********************/ sprptr->getspi$l_proc_cputim = (int)((float)(spiptr->getspi$l_proc_cputim - sprptr->getspi$l_proc_cputim) / DeltaSeconds); sprptr->getspi$l_proc_biocnt = (int)((float)(spiptr->getspi$l_proc_biocnt - sprptr->getspi$l_proc_biocnt) / DeltaSeconds); sprptr->getspi$l_proc_diocnt = (int)((float)(spiptr->getspi$l_proc_diocnt - sprptr->getspi$l_proc_diocnt) / DeltaSeconds); sprptr->getspi$l_proc_pageflts = (int)((float)(spiptr->getspi$l_proc_pageflts - sprptr->getspi$l_proc_pageflts) / DeltaSeconds); /**************************/ /* populate sorted values */ /**************************/ if (sprptr->getspi$l_proc_cputim) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopCpuTim[topcnt]) { if (sprptr->getspi$l_proc_cputim > TopCpuTim[topcnt]->getspi$l_proc_cputim) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopCpuTim[cnt] = TopCpuTim[cnt-1]; TopCpuTim[topcnt] = sprptr; break; } } else { TopCpuTim[topcnt] = sprptr; break; } } if (sprptr->getspi$l_proc_biocnt) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopBioCnt[topcnt]) { if (sprptr->getspi$l_proc_biocnt > TopBioCnt[topcnt]->getspi$l_proc_biocnt) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopBioCnt[cnt] = TopBioCnt[cnt-1]; TopBioCnt[topcnt] = sprptr; break; } } else { TopBioCnt[topcnt] = sprptr; break; } } if (sprptr->getspi$l_proc_diocnt) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopDioCnt[topcnt]) { if (sprptr->getspi$l_proc_diocnt > TopDioCnt[topcnt]->getspi$l_proc_diocnt) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopDioCnt[cnt] = TopDioCnt[cnt-1]; TopDioCnt[topcnt] = sprptr; break; } } else { TopDioCnt[topcnt] = sprptr; break; } } if (sprptr->getspi$l_proc_pageflts) for (topcnt = 0; topcnt < PROC_MAX; topcnt++) { if (TopPageFlts[topcnt]) { if (sprptr->getspi$l_proc_pageflts > TopPageFlts[topcnt]->getspi$l_proc_pageflts) { for (cnt = PROC_MAX-1; cnt > topcnt; cnt--) TopPageFlts[cnt] = TopPageFlts[cnt-1]; TopPageFlts[topcnt] = sprptr; break; } } else { TopPageFlts[topcnt] = sprptr; break; } } /********************/ /* end process loop */ /********************/ } /*******************/ /* build JSON data */ /*******************/ zptr = (sptr = SystemData.ProcBuffer) + sizeof(SystemData.ProcBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"process\""); sptr += snprintf (sptr, zptr-sptr, ",\"spi\":2"); sptr += snprintf (sptr, zptr-sptr, ",\"COM\":%d", StateComCount); sptr += snprintf (sptr, zptr-sptr, ",\"CUR\":%d", StateCurCount); sptr += snprintf (sptr, zptr-sptr, ",\"COMO\":%d", StateComoCount); sptr += snprintf (sptr, zptr-sptr, ",\"HIB\":%d", StateHibCount); sptr += snprintf (sptr, zptr-sptr, ",\"HIBO\":%d", StateHiboCount); sptr += snprintf (sptr, zptr-sptr, ",\"LEF\":%d", StateLefCount); sptr += snprintf (sptr, zptr-sptr, ",\"LEFO\":%d", StateLefoCount); sptr += snprintf (sptr, zptr-sptr, ",\"MWAIT\":%d", StateMwaitCount); sptr += snprintf (sptr, zptr-sptr, ",\"SUSP\":%d", StateSuspCount); sptr += snprintf (sptr, zptr-sptr, ",\"SUSPO\":%d", StateSuspoCount); sptr += snprintf (sptr, zptr-sptr, ",\"PFW\":%d", StatePfwCount); sptr += snprintf (sptr, zptr-sptr, ",\"swapped\":%d", StateComoCount + StateHiboCount + StateLefoCount + StateSuspoCount); sptr += snprintf (sptr, zptr-sptr, ",\"waiting\":%d", StateCefCount + StateColpgCount + StateFpgCount + StateMwaitCount + StatePfwCount); sptr += snprintf (sptr, zptr-sptr, ",\"top\":{"); sptr += snprintf (sptr, zptr-sptr, "\"cpu\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopCpuTim[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %1.2f\"", cnt ? "," : "", TopCpuTim[cnt]->getspi$l_proc_epid, MungeProcName(TopCpuTim[cnt]->getspi$o_proc_lname), (float)TopCpuTim[cnt]->getspi$l_proc_cputim/100.0); } sptr += snprintf (sptr, zptr-sptr, "],\"sek\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopCpuTim[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"n/a\"", cnt ? "," : ""); } sptr += snprintf (sptr, zptr-sptr, "],\"flts\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopPageFlts[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %4u\"", cnt ? "," : "", TopPageFlts[cnt]->getspi$l_proc_epid, MungeProcName(TopPageFlts[cnt]->getspi$o_proc_lname), TopPageFlts[cnt]->getspi$l_proc_pageflts); } sptr += snprintf (sptr, zptr-sptr, "],\"bio\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopBioCnt[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %4u\"", cnt ? "," : "", TopBioCnt[cnt]->getspi$l_proc_epid, MungeProcName(TopBioCnt[cnt]->getspi$o_proc_lname), TopBioCnt[cnt]->getspi$l_proc_biocnt); } sptr += snprintf (sptr, zptr-sptr, "],\"dio\":["); for (cnt = 0; cnt < PROC_MAX; cnt++) { if (!TopDioCnt[cnt]) continue; sptr += snprintf (sptr, zptr-sptr, "%s\"%8.8X %s %4u\"", cnt ? "," : "", TopDioCnt[cnt]->getspi$l_proc_epid, MungeProcName(TopDioCnt[cnt]->getspi$o_proc_lname), TopDioCnt[cnt]->getspi$l_proc_diocnt); } sptr += snprintf (sptr, zptr-sptr, "]}}"); if (sptr >= zptr) EXIT_FI_LI (SS$_BUGCHECK); SystemData.ProcBufferLength = sptr - SystemData.ProcBuffer; if (dbug && DEBUG) fprintf (stdout, "\n%d\n%s\n", SystemData.ProcBufferLength, SystemData.ProcBuffer); } /*****************************************************************************/ /* On other than VAX use $GETRMI to to get the process mode counts. */ #define RMI$_PROCS 4122 #define RMI$_PROCBATCNT 21202 #define RMI$_PROCINTCNT 21203 #define RMI$_PROCNETCNT 21204 void CollectJobModes () { static ulong ProcessCount, ProcessBatCount, ProcessIntCount, ProcessNetCount; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemListRmi[] = { { sizeof(ProcessCount), RMI$_PROCS, &ProcessCount, 0 }, { sizeof(ProcessIntCount), RMI$_PROCINTCNT, &ProcessIntCount, 0 }, { sizeof(ProcessBatCount), RMI$_PROCBATCNT, &ProcessBatCount, 0 }, { sizeof(ProcessNetCount), RMI$_PROCNETCNT, &ProcessNetCount, 0 }, {0,0,0,0} }; int status; IOSBLK IOsb; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectJobModes()\n"); /* collect System Performance Information */ status = sys$getrmi ( EfnSpi, /* efn */ 0, /* csiaddr */ 0, /* nodename */ ItemListRmi, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (VMSnok (status)) EXIT_FI_LI (status); status = sys$synch (EfnSpi, &IOsb); if (VMSnok (status)) EXIT_FI_LI (status); if (VMSnok (IOsb.iosb$w_status)) EXIT_FI_LI (IOsb.iosb$w_status); SystemData.ProcessCount = ProcessCount; SystemData.ProcessBatCount = ProcessBatCount; SystemData.ProcessIntCount = ProcessIntCount; SystemData.ProcessNetCount = ProcessNetCount; } /*****************************************************************************/ /* Collect and format per-process data. */ int CollectPerProc (CLIENT_DATA *clptr) { /* 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] = '-'; } #define ALERT_THRESHOLD(lm,cnt) \ (((float)(cnt)*100.0)/((float)lm) <= (float)PerProcAlert) ? "!" : "" static long JpiAstAct, JpiAstCnt, JpiAstEn, JpiAstLm, JpiBioCnt, JpiBioLm, JpiBufIo, JpiBytCnt, JpiBytLm, JpiCpuId, JpiCpuLim, JpiCpuTim, JpiCreator, JpiDioCnt, JpiDioLm, JpiDirIo, JpiEnqCnt, JpiEnqLm, JpiExecTim, JpiFilCnt, JpiFilLm, JpiControlFlags = JPI$M_IGNORE_TARGET_STATUS, JpiGpgCnt, JpiJobPrcCnt, JpiJobType, JpiKtCount, JpiKrnlTim, JpiMode, JpiMultiThread, JpiOwner, JpiPageFlts, JpiPagFilCnt, JpiPgFlQuota, JpiPid, JpiPpgCnt, JpiPrcCnt, JpiPrcLm, JpiPri, JpiPrib, JpiState, JpiSts, JpiSts2, JpiSuprTim, JpiTqCnt, JpiTqLm, JpiUic, JpiUserTim, JpiVolumes, JpiWsAuth, JpiWsAuthExt, JpiWsExtent, JpiWsPeak, JpiWsQuota, JpiWsSize; static int64 JpiFrePteCnt64, JpiLoginTim64, JpiVirtPeak64; static ushort JpiImagNameLen, JpiTtAccPorNamLen, JpiTtPhyDevNamLen; static char JpiImagName [127+1], JpiPrcNam [15+1], JpiTtAccPorNam [80+1], JpiTtPhyDevNam [32+1], JpiTerminal [16+1], JpiUserName [12+1]; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } ItemListJpi[] = { { sizeof(JpiControlFlags), JPI$_GETJPI_CONTROL_FLAGS, &JpiControlFlags, 0 }, { sizeof(JpiAstAct), JPI$_ASTACT, &JpiAstAct, 0 }, { sizeof(JpiAstCnt), JPI$_ASTCNT, &JpiAstCnt, 0 }, { sizeof(JpiAstEn), JPI$_ASTEN, &JpiAstEn, 0 }, { sizeof(JpiAstLm), JPI$_ASTLM, &JpiAstLm, 0 }, { sizeof(JpiBioCnt), JPI$_BIOCNT, &JpiBioCnt, 0 }, { sizeof(JpiBioLm), JPI$_BIOLM, &JpiBioLm, 0 }, { sizeof(JpiBufIo), JPI$_BUFIO, &JpiBufIo, 0 }, { sizeof(JpiBytCnt), JPI$_BYTCNT, &JpiBytCnt, 0 }, { sizeof(JpiBytLm), JPI$_BYTLM, &JpiBytLm, 0 }, { sizeof(JpiCpuId), JPI$_CPU_ID, &JpiCpuId, 0 }, { sizeof(JpiCpuLim), JPI$_CPULIM, &JpiCpuLim, 0 }, { sizeof(JpiCpuTim), JPI$_CPUTIM, &JpiCpuTim, 0 }, { sizeof(JpiCreator), JPI$_CREATOR, &JpiCreator, 0 }, { sizeof(JpiDioCnt), JPI$_DIOCNT, &JpiDioCnt, 0 }, { sizeof(JpiDioLm), JPI$_DIOLM, &JpiDioLm, 0 }, { sizeof(JpiDirIo), JPI$_DIRIO, &JpiDirIo, 0 }, { sizeof(JpiEnqCnt), JPI$_ENQCNT, &JpiEnqCnt, 0 }, { sizeof(JpiEnqLm), JPI$_ENQLM, &JpiEnqLm, 0 }, { sizeof(JpiFilCnt), JPI$_FILCNT, &JpiFilCnt, 0 }, { sizeof(JpiFilLm), JPI$_FILLM, &JpiFilLm, 0 }, { sizeof(JpiFrePteCnt64), JPI$_FREPTECNT, &JpiFrePteCnt64, 0 }, { sizeof(JpiGpgCnt), JPI$_GPGCNT, &JpiGpgCnt, 0 }, { sizeof(JpiJobPrcCnt), JPI$_JOBPRCCNT, &JpiJobPrcCnt, 0 }, { sizeof(JpiJobType), JPI$_JOBTYPE, &JpiJobType, 0 }, { sizeof(JpiKtCount), JPI$_KT_COUNT, &JpiKtCount, 0 }, { sizeof(JpiMode), JPI$_MODE, &JpiMode, 0 }, { sizeof(JpiMultiThread), JPI$_MULTITHREAD, &JpiMultiThread, 0 }, { sizeof(JpiOwner), JPI$_OWNER, &JpiOwner, 0 }, { sizeof(JpiPageFlts), JPI$_PAGEFLTS, &JpiPageFlts, 0 }, { sizeof(JpiPagFilCnt), JPI$_PAGFILCNT, &JpiPagFilCnt, 0 }, { sizeof(JpiPgFlQuota), JPI$_PGFLQUOTA, &JpiPgFlQuota, 0 }, { sizeof(JpiPid), JPI$_PID, &JpiPid, 0 }, { sizeof(JpiPpgCnt), JPI$_PPGCNT, &JpiPpgCnt, 0 }, { sizeof(JpiPrcCnt), JPI$_PRCCNT, &JpiPrcCnt, 0 }, { sizeof(JpiPrcLm), JPI$_PRCLM, &JpiPrcLm, 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$_STS2, &JpiSts2, 0 }, { sizeof(JpiTqCnt), JPI$_TQCNT, &JpiTqCnt, 0 }, { sizeof(JpiTqLm), JPI$_TQLM, &JpiTqLm, 0 }, { sizeof(JpiUic), JPI$_UIC, &JpiUic, 0 }, { sizeof(JpiVirtPeak64), JPI$_VIRTPEAK, &JpiVirtPeak64, 0 }, { sizeof(JpiVolumes), JPI$_VOLUMES, &JpiVolumes, 0 }, { sizeof(JpiWsAuth), JPI$_WSAUTH, &JpiWsAuth, 0 }, { sizeof(JpiWsAuthExt), JPI$_WSAUTHEXT, &JpiWsAuthExt, 0 }, { sizeof(JpiWsExtent), JPI$_WSEXTENT, &JpiWsExtent, 0 }, { sizeof(JpiWsPeak), JPI$_WSPEAK, &JpiWsPeak, 0 }, { sizeof(JpiWsQuota), JPI$_WSQUOTA, &JpiWsQuota, 0 }, { sizeof(JpiWsSize), JPI$_WSSIZE, &JpiWsSize, 0 }, { sizeof(JpiLoginTim64), JPI$_LOGINTIM, &JpiLoginTim64, 0 }, { sizeof(JpiImagName), JPI$_IMAGNAME, &JpiImagName, &JpiImagNameLen }, { sizeof(JpiPrcNam), JPI$_PRCNAM, &JpiPrcNam, 0 }, { sizeof(JpiTerminal), JPI$_TERMINAL, &JpiTerminal, 0 }, { sizeof(JpiTtAccPorNam), JPI$_TT_ACCPORNAM, &JpiTtAccPorNam, &JpiTtAccPorNamLen }, { sizeof(JpiTtPhyDevNam), JPI$_TT_PHYDEVNAM, &JpiTtPhyDevNam, &JpiTtPhyDevNamLen }, { sizeof(JpiUserName), JPI$_USERNAME, &JpiUserName, 0 }, { sizeof(JpiExecTim), JPI$_EXECTIM, &JpiExecTim, 0 }, { sizeof(JpiKrnlTim), JPI$_KRNLTIM, &JpiKrnlTim, 0 }, { sizeof(JpiSuprTim), JPI$_SUPRTIM, &JpiSuprTim, 0 }, { sizeof(JpiUserTim), JPI$_USERTIM, &JpiUserTim, 0 }, {0,0,0,0} }; static char AlertMsg [128], UicString [64]; static $DESCRIPTOR (AlertMsgDsc, AlertMsg); static $DESCRIPTOR (UicStringDsc, UicString); static $DESCRIPTOR (UicFaoDsc, "!%U\0"); int status; char *cptr, *sptr, *zptr, *mode, *state, *type; ulong pid, BufIo, CpuTime, CpuSekTime, CpuPercent, CpuSekPercent, DirIo, PageFlts; int64 ConTime64, CpuTime64, CpuExecTime64, CpuKrnlTime64, CpuLimTime64, CpuSuprTime64, CpuUserTime64; ushort slen; char AstFlags [16], CpuHash [16], CpuStats [64], PcbFlags [64*8]; IOSBLK IOsb; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "CollectPerProc()\n"); clptr->PerProcBuffer[clptr->PerProcBufferLength = 0] = '\0'; if ((pid = clptr->PerProcPid) != clptr->PrevPerProcPid) { clptr->PrevPerProcPid = clptr->PerProcPid; clptr->PrevCpuTime = clptr->PrevCpuSekTime = clptr->PrevJpiBufIo = clptr->PrevJpiDirIo = clptr->PrevJpiPageFlts = clptr->PerProcDatum = 0; } /****************/ /* collect data */ /****************/ status = sys$getjpiw ( EfnWait, /* efn */ &pid, /* pidaddr */ 0, /* prcnam */ &ItemListJpi, /* item list */ &IOsb, /* iosb */ 0, /* astaddr */ 0 ); /* astprm */ if (dbug) fprintf (stdout, "sys$getjpi() %%X%08.08X IOsb: %%X%08.08X\n", status, IOsb.iosb$w_status); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSnok (status)) { sys$getmsg (status, &slen, &AlertMsgDsc, 0, 0); AlertMsg[slen] = '\0'; clptr->PerProcPid = 0; JsonAlert (clptr, "!PID %08.08X %%X%08.08X %s", pid, status, AlertMsg); sptr = clptr->PerProcBuffer; sptr += snprintf (sptr, sizeof(clptr->PerProcBuffer), "{\"$data\":\"per-process\",\"PID\":\"0\"}"); clptr->PerProcBufferLength = sptr - clptr->PerProcBuffer; return (status); } switch (JpiMode) { case 0 : mode = "OTH"; break; case 1 : mode = "NET"; break; case 2 : mode = "BAT"; break; case 3 : mode = "INT"; break; default : mode = "?"; } switch (JpiJobType) { case 0 : type = "DET"; break; case 1 : type = "NET"; break; case 2 : type = "BAT"; break; case 3 : type = "LOC"; break; case 4 : type = "DIA"; break; case 5 : type = "REM"; break; default : type = "?"; } switch (JpiState) { case 1 : state = "COLPG"; break; case 2 : state = "MWAIT"; break; case 3 : state = "CEF"; break; case 4 : state = "PFW"; break; case 5 : state = "LEF"; break; case 6 : state = "LEFO"; break; case 7 : state = "HIB"; break; case 8 : state = "HIBO"; break; case 9 : state = "SUSP"; break; case 10 : state = "SUSPO"; break; case 11 : state = "FPG"; break; case 12 : state = "COM"; break; case 13 : state = "COMO"; break; case 14 : state = "CUR"; break; default : state = "?"; } /* AST flags */ AstFlags[0] = 'U'; ASTFLG(1,1) AstFlags[2] = 'S'; ASTFLG(2,3) AstFlags[4] = 'E'; ASTFLG(4,5) AstFlags[6] = 'K'; ASTFLG(8,7) AstFlags[8] = '\0'; PcbFlags[0] = '\0'; if (JpiSts & 0x1) strcat (PcbFlags, " RES"); if (JpiSts & 0x2) strcat (PcbFlags, " DELPEN"); if (JpiSts & 0x4) strcat (PcbFlags, " FORCPEN"); if (JpiSts & 0x8) strcat (PcbFlags, " INQUAN"); if (JpiSts & 0x10) strcat (PcbFlags, " PSWAPM"); if (JpiSts & 0x20) strcat (PcbFlags, " RESPEN"); if (JpiSts & 0x40) strcat (PcbFlags, " SSFEXC"); if (JpiSts & 0x80) strcat (PcbFlags, " SSFEXCE"); if (JpiSts & 0x100) strcat (PcbFlags, " SSFEXCS"); if (JpiSts & 0x200) strcat (PcbFlags, " SSFEXCU"); if (JpiSts & 0x400) strcat (PcbFlags, " SSRWAIT"); if (JpiSts & 0x800) strcat (PcbFlags, " SUSPEN"); if (JpiSts & 0x2000) strcat (PcbFlags, " WALL"); if (JpiSts & 0x4000) strcat (PcbFlags, " BATCH"); if (JpiSts & 0x8000) strcat (PcbFlags, " NOACNT"); if (JpiSts & 0x10000) strcat (PcbFlags, " NOSUSPEND"); if (JpiSts & 0x20000) strcat (PcbFlags, " ASTPEN"); if (JpiSts & 0x40000) strcat (PcbFlags, " PHDRES"); if (JpiSts & 0x80000) strcat (PcbFlags, " HIBER"); if (JpiSts & 0x100000) strcat (PcbFlags, " LOGIN"); if (JpiSts & 0x200000) strcat (PcbFlags, " NETWRK"); if (JpiSts & 0x400000) strcat (PcbFlags, " PWRAST"); if (JpiSts & 0x800000) strcat (PcbFlags, " NODELET"); if (JpiSts & 0x1000000) strcat (PcbFlags, " DISAWS"); if (JpiSts & 0x2000000) strcat (PcbFlags, " INTER"); if (JpiSts & 0x4000000) strcat (PcbFlags, " RECOVER"); if (JpiSts & 0x8000000) strcat (PcbFlags, " 0x8000000"); if (JpiSts & 0x1000000) strcat (PcbFlags, " HARDAFF"); if (JpiSts & 0x2000000) strcat (PcbFlags, " ERDACT"); if (JpiSts & 0x4000000) strcat (PcbFlags, " SOFTSUSP"); if (JpiSts & 0x8000000) strcat (PcbFlags, " PREEMPTED"); if (JpiSts2 & 0x1) strcat (PcbFlags, " QRESCHED"); if (JpiSts2 & 0x2) strcat (PcbFlags, " DEPLOCK"); if (JpiSts2 & 0x3) strcat (PcbFlags, " FREDLOCK"); if (JpiSts2 & 0x4) strcat (PcbFlags, " PHDLOCK"); if (JpiSts2 & 0x10) strcat (PcbFlags, " TCB"); if (JpiSts2 & 0x20) strcat (PcbFlags, " TBSPEND"); if (JpiSts2 & 0x40) strcat (PcbFlags, " SSLOGENAB"); if (JpiSts2 & 0x80) strcat (PcbFlags, " SSLOGPERM"); if (JpiSts2 & 0x100) strcat (PcbFlags, " BRKRUNLOAD"); if (JpiSts2 & 0x8000) strcat (PcbFlags, " CLASSSCHEDPERM"); if (JpiSts2 & 0x10000) strcat (PcbFlags, " TERMNOTIFY"); if (JpiSts2 & 0x20000) strcat (PcbFlags, " BYTLMLOAN"); if (JpiSts2 & 0x40000) strcat (PcbFlags, " DISPREEMPT"); if (JpiSts2 & 0x80000) strcat (PcbFlags, " NOUNSHELV"); if (JpiSts2 & 0x100000) strcat (PcbFlags, " SHELVRES"); if (JpiSts2 & 0x200000) strcat (PcbFlags, " CLASSCHED"); if (JpiSts2 & 0x400000) strcat (PcbFlags, " CLASSUPP"); if (JpiSts2 & 0x800000) strcat (PcbFlags, " INTBSSTATE"); if (JpiSts2 & 0x1000000) strcat (PcbFlags, " WINDFALL"); if (JpiSts2 & 0x2000000) strcat (PcbFlags, " NOTIFY"); if (JpiSts2 & 0x3C000000) strcat (PcbFlags, " SINTHREAD"); if (JpiSts2 & 0x40000000) strcat (PcbFlags, " RWAST"); if (JpiSts2 & 0x80000000) strcat (PcbFlags, " SOFTSINTHR"); /* terminate at first trailing space */ JpiUserName[sizeof(JpiUserName)-1] = '\0'; for (sptr = JpiUserName; *sptr && *sptr != ' '; sptr++); *sptr = '\0'; /* terminate and trim trailing spaces */ JpiPrcNam[sizeof(JpiPrcNam)-1] = '\0'; for (sptr = JpiPrcNam; *sptr; sptr++); *sptr-- = '\0'; while (sptr > JpiPrcNam && *sptr == ' ') sptr--; *++sptr = '\0'; JpiImagName[JpiImagNameLen] = '\0'; if (!JpiImagName[0]) strcpy (JpiImagName, "(DCL)"); JpiTtAccPorNam[JpiTtAccPorNamLen] = '\0'; if (!JpiTerminal[0]) strcpy (JpiTerminal, "(none)"); sys$fao (&UicFaoDsc, 0, &UicStringDsc, JpiUic); status = lib$sub_times (&CurrentTime64, &JpiLoginTim64, &ConTime64); if (VMSnok (status)) EXIT_FI_LI (status); /* make the 10mS ticks delta times (MungeTime() will handle the zeros) */ CpuTime64 = JpiCpuTim * -100000; /* make the 10mS ticks into delta times */ CpuKrnlTime64 = (int64)JpiKrnlTim * -100000; CpuExecTime64 = (int64)JpiExecTim * -100000; CpuSuprTime64 = (int64)JpiSuprTim * -100000; CpuUserTime64 = (int64)JpiUserTim * -100000; if (JpiCpuId >= 0) sprintf (CpuHash, " CPU#%d", JpiCpuId); else CpuHash[0] = '\0'; if (JpiCpuLim) sprintf (CpuStats, "%s / %s %u%%", MungeTime(&CpuLimTime64), MungeTime(&CpuTime64), (JpiCpuLim*100)/(JpiCpuTim)); else sprintf (CpuStats, "%s", MungeTime(&CpuTime64)); /*******************/ /* build JSON data */ /*******************/ zptr = (sptr = clptr->PerProcBuffer) + sizeof(clptr->PerProcBuffer); sptr += snprintf (sptr, zptr-sptr, "{\"$data\":\"per-process\""); sptr += snprintf (sptr, zptr-sptr, ",\"PID\":\"%08.08X\"", JpiPid); sptr += snprintf (sptr, zptr-sptr, ",\"owner\":\"%08.08X\"", JpiOwner); sptr += snprintf (sptr, zptr-sptr, ",\"creator\":\"%08.08X\"", JpiCreator); sptr += snprintf (sptr, zptr-sptr, ",\"userName\":\"%s\"", JpiUserName); sptr += snprintf (sptr, zptr-sptr, ",\"UIC\":\"%s\"", UicString); sptr += snprintf (sptr, zptr-sptr, ",\"prcNam\":\"%s\"", MungeString(JpiPrcNam)); sptr += snprintf (sptr, zptr-sptr, ",\"imagName\":\"%s\"", MungeString(JpiImagName)); sptr += snprintf (sptr, zptr-sptr, ",\"priority\":\"%d/%d\"", JpiPrib, JpiPri); /** HP C V7.3-009 on OpenVMS Alpha V8.3 PTRMISMATCH? **/ sptr += snprintf (sptr, zptr-sptr, ",\"login\":\"%s\"", MungeTime(&JpiLoginTim64)); sptr += snprintf (sptr, zptr-sptr, ",\"connect\":\"%s\"", MungeTime(&ConTime64)); sptr += snprintf (sptr, zptr-sptr, ",\"state\":\"%s%s %s %s:%s%s\"", state, CpuHash, AstFlags, type, mode, PcbFlags); sptr += snprintf (sptr, zptr-sptr, ",\"terminal\":\"%s%s%s%s\"", JpiTerminal, JpiTtAccPorNam[0] ? " (" : "", JpiTtAccPorNam, JpiTtAccPorNam[0] ? ")" : ""); sptr += snprintf (sptr, zptr-sptr, ",\"CPU\":\"%s U·%s S·%s E·%s \ K·%s\"", CpuStats, MungeTime(&CpuUserTime64), MungeTime(&CpuSuprTime64), MungeTime(&CpuExecTime64), MungeTime(&CpuKrnlTime64)); sptr += snprintf (sptr, zptr-sptr, ",\"WSET\":\"%s%.5g / %.5g %.5g MB\"", ALERT_THRESHOLD(JpiWsExtent, JpiWsExtent-JpiPpgCnt+JpiGpgCnt), (float)JpiWsExtent * 512.0 / 1048576.0, /* current usage is private plus global pages */ ((float)JpiPpgCnt + (float)JpiGpgCnt) * 512.0 / 1048576.0, (float)JpiWsPeak * 512.0 / 1048576.0); sptr += snprintf (sptr, zptr-sptr, ",\"PGS\":\"%.3g + %.3g = %.3g MB\"", /* private and global pages */ (float)JpiPpgCnt * 512.0 / 1048576.0, (float)JpiGpgCnt * 512.0 / 1048576.0, ((float)JpiPpgCnt + (float)JpiGpgCnt) * 512.0 / 1048576.0); sptr += snprintf (sptr, zptr-sptr, ",\"PGFL\":\"%s%.5g / %.5g MB %u%%\"", /* page file usage */ ALERT_THRESHOLD(JpiPgFlQuota,JpiPagFilCnt), (float)JpiPgFlQuota * 512.0 / 1048576.0, (float)JpiPagFilCnt * 512.0 / 1048576.0, (int)(((float)JpiPagFilCnt*100.0)/(float)JpiPgFlQuota)); sptr += snprintf (sptr, zptr-sptr, ",\"AST\":\"%s%u / %u %u%%\"", ALERT_THRESHOLD(JpiAstLm,JpiAstCnt), JpiAstLm, JpiAstCnt, (JpiAstCnt*100)/(JpiAstLm)); sptr += snprintf (sptr, zptr-sptr, ",\"BIO\":\"%s%u / %u %u%%\"", ALERT_THRESHOLD(JpiBioLm,JpiBioCnt), JpiBioLm, JpiBioCnt, (JpiBioCnt*100)/(JpiBioLm)); sptr += snprintf (sptr, zptr-sptr, ",\"DIO\":\"%s%u / %u %u%%\"", ALERT_THRESHOLD(JpiDioLm,JpiDioCnt), JpiDioLm, JpiDioCnt, (JpiDioCnt*100)/(JpiDioLm)); sptr += snprintf (sptr, zptr-sptr, ",\"ENQ\":\"%s%u / %u %u%%\"", ALERT_THRESHOLD(JpiEnqLm,JpiEnqCnt), JpiEnqLm, JpiEnqCnt, (JpiEnqCnt*100)/(JpiEnqLm)); sptr += snprintf (sptr, zptr-sptr, ",\"VIRT\":\"%.6g / %.6g MB\"", /* possible is minimum physical plus maxium page file */ ((float)JpiWsQuota + (float)JpiPgFlQuota) * 512.0 / 1048576.0, (float)JpiVirtPeak64 * 512.0 / 1048576.0); sptr += snprintf (sptr, zptr-sptr, ",\"FIL\":\"%s%u / %u %u%%\"", ALERT_THRESHOLD(JpiFilLm,JpiFilCnt), JpiFilLm, JpiFilCnt, (JpiFilCnt*100)/(JpiFilLm)); sptr += snprintf (sptr, zptr-sptr, ",\"BYT\":\"%s%u / %u %u%%\"", ALERT_THRESHOLD(JpiBytLm/1000,JpiBytCnt/1000), JpiBytLm, JpiBytCnt, (int)(((float)JpiBytCnt*100.0)/(float)JpiBytLm)); sptr += snprintf (sptr, zptr-sptr, ",\"PRC\":\"%s%u / %u %u%%\"", ALERT_THRESHOLD(JpiPrcLm,JpiPrcLm-JpiPrcCnt), JpiPrcLm, JpiPrcCnt, ((JpiPrcLm-JpiPrcCnt)*100)/JpiPrcLm); sptr += snprintf (sptr, zptr-sptr, ",\"TQ\":\"%s%u / %u %u%%\"", ALERT_THRESHOLD(JpiTqLm,JpiTqCnt), JpiTqLm, JpiTqCnt, (JpiTqCnt*100)/(JpiTqLm)); sptr += snprintf (sptr, zptr-sptr, ",\"KTH\":\"%u / %u\"", /* careful of MULTITHREAD div zero with percentage! */ JpiMultiThread, JpiKtCount); sptr += snprintf (sptr, zptr-sptr, ",\"pageFlts\":%u", JpiPageFlts); sptr += snprintf (sptr, zptr-sptr, ",\"bufIO\":%u", JpiBufIo); sptr += snprintf (sptr, zptr-sptr, ",\"dirIO\":%u", JpiDirIo); sptr += snprintf (sptr, zptr-sptr, ",\"volumes\":%u", JpiVolumes); /****************/ /* graph values */ /****************/ CpuTime = JpiUserTim + JpiSuprTim + JpiExecTim + JpiKrnlTim; CpuPercent = CpuTime - clptr->PrevCpuTime; clptr->PrevCpuTime = CpuTime; CpuSekTime = JpiSuprTim + JpiExecTim + JpiKrnlTim; CpuSekPercent = CpuSekTime - clptr->PrevCpuSekTime; clptr->PrevCpuSekTime = CpuSekTime; /* only after we've collected at least one previously */ if (clptr->PerProcDatum++) { CpuPercent = (uint)((float)CpuPercent / DeltaSeconds); if (CpuPercent > 100 * SystemData.ActiveCpuCount) CpuPercent = 100 * SystemData.ActiveCpuCount; CpuSekPercent = (uint)((float)CpuSekPercent / DeltaSeconds); if (CpuSekPercent > 100 * SystemData.ActiveCpuCount) CpuSekPercent = 100 * SystemData.ActiveCpuCount; BufIo = (uint)((float)(JpiBufIo - clptr->PrevJpiBufIo) / DeltaSeconds); DirIo = (uint)((float)(JpiDirIo - clptr->PrevJpiDirIo) / DeltaSeconds); PageFlts = (uint)((float)(JpiPageFlts - clptr->PrevJpiPageFlts) / DeltaSeconds); sptr += snprintf (sptr, zptr-sptr, ",\"graph\":{"); sptr += snprintf (sptr, zptr-sptr, "\"CPU\":%u", CpuPercent); sptr += snprintf (sptr, zptr-sptr, ",\"SEK\":%u", CpuSekPercent); sptr += snprintf (sptr, zptr-sptr, ",\"bufIO\":%u", BufIo); sptr += snprintf (sptr, zptr-sptr, ",\"dirIO\":%u", DirIo); sptr += snprintf (sptr, zptr-sptr, ",\"pageFlts\":%u", PageFlts); /* current usage is private plus global pages */ sptr += snprintf (sptr, zptr-sptr, ",\"gpgCnt\":%u", JpiGpgCnt * 512); sptr += snprintf (sptr, zptr-sptr, ",\"pagFilCnt\":%u", JpiPagFilCnt * 512); sptr += snprintf (sptr, zptr-sptr, ",\"pgFlQuota\":%u", JpiPgFlQuota * 512); sptr += snprintf (sptr, zptr-sptr, ",\"ppgCnt\":%u", JpiPpgCnt * 512); sptr += snprintf (sptr, zptr-sptr, ",\"wsExtent\":%u}", JpiWsExtent * 512); } clptr->PrevJpiBufIo = JpiBufIo; clptr->PrevJpiDirIo = JpiDirIo; clptr->PrevJpiPageFlts = JpiPageFlts; sptr += snprintf (sptr, zptr-sptr, "}"); if (sptr >= zptr) EXIT_FI_LI (SS$_BUGCHECK); clptr->PerProcBufferLength = sptr - clptr->PerProcBuffer; if (dbug && DEBUG) fprintf (stdout, "\n%d\n%s\n", clptr->PerProcBufferLength, clptr->PerProcBuffer); return (status); } /*****************************************************************************/ /* General access and then access to the top-process and then again per-process and additionally OPCOM displays is controlled by four strings read from a multi-valued logical name. The control strings sets the client access booleans. For WebSocket must be called while CGI variables are valid. */ void AccessControl (CLIENT_DATA *clptr) { static char LogValue[ACCESS_SIZE][255+1]; int status, AddrHit, AddrMatch, NameHit, NameMatch; uint idx, IPaddr, IPnet, mask; char ch; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "AccessControl() |%s|%s|\n", CgiRemoteUserPtr, CgiRemoteAddrPtr); /* initialise and attempt to read up to four values */ for (idx = 0; idx < ACCESS_SIZE; idx++) LogValue[idx][0] = '\0'; for (idx = 0; idx < ACCESS_SIZE; idx++) { cptr = SysTrnLnm (LOGNAM_ACCESS, NULL, idx); if (!cptr) break; strcpy (LogValue[idx], cptr); /* note that OPCOM is access controlled */ if (idx == ACCESS_OPCOM) clptr->OpcomAccess = TRUE; } if (dbug) fprintf (stdout, "|%s|%s|%s|%s|\n", LogValue[0], LogValue[1], LogValue[2], LogValue[3]); if (!LogValue[0][0]) { /* logical name does not exist, therefore general access allowed */ clptr->AccessAllowed[ACCESS_GENERAL] = TRUE; clptr->AccessAllowed[ACCESS_TOP_PROCESS] = clptr->AccessAllowed[ACCESS_PER_PROCESS] = clptr->AccessAllowed[ACCESS_OPCOM] = FALSE; return; } /* first general access, then top-process access, then per-process access, then OPCOM access */ for (idx = 0; idx < ACCESS_SIZE; idx++) { AddrHit = AddrMatch = NameHit = NameMatch = FALSE; cptr = LogValue[idx]; /* may be an empty value */ while (*cptr && *cptr == ' ') cptr++; while (*cptr) { while (*cptr && *cptr != '_' && *cptr != '*' && *cptr != '$' && *cptr != '#') cptr++; if (!*cptr) break; if (*cptr == '_') { /* none allowed access */ AddrHit = AddrMatch = NameHit = NameMatch = FALSE; break; } if (*cptr == '*') { /* all allowed access */ AddrHit = AddrMatch = NameHit = NameMatch = TRUE; break; } if ((*(USHORTPTR)cptr) == '$*') { /* allowed if authenticated */ cptr += 2; NameHit = TRUE; if (CgiRemoteUserPtr[0]) NameMatch = TRUE; continue; } if (*cptr == '$') { /* allowed if matching authentication */ cptr++; NameHit = TRUE; for (zptr = cptr; *zptr && *zptr != ','; zptr++); if (ch = *zptr) *zptr = '\0'; sptr = CgiRemoteUserPtr; while (*cptr && *sptr && toupper(*cptr) == toupper(*sptr)) { cptr++; sptr++; } if (!*cptr && !*sptr) NameMatch = TRUE; *zptr = ch; continue; } if (*cptr == '#') { /* access allowed if matching client IP(v4) address */ AddrHit = TRUE; IPaddr = inet_addr (CgiRemoteAddrPtr); if (IPaddr == 0xffffffff) return; cptr++; for (zptr = cptr; *zptr && *zptr != ','; zptr++); if (ch = *zptr) *zptr = '\0'; for (sptr = cptr; *sptr && *sptr != '/'; sptr++); if (*sptr) { /* subnet mask */ *sptr = '\0'; /* inet_aton() is not available on VMS V7.2 */ IPnet = inet_addr (cptr); *sptr = '/'; *zptr = ch; if (IPnet == 0xffffffff) return; mask = atoi(sptr+1); mask = 0xffffffff >> (32 - mask); if (IPaddr && IPnet && (IPnet == (IPaddr & mask))) { /* both addr are valid and masked client addr matches */ AddrMatch = TRUE; continue; } } if (!strcmp (cptr, CgiRemoteAddrPtr)) { /* matched address */ *zptr = ch; AddrMatch = TRUE; continue; } if (*(cptr = zptr) = ch) cptr++; continue; } } if (AddrHit && !NameHit) clptr->AccessAllowed[idx] = AddrMatch; else if (!AddrHit && NameHit) clptr->AccessAllowed[idx] = NameMatch; else if (AddrHit && NameHit) clptr->AccessAllowed[idx] = AddrMatch && NameMatch; } if (dbug) fprintf (stdout, "|%d|%d|%d|%d|\n", clptr->AccessAllowed[0], clptr->AccessAllowed[1], clptr->AccessAllowed[2], clptr->AccessAllowed[3]); } /*****************************************************************************/ /* HTML-escape any forbidden characters in a null-terminated string. Static buffer so can only be called the once per print call. Up to 255 character resultant string length. */ char* MungeString (char *cptr) { static char StringBuffer [255+6]; char *sptr, *zptr; /*********/ /* begin */ /*********/ zptr = (sptr = StringBuffer) + sizeof(StringBuffer)-6; while (*cptr && sptr < zptr) { switch (*cptr) { case '<' : *(ULONGPTR)sptr = '<'; sptr += 4; break; case '>' : *(ULONGPTR)sptr = '>'; sptr += 4; break; case '&' : *(ULONGPTR)sptr = '&'; sptr += 4; *sptr++ = ';'; break; default : *sptr++ = *cptr; } cptr++; } *sptr = '\0'; return (StringBuffer); } /*****************************************************************************/ /* HTML-escape any forbidden characters in the process name. Process name is a maximum fifteen character counted string. Static buffer so can only be called the once per print call. */ char* MungeProcName (char *ProcName) { /* maximum fifteen ampersand's worth */ static char NameBuffer [15*5+1]; int cnt, filcnt; char *cptr, *sptr; /*********/ /* begin */ /*********/ sptr = NameBuffer; cnt = *ProcName; filcnt = 15 - cnt; cptr = ProcName + 1; while (cnt--) { switch (*cptr) { case '<' : *(ULONGPTR)sptr = '<'; sptr += 4; break; case '>' : *(ULONGPTR)sptr = '>'; sptr += 4; break; case '&' : *(ULONGPTR)sptr = '&'; sptr += 4; *sptr++ = ';'; break; default : *sptr++ = *cptr; } cptr++; } while (filcnt--) *sptr++ = ' '; *sptr = '\0'; return (NameBuffer); } /*****************************************************************************/ /* Make a delta binary time into an null-terminated ASCII string, absorbing non-significant leading characters. Up to four concurrent times are stored in static storage. */ char* MungeTime (int64 *t64ptr) { #undef BUFFER_MAX #define BUFFER_MAX 4 static int BufferIdx; static char Buffer [BUFFER_MAX][32]; static $DESCRIPTOR (BufferDsc, Buffer[0]); static $DESCRIPTOR (TimeFaoDsc, "!%D\0"); char *cptr, *sptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "MungeTime()\n"); BufferDsc.dsc$a_pointer = Buffer[BufferIdx++ % BUFFER_MAX]; if (*t64ptr == 0) strcpy (BufferDsc.dsc$a_pointer, "0.00"); else { sys$fao (&TimeFaoDsc, 0, &BufferDsc, t64ptr); for (cptr = sptr = BufferDsc.dsc$a_pointer; (*cptr == ' ' || *cptr == '0' || *cptr == ':') && cptr - sptr <= 11; cptr++); while (*cptr && *cptr != '.') *sptr++ = *cptr++; *sptr = '\0'; } return (BufferDsc.dsc$a_pointer); } /*****************************************************************************/ /* Control debug characteristics to allow use in a CGIplus environment. Set to record if debug enabled or binary if not. */ void Setdbug (int dbugOn) { /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "Setdbug()\n"); if (!dbugOn) { dbug = 0; return; } if (dbug) return; if (getenv ("MONDESI$DBUG")) dbug = 1; if (dbug) fprintf (stdout, "Status: 200\n\ Content-Type: text/plain\n\ Script-Control: X-content-encoding-gzip=0\n\ \r\n"); if (dbug || Clidbug || HttpdOSU) stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=rec"); else stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin"); } /*****************************************************************************/ /* Return true or false depending on whether the server is an equal or later release to the supplied version string. */ int MinimumWASD (char *vstring) { int major[2], minor[2], tweak[2]; char *cptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "MinimumWASD() %s\n", vstring); major[0] = minor[0] = tweak[0] = major[1] = minor[1] = tweak[1] = 0; if (sscanf (vstring, "%d.%d.%d", &major[0], &minor[0], &tweak[0]) < 3) return (0); cptr = WsLibCgiVar("SERVER_SOFTWARE"); if (dbug) fprintf (stdout, "%s\n", cptr); if (strstr (cptr, "Apache")) return (0); if (strstr (cptr, "OSU")) return (0); /* if not obvious just bail out */ if (!(cptr = strstr (cptr, "WASD/"))) return (0); /* if doesn't make sense just assume the op knows what they're doing */ if (sscanf (cptr+5, "%d.%d.%d", &major[1], &minor[1], &tweak[1]) < 3) return (1); if (major[1] > major[0]) return (1); if (major[1] == major[0]) { if (minor[1] > minor[0]) return (1); if (minor[1] == minor[0] && tweak[1] >= tweak[0]) return (1); } return (0); } /*****************************************************************************/ /* If WASD and the version >= 10.1 then WebSocket is potentially available. The multi-valued logical name MONDESI_WEBSOCKET allows this to be overridden by defining the first value to be zero, or one. Further, additional values can represent partial strings from the user-agent field. A match returns the complement of the first value. So, 0,"Firefox" would disable WebSocket for all but user-agents containing the string "Firefox". If the leading character is '!' then negates the match, so 0,"!Windows NT","Firefox" would disable WebSocket for all but user-agents containing the string "Firefox" unless it also contained the string "Windows NT". Could be used with the first value defined as 1, in which case it would function to exclude usage by specified WebSocket agent strings. With CGIplus-enabled WebSocket, as it is directly accessing a CGI variable, can only be used before the request setup has completed and the asynchronous WebSocket I/O has begun. Bit of a kludge but allows the flexibility currently required. */ int WebSockAvailable () { int cnt, idx, status, WebSocketOk = FALSE; char *cptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "WebSockAvailable()\n"); if (MinimumWASD ("10.1.0")) WebSocketOk = TRUE; for (idx = 0; idx <= 127; idx++) { if (!(cptr = SysTrnLnm (LOGNAM_WEBSOCKET, NULL, idx))) break; if (cptr[0] == '!') { if (strstr (CgiHttpUserAgentPtr, cptr+1)) { WebSocketOk = !WebSocketOk; break; } } else if (strstr (CgiHttpUserAgentPtr, cptr)) break; } if (dbug) fprintf (stdout, "WebSockOk: %d\n", WebSocketOk); return (WebSocketOk); } /*****************************************************************************/ /* Provide a callout to WASD server. */ void ScriptCallout (char *fmt, ...) { int retval; char *cptr; va_list ap; if (!HttpdWASD) return; /* must be received as records */ fflush (stdout); fprintf (stdout, "%s\n", SysTrnLnm("CGIPLUSESC", NULL, 0)); fflush (stdout); va_start (ap, fmt); retval = vfprintf (stdout, fmt, ap); va_end (ap); fflush (stdout); fprintf (stdout, "%s\n", SysTrnLnm("CGIPLUSEOT", NULL, 0)); fflush (stdout); } /*****************************************************************************/ /* */ int HavePriv (ulong *PrivMask) { static ulong JpiImagPriv [2], JpiProcPriv [2]; static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } JpiItemList[] = { { sizeof(JpiImagPriv), JPI$_IMAGPRIV, &JpiImagPriv, 0 }, { sizeof(JpiProcPriv), JPI$_PROCPRIV, &JpiProcPriv, 0 }, {0,0,0,0} }; int status; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "HavePriv() %08.08X%08.08X\n", PrivMask[0], PrivMask[1]); status = sys$getjpiw (EfnWait, 0, 0, &JpiItemList, 0, 0, 0); if (VMSnok (status)) EXIT_FI_LI(status); return ((((JpiImagPriv[0] & PrivMask[0]) == PrivMask[0]) && ((JpiImagPriv[1] & PrivMask[1]) == PrivMask[1])) || (((JpiProcPriv[0] & PrivMask[0]) == PrivMask[0]) && ((JpiProcPriv[1] & PrivMask[1]) == PrivMask[1]))); } /*****************************************************************************/ /* Translate a logical name using LNM$FILE_DEV. Returns a pointer to the value string, or NULL if the name does not exist. If 'LogValue' is supplied the logical name is translated into that (assumed to be large enough), otherwise it's translated into an internal static buffer. 'IndexValue' should be zero for a 'flat' logical name, or 0..127 for interative translations. */ char* SysTrnLnm ( char *LogName, char *LogValue, int IndexValue ) { static ushort ValueLength; static ulong LnmAttributes, LnmIndex; static char StaticLogValue [256]; static $DESCRIPTOR (LogNameDsc, ""); static $DESCRIPTOR (LnmTableDsc, "LNM$FILE_DEV"); static struct { short int buf_len; short int item; void *buf_addr; ushort *ret_len; } LnmItems [] = { { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 }, { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 }, { 255, LNM$_STRING, 0, &ValueLength }, { 0,0,0,0 } }; int status; char *cptr; /*********/ /* begin */ /*********/ LnmIndex = IndexValue; LogNameDsc.dsc$a_pointer = LogName; LogNameDsc.dsc$w_length = strlen(LogName); if (LogValue) cptr = LnmItems[2].buf_addr = LogValue; else cptr = LnmItems[2].buf_addr = StaticLogValue; status = sys$trnlnm (0, &LnmTableDsc, &LogNameDsc, 0, &LnmItems); if (!(status & 1) || !(LnmAttributes & LNM$M_EXISTS)) { if (LogValue) LogValue[0] = '\0'; return (NULL); } cptr[ValueLength] = '\0'; return (cptr); } /****************************************************************************/