From f3f4d511c9b9b8c6c5aca56b521fa19ca05ab428 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 10 Jan 2023 16:53:32 -0500 Subject: [PATCH 01/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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); }