Respect Linux cgroup CPU number for --threads value (#2083)
We want to respect cgroup constraints so that when we run in a container, we respect the limits set for the container rather than use the host OS's number of cores. We check both `/sys/fs/cgroup/cpuset.cpus.effective` (`cgroup v2`) and `/sys/fs/cgroup/cpuset.cpus` (`cgroup v1`) to find the number of cores available. We also check `sys/fs/cgroup/cpu.max` (v1, v2) to calculate the number of cores from the limits set in this file. The max threads value is set to the minimum of these values, and if no values were found in these files, we default to the original value of the host OS.
This commit is contained in:
parent
2eaf0149ef
commit
65c74964a9
4 changed files with 153 additions and 4 deletions
|
|
@ -6,7 +6,7 @@ Note that the only difference between `v2` and `v3` of the CodeQL Action is the
|
|||
|
||||
## [UNRELEASED]
|
||||
|
||||
No user facing changes.
|
||||
- On Linux, the maximum possible value for the `--threads` option now respects the CPU count as specified in `cgroup` files to more accurately reflect the number of available cores when running in containers.
|
||||
|
||||
## 3.23.1 - 17 Jan 2024
|
||||
|
||||
|
|
|
|||
63
lib/util.js
generated
63
lib/util.js
generated
|
|
@ -247,7 +247,18 @@ exports.getAddSnippetsFlag = getAddSnippetsFlag;
|
|||
*/
|
||||
function getThreadsFlagValue(userInput, logger) {
|
||||
let numThreads;
|
||||
const maxThreads = os.cpus().length;
|
||||
const maxThreadsCandidates = [os.cpus().length];
|
||||
if (os.platform() === "linux") {
|
||||
maxThreadsCandidates.push(...["/sys/fs/cgroup/cpuset.cpus.effective", "/sys/fs/cgroup/cpuset.cpus"]
|
||||
.map((file) => getCgroupCpuCountFromCpus(file, logger))
|
||||
.filter((count) => count !== undefined && count > 0)
|
||||
.map((count) => count));
|
||||
maxThreadsCandidates.push(...["/sys/fs/cgroup/cpu.max"]
|
||||
.map((file) => getCgroupCpuCountFromCpuMax(file, logger))
|
||||
.filter((count) => count !== undefined && count > 0)
|
||||
.map((count) => count));
|
||||
}
|
||||
const maxThreads = Math.min(...maxThreadsCandidates);
|
||||
if (userInput) {
|
||||
numThreads = Number(userInput);
|
||||
if (Number.isNaN(numThreads)) {
|
||||
|
|
@ -270,6 +281,56 @@ function getThreadsFlagValue(userInput, logger) {
|
|||
return numThreads;
|
||||
}
|
||||
exports.getThreadsFlagValue = getThreadsFlagValue;
|
||||
/**
|
||||
* Gets the number of available cores specified by the cgroup cpu.max file at the given path.
|
||||
* Format of file: two values, the limit and the duration (period). If the limit is "max" then
|
||||
* we return undefined and do not use this file to determine CPU limits.
|
||||
*/
|
||||
function getCgroupCpuCountFromCpuMax(cpuMaxFile, logger) {
|
||||
if (!fs.existsSync(cpuMaxFile)) {
|
||||
logger.debug(`While resolving threads, did not find a cgroup CPU file at ${cpuMaxFile}.`);
|
||||
return undefined;
|
||||
}
|
||||
const cpuMaxString = fs.readFileSync(cpuMaxFile, "utf-8");
|
||||
const cpuMaxStringSplit = cpuMaxString.split(" ");
|
||||
if (cpuMaxStringSplit.length !== 2) {
|
||||
logger.debug(`While resolving threads, did not use cgroup CPU file at ${cpuMaxFile} because it contained ${cpuMaxStringSplit.length} value(s) rather than the two expected.`);
|
||||
return undefined;
|
||||
}
|
||||
const cpuLimit = cpuMaxStringSplit[0];
|
||||
if (cpuLimit === "max") {
|
||||
return undefined;
|
||||
}
|
||||
const duration = cpuMaxStringSplit[1];
|
||||
const cpuCount = Math.floor(parseInt(cpuLimit) / parseInt(duration));
|
||||
logger.info(`While resolving threads, found a cgroup CPU file with ${cpuCount} CPUs in ${cpuMaxFile}.`);
|
||||
return cpuCount;
|
||||
}
|
||||
/**
|
||||
* Gets the number of available cores listed in the cgroup cpuset.cpus file at the given path.
|
||||
*/
|
||||
function getCgroupCpuCountFromCpus(cpusFile, logger) {
|
||||
if (!fs.existsSync(cpusFile)) {
|
||||
logger.debug(`While resolving threads, did not find a cgroup CPUs file at ${cpusFile}.`);
|
||||
return undefined;
|
||||
}
|
||||
let cpuCount = 0;
|
||||
// Comma-separated numbers and ranges, for eg. 0-1,3
|
||||
const cpusString = fs.readFileSync(cpusFile, "utf-8");
|
||||
for (const token of cpusString.split(",")) {
|
||||
if (!token.includes("-")) {
|
||||
// Not a range
|
||||
++cpuCount;
|
||||
}
|
||||
else {
|
||||
const cpuStartIndex = parseInt(token.split("-")[0]);
|
||||
const cpuEndIndex = parseInt(token.split("-")[1]);
|
||||
cpuCount += cpuEndIndex - cpuStartIndex + 1;
|
||||
}
|
||||
}
|
||||
logger.info(`While resolving threads, found a cgroup CPUs file with ${cpuCount} CPUs in ${cpusFile}.`);
|
||||
return cpuCount;
|
||||
}
|
||||
/**
|
||||
* Get the codeql `--threads` flag specified for the `threads` input.
|
||||
* If no value was specified, all available threads will be used.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
90
src/util.ts
90
src/util.ts
|
|
@ -354,7 +354,22 @@ export function getThreadsFlagValue(
|
|||
logger: Logger,
|
||||
): number {
|
||||
let numThreads: number;
|
||||
const maxThreads = os.cpus().length;
|
||||
const maxThreadsCandidates = [os.cpus().length];
|
||||
if (os.platform() === "linux") {
|
||||
maxThreadsCandidates.push(
|
||||
...["/sys/fs/cgroup/cpuset.cpus.effective", "/sys/fs/cgroup/cpuset.cpus"]
|
||||
.map((file) => getCgroupCpuCountFromCpus(file, logger))
|
||||
.filter((count) => count !== undefined && count > 0)
|
||||
.map((count) => count as number),
|
||||
);
|
||||
maxThreadsCandidates.push(
|
||||
...["/sys/fs/cgroup/cpu.max"]
|
||||
.map((file) => getCgroupCpuCountFromCpuMax(file, logger))
|
||||
.filter((count) => count !== undefined && count > 0)
|
||||
.map((count) => count as number),
|
||||
);
|
||||
}
|
||||
const maxThreads = Math.min(...maxThreadsCandidates);
|
||||
if (userInput) {
|
||||
numThreads = Number(userInput);
|
||||
if (Number.isNaN(numThreads)) {
|
||||
|
|
@ -380,6 +395,79 @@ export function getThreadsFlagValue(
|
|||
return numThreads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of available cores specified by the cgroup cpu.max file at the given path.
|
||||
* Format of file: two values, the limit and the duration (period). If the limit is "max" then
|
||||
* we return undefined and do not use this file to determine CPU limits.
|
||||
*/
|
||||
function getCgroupCpuCountFromCpuMax(
|
||||
cpuMaxFile: string,
|
||||
logger: Logger,
|
||||
): number | undefined {
|
||||
if (!fs.existsSync(cpuMaxFile)) {
|
||||
logger.debug(
|
||||
`While resolving threads, did not find a cgroup CPU file at ${cpuMaxFile}.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cpuMaxString = fs.readFileSync(cpuMaxFile, "utf-8");
|
||||
const cpuMaxStringSplit = cpuMaxString.split(" ");
|
||||
if (cpuMaxStringSplit.length !== 2) {
|
||||
logger.debug(
|
||||
`While resolving threads, did not use cgroup CPU file at ${cpuMaxFile} because it contained ${cpuMaxStringSplit.length} value(s) rather than the two expected.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
const cpuLimit = cpuMaxStringSplit[0];
|
||||
if (cpuLimit === "max") {
|
||||
return undefined;
|
||||
}
|
||||
const duration = cpuMaxStringSplit[1];
|
||||
const cpuCount = Math.floor(parseInt(cpuLimit) / parseInt(duration));
|
||||
|
||||
logger.info(
|
||||
`While resolving threads, found a cgroup CPU file with ${cpuCount} CPUs in ${cpuMaxFile}.`,
|
||||
);
|
||||
|
||||
return cpuCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of available cores listed in the cgroup cpuset.cpus file at the given path.
|
||||
*/
|
||||
function getCgroupCpuCountFromCpus(
|
||||
cpusFile: string,
|
||||
logger: Logger,
|
||||
): number | undefined {
|
||||
if (!fs.existsSync(cpusFile)) {
|
||||
logger.debug(
|
||||
`While resolving threads, did not find a cgroup CPUs file at ${cpusFile}.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let cpuCount = 0;
|
||||
// Comma-separated numbers and ranges, for eg. 0-1,3
|
||||
const cpusString = fs.readFileSync(cpusFile, "utf-8");
|
||||
for (const token of cpusString.split(",")) {
|
||||
if (!token.includes("-")) {
|
||||
// Not a range
|
||||
++cpuCount;
|
||||
} else {
|
||||
const cpuStartIndex = parseInt(token.split("-")[0]);
|
||||
const cpuEndIndex = parseInt(token.split("-")[1]);
|
||||
cpuCount += cpuEndIndex - cpuStartIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`While resolving threads, found a cgroup CPUs file with ${cpuCount} CPUs in ${cpusFile}.`,
|
||||
);
|
||||
|
||||
return cpuCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the codeql `--threads` flag specified for the `threads` input.
|
||||
* If no value was specified, all available threads will be used.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue