#8 Add cgroupv2 support to .NET Core
Closed a year ago by omajid. Opened 4 years ago by omajid.
dotnet-sig/ omajid/dotnet-3-0 master  into  master

@@ -0,0 +1,54 @@ 

+ diff --git a/src/gc/unix/cgroup.cpp b/src/gc/unix/cgroup.cpp

+ index e5760bebd8..d9a4e2dedd 100644

+ --- a/src/gc/unix/cgroup.cpp

+ +++ b/src/gc/unix/cgroup.cpp

+ @@ -31,6 +31,9 @@ Abstract:

+  #ifndef SIZE_T_MAX

+  #define SIZE_T_MAX (~(size_t)0)

+  #endif

+ +#ifndef _countof

+ +#define _countof(_array) (sizeof(_array) / sizeof(_array[0]))

+ +#endif // !_countof

+  

+  #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo"

+  #define PROC_CGROUP_FILENAME "/proc/self/cgroup"

+ @@ -74,7 +77,7 @@ public:

+          if (s_memory_cgroup_path == nullptr)

+              return result;

+  

+ -        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));

+ +        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files));

+          if (mem_limit_filename == nullptr)

+              return result;

+  

+ @@ -97,7 +100,7 @@ public:

+          if (s_memory_cgroup_path == nullptr)

+              return result;

+  

+ -        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));

+ +        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files));

+          if (mem_usage_filename == nullptr)

+              return result;

+  

+ diff --git a/src/pal/src/misc/cgroup.cpp b/src/pal/src/misc/cgroup.cpp

+ index e32b74aabe..0b922123c8 100644

+ --- a/src/pal/src/misc/cgroup.cpp

+ +++ b/src/pal/src/misc/cgroup.cpp

+ @@ -62,7 +61,7 @@ public:

+          if (s_memory_cgroup_path == nullptr)

+              return result;

+  

+ -        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));

+ +        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files));

+          if (mem_limit_filename == nullptr)

+              return result;

+  

+ @@ -85,7 +84,7 @@ public:

+          if (s_memory_cgroup_path == nullptr)

+              return result;

+  

+ -        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));

+ +        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files));

+          if (mem_usage_filename == nullptr)

+              return result;

+  

file added
+876
@@ -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

@@ -0,0 +1,46 @@ 

+ From 1864630f762160e1cb439362cc0577471624192a Mon Sep 17 00:00:00 2001

+ From: Omair Majid <omajid@redhat.com>

+ Date: Fri, 19 Jul 2019 19:18:51 -0400

+ Subject: [PATCH] Fix up cgroup2fs in Interop.MountPoints.FormatInfo

+ 

+ `stat -fc %T /sys/fs/cgroup` calls this file system `cgroup2fs`

+ 

+ Add the cgroup2fs file system magic number. Available from:

+ 

+   - https://www.kernel.org/doc/Documentation/cgroup-v2.txt

+   - man 2 statfs

+ 

+ Move cgroup2fs next to cgroupfs in the drive type list, since it is also

+ DriveType.Ram.

+ ---

+  .../Unix/System.Native/Interop.MountPoints.FormatInfo.cs       | 3 ++-

+  1 file changed, 2 insertions(+), 1 deletion(-)

+ 

+ diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs b/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs

+ index af38a2285ba2..4240bd4853ab 100644

+ --- a/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs

+ +++ b/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs

+ @@ -47,6 +47,7 @@ internal enum UnixFileSystemTypes : long

+              btrfs = 0x9123683E,

+              ceph = 0x00C36400,

+              cgroupfs = 0x0027E0EB,

+ +            cgroup2fs = 0x63677270,

+              cifs = 0xFF534D42,

+              coda = 0x73757245,

+              coherent = 0x012FF7B7,

+ @@ -231,7 +232,6 @@ private static DriveType GetDriveType(string fileSystemName)

+                  case "bpf_fs":

+                  case "btrfs":

+                  case "btrfs_test":

+ -                case "cgroup2fs":

+                  case "coh":

+                  case "daxfs":

+                  case "drvfs":

+ @@ -384,6 +384,7 @@ private static DriveType GetDriveType(string fileSystemName)

