From:	CRDGW2::CRDGW2::MRGATE::"SMTP::CRVAX.SRI.COM::RELAY-INFO-VAX"  2-AUG-1990 07:55:54.08
To:	MRGATE::"ARISIA::EVERHART"
CC:	
Subj:	Re: LOAD_AVERAGE

Received:  by crdgw1.ge.com (5.57/GE 1.70)
	 id AA07958; Tue, 31 Jul 90 19:43:21 EDT
Received: From TGV.COM by CRVAX.SRI.COM with TCP; Tue, 31 JUL 90 13:10:20 PDT
Date:     Tue, 31 Jul 90 11:41:51 PDT
From: adelman@TGV.COM (Kenneth Adelman)
Reply-To: Adelman@TGV.COM (Kenneth Adelman)
Message-Id: <900731113036.2a401734@TGV.COM>
Subject:  Re: LOAD_AVERAGE
To: wilton%hg.uleth.ca@TGV.COM
Cc: info-vax@sri.com

>  In the VMS 5.2 Release notes, section 3.24.2 there is a description of
> the LAT dynamic service rating algorithm.  On of the factors in the equation
> is a number called LOAD_AVERAGE which is described as "a moving average of
> the number of computable processes waiting in the VMS scheduler queues."
> This sounds like it could be a very useful number to be able to display.  Does
> anybody know if this is available through a system call or something similar?
> I see no mention of LOAD_AVERAGE in the master index to the doc set.	When we
> switched from TOPS-20 to VMS a few years back, we really missed the load
> average number on the CTRL-T line.  So if there is some way to get at this
> LOAD_AVERAGE, I would like to hear about it.	Thanks for any help.

    Here is a VMS driver you can load which will keep track of the
load average in a way you can retrieve it. The algorithm used by it is
not necessarily the same as LAT, but closer to TOPS. To read the
load average, $ASSIGN or open a channel to the device _LAV0: and
read 36 bytes from it. The first 12 bytes are three "F"-type floats
which are the 1, 5, and 15 minute load averages respectively. The
next 12 bytes are the 1, 5, and 15 minute average blocking priority (if
your process is below the average blocking priority you aren't going to
get much CPU, if above it you will get a lot). The last three are the
1, 5, and 15 minute averages of the longest I/O queue length on any disk,
a decent indication of how backed up your I/O system is.

    This copy of the driver is the same as the one I posted in late
July 1988, so if you grabbed a copy then, there is no need to bother
unpacking it now.  Note that for VMS V5 you need to add a line VMS_V5=1
to the top to turn on the VMS V5 conditionals.


							Kenneth Adelman
							TGV



...................... Cut between dotted lines and save. .....................
$!.............................................................................
$! VAX/VMS archive file created by VMS_SHARE V06.00 26-May-1988.
$!
$! VMS_SHARE was written by James Gray (Gray:OSBUSouth@Xerox.COM) from
$! VMS_SHAR by Michael Bednarek (U3369429@ucsvc.dn.mu.oz.au).
$!
$! To unpack, simply save, concatinate all parts into one file and
$! execute (@) that file.
$!
$! This archive was created by user ADELMAN
$! on 31-JUL-1990 11:33:02.37.
$!
$! It contains the following 1 file:
$!        LAVDRIVER.MAR
$!
$!==============================================================================
$ SET SYMBOL/SCOPE=( NOLOCAL, NOGLOBAL )
$ VERSION = F$GETSYI( "VERSION" )
$ IF VERSION .GES "V4.4" THEN GOTO VERSION_OK
$ WRITE SYS$OUTPUT "You are running VMS ''VERSION'; ", -
    "VMS_SHARE V06.00 26-May-1988 requires VMS V4.4 or higher."
$ EXIT 44 
$VERSION_OK:
$ GOTO START
$
$UNPACK_FILE:
$ WRITE SYS$OUTPUT "Creating ''FILE_IS'"
$ DEFINE/USER_MODE SYS$OUTPUT NL:
$ EDIT/TPU/COMMAND=SYS$INPUT/NODISPLAY/OUTPUT='FILE_IS'/NOSECTION -
    VMS_SHARE_DUMMY.DUMMY
