Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
memory limit in containers on linux
Browse files Browse the repository at this point in the history
  • Loading branch information
rahul committed Mar 10, 2017
1 parent 515af5e commit 5fc16f8
Show file tree
Hide file tree
Showing 13 changed files with 716 additions and 14 deletions.
3 changes: 2 additions & 1 deletion src/gc/unix/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
266 changes: 266 additions & 0 deletions src/gc/unix/cgroup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
// 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <errno.h>
#include "cgroup.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"
#define MEM_SWAP_LIMIT_FILENAME "/memory.memsw.limit_in_bytes"

CGroup::CGroup()
{
if (!FindMemoryHierarchyMount(m_memory_cgroup_path))
{
m_memory_cgroup_path[0] = '\0';
return;
}
char cgroup_path_relative_to_mount[MAX_PATH];
if (!FindCGroupPathForMemorySubsystem(cgroup_path_relative_to_mount))
{
m_memory_cgroup_path[0] = '\0';
return;
}
strcat(m_memory_cgroup_path, cgroup_path_relative_to_mount);
}

bool CGroup::GetPhysicalMemoryLimit(size_t *val)
{
char mem_limit_filename[MAX_PATH];

if (m_memory_cgroup_path[0] == 0)
return false;

strcpy(mem_limit_filename, m_memory_cgroup_path);
strcat(mem_limit_filename, MEM_LIMIT_FILENAME);
return ReadMemoryValueFromFile(mem_limit_filename, val);
}

bool CGroup::GetSwapMemoryLimit(size_t *val)
{
char memsw_limit_filename[MAX_PATH];

if (m_memory_cgroup_path[0] == 0)
return false;

strcpy(memsw_limit_filename, m_memory_cgroup_path);
strcat(memsw_limit_filename, MEM_SWAP_LIMIT_FILENAME);
return ReadMemoryValueFromFile(memsw_limit_filename, val);
}

bool CGroup::FindMemoryHierarchyMount(char* mountpath)
{
bool retval = false;
char *line = nullptr;
size_t lineLen = 0;
FILE *mountinfofile = fopen(PROC_MOUNTINFO_FILENAME, "r");
if (mountinfofile == NULL)
goto done;

while (!retval && getline(&line, &lineLen, mountinfofile) != -1)
{
char filesystemType[100];
char options[MAX_PATH];

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 = NULL;
char* strTok = strtok_r(options, ",", &context);
while (strTok != NULL)
{
if (strncmp("memory", strTok, 6) == 0)
{
sscanfRet = sscanf(line,
"%*d %*d %*s %*s %s ",
mountpath);
if (sscanfRet != 1)
{
assert(!"Failed to parse mount info file contents with sscanf.");
goto done;
}
retval = true;
break;
}
strTok = strtok_r(NULL, ",", &context);
}
}
}
done:
if (line)
free(line);
if (mountinfofile)
fclose(mountinfofile);
return retval;
}

bool CGroup::FindCGroupPathForMemorySubsystem(char* cgrouppath)
{
bool retval = false;
char *line = nullptr;
size_t lineLen = 0;
FILE *cgroupfile = fopen(PROC_CGROUP_FILENAME, "r");
if (cgroupfile == NULL)
goto done;

while (!retval && getline(&line, &lineLen, cgroupfile) != -1)
{
char subsystem_list[100];
char cgroup_path[MAX_PATH];

// 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 = NULL;
char* strTok = strtok_r(subsystem_list, ",", &context);
while (strTok != NULL)
{
if (strncmp("memory", strTok, 6) == 0)
{
strcpy(cgrouppath, cgroup_path);
retval = true;
break;
}
strTok = strtok_r(NULL, ",", &context);
}
}
done:
if (line)
free(line);
if (cgroupfile)
fclose(cgroupfile);
return retval;
}

bool CGroup::ReadMemoryValueFromFile(const char* filename, size_t* val)
{
bool result = false;
char *line = nullptr;
size_t lineLen = 0;
char* endptr = NULL;
size_t num = 0, l, multiplier;
FILE* file = NULL;

if (val == NULL)
goto done;

file = fopen(filename, "r");
if (file == NULL)
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);
if (line)
free(line);
return result;
}

