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 | |
7 | Module Name: |
8 | |
9 | cgroup.cpp |
10 | |
11 | Abstract: |
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 | |
42 | class CGroup |
43 | { |
44 | static char* s_memory_cgroup_path; |
45 | static char* s_cpu_cgroup_path; |
46 | public: |
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 | |
135 | private: |
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 | |
422 | char *CGroup::s_memory_cgroup_path = nullptr; |
423 | char *CGroup::s_cpu_cgroup_path = nullptr; |
424 | |
425 | void InitializeCGroup() |
426 | { |
427 | CGroup::Initialize(); |
428 | } |
429 | |
430 | void CleanupCGroup() |
431 | { |
432 | CGroup::Cleanup(); |
433 | } |
434 | |
435 | size_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 | |
466 | bool 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 | |
506 | bool GetCpuLimit(uint32_t* val) |
507 | { |
508 | if (val == nullptr) |
509 | return false; |
510 | |
511 | return CGroup::GetCpuLimit(val); |
512 | } |
513 | |