| |
@@ -0,0 +1,876 @@
|
| |
+ From 7b33e078b41819284175533215047359bb952b5d Mon Sep 17 00:00:00 2001
|
| |
+ From: Omair Majid <omajid@redhat.com>
|
| |
+ Date: Fri, 26 Jul 2019 17:12:48 -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.
|
| |
+ ---
|
| |
+ src/gc/unix/cgroup.cpp | 292 +++++++++++++++++++++++++++++-------
|
| |
+ src/pal/src/misc/cgroup.cpp | 287 ++++++++++++++++++++++++++++-------
|
| |
+ 2 files changed, 471 insertions(+), 108 deletions(-)
|
| |
+
|
| |
+ diff --git a/src/gc/unix/cgroup.cpp b/src/gc/unix/cgroup.cpp
|
| |
+ index 1e60898948..e5760bebd8 100644
|
| |
+ --- a/src/gc/unix/cgroup.cpp
|
| |
+ +++ b/src/gc/unix/cgroup.cpp
|
| |
+ @@ -35,20 +35,24 @@ Abstract:
|
| |
+ #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;
|
| |
+ + 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_memory_cgroup_path = FindCGroupPath(&IsCGroup1MemorySubsystem);
|
| |
+ + s_cpu_cgroup_path = FindCGroupPath(&IsCGroup1CpuSubsystem);
|
| |
+ }
|
| |
+
|
| |
+ static void Cleanup()
|
| |
+ @@ -59,20 +63,21 @@ public:
|
| |
+
|
| |
+ static bool GetPhysicalMemoryLimit(size_t *val)
|
| |
+ {
|
| |
+ + const char *files[] = {
|
| |
+ + CGROUP1_MEMORY_LIMIT_FILENAME,
|
| |
+ + CGROUP2_MEMORY_LIMIT_FILENAME
|
| |
+ + };
|
| |
+ +
|
| |
+ 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);
|
| |
+ + mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
|
| |
+ 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;
|
| |
+ @@ -80,6 +85,11 @@ public:
|
| |
+
|
| |
+ static bool GetPhysicalMemoryUsage(size_t *val)
|
| |
+ {
|
| |
+ + const char *files[] = {
|
| |
+ + CGROUP1_MEMORY_USAGE_FILENAME,
|
| |
+ + CGROUP2_MEMORY_USAGE_FILENAME
|
| |
+ + };
|
| |
+ +
|
| |
+ char *mem_usage_filename = nullptr;
|
| |
+ bool result = false;
|
| |
+
|
| |
+ @@ -87,14 +97,10 @@ public:
|
| |
+ 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);
|
| |
+ + mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
|
| |
+ 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, val);
|
| |
+ free(mem_usage_filename);
|
| |
+ return result;
|
| |
+ @@ -112,16 +118,149 @@ public:
|
| |
+ }
|
| |
+
|
| |
+ static bool GetCpuLimit(uint32_t *val)
|
| |
+ + {
|
| |
+ + if (GetCGroup1CpuLimit(val))
|
| |
+ + {
|
| |
+ + return true;
|
| |
+ + }
|
| |
+ +
|
| |
+ + if (GetCGroup2CpuLimit(val))
|
| |
+ + {
|
| |
+ + return true;
|
| |
+ + }
|
| |
+ +
|
| |
+ + return false;
|
| |
+ + }
|
| |
+ +
|
| |
+ +private:
|
| |
+ + static bool IsCGroup1MemorySubsystem(const char *strTok){
|
| |
+ + return strcmp("memory", strTok) == 0;
|
| |
+ + }
|
| |
+ +
|
| |
+ + static bool IsCGroup1CpuSubsystem(const char *strTok){
|
| |
+ + return strcmp("cpu", strTok) == 0;
|
| |
+ + }
|
| |
+ +
|
| |
+ + static bool GetCGroup2CpuLimit(uint32_t *val)
|
| |
+ + {
|
| |
+ + char *line = nullptr;
|
| |
+ + char *filename = nullptr;
|
| |
+ + FILE *file = nullptr;
|
| |
+ + char *max_quota_string = nullptr;
|
| |
+ + char *period_string = nullptr;
|
| |
+ +
|
| |
+ + size_t lineLen = 0;
|
| |
+ + int sscanRet = 0;
|
| |
+ + long long max_quota = 0;
|
| |
+ + long long period = 0;
|
| |
+ + double cpu_count = 0;
|
| |
+ +
|
| |
+ + bool result = false;
|
| |
+ +
|
| |
+ + if (s_cpu_cgroup_path == nullptr)
|
| |
+ + {
|
| |
+ + return false;
|
| |
+ + }
|
| |
+ +
|
| |
+ + size_t filename_len = strlen(s_cpu_cgroup_path) + strlen(CGROUP2_CPU_MAX_FILENAME);
|
| |
+ + filename = (char*)malloc(filename_len + 1);
|
| |
+ + if (filename == nullptr)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + strcpy(filename, s_cpu_cgroup_path);
|
| |
+ + strcat(filename, CGROUP2_CPU_MAX_FILENAME);
|
| |
+ +
|
| |
+ + 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 = (char*) malloc(lineLen + 1);
|
| |
+ + if (max_quota_string == nullptr)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ + period_string = (char*) malloc(lineLen + 1);
|
| |
+ + if (period_string == nullptr)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + sscanRet = sscanf(line, "%s %s", max_quota_string, period_string);
|
| |
+ + if (sscanRet != 2)
|
| |
+ + {
|
| |
+ + assert(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents with sscanf.");
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + // "max" means no cpu limit
|
| |
+ + if (strncmp("max", max_quota_string, lineLen + 1) == 0)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + errno = 0;
|
| |
+ + max_quota = atoll(max_quota_string);
|
| |
+ + if (errno != 0)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + errno = 0;
|
| |
+ + period = atoll(period_string);
|
| |
+ + if (errno != 0)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + // Cannot have less than 1 CPU
|
| |
+ + if (max_quota <= period)
|
| |
+ + {
|
| |
+ + result = true;
|
| |
+ + *val = 1;
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + // Calculate cpu count based on quota and round it up
|
| |
+ + cpu_count = (double) max_quota / period + 0.999999999;
|
| |
+ + *val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX;
|
| |
+ +
|
| |
+ + result = true;
|
| |
+ +
|
| |
+ + done:
|
| |
+ + if (file)
|
| |
+ + fclose(file);
|
| |
+ + free(filename);
|
| |
+ + free(line);
|
| |
+ + free(max_quota_string);
|
| |
+ + free(period_string);
|
| |
+ +
|
| |
+ + return result;
|
| |
+ + }
|
| |
+ +
|
| |
+ + static bool GetCGroup1CpuLimit(uint32_t *val)
|
| |
+ {
|
| |
+ long long quota;
|
| |
+ long long period;
|
| |
+ double cpu_count;
|
| |
+
|
| |
+ - quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME);
|
| |
+ + quota = ReadCpuCGroupValue(CGROUP1_CFS_QUOTA_FILENAME);
|
| |
+ if (quota <= 0)
|
| |
+ return false;
|
| |
+
|
| |
+ - period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME);
|
| |
+ + period = ReadCpuCGroupValue(CGROUP1_CFS_PERIOD_FILENAME);
|
| |
+ if (period <= 0)
|
| |
+ return false;
|
| |
+
|
| |
+ @@ -138,27 +277,19 @@ public:
|
| |
+
|
| |
+ return true;
|
| |
+ }
|
| |
+ -
|
| |
+ -private:
|
| |
+ - static bool IsMemorySubsystem(const char *strTok){
|
| |
+ - return strcmp("memory", strTok) == 0;
|
| |
+ - }
|
| |
+
|
| |
+ - static bool IsCpuSubsystem(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;
|
| |
+ char *cgroup_path_relative_to_mount = nullptr;
|
| |
+ + bool is_cgroupv2 = false;
|
| |
+
|
| |
+ - FindHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root);
|
| |
+ + FindHierarchyMount(is_subsystem, &is_cgroupv2, &hierarchy_mount, &hierarchy_root);
|
| |
+ if (hierarchy_mount == nullptr || hierarchy_root == nullptr)
|
| |
+ goto done;
|
| |
+
|
| |
+ - cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_subsystem);
|
| |
+ + cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_cgroupv2, is_subsystem);
|
| |
+ if (cgroup_path_relative_to_mount == nullptr)
|
| |
+ goto done;
|
| |
+
|
| |
+ @@ -179,7 +310,7 @@ private:
|
| |
+ return cgroup_path;
|
| |
+ }
|
| |
+
|
| |
+ - static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot)
|
| |
+ + static void FindHierarchyMount(bool (*is_subsystem)(const char *), bool* is_cgroupv2, char** pmountpath, char** pmountroot)
|
| |
+ {
|
| |
+ char *line = nullptr;
|
| |
+ size_t lineLen = 0, maxLineLen = 0;
|
| |
+ @@ -219,14 +350,18 @@ private:
|
| |
+ assert(!"Failed to parse mount info file contents with sscanf.");
|
| |
+ goto done;
|
| |
+ }
|
| |
+ -
|
| |
+ +
|
| |
+ if (strncmp(filesystemType, "cgroup", 6) == 0)
|
| |
+ {
|
| |
+ + if (strncmp(filesystemType, "cgroup2", 8) == 0)
|
| |
+ + {
|
| |
+ + *is_cgroupv2 = true;
|
| |
+ + }
|
| |
+ char* context = nullptr;
|
| |
+ char* strTok = strtok_r(options, ",", &context);
|
| |
+ while (strTok != nullptr)
|
| |
+ {
|
| |
+ - if (is_subsystem(strTok))
|
| |
+ + if (*is_cgroupv2 || is_subsystem(strTok))
|
| |
+ {
|
| |
+ mountpath = (char*)malloc(lineLen+1);
|
| |
+ if (mountpath == nullptr)
|
| |
+ @@ -234,7 +369,7 @@ private:
|
| |
+ mountroot = (char*)malloc(lineLen+1);
|
| |
+ if (mountroot == nullptr)
|
| |
+ goto done;
|
| |
+ -
|
| |
+ +
|
| |
+ sscanfRet = sscanf(line,
|
| |
+ "%*s %*s %*s %s %s ",
|
| |
+ mountroot,
|
| |
+ @@ -261,8 +396,8 @@ private:
|
| |
+ if (mountinfofile)
|
| |
+ fclose(mountinfofile);
|
| |
+ }
|
| |
+ -
|
| |
+ - static char* FindCGroupPathForSubsystem(bool (*is_subsystem)(const char *))
|
| |
+ +
|
| |
+ + static char* FindCGroupPathForSubsystem(bool is_cgroupv2, bool (*is_subsystem)(const char *))
|
| |
+ {
|
| |
+ char *line = nullptr;
|
| |
+ size_t lineLen = 0;
|
| |
+ @@ -274,7 +409,7 @@ private:
|
| |
+ FILE *cgroupfile = fopen(PROC_CGROUP_FILENAME, "r");
|
| |
+ if (cgroupfile == nullptr)
|
| |
+ goto done;
|
| |
+ -
|
| |
+ +
|
| |
+ while (!result && getline(&line, &lineLen, cgroupfile) != -1)
|
| |
+ {
|
| |
+ if (subsystem_list == nullptr || lineLen > maxLineLen)
|
| |
+ @@ -289,28 +424,43 @@ private:
|
| |
+ goto done;
|
| |
+ 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 (is_cgroupv2)
|
| |
+ {
|
| |
+ - assert(!"Failed to parse cgroup info file contents with sscanf.");
|
| |
+ - goto done;
|
| |
+ + // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt
|
| |
+ + // Look for "0::/some/path"
|
| |
+ + int sscanfRet = sscanf(line,
|
| |
+ + "0::%s",
|
| |
+ + cgroup_path);
|
| |
+ + if (sscanfRet == 1)
|
| |
+ + {
|
| |
+ + result = true;
|
| |
+ + }
|
| |
+ }
|
| |
+ -
|
| |
+ - char* context = nullptr;
|
| |
+ - char* strTok = strtok_r(subsystem_list, ",", &context);
|
| |
+ - while (strTok != nullptr)
|
| |
+ + else
|
| |
+ {
|
| |
+ - if (is_subsystem(strTok))
|
| |
+ + // 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)
|
| |
+ {
|
| |
+ - result = true;
|
| |
+ - break;
|
| |
+ + 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)
|
| |
+ + {
|
| |
+ + if (is_subsystem(strTok))
|
| |
+ + {
|
| |
+ + result = true;
|
| |
+ + break;
|
| |
+ + }
|
| |
+ + strTok = strtok_r(nullptr, ",", &context);
|
| |
+ }
|
| |
+ - strTok = strtok_r(nullptr, ",", &context);
|
| |
+ }
|
| |
+ }
|
| |
+ done:
|
| |
+ @@ -325,6 +475,36 @@ private:
|
| |
+ fclose(cgroupfile);
|
| |
+ return cgroup_path;
|
| |
+ }
|
| |
+ +
|
| |
+ + static char* SearchForFile(char* search_root, const char* possible_files[], size_t possible_files_len)
|
| |
+ + {
|
| |
+ + if (search_root == nullptr)
|
| |
+ + {
|
| |
+ + return nullptr;
|
| |
+ + }
|
| |
+ +
|
| |
+ + for (size_t i = 0; i < possible_files_len; i++)
|
| |
+ + {
|
| |
+ + const char* possible_file = possible_files[i];
|
| |
+ + size_t len = strlen(search_root);
|
| |
+ + len += strlen(possible_file);
|
| |
+ + char* full_filename = (char*)malloc(len+1);
|
| |
+ + if (full_filename == nullptr)
|
| |
+ + {
|
| |
+ + return nullptr;
|
| |
+ + }
|
| |
+ + strcpy(full_filename, search_root);
|
| |
+ + strcat(full_filename, possible_file);
|
| |
+ + if (access(full_filename, R_OK) != -1)
|
| |
+ + {
|
| |
+ + return full_filename;
|
| |
+ + }
|
| |
+ +
|
| |
+ + free(full_filename);
|
| |
+ + }
|
| |
+ +
|
| |
+ + return nullptr;
|
| |
+ + }
|
| |
+
|
| |
+ static bool ReadMemoryValueFromFile(const char* filename, size_t* val)
|
| |
+ {
|
| |
+ diff --git a/src/pal/src/misc/cgroup.cpp b/src/pal/src/misc/cgroup.cpp
|
| |
+ index 3fc43a7b05..e32b74aabe 100644
|
| |
+ --- a/src/pal/src/misc/cgroup.cpp
|
| |
+ +++ b/src/pal/src/misc/cgroup.cpp
|
| |
+ @@ -23,10 +23,15 @@
|
| |
+ #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;
|
| |
+ @@ -34,8 +39,8 @@
|
| |
+ public:
|
| |
+ static void Initialize()
|
| |
+ {
|
| |
+ - s_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem);
|
| |
+ - s_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem);
|
| |
+ + s_memory_cgroup_path = FindCGroupPath(&IsCGroup1MemorySubsystem);
|
| |
+ + s_cpu_cgroup_path = FindCGroupPath(&IsCGroup1CpuSubsystem);
|
| |
+ }
|
| |
+
|
| |
+ static void Cleanup()
|
| |
+ @@ -43,23 +48,24 @@
|
| |
+ PAL_free(s_memory_cgroup_path);
|
| |
+ PAL_free(s_cpu_cgroup_path);
|
| |
+ }
|
| |
+ -
|
| |
+ +
|
| |
+ static bool GetPhysicalMemoryLimit(size_t *val)
|
| |
+ {
|
| |
+ + const char *files[] = {
|
| |
+ + CGROUP1_MEMORY_LIMIT_FILENAME,
|
| |
+ + CGROUP2_MEMORY_LIMIT_FILENAME
|
| |
+ + };
|
| |
+ +
|
| |
+ 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);
|
| |
+ + mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
|
| |
+ 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;
|
| |
+ @@ -67,36 +73,172 @@
|
| |
+
|
| |
+ static bool GetPhysicalMemoryUsage(size_t *val)
|
| |
+ {
|
| |
+ + const char *files[] = {
|
| |
+ + CGROUP1_MEMORY_USAGE_FILENAME,
|
| |
+ + CGROUP2_MEMORY_USAGE_FILENAME
|
| |
+ + };
|
| |
+ +
|
| |
+ char *mem_usage_filename = nullptr;
|
| |
+ bool result = false;
|
| |
+
|
| |
+ 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);
|
| |
+ + mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
|
| |
+ 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, val);
|
| |
+ - free(mem_usage_filename);
|
| |
+ + PAL_free(mem_usage_filename);
|
| |
+ return result;
|
| |
+ }
|
| |
+
|
| |
+ static bool GetCpuLimit(UINT *val)
|
| |
+ {
|
| |
+ + if (GetCGroup1CpuLimit(val))
|
| |
+ + {
|
| |
+ + return true;
|
| |
+ + }
|
| |
+ +
|
| |
+ + if (GetCGroup2CpuLimit(val))
|
| |
+ + {
|
| |
+ + return true;
|
| |
+ + }
|
| |
+ +
|
| |
+ + return false;
|
| |
+ + }
|
| |
+ +
|
| |
+ +private:
|
| |
+ + static bool IsCGroup1MemorySubsystem(const char *strTok){
|
| |
+ + return strcmp("memory", strTok) == 0;
|
| |
+ + }
|
| |
+ +
|
| |
+ + static bool IsCGroup1CpuSubsystem(const char *strTok){
|
| |
+ + return strcmp("cpu", strTok) == 0;
|
| |
+ + }
|
| |
+ +
|
| |
+ + static bool GetCGroup2CpuLimit(UINT *val)
|
| |
+ + {
|
| |
+ + char *line = nullptr;
|
| |
+ + char *filename = nullptr;
|
| |
+ + FILE *file = nullptr;
|
| |
+ + char *max_quota_string = nullptr;
|
| |
+ + char *period_string = nullptr;
|
| |
+ +
|
| |
+ + size_t lineLen = 0;
|
| |
+ + int sscanRet = 0;
|
| |
+ + long long max_quota = 0;
|
| |
+ + long long period = 0;
|
| |
+ + double cpu_count = 0;
|
| |
+ +
|
| |
+ + bool result = false;
|
| |
+ +
|
| |
+ + if (s_cpu_cgroup_path == nullptr)
|
| |
+ + {
|
| |
+ + return false;
|
| |
+ + }
|
| |
+ +
|
| |
+ + size_t filename_len = strlen(s_cpu_cgroup_path) + strlen(CGROUP2_CPU_MAX_FILENAME);
|
| |
+ + filename = (char*)PAL_malloc(filename_len + 1);
|
| |
+ + if (filename == nullptr)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + strcpy_s(filename, filename_len + 1, s_cpu_cgroup_path);
|
| |
+ + strcat_s(filename, filename_len + 1, CGROUP2_CPU_MAX_FILENAME);
|
| |
+ +
|
| |
+ + 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 = (char*)PAL_malloc(lineLen + 1);
|
| |
+ + if (max_quota_string == nullptr)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ + period_string = (char*)PAL_malloc(lineLen + 1);
|
| |
+ + if (period_string == nullptr)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + sscanRet = sscanf_s(line, "%s %s",
|
| |
+ + max_quota_string, lineLen + 1,
|
| |
+ + period_string, lineLen + 1);
|
| |
+ + if (sscanRet != 2)
|
| |
+ + {
|
| |
+ + _ASSERTE(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents with sscanf_s.");
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + // "max" means no cpu limit
|
| |
+ + if (strncmp("max", max_quota_string, lineLen + 1) == 0)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + errno = 0;
|
| |
+ + max_quota = atoll(max_quota_string);
|
| |
+ + if (errno != 0)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + errno = 0;
|
| |
+ + period = atoll(period_string);
|
| |
+ + if (errno != 0)
|
| |
+ + {
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + // Cannot have less than 1 CPU
|
| |
+ + if (max_quota <= period)
|
| |
+ + {
|
| |
+ + result = true;
|
| |
+ + *val = 1;
|
| |
+ + goto done;
|
| |
+ + }
|
| |
+ +
|
| |
+ + // Calculate cpu count based on quota and round it up
|
| |
+ + cpu_count = (double) max_quota / period + 0.999999999;
|
| |
+ + *val = (cpu_count < UINT_MAX) ? (UINT)cpu_count : UINT_MAX;
|
| |
+ +
|
| |
+ + result = true;
|
| |
+ +
|
| |
+ + done:
|
| |
+ + if (file)
|
| |
+ + fclose(file);
|
| |
+ + PAL_free(filename);
|
| |
+ + PAL_free(line);
|
| |
+ + PAL_free(max_quota_string);
|
| |
+ + PAL_free(period_string);
|
| |
+ +
|
| |
+ + return result;
|
| |
+ + }
|
| |
+ +
|
| |
+ + static bool GetCGroup1CpuLimit(UINT *val)
|
| |
+ + {
|
| |
+ long long quota;
|
| |
+ long long period;
|
| |
+ double cpu_count;
|
| |
+
|
| |
+ - quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME);
|
| |
+ + quota = ReadCpuCGroupValue(CGROUP1_CFS_QUOTA_FILENAME);
|
| |
+ if (quota <= 0)
|
| |
+ return false;
|
| |
+
|
| |
+ - period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME);
|
| |
+ + period = ReadCpuCGroupValue(CGROUP1_CFS_PERIOD_FILENAME);
|
| |
+ if (period <= 0)
|
| |
+ return false;
|
| |
+
|
| |
+ @@ -114,27 +256,19 @@
|
| |
+ return true;
|
| |
+ }
|
| |
+
|
| |
+ -private:
|
| |
+ - static bool IsMemorySubsystem(const char *strTok){
|
| |
+ - return strcmp("memory", strTok) == 0;
|
| |
+ - }
|
| |
+ -
|
| |
+ - static bool IsCpuSubsystem(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;
|
| |
+ char *cgroup_path_relative_to_mount = nullptr;
|
| |
+ + bool is_cgroupv2 = false;
|
| |
+ size_t len;
|
| |
+
|
| |
+ - FindHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root);
|
| |
+ + FindHierarchyMount(is_subsystem, &is_cgroupv2, &hierarchy_mount, &hierarchy_root);
|
| |
+ if (hierarchy_mount == nullptr || hierarchy_root == nullptr)
|
| |
+ goto done;
|
| |
+
|
| |
+ - cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_subsystem);
|
| |
+ + cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_cgroupv2, is_subsystem);
|
| |
+ if (cgroup_path_relative_to_mount == nullptr)
|
| |
+ goto done;
|
| |
+
|
| |
+ @@ -157,7 +291,7 @@
|
| |
+ return cgroup_path;
|
| |
+ }
|
| |
+
|
| |
+ - static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot)
|
| |
+ + static void FindHierarchyMount(bool (*is_subsystem)(const char *), bool* is_cgroupv2, char** pmountpath, char** pmountroot)
|
| |
+ {
|
| |
+ char *line = nullptr;
|
| |
+ size_t lineLen = 0, maxLineLen = 0;
|
| |
+ @@ -199,11 +333,15 @@
|
| |
+
|
| |
+ if (strncmp(filesystemType, "cgroup", 6) == 0)
|
| |
+ {
|
| |
+ + if (strncmp(filesystemType, "cgroup2", 8) == 0)
|
| |
+ + {
|
| |
+ + *is_cgroupv2 = true;
|
| |
+ + }
|
| |
+ char* context = nullptr;
|
| |
+ char* strTok = strtok_s(options, ",", &context);
|
| |
+ while (strTok != nullptr)
|
| |
+ {
|
| |
+ - if (is_subsystem(strTok))
|
| |
+ + if (*is_cgroupv2 || is_subsystem(strTok))
|
| |
+ {
|
| |
+ mountpath = (char*)PAL_malloc(lineLen+1);
|
| |
+ if (mountpath == nullptr)
|
| |
+ @@ -239,7 +377,7 @@
|
| |
+ fclose(mountinfofile);
|
| |
+ }
|
| |
+
|
| |
+ - static char* FindCGroupPathForSubsystem(bool (*is_subsystem)(const char *))
|
| |
+ + static char* FindCGroupPathForSubsystem(bool is_cgroupv2, bool (*is_subsystem)(const char *))
|
| |
+ {
|
| |
+ char *line = nullptr;
|
| |
+ size_t lineLen = 0;
|
| |
+ @@ -267,27 +405,42 @@
|
| |
+ 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 (is_cgroupv2)
|
| |
+ {
|
| |
+ - _ASSERTE(!"Failed to parse cgroup info file contents with sscanf_s.");
|
| |
+ - goto done;
|
| |
+ + // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt
|
| |
+ + // Look for "0::/some/path"
|
| |
+ + int sscanfRet = sscanf_s(line,
|
| |
+ + "0::%s",
|
| |
+ + cgroup_path, lineLen+1);
|
| |
+ + if (sscanfRet == 1)
|
| |
+ + {
|
| |
+ + result = true;
|
| |
+ + }
|
| |
+ }
|
| |
+ -
|
| |
+ - char* context = nullptr;
|
| |
+ - char* strTok = strtok_s(subsystem_list, ",", &context);
|
| |
+ - while (strTok != nullptr)
|
| |
+ + else
|
| |
+ {
|
| |
+ - if (is_subsystem(strTok))
|
| |
+ + // 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)
|
| |
+ {
|
| |
+ - result = true;
|
| |
+ - break;
|
| |
+ + _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)
|
| |
+ + {
|
| |
+ + if (is_subsystem(strTok))
|
| |
+ + {
|
| |
+ + result = true;
|
| |
+ + break;
|
| |
+ + }
|
| |
+ + strTok = strtok_s(nullptr, ",", &context);
|
| |
+ }
|
| |
+ - strTok = strtok_s(nullptr, ",", &context);
|
| |
+ }
|
| |
+ }
|
| |
+ done:
|
| |
+ @@ -303,6 +456,36 @@
|
| |
+ return cgroup_path;
|
| |
+ }
|
| |
+
|
| |
+ + static char* SearchForFile(char* search_root, const char* possible_files[], size_t possible_files_len)
|
| |
+ + {
|
| |
+ + if (search_root == nullptr)
|
| |
+ + {
|
| |
+ + return nullptr;
|
| |
+ + }
|
| |
+ +
|
| |
+ + for (size_t i = 0; i < possible_files_len; i++)
|
| |
+ + {
|
| |
+ + const char* possible_file = possible_files[i];
|
| |
+ + size_t len = strlen(search_root);
|
| |
+ + len += strlen(possible_file);
|
| |
+ + char* full_filename = (char*)PAL_malloc(len+1);
|
| |
+ + if (full_filename == nullptr)
|
| |
+ + {
|
| |
+ + return nullptr;
|
| |
+ + }
|
| |
+ + strcpy_s(full_filename, len+1, search_root);
|
| |
+ + strcat_s(full_filename, len+1, possible_file);
|
| |
+ + if (access(full_filename, R_OK) != -1)
|
| |
+ + {
|
| |
+ + return full_filename;
|
| |
+ + }
|
| |
+ +
|
| |
+ + PAL_free(full_filename);
|
| |
+ + }
|
| |
+ +
|
| |
+ + return nullptr;
|
| |
+ + }
|
| |
+ +
|
| |
+ static bool ReadMemoryValueFromFile(const char* filename, size_t* val)
|
| |
+ {
|
| |
+ return ::ReadMemoryValueFromFile(filename, val);
|
| |
+ --
|
| |
+ 2.23.0
|
| |
This is a backport that contains:
Some of these have been approved/merged; others are waiting on review.