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

Commit c9af4b3

Browse files
committed
Complete and fix cgroup cpu and memory limiting
* Implement GetCurrentProcessCpuCount for Unix * Add CGroup CFS CPU limit support * Support docker cgroup limits * The parsing would find the wrong '-' in lines like this: 354 347 0:28 /system.slice/docker-654dd7b6b8bbfe1739ae3309b471e95ccc82b3a3f56b7879f0a811d68b5c4e1d.scope /sys/fs/cgroup/cpuacct,cpu ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct,cpu * In a docker container, the mount root and relative path in the cgroup have the same value, the relative path must not be appended to the mount point.
1 parent 1b28a11 commit c9af4b3

File tree

12 files changed

+498
-142
lines changed

12 files changed

+498
-142
lines changed

src/classlibnative/bcltype/system.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,13 @@ INT32 QCALLTYPE SystemNative::GetProcessorCount()
393393
processorCount = systemInfo.dwNumberOfProcessors;
394394
}
395395

396+
#ifdef FEATURE_PAL
397+
uint32_t cpuLimit;
398+
399+
if (PAL_GetCpuLimit(&cpuLimit) && cpuLimit < processorCount)
400+
processorCount = cpuLimit;
401+
#endif
402+
396403
END_QCALL;
397404

398405
return processorCount;

src/dlls/mscordac/mscordac_unixexports.src

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ PAL_GetResourceString
2222
PAL_get_stdout
2323
PAL_get_stderr
2424
PAL_GetCurrentThread
25+
PAL_GetCpuLimit
2526
PAL_GetSymbolModuleBase
2627
PAL_GetTransportPipeName
2728
PAL_InitializeDLL
@@ -106,6 +107,7 @@ GetLastError
106107
GetLongPathNameW
107108
GetModuleFileNameW
108109
GetProcAddress
110+
GetProcessAffinityMask
109111
GetProcessHeap
110112
GetShortPathNameW
111113
GetStdHandle

src/dlls/mscoree/mscorwks_unixexports.src

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ GetFileType
5656
GetFullPathNameW
5757
GetLongPathNameW
5858
GetProcAddress
59+
GetProcessAffinityMask
5960
GetStdHandle
6061
GetSystemInfo
6162
GetTempFileNameW

src/gc/unix/cgroup.cpp

Lines changed: 164 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Module Name:
99
cgroup.cpp
1010
1111
Abstract:
12-
Read memory limits for the current process
12+
Read memory and cpu limits for the current process
1313
--*/
1414
#include <cstdint>
1515
#include <cstddef>
@@ -26,42 +26,24 @@ Module Name:
2626
#define PROC_CGROUP_FILENAME "/proc/self/cgroup"
2727
#define PROC_STATM_FILENAME "/proc/self/statm"
2828
#define MEM_LIMIT_FILENAME "/memory.limit_in_bytes"
29+
#define CFS_QUOTA_FILENAME "/cpu.cfs_quota_us"
30+
#define CFS_PERIOD_FILENAME "/cpu.cfs_period_us"
2931

3032
class CGroup
3133
{
3234
char* m_memory_cgroup_path;
35+
char* m_cpu_cgroup_path;
3336
public:
3437
CGroup()
3538
{
36-
m_memory_cgroup_path = nullptr;
37-
char* memoryHierarchyMount = nullptr;
38-
char *cgroup_path_relative_to_mount = nullptr;
39-
size_t len;
40-
memoryHierarchyMount = FindMemoryHierarchyMount();
41-
if (memoryHierarchyMount == nullptr)
42-
goto done;
43-
44-
cgroup_path_relative_to_mount = FindCGroupPathForMemorySubsystem();
45-
if (cgroup_path_relative_to_mount == nullptr)
46-
goto done;
47-
48-
len = strlen(memoryHierarchyMount);
49-
len += strlen(cgroup_path_relative_to_mount);
50-
m_memory_cgroup_path = (char*)malloc(len+1);
51-
if (m_memory_cgroup_path == nullptr)
52-
goto done;
53-
54-
strcpy(m_memory_cgroup_path, memoryHierarchyMount);
55-
strcat(m_memory_cgroup_path, cgroup_path_relative_to_mount);
56-
57-
done:
58-
free(memoryHierarchyMount);
59-
free(cgroup_path_relative_to_mount);
39+
m_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem);
40+
m_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem);
6041
}
6142

