1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5/*++
6
7Module Name:
8
9 cgroup.cpp
10
11Abstract:
12 Read memory and cpu limits for the current process
13--*/
14
15#include "pal/dbgmsg.h"
16SET_DEFAULT_DEBUG_CHANNEL(MISC);
17#include "pal/palinternal.h"
18#include <sys/resource.h>
19#include "pal/virtual.h"
20#include "pal/cgroup.h"
21#include <algorithm>
22
23#define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo"
24#define PROC_CGROUP_FILENAME "/proc/self/cgroup"
25#define PROC_STATM_FILENAME "/proc/self/statm"
26#define MEM_LIMIT_FILENAME "/memory.limit_in_bytes"
27#define MEM_USAGE_FILENAME "/memory.usage_in_bytes"
28#define CFS_QUOTA_FILENAME "/cpu.cfs_quota_us"
29#define CFS_PERIOD_FILENAME "/cpu.cfs_period_us"
30class CGroup
31{
32 static char *s_memory_cgroup_path;
33 static char *s_cpu_cgroup_path;
34public:
35 static void Initialize()
36 {
37 s_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem);
38 s_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem);
39 }
40
41 static void Cleanup()
42 {
43 PAL_free(s_memory_cgroup_path);
44 PAL_free(s_cpu_cgroup_path);
45 }
46
47 static bool GetPhysicalMemoryLimit(size_t *val)
48 {
49 char *mem_limit_filename = nullptr;
50 bool result = false;
51
52 if (s_memory_cgroup_path == nullptr)
53 return result;
54
55 size_t len = strlen(s_memory_cgroup_path);
56 len += strlen(MEM_LIMIT_FILENAME);
57 mem_limit_filename = (char*)PAL_malloc(len+1);
58 if (mem_limit_filename == nullptr)
59 return result;
60
61 strcpy_s(mem_limit_filename, len+1, s_memory_cgroup_path);
62 strcat_s(mem_limit_filename, len+1, MEM_LIMIT_FILENAME);
63 result = ReadMemoryValueFromFile(mem_limit_filename, val);
64 PAL_free(mem_limit_filename);
65 return result;
66 }
67
68 static bool GetPhysicalMemoryUsage(size_t *val)
69 {
70 char *mem_usage_filename = nullptr;
71 bool result = false;
72
73 if (s_memory_cgroup_path == nullptr)
74 return result;
75
76 size_t len = strlen(s_memory_cgroup_path);
77 len += strlen(MEM_USAGE_FILENAME);
78 mem_usage_filename = (char*)malloc(len+1);
79 if (mem_usage_filename == nullptr)
80 return result;
81
82 strcpy(mem_usage_filename, s_memory_cgroup_path);
83 strcat(mem_usage_filename, MEM_USAGE_FILENAME);
84 result = ReadMemoryValueFromFile(mem_usage_filename, val);
85 free(mem_usage_filename);
86 return result;
87 }
88
89 static bool GetCpuLimit(UINT *val)
90 {
91 long long quota;
92 long long period;
93 long long cpu_count;
94
95 quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME);
96 if (quota <= 0)
97 return false;
98
99 period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME);
100 if (period <= 0)
101 return false;
102
103 // Cannot have less than 1 CPU
104 if (quota <= period)
105 {
106 *val = 1;
107 return true;
108 }
109
110 cpu_count = quota / period;
111 if (cpu_count < UINT_MAX)
112 {
113 *val = cpu_count;
114 }
115 else
116 {
117 *val = UINT_MAX;
118 }
119
120 return true;
121 }
122
123private:
124 static bool IsMemorySubsystem(const char *strTok){
125 return strcmp("memory", strTok) == 0;
126 }
127
128 static bool IsCpuSubsystem(const char *strTok){
129 return strcmp("cpu", strTok) == 0;
130 }
131
132 static char* FindCgroupPath(bool (*is_subsystem)(const char *)){
133 char *cgroup_path = nullptr;
134 char *hierarchy_mount = nullptr;
135 char *hierarchy_root = nullptr;
136 char *cgroup_path_relative_to_mount = nullptr;
137 size_t len;
138
139 FindHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root);
140 if (hierarchy_mount == nullptr || hierarchy_root == nullptr)
141 goto done;
142
143 cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_subsystem);
144 if (cgroup_path_relative_to_mount == nullptr)
145 goto done;
146
147 len = strlen(hierarchy_mount);
148 len += strlen(cgroup_path_relative_to_mount);
149 cgroup_path = (char*)PAL_malloc(len+1);
150 if (cgroup_path == nullptr)
151 goto done;
152
153 strcpy_s(cgroup_path, len+1, hierarchy_mount);
154 // For a host cgroup, we need to append the relative path.
155 // In a docker container, the root and relative path are the same and we don't need to append.
156 if (strcmp(hierarchy_root, cgroup_path_relative_to_mount) != 0)
157 strcat_s(cgroup_path, len+1, cgroup_path_relative_to_mount);
158
159 done:
160 PAL_free(hierarchy_mount);
161 PAL_free(hierarchy_root);
162 PAL_free(cgroup_path_relative_to_mount);
163 return cgroup_path;
164 }
165
166 static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot)
167 {
168 char *line = nullptr;
169 size_t lineLen = 0, maxLineLen = 0;
170 char *filesystemType = nullptr;
171 char *options = nullptr;
172 char *mountpath = nullptr;
173 char *mountroot = nullptr;
174
175 FILE *mountinfofile = fopen(PROC_MOUNTINFO_FILENAME, "r");
176 if (mountinfofile == nullptr)
177 goto done;
178
179 while (getline(&line, &lineLen, mountinfofile) != -1)
180 {
181 if (filesystemType == nullptr || lineLen > maxLineLen)
182 {
183 PAL_free(filesystemType);
184 PAL_free(options);
185 filesystemType = (char*)PAL_malloc(lineLen+1);
186 if (filesystemType == nullptr)
187 goto done;
188 options = (char*)PAL_malloc(lineLen+1);
189 if (options == nullptr)
190 goto done;
191 maxLineLen = lineLen;
192 }
193 char* separatorChar = strstr(line, " - ");;
194
195 // See man page of proc to get format for /proc/self/mountinfo file
196 int sscanfRet = sscanf_s(separatorChar,
197 " - %s %*s %s",
198 filesystemType, lineLen+1,
199 options, lineLen+1);
200 if (sscanfRet != 2)
201 {
202 _ASSERTE(!"Failed to parse mount info file contents with sscanf_s.");
203 goto done;
204 }
205
206 if (strncmp(filesystemType, "cgroup", 6) == 0)
207 {
208 char* context = nullptr;
209 char* strTok = strtok_s(options, ",", &context);
210 while (strTok != nullptr)
211 {
212 if (is_subsystem(strTok))
213 {
214 mountpath = (char*)PAL_malloc(lineLen+1);
215 if (mountpath == nullptr)
216 goto done;
217 mountroot = (char*)PAL_malloc(lineLen+1);
218 if (mountroot == nullptr)
219 goto done;
220
221 sscanfRet = sscanf_s(line,
222 "%*s %*s %*s %s %s ",
223 mountroot, lineLen+1,
224 mountpath, lineLen+1);
225 if (sscanfRet != 2)
226 _ASSERTE(!"Failed to parse mount info file contents with sscanf_s.");
227
228 // assign the output arguments and clear the locals so we don't free them.
229 *pmountpath = mountpath;
230 *pmountroot = mountroot;
231 mountpath = mountroot = nullptr;
232 goto done;
233 }
234 strTok = strtok_s(nullptr, ",", &context);
235 }
236 }
237 }
238 done:
239 PAL_free(mountpath);
240 PAL_free(mountroot);
241 PAL_free(filesystemType);
242 PAL_free(options);
243 free(line);
244 if (mountinfofile)
245 fclose(mountinfofile);
246 }
247
248 static char* FindCGroupPathForSubsystem(bool (*is_subsystem)(const char *))
249 {
250 char *line = nullptr;
251 size_t lineLen = 0;
252 size_t maxLineLen = 0;
253 char *subsystem_list = nullptr;
254 char *cgroup_path = nullptr;
255 bool result = false;
256
257 FILE *cgroupfile = fopen(PROC_CGROUP_FILENAME, "r");
258 if (cgroupfile == nullptr)
259 goto done;
260
261 while (!result && getline(&line, &lineLen, cgroupfile) != -1)
262 {
263 if (subsystem_list == nullptr || lineLen > maxLineLen)
264 {
265 PAL_free(subsystem_list);
266 PAL_free(cgroup_path);
267 subsystem_list = (char*)PAL_malloc(lineLen+1);
268 if (subsystem_list == nullptr)
269 goto done;
270 cgroup_path = (char*)PAL_malloc(lineLen+1);
271 if (cgroup_path == nullptr)
272 goto done;
273 maxLineLen = lineLen;
274 }
275
276 // See man page of proc to get format for /proc/self/cgroup file
277 int sscanfRet = sscanf_s(line,
278 "%*[^:]:%[^:]:%s",
279 subsystem_list, lineLen+1,
280 cgroup_path, lineLen+1);
281 if (sscanfRet != 2)
282 {
283 _ASSERTE(!"Failed to parse cgroup info file contents with sscanf_s.");
284 goto done;
285 }
286
287 char* context = nullptr;
288 char* strTok = strtok_s(subsystem_list, ",", &context);
289 while (strTok != nullptr)
290 {
291 if (is_subsystem(strTok))
292 {
293 result = true;
294 break;
295 }
296 strTok = strtok_s(nullptr, ",", &context);
297 }
298 }
299 done:
300 PAL_free(subsystem_list);
301 if (!result)
302 {
303 PAL_free(cgroup_path);
304 cgroup_path = nullptr;
305 }
306 free(line);
307 if (cgroupfile)
308 fclose(cgroupfile);
309 return cgroup_path;
310 }
311
312 static bool ReadMemoryValueFromFile(const char* filename, size_t* val)
313 {
314 return ::ReadMemoryValueFromFile(filename, val);
315 }
316
317 static long long ReadCpuCGroupValue(const char* subsystemFilename){
318 char *filename = nullptr;
319 bool result = false;
320 long long val;
321 size_t len;
322
323 if (s_cpu_cgroup_path == nullptr)
324 return -1;
325
326 len = strlen(s_cpu_cgroup_path);
327 len += strlen(subsystemFilename);
328 filename = (char*)PAL_malloc(len + 1);
329 if (filename == nullptr)
330 return -1;
331
332 strcpy_s(filename, len+1, s_cpu_cgroup_path);
333 strcat_s(filename, len+1, subsystemFilename);
334 result = ReadLongLongValueFromFile(filename, &val);
335 PAL_free(filename);
336 if (!result)
337 return -1;
338
339 return val;
340 }
341
342 static bool ReadLongLongValueFromFile(const char* filename, long long* val)
343 {
344 bool result = false;
345 char *line = nullptr;
346 size_t lineLen = 0;
347
348 if (val == nullptr)
349 return false;;
350
351 FILE* file = fopen(filename, "r");
352 if (file == nullptr)
353 goto done;
354
355 if (getline(&line, &lineLen, file) == -1)
356 goto done;
357
358 errno = 0;
359 *val = atoll(line);
360 if (errno != 0)
361 goto done;
362
363 result = true;
364 done:
365 if (file)
366 fclose(file);
367 free(line);
368 return result;
369 }
370};
371
372char *CGroup::s_memory_cgroup_path = nullptr;
373char *CGroup::s_cpu_cgroup_path = nullptr;
374
375void InitializeCGroup()
376{
377 CGroup::Initialize();
378}
379
380void CleanupCGroup()
381{
382 CGroup::Cleanup();
383}
384
385
386size_t
387PALAPI
388PAL_GetRestrictedPhysicalMemoryLimit()
389{
390 size_t physical_memory_limit;
391
392 if (!CGroup::GetPhysicalMemoryLimit(&physical_memory_limit))
393 physical_memory_limit = SIZE_T_MAX;
394
395 struct rlimit curr_rlimit;
396 size_t rlimit_soft_limit = (size_t)RLIM_INFINITY;
397 if (getrlimit(RLIMIT_AS, &curr_rlimit) == 0)
398 {
399 rlimit_soft_limit = curr_rlimit.rlim_cur;
400 }
401 physical_memory_limit = std::min(physical_memory_limit, rlimit_soft_limit);
402
403 // Ensure that limit is not greater than real memory size
404 long pages = sysconf(_SC_PHYS_PAGES);
405 if (pages != -1)
406 {
407 long pageSize = sysconf(_SC_PAGE_SIZE);
408 if (pageSize != -1)
409 {
410 physical_memory_limit = std::min(physical_memory_limit,
411 (size_t)(pages * pageSize));
412 }
413 }
414
415 if(physical_memory_limit == SIZE_T_MAX)
416 physical_memory_limit = 0;
417 return physical_memory_limit;
418}
419
420BOOL
421PALAPI
422PAL_GetPhysicalMemoryUsed(size_t* val)
423{
424 BOOL result = false;
425 size_t linelen;
426 char* line = nullptr;
427
428 if (val == nullptr)
429 return FALSE;
430
431 // Linux uses cgroup usage to trigger oom kills.
432 if (CGroup::GetPhysicalMemoryUsage(val))
433 return TRUE;
434
435 // process resident set size.
436 FILE* file = fopen(PROC_STATM_FILENAME, "r");
437 if (file != nullptr && getline(&line, &linelen, file) != -1)
438 {
439 char* context = nullptr;
440 char* strTok = strtok_s(line, " ", &context);
441 strTok = strtok_s(nullptr, " ", &context);
442
443 errno = 0;
444 *val = strtoull(strTok, nullptr, 0);
445 if(errno == 0)
446 {
447 *val = *val * GetVirtualPageSize();
448 result = true;
449 }
450 }
451
452 if (file)
453 fclose(file);
454 free(line);
455 return result;
456}
457
458BOOL
459PALAPI
460PAL_GetCpuLimit(UINT* val)
461{
462 if (val == nullptr)
463 return FALSE;
464
465 return CGroup::GetCpuLimit(val);
466}
467