From 481f81c3e7c66836d1d2ed3ff13e6da4a604a32c Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Fri, 9 Sep 2022 16:41:19 -0400
Subject: [PATCH 01/21] add JFR event unit tests for thread sleep and monitor
enter
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 47 +++++++++++
.../oracle/svm/test/jfr/TestThreadSleep.java | 79 +++++++++++++++++++
2 files changed, 126 insertions(+)
create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index a75203702ec4..1b160623377c 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -28,7 +28,15 @@
import static org.junit.Assume.assumeTrue;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
+import java.util.List;
import org.graalvm.nativeimage.ImageInfo;
import org.graalvm.nativeimage.hosted.Feature;
@@ -51,6 +59,8 @@
public abstract class JfrTest {
protected Jfr jfr;
protected Recording recording;
+ private ChronologicalComparator chronologicalComparator = new ChronologicalComparator();
+ protected final long MS_TOLERANCE = 10;
@BeforeClass
public static void checkForJFR() {
@@ -76,6 +86,7 @@ public void startRecording() {
public void endRecording() {
try {
jfr.endRecording(recording);
+ analyzeEvents();
} catch (Exception e) {
Assert.fail("Fail to stop recording! Cause: " + e.getMessage());
}
@@ -100,6 +111,8 @@ protected void enableEvents(String[] events) {
}
public abstract String[] getTestedEvents();
+ public void analyzeEvents(){
+ }
protected void checkEvents() {
HashSet seenEvents = new HashSet<>();
@@ -127,6 +140,40 @@ protected void checkRecording() throws AssertionError {
Assert.fail("Failed to parse recording: " + e.getMessage());
}
}
+
+ private static class ChronologicalComparator implements Comparator {
+ @Override
+ public int compare(RecordedEvent e1, RecordedEvent e2) {
+ return e1.getStartTime().compareTo(e2.getStartTime());
+ }
+ }
+ private Path makeCopy(Recording recording, String testName) throws IOException { // from jdk 19
+ Path p = recording.getDestination();
+ if (p == null) {
+ File directory = new File(".");
+ p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-" + testName+ ".jfr").toPath();
+ recording.dump(p);
+ }
+ return p;
+ }
+ protected List getEvents(Recording recording, String testName) throws IOException {
+ Path p = makeCopy(recording, testName);
+ List events = RecordingFile.readAllEvents(p);
+ Collections.sort(events, chronologicalComparator);
+ return events;
+ }
+
+
+ /** Used for comparing durations with a tolerance of MS_TOLERANCE */
+ protected boolean isEqualDuration(Duration d1, Duration d2) {
+ return d1.minus(d2).abs().compareTo(Duration.ofMillis(MS_TOLERANCE)) < 0;
+ }
+
+ /** Used for comparing durations with a tolerance of MS_TOLERANCE. True if 'larger' really is bigger */
+ protected boolean isGreaterDuration(Duration smaller, Duration larger) {
+ return smaller.minus(larger.plus(Duration.ofMillis(MS_TOLERANCE))).isNegative();
+ }
+
}
class JFRTestFeature implements Feature {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
new file mode 100644
index 000000000000..aed5475b2ae5
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
@@ -0,0 +1,79 @@
+/*
+ * 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.test.jfr;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedThread;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.List;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import com.oracle.svm.test.jfr.JfrTest;
+
+public class TestThreadSleep extends JfrTest {
+ private String sleepingThreadName;
+ private static final int MILLIS = 50;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.ThreadSleep"};
+ }
+ @Override
+ public void analyzeEvents() {
+ List events;
+ try {
+ events = getEvents(recording, "jdk.ThreadSleep");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ boolean foundSleepEvent = false;
+ for (RecordedEvent event : events) {
+ RecordedObject struct = event;
+ String eventThread = struct.getValue("eventThread").getJavaName();
+ if (!eventThread.equals(sleepingThreadName)) {
+ continue;
+ }
+ assertTrue("wrong event type",event.getEventType().getName().equals("jdk.ThreadSleep"));
+
+ assertTrue("Slept wrong duration.",isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS)));
+ foundSleepEvent = true;
+ break;
+ }
+ assertTrue("Sleep event not found.", foundSleepEvent);
+ }
+
+
+ @Test
+ public void test() throws Exception {
+ sleepingThreadName = Thread.currentThread().getName();
+ Thread.sleep(MILLIS);
+ }
+}
From d0f03cf1a4846c6de34825cc52a43b15c202892b Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Mon, 12 Sep 2022 10:59:50 -0400
Subject: [PATCH 02/21] add java monitor wait
---
.../svm/test/jfr/TestJavaMonitorWait.java | 159 ++++++++++++++++++
1 file changed, 159 insertions(+)
create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
new file mode 100644
index 000000000000..3b6eec4f0779
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
@@ -0,0 +1,159 @@
+/*
+ * 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.test.jfr;
+
+import static java.lang.Math.abs;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.List;
+
+import jdk.jfr.consumer.RecordedClass;
+import org.junit.Test;
+
+import com.oracle.svm.test.jfr.JfrTest;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorWait extends JfrTest {
+ private static final int MILLIS = 50;
+ private static final int COUNT = 10;
+ private String producerName;
+ private String consumerName;
+ static Helper helper = new Helper();
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorWait"};
+ }
+ @Override
+ public void analyzeEvents() {
+ List events;
+ try {
+ events = getEvents(recording, "jdk.JavaMonitorWait");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ int prodCount = 0;
+ int consCount = 0;
+ String lastEventThreadName = null; //should alternate if buffer is 1
+ for (RecordedEvent event : events) {
+ RecordedObject struct = event;
+ String eventThread = struct.getValue("eventThread").getJavaName();
+ String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null;
+ assertTrue("No event thread",eventThread != null);
+ if (!eventThread.equals(producerName) && !eventThread.equals(consumerName) ) {
+ continue;
+ }
+ assertTrue( "Wrong event type", event.getEventType().getName().equals("jdk.JavaMonitorWait"));
+ assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration()));
+ assertFalse("Wrong monitor class.",
+ !struct.getValue("monitorClass").getName().equals(Helper.class.getName())
+ && (eventThread.equals(consumerName) ||eventThread.equals(producerName)));
+
+ assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue());
+
+ if (lastEventThreadName == null) {
+ lastEventThreadName = notifThread;
+ }
+ assertTrue("Not alternating", lastEventThreadName.equals(notifThread));
+ if (eventThread.equals(producerName)) {
+ prodCount++;
+ assertTrue("Wrong notifier", notifThread.equals(consumerName));
+ } else if (eventThread.equals(consumerName)) {
+ consCount++;
+ assertTrue("Wrong notifier", notifThread.equals(producerName));
+ }
+ lastEventThreadName = eventThread;
+ }
+ assertFalse("Wrong number of events: "+prodCount + " "+consCount,
+ abs(prodCount - consCount) > 1 || abs(consCount-COUNT) >1);
+ }
+
+
+ @Test
+ public void test() throws Exception {
+ Runnable consumer = () -> {
+ try {
+ helper.consume();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable producer = () -> {
+ try {
+ helper.produce();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ Thread tc = new Thread(consumer);
+ Thread tp = new Thread(producer);
+ producerName = tp.getName();
+ consumerName = tc.getName();
+ tp.start();
+ tc.start();
+ tp.join();
+ tc.join();
+
+ // sleep so we know the event is recorded
+ Thread.sleep(500);
+ }
+
+ static class Helper {
+ private int count = 0;
+ private final int bufferSize = 1;
+
+ public synchronized void produce() throws InterruptedException {
+ for (int i = 0; i< COUNT; i++) {
+ while (count >= bufferSize) {
+ wait();
+ }
+ Thread.sleep(MILLIS);
+ count++;
+ notify();
+ }
+ }
+
+ public synchronized void consume() throws InterruptedException {
+ for (int i = 0; i< COUNT; i++) {
+ while (count == 0) {
+ wait();
+ }
+ Thread.sleep(MILLIS);
+ count--;
+ notify();
+ }
+ }
+ }
+}
From 98e3ac946daa3ad03595936bf1c57f081de37988 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Mon, 12 Sep 2022 14:17:26 -0400
Subject: [PATCH 03/21] make tests more robust
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 2 +-
.../src/com/oracle/svm/test/jfr/Stressor.java | 47 +++++
.../svm/test/jfr/TestJavaMonitorEnter.java | 115 ++++++++++++
.../svm/test/jfr/TestJavaMonitorWait.java | 11 +-
.../test/jfr/TestJavaMonitorWaitTimeout.java | 176 ++++++++++++++++++
.../oracle/svm/test/jfr/TestThreadSleep.java | 10 +-
6 files changed, 350 insertions(+), 11 deletions(-)
create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index 1b160623377c..d776f6ba91d7 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -144,7 +144,7 @@ protected void checkRecording() throws AssertionError {
private static class ChronologicalComparator implements Comparator {
@Override
public int compare(RecordedEvent e1, RecordedEvent e2) {
- return e1.getStartTime().compareTo(e2.getStartTime());
+ return e1.getEndTime().compareTo(e2.getEndTime());
}
}
private Path makeCopy(Recording recording, String testName) throws IOException { // from jdk 19
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
new file mode 100644
index 000000000000..29121ce12e56
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
@@ -0,0 +1,47 @@
+/*
+ * 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.test.jfr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to help run multiple threads executing some task
+ */
+public class Stressor {
+ public static void execute(int numberOfThreads, Thread.UncaughtExceptionHandler eh, Runnable task) throws Exception {
+ List threads = new ArrayList<>();
+ for (int n = 0; n < numberOfThreads; ++n) {
+ Thread t = new Thread(task);
+ t.setUncaughtExceptionHandler(eh);
+ threads.add(t);
+ t.start();
+ }
+ for (Thread t : threads) {
+ t.join();
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
new file mode 100644
index 000000000000..0e4f02f197f4
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.test.jfr;
+
+import static java.lang.Math.abs;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+import jdk.jfr.consumer.RecordedThread;
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import com.oracle.svm.test.jfr.JfrTest;
+
+import com.oracle.svm.test.jfr.Stressor;
+
+public class TestJavaMonitorEnter extends JfrTest {
+ private final int THREADS = 10;
+ private static final int MILLIS = 60;
+ static Object monitor = new Object();
+
+ private static Queue orderedWaiterNames = new LinkedList<>();
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorEnter"};
+ }
+ @Override
+ public void analyzeEvents() {
+ List events;
+ try {
+ events = getEvents(recording, "jdk.JavaMonitorEnter");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ int count = 0;
+ orderedWaiterNames.poll(); //first worker does not wait
+ String waiterName = orderedWaiterNames.poll();
+ Long prev = 0L;
+ for (RecordedEvent event : events) {
+ RecordedObject struct = event;
+ String eventThread = struct.getValue("eventThread").getJavaName();
+ if (event.getEventType().getName().equals("jdk.JavaMonitorEnter")
+ && isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())
+ && waiterName.equals(eventThread)) {
+ Long duration = event.getDuration().toMillis();
+ assertTrue( "Durations not as expected ", abs(duration - prev - MILLIS) < MS_TOLERANCE );
+ count++;
+ waiterName = orderedWaiterNames.poll();
+ prev = duration;
+ }
+ }
+ assertTrue("Wrong number of Java Monitor Enter Events " + count, count == THREADS - 1);// -1 because first thread does not get blocked by any previous thread
+ }
+
+ private static void doWork(Object obj) throws InterruptedException {
+ synchronized(obj){
+ Thread.sleep(MILLIS);
+ orderedWaiterNames.add(Thread.currentThread().getName());
+ }
+ }
+ @Test
+ public void test() throws Exception {
+ int threadCount = THREADS;
+ Runnable r = () -> {
+ // create contention between threads for one lock
+ try {
+ doWork(monitor);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ };
+ Thread.UncaughtExceptionHandler eh = (t, e) -> e.printStackTrace();
+
+ try {
+ Stressor.execute(threadCount, eh, r);
+ // sleep so we know the event is recorded
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
index 3b6eec4f0779..5ba28776182c 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
@@ -71,15 +71,14 @@ public void analyzeEvents() {
String eventThread = struct.getValue("eventThread").getJavaName();
String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null;
assertTrue("No event thread",eventThread != null);
- if (!eventThread.equals(producerName) && !eventThread.equals(consumerName) ) {
+ if (
+ (!eventThread.equals(producerName) && !eventThread.equals(consumerName))
+ || !event.getEventType().getName().equals("jdk.JavaMonitorWait")
+ || !struct.getValue("monitorClass").getName().equals(Helper.class.getName() )) {
continue;
}
- assertTrue( "Wrong event type", event.getEventType().getName().equals("jdk.JavaMonitorWait"));
- assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration()));
- assertFalse("Wrong monitor class.",
- !struct.getValue("monitorClass").getName().equals(Helper.class.getName())
- && (eventThread.equals(consumerName) ||eventThread.equals(producerName)));
+ assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration()));
assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue());
if (lastEventThreadName == null) {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
new file mode 100644
index 000000000000..d4e440f2256f
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -0,0 +1,176 @@
+/*
+ * 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.test.jfr;
+
+import static java.lang.Math.abs;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.oracle.svm.test.jfr.JfrTest;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorWaitTimeout extends JfrTest {
+ private static final int MILLIS = 50;
+ static Helper helper = new Helper();
+ static String timeOutName;
+ static String notifierName;
+ static String simpleWaitName;
+ static String simpleNotifyName;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorWait"};
+ }
+ @Override
+ public void analyzeEvents() {
+ List events;
+ try {
+ events = getEvents(recording, "jdk.JavaMonitorWait");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ for (RecordedEvent event : events) {
+ RecordedObject struct = event;
+ if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
+ continue;
+ }
+ String eventThread = struct.getValue("eventThread").getJavaName();
+ String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null;
+ if (!eventThread.equals(notifierName) &&
+ !eventThread.equals(timeOutName) &&
+ !eventThread.equals(simpleNotifyName) &&
+ !eventThread.equals(simpleWaitName)) {
+ continue;
+ }
+ if (!struct.getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ continue;
+ }
+ assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()));
+ if (eventThread.equals(timeOutName)) {
+ assertTrue("Notifier of timeout thread should be null", notifThread == null);
+ assertTrue("Should have timed out.", struct.getValue("timedOut").booleanValue());
+ } else if (eventThread.equals(simpleWaitName)) {
+ assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyName));
+ }
+
+ }
+ }
+
+
+ @Test
+ public void test() throws Exception {
+ Runnable unheardNotifier = () -> {
+ try {
+ helper.unheardNotify();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable timouter = () -> {
+ try {
+ helper.timeout();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable simpleWaiter = () -> {
+ try {
+ helper.simpleWait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable simpleNotifier = () -> {
+ try {
+ helper.simpleNotify();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ Thread unheardNotifierThread = new Thread(unheardNotifier);
+ Thread timeoutThread = new Thread(timouter);
+ timeOutName = unheardNotifierThread.getName();
+ notifierName = unheardNotifierThread.getName();
+
+
+ timeoutThread.start();
+ Thread.sleep(10);
+ unheardNotifierThread.start();
+
+ timeoutThread.join();
+ unheardNotifierThread.join();
+
+ Thread tw = new Thread(simpleWaiter);
+ Thread tn = new Thread(simpleNotifier);
+ simpleWaitName = tw.getName();
+ simpleNotifyName = tn.getName();
+
+
+ tw.start();
+ Thread.sleep(10);
+ tn.start();
+
+ tw.join();
+ tn.join();
+
+ // sleep so we know the event is recorded
+ Thread.sleep(500);
+ }
+
+ static class Helper {
+ public synchronized void timeout() throws InterruptedException {
+ wait(MILLIS);
+ }
+
+ public synchronized void unheardNotify() throws InterruptedException {
+ Thread.sleep(2*MILLIS);
+ //notify after timeout
+ notify();
+ }
+
+ public synchronized void simpleWait() throws InterruptedException {
+ wait();
+ }
+ public synchronized void simpleNotify() throws InterruptedException {
+ Thread.sleep(2*MILLIS);
+ notify();
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
index aed5475b2ae5..770ef96b23a8 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
@@ -56,15 +56,17 @@ public void analyzeEvents() {
}
boolean foundSleepEvent = false;
for (RecordedEvent event : events) {
+ if (!event.getEventType().getName().equals("jdk.ThreadSleep")) {
+ continue;
+ }
RecordedObject struct = event;
String eventThread = struct.getValue("eventThread").getJavaName();
if (!eventThread.equals(sleepingThreadName)) {
continue;
}
- assertTrue("wrong event type",event.getEventType().getName().equals("jdk.ThreadSleep"));
-
- assertTrue("Slept wrong duration.",isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS)));
- foundSleepEvent = true;
+ if (!isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS))) {
+ continue;
+ } foundSleepEvent = true;
break;
}
assertTrue("Sleep event not found.", foundSleepEvent);
From 06a3c9ffac78893633d3c51ef35e4e53d2231409 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Mon, 12 Sep 2022 15:07:47 -0400
Subject: [PATCH 04/21] add monitor wait interrupt test. checkstyle, format
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 22 ++-
.../src/com/oracle/svm/test/jfr/Stressor.java | 2 +-
.../svm/test/jfr/TestJavaMonitorEnter.java | 27 ++-
.../svm/test/jfr/TestJavaMonitorWait.java | 28 ++-
.../jfr/TestJavaMonitorWaitInterrupt.java | 181 ++++++++++++++++++
.../jfr/TestJavaMonitorWaitNotifyAll.java | 143 ++++++++++++++
.../test/jfr/TestJavaMonitorWaitTimeout.java | 39 ++--
.../oracle/svm/test/jfr/TestThreadSleep.java | 8 +-
8 files changed, 386 insertions(+), 64 deletions(-)
create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index d776f6ba91d7..b70f6bf30d0f 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -30,7 +30,6 @@
import java.io.File;
import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Collections;
@@ -60,7 +59,7 @@ public abstract class JfrTest {
protected Jfr jfr;
protected Recording recording;
private ChronologicalComparator chronologicalComparator = new ChronologicalComparator();
- protected final long MS_TOLERANCE = 10;
+ protected final long msTolerance = 10;
@BeforeClass
public static void checkForJFR() {
@@ -111,7 +110,8 @@ protected void enableEvents(String[] events) {
}
public abstract String[] getTestedEvents();
- public void analyzeEvents(){
+
+ public void analyzeEvents() {
}
protected void checkEvents() {
@@ -147,15 +147,17 @@ public int compare(RecordedEvent e1, RecordedEvent e2) {
return e1.getEndTime().compareTo(e2.getEndTime());
}
}
+
private Path makeCopy(Recording recording, String testName) throws IOException { // from jdk 19
Path p = recording.getDestination();
if (p == null) {
File directory = new File(".");
- p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-" + testName+ ".jfr").toPath();
+ p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-" + testName + ".jfr").toPath();
recording.dump(p);
}
return p;
}
+
protected List getEvents(Recording recording, String testName) throws IOException {
Path p = makeCopy(recording, testName);
List events = RecordingFile.readAllEvents(p);
@@ -163,15 +165,17 @@ protected List getEvents(Recording recording, String testName) th
return events;
}
-
- /** Used for comparing durations with a tolerance of MS_TOLERANCE */
+ /** Used for comparing durations with a tolerance of MS_TOLERANCE. */
protected boolean isEqualDuration(Duration d1, Duration d2) {
- return d1.minus(d2).abs().compareTo(Duration.ofMillis(MS_TOLERANCE)) < 0;
+ return d1.minus(d2).abs().compareTo(Duration.ofMillis(msTolerance)) < 0;
}
- /** Used for comparing durations with a tolerance of MS_TOLERANCE. True if 'larger' really is bigger */
+ /**
+ * Used for comparing durations with a tolerance of MS_TOLERANCE. True if 'larger' really is
+ * bigger
+ */
protected boolean isGreaterDuration(Duration smaller, Duration larger) {
- return smaller.minus(larger.plus(Duration.ofMillis(MS_TOLERANCE))).isNegative();
+ return smaller.minus(larger.plus(Duration.ofMillis(msTolerance))).isNegative();
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
index 29121ce12e56..88ae9316c211 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
@@ -29,7 +29,7 @@
import java.util.List;
/**
- * Class to help run multiple threads executing some task
+ * Class to help run multiple threads executing some task.
*/
public class Stressor {
public static void execute(int numberOfThreads, Thread.UncaughtExceptionHandler eh, Runnable task) throws Exception {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index 0e4f02f197f4..17aad6eaa956 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -27,12 +27,10 @@
package com.oracle.svm.test.jfr;
import static java.lang.Math.abs;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.time.Duration;
-import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
@@ -42,20 +40,19 @@
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedObject;
-import com.oracle.svm.test.jfr.JfrTest;
-
-import com.oracle.svm.test.jfr.Stressor;
public class TestJavaMonitorEnter extends JfrTest {
- private final int THREADS = 10;
+ private final int threads = 10;
private static final int MILLIS = 60;
static Object monitor = new Object();
private static Queue orderedWaiterNames = new LinkedList<>();
+
@Override
public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorEnter"};
}
+
@Override
public void analyzeEvents() {
List events;
@@ -65,34 +62,34 @@ public void analyzeEvents() {
throw new RuntimeException(e);
}
int count = 0;
- orderedWaiterNames.poll(); //first worker does not wait
+ orderedWaiterNames.poll(); // first worker does not wait
String waiterName = orderedWaiterNames.poll();
Long prev = 0L;
for (RecordedEvent event : events) {
RecordedObject struct = event;
- String eventThread = struct.getValue("eventThread").getJavaName();
- if (event.getEventType().getName().equals("jdk.JavaMonitorEnter")
- && isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())
- && waiterName.equals(eventThread)) {
+ String eventThread = struct. getValue("eventThread").getJavaName();
+ if (event.getEventType().getName().equals("jdk.JavaMonitorEnter") && isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()) && waiterName.equals(eventThread)) {
Long duration = event.getDuration().toMillis();
- assertTrue( "Durations not as expected ", abs(duration - prev - MILLIS) < MS_TOLERANCE );
+ assertTrue("Durations not as expected ", abs(duration - prev - MILLIS) < msTolerance);
count++;
waiterName = orderedWaiterNames.poll();
prev = duration;
}
}
- assertTrue("Wrong number of Java Monitor Enter Events " + count, count == THREADS - 1);// -1 because first thread does not get blocked by any previous thread
+ assertTrue("Wrong number of Java Monitor Enter Events " + count, count == threads - 1);
+
}
private static void doWork(Object obj) throws InterruptedException {
- synchronized(obj){
+ synchronized (obj) {
Thread.sleep(MILLIS);
orderedWaiterNames.add(Thread.currentThread().getName());
}
}
+
@Test
public void test() throws Exception {
- int threadCount = THREADS;
+ int threadCount = threads;
Runnable r = () -> {
// create contention between threads for one lock
try {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
index 5ba28776182c..1e8fe39e5525 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
@@ -37,8 +37,6 @@
import jdk.jfr.consumer.RecordedClass;
import org.junit.Test;
-import com.oracle.svm.test.jfr.JfrTest;
-
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordedThread;
@@ -54,6 +52,7 @@ public class TestJavaMonitorWait extends JfrTest {
public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
+
@Override
public void analyzeEvents() {
List events;
@@ -65,21 +64,19 @@ public void analyzeEvents() {
int prodCount = 0;
int consCount = 0;
- String lastEventThreadName = null; //should alternate if buffer is 1
+ String lastEventThreadName = null; // should alternate if buffer is 1
for (RecordedEvent event : events) {
RecordedObject struct = event;
- String eventThread = struct.getValue("eventThread").getJavaName();
- String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null;
- assertTrue("No event thread",eventThread != null);
- if (
- (!eventThread.equals(producerName) && !eventThread.equals(consumerName))
- || !event.getEventType().getName().equals("jdk.JavaMonitorWait")
- || !struct.getValue("monitorClass").getName().equals(Helper.class.getName() )) {
+ String eventThread = struct. getValue("eventThread").getJavaName();
+ String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
+ assertTrue("No event thread", eventThread != null);
+ if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) || !event.getEventType().getName().equals("jdk.JavaMonitorWait") ||
+ !struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration()));
- assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue());
+ assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
if (lastEventThreadName == null) {
lastEventThreadName = notifThread;
@@ -94,11 +91,10 @@ public void analyzeEvents() {
}
lastEventThreadName = eventThread;
}
- assertFalse("Wrong number of events: "+prodCount + " "+consCount,
- abs(prodCount - consCount) > 1 || abs(consCount-COUNT) >1);
+ assertFalse("Wrong number of events: " + prodCount + " " + consCount,
+ abs(prodCount - consCount) > 1 || abs(consCount - COUNT) > 1);
}
-
@Test
public void test() throws Exception {
Runnable consumer = () -> {
@@ -134,7 +130,7 @@ static class Helper {
private final int bufferSize = 1;
public synchronized void produce() throws InterruptedException {
- for (int i = 0; i< COUNT; i++) {
+ for (int i = 0; i < COUNT; i++) {
while (count >= bufferSize) {
wait();
}
@@ -145,7 +141,7 @@ public synchronized void produce() throws InterruptedException {
}
public synchronized void consume() throws InterruptedException {
- for (int i = 0; i< COUNT; i++) {
+ for (int i = 0; i < COUNT; i++) {
while (count == 0) {
wait();
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
new file mode 100644
index 000000000000..de634ce1c41d
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -0,0 +1,181 @@
+/*
+ * 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.test.jfr;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.List;
+
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorWaitInterrupt extends JfrTest {
+ private static final int MILLIS = 50;
+ static Helper helper = new Helper();
+ static String interruptedName;
+ static String interrupterName;
+ static String simpleWaitName;
+ static String simpleNotifyName;
+
+ private boolean interruptedFound = false;
+ private boolean simpleWaitFound = false;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorWait"};
+ }
+
+ @Override
+ public void analyzeEvents() {
+ List events;
+ try {
+ events = getEvents(recording, "TestJavaMonitorWaitInterrupt");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ for (RecordedEvent event : events) {
+ RecordedObject struct = event;
+ if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
+ continue;
+ }
+ String eventThread = struct. getValue("eventThread").getJavaName();
+ String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
+ if (!eventThread.equals(interrupterName) &&
+ !eventThread.equals(interruptedName) &&
+ !eventThread.equals(simpleNotifyName) &&
+ !eventThread.equals(simpleWaitName)) {
+ continue;
+ }
+ if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ continue;
+ }
+ assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()));
+ assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
+
+ if (eventThread.equals(interruptedName)) {
+ assertTrue("Notifier of interrupted thread should be null", notifThread == null);
+ interruptedFound = true;
+ } else if (eventThread.equals(simpleWaitName)) {
+ assertTrue("Notifier of simple wait is incorrect: " + notifThread + " " + simpleNotifyName, notifThread.equals(simpleNotifyName));
+ simpleWaitFound = true;
+ }
+ }
+ assertTrue("Couldn't find expected wait events. SimpleWaiter: " + simpleWaitFound + " interrupted: " + interruptedFound,
+ simpleWaitFound && interruptedFound);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Runnable interrupter = () -> {
+ try {
+ helper.interrupt();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable interrupted = () -> {
+ try {
+ helper.interrupted();
+ throw new RuntimeException("Was not interrupted!!");
+ } catch (InterruptedException e) {
+ // should get interrupted
+ }
+ };
+
+ Runnable simpleWaiter = () -> {
+ try {
+ helper.simpleWait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable simpleNotifier = () -> {
+ try {
+ helper.simpleNotify();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ Thread interrupterThread = new Thread(interrupter);
+ Thread interruptedThread = new Thread(interrupted);
+ helper.interrupted = interruptedThread;
+ interrupterName = interrupterThread.getName();
+ interruptedName = interruptedThread.getName();
+
+ interruptedThread.start();
+ Thread.sleep(MILLIS); // pause to ensure expected ordering of lock acquisition
+ interrupterThread.start();
+
+ interruptedThread.join();
+ interrupterThread.join();
+
+ Thread tw = new Thread(simpleWaiter);
+ Thread tn = new Thread(simpleNotifier);
+ simpleWaitName = tw.getName();
+ simpleNotifyName = tn.getName();
+
+ tw.start();
+ Thread.sleep(50);
+ tn.start();
+
+ tw.join();
+ tn.join();
+
+ // sleep so we know the event is recorded
+ Thread.sleep(500);
+ }
+
+ static class Helper {
+ public Thread interrupted;
+
+ public synchronized void interrupted() throws InterruptedException {
+ wait();
+ }
+
+ public synchronized void interrupt() throws InterruptedException {
+ interrupted.interrupt();
+ }
+
+ public synchronized void simpleWait() throws InterruptedException {
+ wait();
+ }
+
+ public synchronized void simpleNotify() throws InterruptedException {
+ Thread.sleep(2 * MILLIS);
+ notify();
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
new file mode 100644
index 000000000000..46d0f988127e
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
@@ -0,0 +1,143 @@
+/*
+ * 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.test.jfr;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.List;
+
+import org.junit.Test;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJavaMonitorWaitNotifyAll extends JfrTest {
+ private static final int MILLIS = 50;
+ static Helper helper = new Helper();
+ static String waiterName1;
+ static String waiterName2;
+ static String notifierName;
+ private boolean notifierFound = false;
+ private int waitersFound = 0;
+
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{"jdk.JavaMonitorWait"};
+ }
+
+ @Override
+ public void analyzeEvents() {
+ List events;
+ try {
+ events = getEvents(recording, "TestJavaMonitorWaitNotifyAll");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ for (RecordedEvent event : events) {
+ RecordedObject struct = event;
+ if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
+ continue;
+ }
+ String eventThread = struct. getValue("eventThread").getJavaName();
+ String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
+ if (!eventThread.equals(waiterName1) &&
+ !eventThread.equals(waiterName2) &&
+ !eventThread.equals(notifierName)) {
+ continue;
+ }
+ if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ continue;
+ }
+
+ assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()));
+
+ if (eventThread.equals(notifierName)) {
+ assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue());
+ notifierFound = true;
+ } else {
+ assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
+ assertTrue("Notifier thread name is incorrect", notifThread.equals(notifierName));
+ waitersFound++;
+ }
+ }
+ assertTrue("Couldn't find expected wait events. NotifierFound: " + notifierFound + " waitersFound: " + waitersFound,
+ notifierFound && waitersFound == 2);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Runnable consumer = () -> {
+ try {
+ helper.consume();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ Runnable producer = () -> {
+ try {
+ helper.produce();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ Thread tc1 = new Thread(consumer);
+ Thread tp1 = new Thread(producer);
+ Thread tp2 = new Thread(producer);
+ waiterName1 = tp1.getName();
+ waiterName2 = tp2.getName();
+ notifierName = tc1.getName();
+
+ tp1.start();
+ tp2.start();
+ tc1.start();
+
+ tp1.join();
+ tp2.join();
+ tc1.join();
+
+ // sleep so we know the event is recorded
+ Thread.sleep(500);
+ }
+
+ static class Helper {
+ public synchronized void produce() throws InterruptedException {
+ wait();
+ }
+
+ public synchronized void consume() throws InterruptedException {
+ // give the producers a headstart so they can start waiting
+ wait(MILLIS);
+ notifyAll(); // should wake up both producers
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
index d4e440f2256f..2c900e3ebf41 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -26,8 +26,6 @@
package com.oracle.svm.test.jfr;
-import static java.lang.Math.abs;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
@@ -36,8 +34,6 @@
import org.junit.Test;
-import com.oracle.svm.test.jfr.JfrTest;
-
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedObject;
@@ -50,16 +46,19 @@ public class TestJavaMonitorWaitTimeout extends JfrTest {
static String notifierName;
static String simpleWaitName;
static String simpleNotifyName;
+ private boolean timeoutFound = false;
+ private boolean simpleWaitFound = false;
@Override
public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
+
@Override
public void analyzeEvents() {
List events;
try {
- events = getEvents(recording, "jdk.JavaMonitorWait");
+ events = getEvents(recording, "TestJavaMonitorWaitTimeout");
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -68,29 +67,32 @@ public void analyzeEvents() {
if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
continue;
}
- String eventThread = struct.getValue("eventThread").getJavaName();
- String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null;
+ String eventThread = struct. getValue("eventThread").getJavaName();
+ String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
if (!eventThread.equals(notifierName) &&
- !eventThread.equals(timeOutName) &&
- !eventThread.equals(simpleNotifyName) &&
- !eventThread.equals(simpleWaitName)) {
+ !eventThread.equals(timeOutName) &&
+ !eventThread.equals(simpleNotifyName) &&
+ !eventThread.equals(simpleWaitName)) {
continue;
}
- if (!struct.getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()));
if (eventThread.equals(timeOutName)) {
assertTrue("Notifier of timeout thread should be null", notifThread == null);
- assertTrue("Should have timed out.", struct.getValue("timedOut").booleanValue());
+ assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue());
+ timeoutFound = true;
} else if (eventThread.equals(simpleWaitName)) {
assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyName));
+ simpleWaitFound = true;
}
}
+ assertTrue("Couldn't find expected wait events. SimpleWaiter: " + simpleWaitFound + " timeout: " + timeoutFound,
+ simpleWaitFound && timeoutFound);
}
-
@Test
public void test() throws Exception {
Runnable unheardNotifier = () -> {
@@ -126,10 +128,9 @@ public void test() throws Exception {
};
Thread unheardNotifierThread = new Thread(unheardNotifier);
Thread timeoutThread = new Thread(timouter);
- timeOutName = unheardNotifierThread.getName();
+ timeOutName = timeoutThread.getName();
notifierName = unheardNotifierThread.getName();
-
timeoutThread.start();
Thread.sleep(10);
unheardNotifierThread.start();
@@ -142,7 +143,6 @@ public void test() throws Exception {
simpleWaitName = tw.getName();
simpleNotifyName = tn.getName();
-
tw.start();
Thread.sleep(10);
tn.start();
@@ -160,16 +160,17 @@ public synchronized void timeout() throws InterruptedException {
}
public synchronized void unheardNotify() throws InterruptedException {
- Thread.sleep(2*MILLIS);
- //notify after timeout
+ Thread.sleep(2 * MILLIS);
+ // notify after timeout
notify();
}
public synchronized void simpleWait() throws InterruptedException {
wait();
}
+
public synchronized void simpleNotify() throws InterruptedException {
- Thread.sleep(2*MILLIS);
+ Thread.sleep(2 * MILLIS);
notify();
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
index 770ef96b23a8..0ad7ca03f46c 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
@@ -36,7 +36,6 @@
import static org.junit.Assert.assertTrue;
import org.junit.Test;
-import com.oracle.svm.test.jfr.JfrTest;
public class TestThreadSleep extends JfrTest {
private String sleepingThreadName;
@@ -46,6 +45,7 @@ public class TestThreadSleep extends JfrTest {
public String[] getTestedEvents() {
return new String[]{"jdk.ThreadSleep"};
}
+
@Override
public void analyzeEvents() {
List events;
@@ -60,19 +60,19 @@ public void analyzeEvents() {
continue;
}
RecordedObject struct = event;
- String eventThread = struct.getValue("eventThread").getJavaName();
+ String eventThread = struct. getValue("eventThread").getJavaName();
if (!eventThread.equals(sleepingThreadName)) {
continue;
}
if (!isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS))) {
continue;
- } foundSleepEvent = true;
+ }
+ foundSleepEvent = true;
break;
}
assertTrue("Sleep event not found.", foundSleepEvent);
}
-
@Test
public void test() throws Exception {
sleepingThreadName = Thread.currentThread().getName();
From c7b548654de2b679cfda054a81705cfc9a803485 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Mon, 12 Sep 2022 15:50:44 -0400
Subject: [PATCH 05/21] fix hiding field and exceptions
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 6 ++---
.../svm/test/jfr/TestJavaMonitorEnter.java | 2 +-
.../svm/test/jfr/TestJavaMonitorWait.java | 2 +-
.../jfr/TestJavaMonitorWaitInterrupt.java | 27 ++++++++++++++-----
.../jfr/TestJavaMonitorWaitNotifyAll.java | 2 +-
.../test/jfr/TestJavaMonitorWaitTimeout.java | 2 +-
.../oracle/svm/test/jfr/TestThreadSleep.java | 2 +-
7 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index b70f6bf30d0f..4ced427f431a 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -148,7 +148,7 @@ public int compare(RecordedEvent e1, RecordedEvent e2) {
}
}
- private Path makeCopy(Recording recording, String testName) throws IOException { // from jdk 19
+ private Path makeCopy(String testName) throws IOException { // from jdk 19
Path p = recording.getDestination();
if (p == null) {
File directory = new File(".");
@@ -158,8 +158,8 @@ private Path makeCopy(Recording recording, String testName) throws IOException {
return p;
}
- protected List getEvents(Recording recording, String testName) throws IOException {
- Path p = makeCopy(recording, testName);
+ protected List getEvents(String testName) throws IOException {
+ Path p = makeCopy(testName);
List events = RecordingFile.readAllEvents(p);
Collections.sort(events, chronologicalComparator);
return events;
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index 17aad6eaa956..1d6de4d1029d 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -57,7 +57,7 @@ public String[] getTestedEvents() {
public void analyzeEvents() {
List events;
try {
- events = getEvents(recording, "jdk.JavaMonitorEnter");
+ events = getEvents("jdk.JavaMonitorEnter");
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
index 1e8fe39e5525..8bbffc0d3323 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
@@ -57,7 +57,7 @@ public String[] getTestedEvents() {
public void analyzeEvents() {
List events;
try {
- events = getEvents(recording, "jdk.JavaMonitorWait");
+ events = getEvents("jdk.JavaMonitorWait");
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index de634ce1c41d..9d98fe855340 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -60,7 +60,7 @@ public String[] getTestedEvents() {
public void analyzeEvents() {
List events;
try {
- events = getEvents(recording, "TestJavaMonitorWaitInterrupt");
+ events = getEvents("TestJavaMonitorWaitInterrupt");
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -162,20 +162,35 @@ static class Helper {
public Thread interrupted;
public synchronized void interrupted() throws InterruptedException {
- wait();
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ throw new InterruptedException("expected interrupt");
+ }
}
public synchronized void interrupt() throws InterruptedException {
- interrupted.interrupt();
+ try {
+ interrupted.interrupt();
+ } catch (Exception e) {
+ }
+
}
public synchronized void simpleWait() throws InterruptedException {
- wait();
+ try {
+ wait();
+ } catch (Exception e) {
+
+ }
}
public synchronized void simpleNotify() throws InterruptedException {
- Thread.sleep(2 * MILLIS);
- notify();
+ try {
+ Thread.sleep(2 * MILLIS);
+ notify();
+ } catch (Exception e) {
+ }
}
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
index 46d0f988127e..3a9c933c3419 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
@@ -58,7 +58,7 @@ public String[] getTestedEvents() {
public void analyzeEvents() {
List events;
try {
- events = getEvents(recording, "TestJavaMonitorWaitNotifyAll");
+ events = getEvents("TestJavaMonitorWaitNotifyAll");
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
index 2c900e3ebf41..b2dc3deec0e2 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -58,7 +58,7 @@ public String[] getTestedEvents() {
public void analyzeEvents() {
List events;
try {
- events = getEvents(recording, "TestJavaMonitorWaitTimeout");
+ events = getEvents("TestJavaMonitorWaitTimeout");
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
index 0ad7ca03f46c..3ec1535e8e41 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
@@ -50,7 +50,7 @@ public String[] getTestedEvents() {
public void analyzeEvents() {
List events;
try {
- events = getEvents(recording, "jdk.ThreadSleep");
+ events = getEvents("jdk.ThreadSleep");
} catch (IOException e) {
throw new RuntimeException(e);
}
From 5bdeff652b0902fa50cbd95284ad8b9a99eaa5bc Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Mon, 12 Sep 2022 16:26:28 -0400
Subject: [PATCH 06/21] remove unused try catch blocks
---
.../jfr/TestJavaMonitorWaitInterrupt.java | 24 +++++--------------
1 file changed, 6 insertions(+), 18 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index 9d98fe855340..55c189c14603 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -98,11 +98,7 @@ public void analyzeEvents() {
@Test
public void test() throws Exception {
Runnable interrupter = () -> {
- try {
- helper.interrupt();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ helper.interrupt();
};
Runnable interrupted = () -> {
@@ -115,19 +111,11 @@ public void test() throws Exception {
};
Runnable simpleWaiter = () -> {
- try {
- helper.simpleWait();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ helper.simpleWait();
};
Runnable simpleNotifier = () -> {
- try {
- helper.simpleNotify();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ helper.simpleNotify();
};
Thread interrupterThread = new Thread(interrupter);
Thread interruptedThread = new Thread(interrupted);
@@ -169,7 +157,7 @@ public synchronized void interrupted() throws InterruptedException {
}
}
- public synchronized void interrupt() throws InterruptedException {
+ public synchronized void interrupt() {
try {
interrupted.interrupt();
} catch (Exception e) {
@@ -177,7 +165,7 @@ public synchronized void interrupt() throws InterruptedException {
}
- public synchronized void simpleWait() throws InterruptedException {
+ public synchronized void simpleWait() {
try {
wait();
} catch (Exception e) {
@@ -185,7 +173,7 @@ public synchronized void simpleWait() throws InterruptedException {
}
}
- public synchronized void simpleNotify() throws InterruptedException {
+ public synchronized void simpleNotify() {
try {
Thread.sleep(2 * MILLIS);
notify();
From 727adae18a313493f034e713e0efd230d90bcda8 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Fri, 7 Oct 2022 14:11:20 -0400
Subject: [PATCH 07/21] use spinlocks for synchronization. Refactor.
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 25 ++--
.../src/com/oracle/svm/test/jfr/Stressor.java | 3 +-
.../svm/test/jfr/TestJavaMonitorEnter.java | 97 +++++++------
.../svm/test/jfr/TestJavaMonitorWait.java | 16 +--
.../jfr/TestJavaMonitorWaitInterrupt.java | 129 +++++++++---------
.../jfr/TestJavaMonitorWaitNotifyAll.java | 85 ++++++------
.../test/jfr/TestJavaMonitorWaitTimeout.java | 121 ++++++++--------
.../oracle/svm/test/jfr/TestThreadSleep.java | 5 +-
8 files changed, 234 insertions(+), 247 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index 4ced427f431a..5963f7a0a4a2 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -58,7 +58,7 @@
public abstract class JfrTest {
protected Jfr jfr;
protected Recording recording;
- private ChronologicalComparator chronologicalComparator = new ChronologicalComparator();
+ private final ChronologicalComparator chronologicalComparator = new ChronologicalComparator();
protected final long msTolerance = 10;
@BeforeClass
@@ -85,11 +85,15 @@ public void startRecording() {
public void endRecording() {
try {
jfr.endRecording(recording);
- analyzeEvents();
} catch (Exception e) {
Assert.fail("Fail to stop recording! Cause: " + e.getMessage());
}
-
+ checkEvents();
+ try {
+ validateEvents();
+ }catch (Throwable throwable) {
+ Assert.fail("validateEvents failed: " + throwable.getMessage());
+ }
try {
checkRecording();
} finally {
@@ -111,7 +115,7 @@ protected void enableEvents(String[] events) {
public abstract String[] getTestedEvents();
- public void analyzeEvents() {
+ public void validateEvents() throws Throwable{
}
protected void checkEvents() {
@@ -165,19 +169,6 @@ protected List getEvents(String testName) throws IOException {
return events;
}
- /** Used for comparing durations with a tolerance of MS_TOLERANCE. */
- protected boolean isEqualDuration(Duration d1, Duration d2) {
- return d1.minus(d2).abs().compareTo(Duration.ofMillis(msTolerance)) < 0;
- }
-
- /**
- * Used for comparing durations with a tolerance of MS_TOLERANCE. True if 'larger' really is
- * bigger
- */
- protected boolean isGreaterDuration(Duration smaller, Duration larger) {
- return smaller.minus(larger.plus(Duration.ofMillis(msTolerance))).isNegative();
- }
-
}
class JFRTestFeature implements Feature {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
index 88ae9316c211..8667633738ea 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
@@ -32,11 +32,10 @@
* Class to help run multiple threads executing some task.
*/
public class Stressor {
- public static void execute(int numberOfThreads, Thread.UncaughtExceptionHandler eh, Runnable task) throws Exception {
+ public static void execute(int numberOfThreads, Runnable task) throws Exception {
List threads = new ArrayList<>();
for (int n = 0; n < numberOfThreads; ++n) {
Thread t = new Thread(task);
- t.setUncaughtExceptionHandler(eh);
threads.add(t);
t.start();
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index 1d6de4d1029d..40dd2fabfb27 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -26,15 +26,13 @@
package com.oracle.svm.test.jfr;
-import static java.lang.Math.abs;
import static org.junit.Assert.assertTrue;
-import java.io.IOException;
-import java.time.Duration;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
+import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedThread;
import org.junit.Test;
@@ -42,10 +40,12 @@
import jdk.jfr.consumer.RecordedObject;
public class TestJavaMonitorEnter extends JfrTest {
- private final int threads = 10;
private static final int MILLIS = 60;
- static Object monitor = new Object();
+ static boolean inCritical = false;
+ static Thread firstThread;
+ static Thread secondThread;
+ static final Helper helper = new Helper();
private static Queue orderedWaiterNames = new LinkedList<>();
@Override
@@ -53,60 +53,75 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorEnter"};
}
- @Override
- public void analyzeEvents() {
+ public void validateEvents() throws Throwable{
List events;
- try {
- events = getEvents("jdk.JavaMonitorEnter");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ events = getEvents("jdk.JavaMonitorEnter");
int count = 0;
- orderedWaiterNames.poll(); // first worker does not wait
- String waiterName = orderedWaiterNames.poll();
- Long prev = 0L;
+ boolean found = false;
for (RecordedEvent event : events) {
RecordedObject struct = event;
String eventThread = struct. getValue("eventThread").getJavaName();
- if (event.getEventType().getName().equals("jdk.JavaMonitorEnter") && isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()) && waiterName.equals(eventThread)) {
- Long duration = event.getDuration().toMillis();
- assertTrue("Durations not as expected ", abs(duration - prev - MILLIS) < msTolerance);
- count++;
- waiterName = orderedWaiterNames.poll();
- prev = duration;
+ if (event.getEventType().getName().equals("jdk.JavaMonitorEnter")
+ && struct. getValue("monitorClass").getName().equals(Helper.class.getName())
+ && event.getDuration().toMillis() >= MILLIS
+ && secondThread.getName().equals(eventThread)) {
+
+ // verify previous owner
+ assertTrue("Previous owner is wrong",struct. getValue("previousOwner").getJavaName().equals(firstThread.getName()));
+ found = true;
+ break;
}
}
- assertTrue("Wrong number of Java Monitor Enter Events " + count, count == threads - 1);
+ assertTrue("Expected monitor blocked event not found" , found);
}
- private static void doWork(Object obj) throws InterruptedException {
- synchronized (obj) {
- Thread.sleep(MILLIS);
- orderedWaiterNames.add(Thread.currentThread().getName());
- }
- }
-
@Test
public void test() throws Exception {
- int threadCount = threads;
- Runnable r = () -> {
- // create contention between threads for one lock
+ Runnable first = () -> {
try {
- doWork(monitor);
+ helper.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
+ };
+ Runnable second = () -> {
+ try {
+ //wait until lock is held
+ while(!inCritical) {
+ Thread.sleep(10);
+ }
+ helper.doWork();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
};
- Thread.UncaughtExceptionHandler eh = (t, e) -> e.printStackTrace();
-
- try {
- Stressor.execute(threadCount, eh, r);
- // sleep so we know the event is recorded
- Thread.sleep(500);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
+
+ firstThread = new Thread(first);
+ secondThread = new Thread(second);
+ firstThread.start();
+ secondThread.start();
+
+ firstThread.join();
+ secondThread.join();
+ }
+
+ static class Helper {
+ private synchronized void doWork() throws InterruptedException {
+ inCritical = true;
+ if (Thread.currentThread().equals(secondThread)) {
+ inCritical = false;
+ return; // second thread doesn't need to do work.
+ }
+
+ // spin until second thread blocks
+ while(!secondThread.getState().equals(Thread.State.BLOCKED)) {
+ Thread.sleep(10);
+ }
+
+ Thread.sleep(MILLIS);
+ inCritical = false;
}
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
index 8bbffc0d3323..0a5db97f8d0d 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
@@ -46,21 +46,16 @@ public class TestJavaMonitorWait extends JfrTest {
private static final int COUNT = 10;
private String producerName;
private String consumerName;
- static Helper helper = new Helper();
+ static final Helper helper = new Helper();
@Override
public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
- @Override
- public void analyzeEvents() {
+ public void validateEvents() throws Throwable{
List events;
- try {
- events = getEvents("jdk.JavaMonitorWait");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ events = getEvents("jdk.JavaMonitorWait");
int prodCount = 0;
int consCount = 0;
@@ -75,7 +70,7 @@ public void analyzeEvents() {
continue;
}
- assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration()));
+ assertTrue("Wrong event duration", event.getDuration().toMillis() >= MILLIS);
assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
if (lastEventThreadName == null) {
@@ -120,9 +115,6 @@ public void test() throws Exception {
tc.start();
tp.join();
tc.join();
-
- // sleep so we know the event is recorded
- Thread.sleep(500);
}
static class Helper {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index 55c189c14603..ed53f5379fd9 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -29,11 +29,10 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import java.io.IOException;
-import java.time.Duration;
import java.util.List;
import org.junit.Test;
+import org.junit.Assert;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
@@ -42,11 +41,11 @@
public class TestJavaMonitorWaitInterrupt extends JfrTest {
private static final int MILLIS = 50;
- static Helper helper = new Helper();
- static String interruptedName;
- static String interrupterName;
- static String simpleWaitName;
- static String simpleNotifyName;
+ static final Helper helper = new Helper();
+ static Thread interruptedThread;
+ static Thread interrupterThread;
+ static Thread simpleWaitThread;
+ static Thread simpleNotifyThread;
private boolean interruptedFound = false;
private boolean simpleWaitFound = false;
@@ -56,14 +55,10 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
- @Override
- public void analyzeEvents() {
+ public void validateEvents() throws Throwable{
List events;
- try {
- events = getEvents("TestJavaMonitorWaitInterrupt");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ events = getEvents("TestJavaMonitorWaitInterrupt");
+
for (RecordedEvent event : events) {
RecordedObject struct = event;
if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
@@ -71,23 +66,23 @@ public void analyzeEvents() {
}
String eventThread = struct. getValue("eventThread").getJavaName();
String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
- if (!eventThread.equals(interrupterName) &&
- !eventThread.equals(interruptedName) &&
- !eventThread.equals(simpleNotifyName) &&
- !eventThread.equals(simpleWaitName)) {
+ if (!eventThread.equals(interrupterThread.getName()) &&
+ !eventThread.equals(interruptedThread.getName()) &&
+ !eventThread.equals(simpleNotifyThread.getName()) &&
+ !eventThread.equals(simpleWaitThread.getName())) {
continue;
}
if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
- assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()));
+ assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS);
assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
- if (eventThread.equals(interruptedName)) {
+ if (eventThread.equals(interruptedThread.getName())) {
assertTrue("Notifier of interrupted thread should be null", notifThread == null);
interruptedFound = true;
- } else if (eventThread.equals(simpleWaitName)) {
- assertTrue("Notifier of simple wait is incorrect: " + notifThread + " " + simpleNotifyName, notifThread.equals(simpleNotifyName));
+ } else if (eventThread.equals(simpleWaitThread.getName())) {
+ assertTrue("Notifier of simple wait is incorrect: " + notifThread + " " + simpleNotifyThread.getName(), notifThread.equals(simpleNotifyThread.getName()));
simpleWaitFound = true;
}
}
@@ -95,11 +90,7 @@ public void analyzeEvents() {
simpleWaitFound && interruptedFound);
}
- @Test
- public void test() throws Exception {
- Runnable interrupter = () -> {
- helper.interrupt();
- };
+ private void testInterruption() throws Exception{
Runnable interrupted = () -> {
try {
@@ -109,76 +100,84 @@ public void test() throws Exception {
// should get interrupted
}
};
+ interruptedThread = new Thread(interrupted);
- Runnable simpleWaiter = () -> {
- helper.simpleWait();
- };
-
- Runnable simpleNotifier = () -> {
- helper.simpleNotify();
+ Runnable interrupter = () -> {
+ try {
+ while (!interruptedThread.getState().equals(Thread.State.WAITING)) {
+ Thread.sleep(10);
+ }
+ } catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
+ helper.interrupt();
};
- Thread interrupterThread = new Thread(interrupter);
- Thread interruptedThread = new Thread(interrupted);
- helper.interrupted = interruptedThread;
- interrupterName = interrupterThread.getName();
- interruptedName = interruptedThread.getName();
+ interrupterThread = new Thread(interrupter);
interruptedThread.start();
- Thread.sleep(MILLIS); // pause to ensure expected ordering of lock acquisition
interrupterThread.start();
-
interruptedThread.join();
interrupterThread.join();
+ }
- Thread tw = new Thread(simpleWaiter);
- Thread tn = new Thread(simpleNotifier);
- simpleWaitName = tw.getName();
- simpleNotifyName = tn.getName();
+ private void testWaitNotify() throws Exception{
+ Runnable simpleWaiter = () -> {
+ helper.simpleWait();
+ };
- tw.start();
- Thread.sleep(50);
- tn.start();
+ Runnable simpleNotifier = () -> {
+ try {
+ while (!simpleWaitThread.getState().equals(Thread.State.WAITING)) {
+ Thread.sleep(10);
+ }
+ helper.simpleNotify();
+ }catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
+ };
- tw.join();
- tn.join();
+ simpleWaitThread = new Thread(simpleWaiter);
+ simpleNotifyThread = new Thread(simpleNotifier);
- // sleep so we know the event is recorded
- Thread.sleep(500);
+ simpleWaitThread.start();
+ simpleNotifyThread.start();
+ simpleWaitThread.join();
+ simpleNotifyThread.join();
+ }
+ @Test
+ public void test() throws Exception {
+ testInterruption();
+ System.out.println("*** testInterruption done");
+ testWaitNotify();
}
static class Helper {
public Thread interrupted;
public synchronized void interrupted() throws InterruptedException {
- try {
- wait();
- } catch (InterruptedException e) {
- throw new InterruptedException("expected interrupt");
- }
+ wait();
}
public synchronized void interrupt() {
try {
- interrupted.interrupt();
- } catch (Exception e) {
+ Thread.sleep(MILLIS);
+ interruptedThread.interrupt();
+ }catch (Exception e) {
+ Assert.fail(e.getMessage());
}
-
}
public synchronized void simpleWait() {
try {
wait();
} catch (Exception e) {
-
+ Assert.fail(e.getMessage());
}
}
- public synchronized void simpleNotify() {
- try {
- Thread.sleep(2 * MILLIS);
+ public synchronized void simpleNotify() throws InterruptedException {
+ Thread.sleep(MILLIS);
notify();
- } catch (Exception e) {
- }
}
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
index 3a9c933c3419..5c3777634f0c 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
@@ -42,10 +42,11 @@
public class TestJavaMonitorWaitNotifyAll extends JfrTest {
private static final int MILLIS = 50;
- static Helper helper = new Helper();
- static String waiterName1;
- static String waiterName2;
- static String notifierName;
+ static final Helper helper = new Helper();
+ static Thread producerThread1;
+ static Thread producerThread2;
+ static Thread consumerThread;
+
private boolean notifierFound = false;
private int waitersFound = 0;
@@ -54,40 +55,36 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
- @Override
- public void analyzeEvents() {
- List events;
- try {
- events = getEvents("TestJavaMonitorWaitNotifyAll");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ public void validateEvents() throws Throwable{
+ List events = getEvents("TestJavaMonitorWaitNotifyAll");
+
+
for (RecordedEvent event : events) {
RecordedObject struct = event;
if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
continue;
}
- String eventThread = struct. getValue("eventThread").getJavaName();
- String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
- if (!eventThread.equals(waiterName1) &&
- !eventThread.equals(waiterName2) &&
- !eventThread.equals(notifierName)) {
+ String eventThread = struct.getValue("eventThread").getJavaName();
+ String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null;
+ if (!eventThread.equals(producerThread1.getName()) &&
+ !eventThread.equals(producerThread2.getName()) &&
+ !eventThread.equals(consumerThread.getName())) {
continue;
}
- if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ if (!struct.getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
- assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()));
-
- if (eventThread.equals(notifierName)) {
- assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue());
+ assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS);
+ if (eventThread.equals(consumerThread.getName())) {
+ assertTrue("Should have timed out.", struct.getValue("timedOut").booleanValue());
notifierFound = true;
} else {
- assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
- assertTrue("Notifier thread name is incorrect", notifThread.equals(notifierName));
+ assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue());
+ assertTrue("Notifier thread name is incorrect", notifThread.equals(consumerThread.getName()));
waitersFound++;
}
+
}
assertTrue("Couldn't find expected wait events. NotifierFound: " + notifierFound + " waitersFound: " + waitersFound,
notifierFound && waitersFound == 2);
@@ -95,38 +92,35 @@ public void analyzeEvents() {
@Test
public void test() throws Exception {
- Runnable consumer = () -> {
+ Runnable producer = () -> {
try {
- helper.consume();
+ helper.produce();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
- Runnable producer = () -> {
+ producerThread1 = new Thread(producer);
+ producerThread2 = new Thread(producer);
+ Runnable consumer = () -> {
try {
- helper.produce();
+ while (!producerThread1.getState().equals(Thread.State.WAITING) || !producerThread2.getState().equals(Thread.State.WAITING)) {
+ Thread.sleep(10);
+ }
+ helper.consume();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
- Thread tc1 = new Thread(consumer);
- Thread tp1 = new Thread(producer);
- Thread tp2 = new Thread(producer);
- waiterName1 = tp1.getName();
- waiterName2 = tp2.getName();
- notifierName = tc1.getName();
-
- tp1.start();
- tp2.start();
- tc1.start();
-
- tp1.join();
- tp2.join();
- tc1.join();
-
- // sleep so we know the event is recorded
- Thread.sleep(500);
+
+ consumerThread = new Thread(consumer);
+ consumerThread.start();
+ producerThread1.start();
+ producerThread2.start();
+
+ consumerThread.join();
+ producerThread1.join();
+ producerThread2.join();
}
static class Helper {
@@ -135,7 +129,6 @@ public synchronized void produce() throws InterruptedException {
}
public synchronized void consume() throws InterruptedException {
- // give the producers a headstart so they can start waiting
wait(MILLIS);
notifyAll(); // should wake up both producers
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
index b2dc3deec0e2..13e00b9d9dd3 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -28,11 +28,11 @@
import static org.junit.Assert.assertTrue;
-import java.io.IOException;
-import java.time.Duration;
+
import java.util.List;
import org.junit.Test;
+import org.junit.Assert;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
@@ -41,11 +41,12 @@
public class TestJavaMonitorWaitTimeout extends JfrTest {
private static final int MILLIS = 50;
- static Helper helper = new Helper();
- static String timeOutName;
- static String notifierName;
- static String simpleWaitName;
- static String simpleNotifyName;
+ static final Helper helper = new Helper();
+ static Thread unheardNotifierThread;
+ static Thread timeoutThread;
+
+ static Thread simpleWaitThread;
+ static Thread simpleNotifyThread;
private boolean timeoutFound = false;
private boolean simpleWaitFound = false;
@@ -54,14 +55,10 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
- @Override
- public void analyzeEvents() {
+ public void validateEvents() throws Throwable{
List events;
- try {
- events = getEvents("TestJavaMonitorWaitTimeout");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ events = getEvents("TestJavaMonitorWaitTimeout");
+
for (RecordedEvent event : events) {
RecordedObject struct = event;
if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
@@ -69,22 +66,22 @@ public void analyzeEvents() {
}
String eventThread = struct. getValue("eventThread").getJavaName();
String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
- if (!eventThread.equals(notifierName) &&
- !eventThread.equals(timeOutName) &&
- !eventThread.equals(simpleNotifyName) &&
- !eventThread.equals(simpleWaitName)) {
+ if (!eventThread.equals(unheardNotifierThread.getName()) &&
+ !eventThread.equals(timeoutThread.getName()) &&
+ !eventThread.equals(simpleNotifyThread.getName()) &&
+ !eventThread.equals(simpleWaitThread.getName())) {
continue;
}
if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
- assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()));
- if (eventThread.equals(timeOutName)) {
+ assertTrue("Event is wrong duration:"+event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS);
+ if (eventThread.equals(timeoutThread.getName())) {
assertTrue("Notifier of timeout thread should be null", notifThread == null);
assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue());
timeoutFound = true;
- } else if (eventThread.equals(simpleWaitName)) {
- assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyName));
+ } else if (eventThread.equals(simpleWaitThread.getName())) {
+ assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyThread.getName()));
simpleWaitFound = true;
}
@@ -93,13 +90,13 @@ public void analyzeEvents() {
simpleWaitFound && timeoutFound);
}
- @Test
- public void test() throws Exception {
+
+ private void testTimeout() throws InterruptedException {
Runnable unheardNotifier = () -> {
try {
helper.unheardNotify();
} catch (InterruptedException e) {
- throw new RuntimeException(e);
+ Assert.fail(e.getMessage());
}
};
@@ -107,51 +104,51 @@ public void test() throws Exception {
try {
helper.timeout();
} catch (InterruptedException e) {
- throw new RuntimeException(e);
+ Assert.fail(e.getMessage());
}
};
+ unheardNotifierThread = new Thread(unheardNotifier);
+ timeoutThread = new Thread(timouter);
+
+ timeoutThread.start();
+ timeoutThread.join();
+
+ //wait for timeout before trying to notify
+ unheardNotifierThread.start();
+ unheardNotifierThread.join();
+
+ }
+
+ private void testWaitNotify() throws Exception{
Runnable simpleWaiter = () -> {
- try {
helper.simpleWait();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
};
Runnable simpleNotifier = () -> {
try {
+ while (!simpleWaitThread.getState().equals(Thread.State.WAITING)) {
+ Thread.sleep(10);
+ }
helper.simpleNotify();
} catch (InterruptedException e) {
- throw new RuntimeException(e);
+ Assert.fail(e.getMessage());
}
};
- Thread unheardNotifierThread = new Thread(unheardNotifier);
- Thread timeoutThread = new Thread(timouter);
- timeOutName = timeoutThread.getName();
- notifierName = unheardNotifierThread.getName();
-
- timeoutThread.start();
- Thread.sleep(10);
- unheardNotifierThread.start();
-
- timeoutThread.join();
- unheardNotifierThread.join();
- Thread tw = new Thread(simpleWaiter);
- Thread tn = new Thread(simpleNotifier);
- simpleWaitName = tw.getName();
- simpleNotifyName = tn.getName();
+ simpleWaitThread = new Thread(simpleWaiter);
+ simpleNotifyThread = new Thread(simpleNotifier);
- tw.start();
- Thread.sleep(10);
- tn.start();
-
- tw.join();
- tn.join();
-
- // sleep so we know the event is recorded
- Thread.sleep(500);
+ simpleWaitThread.start();
+ simpleNotifyThread.start();
+ simpleWaitThread.join();
+ simpleNotifyThread.join();
+ }
+ @Test
+ public void test() throws Exception {
+ testTimeout();
+ System.out.println("timeout test done");
+ testWaitNotify();
}
static class Helper {
@@ -160,18 +157,20 @@ public synchronized void timeout() throws InterruptedException {
}
public synchronized void unheardNotify() throws InterruptedException {
- Thread.sleep(2 * MILLIS);
- // notify after timeout
notify();
}
- public synchronized void simpleWait() throws InterruptedException {
- wait();
+ public synchronized void simpleWait() {
+ try {
+ wait();
+ } catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
}
public synchronized void simpleNotify() throws InterruptedException {
- Thread.sleep(2 * MILLIS);
- notify();
+ Thread.sleep(MILLIS);
+ notify();
}
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
index 3ec1535e8e41..e0f5a2242405 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
@@ -46,8 +46,7 @@ public String[] getTestedEvents() {
return new String[]{"jdk.ThreadSleep"};
}
- @Override
- public void analyzeEvents() {
+ public void validateEvents() {
List events;
try {
events = getEvents("jdk.ThreadSleep");
@@ -64,7 +63,7 @@ public void analyzeEvents() {
if (!eventThread.equals(sleepingThreadName)) {
continue;
}
- if (!isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS))) {
+ if (event.getDuration().toMillis() < MILLIS) {
continue;
}
foundSleepEvent = true;
From ed0f1ffadb7b9a53a123ed7f25e0b978af0125c6 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Fri, 7 Oct 2022 15:19:53 -0400
Subject: [PATCH 08/21] filter in getEvents and checkstyle
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 11 +++--
.../src/com/oracle/svm/test/jfr/Stressor.java | 46 -------------------
.../svm/test/jfr/TestJavaMonitorEnter.java | 21 +++------
.../svm/test/jfr/TestJavaMonitorWait.java | 6 +--
.../jfr/TestJavaMonitorWaitInterrupt.java | 19 ++++----
.../jfr/TestJavaMonitorWaitNotifyAll.java | 22 ++++-----
.../test/jfr/TestJavaMonitorWaitTimeout.java | 21 ++++-----
.../oracle/svm/test/jfr/TestThreadSleep.java | 4 --
8 files changed, 39 insertions(+), 111 deletions(-)
delete mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index 5963f7a0a4a2..ce8f5082075c 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -31,11 +31,11 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
-import java.time.Duration;
+import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Comparator;
import org.graalvm.nativeimage.ImageInfo;
import org.graalvm.nativeimage.hosted.Feature;
@@ -59,7 +59,6 @@ public abstract class JfrTest {
protected Jfr jfr;
protected Recording recording;
private final ChronologicalComparator chronologicalComparator = new ChronologicalComparator();
- protected final long msTolerance = 10;
@BeforeClass
public static void checkForJFR() {
@@ -91,7 +90,7 @@ public void endRecording() {
checkEvents();
try {
validateEvents();
- }catch (Throwable throwable) {
+ } catch (Throwable throwable) {
Assert.fail("validateEvents failed: " + throwable.getMessage());
}
try {
@@ -115,7 +114,7 @@ protected void enableEvents(String[] events) {
public abstract String[] getTestedEvents();
- public void validateEvents() throws Throwable{
+ public void validateEvents() throws Throwable {
}
protected void checkEvents() {
@@ -166,6 +165,8 @@ protected List getEvents(String testName) throws IOException {
Path p = makeCopy(testName);
List events = RecordingFile.readAllEvents(p);
Collections.sort(events, chronologicalComparator);
+ // remove events that are not in the list of tested events
+ events.removeIf(event -> (Arrays.stream(getTestedEvents()).noneMatch(testedEvent -> (testedEvent.equals(event.getEventType().getName())))));
return events;
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
deleted file mode 100644
index 8667633738ea..000000000000
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.test.jfr;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class to help run multiple threads executing some task.
- */
-public class Stressor {
- public static void execute(int numberOfThreads, Runnable task) throws Exception {
- List threads = new ArrayList<>();
- for (int n = 0; n < numberOfThreads; ++n) {
- Thread t = new Thread(task);
- threads.add(t);
- t.start();
- }
- for (Thread t : threads) {
- t.join();
- }
- }
-}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index 40dd2fabfb27..04323f5cc878 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -28,9 +28,7 @@
import static org.junit.Assert.assertTrue;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Queue;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedThread;
@@ -46,14 +44,13 @@ public class TestJavaMonitorEnter extends JfrTest {
static Thread firstThread;
static Thread secondThread;
static final Helper helper = new Helper();
- private static Queue orderedWaiterNames = new LinkedList<>();
@Override
public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorEnter"};
}
- public void validateEvents() throws Throwable{
+ public void validateEvents() throws Throwable {
List events;
events = getEvents("jdk.JavaMonitorEnter");
int count = 0;
@@ -61,19 +58,15 @@ public void validateEvents() throws Throwable{
for (RecordedEvent event : events) {
RecordedObject struct = event;
String eventThread = struct. getValue("eventThread").getJavaName();
- if (event.getEventType().getName().equals("jdk.JavaMonitorEnter")
- && struct. getValue("monitorClass").getName().equals(Helper.class.getName())
- && event.getDuration().toMillis() >= MILLIS
- && secondThread.getName().equals(eventThread)) {
+ if (struct. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS && secondThread.getName().equals(eventThread)) {
// verify previous owner
- assertTrue("Previous owner is wrong",struct. getValue("previousOwner").getJavaName().equals(firstThread.getName()));
+ assertTrue("Previous owner is wrong", struct. getValue("previousOwner").getJavaName().equals(firstThread.getName()));
found = true;
break;
}
}
- assertTrue("Expected monitor blocked event not found" , found);
-
+ assertTrue("Expected monitor blocked event not found", found);
}
@Test
@@ -88,8 +81,8 @@ public void test() throws Exception {
Runnable second = () -> {
try {
- //wait until lock is held
- while(!inCritical) {
+ // wait until lock is held
+ while (!inCritical) {
Thread.sleep(10);
}
helper.doWork();
@@ -116,7 +109,7 @@ private synchronized void doWork() throws InterruptedException {
}
// spin until second thread blocks
- while(!secondThread.getState().equals(Thread.State.BLOCKED)) {
+ while (!secondThread.getState().equals(Thread.State.BLOCKED)) {
Thread.sleep(10);
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
index 0a5db97f8d0d..b3bacff5cf5c 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
@@ -30,8 +30,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import java.io.IOException;
-import java.time.Duration;
import java.util.List;
import jdk.jfr.consumer.RecordedClass;
@@ -53,7 +51,7 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
- public void validateEvents() throws Throwable{
+ public void validateEvents() throws Throwable {
List events;
events = getEvents("jdk.JavaMonitorWait");
@@ -65,7 +63,7 @@ public void validateEvents() throws Throwable{
String eventThread = struct. getValue("eventThread").getJavaName();
String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
assertTrue("No event thread", eventThread != null);
- if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) || !event.getEventType().getName().equals("jdk.JavaMonitorWait") ||
+ if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) ||
!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index ed53f5379fd9..d6b1ca908b5a 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -55,15 +55,12 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
- public void validateEvents() throws Throwable{
+ public void validateEvents() throws Throwable {
List events;
events = getEvents("TestJavaMonitorWaitInterrupt");
for (RecordedEvent event : events) {
RecordedObject struct = event;
- if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
- continue;
- }
String eventThread = struct. getValue("eventThread").getJavaName();
String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
if (!eventThread.equals(interrupterThread.getName()) &&
@@ -90,7 +87,7 @@ public void validateEvents() throws Throwable{
simpleWaitFound && interruptedFound);
}
- private void testInterruption() throws Exception{
+ private void testInterruption() throws Exception {
Runnable interrupted = () -> {
try {
@@ -120,7 +117,7 @@ private void testInterruption() throws Exception{
interrupterThread.join();
}
- private void testWaitNotify() throws Exception{
+ private void testWaitNotify() throws Exception {
Runnable simpleWaiter = () -> {
helper.simpleWait();
};
@@ -131,7 +128,7 @@ private void testWaitNotify() throws Exception{
Thread.sleep(10);
}
helper.simpleNotify();
- }catch (Exception e) {
+ } catch (Exception e) {
Assert.fail(e.getMessage());
}
};
@@ -144,10 +141,10 @@ private void testWaitNotify() throws Exception{
simpleWaitThread.join();
simpleNotifyThread.join();
}
+
@Test
public void test() throws Exception {
testInterruption();
- System.out.println("*** testInterruption done");
testWaitNotify();
}
@@ -162,7 +159,7 @@ public synchronized void interrupt() {
try {
Thread.sleep(MILLIS);
interruptedThread.interrupt();
- }catch (Exception e) {
+ } catch (Exception e) {
Assert.fail(e.getMessage());
}
}
@@ -176,8 +173,8 @@ public synchronized void simpleWait() {
}
public synchronized void simpleNotify() throws InterruptedException {
- Thread.sleep(MILLIS);
- notify();
+ Thread.sleep(MILLIS);
+ notify();
}
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
index 5c3777634f0c..44b19b2f07d2 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
@@ -29,8 +29,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import java.io.IOException;
-import java.time.Duration;
import java.util.List;
import org.junit.Test;
@@ -55,32 +53,28 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
- public void validateEvents() throws Throwable{
+ public void validateEvents() throws Throwable {
List events = getEvents("TestJavaMonitorWaitNotifyAll");
-
for (RecordedEvent event : events) {
RecordedObject struct = event;
- if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
- continue;
- }
- String eventThread = struct.getValue("eventThread").getJavaName();
- String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null;
+ String eventThread = struct. getValue("eventThread").getJavaName();
+ String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
if (!eventThread.equals(producerThread1.getName()) &&
- !eventThread.equals(producerThread2.getName()) &&
- !eventThread.equals(consumerThread.getName())) {
+ !eventThread.equals(producerThread2.getName()) &&
+ !eventThread.equals(consumerThread.getName())) {
continue;
}
- if (!struct.getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS);
if (eventThread.equals(consumerThread.getName())) {
- assertTrue("Should have timed out.", struct.getValue("timedOut").booleanValue());
+ assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue());
notifierFound = true;
} else {
- assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue());
+ assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
assertTrue("Notifier thread name is incorrect", notifThread.equals(consumerThread.getName()));
waitersFound++;
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
index 13e00b9d9dd3..2b443949e3b3 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -28,7 +28,6 @@
import static org.junit.Assert.assertTrue;
-
import java.util.List;
import org.junit.Test;
@@ -55,15 +54,12 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
- public void validateEvents() throws Throwable{
+ public void validateEvents() throws Throwable {
List events;
events = getEvents("TestJavaMonitorWaitTimeout");
for (RecordedEvent event : events) {
RecordedObject struct = event;
- if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) {
- continue;
- }
String eventThread = struct. getValue("eventThread").getJavaName();
String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
if (!eventThread.equals(unheardNotifierThread.getName()) &&
@@ -75,7 +71,7 @@ public void validateEvents() throws Throwable{
if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
- assertTrue("Event is wrong duration:"+event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS);
+ assertTrue("Event is wrong duration:" + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS);
if (eventThread.equals(timeoutThread.getName())) {
assertTrue("Notifier of timeout thread should be null", notifThread == null);
assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue());
@@ -90,7 +86,6 @@ public void validateEvents() throws Throwable{
simpleWaitFound && timeoutFound);
}
-
private void testTimeout() throws InterruptedException {
Runnable unheardNotifier = () -> {
try {
@@ -114,15 +109,15 @@ private void testTimeout() throws InterruptedException {
timeoutThread.start();
timeoutThread.join();
- //wait for timeout before trying to notify
+ // wait for timeout before trying to notify
unheardNotifierThread.start();
unheardNotifierThread.join();
}
- private void testWaitNotify() throws Exception{
+ private void testWaitNotify() throws Exception {
Runnable simpleWaiter = () -> {
- helper.simpleWait();
+ helper.simpleWait();
};
Runnable simpleNotifier = () -> {
@@ -144,10 +139,10 @@ private void testWaitNotify() throws Exception{
simpleWaitThread.join();
simpleNotifyThread.join();
}
+
@Test
public void test() throws Exception {
testTimeout();
- System.out.println("timeout test done");
testWaitNotify();
}
@@ -169,8 +164,8 @@ public synchronized void simpleWait() {
}
public synchronized void simpleNotify() throws InterruptedException {
- Thread.sleep(MILLIS);
- notify();
+ Thread.sleep(MILLIS);
+ notify();
}
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
index e0f5a2242405..4ccd02c4e01f 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
@@ -31,7 +31,6 @@
import jdk.jfr.consumer.RecordedThread;
import java.io.IOException;
-import java.time.Duration;
import java.util.List;
import static org.junit.Assert.assertTrue;
@@ -55,9 +54,6 @@ public void validateEvents() {
}
boolean foundSleepEvent = false;
for (RecordedEvent event : events) {
- if (!event.getEventType().getName().equals("jdk.ThreadSleep")) {
- continue;
- }
RecordedObject struct = event;
String eventThread = struct. getValue("eventThread").getJavaName();
if (!eventThread.equals(sleepingThreadName)) {
From 5b37c49ab731c1a2e2a77a9a563fbd84feb97928 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Fri, 7 Oct 2022 15:44:30 -0400
Subject: [PATCH 09/21] fixes for gate check
---
.../oracle/svm/test/jfr/TestJavaMonitorEnter.java | 1 +
.../oracle/svm/test/jfr/TestJavaMonitorWait.java | 1 +
.../svm/test/jfr/TestJavaMonitorWaitInterrupt.java | 3 ++-
.../svm/test/jfr/TestJavaMonitorWaitNotifyAll.java | 1 +
.../svm/test/jfr/TestJavaMonitorWaitTimeout.java | 13 +++++--------
5 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index 04323f5cc878..31b337c5cb8b 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -50,6 +50,7 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorEnter"};
}
+ @Override
public void validateEvents() throws Throwable {
List events;
events = getEvents("jdk.JavaMonitorEnter");
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
index b3bacff5cf5c..64cbb358e9aa 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
@@ -51,6 +51,7 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
+ @Override
public void validateEvents() throws Throwable {
List events;
events = getEvents("jdk.JavaMonitorWait");
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index d6b1ca908b5a..f859748fcbf9 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -55,6 +55,7 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
+ @Override
public void validateEvents() throws Throwable {
List events;
events = getEvents("TestJavaMonitorWaitInterrupt");
@@ -87,7 +88,7 @@ public void validateEvents() throws Throwable {
simpleWaitFound && interruptedFound);
}
- private void testInterruption() throws Exception {
+ private static void testInterruption() throws Exception {
Runnable interrupted = () -> {
try {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
index 44b19b2f07d2..f8965e42d113 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
@@ -53,6 +53,7 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
+ @Override
public void validateEvents() throws Throwable {
List events = getEvents("TestJavaMonitorWaitNotifyAll");
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
index 2b443949e3b3..95c68b396873 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -54,6 +54,7 @@ public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
}
+ @Override
public void validateEvents() throws Throwable {
List events;
events = getEvents("TestJavaMonitorWaitTimeout");
@@ -86,13 +87,9 @@ public void validateEvents() throws Throwable {
simpleWaitFound && timeoutFound);
}
- private void testTimeout() throws InterruptedException {
+ private static void testTimeout() throws InterruptedException {
Runnable unheardNotifier = () -> {
- try {
- helper.unheardNotify();
- } catch (InterruptedException e) {
- Assert.fail(e.getMessage());
- }
+ helper.unheardNotify();
};
Runnable timouter = () -> {
@@ -115,7 +112,7 @@ private void testTimeout() throws InterruptedException {
}
- private void testWaitNotify() throws Exception {
+ private static void testWaitNotify() throws Exception {
Runnable simpleWaiter = () -> {
helper.simpleWait();
};
@@ -151,7 +148,7 @@ public synchronized void timeout() throws InterruptedException {
wait(MILLIS);
}
- public synchronized void unheardNotify() throws InterruptedException {
+ public synchronized void unheardNotify() {
notify();
}
From 4e26325fd18a6066ab6663de85c81b02083df41a Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Fri, 7 Oct 2022 16:03:18 -0400
Subject: [PATCH 10/21] more fixes for gate
---
.../src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java | 1 -
.../com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java | 2 +-
.../src/com/oracle/svm/test/jfr/TestThreadSleep.java | 1 +
3 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index 31b337c5cb8b..d8e9284bb15a 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -54,7 +54,6 @@ public String[] getTestedEvents() {
public void validateEvents() throws Throwable {
List events;
events = getEvents("jdk.JavaMonitorEnter");
- int count = 0;
boolean found = false;
for (RecordedEvent event : events) {
RecordedObject struct = event;
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index f859748fcbf9..bcf2b32e6478 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -118,7 +118,7 @@ private static void testInterruption() throws Exception {
interrupterThread.join();
}
- private void testWaitNotify() throws Exception {
+ private static void testWaitNotify() throws Exception {
Runnable simpleWaiter = () -> {
helper.simpleWait();
};
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
index 4ccd02c4e01f..d915b6ee9406 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
@@ -45,6 +45,7 @@ public String[] getTestedEvents() {
return new String[]{"jdk.ThreadSleep"};
}
+ @Override
public void validateEvents() {
List events;
try {
From 417d9f714d55c5f03bc5da4d5dd0cd0ec7e9bd72 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Tue, 11 Oct 2022 10:43:15 -0400
Subject: [PATCH 11/21] set flag before blocking/waiting
---
.../src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java | 8 +++++---
.../oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java | 8 ++++++--
.../oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java | 4 +++-
.../oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java | 4 +++-
4 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index d8e9284bb15a..1aac13f6f09f 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -29,6 +29,7 @@
import static org.junit.Assert.assertTrue;
import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedThread;
@@ -40,7 +41,8 @@
public class TestJavaMonitorEnter extends JfrTest {
private static final int MILLIS = 60;
- static boolean inCritical = false;
+ static volatile boolean inCritical = false;
+ static volatile boolean blockedAtCritical = false;
static Thread firstThread;
static Thread secondThread;
static final Helper helper = new Helper();
@@ -85,12 +87,12 @@ public void test() throws Exception {
while (!inCritical) {
Thread.sleep(10);
}
+ blockedAtCritical = true;
helper.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
-
firstThread = new Thread(first);
secondThread = new Thread(second);
firstThread.start();
@@ -109,7 +111,7 @@ private synchronized void doWork() throws InterruptedException {
}
// spin until second thread blocks
- while (!secondThread.getState().equals(Thread.State.BLOCKED)) {
+ while (!secondThread.getState().equals(Thread.State.BLOCKED) || !blockedAtCritical) {
Thread.sleep(10);
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index bcf2b32e6478..8f108a86c230 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -49,6 +49,7 @@ public class TestJavaMonitorWaitInterrupt extends JfrTest {
private boolean interruptedFound = false;
private boolean simpleWaitFound = false;
+ static volatile boolean waiting = false;
@Override
public String[] getTestedEvents() {
@@ -102,7 +103,7 @@ private static void testInterruption() throws Exception {
Runnable interrupter = () -> {
try {
- while (!interruptedThread.getState().equals(Thread.State.WAITING)) {
+ while (!interruptedThread.getState().equals(Thread.State.WAITING) || !waiting) {
Thread.sleep(10);
}
} catch (Exception e) {
@@ -116,6 +117,7 @@ private static void testInterruption() throws Exception {
interrupterThread.start();
interruptedThread.join();
interrupterThread.join();
+ waiting = false;
}
private static void testWaitNotify() throws Exception {
@@ -125,7 +127,7 @@ private static void testWaitNotify() throws Exception {
Runnable simpleNotifier = () -> {
try {
- while (!simpleWaitThread.getState().equals(Thread.State.WAITING)) {
+ while (!simpleWaitThread.getState().equals(Thread.State.WAITING) || !waiting) {
Thread.sleep(10);
}
helper.simpleNotify();
@@ -153,6 +155,7 @@ static class Helper {
public Thread interrupted;
public synchronized void interrupted() throws InterruptedException {
+ waiting = true;
wait();
}
@@ -167,6 +170,7 @@ public synchronized void interrupt() {
public synchronized void simpleWait() {
try {
+ waiting = true;
wait();
} catch (Exception e) {
Assert.fail(e.getMessage());
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
index f8965e42d113..326647b97b6f 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
@@ -47,6 +47,7 @@ public class TestJavaMonitorWaitNotifyAll extends JfrTest {
private boolean notifierFound = false;
private int waitersFound = 0;
+ static volatile int waiting = 0;
@Override
public String[] getTestedEvents() {
@@ -99,7 +100,7 @@ public void test() throws Exception {
producerThread2 = new Thread(producer);
Runnable consumer = () -> {
try {
- while (!producerThread1.getState().equals(Thread.State.WAITING) || !producerThread2.getState().equals(Thread.State.WAITING)) {
+ while (!producerThread1.getState().equals(Thread.State.WAITING) || !producerThread2.getState().equals(Thread.State.WAITING) || waiting < 2) {
Thread.sleep(10);
}
helper.consume();
@@ -120,6 +121,7 @@ public void test() throws Exception {
static class Helper {
public synchronized void produce() throws InterruptedException {
+ waiting++;
wait();
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
index 95c68b396873..7c570994a82a 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -48,6 +48,7 @@ public class TestJavaMonitorWaitTimeout extends JfrTest {
static Thread simpleNotifyThread;
private boolean timeoutFound = false;
private boolean simpleWaitFound = false;
+ static volatile boolean waiting = false;
@Override
public String[] getTestedEvents() {
@@ -119,7 +120,7 @@ private static void testWaitNotify() throws Exception {
Runnable simpleNotifier = () -> {
try {
- while (!simpleWaitThread.getState().equals(Thread.State.WAITING)) {
+ while (!simpleWaitThread.getState().equals(Thread.State.WAITING) || !waiting) {
Thread.sleep(10);
}
helper.simpleNotify();
@@ -154,6 +155,7 @@ public synchronized void unheardNotify() {
public synchronized void simpleWait() {
try {
+ waiting = true;
wait();
} catch (Exception e) {
Assert.fail(e.getMessage());
From 2902c0bca1e8bf1ee4462ca47e14f7ae3843e4bd Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Tue, 11 Oct 2022 13:50:45 -0400
Subject: [PATCH 12/21] update wait tests
---
.../jfr/TestJavaMonitorWaitInterrupt.java | 53 +++++++++----------
.../jfr/TestJavaMonitorWaitNotifyAll.java | 22 ++++----
.../test/jfr/TestJavaMonitorWaitTimeout.java | 28 +++++-----
3 files changed, 50 insertions(+), 53 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index 8f108a86c230..2610e97fb608 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -49,7 +49,7 @@ public class TestJavaMonitorWaitInterrupt extends JfrTest {
private boolean interruptedFound = false;
private boolean simpleWaitFound = false;
- static volatile boolean waiting = false;
+ static volatile boolean inCritical = false;
@Override
public String[] getTestedEvents() {
@@ -74,7 +74,7 @@ public void validateEvents() throws Throwable {
if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
- assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS);
+ assertTrue("Event is wrong duration." + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS);
assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
if (eventThread.equals(interruptedThread.getName())) {
@@ -93,7 +93,7 @@ private static void testInterruption() throws Exception {
Runnable interrupted = () -> {
try {
- helper.interrupted();
+ helper.interrupt();// must enter first
throw new RuntimeException("Was not interrupted!!");
} catch (InterruptedException e) {
// should get interrupted
@@ -103,13 +103,13 @@ private static void testInterruption() throws Exception {
Runnable interrupter = () -> {
try {
- while (!interruptedThread.getState().equals(Thread.State.WAITING) || !waiting) {
+ while (!inCritical) {
Thread.sleep(10);
}
- } catch (Exception e) {
+ helper.interrupt();
+ } catch (InterruptedException e) {
Assert.fail(e.getMessage());
}
- helper.interrupt();
};
interrupterThread = new Thread(interrupter);
@@ -117,17 +117,20 @@ private static void testInterruption() throws Exception {
interrupterThread.start();
interruptedThread.join();
interrupterThread.join();
- waiting = false;
}
private static void testWaitNotify() throws Exception {
Runnable simpleWaiter = () -> {
- helper.simpleWait();
+ try {
+ helper.simpleNotify();
+ } catch (InterruptedException e) {
+ Assert.fail(e.getMessage());
+ }
};
Runnable simpleNotifier = () -> {
try {
- while (!simpleWaitThread.getState().equals(Thread.State.WAITING) || !waiting) {
+ while (!inCritical) {
Thread.sleep(10);
}
helper.simpleNotify();
@@ -148,38 +151,32 @@ private static void testWaitNotify() throws Exception {
@Test
public void test() throws Exception {
testInterruption();
+ inCritical = false; // reset
testWaitNotify();
}
static class Helper {
public Thread interrupted;
- public synchronized void interrupted() throws InterruptedException {
- waiting = true;
- wait();
- }
-
- public synchronized void interrupt() {
- try {
+ public synchronized void interrupt() throws InterruptedException {
+ if (Thread.currentThread().equals(interruptedThread)) {
+ inCritical = true; // Ensure T1 enters critical section first
+ wait(); // allow T2 to enter section
+ } else if (Thread.currentThread().equals(interrupterThread)) {
+ // If T2 is in the critical section T1 is already waiting.
Thread.sleep(MILLIS);
interruptedThread.interrupt();
- } catch (Exception e) {
- Assert.fail(e.getMessage());
}
}
- public synchronized void simpleWait() {
- try {
- waiting = true;
+ public synchronized void simpleNotify() throws InterruptedException {
+ if (Thread.currentThread().equals(simpleWaitThread)) {
+ inCritical = true;
wait();
- } catch (Exception e) {
- Assert.fail(e.getMessage());
+ } else if (Thread.currentThread().equals(simpleNotifyThread)) {
+ Thread.sleep(MILLIS);
+ notify();
}
}
-
- public synchronized void simpleNotify() throws InterruptedException {
- Thread.sleep(MILLIS);
- notify();
- }
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
index 326647b97b6f..d10bc2934a12 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
@@ -90,7 +90,7 @@ public void validateEvents() throws Throwable {
public void test() throws Exception {
Runnable producer = () -> {
try {
- helper.produce();
+ helper.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
@@ -100,10 +100,10 @@ public void test() throws Exception {
producerThread2 = new Thread(producer);
Runnable consumer = () -> {
try {
- while (!producerThread1.getState().equals(Thread.State.WAITING) || !producerThread2.getState().equals(Thread.State.WAITING) || waiting < 2) {
+ while (waiting < 2) {
Thread.sleep(10);
}
- helper.consume();
+ helper.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
@@ -120,14 +120,14 @@ public void test() throws Exception {
}
static class Helper {
- public synchronized void produce() throws InterruptedException {
- waiting++;
- wait();
- }
-
- public synchronized void consume() throws InterruptedException {
- wait(MILLIS);
- notifyAll(); // should wake up both producers
+ public synchronized void doWork() throws InterruptedException {
+ if (Thread.currentThread().equals(consumerThread)) {
+ wait(MILLIS);
+ notifyAll(); // should wake up both producers
+ } else {
+ waiting++;
+ wait();
+ }
}
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
index 7c570994a82a..dbfef958f76b 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -48,7 +48,7 @@ public class TestJavaMonitorWaitTimeout extends JfrTest {
static Thread simpleNotifyThread;
private boolean timeoutFound = false;
private boolean simpleWaitFound = false;
- static volatile boolean waiting = false;
+ static volatile boolean inCritical = false;
@Override
public String[] getTestedEvents() {
@@ -115,16 +115,20 @@ private static void testTimeout() throws InterruptedException {
private static void testWaitNotify() throws Exception {
Runnable simpleWaiter = () -> {
- helper.simpleWait();
+ try {
+ helper.simpleNotify();
+ } catch (InterruptedException e) {
+ Assert.fail(e.getMessage());
+ }
};
Runnable simpleNotifier = () -> {
try {
- while (!simpleWaitThread.getState().equals(Thread.State.WAITING) || !waiting) {
+ while (!inCritical) {
Thread.sleep(10);
}
helper.simpleNotify();
- } catch (InterruptedException e) {
+ } catch (Exception e) {
Assert.fail(e.getMessage());
}
};
@@ -153,18 +157,14 @@ public synchronized void unheardNotify() {
notify();
}
- public synchronized void simpleWait() {
- try {
- waiting = true;
+ public synchronized void simpleNotify() throws InterruptedException {
+ if (Thread.currentThread().equals(simpleWaitThread)) {
+ inCritical = true;
wait();
- } catch (Exception e) {
- Assert.fail(e.getMessage());
+ } else if (Thread.currentThread().equals(simpleNotifyThread)) {
+ Thread.sleep(MILLIS);
+ notify();
}
}
-
- public synchronized void simpleNotify() throws InterruptedException {
- Thread.sleep(MILLIS);
- notify();
- }
}
}
From 4955e216db10acce661fe37c9d145e075f1a9da8 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Tue, 11 Oct 2022 14:22:12 -0400
Subject: [PATCH 13/21] fix error and checkstyle
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 3 ---
.../src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java | 1 -
.../oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java | 2 +-
.../src/com/oracle/svm/test/jfr/TestThreadSleep.java | 6 +++---
4 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index e4c2ae660734..119f4922a664 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -85,7 +85,6 @@ public void endRecording() {
} catch (Exception e) {
Assert.fail("Fail to stop recording! Cause: " + e.getMessage());
}
- checkEvents();
try {
validateEvents();
} catch (Throwable throwable) {
@@ -110,8 +109,6 @@ private void enableEvents() {
}
}
- public abstract String[] getTestedEvents();
-
public void validateEvents() throws Throwable {
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index 1aac13f6f09f..42d2ef5d1c32 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -29,7 +29,6 @@
import static org.junit.Assert.assertTrue;
import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedThread;
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index 2610e97fb608..da89bb44cbd0 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -93,7 +93,7 @@ private static void testInterruption() throws Exception {
Runnable interrupted = () -> {
try {
- helper.interrupt();// must enter first
+ helper.interrupt(); // must enter first
throw new RuntimeException("Was not interrupted!!");
} catch (InterruptedException e) {
// should get interrupted
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
index d915b6ee9406..e474bc1e6aeb 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
@@ -37,7 +37,7 @@
import org.junit.Test;
public class TestThreadSleep extends JfrTest {
- private String sleepingThreadName;
+ private Thread sleepingThread;
private static final int MILLIS = 50;
@Override
@@ -57,7 +57,7 @@ public void validateEvents() {
for (RecordedEvent event : events) {
RecordedObject struct = event;
String eventThread = struct. getValue("eventThread").getJavaName();
- if (!eventThread.equals(sleepingThreadName)) {
+ if (!eventThread.equals(sleepingThread.getName())) {
continue;
}
if (event.getDuration().toMillis() < MILLIS) {
@@ -71,7 +71,7 @@ public void validateEvents() {
@Test
public void test() throws Exception {
- sleepingThreadName = Thread.currentThread().getName();
+ sleepingThread = Thread.currentThread();
Thread.sleep(MILLIS);
}
}
From cd40dc267ca88fe4ee954b984bdc9595122c8f68 Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Tue, 11 Oct 2022 15:02:37 -0400
Subject: [PATCH 14/21] update visibility
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index 119f4922a664..81efa0e0c80d 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -56,8 +56,8 @@
/** Base class for JFR unit tests. */
public abstract class JfrTest {
- protected Jfr jfr;
- protected Recording recording;
+ private Jfr jfr;
+ private Recording recording;
private final ChronologicalComparator chronologicalComparator = new ChronologicalComparator();
@BeforeClass
@@ -112,7 +112,7 @@ private void enableEvents() {
public void validateEvents() throws Throwable {
}
- protected void checkEvents() {
+ private void checkEvents() {
HashSet seenEvents = new HashSet<>();
try (RecordingFile recordingFile = new RecordingFile(recording.getDestination())) {
while (recordingFile.hasMoreEvents()) {
From 656ddfd9ee352bd999a287cceaef5418fa66ea0f Mon Sep 17 00:00:00 2001
From: Robert Toyonaga
Date: Wed, 12 Oct 2022 15:55:22 -0400
Subject: [PATCH 15/21] rename variable. Have child threads start eachother
---
.../svm/test/jfr/TestJavaMonitorEnter.java | 19 +++++--------------
.../jfr/TestJavaMonitorWaitInterrupt.java | 16 +++-------------
.../jfr/TestJavaMonitorWaitNotifyAll.java | 14 +++++---------
.../test/jfr/TestJavaMonitorWaitTimeout.java | 9 +--------
4 files changed, 14 insertions(+), 44 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
index 42d2ef5d1c32..66df0367e616 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
@@ -39,9 +39,7 @@
public class TestJavaMonitorEnter extends JfrTest {
private static final int MILLIS = 60;
-
- static volatile boolean inCritical = false;
- static volatile boolean blockedAtCritical = false;
+ static volatile boolean passedCheckpoint = false;
static Thread firstThread;
static Thread secondThread;
static final Helper helper = new Helper();
@@ -82,11 +80,7 @@ public void test() throws Exception {
Runnable second = () -> {
try {
- // wait until lock is held
- while (!inCritical) {
- Thread.sleep(10);
- }
- blockedAtCritical = true;
+ passedCheckpoint = true;
helper.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
@@ -95,7 +89,6 @@ public void test() throws Exception {
firstThread = new Thread(first);
secondThread = new Thread(second);
firstThread.start();
- secondThread.start();
firstThread.join();
secondThread.join();
@@ -103,19 +96,17 @@ public void test() throws Exception {
static class Helper {
private synchronized void doWork() throws InterruptedException {
- inCritical = true;
if (Thread.currentThread().equals(secondThread)) {
- inCritical = false;
return; // second thread doesn't need to do work.
}
+ // ensure ordering of critical section entry
+ secondThread.start();
// spin until second thread blocks
- while (!secondThread.getState().equals(Thread.State.BLOCKED) || !blockedAtCritical) {
+ while (!secondThread.getState().equals(Thread.State.BLOCKED) || !passedCheckpoint) {
Thread.sleep(10);
}
-
Thread.sleep(MILLIS);
- inCritical = false;
}
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
index da89bb44cbd0..959665391729 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
@@ -46,10 +46,8 @@ public class TestJavaMonitorWaitInterrupt extends JfrTest {
static Thread interrupterThread;
static Thread simpleWaitThread;
static Thread simpleNotifyThread;
-
private boolean interruptedFound = false;
private boolean simpleWaitFound = false;
- static volatile boolean inCritical = false;
@Override
public String[] getTestedEvents() {
@@ -103,9 +101,6 @@ private static void testInterruption() throws Exception {
Runnable interrupter = () -> {
try {
- while (!inCritical) {
- Thread.sleep(10);
- }
helper.interrupt();
} catch (InterruptedException e) {
Assert.fail(e.getMessage());
@@ -114,7 +109,6 @@ private static void testInterruption() throws Exception {
interrupterThread = new Thread(interrupter);
interruptedThread.start();
- interrupterThread.start();
interruptedThread.join();
interrupterThread.join();
}
@@ -130,9 +124,6 @@ private static void testWaitNotify() throws Exception {
Runnable simpleNotifier = () -> {
try {
- while (!inCritical) {
- Thread.sleep(10);
- }
helper.simpleNotify();
} catch (Exception e) {
Assert.fail(e.getMessage());
@@ -143,7 +134,6 @@ private static void testWaitNotify() throws Exception {
simpleNotifyThread = new Thread(simpleNotifier);
simpleWaitThread.start();
- simpleNotifyThread.start();
simpleWaitThread.join();
simpleNotifyThread.join();
}
@@ -151,7 +141,6 @@ private static void testWaitNotify() throws Exception {
@Test
public void test() throws Exception {
testInterruption();
- inCritical = false; // reset
testWaitNotify();
}
@@ -160,7 +149,8 @@ static class Helper {
public synchronized void interrupt() throws InterruptedException {
if (Thread.currentThread().equals(interruptedThread)) {
- inCritical = true; // Ensure T1 enters critical section first
+ // Ensure T1 enters critical section first
+ interrupterThread.start();
wait(); // allow T2 to enter section
} else if (Thread.currentThread().equals(interrupterThread)) {
// If T2 is in the critical section T1 is already waiting.
@@ -171,7 +161,7 @@ public synchronized void interrupt() throws InterruptedException {
public synchronized void simpleNotify() throws InterruptedException {
if (Thread.currentThread().equals(simpleWaitThread)) {
- inCritical = true;
+ simpleNotifyThread.start();
wait();
} else if (Thread.currentThread().equals(simpleNotifyThread)) {
Thread.sleep(MILLIS);
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
index d10bc2934a12..ca225eb1ccd1 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
@@ -47,7 +47,6 @@ public class TestJavaMonitorWaitNotifyAll extends JfrTest {
private boolean notifierFound = false;
private int waitersFound = 0;
- static volatile int waiting = 0;
@Override
public String[] getTestedEvents() {
@@ -100,9 +99,6 @@ public void test() throws Exception {
producerThread2 = new Thread(producer);
Runnable consumer = () -> {
try {
- while (waiting < 2) {
- Thread.sleep(10);
- }
helper.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
@@ -110,10 +106,7 @@ public void test() throws Exception {
};
consumerThread = new Thread(consumer);
- consumerThread.start();
producerThread1.start();
- producerThread2.start();
-
consumerThread.join();
producerThread1.join();
producerThread2.join();
@@ -124,8 +117,11 @@ public synchronized void doWork() throws InterruptedException {
if (Thread.currentThread().equals(consumerThread)) {
wait(MILLIS);
notifyAll(); // should wake up both producers
- } else {
- waiting++;
+ } else if (Thread.currentThread().equals(producerThread1)){
+ producerThread2.start();
+ wait();
+ } else if (Thread.currentThread().equals(producerThread2)){
+ consumerThread.start();
wait();
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
index dbfef958f76b..ff367fd236fb 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
@@ -43,13 +43,10 @@ public class TestJavaMonitorWaitTimeout extends JfrTest {
static final Helper helper = new Helper();
static Thread unheardNotifierThread;
static Thread timeoutThread;
-
static Thread simpleWaitThread;
static Thread simpleNotifyThread;
private boolean timeoutFound = false;
private boolean simpleWaitFound = false;
- static volatile boolean inCritical = false;
-
@Override
public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
@@ -124,9 +121,6 @@ private static void testWaitNotify() throws Exception {
Runnable simpleNotifier = () -> {
try {
- while (!inCritical) {
- Thread.sleep(10);
- }
helper.simpleNotify();
} catch (Exception e) {
Assert.fail(e.getMessage());
@@ -137,7 +131,6 @@ private static void testWaitNotify() throws Exception {
simpleNotifyThread = new Thread(simpleNotifier);
simpleWaitThread.start();
- simpleNotifyThread.start();
simpleWaitThread.join();
simpleNotifyThread.join();
}
@@ -159,7 +152,7 @@ public synchronized void unheardNotify() {
public synchronized void simpleNotify() throws InterruptedException {
if (Thread.currentThread().equals(simpleWaitThread)) {
- inCritical = true;
+ simpleNotifyThread.start();
wait();
} else if (Thread.currentThread().equals(simpleNotifyThread)) {
Thread.sleep(MILLIS);
From 3275f58578fc8b1fa8e87fa0332457b139e57f39 Mon Sep 17 00:00:00 2001
From: Christian Haeubl
Date: Fri, 20 Jan 2023 16:33:13 +0100
Subject: [PATCH 16/21] Various fixes and improvements for JFR.
---
substratevm/mx.substratevm/suite.py | 1 +
.../core/genscavenge/JfrGCEventSupport.java | 5 +-
.../posix/PosixSubstrateSigprofHandler.java | 103 +++--
.../posix/thread/PosixPlatformThreads.java | 5 +-
.../com/oracle/svm/core/JavaMainWrapper.java | 8 +-
.../svm/core/RuntimeAnalysisWorkarounds.java | 57 ---
.../oracle/svm/core/SubstrateDiagnostics.java | 53 +--
.../svm/core/SubstrateSegfaultHandler.java | 2 -
.../oracle/svm/core/VMInspectionOptions.java | 5 +
.../oracle/svm/core/code/CodeInfoAccess.java | 90 ----
.../oracle/svm/core/code/CodeInfoDecoder.java | 185 +++++++-
.../oracle/svm/core/code/CodeInfoEncoder.java | 2 +-
.../svm/core/code/FrameInfoDecoder.java | 116 +++--
.../svm/core/code/FrameInfoEncoder.java | 43 +-
.../svm/core/code/FrameInfoQueryResult.java | 6 +-
.../svm/core/code/ReusableTypeReader.java | 17 +
.../svm/core/heap/ReferenceHandlerThread.java | 12 +-
.../jdk/management/ManagementFeature.java | 5 +-
.../jdk/management/ManagementSupport.java | 10 +-
.../oracle/svm/core/jfr/JfrChunkWriter.java | 40 +-
.../src/com/oracle/svm/core/jfr/JfrEvent.java | 28 +-
.../com/oracle/svm/core/jfr/JfrFeature.java | 17 +-
.../oracle/svm/core/jfr/JfrJavaEvents.java | 17 +-
.../svm/core/jfr/JfrMethodRepository.java | 50 +--
.../svm/core/jfr/JfrNativeEventWriter.java | 20 +-
.../svm/core/jfr/JfrRecorderThread.java | 29 +-
.../svm/core/jfr/JfrStackTraceRepository.java | 298 ++++++-------
.../oracle/svm/core/jfr/JfrThreadLocal.java | 153 ++++---
.../svm/core/jfr/JfrThreadRepository.java | 5 -
.../com/oracle/svm/core/jfr/SubstrateJVM.java | 244 +++++++---
.../core/jfr/Target_jdk_jfr_internal_JVM.java | 14 +-
.../events/EndChunkNativePeriodicEvents.java | 10 +-
.../EveryChunkNativePeriodicEvents.java | 5 +-
.../jfr/events/ExecuteVMOperationEvent.java | 14 +-
.../core/jfr/events/ExecutionSampleEvent.java | 42 +-
.../jfr/events/JavaMonitorEnterEvent.java | 7 +-
.../core/jfr/events/JavaMonitorWaitEvent.java | 7 +-
.../core/jfr/events/SafepointBeginEvent.java | 3 +-
.../core/jfr/events/SafepointEndEvent.java | 3 +-
.../svm/core/jfr/events/ThreadEndEvent.java | 8 +-
.../svm/core/jfr/events/ThreadParkEvent.java | 9 +-
...pEvent.java => ThreadSleepEventJDK17.java} | 12 +-
.../svm/core/jfr/events/ThreadStartEvent.java | 13 +-
.../sampler/AbstractJfrExecutionSampler.java | 316 +++++++++++++
.../sampler/JfrExecutionSampler.java} | 41 +-
.../jfr/sampler/JfrNoExecutionSampler.java | 99 +++++
.../JfrRecurringCallbackExecutionSampler.java | 172 ++++++++
.../oracle/svm/core/monitor/JavaMonitor.java | 3 +-
.../JavaMonitorQueuedSynchronizer.java | 4 +-
.../sampler/CallStackFrameMethodData.java | 1 +
.../sampler/CallStackFrameMethodInfo.java | 10 +-
.../svm/core/sampler/ProfilingSampler.java | 5 -
.../sampler/SafepointProfilingSampler.java | 34 +-
.../svm/core/sampler/SamplerBuffer.java | 24 -
.../svm/core/sampler/SamplerBufferAccess.java | 20 -
.../svm/core/sampler/SamplerBufferPool.java | 164 ++++---
.../svm/core/sampler/SamplerBufferStack.java | 6 +-
.../core/sampler/SamplerBuffersAccess.java | 267 +++++++----
.../svm/core/sampler/SamplerIsolateLocal.java | 92 ----
.../svm/core/sampler/SamplerSampleWriter.java | 36 +-
.../core/sampler/SamplerSampleWriterData.java | 12 +
.../SamplerSampleWriterDataAccess.java | 25 +-
.../core/sampler/SamplerStackWalkVisitor.java | 16 +-
.../svm/core/sampler/SamplerThreadLocal.java | 153 -------
.../core/sampler/SubstrateSigprofHandler.java | 417 +++++-------------
.../svm/core/stack/ThreadStackPrinter.java | 55 ++-
.../oracle/svm/core/thread/JavaThreads.java | 65 ++-
.../svm/core/thread/JavaVMOperation.java | 23 +-
.../svm/core/thread/NativeVMOperation.java | 30 +-
.../core/thread/NativeVMOperationData.java | 6 +
.../svm/core/thread/PlatformThreads.java | 32 +-
.../com/oracle/svm/core/thread/Safepoint.java | 2 +-
.../svm/core/thread/ThreadListener.java | 16 +-
.../core/thread/ThreadListenerSupport.java | 14 +-
.../svm/core/thread/ThreadingSupportImpl.java | 57 ++-
.../oracle/svm/core/thread/VMOperation.java | 13 +-
.../svm/core/thread/VMOperationControl.java | 15 +-
.../com/oracle/svm/core/thread/VMThreads.java | 10 +-
.../hosted/image/NativeImageCodeCache.java | 2 +-
.../svm/hosted/jfr/JfrEventFeature.java | 36 +-
.../svm/hosted/jfr/JfrEventSubstitution.java | 66 ++-
.../src/com/oracle/svm/test/jfr/JfrTest.java | 49 +-
.../oracle/svm/test/jfr/TestClassEvent.java | 3 +-
...er.java => TestJavaMonitorEnterEvent.java} | 33 +-
...ait.java => TestJavaMonitorWaitEvent.java} | 28 +-
...=> TestJavaMonitorWaitInterruptEvent.java} | 46 +-
...=> TestJavaMonitorWaitNotifyAllEvent.java} | 44 +-
...a => TestJavaMonitorWaitTimeoutEvent.java} | 44 +-
.../svm/test/jfr/TestStackTraceEvent.java | 34 --
.../oracle/svm/test/jfr/TestStringEvent.java | 3 +-
...adSleep.java => TestThreadSleepEvent.java} | 27 +-
91 files changed, 2526 insertions(+), 1917 deletions(-)
delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAnalysisWorkarounds.java
rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/{ThreadSleepEvent.java => ThreadSleepEventJDK17.java} (87%)
create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/AbstractJfrExecutionSampler.java
rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{sampler/SamplerHasSupport.java => jfr/sampler/JfrExecutionSampler.java} (55%)
create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrNoExecutionSampler.java
create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrRecurringCallbackExecutionSampler.java
delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java
delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java
rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorEnter.java => TestJavaMonitorEnterEvent.java} (77%)
rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorWait.java => TestJavaMonitorWaitEvent.java} (84%)
rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorWaitInterrupt.java => TestJavaMonitorWaitInterruptEvent.java} (81%)
rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorWaitNotifyAll.java => TestJavaMonitorWaitNotifyAllEvent.java} (77%)
rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorWaitTimeout.java => TestJavaMonitorWaitTimeoutEvent.java} (80%)
rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestThreadSleep.java => TestThreadSleepEvent.java} (81%)
diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index 255e96668a68..aaff649331dc 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -620,6 +620,7 @@
],
"requiresConcealed" : {
"java.base" : [
+ "jdk.internal.event",
"jdk.internal.misc",
"jdk.internal.vm.annotation",
"jdk.internal.org.objectweb.asm",
diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/JfrGCEventSupport.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/JfrGCEventSupport.java
index 5c813ee3c3fc..47126cddf9e2 100644
--- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/JfrGCEventSupport.java
+++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/JfrGCEventSupport.java
@@ -42,7 +42,6 @@
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 com.oracle.svm.core.util.VMError;
class JfrGCEventSupport {
@@ -66,7 +65,7 @@ public int stopGCPhasePause() {
@Uninterruptible(reason = "Accesses a JFR buffer.")
public void emitGarbageCollectionEvent(UnsignedWord gcEpoch, GCCause cause, long start) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.GarbageCollection)) {
+ if (JfrEvent.GarbageCollection.shouldEmit()) {
long pauseTime = JfrTicks.elapsedTicks() - start;
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
@@ -87,7 +86,7 @@ public void emitGarbageCollectionEvent(UnsignedWord gcEpoch, GCCause cause, long
@Uninterruptible(reason = "Accesses a JFR buffer.")
public void emitGCPhasePauseEvent(UnsignedWord gcEpoch, int level, String name, long startTicks) {
JfrEvent event = getGCPhasePauseEvent(level);
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(event)) {
+ if (event.shouldEmit()) {
long end = JfrTicks.elapsedTicks();
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
index 688aedc41bdc..e5cefead86f8 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
@@ -25,86 +25,110 @@
package com.oracle.svm.core.posix;
+import static com.oracle.svm.core.posix.PosixSubstrateSigprofHandler.Options.SignalHandlerBasedExecutionSampler;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.graalvm.compiler.options.Option;
+import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
+import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.nativeimage.c.struct.SizeOf;
import org.graalvm.nativeimage.c.type.VoidPointer;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.word.Pointer;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
+import com.oracle.svm.core.IsolateListenerSupport;
+import com.oracle.svm.core.RegisterDumper;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.c.function.CEntryPointOptions;
-import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
+import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
+import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
import com.oracle.svm.core.headers.LibC;
import com.oracle.svm.core.heap.RestrictHeapAccess;
+import com.oracle.svm.core.jfr.JfrFeature;
+import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
+import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.posix.headers.Pthread;
import com.oracle.svm.core.posix.headers.Signal;
import com.oracle.svm.core.posix.headers.Time;
import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
+import com.oracle.svm.core.thread.ThreadListenerSupport;
+import com.oracle.svm.core.util.TimeUtils;
-@AutomaticallyRegisteredImageSingleton(SubstrateSigprofHandler.class)
public class PosixSubstrateSigprofHandler extends SubstrateSigprofHandler {
-
- public static final long INTERVAL_S = 0;
- public static final long INTERVAL_uS = 20_000;
+ private static final CEntryPointLiteral advancedSignalDispatcher = CEntryPointLiteral.create(PosixSubstrateSigprofHandler.class,
+ "dispatch", int.class, Signal.siginfo_t.class, Signal.ucontext_t.class);
@Platforms(Platform.HOSTED_ONLY.class)
public PosixSubstrateSigprofHandler() {
}
- /** The address of the signal handler for signals handled by Java code, below. */
- private static final CEntryPointLiteral advancedSignalDispatcher = CEntryPointLiteral.create(PosixSubstrateSigprofHandler.class,
- "dispatch", int.class, Signal.siginfo_t.class, Signal.ucontext_t.class);
-
@SuppressWarnings("unused")
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
@CEntryPointOptions(prologue = CEntryPointOptions.NoPrologue.class, epilogue = CEntryPointOptions.NoEpilogue.class)
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate in sigprof signal handler.")
@Uninterruptible(reason = "Signal handler may only execute uninterruptible code.")
private static void dispatch(@SuppressWarnings("unused") int signalNumber, @SuppressWarnings("unused") Signal.siginfo_t sigInfo, Signal.ucontext_t uContext) {
+ /* We need to keep the code in this method to a minimum to avoid races. */
if (tryEnterIsolate()) {
- doUninterruptibleStackWalk(uContext);
+ CodePointer ip = (CodePointer) RegisterDumper.singleton().getIP(uContext);
+ Pointer sp = (Pointer) RegisterDumper.singleton().getSP(uContext);
+ tryUninterruptibleStackWalk(ip, sp);
}
}
- private static void registerSigprofSignal() {
+ private static void registerSigprofSignal(Signal.AdvancedSignalDispatcher dispatcher) {
int structSigActionSize = SizeOf.get(Signal.sigaction.class);
Signal.sigaction structSigAction = UnsafeStackValue.get(structSigActionSize);
LibC.memset(structSigAction, WordFactory.signed(0), WordFactory.unsigned(structSigActionSize));
/* Register sa_sigaction signal handler */
structSigAction.sa_flags(Signal.SA_SIGINFO() | Signal.SA_NODEFER() | Signal.SA_RESTART());
- structSigAction.sa_sigaction(advancedSignalDispatcher.getFunctionPointer());
+ structSigAction.sa_sigaction(dispatcher);
Signal.sigaction(Signal.SignalEnum.SIGPROF.getCValue(), structSigAction, WordFactory.nullPointer());
}
- private static int callSetitimer() {
- /* Call setitimer to start profiling. */
+ @Override
+ protected void updateInterval() {
+ updateInterval(newIntervalMillis);
+ }
+
+ private static void updateInterval(long ms) {
Time.itimerval newValue = UnsafeStackValue.get(Time.itimerval.class);
- Time.itimerval oldValue = UnsafeStackValue.get(Time.itimerval.class);
+ newValue.it_value().set_tv_sec(ms / TimeUtils.millisPerSecond);
+ newValue.it_value().set_tv_usec(ms % TimeUtils.millisPerSecond);
+ newValue.it_interval().set_tv_sec(ms / TimeUtils.millisPerSecond);
+ newValue.it_interval().set_tv_usec(ms % TimeUtils.millisPerSecond);
- newValue.it_value().set_tv_sec(INTERVAL_S);
- newValue.it_value().set_tv_usec(INTERVAL_uS);
- newValue.it_interval().set_tv_sec(INTERVAL_S);
- newValue.it_interval().set_tv_usec(INTERVAL_uS);
+ int status = Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, WordFactory.nullPointer());
+ PosixUtils.checkStatusIs0(status, "setitimer(which, newValue, oldValue): wrong arguments.");
+ }
- return Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, oldValue);
+ @Override
+ protected void installSignalHandler() {
+ registerSigprofSignal(advancedSignalDispatcher.getFunctionPointer());
+ updateInterval();
}
@Override
- protected void install0() {
- registerSigprofSignal();
- PosixUtils.checkStatusIs0(callSetitimer(), "setitimer(which, newValue, oldValue): wrong arguments.");
+ protected void uninstallSignalHandler() {
+ updateInterval(0);
+ registerSigprofSignal((Signal.AdvancedSignalDispatcher) Signal.SIG_DFL());
}
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected UnsignedWord createThreadLocalKey() {
+ protected UnsignedWord createNativeThreadLocal() {
Pthread.pthread_key_tPointer key = StackValue.get(Pthread.pthread_key_tPointer.class);
PosixUtils.checkStatusIs0(Pthread.pthread_key_create(key, WordFactory.nullPointer()), "pthread_key_create(key, keyDestructor): failed.");
return key.read();
@@ -112,25 +136,50 @@ protected UnsignedWord createThreadLocalKey() {
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected void deleteThreadLocalKey(UnsignedWord key) {
+ protected void deleteNativeThreadLocal(UnsignedWord key) {
int resultCode = Pthread.pthread_key_delete((Pthread.pthread_key_t) key);
PosixUtils.checkStatusIs0(resultCode, "pthread_key_delete(key): failed.");
}
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected void setThreadLocalKeyValue(UnsignedWord key, IsolateThread value) {
+ protected void setNativeThreadLocal(UnsignedWord key, IsolateThread value) {
int resultCode = Pthread.pthread_setspecific((Pthread.pthread_key_t) key, (VoidPointer) value);
PosixUtils.checkStatusIs0(resultCode, "pthread_setspecific(key, value): wrong arguments.");
}
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected IsolateThread getThreadLocalKeyValue(UnsignedWord key) {
+ protected IsolateThread getNativeThreadLocal(UnsignedWord key) {
/*
* Although this method is not async-signal-safe in general we rely on
* implementation-specific behavior here.
*/
return (IsolateThread) Pthread.pthread_getspecific((Pthread.pthread_key_t) key);
}
+
+ public static class Options {
+ @Option(help = "Determines if JFR uses a signal handler for execution sampling.")//
+ public static final HostedOptionKey SignalHandlerBasedExecutionSampler = new HostedOptionKey<>(false);
+ }
+}
+
+@AutomaticallyRegisteredFeature
+class PosixSubstrateSigProfHandlerFeature implements InternalFeature {
+ @Override
+ public List> getRequiredFeatures() {
+ return Collections.singletonList(JfrFeature.class);
+ }
+
+ @Override
+ public void afterRegistration(AfterRegistrationAccess access) {
+ if (JfrFeature.isExecutionSamplerSupported() && Platform.includedIn(Platform.LINUX.class) && SignalHandlerBasedExecutionSampler.getValue()) {
+ SubstrateSigprofHandler sampler = new PosixSubstrateSigprofHandler();
+ ImageSingletons.add(JfrExecutionSampler.class, sampler);
+ ImageSingletons.add(SubstrateSigprofHandler.class, sampler);
+
+ ThreadListenerSupport.get().register(sampler);
+ IsolateListenerSupport.singleton().register(sampler);
+ }
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java
index 9724a0de91c8..1b2e14f4f47e 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java
@@ -265,10 +265,7 @@ public OSThreadHandle startThreadUnmanaged(CFunctionPointer threadRoutine, Point
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public boolean joinThreadUnmanaged(OSThreadHandle threadHandle, WordPointer threadExitStatus) {
int status = Pthread.pthread_join_no_transition((Pthread.pthread_t) threadHandle, threadExitStatus);
- if (status != 0) {
- return false;
- }
- return true;
+ return status == 0;
}
@Override
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java
index e6fb30ca62ca..8bd1bebaf2c7 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java
@@ -67,9 +67,9 @@
import com.oracle.svm.core.jdk.InternalVMMethod;
import com.oracle.svm.core.jdk.RuntimeSupport;
import com.oracle.svm.core.log.Log;
-import com.oracle.svm.core.sampler.ProfilingSampler;
import com.oracle.svm.core.thread.JavaThreads;
import com.oracle.svm.core.thread.PlatformThreads;
+import com.oracle.svm.core.thread.ThreadListenerSupport;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.util.Counter;
import com.oracle.svm.core.util.VMError;
@@ -161,9 +161,7 @@ private static int runCore0() {
VMRuntime.initialize();
}
- if (ImageSingletons.contains(ProfilingSampler.class)) {
- ImageSingletons.lookup(ProfilingSampler.class).registerSampler();
- }
+ ThreadListenerSupport.get().beforeThreadRun();
/*
* Invoke the application's main method. Invoking the main method via a method handle
@@ -182,8 +180,6 @@ private static int runCore0() {
* HotSpot VM.
*/
return 1;
- } finally {
- PlatformThreads.exit(Thread.currentThread());
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAnalysisWorkarounds.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAnalysisWorkarounds.java
deleted file mode 100644
index ef281776457f..000000000000
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAnalysisWorkarounds.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2021, 2022, 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. 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;
-
-import com.oracle.svm.core.option.RuntimeOptionKey;
-import com.oracle.svm.core.sampler.CallStackFrameMethodInfo;
-import org.graalvm.compiler.options.Option;
-import org.graalvm.nativeimage.ImageSingletons;
-
-public class RuntimeAnalysisWorkarounds {
-
- public static class Options {
- @Option(help = "Use the option to avoid the initial value of the enterSamplingCodeMethodId constant folding. " +
- "The value of this option must never be set to true in order to keep the correct information in the variable.")//
- static final RuntimeOptionKey ConstantFoldSamplingCodeStartId = new RuntimeOptionKey<>(false) {
- @Override
- public void update(Boolean value) {
- throw new IllegalStateException("This option must never be set.");
- }
- };
-
- }
-
- public static void avoidFoldingSamplingCodeStart() {
- /*
- * Avoid constant folding the initial value of the enterSamplingCodeMethodId. The true value
- * of the id is set during the image build, and is being used in the runtime. By "falsely"
- * setting the value of the id in runtime, the analysis is "tricked" to never perform the
- * folding. The condition must always be false, and it can't be proved as false.
- */
- if (Options.ConstantFoldSamplingCodeStartId.getValue()) {
- ImageSingletons.lookup(CallStackFrameMethodInfo.class).setEnterSamplingCodeMethodId(0);
- }
- }
-}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java
index bf4868d089d2..5ae4eed350de 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java
@@ -28,6 +28,8 @@
import java.util.Arrays;
+import com.oracle.svm.core.code.CodeInfoDecoder;
+import com.oracle.svm.core.code.FrameInfoQueryResult;
import org.graalvm.collections.EconomicMap;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.core.common.NumUtil;
@@ -55,14 +57,9 @@
import com.oracle.svm.core.c.NonmovableArrays;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.code.CodeInfoAccess;
-import com.oracle.svm.core.code.CodeInfoAccess.DummyValueInfoAllocator;
-import com.oracle.svm.core.code.CodeInfoAccess.FrameInfoState;
-import com.oracle.svm.core.code.CodeInfoAccess.SingleShotFrameInfoQueryResultAllocator;
import com.oracle.svm.core.code.CodeInfoTable;
-import com.oracle.svm.core.code.FrameInfoQueryResult;
import com.oracle.svm.core.code.RuntimeCodeInfoHistory;
import com.oracle.svm.core.code.RuntimeCodeInfoMemory;
-import com.oracle.svm.core.code.ReusableTypeReader;
import com.oracle.svm.core.code.UntetheredCodeInfo;
import com.oracle.svm.core.config.ConfigurationValues;
import com.oracle.svm.core.deopt.DeoptimizationSupport;
@@ -958,10 +955,7 @@ public void printDiagnostics(Log log, ErrorContext context, int maxDiagnosticLev
}
private static class ImageCodeLocationInfoPrinter {
- private final ReusableTypeReader frameInfoReader = new ReusableTypeReader();
- private final SingleShotFrameInfoQueryResultAllocator singleShotFrameInfoQueryResultAllocator = new SingleShotFrameInfoQueryResultAllocator();
- private final DummyValueInfoAllocator dummyValueInfoAllocator = new DummyValueInfoAllocator();
- private final FrameInfoState frameInfoState = new FrameInfoState();
+ private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor();
/**
* If {@code value} points into AOT compiled code, then this method prints information about
@@ -970,41 +964,38 @@ private static class ImageCodeLocationInfoPrinter {
* NOTE: this method may only be called by a single thread.
*/
public boolean printLocationInfo(Log log, UnsignedWord value) {
- CodeInfo info = CodeInfoTable.getImageCodeInfo();
- if (info.equal(value)) {
+ CodeInfo imageCodeInfo = CodeInfoTable.getImageCodeInfo();
+ if (imageCodeInfo.equal(value)) {
log.string("is the image CodeInfo object");
return true;
}
- UnsignedWord codeInfoEnd = ((UnsignedWord) info).add(CodeInfoAccess.getSizeOfCodeInfo());
- if (value.aboveOrEqual((UnsignedWord) info) && value.belowThan(codeInfoEnd)) {
- log.string("points inside the image CodeInfo object ").zhex(info);
+ UnsignedWord codeInfoEnd = ((UnsignedWord) imageCodeInfo).add(CodeInfoAccess.getSizeOfCodeInfo());
+ if (value.aboveOrEqual((UnsignedWord) imageCodeInfo) && value.belowThan(codeInfoEnd)) {
+ log.string("points inside the image CodeInfo object ").zhex(imageCodeInfo);
return true;
}
- if (CodeInfoAccess.contains(info, (CodePointer) value)) {
- log.string("points into AOT compiled code ");
-
- frameInfoReader.reset();
- frameInfoState.reset();
- CodeInfoAccess.initFrameInfoReader(info, (CodePointer) value, frameInfoReader, frameInfoState);
- if (frameInfoState.entryOffset >= 0) {
- FrameInfoQueryResult frameInfo;
- FrameInfoQueryResult rootInfo = null;
- do {
- frameInfo = CodeInfoAccess.nextFrameInfo(info, frameInfoReader, singleShotFrameInfoQueryResultAllocator.reload(), dummyValueInfoAllocator, frameInfoState);
- if (frameInfo != null) {
- rootInfo = frameInfo;
- }
- } while (frameInfo != null);
-
- rootInfo.log(log);
+ if (CodeInfoAccess.contains(imageCodeInfo, (CodePointer) value)) {
+ FrameInfoQueryResult compilationRoot = getCompilationRoot(imageCodeInfo, (CodePointer) value);
+ if (compilationRoot != null) {
+ log.string("points into AOT compiled code ");
+ compilationRoot.log(log);
}
return true;
}
return false;
}
+
+ private FrameInfoQueryResult getCompilationRoot(CodeInfo imageCodeInfo, CodePointer ip) {
+ FrameInfoQueryResult rootInfo = null;
+ frameInfoCursor.initialize(imageCodeInfo, ip);
+ while (frameInfoCursor.advance()) {
+ rootInfo = frameInfoCursor.get();
+ }
+ return rootInfo;
+ }
}
public static class DiagnosticLevel {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
index 075a432ebbd6..6f1aac00256c 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
@@ -58,7 +58,6 @@
import com.oracle.svm.core.stack.StackOverflowCheck;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.thread.VMThreads.SafepointBehavior;
-import com.oracle.svm.core.util.VMError;
@AutomaticallyRegisteredFeature
class SubstrateSegfaultHandlerFeature implements InternalFeature {
@@ -72,7 +71,6 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
ImageSingletons.add(SingleIsolateSegfaultSetup.class, singleIsolateSegfaultSetup);
IsolateListenerSupport.singleton().register(singleIsolateSegfaultSetup);
- VMError.guarantee(ImageSingletons.contains(RegisterDumper.class));
RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSegfaultHandlerStartupHook());
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java
index f0778145c83a..69c41977aeb6 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java
@@ -84,6 +84,11 @@ public static boolean hasHeapDumpSupport() {
return hasAllOrKeywordMonitoringSupport(MONITORING_HEAPDUMP_NAME) && !Platform.includedIn(WINDOWS.class);
}
+ /**
+ * Use {@link com.oracle.svm.core.jfr.HasJfrSupport#get()} instead and don't call this method
+ * directly because the VM inspection options are only one of multiple ways to enable the JFR
+ * support.
+ */
@Fold
public static boolean hasJfrSupport() {
return hasAllOrKeywordMonitoringSupport(MONITORING_JFR_NAME) && !Platform.includedIn(WINDOWS.class);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java
index ffca5383ece7..43624d5362b7 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java
@@ -37,7 +37,6 @@
import com.oracle.svm.core.c.NonmovableArray;
import com.oracle.svm.core.c.NonmovableArrays;
import com.oracle.svm.core.c.NonmovableObjectArray;
-import com.oracle.svm.core.code.FrameInfoDecoder.ValueInfoAllocator;
import com.oracle.svm.core.deopt.SubstrateInstalledCode;
import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
import com.oracle.svm.core.heap.Heap;
@@ -244,23 +243,6 @@ public static CodePointer absoluteIP(CodeInfo info, long relativeIP) {
return (CodePointer) ((UnsignedWord) cast(info).getCodeStart()).add(WordFactory.unsigned(relativeIP));
}
- public static void initFrameInfoReader(CodeInfo info, CodePointer ip, ReusableTypeReader frameInfoReader, FrameInfoState state) {
- long entryOffset = CodeInfoDecoder.lookupCodeInfoEntryOffset(info, relativeIP(info, ip));
- state.entryOffset = entryOffset;
- if (entryOffset >= 0) {
- if (!CodeInfoDecoder.initFrameInfoReader(info, entryOffset, frameInfoReader)) {
- state.entryOffset = -1;
- }
- }
- }
-
- public static FrameInfoQueryResult nextFrameInfo(CodeInfo info, ReusableTypeReader frameInfoReader,
- FrameInfoDecoder.FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator, FrameInfoState state) {
- int entryFlags = CodeInfoDecoder.loadEntryFlags(info, state.entryOffset);
- boolean isDeoptEntry = CodeInfoDecoder.extractFI(entryFlags) == CodeInfoDecoder.FI_DEOPT_ENTRY_INDEX_S4;
- return FrameInfoDecoder.decodeFrameInfo(isDeoptEntry, frameInfoReader, info, resultAllocator, valueInfoAllocator, state);
- }
-
@SuppressWarnings("unchecked")
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
static T getObjectField(CodeInfo info, int index) {
@@ -444,78 +426,6 @@ public static UnsignedWord getSizeOfCodeInfo() {
return SizeOf.unsigned(CodeInfoImpl.class);
}
- public static class FrameInfoState {
- public static final int NO_SUCCESSOR_INDEX_MARKER = -1;
-
- public long entryOffset;
- public boolean isFirstFrame;
- public boolean isDone;
- public int firstValue;
- public int successorIndex;
-
- public FrameInfoState() {
- reset();
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public FrameInfoState reset() {
- entryOffset = -1;
- isFirstFrame = true;
- isDone = false;
- firstValue = -1;
- successorIndex = NO_SUCCESSOR_INDEX_MARKER;
- return this;
- }
- }
-
- public static class SingleShotFrameInfoQueryResultAllocator implements FrameInfoDecoder.FrameInfoQueryResultAllocator {
- private static final FrameInfoQueryResult frameInfoQueryResult = new FrameInfoQueryResult();
-
- private boolean fired;
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public SingleShotFrameInfoQueryResultAllocator reload() {
- fired = false;
- return this;
- }
-
- @Override
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public FrameInfoQueryResult newFrameInfoQueryResult() {
- if (fired) {
- return null;
- }
- fired = true;
- frameInfoQueryResult.init();
- return frameInfoQueryResult;
- }
- }
-
- public static class DummyValueInfoAllocator implements ValueInfoAllocator {
- @Override
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public FrameInfoQueryResult.ValueInfo newValueInfo() {
- return null;
- }
-
- @Override
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public FrameInfoQueryResult.ValueInfo[] newValueInfoArray(int len) {
- return null;
- }
-
- @Override
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public FrameInfoQueryResult.ValueInfo[][] newValueInfoArrayArray(int len) {
- return null;
- }
-
- @Override
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public void decodeConstant(FrameInfoQueryResult.ValueInfo valueInfo, NonmovableObjectArray> frameInfoObjectConstants) {
- }
- }
-
public enum HasInstalledCode {
Yes,
No,
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
index 9aad23d823ff..5adc33766eaf 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
@@ -30,9 +30,13 @@
import org.graalvm.compiler.core.common.util.TypeConversion;
import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.c.function.CodePointer;
import com.oracle.svm.core.AlwaysInline;
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.c.NonmovableObjectArray;
import com.oracle.svm.core.heap.ReferenceMapIndex;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.util.Counter;
@@ -72,7 +76,8 @@ public static class Options {
private CodeInfoDecoder() {
}
- static long lookupCodeInfoEntryOffset(CodeInfo info, long ip) {
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static long lookupCodeInfoEntryOffset(CodeInfo info, long ip) {
long entryIP = lookupEntryIP(ip);
long entryOffset = loadEntryOffset(info, ip);
do {
@@ -339,14 +344,6 @@ private static boolean isDeoptEntryPoint(CodeInfo info, long entryOffset, int en
}
}
- static boolean initFrameInfoReader(CodeInfo info, long entryOffset, ReusableTypeReader frameInfoReader) {
- int entryFlags = loadEntryFlags(info, entryOffset);
- int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags));
- frameInfoReader.setByteIndex(frameInfoIndex);
- frameInfoReader.setData(CodeInfoAccess.getFrameInfoEncodings(info));
- return extractFI(entryFlags) != FI_NO_DEOPT;
- }
-
private static FrameInfoQueryResult loadFrameInfo(CodeInfo info, long entryOffset, int entryFlags) {
boolean isDeoptEntry;
switch (extractFI(entryFlags)) {
@@ -362,8 +359,7 @@ private static FrameInfoQueryResult loadFrameInfo(CodeInfo info, long entryOffse
throw shouldNotReachHere();
}
int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags));
- return FrameInfoDecoder.decodeFrameInfo(isDeoptEntry, new ReusableTypeReader(CodeInfoAccess.getFrameInfoEncodings(info), frameInfoIndex), info,
- FrameInfoDecoder.HeapBasedFrameInfoQueryResultAllocator, FrameInfoDecoder.HeapBasedValueInfoAllocator, new CodeInfoAccess.FrameInfoState());
+ return FrameInfoDecoder.decodeFrameInfo(isDeoptEntry, new ReusableTypeReader(CodeInfoAccess.getFrameInfoEncodings(info), frameInfoIndex), info);
}
@AlwaysInline("Make IP-lookup loop call free")
@@ -465,6 +461,7 @@ static int extractRM(int entryFlags) {
return (entryFlags & RM_MASK_IN_PLACE) >> RM_SHIFT;
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
static int extractFI(int entryFlags) {
return (entryFlags & FI_MASK_IN_PLACE) >> FI_SHIFT;
}
@@ -497,6 +494,7 @@ private static long offsetRM(long entryOffset, int entryFlags) {
return entryOffset + getU1(RM_OFFSET, entryFlags);
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static long offsetFI(long entryOffset, int entryFlags) {
assert extractFI(entryFlags) != FI_NO_DEOPT;
return entryOffset + getU1(FI_OFFSET, entryFlags);
@@ -513,6 +511,171 @@ private static long advanceOffset(long entryOffset, int entryFlags) {
static CodeInfoDecoderCounters counters() {
return ImageSingletons.lookup(CodeInfoDecoderCounters.class);
}
+
+ /**
+ * This class can be used to iterate the Java-level stack trace information for a given
+ * instruction pointer (IP). A single physical stack frame may correspond to multiple Java-level
+ * stack frames.
+ */
+ public static class FrameInfoCursor {
+ private final ReusableTypeReader frameInfoReader = new ReusableTypeReader();
+ private final SingleShotFrameInfoQueryResultAllocator singleShotFrameInfoQueryResultAllocator = new SingleShotFrameInfoQueryResultAllocator();
+ private final FrameInfoState state = new FrameInfoState();
+
+ private CodeInfo info;
+ private FrameInfoQueryResult result;
+ private boolean canDecode;
+
+ public FrameInfoCursor() {
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ @SuppressWarnings("hiding")
+ public void initialize(CodeInfo info, CodePointer ip) {
+ this.info = info;
+ result = null;
+ frameInfoReader.reset();
+ state.reset();
+ canDecode = initFrameInfoReader(ip);
+ }
+
+ /**
+ * Tries to advance to the next frame. If the method succeeds, it returns {@code true} and
+ * invalidates the data of all {@link FrameInfoQueryResult} objects that were previously
+ * returned by {@link FrameInfoCursor#get}.
+ */
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public boolean advance() {
+ decodeNextEntry();
+ return result != null;
+ }
+
+ /**
+ * Returns the information for the current frame.
+ *
+ * Please note there is no caller and no value information present in the
+ * {@link FrameInfoQueryResult} object (i.e., the methods
+ * {@link FrameInfoQueryResult#getCaller()}, {@link FrameInfoQueryResult#getValueInfos()},
+ * and {@link FrameInfoQueryResult#getVirtualObjects()} will return {@code null}).
+ *
+ * Every {@link FrameInfoCursor} object uses only a single {@link FrameInfoQueryResult}
+ * object internally. Therefore, the values of that object are overwritten when
+ * {@link #advance()} is called to move to the next frame.
+ */
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public FrameInfoQueryResult get() {
+ return result;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private void decodeNextEntry() {
+ if (!canDecode) {
+ return;
+ }
+
+ singleShotFrameInfoQueryResultAllocator.reload();
+ int entryFlags = loadEntryFlags(info, state.entryOffset);
+ boolean isDeoptEntry = extractFI(entryFlags) == FI_DEOPT_ENTRY_INDEX_S4;
+ result = FrameInfoDecoder.decodeFrameInfo(isDeoptEntry, frameInfoReader, info, singleShotFrameInfoQueryResultAllocator, DummyValueInfoAllocator.SINGLETON, state);
+ if (result == null) {
+ /* No more entries. */
+ canDecode = false;
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private boolean initFrameInfoReader(CodePointer ip) {
+ long entryOffset = lookupCodeInfoEntryOffset(info, CodeInfoAccess.relativeIP(info, ip));
+ if (entryOffset >= 0) {
+ int entryFlags = loadEntryFlags(info, entryOffset);
+ int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags));
+ frameInfoReader.setByteIndex(frameInfoIndex);
+ frameInfoReader.setData(CodeInfoAccess.getFrameInfoEncodings(info));
+ if (extractFI(entryFlags) == FI_NO_DEOPT) {
+ entryOffset = -1;
+ }
+ }
+ state.entryOffset = entryOffset;
+ return entryOffset >= 0;
+ }
+ }
+
+ public static class FrameInfoState {
+ public static final int NO_SUCCESSOR_INDEX_MARKER = -1;
+
+ long entryOffset;
+ boolean isFirstFrame;
+ boolean isDone;
+ int firstValue;
+ int successorIndex;
+
+ public FrameInfoState() {
+ reset();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public FrameInfoState reset() {
+ entryOffset = -1;
+ isFirstFrame = true;
+ isDone = false;
+ firstValue = -1;
+ successorIndex = NO_SUCCESSOR_INDEX_MARKER;
+ return this;
+ }
+ }
+
+ private static class SingleShotFrameInfoQueryResultAllocator implements FrameInfoDecoder.FrameInfoQueryResultAllocator {
+ private final FrameInfoQueryResult frameInfoQueryResult = new FrameInfoQueryResult();
+ private boolean fired;
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public SingleShotFrameInfoQueryResultAllocator reload() {
+ fired = false;
+ return this;
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public FrameInfoQueryResult newFrameInfoQueryResult() {
+ if (fired) {
+ return null;
+ }
+ fired = true;
+ frameInfoQueryResult.init();
+ return frameInfoQueryResult;
+ }
+ }
+
+ private static final class DummyValueInfoAllocator implements FrameInfoDecoder.ValueInfoAllocator {
+ static final DummyValueInfoAllocator SINGLETON = new DummyValueInfoAllocator();
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ private DummyValueInfoAllocator() {
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public FrameInfoQueryResult.ValueInfo newValueInfo() {
+ return null;
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public FrameInfoQueryResult.ValueInfo[] newValueInfoArray(int len) {
+ return null;
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public FrameInfoQueryResult.ValueInfo[][] newValueInfoArrayArray(int len) {
+ return null;
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void decodeConstant(FrameInfoQueryResult.ValueInfo valueInfo, NonmovableObjectArray> frameInfoObjectConstants) {
+ }
+ }
}
class CodeInfoDecoderCounters {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java
index 9da0bb4b99a5..20d3d274c07e 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java
@@ -547,7 +547,7 @@ private static void verifyValue(CompilationResult compilation, JavaValue e, Valu
assert lock.isEliminated() == actualValue.isEliminatedMonitor();
expectedValue = lock.getOwner();
} else {
- assert actualValue.isEliminatedMonitor() == false;
+ assert !actualValue.isEliminatedMonitor();
}
if (ValueUtil.isIllegalJavaValue(expectedValue)) {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java
index 2b6eb8ae69a4..380f5fdaecdf 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java
@@ -24,18 +24,19 @@
*/
package com.oracle.svm.core.code;
-import static com.oracle.svm.core.code.CodeInfoAccess.FrameInfoState.NO_SUCCESSOR_INDEX_MARKER;
+import static com.oracle.svm.core.code.CodeInfoDecoder.FrameInfoState.NO_SUCCESSOR_INDEX_MARKER;
import java.util.Arrays;
+import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.core.common.util.TypeConversion;
-import org.graalvm.compiler.core.common.util.TypeReader;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.c.NonmovableArray;
import com.oracle.svm.core.c.NonmovableArrays;
import com.oracle.svm.core.c.NonmovableObjectArray;
+import com.oracle.svm.core.code.CodeInfoDecoder.FrameInfoState;
import com.oracle.svm.core.code.FrameInfoQueryResult.ValueInfo;
import com.oracle.svm.core.code.FrameInfoQueryResult.ValueType;
import com.oracle.svm.core.heap.RestrictHeapAccess;
@@ -177,6 +178,7 @@ private static class CompressedFrameDecoderHelper {
* Differentiates between compressed and uncompressed frame slices. Uncompressed frame
* slices start with {@link #UNCOMPRESSED_FRAME_SLICE_MARKER}.
*/
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean isCompressedFrameSlice(int firstValue) {
return firstValue != UNCOMPRESSED_FRAME_SLICE_MARKER;
}
@@ -185,6 +187,7 @@ private static boolean isCompressedFrameSlice(int firstValue) {
* Determines whether a value is a pointer to a shared frame index. See
* FrameInfoEncoder.encodeCompressedFirstEntry for more details.
*/
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean isSharedFramePointer(int value) {
return value < 0;
}
@@ -193,6 +196,7 @@ private static boolean isSharedFramePointer(int value) {
* Complement of FrameInfoEncoder.encodeCompressedFirstEntry when a shared frame index is
* encoded.
*/
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static int decodeSharedFrameIndex(int value) {
VMError.guarantee(value < UNCOMPRESSED_FRAME_SLICE_MARKER);
@@ -204,6 +208,7 @@ private static int decodeSharedFrameIndex(int value) {
* a uniqueSharedFrameSuccessor. See FrameInfoEncoder.encodeCompressedMethodIndex for more
* details.
*/
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean hasEncodedUniqueSharedFrameSuccessor(int encodedSourceMethodNameIndex) {
return encodedSourceMethodNameIndex < 0;
}
@@ -211,6 +216,7 @@ private static boolean hasEncodedUniqueSharedFrameSuccessor(int encodedSourceMet
/**
* Complement of FrameInfoEncoder.encodeCompressedMethodIndex.
*/
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static int decodeMethodIndex(int methodIndex) {
if (methodIndex < 0) {
return -(methodIndex + COMPRESSED_UNIQUE_SUCCESSOR_ADDEND);
@@ -222,6 +228,7 @@ private static int decodeMethodIndex(int methodIndex) {
/**
* See FrameInfoEncoder.encodeCompressedSourceLineNumber for details.
*/
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean isSliceEnd(int encodedSourceLineNumber) {
return encodedSourceLineNumber < 0;
}
@@ -229,14 +236,20 @@ private static boolean isSliceEnd(int encodedSourceLineNumber) {
/**
* Complement of FrameInfoEncode.encodeCompressedSourceLineNumber.
*/
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static int decodeSourceLineNumber(int sourceLineNumber) {
return UninterruptibleUtils.Math.abs(sourceLineNumber) - COMPRESSED_SOURCE_LINE_ADDEND;
}
}
- protected static FrameInfoQueryResult decodeFrameInfo(boolean isDeoptEntry, TypeReader readBuffer, CodeInfo info,
- FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator, CodeInfoAccess.FrameInfoState state) {
+ protected static FrameInfoQueryResult decodeFrameInfo(boolean isDeoptEntry, ReusableTypeReader readBuffer, CodeInfo info) {
+ return decodeFrameInfo(isDeoptEntry, readBuffer, info, FrameInfoDecoder.HeapBasedFrameInfoQueryResultAllocator, FrameInfoDecoder.HeapBasedValueInfoAllocator, new FrameInfoState());
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected static FrameInfoQueryResult decodeFrameInfo(boolean isDeoptEntry, ReusableTypeReader readBuffer, CodeInfo info,
+ FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator, FrameInfoState state) {
if (state.isFirstFrame) {
state.firstValue = readBuffer.getSVInt();
}
@@ -256,13 +269,14 @@ protected static FrameInfoQueryResult decodeFrameInfo(boolean isDeoptEntry, Type
* See (FrameInfoEncoder.CompressedFrameInfoEncodingMetadata) for more information about the
* compressed encoding format.
*/
- private static FrameInfoQueryResult decodeCompressedFrameInfo(boolean isDeoptEntry, TypeReader readBuffer, CodeInfo info,
- FrameInfoQueryResultAllocator resultAllocator, CodeInfoAccess.FrameInfoState state) {
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static FrameInfoQueryResult decodeCompressedFrameInfo(boolean isDeoptEntry, ReusableTypeReader readBuffer, CodeInfo info, FrameInfoQueryResultAllocator resultAllocator,
+ FrameInfoState state) {
FrameInfoQueryResult result = null;
FrameInfoQueryResult prev = null;
while (!state.isDone) {
- FrameInfoQueryResult cur = resultAllocator.newFrameInfoQueryResult();
+ FrameInfoQueryResult cur = newFrameInfoQueryResult(resultAllocator);
if (cur == null) {
return result;
}
@@ -324,7 +338,13 @@ private static FrameInfoQueryResult decodeCompressedFrameInfo(boolean isDeoptEnt
return result;
}
- private static void decodeCompressedFrameData(TypeReader readBuffer, CodeInfo info, CodeInfoAccess.FrameInfoState state, int sourceClassIndex, FrameInfoQueryResult queryResult) {
+ @Uninterruptible(reason = "Some allocators are interruptible.", calleeMustBe = false)
+ private static FrameInfoQueryResult newFrameInfoQueryResult(FrameInfoQueryResultAllocator resultAllocator) {
+ return resultAllocator.newFrameInfoQueryResult();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static void decodeCompressedFrameData(ReusableTypeReader readBuffer, CodeInfo info, FrameInfoState state, int sourceClassIndex, FrameInfoQueryResult queryResult) {
int encodedSourceMethodNameIndex = readBuffer.getSVInt();
int sourceMethodNameIndex = CompressedFrameDecoderHelper.decodeMethodIndex(encodedSourceMethodNameIndex);
int encodedSourceLineNumber = readBuffer.getSVInt();
@@ -350,20 +370,23 @@ private static void decodeCompressedFrameData(TypeReader readBuffer, CodeInfo in
assert !state.isDone || state.successorIndex == NO_SUCCESSOR_INDEX_MARKER;
}
- private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptEntry, TypeReader readBuffer, CodeInfo info,
- FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator, CodeInfoAccess.FrameInfoState state) {
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptEntry, ReusableTypeReader readBuffer, CodeInfo info,
+ FrameInfoQueryResultAllocator resultAllocator, ValueInfoAllocator valueInfoAllocator, FrameInfoState state) {
FrameInfoQueryResult result = null;
FrameInfoQueryResult prev = null;
ValueInfo[][] virtualObjects = null;
while (true) {
- FrameInfoQueryResult cur = resultAllocator.newFrameInfoQueryResult();
- if (cur == null) {
+ long start = readBuffer.getByteIndex();
+ int encodedBci = readBuffer.getSVInt();
+ if (encodedBci == NO_CALLER_BCI) {
return result;
}
- int encodedBci = readBuffer.getSVInt();
- if (encodedBci == NO_CALLER_BCI) {
+ FrameInfoQueryResult cur = newFrameInfoQueryResult(resultAllocator);
+ if (cur == null) {
+ readBuffer.setByteIndex(start);
return result;
}
@@ -372,7 +395,7 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE
cur.encodedBci = encodedBci;
cur.isDeoptEntry = isDeoptEntry;
- final boolean needLocalValues = encodedBci != NO_LOCAL_INFO_BCI;
+ boolean needLocalValues = encodedBci != NO_LOCAL_INFO_BCI;
if (needLocalValues) {
cur.numLocks = readBuffer.getUVInt();
@@ -388,7 +411,6 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE
if (deoptMethodIndex < 0) {
/* Negative number is a reference to the target method. */
cur.deoptMethod = (SharedMethod) NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoObjectConstants(info), -1 - deoptMethodIndex);
- cur.deoptMethodOffset = cur.deoptMethod.getDeoptOffsetInImage();
} else {
/* Positive number is a directly encoded method offset. */
cur.deoptMethodOffset = deoptMethodIndex;
@@ -396,27 +418,27 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE
int curValueInfosLength = readBuffer.getUVInt();
cur.valueInfos = decodeValues(valueInfoAllocator, curValueInfosLength, readBuffer, CodeInfoAccess.getFrameInfoObjectConstants(info));
- }
- if (state.isFirstFrame && needLocalValues) {
- /* This is the first frame, i.e., the top frame that will be returned. */
- int numVirtualObjects = readBuffer.getUVInt();
- virtualObjects = valueInfoAllocator.newValueInfoArrayArray(numVirtualObjects);
- for (int i = 0; i < numVirtualObjects; i++) {
- int numValues = readBuffer.getUVInt();
- ValueInfo[] decodedValues = decodeValues(valueInfoAllocator, numValues, readBuffer, CodeInfoAccess.getFrameInfoObjectConstants(info));
- if (virtualObjects != null) {
- virtualObjects[i] = decodedValues;
+ if (state.isFirstFrame) {
+ /* This is the first frame, i.e., the top frame that will be returned. */
+ int numVirtualObjects = readBuffer.getUVInt();
+ virtualObjects = newValueInfoArrayArray(valueInfoAllocator, numVirtualObjects);
+ for (int i = 0; i < numVirtualObjects; i++) {
+ int numValues = readBuffer.getUVInt();
+ ValueInfo[] decodedValues = decodeValues(valueInfoAllocator, numValues, readBuffer, CodeInfoAccess.getFrameInfoObjectConstants(info));
+ if (virtualObjects != null) {
+ virtualObjects[i] = decodedValues;
+ }
}
}
}
cur.virtualObjects = virtualObjects;
if (encodeSourceReferences()) {
- final int sourceClassIndex = readBuffer.getSVInt();
- final int sourceMethodNameIndex = readBuffer.getSVInt();
- final int sourceLineNumber = readBuffer.getSVInt();
- final int sourceMethodId = readBuffer.getUVInt();
+ int sourceClassIndex = readBuffer.getSVInt();
+ int sourceMethodNameIndex = readBuffer.getSVInt();
+ int sourceLineNumber = readBuffer.getSVInt();
+ int sourceMethodId = readBuffer.getUVInt();
cur.sourceClassIndex = sourceClassIndex;
cur.sourceMethodNameIndex = sourceMethodNameIndex;
@@ -439,11 +461,17 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE
}
}
- private static ValueInfo[] decodeValues(ValueInfoAllocator valueInfoAllocator, int numValues, TypeReader readBuffer, NonmovableObjectArray> frameInfoObjectConstants) {
- ValueInfo[] valueInfos = valueInfoAllocator.newValueInfoArray(numValues);
+ @Uninterruptible(reason = "Some allocators are interruptible.", calleeMustBe = false)
+ private static ValueInfo[][] newValueInfoArrayArray(ValueInfoAllocator valueInfoAllocator, int numVirtualObjects) {
+ return valueInfoAllocator.newValueInfoArrayArray(numVirtualObjects);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static ValueInfo[] decodeValues(ValueInfoAllocator valueInfoAllocator, int numValues, ReusableTypeReader readBuffer, NonmovableObjectArray> frameInfoObjectConstants) {
+ ValueInfo[] valueInfos = newValueInfoArray(valueInfoAllocator, numValues);
for (int i = 0; i < numValues; i++) {
- ValueInfo valueInfo = valueInfoAllocator.newValueInfo();
+ ValueInfo valueInfo = newValueInfo(valueInfoAllocator);
if (valueInfos != null) {
valueInfos[i] = valueInfo;
}
@@ -462,11 +490,27 @@ private static ValueInfo[] decodeValues(ValueInfoAllocator valueInfoAllocator, i
valueInfo.data = valueInfoData;
}
}
- valueInfoAllocator.decodeConstant(valueInfo, frameInfoObjectConstants);
+ decodeConstant(valueInfoAllocator, frameInfoObjectConstants, valueInfo);
}
return valueInfos;
}
+ @Uninterruptible(reason = "Some allocators are interruptible.", calleeMustBe = false)
+ private static void decodeConstant(ValueInfoAllocator valueInfoAllocator, NonmovableObjectArray> frameInfoObjectConstants, ValueInfo valueInfo) {
+ valueInfoAllocator.decodeConstant(valueInfo, frameInfoObjectConstants);
+ }
+
+ @Uninterruptible(reason = "Some allocators are interruptible.", calleeMustBe = false)
+ private static ValueInfo[] newValueInfoArray(ValueInfoAllocator valueInfoAllocator, int numValues) {
+ return valueInfoAllocator.newValueInfoArray(numValues);
+ }
+
+ @Uninterruptible(reason = "Some allocators are interruptible.", calleeMustBe = false)
+ private static ValueInfo newValueInfo(ValueInfoAllocator valueInfoAllocator) {
+ return valueInfoAllocator.newValueInfo();
+ }
+
+ @Fold
protected static boolean encodeSourceReferences() {
return SubstrateOptions.StackTrace.getValue();
}
@@ -531,18 +575,22 @@ public static void logReadableBci(Log log, long encodedBci) {
/* Allow allocation-free access to ValueType values */
private static final ValueType[] ValueTypeValues = ValueType.values();
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static ValueType extractType(int flags) {
return ValueTypeValues[(flags & TYPE_MASK_IN_PLACE) >> TYPE_SHIFT];
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static JavaKind extractKind(int flags) {
return KIND_VALUES[(flags & KIND_MASK_IN_PLACE) >> KIND_SHIFT];
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean extractIsCompressedReference(int flags) {
return (flags & IS_COMPRESSED_REFERENCE_MASK_IN_PLACE) != 0;
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean extractIsEliminatedMonitor(int flags) {
return ((flags & KIND_MASK_IN_PLACE) >> KIND_SHIFT) == IS_ELIMINATED_MONITOR_KIND_VALUE;
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java
index 0dbebc503526..257206ada795 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java
@@ -24,7 +24,7 @@
*/
package com.oracle.svm.core.code;
-import static com.oracle.svm.core.code.CodeInfoAccess.FrameInfoState.NO_SUCCESSOR_INDEX_MARKER;
+import static com.oracle.svm.core.code.CodeInfoDecoder.FrameInfoState.NO_SUCCESSOR_INDEX_MARKER;
import java.util.ArrayList;
import java.util.BitSet;
@@ -921,7 +921,7 @@ private void encodeUncompressedFrameData(FrameData data, UnsafeArrayTypeWriter e
if (cur.deoptMethod != null) {
deoptMethodIndex = -1 - encoders.objectConstants.getIndex(SubstrateObjectConstant.forObject(cur.deoptMethod));
assert deoptMethodIndex < 0;
- assert cur.deoptMethodOffset == cur.deoptMethod.getDeoptOffsetInImage();
+ assert cur.getDeoptMethodOffset() == cur.deoptMethod.getDeoptOffsetInImage();
} else {
deoptMethodIndex = cur.deoptMethodOffset;
assert deoptMethodIndex >= 0;
@@ -1039,10 +1039,8 @@ private static int encodeCompressedSourceLineNumber(int sourceLineNumber, boolea
void verifyEncoding(CodeInfo info) {
for (FrameData expectedData : allDebugInfos) {
-
- FrameInfoQueryResult actualFrame = FrameInfoDecoder.decodeFrameInfo(expectedData.frame.isDeoptEntry,
- new ReusableTypeReader(CodeInfoAccess.getFrameInfoEncodings(info), expectedData.encodedFrameInfoIndex), info,
- FrameInfoDecoder.HeapBasedFrameInfoQueryResultAllocator, FrameInfoDecoder.HeapBasedValueInfoAllocator, new CodeInfoAccess.FrameInfoState());
+ ReusableTypeReader reader = new ReusableTypeReader(CodeInfoAccess.getFrameInfoEncodings(info), expectedData.encodedFrameInfoIndex);
+ FrameInfoQueryResult actualFrame = FrameInfoDecoder.decodeFrameInfo(expectedData.frame.isDeoptEntry, reader, info);
FrameInfoVerifier.verifyFrames(expectedData, expectedData.frame, actualFrame);
}
}
@@ -1054,26 +1052,27 @@ protected static void verifyFrames(FrameInfoEncoder.FrameData expectedData, Fram
FrameInfoQueryResult actualFrame = actualTopFrame;
while (expectedFrame != null) {
assert actualFrame != null;
- assert expectedFrame.isDeoptEntry == actualFrame.isDeoptEntry;
+ assert expectedFrame.isDeoptEntry() == actualFrame.isDeoptEntry();
assert expectedFrame.hasLocalValueInfo() == actualFrame.hasLocalValueInfo();
if (expectedFrame.hasLocalValueInfo()) {
- assert expectedFrame.encodedBci == actualFrame.encodedBci;
- assert expectedFrame.deoptMethod == null && actualFrame.deoptMethod == null ||
- ((expectedFrame.deoptMethod != null) && expectedFrame.deoptMethod.equals(actualFrame.deoptMethod));
- assert expectedFrame.deoptMethodOffset == actualFrame.deoptMethodOffset;
- assert expectedFrame.numLocals == actualFrame.numLocals;
- assert expectedFrame.numStack == actualFrame.numStack;
- assert expectedFrame.numLocks == actualFrame.numLocks;
-
- verifyValues(expectedFrame.valueInfos, actualFrame.valueInfos);
- assert expectedFrame.virtualObjects == expectedTopFrame.virtualObjects;
- assert actualFrame.virtualObjects == actualTopFrame.virtualObjects;
+ assert expectedFrame.getEncodedBci() == actualFrame.getEncodedBci();
+ assert expectedFrame.getMethodId() == actualFrame.getMethodId();
+ assert expectedFrame.getDeoptMethod() == null && actualFrame.getDeoptMethod() == null ||
+ (expectedFrame.getDeoptMethod() != null && expectedFrame.getDeoptMethod().equals(actualFrame.getDeoptMethod()));
+ assert expectedFrame.getDeoptMethodOffset() == actualFrame.getDeoptMethodOffset();
+ assert expectedFrame.getNumLocals() == actualFrame.getNumLocals();
+ assert expectedFrame.getNumStack() == actualFrame.getNumStack();
+ assert expectedFrame.getNumLocks() == actualFrame.getNumLocks();
+
+ verifyValues(expectedFrame.getValueInfos(), actualFrame.getValueInfos());
+ assert expectedFrame.getVirtualObjects() == expectedTopFrame.getVirtualObjects();
+ assert actualFrame.getVirtualObjects() == actualTopFrame.getVirtualObjects();
}
- assert Objects.equals(expectedFrame.sourceClass, actualFrame.sourceClass);
- assert Objects.equals(expectedFrame.sourceMethodName, actualFrame.sourceMethodName);
- assert expectedFrame.sourceLineNumber == actualFrame.sourceLineNumber;
- assert expectedFrame.methodId == actualFrame.methodId;
+ assert Objects.equals(expectedFrame.getSourceClass(), actualFrame.getSourceClass());
+ assert Objects.equals(expectedFrame.getSourceMethodName(), actualFrame.getSourceMethodName());
+ assert expectedFrame.getSourceLineNumber() == actualFrame.getSourceLineNumber();
+ assert expectedFrame.getMethodId() == actualFrame.getMethodId();
assert expectedFrame.sourceClassIndex == actualFrame.sourceClassIndex;
assert expectedFrame.sourceMethodNameIndex == actualFrame.sourceMethodNameIndex;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java
index bb78af328b8a..8f9bae6a46b8 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java
@@ -194,6 +194,7 @@ public void init() {
sourceClass = null;
sourceMethodName = "";
sourceLineNumber = -1;
+ methodId = -1;
sourceClassIndex = -1;
sourceMethodNameIndex = -1;
}
@@ -220,6 +221,9 @@ public SharedMethod getDeoptMethod() {
* that there is no inlining in target methods, so the method + BCI is unique.
*/
public int getDeoptMethodOffset() {
+ if (deoptMethod != null) {
+ return deoptMethod.getDeoptOffsetInImage();
+ }
return deoptMethodOffset;
}
@@ -334,7 +338,7 @@ public String getSourceMethodName() {
* Returns the unique identification number for the method.
*/
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public int getMethodID() {
+ public int getMethodId() {
return methodId;
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ReusableTypeReader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ReusableTypeReader.java
index 70ef66f1242e..f01481fcecf6 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ReusableTypeReader.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ReusableTypeReader.java
@@ -25,6 +25,7 @@
package com.oracle.svm.core.code;
+import com.oracle.svm.core.Uninterruptible;
import org.graalvm.compiler.core.common.util.AbstractTypeReader;
import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter;
@@ -49,30 +50,36 @@ public ReusableTypeReader(NonmovableArray data, long byteIndex) {
this.byteIndex = byteIndex;
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public ReusableTypeReader reset() {
data = NonmovableArrays.nullArray();
byteIndex = -1;
return this;
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public boolean isValid() {
return data != null && byteIndex >= 0;
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public long getByteIndex() {
return byteIndex;
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public void setByteIndex(long byteIndex) {
this.byteIndex = byteIndex;
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public NonmovableArray getData() {
return data;
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public void setData(NonmovableArray data) {
this.data = data;
}
@@ -108,6 +115,7 @@ public long getS8() {
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public int getU1() {
int result = NonmovableByteArrayReader.getU1(data, byteIndex);
byteIndex += Byte.BYTES;
@@ -115,29 +123,35 @@ public int getU1() {
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public int getUVInt() {
return asS4(getUV());
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public int getSVInt() {
return asS4(getSV());
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public long getSV() {
return decodeSign(read());
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public long getUV() {
return read();
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static long decodeSign(long value) {
return (value >>> 1) ^ -(value & 1);
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private long read() {
int b0 = getU1();
if (b0 < UnsafeArrayTypeWriter.NUM_LOW_CODES) {
@@ -147,6 +161,7 @@ private long read() {
}
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private long readPacked(int b0) {
assert b0 >= UnsafeArrayTypeWriter.NUM_LOW_CODES;
long sum = b0;
@@ -161,10 +176,12 @@ private long readPacked(int b0) {
}
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean isS4(long value) {
return value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE;
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static int asS4(long value) {
assert isS4(value);
return (int) value;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceHandlerThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceHandlerThread.java
index 928d713117e2..827a1ac88492 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceHandlerThread.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/ReferenceHandlerThread.java
@@ -32,10 +32,10 @@
import org.graalvm.nativeimage.Platforms;
import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.VMThreads;
-import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.util.VMError;
public final class ReferenceHandlerThread implements Runnable {
@@ -71,13 +71,9 @@ public static boolean isReferenceHandlerThread(Thread other) {
@Override
public void run() {
- this.isolateThread = CurrentIsolate.getCurrentThread();
-
- /*
- * Precaution: this thread does not register a callback itself, but a subclass of Reference,
- * ReferenceQueue, or a Cleaner or Cleanable might do strange things.
- */
ThreadingSupportImpl.pauseRecurringCallback("An exception in a recurring callback must not interrupt pending reference processing because it could result in a memory leak.");
+
+ this.isolateThread = CurrentIsolate.getCurrentThread();
try {
while (true) {
ReferenceInternals.waitForPendingReferences();
@@ -88,8 +84,6 @@ public void run() {
VMError.guarantee(VMThreads.isTearingDown(), "Reference Handler should only be interrupted during tear-down");
} catch (Throwable t) {
VMError.shouldNotReachHere("Reference processing and cleaners must handle all potential exceptions", t);
- } finally {
- ThreadingSupportImpl.resumeRecurringCallback();
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementFeature.java
index 0dc38ef5a691..76a6f091ee31 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementFeature.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementFeature.java
@@ -67,7 +67,10 @@ public List> getRequiredFeatures() {
@Override
public void afterRegistration(AfterRegistrationAccess access) {
- ManagementSupport managementSupport = new ManagementSupport();
+ SubstrateThreadMXBean threadMXBean = new SubstrateThreadMXBean();
+ ImageSingletons.add(SubstrateThreadMXBean.class, threadMXBean);
+
+ ManagementSupport managementSupport = new ManagementSupport(threadMXBean);
ImageSingletons.add(ManagementSupport.class, managementSupport);
ThreadListenerSupport.get().register(managementSupport);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementSupport.java
index 2b25fa461d9e..e969b1036e31 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementSupport.java
@@ -124,14 +124,14 @@ public final class ManagementSupport implements ThreadListener {
MBeanServer platformMBeanServer;
@Platforms(Platform.HOSTED_ONLY.class)
- ManagementSupport() {
+ ManagementSupport(SubstrateThreadMXBean threadMXBean) {
platformManagedObjectsMap = new HashMap<>();
platformManagedObjectsSet = Collections.newSetFromMap(new IdentityHashMap<>());
classLoadingMXBean = new SubstrateClassLoadingMXBean();
compilationMXBean = new SubstrateCompilationMXBean();
runtimeMXBean = new SubstrateRuntimeMXBean();
- threadMXBean = new SubstrateThreadMXBean();
+ this.threadMXBean = threadMXBean;
/*
* Register the platform objects defined in this package. Note that more platform objects
@@ -265,13 +265,13 @@ public boolean isAllowedPlatformManagedObject(PlatformManagedObject object) {
return true;
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ @Uninterruptible(reason = "Only uninterruptible code may be executed before the thread is fully started.")
@Override
- public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) {
+ public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) {
threadMXBean.noteThreadStart(javaThread);
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ @Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.")
@Override
public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) {
threadMXBean.noteThreadFinish(javaThread);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
index e66728803603..fe04d21dc787 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
@@ -38,10 +38,13 @@
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.heap.VMOperationInfos;
+import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
+import com.oracle.svm.core.jfr.sampler.JfrRecurringCallbackExecutionSampler;
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
import com.oracle.svm.core.os.RawFileOperationSupport;
import com.oracle.svm.core.sampler.SamplerBuffersAccess;
import com.oracle.svm.core.thread.JavaVMOperation;
+import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMOperationControl;
import com.oracle.svm.core.thread.VMThreads;
@@ -384,11 +387,12 @@ protected void operate() {
*/
@Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.")
private void changeEpoch() {
- /* Process all unprocessed sampler buffers before changing the epoch. */
- SamplerBuffersAccess.processSamplerBuffers();
+ processSamplerBuffers();
- // Write unflushed data from the thread local buffers but do *not* reinitialize them
- // The thread local code will handle space reclamation on their own time
+ /*
+ * Write unflushed data from the thread local buffers but do *not* reinitialize them.
+ * The thread local code will handle space reclamation on their own time.
+ */
for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
JfrBuffer buffer = JfrThreadLocal.getJavaBuffer(thread);
if (buffer.isNonNull()) {
@@ -413,6 +417,34 @@ private void changeEpoch() {
// Now that the epoch changed, re-register all running threads for the new epoch.
SubstrateJVM.getThreadRepo().registerRunningThreads();
}
+
+ /**
+ * The VM is at a safepoint, so all other threads have a native state. However, execution
+ * sampling could still be executed. For the {@link JfrRecurringCallbackExecutionSampler},
+ * it is sufficient to mark this method as uninterruptible to prevent execution of the
+ * recurring callbacks. If the SIGPROF-based sampler is used, the signal handler may still
+ * be executed at any time for any thread (including the current thread). To prevent races,
+ * we need to ensure that there are no threads that execute the SIGPROF handler while we are
+ * accessing the currently active buffers of other threads.
+ */
+ @Uninterruptible(reason = "Prevent JFR recording.")
+ private void processSamplerBuffers() {
+ assert VMOperation.isInProgressAtSafepoint();
+ assert ThreadingSupportImpl.isRecurringCallbackPaused();
+
+ JfrExecutionSampler.singleton().disallowThreadsInSamplerCode();
+ try {
+ processSamplerBuffers0();
+ } finally {
+ JfrExecutionSampler.singleton().allowThreadsInSamplerCode();
+ }
+ }
+
+ @Uninterruptible(reason = "Prevent JFR recording.")
+ private void processSamplerBuffers0() {
+ SamplerBuffersAccess.processActiveBuffers();
+ SamplerBuffersAccess.processFullBuffers(false);
+ }
}
public long getChunkStartNanos() {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java
index 3635233cfd5c..bff2c58fb9a5 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java
@@ -24,18 +24,14 @@
*/
package com.oracle.svm.core.jfr;
-import java.util.function.BooleanSupplier;
-
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.annotate.TargetClass;
-import com.oracle.svm.core.jdk.JDK17OrEarlier;
-import com.oracle.svm.util.ReflectionUtil;
/**
- * The event IDs depend on the metadata.xml and therefore vary between JDK versions.
+ * This file contains the VM-level events that Native Image supports on all JDK versions. The event
+ * IDs depend on the JDK version (see metadata.xml file) and are computed at image build time.
*/
public final class JfrEvent {
public static final JfrEvent ThreadStart = create("jdk.ThreadStart");
@@ -60,7 +56,6 @@ public final class JfrEvent {
public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd");
public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation");
public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter");
- public static final JfrEvent ThreadSleep = create("jdk.ThreadSleep", JDK17OrEarlier.class);
public static final JfrEvent ThreadPark = create("jdk.ThreadPark");
public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait");
@@ -68,18 +63,8 @@ public final class JfrEvent {
private final String name;
@Platforms(Platform.HOSTED_ONLY.class)
- private static JfrEvent create(String name) {
- return create(name, TargetClass.AlwaysIncluded.class);
- }
-
- @Platforms(Platform.HOSTED_ONLY.class)
- private static JfrEvent create(String name, Class extends BooleanSupplier> onlyWith) {
- BooleanSupplier onlyWithProvider = ReflectionUtil.newInstance(onlyWith);
- if (onlyWithProvider.getAsBoolean()) {
- return new JfrEvent(name);
- } else {
- return null;
- }
+ public static JfrEvent create(String name) {
+ return new JfrEvent(name);
}
@Platforms(Platform.HOSTED_ONLY.class)
@@ -97,4 +82,9 @@ public long getId() {
public String getName() {
return name;
}
+
+ @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true)
+ public boolean shouldEmit() {
+ return SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this);
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java
index 405cc7027057..af3682782ce6 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java
@@ -34,11 +34,13 @@
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.VMInspectionOptions;
+import com.oracle.svm.core.deopt.DeoptimizationSupport;
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.jdk.RuntimeSupport;
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
import com.oracle.svm.core.jfr.traceid.JfrTraceIdMap;
+import com.oracle.svm.core.sampler.SamplerStackWalkVisitor;
import com.oracle.svm.core.thread.ThreadListenerSupport;
import com.oracle.svm.core.thread.ThreadListenerSupportFeature;
import com.oracle.svm.core.util.UserError;
@@ -49,7 +51,6 @@
import com.sun.management.internal.PlatformMBeanProviderImpl;
import jdk.jfr.Configuration;
-import jdk.jfr.Event;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.jfc.JFC;
@@ -59,9 +60,9 @@
*
* There are two different kinds of JFR events:
*
- * - Java-level events are defined by a Java class that extends {@link Event} and that is
- * annotated with JFR-specific annotations. Those events are typically triggered by the Java
- * application and a Java {@code EventWriter} object is used when writing the event to a
+ *
- Java-level events are defined by a Java class that extends {@link jdk.internal.event.Event}
+ * and that is annotated with JFR-specific annotations. Those events are typically triggered by the
+ * Java application and a Java {@code EventWriter} object is used when writing the event to a
* buffer.
* - Native events are triggered by the JVM itself and are defined in the JFR metadata.xml file.
* For writing such an event to a buffer, we call into {@link JfrNativeEventWriter} and pass a
@@ -115,7 +116,7 @@ public static boolean isInConfiguration(boolean allowPrinting) {
boolean runtimeEnabled = VMInspectionOptions.hasJfrSupport();
if (HOSTED_ENABLED && !runtimeEnabled) {
if (allowPrinting) {
- System.err.println("Warning: When FlightRecoder is used to profile the image generator, it is also automatically enabled in the native image at run time. " +
+ System.err.println("Warning: When FlightRecorder is used to profile the image generator, it is also automatically enabled in the native image at run time. " +
"This can affect the measurements because it can can make the image larger and image build time longer.");
}
runtimeEnabled = true;
@@ -127,6 +128,10 @@ private static boolean osSupported() {
return Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class);
}
+ public static boolean isExecutionSamplerSupported() {
+ return HasJfrSupport.get() && !DeoptimizationSupport.enabled();
+ }
+
/**
* We cannot use the proper way of looking up the bean via
* {@link java.lang.management.ManagementFactory} because that initializes too many classes at
@@ -160,9 +165,11 @@ public void afterRegistration(AfterRegistrationAccess access) {
ImageSingletons.add(JfrTraceIdMap.class, new JfrTraceIdMap());
ImageSingletons.add(JfrTraceIdEpoch.class, new JfrTraceIdEpoch());
ImageSingletons.add(JfrGCNames.class, new JfrGCNames());
+ ImageSingletons.add(SamplerStackWalkVisitor.class, new SamplerStackWalkVisitor());
JfrSerializerSupport.get().register(new JfrFrameTypeSerializer());
JfrSerializerSupport.get().register(new JfrThreadStateSerializer());
+
ThreadListenerSupport.get().register(SubstrateJVM.getThreadLocal());
if (HOSTED_ENABLED) {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java
index d0dabe6c6816..d62c88d9468e 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java
@@ -26,7 +26,7 @@
import java.util.ArrayList;
import java.util.List;
-import jdk.jfr.Event;
+
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
@@ -34,14 +34,23 @@
* Holds all JFR Java-level event classes.
*/
public class JfrJavaEvents {
- private static final List> EVENT_CLASSES = new ArrayList<>();
+ private static final List> EVENT_CLASSES = new ArrayList<>();
+ private static final List> JFR_EVENT_CLASSES = new ArrayList<>();
@Platforms(Platform.HOSTED_ONLY.class)
- public static void registerEventClass(Class extends Event> eventClass) {
+ @SuppressWarnings("unchecked")
+ public static void registerEventClass(Class extends jdk.internal.event.Event> eventClass) {
EVENT_CLASSES.add(eventClass);
+ if (jdk.jfr.Event.class.isAssignableFrom(eventClass)) {
+ JFR_EVENT_CLASSES.add((Class extends jdk.jfr.Event>) eventClass);
+ }
}
- public static List> getAllEventClasses() {
+ public static List> getAllEventClasses() {
return EVENT_CLASSES;
}
+
+ public static List> getJfrEventClasses() {
+ return JFR_EVENT_CLASSES;
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java
index 50424ef4508a..5cca88f673af 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMethodRepository.java
@@ -29,10 +29,7 @@
import org.graalvm.nativeimage.StackValue;
import org.graalvm.word.WordFactory;
-import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.code.FrameInfoQueryResult;
-import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
import com.oracle.svm.core.jfr.utils.JfrVisited;
import com.oracle.svm.core.jfr.utils.JfrVisitedTable;
@@ -60,53 +57,52 @@ public void teardown() {
}
@Uninterruptible(reason = "Epoch must not change while in this method.")
- public long getMethodId(FrameInfoQueryResult stackTraceElement) {
- JfrMethodEpochData epochData = getEpochData(false);
+ public long getMethodId(Class> clazz, String methodName, int methodId) {
+ assert clazz != null;
+ assert methodName != null;
+ assert methodId > 0;
+
mutex.lockNoTransition();
try {
- return getMethodId0(stackTraceElement, epochData);
+ return getMethodId0(clazz, methodName, methodId);
} finally {
mutex.unlock();
}
}
@Uninterruptible(reason = "Epoch must not change while in this method.")
- private static long getMethodId0(FrameInfoQueryResult stackTraceElement, JfrMethodEpochData epochData) {
- if (epochData.methodBuffer.isNull()) {
- // This will happen only on the first call.
- epochData.methodBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP);
- }
-
+ private long getMethodId0(Class> clazz, String methodName, int methodId) {
JfrVisited jfrVisited = StackValue.get(JfrVisited.class);
- int methodId = stackTraceElement.getMethodID();
jfrVisited.setId(methodId);
- jfrVisited.setHash(stackTraceElement.hashCode());
+ jfrVisited.setHash(methodId);
+
+ JfrMethodEpochData epochData = getEpochData(false);
if (!epochData.visitedMethods.putIfAbsent(jfrVisited)) {
return methodId;
}
+ if (epochData.methodBuffer.isNull()) {
+ // This will happen only on the first call.
+ epochData.methodBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP);
+ }
+
JfrSymbolRepository symbolRepo = SubstrateJVM.getSymbolRepository();
JfrTypeRepository typeRepo = SubstrateJVM.getTypeRepository();
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initialize(data, epochData.methodBuffer);
-
- /* JFR Method id. */
JfrNativeEventWriter.putLong(data, methodId);
- /* Class. */
- JfrNativeEventWriter.putLong(data, typeRepo.getClassId(stackTraceElement.getSourceClass()));
- /* Method name. */
- JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId(stackTraceElement.getSourceMethodName(), false));
- /* Method description. */
+ JfrNativeEventWriter.putLong(data, typeRepo.getClassId(clazz));
+ JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId(methodName, false));
+ /* Dummy value for signature. */
JfrNativeEventWriter.putLong(data, symbolRepo.getSymbolId("()V", false));
- /* Method modifier. */
- JfrNativeEventWriter.putInt(data, 0);
- /* Is hidden class? */
- JfrNativeEventWriter.putBoolean(data, SubstrateUtil.isHiddenClass(DynamicHub.fromClass(stackTraceElement.getSourceClass())));
+ /* Dummy value for modifiers. */
+ JfrNativeEventWriter.putShort(data, (short) 0);
+ /* Dummy value for isHidden. */
+ JfrNativeEventWriter.putBoolean(data, false);
JfrNativeEventWriter.commit(data);
- // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we
- // need to update the repository pointer as well.
+ /* The buffer may have been replaced with a new one. */
epochData.methodBuffer = data.getJfrBuffer();
return methodId;
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
index e6ff68a67675..0270321df9a5 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
@@ -24,8 +24,6 @@
*/
package com.oracle.svm.core.jfr;
-import org.graalvm.nativeimage.CurrentIsolate;
-import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.word.Pointer;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
@@ -82,7 +80,7 @@ public static void beginSmallEvent(JfrNativeEventWriterData data, JfrEvent event
*/
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
public static void beginEvent(JfrNativeEventWriterData data, JfrEvent event, boolean large) {
- assert SubstrateJVM.isRecording();
+ assert SubstrateJVM.get().isRecording();
assert isValid(data);
assert getUncommittedSize(data).equal(0);
if (large) {
@@ -210,19 +208,23 @@ public static void putString(JfrNativeEventWriterData data, String string) {
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
public static void putEventThread(JfrNativeEventWriterData data) {
- putThread(data, CurrentIsolate.getCurrentThread());
+ putThread(data, SubstrateJVM.getCurrentThreadId());
}
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
- public static void putThread(JfrNativeEventWriterData data, IsolateThread isolateThread) {
- if (isolateThread.isNull()) {
- putLong(data, 0L);
+ public static void putThread(JfrNativeEventWriterData data, Thread thread) {
+ if (thread == null) {
+ putThread(data, 0L);
} else {
- long threadId = SubstrateJVM.getThreadId(isolateThread);
- putLong(data, threadId);
+ putThread(data, SubstrateJVM.getThreadId(thread));
}
}
+ @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
+ public static void putThread(JfrNativeEventWriterData data, long threadId) {
+ putLong(data, threadId);
+ }
+
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
public static void putClass(JfrNativeEventWriterData data, Class> aClass) {
if (aClass == null) {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java
index 90e9f5deb0fd..627cb013c1c1 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java
@@ -37,18 +37,16 @@
import com.oracle.svm.core.locks.VMSemaphore;
import com.oracle.svm.core.sampler.SamplerBuffer;
import com.oracle.svm.core.sampler.SamplerBuffersAccess;
-import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
import com.oracle.svm.core.util.VMError;
/**
* A daemon thread that is created during JFR startup and torn down by
* {@link SubstrateJVM#destroyJFR}.
- *
- * It is used for persisting the {@link JfrGlobalMemory} buffers to a file and for processing the
- * pool of {@link SamplerBuffer}s collected in signal handler (see {@link SubstrateSigprofHandler}).
- * With that in mind, the thread is using {@link VMSemaphore} for a synchronization between threads
- * as it is async signal safe.
- *
+ *
+ * This class is primarily used for persisting the {@link JfrGlobalMemory} buffers to a file.
+ * Besides that, it is also used for processing full {@link SamplerBuffer}s. As
+ * {@link SamplerBuffer}s may also be filled in a signal handler, a {@link VMSemaphore} is used for
+ * notification because it is async-signal-safe.
*/
public class JfrRecorderThread extends Thread {
private static final int BUFFER_FULL_ENOUGH_PERCENTAGE = 50;
@@ -76,10 +74,6 @@ public JfrRecorderThread(JfrGlobalMemory globalMemory, JfrUnlockedChunkWriter un
setDaemon(true);
}
- public void setStopped(boolean value) {
- this.stopped = value;
- }
-
@Override
public void run() {
try {
@@ -116,8 +110,7 @@ private boolean await() {
}
private void run0() {
- /* Process all unprocessed sampler buffers. */
- SamplerBuffersAccess.processSamplerBuffers();
+ SamplerBuffersAccess.processFullBuffers(true);
JfrChunkWriter chunkWriter = unlockedChunkWriter.lock();
try {
if (chunkWriter.hasOpenFile()) {
@@ -190,4 +183,14 @@ private static boolean isFullEnough(JfrBuffer buffer) {
UnsignedWord bufferTargetSize = buffer.getSize().multiply(100).unsignedDivide(BUFFER_FULL_ENOUGH_PERCENTAGE);
return JfrBufferAccess.getAvailableSize(buffer).belowOrEqual(bufferTargetSize);
}
+
+ public void shutdown() {
+ this.stopped = true;
+ this.signal();
+ try {
+ this.join();
+ } catch (InterruptedException e) {
+ throw VMError.shouldNotReachHere(e);
+ }
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
index abfd188fa4c6..6c87bb225135 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
@@ -40,21 +40,20 @@
import com.oracle.svm.core.FrameAccess;
import com.oracle.svm.core.NeverInline;
-import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.UnmanagedMemoryUtil;
-import com.oracle.svm.core.code.FrameInfoQueryResult;
+import com.oracle.svm.core.deopt.DeoptimizationSupport;
import com.oracle.svm.core.headers.LibC;
import com.oracle.svm.core.jdk.AbstractUninterruptibleHashtable;
import com.oracle.svm.core.jdk.UninterruptibleEntry;
+import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
import com.oracle.svm.core.jfr.utils.JfrVisited;
import com.oracle.svm.core.locks.VMMutex;
import com.oracle.svm.core.sampler.SamplerSampleWriter;
import com.oracle.svm.core.sampler.SamplerSampleWriterData;
import com.oracle.svm.core.sampler.SamplerSampleWriterDataAccess;
-import com.oracle.svm.core.sampler.SamplerThreadLocal;
-import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
+import com.oracle.svm.core.sampler.SamplerStackWalkVisitor;
import com.oracle.svm.core.snippets.KnownIntrinsics;
import com.oracle.svm.core.stack.JavaStackWalker;
@@ -62,7 +61,11 @@
* Repository that collects all metadata about stacktraces.
*/
public class JfrStackTraceRepository implements JfrConstantPool {
- private int maxDepth = SubstrateOptions.MaxJavaStackTraceDepth.getValue();
+ private static final int DEFAULT_STACK_DEPTH = 64;
+ private static final int MIN_STACK_DEPTH = 1;
+ private static final int MAX_STACK_DEPTH = 2048;
+
+ private int stackTraceDepth = DEFAULT_STACK_DEPTH;
private final VMMutex mutex;
private final JfrStackTraceEpochData epochData0;
@@ -75,21 +78,19 @@ public class JfrStackTraceRepository implements JfrConstantPool {
this.mutex = new VMMutex("jfrStackTraceRepository");
}
- public void setStackTraceDepth(int depth) {
- if (depth < 0 || depth > SubstrateOptions.MaxJavaStackTraceDepth.getValue()) {
- throw new IllegalArgumentException("StackTrace depth (" + depth + ") is not in a valid range!");
+ public void setStackTraceDepth(int value) {
+ if (value < MIN_STACK_DEPTH) {
+ stackTraceDepth = MIN_STACK_DEPTH;
+ } else if (value > MAX_STACK_DEPTH) {
+ stackTraceDepth = MAX_STACK_DEPTH;
+ } else {
+ stackTraceDepth = value;
}
- this.maxDepth = depth;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public void acquireLock() {
- mutex.lockNoTransition();
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public void releaseLock() {
- mutex.unlock();
+ public int getStackTraceDepth() {
+ return stackTraceDepth;
}
@Uninterruptible(reason = "Releasing repository buffers.")
@@ -99,134 +100,125 @@ public void teardown() {
}
@NeverInline("Starting a stack walk in the caller frame.")
- @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
+ @Uninterruptible(reason = "Accesses a sampler buffer.")
public long getStackTraceId(int skipCount) {
- assert maxDepth >= 0;
- long stackTraceId = 0;
-
- /* Initialize stack walk. */
- SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
- SamplerThreadLocal.setSignalHandlerLocallyDisabled(true);
- if (SamplerSampleWriterDataAccess.initialize(data, skipCount, maxDepth)) {
- SamplerSampleWriter.begin(data);
- /* Walk the stack. */
- Pointer sp = KnownIntrinsics.readCallerStackPointer();
- CodePointer ip = FrameAccess.singleton().readReturnAddress(sp);
- if (JavaStackWalker.walkCurrentThread(sp, ip, SubstrateSigprofHandler.visitor()) || data.getTruncated()) {
- acquireLock();
+ if (DeoptimizationSupport.enabled()) {
+ /* Stack traces are not supported if JIT compilation is used (GR-43686). */
+ return 0;
+ }
+
+ /*
+ * JFR stack traces use the same thread-local buffers as the execution sampler. So, we need
+ * to prevent the sampler from modifying the buffer, while it is used by the code below.
+ */
+ JfrExecutionSampler.singleton().preventSamplingInCurrentThread();
+ try {
+ /* Try to walk the stack. */
+ SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
+ if (SamplerSampleWriterDataAccess.initialize(data, skipCount, true)) {
+ JfrThreadLocal.setSamplerWriterData(data);
try {
- CIntPointer status = StackValue.get(CIntPointer.class);
- Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize());
- stackTraceId = getStackTraceId(start, data.getCurrentPos(), data.getHashCode(), status, false);
- if (JfrStackTraceTableEntryStatus.get(status, JfrStackTraceTableEntryStatus.NEW)) {
- SamplerSampleWriter.end(data, SamplerSampleWriter.JFR_STACK_TRACE_END);
+ SamplerSampleWriter.begin(data);
+ Pointer sp = KnownIntrinsics.readCallerStackPointer();
+ CodePointer ip = FrameAccess.singleton().readReturnAddress(sp);
+ SamplerStackWalkVisitor visitor = ImageSingletons.lookup(SamplerStackWalkVisitor.class);
+ if (JavaStackWalker.walkCurrentThread(sp, ip, visitor) || data.getTruncated()) {
+ /* Deduplicate and store the stack trace. */
+ Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize());
+ UnsignedWord size = data.getCurrentPos().subtract(start);
+
+ CIntPointer statusPtr = StackValue.get(CIntPointer.class);
+ JfrStackTraceTableEntry epochSpecificEntry = getOrPutStackTrace(start, size, data.getHashCode(), statusPtr);
+ if (epochSpecificEntry.isNonNull()) {
+ /* Only commit the data in the thread-local buffer if it is new data. */
+ if (statusPtr.read() == JfrStackTraceTableEntryStatus.INSERTED) {
+ SamplerSampleWriter.end(data, SamplerSampleWriter.JFR_STACK_TRACE_END);
+ }
+ return epochSpecificEntry.getId();
+ }
}
} finally {
- releaseLock();
+ JfrThreadLocal.setSamplerWriterData(WordFactory.nullPointer());
}
}
+ return 0L;
+ } finally {
+ JfrExecutionSampler.singleton().allowSamplingInCurrentThread();
}
- SamplerThreadLocal.setSignalHandlerLocallyDisabled(false);
- return stackTraceId;
}
- @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
- public long getStackTraceId(Pointer start, Pointer end, int hashCode, CIntPointer status, boolean isSerializationInProgress) {
- JfrStackTraceEpochData epochData = getEpochData(false);
- JfrStackTraceTableEntry entry = StackValue.get(JfrStackTraceTableEntry.class);
+ /**
+ * If the same stack trace already exists in the repository, then this method returns the
+ * matching {@link JfrStackTraceTableEntry entry}. Otherwise, it tries to add the stack trace to
+ * the repository. If this is successful, then the newly added entry is returned. Otherwise,
+ * null is returned.
+ *
+ * NOTE: the returned value is only valid until the JFR epoch changes. So, this method may only
+ * be used from uninterruptible code.
+ */
+ @Uninterruptible(reason = "Prevent epoch change. Code that holds the mutex must be fully uninterruptible.", callerMustBe = true)
+ public JfrStackTraceTableEntry getOrPutStackTrace(Pointer start, UnsignedWord size, int hashCode, CIntPointer statusPtr) {
+ mutex.lockNoTransition();
+ try {
+ return getOrPutStackTrace0(start, size, hashCode, statusPtr);
+ } finally {
+ mutex.unlock();
+ }
+ }
- UnsignedWord size = end.subtract(start);
+ @Uninterruptible(reason = "Code that holds the mutex must be fully uninterruptible.")
+ private JfrStackTraceTableEntry getOrPutStackTrace0(Pointer start, UnsignedWord size, int hashCode, CIntPointer statusPtr) {
+ assert size.rawValue() == (int) size.rawValue();
+
+ JfrStackTraceTableEntry entry = StackValue.get(JfrStackTraceTableEntry.class);
entry.setHash(hashCode);
entry.setSize((int) size.rawValue());
+ entry.setRawStackTrace(start);
entry.setSerialized(false);
- /* Do not copy stacktrace into new entry unless it is necessary. */
- entry.setStackTrace(start);
+ JfrStackTraceEpochData epochData = getEpochData(false);
JfrStackTraceTableEntry result = (JfrStackTraceTableEntry) epochData.visitedStackTraces.get(entry);
if (result.isNonNull()) {
- JfrStackTraceTableEntryStatus.update(result, status, false, result.getSerialized(), isSerializationInProgress);
- return result.getId();
+ /* There is an existing stack trace. */
+ int status = result.getSerialized() ? JfrStackTraceTableEntryStatus.EXISTING_SERIALIZED : JfrStackTraceTableEntryStatus.EXISTING_RAW;
+ statusPtr.write(status);
+ return result;
} else {
- /* Replace the previous pointer with new one (entry size and hash remains the same). */
+ /*
+ * Insert a new entry into the hashtable. We need to copy the raw stacktrace data from
+ * the thread-local buffer to the C heap because the thread-local buffer will be
+ * overwritten or freed at some point.
+ */
Pointer to = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(size);
- if (to.isNull()) {
- /* There is not enough space to allocate a new buffer. */
- JfrStackTraceTableEntryStatus.failStatus(status);
- return 0;
- } else {
- entry.setStackTrace(to);
- /* Copy the stacktrace into separate native memory entry in hashtable. */
+ if (to.isNonNull()) {
UnmanagedMemoryUtil.copy(start, to, size);
+ entry.setRawStackTrace(to);
JfrStackTraceTableEntry newEntry = (JfrStackTraceTableEntry) epochData.visitedStackTraces.getOrPut(entry);
- JfrStackTraceTableEntryStatus.update(newEntry, status, true, false, isSerializationInProgress);
- return newEntry.getId();
+ if (newEntry.isNonNull()) {
+ statusPtr.write(JfrStackTraceTableEntryStatus.INSERTED);
+ return newEntry;
+ }
+
+ /* Hashtable entry allocation failed. */
+ ImageSingletons.lookup(UnmanagedMemorySupport.class).free(to);
}
- }
- }
- @Uninterruptible(reason = "Epoch must not change while in this method.")
- public void serializeStackTraceHeader(long stackTraceId, boolean isTruncated, int stackTraceLength) {
- JfrStackTraceEpochData epochData = getEpochData(false);
- if (epochData.stackTraceBuffer.isNull()) {
- // This will happen only on the first call.
- epochData.stackTraceBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP);
+ /* Some allocation failed. */
+ statusPtr.write(JfrStackTraceTableEntryStatus.INSERT_FAILED);
+ return WordFactory.nullPointer();
}
-
- JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
- JfrNativeEventWriterDataAccess.initialize(data, epochData.stackTraceBuffer);
-
- /* JFR Stacktrace id. */
- JfrNativeEventWriter.putLong(data, stackTraceId);
- /* Is truncated. */
- JfrNativeEventWriter.putBoolean(data, isTruncated);
- /* Stacktrace size. */
- JfrNativeEventWriter.putInt(data, stackTraceLength);
-
- epochData.numberOfSerializedStackTraces++;
- JfrNativeEventWriter.commit(data);
-
- /*
- * Maybe during writing, the thread buffer was replaced with a new (larger) one, so we need
- * to update the repository pointer as well.
- */
- epochData.stackTraceBuffer = data.getJfrBuffer();
- }
-
- @Uninterruptible(reason = "Epoch must not change while in this method.")
- public void serializeUnknownStackTraceElement() {
- serializeStackTraceElement0(0, -1, -1);
- }
-
- @Uninterruptible(reason = "Epoch must not change while in this method.")
- public void serializeStackTraceElement(FrameInfoQueryResult stackTraceElement) {
- serializeStackTraceElement0(SubstrateJVM.getMethodRepo().getMethodId(stackTraceElement), stackTraceElement.getSourceLineNumber(), stackTraceElement.getBci());
}
- @Uninterruptible(reason = "Epoch must not change while in this method.")
- private void serializeStackTraceElement0(long methodId, int sourceLineNumber, int bci) {
- JfrStackTraceEpochData epochData = getEpochData(false);
- assert epochData.stackTraceBuffer.isNonNull();
-
- JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
- JfrNativeEventWriterDataAccess.initialize(data, epochData.stackTraceBuffer);
-
- /* Method id. */
- JfrNativeEventWriter.putLong(data, methodId);
- /* Line number. */
- JfrNativeEventWriter.putInt(data, sourceLineNumber);
- /* Bytecode index. */
- JfrNativeEventWriter.putInt(data, bci);
- /* Frame type id. */
- JfrNativeEventWriter.putLong(data, JfrFrameType.FRAME_AOT_COMPILED.getId());
-
- JfrNativeEventWriter.commit(data);
-
- /*
- * Maybe during writing, the thread buffer was replaced with a new (larger) one, so we need
- * to update the repository pointer as well.
- */
- epochData.stackTraceBuffer = data.getJfrBuffer();
+ @Uninterruptible(reason = "Code that holds the mutex must be fully uninterruptible.", callerMustBe = true)
+ public void commitSerializedStackTrace(JfrStackTraceTableEntry entry) {
+ mutex.lockNoTransition();
+ try {
+ entry.setSerialized(true);
+ getEpochData(false).numberOfSerializedStackTraces++;
+ } finally {
+ mutex.unlock();
+ }
}
@Override
@@ -255,13 +247,31 @@ private JfrStackTraceEpochData getEpochData(boolean previousEpoch) {
return epoch ? epochData0 : epochData1;
}
+ /**
+ * NOTE: the returned value is only valid until the JFR epoch changes. So, this method may only
+ * be used from uninterruptible code. This method may return null if a new buffer needs to be
+ * allocated and the allocation fails.
+ */
+ @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true)
+ public JfrBuffer getCurrentBuffer() {
+ JfrStackTraceEpochData epochData = getEpochData(false);
+ if (epochData.stackTraceBuffer.isNull()) {
+ epochData.stackTraceBuffer = JfrBufferAccess.allocate(JfrBufferType.C_HEAP);
+ }
+ return epochData.stackTraceBuffer;
+ }
+
+ /**
+ * Each entry contains raw stack trace data (i.e., a sequence of instruction pointers, without
+ * any metadata).
+ */
@RawStructure
public interface JfrStackTraceTableEntry extends JfrVisited {
@RawField
- Pointer getStackTrace();
+ Pointer getRawStackTrace();
@RawField
- void setStackTrace(Pointer pointer);
+ void setRawStackTrace(Pointer pointer);
@RawField
int getSize();
@@ -290,14 +300,17 @@ protected JfrStackTraceTableEntry[] createTable(int size) {
protected boolean isEqual(UninterruptibleEntry a, UninterruptibleEntry b) {
JfrStackTraceTableEntry entry1 = (JfrStackTraceTableEntry) a;
JfrStackTraceTableEntry entry2 = (JfrStackTraceTableEntry) b;
- return entry1.getSize() == entry2.getSize() && LibC.memcmp(entry1.getStackTrace(), entry2.getStackTrace(), WordFactory.unsigned(entry1.getSize())) == 0;
+ /* We explicitly ignore the field 'serialized' because its value can change. */
+ return entry1.getSize() == entry2.getSize() && LibC.memcmp(entry1.getRawStackTrace(), entry2.getRawStackTrace(), WordFactory.unsigned(entry1.getSize())) == 0;
}
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
protected UninterruptibleEntry copyToHeap(UninterruptibleEntry valueOnStack) {
JfrStackTraceTableEntry result = (JfrStackTraceTableEntry) copyToHeap(valueOnStack, SizeOf.unsigned(JfrStackTraceTableEntry.class));
- result.setId(++nextId);
+ if (result.isNonNull()) {
+ result.setId(++nextId);
+ }
return result;
}
@@ -306,37 +319,20 @@ protected UninterruptibleEntry copyToHeap(UninterruptibleEntry valueOnStack) {
protected void free(UninterruptibleEntry entry) {
JfrStackTraceTableEntry stackTraceEntry = (JfrStackTraceTableEntry) entry;
/* The base method will free only the entry itself, not the pointer with stacktrace. */
- ImageSingletons.lookup(UnmanagedMemorySupport.class).free(stackTraceEntry.getStackTrace());
+ ImageSingletons.lookup(UnmanagedMemorySupport.class).free(stackTraceEntry.getRawStackTrace());
super.free(entry);
}
}
public static class JfrStackTraceTableEntryStatus {
- public static final int NEW = 1;
- public static final int SHOULD_SERIALIZE = NEW << 1;
- public static final int SERIALIZED = SHOULD_SERIALIZE << 1;
- public static final int FAILED = SERIALIZED << 1;
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void failStatus(CIntPointer status) {
- status.write(FAILED);
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void update(JfrStackTraceTableEntry entry, CIntPointer status, boolean setNew, boolean isAlreadySerialized, boolean isSerializationInProgress) {
- int isNew = setNew ? NEW : 0;
- int shouldSerialize = !isAlreadySerialized ? SHOULD_SERIALIZE : 0;
- int isSerialized = isAlreadySerialized ? SERIALIZED : 0;
- status.write(isNew | shouldSerialize | isSerialized);
- if (!isAlreadySerialized && isSerializationInProgress) {
- entry.setSerialized(true);
- }
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static boolean get(CIntPointer status, int check) {
- return (status.read() & check) != 0;
- }
+ /* There was no existing entry in the hashtable, so a new entry was inserted. */
+ public static final int INSERTED = 1;
+ /* There was an existing entry for a raw stack trace in the hashtable. */
+ public static final int EXISTING_RAW = INSERTED << 1;
+ /* There was an existing entry for a serialized stack trace in the hashtable. */
+ public static final int EXISTING_SERIALIZED = EXISTING_RAW << 1;
+ /* Some error occurred while trying to insert a new entry into the hashtable. */
+ public static final int INSERT_FAILED = EXISTING_SERIALIZED << 1;
}
private static class JfrStackTraceEpochData {
@@ -360,9 +356,9 @@ void clear() {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
void teardown() {
visitedStackTraces.teardown();
- if (stackTraceBuffer.isNonNull()) {
- JfrBufferAccess.free(stackTraceBuffer);
- }
+ numberOfSerializedStackTraces = 0;
+
+ JfrBufferAccess.free(stackTraceBuffer);
stackTraceBuffer = WordFactory.nullPointer();
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java
index 5dbcb2c2f8c8..7146086e6032 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java
@@ -33,21 +33,18 @@
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
-import com.oracle.svm.core.SubstrateUtil;
-import com.oracle.svm.core.UnmanagedMemoryUtil;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.jfr.events.ExecutionSampleEvent;
+import com.oracle.svm.core.UnmanagedMemoryUtil;
import com.oracle.svm.core.jfr.events.ThreadEndEvent;
import com.oracle.svm.core.jfr.events.ThreadStartEvent;
-import com.oracle.svm.core.thread.JavaThreads;
-import com.oracle.svm.core.thread.Target_java_lang_Thread;
+import com.oracle.svm.core.sampler.SamplerBuffer;
+import com.oracle.svm.core.sampler.SamplerSampleWriterData;
import com.oracle.svm.core.thread.ThreadListener;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
import com.oracle.svm.core.threadlocal.FastThreadLocalLong;
import com.oracle.svm.core.threadlocal.FastThreadLocalObject;
import com.oracle.svm.core.threadlocal.FastThreadLocalWord;
-import com.oracle.svm.core.util.VMError;
/**
* This class holds various JFR-specific thread local values.
@@ -61,18 +58,29 @@
*
*
* It is necessary to have separate buffers as a native JFR event (e.g., a GC or an allocation)
- * could otherwise destroy an Java-level JFR event. All methods that access a {@link JfrBuffer} must
+ * could otherwise destroy a Java-level JFR event. All methods that access a {@link JfrBuffer} must
* be uninterruptible to avoid races with JFR code that is executed at a safepoint (such code may
- * modify the buffers of other threads).
+ * access and modify the buffers of other threads).
+ *
+ * Additionally, each thread may store stack trace data in a {@link SamplerBuffer}. This buffer is
+ * used for both JFR stack traces and JFR sampling. All methods that access a {@link SamplerBuffer}
+ * must be uninterruptible to avoid races with JFR code that is executed at a safepoint (such code
+ * may access and modify the buffers of other threads). Sometimes, it is additionally necessary to
+ * disable sampling temporarily to avoid that the sampler modifies the buffer unexpectedly.
*/
public class JfrThreadLocal implements ThreadListener {
+ /* Event-related thread-locals. */
private static final FastThreadLocalObject javaEventWriter = FastThreadLocalFactory.createObject(Target_jdk_jfr_internal_EventWriter.class,
"JfrThreadLocal.javaEventWriter");
private static final FastThreadLocalWord javaBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.javaBuffer");
private static final FastThreadLocalWord nativeBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBuffer");
private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost");
- private static final FastThreadLocalLong threadId = FastThreadLocalFactory.createLong("JfrThreadLocal.threadId");
- private static final FastThreadLocalLong parentThreadId = FastThreadLocalFactory.createLong("JfrThreadLocal.parentThreadId");
+
+ /* Stacktrace-related thread-locals. */
+ private static final FastThreadLocalWord samplerBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.samplerBuffer");
+ private static final FastThreadLocalLong missedSamples = FastThreadLocalFactory.createLong("JfrThreadLocal.missedSamples");
+ private static final FastThreadLocalLong unparseableStacks = FastThreadLocalFactory.createLong("JfrThreadLocal.unparseableStacks");
+ private static final FastThreadLocalWord samplerWriterData = FastThreadLocalFactory.createWord("JfrThreadLocal.samplerWriterData");
private long threadLocalBufferSize;
@@ -84,33 +92,28 @@ public void initialize(long bufferSize) {
this.threadLocalBufferSize = bufferSize;
}
- @Uninterruptible(reason = "Accesses a JFR buffer.")
+ @Uninterruptible(reason = "Only uninterruptible code may be executed before the thread is fully started.")
@Override
- public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) {
- // We copy the thread id to a thread-local in the IsolateThread. This is necessary so that
- // we are always able to access that value without having to go through a heap-allocated
- // Java object.
- Target_java_lang_Thread t = SubstrateUtil.cast(javaThread, Target_java_lang_Thread.class);
- threadId.set(isolateThread, t.getId());
- parentThreadId.set(isolateThread, JavaThreads.getParentThreadId(javaThread));
-
- SubstrateJVM.getThreadRepo().registerThread(javaThread);
-
- // Emit ThreadStart event before thread.run().
- ThreadStartEvent.emit(isolateThread);
-
- // Register ExecutionSampleEvent after ThreadStart event and before thread.run().
- ExecutionSampleEvent.tryToRegisterExecutionSampleEventCallback();
+ public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) {
+ if (SubstrateJVM.get().isRecording()) {
+ SubstrateJVM.getThreadRepo().registerThread(javaThread);
+ ThreadStartEvent.emit(javaThread);
+ }
}
- @Uninterruptible(reason = "Accesses a JFR buffer.")
+ @Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.")
@Override
public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) {
- // Emit ThreadEnd event after thread.run() finishes.
- ThreadEndEvent.emit(isolateThread);
+ if (SubstrateJVM.get().isRecording()) {
+ ThreadEndEvent.emit(javaThread);
+ stopRecording(isolateThread, true);
+ }
+ }
- // Flush all buffers if necessary.
- if (SubstrateJVM.isRecording()) {
+ @Uninterruptible(reason = "Accesses various JFR buffers.")
+ public static void stopRecording(IsolateThread isolateThread, boolean flushBuffers) {
+ /* Flush event buffers. From this point onwards, no further JFR events may be emitted. */
+ if (flushBuffers) {
JfrBuffer jb = javaBuffer.get(isolateThread);
if (jb.isNonNull()) {
flush(jb, WordFactory.unsigned(0), 0);
@@ -122,9 +125,7 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) {
}
}
- // Free and reset all data.
- threadId.set(isolateThread, 0);
- parentThreadId.set(isolateThread, 0);
+ /* Clear event-related thread-locals. */
dataLost.set(isolateThread, WordFactory.unsigned(0));
javaEventWriter.set(isolateThread, null);
@@ -133,16 +134,17 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) {
freeBuffer(nativeBuffer.get(isolateThread));
nativeBuffer.set(isolateThread, WordFactory.nullPointer());
- }
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public long getTraceId(IsolateThread isolateThread) {
- return threadId.get(isolateThread);
- }
+ /* Clear stacktrace-related thread-locals. */
+ missedSamples.set(isolateThread, 0);
+ unparseableStacks.set(isolateThread, 0);
+ assert samplerWriterData.get(isolateThread).isNull();
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public long getParentThreadId(IsolateThread isolateThread) {
- return parentThreadId.get(isolateThread);
+ SamplerBuffer buffer = samplerBuffer.get(isolateThread);
+ if (buffer.isNonNull()) {
+ SubstrateJVM.getSamplerBufferPool().pushFullBuffer(buffer);
+ samplerBuffer.set(isolateThread, WordFactory.nullPointer());
+ }
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -154,9 +156,11 @@ public Target_jdk_jfr_internal_EventWriter getEventWriter() {
return javaEventWriter.get();
}
- // If a safepoint happens in this method, the state that another thread can see is always
- // sufficiently consistent as the JFR buffer is still empty. So, this method does not need to be
- // uninterruptible.
+ /**
+ * If a safepoint happens in this method, the state that another thread can see is always
+ * sufficiently consistent as the JFR buffer is still empty. So, this method does not need to be
+ * uninterruptible.
+ */
public Target_jdk_jfr_internal_EventWriter newEventWriter() {
assert javaEventWriter.get() == null;
assert javaBuffer.get().isNull();
@@ -170,7 +174,7 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() {
long startPos = buffer.getPos().rawValue();
long maxPos = JfrBufferAccess.getDataEnd(buffer).rawValue();
long addressOfPos = JfrBufferAccess.getAddressOfPos(buffer).rawValue();
- long jfrThreadId = SubstrateJVM.getThreadId(CurrentIsolate.getCurrentThread());
+ long jfrThreadId = SubstrateJVM.getCurrentThreadId();
Target_jdk_jfr_internal_EventWriter result;
if (JavaVersionUtil.JAVA_SPEC >= 19) {
result = new Target_jdk_jfr_internal_EventWriter(startPos, maxPos, addressOfPos, jfrThreadId, true, false);
@@ -184,7 +188,6 @@ public Target_jdk_jfr_internal_EventWriter newEventWriter() {
@Uninterruptible(reason = "Accesses a JFR buffer.")
public JfrBuffer getJavaBuffer() {
- VMError.guarantee(threadId.get() > 0, "Thread local JFR data must be initialized");
JfrBuffer result = javaBuffer.get();
if (result.isNull()) {
result = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA);
@@ -195,7 +198,6 @@ public JfrBuffer getJavaBuffer() {
@Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true)
public JfrBuffer getNativeBuffer() {
- VMError.guarantee(threadId.get() > 0, "Thread local JFR data must be initialized");
JfrBuffer result = nativeBuffer.get();
if (result.isNull()) {
result = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE);
@@ -206,13 +208,13 @@ public JfrBuffer getNativeBuffer() {
@Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true)
public static JfrBuffer getJavaBuffer(IsolateThread thread) {
- assert (VMOperation.isInProgressAtSafepoint());
+ assert VMOperation.isInProgressAtSafepoint();
return javaBuffer.get(thread);
}
@Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true)
public static JfrBuffer getNativeBuffer(IsolateThread thread) {
- assert (VMOperation.isInProgressAtSafepoint());
+ assert VMOperation.isInProgressAtSafepoint();
return nativeBuffer.get(thread);
}
@@ -238,7 +240,7 @@ public static JfrBuffer flush(JfrBuffer threadLocalBuffer, UnsignedWord uncommit
}
if (uncommitted.aboveThan(0)) {
- // Copy all uncommitted memory to the start of the thread local buffer.
+ /* Copy all uncommitted memory to the start of the thread local buffer. */
assert JfrBufferAccess.getDataStart(threadLocalBuffer).add(uncommitted).belowOrEqual(JfrBufferAccess.getDataEnd(threadLocalBuffer));
UnmanagedMemoryUtil.copy(threadLocalBuffer.getPos(), JfrBufferAccess.getDataStart(threadLocalBuffer), uncommitted);
}
@@ -255,7 +257,7 @@ private static void writeDataLoss(JfrBuffer buffer, UnsignedWord unflushedSize)
assert buffer.isNonNull();
assert unflushedSize.aboveThan(0);
UnsignedWord totalDataLoss = increaseDataLost(unflushedSize);
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.DataLoss)) {
+ if (JfrEvent.DataLoss.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initialize(data, buffer);
@@ -278,4 +280,51 @@ private static UnsignedWord increaseDataLost(UnsignedWord delta) {
private static void freeBuffer(JfrBuffer buffer) {
JfrBufferAccess.free(buffer);
}
+
+ @Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
+ public static SamplerBuffer getSamplerBuffer() {
+ return getSamplerBuffer(CurrentIsolate.getCurrentThread());
+ }
+
+ @Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
+ public static SamplerBuffer getSamplerBuffer(IsolateThread thread) {
+ assert CurrentIsolate.getCurrentThread() == thread || VMOperation.isInProgressAtSafepoint();
+ return samplerBuffer.get(thread);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void setSamplerBuffer(SamplerBuffer buffer) {
+ samplerBuffer.set(buffer);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void increaseMissedSamples() {
+ missedSamples.set(getMissedSamples() + 1);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static long getMissedSamples() {
+ return missedSamples.get();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void increaseUnparseableStacks() {
+ unparseableStacks.set(getUnparseableStacks() + 1);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static long getUnparseableStacks() {
+ return unparseableStacks.get();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static void setSamplerWriterData(SamplerSampleWriterData data) {
+ assert samplerWriterData.get().isNull() || data.isNull();
+ samplerWriterData.set(data);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static SamplerSampleWriterData getSamplerWriterData() {
+ return samplerWriterData.get();
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
index 3b5010986f3f..fe7157a67d80 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
@@ -78,10 +78,6 @@ public void registerRunningThreads() {
@Uninterruptible(reason = "Epoch must not change while in this method.")
public void registerThread(Thread thread) {
- if (!SubstrateJVM.isRecording()) {
- return;
- }
-
mutex.lockNoTransition();
try {
registerThread0(thread);
@@ -92,7 +88,6 @@ public void registerThread(Thread thread) {
@Uninterruptible(reason = "Epoch must not change while in this method.")
private void registerThread0(Thread thread) {
- assert SubstrateJVM.isRecording();
JfrThreadEpochData epochData = getEpochData(false);
if (epochData.threadBuffer.isNull()) {
// This will happen only on the first call.
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
index ffae9cade3b6..61f58d35c71c 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
@@ -39,11 +39,13 @@
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.heap.VMOperationInfos;
import com.oracle.svm.core.hub.DynamicHub;
-import com.oracle.svm.core.jfr.events.ExecutionSampleEvent;
import com.oracle.svm.core.jfr.logging.JfrLogging;
+import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
+import com.oracle.svm.core.sampler.SamplerBufferPool;
import com.oracle.svm.core.thread.JavaThreads;
import com.oracle.svm.core.thread.JavaVMOperation;
import com.oracle.svm.core.thread.ThreadListener;
+import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.util.VMError;
import jdk.internal.event.Event;
@@ -53,6 +55,16 @@
/**
* Manager class that handles most JFR Java API, see {@link Target_jdk_jfr_internal_JVM}.
+ *
+ * Here is the execution order of relevant API methods:
+ *
+ * - {@link #createJFR} - initialize the JFR infrastructure but don't record any events yet.
+ * - {@link #setOutput} - set the path of the file where the JFR data should be written to.
+ * - {@link #beginRecording} - start recording JFR events.
+ * - {@link #setOutput} - either switch to a new file or close the current file.
+ * - {@link #endRecording()} - end recording JFR events.
+ * - {@link #destroyJFR()} - destroy the JFR infrastructure and free data.
+ *
*/
public class SubstrateJVM {
private final List knownConfigurations;
@@ -67,15 +79,18 @@ public class SubstrateJVM {
private final JfrThreadLocal threadLocal;
private final JfrGlobalMemory globalMemory;
+ private final SamplerBufferPool samplerBufferPool;
private final JfrUnlockedChunkWriter unlockedChunkWriter;
private final JfrRecorderThread recorderThread;
private final JfrLogging jfrLogging;
private boolean initialized;
- // We need this separate field for all JDK versions, i.e., even for versions where the field
- // JVM.recording is present (JVM.recording is not set for all the cases that we are interested
- // in).
+ /*
+ * We need this separate field for all JDK versions, i.e., even for versions where the field
+ * JVM.recording is present (JVM.recording is not set for all the cases that we are interested
+ * in).
+ */
private volatile boolean recording;
private byte[] metadataDescriptor;
private String dumpPath;
@@ -92,10 +107,10 @@ public SubstrateJVM(List configurations) {
eventSettings[i] = new JfrNativeEventSetting();
}
+ stackTraceRepo = new JfrStackTraceRepository();
symbolRepo = new JfrSymbolRepository();
typeRepo = new JfrTypeRepository();
threadRepo = new JfrThreadRepository();
- stackTraceRepo = new JfrStackTraceRepository();
methodRepo = new JfrMethodRepository();
/*
* The ordering in the array dictates the writing order of constant pools in the recording.
@@ -106,6 +121,7 @@ public SubstrateJVM(List configurations) {
threadLocal = new JfrThreadLocal();
globalMemory = new JfrGlobalMemory();
+ samplerBufferPool = new SamplerBufferPool();
unlockedChunkWriter = new JfrChunkWriter(globalMemory);
recorderThread = new JfrRecorderThread(globalMemory, unlockedChunkWriter);
@@ -141,9 +157,9 @@ public static ThreadListener getThreadLocal() {
return get().threadLocal;
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static long getParentThreadId(IsolateThread isolateThread) {
- return get().threadLocal.getParentThreadId(isolateThread);
+ @Fold
+ public static SamplerBufferPool getSamplerBufferPool() {
+ return get().samplerBufferPool;
}
@Fold
@@ -190,8 +206,8 @@ public static boolean isInitialized() {
return get().initialized;
}
- @Uninterruptible(reason = "Prevent races with threads that start/stop recording.", callerMustBe = true)
- public static boolean isRecording() {
+ @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true)
+ protected boolean isRecording() {
return get().recording;
}
@@ -214,14 +230,15 @@ public boolean createJFR(boolean simulateFailure) {
unlockedChunkWriter.initialize(options.maxChunkSize.getValue());
recorderThread.start();
+
initialized = true;
return true;
}
/**
* See {@link JVM#destroyJFR}. This method is only called after the recording was already
- * stopped. So, no JFR events can be triggered by this or any other thread and we don't need to
- * take any precautions here.
+ * stopped. As no JFR events can be triggered by the current or any other thread, we don't need
+ * to take any precautions here.
*/
public boolean destroyJFR() {
assert !recording : "must already have been stopped";
@@ -229,13 +246,7 @@ public boolean destroyJFR() {
return false;
}
- recorderThread.setStopped(true);
- recorderThread.signal();
- try {
- recorderThread.join();
- } catch (InterruptedException e) {
- throw VMError.shouldNotReachHere(e);
- }
+ recorderThread.shutdown();
globalMemory.teardown();
symbolRepo.teardown();
@@ -256,7 +267,9 @@ public long getStackTraceId(long eventTypeId, int skipCount) {
}
}
- /** See {@link JVM#getStackTraceId}. */
+ /**
+ * See {@link JVM#getStackTraceId}.
+ */
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public long getStackTraceId(int skipCount) {
return stackTraceRepo.getStackTraceId(skipCount);
@@ -267,7 +280,10 @@ public long getStackTraceId(JfrEvent eventType, int skipCount) {
return getStackTraceId(eventType.getId(), skipCount);
}
- /** See {@link JVM#getThreadId}. */
+ /**
+ * See {@link JVM#getThreadId}.
+ */
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static long getThreadId(Thread thread) {
if (HasJfrSupport.get()) {
return JavaThreads.getThreadId(thread);
@@ -276,23 +292,27 @@ public static long getThreadId(Thread thread) {
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static long getThreadId(IsolateThread isolateThread) {
+ public static long getCurrentThreadId() {
if (HasJfrSupport.get()) {
- long threadId = get().threadLocal.getTraceId(isolateThread);
- VMError.guarantee(threadId > 0);
- return threadId;
+ return JavaThreads.getCurrentThreadId();
}
return 0;
}
- /** See {@link JVM#storeMetadataDescriptor}. */
+ /**
+ * See {@link JVM#storeMetadataDescriptor}.
+ */
public void storeMetadataDescriptor(byte[] bytes) {
metadataDescriptor = bytes;
}
- /** See {@link JVM#beginRecording}. */
+ /**
+ * See {@link JVM#beginRecording}.
+ */
public void beginRecording() {
- assert !recording;
+ if (recording) {
+ return;
+ }
JfrChunkWriter chunkWriter = unlockedChunkWriter.lock();
try {
@@ -307,19 +327,21 @@ public void beginRecording() {
vmOp.enqueue();
}
- /** See {@link JVM#endRecording}. */
+ /**
+ * See {@link JVM#endRecording}.
+ */
public void endRecording() {
- assert recording;
+ if (!recording) {
+ return;
+ }
+
JfrEndRecordingOperation vmOp = new JfrEndRecordingOperation();
vmOp.enqueue();
}
- /** See {@link JVM#isRecording}. This is not thread safe */
- public boolean unsafeIsRecording() {
- return recording;
- }
-
- /** See {@link JVM#getClassId}. */
+ /**
+ * See {@link JVM#getClassId}.
+ */
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public long getClassId(Class> clazz) {
return typeRepo.getClassId(clazz);
@@ -354,64 +376,93 @@ public void setOutput(String file) {
}
}
- /** See {@link JVM#setFileNotification}. */
+ /**
+ * See {@link JVM#setFileNotification}.
+ */
public void setFileNotification(long delta) {
options.maxChunkSize.setUserValue(delta);
}
- /** See {@link JVM#setGlobalBufferCount}. */
+ /**
+ * See {@link JVM#setGlobalBufferCount}.
+ */
public void setGlobalBufferCount(long count) {
options.globalBufferCount.setUserValue(count);
}
- /** See {@link JVM#setGlobalBufferSize}. */
+ /**
+ * See {@link JVM#setGlobalBufferSize}.
+ */
public void setGlobalBufferSize(long size) {
options.globalBufferSize.setUserValue(size);
}
- /** See {@link JVM#setMemorySize}. */
+ /**
+ * See {@link JVM#setMemorySize}.
+ */
public void setMemorySize(long size) {
options.memorySize.setUserValue(size);
}
- /** See {@code JVM#setMethodSamplingInterval}. */
+ /**
+ * See {@code JVM#setMethodSamplingInterval}.
+ */
public void setMethodSamplingInterval(long type, long intervalMillis) {
- long millis = intervalMillis;
if (type != JfrEvent.ExecutionSample.getId()) {
// JFR is currently only supporting ExecutionSample event, but this method is called
// during JFR startup, so we can't throw an error.
return;
}
- if (millis > 0) {
- SubstrateJVM.get().setEnabled(type, true);
- /* Stacktrace walk is disabled by default for ExecutionSample. */
+ JfrExecutionSampler.singleton().setIntervalMillis(intervalMillis);
+
+ if (intervalMillis > 0) {
SubstrateJVM.get().setStackTraceEnabled(type, true);
- } else {
- millis = 0;
+ SubstrateJVM.get().setEnabled(type, true);
}
- ExecutionSampleEvent.setSamplingInterval(millis);
+
+ updateSampler();
+ }
+
+ @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.")
+ private void updateSampler() {
+ if (recording) {
+ updateSampler0();
+ }
+ }
+
+ @Uninterruptible(reason = "The executed VM operation rechecks if JFR recording is active.", calleeMustBe = false)
+ private static void updateSampler0() {
+ JfrExecutionSampler.singleton().update();
}
- /** See {@code JVM#setSampleThreads}. */
+ /**
+ * See {@code JVM#setSampleThreads}.
+ */
public void setSampleThreads(boolean sampleThreads) {
setEnabled(JfrEvent.ExecutionSample.getId(), sampleThreads);
setEnabled(JfrEvent.NativeMethodSample.getId(), sampleThreads);
}
- /** See {@link JVM#setCompressedIntegers}. */
+ /**
+ * See {@link JVM#setCompressedIntegers}.
+ */
public void setCompressedIntegers(boolean compressed) {
if (!compressed) {
throw new IllegalStateException("JFR currently only supports compressed integers.");
}
}
- /** See {@link JVM#setStackDepth}. */
+ /**
+ * See {@link JVM#setStackDepth}.
+ */
public void setStackDepth(int depth) {
stackTraceRepo.setStackTraceDepth(depth);
}
- /** See {@link JVM#setStackTraceEnabled}. */
+ /**
+ * See {@link JVM#setStackTraceEnabled}.
+ */
public void setStackTraceEnabled(long eventTypeId, boolean enabled) {
eventSettings[NumUtil.safeToInt(eventTypeId)].setStackTrace(enabled);
}
@@ -422,12 +473,16 @@ public boolean isStackTraceEnabled(long eventTypeId) {
return eventSettings[(int) eventTypeId].hasStackTrace();
}
- /** See {@link JVM#setThreadBufferSize}. */
+ /**
+ * See {@link JVM#setThreadBufferSize}.
+ */
public void setThreadBufferSize(long size) {
options.threadBufferSize.setUserValue(size);
}
- /** See {@link JVM#flush}. */
+ /**
+ * See {@link JVM#flush}.
+ */
@Uninterruptible(reason = "Accesses a JFR buffer.")
public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommittedSize, int requestedSize) {
assert writer != null;
@@ -459,17 +514,23 @@ public boolean flush(Target_jdk_jfr_internal_EventWriter writer, int uncommitted
return false;
}
- /** See {@link JVM#setRepositoryLocation}. */
+ /**
+ * See {@link JVM#setRepositoryLocation}.
+ */
public void setRepositoryLocation(@SuppressWarnings("unused") String dirText) {
// Would only be used in case of an emergency dump, which is not supported at the moment.
}
- /** See {@code JfrEmergencyDump::set_dump_path}. */
+ /**
+ * See {@code JfrEmergencyDump::set_dump_path}.
+ */
public void setDumpPath(String dumpPathText) {
dumpPath = dumpPathText;
}
- /** See {@code JVM#getDumpPath()}. */
+ /**
+ * See {@code JVM#getDumpPath()}.
+ */
public String getDumpPath() {
if (dumpPath == null) {
dumpPath = Target_jdk_jfr_internal_SecuritySupport.getPathInProperty("user.home", null).toString();
@@ -477,12 +538,16 @@ public String getDumpPath() {
return dumpPath;
}
- /** See {@link JVM#abort}. */
+ /**
+ * See {@link JVM#abort}.
+ */
public void abort(String errorMsg) {
throw VMError.shouldNotReachHere(errorMsg);
}
- /** See {@link JVM#shouldRotateDisk}. */
+ /**
+ * See {@link JVM#shouldRotateDisk}.
+ */
public boolean shouldRotateDisk() {
JfrChunkWriter chunkWriter = unlockedChunkWriter.lock();
try {
@@ -501,29 +566,46 @@ public long getChunkStartNanos() {
}
}
- /** See {@link JVM#log}. */
+ /**
+ * See {@link JVM#log}.
+ */
public void log(int tagSetId, int level, String message) {
jfrLogging.log(tagSetId, level, message);
}
- /** See {@link JVM#subscribeLogLevel}. */
+ /**
+ * See {@link JVM#subscribeLogLevel}.
+ */
public void subscribeLogLevel(@SuppressWarnings("unused") LogTag lt, @SuppressWarnings("unused") int tagSetId) {
// Currently unused because logging support is minimal.
}
- /** See {@link JVM#getEventWriter}. */
+ /**
+ * See {@link JVM#getEventWriter}.
+ */
public Target_jdk_jfr_internal_EventWriter getEventWriter() {
return threadLocal.getEventWriter();
}
- /** See {@link JVM#newEventWriter}. */
+ /**
+ * See {@link JVM#newEventWriter}.
+ */
public Target_jdk_jfr_internal_EventWriter newEventWriter() {
return threadLocal.newEventWriter();
}
- /** See {@link JVM#setEnabled}. */
- public void setEnabled(long eventTypeId, boolean enabled) {
- eventSettings[NumUtil.safeToInt(eventTypeId)].setEnabled(enabled);
+ /**
+ * See {@link JVM#setEnabled}.
+ */
+ public void setEnabled(long eventTypeId, boolean newValue) {
+ boolean oldValue = eventSettings[NumUtil.safeToInt(eventTypeId)].isEnabled();
+ if (newValue != oldValue) {
+ eventSettings[NumUtil.safeToInt(eventTypeId)].setEnabled(newValue);
+
+ if (eventTypeId == JfrEvent.ExecutionSample.getId()) {
+ updateSampler();
+ }
+ }
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -541,13 +623,17 @@ public boolean isLarge(JfrEvent event) {
return eventSettings[(int) event.getId()].isLarge();
}
- /** See {@link JVM#setThreshold}. */
+ /**
+ * See {@link JVM#setThreshold}.
+ */
public boolean setThreshold(long eventTypeId, long ticks) {
eventSettings[NumUtil.safeToInt(eventTypeId)].setThresholdTicks(ticks);
return true;
}
- /** See {@link JVM#setCutoff}. */
+ /**
+ * See {@link JVM#setCutoff}.
+ */
public boolean setCutoff(long eventTypeId, long cutoffTicks) {
eventSettings[NumUtil.safeToInt(eventTypeId)].setCutoffTicks(cutoffTicks);
return true;
@@ -569,10 +655,11 @@ private static class JfrBeginRecordingOperation extends JavaVMOperation {
@Override
protected void operate() {
- SubstrateJVM.get().recording = true;
SubstrateJVM.getThreadRepo().registerRunningThreads();
- // After changing the value of recording to true, JFR events can be triggered at any
- // time.
+ SubstrateJVM.get().recording = true;
+ /* Recording is enabled, so JFR events can be triggered at any time. */
+
+ JfrExecutionSampler.singleton().update();
}
}
@@ -581,11 +668,22 @@ private static class JfrEndRecordingOperation extends JavaVMOperation {
super(VMOperationInfos.get(JfrEndRecordingOperation.class, "JFR end recording", SystemEffect.SAFEPOINT));
}
+ /**
+ * When the safepoint ends, it is guaranteed that all {@link JfrNativeEventWriter}s finished
+ * their job and that no further JFR events will be triggered. It is also guaranteed that no
+ * thread executes any code related to the execution sampling.
+ */
@Override
protected void operate() {
SubstrateJVM.get().recording = false;
- // After the safepoint, it is guaranteed that all JfrNativeEventWriters finished their
- // job and that no further JFR events will be triggered.
+ JfrExecutionSampler.singleton().update();
+
+ /* Free all JFR-related buffers (no further JFR events may be triggered). */
+ for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) {
+ JfrThreadLocal.stopRecording(isolateThread, false);
+ }
+
+ SubstrateJVM.getSamplerBufferPool().teardown();
}
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
index b89b90340d76..937a22b64879 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
@@ -29,6 +29,7 @@
import org.graalvm.nativeimage.ProcessProperties;
import com.oracle.svm.core.Containers;
+import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.Substitute;
@@ -96,17 +97,26 @@ public void endRecording() {
/** See {@link JVM#isRecording}. */
@Substitute
+ @Uninterruptible(reason = "Needed for calling SubstrateJVM.isRecording().")
@TargetElement(onlyWith = JDK17OrLater.class)
public boolean isRecording() {
- return SubstrateJVM.get().unsafeIsRecording();
+ return SubstrateJVM.get().isRecording();
}
/** See {@link JVM#getAllEventClasses}. */
@Substitute
- public List> getAllEventClasses() {
+ @TargetElement(onlyWith = JDK17OrLater.class)
+ public List> getAllEventClasses() {
return JfrJavaEvents.getAllEventClasses();
}
+ /** See {@link JVM#getAllEventClasses}. */
+ @Substitute
+ @TargetElement(name = "getAllEventClasses", onlyWith = JDK11OrEarlier.class)
+ public List> getAllEventClassesJDK11() {
+ return JfrJavaEvents.getJfrEventClasses();
+ }
+
/** See {@link JVM#getUnloadedEventClassCount}. */
@Substitute
public long getUnloadedEventClassCount() {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EndChunkNativePeriodicEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EndChunkNativePeriodicEvents.java
index 6dbbad5b6961..edadb7bfb830 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EndChunkNativePeriodicEvents.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EndChunkNativePeriodicEvents.java
@@ -64,7 +64,7 @@ public static void emit() {
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emitInitialEnvironmentVariables(StringEntry[] envs) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.InitialEnvironmentVariable)) {
+ if (JfrEvent.InitialEnvironmentVariable.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
boolean isLarge = SubstrateJVM.get().isLarge(JfrEvent.InitialEnvironmentVariable);
@@ -89,7 +89,7 @@ private static JfrEventWriteStatus emitInitialEnvironmentVariable(JfrNativeEvent
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emitInitialSystemProperties(StringEntry[] systemProperties) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.InitialSystemProperty)) {
+ if (JfrEvent.InitialSystemProperty.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
boolean isLarge = SubstrateJVM.get().isLarge(JfrEvent.InitialSystemProperty);
@@ -114,7 +114,7 @@ private static JfrEventWriteStatus emitInitialSystemProperty(JfrNativeEventWrite
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emitClassLoadingStatistics(long loadedClassCount) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ClassLoadingStatistics)) {
+ if (JfrEvent.ClassLoadingStatistics.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
@@ -128,7 +128,7 @@ private static void emitClassLoadingStatistics(long loadedClassCount) {
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emitJVMInformation(JVMInformation jvmInformation) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.JVMInformation)) {
+ if (JfrEvent.JVMInformation.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
@@ -156,7 +156,7 @@ private static JfrEventWriteStatus emitJVMInformation0(JfrNativeEventWriterData
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emitOSInformation(String osVersion) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.OSInformation)) {
+ if (JfrEvent.OSInformation.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java
index 8d0384a3809f..a25144e6b2fc 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java
@@ -35,7 +35,6 @@
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 jdk.jfr.Event;
import jdk.jfr.Name;
@@ -55,7 +54,7 @@ public static void emit() {
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emitJavaThreadStats(long activeCount, long daemonCount, long accumulatedCount, long peakCount) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.JavaThreadStatistics)) {
+ if (JfrEvent.JavaThreadStatistics.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
@@ -71,7 +70,7 @@ private static void emitJavaThreadStats(long activeCount, long daemonCount, long
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emitPhysicalMemory(long totalSize, long usedSize) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.PhysicalMemory)) {
+ if (JfrEvent.PhysicalMemory.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java
index decbfb57af3c..931ad313e278 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java
@@ -26,7 +26,6 @@
package com.oracle.svm.core.jfr.events;
-import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.StackValue;
import com.oracle.svm.core.Uninterruptible;
@@ -36,18 +35,21 @@
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 com.oracle.svm.core.thread.Safepoint;
import com.oracle.svm.core.thread.VMOperation;
public class ExecuteVMOperationEvent {
- @Uninterruptible(reason = "Accesses a JFR buffer.")
- public static void emit(VMOperation vmOperation, IsolateThread requestingThread, long startTicks) {
+ public static void emit(VMOperation vmOperation, long requestingThreadId, long startTicks) {
if (!HasJfrSupport.get()) {
return;
}
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ExecuteVMOperation)) {
+ emit0(vmOperation, requestingThreadId, startTicks);
+ }
+
+ @Uninterruptible(reason = "Accesses a JFR buffer.")
+ private static void emit0(VMOperation vmOperation, long requestingThreadId, long startTicks) {
+ if (JfrEvent.ExecuteVMOperation.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
@@ -58,7 +60,7 @@ public static void emit(VMOperation vmOperation, IsolateThread requestingThread,
JfrNativeEventWriter.putLong(data, vmOperation.getId() + 1); // id starts with 1
JfrNativeEventWriter.putBoolean(data, vmOperation.getCausesSafepoint());
JfrNativeEventWriter.putBoolean(data, vmOperation.isBlocking());
- JfrNativeEventWriter.putThread(data, requestingThread);
+ JfrNativeEventWriter.putThread(data, requestingThreadId);
JfrNativeEventWriter.putLong(data, vmOperation.getCausesSafepoint() ? Safepoint.Master.singleton().getSafepointId().rawValue() : 0);
JfrNativeEventWriter.endSmallEvent(data);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java
index 6ee9ea35a2e8..1a6723b9a532 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java
@@ -24,45 +24,18 @@
*/
package com.oracle.svm.core.jfr.events;
-import java.util.concurrent.TimeUnit;
-
-import org.graalvm.nativeimage.CurrentIsolate;
-import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.StackValue;
-import org.graalvm.nativeimage.Threading;
-import org.graalvm.nativeimage.impl.ThreadingSupport;
import com.oracle.svm.core.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.JfrThreadState;
-import com.oracle.svm.core.jfr.JfrTicks;
-import com.oracle.svm.core.jfr.SubstrateJVM;
-import com.oracle.svm.core.thread.PlatformThreads;
public final class ExecutionSampleEvent {
-
- private static long intervalMillis;
- private static final ExecutionSampleEventCallback CALLBACK = new ExecutionSampleEventCallback();
-
- @Uninterruptible(reason = "Called from uninterruptible code.", calleeMustBe = false)
- public static void tryToRegisterExecutionSampleEventCallback() {
- if (intervalMillis > 0) {
- ImageSingletons.lookup(ThreadingSupport.class).registerRecurringCallback(intervalMillis, TimeUnit.MILLISECONDS, CALLBACK);
- }
- }
-
- public static void setSamplingInterval(long intervalMillis) {
- ExecutionSampleEvent.intervalMillis = intervalMillis;
- }
-
@Uninterruptible(reason = "Accesses a JFR buffer.")
public static void writeExecutionSample(long elapsedTicks, long threadId, long stackTraceId, long threadState) {
- SubstrateJVM svm = SubstrateJVM.get();
- if (SubstrateJVM.isRecording() && svm.isEnabled(JfrEvent.ExecutionSample)) {
+ if (JfrEvent.ExecutionSample.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
@@ -74,17 +47,4 @@ public static void writeExecutionSample(long elapsedTicks, long threadId, long s
JfrNativeEventWriter.endSmallEvent(data);
}
}
-
- private static final class ExecutionSampleEventCallback implements Threading.RecurringCallback {
- @Override
- public void run(Threading.RecurringCallbackAccess access) {
- IsolateThread isolateThread = CurrentIsolate.getCurrentThread();
- Thread javaThread = PlatformThreads.fromVMThread(isolateThread);
- ExecutionSampleEvent.writeExecutionSample(
- JfrTicks.elapsedTicks(),
- SubstrateJVM.getThreadId(isolateThread),
- SubstrateJVM.get().getStackTraceId(JfrEvent.ExecutionSample, 0),
- JfrThreadState.getId(javaThread.getState()));
- }
- }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java
index b2a20731fe80..b91b226d1cdf 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java
@@ -26,17 +26,17 @@
package com.oracle.svm.core.jfr.events;
-import com.oracle.svm.core.jfr.HasJfrSupport;
+import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.StackValue;
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.jfr.HasJfrSupport;
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;
public class JavaMonitorEnterEvent {
public static void emit(Object obj, long previousOwnerTid, long startTicks) {
@@ -47,7 +47,7 @@ public static void emit(Object obj, long previousOwnerTid, long startTicks) {
@Uninterruptible(reason = "Accesses a JFR buffer.")
public static void emit0(Object obj, long previousOwnerTid, long startTicks) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.JavaMonitorEnter)) {
+ if (JfrEvent.JavaMonitorEnter.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
@@ -60,7 +60,6 @@ public static void emit0(Object obj, long previousOwnerTid, long startTicks) {
JfrNativeEventWriter.putLong(data, previousOwnerTid);
JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue());
JfrNativeEventWriter.endSmallEvent(data);
-
}
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java
index 6d95c2ed6abd..1a8abfc8fd3f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java
@@ -26,15 +26,16 @@
package com.oracle.svm.core.jfr.events;
+import org.graalvm.compiler.word.Word;
+
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.jfr.HasJfrSupport;
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) {
@@ -45,7 +46,7 @@ public static void emit(long startTicks, Object obj, long notifier, long timeout
@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)) {
+ if (JfrEvent.JavaMonitorWait.shouldEmit()) {
JfrNativeEventWriterData data = org.graalvm.nativeimage.StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.JavaMonitorWait);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointBeginEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointBeginEvent.java
index 34b359c6837e..069068a616b3 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointBeginEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointBeginEvent.java
@@ -36,7 +36,6 @@
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 SafepointBeginEvent {
public static void emit(UnsignedWord safepointId, int numJavaThreads, long startTicks) {
@@ -53,7 +52,7 @@ public static void emit(UnsignedWord safepointId, int numJavaThreads, long start
*/
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emit0(UnsignedWord safepointId, int numJavaThreads, long startTicks) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.SafepointBegin)) {
+ if (JfrEvent.SafepointBegin.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java
index 04ab808d39db..a84090fe11af 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/SafepointEndEvent.java
@@ -36,7 +36,6 @@
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 SafepointEndEvent {
public static void emit(UnsignedWord safepointId, long startTick) {
@@ -48,7 +47,7 @@ public static void emit(UnsignedWord safepointId, long startTick) {
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emit0(UnsignedWord safepointId, long startTick) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.SafepointEnd)) {
+ if (JfrEvent.SafepointEnd.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadEndEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadEndEvent.java
index 7f8bd13eee94..da047b1f1306 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadEndEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadEndEvent.java
@@ -24,7 +24,6 @@
*/
package com.oracle.svm.core.jfr.events;
-import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.StackValue;
import com.oracle.svm.core.Uninterruptible;
@@ -33,20 +32,19 @@
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 ThreadEndEvent {
@Uninterruptible(reason = "Accesses a JFR buffer.")
- public static void emit(IsolateThread isolateThread) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadEnd)) {
+ public static void emit(Thread thread) {
+ if (JfrEvent.ThreadEnd.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadEnd);
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks());
JfrNativeEventWriter.putEventThread(data);
- JfrNativeEventWriter.putThread(data, isolateThread);
+ JfrNativeEventWriter.putThread(data, thread);
JfrNativeEventWriter.endSmallEvent(data);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java
index 2ee102f436d5..5cef29f2319e 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadParkEvent.java
@@ -26,16 +26,17 @@
package com.oracle.svm.core.jfr.events;
+import org.graalvm.compiler.word.Word;
+import org.graalvm.nativeimage.StackValue;
+
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.jfr.HasJfrSupport;
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 com.oracle.svm.core.jfr.HasJfrSupport;
-import org.graalvm.compiler.word.Word;
-import org.graalvm.nativeimage.StackValue;
public class ThreadParkEvent {
public static void emit(long startTicks, Object obj, long timeout, long until) {
@@ -46,7 +47,7 @@ public static void emit(long startTicks, Object obj, long timeout, long until) {
@Uninterruptible(reason = "Accesses a JFR buffer.")
private static void emit0(long startTicks, Object obj, long timeout, long until) {
- if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadPark)) {
+ if (JfrEvent.ThreadPark.shouldEmit()) {
Class> parkedClass = null;
if (obj != null) {
parkedClass = obj.getClass();
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java
similarity index 87%
rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java
index 2c779b5db58a..3bc0d558087b 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java
@@ -29,6 +29,7 @@
import org.graalvm.nativeimage.StackValue;
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.jfr.HasJfrSupport;
import com.oracle.svm.core.jfr.JfrEvent;
import com.oracle.svm.core.jfr.JfrNativeEventWriter;
import com.oracle.svm.core.jfr.JfrNativeEventWriterData;
@@ -36,25 +37,26 @@
import com.oracle.svm.core.jfr.JfrTicks;
import com.oracle.svm.core.jfr.SubstrateJVM;
-public class ThreadSleepEvent {
+public class ThreadSleepEventJDK17 {
+ private static final JfrEvent ThreadSleep = JfrEvent.create("jdk.ThreadSleep");
public static void emit(long time, long startTicks) {
- if (com.oracle.svm.core.jfr.HasJfrSupport.get()) {
+ if (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)) {
+ if (ThreadSleep.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
- JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadSleep);
+ JfrNativeEventWriter.beginSmallEvent(data, 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, SubstrateJVM.get().getStackTraceId(ThreadSleep, 0));
JfrNativeEventWriter.putLong(data, time);
JfrNativeEventWriter.endSmallEvent(data);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java
index 462de3d381e9..c591c25d1a2c 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java
@@ -24,7 +24,6 @@
*/
package com.oracle.svm.core.jfr.events;
-import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.StackValue;
import com.oracle.svm.core.Uninterruptible;
@@ -34,22 +33,22 @@
import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess;
import com.oracle.svm.core.jfr.JfrTicks;
import com.oracle.svm.core.jfr.SubstrateJVM;
+import com.oracle.svm.core.thread.JavaThreads;
public class ThreadStartEvent {
@Uninterruptible(reason = "Accesses a JFR buffer.")
- public static void emit(IsolateThread isolateThread) {
- SubstrateJVM svm = SubstrateJVM.get();
- if (SubstrateJVM.isRecording() && svm.isEnabled(JfrEvent.ThreadStart)) {
+ public static void emit(Thread thread) {
+ if (JfrEvent.ThreadStart.shouldEmit()) {
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadStart);
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks());
JfrNativeEventWriter.putEventThread(data);
- JfrNativeEventWriter.putLong(data, svm.getStackTraceId(JfrEvent.ThreadStart, 0));
- JfrNativeEventWriter.putThread(data, isolateThread);
- JfrNativeEventWriter.putLong(data, SubstrateJVM.getParentThreadId(isolateThread));
+ JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadStart, 0));
+ JfrNativeEventWriter.putThread(data, thread);
+ JfrNativeEventWriter.putLong(data, JavaThreads.getParentThreadId(thread));
JfrNativeEventWriter.endSmallEvent(data);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/AbstractJfrExecutionSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/AbstractJfrExecutionSampler.java
new file mode 100644
index 000000000000..49da48b82b5d
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/AbstractJfrExecutionSampler.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2022, 2022, 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. 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.sampler;
+
+import org.graalvm.compiler.api.replacements.Fold;
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.StackValue;
+import org.graalvm.nativeimage.c.function.CodePointer;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.code.CodeInfo;
+import com.oracle.svm.core.code.CodeInfoAccess;
+import com.oracle.svm.core.code.CodeInfoTable;
+import com.oracle.svm.core.heap.VMOperationInfos;
+import com.oracle.svm.core.jdk.UninterruptibleUtils;
+import com.oracle.svm.core.jfr.JfrEvent;
+import com.oracle.svm.core.jfr.JfrThreadLocal;
+import com.oracle.svm.core.jfr.SubstrateJVM;
+import com.oracle.svm.core.sampler.SamplerSampleWriter;
+import com.oracle.svm.core.sampler.SamplerSampleWriterData;
+import com.oracle.svm.core.sampler.SamplerSampleWriterDataAccess;
+import com.oracle.svm.core.sampler.SamplerStackWalkVisitor;
+import com.oracle.svm.core.stack.JavaFrameAnchor;
+import com.oracle.svm.core.stack.JavaFrameAnchors;
+import com.oracle.svm.core.stack.JavaStackWalker;
+import com.oracle.svm.core.thread.JavaVMOperation;
+import com.oracle.svm.core.thread.VMOperation;
+import com.oracle.svm.core.thread.VMThreads;
+import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
+import com.oracle.svm.core.threadlocal.FastThreadLocalInt;
+
+/*
+ * Base class for different sampler implementations that emit JFR ExecutionSample events.
+ *
+ * The sampler does a stack walk and writes the encountered IPs into a {@link SamplerBuffer}. As it is
+ * impossible to allocate buffers in the sampler, the {@link SamplerBufferPool} is used to ensure that
+ * there are pre-allocated buffers available.
+ *
+ * Conceptually, the sampler produces {@link SamplerBuffer}s, while the {@link JfrRecorderThread} consumes
+ * those buffers. The producer and the consumer are always accessing different buffers:
+ *
+ * - Sampler: pop a buffer from the pool of available buffers and write the IPs into the
+ * buffer. Once the buffer is full, add it to the buffers that await processing.
+ * - {@link JfrRecorderThread}: process the full buffers and reconstruct the stack trace
+ * information based on the IPs.
+ *
+ *
+ * In rare cases, profiling is impossible (e.g., no available buffers in the pool, an unknown IP is
+ * encountered during the stack walk, or the thread holds the pool's lock when the signal arrives).
+ * If such a situation is detected, the sample is omitted.
+ */
+public abstract class AbstractJfrExecutionSampler extends JfrExecutionSampler {
+ private static final FastThreadLocalInt samplerState = FastThreadLocalFactory.createInt("JfrSampler.samplerState");
+ private static final FastThreadLocalInt isDisabledForCurrentThread = FastThreadLocalFactory.createInt("JfrSampler.isDisabledForCurrentThread");
+
+ private final UninterruptibleUtils.AtomicInteger isSignalHandlerDisabledGlobally = new UninterruptibleUtils.AtomicInteger(0);
+ private final UninterruptibleUtils.AtomicInteger threadsInSignalHandler = new UninterruptibleUtils.AtomicInteger(0);
+
+ private volatile boolean isSampling;
+ private long curIntervalMillis;
+ protected long newIntervalMillis;
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public AbstractJfrExecutionSampler() {
+ }
+
+ @Fold
+ public static AbstractJfrExecutionSampler singleton() {
+ return (AbstractJfrExecutionSampler) ImageSingletons.lookup(JfrExecutionSampler.class);
+ }
+
+ @Fold
+ protected static UninterruptibleUtils.AtomicInteger threadsInSignalHandler() {
+ return singleton().threadsInSignalHandler;
+ }
+
+ /** Only sets the field. To apply the new value, {@link #update} needs to be called. */
+ @Override
+ public void setIntervalMillis(long intervalMillis) {
+ newIntervalMillis = intervalMillis;
+ }
+
+ @Override
+ @Uninterruptible(reason = "Prevent VM operations that modify execution sampling.", callerMustBe = true)
+ public boolean isSampling() {
+ return isSampling;
+ }
+
+ @Override
+ public void update() {
+ UpdateJfrExecutionSamplerOperation op = new UpdateJfrExecutionSamplerOperation();
+ op.enqueue();
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void preventSamplingInCurrentThread() {
+ int newValue = isDisabledForCurrentThread.get() + 1;
+ assert newValue >= 0;
+ isDisabledForCurrentThread.set(newValue);
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void allowSamplingInCurrentThread() {
+ int newValue = isDisabledForCurrentThread.get() - 1;
+ assert newValue >= -1;
+ isDisabledForCurrentThread.set(newValue);
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void disallowThreadsInSamplerCode() {
+ /* Prevent threads from entering the sampler code. */
+ int value = isSignalHandlerDisabledGlobally.incrementAndGet();
+ assert value > 0;
+
+ /* Wait until there are no more threads in the sampler code. */
+ while (threadsInSignalHandler.get() > 0) {
+ VMThreads.singleton().yield();
+ }
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void allowThreadsInSamplerCode() {
+ int value = isSignalHandlerDisabledGlobally.decrementAndGet();
+ assert value >= 0;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected static boolean isExecutionSamplingAllowedInCurrentThread() {
+ boolean disallowed = singleton().isSignalHandlerDisabledGlobally.get() > 0 ||
+ isDisabledForCurrentThread.get() > 0 ||
+ SubstrateJVM.getSamplerBufferPool().isLockedByCurrentThread();
+
+ return ExecutionSamplerInstallation.isInstalled(CurrentIsolate.getCurrentThread()) && !disallowed;
+ }
+
+ protected abstract void startSampling();
+
+ protected abstract void stopSampling();
+
+ protected abstract void updateInterval();
+
+ @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
+ protected static void tryUninterruptibleStackWalk(CodePointer ip, Pointer sp) {
+ /*
+ * To prevent races, it is crucial that the thread count is incremented before we do any
+ * other checks.
+ */
+ threadsInSignalHandler().incrementAndGet();
+ try {
+ if (isExecutionSamplingAllowedInCurrentThread()) {
+ doUninterruptibleStackWalk(ip, sp);
+ } else {
+ JfrThreadLocal.increaseMissedSamples();
+ }
+ } finally {
+ threadsInSignalHandler().decrementAndGet();
+ }
+ }
+
+ @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
+ private static void doUninterruptibleStackWalk(CodePointer initialIp, Pointer initialSp) {
+ CodePointer ip = initialIp;
+ Pointer sp = initialSp;
+ if (!isInAOTCompiledCode(ip)) {
+ JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor();
+ if (anchor.isNull()) {
+ /*
+ * The anchor is still null if the function is interrupted during prologue. See:
+ * com.oracle.svm.core.graal.snippets.CFunctionSnippets.prologueSnippet
+ */
+ return;
+ }
+
+ ip = anchor.getLastJavaIP();
+ sp = anchor.getLastJavaSP();
+ if (ip.isNull() || sp.isNull()) {
+ /*
+ * It can happen that anchor is in the list of all anchors, but its IP and SP are
+ * not filled yet.
+ */
+ return;
+ }
+ }
+
+ /* Try to do a stack walk. */
+ SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
+ if (SamplerSampleWriterDataAccess.initialize(data, 0, false)) {
+ JfrThreadLocal.setSamplerWriterData(data);
+ try {
+ SamplerSampleWriter.begin(data);
+ SamplerStackWalkVisitor visitor = ImageSingletons.lookup(SamplerStackWalkVisitor.class);
+ if (JavaStackWalker.walkCurrentThread(sp, ip, visitor) || data.getTruncated()) {
+ SamplerSampleWriter.end(data, SamplerSampleWriter.EXECUTION_SAMPLE_END);
+ }
+ } finally {
+ JfrThreadLocal.setSamplerWriterData(WordFactory.nullPointer());
+ }
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private static boolean isInAOTCompiledCode(CodePointer ip) {
+ CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo();
+ return CodeInfoAccess.contains(codeInfo, ip);
+ }
+
+ /**
+ * Starts/Stops execution sampling and updates the sampling interval.
+ *
+ * This needs to be a VM operation, because a lot of different races could happen otherwise.
+ * Another thread could for example a. enable/disable JFR recording, b. start/stop execution
+ * sampling, c. enable/disable the JFR ExecutionSample event. Therefore, we need to re-query all
+ * the information once we are in the VM operation.
+ */
+ private static class UpdateJfrExecutionSamplerOperation extends JavaVMOperation {
+ UpdateJfrExecutionSamplerOperation() {
+ super(VMOperationInfos.get(UpdateJfrExecutionSamplerOperation.class, "Update JFR sampler", SystemEffect.SAFEPOINT));
+ }
+
+ @Override
+ protected void operate() {
+ AbstractJfrExecutionSampler sampler = AbstractJfrExecutionSampler.singleton();
+ boolean shouldSample = shouldSample();
+ if (sampler.isSampling != shouldSample) {
+ if (shouldSample) {
+ sampler.startSampling();
+ sampler.isSampling = true;
+ } else {
+ sampler.stopSampling();
+ sampler.isSampling = false;
+ }
+ } else if (shouldSample && sampler.newIntervalMillis != sampler.curIntervalMillis) {
+ /* We are already recording but the interval needs to be updated. */
+ sampler.updateInterval();
+ }
+ sampler.curIntervalMillis = sampler.newIntervalMillis;
+ }
+
+ @Uninterruptible(reason = "Needed for calling JfrEvent.shouldEmit().")
+ private static boolean shouldSample() {
+ assert VMOperation.isInProgressAtSafepoint();
+ return JfrEvent.ExecutionSample.shouldEmit() && AbstractJfrExecutionSampler.singleton().newIntervalMillis > 0;
+ }
+ }
+
+ protected static class ExecutionSamplerInstallation {
+ private static final int DISALLOWED = -1;
+ private static final int ALLOWED = 0;
+ private static final int INSTALLED = 1;
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the execution sampler.", callerMustBe = true)
+ public static void disallow(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+ assert samplerState.get(thread) == ALLOWED;
+ samplerState.set(thread, DISALLOWED);
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the execution sampler.", callerMustBe = true)
+ public static void installed(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+ assert samplerState.get(thread) == ALLOWED;
+ samplerState.set(thread, INSTALLED);
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the execution sampler.", callerMustBe = true)
+ public static void uninstalled(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+ assert samplerState.get(thread) == INSTALLED;
+ samplerState.set(thread, ALLOWED);
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the execution sampler.", callerMustBe = true)
+ public static boolean isAllowed(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+ return samplerState.get(thread) == ALLOWED;
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the execution sampler.", callerMustBe = true)
+ public static boolean isInstalled(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+ return samplerState.get(thread) == INSTALLED;
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerHasSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrExecutionSampler.java
similarity index 55%
rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerHasSupport.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrExecutionSampler.java
index 5f5894c20e3a..a96e1c5abf50 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerHasSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrExecutionSampler.java
@@ -22,27 +22,34 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
+package com.oracle.svm.core.jfr.sampler;
-package com.oracle.svm.core.sampler;
-
-import java.util.function.BooleanSupplier;
-
+import com.oracle.svm.core.Uninterruptible;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.nativeimage.ImageSingletons;
-/**
- * Returns {@code true} if the Native Image is built with Sampler support. This does not necessarily
- * mean that Sampler is really enabled at runtime (see
- * {@link SubstrateSigprofHandler#isProfilingEnabled()}).
- */
-public class SamplerHasSupport implements BooleanSupplier {
- @Override
- public boolean getAsBoolean() {
- return get();
- }
-
+public abstract class JfrExecutionSampler {
@Fold
- public static boolean get() {
- return ImageSingletons.contains(SubstrateSigprofHandler.class);
+ public static JfrExecutionSampler singleton() {
+ return ImageSingletons.lookup(JfrExecutionSampler.class);
}
+
+ @Uninterruptible(reason = "Prevent VM operations that modify execution sampling.", callerMustBe = true)
+ public abstract boolean isSampling();
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public abstract void preventSamplingInCurrentThread();
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public abstract void allowSamplingInCurrentThread();
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public abstract void disallowThreadsInSamplerCode();
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public abstract void allowThreadsInSamplerCode();
+
+ public abstract void setIntervalMillis(long intervalMillis);
+
+ public abstract void update();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrNoExecutionSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrNoExecutionSampler.java
new file mode 100644
index 000000000000..6f991c8b1199
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrNoExecutionSampler.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2022, 2022, 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. 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.sampler;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.oracle.svm.core.Uninterruptible;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.hosted.Feature;
+
+import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
+import com.oracle.svm.core.feature.InternalFeature;
+import com.oracle.svm.core.jfr.JfrFeature;
+
+public class JfrNoExecutionSampler extends JfrExecutionSampler {
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public JfrNoExecutionSampler() {
+ }
+
+ @Override
+ @Uninterruptible(reason = "Prevent VM operations that modify execution sampling.", callerMustBe = true)
+ public boolean isSampling() {
+ return false;
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void preventSamplingInCurrentThread() {
+ /* Nothing to do. */
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void allowSamplingInCurrentThread() {
+ /* Nothing to do. */
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void disallowThreadsInSamplerCode() {
+ /* Nothing to do. */
+ }
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void allowThreadsInSamplerCode() {
+ /* Nothing to do. */
+ }
+
+ @Override
+ public void setIntervalMillis(long intervalMillis) {
+ /* Nothing to do. */
+ }
+
+ @Override
+ public void update() {
+ /* Nothing to do. */
+ }
+}
+
+@AutomaticallyRegisteredFeature
+class JfrNoExecutionSamplerFeature implements InternalFeature {
+ @Override
+ public List> getRequiredFeatures() {
+ return Collections.singletonList(JfrFeature.class);
+ }
+
+ @Override
+ public void afterRegistration(AfterRegistrationAccess access) {
+ if (!JfrFeature.isExecutionSamplerSupported()) {
+ ImageSingletons.add(JfrExecutionSampler.class, new JfrNoExecutionSampler());
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrRecurringCallbackExecutionSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrRecurringCallbackExecutionSampler.java
new file mode 100644
index 000000000000..e070684ef53a
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/JfrRecurringCallbackExecutionSampler.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2022, 2022, 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. 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.sampler;
+
+import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer;
+import static com.oracle.svm.core.snippets.KnownIntrinsics.readReturnAddress;
+
+import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
+import com.oracle.svm.core.feature.InternalFeature;
+import com.oracle.svm.core.jfr.JfrFeature;
+import com.oracle.svm.core.thread.ThreadListenerSupport;
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.Threading;
+import org.graalvm.nativeimage.c.function.CodePointer;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.word.Pointer;
+
+import com.oracle.svm.core.NeverInline;
+import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.jfr.SubstrateJVM;
+import com.oracle.svm.core.thread.ThreadListener;
+import com.oracle.svm.core.thread.ThreadingSupportImpl;
+import com.oracle.svm.core.thread.ThreadingSupportImpl.RecurringCallbackTimer;
+import com.oracle.svm.core.thread.VMOperation;
+import com.oracle.svm.core.thread.VMThreads;
+import com.oracle.svm.core.util.TimeUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+public class JfrRecurringCallbackExecutionSampler extends AbstractJfrExecutionSampler implements ThreadListener {
+ private static final ExecutionSampleCallback CALLBACK = new ExecutionSampleCallback();
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ JfrRecurringCallbackExecutionSampler() {
+ }
+
+ @Override
+ protected void startSampling() {
+ assert VMOperation.isInProgressAtSafepoint();
+
+ SubstrateJVM.getSamplerBufferPool().adjustBufferCount();
+
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ install(thread, createRecurringCallbackTimer());
+ }
+ }
+
+ @Override
+ protected void updateInterval() {
+ assert VMOperation.isInProgressAtSafepoint();
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ uninstall(thread);
+ install(thread, createRecurringCallbackTimer());
+ }
+ }
+
+ @Override
+ protected void stopSampling() {
+ assert VMOperation.isInProgressAtSafepoint();
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ uninstall(thread);
+ }
+ }
+
+ private RecurringCallbackTimer createRecurringCallbackTimer() {
+ return ThreadingSupportImpl.createRecurringCallbackTimer(TimeUtils.millisToNanos(newIntervalMillis), CALLBACK);
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the recurring callbacks.")
+ private static void install(IsolateThread thread, RecurringCallbackTimer callbackTimer) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+
+ if (ExecutionSamplerInstallation.isAllowed(thread)) {
+ Threading.RecurringCallback currentCallback = ThreadingSupportImpl.getRecurringCallback(thread);
+ if (currentCallback == null) {
+ ExecutionSamplerInstallation.installed(thread);
+ ThreadingSupportImpl.setRecurringCallback(thread, callbackTimer);
+ }
+ }
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the recurring callbacks.")
+ private static void uninstall(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+
+ if (ExecutionSamplerInstallation.isInstalled(thread)) {
+ Threading.RecurringCallback currentCallback = ThreadingSupportImpl.getRecurringCallback(thread);
+ if (currentCallback == CALLBACK) {
+ ThreadingSupportImpl.removeRecurringCallback(thread);
+ }
+ ExecutionSamplerInstallation.uninstalled(thread);
+ }
+ }
+
+ @Override
+ public void beforeThreadRun() {
+ RecurringCallbackTimer callbackTimer = createRecurringCallbackTimer();
+ beforeThreadRun0(callbackTimer);
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the execution sampler or the recurring callbacks.")
+ private void beforeThreadRun0(RecurringCallbackTimer callbackTimer) {
+ if (isSampling()) {
+ SubstrateJVM.getSamplerBufferPool().adjustBufferCount();
+ install(CurrentIsolate.getCurrentThread(), callbackTimer);
+ }
+ }
+
+ @Override
+ @Uninterruptible(reason = "Prevent VM operations that modify the recurring callbacks.")
+ public void afterThreadRun() {
+ IsolateThread thread = CurrentIsolate.getCurrentThread();
+ uninstall(thread);
+ ExecutionSamplerInstallation.disallow(thread);
+ }
+
+ private static final class ExecutionSampleCallback implements Threading.RecurringCallback {
+ @Override
+ @NeverInline("Starting a stack walk in the caller frame")
+ @Uninterruptible(reason = "Avoid interference with the application.")
+ public void run(Threading.RecurringCallbackAccess access) {
+ Pointer sp = readCallerStackPointer();
+ CodePointer ip = readReturnAddress();
+ tryUninterruptibleStackWalk(ip, sp);
+ }
+ }
+}
+
+@AutomaticallyRegisteredFeature
+class JfrRecurringCallbackExecutionSamplerFeature implements InternalFeature {
+ @Override
+ public List> getRequiredFeatures() {
+ return Collections.singletonList(JfrFeature.class);
+ }
+
+ @Override
+ public void duringSetup(DuringSetupAccess access) {
+ if (JfrFeature.isExecutionSamplerSupported() && !ImageSingletons.contains(JfrExecutionSampler.class)) {
+ JfrRecurringCallbackExecutionSampler sampler = new JfrRecurringCallbackExecutionSampler();
+ ImageSingletons.add(JfrExecutionSampler.class, sampler);
+
+ ThreadListenerSupport.get().register(sampler);
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java
index c297f4446abe..9bbd4a564846 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java
@@ -30,7 +30,6 @@
import static org.graalvm.compiler.nodes.extended.BranchProbabilityNode.NOT_FREQUENT_PROBABILITY;
import static org.graalvm.compiler.nodes.extended.BranchProbabilityNode.probability;
-import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.IsolateThread;
import com.oracle.svm.core.Uninterruptible;
@@ -70,7 +69,7 @@ public void monitorEnter(Object obj) {
JavaMonitorEnterEvent.emit(obj, latestJfrTid, startTicks);
}
- latestJfrTid = SubstrateJVM.getThreadId(CurrentIsolate.getCurrentThread());
+ latestJfrTid = SubstrateJVM.getCurrentThreadId();
}
public void monitorExit() {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java
index ae224bebbcc8..e21372597fe8 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java
@@ -29,8 +29,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
-import org.graalvm.nativeimage.CurrentIsolate;
-
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.jfr.JfrTicks;
import com.oracle.svm.core.jfr.SubstrateJVM;
@@ -411,7 +409,7 @@ private void doSignal(ConditionNode first, boolean all) {
lastWaiter = null;
}
if ((first.getAndUnsetStatus(COND) & COND) != 0) {
- first.notifierJfrTid = SubstrateJVM.getThreadId(CurrentIsolate.getCurrentThread());
+ first.notifierJfrTid = SubstrateJVM.getCurrentThreadId();
enqueue(first);
if (!all) {
break;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/CallStackFrameMethodData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/CallStackFrameMethodData.java
index 2a812d959c8f..5d2449db126c 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/CallStackFrameMethodData.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/CallStackFrameMethodData.java
@@ -32,5 +32,6 @@
*/
public interface CallStackFrameMethodData {
+ /* Returns a unique id for the given method. The returned id is always greater than 0. */
int getMethodId(ResolvedJavaMethod method);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/CallStackFrameMethodInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/CallStackFrameMethodInfo.java
index 393dcb6e1d6a..c277bad2e313 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/CallStackFrameMethodInfo.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/CallStackFrameMethodInfo.java
@@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.Map;
+import com.oracle.svm.core.heap.UnknownPrimitiveField;
import com.oracle.svm.core.snippets.SnippetRuntime;
import com.oracle.svm.core.thread.Safepoint;
import jdk.vm.ci.meta.ResolvedJavaMethod;
@@ -36,8 +37,8 @@ public class CallStackFrameMethodInfo {
protected static final int INITIAL_METHOD_ID = -1;
private final Map sampledMethods = new HashMap<>();
- private int enterSafepointCheckId = INITIAL_METHOD_ID;
- private int enterSafepointFromNativeId = INITIAL_METHOD_ID;
+ @UnknownPrimitiveField private int enterSafepointCheckId = INITIAL_METHOD_ID;
+ @UnknownPrimitiveField private int enterSafepointFromNativeId = INITIAL_METHOD_ID;
public void addMethodInfo(ResolvedJavaMethod method, int methodId) {
String formattedMethod = formatted(method);
@@ -67,9 +68,4 @@ public String methodFor(int methodId) {
public boolean isSamplingCodeEntry(int methodId) {
return enterSafepointCheckId == methodId || enterSafepointFromNativeId == methodId;
}
-
- public void setEnterSamplingCodeMethodId(int i) {
- enterSafepointCheckId = i;
- enterSafepointFromNativeId = i;
- }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/ProfilingSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/ProfilingSampler.java
index 48ca6a0b83b0..b1a340602372 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/ProfilingSampler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/ProfilingSampler.java
@@ -27,10 +27,5 @@
import org.graalvm.collections.LockFreePrefixTree;
public interface ProfilingSampler {
-
- boolean isCollectingActive();
-
- void registerSampler();
-
LockFreePrefixTree prefixTree();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SafepointProfilingSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SafepointProfilingSampler.java
index f63a254bcbd0..d0e777088f95 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SafepointProfilingSampler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SafepointProfilingSampler.java
@@ -24,39 +24,31 @@
*/
package com.oracle.svm.core.sampler;
-import java.util.concurrent.TimeUnit;
-
+import com.oracle.svm.core.util.TimeUtils;
import org.graalvm.collections.LockFreePrefixTree;
-import org.graalvm.nativeimage.Threading;
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
import org.graalvm.word.Pointer;
-import com.oracle.svm.core.RuntimeAnalysisWorkarounds;
import com.oracle.svm.core.NeverInline;
import com.oracle.svm.core.snippets.KnownIntrinsics;
import com.oracle.svm.core.stack.JavaStackWalker;
+import com.oracle.svm.core.thread.ThreadListener;
+import com.oracle.svm.core.thread.ThreadingSupportImpl;
-public class SafepointProfilingSampler implements ProfilingSampler {
+public class SafepointProfilingSampler implements ProfilingSampler, ThreadListener {
- private final boolean collectingActive;
- private LockFreePrefixTree prefixTree = new LockFreePrefixTree();
+ private final LockFreePrefixTree prefixTree = new LockFreePrefixTree();
- public SafepointProfilingSampler(boolean collectingActive) {
- this.collectingActive = collectingActive;
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public SafepointProfilingSampler() {
}
@Override
- public boolean isCollectingActive() {
- return collectingActive;
- }
-
- @Override
- public void registerSampler() {
- if (collectingActive) {
- RuntimeAnalysisWorkarounds.avoidFoldingSamplingCodeStart();
- Threading.registerRecurringCallback(10, TimeUnit.MILLISECONDS, (access) -> {
- sampleThreadStack();
- });
- }
+ public void beforeThreadRun() {
+ ThreadingSupportImpl.RecurringCallbackTimer callback = ThreadingSupportImpl.createRecurringCallbackTimer(TimeUtils.millisToNanos(10), (access) -> sampleThreadStack());
+ ThreadingSupportImpl.setRecurringCallback(CurrentIsolate.getCurrentThread(), callback);
}
@Override
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java
index df1d7a150a7a..16d0a376491e 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java
@@ -50,18 +50,6 @@ public interface SamplerBuffer extends PointerBase {
@RawField
void setNext(SamplerBuffer buffer);
- /**
- * Returns the JFR id of the thread that owns this buffer.
- */
- @RawField
- long getOwner();
-
- /**
- * Sets the JFR id of the thread that owns this buffer.
- */
- @RawField
- void setOwner(long threadId);
-
/**
* Returns the current position. Any data before this position is valid sample data.
*/
@@ -85,16 +73,4 @@ public interface SamplerBuffer extends PointerBase {
*/
@RawField
void setSize(UnsignedWord value);
-
- /**
- * Should this buffer be freed after processing the data in it.
- */
- @RawField
- boolean getFreeable();
-
- /**
- * Sets the freeable status of the buffer.
- */
- @RawField
- void setFreeable(boolean freeable);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
index cce40c004189..fc7ce26e9e45 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
@@ -26,9 +26,7 @@
package com.oracle.svm.core.sampler;
import org.graalvm.compiler.api.replacements.Fold;
-import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.c.struct.SizeOf;
-import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
import org.graalvm.word.Pointer;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
@@ -50,28 +48,10 @@ public static UnsignedWord getHeaderSize() {
return UnsignedUtils.roundUp(SizeOf.unsigned(SamplerBuffer.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize));
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static SamplerBuffer allocate(UnsignedWord dataSize) {
- UnsignedWord headerSize = SamplerBufferAccess.getHeaderSize();
- SamplerBuffer result = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(headerSize.add(dataSize));
- if (result.isNonNull()) {
- result.setSize(dataSize);
- result.setFreeable(false);
- reinitialize(result);
- }
- return result;
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void free(SamplerBuffer buffer) {
- ImageSingletons.lookup(UnmanagedMemorySupport.class).free(buffer);
- }
-
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static void reinitialize(SamplerBuffer buffer) {
Pointer dataStart = getDataStart(buffer);
buffer.setPos(dataStart);
- buffer.setOwner(0L);
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java
index 45df346c78c9..831b8e6f2d28 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java
@@ -25,39 +25,89 @@
package com.oracle.svm.core.sampler;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
+import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean;
import com.oracle.svm.core.jfr.JfrThreadLocal;
import com.oracle.svm.core.jfr.SubstrateJVM;
+import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
import com.oracle.svm.core.locks.VMMutex;
-import com.oracle.svm.core.util.VMError;
/**
- * The pool that maintains the desirable number of buffers in the system by allocating/releasing
- * extra buffers.
+ * Keeps track of {@link #availableBuffers available} and {@link #fullBuffers full} buffers. If
+ * sampling is enabled, this pool maintains the desirable number of buffers in the system.
*/
-class SamplerBufferPool {
+public class SamplerBufferPool {
+ private final VMMutex mutex;
+ private final SamplerBufferStack availableBuffers;
+ private final SamplerBufferStack fullBuffers;
- private static final VMMutex mutex = new VMMutex("SamplerBufferPool");
- private static long bufferCount;
+ private int bufferCount;
- @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
- public static void releaseBufferAndAdjustCount(SamplerBuffer threadLocalBuffer) {
- adjustBufferCount0(threadLocalBuffer);
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public SamplerBufferPool() {
+ mutex = new VMMutex("SamplerBufferPool");
+ availableBuffers = new SamplerBufferStack();
+ fullBuffers = new SamplerBufferStack();
}
- @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
- public static void adjustBufferCount() {
- adjustBufferCount0(WordFactory.nullPointer());
+ public void teardown() {
+ clear(availableBuffers);
+ clear(fullBuffers);
+ assert bufferCount == 0;
}
- @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
- private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) {
+ private void clear(SamplerBufferStack stack) {
+ while (true) {
+ SamplerBuffer buffer = stack.popBuffer();
+ if (buffer.isNull()) {
+ break;
+ }
+ free(buffer);
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public boolean isLockedByCurrentThread() {
+ return availableBuffers.isLockedByCurrentThread() || fullBuffers.isLockedByCurrentThread();
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public SamplerBuffer acquireBuffer(boolean allowAllocation) {
+ SamplerBuffer buffer = availableBuffers.popBuffer();
+ if (buffer.isNull() && allowAllocation) {
+ buffer = SubstrateJVM.getSamplerBufferPool().tryAllocateBuffer();
+ }
+ return buffer;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void releaseBuffer(SamplerBuffer buffer) {
+ SamplerBufferAccess.reinitialize(buffer);
+ availableBuffers.pushBuffer(buffer);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public void pushFullBuffer(SamplerBuffer buffer) {
+ fullBuffers.pushBuffer(buffer);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public SamplerBuffer popFullBuffer() {
+ return fullBuffers.popBuffer();
+ }
+
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
+ public void adjustBufferCount() {
mutex.lockNoTransition();
try {
- releaseThreadLocalBuffer(threadLocalBuffer);
- long diff = diff();
+ int diff = diff();
if (diff > 0) {
for (int i = 0; i < diff; i++) {
if (!allocateAndPush()) {
@@ -65,7 +115,7 @@ private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) {
}
}
} else {
- for (long i = diff; i < 0; i++) {
+ for (int i = diff; i < 0; i++) {
if (!popAndFree()) {
break;
}
@@ -76,56 +126,68 @@ private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) {
}
}
- @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true)
- private static void releaseThreadLocalBuffer(SamplerBuffer buffer) {
- /*
- * buffer is null if the thread is not running yet, or we did not perform the stack walk for
- * this thread during the run.
- */
+ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
+ private SamplerBuffer tryAllocateBuffer() {
+ mutex.lockNoTransition();
+ try {
+ return tryAllocateBuffer0();
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private boolean allocateAndPush() {
+ assert bufferCount >= 0;
+ SamplerBuffer buffer = tryAllocateBuffer0();
if (buffer.isNonNull()) {
- if (SamplerBufferAccess.isEmpty(buffer)) {
- /* We can free it right away. */
- SamplerBufferAccess.free(buffer);
- } else {
- /* Put it in the stack with other unprocessed buffers. */
- buffer.setFreeable(true);
- SubstrateSigprofHandler.singleton().fullBuffers().pushBuffer(buffer);
- }
- VMError.guarantee(bufferCount > 0);
- bufferCount--;
+ availableBuffers.pushBuffer(buffer);
+ return true;
}
+ return false;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- private static boolean allocateAndPush() {
- VMError.guarantee(bufferCount >= 0);
+ private SamplerBuffer tryAllocateBuffer0() {
+ UnsignedWord headerSize = SamplerBufferAccess.getHeaderSize();
JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal();
- SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize()));
- if (buffer.isNonNull()) {
- SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer);
+ UnsignedWord dataSize = WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize());
+
+ SamplerBuffer result = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(headerSize.add(dataSize));
+ if (result.isNonNull()) {
bufferCount++;
- return true;
- } else {
- return false;
+ result.setSize(dataSize);
+ result.setNext(WordFactory.nullPointer());
+ SamplerBufferAccess.reinitialize(result);
}
+ return result;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- private static boolean popAndFree() {
- VMError.guarantee(bufferCount > 0);
- SamplerBuffer buffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer();
+ private boolean popAndFree() {
+ assert bufferCount > 0;
+ SamplerBuffer buffer = availableBuffers.popBuffer();
if (buffer.isNonNull()) {
- SamplerBufferAccess.free(buffer);
- bufferCount--;
+ free(buffer);
return true;
- } else {
- return false;
}
+ return false;
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- private static long diff() {
- double diffD = SubstrateSigprofHandler.singleton().substrateThreadMXBean().getThreadCount() * 1.5 - bufferCount;
- return (long) (diffD + 0.5);
+ private void free(SamplerBuffer buffer) {
+ ImageSingletons.lookup(UnmanagedMemorySupport.class).free(buffer);
+ bufferCount--;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private int diff() {
+ if (JfrExecutionSampler.singleton().isSampling()) {
+ /* Cache buffers for the sampler. */
+ double buffersToCache = ImageSingletons.lookup(SubstrateThreadMXBean.class).getThreadCount() * 1.5 + 0.5;
+ return ((int) buffersToCache) - bufferCount;
+ }
+ /* Don't cache any buffers. */
+ return -bufferCount;
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java
index 5889fe59eb95..06a84eb86ec7 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java
@@ -32,12 +32,10 @@
import com.oracle.svm.core.Uninterruptible;
/**
- * The linked-list implementation of the stack that holds a sequence of native memory buffers.
+ * Holds a sequence of native memory buffers.
*
- * The stack uses spin-lock to protect itself from races with competing pop operations (ABA
+ * The stack uses a spin-lock to protect itself from races with competing pop operations (ABA
* problem).
- *
- * @see SamplerSpinLock
*/
public class SamplerBufferStack {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
index b7282bd38bfa..9620f3ecc707 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
@@ -25,10 +25,9 @@
package com.oracle.svm.core.sampler;
-import static com.oracle.svm.core.jfr.JfrStackTraceRepository.JfrStackTraceTableEntryStatus.FAILED;
-import static com.oracle.svm.core.jfr.JfrStackTraceRepository.JfrStackTraceTableEntryStatus.SERIALIZED;
-import static com.oracle.svm.core.jfr.JfrStackTraceRepository.JfrStackTraceTableEntryStatus.SHOULD_SERIALIZE;
-
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.nativeimage.c.type.CIntPointer;
@@ -38,64 +37,105 @@
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.code.CodeInfoAccess;
-import com.oracle.svm.core.code.CodeInfoQueryResult;
+import com.oracle.svm.core.code.CodeInfoDecoder.FrameInfoCursor;
import com.oracle.svm.core.code.CodeInfoTable;
import com.oracle.svm.core.code.FrameInfoQueryResult;
import com.oracle.svm.core.code.UntetheredCodeInfo;
-import com.oracle.svm.core.jfr.HasJfrSupport;
-import com.oracle.svm.core.jfr.JfrStackTraceRepository;
+import com.oracle.svm.core.jfr.JfrBuffer;
+import com.oracle.svm.core.jfr.JfrFrameType;
+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.JfrStackTraceRepository.JfrStackTraceTableEntry;
+import com.oracle.svm.core.jfr.JfrStackTraceRepository.JfrStackTraceTableEntryStatus;
+import com.oracle.svm.core.jfr.JfrThreadLocal;
import com.oracle.svm.core.jfr.SubstrateJVM;
import com.oracle.svm.core.jfr.events.ExecutionSampleEvent;
+import com.oracle.svm.core.thread.VMOperation;
+import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.util.VMError;
-/**
- * Used to access the pool of {@link SamplerBuffer}s.
- */
public final class SamplerBuffersAccess {
+ /** This value is used by multiple threads but only by a single thread at a time. */
+ private static final FrameInfoCursor FRAME_INFO_CURSOR = new FrameInfoCursor();
+
+ @Platforms(Platform.HOSTED_ONLY.class)
private SamplerBuffersAccess() {
}
- @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
- public static void processSamplerBuffers() {
- if (!HasJfrSupport.get()) {
- /*
- * This method will become reachable on WINDOWS during the building of JFR tests via
- * com.oracle.svm.core.jfr.JfrChunkWriter.closeFile, and it will fail during
- * InvocationPlugin call if we do not have this check.
- *
- * Note that although we are building the JFR tests for Windows as well, they will not
- * be executed because of guard in com.oracle.svm.test.jfr.JfrTest.checkForJFR.
- *
- * Once we have support for Windows, this check will become obsolete.
- */
+ @Uninterruptible(reason = "Prevent JFR recording.")
+ public static void processActiveBuffers() {
+ assert VMOperation.isInProgressAtSafepoint();
+
+ JfrBuffer targetBuffer = SubstrateJVM.getStackTraceRepo().getCurrentBuffer();
+ if (targetBuffer.isNull()) {
+ /* Buffer allocation failed, so don't process any data. */
+ return;
+ }
+
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ SamplerBuffer b = JfrThreadLocal.getSamplerBuffer(thread);
+ if (b.isNonNull()) {
+ serializeStackTraces(b, targetBuffer);
+ assert JfrThreadLocal.getSamplerBuffer(thread) == b;
+ }
+ }
+ }
+
+ /**
+ * The raw instruction pointer stack traces are decoded to Java-level stack trace information,
+ * which is then serialized into a buffer. This method may be called by different threads:
+ *
+ * - The JFR recorder thread processes full buffers periodically.
+ * - When the JFR epoch changes, all buffers that belong to the current epoch need to be
+ * processed within the VM operation that changes the epoch.
+ *
+ */
+ @Uninterruptible(reason = "Prevent JFR recording and epoch change.")
+ public static void processFullBuffers(boolean useSafepointChecks) {
+ JfrBuffer targetBuffer = SubstrateJVM.getStackTraceRepo().getCurrentBuffer();
+ if (targetBuffer.isNull()) {
+ /* Buffer allocation failed, so don't process any data. */
return;
}
- SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(true);
while (true) {
- /* Pop top buffer from stack of full buffers. */
- SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer();
+ SamplerBuffer buffer = SubstrateJVM.getSamplerBufferPool().popFullBuffer();
if (buffer.isNull()) {
- /* No buffers to process. */
- SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false);
- return;
+ break;
}
- /* Process the buffer. */
- processSamplerBuffer(buffer);
- if (buffer.getFreeable()) {
- SamplerBufferAccess.free(buffer);
- } else {
- SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer);
+ serializeStackTraces(buffer, targetBuffer);
+ SubstrateJVM.getSamplerBufferPool().releaseBuffer(buffer);
+
+ /* Do a safepoint check if the caller requested one. */
+ if (useSafepointChecks) {
+ safepointCheck();
}
}
+
+ SubstrateJVM.getSamplerBufferPool().adjustBufferCount();
+ }
+
+ @Uninterruptible(reason = "The callee explicitly does a safepoint check.", calleeMustBe = false)
+ private static void safepointCheck() {
+ safepointCheck0();
}
- @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.")
- public static void processSamplerBuffer(SamplerBuffer buffer) {
- Pointer end = buffer.getPos();
- Pointer current = SamplerBufferAccess.getDataStart(buffer);
+ private static void safepointCheck0() {
+ }
+
+ @Uninterruptible(reason = "Prevent JFR recording and epoch change.")
+ private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer, JfrBuffer targetBuffer) {
+ assert rawStackTraceBuffer.isNonNull();
+ assert targetBuffer.isNonNull();
+
+ Pointer end = rawStackTraceBuffer.getPos();
+ Pointer current = SamplerBufferAccess.getDataStart(rawStackTraceBuffer);
while (current.belowThan(end)) {
+ Pointer entryStart = current;
+ assert entryStart.unsignedRemainder(Long.BYTES).equal(0);
+
/* Sample hash. */
int sampleHash = current.readInt(0);
current = current.add(Integer.BYTES);
@@ -104,64 +144,97 @@ public static void processSamplerBuffer(SamplerBuffer buffer) {
boolean isTruncated = current.readInt(0) == 1;
current = current.add(Integer.BYTES);
- /* Sample size. */
+ /* Sample size, excluding the header and the end marker. */
int sampleSize = current.readInt(0);
current = current.add(Integer.BYTES);
+ /* Padding. */
+ current = current.add(Integer.BYTES);
+
/* Tick. */
long sampleTick = current.readLong(0);
current = current.add(Long.BYTES);
+ /* Event thread. */
+ long threadId = current.readLong(0);
+ current = current.add(Long.BYTES);
+
/* Thread state. */
long threadState = current.readLong(0);
current = current.add(Long.BYTES);
- CIntPointer status = StackValue.get(CIntPointer.class);
- JfrStackTraceRepository stackTraceRepo = SubstrateJVM.getStackTraceRepo();
- stackTraceRepo.acquireLock();
- try {
- long stackTraceId = stackTraceRepo.getStackTraceId(current, current.add(sampleSize), sampleHash, status, true);
- boolean serialized = JfrStackTraceRepository.JfrStackTraceTableEntryStatus.get(status, SERIALIZED);
- boolean failed = JfrStackTraceRepository.JfrStackTraceTableEntryStatus.get(status, FAILED);
- if (serialized || failed) {
- /*
- * Sample/Stack is already there or there is not enough memory to operate, skip
- * the rest of the data.
- */
- current = current.add(sampleSize);
- long endMarker = current.readLong(0);
- if (endMarker == SamplerSampleWriter.SAMPLE_EVENT_DATA_END && serialized) {
- ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState);
- }
- current = current.add(SamplerSampleWriter.END_MARKER_SIZE);
- } else {
- assert JfrStackTraceRepository.JfrStackTraceTableEntryStatus.get(status, SHOULD_SERIALIZE);
- /* Sample is not there. Start walking a stacktrace. */
- stackTraceRepo.serializeStackTraceHeader(stackTraceId, isTruncated, sampleSize / SamplerSampleWriter.IP_SIZE);
- while (current.belowThan(end)) {
- long ip = current.readLong(0);
- /* Check if we hit any of the end markers. */
- if (ip == SamplerSampleWriter.JFR_STACK_TRACE_END || ip == SamplerSampleWriter.SAMPLE_EVENT_DATA_END) {
- if (ip == SamplerSampleWriter.SAMPLE_EVENT_DATA_END) {
- ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState);
- }
- current = current.add(SamplerSampleWriter.END_MARKER_SIZE);
- break;
- } else {
- visitFrame(ip);
- current = current.add(SamplerSampleWriter.IP_SIZE);
- }
- }
+ assert current.subtract(entryStart).equal(SamplerSampleWriter.getHeaderSize());
+
+ CIntPointer statusPtr = StackValue.get(CIntPointer.class);
+ JfrStackTraceTableEntry entry = SubstrateJVM.getStackTraceRepo().getOrPutStackTrace(current, WordFactory.unsigned(sampleSize), sampleHash, statusPtr);
+ long stackTraceId = entry.getId();
+
+ int status = statusPtr.read();
+ if (status == JfrStackTraceTableEntryStatus.INSERTED || status == JfrStackTraceTableEntryStatus.EXISTING_RAW) {
+ /* Walk the IPs and serialize the stacktrace. */
+ assert current.add(sampleSize).belowThan(end);
+ boolean success = serializeStackTrace(targetBuffer, current, sampleSize, isTruncated, stackTraceId);
+ if (success) {
+ SubstrateJVM.getStackTraceRepo().commitSerializedStackTrace(entry);
}
- } finally {
- stackTraceRepo.releaseLock();
+ } else {
+ /* Processing is not needed: skip the rest of the data. */
+ assert status == JfrStackTraceTableEntryStatus.EXISTING_SERIALIZED || status == JfrStackTraceTableEntryStatus.INSERT_FAILED;
+ }
+ current = current.add(sampleSize);
+
+ /*
+ * Emit an event depending on the end marker of the raw stack trace. This needs to be
+ * done here because the sampler can't emit the event directly.
+ */
+ long endMarker = current.readLong(0);
+ if (endMarker == SamplerSampleWriter.EXECUTION_SAMPLE_END && status != JfrStackTraceTableEntryStatus.INSERT_FAILED) {
+ ExecutionSampleEvent.writeExecutionSample(sampleTick, threadId, stackTraceId, threadState);
+ } else {
+ assert endMarker == SamplerSampleWriter.JFR_STACK_TRACE_END;
}
+ current = current.add(SamplerSampleWriter.END_MARKER_SIZE);
}
- SamplerBufferAccess.reinitialize(buffer);
+
+ SamplerBufferAccess.reinitialize(rawStackTraceBuffer);
+ }
+
+ @Uninterruptible(reason = "Prevent JFR recording and epoch change.")
+ private static boolean serializeStackTrace(JfrBuffer targetBuffer, Pointer rawStackTrace, int sampleSize, boolean isTruncated, long stackTraceId) {
+ assert sampleSize % Long.BYTES == 0;
+
+ /*
+ * One IP may correspond to multiple Java-level stack frames. We need to precompute the
+ * number of stack trace elements because the count can't be patched later on
+ * (JfrNativeEventWriter.putInt() would not necessarily reserve enough bytes).
+ */
+ int numStackTraceElements = visitRawStackTrace(rawStackTrace, sampleSize, WordFactory.nullPointer());
+
+ JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
+ JfrNativeEventWriterDataAccess.initialize(data, targetBuffer);
+ JfrNativeEventWriter.putLong(data, stackTraceId);
+ JfrNativeEventWriter.putBoolean(data, isTruncated);
+ JfrNativeEventWriter.putInt(data, numStackTraceElements);
+ visitRawStackTrace(rawStackTrace, sampleSize, data);
+ JfrNativeEventWriter.commit(data);
+ return true;
}
- @Uninterruptible(reason = "The handle should only be accessed from uninterruptible code to prevent that the GC frees the CodeInfo.", callerMustBe = true)
- private static void visitFrame(long address) {
+ @Uninterruptible(reason = "Prevent JFR recording and epoch change.")
+ private static int visitRawStackTrace(Pointer rawStackTrace, int sampleSize, JfrNativeEventWriterData data) {
+ int numStackTraceElements = 0;
+ Pointer rawStackTraceEnd = rawStackTrace.add(sampleSize);
+ Pointer ipPtr = rawStackTrace;
+ while (ipPtr.belowThan(rawStackTraceEnd)) {
+ long ip = ipPtr.readLong(0);
+ numStackTraceElements += visitFrame(data, ip);
+ ipPtr = ipPtr.add(Long.BYTES);
+ }
+ return numStackTraceElements;
+ }
+
+ @Uninterruptible(reason = "Prevent JFR recording, epoch change, and that the GC frees the CodeInfo.")
+ private static int visitFrame(JfrNativeEventWriterData data, long address) {
CodePointer ip = WordFactory.pointer(address);
UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip);
if (untetheredInfo.isNull()) {
@@ -172,22 +245,32 @@ private static void visitFrame(long address) {
Object tether = CodeInfoAccess.acquireTether(untetheredInfo);
CodeInfo tetheredCodeInfo = CodeInfoAccess.convert(untetheredInfo, tether);
try {
- visitFrameInterruptibly(tetheredCodeInfo, ip);
+ return visitFrame(data, tetheredCodeInfo, ip);
} finally {
CodeInfoAccess.releaseTether(untetheredInfo, tether);
}
}
- @Uninterruptible(reason = "CodeInfo is tethered, so safepoints are allowed.", calleeMustBe = false)
- private static void visitFrameInterruptibly(CodeInfo codeInfo, CodePointer ip) {
- CodeInfoQueryResult queryResult = CodeInfoTable.lookupCodeInfoQueryResult(codeInfo, ip);
- VMError.guarantee(queryResult != null);
- FrameInfoQueryResult frameInfoQueryResult = queryResult.getFrameInfo();
- if (frameInfoQueryResult != null) {
- SubstrateJVM.getStackTraceRepo().serializeStackTraceElement(frameInfoQueryResult);
- } else {
- /* We don't have information about native code. */
- SubstrateJVM.getStackTraceRepo().serializeUnknownStackTraceElement();
+ @Uninterruptible(reason = "Prevent JFR recording and epoch change.")
+ private static int visitFrame(JfrNativeEventWriterData data, CodeInfo codeInfo, CodePointer ip) {
+ int numStackTraceElements = 0;
+ FRAME_INFO_CURSOR.initialize(codeInfo, ip);
+ while (FRAME_INFO_CURSOR.advance()) {
+ if (data.isNonNull()) {
+ FrameInfoQueryResult frame = FRAME_INFO_CURSOR.get();
+ serializeStackTraceElement(data, frame);
+ }
+ numStackTraceElements++;
}
+ return numStackTraceElements;
+ }
+
+ @Uninterruptible(reason = "Prevent JFR recording and epoch change.")
+ private static void serializeStackTraceElement(JfrNativeEventWriterData data, FrameInfoQueryResult stackTraceElement) {
+ long methodId = SubstrateJVM.getMethodRepo().getMethodId(stackTraceElement.getSourceClass(), stackTraceElement.getSourceMethodName(), stackTraceElement.getMethodId());
+ JfrNativeEventWriter.putLong(data, methodId);
+ JfrNativeEventWriter.putInt(data, stackTraceElement.getSourceLineNumber());
+ JfrNativeEventWriter.putInt(data, stackTraceElement.getBci());
+ JfrNativeEventWriter.putLong(data, JfrFrameType.FRAME_AOT_COMPILED.getId());
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java
deleted file mode 100644
index 6b47198225f2..000000000000
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (c) 2022, 2022, 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. 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.sampler;
-
-import org.graalvm.compiler.nodes.NamedLocationIdentity;
-import org.graalvm.nativeimage.CurrentIsolate;
-import org.graalvm.nativeimage.Isolate;
-import org.graalvm.word.Pointer;
-import org.graalvm.word.UnsignedWord;
-import org.graalvm.word.WordFactory;
-
-import com.oracle.svm.core.IsolateListenerSupport;
-import com.oracle.svm.core.Isolates;
-import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.c.CGlobalData;
-import com.oracle.svm.core.c.CGlobalDataFactory;
-
-class SamplerIsolateLocal implements IsolateListenerSupport.IsolateListener {
-
- /** Stores the address of the first isolate created. */
- private static final CGlobalData firstIsolate = CGlobalDataFactory.createWord();
-
- /** Stores the isolate-specific key. */
- private static UnsignedWord key = WordFactory.zero();
-
- @Override
- @Uninterruptible(reason = "Thread state not yet set up.")
- public void afterCreateIsolate(Isolate isolate) {
- if (firstIsolate.get().logicCompareAndSwapWord(0, WordFactory.nullPointer(), Isolates.getHeapBase(isolate), NamedLocationIdentity.OFF_HEAP_LOCATION)) {
- key = SubstrateSigprofHandler.singleton().createThreadLocalKey();
- }
- }
-
- @Override
- @Uninterruptible(reason = "The isolate teardown is in progress.")
- public void onIsolateTeardown() {
- if (isKeySet()) {
- /* Invalidate the isolate-specific key. */
- UnsignedWord oldKey = key;
- key = WordFactory.zero();
-
- if (SubstrateSigprofHandler.singleton().isProfilingEnabled()) {
- /*
- * Manually disable sampling for the current thread (no other threads are
- * remaining).
- */
- SamplerThreadLocal.teardown(CurrentIsolate.getCurrentThread());
- }
-
- /* Now, it's safe to delete the isolate-specific key. */
- SubstrateSigprofHandler.singleton().deleteThreadLocalKey(oldKey);
- }
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static Isolate getIsolate() {
- return firstIsolate.get().readWord(0);
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static UnsignedWord getKey() {
- return key;
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static boolean isKeySet() {
- return key.aboveThan(0);
- }
-}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
index 019385dd28f5..ff16008c01fc 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
@@ -32,15 +32,14 @@
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.UnmanagedMemoryUtil;
+import com.oracle.svm.core.jfr.JfrThreadLocal;
import com.oracle.svm.core.jfr.JfrThreadState;
import com.oracle.svm.core.jfr.JfrTicks;
import com.oracle.svm.core.jfr.SubstrateJVM;
public final class SamplerSampleWriter {
-
public static final long JFR_STACK_TRACE_END = -1;
- public static final long SAMPLE_EVENT_DATA_END = -2;
- public static final int IP_SIZE = Long.BYTES;
+ public static final long EXECUTION_SAMPLE_END = -2;
public static final int END_MARKER_SIZE = Long.BYTES;
private SamplerSampleWriter() {
@@ -48,14 +47,14 @@ private SamplerSampleWriter() {
@Fold
public static int getHeaderSize() {
- /* sample hash + is truncated + sample size + tick + thread state. */
- return Integer.BYTES + Integer.BYTES + Integer.BYTES + Long.BYTES + Long.BYTES;
+ return 4 * Integer.BYTES + 3 * Long.BYTES;
}
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
public static void begin(SamplerSampleWriterData data) {
assert isValid(data);
assert getUncommittedSize(data).equal(0);
+ assert data.getCurrentPos().unsignedRemainder(Long.BYTES).equal(0);
/* Sample hash. (will be patched later) */
SamplerSampleWriter.putInt(data, 0);
@@ -63,10 +62,15 @@ public static void begin(SamplerSampleWriterData data) {
SamplerSampleWriter.putInt(data, 0);
/* Sample size. (will be patched later) */
SamplerSampleWriter.putInt(data, 0);
- /* Tick. */
+ /* Padding so that the long values below are aligned. */
+ SamplerSampleWriter.putInt(data, 0);
+
SamplerSampleWriter.putLong(data, JfrTicks.elapsedTicks());
- /* Thread state. */
+ SamplerSampleWriter.putLong(data, SubstrateJVM.getCurrentThreadId());
SamplerSampleWriter.putLong(data, JfrThreadState.getId(Thread.State.RUNNABLE));
+
+ assert getHeaderSize() % Long.BYTES == 0;
+ assert getUncommittedSize(data).equal(getHeaderSize());
}
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
@@ -82,16 +86,15 @@ public static void end(SamplerSampleWriterData data, long endMarker) {
*/
putUncheckedLong(data, endMarker);
+ /* Patch data at start of entry. */
Pointer currentPos = data.getCurrentPos();
data.setCurrentPos(data.getStartPos());
- /* Patch sample hash. */
putUncheckedInt(data, data.getHashCode());
- /* Patch is truncated. */
putUncheckedInt(data, data.getTruncated() ? 1 : 0);
- /* Patch sample size. */
putUncheckedInt(data, (int) sampleSize.rawValue());
data.setCurrentPos(currentPos);
+ assert getUncommittedSize(data).unsignedRemainder(Long.BYTES).equal(0);
commit(data);
}
@@ -166,25 +169,24 @@ private static boolean accommodate(SamplerSampleWriterData data, UnsignedWord un
* Sample is too big to fit into the size of one buffer i.e. we want to do
* accommodations while nothing is committed into buffer.
*/
- SamplerThreadLocal.increaseMissedSamples();
+ JfrThreadLocal.increaseMissedSamples();
return false;
}
- /* Pop first free buffer from the pool. */
- SamplerBuffer newBuffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer();
+ /* Try to get a buffer. */
+ SamplerBuffer newBuffer = SubstrateJVM.getSamplerBufferPool().acquireBuffer(data.getAllowBufferAllocation());
if (newBuffer.isNull()) {
- /* No available buffers on the pool. Fallback! */
- SamplerThreadLocal.increaseMissedSamples();
+ JfrThreadLocal.increaseMissedSamples();
return false;
}
- SamplerThreadLocal.setThreadLocalBuffer(newBuffer);
+ JfrThreadLocal.setSamplerBuffer(newBuffer);
/* Copy the uncommitted content of old buffer into new one. */
UnmanagedMemoryUtil.copy(data.getStartPos(), SamplerBufferAccess.getDataStart(newBuffer), uncommitted);
/* Put in the stack with other unprocessed buffers and send a signal to the JFR recorder. */
SamplerBuffer oldBuffer = data.getSamplerBuffer();
- SubstrateSigprofHandler.singleton().fullBuffers().pushBuffer(oldBuffer);
+ SubstrateJVM.getSamplerBufferPool().pushFullBuffer(oldBuffer);
SubstrateJVM.getRecorderThread().signal();
/* Reinitialize data structure. */
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java
index 625bd9ef4ddc..5c072824251d 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java
@@ -144,4 +144,16 @@ public interface SamplerSampleWriterData extends PointerBase {
*/
@RawField
void setTruncated(boolean value);
+
+ /**
+ * Returns {@code true} if it is allowed to allocate new buffers and {@code false} otherwise.
+ */
+ @RawField
+ boolean getAllowBufferAllocation();
+
+ /**
+ * Determines if allocating new buffers is allowed.
+ */
+ @RawField
+ void setAllowBufferAllocation(boolean allowBufferAllocation);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
index 495773aa96ac..6669e9c24d14 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
@@ -26,6 +26,8 @@
package com.oracle.svm.core.sampler;
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.jfr.JfrThreadLocal;
+import com.oracle.svm.core.jfr.SubstrateJVM;
/**
* Helper class that holds methods related to {@link SamplerSampleWriterData}.
@@ -40,19 +42,18 @@ private SamplerSampleWriterDataAccess() {
* native buffer.
*/
@Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true)
- public static boolean initialize(SamplerSampleWriterData data, int skipCount, int maxDepth) {
- SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer();
+ public static boolean initialize(SamplerSampleWriterData data, int skipCount, boolean allowBufferAllocation) {
+ SamplerBuffer buffer = JfrThreadLocal.getSamplerBuffer();
if (buffer.isNull()) {
- /* Pop first free buffer from the pool. */
- buffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer();
+ buffer = SubstrateJVM.getSamplerBufferPool().acquireBuffer(allowBufferAllocation);
if (buffer.isNull()) {
- /* No available buffers on the pool. Fallback! */
- SamplerThreadLocal.increaseMissedSamples();
+ /* No buffer available. */
+ JfrThreadLocal.increaseMissedSamples();
return false;
}
- SamplerThreadLocal.setThreadLocalBuffer(buffer);
+ JfrThreadLocal.setSamplerBuffer(buffer);
}
- initialize(data, buffer, skipCount, maxDepth);
+ initialize0(data, buffer, skipCount, SubstrateJVM.getStackTraceRepo().getStackTraceDepth(), allowBufferAllocation);
return true;
}
@@ -60,22 +61,18 @@ public static boolean initialize(SamplerSampleWriterData data, int skipCount, in
* Initialize the {@link SamplerSampleWriterData data} so that it uses the given buffer.
*/
@Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true)
- public static void initialize(SamplerSampleWriterData data, SamplerBuffer buffer, int skipCount, int maxDepth) {
+ private static void initialize0(SamplerSampleWriterData data, SamplerBuffer buffer, int skipCount, int maxDepth, boolean allowBufferAllocation) {
assert buffer.isNonNull();
- /* Initialize the writer data. */
data.setSamplerBuffer(buffer);
data.setStartPos(buffer.getPos());
data.setCurrentPos(buffer.getPos());
data.setEndPos(SamplerBufferAccess.getDataEnd(buffer));
-
data.setHashCode(1);
data.setMaxDepth(maxDepth);
data.setTruncated(false);
data.setSkipCount(skipCount);
data.setNumFrames(0);
-
- /* Set the writer data as thread local. */
- SamplerThreadLocal.setWriterData(data);
+ data.setAllowBufferAllocation(allowBufferAllocation);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java
index a0359b57532e..2f6e2e0210c8 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java
@@ -25,19 +25,29 @@
package com.oracle.svm.core.sampler;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.word.Pointer;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.deopt.DeoptimizedFrame;
+import com.oracle.svm.core.jfr.JfrThreadLocal;
import com.oracle.svm.core.stack.ParameterizedStackFrameVisitor;
-final class SamplerStackWalkVisitor extends ParameterizedStackFrameVisitor {
+/* Uninterruptible visitor that holds all its state in a thread-local because it is used concurrently by multiple threads. */
+public final class SamplerStackWalkVisitor extends ParameterizedStackFrameVisitor {
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public SamplerStackWalkVisitor() {
+ }
+
@Override
@Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame, Object data) {
- SamplerSampleWriterData writerData = SamplerThreadLocal.getWriterData();
+ SamplerSampleWriterData writerData = JfrThreadLocal.getSamplerWriterData();
+ assert writerData.isNonNull();
+
boolean shouldSkipFrame = shouldSkipFrame(writerData);
boolean shouldContinueWalk = shouldContinueWalk(writerData);
if (!shouldSkipFrame && shouldContinueWalk) {
@@ -73,7 +83,7 @@ private static int computeHash(int oldHash, long ip) {
@Override
@Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
protected boolean unknownFrame(Pointer sp, CodePointer ip, DeoptimizedFrame deoptimizedFrame, Object data) {
- SamplerThreadLocal.increaseUnparseableStacks();
+ JfrThreadLocal.increaseUnparseableStacks();
return false;
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java
deleted file mode 100644
index 1819cabab3b2..000000000000
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (c) 2022, 2022, 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. 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.sampler;
-
-import org.graalvm.nativeimage.CurrentIsolate;
-import org.graalvm.nativeimage.IsolateThread;
-import org.graalvm.word.UnsignedWord;
-import org.graalvm.word.WordFactory;
-
-import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.jfr.SubstrateJVM;
-import com.oracle.svm.core.thread.ThreadListener;
-import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
-import com.oracle.svm.core.threadlocal.FastThreadLocalInt;
-import com.oracle.svm.core.threadlocal.FastThreadLocalLong;
-import com.oracle.svm.core.threadlocal.FastThreadLocalWord;
-
-public class SamplerThreadLocal implements ThreadListener {
-
- private static final FastThreadLocalWord localBuffer = FastThreadLocalFactory.createWord("SamplerThreadLocal.localBuffer");
- private static final FastThreadLocalLong missedSamples = FastThreadLocalFactory.createLong("SamplerThreadLocal.missedSamples");
- private static final FastThreadLocalLong unparseableStacks = FastThreadLocalFactory.createLong("SamplerThreadLocal.unparseableStacks");
- private static final FastThreadLocalInt isSignalHandlerLocallyDisabled = FastThreadLocalFactory.createInt("SamplerThreadLocal.isSignalHandlerLocallyDisabled");
- /**
- * The data that we are using during the stack walk, allocated on the stack.
- */
- private static final FastThreadLocalWord writerData = FastThreadLocalFactory.createWord("SamplerThreadLocal.writerData");
-
- @Override
- @Uninterruptible(reason = "Only uninterruptible code may be executed before Thread.run.")
- public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) {
- if (SubstrateSigprofHandler.singleton().isProfilingEnabled()) {
- initialize(isolateThread);
- }
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- static void initialize(IsolateThread isolateThread) {
- if (SamplerIsolateLocal.isKeySet()) {
- /* Adjust the number of buffers. */
- SamplerBufferPool.adjustBufferCount();
-
- /*
- * Save isolate thread in thread-local area.
- *
- * Once this value is set, the signal handler may interrupt this thread at any time. So,
- * it is essential that this value is set at the very end of this method.
- */
- UnsignedWord key = SamplerIsolateLocal.getKey();
- SubstrateSigprofHandler.singleton().setThreadLocalKeyValue(key, isolateThread);
- }
- }
-
- @Override
- @Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.")
- public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) {
- if (SubstrateSigprofHandler.singleton().isProfilingEnabled() && SamplerIsolateLocal.isKeySet()) {
- teardown(isolateThread);
- }
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- static void teardown(IsolateThread isolateThread) {
- /*
- * Invalidate thread-local area.
- *
- * Once this value is set to null, the signal handler can't interrupt this thread anymore.
- * So, it is essential that this value is set at the very beginning of this method i.e.
- * before doing cleanup.
- */
- UnsignedWord key = SamplerIsolateLocal.getKey();
- SubstrateSigprofHandler.singleton().setThreadLocalKeyValue(key, WordFactory.nullPointer());
-
- /* Adjust the number of buffers (including the thread-local buffer). */
- SamplerBuffer threadLocalBuffer = localBuffer.get(isolateThread);
- SamplerBufferPool.releaseBufferAndAdjustCount(threadLocalBuffer);
- localBuffer.set(isolateThread, WordFactory.nullPointer());
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static SamplerBuffer getThreadLocalBuffer() {
- return localBuffer.get();
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void setThreadLocalBuffer(SamplerBuffer buffer) {
- buffer.setOwner(SubstrateJVM.getThreadId(CurrentIsolate.getCurrentThread()));
- localBuffer.set(buffer);
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void increaseMissedSamples() {
- missedSamples.set(getMissedSamples() + 1);
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static long getMissedSamples() {
- return missedSamples.get();
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void increaseUnparseableStacks() {
- unparseableStacks.set(getUnparseableStacks() + 1);
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static long getUnparseableStacks() {
- return unparseableStacks.get();
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void setSignalHandlerLocallyDisabled(boolean isDisabled) {
- isSignalHandlerLocallyDisabled.set(isDisabled ? 1 : 0);
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static boolean isSignalHandlerLocallyDisabled() {
- return isSignalHandlerLocallyDisabled.get() == 1;
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static void setWriterData(SamplerSampleWriterData data) {
- writerData.set(data);
- }
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public static SamplerSampleWriterData getWriterData() {
- return writerData.get();
- }
-}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
index a46afbbb5452..be6102924765 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
@@ -24,183 +24,47 @@
*/
package com.oracle.svm.core.sampler;
-import java.lang.management.ManagementFactory;
-import java.util.Arrays;
-import java.util.List;
-
-import org.graalvm.collections.EconomicMap;
import org.graalvm.compiler.api.replacements.Fold;
-import org.graalvm.compiler.options.Option;
-import org.graalvm.compiler.options.OptionKey;
+import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Isolate;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
-import org.graalvm.nativeimage.StackValue;
-import org.graalvm.nativeimage.c.function.CodePointer;
-import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.word.Pointer;
import org.graalvm.word.UnsignedWord;
+import org.graalvm.word.WordFactory;
-import com.oracle.svm.core.IsolateListenerSupport;
-import com.oracle.svm.core.RegisterDumper;
+import com.oracle.svm.core.IsolateListenerSupport.IsolateListener;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.code.CodeInfo;
-import com.oracle.svm.core.code.CodeInfoAccess;
-import com.oracle.svm.core.code.CodeInfoTable;
-import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
-import com.oracle.svm.core.feature.InternalFeature;
+import com.oracle.svm.core.c.CGlobalData;
+import com.oracle.svm.core.c.CGlobalDataFactory;
import com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode;
import com.oracle.svm.core.graal.nodes.WriteHeapBaseNode;
-import com.oracle.svm.core.heap.VMOperationInfos;
-import com.oracle.svm.core.jdk.RuntimeSupport;
-import com.oracle.svm.core.jdk.management.ManagementFeature;
-import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean;
-import com.oracle.svm.core.jfr.HasJfrSupport;
-import com.oracle.svm.core.jfr.JfrFeature;
-import com.oracle.svm.core.jfr.JfrManager;
-import com.oracle.svm.core.jfr.JfrRecorderThread;
-import com.oracle.svm.core.option.RuntimeOptionKey;
-import com.oracle.svm.core.stack.JavaFrameAnchor;
-import com.oracle.svm.core.stack.JavaFrameAnchors;
-import com.oracle.svm.core.stack.JavaStackWalker;
-import com.oracle.svm.core.thread.JavaVMOperation;
-import com.oracle.svm.core.thread.ThreadListenerSupport;
-import com.oracle.svm.core.thread.ThreadListenerSupportFeature;
+import com.oracle.svm.core.jfr.SubstrateJVM;
+import com.oracle.svm.core.jfr.sampler.AbstractJfrExecutionSampler;
+import com.oracle.svm.core.thread.ThreadListener;
+import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMThreads;
-import com.oracle.svm.core.util.VMError;
-
-@AutomaticallyRegisteredFeature
-@SuppressWarnings("unused")
-class SubstrateSigprofHandlerFeature implements InternalFeature {
-
- @Override
- public boolean isInConfiguration(IsInConfigurationAccess access) {
- return JfrFeature.isInConfiguration(true);
- }
-
- @Override
- public List> getRequiredFeatures() {
- return Arrays.asList(ThreadListenerSupportFeature.class, JfrFeature.class, ManagementFeature.class);
- }
-
- @Override
- public void beforeAnalysis(BeforeAnalysisAccess access) {
- if (!SamplerHasSupport.get() && !HasJfrSupport.get()) {
- /* No Sampler and JFR support. */
- return;
- }
-
- /* The common initialization part between Sampler and JFR. */
-
- /* Create stack visitor. */
- ImageSingletons.add(SamplerStackWalkVisitor.class, new SamplerStackWalkVisitor());
-
- /* Add thread listener. */
- ThreadListenerSupport.get().register(new SamplerThreadLocal());
-
- /* Add startup hook. */
- RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSigprofHandlerStartupHook());
-
- /* The Sampler initialization part. */
- if (SamplerHasSupport.get()) {
- VMError.guarantee(ImageSingletons.contains(RegisterDumper.class));
-
- /* Add isolate listener. */
- IsolateListenerSupport.singleton().register(new SamplerIsolateLocal());
- }
- }
-}
-
-final class SubstrateSigprofHandlerStartupHook implements RuntimeSupport.Hook {
- @Override
- public void execute(boolean isFirstIsolate) {
- if (isFirstIsolate) {
- SubstrateSigprofHandler.singleton().install();
- }
- }
-}
/**
- *
- * The core class of low overhead asynchronous sampling based profiler.
- * {@link SubstrateSigprofHandler} handles the periodic signal generated by the OS with a given
- * time-frequency. The asynchronous nature of the signal means that the OS could invoke the signal
- * handler at any time (could be during GC, VM operation, uninterruptible code) and that the signal
- * handler can only execute specific code i.e. the calls that are asynchronous signal safe.
- *
- *
- *
- * The signal handler is divided into three part: restore isolate, isolate-thread, stack and
- * instruction pointers, prepare everything necessary for stack walk, do a stack walk and write IPs
- * into buffer.
- *
- *
- *
- * The signal handler is as a producer. On the other side of relation is
- * {@link JfrRecorderThread} that is consumer. The {@link SamplerBuffer} that we are using in
- * this consumer-producer communication is allocated eagerly, in a part of the heap that is not
- * accessible via GC, and there will always be more available buffers that threads.
- *
- *
- *
- * The communication between consumer and producer goes as follows:
- *
- * - Signal handler (producer): pops the buffer from the pool of available buffers (the buffer now
- * becomes thread-local), writes the IPs into buffer, if the buffer is full and moves it to a pool
- * with buffers that awaits processing.
- * - Recorder thread (consumer): pops the buffer from the pool of full buffers, reconstructs the
- * stack walk based on IPs and pushes the buffer into pool of available buffers.
- *
- * NOTE: The producer and the consumer are always accessing different buffers.
- *
- *
- *
- * In some rare cases, the profiling is impossible e.g. no available buffers in the pool, unknown IP
- * during stack walk, the thread holds the pool's lock when the signal arrives, etc.
- *
+ * This is the core class of the low overhead asynchronous execution sampler. It registers a SIGPROF
+ * signal handler that is then triggered by the OS periodically. The asynchronous nature of the
+ * signal means that the OS could invoke the signal handler at any time (when in native code, during
+ * a GC, during a VM operation, or while executing uninterruptible code). Therefore, we need to be
+ * very careful about the code that the signal handler executes, e.g., all called native methods
+ * need to be async-signal-safe.
*
- * @see SamplerSpinLock
- * @see SamplerBufferStack
+ * The signal handler calls Native Image code to restore reserved registers such as the heap base
+ * and the isolate-thread, before preparing everything that is needed for a stack walk.
*/
-public abstract class SubstrateSigprofHandler {
-
- public static class Options {
- @Option(help = "Allow sampling-based profiling. Default: disabled in execution.")//
- static final RuntimeOptionKey SamplingBasedProfiling = new RuntimeOptionKey<>(Boolean.FALSE) {
- @Override
- protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) {
- if (newValue) {
- /* Enabling sampling-based profiling requires to enabled JFR as well. */
- SubstrateOptions.FlightRecorder.update(values, true);
- }
- }
- };
-
- @SuppressWarnings("unused") @Option(help = "Start sampling-based profiling with options.")//
- public static final RuntimeOptionKey StartSamplingBasedProfiling = new RuntimeOptionKey<>("") {
- @Override
- protected void onValueUpdate(EconomicMap, Object> values, String oldValue, String newValue) {
- if (!newValue.isEmpty()) {
- /* Starting sampling-based profiling requires to start JFR as well. */
- SubstrateOptions.StartFlightRecording.update(values, newValue);
- }
- }
- };
- }
-
- private boolean enabled;
- private volatile boolean isSignalHandlerGloballyDisabled;
- private final SamplerBufferStack availableBuffers;
- private final SamplerBufferStack fullBuffers;
- private SubstrateThreadMXBean threadMXBean;
+public abstract class SubstrateSigprofHandler extends AbstractJfrExecutionSampler implements IsolateListener, ThreadListener {
+ private static final CGlobalData signalHandlerIsolate = CGlobalDataFactory.createWord();
+ private UnsignedWord keyForNativeThreadLocal;
@Platforms(Platform.HOSTED_ONLY.class)
protected SubstrateSigprofHandler() {
- this.availableBuffers = new SamplerBufferStack();
- this.fullBuffers = new SamplerBufferStack();
}
@Fold
@@ -209,151 +73,132 @@ public static SubstrateSigprofHandler singleton() {
}
@Fold
- public static SamplerStackWalkVisitor visitor() {
- return ImageSingletons.lookup(SamplerStackWalkVisitor.class);
+ public static boolean isSupported() {
+ return ImageSingletons.contains(SubstrateSigprofHandler.class);
}
- private static boolean isOSSupported() {
- return Platform.includedIn(Platform.LINUX.class);
+ @Override
+ @Uninterruptible(reason = "Thread state not set up yet.")
+ public void afterCreateIsolate(Isolate isolate) {
+ keyForNativeThreadLocal = createNativeThreadLocal();
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- boolean isProfilingEnabled() {
- return enabled;
+ @Override
+ @Uninterruptible(reason = "The isolate teardown is in progress.")
+ public void onIsolateTeardown() {
+ UnsignedWord oldKey = keyForNativeThreadLocal;
+ keyForNativeThreadLocal = WordFactory.nullPointer();
+ deleteNativeThreadLocal(oldKey);
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- private boolean isSignalHandlerDisabled() {
- return isSignalHandlerGloballyDisabled || SamplerThreadLocal.isSignalHandlerLocallyDisabled();
- }
+ @Override
+ protected void startSampling() {
+ assert VMOperation.isInProgressAtSafepoint();
+ assert getSignalHandlerIsolate().isNull();
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public void setSignalHandlerGloballyDisabled(boolean isDisabled) {
- isSignalHandlerGloballyDisabled = isDisabled;
+ SubstrateJVM.getSamplerBufferPool().adjustBufferCount();
+
+ setSignalHandlerIsolate(CurrentIsolate.getIsolate());
+ installSignalHandler();
+
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ install(thread);
+ }
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public SamplerBufferStack availableBuffers() {
- return availableBuffers;
+ @Override
+ protected abstract void updateInterval();
+
+ @Override
+ protected void stopSampling() {
+ assert VMOperation.isInProgressAtSafepoint();
+ assert getSignalHandlerIsolate().isNonNull();
+
+ /* Uninstall the SIGPROF handler so that it won't be triggered anymore. */
+ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
+ uninstall(thread);
+ }
+ uninstallSignalHandler();
+
+ /* Wait until all threads exited the signal handler and cleanup no longer needed data. */
+ disallowThreadsInSamplerCode();
+ try {
+ setSignalHandlerIsolate(WordFactory.nullPointer());
+ } finally {
+ allowThreadsInSamplerCode();
+ }
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- public SamplerBufferStack fullBuffers() {
- return fullBuffers;
+ private static Isolate getSignalHandlerIsolate() {
+ return signalHandlerIsolate.get().readWord(0);
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- SubstrateThreadMXBean substrateThreadMXBean() {
- return threadMXBean;
+ private static void setSignalHandlerIsolate(Isolate isolate) {
+ assert getSignalHandlerIsolate().isNull() || isolate.isNull();
+ signalHandlerIsolate.get().writeWord(0, isolate);
}
- /**
- * Installs the platform dependent sigprof handler.
- */
- void install() {
- if (JfrManager.isJFREnabled()) {
- threadMXBean = (SubstrateThreadMXBean) ManagementFactory.getThreadMXBean();
- /* Call VM operation to initialize the sampler and the threads. */
- InitializeSamplerOperation initializeSamplerOperation = new InitializeSamplerOperation();
- initializeSamplerOperation.enqueue();
-
- if (Options.SamplingBasedProfiling.getValue()) {
- if (isOSSupported()) {
- /* After the VM operations finishes. Install handler and start profiling. */
- install0();
- } else {
- VMError.shouldNotReachHere("Sampling-based profiling is currently supported only on LINUX!");
- }
- }
+ @Override
+ @Uninterruptible(reason = "Prevent VM operations that modify the global or thread-local execution sampler state.")
+ public void beforeThreadRun() {
+ IsolateThread thread = CurrentIsolate.getCurrentThread();
+ if (isSampling()) {
+ SubstrateJVM.getSamplerBufferPool().adjustBufferCount();
+ install(thread);
}
+ storeIsolateThreadInNativeThreadLocal(thread);
}
- protected abstract void install0();
-
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected abstract UnsignedWord createThreadLocalKey();
+ @Override
+ @Uninterruptible(reason = "Prevent VM operations that modify thread-local execution sampler state.")
+ public void afterThreadRun() {
+ IsolateThread thread = CurrentIsolate.getCurrentThread();
+ uninstall(thread);
+ ExecutionSamplerInstallation.disallow(thread);
+ }
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected abstract void deleteThreadLocalKey(UnsignedWord key);
+ protected abstract void installSignalHandler();
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected abstract void setThreadLocalKeyValue(UnsignedWord key, IsolateThread value);
+ protected abstract void uninstallSignalHandler();
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected abstract IsolateThread getThreadLocalKeyValue(UnsignedWord key);
+ @Uninterruptible(reason = "Prevent VM operations that modify thread-local execution sampler state.")
+ private static void install(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- private static boolean isIPInJavaCode(RegisterDumper.Context uContext) {
- Pointer ip = (Pointer) RegisterDumper.singleton().getIP(uContext);
- CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo();
- Pointer codeStart = (Pointer) CodeInfoAccess.getCodeStart(codeInfo);
- UnsignedWord codeSize = CodeInfoAccess.getCodeSize(codeInfo);
- return ip.aboveOrEqual(codeStart) && ip.belowOrEqual(codeStart.add(codeSize));
+ if (ExecutionSamplerInstallation.isAllowed(thread)) {
+ ExecutionSamplerInstallation.installed(thread);
+ }
}
- @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
- protected static void doUninterruptibleStackWalk(RegisterDumper.Context uContext) {
- CodePointer ip;
- Pointer sp;
- if (isIPInJavaCode(uContext)) {
- ip = (CodePointer) RegisterDumper.singleton().getIP(uContext);
- sp = (Pointer) RegisterDumper.singleton().getSP(uContext);
- } else {
- JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor();
- if (anchor.isNull()) {
- /*
- * The anchor is still null if the function is interrupted during prologue. See:
- * com.oracle.svm.core.graal.snippets.CFunctionSnippets.prologueSnippet
- */
- return;
- }
+ @Uninterruptible(reason = "Prevent VM operations that modify thread-local execution sampler state.")
+ private static void uninstall(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
- ip = anchor.getLastJavaIP();
- sp = anchor.getLastJavaSP();
- if (ip.isNull() || sp.isNull()) {
- /*
- * It can happen that anchor is in list of all anchors, but its IP and SP are not
- * filled yet.
- */
- return;
- }
+ if (ExecutionSamplerInstallation.isInstalled(thread)) {
+ ExecutionSamplerInstallation.uninstalled(thread);
}
+ }
- /* Test if the current thread's signal handler is disabled, or if holds the stack's lock. */
- if (singleton().isSignalHandlerDisabled() || singleton().availableBuffers().isLockedByCurrentThread() || singleton().fullBuffers().isLockedByCurrentThread()) {
- /*
- * The current thread already holds the stack's lock, so we can't access it. It's way
- * better to lose one sample, then potentially the whole buffer.
- *
- * In case of disabled signal handler, if we proceed forward it could pollute the JFR
- * output.
- */
- SamplerThreadLocal.increaseMissedSamples();
- return;
- }
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract UnsignedWord createNativeThreadLocal();
- /* Initialize stack walk. */
- SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class);
- /* Buffer size constrains stack walk size. */
- if (SamplerSampleWriterDataAccess.initialize(data, 0, Integer.MAX_VALUE)) {
- SamplerSampleWriter.begin(data);
- /*
- * Walk the stack.
- *
- * We should commit the sample if: the stack walk was done successfully or the stack
- * walk was interrupted because stack size exceeded given depth.
- */
- if (JavaStackWalker.walkCurrentThread(sp, ip, visitor()) || data.getTruncated()) {
- SamplerSampleWriter.end(data, SamplerSampleWriter.SAMPLE_EVENT_DATA_END);
- }
- }
- }
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract void deleteNativeThreadLocal(UnsignedWord key);
- /** Called from the platform dependent sigprof handler to enter isolate. */
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract void setNativeThreadLocal(UnsignedWord key, IsolateThread value);
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract IsolateThread getNativeThreadLocal(UnsignedWord key);
+
+ /**
+ * Called from the platform dependent sigprof handler to enter isolate.
+ */
@Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
protected static boolean tryEnterIsolate() {
if (SubstrateOptions.SpawnIsolates.getValue()) {
- Isolate isolate = SamplerIsolateLocal.getIsolate();
+ Isolate isolate = getSignalHandlerIsolate();
if (isolate.isNull()) {
/* It may happen that the initial isolate exited. */
return false;
@@ -365,12 +210,8 @@ protected static boolean tryEnterIsolate() {
/* We are keeping reference to isolate thread inside OS thread local area. */
if (SubstrateOptions.MultiThreaded.getValue()) {
- if (!SamplerIsolateLocal.isKeySet()) {
- /* The key becomes invalid during initial isolate teardown. */
- return false;
- }
- UnsignedWord key = SamplerIsolateLocal.getKey();
- IsolateThread thread = singleton().getThreadLocalKeyValue(key);
+ UnsignedWord key = singleton().keyForNativeThreadLocal;
+ IsolateThread thread = singleton().getNativeThreadLocal(key);
if (thread.isNull()) {
/* Thread is not yet initialized or already detached from isolate. */
return false;
@@ -382,30 +223,8 @@ protected static boolean tryEnterIsolate() {
return true;
}
- private class InitializeSamplerOperation extends JavaVMOperation {
-
- protected InitializeSamplerOperation() {
- super(VMOperationInfos.get(InitializeSamplerOperation.class, "Initialize Sampler", SystemEffect.SAFEPOINT));
- }
-
- @Override
- protected void operate() {
- initialize();
- }
-
- /**
- * We need to ensure that all threads are properly initialized at a moment when we start a
- * profiling.
- */
- @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.")
- private void initialize() {
- /*
- * Iterate all over all thread and initialize the thread-local storage of each thread.
- */
- for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
- SamplerThreadLocal.initialize(thread);
- }
- enabled = true;
- }
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ private void storeIsolateThreadInNativeThreadLocal(IsolateThread isolateThread) {
+ setNativeThreadLocal(keyForNativeThreadLocal, isolateThread);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java
index ad679f22048d..faef0e1498cb 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java
@@ -24,6 +24,7 @@
*/
package com.oracle.svm.core.stack;
+import com.oracle.svm.core.code.CodeInfoDecoder;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.word.Pointer;
@@ -32,13 +33,9 @@
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.code.CodeInfoAccess;
-import com.oracle.svm.core.code.CodeInfoAccess.DummyValueInfoAllocator;
-import com.oracle.svm.core.code.CodeInfoAccess.FrameInfoState;
-import com.oracle.svm.core.code.CodeInfoAccess.SingleShotFrameInfoQueryResultAllocator;
import com.oracle.svm.core.code.CodeInfoTable;
import com.oracle.svm.core.code.FrameInfoQueryResult;
import com.oracle.svm.core.code.ImageCodeInfo;
-import com.oracle.svm.core.code.ReusableTypeReader;
import com.oracle.svm.core.deopt.DeoptimizationSupport;
import com.oracle.svm.core.deopt.DeoptimizedFrame;
import com.oracle.svm.core.heap.RestrictHeapAccess;
@@ -48,10 +45,7 @@ public class ThreadStackPrinter {
private static final int MAX_STACK_FRAMES_PER_THREAD_TO_PRINT = 100_000;
public static class StackFramePrintVisitor extends Stage1StackFramePrintVisitor {
- private static final ReusableTypeReader frameInfoReader = new ReusableTypeReader();
- private static final SingleShotFrameInfoQueryResultAllocator singleShotFrameInfoQueryResultAllocator = new SingleShotFrameInfoQueryResultAllocator();
- private static final DummyValueInfoAllocator dummyValueInfoAllocator = new DummyValueInfoAllocator();
- private static final FrameInfoState frameInfoState = new FrameInfoState();
+ private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor();
public StackFramePrintVisitor() {
}
@@ -60,28 +54,31 @@ public StackFramePrintVisitor() {
protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) {
if (deoptFrame != null) {
logVirtualFrames(log, sp, ip, deoptFrame);
- } else {
- CodeInfoAccess.initFrameInfoReader(codeInfo, ip, frameInfoReader.reset(), frameInfoState.reset());
- if (frameInfoState.entryOffset >= 0) {
- boolean isFirst = true;
- FrameInfoQueryResult validResult;
- while ((validResult = CodeInfoAccess.nextFrameInfo(codeInfo, frameInfoReader, singleShotFrameInfoQueryResultAllocator.reload(), dummyValueInfoAllocator, frameInfoState)) != null) {
- if (printedFrames >= MAX_STACK_FRAMES_PER_THREAD_TO_PRINT) {
- log.string("... (truncated)").newline();
- break;
- }
-
- if (!isFirst) {
- log.newline();
- }
- logFrameRaw(log, sp, ip);
- logFrameInfo(log, validResult, CodeInfoAccess.getName(codeInfo));
- isFirst = false;
- printedFrames++;
- }
- } else {
- super.logFrame(log, sp, ip, codeInfo, deoptFrame);
+ return;
+ }
+
+ boolean isFirst = true;
+ frameInfoCursor.initialize(codeInfo, ip);
+ while (frameInfoCursor.advance()) {
+ if (printedFrames >= MAX_STACK_FRAMES_PER_THREAD_TO_PRINT) {
+ log.string("... (truncated)").newline();
+ break;
+ }
+
+ if (!isFirst) {
+ log.newline();
}
+ logFrameRaw(log, sp, ip);
+
+ FrameInfoQueryResult frame = frameInfoCursor.get();
+ logFrameInfo(log, frame, CodeInfoAccess.getName(codeInfo));
+ isFirst = false;
+ printedFrames++;
+ }
+
+ if (isFirst) {
+ /* No information was printed, so try to print less detailed information. */
+ super.logFrame(log, sp, ip, codeInfo, deoptFrame);
}
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
index c03a268ef65b..b42a92f1f5ac 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
@@ -27,6 +27,7 @@
import java.lang.Thread.UncaughtExceptionHandler;
import java.security.AccessControlContext;
import java.security.AccessController;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -35,14 +36,18 @@
import org.graalvm.compiler.core.common.SuppressFBWarnings;
import org.graalvm.compiler.replacements.ReplacementsUtil;
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
+import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.word.Pointer;
-import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.AlwaysInline;
import com.oracle.svm.core.NeverInline;
+import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.jfr.events.ThreadSleepEvent;
+import com.oracle.svm.core.annotate.Alias;
+import com.oracle.svm.core.annotate.TargetClass;
+import com.oracle.svm.core.jdk.JDK19OrLater;
+import com.oracle.svm.core.jfr.events.ThreadSleepEventJDK17;
import com.oracle.svm.core.snippets.KnownIntrinsics;
import com.oracle.svm.util.ReflectionUtil;
@@ -344,13 +349,33 @@ static void initNewThreadLocalsAndLoader(Target_java_lang_Thread tjlt, boolean a
}
static void sleep(long millis) throws InterruptedException {
- long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks();
+ /* Starting with JDK 19, the thread sleep event is implemented as a Java-level event. */
+ if (JavaVersionUtil.JAVA_SPEC >= 19) {
+ if (com.oracle.svm.core.jfr.HasJfrSupport.get() && Target_jdk_internal_event_ThreadSleepEvent.isTurnedOn()) {
+ Target_jdk_internal_event_ThreadSleepEvent event = new Target_jdk_internal_event_ThreadSleepEvent();
+ try {
+ event.time = TimeUnit.MILLISECONDS.toNanos(millis);
+ event.begin();
+ sleep0(millis);
+ } finally {
+ event.commit();
+ }
+ } else {
+ sleep0(millis);
+ }
+ } else {
+ long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks();
+ sleep0(millis);
+ ThreadSleepEventJDK17.emit(millis, startTicks);
+ }
+ }
+
+ private static void sleep0(long millis) throws InterruptedException {
if (supportsVirtual() && isVirtualDisallowLoom(Thread.currentThread()) && !LoomSupport.isEnabled()) {
VirtualThreads.singleton().sleepMillis(millis);
} else {
PlatformThreads.sleep(millis);
}
- ThreadSleepEvent.emit(millis, startTicks);
}
static boolean isAlive(Thread thread) {
@@ -394,10 +419,38 @@ public static void blockedOn(Target_sun_nio_ch_Interruptible b) {
public static long getCurrentThreadId() {
long id = PlatformThreads.currentVThreadId.get();
if (GraalDirectives.inIntrinsic()) {
- ReplacementsUtil.dynamicAssert(id == getThreadId(Thread.currentThread()), "ids must match");
+ ReplacementsUtil.dynamicAssert(id != 0 && id == getThreadId(Thread.currentThread()), "ids must match");
} else {
- assert id == getThreadId(Thread.currentThread());
+ assert id != 0 && id == getThreadId(Thread.currentThread());
}
return id;
}
+
+ /**
+ * Similar to {@link #getCurrentThreadId()} but returns 0 if the thread id is not present. There
+ * is a small number of situations where the thread id might not be available, e.g., when a
+ * freshly attached thread causes a GC (before it initializes its {@link java.lang.Thread}
+ * object) or when a VM operation is enqueued by a non-Java thread.
+ */
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static long getCurrentThreadIdOrZero() {
+ if (CurrentIsolate.getCurrentThread().isNonNull()) {
+ return PlatformThreads.currentVThreadId.get();
+ }
+ return 0L;
+ }
+}
+
+@TargetClass(className = "jdk.internal.event.ThreadSleepEvent", onlyWith = JDK19OrLater.class)
+final class Target_jdk_internal_event_ThreadSleepEvent {
+ @Alias public long time;
+
+ @Alias
+ public static native boolean isTurnedOn();
+
+ @Alias
+ public native void begin();
+
+ @Alias
+ public native void commit();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaVMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaVMOperation.java
index 46a79a1df364..b71a12a98603 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaVMOperation.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaVMOperation.java
@@ -24,12 +24,14 @@
*/
package com.oracle.svm.core.thread;
+import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.word.WordFactory;
import com.oracle.svm.core.SubstrateOptions.ConcealedOptions;
import com.oracle.svm.core.SubstrateUtil;
-import com.oracle.svm.core.heap.RestrictHeapAccess;
import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.heap.RestrictHeapAccess;
import com.oracle.svm.core.heap.VMOperationInfo;
import com.oracle.svm.core.heap.VMOperationInfos;
import com.oracle.svm.core.jdk.SplittableRandomAccessors;
@@ -50,6 +52,7 @@
*/
public abstract class JavaVMOperation extends VMOperation implements VMOperationControl.JavaAllocationFreeQueue.Element {
protected IsolateThread queuingThread;
+ private long queuingThreadId;
private JavaVMOperation next;
private volatile boolean finished;
@@ -84,9 +87,8 @@ protected IsolateThread getQueuingThread(NativeVMOperationData data) {
}
@Override
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected void setQueuingThread(NativeVMOperationData data, IsolateThread thread) {
- queuingThread = thread;
+ protected long getQueuingThreadId(NativeVMOperationData data) {
+ return queuingThreadId;
}
@Override
@@ -96,8 +98,17 @@ protected boolean isFinished(NativeVMOperationData data) {
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected void setFinished(NativeVMOperationData data, boolean value) {
- finished = value;
+ protected void markAsQueued(NativeVMOperationData data) {
+ finished = false;
+ queuingThread = CurrentIsolate.getCurrentThread();
+ queuingThreadId = JavaThreads.getCurrentThreadIdOrZero();
+ }
+
+ @Override
+ protected void markAsFinished(NativeVMOperationData data) {
+ queuingThread = WordFactory.nullPointer();
+ queuingThreadId = 0;
+ finished = true;
}
@Override
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java
index 61e06b69cd04..46f7cadc0953 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java
@@ -24,9 +24,11 @@
*/
package com.oracle.svm.core.thread;
+import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
+import org.graalvm.word.WordFactory;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.heap.VMOperationInfo;
@@ -53,26 +55,34 @@ public void enqueueFromNonJavaThread(NativeVMOperationData data) {
VMOperationControl.get().enqueueFromNonJavaThread(data);
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@Override
- protected boolean isFinished(NativeVMOperationData data) {
- return data.getFinished();
+ protected IsolateThread getQueuingThread(NativeVMOperationData data) {
+ return data.getQueuingThread();
}
@Override
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected void setFinished(NativeVMOperationData data, boolean value) {
- data.setFinished(value);
+ protected long getQueuingThreadId(NativeVMOperationData data) {
+ return data.getQueuingThreadId();
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@Override
- protected IsolateThread getQueuingThread(NativeVMOperationData data) {
- return data.getQueuingThread();
+ protected boolean isFinished(NativeVMOperationData data) {
+ return data.getFinished();
}
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected void setQueuingThread(NativeVMOperationData data, IsolateThread value) {
- data.setQueuingThread(value);
+ protected void markAsQueued(NativeVMOperationData data) {
+ data.setFinished(false);
+ data.setQueuingThread(CurrentIsolate.getCurrentThread());
+ data.setQueuingThreadId(JavaThreads.getCurrentThreadIdOrZero());
+ }
+
+ @Override
+ protected void markAsFinished(NativeVMOperationData data) {
+ data.setQueuingThread(WordFactory.nullPointer());
+ data.setQueuingThreadId(0);
+ data.setFinished(true);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperationData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperationData.java
index fc84e5e7b1f1..ce1cee4d1149 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperationData.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperationData.java
@@ -58,6 +58,12 @@ public interface NativeVMOperationData extends PointerBase {
@RawField
void setQueuingThread(IsolateThread value);
+ @RawField
+ long getQueuingThreadId();
+
+ @RawField
+ void setQueuingThreadId(long currentThreadId);
+
@RawField
boolean getFinished();
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java
index 42c9fcaf79a5..71490c1c17f2 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java
@@ -90,7 +90,6 @@
import com.oracle.svm.core.monitor.MonitorSupport;
import com.oracle.svm.core.nodes.CFunctionEpilogueNode;
import com.oracle.svm.core.nodes.CFunctionPrologueNode;
-import com.oracle.svm.core.sampler.ProfilingSampler;
import com.oracle.svm.core.stack.StackOverflowCheck;
import com.oracle.svm.core.thread.VMThreads.StatusSupport;
import com.oracle.svm.core.threadlocal.FastThreadLocal;
@@ -129,6 +128,10 @@ public static PlatformThreads singleton() {
* The {@linkplain JavaThreads#getThreadId thread id} of the {@link Thread#currentThread()},
* which can be a {@linkplain Target_java_lang_Thread#vthread virtual thread} or the
* {@linkplain #currentThread platform thread itself}.
+ *
+ * As the value of the thread local can change over the thread lifetime (see carrier threads),
+ * it should only be accessed by the owning thread (via {@link FastThreadLocalLong#get()} and
+ * {@link FastThreadLocalLong#set(long)}).
*/
static final FastThreadLocalLong currentVThreadId = FastThreadLocalFactory.createLong("PlatformThreads.currentVThreadId").setMaxOffset(FastThreadLocal.BYTE_OFFSET);
@@ -307,16 +310,6 @@ public static IsolateThread getIsolateThread(Thread t) {
return getIsolateThreadUnsafe(t);
}
- /** Before detaching a thread, run any Java cleanup code. */
- static void threadExit(IsolateThread thread) {
- VMError.guarantee(thread.equal(CurrentIsolate.getCurrentThread()), "Cleanup must execute in detaching thread");
-
- Thread javaThread = currentThread.get(thread);
- if (javaThread != null) {
- toTarget(javaThread).exit();
- }
- }
-
@Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.")
static void afterThreadExit(IsolateThread thread) {
VMError.guarantee(thread.equal(CurrentIsolate.getCurrentThread()), "Cleanup must execute in detaching thread");
@@ -493,7 +486,7 @@ static void assignCurrent(Thread thread, boolean manuallyStarted) {
}
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ @Uninterruptible(reason = "Ensure consistency of vthread and cached vthread id.")
private static void assignCurrent0(Thread thread) {
VMError.guarantee(currentThread.get() == null, "overwriting existing java.lang.Thread");
currentVThreadId.set(JavaThreads.getThreadId(thread));
@@ -698,9 +691,9 @@ private static boolean isApplicationThread(IsolateThread isolateThread) {
return !VMOperationControl.isDedicatedVMOperationThread(isolateThread);
}
- /** This method should only be used to exit the main thread. */
@SuppressFBWarnings(value = "NN", justification = "notifyAll is necessary for Java semantics, no shared state needs to be modified beforehand")
public static void exit(Thread thread) {
+ ThreadListenerSupport.get().afterThreadRun();
/*
* First call Thread.exit(). This allows waiters on the thread object to observe that a
* daemon ThreadGroup is destroyed as well if this thread happens to be the last thread of a
@@ -794,10 +787,6 @@ protected static void threadStartRoutine(ObjectHandle threadHandle) {
singleton().unattachedStartedThreads.decrementAndGet();
singleton().beforeThreadRun(thread);
- if (ImageSingletons.contains(ProfilingSampler.class)) {
- ImageSingletons.lookup(ProfilingSampler.class).registerSampler();
- }
-
try {
if (VMThreads.isTearingDown()) {
/*
@@ -807,11 +796,10 @@ protected static void threadStartRoutine(ObjectHandle threadHandle) {
currentThread.get().interrupt();
}
+ ThreadListenerSupport.get().beforeThreadRun();
thread.run();
} catch (Throwable ex) {
JavaThreads.dispatchUncaughtException(thread, ex);
- } finally {
- exit(thread);
}
}
@@ -999,6 +987,7 @@ static void interrupt(Thread thread) {
}
}
+ @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true)
public static int getThreadStatus(Thread thread) {
assert !isVirtual(thread);
return (JavaVersionUtil.JAVA_SPEC >= 19) ? toTarget(thread).holder.threadStatus : toTarget(thread).threadStatus;
@@ -1032,6 +1021,11 @@ static boolean isAlive(Thread thread) {
return !(threadStatus == ThreadStatus.NEW || threadStatus == ThreadStatus.TERMINATED);
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ static boolean isTerminated(Thread thread) {
+ return getThreadStatus(thread) == ThreadStatus.TERMINATED;
+ }
+
private static ThreadData acquireThreadData(Thread thread) {
return toTarget(thread).threadData.acquire();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java
index ffe0b7dc9ca2..01f45420aa5f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java
@@ -362,7 +362,7 @@ private static void notInlinedLockNoTransition() {
*/
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
static void setSafepointRequested(IsolateThread vmThread, int value) {
- assert StatusSupport.isStatusCreated(vmThread) || VMOperationControl.mayExecuteVmOperations();
+ assert CurrentIsolate.getCurrentThread() == vmThread || StatusSupport.isStatusCreated(vmThread) || VMOperationControl.mayExecuteVmOperations();
assert value > 0;
safepointRequested.setVolatile(vmThread, value);
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListener.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListener.java
index 89c1118ce9b1..7db21aad40e8 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListener.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListener.java
@@ -29,9 +29,19 @@
import com.oracle.svm.core.Uninterruptible;
public interface ThreadListener {
- @Uninterruptible(reason = "Only uninterruptible code may be executed before Thread.run.")
- void beforeThreadRun(IsolateThread isolateThread, Thread javaThread);
+ @Uninterruptible(reason = "Only uninterruptible code may be executed before the thread is fully started.")
+ @SuppressWarnings("unused")
+ default void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) {
+ }
+
+ default void beforeThreadRun() {
+ }
+
+ default void afterThreadRun() {
+ }
@Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.")
- void afterThreadExit(IsolateThread isolateThread, Thread javaThread);
+ @SuppressWarnings("unused")
+ default void afterThreadExit(IsolateThread isolateThread, Thread javaThread) {
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java
index 558e6d41bb2b..8104fbb28c66 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java
@@ -61,7 +61,19 @@ public static ThreadListenerSupport get() {
@Uninterruptible(reason = "Force that all listeners are uninterruptible.")
public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) {
for (int i = 0; i < listeners.length; i++) {
- listeners[i].beforeThreadRun(isolateThread, javaThread);
+ listeners[i].beforeThreadStart(isolateThread, javaThread);
+ }
+ }
+
+ public void beforeThreadRun() {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].beforeThreadRun();
+ }
+ }
+
+ public void afterThreadRun() {
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].afterThreadRun();
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java
index e0ed7fa9fd85..4db3737c02dd 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java
@@ -68,7 +68,7 @@ public static class Options {
* requested time intervals. The timer uses an exponentially weighted moving average (EWMA) to
* adapt to a changing frequency of safepoint checks in the code that the thread executes.
*/
- private static class RecurringCallbackTimer {
+ public static class RecurringCallbackTimer {
private static final RecurringCallbackAccess CALLBACK_ACCESS = new RecurringCallbackAccess() {
@Override
public void throwException(Throwable t) {
@@ -108,7 +108,7 @@ public void throwException(Throwable t) {
}
@Uninterruptible(reason = "Must not contain safepoint checks.")
- public void evaluate() {
+ void evaluate() {
updateStatistics();
try {
executeCallback();
@@ -118,7 +118,7 @@ public void evaluate() {
}
@Uninterruptible(reason = "Must be uninterruptible to avoid races with the safepoint code.")
- public void updateStatistics() {
+ void updateStatistics() {
long now = System.nanoTime();
long elapsedNanos = now - lastCapture;
@@ -197,7 +197,7 @@ private void updateSafepointRequested() {
}
@Uninterruptible(reason = "Called by uninterruptible code.")
- public void setSafepointRequested(int value) {
+ void setSafepointRequested(int value) {
requestedChecks = value;
Safepoint.setSafepointRequested(value);
}
@@ -234,25 +234,64 @@ private void invokeCallback() {
private static final String enableSupportOption = SubstrateOptionsParser.commandArgument(SupportRecurringCallback, "+");
+ /**
+ * Registers or removes a recurring callback for the current thread. Only one recurring callback
+ * can be registered at a time. If there is already another recurring callback registered, it
+ * will be overwritten.
+ */
@Override
public void registerRecurringCallback(long interval, TimeUnit unit, RecurringCallback callback) {
+ IsolateThread thread = CurrentIsolate.getCurrentThread();
+ removeRecurringCallback(thread);
if (callback != null) {
if (!SupportRecurringCallback.getValue()) {
VMError.shouldNotReachHere("Recurring callbacks must be enabled during image build with option " + enableSupportOption);
}
VMError.guarantee(MultiThreaded.getValue(), "Recurring callbacks are only supported in multi-threaded mode.");
+
long intervalNanos = unit.toNanos(interval);
if (intervalNanos < 1) {
throw new IllegalArgumentException("intervalNanos");
}
- RecurringCallbackTimer timer = new RecurringCallbackTimer(intervalNanos, callback);
- activeTimer.set(timer);
- Safepoint.setSafepointRequested(timer.requestedChecks);
- } else {
- activeTimer.set(null);
+
+ RecurringCallbackTimer timer = createRecurringCallbackTimer(intervalNanos, callback);
+ setRecurringCallback(thread, timer);
}
}
+ public static RecurringCallbackTimer createRecurringCallbackTimer(long intervalNanos, RecurringCallback callback) {
+ assert callback != null;
+ return new RecurringCallbackTimer(intervalNanos, callback);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true)
+ public static void setRecurringCallback(IsolateThread thread, RecurringCallbackTimer timer) {
+ assert SupportRecurringCallback.getValue() && MultiThreaded.getValue();
+ assert timer.targetIntervalNanos > 0;
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+
+ activeTimer.set(thread, timer);
+ Safepoint.setSafepointRequested(thread, timer.requestedChecks);
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true)
+ public static RecurringCallback getRecurringCallback(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+
+ RecurringCallbackTimer value = activeTimer.get(thread);
+ if (value != null) {
+ return value.callback;
+ }
+ return null;
+ }
+
+ @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true)
+ public static void removeRecurringCallback(IsolateThread thread) {
+ assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
+
+ activeTimer.set(thread, null);
+ }
+
/**
* Updates the statistics that are used to compute how frequently a thread needs to enter the
* safepoint slowpath and executes the callback if necessary. This also resets the safepoint
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java
index 762ad5332b07..0145a5a4254f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperation.java
@@ -107,7 +107,7 @@ protected final void execute(NativeVMOperationData data) {
trace.string("[VMOperation.execute caught: ").string(t.getClass().getName()).string("]").newline();
throw VMError.shouldNotReachHere(t);
} finally {
- ExecuteVMOperationEvent.emit(this, requestingThread, startTicks);
+ ExecuteVMOperationEvent.emit(this, getQueuingThreadId(data), startTicks);
control.setInProgress(prevOperation, prevQueuingThread, prevExecutingThread, false);
}
}
@@ -178,16 +178,17 @@ protected boolean hasWork(@SuppressWarnings("unused") NativeVMOperationData data
return true;
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ protected abstract void markAsQueued(NativeVMOperationData data);
+
+ protected abstract void markAsFinished(NativeVMOperationData data);
+
protected abstract IsolateThread getQueuingThread(NativeVMOperationData data);
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected abstract void setQueuingThread(NativeVMOperationData data, IsolateThread value);
+ protected abstract long getQueuingThreadId(NativeVMOperationData data);
protected abstract boolean isFinished(NativeVMOperationData data);
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected abstract void setFinished(NativeVMOperationData data, boolean value);
-
@RestrictHeapAccess(access = RestrictHeapAccess.Access.UNRESTRICTED, reason = "Whitelisted because some operations may allocate.")
protected abstract void operate(NativeVMOperationData data);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java
index 5dced02445a7..37870e8a129a 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java
@@ -280,7 +280,7 @@ private void enqueue(VMOperation operation, NativeVMOperationData data) {
if (!MultiThreaded.getValue()) {
// no safepoint is needed, so we can always directly execute the operation
assert !useDedicatedVMOperationThread();
- markAsQueued(operation, data);
+ operation.markAsQueued(data);
try {
operation.execute(data);
} finally {
@@ -317,15 +317,8 @@ public void enqueueFromNonJavaThread(NativeVMOperation operation, NativeVMOperat
mainQueues.enqueueUninterruptibly(operation, data);
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected static void markAsQueued(VMOperation operation, NativeVMOperationData data) {
- operation.setFinished(data, false);
- operation.setQueuingThread(data, CurrentIsolate.getCurrentThread());
- }
-
private static void markAsFinished(VMOperation operation, NativeVMOperationData data, VMCondition operationFinished) {
- operation.setQueuingThread(data, WordFactory.nullPointer());
- operation.setFinished(data, true);
+ operation.markAsFinished(data);
if (operationFinished != null) {
operationFinished.broadcast();
}
@@ -552,7 +545,7 @@ private void enqueue(VMOperation operation, NativeVMOperationData data) {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private void enqueue(NativeVMOperation operation, NativeVMOperationData data) {
assert operation == data.getNativeVMOperation();
- markAsQueued(operation, data);
+ operation.markAsQueued(data);
if (operation.getCausesSafepoint()) {
nativeSafepointOperations.push(data);
} else {
@@ -561,7 +554,7 @@ private void enqueue(NativeVMOperation operation, NativeVMOperationData data) {
}
private void enqueue(JavaVMOperation operation, NativeVMOperationData data) {
- markAsQueued(operation, data);
+ operation.markAsQueued(data);
if (operation.getCausesSafepoint()) {
javaSafepointOperations.push(operation);
} else {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java
index dc33f44de177..ed1a54818d27 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java
@@ -338,7 +338,7 @@ public void detachThread(IsolateThread thread) {
nextOsThreadToCleanup = OSThreadHandleTL.get(thread);
}
- exit(thread);
+ threadExit(thread);
/* Only uninterruptible code may be executed from now on. */
PlatformThreads.afterThreadExit(thread);
@@ -469,8 +469,12 @@ private void waitUntilLastOsThreadExited() {
}
@Uninterruptible(reason = "Called from uninterruptible code, but still safe at this point.", calleeMustBe = false)
- private static void exit(IsolateThread thread) {
- PlatformThreads.threadExit(thread);
+ private static void threadExit(IsolateThread thread) {
+ VMError.guarantee(thread.equal(CurrentIsolate.getCurrentThread()), "Cleanup must execute in detaching thread");
+ Thread javaThread = PlatformThreads.currentThread.get(thread);
+ if (javaThread != null) {
+ PlatformThreads.exit(javaThread);
+ }
}
/**
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java
index 0a9a4cd4ca5f..e9b3d46b0adf 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java
@@ -607,7 +607,7 @@ protected void recordFrame(ResolvedJavaMethod method, Infopoint infopoint, boole
@Override
protected boolean includeLocalValues(ResolvedJavaMethod method, Infopoint infopoint, boolean isDeoptEntry) {
- if (ImageSingletons.contains(ProfilingSampler.class) && ImageSingletons.lookup(ProfilingSampler.class).isCollectingActive()) {
+ if (ImageSingletons.contains(ProfilingSampler.class)) {
return true;
}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java
index aa3d7984ea13..fbccdebb2bdd 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java
@@ -50,8 +50,9 @@
import com.oracle.svm.hosted.FeatureImpl;
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
import com.oracle.svm.util.ModuleSupport;
+import com.oracle.svm.util.ReflectionUtil;
-import jdk.jfr.Event;
+import jdk.internal.event.Event;
import jdk.jfr.internal.JVM;
import jdk.vm.ci.meta.MetaAccessProvider;
@@ -76,6 +77,7 @@ public void afterRegistration(AfterRegistrationAccess access) {
ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventFeature.class, false, "jdk.jfr", "jdk.jfr.events");
ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrFeature.class, false, "jdk.jfr", "jdk.jfr.events");
ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventSubstitution.class, false, "jdk.internal.vm.ci", "jdk.vm.ci.hotspot");
+ ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventFeature.class, false, "java.base", "jdk.internal.event");
}
@Override
@@ -92,10 +94,8 @@ public void duringSetup(DuringSetupAccess c) {
@Override
public void beforeAnalysis(Feature.BeforeAnalysisAccess access) {
if (JavaVersionUtil.JAVA_SPEC < 19) {
- Class> eventClass = access.findClassByName("jdk.internal.event.Event");
- if (eventClass != null) {
- access.registerSubtypeReachabilityHandler(JfrEventFeature::eventSubtypeReachable, eventClass);
- }
+ /* In JDK 19+, events don't have an eventHandler field anymore. */
+ access.registerSubtypeReachabilityHandler(JfrEventFeature::eventSubtypeReachable, Event.class);
}
}
@@ -115,12 +115,13 @@ public void beforeCompilation(BeforeCompilationAccess a) {
// Off-set by one for error-catcher
JfrTraceId.assign(clazz, hub.getTypeID() + 1);
}
+
+ /* Store the event configuration in the dynamic hub companion. */
if (JavaVersionUtil.JAVA_SPEC >= 19) {
try {
FeatureImpl.CompilationAccessImpl accessImpl = ((FeatureImpl.CompilationAccessImpl) a);
Method getConfiguration = JVM.class.getDeclaredMethod("getConfiguration", Class.class);
for (var newEventClass : JfrJavaEvents.getAllEventClasses()) {
- /* Store the event configuration in the companion. */
Object ec = getConfiguration.invoke(JVM.getJVM(), newEventClass);
DynamicHub dynamicHub = accessImpl.getMetaAccess().lookupJavaType(newEventClass).getHub();
dynamicHub.setJrfEventConfiguration(ec);
@@ -132,23 +133,16 @@ public void beforeCompilation(BeforeCompilationAccess a) {
}
private static void eventSubtypeReachable(DuringAnalysisAccess a, Class> c) {
- DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a;
- if (c.getCanonicalName().equals("jdk.jfr.Event") ||
- c.getCanonicalName().equals("jdk.internal.event.Event") ||
- c.getCanonicalName().equals("jdk.jfr.events.AbstractJDKEvent") ||
- c.getCanonicalName().equals("jdk.jfr.events.AbstractBufferStatisticsEvent") ||
- Modifier.isAbstract(c.getModifiers())) {
+ if (Modifier.isAbstract(c.getModifiers())) {
return;
}
- try {
- Field f = c.getDeclaredField("eventHandler");
- RuntimeReflection.register(f);
- access.rescanRoot(f);
- if (!access.concurrentReachabilityHandlers()) {
- access.requireAnalysisIteration();
- }
- } catch (Exception e) {
- throw VMError.shouldNotReachHere("Unable to register eventHandler for: " + c.getCanonicalName(), e);
+
+ DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a;
+ Field f = ReflectionUtil.lookupField(c, "eventHandler");
+ RuntimeReflection.register(f);
+ access.rescanRoot(f);
+ if (!access.concurrentReachabilityHandlers()) {
+ access.requireAnalysisIteration();
}
}
}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java
index 7bf6e1702a4f..f0d230c2c025 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java
@@ -24,11 +24,15 @@
*/
package com.oracle.svm.hosted.jfr;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentHashMap;
+import org.graalvm.collections.EconomicMap;
+import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
+import org.graalvm.nativeimage.AnnotationAccess;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
@@ -38,9 +42,9 @@
import com.oracle.svm.core.jfr.JfrEventWriterAccess;
import com.oracle.svm.core.jfr.JfrJavaEvents;
import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.util.ReflectionUtil;
import jdk.internal.misc.Unsafe;
-import jdk.jfr.Event;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.SecuritySupport;
import jdk.vm.ci.meta.MetaAccessProvider;
@@ -50,31 +54,33 @@
import jdk.vm.ci.meta.Signature;
/**
- * This class triggers the class redefinition (see {@link JVM#retransformClasses}) for all
- * {@link Event} classes that are visited during static analysis.
+ * This class triggers the class redefinition (see {@link JVM#retransformClasses}) for all event
+ * classes that are visited during static analysis.
*/
@Platforms(Platform.HOSTED_ONLY.class)
public class JfrEventSubstitution extends SubstitutionProcessor {
- private final ResolvedJavaType jdkJfrEvent;
+ private final ResolvedJavaType baseEventType;
private final ConcurrentHashMap typeSubstitution;
private final ConcurrentHashMap methodSubstitutions;
private final ConcurrentHashMap fieldSubstitutions;
+ private final EconomicMap> mirrorEventMapping;
JfrEventSubstitution(MetaAccessProvider metaAccess) {
- jdkJfrEvent = metaAccess.lookupJavaType(Event.class);
+ baseEventType = metaAccess.lookupJavaType(jdk.internal.event.Event.class);
ResolvedJavaType jdkJfrEventWriter = metaAccess.lookupJavaType(JfrEventWriterAccess.getEventWriterClass());
changeWriterResetMethod(jdkJfrEventWriter);
typeSubstitution = new ConcurrentHashMap<>();
methodSubstitutions = new ConcurrentHashMap<>();
fieldSubstitutions = new ConcurrentHashMap<>();
+ mirrorEventMapping = createMirrorEventsMapping();
}
@Override
public ResolvedJavaField lookup(ResolvedJavaField field) {
ResolvedJavaType type = field.getDeclaringClass();
if (needsClassRedefinition(type)) {
- typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass);
+ typeSubstitution.computeIfAbsent(type, this::initEventClass);
return fieldSubstitutions.computeIfAbsent(field, JfrEventSubstitution::initEventField);
}
return field;
@@ -84,7 +90,7 @@ public ResolvedJavaField lookup(ResolvedJavaField field) {
public ResolvedJavaMethod lookup(ResolvedJavaMethod method) {
ResolvedJavaType type = method.getDeclaringClass();
if (needsClassRedefinition(type)) {
- typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass);
+ typeSubstitution.computeIfAbsent(type, this::initEventClass);
return methodSubstitutions.computeIfAbsent(method, JfrEventSubstitution::initEventMethod);
}
return method;
@@ -93,7 +99,7 @@ public ResolvedJavaMethod lookup(ResolvedJavaMethod method) {
@Override
public ResolvedJavaType lookup(ResolvedJavaType type) {
if (needsClassRedefinition(type)) {
- typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass);
+ typeSubstitution.computeIfAbsent(type, this::initEventClass);
}
return type;
}
@@ -139,14 +145,23 @@ private static ResolvedJavaMethod initEventMethod(ResolvedJavaMethod oldMethod)
throw VMError.shouldNotReachHere("Could not re-resolve method: " + oldMethod);
}
- private static Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeException {
+ private Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeException {
try {
- Class extends Event> newEventClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), eventType).asSubclass(Event.class);
+ Class extends jdk.internal.event.Event> newEventClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), eventType)
+ .asSubclass(jdk.internal.event.Event.class);
eventType.initialize();
+
+ // It is crucial that mirror events are registered before the actual events.
+ Class extends jdk.jfr.Event> mirrorEventClass = mirrorEventMapping.get(newEventClass.getName());
+ if (mirrorEventClass != null) {
+ SecuritySupport.registerMirror(mirrorEventClass);
+ }
+
SecuritySupport.registerEvent(newEventClass);
+
JfrJavaEvents.registerEventClass(newEventClass);
// the reflection registration for the event handler field is delayed to the JfrFeature
- // duringAnalysis callback so it does not not race/interfere with other retransforms
+ // duringAnalysis callback so it does not race/interfere with other retransforms
JVM.getJVM().retransformClasses(new Class>[]{newEventClass});
return Boolean.TRUE;
} catch (Throwable ex) {
@@ -155,7 +170,7 @@ private static Boolean initEventClass(ResolvedJavaType eventType) throws Runtime
}
private boolean needsClassRedefinition(ResolvedJavaType type) {
- return !type.isAbstract() && jdkJfrEvent.isAssignableFrom(type) && !jdkJfrEvent.equals(type);
+ return !type.isAbstract() && baseEventType.isAssignableFrom(type) && !baseEventType.equals(type);
}
/**
@@ -205,4 +220,31 @@ private static Method getMethodToFetchMetaspaceMethod(Class> method) throws No
}
}
}
+
+ /*
+ * Mirror events contain the JFR-specific annotations. The mirrored event does not have any
+ * dependency on JFR-specific classes. If the mirrored event is used, we must ensure that the
+ * mirror event is registered as well. Otherwise, incorrect JFR metadata would be emitted.
+ */
+ @SuppressWarnings("unchecked")
+ private static EconomicMap> createMirrorEventsMapping() {
+ EconomicMap> result = EconomicMap.create();
+ if (JavaVersionUtil.JAVA_SPEC >= 17) {
+ Class extends Annotation> mirrorEventAnnotationClass = (Class extends Annotation>) ReflectionUtil.lookupClass(false, "jdk.jfr.internal.MirrorEvent");
+ Class> jdkEventsClass = ReflectionUtil.lookupClass(false, "jdk.jfr.internal.instrument.JDKEvents");
+ Class>[] mirrorEventClasses = ReflectionUtil.readStaticField(jdkEventsClass, "mirrorEventClasses");
+ for (int i = 0; i < mirrorEventClasses.length; i++) {
+ Class extends jdk.jfr.Event> mirrorEventClass = (Class extends jdk.jfr.Event>) mirrorEventClasses[i];
+ Annotation mirrorEvent = AnnotationAccess.getAnnotation(mirrorEventClass, mirrorEventAnnotationClass);
+ Method m = ReflectionUtil.lookupMethod(mirrorEventAnnotationClass, "className");
+ try {
+ String className = (String) m.invoke(mirrorEvent);
+ result.put(className, mirrorEventClass);
+ } catch (Exception e) {
+ throw VMError.shouldNotReachHere(e);
+ }
+ }
+ }
+ return result;
+ }
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index 81efa0e0c80d..b6f2de9ffb23 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -33,9 +33,9 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
-import java.util.Comparator;
import org.graalvm.nativeimage.ImageInfo;
import org.graalvm.nativeimage.hosted.Feature;
@@ -48,6 +48,7 @@
import com.oracle.svm.test.jfr.utils.Jfr;
import com.oracle.svm.test.jfr.utils.JfrFileParser;
import com.oracle.svm.test.jfr.utils.LocalJfr;
+import com.oracle.svm.util.ClassUtil;
import com.oracle.svm.util.ModuleSupport;
import jdk.jfr.Recording;
@@ -58,7 +59,6 @@
public abstract class JfrTest {
private Jfr jfr;
private Recording recording;
- private final ChronologicalComparator chronologicalComparator = new ChronologicalComparator();
@BeforeClass
public static void checkForJFR() {
@@ -66,35 +66,21 @@ public static void checkForJFR() {
}
@Before
- public void startRecording() {
- try {
- jfr = new LocalJfr();
- recording = jfr.createRecording(getClass().getName());
- enableEvents();
- jfr.startRecording(recording);
- } catch (Exception e) {
- Assert.fail("Fail to start recording! Cause: " + e.getMessage());
- }
+ public void startRecording() throws Throwable {
+ jfr = new LocalJfr();
+ recording = jfr.createRecording(getClass().getName());
+ enableEvents();
+ jfr.startRecording(recording);
}
@After
- public void endRecording() {
+ public void endRecording() throws Throwable {
try {
jfr.endRecording(recording);
checkRecording();
- } catch (Exception e) {
- Assert.fail("Fail to stop recording! Cause: " + e.getMessage());
- }
- try {
validateEvents();
- } catch (Throwable throwable) {
- Assert.fail("validateEvents failed: " + throwable.getMessage());
} finally {
- try {
- jfr.cleanupRecording(recording);
- } catch (Exception e) {
- Assert.fail("Fail to cleanup recording! Cause: " + e.getMessage());
- }
+ jfr.cleanupRecording(recording);
}
}
@@ -159,15 +145,14 @@ private Path makeCopy(String testName) throws IOException { // from jdk 19
return p;
}
- protected List getEvents(String testName) throws IOException {
- Path p = makeCopy(testName);
+ protected List getEvents() throws IOException {
+ Path p = makeCopy(ClassUtil.getUnqualifiedName(getClass()));
List events = RecordingFile.readAllEvents(p);
- Collections.sort(events, chronologicalComparator);
+ Collections.sort(events, new ChronologicalComparator());
// remove events that are not in the list of tested events
events.removeIf(event -> (Arrays.stream(getTestedEvents()).noneMatch(testedEvent -> (testedEvent.equals(event.getEventType().getName())))));
return events;
}
-
}
class JfrTestFeature implements Feature {
@@ -178,15 +163,5 @@ public void afterRegistration(AfterRegistrationAccess access) {
* com.oracle.svm.test.jfr.utils.poolparsers.ClassConstantPoolParser.parse
*/
ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrTestFeature.class, false, "jdk.internal.vm.compiler", "org.graalvm.compiler.serviceprovider");
-
- /*
- * Use of com.oracle.svm.core.sampler.SamplerBuffer,
- * com.oracle.svm.core.sampler.SamplerBufferAccess.allocate,
- * com.oracle.svm.core.sampler.SamplerBufferAccess.free,
- * com.oracle.svm.core.sampler.SamplerBuffersAccess.processSamplerBuffer and
- * com.oracle.svm.core.sampler.SamplerThreadLocal.setThreadLocalBuffer in
- * com.oracle.svm.test.jfr.TestStackTraceEvent.test.
- */
- ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrTestFeature.class, false, "org.graalvm.nativeimage.builder", "com.oracle.svm.core.sampler");
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
index 04f5f9234afc..88f0f1e30aa3 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
@@ -26,9 +26,10 @@
package com.oracle.svm.test.jfr;
-import com.oracle.svm.test.jfr.events.ClassEvent;
import org.junit.Test;
+import com.oracle.svm.test.jfr.events.ClassEvent;
+
public class TestClassEvent extends JfrTest {
@Override
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java
similarity index 77%
rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java
index 66df0367e616..fa14c37c90f9 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java
@@ -28,21 +28,19 @@
import static org.junit.Assert.assertTrue;
-import java.util.List;
-
-import jdk.jfr.consumer.RecordedClass;
-import jdk.jfr.consumer.RecordedThread;
import org.junit.Test;
+import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
-import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedThread;
-public class TestJavaMonitorEnter extends JfrTest {
+public class TestJavaMonitorEnterEvent extends JfrTest {
private static final int MILLIS = 60;
- static volatile boolean passedCheckpoint = false;
- static Thread firstThread;
- static Thread secondThread;
- static final Helper helper = new Helper();
+
+ private final Helper helper = new Helper();
+ private Thread firstThread;
+ private Thread secondThread;
+ private volatile boolean passedCheckpoint;
@Override
public String[] getTestedEvents() {
@@ -51,16 +49,12 @@ public String[] getTestedEvents() {
@Override
public void validateEvents() throws Throwable {
- List events;
- events = getEvents("jdk.JavaMonitorEnter");
boolean found = false;
- for (RecordedEvent event : events) {
- RecordedObject struct = event;
- String eventThread = struct. getValue("eventThread").getJavaName();
- if (struct. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS && secondThread.getName().equals(eventThread)) {
-
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ if (event. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS && secondThread.getName().equals(eventThread)) {
// verify previous owner
- assertTrue("Previous owner is wrong", struct. getValue("previousOwner").getJavaName().equals(firstThread.getName()));
+ assertTrue("Previous owner is wrong", event. getValue("previousOwner").getJavaName().equals(firstThread.getName()));
found = true;
break;
}
@@ -88,13 +82,14 @@ public void test() throws Exception {
};
firstThread = new Thread(first);
secondThread = new Thread(second);
+
firstThread.start();
firstThread.join();
secondThread.join();
}
- static class Helper {
+ private class Helper {
private synchronized void doWork() throws InterruptedException {
if (Thread.currentThread().equals(secondThread)) {
return; // second thread doesn't need to do work.
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java
similarity index 84%
rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java
index 64cbb358e9aa..e9521c824c7d 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java
@@ -30,21 +30,19 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import java.util.List;
-
-import jdk.jfr.consumer.RecordedClass;
import org.junit.Test;
+import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
-import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordedThread;
-public class TestJavaMonitorWait extends JfrTest {
+public class TestJavaMonitorWaitEvent extends JfrTest {
private static final int MILLIS = 50;
private static final int COUNT = 10;
+
+ private final Helper helper = new Helper();
private String producerName;
private String consumerName;
- static final Helper helper = new Helper();
@Override
public String[] getTestedEvents() {
@@ -53,24 +51,20 @@ public String[] getTestedEvents() {
@Override
public void validateEvents() throws Throwable {
- List events;
- events = getEvents("jdk.JavaMonitorWait");
-
int prodCount = 0;
int consCount = 0;
String lastEventThreadName = null; // should alternate if buffer is 1
- for (RecordedEvent event : events) {
- RecordedObject struct = event;
- String eventThread = struct. getValue("eventThread").getJavaName();
- String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null;
assertTrue("No event thread", eventThread != null);
if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) ||
- !struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ !event. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
assertTrue("Wrong event duration", event.getDuration().toMillis() >= MILLIS);
- assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
+ assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue());
if (lastEventThreadName == null) {
lastEventThreadName = notifThread;
@@ -110,13 +104,15 @@ public void test() throws Exception {
Thread tp = new Thread(producer);
producerName = tp.getName();
consumerName = tc.getName();
+
tp.start();
tc.start();
+
tp.join();
tc.join();
}
- static class Helper {
+ private class Helper {
private int count = 0;
private final int bufferSize = 1;
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java
similarity index 81%
rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java
index 959665391729..99c82f4dd0d4 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java
@@ -29,25 +29,23 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import java.util.List;
-
-import org.junit.Test;
import org.junit.Assert;
+import org.junit.Test;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
-import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordedThread;
-public class TestJavaMonitorWaitInterrupt extends JfrTest {
+public class TestJavaMonitorWaitInterruptEvent extends JfrTest {
private static final int MILLIS = 50;
- static final Helper helper = new Helper();
- static Thread interruptedThread;
- static Thread interrupterThread;
- static Thread simpleWaitThread;
- static Thread simpleNotifyThread;
- private boolean interruptedFound = false;
- private boolean simpleWaitFound = false;
+
+ private Helper helper = new Helper();
+ private Thread interruptedThread;
+ private Thread interrupterThread;
+ private Thread simpleWaitThread;
+ private Thread simpleNotifyThread;
+ private boolean interruptedFound;
+ private boolean simpleWaitFound;
@Override
public String[] getTestedEvents() {
@@ -56,24 +54,20 @@ public String[] getTestedEvents() {
@Override
public void validateEvents() throws Throwable {
- List events;
- events = getEvents("TestJavaMonitorWaitInterrupt");
-
- for (RecordedEvent event : events) {
- RecordedObject struct = event;
- String eventThread = struct. getValue("eventThread").getJavaName();
- String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null;
if (!eventThread.equals(interrupterThread.getName()) &&
!eventThread.equals(interruptedThread.getName()) &&
!eventThread.equals(simpleNotifyThread.getName()) &&
!eventThread.equals(simpleWaitThread.getName())) {
continue;
}
- if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
assertTrue("Event is wrong duration." + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS);
- assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
+ assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue());
if (eventThread.equals(interruptedThread.getName())) {
assertTrue("Notifier of interrupted thread should be null", notifThread == null);
@@ -87,8 +81,7 @@ public void validateEvents() throws Throwable {
simpleWaitFound && interruptedFound);
}
- private static void testInterruption() throws Exception {
-
+ private void testInterruption() throws Exception {
Runnable interrupted = () -> {
try {
helper.interrupt(); // must enter first
@@ -113,7 +106,7 @@ private static void testInterruption() throws Exception {
interrupterThread.join();
}
- private static void testWaitNotify() throws Exception {
+ private void testWaitNotify() throws Exception {
Runnable simpleWaiter = () -> {
try {
helper.simpleNotify();
@@ -134,6 +127,7 @@ private static void testWaitNotify() throws Exception {
simpleNotifyThread = new Thread(simpleNotifier);
simpleWaitThread.start();
+
simpleWaitThread.join();
simpleNotifyThread.join();
}
@@ -144,9 +138,7 @@ public void test() throws Exception {
testWaitNotify();
}
- static class Helper {
- public Thread interrupted;
-
+ private class Helper {
public synchronized void interrupt() throws InterruptedException {
if (Thread.currentThread().equals(interruptedThread)) {
// Ensure T1 enters critical section first
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java
similarity index 77%
rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java
index ca225eb1ccd1..5a70dbd44b2b 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java
@@ -29,24 +29,21 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import java.util.List;
-
import org.junit.Test;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
-import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordedThread;
-public class TestJavaMonitorWaitNotifyAll extends JfrTest {
+public class TestJavaMonitorWaitNotifyAllEvent extends JfrTest {
private static final int MILLIS = 50;
- static final Helper helper = new Helper();
- static Thread producerThread1;
- static Thread producerThread2;
- static Thread consumerThread;
- private boolean notifierFound = false;
- private int waitersFound = 0;
+ private Helper helper = new Helper();
+ private Thread producerThread1;
+ private Thread producerThread2;
+ private Thread consumerThread;
+ private boolean notifierFound;
+ private int waitersFound;
@Override
public String[] getTestedEvents() {
@@ -55,27 +52,24 @@ public String[] getTestedEvents() {
@Override
public void validateEvents() throws Throwable {
- List events = getEvents("TestJavaMonitorWaitNotifyAll");
-
- for (RecordedEvent event : events) {
- RecordedObject struct = event;
- String eventThread = struct. getValue("eventThread").getJavaName();
- String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null;
if (!eventThread.equals(producerThread1.getName()) &&
!eventThread.equals(producerThread2.getName()) &&
!eventThread.equals(consumerThread.getName())) {
continue;
}
- if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS);
if (eventThread.equals(consumerThread.getName())) {
- assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue());
+ assertTrue("Should have timed out.", event. getValue("timedOut").booleanValue());
notifierFound = true;
} else {
- assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue());
+ assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue());
assertTrue("Notifier thread name is incorrect", notifThread.equals(consumerThread.getName()));
waitersFound++;
}
@@ -95,8 +89,6 @@ public void test() throws Exception {
}
};
- producerThread1 = new Thread(producer);
- producerThread2 = new Thread(producer);
Runnable consumer = () -> {
try {
helper.doWork();
@@ -105,22 +97,26 @@ public void test() throws Exception {
}
};
+ producerThread1 = new Thread(producer);
+ producerThread2 = new Thread(producer);
consumerThread = new Thread(consumer);
+
producerThread1.start();
+
consumerThread.join();
producerThread1.join();
producerThread2.join();
}
- static class Helper {
+ private class Helper {
public synchronized void doWork() throws InterruptedException {
if (Thread.currentThread().equals(consumerThread)) {
wait(MILLIS);
notifyAll(); // should wake up both producers
- } else if (Thread.currentThread().equals(producerThread1)){
+ } else if (Thread.currentThread().equals(producerThread1)) {
producerThread2.start();
wait();
- } else if (Thread.currentThread().equals(producerThread2)){
+ } else if (Thread.currentThread().equals(producerThread2)) {
consumerThread.start();
wait();
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java
similarity index 80%
rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java
index ff367fd236fb..908a0db77b3a 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java
@@ -28,25 +28,24 @@
import static org.junit.Assert.assertTrue;
-import java.util.List;
-
-import org.junit.Test;
import org.junit.Assert;
+import org.junit.Test;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedEvent;
-import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordedThread;
-public class TestJavaMonitorWaitTimeout extends JfrTest {
+public class TestJavaMonitorWaitTimeoutEvent extends JfrTest {
private static final int MILLIS = 50;
- static final Helper helper = new Helper();
- static Thread unheardNotifierThread;
- static Thread timeoutThread;
- static Thread simpleWaitThread;
- static Thread simpleNotifyThread;
- private boolean timeoutFound = false;
- private boolean simpleWaitFound = false;
+
+ private final Helper helper = new Helper();
+ private Thread unheardNotifierThread;
+ private Thread timeoutThread;
+ private Thread simpleWaitThread;
+ private Thread simpleNotifyThread;
+ private boolean timeoutFound;
+ private boolean simpleWaitFound;
+
@Override
public String[] getTestedEvents() {
return new String[]{"jdk.JavaMonitorWait"};
@@ -54,26 +53,22 @@ public String[] getTestedEvents() {
@Override
public void validateEvents() throws Throwable {
- List events;
- events = getEvents("TestJavaMonitorWaitTimeout");
-
- for (RecordedEvent event : events) {
- RecordedObject struct = event;
- String eventThread = struct. getValue("eventThread").getJavaName();
- String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null;
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
+ String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null;
if (!eventThread.equals(unheardNotifierThread.getName()) &&
!eventThread.equals(timeoutThread.getName()) &&
!eventThread.equals(simpleNotifyThread.getName()) &&
!eventThread.equals(simpleWaitThread.getName())) {
continue;
}
- if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) {
+ if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) {
continue;
}
assertTrue("Event is wrong duration:" + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS);
if (eventThread.equals(timeoutThread.getName())) {
assertTrue("Notifier of timeout thread should be null", notifThread == null);
- assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue());
+ assertTrue("Should have timed out.", event. getValue("timedOut").booleanValue());
timeoutFound = true;
} else if (eventThread.equals(simpleWaitThread.getName())) {
assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyThread.getName()));
@@ -85,7 +80,7 @@ public void validateEvents() throws Throwable {
simpleWaitFound && timeoutFound);
}
- private static void testTimeout() throws InterruptedException {
+ private void testTimeout() throws InterruptedException {
Runnable unheardNotifier = () -> {
helper.unheardNotify();
};
@@ -110,7 +105,7 @@ private static void testTimeout() throws InterruptedException {
}
- private static void testWaitNotify() throws Exception {
+ private void testWaitNotify() throws Exception {
Runnable simpleWaiter = () -> {
try {
helper.simpleNotify();
@@ -131,6 +126,7 @@ private static void testWaitNotify() throws Exception {
simpleNotifyThread = new Thread(simpleNotifier);
simpleWaitThread.start();
+
simpleWaitThread.join();
simpleNotifyThread.join();
}
@@ -141,7 +137,7 @@ public void test() throws Exception {
testWaitNotify();
}
- static class Helper {
+ private class Helper {
public synchronized void timeout() throws InterruptedException {
wait(MILLIS);
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
index 12d440eec701..5c6e64027120 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
@@ -25,22 +25,14 @@
package com.oracle.svm.test.jfr;
-import org.graalvm.word.WordFactory;
import org.junit.Test;
-import com.oracle.svm.core.jfr.HasJfrSupport;
-import com.oracle.svm.core.sampler.SamplerBuffer;
-import com.oracle.svm.core.sampler.SamplerBufferAccess;
-import com.oracle.svm.core.sampler.SamplerBuffersAccess;
-import com.oracle.svm.core.sampler.SamplerThreadLocal;
import com.oracle.svm.test.jfr.events.StackTraceEvent;
/**
* Test if event ({@link StackTraceEvent}) with stacktrace payload is working.
*/
public class TestStackTraceEvent extends JfrTest {
- private static final int LOCAL_BUFFER_SIZE = 1024;
-
@Override
public String[] getTestedEvents() {
return new String[]{
@@ -50,37 +42,11 @@ public String[] getTestedEvents() {
@Test
public void test() throws Exception {
- if (!HasJfrSupport.get()) {
- /*
- * The static analysis will find reachable the com.oracle.svm.core.jfr.SubstrateJVM via
- * processSamplerBuffer call. Since we are not supporting JFR on Windows yet, JfrFeature
- * will not add the SubstrateJVM to the list of all image singletons and therefore
- * InvocationPlugin will throw an exception while folding the SubstrateJVM (see
- * SubstrateJVM.get).
- *
- * Note that although we are building this JFR test for Windows as well, it will not be
- * executed because of guard in com.oracle.svm.test.jfr.JfrTest.checkForJFR.
- *
- * Once we have support for Windows, this check will become obsolete.
- */
- return;
- }
-
- /* Set thread-local buffer before stack walk. */
- SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(LOCAL_BUFFER_SIZE));
- SamplerThreadLocal.setThreadLocalBuffer(buffer);
-
/*
* Create and commit an event. This will trigger
* com.oracle.svm.core.jfr.JfrStackTraceRepository.getStackTraceId(int) call and stack walk.
*/
StackTraceEvent event = new StackTraceEvent();
event.commit();
-
- /* Call manually buffer processing. */
- SamplerBuffersAccess.processSamplerBuffer(buffer);
-
- /* We need to free memory manually as well afterward. */
- SamplerBufferAccess.free(buffer);
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
index 8585ff196f73..5bf127a9c185 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
@@ -26,9 +26,10 @@
package com.oracle.svm.test.jfr;
-import com.oracle.svm.test.jfr.events.StringEvent;
import org.junit.Test;
+import com.oracle.svm.test.jfr.events.StringEvent;
+
public class TestStringEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java
similarity index 81%
rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java
index e474bc1e6aeb..650886322d89 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java
@@ -26,37 +26,30 @@
package com.oracle.svm.test.jfr;
-import jdk.jfr.consumer.RecordedEvent;
-import jdk.jfr.consumer.RecordedObject;
-import jdk.jfr.consumer.RecordedThread;
+import static org.junit.Assert.assertTrue;
import java.io.IOException;
-import java.util.List;
-import static org.junit.Assert.assertTrue;
import org.junit.Test;
-public class TestThreadSleep extends JfrTest {
- private Thread sleepingThread;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestThreadSleepEvent extends JfrTest {
private static final int MILLIS = 50;
+ private Thread sleepingThread;
+
@Override
public String[] getTestedEvents() {
return new String[]{"jdk.ThreadSleep"};
}
@Override
- public void validateEvents() {
- List events;
- try {
- events = getEvents("jdk.ThreadSleep");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ public void validateEvents() throws IOException {
boolean foundSleepEvent = false;
- for (RecordedEvent event : events) {
- RecordedObject struct = event;
- String eventThread = struct. getValue("eventThread").getJavaName();
+ for (RecordedEvent event : getEvents()) {
+ String eventThread = event. getValue("eventThread").getJavaName();
if (!eventThread.equals(sleepingThread.getName())) {
continue;
}
From 11ebac62388cba22eed085421df2fa0d4106f447 Mon Sep 17 00:00:00 2001
From: Christian Haeubl
Date: Tue, 24 Jan 2023 16:47:53 +0100
Subject: [PATCH 17/21] Cleanups and fixes.
---
.../posix/PosixSubstrateSigprofHandler.java | 4 +--
.../svm/core/code/FrameInfoDecoder.java | 1 +
.../svm/core/code/FrameInfoQueryResult.java | 3 --
.../oracle/svm/core/jfr/JfrChunkWriter.java | 6 ++--
.../svm/core/jfr/JfrStackTraceRepository.java | 35 ++++++++++++-------
.../svm/core/jfr/JfrThreadRepository.java | 2 ++
.../com/oracle/svm/core/jfr/SubstrateJVM.java | 6 +---
.../oracle/svm/core/meta/SharedMethod.java | 2 ++
.../svm/core/sampler/SamplerSampleWriter.java | 22 ++++++------
.../core/sampler/SubstrateSigprofHandler.java | 19 ++++------
.../svm/core/stack/ThreadStackPrinter.java | 2 +-
.../oracle/svm/core/thread/JavaThreads.java | 1 +
.../svm/core/thread/PlatformThreads.java | 5 ---
.../svm/core/thread/ThreadingSupportImpl.java | 15 +++++---
.../svm/graal/meta/SubstrateMethod.java | 2 ++
15 files changed, 67 insertions(+), 58 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
index e5cefead86f8..2d06cea11a3c 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
@@ -143,14 +143,14 @@ protected void deleteNativeThreadLocal(UnsignedWord key) {
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected void setNativeThreadLocal(UnsignedWord key, IsolateThread value) {
+ protected void setNativeThreadLocalValue(UnsignedWord key, IsolateThread value) {
int resultCode = Pthread.pthread_setspecific((Pthread.pthread_key_t) key, (VoidPointer) value);
PosixUtils.checkStatusIs0(resultCode, "pthread_setspecific(key, value): wrong arguments.");
}
@Override
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected IsolateThread getNativeThreadLocal(UnsignedWord key) {
+ protected IsolateThread getNativeThreadLocalValue(UnsignedWord key) {
/*
* Although this method is not async-signal-safe in general we rely on
* implementation-specific behavior here.
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java
index 380f5fdaecdf..e9ce59574fe2 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoDecoder.java
@@ -411,6 +411,7 @@ private static FrameInfoQueryResult decodeUncompressedFrameInfo(boolean isDeoptE
if (deoptMethodIndex < 0) {
/* Negative number is a reference to the target method. */
cur.deoptMethod = (SharedMethod) NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoObjectConstants(info), -1 - deoptMethodIndex);
+ cur.deoptMethodOffset = cur.deoptMethod.getDeoptOffsetInImage();
} else {
/* Positive number is a directly encoded method offset. */
cur.deoptMethodOffset = deoptMethodIndex;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java
index 8f9bae6a46b8..50a8f784364b 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java
@@ -221,9 +221,6 @@ public SharedMethod getDeoptMethod() {
* that there is no inlining in target methods, so the method + BCI is unique.
*/
public int getDeoptMethodOffset() {
- if (deoptMethod != null) {
- return deoptMethod.getDeoptOffsetInImage();
- }
return deoptMethodOffset;
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
index fe04d21dc787..af75517d8212 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java
@@ -390,8 +390,9 @@ private void changeEpoch() {
processSamplerBuffers();
/*
- * Write unflushed data from the thread local buffers but do *not* reinitialize them.
- * The thread local code will handle space reclamation on their own time.
+ * Write unflushed data from the thread-local event buffers to the output file. We do
+ * *not* reinitialize the thread-local buffers as the individual threads will handle
+ * space reclamation on their own time.
*/
for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
JfrBuffer buffer = JfrThreadLocal.getJavaBuffer(thread);
@@ -412,6 +413,7 @@ private void changeEpoch() {
write(buffer);
JfrBufferAccess.reinitialize(buffer);
}
+
JfrTraceIdEpoch.getInstance().changeEpoch();
// Now that the epoch changed, re-register all running threads for the new epoch.
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
index 6c87bb225135..7f55cd496dac 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
@@ -123,19 +123,7 @@ public long getStackTraceId(int skipCount) {
CodePointer ip = FrameAccess.singleton().readReturnAddress(sp);
SamplerStackWalkVisitor visitor = ImageSingletons.lookup(SamplerStackWalkVisitor.class);
if (JavaStackWalker.walkCurrentThread(sp, ip, visitor) || data.getTruncated()) {
- /* Deduplicate and store the stack trace. */
- Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize());
- UnsignedWord size = data.getCurrentPos().subtract(start);
-
- CIntPointer statusPtr = StackValue.get(CIntPointer.class);
- JfrStackTraceTableEntry epochSpecificEntry = getOrPutStackTrace(start, size, data.getHashCode(), statusPtr);
- if (epochSpecificEntry.isNonNull()) {
- /* Only commit the data in the thread-local buffer if it is new data. */
- if (statusPtr.read() == JfrStackTraceTableEntryStatus.INSERTED) {
- SamplerSampleWriter.end(data, SamplerSampleWriter.JFR_STACK_TRACE_END);
- }
- return epochSpecificEntry.getId();
- }
+ return storeDeduplicatedStackTrace(data);
}
} finally {
JfrThreadLocal.setSamplerWriterData(WordFactory.nullPointer());
@@ -147,6 +135,27 @@ public long getStackTraceId(int skipCount) {
}
}
+ @Uninterruptible(reason = "Accesses a sampler buffer.")
+ private long storeDeduplicatedStackTrace(SamplerSampleWriterData data) {
+ if (SamplerSampleWriter.isValid(data)) {
+ /* There is a valid stack trace in the buffer, so deduplicate and store it. */
+ Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize());
+ UnsignedWord size = data.getCurrentPos().subtract(start);
+
+ CIntPointer statusPtr = StackValue.get(CIntPointer.class);
+ JfrStackTraceTableEntry epochSpecificEntry = getOrPutStackTrace(start, size, data.getHashCode(), statusPtr);
+ if (epochSpecificEntry.isNonNull()) {
+ /* Only commit the data in the thread-local buffer if it is new data. */
+ if (statusPtr.read() == JfrStackTraceTableEntryStatus.INSERTED) {
+ boolean success = SamplerSampleWriter.end(data, SamplerSampleWriter.JFR_STACK_TRACE_END);
+ assert success : "must succeed because data was valid earlier";
+ }
+ return epochSpecificEntry.getId();
+ }
+ }
+ return 0L;
+ }
+
/**
* If the same stack trace already exists in the repository, then this method returns the
* matching {@link JfrStackTraceTableEntry entry}. Otherwise, it tries to add the stack trace to
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
index fe7157a67d80..a39c628173fe 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
@@ -88,6 +88,8 @@ public void registerThread(Thread thread) {
@Uninterruptible(reason = "Epoch must not change while in this method.")
private void registerThread0(Thread thread) {
+ assert SubstrateJVM.get().isRecording();
+
JfrThreadEpochData epochData = getEpochData(false);
if (epochData.threadBuffer.isNull()) {
// This will happen only on the first call.
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
index 61f58d35c71c..4c3fcfe5a4a8 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
@@ -202,13 +202,9 @@ public static Object getHandler(Class extends jdk.internal.event.Event> eventC
}
}
- public static boolean isInitialized() {
- return get().initialized;
- }
-
@Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true)
protected boolean isRecording() {
- return get().recording;
+ return recording;
}
/**
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedMethod.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedMethod.java
index 4a4e7a639900..bb92b699b0ed 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedMethod.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedMethod.java
@@ -24,6 +24,7 @@
*/
package com.oracle.svm.core.meta;
+import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.deopt.Deoptimizer;
import com.oracle.svm.core.graal.code.ExplicitCallingConvention;
import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind;
@@ -72,6 +73,7 @@ default SubstrateCallingConventionKind getCallingConventionKind() {
int getCodeOffsetInImage();
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
int getDeoptOffsetInImage();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
index ff16008c01fc..8ea94d09c187 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
@@ -74,16 +74,14 @@ public static void begin(SamplerSampleWriterData data) {
}
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
- public static void end(SamplerSampleWriterData data, long endMarker) {
- assert isValid(data);
+ public static boolean end(SamplerSampleWriterData data, long endMarker) {
+ if (!isValid(data)) {
+ return false;
+ }
UnsignedWord sampleSize = getSampleSize(data);
- /*
- * Put end marker.
- *
- * We need to distinguish between end of a sample and end of a regular stack walk (see
- * com.oracle.svm.core.sampler.SamplerBuffersAccess.processSamplerBuffer)
- */
+
+ /* ensureSize() guarantees that there is enough space for the end marker. */
putUncheckedLong(data, endMarker);
/* Patch data at start of entry. */
@@ -94,8 +92,8 @@ public static void end(SamplerSampleWriterData data, long endMarker) {
putUncheckedInt(data, (int) sampleSize.rawValue());
data.setCurrentPos(currentPos);
- assert getUncommittedSize(data).unsignedRemainder(Long.BYTES).equal(0);
commit(data);
+ return true;
}
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
@@ -136,8 +134,10 @@ private static void putUncheckedInt(SamplerSampleWriterData data, int value) {
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
private static void commit(SamplerSampleWriterData data) {
- SamplerBuffer buffer = data.getSamplerBuffer();
assert isValid(data);
+ assert getUncommittedSize(data).unsignedRemainder(Long.BYTES).equal(0);
+
+ SamplerBuffer buffer = data.getSamplerBuffer();
assert buffer.getPos().equal(data.getStartPos());
assert SamplerBufferAccess.getDataEnd(data.getSamplerBuffer()).equal(data.getEndPos());
@@ -205,7 +205,7 @@ private static void reset(SamplerSampleWriterData data) {
}
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
- private static boolean isValid(SamplerSampleWriterData data) {
+ public static boolean isValid(SamplerSampleWriterData data) {
return data.getEndPos().isNonNull();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
index be6102924765..6a3ce192bc00 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java
@@ -60,7 +60,7 @@
* and the isolate-thread, before preparing everything that is needed for a stack walk.
*/
public abstract class SubstrateSigprofHandler extends AbstractJfrExecutionSampler implements IsolateListener, ThreadListener {
- private static final CGlobalData signalHandlerIsolate = CGlobalDataFactory.createWord();
+ private static final CGlobalData SIGNAL_HANDLER_ISOLATE = CGlobalDataFactory.createWord();
private UnsignedWord keyForNativeThreadLocal;
@Platforms(Platform.HOSTED_ONLY.class)
@@ -72,11 +72,6 @@ public static SubstrateSigprofHandler singleton() {
return ImageSingletons.lookup(SubstrateSigprofHandler.class);
}
- @Fold
- public static boolean isSupported() {
- return ImageSingletons.contains(SubstrateSigprofHandler.class);
- }
-
@Override
@Uninterruptible(reason = "Thread state not set up yet.")
public void afterCreateIsolate(Isolate isolate) {
@@ -131,12 +126,12 @@ protected void stopSampling() {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static Isolate getSignalHandlerIsolate() {
- return signalHandlerIsolate.get().readWord(0);
+ return SIGNAL_HANDLER_ISOLATE.get().readWord(0);
}
private static void setSignalHandlerIsolate(Isolate isolate) {
assert getSignalHandlerIsolate().isNull() || isolate.isNull();
- signalHandlerIsolate.get().writeWord(0, isolate);
+ SIGNAL_HANDLER_ISOLATE.get().writeWord(0, isolate);
}
@Override
@@ -187,10 +182,10 @@ private static void uninstall(IsolateThread thread) {
protected abstract void deleteNativeThreadLocal(UnsignedWord key);
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected abstract void setNativeThreadLocal(UnsignedWord key, IsolateThread value);
+ protected abstract void setNativeThreadLocalValue(UnsignedWord key, IsolateThread value);
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- protected abstract IsolateThread getNativeThreadLocal(UnsignedWord key);
+ protected abstract IsolateThread getNativeThreadLocalValue(UnsignedWord key);
/**
* Called from the platform dependent sigprof handler to enter isolate.
@@ -211,7 +206,7 @@ protected static boolean tryEnterIsolate() {
/* We are keeping reference to isolate thread inside OS thread local area. */
if (SubstrateOptions.MultiThreaded.getValue()) {
UnsignedWord key = singleton().keyForNativeThreadLocal;
- IsolateThread thread = singleton().getNativeThreadLocal(key);
+ IsolateThread thread = singleton().getNativeThreadLocalValue(key);
if (thread.isNull()) {
/* Thread is not yet initialized or already detached from isolate. */
return false;
@@ -225,6 +220,6 @@ protected static boolean tryEnterIsolate() {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private void storeIsolateThreadInNativeThreadLocal(IsolateThread isolateThread) {
- setNativeThreadLocal(keyForNativeThreadLocal, isolateThread);
+ setNativeThreadLocalValue(keyForNativeThreadLocal, isolateThread);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java
index faef0e1498cb..cd4e0a881aa4 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/ThreadStackPrinter.java
@@ -77,7 +77,7 @@ protected void logFrame(Log log, Pointer sp, CodePointer ip, CodeInfo codeInfo,
}
if (isFirst) {
- /* No information was printed, so try to print less detailed information. */
+ /* The code above failed, so print less detailed information. */
super.logFrame(log, sp, ip, codeInfo, deoptFrame);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
index b42a92f1f5ac..31ba8cadaa46 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java
@@ -441,6 +441,7 @@ public static long getCurrentThreadIdOrZero() {
}
}
+/* GR-43733: this class can be removed when we drop the JDK 17 support. */
@TargetClass(className = "jdk.internal.event.ThreadSleepEvent", onlyWith = JDK19OrLater.class)
final class Target_jdk_internal_event_ThreadSleepEvent {
@Alias public long time;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java
index 71490c1c17f2..fc075b7bdac3 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java
@@ -1021,11 +1021,6 @@ static boolean isAlive(Thread thread) {
return !(threadStatus == ThreadStatus.NEW || threadStatus == ThreadStatus.TERMINATED);
}
- @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
- static boolean isTerminated(Thread thread) {
- return getThreadStatus(thread) == ThreadStatus.TERMINATED;
- }
-
private static ThreadData acquireThreadData(Thread thread) {
return toTarget(thread).threadData.acquire();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java
index 4db3737c02dd..a69302bc33b8 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java
@@ -242,7 +242,6 @@ private void invokeCallback() {
@Override
public void registerRecurringCallback(long interval, TimeUnit unit, RecurringCallback callback) {
IsolateThread thread = CurrentIsolate.getCurrentThread();
- removeRecurringCallback(thread);
if (callback != null) {
if (!SupportRecurringCallback.getValue()) {
VMError.shouldNotReachHere("Recurring callbacks must be enabled during image build with option " + enableSupportOption);
@@ -255,7 +254,9 @@ public void registerRecurringCallback(long interval, TimeUnit unit, RecurringCal
}
RecurringCallbackTimer timer = createRecurringCallbackTimer(intervalNanos, callback);
- setRecurringCallback(thread, timer);
+ registerRecurringCallback0(thread, timer);
+ } else {
+ removeRecurringCallback(thread);
}
}
@@ -264,7 +265,13 @@ public static RecurringCallbackTimer createRecurringCallbackTimer(long intervalN
return new RecurringCallbackTimer(intervalNanos, callback);
}
- @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true)
+ @Uninterruptible(reason = "Prevent VM operations that modify the recurring callbacks.")
+ private static void registerRecurringCallback0(IsolateThread thread, RecurringCallbackTimer timer) {
+ removeRecurringCallback(thread);
+ setRecurringCallback(thread, timer);
+ }
+
+ @Uninterruptible(reason = "Prevent VM operations that modify the recurring callbacks.")
public static void setRecurringCallback(IsolateThread thread, RecurringCallbackTimer timer) {
assert SupportRecurringCallback.getValue() && MultiThreaded.getValue();
assert timer.targetIntervalNanos > 0;
@@ -274,7 +281,7 @@ public static void setRecurringCallback(IsolateThread thread, RecurringCallbackT
Safepoint.setSafepointRequested(thread, timer.requestedChecks);
}
- @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true)
+ @Uninterruptible(reason = "Prevent VM operations that modify the recurring callbacks.", callerMustBe = true)
public static RecurringCallback getRecurringCallback(IsolateThread thread) {
assert thread == CurrentIsolate.getCurrentThread() || VMOperation.isInProgressAtSafepoint();
diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
index 342c0d680a7b..60618ca793af 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
@@ -32,6 +32,7 @@
import java.lang.reflect.Type;
import java.util.Arrays;
+import com.oracle.svm.core.Uninterruptible;
import org.graalvm.compiler.core.common.util.TypeConversion;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
@@ -184,6 +185,7 @@ public int getCodeOffsetInImage() {
}
@Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public int getDeoptOffsetInImage() {
return deoptOffsetInImage;
}
From 3fe61b61110fb25fd15f0cb914df307caff67034 Mon Sep 17 00:00:00 2001
From: Christian Haeubl
Date: Tue, 24 Jan 2023 18:23:33 +0100
Subject: [PATCH 18/21] Further fixes and cleanups.
---
.../posix/PosixSubstrateSigprofHandler.java | 8 +--
.../svm/core/SubstrateSegfaultHandler.java | 9 +++
.../oracle/svm/core/code/CodeInfoDecoder.java | 7 ++-
.../core/graal/meta/SharedRuntimeMethod.java | 5 ++
.../oracle/svm/core/jfr/JfrBufferAccess.java | 26 ++++++++-
.../oracle/svm/core/jfr/JfrGlobalMemory.java | 3 +
.../svm/core/jfr/JfrNativeEventWriter.java | 2 +-
.../jfr/JfrNativeEventWriterDataAccess.java | 35 ++++++++++--
.../svm/core/jfr/JfrStackTraceRepository.java | 8 ++-
.../svm/core/jfr/JfrThreadRepository.java | 2 -
.../svm/core/sampler/SamplerBufferAccess.java | 15 +++++
.../core/sampler/SamplerBuffersAccess.java | 55 ++++++++++---------
.../svm/core/sampler/SamplerSampleWriter.java | 14 +++--
.../SamplerSampleWriterDataAccess.java | 19 ++++++-
.../core/sampler/SamplerStackWalkVisitor.java | 32 +++++++----
15 files changed, 178 insertions(+), 62 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
index 2d06cea11a3c..e3e6f6708e2f 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
@@ -27,7 +27,6 @@
import static com.oracle.svm.core.posix.PosixSubstrateSigprofHandler.Options.SignalHandlerBasedExecutionSampler;
-import java.util.Collections;
import java.util.List;
import org.graalvm.compiler.options.Option;
@@ -47,6 +46,7 @@
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.IsolateListenerSupport;
+import com.oracle.svm.core.IsolateListenerSupportFeature;
import com.oracle.svm.core.RegisterDumper;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.c.function.CEntryPointOptions;
@@ -106,9 +106,9 @@ protected void updateInterval() {
private static void updateInterval(long ms) {
Time.itimerval newValue = UnsafeStackValue.get(Time.itimerval.class);
newValue.it_value().set_tv_sec(ms / TimeUtils.millisPerSecond);
- newValue.it_value().set_tv_usec(ms % TimeUtils.millisPerSecond);
+ newValue.it_value().set_tv_usec((ms % TimeUtils.millisPerSecond) * 1000);
newValue.it_interval().set_tv_sec(ms / TimeUtils.millisPerSecond);
- newValue.it_interval().set_tv_usec(ms % TimeUtils.millisPerSecond);
+ newValue.it_interval().set_tv_usec((ms % TimeUtils.millisPerSecond) * 1000);
int status = Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, WordFactory.nullPointer());
PosixUtils.checkStatusIs0(status, "setitimer(which, newValue, oldValue): wrong arguments.");
@@ -168,7 +168,7 @@ public static class Options {
class PosixSubstrateSigProfHandlerFeature implements InternalFeature {
@Override
public List> getRequiredFeatures() {
- return Collections.singletonList(JfrFeature.class);
+ return List.of(IsolateListenerSupportFeature.class, JfrFeature.class);
}
@Override
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
index 6f1aac00256c..dc6811db4af2 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java
@@ -35,6 +35,7 @@
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.LogHandler;
import org.graalvm.nativeimage.c.function.CodePointer;
+import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.word.LocationIdentity;
import org.graalvm.word.Pointer;
import org.graalvm.word.PointerBase;
@@ -59,8 +60,16 @@
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.thread.VMThreads.SafepointBehavior;
+import java.util.Collections;
+import java.util.List;
+
@AutomaticallyRegisteredFeature
class SubstrateSegfaultHandlerFeature implements InternalFeature {
+ @Override
+ public List> getRequiredFeatures() {
+ return Collections.singletonList(IsolateListenerSupportFeature.class);
+ }
+
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
if (!ImageSingletons.contains(SubstrateSegfaultHandler.class)) {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
index 5adc33766eaf..81bb90ae88f0 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
@@ -588,11 +588,12 @@ private boolean initFrameInfoReader(CodePointer ip) {
long entryOffset = lookupCodeInfoEntryOffset(info, CodeInfoAccess.relativeIP(info, ip));
if (entryOffset >= 0) {
int entryFlags = loadEntryFlags(info, entryOffset);
- int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags));
- frameInfoReader.setByteIndex(frameInfoIndex);
- frameInfoReader.setData(CodeInfoAccess.getFrameInfoEncodings(info));
if (extractFI(entryFlags) == FI_NO_DEOPT) {
entryOffset = -1;
+ } else {
+ int frameInfoIndex = NonmovableByteArrayReader.getS4(CodeInfoAccess.getCodeInfoEncodings(info), offsetFI(entryOffset, entryFlags));
+ frameInfoReader.setByteIndex(frameInfoIndex);
+ frameInfoReader.setData(CodeInfoAccess.getFrameInfoEncodings(info));
}
}
state.entryOffset = entryOffset;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SharedRuntimeMethod.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SharedRuntimeMethod.java
index 62746033b9b6..9ddb3d2d49e1 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SharedRuntimeMethod.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SharedRuntimeMethod.java
@@ -24,6 +24,7 @@
*/
package com.oracle.svm.core.graal.meta;
+import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.meta.SharedMethod;
/**
@@ -36,4 +37,8 @@ public interface SharedRuntimeMethod extends SharedMethod {
default SharedRuntimeMethod getOriginal() {
return this;
}
+
+ @Override
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ int getDeoptOffsetInImage();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java
index 3321e245aa99..e115d1cc1e65 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrBufferAccess.java
@@ -77,6 +77,7 @@ public static void free(JfrBuffer buffer) {
@Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.")
public static void reinitialize(JfrBuffer buffer) {
+ assert buffer.isNonNull();
Pointer pos = getDataStart(buffer);
buffer.setPos(pos);
buffer.setTop(pos);
@@ -84,57 +85,80 @@ public static void reinitialize(JfrBuffer buffer) {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static boolean isAcquired(JfrBuffer buffer) {
+ assert buffer.isNonNull();
return buffer.getAcquired() == ACQUIRED;
}
@Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true)
public static boolean acquire(JfrBuffer buffer) {
+ assert buffer.isNonNull();
return ((Pointer) buffer).logicCompareAndSwapInt(JfrBuffer.offsetOfAcquired(), NOT_ACQUIRED, ACQUIRED, NamedLocationIdentity.OFF_HEAP_LOCATION);
}
@Uninterruptible(reason = "We must guarantee that all buffers are in unacquired state when entering a safepoint.", callerMustBe = true)
public static void release(JfrBuffer buffer) {
- assert buffer.getAcquired() == ACQUIRED;
+ assert buffer.isNonNull() && buffer.getAcquired() == ACQUIRED;
buffer.setAcquired(NOT_ACQUIRED);
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static Pointer getAddressOfPos(JfrBuffer buffer) {
+ assert buffer.isNonNull();
return ((Pointer) buffer).add(JfrBuffer.offsetOfPos());
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static Pointer getDataStart(JfrBuffer buffer) {
+ assert buffer.isNonNull();
return ((Pointer) buffer).add(getHeaderSize());
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static Pointer getDataEnd(JfrBuffer buffer) {
+ assert buffer.isNonNull();
return getDataStart(buffer).add(buffer.getSize());
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static UnsignedWord getAvailableSize(JfrBuffer buffer) {
+ assert buffer.isNonNull();
return getDataEnd(buffer).subtract(buffer.getPos());
}
@Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.", callerMustBe = true)
public static UnsignedWord getUnflushedSize(JfrBuffer buffer) {
+ assert buffer.isNonNull();
return buffer.getPos().subtract(buffer.getTop());
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static void increasePos(JfrBuffer buffer, UnsignedWord delta) {
+ assert buffer.isNonNull();
buffer.setPos(buffer.getPos().add(delta));
}
@Uninterruptible(reason = "Prevent safepoints as those could change the top pointer.")
public static void increaseTop(JfrBuffer buffer, UnsignedWord delta) {
+ assert buffer.isNonNull();
buffer.setTop(buffer.getTop().add(delta));
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static boolean isEmpty(JfrBuffer buffer) {
+ assert buffer.isNonNull();
return getDataStart(buffer).equal(buffer.getPos());
}
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static boolean verify(JfrBuffer buffer) {
+ if (buffer.isNull()) {
+ return false;
+ }
+
+ Pointer start = getDataStart(buffer);
+ Pointer end = getDataEnd(buffer);
+ return buffer.getPos().aboveOrEqual(start) && buffer.getPos().belowOrEqual(end) &&
+ buffer.getTop().aboveOrEqual(start) && buffer.getTop().belowOrEqual(end) &&
+ buffer.getTop().belowOrEqual(buffer.getPos());
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java
index 8acc38045923..6f5e1b4e9f98 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrGlobalMemory.java
@@ -58,6 +58,9 @@ public void initialize(long globalBufferSize, long globalBufferCount) {
buffers = UnmanagedMemory.calloc(SizeOf.unsigned(JfrBuffers.class).multiply(WordFactory.unsigned(bufferCount)));
for (int i = 0; i < bufferCount; i++) {
JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(bufferSize), JfrBufferType.GLOBAL_MEMORY);
+ if (buffer.isNull()) {
+ throw new OutOfMemoryError("Could not allocate JFR buffer.");
+ }
buffers.addressOf(i).write(buffer);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
index 0270321df9a5..37300bb68cb0 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
@@ -81,7 +81,7 @@ public static void beginSmallEvent(JfrNativeEventWriterData data, JfrEvent event
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
public static void beginEvent(JfrNativeEventWriterData data, JfrEvent event, boolean large) {
assert SubstrateJVM.get().isRecording();
- assert isValid(data);
+ assert JfrNativeEventWriterDataAccess.verify(data) || !isValid(data);
assert getUncommittedSize(data).equal(0);
if (large) {
reserve(data, Integer.BYTES);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java
index 8197d2730632..a9600515faa3 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterDataAccess.java
@@ -25,6 +25,8 @@
package com.oracle.svm.core.jfr;
import com.oracle.svm.core.Uninterruptible;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.WordFactory;
/**
* Helper class that holds methods related to {@link JfrNativeEventWriterData}.
@@ -39,12 +41,18 @@ private JfrNativeEventWriterDataAccess() {
*/
@Uninterruptible(reason = "Accesses a JFR buffer", callerMustBe = true)
public static void initialize(JfrNativeEventWriterData data, JfrBuffer buffer) {
- assert buffer.isNonNull();
-
- data.setJfrBuffer(buffer);
- data.setStartPos(buffer.getPos());
- data.setCurrentPos(buffer.getPos());
- data.setEndPos(JfrBufferAccess.getDataEnd(buffer));
+ if (buffer.isNonNull()) {
+ assert JfrBufferAccess.verify(buffer);
+ data.setJfrBuffer(buffer);
+ data.setStartPos(buffer.getPos());
+ data.setCurrentPos(buffer.getPos());
+ data.setEndPos(JfrBufferAccess.getDataEnd(buffer));
+ } else {
+ data.setJfrBuffer(WordFactory.nullPointer());
+ data.setStartPos(WordFactory.nullPointer());
+ data.setCurrentPos(WordFactory.nullPointer());
+ data.setEndPos(WordFactory.nullPointer());
+ }
}
/**
@@ -57,4 +65,19 @@ public static void initializeThreadLocalNativeBuffer(JfrNativeEventWriterData da
JfrBuffer nativeBuffer = jfrThreadLocal.getNativeBuffer();
initialize(data, nativeBuffer);
}
+
+ @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
+ public static boolean verify(JfrNativeEventWriterData data) {
+ if (data.isNull() || !JfrBufferAccess.verify(data.getJfrBuffer())) {
+ return false;
+ }
+
+ JfrBuffer buffer = data.getJfrBuffer();
+ Pointer dataStart = JfrBufferAccess.getDataStart(buffer);
+ Pointer dataEnd = JfrBufferAccess.getDataEnd(buffer);
+
+ return data.getStartPos() == buffer.getPos() &&
+ (data.getEndPos() == dataEnd || data.getEndPos().isNull()) &&
+ data.getCurrentPos().aboveOrEqual(dataStart) && data.getCurrentPos().belowOrEqual(dataEnd) && data.getCurrentPos().aboveOrEqual(data.getStartPos());
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
index 7f55cd496dac..3b9143ce2a47 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java
@@ -258,8 +258,7 @@ private JfrStackTraceEpochData getEpochData(boolean previousEpoch) {
/**
* NOTE: the returned value is only valid until the JFR epoch changes. So, this method may only
- * be used from uninterruptible code. This method may return null if a new buffer needs to be
- * allocated and the allocation fails.
+ * be called from uninterruptible code. Returns null if the buffer allocation failed.
*/
@Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true)
public JfrBuffer getCurrentBuffer() {
@@ -270,6 +269,11 @@ public JfrBuffer getCurrentBuffer() {
return epochData.stackTraceBuffer;
}
+ @Uninterruptible(reason = "Prevent epoch change.", callerMustBe = true)
+ public void setCurrentBuffer(JfrBuffer value) {
+ getEpochData(false).stackTraceBuffer = value;
+ }
+
/**
* Each entry contains raw stack trace data (i.e., a sequence of instruction pointers, without
* any metadata).
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
index a39c628173fe..fe7157a67d80 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java
@@ -88,8 +88,6 @@ public void registerThread(Thread thread) {
@Uninterruptible(reason = "Epoch must not change while in this method.")
private void registerThread0(Thread thread) {
- assert SubstrateJVM.get().isRecording();
-
JfrThreadEpochData epochData = getEpochData(false);
if (epochData.threadBuffer.isNull()) {
// This will happen only on the first call.
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
index fc7ce26e9e45..2eb2fae90407 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java
@@ -50,22 +50,37 @@ public static UnsignedWord getHeaderSize() {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static void reinitialize(SamplerBuffer buffer) {
+ assert buffer.isNonNull();
Pointer dataStart = getDataStart(buffer);
buffer.setPos(dataStart);
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static Pointer getDataStart(SamplerBuffer buffer) {
+ assert buffer.isNonNull();
return ((Pointer) buffer).add(getHeaderSize());
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static boolean isEmpty(SamplerBuffer buffer) {
+ assert buffer.isNonNull();
return getDataStart(buffer).equal(buffer.getPos());
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static Pointer getDataEnd(SamplerBuffer buffer) {
+ assert buffer.isNonNull();
return getDataStart(buffer).add(buffer.getSize());
}
+
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+ public static boolean verify(SamplerBuffer buffer) {
+ if (buffer.isNull()) {
+ return false;
+ }
+
+ Pointer start = getDataStart(buffer);
+ Pointer end = getDataEnd(buffer);
+ return buffer.getPos().aboveOrEqual(start) && buffer.getPos().belowOrEqual(end);
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
index 9620f3ecc707..15bdcb3686c0 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
@@ -67,17 +67,11 @@ private SamplerBuffersAccess() {
public static void processActiveBuffers() {
assert VMOperation.isInProgressAtSafepoint();
- JfrBuffer targetBuffer = SubstrateJVM.getStackTraceRepo().getCurrentBuffer();
- if (targetBuffer.isNull()) {
- /* Buffer allocation failed, so don't process any data. */
- return;
- }
-
for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
- SamplerBuffer b = JfrThreadLocal.getSamplerBuffer(thread);
- if (b.isNonNull()) {
- serializeStackTraces(b, targetBuffer);
- assert JfrThreadLocal.getSamplerBuffer(thread) == b;
+ SamplerBuffer buffer = JfrThreadLocal.getSamplerBuffer(thread);
+ if (buffer.isNonNull()) {
+ serializeStackTraces(buffer);
+ assert JfrThreadLocal.getSamplerBuffer(thread) == buffer;
}
}
}
@@ -93,19 +87,14 @@ public static void processActiveBuffers() {
*/
@Uninterruptible(reason = "Prevent JFR recording and epoch change.")
public static void processFullBuffers(boolean useSafepointChecks) {
- JfrBuffer targetBuffer = SubstrateJVM.getStackTraceRepo().getCurrentBuffer();
- if (targetBuffer.isNull()) {
- /* Buffer allocation failed, so don't process any data. */
- return;
- }
-
while (true) {
SamplerBuffer buffer = SubstrateJVM.getSamplerBufferPool().popFullBuffer();
if (buffer.isNull()) {
+ /* No more buffers. */
break;
}
- serializeStackTraces(buffer, targetBuffer);
+ serializeStackTraces(buffer);
SubstrateJVM.getSamplerBufferPool().releaseBuffer(buffer);
/* Do a safepoint check if the caller requested one. */
@@ -126,9 +115,8 @@ private static void safepointCheck0() {
}
@Uninterruptible(reason = "Prevent JFR recording and epoch change.")
- private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer, JfrBuffer targetBuffer) {
+ private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer) {
assert rawStackTraceBuffer.isNonNull();
- assert targetBuffer.isNonNull();
Pointer end = rawStackTraceBuffer.getPos();
Pointer current = SamplerBufferAccess.getDataStart(rawStackTraceBuffer);
@@ -167,14 +155,14 @@ private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer, JfrB
CIntPointer statusPtr = StackValue.get(CIntPointer.class);
JfrStackTraceTableEntry entry = SubstrateJVM.getStackTraceRepo().getOrPutStackTrace(current, WordFactory.unsigned(sampleSize), sampleHash, statusPtr);
- long stackTraceId = entry.getId();
+ long stackTraceId = entry.isNull() ? 0 : entry.getId();
int status = statusPtr.read();
if (status == JfrStackTraceTableEntryStatus.INSERTED || status == JfrStackTraceTableEntryStatus.EXISTING_RAW) {
/* Walk the IPs and serialize the stacktrace. */
assert current.add(sampleSize).belowThan(end);
- boolean success = serializeStackTrace(targetBuffer, current, sampleSize, isTruncated, stackTraceId);
- if (success) {
+ boolean serialized = serializeStackTrace(current, sampleSize, isTruncated, stackTraceId);
+ if (serialized) {
SubstrateJVM.getStackTraceRepo().commitSerializedStackTrace(entry);
}
} else {
@@ -188,8 +176,12 @@ private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer, JfrB
* done here because the sampler can't emit the event directly.
*/
long endMarker = current.readLong(0);
- if (endMarker == SamplerSampleWriter.EXECUTION_SAMPLE_END && status != JfrStackTraceTableEntryStatus.INSERT_FAILED) {
- ExecutionSampleEvent.writeExecutionSample(sampleTick, threadId, stackTraceId, threadState);
+ if (endMarker == SamplerSampleWriter.EXECUTION_SAMPLE_END) {
+ if (stackTraceId != 0) {
+ ExecutionSampleEvent.writeExecutionSample(sampleTick, threadId, stackTraceId, threadState);
+ } else {
+ JfrThreadLocal.increaseMissedSamples();
+ }
} else {
assert endMarker == SamplerSampleWriter.JFR_STACK_TRACE_END;
}
@@ -200,15 +192,21 @@ private static void serializeStackTraces(SamplerBuffer rawStackTraceBuffer, JfrB
}
@Uninterruptible(reason = "Prevent JFR recording and epoch change.")
- private static boolean serializeStackTrace(JfrBuffer targetBuffer, Pointer rawStackTrace, int sampleSize, boolean isTruncated, long stackTraceId) {
+ private static boolean serializeStackTrace(Pointer rawStackTrace, int sampleSize, boolean isTruncated, long stackTraceId) {
assert sampleSize % Long.BYTES == 0;
+ JfrBuffer targetBuffer = SubstrateJVM.getStackTraceRepo().getCurrentBuffer();
+ if (targetBuffer.isNull()) {
+ return false;
+ }
+
/*
* One IP may correspond to multiple Java-level stack frames. We need to precompute the
* number of stack trace elements because the count can't be patched later on
* (JfrNativeEventWriter.putInt() would not necessarily reserve enough bytes).
*/
int numStackTraceElements = visitRawStackTrace(rawStackTrace, sampleSize, WordFactory.nullPointer());
+ assert numStackTraceElements > 0;
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initialize(data, targetBuffer);
@@ -216,8 +214,11 @@ private static boolean serializeStackTrace(JfrBuffer targetBuffer, Pointer rawSt
JfrNativeEventWriter.putBoolean(data, isTruncated);
JfrNativeEventWriter.putInt(data, numStackTraceElements);
visitRawStackTrace(rawStackTrace, sampleSize, data);
- JfrNativeEventWriter.commit(data);
- return true;
+ boolean success = JfrNativeEventWriter.commit(data);
+
+ /* Buffer can get replaced with a larger one. */
+ SubstrateJVM.getStackTraceRepo().setCurrentBuffer(data.getJfrBuffer());
+ return success;
}
@Uninterruptible(reason = "Prevent JFR recording and epoch change.")
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
index 8ea94d09c187..ee943b4156fb 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
@@ -52,7 +52,7 @@ public static int getHeaderSize() {
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
public static void begin(SamplerSampleWriterData data) {
- assert isValid(data);
+ assert SamplerSampleWriterDataAccess.verify(data);
assert getUncommittedSize(data).equal(0);
assert data.getCurrentPos().unsignedRemainder(Long.BYTES).equal(0);
@@ -70,7 +70,7 @@ public static void begin(SamplerSampleWriterData data) {
SamplerSampleWriter.putLong(data, JfrThreadState.getId(Thread.State.RUNNABLE));
assert getHeaderSize() % Long.BYTES == 0;
- assert getUncommittedSize(data).equal(getHeaderSize());
+ assert !isValid(data) || getUncommittedSize(data).equal(getHeaderSize());
}
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
@@ -169,14 +169,14 @@ private static boolean accommodate(SamplerSampleWriterData data, UnsignedWord un
* Sample is too big to fit into the size of one buffer i.e. we want to do
* accommodations while nothing is committed into buffer.
*/
- JfrThreadLocal.increaseMissedSamples();
+ cancel(data);
return false;
}
/* Try to get a buffer. */
SamplerBuffer newBuffer = SubstrateJVM.getSamplerBufferPool().acquireBuffer(data.getAllowBufferAllocation());
if (newBuffer.isNull()) {
- JfrThreadLocal.increaseMissedSamples();
+ cancel(data);
return false;
}
JfrThreadLocal.setSamplerBuffer(newBuffer);
@@ -204,6 +204,12 @@ private static void reset(SamplerSampleWriterData data) {
data.setEndPos(SamplerBufferAccess.getDataEnd(buffer));
}
+ @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
+ private static void cancel(SamplerSampleWriterData data) {
+ data.setEndPos(WordFactory.nullPointer());
+ JfrThreadLocal.increaseMissedSamples();
+ }
+
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
public static boolean isValid(SamplerSampleWriterData data) {
return data.getEndPos().isNonNull();
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
index 6669e9c24d14..4472c10d1dca 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java
@@ -25,6 +25,8 @@
package com.oracle.svm.core.sampler;
+import org.graalvm.word.Pointer;
+
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.jfr.JfrThreadLocal;
import com.oracle.svm.core.jfr.SubstrateJVM;
@@ -62,7 +64,7 @@ public static boolean initialize(SamplerSampleWriterData data, int skipCount, bo
*/
@Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true)
private static void initialize0(SamplerSampleWriterData data, SamplerBuffer buffer, int skipCount, int maxDepth, boolean allowBufferAllocation) {
- assert buffer.isNonNull();
+ assert SamplerBufferAccess.verify(buffer);
data.setSamplerBuffer(buffer);
data.setStartPos(buffer.getPos());
@@ -75,4 +77,19 @@ private static void initialize0(SamplerSampleWriterData data, SamplerBuffer buff
data.setNumFrames(0);
data.setAllowBufferAllocation(allowBufferAllocation);
}
+
+ @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
+ public static boolean verify(SamplerSampleWriterData data) {
+ if (data.isNull() || !SamplerBufferAccess.verify(data.getSamplerBuffer())) {
+ return false;
+ }
+
+ SamplerBuffer buffer = data.getSamplerBuffer();
+ Pointer dataStart = SamplerBufferAccess.getDataStart(buffer);
+ Pointer dataEnd = SamplerBufferAccess.getDataEnd(buffer);
+
+ return data.getStartPos() == buffer.getPos() &&
+ (data.getEndPos() == dataEnd || data.getEndPos().isNull()) &&
+ data.getCurrentPos().aboveOrEqual(dataStart) && data.getCurrentPos().belowOrEqual(dataEnd) && data.getCurrentPos().aboveOrEqual(data.getStartPos());
+ }
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java
index 2f6e2e0210c8..c77301540152 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java
@@ -45,21 +45,13 @@ public SamplerStackWalkVisitor() {
@Override
@Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame, Object data) {
- SamplerSampleWriterData writerData = JfrThreadLocal.getSamplerWriterData();
- assert writerData.isNonNull();
-
- boolean shouldSkipFrame = shouldSkipFrame(writerData);
- boolean shouldContinueWalk = shouldContinueWalk(writerData);
- if (!shouldSkipFrame && shouldContinueWalk) {
- writerData.setHashCode(computeHash(writerData.getHashCode(), ip.rawValue()));
- shouldContinueWalk = SamplerSampleWriter.putLong(writerData, ip.rawValue());
- }
- return shouldContinueWalk;
+ return recordIp(ip);
}
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
private static boolean shouldContinueWalk(SamplerSampleWriterData data) {
- if (data.getNumFrames() >= data.getMaxDepth()) {
+ int numFrames = data.getNumFrames() - data.getSkipCount();
+ if (numFrames > data.getMaxDepth()) {
/* The stack size exceeds given depth. Stop walk! */
data.setTruncated(true);
return false;
@@ -83,7 +75,25 @@ private static int computeHash(int oldHash, long ip) {
@Override
@Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
protected boolean unknownFrame(Pointer sp, CodePointer ip, DeoptimizedFrame deoptimizedFrame, Object data) {
+ /*
+ * The SIGPROF-based sampler may interrupt at any arbitrary code location. The stack
+ * information that we currently have is not always good enough to do a reliable stack walk.
+ */
JfrThreadLocal.increaseUnparseableStacks();
return false;
}
+
+ @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true)
+ private static boolean recordIp(CodePointer ip) {
+ SamplerSampleWriterData writerData = JfrThreadLocal.getSamplerWriterData();
+ assert writerData.isNonNull();
+
+ boolean shouldSkipFrame = shouldSkipFrame(writerData);
+ boolean shouldContinueWalk = shouldContinueWalk(writerData);
+ if (!shouldSkipFrame && shouldContinueWalk) {
+ writerData.setHashCode(computeHash(writerData.getHashCode(), ip.rawValue()));
+ shouldContinueWalk = SamplerSampleWriter.putLong(writerData, ip.rawValue());
+ }
+ return shouldContinueWalk;
+ }
}
From 935676a8a58b5bca48a019f990d25ff0810a8b71 Mon Sep 17 00:00:00 2001
From: Christian Haeubl
Date: Fri, 27 Jan 2023 18:26:16 +0100
Subject: [PATCH 19/21] Improve JFR test cases.
---
.../src/com/oracle/svm/test/jfr/JfrTest.java | 7 +-
.../oracle/svm/test/jfr/TestClassEvent.java | 4 +-
.../test/jfr/TestJavaMonitorEnterEvent.java | 3 +-
.../test/jfr/TestJavaMonitorWaitEvent.java | 3 +-
.../TestJavaMonitorWaitInterruptEvent.java | 5 +-
.../TestJavaMonitorWaitNotifyAllEvent.java | 5 +-
.../jfr/TestJavaMonitorWaitTimeoutEvent.java | 3 +-
.../test/jfr/TestJfrExecutionSampleEvent.java | 119 ++++++++++++++++++
.../svm/test/jfr/TestStackTraceEvent.java | 4 +-
.../oracle/svm/test/jfr/TestStringEvent.java | 4 +-
.../oracle/svm/test/jfr/TestThreadEvent.java | 4 +-
.../com/oracle/svm/test/jfr/utils/Jfr.java | 2 -
.../oracle/svm/test/jfr/utils/LocalJfr.java | 10 +-
13 files changed, 141 insertions(+), 32 deletions(-)
create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrExecutionSampleEvent.java
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
index b6f2de9ffb23..200267c17a61 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java
@@ -87,11 +87,10 @@ public void endRecording() throws Throwable {
protected abstract String[] getTestedEvents();
private void enableEvents() {
+ /* Additionally, enable all events that the test case wants to test explicitly. */
String[] events = getTestedEvents();
- if (events != null) {
- for (String event : events) {
- recording.enable(event);
- }
+ for (String event : events) {
+ recording.enable(event);
}
}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
index 88f0f1e30aa3..ca34d772c0ac 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java
@@ -34,9 +34,7 @@ public class TestClassEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{
- ClassEvent.class.getName()
- };
+ return new String[]{ClassEvent.class.getName()};
}
@Test
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java
index fa14c37c90f9..d8db8c2f0db6 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertTrue;
+import com.oracle.svm.core.jfr.JfrEvent;
import org.junit.Test;
import jdk.jfr.consumer.RecordedClass;
@@ -44,7 +45,7 @@ public class TestJavaMonitorEnterEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{"jdk.JavaMonitorEnter"};
+ return new String[]{JfrEvent.JavaMonitorEnter.getName()};
}
@Override
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java
index e9521c824c7d..db37581af10b 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.oracle.svm.core.jfr.JfrEvent;
import org.junit.Test;
import jdk.jfr.consumer.RecordedClass;
@@ -46,7 +47,7 @@ public class TestJavaMonitorWaitEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{"jdk.JavaMonitorWait"};
+ return new String[]{JfrEvent.JavaMonitorWait.getName()};
}
@Override
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java
index 99c82f4dd0d4..97252b49e425 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java
@@ -29,6 +29,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.oracle.svm.core.jfr.JfrEvent;
import org.junit.Assert;
import org.junit.Test;
@@ -39,7 +40,7 @@
public class TestJavaMonitorWaitInterruptEvent extends JfrTest {
private static final int MILLIS = 50;
- private Helper helper = new Helper();
+ private final Helper helper = new Helper();
private Thread interruptedThread;
private Thread interrupterThread;
private Thread simpleWaitThread;
@@ -49,7 +50,7 @@ public class TestJavaMonitorWaitInterruptEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{"jdk.JavaMonitorWait"};
+ return new String[]{JfrEvent.JavaMonitorWait.getName()};
}
@Override
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java
index 5a70dbd44b2b..0aaa1ae23b12 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java
@@ -29,6 +29,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.oracle.svm.core.jfr.JfrEvent;
import org.junit.Test;
import jdk.jfr.consumer.RecordedClass;
@@ -38,7 +39,7 @@
public class TestJavaMonitorWaitNotifyAllEvent extends JfrTest {
private static final int MILLIS = 50;
- private Helper helper = new Helper();
+ private final Helper helper = new Helper();
private Thread producerThread1;
private Thread producerThread2;
private Thread consumerThread;
@@ -47,7 +48,7 @@ public class TestJavaMonitorWaitNotifyAllEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{"jdk.JavaMonitorWait"};
+ return new String[]{JfrEvent.JavaMonitorWait.getName()};
}
@Override
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java
index 908a0db77b3a..98e0e187e3eb 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertTrue;
+import com.oracle.svm.core.jfr.JfrEvent;
import org.junit.Assert;
import org.junit.Test;
@@ -48,7 +49,7 @@ public class TestJavaMonitorWaitTimeoutEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{"jdk.JavaMonitorWait"};
+ return new String[]{JfrEvent.JavaMonitorWait.getName()};
}
@Override
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrExecutionSampleEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrExecutionSampleEvent.java
new file mode 100644
index 000000000000..a751db0b0031
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrExecutionSampleEvent.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2022, 2022, 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. 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.test.jfr;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.oracle.svm.core.NeverInline;
+import com.oracle.svm.core.util.VMError;
+import jdk.jfr.consumer.RecordedFrame;
+import org.junit.Test;
+
+import com.oracle.svm.core.jfr.JfrEvent;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedStackTrace;
+import jdk.jfr.consumer.RecordedThread;
+
+public class TestJfrExecutionSampleEvent extends JfrTest {
+ @Override
+ public String[] getTestedEvents() {
+ return new String[]{JfrEvent.ExecutionSample.getName()};
+ }
+
+ @Override
+ public void validateEvents() throws Throwable {
+ List events = getEvents();
+ assertTrue(events.size() > 0);
+
+ Set seenThreadIds = new HashSet<>();
+ for (RecordedEvent event : events) {
+ long sampledThreadId = event. getValue("sampledThread").getJavaThreadId();
+ assertTrue(sampledThreadId > 0);
+ seenThreadIds.add(sampledThreadId);
+
+ RecordedStackTrace stackTrace = event.getStackTrace();
+ assertNotNull(stackTrace);
+
+ List frames = stackTrace.getFrames();
+ assertFalse(frames.isEmpty());
+
+ for (RecordedFrame frame : frames) {
+ assertNotNull(frame.getMethod());
+ assertNotNull(frame.getMethod().getName());
+ assertFalse(frame.getMethod().getName().isEmpty());
+ }
+ }
+
+ assertTrue(seenThreadIds.size() > 1);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Worker[] workers = new Worker[8];
+ for (int i = 0; i < workers.length; i++) {
+ workers[i] = new Worker();
+ workers[i].start();
+ }
+
+ for (int i = 0; i < workers.length; i++) {
+ try {
+ workers[i].join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static class Worker extends Thread {
+ @Override
+ public void run() {
+ for (int i = 0; i < 1_000_000; i++) {
+ allocateObject(i);
+ }
+ }
+
+ @NeverInline("Prevent escape analysis.")
+ private static Object allocateObject(int iteration) {
+ int action = iteration % 3;
+ switch (action) {
+ case 0:
+ return new StringBuilder(0);
+ case 1:
+ return new int[43];
+ case 2:
+ return new Object[43];
+ default:
+ throw VMError.shouldNotReachHere();
+ }
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
index 5c6e64027120..c0c76dfee509 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java
@@ -35,9 +35,7 @@
public class TestStackTraceEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{
- StackTraceEvent.class.getName()
- };
+ return new String[]{StackTraceEvent.class.getName()};
}
@Test
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
index 5bf127a9c185..4fdbfb4ddcec 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java
@@ -33,9 +33,7 @@
public class TestStringEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{
- StringEvent.class.getName()
- };
+ return new String[]{StringEvent.class.getName()};
}
@Test
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadEvent.java
index 90057698d077..aa16dc655477 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadEvent.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadEvent.java
@@ -36,9 +36,7 @@
public class TestThreadEvent extends JfrTest {
@Override
public String[] getTestedEvents() {
- return new String[]{
- ThreadEvent.class.getName()
- };
+ return new String[]{ThreadEvent.class.getName()};
}
@Test
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/Jfr.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/Jfr.java
index c615e4779f54..a36b79e97c57 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/Jfr.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/Jfr.java
@@ -37,8 +37,6 @@ public interface Jfr {
Recording createRecording(String recordingName) throws Exception;
- Recording createRecording(String recordingName, String configName) throws Exception;
-
void startRecording(Recording recording);
void endRecording(Recording recording) throws Exception;
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/LocalJfr.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/LocalJfr.java
index 3bb9dde688bd..9fd0399949b7 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/LocalJfr.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/LocalJfr.java
@@ -38,13 +38,9 @@ public class LocalJfr implements Jfr {
@Override
public Recording createRecording(String recordingName) throws Exception {
- return createRecording(new Recording(), recordingName);
- }
-
- @Override
- public Recording createRecording(String recordingName, String configName) throws Exception {
- Configuration c = Configuration.getConfiguration(configName);
- return createRecording(new Recording(c), recordingName);
+ /* Enable a lot of events by default to increase the test coverage. */
+ Configuration defaultConfig = Configuration.getConfiguration("default");
+ return createRecording(new Recording(defaultConfig), recordingName);
}
@Override
From b7dbf935523d0b7d775d4196890def4f28a016ac Mon Sep 17 00:00:00 2001
From: Christian Haeubl
Date: Mon, 30 Jan 2023 14:18:39 +0100
Subject: [PATCH 20/21] Small correctness fixes.
---
.../svm/core/posix/PosixSubstrateSigprofHandler.java | 6 +++++-
.../com/oracle/svm/core/jfr/JfrNativeEventWriter.java | 10 ++++++----
.../core/jfr/sampler/AbstractJfrExecutionSampler.java | 8 +++++++-
.../oracle/svm/core/sampler/SamplerBuffersAccess.java | 4 +++-
.../oracle/svm/core/sampler/SamplerSampleWriter.java | 4 +++-
5 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
index e3e6f6708e2f..98e4a834f345 100644
--- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java
@@ -122,8 +122,12 @@ protected void installSignalHandler() {
@Override
protected void uninstallSignalHandler() {
+ /*
+ * Only disable the sampling but do not replace the signal handler with the default one
+ * because a signal might be pending for some thread (the default signal handler would print
+ * "Profiling timer expired" to the output).
+ */
updateInterval(0);
- registerSigprofSignal((Signal.AdvancedSignalDispatcher) Signal.SIG_DFL());
}
@Override
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
index 37300bb68cb0..21bcc88adedc 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java
@@ -351,20 +351,22 @@ public static boolean commit(JfrNativeEventWriterData data) {
if (!isValid(data)) {
return false;
}
- return commitEvent(data);
+
+ commitEvent(data);
+ return true;
}
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
- private static boolean commitEvent(JfrNativeEventWriterData data) {
- JfrBuffer buffer = data.getJfrBuffer();
+ private static void commitEvent(JfrNativeEventWriterData data) {
assert isValid(data);
+
+ JfrBuffer buffer = data.getJfrBuffer();
assert buffer.getPos().equal(data.getStartPos());
assert JfrBufferAccess.getDataEnd(data.getJfrBuffer()).equal(data.getEndPos());
Pointer newPosition = data.getCurrentPos();
buffer.setPos(newPosition);
data.setStartPos(newPosition);
- return true;
}
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/AbstractJfrExecutionSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/AbstractJfrExecutionSampler.java
index 49da48b82b5d..f05a381f45f8 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/AbstractJfrExecutionSampler.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/sampler/AbstractJfrExecutionSampler.java
@@ -180,7 +180,13 @@ protected static void tryUninterruptibleStackWalk(CodePointer ip, Pointer sp) {
threadsInSignalHandler().incrementAndGet();
try {
if (isExecutionSamplingAllowedInCurrentThread()) {
- doUninterruptibleStackWalk(ip, sp);
+ /* Prevent recursive sampler invocations during the stack walk. */
+ JfrExecutionSampler.singleton().preventSamplingInCurrentThread();
+ try {
+ doUninterruptibleStackWalk(ip, sp);
+ } finally {
+ JfrExecutionSampler.singleton().allowSamplingInCurrentThread();
+ }
} else {
JfrThreadLocal.increaseMissedSamples();
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
index 15bdcb3686c0..df5fda7bc9cc 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java
@@ -206,7 +206,9 @@ private static boolean serializeStackTrace(Pointer rawStackTrace, int sampleSize
* (JfrNativeEventWriter.putInt() would not necessarily reserve enough bytes).
*/
int numStackTraceElements = visitRawStackTrace(rawStackTrace, sampleSize, WordFactory.nullPointer());
- assert numStackTraceElements > 0;
+ if (numStackTraceElements == 0) {
+ return false;
+ }
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
JfrNativeEventWriterDataAccess.initialize(data, targetBuffer);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
index ee943b4156fb..b6d839205c8f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java
@@ -141,7 +141,9 @@ private static void commit(SamplerSampleWriterData data) {
assert buffer.getPos().equal(data.getStartPos());
assert SamplerBufferAccess.getDataEnd(data.getSamplerBuffer()).equal(data.getEndPos());
- buffer.setPos(data.getCurrentPos());
+ Pointer newPosition = data.getCurrentPos();
+ buffer.setPos(newPosition);
+ data.setStartPos(newPosition);
}
@Uninterruptible(reason = "Accesses a sampler buffer.", callerMustBe = true)
From 31b64d7d814b3903a6f277e44181658bfa14ebf1 Mon Sep 17 00:00:00 2001
From: Christian Haeubl
Date: Mon, 30 Jan 2023 14:18:59 +0100
Subject: [PATCH 21/21] JKD20-related fixes.
---
.../com/oracle/svm/core/jfr/SubstrateJVM.java | 7 ++
.../core/jfr/Target_jdk_jfr_internal_JVM.java | 100 +++++++++++-------
.../svm/core/jfr/logging/JfrLogging.java | 32 ++++++
.../Target_jdk_jfr_internal_LogTag.java | 4 +-
4 files changed, 104 insertions(+), 39 deletions(-)
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
index 4c3fcfe5a4a8..80300331c067 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java
@@ -569,6 +569,13 @@ public void log(int tagSetId, int level, String message) {
jfrLogging.log(tagSetId, level, message);
}
+ /**
+ * See {@link JVM#logEvent}.
+ */
+ public void logEvent(int level, String[] lines, boolean system) {
+ jfrLogging.logEvent(level, lines, system);
+ }
+
/**
* See {@link JVM#subscribeLogLevel}.
*/
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
index 937a22b64879..53146a15b441 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java
@@ -71,22 +71,24 @@ public final class Target_jdk_jfr_internal_JVM {
private static void registerNatives() {
}
- /** See {@link JVM#beginRecording}. */
@Substitute
- public void beginRecording() {
- SubstrateJVM.get().beginRecording();
+ @TargetElement(onlyWith = JDK17OrLater.class) //
+ public void markChunkFinal() {
+ // Temporarily do nothing. This is used for JFR streaming.
}
- /** See {@link JVM#counterTime}. */
+ /** See {@link JVM#beginRecording}. */
@Substitute
- public static long counterTime() {
- return JfrTicks.elapsedTicks();
+ public void beginRecording() {
+ SubstrateJVM.get().beginRecording();
}
- /** See {@link JVM#emitEvent}. */
+ /** See {@link JVM#isRecording}. */
@Substitute
- public boolean emitEvent(long eventTypeId, long timestamp, long when) {
- return false;
+ @Uninterruptible(reason = "Needed for calling SubstrateJVM.isRecording().")
+ @TargetElement(onlyWith = JDK17OrLater.class)
+ public boolean isRecording() {
+ return SubstrateJVM.get().isRecording();
}
/** See {@link JVM#endRecording}. */
@@ -95,12 +97,16 @@ public void endRecording() {
SubstrateJVM.get().endRecording();
}
- /** See {@link JVM#isRecording}. */
+ /** See {@link JVM#counterTime}. */
@Substitute
- @Uninterruptible(reason = "Needed for calling SubstrateJVM.isRecording().")
- @TargetElement(onlyWith = JDK17OrLater.class)
- public boolean isRecording() {
- return SubstrateJVM.get().isRecording();
+ public static long counterTime() {
+ return JfrTicks.elapsedTicks();
+ }
+
+ /** See {@link JVM#emitEvent}. */
+ @Substitute
+ public boolean emitEvent(long eventTypeId, long timestamp, long when) {
+ return false;
}
/** See {@link JVM#getAllEventClasses}. */
@@ -167,6 +173,12 @@ public static void log(int tagSetId, int level, String message) {
SubstrateJVM.get().log(tagSetId, level, message);
}
+ /** See {@link JVM#logEvent}. */
+ @Substitute
+ public static void logEvent(int level, String[] lines, boolean system) {
+ SubstrateJVM.get().logEvent(level, lines, system);
+ }
+
/** See {@link JVM#subscribeLogLevel}. */
@Substitute
public static void subscribeLogLevel(LogTag lt, int tagSetId) {
@@ -306,13 +318,6 @@ public double getTimeConversionFactor() {
return 1;
}
- /** See {@link SubstrateJVM#getChunkStartNanos}. */
- @Substitute
- @TargetElement(onlyWith = JDK17OrLater.class)
- public long getChunkStartNanos() {
- return SubstrateJVM.get().getChunkStartNanos();
- }
-
@Substitute
@TargetElement(onlyWith = {JDK17OrLater.class, JDK17OrEarlier.class})
public boolean setHandler(Class extends jdk.internal.event.Event> eventClass, Target_jdk_jfr_internal_handlers_EventHandler handler) {
@@ -328,7 +333,7 @@ public Object getHandler(Class extends jdk.internal.event.Event> eventClass) {
return SubstrateJVM.getHandler(eventClass);
}
- /** See {@link JVM#getTypeId}. */
+ /** See {@link JVM#getTypeId(Class)}. */
@Substitute
public long getTypeId(Class> clazz) {
return JfrTraceId.getTraceId(clazz);
@@ -352,6 +357,13 @@ public static boolean flush(Target_jdk_jfr_internal_EventWriter writer, int unco
return SubstrateJVM.get().flush(writer, uncommittedSize, requestedSize);
}
+ /** See {@link JVM#flush}. */
+ @Substitute
+ @TargetElement(onlyWith = JDK17OrLater.class) //
+ public void flush() {
+ // Temporarily do nothing. This is used for JFR streaming.
+ }
+
/** See {@link JVM#setRepositoryLocation}. */
@Substitute
public void setRepositoryLocation(String dirText) {
@@ -378,6 +390,12 @@ public void abort(String errorMsg) {
SubstrateJVM.get().abort(errorMsg);
}
+ /** See {@link JVM#addStringConstant}. */
+ @Substitute
+ public static boolean addStringConstant(long id, String s) {
+ return false;
+ }
+
/** See {@link JVM#uncaughtException}. */
@Substitute
public void uncaughtException(Thread thread, Throwable t) {
@@ -418,10 +436,9 @@ public boolean shouldRotateDisk() {
return SubstrateJVM.get().shouldRotateDisk();
}
- /** See {@link JVM#flush}. */
@Substitute
@TargetElement(onlyWith = JDK17OrLater.class) //
- public void flush() {
+ public void exclude(Thread thread) {
// Temporarily do nothing. This is used for JFR streaming.
}
@@ -431,12 +448,6 @@ public void include(Thread thread) {
// Temporarily do nothing. This is used for JFR streaming.
}
- @Substitute
- @TargetElement(onlyWith = JDK17OrLater.class) //
- public void exclude(Thread thread) {
- // Temporarily do nothing. This is used for JFR streaming.
- }
-
@Substitute
@TargetElement(onlyWith = JDK17OrLater.class) //
public boolean isExcluded(Thread thread) {
@@ -451,6 +462,20 @@ public boolean isExcluded(Class extends jdk.internal.event.Event> eventClass)
return false;
}
+ @Substitute
+ @TargetElement(onlyWith = JDK19OrLater.class) //
+ public boolean isInstrumented(Class extends jdk.internal.event.Event> eventClass) {
+ // This should check for blessed commit methods in the event class [GR-41200]
+ return true;
+ }
+
+ /** See {@link SubstrateJVM#getChunkStartNanos}. */
+ @Substitute
+ @TargetElement(onlyWith = JDK17OrLater.class)
+ public long getChunkStartNanos() {
+ return SubstrateJVM.get().getChunkStartNanos();
+ }
+
@Substitute
@TargetElement(onlyWith = JDK19OrLater.class) //
public boolean setConfiguration(Class extends jdk.internal.event.Event> eventClass, Target_jdk_jfr_internal_event_EventConfiguration configuration) {
@@ -463,11 +488,11 @@ public Object getConfiguration(Class extends jdk.internal.event.Event> eventCl
return SubstrateJVM.get().getConfiguration(eventClass);
}
+ /** See {@link JVM#getTypeId(String)}. */
@Substitute
- @TargetElement(onlyWith = JDK19OrLater.class) //
- public boolean isInstrumented(Class extends jdk.internal.event.Event> eventClass) {
- // This should check for blessed commit methods in the event class [GR-41200]
- return true;
+ public long getTypeId(String name) {
+ /* Not implemented at the moment. */
+ return -1;
}
@Substitute
@@ -477,8 +502,9 @@ public boolean isContainerized() {
}
@Substitute
- @TargetElement(onlyWith = JDK17OrLater.class) //
- public void markChunkFinal() {
- // Temporarily do nothing. This is used for JFR streaming.
+ @TargetElement(onlyWith = JDK20OrLater.class) //
+ public long hostTotalMemory() {
+ /* Not implemented at the moment. */
+ return 0;
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/JfrLogging.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/JfrLogging.java
index 73da0dae945a..670bdb77ba65 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/JfrLogging.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/JfrLogging.java
@@ -30,6 +30,7 @@
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
+import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.util.ReflectionUtil;
@@ -55,6 +56,12 @@ public void parseConfiguration(String config) {
}
public void log(int tagSetId, int level, String message) {
+ if (message == null) {
+ return;
+ }
+ verifyLogLevel(level);
+ verifyLogTagSetId(tagSetId);
+
String levelDecoration = logLevels[level];
String tagSetDecoration = logTagSets[tagSetId];
@@ -74,6 +81,31 @@ public void log(int tagSetId, int level, String message) {
log.string(message).newline();
}
+ public void logEvent(int level, String[] lines, boolean system) {
+ if (lines == null) {
+ return;
+ }
+ verifyLogLevel(level);
+
+ LogTag logTag = system ? LogTag.JFR_SYSTEM_EVENT : LogTag.JFR_EVENT;
+ int tagSetId = SubstrateUtil.cast(logTag, Target_jdk_jfr_internal_LogTag.class).id;
+ for (int i = 0; i < lines.length; i++) {
+ log(tagSetId, level, lines[i]);
+ }
+ }
+
+ private void verifyLogLevel(int level) {
+ if (level < 0 || level >= logLevels.length || logLevels[level] == null) {
+ throw new IllegalArgumentException("LogLevel passed is outside valid range");
+ }
+ }
+
+ private void verifyLogTagSetId(int tagSetId) {
+ if (tagSetId < 0 || tagSetId >= logTagSets.length) {
+ throw new IllegalArgumentException("LogTagSet id is outside valid range");
+ }
+ }
+
@Platforms(Platform.HOSTED_ONLY.class)
private static String[] createLogLevels() {
LogLevel[] values = LogLevel.values();
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/Target_jdk_jfr_internal_LogTag.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/Target_jdk_jfr_internal_LogTag.java
index 35c793a0626d..398d4aab1c5e 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/Target_jdk_jfr_internal_LogTag.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/logging/Target_jdk_jfr_internal_LogTag.java
@@ -35,11 +35,11 @@
import com.oracle.svm.core.jfr.HasJfrSupport;
@TargetClass(value = jdk.jfr.internal.LogTag.class, onlyWith = HasJfrSupport.class)
-final class Target_jdk_jfr_internal_LogTag {
+public final class Target_jdk_jfr_internal_LogTag {
@Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ComputeTagSetLevel.class) //
volatile int tagSetLevel;
- @Alias int id;
+ @Alias public int id;
}
@Platforms(Platform.HOSTED_ONLY.class)