UNPKG

@cocalc/project

Version:
130 lines 4.4 kB
"use strict"; /* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.activate = void 0; /* * This little utility tames process of this project to be kind to other users. * It's inspired by and – http://and.sourceforge.net/ */ const debug_1 = __importDefault(require("debug")); const L = (0, debug_1.default)("project:autorenice"); const lodash_1 = require("lodash"); const os_1 = require("os"); const awaiting_1 = require("awaiting"); const project_info_1 = require("./project-info"); const project_setup_1 = require("./project-setup"); const INTERVAL_S = 10; // renice configuration -- the first time values must be decreasing const RENICE = (0, lodash_1.reverse)((0, lodash_1.sortBy)([ { time_s: 10 * 60, niceness: 19 }, { time_s: 5 * 60, niceness: 10 }, { time_s: 60, niceness: 4 }, ], "time_s")); class ProcessRenicer { constructor(opts) { const { verbose = false, config = "1" } = opts ?? {}; this.free_project = (0, project_setup_1.is_free_project)(); this.verbose = verbose; this.config = config; L("config", this.config); if (config == "0") return; this.project_info = (0, project_info_1.get_ProjectInfoServer)(); this.init(); this.start(); } async init() { this.project_info.start(); this.project_info.on("info", (info) => { this.update(info); }); } // got new data from the ProjectInfoServer update(info) { if (info != null) { this.processes = info.processes; this.timestamp = info.timestamp; } } // this is the main "infinite loop" async start() { if (this.verbose) L("starting main loop"); while (true) { await (0, awaiting_1.delay)(INTERVAL_S * 1000); // no data yet if (this.processes == null || this.timestamp == null) continue; // ignore outdated data if (this.timestamp < Date.now() - 60 * 1000) continue; // check processes for (const proc of Object.values(this.processes)) { // ignore the init process if (proc.pid == 1) continue; // we also skip the project process if (proc.cocalc?.type == "project") continue; this.adjust_proc(proc); } } } adjust_proc(proc) { // special case: free project processes have a low default priority 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}`); (0, os_1.setPriority)(proc.pid, new_nice); } catch (err) { L(`Error setPriority ${msg}`, err); } } } nice(stat) { // for free projects we do not bother with actual usage – just down prioritize all of them if (this.free_project) { return project_setup_1.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; function activate(opts) { if (singleton != null) { L("blocking attempt to run ProcessRenicer twice"); return; } singleton = new ProcessRenicer(opts); return singleton; } exports.activate = activate; // testing: $ ts-node autorenice.ts async function test() { const pr = activate({ verbose: true }); L("activated ProcessRenicer in test mode", pr); await (0, awaiting_1.delay)(3 * 1000); L("test done"); } if (require.main === module) { test(); } //# sourceMappingURL=autorenice.js.map