Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 50 additions & 110 deletions src/hotspot/os/linux/os_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,8 @@ physical_memory_size_type os::Linux::_physical_memory = 0;
address os::Linux::_initial_thread_stack_bottom = nullptr;
uintptr_t os::Linux::_initial_thread_stack_size = 0;

int (*os::Linux::_pthread_getcpuclockid)(pthread_t, clockid_t *) = nullptr;
int (*os::Linux::_pthread_setname_np)(pthread_t, const char*) = nullptr;
pthread_t os::Linux::_main_thread;
bool os::Linux::_supports_fast_thread_cpu_time = false;
const char * os::Linux::_libc_version = nullptr;
const char * os::Linux::_libpthread_version = nullptr;

Expand Down Expand Up @@ -1529,29 +1527,6 @@ double os::elapsedVTime() {
}
}

void os::Linux::fast_thread_clock_init() {
clockid_t clockid;
struct timespec tp;
int (*pthread_getcpuclockid_func)(pthread_t, clockid_t *) =
(int(*)(pthread_t, clockid_t *)) dlsym(RTLD_DEFAULT, "pthread_getcpuclockid");

// Switch to using fast clocks for thread cpu time if
// the clock_getres() returns 0 error code.
// Note, that some kernels may support the current thread
// clock (CLOCK_THREAD_CPUTIME_ID) but not the clocks
// returned by the pthread_getcpuclockid().
// If the fast POSIX clocks are supported then the clock_getres()
// must return at least tp.tv_sec == 0 which means a resolution
// better than 1 sec. This is extra check for reliability.

if (pthread_getcpuclockid_func &&
pthread_getcpuclockid_func(_main_thread, &clockid) == 0 &&
clock_getres(clockid, &tp) == 0 && tp.tv_sec == 0) {
_supports_fast_thread_cpu_time = true;
_pthread_getcpuclockid = pthread_getcpuclockid_func;
}
}

