diff --git a/src/coreclr/gc/unix/cgroup.cpp b/src/coreclr/gc/unix/cgroup.cpp index 136ff3fb19105..d2ad75bbf787b 100644 --- a/src/coreclr/gc/unix/cgroup.cpp +++ b/src/coreclr/gc/unix/cgroup.cpp @@ -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 " @@ -62,16 +64,18 @@ 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) @@ -79,9 +83,9 @@ class CGroup 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."); @@ -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; @@ -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) @@ -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::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) { @@ -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::max()) + if (usage > std::numeric_limits::max()) { usage = std::numeric_limits::max(); } - else - { - usage = (size_t)temp; - } } free(mem_usage_filename); @@ -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::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::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() { diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index b29d207511788..0a98291984d47 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -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 diff --git a/src/coreclr/pal/src/include/pal/palinternal.h b/src/coreclr/pal/src/include/pal/palinternal.h index a7c5ba129c90f..644918728edf1 100644 --- a/src/coreclr/pal/src/include/pal/palinternal.h +++ b/src/coreclr/pal/src/include/pal/palinternal.h @@ -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); diff --git a/src/coreclr/pal/src/misc/cgroup.cpp b/src/coreclr/pal/src/misc/cgroup.cpp index 0eae4e8db451d..d80cfe0cf6461 100644 --- a/src/coreclr/pal/src/misc/cgroup.cpp +++ b/src/coreclr/pal/src/misc/cgroup.cpp @@ -35,13 +35,6 @@ SET_DEFAULT_DEBUG_CHANNEL(MISC); #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo" #define PROC_CGROUP_FILENAME "/proc/self/cgroup" #define PROC_STATM_FILENAME "/proc/self/statm" -#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes" -#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max" -#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_STAT_INACTIVE_FIELD "total_inactive_file " -#define CGROUP2_MEMORY_STAT_INACTIVE_FIELD "inactive_file " #define CGROUP1_CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" #define CGROUP1_CFS_PERIOD_FILENAME "/cpu.cfs_period_us" #define CGROUP2_CPU_MAX_FILENAME "/cpu.max" @@ -51,52 +44,20 @@ class CGroup // the cgroup version number or 0 to indicate cgroups are not found or not enabled static int s_cgroup_version; - static char *s_memory_cgroup_path; static char *s_cpu_cgroup_path; + public: static void Initialize() { s_cgroup_version = FindCGroupVersion(); - s_memory_cgroup_path = FindCGroupPath(s_cgroup_version == 1 ? &IsCGroup1MemorySubsystem : nullptr); - s_cpu_cgroup_path = FindCGroupPath(s_cgroup_version == 1 ? &IsCGroup1CpuSubsystem : nullptr); + FindCGroupPath(s_cgroup_version == 1 ? &IsCGroup1CpuSubsystem : nullptr, &s_cpu_cgroup_path); } static void Cleanup() { - PAL_free(s_memory_cgroup_path); PAL_free(s_cpu_cgroup_path); } - 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); - else if (s_cgroup_version == 2) - return GetCGroupMemoryLimit(val, CGROUP2_MEMORY_LIMIT_FILENAME); - else - { - _ASSERTE(!"Unknown cgroup version."); - return false; - } - } - - static bool GetPhysicalMemoryUsage(size_t *val) - { - if (s_cgroup_version == 0) - return false; - else if (s_cgroup_version == 1) - return GetCGroupMemoryUsage(val, CGROUP1_MEMORY_USAGE_FILENAME, CGROUP1_MEMORY_STAT_INACTIVE_FIELD); - else if (s_cgroup_version == 2) - return GetCGroupMemoryUsage(val, CGROUP2_MEMORY_USAGE_FILENAME, CGROUP2_MEMORY_STAT_INACTIVE_FIELD); - else - { - _ASSERTE(!"Unknown cgroup version."); - return false; - } - } - static bool GetCpuLimit(UINT *val) { if (s_cgroup_version == 0) @@ -143,15 +104,11 @@ class CGroup #endif } - static bool IsCGroup1MemorySubsystem(const char *strTok){ - return strcmp("memory", strTok) == 0; - } - static bool IsCGroup1CpuSubsystem(const char *strTok){ return strcmp("cpu", 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; @@ -200,10 +157,17 @@ class CGroup strcat_s(cgroup_path, len+1, cgroup_path_relative_to_mount + common_path_prefix_len); done: - PAL_free(hierarchy_mount); PAL_free(hierarchy_root); PAL_free(cgroup_path_relative_to_mount); - return cgroup_path; + *pcgroup_path = cgroup_path; + if (pcgroup_hierarchy_mount != nullptr) + { + *pcgroup_hierarchy_mount = hierarchy_mount; + } + else + { + PAL_free(hierarchy_mount); + } } static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot) @@ -380,99 +344,6 @@ class CGroup return cgroup_path; } - static bool GetCGroupMemoryLimit(uint64_t *val, const char *filename) - { - if (s_memory_cgroup_path == nullptr) - return false; - - char* mem_limit_filename = nullptr; - if (asprintf(&mem_limit_filename, "%s%s", s_memory_cgroup_path, filename) < 0) - return false; - - bool result = ReadMemoryValueFromFile(mem_limit_filename, val); - free(mem_limit_filename); - return result; - } - - static bool GetCGroupMemoryUsage(size_t *val, const char *filename, const char *inactiveFileFieldName) - { - // Use the same way to calculate memory load as popular container tools (Docker, Kubernetes, Containerd etc.) - // For cgroup v1: value of 'memory.usage_in_bytes' minus 'total_inactive_file' value of 'memory.stat' - // For cgroup v2: value of 'memory.current' minus 'inactive_file' value of 'memory.stat' - - char* mem_usage_filename = nullptr; - if (asprintf(&mem_usage_filename, "%s%s", s_memory_cgroup_path, filename) < 0) - return false; - - uint64_t temp = 0; - - size_t usage = 0; - - bool result = ReadMemoryValueFromFile(mem_usage_filename, &temp); - if (result) - { - if (temp > std::numeric_limits::max()) - { - usage = std::numeric_limits::max(); - } - else - { - usage = (size_t)temp; - } - } - - free(mem_usage_filename); - - if (!result) - return result; - - 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) - { - if (strncmp(line, inactiveFileFieldName, inactiveFileFieldNameLength) == 0) - { - 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; - } - } - - fclose(stat_file); - free(line); - - return foundInactiveFileValue; - } - - static bool ReadMemoryValueFromFile(const char* filename, uint64_t* val) - { - return ::PAL_ReadMemoryValueFromFile(filename, val); - } - static bool GetCGroup1CpuLimit(UINT *val) { long long quota; @@ -627,7 +498,6 @@ class CGroup }; int CGroup::s_cgroup_version = 0; -char *CGroup::s_memory_cgroup_path = nullptr; char *CGroup::s_cpu_cgroup_path = nullptr; void InitializeCGroup() @@ -640,99 +510,6 @@ void CleanupCGroup() CGroup::Cleanup(); } -size_t -PALAPI -PAL_GetRestrictedPhysicalMemoryLimit() -{ - uint64_t physical_memory_limit_64 = 0; - size_t physical_memory_limit = 0; - - if (!CGroup::GetPhysicalMemoryLimit(&physical_memory_limit_64)) - return 0; - - // If there's no memory limit specified on the container this - // actually returns 0x7FFFFFFFFFFFF000 (2^63-1 rounded down to - // 4k which is a common page size). So we know we are not - // running in a memory restricted environment. - if (physical_memory_limit_64 > 0x7FFFFFFF00000000) - { - return 0; - } - - if (physical_memory_limit_64 > std::numeric_limits::max()) - { - // It is observed in practice when the memory is unrestricted, Linux control - // group returns a physical limit that is bigger than the address space - physical_memory_limit = std::numeric_limits::max(); - } - else - { - physical_memory_limit = (size_t)physical_memory_limit_64; - } - - struct rlimit curr_rlimit; - size_t rlimit_soft_limit = (size_t)RLIM_INFINITY; - if (getrlimit(RLIMIT_AS, &curr_rlimit) == 0) - { - rlimit_soft_limit = curr_rlimit.rlim_cur; - } - physical_memory_limit = std::min(physical_memory_limit, rlimit_soft_limit); - - // Ensure that limit is not greater than real memory size - long pages = sysconf(_SC_PHYS_PAGES); - if (pages != -1) - { - long pageSize = sysconf(_SC_PAGE_SIZE); - if (pageSize != -1) - { - physical_memory_limit = std::min(physical_memory_limit, - (size_t)(pages * pageSize)); - } - } - - if(physical_memory_limit == SIZE_T_MAX) - physical_memory_limit = 0; - return physical_memory_limit; -} - -BOOL -PALAPI -PAL_GetPhysicalMemoryUsed(size_t* val) -{ - BOOL result = false; - size_t linelen; - char* line = nullptr; - - if (val == nullptr) - return FALSE; - - // Linux uses cgroup usage to trigger oom kills. - if (CGroup::GetPhysicalMemoryUsage(val)) - return TRUE; - - // process resident set size. - FILE* file = fopen(PROC_STATM_FILENAME, "r"); - if (file != nullptr && getline(&line, &linelen, file) != -1) - { - char* context = nullptr; - char* strTok = strtok_s(line, " ", &context); - strTok = strtok_s(nullptr, " ", &context); - - errno = 0; - *val = strtoull(strTok, nullptr, 0); - if(errno == 0) - { - *val = *val * GetVirtualPageSize(); - result = true; - } - } - - if (file) - fclose(file); - free(line); - return result; -} - BOOL PALAPI PAL_GetCpuLimit(UINT* val) diff --git a/src/coreclr/pal/src/misc/sysinfo.cpp b/src/coreclr/pal/src/misc/sysinfo.cpp index 499b905180950..d20fa7b2aabec 100644 --- a/src/coreclr/pal/src/misc/sysinfo.cpp +++ b/src/coreclr/pal/src/misc/sysinfo.cpp @@ -237,56 +237,3 @@ GetSystemInfo( LOGEXIT("GetSystemInfo returns VOID\n"); PERF_EXIT(GetSystemInfo); } - -// Get memory size multiplier based on the passed in units (k = kilo, m = mega, g = giga) -static uint64_t GetMemorySizeMultiplier(char units) -{ - switch(units) - { - case 'g': - case 'G': return 1024 * 1024 * 1024; - case 'm': - case 'M': return 1024 * 1024; - case 'k': - case 'K': return 1024; - } - - // No units multiplier - return 1; -} - -bool -PAL_ReadMemoryValueFromFile(const char* filename, uint64_t* val) -{ - bool result = false; - char *line = nullptr; - size_t lineLen = 0; - char* endptr = nullptr; - uint64_t num = 0, multiplier; - - if (val == nullptr) - return false; - - FILE* file = fopen(filename, "r"); - if (file == nullptr) - goto done; - - if (getline(&line, &lineLen, file) == -1) - goto done; - - errno = 0; - num = strtoull(line, &endptr, 0); - if (errno != 0) - goto done; - - multiplier = GetMemorySizeMultiplier(*endptr); - *val = num * multiplier; - result = true; - if (*val/multiplier != num) - result = false; -done: - if (file) - fclose(file); - free(line); - return result; -} diff --git a/src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs b/src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs index b6b34095c39a4..b4dcedb1cc933 100644 --- a/src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs +++ b/src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs @@ -29,26 +29,119 @@ internal enum CGroupVersion { None, CGroup1, CGroup2 }; /// The version of cgroup that's being used. Mutated by tests only. internal static readonly CGroupVersion s_cgroupVersion = FindCGroupVersion(); + /// Path to the found cgroup memory hierarchy mount path, or null if it couldn't be found. + internal static readonly string? s_cgroupMemoryHierarchyMountPath = FindCGroupMemoryHierarchyMountPath(s_cgroupVersion); + /// Path to the found cgroup memory limit path, or null if it couldn't be found. - internal static readonly string? s_cgroupMemoryLimitPath = FindCGroupMemoryLimitPath(s_cgroupVersion); + internal static readonly string? s_cgroupMemoryPath = FindCGroupMemoryPath(s_cgroupVersion); /// Tries to read the memory limit from the cgroup memory location. /// The read limit, or 0 if it couldn't be read. /// true if the limit was read successfully; otherwise, false. public static bool TryGetMemoryLimit(out ulong limit) { - string? path = s_cgroupMemoryLimitPath; + if (s_cgroupVersion == CGroupVersion.CGroup1) + { + return TryGetMemoryLimitV1(out limit); + } + else if (s_cgroupVersion == CGroupVersion.CGroup2) + { + return TryGetMemoryLimitV2(out limit); + } + + limit = 0; + return false; + } + + /// Tries to read a field of a specified name from the memory.stat file in the current cgroup (cgroup v1 only). + /// Name of the field to read. + /// Value of the field or 0 if the field was not found. + /// true if the field was read successfully; otherwise, false. + internal static bool TryGetMemoryStatField(string fieldName, out ulong val) + { + string? path = s_cgroupMemoryPath; + if (path != null) + { + try + { + // Each field name in the memory.stat is separated by one space from its value + fieldName += ' '; + foreach (string line in File.ReadLines(path + "/memory.stat")) + { + if (line.StartsWith(fieldName)) + { + bool foundFieldValue = ulong.TryParse(line.AsSpan(fieldName.Length), out val); + return foundFieldValue; + } + } + } + catch (Exception e) + { + Debug.Fail($"Failed to read \"{path}/memory.stat\": {e}"); + } + } + + val = 0; + return false; + } - if (path != null && - TryReadMemoryValueFromFile(path, out limit)) + /// Tries to read the memory limit from the cgroup v1 hierarchy. + /// The read limit, or 0 if it couldn't be read. + /// true if the limit was read successfully; otherwise, false. + internal static bool TryGetMemoryLimitV1(out ulong limit) + { + string? path = s_cgroupMemoryPath; + if (path != null) { - return true; + if (TryReadMemoryValueFromFile(path + "/memory.use_hierarchy", out ulong useHierarchy) && (useHierarchy != 0)) + { + return TryGetMemoryStatField("hierarchical_memory_limit", out limit); + } + + if (path != null && + TryReadMemoryValueFromFile(path + "/memory.limit_in_bytes", out limit)) + { + return true; + } } limit = 0; return false; } + /// Tries to read the memory limit from the cgroup v2 hierarchy. + /// The read limit, or 0 if it couldn't be read. + /// true if the limit was read successfully; otherwise, false. + internal static bool TryGetMemoryLimitV2(out ulong limit) + { + bool foundAnyLimit = false; + ulong minLimit = ulong.MaxValue; + string? currentCGroupMemoryPath = s_cgroupMemoryPath; + string? cgroupMemoryHierarchyMountPath = s_cgroupMemoryHierarchyMountPath; + if (currentCGroupMemoryPath != null && cgroupMemoryHierarchyMountPath != null) + { + // 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 (TryReadMemoryValueFromFile(currentCGroupMemoryPath + "/memory.max", out ulong currentLevelLimit)) + { + foundAnyLimit = true; + if (currentLevelLimit < minLimit) + { + minLimit = currentLevelLimit; + } + } + currentCGroupMemoryPath = Path.GetDirectoryName(currentCGroupMemoryPath); + } + while (currentCGroupMemoryPath!.Length != cgroupMemoryHierarchyMountPath.Length); + } + + limit = minLimit; + + return foundAnyLimit; + } + /// Tries to parse a memory limit from the specified file. /// The path to the file to parse. /// The parsed result, or 0 if it couldn't be parsed. @@ -108,47 +201,44 @@ internal static bool TryReadMemoryValueFromFile(string path, out ulong result) /// Find the cgroup version in use on the system. /// The cgroup version. - private static CGroupVersion FindCGroupVersion() + private static unsafe CGroupVersion FindCGroupVersion() { - try + CGroupVersion cgroupVersion = CGroupVersion.None; + const int MountPointFormatBufferSizeInBytes = 32; + byte* formatBuffer = stackalloc byte[MountPointFormatBufferSizeInBytes]; // format names should be small + long numericFormat; + int result = Interop.Sys.GetFormatInfoForMountPoint(SysFsCgroupFileSystemPath, formatBuffer, MountPointFormatBufferSizeInBytes, &numericFormat); + if (result == 0) { - return new DriveInfo(SysFsCgroupFileSystemPath).DriveFormat switch + cgroupVersion = numericFormat switch { - "cgroup2fs" => CGroupVersion.CGroup2, - "tmpfs" => CGroupVersion.CGroup1, + (int)Interop.Sys.UnixFileSystemTypes.cgroup2fs => CGroupVersion.CGroup2, + (int)Interop.Sys.UnixFileSystemTypes.tmpfs => CGroupVersion.CGroup1, _ => CGroupVersion.None, }; } - catch (Exception ex) when (ex is DriveNotFoundException || ex is ArgumentException) - { - return CGroupVersion.None; - } + + return cgroupVersion; } - /// Find the cgroup memory limit path. - /// The cgroup version currently in use on the system. - /// The limit path if found; otherwise, null. - private static string? FindCGroupMemoryLimitPath(CGroupVersion cgroupVersion) + private static string? FindCGroupMemoryHierarchyMountPath(CGroupVersion cgroupVersion) { - string? cgroupMemoryPath = FindCGroupPath(cgroupVersion, "memory"); - if (cgroupMemoryPath != null) + if (TryFindHierarchyMount(cgroupVersion, "memory", out string? _, out string? hierarchyMount)) { - if (cgroupVersion == CGroupVersion.CGroup1) - { - return cgroupMemoryPath + "/memory.limit_in_bytes"; - } - - if (cgroupVersion == CGroupVersion.CGroup2) - { - // 'memory.high' is a soft limit; the process may get throttled - // 'memory.max' is where OOM killer kicks in - return cgroupMemoryPath + "/memory.max"; - } + return hierarchyMount; } return null; } + /// Find the cgroup memory. + /// The cgroup version currently in use on the system. + /// The limit path if found; otherwise, null. + private static string? FindCGroupMemoryPath(CGroupVersion cgroupVersion) + { + return FindCGroupPath(cgroupVersion, "memory"); + } + /// Find the cgroup path for the specified subsystem. /// The cgroup version currently in use on the system. /// The subsystem, e.g. "memory". diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs index ce6266de6f3fc..38884d65a38cc 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs @@ -34,7 +34,7 @@ internal struct MountPointInformation internal static partial int GetSpaceInfoForMountPoint([MarshalAs(UnmanagedType.LPUTF8Str)]string name, out MountPointInformation mpi); [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetFormatInfoForMountPoint", SetLastError = true)] - private static unsafe partial int GetFormatInfoForMountPoint( + internal static unsafe partial int GetFormatInfoForMountPoint( [MarshalAs(UnmanagedType.LPUTF8Str)]string name, byte* formatNameBuffer, int bufferLength, diff --git a/src/libraries/Common/tests/Common.Tests.csproj b/src/libraries/Common/tests/Common.Tests.csproj index 97009778000f5..eae8a369ceff5 100644 --- a/src/libraries/Common/tests/Common.Tests.csproj +++ b/src/libraries/Common/tests/Common.Tests.csproj @@ -9,8 +9,6 @@ Link="Common\System\Collections\DictionaryExtensions.cs" /> - - @@ -131,21 +128,31 @@ Link="System\PasteArguments.Windows.cs" /> + + - + + + + + + + + + true true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-browser @@ -9,9 +10,15 @@ - + + + +