Blame SOURCES/runtime-mono-memfunc-cgroup.patch

8d396f
Index: a/src/mono/mono/utils/memfuncs.c
8d396f
===================================================================
8d396f
--- a/src/mono/mono/utils/memfuncs.c
8d396f
+++ b/src/mono/mono/utils/memfuncs.c
8d396f
@@ -343,6 +343,9 @@ mono_determine_physical_ram_available_si
8d396f
 	host_page_size (host, &page_size);
8d396f
 	return (guint64) vmstat.free_count * page_size;
8d396f
 
8d396f
+#elif defined (__FreeBSD__) || defined (__linux__) || defined (__APPLE__)
8d396f
+	return (getPhysicalMemoryAvail());
8d396f
+
8d396f
 #elif defined (HAVE_SYSCONF)
8d396f
 	gint64 page_size = -1, num_pages = -1;
8d396f
 
8d396f
Index: a/src/mono/mono/utils/memfuncs.h
8d396f
===================================================================
8d396f
--- a/src/mono/mono/utils/memfuncs.h
8d396f
+++ b/src/mono/mono/utils/memfuncs.h
8d396f
@@ -24,5 +24,10 @@ MONO_COMPONENT_API void mono_gc_memmove_
8d396f
 void mono_gc_memmove_aligned (void *dest, const void *src, size_t size);
8d396f
 guint64 mono_determine_physical_ram_size (void);
8d396f
 guint64 mono_determine_physical_ram_available_size (void);
8d396f
+#if defined (__FreeBSD__) || defined (__linux__) || defined (__APPLE__)
8d396f
+size_t getRestrictedPhysicalMemoryLimit(void);
8d396f
+gboolean getPhysicalMemoryUsed(size_t *);
8d396f
+size_t getPhysicalMemoryAvail(void);
8d396f
+#endif
8d396f
 
8d396f
 #endif
