-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add Support for JFR Java Monitor Wait and Thread Sleep events #4676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
775bf69
9e2f972
c0a608b
e6f1cff
eac426f
4ffcf51
1f0de84
4d0e6b2
3d97124
7376370
472884f
b762020
02c8acf
1f19876
a0c805c
66d09fd
45a8aa2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2022, 2022, Red Hat Inc. 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. Oracle designates this | ||
* particular file as subject to the "Classpath" exception as provided | ||
* by Oracle in the LICENSE file that accompanied this code. | ||
* | ||
* 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 com.oracle.svm.core.jfr.events; | ||
import com.oracle.svm.core.annotate.Uninterruptible; | ||
import com.oracle.svm.core.jfr.JfrEvent; | ||
import com.oracle.svm.core.jfr.JfrNativeEventWriter; | ||
import com.oracle.svm.core.jfr.JfrNativeEventWriterData; | ||
import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; | ||
import com.oracle.svm.core.jfr.JfrTicks; | ||
import com.oracle.svm.core.jfr.SubstrateJVM; | ||
import org.graalvm.compiler.word.Word; | ||
import com.oracle.svm.core.jfr.HasJfrSupport; | ||
public class JavaMonitorWaitEvent { | ||
public static void emit(long startTicks, Object obj, long notifier, long timeout, boolean timedOut) { | ||
if (HasJfrSupport.get()) { | ||
emit0(startTicks, obj, notifier, timeout, timedOut); | ||
} | ||
} | ||
|
||
@Uninterruptible(reason = "Accesses a JFR buffer.") | ||
private static void emit0(long startTicks, Object obj, long notifier, long timeout, boolean timedOut) { | ||
if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.JavaMonitorWait)) { | ||
JfrNativeEventWriterData data = org.graalvm.nativeimage.StackValue.get(JfrNativeEventWriterData.class); | ||
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); | ||
JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.JavaMonitorWait); | ||
JfrNativeEventWriter.putLong(data, startTicks); | ||
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks() - startTicks); | ||
JfrNativeEventWriter.putEventThread(data); | ||
JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorWait, 0)); | ||
JfrNativeEventWriter.putClass(data, obj.getClass()); | ||
JfrNativeEventWriter.putLong(data, notifier); | ||
JfrNativeEventWriter.putLong(data, timeout); | ||
JfrNativeEventWriter.putBoolean(data, timedOut); | ||
JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); | ||
JfrNativeEventWriter.endSmallEvent(data); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2022, 2022, Red Hat Inc. 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. Oracle designates this | ||
* particular file as subject to the "Classpath" exception as provided | ||
* by Oracle in the LICENSE file that accompanied this code. | ||
* | ||
* 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 com.oracle.svm.core.jfr.events; | ||
|
||
import org.graalvm.nativeimage.StackValue; | ||
|
||
import com.oracle.svm.core.annotate.Uninterruptible; | ||
import com.oracle.svm.core.jfr.JfrEvent; | ||
import com.oracle.svm.core.jfr.JfrNativeEventWriter; | ||
import com.oracle.svm.core.jfr.JfrNativeEventWriterData; | ||
import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; | ||
import com.oracle.svm.core.jfr.JfrTicks; | ||
import com.oracle.svm.core.jfr.SubstrateJVM; | ||
|
||
public class ThreadSleepEvent { | ||
|
||
public static void emit(long time, long startTicks) { | ||
if (com.oracle.svm.core.jfr.HasJfrSupport.get()) { | ||
emit0(time, startTicks); | ||
} | ||
} | ||
|
||
@Uninterruptible(reason = "Accesses a JFR buffer.") | ||
private static void emit0(long time, long startTicks) { | ||
if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadSleep)) { | ||
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); | ||
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); | ||
|
||
JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadSleep); | ||
JfrNativeEventWriter.putLong(data, startTicks); | ||
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks() - startTicks); | ||
JfrNativeEventWriter.putEventThread(data); | ||
JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadSleep, 0)); | ||
JfrNativeEventWriter.putLong(data, time); | ||
JfrNativeEventWriter.endSmallEvent(data); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,10 @@ | |
|
||
package com.oracle.svm.core.monitor; | ||
|
||
import java.util.Collection; | ||
import java.util.LinkedList; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.locks.Condition; | ||
import java.util.concurrent.locks.ReentrantLock; | ||
|
||
import org.graalvm.nativeimage.CurrentIsolate; | ||
|
@@ -34,12 +38,88 @@ | |
import com.oracle.svm.core.jfr.JfrTicks; | ||
import com.oracle.svm.core.jfr.SubstrateJVM; | ||
import com.oracle.svm.core.jfr.events.JavaMonitorEnterEvent; | ||
import com.oracle.svm.core.jfr.events.JavaMonitorWaitEvent; | ||
|
||
public class JavaMonitor extends ReentrantLock { | ||
private static final long serialVersionUID = 3921577070627519721L; | ||
|
||
private int finishedWaiters = 0; | ||
private Condition condition; | ||
|
||
private LinkedList<Waiter> waiters = new LinkedList<>(); | ||
private long latestJfrTid; | ||
|
||
private long getNotifierTid() { | ||
assert isHeldByCurrentThread(); | ||
long curr = Thread.currentThread().getId(); | ||
Waiter target = null; | ||
// No guarantee on the order in which the waiters reacquire the lock. | ||
// Must compare TIDs. Can be expensive if many waiters and lock acquisition order is opposite wait order. | ||
for (Waiter waiter : waiters) { | ||
if (waiter.getWaiterTid() == curr) { | ||
target = waiter; | ||
break; | ||
} | ||
} | ||
assert waiters.remove(target); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only executed when assertions are enabled, so you are currently leaking memory. |
||
finishedWaiters--; | ||
|
||
return target.getNotifierTid(); // notified if TID > 0 | ||
} | ||
|
||
public void setNotifier(boolean notifyAll) { | ||
assert isHeldByCurrentThread(); | ||
|
||
// If there are extra notifications, there will be no waiters to respond to them, so don't record them. | ||
if (finishedWaiters == waiters.size()) { | ||
return; | ||
} | ||
|
||
long curr = Thread.currentThread().getId(); | ||
int notifications = 1; | ||
if (notifyAll) { | ||
notifications = waiters.size() - finishedWaiters; | ||
} | ||
Collection<Thread> waitingThreads = this.getWaitingThreads(condition); | ||
while (notifications > 0 && finishedWaiters < waiters.size()) { | ||
Waiter waiter = waiters.get(finishedWaiters); | ||
|
||
// Only add NotifierTid to queue if the thread is still waiting. | ||
// waitingThreads collection is not guaranteed to be in any order. | ||
if (waitingThreads.contains(waiter.getThread())) { | ||
waiter.setNotifierTid(curr); | ||
notifications--; | ||
} else if (notifyAll) { | ||
notifications--; | ||
} | ||
finishedWaiters++; | ||
} | ||
} | ||
|
||
public void doWait(Object obj, long timeoutMillis, Condition c) throws InterruptedException { | ||
this.condition = c; | ||
waiters.addLast(new Waiter()); | ||
long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks(); | ||
try { | ||
if (timeoutMillis == 0L) { | ||
this.condition.await(); | ||
JavaMonitorWaitEvent.emit(startTicks, obj, getNotifierTid(), timeoutMillis, false); | ||
} else { | ||
if (this.condition.await(timeoutMillis, TimeUnit.MILLISECONDS)) { | ||
JavaMonitorWaitEvent.emit(startTicks, obj, getNotifierTid(), timeoutMillis, false); | ||
} else { | ||
// remove waiter from queue and check it wasn't notified | ||
assert getNotifierTid() < 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only executed when assertions are enabled, so probably not what you want. |
||
JavaMonitorWaitEvent.emit(startTicks, obj, 0, timeoutMillis, true); | ||
} | ||
} | ||
} catch (InterruptedException e) { | ||
// Similar to hotspot, we should not emit event if interrupted | ||
getNotifierTid(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the thread is interrupted, it won't hold the lock. So, the assertion in |
||
throw e; // pass it back up in case it needs to be handled elsewhere | ||
} | ||
} | ||
|
||
public JavaMonitor() { | ||
Target_java_util_concurrent_locks_ReentrantLock lock = SubstrateUtil.cast(this, Target_java_util_concurrent_locks_ReentrantLock.class); | ||
Target_java_util_concurrent_locks_ReentrantLock_NonfairSync sync = SubstrateUtil.cast(lock.sync, Target_java_util_concurrent_locks_ReentrantLock_NonfairSync.class); | ||
|
@@ -77,4 +157,29 @@ public void monitorEnter(Object obj) { | |
|
||
latestJfrTid = SubstrateJVM.getThreadId(CurrentIsolate.getCurrentThread()); | ||
} | ||
|
||
class Waiter { | ||
private Thread thread; | ||
private long notifierTid = -1; | ||
|
||
Waiter() { | ||
this.thread = Thread.currentThread(); | ||
} | ||
|
||
public Thread getThread() { | ||
return thread; | ||
} | ||
|
||
public long getNotifierTid() { | ||
return notifierTid; | ||
} | ||
|
||
public long getWaiterTid() { | ||
return thread.getId(); | ||
} | ||
|
||
public void setNotifierTid(long notifierTid) { | ||
this.notifierTid = notifierTid; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,8 @@ | |
import java.util.concurrent.atomic.AtomicInteger; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
import com.oracle.svm.core.jfr.events.ThreadSleepEvent; | ||
|
||
import org.graalvm.compiler.api.replacements.Fold; | ||
import org.graalvm.compiler.core.common.SuppressFBWarnings; | ||
import org.graalvm.compiler.serviceprovider.JavaVersionUtil; | ||
|
@@ -301,11 +303,13 @@ static void initializeNewThread( | |
} | ||
|
||
static void sleep(long millis) throws InterruptedException { | ||
long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks(); | ||
if (supportsVirtual() && isVirtualDisallowLoom(Thread.currentThread())) { | ||
VirtualThreads.singleton().sleepMillis(millis); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does HotSpot really emit this JFR event for virtual threads? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried it out using virtual threads with openjdk and the event does get emitted. Please see here |
||
} else { | ||
PlatformThreads.sleep(millis); | ||
} | ||
ThreadSleepEvent.emit(millis, startTicks); | ||
} | ||
|
||
static boolean isAlive(Thread thread) { | ||
|
Uh oh!
There was an error while loading. Please reload this page.