diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 0d9063051032..bd0bc3b7569d 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -6,6 +6,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-51106) Fields that are accessed via a `VarHandle` or `MethodHandle` are no longer marked as "unsafe accessed" when the `VarHandle`/`MethodHandle` can be fully intrinsified. * (GR-49996) Ensure explicitly set image name (e.g., via `-o imagename`) is not accidentally overwritten by `-jar jarfile` option. * (GR-48683) Together with Red Hat, we added partial support for the JFR event `OldObjectSample`. +* (GR-47109) Together with Red Hat, we added support for JFR event throttling and the event `ObjectAllocationSample`. ## GraalVM for JDK 22 (Internal Version 24.0.0) * (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics. diff --git a/substratevm/src/com.oracle.svm.core.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 14ddfe935c14..2fd6543ca1e1 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java @@ -60,7 +60,7 @@ import com.oracle.svm.core.jfr.HasJfrSupport; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.SubstrateJVM; -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; @@ -243,7 +243,7 @@ private static Object slowPathNewInstanceWithoutAllocating(DynamicHub hub, Unsig AlignedHeader newTlab = HeapImpl.getChunkProvider().produceAlignedChunk(); return allocateInstanceInNewTlab(hub, size, newTlab); } finally { - ObjectAllocationInNewTLABEvent.emit(startTicks, hub, size, HeapParameters.getAlignedHeapChunkSize()); + JfrAllocationEvents.emit(startTicks, hub, size, HeapParameters.getAlignedHeapChunkSize()); DeoptTester.enableDeoptTesting(); } } @@ -327,7 +327,7 @@ private static Object slowPathNewArrayLikeObject0(DynamicHub hub, int length, Un } return array; } finally { - ObjectAllocationInNewTLABEvent.emit(startTicks, hub, size, tlabSize); + JfrAllocationEvents.emit(startTicks, hub, size, tlabSize); DeoptTester.enableDeoptTesting(); } } @@ -532,7 +532,7 @@ private static Descriptor retireCurrentAllocationChunk(IsolateThread thread) { private static void sampleSlowPathAllocation(Object obj, UnsignedWord allocatedSize, int arrayLength) { if (HasJfrSupport.get()) { - SubstrateJVM.getJfrOldObjectProfiler().sample(obj, allocatedSize, arrayLength); + SubstrateJVM.getOldObjectProfiler().sample(obj, allocatedSize, arrayLength); } } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibMSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibMSupport.java new file mode 100644 index 000000000000..13afae25d23c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibMSupport.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.posix; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.headers.LibMSupport; +import com.oracle.svm.core.posix.headers.PosixLibM; + +@AutomaticallyRegisteredImageSingleton(LibMSupport.class) +public class PosixLibMSupport implements LibMSupport { + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public double log(double value) { + return PosixLibM.log(value); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibM.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibM.java new file mode 100644 index 000000000000..b31c4759f142 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/PosixLibM.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.posix.headers; + +import java.util.Collections; +import java.util.List; + +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.function.CFunction; + +import com.oracle.svm.core.jfr.HasJfrSupport; + +@CContext(value = LibMDependencies.class) +public class PosixLibM { + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native double log(double value); +} + +class LibMDependencies implements CContext.Directives { + @Override + public boolean isInConfiguration() { + return HasJfrSupport.get(); + } + + @Override + public List getHeaderFiles() { + return Collections.singletonList(""); + } + + @Override + public List getLibraries() { + return Collections.singletonList("m"); + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibMSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibMSupport.java new file mode 100644 index 000000000000..f950192cd3d8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibMSupport.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.windows; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.headers.LibMSupport; +import com.oracle.svm.core.windows.headers.WindowsLibC; + +@AutomaticallyRegisteredImageSingleton(LibMSupport.class) +public class WindowsLibMSupport implements LibMSupport { + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public double log(double value) { + return WindowsLibC.log(value); + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsDirectives.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsDirectives.java index 2c0200afcdd3..1bc2aebad8de 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsDirectives.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsDirectives.java @@ -44,7 +44,8 @@ public class WindowsDirectives implements CContext.Directives { "", "", "", - "" + "", + "" }; @Override @@ -55,8 +56,7 @@ public boolean isInConfiguration() { @Override public List getHeaderFiles() { if (Platform.includedIn(Platform.WINDOWS.class)) { - List result = new ArrayList<>(Arrays.asList(windowsLibs)); - return result; + return new ArrayList<>(Arrays.asList(windowsLibs)); } else { throw VMError.shouldNotReachHere("Unsupported OS"); } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java index 828d67152965..721d0c13d7eb 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WindowsLibC.java @@ -104,4 +104,7 @@ public interface WCharPointer extends PointerBase { @CFunction(transition = CFunction.Transition.NO_TRANSITION) public static native UnsignedWord strtoull(CCharPointer string, CCharPointerPointer endPtr, int base); + + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native double log(double value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 83d61cb0f416..b09e1a7b6081 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -969,6 +969,9 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol @Option(help = "Specifies the number of entries that diagnostic buffers have.", type = OptionType.Debug)// public static final HostedOptionKey DiagnosticBufferSize = new HostedOptionKey<>(30); + @Option(help = "Determines if implicit exceptions are fatal if they don't have a stack trace.", type = OptionType.Debug)// + public static final RuntimeOptionKey ImplicitExceptionWithoutStacktraceIsFatal = new RuntimeOptionKey<>(false); + @SuppressWarnings("unused")// @APIOption(name = "configure-reflection-metadata")// @Option(help = "Enable runtime instantiation of reflection objects for non-invoked methods.", type = OptionType.Expert, deprecated = true)// diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/EnumBitmask.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/EnumBitmask.java new file mode 100644 index 000000000000..974be4545dee --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/EnumBitmask.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.collections; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; + +public final class EnumBitmask { + private EnumBitmask() { + } + + public static int computeBitmask(Enum[] flags) { + int result = 0; + for (Enum flag : flags) { + assert flag.ordinal() <= Integer.SIZE - 1; + result |= flagBit(flag); + } + return result; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static boolean hasBit(int bitmask, Enum flag) { + return (bitmask & flagBit(flag)) != 0; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static int flagBit(Enum flag) { + assert flag.ordinal() < 32; + return 1 << flag.ordinal(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java index b72147d4a989..663a97993d84 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.headers; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; @@ -34,6 +33,8 @@ import com.oracle.svm.core.Uninterruptible; +import jdk.graal.compiler.api.replacements.Fold; + public class LibC { public static final int EXIT_CODE_ABORT = 99; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibM.java new file mode 100644 index 000000000000..11a1f30e269e --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibM.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.headers; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.Uninterruptible; + +import jdk.graal.compiler.api.replacements.Fold; + +public class LibM { + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static double log(double value) { + return libm().log(value); + } + + @Fold + static LibMSupport libm() { + return ImageSingletons.lookup(LibMSupport.class); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibMSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibMSupport.java new file mode 100644 index 000000000000..02d6442347ad --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibMSupport.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.headers; + +import com.oracle.svm.core.Uninterruptible; + +public interface LibMSupport { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + double log(double value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index e0a6c19ff0a9..2645c1f7cab0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -438,6 +438,18 @@ 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 floorToLong(double value) { + assert value == value : "must not be NaN"; + return (long) value; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long ceilToLong(double a) { + long floor = floorToLong(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/JfrEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java index 344cb94c5862..44e4de3e90e6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java @@ -24,10 +24,13 @@ */ package com.oracle.svm.core.jfr; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.collections.EnumBitmask; import com.oracle.svm.core.thread.JavaThreads; /** @@ -35,85 +38,101 @@ * 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 SystemGC = create("jdk.SystemGC", true); - public static final JfrEvent AllocationRequiringGC = create("jdk.AllocationRequiringGC", false); - public static final JfrEvent OldObjectSample = create("jdk.OldObjectSample", false); + public static final JfrEvent ThreadStart = create("jdk.ThreadStart"); + public static final JfrEvent ThreadEnd = create("jdk.ThreadEnd"); + public static final JfrEvent ThreadCPULoad = create("jdk.ThreadCPULoad"); + public static final JfrEvent DataLoss = create("jdk.DataLoss"); + public static final JfrEvent ClassLoadingStatistics = create("jdk.ClassLoadingStatistics"); + public static final JfrEvent InitialEnvironmentVariable = create("jdk.InitialEnvironmentVariable"); + public static final JfrEvent InitialSystemProperty = create("jdk.InitialSystemProperty"); + public static final JfrEvent JavaThreadStatistics = create("jdk.JavaThreadStatistics"); + public static final JfrEvent JVMInformation = create("jdk.JVMInformation"); + public static final JfrEvent OSInformation = create("jdk.OSInformation"); + public static final JfrEvent PhysicalMemory = create("jdk.PhysicalMemory"); + public static final JfrEvent ExecutionSample = create("jdk.ExecutionSample"); + public static final JfrEvent NativeMethodSample = create("jdk.NativeMethodSample"); + public static final JfrEvent GarbageCollection = create("jdk.GarbageCollection", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePause = create("jdk.GCPhasePause", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePauseLevel1 = create("jdk.GCPhasePauseLevel1", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePauseLevel2 = create("jdk.GCPhasePauseLevel2", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePauseLevel3 = create("jdk.GCPhasePauseLevel3", JfrEventFlags.HasDuration); + public static final JfrEvent GCPhasePauseLevel4 = create("jdk.GCPhasePauseLevel4", JfrEventFlags.HasDuration); + public static final JfrEvent SafepointBegin = create("jdk.SafepointBegin", JfrEventFlags.HasDuration); + public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd", JfrEventFlags.HasDuration); + public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation", JfrEventFlags.HasDuration); + public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter", JfrEventFlags.HasDuration); + public static final JfrEvent ThreadPark = create("jdk.ThreadPark", JfrEventFlags.HasDuration); + public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait", JfrEventFlags.HasDuration); + public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate", JfrEventFlags.HasDuration); + public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB"); + public static final JfrEvent GCHeapSummary = create("jdk.GCHeapSummary"); + public static final JfrEvent ThreadAllocationStatistics = create("jdk.ThreadAllocationStatistics"); + public static final JfrEvent SystemGC = create("jdk.SystemGC", JfrEventFlags.HasDuration); + public static final JfrEvent AllocationRequiringGC = create("jdk.AllocationRequiringGC"); + public static final JfrEvent OldObjectSample = create("jdk.OldObjectSample"); + public static final JfrEvent ObjectAllocationSample = create("jdk.ObjectAllocationSample", JfrEventFlags.SupportsThrottling); private final long id; private final String name; - private final boolean hasDuration; + private final int flags; @Platforms(Platform.HOSTED_ONLY.class) - public static JfrEvent create(String name, boolean hasDuration) { - return new JfrEvent(name, hasDuration); + public static JfrEvent create(String name, JfrEventFlags... flags) { + return new JfrEvent(name, flags); } @Platforms(Platform.HOSTED_ONLY.class) - private JfrEvent(String name, boolean hasDuration) { + private JfrEvent(String name, JfrEventFlags... flags) { this.id = JfrMetadataTypeLibrary.lookupPlatformEvent(name); this.name = name; - this.hasDuration = hasDuration; + this.flags = EnumBitmask.computeBitmask(flags); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getId() { return id; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public String getName() { return name; } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private boolean hasDuration() { + return EnumBitmask.hasBit(flags, JfrEventFlags.HasDuration); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean supportsThrottling() { + return EnumBitmask.hasBit(flags, JfrEventFlags.SupportsThrottling); + } + @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) public boolean shouldEmit() { - assert !hasDuration; + assert !hasDuration(); return shouldEmit0() && !JfrThreadLocal.isThreadExcluded(JavaThreads.getCurrentThreadOrNull()); } @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) public boolean shouldEmit(Thread thread) { - assert !hasDuration; + assert !hasDuration(); return shouldEmit0() && !JfrThreadLocal.isThreadExcluded(thread); } @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) public boolean shouldEmit(long durationTicks) { - assert hasDuration; + assert hasDuration(); return shouldEmit0() && durationTicks >= SubstrateJVM.get().getThresholdTicks(this) && !JfrThreadLocal.isThreadExcluded(JavaThreads.getCurrentThreadOrNull()); } @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) private boolean shouldEmit0() { - return SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this); + return SubstrateJVM.get().isRecording() && SubstrateJVM.get().isEnabled(this) && SubstrateJVM.getEventThrottling().shouldCommit(this); + } + + private enum JfrEventFlags { + HasDuration, + SupportsThrottling } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java index 6f10b8f50669..045de0e9059d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrManager.java @@ -126,7 +126,7 @@ private static void parseFlightRecorderOptions() { if (oldObjectQueueSize != null) { if (oldObjectQueueSize >= 0) { - SubstrateJVM.getJfrOldObjectProfiler().configure(oldObjectQueueSize); + SubstrateJVM.getOldObjectProfiler().configure(oldObjectQueueSize); } else { throw argumentParsingFailed(FlightRecorderOptionsArgument.OldObjectQueueSize.getCmdLineKey() + " must be greater or equal 0."); } @@ -167,7 +167,7 @@ public static RuntimeSupport.Hook shutdownHook() { private static void parseFlightRecorderLogging() { String option = SubstrateOptions.FlightRecorderLogging.getValue(); - SubstrateJVM.getJfrLogging().parseConfiguration(option); + SubstrateJVM.getLogging().parseConfiguration(option); } private static void periodicEventSetup() throws SecurityException { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTicks.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTicks.java index 23393e470f3d..0c012bfb21e6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTicks.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTicks.java @@ -52,6 +52,11 @@ public static long elapsedTicks() { return 0; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long now() { + return System.nanoTime(); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long duration(long startTicks) { return elapsedTicks() - startTicks; @@ -61,6 +66,11 @@ public static long getTicksFrequency() { return TimeUtils.nanosPerSecond; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long millisToTicks(long millis) { + return TimeUtils.millisToNanos(millis); + } + public static long currentTimeNanos() { return TimeUtils.millisToNanos(System.currentTimeMillis()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java index 3cc2a20d2975..73a8ebd1b2b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java @@ -36,10 +36,12 @@ 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.oldobject.JfrOldObjectProfiler; import com.oracle.svm.core.jfr.oldobject.JfrOldObjectRepository; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; +import com.oracle.svm.core.jfr.throttling.JfrEventThrottling; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.sampler.SamplerBufferPool; import com.oracle.svm.core.sampler.SubstrateSigprofHandler; @@ -88,6 +90,7 @@ public class SubstrateJVM { private final JfrOldObjectProfiler oldObjectProfiler; private final JfrLogging jfrLogging; + private final JfrEventThrottling eventThrottler; private boolean initialized; /* @@ -125,6 +128,7 @@ public SubstrateJVM(List configurations, boolean writeFile) { oldObjectProfiler = new JfrOldObjectProfiler(); jfrLogging = new JfrLogging(); + eventThrottler = new JfrEventThrottling(); initialized = false; recording = false; @@ -191,20 +195,25 @@ public static JfrStackTraceRepository getStackTraceRepo() { } @Fold - public static JfrLogging getJfrLogging() { + public static JfrLogging getLogging() { return get().jfrLogging; } @Fold - public static JfrOldObjectProfiler getJfrOldObjectProfiler() { + public static JfrOldObjectProfiler getOldObjectProfiler() { return get().oldObjectProfiler; } @Fold - public static JfrOldObjectRepository getJfrOldObjectRepository() { + public static JfrOldObjectRepository getOldObjectRepository() { return get().oldObjectRepo; } + @Fold + public static JfrEventThrottling getEventThrottling() { + return get().eventThrottler; + } + @Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true) protected boolean isRecording() { return recording; @@ -683,6 +692,13 @@ public boolean isLarge(JfrEvent event) { return eventSettings[(int) event.getId()].isLarge(); } + /** + * See {@link JVM#setThrottle}. + */ + public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { + return eventThrottler.setThrottle(eventTypeId, eventSampleSize, periodMs); + } + /** * See {@link JVM#setThreshold}. */ @@ -720,7 +736,8 @@ private static class JfrBeginRecordingOperation extends JavaVMOperation { @Override protected void operate() { - SubstrateJVM.getJfrOldObjectProfiler().initialize(); + SubstrateJVM.getOldObjectProfiler().reset(); + JfrAllocationEvents.reset(); SubstrateJVM.get().recording = true; /* Recording is enabled, so JFR events can be triggered at any time. */ @@ -761,7 +778,7 @@ protected void operate() { SubstrateJVM.getThreadLocal().teardown(); SubstrateJVM.getSamplerBufferPool().teardown(); SubstrateJVM.getGlobalMemory().clear(); - SubstrateJVM.getJfrOldObjectProfiler().teardown(); + SubstrateJVM.getOldObjectProfiler().teardown(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java index 1335d0867ee8..69d39956f355 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java @@ -437,11 +437,11 @@ public static boolean setCutoff(long eventTypeId, long cutoffTicks) { return SubstrateJVM.get().setCutoff(eventTypeId, cutoffTicks); } + /** See {@link JVM#setThrottle}. */ @Substitute @TargetElement(onlyWith = JDK22OrLater.class) public static boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { - // 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/Target_jdk_jfr_internal_JVM_JDK21.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM_JDK21.java index 8ae4e6c5914c..66009899f0a4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM_JDK21.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM_JDK21.java @@ -286,10 +286,10 @@ public boolean setCutoff(long eventTypeId, long cutoffTicks) { return SubstrateJVM.get().setCutoff(eventTypeId, cutoffTicks); } + /** See {@link JVM#setThrottle}. */ @Substitute public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { - // 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/ObjectAllocationInNewTLABEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java similarity index 54% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectAllocationInNewTLABEvent.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JfrAllocationEvents.java index dcccd614a7b4..28a4ca1f6df5 100644 --- 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/JfrAllocationEvents.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 @@ -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; @@ -37,16 +38,29 @@ import com.oracle.svm.core.jfr.JfrNativeEventWriterData; import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; +import com.oracle.svm.core.threadlocal.FastThreadLocalLong; + +public class JfrAllocationEvents { + private static final FastThreadLocalLong lastThreadAllocatedBytes = FastThreadLocalFactory.createLong("JfrAllocationEvents.lastThreadAllocatedBytes"); + + public static void reset() { + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + lastThreadAllocatedBytes.set(isolateThread, 0); + } + } -public class ObjectAllocationInNewTLABEvent { public static void emit(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { if (HasJfrSupport.get()) { - emit0(startTicks, hub, allocationSize, tlabSize); + emitObjectAllocationInNewTLAB(startTicks, hub, allocationSize, tlabSize); + emitObjectAllocationSample(startTicks, hub); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { + private static void emitObjectAllocationInNewTLAB(long startTicks, DynamicHub hub, UnsignedWord allocationSize, UnsignedWord tlabSize) { if (JfrEvent.ObjectAllocationInNewTLAB.shouldEmit()) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); @@ -61,4 +75,25 @@ private static void emit0(long startTicks, DynamicHub hub, UnsignedWord allocati JfrNativeEventWriter.endSmallEvent(data); } } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitObjectAllocationSample(long startTicks, DynamicHub hub) { + if (JfrEvent.ObjectAllocationSample.shouldEmit()) { + long threadAllocatedBytes = PlatformThreads.getThreadAllocatedBytes(); + long weight = threadAllocatedBytes - lastThreadAllocatedBytes.get(); + assert weight > 0; + + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ObjectAllocationSample); + JfrNativeEventWriter.putLong(data, startTicks); + JfrNativeEventWriter.putEventThread(data); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ObjectAllocationSample, 0)); + JfrNativeEventWriter.putClass(data, DynamicHub.toClass(hub)); + JfrNativeEventWriter.putLong(data, weight); + JfrNativeEventWriter.endSmallEvent(data); + + lastThreadAllocatedBytes.set(threadAllocatedBytes); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java index dff7aa49896c..82c7183d4333 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectProfiler.java @@ -62,7 +62,7 @@ public void configure(int oldObjectQueueSize) { this.queueSize = oldObjectQueueSize; } - public void initialize() { + public void reset() { this.sampler = new JfrOldObjectSampler(queueSize); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java index b9ac595696f3..10b2ab35d942 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/oldobject/JfrOldObjectSampler.java @@ -171,7 +171,7 @@ private void emitUnchained() { while (cur != null) { Object obj = cur.getReferent(); if (obj != null) { - long objectId = SubstrateJVM.getJfrOldObjectRepository().serializeOldObject(obj); + long objectId = SubstrateJVM.getOldObjectRepository().serializeOldObject(obj); UnsignedWord objectSize = cur.getObjectSize(); long allocationTicks = cur.getAllocationTicks(); long threadId = cur.getThreadId(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java new file mode 100644 index 000000000000..1216e57ab43d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrAdaptiveSampler.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.headers.LibM; +import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jfr.utils.JfrRandom; +import com.oracle.svm.core.thread.JavaSpinLockUtils; +import com.oracle.svm.core.util.TimeUtils; + +import jdk.internal.misc.Unsafe; + +/** + * This class is based on the JDK 23+8 version of the HotSpot class {@code JfrAdaptiveSampler} (see + * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp). + */ +abstract class JfrAdaptiveSampler { + private static final Unsafe U = Unsafe.getUnsafe(); + protected static final long LOCK_OFFSET = U.objectFieldOffset(JfrAdaptiveSampler.class, "lock"); + private static final long ACTIVE_WINDOW_OFFSET = U.objectFieldOffset(JfrAdaptiveSampler.class, "activeWindow"); + + private final JfrRandom prng; + private final JfrSamplerWindow window0; + private final JfrSamplerWindow window1; + + @SuppressWarnings("unused") private volatile int lock; + protected JfrSamplerWindow activeWindow; + protected double avgPopulationSize; + private double ewmaPopulationSizeAlpha; + private long accumulatedDebtCarryLimit; + private long accumulatedDebtCarryCount; + + JfrAdaptiveSampler() { + prng = new JfrRandom(); + + window0 = new JfrSamplerWindow(); + window1 = new JfrSamplerWindow(); + activeWindow = window0; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected boolean sample(long timestampNs) { + boolean expired = activeWindow.isExpired(timestampNs); + if (expired) { + if (JavaSpinLockUtils.tryLock(this, LOCK_OFFSET)) { + /* Recheck under lock if the current window is still expired. */ + if (activeWindow.isExpired(timestampNs)) { + rotate(activeWindow); + } + JavaSpinLockUtils.unlock(this, LOCK_OFFSET); + } + return false; + } + + return activeWindow.sample(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected void reconfigure() { + rotate(activeWindow); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void rotate(JfrSamplerWindow expired) { + JfrSamplerWindow next = getNextWindow(expired); + JfrSamplerParams params = nextWindowParams(); + configure(params, expired, next); + + /* Install the new window atomically. */ + U.putReferenceRelease(this, ACTIVE_WINDOW_OFFSET, next); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected abstract JfrSamplerParams nextWindowParams(); + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void configure(JfrSamplerParams params, JfrSamplerWindow expired, JfrSamplerWindow next) { + if (params.reconfigure) { + expired.copyParams(params); + next.copyParams(params); + + avgPopulationSize = 0; + ewmaPopulationSizeAlpha = computeEwmaAlphaCoefficient(params.windowLookbackCount); + accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(params.windowDurationMs); + accumulatedDebtCarryCount = accumulatedDebtCarryLimit; + params.reconfigure = false; + } + setRate(params, expired, next); + next.initialize(params.windowDurationMs); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static double computeEwmaAlphaCoefficient(long lookbackCount) { + return lookbackCount <= 1 ? 1d : 1d / lookbackCount; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static long computeAccumulatedDebtCarryLimit(long windowDurationMs) { + if (windowDurationMs == 0 || windowDurationMs >= TimeUtils.millisPerSecond) { + return 1; + } + return TimeUtils.millisPerSecond / windowDurationMs; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void setRate(JfrSamplerParams params, JfrSamplerWindow expired, JfrSamplerWindow next) { + long sampleSize = projectSampleSize(params, expired); + if (sampleSize == 0) { + next.setProjectedPopulationSize(0); + return; + } + next.setSamplingInterval(deriveSamplingInterval(sampleSize, expired)); + assert next.getSamplingInterval() >= 1; + next.setProjectedPopulationSize(sampleSize * next.getSamplingInterval()); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long projectSampleSize(JfrSamplerParams params, JfrSamplerWindow expired) { + return params.samplePointsPerWindow + amortizeDebt(expired); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected long amortizeDebt(JfrSamplerWindow expired) { + long accumulatedDebt = expired.getAccumulatedDebt(); + assert accumulatedDebt <= 0; + if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { + accumulatedDebtCarryCount = 1; + return 0; + } + accumulatedDebtCarryCount++; + return -accumulatedDebt; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long deriveSamplingInterval(double sampleSize, JfrSamplerWindow expired) { + assert sampleSize > 0; + double populationSize = projectPopulationSize(expired); + if (populationSize <= sampleSize) { + return 1; + } + assert populationSize > 0; + double projectedProbability = sampleSize / populationSize; + return nextGeometric(projectedProbability); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private double projectPopulationSize(JfrSamplerWindow expired) { + avgPopulationSize = exponentiallyWeightedMovingAverage(expired.getPopulationSize(), ewmaPopulationSizeAlpha, avgPopulationSize); + return avgPopulationSize; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static double exponentiallyWeightedMovingAverage(double currentMeasurement, double alpha, double prevEwma) { + return alpha * currentMeasurement + (1 - alpha) * prevEwma; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long nextGeometric(double p) { + double u = prng.nextUniform(); + assert u >= 0.0; + assert u <= 1.0; + if (u == 0.0) { + u = 0.01; + } else if (u == 1.0) { + u = 0.99; + } + return UninterruptibleUtils.Math.ceilToLong(LibM.log(1.0 - u) / LibM.log(1.0 - p)); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private JfrSamplerWindow getNextWindow(JfrSamplerWindow expired) { + return expired == window0 ? window1 : window0; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottler.java new file mode 100644 index 000000000000..edd65382fd3c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottler.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.thread.JavaSpinLockUtils; +import com.oracle.svm.core.util.TimeUtils; + +/** + * Each event that allows throttling should have its own throttler instance. Multiple threads may + * use the same throttler instance when emitting a particular JFR event type. The throttler uses a + * rotating window scheme where each window represents a time slice. + * + * This class is based on the JDK 23+8 version of the HotSpot class {@code JfrEventThrottler} (see + * hotspot/share/jfr/recorder/services/jfrEventThrottler.hpp). + */ +public class JfrEventThrottler extends JfrAdaptiveSampler { + private static final long MINUTE = TimeUtils.secondsToMillis(60); + private static final long TEN_PER_1000_MS_IN_MINUTES = 600; + private static final long HOUR = 60 * MINUTE; + private static final long TEN_PER_1000_MS_IN_HOURS = 36000; + private static final long DAY = 24 * HOUR; + private static final long TEN_PER_1000_MS_IN_DAYS = 864000; + + private static final long DEFAULT_WINDOW_LOOKBACK_COUNT = 25; + private static final long LOW_RATE_UPPER_BOUND = 9; + private static final long WINDOW_DIVISOR = 5; + + private static final JfrSamplerParams DISABLED_PARAMS = new JfrSamplerParams(); + + private final JfrSamplerParams lastParams = new JfrSamplerParams(); + + private long sampleSize; + private long periodMs; + private boolean disabled; + private boolean update; + + public JfrEventThrottler() { + disabled = true; + } + + @SuppressWarnings("hiding") + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + public void configure(long sampleSize, long periodMs) { + JavaSpinLockUtils.lockNoTransition(this, LOCK_OFFSET); + try { + this.sampleSize = sampleSize; + this.periodMs = periodMs; + this.update = true; + reconfigure(); + } finally { + JavaSpinLockUtils.unlock(this, LOCK_OFFSET); + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isDisabled() { + return disabled; + } + + @Override + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + protected JfrSamplerParams nextWindowParams() { + if (update) { + disabled = isDisabled(sampleSize); + if (!disabled) { + updateParams(); + } + } + return disabled ? DISABLED_PARAMS : lastParams; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void updateParams() { + normalize(); + setSamplePointsAndWindowDuration(lastParams, sampleSize, periodMs); + setWindowLookback(lastParams); + lastParams.reconfigure = true; + update = false; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static boolean isDisabled(long eventSampleSize) { + return eventSampleSize == Target_jdk_jfr_internal_settings_ThrottleSetting.OFF; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void normalize() { + if (periodMs == TimeUtils.millisPerSecond) { + /* Nothing to do. */ + } else if (periodMs == MINUTE) { + if (sampleSize >= TEN_PER_1000_MS_IN_MINUTES) { + sampleSize /= 60; + periodMs /= 60; + } + } else if (periodMs == HOUR) { + if (sampleSize >= TEN_PER_1000_MS_IN_HOURS) { + sampleSize /= 3600; + periodMs /= 3600; + } + } else if (sampleSize >= TEN_PER_1000_MS_IN_DAYS) { + sampleSize /= 86400; + periodMs /= 86400; + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static void setSamplePointsAndWindowDuration(JfrSamplerParams params, long sampleSize, long periodMs) { + assert sampleSize != Target_jdk_jfr_internal_settings_ThrottleSetting.OFF; + assert sampleSize >= 0; + + if (sampleSize <= LOW_RATE_UPPER_BOUND) { + setLowRate(params, sampleSize, periodMs); + } else if (periodMs == MINUTE && sampleSize < TEN_PER_1000_MS_IN_MINUTES) { + setLowRate(params, sampleSize, periodMs); + } else if (periodMs == HOUR && sampleSize < TEN_PER_1000_MS_IN_HOURS) { + setLowRate(params, sampleSize, periodMs); + } else if (periodMs == DAY && sampleSize < TEN_PER_1000_MS_IN_DAYS) { + setLowRate(params, sampleSize, periodMs); + } else { + assert periodMs % WINDOW_DIVISOR == 0; + params.samplePointsPerWindow = sampleSize / WINDOW_DIVISOR; + params.windowDurationMs = periodMs / WINDOW_DIVISOR; + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static void setLowRate(JfrSamplerParams params, long eventSampleSize, long periodMs) { + params.samplePointsPerWindow = eventSampleSize; + params.windowDurationMs = periodMs; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static void setWindowLookback(JfrSamplerParams params) { + if (params.windowDurationMs <= TimeUtils.millisPerSecond) { + params.windowLookbackCount = DEFAULT_WINDOW_LOOKBACK_COUNT; + } else if (params.windowDurationMs == MINUTE) { + params.windowLookbackCount = 5; + } else { + params.windowLookbackCount = 1; + } + } + + public static class TestingBackdoor { + public static boolean sample(JfrEventThrottler throttler) { + return throttler.sample(JfrTicks.now()); + } + + public static void expireActiveWindow(JfrEventThrottler throttler) { + JfrSamplerWindow window = getActiveWindow(throttler); + JfrSamplerWindow.TestingBackdoor.expire(window); + } + + public static long getActiveWindowAccumulatedDebt(JfrEventThrottler throttler) { + return -getActiveWindow(throttler).getAccumulatedDebt(); + } + + public static double getAveragePopulationSize(JfrEventThrottler throttler) { + return throttler.avgPopulationSize; + } + + public static long getWindowLookbackCount(JfrEventThrottler throttler) { + return throttler.lastParams.windowLookbackCount; + } + + public static long getSampleSize(JfrEventThrottler throttler) { + return throttler.sampleSize; + } + + public static long getPeriodMs(JfrEventThrottler throttler) { + return throttler.periodMs; + } + + public static long getWindowsPerPeriod() { + return WINDOW_DIVISOR; + } + + private static JfrSamplerWindow getActiveWindow(JfrEventThrottler throttler) { + return throttler.activeWindow; + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottling.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottling.java new file mode 100644 index 000000000000..6a52969b6976 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrEventThrottling.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.JfrTicks; + +public class JfrEventThrottling { + private final JfrEventThrottler objectAllocationSampleEventThrottler; + + @Platforms(Platform.HOSTED_ONLY.class) + public JfrEventThrottling() { + objectAllocationSampleEventThrottler = new JfrEventThrottler(); + } + + public boolean setThrottle(long eventTypeId, long eventSampleSize, long periodMs) { + if (eventTypeId == JfrEvent.ObjectAllocationSample.getId()) { + objectAllocationSampleEventThrottler.configure(eventSampleSize, periodMs); + } + return true; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean shouldCommit(JfrEvent event) { + if (event == JfrEvent.ObjectAllocationSample) { + return objectAllocationSampleEventThrottler.isDisabled() || objectAllocationSampleEventThrottler.sample(JfrTicks.now()); + } + + assert !event.supportsThrottling(); + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerParams.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerParams.java new file mode 100644 index 000000000000..b2ee6dda5abc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerParams.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; + +/** + * This class is based on the JDK 23+8 version of the HotSpot struct {@code JfrSamplerParams} (see + * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp). + */ +class JfrSamplerParams { + public long samplePointsPerWindow; + public long windowDurationMs; + public long windowLookbackCount; + public boolean reconfigure; + + JfrSamplerParams() { + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void initializeFrom(JfrSamplerParams other) { + this.samplePointsPerWindow = other.samplePointsPerWindow; + this.windowDurationMs = other.windowDurationMs; + this.windowLookbackCount = other.windowLookbackCount; + this.reconfigure = other.reconfigure; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerWindow.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerWindow.java new file mode 100644 index 000000000000..4ff5e7bdea90 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/JfrSamplerWindow.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.throttling; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jfr.JfrTicks; + +/** + * This class is based on the JDK 23+8 version of the HotSpot class {@code JfrSamplerWindow} (see + * hotspot/share/jfr/support/jfrAdaptiveSampler.hpp). + */ +class JfrSamplerWindow { + private final JfrSamplerParams params = new JfrSamplerParams(); + private final UninterruptibleUtils.AtomicLong endTicks = new UninterruptibleUtils.AtomicLong(0); + private final UninterruptibleUtils.AtomicLong measuredPopulationSize = new UninterruptibleUtils.AtomicLong(0); + + private long samplingInterval; + private long projectedPopulationSize; + + JfrSamplerWindow() { + samplingInterval = 1; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean isExpired(long timestampNs) { + return timestampNs >= endTicks.get(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void initialize(long windowDurationMs) { + assert samplingInterval >= 1; + if (windowDurationMs == 0) { + endTicks.set(0); + return; + } + measuredPopulationSize.set(0); + endTicks.set(JfrTicks.now() + JfrTicks.millisToTicks(windowDurationMs)); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void copyParams(JfrSamplerParams other) { + this.params.initializeFrom(other); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public boolean sample() { + long ordinal = measuredPopulationSize.incrementAndGet(); + return ordinal <= projectedPopulationSize && ordinal % samplingInterval == 0; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getSamplingInterval() { + return samplingInterval; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void setSamplingInterval(long value) { + samplingInterval = value; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getPopulationSize() { + return measuredPopulationSize.get(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getProjectedPopulationSize() { + return projectedPopulationSize; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public void setProjectedPopulationSize(long value) { + projectedPopulationSize = value; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getAccumulatedDebt() { + return projectedPopulationSize == 0 ? 0 : (params.samplePointsPerWindow - getMaxSampleSize()) + getDebt(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long getDebt() { + return projectedPopulationSize == 0 ? 0 : getSampleSize() - params.samplePointsPerWindow; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long getMaxSampleSize() { + return projectedPopulationSize / samplingInterval; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private long getSampleSize() { + long size = getPopulationSize(); + return size > projectedPopulationSize ? getMaxSampleSize() : size / samplingInterval; + } + + static class TestingBackdoor { + public static void expire(JfrSamplerWindow window) { + window.endTicks.set(0); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/Target_jdk_jfr_internal_settings_ThrottleSetting.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/Target_jdk_jfr_internal_settings_ThrottleSetting.java new file mode 100644 index 000000000000..9e41a9e103bc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/throttling/Target_jdk_jfr_internal_settings_ThrottleSetting.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.throttling; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jfr.HasJfrSupport; + +@TargetClass(className = "jdk.jfr.internal.settings.ThrottleSetting", onlyWith = HasJfrSupport.class) +final class Target_jdk_jfr_internal_settings_ThrottleSetting { + @Alias static long OFF; +} 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..623c2153f84c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/JfrRandom.java @@ -0,0 +1,60 @@ +/* + * 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 static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import java.util.concurrent.ThreadLocalRandom; + +import com.oracle.svm.core.Uninterruptible; + +/** + * This class is based on the JDK 23+8 version of the HotSpot class {@code JfrPRNG} (see + * hotspot/share/jfr/utilities/jfrRandom.inline.hpp). + */ +public class JfrRandom { + private static final long PrngMult = 25214903917L; + private static final long PrngAdd = 11; + private static final long PrngModPower = 48; + private static final long PrngModMask = (1L << PrngModPower) - 1; + private static final double PrngDivisor = 67108864; + + private long random; + + public JfrRandom() { + random = ThreadLocalRandom.current().nextLong(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public double nextUniform() { + long rnd = (PrngMult * random + PrngAdd) & PrngModMask; + random = rnd; + + int value = (int) (rnd >> (PrngModPower - 26)); + return value / PrngDivisor; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionKey.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionKey.java index e4de02be01d7..78c9f165bd0c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionKey.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionKey.java @@ -33,6 +33,7 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.collections.EnumBitmask; import com.oracle.svm.core.jdk.RuntimeSupport; import jdk.graal.compiler.api.replacements.Fold; @@ -48,14 +49,16 @@ public class RuntimeOptionKey extends OptionKey implements SubstrateOption private final Consumer> validation; private final int flags; + @Platforms(Platform.HOSTED_ONLY.class) public RuntimeOptionKey(T defaultValue, RuntimeOptionKeyFlag... flags) { this(defaultValue, null, flags); } + @Platforms(Platform.HOSTED_ONLY.class) public RuntimeOptionKey(T defaultValue, Consumer> validation, RuntimeOptionKeyFlag... flags) { super(defaultValue); this.validation = validation; - this.flags = computeFlags(flags); + this.flags = EnumBitmask.computeBitmask(flags); } /** @@ -104,20 +107,11 @@ public void validate() { } public boolean shouldCopyToCompilationIsolate() { - return hasFlag(RuntimeOptionKeyFlag.RelevantForCompilationIsolates); + return EnumBitmask.hasBit(flags, RuntimeOptionKeyFlag.RelevantForCompilationIsolates); } public boolean isImmutable() { - return hasFlag(RuntimeOptionKeyFlag.Immutable); - } - - private boolean hasFlag(RuntimeOptionKeyFlag flag) { - return (flags & flagBit(flag)) != 0; - } - - private static int flagBit(RuntimeOptionKeyFlag flag) { - assert flag.ordinal() < 32; - return 1 << flag.ordinal(); + return EnumBitmask.hasBit(flags, RuntimeOptionKeyFlag.Immutable); } @Fold @@ -125,15 +119,6 @@ public T getHostedValue() { return getValue(RuntimeOptionValues.singleton()); } - private static int computeFlags(RuntimeOptionKeyFlag[] flags) { - int result = 0; - for (RuntimeOptionKeyFlag flag : flags) { - assert flag.ordinal() <= Integer.SIZE - 1; - result |= flagBit(flag); - } - return result; - } - public enum RuntimeOptionKeyFlag { /** If this flag is set, then option value is propagated to all compilation isolates. */ RelevantForCompilationIsolates, diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java index ea1c66cf7b93..7befcc109a3b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java @@ -30,6 +30,7 @@ import java.lang.reflect.GenericSignatureFormatError; import com.oracle.svm.core.SubstrateDiagnostics; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.code.FactoryMethodMarker; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.jdk.InternalVMMethod; @@ -170,8 +171,10 @@ public static void deactivateImplicitExceptionsAreFatal() { implicitExceptionsAreFatal.set(implicitExceptionsAreFatal.get() - 1); } - private static void vmErrorIfImplicitExceptionsAreFatal() { - if ((implicitExceptionsAreFatal.get() > 0 || ExceptionUnwind.exceptionsAreFatal()) && !SubstrateDiagnostics.isFatalErrorHandlingThread()) { + private static void vmErrorIfImplicitExceptionsAreFatal(boolean cachedException) { + if (cachedException && SubstrateOptions.ImplicitExceptionWithoutStacktraceIsFatal.getValue()) { + throw VMError.shouldNotReachHere("AssertionError without stack trace."); + } else if ((implicitExceptionsAreFatal.get() > 0 || ExceptionUnwind.exceptionsAreFatal()) && !SubstrateDiagnostics.isFatalErrorHandlingThread()) { throw VMError.shouldNotReachHere("Implicit exception thrown in code where such exceptions are fatal errors"); } } @@ -179,21 +182,21 @@ private static void vmErrorIfImplicitExceptionsAreFatal() { /** Foreign call: {@link #CREATE_NULL_POINTER_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static NullPointerException createNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new NullPointerException(); } /** Foreign call: {@link #CREATE_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException createIntrinsicOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArrayIndexOutOfBoundsException(); } /** Foreign call: {@link #CREATE_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException createOutOfBoundsException(int index, int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArrayIndexOutOfBoundsException("Index " + index + " out of bounds for length " + length); } @@ -201,7 +204,7 @@ private static ArrayIndexOutOfBoundsException createOutOfBoundsException(int ind @SubstrateForeignCallTarget(stubCallingConvention = true) private static ClassCastException createClassCastException(Object object, Object expectedClass) { assert object != null : "null can be cast to any type, so it cannot show up as a source of a ClassCastException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); String expectedClassName; if (expectedClass instanceof Class) { expectedClassName = ((Class) expectedClass).getTypeName(); @@ -215,35 +218,35 @@ private static ClassCastException createClassCastException(Object object, Object @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayStoreException createArrayStoreException(Object value) { assert value != null : "null can be stored into any array, so it cannot show up as a source of an ArrayStoreException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArrayStoreException(value.getClass().getTypeName()); } /** Foreign call: {@link #CREATE_INCOMPATIBLE_CLASS_CHANGE_ERROR}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static IncompatibleClassChangeError createIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new IncompatibleClassChangeError(); } /** Foreign call: {@link #CREATE_ILLEGAL_ARGUMENT_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static IllegalArgumentException createIllegalArgumentException(String message) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new IllegalArgumentException(message); } /** Foreign call: {@link #CREATE_NEGATIVE_ARRAY_SIZE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static NegativeArraySizeException createNegativeArraySizeException(int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new NegativeArraySizeException(String.valueOf(length)); } /** Foreign call: {@link #CREATE_DIVISION_BY_ZERO_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createDivisionByZeroException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("/ by zero"); } @@ -252,14 +255,14 @@ private static ArithmeticException createDivisionByZeroException() { /** Foreign call: {@link #CREATE_INTEGER_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createIntegerOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("integer overflow"); } /** Foreign call: {@link #CREATE_LONG_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException createLongOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new ArithmeticException("long overflow"); } @@ -268,42 +271,42 @@ private static ArithmeticException createLongOverflowException() { /** Foreign call: {@link #CREATE_ASSERTION_ERROR_NULLARY}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError createAssertionErrorNullary() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new AssertionError(); } /** Foreign call: {@link #CREATE_ASSERTION_ERROR_OBJECT}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError createAssertionErrorObject(Object detailMessage) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); return new AssertionError(detailMessage); } /** Foreign call: {@link #THROW_NEW_NULL_POINTER_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new NullPointerException(); } /** Foreign call: {@link #THROW_NEW_INTRINSIC_OUT_OF_BOUNDS_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIntrinsicOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayIndexOutOfBoundsException(); } /** Foreign call: {@link #THROW_NEW_OUT_OF_BOUNDS_EXCEPTION_WITH_ARGS}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewOutOfBoundsExceptionWithArgs(int index, int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayIndexOutOfBoundsException("Index " + index + " out of bounds for length " + length); } /** Foreign call: {@link #THROW_NEW_CLASS_CAST_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ClassCastException(); } @@ -311,7 +314,7 @@ private static void throwNewClassCastException() { @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewClassCastExceptionWithArgs(Object object, Object expectedClass) { assert object != null : "null can be cast to any type, so it cannot show up as a source of a ClassCastException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); String expectedClassName; if (expectedClass instanceof Class) { expectedClassName = ((Class) expectedClass).getTypeName(); @@ -324,7 +327,7 @@ private static void throwNewClassCastExceptionWithArgs(Object object, Object exp /** Foreign call: {@link #THROW_NEW_ARRAY_STORE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayStoreException(); } @@ -332,42 +335,42 @@ private static void throwNewArrayStoreException() { @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArrayStoreExceptionWithArgs(Object value) { assert value != null : "null can be stored into any array, so it cannot show up as a source of an ArrayStoreException"; - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArrayStoreException(value.getClass().getTypeName()); } /** Foreign call: {@link #THROW_NEW_INCOMPATIBLE_CLASS_CHANGE_ERROR}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new IncompatibleClassChangeError(); } /** Foreign call: {@link #THROW_NEW_ILLEGAL_ARGUMENT_EXCEPTION_WITH_ARGS}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIllegalArgumentExceptionWithArgs(String message) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new IllegalArgumentException(message); } /** Foreign call: {@link #THROW_NEW_NEGATIVE_ARRAY_SIZE_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewNegativeArraySizeException(int length) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new NegativeArraySizeException(String.valueOf(length)); } /** Foreign call: {@link #THROW_NEW_ARITHMETIC_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException(); } /** Foreign call: {@link #THROW_NEW_DIVISION_BY_ZERO_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewDivisionByZeroException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("/ by zero"); } @@ -376,14 +379,14 @@ private static void throwNewDivisionByZeroException() { /** Foreign call: {@link #THROW_NEW_INTEGER_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewIntegerOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("integer overflow"); } /** Foreign call: {@link #THROW_NEW_LONG_OVERFLOW_EXCEPTION}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewLongOverflowException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new ArithmeticException("long overflow"); } @@ -392,14 +395,14 @@ private static void throwNewLongOverflowException() { /** Foreign call: {@link #THROW_NEW_ASSERTION_ERROR_NULLARY}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewAssertionErrorNullary() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new AssertionError(); } /** Foreign call: {@link #THROW_NEW_ASSERTION_ERROR_OBJECT}. */ @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwNewAssertionErrorObject(Object detailMessage) { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(false); throw new AssertionError(detailMessage); } @@ -407,7 +410,7 @@ private static void throwNewAssertionErrorObject(Object detailMessage) { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static NullPointerException getCachedNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_NULL_POINTER_EXCEPTION; } @@ -415,7 +418,7 @@ private static NullPointerException getCachedNullPointerException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayIndexOutOfBoundsException getCachedOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_OUT_OF_BOUNDS_EXCEPTION; } @@ -423,7 +426,7 @@ private static ArrayIndexOutOfBoundsException getCachedOutOfBoundsException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ClassCastException getCachedClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_CLASS_CAST_EXCEPTION; } @@ -431,7 +434,7 @@ private static ClassCastException getCachedClassCastException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArrayStoreException getCachedArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ARRAY_STORE_EXCEPTION; } @@ -439,7 +442,7 @@ private static ArrayStoreException getCachedArrayStoreException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static IncompatibleClassChangeError getCachedIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_INCOMPATIBLE_CLASS_CHANGE_ERROR; } @@ -447,7 +450,7 @@ private static IncompatibleClassChangeError getCachedIncompatibleClassChangeErro @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static IllegalArgumentException getCachedIllegalArgumentException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ILLEGAL_ARGUMENT_EXCEPTION; } @@ -455,7 +458,7 @@ private static IllegalArgumentException getCachedIllegalArgumentException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static NegativeArraySizeException getCachedNegativeArraySizeException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_NEGATIVE_ARRAY_SIZE_EXCEPTION; } @@ -463,7 +466,7 @@ private static NegativeArraySizeException getCachedNegativeArraySizeException() @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static ArithmeticException getCachedArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ARITHMETIC_EXCEPTION; } @@ -471,7 +474,7 @@ private static ArithmeticException getCachedArithmeticException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static AssertionError getCachedAssertionError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); return CACHED_ASSERTION_ERROR; } @@ -479,7 +482,7 @@ private static AssertionError getCachedAssertionError() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedNullPointerException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_NULL_POINTER_EXCEPTION; } @@ -487,7 +490,7 @@ private static void throwCachedNullPointerException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedOutOfBoundsException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_OUT_OF_BOUNDS_EXCEPTION; } @@ -495,7 +498,7 @@ private static void throwCachedOutOfBoundsException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedClassCastException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_CLASS_CAST_EXCEPTION; } @@ -503,7 +506,7 @@ private static void throwCachedClassCastException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedArrayStoreException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ARRAY_STORE_EXCEPTION; } @@ -511,7 +514,7 @@ private static void throwCachedArrayStoreException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedIncompatibleClassChangeError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_INCOMPATIBLE_CLASS_CHANGE_ERROR; } @@ -519,7 +522,7 @@ private static void throwCachedIncompatibleClassChangeError() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedIllegalArgumentException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ILLEGAL_ARGUMENT_EXCEPTION; } @@ -527,7 +530,7 @@ private static void throwCachedIllegalArgumentException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedNegativeArraySizeException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_NEGATIVE_ARRAY_SIZE_EXCEPTION; } @@ -535,7 +538,7 @@ private static void throwCachedNegativeArraySizeException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedArithmeticException() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ARITHMETIC_EXCEPTION; } @@ -543,7 +546,7 @@ private static void throwCachedArithmeticException() { @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Called to report an implicit exception in code that must not allocate.") @SubstrateForeignCallTarget(stubCallingConvention = true) private static void throwCachedAssertionError() { - vmErrorIfImplicitExceptionsAreFatal(); + vmErrorIfImplicitExceptionsAreFatal(true); throw CACHED_ASSERTION_ERROR; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java index 344c6895b28a..ee0355ce92b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java @@ -189,12 +189,17 @@ protected PlatformThreads() { mainGroupThreadsArray[0] = mainThread; } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static long getThreadAllocatedBytes() { + return Heap.getHeap().getThreadAllocatedMemory(CurrentIsolate.getCurrentThread()); + } + @Uninterruptible(reason = "Thread locks/holds the THREAD_MUTEX.") public static long getThreadAllocatedBytes(long javaThreadId) { // Accessing the value for the current thread is fast. Thread curThread = PlatformThreads.currentThread.get(); if (curThread != null && JavaThreads.getThreadId(curThread) == javaThreadId) { - return Heap.getHeap().getThreadAllocatedMemory(CurrentIsolate.getCurrentThread()); + return getThreadAllocatedBytes(); } // If the value of another thread is accessed, then we need to do a slow lookup. diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/AbstractJfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/AbstractJfrTest.java index 0767bb74b2ea..3d1ba5c5da32 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 @@ -99,7 +99,7 @@ protected static void checkRecording(EventValidator validator, Path path, JfrRec } } - private static List getEvents(Path path, String[] testedEvents, boolean validateTestedEventsOnly) throws IOException { + protected static List getEvents(Path path, String[] testedEvents, boolean validateTestedEventsOnly) 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 new file mode 100644 index 000000000000..513363b93393 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThrottler.java @@ -0,0 +1,357 @@ +/* + * 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.test.jfr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntSupplier; + +import org.junit.Test; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.genscavenge.HeapParameters; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.throttling.JfrEventThrottler; +import com.oracle.svm.core.jfr.throttling.JfrEventThrottler.TestingBackdoor; +import com.oracle.svm.core.util.TimeUtils; +import com.oracle.svm.core.util.UnsignedUtils; + +import jdk.jfr.Recording; + +public class TestThrottler extends JfrRecordingTest { + private static final long WINDOWS_PER_PERIOD = TestingBackdoor.getWindowsPerPeriod(); + private static final long WINDOW_DURATION_MS = TimeUtils.secondsToMillis(3600); + + /** + * This is the simplest test that ensures that sampling stops after the cap is hit. All sampling + * is done within the first window. + */ + @Test + public void testCapSingleThread() { + long samplesPerWindow = 10; + JfrEventThrottler throttler = newEventThrottler(samplesPerWindow); + + for (int i = 0; i < 3 * samplesPerWindow; i++) { + boolean sample = TestingBackdoor.sample(throttler); + assertEquals(i < samplesPerWindow, sample); + } + } + + /** + * This test ensures that sampling stops after the cap is hit, even when multiple threads are + * doing sampling. It also checks that sampling can continue after rotating the window. + */ + @Test + public void testCapConcurrent() throws InterruptedException { + long samplesPerWindow = 100000; + JfrEventThrottler throttler = newEventThrottler(samplesPerWindow); + + for (int i = 0; i < 3; i++) { + /* Start a couple threads and sample concurrently. */ + int numThreads = 8; + Thread[] threads = new Thread[numThreads]; + AtomicInteger countedSamples = new AtomicInteger(0); + + for (int j = 0; j < numThreads; j++) { + Thread worker = new Thread(() -> { + for (int k = 0; k < samplesPerWindow; k++) { + boolean sample = TestingBackdoor.sample(throttler); + if (sample) { + countedSamples.incrementAndGet(); + } + } + }); + worker.start(); + threads[j] = worker; + } + + /* Wait until the threads finish. */ + for (Thread thread : threads) { + thread.join(); + } + + assertTrue("Sampling failed!", countedSamples.get() > 0); + assertTrue("Too many samples taken!", countedSamples.get() <= samplesPerWindow); + + /* Repeat the test a few times. */ + countedSamples.set(0); + expireAndRotate(throttler); + } + } + + /** Tests normalization of sample size and period. */ + @Test + public void testNormalization() { + long sampleSize = 10 * 600; + long periodMs = TimeUtils.secondsToMillis(60); + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(sampleSize, periodMs); + assertTrue(TestingBackdoor.getPeriodMs(throttler) == TimeUtils.millisPerSecond && TestingBackdoor.getSampleSize(throttler) == sampleSize / 60); + + sampleSize = 10 * 3600; + periodMs = TimeUtils.secondsToMillis(3600); + throttler.configure(sampleSize, periodMs); + assertTrue(TestingBackdoor.getPeriodMs(throttler) == TimeUtils.millisPerSecond && TestingBackdoor.getSampleSize(throttler) == sampleSize / 3600); + } + + @Test + public void testZeroRateSampling() { + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(0, TimeUtils.secondsToMillis(2)); + assertFalse(TestingBackdoor.sample(throttler)); + + /* Reconfigure the throttler. */ + throttler.configure(10, TimeUtils.secondsToMillis(2)); + assertTrue(TestingBackdoor.sample(throttler)); + } + + /** Checks that no ObjectAllocationSample events are emitted when the sampling rate is 0. */ + @Test + public void testZeroRateRecording() throws IOException { + /* Test applying throttling settings to an actual recording. */ + Recording recording = new Recording(); + recording.setDestination(createTempJfrFile()); + recording.enable(JfrEvent.ObjectAllocationSample.getName()).with("throttle", "0/s"); + recording.start(); + + int alignedHeapChunkSize = UnsignedUtils.safeToInt(HeapParameters.getAlignedHeapChunkSize()); + allocateCharArray(alignedHeapChunkSize); + + recording.stop(); + recording.close(); + + /* Call getEvents directly because we expect zero events. */ + assertEquals(0, getEvents(recording.getDestination(), new String[]{JfrEvent.ObjectAllocationSample.getName()}, true).size()); + } + + /** + * Tests the EWMA calculation. Assumes windowLookback == 25 and windowDuration < 1s. + */ + @Test + public void testEWMA() { + long samplesPerWindow = 10; + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(samplesPerWindow * WINDOWS_PER_PERIOD, TimeUtils.millisPerSecond); + + assertEquals(25, TestingBackdoor.getWindowLookbackCount(throttler)); + + long[] numSamples = {310, 410, 610, 310, 910, 420, 770, 290, 880, 640, 220, 110, 330, 590}; + long[] expectedProjectedPopulation = {12, 28, 51, 61, 95, 108, 135, 141, 170, 189, 190, 187, 193, 209}; + for (int i = 0; i < numSamples.length; i++) { + for (int j = 0; j < numSamples[i]; j++) { + TestingBackdoor.sample(throttler); + } + expireAndRotate(throttler); + + double averagePopulationSize = TestingBackdoor.getAveragePopulationSize(throttler); + assertEquals(expectedProjectedPopulation[i], (long) averagePopulationSize); + } + } + + /** Ensure debt is being calculated as expected. */ + @Test + public void testDebt() { + long samplesPerWindow = 10; + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(samplesPerWindow * WINDOWS_PER_PERIOD, TimeUtils.millisPerSecond); + + /* Sample for some time */ + for (int i = 0; i < 50; i++) { + for (int j = 0; j < samplesPerWindow; j++) { + assertTrue(TestingBackdoor.sample(throttler)); + } + + assertEquals(0, TestingBackdoor.getActiveWindowAccumulatedDebt(throttler)); + expireAndRotate(throttler); + } + + /* Do not sample and rotate the window right away. */ + expireAndRotate(throttler); + + /* Debt should be at least samplesPerWindow because we took no samples last window. */ + long debt = TestingBackdoor.getActiveWindowAccumulatedDebt(throttler); + assertTrue("Should have debt from under sampling.", debt >= samplesPerWindow); + + /* Do some but not enough sampling to increase debt. */ + for (int i = 0; i < samplesPerWindow / 2; i++) { + TestingBackdoor.sample(throttler); + } + expireAndRotate(throttler); + assertTrue("Should have debt from under sampling.", TestingBackdoor.getActiveWindowAccumulatedDebt(throttler) >= debt + samplesPerWindow / 2); + + // Window lookback is 25. Do not sample for 25 windows. + for (int i = 0; i < 25; i++) { + 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++) { + TestingBackdoor.sample(throttler); + } + assertFalse(TestingBackdoor.sample(throttler)); + assertEquals("Should not have any debt remaining.", 0, TestingBackdoor.getActiveWindowAccumulatedDebt(throttler)); + } + + @Test + public void testDistributionUniform() { + int maxPopPerWindow = 2000; + int minPopPerWindow = 2; + int expectedSamplesPerWindow = 50; + testDistribution(() -> ThreadLocalRandom.current().nextInt(minPopPerWindow, maxPopPerWindow + 1), expectedSamplesPerWindow, 0.05); + } + + @Test + public void testDistributionHighRate() { + int maxPopPerWindow = 2000; + int expectedSamplesPerWindow = 50; + testDistribution(() -> maxPopPerWindow, expectedSamplesPerWindow, 0.02); + } + + @Test + public void testDistributionLowRate() { + int minPopPerWindow = 2; + testDistribution(() -> minPopPerWindow, minPopPerWindow, 0.05); + } + + @Test + public void testDistributionEarlyBurst() { + int maxPopulationPerWindow = 2000; + int expectedSamplesPerWindow = 50; + int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + AtomicInteger count = new AtomicInteger(1); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 1 ? maxPopulationPerWindow : 0, expectedSamplesPerWindow, 0.9); + } + + @Test + public void testDistributionMidBurst() { + int maxPopulationPerWindow = 2000; + int expectedSamplesPerWindow = 50; + int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + AtomicInteger count = new AtomicInteger(1); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 5 ? maxPopulationPerWindow : 0, expectedSamplesPerWindow, 0.5); + } + + @Test + public void testDistributionLateBurst() { + int maxPopulationPerWindow = 2000; + int expectedSamplesPerWindow = 50; + int accumulatedDebtCarryLimit = 10; // 1000 / windowDurationMs + AtomicInteger count = new AtomicInteger(1); + testDistribution(() -> count.getAndIncrement() % accumulatedDebtCarryLimit == 0 ? maxPopulationPerWindow : 0, expectedSamplesPerWindow, 0.0); + } + + /** + * This is a more involved test that checks the sample distribution. It is based on + * JfrGTestAdaptiveSampling in the OpenJDK. + */ + private static void testDistribution(IntSupplier incomingPopulation, int samplePointsPerWindow, double errorFactor) { + int distributionSlots = 100; + int windowDurationMs = 100; + int windowCount = 10000; + + int samplesPerWindow = 50; + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(samplesPerWindow * WINDOWS_PER_PERIOD, windowDurationMs * WINDOWS_PER_PERIOD); + + int[] population = new int[distributionSlots]; + int[] sample = new int[distributionSlots]; + + int populationSize = 0; + int sampleSize = 0; + for (int t = 0; t < windowCount; t++) { + int windowPop = incomingPopulation.getAsInt(); + for (int i = 0; i < windowPop; i++) { + populationSize++; + int index = ThreadLocalRandom.current().nextInt(0, 100); + population[index] += 1; + if (TestingBackdoor.sample(throttler)) { + sampleSize++; + sample[index] += 1; + } + } + expireAndRotate(throttler); + } + + int targetSampleSize = samplePointsPerWindow * windowCount; + int expectedSamples = samplesPerWindow * windowCount; + expectNear(targetSampleSize, sampleSize, expectedSamples * errorFactor); + assertDistributionProperties(distributionSlots, population, sample, populationSize, sampleSize); + } + + private static void expectNear(double value1, double value2, double error) { + 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); + expectNear(populationStdev, sampleStdev, 0.5); // 0.5 value to match Hotspot test + expectNear(populationMean, sampleMean, populationStdev); + } + + @NeverInline("Prevent optimizations.") + private static char[] allocateCharArray(int length) { + return new char[length]; + } + + private static JfrEventThrottler newEventThrottler(long samplesPerWindow) { + JfrEventThrottler throttler = new JfrEventThrottler(); + throttler.configure(samplesPerWindow * WINDOWS_PER_PERIOD, WINDOW_DURATION_MS * WINDOWS_PER_PERIOD); + return throttler; + } + + private static void expireAndRotate(JfrEventThrottler throttler) { + TestingBackdoor.expireActiveWindow(throttler); + assertFalse("Should have rotated not sampled!", TestingBackdoor.sample(throttler)); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java index 1d9b91adb4b0..c4e7e5882333 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/JfrOldObjectTest.java @@ -85,7 +85,7 @@ protected void testSampling(Object obj, int arrayLength, EventValidator validato boolean success; long endTime = System.currentTimeMillis() + TimeUtils.secondsToMillis(5); do { - success = SubstrateJVM.getJfrOldObjectProfiler().sample(obj, WordFactory.unsigned(1024 * 1024 * 1024), arrayLength); + success = SubstrateJVM.getOldObjectProfiler().sample(obj, WordFactory.unsigned(1024 * 1024 * 1024), arrayLength); } while (!success && System.currentTimeMillis() < endTime); Assert.assertTrue("Timed out waiting for sampling to complete", success); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java index 1817408af507..5a1096bebd53 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/oldobject/TestOldObjectProfiler.java @@ -181,7 +181,7 @@ public void testEvictOldest() { private static JfrOldObjectProfiler newProfiler(int queueSize) { JfrOldObjectProfiler profiler = new JfrOldObjectProfiler(); profiler.configure(queueSize); - profiler.initialize(); + profiler.reset(); return profiler; }