Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import { readFile } from 'fs/promises';
2import os from 'os';
3
4/**
5 * Get the CFS-imposed limit (if any) for the "number of cores" that the process
6 * is allowed to use. This may not be a whole number.
7 *
8 * "Number of cores" is in quotes because this is actually a measure of the
9 * total amount of time that the process is able to use in a CFS period. The
10 * work is not actually constrained to certain physical cores in the machine (by
11 * default), which explains why this value can be fractional.
12 *
13 * For context on CFS, including why CFS without core pinning can be problematic,
14 * see {@link https://danluu.com/cgroup-throttling/}
15 * and {@link https://www.uber.com/en-UY/blog/avoiding-cpu-throttling-in-a-containerized-environment/}
16 */
17async function getCFSLimit() {
18 try {
19 const [quota, period] = await Promise.all([
20 readFile('/sys/fs/cgroup/cpu/cpu.cfs_quota_us', { encoding: 'utf-8' }),
21 readFile('/sys/fs/cgroup/cpu/cpu.cfs_period_us', { encoding: 'utf-8' }),
22 ]);
23
24 const cores = Number(quota) / Number(period);
25 return Number.isNaN(cores) ? undefined : cores;
26 } catch (err) {
27 return undefined;
28 }
29}
30
31// getCFSLimit is super slow and the result is unlikely to change,
32// so we don't wanna call it every time someone looks up the available core count.
33const cfsLimitAtStartup = await getCFSLimit();
34
35/**
36 * @returns {number} The number of cores available to the process, which will
37 * either be the number of cores on the machine (or virtual machine, or
38 * container, depending on where we're running and how it's configured) or, if a
39 * CFS limit is set, the number of "cores" that the Node process is allowed to
40 * use, which could be fractional. See {@link getCFSLimit}
41 */
42export function getUsableCoreCount() {
43 return cfsLimitAtStartup ?? os.cpus().length;
44}