From:	CRDGW2::CRDGW2::MRGATE::"SMTP::CRVAX.SRI.COM::RELAY-INFO-VAX"  4-DEC-1989 17:05
To:	MRGATE::"ARISIA::EVERHART"
Subj:	Re: Control of the interactive usage of the cpu

Received: From UCBVAX.BERKELEY.EDU by GIZMO.SRI.COM with TCP; Mon,  4 DEC 89 12:04:44 PDT
Received: by ucbvax.Berkeley.EDU (5.61/1.40)
	id AA26293; Mon, 4 Dec 89 11:30:19 -0800
Received: from USENET by ucbvax.Berkeley.EDU with netnews
	for info-vax@kl.sri.com (info-vax@kl.sri.com)
	(contact usenet@ucbvax.Berkeley.EDU if you have questions)
Date: 4 Dec 89 11:02:06 GMT
From: zaphod.mps.ohio-state.edu!samsung!munnari.oz.au!comp.vuw.ac.nz!windy!gpwd!gpwrdcs@tut.cis.ohio-state.edu  (Don Stokes, GPO)
Organization: Government Printing Office, Wellington, New Zealand
Subject: Re: Control of the interactive usage of the cpu
Message-Id: <837@gp.govt.nz>
References: <445@obs.unige.ch>
Sender: info-vax-request@kl.sri.com
To: info-vax@kl.sri.com

In article <445@obs.unige.ch>, pfenniger@obs.unige.ch writes:
> Is there a way to lower automatically the priority of a process after 
> some conditions are met, as the abnormal high usage of the cpu by an 
> interactive session? This would encourage some users to use batch jobs...
> 
> I would appreciate any suggestion. 

PRIMGR follows.  See the comments at the top to see how it works - an 
understanging of what makes it tick is helpful in these things...

The approach used works on our site - we put a lot of thought into how 
the decision to move priorities should be made.  I'll leave it as an 
exercise for the reader to figure out how the scheme was worked out.

Don Stokes  ZL2TNM    /  /                               vuwcomp!windy!gpwd!don
Systems Programmer   /GP/ Government Printing Office     PSI%0530147000028::DON
__(and_Postmaster)__/  /__Wellington__New_Zealand________don@gp.govt.nz________
In technology, anything is possible, if you don't know it isn't available yet. 


--------------------------------cut-here---------------------------------
                .title PRIMGR V1.1 - Interactive Priority Monitor/Manager
                .ident /PRIMGR V1.1/
                .sbttl Description

;++
;
; Program:      Priority Monitor V1.1
; Author:       Don Stokes, Goverment Printing Office, Wellington, NZ
;               don@gp.govt.nz
; Date:         15-Aug-1989
;
; Description:
;       The purpose of the program is to maintain response for interactive
;       jobs, while ensuring that heavy processing does not interfere with
;       production processing.  To achieve this, light processing in local
;       event flag wait states are elevated in priority above the normal base.
;       After a small amount of processing time has been used, the process is
;       reduced to normal interactive priority.  This means that a process
;       using very little CPU gets the processor for as much time as it needs
;       ahead of heavier processing. 
;
;       Processes which are CPU bound, ie are doing very little I/O while 
;       remaining computable are reduced to priority 3 to share the cpu with 
;       batch jobs.
;
;       The algorithm used is as follows: 
;       (deltacpu = cpu time used since last pass, state = scheduler state, 
;       com_passes = no of passes since process was not computable,
;       cpulim = cpu threshold, low_passes = passes since change to
;       priority 3, low_pass_min = minimum pri. 3 passes)
;
;                 low_passes >= low_pass_min    state = wait
;                    deltacpu < cpulim34     deltacpu < cpulim45
;                            -->                     -->
;            Priority 3              Priority 4              Priority 5
;                            <--                     <--
;                 com_passes >= com_limit    deltacpu > cpulim45
;                    deltacpu < cpulim34
;
;       Note that priorities only move one step at at time.  Processes 
;       executing at abnormal priorities (<3 or >5) are ignored.
;
;       Only interactive processes are affected.
;
;--





                .page
                .sbttl Modification history

