memory limit in containers on linux
authorrahul <rahku@github.com>
Thu, 9 Mar 2017 02:55:24 +0000 (18:55 -0800)
committerrahul <rahku@github.com>
Thu, 23 Mar 2017 06:36:32 +0000 (23:36 -0700)
12 files changed:
src/gc/unix/CMakeLists.txt
src/gc/unix/cgroup.cpp [new file with mode: 0644]
src/gc/unix/gcenv.unix.cpp
src/pal/inc/pal.h
src/pal/src/CMakeLists.txt
src/pal/src/misc/cgroup.cpp [new file with mode: 0644]
src/pal/tests/palsuite/miscellaneous/CGroup/CMakeLists.txt [new file with mode: 0644]
src/pal/tests/palsuite/miscellaneous/CGroup/test1/CMakeLists.txt [new file with mode: 0644]
src/pal/tests/palsuite/miscellaneous/CGroup/test1/test.cpp [new file with mode: 0644]
src/pal/tests/palsuite/miscellaneous/CGroup/test1/testinfo.dat [new file with mode: 0644]
src/pal/tests/palsuite/miscellaneous/CMakeLists.txt
src/vm/gcenv.os.cpp

index ef66abf..3e1aa5a 100644 (file)
@@ -5,6 +5,7 @@ include_directories("../env")
 include(configure.cmake)
 
 set(GC_PAL_SOURCES
-    gcenv.unix.cpp)
+    gcenv.unix.cpp
+    cgroup.cpp)
 
 add_library(gc_unix STATIC ${GC_PAL_SOURCES} ${VERSION_FILE_PATH})