size_t GetRestrictedPhysicalMemoryLimit()
{
CGroup cgroup;
size_t result;
size_t physical_memory_limit;
size_t swap_memory_limit;

if (!cgroup.GetPhysicalMemoryLimit(&physical_memory_limit))
physical_memory_limit = SIZE_T_MAX;
if (cgroup.GetSwapMemoryLimit(&swap_memory_limit))
{
assert(swap_memory_limit >= physical_memory_limit);
result = swap_memory_limit;
}
else
result = physical_memory_limit;
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;
}
result = (result < rlimit_soft_limit) ? result : rlimit_soft_limit;
return result;
}

bool GetWorkingSetSize(size_t* val)
{
bool result = false;
size_t linelen;
char* line = NULL;

FILE* file = fopen(PROC_STATM_FILENAME, "r");
if (file != NULL && getline(&line, &linelen, file) != -1)
{
if (sscanf(line, "%*s %zu %*s %*s %*s %*s %*s", val) == 1)
result = true;
}

if (file)
fclose(file);
if (line)
free(line);
return result;
}
31 changes: 31 additions & 0 deletions src/gc/unix/cgroup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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.h
Abstract:
Read memory limits for the current process
--*/

#define MAX_PATH 1024

class CGroup
{
char m_memory_cgroup_path[MAX_PATH];
public:
CGroup();
bool GetPhysicalMemoryLimit(size_t* val);
bool GetSwapMemoryLimit(size_t* val);
private:
bool FindMemoryHierarchyMount(char* mountpath);
bool FindCGroupPathForMemorySubsystem(char* cgrouppath);
bool ReadMemoryValueFromFile(const char* filename, size_t* val);
};

size_t GetRestrictedPhysicalMemoryLimit();
bool GetWorkingSetSize(size_t* val);
27 changes: 24 additions & 3 deletions src/gc/unix/gcenv.unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <pthread.h>
#include <signal.h>
#include "config.h"
#include "cgroup.h"

// clang typedefs uint64_t to be unsigned long long, which clashes with
// PAL/MSVC's unsigned long, causing linker errors. This ugly hack
Expand Down Expand Up @@ -78,6 +79,22 @@ 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;

static size_t g_RestrictedPhysicalMemoryLimit = 0;

static size_t GetRestrictedPhysicalMemoryLimit()
{
LIMITED_METHOD_CONTRACT;

// The limit was cached already
if (g_RestrictedPhysicalMemoryLimit != 0)
return g_RestrictedPhysicalMemoryLimit;

size_t memory_limit = GetRestrictedPhysicalMemoryLimit();

VolatileStore(&g_RestrictedPhysicalMemoryLimit, memory_limit);
return g_RestrictedPhysicalMemoryLimit;
}

// Initialize the interface implementation
// Return:
// true if it has succeeded, false if it has failed
Expand Down Expand Up @@ -442,6 +459,10 @@ size_t GCToOSInterface::GetVirtualMemoryLimit()
// specified, it returns amount of actual physical memory.
uint64_t GCToOSInterface::GetPhysicalMemoryLimit()
{
size_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
if (restricted_limit != 0 && restricted_limit != SIZE_T_MAX)
return restricted_limit;

long pages = sysconf(_SC_PHYS_PAGES);
if (pages == -1)
{
Expand Down Expand Up @@ -471,13 +492,13 @@ 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;
available = total - used;
load = (uint32_t)((used * 100) / total);
}

Expand Down
11 changes: 10 additions & 1 deletion src/pal/inc/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,6 @@ PAL_ProbeMemory(
BOOL fWriteAccess);

/******************* winuser.h Entrypoints *******************************/

PALIMPORT
LPSTR
PALAPI
Expand Down Expand Up @@ -2533,6 +2532,16 @@ DWORD
PALAPI
PAL_GetLogicalCpuCountFromOS(VOID);

PALIMPORT
size_t
PALAPI
PAL_GetRestrictedPhysicalMemoryLimit(VOID);

PALIMPORT
BOOL
PALAPI
PAL_GetWorkingSetSize(size_t* val);

PALIMPORT
size_t
PALAPI
Expand Down
1 change: 1 addition & 0 deletions src/pal/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 5fc16f8

Please sign in to comment.