Copyright 1991-2016 by Kevin G. Barkes All rights reserved. This article may be duplicated or redistributed provided no alterations of any kind are made to this file. This edition of DCL Dialogue is sponsored by Networking Dynamics, developers and marketers of productivity software for OpenVMS systems. Contact our website www.networkingdynamics.com to download free demos of our software and see how you will save time, money and raise productivity! Be sure to mention DCL Dialogue! DCL DIALOGUE Originally published March 1991 Noir For The Faint-Hearted By Kevin G. Barkes Many readers responded to "DCL Noir" (September, 1990), which featured an interactive process killing procedure. However, it appears a lot of you are, well, wimps. You like the idea of blasting errant processes into oblivion, but the idea of manually pulling the trigger leaves you squeamish. For those of you who prefer to have your VAX do the dirty work (and take the blame), consider Idle Basher (Figure), a simple DCL command file that tracks down and zaps idle processes. Older DCL-based idle process killers (IPKs) were notorious for their complexity and inefficiency. They did lots of temporary file creations and deletions, i/o, and large numbers of nasty symbol assignments. The resultant spaghetti code gave system managers nightmares. But Version 5 of VMS features a liberating tool: the F$CONTEXT lexical function. In ancient times (pre-1989), IPKs worked by making repeated calls to the F$PID() lexical, which sequentially returned the process identification number of each process on the system. Complex evaluation routines were used to determine if the processes were eligible for deletion. F$CONTEXT makes things much easier. It's really a sophisticated filter for F$PID, passing a select group of predetermined process identification numbers. To obtain additional information on F$CONTEXT, refer to the DCL Dictionary. But for now, let's dive into IB.COM and see how it works. SET NOON prevents DCL from bombing out of the command file should it encounter an error. The lines immediately following the TOP: label are used to initialize the procedure's symbols. Local symbols are deleted to eliminate conflicts on future executions. The "P" symbol is used so it isn't necessary to type out the string "PROCESS" in the first argument of every F$CONTEXT call. PCOUNT, which is used as the index for a simulated DCL symbol "array", is also reset. The SENSITIVITY symbol sets the threshold for process deletion. In this case, an eligible process must use at least two seconds of cpu time per IB scan to prevent it from being whacked. The value is specified in hundredths of a second. SCANRATE determines how frequently the procedure checks for idle processes; the value is specified in minutes. The WAIT command following the SCANRATE assignment prevents excessive looping if no eligible processes are discovered during execution. The next few lines use F$CONTEXT to determine which processes are considered for later termination. The TEMP symbol is just a throw-away used to execute the lexical. We don't want to kill batch, network or other processes, so we limit the selections passed on to F$PID by having F$CONTEXT first select only interactive processes. The first argument, P, is the symbol we defined to the string "PROCESS". (Currently, only process contexts are supported.) TARG is the name of the context symbol which will be used as the argument in the F$PID lexical. "MODE" is the selection item, "INTERACTIVE" is the selection value, and "EQL" is the value qualifier. Put simply, the first F$CONTEXT call tells the lexical to look at process modes and select only those whose mode equals interactive. We further weed out eligible processes by only selecting users who are logged into terminals on "TX" type ports. Note the use of the "*" wild card. We don't want to delete system-owned processes, so we use F$CONTEXT to eliminate all interactive processes on TX ports which have a SYSTEM UIC. Finally, we don't want to kill off processes that have subprocesses. So our final call to F$CONTEXT specifies that only processes with no subprocesses are selected. This example merely scratches the surface of potential selection methodologies. F$CONTEXT supports over 20 items, permitting such choices as wildcard selection of VAXcluster nodes, username specifications, and process names, to list but three. But on to the real work. The first loop calls F$PID to return an eligible process identification number. If F$PID's gone through the complete list of eligible processes, it jumps to the end of the loop. Otherwise, it uses F$GETJPI to verify the selected process is running DCL, which is returned as the null string by "IMAGNAME". Checks for other images can also be specified here. Next, the "array counter" PCOUNT is incremented, and the process id and its current cpu utilization is stored in the symbol PROCESS'PCOUNT'. The procedure returns to LOOP and checks each eligible process supplied by F$PID. At ENDLOOP, F$TYPE checks the symbol PROCESS1 to see if it exists. If no eligible processes were located by F$PID, the symbol PROCESS1 will not have been constructed. IB.COM then waits for the amount of time specified in SCANRATE before continuing. Next, we store in MAXPCOUNT the number of processes to be checked. We reset PCOUNT to 0 immediately prior to entering KILL_LOOP:, where process deletion actually occurs. PCOUNT is incremented; if it's greater than MAXPCOUNT, the procedure resets itself, since it's already checked all the PROCESS symbols. Otherwise, it extracts the process id and the original cpu utilization figures for comparison. If you run IB.COM in interactive mode from a non-system account, the next few lines prevent your process from committing suicide by eliminating it from the selection process. Finally, IB looks to see if the process has used sufficient cpu time (the SENSITIVITY symbol) and confirms the process is still in DCL before deleting it. This is a "bare-bones" procedure. Niceties which could be added include a notification loop to let the prospective pigeon know he's about to be deleted (although many users abuse the courtesy by entering a few DIRectory commands to eat up cpu time); a more expansive or restrictive selection set; or checks for idle subprocesses. A few caveats are in order. IB must be run from a sufficiently privileged account in order to see all the PIDs on the system. Remember to add trailing spaces when using the USERNAME selection value to make the string 12 characters long. Be certain your version is completely debugged before submitting it as a batch job or detached process (see "RoboVAX", December 1990, for detached process creation information). Happy hunting. ************************************************* Kevin G. Barkes is an independent consultant and publisher of the KGB Report newsletter. He operates the www.kgbreport.com website, lurks on comp.os.vms, and can be reached at kgbarkes@gmail.com ************************** FIGURE **************************** $! IB.COM $ SET NOON $ TOP: $ DELETE/SYMBOL/LOCAL/ALL !Clear all symbols $! Define symbols we'll be using: $ TARG = "" ! Holds PID of target process $ P = "PROCESS" ! Shorthand for lexical argument $ PCOUNT = 0! "Array" index $ SENSITIVITY = 200! Must use 2 seconds of cpu $ SCANRATE = 15! Every 15 minutes $ WAIT 00:'SCANRATE':00 $!Our selection criteria: $!Pick all interactive processes: $ TEMP = F$CONTEXT(P,TARG,"MODE","INTERACTIVE","EQL") $! On TXcn terminals: $ TEMP = F$CONTEXT(P,TARG,"TERMINAL","TX*","EQL") $! Which are not system processes $ TEMP = F$CONTEXT(P,TARG,"UIC","[SYSTEM]","NEQ") $! And which have no subprocesses $ TEMP = F$CONTEXT(P,TARG,"JOBPRCCNT","0","EQL") $! Info gathering loop: $ LOOP: $ PID = F$PID(TARG) $ IF PID .EQS. "" $ THEN $ GOTO ENDLOOP $ ELSE $ IF F$GETJPI(PID,"IMAGNAME") .NES. "" THEN GOTO LOOP $ PCOUNT = PCOUNT + 1 $ PROCESS'PCOUNT' = "''PID',''F$GETJPI(PID,"CPUTIM")'" $ ENDIF $ GOTO LOOP $ ENDLOOP: $! If no processes selected, restart: $ IF F$TYPE(PROCESS1) .EQS. "" THEN GOTO TOP $! Wait for scanrate time: $ WAIT 00:00:'scanrate' $ MAXPCOUNT = PCOUNT $ PCOUNT = 0 $! Process deletion loop: $ KILL_LOOP: $ PCOUNT = PCOUNT + 1 $ IF PCOUNT .GT. MAXPCOUNT THEN GOTO TOP $ PROCESS = F$ELEMENT(0,",",PROCESS'PCOUNT') $ OLDTIME = F$ELEMENT(1,",",PROCESS'PCOUNT') $! Don't commit suicide: $ IF PROCESS .EQS. F$GETJPI("","PID") THEN - GOTO KILL_LOOP $! Don't whack non-DCL processes $ IF (F$GETJPI(PROCESS,"CPUTIM") - OLDTIME) - .LT. SENSITIVITY .AND. - (F$GETJPI(PROCESS,"IMAGNAME") .EQS. "") - THEN STOP/ID='PROCESS' $ GOTO KILL_LOOP $ EXIT