diff --git a/src/gc/unix/cgroup.cpp b/src/gc/unix/cgroup.cpp
new file mode 100644 (file)
index 0000000..1775ef7
--- /dev/null
@@ -0,0 +1,342 @@
+// 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.
+
+/*++
+
+Module Name:
+
+    cgroup.cpp
+
+Abstract:
+    Read memory limits for the current process
+--*/
+#include <cstdint>
+#include <cstddef>
+#include <cassert>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <errno.h>
+
+#define SIZE_T_MAX (~(size_t)0)
+#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"
+
+class CGroup
+{
+    char* m_memory_cgroup_path;
+public:
+    CGroup()
+    {
+        m_memory_cgroup_path = nullptr;
+        char* memoryHierarchyMount = nullptr;
+        char *cgroup_path_relative_to_mount = nullptr;
+        size_t len;
+        memoryHierarchyMount = FindMemoryHierarchyMount();
+        if (memoryHierarchyMount == nullptr)
+            goto done;
+
+        cgroup_path_relative_to_mount = FindCGroupPathForMemorySubsystem();
+        if (cgroup_path_relative_to_mount == nullptr)
+            goto done;
+
+        len = strlen(memoryHierarchyMount);
+        len += strlen(cgroup_path_relative_to_mount);
+        m_memory_cgroup_path = (char*)malloc(len+1);
+        if (m_memory_cgroup_path == nullptr)
+           goto done;
+        
+        strcpy(m_memory_cgroup_path, memoryHierarchyMount);
+        strcat(m_memory_cgroup_path, cgroup_path_relative_to_mount);
+
+    done:
+        free(memoryHierarchyMount);
+        free(cgroup_path_relative_to_mount);
+    }
+
+    ~CGroup()
+    {
+        free(m_memory_cgroup_path);
+    }
+
+    bool GetPhysicalMemoryLimit(size_t *val)
+    {
+        char *mem_limit_filename = nullptr;
+        bool result = false;
+
+        if (m_memory_cgroup_path == nullptr)
+            return result;
+
+        size_t len = strlen(m_memory_cgroup_path);
+        len += strlen(MEM_LIMIT_FILENAME);
+        mem_limit_filename = (char*)malloc(len+1);
+        if (mem_limit_filename == nullptr)
+            return result;
+
+        strcpy(mem_limit_filename, m_memory_cgroup_path);
+        strcat(mem_limit_filename, MEM_LIMIT_FILENAME);
+        result = ReadMemoryValueFromFile(mem_limit_filename, val);
+        free(mem_limit_filename);
+        return result;
+    }
+    
+private:
+    char* FindMemoryHierarchyMount()
+    {
+        char *line = nullptr;
+        size_t lineLen = 0, maxLineLen = 0;
+        char *filesystemType = nullptr;
+        char *options = nullptr;
+        char* mountpath = nullptr;
+
+        FILE *mountinfofile = fopen(PROC_MOUNTINFO_FILENAME, "r");
+        if (mountinfofile == nullptr)
+            goto done;
+    
+        while (getline(&line, &lineLen, mountinfofile) != -1)
+        {
+            if (filesystemType == nullptr || lineLen > maxLineLen)
+            {
+                free(filesystemType);
+                free(options);
+                filesystemType = (char*)malloc(lineLen+1);
+                if (filesystemType == nullptr)
+                    goto done;
+                options = (char*)malloc(lineLen+1);
+                if (options == nullptr)
+                    goto done;
+                maxLineLen = lineLen;
+            }
+
+            char* separatorChar = strchr(line, '-');
+
+            // See man page of proc to get format for /proc/self/mountinfo file
+            int sscanfRet = sscanf(separatorChar, 
+                                   "- %s %*s %s",
+                                   filesystemType,
+                                   options);
+            if (sscanfRet != 2)
+            {
+                assert(!"Failed to parse mount info file contents with sscanf.");
+                goto done;
+            }
+    
+            if (strncmp(filesystemType, "cgroup", 6) == 0)
+            {
+                char* context = nullptr;
+                char* strTok = strtok_r(options, ",", &context); 
+                while (strTok != nullptr)
+                {
+                    if (strncmp("memory", strTok, 6) == 0)
+                    {
+                        mountpath = (char*)malloc(lineLen+1);
+                        if (mountpath == nullptr)
+                            goto done;
+    
+                        sscanfRet = sscanf(line,
+                                           "%*s %*s %*s %*s %s ",
+                                           mountpath);
+                        if (sscanfRet != 1)
+                        {
+                            free(mountpath);
+                            mountpath = nullptr;
+                            assert(!"Failed to parse mount info file contents with sscanf.");
+                        }
+                        goto done;
+                    }
+                    strTok = strtok_r(nullptr, ",", &context);
+                }
+            }
+        }
+    done:
+        free(filesystemType);
+        free(options);
+        free(line);
+        if (mountinfofile)
+            fclose(mountinfofile);
+        return mountpath;
+    }
+    
+    char* FindCGroupPathForMemorySubsystem()
+    {
+        char *line = nullptr;
+        size_t lineLen = 0;
+        size_t maxLineLen = 0;
+        char *subsystem_list = nullptr;
+        char *cgroup_path = nullptr;
+        bool result = false;
+
+        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)
+            {
+                free(subsystem_list);
+                free(cgroup_path);
+                subsystem_list = (char*)malloc(lineLen+1);
+                if (subsystem_list == nullptr)
+                    goto done;
+                cgroup_path = (char*)malloc(lineLen+1);
+                if (cgroup_path == nullptr)
+                    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)
+            {
+                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 (strncmp("memory", strTok, 6) == 0)
+                {
+                    result = true;
+                    break;  
+                }
+                strTok = strtok_r(nullptr, ",", &context);
+            }
+        }
+    done:
+        free(subsystem_list);
+        if (!result)
+        {
+            free(cgroup_path);
+            cgroup_path = nullptr;
+        }
+        free(line);
+        if (cgroupfile)
+            fclose(cgroupfile);
+        return cgroup_path;
+    }
+    
+    bool ReadMemoryValueFromFile(const char* filename, size_t* val)
+    {
+        bool result = false;
+        char *line = nullptr;
+        size_t lineLen = 0;
+        char* endptr = nullptr;
+        size_t num = 0, l, multiplier;
+        FILE* file = nullptr;
+    
+        if (val == nullptr)
+            goto done;
+    
+        file = fopen(filename, "r");
+        if (file == nullptr)
+            goto done;
+        
+        if (getline(&line, &lineLen, file) == -1)
+            goto done;
+    
+        errno = 0;
+        num = strtoull(line, &endptr, 0); 
+        if (errno != 0)
+            goto done;
+    
+        multiplier = 1;
+        switch(*endptr)
+        {
+            case 'g':
+            case 'G': multiplier = 1024;
+            case 'm': 
+            case 'M': multiplier = multiplier*1024;
+            case 'k':
+            case 'K': multiplier = multiplier*1024;
+        }
+    
+        *val = num * multiplier;
+        result = true;
+        if (*val/multiplier != num)
+            result = false;
+    done:
+        if (file)
+            fclose(file);
+        free(line);    
+        return result;
+    }
+};
+   
+size_t GetRestrictedPhysicalMemoryLimit()
+{
+    CGroup cgroup;
+    size_t physical_memory_limit;
+    if (!cgroup.GetPhysicalMemoryLimit(&physical_memory_limit))
+         physical_memory_limit = SIZE_T_MAX;
+
+    struct rlimit curr_rlimit;
+    size_t rlimit_soft_limit = RLIM_INFINITY;
+    if (getrlimit(RLIMIT_AS, &curr_rlimit) == 0)
+    {
+        rlimit_soft_limit = curr_rlimit.rlim_cur;
+    }
+    physical_memory_limit = (physical_memory_limit < rlimit_soft_limit) ? 
+                            physical_memory_limit : rlimit_soft_limit;
+
+    // Ensure that limit is not greater than real memory size
+    long pages = sysconf(_SC_PHYS_PAGES);
+    if (pages != -1) 
+    {
+        long pageSize = sysconf(_SC_PAGE_SIZE);
+        if (pageSize != -1)
+        {
+            physical_memory_limit = (physical_memory_limit < (size_t)pages * pageSize)?
+                                    physical_memory_limit : (size_t)pages * pageSize;
+        }
+    }
+
+    return physical_memory_limit;
+}
+
+bool GetWorkingSetSize(size_t* val)
+{
+    bool result = false;
+    size_t linelen;
+    char* line = nullptr;
+
+    if (val == nullptr)
+        return false;
+
+    FILE* file = fopen(PROC_STATM_FILENAME, "r");
+    if (file != nullptr && getline(&line, &linelen, file) != -1)
+    {
+
+        char* context = nullptr;
+        char* strTok = strtok_r(line, " ", &context); 
+        strTok = strtok_r(nullptr, " ", &context); 
+
+        errno = 0;
+        *val = strtoull(strTok, nullptr, 0); 
+        if (errno == 0)
+        {
+            long pageSize = sysconf(_SC_PAGE_SIZE);
+            if (pageSize != -1)
+            {
+                *val = *val * pageSize;
+                result = true;
+            }
+        }
+    }
+
+    if (file)
+        fclose(file);
+    free(line);
+    return result;
+}
index 34a45b3..45489c6 100644 (file)
@@ -78,6 +78,11 @@ static uint8_t g_helperPage[OS_PAGE_SIZE] __attribute__((aligned(OS_PAGE_SIZE)))
 // Mutex to make the FlushProcessWriteBuffersMutex thread safe
 static pthread_mutex_t g_flushProcessWriteBuffersMutex;
 
