From 0ad9f8d9b15ddeb13822d977b00c47ac7cab01f4 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 7 Sep 2023 10:14:06 -0400 Subject: [PATCH 1/8] data structure and vm op working --- .../svm/core/jfr/events/ObjectCountEvent.java | 61 +++++ .../jfr/events/ObjectCountEventSupport.java | 217 ++++++++++++++++++ .../svm/core/jfr/events/PointerArray.java | 21 ++ .../core/jfr/events/PointerArrayAccess.java | 78 +++++++ 4 files changed, 377 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvent.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEventSupport.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArray.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvent.java new file mode 100644 index 000000000000..ee3bb6232aec --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvent.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.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.jfr.JfrTicks; +import com.oracle.svm.core.jfr.SubstrateJVM; +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.StackValue; +import com.oracle.svm.core.thread.VMOperation; + +import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; + +public class ObjectCountEvent { + /** Should only be called during garbage collection at a safepoint */ +// @com.oracle.svm.core.heap.RestrictHeapAccess(access = NO_ALLOCATION, reason = "Object Counting +// must not allocate.") + public static void emit(long startTick) { + assert VMOperation.isInProgressAtSafepoint(); + if (HasJfrSupport.get()) { + // TODO check here if we should emit before we do an expensive heap walk (similar to + // objectAllocationSample) + com.oracle.svm.core.jfr.events.ObjectCountEventSupport.countObjects(); + // For each object in data structure + emit0(null, 0, 0); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emit0(Class clazz, long count, long size) { + } +} 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..664ad8edb57f --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEventSupport.java @@ -0,0 +1,217 @@ +/* + * 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.UnmanagedMemoryUtil; +import com.oracle.svm.core.thread.NativeVMOperation; +import org.graalvm.nativeimage.Platform.HOSTED_ONLY; +import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.thread.VMOperation.SystemEffect; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.thread.NativeVMOperationData; +import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.heap.Heap; +import org.graalvm.nativeimage.StackValue; +import com.oracle.svm.core.hub.DynamicHub; +import org.graalvm.word.WordFactory; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.word.Pointer; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.util.VMError; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.struct.SizeOf; +import com.oracle.svm.core.hub.DynamicHubSupport; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.PointerBase; +import com.oracle.svm.core.jfr.events.PointerArrayAccess; +import com.oracle.svm.core.jfr.events.PointerArray; + +public class ObjectCountEventSupport { + private final static ObjectCountVisitor objectCountVisitor = new ObjectCountVisitor(); + private static final ObjectCountOperation objectCountOperation = new ObjectCountOperation(); + private static int debugCount1 = 0; + + @Platforms(HOSTED_ONLY.class) + ObjectCountEventSupport() { + } + + public static void countObjects() { + assert VMOperation.isInProgressAtSafepoint(); + int size = SizeOf.get(ObjectCountVMOperationData.class); + ObjectCountVMOperationData data = StackValue.get(size); + UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + PointerArray objectCounts = StackValue.get(PointerArray.class); + // int initialCapacity = Heap.getHeap().getClassCount(); + // max type id:9218 classes count:7909 + int initialCapacity = ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId(); + PointerArrayAccess.initialize(objectCounts, initialCapacity); + + // Throw an error, otherwise we'll probably get a segfault later + VMError.guarantee(objectCounts.getSize() == initialCapacity, "init not done properly"); + + data.setObjectCounts(objectCounts); + objectCountOperation.enqueue(data); + VMError.guarantee(debugCount1 > 2000, "debug count1 should be > 2000"); + + int sizeSum = 0; + int countSum = 0; + for (int i = 0; i < objectCounts.getSize(); i++) { + ObjectCountData objectCountData = (ObjectCountData) PointerArrayAccess.get(objectCounts, i); + if (objectCountData.isNull()) { + continue; + } + countSum += objectCountData.getCount(); + sizeSum += objectCountData.getSize(); + if (objectCountData.getCount() > 0) { + VMError.guarantee(objectCountData.getSize() > 0, "size should be > 0 if count is > 0"); + } + } + // *** Do NOT add prints. Segfault at index 2927 + VMError.guarantee(countSum > 0, "countSum should be >0"); + VMError.guarantee(sizeSum > 0, "sizeSum should be >0"); + + int typeId = DynamicHub.fromClass(String.class).getTypeID(); + ObjectCountData stringOcd = objectCounts.getData().addressOf(typeId).read(); + VMError.guarantee(stringOcd.getCount() > 0, "should have more than 1 String in heap"); + VMError.guarantee(stringOcd.getSize() > 0, "string size should be positive"); + PointerArrayAccess.freeData(objectCounts); + } + + private static class ObjectCountOperation extends NativeVMOperation { + @Platforms(HOSTED_ONLY.class) + ObjectCountOperation() { + super(VMOperationInfos.get(ObjectCountOperation.class, "JFR count objects", SystemEffect.SAFEPOINT)); + } + + @Override +// @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Must not allocate during GC.") + protected void operate(NativeVMOperationData data) { + ObjectCountVMOperationData objectCountVMOperationData = (ObjectCountVMOperationData) data; + objectCountVisitor.initialize(objectCountVMOperationData.getObjectCounts()); + Heap.getHeap().walkImageHeapObjects(objectCountVisitor); + } + } + + private static boolean initializeObjectCountData(PointerArray pointerArray, int idx) { + ObjectCountData objectCountData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(ObjectCountData.class)); + if (objectCountData.isNull()) { + return false; + } + objectCountData.setCount(0); + objectCountData.setSize(0); + PointerArrayAccess.write(pointerArray, idx, objectCountData); + return true; + } + + private static class ObjectCountVisitor implements ObjectVisitor { + PointerArray objectCounts; + + @Platforms(HOSTED_ONLY.class) + ObjectCountVisitor() { + } + + public void initialize(PointerArray objectCounts) { + this.objectCounts = objectCounts; + } + + @Override + public boolean visitObject(Object obj) { // *** Can't allocate in here no matter what. + assert VMOperation.isInProgressAtSafepoint(); + DynamicHub hub = DynamicHub.fromClass(obj.getClass()); + int typeId = hub.getTypeID(); + VMError.guarantee(typeId < objectCounts.getSize(), "Should not encounter a typeId out of scope of the array"); + + // create an ObjectCountData for this typeID if one doesn't already exist + ObjectCountData objectCountData = objectCounts.getData().addressOf(typeId).read(); + if (objectCountData.isNull()) { // *** this is working as expected + debugCount1++; + if (!initializeObjectCountData(objectCounts, typeId)) { + return false; + } + // read it again to refresh the value + objectCountData = objectCounts.getData().addressOf(typeId).read(); + } + + // Increase count + objectCountData.setCount(objectCountData.getCount() + 1); + + // Get size + long size = objectCountData.getSize(); + // int layoutEncoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); + // if (LayoutEncoding.isArray(layoutEncoding)) { + // int elementSize; + // if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { + // elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); + // } else { + // elementSize = ConfigurationValues.getTarget().wordSize; + // } + // int length = ArrayLengthNode.arrayLength(obj); + // size += WordFactory.unsigned(length).multiply(elementSize).rawValue(); + // } else if (LayoutEncoding.isPureInstance(layoutEncoding)) { + // size += LayoutEncoding.getPureInstanceAllocationSize(layoutEncoding).rawValue(); + // } else if (LayoutEncoding.isHybrid(layoutEncoding)){ + // size += LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding); + // } + size += uninterruptibleGetSize(obj); // there's also getSizeFromObjectInGC and + // getMomentarySizeFromObject + objectCountData.setSize(size); + return true; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private long uninterruptibleGetSize(Object obj) { + return LayoutEncoding.getSizeFromObject(obj).rawValue(); + } + } + + @RawStructure + private interface ObjectCountVMOperationData extends NativeVMOperationData { + @RawField + PointerArray getObjectCounts(); + + @RawField + void setObjectCounts(PointerArray value); + } + + @RawStructure + public interface ObjectCountData extends PointerBase { + @RawField + long getCount(); + + @RawField + void setCount(long value); + + @RawField + long getSize(); + + @RawField + void setSize(long value); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArray.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArray.java new file mode 100644 index 000000000000..3c67942694c9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArray.java @@ -0,0 +1,21 @@ +package com.oracle.svm.core.jfr.events; + +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.PointerBase; + +@RawStructure +public interface PointerArray extends PointerBase { + @RawField + int getSize(); + + @RawField + void setSize(int value); + + @RawField + WordPointer getData(); + + @RawField + void setData(WordPointer value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java new file mode 100644 index 000000000000..f0374946f68a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023, 2023, 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.events; + +import com.oracle.svm.core.config.ConfigurationValues; +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.WordFactory; +import org.graalvm.word.PointerBase; +import org.graalvm.word.WordBase; +import com.oracle.svm.core.jfr.events.PointerArray; + +public class PointerArrayAccess { + public static void initialize(PointerArray array, int initialCapacity) { + WordPointer newData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(WordFactory.unsigned(initialCapacity).multiply(wordSize())); +// UnmanagedMemoryUtil.fill((Pointer) newData, WordFactory.unsigned(initialCapacity), (byte) 0); +// TODO newData may be null + array.setData(newData); + for (int i = 0; i < initialCapacity; i++) { // TODO why does this loop make any difference? + array.getData().addressOf(i).write(WordFactory.nullPointer()); + } + array.setSize(initialCapacity); + } + + public static PointerBase get(PointerArray array, int i) { + assert i >= 0 && i < array.getSize(); + return array.getData().addressOf(i).read(); + } + + public static void write(PointerArray array, int i, WordBase word) { + assert i >= 0 && i < array.getSize(); + array.getData().addressOf(i).write(word); + } + + public static void freeData(PointerArray array) { + if (array.isNull()) { + return; + } + for (int i = 0; i < array.getSize(); i++) { + PointerBase ptr = com.oracle.svm.core.jfr.events.PointerArrayAccess.get(array, i); + if (ptr.isNonNull()) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(ptr); + } + } + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(array.getData()); + array.setData(WordFactory.nullPointer()); + array.setSize(0); + } + + @Fold + static int wordSize() { + return ConfigurationValues.getTarget().wordSize; + } +} From 4a5c40473df7e2ab8eccbb69f85f2c60209da3b0 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 8 Sep 2023 16:34:32 -0400 Subject: [PATCH 2/8] event emission --- .../src/com/oracle/svm/core/jfr/JfrEvent.java | 2 + .../jfr/events/ObjectCountEventSupport.java | 152 ++++++++++++------ ...CountEvent.java => ObjectCountEvents.java} | 34 ++-- .../core/jfr/events/PointerArrayAccess.java | 2 +- 4 files changed, 119 insertions(+), 71 deletions(-) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/{ObjectCountEvent.java => ObjectCountEvents.java} (63%) 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/events/ObjectCountEventSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEventSupport.java index 664ad8edb57f..9a47b576d0b9 100644 --- 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 @@ -28,57 +28,102 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; -import com.oracle.svm.core.thread.NativeVMOperation; -import org.graalvm.nativeimage.Platform.HOSTED_ONLY; -import org.graalvm.nativeimage.Platforms; -import com.oracle.svm.core.thread.VMOperation.SystemEffect; -import com.oracle.svm.core.thread.VMOperation; -import com.oracle.svm.core.thread.NativeVMOperationData; -import com.oracle.svm.core.heap.VMOperationInfos; -import com.oracle.svm.core.heap.ObjectVisitor; import com.oracle.svm.core.heap.Heap; -import org.graalvm.nativeimage.StackValue; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.hub.DynamicHub; -import org.graalvm.word.WordFactory; -import org.graalvm.nativeimage.c.struct.RawStructure; -import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.word.Pointer; +import com.oracle.svm.core.hub.DynamicHubSupport; import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.jfr.events.ObjectCountEvents; +import com.oracle.svm.core.thread.NativeVMOperation; +import com.oracle.svm.core.thread.NativeVMOperationData; +import com.oracle.svm.core.thread.VMOperation.SystemEffect; +import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.util.VMError; + import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform.HOSTED_ONLY; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.SizeOf; -import com.oracle.svm.core.hub.DynamicHubSupport; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; + +import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; -import com.oracle.svm.core.jfr.events.PointerArrayAccess; -import com.oracle.svm.core.jfr.events.PointerArray; +import org.graalvm.word.WordFactory; public class ObjectCountEventSupport { private final static ObjectCountVisitor objectCountVisitor = new ObjectCountVisitor(); private static final ObjectCountOperation objectCountOperation = new ObjectCountOperation(); private static int debugCount1 = 0; + private static double cutoffPercentage = 0.005; + private static long totalSize; + + // *** should be volatile because periodic thread writes it and VM op thread reads it + private static volatile boolean shouldSendRequestableEvent = false; @Platforms(HOSTED_ONLY.class) ObjectCountEventSupport() { } + /** This is to be called by the JFR periodic task as part of the JFR periodic events */ +// @Uninterruptible(reason = "Set and unset should be atomic with invoked GC to avoid races.", callerMustBe = true) //TODO revisit + public static void setShouldSendRequestableEvent(boolean value){ + shouldSendRequestableEvent = value; + } + + public static void emitEvents(int gcId, long startTicks){ + if (com.oracle.svm.core.jfr.HasJfrSupport.get() && shouldEmitEvents()) { + emitEvents0(gcId, startTicks); + if (shouldSendRequestableEvent){ + shouldSendRequestableEvent = false; + } + } + } + + /** ShouldEmit will be checked again later. This is merely an optimization.*/ + @Uninterruptible(reason = "Caller of JfrEvent#shouldEmit must be uninterruptible.") + private static boolean shouldEmitEvents(){ + return (shouldSendRequestableEvent && JfrEvent.ObjectCount.shouldEmit()) || JfrEvent.ObjectCountAfterGC.shouldEmit(); + } + private static void emitEvents0(int gcId, long startTicks) { + PointerArray objectCounts = StackValue.get(PointerArray.class); + countObjects(objectCounts); + + for (int i = 0; i < objectCounts.getSize(); i++) { + emitForTypeId(i, objectCounts, gcId, startTicks); + } + PointerArrayAccess.freeData(objectCounts); + } + + private static void emitForTypeId(int typeId, PointerArray objectCounts, int gcId, long startTicks){ + ObjectCountData objectCountData = (ObjectCountData) PointerArrayAccess.get(objectCounts, typeId); + if (objectCountData.isNonNull() && objectCountData.getSize() / (double) totalSize > cutoffPercentage) { + VMError.guarantee(objectCountData.getSize() > 0 && objectCountData.getTraceId() >0 && objectCountData.getSize()>0, "size should be > 0 if count is > 0"); + + ObjectCountEvents.emit(JfrEvent.ObjectCount, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), gcId); + ObjectCountEvents.emit(JfrEvent.ObjectCountAfterGC, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), gcId); + } + } - public static void countObjects() { + private static PointerArray countObjects(PointerArray objectCounts) { assert VMOperation.isInProgressAtSafepoint(); int size = SizeOf.get(ObjectCountVMOperationData.class); - ObjectCountVMOperationData data = StackValue.get(size); - UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); - PointerArray objectCounts = StackValue.get(PointerArray.class); - // int initialCapacity = Heap.getHeap().getClassCount(); - // max type id:9218 classes count:7909 + ObjectCountVMOperationData vmOpData = StackValue.get(size); + UnmanagedMemoryUtil.fill((Pointer) vmOpData, WordFactory.unsigned(size), (byte) 0); + int initialCapacity = ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId(); PointerArrayAccess.initialize(objectCounts, initialCapacity); // Throw an error, otherwise we'll probably get a segfault later VMError.guarantee(objectCounts.getSize() == initialCapacity, "init not done properly"); - data.setObjectCounts(objectCounts); - objectCountOperation.enqueue(data); - VMError.guarantee(debugCount1 > 2000, "debug count1 should be > 2000"); + vmOpData.setObjectCounts(objectCounts); + objectCountOperation.enqueue(vmOpData); + VMError.guarantee(debugCount1 > 500, "debug count1 should be > 500"); int sizeSum = 0; int countSum = 0; @@ -89,11 +134,9 @@ public static void countObjects() { } countSum += objectCountData.getCount(); sizeSum += objectCountData.getSize(); - if (objectCountData.getCount() > 0) { - VMError.guarantee(objectCountData.getSize() > 0, "size should be > 0 if count is > 0"); - } + VMError.guarantee(objectCountData.getSize() > 0, "size should be > 0 if count is > 0"); } - // *** Do NOT add prints. Segfault at index 2927 + VMError.guarantee(countSum > 0, "countSum should be >0"); VMError.guarantee(sizeSum > 0, "sizeSum should be >0"); @@ -101,7 +144,8 @@ public static void countObjects() { ObjectCountData stringOcd = objectCounts.getData().addressOf(typeId).read(); VMError.guarantee(stringOcd.getCount() > 0, "should have more than 1 String in heap"); VMError.guarantee(stringOcd.getSize() > 0, "string size should be positive"); - PointerArrayAccess.freeData(objectCounts); + + return objectCounts; } private static class ObjectCountOperation extends NativeVMOperation { @@ -111,25 +155,35 @@ private static class ObjectCountOperation extends NativeVMOperation { } @Override -// @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Must not allocate during GC.") protected void operate(NativeVMOperationData data) { ObjectCountVMOperationData objectCountVMOperationData = (ObjectCountVMOperationData) data; objectCountVisitor.initialize(objectCountVMOperationData.getObjectCounts()); + totalSize = 0; // compute anew each time we operate Heap.getHeap().walkImageHeapObjects(objectCountVisitor); } } - private static boolean initializeObjectCountData(PointerArray pointerArray, int idx) { + + private static boolean initializeObjectCountData(PointerArray pointerArray, int idx, Object obj) { ObjectCountData objectCountData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(ObjectCountData.class)); if (objectCountData.isNull()) { return false; } objectCountData.setCount(0); objectCountData.setSize(0); + objectCountData.setTraceId(getTraceId(obj.getClass())); PointerArrayAccess.write(pointerArray, idx, objectCountData); return true; } + /** JFR epoch will not change before associated ObjectCount event is committed because this code runs within a + * GC safepoint.*/ // TODO revisit this logic + @Uninterruptible(reason = "Caller of SubstrateJVM#getClassId must be uninterruptible.") + private static long getTraceId(Class c){ + assert VMOperation.isInProgressAtSafepoint(); + return SubstrateJVM.get().getClassId(c); + } + private static class ObjectCountVisitor implements ObjectVisitor { PointerArray objectCounts; @@ -152,7 +206,7 @@ public boolean visitObject(Object obj) { // *** Can't allocate in here no matter ObjectCountData objectCountData = objectCounts.getData().addressOf(typeId).read(); if (objectCountData.isNull()) { // *** this is working as expected debugCount1++; - if (!initializeObjectCountData(objectCounts, typeId)) { + if (!initializeObjectCountData(objectCounts, typeId, obj)) { return false; } // read it again to refresh the value @@ -163,29 +217,15 @@ public boolean visitObject(Object obj) { // *** Can't allocate in here no matter objectCountData.setCount(objectCountData.getCount() + 1); // Get size - long size = objectCountData.getSize(); - // int layoutEncoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); - // if (LayoutEncoding.isArray(layoutEncoding)) { - // int elementSize; - // if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { - // elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); - // } else { - // elementSize = ConfigurationValues.getTarget().wordSize; - // } - // int length = ArrayLengthNode.arrayLength(obj); - // size += WordFactory.unsigned(length).multiply(elementSize).rawValue(); - // } else if (LayoutEncoding.isPureInstance(layoutEncoding)) { - // size += LayoutEncoding.getPureInstanceAllocationSize(layoutEncoding).rawValue(); - // } else if (LayoutEncoding.isHybrid(layoutEncoding)){ - // size += LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding); - // } - size += uninterruptibleGetSize(obj); // there's also getSizeFromObjectInGC and - // getMomentarySizeFromObject - objectCountData.setSize(size); + long additionalSize = uninterruptibleGetSize(obj); + totalSize += additionalSize; + objectCountData.setSize(objectCountData.getSize() + additionalSize); + return true; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + /** GC should not touch this object again before we are done with it.*/ // TODO revisit this logic + @Uninterruptible(reason = "Caller of LayoutEncoding#getSizeFromObject must be uninterruptible.") private long uninterruptibleGetSize(Object obj) { return LayoutEncoding.getSizeFromObject(obj).rawValue(); } @@ -213,5 +253,11 @@ public interface ObjectCountData extends PointerBase { @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/ObjectCountEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvents.java similarity index 63% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvent.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvents.java index ee3bb6232aec..3fe20a3ad7e7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEvents.java @@ -32,30 +32,30 @@ import com.oracle.svm.core.jfr.JfrNativeEventWriter; import com.oracle.svm.core.jfr.JfrNativeEventWriterData; import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; -import com.oracle.svm.core.jfr.JfrTicks; -import com.oracle.svm.core.jfr.SubstrateJVM; -import org.graalvm.compiler.word.Word; -import org.graalvm.nativeimage.StackValue; import com.oracle.svm.core.thread.VMOperation; +import org.graalvm.nativeimage.StackValue; -import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; - -public class ObjectCountEvent { - /** Should only be called during garbage collection at a safepoint */ -// @com.oracle.svm.core.heap.RestrictHeapAccess(access = NO_ALLOCATION, reason = "Object Counting -// must not allocate.") - public static void emit(long startTick) { +/** 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()) { - // TODO check here if we should emit before we do an expensive heap walk (similar to - // objectAllocationSample) - com.oracle.svm.core.jfr.events.ObjectCountEventSupport.countObjects(); - // For each object in data structure - emit0(null, 0, 0); + emit0(eventType, startTick, traceId, count, size, gcId); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(Class clazz, long count, long size) { + 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.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java index f0374946f68a..394f18de2191 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java @@ -48,7 +48,7 @@ public static void initialize(PointerArray array, int initialCapacity) { public static PointerBase get(PointerArray array, int i) { assert i >= 0 && i < array.getSize(); - return array.getData().addressOf(i).read(); + return array.getData().addressOf(i).read();//*** compute address of i'th element. Read the value of that address (which is a pointer to c struct) } public static void write(PointerArray array, int i, WordBase word) { From 644de039e6f791c85ca112fc154710472a321ffe Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 11 Sep 2023 14:25:06 -0400 Subject: [PATCH 3/8] tests, type repo, sync, periodic events final cleanup refactor gate formatting fix minor refactor --- substratevm/mx.substratevm/suite.py | 1 + .../EveryChunkNativeGCPeriodicEvents.java | 53 ++++++ .../oracle/svm/core/genscavenge/GCImpl.java | 4 +- .../core/genscavenge/JfrGCEventSupport.java | 18 ++ .../src/com/oracle/svm/core/heap/GCCause.java | 1 + .../svm/core/hub/DynamicHubSupport.java | 1 - .../svm/core/jfr/JfrTypeRepository.java | 23 ++- .../jfr/events/ObjectCountEventSupport.java | 180 +++++++----------- .../core/jfr/events/ObjectCountEvents.java | 9 +- .../svm/core/jfr/events/PointerArray.java | 21 -- .../svm/core/jfr/utils/PointerArray.java | 47 +++++ .../{events => utils}/PointerArrayAccess.java | 21 +- .../svm/test/jfr/TestObjectCountEvents.java | 82 ++++++++ 13 files changed, 304 insertions(+), 157 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/EveryChunkNativeGCPeriodicEvents.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArray.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArray.java rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/{events => utils}/PointerArrayAccess.java (80%) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestObjectCountEvents.java 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..ab5d52c81524 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/EveryChunkNativeGCPeriodicEvents.java @@ -0,0 +1,53 @@ +/* + * 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.jfr.JfrEvent; +import com.oracle.svm.core.jfr.events.ObjectCountEventSupport; +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(); + } + + @Uninterruptible(reason = "Set and unset should be atomic with invoked GC to avoid races.") + private static void emitObjectCount() { + if (JfrEvent.ObjectCount.shouldEmit()) { + ObjectCountEventSupport.setShouldSendRequestableEvent(true); + GCImpl.getGCImpl().collectWithoutAllocating(GCCause.JfrObjectCount, true); + ObjectCountEventSupport.setShouldSendRequestableEvent(false); + } + } +} 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..b337cdbc6e21 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; @@ -177,7 +178,7 @@ private void collect(GCCause cause, boolean forceFullGC) { @Uninterruptible(reason = "Avoid races with other threads that also try to trigger a GC") @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate in the implementation of garbage collection.") - boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) { + public boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) { VMError.guarantee(!hasNeverCollectPolicy()); int size = SizeOf.get(CollectionVMOperationData.class); @@ -244,6 +245,7 @@ private boolean collectImpl(GCCause cause, long requestingNanoTime, boolean forc } } finally { JfrGCEvents.emitGarbageCollectionEvent(getCollectionEpoch(), cause, startTicks); + ObjectCountEventSupport.emitEvents((int) getCollectionEpoch().rawValue(), startTicks); } 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..8cdd273f5839 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,7 @@ 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); + @DuplicatedInNativeCode public static final GCCause JfrObjectCount = new GCCause("Required for 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/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 1fa9f1eb210e..1106195a4367 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 @@ -102,15 +102,20 @@ private TypeInfo collectTypeInfo(boolean flushpoint) { return typeInfo; } + boolean isClassGenerated(Class clazz) { + return clazz != null && clazz.getPackage() != null && clazz.getPackage().getName().equals("jdk.internal.reflect") && clazz.getName().contains("GeneratedSerializationConstructorAccessor"); + } + private void visitClass(TypeInfo typeInfo, Class clazz) { - if (clazz != null && addClass(typeInfo, clazz)) { - visitPackage(typeInfo, clazz.getPackage(), clazz.getModule()); + if ((clazz != null && addClass(typeInfo, clazz))) { + visitPackage(typeInfo, clazz.getPackage(), clazz.getModule(), isClassGenerated(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, boolean generated) { + if (pkg != null && addPackage(typeInfo, pkg, module, generated)) { visitModule(typeInfo, module); } } @@ -246,10 +251,14 @@ private boolean isClassVisited(TypeInfo typeInfo, Class clazz) { return typeInfo.classes.contains(clazz) || flushedClasses.contains(clazz); } - private boolean addPackage(TypeInfo typeInfo, Package pkg, Module module) { + private boolean addPackage(TypeInfo typeInfo, Package pkg, Module module, boolean generated) { if (isPackageVisited(typeInfo, pkg)) { - assert module == (flushedPackages.containsKey(pkg.getName()) ? flushedPackages.get(pkg.getName()).module : typeInfo.packages.get(pkg.getName()).module); - return false; + Module cached = (flushedPackages.containsKey(pkg.getName()) ? flushedPackages.get(pkg.getName()).module : typeInfo.packages.get(pkg.getName()).module); + if (cached.isNamed() || !module.isNamed()) { + assert module == cached || (generated && !module.isNamed()); + return false; + } + assert module != cached && !generated; } // The empty package represented by "" is always traced with id 0 long id = pkg.getName().isEmpty() ? 0 : ++currentPackageId; 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 index 9a47b576d0b9..116b58d5a1cd 100644 --- 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 @@ -27,21 +27,17 @@ package com.oracle.svm.core.jfr.events; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.ObjectVisitor; -import com.oracle.svm.core.heap.VMOperationInfos; 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.jfr.events.ObjectCountEvents; -import com.oracle.svm.core.thread.NativeVMOperation; -import com.oracle.svm.core.thread.NativeVMOperationData; -import com.oracle.svm.core.thread.VMOperation.SystemEffect; +import com.oracle.svm.core.jfr.utils.PointerArray; +import com.oracle.svm.core.jfr.utils.PointerArrayAccess; import com.oracle.svm.core.thread.VMOperation; -import com.oracle.svm.core.util.VMError; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform.HOSTED_ONLY; @@ -52,140 +48,96 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; -import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.WordFactory; public class ObjectCountEventSupport { - private final static ObjectCountVisitor objectCountVisitor = new ObjectCountVisitor(); - private static final ObjectCountOperation objectCountOperation = new ObjectCountOperation(); - private static int debugCount1 = 0; - private static double cutoffPercentage = 0.005; - private static long totalSize; - - // *** should be volatile because periodic thread writes it and VM op thread reads it + private static final double CUTOFF_PERCENTAGE = 0.005; + private static final ObjectCountVisitor objectCountVisitor = new ObjectCountVisitor(); private static volatile boolean shouldSendRequestableEvent = false; @Platforms(HOSTED_ONLY.class) ObjectCountEventSupport() { } - /** This is to be called by the JFR periodic task as part of the JFR periodic events */ -// @Uninterruptible(reason = "Set and unset should be atomic with invoked GC to avoid races.", callerMustBe = true) //TODO revisit - public static void setShouldSendRequestableEvent(boolean value){ + + @Uninterruptible(reason = "Set and unset should be atomic with invoked GC.", callerMustBe = true) + public static void setShouldSendRequestableEvent(boolean value) { shouldSendRequestableEvent = value; } - public static void emitEvents(int gcId, long startTicks){ - if (com.oracle.svm.core.jfr.HasJfrSupport.get() && shouldEmitEvents()) { - emitEvents0(gcId, startTicks); - if (shouldSendRequestableEvent){ - shouldSendRequestableEvent = false; - } + public static void emitEvents(int gcId, long startTicks) { + if (HasJfrSupport.get() && shouldEmitEvents()) { + emitEvents0(gcId, startTicks); } } - /** ShouldEmit will be checked again later. This is merely an optimization.*/ + /** ShouldEmit will be checked again later. This is merely an optimization. */ @Uninterruptible(reason = "Caller of JfrEvent#shouldEmit must be uninterruptible.") - private static boolean shouldEmitEvents(){ - return (shouldSendRequestableEvent && JfrEvent.ObjectCount.shouldEmit()) || JfrEvent.ObjectCountAfterGC.shouldEmit(); + private static boolean shouldEmitEvents() { + return shouldSendRequestableEvent || JfrEvent.ObjectCountAfterGC.shouldEmit(); } + private static void emitEvents0(int gcId, long startTicks) { PointerArray objectCounts = StackValue.get(PointerArray.class); - countObjects(objectCounts); + try { + int initialCapacity = ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId(); + if (!PointerArrayAccess.initialize(objectCounts, initialCapacity)) { + return; + } + + long totalSize = visitObjects(objectCounts); - for (int i = 0; i < objectCounts.getSize(); i++) { - emitForTypeId(i, objectCounts, gcId, startTicks); + for (int i = 0; i < objectCounts.getSize(); i++) { + emitForTypeId(i, objectCounts, gcId, startTicks, totalSize); + } + } finally { + PointerArrayAccess.freeData(objectCounts); } - PointerArrayAccess.freeData(objectCounts); } - private static void emitForTypeId(int typeId, PointerArray objectCounts, int gcId, long startTicks){ + private static void emitForTypeId(int typeId, PointerArray objectCounts, int gcId, long startTicks, long totalSize) { ObjectCountData objectCountData = (ObjectCountData) PointerArrayAccess.get(objectCounts, typeId); - if (objectCountData.isNonNull() && objectCountData.getSize() / (double) totalSize > cutoffPercentage) { - VMError.guarantee(objectCountData.getSize() > 0 && objectCountData.getTraceId() >0 && objectCountData.getSize()>0, "size should be > 0 if count is > 0"); - - ObjectCountEvents.emit(JfrEvent.ObjectCount, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), gcId); + if (objectCountData.isNonNull() && objectCountData.getSize() / (double) totalSize > CUTOFF_PERCENTAGE) { + assert objectCountData.getSize() > 0 && objectCountData.getTraceId() > 0 && objectCountData.getSize() > 0; + if (shouldSendRequestableEvent) { + ObjectCountEvents.emit(JfrEvent.ObjectCount, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), gcId); + } ObjectCountEvents.emit(JfrEvent.ObjectCountAfterGC, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), gcId); } } - private static PointerArray countObjects(PointerArray objectCounts) { - assert VMOperation.isInProgressAtSafepoint(); - int size = SizeOf.get(ObjectCountVMOperationData.class); - ObjectCountVMOperationData vmOpData = StackValue.get(size); - UnmanagedMemoryUtil.fill((Pointer) vmOpData, WordFactory.unsigned(size), (byte) 0); - - int initialCapacity = ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId(); - PointerArrayAccess.initialize(objectCounts, initialCapacity); - - // Throw an error, otherwise we'll probably get a segfault later - VMError.guarantee(objectCounts.getSize() == initialCapacity, "init not done properly"); - - vmOpData.setObjectCounts(objectCounts); - objectCountOperation.enqueue(vmOpData); - VMError.guarantee(debugCount1 > 500, "debug count1 should be > 500"); - - int sizeSum = 0; - int countSum = 0; - for (int i = 0; i < objectCounts.getSize(); i++) { - ObjectCountData objectCountData = (ObjectCountData) PointerArrayAccess.get(objectCounts, i); - if (objectCountData.isNull()) { - continue; - } - countSum += objectCountData.getCount(); - sizeSum += objectCountData.getSize(); - VMError.guarantee(objectCountData.getSize() > 0, "size should be > 0 if count is > 0"); - } - - VMError.guarantee(countSum > 0, "countSum should be >0"); - VMError.guarantee(sizeSum > 0, "sizeSum should be >0"); - - int typeId = DynamicHub.fromClass(String.class).getTypeID(); - ObjectCountData stringOcd = objectCounts.getData().addressOf(typeId).read(); - VMError.guarantee(stringOcd.getCount() > 0, "should have more than 1 String in heap"); - VMError.guarantee(stringOcd.getSize() > 0, "string size should be positive"); - - return objectCounts; - } - - private static class ObjectCountOperation extends NativeVMOperation { - @Platforms(HOSTED_ONLY.class) - ObjectCountOperation() { - super(VMOperationInfos.get(ObjectCountOperation.class, "JFR count objects", SystemEffect.SAFEPOINT)); - } - - @Override - protected void operate(NativeVMOperationData data) { - ObjectCountVMOperationData objectCountVMOperationData = (ObjectCountVMOperationData) data; - objectCountVisitor.initialize(objectCountVMOperationData.getObjectCounts()); - totalSize = 0; // compute anew each time we operate - Heap.getHeap().walkImageHeapObjects(objectCountVisitor); - } + private static long visitObjects(PointerArray objectCounts) { + assert VMOperation.isGCInProgress(); + objectCountVisitor.initialize(objectCounts); + Heap.getHeap().walkImageHeapObjects(objectCountVisitor); + return objectCountVisitor.getTotalSize(); } - - private static boolean initializeObjectCountData(PointerArray pointerArray, int idx, Object obj) { + private static ObjectCountData initializeObjectCountData(PointerArray pointerArray, int idx, Object obj) { ObjectCountData objectCountData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(SizeOf.unsigned(ObjectCountData.class)); if (objectCountData.isNull()) { - return false; + return WordFactory.nullPointer(); } objectCountData.setCount(0); objectCountData.setSize(0); objectCountData.setTraceId(getTraceId(obj.getClass())); PointerArrayAccess.write(pointerArray, idx, objectCountData); - return true; + return objectCountData; } - /** JFR epoch will not change before associated ObjectCount event is committed because this code runs within a - * GC safepoint.*/ // TODO revisit this logic + /** + * It's ok to ge the trace ID here because 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.isInProgressAtSafepoint(); + private static long getTraceId(Class c) { + assert VMOperation.isGCInProgress(); return SubstrateJVM.get().getClassId(c); } private static class ObjectCountVisitor implements ObjectVisitor { - PointerArray objectCounts; + private PointerArray objectCounts; + private long totalSize; @Platforms(HOSTED_ONLY.class) ObjectCountVisitor() { @@ -193,24 +145,23 @@ private static class ObjectCountVisitor implements ObjectVisitor { public void initialize(PointerArray objectCounts) { this.objectCounts = objectCounts; + this.totalSize = 0; } @Override - public boolean visitObject(Object obj) { // *** Can't allocate in here no matter what. - assert VMOperation.isInProgressAtSafepoint(); + public boolean visitObject(Object obj) { + assert VMOperation.isGCInProgress(); DynamicHub hub = DynamicHub.fromClass(obj.getClass()); int typeId = hub.getTypeID(); - VMError.guarantee(typeId < objectCounts.getSize(), "Should not encounter a typeId out of scope of the array"); + assert typeId < objectCounts.getSize(); - // create an ObjectCountData for this typeID if one doesn't already exist + // Create an ObjectCountData for this typeID if one doesn't already exist ObjectCountData objectCountData = objectCounts.getData().addressOf(typeId).read(); - if (objectCountData.isNull()) { // *** this is working as expected - debugCount1++; - if (!initializeObjectCountData(objectCounts, typeId, obj)) { + if (objectCountData.isNull()) { + objectCountData = initializeObjectCountData(objectCounts, typeId, obj); + if (objectCountData.isNull()) { return false; } - // read it again to refresh the value - objectCountData = objectCounts.getData().addressOf(typeId).read(); } // Increase count @@ -224,20 +175,19 @@ public boolean visitObject(Object obj) { // *** Can't allocate in here no matter return true; } - /** GC should not touch this object again before we are done with it.*/ // TODO revisit this logic + /** + * Caller can be interruptible code because GC should not touch this object again before we + * are done with it. + */ @Uninterruptible(reason = "Caller of LayoutEncoding#getSizeFromObject must be uninterruptible.") private long uninterruptibleGetSize(Object obj) { + assert VMOperation.isGCInProgress(); return LayoutEncoding.getSizeFromObject(obj).rawValue(); } - } - - @RawStructure - private interface ObjectCountVMOperationData extends NativeVMOperationData { - @RawField - PointerArray getObjectCounts(); - @RawField - void setObjectCounts(PointerArray value); + public long getTotalSize() { + return totalSize; + } } @RawStructure 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 index 3fe20a3ad7e7..4fbf6725f30c 100644 --- 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 @@ -35,7 +35,10 @@ 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.*/ +/** + * 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(); @@ -45,8 +48,8 @@ public static void emit(JfrEvent eventType, long startTick, long traceId, long c } @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()){ + 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); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArray.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArray.java deleted file mode 100644 index 3c67942694c9..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArray.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.oracle.svm.core.jfr.events; - -import org.graalvm.nativeimage.c.struct.RawStructure; -import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.nativeimage.c.type.WordPointer; -import org.graalvm.word.PointerBase; - -@RawStructure -public interface PointerArray extends PointerBase { - @RawField - int getSize(); - - @RawField - void setSize(int value); - - @RawField - WordPointer getData(); - - @RawField - void setData(WordPointer value); -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArray.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArray.java new file mode 100644 index 000000000000..471c2fe6c2e7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArray.java @@ -0,0 +1,47 @@ +/* + * 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 org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.PointerBase; + +@RawStructure +public interface PointerArray extends PointerBase { + @RawField + int getSize(); + + @RawField + void setSize(int value); + + @RawField + WordPointer getData(); + + @RawField + void setData(WordPointer value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArrayAccess.java similarity index 80% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArrayAccess.java index 394f18de2191..dc79136564bc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/PointerArrayAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArrayAccess.java @@ -1,5 +1,6 @@ /* * 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 @@ -22,7 +23,8 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.jfr.events; + +package com.oracle.svm.core.jfr.utils; import com.oracle.svm.core.config.ConfigurationValues; import org.graalvm.compiler.api.replacements.Fold; @@ -32,23 +34,24 @@ import org.graalvm.word.WordFactory; import org.graalvm.word.PointerBase; import org.graalvm.word.WordBase; -import com.oracle.svm.core.jfr.events.PointerArray; public class PointerArrayAccess { - public static void initialize(PointerArray array, int initialCapacity) { + public static boolean initialize(PointerArray array, int initialCapacity) { WordPointer newData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(WordFactory.unsigned(initialCapacity).multiply(wordSize())); -// UnmanagedMemoryUtil.fill((Pointer) newData, WordFactory.unsigned(initialCapacity), (byte) 0); -// TODO newData may be null + if (newData.isNull()) { + return false; + } array.setData(newData); - for (int i = 0; i < initialCapacity; i++) { // TODO why does this loop make any difference? + for (int i = 0; i < initialCapacity; i++) { array.getData().addressOf(i).write(WordFactory.nullPointer()); } array.setSize(initialCapacity); + return true; } public static PointerBase get(PointerArray array, int i) { assert i >= 0 && i < array.getSize(); - return array.getData().addressOf(i).read();//*** compute address of i'th element. Read the value of that address (which is a pointer to c struct) + return array.getData().addressOf(i).read(); } public static void write(PointerArray array, int i, WordBase word) { @@ -57,11 +60,11 @@ public static void write(PointerArray array, int i, WordBase word) { } public static void freeData(PointerArray array) { - if (array.isNull()) { + if (array.isNull() || array.getData().isNull()) { return; } for (int i = 0; i < array.getSize(); i++) { - PointerBase ptr = com.oracle.svm.core.jfr.events.PointerArrayAccess.get(array, i); + PointerBase ptr = PointerArrayAccess.get(array, i); if (ptr.isNonNull()) { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(ptr); } 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); + } +} From cdc58091e87e3e031ea0955fb994dfe4c4133463 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 17 Oct 2023 09:34:30 -0400 Subject: [PATCH 4/8] minor feedback items --- .../EveryChunkNativeGCPeriodicEvents.java | 15 ++++++---- .../oracle/svm/core/genscavenge/GCImpl.java | 4 +-- .../src/com/oracle/svm/core/heap/GCCause.java | 4 ++- .../svm/core/jfr/JfrTypeRepository.java | 19 +++++++----- .../jfr/events/ObjectCountEventSupport.java | 29 +++++++++---------- 5 files changed, 39 insertions(+), 32 deletions(-) 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 index ab5d52c81524..8bbc979e7f7d 100644 --- 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 @@ -28,8 +28,8 @@ 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 com.oracle.svm.core.jfr.events.ObjectCountEventSupport; import jdk.jfr.Event; import jdk.jfr.Name; import jdk.jfr.Period; @@ -42,12 +42,15 @@ public static void emit() { emitObjectCount(); } - @Uninterruptible(reason = "Set and unset should be atomic with invoked GC to avoid races.") private static void emitObjectCount() { - if (JfrEvent.ObjectCount.shouldEmit()) { - ObjectCountEventSupport.setShouldSendRequestableEvent(true); - GCImpl.getGCImpl().collectWithoutAllocating(GCCause.JfrObjectCount, true); - ObjectCountEventSupport.setShouldSendRequestableEvent(false); + 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 b337cdbc6e21..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 @@ -178,7 +178,7 @@ private void collect(GCCause cause, boolean forceFullGC) { @Uninterruptible(reason = "Avoid races with other threads that also try to trigger a GC") @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate in the implementation of garbage collection.") - public boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) { + boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) { VMError.guarantee(!hasNeverCollectPolicy()); int size = SizeOf.get(CollectionVMOperationData.class); @@ -245,7 +245,7 @@ private boolean collectImpl(GCCause cause, long requestingNanoTime, boolean forc } } finally { JfrGCEvents.emitGarbageCollectionEvent(getCollectionEpoch(), cause, startTicks); - ObjectCountEventSupport.emitEvents((int) getCollectionEpoch().rawValue(), startTicks); + ObjectCountEventSupport.emitEvents(getCollectionEpoch(), startTicks, cause); } return outOfMemory; } 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 8cdd273f5839..dc36772b2f16 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,7 +49,9 @@ 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); - @DuplicatedInNativeCode public static final GCCause JfrObjectCount = new GCCause("Required for JFR object counting", 6); + /** {@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/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index 1106195a4367..c70ac661680e 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 @@ -102,12 +102,12 @@ private TypeInfo collectTypeInfo(boolean flushpoint) { return typeInfo; } - boolean isClassGenerated(Class clazz) { + private boolean isClassGenerated(Class clazz) { return clazz != null && clazz.getPackage() != null && clazz.getPackage().getName().equals("jdk.internal.reflect") && clazz.getName().contains("GeneratedSerializationConstructorAccessor"); } private void visitClass(TypeInfo typeInfo, Class clazz) { - if ((clazz != null && addClass(typeInfo, clazz))) { + if (clazz != null && addClass(typeInfo, clazz)) { visitPackage(typeInfo, clazz.getPackage(), clazz.getModule(), isClassGenerated(clazz)); visitClass(typeInfo, clazz.getSuperclass()); visitClassLoader(typeInfo, clazz.getClassLoader()); @@ -252,17 +252,22 @@ private boolean isClassVisited(TypeInfo typeInfo, Class clazz) { } private boolean addPackage(TypeInfo typeInfo, Package pkg, Module module, boolean generated) { + /* If the current package is already visited, then the current module must also already be visited, + or it must be the unnamed module because the current class is generated. */ if (isPackageVisited(typeInfo, pkg)) { - Module cached = (flushedPackages.containsKey(pkg.getName()) ? flushedPackages.get(pkg.getName()).module : typeInfo.packages.get(pkg.getName()).module); - if (cached.isNamed() || !module.isNamed()) { - assert module == cached || (generated && !module.isNamed()); + Module cachedModule = (flushedPackages.containsKey(pkg.getName()) ? flushedPackages.get(pkg.getName()).module : typeInfo.packages.get(pkg.getName()).module); + /* We only want to continue on to replace the cachedModule, if it is the unnamed module. + And we only want to bother replacing it with a named module. */ + if (cachedModule.isNamed() || !module.isNamed()) { + assert module == cachedModule || (generated && !module.isNamed()); return false; } - assert module != cached && !generated; + /* At this point we have determined we should replace the cachedModule. */ + assert module != cachedModule && !generated; } // 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(pkg.getName(), new PackageInfo(id, module, pkg)); return true; } 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 index 116b58d5a1cd..a8b8aac404a8 100644 --- 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 @@ -28,6 +28,7 @@ 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; @@ -50,34 +51,30 @@ 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(); - private static volatile boolean shouldSendRequestableEvent = false; @Platforms(HOSTED_ONLY.class) ObjectCountEventSupport() { } - @Uninterruptible(reason = "Set and unset should be atomic with invoked GC.", callerMustBe = true) - public static void setShouldSendRequestableEvent(boolean value) { - shouldSendRequestableEvent = value; - } - public static void emitEvents(int gcId, long startTicks) { - if (HasJfrSupport.get() && shouldEmitEvents()) { - emitEvents0(gcId, startTicks); + 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 shouldEmitEvents() { - return shouldSendRequestableEvent || JfrEvent.ObjectCountAfterGC.shouldEmit(); + private static boolean shouldEmitObjectCountAfterGC() { + return JfrEvent.ObjectCountAfterGC.shouldEmit(); } - private static void emitEvents0(int gcId, long startTicks) { + private static void emitEvents0(UnsignedWord gcId, long startTicks, GCCause cause) { PointerArray objectCounts = StackValue.get(PointerArray.class); try { int initialCapacity = ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId(); @@ -88,21 +85,21 @@ private static void emitEvents0(int gcId, long startTicks) { long totalSize = visitObjects(objectCounts); for (int i = 0; i < objectCounts.getSize(); i++) { - emitForTypeId(i, objectCounts, gcId, startTicks, totalSize); + emitForTypeId(i, objectCounts, gcId, startTicks, totalSize, cause); } } finally { PointerArrayAccess.freeData(objectCounts); } } - private static void emitForTypeId(int typeId, PointerArray objectCounts, int gcId, long startTicks, long totalSize) { + private static void emitForTypeId(int typeId, PointerArray objectCounts, UnsignedWord gcId, long startTicks, long totalSize, GCCause cause) { ObjectCountData objectCountData = (ObjectCountData) PointerArrayAccess.get(objectCounts, typeId); if (objectCountData.isNonNull() && objectCountData.getSize() / (double) totalSize > CUTOFF_PERCENTAGE) { assert objectCountData.getSize() > 0 && objectCountData.getTraceId() > 0 && objectCountData.getSize() > 0; - if (shouldSendRequestableEvent) { - ObjectCountEvents.emit(JfrEvent.ObjectCount, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), gcId); + 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(), gcId); + ObjectCountEvents.emit(JfrEvent.ObjectCountAfterGC, startTicks, objectCountData.getTraceId(), objectCountData.getCount(), objectCountData.getSize(), (int) gcId.rawValue()); } } From 2c3a38106801bff23e5cda7b143bea889193462a Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 17 Oct 2023 10:45:39 -0400 Subject: [PATCH 5/8] handle generated classes properly --- .../EveryChunkNativeGCPeriodicEvents.java | 5 +- .../svm/core/jfr/JfrTypeRepository.java | 75 +++++++++++-------- .../jfr/events/ObjectCountEventSupport.java | 1 - 3 files changed, 46 insertions(+), 35 deletions(-) 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 index 8bbc979e7f7d..9a1a4bd2d229 100644 --- 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 @@ -48,7 +48,10 @@ private static void emitObjectCount() { } } - /** ShouldEmit will be checked again later. This is merely an optimization to avoid a potentially unnecessary GC. */ + /** + * 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/src/com/oracle/svm/core/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrTypeRepository.java index c70ac661680e..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 @@ -102,20 +102,16 @@ private TypeInfo collectTypeInfo(boolean flushpoint) { return typeInfo; } - private boolean isClassGenerated(Class clazz) { - return clazz != null && clazz.getPackage() != null && clazz.getPackage().getName().equals("jdk.internal.reflect") && clazz.getName().contains("GeneratedSerializationConstructorAccessor"); - } - private void visitClass(TypeInfo typeInfo, Class clazz) { if (clazz != null && addClass(typeInfo, clazz)) { - visitPackage(typeInfo, clazz.getPackage(), clazz.getModule(), isClassGenerated(clazz)); + 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, boolean generated) { - if (pkg != null && addPackage(typeInfo, pkg, module, generated)) { + private void visitPackage(TypeInfo typeInfo, Package pkg, Module module, String pkgKey) { + if (pkg != null && addPackage(typeInfo, pkg, module, pkgKey)) { visitModule(typeInfo, module); } } @@ -150,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()); } @@ -173,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 } @@ -233,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; } } @@ -251,36 +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, boolean generated) { - /* If the current package is already visited, then the current module must also already be visited, - or it must be the unnamed module because the current class is generated. */ - if (isPackageVisited(typeInfo, pkg)) { - Module cachedModule = (flushedPackages.containsKey(pkg.getName()) ? flushedPackages.get(pkg.getName()).module : typeInfo.packages.get(pkg.getName()).module); - /* We only want to continue on to replace the cachedModule, if it is the unnamed module. - And we only want to bother replacing it with a named module. */ - if (cachedModule.isNamed() || !module.isNamed()) { - assert module == cachedModule || (generated && !module.isNamed()); - return false; - } - /* At this point we have determined we should replace the cachedModule. */ - assert module != cachedModule && !generated; + /** + * 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, pkg)); + 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 index a8b8aac404a8..22b9d13ad76c 100644 --- 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 @@ -61,7 +61,6 @@ public class ObjectCountEventSupport { ObjectCountEventSupport() { } - public static void emitEvents(UnsignedWord gcId, long startTicks, GCCause cause) { if (HasJfrSupport.get() && (GCCause.JfrObjectCount.equals(cause) || shouldEmitObjectCountAfterGC())) { emitEvents0(gcId, startTicks, cause); From 641d6baad208744fcaff9d9e888a172055f6a39e Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 17 Oct 2023 13:43:17 -0400 Subject: [PATCH 6/8] use NonMovableArrays --- .../jfr/events/ObjectCountEventSupport.java | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) 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 index 22b9d13ad76c..0369f8bb2397 100644 --- 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 @@ -36,14 +36,14 @@ 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.jfr.utils.PointerArray; -import com.oracle.svm.core.jfr.utils.PointerArrayAccess; 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.StackValue; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.SizeOf; @@ -74,25 +74,21 @@ private static boolean shouldEmitObjectCountAfterGC() { } private static void emitEvents0(UnsignedWord gcId, long startTicks, GCCause cause) { - PointerArray objectCounts = StackValue.get(PointerArray.class); + int initialCapacity = ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId(); + NonmovableArray objectCounts = NonmovableArrays.createWordArray(initialCapacity); try { - int initialCapacity = ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId(); - if (!PointerArrayAccess.initialize(objectCounts, initialCapacity)) { - return; - } - long totalSize = visitObjects(objectCounts); - for (int i = 0; i < objectCounts.getSize(); i++) { + for (int i = 0; i < initialCapacity; i++) { emitForTypeId(i, objectCounts, gcId, startTicks, totalSize, cause); } } finally { - PointerArrayAccess.freeData(objectCounts); + NonmovableArrays.releaseUnmanagedArray(objectCounts); } } - private static void emitForTypeId(int typeId, PointerArray objectCounts, UnsignedWord gcId, long startTicks, long totalSize, GCCause cause) { - ObjectCountData objectCountData = (ObjectCountData) PointerArrayAccess.get(objectCounts, typeId); + 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)) { @@ -102,14 +98,14 @@ private static void emitForTypeId(int typeId, PointerArray objectCounts, Unsigne } } - private static long visitObjects(PointerArray objectCounts) { + private static long visitObjects(NonmovableArray objectCounts) { assert VMOperation.isGCInProgress(); objectCountVisitor.initialize(objectCounts); Heap.getHeap().walkImageHeapObjects(objectCountVisitor); return objectCountVisitor.getTotalSize(); } - private static ObjectCountData initializeObjectCountData(PointerArray pointerArray, int idx, Object obj) { + 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(); @@ -117,7 +113,7 @@ private static ObjectCountData initializeObjectCountData(PointerArray pointerArr objectCountData.setCount(0); objectCountData.setSize(0); objectCountData.setTraceId(getTraceId(obj.getClass())); - PointerArrayAccess.write(pointerArray, idx, objectCountData); + NonmovableArrays.setWord(objectCounts, idx, objectCountData); return objectCountData; } @@ -132,14 +128,14 @@ private static long getTraceId(Class c) { } private static class ObjectCountVisitor implements ObjectVisitor { - private PointerArray objectCounts; + private NonmovableArray objectCounts; private long totalSize; @Platforms(HOSTED_ONLY.class) ObjectCountVisitor() { } - public void initialize(PointerArray objectCounts) { + public void initialize(NonmovableArray objectCounts) { this.objectCounts = objectCounts; this.totalSize = 0; } @@ -149,10 +145,10 @@ public boolean visitObject(Object obj) { assert VMOperation.isGCInProgress(); DynamicHub hub = DynamicHub.fromClass(obj.getClass()); int typeId = hub.getTypeID(); - assert typeId < objectCounts.getSize(); + assert typeId < NonmovableArrays.lengthOf(objectCounts); // Create an ObjectCountData for this typeID if one doesn't already exist - ObjectCountData objectCountData = objectCounts.getData().addressOf(typeId).read(); + ObjectCountData objectCountData = NonmovableArrays.getWord(objectCounts, typeId); if (objectCountData.isNull()) { objectCountData = initializeObjectCountData(objectCounts, typeId, obj); if (objectCountData.isNull()) { From 52cb7655c8e341bf3d1fcaad76c74d66146d3422 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 17 Oct 2023 15:59:03 -0400 Subject: [PATCH 7/8] walk whole heap. Remove old assertion --- .../oracle/svm/core/jfr/JfrSymbolRepository.java | 3 --- .../core/jfr/events/ObjectCountEventSupport.java | 16 +++------------- 2 files changed, 3 insertions(+), 16 deletions(-) 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/events/ObjectCountEventSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ObjectCountEventSupport.java index 0369f8bb2397..16b64f7624d5 100644 --- 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 @@ -101,7 +101,7 @@ private static void emitForTypeId(int typeId, NonmovableArray o private static long visitObjects(NonmovableArray objectCounts) { assert VMOperation.isGCInProgress(); objectCountVisitor.initialize(objectCounts); - Heap.getHeap().walkImageHeapObjects(objectCountVisitor); + Heap.getHeap().walkObjects(objectCountVisitor); return objectCountVisitor.getTotalSize(); } @@ -118,7 +118,7 @@ private static ObjectCountData initializeObjectCountData(NonmovableArray Date: Tue, 17 Oct 2023 16:31:53 -0400 Subject: [PATCH 8/8] remove old files and style --- .../src/com/oracle/svm/core/heap/GCCause.java | 6 +- .../svm/core/jfr/utils/PointerArray.java | 47 ----------- .../core/jfr/utils/PointerArrayAccess.java | 81 ------------------- 3 files changed, 4 insertions(+), 130 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArray.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArrayAccess.java 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 dc36772b2f16..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,8 +49,10 @@ 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. */ + /** + * {@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/jfr/utils/PointerArray.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArray.java deleted file mode 100644 index 471c2fe6c2e7..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArray.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.core.jfr.utils; - -import org.graalvm.nativeimage.c.struct.RawStructure; -import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.nativeimage.c.type.WordPointer; -import org.graalvm.word.PointerBase; - -@RawStructure -public interface PointerArray extends PointerBase { - @RawField - int getSize(); - - @RawField - void setSize(int value); - - @RawField - WordPointer getData(); - - @RawField - void setData(WordPointer value); -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArrayAccess.java deleted file mode 100644 index dc79136564bc..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/utils/PointerArrayAccess.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.core.jfr.utils; - -import com.oracle.svm.core.config.ConfigurationValues; -import org.graalvm.compiler.api.replacements.Fold; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.c.type.WordPointer; -import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; -import org.graalvm.word.WordFactory; -import org.graalvm.word.PointerBase; -import org.graalvm.word.WordBase; - -public class PointerArrayAccess { - public static boolean initialize(PointerArray array, int initialCapacity) { - WordPointer newData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(WordFactory.unsigned(initialCapacity).multiply(wordSize())); - if (newData.isNull()) { - return false; - } - array.setData(newData); - for (int i = 0; i < initialCapacity; i++) { - array.getData().addressOf(i).write(WordFactory.nullPointer()); - } - array.setSize(initialCapacity); - return true; - } - - public static PointerBase get(PointerArray array, int i) { - assert i >= 0 && i < array.getSize(); - return array.getData().addressOf(i).read(); - } - - public static void write(PointerArray array, int i, WordBase word) { - assert i >= 0 && i < array.getSize(); - array.getData().addressOf(i).write(word); - } - - public static void freeData(PointerArray array) { - if (array.isNull() || array.getData().isNull()) { - return; - } - for (int i = 0; i < array.getSize(); i++) { - PointerBase ptr = PointerArrayAccess.get(array, i); - if (ptr.isNonNull()) { - ImageSingletons.lookup(UnmanagedMemorySupport.class).free(ptr); - } - } - ImageSingletons.lookup(UnmanagedMemorySupport.class).free(array.getData()); - array.setData(WordFactory.nullPointer()); - array.setSize(0); - } - - @Fold - static int wordSize() { - return ConfigurationValues.getTarget().wordSize; - } -}