Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport final version of available memory PR #32

Merged
merged 1 commit into from
Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 127 additions & 59 deletions src/unix/linux-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -789,99 +789,167 @@ static uint64_t uv__read_uint64(const char* filename) {
}


/* This might return 0 if there was a problem getting the memory limit from
* cgroups. This is OK because a return value of 0 signifies that the memory
* limit is unknown.
*/
static uint64_t uv__get_constrained_memory_fallback(void) {
uint64_t limit;
/* Given a buffer with the contents of a cgroup1 /proc/self/cgroups,
* finds the location and length of the memory controller mount path.
* This disregards the leading / for easy concatenation of paths.
* Returns NULL if the memory controller wasn't found. */
static char* uv__cgroup1_find_memory_controller(char buf[static 1024],
int* n) {
char* p;

limit = uv__read_uint64("/sys/fs/cgroup/memory/memory.limit_in_bytes");
if (limit == LONG_MAX / 4096 * 4096)
return 0;
/* Seek to the memory controller line. */
p = strchr(buf, ':');
while (p != NULL && strncmp(p, ":memory:", 8)) {
p = strchr(p, '\n');
if (p != NULL)
p = strchr(p, ':');
}

return limit;
}
if (p != NULL) {
/* Determine the length of the mount path. */
p = p + strlen(":memory:/");
*n = (int) strcspn(p, "\n");
}

return p;
}