+size_t GetRestrictedPhysicalMemoryLimit();
+bool GetWorkingSetSize(size_t* val);
+
+static size_t g_RestrictedPhysicalMemoryLimit = 0;
+
 // Initialize the interface implementation
 // Return:
 //  true if it has succeeded, false if it has failed
@@ -442,6 +447,18 @@ size_t GCToOSInterface::GetVirtualMemoryLimit()
 //  specified, it returns amount of actual physical memory.
 uint64_t GCToOSInterface::GetPhysicalMemoryLimit()
 {
+    size_t restricted_limit;
+    // The limit was not cached
+    if (g_RestrictedPhysicalMemoryLimit == 0)
+    {
+        restricted_limit = GetRestrictedPhysicalMemoryLimit();
+        VolatileStore(&g_RestrictedPhysicalMemoryLimit, restricted_limit);
+    }
+    restricted_limit = g_RestrictedPhysicalMemoryLimit;
+
+    if (restricted_limit != 0 && restricted_limit != SIZE_T_MAX)
+        return restricted_limit;
+
     long pages = sysconf(_SC_PHYS_PAGES);
     if (pages == -1) 
     {
@@ -471,14 +488,14 @@ void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available
 
         uint64_t available = 0;
         uint32_t load = 0;
+        size_t used;
 
         // Get the physical memory in use - from it, we can get the physical memory available.
         // We do this only when we have the total physical memory available.
-        if (total > 0)
+        if (total > 0 && GetWorkingSetSize(&used))
         {
-            available = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE);
-            uint64_t used = total - available;
-            load = (uint32_t)((used * 100) / total);
+            available = total > used ? total-used : 0; 
+            load = (uint32_t)(((float)used * 100) / (float)total);
         }
 
         if (memory_load != nullptr)
index 8d099af..1d74358 100644 (file)
@@ -538,7 +538,6 @@ PAL_ProbeMemory(
     BOOL fWriteAccess);
 
 /******************* winuser.h Entrypoints *******************************/
-
 PALIMPORT
 LPSTR
 PALAPI
@@ -2536,6 +2535,16 @@ PAL_GetLogicalCpuCountFromOS(VOID);
 PALIMPORT
 size_t
 PALAPI
+PAL_GetRestrictedPhysicalMemoryLimit(VOID);
+
+PALIMPORT
+BOOL
+PALAPI
+PAL_GetWorkingSetSize(size_t* val);
+
+PALIMPORT
+size_t
+PALAPI
 PAL_GetLogicalProcessorCacheSizeFromOS(VOID);
 
 typedef BOOL (*ReadMemoryWordCallback)(SIZE_T address, SIZE_T *value);
index 5142dcc..f21c9db 100644 (file)
@@ -168,6 +168,7 @@ set(SOURCES
   map/virtual.cpp
   memory/heap.cpp
   memory/local.cpp
+  misc/cgroup.cpp
   misc/dbgmsg.cpp
   misc/environ.cpp
   misc/error.cpp
diff --git a/src/pal/src/misc/cgroup.cpp b/src/pal/src/misc/cgroup.cpp
new file mode 100644 (file)
index 0000000..4017803
--- /dev/null
@@ -0,0 +1,335 @@
+// 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.
+
+/*++
+
+Module Name:
+
+    cgroup.cpp
+
+Abstract:
+    Read memory limits for the current process
+--*/
+
+#include "pal/dbgmsg.h"
+SET_DEFAULT_DEBUG_CHANNEL(MISC);
+#include "pal/palinternal.h"
+#include <sys/resource.h>
+#include "pal/virtual.h"
+
+#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"
+
+class CGroup
+{
+    char *m_memory_cgroup_path;
+public:
+    CGroup()
+    {
+        m_memory_cgroup_path = nullptr;
+        char* memoryHierarchyMount = nullptr;
+        char *cgroup_path_relative_to_mount = nullptr;
+        size_t len;
+        memoryHierarchyMount = FindMemoryHierarchyMount();
+        if (memoryHierarchyMount == nullptr)
+            goto done;
+
+        cgroup_path_relative_to_mount = FindCGroupPathForMemorySubsystem();
+        if (cgroup_path_relative_to_mount == nullptr)
+            goto done;
+
+        len = strlen(memoryHierarchyMount);
+        len += strlen(cgroup_path_relative_to_mount);
+        m_memory_cgroup_path = (char*)PAL_malloc(len+1);
+        if (m_memory_cgroup_path == nullptr)
+           goto done;
+        
+        strcpy_s(m_memory_cgroup_path, len+1, memoryHierarchyMount);
+        strcat_s(m_memory_cgroup_path, len+1, cgroup_path_relative_to_mount);
+
+    done:
+        PAL_free(memoryHierarchyMount);
+        PAL_free(cgroup_path_relative_to_mount);
+    }
+    ~CGroup()
+    {
+        PAL_free(m_memory_cgroup_path);
+    }
+    
+    bool GetPhysicalMemoryLimit(size_t *val)
+    {
+        char *mem_limit_filename = nullptr;
+        bool result = false;
+
+        if (m_memory_cgroup_path == nullptr)
+            return result;
+
+        size_t len = strlen(m_memory_cgroup_path);
+        len += strlen(MEM_LIMIT_FILENAME);
+        mem_limit_filename = (char*)PAL_malloc(len+1);
+        if (mem_limit_filename == nullptr)
+            return result;
+
+        strcpy_s(mem_limit_filename, len+1, m_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;
+    }
+private:
+    char* FindMemoryHierarchyMount()
+    {
+        char *line = nullptr;
+        size_t lineLen = 0, maxLineLen = 0;
+        char *filesystemType = nullptr;
+        char *options = nullptr;
+        char* mountpath = nullptr;
+
+        FILE *mountinfofile = fopen(PROC_MOUNTINFO_FILENAME, "r");
+        if (mountinfofile == nullptr)
+            goto done;
+
+        while (getline(&line, &lineLen, mountinfofile) != -1)
+        {
+            if (filesystemType == nullptr || lineLen > maxLineLen)
+            {
+                PAL_free(filesystemType);
+                PAL_free(options);
+                filesystemType = (char*)PAL_malloc(lineLen+1);
+                if (filesystemType == nullptr)
+                    goto done;
+                options = (char*)PAL_malloc(lineLen+1);
+                if (options == nullptr)
+                    goto done;
+                maxLineLen = lineLen;
+            }
+            char* separatorChar = strchr(line, '-');
+
+            // See man page of proc to get format for /proc/self/mountinfo file
+            int sscanfRet = sscanf_s(separatorChar, 
+                                     "- %s %*s %s",
+                                     filesystemType, lineLen+1,
+                                     options, lineLen+1);
+            if (sscanfRet != 2)
+            {
+                _ASSERTE(!"Failed to parse mount info file contents with sscanf_s.");
+                goto done;
+            }
+
+            if (strncmp(filesystemType, "cgroup", 6) == 0)
+            {
+                char* context = nullptr;
+                char* strTok = strtok_s(options, ",", &context); 
+                while (strTok != nullptr)
+                {
+                    if (strncmp("memory", strTok, 6) == 0)
+                    {
+                        mountpath = (char*)PAL_malloc(lineLen+1);
+                        if (mountpath == nullptr)
+                            goto done;
+
+                        sscanfRet = sscanf_s(line,
+                                             "%*s %*s %*s %*s %s ",
+                                             mountpath, lineLen+1);
+                        if (sscanfRet != 1)
+                        {
+                            PAL_free(mountpath);
+                            mountpath = nullptr;
+                            _ASSERTE(!"Failed to parse mount info file contents with sscanf_s.");
+                        }
+                        goto done;
+                    }
+                    strTok = strtok_s(nullptr, ",", &context);
+                }
+            }
+        }
+    done:
+        PAL_free(filesystemType);
+        PAL_free(options);
+        free(line);
+        if (mountinfofile)
+            fclose(mountinfofile);
+        return mountpath;
+    }
+
+    char* FindCGroupPathForMemorySubsystem()
+    {
+        char *line = nullptr;
+        size_t lineLen = 0;
+        size_t maxLineLen = 0;
+        char *subsystem_list = nullptr;
+        char *cgroup_path = nullptr;
+        bool result = false;
+
+        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)
+            {
+                PAL_free(subsystem_list);
+                PAL_free(cgroup_path);
+                subsystem_list = (char*)PAL_malloc(lineLen+1);
+                if (subsystem_list == nullptr)
+                    goto done;
+                cgroup_path = (char*)PAL_malloc(lineLen+1);
+                if (cgroup_path == nullptr)
+                    goto done;
+                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)
+            {
+                _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 (strncmp("memory", strTok, 6) == 0)
+                {
+                    result = true;
+                    break;  
+                }
+                strTok = strtok_s(nullptr, ",", &context);
+            }
+        }
+    done:
+        PAL_free(subsystem_list);
+        if (!result)
+        {
+            PAL_free(cgroup_path);
+            cgroup_path = nullptr;
+        }
+        free(line);
+        if (cgroupfile)
+            fclose(cgroupfile);
+        return cgroup_path;
+    }
+
+    bool ReadMemoryValueFromFile(const char* filename, size_t* val)
+    {
+        bool result = false;
+        char *line = nullptr;
+        size_t lineLen = 0;
+        char* endptr = nullptr;
+        size_t num = 0, l, multiplier;
+
+        if (val == nullptr)
+            return false;
+
+        FILE* file = fopen(filename, "r");
+        if (file == nullptr)
+            goto done;
+        
+        if (getline(&line, &lineLen, file) == -1)
+            goto done;
+
+        errno = 0;
+        num = strtoull(line, &endptr, 0); 
+        if (errno != 0)
+            goto done;
+
+        multiplier = 1;
+        switch(*endptr)
+        {
+            case 'g':
+            case 'G': multiplier = 1024;
+            case 'm': 
+            case 'M': multiplier = multiplier*1024;
+            case 'k':
+            case 'K': multiplier = multiplier*1024;
+        }
+
+        *val = num * multiplier;
+        result = true;
+        if (*val/multiplier != num)
+            result = false;
+    done:
+        if (file)
+            fclose(file);
+        free(line);    
+        return result;
+    }
+};
+
+
+size_t
+PALAPI
+PAL_GetRestrictedPhysicalMemoryLimit()
+{
+    CGroup cgroup;
+    size_t physical_memory_limit;
+
+    if (!cgroup.GetPhysicalMemoryLimit(&physical_memory_limit))
+         physical_memory_limit = SIZE_T_MAX;
+
+    struct rlimit curr_rlimit;
+    size_t rlimit_soft_limit = (size_t)RLIM_INFINITY;
+    if (getrlimit(RLIMIT_AS, &curr_rlimit) == 0)
+    {
+        rlimit_soft_limit = curr_rlimit.rlim_cur;
+    }
+    physical_memory_limit = min(physical_memory_limit, rlimit_soft_limit);
+
+    // Ensure that limit is not greater than real memory size
+    long pages = sysconf(_SC_PHYS_PAGES);
+    if (pages != -1) 
+    {
+        long pageSize = sysconf(_SC_PAGE_SIZE);
+        if (pageSize != -1)
+        {
+            physical_memory_limit = min(physical_memory_limit, 
+                                        (size_t)pages * pageSize);
+        }
+    }
+
+    if(physical_memory_limit == SIZE_T_MAX)
+        physical_memory_limit = 0;
+    return physical_memory_limit;
+}
+
+BOOL
+PALAPI
+PAL_GetWorkingSetSize(size_t* val)
+{
+    BOOL result = false;
+    size_t linelen;
+    char* line = nullptr;
+
+    if (val == nullptr)
+        return FALSE;
+
+    FILE* file = fopen(PROC_STATM_FILENAME, "r");
+    if (file != nullptr && getline(&line, &linelen, file) != -1)
+    {
+        char* context = nullptr;
+        char* strTok = strtok_s(line, " ", &context); 
+        strTok = strtok_s(nullptr, " ", &context); 
+
+        errno = 0;
+        *val = strtoull(strTok, nullptr, 0); 
+        if(errno == 0)
+        {
+            *val = *val * VIRTUAL_PAGE_SIZE;
+            result = true;
+        }
+    }
+
+    if (file)
+        fclose(file);
+    free(line);
+    return result;
+}
diff --git a/src/pal/tests/palsuite/miscellaneous/CGroup/CMakeLists.txt b/src/pal/tests/palsuite/miscellaneous/CGroup/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f6aa0cb
--- /dev/null
@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 2.8.12.2)
+
+add_subdirectory(test1)
+
diff --git a/src/pal/tests/palsuite/miscellaneous/CGroup/test1/CMakeLists.txt b/src/pal/tests/palsuite/miscellaneous/CGroup/test1/CMakeLists.txt
new file mode 100644 (file)
index 0000000..cdd7fa9
--- /dev/null
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 2.8.12.2)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(SOURCES
+  test.cpp
+)
+
+add_executable(paltest_cgroup_test1
+  ${SOURCES}
+)
+
+add_dependencies(paltest_cgroup_test1 coreclrpal)
+
+target_link_libraries(paltest_cgroup_test1
+  ${COMMON_TEST_LIBRARIES}
+)
diff --git a/src/pal/tests/palsuite/miscellaneous/CGroup/test1/test.cpp b/src/pal/tests/palsuite/miscellaneous/CGroup/test1/test.cpp
new file mode 100644 (file)
index 0000000..44b970a
--- /dev/null
@@ -0,0 +1,53 @@
+// 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.
+
+/*============================================================
+**
+** Source: test.c
+**
+** Purpose: Test for CGroup
+**
+**
+**  Steps to run this test on ubuntu:
+**  1. sudo apt-get install cgroup-bin
+**  2. sudo vi /etc/default/grub
+**     Add cgroup_enable=memory swapaccount=1 to GRUB_CMDLINE_LINUX_DEFAULT
+**  3. sudo update-grub
+**  4. reboot
+**  5. sudo cgcreate -g cpu,memory:/myGroup -a <username>:<username> -t <username>:<username>
+**  6. echo 4M > /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes
+**  7. echo 4M > /sys/fs/cgroup/memory/mygroup/memory.memsw.limit_in_bytes
+**  8. cgexe -g memory:/mygroup --sticky <application>
+**=========================================================*/
+
+#include <palsuite.h>
+
+int __cdecl main(int argc,char *argv[]) 
+{
+
+    /*
+     * Initialize the PAL and return FAILURE if this fails
+     */
+
+    if(0 != (PAL_Initialize(argc, argv)))
+    {
+      return FAIL;
+    }
+
+    size_t mem_limit = PAL_GetRestrictedPhysicalMemoryLimit();
+
+    FILE* file = fopen("/sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes", "r");
+    if(file != NULL)
+    {
+        if(mem_limit != 4194304)
+            Fail("Memory limit obtained from PAL_GetRestrictedPhysicalMemory is not 4MB\n");
+        fclose(file); 
+    }
+
+    PAL_Terminate();
+    return PASS;
+}
+
+
+
diff --git a/src/pal/tests/palsuite/miscellaneous/CGroup/test1/testinfo.dat b/src/pal/tests/palsuite/miscellaneous/CGroup/test1/testinfo.dat
new file mode 100644 (file)
index 0000000..86da2d1
--- /dev/null
@@ -0,0 +1,12 @@
+# 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.
+
+Version = 1.0
+Section = Miscellaneous
+Function = CGroup
+Name = Positive Test for CGroup
+TYPE = DEFAULT
+EXE1 = test
+Description
+= Test to see if Cgroup memory limit works properly 
index 0fd4df8..0b5fd37 100644 (file)
@@ -1,5 +1,6 @@
 cmake_minimum_required(VERSION 2.8.12.2)
 