;++
;
; Modifications:
;_When_____Who__Version_Description_____________________________________________
; 26/10/89/dcs  V1.1    Added low-passes limit to prevent oscillations.
;                       Previously, if two processes were computable, one
;                       would tend to execute at priority 4, locking the
;                       other out at priority 3.  On the next pass, because
;                       the second had no CPU it would be raised to priority
;                       4, while the other went to 3.  The fix meant that 
;                       a priority has to spend low_pass_min (currently 4)
;                       passes at priority 3 before being eligible for raising
;                       to priority 4 (unless it is in a wait state).  
;                       com_limit has been raised to prevent processes from 
;                       going to priority 3 too readily, as this mod will 
;                       result in a longer priority 3 burst for processes
;                       "accidentally" lowered.
;                       Also fixed minor bug where processes with 0 in the
;                       MASTER_PID field were processed incorrectly (but
;                       harmlessly - appeared as funnies when PRIMGR is run
;                       interactively with debugging turned on). SWAPPER and 
;                       NULL exhibit this trait.
;
;--




                .page
                .sbttl Assembly time parameters

pass_time       = 5     ; 5 seconds between passes 
cpu_limit_34    = 20    ; 4% cpu threshold for priority 3 <-> 4 transition
cpu_limit_45    = 50    ; 10% cpu threshold for priority 4 <-> 5 transition
com_limit       = 6     ; No. consecutive COM states seen to drop priority to 3
low_pass_min    = 6     ; No. of passes pri. 3 process must stay at priority 3
log_changes     = 0     ; Set to one to log each priority change
debug           = 0     ; Set to one to display debugging info
disable_change  = 0     ; Set to one to disable changing of priority





                .page
                .sbttl Macros

                .library "SYS$LIBRARY:LIB"      ; $STATEDEF lives here.
                $SSDEF
                $JPIDEF
                $DSCDEF
                $STATEDEF
                $FSCNDEF
                $OPCDEF

;
;       STATUS macro - do status check on specified condition code, beanch to
;       error report/restart routine if error detected.
;       eg:     STATUS r0
;
                .macro status condition, ?L1
                blbs condition, L1
                movl condition, R7
                brw crash
L1:             .endm status

;
;       FATAL macro - do status check on specified condition code, abort if
;       error detected.  Used in error report routine.
;       eg:     FATAL r0
;
                .macro fatal condition, ?L1
                blbs condition, L1
                $EXIT_S condition
L1:             .endm fatal

;
;       CALL macro - call a subroutine, pass parameters on stack
;       eg:     CALL LIB$SET_SYMBOL, sym_filename, filename_d
;
                .macro call routine,                                    -
                            p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,         -
                            p12,p13,p14,p15,p16,p17,p18,p19,p20
                .narg call.argc
                call.argc = call.argc - 1
                call.argn = 20
                .irp call.argv, <p20,p19,p18,p17,p16,p15,p14,p13,p12,   -
                                 p11,p10,p9,p8,p7,p6,p5,p4,p3,p2,p1>
                        .if less_equal call.argn - call.argc
                                pushal call.argv
                        .endc
                        call.argn = call.argn - 1
                .endr
                calls #call.argc, G^routine
                .endm

;
;       ITEM macro - create itemlist entry.
;       eg ITEM item, code, buflen, address
;
                .macro item code, buflen, address, lenadr
                .word buflen
                .word code
                .long address
                .if b lenadr 
                        .long 0
                .endc
                .if nb lenadr
                        .long lenadr
                .endc
                .endm

;
;       DESC macro - create string descriptor
;       eg DESCRIPTOR string length
;
                .macro descriptor addr, len
                .word len
                .byte DSC$K_DTYPE_T
                .byte DSC$K_CLASS_S
                .long addr
                .endm





                .page
                .sbttl Data
                .psect data,wrt,noexe,noshr

