diff --git a/make/data/hotspot-symbols/symbols-linux b/make/data/hotspot-symbols/symbols-linux index d1f258297d8..82fdc5cc4db 100644 --- a/make/data/hotspot-symbols/symbols-linux +++ b/make/data/hotspot-symbols/symbols-linux @@ -22,6 +22,7 @@ # JVM_handle_linux_signal +JVM_IsContainerized JVM_IsUseContainerSupport numa_error numa_warn diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index 2a2d83ed748..e753f6d0b28 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp @@ -28,6 +28,7 @@ #include "cgroupSubsystem_linux.hpp" #include "cgroupV1Subsystem_linux.hpp" #include "cgroupV2Subsystem_linux.hpp" +#include "cgroupUtil_linux.hpp" #include "logging/log.hpp" #include "memory/allocation.hpp" #include "os_linux.hpp" @@ -41,7 +42,7 @@ static const char* cg_controller_name[] = { "cpuset", "cpu", "cpuacct", "memory" CgroupSubsystem* CgroupSubsystemFactory::create() { CgroupV1MemoryController* memory = nullptr; CgroupV1Controller* cpuset = nullptr; - CgroupV1Controller* cpu = nullptr; + CgroupV1CpuController* cpu = nullptr; CgroupV1Controller* cpuacct = nullptr; CgroupV1Controller* pids = nullptr; CgroupInfo cg_infos[CG_INFO_LENGTH]; @@ -61,12 +62,18 @@ CgroupSubsystem* CgroupSubsystemFactory::create() { if (is_cgroup_v2(&cg_type_flags)) { // 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(cg_infos[MEMORY_IDX]._mount_path, cg_infos[MEMORY_IDX]._cgroup_path); + // Note: We use the memory for non-cpu non-memory controller look-ups. + // Perhaps we ought to have separate controllers for all. + CgroupV2Controller mem_other = CgroupV2Controller(cg_infos[MEMORY_IDX]._mount_path, + cg_infos[MEMORY_IDX]._cgroup_path, + cg_infos[MEMORY_IDX]._read_only); + CgroupV2MemoryController* memory = new CgroupV2MemoryController(mem_other); + CgroupV2CpuController* cpu = new CgroupV2CpuController(CgroupV2Controller(cg_infos[CPU_IDX]._mount_path, + cg_infos[CPU_IDX]._cgroup_path, + cg_infos[CPU_IDX]._read_only)); log_debug(os, container)("Detected cgroups v2 unified hierarchy"); cleanup(cg_infos); - return new CgroupV2Subsystem(unified); + return new CgroupV2Subsystem(memory, cpu, mem_other); } /* @@ -100,19 +107,19 @@ CgroupSubsystem* CgroupSubsystemFactory::create() { CgroupInfo info = cg_infos[i]; if (info._data_complete) { // pids controller might have incomplete data if (strcmp(info._name, "memory") == 0) { - memory = new CgroupV1MemoryController(info._root_mount_path, info._mount_path); + memory = new CgroupV1MemoryController(CgroupV1Controller(info._root_mount_path, info._mount_path, info._read_only)); memory->set_subsystem_path(info._cgroup_path); } else if (strcmp(info._name, "cpuset") == 0) { - cpuset = new CgroupV1Controller(info._root_mount_path, info._mount_path); + cpuset = new CgroupV1Controller(info._root_mount_path, info._mount_path, info._read_only); cpuset->set_subsystem_path(info._cgroup_path); } else if (strcmp(info._name, "cpu") == 0) { - cpu = new CgroupV1Controller(info._root_mount_path, info._mount_path); + cpu = new CgroupV1CpuController(CgroupV1Controller(info._root_mount_path, info._mount_path, info._read_only)); cpu->set_subsystem_path(info._cgroup_path); } else if (strcmp(info._name, "cpuacct") == 0) { - cpuacct = new CgroupV1Controller(info._root_mount_path, info._mount_path); + cpuacct = new CgroupV1Controller(info._root_mount_path, info._mount_path, info._read_only); cpuacct->set_subsystem_path(info._cgroup_path); } else if (strcmp(info._name, "pids") == 0) { - pids = new CgroupV1Controller(info._root_mount_path, info._mount_path); + pids = new CgroupV1Controller(info._root_mount_path, info._mount_path, info._read_only); pids->set_subsystem_path(info._cgroup_path); } } else { @@ -127,7 +134,8 @@ void CgroupSubsystemFactory::set_controller_paths(CgroupInfo* cg_infos, int controller, const char* name, char* mount_path, - char* root_path) { + char* root_path, + bool read_only) { if (cg_infos[controller]._mount_path != nullptr) { // On some systems duplicate controllers get mounted in addition to // the main cgroup controllers most likely under /sys/fs/cgroup. In that @@ -139,6 +147,7 @@ void CgroupSubsystemFactory::set_controller_paths(CgroupInfo* cg_infos, os::free(cg_infos[controller]._root_mount_path); cg_infos[controller]._mount_path = os::strdup(mount_path); cg_infos[controller]._root_mount_path = os::strdup(root_path); + cg_infos[controller]._read_only = read_only; } else { log_debug(os, container)("Duplicate %s controllers detected. Picking %s, skipping %s.", name, cg_infos[controller]._mount_path, mount_path); @@ -146,9 +155,66 @@ void CgroupSubsystemFactory::set_controller_paths(CgroupInfo* cg_infos, } else { cg_infos[controller]._mount_path = os::strdup(mount_path); cg_infos[controller]._root_mount_path = os::strdup(root_path); + cg_infos[controller]._read_only = read_only; } } +/* + * Determine whether or not the mount options, which are comma separated, + * contain the 'ro' string. + */ +static bool find_ro_opt(char* mount_opts) { + char* token; + char* mo_ptr = mount_opts; + // mount options are comma-separated (man proc). + while ((token = strsep(&mo_ptr, ",")) != NULL) { + if (strcmp(token, "ro") == 0) { + return true; + } + } + return false; +} + +/* + * Read values of a /proc/self/mountinfo line into variables. For cgroups v1 + * super options are needed. On cgroups v2 super options are not used. + * + * The scanning of a single mountinfo line entry is as follows: + * + * 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + * (1) (2) (3):(4) (5) (6) (7) (8) (9) (10) (11) (12) + * + * The numbers in parentheses are labels for the descriptions below: + * + * (1) mount ID: matched with '%*d' and discarded + * (2) parent ID: matched with '%*d' and discarded + * (3) major: ---,---> major, minor separated by ':'. matched with '%*d:%*d' and discarded + * (4) minor: ---' + * (5) root: matched with '%s' and captured in 'tmproot'. Must be non-empty. + * (6) mount point: matched with '%s' and captured in 'tmpmount'. Must be non-empty. + * (7) mount options: matched with '%s' and captured in 'mount_opts'. Must be non-empty. + * (8) optional fields: ---,---> matched with '%*[^-]-'. Anything not a hyphen, followed by a hyphen + * (9) separator: ---' and discarded. Note: The discarded match is space characters if there + * are no optionals. Otherwise it includes the optional fields as well. + * (10) filesystem type: matched with '%s' and captured in 'tmp_fs_type' + * (11) mount source: matched with '%*s' and discarded + * (12) super options: matched with '%s' and captured in 'tmpcgroups' + */ +static inline bool match_mount_info_line(char* line, + char* tmproot, + char* tmpmount, + char* mount_opts, + char* tmp_fs_type, + char* tmpcgroups) { + return sscanf(line, + "%*d %*d %*d:%*d %s %s %s%*[^-]- %s %*s %s", + tmproot, + tmpmount, + mount_opts, + tmp_fs_type, + tmpcgroups) == 5; +} + bool CgroupSubsystemFactory::determine_type(CgroupInfo* cg_infos, const char* proc_cgroups, const char* proc_self_cgroup, @@ -320,26 +386,40 @@ bool CgroupSubsystemFactory::determine_type(CgroupInfo* cg_infos, char tmproot[MAXPATHLEN+1]; char tmpmount[MAXPATHLEN+1]; char tmpcgroups[MAXPATHLEN+1]; + char mount_opts[MAXPATHLEN+1]; char *cptr = tmpcgroups; char *token; - // Cgroup v2 relevant info. We only look for the _mount_path iff is_cgroupsV2 so - // as to avoid memory stomping of the _mount_path pointer later on in the cgroup v1 - // block in the hybrid case. - if (is_cgroupsV2 && sscanf(p, "%*d %*d %*d:%*d %s %s %*[^-]- %s %*s %*s", tmproot, tmpmount, tmp_fs_type) == 3) { + /* Cgroup v2 relevant info. We only look for the _mount_path iff is_cgroupsV2 so + * as to avoid memory stomping of the _mount_path pointer later on in the cgroup v1 + * block in the hybrid case. + * + * We collect the read only mount option in the cgroup infos so as to have that + * info ready when determining is_containerized(). + */ + if (is_cgroupsV2 && match_mount_info_line(p, + tmproot, + tmpmount, + mount_opts, + tmp_fs_type, + tmpcgroups /* unused */)) { // we likely have an early match return (e.g. cgroup fs match), be sure we have cgroup2 as fstype if (strcmp("cgroup2", tmp_fs_type) == 0) { cgroupv2_mount_point_found = true; any_cgroup_mounts_found = true; + // For unified we only have a single line with cgroup2 fs type. + // Therefore use that option for all CG info structs. + bool ro_option = find_ro_opt(mount_opts); for (int i = 0; i < CG_INFO_LENGTH; i++) { - set_controller_paths(cg_infos, i, "(cg2, unified)", tmpmount, tmproot); + set_controller_paths(cg_infos, i, "(cg2, unified)", tmpmount, tmproot, ro_option); } } } /* Cgroup v1 relevant info * - * Find the cgroup mount point for memory, cpuset, cpu, cpuacct, pids + * Find the cgroup mount point for memory, cpuset, cpu, cpuacct, pids. For each controller + * determine whether or not they show up as mounted read only or not. * * Example for docker: * 219 214 0:29 /docker/7208cebd00fa5f2e342b1094f7bed87fa25661471a4637118e65f1c995be8a34 /sys/fs/cgroup/memory ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory @@ -348,8 +428,9 @@ bool CgroupSubsystemFactory::determine_type(CgroupInfo* cg_infos, * 34 28 0:29 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,memory * * 44 31 0:39 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,pids + * */ - if (sscanf(p, "%*d %*d %*d:%*d %s %s %*[^-]- %s %*s %s", tmproot, tmpmount, tmp_fs_type, tmpcgroups) == 4) { + if (match_mount_info_line(p, tmproot, tmpmount, mount_opts, tmp_fs_type, tmpcgroups)) { if (strcmp("cgroup", tmp_fs_type) != 0) { // Skip cgroup2 fs lines on hybrid or unified hierarchy. continue; @@ -357,23 +438,28 @@ bool CgroupSubsystemFactory::determine_type(CgroupInfo* cg_infos, while ((token = strsep(&cptr, ",")) != nullptr) { if (strcmp(token, "memory") == 0) { any_cgroup_mounts_found = true; - set_controller_paths(cg_infos, MEMORY_IDX, token, tmpmount, tmproot); + bool ro_option = find_ro_opt(mount_opts); + set_controller_paths(cg_infos, MEMORY_IDX, token, tmpmount, tmproot, ro_option); cg_infos[MEMORY_IDX]._data_complete = true; } else if (strcmp(token, "cpuset") == 0) { any_cgroup_mounts_found = true; - set_controller_paths(cg_infos, CPUSET_IDX, token, tmpmount, tmproot); + bool ro_option = find_ro_opt(mount_opts); + set_controller_paths(cg_infos, CPUSET_IDX, token, tmpmount, tmproot, ro_option); cg_infos[CPUSET_IDX]._data_complete = true; } else if (strcmp(token, "cpu") == 0) { any_cgroup_mounts_found = true; - set_controller_paths(cg_infos, CPU_IDX, token, tmpmount, tmproot); + bool ro_option = find_ro_opt(mount_opts); + set_controller_paths(cg_infos, CPU_IDX, token, tmpmount, tmproot, ro_option); cg_infos[CPU_IDX]._data_complete = true; } else if (strcmp(token, "cpuacct") == 0) { any_cgroup_mounts_found = true; - set_controller_paths(cg_infos, CPUACCT_IDX, token, tmpmount, tmproot); + bool ro_option = find_ro_opt(mount_opts); + set_controller_paths(cg_infos, CPUACCT_IDX, token, tmpmount, tmproot, ro_option); cg_infos[CPUACCT_IDX]._data_complete = true; } else if (strcmp(token, "pids") == 0) { any_cgroup_mounts_found = true; - set_controller_paths(cg_infos, PIDS_IDX, token, tmpmount, tmproot); + bool ro_option = find_ro_opt(mount_opts); + set_controller_paths(cg_infos, PIDS_IDX, token, tmpmount, tmproot, ro_option); cg_infos[PIDS_IDX]._data_complete = true; } } @@ -477,13 +563,13 @@ void CgroupSubsystemFactory::cleanup(CgroupInfo* cg_infos) { */ int CgroupSubsystem::active_processor_count() { int quota_count = 0; - int cpu_count, limit_count; + int cpu_count; int result; // We use a cache with a timeout to avoid performing expensive // computations in the event this function is called frequently. // [See 8227006]. - CachingCgroupController* contrl = cpu_controller(); + CachingCgroupController* contrl = cpu_controller(); CachedMetric* cpu_limit = contrl->metrics_cache(); if (!cpu_limit->should_check_metric()) { int val = (int)cpu_limit->value(); @@ -491,23 +577,8 @@ int CgroupSubsystem::active_processor_count() { return val; } - cpu_count = limit_count = os::Linux::active_processor_count(); - int quota = cpu_quota(); - int period = cpu_period(); - - if (quota > -1 && period > 0) { - quota_count = ceilf((float)quota / (float)period); - log_trace(os, container)("CPU Quota count based on quota/period: %d", quota_count); - } - - // Use quotas - if (quota_count != 0) { - limit_count = quota_count; - } - - result = MIN2(cpu_count, limit_count); - log_trace(os, container)("OSContainer::active_processor_count: %d", result); - + cpu_count = os::Linux::active_processor_count(); + result = CgroupUtil::processor_count(contrl->controller(), cpu_count); // Update cached metric to avoid re-reading container settings too often cpu_limit->set_value(result, OSCONTAINER_CACHE_TIMEOUT); @@ -524,55 +595,213 @@ int CgroupSubsystem::active_processor_count() { * OSCONTAINER_ERROR for not supported */ jlong CgroupSubsystem::memory_limit_in_bytes() { - CachingCgroupController* contrl = memory_controller(); + CachingCgroupController* contrl = memory_controller(); CachedMetric* memory_limit = contrl->metrics_cache(); if (!memory_limit->should_check_metric()) { return memory_limit->value(); } jlong phys_mem = os::Linux::physical_memory(); log_trace(os, container)("total physical memory: " JLONG_FORMAT, phys_mem); - jlong mem_limit = read_memory_limit_in_bytes(); - - if (mem_limit <= 0 || mem_limit >= phys_mem) { - jlong read_mem_limit = mem_limit; - const char *reason; - if (mem_limit >= phys_mem) { - // Exceeding physical memory is treated as unlimited. Cg v1's implementation - // of read_memory_limit_in_bytes() caps this at phys_mem since Cg v1 has no - // value to represent 'max'. Cg v2 may return a value >= phys_mem if e.g. the - // container engine was started with a memory flag exceeding it. - reason = "ignored"; - mem_limit = -1; - } else if (OSCONTAINER_ERROR == mem_limit) { - reason = "failed"; - } else { - assert(mem_limit == -1, "Expected unlimited"); - reason = "unlimited"; - } - log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value " JLONG_FORMAT, - reason, read_mem_limit, phys_mem); - } - + jlong mem_limit = contrl->controller()->read_memory_limit_in_bytes(phys_mem); // Update cached metric to avoid re-reading container settings too often memory_limit->set_value(mem_limit, OSCONTAINER_CACHE_TIMEOUT); return mem_limit; } -jlong CgroupSubsystem::limit_from_str(char* limit_str) { +bool CgroupController::read_string(const char* filename, char* buf, size_t buf_size) { + assert(buf != nullptr, "buffer must not be null"); + assert(filename != nullptr, "filename must be given"); + const char* s_path = subsystem_path(); + if (s_path == nullptr) { + log_debug(os, container)("read_string: subsystem path is null"); + return false; + } + + stringStream file_path; + file_path.print_raw(s_path); + file_path.print_raw(filename); + + if (file_path.size() > MAXPATHLEN) { + log_debug(os, container)("File path too long %s, %s", file_path.base(), filename); + return false; + } + const char* absolute_path = file_path.freeze(); + log_trace(os, container)("Path to %s is %s", filename, absolute_path); + + FILE* fp = os::fopen(absolute_path, "r"); + if (fp == nullptr) { + log_debug(os, container)("Open of file %s failed, %s", absolute_path, os::strerror(errno)); + return false; + } + + // Read a single line into the provided buffer. + // At most buf_size - 1 characters. + char* line = fgets(buf, buf_size, fp); + fclose(fp); + if (line == nullptr) { + log_debug(os, container)("Empty file %s", absolute_path); + return false; + } + size_t len = strlen(line); + assert(len <= buf_size - 1, "At most buf_size - 1 bytes can be read"); + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; // trim trailing new line + } + return true; +} + +bool CgroupController::read_number(const char* filename, julong* result) { + char buf[1024]; + bool is_ok = read_string(filename, buf, 1024); + if (!is_ok) { + return false; + } + int matched = sscanf(buf, JULONG_FORMAT, result); + if (matched == 1) { + return true; + } + return false; +} + +bool CgroupController::read_number_handle_max(const char* filename, jlong* result) { + char buf[1024]; + bool is_ok = read_string(filename, buf, 1024); + if (!is_ok) { + return false; + } + jlong val = limit_from_str(buf); + if (val == OSCONTAINER_ERROR) { + return false; + } + *result = val; + return true; +} + +bool CgroupController::read_numerical_key_value(const char* filename, const char* key, julong* result) { + assert(key != nullptr, "key must be given"); + assert(result != nullptr, "result pointer must not be null"); + assert(filename != nullptr, "file to search in must be given"); + const char* s_path = subsystem_path(); + if (s_path == nullptr) { + log_debug(os, container)("read_numerical_key_value: subsystem path is null"); + return false; + } + + stringStream file_path; + file_path.print_raw(s_path); + file_path.print_raw(filename); + + if (file_path.size() > MAXPATHLEN) { + log_debug(os, container)("File path too long %s, %s", file_path.base(), filename); + return false; + } + const char* absolute_path = file_path.freeze(); + log_trace(os, container)("Path to %s is %s", filename, absolute_path); + FILE* fp = os::fopen(absolute_path, "r"); + if (fp == nullptr) { + log_debug(os, container)("Open of file %s failed, %s", absolute_path, os::strerror(errno)); + return false; + } + + const int buf_len = MAXPATHLEN+1; + char buf[buf_len]; + char* line = fgets(buf, buf_len, fp); + bool found_match = false; + // File consists of multiple lines in a "key value" + // fashion, we have to find the key. + const size_t key_len = strlen(key); + for (; line != nullptr; line = fgets(buf, buf_len, fp)) { + char after_key = line[key_len]; + if (strncmp(line, key, key_len) == 0 + && isspace(after_key) != 0 + && after_key != '\n') { + // Skip key, skip space + const char* value_substr = line + key_len + 1; + int matched = sscanf(value_substr, JULONG_FORMAT, result); + found_match = matched == 1; + if (found_match) { + break; + } + } + } + fclose(fp); + if (found_match) { + return true; + } + log_debug(os, container)("Type %s (key == %s) not found in file %s", JULONG_FORMAT, + key, absolute_path); + return false; +} + +bool CgroupController::read_numerical_tuple_value(const char* filename, bool use_first, jlong* result) { + char buf[1024]; + bool is_ok = read_string(filename, buf, 1024); + if (!is_ok) { + return false; + } + char token[1024]; + const int matched = sscanf(buf, (use_first ? "%1023s %*s" : "%*s %1023s"), token); + if (matched != 1) { + return false; + } + jlong val = limit_from_str(token); + if (val == OSCONTAINER_ERROR) { + return false; + } + *result = val; + return true; +} + +jlong CgroupController::limit_from_str(char* limit_str) { if (limit_str == nullptr) { return OSCONTAINER_ERROR; } // Unlimited memory in cgroups is the literal string 'max' for // some controllers, for example the pids controller. 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; } + +// CgroupSubsystem implementations + +jlong CgroupSubsystem::memory_and_swap_limit_in_bytes() { + julong phys_mem = os::Linux::physical_memory(); + julong host_swap = os::Linux::host_swap(); + return memory_controller()->controller()->memory_and_swap_limit_in_bytes(phys_mem, host_swap); +} + +jlong CgroupSubsystem::memory_soft_limit_in_bytes() { + julong phys_mem = os::Linux::physical_memory(); + return memory_controller()->controller()->memory_soft_limit_in_bytes(phys_mem); +} + +jlong CgroupSubsystem::memory_usage_in_bytes() { + return memory_controller()->controller()->memory_usage_in_bytes(); +} + +jlong CgroupSubsystem::memory_max_usage_in_bytes() { + return memory_controller()->controller()->memory_max_usage_in_bytes(); +} + +int CgroupSubsystem::cpu_quota() { + return cpu_controller()->controller()->cpu_quota(); +} + +int CgroupSubsystem::cpu_period() { + return cpu_controller()->controller()->cpu_period(); +} + +int CgroupSubsystem::cpu_shares() { + return cpu_controller()->controller()->cpu_shares(); +} + +void CgroupSubsystem::print_version_specific_info(outputStream* st) { + julong phys_mem = os::Linux::physical_memory(); + memory_controller()->controller()->print_version_specific_info(st, phys_mem); +} diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp index 6c5470445f1..b4d325e97ec 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp @@ -69,148 +69,98 @@ #define MEMORY_IDX 3 #define PIDS_IDX 4 -typedef char * cptr; - -class CgroupController: public CHeapObj { - public: - virtual char *subsystem_path() = 0; -}; - -PRAGMA_DIAG_PUSH -PRAGMA_FORMAT_NONLITERAL_IGNORED -// Parses a subsystem's file, looking for a matching line. -// If key is null, then the first line will be matched with scan_fmt. -// If key isn't null, then each line will be matched, looking for something that matches "$key $scan_fmt". -// The matching value will be assigned to returnval. -// scan_fmt uses scanf() syntax. -// Return value: 0 on match, OSCONTAINER_ERROR on error. -template int subsystem_file_line_contents(CgroupController* c, - const char *filename, - const char *key, - const char *scan_fmt, - T returnval) { - if (c == nullptr) { - log_debug(os, container)("subsystem_file_line_contents: CgroupController* is null"); - return OSCONTAINER_ERROR; - } - if (c->subsystem_path() == nullptr) { - log_debug(os, container)("subsystem_file_line_contents: subsystem path is null"); - return OSCONTAINER_ERROR; - } - - stringStream file_path; - file_path.print_raw(c->subsystem_path()); - file_path.print_raw(filename); - - if (file_path.size() > (MAXPATHLEN-1)) { - log_debug(os, container)("File path too long %s, %s", file_path.base(), filename); - return OSCONTAINER_ERROR; - } - const char* absolute_path = file_path.freeze(); - log_trace(os, container)("Path to %s is %s", filename, absolute_path); - - FILE* fp = os::fopen(absolute_path, "r"); - if (fp == nullptr) { - log_debug(os, container)("Open of file %s failed, %s", absolute_path, os::strerror(errno)); - return OSCONTAINER_ERROR; - } - - const int buf_len = MAXPATHLEN+1; - char buf[buf_len]; - char* line = fgets(buf, buf_len, fp); - if (line == nullptr) { - log_debug(os, container)("Empty file %s", absolute_path); - fclose(fp); - return OSCONTAINER_ERROR; - } - - bool found_match = false; - if (key == nullptr) { - // File consists of a single line according to caller, with only a value - int matched = sscanf(line, scan_fmt, returnval); - found_match = matched == 1; - } else { - // File consists of multiple lines in a "key value" - // fashion, we have to find the key. - const int key_len = (int)strlen(key); - for (; line != nullptr; line = fgets(buf, buf_len, fp)) { - char* key_substr = strstr(line, key); - char after_key = line[key_len]; - if (key_substr == line - && isspace(after_key) != 0 - && after_key != '\n') { - // Skip key, skip space - const char* value_substr = line + key_len + 1; - int matched = sscanf(value_substr, scan_fmt, returnval); - found_match = matched == 1; - if (found_match) { - break; - } - } - } - } - fclose(fp); - if (found_match) { - return 0; - } - log_debug(os, container)("Type %s (key == %s) not found in file %s", scan_fmt, - (key == nullptr ? "null" : key), absolute_path); - return OSCONTAINER_ERROR; -} -PRAGMA_DIAG_POP - -// log_fmt can be different than scan_fmt. For example -// cpu_period() for cgv2 uses log_fmt='%d' and scan_fmt='%*s %d' -#define GET_CONTAINER_INFO(return_type, subsystem, filename, \ - logstring, log_fmt, scan_fmt, variable) \ - return_type variable; \ -{ \ - int err; \ - err = subsystem_file_line_contents(subsystem, \ - filename, \ - nullptr, \ - scan_fmt, \ - &variable); \ - if (err != 0) { \ - log_trace(os, container)(logstring "%d", OSCONTAINER_ERROR); \ - return (return_type) OSCONTAINER_ERROR; \ - } \ - \ - log_trace(os, container)(logstring log_fmt, variable); \ +#define CONTAINER_READ_NUMBER_CHECKED(controller, filename, log_string, retval) \ +{ \ + bool is_ok; \ + is_ok = controller->read_number(filename, &retval); \ + if (!is_ok) { \ + log_trace(os, container)(log_string " failed: %d", OSCONTAINER_ERROR); \ + return OSCONTAINER_ERROR; \ + } \ + log_trace(os, container)(log_string " is: " JULONG_FORMAT, retval); \ } -#define GET_CONTAINER_INFO_CPTR(return_type, subsystem, filename, \ - logstring, scan_fmt, variable, bufsize) \ - char variable[bufsize]; \ -{ \ - int err; \ - err = subsystem_file_line_contents(subsystem, \ - filename, \ - nullptr, \ - scan_fmt, \ - variable); \ - if (err != 0) \ - return (return_type) nullptr; \ - \ - log_trace(os, container)(logstring, variable); \ +#define CONTAINER_READ_NUMBER_CHECKED_MAX(controller, filename, log_string, retval) \ +{ \ + bool is_ok; \ + is_ok = controller->read_number_handle_max(filename, &retval); \ + if (!is_ok) { \ + log_trace(os, container)(log_string " failed: %d", OSCONTAINER_ERROR); \ + return OSCONTAINER_ERROR; \ + } \ + log_trace(os, container)(log_string " is: " JLONG_FORMAT, retval); \ } -#define GET_CONTAINER_INFO_LINE(return_type, controller, filename, \ - matchline, logstring, scan_fmt, variable) \ - return_type variable; \ -{ \ - int err; \ - err = subsystem_file_line_contents(controller, \ - filename, \ - matchline, \ - scan_fmt, \ - &variable); \ - if (err != 0) \ - return (return_type) OSCONTAINER_ERROR; \ - \ - log_trace(os, container)(logstring, variable); \ +#define CONTAINER_READ_STRING_CHECKED(controller, filename, log_string, retval, buf_size) \ +{ \ + bool is_ok; \ + is_ok = controller->read_string(filename, retval, buf_size); \ + if (!is_ok) { \ + log_trace(os, container)(log_string " failed: %d", OSCONTAINER_ERROR); \ + return nullptr; \ + } \ + log_trace(os, container)(log_string " is: %s", retval); \ } +class CgroupController: public CHeapObj { + protected: + char* _cgroup_path; + char* _mount_point; + public: + virtual const char* subsystem_path() = 0; + virtual bool is_read_only() = 0; + const char* cgroup_path() { return _cgroup_path; } + const char* mount_point() { return _mount_point; } + virtual bool needs_hierarchy_adjustment() { return false; } + + /* Read a numerical value as unsigned long + * + * returns: false if any error occurred. true otherwise and + * the parsed value is set in the provided julong pointer. + */ + bool read_number(const char* filename, julong* result); + + /* Convenience method to deal with numbers as well as the string 'max' + * in interface files. Otherwise same as read_number(). + * + * returns: false if any error occurred. true otherwise and + * the parsed value (which might be negative) is being set in + * the provided jlong pointer. + */ + bool read_number_handle_max(const char* filename, jlong* result); + + /* Read a string of at most buf_size - 1 characters from the interface file. + * The provided buffer must be at least buf_size in size so as to account + * for the null terminating character. Callers must ensure that the buffer + * is appropriately in-scope and of sufficient size. + * + * returns: false if any error occured. true otherwise and the passed + * in buffer will contain the first buf_size - 1 characters of the string + * or up to the first new line character ('\n') whichever comes first. + */ + bool read_string(const char* filename, char* buf, size_t buf_size); + + /* Read a tuple value as a number. Tuple is: ' '. + * Handles 'max' (for unlimited) for any tuple value. This is handy for + * parsing interface files like cpu.max which contain such tuples. + * + * returns: false if any error occurred. true otherwise and the parsed + * value of the appropriate tuple entry set in the provided jlong pointer. + */ + bool read_numerical_tuple_value(const char* filename, bool use_first, jlong* result); + + /* Read a numerical value from a multi-line interface file. The matched line is + * determined by the provided 'key'. The associated numerical value is being set + * via the passed in julong pointer. Example interface file 'memory.stat' + * + * returns: false if any error occurred. true otherwise and the parsed value is + * being set in the provided julong pointer. + */ + bool read_numerical_key_value(const char* filename, const char* key, julong* result); + + private: + static jlong limit_from_str(char* limit_str); +}; class CachedMetric : public CHeapObj{ private: @@ -236,45 +186,77 @@ class CachedMetric : public CHeapObj{ } }; +template class CachingCgroupController : public CHeapObj { private: - CgroupController* _controller; + T* _controller; CachedMetric* _metrics_cache; public: - CachingCgroupController(CgroupController* cont) { + CachingCgroupController(T* cont) { _controller = cont; _metrics_cache = new CachedMetric(); } CachedMetric* metrics_cache() { return _metrics_cache; } - CgroupController* controller() { return _controller; } + T* controller() { return _controller; } +}; + +// Pure virtual class representing version agnostic CPU controllers +class CgroupCpuController: public CHeapObj { + public: + virtual int cpu_quota() = 0; + virtual int cpu_period() = 0; + virtual int cpu_shares() = 0; + virtual bool needs_hierarchy_adjustment() = 0; + virtual bool is_read_only() = 0; + virtual const char* subsystem_path() = 0; + virtual void set_subsystem_path(const char* cgroup_path) = 0; + virtual const char* mount_point() = 0; + virtual const char* cgroup_path() = 0; +}; + +// Pure virtual class representing version agnostic memory controllers +class CgroupMemoryController: public CHeapObj { + public: + virtual jlong read_memory_limit_in_bytes(julong upper_bound) = 0; + virtual jlong memory_usage_in_bytes() = 0; + virtual jlong memory_and_swap_limit_in_bytes(julong host_mem, julong host_swap) = 0; + virtual jlong memory_soft_limit_in_bytes(julong upper_bound) = 0; + virtual jlong memory_max_usage_in_bytes() = 0; + virtual void print_version_specific_info(outputStream* st, julong host_mem) = 0; + virtual bool needs_hierarchy_adjustment() = 0; + virtual bool is_read_only() = 0; + virtual const char* subsystem_path() = 0; + virtual void set_subsystem_path(const char* cgroup_path) = 0; + virtual const char* mount_point() = 0; + virtual const char* cgroup_path() = 0; }; class CgroupSubsystem: public CHeapObj { public: jlong memory_limit_in_bytes(); int active_processor_count(); - jlong limit_from_str(char* limit_str); - virtual int cpu_quota() = 0; - virtual int cpu_period() = 0; - virtual int cpu_shares() = 0; virtual jlong pids_max() = 0; virtual jlong pids_current() = 0; - virtual jlong memory_usage_in_bytes() = 0; - virtual jlong memory_and_swap_limit_in_bytes() = 0; - virtual jlong memory_soft_limit_in_bytes() = 0; - virtual jlong memory_max_usage_in_bytes() = 0; + virtual bool is_containerized() = 0; virtual char * cpu_cpuset_cpus() = 0; virtual char * cpu_cpuset_memory_nodes() = 0; - virtual jlong read_memory_limit_in_bytes() = 0; virtual const char * container_type() = 0; - virtual CachingCgroupController* memory_controller() = 0; - virtual CachingCgroupController* cpu_controller() = 0; - - virtual void print_version_specific_info(outputStream* st) = 0; + virtual CachingCgroupController* memory_controller() = 0; + virtual CachingCgroupController* cpu_controller() = 0; + + int cpu_quota(); + int cpu_period(); + int cpu_shares(); + + jlong memory_usage_in_bytes(); + jlong memory_and_swap_limit_in_bytes(); + jlong memory_soft_limit_in_bytes(); + jlong memory_max_usage_in_bytes(); + void print_version_specific_info(outputStream* st); }; // Utility class for storing info retrieved from /proc/cgroups, @@ -288,6 +270,7 @@ class CgroupInfo : public StackObj { char* _name; int _hierarchy_id; bool _enabled; + bool _read_only; // whether or not the mount path is mounted read-only bool _data_complete; // indicating cgroup v1 data is complete for this controller char* _cgroup_path; // cgroup controller path from /proc/self/cgroup char* _root_mount_path; // root mount path from /proc/self/mountinfo. Unused for cgroup v2 @@ -298,6 +281,7 @@ class CgroupInfo : public StackObj { _name = nullptr; _hierarchy_id = -1; _enabled = false; + _read_only = false; _data_complete = false; _cgroup_path = nullptr; _root_mount_path = nullptr; @@ -329,7 +313,8 @@ class CgroupSubsystemFactory: AllStatic { int controller, const char* name, char* mount_path, - char* root_path); + char* root_path, + bool read_only); // Determine the cgroup type (version 1 or version 2), given // relevant paths to files. Sets 'flags' accordingly. static bool determine_type(CgroupInfo* cg_infos, diff --git a/src/hotspot/os/linux/cgroupUtil_linux.cpp b/src/hotspot/os/linux/cgroupUtil_linux.cpp new file mode 100644 index 00000000000..bc0e018d6be --- /dev/null +++ b/src/hotspot/os/linux/cgroupUtil_linux.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024, 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 "os_linux.hpp" +#include "cgroupUtil_linux.hpp" + +int CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int host_cpus) { + assert(host_cpus > 0, "physical host cpus must be positive"); + int limit_count = host_cpus; + int quota = cpu_ctrl->cpu_quota(); + int period = cpu_ctrl->cpu_period(); + int quota_count = 0; + int result = 0; + + if (quota > -1 && period > 0) { + quota_count = ceilf((float)quota / (float)period); + log_trace(os, container)("CPU Quota count based on quota/period: %d", quota_count); + } + + // Use quotas + if (quota_count != 0) { + limit_count = quota_count; + } + + result = MIN2(host_cpus, limit_count); + log_trace(os, container)("OSContainer::active_processor_count: %d", result); + return result; +} + +void CgroupUtil::adjust_controller(CgroupMemoryController* mem) { + if (!mem->needs_hierarchy_adjustment()) { + // nothing to do + return; + } + log_trace(os, container)("Adjusting controller path for memory: %s", mem->subsystem_path()); + assert(mem->cgroup_path() != nullptr, "invariant"); + char* orig = os::strdup(mem->cgroup_path()); + char* cg_path = os::strdup(orig); + char* last_slash; + assert(cg_path[0] == '/', "cgroup path must start with '/'"); + julong phys_mem = os::Linux::physical_memory(); + char* limit_cg_path = nullptr; + jlong limit = mem->read_memory_limit_in_bytes(phys_mem); + jlong lowest_limit = phys_mem; + while ((last_slash = strrchr(cg_path, '/')) != cg_path) { + *last_slash = '\0'; // strip path + // update to shortened path and try again + mem->set_subsystem_path(cg_path); + limit = mem->read_memory_limit_in_bytes(phys_mem); + if (limit >= 0 && limit < lowest_limit) { + lowest_limit = limit; + os::free(limit_cg_path); // handles nullptr + limit_cg_path = os::strdup(cg_path); + } + } + // need to check limit at mount point + mem->set_subsystem_path("/"); + limit = mem->read_memory_limit_in_bytes(phys_mem); + if (limit >= 0 && limit < lowest_limit) { + lowest_limit = limit; + os::free(limit_cg_path); // handles nullptr + limit_cg_path = os::strdup("/"); + } + assert(lowest_limit >= 0, "limit must be positive"); + if ((julong)lowest_limit != phys_mem) { + // we've found a lower limit anywhere in the hierarchy, + // set the path to the limit path + assert(limit_cg_path != nullptr, "limit path must be set"); + mem->set_subsystem_path(limit_cg_path); + log_trace(os, container)("Adjusted controller path for memory to: %s. " + "Lowest limit was: " JLONG_FORMAT, + mem->subsystem_path(), + lowest_limit); + } else { + log_trace(os, container)("No lower limit found for memory in hierarchy %s, " + "adjusting to original path %s", + mem->mount_point(), orig); + mem->set_subsystem_path(orig); + } + os::free(cg_path); + os::free(orig); + os::free(limit_cg_path); +} + +void CgroupUtil::adjust_controller(CgroupCpuController* cpu) { + if (!cpu->needs_hierarchy_adjustment()) { + // nothing to do + return; + } + log_trace(os, container)("Adjusting controller path for cpu: %s", cpu->subsystem_path()); + assert(cpu->cgroup_path() != nullptr, "invariant"); + char* orig = os::strdup(cpu->cgroup_path()); + char* cg_path = os::strdup(orig); + char* last_slash; + assert(cg_path[0] == '/', "cgroup path must start with '/'"); + int host_cpus = os::Linux::active_processor_count(); + int cpus = CgroupUtil::processor_count(cpu, host_cpus); + int lowest_limit = host_cpus; + char* limit_cg_path = nullptr; + while ((last_slash = strrchr(cg_path, '/')) != cg_path) { + *last_slash = '\0'; // strip path + // update to shortened path and try again + cpu->set_subsystem_path(cg_path); + cpus = CgroupUtil::processor_count(cpu, host_cpus); + if (cpus != host_cpus && cpus < lowest_limit) { + lowest_limit = cpus; + os::free(limit_cg_path); // handles nullptr + limit_cg_path = os::strdup(cg_path); + } + } + // need to check limit at mount point + cpu->set_subsystem_path("/"); + cpus = CgroupUtil::processor_count(cpu, host_cpus); + if (cpus != host_cpus && cpus < lowest_limit) { + lowest_limit = cpus; + os::free(limit_cg_path); // handles nullptr + limit_cg_path = os::strdup(cg_path); + } + assert(lowest_limit >= 0, "limit must be positive"); + if (lowest_limit != host_cpus) { + // we've found a lower limit anywhere in the hierarchy, + // set the path to the limit path + assert(limit_cg_path != nullptr, "limit path must be set"); + cpu->set_subsystem_path(limit_cg_path); + log_trace(os, container)("Adjusted controller path for cpu to: %s. " + "Lowest limit was: %d", + cpu->subsystem_path(), + lowest_limit); + } else { + log_trace(os, container)("No lower limit found for cpu in hierarchy %s, " + "adjusting to original path %s", + cpu->mount_point(), orig); + cpu->set_subsystem_path(orig); + } + os::free(cg_path); + os::free(orig); + os::free(limit_cg_path); +} diff --git a/src/hotspot/os/linux/cgroupUtil_linux.hpp b/src/hotspot/os/linux/cgroupUtil_linux.hpp new file mode 100644 index 00000000000..19220af3177 --- /dev/null +++ b/src/hotspot/os/linux/cgroupUtil_linux.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, 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_UTIL_LINUX_HPP +#define CGROUP_UTIL_LINUX_HPP + +#include "utilities/globalDefinitions.hpp" +#include "cgroupSubsystem_linux.hpp" + +class CgroupUtil: AllStatic { + + public: + static int processor_count(CgroupCpuController* cpu, int host_cpus); + // Given a memory controller, adjust its path to a point in the hierarchy + // that represents the closest memory limit. + static void adjust_controller(CgroupMemoryController* m); + // Given a cpu controller, adjust its path to a point in the hierarchy + // that represents the closest cpu limit. + static void adjust_controller(CgroupCpuController* c); +}; + +#endif // CGROUP_UTIL_LINUX_HPP diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp index 307c3c53a0e..c15c5d3c4d9 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp @@ -26,6 +26,7 @@ #include #include #include "cgroupV1Subsystem_linux.hpp" +#include "cgroupUtil_linux.hpp" #include "logging/log.hpp" #include "memory/allocation.hpp" #include "runtime/globals.hpp" @@ -37,7 +38,15 @@ * Set directory to subsystem specific files based * on the contents of the mountinfo and cgroup files. */ -void CgroupV1Controller::set_subsystem_path(char *cgroup_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) { if (strcmp(_root, "/") == 0) { @@ -51,7 +60,7 @@ void CgroupV1Controller::set_subsystem_path(char *cgroup_path) { ss.print_raw(_mount_point); _path = os::strdup(ss.base()); } else { - char *p = strstr(cgroup_path, _root); + char *p = strstr((char*)cgroup_path, _root); if (p != nullptr && p == _root) { if (strlen(cgroup_path) > strlen(_root)) { ss.print_raw(_mount_point); @@ -65,48 +74,47 @@ void CgroupV1Controller::set_subsystem_path(char *cgroup_path) { } } -/* uses_mem_hierarchy - * - * Return whether or not hierarchical cgroup accounting is being - * done. - * - * return: - * A number > 0 if true, or - * OSCONTAINER_ERROR for not supported +/* + * 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. */ -jlong CgroupV1MemoryController::uses_mem_hierarchy() { - GET_CONTAINER_INFO(jlong, this, "/memory.use_hierarchy", - "Use Hierarchy is: ", JLONG_FORMAT, JLONG_FORMAT, use_hierarchy); - return use_hierarchy; +bool CgroupV1Controller::needs_hierarchy_adjustment() { + assert(_cgroup_path != nullptr, "sanity"); + return strcmp(_root, _cgroup_path) != 0; } -void CgroupV1MemoryController::set_subsystem_path(char *cgroup_path) { - CgroupV1Controller::set_subsystem_path(cgroup_path); - jlong hierarchy = uses_mem_hierarchy(); - if (hierarchy > 0) { - set_hierarchical(true); - } -} - -jlong CgroupV1Subsystem::read_memory_limit_in_bytes() { - GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.limit_in_bytes", - "Memory Limit is: ", JULONG_FORMAT, JULONG_FORMAT, memlimit); - - if (memlimit >= os::Linux::physical_memory()) { - log_trace(os, container)("Non-Hierarchical Memory Limit is: Unlimited"); - CgroupV1MemoryController* mem_controller = reinterpret_cast(_memory->controller()); - if (mem_controller->is_hierarchical()) { - GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", "hierarchical_memory_limit", - "Hierarchical Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, hier_memlimit) - if (hier_memlimit >= os::Linux::physical_memory()) { - log_trace(os, container)("Hierarchical Memory Limit is: Unlimited"); +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 { - return (jlong)hier_memlimit; + 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); } - return (jlong)-1; } - else { +} + +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; } } @@ -123,32 +131,19 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() { * * -1 if there isn't any limit in place (note: includes values which exceed a physical * upper bound) */ -jlong CgroupV1Subsystem::read_mem_swap() { - julong host_total_memsw; - GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.memsw.limit_in_bytes", - "Memory and Swap Limit is: ", JULONG_FORMAT, JULONG_FORMAT, memswlimit); - host_total_memsw = os::Linux::host_swap() + os::Linux::physical_memory(); +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)("Non-Hierarchical Memory and Swap Limit is: Unlimited"); - CgroupV1MemoryController* mem_controller = reinterpret_cast(_memory->controller()); - if (mem_controller->is_hierarchical()) { - const char* matchline = "hierarchical_memsw_limit"; - GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline, - "Hierarchical Memory and Swap Limit is : " JULONG_FORMAT, JULONG_FORMAT, hier_memswlimit) - if (hier_memswlimit >= host_total_memsw) { - log_trace(os, container)("Hierarchical Memory and Swap Limit is: Unlimited"); - } else { - return (jlong)hier_memswlimit; - } - } + log_trace(os, container)("Memory and Swap Limit is: Unlimited"); return (jlong)-1; } else { return (jlong)memswlimit; } } -jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() { - jlong memory_swap = read_mem_swap(); +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; } @@ -157,7 +152,7 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() { // supported. jlong swappiness = read_mem_swappiness(); if (swappiness == 0 || memory_swap == OSCONTAINER_ERROR) { - jlong memlimit = read_memory_limit_in_bytes(); + 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 { @@ -168,16 +163,23 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() { return memory_swap; } -jlong CgroupV1Subsystem::read_mem_swappiness() { - GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.swappiness", - "Swappiness is: ", JULONG_FORMAT, JULONG_FORMAT, swappiness); - return swappiness; +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 CgroupV1Subsystem::memory_soft_limit_in_bytes() { - GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.soft_limit_in_bytes", - "Memory Soft Limit is: ", JULONG_FORMAT, JULONG_FORMAT, memsoftlimit); - if (memsoftlimit >= os::Linux::physical_memory()) { +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 { @@ -185,6 +187,32 @@ jlong CgroupV1Subsystem::memory_soft_limit_in_bytes() { } } +// 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(memory); + _cpu = new CachingCgroupController(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. @@ -194,10 +222,10 @@ jlong CgroupV1Subsystem::memory_soft_limit_in_bytes() { * -1 for unlimited * OSCONTAINER_ERROR for not supported */ -jlong CgroupV1Subsystem::memory_usage_in_bytes() { - GET_CONTAINER_INFO(jlong, _memory->controller(), "/memory.usage_in_bytes", - "Memory Usage is: ", JLONG_FORMAT, JLONG_FORMAT, memusage); - return memusage; +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 @@ -208,37 +236,36 @@ jlong CgroupV1Subsystem::memory_usage_in_bytes() { * max memory usage in bytes or * OSCONTAINER_ERROR for not supported */ -jlong CgroupV1Subsystem::memory_max_usage_in_bytes() { - GET_CONTAINER_INFO(jlong, _memory->controller(), "/memory.max_usage_in_bytes", - "Maximum Memory Usage is: ", JLONG_FORMAT, JLONG_FORMAT, memmaxusage); - return memmaxusage; +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 CgroupV1Subsystem::kernel_memory_usage_in_bytes() { - GET_CONTAINER_INFO(jlong, _memory->controller(), "/memory.kmem.usage_in_bytes", - "Kernel Memory Usage is: ", JLONG_FORMAT, JLONG_FORMAT, kmem_usage); - return kmem_usage; +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 CgroupV1Subsystem::kernel_memory_limit_in_bytes() { - GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.kmem.limit_in_bytes", - "Kernel Memory Limit is: ", JULONG_FORMAT, JULONG_FORMAT, kmem_limit); - if (kmem_limit >= os::Linux::physical_memory()) { +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 CgroupV1Subsystem::kernel_memory_max_usage_in_bytes() { - GET_CONTAINER_INFO(jlong, _memory->controller(), "/memory.kmem.max_usage_in_bytes", - "Maximum Kernel Memory Usage is: ", JLONG_FORMAT, JLONG_FORMAT, kmem_max_usage); - return kmem_max_usage; +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 CgroupV1Subsystem::print_version_specific_info(outputStream* st) { +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(); + 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"); @@ -246,15 +273,15 @@ void CgroupV1Subsystem::print_version_specific_info(outputStream* st) { OSContainer::print_container_helper(st, kmem_max_usage, "kernel_memory_max_usage_in_bytes"); } -char * CgroupV1Subsystem::cpu_cpuset_cpus() { - GET_CONTAINER_INFO_CPTR(cptr, _cpuset, "/cpuset.cpus", - "cpuset.cpus is: %s", "%1023s", cpus, 1024); +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() { - GET_CONTAINER_INFO_CPTR(cptr, _cpuset, "/cpuset.mems", - "cpuset.mems is: %s", "%1023s", mems, 1024); +char* CgroupV1Subsystem::cpu_cpuset_memory_nodes() { + char mems[1024]; + CONTAINER_READ_STRING_CHECKED(_cpuset, "/cpuset.mems", "cpuset.mems", mems, 1024); return os::strdup(mems); } @@ -268,16 +295,24 @@ char * CgroupV1Subsystem::cpu_cpuset_memory_nodes() { * -1 for no quota * OSCONTAINER_ERROR for not supported */ -int CgroupV1Subsystem::cpu_quota() { - GET_CONTAINER_INFO(int, _cpu->controller(), "/cpu.cfs_quota_us", - "CPU Quota is: ", "%d", "%d", quota); - return quota; +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 CgroupV1Subsystem::cpu_period() { - GET_CONTAINER_INFO(int, _cpu->controller(), "/cpu.cfs_period_us", - "CPU Period is: ", "%d", "%d", period); - return period; +int CgroupV1CpuController::cpu_period() { + julong period; + CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.cfs_period_us", "CPU Period", period); + return (int)period; } /* cpu_shares @@ -290,20 +325,14 @@ int CgroupV1Subsystem::cpu_period() { * -1 for no share setup * OSCONTAINER_ERROR for not supported */ -int CgroupV1Subsystem::cpu_shares() { - GET_CONTAINER_INFO(int, _cpu->controller(), "/cpu.shares", - "CPU Shares is: ", "%d", "%d", shares); +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 == 1024) return -1; - - return shares; -} - + if (shares_int == 1024) return -1; -char* CgroupV1Subsystem::pids_max_val() { - GET_CONTAINER_INFO_CPTR(cptr, _pids, "/pids.max", - "Maximum number of tasks is: %s", "%1023s", pidsmax, 1024); - return os::strdup(pidsmax); + return shares_int; } /* pids_max @@ -317,8 +346,9 @@ char* CgroupV1Subsystem::pids_max_val() { */ jlong CgroupV1Subsystem::pids_max() { if (_pids == nullptr) return OSCONTAINER_ERROR; - char * pidsmax_str = pids_max_val(); - return limit_from_str(pidsmax_str); + jlong pids_max; + CONTAINER_READ_NUMBER_CHECKED_MAX(_pids, "/pids.max", "Maximum number of tasks", pids_max); + return pids_max; } /* pids_current @@ -331,7 +361,7 @@ jlong CgroupV1Subsystem::pids_max() { */ jlong CgroupV1Subsystem::pids_current() { if (_pids == nullptr) return OSCONTAINER_ERROR; - GET_CONTAINER_INFO(jlong, _pids, "/pids.current", - "Current number of tasks is: ", JLONG_FORMAT, JLONG_FORMAT, pids_current); - return pids_current; + julong pids_current; + CONTAINER_READ_NUMBER_CHECKED(_pids, "/pids.current", "Current number of tasks", pids_current); + return (jlong)pids_current; } diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp index fae65da2a58..e43f82836b1 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -28,57 +28,123 @@ #include "runtime/os.hpp" #include "memory/allocation.hpp" #include "cgroupSubsystem_linux.hpp" +#include "cgroupUtil_linux.hpp" // Cgroups version 1 specific implementation class CgroupV1Controller: public CgroupController { private: /* mountinfo contents */ - char *_root; - char *_mount_point; + char* _root; + bool _read_only; /* Constructed subsystem directory */ - char *_path; + char* _path; public: - CgroupV1Controller(char *root, char *mountpoint) { - _root = os::strdup(root); + CgroupV1Controller(char *root, + char *mountpoint, + bool ro) : _root(os::strdup(root)), + _read_only(ro), + _path(nullptr) { + _cgroup_path = nullptr; _mount_point = os::strdup(mountpoint); - _path = nullptr; + } + // Shallow copy constructor + CgroupV1Controller(const CgroupV1Controller& o) : _root(o._root), + _read_only(o._read_only), + _path(o._path) { + _cgroup_path = o._cgroup_path; + _mount_point = o._mount_point; + } + ~CgroupV1Controller() { + // At least one subsystem controller exists with paths to malloc'd path + // names } - virtual void set_subsystem_path(char *cgroup_path); - char *subsystem_path() { return _path; } + void set_subsystem_path(const char *cgroup_path); + const char* subsystem_path() override { return _path; } + bool is_read_only() override { return _read_only; } + bool needs_hierarchy_adjustment() override; }; -class CgroupV1MemoryController: public CgroupV1Controller { +class CgroupV1MemoryController final : public CgroupMemoryController { + private: + CgroupV1Controller _reader; + CgroupV1Controller* reader() { return &_reader; } public: - bool is_hierarchical() { return _uses_mem_hierarchy; } - void set_subsystem_path(char *cgroup_path); + void set_subsystem_path(const char *cgroup_path) override { + reader()->set_subsystem_path(cgroup_path); + } + jlong read_memory_limit_in_bytes(julong upper_bound) override; + jlong memory_usage_in_bytes() override; + jlong memory_and_swap_limit_in_bytes(julong host_mem, julong host_swap) override; + jlong memory_soft_limit_in_bytes(julong upper_bound) override; + jlong memory_max_usage_in_bytes() override; + jlong kernel_memory_usage_in_bytes(); + jlong kernel_memory_limit_in_bytes(julong host_mem); + jlong kernel_memory_max_usage_in_bytes(); + void print_version_specific_info(outputStream* st, julong host_mem) override; + bool needs_hierarchy_adjustment() override { + return reader()->needs_hierarchy_adjustment(); + } + bool is_read_only() override { + return reader()->is_read_only(); + } + const char* subsystem_path() override { return reader()->subsystem_path(); } + const char* mount_point() override { return reader()->mount_point(); } + const char* cgroup_path() override { return reader()->cgroup_path(); } private: - /* Some container runtimes set limits via cgroup - * hierarchy. If set to true consider also memory.stat - * file if everything else seems unlimited */ - bool _uses_mem_hierarchy; - jlong uses_mem_hierarchy(); - void set_hierarchical(bool value) { _uses_mem_hierarchy = value; } + jlong read_mem_swappiness(); + jlong read_mem_swap(julong host_total_memsw); public: - CgroupV1MemoryController(char *root, char *mountpoint) : CgroupV1Controller(root, mountpoint) { - _uses_mem_hierarchy = false; + CgroupV1MemoryController(const CgroupV1Controller& reader) + : _reader(reader) { + } + +}; + +class CgroupV1CpuController final : public CgroupCpuController { + + private: + CgroupV1Controller _reader; + CgroupV1Controller* reader() { return &_reader; } + public: + int cpu_quota() override; + int cpu_period() override; + int cpu_shares() override; + void set_subsystem_path(const char *cgroup_path) override { + reader()->set_subsystem_path(cgroup_path); + } + bool is_read_only() override { + return reader()->is_read_only(); + } + const char* subsystem_path() override { + return reader()->subsystem_path(); } + const char* mount_point() override { + return reader()->mount_point(); + } + bool needs_hierarchy_adjustment() override { + return reader()->needs_hierarchy_adjustment(); + } + const char* cgroup_path() override { return reader()->cgroup_path(); } + public: + CgroupV1CpuController(const CgroupV1Controller& reader) : _reader(reader) { + } }; class CgroupV1Subsystem: public CgroupSubsystem { public: - jlong read_memory_limit_in_bytes(); - jlong memory_and_swap_limit_in_bytes(); - jlong memory_soft_limit_in_bytes(); - jlong memory_usage_in_bytes(); - jlong memory_max_usage_in_bytes(); + CgroupV1Subsystem(CgroupV1Controller* cpuset, + CgroupV1CpuController* cpu, + CgroupV1Controller* cpuacct, + CgroupV1Controller* pids, + CgroupV1MemoryController* memory); jlong kernel_memory_usage_in_bytes(); jlong kernel_memory_limit_in_bytes(); @@ -87,47 +153,24 @@ class CgroupV1Subsystem: public CgroupSubsystem { char * cpu_cpuset_cpus(); char * cpu_cpuset_memory_nodes(); - int cpu_quota(); - int cpu_period(); - - int cpu_shares(); - jlong pids_max(); jlong pids_current(); - - void print_version_specific_info(outputStream* st); + bool is_containerized(); const char * container_type() { return "cgroupv1"; } - CachingCgroupController * memory_controller() { return _memory; } - CachingCgroupController * cpu_controller() { return _cpu; } + CachingCgroupController* memory_controller() { return _memory; } + CachingCgroupController* cpu_controller() { return _cpu; } private: /* controllers */ - CachingCgroupController* _memory = nullptr; + CachingCgroupController* _memory = nullptr; CgroupV1Controller* _cpuset = nullptr; - CachingCgroupController* _cpu = nullptr; + CachingCgroupController* _cpu = nullptr; CgroupV1Controller* _cpuacct = nullptr; CgroupV1Controller* _pids = nullptr; - char * pids_max_val(); - - jlong read_mem_swappiness(); - jlong read_mem_swap(); - - public: - CgroupV1Subsystem(CgroupV1Controller* cpuset, - CgroupV1Controller* cpu, - CgroupV1Controller* cpuacct, - CgroupV1Controller* pids, - CgroupV1MemoryController* memory) { - _cpuset = cpuset; - _cpu = new CachingCgroupController(cpu); - _cpuacct = cpuacct; - _pids = pids; - _memory = new CachingCgroupController(memory); - } }; #endif // CGROUP_V1_SUBSYSTEM_LINUX_HPP diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp index 1a02bbe95d2..998790dcedd 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp @@ -23,6 +23,23 @@ */ #include "cgroupV2Subsystem_linux.hpp" +#include "cgroupUtil_linux.hpp" + +// Constructor +CgroupV2Controller::CgroupV2Controller(char* mount_path, + char *cgroup_path, + bool ro) : _read_only(ro), + _path(construct_path(mount_path, cgroup_path)) { + _cgroup_path = os::strdup(cgroup_path); + _mount_point = os::strdup(mount_path); +} +// Shallow copy constructor +CgroupV2Controller::CgroupV2Controller(const CgroupV2Controller& o) : + _read_only(o._read_only), + _path(o._path) { + _cgroup_path = o._cgroup_path; + _mount_point = o._mount_point; +} /* cpu_shares * @@ -34,11 +51,12 @@ * -1 for no share setup * OSCONTAINER_ERROR for not supported */ -int CgroupV2Subsystem::cpu_shares() { - GET_CONTAINER_INFO(int, _unified, "/cpu.weight", - "Raw value for CPU Shares is: ", "%d", "%d", shares); +int CgroupV2CpuController::cpu_shares() { + julong shares; + CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.weight", "Raw value for CPU Shares", shares); + int shares_int = (int)shares; // Convert default value of 100 to no shares setup - if (shares == 100) { + if (shares_int == 100) { log_debug(os, container)("CPU Shares is: %d", -1); return -1; } @@ -50,7 +68,7 @@ int CgroupV2Subsystem::cpu_shares() { // Use the inverse of (x == OCI value, y == cgroupsv2 value): // ((262142 * y - 1)/9999) + 2 = x // - int x = 262142 * shares - 1; + int x = 262142 * shares_int - 1; double frac = x/9999.0; x = ((int)frac) + 2; log_trace(os, container)("Scaled CPU shares value is: %d", x); @@ -82,34 +100,55 @@ int CgroupV2Subsystem::cpu_shares() { * -1 for no quota * OSCONTAINER_ERROR for not supported */ -int CgroupV2Subsystem::cpu_quota() { - char * cpu_quota_str = cpu_quota_val(); - int limit = (int)limit_from_str(cpu_quota_str); +int CgroupV2CpuController::cpu_quota() { + jlong quota_val; + bool is_ok = reader()->read_numerical_tuple_value("/cpu.max", true /* use_first */, "a_val); + if (!is_ok) { + return OSCONTAINER_ERROR; + } + int limit = (int)quota_val; log_trace(os, container)("CPU Quota is: %d", limit); return limit; } -char * CgroupV2Subsystem::cpu_cpuset_cpus() { - GET_CONTAINER_INFO_CPTR(cptr, _unified, "/cpuset.cpus", - "cpuset.cpus is: %s", "%1023s", cpus, 1024); - return os::strdup(cpus); +// Constructor +CgroupV2Subsystem::CgroupV2Subsystem(CgroupV2MemoryController * memory, + CgroupV2CpuController* cpu, + CgroupV2Controller unified) : + _unified(unified) { + CgroupUtil::adjust_controller(memory); + CgroupUtil::adjust_controller(cpu); + _memory = new CachingCgroupController(memory); + _cpu = new CachingCgroupController(cpu); } -char* CgroupV2Subsystem::cpu_quota_val() { - GET_CONTAINER_INFO_CPTR(cptr, _unified, "/cpu.max", - "Raw value for CPU quota is: %s", "%1023s %*d", quota, 1024); - return os::strdup(quota); +bool CgroupV2Subsystem::is_containerized() { + return _unified.is_read_only() && + _memory->controller()->is_read_only() && + _cpu->controller()->is_read_only(); } -char * CgroupV2Subsystem::cpu_cpuset_memory_nodes() { - GET_CONTAINER_INFO_CPTR(cptr, _unified, "/cpuset.mems", - "cpuset.mems is: %s", "%1023s", mems, 1024); +char* CgroupV2Subsystem::cpu_cpuset_cpus() { + char cpus[1024]; + CONTAINER_READ_STRING_CHECKED(unified(), "/cpuset.cpus", "cpuset.cpus", cpus, 1024); + return os::strdup(cpus); +} + +char* CgroupV2Subsystem::cpu_cpuset_memory_nodes() { + char mems[1024]; + CONTAINER_READ_STRING_CHECKED(unified(), "/cpuset.mems", "cpuset.mems", mems, 1024); return os::strdup(mems); } -int CgroupV2Subsystem::cpu_period() { - GET_CONTAINER_INFO(int, _unified, "/cpu.max", - "CPU Period is: ", "%d", "%*s %d", period); +int CgroupV2CpuController::cpu_period() { + jlong period_val; + bool is_ok = reader()->read_numerical_tuple_value("/cpu.max", false /* use_first */, &period_val); + if (!is_ok) { + log_trace(os, container)("CPU Period failed: %d", OSCONTAINER_ERROR); + return OSCONTAINER_ERROR; + } + int period = (int)period_val; + log_trace(os, container)("CPU Period is: %d", period); return period; } @@ -122,45 +161,42 @@ int CgroupV2Subsystem::cpu_period() { * -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 CgroupV2MemoryController::memory_usage_in_bytes() { + julong memusage; + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.current", "Memory Usage", memusage); + return (jlong)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 CgroupV2MemoryController::memory_soft_limit_in_bytes(julong phys_mem) { + jlong mem_soft_limit; + CONTAINER_READ_NUMBER_CHECKED_MAX(reader(), "/memory.low", "Memory Soft Limit", mem_soft_limit); + return mem_soft_limit; } -jlong CgroupV2Subsystem::memory_max_usage_in_bytes() { +jlong CgroupV2MemoryController::memory_max_usage_in_bytes() { // Log this string at trace level so as to make tests happy. log_trace(os, container)("Maximum Memory Usage is not supported."); return OSCONTAINER_ERROR; // not supported } -char* CgroupV2Subsystem::mem_soft_limit_val() { - GET_CONTAINER_INFO_CPTR(cptr, _unified, "/memory.low", - "Memory Soft Limit is: %s", "%1023s", mem_soft_limit_str, 1024); - return os::strdup(mem_soft_limit_str); -} - // Note that for cgroups v2 the actual limits set for swap and // memory live in two different files, memory.swap.max and memory.max // respectively. In order to properly report a cgroup v1 like // compound value we need to sum the two values. Setting a swap limit // without also setting a memory limit is not allowed. -jlong CgroupV2Subsystem::memory_and_swap_limit_in_bytes() { - char* mem_swp_limit_str = mem_swp_limit_val(); - if (mem_swp_limit_str == nullptr) { +jlong CgroupV2MemoryController::memory_and_swap_limit_in_bytes(julong phys_mem, + julong host_swap /* unused in cg v2 */) { + jlong swap_limit; + bool is_ok = reader()->read_number_handle_max("/memory.swap.max", &swap_limit); + if (!is_ok) { // Some container tests rely on this trace logging to happen. - log_trace(os, container)("Memory and Swap Limit is: %d", OSCONTAINER_ERROR); + log_trace(os, container)("Swap Limit failed: %d", OSCONTAINER_ERROR); // swap disabled at kernel level, treat it as no swap - return read_memory_limit_in_bytes(); + return read_memory_limit_in_bytes(phys_mem); } - jlong swap_limit = limit_from_str(mem_swp_limit_str); + log_trace(os, container)("Swap Limit is: " JLONG_FORMAT, swap_limit); if (swap_limit >= 0) { - jlong memory_limit = read_memory_limit_in_bytes(); + jlong memory_limit = read_memory_limit_in_bytes(phys_mem); assert(memory_limit >= 0, "swap limit without memory limit?"); return memory_limit + swap_limit; } @@ -168,20 +204,22 @@ jlong CgroupV2Subsystem::memory_and_swap_limit_in_bytes() { return swap_limit; } -char* CgroupV2Subsystem::mem_swp_limit_val() { - GET_CONTAINER_INFO_CPTR(cptr, _unified, "/memory.swap.max", - "Memory and Swap Limit is: %s", "%1023s", mem_swp_limit_str, 1024); - return os::strdup(mem_swp_limit_str); +// memory.swap.current : total amount of swap currently used by the cgroup and its descendants +static +jlong memory_swap_current_value(CgroupV2Controller* ctrl) { + julong swap_current; + CONTAINER_READ_NUMBER_CHECKED(ctrl, "/memory.swap.current", "Swap currently used", swap_current); + return (jlong)swap_current; } -// memory.swap.current : total amount of swap currently used by the cgroup and its descendants -char* CgroupV2Subsystem::mem_swp_current_val() { - GET_CONTAINER_INFO_CPTR(cptr, _unified, "/memory.swap.current", - "Swap currently used is: %s", "%1023s", mem_swp_current_str, 1024); - return os::strdup(mem_swp_current_str); +static +jlong memory_limit_value(CgroupV2Controller* ctrl) { + jlong memory_limit; + CONTAINER_READ_NUMBER_CHECKED_MAX(ctrl, "/memory.max", "Memory Limit", memory_limit); + return memory_limit; } -/* memory_limit_in_bytes +/* read_memory_limit_in_bytes * * Return the limit of available memory for this process. * @@ -189,9 +227,8 @@ char* CgroupV2Subsystem::mem_swp_current_val() { * memory limit in bytes or * -1 for unlimited, OSCONTAINER_ERROR for an error */ -jlong CgroupV2Subsystem::read_memory_limit_in_bytes() { - char * mem_limit_str = mem_limit_val(); - jlong limit = limit_from_str(mem_limit_str); +jlong CgroupV2MemoryController::read_memory_limit_in_bytes(julong phys_mem) { + jlong limit = memory_limit_value(reader()); if (log_is_enabled(Trace, os, container)) { if (limit == -1) { log_trace(os, container)("Memory Limit is: Unlimited"); @@ -199,27 +236,53 @@ jlong CgroupV2Subsystem::read_memory_limit_in_bytes() { log_trace(os, container)("Memory Limit is: " JLONG_FORMAT, limit); } } + if (log_is_enabled(Debug, os, container)) { + julong read_limit = (julong)limit; // avoid signed/unsigned compare + if (limit < 0 || read_limit >= phys_mem) { + const char* reason; + if (limit == -1) { + reason = "unlimited"; + } else if (limit == OSCONTAINER_ERROR) { + reason = "failed"; + } else { + assert(read_limit >= phys_mem, "Expected mem limit to exceed host memory"); + reason = "ignored"; + } + log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value " JLONG_FORMAT, + reason, limit, phys_mem); + } + } return limit; } -char* CgroupV2Subsystem::mem_limit_val() { - GET_CONTAINER_INFO_CPTR(cptr, _unified, "/memory.max", - "Raw value for memory limit is: %s", "%1023s", mem_limit_str, 1024); - return os::strdup(mem_limit_str); +static +jlong memory_swap_limit_value(CgroupV2Controller* ctrl) { + jlong swap_limit; + CONTAINER_READ_NUMBER_CHECKED_MAX(ctrl, "/memory.swap.max", "Swap Limit", swap_limit); + return swap_limit; } -void CgroupV2Subsystem::print_version_specific_info(outputStream* st) { - char* mem_swp_current_str = mem_swp_current_val(); - jlong swap_current = limit_from_str(mem_swp_current_str); +void CgroupV2Controller::set_subsystem_path(const char* cgroup_path) { + if (_path != nullptr) { + os::free(_path); + } + _path = construct_path(_mount_point, cgroup_path); +} + +// For cgv2 we only need hierarchy walk if the cgroup path isn't '/' (root) +bool CgroupV2Controller::needs_hierarchy_adjustment() { + return strcmp(_cgroup_path, "/") != 0; +} - char* mem_swp_limit_str = mem_swp_limit_val(); - jlong swap_limit = limit_from_str(mem_swp_limit_str); +void CgroupV2MemoryController::print_version_specific_info(outputStream* st, julong phys_mem) { + jlong swap_current = memory_swap_current_value(reader()); + jlong swap_limit = memory_swap_limit_value(reader()); OSContainer::print_container_helper(st, swap_current, "memory_swap_current_in_bytes"); OSContainer::print_container_helper(st, swap_limit, "memory_swap_max_limit_in_bytes"); } -char* CgroupV2Controller::construct_path(char* mount_path, char *cgroup_path) { +char* CgroupV2Controller::construct_path(char* mount_path, const char* cgroup_path) { stringStream ss; ss.print_raw(mount_path); if (strcmp(cgroup_path, "/") != 0) { @@ -228,12 +291,6 @@ char* CgroupV2Controller::construct_path(char* mount_path, char *cgroup_path) { return os::strdup(ss.base()); } -char* CgroupV2Subsystem::pids_max_val() { - GET_CONTAINER_INFO_CPTR(cptr, _unified, "/pids.max", - "Maximum number of tasks is: %s", "%1023s", pidsmax, 1024); - return os::strdup(pidsmax); -} - /* pids_max * * Return the maximum number of tasks available to the process @@ -244,8 +301,9 @@ char* CgroupV2Subsystem::pids_max_val() { * OSCONTAINER_ERROR for not supported */ jlong CgroupV2Subsystem::pids_max() { - char * pidsmax_str = pids_max_val(); - return limit_from_str(pidsmax_str); + jlong pids_max; + CONTAINER_READ_NUMBER_CHECKED_MAX(unified(), "/pids.max", "Maximum number of tasks", pids_max); + return pids_max; } /* pids_current @@ -257,7 +315,7 @@ jlong CgroupV2Subsystem::pids_max() { * OSCONTAINER_ERROR for not supported */ jlong CgroupV2Subsystem::pids_current() { - GET_CONTAINER_INFO(jlong, _unified, "/pids.current", - "Current number of tasks is: ", JLONG_FORMAT, JLONG_FORMAT, pids_current); + julong pids_current; + CONTAINER_READ_NUMBER_CHECKED(unified(), "/pids.current", "Current number of tasks", pids_current); return pids_current; } diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp index bb6b538c216..b9cb8236b6f 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Red Hat Inc. + * Copyright (c) 2020, 2024, 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 @@ -26,71 +26,114 @@ #define CGROUP_V2_SUBSYSTEM_LINUX_HPP #include "cgroupSubsystem_linux.hpp" +#include "cgroupUtil_linux.hpp" class CgroupV2Controller: public CgroupController { private: - /* the mount path of the cgroup v2 hierarchy */ - char *_mount_path; - /* The cgroup path for the controller */ - char *_cgroup_path; + bool _read_only; /* Constructed full path to the subsystem directory */ char *_path; - static char* construct_path(char* mount_path, char *cgroup_path); + static char* construct_path(char* mount_path, const 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); + CgroupV2Controller(char* mount_path, char *cgroup_path, bool ro); + // Shallow copy constructor + CgroupV2Controller(const CgroupV2Controller& o); + ~CgroupV2Controller() { + // At least one controller exists with references to the paths } - char *subsystem_path() { return _path; } + const char* subsystem_path() override { return _path; } + bool needs_hierarchy_adjustment() override; + // Allow for optional updates of the subsystem path + void set_subsystem_path(const char* cgroup_path); + bool is_read_only() override { return _read_only; } +}; + +class CgroupV2CpuController: public CgroupCpuController { + private: + CgroupV2Controller _reader; + CgroupV2Controller* reader() { return &_reader; } + public: + CgroupV2CpuController(const CgroupV2Controller& reader) : _reader(reader) { + } + int cpu_quota() override; + int cpu_period() override; + int cpu_shares() override; + bool is_read_only() override { + return reader()->is_read_only(); + } + const char* subsystem_path() { + return reader()->subsystem_path(); + } + bool needs_hierarchy_adjustment() override { + return reader()->needs_hierarchy_adjustment(); + } + void set_subsystem_path(const char* cgroup_path) { + reader()->set_subsystem_path(cgroup_path); + } + const char* mount_point() { return reader()->mount_point(); } + const char* cgroup_path() override { return reader()->cgroup_path(); } +}; + +class CgroupV2MemoryController final: public CgroupMemoryController { + private: + CgroupV2Controller _reader; + CgroupV2Controller* reader() { return &_reader; } + public: + CgroupV2MemoryController(const CgroupV2Controller& reader) : _reader(reader) { + } + + jlong read_memory_limit_in_bytes(julong upper_bound) override; + jlong memory_and_swap_limit_in_bytes(julong host_mem, julong host_swp) override; + jlong memory_soft_limit_in_bytes(julong upper_bound) override; + jlong memory_usage_in_bytes() override; + jlong memory_max_usage_in_bytes() override; + void print_version_specific_info(outputStream* st, julong host_mem) override; + bool is_read_only() override { + return reader()->is_read_only(); + } + const char* subsystem_path() { + return reader()->subsystem_path(); + } + bool needs_hierarchy_adjustment() override { + return reader()->needs_hierarchy_adjustment(); + } + void set_subsystem_path(const char* cgroup_path) { + reader()->set_subsystem_path(cgroup_path); + } + const char* mount_point() { return reader()->mount_point(); } + const char* cgroup_path() override { return reader()->cgroup_path(); } }; class CgroupV2Subsystem: public CgroupSubsystem { private: /* One unified controller */ - CgroupController* _unified = nullptr; + CgroupV2Controller _unified; /* Caching wrappers for cpu/memory metrics */ - CachingCgroupController* _memory = nullptr; - CachingCgroupController* _cpu = nullptr; + CachingCgroupController* _memory = nullptr; + CachingCgroupController* _cpu = nullptr; - char *mem_limit_val(); - char *mem_swp_limit_val(); - char *mem_swp_current_val(); - char *mem_soft_limit_val(); - char *cpu_quota_val(); - char *pids_max_val(); + CgroupV2Controller* unified() { return &_unified; } public: - CgroupV2Subsystem(CgroupController * unified) { - _unified = unified; - _memory = new CachingCgroupController(unified); - _cpu = new CachingCgroupController(unified); - } - - jlong read_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(); + CgroupV2Subsystem(CgroupV2MemoryController * memory, + CgroupV2CpuController* cpu, + CgroupV2Controller unified); - char * cpu_cpuset_cpus(); - char * cpu_cpuset_memory_nodes(); - jlong pids_max(); - jlong pids_current(); + char * cpu_cpuset_cpus() override; + char * cpu_cpuset_memory_nodes() override; + jlong pids_max() override; + jlong pids_current() override; - void print_version_specific_info(outputStream* st); + bool is_containerized() override; - const char * container_type() { + const char * container_type() override { return "cgroupv2"; } - CachingCgroupController * memory_controller() { return _memory; } - CachingCgroupController * cpu_controller() { return _cpu; } + CachingCgroupController* memory_controller() override { return _memory; } + CachingCgroupController* cpu_controller() override { return _cpu; } }; #endif // CGROUP_V2_SUBSYSTEM_LINUX_HPP diff --git a/src/hotspot/os/linux/osContainer_linux.cpp b/src/hotspot/os/linux/osContainer_linux.cpp index 3f8c352dee3..87bc484f36d 100644 --- a/src/hotspot/os/linux/osContainer_linux.cpp +++ b/src/hotspot/os/linux/osContainer_linux.cpp @@ -58,8 +58,43 @@ void OSContainer::init() { if (cgroup_subsystem == nullptr) { return; // Required subsystem files not found or other error } - - _is_containerized = true; + /* + * In order to avoid a false positive on is_containerized() on + * Linux systems outside a container *and* to ensure compatibility + * with in-container usage, we detemine is_containerized() by two + * steps: + * 1.) Determine if all the cgroup controllers are mounted read only. + * If yes, is_containerized() == true. Otherwise, do the fallback + * in 2.) + * 2.) Query for memory and cpu limits. If any limit is set, we set + * is_containerized() == true. + * + * Step 1.) covers the basic in container use-cases. Step 2.) ensures + * that limits enforced by other means (e.g. systemd slice) are properly + * detected. + */ + const char *reason; + bool any_mem_cpu_limit_present = false; + bool controllers_read_only = cgroup_subsystem->is_containerized(); + if (controllers_read_only) { + // in-container case + reason = " because all controllers are mounted read-only (container case)"; + } else { + // We can be in one of two cases: + // 1.) On a physical Linux system without any limit + // 2.) On a physical Linux system with a limit enforced by other means (like systemd slice) + any_mem_cpu_limit_present = cgroup_subsystem->memory_limit_in_bytes() > 0 || + os::Linux::active_processor_count() != cgroup_subsystem->active_processor_count(); + if (any_mem_cpu_limit_present) { + reason = " because either a cpu or a memory limit is present"; + } else { + reason = " because no cpu or memory limit is present"; + } + } + _is_containerized = controllers_read_only || any_mem_cpu_limit_present; + log_debug(os, container)("OSContainer::init: is_containerized() = %s%s", + _is_containerized ? "true" : "false", + reason); } const char * OSContainer::container_type() { diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index f1e2c35891a..73e29bcf200 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -150,6 +150,9 @@ JVM_ActiveProcessorCount(void); JNIEXPORT jboolean JNICALL JVM_IsUseContainerSupport(void); +JNIEXPORT jboolean JNICALL +JVM_IsContainerized(void); + JNIEXPORT void * JNICALL JVM_LoadZipLibrary(); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 7852f37d4be..64bed35d08f 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -110,6 +110,9 @@ #if INCLUDE_MANAGEMENT #include "services/finalizerService.hpp" #endif +#ifdef LINUX +#include "osContainer_linux.hpp" +#endif #include @@ -493,6 +496,15 @@ JVM_LEAF(jboolean, JVM_IsUseContainerSupport(void)) return JNI_FALSE; JVM_END +JVM_LEAF(jboolean, JVM_IsContainerized(void)) +#ifdef LINUX + if (OSContainer::is_containerized()) { + return JNI_TRUE; + } +#endif + return JNI_FALSE; +JVM_END + // java.lang.Throwable ////////////////////////////////////////////////////// JVM_ENTRY(void, JVM_FillInStackTrace(JNIEnv *env, jobject receiver)) diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java index 8797711bf4b..af551a07b1e 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupMetrics.java @@ -35,6 +35,11 @@ public class CgroupMetrics implements Metrics { this.subsystem = Objects.requireNonNull(subsystem); } + @Override + public boolean isContainerized() { + return isContainerized0(); + } + @Override public String getProvider() { return subsystem.getProvider(); @@ -194,6 +199,7 @@ public static Metrics getInstance() { } private static native boolean isUseContainerSupport(); + private static native boolean isContainerized0(); private static native long getTotalMemorySize0(); private static native long getTotalSwapSize0(); diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystem.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystem.java index 952de13e9f2..7df86d03ff4 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystem.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystem.java @@ -31,6 +31,11 @@ */ public interface CgroupSubsystem extends Metrics { + + default boolean isContainerized() { + return false; // This default impl is never used + } + /** * Returned for metrics of type long if the underlying implementation * has determined that no limit is being imposed. diff --git a/src/java.base/linux/native/libjava/CgroupMetrics.c b/src/java.base/linux/native/libjava/CgroupMetrics.c index a5e41167bc3..e2f98633459 100644 --- a/src/java.base/linux/native/libjava/CgroupMetrics.c +++ b/src/java.base/linux/native/libjava/CgroupMetrics.c @@ -36,6 +36,12 @@ Java_jdk_internal_platform_CgroupMetrics_isUseContainerSupport(JNIEnv *env, jcla return JVM_IsUseContainerSupport(); } +JNIEXPORT jboolean JNICALL +Java_jdk_internal_platform_CgroupMetrics_isContainerized0(JNIEnv *env, jclass ignored) +{ + return JVM_IsContainerized(); +} + JNIEXPORT jlong JNICALL Java_jdk_internal_platform_CgroupMetrics_getTotalMemorySize0 (JNIEnv *env, jclass ignored) diff --git a/src/java.base/share/classes/jdk/internal/platform/Metrics.java b/src/java.base/share/classes/jdk/internal/platform/Metrics.java index c45e3e52257..b14dbd7738e 100644 --- a/src/java.base/share/classes/jdk/internal/platform/Metrics.java +++ b/src/java.base/share/classes/jdk/internal/platform/Metrics.java @@ -71,6 +71,23 @@ public static Metrics systemMetrics() { */ public String getProvider(); + /** + * Determines whether or not the underlying system + * has useful metrics (a.k.a. is containerized). + * + * @implNote + * Note that Metrics on some systems aren't useful. + * For example on a regular Linux system with cgroups + * present, with no limits enforced and not running in + * a container, Metric aren't useful. This can be used + * in order to determine if the system is containerized. + * + * @return true when the JVM runs in containerized mode. + * false otherwise. + * + */ + public boolean isContainerized(); + /***************************************************************** * CPU Accounting Subsystem diff --git a/src/java.base/share/classes/sun/launcher/LauncherHelper.java b/src/java.base/share/classes/sun/launcher/LauncherHelper.java index 58a6be5fa1e..ff81acbbf7f 100644 --- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java +++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java @@ -347,6 +347,10 @@ private static void printSystemMetrics() { final long longRetvalNotSupported = -2; ostream.println(INDENT + "Provider: " + c.getProvider()); + if (!c.isContainerized()) { + ostream.println(INDENT + "System not containerized."); + return; + } ostream.println(INDENT + "Effective CPU Count: " + c.getEffectiveCpuCount()); ostream.println(formatCpuVal(c.getCpuPeriod(), INDENT + "CPU Period: ", longRetvalNotSupported)); ostream.println(formatCpuVal(c.getCpuQuota(), INDENT + "CPU Quota: ", longRetvalNotSupported)); diff --git a/test/hotspot/gtest/os/linux/test_cgroupSubsystem_linux.cpp b/test/hotspot/gtest/os/linux/test_cgroupSubsystem_linux.cpp deleted file mode 100644 index 475b6eadb74..00000000000 --- a/test/hotspot/gtest/os/linux/test_cgroupSubsystem_linux.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2022, 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 "precompiled.hpp" - -#ifdef LINUX - -#include "runtime/os.hpp" -#include "cgroupSubsystem_linux.hpp" -#include "unittest.hpp" -#include "utilities/globalDefinitions.hpp" - -#include - - -// Utilities -bool file_exists(const char* filename) { - struct stat st; - return os::stat(filename, &st) == 0; -} - -char* temp_file(const char* prefix) { - const testing::TestInfo* test_info = ::testing::UnitTest::GetInstance()->current_test_info(); - stringStream path; - path.print_raw(os::get_temp_directory()); - path.print_raw(os::file_separator()); - path.print("%s-test-jdk.pid%d.%s.%s", prefix, os::current_process_id(), - test_info->test_case_name(), test_info->name()); - return path.as_string(true); -} - -void delete_file(const char* filename) { - if (!file_exists(filename)) { - return; - } - int ret = remove(filename); - EXPECT_TRUE(ret == 0 || errno == ENOENT) << "failed to remove file '" << filename << "': " - << os::strerror(errno) << " (" << errno << ")"; -} - -class TestController : public CgroupController { -public: - char* subsystem_path() override { - // The real subsystem is in /tmp/, generated by temp_file() - return (char*)"/"; - }; -}; - -void fill_file(const char* path, const char* content) { - delete_file(path); - FILE* fp = os::fopen(path, "w"); - if (fp == nullptr) { - return; - } - if (content != nullptr) { - fprintf(fp, "%s", content); - } - fclose(fp); -} - -TEST(cgroupTest, SubSystemFileLineContentsMultipleLinesErrorCases) { - TestController my_controller{}; - const char* test_file = temp_file("cgroups"); - int x = 0; - char s[1024]; - int err = 0; - - s[0] = '\0'; - fill_file(test_file, "foo "); - err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); - EXPECT_NE(err, 0) << "Value must not be missing in key/value case"; - - s[0] = '\0'; - fill_file(test_file, "faulty_start foo bar"); - err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); - EXPECT_NE(err, 0) << "Key must be at start"; - - s[0] = '\0'; - fill_file(test_file, "foof bar"); - err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); - EXPECT_NE(err, 0) << "Key must be exact match"; - - // Cleanup - delete_file(test_file); -} - -TEST(cgroupTest, SubSystemFileLineContentsMultipleLinesSuccessCases) { - TestController my_controller{}; - const char* test_file = temp_file("cgroups"); - int x = 0; - char s[1024]; - int err = 0; - - s[0] = '\0'; - fill_file(test_file, "foo bar"); - err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); - EXPECT_EQ(err, 0); - EXPECT_STREQ(s, "bar") << "Incorrect!"; - - s[0] = '\0'; - fill_file(test_file, "foo\tbar"); - err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); - EXPECT_EQ(err, 0); - EXPECT_STREQ(s, "bar") << "Incorrect!"; - - s[0] = '\0'; - fill_file(test_file, "foof bar\nfoo car"); - err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); - EXPECT_EQ(err, 0); - EXPECT_STREQ(s, "car"); - - s[0] = '\0'; - fill_file(test_file, "foo\ttest\nfoot car"); - err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); - EXPECT_EQ(err, 0); - EXPECT_STREQ(s, "test"); - - s[0] = '\0'; - fill_file(test_file, "foo 1\nfoo car"); - err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); - EXPECT_EQ(err, 0); - EXPECT_STREQ(s, "1"); - - s[0] = '\0'; - fill_file(test_file, "max 10000"); - err = subsystem_file_line_contents(&my_controller, test_file, nullptr, "%s %*d", &s); - EXPECT_EQ(err, 0); - EXPECT_STREQ(s, "max"); - - x = -3; - fill_file(test_file, "max 10001"); - err = subsystem_file_line_contents(&my_controller, test_file, nullptr, "%*s %d", &x); - EXPECT_EQ(err, 0); - EXPECT_EQ(x, 10001); - - // Cleanup - delete_file(test_file); -} - -TEST(cgroupTest, SubSystemFileLineContentsSingleLine) { - TestController my_controller{}; - const char* test_file = temp_file("cgroups"); - int x = 0; - char s[1024]; - int err = 0; - - fill_file(test_file, "foo"); - err = subsystem_file_line_contents(&my_controller, test_file, nullptr, "%s", &s); - EXPECT_EQ(err, 0); - EXPECT_STREQ(s, "foo"); - - fill_file(test_file, "1337"); - err = subsystem_file_line_contents(&my_controller, test_file, nullptr, "%d", &x); - EXPECT_EQ(err, 0); - EXPECT_EQ(x, 1337) << "Wrong value for x"; - - s[0] = '\0'; - fill_file(test_file, "1337"); - err = subsystem_file_line_contents(&my_controller, test_file, nullptr, "%s", &s); - EXPECT_EQ(err, 0); - EXPECT_STREQ(s, "1337"); - - x = -1; - fill_file(test_file, nullptr); - err = subsystem_file_line_contents(&my_controller, test_file, nullptr, "%d", &x); - EXPECT_NE(err, 0) << "Empty file should've failed"; - EXPECT_EQ(x, -1) << "x was altered"; - - jlong y; - fill_file(test_file, "1337"); - err = subsystem_file_line_contents(&my_controller, test_file, nullptr, JLONG_FORMAT, &y); - EXPECT_EQ(err, 0); - EXPECT_EQ(y, 1337) << "Wrong value for y"; - julong z; - fill_file(test_file, "1337"); - err = subsystem_file_line_contents(&my_controller, test_file, nullptr, JULONG_FORMAT, &z); - EXPECT_EQ(err, 0); - EXPECT_EQ(z, (julong)1337) << "Wrong value for z"; - - // Cleanup - delete_file(test_file); -} - -#endif // LINUX diff --git a/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp b/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp new file mode 100644 index 00000000000..f2af6372aa4 --- /dev/null +++ b/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp @@ -0,0 +1,557 @@ +/* + * Copyright (c) 2022, 2024, 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 "precompiled.hpp" + +#ifdef LINUX + +#include "runtime/os.hpp" +#include "cgroupSubsystem_linux.hpp" +#include "cgroupV1Subsystem_linux.hpp" +#include "cgroupV2Subsystem_linux.hpp" +#include "unittest.hpp" +#include "utilities/globalDefinitions.hpp" + +#include + +// for basename +#include + +typedef struct { + const char* mount_path; + const char* root_path; + const char* cgroup_path; + const char* expected_path; +} TestCase; + +// Utilities +static bool file_exists(const char* filename) { + struct stat st; + return os::stat(filename, &st) == 0; +} + +// we rely on temp_file returning modifiable memory in resource area. +static char* temp_file(const char* prefix) { + const testing::TestInfo* test_info = ::testing::UnitTest::GetInstance()->current_test_info(); + stringStream path; + path.print_raw(os::get_temp_directory()); + path.print_raw(os::file_separator()); + path.print("%s-test-jdk.pid%d.%s.%s", prefix, os::current_process_id(), + test_info->test_case_name(), test_info->name()); + return path.as_string(true); +} + +static void delete_file(const char* filename) { + if (!file_exists(filename)) { + return; + } + int ret = remove(filename); + EXPECT_TRUE(ret == 0 || errno == ENOENT) << "failed to remove file '" << filename << "': " + << os::strerror(errno) << " (" << errno << ")"; +} + +class TestController : public CgroupController { +private: + char* _path; +public: + TestController(char* p): _path(p) {} + const char* subsystem_path() override { + return _path; + }; + bool is_read_only() override { + return true; // doesn't matter + } +}; + +static void fill_file(const char* path, const char* content) { + delete_file(path); + FILE* fp = os::fopen(path, "w"); + if (fp == nullptr) { + return; + } + if (content != nullptr) { + fprintf(fp, "%s", content); + } + fclose(fp); +} + +TEST(cgroupTest, read_numerical_key_value_failure_cases) { + char* test_file = temp_file("cgroups"); + const char* b = basename(test_file); + EXPECT_TRUE(b != nullptr) << "basename was null"; + stringStream path; + path.print_raw(os::file_separator()); + path.print_raw(b); + const char* base_with_slash = path.as_string(true); + + TestController* controller = new TestController((char*)os::get_temp_directory()); + constexpr julong bad = 0xBAD; + julong x = bad; + + fill_file(test_file, "foo "); + bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_FALSE(is_ok) << "Value is missing in key/value case, expecting false"; + EXPECT_EQ(bad, x) << "x must be unchanged"; + + x = bad; + fill_file(test_file, "faulty_start foo 101"); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_FALSE(is_ok) << "key must be at the start"; + EXPECT_EQ(bad, x) << "x must be unchanged"; + + x = bad; + fill_file(test_file, nullptr); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_FALSE(is_ok) << "key not in empty file"; + EXPECT_EQ(bad, x) << "x must be unchanged"; + + x = bad; + fill_file(test_file, "foo\n"); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_FALSE(is_ok) << "key must have a value"; + EXPECT_EQ(bad, x) << "x must be unchanged"; + + x = bad; + fill_file(test_file, "foof 1002"); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_FALSE(is_ok) << "key must be exact match"; + EXPECT_EQ(bad, x) << "x must be unchanged"; + + // Cleanup + delete_file(test_file); +} + +TEST(cgroupTest, read_numerical_key_value_success_cases) { + char* test_file = temp_file("cgroups"); + const char* b = basename(test_file); + EXPECT_TRUE(b != nullptr) << "basename was null"; + stringStream path; + path.print_raw(os::file_separator()); + path.print_raw(b); + const char* base_with_slash = path.as_string(true); + + TestController* controller = new TestController((char*)os::get_temp_directory()); + constexpr julong bad = 0xBAD; + julong x = bad; + + fill_file(test_file, "foo 100"); + bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_TRUE(is_ok); + EXPECT_EQ((julong)100, x); + + x = bad; + fill_file(test_file, "foo\t111"); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_TRUE(is_ok); + EXPECT_EQ((julong)111, x); + + x = bad; + fill_file(test_file, "foo\nbar 333\nfoo\t111"); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_TRUE(is_ok); + EXPECT_EQ((julong)111, x); + + x = bad; + fill_file(test_file, "foof 100\nfoo 133"); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_TRUE(is_ok); + EXPECT_EQ((julong)133, x); + + x = bad; + fill_file(test_file, "foo\t333\nfoot 999"); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_TRUE(is_ok); + EXPECT_EQ((julong)333, x); + + x = bad; + fill_file(test_file, "foo 1\nfoo car"); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + EXPECT_TRUE(is_ok); + EXPECT_EQ((julong)1, x); + + // Cleanup + delete_file(test_file); +} + +TEST(cgroupTest, read_number_null) { + TestController* null_path_controller = new TestController((char*)nullptr); + const char* test_file_path = "/not-used"; + constexpr julong bad = 0xBAD; + julong a = bad; + // null subsystem_path() case + bool is_ok = null_path_controller->read_number(test_file_path, &a); + EXPECT_FALSE(is_ok) << "Null subsystem path should be an error"; + EXPECT_EQ(bad, a) << "Expected untouched scan value"; +} + +TEST(cgroupTest, read_string_beyond_max_path) { + char larger_than_max[MAXPATHLEN + 1]; + for (int i = 0; i < (MAXPATHLEN); i++) { + larger_than_max[i] = 'A' + (i % 26); + } + larger_than_max[MAXPATHLEN] = '\0'; + TestController* too_large_path_controller = new TestController(larger_than_max); + const char* test_file_path = "/file-not-found"; + char foo[1024]; + foo[0] = '\0'; + bool is_ok = too_large_path_controller->read_string(test_file_path, foo, 1024); + EXPECT_FALSE(is_ok) << "Too long path should be an error"; + EXPECT_STREQ("", foo) << "Expected untouched scan value"; +} + +TEST(cgroupTest, read_number_file_not_exist) { + TestController* unknown_path_ctrl = new TestController((char*)"/do/not/exist"); + const char* test_file_path = "/file-not-found"; + constexpr julong bad = 0xBAD; + julong result = bad; + bool is_ok = unknown_path_ctrl->read_number(test_file_path, &result); + EXPECT_FALSE(is_ok) << "File not found should be an error"; + EXPECT_EQ(bad, result) << "Expected untouched scan value"; +} + +TEST(cgroupTest, read_numerical_key_value_null) { + TestController* null_path_controller = new TestController((char*)nullptr); + const char* test_file_path = "/not-used"; + const char* key = "something"; + constexpr julong bad = 0xBAD; + julong a = bad; + // null subsystem_path() case + bool is_ok = null_path_controller->read_numerical_key_value(test_file_path, key, &a); + EXPECT_FALSE(is_ok) << "Null subsystem path should be an error"; + EXPECT_EQ(bad, a) << "Expected untouched scan value"; +} + +TEST(cgroupTest, read_number_tests) { + char* test_file = temp_file("cgroups"); + const char* b = basename(test_file); + constexpr julong bad = 0xBAD; + EXPECT_TRUE(b != nullptr) << "basename was null"; + stringStream path; + path.print_raw(os::file_separator()); + path.print_raw(b); + const char* base_with_slash = path.as_string(true); + fill_file(test_file, "8888"); + + TestController* controller = new TestController((char*)os::get_temp_directory()); + julong foo = bad; + bool ok = controller->read_number(base_with_slash, &foo); + EXPECT_TRUE(ok) << "Number parsing should have been successful"; + EXPECT_EQ((julong)8888, foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")"; + + // Some interface files might have negative values, ensure we can read + // them and manually cast them as needed. + fill_file(test_file, "-1"); + foo = bad; + ok = controller->read_number(base_with_slash, &foo); + EXPECT_TRUE(ok) << "Number parsing should have been successful"; + EXPECT_EQ((jlong)-1, (jlong)foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")"; + + foo = bad; + fill_file(test_file, nullptr); + ok = controller->read_number(base_with_slash, &foo); + EXPECT_FALSE(ok) << "Empty file should have failed"; + EXPECT_EQ(bad, foo) << "foo was altered"; + + // Some interface files have numbers as well as the string + // 'max', which means unlimited. + jlong result = -10; + fill_file(test_file, "max\n"); + ok = controller->read_number_handle_max(base_with_slash, &result); + EXPECT_TRUE(ok) << "Number parsing for 'max' string should have been successful"; + EXPECT_EQ((jlong)-1, result) << "'max' means unlimited (-1)"; + + result = -10; + fill_file(test_file, "11114\n"); + ok = controller->read_number_handle_max(base_with_slash, &result); + EXPECT_TRUE(ok) << "Number parsing for should have been successful"; + EXPECT_EQ((jlong)11114, result) << "Incorrect result"; + + result = -10; + fill_file(test_file, "-51114\n"); + ok = controller->read_number_handle_max(base_with_slash, &result); + EXPECT_TRUE(ok) << "Number parsing for should have been successful"; + EXPECT_EQ((jlong)-51114, result) << "Incorrect result"; + + delete_file(test_file); +} + +TEST(cgroupTest, read_string_tests) { + char* test_file = temp_file("cgroups"); + const char* b = basename(test_file); + EXPECT_TRUE(b != nullptr) << "basename was null"; + stringStream path; + path.print_raw(os::file_separator()); + path.print_raw(b); + const char* base_with_slash = path.as_string(true); + fill_file(test_file, "foo-bar"); + + TestController* controller = new TestController((char*)os::get_temp_directory()); + char result[1024]; + bool ok = controller->read_string(base_with_slash, result, 1024); + EXPECT_TRUE(ok) << "String parsing should have been successful"; + EXPECT_STREQ("foo-bar", result); + + result[0] = '\0'; + fill_file(test_file, "1234"); + ok = controller->read_string(base_with_slash, result, 1024); + EXPECT_TRUE(ok) << "String parsing should have been successful"; + EXPECT_STREQ("1234", result); + + // values with a space + result[0] = '\0'; + fill_file(test_file, "abc def"); + ok = controller->read_string(base_with_slash, result, 1024); + EXPECT_TRUE(ok) << "String parsing should have been successful"; + EXPECT_STREQ("abc def", result); + + result[0] = '\0'; + fill_file(test_file, " \na"); + ok = controller->read_string(base_with_slash, result, 1024); + EXPECT_TRUE(ok) << "String parsing should have been successful"; + EXPECT_STREQ(" ", result); + + // only the first line are being returned + result[0] = '\0'; + fill_file(test_file, "test\nabc"); + ok = controller->read_string(base_with_slash, result, 1024); + EXPECT_TRUE(ok) << "String parsing should have been successful"; + EXPECT_STREQ("test", result); + + result[0] = '\0'; + fill_file(test_file, nullptr); + ok = controller->read_string(base_with_slash, result, 1024); + EXPECT_FALSE(ok) << "Empty file should have failed"; + EXPECT_STREQ("", result) << "Expected untouched result"; + delete_file(test_file); + + // File contents larger than 1K + // We only read in the first 1K - 1 bytes + const size_t large_len = 2 * 1024; + char too_large[large_len]; + for (size_t i = 0; i < large_len; i++) { + too_large[i] = 'A' + (i % 26); + } + too_large[large_len - 1] = '\0'; + result[0] = '\0'; + fill_file(test_file, too_large); + ok = controller->read_string(base_with_slash, result, 1024); + EXPECT_TRUE(ok) << "String parsing should have been successful"; + EXPECT_TRUE(1023 == strlen(result)) << "Expected only the first 1023 chars to be read in"; + EXPECT_EQ(0, strncmp(too_large, result, 1023)); + EXPECT_EQ(result[1023], '\0') << "The last character must be the null character"; +} + +TEST(cgroupTest, read_number_tuple_test) { + char* test_file = temp_file("cgroups"); + const char* b = basename(test_file); + EXPECT_TRUE(b != nullptr) << "basename was null"; + stringStream path; + path.print_raw(os::file_separator()); + path.print_raw(b); + const char* base_with_slash = path.as_string(true); + fill_file(test_file, "max 10000"); + + TestController* controller = new TestController((char*)os::get_temp_directory()); + jlong result = -10; + bool ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result); + EXPECT_TRUE(ok) << "Should be OK to read value"; + EXPECT_EQ((jlong)-1, result) << "max should be unlimited (-1)"; + + result = -10; + ok = controller->read_numerical_tuple_value(base_with_slash, false /* use_first */, &result); + EXPECT_TRUE(ok) << "Should be OK to read the value"; + EXPECT_EQ((jlong)10000, result); + + // non-max strings + fill_file(test_file, "abc 10000"); + result = -10; + ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result); + EXPECT_FALSE(ok) << "abc should not be parsable"; + EXPECT_EQ((jlong)-10, result) << "result value should be unchanged"; + + fill_file(test_file, nullptr); + result = -10; + ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result); + EXPECT_FALSE(ok) << "Empty file should be an error"; + EXPECT_EQ((jlong)-10, result) << "result value should be unchanged"; +} + +TEST(cgroupTest, read_numerical_key_beyond_max_path) { + char larger_than_max[MAXPATHLEN + 1]; + for (int i = 0; i < (MAXPATHLEN); i++) { + larger_than_max[i] = 'A' + (i % 26); + } + larger_than_max[MAXPATHLEN] = '\0'; + TestController* too_large_path_controller = new TestController(larger_than_max); + const char* test_file_path = "/file-not-found"; + const char* key = "something"; + julong a = 0xBAD; + bool is_ok = too_large_path_controller->read_numerical_key_value(test_file_path, key, &a); + EXPECT_FALSE(is_ok) << "Too long path should be an error"; + EXPECT_EQ((julong)0xBAD, a) << "Expected untouched scan value"; +} + +TEST(cgroupTest, read_numerical_key_file_not_exist) { + TestController* unknown_path_ctrl = new TestController((char*)"/do/not/exist"); + const char* test_file_path = "/file-not-found"; + const char* key = "something"; + julong a = 0xBAD; + bool is_ok = unknown_path_ctrl->read_numerical_key_value(test_file_path, key, &a); + EXPECT_FALSE(is_ok) << "File not found should be an error"; + EXPECT_EQ((julong)0xBAD, a) << "Expected untouched scan value"; +} + +TEST(cgroupTest, set_cgroupv1_subsystem_path) { + TestCase host = { + "/sys/fs/cgroup/memory", // mount_path + "/", // root_path + "/user.slice/user-1000.slice/user@1000.service", // cgroup_path + "/sys/fs/cgroup/memory/user.slice/user-1000.slice/user@1000.service" // expected_path + }; + TestCase container_engine = { + "/sys/fs/cgroup/mem", // mount_path + "/user.slice/user-1000.slice/user@1000.service", // root_path + "/user.slice/user-1000.slice/user@1000.service", // cgroup_path + "/sys/fs/cgroup/mem" // expected_path + }; + int length = 2; + TestCase* testCases[] = { &host, + &container_engine }; + for (int i = 0; i < length; i++) { + CgroupV1Controller* ctrl = new CgroupV1Controller( (char*)testCases[i]->root_path, + (char*)testCases[i]->mount_path, + true /* read-only mount */); + ctrl->set_subsystem_path((char*)testCases[i]->cgroup_path); + ASSERT_STREQ(testCases[i]->expected_path, ctrl->subsystem_path()); + } +} + +TEST(cgroupTest, set_cgroupv2_subsystem_path) { + TestCase at_mount_root = { + "/sys/fs/cgroup", // mount_path + nullptr, // root_path, ignored + "/", // cgroup_path + "/sys/fs/cgroup" // expected_path + }; + TestCase sub_path = { + "/sys/fs/cgroup", // mount_path + nullptr, // root_path, ignored + "/foobar", // cgroup_path + "/sys/fs/cgroup/foobar" // expected_path + }; + int length = 2; + TestCase* testCases[] = { &at_mount_root, + &sub_path }; + for (int i = 0; i < length; i++) { + CgroupV2Controller* ctrl = new CgroupV2Controller( (char*)testCases[i]->mount_path, + (char*)testCases[i]->cgroup_path, + true /* read-only mount */); + ASSERT_STREQ(testCases[i]->expected_path, ctrl->subsystem_path()); + } +} + +TEST(cgroupTest, cgroupv2_is_hierarchy_walk_needed) { + bool controller_read_only = false; // value irrelevant; + CgroupV2Controller* test = new CgroupV2Controller((char*)"/sys/fs/cgroup", + (char*)"/" /* cgroup_path */, + controller_read_only); + EXPECT_FALSE(test->needs_hierarchy_adjustment()); + test = new CgroupV2Controller((char*)"/sys/fs/cgroup", + (char*)"/bar" /* cgroup_path */, + controller_read_only); + EXPECT_TRUE(test->needs_hierarchy_adjustment()); + test = new CgroupV2Controller((char*)"/sys/fs/cgroup/b", + (char*)"/a/b" /* cgroup_path */, + controller_read_only); + EXPECT_TRUE(test->needs_hierarchy_adjustment()); + + CgroupCpuController* test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup", + (char*)"/" /* cgroup_path */, + controller_read_only)); + EXPECT_FALSE(test2->needs_hierarchy_adjustment()); + test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup", + (char*)"/bar" /* cgroup_path */, + controller_read_only)); + EXPECT_TRUE(test2->needs_hierarchy_adjustment()); + test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup/b", + (char*)"/a/b" /* cgroup_path */, + controller_read_only)); + EXPECT_TRUE(test2->needs_hierarchy_adjustment()); + + CgroupMemoryController* test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup", + (char*)"/" /* cgroup_path */, + controller_read_only)); + EXPECT_FALSE(test3->needs_hierarchy_adjustment()); + test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup", + (char*)"/bar" /* cgroup_path */, + controller_read_only)); + EXPECT_TRUE(test3->needs_hierarchy_adjustment()); + test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup/b", + (char*)"/a/b" /* cgroup_path */, + controller_read_only)); + EXPECT_TRUE(test3->needs_hierarchy_adjustment()); +} + +TEST(cgroupTest, cgroupv1_is_hierarchy_walk_needed) { + bool controller_read_only = true; // shouldn't matter; + CgroupV1Controller* test = new CgroupV1Controller((char*)"/a/b/c" /* root */, + (char*)"/sys/fs/cgroup/memory" /* mount_path */, + controller_read_only); + test->set_subsystem_path((char*)"/a/b/c"); + EXPECT_FALSE(test->needs_hierarchy_adjustment()); + test->set_subsystem_path((char*)"/"); + EXPECT_TRUE(test->needs_hierarchy_adjustment()); + test = new CgroupV1Controller((char*)"/a/b/c" /* root */, + (char*)"/"/* mount_path */, + controller_read_only); + test->set_subsystem_path((char*)"/"); + EXPECT_TRUE(test->needs_hierarchy_adjustment()); + + CgroupCpuController* test2 = new CgroupV1CpuController(CgroupV1Controller((char*)"/a/b/c" /* root */, + (char*)"/sys/fs/cgroup/memory" /* mount_path */, + controller_read_only)); + static_cast(test2)->set_subsystem_path((char*)"/a/b/c"); + EXPECT_FALSE(test2->needs_hierarchy_adjustment()); + static_cast(test2)->set_subsystem_path((char*)"/"); + EXPECT_TRUE(test2->needs_hierarchy_adjustment()); + test2 = new CgroupV1CpuController(CgroupV1Controller((char*)"/a/b/c" /* root */, + (char*)"/"/* mount_path */, + controller_read_only)); + static_cast(test2)->set_subsystem_path((char*)"/"); + EXPECT_TRUE(test2->needs_hierarchy_adjustment()); + + CgroupMemoryController* test3 = new CgroupV1MemoryController(CgroupV1Controller((char*)"/a/b/c" /* root */, + (char*)"/sys/fs/cgroup/memory" /* mount_path */, + controller_read_only)); + static_cast(test3)->set_subsystem_path((char*)"/a/b/c"); + EXPECT_FALSE(test3->needs_hierarchy_adjustment()); + static_cast(test3)->set_subsystem_path((char*)"/"); + EXPECT_TRUE(test3->needs_hierarchy_adjustment()); + test3 = new CgroupV1MemoryController(CgroupV1Controller((char*)"/a/b/c" /* root */, + (char*)"/"/* mount_path */, + controller_read_only)); + static_cast(test3)->set_subsystem_path((char*)"/"); + EXPECT_TRUE(test3->needs_hierarchy_adjustment()); +} + +#endif // LINUX diff --git a/test/hotspot/gtest/runtime/test_os_linux_cgroups.cpp b/test/hotspot/gtest/runtime/test_os_linux_cgroups.cpp deleted file mode 100644 index 21e0152a43d..00000000000 --- a/test/hotspot/gtest/runtime/test_os_linux_cgroups.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2022, 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 "precompiled.hpp" - -#ifdef LINUX - -#include "cgroupV1Subsystem_linux.hpp" -#include "cgroupV2Subsystem_linux.hpp" -#include "unittest.hpp" - -typedef struct { - const char* mount_path; - const char* root_path; - const char* cgroup_path; - const char* expected_path; -} TestCase; - -TEST(cgroupTest, set_cgroupv1_subsystem_path) { - TestCase host = { - "/sys/fs/cgroup/memory", // mount_path - "/", // root_path - "/user.slice/user-1000.slice/user@1000.service", // cgroup_path - "/sys/fs/cgroup/memory/user.slice/user-1000.slice/user@1000.service" // expected_path - }; - TestCase container_engine = { - "/sys/fs/cgroup/mem", // mount_path - "/user.slice/user-1000.slice/user@1000.service", // root_path - "/user.slice/user-1000.slice/user@1000.service", // cgroup_path - "/sys/fs/cgroup/mem" // expected_path - }; - int length = 2; - TestCase* testCases[] = { &host, - &container_engine }; - for (int i = 0; i < length; i++) { - CgroupV1Controller* ctrl = new CgroupV1Controller( (char*)testCases[i]->root_path, - (char*)testCases[i]->mount_path); - ctrl->set_subsystem_path((char*)testCases[i]->cgroup_path); - ASSERT_STREQ(testCases[i]->expected_path, ctrl->subsystem_path()); - } -} - -TEST(cgroupTest, set_cgroupv2_subsystem_path) { - TestCase at_mount_root = { - "/sys/fs/cgroup", // mount_path - nullptr, // root_path, ignored - "/", // cgroup_path - "/sys/fs/cgroup" // expected_path - }; - TestCase sub_path = { - "/sys/fs/cgroup", // mount_path - nullptr, // root_path, ignored - "/foobar", // cgroup_path - "/sys/fs/cgroup/foobar" // expected_path - }; - int length = 2; - TestCase* testCases[] = { &at_mount_root, - &sub_path }; - for (int i = 0; i < length; i++) { - CgroupV2Controller* ctrl = new CgroupV2Controller( (char*)testCases[i]->mount_path, - (char*)testCases[i]->cgroup_path); - ASSERT_STREQ(testCases[i]->expected_path, ctrl->subsystem_path()); - } -} - -#endif diff --git a/test/hotspot/jtreg/containers/cgroup/PlainRead.java b/test/hotspot/jtreg/containers/cgroup/TestContainerized.java similarity index 54% rename from test/hotspot/jtreg/containers/cgroup/PlainRead.java rename to test/hotspot/jtreg/containers/cgroup/TestContainerized.java index 21eccd79835..52cf5451a8d 100644 --- a/test/hotspot/jtreg/containers/cgroup/PlainRead.java +++ b/test/hotspot/jtreg/containers/cgroup/TestContainerized.java @@ -22,57 +22,27 @@ */ /* - * @test PlainRead + * @test + * @bug 8261242 * @key cgroups * @requires os.family == "linux" * @requires vm.flagless * @library /testlibrary /test/lib * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI PlainRead + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI TestContainerized */ import jdk.test.lib.process.ProcessTools; -import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.Platform; import jdk.test.whitebox.WhiteBox; -public class PlainRead { - - static public void match(OutputAnalyzer oa, String what, String value) { - oa.shouldMatch("^.*" + what + " *" + value + ".*$"); - } - - static public void noMatch(OutputAnalyzer oa, String what, String value) { - oa.shouldNotMatch("^.*" + what + " *" + value + ".*$"); - } - - static final String good_value = "(\\d+|-1|-2|Unlimited)"; - static final String bad_value = "(failed)"; - - static final String[] variables = {"Memory Limit is:", "CPU Quota is:", "CPU Period is:", "active_processor_count:"}; - - static public void isContainer(OutputAnalyzer oa) { - for (String v: variables) { - match(oa, v, good_value); - } - for (String v: variables) { - noMatch(oa, v, bad_value); - } - } - - static public void isNotContainer(OutputAnalyzer oa) { - oa.shouldMatch("^.*Can't open /proc/self/mountinfo.*$"); - } +public class TestContainerized { public static void main(String[] args) throws Exception { WhiteBox wb = WhiteBox.getWhiteBox(); - ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder("-Xlog:os+container=trace", "-version"); - OutputAnalyzer output = new OutputAnalyzer(pb.start()); - if (wb.isContainerized()) { - System.out.println("Inside a cgroup, testing..."); - isContainer(output); + throw new RuntimeException("Test failed! Expected not containerized on plain Linux."); } + System.out.println("Plain linux, no limits. Passed!"); } } diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java b/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java index 553ba692ee7..20354cf934d 100644 --- a/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java +++ b/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java @@ -200,8 +200,8 @@ private static void testMemorySwapLimitSanity() throws Exception { .shouldMatch("Memory Limit is:.*" + expectedTraceValue) // Either for cgroup v1: a_1) same as memory limit, or b_1) -2 on systems with swapaccount=0 // Either for cgroup v2: a_2) 0, or b_2) -2 on systems with swapaccount=0 - .shouldMatch("Memory and Swap Limit is:.*(" + expectedTraceValue + "|-2|0)") - .shouldNotMatch("Memory and Swap Limit is:.*" + neg2InUnsignedLong); + .shouldMatch("(Memory and )?Swap Limit is:.*(" + expectedTraceValue + "|-2|0)") + .shouldNotMatch("(Memory and )?Swap Limit is:.*" + neg2InUnsignedLong); } diff --git a/test/jdk/jdk/internal/platform/cgroup/TestSystemSettings.java b/test/jdk/jdk/internal/platform/cgroup/TestSystemSettings.java new file mode 100644 index 00000000000..8d9279e1603 --- /dev/null +++ b/test/jdk/jdk/internal/platform/cgroup/TestSystemSettings.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 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. + */ + +/* + * @test + * @key cgroups + * @requires os.family == "linux" + * @requires vm.flagless + * @library /test/lib + * @build TestSystemSettings + * @run main/othervm TestSystemSettings + */ + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +public class TestSystemSettings { + + public static void main(String[] args) throws Exception { + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder("-XshowSettings:system", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + + output.shouldContain("System not containerized."); + } +} diff --git a/test/lib/jdk/test/lib/containers/cgroup/MetricsTester.java b/test/lib/jdk/test/lib/containers/cgroup/MetricsTester.java index a6eff3d237a..0b29d288cd9 100644 --- a/test/lib/jdk/test/lib/containers/cgroup/MetricsTester.java +++ b/test/lib/jdk/test/lib/containers/cgroup/MetricsTester.java @@ -69,6 +69,16 @@ private void testAll(Metrics m, boolean inContainer) throws Exception { tester.testMemoryUsage(); } tester.testMisc(); + testContainerized(m, inContainer); + } + + private void testContainerized(Metrics m, boolean inContainer) { + if (m.isContainerized() != inContainer) { + throw new RuntimeException("containerized test failed. " + + "Expected isContainerized()==" + inContainer + + " but got '" + m.isContainerized() + "'"); + } + System.out.println("testContainerized() PASSED!"); } public static void main(String[] args) throws Exception {