diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index f84c576f0c2..b5c2de51044 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -61,6 +61,8 @@ #include "runtime/threads.hpp" #include "runtime/threadSMR.hpp" #include "runtime/timer.hpp" +// SapMachine 2026-10-01 +#include "runtime/timerTrace.hpp" #include "runtime/vm_version.hpp" #include "semaphore_posix.hpp" #include "services/runtimeService.hpp" @@ -1155,12 +1157,32 @@ bool os::create_thread(Thread* thread, ThreadType thr_type, log_warning(os, thread)("Failed to start thread \"%s\" - pthread_create failed (%s) for attributes: %s.", thread->name(), os::errno_name(ret), os::Posix::describe_pthread_attr(buf, sizeof(buf), &attr)); // Log some OS information which might explain why creating the thread failed. - log_info(os, thread)("Number of threads approx. running in the VM: %d", Threads::number_of_threads()); - LogStream st(Log(os, thread)::info()); - os::Posix::print_rlimit_info(&st); - os::print_memory_info(&st); - os::Linux::print_proc_sys_info(&st); - os::Linux::print_container_info(&st); + + // SapMachine 2026-10-01: Since this is a lot of output we only use warning level first and + // then turn it down to Info. + static bool is_first = true; + + if (is_first || log_is_enabled(Info, os, thread)) { + uint64_t procs, threads; + + LogStream stw(Log(os, thread)::warning()); + LogStream sti(Log(os, thread)::info()); + LogStream* st = is_first ? &stw : &sti; + + st->print_cr("Number of threads approx. running in the VM: %d", Threads::number_of_threads()); + + if (os::Linux::get_total_procs_and_threads(&procs, &threads)) { + st->print_cr("Number of processes on the system: " UINT64_FORMAT, procs); + st->print_cr("Number of threads on the system: " UINT64_FORMAT, threads); + } + + os::Posix::print_rlimit_info(st); + os::print_memory_info(st); + os::Linux::print_proc_sys_info(st); + os::Linux::print_container_info(st); + + is_first = false; + } } pthread_attr_destroy(&attr); @@ -2448,6 +2470,60 @@ bool os::Linux::query_accurate_process_memory_info(os::Linux::accurate_meminfo_t return true; } +// Helper function, returns true if string is a numerical id +static bool is_numerical_id(const char* s) { + const char* p = s; + while(*p >= '0' && *p <= '9') { + p ++; + } + return *p == '\0' ? true : false; +} + +// SapMachine 2026-10-01 +bool os::Linux::get_total_procs_and_threads(uint64_t* procs, uint64_t* threads) { + TraceTime timer("Iterating all processes", TRACETIME_LOG(Trace, os, timer)); + DIR* d = ::opendir("/proc"); + if (d != nullptr) { + uint64_t procs_count = 0; + uint64_t threads_count = 0; + struct dirent* en = nullptr; + do { + en = ::readdir(d); + if (en != nullptr) { + if (is_numerical_id(en->d_name)) { + procs_count++; + if (threads == nullptr) { + continue; + } + char tmp[128]; + jio_snprintf(tmp, sizeof(tmp), "/proc/%s/stat", en->d_name); + FILE *fp = os::fopen(tmp, "r"); + if (fp != nullptr) { + // See man proc(5) + // (20) num_threads %ld + long num_threads = 0; + if (fscanf(fp, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*d %*d %*d %*d %ld", &num_threads) == 1) { + threads_count += num_threads; + } + ::fclose(fp); + } + } + } + } while(en != nullptr); + ::closedir(d); + if (procs != nullptr) { + *procs = procs_count; + } + if (threads != nullptr) { + *threads = threads_count; + } + + return true; + } + + return false; +} + #ifdef __GLIBC__ // For Glibc, print a one-liner with the malloc tunables. // Most important and popular is MALLOC_ARENA_MAX, but we are diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index a01e0f6dfd3..5b7259f2c24 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -187,6 +187,10 @@ class os::Linux { // fields will contain -1. static bool query_accurate_process_memory_info(accurate_meminfo_t* info); + // SapMachine 2026-10-01: Gets the total number of processes and threads on the system. If a parameter is nullptr + // the value isn't determined. This is useful since the thread count is somewhat expensive. + static bool get_total_procs_and_threads(uint64_t* procs, uint64_t* threads); + // Tells if the user asked for transparent huge pages. static bool _thp_requested; diff --git a/src/hotspot/os/linux/vitals_linux.cpp b/src/hotspot/os/linux/vitals_linux.cpp index ad1976f819f..72c5e34c19f 100644 --- a/src/hotspot/os/linux/vitals_linux.cpp +++ b/src/hotspot/os/linux/vitals_linux.cpp @@ -101,7 +101,6 @@ static Column* g_col_system_pages_swapped_in = nullptr; static Column* g_col_system_pages_swapped_out = nullptr; static Column* g_col_system_num_procs = nullptr; -static Column* g_col_system_num_threads = nullptr; static Column* g_col_system_num_procs_running = nullptr; static Column* g_col_system_num_procs_blocked = nullptr; @@ -173,8 +172,6 @@ bool platform_columns_initialize() { g_col_system_num_procs = define_column(system_cat, nullptr, "p", "Number of processes", true); - g_col_system_num_threads = - define_column(system_cat, nullptr, "t", "Number of threads", true); g_col_system_num_procs_running = define_column(system_cat, nullptr, "tr", "Number of threads running", true); @@ -302,7 +299,6 @@ void sample_platform_values(Sample* sample, Sample* long_term_sample) { } set_value_in_sample(g_col_system_num_procs, sample, OSWrapper::syst_p()); - set_value_in_sample(g_col_system_num_threads, sample, OSWrapper::syst_t()); set_value_in_sample(g_col_process_virt, sample, OSWrapper::proc_virt()); set_value_in_sample(g_col_process_swapped_out, sample, OSWrapper::proc_swdo()); diff --git a/src/hotspot/os/linux/vitals_linux_oswrapper.cpp b/src/hotspot/os/linux/vitals_linux_oswrapper.cpp index 77cb8ec6a15..efff8a57462 100644 --- a/src/hotspot/os/linux/vitals_linux_oswrapper.cpp +++ b/src/hotspot/os/linux/vitals_linux_oswrapper.cpp @@ -25,9 +25,9 @@ #include "jvm_io.h" #include "logging/log.hpp" +#include "os_linux.hpp" #include "osContainer_linux.hpp" #include "runtime/os.hpp" -#include "runtime/timerTrace.hpp" #include "utilities/globalDefinitions.hpp" #include "vitals/vitals_internals.hpp" #include "vitals_linux_oswrapper.hpp" @@ -221,15 +221,6 @@ static void mallinfo_init() { #endif // __GLIBC__ -// Helper function, returns true if string is a numerical id -static bool is_numerical_id(const char* s) { - const char* p = s; - while(*p >= '0' && *p <= '9') { - p ++; - } - return *p == '\0' ? true : false; -} - /////////////// cgroup stuff // We use part of the hotspot cgroup wrapper, but not all of it. // The reason: @@ -493,38 +484,7 @@ ALL_VALUES_DO(RESETVAL) } } - // Number of processes: iterate over /proc/ and count. - // Number of threads: read "num_threads" from /proc//stat - { - TraceTime timer("Iterating all processes", TRACETIME_LOG(Debug, vitals, os)); - DIR* d = ::opendir("/proc"); - if (d != nullptr) { - value_t v_p = 0; - value_t v_t = 0; - struct dirent* en = nullptr; - do { - en = ::readdir(d); - if (en != nullptr) { - if (is_numerical_id(en->d_name)) { - v_p ++; - char tmp[128]; - jio_snprintf(tmp, sizeof(tmp), "/proc/%s/stat", en->d_name); - if (bf.read(tmp)) { - const char* text = bf.text(); - // See man proc(5) - // (20) num_threads %ld - long num_threads = 0; - ::sscanf(text, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*d %*d %*d %*d %ld", &num_threads); - v_t += num_threads; - } - } - } - } while(en != nullptr); - ::closedir(d); - _syst_p = v_p; - _syst_t = v_t; - } - } + os::Linux::get_total_procs_and_threads(&_syst_p, nullptr); if (bf.read("/proc/self/io")) { _proc_io_rd = bf.parsed_prefixed_value("rchar:"); diff --git a/src/hotspot/os/linux/vitals_linux_oswrapper.hpp b/src/hotspot/os/linux/vitals_linux_oswrapper.hpp index 21ae709c223..74e2e057176 100644 --- a/src/hotspot/os/linux/vitals_linux_oswrapper.hpp +++ b/src/hotspot/os/linux/vitals_linux_oswrapper.hpp @@ -45,7 +45,6 @@ class OSWrapper { f(syst_si) \ f(syst_so) \ f(syst_p) \ - f(syst_t) \ f(syst_tr) \ f(syst_tb) \ f(syst_load_average) \ diff --git a/src/hotspot/share/vitals/vitals.cpp b/src/hotspot/share/vitals/vitals.cpp index d8847583ee0..7753fb6aedc 100644 --- a/src/hotspot/share/vitals/vitals.cpp +++ b/src/hotspot/share/vitals/vitals.cpp @@ -40,6 +40,7 @@ #include "runtime/os.hpp" #include "runtime/thread.hpp" #include "runtime/threads.hpp" +#include "runtime/timerTrace.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -970,6 +971,7 @@ static SampleTables* g_all_tables = nullptr; // Samples all values, but leaves timestamp unchanged static void sample_values(Sample* sample, Sample* long_term_sample, bool avoid_locking) { + TraceTime timer("Sampling the values", TRACETIME_LOG(Trace, vitals, timer)); time_t t; ::time(&t); sample->set_timestamp(t); @@ -1034,8 +1036,8 @@ class SamplerThread: public NamedThread { virtual void run() { record_stack_base_and_size(); for (;;) { - take_sample(g_all_tables->next_sample_is_for_long_term()); os::naked_sleep(get_sample_interval_ms()); + take_sample(g_all_tables->next_sample_is_for_long_term()); if (_stop) { break; } diff --git a/test/hotspot/jtreg/runtime/Vitals/TestHiMemReport.java b/test/hotspot/jtreg/runtime/Vitals/TestHiMemReport.java index c770e0ea066..cf78f1660d9 100644 --- a/test/hotspot/jtreg/runtime/Vitals/TestHiMemReport.java +++ b/test/hotspot/jtreg/runtime/Vitals/TestHiMemReport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 SAP SE. All rights reserved. + * Copyright (c) 2021, 2026 SAP SE. 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 @@ -159,7 +159,7 @@ static void testDumpWithExecToReportDir() throws Exception { "-XX:NativeMemoryTracking=summary", "-Xmx128m", "-Xms128m", "-XX:+AlwaysPreTouch", TestHiMemReport.class.getName(), - "sleep", "12" // num seconds to sleep to give the reporter thread time to generate output + "sleep", "30" // num seconds to sleep to give the reporter thread time to generate output ); OutputAnalyzer output = new OutputAnalyzer(pb.start()); @@ -266,7 +266,7 @@ static void testDumpWithExecToStderr() throws Exception { "-XX:NativeMemoryTracking=summary", "-Xmx128m", "-Xms128m", "-XX:+AlwaysPreTouch", TestHiMemReport.class.getName(), - "sleep", "8" // num seconds to sleep to give the reporter thread time to generate output + "sleep", "20" // num seconds to sleep to give the reporter thread time to generate output ); OutputAnalyzer output = new OutputAnalyzer(pb.start()); diff --git a/test/hotspot/jtreg/runtime/Vitals/VitalsValuesSanityCheck.java b/test/hotspot/jtreg/runtime/Vitals/VitalsValuesSanityCheck.java index 33543144ecc..ceb7ec0dc61 100644 --- a/test/hotspot/jtreg/runtime/Vitals/VitalsValuesSanityCheck.java +++ b/test/hotspot/jtreg/runtime/Vitals/VitalsValuesSanityCheck.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, SAP SE. All rights reserved. + * Copyright (c) 2022, 2026 SAP SE. 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 @@ -63,6 +63,9 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Proxy; +import java.net.URL; +import java.net.URLClassLoader; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -128,9 +131,9 @@ private static long findHighestValueForColumn(CSVParser.CSV csv, String colname) */ private static void checkValueIsBetween(long value, String colname, long min, long max) { if (value < min) { - throw new RuntimeException(colname + " seems too low (expected at least " + min + ")"); + throw new RuntimeException(colname + "(" + value + ") seems too low (expected at least " + min + ")"); } else if (value >= max) { - throw new RuntimeException(colname + " seems too high (expected at most " + max + ")"); + throw new RuntimeException(colname + "(" + value + ") seems too high (expected at most " + max + ")"); } } @@ -269,11 +272,6 @@ public void run(CommandExecutor executor) { long min_expected_kernel_threads = min_expected_java_threads; long max_expected_kernel_threads = 1000000000; // same here - long syst_t = checkValueIsBetween(csv, "syst-t", min_expected_kernel_threads, max_expected_kernel_threads); - - // threads running, blocked on disk IO (cannot be larger than number of kernel threads) - checkValueIsBetween(csv, "syst-tr", 0, syst_t); - checkValueIsBetween(csv, "syst-tb", 0, syst_t); // Cgroup // We may not always show this. But if we do, at least the usage numbers should be checked @@ -360,7 +358,11 @@ public void jmx() { try { // wait some time. We sample with 1sec sample frequency, that should give us more than one sample // and therefore some of them should show delta values - Thread.sleep(4000); + for (int i = 0; i < 400; ++i) { + // Load some classes. + Proxy.getProxyClass(new URLClassLoader(new URL[0]), new Class [] { CharSequence.class }); + Thread.sleep(10); + } } catch (InterruptedException e) { e.printStackTrace(); }