;
;       Offsets to elements within old process array
;
oldprc_pid      =  0    ; long
oldprc_cputim   =  4    ; long
oldprc_coms     =  8    ; long
oldprc_low_pass = 12    ; long
oldprc_size     = 16    ; Size of old process element - must be power of 2
oldprc_shift    =  4    ; 2^oldprc_shift = oldprc_size
oldprcbuff:     .blkb oldprc_size * 1024

;
;       $GETJPI item lists and buffers
;
pid:            .long -1
item_list:      item JPI$_PROC_INDEX,   4, jpi_index
                item JPI$_PID,          4, jpi_pid
                item JPI$_PRIB,         4, jpi_prib
                item JPI$_MODE,         4, jpi_mode
                item JPI$_STATE,        4, jpi_state
                item JPI$_CPUTIM,       4, jpi_cputim
                item JPI$_MASTER_PID,   4, jpi_master_pid
                .if ne log_changes
                        item JPI$_USERNAME,  12, jpi_username
                        item JPI$_IMAGNAME, 255, jpi_imagname, jpi_imagname_d
                .endc
                item 0,0,0

item_list_1:    item JPI$_MODE,         4, jpi_mode
                item 0,0,0

jpi_index:      .blkl
jpi_pid:        .blkl
jpi_prib:       .blkl
jpi_mode:       .blkl
jpi_state:      .blkl
jpi_cputim:     .blkl
jpi_master_pid: .blkl
process_state:  .blkb

iosb:           .blkw 4

;
;       Other storage
;
delta_cpu:      .blkl

state_table:    .byte 0[16]
computable      = 1
wait            = 2

time_fao:       .ascid "0 00:00:!2ZL.00"
time_buff:      descriptor 1$, 13
1$:             .blkb 13
wait_time:      .blkq

;
;       Data for error logging routine
;
getmsg_text:    descriptor 1$, 80
1$:             .blkb 80
sndopr_fao:     .ascid "%PRIMGR-W-ERROR, Priority manager reported error, " -
                       "continuing !/!AS"
sndopr_text_d:  descriptor sndopr_text, 160
sndopr_msg:     descriptor 1$, 168
1$:             .byte OPC$_RQ_RQST
                .word OPC$M_NM_CENTRL
                .byte 0
                .long 0
sndopr_text:    .blkb 160
cr_time_buff:   .ascid "0 00:00:30.00"
cr_wait_time:   .blkq


;
;       Data for logging routine if used
;
                .if ne log_changes
setpri_fao:             .ascid "!23AS !12AS PID: !XL !16AS Pri: !1UL --> !1UL"
setpri_out:             descriptor 1$, 80
1$:                     .blkb 80
time_string:            descriptor 1$, 23
1$:                     .blkb 23
image_string:           descriptor 1$, 16
1$:                     .blkb 16
jpi_username:           .blkb 12
jpi_username_d:         descriptor jpi_username, 12
jpi_imagname:           .blkb 255
jpi_imagname_d:         descriptor jpi_imagname, 255
filescan_item:          .blkw
                        .word FSCN$_NAME
                        .blkl
                        .long 0
                .endc

;
; Data for debug routines if used
;
                .if ne debug
info_fao:               .ascid "!12AS !XL !XL !5UL"
info_out:               descriptor 1$, 36
1$:                     .blkb 36
oddprimsg:              .ascid "Strange interactive priority"
newprocmsg:             .ascid "New process logged"
endpassmsg:             .ascid "End of pass"
                .endc






                .page
                .sbttl Code

                .psect code,nowrt,exe,shr
                .entry primgr, ^M<>

;
; Set up states table.  Values are initialised at assembly time to zero,
; so just set the values for a "computable" job and "waiting" job.
; Processes in the LEF, CEF and HIB states are considered "wait" states, worth
; giving a priority boost to get them underway when they become computable.
; Processes in the CUR, COM and PFW states are considered as processing, and
; are candidates for priority reduction.
;
restart:
                movb #wait, state_table+SCH$C_LEF
                movb #wait, state_table+SCH$C_HIB
                movb #wait, state_table+SCH$C_CEF
                movb #computable, state_table+SCH$C_CUR
                movb #computable, state_table+SCH$C_COM
                movb #computable, state_table+SCH$C_PFW

