From 6ce9ef3f05e9aeef93fa732287b9aad99371b9c5 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Wed, 29 May 2024 08:46:27 +0000 Subject: [PATCH] 8302744: Refactor Hotspot container detection code Reviewed-by: jsjolen, stuefe --- .../os/linux/cgroupSubsystem_linux.cpp | 149 +++++- .../os/linux/cgroupSubsystem_linux.hpp | 216 +++----- .../os/linux/cgroupV1Subsystem_linux.cpp | 141 +++--- .../os/linux/cgroupV1Subsystem_linux.hpp | 2 - .../os/linux/cgroupV2Subsystem_linux.cpp | 128 ++--- .../os/linux/cgroupV2Subsystem_linux.hpp | 8 +- .../os/linux/test_cgroupSubsystem_linux.cpp | 204 -------- .../runtime/test_cgroupSubsystem_linux.cpp | 464 ++++++++++++++++++ .../gtest/runtime/test_os_linux_cgroups.cpp | 86 ---- .../docker/TestMemoryAwareness.java | 4 +- 10 files changed, 827 insertions(+), 575 deletions(-) delete mode 100644 test/hotspot/gtest/os/linux/test_cgroupSubsystem_linux.cpp create mode 100644 test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp delete mode 100644 test/hotspot/gtest/runtime/test_os_linux_cgroups.cpp diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index 2a2d83ed748..a348ea424fb 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp @@ -558,21 +558,162 @@ jlong CgroupSubsystem::memory_limit_in_bytes() { 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"); + 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"); + 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; } diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp index 6c5470445f1..5888072bdf3 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp @@ -69,148 +69,91 @@ #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 { + public: + virtual char* subsystem_path() = 0; + + /* 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: @@ -255,7 +198,6 @@ 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; diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp index 307c3c53a0e..229e2e4237b 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp @@ -75,9 +75,9 @@ void CgroupV1Controller::set_subsystem_path(char *cgroup_path) { * OSCONTAINER_ERROR for not supported */ 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; + julong use_hierarchy; + CONTAINER_READ_NUMBER_CHECKED(this, "/memory.use_hierarchy", "Use Hierarchy", use_hierarchy); + return (jlong)use_hierarchy; } void CgroupV1MemoryController::set_subsystem_path(char *cgroup_path) { @@ -89,15 +89,20 @@ void CgroupV1MemoryController::set_subsystem_path(char *cgroup_path) { } 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); - + julong memlimit; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/memory.limit_in_bytes", "Memory Limit", 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) + julong hier_memlimit; + bool is_ok = _memory->controller()->read_numerical_key_value("/memory.stat", + "hierarchical_memory_limit", + &hier_memlimit); + if (!is_ok) { + return OSCONTAINER_ERROR; + } + log_trace(os, container)("Hierarchical Memory Limit is: " JULONG_FORMAT, hier_memlimit); if (hier_memlimit >= os::Linux::physical_memory()) { log_trace(os, container)("Hierarchical Memory Limit is: Unlimited"); } else { @@ -125,16 +130,22 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() { */ 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); + julong hier_memswlimit; + julong memswlimit; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/memory.memsw.limit_in_bytes", "Memory and Swap Limit", memswlimit); host_total_memsw = os::Linux::host_swap() + os::Linux::physical_memory(); 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) + bool is_ok = _memory->controller()->read_numerical_key_value("/memory.stat", + matchline, + &hier_memswlimit); + if (!is_ok) { + return OSCONTAINER_ERROR; + } + log_trace(os, container)("Hierarchical Memory and Swap Limit is: " JULONG_FORMAT, hier_memswlimit); if (hier_memswlimit >= host_total_memsw) { log_trace(os, container)("Hierarchical Memory and Swap Limit is: Unlimited"); } else { @@ -168,15 +179,22 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() { return memory_swap; } +static inline +jlong memory_swap_usage_impl(CgroupController* ctrl) { + julong memory_swap_usage; + CONTAINER_READ_NUMBER_CHECKED(ctrl, "/memory.memsw.usage_in_bytes", "mem swap usage", memory_swap_usage); + return (jlong)memory_swap_usage; +} + jlong CgroupV1Subsystem::read_mem_swappiness() { - GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.swappiness", - "Swappiness is: ", JULONG_FORMAT, JULONG_FORMAT, swappiness); - return swappiness; + julong swappiness; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/memory.swappiness", "Swappiness", swappiness); + return (jlong)swappiness; } 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); + julong memsoftlimit; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/memory.soft_limit_in_bytes", "Memory Soft Limit", memsoftlimit); if (memsoftlimit >= os::Linux::physical_memory()) { log_trace(os, container)("Memory Soft Limit is: Unlimited"); return (jlong)-1; @@ -195,9 +213,9 @@ jlong CgroupV1Subsystem::memory_soft_limit_in_bytes() { * 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; + julong memusage; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/memory.usage_in_bytes", "Memory Usage", memusage); + return (jlong)memusage; } /* memory_max_usage_in_bytes @@ -209,21 +227,20 @@ jlong CgroupV1Subsystem::memory_usage_in_bytes() { * 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; + julong memmaxusage; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/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; + julong kmem_usage; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/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); + julong kmem_limit; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/memory.kmem.limit_in_bytes", "Kernel Memory Limit", kmem_limit); if (kmem_limit >= os::Linux::physical_memory()) { return (jlong)-1; } @@ -231,9 +248,9 @@ jlong CgroupV1Subsystem::kernel_memory_limit_in_bytes() { } 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; + julong kmem_max_usage; + CONTAINER_READ_NUMBER_CHECKED(_memory->controller(), "/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) { @@ -246,15 +263,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); } @@ -269,15 +286,24 @@ char * CgroupV1Subsystem::cpu_cpuset_memory_nodes() { * 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; + julong quota; + bool is_ok = _cpu->controller()-> + 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; + julong period; + CONTAINER_READ_NUMBER_CHECKED(_cpu->controller(), "/cpu.cfs_period_us", "CPU Period", period); + return (int)period; } /* cpu_shares @@ -291,19 +317,13 @@ int CgroupV1Subsystem::cpu_period() { * OSCONTAINER_ERROR for not supported */ int CgroupV1Subsystem::cpu_shares() { - GET_CONTAINER_INFO(int, _cpu->controller(), "/cpu.shares", - "CPU Shares is: ", "%d", "%d", shares); + julong shares; + CONTAINER_READ_NUMBER_CHECKED(_cpu->controller(), "/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 +337,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 +352,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..c7535844bf5 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp @@ -111,8 +111,6 @@ class CgroupV1Subsystem: public CgroupSubsystem { CgroupV1Controller* _cpuacct = nullptr; CgroupV1Controller* _pids = nullptr; - char * pids_max_val(); - jlong read_mem_swappiness(); jlong read_mem_swap(); diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp index 1a02bbe95d2..d189dd6626e 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp @@ -35,10 +35,11 @@ * 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); + julong shares; + CONTAINER_READ_NUMBER_CHECKED(_unified, "/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 +51,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); @@ -83,33 +84,37 @@ int CgroupV2Subsystem::cpu_shares() { * 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); + jlong quota_val; + bool is_ok = _unified->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); +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_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); -} - -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_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); + jlong period_val; + bool is_ok = _unified->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; } @@ -123,14 +128,15 @@ int CgroupV2Subsystem::cpu_period() { * 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; + julong memusage; + CONTAINER_READ_NUMBER_CHECKED(_unified, "/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 mem_soft_limit; + CONTAINER_READ_NUMBER_CHECKED_MAX(_unified, "/memory.low", "Memory Soft Limit", mem_soft_limit); + return mem_soft_limit; } jlong CgroupV2Subsystem::memory_max_usage_in_bytes() { @@ -139,26 +145,21 @@ jlong CgroupV2Subsystem::memory_max_usage_in_bytes() { 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 swap_limit; + bool is_ok = _memory->controller()->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(); } - 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(); assert(memory_limit >= 0, "swap limit without memory limit?"); @@ -168,17 +169,17 @@ 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); +jlong CgroupV2Subsystem::mem_swp_limit_val() { + jlong swap_limit; + CONTAINER_READ_NUMBER_CHECKED_MAX(_unified, "/memory.swap.max", "Swap Limit", swap_limit); + return swap_limit; } // 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); +jlong CgroupV2Subsystem::mem_swp_current_val() { + julong swap_current; + CONTAINER_READ_NUMBER_CHECKED(_unified, "/memory.swap.current", "Swap currently used", swap_current); + return (jlong)swap_current; } /* memory_limit_in_bytes @@ -190,30 +191,14 @@ char* CgroupV2Subsystem::mem_swp_current_val() { * -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); - if (log_is_enabled(Trace, os, container)) { - if (limit == -1) { - log_trace(os, container)("Memory Limit is: Unlimited"); - } else { - log_trace(os, container)("Memory Limit is: " JLONG_FORMAT, limit); - } - } - 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); + jlong memory_limit; + CONTAINER_READ_NUMBER_CHECKED_MAX(_unified, "/memory.max", "Memory Limit", memory_limit); + return memory_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); - - char* mem_swp_limit_str = mem_swp_limit_val(); - jlong swap_limit = limit_from_str(mem_swp_limit_str); + jlong swap_current = mem_swp_current_val(); + jlong swap_limit = mem_swp_limit_val(); 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"); @@ -228,12 +213,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 +223,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 +237,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..18d4a50ff0b 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp @@ -56,12 +56,8 @@ class CgroupV2Subsystem: public CgroupSubsystem { 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(); + jlong mem_swp_limit_val(); + jlong mem_swp_current_val(); public: CgroupV2Subsystem(CgroupController * unified) { 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..cc326dbb502 --- /dev/null +++ b/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp @@ -0,0 +1,464 @@ +/* + * 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 + +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; +} + +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) {} + char* subsystem_path() override { + return _path; + }; +}; + +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) { + const 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) { + const 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) { + const 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) { + const 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) { + const 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); + 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 // 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/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); }