+                  case "binfmt_misc":

+                  case "cgroup":

+                  case "cgroupfs":

+ +                case "cgroup2fs":

+                  case "configfs":

+                  case "cramfs":

+                  case "cramfs-wend":

@@ -0,0 +1,391 @@ 

+ From 2b2273ea4ea1c28472fa0d6ad2ffeb6374500550 Mon Sep 17 00:00:00 2001

+ From: Omair Majid <omajid@redhat.com>

+ Date: Wed, 23 Oct 2019 17:45:59 -0400

+ Subject: [PATCH 1/2] Add cgroup v2 support to Interop.cgroups

+ 

+ Fix up code to adjust cgroup v1 assumptions and check cgroup v2 paths,

+ locations and values.

+ 

+ Continue using the older cgroup v1 terminology for APIs.

+ ---

+  .../Interop/Linux/cgroups/Interop.cgroups.cs  | 116 ++++++++++++++----

+  src/Common/tests/Common.Tests.csproj          |   4 +

+  .../tests/Tests/Interop/cgroupsTests.cs       | 107 ++++++++++++++++

+  .../tests/DescriptionNameTests.cs             |   2 +-

+  4 files changed, 206 insertions(+), 23 deletions(-)

+  create mode 100644 src/Common/tests/Tests/Interop/cgroupsTests.cs

+ 

+ diff --git a/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs b/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs

+ index 0ffd4d7b7c03..186fe0516c5b 100644

+ --- a/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs

+ +++ b/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs

+ @@ -9,17 +9,22 @@

+  

+  internal static partial class Interop