;
; Convert time value in seconds to text time format
; Convert text time to binary time
;
                $FAO_S ctrstr=time_fao, outbuf=time_buff, -
                        p1=#pass_time
                status R0
                $BINTIM_S timbuf=time_buff, timadr=wait_time
                status R0

;
; Schedule regular wakeups for $HIBER call at bottom of outer loop.
;
                $SCHDWK_S daytim=wait_time, reptim=wait_time
                status R0

;
; Top of outer loop: Move -1 to pid value, so that a new wildcard operation 
; is started each time. 
;
pass_loop:      movl #-1, pid

;
; Get process information
; Finish pass if status is SS$_NOMOREPROC
; Try again if suspended.
;
getjpi_loop:    $GETJPIW_S pidadr=pid, itmlst=item_list, iosb=iosb
                cmpl R0, #SS$_SUSPENDED
                beql getjpi_loop
                cmpl R0, #SS$_NOMOREPROC
                bneq 1$
                brw end_of_pass
1$:             status R0
                status iosb

;
; If the pid and master pid don't match (or master pid = 0 for odd-balls
; like NULL & SWAPPER), then this is a subprocess, so extract the mode 
; of the master process, as SPAWN/LIB$SPAWN creates non-interactive 
; processes if the input is other than the keyboard.
;
                cmpl jpi_pid, jpi_master_pid
                beql 6$
                cmpl jpi_master_pid, #0
                beql 6$

                $GETJPIW_S pidadr=jpi_master_pid, itmlst=item_list_1, iosb=iosb
                cmpl R0, #SS$_SUSPENDED
                beql 7$
                status R0
                status iosb
6$:

;
; If this is not an interactive process, then don't process it.
;
                cmpl jpi_mode, #JPI$K_INTERACTIVE
                beql 8$
7$:             brw getjpi_loop
8$:

;
; Debug code - output useful info to screen
;
                .if ne debug
                        $FAO_S ctrstr=info_fao, outbuf=info_out, -
                                p1=#jpi_username_d,p2=jpi_pid,   -
                                p3=jpi_index,p4=jpi_prib
                        status R0
                        call LIB$PUT_OUTPUT, info_out
                        status R0
                .endc

;
; Check if process state is computable, waiting, or irrelevant.
;
                movl jpi_state, R5
                movb state_table(R5), process_state

;
; Calculate base address in R4 for the old processes table entry specified
; by index.
;
                ashl #oldprc_shift, jpi_index, R4
                addl2 #oldprcbuff, R4

;
; Check if the PID for this process matches the corresponding entry in 
; the old processes table.  If not, then the process is a new process, so
; copy data to the array element.
;
                cmpl jpi_pid, oldprc_pid(R4)
                beql 2$
                .if ne debug
                        call LIB$PUT_OUTPUT, newprocmsg
                        status R0
                .endc
                movl #0, oldprc_coms(R4)
                brw end_getjpi
2$:

;
; If process is computable, then increment computables count in preparation
; for a possible move to priority 3.  Otherwise, zero the counter for this
; process.
;
                cmpb process_state, #computable
                beql 3$
                movl #0, oldprc_coms(R4)
                brb 4$
3$:             incl oldprc_coms(R4)
4$:

;
; Calculate delta value for CPU count.  
;
                subl3 oldprc_cputim(R4), jpi_cputim, delta_cpu

;
; Branch to appropriate processing routine for priority 3, 4 or 5.
; If process is not one of these, then drop thru.
;
                casel jpi_prib, #3, #2
5$:             .word check_pri_3 - 5$
                .word check_pri_4 - 5$
                .word check_pri_5 - 5$

;
; We drop through to here if the priority is abnormal, ie < 3 or > 5.
;
                .if ne debug
                        call LIB$PUT_OUTPUT, oddprimsg
                        status R0
                .endc
                brw end_getjpi