8d396f
Index: a/src/mono/mono/utils/mono-cgroup.c
8d396f
===================================================================
8d396f
--- /dev/null
8d396f
+++ b/src/mono/mono/utils/mono-cgroup.c
8d396f
@@ -0,0 +1,709 @@
8d396f
+// Licensed to the .NET Foundation under one or more agreements.
8d396f
+// The .NET Foundation licenses this file to you under the MIT license.
8d396f
+
8d396f
+/*++
8d396f
+
8d396f
+Module Name:
8d396f
+
8d396f
+    mono-cgroup.cpp
8d396f
+
8d396f
+Abstract:
8d396f
+    Read the memory limit for the current process
8d396f
+--*/
8d396f
+#ifdef __FreeBSD__
8d396f
+#define _WITH_GETLINE
8d396f
+#endif
8d396f
+
8d396f
+#include <config.h>
8d396f
+#include <unistd.h>
8d396f
+#include <stdlib.h>
8d396f
+#include <stdint.h>
8d396f
+#include <glib.h>
8d396f
+#include <stdio.h>
8d396f
+#include <string.h>
8d396f
+#include <sys/resource.h>
8d396f
+#if defined(__APPLE__) || defined(__FreeBSD__)
8d396f
+#include <sys/param.h>
8d396f
+#include <sys/mount.h>
8d396f
+#else
8d396f
+#include <sys/vfs.h>
8d396f
+#endif
8d396f
+#include <errno.h>
8d396f
+#include <limits.h>
8d396f
+
8d396f
+#ifndef SIZE_T_MAX
8d396f
+# define SIZE_T_MAX (~(size_t)0)
8d396f
+#endif
8d396f
+
8d396f
+#define CGROUP2_SUPER_MAGIC 0x63677270
8d396f
+#define TMPFS_MAGIC 0x01021994
8d396f
+
8d396f
+#define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo"
8d396f
+#define PROC_CGROUP_FILENAME "/proc/self/cgroup"
8d396f
+#define PROC_STATM_FILENAME "/proc/self/statm"
8d396f
+#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes"
8d396f
+#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max"
8d396f
+#define CGROUP_MEMORY_STAT_FILENAME "/memory.stat"
8d396f
+
8d396f
+static void initialize(void);
8d396f
+static gboolean readMemoryValueFromFile(const char *, guint64 *);
8d396f
+static gboolean getPhysicalMemoryLimit(guint64 *);
8d396f
+static gboolean getPhysicalMemoryUsage(size_t *);
8d396f
+static int findCGroupVersion(void);
8d396f
+static gboolean isCGroup1MemorySubsystem(const char *);
8d396f
+static char *findCGroupPath(gboolean (*is_subsystem)(const char *));
8d396f
+static void findHierarchyMount(gboolean (*is_subsystem)(const char *), char **, char **);
8d396f
+static char *findCGroupPathForSubsystem(gboolean (*is_subsystem)(const char *));
8d396f
+static gboolean getCGroupMemoryLimit(guint64 *, const char *);
8d396f
+static gboolean getCGroupMemoryUsage(size_t *);
8d396f
+static size_t getPhysicalMemoryTotal(guint64);
8d396f
+
8d396f
+size_t getRestrictedPhysicalMemoryLimit(void);
8d396f
+gboolean getPhysicalMemoryUsed(size_t *);
8d396f
+size_t getPhysicalMemoryAvail(void);
8d396f
+
8d396f
+// the cgroup version number or 0 to indicate cgroups are not found or not enabled
8d396f
+static int s_cgroup_version;
8d396f
+
8d396f
+static char *s_memory_cgroup_path = NULL;
8d396f
+
8d396f
+static const char *s_mem_stat_key_names[4];
8d396f
+static size_t s_mem_stat_key_lengths[4];
8d396f
+static size_t s_mem_stat_n_keys = 0;
8d396f
+static long pageSize;
8d396f
+
8d396f
+/**
8d396f
+ * @initialize
8d396f
+ *
8d396f
+ * Initialize variables used by the calculation routines.
8d396f
+ */
8d396f
+static void 
8d396f
+initialize()
8d396f
+{
8d396f
+	s_cgroup_version = findCGroupVersion();
8d396f
+	s_memory_cgroup_path = findCGroupPath(s_cgroup_version == 1 ? &isCGroup1MemorySubsystem : NULL);
8d396f
+
8d396f
+	if (s_cgroup_version == 1) {
8d396f
+		s_mem_stat_n_keys = 4;
8d396f
+		s_mem_stat_key_names[0] = "total_inactive_anon ";
8d396f
+		s_mem_stat_key_names[1] = "total_active_anon ";
8d396f
+		s_mem_stat_key_names[2] = "total_dirty ";
8d396f
+		s_mem_stat_key_names[3] = "total_unevictable ";
8d396f
+	} else {
8d396f
+		s_mem_stat_n_keys = 3;
8d396f
+		s_mem_stat_key_names[0] = "anon ";
8d396f
+		s_mem_stat_key_names[1] = "file_dirty ";
8d396f
+		s_mem_stat_key_names[2] = "unevictable ";
8d396f
+	}
8d396f
+
8d396f
+	for (size_t i = 0; i < s_mem_stat_n_keys; i++)
8d396f
+		s_mem_stat_key_lengths[i] = strlen(s_mem_stat_key_names[i]);
8d396f
+
8d396f
+	pageSize = sysconf(_SC_PAGE_SIZE);
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @readMemoryValueFromFile
8d396f
+ *
8d396f
+ * @param[in] filename - name of file containing value
8d396f
+ * @param[out] val - pointer to the result area
8d396f
+ * @returns True or False depending if value was found
8d396f
+ *
8d396f
+ * Read a value from a specified /sys/fs/cgroup/memory file
8d396f
+ */
8d396f
+static gboolean 
8d396f
+readMemoryValueFromFile(const char* filename, guint64* val)
8d396f
+{
8d396f
+	gboolean result = FALSE;
8d396f
+	char *line = NULL;
8d396f
+	size_t lineLen = 0;
8d396f
+	char *endptr = NULL;
8d396f
+	guint64 num = 0, multiplier;
8d396f
+	FILE *file = NULL;
8d396f
+
8d396f
+	if (val == NULL) {
8d396f
+		file = fopen(filename, "r");
8d396f
+		if (file != NULL) {
8d396f
+			if (getline(&line, &lineLen, file) != -1) {
8d396f
+				errno = 0;
8d396f
+				num = strtoull(line, &endptr, 0);
8d396f
+				if (line != endptr && errno == 0) {
8d396f
+					multiplier = 1;
8d396f
+
8d396f
+					switch (*endptr)
8d396f
+					{
8d396f
+					case 'g':
8d396f
+					case 'G': 
8d396f
+						multiplier = 1024;
8d396f
+					case 'm':
8d396f
+					case 'M': 
8d396f
+						multiplier = multiplier * 1024;
8d396f
+					case 'k':
8d396f
+					case 'K': 
8d396f
+						multiplier = multiplier * 1024;
8d396f
+					}
8d396f
+
8d396f
+					*val = num * multiplier;
8d396f
+					result = TRUE;
8d396f
+					if (*val / multiplier != num)
8d396f
+						result = FALSE;
8d396f
+				}
8d396f
+			}
8d396f
+		}
8d396f
+	}
8d396f
+
8d396f
+	if (file)
8d396f
+		fclose(file);
8d396f
+	free(line);
8d396f
+	return result;
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @getPhysicalMemoryLimit
8d396f
+ *
8d396f
+ * @param[out] val - pointer to the result area
8d396f
+ * @returns True or False depending if a limit was found
8d396f
+ *
8d396f
+ * Interrogate the cgroup memory values to determine if there's
8d396f
+ * a limit on physical memory.
8d396f
+ */
8d396f
+static gboolean 
8d396f
+getPhysicalMemoryLimit(guint64 *val)
8d396f
+{
8d396f
+	if (s_mem_stat_n_keys == 0)
8d396f
+		initialize();
8d396f
+
8d396f
+	if (s_cgroup_version == 0)
8d396f
+		return FALSE;
8d396f
+	else if (s_cgroup_version == 1)
8d396f
+		return getCGroupMemoryLimit(val, CGROUP1_MEMORY_LIMIT_FILENAME);
8d396f
+	else if (s_cgroup_version == 2)
8d396f
+		return getCGroupMemoryLimit(val, CGROUP2_MEMORY_LIMIT_FILENAME);
8d396f
+	else {
8d396f
+		g_assert(!"Unknown cgroup version.");
8d396f
+		return FALSE;
8d396f
+	}
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @getPhysicalMemoryUsage
8d396f
+ *
8d396f
+ * @param[out] val - pointer to the result area
8d396f
+ * @returns True or False depending if a usage value was found
8d396f
+ *
8d396f
+ * Interrogate the cgroup memory values to determine how much 
8d396f
+ * memory is in use.
8d396f
+ */
8d396f
+static gboolean 
8d396f
+getPhysicalMemoryUsage(size_t *val)
8d396f
+{
8d396f
+	if (s_cgroup_version == 0)
8d396f
+		return FALSE;
8d396f
+	else if (s_cgroup_version == 1)
8d396f
+		return getCGroupMemoryUsage(val);
8d396f
+	else if (s_cgroup_version == 2)
8d396f
+		return getCGroupMemoryUsage(val);
8d396f
+	else {
8d396f
+		g_assert(!"Unknown cgroup version.");
8d396f
+		return FALSE;
8d396f
+	}
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @findGroupVersion
8d396f
+ *
8d396f
+ * @returns cgroup version
8d396f
+ *
8d396f
+ * Inspect the /sys/fs/cgroup hierachy to determine what version of 
8d396f
+ * group we are using
8d396f
+ */
8d396f
+static int 
8d396f
+findCGroupVersion()
8d396f
+{
8d396f
+	// It is possible to have both cgroup v1 and v2 enabled on a system.
8d396f
+	// Most non-bleeding-edge Linux distributions fall in this group. We
8d396f
+	// look at the file system type of /sys/fs/cgroup to determine which
8d396f
+	// one is the default. For more details, see:
8d396f
+	// https://systemd.io/CGROUP_DELEGATION/#three-different-tree-setups-
8d396f
+	// We dont care about the difference between the "legacy" and "hybrid"
8d396f
+	// modes because both of those involve cgroup v1 controllers managing
8d396f
+	// resources.
8d396f
+
8d396f
+
8d396f
+	struct statfs stats;
8d396f
+	int result = statfs("/sys/fs/cgroup", &stats);
8d396f
+	if (result != 0)
8d396f
+		return 0;
8d396f
+
8d396f
+	switch (stats.f_type) {
8d396f
+	case TMPFS_MAGIC: return 1;
8d396f
+	case CGROUP2_SUPER_MAGIC: return 2;
8d396f
+	default:
8d396f
+		g_assert(!"Unexpected file system type for /sys/fs/cgroup");
8d396f
+		return 0;
8d396f
+	}
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @isCGroup1MemorySubsystem
8d396f
+ *
8d396f
+ * @param[in] strTok - Token for comparison
8d396f
+ * @returns True if token matches "memory"
8d396f
+ *
8d396f
+ * Check if we've found the memory component of /sys/fs/cgroup
8d396f
+ */
8d396f
+static gboolean 
8d396f
+isCGroup1MemorySubsystem(const char *strTok)
8d396f
+{
8d396f
+	return strcmp("memory", strTok) == 0;
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @findCGroupPath
8d396f
+ *
8d396f
+ * @param[in] is_subsystem - Function used to compare tokens
8d396f
+ * @returns Path to cgroup
8d396f
+ *
8d396f
+ * Navigate the /sys/fs/cgroup to try and find the correct cgroup path
8d396f
+ */
8d396f
+static char *
8d396f
+findCGroupPath(gboolean (*is_subsystem)(const char *))
8d396f
+{
8d396f
+	char *cgroup_path = NULL;
8d396f
+	char *hierarchy_mount = NULL;
8d396f
+	char *hierarchy_root = NULL;
8d396f
+	char *cgroup_path_relative_to_mount = NULL;
8d396f
+	size_t common_path_prefix_len;
8d396f
+
8d396f
+	findHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root);
8d396f
+	if (hierarchy_mount != NULL && hierarchy_root != NULL) {
8d396f
+
8d396f
+		cgroup_path_relative_to_mount = findCGroupPathForSubsystem(is_subsystem);
8d396f
+		if (cgroup_path_relative_to_mount != NULL) {
8d396f
+
8d396f
+			cgroup_path = (char*)malloc(strlen(hierarchy_mount) + strlen(cgroup_path_relative_to_mount) + 1);
8d396f
+			if (cgroup_path != NULL) {
8d396f
+
8d396f
+				strcpy(cgroup_path, hierarchy_mount);
8d396f
+				// For a host cgroup, we need to append the relative path.
8d396f
+				// The root and cgroup path can share a common prefix of the path that should not be appended.
8d396f
+				// Example 1 (docker):
8d396f
+				// hierarchy_mount:               /sys/fs/cgroup/cpu
8d396f
+				// hierarchy_root:                /docker/87ee2de57e51bc75175a4d2e81b71d162811b179d549d6601ed70b58cad83578
8d396f
+				// cgroup_path_relative_to_mount: /docker/87ee2de57e51bc75175a4d2e81b71d162811b179d549d6601ed70b58cad83578/my_named_cgroup
8d396f
+				// append do the cgroup_path:     /my_named_cgroup
8d396f
+				// final cgroup_path:             /sys/fs/cgroup/cpu/my_named_cgroup
8d396f
+				//
8d396f
+				// Example 2 (out of docker)
8d396f
+				// hierarchy_mount:               /sys/fs/cgroup/cpu
8d396f
+				// hierarchy_root:                /
8d396f
+				// cgroup_path_relative_to_mount: /my_named_cgroup
8d396f
+				// append do the cgroup_path:     /my_named_cgroup
8d396f
+				// final cgroup_path:             /sys/fs/cgroup/cpu/my_named_cgroup
8d396f
+				common_path_prefix_len = strlen(hierarchy_root);
8d396f
+				if ((common_path_prefix_len == 1) || 
8d396f
+				    (strncmp(hierarchy_root, cgroup_path_relative_to_mount, common_path_prefix_len) != 0))
8d396f
+					common_path_prefix_len = 0;
8d396f
+
8d396f
+				g_assert((cgroup_path_relative_to_mount[common_path_prefix_len] == '/') || 
8d396f
+				         (cgroup_path_relative_to_mount[common_path_prefix_len] == '\0'));
8d396f
+
8d396f
+				strcat(cgroup_path, cgroup_path_relative_to_mount + common_path_prefix_len);
8d396f
+			}
8d396f
+		}
8d396f
+	}
8d396f
+
8d396f
+	free(hierarchy_mount);
8d396f
+	free(hierarchy_root);
8d396f
+	free(cgroup_path_relative_to_mount);
8d396f
+	return cgroup_path;
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @findHierarchyMount
8d396f
+ *
8d396f
+ * @param[in] is_subsystem - Comparison function
8d396f
+ * @param[out] pmountpath -
8d396f
+ * @param[out] pmountroot -
8d396f
+ *
8d396f
+ * Check the /proc filesystem to determine the root and mount path of /sys/fs/cgroup data
8d396f
+ */
8d396f
+static void 
8d396f
+findHierarchyMount(gboolean (*is_subsystem)(const char *), char** pmountpath, char** pmountroot)
8d396f
+{
8d396f
+	char *line = NULL;
8d396f
+	size_t lineLen = 0, maxLineLen = 0;
8d396f
+	char *filesystemType = NULL;
8d396f
+	char *options = NULL;
8d396f
+	char *mountpath = NULL;
8d396f
+	char *mountroot = NULL;
8d396f
+
8d396f
+	FILE *mountinfofile = fopen(PROC_MOUNTINFO_FILENAME, "r");
8d396f
+	if (mountinfofile == NULL) 
8d396f
+		goto done;
8d396f
+
8d396f
+	while (getline(&line, &lineLen, mountinfofile) != -1) {
8d396f
+		if (filesystemType == NULL || lineLen > maxLineLen) {
8d396f
+			free(filesystemType);
8d396f
+			filesystemType = NULL;
8d396f
+			free(options);
8d396f
+			options = NULL;
8d396f
+			filesystemType = (char*)malloc(lineLen+1);
8d396f
+			if (filesystemType == NULL)
8d396f
+				goto done;
8d396f
+			options = (char*)malloc(lineLen+1);
8d396f
+			if (options == NULL)
8d396f
+				goto done;
8d396f
+			maxLineLen = lineLen;
8d396f
+		}
8d396f
+
8d396f
+		char *separatorChar = strstr(line, " - ");
8d396f
+
8d396f
+		// See man page of proc to get format for /proc/self/mountinfo file
8d396f
+		int sscanfRet = sscanf(separatorChar,
8d396f
+		   " - %s %*s %s",
8d396f
+		   filesystemType,
8d396f
+		   options);
8d396f
+		if (sscanfRet != 2) {
8d396f
+			g_assert(!"Failed to parse mount info file contents with sscanf.");
8d396f
+			goto done;
8d396f
+		}
8d396f
+
8d396f
+		if (strncmp(filesystemType, "cgroup", 6) == 0) {
8d396f
+			gboolean isSubsystemMatch = is_subsystem == NULL;
8d396f
+			if (!isSubsystemMatch) {
8d396f
+				char *context = NULL;
8d396f
+				char *strTok = strtok_r(options, ",", &context);
8d396f
+				while (!isSubsystemMatch && strTok != NULL)
8d396f
+				{
8d396f
+					isSubsystemMatch = is_subsystem(strTok);
8d396f
+					strTok = strtok_r(NULL, ",", &context);
8d396f
+				}
8d396f
+			}
8d396f
+			if (isSubsystemMatch) {
8d396f
+				mountpath = (char*)malloc(lineLen+1);
8d396f
+				if (mountpath == NULL)
8d396f
+					goto done;
8d396f
+				mountroot = (char*)malloc(lineLen+1);
8d396f
+				if (mountroot == NULL)
8d396f
+					goto done;
8d396f
+
8d396f
+				sscanfRet = sscanf(line,
8d396f
+					   "%*s %*s %*s %s %s ",
8d396f
+					   mountroot,
8d396f
+					   mountpath);
8d396f
+				if (sscanfRet != 2)
8d396f
+					g_assert(!"Failed to parse mount info file contents with sscanf.");
8d396f
+
8d396f
+				// assign the output arguments and clear the locals so we don't free them.
8d396f
+				*pmountpath = mountpath;
8d396f
+				*pmountroot = mountroot;
8d396f
+				mountpath = mountroot = NULL;
8d396f
+			}
8d396f
+		}
8d396f
+	}
8d396f
+done:
8d396f
+	free(mountpath);
8d396f
+	free(mountroot);
8d396f
+	free(filesystemType);
8d396f
+	free(options);
8d396f
+	free(line);
8d396f
+	if (mountinfofile)
8d396f
+		fclose(mountinfofile);
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @findCGroupPathForSubsystem
8d396f
+ *
8d396f
+ * @param[in] is_subsystem - Comparison function
8d396f
+ * @returns cgroup path for the memory subsystem
8d396f
+ *
8d396f
+ * Check the /proc filesystem to determine the root and mount path of /sys/fs/cgroup data
8d396f
+ */
8d396f
+static char * 
8d396f
+findCGroupPathForSubsystem(gboolean (*is_subsystem)(const char *))
8d396f
+{
8d396f
+	char *line = NULL;
8d396f
+	size_t lineLen = 0;
8d396f
+	size_t maxLineLen = 0;
8d396f
+	char *subsystem_list = NULL;
8d396f
+	char *cgroup_path = NULL;
8d396f
+	gboolean result = FALSE;
8d396f
+
8d396f
+	FILE *cgroupfile = fopen(PROC_CGROUP_FILENAME, "r");
8d396f
+	if (cgroupfile == NULL)
8d396f
+		goto done;
8d396f
+
8d396f
+	while (!result && getline(&line, &lineLen, cgroupfile) != -1) {
8d396f
+		if (subsystem_list == NULL || lineLen > maxLineLen) {
8d396f
+			free(subsystem_list);
8d396f
+			subsystem_list = NULL;
8d396f
+			free(cgroup_path);
8d396f
+			cgroup_path = NULL;
8d396f
+			subsystem_list = (char*)malloc(lineLen+1);
8d396f
+			if (subsystem_list == NULL)
8d396f
+				goto done;
8d396f
+			cgroup_path = (char*)malloc(lineLen+1);
8d396f
+			if (cgroup_path == NULL)
8d396f
+				goto done;
8d396f
+			maxLineLen = lineLen;
8d396f
+		}
8d396f
+
8d396f
+		if (s_cgroup_version == 1) {
8d396f
+			// See man page of proc to get format for /proc/self/cgroup file
8d396f
+			int sscanfRet = sscanf(line,
8d396f
+			"%*[^:]:%[^:]:%s",
8d396f
+			subsystem_list,
8d396f
+			cgroup_path);
8d396f
+			if (sscanfRet != 2) {
8d396f
+				g_assert(!"Failed to parse cgroup info file contents with sscanf.");
8d396f
+				goto done;
8d396f
+			}
8d396f
+
8d396f
+			char* context = NULL;
8d396f
+			char* strTok = strtok_r(subsystem_list, ",", &context);
8d396f
+			while (strTok != NULL) {
8d396f
+				if (is_subsystem(strTok)) {
8d396f
+					result = TRUE;
8d396f
+					break;
8d396f
+				}
8d396f
+				strTok = strtok_r(NULL, ",", &context);
8d396f
+			}
8d396f
+		} else if (s_cgroup_version == 2) {
8d396f
+			// See https://www.kernel.org/doc/Documentation/cgroup-v2.txt
8d396f
+			// Look for a "0::/some/path"
8d396f
+			int sscanfRet = sscanf(line,
8d396f
+			"0::%s",
8d396f
+			cgroup_path);
8d396f
+			if (sscanfRet == 1)
8d396f
+			{
8d396f
+				result = TRUE;
8d396f
+			}
8d396f
+		} else {
8d396f
+			g_assert(!"Unknown cgroup version in mountinfo.");
8d396f
+			goto done;
8d396f
+		}
8d396f
+	}
8d396f
+done:
8d396f
+	free(subsystem_list);
8d396f
+	if (!result) {
8d396f
+		free(cgroup_path);
8d396f
+		cgroup_path = NULL;
8d396f
+	}
8d396f
+	free(line);
8d396f
+	if (cgroupfile)
8d396f
+		fclose(cgroupfile);
8d396f
+	return cgroup_path;
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @getCGroupMemoryLimit
8d396f
+ *
8d396f
+ * @param[out] val - Memory limit
8d396f
+ * @param[in] filename - name of file from which to extract limit
8d396f
+ * @returns True if value found
8d396f
+ *
8d396f
+ * Extract memory limit from specified /sys/fs/cgroup/memory file
8d396f
+ */
8d396f
+static gboolean 
8d396f
+getCGroupMemoryLimit(guint64 *val, const char *filename)
8d396f
+{
8d396f
+	if (s_memory_cgroup_path == NULL)
8d396f
+		return FALSE;
8d396f
+
8d396f
+	char* mem_limit_filename = NULL;
8d396f
+	if (asprintf(&mem_limit_filename, "%s%s", s_memory_cgroup_path, filename) < 0)
8d396f
+		return FALSE;
8d396f
+
8d396f
+	gboolean result = readMemoryValueFromFile(mem_limit_filename, val);
8d396f
+	free(mem_limit_filename);
8d396f
+	return result;
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @getCGroupMemoryUsage
8d396f
+ *
8d396f
+ * @param[out] val - Memory limit
8d396f
+ * @returns True if value found
8d396f
+ *
8d396f
+ * Extract memory usage from /sys/fs/cgroup/memory.stat file
8d396f
+ */
8d396f
+static gboolean 
8d396f
+getCGroupMemoryUsage(size_t *val)
8d396f
+{
8d396f
+	if (s_memory_cgroup_path == NULL)
8d396f
+		return FALSE;
8d396f
+
8d396f
+	char *stat_filename = NULL;
8d396f
+	if (asprintf(&stat_filename, "%s%s", s_memory_cgroup_path, CGROUP_MEMORY_STAT_FILENAME) < 0)
8d396f
+		return FALSE;
8d396f
+
8d396f
+	FILE *stat_file = fopen(stat_filename, "r");
8d396f
+	free(stat_filename);
8d396f
+	if (stat_file == NULL)
8d396f
+		return FALSE;
8d396f
+
8d396f
+	char *line = NULL;
8d396f
+	size_t lineLen = 0;
8d396f
+	size_t readValues = 0;
8d396f
+	char *endptr;
8d396f
+
8d396f
+	*val = 0;
8d396f
+	while (getline(&line, &lineLen, stat_file) != -1 && readValues < s_mem_stat_n_keys) {
8d396f
+		for (size_t i = 0; i < s_mem_stat_n_keys; i++) {
8d396f
+			if (strncmp(line, s_mem_stat_key_names[i], s_mem_stat_key_lengths[i]) == 0) {
8d396f
+				errno = 0;
8d396f
+				const char *startptr = line + s_mem_stat_key_lengths[i];
8d396f
+				*val += strtoll(startptr, &endptr, 10);
8d396f
+				if (endptr != startptr && errno == 0)
8d396f
+					readValues++;
8d396f
+
8d396f
+				break;
8d396f
+			}
8d396f
+		}
8d396f
+	}
8d396f
+
8d396f
+	fclose(stat_file);
8d396f
+	free(line);
8d396f
+
8d396f
+	if (readValues == s_mem_stat_n_keys)
8d396f
+		return TRUE;
8d396f
+
8d396f
+	return FALSE;
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @getRestrictedPhysicalMemoryLimit
8d396f
+ *
8d396f
+ * @returns Physical memory limit
8d396f
+ *
8d396f
+ * Determine if there are any limits on memory and return the value
8d396f
+ * if so. Zero represents no limit.
8d396f
+ */
8d396f
+size_t 
8d396f
+getRestrictedPhysicalMemoryLimit()
8d396f
+{
8d396f
+	guint64 physical_memory_limit = 0;
8d396f
+
8d396f
+	if (s_mem_stat_n_keys == 0)
8d396f
+		initialize();
8d396f
+
8d396f
+	if (!getPhysicalMemoryLimit(&physical_memory_limit))
8d396f
+		return 0;
8d396f
+
8d396f
+	// If there's no memory limit specified on the container this
8d396f
+	// actually returns 0x7FFFFFFFFFFFF000 (2^63-1 rounded down to
8d396f
+	// 4k which is a common page size). So we know we are not
8d396f
+	// running in a memory restricted environment.
8d396f
+	if (physical_memory_limit > 0x7FFFFFFF00000000)
8d396f
+		return 0;
8d396f
+
8d396f
+	return (getPhysicalMemoryTotal(physical_memory_limit));
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @getPhysicalMemoryTotal
8d396f
+ *
8d396f
+ * @param[in] physical_memory_limit - The max memory on the system
8d396f
+ * @returns Physical memory total
8d396f
+ *
8d396f
+ * Check the input limit against any system limits or actual memory on system
8d396f
+ */
8d396f
+static size_t
8d396f
+getPhysicalMemoryTotal(size_t physical_memory_limit)
8d396f
+{
8d396f
+	struct rlimit curr_rlimit;
8d396f
+	size_t rlimit_soft_limit = (size_t)RLIM_INFINITY;
8d396f
+	if (getrlimit(RLIMIT_AS, &curr_rlimit) == 0)
8d396f
+		rlimit_soft_limit = curr_rlimit.rlim_cur;
8d396f
+	physical_memory_limit = (physical_memory_limit < rlimit_soft_limit) ?
8d396f
+				 physical_memory_limit : rlimit_soft_limit;
8d396f
+
8d396f
+	// Ensure that limit is not greater than real memory size
8d396f
+	long pages = sysconf(_SC_PHYS_PAGES);
8d396f
+	if (pages != -1) {
8d396f
+		if (pageSize != -1) {
8d396f
+			physical_memory_limit = (physical_memory_limit < (size_t)pages * pageSize) ?
8d396f
+						 physical_memory_limit : (size_t)pages * pageSize;
8d396f
+		}
8d396f
+	}
8d396f
+
8d396f
+	if (physical_memory_limit > ULONG_MAX) {
8d396f
+		// It is observed in practice when the memory is unrestricted, Linux control
8d396f
+		// group returns a physical limit that is bigger than the address space
8d396f
+		return ULONG_MAX;
8d396f
+	} else
8d396f
+		return (size_t)physical_memory_limit;
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @getPhysicalMemoryUsed
8d396f
+ *
8d396f
+ * @param[out] val - pointer to the memory usage value 
8d396f
+ * @returns True if we are able to determine usage
8d396f
+ *
8d396f
+ * Determine the amount of memory in use
8d396f
+ */
8d396f
+gboolean 
8d396f
+getPhysicalMemoryUsed(size_t *val)
8d396f
+{
8d396f
+	gboolean result = FALSE;
8d396f
+	size_t linelen;
8d396f
+	char *line = NULL;
8d396f
+
8d396f
+	if (val == NULL)
8d396f
+		return FALSE;
8d396f
+
8d396f
+	// Linux uses cgroup usage to trigger oom kills.
8d396f
+	if (getPhysicalMemoryUsage(val))
8d396f
+		return TRUE;
8d396f
+
8d396f
+	// process resident set size.
8d396f
+	FILE* file = fopen(PROC_STATM_FILENAME, "r");
8d396f
+	if (file != NULL && getline(&line, &linelen, file) != -1) {
8d396f
+		char* context = NULL;
8d396f
+		char* strTok = strtok_r(line, " ", &context);
8d396f
+		strTok = strtok_r(NULL, " ", &context);
8d396f
+
8d396f
+		errno = 0;
8d396f
+		*val = strtoull(strTok, NULL, 0);
8d396f
+		if (errno == 0) {
8d396f
+			if (pageSize != -1) {
8d396f
+				*val = *val * pageSize;
8d396f
+				result = TRUE;
8d396f
+			}
8d396f
+		}
8d396f
+	}
8d396f
+
8d396f
+	if (file)
8d396f
+		fclose(file);
8d396f
+	free(line);
8d396f
+	return result;
8d396f
+}
8d396f
+
8d396f
+/**
8d396f
+ * @getPhysicalMemoryAvail
8d396f
+ *
8d396f
+ * @returns Amount of memory available
8d396f
+ *
8d396f
+ * Determine the amount of memory available by examininig any limits and
8d396f
+ * checking what memory is in use.
8d396f
+ */
8d396f
+size_t
8d396f
+getPhysicalMemoryAvail()
8d396f
+{
8d396f
+	size_t max, used, avail, sysAvail;
8d396f
+
8d396f
+	max = getRestrictedPhysicalMemoryLimit();
8d396f
+
8d396f
+	if (max == 0)
8d396f
+		max = getPhysicalMemoryTotal(ULONG_MAX);
8d396f
+
8d396f
+	if (getPhysicalMemoryUsed(&used))
8d396f
+		avail = max - used;
8d396f
+	else
8d396f
+		avail = max;
8d396f
+
8d396f
+	sysAvail = sysconf(_SC_AVPHYS_PAGES) * pageSize;
8d396f
+	return (avail < sysAvail ? avail : sysAvail);
8d396f
+}
8d396f
Index: a/src/mono/mono/utils/CMakeLists.txt
8d396f
===================================================================
8d396f
--- a/src/mono/mono/utils/CMakeLists.txt
8d396f
+++ b/src/mono/mono/utils/CMakeLists.txt
8d396f
@@ -32,6 +32,7 @@ set(utils_common_sources
8d396f
     mono-sha1.c
8d396f
     mono-logger.c
8d396f
     mono-logger-internals.h
8d396f
+    mono-cgroup.c
8d396f
     mono-codeman.c
8d396f
     mono-counters.c
8d396f
     mono-compiler.h