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

Enable cgroups hierarchical memory limits support #93611

Merged
merged 7 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
190 changes: 141 additions & 49 deletions src/coreclr/gc/unix/cgroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Module Name:
#define CGROUP_MEMORY_STAT_FILENAME "/memory.stat"
#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes"
#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current"
#define CGROUP1_MEMORY_USE_HIERARCHY_FILENAME "/memory.use_hierarchy"
#define CGROUP1_MEMORY_STAT_HIERARCHICAL_MEMORY_LIMIT_FIELD "hierarchical_memory_limit "
#define CGROUP1_MEMORY_STAT_INACTIVE_FIELD "total_inactive_file "
#define CGROUP2_MEMORY_STAT_INACTIVE_FIELD "inactive_file "

Expand All @@ -62,26 +64,28 @@ class CGroup
static int s_cgroup_version;

static char *s_memory_cgroup_path;
static char *s_memory_cgroup_hierarchy_mount;
public:
static void Initialize()
{
s_cgroup_version = FindCGroupVersion();
s_memory_cgroup_path = FindCGroupPath(s_cgroup_version == 1 ? &IsCGroup1MemorySubsystem : nullptr);
FindCGroupPath(s_cgroup_version == 1 ? &IsCGroup1MemorySubsystem : nullptr, &s_memory_cgroup_path, &s_memory_cgroup_hierarchy_mount);
}

static void Cleanup()
{
free(s_memory_cgroup_path);
free(s_memory_cgroup_hierarchy_mount);
}

static bool GetPhysicalMemoryLimit(uint64_t *val)
{
if (s_cgroup_version == 0)
return false;
else if (s_cgroup_version == 1)
return GetCGroupMemoryLimit(val, CGROUP1_MEMORY_LIMIT_FILENAME);
return GetCGroupMemoryLimitV1(val);
else if (s_cgroup_version == 2)
return GetCGroupMemoryLimit(val, CGROUP2_MEMORY_LIMIT_FILENAME);
return GetCGroupMemoryLimitV2(val);
else
{
assert(!"Unknown cgroup version.");
Expand Down Expand Up @@ -139,7 +143,8 @@ class CGroup
return strcmp("memory", strTok) == 0;
}

static char* FindCGroupPath(bool (*is_subsystem)(const char *)){
static void FindCGroupPath(bool (*is_subsystem)(const char *), char** pcgroup_path, char ** pcgroup_hierarchy_mount = nullptr){

char *cgroup_path = nullptr;
char *hierarchy_mount = nullptr;
char *hierarchy_root = nullptr;
Expand Down Expand Up @@ -186,10 +191,17 @@ class CGroup


done:
free(hierarchy_mount);
free(hierarchy_root);
free(cgroup_path_relative_to_mount);
return cgroup_path;
*pcgroup_path = cgroup_path;
if (pcgroup_hierarchy_mount != nullptr)
{
*pcgroup_hierarchy_mount = hierarchy_mount;
}
else
{
free(hierarchy_mount);
}
}

static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot)
Expand Down Expand Up @@ -367,19 +379,128 @@ class CGroup
return cgroup_path;
}

static bool GetCGroupMemoryLimit(uint64_t *val, const char *filename)
static bool GetCGroupMemoryStatField(const char *fieldName, uint64_t *val)
{
if (s_memory_cgroup_path == nullptr)
return false;

char* stat_filename = nullptr;
if (asprintf(&stat_filename, "%s%s", s_memory_cgroup_path, CGROUP_MEMORY_STAT_FILENAME) < 0)
return false;

FILE *stat_file = fopen(stat_filename, "r");
free(stat_filename);
if (stat_file == nullptr)
return false;

char *line = nullptr;
size_t lineLen = 0;
bool foundFieldValue = false;
char* endptr;

size_t fieldNameLength = strlen(fieldName);

while (getline(&line, &lineLen, stat_file) != -1)
{
if (strncmp(line, fieldName, fieldNameLength) == 0)
{
errno = 0;
const char* startptr = line + fieldNameLength;
size_t fieldValue = strtoll(startptr, &endptr, 10);
if (endptr != startptr && errno == 0)
{
foundFieldValue = true;
*val = fieldValue;
}

break;
}
}

fclose(stat_file);
free(line);

return foundFieldValue;
}

static bool GetCGroupMemoryLimitV1(uint64_t *val)
{
if (s_memory_cgroup_path == nullptr)
return false;

char* mem_use_hierarchy_filename = nullptr;
if (asprintf(&mem_use_hierarchy_filename, "%s%s", s_memory_cgroup_path, CGROUP1_MEMORY_USE_HIERARCHY_FILENAME) < 0)
return false;

uint64_t useHierarchy = 0;
ReadMemoryValueFromFile(mem_use_hierarchy_filename, &useHierarchy);
free(mem_use_hierarchy_filename);

if (useHierarchy)
{
return GetCGroupMemoryStatField(CGROUP1_MEMORY_STAT_HIERARCHICAL_MEMORY_LIMIT_FIELD, val);
}

char* mem_limit_filename = nullptr;
if (asprintf(&mem_limit_filename, "%s%s", s_memory_cgroup_path, filename) < 0)
if (asprintf(&mem_limit_filename, "%s%s", s_memory_cgroup_path, CGROUP1_MEMORY_LIMIT_FILENAME) < 0)
return false;

bool result = ReadMemoryValueFromFile(mem_limit_filename, val);
free(mem_limit_filename);
return result;
}