;
; Priority 3: Change to priority 4 if cpu usage is below threshold.
; and job has been at priority 3 for more than low_pass_min passes.
; Only check if process is still not waiting.
;
check_pri_3:    cmpb process_state, #wait
                beql 1$

                incl oldprc_low_pass(R4)
                cmpl oldprc_low_pass(R4), #low_pass_min
                blss 2$

1$:             cmpl delta_cpu, #cpu_limit_34
                bgtr 2$

                movl #4, R6
                brw set_priority

2$:             brw end_getjpi

;
; Priority 4: If in a local event flag wait (ie waiting for I/O)
; and delta CPU is less than cpu_limit then crank process priority up to 5.
; Otherwise check if a drop is in order...
;
check_pri_4:    cmpb process_state, #wait
                bneq 1$
                cmpl delta_cpu, #cpu_limit_45
                bgtr 1$

                movl #5, R6
                brw set_priority
1$:

;
; If this process has been computable for the last <com_limit> passes,
; and has used some CPU, then drop the priority down to 3.
;
                cmpl oldprc_coms(R4), #com_limit
                blss 2$
                cmpl delta_cpu, #cpu_limit_34
                blss 2$

                movl #3, R6
                movl #0, oldprc_low_pass(R4)
                brw set_priority

2$:             brw end_getjpi

;
; Priority 5: If delta CPU is greated than the threshold, then drop priority
; back to 4.
;
check_pri_5:    cmpl delta_cpu, #cpu_limit_45
                blss 1$

                movl #4, R6
                brw set_priority

1$:             brw end_getjpi

;
; Change the process priority.  This is only used if a priority change
; is required.
; New priority is in R6.
;
set_priority:   .if eq disable_change
                        $SETPRI_S pidadr=jpi_pid, pri=R6
                        status R0
                .endc

                .if ne log_changes
                        $FILESCAN_S srcstr=jpi_imagname_d,      -
                                valuelst=filescan_item
                        status R0

                        $ASCTIM_S timbuf=time_string
                        status R0

                        $FAO_S ctrstr=setpri_fao, outbuf=setpri_out, -
                                p1=#time_string, p2=#jpi_username_d, -
                                p3=jpi_pid, p4=#filescan_item,       -
                                p5=jpi_prib, p6=R6
                        status R0
                        call LIB$PUT_OUTPUT, setpri_out
                        status R0
                .endc
;
; Save JPI info for next pass.  (Come here if new process)
;
end_getjpi:     movl jpi_pid, oldprc_pid(R4)
                movl jpi_cputim, oldprc_cputim(R4)

                brw getjpi_loop

;
; Bottom of main loop.  Come here after all $GETJPI passes have completed.
; Go to sleep now, and hope that we get woken up by the $SCHDWK we did all 
; those milliseconds ago...
;
end_of_pass:
                .if ne debug
                        call LIB$PUT_OUTPUT, endpassmsg
                        status R0
                .endc

                $HIBER_S
                status R0

                brw pass_loop

;
; Come here on a fatal error.  Report the error to the operator, wait a bit,
; and restart.  Error code is passed in R7 (placed there by the STATUS macro).
; Get text format of crash message.
; Format it and and send it to the operator.
; Cancel the regular wakeup, and wait for 30 secs.
; Restart.
;
crash:
                movw #80, getmsg_text
                movw #160, sndopr_text_d
                $GETMSG_S msgid=R7, msglen=getmsg_text, bufadr=getmsg_text
                fatal R0
                $FAO_S ctrstr=sndopr_fao, outlen=sndopr_text_d, -
                        outbuf=sndopr_text_d, p1=#getmsg_text
                fatal R0
                addw3 sndopr_text_d, #8, sndopr_msg
                $SNDOPR_S msgbuf=sndopr_msg
                fatal R0
                $CANWAK_S
                fatal R0
                $BINTIM_S timbuf=cr_time_buff, timadr=cr_wait_time
                fatal R0
                $SCHDWK_S daytim=cr_wait_time
                fatal R0
                $HIBER_S
                fatal R0
                brw restart

                .end PRIMGR

