Skip to content
88 changes: 82 additions & 6 deletions src/hotspot/os/linux/os_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
#include "runtime/threads.hpp"
#include "runtime/threadSMR.hpp"
#include "runtime/timer.hpp"
// SapMachine 2026-10-01
#include "runtime/timerTrace.hpp"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotate here as well

#include "runtime/vm_version.hpp"
#include "semaphore_posix.hpp"
#include "services/runtimeService.hpp"
Expand Down Expand Up @@ -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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that something for the OpenJDK as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original change was by me and I wanted to make all warning level, but DH thought it would be too much.

// 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);
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/os/linux/os_linux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 0 additions & 4 deletions src/hotspot/os/linux/vitals_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -173,8 +172,6 @@ bool platform_columns_initialize() {

g_col_system_num_procs =
define_column<PlainValueColumn>(system_cat, nullptr, "p", "Number of processes", true);
g_col_system_num_threads =
define_column<PlainValueColumn>(system_cat, nullptr, "t", "Number of threads", true);

g_col_system_num_procs_running =
define_column<PlainValueColumn>(system_cat, nullptr, "tr", "Number of threads running", true);
Expand Down Expand Up @@ -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());
Expand Down
44 changes: 2 additions & 42 deletions src/hotspot/os/linux/vitals_linux_oswrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -493,38 +484,7 @@ ALL_VALUES_DO(RESETVAL)
}
}

// Number of processes: iterate over /proc/<pid> and count.
// Number of threads: read "num_threads" from /proc/<pid>/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:");
Expand Down
1 change: 0 additions & 1 deletion src/hotspot/os/linux/vitals_linux_oswrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
4 changes: 3 additions & 1 deletion src/hotspot/share/vitals/vitals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
6 changes: 3 additions & 3 deletions test/hotspot/jtreg/runtime/Vitals/TestHiMemReport.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand Down
20 changes: 11 additions & 9 deletions test/hotspot/jtreg/runtime/Vitals/VitalsValuesSanityCheck.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 + ")");
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
Expand Down