static bool GetCGroupMemoryLimitV2(uint64_t *val)
{
if (s_memory_cgroup_path == nullptr)
return false;

// Process the whole CGroup hierarchy to find a level with the most limiting limit
size_t memory_cgroup_hierarchy_mount_length = strlen(s_memory_cgroup_hierarchy_mount);
uint64_t min_limit = std::numeric_limits<uint64_t>::max();
uint64_t limit;
bool found_any_limit = false;

char *mem_limit_filename = nullptr;
if (asprintf(&mem_limit_filename, "%s%s", s_memory_cgroup_path, CGROUP2_MEMORY_LIMIT_FILENAME) < 0)
return false;

size_t cgroupPathLength = strlen(s_memory_cgroup_path);

// Iterate over the directory hierarchy representing the cgroup hierarchy until reaching the
// mount directory. The mount directory doesn't contain the memory.max.
do
{
if (ReadMemoryValueFromFile(mem_limit_filename, &limit))
{
found_any_limit = true;
if (limit < min_limit)
{
min_limit = limit;
}
}

// Get the parent cgroup memory limit file path
char *parent_directory_end = mem_limit_filename + cgroupPathLength - 1;
while (*parent_directory_end != '/')
{
parent_directory_end--;
}

cgroupPathLength = parent_directory_end - mem_limit_filename;

strcpy(parent_directory_end, CGROUP2_MEMORY_LIMIT_FILENAME);
}
while (cgroupPathLength != memory_cgroup_hierarchy_mount_length);

free(mem_limit_filename);

if (found_any_limit)
{
*val = min_limit;
}
return found_any_limit;
}

static bool GetCGroupMemoryUsage(size_t *val, const char *filename, const char *inactiveFileFieldName)
{
Expand All @@ -391,21 +512,15 @@ class CGroup
if (asprintf(&mem_usage_filename, "%s%s", s_memory_cgroup_path, filename) < 0)
return false;

uint64_t temp = 0;

size_t usage = 0;
uint64_t usage = 0;

bool result = ReadMemoryValueFromFile(mem_usage_filename, &temp);
bool result = ReadMemoryValueFromFile(mem_usage_filename, &usage);
if (result)
{
if (temp > std::numeric_limits<size_t>::max())
if (usage > std::numeric_limits<size_t>::max())
{
usage = std::numeric_limits<size_t>::max();
}
else
{
usage = (size_t)temp;
}
}

free(mem_usage_filename);
Expand All @@ -416,49 +531,26 @@ class CGroup
if (s_memory_cgroup_path == nullptr)
return false;

char* stat_filename = nullptr;
if (asprintf(&stat_filename, "%s%s", s_memory_cgroup_path, CGROUP_MEMORY_STAT_FILENAME) < 0)
return false;

FILE *stat_file = fopen(stat_filename, "r");
free(stat_filename);
if (stat_file == nullptr)
return false;

char *line = nullptr;
size_t lineLen = 0;
bool foundInactiveFileValue = false;
char* endptr;

size_t inactiveFileFieldNameLength = strlen(inactiveFileFieldName);

while (getline(&line, &lineLen, stat_file) != -1)
uint64_t inactiveFileValue = 0;
if (GetCGroupMemoryStatField(inactiveFileFieldName, &inactiveFileValue))
{
if (strncmp(line, inactiveFileFieldName, inactiveFileFieldNameLength) == 0)
if (inactiveFileValue > std::numeric_limits<size_t>::max())
{
errno = 0;
const char* startptr = line + inactiveFileFieldNameLength;
size_t inactiveFileValue = strtoll(startptr, &endptr, 10);
if (endptr != startptr && errno == 0)
{
foundInactiveFileValue = true;
*val = usage - inactiveFileValue;
}

break;
inactiveFileValue = std::numeric_limits<size_t>::max();
}
}

fclose(stat_file);
free(line);
*val = (size_t)usage - (size_t)inactiveFileValue;
return true;
}

return foundInactiveFileValue;
return false;
}
};
}

int CGroup::s_cgroup_version = 0;
char *CGroup::s_memory_cgroup_path = nullptr;
char *CGroup::s_memory_cgroup_hierarchy_mount = nullptr;

void InitializeCGroup()
{
Expand Down
10 changes: 0 additions & 10 deletions src/coreclr/pal/inc/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -2708,16 +2708,6 @@ DWORD
PALAPI
PAL_GetTotalCpuCount();

PALIMPORT
size_t
PALAPI
PAL_GetRestrictedPhysicalMemoryLimit();

PALIMPORT
BOOL
PALAPI
PAL_GetPhysicalMemoryUsed(size_t* val);

PALIMPORT
BOOL
PALAPI
Expand Down
3 changes: 0 additions & 3 deletions src/coreclr/pal/src/include/pal/palinternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -633,9 +633,6 @@ typedef enum _TimeConversionConstants
#ifdef __cplusplus
}

bool
PAL_ReadMemoryValueFromFile(const char* filename, uint64_t* val);

#ifdef __APPLE__
bool
GetApplicationContainerFolder(PathCharString& buffer, const char *applicationGroupId, int applicationGroupIdLength);
Expand Down