uint64_t uv_get_constrained_memory(void) {
static void uv__get_cgroup1_memory_limits(char buf[static 1024], uint64_t* high,
uint64_t* max) {
char filename[4097];
char buf[1024];
uint64_t limit;
uint64_t high;
uint64_t max;
char* p;
int n;

if (uv__slurp("/proc/self/cgroup", buf, sizeof(buf)))
return uv__get_constrained_memory_fallback();
/* Find out where the controller is mounted. */
p = uv__cgroup1_find_memory_controller(buf, &n);
if (p != NULL) {
snprintf(filename, sizeof(filename),
"/sys/fs/cgroup/memory/%.*s/memory.soft_limit_in_bytes", n, p);
*high = uv__read_uint64(filename);

/* In the case of cgroupv2, we'll only have a single entry. */
if (memcmp(buf, "0::/", 4))
return uv__get_constrained_memory_fallback();
snprintf(filename, sizeof(filename),
"/sys/fs/cgroup/memory/%.*s/memory.limit_in_bytes", n, p);
*max = uv__read_uint64(filename);

p = strchr(buf, '\n');
if (p != NULL)
*p = '\0';
/* If the controller wasn't mounted, the reads above will have failed,
* as indicated by uv__read_uint64 returning 0.
*/
if (*high != 0 && *max != 0)
return;
}

p = buf + 4;
/* Fall back to the limits of the global memory controller. */
*high = uv__read_uint64("/sys/fs/cgroup/memory/memory.soft_limit_in_bytes");
*max = uv__read_uint64("/sys/fs/cgroup/memory/memory.limit_in_bytes");
}

static void uv__get_cgroup2_memory_limits(char buf[static 1024], uint64_t* high,
uint64_t* max) {
char filename[4097];
char* p;
int n;

/* Find out where the controller is mounted. */
p = buf + strlen("0::/");
n = (int) strcspn(p, "\n");

/* Read the memory limits of the controller. */
snprintf(filename, sizeof(filename), "/sys/fs/cgroup/%.*s/memory.max", n, p);
*max = uv__read_uint64(filename);
snprintf(filename, sizeof(filename), "/sys/fs/cgroup/%.*s/memory.high", n, p);
*high = uv__read_uint64(filename);
}

snprintf(filename, sizeof(filename), "/sys/fs/cgroup/%s/memory.max", p);
max = uv__read_uint64(filename);
static uint64_t uv__get_cgroup_constrained_memory(char buf[static 1024]) {
uint64_t high;
uint64_t max;

if (max == 0)
return uv__get_constrained_memory_fallback();
/* In the case of cgroupv2, we'll only have a single entry. */
if (strncmp(buf, "0::/", 4))
uv__get_cgroup1_memory_limits(buf, &high, &max);
else
uv__get_cgroup2_memory_limits(buf, &high, &max);

snprintf(filename, sizeof(filename), "/sys/fs/cgroup/%s/memory.high", p);
high = uv__read_uint64(filename);
if (high == 0 || max == 0)
return 0;

if (high == 0)
return uv__get_constrained_memory_fallback();
return high < max ? high : max;
}

uint64_t uv_get_constrained_memory(void) {
char buf[1024];

limit = high < max ? high : max;
if (limit == ~0ull)
if (uv__slurp("/proc/self/cgroup", buf, sizeof(buf)))
return 0;
return limit;

return uv__get_cgroup_constrained_memory(buf);
}


static uint64_t uv__get_available_memory_fallback(void) {
static uint64_t uv__get_cgroup1_current_memory(char buf[static 1024]) {
char filename[4097];
uint64_t current;
uint64_t constrained;
char* p;
int n;

constrained = uv__get_constrained_memory_fallback();
if (constrained == 0)
return uv_get_free_memory();
/* Find out where the controller is mounted. */
p = uv__cgroup1_find_memory_controller(buf, &n);
if (p != NULL) {
snprintf(filename, sizeof(filename),
"/sys/fs/cgroup/memory/%.*s/memory.usage_in_bytes", n, p);
current = uv__read_uint64(filename);

current = uv__read_uint64("/sys/fs/cgroup/memory/memory.usage_in_bytes");
return constrained - current;
/* If the controller wasn't mounted, the reads above will have failed,
* as indicated by uv__read_uint64 returning 0.
*/
if (current != 0)
return current;
}

/* Fall back to the usage of the global memory controller. */
return uv__read_uint64("/sys/fs/cgroup/memory/memory.usage_in_bytes");
}

static uint64_t uv__get_cgroup2_current_memory(char buf[static 1024]) {
char filename[4097];
char* p;
int n;

/* Find out where the controller is mounted. */
p = buf + strlen("0::/");
n = (int) strcspn(p, "\n");

snprintf(filename, sizeof(filename),
"/sys/fs/cgroup/%.*s/memory.current", n, p);
return uv__read_uint64(filename);
}

uint64_t uv_get_available_memory(void) {
char filename[4097];
char buf[1024];
uint64_t current;
uint64_t constrained;
char* p;

constrained = uv_get_constrained_memory();
if (constrained == 0)
return uv__get_available_memory_fallback();
uint64_t current;
uint64_t total;

if (uv__slurp("/proc/self/cgroup", buf, sizeof(buf)))
return uv__get_available_memory_fallback();
return 0;

if (memcmp(buf, "0::/", 4))
return uv__get_available_memory_fallback();
constrained = uv__get_cgroup_constrained_memory(buf);
if (constrained == 0)
return uv_get_free_memory();

p = strchr(buf, '\n');
if (p != NULL)
*p = '\0';
total = uv_get_total_memory();
if (constrained > total)
return uv_get_free_memory();

p = buf + 4;
/* In the case of cgroupv2, we'll only have a single entry. */
if (strncmp(buf, "0::/", 4))
current = uv__get_cgroup1_current_memory(buf);
else
current = uv__get_cgroup2_current_memory(buf);

snprintf(filename, sizeof(filename), "/sys/fs/cgroup/%s/memory.current", p);
current = uv__read_uint64(filename);
/* memory usage can be higher than the limit (for short bursts of time) */
if (constrained < current)
return 0;

return constrained - current;
}
Expand Down
10 changes: 8 additions & 2 deletions test/test-get-memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ TEST_IMPL(get_memory) {
uint64_t constrained_mem = uv_get_constrained_memory();
uint64_t available_mem = uv_get_available_memory();

printf("free_mem=%llu, total_mem=%llu, constrained_mem=%llu, available_memo=%llu\n",
printf("free_mem=%llu, total_mem=%llu, constrained_mem=%llu, "
"available_mem=%llu\n",
(unsigned long long) free_mem,
(unsigned long long) total_mem,
(unsigned long long) constrained_mem,
Expand All @@ -42,6 +43,11 @@ TEST_IMPL(get_memory) {
#else
ASSERT(total_mem > free_mem);
#endif
ASSERT(available_mem <= free_mem);
ASSERT_LE(available_mem, total_mem);
/* we'd really want to test if available <= free, but that is fragile:
* with no limit set, get_available calls and returns get_free; so if
* any memory was freed between our calls to get_free and get_available
* we would fail such a test test (as observed on CI).
*/
return 0;
}