From 06c6404e12191a588e06eec5a10afbfec2bd4db3 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 26 Feb 2024 16:36:32 -0500 Subject: [PATCH] Add NMT events to JFR and add peak tracking and some tests. add peak tracking and tests --- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 2 + .../com/oracle/svm/core/jfr/JfrFeature.java | 4 +- .../core/jfr/JfrNmtCategorySerializer.java | 49 ++++++++++ .../src/com/oracle/svm/core/jfr/JfrType.java | 3 +- .../EveryChunkNativePeriodicEvents.java | 67 ++++++++++++- .../events/NativeMemoryUsagePeakEvent.java | 44 +++++++++ .../NativeMemoryUsageTotalPeakEvent.java | 43 ++++++++ .../svm/core/nmt/NativeMemoryTracking.java | 39 +++++++- .../svm/core/nmt/NmtMallocMemoryInfo.java | 47 +++++++-- .../oracle/svm/test/jfr/TestNmtEvents.java | 97 +++++++++++++++++++ .../svm/test/jfr/utils/JfrFileParser.java | 2 + .../NmtCategoryConstantPoolParser.java | 49 ++++++++++ .../test/nmt/NativeMemoryTrackingTests.java | 18 ++++ 13 files changed, 453 insertions(+), 11 deletions(-) create mode 100755 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java index 44e4de3e90e6..49c4355ae3a8 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 @@ -71,6 +71,8 @@ public final class JfrEvent { 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); + public static final JfrEvent NativeMemoryUsage = create("jdk.NativeMemoryUsage"); + public static final JfrEvent NativeMemoryUsageTotal = create("jdk.NativeMemoryUsageTotal"); private final long id; private final String name; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java index 3a7e70b4729c..b1b7212197d9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java @@ -172,7 +172,9 @@ public void afterRegistration(AfterRegistrationAccess access) { JfrSerializerSupport.get().register(new JfrGCNameSerializer()); JfrSerializerSupport.get().register(new JfrVMOperationNameSerializer()); JfrSerializerSupport.get().register(new JfrGCWhenSerializer()); - + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + JfrSerializerSupport.get().register(new JfrNmtCategorySerializer()); + } ThreadListenerSupport.get().register(SubstrateJVM.getThreadLocal()); RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java new file mode 100755 index 000000000000..176cf3b0f178 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNmtCategorySerializer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.jfr; + +import com.oracle.svm.core.nmt.NmtCategory; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +public class JfrNmtCategorySerializer implements JfrSerializer { + @Platforms(Platform.HOSTED_ONLY.class) + public JfrNmtCategorySerializer() { + } + + @Override + public void write(JfrChunkWriter writer) { + writer.writeCompressedLong(JfrType.NMTType.getId()); + + NmtCategory[] nmtCategories = NmtCategory.values(); + writer.writeCompressedLong(nmtCategories.length); + for (int i = 0; i < nmtCategories.length; i++) { + writer.writeCompressedInt(i); + writer.writeString(nmtCategories[i].getName()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java index 4650fda41b87..b3c8603704e6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java @@ -47,7 +47,8 @@ public enum JfrType { GCWhen("jdk.types.GCWhen"), VMOperation("jdk.types.VMOperationType"), MonitorInflationCause("jdk.types.InflateCause"), - OldObject("jdk.types.OldObject"); + OldObject("jdk.types.OldObject"), + NMTType("jdk.types.NMTType"); private final long id; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java index db0a82d9c853..20507e5224b6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, 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 @@ -38,8 +39,11 @@ import com.oracle.svm.core.jfr.JfrNativeEventWriterData; import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.nmt.NmtCategory; +import com.oracle.svm.core.nmt.NativeMemoryTracking; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.VMInspectionOptions; import jdk.jfr.Event; import jdk.jfr.Name; @@ -54,6 +58,7 @@ public static void emit() { emitPhysicalMemory(); emitClassLoadingStatistics(); emitPerThreadEvents(); + emitNativeMemoryTrackingEvents(); } @Uninterruptible(reason = "Accesses a JFR buffer.") @@ -102,6 +107,66 @@ private static void emitClassLoadingStatistics() { } } + /** + * Emit events for NativeMemoryUsage and NativeMemoryUsageTotal. We do not guarantee consistent + * measurements across NMT categories and the total. Each individual NMT category uses atomic + * counters which may change while we are in this method. Similar to OpenJDK, it is only a + * best-effort approach. + */ + private static void emitNativeMemoryTrackingEvents() { + if (VMInspectionOptions.hasNativeMemoryTrackingSupport()) { + emitJdkNmtEvents(NmtCategory.values()); + emitNmtPeakEvents(); + } + } + + /** These peak events are specific to SubstrateVM. */ + private static void emitNmtPeakEvents() { + NativeMemoryUsageTotalPeakEvent nmtTotalPeakEvent = new NativeMemoryUsageTotalPeakEvent(); + nmtTotalPeakEvent.countAtPeak = NativeMemoryTracking.singleton().getCountAtTotalPeakUsage(); + nmtTotalPeakEvent.peakUsage = NativeMemoryTracking.singleton().getPeakTotalUsedMemory(); + nmtTotalPeakEvent.commit(); + + for (NmtCategory nmtCategory : NmtCategory.values()) { + NativeMemoryUsagePeakEvent nmtPeakEvent = new NativeMemoryUsagePeakEvent(); + nmtPeakEvent.type = nmtCategory.getName(); + nmtPeakEvent.countAtPeak = NativeMemoryTracking.singleton().getCountAtPeakUsage(nmtCategory); + nmtPeakEvent.peakUsage = NativeMemoryTracking.singleton().getPeakUsedMemory(nmtCategory); + nmtPeakEvent.commit(); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitJdkNmtEvents(NmtCategory[] nmtCategories) { + long timestamp = JfrTicks.elapsedTicks(); + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + + if (JfrEvent.NativeMemoryUsage.shouldEmit()) { + for (NmtCategory nmtCategory : nmtCategories) { + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.NativeMemoryUsage); + JfrNativeEventWriter.putLong(data, timestamp); + /* Category */ + JfrNativeEventWriter.putLong(data, nmtCategory.ordinal()); + /* Reserved usage */ + JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getUsedMemory(nmtCategory)); + /* Committed usage */ + JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getUsedMemory(nmtCategory)); + JfrNativeEventWriter.endSmallEvent(data); + } + } + if (JfrEvent.NativeMemoryUsageTotal.shouldEmit()) { + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.NativeMemoryUsageTotal); + JfrNativeEventWriter.putLong(data, timestamp); + /* Reserved usage */ + JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getTotalUsedMemory()); + /* Committed usage */ + JfrNativeEventWriter.putLong(data, NativeMemoryTracking.singleton().getTotalUsedMemory()); + JfrNativeEventWriter.endSmallEvent(data); + } + + } + private static void emitPerThreadEvents() { if (needsVMOperation()) { EmitPeriodicPerThreadEventsOperation vmOp = new EmitPeriodicPerThreadEventsOperation(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java new file mode 100644 index 000000000000..8864ac29ec52 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsagePeakEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name("svm.NativeMemoryUsagePeak") +@Label("Native Memory Usage Peak") +@Description("Information about native memory peak usage of committed virtual memory and malloc.") +@Category("Native Image") +@StackTrace(false) +public class NativeMemoryUsagePeakEvent extends Event { + @Label("Memory Type") public String type; + @Label("Peak Usage") public long peakUsage; + @Label("Count At Peak") public long countAtPeak; +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java new file mode 100644 index 000000000000..91a978518de0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/NativeMemoryUsageTotalPeakEvent.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name("svm.NativeMemoryUsageTotalPeak") +@Label("Native Memory Usage Total Peak") +@Description("Information about native memory peak usage of committed virtual memory and malloc.") +@Category("Native Image") +@StackTrace(false) +public class NativeMemoryUsageTotalPeakEvent extends Event { + @Label("Peak Usage") public long peakUsage; + @Label("Count At Peak") public long countAtPeak; +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java index 616cbe1944d0..4a3efa90a907 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NativeMemoryTracking.java @@ -144,10 +144,41 @@ private static Pointer getInnerPointer(NmtMallocHeader mallocHeader) { return ((Pointer) mallocHeader).add(sizeOfNmtHeader()); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) public long getUsedMemory(NmtCategory category) { return mallocMemorySnapshot.getInfoByCategory(category).getUsed(); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getPeakUsedMemory(NmtCategory category) { + return mallocMemorySnapshot.getInfoByCategory(category).getPeakUsed(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getCountAtPeakUsage(NmtCategory category) { + return mallocMemorySnapshot.getInfoByCategory(category).getCountAtPeakUsage(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getTotalCount() { + return mallocMemorySnapshot.getTotalInfo().getCount(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getTotalUsedMemory() { + return mallocMemorySnapshot.getTotalInfo().getUsed(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getPeakTotalUsedMemory() { + return mallocMemorySnapshot.getTotalInfo().getPeakUsed(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public long getCountAtTotalPeakUsage() { + return mallocMemorySnapshot.getTotalInfo().getCountAtPeakUsage(); + } + public static RuntimeSupport.Hook shutdownHook() { return isFirstIsolate -> NativeMemoryTracking.singleton().printStatistics(); } @@ -156,13 +187,17 @@ public void printStatistics() { if (VMInspectionOptions.PrintNMTStatistics.getValue()) { System.out.println(); System.out.println("Native memory tracking"); - System.out.println(" Total used memory: " + mallocMemorySnapshot.getTotalInfo().getUsed() + " bytes"); - System.out.println(" Total alive allocations: " + mallocMemorySnapshot.getTotalInfo().getCount()); + System.out.println(" Peak total used memory: " + getPeakTotalUsedMemory() + " bytes"); + System.out.println(" Total alive allocations at peak usage: " + getCountAtTotalPeakUsage()); + System.out.println(" Total used memory: " + getTotalUsedMemory() + " bytes"); + System.out.println(" Total alive allocations: " + getTotalCount()); for (int i = 0; i < NmtCategory.values().length; i++) { String name = NmtCategory.values()[i].getName(); NmtMallocMemoryInfo info = mallocMemorySnapshot.getInfoByCategory(i); + System.out.println(" " + name + " peak used memory: " + info.getPeakUsed() + " bytes"); + System.out.println(" " + name + " alive allocations at peak: " + info.getCountAtPeakUsage()); System.out.println(" " + name + " used memory: " + info.getUsed() + " bytes"); System.out.println(" " + name + " alive allocations: " + info.getCount()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java index 7a54c3b491ca..6bf524b07cf0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nmt/NmtMallocMemoryInfo.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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,8 @@ package com.oracle.svm.core.nmt; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; @@ -33,32 +35,65 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicLong; +import jdk.internal.misc.Unsafe; + class NmtMallocMemoryInfo { + private static final Unsafe U = Unsafe.getUnsafe(); + protected static final long PEAK_USAGE_OFFSET = U.objectFieldOffset(NmtMallocMemoryInfo.class, "peakUsage"); private final AtomicLong count = new AtomicLong(0); private final AtomicLong used = new AtomicLong(0); + private volatile long peakUsage; + private volatile long peakCount; @Platforms(Platform.HOSTED_ONLY.class) NmtMallocMemoryInfo() { } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) void track(UnsignedWord allocationSize) { - count.incrementAndGet(); - used.addAndGet(allocationSize.rawValue()); + /* + * Similar to Hotspot, we only make a best effort to record the count at the time of the + * peak. Observing the memory used and count is not together atomic. + */ + updatePeak(used.addAndGet(allocationSize.rawValue()), count.incrementAndGet()); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void updatePeak(long newUsed, long newCount) { + long expectedPeakUsage = peakUsage; + while (expectedPeakUsage < newUsed) { + if (U.compareAndSetLong(this, PEAK_USAGE_OFFSET, expectedPeakUsage, newUsed)) { + peakCount = newCount; + return; + } + expectedPeakUsage = peakUsage; + } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) void untrack(UnsignedWord allocationSize) { long lastCount = count.decrementAndGet(); long lastSize = used.addAndGet(-allocationSize.rawValue()); assert lastSize >= 0 && lastCount >= 0; } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) long getUsed() { return used.get(); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) long getCount() { return count.get(); } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + long getPeakUsed() { + return peakUsage; + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + long getCountAtPeakUsage() { + return peakCount; + } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java new file mode 100644 index 000000000000..e2cb5f4fdeac --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestNmtEvents.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, 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.assertTrue; + +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.memory.NativeMemory; +import com.oracle.svm.core.nmt.NmtCategory; + +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import org.junit.Test; + +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +public class TestNmtEvents extends JfrRecordingTest { + private static final int ALLOCATION_SIZE = 1024 * 16; + + @Test + public void test() throws Throwable { + String[] events = new String[]{ + JfrEvent.NativeMemoryUsage.getName(), + JfrEvent.NativeMemoryUsageTotal.getName(), + "svm.NativeMemoryUsageTotalPeak", + "svm.NativeMemoryUsagePeak" + }; + + Recording recording = startRecording(events); + + Pointer ptr = NativeMemory.malloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); + + /* Force a chunk rotation to trigger periodic event emission. */ + recording.dump(createTempJfrFile()); + + NativeMemory.free(ptr); + + stopRecording(recording, TestNmtEvents::validateEvents); + } + + private static void validateEvents(List events) { + boolean foundNativeMemoryUsage = false; + boolean foundNativeMemoryUsageTotal = false; + boolean foundNativeMemoryUsageTotalPeak = false; + boolean foundNativeMemoryUsagePeak = false; + + assertTrue(events.size() >= 4); + for (RecordedEvent e : events) { + if (e.getEventType().getName().equals(JfrEvent.NativeMemoryUsage.getName()) && + e.getString("type").equals(NmtCategory.Code.getName())) { + foundNativeMemoryUsage = true; + + } + if (e.getEventType().getName().equals("svm.NativeMemoryUsageTotalPeak")) { + foundNativeMemoryUsageTotalPeak = true; + } + if (e.getEventType().getName().equals("svm.NativeMemoryUsagePeak") && + e.getString("type").equals(NmtCategory.Code.getName())) { + foundNativeMemoryUsagePeak = true; + } + if (e.getEventType().getName().equals(JfrEvent.NativeMemoryUsageTotal.getName())) { + foundNativeMemoryUsageTotal = true; + } + } + assertTrue(foundNativeMemoryUsage); + assertTrue(foundNativeMemoryUsageTotal); + assertTrue(foundNativeMemoryUsagePeak); + assertTrue(foundNativeMemoryUsageTotalPeak); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index 4898f751a865..f2b17a743fba 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -54,6 +54,7 @@ import com.oracle.svm.test.jfr.utils.poolparsers.MethodConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.ModuleConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.MonitorInflationCauseConstantPoolParser; +import com.oracle.svm.test.jfr.utils.poolparsers.NmtCategoryConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.OldObjectConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.PackageConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.StacktraceConstantPoolParser; @@ -92,6 +93,7 @@ public JfrFileParser(Path path) { addParser(JfrType.MonitorInflationCause, new MonitorInflationCauseConstantPoolParser(this)); addParser(JfrType.GCWhen, new GCWhenConstantPoolParser(this)); addParser(JfrType.OldObject, new OldObjectConstantPoolParser(this)); + addParser(JfrType.NMTType, new NmtCategoryConstantPoolParser(this)); } private void addParser(JfrType type, ConstantPoolParser parser) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java new file mode 100644 index 000000000000..404762e869af --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/NmtCategoryConstantPoolParser.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, 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.utils.poolparsers; + +import com.oracle.svm.test.jfr.utils.JfrFileParser; +import com.oracle.svm.test.jfr.utils.RecordingInput; +import org.junit.Assert; + +import java.io.IOException; + +public class NmtCategoryConstantPoolParser extends AbstractSerializerParser { + public NmtCategoryConstantPoolParser(JfrFileParser parser) { + super(parser); + } + + @Override + public void parse(RecordingInput input) throws IOException { + int count = input.readInt(); + Assert.assertTrue(count > 0); + for (int i = 0; i < count; i++) { + addFoundId(input.readInt()); + Assert.assertFalse("NMT category name is empty!", input.readUTF().isEmpty()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java index 7c0a5ce10e37..0ccf84d8417e 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java @@ -83,6 +83,24 @@ public void testRealloc() { assertEquals(0, getUsedMemory()); } + @Test + public void testPeakTracking() { + assertEquals(0, getUsedMemory()); + + Pointer ptr1 = NativeMemory.malloc(WordFactory.unsigned(ALLOCATION_SIZE), NmtCategory.Code); + long initialPeak = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); + assertTrue(initialPeak > 0); + + Pointer ptr2 = NativeMemory.malloc(WordFactory.unsigned(initialPeak * 2), NmtCategory.Code); + long newPeak = NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code); + assertTrue(newPeak >= initialPeak * 2); + + NativeMemory.free(ptr1); + NativeMemory.free(ptr2); + assertTrue(NativeMemoryTracking.singleton().getPeakUsedMemory(NmtCategory.Code) == newPeak); + assertEquals(0, getUsedMemory()); + } + private static long getUsedMemory() { return NativeMemoryTracking.singleton().getUsedMemory(NmtCategory.Code); }