feat(jdk8): move files to new folder to avoid resources compiled.

This commit is contained in:
2025-09-07 15:25:52 +08:00
parent 3f0047bf6f
commit 8c35cfb1c0
17415 changed files with 217 additions and 213 deletions

View File

@@ -0,0 +1,162 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.List;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.Method;
import jdk.internal.org.objectweb.asm.util.TraceClassVisitor;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.EventInstrumentation.FieldInfo;
final class ASMToolkit {
private static Type TYPE_STRING = Type.getType(String.class);
private static Type Type_THREAD = Type.getType(Thread.class);
private static Type TYPE_CLASS = Type.getType(Class.class);
public static void invokeSpecial(MethodVisitor methodVisitor, String className, Method m) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, className, m.getName(), m.getDescriptor(), false);
}
public static void invokeStatic(MethodVisitor methodVisitor, String className, Method m) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, className, m.getName(), m.getDescriptor(), false);
}
public static void invokeVirtual(MethodVisitor methodVisitor, String className, Method m) {
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, m.getName(), m.getDescriptor(), false);
}
public static Type toType(ValueDescriptor v) {
String typeName = v.getTypeName();
switch (typeName) {
case "byte":
return Type.BYTE_TYPE;
case "short":
return Type.SHORT_TYPE;
case "int":
return Type.INT_TYPE;
case "long":
return Type.LONG_TYPE;
case "double":
return Type.DOUBLE_TYPE;
case "float":
return Type.FLOAT_TYPE;
case "char":
return Type.CHAR_TYPE;
case "boolean":
return Type.BOOLEAN_TYPE;
case "java.lang.String":
return TYPE_STRING;
case "java.lang.Thread":
return Type_THREAD;
case "java.lang.Class":
return TYPE_CLASS;
}
// Add support for SettingControl?
throw new Error("Not a valid type " + v.getTypeName());
}
/**
* Converts "int" into "I" and "java.lang.String" into "Ljava/lang/String;"
*
* @param typeName
* type
*
* @return descriptor
*/
public static String getDescriptor(String typeName) {
if ("int".equals(typeName)) {
return "I";
}
if ("long".equals(typeName)) {
return "J";
}
if ("boolean".equals(typeName)) {
return "Z";
}
if ("float".equals(typeName)) {
return "F";
}
if ("double".equals(typeName)) {
return "D";
}
if ("short".equals(typeName)) {
return "S";
}
if ("char".equals(typeName)) {
return "C";
}
if ("byte".equals(typeName)) {
return "B";
}
String internal = getInternalName(typeName);
return Type.getObjectType(internal).getDescriptor();
}
/**
* Converts java.lang.String into java/lang/String
*
* @param className
*
* @return internal name
*/
public static String getInternalName(String className) {
return className.replace(".", "/");
}
public static Method makeWriteMethod(List<FieldInfo> fields) {
StringBuilder sb = new StringBuilder();
sb.append("(");
for (FieldInfo v : fields) {
if (!v.fieldName.equals(EventInstrumentation.FIELD_EVENT_THREAD) && !v.fieldName.equals(EventInstrumentation.FIELD_STACK_TRACE)) {
sb.append(v.fieldDescriptor);
}
}
sb.append(")V");
return new Method("write", sb.toString());
}
public static void logASM(String className, byte[] bytes) {
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Generated bytecode for class " + className);
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE, () -> {
ClassReader cr = new ClassReader(bytes);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter w = new PrintWriter(baos);
w.println("Bytecode:");
cr.accept(new TraceClassVisitor(w), 0);
return baos.toString();
});
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.List;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.Unsigned;
public final class AnnotationConstruct {
private static final class AnnotationInvokationHandler implements InvocationHandler {
private final AnnotationElement annotationElement;
AnnotationInvokationHandler(AnnotationElement a) {
this.annotationElement = a;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
int parameters = method.getTypeParameters().length;
if (parameters == 0 && annotationElement.hasValue(methodName)) {
return annotationElement.getValue(methodName);
}
throw new UnsupportedOperationException("Flight Recorder proxy only supports members declared in annotation interfaces, i.e. not toString, equals etc.");
}
}
private List<AnnotationElement> annotationElements = Collections.emptyList();
private byte unsignedFlag = -1;
public AnnotationConstruct(List<AnnotationElement> ann) {
this.annotationElements = ann;
}
public AnnotationConstruct() {
}
public void setAnnotationElements(List<AnnotationElement> elements) {
annotationElements = Utils.smallUnmodifiable(elements);
}
public String getLabel() {
Label label = getAnnotation(Label.class);
if (label == null) {
return null;
}
return label.value();
}
public String getDescription() {
Description description = getAnnotation(Description.class);
if (description == null) {
return null;
}
return description.value();
}
@SuppressWarnings("unchecked")
public final <T> T getAnnotation(Class<? extends Annotation> clazz) {
AnnotationElement ae = getAnnotationElement(clazz);
if (ae != null) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz }, new AnnotationInvokationHandler(ae));
}
return null;
}
public List<AnnotationElement> getUnmodifiableAnnotationElements() {
return annotationElements;
}
// package private
boolean remove(AnnotationElement annotation) {
return annotationElements.remove(annotation);
}
private AnnotationElement getAnnotationElement(Class<? extends Annotation> clazz) {
// if multiple annotation elements with the same name exists, prioritize
// the one with the same id. Note, id alone is not a guarantee, since it
// may differ between JVM instances.
long id = Type.getTypeId(clazz);
String className = clazz.getName();
for (AnnotationElement a : getUnmodifiableAnnotationElements()) {
if (a.getTypeId() == id && a.getTypeName().equals(className)) {
return a;
}
}
for (AnnotationElement a : getUnmodifiableAnnotationElements()) {
if (a.getTypeName().equals(className)) {
return a;
}
}
return null;
}
public boolean hasUnsigned() {
// Must be initialized lazily since some annotation elements
// are added after construction
if (unsignedFlag < 0) {
Unsigned unsigned = getAnnotation(Unsigned.class);
unsignedFlag = (byte) (unsigned == null ? 0 :1);
}
return unsignedFlag == (byte)1 ? true : false;
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.nio.ByteOrder;
import sun.misc.Unsafe;
final class Bits { // package-private
private static final Unsafe unsafe = Unsafe.getUnsafe();
// XXX TODO proper value (e.g. copy from java.nio.Bits)
private static final boolean unalignedAccess = false/*unsafe.unalignedAccess()*/;
private static final boolean bigEndian = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
private Bits() { }
// -- Swapping --
private static short swap(short x) {
return Short.reverseBytes(x);
}
private static char swap(char x) {
return Character.reverseBytes(x);
}
private static int swap(int x) {
return Integer.reverseBytes(x);
}
private static long swap(long x) {
return Long.reverseBytes(x);
}
private static float swap(float x) {
return Float.intBitsToFloat(swap(Float.floatToIntBits(x)));
}
private static double swap(double x) {
return Double.longBitsToDouble(swap(Double.doubleToLongBits(x)));
}
// -- Alignment --
private static boolean isAddressAligned(long a, int datumSize) {
return (a & datumSize - 1) == 0;
}
// -- Primitives stored per byte
private static byte char1(char x) { return (byte)(x >> 8); }
private static byte char0(char x) { return (byte)(x ); }
private static byte short1(short x) { return (byte)(x >> 8); }
private static byte short0(short x) { return (byte)(x ); }
private static byte int3(int x) { return (byte)(x >> 24); }
private static byte int2(int x) { return (byte)(x >> 16); }
private static byte int1(int x) { return (byte)(x >> 8); }
private static byte int0(int x) { return (byte)(x ); }
private static byte long7(long x) { return (byte)(x >> 56); }
private static byte long6(long x) { return (byte)(x >> 48); }
private static byte long5(long x) { return (byte)(x >> 40); }
private static byte long4(long x) { return (byte)(x >> 32); }
private static byte long3(long x) { return (byte)(x >> 24); }
private static byte long2(long x) { return (byte)(x >> 16); }
private static byte long1(long x) { return (byte)(x >> 8); }
private static byte long0(long x) { return (byte)(x ); }
private static void putCharBigEndianUnaligned(long a, char x) {
putByte_(a , char1(x));
putByte_(a + 1, char0(x));
}
private static void putShortBigEndianUnaligned(long a, short x) {
putByte_(a , short1(x));
putByte_(a + 1, short0(x));
}
private static void putIntBigEndianUnaligned(long a, int x) {
putByte_(a , int3(x));
putByte_(a + 1, int2(x));
putByte_(a + 2, int1(x));
putByte_(a + 3, int0(x));
}
private static void putLongBigEndianUnaligned(long a, long x) {
putByte_(a , long7(x));
putByte_(a + 1, long6(x));
putByte_(a + 2, long5(x));
putByte_(a + 3, long4(x));
putByte_(a + 4, long3(x));
putByte_(a + 5, long2(x));
putByte_(a + 6, long1(x));
putByte_(a + 7, long0(x));
}
private static void putFloatBigEndianUnaligned(long a, float x) {
putIntBigEndianUnaligned(a, Float.floatToRawIntBits(x));
}
private static void putDoubleBigEndianUnaligned(long a, double x) {
putLongBigEndianUnaligned(a, Double.doubleToRawLongBits(x));
}
private static void putByte_(long a, byte b) {
unsafe.putByte(a, b);
}
private static void putBoolean_(long a, boolean x) {
unsafe.putBoolean(null, a, x);
}
private static void putChar_(long a, char x) {
unsafe.putChar(a, bigEndian ? x : swap(x));
}
private static void putShort_(long a, short x) {
unsafe.putShort(a, bigEndian ? x : swap(x));
}
private static void putInt_(long a, int x) {
unsafe.putInt(a, bigEndian ? x : swap(x));
}
private static void putLong_(long a, long x) {
unsafe.putLong(a, bigEndian ? x : swap(x));
}
private static void putFloat_(long a, float x) {
unsafe.putFloat(a, bigEndian ? x : swap(x));
}
private static void putDouble_(long a, double x) {
unsafe.putDouble(a, bigEndian ? x : swap(x));
}
// external api
static int putByte(long a, byte x) {
putByte_(a, x);
return Byte.BYTES;
}
static int putBoolean(long a, boolean x) {
putBoolean_(a, x);
return Byte.BYTES;
}
static int putChar(long a, char x) {
if (unalignedAccess || isAddressAligned(a, Character.BYTES)) {
putChar_(a, x);
return Character.BYTES;
}
putCharBigEndianUnaligned(a, x);
return Character.BYTES;
}
static int putShort(long a, short x) {
if (unalignedAccess || isAddressAligned(a, Short.BYTES)) {
putShort_(a, x);
return Short.BYTES;
}
putShortBigEndianUnaligned(a, x);
return Short.BYTES;
}
static int putInt(long a, int x) {
if (unalignedAccess || isAddressAligned(a, Integer.BYTES)) {
putInt_(a, x);
return Integer.BYTES;
}
putIntBigEndianUnaligned(a, x);
return Integer.BYTES;
}
static int putLong(long a, long x) {
if (unalignedAccess || isAddressAligned(a, Long.BYTES)) {
putLong_(a, x);
return Long.BYTES;
}
putLongBigEndianUnaligned(a, x);
return Long.BYTES;
}
static int putFloat(long a, float x) {
if (unalignedAccess || isAddressAligned(a, Float.BYTES)) {
putFloat_(a, x);
return Float.BYTES;
}
putFloatBigEndianUnaligned(a, x);
return Float.BYTES;
}
static int putDouble(long a, double x) {
if (unalignedAccess || isAddressAligned(a, Double.BYTES)) {
putDouble_(a, x);
return Double.BYTES;
}
putDoubleBigEndianUnaligned(a, x);
return Double.BYTES;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 2001, 2018, 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 jdk.jfr.internal;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
final class ChunkInputStream extends InputStream {
private final Iterator<RepositoryChunk> chunks;
private RepositoryChunk currentChunk;
private InputStream stream;
ChunkInputStream(List<RepositoryChunk> chunks) throws IOException {
List<RepositoryChunk> l = new ArrayList<>(chunks.size());
for (RepositoryChunk c : chunks) {
c.use(); // keep alive while we're reading.
l.add(c);
}
this.chunks = l.iterator();
nextStream();
}
@Override
public int available() throws IOException {
if (stream != null) {
return stream.available();
}
return 0;
}
private boolean nextStream() throws IOException {
if (!nextChunk()) {
return false;
}
stream = new BufferedInputStream(SecuritySupport.newFileInputStream(currentChunk.getFile()));
return true;
}
private boolean nextChunk() {
if (!chunks.hasNext()) {
return false;
}
currentChunk = chunks.next();
return true;
}
@Override
public int read() throws IOException {
while (true) {
if (stream != null) {
int r = stream.read();
if (r != -1) {
return r;
}
stream.close();
currentChunk.release();
stream = null;
currentChunk = null;
}
if (!nextStream()) {
return -1;
}
}
}
@Override
public void close() throws IOException {
if (stream != null) {
stream.close();
stream = null;
}
while (currentChunk != null) {
currentChunk.release();
currentChunk = null;
if (!nextChunk()) {
return;
}
}
}
@Override
@SuppressWarnings("deprecation")
protected void finalize() throws Throwable {
super.finalize();
close();
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
final class ChunksChannel implements ReadableByteChannel {
private final Iterator<RepositoryChunk> chunks;
private RepositoryChunk current;
private ReadableByteChannel channel;
public ChunksChannel(List<RepositoryChunk> chunks) throws IOException {
if (chunks.isEmpty()) {
throw new FileNotFoundException("No chunks");
}
List<RepositoryChunk> l = new ArrayList<>(chunks.size());
for (RepositoryChunk c : chunks) {
c.use(); // keep alive while we're reading.
l.add(c);
}
this.chunks = l.iterator();
nextChannel();
}
private boolean nextChunk() {
if (!chunks.hasNext()) {
return false;
}
current = chunks.next();
return true;
}
private boolean nextChannel() throws IOException {
if (!nextChunk()) {
return false;
}
channel = current.newChannel();
return true;
}
@Override
public int read(ByteBuffer dst) throws IOException {
for (;;) {
if (channel != null) {
assert current != null;
int r = channel.read(dst);
if (r != -1) {
return r;
}
channel.close();
current.release();
channel = null;
current = null;
}
if (!nextChannel()) {
return -1;
}
}
}
public long transferTo(FileChannel out) throws IOException {
long pos = 0;
for (;;) {
if (channel != null) {
assert current != null;
long rem = current.getSize();
while (rem > 0) {
long n = Math.min(rem, 1024 * 1024);
long w = out.transferFrom(channel, pos, n);
// Prevent endless loop
if (w == 0) {
return out.size();
}
pos += w;
rem -= w;
}
channel.close();
current.release();
channel = null;
current = null;
}
if (!nextChannel()) {
return out.size();
}
}
}
@Override
public void close() throws IOException {
if (channel != null) {
channel.close();
channel = null;
}
while (current != null) {
current.release();
current = null;
if (!nextChunk()) {
return;
}
}
}
@Override
public boolean isOpen() {
return channel != null;
}
@Override
@SuppressWarnings("deprecation")
protected void finalize() throws Throwable {
super.finalize();
close();
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
// User must never be able to subclass directly.
//
// Never put Control or Setting Control in a collections
// so overridable versions of hashCode or equals are
// executed in the wrong context. TODO: wrap this class
// in SsecureControl directly when it is instantiated and
// forward calls using AccessControlContext
abstract public class Control {
private final AccessControlContext context;
private final static int CACHE_SIZE = 5;
private final Set<?>[] cachedUnions = new HashSet<?>[CACHE_SIZE];
private final String[] cachedValues = new String[CACHE_SIZE];
private String defaultValue;
private String lastValue;
// called by exposed subclass in external API
public Control(AccessControlContext acc) {
Objects.requireNonNull(acc);
this.context = acc;
}
// only to be called by trusted VM code
public Control(String defaultValue) {
this.defaultValue = defaultValue;
this.context = null;
}
// For user code to override, must never be called from jdk.jfr.internal
// for user defined settings
public abstract String combine(Set<String> values);
// For user code to override, must never be called from jdk.jfr.internal
// for user defined settings
public abstract void setValue(String value);
// For user code to override, must never be called from jdk.jfr.internal
// for user defined settings
public abstract String getValue();
// Package private, user code should not have access to this method
final void apply(Set<String> values) {
setValueSafe(findCombineSafe(values));
}
// Package private, user code should not have access to this method.
// Only called during event registration
final void setDefault() {
if (defaultValue == null) {
defaultValue = getValueSafe();
}
apply(defaultValue);
}
final String getValueSafe() {
if (context == null) {
// VM events requires no access control context
return getValue();
} else {
return AccessController.doPrivileged(new PrivilegedAction<String>() {
@Override
public String run() {
try {
return getValue();
} catch (Throwable t) {
// Prevent malicious user to propagate exception callback in the wrong context
Logger.log(LogTag.JFR_SETTING, LogLevel.WARN, "Exception occured when trying to get value for " + getClass());
}
return defaultValue != null ? defaultValue : ""; // Need to return something
}
}, context);
}
}
private void apply(String value) {
if (lastValue != null && Objects.equals(value, lastValue)) {
return;
}
setValueSafe(value);
}
final void setValueSafe(String value) {
if (context == null) {
// VM events requires no access control context
try {
setValue(value);
} catch (Throwable t) {
Logger.log(LogTag.JFR_SETTING, LogLevel.WARN, "Exception occured when setting value \"" + value + "\" for " + getClass());
}
} else {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
try {
setValue(value);
} catch (Throwable t) {
// Prevent malicious user to propagate exception callback in the wrong context
Logger.log(LogTag.JFR_SETTING, LogLevel.WARN, "Exception occured when setting value \"" + value + "\" for " + getClass());
}
return null;
}
}, context);
}
lastValue = value;
}
private String combineSafe(Set<String> values) {
if (context == null) {
// VM events requires no access control context
return combine(values);
}
return AccessController.doPrivileged(new PrivilegedAction<String>() {
@Override
public String run() {
try {
combine(Collections.unmodifiableSet(values));
} catch (Throwable t) {
// Prevent malicious user to propagate exception callback in the wrong context
Logger.log(LogTag.JFR_SETTING, LogLevel.WARN, "Exception occured when combining " + values + " for " + getClass());
}
return null;
}
}, context);
}
private final String findCombineSafe(Set<String> values) {
if (values.size() == 1) {
return values.iterator().next();
}
for (int i = 0; i < CACHE_SIZE; i++) {
if (Objects.equals(cachedUnions[i], values)) {
return cachedValues[i];
}
}
String result = combineSafe(values);
for (int i = 0; i < CACHE_SIZE - 1; i++) {
cachedUnions[i + 1] = cachedUnions[i];
cachedValues[i + 1] = cachedValues[i];
}
cachedValues[0] = result;
cachedUnions[0] = values;
return result;
}
// package private, user code should not have access to this method
final String getDefaultValue() {
return defaultValue;
}
// package private, user code should not have access to this method
final String getLastValue() {
return lastValue;
}
// Precaution to prevent a malicious user from instantiating instances
// of a control where the context has not been set up.
@Override
public final Object clone() throws java.lang.CloneNotSupportedException {
throw new CloneNotSupportedException();
}
private final void writeObject(ObjectOutputStream out) throws IOException {
throw new IOException("Object cannot be serialized");
}
private final void readObject(ObjectInputStream in) throws IOException {
throw new IOException("Class cannot be deserialized");
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2016, 2019, 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 jdk.jfr.internal;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jdk.jfr.MetadataDefinition;
/**
* Event annotation, determines the cutoff above which an event should not be
* recorded, i.e. {@code "20 ms"}.
*
* This settings is only supported for JVM events,
*
* @Since 8
*/
@MetadataDefinition
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Cutoff {
/**
* Settings name {@code "cutoff"} for configuring event cutoffs.
*/
public final static String NAME = "cutoff";
public final static String INIFITY = "infinity";
/**
* Cutoff, for example {@code "20 ms"}.
* <p>
* String representation of a positive {@code Long} value followed by an empty
* space and one of the following units<br>
* <br>
* {@code "ns"} (nanoseconds)<br>
* {@code "us"} (microseconds)<br>
* {@code "ms"} (milliseconds)<br>
* {@code "s"} (seconds)<br>
* {@code "m"} (minutes)<br>
* {@code "h"} (hours)<br>
* {@code "d"} (days)<br>
* <p>
* Example values, {@code "0 ns"}, {@code "10 ms"} and {@code "1 s"}. If the
* events has an infinite timespan, the text {@code"infinity"} should be used.
*
* @return the threshold, default {@code "0 ns"} not {@code null}
*/
String value() default "inifity";
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import jdk.internal.org.objectweb.asm.AnnotationVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.GeneratorAdapter;
import jdk.internal.org.objectweb.asm.commons.Method;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Event;
import jdk.jfr.ValueDescriptor;
// Helper class for building dynamic events
public final class EventClassBuilder {
private static final Type TYPE_EVENT = Type.getType(Event.class);
private static final Type TYPE_IOBE = Type.getType(IndexOutOfBoundsException.class);
private static final Method DEFAULT_CONSTRUCTOR = Method.getMethod("void <init> ()");
private static final Method SET_METHOD = Method.getMethod("void set (int, java.lang.Object)");
private static final AtomicLong idCounter = new AtomicLong();
private final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
private final String fullClassName;
private final Type type;
private final List<ValueDescriptor> fields;
private final List<AnnotationElement> annotationElements;
public EventClassBuilder(List<AnnotationElement> annotationElements, List<ValueDescriptor> fields) {
this.fullClassName = "jdk.jfr.DynamicEvent" + idCounter.incrementAndGet();
this.type = Type.getType(fullClassName.replace(".", "/"));
this.fields = fields;
this.annotationElements = annotationElements;
}
public Class<? extends Event> build() {
buildClassInfo();
buildConstructor();
buildFields();
buildSetMethod();
endClass();
byte[] bytes = classWriter.toByteArray();
ASMToolkit.logASM(fullClassName, bytes);
return SecuritySupport.defineClass(type.getInternalName(), bytes, Event.class.getClassLoader()).asSubclass(Event.class);
}
private void endClass() {
classWriter.visitEnd();
}
private void buildSetMethod() {
GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC, SET_METHOD, null, null, classWriter);
int index = 0;
for (ValueDescriptor v : fields) {
ga.loadArg(0);
ga.visitLdcInsn(index);
Label notEqual = new Label();
ga.ifICmp(GeneratorAdapter.NE, notEqual);
ga.loadThis();
ga.loadArg(1);
Type fieldType = ASMToolkit.toType(v);
ga.unbox(ASMToolkit.toType(v));
ga.putField(type, v.getName(), fieldType);
ga.visitInsn(Opcodes.RETURN);
ga.visitLabel(notEqual);
index++;
}
ga.throwException(TYPE_IOBE, "Index must between 0 and " + fields.size());
ga.endMethod();
}
private void buildConstructor() {
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, DEFAULT_CONSTRUCTOR.getName(), DEFAULT_CONSTRUCTOR.getDescriptor(), null, null);
mv.visitIntInsn(Opcodes.ALOAD, 0);
ASMToolkit.invokeSpecial(mv, TYPE_EVENT.getInternalName(), DEFAULT_CONSTRUCTOR);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
}
private void buildClassInfo() {
String internalSuperName = ASMToolkit.getInternalName(Event.class.getName());
String internalClassName = type.getInternalName();
classWriter.visit(52, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, internalClassName, null, internalSuperName, null);
for (AnnotationElement a : annotationElements) {
String descriptor = ASMToolkit.getDescriptor(a.getTypeName());
AnnotationVisitor av = classWriter.visitAnnotation(descriptor, true);
for (ValueDescriptor v : a.getValueDescriptors()) {
Object value = a.getValue(v.getName());
String name = v.getName();
if (v.isArray()) {
AnnotationVisitor arrayVisitor = av.visitArray(name);
Object[] array = (Object[]) value;
for (int i = 0; i < array.length; i++) {
arrayVisitor.visit(null, array[i]);
}
arrayVisitor.visitEnd();
} else {
av.visit(name, value);
}
}
av.visitEnd();
}
}
private void buildFields() {
for (ValueDescriptor v : fields) {
String internal = ASMToolkit.getDescriptor(v.getTypeName());
classWriter.visitField(Opcodes.ACC_PRIVATE, v.getName(), internal, null, null);
// No need to store annotations on field since they will be replaced anyway.
}
}
}

View File

@@ -0,0 +1,291 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Enabled;
import jdk.jfr.Event;
import jdk.jfr.Name;
import jdk.jfr.Period;
import jdk.jfr.SettingControl;
import jdk.jfr.SettingDefinition;
import jdk.jfr.StackTrace;
import jdk.jfr.Threshold;
import jdk.jfr.events.ActiveSettingEvent;
import jdk.jfr.internal.EventInstrumentation.SettingInfo;
import jdk.jfr.internal.settings.CutoffSetting;
import jdk.jfr.internal.settings.EnabledSetting;
import jdk.jfr.internal.settings.PeriodSetting;
import jdk.jfr.internal.settings.StackTraceSetting;
import jdk.jfr.internal.settings.ThresholdSetting;
// This class can't have a hard reference from PlatformEventType, since it
// holds SettingControl instances that need to be released
// when a class is unloaded (to avoid memory leaks).
public final class EventControl {
static final String FIELD_SETTING_PREFIX = "setting";
private static final Type TYPE_ENABLED = TypeLibrary.createType(EnabledSetting.class);
private static final Type TYPE_THRESHOLD = TypeLibrary.createType(ThresholdSetting.class);
private static final Type TYPE_STACK_TRACE = TypeLibrary.createType(StackTraceSetting.class);
private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class);
private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
private final List<SettingInfo> settingInfos = new ArrayList<>();
private final Map<String, Control> eventControls = new HashMap<>(5);
private final PlatformEventType type;
private final String idName;
EventControl(PlatformEventType eventType) {
eventControls.put(Enabled.NAME, defineEnabled(eventType));
if (eventType.hasDuration()) {
eventControls.put(Threshold.NAME, defineThreshold(eventType));
}
if (eventType.hasStackTrace()) {
eventControls.put(StackTrace.NAME, defineStackTrace(eventType));
}
if (eventType.hasPeriod()) {
eventControls.put(Period.NAME, definePeriod(eventType));
}
if (eventType.hasCutoff()) {
eventControls.put(Cutoff.NAME, defineCutoff(eventType));
}
ArrayList<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
remove(eventType, aes, Threshold.class);
remove(eventType, aes, Period.class);
remove(eventType, aes, Enabled.class);
remove(eventType, aes, StackTrace.class);
remove(eventType, aes, Cutoff.class);
aes.trimToSize();
eventType.setAnnotations(aes);
this.type = eventType;
this.idName = String.valueOf(eventType.getId());
}
static void remove(PlatformEventType type, List<AnnotationElement> aes, Class<? extends java.lang.annotation.Annotation> clazz) {
long id = Type.getTypeId(clazz);
for (AnnotationElement a : type.getAnnotationElements()) {
if (a.getTypeId() == id && a.getTypeName().equals(clazz.getName())) {
aes.remove(a);
}
}
}
EventControl(PlatformEventType es, Class<? extends Event> eventClass) {
this(es);
defineSettings(eventClass);
}
@SuppressWarnings("unchecked")
private void defineSettings(Class<?> eventClass) {
// Iterate up the class hierarchy and let
// subclasses shadow base classes.
boolean allowPrivateMethod = true;
while (eventClass != null) {
for (Method m : eventClass.getDeclaredMethods()) {
boolean isPrivate = Modifier.isPrivate(m.getModifiers());
if (m.getReturnType() == Boolean.TYPE && m.getParameterCount() == 1 && (!isPrivate || allowPrivateMethod)) {
SettingDefinition se = m.getDeclaredAnnotation(SettingDefinition.class);
if (se != null) {
Class<?> settingClass = m.getParameters()[0].getType();
if (!Modifier.isAbstract(settingClass.getModifiers()) && SettingControl.class.isAssignableFrom(settingClass)) {
String name = m.getName();
Name n = m.getAnnotation(Name.class);
if (n != null) {
name = n.value();
}
if (!eventControls.containsKey(name)) {
defineSetting((Class<? extends SettingControl>) settingClass, m, type, name);
}
}
}
}
}
eventClass = eventClass.getSuperclass();
allowPrivateMethod = false;
}
}
private void defineSetting(Class<? extends SettingControl> settingsClass, Method method, PlatformEventType eventType, String settingName) {
try {
int index = settingInfos.size();
SettingInfo si = new SettingInfo(FIELD_SETTING_PREFIX + index, index);
si.settingControl = instantiateSettingControl(settingsClass);
Control c = si.settingControl;
c.setDefault();
String defaultValue = c.getValueSafe();
if (defaultValue != null) {
Type settingType = TypeLibrary.createType(settingsClass);
ArrayList<AnnotationElement> aes = new ArrayList<>();
for (Annotation a : method.getDeclaredAnnotations()) {
AnnotationElement ae = TypeLibrary.createAnnotation(a);
if (ae != null) {
aes.add(ae);
}
}
aes.trimToSize();
eventControls.put(settingName, si.settingControl);
eventType.add(PrivateAccess.getInstance().newSettingDescriptor(settingType, settingName, defaultValue, aes));
settingInfos.add(si);
}
} catch (InstantiationException e) {
// Programming error by user, fail fast
throw new InstantiationError("Could not instantiate setting " + settingsClass.getName() + " for event " + eventType.getLogName() + ". " + e.getMessage());
} catch (IllegalAccessException e) {
// Programming error by user, fail fast
throw new IllegalAccessError("Could not access setting " + settingsClass.getName() + " for event " + eventType.getLogName() + ". " + e.getMessage());
}
}
private SettingControl instantiateSettingControl(Class<? extends SettingControl> settingControlClass) throws IllegalAccessException, InstantiationException {
SecuritySupport.makeVisibleToJFR(settingControlClass);
final Constructor<?> cc;
try {
cc = settingControlClass.getDeclaredConstructors()[0];
} catch (Exception e) {
throw (Error) new InternalError("Could not get constructor for " + settingControlClass.getName()).initCause(e);
}
SecuritySupport.setAccessible(cc);
try {
return (SettingControl) cc.newInstance();
} catch (IllegalArgumentException | InvocationTargetException e) {
throw (Error) new InternalError("Could not instantiate setting for class " + settingControlClass.getName());
}
}
private static Control defineEnabled(PlatformEventType type) {
Enabled enabled = type.getAnnotation(Enabled.class);
// Java events are enabled by default,
// JVM events are not, maybe they should be? Would lower learning curve
// there too.
String def = type.isJVM() ? "false" : "true";
if (enabled != null) {
def = Boolean.toString(enabled.value());
}
type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_ENABLED, Enabled.NAME, def, Collections.emptyList()));
return new EnabledSetting(type, def);
}
private static Control defineThreshold(PlatformEventType type) {
Threshold threshold = type.getAnnotation(Threshold.class);
String def = "0 ns";
if (threshold != null) {
def = threshold.value();
}
type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_THRESHOLD, Threshold.NAME, def, Collections.emptyList()));
return new ThresholdSetting(type, def);
}
private static Control defineStackTrace(PlatformEventType type) {
StackTrace stackTrace = type.getAnnotation(StackTrace.class);
String def = "true";
if (stackTrace != null) {
def = Boolean.toString(stackTrace.value());
}
type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_STACK_TRACE, StackTrace.NAME, def, Collections.emptyList()));
return new StackTraceSetting(type, def);
}
private static Control defineCutoff(PlatformEventType type) {
Cutoff cutoff = type.getAnnotation(Cutoff.class);
String def = Cutoff.INIFITY;
if (cutoff != null) {
def = cutoff.value();
}
type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_CUTOFF, Cutoff.NAME, def, Collections.emptyList()));
return new CutoffSetting(type, def);
}
private static Control definePeriod(PlatformEventType type) {
Period period = type.getAnnotation(Period.class);
String def = "everyChunk";
if (period != null) {
def = period.value();
}
type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_PERIOD, PeriodSetting.NAME, def, Collections.emptyList()));
return new PeriodSetting(type, def);
}
void disable() {
for (Control c : eventControls.values()) {
if (c instanceof EnabledSetting) {
c.setValueSafe("false");
return;
}
}
}
void writeActiveSettingEvent() {
if (!type.isRegistered()) {
return;
}
for (Map.Entry<String, Control> entry : eventControls.entrySet()) {
Control c = entry.getValue();
if (Utils.isSettingVisible(c, type.hasEventHook())) {
String value = c.getLastValue();
if (value == null) {
value = c.getDefaultValue();
}
ActiveSettingEvent ase = new ActiveSettingEvent();
ase.id = type.getId();
ase.name = entry.getKey();
ase.value = value;
ase.commit();
}
}
}
public Set<Entry<String, Control>> getEntries() {
return eventControls.entrySet();
}
public PlatformEventType getEventType() {
return type;
}
public String getSettingsId() {
return idName;
}
public List<SettingInfo> getSettingInfos() {
return settingInfos;
}
}

View File

@@ -0,0 +1,338 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.Method;
import jdk.jfr.Event;
import jdk.jfr.EventType;
import jdk.jfr.SettingControl;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.EventInstrumentation.FieldInfo;
import jdk.jfr.internal.EventInstrumentation.SettingInfo;
import jdk.jfr.internal.handlers.EventHandler;
final class EventHandlerCreator {
// TODO:
// How can we find out class version without loading a
// class as resource in a privileged block and use ASM to inspect
// the contents. Using '52' even though we know later versions
// are available. The reason for this is compatibility aspects
// with for example WLS.
private static final int CLASS_VERSION = 52;
// This is needed so a new EventHandler is automatically generated in MetadataRespoistory
// if a user Event class is loaded using APPCDS/CDS.
private static final String SUFFIX = "_" + System.currentTimeMillis() + "-" + JVM.getJVM().getPid();
private static final String FIELD_EVENT_TYPE = "platformEventType";
private static final String FIELD_PREFIX_STRING_POOL = "stringPool";
private final static Class<? extends EventHandler> eventHandlerProxy = EventHandlerProxyCreator.proxyClass;
private final static Type TYPE_STRING_POOL = Type.getType(StringPool.class);
private final static Type TYPE_EVENT_WRITER = Type.getType(EventWriter.class);
private final static Type TYPE_PLATFORM_EVENT_TYPE = Type.getType(PlatformEventType.class);
private final static Type TYPE_EVENT_HANDLER = Type.getType(eventHandlerProxy);
private final static Type TYPE_SETTING_CONTROL = Type.getType(SettingControl.class);
private final static Type TYPE_EVENT_TYPE = Type.getType(EventType.class);
private final static Type TYPE_EVENT_CONTROL = Type.getType(EventControl.class);
private final static String DESCRIPTOR_EVENT_HANDLER = "(" + Type.BOOLEAN_TYPE.getDescriptor() + TYPE_EVENT_TYPE.getDescriptor() + TYPE_EVENT_CONTROL.getDescriptor() + ")V";
private final static Method METHOD_GET_EVENT_WRITER = new Method("getEventWriter", "()" + TYPE_EVENT_WRITER.getDescriptor());
private final static Method METHOD_EVENT_HANDLER_CONSTRUCTOR = new Method("<init>", DESCRIPTOR_EVENT_HANDLER);
private final static Method METHOD_RESET = new Method("reset", "()V");
private final ClassWriter classWriter;
private final String className;
private final String internalClassName;
private final List<SettingInfo> settingInfos;
private final List<FieldInfo> fields;
public EventHandlerCreator(long id, List<SettingInfo> settingInfos, List<FieldInfo> fields) {
this.classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
this.className = makeEventHandlerName(id);
this.internalClassName = ASMToolkit.getInternalName(className);
this.settingInfos = settingInfos;
this.fields = fields;
}
public static String makeEventHandlerName(long id) {
return eventHandlerProxy.getName() + id + SUFFIX;
}
public EventHandlerCreator(long id, List<SettingInfo> settingInfos, EventType type, Class<? extends Event> eventClass) {
this(id, settingInfos, createFieldInfos(eventClass, type));
}
private static List<FieldInfo> createFieldInfos(Class<? extends Event> eventClass, EventType type) throws Error {
List<FieldInfo> fieldInfos = new ArrayList<>();
for (ValueDescriptor v : type.getFields()) {
// Only value descriptors that are not fields on the event class.
if (v != TypeLibrary.STACK_TRACE_FIELD && v != TypeLibrary.THREAD_FIELD) {
String fieldName = PrivateAccess.getInstance().getFieldName(v);
String fieldDescriptor = ASMToolkit.getDescriptor(v.getTypeName());
Class<?> c = eventClass;
String internalName = null;
while (c != Event.class) {
try {
Field field = c.getDeclaredField(fieldName);
if (c == eventClass || !Modifier.isPrivate(field.getModifiers())) {
internalName = ASMToolkit.getInternalName(c.getName());
break;
}
} catch (NoSuchFieldException | SecurityException e) {
// ignore
}
c = c.getSuperclass();
}
if (internalName != null) {
fieldInfos.add(new FieldInfo(fieldName, fieldDescriptor, internalName));
} else {
throw new InternalError("Could not locate field " + fieldName + " for event type" + type.getName());
}
}
}
return fieldInfos;
}
public Class<? extends EventHandler> makeEventHandlerClass() {
buildClassInfo();
buildConstructor();
buildWriteMethod();
byte[] bytes = classWriter.toByteArray();
ASMToolkit.logASM(className, bytes);
return SecuritySupport.defineClass(className, bytes, Event.class.getClassLoader()).asSubclass(EventHandler.class);
}
public static EventHandler instantiateEventHandler(Class<? extends EventHandler> handlerClass, boolean registered, EventType eventType, EventControl eventControl) throws Error {
final Constructor<?> cc;
try {
cc = handlerClass.getDeclaredConstructors()[0];
} catch (Exception e) {
throw (Error) new InternalError("Could not get handler constructor for " + eventType.getName()).initCause(e);
}
// Users should not be allowed to create instances of the event handler
// so we need to unlock it here.
SecuritySupport.setAccessible(cc);
try {
List<SettingInfo> settingInfos = eventControl.getSettingInfos();
Object[] arguments = new Object[3 + settingInfos.size()];
arguments[0] = registered;
arguments[1] = eventType;
arguments[2] = eventControl;
for (SettingInfo si : settingInfos) {
arguments[si.index + 3] = si.settingControl;
}
return (EventHandler) cc.newInstance(arguments);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw (Error) new InternalError("Could not instantiate event handler for " + eventType.getName() + ". " + e.getMessage()).initCause(e);
}
}
private void buildConstructor() {
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PRIVATE, METHOD_EVENT_HANDLER_CONSTRUCTOR.getName(), makeConstructorDescriptor(settingInfos), null, null);
mv.visitVarInsn(Opcodes.ALOAD, 0); // this
mv.visitVarInsn(Opcodes.ILOAD, 1); // registered
mv.visitVarInsn(Opcodes.ALOAD, 2); // event type
mv.visitVarInsn(Opcodes.ALOAD, 3); // event control
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(eventHandlerProxy), METHOD_EVENT_HANDLER_CONSTRUCTOR.getName(), METHOD_EVENT_HANDLER_CONSTRUCTOR.getDescriptor(), false);
for (SettingInfo si : settingInfos) {
mv.visitVarInsn(Opcodes.ALOAD, 0); // this
mv.visitVarInsn(Opcodes.ALOAD, si.index + 4); // Setting Control
mv.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, si.fieldName, TYPE_SETTING_CONTROL.getDescriptor());
}
// initialized string field writers
int fieldIndex = 0;
for (FieldInfo field : fields) {
if (field.isString()) {
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(eventHandlerProxy), "createStringFieldWriter", "()" + TYPE_STRING_POOL.getDescriptor(), false);
mv.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, FIELD_PREFIX_STRING_POOL + fieldIndex, TYPE_STRING_POOL.getDescriptor());
}
fieldIndex++;
}
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void buildClassInfo() {
String internalSuperName = ASMToolkit.getInternalName(eventHandlerProxy.getName());
classWriter.visit(CLASS_VERSION, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, internalClassName, null, internalSuperName, null);
for (SettingInfo si : settingInfos) {
classWriter.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, si.fieldName, TYPE_SETTING_CONTROL.getDescriptor(), null, null);
}
int fieldIndex = 0;
for (FieldInfo field : fields) {
if (field.isString()) {
classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, FIELD_PREFIX_STRING_POOL+ fieldIndex, TYPE_STRING_POOL.getDescriptor(), null, null);
}
fieldIndex++;
}
}
private void visitMethod(final MethodVisitor mv, final int opcode, final Type type, final Method method) {
mv.visitMethodInsn(opcode, type.getInternalName(), method.getName(), method.getDescriptor(), false);
}
private void buildWriteMethod() {
int argIndex = 0; // // indexes the argument type array, the argument type array does not include 'this'
int slotIndex = 1; // indexes the proper slot in the local variable table, takes type size into account, therefore sometimes argIndex != slotIndex
int fieldIndex = 0;
Method desc = ASMToolkit.makeWriteMethod(fields);
Type[] argumentTypes = Type.getArgumentTypes(desc.getDescriptor());
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, desc.getName(), desc.getDescriptor(), null, null);
mv.visitCode();
Label start = new Label();
Label endTryBlock = new Label();
Label exceptionHandler = new Label();
mv.visitTryCatchBlock(start, endTryBlock, exceptionHandler, "java/lang/Throwable");
mv.visitLabel(start);
visitMethod(mv, Opcodes.INVOKESTATIC, TYPE_EVENT_WRITER, METHOD_GET_EVENT_WRITER);
// stack: [BW]
mv.visitInsn(Opcodes.DUP);
// stack: [BW], [BW]
// write begin event
mv.visitVarInsn(Opcodes.ALOAD, 0);
// stack: [BW], [BW], [this]
mv.visitFieldInsn(Opcodes.GETFIELD, TYPE_EVENT_HANDLER.getInternalName(), FIELD_EVENT_TYPE, TYPE_PLATFORM_EVENT_TYPE.getDescriptor());
// stack: [BW], [BW], [BS]
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.BEGIN_EVENT.asASM());
// stack: [BW], [integer]
Label recursive = new Label();
mv.visitJumpInsn(Opcodes.IFEQ, recursive);
// stack: [BW]
// write startTime
mv.visitInsn(Opcodes.DUP);
// stack: [BW], [BW]
mv.visitVarInsn(argumentTypes[argIndex].getOpcode(Opcodes.ILOAD), slotIndex);
// stack: [BW], [BW], [long]
slotIndex += argumentTypes[argIndex++].getSize();
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.asASM());
// stack: [BW]
fieldIndex++;
// write duration
mv.visitInsn(Opcodes.DUP);
// stack: [BW], [BW]
mv.visitVarInsn(argumentTypes[argIndex].getOpcode(Opcodes.ILOAD), slotIndex);
// stack: [BW], [BW], [long]
slotIndex += argumentTypes[argIndex++].getSize();
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_LONG.asASM());
// stack: [BW]
fieldIndex++;
// write eventThread
mv.visitInsn(Opcodes.DUP);
// stack: [BW], [BW]
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_EVENT_THREAD.asASM());
// stack: [BW]
// write stackTrace
mv.visitInsn(Opcodes.DUP);
// stack: [BW], [BW]
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.PUT_STACK_TRACE.asASM());
// stack: [BW]
// write custom fields
while (fieldIndex < fields.size()) {
mv.visitInsn(Opcodes.DUP);
// stack: [BW], [BW]
mv.visitVarInsn(argumentTypes[argIndex].getOpcode(Opcodes.ILOAD), slotIndex);
// stack:[BW], [BW], [field]
slotIndex += argumentTypes[argIndex++].getSize();
FieldInfo field = fields.get(fieldIndex);
if (field.isString()) {
mv.visitVarInsn(Opcodes.ALOAD, 0);
// stack:[BW], [BW], [field], [this]
mv.visitFieldInsn(Opcodes.GETFIELD, this.internalClassName, FIELD_PREFIX_STRING_POOL + fieldIndex, TYPE_STRING_POOL.getDescriptor());
// stack:[BW], [BW], [field], [string]
}
EventWriterMethod eventMethod = EventWriterMethod.lookupMethod(field);
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, eventMethod.asASM());
// stack: [BW]
fieldIndex++;
}
// stack: [BW]
// write end event (writer already on stack)
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, EventWriterMethod.END_EVENT.asASM());
// stack [integer]
// notified -> restart event write attempt
mv.visitJumpInsn(Opcodes.IFEQ, start);
// stack:
mv.visitLabel(endTryBlock);
Label end = new Label();
mv.visitJumpInsn(Opcodes.GOTO, end);
mv.visitLabel(exceptionHandler);
// stack: [ex]
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
visitMethod(mv, Opcodes.INVOKESTATIC, TYPE_EVENT_WRITER, METHOD_GET_EVENT_WRITER);
// stack: [ex] [BW]
mv.visitInsn(Opcodes.DUP);
// stack: [ex] [BW] [BW]
Label rethrow = new Label();
mv.visitJumpInsn(Opcodes.IFNULL, rethrow);
// stack: [ex] [BW]
mv.visitInsn(Opcodes.DUP);
// stack: [ex] [BW] [BW]
visitMethod(mv, Opcodes.INVOKEVIRTUAL, TYPE_EVENT_WRITER, METHOD_RESET);
mv.visitLabel(rethrow);
// stack:[ex] [BW]
mv.visitFrame(Opcodes.F_SAME, 0, null, 2, new Object[] {"java/lang/Throwable", TYPE_EVENT_WRITER.getInternalName()});
mv.visitInsn(Opcodes.POP);
// stack:[ex]
mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(recursive);
// stack: [BW]
mv.visitFrame(Opcodes.F_SAME, 0, null, 1, new Object[] { TYPE_EVENT_WRITER.getInternalName()} );
mv.visitInsn(Opcodes.POP);
mv.visitLabel(end);
// stack:
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static String makeConstructorDescriptor(List<SettingInfo> settingsInfos) {
StringJoiner constructordescriptor = new StringJoiner("", "(", ")V");
constructordescriptor.add(Type.BOOLEAN_TYPE.getDescriptor());
constructordescriptor.add(Type.getType(EventType.class).getDescriptor());
constructordescriptor.add(Type.getType(EventControl.class).getDescriptor());
for (int i = 0; i < settingsInfos.size(); i++) {
constructordescriptor.add(TYPE_SETTING_CONTROL.getDescriptor());
}
return constructordescriptor.toString();
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, Red Hat, Inc.
*
* 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 jdk.jfr.internal;
import java.util.StringJoiner;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.Method;
import jdk.jfr.Event;
import jdk.jfr.EventType;
import jdk.jfr.internal.handlers.EventHandler;
/*
* Generates an EventHandler subclass dynamically, named EventHandlerProxy.
* EventHandlerProxy is located in a publicly accessible package (jdk.jfr)
* and is used as a superclass for classes generated by EventHandlerCreator.
* The rationale behind this scheme is to block access to jdk.jfr.internal
* package and sub-packages when there is a SecurityManager installed, while
* allowing application-defined event classes to invoke the required internal
* APIs.
*/
final class EventHandlerProxyCreator {
private static final int CLASS_VERSION = 52;
private final static Type TYPE_EVENT_TYPE = Type.getType(EventType.class);
private final static Type TYPE_EVENT_CONTROL = Type.getType(EventControl.class);
private final static String DESCRIPTOR_EVENT_HANDLER = "(" + Type.BOOLEAN_TYPE.getDescriptor() + TYPE_EVENT_TYPE.getDescriptor() + TYPE_EVENT_CONTROL.getDescriptor() + ")V";
private final static Method METHOD_EVENT_HANDLER_CONSTRUCTOR = new Method("<init>", DESCRIPTOR_EVENT_HANDLER);
private final static String DESCRIPTOR_TIME_STAMP = "()" + Type.LONG_TYPE.getDescriptor();
private final static Method METHOD_TIME_STAMP = new Method("timestamp", DESCRIPTOR_TIME_STAMP);
private final static String DESCRIPTOR_DURATION = "(" + Type.LONG_TYPE.getDescriptor() + ")" + Type.LONG_TYPE.getDescriptor();
private final static Method METHOD_DURATION = new Method("duration", DESCRIPTOR_DURATION);
private final static ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
private final static String className = "jdk.jfr.proxy.internal.EventHandlerProxy";
private final static String internalClassName = ASMToolkit.getInternalName(className);
// Create the Proxy class instance after all the previous static fields were initialized (textual order)
static final Class<? extends EventHandler> proxyClass = EventHandlerProxyCreator.makeEventHandlerProxyClass();
static void ensureInitialized() {
// trigger clinit which will setup the EventHandlerProxy class.
}
public static Class<? extends EventHandler> makeEventHandlerProxyClass() {
buildClassInfo();
buildConstructor();
buildTimestampMethod();
buildDurationMethod();
byte[] bytes = classWriter.toByteArray();
ASMToolkit.logASM(className, bytes);
return SecuritySupport.defineClass(className, bytes, Event.class.getClassLoader()).asSubclass(EventHandler.class);
}
private static void buildConstructor() {
MethodVisitor mv = classWriter.visitMethod(0x0, METHOD_EVENT_HANDLER_CONSTRUCTOR.getName(), makeConstructorDescriptor(), null, null);
mv.visitVarInsn(Opcodes.ALOAD, 0); // this
mv.visitVarInsn(Opcodes.ILOAD, 1); // registered
mv.visitVarInsn(Opcodes.ALOAD, 2); // event type
mv.visitVarInsn(Opcodes.ALOAD, 3); // event control
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(EventHandler.class), METHOD_EVENT_HANDLER_CONSTRUCTOR.getName(), METHOD_EVENT_HANDLER_CONSTRUCTOR.getDescriptor(), false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void buildClassInfo() {
String internalSuperName = ASMToolkit.getInternalName(EventHandler.class.getName());
classWriter.visit(CLASS_VERSION, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SUPER, internalClassName, null, internalSuperName, null);
}
private static void buildTimestampMethod() {
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), null, null);
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(EventHandler.class), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false);
mv.visitInsn(Opcodes.LRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void buildDurationMethod() {
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, METHOD_DURATION.getName(), METHOD_DURATION.getDescriptor(), null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.LLOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(EventHandler.class), METHOD_DURATION.getName(), METHOD_DURATION.getDescriptor(), false);
mv.visitInsn(Opcodes.LRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static String makeConstructorDescriptor() {
StringJoiner constructordescriptor = new StringJoiner("", "(", ")V");
constructordescriptor.add(Type.BOOLEAN_TYPE.getDescriptor());
constructordescriptor.add(Type.getType(EventType.class).getDescriptor());
constructordescriptor.add(Type.getType(EventControl.class).getDescriptor());
return constructordescriptor.toString();
}
}

View File

@@ -0,0 +1,528 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.Method;
import jdk.internal.org.objectweb.asm.tree.AnnotationNode;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.FieldNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.jfr.Enabled;
import jdk.jfr.Event;
import jdk.jfr.Name;
import jdk.jfr.Registered;
import jdk.jfr.SettingControl;
import jdk.jfr.SettingDefinition;
import jdk.jfr.internal.handlers.EventHandler;
/**
* Class responsible for adding instrumentation to a subclass of {@link Event}.
*
*/
public final class EventInstrumentation {
static final class SettingInfo {
private String methodName;
private String internalSettingName;
private String settingDescriptor;
final String fieldName;
final int index;
// Used when instantiating Setting
SettingControl settingControl;
public SettingInfo(String fieldName, int index) {
this.fieldName = fieldName;
this.index = index;
}
}
static final class FieldInfo {
private final static Type STRING = Type.getType(String.class);
final String fieldName;
final String fieldDescriptor;
final String internalClassName;
public FieldInfo(String fieldName, String fieldDescriptor, String internalClassName) {
this.fieldName = fieldName;
this.fieldDescriptor = fieldDescriptor;
this.internalClassName = internalClassName;
}
public boolean isString() {
return STRING.getDescriptor().equals(fieldDescriptor);
}
}
public static final String FIELD_EVENT_THREAD = "eventThread";
public static final String FIELD_STACK_TRACE = "stackTrace";
public static final String FIELD_DURATION = "duration";
static final String FIELD_EVENT_HANDLER = "eventHandler";
static final String FIELD_START_TIME = "startTime";
private static final Class<? extends EventHandler> eventHandlerProxy = EventHandlerProxyCreator.proxyClass;
private static final Type ANNOTATION_TYPE_NAME = Type.getType(Name.class);
private static final Type ANNOTATION_TYPE_REGISTERED = Type.getType(Registered.class);
private static final Type ANNOTATION_TYPE_ENABLED = Type.getType(Enabled.class);
private static final Type TYPE_EVENT_HANDLER = Type.getType(eventHandlerProxy);
private static final Type TYPE_SETTING_CONTROL = Type.getType(SettingControl.class);
private static final Method METHOD_COMMIT = new Method("commit", Type.VOID_TYPE, new Type[0]);
private static final Method METHOD_BEGIN = new Method("begin", Type.VOID_TYPE, new Type[0]);
private static final Method METHOD_END = new Method("end", Type.VOID_TYPE, new Type[0]);
private static final Method METHOD_IS_ENABLED = new Method("isEnabled", Type.BOOLEAN_TYPE, new Type[0]);
private static final Method METHOD_TIME_STAMP = new Method("timestamp", Type.LONG_TYPE, new Type[0]);
private static final Method METHOD_EVENT_SHOULD_COMMIT = new Method("shouldCommit", Type.BOOLEAN_TYPE, new Type[0]);
private static final Method METHOD_EVENT_HANDLER_SHOULD_COMMIT = new Method("shouldCommit", Type.BOOLEAN_TYPE, new Type[] { Type.LONG_TYPE });
private static final Method METHOD_DURATION = new Method("duration", Type.LONG_TYPE, new Type[] { Type.LONG_TYPE });
private final ClassNode classNode;
private final List<SettingInfo> settingInfos;
private final List<FieldInfo> fieldInfos;;
private final Method writeMethod;
private final String eventHandlerXInternalName;
private final String eventName;
private boolean guardHandlerReference;
private Class<?> superClass;
EventInstrumentation(Class<?> superClass, byte[] bytes, long id) {
this.superClass = superClass;
this.classNode = createClassNode(bytes);
this.settingInfos = buildSettingInfos(superClass, classNode);
this.fieldInfos = buildFieldInfos(superClass, classNode);
this.writeMethod = makeWriteMethod(fieldInfos);
this.eventHandlerXInternalName = ASMToolkit.getInternalName(EventHandlerCreator.makeEventHandlerName(id));
String n = annotationValue(classNode, ANNOTATION_TYPE_NAME.getDescriptor(), String.class);
this.eventName = n == null ? classNode.name.replace("/", ".") : n;
}
public String getClassName() {
return classNode.name.replace("/",".");
}
private ClassNode createClassNode(byte[] bytes) {
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(bytes);
classReader.accept(classNode, 0);
return classNode;
}
boolean isRegistered() {
Boolean result = annotationValue(classNode, ANNOTATION_TYPE_REGISTERED.getDescriptor(), Boolean.class);
if (result != null) {
return result.booleanValue();
}
if (superClass != null) {
Registered r = superClass.getAnnotation(Registered.class);
if (r != null) {
return r.value();
}
}
return true;
}
boolean isEnabled() {
Boolean result = annotationValue(classNode, ANNOTATION_TYPE_ENABLED.getDescriptor(), Boolean.class);
if (result != null) {
return result.booleanValue();
}
if (superClass != null) {
Enabled e = superClass.getAnnotation(Enabled.class);
if (e != null) {
return e.value();
}
}
return true;
}
@SuppressWarnings("unchecked")
private static <T> T annotationValue(ClassNode classNode, String typeDescriptor, Class<?> type) {
if (classNode.visibleAnnotations != null) {
for (AnnotationNode a : classNode.visibleAnnotations) {
if (typeDescriptor.equals(a.desc)) {
List<Object> values = a.values;
if (values != null && values.size() == 2) {
Object key = values.get(0);
Object value = values.get(1);
if (key instanceof String && value != null) {
if (type == value.getClass()) {
String keyName = (String) key;
if ("value".equals(keyName)) {
return (T) value;
}
}
}
}
}
}
}
return null;
}
private static List<SettingInfo> buildSettingInfos(Class<?> superClass, ClassNode classNode) {
Set<String> methodSet = new HashSet<>();
List<SettingInfo> settingInfos = new ArrayList<>();
String settingDescriptor = Type.getType(SettingDefinition.class).getDescriptor();
for (MethodNode m : classNode.methods) {
if (m.visibleAnnotations != null) {
for (AnnotationNode an : m.visibleAnnotations) {
// We can't really validate the method at this
// stage. We would need to check that the parameter
// is an instance of SettingControl.
if (settingDescriptor.equals(an.desc)) {
Type returnType = Type.getReturnType(m.desc);
if (returnType.equals(Type.getType(Boolean.TYPE))) {
Type[] args = Type.getArgumentTypes(m.desc);
if (args.length == 1) {
Type paramType = args[0];
String fieldName = EventControl.FIELD_SETTING_PREFIX + settingInfos.size();
int index = settingInfos.size();
SettingInfo si = new SettingInfo(fieldName, index);
si.methodName = m.name;
si.settingDescriptor = paramType.getDescriptor();
si.internalSettingName = paramType.getInternalName();
methodSet.add(m.name);
settingInfos.add(si);
}
}
}
}
}
}
for (Class<?> c = superClass; c != Event.class; c = c.getSuperclass()) {
for (java.lang.reflect.Method method : c.getDeclaredMethods()) {
if (!methodSet.contains(method.getName())) {
// skip private method in base classes
if (!Modifier.isPrivate(method.getModifiers())) {
if (method.getReturnType().equals(Boolean.TYPE)) {
if (method.getParameterCount() == 1) {
Parameter param = method.getParameters()[0];
Type paramType = Type.getType(param.getType());
String fieldName = EventControl.FIELD_SETTING_PREFIX + settingInfos.size();
int index = settingInfos.size();
SettingInfo si = new SettingInfo(fieldName, index);
si.methodName = method.getName();
si.settingDescriptor = paramType.getDescriptor();
si.internalSettingName = paramType.getInternalName();
methodSet.add(method.getName());
settingInfos.add(si);
}
}
}
}
}
}
return settingInfos;
}
private static List<FieldInfo> buildFieldInfos(Class<?> superClass, ClassNode classNode) {
Set<String> fieldSet = new HashSet<>();
List<FieldInfo> fieldInfos = new ArrayList<>(classNode.fields.size());
// These two field are added by native as transient so they will be
// ignored by the loop below.
// The benefit of adding them manually is that we can
// control in which order they occur and we can add @Name, @Description
// in Java, instead of in native. It also means code for adding implicit
// fields for native can be reused by Java.
fieldInfos.add(new FieldInfo("startTime", Type.LONG_TYPE.getDescriptor(), classNode.name));
fieldInfos.add(new FieldInfo("duration", Type.LONG_TYPE.getDescriptor(), classNode.name));
for (FieldNode field : classNode.fields) {
String className = Type.getType(field.desc).getClassName();
if (!fieldSet.contains(field.name) && isValidField(field.access, className)) {
FieldInfo fi = new FieldInfo(field.name, field.desc, classNode.name);
fieldInfos.add(fi);
fieldSet.add(field.name);
}
}
for (Class<?> c = superClass; c != Event.class; c = c.getSuperclass()) {
for (Field field : c.getDeclaredFields()) {
// skip private field in base classes
if (!Modifier.isPrivate(field.getModifiers())) {
if (isValidField(field.getModifiers(), field.getType().getName())) {
String fieldName = field.getName();
if (!fieldSet.contains(fieldName)) {
Type fieldType = Type.getType(field.getType());
String internalClassName = ASMToolkit.getInternalName(c.getName());
fieldInfos.add(new FieldInfo(fieldName, fieldType.getDescriptor(), internalClassName));
fieldSet.add(fieldName);
}
}
}
}
}
return fieldInfos;
}
public static boolean isValidField(int access, String className) {
if (Modifier.isTransient(access) || Modifier.isStatic(access)) {
return false;
}
return jdk.jfr.internal.Type.isValidJavaFieldType(className);
}
public byte[] buildInstrumented() {
makeInstrumented();
return toByteArray();
}
private byte[] toByteArray() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
classNode.accept(cw);
cw.visitEnd();
byte[] result = cw.toByteArray();
Utils.writeGeneratedASM(classNode.name, result);
return result;
}
public byte[] builUninstrumented() {
makeUninstrumented();
return toByteArray();
}
private void makeInstrumented() {
// MyEvent#isEnabled()
updateMethod(METHOD_IS_ENABLED, methodVisitor -> {
Label nullLabel = new Label();
if (guardHandlerReference) {
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, TYPE_EVENT_HANDLER.getDescriptor());
methodVisitor.visitJumpInsn(Opcodes.IFNULL, nullLabel);
}
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, TYPE_EVENT_HANDLER.getDescriptor());
ASMToolkit.invokeVirtual(methodVisitor, TYPE_EVENT_HANDLER.getInternalName(), METHOD_IS_ENABLED);
methodVisitor.visitInsn(Opcodes.IRETURN);
if (guardHandlerReference) {
methodVisitor.visitLabel(nullLabel);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitInsn(Opcodes.IRETURN);
}
});
// MyEvent#begin()
updateMethod(METHOD_BEGIN, methodVisitor -> {
methodVisitor.visitIntInsn(Opcodes.ALOAD, 0);
ASMToolkit.invokeStatic(methodVisitor, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP);
methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J");
methodVisitor.visitInsn(Opcodes.RETURN);
});
// MyEvent#end()
updateMethod(METHOD_END, methodVisitor -> {
methodVisitor.visitIntInsn(Opcodes.ALOAD, 0);
methodVisitor.visitIntInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
ASMToolkit.invokeStatic(methodVisitor, TYPE_EVENT_HANDLER.getInternalName(), METHOD_DURATION);
methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(0, 0);
});
// MyEvent#commit() - Java event writer
updateMethod(METHOD_COMMIT, methodVisitor -> {
// if (!isEnable()) {
// return;
// }
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_IS_ENABLED.getName(), METHOD_IS_ENABLED.getDescriptor(), false);
Label l0 = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFNE, l0);
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitLabel(l0);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// if (startTime == 0) {
// startTime = EventWriter.timestamp();
// } else {
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
methodVisitor.visitInsn(Opcodes.LCONST_0);
methodVisitor.visitInsn(Opcodes.LCMP);
Label durationalEvent = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFNE, durationalEvent);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(),
METHOD_TIME_STAMP.getDescriptor(), false);
methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J");
Label commit = new Label();
methodVisitor.visitJumpInsn(Opcodes.GOTO, commit);
// if (duration == 0) {
// duration = EventWriter.timestamp() - startTime;
// }
// }
methodVisitor.visitLabel(durationalEvent);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J");
methodVisitor.visitInsn(Opcodes.LCONST_0);
methodVisitor.visitInsn(Opcodes.LCMP);
methodVisitor.visitJumpInsn(Opcodes.IFNE, commit);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
methodVisitor.visitInsn(Opcodes.LSUB);
methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J");
methodVisitor.visitLabel(commit);
// if (shouldCommit()) {
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_EVENT_SHOULD_COMMIT.getName(), METHOD_EVENT_SHOULD_COMMIT.getDescriptor(), false);
Label end = new Label();
// eventHandler.write(...);
// }
methodVisitor.visitJumpInsn(Opcodes.IFEQ, end);
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy));
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName);
for (FieldInfo fi : fieldInfos) {
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, fi.internalClassName, fi.fieldName, fi.fieldDescriptor);
}
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventHandlerXInternalName, writeMethod.getName(), writeMethod.getDescriptor(), false);
methodVisitor.visitLabel(end);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitEnd();
});
// MyEvent#shouldCommit()
updateMethod(METHOD_EVENT_SHOULD_COMMIT, methodVisitor -> {
Label fail = new Label();
// if (!eventHandler.shoouldCommit(duration) goto fail;
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy));
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J");
ASMToolkit.invokeVirtual(methodVisitor, TYPE_EVENT_HANDLER.getInternalName(), METHOD_EVENT_HANDLER_SHOULD_COMMIT);
methodVisitor.visitJumpInsn(Opcodes.IFEQ, fail);
for (SettingInfo si : settingInfos) {
// if (!settingsMethod(eventHandler.settingX)) goto fail;
methodVisitor.visitIntInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy));
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, eventHandlerXInternalName, si.fieldName, TYPE_SETTING_CONTROL.getDescriptor());
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, si.internalSettingName);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), si.methodName, "(" + si.settingDescriptor + ")Z", false);
methodVisitor.visitJumpInsn(Opcodes.IFEQ, fail);
}
// return true
methodVisitor.visitInsn(Opcodes.ICONST_1);
methodVisitor.visitInsn(Opcodes.IRETURN);
// return false
methodVisitor.visitLabel(fail);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitInsn(Opcodes.IRETURN);
});
}
private void makeUninstrumented() {
updateExistingWithReturnFalse(METHOD_EVENT_SHOULD_COMMIT);
updateExistingWithReturnFalse(METHOD_IS_ENABLED);
updateExistingWithEmptyVoidMethod(METHOD_COMMIT);
updateExistingWithEmptyVoidMethod(METHOD_BEGIN);
updateExistingWithEmptyVoidMethod(METHOD_END);
}
private final void updateExistingWithEmptyVoidMethod(Method voidMethod) {
updateMethod(voidMethod, methodVisitor -> {
methodVisitor.visitInsn(Opcodes.RETURN);
});
}
private final void updateExistingWithReturnFalse(Method voidMethod) {
updateMethod(voidMethod, methodVisitor -> {
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitInsn(Opcodes.IRETURN);
});
}
private MethodNode getMethodNode(Method method) {
for (MethodNode m : classNode.methods) {
if (m.name.equals(method.getName()) && m.desc.equals(method.getDescriptor())) {
return m;
}
}
return null;
}
private final void updateMethod(Method method, Consumer<MethodVisitor> code) {
MethodNode old = getMethodNode(method);
int index = classNode.methods.indexOf(old);
classNode.methods.remove(old);
MethodVisitor mv = classNode.visitMethod(old.access, old.name, old.desc, null, null);
mv.visitCode();
code.accept(mv);
mv.visitMaxs(0, 0);
MethodNode newMethod = getMethodNode(method);
classNode.methods.remove(newMethod);
classNode.methods.add(index, newMethod);
}
public static Method makeWriteMethod(List<FieldInfo> fields) {
StringBuilder sb = new StringBuilder();
sb.append("(");
for (FieldInfo v : fields) {
sb.append(v.fieldDescriptor);
}
sb.append(")V");
return new Method("write", sb.toString());
}
private String getInternalClassName() {
return classNode.name;
}
public List<SettingInfo> getSettingInfos() {
return settingInfos;
}
public List<FieldInfo> getFieldInfos() {
return fieldInfos;
}
public String getEventName() {
return eventName;
}
public void setGuardHandler(boolean guardHandlerReference) {
this.guardHandlerReference = guardHandlerReference;
}
}

View File

@@ -0,0 +1,355 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import sun.misc.Unsafe;
import jdk.jfr.internal.consumer.RecordingInput;
/**
* Class must reside in a package with package restriction.
*
* Users should not have direct access to underlying memory.
*
*/
public final class EventWriter {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private final static JVM jvm = JVM.getJVM();
private long startPosition;
private long startPositionAddress;
private long currentPosition;
private long maxPosition;
private final long threadID;
private PlatformEventType eventType;
private int maxEventSize;
private boolean started;
private boolean valid;
private boolean flushOnEnd;
// set by the JVM, not private to avoid being optimized out
boolean notified;
public static EventWriter getEventWriter() {
EventWriter ew = (EventWriter)JVM.getEventWriter();
return ew != null ? ew : JVM.newEventWriter();
}
public void putBoolean(boolean i) {
if (isValidForSize(Byte.BYTES)) {
currentPosition += Bits.putBoolean(currentPosition, i);
}
}
public void putByte(byte i) {
if (isValidForSize(Byte.BYTES)) {
unsafe.putByte(currentPosition, i);
++currentPosition;
}
}
public void putChar(char v) {
if (isValidForSize(Character.BYTES + 1)) {
putUncheckedLong(v);
}
}
private void putUncheckedChar(char v) {
putUncheckedLong(v);
}
public void putShort(short v) {
if (isValidForSize(Short.BYTES + 1)) {
putUncheckedLong(v & 0xFFFF);
}
}
public void putInt(int v) {
if (isValidForSize(Integer.BYTES + 1)) {
putUncheckedLong(v & 0x00000000ffffffffL);
}
}
private void putUncheckedInt(int v) {
putUncheckedLong(v & 0x00000000ffffffffL);
}
public void putFloat(float i) {
if (isValidForSize(Float.BYTES)) {
currentPosition += Bits.putFloat(currentPosition, i);
}
}
public void putLong(long v) {
if (isValidForSize(Long.BYTES + 1)) {
putUncheckedLong(v);
}
}
public void putDouble(double i) {
if (isValidForSize(Double.BYTES)) {
currentPosition += Bits.putDouble(currentPosition, i);
}
}
public void putString(String s, StringPool pool) {
if (s == null) {
putByte(RecordingInput.STRING_ENCODING_NULL);
return;
}
int length = s.length();
if (length == 0) {
putByte(RecordingInput.STRING_ENCODING_EMPTY_STRING);
return;
}
if (length > StringPool.MIN_LIMIT && length < StringPool.MAX_LIMIT) {
long l = StringPool.addString(s);
if (l > 0) {
putByte(RecordingInput.STRING_ENCODING_CONSTANT_POOL);
putLong(l);
return;
}
}
putStringValue(s);
return;
}
private void putStringValue(String s) {
int length = s.length();
if (isValidForSize(1 + 5 + 3 * length)) {
putUncheckedByte(RecordingInput.STRING_ENCODING_CHAR_ARRAY); // 1 byte
putUncheckedInt(length); // max 5 bytes
for (int i = 0; i < length; i++) {
putUncheckedChar(s.charAt(i)); // max 3 bytes
}
}
}
public void putEventThread() {
putLong(threadID);
}
public void putThread(Thread athread) {
if (athread == null) {
putLong(0L);
} else {
putLong(jvm.getThreadId(athread));
}
}
public void putClass(Class<?> aClass) {
if (aClass == null) {
putLong(0L);
} else {
putLong(JVM.getClassIdNonIntrinsic(aClass));
}
}
public void putStackTrace() {
if (eventType.getStackTraceEnabled()) {
putLong(jvm.getStackTraceId(eventType.getStackTraceOffset()));
} else {
putLong(0L);
}
}
private void reserveEventSizeField() {
// move currentPosition Integer.Bytes offset from start position
if (isValidForSize(Integer.BYTES)) {
currentPosition += Integer.BYTES;
}
}
private void reset() {
currentPosition = startPosition;
if (flushOnEnd) {
flushOnEnd = flush();
}
valid = true;
started = false;
}
private boolean isValidForSize(int requestedSize) {
if (!valid) {
return false;
}
if (currentPosition + requestedSize > maxPosition) {
flushOnEnd = flush(usedSize(), requestedSize);
// retry
if (currentPosition + requestedSize > maxPosition) {
Logger.log(LogTag.JFR_SYSTEM,
LogLevel.WARN, () ->
"Unable to commit. Requested size " + requestedSize + " too large");
valid = false;
return false;
}
}
return true;
}
private boolean isNotified() {
return notified;
}
private void resetNotified() {
notified = false;
}
private int usedSize() {
return (int) (currentPosition - startPosition);
}
private boolean flush() {
return flush(usedSize(), 0);
}
private boolean flush(int usedSize, int requestedSize) {
return JVM.flush(this, usedSize, requestedSize);
}
public boolean beginEvent(PlatformEventType eventType) {
if (started) {
// recursive write attempt
return false;
}
started = true;
this.eventType = eventType;
reserveEventSizeField();
putLong(eventType.getId());
return true;
}
public boolean endEvent() {
if (!valid) {
reset();
return true;
}
final int eventSize = usedSize();
if (eventSize > maxEventSize) {
reset();
return true;
}
Bits.putInt(startPosition, makePaddedInt(eventSize));
if (isNotified()) {
resetNotified();
reset();
// returning false will trigger restart of the event write attempt
return false;
}
startPosition = currentPosition;
unsafe.putAddress(startPositionAddress, startPosition);
// the event is now committed
if (flushOnEnd) {
flushOnEnd = flush();
}
started = false;
return true;
}
private EventWriter(long startPos, long maxPos, long startPosAddress, long threadID, boolean valid) {
startPosition = currentPosition = startPos;
maxPosition = maxPos;
startPositionAddress = startPosAddress;
this.threadID = threadID;
started = false;
flushOnEnd = false;
this.valid = valid;
notified = false;
// event may not exceed size for a padded integer
maxEventSize = (1 << 28) -1;
}
private static int makePaddedInt(int v) {
// bit 0-6 + pad => bit 24 - 31
long b1 = (((v >>> 0) & 0x7F) | 0x80) << 24;
// bit 7-13 + pad => bit 16 - 23
long b2 = (((v >>> 7) & 0x7F) | 0x80) << 16;
// bit 14-20 + pad => bit 8 - 15
long b3 = (((v >>> 14) & 0x7F) | 0x80) << 8;
// bit 21-28 => bit 0 - 7
long b4 = (((v >>> 21) & 0x7F)) << 0;
return (int) (b1 + b2 + b3 + b4);
}
private void putUncheckedLong(long v) {
if ((v & ~0x7FL) == 0L) {
putUncheckedByte((byte) v); // 0-6
return;
}
putUncheckedByte((byte) (v | 0x80L)); // 0-6
v >>>= 7;
if ((v & ~0x7FL) == 0L) {
putUncheckedByte((byte) v); // 7-13
return;
}
putUncheckedByte((byte) (v | 0x80L)); // 7-13
v >>>= 7;
if ((v & ~0x7FL) == 0L) {
putUncheckedByte((byte) v); // 14-20
return;
}
putUncheckedByte((byte) (v | 0x80L)); // 14-20
v >>>= 7;
if ((v & ~0x7FL) == 0L) {
putUncheckedByte((byte) v); // 21-27
return;
}
putUncheckedByte((byte) (v | 0x80L)); // 21-27
v >>>= 7;
if ((v & ~0x7FL) == 0L) {
putUncheckedByte((byte) v); // 28-34
return;
}
putUncheckedByte((byte) (v | 0x80L)); // 28-34
v >>>= 7;
if ((v & ~0x7FL) == 0L) {
putUncheckedByte((byte) v); // 35-41
return;
}
putUncheckedByte((byte) (v | 0x80L)); // 35-41
v >>>= 7;
if ((v & ~0x7FL) == 0L) {
putUncheckedByte((byte) v); // 42-48
return;
}
putUncheckedByte((byte) (v | 0x80L)); // 42-48
v >>>= 7;
if ((v & ~0x7FL) == 0L) {
putUncheckedByte((byte) v); // 49-55
return;
}
putUncheckedByte((byte) (v | 0x80L)); // 49-55
putUncheckedByte((byte) (v >>> 7)); // 56-63, last byte as is.
}
private void putUncheckedByte(byte i) {
unsafe.putByte(currentPosition, i);
++currentPosition;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import jdk.internal.org.objectweb.asm.commons.Method;
import jdk.jfr.internal.EventInstrumentation.FieldInfo;
public enum EventWriterMethod {
BEGIN_EVENT("(" + jdk.internal.org.objectweb.asm.Type.getType(PlatformEventType.class).getDescriptor() + ")Z", "???", "beginEvent"),
END_EVENT("()Z", "???", "endEvent"),
PUT_BYTE("(B)V", "byte", "putByte"),
PUT_SHORT("(S)V", "short", "putShort"),
PUT_INT("(I)V", "int", "putInt"),
PUT_LONG("(J)V", "long", "putLong"),
PUT_FLOAT("(F)V", "float", "putFloat"),
PUT_DOUBLE("(D)V", "double", "putDouble"),
PUT_CHAR("(C)V", "char", "putChar"),
PUT_BOOLEAN("(Z)V", "boolean", "putBoolean"),
PUT_THREAD("(Ljava/lang/Thread;)V", Type.THREAD.getName(), "putThread"),
PUT_CLASS("(Ljava/lang/Class;)V", Type.CLASS.getName(), "putClass"),
PUT_STRING("(Ljava/lang/String;Ljdk/jfr/internal/StringPool;)V", Type.STRING.getName(), "putString"),
PUT_EVENT_THREAD("()V", Type.THREAD.getName(), "putEventThread"),
PUT_STACK_TRACE("()V", Type.TYPES_PREFIX + "StackTrace", "putStackTrace");
private final Method asmMethod;
private final String typeDescriptor;
EventWriterMethod(String paramSignature, String typeName, String methodName) {
this.typeDescriptor = ASMToolkit.getDescriptor(typeName);
this.asmMethod = new Method(methodName, paramSignature);
}
public Method asASM() {
return asmMethod;
}
/**
* Return method in {@link EventWriter} class to use when writing event of
* a certain type.
*
* @param v field info
*
* @return the method
*/
public static EventWriterMethod lookupMethod(FieldInfo v) {
// event thread
if (v.fieldName.equals(EventInstrumentation.FIELD_EVENT_THREAD)) {
return EventWriterMethod.PUT_EVENT_THREAD;
}
for (EventWriterMethod m : EventWriterMethod.values()) {
if (v.fieldDescriptor.equals(m.typeDescriptor)) {
return m;
}
}
throw new Error("Unknown type " + v.fieldDescriptor);
}
}

View File

@@ -0,0 +1,535 @@
/*
* Copyright (c) 2017, 2018, 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 jdk.jfr.internal;
import java.io.IOException;
import java.util.List;
import jdk.jfr.Event;
/**
* Interface against the JVM.
*
*/
public final class JVM {
private static final JVM jvm = new JVM();
// JVM signals file changes by doing Object#notifu on this object
static final Object FILE_DELTA_CHANGE = new Object();
static final long RESERVED_CLASS_ID_LIMIT = 400;
private volatile boolean recording;
private volatile boolean nativeOK;
private static native void registerNatives();
static {
registerNatives();
// XXX
// for (LogTag tag : LogTag.values()) {
// subscribeLogLevel(tag, tag.id);
// }
Options.ensureInitialized();
EventHandlerProxyCreator.ensureInitialized();
}
/**
* Get the one and only JVM.
*
* @return the JVM
*/
public static JVM getJVM() {
return jvm;
}
private JVM() {
}
/**
* Begin recording events
*
* Requires that JFR has been started with {@link #createNativeJFR()}
*/
public native void beginRecording();
/**
* Return ticks
*
* @return the time, in ticks
*
*/
// @HotSpotIntrinsicCandidate
public static native long counterTime();
/**
* Emits native periodic event.
*
* @param eventTypeId type id
*
* @param timestamp commit time for event
* @param when when it is being done {@link Periodic.When}
*
* @return true if the event was committed
*/
public native boolean emitEvent(long eventTypeId, long timestamp, long when);
/**
* End recording events, which includes flushing data in thread buffers
*
* Requires that JFR has been started with {@link #createNativeJFR()}
*
*/
public native void endRecording();
/**
* Return a list of all classes deriving from {@link Event}
*
* @return list of event classes.
*/
public native List<Class<? extends Event>> getAllEventClasses();
/**
* Return a count of the number of unloaded classes deriving from {@link Event}
*
* @return number of unloaded event classes.
*/
public native long getUnloadedEventClassCount();
/**
* Return a unique identifier for a class. The class is marked as being
* "in use" in JFR.
*
* @param clazz clazz
*
* @return a unique class identifier
*/
// @HotSpotIntrinsicCandidate
public static native long getClassId(Class<?> clazz);
// temporary workaround until we solve intrinsics supporting epoch shift tagging
public static native long getClassIdNonIntrinsic(Class<?> clazz);
/**
* Return process identifier.
*
* @return process identifier
*/
public native String getPid();
/**
* Return unique identifier for stack trace.
*
* Requires that JFR has been started with {@link #createNativeJFR()}
*
* @param skipCount number of frames to skip
* @return a unique stack trace identifier
*/
public native long getStackTraceId(int skipCount);
/**
* Return identifier for thread
*
* @param t thread
* @return a unique thread identifier
*/
public native long getThreadId(Thread t);
/**
* Frequency, ticks per second
*
* @return frequency
*/
public native long getTicksFrequency();
/**
* Write message to log. Should swallow null or empty message, and be able
* to handle any Java character and not crash with very large message
*
* @param tagSetId the tagset id
* @param level on level
* @param message log message
*
*/
public static native void log(int tagSetId, int level, String message);
/**
* Check whether the logger would output on the given level
*
* @param level on level
* @return {@literal true} if the logger would output on the given level
*/
public static native boolean shouldLog(int level);
/**
* Subscribe to LogLevel updates for LogTag
*
* @param lt the log tag to subscribe
* @param tagSetId the tagset id
*/
public static native void subscribeLogLevel(LogTag lt, int tagSetId);
/**
* Call to invoke event tagging and retransformation of the passed classes
*
* @param classes
*/
public native synchronized void retransformClasses(Class<?>[] classes);
/**
* Enable event
*
* @param eventTypeId event type id
*
* @param enabled enable event
*/
public native void setEnabled(long eventTypeId, boolean enabled);
/**
* Interval at which the JVM should notify on {@link #FILE_DELTA_CHANGE}
*
* @param delta number of bytes, reset after file rotation
*/
public native void setFileNotification(long delta);
/**
* Set the number of global buffers to use
*
* @param count
*
* @throws IllegalArgumentException if count is not within a valid range
* @throws IllegalStateException if value can't be changed
*/
public native void setGlobalBufferCount(long count) throws IllegalArgumentException, IllegalStateException;
/**
* Set size of a global buffer
*
* @param size
*
* @throws IllegalArgumentException if buffer size is not within a valid
* range
*/
public native void setGlobalBufferSize(long size) throws IllegalArgumentException;
/**
* Set overall memory size
*
* @param size
*
* @throws IllegalArgumentException if memory size is not within a valid
* range
*/
public native void setMemorySize(long size) throws IllegalArgumentException;
/**
/**
* Set interval for method samples, in milliseconds.
*
* Setting interval to 0 turns off the method sampler.
*
* @param intervalMillis the sampling interval
*/
public native void setMethodSamplingInterval(long type, long intervalMillis);
/**
* Sets the file where data should be written.
*
* Requires that JFR has been started with {@link #createNativeJFR()}
*
* <pre>
* Recording Previous Current Action
* ==============================================
* true null null Ignore, keep recording in-memory
* true null file1 Start disk recording
* true file null Copy out metadata to disk and continue in-memory recording
* true file1 file2 Copy out metadata and start with new File (file2)
* false * null Ignore, but start recording to memory with {@link #beginRecording()}
* false * file Ignore, but start recording to disk with {@link #beginRecording()}
*
* </pre>
*
* recording can be set to true/false with {@link #beginRecording()}
* {@link #endRecording()}
*
* @param file the file where data should be written, or null if it should
* not be copied out (in memory).
*
* @throws IOException
*/
public native void setOutput(String file);
/**
* Controls if a class deriving from jdk.jfr.Event should
* always be instrumented on class load.
*
* @param force, true to force initialization, false otherwise
*/
public native void setForceInstrumentation(boolean force);
/**
* Turn on/off thread sampling.
*
* @param sampleThreads true if threads should be sampled, false otherwise.
*
* @throws IllegalStateException if state can't be changed.
*/
public native void setSampleThreads(boolean sampleThreads) throws IllegalStateException;
/**
* Turn on/off compressed integers.
*
* @param compressed true if compressed integers should be used, false
* otherwise.
*
* @throws IllegalStateException if state can't be changed.
*/
public native void setCompressedIntegers(boolean compressed) throws IllegalStateException;
/**
* Set stack depth.
*
* @param depth
*
* @throws IllegalArgumentException if not within a valid range
* @throws IllegalStateException if depth can't be changed
*/
public native void setStackDepth(int depth) throws IllegalArgumentException, IllegalStateException;
/**
* Turn on stack trace for an event
*
* @param eventTypeId the event id
*
* @param enabled if stack traces should be enabled
*/
public native void setStackTraceEnabled(long eventTypeId, boolean enabled);
/**
* Set thread buffer size.
*
* @param size
*
* @throws IllegalArgumentException if size is not within a valid range
* @throws IllegalStateException if size can't be changed
*/
public native void setThreadBufferSize(long size) throws IllegalArgumentException, IllegalStateException;
/**
* Set threshold for event,
*
* Long.MAXIMUM_VALUE = no limit
*
* @param eventTypeId the id of the event type
* @param ticks threshold in ticks,
* @return true, if it could be set
*/
public native boolean setThreshold(long eventTypeId, long ticks);
/**
* Store the metadata descriptor that is to be written at the end of a
* chunk, data should be written after GMT offset and size of metadata event
* should be adjusted
*
* Requires that JFR has been started with {@link #createNativeJFR()}
*
* @param bytes binary representation of metadata descriptor
*
* @param binary representation of descriptor
*/
public native void storeMetadataDescriptor(byte[] bytes);
public void endRecording_() {
endRecording();
recording = false;
}
public void beginRecording_() {
beginRecording();
recording = true;
}
public boolean isRecording() {
return recording;
}
/**
* If the JVM supports JVM TI and retransformation has not been disabled this
* method will return true. This flag can not change during the lifetime of
* the JVM.
*
* @return if transform is allowed
*/
public native boolean getAllowedToDoEventRetransforms();
/**
* Set up native resources, data structures, threads etc. for JFR
*
* @param simulateFailure simulate a initialization failure and rollback in
* native, used for testing purposes
*
* @throws IllegalStateException if native part of JFR could not be created.
*
*/
private native boolean createJFR(boolean simulateFailure) throws IllegalStateException;
/**
* Destroys native part of JFR. If already destroy, call is ignored.
*
* Requires that JFR has been started with {@link #createNativeJFR()}
*
* @return if an instance was actually destroyed.
*
*/
private native boolean destroyJFR();
public boolean createFailedNativeJFR() throws IllegalStateException {
return createJFR(true);
}
public void createNativeJFR() {
nativeOK = createJFR(false);
}
public boolean destroyNativeJFR() {
boolean result = destroyJFR();
nativeOK = !result;
return result;
}
public boolean hasNativeJFR() {
return nativeOK;
}
/**
* Cheap test to check if JFR functionality is available.
*
* @return
*/
public native boolean isAvailable();
/**
* To convert ticks to wall clock time.
*/
public native double getTimeConversionFactor();
/**
* Return a unique identifier for a class. Compared to {@link #getClassId()}
* , this method does not tag the class as being "in-use".
*
* @param clazz class
*
* @return a unique class identifier
*/
public native long getTypeId(Class<?> clazz);
/**
* Fast path fetching the EventWriter using VM intrinsics
*
* @return thread local EventWriter
*/
// @HotSpotIntrinsicCandidate
public static native Object getEventWriter();
/**
* Create a new EventWriter
*
* @return thread local EventWriter
*/
public static native EventWriter newEventWriter();
/**
* Flushes the EventWriter for this thread.
*/
public static native boolean flush(EventWriter writer, int uncommittedSize, int requestedSize);
/**
* Sets the location of the disk repository, to be used at an emergency
* dump.
*
* @param dirText
*/
public native void setRepositoryLocation(String dirText);
/**
* Access to VM termination support.
*
*@param errorMsg descriptive message to be include in VM termination sequence
*/
public native void abort(String errorMsg);
/**
* Adds a string to the string constant pool.
*
* If the same string is added twice, two entries will be created.
*
* @param id identifier associated with the string, not negative
*
* @param s string constant to be added, not null
*
* @return the current epoch of this insertion attempt
*/
public static native boolean addStringConstant(boolean epoch, long id, String s);
/**
* Gets the address of the jboolean epoch.
*
* The epoch alternates every checkpoint.
*
* @return The address of the jboolean.
*/
public native long getEpochAddress();
public native void uncaughtException(Thread thread, Throwable t);
/**
* Sets cutoff for event.
*
* Determines how long the event should be allowed to run.
*
* Long.MAXIMUM_VALUE = no limit
*
* @param eventTypeId the id of the event type
* @param cutoffTicks cutoff in ticks,
* @return true, if it could be set
*/
public native boolean setCutoff(long eventTypeId, long cutoffTicks);
/**
* Emit old object sample events.
*
* @param cutoff the cutoff in ticks
* @param emitAll emit all samples in old object queue
*/
public native void emitOldObjectSamples(long cutoff, boolean emitAll);
/**
* Test if a chunk rotation is warranted.
*
* @return if it is time to perform a chunk rotation
*/
public native boolean shouldRotateDisk();
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.IOException;
/**
* Checks if the running VM supports Flight Recorder.
*
* Purpose of this helper class is to detect early and cleanly if the VM has
* support for Flight Recorder, i.e. not throw {@link UnsatisfiedLinkError} in
* unexpected places.
* <p>
* This is needed so a disabled-jfr.jar can be built for non Oracle JDKs.
*/
public final class JVMSupport {
private static final String UNSUPPORTED_VM_MESSAGE = "Flight Recorder is not supported on this VM";
private static final boolean notAvailable = !checkAvailability();
private static boolean checkAvailability() {
// set jfr.unsupported.vm to true to test API on an unsupported VM
try {
if (SecuritySupport.getBooleanProperty("jfr.unsupported.vm")) {
return false;
}
} catch (NoClassDefFoundError cnfe) {
return false;
}
try {
// Will typically throw UnsatisfiedLinkError if
// there is no native implementation
JVM.getJVM().isAvailable();
return true;
} catch (Throwable t) {
return false;
}
}
public static void ensureWithInternalError() {
if (notAvailable) {
throw new InternalError(UNSUPPORTED_VM_MESSAGE);
}
}
public static void ensureWithIOException() throws IOException {
if (notAvailable) {
throw new IOException(UNSUPPORTED_VM_MESSAGE);
}
}
public static void ensureWithIllegalStateException() {
if (notAvailable) {
throw new IllegalStateException(UNSUPPORTED_VM_MESSAGE);
}
}
public static boolean isNotAvailable() {
return notAvailable;
}
public static void tryToInitializeJVM() {
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.lang.reflect.Modifier;
import jdk.jfr.Event;
import jdk.jfr.internal.handlers.EventHandler;
import jdk.jfr.internal.instrument.JDKEvents;
/**
* All upcalls from the JVM should go through this class.
*
*/
// Called by native
final class JVMUpcalls {
/**
* Called by the JVM when a retransform happens on a tagged class
*
* @param traceId
* Id of the class
* @param dummy
* (not used but needed since invoke infrastructure in native
* uses same signature bytesForEagerInstrumentation)
* @param clazz
* class being retransformed
* @param oldBytes
* byte code
* @return byte code to use
* @throws Throwable
*/
static byte[] onRetransform(long traceId, boolean dummy, Class<?> clazz, byte[] oldBytes) throws Throwable {
try {
if (Event.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) {
EventHandler handler = Utils.getHandler(clazz.asSubclass(Event.class));
if (handler == null) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "No event handler found for " + clazz.getName() + ". Ignoring instrumentation request.");
// Probably triggered by some other agent
return oldBytes;
}
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding instrumentation to event class " + clazz.getName() + " using retransform");
EventInstrumentation ei = new EventInstrumentation(clazz.getSuperclass(), oldBytes, traceId);
byte[] bytes = ei.buildInstrumented();
ASMToolkit.logASM(clazz.getName(), bytes);
return bytes;
}
return JDKEvents.retransformCallback(clazz, oldBytes);
} catch (Throwable t) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.WARN, "Unexpected error when adding instrumentation to event class " + clazz.getName());
}
return oldBytes;
}
/**
* Called by the JVM when requested to do an "eager" instrumentation. Would
* normally happen when JVMTI retransform capabilities are not available.
*
* @param traceId
* Id of the class
* @param forceInstrumentation
* add instrumentation regardless if event is enabled or not.
* @param superClazz
* the super class of the class being processed
* @param oldBytes
* byte code
* @return byte code to use
* @throws Throwable
*/
static byte[] bytesForEagerInstrumentation(long traceId, boolean forceInstrumentation, Class<?> superClass, byte[] oldBytes) throws Throwable {
if (JVMSupport.isNotAvailable()) {
return oldBytes;
}
String eventName = "<Unknown>";
try {
EventInstrumentation ei = new EventInstrumentation(superClass, oldBytes, traceId);
eventName = ei.getEventName();
if (!forceInstrumentation) {
// Assume we are recording
MetadataRepository mr = MetadataRepository.getInstance();
// No need to generate bytecode if:
// 1) Event class is disabled, and there is not an external configuration that overrides.
// 2) Event class has @Registered(false)
if (!mr.isEnabled(ei.getEventName()) && !ei.isEnabled() || !ei.isRegistered()) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Skipping instrumentation for event type " + eventName + " since event was disabled on class load");
return oldBytes;
}
}
// Corner case when we are forced to generate bytecode. We can't reference the event
// handler in #isEnabled() before event class has been registered, so we add a
// guard against a null reference.
ei.setGuardHandler(true);
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding " + (forceInstrumentation ? "forced " : "") + "instrumentation for event type " + eventName + " during initial class load");
EventHandlerCreator eh = new EventHandlerCreator(traceId, ei.getSettingInfos(), ei.getFieldInfos());
// Handler class must be loaded before instrumented event class can
// be used
eh.makeEventHandlerClass();
byte[] bytes = ei.buildInstrumented();
ASMToolkit.logASM(ei.getClassName() + "(" + traceId + ")", bytes);
return bytes;
} catch (Throwable t) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.WARN, "Unexpected error when adding instrumentation for event type " + eventName);
return oldBytes;
}
}
/**
* Called by the JVM to create the recorder thread.
*
* @param systemThreadGroup
* the system thread group
*
* @param contextClassLoader
* the context class loader.
*
* @return a new thread
*/
static Thread createRecorderThread(ThreadGroup systemThreadGroup, ClassLoader contextClassLoader) {
return SecuritySupport.createRecorderThread(systemThreadGroup, contextClassLoader);
}
/**
* Called by the JVM to initialize the EventHandlerProxy class.
*
* @return the EventHandlerProxy class
*/
static Class<? extends EventHandler> getEventHandlerProxyClass() {
return EventHandlerProxyCreator.proxyClass;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
public enum LogLevel {
TRACE(1),
DEBUG(2),
INFO(3),
WARN(4),
ERROR(5);
// must be in sync with JVM levels.
final int level;
LogLevel(int level) {
this.level = level;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
/* Mapped against c++ enum in jfrLogTagSet.hpp */
public enum LogTag {
/**
* Covers
* <ul>
* <li>Initialization of Flight Recorder
* <li> recording life cycle (start, stop and dump)
* <li> repository life cycle
* <li>loading of configuration files.
* </ul>
* Target audience: operations
*/
JFR(0),
/**
* Covers general implementation aspects of JFR (for Hotspot developers)
*/
JFR_SYSTEM(1),
/**
* Covers JVM/JDK events (for Hotspot developers)
*/
JFR_SYSTEM_EVENT(2),
/**
* Covers setting for the JVM/JDK (for Hotspot developers)
*/
JFR_SYSTEM_SETTING(3),
/**
* Covers generated bytecode (for Hotspot developers)
*/
JFR_SYSTEM_BYTECODE(4),
/**
* Covers XML parsing (for Hotspot developers)
*/
JFR_SYSTEM_PARSER(5),
/**
* Covers metadata for JVM/JDK (for Hotspot developers)
*/
JFR_SYSTEM_METADATA(6),
/**
* Covers metadata for Java user (for Hotspot developers)
*/
JFR_METADATA(7),
/**
* Covers events (for users of the JDK)
*/
JFR_EVENT(8),
/**
* Covers setting (for users of the JDK)
*/
JFR_SETTING(9),
/**
* Covers usage of jcmd with JFR
*/
JFR_DCMD(10);
/* set from native side */
volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized
final int id;
LogTag(int tagId) {
id = tagId;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.util.function.Supplier;
/**
* JFR logger
*
*/
public final class Logger {
private final static int MAX_SIZE = 10000;
static {
// This will try to initialize the JVM logging system
JVMSupport.tryToInitializeJVM();
}
public static void log(LogTag logTag, LogLevel logLevel, String message) {
if (shouldLog(logTag, logLevel)) {
logInternal(logTag, logLevel, message);
}
}
public static void log(LogTag logTag, LogLevel logLevel, Supplier<String> messageSupplier) {
if (shouldLog(logTag, logLevel)) {
logInternal(logTag, logLevel, messageSupplier.get());
}
}
private static void logInternal(LogTag logTag, LogLevel logLevel, String message) {
if (message == null || message.length() < MAX_SIZE) {
JVM.log(logTag.id, logLevel.level, message);
} else {
JVM.log(logTag.id, logLevel.level, message.substring(0, MAX_SIZE));
}
}
public static boolean shouldLog(LogTag tag, LogLevel level) {
return JVM.shouldLog(level.level);
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import jdk.jfr.EventType;
/**
* Metadata about a chunk
*/
public final class MetadataDescriptor {
static final class Attribute {
final String name;
final String value;
private Attribute(String name, String value) {
this.name = name;
this.value = value;
}
}
static final class Element {
final String name;
final List<Element> elements = new ArrayList<>();
final List<Attribute> attributes = new ArrayList<>();
Element(String name) {
this.name = name;
}
long longValue(String name) {
String v = attribute(name);
if (v != null)
return Long.parseLong(v);
else
throw new IllegalArgumentException(name);
}
String attribute(String name) {
for (Attribute a : attributes) {
if (a.name.equals(name)) {
return a.value;
}
}
return null;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
try {
prettyPrintXML(sb, "", this);
} catch (IOException e) {
// should not happen
}
return sb.toString();
}
long attribute(String name, long defaultValue) {
String text = attribute(name);
if (text == null) {
return defaultValue;
}
return Long.parseLong(text);
}
String attribute(String name, String defaultValue) {
String text = attribute(name);
if (text == null) {
return defaultValue;
}
return text;
}
List<Element> elements(String... names) {
List<Element> filteredElements = new ArrayList<>();
for (String name : names) {
for (Element e : elements) {
if (e.name.equals(name)) {
filteredElements.add(e);
}
}
}
return filteredElements;
}
void add(Element element) {
elements.add(element);
}
void addAttribute(String name, Object value) {
attributes.add(new Attribute(name, String.valueOf(value)));
}
Element newChild(String name) {
Element e = new Element(name);
elements.add(e);
return e;
}
public void addArrayAttribute(Element element, String name, Object value) {
String typeName = value.getClass().getComponentType().getName();
switch (typeName) {
case "int":
int[] ints = (int[]) value;
for (int i = 0; i < ints.length; i++) {
addAttribute(name + "-" + i , ints[i]);
}
break;
case "long":
long[] longs = (long[]) value;
for (int i = 0; i < longs.length; i++) {
addAttribute(name + "-" + i , longs[i]);
}
break;
case "float":
float[] floats = (float[]) value;
for (int i = 0; i < floats.length; i++) {
addAttribute(name + "-" + i , floats[i]);
}
break;
case "double":
double[] doubles = (double[]) value;
for (int i = 0; i < doubles.length; i++) {
addAttribute(name + "-" + i , doubles[i]);
}
break;
case "short":
short[] shorts = (short[]) value;
for (int i = 0; i < shorts.length; i++) {
addAttribute(name + "-" + i , shorts[i]);
}
break;
case "char":
char[] chars = (char[]) value;
for (int i = 0; i < chars.length; i++) {
addAttribute(name + "-" + i , chars[i]);
}
break;
case "byte":
byte[] bytes = (byte[]) value;
for (int i = 0; i < bytes.length; i++) {
addAttribute(name + "-" + i , bytes[i]);
}
break;
case "boolean":
boolean[] booleans = (boolean[]) value;
for (int i = 0; i < booleans.length; i++) {
addAttribute(name + "-" + i , booleans[i]);
}
break;
case "java.lang.String":
String[] strings = (String[]) value;
for (int i = 0; i < strings.length; i++) {
addAttribute(name + "-" + i , strings[i]);
}
break;
default:
throw new InternalError("Array type of " + typeName + " is not supported");
}
}
}
static final String ATTRIBUTE_ID = "id";
static final String ATTRIBUTE_SIMPLE_TYPE = "simpleType";
static final String ATTRIBUTE_GMT_OFFSET = "gmtOffset";
static final String ATTRIBUTE_LOCALE = "locale";
static final String ELEMENT_TYPE = "class";
static final String ELEMENT_SETTING = "setting";
static final String ELEMENT_ANNOTATION = "annotation";
static final String ELEMENT_FIELD = "field";
static final String ATTRIBUTE_SUPER_TYPE = "superType";
static final String ATTRIBUTE_TYPE_ID = "class";
static final String ATTRIBUTE_DIMENSION = "dimension";
static final String ATTRIBUTE_NAME = "name";
static final String ATTRIBUTE_CONSTANT_POOL = "constantPool";
static final String ATTRIBUTE_DEFAULT_VALUE = "defaultValue";
final List<EventType> eventTypes = new ArrayList<>();
final Collection<Type> types = new ArrayList<>();
long gmtOffset;
String locale;
Element root;
// package private
MetadataDescriptor() {
}
private static void prettyPrintXML(Appendable sb, String indent, Element e) throws IOException {
sb.append(indent + "<" + e.name);
for (Attribute a : e.attributes) {
sb.append(" ").append(a.name).append("=\"").append(a.value).append("\"");
}
if (e.elements.size() == 0) {
sb.append("/");
}
sb.append(">\n");
for (Element child : e.elements) {
prettyPrintXML(sb, indent + " ", child);
}
if (e.elements.size() != 0) {
sb.append(indent).append("</").append(e.name).append(">\n");
}
}
public Collection<Type> getTypes() {
return types;
}
public List<EventType> getEventTypes() {
return eventTypes;
}
public int getGMTOffset() {
return (int) gmtOffset;
}
public String getLocale() {
return locale;
}
public static MetadataDescriptor read(DataInput input) throws IOException {
MetadataReader r = new MetadataReader(input);
return r.getDescriptor();
}
static void write(List<Type> types, DataOutput output) throws IOException {
MetadataDescriptor m = new MetadataDescriptor();
m.locale = Locale.getDefault().toString();
m.gmtOffset = TimeZone.getDefault().getRawOffset();
m.types.addAll(types);
MetadataWriter w = new MetadataWriter(m);
w.writeBinary(output);
}
@Override
public String toString() {
return root.toString();
}
}

View File

@@ -0,0 +1,421 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jdk.internal.org.xml.sax.Attributes;
import jdk.internal.org.xml.sax.EntityResolver;
import jdk.internal.org.xml.sax.SAXException;
import jdk.internal.org.xml.sax.helpers.DefaultHandler;
import jdk.internal.util.xml.SAXParser;
import jdk.internal.util.xml.impl.SAXParserImpl;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Enabled;
import jdk.jfr.Experimental;
import jdk.jfr.Label;
import jdk.jfr.Period;
import jdk.jfr.Relational;
import jdk.jfr.StackTrace;
import jdk.jfr.Threshold;
import jdk.jfr.TransitionFrom;
import jdk.jfr.TransitionTo;
import jdk.jfr.Unsigned;
final class MetadataHandler extends DefaultHandler implements EntityResolver {
static class TypeElement {
List<FieldElement> fields = new ArrayList<>();
String name;
String label;
String description;
String category;
String superType;
String period;
boolean thread;
boolean startTime;
boolean stackTrace;
boolean cutoff;
boolean isEvent;
boolean experimental;
boolean valueType;
}
static class FieldElement {
TypeElement referenceType;
String name;
String label;
String description;
String contentType;
String typeName;
String transition;
String relation;
boolean struct;
boolean array;
boolean experimental;
boolean unsigned;
}
static class XmlType {
String name;
String javaType;
String contentType;
boolean unsigned;
}
final Map<String, TypeElement> types = new LinkedHashMap<>(200);
final Map<String, XmlType> xmlTypes = new HashMap<>(20);
final Map<String, List<AnnotationElement>> xmlContentTypes = new HashMap<>(20);
final List<String> relations = new ArrayList<>();
long eventTypeId = 255;
long structTypeId = 33;
FieldElement currentField;
TypeElement currentType;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
switch (qName) {
case "XmlType":
XmlType xmlType = new XmlType();
xmlType.name = attributes.getValue("name");
xmlType.javaType = attributes.getValue("javaType");
xmlType.contentType = attributes.getValue("contentType");
xmlType.unsigned = Boolean.valueOf(attributes.getValue("unsigned"));
xmlTypes.put(xmlType.name, xmlType);
break;
case "Type":
case "Event":
currentType = new TypeElement();
currentType.name = attributes.getValue("name");
currentType.label = attributes.getValue("label");
currentType.description = attributes.getValue("description");
currentType.category = attributes.getValue("category");
currentType.thread = getBoolean(attributes, "thread", false);
currentType.stackTrace = getBoolean(attributes, "stackTrace", false);
currentType.startTime = getBoolean(attributes, "startTime", true);
currentType.period = attributes.getValue("period");
currentType.cutoff = getBoolean(attributes, "cutoff", false);
currentType.experimental = getBoolean(attributes, "experimental", false);
currentType.isEvent = qName.equals("Event");
break;
case "Field":
currentField = new FieldElement();
currentField.struct = getBoolean(attributes, "struct", false);
currentField.array = getBoolean(attributes, "array", false);
currentField.name = attributes.getValue("name");
currentField.label = attributes.getValue("label");
currentField.typeName = attributes.getValue("type");
currentField.description = attributes.getValue("description");
currentField.experimental = getBoolean(attributes, "experimental", false);
currentField.contentType = attributes.getValue("contentType");
currentField.relation = attributes.getValue("relation");
currentField.transition = attributes.getValue("transition");
break;
case "XmlContentType":
String name = attributes.getValue("name");
String annotation = attributes.getValue("annotation");
xmlContentTypes.put(name, createAnnotationElements(annotation));
break;
case "Relation":
String n = attributes.getValue("name");
relations.add(n);
break;
}
}
private List<AnnotationElement> createAnnotationElements(String annotation) throws InternalError {
String[] annotations = annotation.split(",");
List<AnnotationElement> annotationElements = new ArrayList<>();
for (String a : annotations) {
a = a.trim();
int leftParenthesis = a.indexOf("(");
if (leftParenthesis == -1) {
annotationElements.add(new AnnotationElement(createAnnotationClass(a)));
} else {
int rightParenthesis = a.lastIndexOf(")");
if (rightParenthesis == -1) {
throw new InternalError("Expected closing parenthesis for 'XMLContentType'");
}
String value = a.substring(leftParenthesis + 1, rightParenthesis);
String type = a.substring(0, leftParenthesis);
annotationElements.add(new AnnotationElement(createAnnotationClass(type), value));
}
}
return annotationElements;
}
@SuppressWarnings("unchecked")
private Class<? extends Annotation> createAnnotationClass(String type) {
try {
if (!type.startsWith("jdk.jfr.")) {
throw new IllegalStateException("Incorrect type " + type + ". Annotation class must be located in jdk.jfr package.");
}
Class<?> c = Class.forName(type, true, null);
return (Class<? extends Annotation>) c;
} catch (ClassNotFoundException cne) {
throw new IllegalStateException(cne);
}
}
private boolean getBoolean(Attributes attributes, String name, boolean defaultValue) {
String value = attributes.getValue(name);
return value == null ? defaultValue : Boolean.valueOf(value);
}
@Override
public void endElement(String uri, String localName, String qName) {
switch (qName) {
case "Type":
case "Event":
types.put(currentType.name, currentType);
currentType = null;
break;
case "Field":
currentType.fields.add(currentField);
currentField = null;
break;
}
}
public static List<Type> createTypes() throws IOException {
SAXParser parser = new SAXParserImpl();
MetadataHandler t = new MetadataHandler();
try (InputStream is = new BufferedInputStream(SecuritySupport.getResourceAsStream("/jdk/jfr/internal/types/metadata.xml"))) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Parsing metadata.xml");
try {
parser.parse(is, t);
return t.buildTypes();
} catch (Exception e) {
e.printStackTrace();
throw new IOException(e);
}
}
}
private List<Type> buildTypes() {
removeXMLConvenience();
Map<String, Type> typeMap = buildTypeMap();
Map<String, AnnotationElement> relationMap = buildRelationMap(typeMap);
addFields(typeMap, relationMap);
return trimTypes(typeMap);
}
private Map<String, AnnotationElement> buildRelationMap(Map<String, Type> typeMap) {
Map<String, AnnotationElement> relationMap = new HashMap<>();
for (String relation : relations) {
Type relationType = new Type(Type.TYPES_PREFIX + relation, Type.SUPER_TYPE_ANNOTATION, eventTypeId++);
relationType.setAnnotations(Collections.singletonList(new AnnotationElement(Relational.class)));
AnnotationElement ae = PrivateAccess.getInstance().newAnnotation(relationType, Collections.emptyList(), true);
relationMap.put(relation, ae);
typeMap.put(relationType.getName(), relationType);
}
return relationMap;
}
private List<Type> trimTypes(Map<String, Type> lookup) {
List<Type> trimmedTypes = new ArrayList<>(lookup.size());
for (Type t : lookup.values()) {
t.trimFields();
trimmedTypes.add(t);
}
return trimmedTypes;
}
private void addFields(Map<String, Type> lookup, Map<String, AnnotationElement> relationMap) {
for (TypeElement te : types.values()) {
Type type = lookup.get(te.name);
if (te.isEvent) {
boolean periodic = te.period!= null;
TypeLibrary.addImplicitFields(type, periodic, te.startTime && !periodic, te.thread, te.stackTrace && !periodic, te.cutoff);
}
for (FieldElement f : te.fields) {
Type fieldType = Type.getKnownType(f.typeName);
if (fieldType == null) {
fieldType = Objects.requireNonNull(lookup.get(f.referenceType.name));
}
List<AnnotationElement> aes = new ArrayList<>();
if (f.unsigned) {
aes.add(new AnnotationElement(Unsigned.class));
}
if (f.contentType != null) {
aes.addAll(Objects.requireNonNull(xmlContentTypes.get(f.contentType)));
}
if (f.relation != null) {
aes.add(Objects.requireNonNull(relationMap.get(f.relation)));
}
if (f.label != null) {
aes.add(new AnnotationElement(Label.class, f.label));
}
if (f.experimental) {
aes.add(new AnnotationElement(Experimental.class));
}
if (f.description != null) {
aes.add(new AnnotationElement(Description.class, f.description));
}
if ("from".equals(f.transition)) {
aes.add(new AnnotationElement(TransitionFrom.class));
}
if ("to".equals(f.transition)) {
aes.add(new AnnotationElement(TransitionTo.class));
}
boolean constantPool = !f.struct && f.referenceType != null;
type.add(PrivateAccess.getInstance().newValueDescriptor(f.name, fieldType, aes, f.array ? 1 : 0, constantPool, null));
}
}
}
private Map<String, Type> buildTypeMap() {
Map<String, Type> typeMap = new HashMap<>();
for (Type type : Type.getKnownTypes()) {
typeMap.put(type.getName(), type);
}
for (TypeElement t : types.values()) {
List<AnnotationElement> aes = new ArrayList<>();
if (t.category != null) {
aes.add(new AnnotationElement(Category.class, buildCategoryArray(t.category)));
}
if (t.label != null) {
aes.add(new AnnotationElement(Label.class, t.label));
}
if (t.description != null) {
aes.add(new AnnotationElement(Description.class, t.description));
}
if (t.isEvent) {
if (t.period != null) {
aes.add(new AnnotationElement(Period.class, t.period));
} else {
if (t.startTime) {
aes.add(new AnnotationElement(Threshold.class, "0 ns"));
}
if (t.stackTrace) {
aes.add(new AnnotationElement(StackTrace.class, true));
}
}
if (t.cutoff) {
aes.add(new AnnotationElement(Cutoff.class, Cutoff.INIFITY));
}
}
if (t.experimental) {
aes.add(new AnnotationElement(Experimental.class));
}
Type type;
if (t.isEvent) {
aes.add(new AnnotationElement(Enabled.class, false));
type = new PlatformEventType(t.name, eventTypeId++, false, true);
} else {
// Struct types had their own XML-element in the past. To have id assigned in the
// same order as generated .hpp file do some tweaks here.
boolean valueType = t.name.endsWith("StackFrame") || t.valueType;
type = new Type(t.name, null, valueType ? eventTypeId++ : nextTypeId(t.name), false);
}
type.setAnnotations(aes);
typeMap.put(t.name, type);
}
return typeMap;
}
private long nextTypeId(String name) {
if (Type.THREAD.getName().equals(name)) {
return Type.THREAD.getId();
}
if (Type.STRING.getName().equals(name)) {
return Type.STRING.getId();
}
if (Type.CLASS.getName().equals(name)) {
return Type.CLASS.getId();
}
for (Type type : Type.getKnownTypes()) {
if (type.getName().equals(name)) {
return type.getId();
}
}
return structTypeId++;
}
private String[] buildCategoryArray(String category) {
List<String> categories = new ArrayList<>();
StringBuilder sb = new StringBuilder();
for (char c : category.toCharArray()) {
if (c == ',') {
categories.add(sb.toString().trim());
sb.setLength(0);
} else {
sb.append(c);
}
}
categories.add(sb.toString().trim());
return categories.toArray(new String[0]);
}
private void removeXMLConvenience() {
for (TypeElement t : types.values()) {
XmlType xmlType = xmlTypes.get(t.name);
if (xmlType != null && xmlType.javaType != null) {
t.name = xmlType.javaType; // known type, i.e primitive
} else {
if (t.isEvent) {
t.name = Type.EVENT_NAME_PREFIX + t.name;
} else {
t.name = Type.TYPES_PREFIX + t.name;
}
}
}
for (TypeElement t : types.values()) {
for (FieldElement f : t.fields) {
f.referenceType = types.get(f.typeName);
XmlType xmlType = xmlTypes.get(f.typeName);
if (xmlType != null) {
if (xmlType.javaType != null) {
f.typeName = xmlType.javaType;
}
if (xmlType.contentType != null) {
f.contentType = xmlType.contentType;
}
if (xmlType.unsigned) {
f.unsigned = true;
}
}
if (f.struct && f.referenceType != null) {
f.referenceType.valueType = true;
}
}
}
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_CONSTANT_POOL;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_DIMENSION;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_ID;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_NAME;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_SIMPLE_TYPE;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_SUPER_TYPE;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_TYPE_ID;
import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_ANNOTATION;
import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_FIELD;
import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_SETTING;
import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_TYPE;
import java.io.DataInput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jdk.jfr.AnnotationElement;
import jdk.jfr.SettingDescriptor;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.MetadataDescriptor.Element;
import jdk.jfr.internal.consumer.RecordingInput;
/**
* Parses metadata.
*
*/
final class MetadataReader {
private final DataInput input;
private final List<String> pool;
private final MetadataDescriptor descriptor;
private final Map<Long, Type> types = new HashMap<>();
public MetadataReader(DataInput input) throws IOException {
this.input = input;
int size = input.readInt();
((RecordingInput)input).require(size, "Metadata string pool size %d exceeds available data" );
this.pool = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
this.pool.add(input.readUTF());
}
descriptor = new MetadataDescriptor();
Element root = createElement();
Element metadata = root.elements("metadata").get(0);
declareTypes(metadata);
defineTypes(metadata);
annotateTypes(metadata);
buildEvenTypes();
Element time = root.elements("region").get(0);
descriptor.gmtOffset = time.attribute(MetadataDescriptor.ATTRIBUTE_GMT_OFFSET, 1);
descriptor.locale = time.attribute(MetadataDescriptor.ATTRIBUTE_LOCALE, "");
descriptor.root = root;
if (Logger.shouldLog(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE)) {
List<Type> ts = new ArrayList<>(types.values());
Collections.sort(ts, (x,y) -> x.getName().compareTo(y.getName()));
for (Type t : ts) {
t.log("Found", LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE);
}
}
}
private String readString() throws IOException {
return pool.get(readInt());
}
private int readInt() throws IOException {
return input.readInt();
}
private Element createElement() throws IOException {
String name = readString();
Element e = new Element(name);
int attributeCount = readInt();
for (int i = 0; i < attributeCount; i++) {
e.addAttribute(readString(), readString());
}
int childrenCount = readInt();
for (int i = 0; i < childrenCount; i++) {
e.add(createElement());
}
return e;
}
private void annotateTypes(Element metadata) throws IOException {
for (Element typeElement : metadata.elements(ELEMENT_TYPE)) {
Type type = getType(ATTRIBUTE_ID, typeElement);
ArrayList<AnnotationElement> aes = new ArrayList<>();
for (Element annotationElement : typeElement.elements(ELEMENT_ANNOTATION)) {
aes.add(makeAnnotation(annotationElement));
}
aes.trimToSize();
type.setAnnotations(aes);
int index = 0;
if (type instanceof PlatformEventType) {
List<SettingDescriptor> settings = ((PlatformEventType) type).getAllSettings();
for (Element settingElement : typeElement.elements(ELEMENT_SETTING)) {
ArrayList<AnnotationElement> annotations = new ArrayList<>();
for (Element annotationElement : settingElement.elements(ELEMENT_ANNOTATION)) {
annotations.add(makeAnnotation(annotationElement));
}
annotations.trimToSize();
PrivateAccess.getInstance().setAnnotations(settings.get(index), annotations);
index++;
}
}
index = 0;
List<ValueDescriptor> fields = type.getFields();
for (Element fieldElement : typeElement.elements(ELEMENT_FIELD)) {
ArrayList<AnnotationElement> annotations = new ArrayList<>();
for (Element annotationElement : fieldElement.elements(ELEMENT_ANNOTATION)) {
annotations.add(makeAnnotation(annotationElement));
}
annotations.trimToSize();
PrivateAccess.getInstance().setAnnotations(fields.get(index), annotations);
index++;
}
}
}
private AnnotationElement makeAnnotation(Element annotationElement) throws IOException {
Type annotationType = getType(ATTRIBUTE_TYPE_ID, annotationElement);
List<Object> values = new ArrayList<>();
for (ValueDescriptor v : annotationType.getFields()) {
if (v.isArray()) {
List<Object> list = new ArrayList<>();
int index = 0;
while (true) {
String text = annotationElement.attribute(v.getName() + "-" + index);
if (text == null) {
break;
}
list.add(objectify(v.getTypeName(), text));
index++;
}
Object object = Utils.makePrimitiveArray(v.getTypeName(), list);
if (object == null) {
throw new IOException("Unsupported type " + list + " in array");
}
values.add(object);
} else {
String text = annotationElement.attribute(v.getName());
values.add(objectify(v.getTypeName(), text));
}
}
return PrivateAccess.getInstance().newAnnotation(annotationType, values, false);
}
private Object objectify(String typeName, String text) throws IOException {
try {
switch (typeName) {
case "int":
return Integer.valueOf(text);
case "long":
return Long.valueOf(text);
case "double":
return Double.valueOf(text);
case "float":
return Float.valueOf(text);
case "short":
return Short.valueOf(text);
case "char":
if (text.length() != 1) {
throw new IOException("Unexpected size of char");
}
return text.charAt(0);
case "byte":
return Byte.valueOf(text);
case "boolean":
return Boolean.valueOf(text);
case "java.lang.String":
return text;
}
} catch (IllegalArgumentException iae) {
throw new IOException("Could not parse text representation of " + typeName);
}
throw new IOException("Unsupported type for annotation " + typeName);
}
private Type getType(String attribute, Element element) {
long id = element.longValue(attribute);
Type type = types.get(id);
if (type == null) {
String name = element.attribute("type");
throw new IllegalStateException("Type '" + id + "' is not defined for " + name);
}
return type;
}
private void buildEvenTypes() {
for (Type type : descriptor.types) {
if (type instanceof PlatformEventType) {
descriptor.eventTypes.add(PrivateAccess.getInstance().newEventType((PlatformEventType) type));
}
}
}
private void defineTypes(Element metadata) {
for (Element typeElement : metadata.elements(ELEMENT_TYPE)) {
long id = typeElement.attribute(ATTRIBUTE_ID, -1);
Type t = types.get(id);
for (Element fieldElement : typeElement.elements(ELEMENT_SETTING)) {
String name = fieldElement.attribute(ATTRIBUTE_NAME);
String defaultValue = fieldElement.attribute(ATTRIBUTE_NAME);
Type settingType = getType(ATTRIBUTE_TYPE_ID, fieldElement);
PlatformEventType eventType = (PlatformEventType) t;
eventType.add(PrivateAccess.getInstance().newSettingDescriptor(settingType, name, defaultValue, new ArrayList<>(2)));
}
for (Element fieldElement : typeElement.elements(ELEMENT_FIELD)) {
String name = fieldElement.attribute(ATTRIBUTE_NAME);
Type fieldType = getType(ATTRIBUTE_TYPE_ID, fieldElement);
long dimension = fieldElement.attribute(ATTRIBUTE_DIMENSION, 0);
boolean constantPool = fieldElement.attribute(ATTRIBUTE_CONSTANT_POOL) != null;
// Add annotation later, because they may refer to undefined
// types at this stage
t.add(PrivateAccess.getInstance().newValueDescriptor(name, fieldType, new ArrayList<>(), (int) dimension, constantPool, null));
}
t.trimFields();
}
}
private void declareTypes(Element metadata) {
for (Element typeElement : metadata.elements(ELEMENT_TYPE)) {
String typeName = typeElement.attribute(ATTRIBUTE_NAME);
String superType = typeElement.attribute(ATTRIBUTE_SUPER_TYPE);
boolean simpleType = typeElement.attribute(ATTRIBUTE_SIMPLE_TYPE) != null;
long id = typeElement.attribute(ATTRIBUTE_ID, -1);
Type t;
if (Type.SUPER_TYPE_EVENT.equals(superType)) {
t = new PlatformEventType(typeName, id, false, false);
} else {
t = new Type(typeName, superType, id, false, simpleType);
}
types.put(id, t);
descriptor.types.add(t);
}
}
public MetadataDescriptor getDescriptor() {
return descriptor;
}
}

View File

@@ -0,0 +1,277 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import static jdk.jfr.internal.LogLevel.DEBUG;
import static jdk.jfr.internal.LogTag.JFR_SYSTEM;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Event;
import jdk.jfr.EventType;
import jdk.jfr.Period;
import jdk.jfr.StackTrace;
import jdk.jfr.Threshold;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.RequestEngine.RequestHook;
import jdk.jfr.internal.handlers.EventHandler;
public final class MetadataRepository {
private static final JVM jvm = JVM.getJVM();
private static final MetadataRepository instace = new MetadataRepository();
private final List<EventType> nativeEventTypes = new ArrayList<>(100);
private final List<EventControl> nativeControls = new ArrayList<EventControl>(100);
private final TypeLibrary typeLibrary = TypeLibrary.getInstance();
private final SettingsManager settingsManager = new SettingsManager();
private boolean staleMetadata = true;
private boolean unregistered;
private long lastUnloaded = -1;
public MetadataRepository() {
initializeJVMEventTypes();
}
private void initializeJVMEventTypes() {
List<RequestHook> requestHooks = new ArrayList<>();
for (Type type : typeLibrary.getTypes()) {
if (type instanceof PlatformEventType) {
PlatformEventType pEventType = (PlatformEventType) type;
EventType eventType = PrivateAccess.getInstance().newEventType(pEventType);
pEventType.setHasDuration(eventType.getAnnotation(Threshold.class) != null);
pEventType.setHasStackTrace(eventType.getAnnotation(StackTrace.class) != null);
pEventType.setHasCutoff(eventType.getAnnotation(Cutoff.class) != null);
pEventType.setHasPeriod(eventType.getAnnotation(Period.class) != null);
// Must add hook before EventControl is created as it removes
// annotations, such as Period and Threshold.
if (pEventType.hasPeriod()) {
pEventType.setEventHook(true);
if (!(Type.EVENT_NAME_PREFIX + "ExecutionSample").equals(type.getName())) {
requestHooks.add(new RequestHook(pEventType));
}
}
nativeControls.add(new EventControl(pEventType));
nativeEventTypes.add(eventType);
}
}
RequestEngine.addHooks(requestHooks);
}
public static MetadataRepository getInstance() {
return instace;
}
public synchronized List<EventType> getRegisteredEventTypes() {
List<EventHandler> handlers = getEventHandlers();
List<EventType> eventTypes = new ArrayList<>(handlers.size() + nativeEventTypes.size());
for (EventHandler h : handlers) {
if (h.isRegistered()) {
eventTypes.add(h.getEventType());
}
}
eventTypes.addAll(nativeEventTypes);
return eventTypes;
}
public synchronized EventType getEventType(Class<? extends Event> eventClass) {
EventHandler h = getHandler(eventClass);
if (h != null && h.isRegistered()) {
return h.getEventType();
}
throw new IllegalStateException("Event class " + eventClass.getName() + " is not registered");
}
public synchronized void unregister(Class<? extends Event> eventClass) {
Utils.checkRegisterPermission();
EventHandler handler = getHandler(eventClass);
if (handler != null) {
handler.setRegistered(false);
}
// never registered, ignore call
}
public synchronized EventType register(Class<? extends Event> eventClass) {
return register(eventClass, Collections.emptyList(), Collections.emptyList());
}
public synchronized EventType register(Class<? extends Event> eventClass, List<AnnotationElement> dynamicAnnotations, List<ValueDescriptor> dynamicFields) {
Utils.checkRegisterPermission();
EventHandler handler = getHandler(eventClass);
if (handler == null) {
handler = makeHandler(eventClass, dynamicAnnotations, dynamicFields);
}
handler.setRegistered(true);
typeLibrary.addType(handler.getPlatformEventType());
if (jvm.isRecording()) {
storeDescriptorInJVM(); // needed for emergency dump
settingsManager.setEventControl(handler.getEventControl());
settingsManager.updateRetransform(Collections.singletonList((eventClass)));
} else {
setStaleMetadata();
}
return handler.getEventType();
}
private EventHandler getHandler(Class<? extends Event> eventClass) {
Utils.ensureValidEventSubclass(eventClass);
SecuritySupport.makeVisibleToJFR(eventClass);
Utils.ensureInitialized(eventClass);
return Utils.getHandler(eventClass);
}
private EventHandler makeHandler(Class<? extends Event> eventClass, List<AnnotationElement> dynamicAnnotations, List<ValueDescriptor> dynamicFields) throws InternalError {
SecuritySupport.addHandlerExport(eventClass);
PlatformEventType pEventType = (PlatformEventType) TypeLibrary.createType(eventClass, dynamicAnnotations, dynamicFields);
EventType eventType = PrivateAccess.getInstance().newEventType(pEventType);
EventControl ec = new EventControl(pEventType, eventClass);
Class<? extends EventHandler> handlerClass = null;
try {
String eventHandlerName = EventHandlerCreator.makeEventHandlerName(eventType.getId());
handlerClass = Class.forName(eventHandlerName, false, Event.class.getClassLoader()).asSubclass(EventHandler.class);
// Created eagerly on class load, tag as instrumented
pEventType.setInstrumented();
Logger.log(JFR_SYSTEM, DEBUG, "Found existing event handler for " + eventType.getName());
} catch (ClassNotFoundException cne) {
EventHandlerCreator ehc = new EventHandlerCreator(eventType.getId(), ec.getSettingInfos(), eventType, eventClass);
handlerClass = ehc.makeEventHandlerClass();
Logger.log(LogTag.JFR_SYSTEM, DEBUG, "Created event handler for " + eventType.getName());
}
EventHandler handler = EventHandlerCreator.instantiateEventHandler(handlerClass, true, eventType, ec);
Utils.setHandler(eventClass, handler);
return handler;
}
public synchronized void setSettings(List<Map<String, String>> list) {
settingsManager.setSettings(list);
}
synchronized void disableEvents() {
for (EventControl c : getEventControls()) {
c.disable();
}
}
public synchronized List<EventControl> getEventControls() {
List<EventControl> controls = new ArrayList<>();
controls.addAll(nativeControls);
for (EventHandler eh : getEventHandlers()) {
controls.add(eh.getEventControl());
}
return controls;
}
private void storeDescriptorInJVM() throws InternalError {
jvm.storeMetadataDescriptor(getBinaryRepresentation());
staleMetadata = false;
}
private static List<EventHandler> getEventHandlers() {
List<Class<? extends Event>> allEventClasses = jvm.getAllEventClasses();
List<EventHandler> eventHandlers = new ArrayList<>(allEventClasses.size());
for (Class<? extends Event> clazz : allEventClasses) {
EventHandler eh = Utils.getHandler(clazz);
if (eh != null) {
eventHandlers.add(eh);
}
}
return eventHandlers;
}
private byte[] getBinaryRepresentation() {
ByteArrayOutputStream baos = new ByteArrayOutputStream(40000);
DataOutputStream daos = new DataOutputStream(baos);
try {
List<Type> types = typeLibrary.getTypes();
Collections.sort(types);
MetadataDescriptor.write(types, daos);
daos.flush();
return baos.toByteArray();
} catch (IOException e) {
// should not happen
throw new InternalError(e);
}
}
synchronized boolean isEnabled(String eventName) {
return settingsManager.isEnabled(eventName);
}
synchronized void setStaleMetadata() {
staleMetadata = true;
}
// Lock around setOutput ensures that other threads dosn't
// emit event after setOutput and unregister the event class, before a call
// to storeDescriptorInJVM
synchronized void setOutput(String filename) {
jvm.setOutput(filename);
unregisterUnloaded();
if (unregistered) {
staleMetadata = typeLibrary.clearUnregistered();
unregistered = false;
}
if (staleMetadata) {
storeDescriptorInJVM();
}
}
private void unregisterUnloaded() {
long unloaded = jvm.getUnloadedEventClassCount();
if (this.lastUnloaded != unloaded) {
this.lastUnloaded = unloaded;
List<Class<? extends Event>> eventClasses = jvm.getAllEventClasses();
HashSet<Long> knownIds = new HashSet<>(eventClasses.size());
for (Class<? extends Event> ec: eventClasses) {
knownIds.add(Type.getTypeId(ec));
}
for (Type type : typeLibrary.getTypes()) {
if (type instanceof PlatformEventType) {
if (!knownIds.contains(type.getId())) {
PlatformEventType pe = (PlatformEventType) type;
if (!pe.isJVM()) {
pe.setRegistered(false);
}
}
}
}
}
}
synchronized public void setUnregistered() {
unregistered = true;
}
}

View File

@@ -0,0 +1,225 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_CONSTANT_POOL;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_DEFAULT_VALUE;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_DIMENSION;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_GMT_OFFSET;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_ID;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_LOCALE;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_NAME;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_SIMPLE_TYPE;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_SUPER_TYPE;
import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_TYPE_ID;
import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_ANNOTATION;
import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_FIELD;
import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_SETTING;
import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_TYPE;
import java.io.DataOutput;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import jdk.jfr.AnnotationElement;
import jdk.jfr.SettingDescriptor;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.MetadataDescriptor.Attribute;
import jdk.jfr.internal.MetadataDescriptor.Element;
import jdk.jfr.internal.consumer.RecordingInput;
/**
* Class responsible for converting a list of types into a format that can be
* parsed by a client.
*
*/
final class MetadataWriter {
private final Element metadata = new Element("metadata");
private final Element root = new Element("root");
public MetadataWriter(MetadataDescriptor descriptor) {
descriptor.getTypes().forEach(type -> makeTypeElement(metadata, type));
root.add(metadata);
Element region = new Element("region");
region.addAttribute(ATTRIBUTE_LOCALE, descriptor.locale);
region.addAttribute(ATTRIBUTE_GMT_OFFSET, descriptor.gmtOffset);
root.add(region);
}
public void writeBinary(DataOutput output) throws IOException {
Set<String> stringPool = new HashSet<>(1000);
// Possible improvement, sort string by how often they occur.
// and assign low number to the most frequently used.
buildStringPool(root, stringPool);
HashMap<String, Integer> lookup = new LinkedHashMap<>(stringPool.size());
int index = 0;
int poolSize = stringPool.size();
writeInt(output, poolSize);
for (String s : stringPool) {
lookup.put(s, index);
writeString(output, s);
index++;
}
write(output, root, lookup);
}
private void writeString(DataOutput out, String s) throws IOException {
if (s == null ) {
out.writeByte(RecordingInput.STRING_ENCODING_NULL);
return;
}
out.writeByte(RecordingInput.STRING_ENCODING_CHAR_ARRAY); // encoding UTF-16
int length = s.length();
writeInt(out, length);
for (int i = 0; i < length; i++) {
writeInt(out, s.charAt(i));
}
}
private void writeInt(DataOutput out, int v) throws IOException {
long s = v & 0xffffffffL;
if (s < 1 << 7) {
out.write((byte) (s));
return;
}
out.write((byte) (s | 0x80)); // first byte written
s >>= 7;
if (s < 1 << 7) {
out.write((byte) (s));
return;
}
out.write((byte) (s | 0x80)); // second byte written
s >>= 7;
if (s < 1 << 7) {
out.write((byte) (s));
return;
}
out.write((byte) (s | 0x80)); // third byte written
s >>= 7;
if (s < 1 << 7) {
out.write((byte) (s));
return;
}
s >>= 7;
out.write((byte) (s));// fourth byte written
}
private void buildStringPool(Element element, Set<String> pool) {
pool.add(element.name);
for (Attribute a : element.attributes) {
pool.add(a.name);
pool.add(a.value);
}
for (Element child : element.elements) {
buildStringPool(child, pool);
}
}
private void write(DataOutput output,Element element, HashMap<String, Integer> lookup) throws IOException {
writeInt(output, lookup.get(element.name));
writeInt(output, element.attributes.size());
for (Attribute a : element.attributes) {
writeInt(output, lookup.get(a.name));
writeInt(output, lookup.get(a.value));
}
writeInt(output, element.elements.size());
for (Element child : element.elements) {
write(output, child, lookup);
}
}
private void makeTypeElement(Element root, Type type) {
Element element = root.newChild(ELEMENT_TYPE);
element.addAttribute(ATTRIBUTE_NAME, type.getName());
String superType = type.getSuperType();
if (superType != null) {
element.addAttribute(ATTRIBUTE_SUPER_TYPE, superType);
}
if (type.isSimpleType()) {
element.addAttribute(ATTRIBUTE_SIMPLE_TYPE, true);
}
element.addAttribute(ATTRIBUTE_ID, type.getId());
if (type instanceof PlatformEventType) {
for (SettingDescriptor v : ((PlatformEventType)type).getSettings()) {
makeSettingElement(element, v);
}
}
for (ValueDescriptor v : type.getFields()) {
makeFieldElement(element, v);
}
for (AnnotationElement a : type.getAnnotationElements()) {
makeAnnotation(element, a);
}
}
private void makeSettingElement(Element typeElement, SettingDescriptor s) {
Element element = typeElement.newChild(ELEMENT_SETTING);
element.addAttribute(ATTRIBUTE_NAME, s.getName());
element.addAttribute(ATTRIBUTE_TYPE_ID, s.getTypeId());
element.addAttribute(ATTRIBUTE_DEFAULT_VALUE, s.getDefaultValue());
for (AnnotationElement a : s.getAnnotationElements()) {
makeAnnotation(element, a);
}
}
private void makeFieldElement(Element typeElement, ValueDescriptor v) {
Element element = typeElement.newChild(ELEMENT_FIELD);
element.addAttribute(ATTRIBUTE_NAME, v.getName());
element.addAttribute(ATTRIBUTE_TYPE_ID, v.getTypeId());
if (v.isArray()) {
element.addAttribute(ATTRIBUTE_DIMENSION, 1);
}
if (PrivateAccess.getInstance().isConstantPool(v)) {
element.addAttribute(ATTRIBUTE_CONSTANT_POOL, true);
}
for (AnnotationElement a : v.getAnnotationElements()) {
makeAnnotation(element, a);
}
}
private void makeAnnotation(Element entity, AnnotationElement annotation) {
Element element = entity.newChild(ELEMENT_ANNOTATION);
element.addAttribute(ATTRIBUTE_TYPE_ID, annotation.getTypeId());
List<Object> values = annotation.getValues();
int index = 0;
for (ValueDescriptor v : annotation.getValueDescriptors()) {
Object value = values.get(index++);
if (v.isArray()) {
element.addArrayAttribute(element, v.getName(), value);
} else {
element.addAttribute(v.getName(), value);
}
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jdk.jfr.Enabled;
import jdk.jfr.RecordingState;
import jdk.jfr.internal.settings.CutoffSetting;
import jdk.jfr.internal.test.WhiteBox;
// The Old Object event could have been implemented as a periodic event, but
// due to chunk rotations and how settings are calculated when multiple recordings
// are running at the same time, it would lead to unacceptable overhead.
//
// Instead, the event is only emitted before a recording stops and
// if that recording has the event enabled.
//
// This requires special handling and the purpose of this class is to provide that
//
public final class OldObjectSample {
private static final String EVENT_NAME = Type.EVENT_NAME_PREFIX + "OldObjectSample";
private static final String OLD_OBJECT_CUTOFF = EVENT_NAME + "#" + Cutoff.NAME;
private static final String OLD_OBJECT_ENABLED = EVENT_NAME + "#" + Enabled.NAME;
// Emit if old object is enabled in recording with cutoff for that recording
public static void emit(PlatformRecording recording) {
if (isEnabled(recording)) {
long nanos = CutoffSetting.parseValueSafe(recording.getSettings().get(OLD_OBJECT_CUTOFF));
long ticks = Utils.nanosToTicks(nanos);
JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples());
}
}
// Emit if old object is enabled for at least one recording, and use the largest
// cutoff for an enabled recording
public static void emit(List<PlatformRecording> recordings, Boolean pathToGcRoots) {
boolean enabled = false;
long cutoffNanos = Boolean.TRUE.equals(pathToGcRoots) ? Long.MAX_VALUE : 0L;
for (PlatformRecording r : recordings) {
if (r.getState() == RecordingState.RUNNING) {
if (isEnabled(r)) {
enabled = true;
long c = CutoffSetting.parseValueSafe(r.getSettings().get(OLD_OBJECT_CUTOFF));
cutoffNanos = Math.max(c, cutoffNanos);
}
}
}
if (enabled) {
long ticks = Utils.nanosToTicks(cutoffNanos);
JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples());
}
}
public static void updateSettingPathToGcRoots(Map<String, String> s, Boolean pathToGcRoots) {
if (pathToGcRoots != null) {
s.put(OLD_OBJECT_CUTOFF, pathToGcRoots ? "infinity" : "0 ns");
}
}
public static Map<String, String> createSettingsForSnapshot(PlatformRecording recording, Boolean pathToGcRoots) {
Map<String, String> settings = new HashMap<>(recording.getSettings());
updateSettingPathToGcRoots(settings, pathToGcRoots);
return settings;
}
private static boolean isEnabled(PlatformRecording r) {
Map<String, String> settings = r.getSettings();
String s = settings.get(OLD_OBJECT_ENABLED);
return "true".equals(s);
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import jdk.jfr.internal.SecuritySupport.SafePath;
import sun.misc.Unsafe;
/**
* Options that control Flight Recorder.
*
* Can be set using JFR.configure
*
*/
public final class Options {
private final static JVM jvm = JVM.getJVM();
private final static long WAIT_INTERVAL = 1000; // ms;
private final static long MIN_MAX_CHUNKSIZE = 1024 * 1024;
private static final long DEFAULT_GLOBAL_BUFFER_COUNT = 20;
private static final long DEFAULT_GLOBAL_BUFFER_SIZE = 524288;
private static final long DEFAULT_MEMORY_SIZE = DEFAULT_GLOBAL_BUFFER_COUNT * DEFAULT_GLOBAL_BUFFER_SIZE;
private static long DEFAULT_THREAD_BUFFER_SIZE;
private static final int DEFAULT_STACK_DEPTH = 64;
private static final boolean DEFAULT_SAMPLE_THREADS = true;
private static final long DEFAULT_MAX_CHUNK_SIZE = 12 * 1024 * 1024;
private static final SafePath DEFAULT_DUMP_PATH = SecuritySupport.USER_HOME;
private static long memorySize;
private static long globalBufferSize;
private static long globalBufferCount;
private static long threadBufferSize;
private static int stackDepth;
private static boolean sampleThreads;
private static long maxChunkSize;
private static SafePath dumpPath;
static {
final long pageSize = Unsafe.getUnsafe().pageSize();
DEFAULT_THREAD_BUFFER_SIZE = pageSize > 8 * 1024 ? pageSize : 8 * 1024;
reset();
}
public static synchronized void setMaxChunkSize(long max) {
if (max < MIN_MAX_CHUNKSIZE) {
throw new IllegalArgumentException("Max chunk size must be at least " + MIN_MAX_CHUNKSIZE);
}
jvm.setFileNotification(max);
maxChunkSize = max;
}
public static synchronized long getMaxChunkSize() {
return maxChunkSize;
}
public static synchronized void setMemorySize(long memSize) {
jvm.setMemorySize(memSize);
memorySize = memSize;
}
public static synchronized long getMemorySize() {
return memorySize;
}
public static synchronized void setThreadBufferSize(long threadBufSize) {
jvm.setThreadBufferSize(threadBufSize);
threadBufferSize = threadBufSize;
}
public static synchronized long getThreadBufferSize() {
return threadBufferSize;
}
public static synchronized long getGlobalBufferSize() {
return globalBufferSize;
}
public static synchronized void setGlobalBufferCount(long globalBufCount) {
jvm.setGlobalBufferCount(globalBufCount);
globalBufferCount = globalBufCount;
}
public static synchronized long getGlobalBufferCount() {
return globalBufferCount;
}
public static synchronized void setGlobalBufferSize(long globalBufsize) {
jvm.setGlobalBufferSize(globalBufsize);
globalBufferSize = globalBufsize;
}
public static synchronized void setDumpPath(SafePath path) {
dumpPath = path;
}
public static synchronized SafePath getDumpPath() {
return dumpPath;
}
public static synchronized void setStackDepth(Integer stackTraceDepth) {
jvm.setStackDepth(stackTraceDepth);
stackDepth = stackTraceDepth;
}
public static synchronized int getStackDepth() {
return stackDepth;
}
public static synchronized void setSampleThreads(Boolean sample) {
jvm.setSampleThreads(sample);
sampleThreads = sample;
}
public static synchronized boolean getSampleThreads() {
return sampleThreads;
}
private static synchronized void reset() {
setMaxChunkSize(DEFAULT_MAX_CHUNK_SIZE);
setMemorySize(DEFAULT_MEMORY_SIZE);
setGlobalBufferSize(DEFAULT_GLOBAL_BUFFER_SIZE);
setGlobalBufferCount(DEFAULT_GLOBAL_BUFFER_COUNT);
setDumpPath(DEFAULT_DUMP_PATH);
setSampleThreads(DEFAULT_SAMPLE_THREADS);
setStackDepth(DEFAULT_STACK_DEPTH);
setThreadBufferSize(DEFAULT_THREAD_BUFFER_SIZE);
}
static synchronized long getWaitInterval() {
return WAIT_INTERVAL;
}
static void ensureInitialized() {
// trigger clinit which will setup JVM defaults.
}
}

View File

@@ -0,0 +1,281 @@
/*
* Copyright (c) 2017, 2018, 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 jdk.jfr.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import jdk.jfr.SettingDescriptor;
/**
* Implementation of event type.
*
* To avoid memory leaks, this class must not hold strong reference to an event
* class or a setting class
*/
public final class PlatformEventType extends Type {
private final boolean isJVM;
private final boolean isJDK;
private final boolean isMethodSampling;
private final List<SettingDescriptor> settings = new ArrayList<>(5);
private final boolean dynamicSettings;
private final int stackTraceOffset;
// default values
private boolean enabled = false;
private boolean stackTraceEnabled = true;
private long thresholdTicks = 0;
private long period = 0;
private boolean hasHook;
private boolean beginChunk;
private boolean endChunk;
private boolean hasStackTrace = true;
private boolean hasDuration = true;
private boolean hasPeriod = true;
private boolean hasCutoff = false;
private boolean isInstrumented;
private boolean markForInstrumentation;
private boolean registered = true;
private boolean commitable = enabled && registered;
// package private
PlatformEventType(String name, long id, boolean isJDK, boolean dynamicSettings) {
super(name, Type.SUPER_TYPE_EVENT, id);
this.dynamicSettings = dynamicSettings;
this.isJVM = Type.isDefinedByJVM(id);
this.isMethodSampling = name.equals(Type.EVENT_NAME_PREFIX + "ExecutionSample") || name.equals(Type.EVENT_NAME_PREFIX + "NativeMethodSample");
this.isJDK = isJDK;
this.stackTraceOffset = stackTraceOffset(name, isJDK);
}
private static int stackTraceOffset(String name, boolean isJDK) {
if (isJDK) {
if (name.equals(Type.EVENT_NAME_PREFIX + "JavaExceptionThrow")) {
return 5;
}
if (name.equals(Type.EVENT_NAME_PREFIX + "JavaErrorThrow")) {
return 5;
}
}
return 4;
}
public void add(SettingDescriptor settingDescriptor) {
Objects.requireNonNull(settingDescriptor);
settings.add(settingDescriptor);
}
public List<SettingDescriptor> getSettings() {
if (dynamicSettings) {
List<SettingDescriptor> list = new ArrayList<>(settings.size());
for (SettingDescriptor s : settings) {
if (Utils.isSettingVisible(s.getTypeId(), hasHook)) {
list.add(s);
}
}
return list;
}
return settings;
}
public List<SettingDescriptor> getAllSettings() {
return settings;
}
public void setHasStackTrace(boolean hasStackTrace) {
this.hasStackTrace = hasStackTrace;
}
public void setHasDuration(boolean hasDuration) {
this.hasDuration = hasDuration;
}
public void setHasCutoff(boolean hasCutoff) {
this.hasCutoff = hasCutoff;
}
public void setCutoff(long cutoffNanos) {
if (isJVM) {
long cutoffTicks = Utils.nanosToTicks(cutoffNanos);
JVM.getJVM().setCutoff(getId(), cutoffTicks);
}
}
public void setHasPeriod(boolean hasPeriod) {
this.hasPeriod = hasPeriod;
}
public boolean hasStackTrace() {
return this.hasStackTrace;
}
public boolean hasDuration() {
return this.hasDuration;
}
public boolean hasPeriod() {
return this.hasPeriod;
}
public boolean hasCutoff() {
return this.hasCutoff;
}
public boolean isEnabled() {
return enabled;
}
public boolean isJVM() {
return isJVM;
}
public boolean isJDK() {
return isJDK;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
updateCommitable();
if (isJVM) {
if (isMethodSampling) {
long p = enabled ? period : 0;
JVM.getJVM().setMethodSamplingInterval(getId(), p);
} else {
JVM.getJVM().setEnabled(getId(), enabled);
}
}
}
public void setPeriod(long periodMillis, boolean beginChunk, boolean endChunk) {
if (isMethodSampling) {
long p = enabled ? periodMillis : 0;
JVM.getJVM().setMethodSamplingInterval(getId(), p);
}
this.beginChunk = beginChunk;
this.endChunk = endChunk;
this.period = periodMillis;
}
public void setStackTraceEnabled(boolean stackTraceEnabled) {
this.stackTraceEnabled = stackTraceEnabled;
if (isJVM) {
JVM.getJVM().setStackTraceEnabled(getId(), stackTraceEnabled);
}
}
public void setThreshold(long thresholdNanos) {
this.thresholdTicks = Utils.nanosToTicks(thresholdNanos);
if (isJVM) {
JVM.getJVM().setThreshold(getId(), thresholdTicks);
}
}
public boolean isEveryChunk() {
return period == 0;
}
public boolean getStackTraceEnabled() {
return stackTraceEnabled;
}
public long getThresholdTicks() {
return thresholdTicks;
}
public long getPeriod() {
return period;
}
public boolean hasEventHook() {
return hasHook;
}
public void setEventHook(boolean hasHook) {
this.hasHook = hasHook;
}
public boolean isBeginChunk() {
return beginChunk;
}
public boolean isEndChunk() {
return endChunk;
}
public boolean isInstrumented() {
return isInstrumented;
}
public void setInstrumented() {
isInstrumented = true;
}
public void markForInstrumentation(boolean markForInstrumentation) {
this.markForInstrumentation = markForInstrumentation;
}
public boolean isMarkedForInstrumentation() {
return markForInstrumentation;
}
public boolean setRegistered(boolean registered) {
if (this.registered != registered) {
this.registered = registered;
updateCommitable();
LogTag logTag = isJVM() || isJDK() ? LogTag.JFR_SYSTEM_EVENT : LogTag.JFR_EVENT;
if (registered) {
Logger.log(logTag, LogLevel.INFO, "Registered " + getLogName());
} else {
Logger.log(logTag, LogLevel.INFO, "Unregistered " + getLogName());
}
if (!registered) {
MetadataRepository.getInstance().setUnregistered();
}
return true;
}
return false;
}
private void updateCommitable() {
this.commitable = enabled && registered;
}
public final boolean isRegistered() {
return registered;
}
// Efficient check of enabled && registered
public boolean isCommitable() {
return commitable;
}
public int getStackTraceOffset() {
return stackTraceOffset;
}
}

View File

@@ -0,0 +1,554 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import static jdk.jfr.internal.LogLevel.INFO;
import static jdk.jfr.internal.LogLevel.TRACE;
import static jdk.jfr.internal.LogLevel.WARN;
import static jdk.jfr.internal.LogTag.JFR;
import static jdk.jfr.internal.LogTag.JFR_SYSTEM;
import java.io.IOException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import jdk.jfr.EventType;
import jdk.jfr.FlightRecorder;
import jdk.jfr.FlightRecorderListener;
import jdk.jfr.Recording;
import jdk.jfr.RecordingState;
import jdk.jfr.events.ActiveRecordingEvent;
import jdk.jfr.events.ActiveSettingEvent;
import jdk.jfr.internal.SecuritySupport.SecureRecorderListener;
import jdk.jfr.internal.instrument.JDKEvents;
public final class PlatformRecorder {
private final List<PlatformRecording> recordings = new ArrayList<>();
private final static List<SecureRecorderListener> changeListeners = new ArrayList<>();
private final Repository repository;
private final Timer timer;
private final static JVM jvm = JVM.getJVM();
private final EventType activeRecordingEvent;
private final EventType activeSettingEvent;
private final Thread shutdownHook;
private long recordingCounter = 0;
private RepositoryChunk currentChunk;
public PlatformRecorder() throws Exception {
repository = Repository.getRepository();
Logger.log(JFR_SYSTEM, INFO, "Initialized disk repository");
repository.ensureRepository();
jvm.createNativeJFR();
Logger.log(JFR_SYSTEM, INFO, "Created native");
JDKEvents.initialize();
Logger.log(JFR_SYSTEM, INFO, "Registered JDK events");
JDKEvents.addInstrumentation();
startDiskMonitor();
SecuritySupport.registerEvent(ActiveRecordingEvent.class);
activeRecordingEvent = EventType.getEventType(ActiveRecordingEvent.class);
SecuritySupport.registerEvent(ActiveSettingEvent.class);
activeSettingEvent = EventType.getEventType(ActiveSettingEvent.class);
shutdownHook = SecuritySupport.createThreadWitNoPermissions("JFR: Shutdown Hook", new ShutdownHook(this));
SecuritySupport.setUncaughtExceptionHandler(shutdownHook, new ShutdownHook.ExceptionHandler());
SecuritySupport.registerShutdownHook(shutdownHook);
timer = createTimer();
}
private static Timer createTimer() {
try {
List<Timer> result = new CopyOnWriteArrayList<>();
Thread t = SecuritySupport.createThreadWitNoPermissions("Permissionless thread", ()-> {
result.add(new Timer("JFR Recording Scheduler", true));
});
t.start();
t.join();
return result.get(0);
} catch (InterruptedException e) {
throw new IllegalStateException("Not able to create timer task. " + e.getMessage(), e);
}
}
public synchronized PlatformRecording newRecording(Map<String, String> settings) {
return newRecording(settings, ++recordingCounter);
}
// To be used internally when doing dumps.
// Caller must have recorder lock and close recording before releasing lock
public PlatformRecording newTemporaryRecording() {
if(!Thread.holdsLock(this)) {
throw new InternalError("Caller must have recorder lock");
}
return newRecording(new HashMap<>(), 0);
}
private synchronized PlatformRecording newRecording(Map<String, String> settings, long id) {
PlatformRecording recording = new PlatformRecording(this, id);
if (!settings.isEmpty()) {
recording.setSettings(settings);
}
recordings.add(recording);
return recording;
}
synchronized void finish(PlatformRecording recording) {
if (recording.getState() == RecordingState.RUNNING) {
recording.stop("Recording closed");
}
recordings.remove(recording);
}
public synchronized List<PlatformRecording> getRecordings() {
return Collections.unmodifiableList(new ArrayList<PlatformRecording>(recordings));
}
public synchronized static void addListener(FlightRecorderListener changeListener) {
AccessControlContext context = AccessController.getContext();
SecureRecorderListener sl = new SecureRecorderListener(context, changeListener);
boolean runInitialized;
synchronized (PlatformRecorder.class) {
runInitialized = FlightRecorder.isInitialized();
changeListeners.add(sl);
}
if (runInitialized) {
sl.recorderInitialized(FlightRecorder.getFlightRecorder());
}
}
public synchronized static boolean removeListener(FlightRecorderListener changeListener) {
for (SecureRecorderListener s : new ArrayList<>(changeListeners)) {
if (s.getChangeListener() == changeListener) {
changeListeners.remove(s);
return true;
}
}
return false;
}
static synchronized List<FlightRecorderListener> getListeners() {
return new ArrayList<>(changeListeners);
}
Timer getTimer() {
return timer;
}
public static void notifyRecorderInitialized(FlightRecorder recorder) {
Logger.log(JFR_SYSTEM, TRACE, "Notifying listeners that Flight Recorder is initialized");
for (FlightRecorderListener r : getListeners()) {
r.recorderInitialized(recorder);
}
}
// called by shutdown hook
synchronized void destroy() {
try {
timer.cancel();
} catch (Exception ex) {
Logger.log(JFR_SYSTEM, WARN, "Shutdown hook could not cancel timer");
}
for (PlatformRecording p : getRecordings()) {
if (p.getState() == RecordingState.RUNNING) {
try {
p.stop("Shutdown");
} catch (Exception ex) {
Logger.log(JFR, WARN, "Recording " + p.getName() + ":" + p.getId() + " could not be stopped");
}
}
}
JDKEvents.remove();
if (jvm.hasNativeJFR()) {
if (jvm.isRecording()) {
jvm.endRecording_();
}
jvm.destroyNativeJFR();
}
repository.clear();
}
synchronized void start(PlatformRecording recording) {
// State can only be NEW or DELAYED because of previous checks
Instant now = Instant.now();
recording.setStartTime(now);
recording.updateTimer();
Duration duration = recording.getDuration();
if (duration != null) {
recording.setStopTime(now.plus(duration));
}
boolean toDisk = recording.isToDisk();
boolean beginPhysical = true;
for (PlatformRecording s : getRecordings()) {
if (s.getState() == RecordingState.RUNNING) {
beginPhysical = false;
if (s.isToDisk()) {
toDisk = true;
}
}
}
if (beginPhysical) {
RepositoryChunk newChunk = null;
if (toDisk) {
newChunk = repository.newChunk(now);
MetadataRepository.getInstance().setOutput(newChunk.getUnfishedFile().toString());
} else {
MetadataRepository.getInstance().setOutput(null);
}
currentChunk = newChunk;
jvm.beginRecording_();
recording.setState(RecordingState.RUNNING);
updateSettings();
writeMetaEvents();
} else {
RepositoryChunk newChunk = null;
if (toDisk) {
newChunk = repository.newChunk(now);
RequestEngine.doChunkEnd();
MetadataRepository.getInstance().setOutput(newChunk.getUnfishedFile().toString());
}
recording.setState(RecordingState.RUNNING);
updateSettings();
writeMetaEvents();
if (currentChunk != null) {
finishChunk(currentChunk, now, recording);
}
currentChunk = newChunk;
}
RequestEngine.doChunkBegin();
}
synchronized void stop(PlatformRecording recording) {
RecordingState state = recording.getState();
if (Utils.isAfter(state, RecordingState.RUNNING)) {
throw new IllegalStateException("Can't stop an already stopped recording.");
}
if (Utils.isBefore(state, RecordingState.RUNNING)) {
throw new IllegalStateException("Recording must be started before it can be stopped.");
}
Instant now = Instant.now();
boolean toDisk = false;
boolean endPhysical = true;
for (PlatformRecording s : getRecordings()) {
RecordingState rs = s.getState();
if (s != recording && RecordingState.RUNNING == rs) {
endPhysical = false;
if (s.isToDisk()) {
toDisk = true;
}
}
}
OldObjectSample.emit(recording);
if (endPhysical) {
RequestEngine.doChunkEnd();
if (recording.isToDisk()) {
if (currentChunk != null) {
MetadataRepository.getInstance().setOutput(null);
finishChunk(currentChunk, now, null);
currentChunk = null;
}
} else {
// last memory
dumpMemoryToDestination(recording);
}
jvm.endRecording_();
disableEvents();
} else {
RepositoryChunk newChunk = null;
RequestEngine.doChunkEnd();
updateSettingsButIgnoreRecording(recording);
if (toDisk) {
newChunk = repository.newChunk(now);
MetadataRepository.getInstance().setOutput(newChunk.getUnfishedFile().toString());
} else {
MetadataRepository.getInstance().setOutput(null);
}
writeMetaEvents();
if (currentChunk != null) {
finishChunk(currentChunk, now, null);
}
currentChunk = newChunk;
RequestEngine.doChunkBegin();
}
recording.setState(RecordingState.STOPPED);
}
private void dumpMemoryToDestination(PlatformRecording recording) {
WriteableUserPath dest = recording.getDestination();
if (dest != null) {
MetadataRepository.getInstance().setOutput(dest.getRealPathText());
recording.clearDestination();
}
}
private void disableEvents() {
MetadataRepository.getInstance().disableEvents();
}
void updateSettings() {
updateSettingsButIgnoreRecording(null);
}
void updateSettingsButIgnoreRecording(PlatformRecording ignoreMe) {
List<PlatformRecording> recordings = getRunningRecordings();
List<Map<String, String>> list = new ArrayList<>(recordings.size());
for (PlatformRecording r : recordings) {
if (r != ignoreMe) {
list.add(r.getSettings());
}
}
MetadataRepository.getInstance().setSettings(list);
}
synchronized void rotateDisk() {
Instant now = Instant.now();
RepositoryChunk newChunk = repository.newChunk(now);
RequestEngine.doChunkEnd();
MetadataRepository.getInstance().setOutput(newChunk.getUnfishedFile().toString());
writeMetaEvents();
if (currentChunk != null) {
finishChunk(currentChunk, now, null);
}
currentChunk = newChunk;
RequestEngine.doChunkBegin();
}
private List<PlatformRecording> getRunningRecordings() {
List<PlatformRecording> runningRecordings = new ArrayList<>();
for (PlatformRecording recording : getRecordings()) {
if (recording.getState() == RecordingState.RUNNING) {
runningRecordings.add(recording);
}
}
return runningRecordings;
}
private List<RepositoryChunk> makeChunkList(Instant startTime, Instant endTime) {
Set<RepositoryChunk> chunkSet = new HashSet<>();
for (PlatformRecording r : getRecordings()) {
chunkSet.addAll(r.getChunks());
}
if (chunkSet.size() > 0) {
List<RepositoryChunk> chunks = new ArrayList<>(chunkSet.size());
for (RepositoryChunk rc : chunkSet) {
if (rc.inInterval(startTime, endTime)) {
chunks.add(rc);
}
}
// n*log(n), should be able to do n*log(k) with a priority queue,
// where k = number of recordings, n = number of chunks
Collections.sort(chunks, RepositoryChunk.END_TIME_COMPARATOR);
return chunks;
}
return Collections.emptyList();
}
private void startDiskMonitor() {
Thread t = SecuritySupport.createThreadWitNoPermissions("JFR Periodic Tasks", () -> periodicTask());
SecuritySupport.setDaemonThread(t, true);
t.start();
}
private void finishChunk(RepositoryChunk chunk, Instant time, PlatformRecording ignoreMe) {
chunk.finish(time);
for (PlatformRecording r : getRecordings()) {
if (r != ignoreMe && r.getState() == RecordingState.RUNNING) {
r.appendChunk(chunk);
}
}
}
private void writeMetaEvents() {
if (activeRecordingEvent.isEnabled()) {
for (PlatformRecording r : getRecordings()) {
if (r.getState() == RecordingState.RUNNING && r.shouldWriteMetadataEvent()) {
ActiveRecordingEvent event = new ActiveRecordingEvent();
event.id = r.getId();
event.name = r.getName();
WriteableUserPath p = r.getDestination();
event.destination = p == null ? null : p.getRealPathText();
Duration d = r.getDuration();
event.recordingDuration = d == null ? Long.MAX_VALUE : d.toMillis();
Duration age = r.getMaxAge();
event.maxAge = age == null ? Long.MAX_VALUE : age.toMillis();
Long size = r.getMaxSize();
event.maxSize = size == null ? Long.MAX_VALUE : size;
Instant start = r.getStartTime();
event.recordingStart = start == null ? Long.MAX_VALUE : start.toEpochMilli();
event.commit();
}
}
}
if (activeSettingEvent.isEnabled()) {
for (EventControl ec : MetadataRepository.getInstance().getEventControls()) {
ec.writeActiveSettingEvent();
}
}
}
private void periodicTask() {
if (!jvm.hasNativeJFR()) {
return;
}
while (true) {
synchronized (this) {
if (jvm.shouldRotateDisk()) {
rotateDisk();
}
}
long minDelta = RequestEngine.doPeriodic();
long wait = Math.min(minDelta, Options.getWaitInterval());
takeNap(wait);
}
}
private void takeNap(long duration) {
try {
synchronized (JVM.FILE_DELTA_CHANGE) {
JVM.FILE_DELTA_CHANGE.wait(duration < 10 ? 10 : duration);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized Recording newCopy(PlatformRecording r, boolean stop) {
Recording newRec = new Recording();
PlatformRecording copy = PrivateAccess.getInstance().getPlatformRecording(newRec);
copy.setSettings(r.getSettings());
copy.setMaxAge(r.getMaxAge());
copy.setMaxSize(r.getMaxSize());
copy.setDumpOnExit(r.getDumpOnExit());
copy.setName("Clone of " + r.getName());
copy.setToDisk(r.isToDisk());
copy.setInternalDuration(r.getDuration());
copy.setStartTime(r.getStartTime());
copy.setStopTime(r.getStopTime());
if (r.getState() == RecordingState.NEW) {
return newRec;
}
if (r.getState() == RecordingState.DELAYED) {
copy.scheduleStart(r.getStartTime());
return newRec;
}
copy.setState(r.getState());
// recording has started, copy chunks
for (RepositoryChunk c : r.getChunks()) {
copy.add(c);
}
if (r.getState() == RecordingState.RUNNING) {
if (stop) {
copy.stop("Stopped when cloning recording '" + r.getName() + "'");
} else {
if (r.getStopTime() != null) {
TimerTask stopTask = copy.createStopTask();
copy.setStopTask(copy.createStopTask());
getTimer().schedule(stopTask, r.getStopTime().toEpochMilli());
}
}
}
return newRec;
}
public synchronized void fillWithRecordedData(PlatformRecording target, Boolean pathToGcRoots) {
boolean running = false;
boolean toDisk = false;
for (PlatformRecording r : recordings) {
if (r.getState() == RecordingState.RUNNING) {
running = true;
if (r.isToDisk()) {
toDisk = true;
}
}
}
// If needed, flush data from memory
if (running) {
if (toDisk) {
OldObjectSample.emit(recordings, pathToGcRoots);
rotateDisk();
} else {
try (PlatformRecording snapshot = newTemporaryRecording()) {
snapshot.setToDisk(true);
snapshot.setShouldWriteActiveRecordingEvent(false);
snapshot.start();
OldObjectSample.emit(recordings, pathToGcRoots);
snapshot.stop("Snapshot dump");
fillWithDiskChunks(target);
}
return;
}
}
fillWithDiskChunks(target);
}
private void fillWithDiskChunks(PlatformRecording target) {
for (RepositoryChunk c : makeChunkList(null, null)) {
target.add(c);
}
target.setState(RecordingState.STOPPED);
Instant startTime = null;
Instant endTime = null;
for (RepositoryChunk c : target.getChunks()) {
if (startTime == null || c.getStartTime().isBefore(startTime)) {
startTime = c.getStartTime();
}
if (endTime == null || c.getEndTime().isAfter(endTime)) {
endTime = c.getEndTime();
}
}
Instant now = Instant.now();
if (startTime == null) {
startTime = now;
}
if (endTime == null) {
endTime = now;
}
target.setStartTime(startTime);
target.setStopTime(endTime);
target.setInternalDuration(Duration.between(startTime, endTime));
}
}

View File

@@ -0,0 +1,781 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import static jdk.jfr.internal.LogLevel.DEBUG;
import static jdk.jfr.internal.LogLevel.WARN;
import static jdk.jfr.internal.LogTag.JFR;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.TimerTask;
import java.util.TreeMap;
import jdk.jfr.Configuration;
import jdk.jfr.FlightRecorderListener;
import jdk.jfr.Recording;
import jdk.jfr.RecordingState;
import jdk.jfr.internal.SecuritySupport.SafePath;
public final class PlatformRecording implements AutoCloseable {
private final PlatformRecorder recorder;
private final long id;
// Recording settings
private Map<String, String> settings = new LinkedHashMap<>();
private Duration duration;
private Duration maxAge;
private long maxSize;
private WriteableUserPath destination;
private boolean toDisk = true;
private String name;
private boolean dumpOnExit;
private SafePath dumpOnExitDirectory = new SafePath(".");
// Timestamp information
private Instant stopTime;
private Instant startTime;
// Misc, information
private RecordingState state = RecordingState.NEW;
private long size;
private final LinkedList<RepositoryChunk> chunks = new LinkedList<>();
private volatile Recording recording;
private TimerTask stopTask;
private TimerTask startTask;
private AccessControlContext noDestinationDumpOnExitAccessControlContext;
private boolean shuoldWriteActiveRecordingEvent = true;
PlatformRecording(PlatformRecorder recorder, long id) {
// Typically the access control context is taken
// when you call dump(Path) or setDdestination(Path),
// but if no destination is set and dumponexit=true
// the control context of the recording is taken when the
// Recording object is constructed. This works well for
// -XX:StartFlightRecording and JFR.dump
this.noDestinationDumpOnExitAccessControlContext = AccessController.getContext();
this.id = id;
this.recorder = recorder;
this.name = String.valueOf(id);
}
public void start() {
RecordingState oldState;
RecordingState newState;
synchronized (recorder) {
oldState = getState();
if (!Utils.isBefore(state, RecordingState.RUNNING)) {
throw new IllegalStateException("Recording can only be started once.");
}
if (startTask != null) {
startTask.cancel();
startTask = null;
startTime = null;
}
recorder.start(this);
Logger.log(LogTag.JFR, LogLevel.INFO, () -> {
// Only print non-default values so it easy to see
// which options were added
StringJoiner options = new StringJoiner(", ");
if (!toDisk) {
options.add("disk=false");
}
if (maxAge != null) {
options.add("maxage=" + Utils.formatTimespan(maxAge, ""));
}
if (maxSize != 0) {
options.add("maxsize=" + Utils.formatBytesCompact(maxSize));
}
if (dumpOnExit) {
options.add("dumponexit=true");
}
if (duration != null) {
options.add("duration=" + Utils.formatTimespan(duration, ""));
}
if (destination != null) {
options.add("filename=" + destination.getRealPathText());
}
String optionText = options.toString();
if (optionText.length() != 0) {
optionText = "{" + optionText + "}";
}
return "Started recording \"" + getName() + "\" (" + getId() + ") " + optionText;
});
newState = getState();
}
notifyIfStateChanged(oldState, newState);
}
public boolean stop(String reason) {
RecordingState oldState;
RecordingState newState;
synchronized (recorder) {
oldState = getState();
if (stopTask != null) {
stopTask.cancel();
stopTask = null;
}
recorder.stop(this);
String endText = reason == null ? "" : ". Reason \"" + reason + "\".";
Logger.log(LogTag.JFR, LogLevel.INFO, "Stopped recording \"" + getName() + "\" (" + getId() + ")" + endText);
this.stopTime = Instant.now();
newState = getState();
}
WriteableUserPath dest = getDestination();
if (dest != null) {
try {
dumpStopped(dest);
Logger.log(LogTag.JFR, LogLevel.INFO, "Wrote recording \"" + getName() + "\" (" + getId() + ") to " + dest.getRealPathText());
notifyIfStateChanged(newState, oldState);
close(); // remove if copied out
} catch(IOException e) {
// throw e; // BUG8925030
}
} else {
notifyIfStateChanged(newState, oldState);
}
return true;
}
public void scheduleStart(Duration delay) {
synchronized (recorder) {
ensureOkForSchedule();
startTime = Instant.now().plus(delay);
LocalDateTime now = LocalDateTime.now().plus(delay);
setState(RecordingState.DELAYED);
startTask = createStartTask();
recorder.getTimer().schedule(startTask, delay.toMillis());
Logger.log(LogTag.JFR, LogLevel.INFO, "Scheduled recording \"" + getName() + "\" (" + getId() + ") to start at " + now);
}
}
private void ensureOkForSchedule() {
if (getState() != RecordingState.NEW) {
throw new IllegalStateException("Only a new recoridng can be scheduled for start");
}
}
private TimerTask createStartTask() {
// Taking ref. to recording here.
// Opens up for memory leaks.
return new TimerTask() {
@Override
public void run() {
synchronized (recorder) {
if (getState() != RecordingState.DELAYED) {
return;
}
start();
}
}
};
}
void scheduleStart(Instant startTime) {
synchronized (recorder) {
ensureOkForSchedule();
this.startTime = startTime;
setState(RecordingState.DELAYED);
startTask = createStartTask();
recorder.getTimer().schedule(startTask, startTime.toEpochMilli());
}
}
public Map<String, String> getSettings() {
synchronized (recorder) {
return settings;
}
}
public long getSize() {
return size;
}
public Instant getStopTime() {
synchronized (recorder) {
return stopTime;
}
}
public Instant getStartTime() {
synchronized (recorder) {
return startTime;
}
}
public Long getMaxSize() {
synchronized (recorder) {
return maxSize;
}
}
public Duration getMaxAge() {
synchronized (recorder) {
return maxAge;
}
}
public String getName() {
synchronized (recorder) {
return name;
}
}
public RecordingState getState() {
synchronized (recorder) {
return state;
}
}
@Override
public void close() {
RecordingState oldState;
RecordingState newState;
synchronized (recorder) {
oldState = getState();
if (RecordingState.CLOSED != getState()) {
if (startTask != null) {
startTask.cancel();
startTask = null;
}
recorder.finish(this);
for (RepositoryChunk c : chunks) {
removed(c);
}
chunks.clear();
setState(RecordingState.CLOSED);
Logger.log(LogTag.JFR, LogLevel.INFO, "Closed recording \"" + getName() + "\" (" + getId() + ")");
}
newState = getState();
}
notifyIfStateChanged(newState, oldState);
}
// To be used internally when doing dumps.
// Caller must have recorder lock and close recording before releasing lock
public PlatformRecording newSnapshotClone(String reason, Boolean pathToGcRoots) throws IOException {
if(!Thread.holdsLock(recorder)) {
throw new InternalError("Caller must have recorder lock");
}
RecordingState state = getState();
if (state == RecordingState.CLOSED) {
throw new IOException("Recording \"" + name + "\" (id=" + id + ") has been closed, no contents to write");
}
if (state == RecordingState.DELAYED || state == RecordingState.NEW) {
throw new IOException("Recording \"" + name + "\" (id=" + id + ") has not started, no contents to write");
}
if (state == RecordingState.STOPPED) {
PlatformRecording clone = recorder.newTemporaryRecording();
for (RepositoryChunk r : chunks) {
clone.add(r);
}
return clone;
}
// Recording is RUNNING, create a clone
PlatformRecording clone = recorder.newTemporaryRecording();
clone.setShouldWriteActiveRecordingEvent(false);
clone.setName(getName());
clone.setToDisk(true);
// We purposely don't clone settings here, since
// a union a == a
if (!isToDisk()) {
// force memory contents to disk
clone.start();
} else {
// using existing chunks on disk
for (RepositoryChunk c : chunks) {
clone.add(c);
}
clone.setState(RecordingState.RUNNING);
clone.setStartTime(getStartTime());
}
if (pathToGcRoots == null) {
clone.setSettings(getSettings()); // needed for old object sample
clone.stop(reason); // dumps to destination path here
} else {
// Risk of violating lock order here, since
// clone.stop() will take recorder lock inside
// metadata lock, but OK if we already
// have recorder lock when we entered metadata lock
synchronized (MetadataRepository.getInstance()) {
clone.setSettings(OldObjectSample.createSettingsForSnapshot(this, pathToGcRoots));
clone.stop(reason);
}
}
return clone;
}
public boolean isToDisk() {
synchronized (recorder) {
return toDisk;
}
}
public void setMaxSize(long maxSize) {
synchronized (recorder) {
if (getState() == RecordingState.CLOSED) {
throw new IllegalStateException("Can't set max age when recording is closed");
}
this.maxSize = maxSize;
trimToSize();
}
}
public void setDestination(WriteableUserPath userSuppliedPath) throws IOException {
synchronized (recorder) {
if (Utils.isState(getState(), RecordingState.STOPPED, RecordingState.CLOSED)) {
throw new IllegalStateException("Destination can't be set on a recording that has been stopped/closed");
}
this.destination = userSuppliedPath;
}
}
public WriteableUserPath getDestination() {
synchronized (recorder) {
return destination;
}
}
void setState(RecordingState state) {
synchronized (recorder) {
this.state = state;
}
}
void setStartTime(Instant startTime) {
synchronized (recorder) {
this.startTime = startTime;
}
}
void setStopTime(Instant timeStamp) {
synchronized (recorder) {
stopTime = timeStamp;
}
}
public long getId() {
synchronized (recorder) {
return id;
}
}
public void setName(String name) {
synchronized (recorder) {
ensureNotClosed();
this.name = name;
}
}
private void ensureNotClosed() {
if (getState() == RecordingState.CLOSED) {
throw new IllegalStateException("Can't change name on a closed recording");
}
}
public void setDumpOnExit(boolean dumpOnExit) {
synchronized (recorder) {
this.dumpOnExit = dumpOnExit;
}
}
public boolean getDumpOnExit() {
synchronized (recorder) {
return dumpOnExit;
}
}
public void setToDisk(boolean toDisk) {
synchronized (recorder) {
if (Utils.isState(getState(), RecordingState.NEW, RecordingState.DELAYED)) {
this.toDisk = toDisk;
} else {
throw new IllegalStateException("Recording option disk can't be changed after recording has started");
}
}
}
public void setSetting(String id, String value) {
synchronized (recorder) {
this.settings.put(id, value);
if (getState() == RecordingState.RUNNING) {
recorder.updateSettings();
}
}
}
public void setSettings(Map<String, String> settings) {
setSettings(settings, true);
}
private void setSettings(Map<String, String> settings, boolean update) {
if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO) && update) {
TreeMap<String, String> ordered = new TreeMap<>(settings);
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "New settings for recording \"" + getName() + "\" (" + getId() + ")");
for (Map.Entry<String, String> entry : ordered.entrySet()) {
String text = entry.getKey() + "=\"" + entry.getValue() + "\"";
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, text);
}
}
synchronized (recorder) {
this.settings = new LinkedHashMap<>(settings);
if (getState() == RecordingState.RUNNING && update) {
recorder.updateSettings();
}
}
}
private void notifyIfStateChanged(RecordingState newState, RecordingState oldState) {
if (oldState == newState) {
return;
}
for (FlightRecorderListener cl : PlatformRecorder.getListeners()) {
try {
cl.recordingStateChanged(getRecording());
} catch (RuntimeException re) {
Logger.log(JFR, WARN, "Error notifying recorder listener:" + re.getMessage());
}
}
}
public void setRecording(Recording recording) {
this.recording = recording;
}
public Recording getRecording() {
return recording;
}
@Override
public String toString() {
return getName() + " (id=" + getId() + ") " + getState();
}
public void setConfiguration(Configuration c) {
setSettings(c.getSettings());
}
public void setMaxAge(Duration maxAge) {
synchronized (recorder) {
if (getState() == RecordingState.CLOSED) {
throw new IllegalStateException("Can't set max age when recording is closed");
}
this.maxAge = maxAge;
if (maxAge != null) {
trimToAge(Instant.now().minus(maxAge));
}
}
}
void appendChunk(RepositoryChunk chunk) {
if (!chunk.isFinished()) {
throw new Error("not finished chunk " + chunk.getStartTime());
}
synchronized (recorder) {
if (!toDisk) {
return;
}
if (maxAge != null) {
trimToAge(chunk.getEndTime().minus(maxAge));
}
chunks.addLast(chunk);
added(chunk);
trimToSize();
}
}
private void trimToSize() {
if (maxSize == 0) {
return;
}
while (size > maxSize && chunks.size() > 1) {
RepositoryChunk c = chunks.removeFirst();
removed(c);
}
}
private void trimToAge(Instant oldest) {
while (!chunks.isEmpty()) {
RepositoryChunk oldestChunk = chunks.peek();
if (oldestChunk.getEndTime().isAfter(oldest)) {
return;
}
chunks.removeFirst();
removed(oldestChunk);
}
}
void add(RepositoryChunk c) {
chunks.add(c);
added(c);
}
private void added(RepositoryChunk c) {
c.use();
size += c.getSize();
Logger.log(JFR, DEBUG, () -> "Recording \"" + name + "\" (" + id + ") added chunk " + c.toString() + ", current size=" + size);
}
private void removed(RepositoryChunk c) {
size -= c.getSize();
Logger.log(JFR, DEBUG, () -> "Recording \"" + name + "\" (" + id + ") removed chunk " + c.toString() + ", current size=" + size);
c.release();
}
public List<RepositoryChunk> getChunks() {
return chunks;
}
public InputStream open(Instant start, Instant end) throws IOException {
synchronized (recorder) {
if (getState() != RecordingState.STOPPED) {
throw new IOException("Recording must be stopped before it can be read.");
}
List<RepositoryChunk> chunksToUse = new ArrayList<RepositoryChunk>();
for (RepositoryChunk chunk : chunks) {
if (chunk.isFinished()) {
Instant chunkStart = chunk.getStartTime();
Instant chunkEnd = chunk.getEndTime();
if (start == null || !chunkEnd.isBefore(start)) {
if (end == null || !chunkStart.isAfter(end)) {
chunksToUse.add(chunk);
}
}
}
}
if (chunksToUse.isEmpty()) {
return null;
}
return new ChunkInputStream(chunksToUse);
}
}
public Duration getDuration() {
synchronized (recorder) {
return duration;
}
}
void setInternalDuration(Duration duration) {
this.duration = duration;
}
public void setDuration(Duration duration) {
synchronized (recorder) {
if (Utils.isState(getState(), RecordingState.STOPPED, RecordingState.CLOSED)) {
throw new IllegalStateException("Duration can't be set after a recording has been stopped/closed");
}
setInternalDuration(duration);
if (getState() != RecordingState.NEW) {
updateTimer();
}
}
}
void updateTimer() {
if (stopTask != null) {
stopTask.cancel();
stopTask = null;
}
if (getState() == RecordingState.CLOSED) {
return;
}
if (duration != null) {
stopTask = createStopTask();
recorder.getTimer().schedule(stopTask, new Date(startTime.plus(duration).toEpochMilli()));
}
}
TimerTask createStopTask() {
return new TimerTask() {
@Override
public void run() {
try {
stop("End of duration reached");
} catch (Throwable t) {
// Prevent malicious user to propagate exception callback in the wrong context
Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not stop recording.");
}
}
};
}
public Recording newCopy(boolean stop) {
return recorder.newCopy(this, stop);
}
void setStopTask(TimerTask stopTask) {
synchronized (recorder) {
this.stopTask = stopTask;
}
}
void clearDestination() {
destination = null;
}
public AccessControlContext getNoDestinationDumpOnExitAccessControlContext() {
return noDestinationDumpOnExitAccessControlContext;
}
void setShouldWriteActiveRecordingEvent(boolean shouldWrite) {
this.shuoldWriteActiveRecordingEvent = shouldWrite;
}
boolean shouldWriteMetadataEvent() {
return shuoldWriteActiveRecordingEvent;
}
// Dump running and stopped recordings
public void dump(WriteableUserPath writeableUserPath) throws IOException {
synchronized (recorder) {
try(PlatformRecording p = newSnapshotClone("Dumped by user", null)) {
p.dumpStopped(writeableUserPath);
}
}
}
public void dumpStopped(WriteableUserPath userPath) throws IOException {
synchronized (recorder) {
userPath.doPriviligedIO(() -> {
try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(userPath.getReal(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
long bytes = cc.transferTo(fc);
Logger.log(LogTag.JFR, LogLevel.INFO, "Transferred " + bytes + " bytes from the disk repository");
// No need to force if no data was transferred, which avoids IOException when device is /dev/null
if (bytes != 0) {
fc.force(true);
}
}
return null;
});
}
}
public void filter(Instant begin, Instant end, Long maxSize) {
synchronized (recorder) {
List<RepositoryChunk> result = removeAfter(end, removeBefore(begin, new ArrayList<>(chunks)));
if (maxSize != null) {
if (begin != null && end == null) {
result = reduceFromBeginning(maxSize, result);
} else {
result = reduceFromEnd(maxSize, result);
}
}
int size = 0;
for (RepositoryChunk r : result) {
size += r.getSize();
r.use();
}
this.size = size;
for (RepositoryChunk r : chunks) {
r.release();
}
chunks.clear();
chunks.addAll(result);
}
}
private static List<RepositoryChunk> removeBefore(Instant time, List<RepositoryChunk> input) {
if (time == null) {
return input;
}
List<RepositoryChunk> result = new ArrayList<>(input.size());
for (RepositoryChunk r : input) {
if (!r.getEndTime().isBefore(time)) {
result.add(r);
}
}
return result;
}
private static List<RepositoryChunk> removeAfter(Instant time, List<RepositoryChunk> input) {
if (time == null) {
return input;
}
List<RepositoryChunk> result = new ArrayList<>(input.size());
for (RepositoryChunk r : input) {
if (!r.getStartTime().isAfter(time)) {
result.add(r);
}
}
return result;
}
private static List<RepositoryChunk> reduceFromBeginning(Long maxSize, List<RepositoryChunk> input) {
if (maxSize == null || input.isEmpty()) {
return input;
}
List<RepositoryChunk> result = new ArrayList<>(input.size());
long total = 0;
for (RepositoryChunk r : input) {
total += r.getSize();
if (total > maxSize) {
break;
}
result.add(r);
}
// always keep at least one chunk
if (result.isEmpty()) {
result.add(input.get(0));
}
return result;
}
private static List<RepositoryChunk> reduceFromEnd(Long maxSize, List<RepositoryChunk> input) {
Collections.reverse(input);
List<RepositoryChunk> result = reduceFromBeginning(maxSize, input);
Collections.reverse(result);
return result;
}
public void setDumpOnExitDirectory(SafePath directory) {
this.dumpOnExitDirectory = directory;
}
public SafePath getDumpOnExitDirectory() {
return this.dumpOnExitDirectory;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.util.List;
import java.util.Map;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Configuration;
import jdk.jfr.EventType;
import jdk.jfr.FlightRecorderPermission;
import jdk.jfr.Recording;
import jdk.jfr.SettingDescriptor;
import jdk.jfr.ValueDescriptor;
/**
* Provides access to package private function in jdk.jfr.
* <p>
* The static initializer in this class loads the Settings class, which will
* call {@link #setPrivateAccess(PrivateAccess)} on this class, which can be
* used call to package protected methods.
*
* This is similar to how java.lang accesses package private methods in
* java.lang.reflect.
*/
public abstract class PrivateAccess {
private volatile static PrivateAccess instance;
public static PrivateAccess getInstance() {
// Can't be initialized in <clinit> because it may
// deadlock with FlightRecordeerPermission.<clinit>
if (instance == null) {
// Will trigger
// FlightRecordeerPermission.<clinit>
// which will call PrivateAccess.setPrivateAccess
new FlightRecorderPermission(Utils.REGISTER_EVENT);
}
return instance;
}
public static void setPrivateAccess(PrivateAccess pa) {
instance = pa;
}
public abstract Type getType(Object o);
public abstract Configuration newConfiguration(String name, String label, String description, String provider, Map<String,String> settings, String contents);
public abstract EventType newEventType(PlatformEventType eventTypes);
public abstract AnnotationElement newAnnotation(Type annotationType, List<Object> values, boolean boot);
public abstract ValueDescriptor newValueDescriptor(String name, Type fieldType, List<AnnotationElement> annotations, int dimension, boolean constantPool, String fieldName);
public abstract PlatformRecording getPlatformRecording(Recording r);
public abstract PlatformEventType getPlatformEventType(EventType eventType);
public abstract boolean isConstantPool(ValueDescriptor v);
public abstract String getFieldName(ValueDescriptor v);
public abstract ValueDescriptor newValueDescriptor(Class<?> type, String name);
public abstract SettingDescriptor newSettingDescriptor(Type type, String name, String def, List<AnnotationElement> aes);
public abstract void setAnnotations(ValueDescriptor v, List<AnnotationElement> a);
public abstract void setAnnotations(SettingDescriptor s, List<AnnotationElement> a);
public abstract boolean isUnsigned(ValueDescriptor v);
public abstract PlatformRecorder getPlatformRecorder();
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.Set;
import jdk.jfr.internal.SecuritySupport.SafePath;
public final class Repository {
private static final int MAX_REPO_CREATION_RETRIES = 1000;
private static final JVM jvm = JVM.getJVM();
private static final Repository instance = new Repository();
public final static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter
.ofPattern("yyyy_MM_dd_HH_mm_ss");
private final Set<SafePath> cleanupDirectories = new HashSet<>();
private SafePath baseLocation;
private SafePath repository;
private Repository() {
}
public static Repository getRepository() {
return instance;
}
public synchronized void setBasePath(SafePath baseLocation) throws Exception {
// Probe to see if repository can be created, needed for fail fast
// during JVM startup or JFR.configure
this.repository = createRepository(baseLocation);
try {
// Remove so we don't "leak" repositories, if JFR is never started
// and shutdown hook not added.
SecuritySupport.delete(repository);
} catch (IOException ioe) {
Logger.log(LogTag.JFR, LogLevel.INFO, "Could not delete disk repository " + repository);
}
this.baseLocation = baseLocation;
}
synchronized void ensureRepository() throws Exception {
if (baseLocation == null) {
setBasePath(SecuritySupport.JAVA_IO_TMPDIR);
}
}
synchronized RepositoryChunk newChunk(Instant timestamp) {
try {
if (!SecuritySupport.existDirectory(repository)) {
this.repository = createRepository(baseLocation);
jvm.setRepositoryLocation(repository.toString());
cleanupDirectories.add(repository);
}
return new RepositoryChunk(repository, timestamp);
} catch (Exception e) {
String errorMsg = String.format("Could not create chunk in repository %s, %s", repository, e.getMessage());
Logger.log(LogTag.JFR, LogLevel.ERROR, errorMsg);
jvm.abort(errorMsg);
throw new InternalError("Could not abort after JFR disk creation error");
}
}
private static SafePath createRepository(SafePath basePath) throws Exception {
SafePath canonicalBaseRepositoryPath = createRealBasePath(basePath);
SafePath f = null;
String basename = REPO_DATE_FORMAT.format(LocalDateTime.now()) + "_" + JVM.getJVM().getPid();
String name = basename;
int i = 0;
for (; i < MAX_REPO_CREATION_RETRIES; i++) {
f = new SafePath(canonicalBaseRepositoryPath.toPath().resolve(name));
if (tryToUseAsRepository(f)) {
break;
}
name = basename + "_" + i;
}
if (i == MAX_REPO_CREATION_RETRIES) {
throw new Exception("Unable to create JFR repository directory using base location (" + basePath + ")");
}
SafePath canonicalRepositoryPath = SecuritySupport.toRealPath(f);
return canonicalRepositoryPath;
}
private static SafePath createRealBasePath(SafePath safePath) throws Exception {
if (SecuritySupport.exists(safePath)) {
if (!SecuritySupport.isWritable(safePath)) {
throw new IOException("JFR repository directory (" + safePath.toString() + ") exists, but isn't writable");
}
return SecuritySupport.toRealPath(safePath);
}
SafePath p = SecuritySupport.createDirectories(safePath);
return SecuritySupport.toRealPath(p);
}
private static boolean tryToUseAsRepository(final SafePath path) {
Path parent = path.toPath().getParent();
if (parent == null) {
return false;
}
try {
try {
SecuritySupport.createDirectories(path);
} catch (Exception e) {
// file already existed or some other problem occurred
}
if (!SecuritySupport.exists(path)) {
return false;
}
if (!SecuritySupport.isDirectory(path)) {
return false;
}
return true;
} catch (IOException io) {
return false;
}
}
synchronized void clear() {
for (SafePath p : cleanupDirectories) {
try {
SecuritySupport.clearDirectory(p);
Logger.log(LogTag.JFR, LogLevel.INFO, "Removed repository " + p);
} catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, "Repository " + p + " could not be removed at shutdown: " + e.getMessage());
}
}
}
public synchronized SafePath getRepositoryPath() {
return repository;
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.Objects;
import jdk.jfr.internal.SecuritySupport.SafePath;
final class RepositoryChunk {
private static final int MAX_CHUNK_NAMES = 100;
static final Comparator<RepositoryChunk> END_TIME_COMPARATOR = new Comparator<RepositoryChunk>() {
@Override
public int compare(RepositoryChunk c1, RepositoryChunk c2) {
return c1.endTime.compareTo(c2.endTime);
}
};
private final SafePath repositoryPath;
private final SafePath unFinishedFile;
private final SafePath file;
private final Instant startTime;
private final RandomAccessFile unFinishedRAF;
private Instant endTime = null; // unfinished
private int refCount = 0;
private long size;
RepositoryChunk(SafePath path, Instant startTime) throws Exception {
ZonedDateTime z = ZonedDateTime.now();
String fileName = Repository.REPO_DATE_FORMAT.format(
LocalDateTime.ofInstant(startTime, z.getZone()));
this.startTime = startTime;
this.repositoryPath = path;
this.unFinishedFile = findFileName(repositoryPath, fileName, ".part");
this.file = findFileName(repositoryPath, fileName, ".jfr");
this.unFinishedRAF = SecuritySupport.createRandomAccessFile(unFinishedFile);
SecuritySupport.touch(file);
}
private static SafePath findFileName(SafePath directory, String name, String extension) throws Exception {
Path p = directory.toPath().resolve(name + extension);
for (int i = 1; i < MAX_CHUNK_NAMES; i++) {
SafePath s = new SafePath(p);
if (!SecuritySupport.exists(s)) {
return s;
}
String extendedName = String.format("%s_%02d%s", name, i, extension);
p = directory.toPath().resolve(extendedName);
}
p = directory.toPath().resolve(name + "_" + System.currentTimeMillis() + extension);
return SecuritySupport.toRealPath(new SafePath(p));
}
public SafePath getUnfishedFile() {
return unFinishedFile;
}
void finish(Instant endTime) {
try {
finishWithException(endTime);
} catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not finish chunk. " + e.getMessage());
}
}
private void finishWithException(Instant endTime) throws IOException {
unFinishedRAF.close();
this.size = finish(unFinishedFile, file);
this.endTime = endTime;
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Chunk finished: " + file);
}
private static long finish(SafePath unFinishedFile, SafePath file) throws IOException {
Objects.requireNonNull(unFinishedFile);
Objects.requireNonNull(file);
SecuritySupport.delete(file);
SecuritySupport.moveReplace(unFinishedFile, file);
return SecuritySupport.getFileSize(file);
}
public Instant getStartTime() {
return startTime;
}
public Instant getEndTime() {
return endTime;
}
private void delete(SafePath f) {
try {
SecuritySupport.delete(f);
Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Repository chunk " + f + " deleted");
} catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, () -> "Repository chunk " + f + " could not be deleted: " + e.getMessage());
if (f != null) {
SecuritySupport.deleteOnExit(f);
}
}
}
private void destroy() {
if (!isFinished()) {
finish(Instant.MIN);
}
if (file != null) {
delete(file);
}
try {
unFinishedRAF.close();
} catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, () -> "Could not close random access file: " + unFinishedFile.toString() + ". File will not be deleted due to: " + e.getMessage());
}
}
public synchronized void use() {
++refCount;
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Use chunk " + toString() + " ref count now " + refCount);
}
public synchronized void release() {
--refCount;
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Release chunk " + toString() + " ref count now " + refCount);
if (refCount == 0) {
destroy();
}
}
@Override
@SuppressWarnings("deprecation")
protected void finalize() {
boolean destroy = false;
synchronized (this) {
if (refCount > 0) {
destroy = true;
}
}
if (destroy) {
destroy();
}
}
public long getSize() {
return size;
}
public boolean isFinished() {
return endTime != null;
}
@Override
public String toString() {
if (isFinished()) {
return file.toString();
}
return unFinishedFile.toString();
}
ReadableByteChannel newChannel() throws IOException {
if (!isFinished()) {
throw new IOException("Chunk not finished");
}
return ((SecuritySupport.newFileChannelToRead(file)));
}
public boolean inInterval(Instant startTime, Instant endTime) {
if (startTime != null && getEndTime().isBefore(startTime)) {
return false;
}
if (endTime != null && getStartTime().isAfter(endTime)) {
return false;
}
return true;
}
public SafePath getFile() {
return file;
}
}

View File

@@ -0,0 +1,260 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import jdk.jfr.Event;
import jdk.jfr.EventType;
public final class RequestEngine {
private final static JVM jvm = JVM.getJVM();
final static class RequestHook {
private final Runnable hook;
private final PlatformEventType type;
private final AccessControlContext accessControllerContext;
private long delta;
// Java events
private RequestHook(AccessControlContext acc, PlatformEventType eventType, Runnable hook) {
this.hook = hook;
this.type = eventType;
this.accessControllerContext = acc;
}
// native events
RequestHook(PlatformEventType eventType) {
this(null, eventType, null);
}
private void execute() {
try {
if (accessControllerContext == null) { // native
if (type.isJDK()) {
hook.run();
} else {
jvm.emitEvent(type.getId(), JVM.counterTime(), 0);
}
if (Logger.shouldLog(LogTag.JFR_EVENT, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.DEBUG, ()-> "Executed periodic hook for " + type.getLogName());
}
} else {
executeSecure();
}
} catch (Throwable e) {
// Prevent malicious user to propagate exception callback in the wrong context
Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.WARN, "Exception occured during execution of period hook for " + type.getLogName());
}
}
private void executeSecure() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
try {
hook.run();
if (Logger.shouldLog(LogTag.JFR_EVENT, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_EVENT, LogLevel.DEBUG, ()-> "Executed periodic hook for " + type.getLogName());
}
} catch (Throwable t) {
// Prevent malicious user to propagate exception callback in the wrong context
Logger.log(LogTag.JFR_EVENT, LogLevel.WARN, "Exception occured during execution of period hook for " + type.getLogName());
}
return null;
}
}, accessControllerContext);
}
}
private final static List<RequestHook> entries = new CopyOnWriteArrayList<>();
private static long lastTimeMillis;
public static void addHook(AccessControlContext acc, PlatformEventType type, Runnable hook) {
Objects.requireNonNull(acc);
addHookInternal(acc, type, hook);
}
private static void addHookInternal(AccessControlContext acc, PlatformEventType type, Runnable hook) {
RequestHook he = new RequestHook(acc, type, hook);
for (RequestHook e : entries) {
if (e.hook == hook) {
throw new IllegalArgumentException("Hook has already been added");
}
}
he.type.setEventHook(true);
// Insertion takes O(2*n), could be O(1) with HashMap, but
// thinking is that CopyOnWriteArrayList is faster
// to iterate over, which will happen more over time.
entries.add(he);
logHook("Added", type);
}
public static void addTrustedJDKHook(Class<? extends Event> eventClass, Runnable runnable) {
if (eventClass.getClassLoader() != null) {
throw new SecurityException("Hook can only be registered for event classes that are loaded by the bootstrap class loader");
}
if (runnable.getClass().getClassLoader() != null) {
throw new SecurityException("Runnable hook class must be loaded by the bootstrap class loader");
}
EventType eType = MetadataRepository.getInstance().getEventType(eventClass);
PlatformEventType pType = PrivateAccess.getInstance().getPlatformEventType(eType);
addHookInternal(null, pType, runnable);
}
private static void logHook(String action, PlatformEventType type) {
if (type.isJDK() || type.isJVM()) {
Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.INFO, action + " periodic hook for " + type.getLogName());
} else {
Logger.log(LogTag.JFR_EVENT, LogLevel.INFO, action + " periodic hook for " + type.getLogName());
}
}
// Takes O(2*n), see addHook.
public static boolean removeHook(Runnable hook) {
for (RequestHook rh : entries) {
if (rh.hook == hook) {
entries.remove(rh);
rh.type.setEventHook(false);
logHook("Removed", rh.type);
return true;
}
}
return false;
}
// Only to be used for JVM events. No access control contest
// or check if hook already exists
static void addHooks(List<RequestHook> newEntries) {
List<RequestHook> addEntries = new ArrayList<>();
for (RequestHook rh : newEntries) {
rh.type.setEventHook(true);
addEntries.add(rh);
logHook("Added", rh.type);
}
entries.addAll(newEntries);
}
static void doChunkEnd() {
doChunk(x -> x.isEndChunk());
}
static void doChunkBegin() {
doChunk(x -> x.isBeginChunk());
}
private static void doChunk(Predicate<PlatformEventType> predicate) {
for (RequestHook requestHook : entries) {
PlatformEventType s = requestHook.type;
if (s.isEnabled() && predicate.test(s)) {
requestHook.execute();
}
}
}
static long doPeriodic() {
return run_requests(entries);
}
// code copied from native impl.
private static long run_requests(Collection<RequestHook> entries) {
long last = lastTimeMillis;
// Bug 9000556 - current time millis has rather lame resolution
// The use of os::elapsed_counter() is deliberate here, we don't
// want it exchanged for os::ft_elapsed_counter().
// Keeping direct call os::elapsed_counter() here for reliable
// real time values in order to decide when registered requestable
// events are due.
long now = System.currentTimeMillis();
long min = 0;
long delta = 0;
if (last == 0) {
last = now;
}
// time from then to now
delta = now - last;
if (delta < 0) {
// to handle time adjustments
// for example Daylight Savings
lastTimeMillis = now;
return 0;
}
for (RequestHook he : entries) {
long left = 0;
PlatformEventType es = he.type;
// Not enabled, skip.
if (!es.isEnabled() || es.isEveryChunk()) {
continue;
}
long r_period = es.getPeriod();
long r_delta = he.delta;
// add time elapsed.
r_delta += delta;
// above threshold?
if (r_delta >= r_period) {
// Bug 9000556 - don't try to compensate
// for wait > period
r_delta = 0;
he.execute();
;
}
// calculate time left
left = (r_period - r_delta);
/**
* nothing outside checks that a period is >= 0, so left can end up
* negative here. ex. (r_period =(-1)) - (r_delta = 0) if it is,
* handle it.
*/
if (left < 0) {
left = 0;
}
// assign delta back
he.delta = r_delta;
if (min == 0 || left < min) {
min = left;
}
}
lastTimeMillis = now;
return min;
}
}

View File

@@ -0,0 +1,391 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ReflectPermission;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.PropertyPermission;
import java.util.concurrent.Callable;
import sun.misc.Unsafe;
import jdk.jfr.Event;
import jdk.jfr.FlightRecorder;
import jdk.jfr.FlightRecorderListener;
import jdk.jfr.FlightRecorderPermission;
import jdk.jfr.Recording;
/**
* Contains JFR code that does
* {@link AccessController#doPrivileged(PrivilegedAction)}
*/
public final class SecuritySupport {
private final static Unsafe unsafe = Unsafe.getUnsafe();
public final static SafePath JFC_DIRECTORY = getPathInProperty("java.home", "lib/jfr");
static final SafePath USER_HOME = getPathInProperty("user.home", null);
static final SafePath JAVA_IO_TMPDIR = getPathInProperty("java.io.tmpdir", null);
final static class SecureRecorderListener implements FlightRecorderListener {
private final AccessControlContext context;
private final FlightRecorderListener changeListener;
SecureRecorderListener(AccessControlContext context, FlightRecorderListener changeListener) {
this.context = Objects.requireNonNull(context);
this.changeListener = Objects.requireNonNull(changeListener);
}
@Override
public void recordingStateChanged(Recording recording) {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
try {
changeListener.recordingStateChanged(recording);
} catch (Throwable t) {
// Prevent malicious user to propagate exception callback in the wrong context
Logger.log(LogTag.JFR, LogLevel.WARN, "Unexpected exception in listener " + changeListener.getClass()+ " at recording state change");
}
return null;
}, context);
}
@Override
public void recorderInitialized(FlightRecorder recorder) {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
try {
changeListener.recorderInitialized(recorder);
} catch (Throwable t) {
// Prevent malicious user to propagate exception callback in the wrong context
Logger.log(LogTag.JFR, LogLevel.WARN, "Unexpected exception in listener " + changeListener.getClass()+ " when initializing FlightRecorder");
}
return null;
}, context);
}
public FlightRecorderListener getChangeListener() {
return changeListener;
}
}
private static final class DirectoryCleaner extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
Files.delete(path);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (exc != null) {
throw exc;
}
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
}
/**
* Path created by the default file provider,and not
* a malicious provider.
*
*/
public static final class SafePath {
private final Path path;
private final String text;
public SafePath(Path p) {
// sanitize
text = p.toString();
path = Paths.get(text);
}
public SafePath(String path) {
this(Paths.get(path));
}
public Path toPath() {
return path;
}
public String toString() {
return text;
}
}
private interface RunnableWithCheckedException {
public void run() throws Exception;
}
private interface CallableWithoutCheckException<T> {
public T call();
}
private static <U> U doPrivilegedIOWithReturn(Callable<U> function) throws IOException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<U>() {
@Override
public U run() throws Exception {
return function.call();
}
}, null);
} catch (PrivilegedActionException e) {
Throwable t = e.getCause();
if (t instanceof IOException) {
throw (IOException) t;
}
throw new IOException("Unexpected error during I/O operation. " + t.getMessage(), t);
}
}
private static void doPriviligedIO(RunnableWithCheckedException function) throws IOException {
doPrivilegedIOWithReturn(() -> {
function.run();
return null;
});
}
private static void doPrivileged(Runnable function, Permission... perms) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
function.run();
return null;
}
}, null, perms);
}
private static void doPrivileged(Runnable function) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
function.run();
return null;
}
});
}
private static <T> T doPrivilegedWithReturn(CallableWithoutCheckException<T> function, Permission... perms) {
return AccessController.doPrivileged(new PrivilegedAction<T>() {
@Override
public T run() {
return function.call();
}
}, null, perms);
}
public static List<SafePath> getPredefinedJFCFiles() {
List<SafePath> list = new ArrayList<>();
try {
Iterator<Path> pathIterator = doPrivilegedIOWithReturn(() -> {
return Files.newDirectoryStream(JFC_DIRECTORY.toPath(), "*").iterator();
});
while (pathIterator.hasNext()) {
Path path = pathIterator.next();
if (path.toString().endsWith(".jfc")) {
list.add(new SafePath(path));
}
}
} catch (IOException ioe) {
Logger.log(LogTag.JFR, LogLevel.WARN, "Could not access .jfc-files in " + JFC_DIRECTORY + ", " + ioe.getMessage());
}
return list;
}
static void makeVisibleToJFR(Class<?> clazz) {
// nothing to do for JDK8
}
/**
* Adds a qualified export of the internal.jdk.jfr.internal.handlers package
* (for EventHandler)
*/
static void addHandlerExport(Class<?> clazz) {
// nothing to do for JDK8
}
public static void registerEvent(Class<? extends Event> eventClass) {
doPrivileged(() -> FlightRecorder.register(eventClass), new FlightRecorderPermission(Utils.REGISTER_EVENT));
}
static boolean getBooleanProperty(String propertyName) {
return doPrivilegedWithReturn(() -> Boolean.getBoolean(propertyName), new PropertyPermission(propertyName, "read"));
}
private static SafePath getPathInProperty(String prop, String subPath) {
return doPrivilegedWithReturn(() -> {
String path = System.getProperty(prop);
if (path == null) {
return null;
}
File file = subPath == null ? new File(path) : new File(path, subPath);
return new SafePath(file.getAbsolutePath());
}, new PropertyPermission("*", "read"));
}
// Called by JVM during initialization of JFR
static Thread createRecorderThread(ThreadGroup systemThreadGroup, ClassLoader contextClassLoader) {
// The thread should have permission = new Permission[0], and not "modifyThreadGroup" and "modifyThread" on the stack,
// but it's hard circumvent if we are going to pass in system thread group in the constructor
Thread thread = doPrivilegedWithReturn(() -> new Thread(systemThreadGroup, "JFR Recorder Thread"), new RuntimePermission("modifyThreadGroup"), new RuntimePermission("modifyThread"));
doPrivileged(() -> thread.setContextClassLoader(contextClassLoader), new RuntimePermission("setContextClassLoader"), new RuntimePermission("modifyThread"));
return thread;
}
static void registerShutdownHook(Thread shutdownHook) {
doPrivileged(() -> Runtime.getRuntime().addShutdownHook(shutdownHook), new RuntimePermission("shutdownHooks"));
}
static void setUncaughtExceptionHandler(Thread thread, Thread.UncaughtExceptionHandler eh) {
doPrivileged(() -> thread.setUncaughtExceptionHandler(eh), new RuntimePermission("modifyThread"));
}
static void moveReplace(SafePath from, SafePath to) throws IOException {
doPrivilegedIOWithReturn(() -> Files.move(from.toPath(), to.toPath()));
}
static void clearDirectory(SafePath safePath) throws IOException {
doPriviligedIO(() -> Files.walkFileTree(safePath.toPath(), new DirectoryCleaner()));
}
static SafePath toRealPath(SafePath safePath) throws Exception {
return new SafePath(doPrivilegedIOWithReturn(() -> safePath.toPath().toRealPath()));
}
static boolean existDirectory(SafePath directory) throws IOException {
return doPrivilegedIOWithReturn(() -> Files.exists(directory.toPath()));
}
static RandomAccessFile createRandomAccessFile(SafePath path) throws Exception {
return doPrivilegedIOWithReturn(() -> new RandomAccessFile(path.toPath().toFile(), "rw"));
}
public static InputStream newFileInputStream(SafePath safePath) throws IOException {
return doPrivilegedIOWithReturn(() -> Files.newInputStream(safePath.toPath()));
}
public static long getFileSize(SafePath safePath) throws IOException {
return doPrivilegedIOWithReturn(() -> Files.size(safePath.toPath()));
}
static SafePath createDirectories(SafePath safePath) throws IOException {
Path p = doPrivilegedIOWithReturn(() -> Files.createDirectories(safePath.toPath()));
return new SafePath(p);
}
public static boolean exists(SafePath safePath) throws IOException {
return doPrivilegedIOWithReturn(() -> Files.exists(safePath.toPath()));
}
public static boolean isDirectory(SafePath safePath) throws IOException {
return doPrivilegedIOWithReturn(() -> Files.isDirectory(safePath.toPath()));
}
static void delete(SafePath localPath) throws IOException {
doPriviligedIO(() -> Files.delete(localPath.toPath()));
}
static boolean isWritable(SafePath safePath) throws IOException {
return doPrivilegedIOWithReturn(() -> Files.isWritable(safePath.toPath()));
}
static void deleteOnExit(SafePath safePath) {
doPrivileged(() -> safePath.toPath().toFile().deleteOnExit());
}
static ReadableByteChannel newFileChannelToRead(SafePath safePath) throws IOException {
return doPrivilegedIOWithReturn(() -> FileChannel.open(safePath.toPath(), StandardOpenOption.READ));
}
public static InputStream getResourceAsStream(String name) throws IOException {
return doPrivilegedIOWithReturn(() -> SecuritySupport.class.getResourceAsStream(name));
}
public static Reader newFileReader(SafePath safePath) throws FileNotFoundException, IOException {
return doPrivilegedIOWithReturn(() -> Files.newBufferedReader(safePath.toPath()));
}
static void touch(SafePath path) throws IOException {
doPriviligedIO(() -> new RandomAccessFile(path.toPath().toFile(), "rw").close());
}
static void setAccessible(Method method) {
doPrivileged(() -> method.setAccessible(true), new ReflectPermission("suppressAccessChecks"));
}
static void setAccessible(Field field) {
doPrivileged(() -> field.setAccessible(true), new ReflectPermission("suppressAccessChecks"));
}
static void setAccessible(Constructor<?> constructor) {
doPrivileged(() -> constructor.setAccessible(true), new ReflectPermission("suppressAccessChecks"));
}
static void ensureClassIsInitialized(Class<?> clazz) {
unsafe.ensureClassInitialized(clazz);
}
static Class<?> defineClass(String name, byte[] bytes, ClassLoader classLoader) {
return unsafe.defineClass(name, bytes, 0, bytes.length, classLoader, null);
}
static Thread createThreadWitNoPermissions(String threadName, Runnable runnable) {
return doPrivilegedWithReturn(() -> new Thread(runnable, threadName), new Permission[0]);
}
static void setDaemonThread(Thread t, boolean daeomn) {
doPrivileged(()-> t.setDaemon(daeomn), new RuntimePermission("modifyThread"));
}
public static SafePath getAbsolutePath(SafePath path) throws IOException {
return new SafePath(doPrivilegedIOWithReturn((()-> path.toPath().toAbsolutePath())));
}
}

View File

@@ -0,0 +1,292 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringJoiner;
import jdk.jfr.Event;
import jdk.jfr.internal.handlers.EventHandler;
final class SettingsManager {
private static class InternalSetting {
private final String identifier;
private Map<String, Set<String>> enabledMap = new LinkedHashMap<>(5);
private Map<String, Set<String>> allMap = new LinkedHashMap<>(5);
private boolean enabled;
/**
* Settings identifier, for example "com.example.HelloWorld" or "56"
* (id of event)
*
* @param settingsId
*/
public InternalSetting(String settingsId) {
this.identifier = settingsId;
}
public Set<String> getValues(String key) {
if (enabled) {
return enabledMap.get(key);
} else {
return allMap.get(key);
}
}
public void add(String attribute, String value) {
if ("enabled".equals(attribute) && "true".equals(value)) {
enabled = true;
allMap = null; // no need to keep these around
}
addToMap(enabledMap, attribute, value);
if (allMap != null) {
addToMap(allMap, attribute, value);
}
}
private void addToMap(Map<String, Set<String>> map, String attribute, String value) {
Set<String> values = map.get(attribute);
if (values == null) {
values = new HashSet<String>(5);
map.put(attribute, values);
}
values.add(value);
}
public String getSettingsId() {
return identifier;
}
public void add(InternalSetting enabled) {
for (Map.Entry<String, Set<String>> entry : enabled.enabledMap.entrySet()) {
for (String value : entry.getValue()) {
add(entry.getKey(), value);
}
}
}
public boolean isEnabled() {
return enabled;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(identifier);
sb.append(": ");
sb.append(enabledMap.toString());
return sb.toString();
}
public void finish() {
if (!enabled) {
// settings from disabled
// events should not impact results, but
// we can't clear enabledMap since enabled=false
// needs be there, so events that are enabled
// by default are turned off
Map<String, Set<String>> disabledMap = new HashMap<>(2);
Set<String> values = new HashSet<>(2);
values.add("false");
disabledMap.put("enabled", values);
enabledMap = disabledMap;
}
}
}
private Map<String, InternalSetting> availableSettings = new LinkedHashMap<>();
void setSettings(List<Map<String, String>> activeSettings) {
// store settings so they are available if a new event class is loaded
availableSettings = createSettingsMap(activeSettings);
List<EventControl> eventControls = MetadataRepository.getInstance().getEventControls();
if (!JVM.getJVM().isRecording()) {
for (EventControl ec : eventControls) {
ec.disable();
}
} else {
if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) {
Collections.sort(eventControls, (x,y) -> x.getEventType().getName().compareTo(y.getEventType().getName()));
}
for (EventControl ec : eventControls) {
setEventControl(ec);
}
}
if (JVM.getJVM().getAllowedToDoEventRetransforms()) {
updateRetransform(JVM.getJVM().getAllEventClasses());
}
}
public void updateRetransform(List<Class<? extends Event>> eventClasses) {
List<Class<?>> classes = new ArrayList<>();
for(Class<? extends Event> eventClass: eventClasses) {
EventHandler eh = Utils.getHandler(eventClass);
if (eh != null ) {
PlatformEventType eventType = eh.getPlatformEventType();
if (eventType.isMarkedForInstrumentation()) {
classes.add(eventClass);
eventType.markForInstrumentation(false);
// A bit premature to set it here, but hard to check
// after call to retransformClasses.
eventType.setInstrumented();
}
}
}
if (!classes.isEmpty()) {
JVM.getJVM().retransformClasses(classes.toArray(new Class<?>[0]));
}
}
private Map<String, InternalSetting> createSettingsMap(List<Map<String,String>> activeSettings) {
Map<String, InternalSetting> map = new LinkedHashMap<>(activeSettings.size());
for (Map<String, String> rec : activeSettings) {
for (InternalSetting internal : makeInternalSettings(rec)) {
InternalSetting is = map.get(internal.getSettingsId());
if (is == null) {
map.put(internal.getSettingsId(), internal);
} else {
is.add(internal);
}
}
}
return map;
}
private Collection<InternalSetting> makeInternalSettings(Map<String, String> rec) {
Map<String, InternalSetting> internals = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : rec.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
int index = key.indexOf("#");
if (index > 1 && index < key.length() - 2) {
String eventName = key.substring(0, index);
eventName = Utils.upgradeLegacyJDKEvent(eventName);
InternalSetting s = internals.get(eventName);
String settingName = key.substring(index + 1).trim();
if (s == null) {
s = new InternalSetting(eventName);
internals.put(eventName, s);
}
s.add(settingName, value);
}
}
for (InternalSetting s : internals.values()) {
s.finish();
}
return internals.values();
}
void setEventControl(EventControl ec) {
InternalSetting is = getInternalSetting(ec);
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "Applied settings for " + ec.getEventType().getLogName() + " {");
for (Entry<String, Control> entry : ec.getEntries()) {
Set<String> values = null;
String settingName = entry.getKey();
if (is != null) {
values = is.getValues(settingName);
}
Control control = entry.getValue();
if (values != null) {
control.apply(values);
String after = control.getLastValue();
if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) {
if (Utils.isSettingVisible(control, ec.getEventType().hasEventHook())) {
if (values.size() > 1) {
StringJoiner sj = new StringJoiner(", ", "{", "}");
for (String s : values) {
sj.add("\"" + s + "\"");
}
String message = " " + settingName + "= " + sj.toString() + " => \"" + after + "\"";
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message);
} else {
String message = " " + settingName + "=\"" + control.getLastValue() + "\"";
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message);
}
}
}
} else {
control.setDefault();
if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) {
String message = " " + settingName + "=\"" + control.getLastValue() + "\"";
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message);
}
}
}
ec.writeActiveSettingEvent();
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "}");
}
private InternalSetting getInternalSetting(EventControl ec) {
String name = ec.getEventType().getName();
InternalSetting nameBased = availableSettings.get(name);
InternalSetting idBased = availableSettings.get(ec.getSettingsId());
if (nameBased == null && idBased == null) {
return null;
}
if (idBased == null) {
return nameBased;
}
if (nameBased == null) {
return idBased;
}
InternalSetting mixed = new InternalSetting(nameBased.getSettingsId());
mixed.add(nameBased);
mixed.add(idBased);
return mixed;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (InternalSetting enabled : availableSettings.values()) {
sb.append(enabled.toString());
sb.append("\n");
}
return sb.toString();
}
boolean isEnabled(String eventName) {
InternalSetting is = availableSettings.get(eventName);
if (is == null) {
return false;
}
return is.isEnabled();
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.IOException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import jdk.jfr.RecordingState;
/**
* Class responsible for dumping recordings on exit
*
*/
final class ShutdownHook implements Runnable {
private final PlatformRecorder recorder;
Object tlabDummyObject;
ShutdownHook(PlatformRecorder recorder) {
this.recorder = recorder;
}
@Override
public void run() {
// this allocation is done in order to fetch a new TLAB before
// starting any "real" operations. In low memory situations,
// we would like to take an OOM as early as possible.
tlabDummyObject = new Object();
for (PlatformRecording recording : recorder.getRecordings()) {
if (recording.getDumpOnExit() && recording.getState() == RecordingState.RUNNING) {
dump(recording);
}
}
recorder.destroy();
}
private void dump(PlatformRecording recording) {
try {
WriteableUserPath dest = recording.getDestination();
if (dest == null) {
dest = makeDumpOnExitPath(recording);
recording.setDestination(dest);
}
if (dest != null) {
recording.stop("Dump on exit");
}
} catch (Exception e) {
Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Could not dump recording " + recording.getName() + " on exit.");
}
}
private WriteableUserPath makeDumpOnExitPath(PlatformRecording recording) {
try {
String name = Utils.makeFilename(recording.getRecording());
AccessControlContext acc = recording.getNoDestinationDumpOnExitAccessControlContext();
return AccessController.doPrivileged(new PrivilegedExceptionAction<WriteableUserPath>() {
@Override
public WriteableUserPath run() throws Exception {
return new WriteableUserPath(recording.getDumpOnExitDirectory().toPath().resolve(name));
}
}, acc);
} catch (PrivilegedActionException e) {
Throwable t = e.getCause();
if (t instanceof SecurityException) {
Logger.log(LogTag.JFR, LogLevel.WARN, "Not allowed to create dump path for recording " + recording.getId() + " on exit.");
}
if (t instanceof IOException) {
Logger.log(LogTag.JFR, LogLevel.WARN, "Could not dump " + recording.getId() + " on exit.");
}
return null;
}
}
static final class ExceptionHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
JVM.getJVM().uncaughtException(t, e);
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import sun.misc.Unsafe;
public final class StringPool {
private static final Unsafe unsafe = Unsafe.getUnsafe();
static final int MIN_LIMIT = 16;
static final int MAX_LIMIT = 128; /* 0 MAX means disabled */
private static final long epochAddress;
private static final SimpleStringIdPool sp = new SimpleStringIdPool();
static {
epochAddress = JVM.getJVM().getEpochAddress();
sp.reset();
}
public static long addString(String s) {
return sp.addString(s);
}
private static boolean getCurrentEpoch() {
return unsafe.getByte(epochAddress) == 1;
}
private static class SimpleStringIdPool {
/* string id index */
private final AtomicLong sidIdx = new AtomicLong();
/* epoch of cached strings */
private boolean poolEpoch;
/* the cache */
private final ConcurrentHashMap<String, Long> cache;
/* max size */
private final int MAX_SIZE = 32*1024;
/* max size bytes*/
private final long MAX_SIZE_UTF16 = 16*1024*1024;
/* max size bytes*/
private long currentSizeUTF16;
/* looking at a biased data set 4 is a good value */
private final String[] preCache = new String[]{"", "" , "" ,""};
/* index of oldest */
private int preCacheOld = 0;
/* loop mask */
private static final int preCacheMask = 0x03;
SimpleStringIdPool() {
cache = new ConcurrentHashMap<>(MAX_SIZE, 0.75f);
}
void reset() {
reset(getCurrentEpoch());
}
private void reset(boolean epoch) {
this.cache.clear();
this.poolEpoch = epoch;
this.currentSizeUTF16 = 0;
}
private long addString(String s) {
boolean currentEpoch = getCurrentEpoch();
if (poolEpoch == currentEpoch) {
/* pool is for current chunk */
Long lsid = this.cache.get(s);
if (lsid != null) {
return lsid.longValue();
}
} else {
/* pool is for an old chunk */
reset(currentEpoch);
}
if (!preCache(s)) {
/* we should not pool this string */
return -1;
}
if (cache.size() > MAX_SIZE || currentSizeUTF16 > MAX_SIZE_UTF16) {
/* pool was full */
reset(currentEpoch);
}
return storeString(s);
}
private long storeString(String s) {
long sid = this.sidIdx.getAndIncrement();
/* we can race but it is ok */
this.cache.put(s, sid);
boolean currentEpoch;
synchronized(SimpleStringIdPool.class) {
currentEpoch = JVM.addStringConstant(poolEpoch, sid, s);
currentSizeUTF16 += s.length();
}
/* did we write in chunk that this pool represent */
return currentEpoch == poolEpoch ? sid : -1;
}
private boolean preCache(String s) {
if (preCache[0].equals(s)) {
return true;
}
if (preCache[1].equals(s)) {
return true;
}
if (preCache[2].equals(s)) {
return true;
}
if (preCache[3].equals(s)) {
return true;
}
preCacheOld = (preCacheOld - 1) & preCacheMask;
preCache[preCacheOld] = s;
return false;
}
}
}

View File

@@ -0,0 +1,326 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Event;
import jdk.jfr.SettingControl;
import jdk.jfr.ValueDescriptor;
/**
* Internal data structure that describes a type,
*
* Used to create event types, value descriptor and annotations.
*
*/
public class Type implements Comparable<Type> {
public static final String SUPER_TYPE_ANNOTATION = java.lang.annotation.Annotation.class.getName();
public static final String SUPER_TYPE_SETTING = SettingControl.class.getName();
public static final String SUPER_TYPE_EVENT = Event.class.getName();
public static final String EVENT_NAME_PREFIX = "jdk.";
public static final String TYPES_PREFIX = "jdk.types.";
public static final String SETTINGS_PREFIX = "jdk.settings.";
// Initialization of known types
private final static Map<Type, Class<?>> knownTypes = new HashMap<>();
static final Type BOOLEAN = register(boolean.class, new Type("boolean", null, 4));
static final Type CHAR = register(char.class, new Type("char", null, 5));
static final Type FLOAT = register(float.class, new Type("float", null, 6));
static final Type DOUBLE = register(double.class, new Type("double", null, 7));
static final Type BYTE = register(byte.class, new Type("byte", null, 8));
static final Type SHORT = register(short.class, new Type("short", null, 9));
static final Type INT = register(int.class, new Type("int", null, 10));
static final Type LONG = register(long.class, new Type("long", null, 11));
static final Type CLASS = register(Class.class, new Type("java.lang.Class", null, 20));
static final Type STRING = register(String.class, new Type("java.lang.String", null, 21));
static final Type THREAD = register(Thread.class, new Type("java.lang.Thread", null, 22));
static final Type STACK_TRACE = register(null, new Type(TYPES_PREFIX + "StackTrace", null, 23));
private final AnnotationConstruct annos = new AnnotationConstruct();
private final String name;
private final String superType;
private final boolean constantPool;
private final long id;
private List<ValueDescriptor> fields = new ArrayList<>();
private Boolean simpleType; // calculated lazy
private boolean remove = true;
/**
* Creates a type
*
* @param javaTypeName i.e "java.lang.String"
* @param superType i.e "java.lang.Annotation"
* @param id the class id that represents the class in the JVM
*
*/
public Type(String javaTypeName, String superType, long typeId) {
this(javaTypeName, superType, typeId, false);
}
Type(String javaTypeName, String superType, long typeId, boolean contantPool) {
this(javaTypeName, superType, typeId, contantPool, null);
}
Type(String javaTypeName, String superType, long typeId, boolean contantPool, Boolean simpleType) {
Objects.requireNonNull(javaTypeName);
if (!isValidJavaIdentifier(javaTypeName)) {
throw new IllegalArgumentException(javaTypeName + " is not a valid Java identifier");
}
this.constantPool = contantPool;
this.superType = superType;
this.name = javaTypeName;
this.id = typeId;
this.simpleType = simpleType;
}
static boolean isDefinedByJVM(long id) {
return id < JVM.RESERVED_CLASS_ID_LIMIT;
}
public static long getTypeId(Class<?> clazz) {
Type type = Type.getKnownType(clazz);
return type == null ? JVM.getJVM().getTypeId(clazz) : type.getId();
}
static Collection<Type> getKnownTypes() {
return knownTypes.keySet();
}
public static boolean isValidJavaIdentifier(String identifier) {
if (identifier.isEmpty()) {
return false;
}
if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
return false;
}
for (int i = 1; i < identifier.length(); i++) {
char c = identifier.charAt(i);
if (c != '.') {
if (!Character.isJavaIdentifierPart(c)) {
return false;
}
}
}
return true;
}
public static boolean isValidJavaFieldType(String name) {
for (Map.Entry<Type, Class<?>> entry : knownTypes.entrySet()) {
Class<?> clazz = entry.getValue();
if (clazz != null && name.equals(clazz.getName())) {
return true;
}
}
return false;
}
public static Type getKnownType(String typeName) {
for (Type type : knownTypes.keySet()) {
if (type.getName().equals(typeName)) {
return type;
}
}
return null;
}
static boolean isKnownType(Class<?> type) {
if (type.isPrimitive()) {
return true;
}
if (type.equals(Class.class) || type.equals(Thread.class) || type.equals(String.class)) {
return true;
}
return false;
}
public static Type getKnownType(Class<?> clazz) {
for (Map.Entry<Type, Class<?>> entry : knownTypes.entrySet()) {
if (clazz != null && clazz.equals(entry.getValue())) {
return entry.getKey();
}
}
return null;
}
public String getName() {
return name;
}
public String getLogName() {
return getName() + "(" + getId() + ")";
}
public List<ValueDescriptor> getFields() {
if (fields instanceof ArrayList) {
((ArrayList<ValueDescriptor>) fields).trimToSize();
fields = Collections.unmodifiableList(fields);
}
return fields;
}
public boolean isSimpleType() {
if (simpleType == null) {
simpleType = calculateSimpleType();
}
return simpleType.booleanValue();
}
private boolean calculateSimpleType() {
if (fields.size() != 1) {
return false;
}
// annotation, settings and event can never be simple types
return superType == null;
}
public boolean isDefinedByJVM() {
return id < JVM.RESERVED_CLASS_ID_LIMIT;
}
private static Type register(Class<?> clazz, Type type) {
knownTypes.put(type, clazz);
return type;
}
public void add(ValueDescriptor valueDescriptor) {
Objects.requireNonNull(valueDescriptor);
fields.add(valueDescriptor);
}
void trimFields() {
getFields();
}
void setAnnotations(List<AnnotationElement> annotations) {
annos.setAnnotationElements(annotations);
}
public String getSuperType() {
return superType;
}
public long getId() {
return id;
}
public boolean isConstantPool() {
return constantPool;
}
public String getLabel() {
return annos.getLabel();
}
public List<AnnotationElement> getAnnotationElements() {
return annos.getUnmodifiableAnnotationElements();
}
public <T> T getAnnotation(Class<? extends java.lang.annotation.Annotation> clazz) {
return annos.getAnnotation(clazz);
}
public String getDescription() {
return annos.getDescription();
}
@Override
public int hashCode() {
return Long.hashCode(id);
}
@Override
public boolean equals(Object object) {
if (object instanceof Type) {
Type that = (Type) object;
return that.id == this.id;
}
return false;
}
@Override
public int compareTo(Type that) {
return Long.compare(this.id, that.id);
}
void log(String action, LogTag logTag, LogLevel level) {
if (Logger.shouldLog(logTag, level) && !isSimpleType()) {
Logger.log(logTag, LogLevel.TRACE, action + " " + typeText() + " " + getLogName() + " {");
for (ValueDescriptor v : getFields()) {
String array = v.isArray() ? "[]" : "";
Logger.log(logTag, LogLevel.TRACE, " " + v.getTypeName() + array + " " + v.getName() + ";");
}
Logger.log(logTag, LogLevel.TRACE, "}");
} else {
if (Logger.shouldLog(logTag, LogLevel.INFO) && !isSimpleType()) {
Logger.log(logTag, LogLevel.INFO, action + " " + typeText() + " " + getLogName());
}
}
}
private String typeText() {
if (this instanceof PlatformEventType) {
return "event type";
}
if (Type.SUPER_TYPE_SETTING.equals(superType)) {
return "setting type";
}
if (Type.SUPER_TYPE_ANNOTATION.equals(superType)) {
return "annotation type";
}
return "type";
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getLogName());
if (!getFields().isEmpty()) {
sb.append(" {\n");
for (ValueDescriptor td : getFields()) {
sb.append(" type=" + td.getTypeName() + "(" + td.getTypeId() + ") name=" + td.getName() + "\n");
}
sb.append("}\n");
}
return sb.toString();
}
public void setRemove(boolean remove) {
this.remove = remove;
}
public boolean getRemove() {
return remove;
}
}

View File

@@ -0,0 +1,492 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import jdk.jfr.AnnotationElement;
import jdk.jfr.Description;
import jdk.jfr.Event;
import jdk.jfr.Label;
import jdk.jfr.MetadataDefinition;
import jdk.jfr.Name;
import jdk.jfr.SettingDescriptor;
import jdk.jfr.Timespan;
import jdk.jfr.Timestamp;
import jdk.jfr.ValueDescriptor;
public final class TypeLibrary {
private static TypeLibrary instance;
private static final Map<Long, Type> types = new LinkedHashMap<>(100);
static final ValueDescriptor DURATION_FIELD = createDurationField();
static final ValueDescriptor THREAD_FIELD = createThreadField();
static final ValueDescriptor STACK_TRACE_FIELD = createStackTraceField();
static final ValueDescriptor START_TIME_FIELD = createStartTimeField();
private TypeLibrary(List<Type> jvmTypes) {
visitReachable(jvmTypes, t -> !types.containsKey(t.getId()), t -> types.put(t.getId(), t));
if (Logger.shouldLog(LogTag.JFR_SYSTEM_METADATA, LogLevel.INFO)) {
Stream<Type> s = types.values().stream().sorted((x, y) -> Long.compare(x.getId(), y.getId()));
s.forEach(t -> t.log("Added", LogTag.JFR_SYSTEM_METADATA, LogLevel.INFO));
}
}
private static ValueDescriptor createStartTimeField() {
List<AnnotationElement> annos = createStandardAnnotations("Start Time", null);
annos.add(new jdk.jfr.AnnotationElement(Timestamp.class, Timestamp.TICKS));
return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_START_TIME, Type.LONG, annos, 0, false,
EventInstrumentation.FIELD_START_TIME);
}
private static ValueDescriptor createStackTraceField() {
List<AnnotationElement> annos = new ArrayList<>();
annos = createStandardAnnotations("Stack Trace", "Stack Trace starting from the method the event was committed in");
return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_STACK_TRACE, Type.STACK_TRACE, annos, 0, true,
EventInstrumentation.FIELD_STACK_TRACE);
}
private static ValueDescriptor createThreadField() {
List<AnnotationElement> annos = new ArrayList<>();
annos = createStandardAnnotations("Event Thread", "Thread in which event was committed in");
return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_EVENT_THREAD, Type.THREAD, annos, 0, true,
EventInstrumentation.FIELD_EVENT_THREAD);
}
private static ValueDescriptor createDurationField() {
List<AnnotationElement> annos = new ArrayList<>();
annos = createStandardAnnotations("Duration", null);
annos.add(new jdk.jfr.AnnotationElement(Timespan.class, Timespan.TICKS));
return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_DURATION, Type.LONG, annos, 0, false, EventInstrumentation.FIELD_DURATION);
}
public static TypeLibrary getInstance() {
synchronized (TypeLibrary.class) {
if (instance == null) {
List<Type> jvmTypes;
try {
jvmTypes = MetadataHandler.createTypes();
Collections.sort(jvmTypes, (a,b) -> Long.compare(a.getId(), b.getId()));
} catch (IOException e) {
throw new Error("JFR: Could not read metadata");
}
instance = new TypeLibrary(jvmTypes);
}
return instance;
}
}
public List<Type> getTypes() {
return new ArrayList<>(types.values());
}
public static Type createAnnotationType(Class<? extends Annotation> a) {
if (shouldPersist(a)) {
Type type = defineType(a, Type.SUPER_TYPE_ANNOTATION, false);
if (type != null) {
SecuritySupport.makeVisibleToJFR(a);
for (Method method : a.getDeclaredMethods()) {
type.add(PrivateAccess.getInstance().newValueDescriptor(method.getReturnType(), method.getName()));
}
ArrayList<AnnotationElement> aes = new ArrayList<>();
for (Annotation annotation : resolveRepeatedAnnotations(a.getAnnotations())) {
AnnotationElement ae = createAnnotation(annotation);
if (ae != null) {
aes.add(ae);
}
}
aes.trimToSize();
type.setAnnotations(aes);
}
return getType(a);
}
return null;
}
static AnnotationElement createAnnotation(Annotation annotation) {
Class<? extends Annotation> annotationType = annotation.annotationType();
Type type = createAnnotationType(annotationType);
if (type != null) {
List<Object> values = new ArrayList<>();
for (ValueDescriptor v : type.getFields()) {
values.add(invokeAnnotation(annotation, v.getName()));
}
return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null);
}
return null;
}
private static Object invokeAnnotation(Annotation annotation, String methodName) {
final Method m;
try {
m = annotation.getClass().getMethod(methodName, new Class<?>[0]);
} catch (NoSuchMethodException e1) {
throw (Error) new InternalError("Could not loacate method " + methodName + " in annotation " + annotation.getClass().getName());
}
SecuritySupport.setAccessible(m);
try {
return m.invoke(annotation, new Object[0]);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw (Error) new InternalError("Could not get value for method " + methodName + " in annotation " + annotation.getClass().getName());
}
}
private static boolean shouldPersist(Class<? extends Annotation> a) {
if (a == MetadataDefinition.class || a.getAnnotation(MetadataDefinition.class) == null) {
return false;
}
return true;
}
private static boolean isDefined(Class<?> clazz) {
return types.containsKey(Type.getTypeId(clazz));
}
private static Type getType(Class<?> clazz) {
return types.get(Type.getTypeId(clazz));
}
private static Type defineType(Class<?> clazz, String superType, boolean eventType) {
if (!isDefined(clazz)) {
Name name = clazz.getAnnotation(Name.class);
String typeName = name != null ? name.value() : clazz.getName();
long id = Type.getTypeId(clazz);
Type t;
if (eventType) {
t = new PlatformEventType(typeName, id, clazz.getClassLoader() == null, true);
} else {
t = new Type(typeName, superType, id);
}
types.put(t.getId(), t);
return t;
}
return null;
}
public static Type createType(Class<?> clazz) {
return createType(clazz, Collections.emptyList(), Collections.emptyList());
}
public static Type createType(Class<?> clazz, List<AnnotationElement> dynamicAnnotations, List<ValueDescriptor> dynamicFields) {
if (Thread.class == clazz) {
return Type.THREAD;
}
if (Class.class.isAssignableFrom(clazz)) {
return Type.CLASS;
}
if (String.class.equals(clazz)) {
return Type.STRING;
}
if (isDefined(clazz)) {
return getType(clazz);
}
if (clazz.isPrimitive()) {
return defineType(clazz, null,false);
}
if (clazz.isArray()) {
throw new InternalError("Arrays not supported");
}
// STRUCT
String superType = null;
boolean eventType = false;
if (Event.class.isAssignableFrom(clazz)) {
superType = Type.SUPER_TYPE_EVENT;
eventType= true;
}
if (Control.class.isAssignableFrom(clazz)) {
superType = Type.SUPER_TYPE_SETTING;
}
// forward declare to avoid infinite recursion
defineType(clazz, superType, eventType);
Type type = getType(clazz);
if (eventType) {
addImplicitFields(type, true, true, true, true ,false);
addUserFields(clazz, type, dynamicFields);
type.trimFields();
}
addAnnotations(clazz, type, dynamicAnnotations);
if (clazz.getClassLoader() == null) {
type.log("Added", LogTag.JFR_SYSTEM_METADATA, LogLevel.INFO);
} else {
type.log("Added", LogTag.JFR_METADATA, LogLevel.INFO);
}
return type;
}
private static void addAnnotations(Class<?> clazz, Type type, List<AnnotationElement> dynamicAnnotations) {
ArrayList<AnnotationElement> aes = new ArrayList<>();
if (dynamicAnnotations.isEmpty()) {
for (Annotation a : Utils.getAnnotations(clazz)) {
AnnotationElement ae = createAnnotation(a);
if (ae != null) {
aes.add(ae);
}
}
} else {
List<Type> newTypes = new ArrayList<>();
aes.addAll(dynamicAnnotations);
for (AnnotationElement ae : dynamicAnnotations) {
newTypes.add(PrivateAccess.getInstance().getType(ae));
}
addTypes(newTypes);
}
type.setAnnotations(aes);
aes.trimToSize();
}
private static void addUserFields(Class<?> clazz, Type type, List<ValueDescriptor> dynamicFields) {
Map<String, ValueDescriptor> dynamicFieldSet = new HashMap<>();
for (ValueDescriptor dynamicField : dynamicFields) {
dynamicFieldSet.put(dynamicField.getName(), dynamicField);
}
List<Type> newTypes = new ArrayList<>();
for (Field field : Utils.getVisibleEventFields(clazz)) {
ValueDescriptor vd = dynamicFieldSet.get(field.getName());
if (vd != null) {
if (!vd.getTypeName().equals(field.getType().getName())) {
throw new InternalError("Type expected to match for field " + vd.getName() + " expected " + field.getName() + " but got " + vd.getName());
}
for (AnnotationElement ae : vd.getAnnotationElements()) {
newTypes.add(PrivateAccess.getInstance().getType(ae));
}
newTypes.add(PrivateAccess.getInstance().getType(vd));
} else {
vd = createField(field);
}
if (vd != null) {
type.add(vd);
}
}
addTypes(newTypes);
}
// By convention all events have these fields.
static void addImplicitFields(Type type, boolean requestable, boolean hasDuration, boolean hasThread, boolean hasStackTrace, boolean hasCutoff) {
createAnnotationType(Timespan.class);
createAnnotationType(Timestamp.class);
createAnnotationType(Label.class);
defineType(long.class, null,false);
addFields(type, requestable, hasDuration, hasThread, hasStackTrace, hasCutoff);
}
private static void addFields(Type type, boolean requestable, boolean hasDuration, boolean hasThread, boolean hasStackTrace, boolean hasCutoff) {
type.add(START_TIME_FIELD);
if (hasDuration || hasCutoff) {
type.add(DURATION_FIELD);
}
if (hasThread) {
type.add(THREAD_FIELD);
}
if (hasStackTrace) {
type.add(STACK_TRACE_FIELD);
}
}
private static List<AnnotationElement> createStandardAnnotations(String name, String description) {
List<AnnotationElement> annotationElements = new ArrayList<>(2);
annotationElements.add(new jdk.jfr.AnnotationElement(Label.class, name));
if (description != null) {
annotationElements.add(new jdk.jfr.AnnotationElement(Description.class, description));
}
return annotationElements;
}
private static ValueDescriptor createField(Field field) {
int mod = field.getModifiers();
if (Modifier.isTransient(mod)) {
return null;
}
if (Modifier.isStatic(mod)) {
return null;
}
Class<?> fieldType = field.getType();
if (!Type.isKnownType(fieldType)) {
return null;
}
boolean constantPool = Thread.class == fieldType || fieldType == Class.class;
Type type = createType(fieldType);
String fieldName = field.getName();
Name name = field.getAnnotation(Name.class);
String useName = fieldName;
if (name != null) {
useName = name.value();
}
List<jdk.jfr.AnnotationElement> ans = new ArrayList<>();
for (Annotation a : resolveRepeatedAnnotations(field.getAnnotations())) {
AnnotationElement ae = createAnnotation(a);
if (ae != null) {
ans.add(ae);
}
}
return PrivateAccess.getInstance().newValueDescriptor(useName, type, ans, 0, constantPool, fieldName);
}
private static List<Annotation> resolveRepeatedAnnotations(Annotation[] annotations) {
List<Annotation> annos = new ArrayList<>(annotations.length);
for (Annotation a : annotations) {
boolean repeated = false;
Method m;
try {
m = a.annotationType().getMethod("value");
Class<?> returnType = m.getReturnType();
if (returnType.isArray()) {
Class<?> ct = returnType.getComponentType();
if (Annotation.class.isAssignableFrom(ct) && ct.getAnnotation(Repeatable.class) != null) {
Object res = m.invoke(a, new Object[0]);
if (res != null && Annotation[].class.isAssignableFrom(res.getClass())) {
for (Annotation rep : (Annotation[]) m.invoke(a, new Object[0])) {
annos.add(rep);
}
repeated = true;
}
}
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// Ignore, can't access repeatable information
}
if (!repeated) {
annos.add(a);
}
}
return annos;
}
// Purpose of this method is to mark types that are reachable
// from registered event types. Those types that are not reachable can
// safely be removed
public boolean clearUnregistered() {
Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Cleaning out obsolete metadata");
List<Type> registered = new ArrayList<>();
for (Type type : types.values()) {
if (type instanceof PlatformEventType) {
if (((PlatformEventType) type).isRegistered()) {
registered.add(type);
}
}
}
visitReachable(registered, t -> t.getRemove(), t -> t.setRemove(false));
List<Long> removeIds = new ArrayList<>();
for (Type type : types.values()) {
if (type.getRemove() && !Type.isDefinedByJVM(type.getId())) {
removeIds.add(type.getId());
if (Logger.shouldLog(LogTag.JFR_METADATA, LogLevel.TRACE)) {
Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Removed obsolete metadata " + type.getName());
}
}
// Optimization, set to true now to avoid iterating
// types first thing at next call to clearUnregistered
type.setRemove(true);
}
for (Long id : removeIds) {
types.remove(id);
}
return !removeIds.isEmpty();
}
public void addType(Type type) {
addTypes(Collections.singletonList(type));
}
public static void addTypes(List<Type> ts) {
if (!ts.isEmpty()) {
visitReachable(ts, t -> !types.containsKey(t.getId()), t -> types.put(t.getId(), t));
}
}
/**
* Iterates all reachable types from a start collection
*
* @param rootSet the types to start from
* @param p if a type should be accepted
* @param c action to take on an accepted type
*/
private static void visitReachable(Collection<Type> rootSet, Predicate<Type> p, Consumer<Type> c) {
Queue<Type> typeQ = new ArrayDeque<>(rootSet);
while (!typeQ.isEmpty()) {
Type type = typeQ.poll();
if (p.test(type)) {
c.accept(type);
visitAnnotations(typeQ, type.getAnnotationElements());
for (ValueDescriptor v : type.getFields()) {
typeQ.add(PrivateAccess.getInstance().getType(v));
visitAnnotations(typeQ, v.getAnnotationElements());
}
if (type instanceof PlatformEventType) {
PlatformEventType pe = (PlatformEventType) type;
for (SettingDescriptor s : pe.getAllSettings()) {
typeQ.add(PrivateAccess.getInstance().getType(s));
visitAnnotations(typeQ, s.getAnnotationElements());
}
}
}
}
}
private static void visitAnnotations(Queue<Type> typeQ, List<AnnotationElement> aes) {
Queue<AnnotationElement> aQ = new ArrayDeque<>(aes);
Set<AnnotationElement> visited = new HashSet<>();
while (!aQ.isEmpty()) {
AnnotationElement ae = aQ.poll();
if (!visited.contains(ae)) {
Type ty = PrivateAccess.getInstance().getType(ae);
typeQ.add(ty);
visited.add(ae);
}
aQ.addAll(ae.getAnnotationElements());
}
}
}

View File

@@ -0,0 +1,554 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import jdk.jfr.Event;
import jdk.jfr.FlightRecorderPermission;
import jdk.jfr.Recording;
import jdk.jfr.RecordingState;
import jdk.jfr.internal.handlers.EventHandler;
import jdk.jfr.internal.settings.PeriodSetting;
import jdk.jfr.internal.settings.StackTraceSetting;
import jdk.jfr.internal.settings.ThresholdSetting;
public final class Utils {
private static final String INFINITY = "infinity";
private static Boolean SAVE_GENERATED;
public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events";
public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument";
public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers";
public static final String REGISTER_EVENT = "registerEvent";
public static final String ACCESS_FLIGHT_RECORDER = "accessFlightRecorder";
private final static String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk.";
public static void checkAccessFlightRecorder() throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new FlightRecorderPermission(ACCESS_FLIGHT_RECORDER));
}
}
public static void checkRegisterPermission() throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new FlightRecorderPermission(REGISTER_EVENT));
}
}
private static enum TimespanUnit {
NANOSECONDS("ns", 1000), MICROSECONDS("us", 1000), MILLISECONDS("ms", 1000), SECONDS("s", 60), MINUTES("m", 60), HOURS("h", 24), DAYS("d", 7);
final String text;
final long amount;
TimespanUnit(String unit, long amount) {
this.text = unit;
this.amount = amount;
}
}
// Tjis method can't handle Long.MIN_VALUE because absolute value is negative
private static String formatDataAmount(String formatter, long amount) {
int exp = (int) (Math.log(Math.abs(amount)) / Math.log(1024));
char unitPrefix = "kMGTPE".charAt(exp - 1);
return String.format(formatter, amount / Math.pow(1024, exp), unitPrefix);
}
public static String formatBytesCompact(long bytes) {
if (bytes < 1024) {
return String.valueOf(bytes);
}
return formatDataAmount("%.1f%cB", bytes);
}
public static String formatBits(long bits) {
if (bits == 1 || bits == -1) {
return bits + " bit";
}
if (bits < 1024 && bits > -1024) {
return bits + " bits";
}
return formatDataAmount("%.1f %cbit", bits);
}
public static String formatBytes(long bytes) {
if (bytes == 1 || bytes == -1) {
return bytes + " byte";
}
if (bytes < 1024 && bytes > -1024) {
return bytes + " bytes";
}
return formatDataAmount("%.1f %cB", bytes);
}
public static String formatBytesPerSecond(long bytes) {
if (bytes < 1024 && bytes > -1024) {
return bytes + " byte/s";
}
return formatDataAmount("%.1f %cB/s", bytes);
}
public static String formatBitsPerSecond(long bits) {
if (bits < 1024 && bits > -1024) {
return bits + " bps";
}
return formatDataAmount("%.1f %cbps", bits);
}
public static String formatTimespan(Duration dValue, String separation) {
if (dValue == null) {
return "0";
}
long value = dValue.toNanos();
TimespanUnit result = TimespanUnit.NANOSECONDS;
for (TimespanUnit unit : TimespanUnit.values()) {
result = unit;
long amount = unit.amount;
if (result == TimespanUnit.DAYS || value < amount || value % amount != 0) {
break;
}
value /= amount;
}
return String.format("%d%s%s", value, separation, result.text);
}
public static long parseTimespanWithInfinity(String s) {
if (INFINITY.equals(s)) {
return Long.MAX_VALUE;
}
return parseTimespan(s);
}
public static long parseTimespan(String s) {
if (s.endsWith("ns")) {
return Long.parseLong(s.substring(0, s.length() - 2).trim());
}
if (s.endsWith("us")) {
return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 2).trim()), MICROSECONDS);
}
if (s.endsWith("ms")) {
return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 2).trim()), MILLISECONDS);
}
if (s.endsWith("s")) {
return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
}
if (s.endsWith("m")) {
return 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
}
if (s.endsWith("h")) {
return 60 * 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
}
if (s.endsWith("d")) {
return 24 * 60 * 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
}
try {
Long.parseLong(s);
} catch (NumberFormatException nfe) {
throw new NumberFormatException("'" + s + "' is not a valid timespan. Shoule be numeric value followed by a unit, i.e. 20 ms. Valid units are ns, us, s, m, h and d.");
}
// Only accept values with units
throw new NumberFormatException("Timespan + '" + s + "' is missing unit. Valid units are ns, us, s, m, h and d.");
}
/**
* Return all annotations as they are visible in the source code
*
* @param clazz class to return annotations from
*
* @return list of annotation
*
*/
static List<Annotation> getAnnotations(Class<?> clazz) {
List<Annotation> annos = new ArrayList<>();
for (Annotation a : clazz.getAnnotations()) {
annos.addAll(getAnnotation(a));
}
return annos;
}
private static List<? extends Annotation> getAnnotation(Annotation a) {
Class<?> annotated = a.annotationType();
Method valueMethod = getValueMethod(annotated);
if (valueMethod != null) {
Class<?> returnType = valueMethod.getReturnType();
if (returnType.isArray()) {
Class<?> candidate = returnType.getComponentType();
Repeatable r = candidate.getAnnotation(Repeatable.class);
if (r != null) {
Class<?> repeatClass = r.value();
if (annotated == repeatClass) {
return getAnnotationValues(a, valueMethod);
}
}
}
}
List<Annotation> annos = new ArrayList<>();
annos.add(a);
return annos;
}
static boolean isAfter(RecordingState stateToTest, RecordingState b) {
return stateToTest.ordinal() > b.ordinal();
}
static boolean isBefore(RecordingState stateToTest, RecordingState b) {
return stateToTest.ordinal() < b.ordinal();
}
static boolean isState(RecordingState stateToTest, RecordingState... states) {
for (RecordingState s : states) {
if (s == stateToTest) {
return true;
}
}
return false;
}
private static List<Annotation> getAnnotationValues(Annotation a, Method valueMethod) {
try {
return Arrays.asList((Annotation[]) valueMethod.invoke(a, new Object[0]));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
return new ArrayList<>();
}
}
private static Method getValueMethod(Class<?> annotated) {
try {
return annotated.getMethod("value", new Class<?>[0]);
} catch (NoSuchMethodException e) {
return null;
}
}
public static void touch(Path dumpFile) throws IOException {
RandomAccessFile raf = new RandomAccessFile(dumpFile.toFile(), "rw");
raf.close();
}
public static Class<?> unboxType(Class<?> t) {
if (t == Integer.class) {
return int.class;
}
if (t == Long.class) {
return long.class;
}
if (t == Float.class) {
return float.class;
}
if (t == Double.class) {
return double.class;
}
if (t == Byte.class) {
return byte.class;
}
if (t == Short.class) {
return short.class;
}
if (t == Boolean.class) {
return boolean.class;
}
if (t == Character.class) {
return char.class;
}
return t;
}
static long nanosToTicks(long nanos) {
return (long) (nanos * JVM.getJVM().getTimeConversionFactor());
}
static synchronized EventHandler getHandler(Class<? extends Event> eventClass) {
Utils.ensureValidEventSubclass(eventClass);
try {
Field f = eventClass.getDeclaredField(EventInstrumentation.FIELD_EVENT_HANDLER);
SecuritySupport.setAccessible(f);
return (EventHandler) f.get(null);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
throw new InternalError("Could not access event handler");
}
}
static synchronized void setHandler(Class<? extends Event> eventClass, EventHandler handler) {
Utils.ensureValidEventSubclass(eventClass);
try {
Field field = eventClass.getDeclaredField(EventInstrumentation.FIELD_EVENT_HANDLER);
SecuritySupport.setAccessible(field);
field.set(null, handler);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
throw new InternalError("Could not access event handler");
}
}
public static Map<String, String> sanitizeNullFreeStringMap(Map<String, String> settings) {
HashMap<String, String> map = new HashMap<>(settings.size());
for (Map.Entry<String, String> e : settings.entrySet()) {
String key = e.getKey();
if (key == null) {
throw new NullPointerException("Null key is not allowed in map");
}
String value = e.getValue();
if (value == null) {
throw new NullPointerException("Null value is not allowed in map");
}
map.put(key, value);
}
return map;
}
public static <T> List<T> sanitizeNullFreeList(List<T> elements, Class<T> clazz) {
List<T> sanitized = new ArrayList<>(elements.size());
for (T element : elements) {
if (element == null) {
throw new NullPointerException("Null is not an allowed element in list");
}
if (element.getClass() != clazz) {
throw new ClassCastException();
}
sanitized.add(element);
}
return sanitized;
}
static List<Field> getVisibleEventFields(Class<?> clazz) {
Utils.ensureValidEventSubclass(clazz);
List<Field> fields = new ArrayList<>();
for (Class<?> c = clazz; c != Event.class; c = c.getSuperclass()) {
for (Field field : c.getDeclaredFields()) {
// skip private field in base classes
if (c == clazz || !Modifier.isPrivate(field.getModifiers())) {
fields.add(field);
}
}
}
return fields;
}
public static void ensureValidEventSubclass(Class<?> eventClass) {
if (Event.class.isAssignableFrom(eventClass) && Modifier.isAbstract(eventClass.getModifiers())) {
throw new IllegalArgumentException("Abstract event classes are not allowed");
}
if (eventClass == Event.class || !Event.class.isAssignableFrom(eventClass)) {
throw new IllegalArgumentException("Must be a subclass to " + Event.class.getName());
}
}
public static void writeGeneratedASM(String className, byte[] bytes) {
if (SAVE_GENERATED == null) {
// We can't calculate value statically because it will force
// initialization of SecuritySupport, which cause
// UnsatisfiedLinkedError on JDK 8 or non-Oracle JDKs
SAVE_GENERATED = SecuritySupport.getBooleanProperty("jfr.save.generated.asm");
}
if (SAVE_GENERATED) {
try {
try (FileOutputStream fos = new FileOutputStream(className + ".class")) {
fos.write(bytes);
}
try (FileWriter fw = new FileWriter(className + ".asm"); PrintWriter pw = new PrintWriter(fw)) {
ClassReader cr = new ClassReader(bytes);
CheckClassAdapter.verify(cr, true, pw);
}
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Instrumented code saved to " + className + ".class and .asm");
} catch (IOException e) {
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Could not save instrumented code, for " + className + ".class and .asm");
}
}
}
public static void ensureInitialized(Class<? extends Event> eventClass) {
SecuritySupport.ensureClassIsInitialized(eventClass);
}
public static Object makePrimitiveArray(String typeName, List<Object> values) {
int length = values.size();
switch (typeName) {
case "int":
int[] ints = new int[length];
for (int i = 0; i < length; i++) {
ints[i] = (int) values.get(i);
}
return ints;
case "long":
long[] longs = new long[length];
for (int i = 0; i < length; i++) {
longs[i] = (long) values.get(i);
}
return longs;
case "float":
float[] floats = new float[length];
for (int i = 0; i < length; i++) {
floats[i] = (float) values.get(i);
}
return floats;
case "double":
double[] doubles = new double[length];
for (int i = 0; i < length; i++) {
doubles[i] = (double) values.get(i);
}
return doubles;
case "short":
short[] shorts = new short[length];
for (int i = 0; i < length; i++) {
shorts[i] = (short) values.get(i);
}
return shorts;
case "char":
char[] chars = new char[length];
for (int i = 0; i < length; i++) {
chars[i] = (char) values.get(i);
}
return chars;
case "byte":
byte[] bytes = new byte[length];
for (int i = 0; i < length; i++) {
bytes[i] = (byte) values.get(i);
}
return bytes;
case "boolean":
boolean[] booleans = new boolean[length];
for (int i = 0; i < length; i++) {
booleans[i] = (boolean) values.get(i);
}
return booleans;
case "java.lang.String":
String[] strings = new String[length];
for (int i = 0; i < length; i++) {
strings[i] = (String) values.get(i);
}
return strings;
}
return null;
}
public static boolean isSettingVisible(Control c, boolean hasEventHook) {
if (c instanceof ThresholdSetting) {
return !hasEventHook;
}
if (c instanceof PeriodSetting) {
return hasEventHook;
}
if (c instanceof StackTraceSetting) {
return !hasEventHook;
}
return true;
}
public static boolean isSettingVisible(long typeId, boolean hasEventHook) {
if (ThresholdSetting.isType(typeId)) {
return !hasEventHook;
}
if (PeriodSetting.isType(typeId)) {
return hasEventHook;
}
if (StackTraceSetting.isType(typeId)) {
return !hasEventHook;
}
return true;
}
public static Type getValidType(Class<?> type, String name) {
Objects.requireNonNull(type, "Null is not a valid type for value descriptor " + name);
if (type.isArray()) {
type = type.getComponentType();
if (type != String.class && !type.isPrimitive()) {
throw new IllegalArgumentException("Only arrays of primitives and Strings are allowed");
}
}
Type knownType = Type.getKnownType(type);
if (knownType == null || knownType == Type.STACK_TRACE) {
throw new IllegalArgumentException("Only primitive types, java.lang.Thread, java.lang.String and java.lang.Class are allowed for value descriptors. " + type.getName());
}
return knownType;
}
public static <T> List<T> smallUnmodifiable(List<T> list) {
if (list.isEmpty()) {
return Collections.emptyList();
}
if (list.size() == 1) {
return Collections.singletonList(list.get(0));
}
return Collections.unmodifiableList(list);
}
public static String upgradeLegacyJDKEvent(String eventName) {
if (eventName.length() <= LEGACY_EVENT_NAME_PREFIX.length()) {
return eventName;
}
if (eventName.startsWith(LEGACY_EVENT_NAME_PREFIX)) {
int index = eventName.lastIndexOf(".");
if (index == LEGACY_EVENT_NAME_PREFIX.length() - 1) {
return Type.EVENT_NAME_PREFIX + eventName.substring(index + 1);
}
}
return eventName;
}
public static String makeFilename(Recording recording) {
String pid = JVM.getJVM().getPid();
String date = Repository.REPO_DATE_FORMAT.format(LocalDateTime.now());
String idText = recording == null ? "" : "-id-" + Long.toString(recording.getId());
return "hotspot-" + "pid-" + pid + idText + "-" + date + ".jfr";
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.Callable;
/**
* Purpose of this class is to simplify analysis of security risks.
* <p>
* Paths in the public API should be wrapped in this class so we
* at all time know what kind of paths we are dealing with.
* <p>
* A user supplied path must never be used in an unsafe context, such as a
* shutdown hook or any other thread created by JFR.
* <p>
* All operation using this path must happen in {@link #doPriviligedIO(Callable)}
*/
public final class WriteableUserPath {
private final AccessControlContext controlContext;
private final Path original;
private final Path real;
private final String realPathText;
private final String originalText;
// Not to ensure security, but to help
// against programming errors
private volatile boolean inPrivileged;
public WriteableUserPath(Path path) throws IOException {
controlContext = AccessController.getContext();
// verify that the path is writeable
if (Files.exists(path) && !Files.isWritable(path)) {
// throw same type of exception as FileOutputStream
// constructor, if file can't be opened.
throw new FileNotFoundException("Could not write to file: " + path.toAbsolutePath());
}
// will throw if non-writeable
BufferedWriter fw = Files.newBufferedWriter(path);
fw.close();
this.original = path;
this.originalText = path.toString();
this.real = path.toRealPath();
this.realPathText = real.toString();
}
/**
* Returns a potentially malicious path where the user may have implemented
* their own version of Path. This method should never be called in an
* unsafe context and the Path value should never be passed along to other
* methods.
*
* @return path from a potentially malicious user
*/
public Path getPotentiallyMaliciousOriginal() {
return original;
}
/**
* Returns a string representation of the real path.
*
* @return path as text
*/
public String getRealPathText() {
return realPathText;
}
/**
* Returns a string representation of the original path.
*
* @return path as text
*/
public String getOriginalText() {
return originalText;
}
/**
* Returns a potentially malicious path where the user may have implemented
* their own version of Path. This method should never be called in an
* unsafe context and the Path value should never be passed along to other
* methods.
*
* @return path from a potentially malicious user
*/
public Path getReal() {
if (!inPrivileged) {
throw new InternalError("A user path was accessed outside the context it was supplied in");
}
return real;
}
public void doPriviligedIO(Callable<?> function) throws IOException {
try {
inPrivileged = true;
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
function.call();
return null;
}
}, controlContext);
} catch (Throwable t) {
// prevent malicious user to propagate exception callback
// in the wrong context
throw new IOException("Unexpected error during I/O operation");
} finally {
inPrivileged = false;
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.consumer;
import java.io.DataInput;
import java.io.IOException;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.MetadataDescriptor;
public final class ChunkHeader {
private static final long METADATA_TYPE_ID = 0;
private static final byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' };
private final short major;
private final short minor;
private final long chunkSize;
private final long chunkStartTicks;
private final long ticksPerSecond;
private final long chunkStartNanos;
private final long metadataPosition;
// private final long absoluteInitialConstantPoolPosition;
private final long absoluteChunkEnd;
private final long absoluteEventStart;
private final long absoluteChunkStart;
private final boolean lastChunk;
private final RecordingInput input;
private final long durationNanos;
private final long id;
private long constantPoolPosition;
public ChunkHeader(RecordingInput input) throws IOException {
this(input, 0, 0);
}
private ChunkHeader(RecordingInput input, long absoluteChunkStart, long id) throws IOException {
input.position(absoluteChunkStart);
if (input.position() >= input.size()) {
throw new IOException("Chunk contains no data");
}
verifyMagic(input);
this.input = input;
this.id = id;
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk " + id);
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startPosition=" + absoluteChunkStart);
major = input.readRawShort();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: major=" + major);
minor = input.readRawShort();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: minor=" + minor);
if (major != 1 && major != 2) {
throw new IOException("File version " + major + "." + minor + ". Only Flight Recorder files of version 1.x and 2.x can be read by this JDK.");
}
chunkSize = input.readRawLong();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + chunkSize);
this.constantPoolPosition = input.readRawLong();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition);
metadataPosition = input.readRawLong();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition);
chunkStartNanos = input.readRawLong(); // nanos since epoch
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startNanos=" + chunkStartNanos);
durationNanos = input.readRawLong(); // duration nanos, not used
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: durationNanos=" + durationNanos);
chunkStartTicks = input.readRawLong();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startTicks=" + chunkStartTicks);
ticksPerSecond = input.readRawLong();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: ticksPerSecond=" + ticksPerSecond);
input.readRawInt(); // features, not used
// set up boundaries
this.absoluteChunkStart = absoluteChunkStart;
absoluteChunkEnd = absoluteChunkStart + chunkSize;
lastChunk = input.size() == absoluteChunkEnd;
absoluteEventStart = input.position();
// read metadata
input.position(absoluteEventStart);
}
public ChunkHeader nextHeader() throws IOException {
return new ChunkHeader(input, absoluteChunkEnd, id + 1);
}
public MetadataDescriptor readMetadata() throws IOException {
input.position(absoluteChunkStart + metadataPosition);
input.readInt(); // size
long id = input.readLong(); // event type id
if (id != METADATA_TYPE_ID) {
throw new IOException("Expected metadata event. Type id=" + id + ", should have been " + METADATA_TYPE_ID);
}
input.readLong(); // start time
input.readLong(); // duration
long metadataId = input.readLong();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Metadata id=" + metadataId);
// No need to read if metadataId == lastMetadataId, but we
// do it for verification purposes.
return MetadataDescriptor.read(input);
}
public boolean isLastChunk() {
return lastChunk;
}
public short getMajor() {
return major;
}
public short getMinor() {
return minor;
}
public long getAbsoluteChunkStart() {
return absoluteChunkStart;
}
public long getConstantPoolPosition() {
return constantPoolPosition;
}
public long getStartTicks() {
return chunkStartTicks;
}
public double getTicksPerSecond() {
return ticksPerSecond;
}
public long getStartNanos() {
return chunkStartNanos;
}
public long getEnd() {
return absoluteChunkEnd;
}
public long getSize() {
return chunkSize;
}
public long getDurationNanos() {
return durationNanos;
}
public RecordingInput getInput() {
return input;
}
private static void verifyMagic(DataInput input) throws IOException {
for (byte c : FILE_MAGIC) {
if (input.readByte() != c) {
throw new IOException("Not a Flight Recorder file");
}
}
}
public long getEventStart() {
return absoluteEventStart;
}
}

View File

@@ -0,0 +1,349 @@
/*
* Copyright (c) 2016, 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 jdk.jfr.internal.consumer;
import java.io.DataInput;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
public final class RecordingInput implements DataInput, AutoCloseable {
public static final byte STRING_ENCODING_NULL = 0;
public static final byte STRING_ENCODING_EMPTY_STRING = 1;
public static final byte STRING_ENCODING_CONSTANT_POOL = 2;
public static final byte STRING_ENCODING_UTF8_BYTE_ARRAY = 3;
public static final byte STRING_ENCODING_CHAR_ARRAY = 4;
public static final byte STRING_ENCODING_LATIN1_BYTE_ARRAY = 5;
private final static int DEFAULT_BLOCK_SIZE = 16 * 1024 * 1024;
private final static Charset UTF8 = Charset.forName("UTF-8");
private final static Charset LATIN1 = Charset.forName("ISO-8859-1");
private static final class Block {
private byte[] bytes = new byte[0];
private long blockPosition;
boolean contains(long position) {
return position >= blockPosition && position < blockPosition + bytes.length;
}
public void read(RandomAccessFile file, int amount) throws IOException {
blockPosition = file.getFilePointer();
// reuse byte array, if possible
if (amount != bytes.length) {
bytes = new byte[amount];
}
file.readFully(bytes);
}
public byte get(long position) {
return bytes[(int) (position - blockPosition)];
}
}
private final RandomAccessFile file;
private final long size;
private Block currentBlock = new Block();
private Block previousBlock = new Block();
private long position;
private final int blockSize;
private RecordingInput(File f, int blockSize) throws IOException {
this.size = f.length();
this.blockSize = blockSize;
this.file = new RandomAccessFile(f, "r");
if (size < 8) {
throw new IOException("Not a valid Flight Recorder file. File length is only " + size + " bytes.");
}
}
public RecordingInput(File f) throws IOException {
this(f, DEFAULT_BLOCK_SIZE);
}
@Override
public final byte readByte() throws IOException {
if (!currentBlock.contains(position)) {
position(position);
}
return currentBlock.get(position++);
}
@Override
public final void readFully(byte[] dest, int offset, int length) throws IOException {
// TODO: Optimize, use Arrays.copy if all bytes are in current block
// array
for (int i = 0; i < length; i++) {
dest[i + offset] = readByte();
}
}
@Override
public final void readFully(byte[] dst) throws IOException {
readFully(dst, 0, dst.length);
}
public final short readRawShort() throws IOException {
// copied from java.io.Bits
byte b0 = readByte();
byte b1 = readByte();
return (short) ((b1 & 0xFF) + (b0 << 8));
}
@Override
public final double readDouble() throws IOException {
// copied from java.io.Bits
return Double.longBitsToDouble(readRawLong());
}
@Override
public final float readFloat() throws IOException {
// copied from java.io.Bits
return Float.intBitsToFloat(readRawInt());
}
public final int readRawInt() throws IOException {
// copied from java.io.Bits
byte b0 = readByte();
byte b1 = readByte();
byte b2 = readByte();
byte b3 = readByte();
return ((b3 & 0xFF)) + ((b2 & 0xFF) << 8) + ((b1 & 0xFF) << 16) + ((b0) << 24);
}
public final long readRawLong() throws IOException {
// copied from java.io.Bits
byte b0 = readByte();
byte b1 = readByte();
byte b2 = readByte();
byte b3 = readByte();
byte b4 = readByte();
byte b5 = readByte();
byte b6 = readByte();
byte b7 = readByte();
return ((b7 & 0xFFL)) + ((b6 & 0xFFL) << 8) + ((b5 & 0xFFL) << 16) + ((b4 & 0xFFL) << 24) + ((b3 & 0xFFL) << 32) + ((b2 & 0xFFL) << 40) + ((b1 & 0xFFL) << 48) + (((long) b0) << 56);
}
public final long position() throws IOException {
return position;
}
public final void position(long newPosition) throws IOException {
if (!currentBlock.contains(newPosition)) {
if (!previousBlock.contains(newPosition)) {
if (newPosition > size()) {
throw new EOFException("Trying to read at " + newPosition + ", but file is only " + size() + " bytes.");
}
long blockStart = trimToFileSize(calculateBlockStart(newPosition));
file.seek(blockStart);
// trim amount to file size
long amount = Math.min(size() - blockStart, blockSize);
previousBlock.read(file, (int) amount);
}
// swap previous and current
Block tmp = currentBlock;
currentBlock = previousBlock;
previousBlock = tmp;
}
position = newPosition;
}
private final long trimToFileSize(long position) throws IOException {
return Math.min(size(), Math.max(0, position));
}
private final long calculateBlockStart(long newPosition) {
// align to end of current block
if (currentBlock.contains(newPosition - blockSize)) {
return currentBlock.blockPosition + currentBlock.bytes.length;
}
// align before current block
if (currentBlock.contains(newPosition + blockSize)) {
return currentBlock.blockPosition - blockSize;
}
// not near current block, pick middle
return newPosition - blockSize / 2;
}
public final long size() throws IOException {
return size;
}
public final void close() throws IOException {
file.close();
}
@Override
public final int skipBytes(int n) throws IOException {
long position = position();
position(position + n);
return (int) (position() - position);
}
@Override
public final boolean readBoolean() throws IOException {
return readByte() != 0;
}
@Override
public int readUnsignedByte() throws IOException {
return readByte() & 0x00FF;
}
@Override
public int readUnsignedShort() throws IOException {
return readShort() & 0xFFFF;
}
@Override
public final String readLine() throws IOException {
throw new UnsupportedOperationException();
}
// NOTE, this method should really be called readString
// but can't be renamed without making RecordingInput a
// public class.
//
// This method DOES Not read as expected (s2 + utf8 encoded character)
// instead it read:
// byte encoding
// int size
// data (byte or char)
//
// where encoding
//
// 0, means null
// 1, means UTF8 encoded byte array
// 2, means char array
// 3, means latin-1 (ISO-8859-1) encoded byte array
// 4, means ""
@Override
public String readUTF() throws IOException {
return readEncodedString(readByte());
}
public String readEncodedString(byte encoding) throws IOException {
if (encoding == STRING_ENCODING_NULL) {
return null;
}
if (encoding == STRING_ENCODING_EMPTY_STRING) {
return "";
}
int size = readInt();
require(size, "String size %d exceeds available data");
if (encoding == STRING_ENCODING_CHAR_ARRAY) {
char[] c = new char[size];
for (int i = 0; i < size; i++) {
c[i] = readChar();
}
return new String(c);
}
byte[] bytes = new byte[size];
readFully(bytes); // TODO: optimize, check size, and copy only if needed
if (encoding == STRING_ENCODING_UTF8_BYTE_ARRAY) {
return new String(bytes, UTF8);
}
if (encoding == STRING_ENCODING_LATIN1_BYTE_ARRAY) {
return new String(bytes, LATIN1);
}
throw new IOException("Unknown string encoding " + encoding);
}
@Override
public char readChar() throws IOException {
return (char) readLong();
}
@Override
public short readShort() throws IOException {
return (short) readLong();
}
@Override
public int readInt() throws IOException {
return (int) readLong();
}
@Override
public long readLong() throws IOException {
// can be optimized by branching checks, but will do for now
byte b0 = readByte();
long ret = (b0 & 0x7FL);
if (b0 >= 0) {
return ret;
}
int b1 = readByte();
ret += (b1 & 0x7FL) << 7;
if (b1 >= 0) {
return ret;
}
int b2 = readByte();
ret += (b2 & 0x7FL) << 14;
if (b2 >= 0) {
return ret;
}
int b3 = readByte();
ret += (b3 & 0x7FL) << 21;
if (b3 >= 0) {
return ret;
}
int b4 = readByte();
ret += (b4 & 0x7FL) << 28;
if (b4 >= 0) {
return ret;
}
int b5 = readByte();
ret += (b5 & 0x7FL) << 35;
if (b5 >= 0) {
return ret;
}
int b6 = readByte();
ret += (b6 & 0x7FL) << 42;
if (b6 >= 0) {
return ret;
}
int b7 = readByte();
ret += (b7 & 0x7FL) << 49;
if (b7 >= 0) {
return ret;
}
int b8 = readByte(); // read last byte raw
return ret + (((long) (b8 & 0XFF)) << 56);
}
// Purpose of this method is to prevent OOM by sanity check
// the minimum required number of bytes against what is available in
// segment/chunk/file
public void require(int minimumBytes, String errorMessage) throws IOException {
if (position + minimumBytes > size) {
throw new IOException(String.format(errorMessage, minimumBytes));
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal.consumer;
import java.io.IOException;
import java.util.List;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordingFile;
import jdk.jfr.internal.Type;
public abstract class RecordingInternals {
public static RecordingInternals INSTANCE;
public abstract boolean isLastEventInChunk(RecordingFile file);
public abstract Object getOffsetDataTime(RecordedObject event, String name);
public abstract List<Type> readTypes(RecordingFile file) throws IOException;
public abstract void sort(List<RecordedEvent> events);
}

View File

@@ -0,0 +1,201 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.dcmd;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.SecuritySupport.SafePath;
import jdk.jfr.internal.Utils;
/**
* Base class for JFR diagnostic commands
*
*/
abstract class AbstractDCmd {
private final StringWriter result;
private final PrintWriter log;
protected AbstractDCmd() {
result = new StringWriter();
log = new PrintWriter(result);
}
protected final FlightRecorder getFlightRecorder() {
return FlightRecorder.getFlightRecorder();
}
public final String getResult() {
return result.toString();
}
public String getPid() {
// Invoking ProcessHandle.current().pid() would require loading more
// classes during startup so instead JVM.getJVM().getPid() is used.
// The pid will not be exposed to running Java application, only when starting
// JFR from command line (-XX:StartFlightRecordin) or jcmd (JFR.start and JFR.check)
return JVM.getJVM().getPid();
}
protected final SafePath resolvePath(Recording recording, String filename) throws InvalidPathException {
if (filename == null) {
return makeGenerated(recording, Paths.get("."));
}
Path path = Paths.get(filename);
if (Files.isDirectory(path)) {
return makeGenerated(recording, path);
}
return new SafePath(path.toAbsolutePath().normalize());
}
private SafePath makeGenerated(Recording recording, Path directory) {
return new SafePath(directory.toAbsolutePath().resolve(Utils.makeFilename(recording)).normalize());
}
protected final Recording findRecording(String name) throws DCmdException {
try {
return findRecordingById(Integer.parseInt(name));
} catch (NumberFormatException nfe) {
// User specified a name, not an id.
return findRecordingByName(name);
}
}
protected final void reportOperationComplete(String actionPrefix, String name, SafePath file) {
print(actionPrefix);
print(" recording");
if (name != null) {
print(" \"" + name + "\"");
}
if (file != null) {
print(",");
try {
print(" ");
long bytes = SecuritySupport.getFileSize(file);
printBytes(bytes);
} catch (IOException e) {
// Ignore, not essential
}
println(" written to:");
println();
printPath(file);
} else {
println(".");
}
}
protected final List<Recording> getRecordings() {
List<Recording> list = new ArrayList<>(getFlightRecorder().getRecordings());
Collections.sort(list, Comparator.comparing(Recording::getId));
return list;
}
static String quoteIfNeeded(String text) {
if (text.contains(" ")) {
return "\\\"" + text + "\\\"";
} else {
return text;
}
}
protected final void println() {
log.println();
}
protected final void print(String s) {
log.print(s);
}
protected final void print(String s, Object... args) {
log.printf(s, args);
}
protected final void println(String s, Object... args) {
print(s, args);
println();
}
protected final void printBytes(long bytes) {
print(Utils.formatBytes(bytes));
}
protected final void printTimespan(Duration timespan, String separator) {
print(Utils.formatTimespan(timespan, separator));
}
protected final void printPath(SafePath path) {
if (path == null) {
print("N/A");
return;
}
try {
printPath(SecuritySupport.getAbsolutePath(path).toPath());
} catch (IOException ioe) {
printPath(path.toPath());
}
}
protected final void printPath(Path path) {
try {
println(path.toAbsolutePath().toString());
} catch (SecurityException e) {
// fall back on filename
println(path.toString());
}
}
private Recording findRecordingById(int id) throws DCmdException {
for (Recording r : getFlightRecorder().getRecordings()) {
if (r.getId() == id) {
return r;
}
}
throw new DCmdException("Could not find %d.\n\nUse JFR.check without options to see list of all available recordings.", id);
}
private Recording findRecordingByName(String name) throws DCmdException {
for (Recording recording : getFlightRecorder().getRecordings()) {
if (name.equals(recording.getName())) {
return recording;
}
}
throw new DCmdException("Could not find %s.\n\nUse JFR.check without options to see list of all available recordings.", name);
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.dcmd;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import jdk.jfr.EventType;
import jdk.jfr.Recording;
import jdk.jfr.SettingDescriptor;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.Utils;
/**
* JFR.check - invoked from native
*
*/
final class DCmdCheck extends AbstractDCmd {
/**
* Execute JFR.check
*
* @param recordingText name or id of the recording to check, or
* <code>null</code> to show a list of all recordings.
*
* @param verbose if event settings should be included.
*
* @return result output
*
* @throws DCmdException if the check could not be completed.
*/
public String execute(String recordingText, Boolean verbose) throws DCmdException {
executeInternal(recordingText, verbose);
return getResult();
}
private void executeInternal(String name, Boolean verbose) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdCheck: name=" + name + ", verbose=" + verbose);
}
if (verbose == null) {
verbose = Boolean.FALSE;
}
if (name != null) {
printRecording(findRecording(name), verbose);
return;
}
List<Recording> recordings = getRecordings();
if (!verbose && recordings.isEmpty()) {
println("No available recordings.");
println();
println("Use jcmd " + getPid() + " JFR.start to start a recording.");
return;
}
boolean first = true;
for (Recording recording : recordings) {
// Print separation between recordings,
if (!first) {
println();
if (Boolean.TRUE.equals(verbose)) {
println();
}
}
first = false;
printRecording(recording, verbose);
}
}
private void printRecording(Recording recording, boolean verbose) {
printGeneral(recording);
if (verbose) {
println();
printSetttings(recording);
}
}
private void printGeneral(Recording recording) {
print("Recording " + recording.getId() + ": name=" + recording.getName());
Duration duration = recording.getDuration();
if (duration != null) {
print(" duration=");
printTimespan(duration, "");
}
long maxSize = recording.getMaxSize();
if (maxSize != 0) {
print(" maxsize=");
print(Utils.formatBytesCompact(maxSize));
}
Duration maxAge = recording.getMaxAge();
if (maxAge != null) {
print(" maxage=");
printTimespan(maxAge, "");
}
print(" (" + recording.getState().toString().toLowerCase() + ")");
println();
}
private void printSetttings(Recording recording) {
Map<String, String> settings = recording.getSettings();
for (EventType eventType : sortByEventPath(getFlightRecorder().getEventTypes())) {
StringJoiner sj = new StringJoiner(",", "[", "]");
sj.setEmptyValue("");
for (SettingDescriptor s : eventType.getSettingDescriptors()) {
String settingsPath = eventType.getName() + "#" + s.getName();
if (settings.containsKey(settingsPath)) {
sj.add(s.getName() + "=" + settings.get(settingsPath));
}
}
String settingsText = sj.toString();
if (!settingsText.isEmpty()) {
print(" %s (%s)", eventType.getLabel(), eventType.getName());
println();
println(" " + settingsText);
}
}
}
private static List<EventType> sortByEventPath(Collection<EventType> events) {
List<EventType> sorted = new ArrayList<>();
sorted.addAll(events);
Collections.sort(sorted, new Comparator<EventType>() {
@Override
public int compare(EventType e1, EventType e2) {
return e1.getName().compareTo(e2.getName());
}
});
return sorted;
}
}

View File

@@ -0,0 +1,217 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.dcmd;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.Options;
import jdk.jfr.internal.Repository;
import jdk.jfr.internal.SecuritySupport.SafePath;
/**
* JFR.configure - invoked from native
*
*/
//Instantiated by native
final class DCmdConfigure extends AbstractDCmd {
/**
* Execute JFR.configure.
*
* @param repositoryPath the path
* @param dumpPath path to dump to on fatal error (oom)
* @param stackDepth depth of stack traces
* @param globalBufferCount number of global buffers
* @param globalBufferSize size of global buffers
* @param threadBufferSize size of thread buffer for events
* @param maxChunkSize threshold at which a new chunk is created in the disk repository
* @param sampleThreads if thread sampling should be enabled
*
* @return result
* @throws DCmdException
* if the dump could not be completed
*/
public String execute
(
String repositoryPath,
String dumpPath,
Integer stackDepth,
Long globalBufferCount,
Long globalBufferSize,
Long threadBufferSize,
Long memorySize,
Long maxChunkSize,
Boolean sampleThreads
) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdConfigure: repositorypath=" + repositoryPath +
", dumppath=" + dumpPath +
", stackdepth=" + stackDepth +
", globalbuffercount=" + globalBufferCount +
", globalbuffersize=" + globalBufferSize +
", thread_buffer_size" + threadBufferSize +
", memorysize" + memorySize +
", maxchunksize=" + maxChunkSize +
", samplethreads" + sampleThreads);
}
boolean updated = false;
if (repositoryPath != null) {
try {
SafePath s = new SafePath(repositoryPath);
Repository.getRepository().setBasePath(s);
Logger.log(LogTag.JFR, LogLevel.INFO, "Base repository path set to " + repositoryPath);
} catch (Exception e) {
throw new DCmdException("Could not use " + repositoryPath + " as repository. " + e.getMessage(), e);
}
printRepositoryPath();
updated = true;
}
if (dumpPath != null) {
Options.setDumpPath(new SafePath(dumpPath));
Logger.log(LogTag.JFR, LogLevel.INFO, "Emergency dump path set to " + dumpPath);
printDumpPath();
updated = true;
}
if (stackDepth != null) {
Options.setStackDepth(stackDepth);
Logger.log(LogTag.JFR, LogLevel.INFO, "Stack depth set to " + stackDepth);
printStackDepth();
updated = true;
}
if (globalBufferCount != null) {
Options.setGlobalBufferCount(globalBufferCount);
Logger.log(LogTag.JFR, LogLevel.INFO, "Global buffer count set to " + globalBufferCount);
printGlobalBufferCount();
updated = true;
}
if (globalBufferSize != null) {
Options.setGlobalBufferSize(globalBufferSize);
Logger.log(LogTag.JFR, LogLevel.INFO, "Global buffer size set to " + globalBufferSize);
printGlobalBufferSize();
updated = true;
}
if (threadBufferSize != null) {
Options.setThreadBufferSize(threadBufferSize);
Logger.log(LogTag.JFR, LogLevel.INFO, "Thread buffer size set to " + threadBufferSize);
printThreadBufferSize();
updated = true;
}
if (memorySize != null) {
Options.setMemorySize(memorySize);
Logger.log(LogTag.JFR, LogLevel.INFO, "Memory size set to " + memorySize);
printMemorySize();
updated = true;
}
if (maxChunkSize != null) {
Options.setMaxChunkSize(maxChunkSize);
Logger.log(LogTag.JFR, LogLevel.INFO, "Max chunk size set to " + maxChunkSize);
printMaxChunkSize();
updated = true;
}
if (sampleThreads != null) {
Options.setSampleThreads(sampleThreads);
Logger.log(LogTag.JFR, LogLevel.INFO, "Sample threads set to " + sampleThreads);
printSampleThreads();
updated = true;
}
if (!updated) {
println("Current configuration:");
println();
printRepositoryPath();
printStackDepth();
printGlobalBufferCount();
printGlobalBufferSize();
printThreadBufferSize();
printMemorySize();
printMaxChunkSize();
printSampleThreads();
}
return getResult();
}
private void printRepositoryPath() {
print("Repository path: ");
printPath(Repository.getRepository().getRepositoryPath());
println();
}
private void printDumpPath() {
print("Dump path: ");
printPath(Options.getDumpPath());
println();
}
private void printSampleThreads() {
println("Sample threads: " + Options.getSampleThreads());
}
private void printStackDepth() {
println("Stack depth: " + Options.getStackDepth());
}
private void printGlobalBufferCount() {
println("Global buffer count: " + Options.getGlobalBufferCount());
}
private void printGlobalBufferSize() {
print("Global buffer size: ");
printBytes(Options.getGlobalBufferSize());
println();
}
private void printThreadBufferSize() {
print("Thread buffer size: ");
printBytes(Options.getThreadBufferSize());
println();
}
private void printMemorySize() {
print("Memory size: ");
printBytes(Options.getMemorySize());
println();
}
private void printMaxChunkSize() {
print("Max chunk size: ");
printBytes(Options.getMaxChunkSize());
println();
}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.dcmd;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.PlatformRecorder;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.SecuritySupport.SafePath;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.WriteableUserPath;
/**
* JFR.dump
*
*/
// Instantiated by native
final class DCmdDump extends AbstractDCmd {
/**
* Execute JFR.dump.
*
* @param name name or id of the recording to dump, or <code>null</code> to dump everything
*
* @param filename file path where recording should be written, not null
* @param maxAge how far back in time to dump, may be null
* @param maxSize how far back in size to dump data from, may be null
* @param begin point in time to dump data from, may be null
* @param end point in time to dump data to, may be null
* @param pathToGcRoots if Java heap should be swept for reference chains
*
* @return result output
*
* @throws DCmdException if the dump could not be completed
*/
public String execute(String name, String filename, Long maxAge, Long maxSize, String begin, String end, Boolean pathToGcRoots) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG,
"Executing DCmdDump: name=" + name +
", filename=" + filename +
", maxage=" + maxAge +
", maxsize=" + maxSize +
", begin=" + begin +
", end" + end +
", path-to-gc-roots=" + pathToGcRoots);
}
if (FlightRecorder.getFlightRecorder().getRecordings().isEmpty()) {
throw new DCmdException("No recordings to dump from. Use JFR.start to start a recording.");
}
if (maxAge != null) {
if (end != null || begin != null) {
throw new DCmdException("Dump failed, maxage can't be combined with begin or end.");
}
if (maxAge < 0) {
throw new DCmdException("Dump failed, maxage can't be negative.");
}
if (maxAge == 0) {
maxAge = Long.MAX_VALUE / 2; // a high value that won't overflow
}
}
if (maxSize!= null) {
if (maxSize < 0) {
throw new DCmdException("Dump failed, maxsize can't be negative.");
}
if (maxSize == 0) {
maxSize = Long.MAX_VALUE / 2; // a high value that won't overflow
}
}
Instant beginTime = parseTime(begin, "begin");
Instant endTime = parseTime(end, "end");
if (beginTime != null && endTime != null) {
if (endTime.isBefore(beginTime)) {
throw new DCmdException("Dump failed, begin must preceed end.");
}
}
Duration duration = null;
if (maxAge != null) {
duration = Duration.ofNanos(maxAge);
beginTime = Instant.now().minus(duration);
}
Recording recording = null;
if (name != null) {
recording = findRecording(name);
}
PlatformRecorder recorder = PrivateAccess.getInstance().getPlatformRecorder();
try {
synchronized (recorder) {
dump(recorder, recording, name, filename, maxSize, pathToGcRoots, beginTime, endTime);
}
} catch (IOException | InvalidPathException e) {
throw new DCmdException("Dump failed. Could not copy recording data. %s", e.getMessage());
}
return getResult();
}
public void dump(PlatformRecorder recorder, Recording recording, String name, String filename, Long maxSize, Boolean pathToGcRoots, Instant beginTime, Instant endTime) throws DCmdException, IOException {
try (PlatformRecording r = newSnapShot(recorder, recording, pathToGcRoots)) {
r.filter(beginTime, endTime, maxSize);
if (r.getChunks().isEmpty()) {
throw new DCmdException("Dump failed. No data found in the specified interval.");
}
// If a filename exist, use it
// if a filename doesn't exist, use destination set earlier
// if destination doesn't exist, generate a filename
WriteableUserPath wup = null;
if (recording != null) {
PlatformRecording pRecording = PrivateAccess.getInstance().getPlatformRecording(recording);
wup = pRecording.getDestination();
}
if (filename != null || (filename == null && wup == null) ) {
SafePath safe = resolvePath(recording, filename);
wup = new WriteableUserPath(safe.toPath());
}
r.dumpStopped(wup);
reportOperationComplete("Dumped", name, new SafePath(wup.getRealPathText()));
}
}
private Instant parseTime(String time, String parameter) throws DCmdException {
if (time == null) {
return null;
}
try {
return Instant.parse(time);
} catch (DateTimeParseException dtp) {
// fall through
}
try {
LocalDateTime ldt = LocalDateTime.parse(time);
return ZonedDateTime.of(ldt, ZoneId.systemDefault()).toInstant();
} catch (DateTimeParseException dtp) {
// fall through
}
try {
LocalTime lt = LocalTime.parse(time);
LocalDate ld = LocalDate.now();
Instant instant = ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant();
Instant now = Instant.now();
if (instant.isAfter(now) && !instant.isBefore(now.plusSeconds(3600))) {
// User must have meant previous day
ld = ld.minusDays(1);
}
return ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant();
} catch (DateTimeParseException dtp) {
// fall through
}
if (time.startsWith("-")) {
try {
long durationNanos = Utils.parseTimespan(time.substring(1));
Duration duration = Duration.ofNanos(durationNanos);
return Instant.now().minus(duration);
} catch (NumberFormatException nfe) {
// fall through
}
}
throw new DCmdException("Dump failed, not a valid %s time.", parameter);
}
private PlatformRecording newSnapShot(PlatformRecorder recorder, Recording recording, Boolean pathToGcRoots) throws DCmdException, IOException {
if (recording == null) {
// Operate on all recordings
PlatformRecording snapshot = recorder.newTemporaryRecording();
recorder.fillWithRecordedData(snapshot, pathToGcRoots);
return snapshot;
}
PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
return pr.newSnapshotClone("Dumped by user", pathToGcRoots);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.dcmd;
import java.util.Formatter;
/**
* Thrown to indicate that a diagnostic command could not be executed
* successfully.
*/
final class DCmdException extends Exception {
private static final long serialVersionUID = -3792411099340016465L;
/**
* Constructs a new exception with message derived from a format string.
*
* @param format format string as described in {@link Formatter} class.
*
* @param args arguments referenced by the format specifiers in the format
* string.
*
*/
public DCmdException(String format, Object... args) {
super(format(format, args));
}
/**
* Constructs a new exception with message derived from a format string.
*
* @param cause exception that stopped the diagnostic command to complete.
*
* @param format format string as described in {@link Formatter} class.
*
* @param args arguments referenced by the format specifiers in the format
* string.
*
*/
public DCmdException(Throwable cause, String format, Object... args) {
super(format(format, args), cause);
}
private static String format(String message, Object... args) {
try (Formatter formatter = new Formatter()) {
return formatter.format(message, args).toString();
}
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.dcmd;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.OldObjectSample;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.SecuritySupport.SafePath;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.jfc.JFC;
/**
* JFR.start
*
*/
//Instantiated by native
final class DCmdStart extends AbstractDCmd {
/**
* Execute JFR.start.
*
* @param name optional name that can be used to identify recording.
* @param settings names of settings files to use, i.e. "default" or
* "default.jfc".
* @param delay delay before recording is started, in nanoseconds. Must be
* at least 1 second.
* @param duration duration of the recording, in nanoseconds. Must be at
* least 1 second.
* @param disk if recording should be persisted to disk
* @param path file path where recording data should be written
* @param maxAge how long recording data should be kept in the disk
* repository, or <code>0</code> if no limit should be set.
*
* @param maxSize the minimum amount data to keep in the disk repository
* before it is discarded, or <code>0</code> if no limit should be
* set.
*
* @param dumpOnExit if recording should dump on exit
*
* @return result output
*
* @throws DCmdException if recording could not be started
*/
@SuppressWarnings("resource")
public String execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name +
", settings=" + (settings != null ? Arrays.asList(settings) : "(none)") +
", delay=" + delay +
", duration=" + duration +
", disk=" + disk+
", filename=" + path +
", maxage=" + maxAge +
", maxsize=" + maxSize +
", dumponexit =" + dumpOnExit +
", path-to-gc-roots=" + pathToGcRoots);
}
if (name != null) {
try {
Integer.parseInt(name);
throw new DCmdException("Name of recording can't be numeric");
} catch (NumberFormatException nfe) {
// ok, can't be mixed up with name
}
}
if (duration == null && Boolean.FALSE.equals(dumpOnExit) && path != null) {
throw new DCmdException("Filename can only be set for a time bound recording or if dumponexit=true. Set duration/dumponexit or omit filename.");
}
if (settings.length == 1 && settings[0].length() == 0) {
throw new DCmdException("No settings specified. Use settings=none to start without any settings");
}
Map<String, String> s = new HashMap<>();
for (String configName : settings) {
try {
s.putAll(JFC.createKnown(configName).getSettings());
} catch(FileNotFoundException e) {
throw new DCmdException("Could not find settings file'" + configName + "'", e);
} catch (IOException | ParseException e) {
throw new DCmdException("Could not parse settings file '" + settings[0] + "'", e);
}
}
OldObjectSample.updateSettingPathToGcRoots(s, pathToGcRoots);
if (duration != null) {
if (duration < 1000L * 1000L * 1000L) {
// to avoid typo, duration below 1s makes no sense
throw new DCmdException("Could not start recording, duration must be at least 1 second.");
}
}
if (delay != null) {
if (delay < 1000L * 1000L * 1000) {
// to avoid typo, delay shorter than 1s makes no sense.
throw new DCmdException("Could not start recording, delay must be at least 1 second.");
}
}
if (!FlightRecorder.isInitialized() && delay == null) {
initializeWithForcedInstrumentation(s);
}
Recording recording = new Recording();
if (name != null) {
recording.setName(name);
}
if (disk != null) {
recording.setToDisk(disk.booleanValue());
}
recording.setSettings(s);
SafePath safePath = null;
if (path != null) {
try {
if (dumpOnExit == null) {
// default to dumponexit=true if user specified filename
dumpOnExit = Boolean.TRUE;
}
Path p = Paths.get(path);
if (Files.isDirectory(p) && Boolean.TRUE.equals(dumpOnExit)) {
// Decide destination filename at dump time
// Purposely avoid generating filename in Recording#setDestination due to
// security concerns
PrivateAccess.getInstance().getPlatformRecording(recording).setDumpOnExitDirectory(new SafePath(p));
} else {
safePath = resolvePath(recording, path);
recording.setDestination(safePath.toPath());
}
} catch (IOException | InvalidPathException e) {
recording.close();
throw new DCmdException("Could not start recording, not able to write to file %s. %s ", path, e.getMessage());
}
}
if (maxAge != null) {
recording.setMaxAge(Duration.ofNanos(maxAge));
}
if (maxSize != null) {
recording.setMaxSize(maxSize);
}
if (duration != null) {
recording.setDuration(Duration.ofNanos(duration));
}
if (dumpOnExit != null) {
recording.setDumpOnExit(dumpOnExit);
}
if (delay != null) {
Duration dDelay = Duration.ofNanos(delay);
recording.scheduleStart(dDelay);
print("Recording " + recording.getId() + " scheduled to start in ");
printTimespan(dDelay, " ");
print(".");
} else {
recording.start();
print("Started recording " + recording.getId() + ".");
}
if (recording.isToDisk() && duration == null && maxAge == null && maxSize == null) {
print(" No limit specified, using maxsize=250MB as default.");
recording.setMaxSize(250*1024L*1024L);
}
if (safePath != null && duration != null) {
println(" The result will be written to:");
println();
printPath(safePath);
} else {
println();
println();
String cmd = duration == null ? "dump" : "stop";
String fileOption = path == null ? "filename=FILEPATH " : "";
String recordingspecifier = "name=" + recording.getId();
// if user supplied a name, use it.
if (name != null) {
recordingspecifier = "name=" + quoteIfNeeded(name);
}
print("Use jcmd " + getPid() + " JFR." + cmd + " " + recordingspecifier + " " + fileOption + "to copy recording data to file.");
println();
}
return getResult();
}
// Instruments JDK-events on class load to reduce startup time
private void initializeWithForcedInstrumentation(Map<String, String> settings) {
if (!hasJDKEvents(settings)) {
return;
}
JVM jvm = JVM.getJVM();
try {
jvm.setForceInstrumentation(true);
FlightRecorder.getFlightRecorder();
} finally {
jvm.setForceInstrumentation(false);
}
}
private boolean hasJDKEvents(Map<String, String> settings) {
String[] eventNames = new String[7];
eventNames[0] = "FileRead";
eventNames[1] = "FileWrite";
eventNames[2] = "SocketRead";
eventNames[3] = "SocketWrite";
eventNames[4] = "JavaErrorThrow";
eventNames[5] = "JavaExceptionThrow";
eventNames[6] = "FileForce";
for (String eventName : eventNames) {
if ("true".equals(settings.get(Type.EVENT_NAME_PREFIX + eventName + "#enabled"))) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.dcmd;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import jdk.jfr.Recording;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.SecuritySupport.SafePath;
/**
* JFR.stop
*
*/
// Instantiated by native
final class DCmdStop extends AbstractDCmd {
/**
* Execute JFR.stop
*
* Requires that either <code>name or <code>id</code> is set.
*
* @param name name or id of the recording to stop.
*
* @param filename file path where data should be written after recording has
* been stopped, or <code>null</code> if recording shouldn't be written
* to disk.
* @return result text
*
* @throws DCmdException if recording could not be stopped
*/
public String execute(String name, String filename) throws DCmdException {
if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name + ", filename=" + filename);
}
try {
SafePath safePath = null;
Recording recording = findRecording(name);
if (filename != null) {
try {
// Ensure path is valid. Don't generate safePath if filename == null, as a user may
// want to stop recording without a dump
safePath = resolvePath(null, filename);
recording.setDestination(Paths.get(filename));
} catch (IOException | InvalidPathException e) {
throw new DCmdException("Failed to stop %s. Could not set destination for \"%s\" to file %s", recording.getName(), filename, e.getMessage());
}
}
recording.stop();
reportOperationComplete("Stopped", recording.getName(), safePath);
recording.close();
return getResult();
} catch (InvalidPathException | DCmdException e) {
if (filename != null) {
throw new DCmdException("Could not write recording \"%s\" to file. %s", name, e.getMessage());
}
throw new DCmdException(e, "Could not stop recording \"%s\".", name, e.getMessage());
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.handlers;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import jdk.jfr.EventType;
import jdk.jfr.internal.EventControl;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.PlatformEventType;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.StringPool;
// Users should not be subclass for security reasons.
public abstract class EventHandler {
// Accessed by generated sub class
protected final PlatformEventType platformEventType;
private final EventType eventType;
private final EventControl eventControl;
// Accessed by generated sub class
protected EventHandler(boolean registered, EventType eventType, EventControl eventControl) {
if (System.getSecurityManager() != null) {
// Do not allow user subclasses when security is enforced.
if (EventHandler.class.getClassLoader() != this.getClass().getClassLoader()) {
throw new SecurityException("Illegal subclass");
}
}
this.eventType = eventType;
this.platformEventType = PrivateAccess.getInstance().getPlatformEventType(eventType);
this.eventControl = eventControl;
platformEventType.setRegistered(registered);
}
final protected StringPool createStringFieldWriter() {
return new StringPool();
}
// Accessed by generated code in event class
public final boolean shouldCommit(long duration) {
return isEnabled() && duration >= platformEventType.getThresholdTicks();
}
// Accessed by generated code in event class
// Accessed by generated sub class
public final boolean isEnabled() {
return platformEventType.isCommitable();
}
public final EventType getEventType() {
return eventType;
}
public final PlatformEventType getPlatformEventType() {
return platformEventType;
}
public final EventControl getEventControl() {
return eventControl;
}
public static long timestamp() {
return JVM.counterTime();
}
public static long duration(long startTime) {
if (startTime == 0) {
// User forgot to invoke begin, or instrumentation was
// added after the user invoked begin.
// Returning 0 will make it an instant event
return 0;
}
return timestamp() - startTime;
}
// Prevent a malicious user from instantiating a generated event handlers.
@Override
public final Object clone() throws java.lang.CloneNotSupportedException {
throw new CloneNotSupportedException();
}
private final void writeObject(ObjectOutputStream out) throws IOException {
throw new IOException("Object cannot be serialized");
}
private final void readObject(ObjectInputStream in) throws IOException {
throw new IOException("Class cannot be deserialized");
}
public boolean isRegistered() {
return platformEventType.isRegistered();
}
public boolean setRegistered(boolean registered) {
return platformEventType.setRegistered(registered);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.instrument;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
final class ConstructorTracerWriter extends ClassVisitor {
private ConstructorWriter useInputParameter, noUseInputParameter;
static byte[] generateBytes(Class<?> clz, byte[] oldBytes) throws IOException {
InputStream in = new ByteArrayInputStream(oldBytes);
ClassReader cr = new ClassReader(in);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ConstructorTracerWriter ctw = new ConstructorTracerWriter(cw, clz);
cr.accept(ctw, 0);
return cw.toByteArray();
}
private ConstructorTracerWriter(ClassVisitor cv, Class<?> classToChange) {
super(Opcodes.ASM5, cv);
useInputParameter = new ConstructorWriter(classToChange, true);
noUseInputParameter = new ConstructorWriter(classToChange, false);
}
private boolean isConstructor(String name) {
return name.equals("<init>");
}
private boolean takesStringParameter(String desc) {
Type[] types = Type.getArgumentTypes(desc);
if (types.length > 0 && types[0].getClassName().equals(String.class.getName())) {
return true;
}
return false;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
// Get a hold of the constructors that takes a String as a parameter
if (isConstructor(name)) {
if (takesStringParameter(desc)) {
useInputParameter.setMethodVisitor(mv);
return useInputParameter;
}
noUseInputParameter.setMethodVisitor(mv);
return noUseInputParameter;
}
return mv;
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.instrument;
import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL;
import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
final class ConstructorWriter extends MethodVisitor {
private boolean useInputParameter;
private String shortClassName;
private String fullClassName;
ConstructorWriter(Class<?> classToChange, boolean useInputParameter) {
super(Opcodes.ASM5);
this.useInputParameter = useInputParameter;
shortClassName = classToChange.getSimpleName();
fullClassName = classToChange.getName().replace('.', '/');
}
@Override
public void visitInsn(int opcode)
{
if (opcode == RETURN) {
if (useInputParameter) {
useInput();
} else {
noInput();
}
}
mv.visitInsn(opcode);
}
@SuppressWarnings("deprecation")
private void useInput()
{
//Load 'this' from local variable 0
//Load first input parameter
//Invoke ThrowableTracer.traceCLASS(this, parameter) for current class
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESTATIC, "jdk/jfr/internal/instrument/ThrowableTracer",
"trace" + shortClassName, "(L" + fullClassName +
";Ljava/lang/String;)V");
}
@SuppressWarnings("deprecation")
private void noInput()
{
//Load 'this' from local variable 0
//Load ""
//Invoke ThrowableTracer.traceCLASS(this, "") for current class
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(ACONST_NULL);
mv.visitMethodInsn(INVOKESTATIC, "jdk/jfr/internal/instrument/ThrowableTracer",
"trace" + shortClassName, "(L" + fullClassName +
";Ljava/lang/String;)V");
}
public void setMethodVisitor(MethodVisitor mv) {
this.mv = mv;
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.io.IOException;
import java.nio.ByteBuffer;
import jdk.jfr.events.FileForceEvent;
import jdk.jfr.events.FileReadEvent;
import jdk.jfr.events.FileWriteEvent;
/**
* See {@link JITracer} for an explanation of this code.
*/
@JIInstrumentationTarget("sun.nio.ch.FileChannelImpl")
final class FileChannelImplInstrumentor {
private FileChannelImplInstrumentor() {
}
private String path;
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public void force(boolean metaData) throws IOException {
FileForceEvent event = FileForceEvent.EVENT.get();
if (!event.isEnabled()) {
force(metaData);
return;
}
try {
event.begin();
force(metaData);
} finally {
event.path = path;
event.metaData = metaData;
event.commit();
event.reset();
}
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read(ByteBuffer dst) throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(dst);
}
int bytesRead = 0;
try {
event.begin();
bytesRead = read(dst);
} finally {
if (bytesRead < 0) {
event.endOfFile = true;
} else {
event.bytesRead = bytesRead;
}
event.path = path;
event.commit();
event.reset();
}
return bytesRead;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read(ByteBuffer dst, long position) throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(dst, position);
}
int bytesRead = 0;
try {
event.begin();
bytesRead = read(dst, position);
} finally {
if (bytesRead < 0) {
event.endOfFile = true;
} else {
event.bytesRead = bytesRead;
}
event.path = path;
event.commit();
event.reset();
}
return bytesRead;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(dsts, offset, length);
}
long bytesRead = 0;
try {
event.begin();
bytesRead = read(dsts, offset, length);
} finally {
if (bytesRead < 0) {
event.endOfFile = true;
} else {
event.bytesRead = bytesRead;
}
event.path = path;
event.commit();
event.reset();
}
return bytesRead;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int write(ByteBuffer src) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
return write(src);
}
int bytesWritten = 0;
try {
event.begin();
bytesWritten = write(src);
} finally {
event.bytesWritten = bytesWritten > 0 ? bytesWritten : 0;
event.path = path;
event.commit();
event.reset();
}
return bytesWritten;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int write(ByteBuffer src, long position) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
return write(src, position);
}
int bytesWritten = 0;
try {
event.begin();
bytesWritten = write(src, position);
} finally {
event.bytesWritten = bytesWritten > 0 ? bytesWritten : 0;
event.path = path;
event.commit();
event.reset();
}
return bytesWritten;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
return write(srcs, offset, length);
}
long bytesWritten = 0;
try {
event.begin();
bytesWritten = write(srcs, offset, length);
} finally {
event.bytesWritten = bytesWritten > 0 ? bytesWritten : 0;
event.path = path;
event.commit();
event.reset();
}
return bytesWritten;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.io.IOException;
import jdk.jfr.events.FileReadEvent;
/**
* See {@link JITracer} for an explanation of this code.
*/
@JIInstrumentationTarget("java.io.FileInputStream")
final class FileInputStreamInstrumentor {
private FileInputStreamInstrumentor() {
}
private String path;
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read() throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read();
}
int result = 0;
try {
event.begin();
result = read();
if (result < 0) {
event.endOfFile = true;
} else {
event.bytesRead = 1;
}
} finally {
event.path = path;
event.commit();
event.reset();
}
return result;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read(byte b[]) throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(b);
}
int bytesRead = 0;
try {
event.begin();
bytesRead = read(b);
} finally {
if (bytesRead < 0) {
event.endOfFile = true;
} else {
event.bytesRead = bytesRead;
}
event.path = path;
event.commit();
event.reset();
}
return bytesRead;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read(byte b[], int off, int len) throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(b, off, len);
}
int bytesRead = 0;
try {
event.begin();
bytesRead = read(b, off, len);
} finally {
if (bytesRead < 0) {
event.endOfFile = true;
} else {
event.bytesRead = bytesRead;
}
event.path = path;
event.commit();
event.reset();
}
return bytesRead;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.io.IOException;
import jdk.jfr.events.FileWriteEvent;
/**
* See {@link JITracer} for an explanation of this code.
*/
@JIInstrumentationTarget("java.io.FileOutputStream")
final class FileOutputStreamInstrumentor {
private FileOutputStreamInstrumentor() {
}
private String path;
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public void write(int b) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
write(b);
return;
}
try {
event.begin();
write(b);
event.bytesWritten = 1;
} finally {
event.path = path;
event.commit();
event.reset();
}
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public void write(byte b[]) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
write(b);
return;
}
try {
event.begin();
write(b);
event.bytesWritten = b.length;
} finally {
event.path = path;
event.commit();
event.reset();
}
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public void write(byte b[], int off, int len) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
write(b, off, len);
return;
}
try {
event.begin();
write(b, off, len);
event.bytesWritten = len;
} finally {
event.path = path;
event.commit();
event.reset();
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.instrument;
import java.util.ArrayList;
import java.util.List;
import jdk.jfr.Event;
import jdk.jfr.events.ActiveRecordingEvent;
import jdk.jfr.events.ActiveSettingEvent;
import jdk.jfr.events.ErrorThrownEvent;
import jdk.jfr.events.ExceptionStatisticsEvent;
import jdk.jfr.events.ExceptionThrownEvent;
import jdk.jfr.events.FileForceEvent;
import jdk.jfr.events.FileReadEvent;
import jdk.jfr.events.FileWriteEvent;
import jdk.jfr.events.SocketReadEvent;
import jdk.jfr.events.SocketWriteEvent;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.RequestEngine;
import jdk.jfr.internal.SecuritySupport;
public final class JDKEvents {
private static final Class<?>[] eventClasses = {
FileForceEvent.class,
FileReadEvent.class,
FileWriteEvent.class,
SocketReadEvent.class,
SocketWriteEvent.class,
ExceptionThrownEvent.class,
ExceptionStatisticsEvent.class,
ErrorThrownEvent.class,
ActiveSettingEvent.class,
ActiveRecordingEvent.class
};
// This is a list of the classes with instrumentation code that should be applied.
private static final Class<?>[] instrumentationClasses = new Class<?>[] {
FileInputStreamInstrumentor.class,
FileOutputStreamInstrumentor.class,
RandomAccessFileInstrumentor.class,
FileChannelImplInstrumentor.class,
SocketInputStreamInstrumentor.class,
SocketOutputStreamInstrumentor.class,
SocketChannelImplInstrumentor.class
};
private static final Class<?>[] targetClasses = new Class<?>[instrumentationClasses.length];
private static final JVM jvm = JVM.getJVM();
private static final Runnable emitExceptionStatistics = JDKEvents::emitExceptionStatistics;
private static boolean initializationTriggered;
@SuppressWarnings("unchecked")
public synchronized static void initialize() {
try {
if (initializationTriggered == false) {
for (Class<?> eventClass : eventClasses) {
SecuritySupport.registerEvent((Class<? extends Event>) eventClass);
}
initializationTriggered = true;
RequestEngine.addTrustedJDKHook(ExceptionStatisticsEvent.class, emitExceptionStatistics);
}
} catch (Exception e) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.WARN, "Could not initialize JDK events. " + e.getMessage());
}
}
public static void addInstrumentation() {
try {
List<Class<?>> list = new ArrayList<>();
for (int i = 0; i < instrumentationClasses.length; i++) {
JIInstrumentationTarget tgt = instrumentationClasses[i].getAnnotation(JIInstrumentationTarget.class);
Class<?> clazz = Class.forName(tgt.value());
targetClasses[i] = clazz;
list.add(clazz);
}
list.add(java.lang.Throwable.class);
list.add(java.lang.Error.class);
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Retransformed JDK classes");
jvm.retransformClasses(list.toArray(new Class<?>[list.size()]));
} catch (Exception e) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.WARN, "Could not add instrumentation for JDK events. " + e.getMessage());
}
}
private static void emitExceptionStatistics() {
ExceptionStatisticsEvent t = new ExceptionStatisticsEvent();
t.throwables = ThrowableTracer.numThrowables();
t.commit();
}
@SuppressWarnings("deprecation")
public static byte[] retransformCallback(Class<?> klass, byte[] oldBytes) throws Throwable {
if (java.lang.Throwable.class == klass) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.TRACE, "Instrumenting java.lang.Throwable");
return ConstructorTracerWriter.generateBytes(java.lang.Throwable.class, oldBytes);
}
if (java.lang.Error.class == klass) {
Logger.log(LogTag.JFR_SYSTEM, LogLevel.TRACE, "Instrumenting java.lang.Error");
return ConstructorTracerWriter.generateBytes(java.lang.Error.class, oldBytes);
}
for (int i = 0; i < targetClasses.length; i++) {
if (targetClasses[i].equals(klass)) {
Class<?> c = instrumentationClasses[i];
Logger.log(LogTag.JFR_SYSTEM, LogLevel.TRACE, () -> "Processing instrumentation class: " + c);
return new JIClassInstrumentation(instrumentationClasses[i], klass, oldBytes).getNewBytes();
}
}
return oldBytes;
}
public static void remove() {
RequestEngine.removeHook(JDKEvents::emitExceptionStatistics);
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.Utils;
/**
* This class will perform byte code instrumentation given an "instrumentor" class.
*
* @see JITracer
*
* @author Staffan Larsen
*/
@Deprecated
final class JIClassInstrumentation {
private final Class<?> instrumentor;
private final String targetName;
private final String instrumentorName;
private final byte[] newBytes;
private final ClassReader targetClassReader;
private final ClassReader instrClassReader;
/**
* Creates an instance and performs the instrumentation.
*
* @param instrumentor instrumentor class
* @param target target class
* @param old_target_bytes bytes in target
*
* @throws ClassNotFoundException
* @throws IOException
*/
JIClassInstrumentation(Class<?> instrumentor, Class<?> target, byte[] old_target_bytes) throws ClassNotFoundException, IOException {
instrumentorName = instrumentor.getName();
this.targetName = target.getName();
this.instrumentor = instrumentor;
this.targetClassReader = new ClassReader(old_target_bytes);
this.instrClassReader = new ClassReader(getOriginalClassBytes(instrumentor));
this.newBytes = makeBytecode();
Utils.writeGeneratedASM(target.getName(), newBytes);
}
private static byte[] getOriginalClassBytes(Class<?> clazz) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String name = "/" + clazz.getName().replace(".", "/") + ".class";
InputStream is = SecuritySupport.getResourceAsStream(name);
int bytesRead;
byte[] buffer = new byte[16384];
while ((bytesRead = is.read(buffer, 0, buffer.length)) != -1) {
baos.write(buffer, 0, bytesRead);
}
baos.flush();
is.close();
return baos.toByteArray();
}
private byte[] makeBytecode() throws IOException, ClassNotFoundException {
// Find the methods to instrument and inline
final List<Method> instrumentationMethods = new ArrayList<>();
for (final Method m : instrumentor.getDeclaredMethods()) {
JIInstrumentationMethod im = m.getAnnotation(JIInstrumentationMethod.class);
if (im != null) {
instrumentationMethods.add(m);
}
}
// We begin by inlining the target's methods into the instrumentor
ClassNode temporary = new ClassNode();
ClassVisitor inliner = new JIInliner(
Opcodes.ASM5,
temporary,
targetName,
instrumentorName,
targetClassReader,
instrumentationMethods);
instrClassReader.accept(inliner, ClassReader.EXPAND_FRAMES);
// Now we have the target's methods inlined into the instrumentation code (in 'temporary').
// We now need to replace the target's method with the code in the
// instrumentation method.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
JIMethodMergeAdapter ma = new JIMethodMergeAdapter(
cw,
temporary,
instrumentationMethods,
instrumentor.getAnnotationsByType(JITypeMapping.class));
targetClassReader.accept(ma, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
}
/**
* Get the instrumented byte codes that can be used to retransform the class.
*
* @return bytes
*/
public byte[] getNewBytes() {
return newBytes.clone();
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
@Deprecated
final class JIInliner extends ClassVisitor {
private final String targetClassName;
private final String instrumentationClassName;
private final ClassNode targetClassNode;
private final List<Method> instrumentationMethods;
/**
* A ClassVisitor which will check all methods of the class it visits against the instrumentationMethods
* list. If a method is on that list, the method will be further processed for inlining into that
* method.
*/
JIInliner(int api, ClassVisitor cv, String targetClassName, String instrumentationClassName,
ClassReader targetClassReader,
List<Method> instrumentationMethods) {
super(api, cv);
this.targetClassName = targetClassName;
this.instrumentationClassName = instrumentationClassName;
this.instrumentationMethods = instrumentationMethods;
ClassNode cn = new ClassNode(Opcodes.ASM5);
targetClassReader.accept(cn, ClassReader.EXPAND_FRAMES);
this.targetClassNode = cn;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (isInstrumentationMethod(name, desc)) {
MethodNode methodToInline = findTargetMethodNode(name, desc);
if (methodToInline == null) {
throw new IllegalArgumentException("Could not find the method to instrument in the target class");
}
if (Modifier.isNative(methodToInline.access)) {
throw new IllegalArgumentException("Cannot instrument native methods: " + targetClassNode.name + "." + methodToInline.name + methodToInline.desc);
}
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.DEBUG, "Inliner processing method " + name + desc);
JIMethodCallInliner mci = new JIMethodCallInliner(access,
desc,
mv,
methodToInline,
targetClassName,
instrumentationClassName);
return mci;
}
return mv;
}
private boolean isInstrumentationMethod(String name, String desc) {
for(Method m : instrumentationMethods) {
if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) {
return true;
}
}
return false;
}
private MethodNode findTargetMethodNode(String name, String desc) {
for (MethodNode mn : targetClassNode.methods) {
if (mn.desc.equals(desc) && mn.name.equals(name)) {
return mn;
}
}
throw new IllegalArgumentException("could not find MethodNode for "
+ name + desc);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface JIInstrumentationMethod {
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface JIInstrumentationTarget {
String value();
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.util.ArrayList;
import java.util.List;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.commons.LocalVariablesSorter;
import jdk.internal.org.objectweb.asm.commons.Remapper;
import jdk.internal.org.objectweb.asm.commons.SimpleRemapper;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
/**
* Class responsible for finding the call to inline and inlining it.
*
* This code is heavily influenced by section 3.2.6 "Inline Method" in
* "Using ASM framework to implement common bytecode transformation patterns",
* E. Kuleshov, AOSD.07, March 2007, Vancouver, Canada.
* http://asm.ow2.org/index.html
*/
@Deprecated
final class JIMethodCallInliner extends LocalVariablesSorter {
private final String oldClass;
private final String newClass;
private final MethodNode inlineTarget;
private final List<CatchBlock> blocks = new ArrayList<>();
private boolean inlining;
/**
* inlineTarget defines the method to inline and also contains the actual
* code to inline.
*
* @param access
* @param desc
* @param mv
* @param inlineTarget
* @param oldClass
* @param newClass
* @param logger
*/
public JIMethodCallInliner(int access, String desc, MethodVisitor mv,
MethodNode inlineTarget, String oldClass, String newClass) {
super(Opcodes.ASM5, access, desc, mv);
this.oldClass = oldClass;
this.newClass = newClass;
this.inlineTarget = inlineTarget;
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.DEBUG, "MethodCallInliner: targetMethod=" + newClass + "."
+ inlineTarget.name + inlineTarget.desc);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
// Now we are looking at method call in the source method
if (!shouldBeInlined(owner, name, desc)) {
// If this method call should not be inlined, just keep it
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
// If the call should be inlined, we create a MethodInliningAdapter
// The MIA will walk the instructions in the inlineTarget and add them
// to the current method, doing the necessary name remappings.
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.DEBUG, "Inlining call to " + name + desc);
Remapper remapper = new SimpleRemapper(oldClass, newClass);
Label end = new Label();
inlining = true;
inlineTarget.instructions.resetLabels();
JIMethodInliningAdapter mia = new JIMethodInliningAdapter(this, end,
opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,
remapper);
inlineTarget.accept(mia);
inlining = false;
super.visitLabel(end);
}
/**
* Determine if the method should be inlined or not.
*/
private boolean shouldBeInlined(String owner, String name, String desc) {
return inlineTarget.desc.equals(desc) && inlineTarget.name.equals(name)
&& owner.equals(newClass.replace('.', '/'));
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler,
String type) {
if (!inlining) {
// try-catch blocks are saved here and replayed at the end
// of the method (in visitMaxs)
blocks.add(new CatchBlock(start, end, handler, type));
} else {
super.visitTryCatchBlock(start, end, handler, type);
}
}
@Override
public void visitMaxs(int stack, int locals) {
for (CatchBlock b : blocks) {
super.visitTryCatchBlock(b.start, b.end, b.handler, b.type);
}
super.visitMaxs(stack, locals);
}
static final class CatchBlock {
final Label start;
final Label end;
final Label handler;
final String type;
CatchBlock(Label start, Label end, Label handler, String type) {
this.start = start;
this.end = end;
this.handler = handler;
this.type = type;
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.LocalVariablesSorter;
import jdk.internal.org.objectweb.asm.commons.Remapper;
import jdk.internal.org.objectweb.asm.commons.RemappingMethodAdapter;
@Deprecated
final class JIMethodInliningAdapter extends RemappingMethodAdapter {
private final LocalVariablesSorter lvs;
private final Label end;
public JIMethodInliningAdapter(LocalVariablesSorter mv, Label end, int acc, String desc, Remapper remapper) {
super(acc, desc, mv, remapper);
this.lvs = mv;
this.end = end;
int offset = isStatic(acc) ? 0 : 1;
Type[] args = Type.getArgumentTypes(desc);
for (int i = args.length - 1; i >= 0; i--) {
super.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), i + offset);
}
if (offset > 0) {
super.visitVarInsn(Opcodes.ASTORE, 0);
}
}
private boolean isStatic(int acc) {
return (acc & Opcodes.ACC_STATIC) != 0;
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN || opcode == Opcodes.IRETURN
|| opcode == Opcodes.ARETURN || opcode == Opcodes.LRETURN) {
super.visitJumpInsn(Opcodes.GOTO, end);
} else {
super.visitInsn(opcode);
}
}
@Override
public void visitMaxs(int stack, int locals) {
}
@Override
protected int newLocalMapping(Type type) {
return lvs.newLocal(type);
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.RemappingMethodAdapter;
import jdk.internal.org.objectweb.asm.commons.SimpleRemapper;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
/**
* This class will merge (some) methods from one class into another one.
*
* @author Staffan Larsen
*/
@Deprecated
final class JIMethodMergeAdapter extends ClassVisitor {
private final ClassNode cn;
private final List<Method> methodFilter;
private final Map<String, String> typeMap;
/**
* Methods in methodFilter that exist in cn will be merged into cv. If the method already exists,
* the original method will be deleted.
*
* @param cv
* @param cn - a ClassNode with Methods that will be merged into this class
* @param methodFilter - only methods in this list will be merged
* @param typeMappings - while merging, type references in the methods will be changed according to this map
*/
public JIMethodMergeAdapter(ClassVisitor cv, ClassNode cn, List<Method> methodFilter, JITypeMapping[] typeMappings) {
super(Opcodes.ASM5, cv);
this.cn = cn;
this.methodFilter = methodFilter;
this.typeMap = new HashMap<>();
for (JITypeMapping tm : typeMappings) {
typeMap.put(tm.from().replace('.', '/'), tm.to().replace('.', '/'));
}
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
typeMap.put(cn.name, name);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if(methodInFilter(name, desc)) {
// If the method is one that we will be replacing, delete the method
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.DEBUG, "Deleting " + name + desc);
return null;
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
SimpleRemapper remapper = new SimpleRemapper(typeMap);
for (MethodNode mn : cn.methods) {
// Check if the method is in the list of methods to copy
if (methodInFilter(mn.name, mn.desc)) {
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.DEBUG, "Copying method: " + mn.name + mn.desc);
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.DEBUG, " with mapper: " + typeMap);
String[] exceptions = new String[mn.exceptions.size()];
mn.exceptions.toArray(exceptions);
MethodVisitor mv = cv.visitMethod(mn.access, mn.name, mn.desc, mn.signature, exceptions);
mn.instructions.resetLabels();
mn.accept(new RemappingMethodAdapter(mn.access, mn.desc, mv, remapper));
}
}
super.visitEnd();
}
private boolean methodInFilter(String name, String desc) {
for(Method m : methodFilter) {
if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface JITypeMapping {
String from();
String to();
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.io.IOException;
import jdk.jfr.events.FileReadEvent;
import jdk.jfr.events.FileWriteEvent;
/**
* See {@link JITracer} for an explanation of this code.
*/
@JIInstrumentationTarget("java.io.RandomAccessFile")
final class RandomAccessFileInstrumentor {
private RandomAccessFileInstrumentor() {
}
private String path;
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read() throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read();
}
int result = 0;
try {
event.begin();
result = read();
if (result < 0) {
event.endOfFile = true;
} else {
event.bytesRead = 1;
}
} finally {
event.path = path;
event.commit();
event.reset();
}
return result;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read(byte b[]) throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(b);
}
int bytesRead = 0;
try {
event.begin();
bytesRead = read(b);
} finally {
if (bytesRead < 0) {
event.endOfFile = true;
} else {
event.bytesRead = bytesRead;
}
event.path = path;
event.commit();
event.reset();
}
return bytesRead;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read(byte b[], int off, int len) throws IOException {
FileReadEvent event = FileReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(b, off, len);
}
int bytesRead = 0;
try {
event.begin();
bytesRead = read(b, off, len);
} finally {
if (bytesRead < 0) {
event.endOfFile = true;
} else {
event.bytesRead = bytesRead;
}
event.path = path;
event.commit();
event.reset();
}
return bytesRead;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public void write(int b) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
write(b);
return;
}
try {
event.begin();
write(b);
event.bytesWritten = 1;
} finally {
event.path = path;
event.commit();
event.reset();
}
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public void write(byte b[]) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
write(b);
return;
}
try {
event.begin();
write(b);
event.bytesWritten = b.length;
} finally {
event.path = path;
event.commit();
event.reset();
}
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public void write(byte b[], int off, int len) throws IOException {
FileWriteEvent event = FileWriteEvent.EVENT.get();
if (!event.isEnabled()) {
write(b, off, len);
return;
}
try {
event.begin();
write(b, off, len);
event.bytesWritten = len;
} finally {
event.path = path;
event.commit();
event.reset();
}
}
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import jdk.jfr.events.SocketReadEvent;
import jdk.jfr.events.SocketWriteEvent;
/**
* See {@link JITracer} for an explanation of this code.
*/
@JIInstrumentationTarget("sun.nio.ch.SocketChannelImpl")
final class SocketChannelImplInstrumentor {
private SocketChannelImplInstrumentor() {
}
private InetSocketAddress remoteAddress;
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int read(ByteBuffer dst) throws IOException {
SocketReadEvent event = SocketReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(dst);
}
int bytesRead = 0;
try {
event.begin();
bytesRead = read(dst);
} finally {
event.end();
if (event.shouldCommit()) {
String hostString = remoteAddress.getAddress().toString();
int delimiterIndex = hostString.lastIndexOf('/');
event.host = hostString.substring(0, delimiterIndex);
event.address = hostString.substring(delimiterIndex + 1);
event.port = remoteAddress.getPort();
if (bytesRead < 0) {
event.endOfStream = true;
} else {
event.bytesRead = bytesRead;
}
event.timeout = 0;
event.commit();
event.reset();
}
}
return bytesRead;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
SocketReadEvent event = SocketReadEvent.EVENT.get();
if(!event.isEnabled()) {
return read(dsts, offset, length);
}
long bytesRead = 0;
try {
event.begin();
bytesRead = read(dsts, offset, length);
} finally {
event.end();
if (event.shouldCommit()) {
String hostString = remoteAddress.getAddress().toString();
int delimiterIndex = hostString.lastIndexOf('/');
event.host = hostString.substring(0, delimiterIndex);
event.address = hostString.substring(delimiterIndex + 1);
event.port = remoteAddress.getPort();
if (bytesRead < 0) {
event.endOfStream = true;
} else {
event.bytesRead = bytesRead;
}
event.timeout = 0;
event.commit();
event.reset();
}
}
return bytesRead;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public int write(ByteBuffer buf) throws IOException {
SocketWriteEvent event = SocketWriteEvent.EVENT.get();
if (!event.isEnabled()) {
return write(buf);
}
int bytesWritten = 0;
try {
event.begin();
bytesWritten = write(buf);
} finally {
event.end();
if (event.shouldCommit()) {
String hostString = remoteAddress.getAddress().toString();
int delimiterIndex = hostString.lastIndexOf('/');
event.host = hostString.substring(0, delimiterIndex);
event.address = hostString.substring(delimiterIndex + 1);
event.port = remoteAddress.getPort();
event.bytesWritten = bytesWritten < 0 ? 0 : bytesWritten;
event.commit();
event.reset();
}
}
return bytesWritten;
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
SocketWriteEvent event = SocketWriteEvent.EVENT.get();
if (!event.isEnabled()) {
return write(srcs, offset, length);
}
long bytesWritten = 0;
try {
event.begin();
bytesWritten = write(srcs, offset, length);
} finally {
event.end();
if (event.shouldCommit()) {
String hostString = remoteAddress.getAddress().toString();
int delimiterIndex = hostString.lastIndexOf('/');
event.host = hostString.substring(0, delimiterIndex);
event.address = hostString.substring(delimiterIndex + 1);
event.port = remoteAddress.getPort();
event.bytesWritten = bytesWritten < 0 ? 0 : bytesWritten;
event.commit();
event.reset();
}
}
return bytesWritten;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.io.IOException;
import java.net.InetAddress;
import jdk.jfr.events.SocketReadEvent;
/**
* See {@link JITracer} for an explanation of this code.
*/
@JIInstrumentationTarget("java.net.SocketInputStream")
@JITypeMapping(from = "jdk.jfr.internal.instrument.SocketInputStreamInstrumentor$AbstractPlainSocketImpl",
to = "java.net.AbstractPlainSocketImpl")
final class SocketInputStreamInstrumentor {
private SocketInputStreamInstrumentor() {
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
int read(byte b[], int off, int length, int timeout) throws IOException {
SocketReadEvent event = SocketReadEvent.EVENT.get();
if (!event.isEnabled()) {
return read(b, off, length, timeout);
}
int bytesRead = 0;
try {
event.begin();
bytesRead = read(b, off, length, timeout);
} finally {
event.end();
if (event.shouldCommit()) {
String hostString = impl.address.toString();
int delimiterIndex = hostString.lastIndexOf('/');
event.host = hostString.substring(0, delimiterIndex);
event.address = hostString.substring(delimiterIndex + 1);
event.port = impl.port;
if (bytesRead < 0) {
event.endOfStream = true;
} else {
event.bytesRead = bytesRead;
}
event.timeout = timeout;
event.commit();
event.reset();
}
}
return bytesRead;
}
private AbstractPlainSocketImpl impl = null;
void silenceFindBugsUnwrittenField(InetAddress dummy) {
impl.address = dummy;
}
static class AbstractPlainSocketImpl {
InetAddress address;
int port;
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2013, 2018, 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 jdk.jfr.internal.instrument;
import java.io.IOException;
import java.net.InetAddress;
import jdk.jfr.events.SocketWriteEvent;
/**
* See {@link JITracer} for an explanation of this code.
*/
@JIInstrumentationTarget("java.net.SocketOutputStream")
@JITypeMapping(from = "jdk.jfr.internal.instrument.SocketOutputStreamInstrumentor$AbstractPlainSocketImpl",
to = "java.net.AbstractPlainSocketImpl")
final class SocketOutputStreamInstrumentor {
private SocketOutputStreamInstrumentor() {
}
@SuppressWarnings("deprecation")
@JIInstrumentationMethod
private void socketWrite(byte b[], int off, int len) throws IOException {
SocketWriteEvent event = SocketWriteEvent.EVENT.get();
if (!event.isEnabled()) {
socketWrite(b, off, len);
return;
}
int bytesWritten = 0;
try {
event.begin();
socketWrite(b, off, len);
bytesWritten = len;
} finally {
event.end() ;
if (event.shouldCommit()) {
String hostString = impl.address.toString();
int delimiterIndex = hostString.lastIndexOf('/');
event.host = hostString.substring(0, delimiterIndex);
event.address = hostString.substring(delimiterIndex + 1);
event.port = impl.port;
event.bytesWritten = bytesWritten < 0 ? 0 : bytesWritten;
event.commit();
event.reset();
}
}
}
private AbstractPlainSocketImpl impl = null;
void silenceFindBugsUnwrittenField(InetAddress dummy) {
impl.address = dummy;
}
static class AbstractPlainSocketImpl {
InetAddress address;
int port;
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.instrument;
import java.util.concurrent.atomic.AtomicLong;
import jdk.jfr.events.ErrorThrownEvent;
import jdk.jfr.events.ExceptionThrownEvent;
public final class ThrowableTracer {
private static AtomicLong numThrowables = new AtomicLong(0);
public static void traceError(Error e, String message) {
if (e instanceof OutOfMemoryError) {
return;
}
ErrorThrownEvent errorEvent = new ErrorThrownEvent();
errorEvent.message = message;
errorEvent.thrownClass = e.getClass();
errorEvent.commit();
ExceptionThrownEvent exceptionEvent = new ExceptionThrownEvent();
exceptionEvent.message = message;
exceptionEvent.thrownClass = e.getClass();
exceptionEvent.commit();
numThrowables.incrementAndGet();
}
public static void traceThrowable(Throwable t, String message) {
ExceptionThrownEvent event = new ExceptionThrownEvent();
event.message = message;
event.thrownClass = t.getClass();
event.commit();
numThrowables.incrementAndGet();
}
public static long numThrowables() {
return numThrowables.get();
}
}

View File

@@ -0,0 +1,247 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.jfc;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jdk.jfr.Configuration;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.SecuritySupport.SafePath;
/**
* {@link Configuration} factory for JFC files. *
*/
public final class JFC {
private static final int BUFFER_SIZE = 8192;
private static final int MAXIMUM_FILE_SIZE = 1024 * 1024;
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
private static volatile List<KnownConfiguration> knownConfigurations;
/**
* Reads a known configuration file (located into a string, but doesn't
* parse it until it's being used.
*/
private static final class KnownConfiguration {
private final String content;
private final String filename;
private final String name;
private Configuration configuration;
public KnownConfiguration(SafePath knownPath) throws IOException {
this.content = readContent(knownPath);
this.name = nameFromPath(knownPath.toPath());
this.filename = nullSafeFileName(knownPath.toPath());
}
public boolean isNamed(String name) {
return filename.equals(name) || this.name.equals(name);
}
public Configuration getConfigurationFile() throws IOException, ParseException {
if (configuration == null) {
configuration = JFCParser.createConfiguration(name, content);
}
return configuration;
}
public String getName() {
return name;
}
private static String readContent(SafePath knownPath) throws IOException {
if (SecuritySupport.getFileSize(knownPath) > MAXIMUM_FILE_SIZE) {
throw new IOException("Configuration with more than "
+ MAXIMUM_FILE_SIZE + " characters can't be read.");
}
try (InputStream r = SecuritySupport.newFileInputStream(knownPath)) {
return JFC.readContent(r);
}
}
}
private JFC() {
// private utility class
}
/**
* Reads a configuration from a file.
*
* @param path the file containing the configuration, not {@code null}
* @return {@link Configuration}, not {@code null}
* @throws ParseException if the file can't be parsed
* @throws IOException if the file can't be read
*
* @throws SecurityException if a security manager exists and its
* <code>checkRead</code> method denies read access to the file.
* @see java.io.File#getPath()
* @see java.lang.SecurityManager#checkRead(java.lang.String)
*/
public static Configuration create(String name, Reader reader) throws IOException, ParseException {
return JFCParser.createConfiguration(name, reader);
}
private static String nullSafeFileName(Path file) throws IOException {
Path filename = file.getFileName();
if (filename == null) {
throw new IOException("Path has no file name");
}
return filename.toString();
}
public static String nameFromPath(Path file) throws IOException {
String f = nullSafeFileName(file);
if (f.endsWith(JFCParser.FILE_EXTENSION)) {
return f.substring(0, f.length() - JFCParser.FILE_EXTENSION.length());
} else {
return f;
}
}
// Invoked by DCmdStart
public static Configuration createKnown(String name) throws IOException, ParseException {
// Known name, no need for permission
for (KnownConfiguration known : getKnownConfigurations()) {
if (known.isNamed(name)) {
return known.getConfigurationFile();
}
}
// Check JFC directory
SafePath path = SecuritySupport.JFC_DIRECTORY;
if (path != null && SecuritySupport.exists(path)) {
for (String extension : Arrays.asList("", JFCParser.FILE_EXTENSION)) {
SafePath file = new SafePath(path.toPath().resolveSibling(name + extension));
if (SecuritySupport.exists(file) && !SecuritySupport.isDirectory(file)) {
try (Reader r = SecuritySupport.newFileReader(file)) {
String jfcName = nameFromPath(file.toPath());
return JFCParser.createConfiguration(jfcName, r);
}
}
}
}
// Assume path included in name
Path localPath = Paths.get(name);
String jfcName = nameFromPath(localPath);
try (Reader r = Files.newBufferedReader(localPath)) {
return JFCParser.createConfiguration(jfcName, r);
}
}
private static String readContent(InputStream source) throws IOException {
byte[] bytes = read(source, BUFFER_SIZE);
return new String(bytes, StandardCharsets.UTF_8);
}
// copied from java.io.file.Files to avoid dependency on JDK 9 code
private static byte[] read(InputStream source, int initialSize) throws IOException {
int capacity = initialSize;
byte[] buf = new byte[capacity];
int nread = 0;
int n;
for (;;) {
// read to EOF which may read more or less than initialSize (eg: file
// is truncated while we are reading)
while ((n = source.read(buf, nread, capacity - nread)) > 0)
nread += n;
// if last call to source.read() returned -1, we are done
// otherwise, try to read one more byte; if that failed we're done too
if (n < 0 || (n = source.read()) < 0)
break;
// one more byte was read; need to allocate a larger buffer
if (capacity <= MAX_BUFFER_SIZE - capacity) {
capacity = Math.max(capacity << 1, BUFFER_SIZE);
} else {
if (capacity == MAX_BUFFER_SIZE)
throw new OutOfMemoryError("Required array size too large");
capacity = MAX_BUFFER_SIZE;
}
buf = Arrays.copyOf(buf, capacity);
buf[nread++] = (byte)n;
}
return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}
/**
* Returns list of predefined configurations available.
*
* @return list of configurations, not null
*/
public static List<Configuration> getConfigurations() {
List<Configuration> configs = new ArrayList<>();
for (KnownConfiguration knownConfig : getKnownConfigurations()) {
try {
configs.add(knownConfig.getConfigurationFile());
} catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.WARN, "Could not load configuration " + knownConfig.getName() + ". " + e.getMessage());
} catch (ParseException e) {
Logger.log(LogTag.JFR, LogLevel.WARN, "Could not parse configuration " + knownConfig.getName() + ". " + e.getMessage());
}
}
return configs;
}
private static List<KnownConfiguration> getKnownConfigurations() {
if (knownConfigurations == null) {
List<KnownConfiguration> configProxies = new ArrayList<>();
for (SafePath p : SecuritySupport.getPredefinedJFCFiles()) {
try {
configProxies.add(new KnownConfiguration(p));
} catch (IOException ioe) {
// ignore
}
}
knownConfigurations = configProxies;
}
return knownConfigurations;
}
public static Configuration getPredefined(String name) throws IOException, ParseException {
for (KnownConfiguration knownConfig : getKnownConfigurations()) {
if (knownConfig.getName().equals(name)) {
return knownConfig.getConfigurationFile();
}
}
throw new NoSuchFileException("Could not locate configuration with name " + name);
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2012, 2018, 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 jdk.jfr.internal.jfc;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
import java.text.ParseException;
import jdk.internal.org.xml.sax.InputSource;
import jdk.internal.org.xml.sax.SAXException;
import jdk.internal.util.xml.SAXParser;
import jdk.internal.util.xml.impl.SAXParserImpl;
import jdk.jfr.Configuration;
import jdk.jfr.internal.PrivateAccess;
/**
* Parses a JDK Flight Recorder Configuration file (.jfc)
*/
final class JFCParser {
static final String FILE_EXTENSION = ".jfc";
private static final int MAXIMUM_FILE_SIZE = 1024 * 1024;
public static Configuration createConfiguration(String name, Reader reader) throws IOException, ParseException {
return createConfiguration(name, readContent(reader));
}
public static Configuration createConfiguration(String name, String content) throws IOException, ParseException {
try {
JFCParserHandler ch = new JFCParserHandler();
parseXML(content, ch);
return PrivateAccess.getInstance().newConfiguration(name, ch.label, ch.description, ch.provider, ch.settings, content);
} catch (IllegalArgumentException iae) {
throw new ParseException(iae.getMessage(), -1);
} catch (SAXException e) {
ParseException pe = new ParseException("Error reading JFC file. " + e.getMessage(), -1);
pe.initCause(e);
throw pe;
}
}
private static void parseXML(String content, JFCParserHandler ch) throws SAXException, IOException {
CharArrayReader r = new CharArrayReader(content.toCharArray());
SAXParser parser = new SAXParserImpl();
parser.parse(new InputSource(r), ch);
}
private static String readContent(Reader r) throws IOException {
CharArrayWriter writer = new CharArrayWriter(1024);
int count = 0;
int ch;
while ((ch = r.read()) != -1) {
writer.write(ch);
count++;
if (count >= MAXIMUM_FILE_SIZE) {
throw new IOException("Presets with more than " + MAXIMUM_FILE_SIZE + " characters can't be read.");
}
}
return new String(writer.toCharArray());
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.jfc;
import java.util.LinkedHashMap;
import java.util.Map;
import jdk.internal.org.xml.sax.Attributes;
import jdk.internal.org.xml.sax.SAXException;
import jdk.internal.org.xml.sax.helpers.DefaultHandler;
final class JFCParserHandler extends DefaultHandler {
private static final String ELEMENT_CONFIGURATION = "configuration";
private static final String ELEMENT_EVENT_TYPE = "event";
private static final String ELEMENT_SETTING = "setting";
private static final String ATTRIBUTE_NAME = "name";
private static final String ATTRIBUTE_LABEL = "label";
private static final String ATTRIBUTE_DESCRIPTION = "description";
private static final String ATTRIBUTE_PROVIDER = "provider";
private static final String ATTRIBUTE_VERSION = "version";
final Map<String, String> settings = new LinkedHashMap<String, String>();
private String currentEventPath;
private String currentSettingsName;
private StringBuilder currentCharacters;
String label;
String provider;
String description;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
switch (qName.toLowerCase()) {
case ELEMENT_CONFIGURATION:
String version = attributes.getValue(ATTRIBUTE_VERSION);
if (version == null || !version.startsWith("2.")) {
throw new SAXException("This version of Flight Recorder can only read JFC file format version 2.x");
}
label = attributes.getValue(ATTRIBUTE_LABEL);
description = getOptional(attributes, ATTRIBUTE_DESCRIPTION, "");
provider = getOptional(attributes, ATTRIBUTE_PROVIDER, "");
break;
case ELEMENT_EVENT_TYPE:
currentEventPath = attributes.getValue(ATTRIBUTE_NAME);
break;
case ELEMENT_SETTING:
currentSettingsName = attributes.getValue(ATTRIBUTE_NAME);
break;
}
currentCharacters = null;
}
private String getOptional(Attributes attributes, String name, String defaultValue) {
String value = attributes.getValue(name);
return value == null ? defaultValue : value;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (currentCharacters == null) {
currentCharacters = new StringBuilder(length);
}
currentCharacters.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) {
switch (qName.toLowerCase()) {
case ELEMENT_CONFIGURATION:
break;
case ELEMENT_EVENT_TYPE:
currentEventPath = null;
break;
case ELEMENT_SETTING:
String settingsValue = currentCharacters == null ? "" : currentCharacters.toString();
settings.put(currentEventPath + "#" + currentSettingsName, "" + settingsValue);
currentSettingsName = null;
break;
}
}
public Map<String, String> getSettings() {
return settings;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, 2019, 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.
*/
/**
* This package contains classes for configuring Flight Recorder using JFC-files.
*
* @since 8
*/
package jdk.jfr.internal.jfc;

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.management;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jdk.jfr.EventType;
import jdk.jfr.Recording;
import jdk.jfr.internal.JVMSupport;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.MetadataRepository;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.WriteableUserPath;
import jdk.jfr.internal.instrument.JDKEvents;
/**
* The management API in module jdk.management.jfr should be built on top of the
* public API in jdk.jfr. Before putting more functionality here, consider if it
* should not be part of the public API, and if not, please provide motivation
*
*/
public final class ManagementSupport {
// Purpose of this method is to expose the event types to the
// FlightRecorderMXBean without instantiating Flight Recorder.
//
// This allows:
//
// 1) discoverability, so event settings can be exposed without the need to
// create a new Recording in FlightrecorderMXBean.
//
// 2) a graphical JMX client to list all attributes to the user, without
// loading JFR memory buffers. This is especially important when there is
// no intent to use Flight Recorder.
//
// An alternative design would be to make FlightRecorder#getEventTypes
// static, but it would the make the API look strange
//
public static List<EventType> getEventTypes() {
// would normally be checked when a Flight Recorder instance is created
Utils.checkAccessFlightRecorder();
if (JVMSupport.isNotAvailable()) {
return new ArrayList<>();
}
JDKEvents.initialize(); // make sure JDK events are available
return Collections.unmodifiableList(MetadataRepository.getInstance().getRegisteredEventTypes());
}
// Reuse internal code for parsing a timespan
public static long parseTimespan(String s) {
return Utils.parseTimespan(s);
}
// Reuse internal code for formatting settings
public static final String formatTimespan(Duration dValue, String separation) {
return Utils.formatTimespan(dValue, separation);
}
// Reuse internal logging mechanism
public static void logError(String message) {
Logger.log(LogTag.JFR, LogLevel.ERROR, message);
}
// Get the textual representation when the destination was set, which
// requires access to jdk.jfr.internal.PlatformRecording
public static String getDestinationOriginalText(Recording recording) {
PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
WriteableUserPath wup = pr.getDestination();
return wup == null ? null : wup.getOriginalText();
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.settings;
import java.util.Set;
/**
* Helper class for settings that use boolean numbers
*
*/
final class BooleanValue {
private String value = "false";
private boolean booleanValue;
private BooleanValue(boolean b) {
booleanValue = b;
value = b ? "true" : "false";
}
public String union(Set<String> values) {
for (String v : values) {
if ("true".equals(v)) {
return "true";
}
}
return "false";
}
public void setValue(String value) {
this.value = value;
this.booleanValue = Boolean.valueOf(value);
}
public final String getValue() {
return this.value;
}
public boolean getBoolean() {
return booleanValue;
}
public static BooleanValue valueOf(String defaultValue) {
if ("true".equals(defaultValue)) {
return new BooleanValue(true);
}
if ("false".equals(defaultValue)) {
return new BooleanValue(false);
}
throw new InternalError("Unknown default value for settings '" + defaultValue + "'");
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal.settings;
import java.util.Objects;
import java.util.Set;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.MetadataDefinition;
import jdk.jfr.Name;
import jdk.jfr.Timespan;
import jdk.jfr.internal.Control;
import jdk.jfr.internal.PlatformEventType;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
@MetadataDefinition
@Label("Cutoff")
@Description("Limit running time of event")
@Name(Type.SETTINGS_PREFIX + "Cutoff")
@Timespan
public final class CutoffSetting extends Control {
private final static long typeId = Type.getTypeId(CutoffSetting.class);
private String value = "0 ns";
private final PlatformEventType eventType;
public CutoffSetting(PlatformEventType eventType, String defaultValue) {
super(defaultValue);
this.eventType = Objects.requireNonNull(eventType);
}
@Override
public String combine(Set<String> values) {
long max = 0;
String text = "0 ns";
for (String value : values) {
long l = Utils.parseTimespanWithInfinity(value);
if (l > max) {
text = value;
max = l;
}
}
return text;
}
@Override
public void setValue(String value) {
long l = Utils.parseTimespanWithInfinity(value);
this.value = value;
eventType.setCutoff(l);
}
@Override
public String getValue() {
return value;
}
public static boolean isType(long typeId) {
return CutoffSetting.typeId == typeId;
}
public static long parseValueSafe(String value) {
if (value == null) {
return 0L;
}
try {
return Utils.parseTimespanWithInfinity(value);
} catch (NumberFormatException nfe) {
return 0L;
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.settings;
import java.util.Objects;
import java.util.Set;
import jdk.jfr.Description;
import jdk.jfr.BooleanFlag;
import jdk.jfr.Label;
import jdk.jfr.MetadataDefinition;
import jdk.jfr.Name;
import jdk.jfr.internal.PlatformEventType;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Control;
@MetadataDefinition
@Label("Enabled")
@Description("Record event")
@Name(Type.SETTINGS_PREFIX + "Enabled")
@BooleanFlag
public final class EnabledSetting extends Control {
private final BooleanValue booleanValue;
private final PlatformEventType eventType;
public EnabledSetting(PlatformEventType eventType, String defaultValue) {
super(defaultValue);
this.booleanValue = BooleanValue.valueOf(defaultValue);
this.eventType = Objects.requireNonNull(eventType);
}
@Override
public String combine(Set<String> values) {
return booleanValue.union(values);
}
@Override
public void setValue(String value) {
booleanValue.setValue(value);
eventType.setEnabled(booleanValue.getBoolean());
if (eventType.isEnabled() && !eventType.isJVM()) {
if (!eventType.isInstrumented()) {
eventType.markForInstrumentation(true);
}
}
}
@Override
public String getValue() {
return booleanValue.getValue();
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.settings;
import java.util.Objects;
import java.util.Set;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.MetadataDefinition;
import jdk.jfr.Name;
import jdk.jfr.internal.PlatformEventType;
import jdk.jfr.internal.Control;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
@MetadataDefinition
@Label("Period")
@Description("Record event at interval")
@Name(Type.SETTINGS_PREFIX + "Period")
public final class PeriodSetting extends Control {
private static final long typeId = Type.getTypeId(PeriodSetting.class);
public static final String EVERY_CHUNK = "everyChunk";
public static final String BEGIN_CHUNK = "beginChunk";
public static final String END_CHUNK = "endChunk";
public static final String NAME = "period";
private final PlatformEventType eventType;
private String value = EVERY_CHUNK;
public PeriodSetting(PlatformEventType eventType, String defaultValue) {
super(defaultValue);
this.eventType = Objects.requireNonNull(eventType);
}
@Override
public String combine(Set<String> values) {
boolean beginChunk = false;
boolean endChunk = false;
Long min = null;
String text = null;
for (String value : values) {
switch (value) {
case EVERY_CHUNK:
beginChunk = true;
endChunk = true;
break;
case BEGIN_CHUNK:
beginChunk = true;
break;
case END_CHUNK:
endChunk = true;
break;
default:
long l = Utils.parseTimespanWithInfinity(value);
// Always accept first specified value
if (min == null) {
text = value;
min = l;
} else {
if (l < min) {
text = value;
min = l;
}
}
}
}
// A specified interval trumps *_CHUNK
if (min != null) {
return text;
}
if (beginChunk && !endChunk) {
return BEGIN_CHUNK;
}
if (!beginChunk && endChunk) {
return END_CHUNK;
}
return EVERY_CHUNK; // also default
}
@Override
public void setValue(String value) {
switch (value) {
case EVERY_CHUNK:
eventType.setPeriod(0, true, true);
break;
case BEGIN_CHUNK:
eventType.setPeriod(0, true, false);
break;
case END_CHUNK:
eventType.setPeriod(0, false, true);
break;
default:
long nanos = Utils.parseTimespanWithInfinity(value);
if (nanos != Long.MAX_VALUE) {
eventType.setPeriod(nanos / 1_000_000, false, false);
} else {
eventType.setPeriod(Long.MAX_VALUE, false, false);
}
}
this.value = value;
}
@Override
public String getValue() {
return value;
}
public static boolean isType(long typeId) {
return PeriodSetting.typeId == typeId;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.settings;
import java.util.Objects;
import java.util.Set;
import jdk.jfr.Description;
import jdk.jfr.BooleanFlag;
import jdk.jfr.Label;
import jdk.jfr.MetadataDefinition;
import jdk.jfr.Name;
import jdk.jfr.internal.PlatformEventType;
import jdk.jfr.internal.Control;
import jdk.jfr.internal.Type;
@MetadataDefinition
@Label("Stack Trace")
@Name(Type.SETTINGS_PREFIX + "StackTrace")
@Description("Record stack traces")
@BooleanFlag
public final class StackTraceSetting extends Control {
private final static long typeId = Type.getTypeId(StackTraceSetting.class);
private final BooleanValue booleanValue;
private final PlatformEventType eventType;
public StackTraceSetting(PlatformEventType eventType, String defaultValue) {
super(defaultValue);
this.booleanValue = BooleanValue.valueOf(defaultValue);
this.eventType = Objects.requireNonNull(eventType);
}
@Override
public String combine(Set<String> values) {
return booleanValue.union(values);
}
@Override
public void setValue(String value) {
booleanValue.setValue(value);
eventType.setStackTraceEnabled(booleanValue.getBoolean());
}
@Override
public String getValue() {
return booleanValue.getValue();
}
public static boolean isType(long typeId) {
return StackTraceSetting.typeId == typeId;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.settings;
import java.util.Objects;
import java.util.Set;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.MetadataDefinition;
import jdk.jfr.Name;
import jdk.jfr.Timespan;
import jdk.jfr.internal.PlatformEventType;
import jdk.jfr.internal.Control;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
@MetadataDefinition
@Label("Threshold")
@Name(Type.SETTINGS_PREFIX + "Threshold")
@Description("Record event with duration above or equal to threshold")
@Timespan
public final class ThresholdSetting extends Control {
private final static long typeId = Type.getTypeId(ThresholdSetting.class);
private String value = "0 ns";
private final PlatformEventType eventType;
public ThresholdSetting(PlatformEventType eventType, String defaultValue) {
super(defaultValue);
this.eventType = Objects.requireNonNull(eventType);
}
@Override
public String combine(Set<String> values) {
Long min = null;
String text = null;
for (String value : values) {
long l = Utils.parseTimespanWithInfinity(value);
// always accept first value
if (min == null) {
min = l;
text = value;
} else {
if (l < min) {
text = value;
min = l;
}
}
}
return text == null ? "0 ns" : text;
}
@Override
public void setValue(String value) {
long l = Utils.parseTimespanWithInfinity(value);
this.value = value;
eventType.setThreshold(l);
}
@Override
public String getValue() {
return value;
}
public static boolean isType(long typeId) {
return ThresholdSetting.typeId == typeId;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal.test;
public final class WhiteBox {
private static boolean writeAllObjectSamples;
/**
* If OldObjectSample event is enabled, calling this method
* ensures that all object samples are written, including short-lived objects.
* Purpose of this method is to increase determinism in tests.
*
* @param writeAllObjectSamples if all samples should be written or not
*
*/
public static void setWriteAllObjectSamples(boolean writeAllSamples) {
writeAllObjectSamples = writeAllSamples;
}
public static boolean getWriteAllObjectSamples() {
return writeAllObjectSamples;
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.channels.FileChannel;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
final class Assemble extends Command {
@Override
public String getName() {
return "assemble";
}
@Override
public List<String> getOptionSyntax() {
return Collections.singletonList("<repository> <file>");
}
@Override
public String getDescription() {
return "Assemble leftover chunks from a disk repository into a recording file";
}
@Override
public void displayOptionUsage(PrintStream stream) {
stream.println(" <repository> Directory where the repository is located");
stream.println();
stream.println(" <file> Name of the recording file (.jfr) to create");
}
@Override
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
ensureMinArgumentCount(options, 2);
ensureMaxArgumentCount(options, 2);
Path repository = getDirectory(options.pop());
Path file = Paths.get(options.pop());
ensureFileDoesNotExist(file);
ensureJFRFile(file);
try (FileOutputStream fos = new FileOutputStream(file.toFile())) {
List<Path> files = listJFRFiles(repository);
if (files.isEmpty()) {
throw new UserDataException("no *.jfr files found at " + repository);
}
println();
println("Assembling files... ");
println();
transferTo(files, file, fos.getChannel());
println();
println("Finished.");
} catch (IOException e) {
throw new UserDataException("could not open destination file " + file + ". " + e.getMessage());
}
}
private List<Path> listJFRFiles(Path path) throws UserDataException {
try {
List<Path> files = new ArrayList<>();
if (Files.isDirectory(path)) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path, "*.jfr")) {
for (Path p : stream) {
if (!Files.isDirectory(p) && Files.isReadable(p)) {
files.add(p);
}
}
}
}
files.sort((u, v) -> u.getFileName().compareTo(v.getFileName()));
return files;
} catch (IOException ioe) {
throw new UserDataException("could not list *.jfr for directory " + path + ". " + ioe.getMessage());
}
}
private void transferTo(List<Path> sourceFiles, Path output, FileChannel out) throws UserDataException {
long pos = 0;
for (Path p : sourceFiles) {
println(" " + p.toString());
try (FileChannel sourceChannel = FileChannel.open(p)) {
long rem = Files.size(p);
while (rem > 0) {
long n = Math.min(rem, 1024 * 1024);
long w = out.transferFrom(sourceChannel, pos, n);
pos += w;
rem -= w;
}
} catch (IOException ioe) {
throw new UserDataException("could not copy recording chunk " + p + " to new file. " + ioe.getMessage());
}
}
}
}

View File

@@ -0,0 +1,306 @@
/*
* Copyright (c) 2016, 2019, 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 jdk.jfr.internal.tool;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOError;
import java.io.IOException;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
abstract class Command {
public final static String title = "Tool for working with Flight Recorder files (.jfr)";
private final static Command HELP = new Help();
private final static List<Command> COMMANDS = createCommands();
private static List<Command> createCommands() {
List<Command> commands = new ArrayList<>();
commands.add(new Print());
commands.add(new Metadata());
commands.add(new Summary());
commands.add(new Assemble());
commands.add(new Disassemble());
commands.add(new Version());
commands.add(HELP);
return Collections.unmodifiableList(commands);
}
static void displayHelp() {
System.out.println(title);
System.out.println();
displayAvailableCommands(System.out);
}
abstract public String getName();
abstract public String getDescription();
abstract public void execute(Deque<String> argList) throws UserSyntaxException, UserDataException;
protected String getTitle() {
return getDescription();
}
static void displayAvailableCommands(PrintStream stream) {
boolean first = true;
for (Command c : Command.COMMANDS) {
if (!first) {
System.out.println();
}
displayCommand(stream, c);
stream.println(" " + c.getDescription());
first = false;
}
}
protected static void displayCommand(PrintStream stream, Command c) {
boolean firstSyntax = true;
String alias = buildAlias(c);
String initial = " jfr " + c.getName();
for (String syntaxLine : c.getOptionSyntax()) {
if (firstSyntax) {
if (syntaxLine.length() != 0) {
stream.println(initial + " " + syntaxLine + alias);
} else {
stream.println(initial + alias);
}
} else {
for (int i = 0; i < initial.length(); i++) {
stream.print(" ");
}
stream.println(" " + syntaxLine);
}
firstSyntax = false;
}
}
private static String buildAlias(Command c) {
List<String> aliases = c.getAliases();
if (aliases.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
if (aliases.size() == 1) {
sb.append(" (alias ");
sb.append(aliases.get(0));
sb.append(")");
return sb.toString();
}
sb.append(" (aliases ");
for (int i = 0; i< aliases.size(); i ++ ) {
sb.append(aliases.get(i));
if (i < aliases.size() -1) {
sb.append(", ");
}
}
sb.append(")");
return sb.toString();
}
public static List<Command> getCommands() {
return COMMANDS;
}
public static Command valueOf(String commandName) {
for (Command command : COMMANDS) {
if (command.getName().equals(commandName)) {
return command;
}
}
return null;
}
public List<String> getOptionSyntax() {
return Collections.singletonList("");
}
public void displayOptionUsage(PrintStream stream) {
}
protected boolean acceptOption(Deque<String> options, String expected) throws UserSyntaxException {
if (expected.equals(options.peek())) {
if (options.size() < 2) {
throw new UserSyntaxException("missing value for " + options.peek());
}
options.remove();
return true;
}
return false;
}
protected void warnForWildcardExpansion(String option, String filter) throws UserDataException {
// Users should quote their wildcards to avoid expansion by the shell
try {
if (!filter.contains(File.pathSeparator)) {
Path p = Paths.get(".", filter);
if (!Files.exists(p)) {
return;
}
}
throw new UserDataException("wildcards should be quoted, for example " + option + " \"Foo*\"");
} catch (InvalidPathException ipe) {
// ignore
}
}
protected boolean acceptFilterOption(Deque<String> options, String expected) throws UserSyntaxException {
if (!acceptOption(options, expected)) {
return false;
}
if (options.isEmpty()) {
throw new UserSyntaxException("missing filter after " + expected);
}
String filter = options.peek();
if (filter.startsWith("--")) {
throw new UserSyntaxException("missing filter after " + expected);
}
return true;
}
final protected void ensureMaxArgumentCount(Deque<String> options, int maxCount) throws UserSyntaxException {
if (options.size() > maxCount) {
throw new UserSyntaxException("too many arguments");
}
}
final protected void ensureMinArgumentCount(Deque<String> options, int minCount) throws UserSyntaxException {
if (options.size() < minCount) {
throw new UserSyntaxException("too few arguments");
}
}
final protected Path getDirectory(String pathText) throws UserDataException {
try {
Path path = Paths.get(pathText).toAbsolutePath();
if (!Files.exists((path))) {
throw new UserDataException("directory does not exist, " + pathText);
}
if (!Files.isDirectory(path)) {
throw new UserDataException("path must be directory, " + pathText);
}
return path;
} catch (InvalidPathException ipe) {
throw new UserDataException("invalid path '" + pathText + "'");
}
}
final protected Path getJFRInputFile(Deque<String> options) throws UserSyntaxException, UserDataException {
if (options.isEmpty()) {
throw new UserSyntaxException("missing file");
}
String file = options.removeLast();
if (file.startsWith("--")) {
throw new UserSyntaxException("missing file");
}
try {
Path path = Paths.get(file).toAbsolutePath();
ensureAccess(path);
ensureJFRFile(path);
return path;
} catch (IOError ioe) {
throw new UserDataException("i/o error reading file '" + file + "', " + ioe.getMessage());
} catch (InvalidPathException ipe) {
throw new UserDataException("invalid path '" + file + "'");
}
}
private void ensureAccess(Path path) throws UserDataException {
try (RandomAccessFile rad = new RandomAccessFile(path.toFile(), "r")) {
if (rad.length() == 0) {
throw new UserDataException("file is empty '" + path + "'");
}
rad.read(); // try to read 1 byte
} catch (FileNotFoundException e) {
throw new UserDataException("could not open file " + e.getMessage());
} catch (IOException e) {
throw new UserDataException("i/o error reading file '" + path + "', " + e.getMessage());
}
}
final protected void couldNotReadError(Path p, IOException e) throws UserDataException {
throw new UserDataException("could not read recording at " + p.toAbsolutePath() + ". " + e.getMessage());
}
final protected Path ensureFileDoesNotExist(Path file) throws UserDataException {
if (Files.exists(file)) {
throw new UserDataException("file '" + file + "' already exists");
}
return file;
}
final protected void ensureJFRFile(Path path) throws UserDataException {
if (!path.toString().endsWith(".jfr")) {
throw new UserDataException("filename must end with '.jfr'");
}
}
protected void displayUsage(PrintStream stream) {
displayCommand(stream, this);
stream.println();
displayOptionUsage(stream);
}
final protected void println() {
System.out.println();
}
final protected void print(String text) {
System.out.print(text);
}
final protected void println(String text) {
System.out.println(text);
}
final protected boolean matches(String command) {
for (String s : getNames()) {
if (s.equals(command)) {
return true;
}
}
return false;
}
protected List<String> getAliases() {
return Collections.emptyList();
}
public List<String> getNames() {
List<String> names = new ArrayList<>();
names.add(getName());
names.addAll(getAliases());
return names;
}
}

View File

@@ -0,0 +1,250 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import jdk.jfr.internal.consumer.ChunkHeader;
import jdk.jfr.internal.consumer.RecordingInput;
final class Disassemble extends Command {
@Override
public String getName() {
return "disassemble";
}
@Override
public List<String> getOptionSyntax() {
List<String> list = new ArrayList<>();
list.add("[--output <directory>]");
list.add("[--max-chunks <chunks>]");
list.add("[--max-size <size>]");
list.add("<file>");
return list;
}
@Override
public void displayOptionUsage(PrintStream stream) {
stream.println(" --output <directory> The location to write the disassembled file,");
stream.println(" by default the current directory");
stream.println("");
stream.println(" --max-chunks <chunks> Maximum number of chunks per disassembled file,");
stream.println(" by default 5. The chunk size varies, but is ");
stream.println(" typically around 15 MB.");
stream.println("");
stream.println(" --max-size <size> Maximum number of bytes per file.");
stream.println("");
stream.println(" <file> Location of the recording file (.jfr)");
}
@Override
public String getDescription() {
return "Disassamble a recording file into smaller files/chunks";
}
@Override
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
if (options.isEmpty()) {
throw new UserSyntaxException("missing file");
}
Path file = getJFRInputFile(options);
int maxChunks = Integer.MAX_VALUE;
int maxsize = Integer.MAX_VALUE;
String output = System.getProperty("user.dir");
int optionCount = options.size();
while (optionCount > 0) {
if (acceptOption(options, "--output")) {
output = options.pop();
}
if (acceptOption(options, "--max-size")) {
String value = options.pop();
try {
maxsize = Integer.parseInt(value);
if (maxsize < 1) {
throw new UserDataException("max size must be at least 1");
}
} catch (NumberFormatException nfe) {
throw new UserDataException("not a valid value for --max-size.");
}
}
if (acceptOption(options, "--max-chunks")) {
String value = options.pop();
try {
maxChunks = Integer.parseInt(value);
if (maxChunks < 1) {
throw new UserDataException("max chunks must be at least 1.");
}
} catch (NumberFormatException nfe) {
throw new UserDataException("not a valid value for --max-size.");
}
}
if (optionCount == options.size()) {
// No progress made
throw new UserSyntaxException("unknown option " + options.peek());
}
optionCount = options.size();
}
Path outputPath = getDirectory(output);
println();
println("Examining recording " + file + " ...");
List<Long> sizes;
if (maxsize != Integer.MAX_VALUE && maxChunks == Integer.MAX_VALUE) {
try {
long fileSize = Files.size(file);
if (maxsize >=fileSize) {
println();
println("File size (" + fileSize +") does not exceed max size (" + maxsize + ")");
return;
}
} catch (IOException e) {
throw new UserDataException("unexpected i/o error when determining file size" + e.getMessage());
}
}
if (maxsize == Integer.MAX_VALUE && maxChunks == Integer.MAX_VALUE) {
maxChunks = 5;
}
try {
sizes = findChunkSizes(file);
} catch (IOException e) {
throw new UserDataException("unexpected i/o error. " + e.getMessage());
}
if (maxsize == Integer.MAX_VALUE == sizes.size() <= maxChunks) {
throw new UserDataException("number of chunks in recording (" + sizes.size() + ") doesn't exceed max chunks (" + maxChunks + ")");
}
println();
if (sizes.size() > 0) {
List<Long> combinedSizes = combineChunkSizes(sizes, maxChunks, maxsize);
print("File consists of " + sizes.size() + " chunks. The recording will be split into ");
println(combinedSizes.size() + " files");
println();
splitFile(outputPath, file, combinedSizes);
} else {
throw new UserDataException("no JFR chunks found in file.");
}
}
private List<Long> findChunkSizes(Path p) throws IOException {
try (RecordingInput input = new RecordingInput(p.toFile())) {
List<Long> sizes = new ArrayList<>();
ChunkHeader ch = new ChunkHeader(input);
sizes.add(ch.getSize());
while (!ch.isLastChunk()) {
ch = ch.nextHeader();
sizes.add(ch.getSize());
}
return sizes;
}
}
private List<Long> combineChunkSizes(List<Long> sizes, int maxChunks, long maxSize) {
List<Long> reduced = new ArrayList<Long>();
int chunks = 1;
long fileSize = sizes.get(0);
for (int i = 1; i < sizes.size(); i++) {
long size = sizes.get(i);
if (fileSize + size > maxSize) {
reduced.add(fileSize);
chunks = 1;
fileSize = size;
continue;
}
fileSize += size;
if (chunks == maxChunks) {
reduced.add(fileSize);
fileSize = 0;
chunks = 1;
continue;
}
chunks++;
}
if (fileSize != 0) {
reduced.add(fileSize);
}
return reduced;
}
private void splitFile(Path directory, Path file, List<Long> splitPositions) throws UserDataException {
int padAmountZeros = String.valueOf(splitPositions.size() - 1).length();
String fileName = file.getFileName().toString();
String fileFormatter = fileName.subSequence(0, fileName.length() - 4) + "_%0" + padAmountZeros + "d.jfr";
for (int i = 0; i < splitPositions.size(); i++) {
String formattedFilename = String.format(fileFormatter, i);
try {
Path p = directory.resolve(formattedFilename);
if (Files.exists(p)) {
throw new UserDataException("can't create disassembled file " + p + ", a file with that name already exist");
}
} catch (InvalidPathException ipe) {
throw new UserDataException("can't construct path with filename" + formattedFilename);
}
}
try (DataInputStream stream = new DataInputStream(new BufferedInputStream(new FileInputStream(file.toFile())))) {
for (int i = 0; i < splitPositions.size(); i++) {
Long l = splitPositions.get(i);
byte[] bytes = readBytes(stream, l.intValue());
String formattedFilename = String.format(fileFormatter, i);
Path p = directory.resolve(formattedFilename);
File splittedFile = p.toFile();
println("Writing " + splittedFile + " ... " + bytes.length);
FileOutputStream fos = new FileOutputStream(splittedFile);
fos.write(bytes);
fos.close();
}
} catch (IOException ioe) {
throw new UserDataException("i/o error writing file " + file);
}
}
private byte[] readBytes(InputStream stream, int count) throws UserDataException, IOException {
byte[] data = new byte[count];
int totalRead = 0;
while (totalRead < data.length) {
int read = stream.read(data, totalRead, data.length - totalRead);
if (read == -1) {
throw new UserDataException("unexpected end of data");
}
totalRead += read;
}
return data;
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal.tool;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import jdk.jfr.EventType;
import jdk.jfr.Timespan;
import jdk.jfr.Timestamp;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordingFile;
import jdk.jfr.internal.consumer.RecordingInternals;
abstract class EventPrintWriter extends StructuredWriter {
enum ValueType {
TIMESPAN, TIMESTAMP, OTHER
}
protected static final String STACK_TRACE_FIELD = "stackTrace";
protected static final String EVENT_THREAD_FIELD = "eventThread";
private Predicate<EventType> eventFilter = x -> true;
private int stackDepth;
// cach that will speed up annotation lookup
private Map<ValueDescriptor, ValueType> typeOfValues = new HashMap<>();
EventPrintWriter(PrintWriter p) {
super(p);
}
abstract protected void print(List<RecordedEvent> events);
void print(Path source) throws FileNotFoundException, IOException {
List<RecordedEvent> events = new ArrayList<>(500_000);
printBegin();
try (RecordingFile file = new RecordingFile(source)) {
while (file.hasMoreEvents()) {
RecordedEvent event = file.readEvent();
if (acceptEvent(event)) {
events.add(event);
}
if (RecordingInternals.INSTANCE.isLastEventInChunk(file)) {
RecordingInternals.INSTANCE.sort(events);
print(events);
events.clear();
}
}
}
printEnd();
flush(true);
}
protected void printEnd() {
}
protected void printBegin() {
}
public final void setEventFilter(Predicate<EventType> eventFilter) {
this.eventFilter = eventFilter;
}
protected final boolean acceptEvent(RecordedEvent event) {
return eventFilter.test(event.getEventType());
}
protected final int getStackDepth() {
return stackDepth;
}
protected final boolean isLateField(String name) {
return name.equals(EVENT_THREAD_FIELD) || name.equals(STACK_TRACE_FIELD);
}
public void setStackDepth(int stackDepth) {
this.stackDepth = stackDepth;
}
protected Object getValue(RecordedObject object, ValueDescriptor v) {
ValueType valueType = typeOfValues.get(v);
if (valueType == null) {
valueType = determineValueType(v);
typeOfValues.put(v, valueType);
}
switch (valueType) {
case TIMESPAN:
return object.getDuration(v.getName());
case TIMESTAMP:
return RecordingInternals.INSTANCE.getOffsetDataTime(object, v.getName());
default:
return object.getValue(v.getName());
}
}
// It's expensive t check
private ValueType determineValueType(ValueDescriptor v) {
if (v.getAnnotation(Timespan.class) != null) {
return ValueType.TIMESPAN;
}
if (v.getAnnotation(Timestamp.class) != null) {
return ValueType.TIMESTAMP;
}
return ValueType.OTHER;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
final class Help extends Command {
@Override
public String getName() {
return "help";
}
@Override
public List<String> getOptionSyntax() {
return Collections.singletonList("[<command>]");
}
protected List<String> getAliases() {
return Arrays.asList("--help", "-h", "-?");
}
@Override
public void displayOptionUsage(PrintStream stream) {
println(" <command> The name of the command to get help for");
}
@Override
public String getDescription() {
return "Display all available commands, or help about a specific command";
}
@Override
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
if (options.isEmpty()) {
Command.displayHelp();
return;
}
ensureMaxArgumentCount(options, 1);
String commandName = options.remove();
Command c = Command.valueOf(commandName);
if (c == null) {
throw new UserDataException("unknown command '" + commandName + "'");
}
println(c.getTitle());
println();
c.displayUsage(System.out);
}
}

View File

@@ -0,0 +1,261 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.PrintWriter;
import java.util.List;
import jdk.jfr.EventType;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedObject;
final class JSONWriter extends EventPrintWriter {
private boolean first = true;
public JSONWriter(PrintWriter writer) {
super(writer);
}
@Override
protected void printBegin() {
printObjectBegin();
printDataStructureName("recording");
printObjectBegin();
printDataStructureName("events");
printArrayBegin();
}
@Override
protected void print(List<RecordedEvent> events) {
for (RecordedEvent event : events) {
printNewDataStructure(first, true, null);
printEvent(event);
flush(false);
first = false;
}
}
@Override
protected void printEnd() {
printArrayEnd();;
printObjectEnd();
printObjectEnd();
}
private void printEvent(RecordedEvent event) {
printObjectBegin();
EventType type = event.getEventType();
printValue(true, false, "type", type.getName());
printNewDataStructure(false, false, "values");
printObjectBegin();
boolean first = true;
for (ValueDescriptor v : event.getFields()) {
printValueDescriptor(first, false, v, getValue(event, v));
first = false;
}
printObjectEnd();
printObjectEnd();
}
void printValue(boolean first, boolean arrayElement, String name, Object value) {
printNewDataStructure(first, arrayElement, name);
if (!printIfNull(value)) {
if (value instanceof Boolean) {
printAsString(value);
return;
}
if (value instanceof Double) {
Double dValue = (Double) value;
if (Double.isNaN(dValue) || Double.isInfinite(dValue)) {
printNull();
return;
}
printAsString(value);
return;
}
if (value instanceof Float) {
Float fValue = (Float) value;
if (Float.isNaN(fValue) || Float.isInfinite(fValue)) {
printNull();
return;
}
printAsString(value);
return;
}
if (value instanceof Number) {
printAsString(value);
return;
}
print("\"");
printEscaped(String.valueOf(value));
print("\"");
}
}
public void printObject(RecordedObject object) {
printObjectBegin();
boolean first = true;
for (ValueDescriptor v : object.getFields()) {
printValueDescriptor(first, false, v, getValue(object, v));
first = false;
}
printObjectEnd();
}
private void printArray(ValueDescriptor v, Object[] array) {
printArrayBegin();
boolean first = true;
int depth = 0;
for (Object arrayElement : array) {
if (!(arrayElement instanceof RecordedFrame) || depth < getStackDepth()) {
printValueDescriptor(first, true, v, arrayElement);
}
depth++;
first = false;
}
printArrayEnd();
}
private void printValueDescriptor(boolean first, boolean arrayElement, ValueDescriptor vd, Object value) {
if (vd.isArray() && !arrayElement) {
printNewDataStructure(first, arrayElement, vd.getName());
if (!printIfNull(value)) {
printArray(vd, (Object[]) value);
}
return;
}
if (!vd.getFields().isEmpty()) {
printNewDataStructure(first, arrayElement, vd.getName());
if (!printIfNull(value)) {
printObject((RecordedObject) value);
}
return;
}
printValue(first, arrayElement, vd.getName(), value);
}
private void printNewDataStructure(boolean first, boolean arrayElement, String name) {
if (!first) {
print(", ");
if (!arrayElement) {
println();
}
}
if (!arrayElement) {
printDataStructureName(name);
}
}
private boolean printIfNull(Object value) {
if (value == null) {
printNull();
return true;
}
return false;
}
private void printNull() {
print("null");
}
private void printDataStructureName(String text) {
printIndent();
print("\"");
printEscaped(text);
print("\": ");
}
private void printObjectEnd() {
retract();
println();
printIndent();
print("}");
}
private void printObjectBegin() {
println("{");
indent();
}
private void printArrayEnd() {
print("]");
}
private void printArrayBegin() {
print("[");
}
private void printEscaped(String text) {
for (int i = 0; i < text.length(); i++) {
printEscaped(text.charAt(i));
}
}
private void printEscaped(char c) {
if (c == '\b') {
print("\\b");
return;
}
if (c == '\n') {
print("\\n");
return;
}
if (c == '\t') {
print("\\t");
return;
}
if (c == '\f') {
print("\\f");
return;
}
if (c == '\r') {
print("\\r");
return;
}
if (c == '\"') {
print("\\\"");
return;
}
if (c == '\\') {
print("\\\\");
return;
}
if (c == '/') {
print("\\/");
return;
}
if (c > 0x7F || c < 32) {
print("\\u");
// 0x10000 will pad with zeros.
print(Integer.toHexString(0x10000 + (int) c).substring(1));
return;
}
print(c);
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
/**
* Launcher class for the JDK_HOME\bin\jfr tool
*
*/
public final class Main {
private static final int EXIT_OK = 0;
private static final int EXIT_FAILED = 1;
private static final int EXIT_WRONG_ARGUMENTS = 2;
public static void main(String... args) {
Deque<String> argList = new LinkedList<>(Arrays.asList(args));
if (argList.isEmpty()) {
System.out.println(Command.title);
System.out.println();
System.out.println("Before using this tool, you must have a recording file.");
System.out.println("A file can be created by starting a recording from command line:");
System.out.println();
System.out.println(" java -XX:StartFlightRecording:filename=recording.jfr,duration=30s ... ");
System.out.println();
System.out.println("A recording can also be started on already running Java Virtual Machine:");
System.out.println();
System.out.println(" jcmd (to list available pids)");
System.out.println(" jcmd <pid> JFR.start");
System.out.println();
System.out.println("Recording data can be dumped to file using the JFR.dump command:");
System.out.println();
System.out.println(" jcmd <pid> JFR.dump filename=recording.jfr");
System.out.println();
System.out.println("The contents of the recording can then be printed, for example:");
System.out.println();
System.out.println(" jfr print recording.jfr");
System.out.println();
System.out.println(" jfr print --events CPULoad,GarbageCollection recording.jfr");
System.out.println();
System.out.println(" jfr print --json --events CPULoad recording.jfr");
System.out.println();
System.out.println(" jfr print --categories \"GC,JVM,Java*\" recording.jfr");
System.out.println();
System.out.println(" jfr print --events \"jdk.*\" --stack-depth 64 recording.jfr");
System.out.println();
System.out.println(" jfr summary recording.jfr");
System.out.println();
System.out.println(" jfr metadata recording.jfr");
System.out.println();
System.out.println("For more information about available commands, use 'jfr help'");
System.exit(EXIT_OK);
}
String command = argList.remove();
for (Command c : Command.getCommands()) {
if (c.matches(command)) {
try {
c.execute(argList);
System.exit(EXIT_OK);
} catch (UserDataException ude) {
System.err.println("jfr " + c.getName() + ": " + ude.getMessage());
System.exit(EXIT_FAILED);
} catch (UserSyntaxException use) {
System.err.println("jfr " + c.getName() + ": " + use.getMessage());
System.err.println();
System.err.println("Usage:");
System.err.println();
c.displayUsage(System.err);
System.exit(EXIT_WRONG_ARGUMENTS);
} catch (Throwable e) {
System.err.println("jfr " + c.getName() + ": unexpected internal error, " + e.getMessage());
e.printStackTrace();
System.exit(EXIT_FAILED);
}
}
}
System.err.println("jfr: unknown command '" + command + "'");
System.err.println();
System.err.println("List of available commands:");
System.err.println();
Command.displayAvailableCommands(System.err);
System.exit(EXIT_WRONG_ARGUMENTS);
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal.tool;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import jdk.jfr.consumer.RecordingFile;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.RecordingInternals;
final class Metadata extends Command {
private static class TypeComparator implements Comparator<Type> {
@Override
public int compare(Type t1, Type t2) {
int g1 = groupValue(t1);
int g2 = groupValue(t2);
if (g1 == g2) {
String n1 = t1.getName();
String n2 = t2.getName();
String package1 = n1.substring(0, n1.lastIndexOf('.') + 1);
String package2 = n2.substring(0, n2.lastIndexOf('.') + 1);
if (package1.equals(package2)) {
return n1.compareTo(n2);
} else {
// Ensure that jdk.* are printed first
// This makes it easier to find user defined events at the end.
if (Type.SUPER_TYPE_EVENT.equals(t1.getSuperType()) && !package1.equals(package2)) {
if (package1.equals("jdk.jfr")) {
return -1;
}
if (package2.equals("jdk.jfr")) {
return 1;
}
}
return package1.compareTo(package2);
}
} else {
return Integer.compare(groupValue(t1), groupValue(t2));
}
}
int groupValue(Type t) {
String superType = t.getSuperType();
if (superType == null) {
return 1;
}
if (Type.SUPER_TYPE_ANNOTATION.equals(superType)) {
return 3;
}
if (Type.SUPER_TYPE_SETTING.equals(superType)) {
return 4;
}
if (Type.SUPER_TYPE_EVENT.equals(superType)) {
return 5;
}
return 2; // reserved for enums in the future
}
}
@Override
public String getName() {
return "metadata";
}
@Override
public List<String> getOptionSyntax() {
return Collections.singletonList("<file>");
}
@Override
public String getDescription() {
return "Display event metadata, such as labels, descriptions and field layout";
}
@Override
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
Path file = getJFRInputFile(options);
boolean showIds = false;
int optionCount = options.size();
while (optionCount > 0) {
if (acceptOption(options, "--ids")) {
showIds = true;
}
if (optionCount == options.size()) {
// No progress made
throw new UserSyntaxException("unknown option " + options.peek());
}
optionCount = options.size();
}
try (PrintWriter pw = new PrintWriter(System.out)) {
PrettyWriter prettyWriter = new PrettyWriter(pw);
prettyWriter.setShowIds(showIds);
try (RecordingFile rf = new RecordingFile(file)) {
List<Type> types = RecordingInternals.INSTANCE.readTypes(rf);
Collections.sort(types, new TypeComparator());
for (Type type : types) {
prettyWriter.printType(type);
}
prettyWriter.flush(true);
} catch (IOException ioe) {
couldNotReadError(file, ioe);
}
}
}
}

View File

@@ -0,0 +1,634 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.PrintWriter;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import jdk.jfr.AnnotationElement;
import jdk.jfr.DataAmount;
import jdk.jfr.Frequency;
import jdk.jfr.MemoryAddress;
import jdk.jfr.Percentage;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedClassLoader;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedMethod;
import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.consumer.RecordedThread;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
/**
* Print events in a human-readable format.
*
* This class is also used by {@link RecordedObject#toString()}
*/
public final class PrettyWriter extends EventPrintWriter {
private static final String TYPE_OLD_OBJECT = Type.TYPES_PREFIX + "OldObject";
private final static DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
private final static Long ZERO = 0L;
private boolean showIds;
private RecordedEvent currentEvent;
public PrettyWriter(PrintWriter destination) {
super(destination);
}
@Override
protected void print(List<RecordedEvent> events) {
for (RecordedEvent e : events) {
print(e);
flush(false);
}
}
public void printType(Type t) {
if (showIds) {
print("// id: ");
println(String.valueOf(t.getId()));
}
int commentIndex = t.getName().length() + 10;
String typeName = t.getName();
int index = typeName.lastIndexOf(".");
if (index != -1) {
println("@Name(\"" + typeName + "\")");
}
printAnnotations(commentIndex, t.getAnnotationElements());
print("class " + typeName.substring(index + 1));
String superType = t.getSuperType();
if (superType != null) {
print(" extends " + superType);
}
println(" {");
indent();
boolean first = true;
for (ValueDescriptor v : t.getFields()) {
printField(commentIndex, v, first);
first = false;
}
retract();
println("}");
println();
}
private void printField(int commentIndex, ValueDescriptor v, boolean first) {
if (!first) {
println();
}
printAnnotations(commentIndex, v.getAnnotationElements());
printIndent();
Type vType = PrivateAccess.getInstance().getType(v);
if (Type.SUPER_TYPE_SETTING.equals(vType.getSuperType())) {
print("static ");
}
print(makeSimpleType(v.getTypeName()));
if (v.isArray()) {
print("[]");
}
print(" ");
print(v.getName());
print(";");
printCommentRef(commentIndex, v.getTypeId());
}
private void printCommentRef(int commentIndex, long typeId) {
if (showIds) {
int column = getColumn();
if (column > commentIndex) {
print(" ");
} else {
while (column < commentIndex) {
print(" ");
column++;
}
}
println(" // id=" + typeId);
} else {
println();
}
}
private void printAnnotations(int commentIndex, List<AnnotationElement> annotations) {
for (AnnotationElement a : annotations) {
printIndent();
print("@");
print(makeSimpleType(a.getTypeName()));
List<ValueDescriptor> vs = a.getValueDescriptors();
if (!vs.isEmpty()) {
printAnnotation(a);
printCommentRef(commentIndex, a.getTypeId());
} else {
println();
}
}
}
private void printAnnotation(AnnotationElement a) {
StringJoiner sj = new StringJoiner(", ", "(", ")");
List<ValueDescriptor> vs = a.getValueDescriptors();
for (ValueDescriptor v : vs) {
Object o = a.getValue(v.getName());
if (vs.size() == 1 && v.getName().equals("value")) {
sj.add(textify(o));
} else {
sj.add(v.getName() + "=" + textify(o));
}
}
print(sj.toString());
}
private String textify(Object o) {
if (o.getClass().isArray()) {
Object[] array = (Object[]) o;
if (array.length == 1) {
return quoteIfNeeded(array[0]);
}
StringJoiner s = new StringJoiner(", ", "{", "}");
for (Object ob : array) {
s.add(quoteIfNeeded(ob));
}
return s.toString();
} else {
return quoteIfNeeded(o);
}
}
private String quoteIfNeeded(Object o) {
if (o instanceof String) {
return "\"" + o + "\"";
} else {
return String.valueOf(o);
}
}
private String makeSimpleType(String typeName) {
int index = typeName.lastIndexOf(".");
return typeName.substring(index + 1);
}
public void print(RecordedEvent event) {
currentEvent = event;
print(event.getEventType().getName(), " ");
println("{");
indent();
for (ValueDescriptor v : event.getFields()) {
String name = v.getName();
if (!isZeroDuration(event, name) && !isLateField(name)) {
printFieldValue(event, v);
}
}
if (event.getThread() != null) {
printIndent();
print(EVENT_THREAD_FIELD + " = ");
printThread(event.getThread(), "");
}
if (event.getStackTrace() != null) {
printIndent();
print(STACK_TRACE_FIELD + " = ");
printStackTrace(event.getStackTrace());
}
retract();
printIndent();
println("}");
println();
}
private boolean isZeroDuration(RecordedEvent event, String name) {
return name.equals("duration") && ZERO.equals(event.getValue("duration"));
}
private void printStackTrace(RecordedStackTrace stackTrace) {
println("[");
List<RecordedFrame> frames = stackTrace.getFrames();
indent();
int i = 0;
while (i < frames.size() && i < getStackDepth()) {
RecordedFrame frame = frames.get(i);
if (frame.isJavaFrame()) {
printIndent();
printValue(frame, null, "");
println();
i++;
}
}
if (stackTrace.isTruncated() || i == getStackDepth()) {
printIndent();
println("...");
}
retract();
printIndent();
println("]");
}
public void print(RecordedObject struct, String postFix) {
println("{");
indent();
for (ValueDescriptor v : struct.getFields()) {
printFieldValue(struct, v);
}
retract();
printIndent();
println("}" + postFix);
}
private void printFieldValue(RecordedObject struct, ValueDescriptor v) {
printIndent();
print(v.getName(), " = ");
printValue(getValue(struct, v), v, "");
}
private void printArray(Object[] array) {
println("[");
indent();
for (int i = 0; i < array.length; i++) {
printIndent();
printValue(array[i], null, i + 1 < array.length ? ", " : "");
}
retract();
printIndent();
println("]");
}
private void printValue(Object value, ValueDescriptor field, String postFix) {
if (value == null) {
println("N/A" + postFix);
return;
}
if (value instanceof RecordedObject) {
if (value instanceof RecordedThread) {
printThread((RecordedThread) value, postFix);
return;
}
if (value instanceof RecordedClass) {
printClass((RecordedClass) value, postFix);
return;
}
if (value instanceof RecordedClassLoader) {
printClassLoader((RecordedClassLoader) value, postFix);
return;
}
if (value instanceof RecordedFrame) {
RecordedFrame frame = (RecordedFrame) value;
if (frame.isJavaFrame()) {
printJavaFrame((RecordedFrame) value, postFix);
return;
}
}
if (value instanceof RecordedMethod) {
println(formatMethod((RecordedMethod) value));
return;
}
if (field.getTypeName().equals(TYPE_OLD_OBJECT)) {
printOldObject((RecordedObject) value);
return;
}
print((RecordedObject) value, postFix);
return;
}
if (value.getClass().isArray()) {
printArray((Object[]) value);
return;
}
if (value instanceof Double) {
Double d = (Double) value;
if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) {
println("N/A");
return;
}
}
if (value instanceof Float) {
Float f = (Float) value;
if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) {
println("N/A");
return;
}
}
if (value instanceof Long) {
Long l = (Long) value;
if (l == Long.MIN_VALUE) {
println("N/A");
return;
}
}
if (value instanceof Integer) {
Integer i = (Integer) value;
if (i == Integer.MIN_VALUE) {
println("N/A");
return;
}
}
if (field.getContentType() != null) {
if (printFormatted(field, value)) {
return;
}
}
String text = String.valueOf(value);
if (value instanceof String) {
text = "\"" + text + "\"";
}
println(text);
}
private void printOldObject(RecordedObject object) {
println(" [");
indent();
printIndent();
try {
printReferenceChain(object);
} catch (IllegalArgumentException iae) {
// Could not find a field
// Not possible to validate fields beforehand using RecordedObject#hasField
// since nested objects, for example object.referrer.array.index, requires
// an actual array object (which may be null).
}
retract();
printIndent();
println("]");
}
private void printReferenceChain(RecordedObject object) {
printObject(object, currentEvent.getLong("arrayElements"));
for (RecordedObject ref = object.getValue("referrer"); ref != null; ref = object.getValue("referrer")) {
long skip = ref.getLong("skip");
if (skip > 0) {
printIndent();
println("...");
}
String objectHolder = "";
long size = Long.MIN_VALUE;
RecordedObject array = ref.getValue("array");
if (array != null) {
long index = array.getLong("index");
size = array.getLong("size");
objectHolder = "[" + index + "]";
}
RecordedObject field = ref.getValue("field");
if (field != null) {
objectHolder = field.getString("name");
}
printIndent();
print(objectHolder);
print(" : ");
object = ref.getValue("object");
if (object != null) {
printObject(object, size);
}
}
}
void printObject(RecordedObject object, long arraySize) {
RecordedClass clazz = object.getClass("type");
if (clazz != null) {
String className = clazz.getName();
if (className!= null && className.startsWith("[")) {
className = decodeDescriptors(className, arraySize > 0 ? Long.toString(arraySize) : "").get(0);
}
print(className);
String description = object.getString("description");
if (description != null) {
print(" ");
print(description);
}
}
println();
}
private void printClassLoader(RecordedClassLoader cl, String postFix) {
// Purposely not printing class loader name to avoid cluttered output
RecordedClass clazz = cl.getType();
print(clazz == null ? "null" : clazz.getName());
if (clazz != null) {
print(" (");
print("id = ");
print(String.valueOf(cl.getId()));
println(")");
}
}
private void printJavaFrame(RecordedFrame f, String postFix) {
print(formatMethod(f.getMethod()));
int line = f.getLineNumber();
if (line >= 0) {
print(" line: " + line);
}
print(postFix);
}
private String formatMethod(RecordedMethod m) {
StringBuilder sb = new StringBuilder();
sb.append(m.getType().getName());
sb.append(".");
sb.append(m.getName());
sb.append("(");
StringJoiner sj = new StringJoiner(", ");
String md = m.getDescriptor().replace("/", ".");
String parameter = md.substring(1, md.lastIndexOf(")"));
for (String qualifiedName : decodeDescriptors(parameter, "")) {
String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
sj.add(typeName);
}
sb.append(sj);
sb.append(")");
return sb.toString();
}
private void printClass(RecordedClass clazz, String postFix) {
RecordedClassLoader classLoader = clazz.getClassLoader();
String classLoaderName = "null";
if (classLoader != null) {
if (classLoader.getName() != null) {
classLoaderName = classLoader.getName();
} else {
classLoaderName = classLoader.getType().getName();
}
}
String className = clazz.getName();
if (className.startsWith("[")) {
className = decodeDescriptors(className, "").get(0);
}
println(className + " (classLoader = " + classLoaderName + ")" + postFix);
}
List<String> decodeDescriptors(String descriptor, String arraySize) {
List<String> descriptors = new ArrayList<>();
for (int index = 0; index < descriptor.length(); index++) {
String arrayBrackets = "";
while (descriptor.charAt(index) == '[') {
arrayBrackets = arrayBrackets + "[" + arraySize + "]" ;
arraySize = "";
index++;
}
char c = descriptor.charAt(index);
String type;
switch (c) {
case 'L':
int endIndex = descriptor.indexOf(';', index);
type = descriptor.substring(index + 1, endIndex);
index = endIndex;
break;
case 'I':
type = "int";
break;
case 'J':
type = "long";
break;
case 'Z':
type = "boolean";
break;
case 'D':
type = "double";
break;
case 'F':
type = "float";
break;
case 'S':
type = "short";
break;
case 'C':
type = "char";
break;
case 'B':
type = "byte";
break;
default:
type = "<unknown-descriptor-type>";
}
descriptors.add(type + arrayBrackets);
}
return descriptors;
}
private void printThread(RecordedThread thread, String postFix) {
long javaThreadId = thread.getJavaThreadId();
if (javaThreadId > 0) {
println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix);
} else {
println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix);
}
}
private boolean printFormatted(ValueDescriptor field, Object value) {
if (value instanceof Duration) {
Duration d = (Duration) value;
if (d.getSeconds() == Long.MIN_VALUE && d.getNano() == 0) {
println("N/A");
return true;
}
double s = d.getNano() / 1000_000_000.0 + (int) (d.getSeconds() % 60);
if (s < 1.0) {
if (s < 0.001) {
println(String.format("%.3f", s * 1_000_000) + " us");
} else {
println(String.format("%.3f", s * 1_000) + " ms");
}
} else {
if (s < 1000.0) {
println(String.format("%.3f", s) + " s");
} else {
println(String.format("%.0f", s) + " s");
}
}
return true;
}
if (value instanceof OffsetDateTime) {
OffsetDateTime odt = (OffsetDateTime) value;
if (odt.equals(OffsetDateTime.MIN)) {
println("N/A");
return true;
}
println(TIME_FORMAT.format(odt));
return true;
}
Percentage percentage = field.getAnnotation(Percentage.class);
if (percentage != null) {
if (value instanceof Number) {
double d = ((Number) value).doubleValue();
println(String.format("%.2f", d * 100) + "%");
return true;
}
}
DataAmount dataAmount = field.getAnnotation(DataAmount.class);
if (dataAmount != null) {
if (value instanceof Number) {
Number n = (Number) value;
long amount = n.longValue();
if (field.getAnnotation(Frequency.class) != null) {
if (dataAmount.value().equals(DataAmount.BYTES)) {
println(Utils.formatBytesPerSecond(amount));
return true;
}
if (dataAmount.value().equals(DataAmount.BITS)) {
println(Utils.formatBitsPerSecond(amount));
return true;
}
} else {
if (dataAmount.value().equals(DataAmount.BYTES)) {
println(Utils.formatBytes(amount));
return true;
}
if (dataAmount.value().equals(DataAmount.BITS)) {
println(Utils.formatBits(amount));
return true;
}
}
}
}
MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class);
if (memoryAddress != null) {
if (value instanceof Number) {
long d = ((Number) value).longValue();
println(String.format("0x%08X", d));
return true;
}
}
Frequency frequency = field.getAnnotation(Frequency.class);
if (frequency != null) {
if (value instanceof Number) {
println(value + " Hz");
return true;
}
}
return false;
}
public void setShowIds(boolean showIds) {
this.showIds = showIds;
}
}

View File

@@ -0,0 +1,282 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import jdk.jfr.EventType;
final class Print extends Command {
@Override
public String getName() {
return "print";
}
@Override
public List<String> getOptionSyntax() {
List<String> list = new ArrayList<>();
list.add("[--xml|--json]");
list.add("[--categories <filter>]");
list.add("[--events <filter>]");
list.add("[--stack-depth <depth>]");
list.add("<file>");
return list;
}
@Override
protected String getTitle() {
return "Print contents of a recording file";
}
@Override
public String getDescription() {
return getTitle() + ". See 'jfr help print' for details.";
}
@Override
public void displayOptionUsage(PrintStream stream) {
stream.println(" --xml Print recording in XML format");
stream.println();
stream.println(" --json Print recording in JSON format");
stream.println();
stream.println(" --categories <filter> Select events matching a category name.");
stream.println(" The filter is a comma-separated list of names,");
stream.println(" simple and/or qualified, and/or quoted glob patterns");
stream.println();
stream.println(" --events <filter> Select events matching an event name.");
stream.println(" The filter is a comma-separated list of names,");
stream.println(" simple and/or qualified, and/or quoted glob patterns");
stream.println();
stream.println(" --stack-depth <depth> Number of frames in stack traces, by default 5");
stream.println();
stream.println(" <file> Location of the recording file (.jfr)");
stream.println();
stream.println();
stream.println("Example usage:");
stream.println();
stream.println(" jfr print --events OldObjectSample recording.jfr");
stream.println();
stream.println(" jfr print --events CPULoad,GarbageCollection recording.jfr");
stream.println();
stream.println(" jfr print --categories \"GC,JVM,Java*\" recording.jfr");
stream.println();
stream.println(" jfr print --events \"jdk.*\" --stack-depth 64 recording.jfr");
stream.println();
stream.println(" jfr print --json --events CPULoad recording.jfr");
}
@Override
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
Path file = getJFRInputFile(options);
PrintWriter pw = new PrintWriter(System.out, false);
Predicate<EventType> eventFilter = null;
int stackDepth = 5;
EventPrintWriter eventWriter = null;
int optionCount = options.size();
boolean foundEventFilter = false;
boolean foundCategoryFilter = false;
while (optionCount > 0) {
if (acceptFilterOption(options, "--events")) {
if (foundEventFilter) {
throw new UserSyntaxException("use --events event1,event2,event3 to include multiple events");
}
foundEventFilter = true;
String filter = options.remove();
warnForWildcardExpansion("--events", filter);
eventFilter = addEventFilter(filter, eventFilter);
}
if (acceptFilterOption(options, "--categories")) {
if (foundCategoryFilter) {
throw new UserSyntaxException("use --categories category1,category2 to include multiple categories");
}
foundCategoryFilter = true;
String filter = options.remove();
warnForWildcardExpansion("--categories", filter);
eventFilter = addCategoryFilter(filter, eventFilter);
}
if (acceptOption(options, "--stack-depth")) {
String value = options.pop();
try {
stackDepth = Integer.parseInt(value);
if (stackDepth < 0) {
throw new UserSyntaxException("stack depth must be zero or a positive integer.");
}
} catch (NumberFormatException nfe) {
throw new UserSyntaxException("not a valid value for --stack-depth");
}
}
if (acceptFormatterOption(options, eventWriter, "--json")) {
eventWriter = new JSONWriter(pw);
}
if (acceptFormatterOption(options, eventWriter, "--xml")) {
eventWriter = new XMLWriter(pw);
}
if (optionCount == options.size()) {
// No progress made
checkCommonError(options, "--event", "--events");
checkCommonError(options, "--category", "--categories");
throw new UserSyntaxException("unknown option " + options.peek());
}
optionCount = options.size();
}
if (eventWriter == null) {
eventWriter = new PrettyWriter(pw); // default to pretty printer
}
eventWriter.setStackDepth(stackDepth);
if (eventFilter != null) {
eventFilter = addCache(eventFilter, eventType -> eventType.getId());
eventWriter.setEventFilter(eventFilter);
}
try {
eventWriter.print(file);
} catch (IOException ioe) {
couldNotReadError(file, ioe);
}
pw.flush();
}
private void checkCommonError(Deque<String> options, String typo, String correct) throws UserSyntaxException {
if (typo.equals(options.peek())) {
throw new UserSyntaxException("unknown option " + typo + ", did you mean " + correct + "?");
}
}
private static boolean acceptFormatterOption(Deque<String> options, EventPrintWriter eventWriter, String expected) throws UserSyntaxException {
if (expected.equals(options.peek())) {
if (eventWriter != null) {
throw new UserSyntaxException("only one format can be specified at a time");
}
options.remove();
return true;
}
return false;
}
private static <T, X> Predicate<T> addCache(final Predicate<T> filter, Function<T, X> cacheFunction) {
Map<X, Boolean> cache = new HashMap<>();
return t -> cache.computeIfAbsent(cacheFunction.apply(t), x -> filter.test(t));
}
private static <T> Predicate<T> recurseIfPossible(Predicate<T> filter) {
return x -> filter != null && filter.test(x);
}
private static Predicate<EventType> addCategoryFilter(String filterText, Predicate<EventType> eventFilter) throws UserSyntaxException {
List<String> filters = explodeFilter(filterText);
Predicate<EventType> newFilter = recurseIfPossible(eventType -> {
for (String category : eventType.getCategoryNames()) {
for (String filter : filters) {
if (match(category, filter)) {
return true;
}
if (category.contains(" ") && acronomify(category).equals(filter)) {
return true;
}
}
}
return false;
});
return eventFilter == null ? newFilter : eventFilter.or(newFilter);
}
private static String acronomify(String multipleWords) {
boolean newWord = true;
String acronym = "";
for (char c : multipleWords.toCharArray()) {
if (newWord) {
if (Character.isAlphabetic(c) && Character.isUpperCase(c)) {
acronym += c;
}
}
newWord = Character.isWhitespace(c);
}
return acronym;
}
private static Predicate<EventType> addEventFilter(String filterText, final Predicate<EventType> eventFilter) throws UserSyntaxException {
List<String> filters = explodeFilter(filterText);
Predicate<EventType> newFilter = recurseIfPossible(eventType -> {
for (String filter : filters) {
String fullEventName = eventType.getName();
if (match(fullEventName, filter)) {
return true;
}
String eventName = fullEventName.substring(fullEventName.lastIndexOf(".") + 1);
if (match(eventName, filter)) {
return true;
}
}
return false;
});
return eventFilter == null ? newFilter : eventFilter.or(newFilter);
}
private static boolean match(String text, String filter) {
if (filter.length() == 0) {
// empty filter string matches if string is empty
return text.length() == 0;
}
if (filter.charAt(0) == '*') { // recursive check
filter = filter.substring(1);
for (int n = 0; n <= text.length(); n++) {
if (match(text.substring(n), filter))
return true;
}
} else if (text.length() == 0) {
// empty string and non-empty filter does not match
return false;
} else if (filter.charAt(0) == '?') {
// eat any char and move on
return match(text.substring(1), filter.substring(1));
} else if (filter.charAt(0) == text.charAt(0)) {
// eat chars and move on
return match(text.substring(1), filter.substring(1));
}
return false;
}
private static List<String> explodeFilter(String filter) throws UserSyntaxException {
List<String> list = new ArrayList<>();
for (String s : filter.split(",")) {
s = s.trim();
if (!s.isEmpty()) {
list.add(s);
}
}
return list;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.PrintWriter;
abstract class StructuredWriter {
private final static String LINE_SEPARATOR = String.format("%n");
private final PrintWriter out;
private final StringBuilder builder = new StringBuilder(4000);
private char[] indentionArray = new char[0];
private int indent = 0;
private int column;
// print first event immediately so tool feels responsive
private boolean first = true;
StructuredWriter(PrintWriter p) {
out = p;
}
final protected int getColumn() {
return column;
}
// Flush to print writer
public final void flush(boolean hard) {
if (hard) {
out.print(builder.toString());
builder.setLength(0);
return;
}
if (first || builder.length() > 100_000) {
out.print(builder.toString());
builder.setLength(0);
first = false;
}
}
final public void printIndent() {
builder.append(indentionArray, 0, indent);
column += indent;
}
final public void println() {
builder.append(LINE_SEPARATOR);
column = 0;
}
final public void print(String... texts) {
for (String text : texts) {
print(text);
}
}
final public void printAsString(Object o) {
print(String.valueOf(o));
}
final public void print(String text) {
builder.append(text);
column += text.length();
}
final public void print(char c) {
builder.append(c);
column++;
}
final public void print(int value) {
print(String.valueOf(value));
}
final public void indent() {
indent += 2;
updateIndent();
}
final public void retract() {
indent -= 2;
updateIndent();
}
final public void println(String text) {
print(text);
println();
}
private void updateIndent() {
if (indent > indentionArray.length) {
indentionArray = new char[indent];
for (int i = 0; i < indentionArray.length; i++) {
indentionArray[i] = ' ';
}
}
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import jdk.jfr.EventType;
import jdk.jfr.internal.MetadataDescriptor;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.ChunkHeader;
import jdk.jfr.internal.consumer.RecordingInput;
final class Summary extends Command {
private final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withLocale(Locale.UK).withZone(ZoneOffset.UTC);
@Override
public String getName() {
return "summary";
}
private static class Statistics {
Statistics(String name) {
this.name = name;
}
String name;
long count;
long size;
}
@Override
public List<String> getOptionSyntax() {
return Collections.singletonList("<file>");
}
@Override
public void displayOptionUsage(PrintStream stream) {
stream.println(" <file> Location of the recording file (.jfr) to display information about");
}
@Override
public String getDescription() {
return "Display general information about a recording file (.jfr)";
}
@Override
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
ensureMaxArgumentCount(options, 1);
Path p = getJFRInputFile(options);
try {
printInformation(p);
} catch (IOException e) {
couldNotReadError(p, e);
}
}
private void printInformation(Path p) throws IOException {
long totalDuration = 0;
long chunks = 0;
try (RecordingInput input = new RecordingInput(p.toFile())) {
ChunkHeader first = new ChunkHeader(input);
ChunkHeader ch = first;
String eventPrefix = Type.EVENT_NAME_PREFIX;
if (first.getMajor() == 1) {
eventPrefix = "com.oracle.jdk.";
}
HashMap<Long, Statistics> stats = new HashMap<>();
stats.put(0L, new Statistics(eventPrefix + "Metadata"));
stats.put(1L, new Statistics(eventPrefix + "CheckPoint"));
int minWidth = 0;
while (true) {
long chunkEnd = ch.getEnd();
MetadataDescriptor md = ch.readMetadata();
for (EventType eventType : md.getEventTypes()) {
stats.computeIfAbsent(eventType.getId(), (e) -> new Statistics(eventType.getName()));
minWidth = Math.max(minWidth, eventType.getName().length());
}
totalDuration += ch.getDurationNanos();
chunks++;
input.position(ch.getEventStart());
while (input.position() < chunkEnd) {
long pos = input.position();
int size = input.readInt();
long eventTypeId = input.readLong();
Statistics s = stats.get(eventTypeId);
if (s != null) {
s.count++;
s.size += size;
}
input.position(pos + size);
}
if (ch.isLastChunk()) {
break;
}
ch = ch.nextHeader();
}
println();
long epochSeconds = first.getStartNanos() / 1_000_000_000L;
long adjustNanos = first.getStartNanos() - epochSeconds * 1_000_000_000L;
println(" Version: " + first.getMajor() + "." + first.getMinor());
println(" Chunks: " + chunks);
println(" Start: " + DATE_FORMAT.format(Instant.ofEpochSecond(epochSeconds, adjustNanos)) + " (UTC)");
println(" Duration: " + (totalDuration + 500_000_000) / 1_000_000_000 + " s");
List<Statistics> statsList = new ArrayList<>(stats.values());
Collections.sort(statsList, (u, v) -> Long.compare(v.count, u.count));
println();
String header = " Count Size (bytes) ";
String typeHeader = " Event Type";
minWidth = Math.max(minWidth, typeHeader.length());
println(typeHeader + pad(minWidth - typeHeader.length(), ' ') + header);
println(pad(minWidth + header.length(), '='));
for (Statistics s : statsList) {
System.out.printf(" %-" + minWidth + "s%10d %12d\n", s.name, s.count, s.size);
}
}
}
private String pad(int count, char c) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append(c);
}
return sb.toString();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal.tool;
/**
* Exception that is thrown if there is something wrong with the input, for instance
* a file that can't be read or a numerical value that is out of range.
* <p>
* When this exception is thrown, a user will typically not want to see the
* command line syntax, but instead information about what was wrong with the
* input.
*/
final class UserDataException extends Exception {
private static final long serialVersionUID = 6656457380115167810L;
/**
* The error message.
*
* The first letter should not be capitalized, so a context can be printed prior
* to the error message.
*
* @param errorMessage
*/
public UserDataException(String errorMessage) {
super(errorMessage);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal.tool;
/**
* Exception that is thrown if options don't follow the syntax of the command.
*/
final class UserSyntaxException extends Exception {
private static final long serialVersionUID = 3437009454344160933L;
/**
* The error message.
*
* The first letter should not be capitalized, so a context can be printed prior
* to the error message.
*
* @param errorMessage
*/
public UserSyntaxException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2018, 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 jdk.jfr.internal.tool;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
final class Version extends Command {
@Override
public String getName() {
return "version";
}
@Override
public String getDescription() {
return "Display version of the jfr tool";
}
@Override
public void execute(Deque<String> options) {
System.out.println("1.0");
}
protected List<String> getAliases() {
return Arrays.asList("--version");
}
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (c) 2016, 2018, 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 jdk.jfr.internal.tool;
import java.io.PrintWriter;
import java.util.List;
import jdk.jfr.EventType;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedObject;
final class XMLWriter extends EventPrintWriter {
public XMLWriter(PrintWriter destination) {
super(destination);
}
@Override
protected void printBegin() {
println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
println("<recording xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
indent();
printIndent();
println("<events>");
indent();
}
@Override
protected void printEnd() {
retract();
printIndent();
println("</events>");
retract();
println("</recording>");
}
@Override
protected void print(List<RecordedEvent> events) {
for (RecordedEvent event : events) {
printEvent(event);
}
}
private void printEvent(RecordedEvent event) {
EventType type = event.getEventType();
printIndent();
print("<event");
printAttribute("type", type.getName());
print(">");
println();
indent();
for (ValueDescriptor v : event.getFields()) {
printValueDescriptor(v, getValue(event, v), -1);
}
retract();
printIndent();
println("</event>");
println();
}
private void printAttribute(String name, String value) {
print(" ");
print(name); // Only known strings
print("=\"");
printEscaped(value);
print("\"");
}
public void printObject(RecordedObject struct) {
println();
indent();
for (ValueDescriptor v : struct.getFields()) {
printValueDescriptor(v, getValue(struct, v), -1);
}
retract();
}
private void printArray(ValueDescriptor v, Object[] array) {
println();
indent();
int depth = 0;
for (int index = 0; index < array.length; index++) {
Object arrayElement = array[index];
if (!(arrayElement instanceof RecordedFrame) || depth < getStackDepth()) {
printValueDescriptor(v, array[index], index);
}
depth++;
}
retract();
}
private void printValueDescriptor(ValueDescriptor vd, Object value, int index) {
boolean arrayElement = index != -1;
String name = arrayElement ? null : vd.getName();
if (vd.isArray() && !arrayElement) {
if (printBeginElement("array", name, value, index)) {
printArray(vd, (Object[]) value);
printIndent();
printEndElement("array");
}
return;
}
if (!vd.getFields().isEmpty()) {
if (printBeginElement("struct", name, value, index)) {
printObject((RecordedObject) value);
printIndent();
printEndElement("struct");
}
return;
}
if (printBeginElement("value", name, value, index)) {
printEscaped(String.valueOf(value));
printEndElement("value");
}
}
private boolean printBeginElement(String elementName, String name, Object value, int index) {
printIndent();
print("<", elementName);
if (name != null) {
printAttribute("name", name);
}
if (index != -1) {
printAttribute("index", Integer.toString(index));
}
if (value == null) {
printAttribute("xsi:nil", "true");
println("/>");
return false;
}
if (value.getClass().isArray()) {
Object[] array = (Object[]) value;
printAttribute("size", Integer.toString(array.length));
}
print(">");
return true;
}
private void printEndElement(String elementName) {
print("</");
print(elementName);
println(">");
}
private void printEscaped(String text) {
for (int i = 0; i < text.length(); i++) {
printEscaped(text.charAt(i));
}
}
private void printEscaped(char c) {
if (c == 34) {
print("&quot;");
return;
}
if (c == 38) {
print("&amp;");
return;
}
if (c == 39) {
print("&apos;");
return;
}
if (c == 60) {
print("&lt;");
return;
}
if (c == 62) {
print("&gt;");
return;
}
if (c > 0x7F) {
print("&#");
print((int) c);
print(';');
return;
}
print(c);
}
}