+add_subdirectory(CGroup)
 add_subdirectory(CharNextA)
 add_subdirectory(CharNextExA)
 add_subdirectory(CloseHandle)
index 0cbe016..6c08558 100644 (file)
@@ -360,13 +360,13 @@ size_t GCToOSInterface::GetVirtualMemoryLimit()
 }
 
 
+static size_t g_RestrictedPhysicalMemoryLimit = (size_t)MAX_PTR;
+
 #ifndef FEATURE_PAL
 
 typedef BOOL (WINAPI *PGET_PROCESS_MEMORY_INFO)(HANDLE handle, PROCESS_MEMORY_COUNTERS* memCounters, uint32_t cb);
 static PGET_PROCESS_MEMORY_INFO GCGetProcessMemoryInfo = 0;
 
-static size_t g_RestrictedPhysicalMemoryLimit = (size_t)MAX_PTR;
-
 typedef BOOL (WINAPI *PIS_PROCESS_IN_JOB)(HANDLE processHandle, HANDLE jobHandle, BOOL* result);
 typedef BOOL (WINAPI *PQUERY_INFORMATION_JOB_OBJECT)(HANDLE jobHandle, JOBOBJECTINFOCLASS jobObjectInfoClass, void* lpJobObjectInfo, DWORD cbJobObjectInfoLength, LPDWORD lpReturnLength);
 
