summaryrefslogtreecommitdiff
path: root/src/gc/unix
diff options
context:
space:
mode:
Diffstat (limited to 'src/gc/unix')
-rw-r--r--src/gc/unix/CMakeLists.txt3
-rw-r--r--src/gc/unix/cgroup.cpp342
-rw-r--r--src/gc/unix/gcenv.unix.cpp25
3 files changed, 365 insertions, 5 deletions
diff --git a/src/gc/unix/CMakeLists.txt b/src/gc/unix/CMakeLists.txt
index ef66abf32a..3e1aa5ad19 100644
--- a/src/gc/unix/CMakeLists.txt
+++ b/src/gc/unix/CMakeLists.txt
@@ -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
index 0000000000..1775ef7ff0
--- /dev/null
+++ b/src/gc/unix/cgroup.cpp
@@ -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;
+}
diff --git a/src/gc/unix/gcenv.unix.cpp b/src/gc/unix/gcenv.unix.cpp
index 34a45b3cc1..45489c69a7 100644
--- a/src/gc/unix/gcenv.unix.cpp
+++ b/src/gc/unix/gcenv.unix.cpp
@@ -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)