diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java index cc63053484bf..bc1f5ed3bda4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java @@ -54,6 +54,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.AnnotateOriginal; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.KeepOriginal; import com.oracle.svm.core.annotate.NeverInline; @@ -150,6 +151,10 @@ private static Enum valueOf(Class> enumType, String name) { throw new IllegalArgumentException("No enum constant " + enumType.getName() + "." + name); } } + + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public native int ordinal(); } @TargetClass(java.lang.String.class) @@ -160,6 +165,32 @@ public String intern() { String thisStr = SubstrateUtil.cast(this, String.class); return ImageSingletons.lookup(StringInternSupport.class).intern(thisStr); } + + @AnnotateOriginal + @TargetElement(onlyWith = JDK11OrLater.class) + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + native boolean isLatin1(); + + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public native int length(); + + @AnnotateOriginal + @TargetElement(onlyWith = JDK11OrLater.class) + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + native byte coder(); + + @Alias @TargetElement(name = "value", onlyWith = JDK11OrLater.class) byte[] valueJDK11; + + @Alias @TargetElement(name = "value", onlyWith = JDK8OrEarlier.class) char[] valueJDK8; +} + +@TargetClass(className = "java.lang.StringUTF16", onlyWith = JDK11OrLater.class) +final class Target_java_lang_StringUTF16 { + + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static native char getChar(byte[] val, int index); } @TargetClass(java.lang.Throwable.class) @@ -663,6 +694,16 @@ final class Target_jdk_internal_loader_ClassLoaders { /** Dummy class to have a class with the file's name. */ public final class JavaLangSubstitutions { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static byte[] getBytes(String string) { + return SubstrateUtil.cast(string, Target_java_lang_String.class).valueJDK11; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isLatin1(String string) { + return SubstrateUtil.cast(string, Target_java_lang_String.class).isLatin1(); + } + public static final class ClassValueSupport { /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleAbstractHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleAbstractHashtable.java new file mode 100644 index 000000000000..e13ec980bd89 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleAbstractHashtable.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import com.oracle.svm.core.annotate.Uninterruptible; + +/** + * Abstract base class for all uninterruptible hashtables. + */ +public interface UninterruptibleAbstractHashtable> { + + static > UninterruptibleThreadSafeHashtable makeHashtableThreadSafe(String name, UninterruptibleAbstractHashtable table) { + return new UninterruptibleThreadSafeHashtable<>(name, table); + } + + /** + * Sets hashtable size. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void setSize(int size); + + /** + * Gets hashtable size. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + int getSize(); + + /** + * Gets hashtable. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + T[] getTable(); + + /** + * Check if {@code valueOnStack} exists in hashtable, if it's exists, return that value. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + T contains(T valueOnStack); + + /** + * Put {@code valueOnStack} in hashtable. + * + * @return new entry id or existing entry id, if old one is equals to the {@code valueOnStack}. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + long put(T valueOnStack); + + /** + * Put {@code valueOnStack} in hashtable. + * + * @return true if new entry is created, false, if it's already there. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + boolean putIfAbsent(T valueOnStack); + + /** + * Deallocate memory occupied by {@code t}. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void free(T t); + + /** + * Clear all entries from map. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void clear(); + + /** + * Teardown hashtable. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + void teardown(); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java index 90b336f40c74..eb2b26e2b190 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleHashtable.java @@ -24,34 +24,37 @@ */ package com.oracle.svm.core.jdk; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.annotate.Uninterruptible; -import com.oracle.svm.core.locks.VMMutex; /** * An uninterruptible hashtable with a fixed size that uses chaining in case of a collision. */ -public abstract class UninterruptibleHashtable> { - private static final int DEFAULT_TABLE_LENGTH = 2053; +public abstract class UninterruptibleHashtable> implements UninterruptibleAbstractHashtable { - private final T[] table; - private final VMMutex mutex; + protected static final int DEFAULT_TABLE_LENGTH = 2053; - private long nextId; - private int size; + protected final T[] table; + + protected long nextId; + protected int size; @Platforms(Platform.HOSTED_ONLY.class) - public UninterruptibleHashtable(String name) { - this(name, DEFAULT_TABLE_LENGTH); + public UninterruptibleHashtable() { + this(DEFAULT_TABLE_LENGTH); } @Platforms(Platform.HOSTED_ONLY.class) - public UninterruptibleHashtable(String name, int primeLength) { + public UninterruptibleHashtable(int primeLength) { this.table = createTable(primeLength); - this.mutex = new VMMutex(name); this.size = 0; } @@ -62,78 +65,115 @@ public UninterruptibleHashtable(String name, int primeLength) { protected abstract boolean isEqual(T a, T b); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected abstract T copyToHeap(T valueOnStack); + protected T allocateOnHeap(Pointer pointerOnStack, UnsignedWord sizeToAlloc) { + T pointerOnHeap = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(sizeToAlloc); + if (pointerOnHeap.isNonNull()) { + UnmanagedMemoryUtil.copy(pointerOnStack, (Pointer) pointerOnHeap, sizeToAlloc); + return pointerOnHeap; + } + return WordFactory.nullPointer(); + } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected abstract void free(T t); + protected abstract T copyToHeap(T valueOnStack); @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void clear() { - for (int i = 0; i < table.length; i++) { - T entry = table[i]; - while (entry.isNonNull()) { - T tmp = entry; - entry = entry.getNext(); - free(tmp); - } - table[i] = WordFactory.nullPointer(); + protected long insertEntry(T valueOnStack) { + int index = Integer.remainderUnsigned(valueOnStack.getHash(), DEFAULT_TABLE_LENGTH); + T newEntry = copyToHeap(valueOnStack); + if (newEntry.isNonNull()) { + long id = ++nextId; + T existingEntry = table[index]; + newEntry.setNext(existingEntry); + newEntry.setId(id); + table[index] = newEntry; + size++; + return id; } - size = 0; + return 0L; } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void setSize(int size) { this.size = size; } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void teardown() { - clear(); + public int getSize() { + return size; } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public int getSize() { - return size; + public T[] getTable() { + return table; } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public long add(T valueOnStack) { + public T contains(T valueOnStack) { + int index = Integer.remainderUnsigned(valueOnStack.getHash(), DEFAULT_TABLE_LENGTH); + T entry = table[index]; + while (entry.isNonNull()) { + if (isEqual(valueOnStack, entry)) { + return entry; + } + entry = entry.getNext(); + } + return WordFactory.nullPointer(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long put(T valueOnStack) { assert valueOnStack.isNonNull(); - mutex.lockNoTransition(); - try { - // Try to find the entry in the hashtable - int index = Integer.remainderUnsigned(valueOnStack.getHash(), DEFAULT_TABLE_LENGTH); - T entry = table[index]; - while (entry.isNonNull()) { - if (isEqual(valueOnStack, entry)) { - return entry.getId(); - } - entry = entry.getNext(); - } + T entry = contains(valueOnStack); + if (entry.isNonNull()) { + return entry.getId(); + } else { + return insertEntry(valueOnStack); + } + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean putIfAbsent(T valueOnStack) { + assert valueOnStack.isNonNull(); - return insertEntry(index, valueOnStack); - } finally { - mutex.unlock(); + T entry = contains(valueOnStack); + if (entry.isNonNull()) { + return false; + } else { + insertEntry(valueOnStack); + return true; } } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private long insertEntry(int index, T valueOnStack) { - T newEntry = copyToHeap(valueOnStack); - if (newEntry.isNonNull()) { - long id = ++nextId; - T existingEntry = table[index]; - newEntry.setNext(existingEntry); - newEntry.setId(id); - table[index] = newEntry; - size++; - return id; + public abstract void free(T t); + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void clear() { + for (int i = 0; i < table.length; i++) { + T entry = table[i]; + while (entry.isNonNull()) { + T tmp = entry; + entry = entry.getNext(); + free(tmp); + } + table[i] = WordFactory.nullPointer(); } - return 0L; + size = 0; } - public T[] getTable() { - return table; + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void teardown() { + clear(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleThreadSafeHashtable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleThreadSafeHashtable.java new file mode 100644 index 000000000000..0584f9eb7471 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleThreadSafeHashtable.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.locks.VMMutex; + +/** + * An uninterruptible thread-safe hashtable with a fixed size that uses chaining in case of a + * collision. + */ +final class UninterruptibleThreadSafeHashtable> implements UninterruptibleAbstractHashtable { + + private final VMMutex mutex; + private final UninterruptibleAbstractHashtable hashtable; + + @Platforms(Platform.HOSTED_ONLY.class) + UninterruptibleThreadSafeHashtable(String name, UninterruptibleAbstractHashtable hashtable) { + this.hashtable = hashtable; + this.mutex = new VMMutex(name); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void setSize(int size) { + hashtable.setSize(size); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getSize() { + return hashtable.getSize(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public T[] getTable() { + return hashtable.getTable(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public T contains(T valueOnStack) { + return hashtable.contains(valueOnStack); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long put(T valueOnStack) { + mutex.lockNoTransition(); + try { + return hashtable.put(valueOnStack); + } finally { + mutex.unlock(); + } + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean putIfAbsent(T valueOnStack) { + mutex.lockNoTransition(); + try { + return hashtable.putIfAbsent(valueOnStack); + } finally { + mutex.unlock(); + } + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void free(T t) { + hashtable.free(t); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void clear() { + hashtable.clear(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void teardown() { + hashtable.teardown(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index de2cd802b210..0f00a05edc5c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -146,6 +146,11 @@ public long incrementAndGet() { return addAndGet(1); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getAndIncrement() { + return getAndAdd(1); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long decrementAndGet() { return addAndGet(-1); @@ -460,50 +465,89 @@ public static int compareUnsigned(int x, int y) { } public static class String { + /** - * Gets the length of String when encoded using modified UTF8 (null characters that are - * present in the input will be encoded in a way that they do not interfere with a - * null-terminator). + * Gets the number of bytes for a char in modified UTF8 format. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static int modifiedUtf8Length(java.lang.String string, boolean addNullTerminator) { - int result = 0; - for (int i = 0; i < string.length(); i++) { - int c = string.charAt(i); - if (c >= 0x0001 && c <= 0x007F) { - result += 1; - } else if (c <= 0x07FF) { - result += 2; + private static int sizeInModifiedUTF8(char c) { + if (c >= 0x0001 && c <= 0x007F) { + // ASCII character. + return 1; + } else { + if (c <= 0x07FF) { + return 2; } else { - result += 3; + return 3; } } + } + + /** + * Write a char in modified UTF8 format into the buffer at position {@code buffer}. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static Pointer writeModifiedUTF8(Pointer buffer, char c) { + Pointer pos = buffer; + if (c >= 0x0001 && c <= 0x007F) { + pos.writeByte(0, (byte) c); + pos = pos.add(1); + } else if (c <= 0x07FF) { + pos.writeByte(0, (byte) (0xC0 | (c >> 6))); + pos.writeByte(1, (byte) (0x80 | (c & 0x3F))); + pos = pos.add(2); + } else { + pos.writeByte(0, (byte) (0xE0 | (c >> 12))); + pos.writeByte(1, (byte) (0x80 | ((c >> 6) & 0x3F))); + pos.writeByte(2, (byte) (0x80 | (c & 0x3F))); + pos = pos.add(3); + } + return pos; + } + + /** + * Gets the length of {@code string} when encoded using modified UTF8 (null characters that + * are present in the input will be encoded in a way that they do not interfere with a + * null-terminator). + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int modifiedUTF8Length(java.lang.String string, boolean addNullTerminator) { + int result = 0; + boolean isLatin1 = JavaLangSubstitutions.isLatin1(string); + byte[] value = JavaLangSubstitutions.getBytes(string); + for (int index = 0; index < string.length(); index++) { + result += sizeInModifiedUTF8(charAt(isLatin1, value, index)); + } + return result + (addNullTerminator ? 1 : 0); } /** - * Writes the encoded String into the given buffer using the modified UTF8 encoding (null - * characters that are present in the input will be encoded in a way that they do not - * interfere with the null terminator). + * Returns a character from a string at {@code index} position based on the encoding format. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void toModifiedUtf8(java.lang.String string, Pointer buffer, Pointer bufferEnd, boolean addNullTerminator) { + public static char charAt(boolean isLatin1, byte[] value, int index) { + if (isLatin1) { + return (char) (value[index] & 0xFF); + } else { + return Target_java_lang_StringUTF16.getChar(value, index); + } + } + + /** + * Writes the encoded {@code string} into the given {@code buffer} using the modified UTF8 + * encoding (null characters that are present in the input will be encoded in a way that + * they do not interfere with the null terminator). + * + * @return pointer on new position in buffer. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static Pointer toModifiedUTF8(java.lang.String string, Pointer buffer, Pointer bufferEnd, boolean addNullTerminator) { Pointer pos = buffer; - for (int i = 0; i < string.length(); i++) { - char c = string.charAt(i); - if (c >= 0x0001 && c <= 0x007F) { - pos.writeByte(0, (byte) c); - pos = pos.add(1); - } else if (c <= 0x07FF) { - pos.writeByte(0, (byte) (0xC0 | (c >> 6))); - pos.writeByte(1, (byte) (0x80 | (c & 0x3F))); - pos = pos.add(2); - } else { - pos.writeByte(0, (byte) (0xE0 | (c >> 12))); - pos.writeByte(1, (byte) (0x80 | ((c >> 6) & 0x3F))); - pos.writeByte(2, (byte) (0x80 | (c & 0x3f))); - pos = pos.add(3); - } + boolean isLatin1 = JavaLangSubstitutions.isLatin1(string); + byte[] value = JavaLangSubstitutions.getBytes(string); + for (int index = 0; index < string.length(); index++) { + pos = writeModifiedUTF8(pos, charAt(isLatin1, value, index)); } if (addNullTerminator) { @@ -511,6 +555,28 @@ public static void toModifiedUtf8(java.lang.String string, Pointer buffer, Point pos = pos.add(1); } VMError.guarantee(pos.belowOrEqual(bufferEnd), "Must not write out of bounds."); + return pos; + } + } + + public static class ImmutablePair { + + private final K key; + private final V value; + + public ImmutablePair(K key, V value) { + this.key = key; + this.value = value; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public K getKey() { + return key; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public V getValue() { + return value; } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ThreadGroup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java similarity index 81% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ThreadGroup.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java index 45a1560f6fb8..0036d9209ecb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ThreadGroup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java @@ -27,11 +27,17 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.AnnotateOriginal; +import com.oracle.svm.core.annotate.Inject; +import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.jdk.NotLoomJDK; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; @@ -61,6 +67,12 @@ final class Target_java_lang_ThreadGroup { @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupGroupsRecomputation.class, disableCaching = true)// private ThreadGroup[] groups; + @Inject @InjectAccessors(ThreadGroupIdAccessor.class) // + public long id; + + @Inject @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + long injectedId; + @Alias @TargetElement(onlyWith = NotLoomJDK.class)// native void addUnstarted(); @@ -68,6 +80,28 @@ final class Target_java_lang_ThreadGroup { @Alias @TargetElement(onlyWith = NotLoomJDK.class)// native void add(Thread t); + + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public native String getName(); + + @Alias ThreadGroup parent; +} + +/** + * This class assigns a unique id to each thread group, and this unique id is used by JFR. + */ +class ThreadGroupIdAccessor { + + private static final UninterruptibleUtils.AtomicLong nextID = new UninterruptibleUtils.AtomicLong(0L); + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static long getId(Target_java_lang_ThreadGroup that) { + if (that.injectedId == 0) { + that.injectedId = nextID.incrementAndGet(); + } + return that.injectedId; + } } @Platforms(Platform.HOSTED_ONLY.class) @@ -163,3 +197,16 @@ public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, return JavaThreadsFeature.singleton().reachableThreadGroups.get(group).groups; } } + +public class JavaLangThreadGroupSubstitutions { + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static ThreadGroup getParentThreadGroupUnsafe(ThreadGroup threadGroup) { + return SubstrateUtil.cast(threadGroup, Target_java_lang_ThreadGroup.class).parent; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long getThreadGroupId(ThreadGroup threadGroup) { + return SubstrateUtil.cast(threadGroup, Target_java_lang_ThreadGroup.class).id; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java index bb208acc802d..b6242ed65cfc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java @@ -202,8 +202,14 @@ protected static boolean wasStartedByCurrentIsolate(Thread thread) { return toTarget(thread).wasStartedByCurrentIsolate; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long getParentThreadId(Thread thread) { + return toTarget(thread).parentThreadId; + } + /* End of accessor functions. */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static Thread fromVMThread(IsolateThread vmThread) { return currentThread.get(vmThread); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java index 5f45e94a4b90..2a924670eff8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.Objects; +import com.oracle.svm.core.annotate.AnnotateOriginal; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.IsolateThread; @@ -79,6 +80,9 @@ public final class Target_java_lang_Thread { @Inject @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// boolean wasStartedByCurrentIsolate; + @Inject @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + long parentThreadId; + /** * Every thread has a {@link ParkEvent} for {@link sun.misc.Unsafe#park} and * {@link sun.misc.Unsafe#unpark}. Lazily initialized. @@ -211,6 +215,14 @@ public long getId() { return tid; } + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public native String getName(); + + @AnnotateOriginal + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public native ThreadGroup getThreadGroup(); + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Substitute public boolean isDaemon() { @@ -354,6 +366,7 @@ private void start0() { */ JavaContinuations.LoomCompatibilityUtil.setThreadStatus(this, ThreadStatus.RUNNABLE); wasStartedByCurrentIsolate = true; + parentThreadId = Thread.currentThread().getId(); long stackSize = JavaThreads.getRequestedThreadSize(JavaThreads.fromTarget(this)); JavaThreads.singleton().startThread(JavaThreads.fromTarget(this), stackSize); } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBuffer.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBuffer.java index 82360534b247..b96139baf89d 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBuffer.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBuffer.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.jfr; +import com.oracle.svm.core.c.struct.PinnedObjectField; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawFieldOffset; import org.graalvm.nativeimage.c.struct.RawStructure; @@ -96,4 +97,18 @@ static int offsetOfPos() { static int offsetOfAcquired() { throw VMError.unimplemented(); // replaced } + + /** + * Returns the type of the buffer. + */ + @RawField + @PinnedObjectField + void setBufferType(JfrBufferType bufferType); + + /** + * Sets the size of the buffer. + */ + @RawField + @PinnedObjectField + JfrBufferType getBufferType(); } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBufferAccess.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBufferAccess.java index b5db581125ba..a7efed2afaa6 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBufferAccess.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBufferAccess.java @@ -43,6 +43,7 @@ public final class JfrBufferAccess { private static final int ACQUIRED = 1; private static final int NOT_ACQUIRED = 0; + private static final int INCREASE_BUFFER_SIZE_STEP = 2; private JfrBufferAccess() { } @@ -53,11 +54,12 @@ public static UnsignedWord getHeaderSize() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static JfrBuffer allocate(UnsignedWord dataSize) { + public static JfrBuffer allocate(UnsignedWord dataSize, JfrBufferType bufferType) { UnsignedWord headerSize = JfrBufferAccess.getHeaderSize(); JfrBuffer result = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(headerSize.add(dataSize)); if (result.isNonNull()) { result.setSize(dataSize); + result.setBufferType(bufferType); reinitialize(result); } return result; @@ -130,4 +132,13 @@ public static void increaseTop(JfrBuffer buffer, UnsignedWord delta) { public static boolean isEmpty(JfrBuffer buffer) { return getDataStart(buffer).equal(buffer.getPos()); } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static UnsignedWord sizeToMatchRequirements(JfrBuffer buffer, UnsignedWord required) { + UnsignedWord bufferSize = buffer.getSize().multiply(INCREASE_BUFFER_SIZE_STEP); + while (bufferSize.belowThan(required)) { + bufferSize = bufferSize.multiply(INCREASE_BUFFER_SIZE_STEP); + } + return bufferSize; + } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ClassLoadingStatistics.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBufferType.java similarity index 51% rename from substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ClassLoadingStatistics.java rename to substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBufferType.java index c49bd16e1439..bf25dfc868af 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ClassLoadingStatistics.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrBufferType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,34 +22,26 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.jfr.events; +package com.oracle.svm.jfr; -import com.oracle.svm.core.heap.Heap; -import jdk.jfr.Category; -import jdk.jfr.Description; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.Period; -import jdk.jfr.StackTrace; -import jdk.jfr.internal.Type; - -@Label("Class Loading Statistics") -@Category("Java Application, Statistics") -@StackTrace(false) -@Name(Type.EVENT_NAME_PREFIX + "ClassLoadingStatistics") -@Period(value = "everyChunk") -public class ClassLoadingStatistics extends Event { - - @Label("Loaded Class Count") @Description("Number of classes loaded since JVM start") long loadedClassCount; - - @Label("Unloaded Class Count") @Description("Number of classes unloaded since JVM start") long unloadedClassCount; - - public static void emitClassLoadingStats() { - ClassLoadingStatistics classStats = new ClassLoadingStatistics(); - - classStats.loadedClassCount = Heap.getHeap().getClassCount(); - classStats.unloadedClassCount = 0; - classStats.commit(); - } +/** + * List of all possible buffer types, see {@link com.oracle.svm.jfr.JfrBuffer}. + */ +public enum JfrBufferType { + /** + * The thread-local native buffer, see {@link com.oracle.svm.jfr.JfrThreadLocal}. + */ + THREAD_LOCAL_NATIVE, + /** + * The thread-local java buffer, see {@link com.oracle.svm.jfr.JfrThreadLocal}. + */ + THREAD_LOCAL_JAVA, + /** + * The global JFR buffers, see {@link com.oracle.svm.jfr.JfrGlobalMemory}. + */ + GLOBAL_MEMORY, + /** + * All other buffers, like the one in {@link com.oracle.svm.jfr.JfrThreadRepository}. + */ + C_HEAP } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrChunkWriter.java index 2ed1c338dab1..decddecd8279 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrChunkWriter.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrChunkWriter.java @@ -148,10 +148,10 @@ public boolean write(JfrBuffer buffer) { */ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) { assert lock.isHeldByCurrentThread(); - JfrCloseFileOperation op = new JfrCloseFileOperation(); + JfrChangeEpochOperation op = new JfrChangeEpochOperation(); op.enqueue(); - // JfrCloseFileOperation will switch to a new epoch so data for the old epoch will not + // JfrChangeEpochOperation will switch to a new epoch so data for the old epoch will not // be modified by other threads and can be written without a safepoint SignedWord constantPoolPosition = writeCheckpointEvent(repositories); @@ -334,8 +334,8 @@ public void writeString(String str) { if (str.isEmpty()) { getFileSupport().writeByte(fd, StringEncoding.EMPTY_STRING.byteValue); } else { - getFileSupport().writeByte(fd, StringEncoding.UTF8_BYTE_ARRAY.byteValue); byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + getFileSupport().writeByte(fd, StringEncoding.UTF8_BYTE_ARRAY.byteValue); writeCompressedInt(bytes.length); getFileSupport().write(fd, bytes); } @@ -360,9 +360,9 @@ public enum StringEncoding { } } - private class JfrCloseFileOperation extends JavaVMOperation { - protected JfrCloseFileOperation() { - // Some of the JDK code that deals with files uses Java synchronization. So, we need to + private class JfrChangeEpochOperation extends JavaVMOperation { + protected JfrChangeEpochOperation() { + // Some JDK code that deals with files uses Java synchronization. So, we need to // allow Java synchronization for this VM operation. super("JFR close file", SystemEffect.SAFEPOINT); } @@ -402,6 +402,7 @@ private void changeEpoch() { JfrBufferAccess.reinitialize(buffer); } JfrTraceIdEpoch.getInstance().changeEpoch(); + SubstrateJVM.getThreadRepo().reinitializeRepository(); } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrConstantPool.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrConstantPool.java index 8127adcd8faa..b9a4cf4a2252 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrConstantPool.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrConstantPool.java @@ -33,6 +33,19 @@ * other JFR code could see partially added data when it tries to iterate the data at a safepoint. */ public interface JfrConstantPool { + + /** + * If constant pool is empty, the {@link JfrConstantPool#write(JfrChunkWriter)} function returns + * this value. + */ + int EMPTY = 0; + + /** + * If constant pool is not empty, the {@link JfrConstantPool#write(JfrChunkWriter)} function + * returns this value. + */ + int NON_EMPTY = 1; + /** * Persists the data of the previous epoch. May only be called at a safepoint, after the epoch * changed. diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrEvents.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrEvents.java index c685a30f5cee..f949c799a916 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrEvents.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrEvents.java @@ -24,22 +24,34 @@ */ package com.oracle.svm.jfr; +import org.graalvm.compiler.core.common.NumUtil; +import org.graalvm.compiler.options.OptionsParser; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.util.VMError; + import jdk.jfr.internal.PlatformEventType; import jdk.jfr.internal.Type; import jdk.jfr.internal.TypeLibrary; -import org.graalvm.compiler.core.common.NumUtil; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; /** * The event IDs depend on the metadata.xml and therefore vary between JDK versions. */ public enum JfrEvents { - ThreadStartEvent("jdk.ThreadStart"), - ThreadEndEvent("jdk.ThreadEnd"), - DataLossEvent("jdk.DataLoss"); + ThreadStart("jdk.ThreadStart"), + ThreadEnd("jdk.ThreadEnd"), + DataLoss("jdk.DataLoss"), + ClassLoadingStatistics("jdk.ClassLoadingStatistics"), + InitialEnvironmentVariable("jdk.InitialEnvironmentVariable"), + InitialSystemProperty("jdk.InitialSystemProperty"), + JavaThreadStatistics("jdk.JavaThreadStatistics"), + JVMInformation("jdk.JVMInformation"), + OSInformation("jdk.OSInformation"), + PhysicalMemory("jdk.PhysicalMemory"), + ExecutionSample("jdk.ExecutionSample"), + NativeMethodSample("jdk.NativeMethodSample"); private final long id; @@ -48,6 +60,22 @@ public enum JfrEvents { this.id = getEventTypeId(name); } + @Platforms(Platform.HOSTED_ONLY.class) + private static String getMostSimilarEvent(String missingTypeName) { + float threshold = OptionsParser.FUZZY_MATCH_THRESHOLD; + String mostSimilar = null; + for (Type type : TypeLibrary.getInstance().getTypes()) { + if (type instanceof PlatformEventType) { + float similarity = OptionsParser.stringSimilarity(type.getName(), missingTypeName); + if (similarity > threshold) { + threshold = similarity; + mostSimilar = type.getName(); + } + } + } + return mostSimilar; + } + @Platforms(Platform.HOSTED_ONLY.class) private static long getEventTypeId(String name) { try { @@ -56,10 +84,17 @@ private static long getEventTypeId(String name) { return type.getId(); } } - return 0; + + String exceptionMessage = "Event " + name + " is not found!"; + String mostSimilarEvent = getMostSimilarEvent(name); + if (mostSimilarEvent != null) { + exceptionMessage += " The most similar event is " + mostSimilarEvent + "."; + } + exceptionMessage += " Take a look at 'metadata.xml' to see all available events."; + + throw VMError.shouldNotReachHere(exceptionMessage); } catch (Exception ex) { - VMError.shouldNotReachHere(ex); - return 0; + throw VMError.shouldNotReachHere(ex); } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java index 2b788a86d275..8e9d4eced53a 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java @@ -49,7 +49,6 @@ import com.oracle.svm.core.thread.ThreadListenerSupport; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.jfr.events.ClassLoadingStatistics; import com.oracle.svm.jfr.traceid.JfrTraceId; import com.oracle.svm.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.jfr.traceid.JfrTraceIdMap; @@ -68,7 +67,7 @@ * * There are two different kinds of JFR events: *
    - *
  • Java-level events where there is a Java class such as {@link ClassLoadingStatistics} that + *
  • Java-level events where there is a Java class such as {@link com.oracle.svm.jfr.events.JVMInformation} that * defines the event. Those events are typically triggered by the Java application and a Java * {@link EventWriter} object is used when writing the event to a buffer.
  • *
  • Native events are triggered by the JVM itself and are defined in the JFR metadata.xml file. @@ -131,6 +130,7 @@ public void afterRegistration(AfterRegistrationAccess access) { ImageSingletons.add(JfrTraceIdEpoch.class, new JfrTraceIdEpoch()); JfrSerializerSupport.get().register(new JfrFrameTypeSerializer()); + JfrSerializerSupport.get().register(new JfrThreadStateSerializer()); ThreadListenerSupport.get().register(SubstrateJVM.getThreadLocal()); } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFrameTypeSerializer.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFrameTypeSerializer.java index 669339b0ff8e..72dbfb1a692d 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFrameTypeSerializer.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFrameTypeSerializer.java @@ -45,6 +45,6 @@ public int write(JfrChunkWriter writer) { writer.writeCompressedInt(i); writer.writeString(values[i].getText()); } - return 1; + return NON_EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrGlobalMemory.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrGlobalMemory.java index b848acf02876..cb5df203797c 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrGlobalMemory.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrGlobalMemory.java @@ -57,7 +57,7 @@ public void initialize(long globalBufferSize, long globalBufferCount) { // Allocate all buffers eagerly. buffers = UnmanagedMemory.calloc(SizeOf.unsigned(JfrBuffers.class).multiply(WordFactory.unsigned(bufferCount))); for (int i = 0; i < bufferCount; i++) { - JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(bufferSize)); + JfrBuffer buffer = JfrBufferAccess.allocate(WordFactory.unsigned(bufferSize), JfrBufferType.GLOBAL_MEMORY); buffers.addressOf(i).write(buffer); } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrManager.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrManager.java index 54bf41218a86..6ceaafbd8e83 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrManager.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrManager.java @@ -44,13 +44,8 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.util.UserError.UserException; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.jfr.events.ClassLoadingStatistics; -import com.oracle.svm.jfr.events.InitialEnvironmentVariable; -import com.oracle.svm.jfr.events.InitialSystemProperty; -import com.oracle.svm.jfr.events.JVMInformation; -import com.oracle.svm.jfr.events.JavaThreadStatistics; -import com.oracle.svm.jfr.events.OSInformation; -import com.oracle.svm.jfr.events.PhysicalMemory; +import com.oracle.svm.jfr.events.EndChunkPeriodEvents; +import com.oracle.svm.jfr.events.EveryChunkPeriodEvents; import jdk.jfr.FlightRecorder; import jdk.jfr.Recording; @@ -91,9 +86,10 @@ Runnable startupHook() { Runnable shutdownHook() { return () -> { if (SubstrateOptions.FlightRecorder.getValue()) { - // Everything should already have been torn down by JVM.destroyJFR(), which is - // called in a shutdown hook. - assert !SubstrateJVM.isInitialized(); + // Everything should already have been torn down by JVM.destroyJFR(), which is called in + // a shutdown hook. So in this method we should only unregister periodic events. + FlightRecorder.removePeriodicEvent(EveryChunkPeriodEvents::emitEveryChunkPeriodEvents); + FlightRecorder.removePeriodicEvent(EndChunkPeriodEvents::emitEndChunkPeriodEvents); } }; } @@ -103,13 +99,11 @@ private static void parseFlightRecorderLogging(String option) { } private static void periodicEventSetup() throws SecurityException { - FlightRecorder.addPeriodicEvent(InitialSystemProperty.class, InitialSystemProperty::emitSystemProperties); - FlightRecorder.addPeriodicEvent(InitialEnvironmentVariable.class, InitialEnvironmentVariable::emitEnvironmentVariables); - FlightRecorder.addPeriodicEvent(JVMInformation.class, JVMInformation::emitJVMInformation); - FlightRecorder.addPeriodicEvent(OSInformation.class, OSInformation::emitOSInformation); - FlightRecorder.addPeriodicEvent(PhysicalMemory.class, PhysicalMemory::emitPhysicalMemory); - FlightRecorder.addPeriodicEvent(JavaThreadStatistics.class, JavaThreadStatistics::emitJavaThreadStats); - FlightRecorder.addPeriodicEvent(ClassLoadingStatistics.class, ClassLoadingStatistics::emitClassLoadingStats); + // Here, we are registering two "abstract" periodic events. They are "abstract" because, we + // are using them only to emit "real" events (like OSInformation, JVMInformation...) in + // callbacks (second argument) after specific period of time ("everyChunk" or "endChunk"). + FlightRecorder.addPeriodicEvent(EveryChunkPeriodEvents.class, EveryChunkPeriodEvents::emitEveryChunkPeriodEvents); + FlightRecorder.addPeriodicEvent(EndChunkPeriodEvents.class, EndChunkPeriodEvents::emitEndChunkPeriodEvents); } private static void initRecording() { diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/OSInformation.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrMethodRepository.java similarity index 57% rename from substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/OSInformation.java rename to substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrMethodRepository.java index 2bd49eac77f8..f27ced097873 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/OSInformation.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrMethodRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,31 +22,22 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.jfr.events; +package com.oracle.svm.jfr; -import jdk.jfr.Category; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.Period; -import jdk.jfr.StackTrace; -import jdk.jfr.internal.Type; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; -@Label("OS Information") -@Category("Operating System") -@StackTrace(false) -@Name(Type.EVENT_NAME_PREFIX + "OSInformation") -@Period(value = "endChunk") -public class OSInformation extends Event { +public class JfrMethodRepository implements JfrConstantPool { - @Label("OS Version") String osVersion; + @Platforms(Platform.HOSTED_ONLY.class) + public JfrMethodRepository() { + } + + public void teardown() { + } - public static void emitOSInformation() { - OSInformation osInfo = new OSInformation(); - String name = System.getProperty("os.name"); - String ver = System.getProperty("os.version"); - String arch = System.getProperty("os.arch"); - osInfo.osVersion = name + " (" + ver + ") arch:" + arch; - osInfo.commit(); + @Override + public int write(JfrChunkWriter writer) { + return EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventSetting.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventSetting.java index fad3fcb4900d..b43706836459 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventSetting.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventSetting.java @@ -60,6 +60,7 @@ public void setCutoffTicks(long cutoffTicks) { this.cutoffTicks = cutoffTicks; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean hasStackTrace() { return stackTrace; } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventWriter.java index 10e0758be897..16ce2eece90b 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventWriter.java @@ -30,9 +30,12 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.annotate.DuplicatedInNativeCode; import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.jdk.JavaLangSubstitutions; import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.util.VMError; /** * A JFR event writer that does not allocate any objects in the Java heap. Can only be used from @@ -147,10 +150,18 @@ public static void putLong(JfrNativeEventWriterData data, long v) { } @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) - public static void putUtf8(JfrNativeEventWriterData data, String string) { - int size = UninterruptibleUtils.String.modifiedUtf8Length(string, true); - if (ensureSize(data, size)) { - UninterruptibleUtils.String.toModifiedUtf8(string, data.getCurrentPos(), data.getEndPos(), true); + public static void putString(JfrNativeEventWriterData data, String string) { + byte[] byteString = JavaLangSubstitutions.getBytes(string); + if (byteString.length == 0) { + putByte(data, JfrChunkWriter.StringEncoding.EMPTY_STRING.byteValue); + } else { + int mUTF8Length = UninterruptibleUtils.String.modifiedUTF8Length(string, false); + putByte(data, JfrChunkWriter.StringEncoding.UTF8_BYTE_ARRAY.byteValue); + putInt(data, mUTF8Length); + if (ensureSize(data, mUTF8Length)) { + Pointer newPosition = UninterruptibleUtils.String.toModifiedUTF8(string, data.getCurrentPos(), data.getEndPos(), false); + data.setCurrentPos(newPosition); + } } } @@ -208,7 +219,7 @@ private static void hardReset(JfrNativeEventWriterData data) { JfrBuffer buffer = data.getJfrBuffer(); data.setStartPos(buffer.getPos()); data.setCurrentPos(buffer.getPos()); - data.setEndPos(buffer.getPos().add(buffer.getSize())); + data.setEndPos(JfrBufferAccess.getDataEnd(buffer)); } @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) @@ -223,15 +234,50 @@ private static void cancel(JfrNativeEventWriterData data) { @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) private static boolean accommodate(JfrNativeEventWriterData data, UnsignedWord used, int requested) { - // In case that the thread-local buffer is still not large enough to hold the data of the - // event even though the buffer was flushed successfully, a larger buffer may be returned. - JfrBuffer newBuffer = JfrThreadLocal.flush(data.getJfrBuffer(), used, requested); + JfrBuffer newBuffer = WordFactory.nullPointer(); + JfrBuffer oldBuffer = data.getJfrBuffer(); + switch (oldBuffer.getBufferType()) { + case THREAD_LOCAL_NATIVE: + // In case that the thread-local buffer is still not large enough to hold the data + // of the event even though the buffer was flushed successfully, a larger buffer may + // be returned. + newBuffer = JfrThreadLocal.flush(data.getJfrBuffer(), used, requested); + break; + case C_HEAP: + // Allocate new buffer. + newBuffer = JfrBufferAccess.allocate(JfrBufferAccess.sizeToMatchRequirements(oldBuffer, + WordFactory.unsigned(requested).subtract(getAvailableSize(data))), JfrBufferType.C_HEAP); + if (newBuffer.isNull()) { + break; + } + + // Copy the entire contents of the old buffer to the new one. + UnsignedWord unflushedSize = JfrBufferAccess.getUnflushedSize(oldBuffer); + UnmanagedMemoryUtil.copy(oldBuffer.getTop(), newBuffer.getTop(), unflushedSize.add(used)); + + // Move the current pointer in the new buffer to the last byte that was committed. + JfrBufferAccess.increasePos(newBuffer, unflushedSize); + + // Destroy the old buffer. + JfrBufferAccess.free(oldBuffer); + + assert newBuffer.getSize().aboveThan(unflushedSize.add(used).add(requested)); + break; + case THREAD_LOCAL_JAVA: + VMError.shouldNotReachHere("Cannot expand thread-local java buffer!"); + break; + case GLOBAL_MEMORY: + VMError.shouldNotReachHere("Cannot expand global buffers!"); + break; + } + if (newBuffer.isNull()) { // The flush failed for some reason (e.g., because not enough global memory was // available). cancel(data); return false; } + data.setJfrBuffer(newBuffer); hardReset(data); increaseCurrentPos(data, used); @@ -239,7 +285,7 @@ private static boolean accommodate(JfrNativeEventWriterData data, UnsignedWord u } @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) - private static void commit(JfrNativeEventWriterData data) { + public static void commit(JfrNativeEventWriterData data) { JfrBuffer buffer = data.getJfrBuffer(); assert isValid(data); assert buffer.getPos().equal(data.getStartPos()); diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventWriterDataAccess.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventWriterDataAccess.java index 2559714f410e..946ce5d8616f 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventWriterDataAccess.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrNativeEventWriterDataAccess.java @@ -30,6 +30,10 @@ * Helper class that holds methods related to {@link JfrNativeEventWriterData}. */ public final class JfrNativeEventWriterDataAccess { + + /** + * Initialize the {@code data} buffer. Overflow policy (default one): flush into global memory. + */ @Uninterruptible(reason = "Accesses a JFR buffer", callerMustBe = true) public static void initialize(JfrNativeEventWriterData data, JfrBuffer buffer) { assert buffer.isNonNull(); @@ -39,4 +43,24 @@ public static void initialize(JfrNativeEventWriterData data, JfrBuffer buffer) { data.setCurrentPos(buffer.getPos()); data.setEndPos(JfrBufferAccess.getDataEnd(buffer)); } + + /** + * Initialize the current thread's native local buffer. + */ + @Uninterruptible(reason = "Accesses a JFR buffer", callerMustBe = true) + public static void initializeNativeBuffer(JfrNativeEventWriterData data) { + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); + JfrBuffer nativeBuffer = jfrThreadLocal.getNativeBuffer(); + initialize(data, nativeBuffer); + } + + /** + * Initialize the current thread's java local buffer. + */ + @Uninterruptible(reason = "Accesses a JFR buffer", callerMustBe = true) + public static void initializeJavaBuffer(JfrNativeEventWriterData data) { + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); + JfrBuffer nativeBuffer = jfrThreadLocal.getJavaBuffer(); + initialize(data, nativeBuffer); + } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrStackTraceRepository.java new file mode 100644 index 000000000000..b086a15a53e1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrStackTraceRepository.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jfr; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.annotate.Uninterruptible; + +/** + * Repository that collects all metadata about stacktraces. + */ +public class JfrStackTraceRepository implements JfrConstantPool { + + private int depth = SubstrateOptions.MaxJavaStackTraceDepth.getValue(); + + @Platforms(Platform.HOSTED_ONLY.class) + JfrStackTraceRepository() { + } + + public void teardown() { + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + public long getStackTraceId(@SuppressWarnings("unused") int skipCount, @SuppressWarnings("unused") boolean previousEpoch) { + return 0; + } + + public void setStackTraceDepth(int depth) { + if (depth < 0 || depth > SubstrateOptions.MaxJavaStackTraceDepth.getValue()) { + throw new IllegalArgumentException("StackTrace depth (" + depth + ") is not in a valid range!"); + } + this.depth = depth; + } + + @Override + public int write(@SuppressWarnings("unused") JfrChunkWriter writer) { + return EMPTY; + } +} diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrSymbolRepository.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrSymbolRepository.java index 56ebfe597881..a42ab33843f7 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrSymbolRepository.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrSymbolRepository.java @@ -26,8 +26,6 @@ import java.nio.charset.StandardCharsets; -import com.oracle.svm.core.jdk.UninterruptibleEntry; -import com.oracle.svm.core.jdk.UninterruptibleHashtable; import org.graalvm.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -38,26 +36,27 @@ import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; -import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; -import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.c.struct.PinnedObjectField; import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.jdk.UninterruptibleAbstractHashtable; +import com.oracle.svm.core.jdk.UninterruptibleEntry; +import com.oracle.svm.core.jdk.UninterruptibleHashtable; import com.oracle.svm.jfr.traceid.JfrTraceIdEpoch; /** * In Native Image, we use {@link java.lang.String} objects that live in the image heap as symbols. */ public class JfrSymbolRepository implements JfrConstantPool { - private final JfrSymbolHashtable table0; - private final JfrSymbolHashtable table1; + private final UninterruptibleAbstractHashtable table0; + private final UninterruptibleAbstractHashtable table1; @Platforms(Platform.HOSTED_ONLY.class) public JfrSymbolRepository() { - table0 = new JfrSymbolHashtable(); - table1 = new JfrSymbolHashtable(); + table0 = UninterruptibleAbstractHashtable.makeHashtableThreadSafe("jfrSymbolHashtable", new JfrSymbolHashtable()); + table1 = UninterruptibleAbstractHashtable.makeHashtableThreadSafe("jfrSymbolHashtable", new JfrSymbolHashtable()); } public void teardown() { @@ -66,7 +65,7 @@ public void teardown() { } @Uninterruptible(reason = "Called by uninterruptible code.") - private JfrSymbolHashtable getTable(boolean previousEpoch) { + private UninterruptibleAbstractHashtable getTable(boolean previousEpoch) { boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); if (epoch) { return table0; @@ -96,14 +95,14 @@ public long getSymbolId(String imageHeapString, boolean previousEpoch, boolean r int hashcode = (int) (rawPointerValue ^ (rawPointerValue >>> 32)); symbol.setHash(hashcode); - return getTable(previousEpoch).add(symbol); + return getTable(previousEpoch).put(symbol); } @Override public int write(JfrChunkWriter writer) { - JfrSymbolHashtable table = getTable(true); + UninterruptibleAbstractHashtable table = getTable(true); if (table.getSize() == 0) { - return 0; + return EMPTY; } writer.writeCompressedLong(JfrTypes.Symbol.getId()); writer.writeCompressedLong(table.getSize()); @@ -122,7 +121,7 @@ public int write(JfrChunkWriter writer) { } } table.setSize(0); - return 1; + return NON_EMPTY; } private static void writeSymbol(JfrChunkWriter writer, JfrSymbol symbol) { @@ -162,10 +161,6 @@ private interface JfrSymbol extends UninterruptibleEntry { } private static class JfrSymbolHashtable extends UninterruptibleHashtable { - @Platforms(Platform.HOSTED_ONLY.class) - JfrSymbolHashtable() { - super("jfrSymbolHashtable"); - } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override @@ -175,7 +170,7 @@ protected JfrSymbol[] createTable(int size) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - protected void free(JfrSymbol t) { + public void free(JfrSymbol t) { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(t); } @@ -188,13 +183,7 @@ protected boolean isEqual(JfrSymbol a, JfrSymbol b) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override protected JfrSymbol copyToHeap(JfrSymbol symbolOnStack) { - UnsignedWord size = SizeOf.unsigned(JfrSymbol.class); - JfrSymbol symbolOnHeap = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(size); - if (symbolOnHeap.isNonNull()) { - UnmanagedMemoryUtil.copy((Pointer) symbolOnStack, (Pointer) symbolOnHeap, size); - return symbolOnHeap; - } - return WordFactory.nullPointer(); + return allocateOnHeap((Pointer) symbolOnStack, SizeOf.unsigned(JfrSymbol.class)); } } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadEpochData.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadEpochData.java new file mode 100644 index 000000000000..42e06d205de0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadEpochData.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jfr; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleAbstractHashtable; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +public class JfrThreadEpochData { + private final UninterruptibleAbstractHashtable visitedThreadGroups; + + private JfrBuffer threadBuffer; + private int threadCount; + private JfrBuffer threadGroupBuffer; + private int threadGroupCount; + + @Platforms(Platform.HOSTED_ONLY.class) + JfrThreadEpochData() { + this.threadCount = 0; + this.threadGroupCount = 0; + this.visitedThreadGroups = new JfrThreadRepository.JfrVisitedThreadGroups(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public UninterruptibleAbstractHashtable getVisitedThreadGroups() { + return visitedThreadGroups; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public JfrBuffer getThreadBuffer() { + return threadBuffer; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void setThreadBuffer(JfrBuffer threadBuffer) { + this.threadBuffer = threadBuffer; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getThreadCount() { + return threadCount; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void setThreadCount(int threadCount) { + this.threadCount = threadCount; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void incThreadCount() { + threadCount++; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public JfrBuffer getThreadGroupBuffer() { + return threadGroupBuffer; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void setThreadGroupBuffer(JfrBuffer threadGroupBuffer) { + this.threadGroupBuffer = threadGroupBuffer; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getThreadGroupCount() { + return threadGroupCount; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void setThreadGroupCount(int threadGroupCount) { + this.threadGroupCount = threadGroupCount; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void incThreadGroupCount() { + threadGroupCount++; + } +} diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadLocal.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadLocal.java index 06cac6b8d089..35f51d0318b7 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadLocal.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadLocal.java @@ -35,6 +35,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.thread.Target_java_lang_Thread; import com.oracle.svm.core.thread.ThreadListener; import com.oracle.svm.core.thread.VMOperation; @@ -43,6 +44,8 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalObject; import com.oracle.svm.core.threadlocal.FastThreadLocalWord; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.jfr.events.ThreadEndEvent; +import com.oracle.svm.jfr.events.ThreadStartEvent; import jdk.jfr.internal.EventWriter; @@ -69,6 +72,7 @@ public class JfrThreadLocal implements ThreadListener { private static final FastThreadLocalWord nativeBuffer = FastThreadLocalFactory.createWord("JfrThreadLocal.nativeBuffer"); private static final FastThreadLocalWord dataLost = FastThreadLocalFactory.createWord("JfrThreadLocal.dataLost"); private static final FastThreadLocalLong traceId = FastThreadLocalFactory.createLong("JfrThreadLocal.traceId"); + private static final FastThreadLocalLong parentThreadId = FastThreadLocalFactory.createLong("JfrThreadLocal.parentThreadId"); private long threadLocalBufferSize; @@ -84,15 +88,25 @@ public void initialize(long bufferSize) { @Override public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { // We copy the thread id to a thread-local in the IsolateThread. This is necessary so that - // we are always able access that value without having to go through a heap-allocated Java - // object. + // we are always able to access that value without having to go through a heap-allocated + // Java object. Target_java_lang_Thread t = SubstrateUtil.cast(javaThread, Target_java_lang_Thread.class); traceId.set(isolateThread, t.getId()); + parentThreadId.set(isolateThread, JavaThreads.getParentThreadId(javaThread)); + + SubstrateJVM.getThreadRepo().serializeThread(javaThread); + + // Emit ThreadStart event before thread.run(). + ThreadStartEvent.emit(isolateThread); } @Uninterruptible(reason = "Accesses a JFR buffer.") @Override public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { + + // Emit ThreadEnd event after thread.run() finishes. + ThreadEndEvent.emit(isolateThread); + // Flush all buffers if necessary. if (SubstrateJVM.isRecording()) { JfrBuffer jb = javaBuffer.get(isolateThread); @@ -108,6 +122,7 @@ public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { // Free and reset all data. traceId.set(isolateThread, 0); + parentThreadId.set(isolateThread, 0); dataLost.set(isolateThread, WordFactory.unsigned(0)); javaEventWriter.set(isolateThread, null); @@ -123,6 +138,11 @@ public long getTraceId(IsolateThread isolateThread) { return traceId.get(isolateThread); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getParentThreadId(IsolateThread isolateThread) { + return parentThreadId.get(isolateThread); + } + public Target_jdk_jfr_internal_EventWriter getEventWriter() { return javaEventWriter.get(); } @@ -155,7 +175,7 @@ public JfrBuffer getJavaBuffer() { VMError.guarantee(traceId.get() > 0, "Thread local JFR data must be initialized"); JfrBuffer result = javaBuffer.get(); if (result.isNull()) { - result = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize)); + result = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_JAVA); javaBuffer.set(result); } return result; @@ -166,7 +186,7 @@ public JfrBuffer getNativeBuffer() { VMError.guarantee(traceId.get() > 0, "Thread local JFR data must be initialized"); JfrBuffer result = nativeBuffer.get(); if (result.isNull()) { - result = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize)); + result = JfrBufferAccess.allocate(WordFactory.unsigned(threadLocalBufferSize), JfrBufferType.THREAD_LOCAL_NATIVE); nativeBuffer.set(result); } return result; @@ -223,12 +243,12 @@ private static void writeDataLoss(JfrBuffer buffer, UnsignedWord unflushedSize) assert buffer.isNonNull(); assert unflushedSize.aboveThan(0); UnsignedWord totalDataLoss = increaseDataLost(unflushedSize); - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.DataLossEvent)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.DataLoss)) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initialize(data, buffer); JfrNativeEventWriter.beginEventWrite(data, false); - JfrNativeEventWriter.putLong(data, JfrEvents.DataLossEvent.getId()); + JfrNativeEventWriter.putLong(data, JfrEvents.DataLoss.getId()); JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); JfrNativeEventWriter.putLong(data, unflushedSize.rawValue()); JfrNativeEventWriter.putLong(data, totalDataLoss.rawValue()); diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadRepository.java new file mode 100644 index 000000000000..a02daf9b2d0b --- /dev/null +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadRepository.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jfr; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; +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 org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleEntry; +import com.oracle.svm.core.jdk.UninterruptibleHashtable; +import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.thread.JavaLangThreadGroupSubstitutions; +import com.oracle.svm.core.thread.JavaThreads; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.jfr.traceid.JfrTraceIdEpoch; + +import jdk.jfr.internal.Options; + +/** + * Repository that collects all metadata about threads and thread groups. + */ +public final class JfrThreadRepository implements JfrConstantPool { + + private static final long INITIAL_BUFFER_SIZE = Options.getThreadBufferSize(); + + private final VMMutex mutex; + + private final JfrThreadEpochData epochData0; + private final JfrThreadEpochData epochData1; + + @Platforms(Platform.HOSTED_ONLY.class) + JfrThreadRepository() { + this.epochData0 = new JfrThreadEpochData(); + this.epochData1 = new JfrThreadEpochData(); + this.mutex = new VMMutex("jfrThreadRepository"); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private JfrThreadEpochData getEpochData(boolean previousEpoch) { + boolean epoch = previousEpoch ? JfrTraceIdEpoch.getInstance().previousEpoch() : JfrTraceIdEpoch.getInstance().currentEpoch(); + return epoch ? epochData0 : epochData1; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void clearEpochData(JfrThreadEpochData epochData) { + epochData.setThreadCount(0); + epochData.setThreadGroupCount(0); + + epochData.getVisitedThreadGroups().clear(); + + JfrBufferAccess.reinitialize(epochData.getThreadBuffer()); + JfrBufferAccess.reinitialize(epochData.getThreadGroupBuffer()); + } + + @Uninterruptible(reason = "Releasing repository buffers.") + public void teardown() { + epochData0.getVisitedThreadGroups().teardown(); + epochData1.getVisitedThreadGroups().teardown(); + + JfrBufferAccess.free(epochData0.getThreadBuffer()); + JfrBufferAccess.free(epochData1.getThreadBuffer()); + epochData0.setThreadBuffer(WordFactory.nullPointer()); + epochData1.setThreadBuffer(WordFactory.nullPointer()); + + JfrBufferAccess.free(epochData0.getThreadGroupBuffer()); + JfrBufferAccess.free(epochData1.getThreadGroupBuffer()); + epochData0.setThreadGroupBuffer(WordFactory.nullPointer()); + epochData1.setThreadGroupBuffer(WordFactory.nullPointer()); + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + public void serializeThread(Thread thread) { + if (!SubstrateJVM.isRecording()) { + return; + } + + mutex.lockNoTransition(); + JfrThreadEpochData epochData = getEpochData(false); + try { + if (epochData.getThreadBuffer().isNull()) { + // This will happen only on the first call of the serialize method. + epochData.setThreadBuffer(JfrBufferAccess.allocate(WordFactory.unsigned(INITIAL_BUFFER_SIZE), JfrBufferType.C_HEAP)); + } + + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.getThreadBuffer()); + + JfrNativeEventWriter.putLong(data, thread.getId()); + JfrNativeEventWriter.putString(data, thread.getName()); + JfrNativeEventWriter.putLong(data, thread.getId()); + JfrNativeEventWriter.putString(data, thread.getName()); + JfrNativeEventWriter.putLong(data, thread.getId()); + + ThreadGroup threadGroup = thread.getThreadGroup(); + if (threadGroup != null) { + long threadGroupId = JavaLangThreadGroupSubstitutions.getThreadGroupId(threadGroup); + JfrNativeEventWriter.putLong(data, threadGroupId); + serializeThreadGroup(threadGroupId, threadGroup); + } else { + JfrNativeEventWriter.putLong(data, 0); + } + epochData.incThreadCount(); + + // Maybe during writing, the thread buffer was replaced with a new (larger) one, so we + // need to update the repository pointer as well. + epochData.setThreadBuffer(data.getJfrBuffer()); + + JfrNativeEventWriter.commit(data); + } finally { + mutex.unlock(); + } + } + + @Uninterruptible(reason = "Epoch must not change while in this method.") + private void serializeThreadGroup(long threadGroupId, ThreadGroup threadGroup) { + VMError.guarantee(mutex.isOwner(), "The current thread is not the owner of the mutex!"); + + JfrThreadEpochData epochData = getEpochData(false); + if (epochData.getThreadGroupBuffer().isNull()) { + // This will happen only on the first call of the serialize method. + epochData.setThreadGroupBuffer(JfrBufferAccess.allocate(WordFactory.unsigned(INITIAL_BUFFER_SIZE), JfrBufferType.C_HEAP)); + } + + JfrVisited jfrVisited = StackValue.get(JfrVisited.class); + jfrVisited.setThreadGroupId(threadGroupId); + jfrVisited.setHash((int) threadGroupId); + if (!epochData.getVisitedThreadGroups().putIfAbsent(jfrVisited)) { + return; + } + + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initialize(data, epochData.getThreadGroupBuffer()); + JfrNativeEventWriter.putLong(data, threadGroupId); + + ThreadGroup parentThreadGroup = JavaLangThreadGroupSubstitutions.getParentThreadGroupUnsafe(threadGroup); + long parentThreadGroupId = 0; + if (parentThreadGroup != null) { + parentThreadGroupId = JavaLangThreadGroupSubstitutions.getThreadGroupId(parentThreadGroup); + } + JfrNativeEventWriter.putLong(data, parentThreadGroupId); + + JfrNativeEventWriter.putString(data, threadGroup.getName()); + epochData.incThreadGroupCount(); + + // Maybe during writing, the thread group buffer was replaced with a new (larger) one, so we + // need to update the repository pointer as well. + epochData.setThreadGroupBuffer(data.getJfrBuffer()); + + JfrNativeEventWriter.commit(data); + + if (parentThreadGroupId > 0) { + // Parent is not null, need to visit him as well. + serializeThreadGroup(parentThreadGroupId, parentThreadGroup); + } + } + + @Override + public int write(JfrChunkWriter writer) { + JfrThreadEpochData epochData = getEpochData(true); + int count = writeThreads(writer, epochData); + count += writeThreadGroups(writer, epochData); + clearEpochData(epochData); + return count; + } + + private int writeThreads(JfrChunkWriter writer, JfrThreadEpochData epochData) { + VMError.guarantee(epochData.getThreadCount() > 0, "Thread repository must not be empty."); + + writer.writeCompressedLong(JfrTypes.Thread.getId()); + writer.writeCompressedInt(epochData.getThreadCount()); + writer.write(epochData.getThreadBuffer()); + + return NON_EMPTY; + } + + private int writeThreadGroups(JfrChunkWriter writer, JfrThreadEpochData epochData) { + if (epochData.getThreadGroupCount() == 0) { + return EMPTY; + } + + writer.writeCompressedLong(JfrTypes.ThreadGroup.getId()); + writer.writeCompressedInt(epochData.getThreadGroupCount()); + writer.write(epochData.getThreadGroupBuffer()); + + return NON_EMPTY; + } + + /** + * After writing all the data into the chunk, it is necessary to clear data structures and to + * re-register all threads and thread groups of the currently running threads (i.e., it is + * necessary to iterate over all threads). Otherwise, this would leak memory. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void reinitializeRepository() { + for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + serializeThread(JavaThreads.fromVMThread(thread)); + } + } + + @RawStructure + interface JfrVisited extends UninterruptibleEntry { + @RawField + void setThreadGroupId(long threadGroupId); + + @RawField + long getThreadGroupId(); + } + + static class JfrVisitedThreadGroups extends UninterruptibleHashtable { + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected JfrVisited[] createTable(int size) { + return new JfrVisited[size]; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void free(JfrVisited t) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(t); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected boolean isEqual(JfrVisited a, JfrVisited b) { + return a.getThreadGroupId() == b.getThreadGroupId(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected JfrVisited copyToHeap(JfrVisited visitedOnStack) { + return allocateOnHeap((Pointer) visitedOnStack, SizeOf.unsigned(JfrVisited.class)); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadState.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadState.java new file mode 100644 index 000000000000..7cf8a6ca46b1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadState.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jfr; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.util.VMError; + +/** + * List of all possible thread states. + */ +public enum JfrThreadState { + NEW("STATE_NEW"), + RUNNABLE("STATE_RUNNABLE"), + BLOCKED("STATE_BLOCKED"), + WAITING("STATE_WAITING"), + TIMED_WAITING("STATE_TIMED_WAITING"), + TERMINATED("STATE_TERMINATED"); + + private final String text; + + @Platforms(Platform.HOSTED_ONLY.class) + JfrThreadState(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public byte getId() { + // First entry needs to have id 0. + return (byte) ordinal(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static byte getId(Thread.State threadState) { + return threadStateToJfrThreadState(threadState).getId(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static JfrThreadState threadStateToJfrThreadState(Thread.State threadState) { + switch (threadState) { + case NEW: + return NEW; + case RUNNABLE: + return RUNNABLE; + case BLOCKED: + return BLOCKED; + case WAITING: + return WAITING; + case TIMED_WAITING: + return TIMED_WAITING; + case TERMINATED: + return TERMINATED; + default: + throw VMError.shouldNotReachHere("Unknown thread state - " + threadState); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/PhysicalMemory.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadStateSerializer.java similarity index 52% rename from substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/PhysicalMemory.java rename to substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadStateSerializer.java index 3589b0efb595..ebee7f46d303 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/PhysicalMemory.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrThreadStateSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,35 +22,31 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.jfr.events; +package com.oracle.svm.jfr; -import jdk.jfr.Category; -import jdk.jfr.DataAmount; -import jdk.jfr.Description; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.Period; -import jdk.jfr.StackTrace; -import jdk.jfr.internal.Type; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; -@Label("Physical Memory") -@Description("OS Physical Memory") -@Category("Operating System, Memory") -@StackTrace(false) -@Name(Type.EVENT_NAME_PREFIX + "PhysicalMemory") -@Period(value = "everyChunk") -public class PhysicalMemory extends Event { +/** + * Used to serialize all possible thread states into the chunk. + */ +public class JfrThreadStateSerializer implements JfrConstantPool { - @Label("Total Size") @Description("Total amount of physical memory available to OS") @DataAmount long totalSize; + @Platforms(Platform.HOSTED_ONLY.class) + public JfrThreadStateSerializer() { + } - @Label("Used Size") @Description("Total amount of physical memory in use") @DataAmount long usedSize; + @Override + public int write(JfrChunkWriter writer) { + writer.writeCompressedLong(JfrTypes.ThreadState.getId()); - public static void emitPhysicalMemory() { - PhysicalMemory pmInfo = new PhysicalMemory(); + JfrThreadState[] threadStates = JfrThreadState.values(); + writer.writeCompressedLong(threadStates.length); + for (int i = 0; i < threadStates.length; i++) { + writer.writeCompressedInt(i); + writer.writeString(threadStates[i].getText()); + } - pmInfo.totalSize = com.oracle.svm.core.heap.PhysicalMemory.size().rawValue(); - // usedSize is not implemented at the moment. - pmInfo.commit(); + return NON_EMPTY; } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrTypeRepository.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrTypeRepository.java index 17ee27da460a..232d074892f5 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrTypeRepository.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrTypeRepository.java @@ -103,7 +103,7 @@ private void visitClassLoader(TypeInfo typeInfo, ClassLoader classLoader) { public int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo) { if (typeInfo.getClasses().isEmpty()) { - return 0; + return EMPTY; } writer.writeCompressedLong(JfrTypes.Class.getId()); writer.writeCompressedInt(typeInfo.getClasses().size()); @@ -111,7 +111,7 @@ public int writeClasses(JfrChunkWriter writer, TypeInfo typeInfo) { for (Class clazz : typeInfo.getClasses()) { writeClass(writer, typeInfo, clazz); } - return 1; + return NON_EMPTY; } private static void writeClass(JfrChunkWriter writer, TypeInfo typeInfo, Class clazz) { @@ -129,7 +129,7 @@ private static void writeClass(JfrChunkWriter writer, TypeInfo typeInfo, Class packages = typeInfo.getPackages(); if (packages.isEmpty()) { - return 0; + return EMPTY; } writer.writeCompressedLong(JfrTypes.Package.getId()); writer.writeCompressedInt(packages.size()); @@ -137,7 +137,7 @@ private static int writePackages(JfrChunkWriter writer, TypeInfo typeInfo) { for (Map.Entry pkgInfo : packages.entrySet()) { writePackage(writer, typeInfo, pkgInfo.getKey(), pkgInfo.getValue()); } - return 1; + return NON_EMPTY; } private static void writePackage(JfrChunkWriter writer, TypeInfo typeInfo, String pkgName, PackageInfo pkgInfo) { @@ -151,7 +151,7 @@ private static void writePackage(JfrChunkWriter writer, TypeInfo typeInfo, Strin private static int writeModules(JfrChunkWriter writer, TypeInfo typeInfo) { Map modules = typeInfo.getModules(); if (modules.isEmpty()) { - return 0; + return EMPTY; } writer.writeCompressedLong(JfrTypes.Module.getId()); writer.writeCompressedInt(modules.size()); @@ -159,7 +159,7 @@ private static int writeModules(JfrChunkWriter writer, TypeInfo typeInfo) { for (Map.Entry modInfo : modules.entrySet()) { writeModule(writer, typeInfo, modInfo.getKey(), modInfo.getValue()); } - return 1; + return NON_EMPTY; } private static void writeModule(JfrChunkWriter writer, TypeInfo typeInfo, Module module, long id) { @@ -174,7 +174,7 @@ private static void writeModule(JfrChunkWriter writer, TypeInfo typeInfo, Module private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo) { Map classLoaders = typeInfo.getClassLoaders(); if (classLoaders.isEmpty()) { - return 0; + return EMPTY; } writer.writeCompressedLong(JfrTypes.ClassLoader.getId()); writer.writeCompressedInt(classLoaders.size()); @@ -182,7 +182,7 @@ private static int writeClassLoaders(JfrChunkWriter writer, TypeInfo typeInfo) { for (Map.Entry clInfo : classLoaders.entrySet()) { writeClassLoader(writer, clInfo.getKey(), clInfo.getValue()); } - return 1; + return NON_EMPTY; } private static void writeClassLoader(JfrChunkWriter writer, ClassLoader cl, long id) { diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrTypes.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrTypes.java index 4461970b7181..f88ac328069e 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrTypes.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrTypes.java @@ -24,12 +24,14 @@ */ package com.oracle.svm.jfr; -import jdk.jfr.internal.Type; -import jdk.jfr.internal.TypeLibrary; +import org.graalvm.compiler.options.OptionsParser; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import java.util.List; +import com.oracle.svm.core.util.VMError; + +import jdk.jfr.internal.Type; +import jdk.jfr.internal.TypeLibrary; /** * Maps JFR types against their IDs in the JDK. @@ -37,6 +39,9 @@ public enum JfrTypes { Class("java.lang.Class"), String("java.lang.String"), + Thread("java.lang.Thread"), + ThreadState("jdk.types.ThreadState"), + ThreadGroup("jdk.types.ThreadGroup"), StackTrace("jdk.types.StackTrace"), ClassLoader("jdk.types.ClassLoader"), Method("jdk.types.Method"), @@ -55,14 +60,35 @@ public long getId() { return id; } + @Platforms(Platform.HOSTED_ONLY.class) + private static String getMostSimilarType(String missingTypeName) { + float threshold = OptionsParser.FUZZY_MATCH_THRESHOLD; + String mostSimilar = null; + for (Type type : TypeLibrary.getInstance().getTypes()) { + float similarity = OptionsParser.stringSimilarity(type.getName(), missingTypeName); + if (similarity > threshold) { + threshold = similarity; + mostSimilar = type.getName(); + } + } + return mostSimilar; + } + @Platforms(Platform.HOSTED_ONLY.class) private static long getTypeId(String typeName) { - List types = TypeLibrary.getInstance().getTypes(); - for (Type type : types) { + for (Type type : TypeLibrary.getInstance().getTypes()) { if (typeName.equals(type.getName())) { return type.getId(); } } - return 0; + + String exceptionMessage = "Type " + typeName + " is not found!"; + String mostSimilarType = getMostSimilarType(typeName); + if (mostSimilarType != null) { + exceptionMessage += " The most similar type is " + mostSimilarType; + } + exceptionMessage += " Take a look at 'metadata.xml' to see all available types."; + + throw VMError.shouldNotReachHere(exceptionMessage); } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/SubstrateJVM.java index 68abedd6ca90..94b731592f87 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/SubstrateJVM.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/SubstrateJVM.java @@ -25,9 +25,11 @@ package com.oracle.svm.jfr; //Checkstyle: allow reflection + import java.lang.reflect.Field; import java.util.List; +import com.oracle.svm.core.thread.ThreadListener; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.nativeimage.ImageSingletons; @@ -39,7 +41,6 @@ import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.thread.JavaVMOperation; -import com.oracle.svm.core.thread.ThreadListener; import com.oracle.svm.core.util.VMError; import com.oracle.svm.jfr.logging.JfrLogging; @@ -51,12 +52,15 @@ /** * Manager class that handles most JFR Java API, see {@link Target_jdk_jfr_internal_JVM}. */ -class SubstrateJVM { +public class SubstrateJVM { private final List knownConfigurations; private final JfrOptionSet options; private final JfrNativeEventSetting[] eventSettings; private final JfrSymbolRepository symbolRepo; private final JfrTypeRepository typeRepo; + private final JfrThreadRepository threadRepo; + private final JfrStackTraceRepository stackTraceRepo; + private final JfrMethodRepository methodRepo; private final JfrConstantPool[] repositories; private final JfrThreadLocal threadLocal; @@ -87,9 +91,15 @@ class SubstrateJVM { symbolRepo = new JfrSymbolRepository(); typeRepo = new JfrTypeRepository(); - // The ordering in the array dictates the order in which the constant pools will be written - // in the recording. - repositories = new JfrConstantPool[]{typeRepo, symbolRepo}; + threadRepo = new JfrThreadRepository(); + stackTraceRepo = new JfrStackTraceRepository(); + methodRepo = new JfrMethodRepository(); + /* + * The ordering in the array dictates the writing order of constant pools in the recording. + * Current rules: 1. methodRepo should be after stackTraceRepo; 2. typeRepo should be after + * methodRepo and stackTraceRepo; 3. symbolRepo should be on end. + */ + repositories = new JfrConstantPool[]{stackTraceRepo, methodRepo, typeRepo, threadRepo, symbolRepo}; threadLocal = new JfrThreadLocal(); globalMemory = new JfrGlobalMemory(); @@ -128,6 +138,11 @@ public static ThreadListener getThreadLocal() { return get().threadLocal; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long getParentThreadId(IsolateThread isolateThread) { + return get().threadLocal.getParentThreadId(isolateThread); + } + @Fold public static JfrTypeRepository getTypeRepository() { return get().typeRepo; @@ -138,6 +153,16 @@ public static JfrSymbolRepository getSymbolRepository() { return get().symbolRepo; } + @Fold + public static JfrThreadRepository getThreadRepo() { + return get().threadRepo; + } + + @Fold + public static JfrMethodRepository getMethodRepo() { + return get().methodRepo; + } + @Fold public static JfrLogging getJfrLogging() { return get().jfrLogging; @@ -206,15 +231,18 @@ public boolean destroyJFR() { globalMemory.teardown(); symbolRepo.teardown(); + threadRepo.teardown(); + stackTraceRepo.teardown(); + methodRepo.teardown(); initialized = false; return true; } /** See {@link JVM#getStackTraceId}. */ - public long getStackTraceId(@SuppressWarnings("unused") int skipCount) { - // Stack traces are not supported at the moment. - return 0; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getStackTraceId(int skipCount) { + return stackTraceRepo.getStackTraceId(skipCount, false); } /** See {@link JVM#getThreadId}. */ @@ -252,9 +280,7 @@ public void beginRecording() { /** See {@link JVM#endRecording}. */ public void endRecording() { assert recording; - JavaVMOperation.enqueueBlockingSafepoint("JFR end recording", () -> { - recording = false; - }); + JavaVMOperation.enqueueBlockingSafepoint("JFR end recording", () -> recording = false); // After the safepoint, it is guaranteed that all JfrNativeEventWriters finished their job // and that no further JFR events will be triggered. } @@ -325,8 +351,9 @@ public void setMethodSamplingInterval(@SuppressWarnings("unused") long type, @Su } /** See {@link JVM#setSampleThreads}. */ - public void setSampleThreads(@SuppressWarnings("unused") boolean sampleThreads) { - throw new IllegalStateException("JFR Thread sampling is currently not supported."); + public void setSampleThreads(boolean sampleThreads) { + setEnabled(JfrEvents.ExecutionSample.getId(), sampleThreads); + setEnabled(JfrEvents.NativeMethodSample.getId(), sampleThreads); } /** See {@link JVM#setCompressedIntegers}. */ @@ -337,13 +364,19 @@ public void setCompressedIntegers(boolean compressed) { } /** See {@link JVM#setStackDepth}. */ - public void setStackDepth(@SuppressWarnings("unused") int depth) { - throw new IllegalStateException("JFR stack traces are not supported"); + public void setStackDepth(int depth) { + stackTraceRepo.setStackTraceDepth(depth); } /** See {@link JVM#setStackTraceEnabled}. */ - public void setStackTraceEnabled(@SuppressWarnings("unused") long eventTypeId, @SuppressWarnings("unused") boolean enabled) { - // Not supported but this method is called during JFR startup, so we can't throw an error. + public void setStackTraceEnabled(long eventTypeId, boolean enabled) { + eventSettings[NumUtil.safeToInt(eventTypeId)].setStackTrace(enabled); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isStackTraceEnabled(long eventTypeId) { + assert (int) eventTypeId == eventTypeId; + return eventSettings[(int) eventTypeId].hasStackTrace(); } /** See {@link JVM#setThreadBufferSize}. */ diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/EndChunkPeriodEvents.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/EndChunkPeriodEvents.java new file mode 100644 index 000000000000..e0e2121e3ce9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/EndChunkPeriodEvents.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jfr.events; + +import com.oracle.svm.core.jdk.UninterruptibleUtils; +import org.graalvm.nativeimage.StackValue; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.jfr.JfrEvents; +import com.oracle.svm.jfr.JfrNativeEventWriter; +import com.oracle.svm.jfr.JfrNativeEventWriterData; +import com.oracle.svm.jfr.JfrNativeEventWriterDataAccess; +import com.oracle.svm.jfr.JfrTicks; +import com.oracle.svm.jfr.SubstrateJVM; + +import jdk.jfr.Event; +import jdk.jfr.Name; +import jdk.jfr.Period; + +@Name("EndChunkPeriodEvents") +@Period(value = "endChunk") +public class EndChunkPeriodEvents extends Event { + + private static String formatOSInformation() { + String name = System.getProperty("os.name"); + String ver = System.getProperty("os.version"); + String arch = System.getProperty("os.arch"); + return (name + " (" + ver + ") arch:" + arch); + } + + public static void emitEndChunkPeriodEvents() { + emitClassLoadingStatistics(Heap.getHeap().getClassCount(), 0); + emitJVMInformation(JVMInformation.getJVMInfo()); + emitOSInformation(formatOSInformation()); + + for (UninterruptibleUtils.ImmutablePair environmentVariable : InitialEnvironmentVariable.getEnvironmentVariables()) { + emitInitialEnvironmentVariables(environmentVariable); + } + + for (UninterruptibleUtils.ImmutablePair systemProperty : InitialSystemProperty.getSystemProperties()) { + emitInitialSystemProperty(systemProperty); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitInitialEnvironmentVariables(UninterruptibleUtils.ImmutablePair initialEnvironmentVariable) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.InitialEnvironmentVariable)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.InitialEnvironmentVariable.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putString(data, initialEnvironmentVariable.getKey()); + JfrNativeEventWriter.putString(data, initialEnvironmentVariable.getValue()); + JfrNativeEventWriter.endEventWrite(data, false); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitInitialSystemProperty(UninterruptibleUtils.ImmutablePair initialSystemProperty) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.InitialSystemProperty)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.InitialSystemProperty.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putString(data, initialSystemProperty.getKey()); + JfrNativeEventWriter.putString(data, initialSystemProperty.getValue()); + JfrNativeEventWriter.endEventWrite(data, false); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitClassLoadingStatistics(long loadedClassCount, long unloadedClassCount) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.ClassLoadingStatistics)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.ClassLoadingStatistics.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putLong(data, loadedClassCount); + JfrNativeEventWriter.putLong(data, unloadedClassCount); + JfrNativeEventWriter.endEventWrite(data, false); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitJVMInformation(JVMInformation jvmInformation) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.JVMInformation)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.JVMInformation.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putString(data, jvmInformation.getJvmName()); + JfrNativeEventWriter.putString(data, jvmInformation.getJvmVersion()); + JfrNativeEventWriter.putString(data, jvmInformation.getJvmArguments()); + JfrNativeEventWriter.putString(data, jvmInformation.getJvmFlags()); + JfrNativeEventWriter.putString(data, jvmInformation.getJavaArguments()); + JfrNativeEventWriter.putLong(data, jvmInformation.getJvmStartTime()); + JfrNativeEventWriter.putLong(data, jvmInformation.getJvmPid()); + JfrNativeEventWriter.endEventWrite(data, false); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitOSInformation(String osVersion) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.OSInformation)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.OSInformation.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putString(data, osVersion); + JfrNativeEventWriter.endEventWrite(data, false); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/EveryChunkPeriodEvents.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/EveryChunkPeriodEvents.java new file mode 100644 index 000000000000..4e42a3717d0c --- /dev/null +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/EveryChunkPeriodEvents.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jfr.events; + +import org.graalvm.nativeimage.StackValue; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.jfr.JfrEvents; +import com.oracle.svm.jfr.JfrNativeEventWriter; +import com.oracle.svm.jfr.JfrNativeEventWriterData; +import com.oracle.svm.jfr.JfrNativeEventWriterDataAccess; +import com.oracle.svm.jfr.JfrTicks; +import com.oracle.svm.jfr.SubstrateJVM; + +import jdk.jfr.Event; +import jdk.jfr.Name; +import jdk.jfr.Period; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; + +@Name("EveryChunkPeriodEvents") +@Period(value = "everyChunk") +public class EveryChunkPeriodEvents extends Event { + + public static void emitEveryChunkPeriodEvents() { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + emitJavaThreadStats(threadMXBean.getThreadCount(), threadMXBean.getDaemonThreadCount(), + threadMXBean.getTotalStartedThreadCount(), threadMXBean.getPeakThreadCount()); + + emitPhysicalMemory(com.oracle.svm.core.heap.PhysicalMemory.size().rawValue(), 0); + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitJavaThreadStats(long activeCount, long daemonCount, long accumulatedCount, long peakCount) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.JavaThreadStatistics)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.JavaThreadStatistics.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putLong(data, activeCount); + JfrNativeEventWriter.putLong(data, daemonCount); + JfrNativeEventWriter.putLong(data, accumulatedCount); + JfrNativeEventWriter.putLong(data, peakCount); + JfrNativeEventWriter.endEventWrite(data, false); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + private static void emitPhysicalMemory(long totalSize, long usedSize) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.PhysicalMemory)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.PhysicalMemory.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putLong(data, totalSize); + JfrNativeEventWriter.putLong(data, usedSize); + JfrNativeEventWriter.endEventWrite(data, false); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/InitialEnvironmentVariable.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/InitialEnvironmentVariable.java index f4e1c7c46eb6..953c40cc0796 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/InitialEnvironmentVariable.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/InitialEnvironmentVariable.java @@ -24,34 +24,21 @@ */ package com.oracle.svm.jfr.events; +import java.util.ArrayList; import java.util.Map; -import jdk.jfr.Category; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.Period; -import jdk.jfr.StackTrace; -import jdk.jfr.internal.Type; -@Label("Initial Environment Variable") -@Category("Operating System") -@StackTrace(false) -@Name(Type.EVENT_NAME_PREFIX + "InitialEnvironmentVariable") -@Period(value = "endChunk") -public class InitialEnvironmentVariable extends Event { - @Label("Key") public String key; +import com.oracle.svm.core.jdk.UninterruptibleUtils; - @Label("Value") public String value; +public class InitialEnvironmentVariable { - public static void emitEnvironmentVariables() { + public static ArrayList> getEnvironmentVariables() { + ArrayList> result = new ArrayList<>(); Map env = System.getenv(); for (Map.Entry entry : env.entrySet()) { - InitialEnvironmentVariable ee = new InitialEnvironmentVariable(); - - ee.key = entry.getKey(); - ee.value = entry.getValue(); - ee.commit(); + result.add(new UninterruptibleUtils.ImmutablePair<>(entry.getKey(), entry.getValue())); } + + return result; } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/InitialSystemProperty.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/InitialSystemProperty.java index d374b07ffad3..e8c0f80d55e5 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/InitialSystemProperty.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/InitialSystemProperty.java @@ -24,36 +24,21 @@ */ package com.oracle.svm.jfr.events; +import java.util.ArrayList; import java.util.Properties; -import jdk.jfr.Category; -import jdk.jfr.Description; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.Period; -import jdk.jfr.StackTrace; -import jdk.jfr.internal.Type; -@Label("Initial System Property") -@Description("System Property at JVM start") -@Category("Java Virtual Machine") -@StackTrace(false) -@Name(Type.EVENT_NAME_PREFIX + "InitialSystemProperty") -@Period(value = "endChunk") -public class InitialSystemProperty extends Event { - @Label("Key") public String key; +import com.oracle.svm.core.jdk.UninterruptibleUtils; - @Label("Value") public String value; +public class InitialSystemProperty { - public static void emitSystemProperties() { - Properties props = System.getProperties(); + public static ArrayList> getSystemProperties() { + ArrayList> result = new ArrayList<>(); + Properties properties = System.getProperties(); - for (String key : props.stringPropertyNames()) { - InitialSystemProperty pp = new InitialSystemProperty(); - - pp.key = key; - pp.value = props.getProperty(key); - pp.commit(); + for (String key : properties.stringPropertyNames()) { + result.add(new UninterruptibleUtils.ImmutablePair<>(key, properties.getProperty(key))); } + + return result; } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/JVMInformation.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/JVMInformation.java index 96b7988dff41..a5027c5b10ad 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/JVMInformation.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/JVMInformation.java @@ -24,53 +24,80 @@ */ package com.oracle.svm.jfr.events; -import com.oracle.svm.core.JavaMainWrapper; import java.lang.management.ManagementFactory; -import jdk.jfr.Category; -import jdk.jfr.Description; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.Period; -import jdk.jfr.StackTrace; -import jdk.jfr.Timestamp; -import jdk.jfr.internal.Type; + import org.graalvm.nativeimage.ImageSingletons; -@Label("JVM Information") -@Description("Description of JVM and the Java application") -@Category("Java Virtual Machine") -@StackTrace(false) -@Name(Type.EVENT_NAME_PREFIX + "JVMInformation") -@Period(value = "endChunk") -public class JVMInformation extends Event { +import com.oracle.svm.core.JavaMainWrapper; +import com.oracle.svm.core.annotate.Uninterruptible; + +public class JVMInformation { + + private String jvmName; + + private String jvmVersion; + + private String jvmArguments; + + private String jvmFlags; + + private String javaArguments; + + private long jvmStartTime; + + private long jvmPid; - @Label("JVM Name") String jvmName; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public String getJvmName() { + return jvmName; + } - @Label("JVM Version") String jvmVersion; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public String getJvmVersion() { + return jvmVersion; + } - @Label("JVM Command Line Arguments") String jvmArguments; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public String getJvmArguments() { + return jvmArguments; + } - @Label("JVM Settings File Arguments") String jvmFlags; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public String getJvmFlags() { + return jvmFlags; + } - @Label("Java Application Arguments") String javaArguments; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public String getJavaArguments() { + return javaArguments; + } - @Label("JVM Start Time") @Timestamp long jvmStartTime; + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getJvmStartTime() { + return jvmStartTime; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long getJvmPid() { + return jvmPid; + } + + public static JVMInformation getJVMInfo() { + JVMInformation jvmInfo = new JVMInformation(); - public static void emitJVMInformation() { if (ImageSingletons.contains(JavaMainWrapper.JavaMainSupport.class)) { JavaMainWrapper.JavaMainSupport support = ImageSingletons.lookup(JavaMainWrapper.JavaMainSupport.class); - JVMInformation jvmInfo = new JVMInformation(); - jvmInfo.jvmName = System.getProperty("java.vm.name"); jvmInfo.jvmVersion = System.getProperty("java.vm.version"); jvmInfo.jvmArguments = getVmArgs(support); jvmInfo.jvmFlags = ""; jvmInfo.javaArguments = support.getJavaCommand(); jvmInfo.jvmStartTime = ManagementFactory.getRuntimeMXBean().getStartTime(); - jvmInfo.commit(); + jvmInfo.jvmPid = ManagementFactory.getRuntimeMXBean().getPid(); } + + return jvmInfo; } private static String getVmArgs(JavaMainWrapper.JavaMainSupport support) { diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/JavaThreadStatistics.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/JavaThreadStatistics.java deleted file mode 100644 index 75c63216c534..000000000000 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/JavaThreadStatistics.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.jfr.events; - -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; -import jdk.jfr.Category; -import jdk.jfr.Description; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.Period; -import jdk.jfr.StackTrace; -import jdk.jfr.internal.Type; - -@Label("Java Thread Statistics") -@Category("Java Application, Statistics") -@StackTrace(false) -@Name(Type.EVENT_NAME_PREFIX + "JavaThreadStatistics") -@Period(value = "everyChunk") -public class JavaThreadStatistics extends Event { - - @Label("Active Threads") @Description("Number of live active threads including both daemon and non-daemon threads") long activeCount; - - @Label("Daemon Threads") @Description("Number of live daemon threads") long daemonCount; - - @Label("Accumulated Threads") @Description("Number of threads created and also started since JVM start") long accumulatedCount; - - @Label("Peak Threads") @Description("Peak live thread count since JVM start or when peak count was reset") long peakCount; - - public static void emitJavaThreadStats() { - JavaThreadStatistics threadStats = new JavaThreadStatistics(); - ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); - - threadStats.activeCount = threadMXBean.getThreadCount(); - threadStats.daemonCount = threadMXBean.getDaemonThreadCount(); - threadStats.accumulatedCount = threadMXBean.getTotalStartedThreadCount(); - threadStats.peakCount = threadMXBean.getPeakThreadCount(); - threadStats.commit(); - } -} diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ThreadEndEvent.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ThreadEndEvent.java new file mode 100644 index 000000000000..ac94a2523fe0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ThreadEndEvent.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jfr.events; + +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.StackValue; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.jfr.JfrEvents; +import com.oracle.svm.jfr.JfrNativeEventWriter; +import com.oracle.svm.jfr.JfrNativeEventWriterData; +import com.oracle.svm.jfr.JfrNativeEventWriterDataAccess; +import com.oracle.svm.jfr.JfrTicks; +import com.oracle.svm.jfr.SubstrateJVM; + +public class ThreadEndEvent { + + @Uninterruptible(reason = "Accesses a JFR buffer.") + public static void emit(IsolateThread isolateThread) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvents.ThreadEnd)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.ThreadEnd.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putEventThread(data); + JfrNativeEventWriter.putThread(data, isolateThread); + JfrNativeEventWriter.endEventWrite(data, false); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ThreadStartEvent.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ThreadStartEvent.java new file mode 100644 index 000000000000..3750837cc6ea --- /dev/null +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/events/ThreadStartEvent.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.jfr.events; + +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.StackValue; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.jfr.JfrEvents; +import com.oracle.svm.jfr.JfrNativeEventWriter; +import com.oracle.svm.jfr.JfrNativeEventWriterData; +import com.oracle.svm.jfr.JfrNativeEventWriterDataAccess; +import com.oracle.svm.jfr.JfrTicks; +import com.oracle.svm.jfr.SubstrateJVM; + +public class ThreadStartEvent { + + @Uninterruptible(reason = "Accesses a JFR buffer.") + public static void emit(IsolateThread isolateThread) { + SubstrateJVM svm = SubstrateJVM.get(); + if (SubstrateJVM.isRecording() && svm.isEnabled(JfrEvents.ThreadStart)) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeNativeBuffer(data); + + JfrNativeEventWriter.beginEventWrite(data, false); + JfrNativeEventWriter.putLong(data, JfrEvents.ThreadStart.getId()); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks()); + JfrNativeEventWriter.putEventThread(data); + if (svm.isStackTraceEnabled(JfrEvents.ThreadStart.getId())) { + JfrNativeEventWriter.putLong(data, svm.getStackTraceId(0)); + } else { + JfrNativeEventWriter.putLong(data, 0L); + } + JfrNativeEventWriter.putThread(data, isolateThread); + JfrNativeEventWriter.putLong(data, SubstrateJVM.getParentThreadId(isolateThread)); + JfrNativeEventWriter.endEventWrite(data, false); + } + } +}