| /* |
| * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| #include <string.h> |
| #include <math.h> |
| #include <errno.h> |
| #include "cgroupV1Subsystem_linux.hpp" |
| #include "cgroupUtil_linux.hpp" |
| #include "logging/log.hpp" |
| #include "memory/allocation.hpp" |
| #include "runtime/globals.hpp" |
| #include "runtime/os.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| #include "os_linux.hpp" |
| |
| /* |
| * Set directory to subsystem specific files based |
| * on the contents of the mountinfo and cgroup files. |
| * |
| * The method determines whether it runs in |
| * - host mode |
| * - container mode |
| * |
| * In the host mode, _root is equal to "/" and |
| * the subsystem path is equal to the _mount_point path |
| * joined with cgroup_path. |
| * |
| * In the container mode, it can be two possibilities: |
| * - private namespace (cgroupns=private) |
| * - host namespace (cgroupns=host, default mode in cgroup V1 hosts) |
| * |
| * Private namespace is equivalent to the host mode, i.e. |
| * the subsystem path is set by concatenating |
| * _mount_point and cgroup_path. |
| * |
| * In the host namespace, _root is equal to host's cgroup path |
| * of the control group to which the containerized process |
| * belongs to at the moment of creation. The mountinfo and |
| * cgroup files are mirrored from the host, while the subsystem |
| * specific files are mapped directly at _mount_point, i.e. |
| * at /sys/fs/cgroup/<controller>/, the subsystem path is |
| * then set equal to _mount_point. |
| * |
| * A special case of the subsystem path is when a cgroup path |
| * includes a subgroup, when a containerized process was associated |
| * with an existing cgroup, that is different from cgroup |
| * in which the process has been created. |
| * Here, the _root is equal to the host's initial cgroup path, |
| * cgroup_path will be equal to host's new cgroup path. |
| * As host cgroup hierarchies are not accessible in the container, |
| * it needs to be determined which part of cgroup path |
| * is accessible inside container, i.e. mapped under |
| * /sys/fs/cgroup/<controller>/<subgroup>. |
| * In Docker default setup, host's cgroup path can be |
| * of the form: /docker/<CONTAINER_ID>/<subgroup>, |
| * from which only <subgroup> is mapped. |
| * The method trims cgroup path from left, until the subgroup |
| * component is found. The subsystem path will be set to |
| * the _mount_point joined with the subgroup path. |
| */ |
| void CgroupV1Controller::set_subsystem_path(const char* cgroup_path) { |
| if (_cgroup_path != nullptr) { |
| os::free(_cgroup_path); |
| } |
| if (_path != nullptr) { |
| os::free(_path); |
| _path = nullptr; |
| } |
| _cgroup_path = os::strdup(cgroup_path); |
| stringStream ss; |
| if (_root != nullptr && cgroup_path != nullptr) { |
| ss.print_raw(_mount_point); |
| if (strcmp(_root, "/") == 0) { |
| // host processes and containers with cgroupns=private |
| if (strcmp(cgroup_path,"/") != 0) { |
| ss.print_raw(cgroup_path); |
| } |
| } else { |
| // containers with cgroupns=host, default setting is _root==cgroup_path |
| if (strcmp(_root, cgroup_path) != 0) { |
| if (*cgroup_path != '\0' && strcmp(cgroup_path, "/") != 0) { |
| // When moved to a subgroup, between subgroups, the path suffix will change. |
| const char *suffix = cgroup_path; |
| while (suffix != nullptr) { |
| stringStream pp; |
| pp.print_raw(_mount_point); |
| pp.print_raw(suffix); |
| if (os::file_exists(pp.base())) { |
| ss.print_raw(suffix); |
| if (suffix != cgroup_path) { |
| log_trace(os, container)("set_subsystem_path: cgroup v1 path reduced to: %s.", suffix); |
| } |
| break; |
| } |
| log_trace(os, container)("set_subsystem_path: skipped non-existent directory: %s.", suffix); |
| suffix = strchr(suffix + 1, '/'); |
| } |
| } |
| } |
| } |
| _path = os::strdup(ss.base()); |
| } |
| } |
| |
| /* |
| * The common case, containers, we have _root == _cgroup_path, and thus set the |
| * controller path to the _mount_point. This is where the limits are exposed in |
| * the cgroup pseudo filesystem (at the leaf) and adjustment of the path won't |
| * be needed for that reason. |
| */ |
| bool CgroupV1Controller::needs_hierarchy_adjustment() { |
| assert(_cgroup_path != nullptr, "sanity"); |
| return strcmp(_root, _cgroup_path) != 0; |
| } |
| |
| static inline |
| void verbose_log(julong read_mem_limit, julong host_mem) { |
| if (log_is_enabled(Debug, os, container)) { |
| jlong mem_limit = (jlong)read_mem_limit; // account for negative values |
| if (mem_limit < 0 || read_mem_limit >= host_mem) { |
| const char *reason; |
| if (mem_limit == OSCONTAINER_ERROR) { |
| reason = "failed"; |
| } else if (mem_limit == -1) { |
| reason = "unlimited"; |
| } else { |
| assert(read_mem_limit >= host_mem, "Expected read value exceeding host_mem"); |
| // Exceeding physical memory is treated as unlimited. This implementation |
| // caps it at host_mem since Cg v1 has no value to represent 'max'. |
| reason = "ignored"; |
| } |
| log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value " JLONG_FORMAT, |
| reason, mem_limit, host_mem); |
| } |
| } |
| } |
| |
| jlong CgroupV1MemoryController::read_memory_limit_in_bytes(julong phys_mem) { |
| julong memlimit; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.limit_in_bytes", "Memory Limit", memlimit); |
| if (memlimit >= phys_mem) { |
| verbose_log(memlimit, phys_mem); |
| return (jlong)-1; |
| } else { |
| verbose_log(memlimit, phys_mem); |
| return (jlong)memlimit; |
| } |
| } |
| |
| /* read_mem_swap |
| * |
| * Determine the memory and swap limit metric. Returns a positive limit value strictly |
| * lower than the physical memory and swap limit iff there is a limit. Otherwise a |
| * negative value is returned indicating the determined status. |
| * |
| * returns: |
| * * A number > 0 if the limit is available and lower than a physical upper bound. |
| * * OSCONTAINER_ERROR if the limit cannot be retrieved (i.e. not supported) or |
| * * -1 if there isn't any limit in place (note: includes values which exceed a physical |
| * upper bound) |
| */ |
| jlong CgroupV1MemoryController::read_mem_swap(julong host_total_memsw) { |
| julong memswlimit; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.memsw.limit_in_bytes", "Memory and Swap Limit", memswlimit); |
| if (memswlimit >= host_total_memsw) { |
| log_trace(os, container)("Memory and Swap Limit is: Unlimited"); |
| return (jlong)-1; |
| } else { |
| return (jlong)memswlimit; |
| } |
| } |
| |
| jlong CgroupV1MemoryController::memory_and_swap_limit_in_bytes(julong host_mem, julong host_swap) { |
| jlong memory_swap = read_mem_swap(host_mem + host_swap); |
| if (memory_swap == -1) { |
| return memory_swap; |
| } |
| // If there is a swap limit, but swappiness == 0, reset the limit |
| // to the memory limit. Do the same for cases where swap isn't |
| // supported. |
| jlong swappiness = read_mem_swappiness(); |
| if (swappiness == 0 || memory_swap == OSCONTAINER_ERROR) { |
| jlong memlimit = read_memory_limit_in_bytes(host_mem); |
| if (memory_swap == OSCONTAINER_ERROR) { |
| log_trace(os, container)("Memory and Swap Limit has been reset to " JLONG_FORMAT " because swap is not supported", memlimit); |
| } else { |
| log_trace(os, container)("Memory and Swap Limit has been reset to " JLONG_FORMAT " because swappiness is 0", memlimit); |
| } |
| return memlimit; |
| } |
| return memory_swap; |
| } |
| |
| static inline |
| jlong memory_swap_usage_impl(CgroupController* ctrl) { |
| julong memory_swap_usage; |
| CONTAINER_READ_NUMBER_CHECKED(ctrl, "/memory.memsw.usage_in_bytes", "mem swap usage", memory_swap_usage); |
| return (jlong)memory_swap_usage; |
| } |
| |
| jlong CgroupV1MemoryController::memory_and_swap_usage_in_bytes(julong phys_mem, julong host_swap) { |
| jlong memory_sw_limit = memory_and_swap_limit_in_bytes(phys_mem, host_swap); |
| jlong memory_limit = read_memory_limit_in_bytes(phys_mem); |
| if (memory_sw_limit > 0 && memory_limit > 0) { |
| jlong delta_swap = memory_sw_limit - memory_limit; |
| if (delta_swap > 0) { |
| return memory_swap_usage_impl(reader()); |
| } |
| } |
| return memory_usage_in_bytes(); |
| } |
| |
| jlong CgroupV1MemoryController::read_mem_swappiness() { |
| julong swappiness; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.swappiness", "Swappiness", swappiness); |
| return (jlong)swappiness; |
| } |
| |
| jlong CgroupV1MemoryController::memory_soft_limit_in_bytes(julong phys_mem) { |
| julong memsoftlimit; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.soft_limit_in_bytes", "Memory Soft Limit", memsoftlimit); |
| if (memsoftlimit >= phys_mem) { |
| log_trace(os, container)("Memory Soft Limit is: Unlimited"); |
| return (jlong)-1; |
| } else { |
| return (jlong)memsoftlimit; |
| } |
| } |
| |
| // Constructor |
| CgroupV1Subsystem::CgroupV1Subsystem(CgroupV1Controller* cpuset, |
| CgroupV1CpuController* cpu, |
| CgroupV1Controller* cpuacct, |
| CgroupV1Controller* pids, |
| CgroupV1MemoryController* memory) : |
| _cpuset(cpuset), |
| _cpuacct(cpuacct), |
| _pids(pids) { |
| CgroupUtil::adjust_controller(memory); |
| CgroupUtil::adjust_controller(cpu); |
| _memory = new CachingCgroupController<CgroupMemoryController>(memory); |
| _cpu = new CachingCgroupController<CgroupCpuController>(cpu); |
| } |
| |
| bool CgroupV1Subsystem::is_containerized() { |
| // containerized iff all required controllers are mounted |
| // read-only. See OSContainer::is_containerized() for |
| // the full logic. |
| // |
| return _memory->controller()->is_read_only() && |
| _cpu->controller()->is_read_only() && |
| _cpuacct->is_read_only() && |
| _cpuset->is_read_only(); |
| } |
| |
| /* memory_usage_in_bytes |
| * |
| * Return the amount of used memory for this process. |
| * |
| * return: |
| * memory usage in bytes or |
| * -1 for unlimited |
| * OSCONTAINER_ERROR for not supported |
| */ |
| jlong CgroupV1MemoryController::memory_usage_in_bytes() { |
| julong memusage; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.usage_in_bytes", "Memory Usage", memusage); |
| return (jlong)memusage; |
| } |
| |
| /* memory_max_usage_in_bytes |
| * |
| * Return the maximum amount of used memory for this process. |
| * |
| * return: |
| * max memory usage in bytes or |
| * OSCONTAINER_ERROR for not supported |
| */ |
| jlong CgroupV1MemoryController::memory_max_usage_in_bytes() { |
| julong memmaxusage; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.max_usage_in_bytes", "Maximum Memory Usage", memmaxusage); |
| return (jlong)memmaxusage; |
| } |
| |
| jlong CgroupV1MemoryController::rss_usage_in_bytes() { |
| julong rss; |
| bool is_ok = reader()->read_numerical_key_value("/memory.stat", "rss", &rss); |
| if (!is_ok) { |
| return OSCONTAINER_ERROR; |
| } |
| log_trace(os, container)("RSS usage is: " JULONG_FORMAT, rss); |
| return (jlong)rss; |
| } |
| |
| jlong CgroupV1MemoryController::cache_usage_in_bytes() { |
| julong cache; |
| bool is_ok = reader()->read_numerical_key_value("/memory.stat", "cache", &cache); |
| if (!is_ok) { |
| return OSCONTAINER_ERROR; |
| } |
| log_trace(os, container)("Cache usage is: " JULONG_FORMAT, cache); |
| return cache; |
| } |
| |
| jlong CgroupV1MemoryController::kernel_memory_usage_in_bytes() { |
| julong kmem_usage; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.usage_in_bytes", "Kernel Memory Usage", kmem_usage); |
| return (jlong)kmem_usage; |
| } |
| |
| jlong CgroupV1MemoryController::kernel_memory_limit_in_bytes(julong phys_mem) { |
| julong kmem_limit; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.limit_in_bytes", "Kernel Memory Limit", kmem_limit); |
| if (kmem_limit >= phys_mem) { |
| return (jlong)-1; |
| } |
| return (jlong)kmem_limit; |
| } |
| |
| jlong CgroupV1MemoryController::kernel_memory_max_usage_in_bytes() { |
| julong kmem_max_usage; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.max_usage_in_bytes", "Maximum Kernel Memory Usage", kmem_max_usage); |
| return (jlong)kmem_max_usage; |
| } |
| |
| void CgroupV1MemoryController::print_version_specific_info(outputStream* st, julong phys_mem) { |
| jlong kmem_usage = kernel_memory_usage_in_bytes(); |
| jlong kmem_limit = kernel_memory_limit_in_bytes(phys_mem); |
| jlong kmem_max_usage = kernel_memory_max_usage_in_bytes(); |
| |
| OSContainer::print_container_helper(st, kmem_limit, "kernel_memory_limit_in_bytes"); |
| OSContainer::print_container_helper(st, kmem_usage, "kernel_memory_usage_in_bytes"); |
| OSContainer::print_container_helper(st, kmem_max_usage, "kernel_memory_max_usage_in_bytes"); |
| } |
| |
| char* CgroupV1Subsystem::cpu_cpuset_cpus() { |
| char cpus[1024]; |
| CONTAINER_READ_STRING_CHECKED(_cpuset, "/cpuset.cpus", "cpuset.cpus", cpus, 1024); |
| return os::strdup(cpus); |
| } |
| |
| char* CgroupV1Subsystem::cpu_cpuset_memory_nodes() { |
| char mems[1024]; |
| CONTAINER_READ_STRING_CHECKED(_cpuset, "/cpuset.mems", "cpuset.mems", mems, 1024); |
| return os::strdup(mems); |
| } |
| |
| /* cpu_quota |
| * |
| * Return the number of microseconds per period |
| * process is guaranteed to run. |
| * |
| * return: |
| * quota time in microseconds |
| * -1 for no quota |
| * OSCONTAINER_ERROR for not supported |
| */ |
| int CgroupV1CpuController::cpu_quota() { |
| julong quota; |
| bool is_ok = reader()->read_number("/cpu.cfs_quota_us", "a); |
| if (!is_ok) { |
| log_trace(os, container)("CPU Quota failed: %d", OSCONTAINER_ERROR); |
| return OSCONTAINER_ERROR; |
| } |
| // cast to int since the read value might be negative |
| // and we want to avoid logging -1 as a large unsigned value. |
| int quota_int = (int)quota; |
| log_trace(os, container)("CPU Quota is: %d", quota_int); |
| return quota_int; |
| } |
| |
| int CgroupV1CpuController::cpu_period() { |
| julong period; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.cfs_period_us", "CPU Period", period); |
| return (int)period; |
| } |
| |
| /* cpu_shares |
| * |
| * Return the amount of cpu shares available to the process |
| * |
| * return: |
| * Share number (typically a number relative to 1024) |
| * (2048 typically expresses 2 CPUs worth of processing) |
| * -1 for no share setup |
| * OSCONTAINER_ERROR for not supported |
| */ |
| int CgroupV1CpuController::cpu_shares() { |
| julong shares; |
| CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.shares", "CPU Shares", shares); |
| int shares_int = (int)shares; |
| // Convert 1024 to no shares setup |
| if (shares_int == 1024) return -1; |
| |
| return shares_int; |
| } |
| |
| /* pids_max |
| * |
| * Return the maximum number of tasks available to the process |
| * |
| * return: |
| * maximum number of tasks |
| * -1 for unlimited |
| * OSCONTAINER_ERROR for not supported |
| */ |
| jlong CgroupV1Subsystem::pids_max() { |
| if (_pids == nullptr) return OSCONTAINER_ERROR; |
| jlong pids_max; |
| CONTAINER_READ_NUMBER_CHECKED_MAX(_pids, "/pids.max", "Maximum number of tasks", pids_max); |
| return pids_max; |
| } |
| |
| /* pids_current |
| * |
| * The number of tasks currently in the cgroup (and its descendants) of the process |
| * |
| * return: |
| * current number of tasks |
| * OSCONTAINER_ERROR for not supported |
| */ |
| jlong CgroupV1Subsystem::pids_current() { |
| if (_pids == nullptr) return OSCONTAINER_ERROR; |
| julong pids_current; |
| CONTAINER_READ_NUMBER_CHECKED(_pids, "/pids.current", "Current number of tasks", pids_current); |
| return (jlong)pids_current; |
| } |