b_part := CREATE_BUFFER( "{Part}", GET_INFO( COMMAND_LINE, "file_name" ) )
; s_file_spec := GET_INFO( COMMAND_LINE, "output_file" ); SET( OUTPUT_FILE
, b_part, s_file_spec ); b_errors := CREATE_BUFFER( "{Errors}" ); i_errors := 0
; pat_beg_1 := ANCHOR & "-+-+-+ Beginning"; pat_beg_2 := LINE_BEGIN 
& "+-+-+-+ Beginning"; pat_end := ANCHOR & "+-+-+-+-+ End"; POSITION
( BEGINNING_OF( b_part ) ); i_append_line := 0; LOOP EXITIF MARK( NONE 
) = END_OF( b_part ); s_x := ERASE_CHARACTER( 1 ); IF s_x = "+" THEN r_skip 
:= SEARCH( pat_beg_1, FORWARD, EXACT ); IF r_skip <> 0 THEN s_x := ""
; MOVE_HORIZONTAL( -CURRENT_OFFSET ); ERASE_LINE; ENDIF; ENDIF
; IF s_x = "-" THEN r_skip := SEARCH( pat_end, FORWARD, EXACT ); IF r_skip <
> 0 THEN s_x := ""; MOVE_HORIZONTAL( -CURRENT_OFFSET ); m_skip := MARK( NONE )
; r_skip := SEARCH( pat_beg_2, FORWARD, EXACT ); IF r_skip <> 0 THEN POSITION
( END_OF( r_skip ) ); MOVE_HORIZONTAL( -CURRENT_OFFSET ); MOVE_VERTICAL( 1 )
; MOVE_HORIZONTAL( -1 ); ELSE POSITION( END_OF( b_part ) ); ENDIF; ERASE
( CREATE_RANGE( m_skip, MARK( NONE ), NONE ) ); ENDIF; ENDIF
; IF s_x = "V" THEN s_x := ""; IF i_append_line <> 0 THEN APPEND_LINE
; MOVE_HORIZONTAL( -CURRENT_OFFSET ); ENDIF; i_append_line := 1; MOVE_VERTICAL
( 1 ); ENDIF; IF s_x = "X" THEN s_x := ""; IF i_append_line <
> 0 THEN APPEND_LINE; MOVE_HORIZONTAL( -CURRENT_OFFSET ); ENDIF; i_append_line 
:= 0; MOVE_VERTICAL( 1 ); ENDIF; IF s_x <> "" THEN i_errors := i_errors + 1
; s_text := CURRENT_LINE; POSITION( b_errors ); COPY_TEXT
( "The following line could not be unpacked properly:" ); SPLIT_LINE; COPY_TEXT
( s_x ); COPY_TEXT( s_text ); POSITION( b_part ); MOVE_VERTICAL( 1 ); ENDIF
; ENDLOOP; POSITION( BEGINNING_OF( b_part ) ); LOOP r_x := SEARCH( "`", FORWARD
, EXACT ); EXITIF r_x = 0; POSITION( r_x ); ERASE_CHARACTER( 1 )
; IF CURRENT_CHARACTER = "`" THEN MOVE_HORIZONTAL( 1 ); ELSE COPY_TEXT( ASCII
( INT( ERASE_CHARACTER( 3 ) ) ) ); ENDIF; ENDLOOP; IF i_errors = 0 THEN SET
( NO_WRITE, b_errors, ON ); ELSE POSITION( BEGINNING_OF( b_errors ) )
; COPY_TEXT( FAO( "The following !UL errors were detected while unpacking !AS"
, i_errors, s_file_spec ) ); SPLIT_LINE; SET( OUTPUT_FILE, b_errors
, "SYS$COMMAND" ); ENDIF; EXIT; 
$ DELETE VMS_SHARE_DUMMY.DUMMY;*
$ CHECKSUM 'FILE_IS
$ WRITE SYS$OUTPUT " CHECKSUM ", -
  F$ELEMENT( CHECKSUM_IS .EQ. CHECKSUM$CHECKSUM, ",", "failed!,passed." )
