@cocalc/project
Version:
CoCalc: project daemon
130 lines • 4.4 kB
JavaScript
;
/*
* 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