--- old/src/hotspot/os/linux/cgroupSubsystem_linux.cpp 2019-10-14 20:38:38.123929959 +0200 +++ new/src/hotspot/os/linux/cgroupSubsystem_linux.cpp 2019-10-14 20:38:37.997929696 +0200 @@ -27,6 +27,7 @@ #include #include "cgroupSubsystem_linux.hpp" #include "cgroupV1Subsystem_linux.hpp" +#include "cgroupV2Subsystem_linux.hpp" #include "logging/log.hpp" #include "memory/allocation.hpp" #include "runtime/globals.hpp" @@ -39,11 +40,176 @@ CgroupV1Controller* cpu = NULL; CgroupV1Controller* cpuacct = NULL; FILE *mntinfo = NULL; + FILE *cgroups = NULL; FILE *cgroup = NULL; char buf[MAXPATHLEN+1]; char tmproot[MAXPATHLEN+1]; char tmpmount[MAXPATHLEN+1]; char *p; + bool is_cgroupsV2; + // true iff all controllers, memory, cpu, cpuset, cpuacct are enabled + // at the kernel level. + bool all_controllers_enabled; + + CgroupInfo cg_infos[CG_INFO_LENGTH]; + int cpuset_idx = 0; + int cpu_idx = 1; + int cpuacct_idx = 2; + int memory_idx = 3; + + /* + * Read /proc/cgroups so as to be able to distinguish cgroups v2 vs cgroups v1. + * + * For cgroups v1 unified hierarchy, cpu, cpuacct, cpuset, memory controllers + * must have non-zero for the hierarchy ID field. + */ + cgroups = fopen("/proc/cgroups", "r"); + if (cgroups == NULL) { + log_debug(os, container)("Can't open /proc/cgroups, %s", + os::strerror(errno)); + return NULL; + } + + while ((p = fgets(buf, MAXPATHLEN, cgroups)) != NULL) { + char name[MAXPATHLEN+1]; + int hierarchy_id; + int enabled; + + // Format of /proc/cgroups documented via man 7 cgroups + if (sscanf(p, "%s %d %*d %d", name, &hierarchy_id, &enabled) != 3) { + continue; + } + if (strcmp(name, "memory") == 0) { + cg_infos[memory_idx]._name = os::strdup(name); + cg_infos[memory_idx]._hierarchy_id = hierarchy_id; + cg_infos[memory_idx]._enabled = (enabled == 1); + } else if (strcmp(name, "cpuset") == 0) { + cg_infos[cpuset_idx]._name = os::strdup(name); + cg_infos[cpuset_idx]._hierarchy_id = hierarchy_id; + cg_infos[cpuset_idx]._enabled = (enabled == 1); + } else if (strcmp(name, "cpu") == 0) { + cg_infos[cpu_idx]._name = os::strdup(name); + cg_infos[cpu_idx]._hierarchy_id = hierarchy_id; + cg_infos[cpu_idx]._enabled = (enabled == 1); + } else if (strcmp(name, "cpuacct") == 0) { + cg_infos[cpuacct_idx]._name = os::strdup(name); + cg_infos[cpuacct_idx]._hierarchy_id = hierarchy_id; + cg_infos[cpuacct_idx]._enabled = (enabled == 1); + } + } + fclose(cgroups); + + is_cgroupsV2 = true; + all_controllers_enabled = true; + for (int i = 0; i < CG_INFO_LENGTH; i++) { + is_cgroupsV2 = is_cgroupsV2 && cg_infos[i]._hierarchy_id == 0; + all_controllers_enabled = all_controllers_enabled && cg_infos[i]._enabled; + } + + if (!all_controllers_enabled) { + // one or more controllers enabled, disable container support + log_debug(os, container)("One or more required controllers not enabled at kernel level."); + return NULL; + } + + /* + * Read /proc/self/cgroup and determine: + * - the cgroup path for cgroups v2 or + * - on a cgroups v1 system, collect info for mapping + * the host mount point to the local one via /proc/self/mountinfo below. + */ + cgroup = fopen("/proc/self/cgroup", "r"); + if (cgroup == NULL) { + log_debug(os, container)("Can't open /proc/self/cgroup, %s", + os::strerror(errno)); + return NULL; + } + + while ((p = fgets(buf, MAXPATHLEN, cgroup)) != NULL) { + char *controllers; + char *token; + char *hierarchy_id_str; + int hierarchy_id; + char *cgroup_path; + + hierarchy_id_str = strsep(&p, ":"); + hierarchy_id = atoi(hierarchy_id_str); + /* Get controllers and base */ + controllers = strsep(&p, ":"); + cgroup_path = strsep(&p, "\n"); + + if (controllers == NULL) { + continue; + } + + while (!is_cgroupsV2 && (token = strsep(&controllers, ",")) != NULL) { + if (strcmp(token, "memory") == 0) { + assert(hierarchy_id == cg_infos[memory_idx]._hierarchy_id, "/proc/cgroups and /proc/self/cgroup hierarchy mismatch"); + cg_infos[memory_idx]._cgroup_path = os::strdup(cgroup_path); + } else if (strcmp(token, "cpuset") == 0) { + assert(hierarchy_id == cg_infos[cpuset_idx]._hierarchy_id, "/proc/cgroups and /proc/self/cgroup hierarchy mismatch"); + cg_infos[cpuset_idx]._cgroup_path = os::strdup(cgroup_path); + } else if (strcmp(token, "cpu") == 0) { + assert(hierarchy_id == cg_infos[cpu_idx]._hierarchy_id, "/proc/cgroups and /proc/self/cgroup hierarchy mismatch"); + cg_infos[cpu_idx]._cgroup_path = os::strdup(cgroup_path); + } else if (strcmp(token, "cpuacct") == 0) { + assert(hierarchy_id == cg_infos[cpuacct_idx]._hierarchy_id, "/proc/cgroups and /proc/self/cgroup hierarchy mismatch"); + cg_infos[cpuacct_idx]._cgroup_path = os::strdup(cgroup_path); + } + } + if (is_cgroupsV2) { + for (int i = 0; i < CG_INFO_LENGTH; i++) { + cg_infos[i]._cgroup_path = os::strdup(cgroup_path); + } + } + } + fclose(cgroup); + + if (is_cgroupsV2) { + // Find the cgroup2 mount point by reading /proc/self/mountinfo + mntinfo = fopen("/proc/self/mountinfo", "r"); + if (mntinfo == NULL) { + log_debug(os, container)("Can't open /proc/self/mountinfo, %s", + os::strerror(errno)); + return NULL; + } + + char cgroupv2_mount[MAXPATHLEN+1]; + char fstype[MAXPATHLEN+1]; + bool mount_point_found = false; + while ((p = fgets(buf, MAXPATHLEN, mntinfo)) != NULL) { + char *tmp_mount_point = cgroupv2_mount; + char *tmp_fs_type = fstype; + + // mountinfo format is documented at https://www.kernel.org/doc/Documentation/filesystems/proc.txt + if (sscanf(p, "%*d %*d %*d:%*d %*s %s %*[^-]- %s cgroup2 %*s", tmp_mount_point, tmp_fs_type) == 2) { + // we likely have an early match return, be sure we have cgroup2 as fstype + if (strcmp("cgroup2", tmp_fs_type) == 0) { + mount_point_found = true; + break; + } + } + } + fclose(mntinfo); + if (!mount_point_found) { + log_trace(os, container)("Mount point for cgroupv2 not found in /proc/self/mountinfo"); + return NULL; + } + // Cgroups v2 case, we have all the info we need. + // Construct the subsystem, free resources and return + // Note: any index in cg_infos will do as the path is the same for + // all controllers. + CgroupController* unified = new CgroupV2Controller(cgroupv2_mount, cg_infos[memory_idx]._cgroup_path); + for (int i = 0; i < CG_INFO_LENGTH; i++) { + os::free(cg_infos[i]._name); + os::free(cg_infos[i]._cgroup_path); + } + log_debug(os, container)("Detected cgroups v2 unified hierarchy"); + return new CgroupV2Subsystem(unified); + } + + // What follows is cgroups v1 + log_debug(os, container)("Detected cgroups hybrid or legacy hierarchy, using cgroups v1 controllers"); /* * Find the cgroup mount point for memory and cpuset @@ -87,24 +253,25 @@ fclose(mntinfo); if (memory == NULL) { - log_debug(os, container)("Required cgroup memory subsystem not found"); + log_debug(os, container)("Required cgroup v1 memory subsystem not found"); return NULL; } if (cpuset == NULL) { - log_debug(os, container)("Required cgroup cpuset subsystem not found"); + log_debug(os, container)("Required cgroup v1 cpuset subsystem not found"); return NULL; } if (cpu == NULL) { - log_debug(os, container)("Required cgroup cpu subsystem not found"); + log_debug(os, container)("Required cgroup v1 cpu subsystem not found"); return NULL; } if (cpuacct == NULL) { - log_debug(os, container)("Required cgroup cpuacct subsystem not found"); + log_debug(os, container)("Required cgroup v1 cpuacct subsystem not found"); return NULL; } /* - * Read /proc/self/cgroup and map host mount point to + * Use info gathered previously from /proc/self/cgroup + * and map host mount point to * local one via /proc/self/mountinfo content above * * Docker example: @@ -126,42 +293,18 @@ * /sys/fs/cgroup/memory/user.slice * */ - cgroup = fopen("/proc/self/cgroup", "r"); - if (cgroup == NULL) { - log_debug(os, container)("Can't open /proc/self/cgroup, %s", - os::strerror(errno)); - return NULL; - } - - while ((p = fgets(buf, MAXPATHLEN, cgroup)) != NULL) { - char *controllers; - char *token; - char *base; - - /* Skip cgroup number */ - strsep(&p, ":"); - /* Get controllers and base */ - controllers = strsep(&p, ":"); - base = strsep(&p, "\n"); - - if (controllers == NULL) { - continue; - } - - while ((token = strsep(&controllers, ",")) != NULL) { - if (strcmp(token, "memory") == 0) { - memory->set_subsystem_path(base); - } else if (strcmp(token, "cpuset") == 0) { - cpuset->set_subsystem_path(base); - } else if (strcmp(token, "cpu") == 0) { - cpu->set_subsystem_path(base); - } else if (strcmp(token, "cpuacct") == 0) { - cpuacct->set_subsystem_path(base); - } + for (int i = 0; i < CG_INFO_LENGTH; i++) { + CgroupInfo info = cg_infos[i]; + if (strcmp(info._name, "memory") == 0) { + memory->set_subsystem_path(info._cgroup_path); + } else if (strcmp(info._name, "cpuset") == 0) { + cpuset->set_subsystem_path(info._cgroup_path); + } else if (strcmp(info._name, "cpu") == 0) { + cpu->set_subsystem_path(info._cgroup_path); + } else if (strcmp(info._name, "cpuacct") == 0) { + cpuacct->set_subsystem_path(info._cgroup_path); } } - - fclose(cgroup); return new CgroupV1Subsystem(cpuset, cpu, cpuacct, memory); } --- old/src/hotspot/os/linux/cgroupSubsystem_linux.hpp 2019-10-14 20:38:38.720931207 +0200 +++ new/src/hotspot/os/linux/cgroupSubsystem_linux.hpp 2019-10-14 20:38:38.594930944 +0200 @@ -181,6 +181,9 @@ log_trace(os, container)(logstring, variable); \ } +// Four controllers: cpu, cpuset, cpuacct, memory +#define CG_INFO_LENGTH 4 + class CgroupSubsystem: CHeapObj { friend class CgroupSubsystemFactory; public: @@ -204,4 +207,18 @@ static CgroupSubsystem* create(); }; +// Class representing info in /proc/self/cgroup. +// See man 7 cgroups +class CgroupInfo : public StackObj { + friend class CgroupSubsystemFactory; + + private: + char* _name; + int _hierarchy_id; + bool _enabled; + char* _cgroup_path; + +}; + + #endif // CGROUP_SUBSYSTEM_LINUX_HPP --- old/src/hotspot/share/prims/whitebox.cpp 2019-10-14 20:38:39.313932446 +0200 +++ new/src/hotspot/share/prims/whitebox.cpp 2019-10-14 20:38:39.188932185 +0200 @@ -2130,6 +2130,20 @@ return false; WB_END +WB_ENTRY(jobject, WB_ContainerType(JNIEnv* env, jobject o)) + jstring cg_type = NULL; +#ifdef LINUX +#ifndef PRODUCT + const char* p = OSContainer::container_type(); + ThreadToNativeFromVM ttn(thread); + cg_type = env->NewStringUTF(p); + + CHECK_JNI_EXCEPTION_(env, NULL); +#endif +#endif + return cg_type; +WB_END + WB_ENTRY(void, WB_PrintOsInfo(JNIEnv* env, jobject o)) os::print_os_info(tty); WB_END @@ -2390,6 +2404,8 @@ (void*)&WB_CheckLibSpecifiesNoexecstack}, {CC"isContainerized", CC"()Z", (void*)&WB_IsContainerized }, {CC"printOsInfo", CC"()V", (void*)&WB_PrintOsInfo }, + {CC"containerType", CC"()Ljava/lang/String;", + (void*)&WB_ContainerType }, {CC"disableElfSectionCache", CC"()V", (void*)&WB_DisableElfSectionCache }, {CC"resolvedMethodItemsCount", CC"()J", (void*)&WB_ResolvedMethodItemsCount }, {CC"protectionDomainRemovedCount", CC"()I", (void*)&WB_ProtectionDomainRemovedCount }, --- old/test/hotspot/jtreg/containers/cgroup/PlainRead.java 2019-10-14 20:38:39.919933713 +0200 +++ new/test/hotspot/jtreg/containers/cgroup/PlainRead.java 2019-10-14 20:38:39.796933456 +0200 @@ -46,7 +46,7 @@ oa.shouldNotMatch("^.*" + what + " *" + value + ".*$"); } - static final String good_value = "(\\d+|-1|Unlimited)"; + static final String good_value = "(\\d+|-1|Unlimited|max)"; static final String bad_value = "(failed)"; static final String[] variables = {"Memory Limit is:", "CPU Shares is:", "CPU Quota is:", "CPU Period is:", "active_processor_count:"}; --- old/test/hotspot/jtreg/containers/docker/PrintContainerInfo.java 2019-10-14 20:38:40.522934973 +0200 +++ new/test/hotspot/jtreg/containers/docker/PrintContainerInfo.java 2019-10-14 20:38:40.399934716 +0200 @@ -29,6 +29,11 @@ System.out.println("PrintContainerInfo: Entering"); WhiteBox wb = WhiteBox.getWhiteBox(); + String cgVersion = wb.containerType(); + if (cgVersion != null) { // will be null for product builds + System.out.println("Detected CGroups version is: " + cgVersion); + } + wb.printOsInfo(); } } --- old/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java 2019-10-14 20:38:41.117936217 +0200 +++ new/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java 2019-10-14 20:38:40.995935962 +0200 @@ -33,10 +33,12 @@ * @run driver TestCPUAwareness */ import java.util.List; +import java.util.Optional; import jdk.test.lib.containers.docker.Common; import jdk.test.lib.containers.docker.DockerRunOptions; import jdk.test.lib.containers.docker.DockerTestUtils; import jdk.test.lib.containers.cgroup.CPUSetsReader; +import jdk.test.lib.process.OutputAnalyzer; public class TestCPUAwareness { private static final String imageName = Common.imageName("cpu"); @@ -202,8 +204,28 @@ DockerRunOptions opts = Common.newOpts(imageName) .addDockerOpts("--cpu-shares=" + shares); - Common.run(opts) - .shouldMatch("CPU Shares is.*" + shares) - .shouldMatch("active_processor_count.*" + expectedAPC); + OutputAnalyzer out = Common.run(opts); + String cgroupVer = getDetectedCgroupVersion(out); + if (cgroupVer != null ) { + if ("cgroupv1".equals(cgroupVer)) { + out.shouldMatch("CPU Shares is.*" + shares); + } else if ("cgroupsv2".equals(cgroupVer)) { + out.shouldMatch("Scaled CPU Shares value is:.*"); + } + } + out.shouldMatch("active_processor_count.*" + expectedAPC); } + + private static String getDetectedCgroupVersion(OutputAnalyzer out) throws Exception { + Optional cgroupVersString = out.asLines() + .stream() + .filter( l -> l.startsWith("Detected CGroups version is:") ) + .findFirst(); + if (cgroupVersString.isPresent()) { // only non-product builds have this + return cgroupVersString.get().split(": ")[1].trim(); + } else { + return null; + } + } + } --- old/test/hotspot/jtreg/containers/docker/TestMisc.java 2019-10-14 20:38:41.715937467 +0200 +++ new/test/hotspot/jtreg/containers/docker/TestMisc.java 2019-10-14 20:38:41.590937206 +0200 @@ -34,6 +34,7 @@ * @run driver ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox sun.hotspot.WhiteBox$WhiteBoxPermission * @run driver TestMisc */ +import java.util.Optional; import jdk.test.lib.containers.docker.Common; import jdk.test.lib.containers.docker.DockerTestUtils; import jdk.test.lib.containers.docker.DockerRunOptions; @@ -89,11 +90,13 @@ DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo"); Common.addWhiteBoxOpts(opts); - checkContainerInfo(Common.run(opts)); + OutputAnalyzer out = Common.run(opts); + checkContainerInfoCommon(out); + checkContainerInfoCgroupSpecific(out); } - private static void checkContainerInfo(OutputAnalyzer out) throws Exception { + private static void checkContainerInfoCommon(OutputAnalyzer out) throws Exception { String[] expectedToContain = new String[] { "cpuset.cpus", "cpuset.mems", @@ -104,7 +107,6 @@ "Memory Limit", "Memory Soft Limit", "Memory Usage", - "Maximum Memory Usage", "memory_max_usage_in_bytes" }; @@ -113,4 +115,17 @@ } } + private static void checkContainerInfoCgroupSpecific(OutputAnalyzer out) throws Exception { + Optional cgroupVersString = out.asLines() + .stream() + .filter( l -> l.startsWith("Detected CGroups version is:") ) + .findFirst(); + if (cgroupVersString.isPresent()) { // only non-product builds have this + String cgroupVers = cgroupVersString.get().split(": ")[1].trim(); + if ("cgroupv1".equals(cgroupVers)) { + out.shouldContain("Maximum Memory Usage"); // only supported for cgroups v1 + } + } + } + } --- old/test/lib/sun/hotspot/WhiteBox.java 2019-10-14 20:38:42.331938754 +0200 +++ new/test/lib/sun/hotspot/WhiteBox.java 2019-10-14 20:38:42.190938460 +0200 @@ -539,6 +539,13 @@ // Container testing public native boolean isContainerized(); + public native String containerType(); + public boolean isCgroupsV1() { + return "cgroupv1".equals(containerType()); + } + public boolean isCgroupsV2() { + return "cgroupv2".equals(containerType()); + } public native void printOsInfo(); // Decoder --- /dev/null 2019-10-14 09:48:32.605001340 +0200 +++ new/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp 2019-10-14 20:38:42.821939779 +0200 @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2019, Red Hat Inc. + * 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 "cgroupV2Subsystem_linux.hpp" + +/* 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 CgroupV2Subsystem::cpu_shares() { + GET_CONTAINER_INFO(int, _unified, "/cpu.weight", + "CPU Shares is: %d", "%d", shares); + // Convert default value of 100 to no shares setup + if (shares == 100) return -1; + + // CPU shares (OCI) value needs to get translated into + // a proper Cgroups v2 value. See: + // https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller + // + // Use the inverse of (x == OCI value, y == cgroupsv2 value): + // ((262142 * y - 1)/9999) + 2 = x + // + int x = 262142 * shares - 1; + double frac = x/9999.0; + x = ((int)frac) + 2; + log_trace(os, container)("Scaled CPU Shares value is: %d", x); + // Since the scaled value is not precise, return the closest + // multiple of PER_CPU_SHARES for a more conservative mapping + if ( x <= PER_CPU_SHARES ) { + // will always map to 1 CPU + return x; + } + int f = x/PER_CPU_SHARES; + int lower_multiple = f * PER_CPU_SHARES; + int upper_multiple = (f + 1) * PER_CPU_SHARES; + int distance_lower = MAX2(lower_multiple, x) - MIN2(lower_multiple, x); + int distance_upper = MAX2(upper_multiple, x) - MIN2(upper_multiple, x); + x = distance_lower <= distance_upper ? lower_multiple : upper_multiple; + log_trace(os, container)("Closest multiple of %d of the CPU Shares value is: %d", PER_CPU_SHARES, x); + return x; +} + +/* cpu_quota + * + * Return the number of milliseconds per period + * process is guaranteed to run. + * + * return: + * quota time in milliseconds + * -1 for no quota + * OSCONTAINER_ERROR for not supported + */ +int CgroupV2Subsystem::cpu_quota() { + char * cpu_quota_str = cpu_quota_val(); + return (int)limit_from_str(cpu_quota_str); +} + +char * CgroupV2Subsystem::cpu_cpuset_cpus() { + GET_CONTAINER_INFO_CPTR(cptr, _unified, "/cpuset.cpus", + "cpuset.cpus is: %s", "%1023s", cpus, 1024); + if (cpus == NULL) { + return NULL; + } + return os::strdup(cpus); +} + +char* CgroupV2Subsystem::cpu_quota_val() { + GET_CONTAINER_INFO_CPTR(cptr, _unified, "/cpu.max", + "CPU Quota is: %s", "%s %*d", quota, 1024); + if (quota == NULL) { + return NULL; + } + return os::strdup(quota); +} + +char * CgroupV2Subsystem::cpu_cpuset_memory_nodes() { + GET_CONTAINER_INFO_CPTR(cptr, _unified, "/cpuset.mems", + "cpuset.mems is: %s", "%1023s", mems, 1024); + if (mems == NULL) { + return NULL; + } + return os::strdup(mems); +} + +int CgroupV2Subsystem::cpu_period() { + GET_CONTAINER_INFO(int, _unified, "/cpu.max", + "CPU Period is: %d", "%*s %d", period); + return period; +} + +/* memory_usage_in_bytes + * + * Return the amount of used memory used by this cgroup and decendents + * + * return: + * memory usage in bytes or + * -1 for unlimited + * OSCONTAINER_ERROR for not supported + */ +jlong CgroupV2Subsystem::memory_usage_in_bytes() { + GET_CONTAINER_INFO(jlong, _unified, "/memory.current", + "Memory Usage is: " JLONG_FORMAT, JLONG_FORMAT, memusage); + return memusage; +} + +jlong CgroupV2Subsystem::memory_soft_limit_in_bytes() { + char* mem_soft_limit_str = mem_soft_limit_val(); + return limit_from_str(mem_soft_limit_str); +} + +jlong CgroupV2Subsystem::memory_max_usage_in_bytes() { + return OSCONTAINER_ERROR; // Not supported for Cgroups V2. +} + +char* CgroupV2Subsystem::mem_soft_limit_val() { + GET_CONTAINER_INFO_CPTR(cptr, _unified, "/memory.high", + "Memory Soft Limit is: %s", "%s", mem_soft_limit_str, 1024); + if (mem_soft_limit_str == NULL) { + return NULL; + } + return os::strdup(mem_soft_limit_str); +} + +jlong CgroupV2Subsystem::memory_and_swap_limit_in_bytes() { + char* mem_swp_limit_str = mem_swp_limit_val(); + return limit_from_str(mem_swp_limit_str); +} + +char* CgroupV2Subsystem::mem_swp_limit_val() { + GET_CONTAINER_INFO_CPTR(cptr, _unified, "/memory.swap.max", + "Memory and Swap Limit is: %s", "%s", mem_swp_limit_str, 1024); + if (mem_swp_limit_str == NULL) { + return NULL; + } + return os::strdup(mem_swp_limit_str); +} + +/* memory_limit_in_bytes + * + * Return the limit of available memory for this process. + * + * return: + * memory limit in bytes or + * -1 for unlimited, OSCONTAINER_ERROR for an error + */ +jlong CgroupV2Subsystem::memory_limit_in_bytes() { + char * mem_limit_str = mem_limit_val(); + return limit_from_str(mem_limit_str); +} + +jlong CgroupV2Subsystem::limit_from_str(char* limit_str) { + if (limit_str == NULL) { + return OSCONTAINER_ERROR; + } + // Unlimited memory in Cgroups V2 is the literal string 'max' + if (strcmp("max", limit_str) == 0) { + os::free(limit_str); + return (jlong)-1; + } + julong limit; + if (sscanf(limit_str, JULONG_FORMAT, &limit) != 1) { + os::free(limit_str); + return OSCONTAINER_ERROR; + } + os::free(limit_str); + return (jlong)limit; +} + +char* CgroupV2Subsystem::mem_limit_val() { + GET_CONTAINER_INFO_CPTR(cptr, _unified, "/memory.max", + "Memory Limit is: %s", "%s", mem_limit_str, 1024); + if (mem_limit_str == NULL) { + return NULL; + } + return os::strdup(mem_limit_str); +} + +char* CgroupV2Controller::construct_path(char* mount_path, char *cgroup_path) { + char buf[MAXPATHLEN+1]; + int buflen; + strncpy(buf, mount_path, MAXPATHLEN); + buf[MAXPATHLEN] = '\0'; + buflen = strlen(buf); + if ((buflen + strlen(cgroup_path)) > MAXPATHLEN) { + return NULL; + } + strncat(buf, cgroup_path, MAXPATHLEN-buflen); + buf[MAXPATHLEN] = '\0'; + return os::strdup(buf); +} + --- /dev/null 2019-10-14 09:48:32.605001340 +0200 +++ new/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp 2019-10-14 20:38:43.440941072 +0200 @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019, Red Hat Inc. + * 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. + * + */ + +#ifndef CGROUP_V2_SUBSYSTEM_LINUX_HPP +#define CGROUP_V2_SUBSYSTEM_LINUX_HPP + +#include "cgroupSubsystem_linux.hpp" + +class CgroupV2Controller: public CgroupController { + friend class CgroupSubsystemFactory; + private: + /* the mount path of the cgroup v2 hierarchy */ + char *_mount_path; + /* The cgroup path for the controller */ + char *_cgroup_path; + + /* Constructed full path to the subsystem directory */ + char *_path; + static char* construct_path(char* mount_path, char *cgroup_path); + + public: + CgroupV2Controller(char * mount_path, char *cgroup_path) { + _mount_path = mount_path; + _cgroup_path = os::strdup(cgroup_path); + _path = construct_path(mount_path, cgroup_path); + } + + char *subsystem_path() { return _path; } +}; + +class CgroupV2Subsystem: CgroupSubsystem { + friend class CgroupSubsystemFactory; + private: + /* One unified controller */ + CgroupController* _unified = NULL; + + CgroupV2Subsystem(CgroupController* unified) { + _unified = unified; + } + + char *mem_limit_val(); + char *mem_swp_limit_val(); + char *mem_soft_limit_val(); + char *cpu_quota_val(); + jlong limit_from_str(char* limit_str); + + public: + jlong memory_limit_in_bytes(); + int cpu_quota(); + int cpu_period(); + int cpu_shares(); + jlong memory_and_swap_limit_in_bytes(); + jlong memory_soft_limit_in_bytes(); + jlong memory_usage_in_bytes(); + jlong memory_max_usage_in_bytes(); + char * cpu_cpuset_cpus(); + char * cpu_cpuset_memory_nodes(); + const char * container_type() { + return "cgroupv2"; + } +}; + +#endif // CGROUP_V2_SUBSYSTEM_LINUX_HPP