$ RETURN
$
$START:
$ FILE_IS = "LAVDRIVER.MAR"
$ CHECKSUM_IS = 1784354413
$ COPY SYS$INPUT VMS_SHARE_DUMMY.DUMMY
X`009.TITLE`009LAVDRIVER`009VAX/VMS Load Average Pseudo Device
X`009.IDENT`009/Version 4.00/
X`009.SBTTL`009DOCUMENTATION
X;---------------S-R-I-International-Artificial-Intelligence-Center-----------
X;
X;
X; MODULE NAME:
X;
X;`009LAVDRIVER
X;
X; PURPOSE:
X;
X;`009This is a pseudo device which supplies load average information to
X;`009programs wishing to display some indication of system load.
X;
X; KEYWORDS:
X;
X;`009Drivers
X;
X; USAGE:
X;
X;`009Standard system QIO calls.
X;
X;`009The driver is installed via sysgen.  The following is the correct
X;`009`009sysgen command sequence.
X;
X;`009`009SYSGEN>  LOAD LAVDRIVER
X;`009`009SYSGEN>  CONNECT LAV0 /NOADA/DRIVER=LAVDRIVER
X;
X;`009`009Note: LAVDRIVER.EXE should reside in [SYS$LDR]
X;
X;
X; SPECIAL REQUIREMENTS:
X;
X;`009none
X;
X; LANGUAGE:
X;
X;`009VAX-11 MACRO
X;
X; PARAMETERS:
X;
X;`009none
X;
X; SPECIAL TECHNIQUES:
X;
X;`009none
X;
X; AUTHOR:
X;
X;`009David Kashtan
X;
X; DATE CREATED:
X;
X;`009Wed Jul 21 11:25:19 1982
X;
X; MAINTAINED BY:
X;
X;`009David Kashtan
X;
X; UPDATES:
X;
X;`009Modified for VMS V5 SMP  Thu Oct 8 1987       Kenneth Adelman / LBL
X;
X;---------------S-R-I-International-Artificial-Intelligence-Center-----------
X`012
X`009.SBTTL`009DEFINITIONS
X`009.LIBRARY`009/SYS$LIBRARY:LIB.MLB/
X;+ ---
X;
X; DEFINE CONTROL BLOCK OFFSETS & OTHER SYSTEM STUFF BY CALLING 'DEF' MACROS
X;
X;- ---
X.IF`009DF,VMS_V5
X`009$CPUDEF`009`009`009;DEFINE PER-CPU STRUCTURE
X.ENDC
X`009$CRBDEF`009`009`009;DEFINE CHANNEL REQUEST BLOCK
X`009$DCDEF`009`009`009;DEFINE DEVICE TYPES
X`009$DDBDEF`009`009`009;DEFINE DEVICE DATA BLOCK
X`009$DEVDEF`009`009`009;DEFINE DEVICE CONSTANTS
X`009$DPTDEF`009`009`009;DEFINE DRIVER PROLOGUE TABLE
X`009$DYNDEF`009`009`009;DEFINE DYNAMIC STRUCTURE CODES
X`009$FKBDEF`009`009`009;DEFINE FORK BLOCK CODES
X`009$IODEF`009`009`009;DEFINE I/O FUNCTION CODES
X.IF`009NDF,VMS_V5
X`009$IPLDEF`009`009`009;DEFINE INTERRUPT PRIORITY LEVELS
X.ENDC
X`009$IRPDEF`009`009`009;DEFINE I/O REQUEST PACKET
X`009$PCBDEF`009`009`009;DEFINE PROCESS CONTROL BLOCK
X`009$PHDDEF`009`009`009;DEFINE PROCESS HEADER
X`009$PRVDEF`009`009`009;DEFINE VMS PRIVS
X`009$TQEDEF`009`009`009;DEFINE TIMER QUEUE ENTRY
X`009$UCBDEF`009`009`009;DEFINE UNIT CONTROL BLOCK
X`009$VECDEF`009`009`009;DEFINE VECTOR OFFSETS
X;
X`012
X`009.SBTTL`009DRIVER PROLOGUE TABLE
X;+ ---
X;
X; DRIVER CHARACTERISTICS
X;
X;- ---
X.IF`009NDF,VMS_V5
X`009DPTAB`009       -
X`009`009`009END=LAV_END,`009       -;END OF DRIVER (SIZE)
X`009`009`009ADAPTER=NULL,`009       -;UNIBUS DEVICE
X`009`009`009UCBSIZE=UCB$C_Length,  -;UNIT CONTROL BLOCK LENGTH
X`009`009`009MAXUNITS=1,`009       -;ONLY ONE LAV0 DEVICE
X`009`009`009UNLOAD=LAV_UNLOAD,     -;UNLOAD ROUTINE
X`009`009`009NAME=LAVDRIVER`009`009;DRIVER NAME
X.IFF
X`009DPTAB`009       -
X`009`009`009END=LAV_END,`009       -;END OF DRIVER (SIZE)
X`009`009`009ADAPTER=NULL,`009       -;UNIBUS DEVICE
X`009`009`009UCBSIZE=UCB$C_Length,  -;UNIT CONTROL BLOCK LENGTH
X`009`009`009MAXUNITS=1,`009       -;ONLY ONE LAV0 DEVICE
X`009`009`009UNLOAD=LAV_UNLOAD,     -;UNLOAD ROUTINE
X`009`009`009NAME=LAVDRIVER,        -;DRIVER NAME
X`009`009`009SMP=YES`009`009`009;Works under SMP
X.ENDC
X;+ ---
X;
X; INITIALIZATION INFORMATION
X;
X;- ---
X`009DPT_STORE`009INIT
X.IF`009NDF,VMS_V5
X`009DPT_STORE`009UCB,UCB$B_FIPL,B,8`009;FORK IPL LEVEL
X.IFF
X`009DPT_STORE`009UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8 ;FORK SPINLOCK
X.ENDC
X`009DPT_STORE`009UCB,UCB$B_DIPL,B,21`009;DEVICE IPL LEVEL (BR5 = 21)
X`009DPT_STORE`009UCB,UCB$L_DEVCHAR,L,   -;DEVICE CHARACTERISTICS:
X`009`009       <-
X`009`009`009DEV$M_AVL!`009       -;   DEVICE AVAILABLE
X`009`009`009DEV$M_REC!`009       -;   RECORD ORIENTED
X`009`009`009DEV$M_IDV!`009       -;   INPUT DEVICE
X`009`009`009DEV$M_SHR`009       -;   SHARED
X`009`009       >
X`009DPT_STORE`009UCB,UCB$W_DEVBUFSIZ,   -;DEVICE BUFFER SIZE
X`009`009`009`009W,36`009`009;    (36 bytes)
X;+ ---
X;
X; RE-INITIALIZATION INFORMATION
X;
X;- ---
X`009DPT_STORE`009REINIT
X`009DPT_STORE`009DDB,DDB$L_DDT,D,LAV$DDT ;DRIVER DISPATCH TABLE
X`009DPT_STORE`009CRB,`009`009       -;CONTROLLER INITIALIZATION ROUTINE
X`009`009`009 CRB$L_INTD+VEC$L_INITIAL,D,-
X`009`009`009`009LAV_CONTROLLER_INIT
X`009DPT_STORE`009CRB,`009`009       -;UNIT INITIALIZATION ROUTINE
X`009`009`009 CRB$L_INTD+VEC$L_UNITINIT,D,-
X`009`009`009`009LAV_UNIT_INIT
X`009DPT_STORE`009END
X;
X`012
X`009.SBTTL`009DRIVER DISPATCH TABLE
X;+ ---
X;
X; ADDRESSES OF VARIOUS DRIVER ENTRY POINTS
X;
X;- ---
X`009DDTAB`009-
X`009`009 DEVNAM=LAV,`009       -;DEVICE NAME
X`009`009 START=0,`009       -;ADDRESS OF START I/O ENTRY POINT
X`009`009 UNSOLIC=0,`009       -;ADDRESS OF UNSOLICITED INT. ROUTINE
X`009`009 FUNCTB=LAV_FUNCTABLE, -;ADDRESS OF FUNCTION DECISION TABLE
X`009`009 CANCEL=0,`009       -;ADDRESS OF CANCEL I/O ENTRY POINT
X`009`009 REGDMP=0,`009       -;ADDRESS OF REGISTER DUMP ROUTINE
X`009`009 DIAGBF=0,`009       -;SIZE OF DIAGNOSTIC BUFFER
X`009`009 ERLGBF=0`009`009;SIZE OF ERROR LOG BUFFER
X;
X`012
X`009.SBTTL`009FUNCTION DECISION TABLE
XLAV_FUNCTABLE:
X;+ ---
X;
X; SPECIFY LEGAL FUNCTION CODES
X;
X;- ---
X`009FUNCTAB ,`009       -;LEGAL FUNCTION CODE ENTRY
X`009`009<`009       -;
X`009`009 READVBLK,     -;READ VIRTUAL BLOCK
X`009`009 READLBLK,     -;READ LOGICAL BLOCK
X`009`009 READPBLK,     -;READ PHYSICAL BLOCK
X`009`009 WRITEVBLK,    -;WRITE VIRTUAL BLOCK
X`009`009 WRITELBLK,    -;WRITE LOGICAL BLOCK
X`009`009 WRITEPBLK,    -;WRITE PHYSICAL BLOCK
X`009`009>
X;+ ---
X;
X; SPECIFY BUFFERED I/O FUNCTION CODES
X;
X;- ---
X`009FUNCTAB ,`009       -;BUFFERED FUNCTION CODE ENTRY
X`009`009<>`009`009;NONE
X;+ ---
X;
X; SPECIFY 1ST FDT ROUTINE
X;
X;- ---
X`009FUNCTAB GET_LOAD_AVE,  -;GET THE CURRENT LOAD AVERAGES
X`009`009<`009       -;USED BY:
X`009`009 READVBLK,     -;READ VIRTUAL BLOCK
X`009`009 READLBLK,     -;READ LOGICAL BLOCK
X`009`009 READPBLK      -;READ PHYSICAL BLOCK
X`009`009>`009`009;
X;+ ---
X;
X; SPECIFY 2ND FDT ROUTINE
X;
X;- ---
X`009FUNCTAB SET_PRIORITIES, -;SET WHICH PRIORITIES ARE SCANNED
X`009`009<`009`009-;USED BY:
X`009`009 WRITEVBLK,`009-;WRITE VIRTUAL BLOCK
X`009`009 WRITELBLK,`009-;WRITE LOGICAL BLOCK
X`009`009 WRITEPBLK`009-;WRITE PHYSICAL BLOCK
X`009`009>`009`009;
X;
X`012
X`009.SBTTL`009READ FDT ROUTINE
X;+ ---
X;
X; GET_LOAD_AVE`009`009READ FDT ROUTINE
X;
X; FUNCTIONAL DESCRIPTION:
X;
X;`009All this routine does is verify the read arguments and return M1->M15
X;
X; INPUTS:
X;
X;`009R3 = I/O Packet address
X;`009R4 = Current Process Control Block address
X;`009R5 = Unit Control Block Address
X;`009R6 = Channel Control Block Address
X;`009R7 = Function Code
X;`009R8 = Scratch
X;`009R9 = Scratch
X;
X;`0090(AP) --> Read Size
X;`0094(AP) --> Read Buffer
X;
X; OUTPUTS:
X;
X;`009none
X;
X;- ---
XGET_LOAD_AVE:
X
X;+ ---
X; Get Read Size (limited to 36 bytes)
X;- ---
X`009MOVZWL`0094(AP),R0`009`009`009;Get Read Size
X`009CMPL`009R0,#36`009`009`009`009;Bigger than 36??
X`009BLEQU`00910$`009`009`009`009;No:
X`009MOVL`009#36,R0`009`009`009`009;Yes: Truncate to 36 bytes
X10$:
X;+ ---
X; Make sure that the buffer is writeable
X;- ---
X`009IFNOWRT R0,@(AP),ACCVIO`009`009`009;Return SS$_ACCVIO if no write
X;+ ---
X; Move the data
X;- ---
X`009PUSHR`009#`094M<R0,R1,R2,R3,R4,R5>`009`009;Be conservative!!!
X`009MOVC3`009R0,M1,@(AP)`009`009`009;Move the Data
X`009POPR`009#`094M<R0,R1,R2,R3,R4,R5>`009`009;Restore registers
X;+ ---
X; Complete the I/O request
X;- ---
X`009ASHL`009#16,R0,R0`009`009`009;Set transfer size
X`009BISW2`009#SS$_NORMAL,R0`009`009`009;Set Completion code
X`009JMP`009G`094EXE$FINISHIOC`009`009`009;Complete the request
X
X
X;+ ---
X; Return an access violation
X;- ---
XACCVIO:
X`009MOVZWL`009#SS$_ACCVIO,R0`009`009`009;Set return code
X`009CLRL`009R1
X`009JMP`009G`094EXE$ABORTIO`009`009`009;Abort the I/O request
X
X`012
X`009.SBTTL`009WRITE FDT ROUTINE
X;+ ---
X;
X; SET_PRIORITIES`009WRITE FDT ROUTINE
X;
X; FUNCTIONAL DESCRIPTION:
X;
X;`009All this routine does is verify the write arguments and change the
X;`009priorities mask.
X;
X; INPUTS:
X;
X;`009R3 = I/O Packet address
X;`009R4 = Current Process Control Block address
X;`009R5 = Unit Control Block Address
X;`009R6 = Channel Control Block Address
X;`009R7 = Function Code
X;`009R8 = Scratch
X;`009R9 = Scratch
X;
X;`0090(AP) --> Write Size
X;`0094(AP) --> Write Buffer
X;
X; OUTPUTS:
X;
X;`009none
X;
X;- ---
XSET_PRIORITIES:
X;+ ---
X; Check privs (must have CMKRNL)
X;- ---
X
X`009ASSUME`009PHD$Q_PRIVMSK EQ 0
X`009BBC`009#PRV$V_CMKRNL,@PCB$L_PHD(R4),NOPRIV
X
X;+ ---
X; Get Write Size (limited to 4 bytes)
X;- ---
X`009MOVZWL`0094(AP),R0`009`009`009;Get Write Size
X`009CMPL`009R0,#4`009`009`009`009;Bigger than 4??
X`009BLEQU`00910$`009`009`009`009;No:
X`009MOVL`009#4,R0`009`009`009`009;Yes: Truncate to 4 bytes
X10$:
X;+ ---
X; Make sure that the buffer is readable
X;- ---
X`009IFNORD`009R0,@(AP),ACCVIO`009`009`009;Return SS$_ACCVIO if no write
X;+ ---
X; Move the data
X;- ---
X`009MOVL`009@(AP),PRIORITY_MASK
X;+ ---
X; Complete the I/O request
X;- ---
X`009ASHL`009#16,R0,R0`009`009`009;Set transfer size
X`009BISW2`009#SS$_NORMAL,R0`009`009`009;Set Completion code
X`009JMP`009G`094EXE$FINISHIOC`009`009`009;Complete the request
X
X;+ ---
X; Return and access violation
X;- ---
XNOPRIV:
X`009MOVZWL`009#SS$_NOPRIV,R0`009`009`009;Set return code
X`009CLRL`009R1
X`009JMP`009G`094EXE$ABORTIO`009`009`009;Abort the I/O request
X
X`012
X`009.SBTTL`009CONTROLLER INITIALIZATION ROUTINE
X;+ ---
X;
X; ROUTINE CALLED WHEN THE LOADING PROCEDURE SETS UP THE CONTROLLER
X;
X; INPUT REGISTERS:
X;`009R0 = SCRATCH
X;`009R5 = ADDRESS OF UNIT CONTROL BLOCK
X;
X; REGISTERS MODIFIED:
X;`009R0 = SCRATCH
X;
X; SIDE EFFECTS:
X;`009LOAD AVERAGE WATCHER IS SET UP
X;
X;- ---
XLAV_CONTROLLER_INIT:
X`009TSTL`009HAVEINITED`009`009; If power failure recovery,
X`009BEQL`0091$`009`009       ; don't reenter the TQE block.
X`009RSB
X1$:`009MOVL`009#1,HAVEINITED
X
X; SET LOAD AVES TO ZERO
X
X`009CLRF`009M1
X`009CLRF`009M5
X`009CLRF`009M15
X`009CLRF`009P1
X`009CLRF`009P5
X`009CLRF`009P15
X`009CLRF`009Q1
X`009CLRF`009Q5
X`009CLRF`009Q15
X
X; SET UP THE LOAD AVERAGE WATCHING CODE (INSERT THE TIMER QUEUE ENTRY)
X;
X; Fork to a lower IPL
X;
X`009MOVQ`009R4,-(SP)`009    ; Not allowed to modify R4 or R5
X`009MOVAQ`009FKB,R5
X`009PUSHAB`00910$`009`009    ; Trick EXE$FORK into returning to us.
X`009PUSHAB`00920$`009`009    ; Address to fork to
X`009JMP`009G`094EXE$FORK
X10$:
X`009MOVQ`009(SP)+,R4`009    ; Restore R4, R5
X15$:`009RSB`009`009`009    ; Return from unit init routine
X;
X; Now running at IPL 6
X;
X20$:`009MOVAB`009UPDATE_LOAD_AVERAGE,TQE_PC
X`009MOVAL`009TQE,R5
X`009MOVB`009#1,RUNNING`009    ; CURRENTLY RUNNING
X`009CLRB`009STOPPING`009    ; AND NOT STOPPING
X.IF`009NDF,VMS_V5
X`009MOVQ`009@#EXE$GQ_SYSTIME,TQE_DUE
X`009INSQUE`009TQE,@#EXE$GL_TQFL
X.IFF
X`009READ_SYSTIME R0
X`009JSB`009G`094EXE$INSTIMQ
X.ENDC
X30$:`009RSB`009`009`009`009`009;AND RETURN
X;
X`012
X`009.SBTTL`009CONTROLLER UNLOAD ROUTINE
X;+ ---
X;
X; ROUTINE CALLED WHEN THE DRIVER IS RELOADED
X;
X; INPUT REGISTERS:
X;`009R0 = SCRATCH
X;`009R5 = ADDRESS OF UNIT CONTROL BLOCK
X;
X; REGISTERS MODIFIED:
X;`009R0 = SCRATCH
X;
X; SIDE EFFECTS:
X;`0091st call TQE entry is removed
X;`0092nd call driver is unloaded
X;- ---
XLAV_UNLOAD:
X`009TSTB`009RUNNING`009`009    ; If not running, unload
X`009BNEQ`00910$
X`009MOVL`009#SS$_NORMAL,R0`009    ; If not running, ok to unload driver
X`009RSB
X10$:`009MOVB`009#1,STOPPING`009    ; SET THE STOPPING FLAG
X`009CLRL`009R0
X`009RSB
X;
X`012
X`009.SBTTL`009UNIT INITIALIZATION ROUTINE
X;+ ---
X;
X; ROUTINE CALLED WHEN THE LOADING PROCEDURE SETS UP THE UNIT.
X;  THE DEVICE ONLINE BIT IS SET IN THE UCB.
X;
X; INPUT REGISTERS:
X;`009R0 = SCRATCH
X;`009R5 = ADDRESS OF UNIT CONTROL BLOCK
X;
X; REGISTERS MODIFIED:
X;`009R0 = SCRATCH
X;
X; SIDE EFFECTS:
X;`009UNIT ONLINE BIT IS SET IN UCB
X;
X;- ---
XLAV_UNIT_INIT:
X`009BISW`009#UCB$M_ONLINE,UCB$W_STS(R5)`009;SET STATUS = ONLINE
X`009RSB
X;
X`012
X`009.SBTTL`009LOCAL DATA
X;+ ---
X; Local Data for the Load Average Watcher
X;- ---
XM1:`009.FLOAT`0090.0`009`009`009`009;1  Min. Load Ave.
XM5:`009.FLOAT`0090.0`009`009`009`009;5  Min. Load Ave.
XM15:`009.FLOAT`0090.0`009`009`009`009;15 Min. Load Ave.
X
XP1:`009.FLOAT`0090.0`009`009`009`009;1  Min. Priority Ave.
XP5:`009.FLOAT`0090.0`009`009`009`009;5  Min. Priority Ave.
XP15:`009.FLOAT`0090.0`009`009`009`009;15 Min. Priority Ave.
X
XQ1:`009.FLOAT`0090.0`009`009`009`009;1  Min. Ave disk queue len
XQ5:`009.FLOAT`0090.0`009`009`009`009;5  Min. Ave disk queue len
XQ15:`009.FLOAT`0090.0`009`009`009`009;15 Min. Ave disk queue len
X
XF1:`009.FLOAT`0090.983471453`009`009`009;1 min. load ave. constants
XF11:`009.FLOAT`0090.016528547
X
XF2:`009.FLOAT`0090.996672213`009`009`009;5 min. load ave. constants
XF12:`009.FLOAT`0090.003327787
X
XF3:`009.FLOAT`0090.998889506`009`009`009;15 min. load ave. constants
XF13:`009.FLOAT`0090.001110494
X
X
XTQE:`009.LONG`0090`009`009`009`009;TIMER QUEUE ELEMENT (FLINK)
X`009.LONG`0090`009`009`009`009;(BLINK)
X`009.WORD`009TQE_LEN`009`009`009`009;SIZE
X`009.BYTE`009DYN$C_TQE`009`009`009;TYPE
X`009.BYTE`009TQE$C_SSREPT`009`009`009;REQUEST == REPEAT
XTQE_PC: .LONG`0090`009`009`009`009;PC TO CALL
X`009.LONG`0090`009`009`009`009;N/A
X`009.LONG`0090`009`009`009`009;N/A
XTQE_DUE:.LONG`0090`009`009`009`009;DUE TIME
X`009.LONG`0090
X`009.LONG`009100000*100`009`009`009;Repeat time (1 Sec.)
X`009.LONG`0090
XTQE_LEN = .-TQE
X
XHAVEINITED:
X`009.LONG`0090`009`009`009`009; Set if alread initied
XRUNNING:
X`009.BYTE`0090`009`009`009`009; True if TQE entry is in queue.
XSTOPPING:
X`009.BYTE`0090`009`009`009`009; SET TO REQUEST TQE TO ABORT
XPRIORITY_MASK:
X`009.LONG`0090`009`009`009`009; Check all priorities
X
XFKB:`009.LONG`0090`009`009`009`009; FQFL
X`009.LONG`0090`009`009`009`009; FQBL
X`009.WORD`0090`009`009`009`009; SIZE
X`009.BYTE`009DYN$C_FRK`009`009`009; TYPE
X`009.BYTE`009IPL$_TIMER`009`009`009; IPL LOW ENOUGH TO LOCK TIMER
XFKB_PC: .LONG`0090`009`009`009`009; PC
X`009.LONG`0090`009`009`009`009; R3
X`009.LONG`0090`009`009`009`009; R4
X`012
X`009.SBTTL`009Load Average Watching Code
X
XUPDATE_LOAD_AVERAGE:`009`009`009`009;We link into the
X`009`009`009`009`009`009;VMS kernel via the timer q
X`009`009`009`009`009`009;and is entered by JSB when
X`009`009`009`009`009`009;each 1 sec. interval ends.
X`009`009`009`009`009`009;R0-R4 are available
X
X;---
X; See if we are supposed to abort
X;---
X`009TSTB`009STOPPING
X`009BEQL`00910$
X.IF`009NDF,VMS_V5
X`009MOVAL`009@#EXE$AL_TQENOREPT,R5
X.IFF
X`009MOVL`009G`094EXE$AR_TQENOREPT,R5
X.ENDC
X`009CLRB`009RUNNING
X`009RSB
X10$:
X
X`009CLRL`009R2`009`009`009`009;Initialize the run count=0
X;+ ---
X; Check the run queues for the number of processes in 'COM' state
X;- ---
X`009CLRL`009R1`009`009`009`009;Initialize summary word pos=0
X
X.IF`009NDF,VMS_V5
X`009DSBINT`009#IPL$_SYNCH
X.IFF
X`009LOCK`009SCHED
X.ENDC
X`009BICL3`009PRIORITY_MASK,@#SCH$GL_Comqs,R4 ;Get masked queues
XFind_Comq:`009`009`009`009`009;Find a run queue with entries
X`009SUBL3`009R1,#32,R0`009`009`009;Calculate #bits left (32-R1)
X`009FFS`009R1,R0,R4,R1`009`009`009;Look for an active queue
X`009BEQL`009Ncomo`009`009`009`009;None: look for procs waiting
X`009`009`009`009`009`009;`009for inswap
X`009MOVAQ`009@#SCH$AQ_Comh[R1],R0`009`009;Got one: get its queue header
X`009MOVL`009R0,R3`009`009`009`009;Save terminating address
X
X10$:`009MOVL`009(R0),R0`009`009`009`009;Get the next process in the q
X`009CMPL`009R0,R3`009`009`009`009;End of queue?
X`009BEQL`00920$`009`009`009`009;Yes: look for next queue
X`009INCL`009R2`009`009`009`009;No: count this process
X`009BRB`00910$`009`009`009`009;and try the next one
X
X20$:`009INCL`009R1`009`009`009`009;Queue done, look for next one
X`009BRB`009Find_Comq
X
X;+ ---
X; Check the COMO queues for the number of processes in 'COM' state (outswap)
X;- ---
XNcomo:`009CLRL`009R1`009`009`009`009;Initialize summary word pos=0
X`009BICL3`009PRIORITY_MASK,@#SCH$GL_Comoqs,R4 ;Get masked queues
XFind_Comoq:`009`009`009`009`009;Find a run queue with entries
X`009SUBL3`009R1,#32,R0`009`009`009;Calculate #bits left (32-R1)
X`009FFS`009R1,R0,R4,R1`009`009`009;Look for an active queue
X`009BEQL`009Nwait`009`009`009`009;None: look for procs waiting
X`009`009`009`009`009`009;`009for resources
X`009MOVAQ`009@#SCH$AQ_Comoh[R1],R0`009`009;Got one: get its queue header
X`009MOVL`009R0,R3`009`009`009`009;Save terminating address
X
X10$:`009MOVL`009(R0),R0`009`009`009`009;Get the next process in the q
X`009CMPL`009R0,R3`009`009`009`009;End of queue?
X`009BEQL`00920$`009`009`009`009;Yes: look for next queue
X`009INCL`009R2`009`009`009`009;No: count this process
X`009BRB`00910$`009`009`009`009;and try the next one
X
X20$:`009INCL`009R1`009`009`009`009;Queue done, look for next one
X`009BRB`009Find_Comoq
X
X;+ ---
X; Count processes in collided page wait
X;- ---
XNwait:
X`009MOVAQ`009@#SCH$GQ_Colpgwq,R0`009`009;Get the collided page wait q
X
X10$:`009MOVL`009(R0),R0`009`009`009`009;Get the next process in the q
X`009CMPL`009R0,#SCH$GQ_Colpgwq`009`009;End of queue?
X`009BEQL`009Npfw`009`009`009`009;Yes: look at page fault waits
X`009INCL`009R2`009`009`009`009;No: count this process
X`009BRB`00910$`009`009`009`009;and try the next one
X
X;+ ---
X; Count processes in page fault wait
X;- ---
XNpfw:
X`009MOVAQ`009@#SCH$GQ_PfwQ,R0`009`009;Get the page fault wait q
X
X10$:`009MOVL`009(R0),R0`009`009`009`009;Get the next process in the q
X`009CMPL`009R0,#SCH$GQ_Pfwq`009`009`009;End of queue?
X`009BEQL`009Nfpw`009`009`009`009;Yes: look at free page waits
X`009INCL`009R2`009`009`009`009;No: count this process
X`009BRB`00910$`009`009`009`009;and try the next one
X
X;+ ---
X; Count processes in free page wait
X;- ---
XNfpw:
X`009MOVAQ`009@#SCH$GQ_FpgwQ,R0`009`009;Get the free page wait q
X
X10$:`009MOVL`009(R0),R0`009`009`009`009;Get the next process in the q
X`009CMPL`009R0,#SCH$GQ_Fpgwq`009`009;End of queue?
X`009BEQL`009Count_Done`009`009`009;Yes: all done
X`009INCL`009R2`009`009`009`009;No: count this process
X`009BRB`00910$`009`009`009`009;and try the next one
X
XCount_Done:
X.IF`009NDF,VMS_V5
X`009ENBINT
X.IFF
X`009UNLOCK`009SCHED
X.ENDC
X.IF`009NDF,VMS_V5
X;
X;   Adjust for the NULL job, which might be missing if the mask
X;   exclused the priority 0 queue.
X;
X`009BBS`009#31,PRIORITY_MASK,10$
X`009DECL`009R2`009`009`009`009; SUBTRACT THE NULL JOB
X10$:
X;
X;   Adjust for the CURrent job if the priority is high enough.
X;
X`009BBS`009@#SCH$GB_PRI,PRIORITY_MASK,20$
X`009INCL`009R2`009`009`009`009; ADD THE CURRENT JOB
X20$:
X.ENDC
X.IF`009NDF,VMS_V5
X`009MOVL`009G`094SCH$GB_PRI,R3
X.IFF
X;
X;   Count done, now scan through each CPU. Store the LOWEST priority
X;   found and the number of executing CPUs.
X;
X`009CLRL`009R3`009`009`009`009; Lowest priority
X`009CLRL`009R4`009`009`009`009; Number of CPUs active
X`009CLRL`009R1`009`009`009`009; Current CPU index
X11$:`009SUBL3`009R1,#32,R0`009`009`009;Calculate #bits left (32-R1)
X`009FFS`009R1,R0,@#SMP$GL_ACTIVE_CPUS,R1`009;Look for an active CPU
X`009BEQL`00935$
X15$:`009MOVL`009g`094SMP$GL_CPU_DATA[R1],R0`009; Get the CPU database
X`009BEQL`00930$`009`009`009`009; This CPU doesn't exist
X`009INCL`009R4`009`009`009`009; Seen one more CPU
X`009CMPL`009CPU$L_CURPCB(R0),@#SCH$AR_NULLPCB ; Running 'the null job'?
X`009BEQL`00920$
X`009MOVZBL`009CPU$B_CUR_PRI(R0),-(SP)
X`009BBS`009(SP)+,PRIORITY_MASK,20$`009`009; but priority is 'masked'
X`009INCL`009R2`009`009`009`009; CPU is active.
V20$:`009CMPB`009CPU$B_CUR_PRI(R0),R3`009`009; Store the lowest executing priori
Xty
X`009BLEQU`00930$
X`009MOVB`009CPU$B_CUR_PRI(R0),R3
X30$:`009INCL`009R1
X`009BRB`00911$`009`009`009`009; Check for all 32 CPUs.
X35$:
X.ENDC
X
X;+ ---
X; Count done, calculate the load ave.
X;- ---
X`009CVTLF`009R2,R2`009`009`009`009;Float the count of processes
X.IF`009DF,VMS_V5
X`009CVTLF`009R4,R4`009`009`009`009;Float the count of CPUs
X`009DIVF2`009R4,R2`009`009`009`009;Load is Processes/CPUs.
X.ENDC
X; 1 min. load ave.
X`009MULF3`009R2,F11,R0`009`009`009;La=OldLa*F+NewLa*(1-F)
X`009MULF3`009M1,F1,R1
X`009ADDF3`009R0,R1,M1
X; 5 min. load ave.
X`009MULF3`009R2,F12,R0
X`009MULF3`009M5,F2,R1
X`009ADDF3`009R0,R1,M5
X; 15 min. load ave.
X`009MULF3`009R2,F13,R0
X`009MULF3`009M15,F3,R1
X`009ADDF3`009R0,R1,M15
X
X`009CMPB`009R3,#255`009`009`009`009; NULL JOB
X`009BNEQ`00940$
X`009MOVB`009#32,R3`009`009`009`009; TREAT AS PRIORITY 0
X40$:`009SUBB3`009R3,#32,R2
X`009CVTBF`009R2,R2
X; 1 min. priority ave.
X`009MULF3`009R2,F11,R0`009`009`009;La=OldLa*F+NewLa*(1-F)
X`009MULF3`009P1,F1,R1
X`009ADDF3`009R0,R1,P1
X; 5 min. priority ave.
X`009MULF3`009R2,F12,R0
X`009MULF3`009P5,F2,R1
X`009ADDF3`009R0,R1,P5
X; 15 min. priority ave.
X`009MULF3`009R2,F13,R0
X`009MULF3`009P15,F3,R1
X`009ADDF3`009R0,R1,P15
X
X;
X;   Update the disk queue length information
X;
XQUEUE_LEN:
X`009CLRL`009R2`009`009`009`009; R2 = Sun of queue lengths
X`009PUSHR`009#`094M<R10,R11>
X`009CLRQ`009R10`009`009`009`009; Indicate start of I/O d.b.
V10$:`009JSB`009G`094IOC$SCAN_IODB`009`009`009; Get next DDB and UCB into R10, R
X11
X`009BLBC`009R0,40$`009`009`009`009;  no more, done
X`009CMPB`009#DC$_DISK,UCB$B_DEVCLASS(R10)`009; Is this controller a disk?
X`009BEQL`00920$
X`009CLRL`009R10`009`009`009`009; Go for next controller
X`009BRB`00910$
X20$:`009BBC`009#DEV$V_MNT,UCB$L_DEVCHAR(R10),10$ ; Skip if not mounted
V`009BITL`009#<DEV$M_CDP+DEV$M_SSM>,UCB$L_DEVCHAR2(R10) ; class driver or shadow
X set member?
X`009BNEQ`00910$`009`009`009`009;`009      if so, skip
X`009CVTWL`009UCB$W_QLEN(R10),R0`009`009; Get queue length
X`009BGEQ`00930$
X`009CLRL`009R0`009`009`009`009; negative queu length is a temporary condition
X30$:`009CMPL`009R0,R2
X`009BLEQ`00910$
X`009MOVL`009R0,R2
X`009BRB`00910$
X40$:`009POPR`009#`094M<R10,R11>`009`009`009; restore registers
X
X`009CVTLF`009R2,R2`009`009`009`009;Float the count
X; 1 min. queue ave.
X`009MULF3`009R2,F11,R0`009`009`009;La=OldLa*F+NewLa*(1-F)
X`009MULF3`009Q1,F1,R1
X`009ADDF3`009R0,R1,Q1
X; 5 min. queue ave.
X`009MULF3`009R2,F12,R0
X`009MULF3`009Q5,F2,R1
X`009ADDF3`009R0,R1,Q5
X; 15 min. queue ave.
X`009MULF3`009R2,F13,R0
X`009MULF3`009Q15,F3,R1
X`009ADDF3`009R0,R1,Q15
X`009RSB`009`009`009`009`009;Return to clock dispatcher
X
X
X`012
X`009.SBTTL`009END OF DRIVER
X;+ ---
X;
X; END OF DRIVER
X;
X;- ---
XLAV_END:`009`009`009`009`009;LET DPTAB KNOW THE DRIVER SIZE
X;
X`009.END
$ GOSUB UNPACK_FILE
$ EXIT