+  {

+ +    /// <summary>Provides access to some cgroup (v1 and v2) features</summary>

+      internal static partial class cgroups

+      {

+ +        // For cgroup v1, see https://www.kernel.org/doc/Documentation/cgroup-v1/

+ +        // For cgroup v2, see https://www.kernel.org/doc/Documentation/cgroup-v2.txt

+ +

+ +        /// <summary>The version of cgroup that's being used </summary>

+ +        internal enum CGroupVersion { None, CGroup1, CGroup2 };

+ +

+          /// <summary>Path to mountinfo file in procfs for the current process.</summary>

+          private const string ProcMountInfoFilePath = "/proc/self/mountinfo";

+          /// <summary>Path to cgroup directory in procfs for the current process.</summary>

+          private const string ProcCGroupFilePath = "/proc/self/cgroup";

+  

+ -        /// <summary>Path to the found cgroup location, or null if it couldn't be found.</summary>

+ -        internal static readonly string s_cgroupMemoryPath = FindCGroupPath("memory");

+ -        /// <summary>Path to the found cgroup memory limit_in_bytes path, or null if it couldn't be found.</summary>

+ -        private static readonly string s_cgroupMemoryLimitPath = s_cgroupMemoryPath != null ? s_cgroupMemoryPath + "/memory.limit_in_bytes" : null;

+ +        /// <summary>Path to the found cgroup memory limit path, or null if it couldn't be found.</summary>

+ +        internal static readonly string s_cgroupMemoryLimitPath = FindCGroupMemoryLimitPath();

+  

+          /// <summary>Tries to read the memory limit from the cgroup memory location.</summary>

+          /// <param name="limit">The read limit, or 0 if it couldn't be read.</param>

+ @@ -42,7 +47,7 @@ public static bool TryGetMemoryLimit(out ulong limit)

+          /// <param name="path">The path to the file to parse.</param>

+          /// <param name="result">The parsed result, or 0 if it couldn't be parsed.</param>

+          /// <returns>true if the value was read successfully; otherwise, false.</returns>

+ -        private static bool TryReadMemoryValueFromFile(string path, out ulong result)

+ +        internal static bool TryReadMemoryValueFromFile(string path, out ulong result)

+          {

+              if (File.Exists(path))

+              {

+ @@ -79,6 +84,11 @@ private static bool TryReadMemoryValueFromFile(string path, out ulong result)

+                          result = checked(ulongValue * multiplier);

+                          return true;

+                      }

+ +

+ +                    // 'max' is also a possible valid value

+ +                    //

+ +                    // Treat this as 'no memory limit' and let the caller

+ +                    // fallback to reading the real limit via other means

+                  }

+                  catch (Exception e)

+                  {

+ @@ -90,12 +100,35 @@ private static bool TryReadMemoryValueFromFile(string path, out ulong result)

+              return false;

+          }

+  

+ +        /// <summary>Find the cgroup memory limit path.</summary>

+ +        /// <returns>The limit path if found; otherwise, null.</returns>

+ +        private static string FindCGroupMemoryLimitPath()

+ +        {

+ +            string cgroupMemoryPath = FindCGroupPath("memory", out CGroupVersion version);

+ +            if (cgroupMemoryPath != null)

+ +            {

+ +                if (version == CGroupVersion.CGroup1)

+ +                {

+ +                    return cgroupMemoryPath + "/memory.limit_in_bytes";

+ +                }

+ +

+ +                if (version == 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 null;

+ +        }

+ +

+          /// <summary>Find the cgroup path for the specified subsystem.</summary>

+          /// <param name="subsystem">The subsystem, e.g. "memory".</param>

+          /// <returns>The cgroup path if found; otherwise, null.</returns>

+ -        private static string FindCGroupPath(string subsystem)

+ +        private static string FindCGroupPath(string subsystem, out CGroupVersion version)

+          {

+ -            if (TryFindHierarchyMount(subsystem, out string hierarchyRoot, out string hierarchyMount) &&

+ +            if (TryFindHierarchyMount(subsystem, out version, out string hierarchyRoot, out string hierarchyMount) &&

+                  TryFindCGroupPathForSubsystem(subsystem, out string cgroupPathRelativeToMount))

+              {

+                  // For a host cgroup, we need to append the relative path.

+ @@ -113,19 +146,24 @@ private static string FindCGroupPath(string subsystem)

+          /// <param name="root">The path of the directory in the filesystem which forms the root of this mount; null if not found.</param>

+          /// <param name="path">The path of the mount point relative to the process's root directory; null if not found.</param>

+          /// <returns>true if the mount was found; otherwise, null.</returns>

+ -        private static bool TryFindHierarchyMount(string subsystem, out string root, out string path)

+ +        private static bool TryFindHierarchyMount(string subsystem, out CGroupVersion version, out string root, out string path)

+          {

+ -            if (File.Exists(ProcMountInfoFilePath))

+ +            return TryFindHierarchyMount(ProcMountInfoFilePath, subsystem, out version, out root, out path);

+ +        }

+ +

+ +        internal static bool TryFindHierarchyMount(string mountInfoFilePath, string subsystem, out CGroupVersion version, out string root, out string path)

+ +        {

+ +            if (File.Exists(mountInfoFilePath))

+              {

+                  try

+                  {

+ -                    using (var reader = new StreamReader(ProcMountInfoFilePath))

+ +                    using (var reader = new StreamReader(mountInfoFilePath))

+                      {

+                          string line;

+                          while ((line = reader.ReadLine()) != null)

+                          {

+                              // Look for an entry that has cgroup as the "filesystem type"

+ -                            // and that has options containing the specified subsystem.

+ +                            // and, for cgroup1, that has options containing the specified subsystem

+                              // See man page for /proc/[pid]/mountinfo for details, e.g.:

+                              //     (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)

+                              //     36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue

+ @@ -148,17 +186,35 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out

+                                  continue;

+                              }

+  

+ -                            if (postSeparatorlineParts[0] != "cgroup" ||

+ -                                Array.IndexOf(postSeparatorlineParts[2].Split(','), subsystem) < 0)

+ +                            bool validCGroup1Entry = ((postSeparatorlineParts[0] == "cgroup") &&

+ +                                    (Array.IndexOf(postSeparatorlineParts[2].Split(','), subsystem) >= 0));

+ +                            bool validCGroup2Entry = postSeparatorlineParts[0] == "cgroup2";

+ +

+ +                            if (!validCGroup1Entry && !validCGroup2Entry)

+                              {

+                                  // Not the relevant entry.

+                                  continue;

+                              }

+  

+ -                            // Found the relevant entry.  Extract the mount root and path.

+ +                            // Found the relevant entry.  Extract the cgroup version, mount root and path.

+ +                            switch (postSeparatorlineParts[0])

+ +                            {

+ +                                case "cgroup":

+ +                                    version = CGroupVersion.CGroup1;

+ +                                    break;

+ +                                case "cgroup2":

+ +                                    version = CGroupVersion.CGroup2;

+ +                                    break;

+ +                                default:

+ +                                    version = CGroupVersion.None;

+ +                                    Debug.Fail($"invalid value for CGroupVersion \"{postSeparatorlineParts[0]}\"");

+ +                                    break;

+ +                            }

+ +

+                              string[] lineParts = line.Substring(0, endOfOptionalFields).Split(' ');

+                              root = lineParts[3];

+                              path = lineParts[4];

+ +

+                              return true;

+                          }

+                      }

+ @@ -169,6 +225,7 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out

+                  }

+              }

+  

+ +            version = CGroupVersion.None;

+              root = null;

+              path = null;

+              return false;

+ @@ -180,27 +237,42 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out

+          /// <returns></returns>

+          private static bool TryFindCGroupPathForSubsystem(string subsystem, out string path)

+          {

+ -            if (File.Exists(ProcCGroupFilePath))

+ +            return TryFindCGroupPathForSubsystem(ProcCGroupFilePath, subsystem, out path);

+ +        }

+ +

+ +        internal static bool TryFindCGroupPathForSubsystem(string procCGroupFilePath, string subsystem, out string path)

+ +        {

+ +            if (File.Exists(procCGroupFilePath))

+              {

+                  try

+                  {

+ -                    using (var reader = new StreamReader(ProcCGroupFilePath))

+ +                    using (var reader = new StreamReader(procCGroupFilePath))

+                      {

+                          string line;

+                          while ((line = reader.ReadLine()) != null)

+                          {

+ -                            // Find the first entry that has the subsystem listed in its controller

+ -                            // list. See man page for cgroups for /proc/[pid]/cgroups format, e.g:

+ -                            //     hierarchy-ID:controller-list:cgroup-path

+ -                            //     5:cpuacct,cpu,cpuset:/daemons

+ -

+                              string[] lineParts = line.Split(':');

+ +

+                              if (lineParts.Length != 3)

+                              {

+                                  // Malformed line.

+                                  continue;

+                              }

+  

+ +                            // cgroup v2: Find the first entry that matches the cgroup v2 hierarchy:

+ +                            //     0::$PATH

+ +

+ +                            if ((lineParts[0] == "0") && (string.Empty == lineParts[1]))

+ +                            {

+ +                                path = lineParts[2];

+ +                                return true;

+ +                            }

+ +

+ +                            // cgroup v1: Find the first entry that has the subsystem listed in its controller

+ +                            // list. See man page for cgroups for /proc/[pid]/cgroups format, e.g:

+ +                            //     hierarchy-ID:controller-list:cgroup-path

+ +                            //     5:cpuacct,cpu,cpuset:/daemons

+ +

+                              if (Array.IndexOf(lineParts[1].Split(','), subsystem) < 0)

+                              {

+                                  // Not the relevant entry.

+ @@ -214,7 +286,7 @@ private static bool TryFindCGroupPathForSubsystem(string subsystem, out string p

+                  }

+                  catch (Exception e)

+                  {

+ -                    Debug.Fail($"Failed to read or parse \"{ProcMountInfoFilePath}\": {e}");

+ +                    Debug.Fail($"Failed to read or parse \"{procCGroupFilePath}\": {e}");

+                  }

+              }

+  

+ diff --git a/src/Common/tests/Common.Tests.csproj b/src/Common/tests/Common.Tests.csproj

+ index a189d856348b..979c8dd7fbe6 100644

+ --- a/src/Common/tests/Common.Tests.csproj

+ +++ b/src/Common/tests/Common.Tests.csproj

+ @@ -12,6 +12,9 @@

+      <Compile Include="$(CommonTestPath)\System\Security\Cryptography\ByteUtils.cs">

+        <Link>Common\System\Security\Cryptography\ByteUtils.cs</Link>

+      </Compile>

+ +    <Compile Include="$(CommonPath)\Interop\Linux\cgroups\Interop.cgroups.cs">

+ +      <Link>Common\Interop\Linux\cgroups\Interop.cgroups.cs</Link>

+ +    </Compile>

+      <Compile Include="$(CommonPath)\Interop\Linux\procfs\Interop.ProcFsStat.cs">

+        <Link>Common\Interop\Linux\procfs\Interop.ProcFsStat.cs</Link>

+      </Compile>

+ @@ -69,6 +72,7 @@

+      <Compile Include="$(CommonPath)\CoreLib\System\PasteArguments.cs">

+        <Link>Common\CoreLib\System\PasteArguments.cs</Link>

+      </Compile>

+ +    <Compile Include="Tests\Interop\cgroupsTests.cs" />

+      <Compile Include="Tests\Interop\procfsTests.cs" />

+      <Compile Include="Tests\System\CharArrayHelpersTests.cs" />

+      <Compile Include="Tests\System\IO\PathInternal.Tests.cs" />

+ diff --git a/src/Common/tests/Tests/Interop/cgroupsTests.cs b/src/Common/tests/Tests/Interop/cgroupsTests.cs

+ new file mode 100644

+ index 000000000000..f16d9242879c

+ --- /dev/null

+ +++ b/src/Common/tests/Tests/Interop/cgroupsTests.cs

+ @@ -0,0 +1,107 @@

+ +// Licensed to the .NET Foundation under one or more agreements.

+ +// The .NET Foundation licenses this file to you under the MIT license.

+ +// See the LICENSE file in the project root for more information.

+ +

+ +using System;

+ +using System.IO;

+ +using System.Text;

+ +using Xunit;

+ +

+ +namespace Common.Tests

+ +{

+ +    public class cgroupsTests

+ +    {

+ +        [Theory]

+ +        [InlineData(true, "0",  0)]

+ +        [InlineData(false, "max",  0)]

+ +        [InlineData(true, "1k",  1024)]

+ +        [InlineData(true, "1K",  1024)]

+ +        public static void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue)

+ +        {

+ +            string path = Path.GetTempFileName();

+ +            try

+ +            {

+ +                File.WriteAllText(path, valueText);

+ +

+ +                bool result = Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val);

+ +

+ +                Assert.Equal(expectedResult, result);

+ +                if (result)

+ +                {

+ +                    Assert.Equal(expectedValue, val);

+ +                }

+ +            }

+ +            finally

+ +            {

+ +                File.Delete(path);

+ +            }

+ +        }

+ +

+ +        [Theory]

+ +        [InlineData(false, "0 0 0:0 / /foo ignore ignore - overlay overlay ignore", "ignore", 0, "/", "/")]

+ +        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "ignore", 2, "/", "/foo")]

+ +        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "memory", 2, "/", "/foo")]

+ +        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")]

+ +        [InlineData(true, "0 0 0:0 / /foo ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")]

+ +        [InlineData(true, "0 0 0:0 / /foo ignore ignore ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")]

+ +        [InlineData(true, "0 0 0:0 / /foo-with-dashes ignore ignore - cgroup2 cgroup2 ignore", "ignore", 2, "/", "/foo-with-dashes")]

+ +        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory", "memory", 1, "/", "/foo")]

+ +        [InlineData(true, "0 0 0:0 / /foo-with-dashes ignore ignore - cgroup cgroup memory", "memory", 1, "/", "/foo-with-dashes")]

+ +        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu,memory", "memory", 1, "/", "/foo")]

+ +        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory,cpu", "memory", 1, "/", "/foo")]

+ +        [InlineData(false, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu", "memory", 0, "/", "/foo")]

+ +        public static void ParseValidateMountInfo(bool found, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount)

+ +        {

+ +            string path = Path.GetTempFileName();

+ +            try

+ +            {

+ +                File.WriteAllText(path, procSelfMountInfoText);

+ +

+ +                bool result = Interop.cgroups.TryFindHierarchyMount(path, subsystem,  out Interop.cgroups.CGroupVersion version, out string root, out string mount);

+ +

+ +                Assert.Equal(found, result);

+ +                if (found)

+ +                {

+ +                    Assert.Equal(expectedVersion, (int)version);

+ +                    Assert.Equal(expectedRoot, root);

+ +                    Assert.Equal(expectedMount, mount);

+ +                }

+ +            }

+ +            finally

+ +            {

+ +                File.Delete(path);

+ +            }

+ +        }

+ +

+ +        [Theory]

+ +        [InlineData(true, "0::/foo", "ignore", "/foo")]

+ +        [InlineData(true, "0::/bar", "ignore", "/bar")]

+ +        [InlineData(true, "0::frob", "ignore", "frob")]

+ +        [InlineData(false, "1::frob", "ignore", "ignore")]

+ +        [InlineData(true, "1:foo:bar", "foo", "bar")]

+ +        [InlineData(true, "2:foo:bar", "foo", "bar")]

+ +        [InlineData(false, "2:foo:bar", "bar", "ignore")]

+ +        [InlineData(true, "1:foo:bar\n2:eggs:spam", "foo", "bar")]

+ +        [InlineData(true, "1:foo:bar\n2:eggs:spam", "eggs", "spam")]

+ +        public static void ParseValidateProcCGroup(bool found, string procSelfCgroupText, string subsystem, string expectedMountPath)

+ +        {

+ +            string path = Path.GetTempFileName();

+ +            try

+ +            {

+ +                File.WriteAllText(path, procSelfCgroupText);

+ +

+ +                bool result = Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem,  out string mountPath);

+ +

+ +                Assert.Equal(found, result);

+ +                if (found)

+ +                {

+ +                    Assert.Equal(expectedMountPath, mountPath);

+ +                }

+ +            }

+ +            finally

+ +            {

+ +                File.Delete(path);

+ +            }

+ +        }

+ +    }

+ +}

+ diff --git a/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs b/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs

+ index 910af2fd82b4..73f692898dbc 100644

+ --- a/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs

+ +++ b/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs

+ @@ -40,7 +40,7 @@ public void DumpRuntimeInformationToConsole()

+  

+              Console.WriteLine($"### CURRENT DIRECTORY: {Environment.CurrentDirectory}");

+  

+ -            string cgroupsLocation = Interop.cgroups.s_cgroupMemoryPath;

+ +            string cgroupsLocation = Interop.cgroups.s_cgroupMemoryLimitPath;

+              if (cgroupsLocation != null)

+              {

+                  Console.WriteLine($"### CGROUPS MEMORY: {cgroupsLocation}");

+ 

@@ -0,0 +1,129 @@ 

+ From 9a8c5e4014ffca8aff70808cc0e50a403d38c292 Mon Sep 17 00:00:00 2001

+ From: Stephen Toub <stoub@microsoft.com>

+ Date: Wed, 23 Oct 2019 20:35:49 -0400

+ Subject: [PATCH 2/2] Clean up new tests

+ 

+ ---

+  .../tests/Tests/Interop/cgroupsTests.cs       | 79 ++++++-------------

+  1 file changed, 25 insertions(+), 54 deletions(-)

+ 

+ diff --git a/src/Common/tests/Tests/Interop/cgroupsTests.cs b/src/Common/tests/Tests/Interop/cgroupsTests.cs

+ index f16d9242879c..fc6ab5c9753c 100644

+ --- a/src/Common/tests/Tests/Interop/cgroupsTests.cs

+ +++ b/src/Common/tests/Tests/Interop/cgroupsTests.cs

+ @@ -2,38 +2,27 @@

+  // The .NET Foundation licenses this file to you under the MIT license.

+  // See the LICENSE file in the project root for more information.

+  

+ -using System;

+  using System.IO;

+ -using System.Text;

+  using Xunit;

+  

+  namespace Common.Tests

+  {

+ -    public class cgroupsTests

+ +    public class cgroupsTests : FileCleanupTestBase

+      {

+          [Theory]

+ -        [InlineData(true, "0",  0)]

+ -        [InlineData(false, "max",  0)]

+ -        [InlineData(true, "1k",  1024)]

+ -        [InlineData(true, "1K",  1024)]

+ -        public static void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue)

+ +        [InlineData(true, "0", 0)]

+ +        [InlineData(false, "max", 0)]

+ +        [InlineData(true, "1k", 1024)]

+ +        [InlineData(true, "1K", 1024)]

+ +        public void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue)

+          {

+ -            string path = Path.GetTempFileName();

+ -            try

+ -            {

+ -                File.WriteAllText(path, valueText);

+ -

+ -                bool result = Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val);

+ +            string path = GetTestFilePath();

+ +            File.WriteAllText(path, valueText);

+  

+ -                Assert.Equal(expectedResult, result);

+ -                if (result)

+ -                {

+ -                    Assert.Equal(expectedValue, val);

+ -                }

+ -            }

+ -            finally

+ +            Assert.Equal(expectedResult, Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val));

+ +            if (expectedResult)

+              {

+ -                File.Delete(path);

+ +                Assert.Equal(expectedValue, val);

+              }

+          }

+  

+ @@ -50,26 +39,17 @@ public static void ValidateTryReadMemoryValue(bool expectedResult, string valueT

+          [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu,memory", "memory", 1, "/", "/foo")]

+          [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory,cpu", "memory", 1, "/", "/foo")]

+          [InlineData(false, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu", "memory", 0, "/", "/foo")]

+ -        public static void ParseValidateMountInfo(bool found, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount)

+ +        public void ParseValidateMountInfo(bool expectedFound, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount)

+          {

+ -            string path = Path.GetTempFileName();

+ -            try

+ -            {

+ -                File.WriteAllText(path, procSelfMountInfoText);

+ -

+ -                bool result = Interop.cgroups.TryFindHierarchyMount(path, subsystem,  out Interop.cgroups.CGroupVersion version, out string root, out string mount);

+ +            string path = GetTestFilePath();

+ +            File.WriteAllText(path, procSelfMountInfoText);

+  

+ -                Assert.Equal(found, result);

+ -                if (found)

+ -                {

+ -                    Assert.Equal(expectedVersion, (int)version);

+ -                    Assert.Equal(expectedRoot, root);

+ -                    Assert.Equal(expectedMount, mount);

+ -                }

+ -            }

+ -            finally

+ +            Assert.Equal(expectedFound, Interop.cgroups.TryFindHierarchyMount(path, subsystem, out Interop.cgroups.CGroupVersion version, out string root, out string mount));

+ +            if (expectedFound)

+              {

+ -                File.Delete(path);

+ +                Assert.Equal(expectedVersion, (int)version);

+ +                Assert.Equal(expectedRoot, root);

+ +                Assert.Equal(expectedMount, mount);

+              }

+          }

+  

+ @@ -83,24 +63,15 @@ public static void ParseValidateMountInfo(bool found, string procSelfMountInfoTe

+          [InlineData(false, "2:foo:bar", "bar", "ignore")]

+          [InlineData(true, "1:foo:bar\n2:eggs:spam", "foo", "bar")]

+          [InlineData(true, "1:foo:bar\n2:eggs:spam", "eggs", "spam")]

+ -        public static void ParseValidateProcCGroup(bool found, string procSelfCgroupText, string subsystem, string expectedMountPath)

+ +        public void ParseValidateProcCGroup(bool expectedFound, string procSelfCgroupText, string subsystem, string expectedMountPath)

+          {

+ -            string path = Path.GetTempFileName();

+ -            try

+ -            {

+ -                File.WriteAllText(path, procSelfCgroupText);

+ +            string path = GetTestFilePath();

+ +            File.WriteAllText(path, procSelfCgroupText);

+  

+ -                bool result = Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem,  out string mountPath);

+ -

+ -                Assert.Equal(found, result);

+ -                if (found)

+ -                {

+ -                    Assert.Equal(expectedMountPath, mountPath);

+ -                }

+ -            }

+ -            finally

+ +            Assert.Equal(expectedFound, Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem, out string mountPath));

+ +            if (expectedFound)

+              {

+ -                File.Delete(path);

+ +                Assert.Equal(expectedMountPath, mountPath);

+              }

+          }

+      }

file modified
+14 -3
@@ -52,7 +52,7 @@ 

  

  Name:           dotnet3.0

  Version:        %{sdk_rpm_version}

- Release:        4%{?dist}

+ Release:        5%{?dist}

  Summary:        .NET Core CLI tools and runtime

  License:        MIT and ASL 2.0 and BSD

  URL:            https://github.com/dotnet/
@@ -64,9 +64,14 @@ 

  Source2:        dotnet.sh.in

  

  Patch100:       corefx-optflags-support.patch

+ Patch101:       corefx-39686-cgroupv2-01.patch

+ Patch102:       corefx-39686-cgroupv2-02.patch

+ Patch103:       corefx-39633-cgroupv2-mountpoints.patch

  

  Patch200:       coreclr-27048-sysctl-deprecation.patch

  Patch201:       coreclr-hardening-flags.patch

+ Patch202:       coreclr-cgroupv2.patch

+ Patch203:       coreclr-cgroupv2-02.patch

  

  Patch300:       core-setup-do-not-strip.patch

  Patch301:       core-setup-hardening-flags.patch
@@ -94,13 +99,11 @@ 

  %if ! %{use_bundled_libunwind}

  BuildRequires:  libunwind-devel

  %endif

- BuildRequires:  libuuid-devel

  BuildRequires:  lldb-devel

  BuildRequires:  llvm

  BuildRequires:  lttng-ust-devel

  BuildRequires:  openssl-devel

  BuildRequires:  python3

- BuildRequires:  strace

  BuildRequires:  tar

  BuildRequires:  zlib-devel

  
@@ -302,11 +305,16 @@ 

  

  pushd src/corefx.*

  %patch100 -p1

+ %patch101 -p1

+ %patch102 -p1

+ %patch103 -p1

  popd

  

  pushd src/coreclr.*

  %patch200 -p1

  %patch201 -p1

+ %patch202 -p1

+ %patch203 -p1

  popd

  

  pushd src/core-setup.*
@@ -438,6 +446,9 @@ 

  %dir %{_libdir}/dotnet/packs

  

  %changelog

+ * Thu Oct 24 2019 Omair Majid <omajid@redhat.com> - 3.0.100-5

+ - Add cgroupv2 support to .NET Core

+ 

  * Wed Oct 16 2019 Omair Majid <omajid@redhat.com> - 3.0.100-4

  - Include fix from coreclr for building on Fedora 32

  

Nice work @omajid !

I don't intend to look into this/try it until I have a fedora 32 install. I think this is important, but not urgent.

@tmds Just for the record, Fedora 31 (released today, I think) has cgroupv2 enabled by default. Everything not supporting cgroupv2 (eg, docker/moby) is broken. This seems somewhat urgent to me.

What is the effect for a .NET Core app that only supports cgroupv1?
Did cgroupv2 remove the files that were used by v1, or is there some backwards compatibility?

What is the effect for a .NET Core app that only supports cgroupv1?

It wont be able to run on a kernel using cgroup2 (eg, Fedora 31, or even Fedora 30 with modified kernel boot parameters).

Did cgroupv2 remove the files that were used by v1, or is there some backwards compatibility?

There's been some re-organziation of things on the kernel side. The cgroup hierarchy is a bit different between the two cgroup versions. As are the names and contents of files that contain data. There's no backward or forward compatibility: an application that only handles cgroup v1 locations and data will not work with cgroup v2 (and the other way around).

However, it's pretty easy to support both cgroup versions. We can detect the cgroup version at runtime and read the appropriate configuration/files.

It wont be able to run on a kernel using cgroup2

Ah. I may be misremembering.. I thought not being able to parse cgroup info was not a fatal error.

an application that only handles cgroup v1 locations and data will not work with cgroup v2 (and the other way around).

I see. The kernel doesn't often break backwards compatibility.
I had noticed in your PR that the names don't overlap, so I thought both might still be available.

Ah. I may be misremembering.. I thought not being able to parse cgroup info was not a fatal error.

My bad. You are correct: it's not a fatal error. .NET Core just wont realize it's running under cgroup restrictions.

3.0 has been EOL for ages

Pull-Request has been closed by omajid

a year ago