From 392dd8b01aab6b3731fe7e5fbacdfd218c57da78 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Tue, 31 Mar 2020 12:06:44 -0400 Subject: [PATCH] Add cgroup v2 support to coreclr Upstream cgroup v2 documentation is available at: https://www.kernel.org/doc/Documentation/cgroup-v2.txt Some notable differences between cgroup v1 and v2, from a coreclr point of view, include: - cgroup v2 has a single hierarchy, so we just look for a single "cgroup2" entry in /proc/self/mountinfo (without looking for a subsystem match). - Since cgroup v2 has a single hierarchy, /proc/self/cgroup generally has a single line "0::/path". There's no need to match subsystems or hierarchy ids here. - "memory.limit_in_bytes" is now "memory.max". It can contain the literal "max" to indicate no limit. - "memory.usage_in_bytes" is now "memory.current" - "cpu.cfs_quota_us" and "cpu.cfs_period_us" have been combined into a single "cpu.max" file with the format "$MAX $PERIOD". The max value can be a literal "max" to indicate a limit is not active. It is possible to have both cgroup v1 and v2 enabled on a host (but not inside a container, AFAIK). In that case, this change will pick one based on /sys/fs/cgroup. --- src/coreclr/src/gc/unix/cgroup.cpp | 371 +++++++++++++++++++++++--------- src/coreclr/src/pal/src/misc/cgroup.cpp | 366 ++++++++++++++++++++++--------- 2 files changed, 536 insertions(+), 201 deletions(-) diff --git a/src/coreclr/src/gc/unix/cgroup.cpp b/src/coreclr/src/gc/unix/cgroup.cpp index f3ae610..be3af90 100644 --- a/src/coreclr/src/gc/unix/cgroup.cpp +++ b/src/coreclr/src/gc/unix/cgroup.cpp @@ -23,6 +23,12 @@ Abstract: #include #include #include +#ifdef __APPLE__ +#include +#include +#else +#include +#endif #include #include @@ -32,23 +38,35 @@ Abstract: #define SIZE_T_MAX (~(size_t)0) #endif +#define CGROUP2_SUPER_MAGIC 0x63677270 +#define TMPFS_MAGIC 0x01021994 + +#define BASE_TEN 10 + #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo" #define PROC_CGROUP_FILENAME "/proc/self/cgroup" #define PROC_STATM_FILENAME "/proc/self/statm" -#define MEM_LIMIT_FILENAME "/memory.limit_in_bytes" -#define MEM_USAGE_FILENAME "/memory.usage_in_bytes" -#define CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" -#define CFS_PERIOD_FILENAME "/cpu.cfs_period_us" +#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes" +#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max" +#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes" +#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current" +#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" class CGroup { - static char* s_memory_cgroup_path; - static char* s_cpu_cgroup_path; + // 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_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem); - s_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem); + s_cgroup_version = FindCGroupVersion(); + s_memory_cgroup_path = FindCGroupPath(&IsCGroup1MemorySubsystem); + s_cpu_cgroup_path = FindCGroupPath(&IsCGroup1CpuSubsystem); } static void Cleanup() @@ -59,96 +77,85 @@ public: static bool GetPhysicalMemoryLimit(uint64_t *val) { - char *mem_limit_filename = nullptr; - bool result = false; - - if (s_memory_cgroup_path == nullptr) - return result; - - size_t len = strlen(s_memory_cgroup_path); - len += strlen(MEM_LIMIT_FILENAME); - mem_limit_filename = (char*)malloc(len+1); - if (mem_limit_filename == nullptr) - return result; - - strcpy(mem_limit_filename, s_memory_cgroup_path); - strcat(mem_limit_filename, MEM_LIMIT_FILENAME); - result = ReadMemoryValueFromFile(mem_limit_filename, val); - free(mem_limit_filename); - return result; + 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 + { + assert(!"Unknown cgroup version."); + return false; + } } static bool GetPhysicalMemoryUsage(size_t *val) { - char *mem_usage_filename = nullptr; - bool result = false; - uint64_t temp = 0; - - if (s_memory_cgroup_path == nullptr) - return result; - - size_t len = strlen(s_memory_cgroup_path); - len += strlen(MEM_USAGE_FILENAME); - mem_usage_filename = (char*)malloc(len+1); - if (mem_usage_filename == nullptr) - return result; - - strcpy(mem_usage_filename, s_memory_cgroup_path); - strcat(mem_usage_filename, MEM_USAGE_FILENAME); - result = ReadMemoryValueFromFile(mem_usage_filename, &temp); - if (result) + if (s_cgroup_version == 0) + return false; + else if (s_cgroup_version == 1) + return GetCGroupMemoryUsage(val, CGROUP1_MEMORY_USAGE_FILENAME); + else if (s_cgroup_version == 2) + return GetCGroupMemoryUsage(val, CGROUP2_MEMORY_USAGE_FILENAME); + else { - if (temp > std::numeric_limits::max()) - { - *val = std::numeric_limits::max(); - } - else - { - *val = (size_t)temp; - } + assert(!"Unknown cgroup version."); + return false; } - free(mem_usage_filename); - return result; } static bool GetCpuLimit(uint32_t *val) { - long long quota; - long long period; - double cpu_count; - - quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME); - if (quota <= 0) + if (s_cgroup_version == 0) return false; - - period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME); - if (period <= 0) + else if (s_cgroup_version == 1) + return GetCGroup1CpuLimit(val); + else if (s_cgroup_version == 2) + return GetCGroup2CpuLimit(val); + else + { + assert(!"Unknown cgroup version."); return false; + } + } - // Cannot have less than 1 CPU - if (quota <= period) +private: + static int FindCGroupVersion() + { + // It is possible to have both cgroup v1 and v2 enabled on a system. + // Most non-bleeding-edge Linux distributions fall in this group. We + // look at the file system type of /sys/fs/cgroup to determine which + // one is the default. For more details, see: + // https://systemd.io/CGROUP_DELEGATION/#three-different-tree-setups- + // We dont care about the difference between the "legacy" and "hybrid" + // modes because both of those involve cgroup v1 controllers managing + // resources. + + struct statfs stats; + int result = statfs("/sys/fs/cgroup", &stats); + if (result != 0) + return 0; + + switch (stats.f_type) { - *val = 1; - return true; + case TMPFS_MAGIC: return 1; + case CGROUP2_SUPER_MAGIC: return 2; + default: + assert(!"Unexpected file system type for /sys/fs/cgroup"); + return 0; } - - // Calculate cpu count based on quota and round it up - cpu_count = (double) quota / period + 0.999999999; - *val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX; - - return true; } -private: - static bool IsMemorySubsystem(const char *strTok){ + static bool IsCGroup1MemorySubsystem(const char *strTok){ return strcmp("memory", strTok) == 0; } - static bool IsCpuSubsystem(const char *strTok){ + static bool IsCGroup1CpuSubsystem(const char *strTok){ return strcmp("cpu", strTok) == 0; } - static char* FindCgroupPath(bool (*is_subsystem)(const char *)){ + static char* FindCGroupPath(bool (*is_subsystem)(const char *)){ char *cgroup_path = nullptr; char *hierarchy_mount = nullptr; char *hierarchy_root = nullptr; @@ -248,7 +255,7 @@ private: char* strTok = strtok_r(options, ",", &context); while (strTok != nullptr) { - if (is_subsystem(strTok)) + if ((s_cgroup_version == 2) || ((s_cgroup_version == 1) && is_subsystem(strTok))) { mountpath = (char*)malloc(lineLen+1); if (mountpath == nullptr) @@ -312,27 +319,47 @@ private: maxLineLen = lineLen; } - // See man page of proc to get format for /proc/self/cgroup file - int sscanfRet = sscanf(line, - "%*[^:]:%[^:]:%s", - subsystem_list, - cgroup_path); - if (sscanfRet != 2) + if (s_cgroup_version == 1) { - assert(!"Failed to parse cgroup info file contents with sscanf."); - goto done; - } + // See man page of proc to get format for /proc/self/cgroup file + int sscanfRet = sscanf(line, + "%*[^:]:%[^:]:%s", + subsystem_list, + cgroup_path); + if (sscanfRet != 2) + { + assert(!"Failed to parse cgroup info file contents with sscanf."); + goto done; + } - char* context = nullptr; - char* strTok = strtok_r(subsystem_list, ",", &context); - while (strTok != nullptr) + char* context = nullptr; + char* strTok = strtok_r(subsystem_list, ",", &context); + while (strTok != nullptr) + { + if (is_subsystem(strTok)) + { + result = true; + break; + } + strTok = strtok_r(nullptr, ",", &context); + } + } + else if (s_cgroup_version == 2) { - if (is_subsystem(strTok)) + // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt + // Look for a "0::/some/path" + int sscanfRet = sscanf(line, + "0::%s", + cgroup_path); + if (sscanfRet == 1) { result = true; - break; } - strTok = strtok_r(nullptr, ",", &context); + } + else + { + assert(!"Unknown cgroup version in mountinfo."); + goto done; } } done: @@ -348,6 +375,46 @@ private: 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) + { + if (s_memory_cgroup_path == nullptr) + return false; + + char* mem_usage_filename = nullptr; + if (asprintf(&mem_usage_filename, "%s%s", s_memory_cgroup_path, filename) < 0) + return false; + + uint64_t temp = 0; + bool result = ReadMemoryValueFromFile(mem_usage_filename, &temp); + if (result) + { + if (temp > std::numeric_limits::max()) + { + *val = std::numeric_limits::max(); + } + else + { + *val = (size_t)temp; + } + } + + return result; + } + static bool ReadMemoryValueFromFile(const char* filename, uint64_t* val) { bool result = false; @@ -369,7 +436,7 @@ private: errno = 0; num = strtoull(line, &endptr, 0); - if (errno != 0) + if (line == endptr || errno != 0) goto done; multiplier = 1; @@ -394,6 +461,109 @@ private: return result; } + static bool GetCGroup1CpuLimit(uint32_t *val) + { + long long quota; + long long period; + + quota = ReadCpuCGroupValue(CGROUP1_CFS_QUOTA_FILENAME); + if (quota <= 0) + return false; + + period = ReadCpuCGroupValue(CGROUP1_CFS_PERIOD_FILENAME); + if (period <= 0) + return false; + + ComputeCpuLimit(period, quota, val); + + return true; + } + + static bool GetCGroup2CpuLimit(uint32_t *val) + { + char *filename = nullptr; + FILE *file = nullptr; + char *endptr = nullptr; + char *max_quota_string = nullptr; + char *period_string = nullptr; + char *context = nullptr; + char *line = nullptr; + size_t lineLen = 0; + + long long quota = 0; + long long period = 0; + + bool result = false; + + if (s_cpu_cgroup_path == nullptr) + return false; + + if (asprintf(&filename, "%s%s", s_cpu_cgroup_path, CGROUP2_CPU_MAX_FILENAME) < 0) + return false; + + file = fopen(filename, "r"); + if (file == nullptr) + goto done; + + if (getline(&line, &lineLen, file) == -1) + goto done; + + // The expected format is: + // $MAX $PERIOD + // Where "$MAX" may be the string literal "max" + + max_quota_string = strtok_r(line, " ", &context); + if (max_quota_string == nullptr) + { + assert(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents."); + goto done; + } + period_string = strtok_r(nullptr, " ", &context); + if (period_string == nullptr) + { + assert(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents."); + goto done; + } + + // "max" means no cpu limit + if (strcmp("max", max_quota_string) == 0) + goto done; + + errno = 0; + quota = strtoll(max_quota_string, &endptr, BASE_TEN); + if (max_quota_string == endptr || errno != 0) + goto done; + + period = strtoll(period_string, &endptr, BASE_TEN); + if (period_string == endptr || errno != 0) + goto done; + + ComputeCpuLimit(period, quota, val); + result = true; + + done: + if (file) + fclose(file); + free(filename); + free(line); + + return result; + } + + static void ComputeCpuLimit(long long period, long long quota, uint32_t *val) + { + // Cannot have less than 1 CPU + if (quota <= period) + { + *val = 1; + return; + } + + // Calculate cpu count based on quota and round it up + double cpu_count = (double) quota / period + 0.999999999; + *val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX; + } + static long long ReadCpuCGroupValue(const char* subsystemFilename){ char *filename = nullptr; bool result = false; @@ -402,12 +572,9 @@ private: if (s_cpu_cgroup_path == nullptr) return -1; - filename = (char*)malloc(strlen(s_cpu_cgroup_path) + strlen(subsystemFilename) + 1); - if (filename == nullptr) + if (asprintf(&filename, "%s%s", s_cpu_cgroup_path, subsystemFilename) < 0) return -1; - strcpy(filename, s_cpu_cgroup_path); - strcat(filename, subsystemFilename); result = ReadLongLongValueFromFile(filename, &val); free(filename); if (!result) @@ -421,13 +588,12 @@ private: bool result = false; char *line = nullptr; size_t lineLen = 0; - - FILE* file = nullptr; + char *endptr = nullptr; if (val == nullptr) - goto done; + return false; - file = fopen(filename, "r"); + FILE* file = fopen(filename, "r"); if (file == nullptr) goto done; @@ -435,8 +601,8 @@ private: goto done; errno = 0; - *val = atoll(line); - if (errno != 0) + *val = strtoll(line, &endptr, BASE_TEN); + if (line == endptr || errno != 0) goto done; result = true; @@ -448,6 +614,7 @@ private: } }; +int CGroup::s_cgroup_version = 0; char *CGroup::s_memory_cgroup_path = nullptr; char *CGroup::s_cpu_cgroup_path = nullptr; diff --git a/src/coreclr/src/pal/src/misc/cgroup.cpp b/src/coreclr/src/pal/src/misc/cgroup.cpp index 03e4724..e6c114e 100644 --- a/src/coreclr/src/pal/src/misc/cgroup.cpp +++ b/src/coreclr/src/pal/src/misc/cgroup.cpp @@ -15,27 +15,47 @@ Abstract: #include "pal/dbgmsg.h" SET_DEFAULT_DEBUG_CHANNEL(MISC); #include "pal/palinternal.h" +#include #include #include "pal/virtual.h" #include "pal/cgroup.h" #include +#ifdef __APPLE__ +#include +#include +#else +#include +#endif + +#define CGROUP2_SUPER_MAGIC 0x63677270 +#define TMPFS_MAGIC 0x01021994 + +#define BASE_TEN 10 #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo" #define PROC_CGROUP_FILENAME "/proc/self/cgroup" #define PROC_STATM_FILENAME "/proc/self/statm" -#define MEM_LIMIT_FILENAME "/memory.limit_in_bytes" -#define MEM_USAGE_FILENAME "/memory.usage_in_bytes" -#define CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" -#define CFS_PERIOD_FILENAME "/cpu.cfs_period_us" +#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes" +#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max" +#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes" +#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current" +#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" + 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_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem); - s_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem); + s_cgroup_version = FindCGroupVersion(); + s_memory_cgroup_path = FindCGroupPath(&IsCGroup1MemorySubsystem); + s_cpu_cgroup_path = FindCGroupPath(&IsCGroup1CpuSubsystem); } static void Cleanup() @@ -46,96 +66,85 @@ public: static bool GetPhysicalMemoryLimit(uint64_t *val) { - char *mem_limit_filename = nullptr; - bool result = false; - - if (s_memory_cgroup_path == nullptr) - return result; - - size_t len = strlen(s_memory_cgroup_path); - len += strlen(MEM_LIMIT_FILENAME); - mem_limit_filename = (char*)PAL_malloc(len+1); - if (mem_limit_filename == nullptr) - return result; - - strcpy_s(mem_limit_filename, len+1, s_memory_cgroup_path); - strcat_s(mem_limit_filename, len+1, MEM_LIMIT_FILENAME); - result = ReadMemoryValueFromFile(mem_limit_filename, val); - PAL_free(mem_limit_filename); - return result; + 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) { - char *mem_usage_filename = nullptr; - bool result = false; - uint64_t temp; - - if (s_memory_cgroup_path == nullptr) - return result; - - size_t len = strlen(s_memory_cgroup_path); - len += strlen(MEM_USAGE_FILENAME); - mem_usage_filename = (char*)malloc(len+1); - if (mem_usage_filename == nullptr) - return result; - - strcpy(mem_usage_filename, s_memory_cgroup_path); - strcat(mem_usage_filename, MEM_USAGE_FILENAME); - result = ReadMemoryValueFromFile(mem_usage_filename, &temp); - if (result) + if (s_cgroup_version == 0) + return false; + else if (s_cgroup_version == 1) + return GetCGroupMemoryUsage(val, CGROUP1_MEMORY_USAGE_FILENAME); + else if (s_cgroup_version == 2) + return GetCGroupMemoryUsage(val, CGROUP2_MEMORY_USAGE_FILENAME); + else { - if (temp > std::numeric_limits::max()) - { - *val = std::numeric_limits::max(); - } - else - { - *val = (size_t)temp; - } + _ASSERTE(!"Unknown cgroup version."); + return false; } - free(mem_usage_filename); - return result; } static bool GetCpuLimit(UINT *val) { - long long quota; - long long period; - double cpu_count; - - quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME); - if (quota <= 0) + if (s_cgroup_version == 0) return false; - - period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME); - if (period <= 0) + else if (s_cgroup_version == 1) + return GetCGroup1CpuLimit(val); + else if (s_cgroup_version == 2) + return GetCGroup2CpuLimit(val); + else + { + _ASSERTE(!"Unknown cgroup version."); return false; + } + } - // Cannot have less than 1 CPU - if (quota <= period) +private: + static int FindCGroupVersion() + { + // It is possible to have both cgroup v1 and v2 enabled on a system. + // Most non-bleeding-edge Linux distributions fall in this group. We + // look at the file system type of /sys/fs/cgroup to determine which + // one is the default. For more details, see: + // https://systemd.io/CGROUP_DELEGATION/#three-different-tree-setups- + // We dont care about the difference between the "legacy" and "hybrid" + // modes because both of those involve cgroup v1 controllers managing + // resources. + + struct statfs stats; + int result = statfs("/sys/fs/cgroup", &stats); + if (result != 0) + return 0; + + switch (stats.f_type) { - *val = 1; - return true; + case TMPFS_MAGIC: return 1; + case CGROUP2_SUPER_MAGIC: return 2; + default: + _ASSERTE(!"Unexpected file system type for /sys/fs/cgroup"); + return 0; } - - // Calculate cpu count based on quota and round it up - cpu_count = (double) quota / period + 0.999999999; - *val = (cpu_count < UINT_MAX) ? (UINT)cpu_count : UINT_MAX; - - return true; } -private: - static bool IsMemorySubsystem(const char *strTok){ + static bool IsCGroup1MemorySubsystem(const char *strTok){ return strcmp("memory", strTok) == 0; } - static bool IsCpuSubsystem(const char *strTok){ + static bool IsCGroup1CpuSubsystem(const char *strTok){ return strcmp("cpu", strTok) == 0; } - static char* FindCgroupPath(bool (*is_subsystem)(const char *)){ + static char* FindCGroupPath(bool (*is_subsystem)(const char *)){ char *cgroup_path = nullptr; char *hierarchy_mount = nullptr; char *hierarchy_root = nullptr; @@ -300,27 +309,47 @@ private: maxLineLen = lineLen; } - // See man page of proc to get format for /proc/self/cgroup file - int sscanfRet = sscanf_s(line, - "%*[^:]:%[^:]:%s", - subsystem_list, lineLen+1, - cgroup_path, lineLen+1); - if (sscanfRet != 2) + if (s_cgroup_version == 1) { - _ASSERTE(!"Failed to parse cgroup info file contents with sscanf_s."); - goto done; - } + // See man page of proc to get format for /proc/self/cgroup file + int sscanfRet = sscanf_s(line, + "%*[^:]:%[^:]:%s", + subsystem_list, lineLen+1, + cgroup_path, lineLen+1); + if (sscanfRet != 2) + { + _ASSERTE(!"Failed to parse cgroup info file contents with sscanf_s."); + goto done; + } - char* context = nullptr; - char* strTok = strtok_s(subsystem_list, ",", &context); - while (strTok != nullptr) + char* context = nullptr; + char* strTok = strtok_s(subsystem_list, ",", &context); + while (strTok != nullptr) + { + if (is_subsystem(strTok)) + { + result = true; + break; + } + strTok = strtok_s(nullptr, ",", &context); + } + } + else if (s_cgroup_version == 2) { - if (is_subsystem(strTok)) + // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt + // Look for a "0::/some/path" + int sscanfRet = sscanf_s(line, + "0::%s", lineLen+1, + cgroup_path, lineLen+1); + if (sscanfRet == 1) { result = true; - break; } - strTok = strtok_s(nullptr, ",", &context); + } + else + { + _ASSERTE(!"Unknown cgroup version in mountinfo."); + goto done; } } done: @@ -336,30 +365,167 @@ private: 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) + { + if (s_memory_cgroup_path == nullptr) + return false; + + char* mem_usage_filename = nullptr; + if (asprintf(&mem_usage_filename, "%s%s", s_memory_cgroup_path, filename) < 0) + return false; + + uint64_t temp = 0; + bool result = ReadMemoryValueFromFile(mem_usage_filename, &temp); + if (result) + { + if (temp > std::numeric_limits::max()) + { + *val = std::numeric_limits::max(); + } + else + { + *val = (size_t)temp; + } + } + + return result; + } + static bool ReadMemoryValueFromFile(const char* filename, uint64_t* val) { return ::ReadMemoryValueFromFile(filename, val); } + static bool GetCGroup1CpuLimit(UINT *val) + { + long long quota; + long long period; + + quota = ReadCpuCGroupValue(CGROUP1_CFS_QUOTA_FILENAME); + if (quota <= 0) + return false; + + period = ReadCpuCGroupValue(CGROUP1_CFS_PERIOD_FILENAME); + if (period <= 0) + return false; + + ComputeCpuLimit(period, quota, val); + + return true; + } + + static bool GetCGroup2CpuLimit(UINT *val) + { + char *filename = nullptr; + FILE *file = nullptr; + char *endptr = nullptr; + char *max_quota_string = nullptr; + char *period_string = nullptr; + char *context = nullptr; + char *line = nullptr; + size_t lineLen = 0; + + long long quota = 0; + long long period = 0; + + bool result = false; + + if (s_cpu_cgroup_path == nullptr) + return false; + + if (asprintf(&filename, "%s%s", s_cpu_cgroup_path, CGROUP2_CPU_MAX_FILENAME) < 0) + return false; + + file = fopen(filename, "r"); + if (file == nullptr) + goto done; + + if (getline(&line, &lineLen, file) == -1) + goto done; + + // The expected format is: + // $MAX $PERIOD + // Where "$MAX" may be the string literal "max" + + max_quota_string = strtok_s(line, " ", &context); + if (max_quota_string == nullptr) + { + _ASSERTE(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents."); + goto done; + } + period_string = strtok_s(nullptr, " ", &context); + if (period_string == nullptr) + { + _ASSERTE(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents."); + goto done; + } + + // "max" means no cpu limit + if (strcmp("max", max_quota_string) == 0) + goto done; + + errno = 0; + quota = strtoll(max_quota_string, &endptr, BASE_TEN); + if (max_quota_string == endptr || errno != 0) + goto done; + + period = strtoll(period_string, &endptr, BASE_TEN); + if (period_string == endptr || errno != 0) + goto done; + + ComputeCpuLimit(period, quota, val); + result = true; + + done: + if (file) + fclose(file); + free(filename); + free(line); + + return result; + } + + static void ComputeCpuLimit(long long period, long long quota, uint32_t *val) + { + // Cannot have less than 1 CPU + if (quota <= period) + { + *val = 1; + return; + } + + // Calculate cpu count based on quota and round it up + double cpu_count = (double) quota / period + 0.999999999; + *val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX; + } + static long long ReadCpuCGroupValue(const char* subsystemFilename){ char *filename = nullptr; bool result = false; long long val; - size_t len; if (s_cpu_cgroup_path == nullptr) return -1; - len = strlen(s_cpu_cgroup_path); - len += strlen(subsystemFilename); - filename = (char*)PAL_malloc(len + 1); - if (filename == nullptr) + if (asprintf(&filename, "%s%s", s_cpu_cgroup_path, subsystemFilename) < 0) return -1; - strcpy_s(filename, len+1, s_cpu_cgroup_path); - strcat_s(filename, len+1, subsystemFilename); result = ReadLongLongValueFromFile(filename, &val); - PAL_free(filename); + free(filename); if (!result) return -1; @@ -371,9 +537,10 @@ private: bool result = false; char *line = nullptr; size_t lineLen = 0; + char *endptr = nullptr; if (val == nullptr) - return false;; + return false; FILE* file = fopen(filename, "r"); if (file == nullptr) @@ -383,8 +550,8 @@ private: goto done; errno = 0; - *val = atoll(line); - if (errno != 0) + *val = strtoll(line, &endptr, BASE_TEN); + if (line == endptr || errno != 0) goto done; result = true; @@ -396,6 +563,7 @@ private: } }; +int CGroup::s_cgroup_version = 0; char *CGroup::s_memory_cgroup_path = nullptr; char *CGroup::s_cpu_cgroup_path = nullptr; -- 2.7.4