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 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 eventClass) { + @SuppressWarnings("unchecked") + public static void registerEventClass(Class eventClass) { EVENT_CLASSES.add(eventClass); + if (jdk.jfr.Event.class.isAssignableFrom(eventClass)) { + JFR_EVENT_CLASSES.add((Class) 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 newEventClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), eventType).asSubclass(Event.class); + Class 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 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 mirrorEventAnnotationClass = (Class) 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 mirrorEventClass = (Class) 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 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 eventClass, Target_jdk_jfr_internal_handlers_EventHandler handler) { @@ -328,7 +333,7 @@ public Object getHandler(Class 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 eventClass) return false; } + @Substitute + @TargetElement(onlyWith = JDK19OrLater.class) // + public boolean isInstrumented(Class 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 eventClass, Target_jdk_jfr_internal_event_EventConfiguration configuration) { @@ -463,11 +488,11 @@ public Object getConfiguration(Class eventCl return SubstrateJVM.get().getConfiguration(eventClass); } + /** See {@link JVM#getTypeId(String)}. */ @Substitute - @TargetElement(onlyWith = JDK19OrLater.class) // - public boolean isInstrumented(Class 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)