;From: IN%"Kenneth Adelman " 1-AUG-1988 13:48 ;To: _RAND ! sent to info-vax@kl.sri.com ;Subj: Nice software; be a shame if it were to bugcheck. ; ; ; Oops. The Load Average driver I posted a few weeks ago had a ;fatal bug. Fortunately, I fell on my own sword before anyone else ;reported this to me. Here is a fixed version, which includes the ;conditionals to run on VMS V4. Add a "VMS_V5=1" line at the top for ;VMS V5. ; ; Ken ; ; .TITLE LAVDRIVER VAX/VMS Load Average Pseudo Device .IDENT /Version 4.00/ .SBTTL DOCUMENTATION ;---------------S-R-I-International-Artificial-Intelligence-Center----------- ; ; ; MODULE NAME: ; ; LAVDRIVER ; ; PURPOSE: ; ; This is a pseudo device which supplies load average information to ; programs wishing to display some indication of system load. ; ; KEYWORDS: ; ; Drivers ; ; USAGE: ; ; Standard system QIO calls. ; ; The driver is installed via sysgen. The following is the correct ; sysgen command sequence. ; ; SYSGEN> LOAD LAVDRIVER ; SYSGEN> CONNECT LAV0 /NOADA/DRIVER=LAVDRIVER ; ; Note: LAVDRIVER.EXE should reside in [SYS$LDR] ; ; ; SPECIAL REQUIREMENTS: ; ; none ; ; LANGUAGE: ; ; VAX-11 MACRO ; ; PARAMETERS: ; ; none ; ; SPECIAL TECHNIQUES: ; ; none ; ; AUTHOR: ; ; David Kashtan ; ; DATE CREATED: ; ; Wed Jul 21 11:25:19 1982 ; ; MAINTAINED BY: ; ; David Kashtan ; ; UPDATES: ; ; Modified for VMS V5 SMP Thu Oct 8 1987 Kenneth Adelman / LBL ; ;---------------S-R-I-International-Artificial-Intelligence-Center----------- .SBTTL DEFINITIONS .LIBRARY /SYS$LIBRARY:LIB.MLB/ ;+ --- ; ; DEFINE CONTROL BLOCK OFFSETS & OTHER SYSTEM STUFF BY CALLING 'DEF' MACROS ; ;- --- VMS_V5=1 .IF DF,VMS_V5 $CPUDEF ;DEFINE PER-CPU STRUCTURE .ENDC $CRBDEF ;DEFINE CHANNEL REQUEST BLOCK $DCDEF ;DEFINE DEVICE TYPES $DDBDEF ;DEFINE DEVICE DATA BLOCK $DEVDEF ;DEFINE DEVICE CONSTANTS $DPTDEF ;DEFINE DRIVER PROLOGUE TABLE $DYNDEF ;DEFINE DYNAMIC STRUCTURE CODES $FKBDEF ;DEFINE FORK BLOCK CODES $IODEF ;DEFINE I/O FUNCTION CODES .IF NDF,VMS_V5 $IPLDEF ;DEFINE INTERRUPT PRIORITY LEVELS .ENDC $IRPDEF ;DEFINE I/O REQUEST PACKET $PCBDEF ;DEFINE PROCESS CONTROL BLOCK $PHDDEF ;DEFINE PROCESS HEADER $PRVDEF ;DEFINE VMS PRIVS $TQEDEF ;DEFINE TIMER QUEUE ENTRY $UCBDEF ;DEFINE UNIT CONTROL BLOCK $VECDEF ;DEFINE VECTOR OFFSETS ; .SBTTL DRIVER PROLOGUE TABLE ;+ --- ; ; DRIVER CHARACTERISTICS ; ;- --- .IF NDF,VMS_V5 DPTAB - END=LAV_END, -;END OF DRIVER (SIZE) ADAPTER=NULL, -;UNIBUS DEVICE UCBSIZE=UCB$C_Length, -;UNIT CONTROL BLOCK LENGTH MAXUNITS=1, -;ONLY ONE LAV0 DEVICE UNLOAD=LAV_UNLOAD, -;UNLOAD ROUTINE NAME=LAVDRIVER ;DRIVER NAME .IFF DPTAB - END=LAV_END, -;END OF DRIVER (SIZE) ADAPTER=NULL, -;UNIBUS DEVICE UCBSIZE=UCB$C_Length, -;UNIT CONTROL BLOCK LENGTH MAXUNITS=1, -;ONLY ONE LAV0 DEVICE UNLOAD=LAV_UNLOAD, -;UNLOAD ROUTINE NAME=LAVDRIVER, -;DRIVER NAME SMP=YES ;Works under SMP .ENDC ;+ --- ; ; INITIALIZATION INFORMATION ; ;- --- DPT_STORE INIT .IF NDF,VMS_V5 DPT_STORE UCB,UCB$B_FIPL,B,8 ;FORK IPL LEVEL .IFF DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8 ;FORK SPINLOCK .ENDC DPT_STORE UCB,UCB$B_DIPL,B,21 ;DEVICE IPL LEVEL (BR5 = 21) DPT_STORE UCB,UCB$L_DEVCHAR,L, -;DEVICE CHARACTERISTICS: <- DEV$M_AVL! -; DEVICE AVAILABLE DEV$M_REC! -; RECORD ORIENTED DEV$M_IDV! -; INPUT DEVICE DEV$M_SHR -; SHARED > DPT_STORE UCB,UCB$W_DEVBUFSIZ, -;DEVICE BUFFER SIZE W,36 ; (36 bytes) ;+ --- ; ; RE-INITIALIZATION INFORMATION ; ;- --- DPT_STORE REINIT DPT_STORE DDB,DDB$L_DDT,D,LAV$DDT ;DRIVER DISPATCH TABLE DPT_STORE CRB, -;CONTROLLER INITIALIZATION ROUTINE CRB$L_INTD+VEC$L_INITIAL,D,- LAV_CONTROLLER_INIT DPT_STORE CRB, -;UNIT INITIALIZATION ROUTINE CRB$L_INTD+VEC$L_UNITINIT,D,- LAV_UNIT_INIT DPT_STORE END ; .SBTTL DRIVER DISPATCH TABLE ;+ --- ; ; ADDRESSES OF VARIOUS DRIVER ENTRY POINTS ; ;- --- DDTAB - DEVNAM=LAV, -;DEVICE NAME START=0, -;ADDRESS OF START I/O ENTRY POINT UNSOLIC=0, -;ADDRESS OF UNSOLICITED INT. ROUTINE FUNCTB=LAV_FUNCTABLE, -;ADDRESS OF FUNCTION DECISION TABLE CANCEL=0, -;ADDRESS OF CANCEL I/O ENTRY POINT REGDMP=0, -;ADDRESS OF REGISTER DUMP ROUTINE DIAGBF=0, -;SIZE OF DIAGNOSTIC BUFFER ERLGBF=0 ;SIZE OF ERROR LOG BUFFER ; .SBTTL FUNCTION DECISION TABLE LAV_FUNCTABLE: ;+ --- ; ; SPECIFY LEGAL FUNCTION CODES ; ;- --- FUNCTAB , -;LEGAL FUNCTION CODE ENTRY < -; READVBLK, -;READ VIRTUAL BLOCK READLBLK, -;READ LOGICAL BLOCK READPBLK, -;READ PHYSICAL BLOCK WRITEVBLK, -;WRITE VIRTUAL BLOCK WRITELBLK, -;WRITE LOGICAL BLOCK WRITEPBLK, -;WRITE PHYSICAL BLOCK > ;+ --- ; ; SPECIFY BUFFERED I/O FUNCTION CODES ; ;- --- FUNCTAB , -;BUFFERED FUNCTION CODE ENTRY <> ;NONE ;+ --- ; ; SPECIFY 1ST FDT ROUTINE ; ;- --- FUNCTAB GET_LOAD_AVE, -;GET THE CURRENT LOAD AVERAGES < -;USED BY: READVBLK, -;READ VIRTUAL BLOCK READLBLK, -;READ LOGICAL BLOCK READPBLK -;READ PHYSICAL BLOCK > ; ;+ --- ; ; SPECIFY 2ND FDT ROUTINE ; ;- --- FUNCTAB SET_PRIORITIES, -;SET WHICH PRIORITIES ARE SCANNED < -;USED BY: WRITEVBLK, -;WRITE VIRTUAL BLOCK WRITELBLK, -;WRITE LOGICAL BLOCK WRITEPBLK -;WRITE PHYSICAL BLOCK > ; ; .SBTTL READ FDT ROUTINE ;+ --- ; ; GET_LOAD_AVE READ FDT ROUTINE ; ; FUNCTIONAL DESCRIPTION: ; ; All this routine does is verify the read arguments and return M1->M15 ; ; INPUTS: ; ; R3 = I/O Packet address ; R4 = Current Process Control Block address ; R5 = Unit Control Block Address ; R6 = Channel Control Block Address ; R7 = Function Code ; R8 = Scratch ; R9 = Scratch ; ; 0(AP) --> Read Size ; 4(AP) --> Read Buffer ; ; OUTPUTS: ; ; none ; ;- --- GET_LOAD_AVE: ;+ --- ; Get Read Size (limited to 36 bytes) ;- --- MOVZWL 4(AP),R0 ;Get Read Size CMPL R0,#36 ;Bigger than 36?? BLEQU 10$ ;No: MOVL #36,R0 ;Yes: Truncate to 36 bytes 10$: ;+ --- ; Make sure that the buffer is writeable ;- --- IFNOWRT R0,@(AP),ACCVIO ;Return SS$_ACCVIO if no write ;+ --- ; Move the data ;- --- PUSHR #^M ;Be conservative!!! MOVC3 R0,M1,@(AP) ;Move the Data POPR #^M ;Restore registers ;+ --- ; Complete the I/O request ;- --- ASHL #16,R0,R0 ;Set transfer size BISW2 #SS$_NORMAL,R0 ;Set Completion code JMP G^EXE$FINISHIOC ;Complete the request ;+ --- ; Return an access violation ;- --- ACCVIO: MOVZWL #SS$_ACCVIO,R0 ;Set return code CLRL R1 JMP G^EXE$ABORTIO ;Abort the I/O request .SBTTL WRITE FDT ROUTINE ;+ --- ; ; SET_PRIORITIES WRITE FDT ROUTINE ; ; FUNCTIONAL DESCRIPTION: ; ; All this routine does is verify the write arguments and change the ; priorities mask. ; ; INPUTS: ; ; R3 = I/O Packet address ; R4 = Current Process Control Block address ; R5 = Unit Control Block Address ; R6 = Channel Control Block Address ; R7 = Function Code ; R8 = Scratch ; R9 = Scratch ; ; 0(AP) --> Write Size ; 4(AP) --> Write Buffer ; ; OUTPUTS: ; ; none ; ;- --- SET_PRIORITIES: ;+ --- ; Check privs (must have CMKRNL) ;- --- ASSUME PHD$Q_PRIVMSK EQ 0 BBC #PRV$V_CMKRNL,@PCB$L_PHD(R4),NOPRIV ;+ --- ; Get Write Size (limited to 4 bytes) ;- --- MOVZWL 4(AP),R0 ;Get Write Size CMPL R0,#4 ;Bigger than 4?? BLEQU 10$ ;No: MOVL #4,R0 ;Yes: Truncate to 4 bytes 10$: ;+ --- ; Make sure that the buffer is readable ;- --- IFNORD R0,@(AP),ACCVIO ;Return SS$_ACCVIO if no write ;+ --- ; Move the data ;- --- MOVL @(AP),PRIORITY_MASK ;+ --- ; Complete the I/O request ;- --- ASHL #16,R0,R0 ;Set transfer size BISW2 #SS$_NORMAL,R0 ;Set Completion code JMP G^EXE$FINISHIOC ;Complete the request ;+ --- ; Return and access violation ;- --- NOPRIV: MOVZWL #SS$_NOPRIV,R0 ;Set return code CLRL R1 JMP G^EXE$ABORTIO ;Abort the I/O request .SBTTL CONTROLLER INITIALIZATION ROUTINE ;+ --- ; ; ROUTINE CALLED WHEN THE LOADING PROCEDURE SETS UP THE CONTROLLER ; ; INPUT REGISTERS: ; R0 = SCRATCH ; R5 = ADDRESS OF UNIT CONTROL BLOCK ; ; REGISTERS MODIFIED: ; R0 = SCRATCH ; ; SIDE EFFECTS: ; LOAD AVERAGE WATCHER IS SET UP ; ;- --- LAV_CONTROLLER_INIT: TSTL HAVEINITED ; If power failure recovery, BEQL 1$ ; don't reenter the TQE block. RSB 1$: MOVL #1,HAVEINITED ; SET LOAD AVES TO ZERO CLRF M1 CLRF M5 CLRF M15 CLRF P1 CLRF P5 CLRF P15 CLRF Q1 CLRF Q5 CLRF Q15 ; SET UP THE LOAD AVERAGE WATCHING CODE (INSERT THE TIMER QUEUE ENTRY) ; ; Fork to a lower IPL ; MOVQ R4,-(SP) ; Not allowed to modify R4 or R5 MOVAQ FKB,R5 PUSHAB 10$ ; Trick EXE$FORK into returning to us. PUSHAB 20$ ; Address to fork to JMP G^EXE$FORK 10$: MOVQ (SP)+,R4 ; Restore R4, R5 15$: RSB ; Return from unit init routine ; ; Now running at IPL 6 ; 20$: MOVAB UPDATE_LOAD_AVERAGE,TQE_PC MOVAL TQE,R5 MOVB #1,RUNNING ; CURRENTLY RUNNING CLRB STOPPING ; AND NOT STOPPING .IF NDF,VMS_V5 MOVQ @#EXE$GQ_SYSTIME,TQE_DUE INSQUE TQE,@#EXE$GL_TQFL .IFF READ_SYSTIME R0 JSB G^EXE$INSTIMQ .ENDC 30$: RSB ;AND RETURN ; .SBTTL CONTROLLER UNLOAD ROUTINE ;+ --- ; ; ROUTINE CALLED WHEN THE DRIVER IS RELOADED ; ; INPUT REGISTERS: ; R0 = SCRATCH ; R5 = ADDRESS OF UNIT CONTROL BLOCK ; ; REGISTERS MODIFIED: ; R0 = SCRATCH ; ; SIDE EFFECTS: ; 1st call TQE entry is removed ; 2nd call driver is unloaded ;- --- LAV_UNLOAD: TSTB RUNNING ; If not running, unload BNEQ 10$ MOVL #SS$_NORMAL,R0 ; If not running, ok to unload driver RSB 10$: MOVB #1,STOPPING ; SET THE STOPPING FLAG CLRL R0 RSB ; .SBTTL UNIT INITIALIZATION ROUTINE ;+ --- ; ; ROUTINE CALLED WHEN THE LOADING PROCEDURE SETS UP THE UNIT. ; THE DEVICE ONLINE BIT IS SET IN THE UCB. ; ; INPUT REGISTERS: ; R0 = SCRATCH ; R5 = ADDRESS OF UNIT CONTROL BLOCK ; ; REGISTERS MODIFIED: ; R0 = SCRATCH ; ; SIDE EFFECTS: ; UNIT ONLINE BIT IS SET IN UCB ; ;- --- LAV_UNIT_INIT: BISW #UCB$M_ONLINE,UCB$W_STS(R5) ;SET STATUS = ONLINE RSB ; .SBTTL LOCAL DATA ;+ --- ; Local Data for the Load Average Watcher ;- --- M1: .FLOAT 0.0 ;1 Min. Load Ave. M5: .FLOAT 0.0 ;5 Min. Load Ave. M15: .FLOAT 0.0 ;15 Min. Load Ave. P1: .FLOAT 0.0 ;1 Min. Priority Ave. P5: .FLOAT 0.0 ;5 Min. Priority Ave. P15: .FLOAT 0.0 ;15 Min. Priority Ave. Q1: .FLOAT 0.0 ;1 Min. Ave disk queue len Q5: .FLOAT 0.0 ;5 Min. Ave disk queue len Q15: .FLOAT 0.0 ;15 Min. Ave disk queue len F1: .FLOAT 0.983471453 ;1 min. load ave. constants F11: .FLOAT 0.016528547 F2: .FLOAT 0.996672213 ;5 min. load ave. constants F12: .FLOAT 0.003327787 F3: .FLOAT 0.998889506 ;15 min. load ave. constants F13: .FLOAT 0.001110494 TQE: .LONG 0 ;TIMER QUEUE ELEMENT (FLINK) .LONG 0 ;(BLINK) .WORD TQE_LEN ;SIZE .BYTE DYN$C_TQE ;TYPE .BYTE TQE$C_SSREPT ;REQUEST == REPEAT TQE_PC: .LONG 0 ;PC TO CALL .LONG 0 ;N/A .LONG 0 ;N/A TQE_DUE:.LONG 0 ;DUE TIME .LONG 0 .LONG 100000*100 ;Repeat time (1 Sec.) .LONG 0 TQE_LEN = .-TQE HAVEINITED: .LONG 0 ; Set if alread initied RUNNING: .BYTE 0 ; True if TQE entry is in queue. STOPPING: .BYTE 0 ; SET TO REQUEST TQE TO ABORT PRIORITY_MASK: .LONG 0 ; Check all priorities FKB: .LONG 0 ; FQFL .LONG 0 ; FQBL .WORD 0 ; SIZE .BYTE DYN$C_FRK ; TYPE .BYTE IPL$_TIMER ; IPL LOW ENOUGH TO LOCK TIMER FKB_PC: .LONG 0 ; PC .LONG 0 ; R3 .LONG 0 ; R4 .SBTTL Load Average Watching Code UPDATE_LOAD_AVERAGE: ;We link into the ;VMS kernel via the timer q ;and is entered by JSB when ;each 1 sec. interval ends. ;R0-R4 are available ;--- ; See if we are supposed to abort ;--- TSTB STOPPING BEQL 10$ .IF NDF,VMS_V5 MOVAL @#EXE$AL_TQENOREPT,R5 .IFF MOVL G^EXE$AR_TQENOREPT,R5 .ENDC CLRB RUNNING RSB 10$: CLRL R2 ;Initialize the run count=0 ;+ --- ; Check the run queues for the number of processes in 'COM' state ;- --- CLRL R1 ;Initialize summary word pos=0 .IF NDF,VMS_V5 DSBINT #IPL$_SYNCH .IFF LOCK SCHED .ENDC BICL3 PRIORITY_MASK,@#SCH$GL_Comqs,R4 ;Get masked queues Find_Comq: ;Find a run queue with entries SUBL3 R1,#32,R0 ;Calculate #bits left (32-R1) FFS R1,R0,R4,R1 ;Look for an active queue BEQL Ncomo ;None: look for procs waiting ; for inswap MOVAQ @#SCH$AQ_Comh[R1],R0 ;Got one: get its queue header MOVL R0,R3 ;Save terminating address 10$: MOVL (R0),R0 ;Get the next process in the q CMPL R0,R3 ;End of queue? BEQL 20$ ;Yes: look for next queue INCL R2 ;No: count this process BRB 10$ ;and try the next one 20$: INCL R1 ;Queue done, look for next one BRB Find_Comq ;+ --- ; Check the COMO queues for the number of processes in 'COM' state (outswap) ;- --- Ncomo: CLRL R1 ;Initialize summary word pos=0 BICL3 PRIORITY_MASK,@#SCH$GL_Comoqs,R4 ;Get masked queues Find_Comoq: ;Find a run queue with entries SUBL3 R1,#32,R0 ;Calculate #bits left (32-R1) FFS R1,R0,R4,R1 ;Look for an active queue BEQL Nwait ;None: look for procs waiting ; for resources MOVAQ @#SCH$AQ_Comoh[R1],R0 ;Got one: get its queue header MOVL R0,R3 ;Save terminating address 10$: MOVL (R0),R0 ;Get the next process in the q CMPL R0,R3 ;End of queue? BEQL 20$ ;Yes: look for next queue INCL R2 ;No: count this process BRB 10$ ;and try the next one 20$: INCL R1 ;Queue done, look for next one BRB Find_Comoq ;+ --- ; Count processes in collided page wait ;- --- Nwait: MOVAQ @#SCH$GQ_Colpgwq,R0 ;Get the collided page wait q 10$: MOVL (R0),R0 ;Get the next process in the q CMPL R0,#SCH$GQ_Colpgwq ;End of queue? BEQL Npfw ;Yes: look at page fault waits INCL R2 ;No: count this process BRB 10$ ;and try the next one ;+ --- ; Count processes in page fault wait ;- --- Npfw: MOVAQ @#SCH$GQ_PfwQ,R0 ;Get the page fault wait q 10$: MOVL (R0),R0 ;Get the next process in the q CMPL R0,#SCH$GQ_Pfwq ;End of queue? BEQL Nfpw ;Yes: look at free page waits INCL R2 ;No: count this process BRB 10$ ;and try the next one ;+ --- ; Count processes in free page wait ;- --- Nfpw: MOVAQ @#SCH$GQ_FpgwQ,R0 ;Get the free page wait q 10$: MOVL (R0),R0 ;Get the next process in the q CMPL R0,#SCH$GQ_Fpgwq ;End of queue? BEQL Count_Done ;Yes: all done INCL R2 ;No: count this process BRB 10$ ;and try the next one Count_Done: .IF NDF,VMS_V5 ENBINT .IFF UNLOCK SCHED .ENDC .IF NDF,VMS_V5 ; ; Adjust for the NULL job, which might be missing if the mask ; exclused the priority 0 queue. ; BBS #31,PRIORITY_MASK,10$ DECL R2 ; SUBTRACT THE NULL JOB 10$: ; ; Adjust for the CURrent job if the priority is high enough. ; BBS @#SCH$GB_PRI,PRIORITY_MASK,20$ INCL R2 ; ADD THE CURRENT JOB 20$: .ENDC .IF NDF,VMS_V5 MOVL G^SCH$GB_PRI,R3 .IFF ; ; Count done, now scan through each CPU. Store the LOWEST priority ; found and the number of executing CPUs. ; CLRL R3 ; Lowest priority CLRL R4 ; Number of CPUs active CLRL R1 ; Current CPU index 11$: SUBL3 R1,#32,R0 ;Calculate #bits left (32-R1) FFS R1,R0,@#SMP$GL_ACTIVE_CPUS,R1 ;Look for an active CPU BEQL 35$ 15$: MOVL g^SMP$GL_CPU_DATA[R1],R0 ; Get the CPU database BEQL 30$ ; This CPU doesn't exist INCL R4 ; Seen one more CPU CMPL CPU$L_CURPCB(R0),@#SCH$AR_NULLPCB ; Running 'the null job'? BEQL 20$ MOVZBL CPU$B_CUR_PRI(R0),-(SP) BBS (SP)+,PRIORITY_MASK,20$ ; but priority is 'masked' INCL R2 ; CPU is active. 20$: CMPB CPU$B_CUR_PRI(R0),R3 ; Store the lowest executing priority BLEQU 30$ MOVB CPU$B_CUR_PRI(R0),R3 30$: INCL R1 BRB 11$ ; Check for all 32 CPUs. 35$: .ENDC ;+ --- ; Count done, calculate the load ave. ;- --- CVTLF R2,R2 ;Float the count of processes .IF DF,VMS_V5 CVTLF R4,R4 ;Float the count of CPUs DIVF2 R4,R2 ;Load is Processes/CPUs. .ENDC ; 1 min. load ave. MULF3 R2,F11,R0 ;La=OldLa*F+NewLa*(1-F) MULF3 M1,F1,R1 ADDF3 R0,R1,M1 ; 5 min. load ave. MULF3 R2,F12,R0 MULF3 M5,F2,R1 ADDF3 R0,R1,M5 ; 15 min. load ave. MULF3 R2,F13,R0 MULF3 M15,F3,R1 ADDF3 R0,R1,M15 CMPB R3,#255 ; NULL JOB BNEQ 40$ MOVB #32,R3 ; TREAT AS PRIORITY 0 40$: SUBB3 R3,#32,R2 CVTBF R2,R2 ; 1 min. priority ave. MULF3 R2,F11,R0 ;La=OldLa*F+NewLa*(1-F) MULF3 P1,F1,R1 ADDF3 R0,R1,P1 ; 5 min. priority ave. MULF3 R2,F12,R0 MULF3 P5,F2,R1 ADDF3 R0,R1,P5 ; 15 min. priority ave. MULF3 R2,F13,R0 MULF3 P15,F3,R1 ADDF3 R0,R1,P15 ; ; Update the disk queue length information ; QUEUE_LEN: CLRL R2 ; R2 = Sun of queue lengths PUSHR #^M CLRQ R10 ; Indicate start of I/O d.b. 10$: JSB G^IOC$SCAN_IODB ; Get next DDB and UCB into R10, R11 BLBC R0,40$ ; no more, done CMPB #DC$_DISK,UCB$B_DEVCLASS(R10) ; Is this controller a disk? BEQL 20$ CLRL R10 ; Go for next controller BRB 10$ 20$: BBC #DEV$V_MNT,UCB$L_DEVCHAR(R10),10$ ; Skip if not mounted BITL #,UCB$L_DEVCHAR2(R10) ; class driver or shadow set member? BNEQ 10$ ; if so, skip CVTWL UCB$W_QLEN(R10),R0 ; Get queue length BGEQ 30$ CLRL R0 ; negative queu length is a temporary condition 30$: CMPL R0,R2 BLEQ 10$ MOVL R0,R2 BRB 10$ 40$: POPR #^M ; restore registers CVTLF R2,R2 ;Float the count ; 1 min. queue ave. MULF3 R2,F11,R0 ;La=OldLa*F+NewLa*(1-F) MULF3 Q1,F1,R1 ADDF3 R0,R1,Q1 ; 5 min. queue ave. MULF3 R2,F12,R0 MULF3 Q5,F2,R1 ADDF3 R0,R1,Q5 ; 15 min. queue ave. MULF3 R2,F13,R0 MULF3 Q15,F3,R1 ADDF3 R0,R1,Q15 RSB ;Return to clock dispatcher .SBTTL END OF DRIVER ;+ --- ; ; END OF DRIVER ; ;- --- LAV_END: ;LET DPTAB KNOW THE DRIVER SIZE ; .END