6243
~CGroup()
6344
{
6445
free(m_memory_cgroup_path);
46+
free(m_cpu_cgroup_path);
6547
}
6648

6749
bool GetPhysicalMemoryLimit(size_t *val)
@@ -84,15 +66,89 @@ class CGroup
8466
free(mem_limit_filename);
8567
return result;
8668
}
69+
70+
bool GetCpuLimit(uint32_t *val)
71+
{
72+
long long quota;
73+
long long period;
74+
long long cpu_count;
75+
76+
quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME);
77+
if (quota <= 0)
78+
return false;
79+
80+
period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME);
81+
if (period <= 0)
82+
return false;
83+
84+
// Cannot have less than 1 CPU
85+
if (quota <= period)
86+
{
87+
*val = 1;
88+
return true;
89+
}
90+
91+
cpu_count = quota / period;
92+
if (cpu_count < UINT32_MAX)
93+
{
94+
*val = cpu_count;
95+
}
96+
else
97+
{
98+
*val = UINT32_MAX;
99+
}
100+
101+
return true;
102+
}
87103

88104
private:
89-
char* FindMemoryHierarchyMount()
105+
static bool IsMemorySubsystem(const char *strTok){
106+
return strcmp("memory", strTok) == 0;
107+
}
108+
109+
static bool IsCpuSubsystem(const char *strTok){
110+
return strcmp("cpu", strTok) == 0;
111+
}
112+
113+
static char* FindCgroupPath(bool (*is_subsystem)(const char *)){
114+
char *cgroup_path = nullptr;
115+
char *hierarchy_mount = nullptr;
116+
char *hierarchy_root = nullptr;
117+
char *cgroup_path_relative_to_mount = nullptr;
118+
119+
FindHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root);
120+
if (hierarchy_mount == nullptr || hierarchy_root == nullptr)
121+
goto done;
122+
123+
cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_subsystem);
124+
if (cgroup_path_relative_to_mount == nullptr)
125+
goto done;
126+
127+
cgroup_path = (char*)malloc(strlen(hierarchy_mount) + strlen(cgroup_path_relative_to_mount) + 1);
128+
if (cgroup_path == nullptr)
129+
goto done;
130+
131+
strcpy(cgroup_path, hierarchy_mount);
132+
// For a host cgroup, we need to append the relative path.
133+
// In a docker container, the root and relative path are the same and we don't need to append.
134+
if (strcmp(hierarchy_root, cgroup_path_relative_to_mount) != 0)
135+
strcat(cgroup_path, cgroup_path_relative_to_mount);
136+
137+
done:
138+
free(hierarchy_mount);
139+
free(hierarchy_root);
140+
free(cgroup_path_relative_to_mount);
141+
return cgroup_path;
142+
}
143+
144+
static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot)
90145
{
91146
char *line = nullptr;
92147
size_t lineLen = 0, maxLineLen = 0;
93148
char *filesystemType = nullptr;
94149
char *options = nullptr;
95-
char* mountpath = nullptr;
150+
char *mountpath = nullptr;
151+
char *mountroot = nullptr;
96152

97153
FILE *mountinfofile = fopen(PROC_MOUNTINFO_FILENAME, "r");
98154
if (mountinfofile == nullptr)
@@ -113,11 +169,11 @@ class CGroup
113169
maxLineLen = lineLen;
114170
}
115171

116-
char* separatorChar = strchr(line, '-');
172+
char* separatorChar = strstr(line, " - ");
117173

