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