@@ -455,6 +455,21 @@ exit:
     return g_RestrictedPhysicalMemoryLimit;
 }
 
+#else
+
+static size_t GetRestrictedPhysicalMemoryLimit()
+{
+    LIMITED_METHOD_CONTRACT;
+
+    // The limit was cached already
+    if (g_RestrictedPhysicalMemoryLimit != (size_t)MAX_PTR)
+        return g_RestrictedPhysicalMemoryLimit;
+
+    size_t memory_limit = PAL_GetRestrictedPhysicalMemoryLimit();
+    
+    VolatileStore(&g_RestrictedPhysicalMemoryLimit, memory_limit);
+    return g_RestrictedPhysicalMemoryLimit;
+}
 #endif // FEATURE_PAL
 
 
@@ -465,11 +480,9 @@ uint64_t GCToOSInterface::GetPhysicalMemoryLimit()
 {
     LIMITED_METHOD_CONTRACT;
 
-#ifndef FEATURE_PAL
     size_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
     if (restricted_limit != 0)
         return restricted_limit;
-#endif
 
     MEMORYSTATUSEX memStatus;
     ::GetProcessMemoryLoad(&memStatus);
@@ -489,17 +502,29 @@ void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available
 {
     LIMITED_METHOD_CONTRACT;
 
-#ifndef FEATURE_PAL
     uint64_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
     if (restricted_limit != 0)
     {
+        size_t workingSetSize;
+        BOOL status = FALSE;
+#ifndef FEATURE_PAL
         PROCESS_MEMORY_COUNTERS pmc;
-        if (GCGetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
+        status = GCGetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
+        workingSetSize = pmc.WorkingSetSize;
+#else
+        status = PAL_GetWorkingSetSize(&workingSetSize);
+#endif
+        if(status)
         {
             if (memory_load)
-                *memory_load = (uint32_t)((float)pmc.WorkingSetSize * 100.0 / (float)restricted_limit);
+                *memory_load = (uint32_t)((float)workingSetSize * 100.0 / (float)restricted_limit);
             if (available_physical)
-                *available_physical = restricted_limit - pmc.WorkingSetSize;
+            {
+                if(workingSetSize > restricted_limit)
+                    *available_physical = 0;
+                else
+                    *available_physical = restricted_limit - workingSetSize;
+            }
             // Available page file doesn't mean much when physical memory is restricted since
             // we don't know how much of it is available to this process so we are not going to 
             // bother to make another OS call for it.
@@ -509,7 +534,6 @@ void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available
             return;
         }
     }
-#endif
 
     MEMORYSTATUSEX ms;
     ::GetProcessMemoryLoad(&ms);