118174
// See man page of proc to get format for /proc/self/mountinfo file
119175
int sscanfRet = sscanf(separatorChar,
120-
"- %s %*s %s",
176+
" - %s %*s %s",
121177
filesystemType,
122178
options);
123179
if (sscanfRet != 2)
@@ -132,37 +188,43 @@ class CGroup
132188
char* strTok = strtok_r(options, ",", &context);
133189
while (strTok != nullptr)
134190
{
135-
if (strncmp("memory", strTok, 6) == 0)
191+
if (is_subsystem(strTok))
136192
{
137193
mountpath = (char*)malloc(lineLen+1);
138194
if (mountpath == nullptr)
139195
goto done;
196+
mountroot = (char*)malloc(lineLen+1);
197+
if (mountroot == nullptr)
198+
goto done;
140199

141200
sscanfRet = sscanf(line,
142-
"%*s %*s %*s %*s %s ",
201+
"%*s %*s %*s %s %s ",
202+
mountroot,
143203
mountpath);
144-
if (sscanfRet != 1)
145-
{
146-
free(mountpath);
147-
mountpath = nullptr;
204+
if (sscanfRet != 2)
148205
assert(!"Failed to parse mount info file contents with sscanf.");
149-
}
206+
207+
// assign the output arguments and clear the locals so we don't free them.
208+
*pmountpath = mountpath;
209+
*pmountroot = mountroot;
210+
mountpath = mountroot = nullptr;
150211
goto done;
151212
}
152213
strTok = strtok_r(nullptr, ",", &context);
153214
}
154215
}
155216
}
156217
done:
218+
free(mountpath);
219+
free(mountroot);
157220
free(filesystemType);
158221
free(options);
159222
free(line);
160223
if (mountinfofile)
161224
fclose(mountinfofile);
162-
return mountpath;
163225
}
164226

165-
char* FindCGroupPathForMemorySubsystem()
227+
static char* FindCGroupPathForSubsystem(bool (*is_subsystem)(const char *))
166228
{
167229
char *line = nullptr;
168230
size_t lineLen = 0;
@@ -205,7 +267,7 @@ class CGroup
205267
char* strTok = strtok_r(subsystem_list, ",", &context);
206268
while (strTok != nullptr)
207269
{
208-
if (strncmp("memory", strTok, 6) == 0)
270+
if (is_subsystem(strTok))
209271
{
210272
result = true;
211273
break;
@@ -271,6 +333,59 @@ class CGroup
271333
free(line);
272334
return result;
273335
}
336+
337+
long long ReadCpuCGroupValue(const char* subsystemFilename){
338+
char *filename = nullptr;
339+
bool result = false;
340+
long long val;
341+
342+
if (m_cpu_cgroup_path == nullptr)
343+
return -1;
344+
345+
filename = (char*)malloc(strlen(m_cpu_cgroup_path) + strlen(subsystemFilename) + 1);
346+
if (filename == nullptr)
347+
return -1;
348+
349+
strcpy(filename, m_cpu_cgroup_path);
350+
strcat(filename, subsystemFilename);
351+
result = ReadLongLongValueFromFile(filename, &val);
352+
free(filename);
353+
if (!result)
354+
return -1;
355+
356+
return val;
357+
}
358+
359+
bool ReadLongLongValueFromFile(const char* filename, long long* val)
360+
{
361+
bool result = false;
362+
char *line = nullptr;
363+
size_t lineLen = 0;
364+
365+
FILE* file = nullptr;
366+
367+
if (val == nullptr)
368+
goto done;
369+
370+
file = fopen(filename, "r");
371+
if (file == nullptr)
372+
goto done;
373+
374+
if (getline(&line, &lineLen, file) == -1)
375+
goto done;
376+
377+
errno = 0;
378+
*val = atoll(line);
379+
if (errno != 0)
380+
goto done;
381+
382+
result = true;
383+
done:
384+
if (file)
385+
fclose(file);
386+
free(line);
387+
return result;
388+
}
274389
};
275390

276391
size_t GetRestrictedPhysicalMemoryLimit()
@@ -340,3 +455,13 @@ bool GetWorkingSetSize(size_t* val)
340455
free(line);
341456
return result;
342457
}
458+
459+
bool GetCpuLimit(uint32_t* val)
460+
{
461+
CGroup cgroup;
462+
463+
if (val == nullptr)
464+
return false;
465+
466+
return cgroup.GetCpuLimit(val);
467+
}

src/gc/unix/config.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
#cmakedefine01 HAVE_SCHED_GETCPU
1313
#cmakedefine01 HAVE_PTHREAD_CONDATTR_SETCLOCK
1414
#cmakedefine01 HAVE_MACH_ABSOLUTE_TIME
15+
#cmakedefine01 HAVE_SCHED_GETAFFINITY
1516

1617
#endif // __CONFIG_H__

src/gc/unix/configure.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,6 @@ check_cxx_source_runs("
5252
}
5353
" HAVE_MACH_ABSOLUTE_TIME)
5454

55+
check_library_exists(c sched_getaffinity "" HAVE_SCHED_GETAFFINITY)
56+
5557
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)

0 commit comments

Comments
 (0)