linux: align CPU quota calculation with Rust (#4746)

Align CPU quota calculation with Rust's cgroup heuristics.

Fixes: https://github.com/libuv/libuv/issues/4740
Signed-off-by: Juan José Arboleda <soyjuanarbol@gmail.com>
This commit is contained in:
Juan José 2025-04-22 15:12:30 -05:00 committed by GitHub
parent 47a5c85c4e
commit 69d2dfec14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 57 deletions

View File

@ -2046,14 +2046,11 @@ unsigned int uv_available_parallelism(void) {
#ifdef __linux__
{
double rc_with_cgroup;
uv__cpu_constraint c = {0, 0, 0.0};
long long quota = 0;
if (uv__get_constrained_cpu(&c) == 0 && c.period_length > 0) {
rc_with_cgroup = (double)c.quota_per_period / c.period_length * c.proportions;
if (rc_with_cgroup < rc)
rc = (long)rc_with_cgroup; /* Casting is safe since rc_with_cgroup < rc < LONG_MAX */
}
if (uv__get_constrained_cpu(&quota) == 0)
if (quota > 0 && quota < rc)
rc = quota;
}
#endif /* __linux__ */

View File

@ -494,13 +494,7 @@ uv__fs_copy_file_range(int fd_in,
#endif
#ifdef __linux__
typedef struct {
long long quota_per_period;
long long period_length;
double proportions;
} uv__cpu_constraint;
int uv__get_constrained_cpu(uv__cpu_constraint* constraint);
int uv__get_constrained_cpu(long long* quota);
#endif
#if defined(__sun) && !defined(__illumos__)

View File

@ -2305,49 +2305,83 @@ uint64_t uv_get_available_memory(void) {
static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
uv__cpu_constraint* constraint) {
char path[256];
char buf[1024];
unsigned int weight;
int cgroup_size;
long long* quota) {
static const char cgroup_mount[] = "/sys/fs/cgroup";
const char* cgroup_trimmed;
char buf[1024];
char full_path[256];
char path[256];
char quota_buf[16];
char* last_slash;
int cgroup_size;
long long limit;
long long min_quota;
long long period;
if (strncmp(cgroup, "0::/", 4) != 0)
return UV_EINVAL;
/* Trim ending \n by replacing it with a 0 */
cgroup_trimmed = cgroup + sizeof("0::/") - 1; /* Skip the prefix "0::/" */
cgroup_size = (int)strcspn(cgroup_trimmed, "\n"); /* Find the first slash */
cgroup_size = (int)strcspn(cgroup_trimmed, "\n"); /* Find the first \n */
min_quota = LLONG_MAX;
/* Construct the path to the cpu.max file */
snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
cgroup_trimmed);
/* Construct the path to the cpu.max files */
snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
cgroup_size, cgroup_trimmed);
/* Read cpu.max */
/* Read controllers, if not exists, not really a cgroup */
if (uv__slurp(path, buf, sizeof(buf)) < 0)
return UV_EIO;
if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
return UV_EINVAL;
if (strncmp(quota_buf, "max", 3) == 0)
constraint->quota_per_period = LLONG_MAX;
else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
return UV_EINVAL; // conversion failed
/* Construct the path to the cpu.weight file */
snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size,
snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
cgroup_trimmed);
/* Read cpu.weight */
if (uv__slurp(path, buf, sizeof(buf)) < 0)
return UV_EIO;
/*
* Traverse up the cgroup v2 hierarchy, starting from the current cgroup path.
* At each level, attempt to read the "cpu.max" file, which defines the CPU
* quota and period.
*
* This reflects how Linux applies cgroup limits hierarchically.
*
* e.g: given a path like /sys/fs/cgroup/foo/bar/baz, we check:
* - /sys/fs/cgroup/foo/bar/baz/cpu.max
* - /sys/fs/cgroup/foo/bar/cpu.max
* - /sys/fs/cgroup/foo/cpu.max
* - /sys/fs/cgroup/cpu.max
*/
while (strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
if (sscanf(buf, "%u", &weight) != 1)
return UV_EINVAL;
/* Silently ignore and continue if the file does not exist */
if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
goto next;
constraint->proportions = (double)weight / 100.0;
/* No limit, move on */
if (strncmp(quota_buf, "max", 3) == 0)
goto next;
/* Read cpu.max */
if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2)
goto next;
/* Can't divide by 0 */
if (period == 0)
goto next;
*quota = limit / period;
if (*quota < min_quota)
min_quota = *quota;
next:
/* Move up one level in the cgroup hierarchy by trimming the last path.
* The loop ends once we reach the cgroup root mount point.
*/
last_slash = strrchr(path, '/');
if (last_slash == NULL || strcmp(path, cgroup_mount) == 0)
break;
*last_slash = '\0';
}
return 0;
}
@ -2368,12 +2402,13 @@ static char* uv__cgroup1_find_cpu_controller(const char* cgroup,
}
static int uv__get_cgroupv1_constrained_cpu(const char* cgroup,
uv__cpu_constraint* constraint) {
long long* quota) {
char path[256];
char buf[1024];
unsigned int shares;
int cgroup_size;
char* cgroup_cpu;
long long period_length;
long long quota_per_period;
cgroup_cpu = uv__cgroup1_find_cpu_controller(cgroup, &cgroup_size);
@ -2384,10 +2419,11 @@ static int uv__get_cgroupv1_constrained_cpu(const char* cgroup,
snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.cfs_quota_us",
cgroup_size, cgroup_cpu);
/* Read cpu.cfs_quota_us */
if (uv__slurp(path, buf, sizeof(buf)) < 0)
return UV_EIO;
if (sscanf(buf, "%lld", &constraint->quota_per_period) != 1)
if (sscanf(buf, "%lld", &quota_per_period) != 1)
return UV_EINVAL;
/* Construct the path to the cpu.cfs_period_us file */
@ -2398,26 +2434,19 @@ static int uv__get_cgroupv1_constrained_cpu(const char* cgroup,
if (uv__slurp(path, buf, sizeof(buf)) < 0)
return UV_EIO;
if (sscanf(buf, "%lld", &constraint->period_length) != 1)
if (sscanf(buf, "%lld", &period_length) != 1)
return UV_EINVAL;
/* Construct the path to the cpu.shares file */
snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.shares", cgroup_size,
cgroup_cpu);
/* Read cpu.shares */
if (uv__slurp(path, buf, sizeof(buf)) < 0)
return UV_EIO;
if (sscanf(buf, "%u", &shares) != 1)
/* Can't divide by 0 */
if (period_length == 0)
return UV_EINVAL;
constraint->proportions = (double)shares / 1024.0;
*quota = quota_per_period / period_length;
return 0;
}
int uv__get_constrained_cpu(uv__cpu_constraint* constraint) {
int uv__get_constrained_cpu(long long* quota) {
char cgroup[1024];
/* Read the cgroup from /proc/self/cgroup */
@ -2428,9 +2457,9 @@ int uv__get_constrained_cpu(uv__cpu_constraint* constraint) {
* The entry for cgroup v2 is always in the format "0::$PATH"
* see https://docs.kernel.org/admin-guide/cgroup-v2.html */
if (strncmp(cgroup, "0::/", 4) == 0)
return uv__get_cgroupv2_constrained_cpu(cgroup, constraint);
return uv__get_cgroupv2_constrained_cpu(cgroup, quota);
else
return uv__get_cgroupv1_constrained_cpu(cgroup, constraint);
return uv__get_cgroupv1_constrained_cpu(cgroup, quota);
}