diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index cf64c22ddff..880dbeccf7d 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -4305,7 +4305,7 @@ OSReturn os::get_native_priority(const Thread* const thread, // For reference, please, see IEEE Std 1003.1-2004: // http://www.unix.org/single_unix_specification -jlong os::Linux::total_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)); @@ -4960,20 +4960,42 @@ int os::open(const char *path, int oflag, int mode) { return fd; } +// 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; + } + + 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; - int rc = pthread_getcpuclockid(thread->osthread()->pthread_id(), - &clockid); - if (rc == 0) { - return os::Linux::total_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; - } + 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) @@ -4984,7 +5006,7 @@ static jlong total_thread_cpu_time(Thread *thread) { // the fast estimate available on the platform. jlong os::current_thread_cpu_time() { - return os::Linux::total_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); + return os::Linux::thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); } jlong os::thread_cpu_time(Thread* thread) { @@ -4993,7 +5015,7 @@ jlong os::thread_cpu_time(Thread* thread) { jlong os::current_thread_cpu_time(bool user_sys_cpu_time) { if (user_sys_cpu_time) { - return os::Linux::total_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); + return os::Linux::thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); } else { return user_thread_cpu_time(Thread::current()); } @@ -5007,46 +5029,11 @@ jlong os::thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { } } -// -1 on error. static jlong user_thread_cpu_time(Thread *thread) { - 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; + clockid_t clockid; + bool success = get_thread_clockid(thread, &clockid, false); - os::snprintf_checked(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; - - return (jlong)user_time * (1000000000 / os::Posix::clock_tics_per_second()); + return success ? os::Linux::thread_cpu_time(clockid) : -1; } void os::current_thread_cpu_time_info(jvmtiTimerInfo *info_ptr) { diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index dd07cb600b9..43f70b4af56 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -142,7 +142,7 @@ class os::Linux { static bool manually_expand_stack(JavaThread * t, address addr); static void expand_stack_to(address bottom); - static jlong total_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); diff --git a/test/micro/org/openjdk/bench/vm/runtime/ThreadMXBeanBench.java b/test/micro/org/openjdk/bench/vm/runtime/ThreadMXBeanBench.java new file mode 100644 index 00000000000..f041eb89f5a --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/runtime/ThreadMXBeanBench.java @@ -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(); + } +}