diff options
author | Juan José <soyjuanarbol@gmail.com> | 2025-04-22 15:12:30 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-22 22:12:30 +0200 |
commit | 69d2dfec148af697fa6dd45eb53816dfb4c86ecc (patch) | |
tree | bff200f38548ff6528556d537ebb0e9e37d75dba /src | |
parent | 47a5c85c4e7c63adc838d8e252fb4859e6cb743d (diff) | |
download | libuv-69d2dfec148af697fa6dd45eb53816dfb4c86ecc.tar.gz libuv-69d2dfec148af697fa6dd45eb53816dfb4c86ecc.zip |
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>
Diffstat (limited to 'src')
-rw-r--r-- | src/unix/core.c | 11 | ||||
-rw-r--r-- | src/unix/internal.h | 8 | ||||
-rw-r--r-- | src/unix/linux.c | 113 |
3 files changed, 76 insertions, 56 deletions
diff --git a/src/unix/core.c b/src/unix/core.c index 115cbe69..bd51b69b 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -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("a) == 0) + if (quota > 0 && quota < rc) + rc = quota; } #endif /* __linux__ */ diff --git a/src/unix/internal.h b/src/unix/internal.h index 5002c5fd..a1d7d436 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -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__) diff --git a/src/unix/linux.c b/src/unix/linux.c index 5fa4627d..ea3e2de0 100644 --- a/src/unix/linux.c +++ b/src/unix/linux.c @@ -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; + snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size, + cgroup_trimmed); - 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 + /* + * 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); - /* Construct the path to the cpu.weight file */ - snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size, - cgroup_trimmed); + /* Silently ignore and continue if the file does not exist */ + if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0) + goto next; - /* Read cpu.weight */ - if (uv__slurp(path, buf, sizeof(buf)) < 0) - return UV_EIO; + /* No limit, move on */ + if (strncmp(quota_buf, "max", 3) == 0) + goto next; - if (sscanf(buf, "%u", &weight) != 1) - return UV_EINVAL; + /* Read cpu.max */ + if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2) + goto next; - constraint->proportions = (double)weight / 100.0; + /* 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", "a_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); } |