diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index c2bd3225190b..0b4264abca6d 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -336,6 +336,7 @@ ], "requires" : [ "jdk.management", + "jdk.jfr", ], "requiresConcealed" : { "java.base": [ diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/EveryChunkNativeGCPeriodicEvents.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/EveryChunkNativeGCPeriodicEvents.java new file mode 100644 index 000000000000..9a1a4bd2d229 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/EveryChunkNativeGCPeriodicEvents.java @@ -0,0 +1,59 @@ +/* + * 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.genscavenge; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.heap.GCCause; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.jfr.JfrEvent; +import jdk.jfr.Event; +import jdk.jfr.Name; +import jdk.jfr.Period; + +@Name("EveryChunkPeriodicGCEvents") +@Period(value = "everyChunk") +public class EveryChunkNativeGCPeriodicEvents extends Event { + + public static void emit() { + emitObjectCount(); + } + + private static void emitObjectCount() { + if (shouldEmitObjectCount()) { + Heap.getHeap().getGC().collectCompletely(GCCause.JfrObjectCount); + } + } + + /** + * ShouldEmit will be checked again later. This is merely an optimization to avoid a potentially + * unnecessary GC. + */ + @Uninterruptible(reason = "Caller of JfrEvent#shouldEmit must be uninterruptible.") + private static boolean shouldEmitObjectCount() { + return JfrEvent.ObjectCount.shouldEmit(); + } +} diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index a5caf759d016..86a26a0dce70 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -82,6 +82,7 @@ import com.oracle.svm.core.jfr.JfrGCWhen; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.events.AllocationRequiringGCEvent; +import com.oracle.svm.core.jfr.events.ObjectCountEventSupport; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.os.CommittedMemoryProvider; import com.oracle.svm.core.snippets.ImplicitExceptions; @@ -244,6 +245,7 @@ private boolean collectImpl(GCCause cause, long requestingNanoTime, boolean forc } } finally { JfrGCEvents.emitGarbageCollectionEvent(getCollectionEpoch(), cause, startTicks); + ObjectCountEventSupport.emitEvents(getCollectionEpoch(), startTicks, cause); } return outOfMemory; } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/JfrGCEventSupport.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/JfrGCEventSupport.java index 73b00fd8dec5..37e4869110bc 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/JfrGCEventSupport.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/JfrGCEventSupport.java @@ -25,10 +25,12 @@ */ package com.oracle.svm.core.genscavenge; +import jdk.jfr.FlightRecorder; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.StackValue; import org.graalvm.word.UnsignedWord; +import com.oracle.svm.core.jdk.RuntimeSupport; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; @@ -136,6 +138,18 @@ private int popPhase() { assert currentPhase > 0; return --currentPhase; } + + public static RuntimeSupport.Hook startupHook() { + return isFirstIsolate -> { + FlightRecorder.addPeriodicEvent(EveryChunkNativeGCPeriodicEvents.class, EveryChunkNativeGCPeriodicEvents::emit); + }; + } + + public static RuntimeSupport.Hook shutdownHook() { + return isFirstIsolate -> { + FlightRecorder.removePeriodicEvent(EveryChunkNativeGCPeriodicEvents::emit); + }; + } } @AutomaticallyRegisteredFeature @@ -150,6 +164,10 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { if (HasJfrSupport.get()) { JfrGCName name = JfrGCNames.singleton().addGCName("serial"); ImageSingletons.add(JfrGCEventSupport.class, new JfrGCEventSupport(name)); + + RuntimeSupport runtime = RuntimeSupport.getRuntimeSupport(); + runtime.addStartupHook(JfrGCEventSupport.startupHook()); + runtime.addShutdownHook(JfrGCEventSupport.shutdownHook()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java index c4529f2ade9f..e4a299b219b2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java @@ -49,6 +49,11 @@ public class GCCause { @DuplicatedInNativeCode public static final GCCause HintedGC = new GCCause("Hinted GC", 3); @DuplicatedInNativeCode public static final GCCause JvmtiForceGC = new GCCause("JvmtiEnv ForceGarbageCollection", 4); @DuplicatedInNativeCode public static final GCCause HeapDump = new GCCause("Heap Dump Initiated GC ", 5); + /** + * {@link GCCause#JfrObjectCount} is a GC cause hotspot does not have. It indicates the GC was + * invoked in order to emit JFR ObjectCount periodic events. + */ + @DuplicatedInNativeCode public static final GCCause JfrObjectCount = new GCCause("JFR object counting", 6); @UnknownObjectField(availability = ReadyForCompilation.class) protected static GCCause[] GCCauses; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java index 71befa3164c8..aa576dbfb5ae 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubSupport.java @@ -50,7 +50,6 @@ public void setMaxTypeId(int maxTypeId) { this.maxTypeId = maxTypeId; } - @Platforms(Platform.HOSTED_ONLY.class) public int getMaxTypeId() { return maxTypeId; } 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 70a673e36ebc..ee5a8ecba5e9 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 @@ -65,6 +65,8 @@ public final class JfrEvent { 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 ObjectCount = create("jdk.ObjectCount", false); + public static final JfrEvent ObjectCountAfterGC = create("jdk.ObjectCountAfterGC", false); private final long id; private final String name; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java index 0d17658f8ad6..3a46fe6536f6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrSymbolRepository.java @@ -38,7 +38,6 @@ import com.oracle.svm.core.c.struct.PinnedObjectField; import com.oracle.svm.core.collections.AbstractUninterruptibleHashtable; import com.oracle.svm.core.collections.UninterruptibleEntry; -import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jdk.UninterruptibleUtils.CharReplacer; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; @@ -77,8 +76,6 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r return 0; } - assert Heap.getHeap().isInImageHeap(imageHeapString); - JfrSymbol symbol = StackValue.get(JfrSymbol.class); symbol.setValue(imageHeapString); symbol.setReplaceDotWithSlash(replaceDotWithSlash); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 1fa9f1eb210e..4c645b94edfc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java @@ -104,13 +104,14 @@ private TypeInfo collectTypeInfo(boolean flushpoint) { private void visitClass(TypeInfo typeInfo, Class clazz) { if (clazz != null && addClass(typeInfo, clazz)) { - visitPackage(typeInfo, clazz.getPackage(), clazz.getModule()); + visitPackage(typeInfo, clazz.getPackage(), clazz.getModule(), getPackageKey(clazz)); visitClass(typeInfo, clazz.getSuperclass()); + visitClassLoader(typeInfo, clazz.getClassLoader()); } } - private void visitPackage(TypeInfo typeInfo, Package pkg, Module module) { - if (pkg != null && addPackage(typeInfo, pkg, module)) { + private void visitPackage(TypeInfo typeInfo, Package pkg, Module module, String pkgKey) { + if (pkg != null && addPackage(typeInfo, pkg, module, pkgKey)) { visitModule(typeInfo, module); } } @@ -145,7 +146,7 @@ private void writeClass(TypeInfo typeInfo, JfrChunkWriter writer, Class clazz writer.writeCompressedLong(JfrTraceId.getTraceId(clazz)); writer.writeCompressedLong(getClassLoaderId(typeInfo, clazz.getClassLoader())); writer.writeCompressedLong(getSymbolId(writer, clazz.getName(), flushpoint, true)); - writer.writeCompressedLong(getPackageId(typeInfo, clazz.getPackage())); + writer.writeCompressedLong(getPackageId(typeInfo, clazz)); writer.writeCompressedLong(clazz.getModifiers()); writer.writeBoolean(clazz.isHidden()); } @@ -168,14 +169,14 @@ private int writePackages(JfrChunkWriter writer, TypeInfo typeInfo, boolean flus writer.writeCompressedInt(typeInfo.packages.size()); for (Map.Entry pkgInfo : typeInfo.packages.entrySet()) { - writePackage(typeInfo, writer, pkgInfo.getKey(), pkgInfo.getValue(), flushpoint); + writePackage(typeInfo, writer, pkgInfo.getValue(), flushpoint); } return NON_EMPTY; } - private void writePackage(TypeInfo typeInfo, JfrChunkWriter writer, String pkgName, PackageInfo pkgInfo, boolean flushpoint) { + private void writePackage(TypeInfo typeInfo, JfrChunkWriter writer, PackageInfo pkgInfo, boolean flushpoint) { writer.writeCompressedLong(pkgInfo.id); // id - writer.writeCompressedLong(getSymbolId(writer, pkgName, flushpoint, true)); + writer.writeCompressedLong(getSymbolId(writer, pkgInfo.pkgName, flushpoint, true)); writer.writeCompressedLong(getModuleId(typeInfo, pkgInfo.module)); writer.writeBoolean(false); // exported } @@ -228,10 +229,12 @@ private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long private static class PackageInfo { private final long id; private final Module module; + private final String pkgName; - PackageInfo(long id, Module module) { + PackageInfo(long id, Module module, String pkgName) { this.id = id; this.module = module; + this.pkgName = pkgName; } } @@ -246,27 +249,47 @@ private boolean isClassVisited(TypeInfo typeInfo, Class clazz) { return typeInfo.classes.contains(clazz) || flushedClasses.contains(clazz); } - private boolean addPackage(TypeInfo typeInfo, Package pkg, Module module) { - if (isPackageVisited(typeInfo, pkg)) { - assert module == (flushedPackages.containsKey(pkg.getName()) ? flushedPackages.get(pkg.getName()).module : typeInfo.packages.get(pkg.getName()).module); + /** + * It is possible that generated classes may have the same package as ordinary non-generated + * classes, however their module may be different (the unnamed module). We must deduplicate + * packages during serialization, but since a single package name may correspond to multiple + * modules, we make the deduplication key the concatenation of the package and module name. This + * allows each package module combination to be represented in the serialized data. + */ + private static String getPackageKey(Class clazz) { + if (clazz.getPackage() == null) { + return null; + } + String packageKey = clazz.getPackage().getName(); + if (clazz.getModule() != null && clazz.getModule().getName() != null) { + packageKey += clazz.getModule().getName(); + } + return packageKey; + } + + private boolean addPackage(TypeInfo typeInfo, Package pkg, Module module, String pkgKey) { + + if (isPackageVisited(typeInfo, pkgKey)) { + assert module == (flushedPackages.containsKey(pkgKey) ? flushedPackages.get(pkgKey).module : typeInfo.packages.get(pkgKey).module); return false; } // The empty package represented by "" is always traced with id 0 long id = pkg.getName().isEmpty() ? 0 : ++currentPackageId; - typeInfo.packages.put(pkg.getName(), new PackageInfo(id, module)); + typeInfo.packages.put(pkgKey, new PackageInfo(id, module, pkg.getName())); return true; } - private boolean isPackageVisited(TypeInfo typeInfo, Package pkg) { - return flushedPackages.containsKey(pkg.getName()) || typeInfo.packages.containsKey(pkg.getName()); + private boolean isPackageVisited(TypeInfo typeInfo, String packageKey) { + return flushedPackages.containsKey(packageKey) || typeInfo.packages.containsKey(packageKey); } - private long getPackageId(TypeInfo typeInfo, Package pkg) { - if (pkg != null) { - if (flushedPackages.containsKey(pkg.getName())) { - return flushedPackages.get(pkg.getName()).id; + private long getPackageId(TypeInfo typeInfo, Class clazz) { + String packageKey = getPackageKey(clazz); + if (packageKey != null) { + if (flushedPackages.containsKey(packageKey)) { + return flushedPackages.get(packageKey).id; } - return typeInfo.packages.get(pkg.getName()).id; + return typeInfo.packages.get(packageKey).id; } else { return 0; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEventSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEventSupport.java new file mode 100644 index 000000000000..16b64f7624d5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEventSupport.java @@ -0,0 +1,195 @@ +/* + * 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.events; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.GCCause; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.DynamicHubSupport; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.jfr.HasJfrSupport; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.thread.VMOperation; + +import com.oracle.svm.core.c.NonmovableArray; +import com.oracle.svm.core.c.NonmovableArrays; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform.HOSTED_ONLY; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; + +import org.graalvm.word.PointerBase; +import org.graalvm.word.WordFactory; +import org.graalvm.word.UnsignedWord; + +public class ObjectCountEventSupport { + private static final double CUTOFF_PERCENTAGE = 0.005; + private static final ObjectCountVisitor objectCountVisitor = new ObjectCountVisitor(); + + @Platforms(HOSTED_ONLY.class) + ObjectCountEventSupport() { + } + + public static void emitEvents(UnsignedWord gcId, long startTicks, GCCause cause) { + if (HasJfrSupport.get() && (GCCause.JfrObjectCount.equals(cause) || shouldEmitObjectCountAfterGC())) { + emitEvents0(gcId, startTicks, cause); + } + } + + /** ShouldEmit will be checked again later. This is merely an optimization. */ + @Uninterruptible(reason = "Caller of JfrEvent#shouldEmit must be uninterruptible.") + private static boolean shouldEmitObjectCountAfterGC() { + return JfrEvent.ObjectCountAfterGC.shouldEmit(); + } + + private static void emitEvents0(UnsignedWord gcId, long startTicks, GCCause cause) { + int initialCapacity = ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId(); + NonmovableArray objectCounts = NonmovableArrays.createWordArray(initialCapacity); + try { + long totalSize = visitObjects(objectCounts); + + for (int i = 0; i < initialCapacity; i++) { + emitForTypeId(i, objectCounts, gcId, startTicks, totalSize, cause); + } + } finally { + NonmovableArrays.releaseUnmanagedArray(objectCounts); + } + } + + private static void emitForTypeId(int typeId, NonmovableArray objectCounts, UnsignedWord gcId, long startTicks, long totalSize, GCCause cause) { + ObjectCountData objectCountData = NonmovableArrays.getWord(objectCounts, typeId); + if (objectCountData.isNonNull() && objectCountData.getSize() / (double) totalSize > CUTOFF_PERCENTAGE) { + assert objectCountData.getSize() > 0 && objectCountData.getTraceId() > 0 && objectCountData.getSize() > 0; + if (GCCause.JfrObjectCount.equals(cause)) { + ObjectCountEvents.emit(JfrEvent.ObjectCount, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), (int) gcId.rawValue()); + } + ObjectCountEvents.emit(JfrEvent.ObjectCountAfterGC, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), (int) gcId.rawValue()); + } + } + + private static long visitObjects(NonmovableArray objectCounts) { + assert VMOperation.isGCInProgress(); + objectCountVisitor.initialize(objectCounts); + Heap.getHeap().walkObjects(objectCountVisitor); + return objectCountVisitor.getTotalSize(); + } + + private static ObjectCountData initializeObjectCountData(NonmovableArray objectCounts, int idx, Object obj) { + ObjectCountData objectCountData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(ObjectCountData.class)); + if (objectCountData.isNull()) { + return WordFactory.nullPointer(); + } + objectCountData.setCount(0); + objectCountData.setSize(0); + objectCountData.setTraceId(getTraceId(obj.getClass())); + NonmovableArrays.setWord(objectCounts, idx, objectCountData); + return objectCountData; + } + + /** + * It's ok to get the trace ID here because the JFR epoch will not change before jdk.ObjectCount + * events are committed. + */ + @Uninterruptible(reason = "Caller of SubstrateJVM#getClassId must be uninterruptible.") + private static long getTraceId(Class c) { + assert VMOperation.isGCInProgress(); + return SubstrateJVM.get().getClassId(c); + } + + private static class ObjectCountVisitor implements ObjectVisitor { + private NonmovableArray objectCounts; + private long totalSize; + + @Platforms(HOSTED_ONLY.class) + ObjectCountVisitor() { + } + + public void initialize(NonmovableArray objectCounts) { + this.objectCounts = objectCounts; + this.totalSize = 0; + } + + @Override + public boolean visitObject(Object obj) { + assert VMOperation.isGCInProgress(); + DynamicHub hub = DynamicHub.fromClass(obj.getClass()); + int typeId = hub.getTypeID(); + assert typeId < NonmovableArrays.lengthOf(objectCounts); + + // Create an ObjectCountData for this typeID if one doesn't already exist + ObjectCountData objectCountData = NonmovableArrays.getWord(objectCounts, typeId); + if (objectCountData.isNull()) { + objectCountData = initializeObjectCountData(objectCounts, typeId, obj); + if (objectCountData.isNull()) { + return false; + } + } + + // Increase count + objectCountData.setCount(objectCountData.getCount() + 1); + + // Get size + long additionalSize = LayoutEncoding.getSizeFromObjectInGC(obj).rawValue(); + totalSize += additionalSize; + objectCountData.setSize(objectCountData.getSize() + additionalSize); + + return true; + } + + public long getTotalSize() { + return totalSize; + } + } + + @RawStructure + public interface ObjectCountData extends PointerBase { + @RawField + long getCount(); + + @RawField + void setCount(long value); + + @RawField + long getSize(); + + @RawField + void setSize(long value); + + @RawField + long getTraceId(); + + @RawField + void setTraceId(long value); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvents.java new file mode 100644 index 000000000000..4fbf6725f30c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvents.java @@ -0,0 +1,64 @@ +/* + * 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.events; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.HasJfrSupport; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.JfrNativeEventWriter; +import com.oracle.svm.core.jfr.JfrNativeEventWriterData; +import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; +import com.oracle.svm.core.thread.VMOperation; +import org.graalvm.nativeimage.StackValue; + +/** + * This class is used for both jdk.ObjectCount and jdk.ObjectCountAfterGC since they contain + * identical information. + */ +public class ObjectCountEvents { + public static void emit(JfrEvent eventType, long startTick, long traceId, long count, long size, int gcId) { + assert VMOperation.isInProgressAtSafepoint(); + if (HasJfrSupport.get()) { + emit0(eventType, startTick, traceId, count, size, gcId); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + public static void emit0(JfrEvent eventType, long startTick, long traceId, long count, long size, int gcId) { + if (eventType.shouldEmit()) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + JfrNativeEventWriter.beginSmallEvent(data, eventType); + JfrNativeEventWriter.putLong(data, startTick); + JfrNativeEventWriter.putInt(data, gcId); + JfrNativeEventWriter.putLong(data, traceId); + JfrNativeEventWriter.putLong(data, count); + JfrNativeEventWriter.putLong(data, size); + JfrNativeEventWriter.endSmallEvent(data); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectCountEvents.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectCountEvents.java new file mode 100644 index 000000000000..7cc9d8e61107 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectCountEvents.java @@ -0,0 +1,82 @@ +/* + * 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 com.oracle.svm.core.jfr.JfrEvent; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import org.junit.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertTrue; + +public class TestObjectCountEvents extends JfrRecordingTest { + private static final int SKIPPED_COUNT = 3; + + @Test + public void test() throws Throwable { + String[] events = new String[]{JfrEvent.ObjectCount.getName(), JfrEvent.ObjectCountAfterGC.getName()}; + Recording recording = startRecording(events); + + for (int i = 0; i < SKIPPED_COUNT; i++) { + System.gc(); + } + + stopRecording(recording, TestObjectCountEvents::validateEvents); + } + + private static void validateEvents(List events) { + Set seen = new HashSet<>(); + Set seenAfterGC = new HashSet<>(); + int max = 0; + int min = Integer.MAX_VALUE; + assertTrue(events.size() > 0); + for (RecordedEvent event : events) { + assertTrue("Objects should have a non-zero total size", event. getValue("totalSize") > 0); + assertTrue("Object counts should be non-zero", event. getValue("count") > 0); + int gcId = event. getValue("gcId"); + if (max < gcId) { + max = gcId; + } + if (min > gcId) { + min = gcId; + } + seenAfterGC.add(gcId); + if (event.getEventType().getName().equals(JfrEvent.ObjectCount.getName())) { + seen.add(gcId); + } + + } + int skippedCount = max - min - seen.size() + 1; + assertTrue("Not every GC should result in jdk.ObjectCount events.", skippedCount >= SKIPPED_COUNT); + int skippedCountAfterGc = max - min - seenAfterGC.size() + 1; + assertTrue("Every GC should result in jdk.ObjectCountAfterGC events.", skippedCountAfterGc == 0); + } +}