Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/project/autorenice.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6* This little utility tames process of this project to be kind to other users.7* It's inspired by and – http://and.sourceforge.net/8*/910import { delay } from "awaiting";11import { reverse, sortBy } from "lodash";12import { setPriority } from "node:os";1314import { getLogger } from "./logger";15import { ProjectInfoServer, get_ProjectInfoServer } from "./project-info";16import {17Process,18Processes,19ProjectInfo,20} from "@cocalc/util/types/project-info/types";21import { DEFAULT_FREE_PROCS_NICENESS, is_free_project } from "./project-setup";2223const L = getLogger("autorenice").debug;2425const INTERVAL_S = 10;2627// renice configuration -- the first time values must be decreasing28const RENICE = reverse(29sortBy(30[31{ time_s: 10 * 60, niceness: 19 },32{ time_s: 5 * 60, niceness: 10 },33{ time_s: 60, niceness: 4 },34],35"time_s",36),37);3839interface Opts {40verbose?: boolean;41config?: string; // TODO: make it possible to set via env var COCALC_PROJECT_AUTORENICE (also there are only harcoded values).42}4344class ProcessRenicer {45private readonly verbose: boolean;46private readonly free_project: boolean;47private readonly project_info: ProjectInfoServer;48private readonly config: string;49private timestamp?: number;50private processes?: Processes;5152constructor(opts?: Opts) {53const { verbose = false, config = "1" } = opts ?? {};54this.free_project = is_free_project();55this.verbose = verbose;56this.config = config;57L("config", this.config);58if (config == "0") return;59this.project_info = get_ProjectInfoServer();60this.init();61this.start();62}6364private async init(): Promise<void> {65this.project_info.start();66this.project_info.on("info", (info: ProjectInfo) => {67this.update(info);68});69}7071// got new data from the ProjectInfoServer72private update(info: ProjectInfo) {73if (info != null) {74this.processes = info.processes;75this.timestamp = info.timestamp;76}77}7879// this is the main "infinite loop"80private async start(): Promise<void> {81if (this.verbose) L("starting main loop");82while (true) {83await delay(INTERVAL_S * 1000);8485// no data yet86if (this.processes == null || this.timestamp == null) continue;8788// ignore outdated data89if (this.timestamp < Date.now() - 60 * 1000) continue;9091// check processes92for (const proc of Object.values(this.processes)) {93// ignore the init process94if (proc.pid == 1) continue;9596// we also skip the project process97if (proc.cocalc?.type == "project") continue;9899this.adjust_proc(proc);100}101}102}103104private adjust_proc(proc: Process) {105// special case: free project processes have a low default priority106const old_nice = proc.stat.nice;107const new_nice = this.nice(proc.stat);108if (old_nice < new_nice) {109const msg = `${proc.pid} from ${old_nice} to ${new_nice}`;110try {111L(`setPriority ${msg}`);112setPriority(proc.pid, new_nice);113} catch (err) {114L(`Error setPriority ${msg}`, err);115}116}117}118119private nice(stat) {120// for free projects we do not bother with actual usage – just down prioritize all of them121if (this.free_project) {122return DEFAULT_FREE_PROCS_NICENESS;123}124125const { utime, stime, cutime, cstime } = stat;126const self = utime + stime;127const child = cutime + cstime;128129for (const { time_s, niceness } of RENICE) {130if (self > time_s || child > time_s) {131return niceness;132}133}134return 0;135}136}137138let singleton: ProcessRenicer | undefined = undefined;139140export function activate(opts?: Opts) {141if (singleton != null) {142L("blocking attempt to run ProcessRenicer twice");143return;144}145singleton = new ProcessRenicer(opts);146return singleton;147}148149// testing: $ ts-node autorenice.ts150async function test() {151const pr = activate({ verbose: true });152L("activated ProcessRenicer in test mode", pr);153await delay(3 * 1000);154L("test done");155}156157if (require.main === module) {158test();159}160161162