From d58b668227b70ea94326a40dc3a5f7af194cf0cf Mon Sep 17 00:00:00 2001 From: Saint Wesonga Date: Mon, 8 Jul 2024 11:23:54 -0600 Subject: [PATCH] Backport 6942632: Hotspot should be able to use more than 64 logical processors on Windows (#22) * 6942632: Hotspot should be able to use more than 64 logical processors on Windows Reviewed-by: jsjolen, dholmes * Include versionhelpers header * Create JDK-6942632 ms-patches metadata file --- make/test/JtregNativeHotspot.gmk | 2 + ...e-all-windows-processor-groups-6942632.yml | 10 + src/hotspot/os/windows/globals_windows.hpp | 3 + src/hotspot/os/windows/os_windows.cpp | 368 ++++++++++++++---- src/hotspot/os/windows/os_windows.hpp | 40 ++ .../hotspot/gtest/runtime/test_os_windows.cpp | 19 + test/hotspot/jtreg/TEST.groups | 1 + .../jtreg/gtest/WindowsProcessorGroups.java | 34 ++ .../os/windows/GetAvailableProcessors.java | 29 ++ .../os/windows/TestAvailableProcessors.java | 218 +++++++++++ .../runtime/os/windows/exeGetProcessorInfo.c | 77 ++++ 11 files changed, 733 insertions(+), 68 deletions(-) create mode 100644 ms-patches/use-all-windows-processor-groups-6942632.yml create mode 100644 test/hotspot/jtreg/gtest/WindowsProcessorGroups.java create mode 100644 test/hotspot/jtreg/runtime/os/windows/GetAvailableProcessors.java create mode 100644 test/hotspot/jtreg/runtime/os/windows/TestAvailableProcessors.java create mode 100644 test/hotspot/jtreg/runtime/os/windows/exeGetProcessorInfo.c diff --git a/make/test/JtregNativeHotspot.gmk b/make/test/JtregNativeHotspot.gmk index 92af1a8acc9..b5bf5fc06bf 100644 --- a/make/test/JtregNativeHotspot.gmk +++ b/make/test/JtregNativeHotspot.gmk @@ -1511,6 +1511,8 @@ else BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libterminatedThread += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit += -ljvm BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -lpthread + + BUILD_HOTSPOT_JTREG_EXCLUDE += exeGetProcessorInfo.c endif # This evaluation is expensive and should only be done if this target was diff --git a/ms-patches/use-all-windows-processor-groups-6942632.yml b/ms-patches/use-all-windows-processor-groups-6942632.yml new file mode 100644 index 00000000000..b0bd4e4cc7c --- /dev/null +++ b/ms-patches/use-all-windows-processor-groups-6942632.yml @@ -0,0 +1,10 @@ +title: Hotspot should be able to use more than 64 logical processors on Windows +- work_item: 2114222 +- jbs_bug: JDK-6942632 +- author: swesonga +- owner: swesonga +- contributors: +- details: + - Backport of https://github.com/openjdk/jdk/pull/17576. +- release_note: + - Enables Hotspot to use processors across all Windows processor groups on Windows 11/Windows Server 2022 and later. \ No newline at end of file diff --git a/src/hotspot/os/windows/globals_windows.hpp b/src/hotspot/os/windows/globals_windows.hpp index 7ddf3c9131b..f0cf567254d 100644 --- a/src/hotspot/os/windows/globals_windows.hpp +++ b/src/hotspot/os/windows/globals_windows.hpp @@ -36,6 +36,9 @@ range, \ constraint) \ \ +product(bool, UseAllWindowsProcessorGroups, false, \ + "Use all processor groups on supported Windows versions") \ + \ product(bool, UseOSErrorReporting, false, \ "Let VM fatal error propagate to the OS (ie. WER on Windows)") diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index c4b82d4f6a1..5b3840920ad 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -73,6 +73,7 @@ #include "utilities/defaultStream.hpp" #include "utilities/events.hpp" #include "utilities/macros.hpp" +#include "utilities/population_count.hpp" #include "utilities/vmError.hpp" #include "symbolengine.hpp" #include "windbghelp.hpp" @@ -102,6 +103,7 @@ #include #include #include +#include // for timer info max values which include all bits #define ALL_64_BITS CONST64(-1) @@ -877,21 +879,119 @@ int os::active_processor_count() { return ActiveProcessorCount; } - DWORD_PTR lpProcessAffinityMask = 0; - DWORD_PTR lpSystemAffinityMask = 0; - int proc_count = processor_count(); - if (proc_count <= sizeof(UINT_PTR) * BitsPerByte && - GetProcessAffinityMask(GetCurrentProcess(), &lpProcessAffinityMask, &lpSystemAffinityMask)) { - // Nof active processors is number of bits in process affinity mask - int bitcount = 0; - while (lpProcessAffinityMask != 0) { - lpProcessAffinityMask = lpProcessAffinityMask & (lpProcessAffinityMask-1); - bitcount++; + bool schedules_all_processor_groups = win32::is_windows_11_or_greater() || win32::is_windows_server_2022_or_greater(); + if (UseAllWindowsProcessorGroups && !schedules_all_processor_groups && !win32::processor_group_warning_displayed()) { + win32::set_processor_group_warning_displayed(true); + FLAG_SET_DEFAULT(UseAllWindowsProcessorGroups, false); + warning("The UseAllWindowsProcessorGroups flag is not supported on this Windows version and will be ignored."); + } + + DWORD active_processor_groups = 0; + DWORD processors_in_job_object = win32::active_processors_in_job_object(&active_processor_groups); + + if (processors_in_job_object > 0) { + if (schedules_all_processor_groups) { + // If UseAllWindowsProcessorGroups is enabled then all the processors in the job object + // can be used. Otherwise, we will fall through to inspecting the process affinity mask. + // This will result in using only the subset of the processors in the default processor + // group allowed by the job object i.e. only 1 processor group will be used and only + // the processors in that group that are allowed by the job object will be used. + // This preserves the behavior where older OpenJDK versions always used one processor + // group regardless of whether they were launched in a job object. + if (!UseAllWindowsProcessorGroups && active_processor_groups > 1) { + if (!win32::job_object_processor_group_warning_displayed()) { + win32::set_job_object_processor_group_warning_displayed(true); + warning("The Windows job object has enabled multiple processor groups (%d) but the UseAllWindowsProcessorGroups flag is off. Some processors might not be used.", active_processor_groups); + } + } else { + return processors_in_job_object; + } + } else { + if (active_processor_groups > 1 && !win32::job_object_processor_group_warning_displayed()) { + win32::set_job_object_processor_group_warning_displayed(true); + warning("The Windows job object has enabled multiple processor groups (%d) but only 1 is supported on this Windows version. Some processors might not be used.", active_processor_groups); + } + return processors_in_job_object; + } + } + + DWORD logical_processors = 0; + SYSTEM_INFO si; + GetSystemInfo(&si); + + USHORT group_count = 0; + bool use_process_affinity_mask = false; + bool got_process_group_affinity = false; + + if (GetProcessGroupAffinity(GetCurrentProcess(), &group_count, nullptr) == 0) { + DWORD last_error = GetLastError(); + if (last_error == ERROR_INSUFFICIENT_BUFFER) { + if (group_count > 0) { + got_process_group_affinity = true; + + if (group_count == 1) { + use_process_affinity_mask = true; + } + } else { + warning("Unexpected group count of 0 from GetProcessGroupAffinity."); + assert(false, "Group count must not be 0."); + } + } else { + char buf[512]; + size_t buf_len = os::lasterror(buf, sizeof(buf)); + warning("Attempt to get process group affinity failed: %s", buf_len != 0 ? buf : ""); } - return bitcount; } else { - return proc_count; + warning("Unexpected GetProcessGroupAffinity success result."); + assert(false, "Unexpected GetProcessGroupAffinity success result"); + } + + // Fall back to SYSTEM_INFO.dwNumberOfProcessors if the process group affinity could not be determined. + if (!got_process_group_affinity) { + return si.dwNumberOfProcessors; } + + // If the process it not in a job and the process group affinity is exactly 1 group + // then get the number of available logical processors from the process affinity mask + if (use_process_affinity_mask) { + DWORD_PTR lpProcessAffinityMask = 0; + DWORD_PTR lpSystemAffinityMask = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &lpProcessAffinityMask, &lpSystemAffinityMask) != 0) { + // Number of active processors is number of bits in process affinity mask + logical_processors = population_count(lpProcessAffinityMask); + + if (logical_processors > 0) { + return logical_processors; + } else { + // We only check the process affinity mask if GetProcessGroupAffinity determined that there was + // only 1 active group. In this case, GetProcessAffinityMask will not set the affinity mask to 0. + warning("Unexpected process affinity mask of 0 from GetProcessAffinityMask."); + assert(false, "Found unexpected process affinity mask: 0"); + } + } else { + char buf[512]; + size_t buf_len = os::lasterror(buf, sizeof(buf)); + warning("Attempt to get the process affinity mask failed: %s", buf_len != 0 ? buf : ""); + } + + // Fall back to SYSTEM_INFO.dwNumberOfProcessors if the process affinity mask could not be determined. + return si.dwNumberOfProcessors; + } + + if (UseAllWindowsProcessorGroups) { + // There are no processor affinity restrictions at this point so we can return + // the overall processor count if the OS automatically schedules threads across + // all processors on the system. Note that older operating systems can + // correctly report processor count but will not schedule threads across + // processor groups unless the application explicitly uses group affinity APIs + // to assign threads to processor groups. On these older operating systems, we + // will continue to use the dwNumberOfProcessors field. + if (schedules_all_processor_groups) { + logical_processors = processor_count(); + } + } + + return logical_processors == 0 ? si.dwNumberOfProcessors : logical_processors; } uint os::processor_id() { @@ -1793,62 +1893,13 @@ void os::print_os_info(outputStream* st) { } void os::win32::print_windows_version(outputStream* st) { - OSVERSIONINFOEX osvi; - VS_FIXEDFILEINFO *file_info; - TCHAR kernel32_path[MAX_PATH]; - UINT len, ret; - - // Use the GetVersionEx information to see if we're on a server or - // workstation edition of Windows. Starting with Windows 8.1 we can't - // trust the OS version information returned by this API. - ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - if (!GetVersionEx((OSVERSIONINFO *)&osvi)) { - st->print_cr("Call to GetVersionEx failed"); - return; - } - bool is_workstation = (osvi.wProductType == VER_NT_WORKSTATION); + bool is_workstation = !IsWindowsServer(); - // Get the full path to \Windows\System32\kernel32.dll and use that for - // determining what version of Windows we're running on. - len = MAX_PATH - (UINT)strlen("\\kernel32.dll") - 1; - ret = GetSystemDirectory(kernel32_path, len); - if (ret == 0 || ret > len) { - st->print_cr("Call to GetSystemDirectory failed"); - return; - } - strncat(kernel32_path, "\\kernel32.dll", MAX_PATH - ret); - - DWORD version_size = GetFileVersionInfoSize(kernel32_path, NULL); - if (version_size == 0) { - st->print_cr("Call to GetFileVersionInfoSize failed"); - return; - } - - LPTSTR version_info = (LPTSTR)os::malloc(version_size, mtInternal); - if (version_info == NULL) { - st->print_cr("Failed to allocate version_info"); - return; - } - - if (!GetFileVersionInfo(kernel32_path, NULL, version_size, version_info)) { - os::free(version_info); - st->print_cr("Call to GetFileVersionInfo failed"); - return; - } - - if (!VerQueryValue(version_info, TEXT("\\"), (LPVOID*)&file_info, &len)) { - os::free(version_info); - st->print_cr("Call to VerQueryValue failed"); - return; - } - - int major_version = HIWORD(file_info->dwProductVersionMS); - int minor_version = LOWORD(file_info->dwProductVersionMS); - int build_number = HIWORD(file_info->dwProductVersionLS); - int build_minor = LOWORD(file_info->dwProductVersionLS); + int major_version = windows_major_version(); + int minor_version = windows_minor_version(); + int build_number = windows_build_number(); + int build_minor = windows_build_minor(); int os_vers = major_version * 1000 + minor_version; - os::free(version_info); st->print(" Windows "); switch (os_vers) { @@ -1944,6 +1995,12 @@ void os::pd_print_cpu_info(outputStream* st, char* buf, size_t buflen) { if (proc_count < 1) { SYSTEM_INFO si; GetSystemInfo(&si); + + // This is the number of logical processors in the current processor group only and is therefore + // at most 64. The GetLogicalProcessorInformation function is used to compute the total number + // of processors. However, it requires memory to be allocated for the processor information buffer. + // Since this method is used in paths where memory allocation should not be done (i.e. after a crash), + // only the number of processors in the current group will be returned. proc_count = si.dwNumberOfProcessors; } @@ -1973,7 +2030,7 @@ void os::pd_print_cpu_info(outputStream* st, char* buf, size_t buflen) { } if (same_vals_for_all_cpus && max_mhz != -1) { - st->print_cr("Processor Information for all %d processors :", proc_count); + st->print_cr("Processor Information for the first %d processors :", proc_count); st->print_cr(" Max Mhz: %d, Current Mhz: %d, Mhz Limit: %d", max_mhz, current_mhz, mhz_limit); return; } @@ -3975,6 +4032,161 @@ bool os::win32::_is_windows_server = false; // including the latest one (as of this writing - Windows Server 2012 R2) bool os::win32::_has_exit_bug = true; +int os::win32::_major_version = 0; +int os::win32::_minor_version = 0; +int os::win32::_build_number = 0; +int os::win32::_build_minor = 0; + +bool os::win32::_processor_group_warning_displayed = false; +bool os::win32::_job_object_processor_group_warning_displayed = false; + +void os::win32::initialize_windows_version() { + assert(_major_version == 0, "windows version already initialized."); + + VS_FIXEDFILEINFO *file_info; + TCHAR kernel32_path[MAX_PATH]; + UINT len, ret; + char error_msg_buffer[512]; + + // Get the full path to \Windows\System32\kernel32.dll and use that for + // determining what version of Windows we're running on. + len = MAX_PATH - (UINT)strlen("\\kernel32.dll") - 1; + ret = GetSystemDirectory(kernel32_path, len); + if (ret == 0 || ret > len) { + size_t buf_len = os::lasterror(error_msg_buffer, sizeof(error_msg_buffer)); + warning("Attempt to determine system directory failed: %s", buf_len != 0 ? error_msg_buffer : ""); + return; + } + strncat(kernel32_path, "\\kernel32.dll", MAX_PATH - ret); + + DWORD version_size = GetFileVersionInfoSize(kernel32_path, nullptr); + if (version_size == 0) { + size_t buf_len = os::lasterror(error_msg_buffer, sizeof(error_msg_buffer)); + warning("Failed to determine whether the OS can retrieve version information from kernel32.dll: %s", buf_len != 0 ? error_msg_buffer : ""); + return; + } + + LPTSTR version_info = (LPTSTR)os::malloc(version_size, mtInternal); + if (version_info == nullptr) { + warning("os::malloc() failed to allocate %ld bytes for GetFileVersionInfo buffer", version_size); + return; + } + + if (GetFileVersionInfo(kernel32_path, 0, version_size, version_info) == 0) { + os::free(version_info); + size_t buf_len = os::lasterror(error_msg_buffer, sizeof(error_msg_buffer)); + warning("Attempt to retrieve version information from kernel32.dll failed: %s", buf_len != 0 ? error_msg_buffer : ""); + return; + } + + if (VerQueryValue(version_info, TEXT("\\"), (LPVOID*)&file_info, &len) == 0) { + os::free(version_info); + size_t buf_len = os::lasterror(error_msg_buffer, sizeof(error_msg_buffer)); + warning("Attempt to determine Windows version from kernel32.dll failed: %s", buf_len != 0 ? error_msg_buffer : ""); + return; + } + + _major_version = HIWORD(file_info->dwProductVersionMS); + _minor_version = LOWORD(file_info->dwProductVersionMS); + _build_number = HIWORD(file_info->dwProductVersionLS); + _build_minor = LOWORD(file_info->dwProductVersionLS); + + os::free(version_info); +} + +bool os::win32::is_windows_11_or_greater() { + if (IsWindowsServer()) { + return false; + } + + // Windows 11 starts at build 22000 (Version 21H2) + return (windows_major_version() == 10 && windows_build_number() >= 22000) || (windows_major_version() > 10); +} + +bool os::win32::is_windows_server_2022_or_greater() { + if (!IsWindowsServer()) { + return false; + } + + // Windows Server 2022 starts at build 20348.169 + return (windows_major_version() == 10 && windows_build_number() >= 20348) || (windows_major_version() > 10); +} + +DWORD os::win32::active_processors_in_job_object(DWORD* active_processor_groups) { + if (active_processor_groups != nullptr) { + *active_processor_groups = 0; + } + BOOL is_in_job_object = false; + if (IsProcessInJob(GetCurrentProcess(), nullptr, &is_in_job_object) == 0) { + char buf[512]; + size_t buf_len = os::lasterror(buf, sizeof(buf)); + warning("Attempt to determine whether the process is running in a job failed: %s", buf_len != 0 ? buf : ""); + return 0; + } + + if (!is_in_job_object) { + return 0; + } + + DWORD processors = 0; + + LPVOID job_object_information = nullptr; + DWORD job_object_information_length = 0; + + if (QueryInformationJobObject(nullptr, JobObjectGroupInformationEx, nullptr, 0, &job_object_information_length) != 0) { + warning("Unexpected QueryInformationJobObject success result."); + assert(false, "Unexpected QueryInformationJobObject success result"); + return 0; + } + + DWORD last_error = GetLastError(); + if (last_error == ERROR_INSUFFICIENT_BUFFER) { + DWORD group_count = job_object_information_length / sizeof(GROUP_AFFINITY); + + job_object_information = os::malloc(job_object_information_length, mtInternal); + if (job_object_information != nullptr) { + if (QueryInformationJobObject(nullptr, JobObjectGroupInformationEx, job_object_information, job_object_information_length, &job_object_information_length) != 0) { + DWORD groups_found = job_object_information_length / sizeof(GROUP_AFFINITY); + if (groups_found != group_count) { + warning("Unexpected processor group count: %ld. Expected %ld processor groups.", groups_found, group_count); + assert(false, "Unexpected group count"); + } + + GROUP_AFFINITY* group_affinity_data = ((GROUP_AFFINITY*)job_object_information); + for (DWORD i = 0; i < groups_found; i++, group_affinity_data++) { + DWORD processors_in_group = population_count(group_affinity_data->Mask); + processors += processors_in_group; + if (active_processor_groups != nullptr && processors_in_group > 0) { + (*active_processor_groups)++; + } + } + + if (processors == 0) { + warning("Could not determine processor count from the job object."); + assert(false, "Must find at least 1 logical processor"); + } + } else { + char buf[512]; + size_t buf_len = os::lasterror(buf, sizeof(buf)); + warning("Attempt to query job object information failed: %s", buf_len != 0 ? buf : ""); + } + + os::free(job_object_information); + } else { + warning("os::malloc() failed to allocate %ld bytes for QueryInformationJobObject", job_object_information_length); + } + } else { + char buf[512]; + size_t buf_len = os::lasterror(buf, sizeof(buf)); + warning("Attempt to query job object information failed: %s", buf_len != 0 ? buf : ""); + assert(false, "Unexpected QueryInformationJobObject error code"); + return 0; + } + + log_debug(os)("Process is running in a job with %d active processors.", processors); + return processors; +} + void os::win32::initialize_system_info() { SYSTEM_INFO si; GetSystemInfo(&si); @@ -3982,7 +4194,20 @@ void os::win32::initialize_system_info() { _vm_allocation_granularity = si.dwAllocationGranularity; _processor_type = si.dwProcessorType; _processor_level = si.wProcessorLevel; - set_processor_count(si.dwNumberOfProcessors); + + DWORD processors = 0; + bool schedules_all_processor_groups = win32::is_windows_11_or_greater() || win32::is_windows_server_2022_or_greater(); + if (schedules_all_processor_groups) { + processors = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); + if (processors == 0) { + char buf[512]; + size_t buf_len = os::lasterror(buf, sizeof(buf)); + warning("Attempt to determine the processor count from GetActiveProcessorCount() failed: %s", buf_len != 0 ? buf : ""); + assert(false, "Must find at least 1 logical processor"); + } + } + + set_processor_count(processors > 0 ? processors : si.dwNumberOfProcessors); MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); @@ -4310,6 +4535,7 @@ void nx_check_protection() { void os::init(void) { _initial_pid = _getpid(); + win32::initialize_windows_version(); win32::initialize_system_info(); win32::setmode_streams(); _page_sizes.add(win32::vm_page_size()); @@ -4341,6 +4567,12 @@ static jint initSock(); // this is called _after_ the global arguments have been parsed jint os::init_2(void) { + const char* auto_schedules_message = "Host Windows OS automatically schedules threads across all processor groups."; + const char* no_auto_schedules_message = "Host Windows OS does not automatically schedule threads across all processor groups."; + + bool schedules_all_processor_groups = win32::is_windows_11_or_greater() || win32::is_windows_server_2022_or_greater(); + log_debug(os)(schedules_all_processor_groups ? auto_schedules_message : no_auto_schedules_message); + log_debug(os)("%d logical processors found.", processor_count()); // This could be set any time but all platforms // have to set it the same so we have to mirror Solaris. diff --git a/src/hotspot/os/windows/os_windows.hpp b/src/hotspot/os/windows/os_windows.hpp index a195af2c6d1..6a9e9dfe21f 100644 --- a/src/hotspot/os/windows/os_windows.hpp +++ b/src/hotspot/os/windows/os_windows.hpp @@ -53,6 +53,13 @@ class win32 { static size_t _default_stack_size; static bool _is_windows_server; static bool _has_exit_bug; + static bool _processor_group_warning_displayed; + static bool _job_object_processor_group_warning_displayed; + + static int _major_version; + static int _minor_version; + static int _build_number; + static int _build_minor; static void print_windows_version(outputStream* st); static void print_uptime_info(outputStream* st); @@ -61,6 +68,37 @@ class win32 { // Windows-specific interface: static void initialize_system_info(); static void setmode_streams(); + static bool is_windows_11_or_greater(); + static bool is_windows_server_2022_or_greater(); + static int windows_major_version() { + assert(_major_version > 0, "windows version not initialized."); + return _major_version; + } + static int windows_minor_version() { + assert(_major_version > 0, "windows version not initialized."); + return _minor_version; + } + static int windows_build_number() { + assert(_major_version > 0, "windows version not initialized."); + return _build_number; + } + static int windows_build_minor() { + assert(_major_version > 0, "windows version not initialized."); + return _build_minor; + } + + static void set_processor_group_warning_displayed(bool displayed) { + _processor_group_warning_displayed = displayed; + } + static bool processor_group_warning_displayed() { + return _processor_group_warning_displayed; + } + static void set_job_object_processor_group_warning_displayed(bool displayed) { + _job_object_processor_group_warning_displayed = displayed; + } + static bool job_object_processor_group_warning_displayed() { + return _job_object_processor_group_warning_displayed; + } // Processor info as provided by NT static int processor_type() { return _processor_type; } @@ -79,6 +117,8 @@ class win32 { static int exit_process_or_thread(Ept what, int exit_code); static void initialize_performance_counter(); + static void initialize_windows_version(); + static DWORD active_processors_in_job_object(DWORD* active_processor_groups = nullptr); public: // Generic interface: diff --git a/test/hotspot/gtest/runtime/test_os_windows.cpp b/test/hotspot/gtest/runtime/test_os_windows.cpp index 0308857989c..b99cdec9ab9 100644 --- a/test/hotspot/gtest/runtime/test_os_windows.cpp +++ b/test/hotspot/gtest/runtime/test_os_windows.cpp @@ -703,6 +703,25 @@ TEST_VM(os_windows, reserve_memory_special) { TestReserveMemorySpecial_test(); } +TEST_VM(os_windows, processor_count) { + JVMFlag* flag = JVMFlag::find_flag("UseAllWindowsProcessorGroups"); + EXPECT_NE(flag, nullptr) << "Expected UseAllWindowsProcessorGroups product flag to be available"; + + int processors = os::processor_count(); + EXPECT_GT(processors, 0) << "Expected at least 1 processor"; + + int active_processors = os::active_processor_count(); + EXPECT_GT(active_processors, 0) << "Expected at least 1 active processor"; + + bool schedules_all_processor_groups = os::win32::is_windows_11_or_greater() || os::win32::is_windows_server_2022_or_greater(); + if (schedules_all_processor_groups && UseAllWindowsProcessorGroups) { + EXPECT_EQ(active_processors, processors) << "Expected all processors to be active"; + } else { + // active_processors should be at most the number of processors in 1 Windows processor group. + EXPECT_LE(active_processors, processors) << "Expected active processors to not exceed available processors"; + } +} + class ReserveMemorySpecialRunnable : public TestRunnable { public: void runUnitTest() const { diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 8a5f7a44919..2505a582fd7 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -122,6 +122,7 @@ tier1_common = \ gtest/MetaspaceGtests.java \ gtest/LargePageGtests.java \ gtest/NMTGtests.java \ + gtest/WindowsProcessorGroups.java tier1_compiler = \ :tier1_compiler_1 \ diff --git a/test/hotspot/jtreg/gtest/WindowsProcessorGroups.java b/test/hotspot/jtreg/gtest/WindowsProcessorGroups.java new file mode 100644 index 00000000000..73298bcc023 --- /dev/null +++ b/test/hotspot/jtreg/gtest/WindowsProcessorGroups.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 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. + * + */ + +/* + * This runs the os related gtests on Windows with all processor groups enabled. + */ + +/* @test id=use-all-windows-processor-groups + * @summary Run gtests with all Windows processor groups enabled + * @library /test/lib + * @requires os.family == "windows" + * @run main/native GTestWrapper --gtest_filter=os* -XX:+UseAllWindowsProcessorGroups + */ diff --git a/test/hotspot/jtreg/runtime/os/windows/GetAvailableProcessors.java b/test/hotspot/jtreg/runtime/os/windows/GetAvailableProcessors.java new file mode 100644 index 00000000000..ee7d7c2f42f --- /dev/null +++ b/test/hotspot/jtreg/runtime/os/windows/GetAvailableProcessors.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 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. + * + */ + +public class GetAvailableProcessors { + public static void main(String[] args) { + System.out.println("Runtime.availableProcessors: " + Runtime.getRuntime().availableProcessors()); + } +} diff --git a/test/hotspot/jtreg/runtime/os/windows/TestAvailableProcessors.java b/test/hotspot/jtreg/runtime/os/windows/TestAvailableProcessors.java new file mode 100644 index 00000000000..f7dc237d07d --- /dev/null +++ b/test/hotspot/jtreg/runtime/os/windows/TestAvailableProcessors.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 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. + * + */ + +/* + * @test + * @bug 6942632 + * @requires os.family == "windows" + * @summary This test verifies that OpenJDK can use all available + * processors on Windows 11/Windows Server 2022 and later. + * @requires vm.flagless + * @library /test/lib + * @compile GetAvailableProcessors.java + * @run testng TestAvailableProcessors + */ + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.HashSet; +import java.util.Set; + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TestAvailableProcessors { + + private static final String totalProcessorCountMessage = "Active processor count across all processor groups: "; + private static final String processorCountPerGroupMessage = "Active processors per group: "; + private static final String isWindowsServerMessage = "IsWindowsServer: "; + + private static final String runtimeAvailableProcessorsMessage = "Runtime.availableProcessors: "; + private static final String osVersionMessage = "OS Version: "; + private static final String unsupportedPlatformMessage = "The UseAllWindowsProcessorGroups flag is not supported on this Windows version and will be ignored."; + + private static String getWindowsVersion() throws IOException { + String systeminfoPath = "systeminfo.exe"; + + var processBuilder = new ProcessBuilder(systeminfoPath); + OutputAnalyzer outputAnalyzer = new OutputAnalyzer(processBuilder.start()); + outputAnalyzer.shouldHaveExitValue(0); + outputAnalyzer.shouldContain(osVersionMessage); + List lines = outputAnalyzer.stdoutAsLines(); + + String osVersion = null; + for (var line: lines) { + if (line.startsWith(osVersionMessage)) { + osVersion = line.substring(osVersionMessage.length()).trim(); + break; + } + } + + System.out.println("Found OS version: " + osVersion); + return osVersion; + } + + private static boolean getSchedulesAllProcessorGroups(boolean isWindowsServer) throws IOException { + String windowsVer = getWindowsVersion(); + String[] parts = windowsVer.split(" "); + String[] versionParts = parts[0].split("\\."); + + if (versionParts.length != 3) { + throw new RuntimeException("Unexpected Windows version format."); + } + + int major = Integer.parseInt(versionParts[0]); + int minor = Integer.parseInt(versionParts[1]); + int build = Integer.parseInt(versionParts[2]); + + if (major > 10) { + return true; + } + + if (major < 10) { + return false; + } + + if (minor > 0) { + return true; + } + + if (isWindowsServer) { + return build >= 20348; + } else { + return build >= 22000; + } + } + + private static OutputAnalyzer getAvailableProcessorsOutput(boolean productFlagEnabled) throws IOException { + String productFlag = productFlagEnabled ? "-XX:+UseAllWindowsProcessorGroups" : "-XX:-UseAllWindowsProcessorGroups"; + + ProcessBuilder processBuilder = ProcessTools.createLimitedTestJavaProcessBuilder( + new String[] {productFlag, "GetAvailableProcessors"} + ); + + var output = new OutputAnalyzer(processBuilder.start()); + output.shouldHaveExitValue(0); + output.shouldContain(runtimeAvailableProcessorsMessage); + + return output; + } + + private static int getAvailableProcessors(OutputAnalyzer outputAnalyzer) { + int runtimeAvailableProcs = 0; + List output = outputAnalyzer.stdoutAsLines(); + + for (var line: output) { + if (line.startsWith(runtimeAvailableProcessorsMessage)) { + String runtimeAvailableProcsStr = line.substring(runtimeAvailableProcessorsMessage.length()); + runtimeAvailableProcs = Integer.parseInt(runtimeAvailableProcsStr); + } + } + + return runtimeAvailableProcs; + } + + private static int getAvailableProcessors(boolean productFlagEnabled) throws IOException { + OutputAnalyzer outputAnalyzer = getAvailableProcessorsOutput(productFlagEnabled); + return getAvailableProcessors(outputAnalyzer); + } + + private static void verifyAvailableProcessorsWithDisabledProductFlag(Set processorGroupSizes) throws IOException { + boolean productFlagEnabled = false; + int runtimeAvailableProcs = getAvailableProcessors(productFlagEnabled); + + String error = String.format("Runtime.availableProcessors (%d) is not a valid processor group size on this machine.", runtimeAvailableProcs); + Assert.assertTrue(processorGroupSizes.contains(runtimeAvailableProcs), error); + } + + private static void verifyAvailableProcessorsWithEnabledProductFlag(boolean schedulesAllProcessorGroups, int totalProcessorCount, Set processorGroupSizes) throws IOException { + boolean productFlagEnabled = true; + + OutputAnalyzer outputAnalyzer = getAvailableProcessorsOutput(productFlagEnabled); + int runtimeAvailableProcs = getAvailableProcessors(outputAnalyzer); + + if (schedulesAllProcessorGroups) { + String error = String.format("Runtime.availableProcessors (%d) is not equal to the expected total processor count (%d)", runtimeAvailableProcs, totalProcessorCount); + Assert.assertEquals(runtimeAvailableProcs, totalProcessorCount, error); + } else { + outputAnalyzer.shouldContain(unsupportedPlatformMessage); + + String error = String.format("Runtime.availableProcessors (%d) is not a valid processor group size on this machine.", runtimeAvailableProcs); + Assert.assertTrue(processorGroupSizes.contains(runtimeAvailableProcs), error); + } + } + + @Test + private static void testProcessorAvailability() throws IOException { + // Launch GetProcessorInfo.exe to gather processor counts + Path nativeGetProcessorInfo = Paths.get(Utils.TEST_NATIVE_PATH) + .resolve("GetProcessorInfo.exe") + .toAbsolutePath(); + + var processBuilder = new ProcessBuilder(nativeGetProcessorInfo.toString()); + var outputAnalyzer= new OutputAnalyzer(processBuilder.start()); + outputAnalyzer.shouldHaveExitValue(0); + outputAnalyzer.shouldContain(totalProcessorCountMessage); + outputAnalyzer.shouldContain(processorCountPerGroupMessage); + outputAnalyzer.shouldContain(isWindowsServerMessage); + + int totalProcessorCount = 0; + boolean isWindowsServer = false; + var processorGroupSizes = new HashSet(); + + List lines = outputAnalyzer.stdoutAsLines(); + + for (var line: lines) { + if (line.startsWith(totalProcessorCountMessage)) { + String totalProcessorCountStr = line.substring(totalProcessorCountMessage.length()); + totalProcessorCount = Integer.parseInt(totalProcessorCountStr); + } else if (line.startsWith(processorCountPerGroupMessage)) { + String processorCountPerGroupStr = line.substring(processorCountPerGroupMessage.length()); + String[] processorCountsPerGroup = processorCountPerGroupStr.split(","); + + for (var processorCountStr: processorCountsPerGroup) { + int processorCount = Integer.parseInt(processorCountStr); + processorGroupSizes.add(processorCount); + } + } else if (line.startsWith(isWindowsServerMessage)) { + String isWindowsServerStr = line.substring(isWindowsServerMessage.length()); + isWindowsServer = Integer.parseInt(isWindowsServerStr) > 0; + } + } + + // Launch java without the start command and with the product flag disabled + verifyAvailableProcessorsWithDisabledProductFlag(processorGroupSizes); + + // Launch java without the start command and with the product flag enabled + boolean schedulesAllProcessorGroups = getSchedulesAllProcessorGroups(isWindowsServer); + verifyAvailableProcessorsWithEnabledProductFlag(schedulesAllProcessorGroups, totalProcessorCount, processorGroupSizes); + } +} diff --git a/test/hotspot/jtreg/runtime/os/windows/exeGetProcessorInfo.c b/test/hotspot/jtreg/runtime/os/windows/exeGetProcessorInfo.c new file mode 100644 index 00000000000..5d19c2082d5 --- /dev/null +++ b/test/hotspot/jtreg/runtime/os/windows/exeGetProcessorInfo.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 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 +#include +#include +#include + +int main() +{ + DWORD active_processor_count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); + if (active_processor_count == 0) { + printf("GetActiveProcessorCount failed with error: %x\n", GetLastError()); + return 1; + } + + printf("IsWindowsServer: %d\n", IsWindowsServer() ? 1 : 0); + printf("Active processor count across all processor groups: %d\n", active_processor_count); + + USHORT group_count = 0; + + if (GetProcessGroupAffinity(GetCurrentProcess(), &group_count, NULL) == 0) { + DWORD last_error = GetLastError(); + if (last_error == ERROR_INSUFFICIENT_BUFFER) { + if (group_count == 0) { + printf("Unexpected group count of 0 from GetProcessGroupAffinity.\n"); + return 1; + } + } else { + printf("GetActiveProcessorCount failed with error: %x\n", GetLastError()); + return 1; + } + } else { + printf("Unexpected GetProcessGroupAffinity success result.\n"); + return 1; + } + + PUSHORT group_array = (PUSHORT)malloc(group_count * sizeof(USHORT)); + if (group_array == NULL) { + printf("malloc failed.\n"); + return 1; + } + + printf("Active processors per group: "); + for (USHORT i=0; i < group_count; i++) { + DWORD active_processors_in_group = GetActiveProcessorCount(i); + if (active_processors_in_group == 0) { + printf("GetActiveProcessorCount(%d) failed with error: %x\n", i, GetLastError()); + return 1; + } + + printf("%d,", active_processors_in_group); + } + + free(group_array); + return 0; +}