// thread_id is kernel thread id (similar to Solaris LWP id)
intx os::current_thread_id() { return os::Linux::gettid(); }
int os::current_process_id() {
Expand Down Expand Up @@ -4370,14 +4345,7 @@ OSReturn os::get_native_priority(const Thread* const thread,
return (*priority_ptr != -1 || errno == 0 ? OS_OK : OS_ERR);
}

// This is the fastest way to get thread cpu time on Linux.
// Returns cpu time (user+sys) for any thread, not only for current.
// POSIX compliant clocks are implemented in the kernels 2.6.16+.
// It might work on 2.6.10+ with a special kernel/glibc patch.
// For reference, please, see IEEE Std 1003.1-2004:
// http://www.unix.org/single_unix_specification

jlong os::Linux::fast_thread_cpu_time(clockid_t clockid) {
jlong os::Linux::thread_cpu_time(clockid_t clockid) {
struct timespec tp;
int status = clock_gettime(clockid, &tp);
assert(status == 0, "clock_gettime error: %s", os::strerror(errno));
Expand Down Expand Up @@ -4690,8 +4658,6 @@ jint os::init_2(void) {

os::Posix::init_2();

Linux::fast_thread_clock_init();

if (PosixSignals::init() == JNI_ERR) {
return JNI_ERR;
}
Expand Down Expand Up @@ -5118,20 +5084,42 @@ int os::open(const char *path, int oflag, int mode) {
return fd;
}

static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time);
// Since kernel v2.6.12 the Linux ABI has had support for encoding the clock
// types in the last three bits. Bit 2 indicates whether a cpu clock refers to a
// thread or a process. Bits 1 and 0 give the type: PROF=0, VIRT=1, SCHED=2, or
// FD=3. The clock CPUCLOCK_VIRT (0b001) reports the thread's consumed user
// time. POSIX compliant implementations of pthread_getcpuclockid return the
// clock CPUCLOCK_SCHED (0b010) which reports the thread's consumed system+user
// time (as mandated by the POSIX standard POSIX.1-2024/IEEE Std 1003.1-2024
// §3.90).
static bool get_thread_clockid(Thread* thread, clockid_t* clockid, bool total) {
constexpr clockid_t CLOCK_TYPE_MASK = 3;
constexpr clockid_t CPUCLOCK_VIRT = 1;

int rc = pthread_getcpuclockid(thread->osthread()->pthread_id(), clockid);
if (rc != 0) {
// It's possible to encounter a terminated native thread that failed
// to detach itself from the VM - which should result in ESRCH.
assert_status(rc == ESRCH, rc, "pthread_getcpuclockid failed");
return false;
}

static jlong fast_cpu_time(Thread *thread) {
clockid_t clockid;
int rc = os::Linux::pthread_getcpuclockid(thread->osthread()->pthread_id(),
&clockid);
if (rc == 0) {
return os::Linux::fast_thread_cpu_time(clockid);
} else {
// It's possible to encounter a terminated native thread that failed
// to detach itself from the VM - which should result in ESRCH.
assert_status(rc == ESRCH, rc, "pthread_getcpuclockid failed");
return -1;
}
if (!total) {
clockid_t clockid_tmp = *clockid;
clockid_tmp = (clockid_tmp & ~CLOCK_TYPE_MASK) | CPUCLOCK_VIRT;
*clockid = clockid_tmp;
}

return true;
}

static jlong user_thread_cpu_time(Thread *thread);

static jlong total_thread_cpu_time(Thread *thread) {
clockid_t clockid;
bool success = get_thread_clockid(thread, &clockid, true);

return success ? os::Linux::thread_cpu_time(clockid) : -1;
}

// current_thread_cpu_time(bool) and thread_cpu_time(Thread*, bool)
Expand All @@ -5142,84 +5130,36 @@ static jlong fast_cpu_time(Thread *thread) {
// the fast estimate available on the platform.

jlong os::current_thread_cpu_time() {
if (os::Linux::supports_fast_thread_cpu_time()) {
return os::Linux::fast_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID);
} else {
// return user + sys since the cost is the same
return slow_thread_cpu_time(Thread::current(), true /* user + sys */);
}
return os::Linux::thread_cpu_time(CLOCK_THREAD_CPUTIME_ID);
}

jlong os::thread_cpu_time(Thread* thread) {
// consistent with what current_thread_cpu_time() returns
if (os::Linux::supports_fast_thread_cpu_time()) {
return fast_cpu_time(thread);
} else {
return slow_thread_cpu_time(thread, true /* user + sys */);
}
return total_thread_cpu_time(thread);
}

jlong os::current_thread_cpu_time(bool user_sys_cpu_time) {
if (user_sys_cpu_time && os::Linux::supports_fast_thread_cpu_time()) {
return os::Linux::fast_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID);
if (user_sys_cpu_time) {
return os::Linux::thread_cpu_time(CLOCK_THREAD_CPUTIME_ID);
} else {
return slow_thread_cpu_time(Thread::current(), user_sys_cpu_time);
return user_thread_cpu_time(Thread::current());
}
}

jlong os::thread_cpu_time(Thread *thread, bool user_sys_cpu_time) {
if (user_sys_cpu_time && os::Linux::supports_fast_thread_cpu_time()) {
return fast_cpu_time(thread);
} else {
return slow_thread_cpu_time(thread, user_sys_cpu_time);
}
}

// -1 on error.
static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time) {
pid_t tid = thread->osthread()->thread_id();
char *s;
char stat[2048];
size_t statlen;
char proc_name[64];
int count;
long sys_time, user_time;
char cdummy;
int idummy;
long ldummy;
FILE *fp;

snprintf(proc_name, 64, "/proc/self/task/%d/stat", tid);
fp = os::fopen(proc_name, "r");
if (fp == nullptr) return -1;
statlen = fread(stat, 1, 2047, fp);
stat[statlen] = '\0';
fclose(fp);

// Skip pid and the command string. Note that we could be dealing with
// weird command names, e.g. user could decide to rename java launcher
// to "java 1.4.2 :)", then the stat file would look like
// 1234 (java 1.4.2 :)) R ... ...
// We don't really need to know the command string, just find the last
// occurrence of ")" and then start parsing from there. See bug 4726580.
s = strrchr(stat, ')');
if (s == nullptr) return -1;

// Skip blank chars
do { s++; } while (s && isspace((unsigned char) *s));

count = sscanf(s,"%c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu",
&cdummy, &idummy, &idummy, &idummy, &idummy, &idummy,
&ldummy, &ldummy, &ldummy, &ldummy, &ldummy,
&user_time, &sys_time);
if (count != 13) return -1;
if (user_sys_cpu_time) {
return ((jlong)sys_time + (jlong)user_time) * (1000000000 / os::Posix::clock_tics_per_second());
return total_thread_cpu_time(thread);
} else {
return (jlong)user_time * (1000000000 / os::Posix::clock_tics_per_second());
return user_thread_cpu_time(thread);
}
}

static jlong user_thread_cpu_time(Thread *thread) {
clockid_t clockid;
bool success = get_thread_clockid(thread, &clockid, false);

return success ? os::Linux::thread_cpu_time(clockid) : -1;
}

void os::current_thread_cpu_time_info(jvmtiTimerInfo *info_ptr) {
info_ptr->max_value = all_bits_jlong; // will not wrap in less than 64 bits
info_ptr->may_skip_backward = false; // elapsed time not wall time
Expand Down
16 changes: 1 addition & 15 deletions src/hotspot/os/linux/os_linux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
class os::Linux {
friend class os;

static int (*_pthread_getcpuclockid)(pthread_t, clockid_t *);
static int (*_pthread_setname_np)(pthread_t, const char*);

static address _initial_thread_stack_bottom;
Expand All @@ -41,8 +40,6 @@ class os::Linux {
static const char *_libc_version;
static const char *_libpthread_version;

static bool _supports_fast_thread_cpu_time;

static GrowableArray<int>* _cpu_to_node;
static GrowableArray<int>* _nindex_to_node;

Expand Down Expand Up @@ -145,18 +142,7 @@ class os::Linux {
static bool manually_expand_stack(JavaThread * t, address addr);
static void expand_stack_to(address bottom);

// fast POSIX clocks support
static void fast_thread_clock_init(void);

static int pthread_getcpuclockid(pthread_t tid, clockid_t *clock_id) {
return _pthread_getcpuclockid ? _pthread_getcpuclockid(tid, clock_id) : -1;
}

static bool supports_fast_thread_cpu_time() {
return _supports_fast_thread_cpu_time;
}

static jlong fast_thread_cpu_time(clockid_t clockid);
static jlong thread_cpu_time(clockid_t clockid);

static jlong sendfile(int out_fd, int in_fd, jlong* offset, jlong count);

Expand Down
3 changes: 0 additions & 3 deletions src/hotspot/share/runtime/cpuTimeCounters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,5 @@ ThreadTotalCPUTimeClosure::~ThreadTotalCPUTimeClosure() {
}

void ThreadTotalCPUTimeClosure::do_thread(Thread* thread) {
// The default code path (fast_thread_cpu_time()) asserts that
// pthread_getcpuclockid() and clock_gettime() must return 0. Thus caller
// must ensure the thread exists and has not terminated.
_total += os::thread_cpu_time(thread);
}
5 changes: 1 addition & 4 deletions src/hotspot/share/runtime/os.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,10 +974,7 @@ class os: AllStatic {
// The thread_cpu_time() and current_thread_cpu_time() are only
// supported if is_thread_cpu_time_supported() returns true.

// Thread CPU Time - return the fast estimate on a platform
// On Linux - fast clock_gettime where available - user+sys
// - otherwise: very slow /proc fs - user+sys
// On Windows - GetThreadTimes - user+sys
// Thread CPU Time - return the fast estimate on a platform - user+sys
static jlong current_thread_cpu_time();
static jlong thread_cpu_time(Thread* t);

Expand Down
55 changes: 55 additions & 0 deletions test/micro/org/openjdk/bench/vm/runtime/ThreadMXBeanBench.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2025, 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.
*/
package org.openjdk.bench.vm.runtime;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;

@State(Scope.Benchmark)
@Warmup(iterations = 2, time = 5)
@Measurement(iterations = 5, time = 5)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Threads(1)
@Fork(value = 10)
public class ThreadMXBeanBench {
static final ThreadMXBean mxThreadBean = ManagementFactory.getThreadMXBean();
static long user; // To avoid dead-code elimination

@Benchmark
public void getCurrentThreadUserTime() throws Throwable {
user = mxThreadBean.getCurrentThreadUserTime();
}
}