From f3f4d511c9b9b8c6c5aca56b521fa19ca05ab428 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 10 Jan 2023 16:53:32 -0500 Subject: [PATCH 01/34] rebase with master. basic. Not uninterruptible. No stack traces --- .../genscavenge/ThreadLocalAllocation.java | 4 ++ .../src/com/oracle/svm/core/jfr/JfrEvent.java | 1 + .../svm/core/jfr/JfrRecorderThread.java | 1 + .../oracle/svm/core/jfr/JfrThreadLocal.java | 7 ++ .../com/oracle/svm/core/jfr/JfrThrottler.java | 69 +++++++++++++++++++ .../svm/core/jfr/JfrThrottlerSupport.java | 47 +++++++++++++ .../com/oracle/svm/core/jfr/SubstrateJVM.java | 20 ++++++ .../core/jfr/Target_jdk_jfr_internal_JVM.java | 3 +- .../events/ObjectAllocationSampleEvent.java | 69 +++++++++++++++++++ 9 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index e42ad9153ec2..90c733cb418c 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -75,6 +75,8 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalWord; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.jfr.events.ObjectAllocationSampleEvent; +import com.oracle.svm.core.jfr.JfrTicks; /** * Bump-pointer allocation from thread-local top and end Pointers. Many of these methods are called @@ -238,6 +240,7 @@ private static Object slowPathNewInstanceWithoutAllocating(DynamicHub hub) { return allocateInstanceInNewTlab(hub, newTlab); } finally { ObjectAllocationInNewTLABEvent.emit(startTicks, hub, LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()), HeapParameters.getAlignedHeapChunkSize()); + ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub), 0); DeoptTester.enableDeoptTesting(); } } @@ -317,6 +320,7 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un return array; } finally { ObjectAllocationInNewTLABEvent.emit(startTicks, hub, size, tlabSize); + ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub), 0); DeoptTester.enableDeoptTesting(); } } 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 295cc0d57ee0..d383f33ab751 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 @@ -62,6 +62,7 @@ public final class JfrEvent { public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate"); public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB"); public static final JfrEvent GCHeapSummary = create("jdk.GCHeapSummary"); + public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample"); private final long id; private final String name; 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 cf26ef4c4ce9..7f6781619304 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 @@ -83,6 +83,7 @@ public void run() { } } } catch (Throwable e) { + System.out.println(e); VMError.shouldNotReachHere("No exception must by thrown in the JFR recorder thread as this could break file IO operations."); } } 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 6721e6994293..4270d4770ff6 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 @@ -304,6 +304,7 @@ private static JfrBuffer reinstateJavaBuffer(JfrBuffer buffer) { return buffer; } +<<<<<<< HEAD @Uninterruptible(reason = "Accesses a JFR buffer.") public JfrBuffer getJavaBuffer() { JfrBuffer buffer = javaBuffer.get(); @@ -323,6 +324,12 @@ public JfrBuffer getJavaBuffer() { return buffer; } + @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) + public boolean initialized() { + return threadId.get() > 0; + } + + @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public JfrBuffer getNativeBuffer() { JfrBuffer buffer = nativeBuffer.get(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java new file mode 100644 index 000000000000..6063e899b64b --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -0,0 +1,69 @@ +package com.oracle.svm.core.jfr; + +import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jfr.JfrEvent; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.jfr.JfrTicks; + + +/** + * Similar to Hotspot, each event that allows throttling, shall have its own throttler instance. + * An alternative approach could be to use event specific throttling parameters in each event's event settings. + * + * Another alternative could be to maintain a set of parameters for each event type within the JfrThrottler singleton. + * But maps cannot be used because allocation cannot be done while on the allocation slow path. + * Maybe the map can be created in hosted mode before any allocation happens + */ +public class JfrThrottler { + private long eventSampleSize; + private long periodNs; + private UninterruptibleUtils.AtomicInteger measuredPopSize; // already volatile + private UninterruptibleUtils.AtomicLong endTicks; // already volatile + private UninterruptibleUtils.AtomicBoolean lock; // already volatile + JfrThrottler() { + this.lock = new UninterruptibleUtils.AtomicBoolean(false); + periodNs = 0; + eventSampleSize = 0; + measuredPopSize = new UninterruptibleUtils.AtomicInteger(0); + this.endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + periodNs); + } + + public boolean setThrottle(long eventSampleSize, long periodMs) { + //TODO may need to protect with mutex + + this.eventSampleSize = eventSampleSize; + this.periodNs = periodMs * 1000000; //convert to ns from ms + return true; + } + + public boolean sample() { + boolean expired = isExpired(); + if (expired) { + //If fail to acquire lock, it means another thread is already handling it and this one can move on. + if (lock.compareAndSet(false,true)) { + rotateWindow(); + lock.set(false); + } + return false; // if expired, hotspot returns false + } + + long prevMeasuredPopSize = measuredPopSize.incrementAndGet(); //guarantees only one thread can record the last event of the window + if ( prevMeasuredPopSize > eventSampleSize) { + return false; + } + return true; + } + + private boolean isExpired() { + if (JfrTicks.currentTimeNanos() > endTicks.get()) { + return true; + } + return false; + } + + private void rotateWindow() { + measuredPopSize.set(0); + endTicks.set(JfrTicks.currentTimeNanos() + periodNs); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java new file mode 100644 index 000000000000..7a91d1419924 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java @@ -0,0 +1,47 @@ +package com.oracle.svm.core.jfr; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.JfrThrottler; + +/** + * Each event that supports throttling has its own throttler that can be accessed through this class. + * TODO: Consider making this a proper singleton later + * When more evnets support throttling, some sort of allocation free hashmap should be used. + */ +public class JfrThrottlerSupport { + JfrThrottler objectAllocationSampleThrottler; + @Platforms(Platform.HOSTED_ONLY.class) + JfrThrottlerSupport() { + objectAllocationSampleThrottler = new JfrThrottler(); + } + + private JfrThrottler getThrottler(long eventId) { + if (eventId == JfrEvent.ObjectAllocationSample.getId()) { + return objectAllocationSampleThrottler; + } + return null; + } + + public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { + //TODO may need to protect with mutex + JfrThrottler throttler = getThrottler(eventTypeId); + if (throttler == null) { + //event doesn't support throttling + return false; + } + throttler.setThrottle(eventSampleSize, periodMs); + return true; + } + + public boolean shouldCommit(long eventTypeId) { + JfrThrottler throttler = getThrottler(eventTypeId); + if (throttler == null) { + //event doesn't support throttling + return true; + } + return throttler.sample(); + } +} 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 17c7b7fd30b2..14cefd72a8bd 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 @@ -45,6 +45,7 @@ import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.jfr.JfrThrottlerSupport; import jdk.internal.event.Event; import jdk.jfr.Configuration; @@ -79,6 +80,8 @@ public class SubstrateJVM { private final JfrUnlockedChunkWriter unlockedChunkWriter; private final JfrRecorderThread recorderThread; + private final JfrThrottlerSupport jfrThrottlerSupport; + private final JfrLogging jfrLogging; private boolean initialized; @@ -118,6 +121,8 @@ public SubstrateJVM(List configurations) { initialized = false; recording = false; + + jfrThrottlerSupport = new JfrThrottlerSupport(); } @Fold @@ -640,6 +645,21 @@ public boolean isEnabled(JfrEvent event) { return eventSettings[(int) event.getId()].isEnabled(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean shouldCommit(JfrEvent event) { + //find the right throttler for the event (each event should have its own)like in hotspot + //if none found, return true. + // call into throttler code + return jfrThrottlerSupport.shouldCommit(event.getId()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { + // find the right throttler for the event and set the new params there + return jfrThrottlerSupport.setThrottle(eventTypeId, eventSampleSize, periodMs); + //TODO: why would it ever return false? Maybe if the throttler doesn't exist? + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void setLarge(JfrEvent event, boolean large) { eventSettings[(int) event.getId()].setLarge(large); 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 a3afc8bae0a9..4b7fffc73645 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 @@ -402,8 +402,7 @@ public boolean setCutoff(long eventTypeId, long cutoffTicks) { @Substitute public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { - // Not supported but this method is called during JFR startup, so we can't throw an error. - return true; + return SubstrateJVM.get().setThrottle(eventTypeId, eventSampleSize, periodMs); } /** See {@link JVM#emitOldObjectSamples}. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java new file mode 100644 index 000000000000..f33943253521 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.jfr.events; + +import com.oracle.svm.core.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.SubstrateJVM; +import com.oracle.svm.core.jfr.HasJfrSupport; +import org.graalvm.nativeimage.StackValue; +import com.oracle.svm.core.jfr.JfrThreadLocal; + +public class ObjectAllocationSampleEvent { + public static void emit(long startTicks, Class clazz, long weight) { + if (HasJfrSupport.get()) { + if (SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { // TODO: consider moving this to after the isRecording check. Might be a pain to deal with uninterruptibility though. + emit0(startTicks, clazz, weight); + } + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emit0(long startTicks, Class clazz, long weight) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ObjectAllocationSample)) { + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); + + // This check is needed to avoid issues upon allocation from the thread that invokes the + // Java main method and shutdown. + if (!jfrThreadLocal.initialized()) { + return; + } + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationSample); + JfrNativeEventWriter.putLong(data, startTicks); + JfrNativeEventWriter.putEventThread(data); +// JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); //This causes problems during JFR shutdown + JfrNativeEventWriter.putClass(data, clazz); + JfrNativeEventWriter.putLong(data, weight); + JfrNativeEventWriter.endSmallEvent(data); + } + } +} From 6aeef6b338008265c11d0657cb813e67fb324868 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 17 Jan 2023 16:08:29 -0500 Subject: [PATCH 02/34] add weight --- .../oracle/svm/core/jfr/JfrThreadLocal.java | 3 ++- .../svm/core/jfr/JfrThrottlerSupport.java | 2 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 7 +++++++ .../events/ObjectAllocationSampleEvent.java | 19 +++++++++++++------ 4 files changed, 23 insertions(+), 8 deletions(-) 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 4270d4770ff6..9625ff4f0d0b 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 @@ -326,7 +326,8 @@ public JfrBuffer getJavaBuffer() { @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public boolean initialized() { - return threadId.get() > 0; + // Thread start event must have already been emitted. + return threadId.get() > 0 && (javaBuffer.get().isNonNull() || nativeBuffer.get().isNonNull()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java index 7a91d1419924..e7c7b0d75216 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java @@ -9,7 +9,7 @@ /** * Each event that supports throttling has its own throttler that can be accessed through this class. * TODO: Consider making this a proper singleton later - * When more evnets support throttling, some sort of allocation free hashmap should be used. + * When more events support throttling, some sort of allocation free hashmap should be used. */ public class JfrThrottlerSupport { JfrThrottler objectAllocationSampleThrottler; 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 14cefd72a8bd..44847c5aaa99 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 @@ -205,6 +205,13 @@ protected boolean isRecording() { return recording; } + /** + * Use for time saving checks, not as a check before doing an operation that could result in races. + */ + public static boolean isRecordingInterruptible() { + return get().recording; + } + /** * See {@link JVM#createJFR}. Until {@link #beginRecording} is executed, no JFR events can be * triggered yet. So, we don't need to take any precautions here. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java index f33943253521..db2ebbcccaae 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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 @@ -35,11 +35,16 @@ import com.oracle.svm.core.jfr.HasJfrSupport; import org.graalvm.nativeimage.StackValue; import com.oracle.svm.core.jfr.JfrThreadLocal; +import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; +import com.oracle.svm.core.threadlocal.FastThreadLocalLong; public class ObjectAllocationSampleEvent { + private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); public static void emit(long startTicks, Class clazz, long weight) { if (HasJfrSupport.get()) { - if (SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { // TODO: consider moving this to after the isRecording check. Might be a pain to deal with uninterruptibility though. + // TODO: consider moving this to after the isRecording check in emit0 to avoid duplicate checks. Might be a pain to deal with uninterruptibility though. + // Doesn't hurt to check twice, might save us some time doing the sampling + if (SubstrateJVM.isRecordingInterruptible() && SubstrateJVM.get().isEnabled(JfrEvent.ObjectAllocationSample) && SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { emit0(startTicks, clazz, weight); } } @@ -49,21 +54,23 @@ public static void emit(long startTicks, Class clazz, long weight) { private static void emit0(long startTicks, Class clazz, long weight) { if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ObjectAllocationSample)) { JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); - // This check is needed to avoid issues upon allocation from the thread that invokes the // Java main method and shutdown. if (!jfrThreadLocal.initialized()) { return; } + long currentAllocationSize = com.oracle.svm.core.thread.PlatformThreads.getThreadAllocatedBytes(Thread.currentThread().getId()); + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationSample); JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putEventThread(data); -// JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); //This causes problems during JFR shutdown + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); //This causes problems during JFR shutdown JfrNativeEventWriter.putClass(data, clazz); - JfrNativeEventWriter.putLong(data, weight); + JfrNativeEventWriter.putLong(data, currentAllocationSize - lastAllocationSize.get()); JfrNativeEventWriter.endSmallEvent(data); + lastAllocationSize.set(currentAllocationSize); } } } From b72be8ab3ae976b95e19580c9cb18c91acf5c427 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 18 Jan 2023 15:42:56 -0500 Subject: [PATCH 03/34] evenly space out samples based on previous window --- .../genscavenge/ThreadLocalAllocation.java | 6 +- .../com/oracle/svm/core/jfr/JfrThrottler.java | 133 +++++++++++++----- .../svm/core/jfr/JfrThrottlerSupport.java | 1 - .../svm/core/jfr/JfrThrottlerWindow.java | 40 ++++++ .../events/ObjectAllocationSampleEvent.java | 9 +- 5 files changed, 151 insertions(+), 38 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index 90c733cb418c..ccf6716bd6d8 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -240,7 +240,7 @@ private static Object slowPathNewInstanceWithoutAllocating(DynamicHub hub) { return allocateInstanceInNewTlab(hub, newTlab); } finally { ObjectAllocationInNewTLABEvent.emit(startTicks, hub, LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()), HeapParameters.getAlignedHeapChunkSize()); - ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub), 0); + ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub)); DeoptTester.enableDeoptTesting(); } } @@ -319,8 +319,12 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un } return array; } finally { +<<<<<<< HEAD ObjectAllocationInNewTLABEvent.emit(startTicks, hub, size, tlabSize); ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub), 0); +======= + ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub)); +>>>>>>> 7bf8d435e5a (evenly space out samples based on previous window) DeoptTester.enableDeoptTesting(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 6063e899b64b..ccab3e225b80 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -1,11 +1,16 @@ package com.oracle.svm.core.jfr; import com.oracle.svm.core.jdk.UninterruptibleUtils; -import com.oracle.svm.core.jfr.JfrEvent; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; + import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.jfr.JfrThrottlerWindow; +import org.graalvm.word.WordFactory; +import org.graalvm.compiler.nodes.PauseNode; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.IsolateThread; +import com.oracle.svm.core.util.VMError; + /** * Similar to Hotspot, each event that allows throttling, shall have its own throttler instance. @@ -16,54 +21,118 @@ * Maybe the map can be created in hosted mode before any allocation happens */ public class JfrThrottler { - private long eventSampleSize; - private long periodNs; - private UninterruptibleUtils.AtomicInteger measuredPopSize; // already volatile - private UninterruptibleUtils.AtomicLong endTicks; // already volatile - private UninterruptibleUtils.AtomicBoolean lock; // already volatile + public UninterruptibleUtils.AtomicBoolean disabled; // already volatile + private JfrThrottlerWindow window0; // Race allowed + private JfrThrottlerWindow window1; // Race allowed + private volatile JfrThrottlerWindow activeWindow; + public volatile long eventSampleSize; // Race allowed + public volatile long periodNs; // Race allowed + private final UninterruptibleUtils.AtomicPointer lock; // Can't use reentrant lock because it could allocate + JfrThrottler() { - this.lock = new UninterruptibleUtils.AtomicBoolean(false); - periodNs = 0; - eventSampleSize = 0; - measuredPopSize = new UninterruptibleUtils.AtomicInteger(0); - this.endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + periodNs); + disabled = new UninterruptibleUtils.AtomicBoolean(true); + lock = new UninterruptibleUtils.AtomicPointer<>(); // I assume this is initially WordFactorynullPointer() + window0 = new JfrThrottlerWindow(); + window1 = new JfrThrottlerWindow(); + activeWindow = window0; // same as hotspot. Unguarded is ok because all throttler instances are created before program execution. } public boolean setThrottle(long eventSampleSize, long periodMs) { - //TODO may need to protect with mutex - + if (eventSampleSize == 0) { + disabled.set(true); + } this.eventSampleSize = eventSampleSize; - this.periodNs = periodMs * 1000000; //convert to ns from ms + this.periodNs = periodMs * 1000000; + lock(); + rotateWindow(); // could omit this and choose to wait until next rotation. + unlock(); + disabled.set(false); // should be after the above are set return true; } + /** The real active window may change while we're doing the sampling. That's ok as long as we perform operations wrt a consistent window during this method. + That's why we declare a stack variable: window. If the active window does change after we've read it from main memory, there's no harm done because now we'll be + writing to the next window (which gets reset before becoming active again). + */ public boolean sample() { - boolean expired = isExpired(); + if (disabled.get()) { + return true; + } + JfrThrottlerWindow window = activeWindow; + boolean expired = window.isExpired(); if (expired) { - //If fail to acquire lock, it means another thread is already handling it and this one can move on. - if (lock.compareAndSet(false,true)) { - rotateWindow(); - lock.set(false); + // Check lock to see if someone is already rotating. If so, move on. + if (tryLock()) { + // Once in critical section ensure active window is still expired. + // Another thread may have already handled the expired window, or new settings may have already triggered a rotation. + if (activeWindow.isExpired()) { + rotateWindow(); + } } return false; // if expired, hotspot returns false } + return window.sample(); + } + + /* + Locked. Only one thread should be rotating at once. + If due to an expired window, then other threads that try to rotate due to expiry, will just return false. + If race between two threads updating settings, then one will just have to wait for the other to finish. Order doesn't matter as long as they are not interrupted. + */ + private void rotateWindow() { + VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + configure(); + installNextWindow(); + } - long prevMeasuredPopSize = measuredPopSize.incrementAndGet(); //guarantees only one thread can record the last event of the window - if ( prevMeasuredPopSize > eventSampleSize) { - return false; + private void installNextWindow(){ + VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + activeWindow = getNextWindow(); + } + + JfrThrottlerWindow getNextWindow(){ + VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + if (window0 == activeWindow){ + return window1; } - return true; + return window0; } - private boolean isExpired() { - if (JfrTicks.currentTimeNanos() > endTicks.get()) { - return true; + public void configure(){ + VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + JfrThrottlerWindow next = getNextWindow(); + + // Store (possibly) updated params to both windows. + activeWindow.eventSampleSize = eventSampleSize; + activeWindow.periodNs = periodNs; + next.eventSampleSize = eventSampleSize; + next.periodNs = periodNs; + + //use updated params to compute + if (activeWindow.measuredPopSize.get() == 0) { + // Avoid math errors. If population is 0, consider every event. + // Also means for first window on program start, we consider every sample (up until the cap) + next.samplingInterval = 1; + } else { + // *** result will be a long. No floating points. long/long = long rounded + next.samplingInterval = activeWindow.measuredPopSize.get() / next.eventSampleSize; // TODO: needs to be more advanced. } - return false; + //reset + next.measuredPopSize.set(0); + next.endTicks.set(JfrTicks.currentTimeNanos() + next.periodNs); } - private void rotateWindow() { - measuredPopSize.set(0); - endTicks.set(JfrTicks.currentTimeNanos() + periodNs); + /** Basic spin lock*/ + private void lock() { + while(!tryLock()){ + org.graalvm.compiler.nodes.PauseNode.pause(); + } + } + private boolean tryLock() { + return lock.compareAndSet(WordFactory.nullPointer(),CurrentIsolate.getCurrentThread()); + } + private void unlock() { + VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + lock.set(WordFactory.nullPointer()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java index e7c7b0d75216..d9725e9c7684 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java @@ -26,7 +26,6 @@ private JfrThrottler getThrottler(long eventId) { } public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { - //TODO may need to protect with mutex JfrThrottler throttler = getThrottler(eventTypeId); if (throttler == null) { //event doesn't support throttling diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java new file mode 100644 index 000000000000..8bd1a04006e9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -0,0 +1,40 @@ +package com.oracle.svm.core.jfr; +import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jfr.JfrTicks; +public class JfrThrottlerWindow { + // reset every rotation + public UninterruptibleUtils.AtomicLong measuredPopSize; // already volatile + public UninterruptibleUtils.AtomicLong endTicks; // already volatile + + // Calculated every rotation based on params set by user and results from previous windows + public long samplingInterval; + + //params set by user + public long eventSampleSize; + public long periodNs; + + public JfrThrottlerWindow(){ + periodNs = 0; + eventSampleSize = 0; + measuredPopSize = new UninterruptibleUtils.AtomicLong(0); + endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + periodNs); + samplingInterval = 1; + } + public boolean isExpired() { + if (JfrTicks.currentTimeNanos() > endTicks.get()) { + return true; + } + return false; + } + + public boolean sample(){ + // Guarantees only one thread can record the last event of the window + long prevMeasuredPopSize = measuredPopSize.incrementAndGet(); + if (prevMeasuredPopSize % samplingInterval != 0 || prevMeasuredPopSize > eventSampleSize) { + return false; + } + return true; + } + + +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java index db2ebbcccaae..29585d0cb294 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java @@ -37,21 +37,22 @@ import com.oracle.svm.core.jfr.JfrThreadLocal; import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalLong; +import com.oracle.svm.core.thread.PlatformThreads; public class ObjectAllocationSampleEvent { private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); - public static void emit(long startTicks, Class clazz, long weight) { + public static void emit(long startTicks, Class clazz) { if (HasJfrSupport.get()) { // TODO: consider moving this to after the isRecording check in emit0 to avoid duplicate checks. Might be a pain to deal with uninterruptibility though. // Doesn't hurt to check twice, might save us some time doing the sampling if (SubstrateJVM.isRecordingInterruptible() && SubstrateJVM.get().isEnabled(JfrEvent.ObjectAllocationSample) && SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { - emit0(startTicks, clazz, weight); + emit0(startTicks, clazz); } } } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(long startTicks, Class clazz, long weight) { + private static void emit0(long startTicks, Class clazz) { if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ObjectAllocationSample)) { JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); // This check is needed to avoid issues upon allocation from the thread that invokes the @@ -59,7 +60,7 @@ private static void emit0(long startTicks, Class clazz, long weight) { if (!jfrThreadLocal.initialized()) { return; } - long currentAllocationSize = com.oracle.svm.core.thread.PlatformThreads.getThreadAllocatedBytes(Thread.currentThread().getId()); + long currentAllocationSize = PlatformThreads.getThreadAllocatedBytes(Thread.currentThread().getId()); JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); From bdc75f2c774335305c2517d9436bc4d7804c94ce Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 25 Jan 2023 13:35:00 -0500 Subject: [PATCH 04/34] debt. tests. EWMA. --- .../com/oracle/svm/core/jfr/JfrThrottler.java | 205 +++++++++++---- .../svm/core/jfr/JfrThrottlerWindow.java | 92 +++++-- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 9 +- .../oracle/svm/test/jfr/TestThrottler.java | 238 ++++++++++++++++++ 4 files changed, 479 insertions(+), 65 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index ccab3e225b80..17399c3632a5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -11,14 +11,14 @@ import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.util.VMError; - /** - * Similar to Hotspot, each event that allows throttling, shall have its own throttler instance. - * An alternative approach could be to use event specific throttling parameters in each event's event settings. + * Similar to Hotspot, each event that allows throttling, shall have its own throttler instance. An + * alternative approach could be to use event specific throttling parameters in each event's event + * settings. * - * Another alternative could be to maintain a set of parameters for each event type within the JfrThrottler singleton. - * But maps cannot be used because allocation cannot be done while on the allocation slow path. - * Maybe the map can be created in hosted mode before any allocation happens + * Another alternative could be to maintain a set of parameters for each event type within the + * JfrThrottler singleton. But maps cannot be used because allocation cannot be done while on the + * allocation slow path. Maybe the map can be created in hosted mode before any allocation happens */ public class JfrThrottler { public UninterruptibleUtils.AtomicBoolean disabled; // already volatile @@ -27,32 +27,56 @@ public class JfrThrottler { private volatile JfrThrottlerWindow activeWindow; public volatile long eventSampleSize; // Race allowed public volatile long periodNs; // Race allowed - private final UninterruptibleUtils.AtomicPointer lock; // Can't use reentrant lock because it could allocate + private double ewmaPopulationSizeAlpha = 0; // only accessed in critical section + private double avgPopulationSize = 0; // only accessed in critical section + + private final int windowDivisor = 5; // Copied from hotspot + private volatile boolean reconfigure; // does it have to be volatile? The same thread will set + // and check this. + private final UninterruptibleUtils.AtomicPointer lock; // Can't use reentrant + // lock because it could + // allocate + + private static final long SECOND_IN_NS = 1000000000; + private static final long MINUTE_IN_NS = SECOND_IN_NS * 60; + private long accumulatedDebtCarryLimit; + private long accumulatedDebtCarryCount; - JfrThrottler() { + public JfrThrottler() { + accumulatedDebtCarryLimit = 0; + accumulatedDebtCarryCount = 0; + reconfigure = false; disabled = new UninterruptibleUtils.AtomicBoolean(true); - lock = new UninterruptibleUtils.AtomicPointer<>(); // I assume this is initially WordFactorynullPointer() + lock = new UninterruptibleUtils.AtomicPointer<>(); // I assume this is initially + // WordFactorynullPointer() window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); - activeWindow = window0; // same as hotspot. Unguarded is ok because all throttler instances are created before program execution. + activeWindow = window0; // same as hotspot. Unguarded is ok because all throttler instances + // are created before program execution. } public boolean setThrottle(long eventSampleSize, long periodMs) { if (eventSampleSize == 0) { disabled.set(true); } + this.eventSampleSize = eventSampleSize; this.periodNs = periodMs * 1000000; + // Blocking lock because new settings MUST be applied. lock(); + reconfigure = true; rotateWindow(); // could omit this and choose to wait until next rotation. unlock(); disabled.set(false); // should be after the above are set return true; } - /** The real active window may change while we're doing the sampling. That's ok as long as we perform operations wrt a consistent window during this method. - That's why we declare a stack variable: window. If the active window does change after we've read it from main memory, there's no harm done because now we'll be - writing to the next window (which gets reset before becoming active again). + /** + * The real active window may change while we're doing the sampling. That's ok as long as we + * perform operations wrt a consistent window during this method. That's why we declare a stack + * variable: "window". If the active window does change after we've read it from main memory, + * there's no harm done because now we'll be writing to the next window (which gets reset before + * becoming active again). */ public boolean sample() { if (disabled.get()) { @@ -64,10 +88,12 @@ public boolean sample() { // Check lock to see if someone is already rotating. If so, move on. if (tryLock()) { // Once in critical section ensure active window is still expired. - // Another thread may have already handled the expired window, or new settings may have already triggered a rotation. + // Another thread may have already handled the expired window, or new settings may + // have already triggered a rotation. if (activeWindow.isExpired()) { rotateWindow(); } + unlock(); } return false; // if expired, hotspot returns false } @@ -75,64 +101,151 @@ public boolean sample() { } /* - Locked. Only one thread should be rotating at once. - If due to an expired window, then other threads that try to rotate due to expiry, will just return false. - If race between two threads updating settings, then one will just have to wait for the other to finish. Order doesn't matter as long as they are not interrupted. + * Locked. Only one thread should be rotating at once. If due to an expired window, then other + * threads that try to rotate due to expiry, will just return false. If race between two threads + * updating settings, then one will just have to wait for the other to finish. Order doesn't + * matter as long as they are not interrupted. */ private void rotateWindow() { - VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); configure(); installNextWindow(); } - private void installNextWindow(){ - VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + private void installNextWindow() { + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); activeWindow = getNextWindow(); } - JfrThrottlerWindow getNextWindow(){ - VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); - if (window0 == activeWindow){ + JfrThrottlerWindow getNextWindow() { + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + if (window0 == activeWindow) { return window1; } return window0; } - public void configure(){ - VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + private long computeAccumulatedDebtCarryLimitHotspot(long windowDurationNs) { + if (periodNs == 0 || windowDurationNs >= SECOND_IN_NS) { + return 1; + } + return SECOND_IN_NS / windowDurationNs; + } + + private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { + if (periodNs == 0 || windowDurationNs > MINUTE_IN_NS) { + return 1; + } + return windowDivisor; // this var isn't available to the class in Hotspot. + } + + private long amortizeDebt(JfrThrottlerWindow lastWindow) { + if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { + accumulatedDebtCarryCount = 1; + return 0; // reset because new settings have been applied + } + accumulatedDebtCarryCount++; + // did we sample less than we were supposed to? + return lastWindow.samplesExpected() - lastWindow.samplesTaken(); // (base samples + carried + // over debt) - samples + // taken + } + + public void configure() { + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); JfrThrottlerWindow next = getNextWindow(); - // Store (possibly) updated params to both windows. - activeWindow.eventSampleSize = eventSampleSize; - activeWindow.periodNs = periodNs; - next.eventSampleSize = eventSampleSize; - next.periodNs = periodNs; - - //use updated params to compute - if (activeWindow.measuredPopSize.get() == 0) { - // Avoid math errors. If population is 0, consider every event. - // Also means for first window on program start, we consider every sample (up until the cap) - next.samplingInterval = 1; + // Store updated params to both windows. + if (reconfigure) { + long windowDurationNs = periodNs / windowDivisor; + accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(windowDurationNs); + accumulatedDebtCarryCount = accumulatedDebtCarryLimit; + activeWindow.samplesPerWindow = eventSampleSize / windowDivisor; // TODO: handle low + // rate so we don't + // always undersample + activeWindow.windowDurationNs = windowDurationNs; + next.samplesPerWindow = eventSampleSize / windowDivisor; + next.windowDurationNs = windowDurationNs; + // compute alpha and debt + avgPopulationSize = 0; + ewmaPopulationSizeAlpha = (double) 1 / windowLookback(next); // lookback count; + reconfigure = false; + } + next.projectedPopSize = projectPopulationSize(activeWindow.measuredPopSize.get()); + + next.configure(amortizeDebt(activeWindow)); + } + + private double windowLookback(JfrThrottlerWindow window) { + + if (window.windowDurationNs <= SECOND_IN_NS) { + return 25.0; + } else if (window.windowDurationNs <= MINUTE_IN_NS) { + return 5.0; } else { - // *** result will be a long. No floating points. long/long = long rounded - next.samplingInterval = activeWindow.measuredPopSize.get() / next.eventSampleSize; // TODO: needs to be more advanced. + return 1.0; } - //reset - next.measuredPopSize.set(0); - next.endTicks.set(JfrTicks.currentTimeNanos() + next.periodNs); + } - /** Basic spin lock*/ + private double projectPopulationSize(long lastWindowMeasuredPop) { + avgPopulationSize = exponentiallyWeightedMovingAverage(lastWindowMeasuredPop, ewmaPopulationSizeAlpha, avgPopulationSize); + return avgPopulationSize; + } + + private static double exponentiallyWeightedMovingAverage(double Y, double alpha, double S) { + return alpha * Y + (1 - alpha) * S; + } + + /** Basic spin lock */ private void lock() { - while(!tryLock()){ - org.graalvm.compiler.nodes.PauseNode.pause(); + while (!tryLock()) { + PauseNode.pause(); } } + private boolean tryLock() { - return lock.compareAndSet(WordFactory.nullPointer(),CurrentIsolate.getCurrentThread()); + return lock.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread()); } + private void unlock() { - VMError.guarantee(!lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); lock.set(WordFactory.nullPointer()); } + + /** Visible for testing. */ + public void beginTest(long eventSampleSize, long periodMs) { + window0.isTest = true; + window1.isTest = true; + window0.testCurrentNanos = 0; + window1.testCurrentNanos = 0; + setThrottle(eventSampleSize, periodMs); + } + + /** Visible for testing. */ + public double getActiveWindowProjectedPopulationSize() { + return activeWindow.projectedPopSize; + } + + /** Visible for testing. */ + public long getActiveWindowSamplingInterval() { + return activeWindow.samplingInterval; + } + + /** Visible for testing. */ + public long getActiveWindowDebt() { + return activeWindow.debt; + } + + /** Visible for testing. */ + public boolean IsActiveWindowExpired() { + return activeWindow.isExpired(); + } + + /** Visible for testing. */ + public void expireActiveWindow() { + window0.testCurrentNanos += periodNs / windowDivisor; + window1.testCurrentNanos += periodNs / windowDivisor; + } + } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index 8bd1a04006e9..5a4900ed2ac2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -1,40 +1,102 @@ package com.oracle.svm.core.jfr; + import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jfr.JfrTicks; + public class JfrThrottlerWindow { // reset every rotation public UninterruptibleUtils.AtomicLong measuredPopSize; // already volatile + public double projectedPopSize; public UninterruptibleUtils.AtomicLong endTicks; // already volatile // Calculated every rotation based on params set by user and results from previous windows public long samplingInterval; - //params set by user - public long eventSampleSize; - public long periodNs; + // params set by user + public long samplesPerWindow; + public long windowDurationNs; + public long debt; - public JfrThrottlerWindow(){ - periodNs = 0; - eventSampleSize = 0; + public JfrThrottlerWindow() { + windowDurationNs = 0; + samplesPerWindow = 0; + projectedPopSize = 0; measuredPopSize = new UninterruptibleUtils.AtomicLong(0); - endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + periodNs); + endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + windowDurationNs); samplingInterval = 1; + debt = 0; } - public boolean isExpired() { - if (JfrTicks.currentTimeNanos() > endTicks.get()) { + + /** + * A rotation of the active window could happen while in this method. If so, then this window + * will be updated as usual, although it is now the "next" window. This results in some wasted + * effort, but doesn't affect correctness because this window will be reset before it becomes + * active again. + */ + public boolean sample() { + // Guarantees only one thread can record the last event of the window + long prevMeasuredPopSize = measuredPopSize.getAndIncrement(); + + // Stop sampling if we're already over the projected population size, and we're over the + // samples per window + if (prevMeasuredPopSize % samplingInterval == 0 && + (prevMeasuredPopSize < projectedPopSize || prevMeasuredPopSize < samplesPerWindow)) { return true; } return false; } - public boolean sample(){ - // Guarantees only one thread can record the last event of the window - long prevMeasuredPopSize = measuredPopSize.incrementAndGet(); - if (prevMeasuredPopSize % samplingInterval != 0 || prevMeasuredPopSize > eventSampleSize) { - return false; + public long samplesTaken() { + if (measuredPopSize.get() > projectedPopSize) { + return samplesExpected(); } - return true; + return measuredPopSize.get() / samplingInterval; + } + + public long samplesExpected() { + return samplesPerWindow + debt; + } + + public void configure(long debt) { + this.debt = debt; + if (projectedPopSize <= samplesPerWindow) { + samplingInterval = 1; + } else { + // It's important to round *up* otherwise we risk violating the upper bound + samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); // TODO: + // needs + // to + // be + // more + // advanced. + } + // reset + measuredPopSize.set(0); + + if (isTest) { + // There is a need to mock JfrTicks for testing. + endTicks.set(testCurrentNanos + windowDurationNs); + } else { + endTicks.set(JfrTicks.currentTimeNanos() + windowDurationNs); + } + + } + + public boolean isExpired() { + if (isTest) { + // There is a need to mock JfrTicks for testing. + if (testCurrentNanos >= endTicks.get()) { + return true; + } + } else if (JfrTicks.currentTimeNanos() >= endTicks.get()) { + return true; + } + return false; } + /** Visible for testing. */ + public volatile boolean isTest = false; + /** Visible for testing. */ + public volatile long testCurrentNanos = 0; } 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 44847c5aaa99..c6d718063215 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 @@ -206,7 +206,8 @@ protected boolean isRecording() { } /** - * Use for time saving checks, not as a check before doing an operation that could result in races. + * Use for time saving checks, not as a check before doing an operation that could result in + * races. */ public static boolean isRecordingInterruptible() { return get().recording; @@ -654,8 +655,8 @@ public boolean isEnabled(JfrEvent event) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean shouldCommit(JfrEvent event) { - //find the right throttler for the event (each event should have its own)like in hotspot - //if none found, return true. + // find the right throttler for the event (each event should have its own)like in hotspot + // if none found, return true. // call into throttler code return jfrThrottlerSupport.shouldCommit(event.getId()); } @@ -664,7 +665,7 @@ public boolean shouldCommit(JfrEvent event) { public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { // find the right throttler for the event and set the new params there return jfrThrottlerSupport.setThrottle(eventTypeId, eventSampleSize, periodMs); - //TODO: why would it ever return false? Maybe if the throttler doesn't exist? + // TODO: why would it ever return false? Maybe if the throttler doesn't exist? } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java new file mode 100644 index 000000000000..20306aad3f7e --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -0,0 +1,238 @@ +/* + * 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 com.oracle.svm.core.jfr.JfrThrottler; +import org.junit.Test; +import org.junit.Assert; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertTrue; + +public class TestThrottler { + + private final long WINDOWS_PER_PERIOD = 5; // Based on hardcoded value in the throttler class. + // Can change later. + private final long WINDOW_DURATION_MS = 10; // Arbitrary. It doesn't matter what this is. + private final long SAMPLES_PER_WINDOW = 10; + + /** + * This test ensures that sampling stops after the cap is hit. Single thread. + */ + @Test + public void testCapSingleThread() { + // Doesn't rotate after starting sampling + + JfrThrottler throttler = new JfrThrottler(); + throttler.setThrottle(SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + for (int i = 0; i < SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD; i++) { + boolean sample = throttler.sample(); + if (i < SAMPLES_PER_WINDOW && !sample) { + Assert.fail("failed! should take sample if under window limit"); + } else if (i >= SAMPLES_PER_WINDOW && sample) { + Assert.fail("failed! should not take sample if over window limit"); + } + } + } + + /** + * This test ensures that sampling stops after the cap is hit, even when multiple threads are + * doing sampling. + */ + @Test + public void testCapConcurrent() throws InterruptedException { + final long samplesPerWindow = 100000; + final AtomicInteger count = new AtomicInteger(); + JfrThrottler throttler = new JfrThrottler(); + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + Runnable doSampling = () -> { + for (int i = 0; i < samplesPerWindow; i++) { + boolean sample = throttler.sample(); + if (sample) { + count.incrementAndGet(); + } + } + }; + count.set(0); + Thread firstThread = new Thread(doSampling); + Thread secondThread = new Thread(doSampling); + Thread thirdThread = new Thread(doSampling); + firstThread.start(); + secondThread.start(); + thirdThread.start(); + firstThread.join(); + secondThread.join(); + thirdThread.join(); + + if (count.get() > samplesPerWindow) { + Assert.fail("failed! Too many samples taken! " + count.get()); + } + // Previous measured population should be 3*samplesPerWindow + // Force window rotation and repeat. + count.set(0); + throttler.setThrottle(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + Thread firstThread1 = new Thread(doSampling); + Thread secondThread1 = new Thread(doSampling); + Thread thirdThread1 = new Thread(doSampling); + firstThread1.start(); + secondThread1.start(); + thirdThread1.start(); + firstThread1.join(); + secondThread1.join(); + thirdThread1.join(); + + if (count.get() > samplesPerWindow) { + Assert.fail("failed! Too many samples taken (after rotation)! " + count.get()); + } + } + + /** + * This test ensures that sampling stops after the cap is hit. Then sampling resumes once the + * window rotates. + */ + @Test + public void testExpiry() { + final long samplesPerWindow = 10; + JfrThrottler throttler = new JfrThrottler(); + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + int count = 0; + + for (int i = 0; i < samplesPerWindow * 10; i++) { + boolean sample = throttler.sample(); + if (sample) { + count++; + } + } + + assertTrue("Should have taken maximum possible samples: " + samplesPerWindow + " but took:" + count, samplesPerWindow == count); + + // rotate window by advancing time forward + expireAndRotate(throttler); + + assertTrue("After window rotation, it should be possible to take more samples", throttler.sample()); + + } + + /** + * This test check that the projected population and sampling interval after a window rotation + * is as expected. + */ + @Test + public void testEWMA() { + final long samplesPerWindow = 10; + JfrThrottler throttler = new JfrThrottler(); + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * 60 * 1000); // period + // is + // 5 + // min + // resulting + // in + // a + // lookback + // of + // 5 + // windows + int[] population = {21, 41, 61, 31, 91, 42, 77, 29, 88, 64, 22, 11, 33, 59}; // Arbitrary + double[] actualProjections = {4.2, 11.56, 21.44, 23.35, 36.88, 37.90, 45.72, 42.38, 51.50, 54.00, 47.60, 40.28, 38.82, 42.86}; // used + // EMWA + // calculator + // to + // determine + + for (int p = 0; p < population.length; p++) { + for (int i = 0; i < population[p]; i++) { + throttler.sample(); + } + expireAndRotate(throttler); + double projectedPopulation = throttler.getActiveWindowProjectedPopulationSize(); + assertTrue((int) actualProjections[p] == (int) projectedPopulation); + } + } + + /** + * Note: computeAccumulatedDebtCarryLimit depends on window duration. + * + * Window lookback for this test is 5. Window duration is 1 minute. Window divisor is default of + * 5. + */ + @Test + public void testDebt() { + final long samplesPerWindow = 10; + final long actualSamplesPerWindow = 50; + JfrThrottler throttler = new JfrThrottler(); + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * 60 * 1000); // period + // is + // 5 + // min + // resulting + // in + // a + // lookback + // of + // 5 + // windows + + for (int p = 0; p < 10; p++) { + for (int i = 0; i < actualSamplesPerWindow; i++) { + throttler.sample(); + } + expireAndRotate(throttler); + } + // now the sampling interval must be 50 / 10 = 5 + assertTrue("Sampling interval is incorrect", throttler.getActiveWindowSamplingInterval() == 5); + + // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 6 + // samples + for (int i = 0; i < 20; i++) { + throttler.sample(); + } + expireAndRotate(throttler); + assertTrue("Should have debt from under sampling.", throttler.getActiveWindowDebt() == 6); + // sampling interval should be 3 now. Take no samples and rotate. Results in accumulated + // debt 6 + 10 + expireAndRotate(throttler); + assertTrue("Should have accumulated debt from under sampling consecutively.", throttler.getActiveWindowDebt() == 16); + expireAndRotate(throttler); + expireAndRotate(throttler); + assertTrue("Debt is so high we should not skip any samples now.", throttler.getActiveWindowSamplingInterval() == 1); + expireAndRotate(throttler); + assertTrue("Debt should be forgiven at beginning of new period.", throttler.getActiveWindowDebt() == 0); + + } + + /** + * Helper method that expires and rotates a throttler's active window + */ + private void expireAndRotate(JfrThrottler throttler) { + throttler.expireActiveWindow(); + assertTrue("should be expired", throttler.IsActiveWindowExpired()); + if (throttler.sample()) { + Assert.fail("Should have rotated not sampled!"); + } + } +} From 59b19f44e84158998dc11030c5b0334ae2664267 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 30 Jan 2023 18:26:10 -0500 Subject: [PATCH 05/34] normalize and set low rate. tests pass --- .../com/oracle/svm/core/jfr/JfrThrottler.java | 149 +++++++++++++++--- .../svm/core/jfr/JfrThrottlerWindow.java | 12 +- .../events/ObjectAllocationSampleEvent.java | 2 +- .../oracle/svm/test/jfr/TestThrottler.java | 117 +++++++------- 4 files changed, 182 insertions(+), 98 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 17399c3632a5..c8165bd4c7a8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -21,24 +21,34 @@ * allocation slow path. Maybe the map can be created in hosted mode before any allocation happens */ public class JfrThrottler { - public UninterruptibleUtils.AtomicBoolean disabled; // already volatile + public UninterruptibleUtils.AtomicBoolean disabled; // Already volatile private JfrThrottlerWindow window0; // Race allowed private JfrThrottlerWindow window1; // Race allowed private volatile JfrThrottlerWindow activeWindow; public volatile long eventSampleSize; // Race allowed public volatile long periodNs; // Race allowed - private double ewmaPopulationSizeAlpha = 0; // only accessed in critical section - private double avgPopulationSize = 0; // only accessed in critical section - - private final int windowDivisor = 5; // Copied from hotspot - private volatile boolean reconfigure; // does it have to be volatile? The same thread will set - // and check this. - private final UninterruptibleUtils.AtomicPointer lock; // Can't use reentrant - // lock because it could - // allocate + // Only accessed in critical section + private double ewmaPopulationSizeAlpha = 0; + // Only accessed in critical section + private double avgPopulationSize = 0; + // Copied from hotspot + private final int windowDivisor = 5; + // does it have to be volatile? The same thread will set and check this. + private volatile boolean reconfigure; + // Can't use reentrant lock because it allocates + private final UninterruptibleUtils.AtomicPointer lock; private static final long SECOND_IN_NS = 1000000000; + private static final long SECOND_IN_MS = 1000; private static final long MINUTE_IN_NS = SECOND_IN_NS * 60; + private static final long MINUTE_IN_MS = SECOND_IN_MS *60; + private static final long HOUR_IN_MS = MINUTE_IN_MS * 60; + private static final long HOUR_IN_NS = MINUTE_IN_NS * 60; + private static final long DAY_IN_MS = HOUR_IN_MS * 24; + private static final long DAY_IN_NS = HOUR_IN_NS * 24; + private static final long TEN_PER_S_IN_MINUTES = 600; + private static final long TEN_PER_S_IN_HOURS = 36000; + private static final long TEN_PER_S_IN_DAYS = 864000; private long accumulatedDebtCarryLimit; private long accumulatedDebtCarryCount; @@ -55,15 +65,60 @@ public JfrThrottler() { // are created before program execution. } + /** + * Convert rate to samples/second if possible. + * Want to avoid long period with large windows with a large number of samples per window + * in favor of many smaller windows. + * This is in the critical section because setting the sample size and period must be done together atomically. + * Otherwise, we risk a window's params being set with only one of the two updated. + */ + private void normalize1(long eventSampleSize, long periodMs){ + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + // Do we want more than 10samples/s ? If so convert to samples/s + if (periodMs <= SECOND_IN_MS){ + //nothing + } else if (periodMs <= MINUTE_IN_MS) { + if (eventSampleSize >= TEN_PER_S_IN_MINUTES) { + eventSampleSize /= 60; + periodMs /= 60; + } + } else if (periodMs <= HOUR_IN_MS) { + if (eventSampleSize >=TEN_PER_S_IN_HOURS) { + eventSampleSize /= 3600; + periodMs /= 3600; + } + } else if (periodMs <= DAY_IN_MS) { + if (eventSampleSize >=TEN_PER_S_IN_DAYS) { + eventSampleSize /= 86400; + periodMs /= 86400; + } + } + this.eventSampleSize = eventSampleSize; + this.periodNs = periodMs * 1000000; + } + private void normalize(double samplesPerPeriod, double periodMs){ + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + // Do we want more than 10samples/s ? If so convert to samples/s + double periodsPerSecond = 1000.0/ periodMs; + double samplesPerSecond = samplesPerPeriod * periodsPerSecond; + if (samplesPerSecond >= 10) { + this.periodNs = SECOND_IN_NS; + this.eventSampleSize = (long) samplesPerSecond; + return; + } + + this.eventSampleSize = eventSampleSize; + this.periodNs = (long) periodMs * 1000000; + } + public boolean setThrottle(long eventSampleSize, long periodMs) { if (eventSampleSize == 0) { disabled.set(true); } - this.eventSampleSize = eventSampleSize; - this.periodNs = periodMs * 1000000; // Blocking lock because new settings MUST be applied. lock(); + normalize(eventSampleSize, periodMs); reconfigure = true; rotateWindow(); // could omit this and choose to wait until next rotation. unlock(); @@ -133,6 +188,7 @@ private long computeAccumulatedDebtCarryLimitHotspot(long windowDurationNs) { } private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); if (periodNs == 0 || windowDurationNs > MINUTE_IN_NS) { return 1; } @@ -140,6 +196,7 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { } private long amortizeDebt(JfrThrottlerWindow lastWindow) { + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { accumulatedDebtCarryCount = 1; return 0; // reset because new settings have been applied @@ -151,33 +208,64 @@ private long amortizeDebt(JfrThrottlerWindow lastWindow) { // taken } + /** + * Handles the case where the sampling rate is very low. + */ + private void setSamplePointsAndWindowDuration1(){ + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); + JfrThrottlerWindow next = getNextWindow(); + long samplesPerWindow = eventSampleSize / windowDivisor; + long windowDurationNs = periodNs / windowDivisor; + if (eventSampleSize < 10 + || periodNs >= MINUTE_IN_NS && eventSampleSize < TEN_PER_S_IN_MINUTES + || periodNs >= HOUR_IN_NS && eventSampleSize < TEN_PER_S_IN_HOURS + || periodNs >= DAY_IN_NS && eventSampleSize < TEN_PER_S_IN_DAYS){ + samplesPerWindow = eventSampleSize; + windowDurationNs = periodNs; + } + activeWindow.samplesPerWindow = samplesPerWindow; + activeWindow.windowDurationNs = windowDurationNs; + next.samplesPerWindow = samplesPerWindow; + next.windowDurationNs = windowDurationNs; + } + private void setSamplePointsAndWindowDuration(){ + VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); + JfrThrottlerWindow next = getNextWindow(); + long samplesPerWindow = eventSampleSize / windowDivisor; + long windowDurationNs = periodNs / windowDivisor; + // If period isn't 1s, then we're effectively taking under 10 samples/s + // because the values have already undergone normalization. + if (eventSampleSize < 10 || periodNs > SECOND_IN_NS){ + samplesPerWindow = eventSampleSize; + windowDurationNs = periodNs; + } + activeWindow.samplesPerWindow = samplesPerWindow; + activeWindow.windowDurationNs = windowDurationNs; + next.samplesPerWindow = samplesPerWindow; + next.windowDurationNs = windowDurationNs; + } + public void configure() { VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); JfrThrottlerWindow next = getNextWindow(); // Store updated params to both windows. if (reconfigure) { - long windowDurationNs = periodNs / windowDivisor; - accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(windowDurationNs); + setSamplePointsAndWindowDuration(); + accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(next.windowDurationNs); accumulatedDebtCarryCount = accumulatedDebtCarryLimit; - activeWindow.samplesPerWindow = eventSampleSize / windowDivisor; // TODO: handle low - // rate so we don't - // always undersample - activeWindow.windowDurationNs = windowDurationNs; - next.samplesPerWindow = eventSampleSize / windowDivisor; - next.windowDurationNs = windowDurationNs; // compute alpha and debt avgPopulationSize = 0; ewmaPopulationSizeAlpha = (double) 1 / windowLookback(next); // lookback count; reconfigure = false; } - next.projectedPopSize = projectPopulationSize(activeWindow.measuredPopSize.get()); - next.configure(amortizeDebt(activeWindow)); + next.configure(amortizeDebt(activeWindow), projectPopulationSize(activeWindow.measuredPopSize.get())); } private double windowLookback(JfrThrottlerWindow window) { - if (window.windowDurationNs <= SECOND_IN_NS) { return 25.0; } else if (window.windowDurationNs <= MINUTE_IN_NS) { @@ -185,7 +273,6 @@ private double windowLookback(JfrThrottlerWindow window) { } else { return 1.0; } - } private double projectPopulationSize(long lastWindowMeasuredPop) { @@ -241,11 +328,21 @@ public long getActiveWindowDebt() { public boolean IsActiveWindowExpired() { return activeWindow.isExpired(); } - + /** Visible for testing. */ + public long getPeriodNs() { + return periodNs; + } + /** Visible for testing. */ + public long getEventSampleSize() { + return eventSampleSize; + } /** Visible for testing. */ public void expireActiveWindow() { + if (eventSampleSize < 10 || periodNs > SECOND_IN_NS){ + window0.testCurrentNanos += periodNs; + window1.testCurrentNanos += periodNs; + } window0.testCurrentNanos += periodNs / windowDivisor; window1.testCurrentNanos += periodNs / windowDivisor; } - } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index 5a4900ed2ac2..f4172c3a9ea4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -6,11 +6,11 @@ public class JfrThrottlerWindow { // reset every rotation public UninterruptibleUtils.AtomicLong measuredPopSize; // already volatile - public double projectedPopSize; public UninterruptibleUtils.AtomicLong endTicks; // already volatile // Calculated every rotation based on params set by user and results from previous windows public long samplingInterval; + public double projectedPopSize; // params set by user public long samplesPerWindow; @@ -57,18 +57,14 @@ public long samplesExpected() { return samplesPerWindow + debt; } - public void configure(long debt) { + public void configure(long debt, double projectedPopSize) { this.debt = debt; + this.projectedPopSize = projectedPopSize; if (projectedPopSize <= samplesPerWindow) { samplingInterval = 1; } else { // It's important to round *up* otherwise we risk violating the upper bound - samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); // TODO: - // needs - // to - // be - // more - // advanced. + samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); } // reset measuredPopSize.set(0); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java index 29585d0cb294..696503671d96 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java @@ -43,7 +43,7 @@ public class ObjectAllocationSampleEvent { private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); public static void emit(long startTicks, Class clazz) { if (HasJfrSupport.get()) { - // TODO: consider moving this to after the isRecording check in emit0 to avoid duplicate checks. Might be a pain to deal with uninterruptibility though. + // TODO: consider moving this to after the isRecording check in emit0 to avoid duplicate checks. Might be a pain to deal with uninterruptibility though. Also we want to minimize uninterruptible code usage. // Doesn't hurt to check twice, might save us some time doing the sampling if (SubstrateJVM.isRecordingInterruptible() && SubstrateJVM.get().isEnabled(JfrEvent.ObjectAllocationSample) && SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { emit0(startTicks, clazz); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 20306aad3f7e..74d643f6ec0c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -32,17 +32,20 @@ import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class TestThrottler { - private final long WINDOWS_PER_PERIOD = 5; // Based on hardcoded value in the throttler class. - // Can change later. - private final long WINDOW_DURATION_MS = 10; // Arbitrary. It doesn't matter what this is. + // Based on hardcoded value in the throttler class. + private final long WINDOWS_PER_PERIOD = 5; + // Arbitrary. It doesn't matter what this is. + private final long WINDOW_DURATION_MS = 200; private final long SAMPLES_PER_WINDOW = 10; /** - * This test ensures that sampling stops after the cap is hit. Single thread. + * This is the simplest test that ensures that sampling stops after the cap is hit. Single thread. + * All sampling is done within the first window. No rotations. */ @Test public void testCapSingleThread() { @@ -52,11 +55,8 @@ public void testCapSingleThread() { throttler.setThrottle(SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); for (int i = 0; i < SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD; i++) { boolean sample = throttler.sample(); - if (i < SAMPLES_PER_WINDOW && !sample) { - Assert.fail("failed! should take sample if under window limit"); - } else if (i >= SAMPLES_PER_WINDOW && sample) { - Assert.fail("failed! should not take sample if over window limit"); - } + assertFalse("failed! should take sample if under window limit", i < SAMPLES_PER_WINDOW && !sample); + assertFalse("failed! should not take sample if over window limit", i >= SAMPLES_PER_WINDOW && sample); } } @@ -89,26 +89,22 @@ public void testCapConcurrent() throws InterruptedException { secondThread.join(); thirdThread.join(); - if (count.get() > samplesPerWindow) { - Assert.fail("failed! Too many samples taken! " + count.get()); - } + assertFalse("failed! Too many samples taken! " + count.get(), count.get() > samplesPerWindow); // Previous measured population should be 3*samplesPerWindow // Force window rotation and repeat. count.set(0); throttler.setThrottle(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); - Thread firstThread1 = new Thread(doSampling); - Thread secondThread1 = new Thread(doSampling); - Thread thirdThread1 = new Thread(doSampling); - firstThread1.start(); - secondThread1.start(); - thirdThread1.start(); - firstThread1.join(); - secondThread1.join(); - thirdThread1.join(); - - if (count.get() > samplesPerWindow) { - Assert.fail("failed! Too many samples taken (after rotation)! " + count.get()); - } + Thread fourthThread = new Thread(doSampling); + Thread fifthThread = new Thread(doSampling); + Thread sixthThread = new Thread(doSampling); + fourthThread.start(); + fifthThread.start(); + sixthThread.start(); + fourthThread.join(); + fifthThread.join(); + sixthThread.join(); + + assertFalse("failed! Too many samples taken (after rotation)! " + count.get(), count.get() > samplesPerWindow); } /** @@ -139,30 +135,21 @@ public void testExpiry() { } /** - * This test check that the projected population and sampling interval after a window rotation - * is as expected. + * This test checks that the projected population and sampling interval after a window rotation + * is as expected. Window lookback for this test is 5. + * + * Will be low rate, which results in lookback of 5, because window duration is period. */ @Test public void testEWMA() { - final long samplesPerWindow = 10; + final long samplesPerWindow = 1000; JfrThrottler throttler = new JfrThrottler(); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * 60 * 1000); // period - // is - // 5 - // min - // resulting - // in - // a - // lookback - // of - // 5 - // windows + // Period is 5min resulting in a lookback of 5 windows + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, 60 * 1000); + int[] population = {21, 41, 61, 31, 91, 42, 77, 29, 88, 64, 22, 11, 33, 59}; // Arbitrary - double[] actualProjections = {4.2, 11.56, 21.44, 23.35, 36.88, 37.90, 45.72, 42.38, 51.50, 54.00, 47.60, 40.28, 38.82, 42.86}; // used - // EMWA - // calculator - // to - // determine + // Used EWMA calculator to confirm actualProjections + double[] actualProjections = {4.2, 11.56, 21.44, 23.35, 36.88, 37.90, 45.72, 42.38, 51.50, 54.00, 47.60, 40.28, 38.82, 42.86}; for (int p = 0; p < population.length; p++) { for (int i = 0; i < population[p]; i++) { @@ -177,34 +164,23 @@ public void testEWMA() { /** * Note: computeAccumulatedDebtCarryLimit depends on window duration. * - * Window lookback for this test is 5. Window duration is 1 minute. Window divisor is default of - * 5. + * Window lookback for this test is 25. Window duration is 1 second. Window divisor is default of 5. */ @Test public void testDebt() { final long samplesPerWindow = 10; final long actualSamplesPerWindow = 50; JfrThrottler throttler = new JfrThrottler(); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * 60 * 1000); // period - // is - // 5 - // min - // resulting - // in - // a - // lookback - // of - // 5 - // windows - - for (int p = 0; p < 10; p++) { + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * WINDOW_DURATION_MS); + + for (int p = 0; p < 50; p++) { for (int i = 0; i < actualSamplesPerWindow; i++) { throttler.sample(); } expireAndRotate(throttler); } // now the sampling interval must be 50 / 10 = 5 - assertTrue("Sampling interval is incorrect", throttler.getActiveWindowSamplingInterval() == 5); + assertTrue("Sampling interval is incorrect:"+ throttler.getActiveWindowSamplingInterval(), throttler.getActiveWindowSamplingInterval() == 5); // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 6 // samples @@ -225,14 +201,29 @@ public void testDebt() { } + /** + * Tests normalization of sample size and period. + */ + @Test + public void testNormalization() { + long sampleSize = 10 * 600; + long periodMs = 60*1000; + JfrThrottler throttler = new JfrThrottler(); + throttler.beginTest(sampleSize, periodMs); + assertTrue(throttler.getPeriodNs()+" "+ throttler.getEventSampleSize(),throttler.getEventSampleSize()==sampleSize/60 && throttler.getPeriodNs() == 1000000000); + + sampleSize = 10*3600; + periodMs = 3600*1000; + throttler.setThrottle(sampleSize, periodMs); + assertTrue(throttler.getPeriodNs()+" "+ throttler.getEventSampleSize(), throttler.getEventSampleSize()==sampleSize/3600 && throttler.getPeriodNs() == 1000000000); + } + /** * Helper method that expires and rotates a throttler's active window */ private void expireAndRotate(JfrThrottler throttler) { throttler.expireActiveWindow(); assertTrue("should be expired", throttler.IsActiveWindowExpired()); - if (throttler.sample()) { - Assert.fail("Should have rotated not sampled!"); - } + assertFalse("Should have rotated not sampled!", throttler.sample()); } } From 26ab73c4b3dc3ecc2faaf021a9d5cfd3c1d9cf78 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 5 May 2023 15:23:37 -0400 Subject: [PATCH 06/34] minor updates, comments, text adjustment --- .../genscavenge/ThreadLocalAllocation.java | 4 --- .../svm/core/jfr/JfrRecorderThread.java | 1 - .../oracle/svm/core/jfr/JfrThreadLocal.java | 8 ----- .../com/oracle/svm/core/jfr/JfrThrottler.java | 36 ++++++++++--------- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 9 ----- .../events/ObjectAllocationSampleEvent.java | 16 +++------ .../oracle/svm/test/jfr/TestThrottler.java | 11 ++++-- 7 files changed, 32 insertions(+), 53 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index ccf6716bd6d8..6f6b9d76f92a 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -319,12 +319,8 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un } return array; } finally { -<<<<<<< HEAD ObjectAllocationInNewTLABEvent.emit(startTicks, hub, size, tlabSize); - ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub), 0); -======= ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub)); ->>>>>>> 7bf8d435e5a (evenly space out samples based on previous window) DeoptTester.enableDeoptTesting(); } } 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 7f6781619304..cf26ef4c4ce9 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 @@ -83,7 +83,6 @@ public void run() { } } } catch (Throwable e) { - System.out.println(e); VMError.shouldNotReachHere("No exception must by thrown in the JFR recorder thread as this could break file IO operations."); } } 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 9625ff4f0d0b..6721e6994293 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 @@ -304,7 +304,6 @@ private static JfrBuffer reinstateJavaBuffer(JfrBuffer buffer) { return buffer; } -<<<<<<< HEAD @Uninterruptible(reason = "Accesses a JFR buffer.") public JfrBuffer getJavaBuffer() { JfrBuffer buffer = javaBuffer.get(); @@ -324,13 +323,6 @@ public JfrBuffer getJavaBuffer() { return buffer; } - @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) - public boolean initialized() { - // Thread start event must have already been emitted. - return threadId.get() > 0 && (javaBuffer.get().isNonNull() || nativeBuffer.get().isNonNull()); - } - - @Uninterruptible(reason = "Accesses a JFR buffer.", callerMustBe = true) public JfrBuffer getNativeBuffer() { JfrBuffer buffer = nativeBuffer.get(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index c8165bd4c7a8..d769b3c71149 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -21,23 +21,6 @@ * allocation slow path. Maybe the map can be created in hosted mode before any allocation happens */ public class JfrThrottler { - public UninterruptibleUtils.AtomicBoolean disabled; // Already volatile - private JfrThrottlerWindow window0; // Race allowed - private JfrThrottlerWindow window1; // Race allowed - private volatile JfrThrottlerWindow activeWindow; - public volatile long eventSampleSize; // Race allowed - public volatile long periodNs; // Race allowed - // Only accessed in critical section - private double ewmaPopulationSizeAlpha = 0; - // Only accessed in critical section - private double avgPopulationSize = 0; - // Copied from hotspot - private final int windowDivisor = 5; - // does it have to be volatile? The same thread will set and check this. - private volatile boolean reconfigure; - // Can't use reentrant lock because it allocates - private final UninterruptibleUtils.AtomicPointer lock; - private static final long SECOND_IN_NS = 1000000000; private static final long SECOND_IN_MS = 1000; private static final long MINUTE_IN_NS = SECOND_IN_NS * 60; @@ -49,6 +32,20 @@ public class JfrThrottler { private static final long TEN_PER_S_IN_MINUTES = 600; private static final long TEN_PER_S_IN_HOURS = 36000; private static final long TEN_PER_S_IN_DAYS = 864000; + // Can't use reentrant lock because it allocates + private final UninterruptibleUtils.AtomicPointer lock; + // Copied from hotspot + private final int windowDivisor = 5; + + public UninterruptibleUtils.AtomicBoolean disabled; // Already volatile + private JfrThrottlerWindow window0; + private JfrThrottlerWindow window1; + private volatile JfrThrottlerWindow activeWindow; + public volatile long eventSampleSize; + public volatile long periodNs; + private volatile double ewmaPopulationSizeAlpha = 0; + private volatile double avgPopulationSize = 0; + private volatile boolean reconfigure; private long accumulatedDebtCarryLimit; private long accumulatedDebtCarryCount; @@ -255,6 +252,7 @@ public void configure() { if (reconfigure) { setSamplePointsAndWindowDuration(); accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(next.windowDurationNs); + // This effectively means we reset debt count upon reconfigure accumulatedDebtCarryCount = accumulatedDebtCarryLimit; // compute alpha and debt avgPopulationSize = 0; @@ -324,6 +322,10 @@ public long getActiveWindowDebt() { return activeWindow.debt; } + public double getWindowLookback() { + return windowLookback(activeWindow); + } + /** Visible for testing. */ public boolean IsActiveWindowExpired() { return activeWindow.isExpired(); 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 c6d718063215..094d90fa52cf 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 @@ -205,14 +205,6 @@ protected boolean isRecording() { return recording; } - /** - * Use for time saving checks, not as a check before doing an operation that could result in - * races. - */ - public static boolean isRecordingInterruptible() { - return get().recording; - } - /** * See {@link JVM#createJFR}. Until {@link #beginRecording} is executed, no JFR events can be * triggered yet. So, we don't need to take any precautions here. @@ -653,7 +645,6 @@ public boolean isEnabled(JfrEvent event) { return eventSettings[(int) event.getId()].isEnabled(); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean shouldCommit(JfrEvent event) { // find the right throttler for the event (each event should have its own)like in hotspot // if none found, return true. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java index 696503671d96..d76f461313e5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java @@ -41,26 +41,20 @@ public class ObjectAllocationSampleEvent { private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); - public static void emit(long startTicks, Class clazz) { + public static void emit(long startTicks, Class clazz) { if (HasJfrSupport.get()) { // TODO: consider moving this to after the isRecording check in emit0 to avoid duplicate checks. Might be a pain to deal with uninterruptibility though. Also we want to minimize uninterruptible code usage. // Doesn't hurt to check twice, might save us some time doing the sampling - if (SubstrateJVM.isRecordingInterruptible() && SubstrateJVM.get().isEnabled(JfrEvent.ObjectAllocationSample) && SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { + if (SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { emit0(startTicks, clazz); } } } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(long startTicks, Class clazz) { - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ObjectAllocationSample)) { - JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); - // This check is needed to avoid issues upon allocation from the thread that invokes the - // Java main method and shutdown. - if (!jfrThreadLocal.initialized()) { - return; - } - long currentAllocationSize = PlatformThreads.getThreadAllocatedBytes(Thread.currentThread().getId()); + private static void emit0(long startTicks, Class clazz) { + if (JfrEvent.ObjectAllocationSample.shouldEmit()) { + long currentAllocationSize = PlatformThreads.getThreadAllocatedBytes(com.oracle.svm.core.thread.JavaThreads.getCurrentThreadId()); JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 74d643f6ec0c..ef0a1df454cc 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -142,10 +142,11 @@ public void testExpiry() { */ @Test public void testEWMA() { - final long samplesPerWindow = 1000; + final long samplesPerWindow = 100; JfrThrottler throttler = new JfrThrottler(); - // Period is 5min resulting in a lookback of 5 windows + // Samples per second is low (<10) resulting in a windowDuration being un-normalized (1 min) resulting in a window lookback of 5.0 throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, 60 * 1000); + assertTrue(throttler.getWindowLookback() == 5.0); int[] population = {21, 41, 61, 31, 91, 42, 77, 29, 88, 64, 22, 11, 33, 59}; // Arbitrary // Used EWMA calculator to confirm actualProjections @@ -157,7 +158,11 @@ public void testEWMA() { } expireAndRotate(throttler); double projectedPopulation = throttler.getActiveWindowProjectedPopulationSize(); - assertTrue((int) actualProjections[p] == (int) projectedPopulation); + if ((int) actualProjections[p] != (int) projectedPopulation) + { + System.out.println(actualProjections[p]+ " "+ projectedPopulation); + } +// assertTrue((int) actualProjections[p] == (int) projectedPopulation); } } From f4ce1689372d6d40b003ac49403a50bbb9697369 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 9 May 2023 10:24:32 -0400 Subject: [PATCH 07/34] improve tests --- .../oracle/svm/test/jfr/TestThrottler.java | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index ef0a1df454cc..8901aa40e22e 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -30,6 +30,8 @@ import org.junit.Test; import org.junit.Assert; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertFalse; @@ -50,7 +52,6 @@ public class TestThrottler { @Test public void testCapSingleThread() { // Doesn't rotate after starting sampling - JfrThrottler throttler = new JfrThrottler(); throttler.setThrottle(SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); for (int i = 0; i < SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD; i++) { @@ -67,7 +68,9 @@ public void testCapSingleThread() { @Test public void testCapConcurrent() throws InterruptedException { final long samplesPerWindow = 100000; + final int testingThreadCount = 10; final AtomicInteger count = new AtomicInteger(); + List testingThreads = new ArrayList<>(); JfrThrottler throttler = new JfrThrottler(); throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); Runnable doSampling = () -> { @@ -79,30 +82,29 @@ public void testCapConcurrent() throws InterruptedException { } }; count.set(0); - Thread firstThread = new Thread(doSampling); - Thread secondThread = new Thread(doSampling); - Thread thirdThread = new Thread(doSampling); - firstThread.start(); - secondThread.start(); - thirdThread.start(); - firstThread.join(); - secondThread.join(); - thirdThread.join(); + + for (int i = 0; i < testingThreadCount; i++) { + Thread worker = new Thread(doSampling); + worker.start(); + testingThreads.add(worker); + } + for (Thread thread : testingThreads) { + thread.join(); + } assertFalse("failed! Too many samples taken! " + count.get(), count.get() > samplesPerWindow); // Previous measured population should be 3*samplesPerWindow // Force window rotation and repeat. count.set(0); - throttler.setThrottle(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); - Thread fourthThread = new Thread(doSampling); - Thread fifthThread = new Thread(doSampling); - Thread sixthThread = new Thread(doSampling); - fourthThread.start(); - fifthThread.start(); - sixthThread.start(); - fourthThread.join(); - fifthThread.join(); - sixthThread.join(); + expireAndRotate(throttler); + for (int i = 0; i < testingThreadCount; i++) { + Thread worker = new Thread(doSampling); + worker.start(); + testingThreads.add(worker); + } + for (Thread thread : testingThreads) { + thread.join(); + } assertFalse("failed! Too many samples taken (after rotation)! " + count.get(), count.get() > samplesPerWindow); } @@ -131,7 +133,6 @@ public void testExpiry() { expireAndRotate(throttler); assertTrue("After window rotation, it should be possible to take more samples", throttler.sample()); - } /** @@ -158,11 +159,7 @@ public void testEWMA() { } expireAndRotate(throttler); double projectedPopulation = throttler.getActiveWindowProjectedPopulationSize(); - if ((int) actualProjections[p] != (int) projectedPopulation) - { - System.out.println(actualProjections[p]+ " "+ projectedPopulation); - } -// assertTrue((int) actualProjections[p] == (int) projectedPopulation); + assertTrue((int) actualProjections[p] == (int) projectedPopulation); } } @@ -203,7 +200,6 @@ public void testDebt() { assertTrue("Debt is so high we should not skip any samples now.", throttler.getActiveWindowSamplingInterval() == 1); expireAndRotate(throttler); assertTrue("Debt should be forgiven at beginning of new period.", throttler.getActiveWindowDebt() == 0); - } /** From 1d6f2f4a033c1ee168db30329764ae3ee6027010 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 19 Jun 2023 10:43:13 -0400 Subject: [PATCH 08/34] use VMMutex instead of spinlock. Minor tweaks --- .../com/oracle/svm/core/jfr/JfrThrottler.java | 107 +++++++++--------- .../svm/core/jfr/JfrThrottlerSupport.java | 3 +- .../svm/core/jfr/JfrThrottlerWindow.java | 10 +- .../oracle/svm/test/jfr/TestThrottler.java | 18 +-- 4 files changed, 70 insertions(+), 68 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index d769b3c71149..06ddd9f7ca83 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -10,6 +10,7 @@ import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.locks.VMMutex; /** * Similar to Hotspot, each event that allows throttling, shall have its own throttler instance. An @@ -32,10 +33,13 @@ public class JfrThrottler { private static final long TEN_PER_S_IN_MINUTES = 600; private static final long TEN_PER_S_IN_HOURS = 36000; private static final long TEN_PER_S_IN_DAYS = 864000; + // Copied from hotspot + private static final int WINDOW_DIVISOR = 5; + // Can't use reentrant lock because it allocates private final UninterruptibleUtils.AtomicPointer lock; - // Copied from hotspot - private final int windowDivisor = 5; + private final VMMutex mutex; + public UninterruptibleUtils.AtomicBoolean disabled; // Already volatile private JfrThrottlerWindow window0; @@ -49,7 +53,7 @@ public class JfrThrottler { private long accumulatedDebtCarryLimit; private long accumulatedDebtCarryCount; - public JfrThrottler() { + public JfrThrottler(com.oracle.svm.core.locks.VMMutex mutex) { accumulatedDebtCarryLimit = 0; accumulatedDebtCarryCount = 0; reconfigure = false; @@ -60,6 +64,7 @@ public JfrThrottler() { window1 = new JfrThrottlerWindow(); activeWindow = window0; // same as hotspot. Unguarded is ok because all throttler instances // are created before program execution. + this.mutex = mutex; } /** @@ -70,7 +75,7 @@ public JfrThrottler() { * Otherwise, we risk a window's params being set with only one of the two updated. */ private void normalize1(long eventSampleSize, long periodMs){ - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); // Do we want more than 10samples/s ? If so convert to samples/s if (periodMs <= SECOND_IN_MS){ //nothing @@ -93,8 +98,8 @@ private void normalize1(long eventSampleSize, long periodMs){ this.eventSampleSize = eventSampleSize; this.periodNs = periodMs * 1000000; } - private void normalize(double samplesPerPeriod, double periodMs){ - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + private void normalize(long samplesPerPeriod, double periodMs){ + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); // Do we want more than 10samples/s ? If so convert to samples/s double periodsPerSecond = 1000.0/ periodMs; double samplesPerSecond = samplesPerPeriod * periodsPerSecond; @@ -104,7 +109,7 @@ private void normalize(double samplesPerPeriod, double periodMs){ return; } - this.eventSampleSize = eventSampleSize; + this.eventSampleSize = samplesPerPeriod; this.periodNs = (long) periodMs * 1000000; } @@ -114,11 +119,11 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { } // Blocking lock because new settings MUST be applied. - lock(); + mutex.lock(); normalize(eventSampleSize, periodMs); reconfigure = true; rotateWindow(); // could omit this and choose to wait until next rotation. - unlock(); + mutex.unlock(); disabled.set(false); // should be after the above are set return true; } @@ -138,15 +143,14 @@ public boolean sample() { boolean expired = window.isExpired(); if (expired) { // Check lock to see if someone is already rotating. If so, move on. - if (tryLock()) { - // Once in critical section ensure active window is still expired. - // Another thread may have already handled the expired window, or new settings may - // have already triggered a rotation. - if (activeWindow.isExpired()) { - rotateWindow(); - } - unlock(); + mutex.lock(); // TODO: would be better to tryLock() if possible. + // Once in critical section ensure active window is still expired. + // Another thread may have already handled the expired window, or new settings may + // have already triggered a rotation. + if (activeWindow.isExpired()) { + rotateWindow(); } + mutex.unlock(); return false; // if expired, hotspot returns false } return window.sample(); @@ -159,41 +163,34 @@ public boolean sample() { * matter as long as they are not interrupted. */ private void rotateWindow() { - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); configure(); installNextWindow(); } private void installNextWindow() { - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); activeWindow = getNextWindow(); } JfrThrottlerWindow getNextWindow() { - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); if (window0 == activeWindow) { return window1; } return window0; } - private long computeAccumulatedDebtCarryLimitHotspot(long windowDurationNs) { - if (periodNs == 0 || windowDurationNs >= SECOND_IN_NS) { - return 1; - } - return SECOND_IN_NS / windowDurationNs; - } - private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); - if (periodNs == 0 || windowDurationNs > MINUTE_IN_NS) { + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); + if (periodNs == 0 || windowDurationNs >= SECOND_IN_NS) { return 1; } - return windowDivisor; // this var isn't available to the class in Hotspot. + return WINDOW_DIVISOR; // this var isn't available to the class in Hotspot. } private long amortizeDebt(JfrThrottlerWindow lastWindow) { - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { accumulatedDebtCarryCount = 1; return 0; // reset because new settings have been applied @@ -209,11 +206,11 @@ private long amortizeDebt(JfrThrottlerWindow lastWindow) { * Handles the case where the sampling rate is very low. */ private void setSamplePointsAndWindowDuration1(){ - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); JfrThrottlerWindow next = getNextWindow(); - long samplesPerWindow = eventSampleSize / windowDivisor; - long windowDurationNs = periodNs / windowDivisor; + long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; + long windowDurationNs = periodNs / WINDOW_DIVISOR; if (eventSampleSize < 10 || periodNs >= MINUTE_IN_NS && eventSampleSize < TEN_PER_S_IN_MINUTES || periodNs >= HOUR_IN_NS && eventSampleSize < TEN_PER_S_IN_HOURS @@ -227,11 +224,11 @@ private void setSamplePointsAndWindowDuration1(){ next.windowDurationNs = windowDurationNs; } private void setSamplePointsAndWindowDuration(){ - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); JfrThrottlerWindow next = getNextWindow(); - long samplesPerWindow = eventSampleSize / windowDivisor; - long windowDurationNs = periodNs / windowDivisor; + long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; + long windowDurationNs = periodNs / WINDOW_DIVISOR; // If period isn't 1s, then we're effectively taking under 10 samples/s // because the values have already undergone normalization. if (eventSampleSize < 10 || periodNs > SECOND_IN_NS){ @@ -245,7 +242,7 @@ private void setSamplePointsAndWindowDuration(){ } public void configure() { - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); + VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); JfrThrottlerWindow next = getNextWindow(); // Store updated params to both windows. @@ -282,21 +279,21 @@ private static double exponentiallyWeightedMovingAverage(double Y, double alpha, return alpha * Y + (1 - alpha) * S; } - /** Basic spin lock */ - private void lock() { - while (!tryLock()) { - PauseNode.pause(); - } - } - - private boolean tryLock() { - return lock.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread()); - } - - private void unlock() { - VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); - lock.set(WordFactory.nullPointer()); - } +// /** Basic spin lock */ +// private void lock() { +// while (!tryLock()) { +// PauseNode.pause(); +// } +// } +// +// private boolean tryLock() { +// return lock.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread()); +// } +// +// private void unlock() { +//// VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); +// lock.set(WordFactory.nullPointer()); +// } /** Visible for testing. */ public void beginTest(long eventSampleSize, long periodMs) { @@ -344,7 +341,7 @@ public void expireActiveWindow() { window0.testCurrentNanos += periodNs; window1.testCurrentNanos += periodNs; } - window0.testCurrentNanos += periodNs / windowDivisor; - window1.testCurrentNanos += periodNs / windowDivisor; + window0.testCurrentNanos += periodNs / WINDOW_DIVISOR; + window1.testCurrentNanos += periodNs / WINDOW_DIVISOR; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java index d9725e9c7684..e683473f9cec 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java @@ -5,6 +5,7 @@ import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.jfr.JfrThrottler; +import com.oracle.svm.core.locks.VMMutex; /** * Each event that supports throttling has its own throttler that can be accessed through this class. @@ -15,7 +16,7 @@ public class JfrThrottlerSupport { JfrThrottler objectAllocationSampleThrottler; @Platforms(Platform.HOSTED_ONLY.class) JfrThrottlerSupport() { - objectAllocationSampleThrottler = new JfrThrottler(); + objectAllocationSampleThrottler = new JfrThrottler(new VMMutex("jfrThrottler")); } private JfrThrottler getThrottler(long eventId) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index f4172c3a9ea4..f7ccccfc4065 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -9,13 +9,13 @@ public class JfrThrottlerWindow { public UninterruptibleUtils.AtomicLong endTicks; // already volatile // Calculated every rotation based on params set by user and results from previous windows - public long samplingInterval; - public double projectedPopSize; + public volatile long samplingInterval; + public volatile double projectedPopSize; // params set by user - public long samplesPerWindow; - public long windowDurationNs; - public long debt; + public volatile long samplesPerWindow; + public volatile long windowDurationNs; + public volatile long debt; public JfrThrottlerWindow() { windowDurationNs = 0; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 8901aa40e22e..faf1c1f28385 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -37,6 +37,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import com.oracle.svm.core.locks.VMMutex; + public class TestThrottler { // Based on hardcoded value in the throttler class. @@ -44,6 +46,8 @@ public class TestThrottler { // Arbitrary. It doesn't matter what this is. private final long WINDOW_DURATION_MS = 200; private final long SAMPLES_PER_WINDOW = 10; + private static final VMMutex mutex = new VMMutex("testThrottler"); + /** * This is the simplest test that ensures that sampling stops after the cap is hit. Single thread. @@ -52,7 +56,7 @@ public class TestThrottler { @Test public void testCapSingleThread() { // Doesn't rotate after starting sampling - JfrThrottler throttler = new JfrThrottler(); + JfrThrottler throttler = new JfrThrottler(mutex); throttler.setThrottle(SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); for (int i = 0; i < SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD; i++) { boolean sample = throttler.sample(); @@ -71,7 +75,7 @@ public void testCapConcurrent() throws InterruptedException { final int testingThreadCount = 10; final AtomicInteger count = new AtomicInteger(); List testingThreads = new ArrayList<>(); - JfrThrottler throttler = new JfrThrottler(); + JfrThrottler throttler = new JfrThrottler(mutex); throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); Runnable doSampling = () -> { for (int i = 0; i < samplesPerWindow; i++) { @@ -116,7 +120,7 @@ public void testCapConcurrent() throws InterruptedException { @Test public void testExpiry() { final long samplesPerWindow = 10; - JfrThrottler throttler = new JfrThrottler(); + JfrThrottler throttler = new JfrThrottler(mutex); throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); int count = 0; @@ -144,7 +148,7 @@ public void testExpiry() { @Test public void testEWMA() { final long samplesPerWindow = 100; - JfrThrottler throttler = new JfrThrottler(); + JfrThrottler throttler = new JfrThrottler(mutex); // Samples per second is low (<10) resulting in a windowDuration being un-normalized (1 min) resulting in a window lookback of 5.0 throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, 60 * 1000); assertTrue(throttler.getWindowLookback() == 5.0); @@ -172,7 +176,7 @@ public void testEWMA() { public void testDebt() { final long samplesPerWindow = 10; final long actualSamplesPerWindow = 50; - JfrThrottler throttler = new JfrThrottler(); + JfrThrottler throttler = new JfrThrottler(mutex); throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * WINDOW_DURATION_MS); for (int p = 0; p < 50; p++) { @@ -184,7 +188,7 @@ public void testDebt() { // now the sampling interval must be 50 / 10 = 5 assertTrue("Sampling interval is incorrect:"+ throttler.getActiveWindowSamplingInterval(), throttler.getActiveWindowSamplingInterval() == 5); - // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 6 + // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 10 - (20/5) = 6 // samples for (int i = 0; i < 20; i++) { throttler.sample(); @@ -209,7 +213,7 @@ public void testDebt() { public void testNormalization() { long sampleSize = 10 * 600; long periodMs = 60*1000; - JfrThrottler throttler = new JfrThrottler(); + JfrThrottler throttler = new JfrThrottler(mutex); throttler.beginTest(sampleSize, periodMs); assertTrue(throttler.getPeriodNs()+" "+ throttler.getEventSampleSize(),throttler.getEventSampleSize()==sampleSize/60 && throttler.getPeriodNs() == 1000000000); From 0c1736d013129ee46baa3e1f28642d2113eb682a Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 19 Jun 2023 16:30:48 -0400 Subject: [PATCH 09/34] fix computeAccumulatedDebtCarryLimit. Add zeroRate test. Add partial test of distribution --- .../com/oracle/svm/core/jfr/JfrThrottler.java | 151 +++++++++--------- .../oracle/svm/test/jfr/AbstractJfrTest.java | 2 +- .../oracle/svm/test/jfr/TestThrottler.java | 84 +++++++++- 3 files changed, 159 insertions(+), 78 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 06ddd9f7ca83..23056fa945b5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -2,13 +2,6 @@ import com.oracle.svm.core.jdk.UninterruptibleUtils; -import com.oracle.svm.core.jfr.JfrTicks; - -import com.oracle.svm.core.jfr.JfrThrottlerWindow; -import org.graalvm.word.WordFactory; -import org.graalvm.compiler.nodes.PauseNode; -import org.graalvm.nativeimage.CurrentIsolate; -import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.locks.VMMutex; @@ -25,19 +18,20 @@ public class JfrThrottler { private static final long SECOND_IN_NS = 1000000000; private static final long SECOND_IN_MS = 1000; private static final long MINUTE_IN_NS = SECOND_IN_NS * 60; - private static final long MINUTE_IN_MS = SECOND_IN_MS *60; - private static final long HOUR_IN_MS = MINUTE_IN_MS * 60; - private static final long HOUR_IN_NS = MINUTE_IN_NS * 60; - private static final long DAY_IN_MS = HOUR_IN_MS * 24; - private static final long DAY_IN_NS = HOUR_IN_NS * 24; - private static final long TEN_PER_S_IN_MINUTES = 600; - private static final long TEN_PER_S_IN_HOURS = 36000; - private static final long TEN_PER_S_IN_DAYS = 864000; +// private static final long MINUTE_IN_MS = SECOND_IN_MS *60; +// private static final long HOUR_IN_MS = MINUTE_IN_MS * 60; +// private static final long HOUR_IN_NS = MINUTE_IN_NS * 60; +// private static final long DAY_IN_MS = HOUR_IN_MS * 24; +// private static final long DAY_IN_NS = HOUR_IN_NS * 24; +// private static final long TEN_PER_S_IN_MINUTES = 600; +// private static final long TEN_PER_S_IN_HOURS = 36000; +// private static final long TEN_PER_S_IN_DAYS = 864000; // Copied from hotspot private static final int WINDOW_DIVISOR = 5; + private static final int EVENT_THROTTLER_OFF = -2; // Can't use reentrant lock because it allocates - private final UninterruptibleUtils.AtomicPointer lock; +// private final UninterruptibleUtils.AtomicPointer lock; private final VMMutex mutex; @@ -50,15 +44,15 @@ public class JfrThrottler { private volatile double ewmaPopulationSizeAlpha = 0; private volatile double avgPopulationSize = 0; private volatile boolean reconfigure; - private long accumulatedDebtCarryLimit; - private long accumulatedDebtCarryCount; + private volatile long accumulatedDebtCarryLimit; + private volatile long accumulatedDebtCarryCount; - public JfrThrottler(com.oracle.svm.core.locks.VMMutex mutex) { + public JfrThrottler(VMMutex mutex) { accumulatedDebtCarryLimit = 0; accumulatedDebtCarryCount = 0; reconfigure = false; disabled = new UninterruptibleUtils.AtomicBoolean(true); - lock = new UninterruptibleUtils.AtomicPointer<>(); // I assume this is initially +// lock = new UninterruptibleUtils.AtomicPointer<>(); // I assume this is initially // WordFactorynullPointer() window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); @@ -74,36 +68,36 @@ public JfrThrottler(com.oracle.svm.core.locks.VMMutex mutex) { * This is in the critical section because setting the sample size and period must be done together atomically. * Otherwise, we risk a window's params being set with only one of the two updated. */ - private void normalize1(long eventSampleSize, long periodMs){ - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); - // Do we want more than 10samples/s ? If so convert to samples/s - if (periodMs <= SECOND_IN_MS){ - //nothing - } else if (periodMs <= MINUTE_IN_MS) { - if (eventSampleSize >= TEN_PER_S_IN_MINUTES) { - eventSampleSize /= 60; - periodMs /= 60; - } - } else if (periodMs <= HOUR_IN_MS) { - if (eventSampleSize >=TEN_PER_S_IN_HOURS) { - eventSampleSize /= 3600; - periodMs /= 3600; - } - } else if (periodMs <= DAY_IN_MS) { - if (eventSampleSize >=TEN_PER_S_IN_DAYS) { - eventSampleSize /= 86400; - periodMs /= 86400; - } - } - this.eventSampleSize = eventSampleSize; - this.periodNs = periodMs * 1000000; - } +// private void normalize1(long eventSampleSize, long periodMs){ +// VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); +// // Do we want more than 10samples/s ? If so convert to samples/s +// if (periodMs <= SECOND_IN_MS){ +// //nothing +// } else if (periodMs <= MINUTE_IN_MS) { +// if (eventSampleSize >= TEN_PER_S_IN_MINUTES) { +// eventSampleSize /= 60; +// periodMs /= 60; +// } +// } else if (periodMs <= HOUR_IN_MS) { +// if (eventSampleSize >=TEN_PER_S_IN_HOURS) { +// eventSampleSize /= 3600; +// periodMs /= 3600; +// } +// } else if (periodMs <= DAY_IN_MS) { +// if (eventSampleSize >=TEN_PER_S_IN_DAYS) { +// eventSampleSize /= 86400; +// periodMs /= 86400; +// } +// } +// this.eventSampleSize = eventSampleSize; +// this.periodNs = periodMs * 1000000; +// } private void normalize(long samplesPerPeriod, double periodMs){ VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); // Do we want more than 10samples/s ? If so convert to samples/s double periodsPerSecond = 1000.0/ periodMs; double samplesPerSecond = samplesPerPeriod * periodsPerSecond; - if (samplesPerSecond >= 10) { + if (samplesPerSecond >= 10 && periodMs > SECOND_IN_MS) { this.periodNs = SECOND_IN_NS; this.eventSampleSize = (long) samplesPerSecond; return; @@ -114,16 +108,20 @@ private void normalize(long samplesPerPeriod, double periodMs){ } public boolean setThrottle(long eventSampleSize, long periodMs) { - if (eventSampleSize == 0) { + if (eventSampleSize == EVENT_THROTTLER_OFF) { disabled.set(true); + return true; } // Blocking lock because new settings MUST be applied. mutex.lock(); - normalize(eventSampleSize, periodMs); - reconfigure = true; - rotateWindow(); // could omit this and choose to wait until next rotation. - mutex.unlock(); + try { + normalize(eventSampleSize, periodMs); + reconfigure = true; + rotateWindow(); // could omit this and choose to wait until next rotation. + } finally { + mutex.unlock(); + } disabled.set(false); // should be after the above are set return true; } @@ -144,13 +142,16 @@ public boolean sample() { if (expired) { // Check lock to see if someone is already rotating. If so, move on. mutex.lock(); // TODO: would be better to tryLock() if possible. - // Once in critical section ensure active window is still expired. - // Another thread may have already handled the expired window, or new settings may - // have already triggered a rotation. - if (activeWindow.isExpired()) { - rotateWindow(); + try { + // Once in critical section ensure active window is still expired. + // Another thread may have already handled the expired window, or new settings may + // have already triggered a rotation. + if (activeWindow.isExpired()) { + rotateWindow(); + } + } finally { + mutex.unlock(); } - mutex.unlock(); return false; // if expired, hotspot returns false } return window.sample(); @@ -186,7 +187,7 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { if (periodNs == 0 || windowDurationNs >= SECOND_IN_NS) { return 1; } - return WINDOW_DIVISOR; // this var isn't available to the class in Hotspot. + return SECOND_IN_NS/windowDurationNs; // this var isn't available to the class in Hotspot. } private long amortizeDebt(JfrThrottlerWindow lastWindow) { @@ -205,24 +206,24 @@ private long amortizeDebt(JfrThrottlerWindow lastWindow) { /** * Handles the case where the sampling rate is very low. */ - private void setSamplePointsAndWindowDuration1(){ - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); - VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); - JfrThrottlerWindow next = getNextWindow(); - long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; - long windowDurationNs = periodNs / WINDOW_DIVISOR; - if (eventSampleSize < 10 - || periodNs >= MINUTE_IN_NS && eventSampleSize < TEN_PER_S_IN_MINUTES - || periodNs >= HOUR_IN_NS && eventSampleSize < TEN_PER_S_IN_HOURS - || periodNs >= DAY_IN_NS && eventSampleSize < TEN_PER_S_IN_DAYS){ - samplesPerWindow = eventSampleSize; - windowDurationNs = periodNs; - } - activeWindow.samplesPerWindow = samplesPerWindow; - activeWindow.windowDurationNs = windowDurationNs; - next.samplesPerWindow = samplesPerWindow; - next.windowDurationNs = windowDurationNs; - } +// private void setSamplePointsAndWindowDuration1(){ +// VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); +// VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); +// JfrThrottlerWindow next = getNextWindow(); +// long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; +// long windowDurationNs = periodNs / WINDOW_DIVISOR; +// if (eventSampleSize < 10 +// || periodNs >= MINUTE_IN_NS && eventSampleSize < TEN_PER_S_IN_MINUTES +// || periodNs >= HOUR_IN_NS && eventSampleSize < TEN_PER_S_IN_HOURS +// || periodNs >= DAY_IN_NS && eventSampleSize < TEN_PER_S_IN_DAYS){ +// samplesPerWindow = eventSampleSize; +// windowDurationNs = periodNs; +// } +// activeWindow.samplesPerWindow = samplesPerWindow; +// activeWindow.windowDurationNs = windowDurationNs; +// next.samplesPerWindow = samplesPerWindow; +// next.windowDurationNs = windowDurationNs; +// } private void setSamplePointsAndWindowDuration(){ VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/AbstractJfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/AbstractJfrTest.java index b16da09ffccc..af57c008b244 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/AbstractJfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/AbstractJfrTest.java @@ -102,7 +102,7 @@ protected static void checkRecording(EventValidator validator, Path path, JfrRec } } - private static List getEvents(Path path, String[] testedEvents) throws IOException { + protected static List getEvents(Path path, String[] testedEvents) throws IOException { /* Only return events that are in the list of tested events. */ ArrayList result = new ArrayList<>(); for (RecordedEvent event : RecordingFile.readAllEvents(path)) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index faf1c1f28385..aea460976a7b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -26,12 +26,20 @@ package com.oracle.svm.test.jfr; +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.genscavenge.HeapParameters; +import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.jfr.JfrThrottler; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; import org.junit.Test; import org.junit.Assert; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertFalse; @@ -39,7 +47,7 @@ import com.oracle.svm.core.locks.VMMutex; -public class TestThrottler { +public class TestThrottler extends JfrRecordingTest{ // Based on hardcoded value in the throttler class. private final long WINDOWS_PER_PERIOD = 5; @@ -223,10 +231,82 @@ public void testNormalization() { assertTrue(throttler.getPeriodNs()+" "+ throttler.getEventSampleSize(), throttler.getEventSampleSize()==sampleSize/3600 && throttler.getPeriodNs() == 1000000000); } + /** + * Checks that no ObjectAllocationSample events are emitted when the sampling rate is 0. + */ + @Test + public void testZeroRate() throws Throwable{ + // Test throttler in isolation + JfrThrottler throttler = new JfrThrottler(mutex); + throttler.setThrottle(0, 2000); + assertFalse(throttler.sample()); + throttler.setThrottle(10, 2000); + assertTrue(throttler.sample()); + + // Test applying throttling settings to an in-progress recording + Recording recording = startRecording(new String[]{}, null, new HashMap<>()); // don't use default configuration because it includes ObjectAllocationSample by default + recording.enable(JfrEvent.ObjectAllocationSample.getName()).with("throttle", "0/s"); + final int alignedHeapChunkSize = com.oracle.svm.core.util.UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); + allocateCharArray(alignedHeapChunkSize); + + recording.stop(); + recording.close(); + + assertTrue(getEvents(recording.getDestination(),new String[]{JfrEvent.ObjectAllocationSample.getName()}).size()==0); + } + + @NeverInline("Prevent escape analysis.") + private static char[] allocateCharArray(int length) { + return new char[length]; + } + + /** + * This is a more involved test that checks the sample distribution. It has been mostly copied from + * JfrGTestAdaptiveSampling in the OpenJDK + */ + @Test + public void testDistribution(){ + final int maxPopPerWindow = 2000; + final int minPopPerWindow = 2; + final int windowCount = 10000; + final int windowDurationMs = 100; + final int expectedSamplesPerWindow = 50; + final int expectedSamples = expectedSamplesPerWindow * windowCount; + final int windowLookBackCount = 50; + final double maxSampleBias =0.11; + JfrThrottler throttler = new JfrThrottler(mutex); + throttler.beginTest(expectedSamplesPerWindow*WINDOWS_PER_PERIOD, windowDurationMs*WINDOWS_PER_PERIOD); + + int[] population = new int[100]; + int[] sample = new int[100]; + + int populationSize = 0; + int sampleSize = 0; + for(int t = 0; t < windowCount; t++){ + int windowPop = ThreadLocalRandom.current().nextInt(minPopPerWindow, maxPopPerWindow + 1); + for(int i =0 ; i < windowPop; i++){ + populationSize++; + int index = ThreadLocalRandom.current().nextInt(0, 100); + population[index] += 1; + if (throttler.sample()){ + sampleSize++; + sample[index] +=1; + } + } + expireAndRotate(throttler); + } + expectNear(expectedSamples, sampleSize, expectedSamples* 0.05); + } + + private static void expectNear(int value1, int value2, double error){ + System.out.println(value1 +" "+ value2); + assertTrue(Math.abs(value1-value2) < error); + } + /** * Helper method that expires and rotates a throttler's active window */ - private void expireAndRotate(JfrThrottler throttler) { + private static void expireAndRotate(JfrThrottler throttler) { throttler.expireActiveWindow(); assertTrue("should be expired", throttler.IsActiveWindowExpired()); assertFalse("Should have rotated not sampled!", throttler.sample()); From 929debd35e137e06c1933b3a0005c911119bf7d7 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 22 Jun 2023 12:23:34 -0400 Subject: [PATCH 10/34] distribution tests and fix adjustedProjectedpop --- .../com/oracle/svm/core/jfr/JfrThrottler.java | 2 +- .../svm/core/jfr/JfrThrottlerWindow.java | 9 +- .../oracle/svm/test/jfr/TestThrottler.java | 112 +++++++++++++++--- 3 files changed, 102 insertions(+), 21 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 23056fa945b5..bee1e54dbd9f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -187,7 +187,7 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { if (periodNs == 0 || windowDurationNs >= SECOND_IN_NS) { return 1; } - return SECOND_IN_NS/windowDurationNs; // this var isn't available to the class in Hotspot. + return SECOND_IN_NS/windowDurationNs; } private long amortizeDebt(JfrThrottlerWindow lastWindow) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index f7ccccfc4065..16019540c0dd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -11,6 +11,7 @@ public class JfrThrottlerWindow { // Calculated every rotation based on params set by user and results from previous windows public volatile long samplingInterval; public volatile double projectedPopSize; + public volatile double adjustedProjectedPopSize; // params set by user public volatile long samplesPerWindow; @@ -21,6 +22,7 @@ public JfrThrottlerWindow() { windowDurationNs = 0; samplesPerWindow = 0; projectedPopSize = 0; + adjustedProjectedPopSize = 0; measuredPopSize = new UninterruptibleUtils.AtomicLong(0); endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + windowDurationNs); samplingInterval = 1; @@ -40,14 +42,14 @@ public boolean sample() { // Stop sampling if we're already over the projected population size, and we're over the // samples per window if (prevMeasuredPopSize % samplingInterval == 0 && - (prevMeasuredPopSize < projectedPopSize || prevMeasuredPopSize < samplesPerWindow)) { + (prevMeasuredPopSize < adjustedProjectedPopSize || prevMeasuredPopSize < samplesPerWindow)) { return true; } return false; } public long samplesTaken() { - if (measuredPopSize.get() > projectedPopSize) { + if (measuredPopSize.get() > adjustedProjectedPopSize) { return samplesExpected(); } return measuredPopSize.get() / samplingInterval; @@ -64,8 +66,9 @@ public void configure(long debt, double projectedPopSize) { samplingInterval = 1; } else { // It's important to round *up* otherwise we risk violating the upper bound - samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); + samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); // *** TODO: geometric distribution stuff } + this.adjustedProjectedPopSize = samplesExpected() * samplingInterval; // reset measuredPopSize.set(0); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index aea460976a7b..5a23c26c6187 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -31,14 +31,11 @@ import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.jfr.JfrThrottler; import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordedEvent; import org.junit.Test; -import org.junit.Assert; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; @@ -260,30 +257,75 @@ private static char[] allocateCharArray(int length) { return new char[length]; } - /** - * This is a more involved test that checks the sample distribution. It has been mostly copied from - * JfrGTestAdaptiveSampling in the OpenJDK - */ + @Test - public void testDistribution(){ + public void testDistributionUniform(){ final int maxPopPerWindow = 2000; final int minPopPerWindow = 2; - final int windowCount = 10000; + final int expectedSamplesPerWindow = 50; + testDistribution(() -> ThreadLocalRandom.current().nextInt(minPopPerWindow, maxPopPerWindow + 1), expectedSamplesPerWindow, 0.05); + } + + @Test + public void testDistributionHighRate(){ + final int maxPopPerWindow = 2000; + final int expectedSamplesPerWindow = 50; + testDistribution(() -> maxPopPerWindow + , expectedSamplesPerWindow, 0.02); + } + @Test + public void testDistributionLowRate(){ + final int minPopPerWindow = 2; + testDistribution(() -> minPopPerWindow, minPopPerWindow, 0.05); + } + @Test + public void testDistributionEarlyBurst(){ // maybe this is 41000 instead of 50000 because projected population size is low + final int maxPopPerWindow = 2000; + final int expectedSamplesPerWindow = 50; + final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + AtomicInteger count = new AtomicInteger(1); + testDistribution(() ->count.getAndIncrement() % accumulatedDebtCarryLimit == 1 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.9); + } + + @Test + public void testDistributionMidBurst(){ + final int maxPopPerWindow = 2000; + final int expectedSamplesPerWindow = 50; + final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + AtomicInteger count = new AtomicInteger(1); + testDistribution(() ->count.getAndIncrement() % accumulatedDebtCarryLimit == 5 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.5); + } + + @Test + public void testDistributionLateBurst(){ // *** I think its [NOT] bursting every window by accident. windowCount=10000 not 1000 + final int maxPopPerWindow = 2000; + final int expectedSamplesPerWindow = 50; + final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + AtomicInteger count = new AtomicInteger(1); + testDistribution(() ->count.getAndIncrement() % accumulatedDebtCarryLimit == 0 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.0); + } + /** + * This is a more involved test that checks the sample distribution. It has been adapted from + * JfrGTestAdaptiveSampling in the OpenJDK + */ + private void testDistribution(IncomingPopulation incomingPopulation, int samplePointsPerWindow, double errorFactor){ + final int distributionSlots = 100; final int windowDurationMs = 100; + final int windowCount = 10000; final int expectedSamplesPerWindow = 50; final int expectedSamples = expectedSamplesPerWindow * windowCount; final int windowLookBackCount = 50; - final double maxSampleBias =0.11; + final double maxSampleBias = 0.11; JfrThrottler throttler = new JfrThrottler(mutex); throttler.beginTest(expectedSamplesPerWindow*WINDOWS_PER_PERIOD, windowDurationMs*WINDOWS_PER_PERIOD); - int[] population = new int[100]; - int[] sample = new int[100]; + int[] population = new int[distributionSlots]; + int[] sample = new int[distributionSlots]; int populationSize = 0; int sampleSize = 0; for(int t = 0; t < windowCount; t++){ - int windowPop = ThreadLocalRandom.current().nextInt(minPopPerWindow, maxPopPerWindow + 1); + int windowPop = incomingPopulation.getWindowPopulation(); for(int i =0 ; i < windowPop; i++){ populationSize++; int index = ThreadLocalRandom.current().nextInt(0, 100); @@ -295,12 +337,48 @@ public void testDistribution(){ } expireAndRotate(throttler); } - expectNear(expectedSamples, sampleSize, expectedSamples* 0.05); + int targetSampleSize = samplePointsPerWindow * windowCount; + System.out.println("Population size:" + populationSize + " Sample size: "+sampleSize); + expectNear(targetSampleSize, sampleSize, expectedSamples* errorFactor); + assertDistributionProperties(distributionSlots, population, sample, populationSize, sampleSize); + } + + private static void expectNear(double value1, double value2, double error){ +// System.out.println(value1 +" "+ value2); + assertTrue(Math.abs(value1-value2) <= error); + } + + private static void assertDistributionProperties(int distributionSlots, int[] population, int[] sample, int populationSize, int sampleSize ) { + int populationSum=0; + int sampleSum = 0; + for (int i = 0; i < distributionSlots; i++) { + populationSum += i * population[i]; + sampleSum += i * sample[i]; + } + + double populationMean = populationSum / (double)populationSize; + double sampleMean = sampleSum / (double)sampleSize; + + double populationVariance = 0; + double sampleVariance = 0; + for (int i = 0; i < distributionSlots; i++) { + double populationDiff = i - populationMean; + populationVariance += population[i] * populationDiff * populationDiff; + + double sampleDiff = i - sampleMean; + sampleVariance += sample[i] * sampleDiff * sampleDiff; + } + populationVariance = populationVariance / (populationSize - 1); + sampleVariance = sampleVariance / (sampleSize - 1); + double populationStdev = Math.sqrt(populationVariance); + double sampleStdev = Math.sqrt(sampleVariance); +// System.out.println("populationStdev:"+populationStdev +" sampleStdev:"+sampleStdev ); + expectNear(populationStdev, sampleStdev, 0.5); // 0.5 value copied from Hotspot test + expectNear(populationMean, sampleMean, populationStdev); } - private static void expectNear(int value1, int value2, double error){ - System.out.println(value1 +" "+ value2); - assertTrue(Math.abs(value1-value2) < error); + interface IncomingPopulation{ + int getWindowPopulation(); } /** From d1386ea688a50b83b0cedb5bf6baa714a6ea5b59 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 23 Jun 2023 13:11:36 -0400 Subject: [PATCH 11/34] cleanup. Fix EWMA test case --- .../genscavenge/ThreadLocalAllocation.java | 1 - .../com/oracle/svm/core/jfr/JfrThrottler.java | 205 ++++++++++-------- .../svm/core/jfr/JfrThrottlerSupport.java | 28 ++- .../svm/core/jfr/JfrThrottlerWindow.java | 53 +++-- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 1 - .../events/ObjectAllocationSampleEvent.java | 1 - .../oracle/svm/test/jfr/TestThrottler.java | 141 ++++++------ 7 files changed, 258 insertions(+), 172 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index 6f6b9d76f92a..f23919b3ba6b 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -76,7 +76,6 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalWord; import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.jfr.events.ObjectAllocationSampleEvent; -import com.oracle.svm.core.jfr.JfrTicks; /** * Bump-pointer allocation from thread-local top and end Pointers. Many of these methods are called diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index bee1e54dbd9f..50dce3b3b450 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -1,3 +1,29 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package com.oracle.svm.core.jfr; import com.oracle.svm.core.jdk.UninterruptibleUtils; @@ -18,23 +44,22 @@ public class JfrThrottler { private static final long SECOND_IN_NS = 1000000000; private static final long SECOND_IN_MS = 1000; private static final long MINUTE_IN_NS = SECOND_IN_NS * 60; -// private static final long MINUTE_IN_MS = SECOND_IN_MS *60; -// private static final long HOUR_IN_MS = MINUTE_IN_MS * 60; -// private static final long HOUR_IN_NS = MINUTE_IN_NS * 60; -// private static final long DAY_IN_MS = HOUR_IN_MS * 24; -// private static final long DAY_IN_NS = HOUR_IN_NS * 24; -// private static final long TEN_PER_S_IN_MINUTES = 600; -// private static final long TEN_PER_S_IN_HOURS = 36000; -// private static final long TEN_PER_S_IN_DAYS = 864000; - // Copied from hotspot +// private static final long MINUTE_IN_MS = SECOND_IN_MS *60; +// private static final long HOUR_IN_MS = MINUTE_IN_MS * 60; +// private static final long HOUR_IN_NS = MINUTE_IN_NS * 60; +// private static final long DAY_IN_MS = HOUR_IN_MS * 24; +// private static final long DAY_IN_NS = HOUR_IN_NS * 24; +// private static final long TEN_PER_S_IN_MINUTES = 600; +// private static final long TEN_PER_S_IN_HOURS = 36000; +// private static final long TEN_PER_S_IN_DAYS = 864000; +// Copied from hotspot private static final int WINDOW_DIVISOR = 5; private static final int EVENT_THROTTLER_OFF = -2; // Can't use reentrant lock because it allocates -// private final UninterruptibleUtils.AtomicPointer lock; +// private final UninterruptibleUtils.AtomicPointer lock; private final VMMutex mutex; - public UninterruptibleUtils.AtomicBoolean disabled; // Already volatile private JfrThrottlerWindow window0; private JfrThrottlerWindow window1; @@ -52,8 +77,8 @@ public JfrThrottler(VMMutex mutex) { accumulatedDebtCarryCount = 0; reconfigure = false; disabled = new UninterruptibleUtils.AtomicBoolean(true); -// lock = new UninterruptibleUtils.AtomicPointer<>(); // I assume this is initially - // WordFactorynullPointer() +// lock = new UninterruptibleUtils.AtomicPointer<>(); // I assume this is initially +// WordFactorynullPointer() window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); activeWindow = window0; // same as hotspot. Unguarded is ok because all throttler instances @@ -62,40 +87,39 @@ public JfrThrottler(VMMutex mutex) { } /** - * Convert rate to samples/second if possible. - * Want to avoid long period with large windows with a large number of samples per window - * in favor of many smaller windows. - * This is in the critical section because setting the sample size and period must be done together atomically. + * Convert rate to samples/second if possible. Want to avoid long period with large windows with + * a large number of samples per window in favor of many smaller windows. This is in the + * critical section because setting the sample size and period must be done together atomically. * Otherwise, we risk a window's params being set with only one of the two updated. */ -// private void normalize1(long eventSampleSize, long periodMs){ -// VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); -// // Do we want more than 10samples/s ? If so convert to samples/s -// if (periodMs <= SECOND_IN_MS){ -// //nothing -// } else if (periodMs <= MINUTE_IN_MS) { -// if (eventSampleSize >= TEN_PER_S_IN_MINUTES) { -// eventSampleSize /= 60; -// periodMs /= 60; -// } -// } else if (periodMs <= HOUR_IN_MS) { -// if (eventSampleSize >=TEN_PER_S_IN_HOURS) { -// eventSampleSize /= 3600; -// periodMs /= 3600; -// } -// } else if (periodMs <= DAY_IN_MS) { -// if (eventSampleSize >=TEN_PER_S_IN_DAYS) { -// eventSampleSize /= 86400; -// periodMs /= 86400; -// } -// } -// this.eventSampleSize = eventSampleSize; -// this.periodNs = periodMs * 1000000; -// } - private void normalize(long samplesPerPeriod, double periodMs){ +// private void normalize1(long eventSampleSize, long periodMs){ +// VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); +// // Do we want more than 10samples/s ? If so convert to samples/s +// if (periodMs <= SECOND_IN_MS){ +// //nothing +// } else if (periodMs <= MINUTE_IN_MS) { +// if (eventSampleSize >= TEN_PER_S_IN_MINUTES) { +// eventSampleSize /= 60; +// periodMs /= 60; +// } +// } else if (periodMs <= HOUR_IN_MS) { +// if (eventSampleSize >=TEN_PER_S_IN_HOURS) { +// eventSampleSize /= 3600; +// periodMs /= 3600; +// } +// } else if (periodMs <= DAY_IN_MS) { +// if (eventSampleSize >=TEN_PER_S_IN_DAYS) { +// eventSampleSize /= 86400; +// periodMs /= 86400; +// } +// } +// this.eventSampleSize = eventSampleSize; +// this.periodNs = periodMs * 1000000; +// } + private void normalize(long samplesPerPeriod, double periodMs) { VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); // Do we want more than 10samples/s ? If so convert to samples/s - double periodsPerSecond = 1000.0/ periodMs; + double periodsPerSecond = 1000.0 / periodMs; double samplesPerSecond = samplesPerPeriod * periodsPerSecond; if (samplesPerSecond >= 10 && periodMs > SECOND_IN_MS) { this.periodNs = SECOND_IN_NS; @@ -187,7 +211,7 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { if (periodNs == 0 || windowDurationNs >= SECOND_IN_NS) { return 1; } - return SECOND_IN_NS/windowDurationNs; + return SECOND_IN_NS / windowDurationNs; } private long amortizeDebt(JfrThrottlerWindow lastWindow) { @@ -206,25 +230,26 @@ private long amortizeDebt(JfrThrottlerWindow lastWindow) { /** * Handles the case where the sampling rate is very low. */ -// private void setSamplePointsAndWindowDuration1(){ -// VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); -// VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); -// JfrThrottlerWindow next = getNextWindow(); -// long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; -// long windowDurationNs = periodNs / WINDOW_DIVISOR; -// if (eventSampleSize < 10 -// || periodNs >= MINUTE_IN_NS && eventSampleSize < TEN_PER_S_IN_MINUTES -// || periodNs >= HOUR_IN_NS && eventSampleSize < TEN_PER_S_IN_HOURS -// || periodNs >= DAY_IN_NS && eventSampleSize < TEN_PER_S_IN_DAYS){ -// samplesPerWindow = eventSampleSize; -// windowDurationNs = periodNs; -// } -// activeWindow.samplesPerWindow = samplesPerWindow; -// activeWindow.windowDurationNs = windowDurationNs; -// next.samplesPerWindow = samplesPerWindow; -// next.windowDurationNs = windowDurationNs; -// } - private void setSamplePointsAndWindowDuration(){ +// private void setSamplePointsAndWindowDuration1(){ +// VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); +// VMError.guarantee(reconfigure, "Should only modify sample size and window duration during +// reconfigure."); +// JfrThrottlerWindow next = getNextWindow(); +// long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; +// long windowDurationNs = periodNs / WINDOW_DIVISOR; +// if (eventSampleSize < 10 +// || periodNs >= MINUTE_IN_NS && eventSampleSize < TEN_PER_S_IN_MINUTES +// || periodNs >= HOUR_IN_NS && eventSampleSize < TEN_PER_S_IN_HOURS +// || periodNs >= DAY_IN_NS && eventSampleSize < TEN_PER_S_IN_DAYS){ +// samplesPerWindow = eventSampleSize; +// windowDurationNs = periodNs; +// } +// activeWindow.samplesPerWindow = samplesPerWindow; +// activeWindow.windowDurationNs = windowDurationNs; +// next.samplesPerWindow = samplesPerWindow; +// next.windowDurationNs = windowDurationNs; +// } + private void setSamplePointsAndWindowDuration() { VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); JfrThrottlerWindow next = getNextWindow(); @@ -232,7 +257,7 @@ private void setSamplePointsAndWindowDuration(){ long windowDurationNs = periodNs / WINDOW_DIVISOR; // If period isn't 1s, then we're effectively taking under 10 samples/s // because the values have already undergone normalization. - if (eventSampleSize < 10 || periodNs > SECOND_IN_NS){ + if (eventSampleSize < 10 || periodNs > SECOND_IN_NS) { samplesPerWindow = eventSampleSize; windowDurationNs = periodNs; } @@ -276,38 +301,39 @@ private double projectPopulationSize(long lastWindowMeasuredPop) { return avgPopulationSize; } - private static double exponentiallyWeightedMovingAverage(double Y, double alpha, double S) { - return alpha * Y + (1 - alpha) * S; + private static double exponentiallyWeightedMovingAverage(double currentMeasurement, double alpha, double prevEwma) { + return alpha * currentMeasurement + (1 - alpha) * prevEwma; } -// /** Basic spin lock */ -// private void lock() { -// while (!tryLock()) { -// PauseNode.pause(); -// } -// } +// /** Basic spin lock */ +// private void lock() { +// while (!tryLock()) { +// PauseNode.pause(); +// } +// } // -// private boolean tryLock() { -// return lock.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread()); -// } +// private boolean tryLock() { +// return lock.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread()); +// } // -// private void unlock() { -//// VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be acquired in critical section."); -// lock.set(WordFactory.nullPointer()); -// } +// private void unlock() { +//// VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be +// acquired in critical section."); +// lock.set(WordFactory.nullPointer()); +// } /** Visible for testing. */ public void beginTest(long eventSampleSize, long periodMs) { window0.isTest = true; window1.isTest = true; - window0.testCurrentNanos = 0; - window1.testCurrentNanos = 0; + window0.currentTestNanos = 0; + window1.currentTestNanos = 0; setThrottle(eventSampleSize, periodMs); } /** Visible for testing. */ public double getActiveWindowProjectedPopulationSize() { - return activeWindow.projectedPopSize; + return avgPopulationSize; } /** Visible for testing. */ @@ -325,24 +351,27 @@ public double getWindowLookback() { } /** Visible for testing. */ - public boolean IsActiveWindowExpired() { + public boolean isActiveWindowExpired() { return activeWindow.isExpired(); } + /** Visible for testing. */ public long getPeriodNs() { return periodNs; } + /** Visible for testing. */ public long getEventSampleSize() { return eventSampleSize; } + /** Visible for testing. */ public void expireActiveWindow() { - if (eventSampleSize < 10 || periodNs > SECOND_IN_NS){ - window0.testCurrentNanos += periodNs; - window1.testCurrentNanos += periodNs; + if (eventSampleSize < 10 || periodNs > SECOND_IN_NS) { + window0.currentTestNanos += periodNs; + window1.currentTestNanos += periodNs; } - window0.testCurrentNanos += periodNs / WINDOW_DIVISOR; - window1.testCurrentNanos += periodNs / WINDOW_DIVISOR; + window0.currentTestNanos += periodNs / WINDOW_DIVISOR; + window1.currentTestNanos += periodNs / WINDOW_DIVISOR; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java index e683473f9cec..9f1b90848be4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java @@ -1,10 +1,34 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package com.oracle.svm.core.jfr; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import com.oracle.svm.core.jfr.JfrEvent; -import com.oracle.svm.core.jfr.JfrThrottler; import com.oracle.svm.core.locks.VMMutex; /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index 16019540c0dd..f7e54388fef2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -1,7 +1,31 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ package com.oracle.svm.core.jfr; import com.oracle.svm.core.jdk.UninterruptibleUtils; -import com.oracle.svm.core.jfr.JfrTicks; public class JfrThrottlerWindow { // reset every rotation @@ -10,8 +34,7 @@ public class JfrThrottlerWindow { // Calculated every rotation based on params set by user and results from previous windows public volatile long samplingInterval; - public volatile double projectedPopSize; - public volatile double adjustedProjectedPopSize; + public volatile double activeWindowSampleLimit; // params set by user public volatile long samplesPerWindow; @@ -21,8 +44,7 @@ public class JfrThrottlerWindow { public JfrThrottlerWindow() { windowDurationNs = 0; samplesPerWindow = 0; - projectedPopSize = 0; - adjustedProjectedPopSize = 0; + activeWindowSampleLimit = 0; measuredPopSize = new UninterruptibleUtils.AtomicLong(0); endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + windowDurationNs); samplingInterval = 1; @@ -42,14 +64,14 @@ public boolean sample() { // Stop sampling if we're already over the projected population size, and we're over the // samples per window if (prevMeasuredPopSize % samplingInterval == 0 && - (prevMeasuredPopSize < adjustedProjectedPopSize || prevMeasuredPopSize < samplesPerWindow)) { + (prevMeasuredPopSize < activeWindowSampleLimit)) { return true; } return false; } public long samplesTaken() { - if (measuredPopSize.get() > adjustedProjectedPopSize) { + if (measuredPopSize.get() > activeWindowSampleLimit) { return samplesExpected(); } return measuredPopSize.get() / samplingInterval; @@ -61,20 +83,25 @@ public long samplesExpected() { public void configure(long debt, double projectedPopSize) { this.debt = debt; - this.projectedPopSize = projectedPopSize; if (projectedPopSize <= samplesPerWindow) { samplingInterval = 1; } else { // It's important to round *up* otherwise we risk violating the upper bound - samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); // *** TODO: geometric distribution stuff + samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); // *** + // TODO: + // geometric + // distribution + // stuff } - this.adjustedProjectedPopSize = samplesExpected() * samplingInterval; + // activeWindowSampleLimit is either projectedPopSize or samplesExpected() (if + // projectedPopSize < samplesPerWindow) + this.activeWindowSampleLimit = samplesExpected() * samplingInterval; // reset measuredPopSize.set(0); if (isTest) { // There is a need to mock JfrTicks for testing. - endTicks.set(testCurrentNanos + windowDurationNs); + endTicks.set(currentTestNanos + windowDurationNs); } else { endTicks.set(JfrTicks.currentTimeNanos() + windowDurationNs); } @@ -84,7 +111,7 @@ public void configure(long debt, double projectedPopSize) { public boolean isExpired() { if (isTest) { // There is a need to mock JfrTicks for testing. - if (testCurrentNanos >= endTicks.get()) { + if (currentTestNanos >= endTicks.get()) { return true; } } else if (JfrTicks.currentTimeNanos() >= endTicks.get()) { @@ -97,5 +124,5 @@ public boolean isExpired() { public volatile boolean isTest = false; /** Visible for testing. */ - public volatile long testCurrentNanos = 0; + public volatile long currentTestNanos = 0; } 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 094d90fa52cf..89adac0af8e9 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 @@ -45,7 +45,6 @@ import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.core.jfr.JfrThrottlerSupport; import jdk.internal.event.Event; import jdk.jfr.Configuration; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java index d76f461313e5..6c0ae20c168e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java @@ -34,7 +34,6 @@ import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.HasJfrSupport; import org.graalvm.nativeimage.StackValue; -import com.oracle.svm.core.jfr.JfrThreadLocal; import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalLong; import com.oracle.svm.core.thread.PlatformThreads; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 5a23c26c6187..67cd9b8c2596 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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 @@ -44,19 +44,19 @@ import com.oracle.svm.core.locks.VMMutex; -public class TestThrottler extends JfrRecordingTest{ +public class TestThrottler extends JfrRecordingTest { // Based on hardcoded value in the throttler class. - private final long WINDOWS_PER_PERIOD = 5; + private static final long WINDOWS_PER_PERIOD = 5; // Arbitrary. It doesn't matter what this is. - private final long WINDOW_DURATION_MS = 200; - private final long SAMPLES_PER_WINDOW = 10; + private static final long WINDOW_DURATION_MS = 200; + private static final long SAMPLES_PER_WINDOW = 10; + private static final long SECOND_IN_MS = 1000; private static final VMMutex mutex = new VMMutex("testThrottler"); - /** - * This is the simplest test that ensures that sampling stops after the cap is hit. Single thread. - * All sampling is done within the first window. No rotations. + * This is the simplest test that ensures that sampling stops after the cap is hit. Single + * thread. All sampling is done within the first window. No rotations. */ @Test public void testCapSingleThread() { @@ -145,37 +145,36 @@ public void testExpiry() { } /** - * This test checks that the projected population and sampling interval after a window rotation - * is as expected. Window lookback for this test is 5. - * - * Will be low rate, which results in lookback of 5, because window duration is period. + * This test checks the projected population after a window rotation. This is a test of the EWMA + * calculation. Window lookback is 25 and windowDuration is un-normalized because period is not + * greater than 1s. */ @Test public void testEWMA() { - final long samplesPerWindow = 100; + // Results in 50 samples per second + final long samplesPerWindow = 10; JfrThrottler throttler = new JfrThrottler(mutex); - // Samples per second is low (<10) resulting in a windowDuration being un-normalized (1 min) resulting in a window lookback of 5.0 - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, 60 * 1000); - assertTrue(throttler.getWindowLookback() == 5.0); - - int[] population = {21, 41, 61, 31, 91, 42, 77, 29, 88, 64, 22, 11, 33, 59}; // Arbitrary + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, SECOND_IN_MS); + assertTrue(throttler.getWindowLookback() == 25.0); + // Arbitrarily chosen + int[] population = {310, 410, 610, 310, 910, 420, 770, 290, 880, 640, 220, 110, 330, 590}; // Used EWMA calculator to confirm actualProjections - double[] actualProjections = {4.2, 11.56, 21.44, 23.35, 36.88, 37.90, 45.72, 42.38, 51.50, 54.00, 47.60, 40.28, 38.82, 42.86}; - + int[] actualProjections = {12, 28, 51, 61, 95, 108, 135, 141, 170, 189, 190, 187, 193, 209}; for (int p = 0; p < population.length; p++) { for (int i = 0; i < population[p]; i++) { throttler.sample(); } expireAndRotate(throttler); double projectedPopulation = throttler.getActiveWindowProjectedPopulationSize(); - assertTrue((int) actualProjections[p] == (int) projectedPopulation); + assertTrue(actualProjections[p] == (int) projectedPopulation); } } /** * Note: computeAccumulatedDebtCarryLimit depends on window duration. * - * Window lookback for this test is 25. Window duration is 1 second. Window divisor is default of 5. + * Window lookback for this test is 25. Window duration is 1 second. Window divisor is default + * of 5. */ @Test public void testDebt() { @@ -191,9 +190,10 @@ public void testDebt() { expireAndRotate(throttler); } // now the sampling interval must be 50 / 10 = 5 - assertTrue("Sampling interval is incorrect:"+ throttler.getActiveWindowSamplingInterval(), throttler.getActiveWindowSamplingInterval() == 5); + assertTrue("Sampling interval is incorrect:" + throttler.getActiveWindowSamplingInterval(), throttler.getActiveWindowSamplingInterval() == 5); - // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 10 - (20/5) = 6 + // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 10 - + // (20/5) = 6 // samples for (int i = 0; i < 20; i++) { throttler.sample(); @@ -217,31 +217,37 @@ public void testDebt() { @Test public void testNormalization() { long sampleSize = 10 * 600; - long periodMs = 60*1000; + long periodMs = 60 * SECOND_IN_MS; JfrThrottler throttler = new JfrThrottler(mutex); throttler.beginTest(sampleSize, periodMs); - assertTrue(throttler.getPeriodNs()+" "+ throttler.getEventSampleSize(),throttler.getEventSampleSize()==sampleSize/60 && throttler.getPeriodNs() == 1000000000); + assertTrue(throttler.getPeriodNs() + " " + throttler.getEventSampleSize(), throttler.getEventSampleSize() == sampleSize / 60 && throttler.getPeriodNs() == 1000000 * SECOND_IN_MS); - sampleSize = 10*3600; - periodMs = 3600*1000; + sampleSize = 10 * 3600; + periodMs = 3600 * SECOND_IN_MS; throttler.setThrottle(sampleSize, periodMs); - assertTrue(throttler.getPeriodNs()+" "+ throttler.getEventSampleSize(), throttler.getEventSampleSize()==sampleSize/3600 && throttler.getPeriodNs() == 1000000000); + assertTrue(throttler.getPeriodNs() + " " + throttler.getEventSampleSize(), throttler.getEventSampleSize() == sampleSize / 3600 && throttler.getPeriodNs() == 1000000 * SECOND_IN_MS); } /** * Checks that no ObjectAllocationSample events are emitted when the sampling rate is 0. */ @Test - public void testZeroRate() throws Throwable{ + public void testZeroRate() throws Throwable { // Test throttler in isolation JfrThrottler throttler = new JfrThrottler(mutex); - throttler.setThrottle(0, 2000); + throttler.setThrottle(0, 2 * SECOND_IN_MS); assertFalse(throttler.sample()); - throttler.setThrottle(10, 2000); + throttler.setThrottle(10, 2 * SECOND_IN_MS); assertTrue(throttler.sample()); // Test applying throttling settings to an in-progress recording - Recording recording = startRecording(new String[]{}, null, new HashMap<>()); // don't use default configuration because it includes ObjectAllocationSample by default + Recording recording = startRecording(new String[]{}, null, new HashMap<>()); // don't use + // default + // configuration + // because it + // includes + // ObjectAllocationSample + // by default recording.enable(JfrEvent.ObjectAllocationSample.getName()).with("throttle", "0/s"); final int alignedHeapChunkSize = com.oracle.svm.core.util.UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); allocateCharArray(alignedHeapChunkSize); @@ -249,7 +255,7 @@ public void testZeroRate() throws Throwable{ recording.stop(); recording.close(); - assertTrue(getEvents(recording.getDestination(),new String[]{JfrEvent.ObjectAllocationSample.getName()}).size()==0); + assertTrue(getEvents(recording.getDestination(), new String[]{JfrEvent.ObjectAllocationSample.getName()}).size() == 0); } @NeverInline("Prevent escape analysis.") @@ -257,9 +263,8 @@ private static char[] allocateCharArray(int length) { return new char[length]; } - @Test - public void testDistributionUniform(){ + public void testDistributionUniform() { final int maxPopPerWindow = 2000; final int minPopPerWindow = 2; final int expectedSamplesPerWindow = 50; @@ -267,48 +272,52 @@ public void testDistributionUniform(){ } @Test - public void testDistributionHighRate(){ + public void testDistributionHighRate() { final int maxPopPerWindow = 2000; final int expectedSamplesPerWindow = 50; - testDistribution(() -> maxPopPerWindow - , expectedSamplesPerWindow, 0.02); + testDistribution(() -> maxPopPerWindow, expectedSamplesPerWindow, 0.02); } + @Test - public void testDistributionLowRate(){ + public void testDistributionLowRate() { final int minPopPerWindow = 2; testDistribution(() -> minPopPerWindow, minPopPerWindow, 0.05); } + @Test - public void testDistributionEarlyBurst(){ // maybe this is 41000 instead of 50000 because projected population size is low + public void testDistributionEarlyBurst() { // maybe this is 41000 instead of 50000 because + // projected population size is low final int maxPopPerWindow = 2000; final int expectedSamplesPerWindow = 50; final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs AtomicInteger count = new AtomicInteger(1); - testDistribution(() ->count.getAndIncrement() % accumulatedDebtCarryLimit == 1 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.9); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 1 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.9); } @Test - public void testDistributionMidBurst(){ + public void testDistributionMidBurst() { final int maxPopPerWindow = 2000; final int expectedSamplesPerWindow = 50; final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs AtomicInteger count = new AtomicInteger(1); - testDistribution(() ->count.getAndIncrement() % accumulatedDebtCarryLimit == 5 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.5); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 5 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.5); } @Test - public void testDistributionLateBurst(){ // *** I think its [NOT] bursting every window by accident. windowCount=10000 not 1000 + public void testDistributionLateBurst() { // *** I think its [NOT] bursting every window by + // accident. windowCount=10000 not 1000 final int maxPopPerWindow = 2000; final int expectedSamplesPerWindow = 50; final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs AtomicInteger count = new AtomicInteger(1); - testDistribution(() ->count.getAndIncrement() % accumulatedDebtCarryLimit == 0 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.0); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 0 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.0); } + /** * This is a more involved test that checks the sample distribution. It has been adapted from * JfrGTestAdaptiveSampling in the OpenJDK */ - private void testDistribution(IncomingPopulation incomingPopulation, int samplePointsPerWindow, double errorFactor){ + private void testDistribution(IncomingPopulation incomingPopulation, int samplePointsPerWindow, double errorFactor) { final int distributionSlots = 100; final int windowDurationMs = 100; final int windowCount = 10000; @@ -317,47 +326,47 @@ private void testDistribution(IncomingPopulation incomingPopulation, int sampleP final int windowLookBackCount = 50; final double maxSampleBias = 0.11; JfrThrottler throttler = new JfrThrottler(mutex); - throttler.beginTest(expectedSamplesPerWindow*WINDOWS_PER_PERIOD, windowDurationMs*WINDOWS_PER_PERIOD); + throttler.beginTest(expectedSamplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); int[] population = new int[distributionSlots]; int[] sample = new int[distributionSlots]; int populationSize = 0; int sampleSize = 0; - for(int t = 0; t < windowCount; t++){ + for (int t = 0; t < windowCount; t++) { int windowPop = incomingPopulation.getWindowPopulation(); - for(int i =0 ; i < windowPop; i++){ + for (int i = 0; i < windowPop; i++) { populationSize++; int index = ThreadLocalRandom.current().nextInt(0, 100); population[index] += 1; - if (throttler.sample()){ + if (throttler.sample()) { sampleSize++; - sample[index] +=1; + sample[index] += 1; } } expireAndRotate(throttler); } int targetSampleSize = samplePointsPerWindow * windowCount; - System.out.println("Population size:" + populationSize + " Sample size: "+sampleSize); - expectNear(targetSampleSize, sampleSize, expectedSamples* errorFactor); + System.out.println("Population size:" + populationSize + " Sample size: " + sampleSize); + expectNear(targetSampleSize, sampleSize, expectedSamples * errorFactor); assertDistributionProperties(distributionSlots, population, sample, populationSize, sampleSize); } - private static void expectNear(double value1, double value2, double error){ -// System.out.println(value1 +" "+ value2); - assertTrue(Math.abs(value1-value2) <= error); + private static void expectNear(double value1, double value2, double error) { +// System.out.println(value1 +" "+ value2); + assertTrue(Math.abs(value1 - value2) <= error); } - private static void assertDistributionProperties(int distributionSlots, int[] population, int[] sample, int populationSize, int sampleSize ) { - int populationSum=0; + private static void assertDistributionProperties(int distributionSlots, int[] population, int[] sample, int populationSize, int sampleSize) { + int populationSum = 0; int sampleSum = 0; for (int i = 0; i < distributionSlots; i++) { populationSum += i * population[i]; sampleSum += i * sample[i]; } - double populationMean = populationSum / (double)populationSize; - double sampleMean = sampleSum / (double)sampleSize; + double populationMean = populationSum / (double) populationSize; + double sampleMean = sampleSum / (double) sampleSize; double populationVariance = 0; double sampleVariance = 0; @@ -372,21 +381,21 @@ private static void assertDistributionProperties(int distributionSlots, int[] po sampleVariance = sampleVariance / (sampleSize - 1); double populationStdev = Math.sqrt(populationVariance); double sampleStdev = Math.sqrt(sampleVariance); -// System.out.println("populationStdev:"+populationStdev +" sampleStdev:"+sampleStdev ); +// System.out.println("populationStdev:"+populationStdev +" sampleStdev:"+sampleStdev ); expectNear(populationStdev, sampleStdev, 0.5); // 0.5 value copied from Hotspot test expectNear(populationMean, sampleMean, populationStdev); } - interface IncomingPopulation{ + interface IncomingPopulation { int getWindowPopulation(); } /** - * Helper method that expires and rotates a throttler's active window + * Helper method that expires and rotates a throttler's active window. */ private static void expireAndRotate(JfrThrottler throttler) { throttler.expireActiveWindow(); - assertTrue("should be expired", throttler.IsActiveWindowExpired()); + assertTrue("should be expired", throttler.isActiveWindowExpired()); assertFalse("Should have rotated not sampled!", throttler.sample()); } } From dc8f2d0c16be562970a148a05bc2e140ba1776d2 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 23 Jun 2023 15:28:46 -0400 Subject: [PATCH 12/34] use geometric sampling distribution. Adjust debt testcase --- .../svm/core/jfr/JfrThrottlerWindow.java | 22 +++-- .../events/ObjectAllocationSampleEvent.java | 3 +- .../oracle/svm/test/jfr/TestThrottler.java | 95 +++++++++++++------ 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index f7e54388fef2..7ad2c2c317fe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -27,6 +27,8 @@ import com.oracle.svm.core.jdk.UninterruptibleUtils; +import static java.lang.Math.log; + public class JfrThrottlerWindow { // reset every rotation public UninterruptibleUtils.AtomicLong measuredPopSize; // already volatile @@ -83,15 +85,14 @@ public long samplesExpected() { public void configure(long debt, double projectedPopSize) { this.debt = debt; - if (projectedPopSize <= samplesPerWindow) { + if (projectedPopSize <= samplesExpected()) { samplingInterval = 1; } else { // It's important to round *up* otherwise we risk violating the upper bound - samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); // *** - // TODO: - // geometric - // distribution - // stuff + // TODO geometric +// samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); + double projectedProbability = (double) samplesExpected() / projectedPopSize; + samplingInterval = nextGeometric(projectedProbability, Math.random()); } // activeWindowSampleLimit is either projectedPopSize or samplesExpected() (if // projectedPopSize < samplesPerWindow) @@ -108,6 +109,15 @@ public void configure(long debt, double projectedPopSize) { } + long nextGeometric(double p, double u) { // *** is P is larger, then its more likely sampling + // interval is smaller + if (u == 0.0) { + u = 0.01; + } + // Inverse CDF for the geometric distribution. + return (long) Math.ceil(log(1.0 - u) / log(1.0 - p)); + } + public boolean isExpired() { if (isTest) { // There is a need to mock JfrTicks for testing. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java index 6c0ae20c168e..9fdf47739a82 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java @@ -37,6 +37,7 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalLong; import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.JavaThreads; public class ObjectAllocationSampleEvent { private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); @@ -53,7 +54,7 @@ public static void emit(long startTicks, Class clazz) { @Uninterruptible(reason = "Accesses a JFR buffer.") private static void emit0(long startTicks, Class clazz) { if (JfrEvent.ObjectAllocationSample.shouldEmit()) { - long currentAllocationSize = PlatformThreads.getThreadAllocatedBytes(com.oracle.svm.core.thread.JavaThreads.getCurrentThreadId()); + long currentAllocationSize = PlatformThreads.getThreadAllocatedBytes(JavaThreads.getCurrentThreadId()); JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 67cd9b8c2596..120559f2cf06 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -176,6 +176,45 @@ public void testEWMA() { * Window lookback for this test is 25. Window duration is 1 second. Window divisor is default * of 5. */ +// @Test +// public void testDebt() { +// final long samplesPerWindow = 10; +// final long actualSamplesPerWindow = 50; +// JfrThrottler throttler = new JfrThrottler(mutex); +// throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * +// WINDOW_DURATION_MS); +// +// for (int p = 0; p < 50; p++) { +// for (int i = 0; i < actualSamplesPerWindow; i++) { +// throttler.sample(); +// } +// expireAndRotate(throttler); +// } +// // now the sampling interval must be 50 / 10 = 5 +// assertTrue("Sampling interval is incorrect:" + throttler.getActiveWindowSamplingInterval(), +// throttler.getActiveWindowSamplingInterval() == 5); +// +// // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 10 - +// // (20/5) = 6 +// // samples +// for (int i = 0; i < 20; i++) { +// throttler.sample(); +// } +// expireAndRotate(throttler); +// assertTrue("Should have debt from under sampling.", throttler.getActiveWindowDebt() == 6); +// // sampling interval should be 3 now. Take no samples and rotate. Results in accumulated +// // debt 6 + 10 +// expireAndRotate(throttler); +// assertTrue("Should have accumulated debt from under sampling consecutively.", +// throttler.getActiveWindowDebt() == 16); +// expireAndRotate(throttler); +// expireAndRotate(throttler); +// assertTrue("Debt is so high we should not skip any samples now.", +// throttler.getActiveWindowSamplingInterval() == 1); +// expireAndRotate(throttler); +// assertTrue("Debt should be forgiven at beginning of new period.", throttler.getActiveWindowDebt() +// == 0); +// } @Test public void testDebt() { final long samplesPerWindow = 10; @@ -189,26 +228,36 @@ public void testDebt() { } expireAndRotate(throttler); } - // now the sampling interval must be 50 / 10 = 5 - assertTrue("Sampling interval is incorrect:" + throttler.getActiveWindowSamplingInterval(), throttler.getActiveWindowSamplingInterval() == 5); - // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 10 - - // (20/5) = 6 - // samples - for (int i = 0; i < 20; i++) { + // Do not sample for this window. Rotate. + expireAndRotate(throttler); + + // Debt should be at least 10 because we took no samples last window. + long debt = throttler.getActiveWindowDebt(); + assertTrue("Should have debt from under sampling.", debt >= 10); + + // Limit max potential samples to half samplesPerWindow. Meaning debt must increase by at + // least samplesPerWindow/2. + for (int i = 0; i < samplesPerWindow / 2; i++) { throttler.sample(); } expireAndRotate(throttler); - assertTrue("Should have debt from under sampling.", throttler.getActiveWindowDebt() == 6); - // sampling interval should be 3 now. Take no samples and rotate. Results in accumulated - // debt 6 + 10 - expireAndRotate(throttler); - assertTrue("Should have accumulated debt from under sampling consecutively.", throttler.getActiveWindowDebt() == 16); - expireAndRotate(throttler); - expireAndRotate(throttler); - assertTrue("Debt is so high we should not skip any samples now.", throttler.getActiveWindowSamplingInterval() == 1); + assertTrue("Should have debt from under sampling.", throttler.getActiveWindowDebt() >= debt + samplesPerWindow / 2); + + // Window lookback is 25. Do not sample for 25 windows. + for (int i = 0; i < 25; i++) { + expireAndRotate(throttler); + } + + // At this point sampling interval must be 1 because the projected population must be 0. + for (int i = 0; i < (samplesPerWindow + samplesPerWindow * WINDOWS_PER_PERIOD); i++) { + throttler.sample(); + } + + assertFalse(throttler.sample()); + expireAndRotate(throttler); - assertTrue("Debt should be forgiven at beginning of new period.", throttler.getActiveWindowDebt() == 0); + assertTrue(throttler.getActiveWindowDebt() == 0); } /** @@ -241,13 +290,9 @@ public void testZeroRate() throws Throwable { assertTrue(throttler.sample()); // Test applying throttling settings to an in-progress recording - Recording recording = startRecording(new String[]{}, null, new HashMap<>()); // don't use - // default - // configuration - // because it - // includes - // ObjectAllocationSample - // by default + // Avoid using default configuration becuase it enables ObjectAllocationSample events by + // default + Recording recording = startRecording(new String[]{}, null, new HashMap<>()); recording.enable(JfrEvent.ObjectAllocationSample.getName()).with("throttle", "0/s"); final int alignedHeapChunkSize = com.oracle.svm.core.util.UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); allocateCharArray(alignedHeapChunkSize); @@ -323,8 +368,7 @@ private void testDistribution(IncomingPopulation incomingPopulation, int sampleP final int windowCount = 10000; final int expectedSamplesPerWindow = 50; final int expectedSamples = expectedSamplesPerWindow * windowCount; - final int windowLookBackCount = 50; - final double maxSampleBias = 0.11; + JfrThrottler throttler = new JfrThrottler(mutex); throttler.beginTest(expectedSamplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); @@ -347,13 +391,11 @@ private void testDistribution(IncomingPopulation incomingPopulation, int sampleP expireAndRotate(throttler); } int targetSampleSize = samplePointsPerWindow * windowCount; - System.out.println("Population size:" + populationSize + " Sample size: " + sampleSize); expectNear(targetSampleSize, sampleSize, expectedSamples * errorFactor); assertDistributionProperties(distributionSlots, population, sample, populationSize, sampleSize); } private static void expectNear(double value1, double value2, double error) { -// System.out.println(value1 +" "+ value2); assertTrue(Math.abs(value1 - value2) <= error); } @@ -381,7 +423,6 @@ private static void assertDistributionProperties(int distributionSlots, int[] po sampleVariance = sampleVariance / (sampleSize - 1); double populationStdev = Math.sqrt(populationVariance); double sampleStdev = Math.sqrt(sampleVariance); -// System.out.println("populationStdev:"+populationStdev +" sampleStdev:"+sampleStdev ); expectNear(populationStdev, sampleStdev, 0.5); // 0.5 value copied from Hotspot test expectNear(populationMean, sampleMean, populationStdev); } From ecc2da11b7afc9548fe77980e628c7be8a609723 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 23 Jun 2023 17:00:59 -0400 Subject: [PATCH 13/34] Clean up. Group allocation events. Add testing back door static class. --- .../genscavenge/ThreadLocalAllocation.java | 9 +- .../com/oracle/svm/core/jfr/JfrThrottler.java | 251 ++++++------------ .../svm/core/jfr/JfrThrottlerSupport.java | 10 +- .../svm/core/jfr/JfrThrottlerWindow.java | 36 +-- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 5 - ...pleEvent.java => JfrAllocationEvents.java} | 49 +++- .../ObjectAllocationInNewTLABEvent.java | 63 ----- .../oracle/svm/test/jfr/TestThrottler.java | 102 +++---- 8 files changed, 181 insertions(+), 344 deletions(-) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/{ObjectAllocationSampleEvent.java => JfrAllocationEvents.java} (60%) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationInNewTLABEvent.java diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index f23919b3ba6b..ec6ab6a0e6bd 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -62,7 +62,7 @@ import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.LayoutEncoding; import com.oracle.svm.core.jfr.JfrTicks; -import com.oracle.svm.core.jfr.events.ObjectAllocationInNewTLABEvent; +import com.oracle.svm.core.jfr.events.JfrAllocationEvents; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; @@ -75,7 +75,6 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalWord; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.core.jfr.events.ObjectAllocationSampleEvent; /** * Bump-pointer allocation from thread-local top and end Pointers. Many of these methods are called @@ -238,8 +237,7 @@ private static Object slowPathNewInstanceWithoutAllocating(DynamicHub hub) { AlignedHeader newTlab = HeapImpl.getChunkProvider().produceAlignedChunk(); return allocateInstanceInNewTlab(hub, newTlab); } finally { - ObjectAllocationInNewTLABEvent.emit(startTicks, hub, LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()), HeapParameters.getAlignedHeapChunkSize()); - ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub)); + JfrAllocationEvents.emit(startTicks, DynamicHub.toClass(hub), LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()), HeapParameters.getAlignedHeapChunkSize()); DeoptTester.enableDeoptTesting(); } } @@ -318,8 +316,7 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un } return array; } finally { - ObjectAllocationInNewTLABEvent.emit(startTicks, hub, size, tlabSize); - ObjectAllocationSampleEvent.emit(startTicks, DynamicHub.toClass(hub)); + JfrAllocationEvents.emit(startTicks, DynamicHub.toClass(hub), size, tlabSize); DeoptTester.enableDeoptTesting(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 50dce3b3b450..cb91ef4f5458 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -28,39 +28,24 @@ import com.oracle.svm.core.jdk.UninterruptibleUtils; -import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.locks.VMMutex; /** - * Similar to Hotspot, each event that allows throttling, shall have its own throttler instance. An - * alternative approach could be to use event specific throttling parameters in each event's event - * settings. - * - * Another alternative could be to maintain a set of parameters for each event type within the - * JfrThrottler singleton. But maps cannot be used because allocation cannot be done while on the - * allocation slow path. Maybe the map can be created in hosted mode before any allocation happens + * Each event that allows throttling should have its own throttler instance. */ public class JfrThrottler { - private static final long SECOND_IN_NS = 1000000000; private static final long SECOND_IN_MS = 1000; + private static final long SECOND_IN_NS = 1000000 * SECOND_IN_MS; private static final long MINUTE_IN_NS = SECOND_IN_NS * 60; -// private static final long MINUTE_IN_MS = SECOND_IN_MS *60; -// private static final long HOUR_IN_MS = MINUTE_IN_MS * 60; -// private static final long HOUR_IN_NS = MINUTE_IN_NS * 60; -// private static final long DAY_IN_MS = HOUR_IN_MS * 24; -// private static final long DAY_IN_NS = HOUR_IN_NS * 24; -// private static final long TEN_PER_S_IN_MINUTES = 600; -// private static final long TEN_PER_S_IN_HOURS = 36000; -// private static final long TEN_PER_S_IN_DAYS = 864000; -// Copied from hotspot + + // The following are set to match the values in OpenJDK private static final int WINDOW_DIVISOR = 5; + private static final int LOW_RATE_UPPER_BOUND = 9; private static final int EVENT_THROTTLER_OFF = -2; - // Can't use reentrant lock because it allocates -// private final UninterruptibleUtils.AtomicPointer lock; private final VMMutex mutex; - public UninterruptibleUtils.AtomicBoolean disabled; // Already volatile + public UninterruptibleUtils.AtomicBoolean disabled; private JfrThrottlerWindow window0; private JfrThrottlerWindow window1; private volatile JfrThrottlerWindow activeWindow; @@ -77,12 +62,9 @@ public JfrThrottler(VMMutex mutex) { accumulatedDebtCarryCount = 0; reconfigure = false; disabled = new UninterruptibleUtils.AtomicBoolean(true); -// lock = new UninterruptibleUtils.AtomicPointer<>(); // I assume this is initially -// WordFactorynullPointer() window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); - activeWindow = window0; // same as hotspot. Unguarded is ok because all throttler instances - // are created before program execution. + activeWindow = window0; this.mutex = mutex; } @@ -92,36 +74,12 @@ public JfrThrottler(VMMutex mutex) { * critical section because setting the sample size and period must be done together atomically. * Otherwise, we risk a window's params being set with only one of the two updated. */ -// private void normalize1(long eventSampleSize, long periodMs){ -// VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); -// // Do we want more than 10samples/s ? If so convert to samples/s -// if (periodMs <= SECOND_IN_MS){ -// //nothing -// } else if (periodMs <= MINUTE_IN_MS) { -// if (eventSampleSize >= TEN_PER_S_IN_MINUTES) { -// eventSampleSize /= 60; -// periodMs /= 60; -// } -// } else if (periodMs <= HOUR_IN_MS) { -// if (eventSampleSize >=TEN_PER_S_IN_HOURS) { -// eventSampleSize /= 3600; -// periodMs /= 3600; -// } -// } else if (periodMs <= DAY_IN_MS) { -// if (eventSampleSize >=TEN_PER_S_IN_DAYS) { -// eventSampleSize /= 86400; -// periodMs /= 86400; -// } -// } -// this.eventSampleSize = eventSampleSize; -// this.periodNs = periodMs * 1000000; -// } private void normalize(long samplesPerPeriod, double periodMs) { - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); + assert mutex.isOwner(); // Do we want more than 10samples/s ? If so convert to samples/s double periodsPerSecond = 1000.0 / periodMs; double samplesPerSecond = samplesPerPeriod * periodsPerSecond; - if (samplesPerSecond >= 10 && periodMs > SECOND_IN_MS) { + if (samplesPerSecond > LOW_RATE_UPPER_BOUND && periodMs > SECOND_IN_MS) { this.periodNs = SECOND_IN_NS; this.eventSampleSize = (long) samplesPerSecond; return; @@ -142,20 +100,19 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { try { normalize(eventSampleSize, periodMs); reconfigure = true; - rotateWindow(); // could omit this and choose to wait until next rotation. + rotateWindow(); } finally { mutex.unlock(); } - disabled.set(false); // should be after the above are set + disabled.set(false); return true; } /** - * The real active window may change while we're doing the sampling. That's ok as long as we - * perform operations wrt a consistent window during this method. That's why we declare a stack - * variable: "window". If the active window does change after we've read it from main memory, - * there's no harm done because now we'll be writing to the next window (which gets reset before - * becoming active again). + * The real active window may change while we're doing the sampling. That's fine as long as we + * perform operations with respect a consistent window during this method. It's fine if the + * active window changes after we've read it from memory, because now we'll be writing to the + * next window (which gets reset before becoming active again). */ public boolean sample() { if (disabled.get()) { @@ -164,42 +121,44 @@ public boolean sample() { JfrThrottlerWindow window = activeWindow; boolean expired = window.isExpired(); if (expired) { - // Check lock to see if someone is already rotating. If so, move on. - mutex.lock(); // TODO: would be better to tryLock() if possible. + // Check lock in case thread is already rotating. + mutex.lock(); try { - // Once in critical section ensure active window is still expired. - // Another thread may have already handled the expired window, or new settings may - // have already triggered a rotation. + /* + * Once in critical section, ensure active window is still expired. Another thread + * may have already handled the expired window, or new settings may have already + * triggered a rotation. + */ if (activeWindow.isExpired()) { rotateWindow(); } } finally { mutex.unlock(); } - return false; // if expired, hotspot returns false + return false; } return window.sample(); } - /* - * Locked. Only one thread should be rotating at once. If due to an expired window, then other - * threads that try to rotate due to expiry, will just return false. If race between two threads - * updating settings, then one will just have to wait for the other to finish. Order doesn't - * matter as long as they are not interrupted. + /** + * Only one thread should be rotating at once. If rotating due to an expired window, then other + * threads that try to rotate due to expiry, will simply return false. If there's a race with a + * thread updating settings, then one will just have to wait for the other to finish. Order + * doesn't really matter as long as they are not interrupted. */ private void rotateWindow() { - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); + assert mutex.isOwner(); configure(); installNextWindow(); } private void installNextWindow() { - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); + assert mutex.isOwner(); activeWindow = getNextWindow(); } JfrThrottlerWindow getNextWindow() { - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); + assert mutex.isOwner(); if (window0 == activeWindow) { return window1; } @@ -207,7 +166,7 @@ JfrThrottlerWindow getNextWindow() { } private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); + assert mutex.isOwner(); if (periodNs == 0 || windowDurationNs >= SECOND_IN_NS) { return 1; } @@ -215,49 +174,30 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { } private long amortizeDebt(JfrThrottlerWindow lastWindow) { - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); + assert mutex.isOwner(); if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { accumulatedDebtCarryCount = 1; return 0; // reset because new settings have been applied } accumulatedDebtCarryCount++; - // did we sample less than we were supposed to? - return lastWindow.samplesExpected() - lastWindow.samplesTaken(); // (base samples + carried - // over debt) - samples - // taken + // Did we sample less than we were supposed to? + return lastWindow.samplesExpected() - lastWindow.samplesTaken(); } /** * Handles the case where the sampling rate is very low. */ -// private void setSamplePointsAndWindowDuration1(){ -// VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); -// VMError.guarantee(reconfigure, "Should only modify sample size and window duration during -// reconfigure."); -// JfrThrottlerWindow next = getNextWindow(); -// long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; -// long windowDurationNs = periodNs / WINDOW_DIVISOR; -// if (eventSampleSize < 10 -// || periodNs >= MINUTE_IN_NS && eventSampleSize < TEN_PER_S_IN_MINUTES -// || periodNs >= HOUR_IN_NS && eventSampleSize < TEN_PER_S_IN_HOURS -// || periodNs >= DAY_IN_NS && eventSampleSize < TEN_PER_S_IN_DAYS){ -// samplesPerWindow = eventSampleSize; -// windowDurationNs = periodNs; -// } -// activeWindow.samplesPerWindow = samplesPerWindow; -// activeWindow.windowDurationNs = windowDurationNs; -// next.samplesPerWindow = samplesPerWindow; -// next.windowDurationNs = windowDurationNs; -// } private void setSamplePointsAndWindowDuration() { - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); - VMError.guarantee(reconfigure, "Should only modify sample size and window duration during reconfigure."); + assert mutex.isOwner(); + assert reconfigure; JfrThrottlerWindow next = getNextWindow(); long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; long windowDurationNs = periodNs / WINDOW_DIVISOR; - // If period isn't 1s, then we're effectively taking under 10 samples/s - // because the values have already undergone normalization. - if (eventSampleSize < 10 || periodNs > SECOND_IN_NS) { + /* + * If period isn't 1s, then we're effectively taking under 10 samples/s because the values + * have already undergone normalization. + */ + if (eventSampleSize <= LOW_RATE_UPPER_BOUND || periodNs > SECOND_IN_NS) { samplesPerWindow = eventSampleSize; windowDurationNs = periodNs; } @@ -268,25 +208,27 @@ private void setSamplePointsAndWindowDuration() { } public void configure() { - VMError.guarantee(mutex.isOwner(), "Throttler lock must be acquired in critical section."); + assert mutex.isOwner(); JfrThrottlerWindow next = getNextWindow(); - // Store updated params to both windows. + // Store updated parameters to both windows. if (reconfigure) { setSamplePointsAndWindowDuration(); accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(next.windowDurationNs); - // This effectively means we reset debt count upon reconfigure + // This effectively means we reset the debt count upon reconfiguration accumulatedDebtCarryCount = accumulatedDebtCarryLimit; - // compute alpha and debt avgPopulationSize = 0; - ewmaPopulationSizeAlpha = (double) 1 / windowLookback(next); // lookback count; + ewmaPopulationSizeAlpha = (double) 1 / windowLookback(next); reconfigure = false; } next.configure(amortizeDebt(activeWindow), projectPopulationSize(activeWindow.measuredPopSize.get())); } - private double windowLookback(JfrThrottlerWindow window) { + /** + * The lookback values are set to match the values in OpenJDK. + */ + private static double windowLookback(JfrThrottlerWindow window) { if (window.windowDurationNs <= SECOND_IN_NS) { return 25.0; } else if (window.windowDurationNs <= MINUTE_IN_NS) { @@ -305,73 +247,48 @@ private static double exponentiallyWeightedMovingAverage(double currentMeasureme return alpha * currentMeasurement + (1 - alpha) * prevEwma; } -// /** Basic spin lock */ -// private void lock() { -// while (!tryLock()) { -// PauseNode.pause(); -// } -// } -// -// private boolean tryLock() { -// return lock.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread()); -// } -// -// private void unlock() { -//// VMError.guarantee(lock.get().equal(CurrentIsolate.getCurrentThread()), "Throttler lock must be -// acquired in critical section."); -// lock.set(WordFactory.nullPointer()); -// } - - /** Visible for testing. */ - public void beginTest(long eventSampleSize, long periodMs) { - window0.isTest = true; - window1.isTest = true; - window0.currentTestNanos = 0; - window1.currentTestNanos = 0; - setThrottle(eventSampleSize, periodMs); - } + /** Visible for testing. This assumes the tests are taking care of synchronization. */ + public static class TestingBackDoor { - /** Visible for testing. */ - public double getActiveWindowProjectedPopulationSize() { - return avgPopulationSize; - } + public static void beginTest(JfrThrottler throttler, long eventSampleSize, long periodMs) { + throttler.window0.isTest = true; + throttler.window1.isTest = true; + throttler.window0.currentTestNanos = 0; + throttler.window1.currentTestNanos = 0; + throttler.setThrottle(eventSampleSize, periodMs); + } - /** Visible for testing. */ - public long getActiveWindowSamplingInterval() { - return activeWindow.samplingInterval; - } + public static double getActiveWindowProjectedPopulationSize(JfrThrottler throttler) { + return throttler.avgPopulationSize; + } - /** Visible for testing. */ - public long getActiveWindowDebt() { - return activeWindow.debt; - } + public static long getActiveWindowDebt(JfrThrottler throttler) { + return throttler.activeWindow.debt; + } - public double getWindowLookback() { - return windowLookback(activeWindow); - } + public static double getWindowLookback(JfrThrottler throttler) { + return windowLookback(throttler.activeWindow); + } - /** Visible for testing. */ - public boolean isActiveWindowExpired() { - return activeWindow.isExpired(); - } + public static boolean isActiveWindowExpired(JfrThrottler throttler) { + return throttler.activeWindow.isExpired(); + } - /** Visible for testing. */ - public long getPeriodNs() { - return periodNs; - } + public static long getPeriodNs(JfrThrottler throttler) { + return throttler.periodNs; + } - /** Visible for testing. */ - public long getEventSampleSize() { - return eventSampleSize; - } + public static long getEventSampleSize(JfrThrottler throttler) { + return throttler.eventSampleSize; + } - /** Visible for testing. */ - public void expireActiveWindow() { - if (eventSampleSize < 10 || periodNs > SECOND_IN_NS) { - window0.currentTestNanos += periodNs; - window1.currentTestNanos += periodNs; + public static void expireActiveWindow(JfrThrottler throttler) { + if (throttler.eventSampleSize <= LOW_RATE_UPPER_BOUND || throttler.periodNs > SECOND_IN_NS) { + throttler.window0.currentTestNanos += throttler.periodNs; + throttler.window1.currentTestNanos += throttler.periodNs; + } + throttler.window0.currentTestNanos += throttler.periodNs / WINDOW_DIVISOR; + throttler.window1.currentTestNanos += throttler.periodNs / WINDOW_DIVISOR; } - window0.currentTestNanos += periodNs / WINDOW_DIVISOR; - window1.currentTestNanos += periodNs / WINDOW_DIVISOR; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java index 9f1b90848be4..7ec083a0eee7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java @@ -33,14 +33,14 @@ /** * Each event that supports throttling has its own throttler that can be accessed through this class. - * TODO: Consider making this a proper singleton later - * When more events support throttling, some sort of allocation free hashmap should be used. */ public class JfrThrottlerSupport { JfrThrottler objectAllocationSampleThrottler; @Platforms(Platform.HOSTED_ONLY.class) JfrThrottlerSupport() { - objectAllocationSampleThrottler = new JfrThrottler(new VMMutex("jfrThrottler")); + if (HasJfrSupport.get()) { + objectAllocationSampleThrottler = new JfrThrottler(new VMMutex("jfrThrottler")); + } } private JfrThrottler getThrottler(long eventId) { @@ -53,7 +53,7 @@ private JfrThrottler getThrottler(long eventId) { public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { JfrThrottler throttler = getThrottler(eventTypeId); if (throttler == null) { - //event doesn't support throttling + // This event doesn't support throttling return false; } throttler.setThrottle(eventSampleSize, periodMs); @@ -63,7 +63,7 @@ public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs public boolean shouldCommit(long eventTypeId) { JfrThrottler throttler = getThrottler(eventTypeId); if (throttler == null) { - //event doesn't support throttling + // This event doesn't support throttling return true; } return throttler.sample(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index 7ad2c2c317fe..d2e23c298f45 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -31,14 +31,14 @@ public class JfrThrottlerWindow { // reset every rotation - public UninterruptibleUtils.AtomicLong measuredPopSize; // already volatile - public UninterruptibleUtils.AtomicLong endTicks; // already volatile + public UninterruptibleUtils.AtomicLong measuredPopSize; + public UninterruptibleUtils.AtomicLong endTicks; // Calculated every rotation based on params set by user and results from previous windows public volatile long samplingInterval; - public volatile double activeWindowSampleLimit; + public volatile double maxSampleablePopulation; - // params set by user + // Set by user public volatile long samplesPerWindow; public volatile long windowDurationNs; public volatile long debt; @@ -46,7 +46,7 @@ public class JfrThrottlerWindow { public JfrThrottlerWindow() { windowDurationNs = 0; samplesPerWindow = 0; - activeWindowSampleLimit = 0; + maxSampleablePopulation = 0; measuredPopSize = new UninterruptibleUtils.AtomicLong(0); endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + windowDurationNs); samplingInterval = 1; @@ -63,17 +63,17 @@ public boolean sample() { // Guarantees only one thread can record the last event of the window long prevMeasuredPopSize = measuredPopSize.getAndIncrement(); - // Stop sampling if we're already over the projected population size, and we're over the - // samples per window + // Stop sampling if we're already over maxSampleablePopulation and we're over the expected + // samples per window. if (prevMeasuredPopSize % samplingInterval == 0 && - (prevMeasuredPopSize < activeWindowSampleLimit)) { + (prevMeasuredPopSize < maxSampleablePopulation)) { return true; } return false; } public long samplesTaken() { - if (measuredPopSize.get() > activeWindowSampleLimit) { + if (measuredPopSize.get() > maxSampleablePopulation) { return samplesExpected(); } return measuredPopSize.get() / samplingInterval; @@ -88,15 +88,13 @@ public void configure(long debt, double projectedPopSize) { if (projectedPopSize <= samplesExpected()) { samplingInterval = 1; } else { - // It's important to round *up* otherwise we risk violating the upper bound - // TODO geometric -// samplingInterval = (long) Math.ceil(projectedPopSize / (double) samplesExpected()); + double projectedProbability = (double) samplesExpected() / projectedPopSize; samplingInterval = nextGeometric(projectedProbability, Math.random()); } - // activeWindowSampleLimit is either projectedPopSize or samplesExpected() (if - // projectedPopSize < samplesPerWindow) - this.activeWindowSampleLimit = samplesExpected() * samplingInterval; + + this.maxSampleablePopulation = samplesExpected() * samplingInterval; + // reset measuredPopSize.set(0); @@ -106,11 +104,13 @@ public void configure(long debt, double projectedPopSize) { } else { endTicks.set(JfrTicks.currentTimeNanos() + windowDurationNs); } - } - long nextGeometric(double p, double u) { // *** is P is larger, then its more likely sampling - // interval is smaller + /** + * This method is essentially the same as jfrAdaptiveSampler::next_geometric(double, double) in + * the OpenJDK. + */ + private long nextGeometric(double p, double u) { if (u == 0.0) { u = 0.01; } 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 89adac0af8e9..f987e90162c1 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 @@ -645,17 +645,12 @@ public boolean isEnabled(JfrEvent event) { } public boolean shouldCommit(JfrEvent event) { - // find the right throttler for the event (each event should have its own)like in hotspot - // if none found, return true. - // call into throttler code return jfrThrottlerSupport.shouldCommit(event.getId()); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { - // find the right throttler for the event and set the new params there return jfrThrottlerSupport.setThrottle(eventTypeId, eventSampleSize, periodMs); - // TODO: why would it ever return false? Maybe if the throttler doesn't exist? } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java similarity index 60% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java index 9fdf47739a82..e988562e69a1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java @@ -26,33 +26,62 @@ package com.oracle.svm.core.jfr.events; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.word.UnsignedWord; + 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.SubstrateJVM; -import com.oracle.svm.core.jfr.HasJfrSupport; -import org.graalvm.nativeimage.StackValue; import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalLong; import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.JavaThreads; -public class ObjectAllocationSampleEvent { +public class JfrAllocationEvents { private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); - public static void emit(long startTicks, Class clazz) { + + public static void emit(long startTicks, Class clazz, UnsignedWord allocationSize, UnsignedWord tlabSize) { if (HasJfrSupport.get()) { - // TODO: consider moving this to after the isRecording check in emit0 to avoid duplicate checks. Might be a pain to deal with uninterruptibility though. Also we want to minimize uninterruptible code usage. - // Doesn't hurt to check twice, might save us some time doing the sampling - if (SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { - emit0(startTicks, clazz); + emitObjectAllocationInNewTLAB(startTicks, clazz, allocationSize, tlabSize); + + if (shouldEmitObjectAllocationSample() && SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { + emitObjectAllocationSample(startTicks, clazz); } } } + /** + * This method exists as a slight optimization to avoid entering the sampler code if + * unnecessary. We'll have to check {@link JfrEvent.shouldEmit()} again if a sample ends up + * being taken. + */ + @Uninterruptible(reason = "Needed for JfrEvent.shouldEmit().") + private static boolean shouldEmitObjectAllocationSample() { + return JfrEvent.ObjectAllocationSample.shouldEmit(); + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitObjectAllocationInNewTLAB(long startTicks, Class clazz, UnsignedWord allocationSize, UnsignedWord tlabSize) { + if (JfrEvent.ObjectAllocationInNewTLAB.shouldEmit()) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationInNewTLAB); + JfrNativeEventWriter.putLong(data, startTicks); + JfrNativeEventWriter.putEventThread(data); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationInNewTLAB, 0)); + JfrNativeEventWriter.putClass(data, clazz); + JfrNativeEventWriter.putLong(data, allocationSize.rawValue()); + JfrNativeEventWriter.putLong(data, tlabSize.rawValue()); + JfrNativeEventWriter.endSmallEvent(data); + } + } + @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(long startTicks, Class clazz) { + private static void emitObjectAllocationSample(long startTicks, Class clazz) { if (JfrEvent.ObjectAllocationSample.shouldEmit()) { long currentAllocationSize = PlatformThreads.getThreadAllocatedBytes(JavaThreads.getCurrentThreadId()); @@ -61,7 +90,7 @@ private static void emit0(long startTicks, Class clazz) { JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationSample); JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); //This causes problems during JFR shutdown + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); JfrNativeEventWriter.putClass(data, clazz); JfrNativeEventWriter.putLong(data, currentAllocationSize - lastAllocationSize.get()); JfrNativeEventWriter.endSmallEvent(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationInNewTLABEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationInNewTLABEvent.java deleted file mode 100644 index 76226394d56a..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationInNewTLABEvent.java +++ /dev/null @@ -1,63 +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.core.jfr.events; - -import org.graalvm.nativeimage.StackValue; -import org.graalvm.word.UnsignedWord; - -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.hub.DynamicHub; -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.SubstrateJVM; - -public class ObjectAllocationInNewTLABEvent { - public static void emit(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { - if (HasJfrSupport.get()) { - emit0(startTicks, hub, allocationSize, tlabSize); - } - } - - @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { - if (JfrEvent.ObjectAllocationInNewTLAB.shouldEmit()) { - JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); - JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); - JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationInNewTLAB); - JfrNativeEventWriter.putLong(data, startTicks); - JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationInNewTLAB, 0)); - JfrNativeEventWriter.putClass(data, DynamicHub.toClass(hub)); - JfrNativeEventWriter.putLong(data, allocationSize.rawValue()); - JfrNativeEventWriter.putLong(data, tlabSize.rawValue()); - JfrNativeEventWriter.endSmallEvent(data); - } - } -} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 120559f2cf06..5478e463f189 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -30,11 +30,12 @@ import com.oracle.svm.core.genscavenge.HeapParameters; import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.jfr.JfrThrottler; +import com.oracle.svm.core.jfr.JfrThrottler.TestingBackDoor; +import com.oracle.svm.core.util.UnsignedUtils; import jdk.jfr.Recording; import org.junit.Test; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; @@ -46,9 +47,9 @@ public class TestThrottler extends JfrRecordingTest { - // Based on hardcoded value in the throttler class. + // Based on the hardcoded value in the throttler class. private static final long WINDOWS_PER_PERIOD = 5; - // Arbitrary. It doesn't matter what this is. + // Arbitrary private static final long WINDOW_DURATION_MS = 200; private static final long SAMPLES_PER_WINDOW = 10; private static final long SECOND_IN_MS = 1000; @@ -81,7 +82,7 @@ public void testCapConcurrent() throws InterruptedException { final AtomicInteger count = new AtomicInteger(); List testingThreads = new ArrayList<>(); JfrThrottler throttler = new JfrThrottler(mutex); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); Runnable doSampling = () -> { for (int i = 0; i < samplesPerWindow; i++) { boolean sample = throttler.sample(); @@ -126,7 +127,7 @@ public void testCapConcurrent() throws InterruptedException { public void testExpiry() { final long samplesPerWindow = 10; JfrThrottler throttler = new JfrThrottler(mutex); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); int count = 0; for (int i = 0; i < samplesPerWindow * 10; i++) { @@ -154,76 +155,34 @@ public void testEWMA() { // Results in 50 samples per second final long samplesPerWindow = 10; JfrThrottler throttler = new JfrThrottler(mutex); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, SECOND_IN_MS); - assertTrue(throttler.getWindowLookback() == 25.0); + TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, SECOND_IN_MS); + assertTrue(TestingBackDoor.getWindowLookback(throttler) == 25.0); // Arbitrarily chosen int[] population = {310, 410, 610, 310, 910, 420, 770, 290, 880, 640, 220, 110, 330, 590}; - // Used EWMA calculator to confirm actualProjections + // actualProjections are the expected EWMA values int[] actualProjections = {12, 28, 51, 61, 95, 108, 135, 141, 170, 189, 190, 187, 193, 209}; for (int p = 0; p < population.length; p++) { for (int i = 0; i < population[p]; i++) { throttler.sample(); } expireAndRotate(throttler); - double projectedPopulation = throttler.getActiveWindowProjectedPopulationSize(); + double projectedPopulation = TestingBackDoor.getActiveWindowProjectedPopulationSize(throttler); assertTrue(actualProjections[p] == (int) projectedPopulation); } } /** - * Note: computeAccumulatedDebtCarryLimit depends on window duration. - * - * Window lookback for this test is 25. Window duration is 1 second. Window divisor is default - * of 5. + * Ensure debt is being calculated as expected. */ -// @Test -// public void testDebt() { -// final long samplesPerWindow = 10; -// final long actualSamplesPerWindow = 50; -// JfrThrottler throttler = new JfrThrottler(mutex); -// throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * -// WINDOW_DURATION_MS); -// -// for (int p = 0; p < 50; p++) { -// for (int i = 0; i < actualSamplesPerWindow; i++) { -// throttler.sample(); -// } -// expireAndRotate(throttler); -// } -// // now the sampling interval must be 50 / 10 = 5 -// assertTrue("Sampling interval is incorrect:" + throttler.getActiveWindowSamplingInterval(), -// throttler.getActiveWindowSamplingInterval() == 5); -// -// // create debt by under sampling. Instead of 50, only sample 20 times. Debt should be 10 - -// // (20/5) = 6 -// // samples -// for (int i = 0; i < 20; i++) { -// throttler.sample(); -// } -// expireAndRotate(throttler); -// assertTrue("Should have debt from under sampling.", throttler.getActiveWindowDebt() == 6); -// // sampling interval should be 3 now. Take no samples and rotate. Results in accumulated -// // debt 6 + 10 -// expireAndRotate(throttler); -// assertTrue("Should have accumulated debt from under sampling consecutively.", -// throttler.getActiveWindowDebt() == 16); -// expireAndRotate(throttler); -// expireAndRotate(throttler); -// assertTrue("Debt is so high we should not skip any samples now.", -// throttler.getActiveWindowSamplingInterval() == 1); -// expireAndRotate(throttler); -// assertTrue("Debt should be forgiven at beginning of new period.", throttler.getActiveWindowDebt() -// == 0); -// } @Test public void testDebt() { final long samplesPerWindow = 10; - final long actualSamplesPerWindow = 50; + final long populationPerWindow = 50; JfrThrottler throttler = new JfrThrottler(mutex); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * WINDOW_DURATION_MS); + TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * WINDOW_DURATION_MS); for (int p = 0; p < 50; p++) { - for (int i = 0; i < actualSamplesPerWindow; i++) { + for (int i = 0; i < populationPerWindow; i++) { throttler.sample(); } expireAndRotate(throttler); @@ -233,16 +192,16 @@ public void testDebt() { expireAndRotate(throttler); // Debt should be at least 10 because we took no samples last window. - long debt = throttler.getActiveWindowDebt(); + long debt = TestingBackDoor.getActiveWindowDebt(throttler); assertTrue("Should have debt from under sampling.", debt >= 10); - // Limit max potential samples to half samplesPerWindow. Meaning debt must increase by at + // Limit max potential samples to half samplesPerWindow. This means debt must increase by at // least samplesPerWindow/2. for (int i = 0; i < samplesPerWindow / 2; i++) { throttler.sample(); } expireAndRotate(throttler); - assertTrue("Should have debt from under sampling.", throttler.getActiveWindowDebt() >= debt + samplesPerWindow / 2); + assertTrue("Should have debt from under sampling.", TestingBackDoor.getActiveWindowDebt(throttler) >= debt + samplesPerWindow / 2); // Window lookback is 25. Do not sample for 25 windows. for (int i = 0; i < 25; i++) { @@ -257,7 +216,7 @@ public void testDebt() { assertFalse(throttler.sample()); expireAndRotate(throttler); - assertTrue(throttler.getActiveWindowDebt() == 0); + assertTrue("Should not have any debt remaining.", TestingBackDoor.getActiveWindowDebt(throttler) == 0); } /** @@ -268,13 +227,15 @@ public void testNormalization() { long sampleSize = 10 * 600; long periodMs = 60 * SECOND_IN_MS; JfrThrottler throttler = new JfrThrottler(mutex); - throttler.beginTest(sampleSize, periodMs); - assertTrue(throttler.getPeriodNs() + " " + throttler.getEventSampleSize(), throttler.getEventSampleSize() == sampleSize / 60 && throttler.getPeriodNs() == 1000000 * SECOND_IN_MS); + TestingBackDoor.beginTest(throttler, sampleSize, periodMs); + assertTrue(TestingBackDoor.getPeriodNs(throttler) + " " + TestingBackDoor.getEventSampleSize(throttler), + TestingBackDoor.getEventSampleSize(throttler) == sampleSize / 60 && TestingBackDoor.getPeriodNs(throttler) == 1000000 * SECOND_IN_MS); sampleSize = 10 * 3600; periodMs = 3600 * SECOND_IN_MS; throttler.setThrottle(sampleSize, periodMs); - assertTrue(throttler.getPeriodNs() + " " + throttler.getEventSampleSize(), throttler.getEventSampleSize() == sampleSize / 3600 && throttler.getPeriodNs() == 1000000 * SECOND_IN_MS); + assertTrue(TestingBackDoor.getPeriodNs(throttler) + " " + TestingBackDoor.getEventSampleSize(throttler), + TestingBackDoor.getEventSampleSize(throttler) == sampleSize / 3600 && TestingBackDoor.getPeriodNs(throttler) == 1000000 * SECOND_IN_MS); } /** @@ -290,11 +251,12 @@ public void testZeroRate() throws Throwable { assertTrue(throttler.sample()); // Test applying throttling settings to an in-progress recording - // Avoid using default configuration becuase it enables ObjectAllocationSample events by - // default - Recording recording = startRecording(new String[]{}, null, new HashMap<>()); + Recording recording = new Recording(); + recording.setDestination(createTempJfrFile()); recording.enable(JfrEvent.ObjectAllocationSample.getName()).with("throttle", "0/s"); - final int alignedHeapChunkSize = com.oracle.svm.core.util.UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); + recording.start(); + + final int alignedHeapChunkSize = UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); allocateCharArray(alignedHeapChunkSize); recording.stop(); @@ -360,7 +322,7 @@ public void testDistributionLateBurst() { // *** I think its [NOT] bursting ever /** * This is a more involved test that checks the sample distribution. It has been adapted from - * JfrGTestAdaptiveSampling in the OpenJDK + * JfrGTestAdaptiveSampling in the OpenJDK. */ private void testDistribution(IncomingPopulation incomingPopulation, int samplePointsPerWindow, double errorFactor) { final int distributionSlots = 100; @@ -370,7 +332,7 @@ private void testDistribution(IncomingPopulation incomingPopulation, int sampleP final int expectedSamples = expectedSamplesPerWindow * windowCount; JfrThrottler throttler = new JfrThrottler(mutex); - throttler.beginTest(expectedSamplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); + TestingBackDoor.beginTest(throttler, expectedSamplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); int[] population = new int[distributionSlots]; int[] sample = new int[distributionSlots]; @@ -435,8 +397,8 @@ interface IncomingPopulation { * Helper method that expires and rotates a throttler's active window. */ private static void expireAndRotate(JfrThrottler throttler) { - throttler.expireActiveWindow(); - assertTrue("should be expired", throttler.isActiveWindowExpired()); + TestingBackDoor.expireActiveWindow(throttler); + assertTrue("should be expired", TestingBackDoor.isActiveWindowExpired(throttler)); assertFalse("Should have rotated not sampled!", throttler.sample()); } } From 26b20bd44cbef1cf16c2e1e2603319142cf8725b Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 27 Jun 2023 14:54:15 -0400 Subject: [PATCH 14/34] gate fixes --- .../src/com/oracle/svm/core/jfr/JfrThrottler.java | 2 +- .../oracle/svm/core/jfr/JfrThrottlerSupport.java | 4 +++- .../oracle/svm/core/jfr/JfrThrottlerWindow.java | 15 ++++++++------- .../src/com/oracle/svm/core/jfr/SubstrateJVM.java | 1 - .../svm/core/jfr/events/JfrAllocationEvents.java | 3 +-- .../com/oracle/svm/test/jfr/TestThrottler.java | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index cb91ef4f5458..0d021a19261c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -218,7 +218,7 @@ public void configure() { // This effectively means we reset the debt count upon reconfiguration accumulatedDebtCarryCount = accumulatedDebtCarryLimit; avgPopulationSize = 0; - ewmaPopulationSizeAlpha = (double) 1 / windowLookback(next); + ewmaPopulationSizeAlpha = 1.0 / windowLookback(next); reconfigure = false; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java index 7ec083a0eee7..abf5597f42d8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java @@ -32,10 +32,12 @@ import com.oracle.svm.core.locks.VMMutex; /** - * Each event that supports throttling has its own throttler that can be accessed through this class. + * Each event that supports throttling has its own throttler that can be accessed through this + * class. */ public class JfrThrottlerSupport { JfrThrottler objectAllocationSampleThrottler; + @Platforms(Platform.HOSTED_ONLY.class) JfrThrottlerSupport() { if (HasJfrSupport.get()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index d2e23c298f45..1fd8b29e90a6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -83,13 +83,13 @@ public long samplesExpected() { return samplesPerWindow + debt; } - public void configure(long debt, double projectedPopSize) { - this.debt = debt; + public void configure(long newDebt, double projectedPopSize) { + this.debt = newDebt; if (projectedPopSize <= samplesExpected()) { samplingInterval = 1; } else { - double projectedProbability = (double) samplesExpected() / projectedPopSize; + double projectedProbability = samplesExpected() / projectedPopSize; samplingInterval = nextGeometric(projectedProbability, Math.random()); } @@ -110,12 +110,13 @@ public void configure(long debt, double projectedPopSize) { * This method is essentially the same as jfrAdaptiveSampler::next_geometric(double, double) in * the OpenJDK. */ - private long nextGeometric(double p, double u) { - if (u == 0.0) { - u = 0.01; + private static long nextGeometric(double probability, double u) { + double randomVar = u; + if (randomVar == 0.0) { + randomVar = 0.01; } // Inverse CDF for the geometric distribution. - return (long) Math.ceil(log(1.0 - u) / log(1.0 - p)); + return (long) Math.ceil(log(1.0 - randomVar) / log(1.0 - probability)); } public boolean isExpired() { 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 f987e90162c1..d68e6b4c3098 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 @@ -648,7 +648,6 @@ public boolean shouldCommit(JfrEvent event) { return jfrThrottlerSupport.shouldCommit(event.getId()); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { return jfrThrottlerSupport.setThrottle(eventTypeId, eventSampleSize, periodMs); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java index e988562e69a1..f27b74ee2394 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java @@ -56,8 +56,7 @@ public static void emit(long startTicks, Class clazz, UnsignedWord allocation /** * This method exists as a slight optimization to avoid entering the sampler code if - * unnecessary. We'll have to check {@link JfrEvent.shouldEmit()} again if a sample ends up - * being taken. + * unnecessary. This is required because the sampler code is interruptible. */ @Uninterruptible(reason = "Needed for JfrEvent.shouldEmit().") private static boolean shouldEmitObjectAllocationSample() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 5478e463f189..bf8d9581401d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -324,7 +324,7 @@ public void testDistributionLateBurst() { // *** I think its [NOT] bursting ever * This is a more involved test that checks the sample distribution. It has been adapted from * JfrGTestAdaptiveSampling in the OpenJDK. */ - private void testDistribution(IncomingPopulation incomingPopulation, int samplePointsPerWindow, double errorFactor) { + private static void testDistribution(IncomingPopulation incomingPopulation, int samplePointsPerWindow, double errorFactor) { final int distributionSlots = 100; final int windowDurationMs = 100; final int windowCount = 10000; From 2f3ff732fba46d87f1e30d01a1c88fec02585907 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 27 Jun 2023 17:05:15 -0400 Subject: [PATCH 15/34] throttle --- .../src/com/oracle/svm/core/jfr/JfrThrottler.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 0d021a19261c..4f7f52d901f9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -55,11 +55,11 @@ public class JfrThrottler { private volatile double avgPopulationSize = 0; private volatile boolean reconfigure; private volatile long accumulatedDebtCarryLimit; - private volatile long accumulatedDebtCarryCount; + private UninterruptibleUtils.AtomicLong accumulatedDebtCarryCount; public JfrThrottler(VMMutex mutex) { accumulatedDebtCarryLimit = 0; - accumulatedDebtCarryCount = 0; + accumulatedDebtCarryCount = new UninterruptibleUtils.AtomicLong(0); reconfigure = false; disabled = new UninterruptibleUtils.AtomicBoolean(true); window0 = new JfrThrottlerWindow(); @@ -175,11 +175,11 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { private long amortizeDebt(JfrThrottlerWindow lastWindow) { assert mutex.isOwner(); - if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { - accumulatedDebtCarryCount = 1; + if (accumulatedDebtCarryCount.get() == accumulatedDebtCarryLimit) { + accumulatedDebtCarryCount.set(1); return 0; // reset because new settings have been applied } - accumulatedDebtCarryCount++; + accumulatedDebtCarryCount.incrementAndGet(); // Did we sample less than we were supposed to? return lastWindow.samplesExpected() - lastWindow.samplesTaken(); } @@ -216,7 +216,7 @@ public void configure() { setSamplePointsAndWindowDuration(); accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(next.windowDurationNs); // This effectively means we reset the debt count upon reconfiguration - accumulatedDebtCarryCount = accumulatedDebtCarryLimit; + accumulatedDebtCarryCount.set(accumulatedDebtCarryLimit); avgPopulationSize = 0; ewmaPopulationSizeAlpha = 1.0 / windowLookback(next); reconfigure = false; From e9bb9ac37a0cfaebee8a0045dfa3842cd2796996 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 5 Jul 2023 14:45:06 -0400 Subject: [PATCH 16/34] clean up volatile vars --- .../com/oracle/svm/core/jfr/JfrThrottler.java | 36 +++++++++---------- .../svm/core/jfr/JfrThrottlerWindow.java | 6 ++-- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 4f7f52d901f9..dcd30a75f677 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -26,8 +26,6 @@ package com.oracle.svm.core.jfr; -import com.oracle.svm.core.jdk.UninterruptibleUtils; - import com.oracle.svm.core.locks.VMMutex; /** @@ -45,23 +43,21 @@ public class JfrThrottler { private final VMMutex mutex; - public UninterruptibleUtils.AtomicBoolean disabled; + public volatile boolean disabled; private JfrThrottlerWindow window0; private JfrThrottlerWindow window1; private volatile JfrThrottlerWindow activeWindow; - public volatile long eventSampleSize; - public volatile long periodNs; - private volatile double ewmaPopulationSizeAlpha = 0; - private volatile double avgPopulationSize = 0; - private volatile boolean reconfigure; - private volatile long accumulatedDebtCarryLimit; - private UninterruptibleUtils.AtomicLong accumulatedDebtCarryCount; + public long eventSampleSize; + public long periodNs; + private double ewmaPopulationSizeAlpha = 0; + private double avgPopulationSize = 0; + private boolean reconfigure; + private long accumulatedDebtCarryLimit; + private long accumulatedDebtCarryCount; public JfrThrottler(VMMutex mutex) { - accumulatedDebtCarryLimit = 0; - accumulatedDebtCarryCount = new UninterruptibleUtils.AtomicLong(0); reconfigure = false; - disabled = new UninterruptibleUtils.AtomicBoolean(true); + disabled = true; window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); activeWindow = window0; @@ -91,7 +87,7 @@ private void normalize(long samplesPerPeriod, double periodMs) { public boolean setThrottle(long eventSampleSize, long periodMs) { if (eventSampleSize == EVENT_THROTTLER_OFF) { - disabled.set(true); + disabled = true; return true; } @@ -104,7 +100,7 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { } finally { mutex.unlock(); } - disabled.set(false); + disabled = false; return true; } @@ -115,7 +111,7 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { * next window (which gets reset before becoming active again). */ public boolean sample() { - if (disabled.get()) { + if (disabled) { return true; } JfrThrottlerWindow window = activeWindow; @@ -175,11 +171,11 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { private long amortizeDebt(JfrThrottlerWindow lastWindow) { assert mutex.isOwner(); - if (accumulatedDebtCarryCount.get() == accumulatedDebtCarryLimit) { - accumulatedDebtCarryCount.set(1); + if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { + accumulatedDebtCarryCount = 1; return 0; // reset because new settings have been applied } - accumulatedDebtCarryCount.incrementAndGet(); + accumulatedDebtCarryCount++; // Did we sample less than we were supposed to? return lastWindow.samplesExpected() - lastWindow.samplesTaken(); } @@ -216,7 +212,7 @@ public void configure() { setSamplePointsAndWindowDuration(); accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(next.windowDurationNs); // This effectively means we reset the debt count upon reconfiguration - accumulatedDebtCarryCount.set(accumulatedDebtCarryLimit); + accumulatedDebtCarryCount = accumulatedDebtCarryLimit; avgPopulationSize = 0; ewmaPopulationSizeAlpha = 1.0 / windowLookback(next); reconfigure = false; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index 1fd8b29e90a6..efacb11a50fa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -39,9 +39,9 @@ public class JfrThrottlerWindow { public volatile double maxSampleablePopulation; // Set by user - public volatile long samplesPerWindow; - public volatile long windowDurationNs; - public volatile long debt; + public long samplesPerWindow; + public long windowDurationNs; + public long debt; public JfrThrottlerWindow() { windowDurationNs = 0; From 9a20073eaa57044882fa5786a2ac9b9957ab9686 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 7 Jul 2023 12:14:04 -0400 Subject: [PATCH 17/34] update changelog --- substratevm/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 2e21fb8ad38d..4776387400f4 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -6,6 +6,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases. * (GR-45841) BellSoft added support for the JFR event ThreadCPULoad. * (GR-45994) Removed the option `-H:EnableSignalAPI`. Please use the runtime option `EnableSignalHandling` if it is necessary to enable or disable signal handling explicitly. +* (GR-47109) JFR event throttling support was added, along with the jdk.ObjectAllocationSample event. ## Version 23.0.0 * (GR-40187) Report invalid use of SVM specific classes on image class- or module-path as error. As a temporary workaround, `-H:+AllowDeprecatedBuilderClassesOnImageClasspath` allows turning the error into a warning. From 1990018c1e7a452a15ff36113c7b0bfb8806e535 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 10 Jul 2023 15:50:16 -0400 Subject: [PATCH 18/34] clean up missed comments --- .../src/com/oracle/svm/test/jfr/TestThrottler.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index bf8d9581401d..f6e2e473a3f3 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -147,8 +147,8 @@ public void testExpiry() { /** * This test checks the projected population after a window rotation. This is a test of the EWMA - * calculation. Window lookback is 25 and windowDuration is un-normalized because period is not - * greater than 1s. + * calculation. Window lookback is 25 and windowDuration is un-normalized because the period is + * not greater than 1s. */ @Test public void testEWMA() { @@ -292,8 +292,7 @@ public void testDistributionLowRate() { } @Test - public void testDistributionEarlyBurst() { // maybe this is 41000 instead of 50000 because - // projected population size is low + public void testDistributionEarlyBurst() { final int maxPopPerWindow = 2000; final int expectedSamplesPerWindow = 50; final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs @@ -311,8 +310,7 @@ public void testDistributionMidBurst() { } @Test - public void testDistributionLateBurst() { // *** I think its [NOT] bursting every window by - // accident. windowCount=10000 not 1000 + public void testDistributionLateBurst() { final int maxPopPerWindow = 2000; final int expectedSamplesPerWindow = 50; final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs @@ -385,7 +383,7 @@ private static void assertDistributionProperties(int distributionSlots, int[] po sampleVariance = sampleVariance / (sampleSize - 1); double populationStdev = Math.sqrt(populationVariance); double sampleStdev = Math.sqrt(sampleVariance); - expectNear(populationStdev, sampleStdev, 0.5); // 0.5 value copied from Hotspot test + expectNear(populationStdev, sampleStdev, 0.5); // 0.5 value to match Hotspot test expectNear(populationMean, sampleMean, populationStdev); } From f58563162090eb682a67066ae5e7235e4818d3ad Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 20 Sep 2023 11:27:15 -0400 Subject: [PATCH 19/34] minor review feedback resolution --- .../genscavenge/ThreadLocalAllocation.java | 10 +++--- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 7 +++- .../com/oracle/svm/core/jfr/JfrThrottler.java | 31 +++++++--------- .../svm/core/jfr/JfrThrottlerSupport.java | 8 ++--- .../svm/core/jfr/JfrThrottlerWindow.java | 7 ++-- ...jfr_internal_settings_ThrottleSetting.java | 35 +++++++++++++++++++ .../core/jfr/events/JfrAllocationEvents.java | 22 ++++-------- 7 files changed, 71 insertions(+), 49 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index 4ca137ddf954..d5bb382ea269 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -230,14 +230,15 @@ private static Object slowPathNewInstance(Word objectHeader) { private static Object slowPathNewInstanceWithoutAllocating(DynamicHub hub) { DeoptTester.disableDeoptTesting(); long startTicks = JfrTicks.elapsedTicks(); + UnsignedWord size = LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()); try { HeapImpl.exitIfAllocationDisallowed("ThreadLocalAllocation.slowPathNewInstanceWithoutAllocating", DynamicHub.toClass(hub).getName()); GCImpl.getGCImpl().maybeCollectOnAllocation(); AlignedHeader newTlab = HeapImpl.getChunkProvider().produceAlignedChunk(); - return allocateInstanceInNewTlab(hub, newTlab); + return allocateInstanceInNewTlab(hub, newTlab, size); } finally { - JfrAllocationEvents.emit(startTicks, DynamicHub.toClass(hub), LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()), HeapParameters.getAlignedHeapChunkSize()); + JfrAllocationEvents.emit(startTicks, hub, size, HeapParameters.getAlignedHeapChunkSize()); DeoptTester.enableDeoptTesting(); } } @@ -316,14 +317,13 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un } return array; } finally { - JfrAllocationEvents.emit(startTicks, DynamicHub.toClass(hub), size, tlabSize); + JfrAllocationEvents.emit(startTicks, hub, size, tlabSize); DeoptTester.enableDeoptTesting(); } } @Uninterruptible(reason = "Holds uninitialized memory.") - private static Object allocateInstanceInNewTlab(DynamicHub hub, AlignedHeader newTlabChunk) { - UnsignedWord size = LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding()); + private static Object allocateInstanceInNewTlab(DynamicHub hub, AlignedHeader newTlabChunk, UnsignedWord size) { Pointer memory = allocateRawMemoryInNewTlab(size, newTlabChunk); return FormatObjectNode.formatObject(memory, DynamicHub.toClass(hub), false, FillContent.WITH_ZEROES, true); } 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 a1938a8a9371..fb85c4950fe9 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 @@ -111,6 +111,11 @@ public boolean shouldEmit(long durationTicks) { @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) private boolean shouldEmit0() { - return SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this); + return shouldCommit() && SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this); + } + + @Uninterruptible(reason = "This is executed before the recording state is checked", mayBeInlined = true, calleeMustBe = false) + private boolean shouldCommit() { + return SubstrateJVM.get().shouldCommit(this); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index dcd30a75f677..4fdcc9db69ad 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -27,25 +27,20 @@ package com.oracle.svm.core.jfr; import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.util.TimeUtils; /** * Each event that allows throttling should have its own throttler instance. */ public class JfrThrottler { - private static final long SECOND_IN_MS = 1000; - private static final long SECOND_IN_NS = 1000000 * SECOND_IN_MS; - private static final long MINUTE_IN_NS = SECOND_IN_NS * 60; - // The following are set to match the values in OpenJDK private static final int WINDOW_DIVISOR = 5; private static final int LOW_RATE_UPPER_BOUND = 9; - private static final int EVENT_THROTTLER_OFF = -2; - + private final JfrThrottlerWindow window0; + private final JfrThrottlerWindow window1; private final VMMutex mutex; public volatile boolean disabled; - private JfrThrottlerWindow window0; - private JfrThrottlerWindow window1; private volatile JfrThrottlerWindow activeWindow; public long eventSampleSize; public long periodNs; @@ -75,18 +70,18 @@ private void normalize(long samplesPerPeriod, double periodMs) { // Do we want more than 10samples/s ? If so convert to samples/s double periodsPerSecond = 1000.0 / periodMs; double samplesPerSecond = samplesPerPeriod * periodsPerSecond; - if (samplesPerSecond > LOW_RATE_UPPER_BOUND && periodMs > SECOND_IN_MS) { - this.periodNs = SECOND_IN_NS; + if (samplesPerSecond > LOW_RATE_UPPER_BOUND && periodMs > TimeUtils.millisPerSecond) { + this.periodNs = TimeUtils.nanosPerSecond; this.eventSampleSize = (long) samplesPerSecond; return; } this.eventSampleSize = samplesPerPeriod; - this.periodNs = (long) periodMs * 1000000; + this.periodNs = TimeUtils.millisToNanos((long) periodMs); } public boolean setThrottle(long eventSampleSize, long periodMs) { - if (eventSampleSize == EVENT_THROTTLER_OFF) { + if (eventSampleSize == Target_jdk_jfr_internal_settings_ThrottleSetting.OFF) { disabled = true; return true; } @@ -163,10 +158,10 @@ JfrThrottlerWindow getNextWindow() { private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { assert mutex.isOwner(); - if (periodNs == 0 || windowDurationNs >= SECOND_IN_NS) { + if (periodNs == 0 || windowDurationNs >= TimeUtils.nanosPerSecond) { return 1; } - return SECOND_IN_NS / windowDurationNs; + return TimeUtils.nanosPerSecond / windowDurationNs; } private long amortizeDebt(JfrThrottlerWindow lastWindow) { @@ -193,7 +188,7 @@ private void setSamplePointsAndWindowDuration() { * If period isn't 1s, then we're effectively taking under 10 samples/s because the values * have already undergone normalization. */ - if (eventSampleSize <= LOW_RATE_UPPER_BOUND || periodNs > SECOND_IN_NS) { + if (eventSampleSize <= LOW_RATE_UPPER_BOUND || periodNs > TimeUtils.nanosPerSecond) { samplesPerWindow = eventSampleSize; windowDurationNs = periodNs; } @@ -225,9 +220,9 @@ public void configure() { * The lookback values are set to match the values in OpenJDK. */ private static double windowLookback(JfrThrottlerWindow window) { - if (window.windowDurationNs <= SECOND_IN_NS) { + if (window.windowDurationNs <= TimeUtils.nanosPerSecond) { return 25.0; - } else if (window.windowDurationNs <= MINUTE_IN_NS) { + } else if (window.windowDurationNs <= TimeUtils.nanosPerSecond * 60L) { return 5.0; } else { return 1.0; @@ -279,7 +274,7 @@ public static long getEventSampleSize(JfrThrottler throttler) { } public static void expireActiveWindow(JfrThrottler throttler) { - if (throttler.eventSampleSize <= LOW_RATE_UPPER_BOUND || throttler.periodNs > SECOND_IN_NS) { + if (throttler.eventSampleSize <= LOW_RATE_UPPER_BOUND || throttler.periodNs > TimeUtils.nanosPerSecond) { throttler.window0.currentTestNanos += throttler.periodNs; throttler.window1.currentTestNanos += throttler.periodNs; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java index abf5597f42d8..d835295d0dde 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java @@ -26,6 +26,7 @@ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.Uninterruptible; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -36,15 +37,14 @@ * class. */ public class JfrThrottlerSupport { - JfrThrottler objectAllocationSampleThrottler; + private JfrThrottler objectAllocationSampleThrottler; @Platforms(Platform.HOSTED_ONLY.class) JfrThrottlerSupport() { - if (HasJfrSupport.get()) { - objectAllocationSampleThrottler = new JfrThrottler(new VMMutex("jfrThrottler")); - } + objectAllocationSampleThrottler = new JfrThrottler(new VMMutex("jfrThrottler")); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private JfrThrottler getThrottler(long eventId) { if (eventId == JfrEvent.ObjectAllocationSample.getId()) { return objectAllocationSampleThrottler; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index efacb11a50fa..84aadc6bc639 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -65,11 +65,8 @@ public boolean sample() { // Stop sampling if we're already over maxSampleablePopulation and we're over the expected // samples per window. - if (prevMeasuredPopSize % samplingInterval == 0 && - (prevMeasuredPopSize < maxSampleablePopulation)) { - return true; - } - return false; + return prevMeasuredPopSize % samplingInterval == 0 && + (prevMeasuredPopSize < maxSampleablePopulation); } public long samplesTaken() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java new file mode 100644 index 000000000000..478fc1c4629c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, 2021, 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; + +import com.oracle.svm.core.annotate.Alias; + +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "jdk.jfr.internal.settings.ThrottleSetting", onlyWith = HasJfrSupport.class) +final class Target_jdk_jfr_internal_settings_ThrottleSetting { + @Alias + static long OFF; +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java index f27b74ee2394..235742e8ad21 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java @@ -30,6 +30,7 @@ import org.graalvm.word.UnsignedWord; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jfr.HasJfrSupport; import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.jfr.JfrNativeEventWriter; @@ -44,25 +45,14 @@ public class JfrAllocationEvents { private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); - public static void emit(long startTicks, Class clazz, UnsignedWord allocationSize, UnsignedWord tlabSize) { + public static void emit(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { if (HasJfrSupport.get()) { + Class clazz = DynamicHub.toClass(hub); emitObjectAllocationInNewTLAB(startTicks, clazz, allocationSize, tlabSize); - - if (shouldEmitObjectAllocationSample() && SubstrateJVM.get().shouldCommit(JfrEvent.ObjectAllocationSample)) { - emitObjectAllocationSample(startTicks, clazz); - } + emitObjectAllocationSample(startTicks, clazz); } } - /** - * This method exists as a slight optimization to avoid entering the sampler code if - * unnecessary. This is required because the sampler code is interruptible. - */ - @Uninterruptible(reason = "Needed for JfrEvent.shouldEmit().") - private static boolean shouldEmitObjectAllocationSample() { - return JfrEvent.ObjectAllocationSample.shouldEmit(); - } - @Uninterruptible(reason = "Accesses a JFR buffer.") private static void emitObjectAllocationInNewTLAB(long startTicks, Class clazz, UnsignedWord allocationSize, UnsignedWord tlabSize) { if (JfrEvent.ObjectAllocationInNewTLAB.shouldEmit()) { @@ -83,7 +73,7 @@ private static void emitObjectAllocationInNewTLAB(long startTicks, Class claz private static void emitObjectAllocationSample(long startTicks, Class clazz) { if (JfrEvent.ObjectAllocationSample.shouldEmit()) { long currentAllocationSize = PlatformThreads.getThreadAllocatedBytes(JavaThreads.getCurrentThreadId()); - + long weight = currentAllocationSize - lastAllocationSize.get(); JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationSample); @@ -91,7 +81,7 @@ private static void emitObjectAllocationSample(long startTicks, Class clazz) JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); JfrNativeEventWriter.putClass(data, clazz); - JfrNativeEventWriter.putLong(data, currentAllocationSize - lastAllocationSize.get()); + JfrNativeEventWriter.putLong(data, weight); JfrNativeEventWriter.endSmallEvent(data); lastAllocationSize.set(currentAllocationSize); } From 1823f09714ff4837828fd021f5d84e85743bacfc Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 21 Sep 2023 13:21:40 -0400 Subject: [PATCH 20/34] remove JfrThrottlerSupport. Add field to JfrEvent. Order JfrThrottler fields by lock access. Restrict access in JfrThrottler. --- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 86 +++++++++++-------- .../com/oracle/svm/core/jfr/JfrThrottler.java | 22 +++-- .../svm/core/jfr/JfrThrottlerSupport.java | 73 ---------------- .../svm/core/jfr/JfrThrottlerWindow.java | 5 ++ .../com/oracle/svm/core/jfr/SubstrateJVM.java | 29 ++++++- 5 files changed, 95 insertions(+), 120 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java 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 fb85c4950fe9..351fa3b99a89 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 @@ -28,57 +28,75 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.locks.VMMutex; + +import java.util.ArrayList; +import java.util.List; /** * 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", false); - public static final JfrEvent ThreadEnd = create("jdk.ThreadEnd", false); - public static final JfrEvent ThreadCPULoad = create("jdk.ThreadCPULoad", false); - public static final JfrEvent DataLoss = create("jdk.DataLoss", false); - public static final JfrEvent ClassLoadingStatistics = create("jdk.ClassLoadingStatistics", false); - public static final JfrEvent InitialEnvironmentVariable = create("jdk.InitialEnvironmentVariable", false); - public static final JfrEvent InitialSystemProperty = create("jdk.InitialSystemProperty", false); - public static final JfrEvent JavaThreadStatistics = create("jdk.JavaThreadStatistics", false); - public static final JfrEvent JVMInformation = create("jdk.JVMInformation", false); - public static final JfrEvent OSInformation = create("jdk.OSInformation", false); - public static final JfrEvent PhysicalMemory = create("jdk.PhysicalMemory", false); - public static final JfrEvent ExecutionSample = create("jdk.ExecutionSample", false); - public static final JfrEvent NativeMethodSample = create("jdk.NativeMethodSample", false); - public static final JfrEvent GarbageCollection = create("jdk.GarbageCollection", true); - public static final JfrEvent GCPhasePause = create("jdk.GCPhasePause", true); - public static final JfrEvent GCPhasePauseLevel1 = create("jdk.GCPhasePauseLevel1", true); - public static final JfrEvent GCPhasePauseLevel2 = create("jdk.GCPhasePauseLevel2", true); - public static final JfrEvent GCPhasePauseLevel3 = create("jdk.GCPhasePauseLevel3", true); - public static final JfrEvent GCPhasePauseLevel4 = create("jdk.GCPhasePauseLevel4", true); - public static final JfrEvent SafepointBegin = create("jdk.SafepointBegin", true); - public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd", true); - public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation", true); - public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter", true); - public static final JfrEvent ThreadPark = create("jdk.ThreadPark", true); - public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait", true); - public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate", true); - public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB", false); - public static final JfrEvent GCHeapSummary = create("jdk.GCHeapSummary", false); - public static final JfrEvent ThreadAllocationStatistics = create("jdk.ThreadAllocationStatistics", false); - public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample", false); - + private static final List events = new ArrayList<>(); + public static final JfrEvent ThreadStart = create("jdk.ThreadStart", false, false); + public static final JfrEvent ThreadEnd = create("jdk.ThreadEnd", false, false); + public static final JfrEvent ThreadCPULoad = create("jdk.ThreadCPULoad", false, false); + public static final JfrEvent DataLoss = create("jdk.DataLoss", false, false); + public static final JfrEvent ClassLoadingStatistics = create("jdk.ClassLoadingStatistics", false, false); + public static final JfrEvent InitialEnvironmentVariable = create("jdk.InitialEnvironmentVariable", false, false); + public static final JfrEvent InitialSystemProperty = create("jdk.InitialSystemProperty", false, false); + public static final JfrEvent JavaThreadStatistics = create("jdk.JavaThreadStatistics", false, false); + public static final JfrEvent JVMInformation = create("jdk.JVMInformation", false, false); + public static final JfrEvent OSInformation = create("jdk.OSInformation", false, false); + public static final JfrEvent PhysicalMemory = create("jdk.PhysicalMemory", false, false); + public static final JfrEvent ExecutionSample = create("jdk.ExecutionSample", false, false); + public static final JfrEvent NativeMethodSample = create("jdk.NativeMethodSample", false, false); + public static final JfrEvent GarbageCollection = create("jdk.GarbageCollection", true, false); + public static final JfrEvent GCPhasePause = create("jdk.GCPhasePause", true, false); + public static final JfrEvent GCPhasePauseLevel1 = create("jdk.GCPhasePauseLevel1", true, false); + public static final JfrEvent GCPhasePauseLevel2 = create("jdk.GCPhasePauseLevel2", true, false); + public static final JfrEvent GCPhasePauseLevel3 = create("jdk.GCPhasePauseLevel3", true, false); + public static final JfrEvent GCPhasePauseLevel4 = create("jdk.GCPhasePauseLevel4", true, false); + public static final JfrEvent SafepointBegin = create("jdk.SafepointBegin", true, false); + public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd", true, false); + public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation", true, false); + public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter", true, false); + public static final JfrEvent ThreadPark = create("jdk.ThreadPark", true, false); + public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait", true, false); + public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate", true, false); + public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB", false, false); + public static final JfrEvent GCHeapSummary = create("jdk.GCHeapSummary", false, false); + public static final JfrEvent ThreadAllocationStatistics = create("jdk.ThreadAllocationStatistics", false, false); + public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample", false, true); private final long id; private final String name; private final boolean hasDuration; + private JfrThrottler throttler; @Platforms(Platform.HOSTED_ONLY.class) - public static JfrEvent create(String name, boolean hasDuration) { - return new JfrEvent(name, hasDuration); + public static JfrEvent create(String name, boolean hasDuration, boolean hasThrottling) { + return new JfrEvent(name, hasDuration, hasThrottling); } @Platforms(Platform.HOSTED_ONLY.class) - private JfrEvent(String name, boolean hasDuration) { + private JfrEvent(String name, boolean hasDuration, boolean hasThrottling) { this.id = JfrMetadataTypeLibrary.lookupPlatformEvent(name); this.name = name; this.hasDuration = hasDuration; + if (hasThrottling) { + throttler = new JfrThrottler(new VMMutex("jfrThrottler_" + name)); + } + events.add(this); + } + + public static List getEvents() { + return events; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public JfrThrottler getThrottler() { + return throttler; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 4fdcc9db69ad..043bcaf87196 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -40,16 +40,19 @@ public class JfrThrottler { private final JfrThrottlerWindow window1; private final VMMutex mutex; - public volatile boolean disabled; - private volatile JfrThrottlerWindow activeWindow; - public long eventSampleSize; - public long periodNs; + // The following fields are only be accessed by threads holding the lock + private long periodNs; + private long eventSampleSize; private double ewmaPopulationSizeAlpha = 0; private double avgPopulationSize = 0; private boolean reconfigure; private long accumulatedDebtCarryLimit; private long accumulatedDebtCarryCount; + // The following fields may be accessed by multiple threads without acquiring the lock + private volatile JfrThrottlerWindow activeWindow; + private volatile boolean disabled; + public JfrThrottler(VMMutex mutex) { reconfigure = false; disabled = true; @@ -60,8 +63,8 @@ public JfrThrottler(VMMutex mutex) { } /** - * Convert rate to samples/second if possible. Want to avoid long period with large windows with - * a large number of samples per window in favor of many smaller windows. This is in the + * Convert rate to samples/second if possible. Want to avoid a long period with large windows + * with a large number of samples per window in favor of many smaller windows. This is in the * critical section because setting the sample size and period must be done together atomically. * Otherwise, we risk a window's params being set with only one of the two updated. */ @@ -101,7 +104,7 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { /** * The real active window may change while we're doing the sampling. That's fine as long as we - * perform operations with respect a consistent window during this method. It's fine if the + * perform operations with respect to a consistent window during this method. It's fine if the * active window changes after we've read it from memory, because now we'll be writing to the * next window (which gets reset before becoming active again). */ @@ -148,7 +151,7 @@ private void installNextWindow() { activeWindow = getNextWindow(); } - JfrThrottlerWindow getNextWindow() { + private JfrThrottlerWindow getNextWindow() { assert mutex.isOwner(); if (window0 == activeWindow) { return window1; @@ -198,7 +201,7 @@ private void setSamplePointsAndWindowDuration() { next.windowDurationNs = windowDurationNs; } - public void configure() { + private void configure() { assert mutex.isOwner(); JfrThrottlerWindow next = getNextWindow(); @@ -230,6 +233,7 @@ private static double windowLookback(JfrThrottlerWindow window) { } private double projectPopulationSize(long lastWindowMeasuredPop) { + assert mutex.isOwner(); avgPopulationSize = exponentiallyWeightedMovingAverage(lastWindowMeasuredPop, ewmaPopulationSizeAlpha, avgPopulationSize); return avgPopulationSize; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java deleted file mode 100644 index d835295d0dde..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerSupport.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.core.jfr; - -import com.oracle.svm.core.Uninterruptible; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - -import com.oracle.svm.core.locks.VMMutex; - -/** - * Each event that supports throttling has its own throttler that can be accessed through this - * class. - */ -public class JfrThrottlerSupport { - private JfrThrottler objectAllocationSampleThrottler; - - @Platforms(Platform.HOSTED_ONLY.class) - JfrThrottlerSupport() { - objectAllocationSampleThrottler = new JfrThrottler(new VMMutex("jfrThrottler")); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private JfrThrottler getThrottler(long eventId) { - if (eventId == JfrEvent.ObjectAllocationSample.getId()) { - return objectAllocationSampleThrottler; - } - return null; - } - - public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { - JfrThrottler throttler = getThrottler(eventTypeId); - if (throttler == null) { - // This event doesn't support throttling - return false; - } - throttler.setThrottle(eventSampleSize, periodMs); - return true; - } - - public boolean shouldCommit(long eventTypeId) { - JfrThrottler throttler = getThrottler(eventTypeId); - if (throttler == null) { - // This event doesn't support throttling - return true; - } - return throttler.sample(); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index 84aadc6bc639..ce01a7dba447 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -58,6 +58,8 @@ public JfrThrottlerWindow() { * will be updated as usual, although it is now the "next" window. This results in some wasted * effort, but doesn't affect correctness because this window will be reset before it becomes * active again. + * + * Threads calling this method may not have acquired the JfrThrottler lock. */ public boolean sample() { // Guarantees only one thread can record the last event of the window @@ -69,6 +71,7 @@ public boolean sample() { (prevMeasuredPopSize < maxSampleablePopulation); } + /** Thread's calling this method should have acquired the JftThrottler lock. */ public long samplesTaken() { if (measuredPopSize.get() > maxSampleablePopulation) { return samplesExpected(); @@ -76,10 +79,12 @@ public long samplesTaken() { return measuredPopSize.get() / samplingInterval; } + /** Thread's calling this method should have acquired the JftThrottler lock. */ public long samplesExpected() { return samplesPerWindow + debt; } + /** Thread's calling this method should have acquired the JftThrottler lock. */ public void configure(long newDebt, double projectedPopSize) { this.debt = newDebt; if (projectedPopSize <= samplesExpected()) { 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 a6b21d49ef0d..ccfc7c9aaef1 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 @@ -80,7 +80,7 @@ public class SubstrateJVM { private final JfrUnlockedChunkWriter unlockedChunkWriter; private final JfrRecorderThread recorderThread; - private final JfrThrottlerSupport jfrThrottlerSupport; +// private final JfrThrottlerSupport jfrThrottlerSupport; private final JfrLogging jfrLogging; @@ -122,7 +122,7 @@ public SubstrateJVM(List configurations) { initialized = false; recording = false; - jfrThrottlerSupport = new JfrThrottlerSupport(); +// jfrThrottlerSupport = new JfrThrottlerSupport(); } @Fold @@ -661,12 +661,33 @@ public boolean isEnabled(JfrEvent event) { return eventSettings[(int) event.getId()].isEnabled(); } +// public boolean shouldCommit(JfrEvent event) { +// return jfrThrottlerSupport.shouldCommit(event.getId()); +// } + public boolean shouldCommit(JfrEvent event) { - return jfrThrottlerSupport.shouldCommit(event.getId()); + JfrThrottler throttler = event.getThrottler(); + if (throttler != null) { + return throttler.sample(); + } + return true; } +// public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { +// return jfrThrottlerSupport.setThrottle(eventTypeId, eventSampleSize, periodMs); +// } + public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { - return jfrThrottlerSupport.setThrottle(eventTypeId, eventSampleSize, periodMs); + for (JfrEvent event : JfrEvent.getEvents()) { + if (eventTypeId == event.getId()) { + JfrThrottler throttler = event.getThrottler(); + if (throttler != null) { + return throttler.setThrottle(eventSampleSize, periodMs); + } + return false; + } + } + return false; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) From 750cf4bb9b1af162601e945700c2b7c91b983757 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 25 Sep 2023 11:09:05 -0400 Subject: [PATCH 21/34] make uninterruptible. JfrRandom --- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 7 +-- .../com/oracle/svm/core/jfr/JfrManager.java | 1 + .../com/oracle/svm/core/jfr/JfrThrottler.java | 44 +++++++++----- .../svm/core/jfr/JfrThrottlerWindow.java | 13 +++- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 26 ++++---- .../oracle/svm/core/jfr/utils/JfrRandom.java | 60 +++++++++++++++++++ 6 files changed, 114 insertions(+), 37 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java 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 351fa3b99a89..0ae74256cc0a 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 @@ -129,11 +129,6 @@ public boolean shouldEmit(long durationTicks) { @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) private boolean shouldEmit0() { - return shouldCommit() && SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this); - } - - @Uninterruptible(reason = "This is executed before the recording state is checked", mayBeInlined = true, calleeMustBe = false) - private boolean shouldCommit() { - return SubstrateJVM.get().shouldCommit(this); + return SubstrateJVM.get().shouldCommit(this) && SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java index 7d8e4bf1e556..073bffd2e5e4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java @@ -86,6 +86,7 @@ public RuntimeSupport.Hook startupHook() { return isFirstIsolate -> { parseFlightRecorderLogging(SubstrateOptions.FlightRecorderLogging.getValue()); periodicEventSetup(); + com.oracle.svm.core.jfr.SubstrateJVM.getJfrRandom().resetSeed(); if (isJFREnabled()) { initRecording(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 043bcaf87196..ff690da772ee 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -26,6 +26,7 @@ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.util.TimeUtils; @@ -38,7 +39,7 @@ public class JfrThrottler { private static final int LOW_RATE_UPPER_BOUND = 9; private final JfrThrottlerWindow window0; private final JfrThrottlerWindow window1; - private final VMMutex mutex; + private final VMMutex rotationLock; // The following fields are only be accessed by threads holding the lock private long periodNs; @@ -59,7 +60,7 @@ public JfrThrottler(VMMutex mutex) { window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); activeWindow = window0; - this.mutex = mutex; + this.rotationLock = mutex; } /** @@ -69,7 +70,7 @@ public JfrThrottler(VMMutex mutex) { * Otherwise, we risk a window's params being set with only one of the two updated. */ private void normalize(long samplesPerPeriod, double periodMs) { - assert mutex.isOwner(); + assert rotationLock.isOwner(); // Do we want more than 10samples/s ? If so convert to samples/s double periodsPerSecond = 1000.0 / periodMs; double samplesPerSecond = samplesPerPeriod * periodsPerSecond; @@ -80,7 +81,7 @@ private void normalize(long samplesPerPeriod, double periodMs) { } this.eventSampleSize = samplesPerPeriod; - this.periodNs = TimeUtils.millisToNanos((long) periodMs); + this.periodNs = (long) periodMs * 1000000; } public boolean setThrottle(long eventSampleSize, long periodMs) { @@ -90,13 +91,13 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { } // Blocking lock because new settings MUST be applied. - mutex.lock(); + rotationLock.lock(); try { normalize(eventSampleSize, periodMs); reconfigure = true; rotateWindow(); } finally { - mutex.unlock(); + rotationLock.unlock(); } disabled = false; return true; @@ -108,6 +109,7 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { * active window changes after we've read it from memory, because now we'll be writing to the * next window (which gets reset before becoming active again). */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean sample() { if (disabled) { return true; @@ -116,7 +118,7 @@ public boolean sample() { boolean expired = window.isExpired(); if (expired) { // Check lock in case thread is already rotating. - mutex.lock(); + rotationLock.lockNoTransition(); try { /* * Once in critical section, ensure active window is still expired. Another thread @@ -127,7 +129,7 @@ public boolean sample() { rotateWindow(); } } finally { - mutex.unlock(); + rotationLock.unlock(); } return false; } @@ -140,35 +142,40 @@ public boolean sample() { * thread updating settings, then one will just have to wait for the other to finish. Order * doesn't really matter as long as they are not interrupted. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void rotateWindow() { - assert mutex.isOwner(); + assert rotationLock.isOwner(); configure(); installNextWindow(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void installNextWindow() { - assert mutex.isOwner(); + assert rotationLock.isOwner(); activeWindow = getNextWindow(); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private JfrThrottlerWindow getNextWindow() { - assert mutex.isOwner(); + assert rotationLock.isOwner(); if (window0 == activeWindow) { return window1; } return window0; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { - assert mutex.isOwner(); + assert rotationLock.isOwner(); if (periodNs == 0 || windowDurationNs >= TimeUtils.nanosPerSecond) { return 1; } return TimeUtils.nanosPerSecond / windowDurationNs; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private long amortizeDebt(JfrThrottlerWindow lastWindow) { - assert mutex.isOwner(); + assert rotationLock.isOwner(); if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { accumulatedDebtCarryCount = 1; return 0; // reset because new settings have been applied @@ -181,8 +188,9 @@ private long amortizeDebt(JfrThrottlerWindow lastWindow) { /** * Handles the case where the sampling rate is very low. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void setSamplePointsAndWindowDuration() { - assert mutex.isOwner(); + assert rotationLock.isOwner(); assert reconfigure; JfrThrottlerWindow next = getNextWindow(); long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; @@ -201,8 +209,9 @@ private void setSamplePointsAndWindowDuration() { next.windowDurationNs = windowDurationNs; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void configure() { - assert mutex.isOwner(); + assert rotationLock.isOwner(); JfrThrottlerWindow next = getNextWindow(); // Store updated parameters to both windows. @@ -222,6 +231,7 @@ private void configure() { /** * The lookback values are set to match the values in OpenJDK. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static double windowLookback(JfrThrottlerWindow window) { if (window.windowDurationNs <= TimeUtils.nanosPerSecond) { return 25.0; @@ -232,12 +242,14 @@ private static double windowLookback(JfrThrottlerWindow window) { } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private double projectPopulationSize(long lastWindowMeasuredPop) { - assert mutex.isOwner(); + assert rotationLock.isOwner(); avgPopulationSize = exponentiallyWeightedMovingAverage(lastWindowMeasuredPop, ewmaPopulationSizeAlpha, avgPopulationSize); return avgPopulationSize; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static double exponentiallyWeightedMovingAverage(double currentMeasurement, double alpha, double prevEwma) { return alpha * currentMeasurement + (1 - alpha) * prevEwma; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index ce01a7dba447..acd39f4d4f39 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -25,6 +25,7 @@ */ package com.oracle.svm.core.jfr; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils; import static java.lang.Math.log; @@ -61,6 +62,7 @@ public JfrThrottlerWindow() { * * Threads calling this method may not have acquired the JfrThrottler lock. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean sample() { // Guarantees only one thread can record the last event of the window long prevMeasuredPopSize = measuredPopSize.getAndIncrement(); @@ -72,6 +74,7 @@ public boolean sample() { } /** Thread's calling this method should have acquired the JftThrottler lock. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long samplesTaken() { if (measuredPopSize.get() > maxSampleablePopulation) { return samplesExpected(); @@ -80,11 +83,13 @@ public long samplesTaken() { } /** Thread's calling this method should have acquired the JftThrottler lock. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long samplesExpected() { return samplesPerWindow + debt; } /** Thread's calling this method should have acquired the JftThrottler lock. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void configure(long newDebt, double projectedPopSize) { this.debt = newDebt; if (projectedPopSize <= samplesExpected()) { @@ -92,7 +97,7 @@ public void configure(long newDebt, double projectedPopSize) { } else { double projectedProbability = samplesExpected() / projectedPopSize; - samplingInterval = nextGeometric(projectedProbability, Math.random()); + samplingInterval = nextGeometric(projectedProbability, SubstrateJVM.getNextRandomUniform()); } this.maxSampleablePopulation = samplesExpected() * samplingInterval; @@ -104,7 +109,7 @@ public void configure(long newDebt, double projectedPopSize) { // There is a need to mock JfrTicks for testing. endTicks.set(currentTestNanos + windowDurationNs); } else { - endTicks.set(JfrTicks.currentTimeNanos() + windowDurationNs); + endTicks.set(System.nanoTime() + windowDurationNs); } } @@ -112,6 +117,7 @@ public void configure(long newDebt, double projectedPopSize) { * This method is essentially the same as jfrAdaptiveSampler::next_geometric(double, double) in * the OpenJDK. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static long nextGeometric(double probability, double u) { double randomVar = u; if (randomVar == 0.0) { @@ -121,13 +127,14 @@ private static long nextGeometric(double probability, double u) { return (long) Math.ceil(log(1.0 - randomVar) / log(1.0 - probability)); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isExpired() { if (isTest) { // There is a need to mock JfrTicks for testing. if (currentTestNanos >= endTicks.get()) { return true; } - } else if (JfrTicks.currentTimeNanos() >= endTicks.get()) { + } else if (System.nanoTime() >= endTicks.get()) { return true; } return false; 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 ccfc7c9aaef1..dd1ff70e277a 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 @@ -41,6 +41,7 @@ import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jfr.logging.JfrLogging; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; +import com.oracle.svm.core.jfr.utils.JfrRandom; import com.oracle.svm.core.sampler.SamplerBufferPool; import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.thread.JavaVMOperation; @@ -79,10 +80,8 @@ public class SubstrateJVM { private final SamplerBufferPool samplerBufferPool; private final JfrUnlockedChunkWriter unlockedChunkWriter; private final JfrRecorderThread recorderThread; - -// private final JfrThrottlerSupport jfrThrottlerSupport; - private final JfrLogging jfrLogging; + private final JfrRandom jfrRandom; private boolean initialized; /* @@ -118,6 +117,7 @@ public SubstrateJVM(List configurations) { recorderThread = new JfrRecorderThread(globalMemory, unlockedChunkWriter); jfrLogging = new JfrLogging(); + jfrRandom = new JfrRandom(); initialized = false; recording = false; @@ -190,6 +190,15 @@ public static JfrLogging getJfrLogging() { return get().jfrLogging; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static double getNextRandomUniform() { + return get().jfrRandom.nextUniform(); + } + + public static JfrRandom getJfrRandom() { + return get().jfrRandom; + } + public static Object getHandler(Class eventClass) { try { Field f = eventClass.getDeclaredField("eventHandler"); @@ -201,7 +210,7 @@ public static Object getHandler(Class eventC } @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) - protected boolean isRecording() { + public boolean isRecording() { return recording; } @@ -661,10 +670,7 @@ public boolean isEnabled(JfrEvent event) { return eventSettings[(int) event.getId()].isEnabled(); } -// public boolean shouldCommit(JfrEvent event) { -// return jfrThrottlerSupport.shouldCommit(event.getId()); -// } - +@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean shouldCommit(JfrEvent event) { JfrThrottler throttler = event.getThrottler(); if (throttler != null) { @@ -673,10 +679,6 @@ public boolean shouldCommit(JfrEvent event) { return true; } -// public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { -// return jfrThrottlerSupport.setThrottle(eventTypeId, eventSampleSize, periodMs); -// } - public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { for (JfrEvent event : JfrEvent.getEvents()) { if (eventTypeId == event.getId()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java new file mode 100644 index 000000000000..dda712e748e0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java @@ -0,0 +1,60 @@ +package com.oracle.svm.core.jfr.utils; + +import jdk.internal.misc.Unsafe; +import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.Uninterruptible; +import org.graalvm.nativeimage.Platform; +import com.oracle.svm.core.locks.VMMutex; + +/** This class is essentially the same as JfrPRNG in jdk/src/hotspot/shar/jfr/utilities/jfrRandom.inline.hpp in the OpenJDK + * Commit hash: 1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817 */ +public class JfrRandom { + private static final long prngMult = 25214903917L; + private static final long prngAdd = 11; + private static final long prngModPower = 48; + private static final long modMask = (1L << prngModPower) - 1; + private volatile long random = 0; + + private static com.oracle.svm.core.locks.VMMutex mutex; + + @Platforms(Platform.HOSTED_ONLY.class) + public JfrRandom(){ + mutex = new VMMutex("asdf"); + } + + /** This is the formula for RAND48 used in unix systems (linear congruential generator). This is also what JFR in hotspot uses.*/ + @Uninterruptible(reason = "Locking with no transition.") + private long nextRandom(){ + // Should be atomic to avoid repeated values + mutex.lockNoTransition(); + try { + if(random == 0){ + random = System.currentTimeMillis(); //TODO reset in startup hook + } + long next = (prngMult * random + prngAdd) & modMask; + random = next; + com.oracle.svm.core.util.VMError.guarantee(random >0); + return next; + } finally { + mutex.unlock(); + } + } + + public void resetSeed(){ + mutex.lock(); + try { + random = 0; + } finally { + mutex.unlock(); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public double nextUniform(){ + long next = nextRandom(); + // Take the top 26 bits + long masked = next >> (prngModPower - 26); + // Normalize between 0 and 1 + return masked / (double) (1L << 26); + } +} From f68763ae0d5b4b05807e398ecfc4ef2670da9555 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 25 Sep 2023 16:22:15 -0400 Subject: [PATCH 22/34] add read-write lock --- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 2 +- .../com/oracle/svm/core/jfr/JfrThrottler.java | 70 ++++++------ .../svm/core/jfr/utils/JfrReadWriteLock.java | 100 ++++++++++++++++++ .../oracle/svm/test/jfr/TestThrottler.java | 19 ++-- 4 files changed, 147 insertions(+), 44 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java 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 0ae74256cc0a..f6c1a42307c1 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 @@ -85,7 +85,7 @@ private JfrEvent(String name, boolean hasDuration, boolean hasThrottling) { this.name = name; this.hasDuration = hasDuration; if (hasThrottling) { - throttler = new JfrThrottler(new VMMutex("jfrThrottler_" + name)); + throttler = new JfrThrottler(); } events.add(this); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index ff690da772ee..8e2d3856f7d1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -29,9 +29,11 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.util.TimeUtils; - +import com.oracle.svm.core.jfr.utils.JfrReadWriteLock; /** - * Each event that allows throttling should have its own throttler instance. + * Each event that allows throttling should have its own throttler instance. Multiple threads may use the same + * throttler instance when emitting a particular JFR event type. The throttler uses a rotating window scheme. The active + * window is guaranteed not to change while there are threads busy sampling. */ public class JfrThrottler { // The following are set to match the values in OpenJDK @@ -39,9 +41,9 @@ public class JfrThrottler { private static final int LOW_RATE_UPPER_BOUND = 9; private final JfrThrottlerWindow window0; private final JfrThrottlerWindow window1; - private final VMMutex rotationLock; + private final JfrReadWriteLock rwlock; - // The following fields are only be accessed by threads holding the lock + // The following fields are only be accessed by threads holding the writer lock private long periodNs; private long eventSampleSize; private double ewmaPopulationSizeAlpha = 0; @@ -54,13 +56,13 @@ public class JfrThrottler { private volatile JfrThrottlerWindow activeWindow; private volatile boolean disabled; - public JfrThrottler(VMMutex mutex) { + public JfrThrottler() { reconfigure = false; disabled = true; window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); activeWindow = window0; - this.rotationLock = mutex; + rwlock = new JfrReadWriteLock(); } /** @@ -69,8 +71,9 @@ public JfrThrottler(VMMutex mutex) { * critical section because setting the sample size and period must be done together atomically. * Otherwise, we risk a window's params being set with only one of the two updated. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void normalize(long samplesPerPeriod, double periodMs) { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); // Do we want more than 10samples/s ? If so convert to samples/s double periodsPerSecond = 1000.0 / periodMs; double samplesPerSecond = samplesPerPeriod * periodsPerSecond; @@ -84,6 +87,7 @@ private void normalize(long samplesPerPeriod, double periodMs) { this.periodNs = (long) periodMs * 1000000; } + @Uninterruptible(reason = "Avoid deadlock due to locking without transition.") public boolean setThrottle(long eventSampleSize, long periodMs) { if (eventSampleSize == Target_jdk_jfr_internal_settings_ThrottleSetting.OFF) { disabled = true; @@ -91,49 +95,51 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { } // Blocking lock because new settings MUST be applied. - rotationLock.lock(); + rwlock.writeLockNoTransition(); try { normalize(eventSampleSize, periodMs); reconfigure = true; rotateWindow(); } finally { - rotationLock.unlock(); + rwlock.unlock(); } disabled = false; return true; } /** - * The real active window may change while we're doing the sampling. That's fine as long as we - * perform operations with respect to a consistent window during this method. It's fine if the - * active window changes after we've read it from memory, because now we'll be writing to the - * next window (which gets reset before becoming active again). + * Immediately acquiring the reader lock when entering this method prevents the active window from changing while + * sampling is in progress. If we encounter an expired window, there's no point in sampling, so the reader + * lock can be returned. An expired window should be rotated. The writer lock must be acquired before attempting + * to rotate. Once an expired window is detected, it is guaranteed to be rotated before any NEW threads (readers) + * are allowed to begin sampling. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean sample() { if (disabled) { return true; } - JfrThrottlerWindow window = activeWindow; - boolean expired = window.isExpired(); - if (expired) { - // Check lock in case thread is already rotating. - rotationLock.lockNoTransition(); - try { + // New readers will block here if there is a writer waiting for the lock. + rwlock.readLockNoTransition(); + try { + boolean expired = activeWindow.isExpired(); + if (expired) { + rwlock.unlock(); + rwlock.writeLockNoTransition(); /* - * Once in critical section, ensure active window is still expired. Another thread + * Once in the critical section, ensure the active window is still expired. Another thread * may have already handled the expired window, or new settings may have already * triggered a rotation. */ if (activeWindow.isExpired()) { rotateWindow(); } - } finally { - rotationLock.unlock(); + return false; } - return false; + return activeWindow.sample(); + } finally { + rwlock.unlock(); } - return window.sample(); } /** @@ -144,20 +150,20 @@ public boolean sample() { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void rotateWindow() { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); configure(); installNextWindow(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void installNextWindow() { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); activeWindow = getNextWindow(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private JfrThrottlerWindow getNextWindow() { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); if (window0 == activeWindow) { return window1; } @@ -166,7 +172,7 @@ private JfrThrottlerWindow getNextWindow() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); if (periodNs == 0 || windowDurationNs >= TimeUtils.nanosPerSecond) { return 1; } @@ -175,7 +181,7 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private long amortizeDebt(JfrThrottlerWindow lastWindow) { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { accumulatedDebtCarryCount = 1; return 0; // reset because new settings have been applied @@ -190,7 +196,7 @@ private long amortizeDebt(JfrThrottlerWindow lastWindow) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void setSamplePointsAndWindowDuration() { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); assert reconfigure; JfrThrottlerWindow next = getNextWindow(); long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; @@ -211,7 +217,7 @@ private void setSamplePointsAndWindowDuration() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void configure() { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); JfrThrottlerWindow next = getNextWindow(); // Store updated parameters to both windows. @@ -244,7 +250,7 @@ private static double windowLookback(JfrThrottlerWindow window) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private double projectPopulationSize(long lastWindowMeasuredPop) { - assert rotationLock.isOwner(); + assert rwlock.isWriteOwner(); avgPopulationSize = exponentiallyWeightedMovingAverage(lastWindowMeasuredPop, ewmaPopulationSizeAlpha, avgPopulationSize); return avgPopulationSize; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java new file mode 100644 index 000000000000..8d424ae989b1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java @@ -0,0 +1,100 @@ +package com.oracle.svm.core.jfr.utils; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleUtils; +import org.graalvm.compiler.nodes.PauseNode; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.jfr.SubstrateJVM; + +/** An uninterruptible read-write lock implementation using atomics with writer preference.*/ +public class JfrReadWriteLock { + private static final long CURRENTLY_WRITING = Long.MAX_VALUE; + private final UninterruptibleUtils.AtomicLong ownerCount; + private final UninterruptibleUtils.AtomicLong waitingWriters; + private volatile long writeOwnerTid; + + public JfrReadWriteLock() { + ownerCount = new UninterruptibleUtils.AtomicLong(0); + waitingWriters = new UninterruptibleUtils.AtomicLong(0); + writeOwnerTid = -1; + } + + @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) + public void readLockNoTransition(){ + readTryLock(Integer.MAX_VALUE); + } + + @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) + public void writeLockNoTransition(){ + writeTryLock(Integer.MAX_VALUE); + } + + /** The bias towards writers does NOT ensure that there are no waiting writers at the time the readerCount is + * compared and set. The only guarantee is that this reader will not acquire the lock before any writer that has + * been waiting longer than it. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void readTryLock(int retries){ + int yields = 0; + for (int i = 0; i < retries; i++) { + long readers = ownerCount.get(); + // Only attempt to enter the critical section if no writers are waiting or writes in-progress. + if (waitingWriters.get() > 0 || readers == CURRENTLY_WRITING){ + yields = maybeYield(i, yields); + } else { + // Attempt to take the lock + if (ownerCount.compareAndSet(readers, readers+1)){ + return; + } + } + } + } + @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) + public void writeTryLock(int retries){ + int yields = 0; + // Increment the writer to count to signal intent. + waitingWriters.incrementAndGet(); + for (int i = 0; i < retries; i++) { + long readers = ownerCount.get(); + // Only enter the critical section if all in-progress readers have finished. + if (readers != 0){ + yields = maybeYield(i, yields); + } else { + // Attempt to take the lock + if (ownerCount.compareAndSet(0, CURRENTLY_WRITING)){ + // Success. Signal no longer waiting. + long waiters = waitingWriters.decrementAndGet(); + assert waiters >= 0; + writeOwnerTid = SubstrateJVM.getCurrentThreadId(); + return; + } + } + } + } + + /** This is the same logic as in {@link com.oracle.svm.core.thread.JavaSpinLockUtils#tryLock(Object, long, int)}*/ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static int maybeYield(int retryCount, int yields){ + if ((retryCount & 0xff) == 0 && VMThreads.singleton().supportsNativeYieldAndSleep()) { + if (yields > 5) { + VMThreads.singleton().nativeSleep(1); + } else { + VMThreads.singleton().yield(); + return yields + 1; + } + } else { + PauseNode.pause(); + } + return yields; + } + + @Uninterruptible(reason = "Used in locking without transition, so the whole critical section must be uninterruptible.", callerMustBe = true) + public void unlock(){ + writeOwnerTid = -1; + ownerCount.set(0); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isWriteOwner(){ + return writeOwnerTid == SubstrateJVM.getCurrentThreadId(); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index f6e2e473a3f3..e744441f4527 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -43,8 +43,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import com.oracle.svm.core.locks.VMMutex; - public class TestThrottler extends JfrRecordingTest { // Based on the hardcoded value in the throttler class. @@ -53,7 +51,6 @@ public class TestThrottler extends JfrRecordingTest { private static final long WINDOW_DURATION_MS = 200; private static final long SAMPLES_PER_WINDOW = 10; private static final long SECOND_IN_MS = 1000; - private static final VMMutex mutex = new VMMutex("testThrottler"); /** * This is the simplest test that ensures that sampling stops after the cap is hit. Single @@ -62,7 +59,7 @@ public class TestThrottler extends JfrRecordingTest { @Test public void testCapSingleThread() { // Doesn't rotate after starting sampling - JfrThrottler throttler = new JfrThrottler(mutex); + JfrThrottler throttler = new JfrThrottler(); throttler.setThrottle(SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); for (int i = 0; i < SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD; i++) { boolean sample = throttler.sample(); @@ -81,7 +78,7 @@ public void testCapConcurrent() throws InterruptedException { final int testingThreadCount = 10; final AtomicInteger count = new AtomicInteger(); List testingThreads = new ArrayList<>(); - JfrThrottler throttler = new JfrThrottler(mutex); + JfrThrottler throttler = new JfrThrottler(); TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); Runnable doSampling = () -> { for (int i = 0; i < samplesPerWindow; i++) { @@ -126,7 +123,7 @@ public void testCapConcurrent() throws InterruptedException { @Test public void testExpiry() { final long samplesPerWindow = 10; - JfrThrottler throttler = new JfrThrottler(mutex); + JfrThrottler throttler = new JfrThrottler(); TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); int count = 0; @@ -154,7 +151,7 @@ public void testExpiry() { public void testEWMA() { // Results in 50 samples per second final long samplesPerWindow = 10; - JfrThrottler throttler = new JfrThrottler(mutex); + JfrThrottler throttler = new JfrThrottler(); TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, SECOND_IN_MS); assertTrue(TestingBackDoor.getWindowLookback(throttler) == 25.0); // Arbitrarily chosen @@ -178,7 +175,7 @@ public void testEWMA() { public void testDebt() { final long samplesPerWindow = 10; final long populationPerWindow = 50; - JfrThrottler throttler = new JfrThrottler(mutex); + JfrThrottler throttler = new JfrThrottler(); TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * WINDOW_DURATION_MS); for (int p = 0; p < 50; p++) { @@ -226,7 +223,7 @@ public void testDebt() { public void testNormalization() { long sampleSize = 10 * 600; long periodMs = 60 * SECOND_IN_MS; - JfrThrottler throttler = new JfrThrottler(mutex); + JfrThrottler throttler = new JfrThrottler(); TestingBackDoor.beginTest(throttler, sampleSize, periodMs); assertTrue(TestingBackDoor.getPeriodNs(throttler) + " " + TestingBackDoor.getEventSampleSize(throttler), TestingBackDoor.getEventSampleSize(throttler) == sampleSize / 60 && TestingBackDoor.getPeriodNs(throttler) == 1000000 * SECOND_IN_MS); @@ -244,7 +241,7 @@ public void testNormalization() { @Test public void testZeroRate() throws Throwable { // Test throttler in isolation - JfrThrottler throttler = new JfrThrottler(mutex); + JfrThrottler throttler = new JfrThrottler(); throttler.setThrottle(0, 2 * SECOND_IN_MS); assertFalse(throttler.sample()); throttler.setThrottle(10, 2 * SECOND_IN_MS); @@ -329,7 +326,7 @@ private static void testDistribution(IncomingPopulation incomingPopulation, int final int expectedSamplesPerWindow = 50; final int expectedSamples = expectedSamplesPerWindow * windowCount; - JfrThrottler throttler = new JfrThrottler(mutex); + JfrThrottler throttler = new JfrThrottler(); TestingBackDoor.beginTest(throttler, expectedSamplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); int[] population = new int[distributionSlots]; From 34ceac8b2d82defd97c24e559eb54efeb6b82445 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 26 Sep 2023 12:58:54 -0400 Subject: [PATCH 23/34] cleanup. comments. javadoc. error handling. reset lastAllocationSize. --- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 1 - .../com/oracle/svm/core/jfr/JfrThrottler.java | 38 ++++++---- .../svm/core/jfr/JfrThrottlerWindow.java | 19 ++--- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 13 ++-- .../core/jfr/events/JfrAllocationEvents.java | 5 ++ .../oracle/svm/core/jfr/utils/JfrRandom.java | 53 +++++++++++--- .../svm/core/jfr/utils/JfrReadWriteLock.java | 69 ++++++++++++++----- 7 files changed, 140 insertions(+), 58 deletions(-) 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 f6c1a42307c1..a25f0cf6e4e3 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 @@ -28,7 +28,6 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.locks.VMMutex; import java.util.ArrayList; import java.util.List; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 8e2d3856f7d1..f7ab70c13105 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -27,13 +27,19 @@ package com.oracle.svm.core.jfr; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.jfr.utils.JfrReadWriteLock; + /** - * Each event that allows throttling should have its own throttler instance. Multiple threads may use the same - * throttler instance when emitting a particular JFR event type. The throttler uses a rotating window scheme. The active - * window is guaranteed not to change while there are threads busy sampling. + * Each event that allows throttling should have its own throttler instance. Multiple threads may + * use the same throttler instance when emitting a particular JFR event type. The throttler uses a + * rotating window scheme where each window represents a time slice. The data from the current + * window is used to set the parameters of the next window. The active window is guaranteed not to + * change while there are threads using it for sampling. + * + * This class is based on JfrAdaptiveSampler in hotspot/share/jfr/support/jfrAdaptiveSampler.cpp and + * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp. Commit + * hash:1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817. Openjdk version "22-internal". */ public class JfrThrottler { // The following are set to match the values in OpenJDK @@ -88,10 +94,12 @@ private void normalize(long samplesPerPeriod, double periodMs) { } @Uninterruptible(reason = "Avoid deadlock due to locking without transition.") - public boolean setThrottle(long eventSampleSize, long periodMs) { + public void setThrottle(long eventSampleSize, long periodMs) { if (eventSampleSize == Target_jdk_jfr_internal_settings_ThrottleSetting.OFF) { disabled = true; - return true; + return; + } else if (eventSampleSize < 0 || periodMs < 0) { + return; } // Blocking lock because new settings MUST be applied. @@ -104,15 +112,15 @@ public boolean setThrottle(long eventSampleSize, long periodMs) { rwlock.unlock(); } disabled = false; - return true; } /** - * Immediately acquiring the reader lock when entering this method prevents the active window from changing while - * sampling is in progress. If we encounter an expired window, there's no point in sampling, so the reader - * lock can be returned. An expired window should be rotated. The writer lock must be acquired before attempting - * to rotate. Once an expired window is detected, it is guaranteed to be rotated before any NEW threads (readers) - * are allowed to begin sampling. + * Acquiring the reader lock before using the active window prevents the active window from + * changing while sampling is in progress. If we encounter an expired window, there's no point + * in sampling, so the reader lock can be returned. An expired window should be rotated. The + * writer lock must be acquired before attempting to rotate. Once an expired window is detected, + * it is likely, but not guaranteed, to be rotated before any NEW threads (readers) are allowed + * to begin sampling. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean sample() { @@ -127,9 +135,9 @@ public boolean sample() { rwlock.unlock(); rwlock.writeLockNoTransition(); /* - * Once in the critical section, ensure the active window is still expired. Another thread - * may have already handled the expired window, or new settings may have already - * triggered a rotation. + * Once in the critical section, ensure the active window is still expired. Another + * thread may have already handled the expired window, or new settings may have + * already triggered a rotation. */ if (activeWindow.isExpired()) { rotateWindow(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index acd39f4d4f39..799bc9b1357b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -30,6 +30,11 @@ import static java.lang.Math.log; +/** + * This class is based on JfrSamplerWindow in hotspot/share/jfr/support/jfrAdaptiveSampler.cpp and + * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp. Commit hash: + * 1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817. Openjdk version "22-internal". + */ public class JfrThrottlerWindow { // reset every rotation public UninterruptibleUtils.AtomicLong measuredPopSize; @@ -55,12 +60,8 @@ public JfrThrottlerWindow() { } /** - * A rotation of the active window could happen while in this method. If so, then this window - * will be updated as usual, although it is now the "next" window. This results in some wasted - * effort, but doesn't affect correctness because this window will be reset before it becomes - * active again. - * - * Threads calling this method may not have acquired the JfrThrottler lock. + * The reader lock must be acquired here. The active window will not change while in this + * method. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean sample() { @@ -73,7 +74,7 @@ public boolean sample() { (prevMeasuredPopSize < maxSampleablePopulation); } - /** Thread's calling this method should have acquired the JftThrottler lock. */ + /** Thread's calling this method should have acquired the JftThrottler writer lcok. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long samplesTaken() { if (measuredPopSize.get() > maxSampleablePopulation) { @@ -82,13 +83,13 @@ public long samplesTaken() { return measuredPopSize.get() / samplingInterval; } - /** Thread's calling this method should have acquired the JftThrottler lock. */ + /** Thread's calling this method should have acquired the JftThrottler writer lock. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long samplesExpected() { return samplesPerWindow + debt; } - /** Thread's calling this method should have acquired the JftThrottler lock. */ + /** Thread's calling this method should have acquired the JftThrottler writer lock. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void configure(long newDebt, double projectedPopSize) { this.debt = newDebt; 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 dd1ff70e277a..b892326966ad 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,6 +39,7 @@ 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.JfrAllocationEvents; import com.oracle.svm.core.jfr.logging.JfrLogging; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; import com.oracle.svm.core.jfr.utils.JfrRandom; @@ -670,7 +671,7 @@ public boolean isEnabled(JfrEvent event) { return eventSettings[(int) event.getId()].isEnabled(); } -@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean shouldCommit(JfrEvent event) { JfrThrottler throttler = event.getThrottler(); if (throttler != null) { @@ -684,12 +685,12 @@ public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs if (eventTypeId == event.getId()) { JfrThrottler throttler = event.getThrottler(); if (throttler != null) { - return throttler.setThrottle(eventSampleSize, periodMs); + throttler.setThrottle(eventSampleSize, periodMs); + break; } - return false; } } - return false; + return true; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -748,6 +749,10 @@ private static class JfrBeginRecordingOperation extends JavaVMOperation { @Override protected void operate() { + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + JfrAllocationEvents.resetLastAllocationSize(isolateThread); + } + SubstrateJVM.get().recording = true; /* Recording is enabled, so JFR events can be triggered at any time. */ SubstrateJVM.getThreadRepo().registerRunningThreads(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java index 235742e8ad21..d0ae6e80e5df 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java @@ -26,6 +26,7 @@ package com.oracle.svm.core.jfr.events; +import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.StackValue; import org.graalvm.word.UnsignedWord; @@ -45,6 +46,10 @@ public class JfrAllocationEvents { private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); + public static void resetLastAllocationSize(IsolateThread thread) { + lastAllocationSize.set(thread, 0); + } + public static void emit(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { if (HasJfrSupport.get()) { Class clazz = DynamicHub.toClass(hub); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java index dda712e748e0..31dd95bb2b0b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java @@ -1,13 +1,41 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package com.oracle.svm.core.jfr.utils; -import jdk.internal.misc.Unsafe; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.Uninterruptible; import org.graalvm.nativeimage.Platform; import com.oracle.svm.core.locks.VMMutex; -/** This class is essentially the same as JfrPRNG in jdk/src/hotspot/shar/jfr/utilities/jfrRandom.inline.hpp in the OpenJDK - * Commit hash: 1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817 */ +/** + * This class is essentially the same as JfrPRNG in + * jdk/src/hotspot/shar/jfr/utilities/jfrRandom.inline.hpp in the OpenJDK. Commit hash: + * 1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817. + */ public class JfrRandom { private static final long prngMult = 25214903917L; private static final long prngAdd = 11; @@ -18,29 +46,32 @@ public class JfrRandom { private static com.oracle.svm.core.locks.VMMutex mutex; @Platforms(Platform.HOSTED_ONLY.class) - public JfrRandom(){ + public JfrRandom() { mutex = new VMMutex("asdf"); } - /** This is the formula for RAND48 used in unix systems (linear congruential generator). This is also what JFR in hotspot uses.*/ + /** + * This is the formula for RAND48 used in unix systems (linear congruential generator). This is + * also what JFR in hotspot uses. + */ @Uninterruptible(reason = "Locking with no transition.") - private long nextRandom(){ + private long nextRandom() { // Should be atomic to avoid repeated values mutex.lockNoTransition(); try { - if(random == 0){ - random = System.currentTimeMillis(); //TODO reset in startup hook + if (random == 0) { + random = System.currentTimeMillis(); // TODO reset in startup hook } long next = (prngMult * random + prngAdd) & modMask; random = next; - com.oracle.svm.core.util.VMError.guarantee(random >0); + com.oracle.svm.core.util.VMError.guarantee(random > 0); return next; } finally { mutex.unlock(); } } - public void resetSeed(){ + public void resetSeed() { mutex.lock(); try { random = 0; @@ -50,7 +81,7 @@ public void resetSeed(){ } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public double nextUniform(){ + public double nextUniform() { long next = nextRandom(); // Take the top 26 bits long masked = next >> (prngModPower - 26); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java index 8d424ae989b1..7a06d106974f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java @@ -1,3 +1,29 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package com.oracle.svm.core.jfr.utils; import com.oracle.svm.core.Uninterruptible; @@ -6,7 +32,7 @@ import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.jfr.SubstrateJVM; -/** An uninterruptible read-write lock implementation using atomics with writer preference.*/ +/** An uninterruptible read-write lock implementation using atomics with writer preference. */ public class JfrReadWriteLock { private static final long CURRENTLY_WRITING = Long.MAX_VALUE; private final UninterruptibleUtils.AtomicLong ownerCount; @@ -20,50 +46,54 @@ public JfrReadWriteLock() { } @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public void readLockNoTransition(){ + public void readLockNoTransition() { readTryLock(Integer.MAX_VALUE); } @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public void writeLockNoTransition(){ + public void writeLockNoTransition() { writeTryLock(Integer.MAX_VALUE); } - /** The bias towards writers does NOT ensure that there are no waiting writers at the time the readerCount is - * compared and set. The only guarantee is that this reader will not acquire the lock before any writer that has - * been waiting longer than it. */ + /** + * The bias towards writers does NOT ensure that there are no waiting writers at the time a + * reader enters the critical section. Readers only make a best-effort check there are no + * waiting writers before they attempt to acquire the lock to prevent writer starvation. + */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void readTryLock(int retries){ + public void readTryLock(int retries) { int yields = 0; for (int i = 0; i < retries; i++) { long readers = ownerCount.get(); - // Only attempt to enter the critical section if no writers are waiting or writes in-progress. - if (waitingWriters.get() > 0 || readers == CURRENTLY_WRITING){ + // Only attempt to enter the critical section if no writers are waiting or writes + // in-progress. + if (waitingWriters.get() > 0 || readers == CURRENTLY_WRITING) { yields = maybeYield(i, yields); } else { // Attempt to take the lock - if (ownerCount.compareAndSet(readers, readers+1)){ + if (ownerCount.compareAndSet(readers, readers + 1)) { return; } } } } + @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public void writeTryLock(int retries){ + public void writeTryLock(int retries) { int yields = 0; // Increment the writer to count to signal intent. waitingWriters.incrementAndGet(); for (int i = 0; i < retries; i++) { long readers = ownerCount.get(); // Only enter the critical section if all in-progress readers have finished. - if (readers != 0){ + if (readers != 0) { yields = maybeYield(i, yields); } else { // Attempt to take the lock - if (ownerCount.compareAndSet(0, CURRENTLY_WRITING)){ + if (ownerCount.compareAndSet(0, CURRENTLY_WRITING)) { // Success. Signal no longer waiting. long waiters = waitingWriters.decrementAndGet(); - assert waiters >= 0; + assert waiters >= 0; writeOwnerTid = SubstrateJVM.getCurrentThreadId(); return; } @@ -71,9 +101,12 @@ public void writeTryLock(int retries){ } } - /** This is the same logic as in {@link com.oracle.svm.core.thread.JavaSpinLockUtils#tryLock(Object, long, int)}*/ + /** + * This is essentially the same logic as in + * {@link com.oracle.svm.core.thread.JavaSpinLockUtils#tryLock(Object, long, int)}. + */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static int maybeYield(int retryCount, int yields){ + private static int maybeYield(int retryCount, int yields) { if ((retryCount & 0xff) == 0 && VMThreads.singleton().supportsNativeYieldAndSleep()) { if (yields > 5) { VMThreads.singleton().nativeSleep(1); @@ -88,13 +121,13 @@ private static int maybeYield(int retryCount, int yields){ } @Uninterruptible(reason = "Used in locking without transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public void unlock(){ + public void unlock() { writeOwnerTid = -1; ownerCount.set(0); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean isWriteOwner(){ + public boolean isWriteOwner() { return writeOwnerTid == SubstrateJVM.getCurrentThreadId(); } } From d4eb4016a9a7b41975445198eaefac53b358ec08 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 26 Sep 2023 15:51:05 -0400 Subject: [PATCH 24/34] dedicated test class in test code minor clean up --- .../com/oracle/svm/core/jfr/JfrManager.java | 2 +- .../com/oracle/svm/core/jfr/JfrThrottler.java | 71 ++-------- .../svm/core/jfr/JfrThrottlerWindow.java | 24 +--- .../oracle/svm/core/jfr/utils/JfrRandom.java | 4 +- .../oracle/svm/test/jfr/TestThrottler.java | 130 ++++++++++++++---- 5 files changed, 127 insertions(+), 104 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java index 073bffd2e5e4..e57a1d95adbe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java @@ -86,7 +86,7 @@ public RuntimeSupport.Hook startupHook() { return isFirstIsolate -> { parseFlightRecorderLogging(SubstrateOptions.FlightRecorderLogging.getValue()); periodicEventSetup(); - com.oracle.svm.core.jfr.SubstrateJVM.getJfrRandom().resetSeed(); + SubstrateJVM.getJfrRandom().resetSeed(); if (isJFREnabled()) { initRecording(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index f7ab70c13105..230710d19645 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -43,32 +43,36 @@ */ public class JfrThrottler { // The following are set to match the values in OpenJDK - private static final int WINDOW_DIVISOR = 5; - private static final int LOW_RATE_UPPER_BOUND = 9; - private final JfrThrottlerWindow window0; - private final JfrThrottlerWindow window1; + protected static final int WINDOW_DIVISOR = 5; + protected static final int LOW_RATE_UPPER_BOUND = 9; + protected JfrThrottlerWindow window0; + protected JfrThrottlerWindow window1; private final JfrReadWriteLock rwlock; // The following fields are only be accessed by threads holding the writer lock - private long periodNs; - private long eventSampleSize; + protected long periodNs; + protected long eventSampleSize; private double ewmaPopulationSizeAlpha = 0; - private double avgPopulationSize = 0; + protected double avgPopulationSize = 0; private boolean reconfigure; private long accumulatedDebtCarryLimit; private long accumulatedDebtCarryCount; // The following fields may be accessed by multiple threads without acquiring the lock - private volatile JfrThrottlerWindow activeWindow; + protected volatile JfrThrottlerWindow activeWindow; private volatile boolean disabled; public JfrThrottler() { reconfigure = false; disabled = true; + initializeWindows(); + rwlock = new JfrReadWriteLock(); + } + + protected void initializeWindows() { window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); activeWindow = window0; - rwlock = new JfrReadWriteLock(); } /** @@ -90,7 +94,7 @@ private void normalize(long samplesPerPeriod, double periodMs) { } this.eventSampleSize = samplesPerPeriod; - this.periodNs = (long) periodMs * 1000000; + this.periodNs = TimeUtils.millisToNanos((long) periodMs); } @Uninterruptible(reason = "Avoid deadlock due to locking without transition.") @@ -246,7 +250,7 @@ private void configure() { * The lookback values are set to match the values in OpenJDK. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static double windowLookback(JfrThrottlerWindow window) { + protected static double windowLookback(JfrThrottlerWindow window) { if (window.windowDurationNs <= TimeUtils.nanosPerSecond) { return 25.0; } else if (window.windowDurationNs <= TimeUtils.nanosPerSecond * 60L) { @@ -267,49 +271,4 @@ private double projectPopulationSize(long lastWindowMeasuredPop) { private static double exponentiallyWeightedMovingAverage(double currentMeasurement, double alpha, double prevEwma) { return alpha * currentMeasurement + (1 - alpha) * prevEwma; } - - /** Visible for testing. This assumes the tests are taking care of synchronization. */ - public static class TestingBackDoor { - - public static void beginTest(JfrThrottler throttler, long eventSampleSize, long periodMs) { - throttler.window0.isTest = true; - throttler.window1.isTest = true; - throttler.window0.currentTestNanos = 0; - throttler.window1.currentTestNanos = 0; - throttler.setThrottle(eventSampleSize, periodMs); - } - - public static double getActiveWindowProjectedPopulationSize(JfrThrottler throttler) { - return throttler.avgPopulationSize; - } - - public static long getActiveWindowDebt(JfrThrottler throttler) { - return throttler.activeWindow.debt; - } - - public static double getWindowLookback(JfrThrottler throttler) { - return windowLookback(throttler.activeWindow); - } - - public static boolean isActiveWindowExpired(JfrThrottler throttler) { - return throttler.activeWindow.isExpired(); - } - - public static long getPeriodNs(JfrThrottler throttler) { - return throttler.periodNs; - } - - public static long getEventSampleSize(JfrThrottler throttler) { - return throttler.eventSampleSize; - } - - public static void expireActiveWindow(JfrThrottler throttler) { - if (throttler.eventSampleSize <= LOW_RATE_UPPER_BOUND || throttler.periodNs > TimeUtils.nanosPerSecond) { - throttler.window0.currentTestNanos += throttler.periodNs; - throttler.window1.currentTestNanos += throttler.periodNs; - } - throttler.window0.currentTestNanos += throttler.periodNs / WINDOW_DIVISOR; - throttler.window1.currentTestNanos += throttler.periodNs / WINDOW_DIVISOR; - } - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index 799bc9b1357b..8a0e76935035 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -105,13 +105,12 @@ public void configure(long newDebt, double projectedPopSize) { // reset measuredPopSize.set(0); + advanceEndTicks(); + } - if (isTest) { - // There is a need to mock JfrTicks for testing. - endTicks.set(currentTestNanos + windowDurationNs); - } else { - endTicks.set(System.nanoTime() + windowDurationNs); - } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void advanceEndTicks() { + endTicks.set(System.nanoTime() + windowDurationNs); } /** @@ -130,20 +129,9 @@ private static long nextGeometric(double probability, double u) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isExpired() { - if (isTest) { - // There is a need to mock JfrTicks for testing. - if (currentTestNanos >= endTicks.get()) { - return true; - } - } else if (System.nanoTime() >= endTicks.get()) { + if (System.nanoTime() >= endTicks.get()) { return true; } return false; } - - /** Visible for testing. */ - public volatile boolean isTest = false; - - /** Visible for testing. */ - public volatile long currentTestNanos = 0; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java index 31dd95bb2b0b..7b84d1b3d352 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java @@ -47,7 +47,7 @@ public class JfrRandom { @Platforms(Platform.HOSTED_ONLY.class) public JfrRandom() { - mutex = new VMMutex("asdf"); + mutex = new VMMutex("JfrRandom"); } /** @@ -60,7 +60,7 @@ private long nextRandom() { mutex.lockNoTransition(); try { if (random == 0) { - random = System.currentTimeMillis(); // TODO reset in startup hook + random = System.currentTimeMillis(); } long next = (prngMult * random + prngAdd) & modMask; random = next; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index e744441f4527..17b41a9e54cb 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -27,10 +27,11 @@ package com.oracle.svm.test.jfr; import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.genscavenge.HeapParameters; import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.jfr.JfrThrottler; -import com.oracle.svm.core.jfr.JfrThrottler.TestingBackDoor; +import com.oracle.svm.core.jfr.JfrThrottlerWindow; import com.oracle.svm.core.util.UnsignedUtils; import jdk.jfr.Recording; import org.junit.Test; @@ -59,7 +60,7 @@ public class TestThrottler extends JfrRecordingTest { @Test public void testCapSingleThread() { // Doesn't rotate after starting sampling - JfrThrottler throttler = new JfrThrottler(); + JfrThrottler throttler = new JfrTestThrottler(); throttler.setThrottle(SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); for (int i = 0; i < SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD; i++) { boolean sample = throttler.sample(); @@ -78,8 +79,8 @@ public void testCapConcurrent() throws InterruptedException { final int testingThreadCount = 10; final AtomicInteger count = new AtomicInteger(); List testingThreads = new ArrayList<>(); - JfrThrottler throttler = new JfrThrottler(); - TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + JfrTestThrottler throttler = new JfrTestThrottler(); + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); Runnable doSampling = () -> { for (int i = 0; i < samplesPerWindow; i++) { boolean sample = throttler.sample(); @@ -123,8 +124,8 @@ public void testCapConcurrent() throws InterruptedException { @Test public void testExpiry() { final long samplesPerWindow = 10; - JfrThrottler throttler = new JfrThrottler(); - TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + JfrTestThrottler throttler = new JfrTestThrottler(); + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); int count = 0; for (int i = 0; i < samplesPerWindow * 10; i++) { @@ -151,9 +152,9 @@ public void testExpiry() { public void testEWMA() { // Results in 50 samples per second final long samplesPerWindow = 10; - JfrThrottler throttler = new JfrThrottler(); - TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, SECOND_IN_MS); - assertTrue(TestingBackDoor.getWindowLookback(throttler) == 25.0); + JfrTestThrottler throttler = new JfrTestThrottler(); + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, SECOND_IN_MS); + assertTrue(throttler.getWindowLookback() == 25.0); // Arbitrarily chosen int[] population = {310, 410, 610, 310, 910, 420, 770, 290, 880, 640, 220, 110, 330, 590}; // actualProjections are the expected EWMA values @@ -163,7 +164,7 @@ public void testEWMA() { throttler.sample(); } expireAndRotate(throttler); - double projectedPopulation = TestingBackDoor.getActiveWindowProjectedPopulationSize(throttler); + double projectedPopulation = throttler.getActiveWindowProjectedPopulationSize(); assertTrue(actualProjections[p] == (int) projectedPopulation); } } @@ -175,8 +176,8 @@ public void testEWMA() { public void testDebt() { final long samplesPerWindow = 10; final long populationPerWindow = 50; - JfrThrottler throttler = new JfrThrottler(); - TestingBackDoor.beginTest(throttler, samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * WINDOW_DURATION_MS); + JfrTestThrottler throttler = new JfrTestThrottler(); + throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * WINDOW_DURATION_MS); for (int p = 0; p < 50; p++) { for (int i = 0; i < populationPerWindow; i++) { @@ -189,7 +190,7 @@ public void testDebt() { expireAndRotate(throttler); // Debt should be at least 10 because we took no samples last window. - long debt = TestingBackDoor.getActiveWindowDebt(throttler); + long debt = throttler.getActiveWindowDebt(); assertTrue("Should have debt from under sampling.", debt >= 10); // Limit max potential samples to half samplesPerWindow. This means debt must increase by at @@ -198,7 +199,7 @@ public void testDebt() { throttler.sample(); } expireAndRotate(throttler); - assertTrue("Should have debt from under sampling.", TestingBackDoor.getActiveWindowDebt(throttler) >= debt + samplesPerWindow / 2); + assertTrue("Should have debt from under sampling.", throttler.getActiveWindowDebt() >= debt + samplesPerWindow / 2); // Window lookback is 25. Do not sample for 25 windows. for (int i = 0; i < 25; i++) { @@ -213,7 +214,7 @@ public void testDebt() { assertFalse(throttler.sample()); expireAndRotate(throttler); - assertTrue("Should not have any debt remaining.", TestingBackDoor.getActiveWindowDebt(throttler) == 0); + assertTrue("Should not have any debt remaining.", throttler.getActiveWindowDebt() == 0); } /** @@ -223,16 +224,16 @@ public void testDebt() { public void testNormalization() { long sampleSize = 10 * 600; long periodMs = 60 * SECOND_IN_MS; - JfrThrottler throttler = new JfrThrottler(); - TestingBackDoor.beginTest(throttler, sampleSize, periodMs); - assertTrue(TestingBackDoor.getPeriodNs(throttler) + " " + TestingBackDoor.getEventSampleSize(throttler), - TestingBackDoor.getEventSampleSize(throttler) == sampleSize / 60 && TestingBackDoor.getPeriodNs(throttler) == 1000000 * SECOND_IN_MS); + JfrTestThrottler throttler = new JfrTestThrottler(); + throttler.beginTest(sampleSize, periodMs); + assertTrue(throttler.getPeriodNs() + " " + throttler.getEventSampleSize(), + throttler.getEventSampleSize() == sampleSize / 60 && throttler.getPeriodNs() == 1000000 * SECOND_IN_MS); sampleSize = 10 * 3600; periodMs = 3600 * SECOND_IN_MS; throttler.setThrottle(sampleSize, periodMs); - assertTrue(TestingBackDoor.getPeriodNs(throttler) + " " + TestingBackDoor.getEventSampleSize(throttler), - TestingBackDoor.getEventSampleSize(throttler) == sampleSize / 3600 && TestingBackDoor.getPeriodNs(throttler) == 1000000 * SECOND_IN_MS); + assertTrue(throttler.getPeriodNs() + " " + throttler.getEventSampleSize(), + throttler.getEventSampleSize() == sampleSize / 3600 && throttler.getPeriodNs() == 1000000 * SECOND_IN_MS); } /** @@ -241,7 +242,7 @@ public void testNormalization() { @Test public void testZeroRate() throws Throwable { // Test throttler in isolation - JfrThrottler throttler = new JfrThrottler(); + JfrTestThrottler throttler = new JfrTestThrottler(); throttler.setThrottle(0, 2 * SECOND_IN_MS); assertFalse(throttler.sample()); throttler.setThrottle(10, 2 * SECOND_IN_MS); @@ -326,8 +327,8 @@ private static void testDistribution(IncomingPopulation incomingPopulation, int final int expectedSamplesPerWindow = 50; final int expectedSamples = expectedSamplesPerWindow * windowCount; - JfrThrottler throttler = new JfrThrottler(); - TestingBackDoor.beginTest(throttler, expectedSamplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); + JfrTestThrottler throttler = new JfrTestThrottler(); + throttler.beginTest(expectedSamplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); int[] population = new int[distributionSlots]; int[] sample = new int[distributionSlots]; @@ -391,9 +392,84 @@ interface IncomingPopulation { /** * Helper method that expires and rotates a throttler's active window. */ - private static void expireAndRotate(JfrThrottler throttler) { - TestingBackDoor.expireActiveWindow(throttler); - assertTrue("should be expired", TestingBackDoor.isActiveWindowExpired(throttler)); + private static void expireAndRotate(JfrTestThrottler throttler) { + throttler.expireActiveWindow(); + assertTrue("should be expired", throttler.isActiveWindowExpired()); assertFalse("Should have rotated not sampled!", throttler.sample()); } + + private static class JfrTestThrottler extends JfrThrottler { + @Override + protected void initializeWindows() { + window0 = new JfrTestThrottlerWindow(); + window1 = new JfrTestThrottlerWindow(); + activeWindow = window0; + } + + public void beginTest(long eventSampleSize, long periodMs) { + window0().currentTestNanos = 0; + window1().currentTestNanos = 0; + setThrottle(eventSampleSize, periodMs); + } + + public double getActiveWindowProjectedPopulationSize() { + return avgPopulationSize; + } + + public long getActiveWindowDebt() { + return activeWindow.debt; + } + + public double getWindowLookback() { + return windowLookback(activeWindow); + } + + public boolean isActiveWindowExpired() { + return activeWindow.isExpired(); + } + + public long getPeriodNs() { + return periodNs; + } + + public long getEventSampleSize() { + return eventSampleSize; + } + + public void expireActiveWindow() { + if (eventSampleSize <= LOW_RATE_UPPER_BOUND || periodNs > com.oracle.svm.core.util.TimeUtils.nanosPerSecond) { + window0().currentTestNanos += periodNs; + window1().currentTestNanos += periodNs; + } + window0().currentTestNanos += periodNs / WINDOW_DIVISOR; + window1().currentTestNanos += periodNs / WINDOW_DIVISOR; + } + + private JfrTestThrottlerWindow window0() { + return (JfrTestThrottlerWindow) window0; + } + + private JfrTestThrottlerWindow window1() { + return (JfrTestThrottlerWindow) window1; + } + } + + private static class JfrTestThrottlerWindow extends JfrThrottlerWindow { + public volatile long currentTestNanos = 0; + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isExpired() { + if (currentTestNanos >= endTicks.get()) { + return true; + } + return false; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void advanceEndTicks() { + endTicks.set(currentTestNanos + windowDurationNs); + } + } } From 48ac4400f4ea94c0e794f210f9b06b16e6742e61 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 27 Sep 2023 12:11:30 -0400 Subject: [PATCH 25/34] gate fixes and cleaning --- substratevm/CHANGELOG.md | 1 - .../genscavenge/ThreadLocalAllocation.java | 1 - .../com/oracle/svm/core/jfr/JfrThrottler.java | 21 +++++++------------ .../com/oracle/svm/core/jfr/SubstrateJVM.java | 2 -- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 2 +- ...jfr_internal_settings_ThrottleSetting.java | 3 ++- .../oracle/svm/core/jfr/utils/JfrRandom.java | 2 +- .../oracle/svm/test/jfr/TestThrottler.java | 6 +----- 8 files changed, 13 insertions(+), 25 deletions(-) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 7376ffd22536..1dbf92b5aac4 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -28,7 +28,6 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-46420) Switch to directly using cgroup support from the JDK. * (GR-29688) More sophisticated intrinsification and inlining of method handle usages, both explicit and implicit (lambdas, string concatenation and record classes), and various fixes for non-intrinsified usages. - ## GraalVM for JDK 17 and GraalVM for JDK 20 (Internal Version 23.0.0) * (GR-40187) Report invalid use of SVM specific classes on image class- or module-path as error. As a temporary workaround, `-H:+AllowDeprecatedBuilderClassesOnImageClasspath` allows turning the error into a warning. * (GR-41196) Provide `.debug.svm.imagebuild.*` sections that contain build options and properties used in the build of the image. diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index 2b8a718bacf1..47f7e05fbe2c 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -325,7 +325,6 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un } @Uninterruptible(reason = "Holds uninitialized memory.") - private static Object allocateInstanceInNewTlab(DynamicHub hub, UnsignedWord size, AlignedHeader newTlabChunk) { assert size.equal(LayoutEncoding.getPureInstanceAllocationSize(hub.getLayoutEncoding())); Pointer memory = allocateRawMemoryInNewTlab(size, newTlabChunk); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 230710d19645..5ffe3bcd4479 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -42,37 +42,32 @@ * hash:1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817. Openjdk version "22-internal". */ public class JfrThrottler { + private final JfrReadWriteLock rwlock; + private volatile boolean disabled; + protected volatile JfrThrottlerWindow activeWindow; + // The following are set to match the values in OpenJDK protected static final int WINDOW_DIVISOR = 5; protected static final int LOW_RATE_UPPER_BOUND = 9; protected JfrThrottlerWindow window0; protected JfrThrottlerWindow window1; - private final JfrReadWriteLock rwlock; // The following fields are only be accessed by threads holding the writer lock - protected long periodNs; - protected long eventSampleSize; private double ewmaPopulationSizeAlpha = 0; - protected double avgPopulationSize = 0; private boolean reconfigure; private long accumulatedDebtCarryLimit; private long accumulatedDebtCarryCount; - - // The following fields may be accessed by multiple threads without acquiring the lock - protected volatile JfrThrottlerWindow activeWindow; - private volatile boolean disabled; + protected long periodNs; + protected long eventSampleSize; + protected double avgPopulationSize = 0; public JfrThrottler() { reconfigure = false; disabled = true; - initializeWindows(); - rwlock = new JfrReadWriteLock(); - } - - protected void initializeWindows() { window0 = new JfrThrottlerWindow(); window1 = new JfrThrottlerWindow(); activeWindow = window0; + rwlock = new JfrReadWriteLock(); } /** 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 b892326966ad..eec718854c98 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 @@ -122,8 +122,6 @@ public SubstrateJVM(List configurations) { initialized = false; recording = false; - -// jfrThrottlerSupport = new JfrThrottlerSupport(); } @Fold 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 e763df2a90db..272dfbed248f 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 @@ -413,7 +413,7 @@ public static boolean setCutoff(long eventTypeId, long cutoffTicks) { @Substitute @TargetElement(onlyWith = JDK22OrLater.class) - public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { + public static boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { return SubstrateJVM.get().setThrottle(eventTypeId, eventSampleSize, periodMs); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java index 478fc1c4629c..69a7b5c3faf0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, 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 diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java index 7b84d1b3d352..eb4353adfb9a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java @@ -43,7 +43,7 @@ public class JfrRandom { private static final long modMask = (1L << prngModPower) - 1; private volatile long random = 0; - private static com.oracle.svm.core.locks.VMMutex mutex; + private VMMutex mutex; @Platforms(Platform.HOSTED_ONLY.class) public JfrRandom() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 17b41a9e54cb..6f58ab183144 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -399,14 +399,10 @@ private static void expireAndRotate(JfrTestThrottler throttler) { } private static class JfrTestThrottler extends JfrThrottler { - @Override - protected void initializeWindows() { + public void beginTest(long eventSampleSize, long periodMs) { window0 = new JfrTestThrottlerWindow(); window1 = new JfrTestThrottlerWindow(); activeWindow = window0; - } - - public void beginTest(long eventSampleSize, long periodMs) { window0().currentTestNanos = 0; window1().currentTestNanos = 0; setThrottle(eventSampleSize, periodMs); From f5fa957778231b1560035bce95b20ebcbb152ae7 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 27 Sep 2023 13:32:28 -0400 Subject: [PATCH 26/34] gate fixes. Math.ceil and style --- .../com/oracle/svm/core/jdk/UninterruptibleUtils.java | 11 +++++++++++ .../com/oracle/svm/core/jfr/JfrThrottlerWindow.java | 2 +- ...get_jdk_jfr_internal_settings_ThrottleSetting.java | 3 +-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index 361d6ae7f91e..8f23d8387632 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -438,6 +438,17 @@ public static int abs(int a) { public static long abs(long a) { return (a < 0) ? -a : a; } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long floor(double a) { + return (long) a; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long ceil(double a) { + long floor = floor(a); + return a > floor ? floor + 1 : floor; + } } public static class Byte { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java index 8a0e76935035..ecaa01e4195c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java @@ -124,7 +124,7 @@ private static long nextGeometric(double probability, double u) { randomVar = 0.01; } // Inverse CDF for the geometric distribution. - return (long) Math.ceil(log(1.0 - randomVar) / log(1.0 - probability)); + return UninterruptibleUtils.Math.ceil(log(1.0 - randomVar) / log(1.0 - probability)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java index 69a7b5c3faf0..fed5c41150a3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java @@ -31,6 +31,5 @@ @TargetClass(className = "jdk.jfr.internal.settings.ThrottleSetting", onlyWith = HasJfrSupport.class) final class Target_jdk_jfr_internal_settings_ThrottleSetting { - @Alias - static long OFF; + @Alias static long OFF; } From 41811ee00ec9d80c20dda5b919973eb2df1be9ed Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 11 Oct 2023 11:41:20 -0400 Subject: [PATCH 27/34] fix some concurrency issues clean up JFR random --- .../com/oracle/svm/core/jfr/JfrThrottler.java | 18 +++---- .../oracle/svm/core/jfr/utils/JfrRandom.java | 3 +- .../svm/core/jfr/utils/JfrReadWriteLock.java | 54 +++++++++++-------- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java index 5ffe3bcd4479..d67327f2187d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java @@ -78,7 +78,7 @@ public JfrThrottler() { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void normalize(long samplesPerPeriod, double periodMs) { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); // Do we want more than 10samples/s ? If so convert to samples/s double periodsPerSecond = 1000.0 / periodMs; double samplesPerSecond = samplesPerPeriod * periodsPerSecond; @@ -157,20 +157,20 @@ public boolean sample() { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void rotateWindow() { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); configure(); installNextWindow(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void installNextWindow() { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); activeWindow = getNextWindow(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private JfrThrottlerWindow getNextWindow() { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); if (window0 == activeWindow) { return window1; } @@ -179,7 +179,7 @@ private JfrThrottlerWindow getNextWindow() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); if (periodNs == 0 || windowDurationNs >= TimeUtils.nanosPerSecond) { return 1; } @@ -188,7 +188,7 @@ private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private long amortizeDebt(JfrThrottlerWindow lastWindow) { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { accumulatedDebtCarryCount = 1; return 0; // reset because new settings have been applied @@ -203,7 +203,7 @@ private long amortizeDebt(JfrThrottlerWindow lastWindow) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void setSamplePointsAndWindowDuration() { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); assert reconfigure; JfrThrottlerWindow next = getNextWindow(); long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; @@ -224,7 +224,7 @@ private void setSamplePointsAndWindowDuration() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void configure() { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); JfrThrottlerWindow next = getNextWindow(); // Store updated parameters to both windows. @@ -257,7 +257,7 @@ protected static double windowLookback(JfrThrottlerWindow window) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private double projectPopulationSize(long lastWindowMeasuredPop) { - assert rwlock.isWriteOwner(); + assert rwlock.isCurrentThreadWriteOwner(); avgPopulationSize = exponentiallyWeightedMovingAverage(lastWindowMeasuredPop, ewmaPopulationSizeAlpha, avgPopulationSize); return avgPopulationSize; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java index eb4353adfb9a..b07a3ea38487 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java @@ -64,7 +64,7 @@ private long nextRandom() { } long next = (prngMult * random + prngAdd) & modMask; random = next; - com.oracle.svm.core.util.VMError.guarantee(random > 0); + assert random > 0; return next; } finally { mutex.unlock(); @@ -80,6 +80,7 @@ public void resetSeed() { } } + /** This logic is essentially copied from JfrPRNG in Hotspot. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public double nextUniform() { long next = nextRandom(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java index 7a06d106974f..3e5e217a980c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java @@ -37,7 +37,8 @@ public class JfrReadWriteLock { private static final long CURRENTLY_WRITING = Long.MAX_VALUE; private final UninterruptibleUtils.AtomicLong ownerCount; private final UninterruptibleUtils.AtomicLong waitingWriters; - private volatile long writeOwnerTid; + private volatile long writeOwnerTid; // If this is set, then a writer owns the lock. Otherwise + // -1. public JfrReadWriteLock() { ownerCount = new UninterruptibleUtils.AtomicLong(0); @@ -65,12 +66,12 @@ public void readTryLock(int retries) { int yields = 0; for (int i = 0; i < retries; i++) { long readers = ownerCount.get(); - // Only attempt to enter the critical section if no writers are waiting or writes - // in-progress. + // Only begin the attempt to enter the critical section if no writers are waiting or + // writes are in-progress. if (waitingWriters.get() > 0 || readers == CURRENTLY_WRITING) { yields = maybeYield(i, yields); } else { - // Attempt to take the lock + // Attempt to take the lock. if (ownerCount.compareAndSet(readers, readers + 1)) { return; } @@ -78,26 +79,29 @@ public void readTryLock(int retries) { } } - @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void writeTryLock(int retries) { - int yields = 0; - // Increment the writer to count to signal intent. + // Increment the writer count to signal our intent to acquire the lock. waitingWriters.incrementAndGet(); - for (int i = 0; i < retries; i++) { - long readers = ownerCount.get(); - // Only enter the critical section if all in-progress readers have finished. - if (readers != 0) { - yields = maybeYield(i, yields); - } else { - // Attempt to take the lock - if (ownerCount.compareAndSet(0, CURRENTLY_WRITING)) { - // Success. Signal no longer waiting. - long waiters = waitingWriters.decrementAndGet(); - assert waiters >= 0; - writeOwnerTid = SubstrateJVM.getCurrentThreadId(); - return; + try { + int yields = 0; + for (int i = 0; i < retries; i++) { + long readers = ownerCount.get(); + // Only enter the critical section if all in-progress readers have finished. + if (readers != 0) { + yields = maybeYield(i, yields); + } else { + // Attempt to acquire the lock. + if (ownerCount.compareAndSet(0, CURRENTLY_WRITING)) { + writeOwnerTid = SubstrateJVM.getCurrentThreadId(); + return; + } } } + } finally { + // Regardless of whether we eventually acquired the lock, signal we are done waiting. + long waiters = waitingWriters.decrementAndGet(); + assert waiters >= 0; } } @@ -122,12 +126,20 @@ private static int maybeYield(int retryCount, int yields) { @Uninterruptible(reason = "Used in locking without transition, so the whole critical section must be uninterruptible.", callerMustBe = true) public void unlock() { + if (writeOwnerTid < 0) { + // Readers own the lock. + long readerCount = ownerCount.decrementAndGet(); + assert readerCount >= 0; + return; + } + // A writer owns the lock. + assert isCurrentThreadWriteOwner(); writeOwnerTid = -1; ownerCount.set(0); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean isWriteOwner() { + public boolean isCurrentThreadWriteOwner() { return writeOwnerTid == SubstrateJVM.getCurrentThreadId(); } } From 7e2cb91f41d6c6997a980f04389efd93eb5a62a5 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 1 Nov 2023 13:16:52 -0400 Subject: [PATCH 28/34] minor clean up. Use jdk.graal.compiler --- .../src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java | 2 +- .../src/com/oracle/svm/test/jfr/TestThrottler.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java index 3e5e217a980c..69b33d751f18 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java @@ -28,7 +28,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils; -import org.graalvm.compiler.nodes.PauseNode; +import jdk.graal.compiler.nodes.PauseNode; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.jfr.SubstrateJVM; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 6f58ab183144..a4b9b756bec0 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -32,6 +32,7 @@ import com.oracle.svm.core.jfr.JfrEvent; import com.oracle.svm.core.jfr.JfrThrottler; import com.oracle.svm.core.jfr.JfrThrottlerWindow; +import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.UnsignedUtils; import jdk.jfr.Recording; import org.junit.Test; @@ -433,7 +434,7 @@ public long getEventSampleSize() { } public void expireActiveWindow() { - if (eventSampleSize <= LOW_RATE_UPPER_BOUND || periodNs > com.oracle.svm.core.util.TimeUtils.nanosPerSecond) { + if (eventSampleSize <= LOW_RATE_UPPER_BOUND || periodNs > TimeUtils.nanosPerSecond) { window0().currentTestNanos += periodNs; window1().currentTestNanos += periodNs; } From b0960fec85f3f71f45ed97e3066635601a1226f2 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 13 Nov 2023 09:40:39 -0500 Subject: [PATCH 29/34] fix conflicts style --- .../src/com/oracle/svm/core/jfr/SubstrateJVM.java | 11 ----------- 1 file changed, 11 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 1cb93d5bdcb8..c8213b32b7a8 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 @@ -197,17 +197,6 @@ public static JfrRandom getJfrRandom() { return get().jfrRandom; } - public static Object getHandler(Class eventClass) { - try { - Field f = eventClass.getDeclaredField("eventHandler"); - f.setAccessible(true); - return f.get(null); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { - throw new InternalError("Could not access event handler"); - } - } - - @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) public boolean isRecording() { return recording; From 8a64fd656cc8afd1b93a42a8c0e20e9e83bbf6c9 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 4 Dec 2023 12:52:33 -0500 Subject: [PATCH 30/34] comment --- .../src/com/oracle/svm/test/jfr/TestThrottler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index a4b9b756bec0..4a146ef913ff 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -249,7 +249,7 @@ public void testZeroRate() throws Throwable { throttler.setThrottle(10, 2 * SECOND_IN_MS); assertTrue(throttler.sample()); - // Test applying throttling settings to an in-progress recording + // Test applying throttling settings to an actual recording Recording recording = new Recording(); recording.setDestination(createTempJfrFile()); recording.enable(JfrEvent.ObjectAllocationSample.getName()).with("throttle", "0/s"); @@ -261,7 +261,8 @@ public void testZeroRate() throws Throwable { recording.stop(); recording.close(); - assertTrue(getEvents(recording.getDestination(), new String[]{JfrEvent.ObjectAllocationSample.getName()}).size() == 0); + // Call getEvents directly because we expect zero events (which ordinarily would result in failure). + assertTrue(getEvents(recording.getDestination(), new String[]{JfrEvent.ObjectAllocationSample.getName()}, true).size() == 0); } @NeverInline("Prevent escape analysis.") From 6db1df2a0ba72a0ab3fded02e8269d47dcd43574 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 4 Dec 2023 14:19:06 -0500 Subject: [PATCH 31/34] style --- .../src/com/oracle/svm/test/jfr/TestThrottler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index 4a146ef913ff..c2ff45da8f9f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -261,7 +261,8 @@ public void testZeroRate() throws Throwable { recording.stop(); recording.close(); - // Call getEvents directly because we expect zero events (which ordinarily would result in failure). + // Call getEvents directly because we expect zero events (which ordinarily would result in + // failure). assertTrue(getEvents(recording.getDestination(), new String[]{JfrEvent.ObjectAllocationSample.getName()}, true).size() == 0); } From 8db8411f60dbc3e13192d61b1225e7317ed78051 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 5 Feb 2024 17:01:52 +0100 Subject: [PATCH 32/34] Cleanups and fixes. --- substratevm/CHANGELOG.md | 2 +- .../genscavenge/ThreadLocalAllocation.java | 4 +- .../svm/core/collections/EnumBitmask.java | 54 +++ .../svm/core/jdk/UninterruptibleUtils.java | 9 +- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 123 ++--- .../com/oracle/svm/core/jfr/JfrManager.java | 5 +- .../com/oracle/svm/core/jfr/JfrThrottler.java | 269 ----------- .../svm/core/jfr/JfrThrottlerWindow.java | 137 ------ .../src/com/oracle/svm/core/jfr/JfrTicks.java | 10 + .../com/oracle/svm/core/jfr/SubstrateJVM.java | 63 +-- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 1 + .../Target_jdk_jfr_internal_JVM_JDK21.java | 1 + .../core/jfr/events/JfrAllocationEvents.java | 34 +- .../jfr/oldobject/JfrOldObjectProfiler.java | 2 +- .../jfr/oldobject/JfrOldObjectSampler.java | 2 +- .../jfr/throttling/JfrAdaptiveSampler.java | 201 ++++++++ .../jfr/throttling/JfrEventThrottler.java | 208 ++++++++ .../jfr/throttling/JfrEventThrottling.java | 61 +++ .../core/jfr/throttling/JfrSamplerParams.java | 51 ++ .../core/jfr/throttling/JfrSamplerWindow.java | 128 +++++ ...jfr_internal_settings_ThrottleSetting.java | 4 +- .../oracle/svm/core/jfr/utils/JfrRandom.java | 70 +-- .../svm/core/jfr/utils/JfrReadWriteLock.java | 145 ------ .../svm/core/option/RuntimeOptionKey.java | 27 +- .../svm/core/thread/PlatformThreads.java | 7 +- .../oracle/svm/test/jfr/TestThrottler.java | 457 +++++++----------- .../test/jfr/oldobject/JfrOldObjectTest.java | 2 +- .../jfr/oldobject/TestOldObjectProfiler.java | 2 +- 28 files changed, 1034 insertions(+), 1045 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/EnumBitmask.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottler.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottling.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerParams.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerWindow.java rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/{ => throttling}/Target_jdk_jfr_internal_settings_ThrottleSetting.java (94%) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index c732d2cff6b9..2ae417640d97 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -6,12 +6,12 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-51106) Fields that are accessed via a `VarHandle` or `MethodHandle` are no longer marked as "unsafe accessed" when the `VarHandle`/`MethodHandle` can be fully intrinsified. * (GR-49996) Ensure explicitly set image name (e.g., via `-o imagename`) is not accidentally overwritten by `-jar jarfile` option. * (GR-48683) Together with Red Hat, we added partial support for the JFR event `OldObjectSample`. +* (GR-47109) JFR event throttling support was added, along with the `ObjectAllocationSample` event. ## GraalVM for JDK 22 (Internal Version 24.0.0) * (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics. * (GR-48343) Red Hat added support for the JFR events AllocationRequiringGC and SystemGC. * (GR-48612) Enable `--strict-image-heap` by default. The option is now deprecated and can be removed from your argument list. A blog post with more information will follow shortly. -* (GR-47109) JFR event throttling support was added, along with the jdk.ObjectAllocationSample event. * (GR-48354) Remove native-image-agent legacy `build`-option * (GR-49221) Support for thread dumps can now be enabled with `--enable-monitoring=threaddump`. The option `-H:±DumpThreadStacksOnSignal` is deprecated and marked for removal. * (GR-48579) Options ParseOnce, ParseOnceJIT, and InlineBeforeAnalysis are deprecated and no longer have any effect. diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java index 09cf000bea69..2fd6543ca1e1 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -59,8 +59,8 @@ import com.oracle.svm.core.hub.LayoutEncoding; import com.oracle.svm.core.jfr.HasJfrSupport; import com.oracle.svm.core.jfr.JfrTicks; -import com.oracle.svm.core.jfr.events.JfrAllocationEvents; import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.jfr.events.JfrAllocationEvents; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; @@ -532,7 +532,7 @@ private static Descriptor retireCurrentAllocationChunk(IsolateThread thread) { private static void sampleSlowPathAllocation(Object obj, UnsignedWord allocatedSize, int arrayLength) { if (HasJfrSupport.get()) { - SubstrateJVM.getJfrOldObjectProfiler().sample(obj, allocatedSize, arrayLength); + SubstrateJVM.getOldObjectProfiler().sample(obj, allocatedSize, arrayLength); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/EnumBitmask.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/EnumBitmask.java new file mode 100644 index 000000000000..974be4545dee --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/EnumBitmask.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, 2024, 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.collections; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; + +public final class EnumBitmask { + private EnumBitmask() { + } + + public static int computeBitmask(Enum[] flags) { + int result = 0; + for (Enum flag : flags) { + assert flag.ordinal() <= Integer.SIZE - 1; + result |= flagBit(flag); + } + return result; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static boolean hasBit(int bitmask, Enum flag) { + return (bitmask & flagBit(flag)) != 0; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static int flagBit(Enum flag) { + assert flag.ordinal() < 32; + return 1 << flag.ordinal(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index 99b7e81531c9..2645c1f7cab0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -440,13 +440,14 @@ public static long abs(long a) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static long floor(double a) { - return (long) a; + public static long floorToLong(double value) { + assert value == value : "must not be NaN"; + return (long) value; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static long ceil(double a) { - long floor = floor(a); + public static long ceilToLong(double a) { + long floor = floorToLong(a); return a > floor ? floor + 1 : floor; } } 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 b23717be2945..44e4de3e90e6 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,114 +24,115 @@ */ package com.oracle.svm.core.jfr; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.collections.EnumBitmask; import com.oracle.svm.core.thread.JavaThreads; -import java.util.ArrayList; -import java.util.List; - /** * 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", false, false); - public static final JfrEvent ThreadEnd = create("jdk.ThreadEnd", false, false); - public static final JfrEvent ThreadCPULoad = create("jdk.ThreadCPULoad", false, false); - public static final JfrEvent DataLoss = create("jdk.DataLoss", false, false); - public static final JfrEvent ClassLoadingStatistics = create("jdk.ClassLoadingStatistics", false, false); - public static final JfrEvent InitialEnvironmentVariable = create("jdk.InitialEnvironmentVariable", false, false); - public static final JfrEvent InitialSystemProperty = create("jdk.InitialSystemProperty", false, false); - public static final JfrEvent JavaThreadStatistics = create("jdk.JavaThreadStatistics", false, false); - public static final JfrEvent JVMInformation = create("jdk.JVMInformation", false, false); - public static final JfrEvent OSInformation = create("jdk.OSInformation", false, false); - public static final JfrEvent PhysicalMemory = create("jdk.PhysicalMemory", false, false); - public static final JfrEvent ExecutionSample = create("jdk.ExecutionSample", false, false); - public static final JfrEvent NativeMethodSample = create("jdk.NativeMethodSample", false, false); - public static final JfrEvent GarbageCollection = create("jdk.GarbageCollection", true, false); - public static final JfrEvent GCPhasePause = create("jdk.GCPhasePause", true, false); - public static final JfrEvent GCPhasePauseLevel1 = create("jdk.GCPhasePauseLevel1", true, false); - public static final JfrEvent GCPhasePauseLevel2 = create("jdk.GCPhasePauseLevel2", true, false); - public static final JfrEvent GCPhasePauseLevel3 = create("jdk.GCPhasePauseLevel3", true, false); - public static final JfrEvent GCPhasePauseLevel4 = create("jdk.GCPhasePauseLevel4", true, false); - public static final JfrEvent SafepointBegin = create("jdk.SafepointBegin", true, false); - public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd", true, false); - public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation", true, false); - public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter", true, false); - public static final JfrEvent ThreadPark = create("jdk.ThreadPark", true, false); - public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait", true, false); - public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate", true, false); - public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB", false, false); - public static final JfrEvent GCHeapSummary = create("jdk.GCHeapSummary", false, false); - public static final JfrEvent ThreadAllocationStatistics = create("jdk.ThreadAllocationStatistics", false, false); - public static final JfrEvent SystemGC = create("jdk.SystemGC", true, false); - public static final JfrEvent OldObjectSample = create("jdk.OldObjectSample", false); - public static final JfrEvent AllocationRequiringGC = create("jdk.AllocationRequiringGC", false, false, false); - public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample", false, true, true); + public static final JfrEvent ThreadStart = create("jdk.ThreadStart"); + public static final JfrEvent ThreadEnd = create("jdk.ThreadEnd"); + public static final JfrEvent ThreadCPULoad = create("jdk.ThreadCPULoad"); + public static final JfrEvent DataLoss = create("jdk.DataLoss"); + public static final JfrEvent ClassLoadingStatistics = create("jdk.ClassLoadingStatistics"); + public static final JfrEvent InitialEnvironmentVariable = create("jdk.InitialEnvironmentVariable"); + public static final JfrEvent InitialSystemProperty = create("jdk.InitialSystemProperty"); + public static final JfrEvent JavaThreadStatistics = create("jdk.JavaThreadStatistics"); + public static final JfrEvent JVMInformation = create("jdk.JVMInformation"); + public static final JfrEvent OSInformation = create("jdk.OSInformation"); + public static final JfrEvent PhysicalMemory = create("jdk.PhysicalMemory"); + public static final JfrEvent ExecutionSample = create("jdk.ExecutionSample"); + public static final JfrEvent NativeMethodSample = create("jdk.NativeMethodSample"); + public static final JfrEvent GarbageCollection = create("jdk.GarbageCollection", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePause = create("jdk.GCPhasePause", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePauseLevel1 = create("jdk.GCPhasePauseLevel1", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePauseLevel2 = create("jdk.GCPhasePauseLevel2", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePauseLevel3 = create("jdk.GCPhasePauseLevel3", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePauseLevel4 = create("jdk.GCPhasePauseLevel4", JfrEventFlags.HasDuration); + public static final JfrEvent SafepointBegin = create("jdk.SafepointBegin", JfrEventFlags.HasDuration); + public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd", JfrEventFlags.HasDuration); + public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation", JfrEventFlags.HasDuration); + public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter", JfrEventFlags.HasDuration); + public static final JfrEvent ThreadPark = create("jdk.ThreadPark", JfrEventFlags.HasDuration); + public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait", JfrEventFlags.HasDuration); + public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate", JfrEventFlags.HasDuration); + public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB"); + public static final JfrEvent GCHeapSummary = create("jdk.GCHeapSummary"); + public static final JfrEvent ThreadAllocationStatistics = create("jdk.ThreadAllocationStatistics"); + public static final JfrEvent SystemGC = create("jdk.SystemGC", JfrEventFlags.HasDuration); + public static final JfrEvent AllocationRequiringGC = create("jdk.AllocationRequiringGC"); + public static final JfrEvent OldObjectSample = create("jdk.OldObjectSample"); + public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample", JfrEventFlags.SupportsThrottling); private final long id; private final String name; - private final boolean hasDuration; - private JfrThrottler throttler; + private final int flags; @Platforms(Platform.HOSTED_ONLY.class) - public static JfrEvent create(String name, boolean hasDuration, boolean hasThrottling) { - return new JfrEvent(name, hasDuration, hasThrottling); + public static JfrEvent create(String name, JfrEventFlags... flags) { + return new JfrEvent(name, flags); } @Platforms(Platform.HOSTED_ONLY.class) - private JfrEvent(String name, boolean hasDuration, boolean hasThrottling) { + private JfrEvent(String name, JfrEventFlags... flags) { this.id = JfrMetadataTypeLibrary.lookupPlatformEvent(name); this.name = name; - this.hasDuration = hasDuration; - if (hasThrottling) { - throttler = new JfrThrottler(); - } - events.add(this); - } - - public static List getEvents() { - return events; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public JfrThrottler getThrottler() { - return throttler; + this.flags = EnumBitmask.computeBitmask(flags); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getId() { return id; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public String getName() { return name; } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private boolean hasDuration() { + return EnumBitmask.hasBit(flags, JfrEventFlags.HasDuration); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean supportsThrottling() { + return EnumBitmask.hasBit(flags, JfrEventFlags.SupportsThrottling); + } + @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) public boolean shouldEmit() { - assert !hasDuration; + assert !hasDuration(); return shouldEmit0() && !JfrThreadLocal.isThreadExcluded(JavaThreads.getCurrentThreadOrNull()); } @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) public boolean shouldEmit(Thread thread) { - assert !hasDuration; + assert !hasDuration(); return shouldEmit0() && !JfrThreadLocal.isThreadExcluded(thread); } @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) public boolean shouldEmit(long durationTicks) { - assert hasDuration; + assert hasDuration(); return shouldEmit0() && durationTicks >= SubstrateJVM.get().getThresholdTicks(this) && !JfrThreadLocal.isThreadExcluded(JavaThreads.getCurrentThreadOrNull()); } @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) private boolean shouldEmit0() { - return SubstrateJVM.get().shouldCommit(this) && SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this); + return SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this) && SubstrateJVM.getEventThrottling().shouldCommit(this); + } + + private enum JfrEventFlags { + HasDuration, + SupportsThrottling } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java index 60b064a640b7..045de0e9059d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java @@ -93,7 +93,6 @@ public static RuntimeSupport.Hook initializationHook() { public static RuntimeSupport.Hook startupHook() { return isFirstIsolate -> { periodicEventSetup(); - SubstrateJVM.getJfrRandom().resetSeed(); boolean startRecording = SubstrateOptions.FlightRecorder.getValue() || !SubstrateOptions.StartFlightRecording.getValue().isEmpty(); if (startRecording) { @@ -127,7 +126,7 @@ private static void parseFlightRecorderOptions() { if (oldObjectQueueSize != null) { if (oldObjectQueueSize >= 0) { - SubstrateJVM.getJfrOldObjectProfiler().configure(oldObjectQueueSize); + SubstrateJVM.getOldObjectProfiler().configure(oldObjectQueueSize); } else { throw argumentParsingFailed(FlightRecorderOptionsArgument.OldObjectQueueSize.getCmdLineKey() + " must be greater or equal 0."); } @@ -168,7 +167,7 @@ public static RuntimeSupport.Hook shutdownHook() { private static void parseFlightRecorderLogging() { String option = SubstrateOptions.FlightRecorderLogging.getValue(); - SubstrateJVM.getJfrLogging().parseConfiguration(option); + SubstrateJVM.getLogging().parseConfiguration(option); } private static void periodicEventSetup() throws SecurityException { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java deleted file mode 100644 index d67327f2187d..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottler.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.core.jfr; - -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.util.TimeUtils; -import com.oracle.svm.core.jfr.utils.JfrReadWriteLock; - -/** - * Each event that allows throttling should have its own throttler instance. Multiple threads may - * use the same throttler instance when emitting a particular JFR event type. The throttler uses a - * rotating window scheme where each window represents a time slice. The data from the current - * window is used to set the parameters of the next window. The active window is guaranteed not to - * change while there are threads using it for sampling. - * - * This class is based on JfrAdaptiveSampler in hotspot/share/jfr/support/jfrAdaptiveSampler.cpp and - * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp. Commit - * hash:1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817. Openjdk version "22-internal". - */ -public class JfrThrottler { - private final JfrReadWriteLock rwlock; - private volatile boolean disabled; - protected volatile JfrThrottlerWindow activeWindow; - - // The following are set to match the values in OpenJDK - protected static final int WINDOW_DIVISOR = 5; - protected static final int LOW_RATE_UPPER_BOUND = 9; - protected JfrThrottlerWindow window0; - protected JfrThrottlerWindow window1; - - // The following fields are only be accessed by threads holding the writer lock - private double ewmaPopulationSizeAlpha = 0; - private boolean reconfigure; - private long accumulatedDebtCarryLimit; - private long accumulatedDebtCarryCount; - protected long periodNs; - protected long eventSampleSize; - protected double avgPopulationSize = 0; - - public JfrThrottler() { - reconfigure = false; - disabled = true; - window0 = new JfrThrottlerWindow(); - window1 = new JfrThrottlerWindow(); - activeWindow = window0; - rwlock = new JfrReadWriteLock(); - } - - /** - * Convert rate to samples/second if possible. Want to avoid a long period with large windows - * with a large number of samples per window in favor of many smaller windows. This is in the - * critical section because setting the sample size and period must be done together atomically. - * Otherwise, we risk a window's params being set with only one of the two updated. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void normalize(long samplesPerPeriod, double periodMs) { - assert rwlock.isCurrentThreadWriteOwner(); - // Do we want more than 10samples/s ? If so convert to samples/s - double periodsPerSecond = 1000.0 / periodMs; - double samplesPerSecond = samplesPerPeriod * periodsPerSecond; - if (samplesPerSecond > LOW_RATE_UPPER_BOUND && periodMs > TimeUtils.millisPerSecond) { - this.periodNs = TimeUtils.nanosPerSecond; - this.eventSampleSize = (long) samplesPerSecond; - return; - } - - this.eventSampleSize = samplesPerPeriod; - this.periodNs = TimeUtils.millisToNanos((long) periodMs); - } - - @Uninterruptible(reason = "Avoid deadlock due to locking without transition.") - public void setThrottle(long eventSampleSize, long periodMs) { - if (eventSampleSize == Target_jdk_jfr_internal_settings_ThrottleSetting.OFF) { - disabled = true; - return; - } else if (eventSampleSize < 0 || periodMs < 0) { - return; - } - - // Blocking lock because new settings MUST be applied. - rwlock.writeLockNoTransition(); - try { - normalize(eventSampleSize, periodMs); - reconfigure = true; - rotateWindow(); - } finally { - rwlock.unlock(); - } - disabled = false; - } - - /** - * Acquiring the reader lock before using the active window prevents the active window from - * changing while sampling is in progress. If we encounter an expired window, there's no point - * in sampling, so the reader lock can be returned. An expired window should be rotated. The - * writer lock must be acquired before attempting to rotate. Once an expired window is detected, - * it is likely, but not guaranteed, to be rotated before any NEW threads (readers) are allowed - * to begin sampling. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean sample() { - if (disabled) { - return true; - } - // New readers will block here if there is a writer waiting for the lock. - rwlock.readLockNoTransition(); - try { - boolean expired = activeWindow.isExpired(); - if (expired) { - rwlock.unlock(); - rwlock.writeLockNoTransition(); - /* - * Once in the critical section, ensure the active window is still expired. Another - * thread may have already handled the expired window, or new settings may have - * already triggered a rotation. - */ - if (activeWindow.isExpired()) { - rotateWindow(); - } - return false; - } - return activeWindow.sample(); - } finally { - rwlock.unlock(); - } - } - - /** - * Only one thread should be rotating at once. If rotating due to an expired window, then other - * threads that try to rotate due to expiry, will simply return false. If there's a race with a - * thread updating settings, then one will just have to wait for the other to finish. Order - * doesn't really matter as long as they are not interrupted. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void rotateWindow() { - assert rwlock.isCurrentThreadWriteOwner(); - configure(); - installNextWindow(); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void installNextWindow() { - assert rwlock.isCurrentThreadWriteOwner(); - activeWindow = getNextWindow(); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private JfrThrottlerWindow getNextWindow() { - assert rwlock.isCurrentThreadWriteOwner(); - if (window0 == activeWindow) { - return window1; - } - return window0; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private long computeAccumulatedDebtCarryLimit(long windowDurationNs) { - assert rwlock.isCurrentThreadWriteOwner(); - if (periodNs == 0 || windowDurationNs >= TimeUtils.nanosPerSecond) { - return 1; - } - return TimeUtils.nanosPerSecond / windowDurationNs; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private long amortizeDebt(JfrThrottlerWindow lastWindow) { - assert rwlock.isCurrentThreadWriteOwner(); - if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { - accumulatedDebtCarryCount = 1; - return 0; // reset because new settings have been applied - } - accumulatedDebtCarryCount++; - // Did we sample less than we were supposed to? - return lastWindow.samplesExpected() - lastWindow.samplesTaken(); - } - - /** - * Handles the case where the sampling rate is very low. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void setSamplePointsAndWindowDuration() { - assert rwlock.isCurrentThreadWriteOwner(); - assert reconfigure; - JfrThrottlerWindow next = getNextWindow(); - long samplesPerWindow = eventSampleSize / WINDOW_DIVISOR; - long windowDurationNs = periodNs / WINDOW_DIVISOR; - /* - * If period isn't 1s, then we're effectively taking under 10 samples/s because the values - * have already undergone normalization. - */ - if (eventSampleSize <= LOW_RATE_UPPER_BOUND || periodNs > TimeUtils.nanosPerSecond) { - samplesPerWindow = eventSampleSize; - windowDurationNs = periodNs; - } - activeWindow.samplesPerWindow = samplesPerWindow; - activeWindow.windowDurationNs = windowDurationNs; - next.samplesPerWindow = samplesPerWindow; - next.windowDurationNs = windowDurationNs; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void configure() { - assert rwlock.isCurrentThreadWriteOwner(); - JfrThrottlerWindow next = getNextWindow(); - - // Store updated parameters to both windows. - if (reconfigure) { - setSamplePointsAndWindowDuration(); - accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(next.windowDurationNs); - // This effectively means we reset the debt count upon reconfiguration - accumulatedDebtCarryCount = accumulatedDebtCarryLimit; - avgPopulationSize = 0; - ewmaPopulationSizeAlpha = 1.0 / windowLookback(next); - reconfigure = false; - } - - next.configure(amortizeDebt(activeWindow), projectPopulationSize(activeWindow.measuredPopSize.get())); - } - - /** - * The lookback values are set to match the values in OpenJDK. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected static double windowLookback(JfrThrottlerWindow window) { - if (window.windowDurationNs <= TimeUtils.nanosPerSecond) { - return 25.0; - } else if (window.windowDurationNs <= TimeUtils.nanosPerSecond * 60L) { - return 5.0; - } else { - return 1.0; - } - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private double projectPopulationSize(long lastWindowMeasuredPop) { - assert rwlock.isCurrentThreadWriteOwner(); - avgPopulationSize = exponentiallyWeightedMovingAverage(lastWindowMeasuredPop, ewmaPopulationSizeAlpha, avgPopulationSize); - return avgPopulationSize; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static double exponentiallyWeightedMovingAverage(double currentMeasurement, double alpha, double prevEwma) { - return alpha * currentMeasurement + (1 - alpha) * prevEwma; - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java deleted file mode 100644 index ecaa01e4195c..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThrottlerWindow.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.jfr; - -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.jdk.UninterruptibleUtils; - -import static java.lang.Math.log; - -/** - * This class is based on JfrSamplerWindow in hotspot/share/jfr/support/jfrAdaptiveSampler.cpp and - * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp. Commit hash: - * 1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817. Openjdk version "22-internal". - */ -public class JfrThrottlerWindow { - // reset every rotation - public UninterruptibleUtils.AtomicLong measuredPopSize; - public UninterruptibleUtils.AtomicLong endTicks; - - // Calculated every rotation based on params set by user and results from previous windows - public volatile long samplingInterval; - public volatile double maxSampleablePopulation; - - // Set by user - public long samplesPerWindow; - public long windowDurationNs; - public long debt; - - public JfrThrottlerWindow() { - windowDurationNs = 0; - samplesPerWindow = 0; - maxSampleablePopulation = 0; - measuredPopSize = new UninterruptibleUtils.AtomicLong(0); - endTicks = new UninterruptibleUtils.AtomicLong(JfrTicks.currentTimeNanos() + windowDurationNs); - samplingInterval = 1; - debt = 0; - } - - /** - * The reader lock must be acquired here. The active window will not change while in this - * method. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean sample() { - // Guarantees only one thread can record the last event of the window - long prevMeasuredPopSize = measuredPopSize.getAndIncrement(); - - // Stop sampling if we're already over maxSampleablePopulation and we're over the expected - // samples per window. - return prevMeasuredPopSize % samplingInterval == 0 && - (prevMeasuredPopSize < maxSampleablePopulation); - } - - /** Thread's calling this method should have acquired the JftThrottler writer lcok. */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public long samplesTaken() { - if (measuredPopSize.get() > maxSampleablePopulation) { - return samplesExpected(); - } - return measuredPopSize.get() / samplingInterval; - } - - /** Thread's calling this method should have acquired the JftThrottler writer lock. */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public long samplesExpected() { - return samplesPerWindow + debt; - } - - /** Thread's calling this method should have acquired the JftThrottler writer lock. */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void configure(long newDebt, double projectedPopSize) { - this.debt = newDebt; - if (projectedPopSize <= samplesExpected()) { - samplingInterval = 1; - } else { - - double projectedProbability = samplesExpected() / projectedPopSize; - samplingInterval = nextGeometric(projectedProbability, SubstrateJVM.getNextRandomUniform()); - } - - this.maxSampleablePopulation = samplesExpected() * samplingInterval; - - // reset - measuredPopSize.set(0); - advanceEndTicks(); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected void advanceEndTicks() { - endTicks.set(System.nanoTime() + windowDurationNs); - } - - /** - * This method is essentially the same as jfrAdaptiveSampler::next_geometric(double, double) in - * the OpenJDK. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static long nextGeometric(double probability, double u) { - double randomVar = u; - if (randomVar == 0.0) { - randomVar = 0.01; - } - // Inverse CDF for the geometric distribution. - return UninterruptibleUtils.Math.ceil(log(1.0 - randomVar) / log(1.0 - probability)); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean isExpired() { - if (System.nanoTime() >= endTicks.get()) { - return true; - } - return false; - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTicks.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTicks.java index 23393e470f3d..0c012bfb21e6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTicks.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTicks.java @@ -52,6 +52,11 @@ public static long elapsedTicks() { return 0; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long now() { + return System.nanoTime(); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long duration(long startTicks) { return elapsedTicks() - startTicks; @@ -61,6 +66,11 @@ public static long getTicksFrequency() { return TimeUtils.nanosPerSecond; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long millisToTicks(long millis) { + return TimeUtils.millisToNanos(millis); + } + public static long currentTimeNanos() { return TimeUtils.millisToNanos(System.currentTimeMillis()); } 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 797de225cac1..73a8ebd1b2b1 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 @@ -41,7 +41,7 @@ import com.oracle.svm.core.jfr.oldobject.JfrOldObjectProfiler; import com.oracle.svm.core.jfr.oldobject.JfrOldObjectRepository; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; -import com.oracle.svm.core.jfr.utils.JfrRandom; +import com.oracle.svm.core.jfr.throttling.JfrEventThrottling; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.sampler.SamplerBufferPool; import com.oracle.svm.core.sampler.SubstrateSigprofHandler; @@ -88,8 +88,9 @@ public class SubstrateJVM { private final JfrUnlockedChunkWriter unlockedChunkWriter; private final JfrRecorderThread recorderThread; private final JfrOldObjectProfiler oldObjectProfiler; + private final JfrLogging jfrLogging; - private final JfrRandom jfrRandom; + private final JfrEventThrottling eventThrottler; private boolean initialized; /* @@ -127,7 +128,7 @@ public SubstrateJVM(List configurations, boolean writeFile) { oldObjectProfiler = new JfrOldObjectProfiler(); jfrLogging = new JfrLogging(); - jfrRandom = new JfrRandom(); + eventThrottler = new JfrEventThrottling(); initialized = false; recording = false; @@ -194,31 +195,27 @@ public static JfrStackTraceRepository getStackTraceRepo() { } @Fold - public static JfrLogging getJfrLogging() { + public static JfrLogging getLogging() { return get().jfrLogging; } @Fold - public static JfrOldObjectProfiler getJfrOldObjectProfiler() { + public static JfrOldObjectProfiler getOldObjectProfiler() { return get().oldObjectProfiler; } @Fold - public static JfrOldObjectRepository getJfrOldObjectRepository() { + public static JfrOldObjectRepository getOldObjectRepository() { return get().oldObjectRepo; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static double getNextRandomUniform() { - return get().jfrRandom.nextUniform(); - } - - public static JfrRandom getJfrRandom() { - return get().jfrRandom; + @Fold + public static JfrEventThrottling getEventThrottling() { + return get().eventThrottler; } @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) - public boolean isRecording() { + protected boolean isRecording() { return recording; } @@ -685,28 +682,6 @@ public boolean isEnabled(JfrEvent event) { return eventSettings[(int) event.getId()].isEnabled(); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean shouldCommit(JfrEvent event) { - JfrThrottler throttler = event.getThrottler(); - if (throttler != null) { - return throttler.sample(); - } - return true; - } - - public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { - for (JfrEvent event : JfrEvent.getEvents()) { - if (eventTypeId == event.getId()) { - JfrThrottler throttler = event.getThrottler(); - if (throttler != null) { - throttler.setThrottle(eventSampleSize, periodMs); - break; - } - } - } - return true; - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void setLarge(JfrEvent event, boolean large) { eventSettings[(int) event.getId()].setLarge(large); @@ -717,6 +692,13 @@ public boolean isLarge(JfrEvent event) { return eventSettings[(int) event.getId()].isLarge(); } + /** + * See {@link JVM#setThrottle}. + */ + public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { + return eventThrottler.setThrottle(eventTypeId, eventSampleSize, periodMs); + } + /** * See {@link JVM#setThreshold}. */ @@ -754,11 +736,8 @@ private static class JfrBeginRecordingOperation extends JavaVMOperation { @Override protected void operate() { - SubstrateJVM.getJfrOldObjectProfiler().initialize(); - - for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { - JfrAllocationEvents.resetLastAllocationSize(isolateThread); - } + SubstrateJVM.getOldObjectProfiler().reset(); + JfrAllocationEvents.reset(); SubstrateJVM.get().recording = true; /* Recording is enabled, so JFR events can be triggered at any time. */ @@ -799,7 +778,7 @@ protected void operate() { SubstrateJVM.getThreadLocal().teardown(); SubstrateJVM.getSamplerBufferPool().teardown(); SubstrateJVM.getGlobalMemory().clear(); - SubstrateJVM.getJfrOldObjectProfiler().teardown(); + SubstrateJVM.getOldObjectProfiler().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 fe11c575b129..69d39956f355 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 @@ -437,6 +437,7 @@ public static boolean setCutoff(long eventTypeId, long cutoffTicks) { return SubstrateJVM.get().setCutoff(eventTypeId, cutoffTicks); } + /** See {@link JVM#setThrottle}. */ @Substitute @TargetElement(onlyWith = JDK22OrLater.class) public static boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM_JDK21.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM_JDK21.java index d0c088f3da80..66009899f0a4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM_JDK21.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM_JDK21.java @@ -286,6 +286,7 @@ public boolean setCutoff(long eventTypeId, long cutoffTicks) { return SubstrateJVM.get().setCutoff(eventTypeId, cutoffTicks); } + /** See {@link JVM#setThrottle}. */ @Substitute public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { return SubstrateJVM.get().setThrottle(eventTypeId, eventSampleSize, periodMs); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java index 47e6855b6ced..28a4ca1f6df5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java @@ -38,28 +38,29 @@ import com.oracle.svm.core.jfr.JfrNativeEventWriterData; import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalLong; -import com.oracle.svm.core.thread.PlatformThreads; -import com.oracle.svm.core.thread.JavaThreads; public class JfrAllocationEvents { - private static final FastThreadLocalLong lastAllocationSize = FastThreadLocalFactory.createLong("ObjectAllocationSampleEvent.lastAllocationSize"); + private static final FastThreadLocalLong lastThreadAllocatedBytes = FastThreadLocalFactory.createLong("JfrAllocationEvents.lastThreadAllocatedBytes"); - public static void resetLastAllocationSize(IsolateThread thread) { - lastAllocationSize.set(thread, 0); + public static void reset() { + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + lastThreadAllocatedBytes.set(isolateThread, 0); + } } public static void emit(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { if (HasJfrSupport.get()) { - Class clazz = DynamicHub.toClass(hub); - emitObjectAllocationInNewTLAB(startTicks, clazz, allocationSize, tlabSize); - emitObjectAllocationSample(startTicks, clazz); + emitObjectAllocationInNewTLAB(startTicks, hub, allocationSize, tlabSize); + emitObjectAllocationSample(startTicks, hub); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emitObjectAllocationInNewTLAB(long startTicks, Class clazz, UnsignedWord allocationSize, UnsignedWord tlabSize) { + private static void emitObjectAllocationInNewTLAB(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { if (JfrEvent.ObjectAllocationInNewTLAB.shouldEmit()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); @@ -68,7 +69,7 @@ private static void emitObjectAllocationInNewTLAB(long startTicks, Class claz JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationInNewTLAB, 0)); - JfrNativeEventWriter.putClass(data, clazz); + JfrNativeEventWriter.putClass(data, DynamicHub.toClass(hub)); JfrNativeEventWriter.putLong(data, allocationSize.rawValue()); JfrNativeEventWriter.putLong(data, tlabSize.rawValue()); JfrNativeEventWriter.endSmallEvent(data); @@ -76,20 +77,23 @@ private static void emitObjectAllocationInNewTLAB(long startTicks, Class claz } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emitObjectAllocationSample(long startTicks, Class clazz) { + private static void emitObjectAllocationSample(long startTicks, DynamicHub hub) { if (JfrEvent.ObjectAllocationSample.shouldEmit()) { - long currentAllocationSize = PlatformThreads.getThreadAllocatedBytes(JavaThreads.getCurrentThreadId()); - long weight = currentAllocationSize - lastAllocationSize.get(); + long threadAllocatedBytes = PlatformThreads.getThreadAllocatedBytes(); + long weight = threadAllocatedBytes - lastThreadAllocatedBytes.get(); + assert weight > 0; + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationSample); JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); - JfrNativeEventWriter.putClass(data, clazz); + JfrNativeEventWriter.putClass(data, DynamicHub.toClass(hub)); JfrNativeEventWriter.putLong(data, weight); JfrNativeEventWriter.endSmallEvent(data); - lastAllocationSize.set(currentAllocationSize); + + lastThreadAllocatedBytes.set(threadAllocatedBytes); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java index dff7aa49896c..82c7183d4333 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java @@ -62,7 +62,7 @@ public void configure(int oldObjectQueueSize) { this.queueSize = oldObjectQueueSize; } - public void initialize() { + public void reset() { this.sampler = new JfrOldObjectSampler(queueSize); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java index b9ac595696f3..10b2ab35d942 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java @@ -171,7 +171,7 @@ private void emitUnchained() { while (cur != null) { Object obj = cur.getReferent(); if (obj != null) { - long objectId = SubstrateJVM.getJfrOldObjectRepository().serializeOldObject(obj); + long objectId = SubstrateJVM.getOldObjectRepository().serializeOldObject(obj); UnsignedWord objectSize = cur.getObjectSize(); long allocationTicks = cur.getAllocationTicks(); long threadId = cur.getThreadId(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java new file mode 100644 index 000000000000..16e4b0b54d14 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import static java.lang.Math.log; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jfr.utils.JfrRandom; +import com.oracle.svm.core.thread.JavaSpinLockUtils; +import com.oracle.svm.core.util.TimeUtils; + +import jdk.internal.misc.Unsafe; + +/** + * This class is based on the JDK 23+8 version of the HotSpot class {@code JfrAdaptiveSampler} (see + * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp). + */ +abstract class JfrAdaptiveSampler { + private static final Unsafe U = Unsafe.getUnsafe(); + protected static final long LOCK_OFFSET = U.objectFieldOffset(JfrAdaptiveSampler.class, "lock"); + private static final long ACTIVE_WINDOW_OFFSET = U.objectFieldOffset(JfrAdaptiveSampler.class, "activeWindow"); + + private final JfrRandom prng; + private final JfrSamplerWindow window0; + private final JfrSamplerWindow window1; + + @SuppressWarnings("unused") private volatile int lock; + protected JfrSamplerWindow activeWindow; + protected double avgPopulationSize; + private double ewmaPopulationSizeAlpha; + private long accumulatedDebtCarryLimit; + private long accumulatedDebtCarryCount; + + JfrAdaptiveSampler() { + prng = new JfrRandom(); + + window0 = new JfrSamplerWindow(); + window1 = new JfrSamplerWindow(); + activeWindow = window0; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected boolean sample(long timestampNs) { + boolean expired = activeWindow.isExpired(timestampNs); + if (expired) { + if (JavaSpinLockUtils.tryLock(this, LOCK_OFFSET)) { + /* Recheck under lock if the current window is still expired. */ + if (activeWindow.isExpired(timestampNs)) { + rotate(activeWindow); + } + JavaSpinLockUtils.unlock(this, LOCK_OFFSET); + } + return false; + } + + return activeWindow.sample(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected void reconfigure() { + rotate(activeWindow); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void rotate(JfrSamplerWindow expired) { + JfrSamplerWindow next = getNextWindow(expired); + JfrSamplerParams params = nextWindowParams(); + configure(params, expired, next); + + /* Install the new window atomically. */ + U.putReferenceRelease(this, ACTIVE_WINDOW_OFFSET, next); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected abstract JfrSamplerParams nextWindowParams(); + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void configure(JfrSamplerParams params, JfrSamplerWindow expired, JfrSamplerWindow next) { + if (params.reconfigure) { + expired.copyParams(params); + next.copyParams(params); + + avgPopulationSize = 0; + ewmaPopulationSizeAlpha = computeEwmaAlphaCoefficient(params.windowLookbackCount); + accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(params.windowDurationMs); + accumulatedDebtCarryCount = accumulatedDebtCarryLimit; + params.reconfigure = false; + } + setRate(params, expired, next); + next.initialize(params.windowDurationMs); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static double computeEwmaAlphaCoefficient(long lookbackCount) { + return lookbackCount <= 1 ? 1 : 1d / lookbackCount; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static long computeAccumulatedDebtCarryLimit(long windowDurationMs) { + if (windowDurationMs == 0 || windowDurationMs >= TimeUtils.millisPerSecond) { + return 1; + } + return TimeUtils.millisPerSecond / windowDurationMs; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void setRate(JfrSamplerParams params, JfrSamplerWindow expired, JfrSamplerWindow next) { + long sampleSize = projectSampleSize(params, expired); + if (sampleSize == 0) { + next.setProjectedPopulationSize(0); + return; + } + next.setSamplingInterval(deriveSamplingInterval(sampleSize, expired)); + assert next.getSamplingInterval() >= 1; + next.setProjectedPopulationSize(sampleSize * next.getSamplingInterval()); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long projectSampleSize(JfrSamplerParams params, JfrSamplerWindow expired) { + return params.samplePointsPerWindow + amortizeDebt(expired); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected long amortizeDebt(JfrSamplerWindow expired) { + long accumulatedDebt = expired.getAccumulatedDebt(); + assert accumulatedDebt <= 0; + if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { + accumulatedDebtCarryCount = 1; + return 0; + } + accumulatedDebtCarryCount++; + return -accumulatedDebt; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long deriveSamplingInterval(double sampleSize, JfrSamplerWindow expired) { + assert sampleSize > 0; + double populationSize = projectPopulationSize(expired); + if (populationSize <= sampleSize) { + return 1; + } + assert populationSize > 0; + double projectedProbability = sampleSize / populationSize; + return nextGeometric(projectedProbability); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private double projectPopulationSize(JfrSamplerWindow expired) { + avgPopulationSize = exponentiallyWeightedMovingAverage((double) expired.getPopulationSize(), ewmaPopulationSizeAlpha, avgPopulationSize); + return avgPopulationSize; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static double exponentiallyWeightedMovingAverage(double currentMeasurement, double alpha, double prevEwma) { + return alpha * currentMeasurement + (1 - alpha) * prevEwma; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long nextGeometric(double p) { + double u = prng.nextUniform(); + assert u >= 0.0; + assert u <= 1.0; + if (u == 0.0) { + u = 0.01; + } else if (u == 1.0) { + u = 0.99; + } + return UninterruptibleUtils.Math.ceilToLong(log(1.0 - u) / log(1.0 - p)); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private JfrSamplerWindow getNextWindow(JfrSamplerWindow expired) { + return expired == window0 ? window1 : window0; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottler.java new file mode 100644 index 000000000000..edd65382fd3c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottler.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.thread.JavaSpinLockUtils; +import com.oracle.svm.core.util.TimeUtils; + +/** + * Each event that allows throttling should have its own throttler instance. Multiple threads may + * use the same throttler instance when emitting a particular JFR event type. The throttler uses a + * rotating window scheme where each window represents a time slice. + * + * This class is based on the JDK 23+8 version of the HotSpot class {@code JfrEventThrottler} (see + * hotspot/share/jfr/recorder/services/jfrEventThrottler.hpp). + */ +public class JfrEventThrottler extends JfrAdaptiveSampler { + private static final long MINUTE = TimeUtils.secondsToMillis(60); + private static final long TEN_PER_1000_MS_IN_MINUTES = 600; + private static final long HOUR = 60 * MINUTE; + private static final long TEN_PER_1000_MS_IN_HOURS = 36000; + private static final long DAY = 24 * HOUR; + private static final long TEN_PER_1000_MS_IN_DAYS = 864000; + + private static final long DEFAULT_WINDOW_LOOKBACK_COUNT = 25; + private static final long LOW_RATE_UPPER_BOUND = 9; + private static final long WINDOW_DIVISOR = 5; + + private static final JfrSamplerParams DISABLED_PARAMS = new JfrSamplerParams(); + + private final JfrSamplerParams lastParams = new JfrSamplerParams(); + + private long sampleSize; + private long periodMs; + private boolean disabled; + private boolean update; + + public JfrEventThrottler() { + disabled = true; + } + + @SuppressWarnings("hiding") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + public void configure(long sampleSize, long periodMs) { + JavaSpinLockUtils.lockNoTransition(this, LOCK_OFFSET); + try { + this.sampleSize = sampleSize; + this.periodMs = periodMs; + this.update = true; + reconfigure(); + } finally { + JavaSpinLockUtils.unlock(this, LOCK_OFFSET); + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isDisabled() { + return disabled; + } + + @Override + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected JfrSamplerParams nextWindowParams() { + if (update) { + disabled = isDisabled(sampleSize); + if (!disabled) { + updateParams(); + } + } + return disabled ? DISABLED_PARAMS : lastParams; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void updateParams() { + normalize(); + setSamplePointsAndWindowDuration(lastParams, sampleSize, periodMs); + setWindowLookback(lastParams); + lastParams.reconfigure = true; + update = false; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static boolean isDisabled(long eventSampleSize) { + return eventSampleSize == Target_jdk_jfr_internal_settings_ThrottleSetting.OFF; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void normalize() { + if (periodMs == TimeUtils.millisPerSecond) { + /* Nothing to do. */ + } else if (periodMs == MINUTE) { + if (sampleSize >= TEN_PER_1000_MS_IN_MINUTES) { + sampleSize /= 60; + periodMs /= 60; + } + } else if (periodMs == HOUR) { + if (sampleSize >= TEN_PER_1000_MS_IN_HOURS) { + sampleSize /= 3600; + periodMs /= 3600; + } + } else if (sampleSize >= TEN_PER_1000_MS_IN_DAYS) { + sampleSize /= 86400; + periodMs /= 86400; + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static void setSamplePointsAndWindowDuration(JfrSamplerParams params, long sampleSize, long periodMs) { + assert sampleSize != Target_jdk_jfr_internal_settings_ThrottleSetting.OFF; + assert sampleSize >= 0; + + if (sampleSize <= LOW_RATE_UPPER_BOUND) { + setLowRate(params, sampleSize, periodMs); + } else if (periodMs == MINUTE && sampleSize < TEN_PER_1000_MS_IN_MINUTES) { + setLowRate(params, sampleSize, periodMs); + } else if (periodMs == HOUR && sampleSize < TEN_PER_1000_MS_IN_HOURS) { + setLowRate(params, sampleSize, periodMs); + } else if (periodMs == DAY && sampleSize < TEN_PER_1000_MS_IN_DAYS) { + setLowRate(params, sampleSize, periodMs); + } else { + assert periodMs % WINDOW_DIVISOR == 0; + params.samplePointsPerWindow = sampleSize / WINDOW_DIVISOR; + params.windowDurationMs = periodMs / WINDOW_DIVISOR; + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static void setLowRate(JfrSamplerParams params, long eventSampleSize, long periodMs) { + params.samplePointsPerWindow = eventSampleSize; + params.windowDurationMs = periodMs; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static void setWindowLookback(JfrSamplerParams params) { + if (params.windowDurationMs <= TimeUtils.millisPerSecond) { + params.windowLookbackCount = DEFAULT_WINDOW_LOOKBACK_COUNT; + } else if (params.windowDurationMs == MINUTE) { + params.windowLookbackCount = 5; + } else { + params.windowLookbackCount = 1; + } + } + + public static class TestingBackdoor { + public static boolean sample(JfrEventThrottler throttler) { + return throttler.sample(JfrTicks.now()); + } + + public static void expireActiveWindow(JfrEventThrottler throttler) { + JfrSamplerWindow window = getActiveWindow(throttler); + JfrSamplerWindow.TestingBackdoor.expire(window); + } + + public static long getActiveWindowAccumulatedDebt(JfrEventThrottler throttler) { + return -getActiveWindow(throttler).getAccumulatedDebt(); + } + + public static double getAveragePopulationSize(JfrEventThrottler throttler) { + return throttler.avgPopulationSize; + } + + public static long getWindowLookbackCount(JfrEventThrottler throttler) { + return throttler.lastParams.windowLookbackCount; + } + + public static long getSampleSize(JfrEventThrottler throttler) { + return throttler.sampleSize; + } + + public static long getPeriodMs(JfrEventThrottler throttler) { + return throttler.periodMs; + } + + public static long getWindowsPerPeriod() { + return WINDOW_DIVISOR; + } + + private static JfrSamplerWindow getActiveWindow(JfrEventThrottler throttler) { + return throttler.activeWindow; + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottling.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottling.java new file mode 100644 index 000000000000..6a52969b6976 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottling.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.JfrTicks; + +public class JfrEventThrottling { + private final JfrEventThrottler objectAllocationSampleEventThrottler; + + @Platforms(Platform.HOSTED_ONLY.class) + public JfrEventThrottling() { + objectAllocationSampleEventThrottler = new JfrEventThrottler(); + } + + public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { + if (eventTypeId == JfrEvent.ObjectAllocationSample.getId()) { + objectAllocationSampleEventThrottler.configure(eventSampleSize, periodMs); + } + return true; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean shouldCommit(JfrEvent event) { + if (event == JfrEvent.ObjectAllocationSample) { + return objectAllocationSampleEventThrottler.isDisabled() || objectAllocationSampleEventThrottler.sample(JfrTicks.now()); + } + + assert !event.supportsThrottling(); + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerParams.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerParams.java new file mode 100644 index 000000000000..b2ee6dda5abc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerParams.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, 2024, 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.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; + +/** + * This class is based on the JDK 23+8 version of the HotSpot struct {@code JfrSamplerParams} (see + * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp). + */ +class JfrSamplerParams { + public long samplePointsPerWindow; + public long windowDurationMs; + public long windowLookbackCount; + public boolean reconfigure; + + JfrSamplerParams() { + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void initializeFrom(JfrSamplerParams other) { + this.samplePointsPerWindow = other.samplePointsPerWindow; + this.windowDurationMs = other.windowDurationMs; + this.windowLookbackCount = other.windowLookbackCount; + this.reconfigure = other.reconfigure; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerWindow.java new file mode 100644 index 000000000000..4ff5e7bdea90 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerWindow.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jfr.JfrTicks; + +/** + * This class is based on the JDK 23+8 version of the HotSpot class {@code JfrSamplerWindow} (see + * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp). + */ +class JfrSamplerWindow { + private final JfrSamplerParams params = new JfrSamplerParams(); + private final UninterruptibleUtils.AtomicLong endTicks = new UninterruptibleUtils.AtomicLong(0); + private final UninterruptibleUtils.AtomicLong measuredPopulationSize = new UninterruptibleUtils.AtomicLong(0); + + private long samplingInterval; + private long projectedPopulationSize; + + JfrSamplerWindow() { + samplingInterval = 1; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isExpired(long timestampNs) { + return timestampNs >= endTicks.get(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void initialize(long windowDurationMs) { + assert samplingInterval >= 1; + if (windowDurationMs == 0) { + endTicks.set(0); + return; + } + measuredPopulationSize.set(0); + endTicks.set(JfrTicks.now() + JfrTicks.millisToTicks(windowDurationMs)); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void copyParams(JfrSamplerParams other) { + this.params.initializeFrom(other); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean sample() { + long ordinal = measuredPopulationSize.incrementAndGet(); + return ordinal <= projectedPopulationSize && ordinal % samplingInterval == 0; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getSamplingInterval() { + return samplingInterval; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void setSamplingInterval(long value) { + samplingInterval = value; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getPopulationSize() { + return measuredPopulationSize.get(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getProjectedPopulationSize() { + return projectedPopulationSize; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void setProjectedPopulationSize(long value) { + projectedPopulationSize = value; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getAccumulatedDebt() { + return projectedPopulationSize == 0 ? 0 : (params.samplePointsPerWindow - getMaxSampleSize()) + getDebt(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long getDebt() { + return projectedPopulationSize == 0 ? 0 : getSampleSize() - params.samplePointsPerWindow; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long getMaxSampleSize() { + return projectedPopulationSize / samplingInterval; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long getSampleSize() { + long size = getPopulationSize(); + return size > projectedPopulationSize ? getMaxSampleSize() : size / samplingInterval; + } + + static class TestingBackdoor { + public static void expire(JfrSamplerWindow window) { + window.endTicks.set(0); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/Target_jdk_jfr_internal_settings_ThrottleSetting.java similarity index 94% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/Target_jdk_jfr_internal_settings_ThrottleSetting.java index fed5c41150a3..9e41a9e103bc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_settings_ThrottleSetting.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/Target_jdk_jfr_internal_settings_ThrottleSetting.java @@ -23,11 +23,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.jfr; +package com.oracle.svm.core.jfr.throttling; import com.oracle.svm.core.annotate.Alias; - import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jfr.HasJfrSupport; @TargetClass(className = "jdk.jfr.internal.settings.ThrottleSetting", onlyWith = HasJfrSupport.class) final class Target_jdk_jfr_internal_settings_ThrottleSetting { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java index b07a3ea38487..623c2153f84c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java @@ -26,67 +26,35 @@ package com.oracle.svm.core.jfr.utils; -import org.graalvm.nativeimage.Platforms; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import java.util.concurrent.ThreadLocalRandom; + import com.oracle.svm.core.Uninterruptible; -import org.graalvm.nativeimage.Platform; -import com.oracle.svm.core.locks.VMMutex; /** - * This class is essentially the same as JfrPRNG in - * jdk/src/hotspot/shar/jfr/utilities/jfrRandom.inline.hpp in the OpenJDK. Commit hash: - * 1100dbc6b2a1f2d5c431c6f5c6eb0b9092aee817. + * This class is based on the JDK 23+8 version of the HotSpot class {@code JfrPRNG} (see + * hotspot/share/jfr/utilities/jfrRandom.inline.hpp). */ public class JfrRandom { - private static final long prngMult = 25214903917L; - private static final long prngAdd = 11; - private static final long prngModPower = 48; - private static final long modMask = (1L << prngModPower) - 1; - private volatile long random = 0; + private static final long PrngMult = 25214903917L; + private static final long PrngAdd = 11; + private static final long PrngModPower = 48; + private static final long PrngModMask = (1L << PrngModPower) - 1; + private static final double PrngDivisor = 67108864; - private VMMutex mutex; + private long random; - @Platforms(Platform.HOSTED_ONLY.class) public JfrRandom() { - mutex = new VMMutex("JfrRandom"); - } - - /** - * This is the formula for RAND48 used in unix systems (linear congruential generator). This is - * also what JFR in hotspot uses. - */ - @Uninterruptible(reason = "Locking with no transition.") - private long nextRandom() { - // Should be atomic to avoid repeated values - mutex.lockNoTransition(); - try { - if (random == 0) { - random = System.currentTimeMillis(); - } - long next = (prngMult * random + prngAdd) & modMask; - random = next; - assert random > 0; - return next; - } finally { - mutex.unlock(); - } + random = ThreadLocalRandom.current().nextLong(); } - public void resetSeed() { - mutex.lock(); - try { - random = 0; - } finally { - mutex.unlock(); - } - } - - /** This logic is essentially copied from JfrPRNG in Hotspot. */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public double nextUniform() { - long next = nextRandom(); - // Take the top 26 bits - long masked = next >> (prngModPower - 26); - // Normalize between 0 and 1 - return masked / (double) (1L << 26); + long rnd = (PrngMult * random + PrngAdd) & PrngModMask; + random = rnd; + + int value = (int) (rnd >> (PrngModPower - 26)); + return value / PrngDivisor; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java deleted file mode 100644 index 69b33d751f18..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrReadWriteLock.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.core.jfr.utils; - -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.jdk.UninterruptibleUtils; -import jdk.graal.compiler.nodes.PauseNode; -import com.oracle.svm.core.thread.VMThreads; -import com.oracle.svm.core.jfr.SubstrateJVM; - -/** An uninterruptible read-write lock implementation using atomics with writer preference. */ -public class JfrReadWriteLock { - private static final long CURRENTLY_WRITING = Long.MAX_VALUE; - private final UninterruptibleUtils.AtomicLong ownerCount; - private final UninterruptibleUtils.AtomicLong waitingWriters; - private volatile long writeOwnerTid; // If this is set, then a writer owns the lock. Otherwise - // -1. - - public JfrReadWriteLock() { - ownerCount = new UninterruptibleUtils.AtomicLong(0); - waitingWriters = new UninterruptibleUtils.AtomicLong(0); - writeOwnerTid = -1; - } - - @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public void readLockNoTransition() { - readTryLock(Integer.MAX_VALUE); - } - - @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public void writeLockNoTransition() { - writeTryLock(Integer.MAX_VALUE); - } - - /** - * The bias towards writers does NOT ensure that there are no waiting writers at the time a - * reader enters the critical section. Readers only make a best-effort check there are no - * waiting writers before they attempt to acquire the lock to prevent writer starvation. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void readTryLock(int retries) { - int yields = 0; - for (int i = 0; i < retries; i++) { - long readers = ownerCount.get(); - // Only begin the attempt to enter the critical section if no writers are waiting or - // writes are in-progress. - if (waitingWriters.get() > 0 || readers == CURRENTLY_WRITING) { - yields = maybeYield(i, yields); - } else { - // Attempt to take the lock. - if (ownerCount.compareAndSet(readers, readers + 1)) { - return; - } - } - } - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void writeTryLock(int retries) { - // Increment the writer count to signal our intent to acquire the lock. - waitingWriters.incrementAndGet(); - try { - int yields = 0; - for (int i = 0; i < retries; i++) { - long readers = ownerCount.get(); - // Only enter the critical section if all in-progress readers have finished. - if (readers != 0) { - yields = maybeYield(i, yields); - } else { - // Attempt to acquire the lock. - if (ownerCount.compareAndSet(0, CURRENTLY_WRITING)) { - writeOwnerTid = SubstrateJVM.getCurrentThreadId(); - return; - } - } - } - } finally { - // Regardless of whether we eventually acquired the lock, signal we are done waiting. - long waiters = waitingWriters.decrementAndGet(); - assert waiters >= 0; - } - } - - /** - * This is essentially the same logic as in - * {@link com.oracle.svm.core.thread.JavaSpinLockUtils#tryLock(Object, long, int)}. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static int maybeYield(int retryCount, int yields) { - if ((retryCount & 0xff) == 0 && VMThreads.singleton().supportsNativeYieldAndSleep()) { - if (yields > 5) { - VMThreads.singleton().nativeSleep(1); - } else { - VMThreads.singleton().yield(); - return yields + 1; - } - } else { - PauseNode.pause(); - } - return yields; - } - - @Uninterruptible(reason = "Used in locking without transition, so the whole critical section must be uninterruptible.", callerMustBe = true) - public void unlock() { - if (writeOwnerTid < 0) { - // Readers own the lock. - long readerCount = ownerCount.decrementAndGet(); - assert readerCount >= 0; - return; - } - // A writer owns the lock. - assert isCurrentThreadWriteOwner(); - writeOwnerTid = -1; - ownerCount.set(0); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean isCurrentThreadWriteOwner() { - return writeOwnerTid == SubstrateJVM.getCurrentThreadId(); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionKey.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionKey.java index e4de02be01d7..78c9f165bd0c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionKey.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionKey.java @@ -33,6 +33,7 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.collections.EnumBitmask; import com.oracle.svm.core.jdk.RuntimeSupport; import jdk.graal.compiler.api.replacements.Fold; @@ -48,14 +49,16 @@ public class RuntimeOptionKey extends OptionKey implements SubstrateOption private final Consumer> validation; private final int flags; + @Platforms(Platform.HOSTED_ONLY.class) public RuntimeOptionKey(T defaultValue, RuntimeOptionKeyFlag... flags) { this(defaultValue, null, flags); } + @Platforms(Platform.HOSTED_ONLY.class) public RuntimeOptionKey(T defaultValue, Consumer> validation, RuntimeOptionKeyFlag... flags) { super(defaultValue); this.validation = validation; - this.flags = computeFlags(flags); + this.flags = EnumBitmask.computeBitmask(flags); } /** @@ -104,20 +107,11 @@ public void validate() { } public boolean shouldCopyToCompilationIsolate() { - return hasFlag(RuntimeOptionKeyFlag.RelevantForCompilationIsolates); + return EnumBitmask.hasBit(flags, RuntimeOptionKeyFlag.RelevantForCompilationIsolates); } public boolean isImmutable() { - return hasFlag(RuntimeOptionKeyFlag.Immutable); - } - - private boolean hasFlag(RuntimeOptionKeyFlag flag) { - return (flags & flagBit(flag)) != 0; - } - - private static int flagBit(RuntimeOptionKeyFlag flag) { - assert flag.ordinal() < 32; - return 1 << flag.ordinal(); + return EnumBitmask.hasBit(flags, RuntimeOptionKeyFlag.Immutable); } @Fold @@ -125,15 +119,6 @@ public T getHostedValue() { return getValue(RuntimeOptionValues.singleton()); } - private static int computeFlags(RuntimeOptionKeyFlag[] flags) { - int result = 0; - for (RuntimeOptionKeyFlag flag : flags) { - assert flag.ordinal() <= Integer.SIZE - 1; - result |= flagBit(flag); - } - return result; - } - public enum RuntimeOptionKeyFlag { /** If this flag is set, then option value is propagated to all compilation isolates. */ RelevantForCompilationIsolates, 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 99c738cc318b..bff7d8c96735 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 @@ -189,12 +189,17 @@ protected PlatformThreads() { mainGroupThreadsArray[0] = mainThread; } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static long getThreadAllocatedBytes() { + return Heap.getHeap().getThreadAllocatedMemory(CurrentIsolate.getCurrentThread()); + } + @Uninterruptible(reason = "Thread locks/holds the THREAD_MUTEX.") public static long getThreadAllocatedBytes(long javaThreadId) { // Accessing the value for the current thread is fast. Thread curThread = PlatformThreads.currentThread.get(); if (curThread != null && JavaThreads.getThreadId(curThread) == javaThreadId) { - return Heap.getHeap().getThreadAllocatedMemory(CurrentIsolate.getCurrentThread()); + return getThreadAllocatedBytes(); } // If the value of another thread is accessed, then we need to do a slow lookup. diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java index c2ff45da8f9f..513363b93393 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -26,181 +26,187 @@ package com.oracle.svm.test.jfr; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntSupplier; + +import org.junit.Test; + import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.genscavenge.HeapParameters; import com.oracle.svm.core.jfr.JfrEvent; -import com.oracle.svm.core.jfr.JfrThrottler; -import com.oracle.svm.core.jfr.JfrThrottlerWindow; +import com.oracle.svm.core.jfr.throttling.JfrEventThrottler; +import com.oracle.svm.core.jfr.throttling.JfrEventThrottler.TestingBackdoor; import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.UnsignedUtils; -import jdk.jfr.Recording; -import org.junit.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import jdk.jfr.Recording; public class TestThrottler extends JfrRecordingTest { - - // Based on the hardcoded value in the throttler class. - private static final long WINDOWS_PER_PERIOD = 5; - // Arbitrary - private static final long WINDOW_DURATION_MS = 200; - private static final long SAMPLES_PER_WINDOW = 10; - private static final long SECOND_IN_MS = 1000; + private static final long WINDOWS_PER_PERIOD = TestingBackdoor.getWindowsPerPeriod(); + private static final long WINDOW_DURATION_MS = TimeUtils.secondsToMillis(3600); /** - * This is the simplest test that ensures that sampling stops after the cap is hit. Single - * thread. All sampling is done within the first window. No rotations. + * This is the simplest test that ensures that sampling stops after the cap is hit. All sampling + * is done within the first window. */ @Test public void testCapSingleThread() { - // Doesn't rotate after starting sampling - JfrThrottler throttler = new JfrTestThrottler(); - throttler.setThrottle(SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); - for (int i = 0; i < SAMPLES_PER_WINDOW * WINDOWS_PER_PERIOD; i++) { - boolean sample = throttler.sample(); - assertFalse("failed! should take sample if under window limit", i < SAMPLES_PER_WINDOW && !sample); - assertFalse("failed! should not take sample if over window limit", i >= SAMPLES_PER_WINDOW && sample); + long samplesPerWindow = 10; + JfrEventThrottler throttler = newEventThrottler(samplesPerWindow); + + for (int i = 0; i < 3 * samplesPerWindow; i++) { + boolean sample = TestingBackdoor.sample(throttler); + assertEquals(i < samplesPerWindow, sample); } } /** * This test ensures that sampling stops after the cap is hit, even when multiple threads are - * doing sampling. + * doing sampling. It also checks that sampling can continue after rotating the window. */ @Test public void testCapConcurrent() throws InterruptedException { - final long samplesPerWindow = 100000; - final int testingThreadCount = 10; - final AtomicInteger count = new AtomicInteger(); - List testingThreads = new ArrayList<>(); - JfrTestThrottler throttler = new JfrTestThrottler(); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); - Runnable doSampling = () -> { - for (int i = 0; i < samplesPerWindow; i++) { - boolean sample = throttler.sample(); - if (sample) { - count.incrementAndGet(); - } + long samplesPerWindow = 100000; + JfrEventThrottler throttler = newEventThrottler(samplesPerWindow); + + for (int i = 0; i < 3; i++) { + /* Start a couple threads and sample concurrently. */ + int numThreads = 8; + Thread[] threads = new Thread[numThreads]; + AtomicInteger countedSamples = new AtomicInteger(0); + + for (int j = 0; j < numThreads; j++) { + Thread worker = new Thread(() -> { + for (int k = 0; k < samplesPerWindow; k++) { + boolean sample = TestingBackdoor.sample(throttler); + if (sample) { + countedSamples.incrementAndGet(); + } + } + }); + worker.start(); + threads[j] = worker; } - }; - count.set(0); - for (int i = 0; i < testingThreadCount; i++) { - Thread worker = new Thread(doSampling); - worker.start(); - testingThreads.add(worker); - } - for (Thread thread : testingThreads) { - thread.join(); - } + /* Wait until the threads finish. */ + for (Thread thread : threads) { + thread.join(); + } - assertFalse("failed! Too many samples taken! " + count.get(), count.get() > samplesPerWindow); - // Previous measured population should be 3*samplesPerWindow - // Force window rotation and repeat. - count.set(0); - expireAndRotate(throttler); - for (int i = 0; i < testingThreadCount; i++) { - Thread worker = new Thread(doSampling); - worker.start(); - testingThreads.add(worker); - } - for (Thread thread : testingThreads) { - thread.join(); + assertTrue("Sampling failed!", countedSamples.get() > 0); + assertTrue("Too many samples taken!", countedSamples.get() <= samplesPerWindow); + + /* Repeat the test a few times. */ + countedSamples.set(0); + expireAndRotate(throttler); } + } + + /** Tests normalization of sample size and period. */ + @Test + public void testNormalization() { + long sampleSize = 10 * 600; + long periodMs = TimeUtils.secondsToMillis(60); + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(sampleSize, periodMs); + assertTrue(TestingBackdoor.getPeriodMs(throttler) == TimeUtils.millisPerSecond && TestingBackdoor.getSampleSize(throttler) == sampleSize / 60); - assertFalse("failed! Too many samples taken (after rotation)! " + count.get(), count.get() > samplesPerWindow); + sampleSize = 10 * 3600; + periodMs = TimeUtils.secondsToMillis(3600); + throttler.configure(sampleSize, periodMs); + assertTrue(TestingBackdoor.getPeriodMs(throttler) == TimeUtils.millisPerSecond && TestingBackdoor.getSampleSize(throttler) == sampleSize / 3600); } - /** - * This test ensures that sampling stops after the cap is hit. Then sampling resumes once the - * window rotates. - */ @Test - public void testExpiry() { - final long samplesPerWindow = 10; - JfrTestThrottler throttler = new JfrTestThrottler(); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); - int count = 0; - - for (int i = 0; i < samplesPerWindow * 10; i++) { - boolean sample = throttler.sample(); - if (sample) { - count++; - } - } + public void testZeroRateSampling() { + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(0, TimeUtils.secondsToMillis(2)); + assertFalse(TestingBackdoor.sample(throttler)); + + /* Reconfigure the throttler. */ + throttler.configure(10, TimeUtils.secondsToMillis(2)); + assertTrue(TestingBackdoor.sample(throttler)); + } - assertTrue("Should have taken maximum possible samples: " + samplesPerWindow + " but took:" + count, samplesPerWindow == count); + /** Checks that no ObjectAllocationSample events are emitted when the sampling rate is 0. */ + @Test + public void testZeroRateRecording() throws IOException { + /* Test applying throttling settings to an actual recording. */ + Recording recording = new Recording(); + recording.setDestination(createTempJfrFile()); + recording.enable(JfrEvent.ObjectAllocationSample.getName()).with("throttle", "0/s"); + recording.start(); - // rotate window by advancing time forward - expireAndRotate(throttler); + int alignedHeapChunkSize = UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); + allocateCharArray(alignedHeapChunkSize); - assertTrue("After window rotation, it should be possible to take more samples", throttler.sample()); + recording.stop(); + recording.close(); + + /* Call getEvents directly because we expect zero events. */ + assertEquals(0, getEvents(recording.getDestination(), new String[]{JfrEvent.ObjectAllocationSample.getName()}, true).size()); } /** - * This test checks the projected population after a window rotation. This is a test of the EWMA - * calculation. Window lookback is 25 and windowDuration is un-normalized because the period is - * not greater than 1s. + * Tests the EWMA calculation. Assumes windowLookback == 25 and windowDuration < 1s. */ @Test public void testEWMA() { - // Results in 50 samples per second - final long samplesPerWindow = 10; - JfrTestThrottler throttler = new JfrTestThrottler(); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, SECOND_IN_MS); - assertTrue(throttler.getWindowLookback() == 25.0); - // Arbitrarily chosen - int[] population = {310, 410, 610, 310, 910, 420, 770, 290, 880, 640, 220, 110, 330, 590}; - // actualProjections are the expected EWMA values - int[] actualProjections = {12, 28, 51, 61, 95, 108, 135, 141, 170, 189, 190, 187, 193, 209}; - for (int p = 0; p < population.length; p++) { - for (int i = 0; i < population[p]; i++) { - throttler.sample(); + long samplesPerWindow = 10; + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(samplesPerWindow * WINDOWS_PER_PERIOD, TimeUtils.millisPerSecond); + + assertEquals(25, TestingBackdoor.getWindowLookbackCount(throttler)); + + long[] numSamples = {310, 410, 610, 310, 910, 420, 770, 290, 880, 640, 220, 110, 330, 590}; + long[] expectedProjectedPopulation = {12, 28, 51, 61, 95, 108, 135, 141, 170, 189, 190, 187, 193, 209}; + for (int i = 0; i < numSamples.length; i++) { + for (int j = 0; j < numSamples[i]; j++) { + TestingBackdoor.sample(throttler); } expireAndRotate(throttler); - double projectedPopulation = throttler.getActiveWindowProjectedPopulationSize(); - assertTrue(actualProjections[p] == (int) projectedPopulation); + + double averagePopulationSize = TestingBackdoor.getAveragePopulationSize(throttler); + assertEquals(expectedProjectedPopulation[i], (long) averagePopulationSize); } } - /** - * Ensure debt is being calculated as expected. - */ + /** Ensure debt is being calculated as expected. */ @Test public void testDebt() { - final long samplesPerWindow = 10; - final long populationPerWindow = 50; - JfrTestThrottler throttler = new JfrTestThrottler(); - throttler.beginTest(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOWS_PER_PERIOD * WINDOW_DURATION_MS); - - for (int p = 0; p < 50; p++) { - for (int i = 0; i < populationPerWindow; i++) { - throttler.sample(); + long samplesPerWindow = 10; + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(samplesPerWindow * WINDOWS_PER_PERIOD, TimeUtils.millisPerSecond); + + /* Sample for some time */ + for (int i = 0; i < 50; i++) { + for (int j = 0; j < samplesPerWindow; j++) { + assertTrue(TestingBackdoor.sample(throttler)); } + + assertEquals(0, TestingBackdoor.getActiveWindowAccumulatedDebt(throttler)); expireAndRotate(throttler); } - // Do not sample for this window. Rotate. + /* Do not sample and rotate the window right away. */ expireAndRotate(throttler); - // Debt should be at least 10 because we took no samples last window. - long debt = throttler.getActiveWindowDebt(); - assertTrue("Should have debt from under sampling.", debt >= 10); + /* Debt should be at least samplesPerWindow because we took no samples last window. */ + long debt = TestingBackdoor.getActiveWindowAccumulatedDebt(throttler); + assertTrue("Should have debt from under sampling.", debt >= samplesPerWindow); - // Limit max potential samples to half samplesPerWindow. This means debt must increase by at - // least samplesPerWindow/2. + /* Do some but not enough sampling to increase debt. */ for (int i = 0; i < samplesPerWindow / 2; i++) { - throttler.sample(); + TestingBackdoor.sample(throttler); } expireAndRotate(throttler); - assertTrue("Should have debt from under sampling.", throttler.getActiveWindowDebt() >= debt + samplesPerWindow / 2); + assertTrue("Should have debt from under sampling.", TestingBackdoor.getActiveWindowAccumulatedDebt(throttler) >= debt + samplesPerWindow / 2); // Window lookback is 25. Do not sample for 25 windows. for (int i = 0; i < 25; i++) { @@ -208,130 +214,73 @@ public void testDebt() { } // At this point sampling interval must be 1 because the projected population must be 0. - for (int i = 0; i < (samplesPerWindow + samplesPerWindow * WINDOWS_PER_PERIOD); i++) { - throttler.sample(); + for (int i = 0; i < samplesPerWindow + samplesPerWindow * WINDOWS_PER_PERIOD; i++) { + TestingBackdoor.sample(throttler); } - - assertFalse(throttler.sample()); - - expireAndRotate(throttler); - assertTrue("Should not have any debt remaining.", throttler.getActiveWindowDebt() == 0); - } - - /** - * Tests normalization of sample size and period. - */ - @Test - public void testNormalization() { - long sampleSize = 10 * 600; - long periodMs = 60 * SECOND_IN_MS; - JfrTestThrottler throttler = new JfrTestThrottler(); - throttler.beginTest(sampleSize, periodMs); - assertTrue(throttler.getPeriodNs() + " " + throttler.getEventSampleSize(), - throttler.getEventSampleSize() == sampleSize / 60 && throttler.getPeriodNs() == 1000000 * SECOND_IN_MS); - - sampleSize = 10 * 3600; - periodMs = 3600 * SECOND_IN_MS; - throttler.setThrottle(sampleSize, periodMs); - assertTrue(throttler.getPeriodNs() + " " + throttler.getEventSampleSize(), - throttler.getEventSampleSize() == sampleSize / 3600 && throttler.getPeriodNs() == 1000000 * SECOND_IN_MS); - } - - /** - * Checks that no ObjectAllocationSample events are emitted when the sampling rate is 0. - */ - @Test - public void testZeroRate() throws Throwable { - // Test throttler in isolation - JfrTestThrottler throttler = new JfrTestThrottler(); - throttler.setThrottle(0, 2 * SECOND_IN_MS); - assertFalse(throttler.sample()); - throttler.setThrottle(10, 2 * SECOND_IN_MS); - assertTrue(throttler.sample()); - - // Test applying throttling settings to an actual recording - Recording recording = new Recording(); - recording.setDestination(createTempJfrFile()); - recording.enable(JfrEvent.ObjectAllocationSample.getName()).with("throttle", "0/s"); - recording.start(); - - final int alignedHeapChunkSize = UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); - allocateCharArray(alignedHeapChunkSize); - - recording.stop(); - recording.close(); - - // Call getEvents directly because we expect zero events (which ordinarily would result in - // failure). - assertTrue(getEvents(recording.getDestination(), new String[]{JfrEvent.ObjectAllocationSample.getName()}, true).size() == 0); - } - - @NeverInline("Prevent escape analysis.") - private static char[] allocateCharArray(int length) { - return new char[length]; + assertFalse(TestingBackdoor.sample(throttler)); + assertEquals("Should not have any debt remaining.", 0, TestingBackdoor.getActiveWindowAccumulatedDebt(throttler)); } @Test public void testDistributionUniform() { - final int maxPopPerWindow = 2000; - final int minPopPerWindow = 2; - final int expectedSamplesPerWindow = 50; + int maxPopPerWindow = 2000; + int minPopPerWindow = 2; + int expectedSamplesPerWindow = 50; testDistribution(() -> ThreadLocalRandom.current().nextInt(minPopPerWindow, maxPopPerWindow + 1), expectedSamplesPerWindow, 0.05); } @Test public void testDistributionHighRate() { - final int maxPopPerWindow = 2000; - final int expectedSamplesPerWindow = 50; + int maxPopPerWindow = 2000; + int expectedSamplesPerWindow = 50; testDistribution(() -> maxPopPerWindow, expectedSamplesPerWindow, 0.02); } @Test public void testDistributionLowRate() { - final int minPopPerWindow = 2; + int minPopPerWindow = 2; testDistribution(() -> minPopPerWindow, minPopPerWindow, 0.05); } @Test public void testDistributionEarlyBurst() { - final int maxPopPerWindow = 2000; - final int expectedSamplesPerWindow = 50; - final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + int maxPopulationPerWindow = 2000; + int expectedSamplesPerWindow = 50; + int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs AtomicInteger count = new AtomicInteger(1); - testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 1 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.9); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 1 ? maxPopulationPerWindow : 0, expectedSamplesPerWindow, 0.9); } @Test public void testDistributionMidBurst() { - final int maxPopPerWindow = 2000; - final int expectedSamplesPerWindow = 50; - final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + int maxPopulationPerWindow = 2000; + int expectedSamplesPerWindow = 50; + int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs AtomicInteger count = new AtomicInteger(1); - testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 5 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.5); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 5 ? maxPopulationPerWindow : 0, expectedSamplesPerWindow, 0.5); } @Test public void testDistributionLateBurst() { - final int maxPopPerWindow = 2000; - final int expectedSamplesPerWindow = 50; - final int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + int maxPopulationPerWindow = 2000; + int expectedSamplesPerWindow = 50; + int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs AtomicInteger count = new AtomicInteger(1); - testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 0 ? maxPopPerWindow : 0, expectedSamplesPerWindow, 0.0); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 0 ? maxPopulationPerWindow : 0, expectedSamplesPerWindow, 0.0); } /** - * This is a more involved test that checks the sample distribution. It has been adapted from + * This is a more involved test that checks the sample distribution. It is based on * JfrGTestAdaptiveSampling in the OpenJDK. */ - private static void testDistribution(IncomingPopulation incomingPopulation, int samplePointsPerWindow, double errorFactor) { - final int distributionSlots = 100; - final int windowDurationMs = 100; - final int windowCount = 10000; - final int expectedSamplesPerWindow = 50; - final int expectedSamples = expectedSamplesPerWindow * windowCount; + private static void testDistribution(IntSupplier incomingPopulation, int samplePointsPerWindow, double errorFactor) { + int distributionSlots = 100; + int windowDurationMs = 100; + int windowCount = 10000; - JfrTestThrottler throttler = new JfrTestThrottler(); - throttler.beginTest(expectedSamplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); + int samplesPerWindow = 50; + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(samplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); int[] population = new int[distributionSlots]; int[] sample = new int[distributionSlots]; @@ -339,19 +288,21 @@ private static void testDistribution(IncomingPopulation incomingPopulation, int int populationSize = 0; int sampleSize = 0; for (int t = 0; t < windowCount; t++) { - int windowPop = incomingPopulation.getWindowPopulation(); + int windowPop = incomingPopulation.getAsInt(); for (int i = 0; i < windowPop; i++) { populationSize++; int index = ThreadLocalRandom.current().nextInt(0, 100); population[index] += 1; - if (throttler.sample()) { + if (TestingBackdoor.sample(throttler)) { sampleSize++; sample[index] += 1; } } expireAndRotate(throttler); } + int targetSampleSize = samplePointsPerWindow * windowCount; + int expectedSamples = samplesPerWindow * windowCount; expectNear(targetSampleSize, sampleSize, expectedSamples * errorFactor); assertDistributionProperties(distributionSlots, population, sample, populationSize, sampleSize); } @@ -388,87 +339,19 @@ private static void assertDistributionProperties(int distributionSlots, int[] po expectNear(populationMean, sampleMean, populationStdev); } - interface IncomingPopulation { - int getWindowPopulation(); - } - - /** - * Helper method that expires and rotates a throttler's active window. - */ - private static void expireAndRotate(JfrTestThrottler throttler) { - throttler.expireActiveWindow(); - assertTrue("should be expired", throttler.isActiveWindowExpired()); - assertFalse("Should have rotated not sampled!", throttler.sample()); + @NeverInline("Prevent optimizations.") + private static char[] allocateCharArray(int length) { + return new char[length]; } - private static class JfrTestThrottler extends JfrThrottler { - public void beginTest(long eventSampleSize, long periodMs) { - window0 = new JfrTestThrottlerWindow(); - window1 = new JfrTestThrottlerWindow(); - activeWindow = window0; - window0().currentTestNanos = 0; - window1().currentTestNanos = 0; - setThrottle(eventSampleSize, periodMs); - } - - public double getActiveWindowProjectedPopulationSize() { - return avgPopulationSize; - } - - public long getActiveWindowDebt() { - return activeWindow.debt; - } - - public double getWindowLookback() { - return windowLookback(activeWindow); - } - - public boolean isActiveWindowExpired() { - return activeWindow.isExpired(); - } - - public long getPeriodNs() { - return periodNs; - } - - public long getEventSampleSize() { - return eventSampleSize; - } - - public void expireActiveWindow() { - if (eventSampleSize <= LOW_RATE_UPPER_BOUND || periodNs > TimeUtils.nanosPerSecond) { - window0().currentTestNanos += periodNs; - window1().currentTestNanos += periodNs; - } - window0().currentTestNanos += periodNs / WINDOW_DIVISOR; - window1().currentTestNanos += periodNs / WINDOW_DIVISOR; - } - - private JfrTestThrottlerWindow window0() { - return (JfrTestThrottlerWindow) window0; - } - - private JfrTestThrottlerWindow window1() { - return (JfrTestThrottlerWindow) window1; - } + private static JfrEventThrottler newEventThrottler(long samplesPerWindow) { + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + return throttler; } - private static class JfrTestThrottlerWindow extends JfrThrottlerWindow { - public volatile long currentTestNanos = 0; - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean isExpired() { - if (currentTestNanos >= endTicks.get()) { - return true; - } - return false; - } - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected void advanceEndTicks() { - endTicks.set(currentTestNanos + windowDurationNs); - } + private static void expireAndRotate(JfrEventThrottler throttler) { + TestingBackdoor.expireActiveWindow(throttler); + assertFalse("Should have rotated not sampled!", TestingBackdoor.sample(throttler)); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java index 1d9b91adb4b0..c4e7e5882333 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java @@ -85,7 +85,7 @@ protected void testSampling(Object obj, int arrayLength, EventValidator validato boolean success; long endTime = System.currentTimeMillis() + TimeUtils.secondsToMillis(5); do { - success = SubstrateJVM.getJfrOldObjectProfiler().sample(obj, WordFactory.unsigned(1024 * 1024 * 1024), arrayLength); + success = SubstrateJVM.getOldObjectProfiler().sample(obj, WordFactory.unsigned(1024 * 1024 * 1024), arrayLength); } while (!success && System.currentTimeMillis() < endTime); Assert.assertTrue("Timed out waiting for sampling to complete", success); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java index 1817408af507..5a1096bebd53 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java @@ -181,7 +181,7 @@ public void testEvictOldest() { private static JfrOldObjectProfiler newProfiler(int queueSize) { JfrOldObjectProfiler profiler = new JfrOldObjectProfiler(); profiler.configure(queueSize); - profiler.initialize(); + profiler.reset(); return profiler; } From a7f26c7518ba63c232fdf3c4c5ec94d92f99c470 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 5 Feb 2024 17:02:02 +0100 Subject: [PATCH 33/34] Add option ImplicitExceptionWithoutStacktraceIsFatal. --- substratevm/CHANGELOG.md | 2 +- .../com/oracle/svm/core/SubstrateOptions.java | 3 + .../jfr/throttling/JfrAdaptiveSampler.java | 2 +- .../svm/core/snippets/ImplicitExceptions.java | 101 +++++++++--------- 4 files changed, 57 insertions(+), 51 deletions(-) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 2ae417640d97..bd0bc3b7569d 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -6,7 +6,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-51106) Fields that are accessed via a `VarHandle` or `MethodHandle` are no longer marked as "unsafe accessed" when the `VarHandle`/`MethodHandle` can be fully intrinsified. * (GR-49996) Ensure explicitly set image name (e.g., via `-o imagename`) is not accidentally overwritten by `-jar jarfile` option. * (GR-48683) Together with Red Hat, we added partial support for the JFR event `OldObjectSample`. -* (GR-47109) JFR event throttling support was added, along with the `ObjectAllocationSample` event. +* (GR-47109) Together with Red Hat, we added support for JFR event throttling and the event `ObjectAllocationSample`. ## GraalVM for JDK 22 (Internal Version 24.0.0) * (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 46cf946a543d..c1471fea166b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -966,6 +966,9 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol @Option(help = "Specifies the number of entries that diagnostic buffers have.", type = OptionType.Debug)// public static final HostedOptionKey DiagnosticBufferSize = new HostedOptionKey<>(30); + @Option(help = "Determines if implicit exceptions are fatal if they don't have a stack trace.", type = OptionType.Debug)// + public static final RuntimeOptionKey ImplicitExceptionWithoutStacktraceIsFatal = new RuntimeOptionKey<>(false); + @SuppressWarnings("unused")// @APIOption(name = "configure-reflection-metadata")// @Option(help = "Enable runtime instantiation of reflection objects for non-invoked methods.", type = OptionType.Expert, deprecated = true)// diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java index 16e4b0b54d14..a85926c7fed8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java @@ -118,7 +118,7 @@ private void configure(JfrSamplerParams params, JfrSamplerWindow expired, JfrSam @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private static double computeEwmaAlphaCoefficient(long lookbackCount) { - return lookbackCount <= 1 ? 1 : 1d / lookbackCount; + return lookbackCount <= 1 ? 1d : 1d / lookbackCount; } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java index ea1c66cf7b93..7befcc109a3b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java @@ -30,6 +30,7 @@ import java.lang.reflect.GenericSignatureFormatError; import com.oracle.svm.core.SubstrateDiagnostics; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.code.FactoryMethodMarker; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.jdk.InternalVMMethod; @@ -170,8 +171,10 @@ public static void deactivateImplicitExceptionsAreFatal() { implicitExceptionsAreFatal.set(implicitExceptionsAreFatal.get() - 1); } - private static void vmErrorIfImplicitExceptionsAreFatal() { - if ((implicitExceptionsAreFatal.get() > 0 || ExceptionUnwind.exceptionsAreFatal()) && !SubstrateDiagnostics.isFatalErrorHandlingThread()) { + private static void vmErrorIfImplicitExceptionsAreFatal(boolean cachedException) { + if (cachedException && SubstrateOptions.ImplicitExceptionWithoutStacktraceIsFatal.getValue()) { + throw VMError.shouldNotReachHere("AssertionError without stack trace."); + } else if ((implicitExceptionsAreFatal.get() > 0 || ExceptionUnwind.exceptionsAreFatal()) && !SubstrateDiagnostics.isFatalErrorHandlingThread()) { throw VMError.shouldNotReachHere("Implicit exception thrown in code where such exceptions are fatal errors"); } } @@ -179,21 +182,21 @@ private static void vmErrorIfImplicitExceptionsAreFatal() { /** Foreign call: {@link #CREATE_NULL_POINTER_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static NullPointerException createNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new NullPointerException(); } /** Foreign call: {@link #CREATE_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException createIntrinsicOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArrayIndexOutOfBoundsException(); } /** Foreign call: {@link #CREATE_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException createOutOfBoundsException(int index, int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArrayIndexOutOfBoundsException("Index " + index + " out of bounds for length " + length); } @@ -201,7 +204,7 @@ private static ArrayIndexOutOfBoundsException createOutOfBoundsException(int ind @SubstrateForeignCallTarget(stubCallingConvention = true) private static ClassCastException createClassCastException(Object object, Object expectedClass) { assert object != null : "null can be cast to any type, so it cannot show up as a source of a ClassCastException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); String expectedClassName; if (expectedClass instanceof Class) { expectedClassName = ((Class) expectedClass).getTypeName(); @@ -215,35 +218,35 @@ private static ClassCastException createClassCastException(Object object, Object @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayStoreException createArrayStoreException(Object value) { assert value != null : "null can be stored into any array, so it cannot show up as a source of an ArrayStoreException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArrayStoreException(value.getClass().getTypeName()); } /** Foreign call: {@link #CREATE_INCOMPATIBLE_CLASS_CHANGE_ERROR}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static IncompatibleClassChangeError createIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new IncompatibleClassChangeError(); } /** Foreign call: {@link #CREATE_ILLEGAL_ARGUMENT_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static IllegalArgumentException createIllegalArgumentException(String message) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new IllegalArgumentException(message); } /** Foreign call: {@link #CREATE_NEGATIVE_ARRAY_SIZE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static NegativeArraySizeException createNegativeArraySizeException(int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new NegativeArraySizeException(String.valueOf(length)); } /** Foreign call: {@link #CREATE_DIVISION_BY_ZERO_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createDivisionByZeroException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("/ by zero"); } @@ -252,14 +255,14 @@ private static ArithmeticException createDivisionByZeroException() { /** Foreign call: {@link #CREATE_INTEGER_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createIntegerOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("integer overflow"); } /** Foreign call: {@link #CREATE_LONG_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createLongOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("long overflow"); } @@ -268,42 +271,42 @@ private static ArithmeticException createLongOverflowException() { /** Foreign call: {@link #CREATE_ASSERTION_ERROR_NULLARY}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError createAssertionErrorNullary() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new AssertionError(); } /** Foreign call: {@link #CREATE_ASSERTION_ERROR_OBJECT}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError createAssertionErrorObject(Object detailMessage) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new AssertionError(detailMessage); } /** Foreign call: {@link #THROW_NEW_NULL_POINTER_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new NullPointerException(); } /** Foreign call: {@link #THROW_NEW_INTRINSIC_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIntrinsicOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayIndexOutOfBoundsException(); } /** Foreign call: {@link #THROW_NEW_OUT_OF_BOUNDS_EXCEPTION_WITH_ARGS}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewOutOfBoundsExceptionWithArgs(int index, int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayIndexOutOfBoundsException("Index " + index + " out of bounds for length " + length); } /** Foreign call: {@link #THROW_NEW_CLASS_CAST_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ClassCastException(); } @@ -311,7 +314,7 @@ private static void throwNewClassCastException() { @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewClassCastExceptionWithArgs(Object object, Object expectedClass) { assert object != null : "null can be cast to any type, so it cannot show up as a source of a ClassCastException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); String expectedClassName; if (expectedClass instanceof Class) { expectedClassName = ((Class) expectedClass).getTypeName(); @@ -324,7 +327,7 @@ private static void throwNewClassCastExceptionWithArgs(Object object, Object exp /** Foreign call: {@link #THROW_NEW_ARRAY_STORE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayStoreException(); } @@ -332,42 +335,42 @@ private static void throwNewArrayStoreException() { @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArrayStoreExceptionWithArgs(Object value) { assert value != null : "null can be stored into any array, so it cannot show up as a source of an ArrayStoreException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayStoreException(value.getClass().getTypeName()); } /** Foreign call: {@link #THROW_NEW_INCOMPATIBLE_CLASS_CHANGE_ERROR}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new IncompatibleClassChangeError(); } /** Foreign call: {@link #THROW_NEW_ILLEGAL_ARGUMENT_EXCEPTION_WITH_ARGS}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIllegalArgumentExceptionWithArgs(String message) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new IllegalArgumentException(message); } /** Foreign call: {@link #THROW_NEW_NEGATIVE_ARRAY_SIZE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewNegativeArraySizeException(int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new NegativeArraySizeException(String.valueOf(length)); } /** Foreign call: {@link #THROW_NEW_ARITHMETIC_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException(); } /** Foreign call: {@link #THROW_NEW_DIVISION_BY_ZERO_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewDivisionByZeroException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("/ by zero"); } @@ -376,14 +379,14 @@ private static void throwNewDivisionByZeroException() { /** Foreign call: {@link #THROW_NEW_INTEGER_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIntegerOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("integer overflow"); } /** Foreign call: {@link #THROW_NEW_LONG_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewLongOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("long overflow"); } @@ -392,14 +395,14 @@ private static void throwNewLongOverflowException() { /** Foreign call: {@link #THROW_NEW_ASSERTION_ERROR_NULLARY}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewAssertionErrorNullary() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new AssertionError(); } /** Foreign call: {@link #THROW_NEW_ASSERTION_ERROR_OBJECT}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewAssertionErrorObject(Object detailMessage) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new AssertionError(detailMessage); } @@ -407,7 +410,7 @@ private static void throwNewAssertionErrorObject(Object detailMessage) { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static NullPointerException getCachedNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_NULL_POINTER_EXCEPTION; } @@ -415,7 +418,7 @@ private static NullPointerException getCachedNullPointerException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException getCachedOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_OUT_OF_BOUNDS_EXCEPTION; } @@ -423,7 +426,7 @@ private static ArrayIndexOutOfBoundsException getCachedOutOfBoundsException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ClassCastException getCachedClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_CLASS_CAST_EXCEPTION; } @@ -431,7 +434,7 @@ private static ClassCastException getCachedClassCastException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayStoreException getCachedArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ARRAY_STORE_EXCEPTION; } @@ -439,7 +442,7 @@ private static ArrayStoreException getCachedArrayStoreException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static IncompatibleClassChangeError getCachedIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_INCOMPATIBLE_CLASS_CHANGE_ERROR; } @@ -447,7 +450,7 @@ private static IncompatibleClassChangeError getCachedIncompatibleClassChangeErro @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static IllegalArgumentException getCachedIllegalArgumentException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ILLEGAL_ARGUMENT_EXCEPTION; } @@ -455,7 +458,7 @@ private static IllegalArgumentException getCachedIllegalArgumentException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static NegativeArraySizeException getCachedNegativeArraySizeException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_NEGATIVE_ARRAY_SIZE_EXCEPTION; } @@ -463,7 +466,7 @@ private static NegativeArraySizeException getCachedNegativeArraySizeException() @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException getCachedArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ARITHMETIC_EXCEPTION; } @@ -471,7 +474,7 @@ private static ArithmeticException getCachedArithmeticException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError getCachedAssertionError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ASSERTION_ERROR; } @@ -479,7 +482,7 @@ private static AssertionError getCachedAssertionError() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_NULL_POINTER_EXCEPTION; } @@ -487,7 +490,7 @@ private static void throwCachedNullPointerException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_OUT_OF_BOUNDS_EXCEPTION; } @@ -495,7 +498,7 @@ private static void throwCachedOutOfBoundsException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_CLASS_CAST_EXCEPTION; } @@ -503,7 +506,7 @@ private static void throwCachedClassCastException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ARRAY_STORE_EXCEPTION; } @@ -511,7 +514,7 @@ private static void throwCachedArrayStoreException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_INCOMPATIBLE_CLASS_CHANGE_ERROR; } @@ -519,7 +522,7 @@ private static void throwCachedIncompatibleClassChangeError() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedIllegalArgumentException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ILLEGAL_ARGUMENT_EXCEPTION; } @@ -527,7 +530,7 @@ private static void throwCachedIllegalArgumentException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedNegativeArraySizeException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_NEGATIVE_ARRAY_SIZE_EXCEPTION; } @@ -535,7 +538,7 @@ private static void throwCachedNegativeArraySizeException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ARITHMETIC_EXCEPTION; } @@ -543,7 +546,7 @@ private static void throwCachedArithmeticException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedAssertionError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ASSERTION_ERROR; } From d721c1b063978ff237dd55b43904c90035f8981f Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Wed, 7 Feb 2024 11:23:16 +0100 Subject: [PATCH 34/34] Use log function from libm. --- .../svm/core/posix/PosixLibMSupport.java | 39 +++++++++++++ .../svm/core/posix/headers/PosixLibM.java | 56 +++++++++++++++++++ .../svm/core/windows/WindowsLibMSupport.java | 39 +++++++++++++ .../windows/headers/WindowsDirectives.java | 6 +- .../svm/core/windows/headers/WindowsLibC.java | 3 + .../src/com/oracle/svm/core/headers/LibC.java | 3 +- .../src/com/oracle/svm/core/headers/LibM.java | 45 +++++++++++++++ .../oracle/svm/core/headers/LibMSupport.java | 32 +++++++++++ .../jfr/throttling/JfrAdaptiveSampler.java | 6 +- 9 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibMSupport.java create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibM.java create mode 100644 substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibMSupport.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibM.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibMSupport.java diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibMSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibMSupport.java new file mode 100644 index 000000000000..13afae25d23c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibMSupport.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, 2024, 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.posix; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.headers.LibMSupport; +import com.oracle.svm.core.posix.headers.PosixLibM; + +@AutomaticallyRegisteredImageSingleton(LibMSupport.class) +public class PosixLibMSupport implements LibMSupport { + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public double log(double value) { + return PosixLibM.log(value); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibM.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibM.java new file mode 100644 index 000000000000..b31c4759f142 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibM.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, 2024, 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.posix.headers; + +import java.util.Collections; +import java.util.List; + +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.function.CFunction; + +import com.oracle.svm.core.jfr.HasJfrSupport; + +@CContext(value = LibMDependencies.class) +public class PosixLibM { + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native double log(double value); +} + +class LibMDependencies implements CContext.Directives { + @Override + public boolean isInConfiguration() { + return HasJfrSupport.get(); + } + + @Override + public List getHeaderFiles() { + return Collections.singletonList(""); + } + + @Override + public List getLibraries() { + return Collections.singletonList("m"); + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibMSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibMSupport.java new file mode 100644 index 000000000000..f950192cd3d8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibMSupport.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, 2024, 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.windows; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.headers.LibMSupport; +import com.oracle.svm.core.windows.headers.WindowsLibC; + +@AutomaticallyRegisteredImageSingleton(LibMSupport.class) +public class WindowsLibMSupport implements LibMSupport { + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public double log(double value) { + return WindowsLibC.log(value); + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsDirectives.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsDirectives.java index 2c0200afcdd3..1bc2aebad8de 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsDirectives.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsDirectives.java @@ -44,7 +44,8 @@ public class WindowsDirectives implements CContext.Directives { "", "", "", - "" + "", + "" }; @Override @@ -55,8 +56,7 @@ public boolean isInConfiguration() { @Override public List getHeaderFiles() { if (Platform.includedIn(Platform.WINDOWS.class)) { - List result = new ArrayList<>(Arrays.asList(windowsLibs)); - return result; + return new ArrayList<>(Arrays.asList(windowsLibs)); } else { throw VMError.shouldNotReachHere("Unsupported OS"); } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java index 828d67152965..721d0c13d7eb 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java @@ -104,4 +104,7 @@ public interface WCharPointer extends PointerBase { @CFunction(transition = CFunction.Transition.NO_TRANSITION) public static native UnsignedWord strtoull(CCharPointer string, CCharPointerPointer endPtr, int base); + + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native double log(double value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java index b72147d4a989..663a97993d84 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.headers; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; @@ -34,6 +33,8 @@ import com.oracle.svm.core.Uninterruptible; +import jdk.graal.compiler.api.replacements.Fold; + public class LibC { public static final int EXIT_CODE_ABORT = 99; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibM.java new file mode 100644 index 000000000000..11a1f30e269e --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibM.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 2024, 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.headers; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.Uninterruptible; + +import jdk.graal.compiler.api.replacements.Fold; + +public class LibM { + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static double log(double value) { + return libm().log(value); + } + + @Fold + static LibMSupport libm() { + return ImageSingletons.lookup(LibMSupport.class); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibMSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibMSupport.java new file mode 100644 index 000000000000..02d6442347ad --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibMSupport.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, 2024, 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.headers; + +import com.oracle.svm.core.Uninterruptible; + +public interface LibMSupport { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + double log(double value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java index a85926c7fed8..1216e57ab43d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java @@ -27,9 +27,9 @@ package com.oracle.svm.core.jfr.throttling; import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; -import static java.lang.Math.log; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.headers.LibM; import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jfr.utils.JfrRandom; import com.oracle.svm.core.thread.JavaSpinLockUtils; @@ -172,7 +172,7 @@ private long deriveSamplingInterval(double sampleSize, JfrSamplerWindow expired) @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) private double projectPopulationSize(JfrSamplerWindow expired) { - avgPopulationSize = exponentiallyWeightedMovingAverage((double) expired.getPopulationSize(), ewmaPopulationSizeAlpha, avgPopulationSize); + avgPopulationSize = exponentiallyWeightedMovingAverage(expired.getPopulationSize(), ewmaPopulationSizeAlpha, avgPopulationSize); return avgPopulationSize; } @@ -191,7 +191,7 @@ private long nextGeometric(double p) { } else if (u == 1.0) { u = 0.99; } - return UninterruptibleUtils.Math.ceilToLong(log(1.0 - u) / log(1.0 - p)); + return UninterruptibleUtils.Math.ceilToLong(LibM.log(1.0 - u) / LibM.log(1.0 - p)); } @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)