import debug from "debug" ; const L = debug ( "project:autorenice" ) ; import { reverse , sortBy } from "lodash" ; import { setPriority } from "os" ; import { delay } from "awaiting" ; import { ProjectInfoServer , get_ProjectInfoServer } from "./project-info" ; import { ProjectInfo , Processes , Process } from "./project-info/types" ; import { is_free_project , DEFAULT_FREE_PROCS_NICENESS } from "./project-setup" ; const INTERVAL_S = 10 ; const RENICE = reverse ( sortBy ( [ { time_s : 10 * 60 , niceness : 19 } , { time_s : 5 * 60 , niceness : 10 } , { time_s : 60 , niceness : 4 } , ] , "time_s" ) ) ; interface Opts { verbose ? : boolean ; config ? : string ; } class ProcessRenicer { private readonly verbose : boolean ; private readonly free_project : boolean ; private readonly project_info : ProjectInfoServer ; private readonly config : string ; private timestamp ? : number ; private processes ? : Processes ; constructor ( opts ? ) { const { verbose = false , config = "1" } = opts ?? { } ; this . free_project = is_free_project ( ) ; this . verbose = verbose ; this . config = config ; L ( "config" , this . config ) ; if ( config == "0" ) return ; this . project_info = get_ProjectInfoServer ( ) ; this . init ( ) ; this . start ( ) ; } private async init ( ) { this . project_info . start ( ) ; this . project_info . on ( "info" , ( info ) => { this . update ( info ) ; } ) ; } private update ( info ) { if ( info != null ) { this . processes = info . processes ; this . timestamp = info . timestamp ; } } private async start ( ) { if ( this . verbose ) L ( "starting main loop" ) ; while ( true ) { await delay ( INTERVAL_S * 1000 ) ; if ( this . processes == null || this . timestamp == null ) continue ; if ( this . timestamp < Date . now ( ) - 60 * 1000 ) continue ; for ( const proc of Object . values ( this . processes ) ) { if ( proc . pid == 1 ) continue ; if ( proc . cocalc ?. type == "project" ) continue ; this . adjust_proc ( proc ) ; } } } private adjust_proc ( proc ) { const old_nice = proc . stat . nice ; const new_nice = this . nice ( proc . stat ) ; if ( old_nice < new_nice ) { const msg = `${ proc . pid } from ${ old_nice } to ${ new_nice }` ; try { L ( `setPriority ${ msg }` ) ; setPriority ( proc . pid , new_nice ) ; } catch ( err ) { L ( `Error setPriority ${ msg }` , err ) ; } } } private nice ( stat ) { if ( this . free_project ) { return DEFAULT_FREE_PROCS_NICENESS ; } const { utime , stime , cutime , cstime } = stat ; const self = utime + stime ; const child = cutime + cstime ; for ( const { time_s , niceness } of RENICE ) { if ( self > time_s || child > time_s ) { return niceness ; } } return 0 ; } } let singleton = undefined ; export function activate ( opts ? ) { if ( singleton != null ) { L ( "blocking attempt to run ProcessRenicer twice" ) ; return ; } singleton = new ProcessRenicer ( opts ) ; return singleton ; } async function test ( ) { const pr = activate ( { verbose : true } ) ; L ( "activated ProcessRenicer in test mode" , pr ) ; await delay ( 3 * 1000 ) ; L ( "test done" ) ; } if ( require . main === module ) { test ( ) ; }