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,420 @@
/*
* 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;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
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.Objects;
import java.util.Set;
import java.util.StringJoiner;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.TypeLibrary;
import jdk.jfr.internal.Utils;
/**
* Describes event metadata, such as labels, descriptions and units.
* <p>
* The following example shows how {@code AnnotationElement} can be used to dynamically define events.
*
* <pre>
* <code>
* List{@literal <}AnnotationElement{@literal >} typeAnnotations = new ArrayList{@literal <}{@literal >}();
* typeannotations.add(new AnnotationElement(Name.class, "com.example.HelloWorld");
* typeAnnotations.add(new AnnotationElement(Label.class, "Hello World"));
* typeAnnotations.add(new AnnotationElement(Description.class, "Helps programmer getting started"));
*
* List{@literal <}AnnotationElement{@literal >} fieldAnnotations = new ArrayList{@literal <}{@literal >}();
* fieldAnnotations.add(new AnnotationElement(Label.class, "Message"));
*
* List{@literal <}ValueDescriptor{@literal >} fields = new ArrayList{@literal <}{@literal >}();
* fields.add(new ValueDescriptor(String.class, "message", fieldAnnotations));
*
* EventFactory f = EventFactory.create(typeAnnotations, fields);
* Event event = f.newEvent();
* event.commit();
* </code>
* </pre>
*
* @since 8
*/
public final class AnnotationElement {
private final Type type;
private final List<Object> annotationValues;
private final List<String> annotationNames;
private final boolean inBootClassLoader;
// package private
AnnotationElement(Type type, List<Object> objects, boolean boot) {
Objects.requireNonNull(type);
Objects.requireNonNull(objects);
this.type = type;
if (objects.size() != type.getFields().size()) {
StringJoiner descriptors = new StringJoiner(",", "[", "]");
for (ValueDescriptor v : type.getFields()) {
descriptors.add(v.getName());
}
StringJoiner values = new StringJoiner(",", "[", "]");
for (Object object : objects) {
descriptors.add(String.valueOf(object));
}
throw new IllegalArgumentException("Annotation " + descriptors + " for " + type.getName() + " doesn't match number of values " + values);
}
List<String> n = new ArrayList<>();
List<Object> v = new ArrayList<>();
int index = 0;
for (ValueDescriptor valueDescriptor : type.getFields()) {
Object object = objects.get(index);
if (object == null) {
throw new IllegalArgumentException("Annotation value can't be null");
}
Class<?> valueType = object.getClass();
if (valueDescriptor.isArray()) {
valueType = valueType.getComponentType();
}
checkType(Utils.unboxType(valueType));
n.add(valueDescriptor.getName());
v.add(object);
index++;
}
this.annotationValues = Utils.smallUnmodifiable(v);
this.annotationNames = Utils.smallUnmodifiable(n);
this.inBootClassLoader = boot;
}
/**
* Creates an annotation element to use for dynamically defined events.
* <p>
* Supported value types are {@code byte}, {@code int}, {@code short},
* {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char},
* and {@code String}. Enums, arrays and classes, are not supported.
* <p>
* If {@code annotationType} has annotations (directly present, indirectly
* present, or associated), then those annotation are recursively included.
* However, both the {@code annotationType} and any annotation found recursively
* must have the {@link MetadataDefinition} annotation.
* <p>
* To statically define events, see {@link Event} class.
*
* @param annotationType interface extending
* {@code java.lang.annotation.Annotation}, not {@code null}
* @param values a {@code Map} with keys that match method names of the specified
* annotation interface
* @throws IllegalArgumentException if value/key is {@code null}, an unsupported
* value type is used, or a value/key is used that doesn't match the
* signatures in the {@code annotationType}
*/
public AnnotationElement(Class<? extends Annotation> annotationType, Map<String, Object> values) {
Objects.requireNonNull(annotationType);
Objects.requireNonNull(values);
Utils.checkRegisterPermission();
// copy values to avoid modification after validation
HashMap<String, Object> map = new HashMap<>(values);
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey() == null) {
throw new NullPointerException("Name of annotation method can't be null");
}
if (entry.getValue() == null) {
throw new NullPointerException("Return value for annotation method can't be null");
}
}
if (AnnotationElement.class.isAssignableFrom(annotationType) && annotationType.isInterface()) {
throw new IllegalArgumentException("Must be interface extending " + Annotation.class.getName());
}
if (!isKnownJFRAnnotation(annotationType) && annotationType.getAnnotation(MetadataDefinition.class) == null) {
throw new IllegalArgumentException("Annotation class must be annotated with jdk.jfr.MetadataDefinition to be valid");
}
if (isKnownJFRAnnotation(annotationType)) {
this.type = new Type(annotationType.getCanonicalName(), Type.SUPER_TYPE_ANNOTATION, Type.getTypeId(annotationType));
} else {
this.type = TypeLibrary.createAnnotationType(annotationType);
}
Method[] methods = annotationType.getDeclaredMethods();
if (methods.length != map.size()) {
throw new IllegalArgumentException("Number of declared methods must match size of value map");
}
List<String> n = new ArrayList<>();
List<Object> v = new ArrayList<>();
Set<String> nameSet = new HashSet<>();
for (Method method : methods) {
String fieldName = method.getName();
Object object = map.get(fieldName);
if (object == null) {
throw new IllegalArgumentException("No method in annotation interface " + annotationType.getName() + " matching name " + fieldName);
}
Class<?> fieldType = object.getClass();
if (fieldType == Class.class) {
throw new IllegalArgumentException("Annotation value for " + fieldName + " can't be class");
}
if (object instanceof Enum) {
throw new IllegalArgumentException("Annotation value for " + fieldName + " can't be enum");
}
if (!fieldType.equals(object.getClass())) {
throw new IllegalArgumentException("Return type of annotation " + fieldType.getName() + " must match type of object" + object.getClass());
}
if (fieldType.isArray()) {
Class<?> componentType = fieldType.getComponentType();
checkType(componentType);
if (componentType.equals(String.class)) {
String[] stringArray = (String[]) object;
for (int i = 0; i < stringArray.length; i++) {
if (stringArray[i] == null) {
throw new IllegalArgumentException("Annotation value for " + fieldName + " contains null");
}
}
}
} else {
fieldType = Utils.unboxType(object.getClass());
checkType(fieldType);
}
if (nameSet.contains(fieldName)) {
throw new IllegalArgumentException("Value with name '" + fieldName + "' already exists");
}
if (isKnownJFRAnnotation(annotationType)) {
ValueDescriptor vd = new ValueDescriptor(fieldType, fieldName, Collections.emptyList(), true);
type.add(vd);
}
n.add(fieldName);
v.add(object);
}
this.annotationValues = Utils.smallUnmodifiable(v);
this.annotationNames = Utils.smallUnmodifiable(n);
this.inBootClassLoader = annotationType.getClassLoader() == null;
}
/**
* Creates an annotation element to use for dynamically defined events.
* <p>
* Supported value types are {@code byte}, {@code int}, {@code short},
* {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char},
* and {@code String}. Enums, arrays, and classes are not supported.
* <p>
* If {@code annotationType} has annotations (directly present, indirectly
* present, or associated), then those annotations are recursively included.
* However, both {@code annotationType} and any annotation found recursively
* must have the {@link MetadataDefinition} annotation.
* <p>
* To statically define events, see {@link Event} class.
*
* @param annotationType interface extending
* {@code java.lang.annotation.Annotation,} not {@code null}
* @param value the value that matches the {@code value} method of the specified
* {@code annotationType}
* @throws IllegalArgumentException if value/key is {@code null}, an unsupported
* value type is used, or a value/key is used that doesn't match the
* signatures in the {@code annotationType}
*/
public AnnotationElement(Class<? extends Annotation> annotationType, Object value) {
this(annotationType, Collections.singletonMap("value", Objects.requireNonNull(value)));
}
/**
* Creates an annotation element to use for dynamically defined events.
* <p>
* Supported value types are {@code byte}, {@code short}, {@code int},
* {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char},
* and {@code String}. Enums, arrays, and classes are not supported.
* <p>
* If {@code annotationType} has annotations (directly present, indirectly
* present or associated), then those annotation are recursively included.
* However, both {@code annotationType} and any annotation found recursively
* must have the {@link MetadataDefinition} annotation.
* <p>
* To statically define events, see {@link Event} class.
*
* @param annotationType interface extending java.lang.annotation.Annotation,
* not {@code null}
*/
public AnnotationElement(Class<? extends Annotation> annotationType) {
this(annotationType, Collections.emptyMap());
}
/**
* Returns an immutable list of annotation values in an order that matches the
* value descriptors for this {@code AnnotationElement}.
*
* @return list of values, not {@code null}
*/
public List<Object> getValues() {
return annotationValues;
}
/**
* Returns an immutable list of descriptors that describes the annotation values
* for this {@code AnnotationElement}.
*
* @return the list of value descriptors for this {@code Annotation}, not
* {@code null}
*/
public List<ValueDescriptor> getValueDescriptors() {
return Collections.unmodifiableList(type.getFields());
}
/**
* Returns an immutable list of annotation elements for this
* {@code AnnotationElement}.
*
* @return a list of meta annotation, not {@code null}
*/
public List<AnnotationElement> getAnnotationElements() {
return type.getAnnotationElements();
}
/**
* Returns the fully qualified name of the annotation type that corresponds to
* this {@code AnnotationElement} (for example, {@code "jdk.jfr.Label"}).
*
* @return type name, not {@code null}
*/
public String getTypeName() {
return type.getName();
}
/**
* Returns a value for this {@code AnnotationElement}.
*
* @param name the name of the method in the annotation interface, not
* {@code null}.
*
* @return the annotation value, not {@code null}.
*
* @throws IllegalArgumentException if a method with the specified name does
* not exist in the annotation
*/
public Object getValue(String name) {
Objects.requireNonNull(name);
int index = 0;
for (String n : annotationNames) {
if (name.equals(n)) {
return annotationValues.get(index);
}
index++;
}
StringJoiner valueNames = new StringJoiner(",", "[", "]");
for (ValueDescriptor v : type.getFields()) {
valueNames.add(v.getName());
}
throw new IllegalArgumentException("No value with name '" + name + "'. Valid names are " + valueNames);
}
/**
* Returns {@code true} if an annotation value with the specified name exists in
* this {@code AnnotationElement}.
*
* @param name name of the method in the annotation interface to find, not
* {@code null}
*
* @return {@code true} if method exists, {@code false} otherwise
*/
public boolean hasValue(String name) {
Objects.requireNonNull(name);
for (String n : annotationNames) {
if (name.equals(n)) {
return true;
}
}
return false;
}
/**
* Returns the first annotation for the specified type if an
* {@code AnnotationElement} with the same name exists, else {@code null}.
*
* @param <A> the type of the annotation to query for and return if it exists
* @param annotationType the {@code Class object} corresponding to the annotation type,
* not {@code null}
* @return this element's annotation for the specified annotation type if
* it it exists, else {@code null}
*/
public final <A> A getAnnotation(Class<? extends Annotation> annotationType) {
Objects.requireNonNull(annotationType);
return type.getAnnotation(annotationType);
}
/**
* Returns the type ID for this {@code AnnotationElement}.
* <p>
* The ID is a unique identifier for the type in the Java Virtual Machine (JVM). The ID might not
* be the same between JVM instances.
*
* @return the type ID, not negative
*/
public long getTypeId() {
return type.getId();
}
// package private
Type getType() {
return type;
}
private static void checkType(Class<?> type) {
if (type.isPrimitive()) {
return;
}
if (type == String.class) {
return;
}
throw new IllegalArgumentException("Only primitives types or java.lang.String are allowed");
}
// Whitelist of annotation classes that are allowed, even though
// they don't have @MetadataDefinition.
private static boolean isKnownJFRAnnotation(Class<? extends Annotation> annotationType) {
if (annotationType == Registered.class) {
return true;
}
if (annotationType == Threshold.class) {
return true;
}
if (annotationType == StackTrace.class) {
return true;
}
if (annotationType == Period.class) {
return true;
}
if (annotationType == Enabled.class) {
return true;
}
return false;
}
// package private
boolean isInBoot() {
return inBootClassLoader;
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that the value is a boolean flag, a {@code true} or
* {@code false} value
*
* @since 8
*/
@MetadataDefinition
@ContentType
@Label("Flag")
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
public @interface BooleanFlag {
}

View File

@@ -0,0 +1,130 @@
/*
* 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;
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;
/**
* Event annotation, to associate the event type with a category, in the format
* of a human-readable path.
* <p>
* The category determines how an event is presented to the user. Events that
* are in the same category are typically displayed together in graphs and
* trees. To avoid the overlap of durational events in graphical
* representations, overlapping events must be in separate categories.
* <p>
* For example, to monitor image uploads to a web server with a separate thread
* for each upload, an event called File Upload starts when the user uploads a
* file and ends when the upload is complete. For advanced diagnostics about
* image uploads, more detailed events are created (for example, Image Read,
* Image Resize, and Image Write). During these detailed events. other low
* level-events could occur (for example, Socket Read and File Write).
* <p>
* The following example shows a visualization that avoids overlaps:
*
* <pre>
* -------------------------------------------------------------------
* | File Upload |
* ------------------------------------------------------------------
* | Image Read | Image Resize | Image Write |
* ------------------------------------------------------------------
* | Socket Read | Socket Read | | File Write |
* -------------------------------------------------------------------
* </pre>
*
* The example can be achieved by using the following categories:
*
* <table class="striped">
* <caption>Recording options and their purpose.</caption> <thead>
* <tr>
* <th scope="col">Event Name</th>
* <th scope="col">Annotation</th>
* </tr>
* </thead> <tbody>
* <tr>
* <th scope="row">File Upload</th>
* <td><code>@Category("Upload")</code></td>
* </tr>
* <tr>
* <th scope="row">Image Read</th>
* <td><code>@Category({"Upload", "Image Upload"})</code></td>
* </tr>
* <tr>
* <th scope="row">Image Resize</th>
* <td><code>@Category({"Upload", "Image Upload"})</code></td>
* </tr>
* <tr>
* <th scope="row">Image Write</th>
* <td><code>@Category({"Upload", "Image Upload"})</code></td>
* </tr>
* <tr>
* <th scope="row">Socket Read</th>
* <td><code>@Category("Java Application")</code></td>
* </tr>
* <tr>
* <th scope="row">File Write</th>
* <td><code>@Category("Java Application")</code></td>
* </tr>
* </tbody>
* </table>
* <p>
* The File Upload, Image Read, and Socket Read events happen concurrently (in
* the same thread), but the events are in different categories so they do not
* overlap in the visualization.
* <p>
* The following examples shows how the category is used to determine how events
* are visualized in a tree:
*
* <pre>
* |- Java Application
* | |- Socket Read
* | |- File Write
* |- Upload
* |- File Upload
* |- Image Upload
* |- Image Read
* |- Image Resize
* |- File Write
* </pre>
*
* @since 8
*/
@MetadataDefinition
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Category {
/**
* Returns the category names for this annotation, starting with the root.
*
* @return the category names
*/
String[] value();
}

View File

@@ -0,0 +1,197 @@
/*
* 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;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jdk.jfr.internal.JVMSupport;
import jdk.jfr.internal.jfc.JFC;
/**
* A collection of settings and metadata describing the configuration.
*
* @since 8
*/
public final class Configuration {
private final Map<String, String> settings;
private final String label;
private final String description;
private final String provider;
private final String contents;
private final String name;
// package private
Configuration(String name, String label, String description, String provider, Map<String, String> settings, String contents) {
this.name = name;
this.label = label;
this.description = description;
this.provider = provider;
this.settings = settings;
this.contents = contents;
}
/**
* Returns the settings that specifies how a recording is configured.
* <p>
* Modifying the returned {@code Map} object doesn't change the
* configuration.
*
* @return settings, not {@code null}
*/
public Map<String, String> getSettings() {
return new LinkedHashMap<String, String>(settings);
}
/**
* Returns an identifying name (for example, {@code "default" or "profile")}.
*
* @return the name, or {@code null} if it doesn't exist
*/
public String getName() {
return this.name;
}
/**
* Returns a human-readable name (for example, {@code "Continuous" or "Profiling"}}.
*
* @return the label, or {@code null} if it doesn't exist
*/
public String getLabel() {
return this.label;
}
/**
* Returns a short sentence that describes the configuration (for example
* {@code "Low
* overhead configuration safe for continuous use in production
* environments"})
*
* @return the description, or {@code null} if it doesn't exist
*/
public String getDescription() {
return description;
}
/**
* Returns who created the configuration (for example {@code "OpenJDK"}).
*
* @return the provider, or {@code null} if it doesn't exist
*/
public String getProvider() {
return provider;
}
/**
* Returns a textual representation of the configuration (for example, the
* contents of a JFC file).
*
* @return contents, or {@code null} if it doesn't exist
*
* @see Configuration#getContents()
*/
public String getContents() {
return contents;
}
/**
* Reads a configuration from a file.
*
* @param path the file that contains the configuration, not {@code null}
* @return the read {@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} method denies read access to the file.
*
* @see java.io.File#getPath()
* @see java.lang.SecurityManager#checkRead(java.lang.String)
*/
public static Configuration create(Path path) throws IOException, ParseException {
Objects.requireNonNull(path);
JVMSupport.ensureWithIOException();
try (Reader reader = Files.newBufferedReader(path)) {
return JFC.create(JFC.nameFromPath(path), reader);
}
}
/**
* Reads a configuration from a character stream.
*
* @param reader a {@code Reader} that provides the configuration contents, not
* {@code null}
* @return a configuration, not {@code null}
* @throws IOException if an I/O error occurs while trying to read contents
* from the {@code Reader}
* @throws ParseException if the file can't be parsed
*/
public static Configuration create(Reader reader) throws IOException, ParseException {
Objects.requireNonNull(reader);
JVMSupport.ensureWithIOException();
return JFC.create(null, reader);
}
/**
* Returns a predefined configuration.
* <p>
* See {@link Configuration#getConfigurations()} for available configuration
* names.
*
* @param name the name of the configuration (for example, {@code "default"} or
* {@code "profile"})
* @return a configuration, not {@code null}
*
* @throws IOException if a configuration with the given name does not
* exist, or if an I/O error occurs while reading the
* configuration file
* @throws ParseException if the configuration file can't be parsed
*/
public static Configuration getConfiguration(String name) throws IOException, ParseException {
JVMSupport.ensureWithIOException();
return JFC.getPredefined(name);
}
/**
* Returns an immutable list of predefined configurations for this Java Virtual Machine (JVM).
*
* @return the list of predefined configurations, not {@code null}
*/
public static List<Configuration> getConfigurations() {
if (JVMSupport.isNotAvailable()) {
return new ArrayList<>();
}
return Collections.unmodifiableList(JFC.getConfigurations());
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Meta annotation, specifies that an annotation represents a content type, such
* as a time span or a frequency.
*
* @since 8
*/
@MetadataDefinition
@Label("Content Type")
@Description("Semantic meaning of a value")
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentType {
}

View File

@@ -0,0 +1,60 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that a value represents an amount of data (for example, bytes).
*
* @since 8
*/
@MetadataDefinition
@ContentType
@Label("Data Amount")
@Description("Amount of data")
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
public @interface DataAmount {
/**
* Unit for bits
*/
public static final String BITS = "BITS";
/**
* Unit for bytes
*/
public static final String BYTES = "BYTES";
/**
* Returns the unit for the data amount, by default bytes.
*
* @return the data amount unit, default {@code BYTES}, not {@code null}
*/
String value() default BYTES;
}

View File

@@ -0,0 +1,52 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that describes an element by using a sentence or two.
* <p>
* Use sentence-style capitalization, capitalize the first letter of the first
* word, and any proper names such as the word Java. If the description is one
* sentence, a period should not be included.
*
* @since 8
*/
@MetadataDefinition
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Description {
/**
* Returns a sentence or two that describes the annotated element.
*
* @return a description, not {@code null}
*/
String value();
}

View File

@@ -0,0 +1,58 @@
/*
* 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;
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;
/**
* Event annotation, determines if an event should be enabled by default.
* <p>
* If an event doesn't have the annotation, then by default the event is enabled.
*
* @since 8
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@MetadataDefinition
public @interface Enabled {
/**
* Setting name {@code "enabled"}, signifies that the event should be
* recorded.
*/
public final static String NAME = "enabled";
/**
* Returns {@code true} if by default the event should be enabled, {@code false} otherwise.
*
* @return {@code true} if by default the event should be enabled by default, {@code false} otherwise
*/
boolean value() default true;
}

View File

@@ -0,0 +1,171 @@
/*
* 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;
/**
* Base class for events, to be subclassed in order to define events and their
* fields.
* <p>
* The following example shows how to implement an {@code Event} class.
*
* <pre>
* <code>
* import jdk.jfr.Event;
* import jdk.jfr.Description;
* import jdk.jfr.Label;
*
* public class Example {
*
* &#064;Label("Hello World")
* &#064;Description("Helps programmer getting started")
* static class HelloWorld extends Event {
* &#064;Label("Message")
* String message;
* }
*
* public static void main(String... args) {
* HelloWorld event = new HelloWorld();
* event.message = "hello, world!";
* event.commit();
* }
* }
* </code>
* </pre>
* <p>
* After an event is allocated and its field members are populated, it can be
* written to the Flight Recorder system by using the {@code #commit()} method.
* <p>
* By default, an event is enabled. To disable an event annotate the
* {@link Event} class with {@code @Enabled(false)}.
* <p>
* Supported field types are the Java primitives: {@code boolean}, {@code char},
* {@code byte}, {@code short}, {@code int}, {@code long}, {@code float}, and
* {@code double}. Supported reference types are: {@code String}, {@code Thread}
* and {@code Class}. Arrays, enums, and other reference types are silently
* ignored and not included. Fields that are of the supported types can be
* excluded by using the transient modifier. Static fields, even of the
* supported types, are not included.
* <p>
* Tools can visualize data in a meaningful way when annotations are used (for
* example, {@code Label}, {@code Description}, and {@code Timespan}).
* Annotations applied to an {@link Event} class or its fields are included if
* they are present (indirectly, directly, or associated), have the
* {@code MetadataDefinition} annotation, and they do not contain enums, arrays,
* or classes.
* <p>
* Gathering data to store in an event can be expensive. The
* {@link Event#shouldCommit()} method can be used to verify whether an event
* instance would actually be written to the system when the
* {@code Event#commit()commit} method is invoked. If
* {@link Event#shouldCommit()} returns false, then those operations can be
* avoided.
*
* @since 8
*/
@Enabled(true)
@StackTrace(true)
@Registered(true)
abstract public class Event {
/**
* Sole constructor, for invocation by subclass constructors, typically
* implicit.
*/
protected Event() {
}
/**
* Starts the timing of this event.
*/
final public void begin() {
}
/**
* Ends the timing of this event.
*
* The {@code end} method must be invoked after the {@code begin} method.
*/
final public void end() {
}
/**
* Writes the field values, time stamp, and event duration to the Flight
* Recorder system.
* <p>
* If the event starts with an invocation of the {@code begin} method, but does
* not end with an explicit invocation of the {@code end} method, then the event
* ends when the {@code commit} method is invoked.
*/
final public void commit() {
}
/**
* Returns {@code true} if at least one recording is running, and the
* enabled setting for this event is set to {@code true}, otherwise
* {@code false} is returned.
*
* @return {@code true} if event is enabled, {@code false} otherwise
*/
final public boolean isEnabled() {
return false;
}
/**
* Returns {@code true} if the enabled setting for this event is set to
* {@code true} and if the duration is within the threshold for the event,
* {@code false} otherwise. The threshold is the minimum threshold for all
* running recordings.
*
* @return {@code true} if the event can be written to the Flight Recorder
* system, {@code false} otherwise
*/
final public boolean shouldCommit() {
return false;
}
/**
* Sets a field value.
* <p>
* Applicable only if the event is dynamically defined using the
* {@code EventFactory} class.
* <p>
* The supplied {@code index} corresponds to the index of the
* {@link ValueDescriptor} object passed to the factory method of the
* {@code EventFactory} class.
*
* @param index the index of the field that is passed to
* {@code EventFactory#create(String, java.util.List, java.util.List)}
* @param value value to set, can be {@code null}
* @throws UnsupportedOperationException if it's not a dynamically generated
* event
* @throws IndexOutOfBoundsException if {@code index} is less than {@code 0} or
* greater than or equal to the number of fields specified for the event
*
* @see EventType#getFields()
* @see EventFactory
*/
final public void set(int index, Object value) {
}
}

View File

@@ -0,0 +1,251 @@
/*
* 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;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import jdk.jfr.internal.EventClassBuilder;
import jdk.jfr.internal.JVMSupport;
import jdk.jfr.internal.MetadataRepository;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
/**
* Class for defining an event at runtime.
* <p>
* It's highly recommended that the event is defined at compile time, if the
* field layout is known, so the Java Virtual Machine (JVM) can optimize the
* code, possibly remove all instrumentation if Flight Recorder is inactive or
* if the enabled setting for this event is set to {@code false}.
* <p>
* To define an event at compile time, see {@link Event}.
* <p>
* The following example shows how to implement a dynamic {@code Event} class.
*
* <pre>
* {@code
* List<ValueDescriptor> fields = new ArrayList<>();
* List<AnnotationElement> messageAnnotations = Collections.singletonList(new AnnotationElement(Label.class, "Message"));
* fields.add(new ValueDescriptor(String.class, "message", messageAnnotations));
* List<AnnotationElement> numberAnnotations = Collections.singletonList(new AnnotationElement(Label.class, "Number"));
* fields.add(new ValueDescriptor(int.class, "number", numberAnnotations));
*
* String[] category = { "Example", "Getting Started" };
* List<AnnotationElement> eventAnnotations = new ArrayList<>();
* eventAnnotations.add(new AnnotationElement(Name.class, "com.example.HelloWorld"));
* eventAnnotations.add(new AnnotationElement(Label.class, "Hello World"));
* eventAnnotations.add(new AnnotationElement(Description.class, "Helps programmer getting started"));
* eventAnnotations.add(new AnnotationElement(Category.class, category));
*
* EventFactory f = EventFactory.create(eventAnnotations, fields);
*
* Event event = f.newEvent();
* event.set(0, "hello, world!");
* event.set(1, 4711);
* event.commit();
* }
* </pre>
*
* @since 8
*/
public final class EventFactory {
private static final long REGISTERED_ID = Type.getTypeId(Registered.class);
private final Class<? extends Event> eventClass;
private final MethodHandle constructorHandle;
private final List<AnnotationElement> sanitizedAnnotation;
private final List<ValueDescriptor> sanitizedFields;
private EventFactory(Class<? extends Event> eventClass, List<AnnotationElement> sanitizedAnnotation, List<ValueDescriptor> sanitizedFields) throws IllegalAccessException, NoSuchMethodException, SecurityException {
this.constructorHandle = MethodHandles.lookup().unreflectConstructor(eventClass.getConstructor());
this.eventClass = eventClass;
this.sanitizedAnnotation = sanitizedAnnotation;
this.sanitizedFields = sanitizedFields;
}
/**
* Creates an {@code EventFactory} object.
* <p>
* The order of the value descriptors specifies the index to use when setting
* event values.
*
* @param annotationElements list of annotation elements that describes the
* annotations on the event, not {@code null}
*
* @param fields list of descriptors that describes the fields of the event, not
* {@code null}
*
* @return event factory, not {@code null}
*
* @throws IllegalArgumentException if the input is not valid. For example,
* input might not be valid if the field type or name is not valid in
* the Java language or an annotation element references a type that
* can't be found.
*
* @throws SecurityException if a security manager exists and the caller does
* not have {@code FlightRecorderPermission("registerEvent")}
*
* @see Event#set(int, Object)
*/
public static EventFactory create(List<AnnotationElement> annotationElements, List<ValueDescriptor> fields) {
Objects.requireNonNull(fields);
Objects.requireNonNull(annotationElements);
JVMSupport.ensureWithInternalError();
Utils.checkRegisterPermission();
List<AnnotationElement> sanitizedAnnotation = Utils.sanitizeNullFreeList(annotationElements, AnnotationElement.class);
List<ValueDescriptor> sanitizedFields = Utils.sanitizeNullFreeList(fields, ValueDescriptor.class);
Set<String> nameSet = new HashSet<>();
for (ValueDescriptor v : sanitizedFields) {
String name = v.getName();
if (v.isArray()) {
throw new IllegalArgumentException("Array types are not allowed for fields");
}
if (!Type.isValidJavaFieldType(v.getTypeName())) {
throw new IllegalArgumentException(v.getTypeName() + " is not a valid type for an event field");
}
if (!Type.isValidJavaIdentifier(v.getName())) {
throw new IllegalArgumentException(name + " is not a valid name for an event field");
}
if (nameSet.contains(name)) {
throw new IllegalArgumentException("Name of fields must be unique. Found two instances of " + name);
}
nameSet.add(name);
}
// Prevent event from being registered in <clinit>
// and only use annotations that can be resolved (those in boot class loader)
boolean needRegister = true;
List<AnnotationElement> bootAnnotations = new ArrayList<>();
for (AnnotationElement ae : sanitizedAnnotation) {
long id = ae.getTypeId();
if (ae.isInBoot()) {
if (id == REGISTERED_ID) {
if (Boolean.FALSE.equals(ae.getValue("value"))) {
needRegister = false;
}
} else {
bootAnnotations.add(ae);
}
}
}
bootAnnotations.add(new AnnotationElement(Registered.class, false));
EventClassBuilder ecb = new EventClassBuilder(bootAnnotations, sanitizedFields);
Class<? extends Event> eventClass = ecb.build();
if (needRegister) {
MetadataRepository.getInstance().register(eventClass, sanitizedAnnotation, sanitizedFields);
}
try {
return new EventFactory(eventClass, sanitizedAnnotation, sanitizedFields);
} catch (IllegalAccessException e) {
throw new IllegalAccessError("Could not accees constructor of generated event handler, " + e.getMessage());
} catch (NoSuchMethodException e) {
throw new InternalError("Could not find constructor in generated event handler, " + e.getMessage());
}
}
/**
* Instantiates an event, so it can be populated with data and written to the
* Flight Recorder system.
* <p>
* Use the {@link Event#set(int, Object)} method to set a value.
*
* @return an event instance, not {@code null}
*/
public Event newEvent() {
try {
return (Event) constructorHandle.invoke();
} catch (Throwable e) {
throw new InstantiationError("Could not instantaite dynamically generated event class " + eventClass.getName() + ". " + e.getMessage());
}
}
/**
* Returns the event type that is associated with this event factory.
*
* @return event type that is associated with this event factory, not
* {@code null}
*
* @throws java.lang.IllegalStateException if the event factory is created with
* the {@code Registered(false)} annotation and the event class is not
* manually registered before the invocation of this method
*/
public EventType getEventType() {
return EventType.getEventType(eventClass);
}
/**
* Registers an unregistered event.
* <p>
* By default, the event class associated with this event factory is registered
* when the event factory is created, unless the event has the
* {@link Registered} annotation.
* <p>
* A registered event class can write data to Flight Recorder and event metadata
* can be obtained by invoking {@link FlightRecorder#getEventTypes()}.
* <p>
* If the event class associated with this event factory is already registered,
* the call to this method is ignored.
*
* @throws SecurityException if a security manager exists and the caller
* does not have {@code FlightRecorderPermission("registerEvent")}
* @see Registered
* @see FlightRecorder#register(Class)
*/
public void register() {
MetadataRepository.getInstance().register(eventClass, sanitizedAnnotation, sanitizedFields);
}
/**
* Unregisters the event that is associated with this event factory.
* <p>
* A unregistered event class can't write data to Flight Recorder and event
* metadata can't be obtained by invoking
* {@link FlightRecorder#getEventTypes()}.
* <p>
* If the event class associated with this event factory is not already
* registered, the call to this method is ignored.
*
* @throws SecurityException if a security manager exists and the caller does
* not have {@code FlightRecorderPermission("registerEvent")}
* @see Registered
* @see FlightRecorder#unregister(Class)
*/
public void unregister() {
MetadataRepository.getInstance().unregister(eventClass);
}
}

View File

@@ -0,0 +1,141 @@
/*
* 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;
import java.time.Duration;
import java.util.Map;
/**
* Convenience class for applying event settings to a recording.
* <p>
* An {@code EventSettings} object for a recording can be obtained by invoking
* the {@link Recording#enable(String)} method which is configured using method
* chaining.
* <p>
* The following example shows how to use the {@code EventSettings} class.
* <pre>
* {@code
* Recording r = new Recording();
* r.enable("jdk.CPULoad")
* .withPeriod(Duration.ofSeconds(1));
* r.enable("jdk.FileWrite")
* .withoutStackTrace()
* .withThreshold(Duration.ofNanos(10));
* r.start();
* Thread.sleep(10_000);
* r.stop();
* r.dump(Files.createTempFile("recording", ".jfr"));
*
* }
* </pre>
* @since 8
*/
public abstract class EventSettings {
// package private
EventSettings() {
}
/**
* Enables stack traces for the event that is associated with this event setting.
* <p>
* Equivalent to invoking the {@code with("stackTrace", "true")} method.
*
* @return event settings object for further configuration, not {@code null}
*/
final public EventSettings withStackTrace() {
return with(StackTrace.NAME, "true");
}
/**
* Disables stack traces for the event that is associated with this event setting.
* <p>
* Equivalent to invoking the {@code with("stackTrace", "false")} method.
*
* @return event settings object for further configuration, not {@code null}
*/
final public EventSettings withoutStackTrace() {
return with(StackTrace.NAME, "false");
}
/**
* Specifies that a threshold is not used.
* <p>
* This is a convenience method, equivalent to invoking the
* {@code with("threshold", "0 s")} method.
*
* @return event settings object for further configuration, not {@code null}
*/
final public EventSettings withoutThreshold() {
return with(Threshold.NAME, "0 s");
}
/**
* Sets the interval for the event that is associated with this event setting.
*
* @param duration the duration, not {@code null}
*
* @return event settings object for further configuration, not {@code null}
*/
final public EventSettings withPeriod(Duration duration) {
return with(Period.NAME, duration.toNanos() + " ns");
}
/**
* Sets the threshold for the event that is associated with this event setting.
*
* @param duration the duration, or {@code null} if no duration is used
*
* @return event settings object for further configuration, not {@code null}
*/
final public EventSettings withThreshold(Duration duration) {
if (duration == null) {
return with(Threshold.NAME, "0 ns");
} else {
return with(Threshold.NAME, duration.toNanos() + " ns");
}
}
/**
* Sets a setting value for the event that is associated with this event setting.
*
* @param name the name of the setting (for example, {@code "threshold"})
*
* @param value the value to set (for example {@code "20 ms"} not
* {@code null})
*
* @return event settings object for further configuration, not {@code null}
*/
abstract public EventSettings with(String name, String value);
/**
* Creates a settings {@code Map} for the event that is associated with this
* event setting.
*
* @return a settings {@code Map}, not {@code null}
*/
abstract Map<String, String> toMap();
}

View File

@@ -0,0 +1,237 @@
/*
* 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;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jdk.jfr.internal.JVMSupport;
import jdk.jfr.internal.MetadataRepository;
import jdk.jfr.internal.PlatformEventType;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
/**
* Describes an event, its fields, settings and annotations.
*
* @since 8
*/
public final class EventType {
private final PlatformEventType platformEventType;
private final List<String> UNCATEGORIZED = Collections.singletonList("Uncategorized");
private Map<String, ValueDescriptor> cache; // create lazy to avoid memory overhead
// helper constructor
EventType(PlatformEventType platformEventType) {
this.platformEventType = platformEventType;
}
/**
* Returns an immutable list of descriptors that describe the event fields of
* this event type.
*
* @return the list of field descriptors, not {@code null}
*/
public List<ValueDescriptor> getFields() {
return platformEventType.getFields();
}
/**
* Returns the field with the specified name, or {@code null} if it doesn't
* exist.
*
* @return a value descriptor that describes the field, or <code>null</code> if
* the field with the specified name doesn't exist
*
* @return a value descriptor, or <code>null</code> if it doesn't exist
*/
public ValueDescriptor getField(String name) {
Objects.requireNonNull(name);
if (cache == null) {
List<ValueDescriptor> fields = getFields();
Map<String, ValueDescriptor> newCache = new LinkedHashMap<String, ValueDescriptor>(fields.size());
for (ValueDescriptor v :fields) {
newCache.put(v.getName(), v);
}
cache = newCache;
}
return cache.get(name);
}
/**
* Returns an identifier for the event (for example,
* {@code "jdk.CPULoad"}).
* <p>
* The identifier is the fully qualified name of the event class, if not set using
* the {@link Name} annotation.
*
* @return the name, not {@code null}
*
* @see Name
*/
public String getName() {
return platformEventType.getName();
}
/**
* Returns a human-readable name (for example, {@code "CPU Load"}).
* <p>
* The label of an event class can be set with {@link Label}.
*
* @return the label, or {@code null} if a label is not set
*
* @see Label
*/
public String getLabel() {
return platformEventType.getLabel();
}
/**
* Returns a unique ID for this event type in the Java Virtual Machine (JVM).
*
* @return the ID that is used in the JVM
*/
public long getId() {
return platformEventType.getId();
}
/**
* Returns an immutable list of annotation elements for this event type.
*
* @return an immutable list of annotations or an empty list if no
* annotations exists, not {@code null}
*/
public List<AnnotationElement> getAnnotationElements() {
return platformEventType.getAnnotationElements();
}
/**
* Returns {@code true} if the event is enabled and at least one recording is
* running, {@code false} otherwise.
* <p>
* By default, the event is enabled. The event can be enabled or disabled by
* setting the enabled setting to {@code true} or {@code false}, programmatically or by using a
* configuration file. The event can also be disabled by annotating event with
* the {@code @Enabled(false)} annotation.
*
* @return true if event is enabled, false otherwise
*
* @see Enabled
* @see Recording#enable(Class)
*/
public boolean isEnabled() {
return platformEventType.isEnabled();
}
/**
* Returns a short sentence that describes the event class.
* <p>
* The description of an event class can be set with {@link Description}.
*
* @return the description, or {@code null} if no description exists
*
* @see Description
*/
public String getDescription() {
return platformEventType.getDescription();
}
/**
* Returns the first annotation for the specified type if an annotation
* element with the same name is directly present, otherwise {@code null}.
*
* @param <A> the type of the annotation to query for and return if present
* @param annotationClass the {@code Class} object that corresponds to the
* annotation type, not {@code null}
* @return this element's annotation for the specified annotation type if
* directly present, else {@code null}
*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return platformEventType.getAnnotation(annotationClass);
}
/**
* Returns the event type for an event class, or {@code null} if it doesn't
* exist.
*
* @param eventClass the event class, not {@code null}
* @return the event class, or null if class doesn't exist
*
* @throws IllegalArgumentException if {@code eventClass} is an abstract class
*
* @throws IllegalStateException if the class is annotated with
* {@code Registered(false)}, but not manually registered
*/
public static EventType getEventType(Class<? extends Event> eventClass) {
Objects.requireNonNull(eventClass);
Utils.ensureValidEventSubclass(eventClass);
JVMSupport.ensureWithInternalError();
return MetadataRepository.getInstance().getEventType(eventClass);
}
/**
* Returns an immutable list of the setting descriptors that describe the available
* event settings for this event type.
*
* @return the list of setting descriptors for this event type, not
* {@code null}
*/
public List<SettingDescriptor> getSettingDescriptors() {
return Collections.unmodifiableList(platformEventType.getSettings());
}
/**
* Returns the list of human-readable names that makes up the categories for
* this event type (for example, {@code "Java Application"}, {@code "Statistics"}).
*
* @return an immutable list of category names, or a list with the name
* {@code "Uncategorized"} if no category is set
*
* @see Category
*/
public List<String> getCategoryNames() {
Category c = platformEventType.getAnnotation(Category.class);
if (c == null) {
return UNCATEGORIZED;
}
return Collections.unmodifiableList(Arrays.asList(c.value()));
}
// package private
Type getType() {
return platformEventType;
}
// package private
PlatformEventType getPlatformEventType() {
return platformEventType;
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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;
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;
/**
* Annotation that specifies that an element is experimental and may change
* without notice.
* <p>
* Clients that visualize Flight Recorder events should <em>not</em> show the
* events or fields annotated with the {@code Experimental} annotation by
* default. This annotation allows event producers the freedom to try out new
* events without committing to them.
* <p>
* Clients may provide a check box (for example, in a preference page) where a
* user can opt-in to display experimental data. If the user decide to do so,
* the user interface should mark experimental events or fields so users can
* distinguish them from non-experimental events.
* <p>
* This annotation is inherited.
*
* @since 8
*/
@MetadataDefinition
@Label("Experimental")
@Description("Element is not to be shown to a user by default")
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
public @interface Experimental {
}

View File

@@ -0,0 +1,353 @@
/*
* 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;
import static jdk.jfr.internal.LogLevel.DEBUG;
import static jdk.jfr.internal.LogLevel.INFO;
import static jdk.jfr.internal.LogTag.JFR;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.JVMSupport;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.MetadataRepository;
import jdk.jfr.internal.Options;
import jdk.jfr.internal.PlatformRecorder;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.Repository;
import jdk.jfr.internal.RequestEngine;
import jdk.jfr.internal.Utils;
/**
* Class for accessing, controlling, and managing Flight Recorder.
* <p>
* This class provides the methods necessary for creating, starting, stopping,
* and destroying recordings.
*
* @since 8
*/
public final class FlightRecorder {
private static volatile FlightRecorder platformRecorder;
private static volatile boolean initialized;
private final PlatformRecorder internal;
private FlightRecorder(PlatformRecorder internal) {
this.internal = internal;
}
/**
* Returns an immutable list of the available recordings.
* <p>
* A recording becomes available when it is created. It becomes unavailable when it
* is in the {@code CLOSED} state, typically after a call to
* {@link Recording#close()}.
*
* @return a list of recordings, not {@code null}
*/
public List<Recording> getRecordings() {
List<Recording> recs = new ArrayList<>();
for (PlatformRecording r : internal.getRecordings()) {
recs.add(r.getRecording());
}
return Collections.unmodifiableList(recs);
}
/**
* Creates a snapshot of all available recorded data.
* <p>
* A snapshot is a synthesized recording in a {@code STOPPPED} state. If no data is
* available, a recording with size {@code 0} is returned.
* <p>
* A snapshot provides stable access to data for later operations (for example,
* operations to change the interval or to reduce the data size).
* <p>
* The following example shows how to create a snapshot and write a subset of the data to a file.
*
* <pre>
* <code>
* try (Recording snapshot = FlightRecorder.getFlightRecorder().takeSnapshot()) {
* if (snapshot.getSize() &gt; 0) {
* snapshot.setMaxSize(100_000_000);
* snapshot.setMaxAge(Duration.ofMinutes(5));
* snapshot.dump(Paths.get("snapshot.jfr"));
* }
* }
* </code>
* </pre>
*
* The caller must close the recording when access to the data is no longer
* needed.
*
* @return a snapshot of all available recording data, not {@code null}
*/
public Recording takeSnapshot() {
Recording snapshot = new Recording();
snapshot.setName("Snapshot");
internal.fillWithRecordedData(snapshot.getInternal(), null);
return snapshot;
}
/**
* Registers an event class.
* <p>
* If the event class is already registered, then the invocation of this method is
* ignored.
*
* @param eventClass the event class to register, not {@code null}
*
* @throws IllegalArgumentException if class is abstract or not a subclass
* of {@link Event}
* @throws SecurityException if a security manager exists and the caller
* does not have {@code FlightRecorderPermission("registerEvent")}
*/
public static void register(Class<? extends Event> eventClass) {
Objects.requireNonNull(eventClass);
if (JVMSupport.isNotAvailable()) {
return;
}
Utils.ensureValidEventSubclass(eventClass);
MetadataRepository.getInstance().register(eventClass);
}
/**
* Unregisters an event class.
* <p>
* If the event class is not registered, then the invocation of this method is
* ignored.
*
* @param eventClass the event class to unregistered, not {@code null}
* @throws IllegalArgumentException if a class is abstract or not a subclass
* of {@link Event}
*
* @throws SecurityException if a security manager exists and the caller
* does not have {@code FlightRecorderPermission("registerEvent")}
*/
public static void unregister(Class<? extends Event> eventClass) {
Objects.requireNonNull(eventClass);
if (JVMSupport.isNotAvailable()) {
return;
}
Utils.ensureValidEventSubclass(eventClass);
MetadataRepository.getInstance().unregister(eventClass);
}
/**
* Returns the Flight Recorder for the platform.
*
* @return a Flight Recorder instance, not {@code null}
*
* @throws IllegalStateException if Flight Recorder can't be created (for
* example, if the Java Virtual Machine (JVM) lacks Flight Recorder
* support, or if the file repository can't be created or accessed)
*
* @throws SecurityException if a security manager exists and the caller does
* not have {@code FlightRecorderPermission("accessFlightRecorder")}
*/
public static FlightRecorder getFlightRecorder() throws IllegalStateException, SecurityException {
synchronized (PlatformRecorder.class) {
Utils.checkAccessFlightRecorder();
JVMSupport.ensureWithIllegalStateException();
if (platformRecorder == null) {
try {
platformRecorder = new FlightRecorder(new PlatformRecorder());
} catch (IllegalStateException ise) {
throw ise;
} catch (Exception e) {
throw new IllegalStateException("Can't create Flight Recorder. " + e.getMessage(), e);
}
// Must be in synchronized block to prevent instance leaking out
// before initialization is done
initialized = true;
Logger.log(JFR, INFO, "Flight Recorder initialized");
Logger.log(JFR, DEBUG, "maxchunksize: " + Options.getMaxChunkSize()+ " bytes");
Logger.log(JFR, DEBUG, "memorysize: " + Options.getMemorySize()+ " bytes");
Logger.log(JFR, DEBUG, "globalbuffersize: " + Options.getGlobalBufferSize()+ " bytes");
Logger.log(JFR, DEBUG, "globalbuffercount: " + Options.getGlobalBufferCount());
Logger.log(JFR, DEBUG, "dumppath: " + Options.getDumpPath());
Logger.log(JFR, DEBUG, "samplethreads: " + Options.getSampleThreads());
Logger.log(JFR, DEBUG, "stackdepth: " + Options.getStackDepth());
Logger.log(JFR, DEBUG, "threadbuffersize: " + Options.getThreadBufferSize());
Logger.log(JFR, LogLevel.INFO, "Created repository " + Repository.getRepository().getRepositoryPath().toString());
PlatformRecorder.notifyRecorderInitialized(platformRecorder);
}
}
return platformRecorder;
}
/**
* Adds a hook for a periodic event.
* <p>
* The implementation of the hook should return as soon as possible, to
* avoid blocking other Flight Recorder operations. The hook should emit
* one or more events of the specified type. When a hook is added, the
* interval at which the call is invoked is configurable using the
* {@code "period"} setting.
*
* @param eventClass the class that the hook should run for, not {@code null}
* @param hook the hook, not {@code null}
* @throws IllegalArgumentException if a class is not a subclass of
* {@link Event}, is abstract, or the hook is already added
* @throws IllegalStateException if the event class has the
* {@code Registered(false)} annotation and is not registered manually
* @throws SecurityException if a security manager exists and the caller
* does not have {@code FlightRecorderPermission("registerEvent")}
*/
public static void addPeriodicEvent(Class<? extends Event> eventClass, Runnable hook) throws SecurityException {
Objects.requireNonNull(eventClass);
Objects.requireNonNull(hook);
if (JVMSupport.isNotAvailable()) {
return;
}
Utils.ensureValidEventSubclass(eventClass);
Utils.checkRegisterPermission();
AccessControlContext acc = AccessController.getContext();
RequestEngine.addHook(acc, EventType.getEventType(eventClass).getPlatformEventType(), hook);
}
/**
* Removes a hook for a periodic event.
*
* @param hook the hook to remove, not {@code null}
* @return {@code true} if hook is removed, {@code false} otherwise
* @throws SecurityException if a security manager exists and the caller
* does not have {@code FlightRecorderPermission("registerEvent")}
*/
public static boolean removePeriodicEvent(Runnable hook) throws SecurityException {
Objects.requireNonNull(hook);
Utils.checkRegisterPermission();
if (JVMSupport.isNotAvailable()) {
return false;
}
return RequestEngine.removeHook(hook);
}
/**
* Returns an immutable list that contains all currently registered events.
* <p>
* By default, events are registered when they are first used, typically
* when an event object is allocated. To ensure an event is visible early,
* registration can be triggered by invoking the
* {@link FlightRecorder#register(Class)} method.
*
* @return list of events, not {@code null}
*/
public List<EventType> getEventTypes() {
return Collections.unmodifiableList(MetadataRepository.getInstance().getRegisteredEventTypes());
}
/**
* Adds a recorder listener and captures the {@code AccessControlContext} to
* use when invoking the listener.
* <p>
* If Flight Recorder is already initialized when the listener is added, then the method
* {@link FlightRecorderListener#recorderInitialized(FlightRecorder)} method is
* invoked before returning from this method.
*
* @param changeListener the listener to add, not {@code null}
*
* @throws SecurityException if a security manager exists and the caller
* does not have
* {@code FlightRecorderPermission("accessFlightRecorder")}
*/
public static void addListener(FlightRecorderListener changeListener) {
Objects.requireNonNull(changeListener);
Utils.checkAccessFlightRecorder();
if (JVMSupport.isNotAvailable()) {
return;
}
PlatformRecorder.addListener(changeListener);
}
/**
* Removes a recorder listener.
* <p>
* If the same listener is added multiple times, only one instance is
* removed.
*
* @param changeListener listener to remove, not {@code null}
*
* @throws SecurityException if a security manager exists and the caller
* does not have
* {@code FlightRecorderPermission("accessFlightRecorder")}
*
* @return {@code true}, if the listener could be removed, {@code false}
* otherwise
*/
public static boolean removeListener(FlightRecorderListener changeListener) {
Objects.requireNonNull(changeListener);
Utils.checkAccessFlightRecorder();
if (JVMSupport.isNotAvailable()) {
return false;
}
return PlatformRecorder.removeListener(changeListener);
}
/**
* Returns {@code true} if the Java Virtual Machine (JVM) has Flight Recorder capabilities.
* <p>
* This method can quickly check whether Flight Recorder can be
* initialized, without actually doing the initialization work. The value may
* change during runtime and it is not safe to cache it.
*
* @return {@code true}, if Flight Recorder is available, {@code false}
* otherwise
*
* @see FlightRecorderListener for callback when Flight Recorder is
* initialized
*/
public static boolean isAvailable() {
if (JVMSupport.isNotAvailable()) {
return false;
}
return JVM.getJVM().isAvailable();
}
/**
* Returns {@code true} if Flight Recorder is initialized.
*
* @return {@code true}, if Flight Recorder is initialized,
* {@code false} otherwise
*
* @see FlightRecorderListener for callback when Flight Recorder is
* initialized
*/
public static boolean isInitialized() {
return initialized;
}
PlatformRecorder getInternal() {
return internal;
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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;
/**
* Callback interface to monitor Flight Recorder's life cycle.
*
* @since 8
*/
public interface FlightRecorderListener {
/**
* Receives notification when Flight Recorder is initialized.
* <p>
* This method is also be invoked when a listener is added to an already
* initialized Flight Recorder.
* <p>
* This method allows clients to implement their own initialization mechanism
* that is executed before a {@code FlightRecorder} instance is returned by
* {@code FlightRecorder#getFlightRecorder()}.
*
* @implNote This method should return as soon as possible, to avoid blocking
* initialization of Flight Recorder. To avoid deadlocks or unexpected
* behavior, this method should not call
* {@link FlightRecorder#getFlightRecorder()} or start new recordings.
*
* @implSpec The default implementation of this method is empty.
*
* @param recorder Flight Recorder instance, not {@code null}
*
* @see FlightRecorder#addListener(FlightRecorderListener)
*/
default void recorderInitialized(FlightRecorder recorder) {
}
/**
* Receives notification when the state of a recording changes.
* <p>
* Callback is invoked when a recording reaches the {@code RUNNING},
* {@code STOPPED} and {@code CLOSED} state.
*
* @implNote The implementation of this method should return as soon as possible
* to avoid blocking normal operation of Flight Recorder.
*
* @implSpec The default implementation of this method is empty.
*
* @param recording the recording where the state change occurred, not
* {@code null}
*
* @see FlightRecorder#addListener(FlightRecorderListener)
* @see RecordingState
*
*/
default void recordingStateChanged(Recording recording) {
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jdk.jfr.internal.PlatformEventType;
import jdk.jfr.internal.PlatformRecorder;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
/**
* Permission for controlling access to Flight Recorder.
* <p>
* The following table provides a summary of what the permission
* allows, and the risks of granting code the permission.
*
* <table class="striped">
* <caption style="display:none">Table shows permission target name,
* what the permission allows, and associated risks</caption>
* <thead>
* <tr>
* <th scope="col">Permission Target Name</th>
* <th scope="col">What the Permission Allows</th>
* <th scope="col">Risks of Allowing this Permission</th>
* </tr>
* </thead>
*
* <tbody>
* <tr>
* <th scope="row">{@code accessFlightRecorder}</th>
* <td>Ability to create a Flight Recorder instance, register callbacks to
* monitor the Flight Recorder life cycle, and control an existing instance
* of Flight Recorder, which can record and dump runtime information, such as
* stack traces, class names, and data in user defined events.</td>
* <td>A malicious user may be able to extract sensitive information that is stored in
* events and interrupt Flight Recorder by installing listeners or hooks that
* never finish.</td>
* </tr>
* <tr>
* <th scope="row">{@code registerEvent}</th>
* <td>Ability to register events, write data to the Flight Recorder buffers,
* and execute code in a callback function for periodic events.
*
* <td>A malicious user may be able to write sensitive information to Flight
* Recorder buffers.</td>
* </tr>
* </tbody>
* </table>
*
* <p>
* Typically, programmers do not create {@code FlightRecorderPermission} objects
* directly. Instead the objects are created by the security policy code that is based on
* reading the security policy file.
*
* @since 8
*
* @see java.security.BasicPermission
* @see java.security.Permission
* @see java.security.Permissions
* @see java.security.PermissionCollection
* @see java.lang.SecurityManager
*
*/
@SuppressWarnings("serial")
public final class FlightRecorderPermission extends java.security.BasicPermission {
// Purpose of InternalAccess is to give classes in jdk.jfr.internal
// access to package private methods in this package (jdk.jfr).
//
// The initialization could be done in any class in this package,
// but this one was chosen because it is light weight and
// lacks dependencies on other public classes.
static {
PrivateAccess.setPrivateAccess(new InternalAccess());
}
private final static class InternalAccess extends PrivateAccess {
@Override
public Type getType(Object o) {
if (o instanceof AnnotationElement) {
return ((AnnotationElement) o).getType();
}
if (o instanceof EventType) {
return ((EventType) o).getType();
}
if (o instanceof ValueDescriptor) {
return ((ValueDescriptor) o).getType();
}
if (o instanceof SettingDescriptor) {
return ((SettingDescriptor) o).getType();
}
throw new Error("Unknown type " + o.getClass());
}
@Override
public Configuration newConfiguration(String name, String label, String description, String provider, Map<String, String> settings, String contents) {
return new Configuration(name, label, description, provider, settings, contents);
}
@Override
public EventType newEventType(PlatformEventType platformEventType) {
return new EventType(platformEventType);
}
@Override
public AnnotationElement newAnnotation(Type annotationType, List<Object> values, boolean boot) {
return new AnnotationElement(annotationType, values, boot);
}
@Override
public ValueDescriptor newValueDescriptor(String name, Type fieldType, List<AnnotationElement> annos, int dimension, boolean constantPool, String fieldName) {
return new ValueDescriptor(fieldType, name, annos, dimension, constantPool, fieldName);
}
@Override
public PlatformRecording getPlatformRecording(Recording r) {
return r.getInternal();
}
@Override
public PlatformEventType getPlatformEventType(EventType eventType) {
return eventType.getPlatformEventType();
}
@Override
public boolean isConstantPool(ValueDescriptor v) {
return v.isConstantPool();
}
@Override
public void setAnnotations(ValueDescriptor v, List<AnnotationElement> a) {
v.setAnnotations(a);
}
@Override
public void setAnnotations(SettingDescriptor s, List<AnnotationElement> a) {
s.setAnnotations(a);
}
@Override
public String getFieldName(ValueDescriptor v) {
return v.getJavaFieldName();
}
@Override
public ValueDescriptor newValueDescriptor(Class<?> type, String name) {
return new ValueDescriptor(type, name, Collections.emptyList(), true);
}
@Override
public SettingDescriptor newSettingDescriptor(Type type, String name, String defaultValue, List<AnnotationElement> annotations) {
return new SettingDescriptor(type, name, defaultValue, annotations);
}
@Override
public boolean isUnsigned(ValueDescriptor v) {
return v.isUnsigned();
}
@Override
public PlatformRecorder getPlatformRecorder() {
return FlightRecorder.getFlightRecorder().getInternal();
}
}
/**
* Constructs a {@code FlightRecorderPermission} with the specified name.
*
* @param name the permission name, must be either
* {@code "accessFlightRecorder"} or {@code "registerEvent"}, not
* {@code null}
*
* @throws IllegalArgumentException if {@code name} is empty or not valid
*/
public FlightRecorderPermission(String name) {
super(Objects.requireNonNull(name));
if (!name.equals(Utils.ACCESS_FLIGHT_RECORDER) && !name.equals(Utils.REGISTER_EVENT)) {
throw new IllegalArgumentException("name: " + name);
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that the value is a frequency, measured in Hz.
*
* @since 8
*/
@MetadataDefinition
@ContentType
@Label("Frequency")
@Description("Measure of how often something occurs, in Hertz")
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface Frequency {
}

View File

@@ -0,0 +1,55 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that sets a human-readable name for an element (for example,
* {@code "Maximum Throughput"}).
* <p>
* Use headline-style capitalization, capitalize the first and last words, and
* all nouns, pronouns, adjectives, verbs and adverbs. Do not include ending
* punctuation.
* <p>
* The label should not be used as an identifier, see {@link Name}.
*
* @since 8
*/
@MetadataDefinition
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Label {
/**
* Returns a human-readable name for the annotated element.
*
* @return a human-readable name, not {@code null}
*/
String value();
}

View File

@@ -0,0 +1,45 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that the value is a memory address.
*
* @since 8
*/
@MetadataDefinition
@ContentType
@Label("Memory Address")
@Description("Represents a physical memory address")
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD })
public @interface MemoryAddress {
}

View File

@@ -0,0 +1,81 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Meta annotation for defining new types of event metadata.
* <p>
* In the following example, a transaction event is defined with two
* user-defined annotations, {@code @Severity} and {@code @TransactionId}.
*
* <pre>
* <code>
*{@literal @}MetadataDefinition
*{@literal @}Label("Severity")
*{@literal @}Description("Value between 0 and 100 that indicates severity. 100 is most severe.")
*{@literal @}Retention(RetentionPolicy.RUNTIME)
*{@literal @}Target({ ElementType.TYPE })
* public {@literal @}interface {@literal @}Severity {
* int value() default 50;
* }
*
*{@literal @}MetadataDefinition
*{@literal @}Label("Transaction Id")
*{@literal @}Relational
*{@literal @}Retention(RetentionPolicy.RUNTIME)
*{@literal @}Target({ ElementType.FIELD })
* public {@literal @}interface {@literal @}Severity {
* }
*
*{@literal @}Severity(80)
*{@literal @}Label("Transaction Blocked");
* class TransactionBlocked extends Event {
* {@literal @}TransactionId
* {@literal @}Label("Transaction");
* long transactionId;
*
* {@literal @}TransactionId
* {@literal @}Label("Transaction Blocker");
* long transactionId;
* }
*
* </code>
* </pre>
*
* Adding {@code @MetadataDefinition} to the declaration of {@code @Severity} and {@code @TransactionId}
* ensures the information is saved by Flight Recorder.
*
* @since 8
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface MetadataDefinition {
}

View File

@@ -0,0 +1,53 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that overrides the default name for an element (for example, when
* the default package for an event is not appropriate).
* <p>
* The name must be a valid identifiers in the Java language (for example,
* {@code "com.example.MyEvent"} for an event class or {@code "message"} for an
* event field).
*
* @since 8
*/
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MetadataDefinition
public @interface Name {
/**
* Returns the name.
*
* @return the name
*/
String value();
}

View File

@@ -0,0 +1,50 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation to use on fractions, typically between {@code 0.0}
* and {@code 1.0}, to specify that the value is a percentage.
* <p>
* For example, a field with the value {@code 0.5} annotated by this annotation,
* should be interpreted as {@code 50%} and rendered in a graphical user
* interface with a percentage sign to avoid confusion with {@code 0.005%}.
*
* @since 8
*/
@MetadataDefinition
@ContentType
@Label("Percentage")
@Description("Percentage, represented as a number between 0 and 1")
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
public @interface Percentage {
}

View File

@@ -0,0 +1,72 @@
/*
* 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;
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;
/**
* Event annotation, specifies the default setting value for a periodic event.
*
* @since 8
*/
@MetadataDefinition
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(ElementType.TYPE)
public @interface Period {
/**
* Settings name {@code "period"} for configuring periodic events
*/
public final static String NAME = "period";
/**
* Returns the default setting value for a periodic setting.
* <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"}.
* <p>
* A period may also be <code>"everyChunk"</code> to specify that it occurs at
* least once for every recording file. The number of events that are emitted
* depends on how many times the file rotations occur when data is recorded.
*
* @return the default setting value, not {@code null}
*/
String value() default "everyChunk";
}

View File

@@ -0,0 +1,676 @@
/*
* 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;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import jdk.jfr.internal.PlatformRecorder;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.WriteableUserPath;
/**
* Provides means to configure, start, stop and dump recording data to disk.
* <p>
* The following example shows how configure, start, stop and dump recording data to disk.
*
* <pre>
* <code>
* Configuration c = Configuration.getConfiguration("default");
* Recording r = new Recording(c);
* r.start();
* System.gc();
* Thread.sleep(5000);
* r.stop();
* r.dump(Files.createTempFile("my-recording", ".jfr"));
* </code>
* </pre>
*
* @since 8
*/
public final class Recording implements Closeable {
private static class RecordingSettings extends EventSettings {
private final Recording recording;
private final String identifier;
RecordingSettings(Recording r, String identifier) {
this.recording = r;
this.identifier = identifier;
}
RecordingSettings(Recording r, Class<? extends Event> eventClass) {
Utils.ensureValidEventSubclass(eventClass);
this.recording = r;
this.identifier = String.valueOf(Type.getTypeId(eventClass));
}
@Override
public EventSettings with(String name, String value) {
Objects.requireNonNull(value);
recording.setSetting(identifier + "#" + name, value);
return this;
}
@Override
public Map<String, String> toMap() {
return recording.getSettings();
}
}
private final PlatformRecording internal;
public Recording(Map<String, String> settings) {
PlatformRecorder r = FlightRecorder.getFlightRecorder().getInternal();
synchronized (r) {
this.internal = r.newRecording(settings);
this.internal.setRecording(this);
if (internal.getRecording() != this) {
throw new InternalError("Internal recording not properly setup");
}
}
}
/**
* Creates a recording without any settings.
* <p>
* A newly created recording is in the {@link RecordingState#NEW} state. To start
* the recording, invoke the {@link Recording#start()} method.
*
* @throws IllegalStateException if Flight Recorder can't be created (for
* example, if the Java Virtual Machine (JVM) lacks Flight Recorder
* support, or if the file repository can't be created or accessed)
*
* @throws SecurityException If a security manager is used and
* FlightRecorderPermission "accessFlightRecorder" is not set.
*/
public Recording() {
this(new HashMap<String, String>());
}
/**
* Creates a recording with settings from a configuration.
* <p>
* The following example shows how create a recording that uses a predefined configuration.
*
* <pre>
* <code>
* Recording r = new Recording(Configuration.getConfiguration("default"));
* </code>
* </pre>
*
* The newly created recording is in the {@link RecordingState#NEW} state. To
* start the recording, invoke the {@link Recording#start()} method.
*
* @param configuration configuration that contains the settings to be use, not
* {@code null}
*
* @throws IllegalStateException if Flight Recorder can't be created (for
* example, if the Java Virtual Machine (JVM) lacks Flight Recorder
* support, or if the file repository can't be created or accessed)
*
* @throws SecurityException if a security manager is used and
* FlightRecorderPermission "accessFlightRecorder" is not set.
*
* @see Configuration
*/
public Recording(Configuration configuration) {
this(configuration.getSettings());
}
/**
* Starts this recording.
* <p>
* It's recommended that the recording options and event settings are configured
* before calling this method. The benefits of doing so are a more consistent
* state when analyzing the recorded data, and improved performance because the
* configuration can be applied atomically.
* <p>
* After a successful invocation of this method, this recording is in the
* {@code RUNNING} state.
*
* @throws IllegalStateException if recording is already started or is in the
* {@code CLOSED} state
*/
public void start() {
internal.start();
}
/**
* Starts this recording after a delay.
* <p>
* After a successful invocation of this method, this recording is in the
* {@code DELAYED} state.
*
* @param delay the time to wait before starting this recording, not
* {@code null}
* @throws IllegalStateException if the recording is not it the {@code NEW} state
*/
public void scheduleStart(Duration delay) {
Objects.requireNonNull(delay);
internal.scheduleStart(delay);
}
/**
* Stops this recording.
* <p>
* When a recording is stopped it can't be restarted. If this
* recording has a destination, data is written to that destination and
* the recording is closed. After a recording is closed, the data is no longer
* available.
* <p>
* After a successful invocation of this method, this recording will be
* in the {@code STOPPED} state.
*
* @return {@code true} if recording is stopped, {@code false} otherwise
*
* @throws IllegalStateException if the recording is not started or is already stopped
*
* @throws SecurityException if a security manager exists and the caller
* doesn't have {@code FilePermission} to write to the destination
* path
*
* @see #setDestination(Path)
*
*/
public boolean stop() {
return internal.stop("Stopped by user");
}
/**
* Returns settings used by this recording.
* <p>
* Modifying the returned {@code Map} will not change the settings for this recording.
* <p>
* If no settings are set for this recording, an empty {@code Map} is
* returned.
*
* @return recording settings, not {@code null}
*/
public Map<String, String> getSettings() {
return new HashMap<>(internal.getSettings());
}
/**
* Returns the current size of this recording in the disk repository,
* measured in bytes.
* <p>
* The size is updated when recording buffers are flushed. If the recording is
* not written to the disk repository the returned size is always {@code 0}.
*
* @return amount of recorded data, measured in bytes, or {@code 0} if the
* recording is not written to the disk repository
*/
public long getSize() {
return internal.getSize();
}
/**
* Returns the time when this recording was stopped.
*
* @return the time, or {@code null} if this recording is not stopped
*/
public Instant getStopTime() {
return internal.getStopTime();
}
/**
* Returns the time when this recording was started.
*
* @return the the time, or {@code null} if this recording is not started
*/
public Instant getStartTime() {
return internal.getStartTime();
}
/**
* Returns the maximum size, measured in bytes, at which data is no longer kept in the disk repository.
*
* @return maximum size in bytes, or {@code 0} if no maximum size is set
*/
public long getMaxSize() {
return internal.getMaxSize();
}
/**
* Returns the length of time that the data is kept in the disk repository
* before it is removed.
*
* @return maximum length of time, or {@code null} if no maximum length of time
* has been set
*/
public Duration getMaxAge() {
return internal.getMaxAge();
}
/**
* Returns the name of this recording.
* <p>
* By default, the name is the same as the recording ID.
*
* @return the recording name, not {@code null}
*/
public String getName() {
return internal.getName();
}
/**
* Replaces all settings for this recording.
* <p>
* The following example shows how to set event settings for a recording.
*
* <pre>
* <code>
* Map{@literal <}String, String{@literal >} settings = new HashMap{@literal <}{@literal >}();
* settings.putAll(EventSettings.enabled("jdk.CPUSample").withPeriod(Duration.ofSeconds(2)).toMap());
* settings.putAll(EventSettings.enabled(MyEvent.class).withThreshold(Duration.ofSeconds(2)).withoutStackTrace().toMap());
* settings.put("jdk.ExecutionSample#period", "10 ms");
* recording.setSettings(settings);
* </code>
* </pre>
*
* The following example shows how to merge settings.
*
* <pre>
* {@code
* Map<String, String> settings = recording.getSettings();
* settings.putAll(additionalSettings);
* recording.setSettings(settings);
* }
* </pre>
*
* @param settings the settings to set, not {@code null}
*/
public void setSettings(Map<String, String> settings) {
Objects.requireNonNull(settings);
Map<String, String> sanitized = Utils.sanitizeNullFreeStringMap(settings);
internal.setSettings(sanitized);
}
/**
* Returns the recording state that this recording is currently in.
*
* @return the recording state, not {@code null}
*
* @see RecordingState
*/
public RecordingState getState() {
return internal.getState();
}
/**
* Releases all data that is associated with this recording.
* <p>
* After a successful invocation of this method, this recording is in the
* {@code CLOSED} state.
*/
@Override
public void close() {
internal.close();
}
/**
* Returns a clone of this recording, with a new recording ID and name.
*
* Clones are useful for dumping data without stopping the recording. After
* a clone is created, the amount of data to copy is constrained
* with the {@link #setMaxAge(Duration)} method and the {@link #setMaxSize(long)}method.
*
* @param stop {@code true} if the newly created copy should be stopped
* immediately, {@code false} otherwise
* @return the recording copy, not {@code null}
*/
public Recording copy(boolean stop) {
return internal.newCopy(stop);
}
/**
* Writes recording data to a file.
* <p>
* Recording must be started, but not necessarily stopped.
*
* @param destination the location where recording data is written, not
* {@code null}
*
* @throws IOException if the recording can't be copied to the specified
* location
*
* @throws SecurityException if a security manager exists and the caller doesn't
* have {@code FilePermission} to write to the destination path
*/
public void dump(Path destination) throws IOException {
Objects.requireNonNull(destination);
internal.dump(new WriteableUserPath(destination));
}
/**
* Returns {@code true} if this recording uses the disk repository, {@code false} otherwise.
* <p>
* If no value is set, {@code true} is returned.
*
* @return {@code true} if the recording uses the disk repository, {@code false}
* otherwise
*/
public boolean isToDisk() {
return internal.isToDisk();
}
/**
* Determines how much data is kept in the disk repository.
* <p>
* To control the amount of recording data that is stored on disk, the maximum
* amount of data to retain can be specified. When the maximum limit is
* exceeded, the Java Virtual Machine (JVM) removes the oldest chunk to make
* room for a more recent chunk.
* <p>
* If neither maximum limit or the maximum age is set, the size of the
* recording may grow indefinitely.
*
* @param maxSize the amount of data to retain, {@code 0} if infinite
*
* @throws IllegalArgumentException if <code>maxSize</code> is negative
*
* @throws IllegalStateException if the recording is in {@code CLOSED} state
*/
public void setMaxSize(long maxSize) {
if (maxSize < 0) {
throw new IllegalArgumentException("Max size of recording can't be negative");
}
internal.setMaxSize(maxSize);
}
/**
* Determines how far back data is kept in the disk repository.
* <p>
* To control the amount of recording data stored on disk, the maximum length of
* time to retain the data can be specified. Data stored on disk that is older
* than the specified length of time is removed by the Java Virtual Machine (JVM).
* <p>
* If neither maximum limit or the maximum age is set, the size of the
* recording may grow indefinitely.
*
* @param maxAge the length of time that data is kept, or {@code null} if infinite
*
* @throws IllegalArgumentException if <code>maxAge</code> is negative
*
* @throws IllegalStateException if the recording is in the {@code CLOSED} state
*/
public void setMaxAge(Duration maxAge) {
if (maxAge != null && maxAge.isNegative()) {
throw new IllegalArgumentException("Max age of recording can't be negative");
}
internal.setMaxAge(maxAge);
}
/**
* Sets a location where data is written on recording stop, or
* {@code null} if data is not to be dumped.
* <p>
* If a destination is set, this recording is automatically closed
* after data is successfully copied to the destination path.
* <p>
* If a destination is <em>not</em> set, Flight Recorder retains the
* recording data until this recording is closed. Use the {@link #dump(Path)} method to
* manually write data to a file.
*
* @param destination the destination path, or {@code null} if recording should
* not be dumped at stop
*
* @throws IllegalStateException if recording is in the {@code STOPPED} or
* {@code CLOSED} state.
*
* @throws SecurityException if a security manager exists and the caller
* doesn't have {@code FilePermission} to read, write, and delete the
* {@code destination} file
*
* @throws IOException if the path is not writable
*/
public void setDestination(Path destination) throws IOException {
internal.setDestination(destination != null ? new WriteableUserPath(destination) : null);
}
/**
* Returns the destination file, where recording data is written when the
* recording stops, or {@code null} if no destination is set.
*
* @return the destination file, or {@code null} if not set.
*/
public Path getDestination() {
WriteableUserPath usp = internal.getDestination();
if (usp == null) {
return null;
} else {
return usp.getPotentiallyMaliciousOriginal();
}
}
/**
* Returns a unique ID for this recording.
*
* @return the recording ID
*/
public long getId() {
return internal.getId();
}
/**
* Sets a human-readable name (for example, {@code "My Recording"}).
*
* @param name the recording name, not {@code null}
*
* @throws IllegalStateException if the recording is in {@code CLOSED} state
*/
public void setName(String name) {
Objects.requireNonNull(name);
internal.setName(name);
}
/**
* Sets whether this recording is dumped to disk when the JVM exits.
*
* @param dumpOnExit if this recording should be dumped when the JVM exits
*/
public void setDumpOnExit(boolean dumpOnExit) {
internal.setDumpOnExit(dumpOnExit);
}
/**
* Returns whether this recording is dumped to disk when the JVM exits.
* <p>
* If dump on exit is not set, {@code false} is returned.
*
* @return {@code true} if the recording is dumped on exit, {@code false}
* otherwise.
*/
public boolean getDumpOnExit() {
return internal.getDumpOnExit();
}
/**
* Determines whether this recording is continuously flushed to the disk
* repository or data is constrained to what is available in memory buffers.
*
* @param disk {@code true} if this recording is written to disk,
* {@code false} if in-memory
*
*/
public void setToDisk(boolean disk) {
internal.setToDisk(disk);
}
/**
* Creates a data stream for a specified interval.
* <p>
* The stream may contain some data outside the specified range.
*
* @param the start start time for the stream, or {@code null} to get data from
* start time of the recording
*
* @param the end end time for the stream, or {@code null} to get data until the
* present time.
*
* @return an input stream, or {@code null} if no data is available in the
* interval.
*
* @throws IllegalArgumentException if {@code end} happens before
* {@code start}
*
* @throws IOException if a stream can't be opened
*/
public InputStream getStream(Instant start, Instant end) throws IOException {
if (start != null && end != null && end.isBefore(start)) {
throw new IllegalArgumentException("End time of requested stream must not be before start time");
}
return internal.open(start, end);
}
/**
* Returns the specified duration for this recording, or {@code null} if no
* duration is set.
* <p>
* The duration can be set only when the recording is in the
* {@link RecordingState#NEW} state.
*
* @return the desired duration of the recording, or {@code null} if no duration
* has been set.
*/
public Duration getDuration() {
return internal.getDuration();
}
/**
* Sets a duration for how long a recording runs before it stops.
* <p>
* By default, a recording has no duration ({@code null}).
*
* @param duration the duration, or {@code null} if no duration is set
*
* @throws IllegalStateException if recording is in the {@code STOPPED} or {@code CLOSED} state
*/
public void setDuration(Duration duration) {
internal.setDuration(duration);
}
/**
* Enables the event with the specified name.
* <p>
* If multiple events have the same name (for example, the same class is loaded
* in different class loaders), then all events that match the name are enabled. To
* enable a specific class, use the {@link #enable(Class)} method or a {@code String}
* representation of the event type ID.
*
* @param name the settings for the event, not {@code null}
*
* @return an event setting for further configuration, not {@code null}
*
* @see EventType
*/
public EventSettings enable(String name) {
Objects.requireNonNull(name);
RecordingSettings rs = new RecordingSettings(this, name);
rs.with("enabled", "true");
return rs;
}
/**
* Disables event with the specified name.
* <p>
* If multiple events with same name (for example, the same class is loaded
* in different class loaders), then all events that match the
* name is disabled. To disable a specific class, use the
* {@link #disable(Class)} method or a {@code String} representation of the event
* type ID.
*
* @param name the settings for the event, not {@code null}
*
* @return an event setting for further configuration, not {@code null}
*
*/
public EventSettings disable(String name) {
Objects.requireNonNull(name);
RecordingSettings rs = new RecordingSettings(this, name);
rs.with("enabled", "false");
return rs;
}
/**
* Enables event.
*
* @param eventClass the event to enable, not {@code null}
*
* @throws IllegalArgumentException if {@code eventClass} is an abstract
* class or not a subclass of {@link Event}
*
* @return an event setting for further configuration, not {@code null}
*/
public EventSettings enable(Class<? extends Event> eventClass) {
Objects.requireNonNull(eventClass);
RecordingSettings rs = new RecordingSettings(this, eventClass);
rs.with("enabled", "true");
return rs;
}
/**
* Disables event.
*
* @param eventClass the event to enable, not {@code null}
*
* @throws IllegalArgumentException if {@code eventClass} is an abstract
* class or not a subclass of {@link Event}
*
* @return an event setting for further configuration, not {@code null}
*
*/
public EventSettings disable(Class<? extends Event> eventClass) {
Objects.requireNonNull(eventClass);
RecordingSettings rs = new RecordingSettings(this, eventClass);
rs.with("enabled", "false");
return rs;
}
// package private
PlatformRecording getInternal() {
return internal;
}
private void setSetting(String id, String value) {
Objects.requireNonNull(id);
Objects.requireNonNull(value);
internal.setSetting(id, value);
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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;
/**
* Indicates a state in the life cycle of a recording.
*
* @since 8
*/
public enum RecordingState {
/**
* The initial state when a {@code Recording} is created.
*/
NEW,
/**
* The recording is scheduled to start with a start time in the future.
* <p>
* An invocation of the {@link Recording#start()} method will transition the
* recording to the {@code RUNNING} state.
*/
DELAYED,
/**
* The recording is recording data and an invocation of the {@link Recording#stop()}
* method will transition the recording to the {@code STOPPED} state.
*/
RUNNING,
/**
* The recording is stopped and is holding recorded data that can be dumped to
* disk.
* <p>
* An invocation of the {@link Recording#close()} method will release the
* data and transition the recording to the {@code CLOSED} state.
*/
STOPPED,
/**
* The recording is closed and all resources that are associated with the
* recording are released.
* <p>
* Nothing that can be done with a recording from this point, and it's
* no longer retrievable from the {@code FlightRrecorder.getRecordings()} method.
*/
CLOSED;
}

View File

@@ -0,0 +1,55 @@
/*
* 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;
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;
/**
* Event annotation, for programmatic event registration.
* <p>
* Events are automatically registered when they are first used. This annotation
* can be used to override that registration. To register
* events programmatically, use {@link FlightRecorder#register(Class)}.
*
* @since 8
*/
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Registered {
/**
* Returns {@code true} if the event is to be registered when the event class is
* first used, {@code false} otherwise.
*
* @return {@code true} if the event is to be registered when the event class is
* first used, {@code false} otherwise.
*/
public boolean value() default true;
}

View File

@@ -0,0 +1,43 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Meta annotation for relational annotations, to be used on an annotation.
*
* @since 8
*/
@MetadataDefinition
@Label("Relation")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Relational {
}

View File

@@ -0,0 +1,220 @@
/*
* 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;
import java.security.AccessController;
import java.util.Set;
import jdk.jfr.internal.Control;
/**
* Base class to extend to create setting controls.
* <p>
* The following example shows a naive implementation of a setting control for
* regular expressions:
*
* <pre>
* <code>
* final class RegExpControl extends SettingControl {
* private Pattern pattern = Pattern.compile(".*");
*
* {@literal @}Override
* public void setValue(String value) {
* this.pattern = Pattern.compile(value);
* }
*
* {@literal @}Override
* public String combine(Set{@literal <}String{@literal >} values) {
* return String.join("|", values);
* }
*
* {@literal @}Override
* public String getValue() {
* return pattern.toString();
* }
*
* public String matches(String s) {
* return pattern.matcher(s).find();
* }
* }
* </code>
* </pre>
*
* The {@code setValue(String)}, {@code getValue()} and
* {@code combine(Set<String>)} methods are invoked when a setting value
* changes, which typically happens when a recording is started or stopped. The
* {@code combine(Set<String>)} method is invoked to resolve what value to use
* when multiple recordings are running at the same time.
* <p>
* The setting control must have a default constructor that can be invoked when
* the event is registered.
* <p>
* To use a setting control with an event, add a method that returns a
* {@code boolean} value and takes the setting control as a parameter. Annotate
* the method with the {@code @SettingDefinition} annotation. By default, the
* method name is used as the setting name, but the name can be set explicitly
* by using the {@code @Name} annotation. If the method returns {@code true},
* the event will be committed.
* <p>
* It is recommended that the {@code setValue(String)} method updates an
* efficient data structure that can be quickly checked when the event is
* committed.
* <p>
* The following example shows how to create an event that uses the
* regular expression filter defined above.
*
* <pre>
* <code>
* abstract class HTTPRequest extends Event {
* {@literal @}Label("Request URI")
* protected String uri;
*
* {@literal @}Label("Servlet URI Filter")
* {@literal @}SettingDefinition
* protected boolean uriFilter(RegExpControl regExp) {
* return regExp.matches(uri);
* }
* }
*
* {@literal @}Label("HTTP Get Request")
* class HTTPGetRequest extends HTTPRequest {
* }
*
* {@literal @}Label("HTTP Post Request")
* class HTTPPostRequest extends HTTPRequest {
* }
*
* class ExampleServlet extends HTTPServlet {
* protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
* HTTPGetRequest request = new HTTPGetRequest();
* request.begin();
* request.uri = req.getRequestURI();
* ...
* request.commit();
* }
*
* protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
* HTTPPostRequest request = new HTTPPostRequest();
* request.begin();
* request.uri = req.getRequestURI();
* ...
* request.commit();
* }
* }
* </code>
* </pre>
*
* The following example shows how an event can be filtered by assigning the
* {@code "uriFilter"} setting with the specified regular expressions.
*
* <pre>
* <code>
* Recording r = new Recording();
* r.enable("HTTPGetRequest").with("uriFilter", "https://www.example.com/list/.*");
* r.enable("HTTPPostRequest").with("uriFilter", "https://www.example.com/login/.*");
* r.start();
* </code>
* </pre>
*
*
*
* @see SettingDefinition
*
* @since 8
*/
@MetadataDefinition
public abstract class SettingControl extends Control {
/**
* Constructor for invocation by subclass constructors.
*/
protected SettingControl() {
super(AccessController.getContext());
}
/**
* Combines the setting values for all running recordings into one value when
* multiple recordings are running at the same time,
* <p>
* The semantics of how setting values are combined depends on the setting
* control that is implemented, but all recordings should get at least all the
* events they request.
* <p>
* This method should have no side effects, because the caller might cache values.
* This method should never return {@code null} or throw an exception. If a
* value is not valid for this setting control, the value should be ignored.
* <p>
* Examples:
* <p>
* if the setting control represents a threshold and three recordings are
* running at the same time with the setting values {@code "10 ms"},
* {@code "8 s"}, and {@code "1 ms"}, this method returns {@code "1 ms"}
* because it means that all recordings get at least all the requested data.
* <p>
* If the setting control represents a set of names and two recordings are
* running at the same time with the setting values {@code "Smith, Jones"} and {@code "Jones,
* Williams"} the returned value is {@code "Smith, Jones, Williams"} because all names would be accepted.
* <p>
* If the setting control represents a boolean condition and four recordings are
* running at the same time with the following values {@code "true"}, {@code "false"}, {@code "false"}, and
* {@code "incorrect"}, this method returns {@code "true"}, because all
* recordings get at least all the requested data.
*
* @param settingValues the set of values, not {@code null}
*
* @return the value to use, not {@code null}
*/
@Override
public abstract String combine(Set<String> settingValues);
/**
* Sets the value for this setting.
* <p>
* If the setting value is not valid for this setting, this method
* does not throw an exception. Instead, the value is ignored.
*
* @param settingValue the string value, not {@code null}
*/
@Override
public abstract void setValue(String settingValue);
/**
* Returns the currently used value for this setting, not {@code null}.
* <p>
* The value returned by this method is valid as an argument to both
* the {@code setValue(String)} method and {@code combine(Set)} method.
* <p>
* This method is invoked when an event is registered to obtain the
* default value. It is therefore important that a valid value can be
* returned immediately after an instance of this class is created. It is
* not valid to return {@code null}.
*
* @return the setting value, not {@code null}
*/
@Override
public abstract String getValue();
}

View File

@@ -0,0 +1,70 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that specifies that a method in an event class should be used to
* filter out events.
* <p>
* For the method to be valid it must return a {@code SettingControl} and only have one
* parameter, which should be a non-abstract subclass of {@link SettingControl}
* <p>
* The return value of the method specifies whether the event is to be
* written to the Flight Recorder system or not.
* <p>
* The following example shows how to annotate a method in an event class.
*
* <pre>
* <code>
* class HelloWorld extend Event {
*
* {@literal @}Label("Message");
* String message;
*
* {@literal @}SettingDefinition;
* {@literal @}Label("Message Filter");
* public boolean filter(RegExpControl regExp) {
* return regExp.matches(message);
* }
* }
* </code>
* </pre>
*
* For an example of how the setting controls are defined, see
* {@link SettingControl}.
*
* @see SettingControl
*
* @since 8
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface SettingDefinition {
}

View File

@@ -0,0 +1,215 @@
/*
* 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;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import jdk.jfr.internal.AnnotationConstruct;
import jdk.jfr.internal.Type;
/**
* Describes an event setting.
*
* @since 8
*/
public final class SettingDescriptor {
private final AnnotationConstruct annotationConstruct;
private final Type type;
private final String name;
private final String defaultValue;
// package private, invoked by jdk.internal.
SettingDescriptor(Type type, String name, String defaultValue, List<AnnotationElement> annotations) {
Objects.requireNonNull(annotations);
this.name = Objects.requireNonNull(name, "Name of value descriptor can't be null");
this.type = Objects.requireNonNull(type);
this.annotationConstruct = new AnnotationConstruct(annotations);
this.defaultValue = Objects.requireNonNull(defaultValue);
}
// package private
void setAnnotations(List<AnnotationElement> as) {
annotationConstruct.setAnnotationElements(as);
}
/**
* Returns the name of the setting (for example, {@code "threshold"}).
*
* @return the name, not {@code null}
*/
public String getName() {
return name;
}
/**
* Returns a human-readable name that describes the setting (for example,
* {@code "Threshold"}).
* <p>
* If the setting lacks a label, the label for the type that is associated with this
* setting is returned, or {@code null} if doesn't exist
*
* @return a human-readable name, or {@code null} if doesn't exist
*/
public String getLabel() {
String label = annotationConstruct.getLabel();
if (label == null) {
label = type.getLabel();
}
return label;
}
/**
* Returns a sentence that describes the setting (for example
* {@code "Record event with duration
* above or equal to threshold"}).
* <p>
* If the setting lacks a description, the description for the type that is
* associated with this setting is returned, or {@code null} if doesn't exist.
*
* @return the description, or {@code null} if doesn't exist
*/
public String getDescription() {
String description = annotationConstruct.getDescription();
if (description == null) {
description = type.getDescription();
}
return description;
}
/**
* Returns a textual identifier that specifies how a value that is represented by
* this {@code SettingDescriptor} object is interpreted or formatted.
* <p>
* For example, if the setting descriptor represents a percentage, then
* {@code "jdk.jfr.Percentage"} hints to a client that a value of "0.5"
* is formatted as "50%".
* <p>
* The JDK provides the following predefined content types:
* <ul>
* <li>jdk.jfr.Percentage</li>
* <li>jdk.jfr.Timespan</li>
* <li>jdk.jfr.Timestamp</li>
* <li>jdk.jfr.Frequency</li>
* <li>jdk.jfr.Flag</li>
* <li>jdk.jfr.MemoryAddress</li>
* <li>jdk.jfr.DataAmount</li>
* <li>jdk.jfr.NetworkAddress</li>
* </ul>
* <p>
* User-defined content types can be created by using {@link ContentType}.
* <p>
* If the setting lacks a content type, the content type for the type
* that is associated with this setting is returned, or {@code null} if not
* available.
*
* @return the content type, or {@code null} if doesn't exist
*
* @see ContentType
*/
public String getContentType() {
for (AnnotationElement anno : getAnnotationElements()) {
for (AnnotationElement meta : anno.getAnnotationElements()) {
if (meta.getTypeName().equals(ContentType.class.getName())) {
return anno.getTypeName();
}
}
}
for (AnnotationElement anno : type.getAnnotationElements()) {
for (AnnotationElement meta : anno.getAnnotationElements()) {
if (meta.getTypeName().equals(ContentType.class.getName())) {
return anno.getTypeName();
}
}
}
return null;
}
/**
* Returns the fully qualified class name of the type that is associated with this
* setting descriptor.
*
* @return the type name, not {@code null}
*
* @see SettingDescriptor#getTypeId()
*/
public String getTypeName() {
return type.getName();
}
/**
* Returns a unique ID for the type in the Java Virtual Machine (JVM).
* <p>
* The ID might not be the same between JVM instances.
*
* @return the type ID, not negative
*/
public long getTypeId() {
return type.getId();
}
/**
* Returns the first annotation for the specified type if an annotation
* element with the same name is available, {@code null} otherwise.
*
* @param <A> the type of the annotation to query for and return if available
* @param annotationType the Class object that corresponds to the annotation
* type, not {@code null}
* @return this element's annotation for the specified annotation type if
* available, {@code null} otherwise
*/
public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
Objects.requireNonNull(annotationType);
return annotationConstruct.getAnnotation(annotationType);
}
/**
* Returns an immutable list of annotation elements for this value
* descriptor.
*
* @return a list of annotations, not {@code null}
*/
public List<AnnotationElement> getAnnotationElements() {
return Collections.unmodifiableList(annotationConstruct.getUnmodifiableAnnotationElements());
}
/**
* Returns the default value for this setting descriptor.
*
* @return the default value, not {@code null}
*/
public String getDefaultValue() {
return defaultValue;
}
// package private
Type getType() {
return type;
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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;
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;
/**
* Event annotation, determines whether an event by default has a stack trace
* or not.
*
* @since 8
*/
@MetadataDefinition
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface StackTrace {
/**
* Settings name {@code "stackTrace"} to be used for enabling event stack traces.
*/
public final static String NAME = "stackTrace";
/**
* Returns if the stack trace from the {@code Event#commit()} method should be recorded.
*
* @return {@code true} if the stack trace should be recorded, {@code false}
* otherwise
*/
boolean value() default true;
}

View File

@@ -0,0 +1,69 @@
/*
* 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;
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;
/**
* Event annotation, specifies the default duration below which an event is not
* recorded (for example, {@code "20 ms"}).
*
* @since 8
*/
@MetadataDefinition
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Threshold {
/**
* Setting name {@code "threshold"} for configuring event thresholds.
*/
public final static String NAME = "threshold";
/**
* The threshold (for example, {@code "20 ms"}).
* <p>
* A {@code 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 are {@code "0 ns"}, {@code "10 ms"}, and {@code "1 s"}.
*
* @return the threshold, default {@code "0 ns"}, not {@code null}
*/
String value() default "0 ns";
}

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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that the value is a duration.
*
* @since 8
*/
@MetadataDefinition
@ContentType
@Label("Timespan")
@Description("A duration, measured in nanoseconds by default")
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
public @interface Timespan {
/**
* Unit for ticks.
*/
public static final String TICKS = "TICKS";
/**
* Unit for seconds.
*/
public static final String SECONDS = "SECONDS";
/**
* Unit for milliseconds.
*/
public static final String MILLISECONDS = "MILLISECONDS";
/**
* Unit for nanoseconds.
*/
public static final String NANOSECONDS = "NANOSECONDS";
/**
* Unit for microseconds.
*/
public static final String MICROSECONDS = "MICROSECONDS";
/**
* Returns the unit of measure for the time span.
* <p>
* By default, the unit is nanoseconds.
*
* @return the time span unit, default {@code #NANOSECONDS}, not {@code null}
*/
String value() default NANOSECONDS;
}

View File

@@ -0,0 +1,63 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that the value is a point in time.
*
* @since 8
*/
@MetadataDefinition
@ContentType
@Label("Timestamp")
@Description("A point in time")
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
public @interface Timestamp {
/**
* The unit for the difference, measured in milliseconds, between the current
* time and midnight, January 1, 1970 UTC.
*/
public final static String MILLISECONDS_SINCE_EPOCH = "MILLISECONDS_SINCE_EPOCH";
/**
* The unit for the number of ticks that have transpired since some arbitrary
* starting date.
*/
public final static String TICKS = "TICKS";
/**
* Unit for the time stamp.
*
* @return time stamp unit, not {@code null}
*/
String value() default Timestamp.MILLISECONDS_SINCE_EPOCH;
}

View File

@@ -0,0 +1,43 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that the event transitioned from a thread.
*
* @since 8
*/
@MetadataDefinition
@Label("Transition From")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TransitionFrom {
}

View File

@@ -0,0 +1,43 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that the event will soon transition to a thread.
*
* @since 8
*/
@MetadataDefinition
@Label("Transition To")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TransitionTo {
}

View File

@@ -0,0 +1,45 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event field annotation, specifies that the value is of an unsigned data type.
*
* @since 8
*/
@MetadataDefinition
@ContentType
@Label("Unsigned Value")
@Description("Value should be interpreted as unsigned data type")
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
public @interface Unsigned {
}

View File

@@ -0,0 +1,322 @@
/*
* 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;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import jdk.jfr.internal.AnnotationConstruct;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
/**
* Describes the event fields and annotation elements.
*
* @since 8
*/
public final class ValueDescriptor {
private final AnnotationConstruct annotationConstruct;
private final Type type;
private final String name;
private final boolean isArray;
private final boolean constantPool;
private final String javaFieldName;
// package private, invoked by jdk.internal.
ValueDescriptor(Type type, String name, List<AnnotationElement> annotations, int dimension, boolean constantPool, String fieldName) {
Objects.requireNonNull(annotations);
if (dimension < 0) {
throw new IllegalArgumentException("Dimension must be positive");
}
this.name = Objects.requireNonNull(name, "Name of value descriptor can't be null");
this.type = Objects.requireNonNull(type);
this.isArray = dimension > 0;
this.constantPool = constantPool;
this.annotationConstruct = new AnnotationConstruct(annotations);
this.javaFieldName = fieldName;
}
/**
* <p>
* Constructs a value descriptor, useful for dynamically creating event types and
* annotations.
* <P>
* The following types are supported:
* <ul>
* <li>{@code byte.class}
* <li>{@code short.class}
* <li>{@code int.class}
* <li>{@code long.class}
* <li>{@code char.class}
* <li>{@code float.class}
* <li>{@code double.class}
* <li>{@code boolean.class}
* <li>{@code String.class}
* <li>{@code Class.class}
* <li>{@code Thread.class}
* </ul>
*
* <p>
* The name must be a valid Java identifier (for example, {@code "maxThroughput"}). See 3.8
* Java Language Specification for more information.
*
* @param type the type, not {@code null}
* @param name the name, not {@code null}
*
* @throws SecurityException if a security manager is present and the caller
* doesn't have {@code FlightRecorderPermission("registerEvent")}
*
*/
public ValueDescriptor(Class<?> type, String name) {
this(type, name, Collections.<AnnotationElement> emptyList());
}
/**
* <p>
* Constructs a value descriptor, useful for dynamically creating event types and
* annotations.
* <P>
* The following types are supported:
* <ul>
* <li>{@code byte.class}
* <li>{@code short.class}
* <li>{@code int.class}
* <li>{@code long.class}
* <li>{@code char.class}
* <li>{@code float.class}
* <li>{@code double.class}
* <li>{@code boolean.class}
* <li>{@code String.class}
* <li>{@code Class.class}
* <li>{@code Thread.class}
* </ul>
*
* <p>
* The name must be a valid Java identifier (for example, {@code "maxThroughput"}). See 3.8
* Java Language Specification for more information.
*
* @param type the type, not {@code null}
* @param name the name, not {@code null}
* @param annotations the annotations on the value descriptors, not
* {@code null}
*
* @throws SecurityException if a security manager is present and the caller
* doesn't have {@code FlightRecorderPermission("registerEvent")}
*/
public ValueDescriptor(Class<?> type, String name, List<AnnotationElement> annotations) {
this(type, name, new ArrayList<>(annotations), false);
}
ValueDescriptor(Class<?> type, String name, List<AnnotationElement> annotations, boolean allowArray) {
Objects.requireNonNull(annotations);
Utils.checkRegisterPermission();
if (!allowArray) {
if (type.isArray()) {
throw new IllegalArgumentException("Array types are not allowed");
}
}
this.name = Objects.requireNonNull(name, "Name of value descriptor can't be null");
this.type = Objects.requireNonNull(Utils.getValidType(Objects.requireNonNull(type), Objects.requireNonNull(name)));
this.annotationConstruct = new AnnotationConstruct(annotations);
this.javaFieldName = name; // Needed for dynamic events
this.isArray = type.isArray();
// Assume we always want to store String and Thread in constant pool
this.constantPool = type == Class.class || type == Thread.class;
}
/**
* Returns a human-readable name that describes the value (for example,
* {@code "Maximum Throughput"}).
*
* @return a human-readable name, or {@code null} if doesn't exist
*/
public String getLabel() {
return annotationConstruct.getLabel();
}
/**
* Returns the name of the value (for example, {@code "maxThroughput"}).
*
* @return the name, not {@code null}
*/
public String getName() {
return name;
}
/**
* Returns a sentence describing the value (for example, {@code "Maximum
* throughput in the transaction system. Value is reset after each new
* batch."}).
*
* @return the description, or {@code null} if doesn't exist
*/
public String getDescription() {
return annotationConstruct.getDescription();
}
/**
* Returns a textual identifier that specifies how a value represented by
* this {@link ValueDescriptor} is interpreted or formatted.
* <p>
* For example, if the value descriptor's type is {@code float} and the
* event value is {@code 0.5f}, a content type of
* {@code "jdk.jfr.Percentage"} hints to a client that the value is a
* percentage and that it should be rendered as {@code "50%"}.
* <p>
* The JDK provides the following predefined content types:
* <ul>
* <li>jdk.jfr.Percentage</li>
* <li>jdk.jfr.Timespan</li>
* <li>jdk.jfr.Timestamp</li>
* <li>jdk.jfr.Frequency</li>
* <li>jdk.jfr.Flag</li>
* <li>jdk.jfr.MemoryAddress</li>
* <li>jdk.jfr.DataAmount</li>
* <li>jdk.jfr.NetworkAddress</li>
* </ul>
* <p>
* User-defined content types can be created by using the {@link ContentType} class.
*
* @return the content type, or {@code null} if doesn't exist
*
* @see ContentType
*/
public String getContentType() {
for (AnnotationElement anno : getAnnotationElements()) {
for (AnnotationElement meta : anno.getAnnotationElements()) {
if (meta.getTypeName().equals(ContentType.class.getName())) {
return anno.getTypeName();
}
}
}
return null;
}
/**
* Returns the fully qualified class name of the type that is associated with
* this value descriptor.
*
* @return the type name, not {@code null}
*
* @see ValueDescriptor#getTypeId()
*/
public String getTypeName() {
if (type.isSimpleType()) {
return type.getFields().get(0).getTypeName();
}
return type.getName();
}
/**
* Returns a unique ID for the type in the Java virtual Machine (JVM).
*
* The ID might not be the same between JVM instances.
*
* @return the type ID, not negative
*/
public long getTypeId() {
return type.getId();
}
/**
* Returns if this value descriptor is an array type.
*
* @return {@code true} if it is an array type, {@code false} otherwise
*/
public boolean isArray() {
return isArray;
}
/**
* Returns the first annotation for the specified type if an annotation
* element with the same name is directly present for this value descriptor,
* {@code null} otherwise.
*
* @param <A> the type of the annotation to query for and return if present
* @param annotationType the Class object that corresponds to the annotation
* type, not {@code null}
* @return this element's annotation for the specified annotation type if
* directly present, else {@code null}
*/
public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
Objects.requireNonNull(annotationType);
return annotationConstruct.getAnnotation(annotationType);
}
/**
* Returns an immutable list of annotation elements for this value
* descriptor.
*
* @return a list of annotations, not {@code null}
*/
public List<AnnotationElement> getAnnotationElements() {
return annotationConstruct.getUnmodifiableAnnotationElements();
}
/**
* Returns an immutable list of value descriptors if the type is complex,
* else an empty list.
*
* @return a list of value descriptors, not {@code null}
*/
public List<ValueDescriptor> getFields() {
if (type.isSimpleType()) {
return Collections.emptyList();
}
return type.getFields();
}
// package private
Type getType() {
return type;
}
// package private
void setAnnotations(List<AnnotationElement> anno) {
annotationConstruct.setAnnotationElements(anno);
}
// package private
boolean isConstantPool() {
return constantPool;
}
// package private
String getJavaFieldName() {
return javaFieldName;
}
// package private
boolean isUnsigned() {
return annotationConstruct.hasUnsigned();
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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.consumer;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import jdk.jfr.EventType;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.MetadataDescriptor;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.ChunkHeader;
import jdk.jfr.internal.consumer.RecordingInput;
/**
* Parses a chunk.
*
*/
final class ChunkParser {
private static final long CONSTANT_POOL_TYPE_ID = 1;
private final RecordingInput input;
private final LongMap<Parser> parsers;
private final ChunkHeader chunkHeader;
private final long absoluteChunkEnd;
private final MetadataDescriptor metadata;
private final LongMap<Type> typeMap;
private final TimeConverter timeConverter;
public ChunkParser(RecordingInput input) throws IOException {
this(new ChunkHeader(input));
}
private ChunkParser(ChunkHeader header) throws IOException {
this.input = header.getInput();
this.chunkHeader = header;
this.metadata = header.readMetadata();
this.absoluteChunkEnd = header.getEnd();
this.timeConverter = new TimeConverter(chunkHeader, metadata.getGMTOffset());
ParserFactory factory = new ParserFactory(metadata, timeConverter);
LongMap<ConstantMap> constantPools = factory.getConstantPools();
parsers = factory.getParsers();
typeMap = factory.getTypeMap();
fillConstantPools(parsers, constantPools);
constantPools.forEach(ConstantMap::setIsResolving);
constantPools.forEach(ConstantMap::resolve);
constantPools.forEach(ConstantMap::setResolved);
input.position(chunkHeader.getEventStart());
}
public RecordedEvent readEvent() throws IOException {
while (input.position() < absoluteChunkEnd) {
long pos = input.position();
int size = input.readInt();
if (size == 0) {
throw new IOException("Event can't have zero size");
}
long typeId = input.readLong();
if (typeId > CONSTANT_POOL_TYPE_ID) { // also skips metadata (id=0)
Parser ep = parsers.get(typeId);
if (ep instanceof EventParser) {
return (RecordedEvent) ep.parse(input);
}
}
input.position(pos + size);
}
return null;
}
private void fillConstantPools(LongMap<Parser> typeParser, LongMap<ConstantMap> constantPools) throws IOException {
long nextCP = chunkHeader.getAbsoluteChunkStart();
long deltaToNext = chunkHeader.getConstantPoolPosition();
while (deltaToNext != 0) {
nextCP += deltaToNext;
input.position(nextCP);
final long position = nextCP;
int size = input.readInt(); // size
long typeId = input.readLong();
if (typeId != CONSTANT_POOL_TYPE_ID) {
throw new IOException("Expected check point event (id = 1) at position " + nextCP + ", but found type id = " + typeId);
}
input.readLong(); // timestamp
input.readLong(); // duration
deltaToNext = input.readLong();
final long delta = deltaToNext;
boolean flush = input.readBoolean();
int poolCount = input.readInt();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> {
return "New constant pool: startPosition=" + position + ", size=" + size + ", deltaToNext=" + delta + ", flush=" + flush + ", poolCount=" + poolCount;
});
for (int i = 0; i < poolCount; i++) {
long id = input.readLong(); // type id
ConstantMap pool = constantPools.get(id);
Type type = typeMap.get(id);
if (pool == null) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found constant pool(" + id + ") that is never used");
if (type == null) {
throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + nextCP + ", " + nextCP + size + "]");
}
pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
constantPools.put(type.getId(), pool);
}
Parser parser = typeParser.get(id);
if (parser == null) {
throw new IOException("Could not find constant pool type with id = " + id);
}
try {
int count = input.readInt();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> "Constant: " + getName(id) + "[" + count + "]");
for (int j = 0; j < count; j++) {
long key = input.readLong();
Object value = parser.parse(input);
pool.put(key, value);
}
} catch (Exception e) {
throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + nextCP + ", " + nextCP + size + "]", e);
}
}
if (input.position() != nextCP + size) {
throw new IOException("Size of check point event doesn't match content");
}
}
}
private String getName(long id) {
Type type = typeMap.get(id);
return type == null ? ("unknown(" + id + ")") : type.getName();
}
public Collection<Type> getTypes() {
return metadata.getTypes();
}
public List<EventType> getEventTypes() {
return metadata.getEventTypes();
}
public boolean isLastChunk() {
return chunkHeader.isLastChunk();
}
public ChunkParser nextChunkParser() throws IOException {
return new ChunkParser(chunkHeader.nextHeader());
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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.consumer;
import java.util.ArrayList;
import java.util.List;
/**
* Holds mapping between a set of keys and their corresponding object.
*
* If the type is a known type, i.e. {@link RecordedThread}, an
* {@link ObjectFactory} can be supplied which will instantiate a typed object.
*/
final class ConstantMap {
private final static class Reference {
private final long key;
private final ConstantMap pool;
Reference(ConstantMap pool, long key) {
this.pool = pool;
this.key = key;
}
Object resolve() {
return pool.get(key);
}
}
private final ObjectFactory<?> factory;
private final LongMap<Object> objects;
private LongMap<Boolean> isResolving;
private boolean allResolved;
private String name;
ConstantMap(ObjectFactory<?> factory, String name) {
this.name = name;
this.objects = new LongMap<>();
this.factory = factory;
}
Object get(long id) {
// fast path, all objects in pool resolved
if (allResolved) {
return objects.get(id);
}
// referenced from a pool, deal with this later
if (isResolving == null) {
return new Reference(this, id);
}
Boolean beingResolved = isResolving.get(id);
// we are resolved (but not the whole pool)
if (Boolean.FALSE.equals(beingResolved)) {
return objects.get(id);
}
// resolving ourself, abort to avoid infinite recursion
if (Boolean.TRUE.equals(beingResolved)) {
return null;
}
// resolve me!
isResolving.put(id, Boolean.TRUE);
Object resolved = resolve(objects.get(id));
isResolving.put(id, Boolean.FALSE);
if (factory != null) {
Object factorized = factory.createObject(id, resolved);
objects.put(id, factorized);
return factorized;
} else {
objects.put(id, resolved);
return resolved;
}
}
private static Object resolve(Object o) {
if (o instanceof Reference) {
return resolve(((Reference) o).resolve());
}
if (o != null && o.getClass().isArray()) {
final Object[] array = (Object[]) o;
for (int i = 0; i < array.length; i++) {
array[i] = resolve(array[i]);
}
return array;
}
return o;
}
public void resolve() {
List<Long> keyList = new ArrayList<>();
objects.keys().forEachRemaining(keyList::add);
for (Long l : keyList) {
get(l);
}
}
public void put(long key, Object value) {
objects.put(key, value);
}
public void setIsResolving() {
isResolving = new LongMap<>();
}
public void setResolved() {
allResolved = true;
isResolving = null; // pool finished, release memory
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.consumer;
import static jdk.jfr.internal.EventInstrumentation.FIELD_DURATION;
import java.io.IOException;
import java.util.List;
import jdk.jfr.EventType;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.consumer.RecordingInput;
/**
* Parses an event and returns a {@link RecordedEvent}.
*
*/
final class EventParser extends Parser {
private final Parser[] parsers;
private final EventType eventType;
private final TimeConverter timeConverter;
private final boolean hasDuration;
private final List<ValueDescriptor> valueDescriptors;
EventParser(TimeConverter timeConverter, EventType type, Parser[] parsers) {
this.timeConverter = timeConverter;
this.parsers = parsers;
this.eventType = type;
this.hasDuration = type.getField(FIELD_DURATION) != null;
this.valueDescriptors = type.getFields();
}
@Override
public Object parse(RecordingInput input) throws IOException {
Object[] values = new Object[parsers.length];
for (int i = 0; i < parsers.length; i++) {
values[i] = parsers[i].parse(input);
}
Long startTicks = (Long) values[0];
long startTime = timeConverter.convertTimestamp(startTicks);
if (hasDuration) {
long durationTicks = (Long) values[1];
long endTime = timeConverter.convertTimestamp(startTicks + durationTicks);
return new RecordedEvent(eventType, valueDescriptors, values, startTime, endTime, timeConverter);
} else {
return new RecordedEvent(eventType, valueDescriptors, values, startTime, startTime, timeConverter);
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.consumer;
import java.util.HashMap;
import java.util.Iterator;
/**
* Commonly used data structure for looking up objects given an id (long value)
*
* TODO: Implement without using Map and Long objects, to minimize allocation
*
* @param <T>
*/
final class LongMap<T> implements Iterable<T> {
private final HashMap<Long, T> map;
LongMap() {
map = new HashMap<>(101);
}
void put(long id, T object) {
map.put(id, object);
}
T get(long id) {
return map.get(id);
}
@Override
public Iterator<T> iterator() {
return map.values().iterator();
}
Iterator<Long> keys() {
return map.keySet().iterator();
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.consumer;
import java.util.List;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/**
* Abstract factory for creating specialized types
*/
abstract class ObjectFactory<T> {
final static String TYPE_PREFIX_VERSION_1 = "com.oracle.jfr.types.";
final static String TYPE_PREFIX_VERSION_2 = Type.TYPES_PREFIX;
final static String STACK_FRAME_VERSION_1 = TYPE_PREFIX_VERSION_1 + "StackFrame";
final static String STACK_FRAME_VERSION_2 = TYPE_PREFIX_VERSION_2 + "StackFrame";
public static ObjectFactory<?> create(Type type, TimeConverter timeConverter) {
switch (type.getName()) {
case "java.lang.Thread":
return RecordedThread.createFactory(type, timeConverter);
case TYPE_PREFIX_VERSION_1 + "StackFrame":
case TYPE_PREFIX_VERSION_2 + "StackFrame":
return RecordedFrame.createFactory(type, timeConverter);
case TYPE_PREFIX_VERSION_1 + "Method":
case TYPE_PREFIX_VERSION_2 + "Method":
return RecordedMethod.createFactory(type, timeConverter);
case TYPE_PREFIX_VERSION_1 + "ThreadGroup":
case TYPE_PREFIX_VERSION_2 + "ThreadGroup":
return RecordedThreadGroup.createFactory(type, timeConverter);
case TYPE_PREFIX_VERSION_1 + "StackTrace":
case TYPE_PREFIX_VERSION_2 + "StackTrace":
return RecordedStackTrace.createFactory(type, timeConverter);
case TYPE_PREFIX_VERSION_1 + "ClassLoader":
case TYPE_PREFIX_VERSION_2 + "ClassLoader":
return RecordedClassLoader.createFactory(type, timeConverter);
case "java.lang.Class":
return RecordedClass.createFactory(type, timeConverter);
}
return null;
}
private final List<ValueDescriptor> valueDescriptors;
ObjectFactory(Type type) {
this.valueDescriptors = type.getFields();
}
T createObject(long id, Object value) {
if (value == null) {
return null;
}
if (value instanceof Object[]) {
return createTyped(valueDescriptors, id, (Object[]) value);
}
throw new InternalError("Object factory must have struct type");
}
abstract T createTyped(List<ValueDescriptor> valueDescriptors, long id, Object[] values);
}

View File

@@ -0,0 +1,45 @@
/*
* 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.consumer;
import java.io.IOException;
import jdk.jfr.internal.consumer.RecordingInput;
/**
* Base class for parsing data from a {@link RecordingInput}.
*/
abstract class Parser {
/**
* Parses data from a {@link RecordingInput} and return an object.
*
* @param input input to read from
* @return an object
* @throws IOException if operation couldn't be completed due to I/O
* problems
*/
abstract Object parse(RecordingInput input) throws IOException;
}

View File

@@ -0,0 +1,304 @@
/*
* 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.consumer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import jdk.jfr.EventType;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.MetadataDescriptor;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.RecordingInput;
/**
* Class that create parsers suitable for reading events and constant pools
*/
final class ParserFactory {
private final LongMap<Parser> parsers = new LongMap<>();
private final TimeConverter timeConverter;
private final LongMap<Type> types = new LongMap<>();
private final LongMap<ConstantMap> constantPools;
public ParserFactory(MetadataDescriptor metadata, TimeConverter timeConverter) throws IOException {
this.constantPools = new LongMap<>();
this.timeConverter = timeConverter;
for (Type t : metadata.getTypes()) {
types.put(t.getId(), t);
}
for (Type t : types) {
if (!t.getFields().isEmpty()) { // Avoid primitives
CompositeParser cp = createCompositeParser(t);
if (t.isSimpleType()) { // Reduce to nested parser
parsers.put(t.getId(), cp.parsers[0]);
}
}
}
// Override event types with event parsers
for (EventType t : metadata.getEventTypes()) {
parsers.put(t.getId(), createEventParser(t));
}
}
public LongMap<Parser> getParsers() {
return parsers;
}
public LongMap<ConstantMap> getConstantPools() {
return constantPools;
}
public LongMap<Type> getTypeMap() {
return types;
}
private EventParser createEventParser(EventType eventType) throws IOException {
List<Parser> parsers = new ArrayList<Parser>();
for (ValueDescriptor f : eventType.getFields()) {
parsers.add(createParser(f));
}
return new EventParser(timeConverter, eventType, parsers.toArray(new Parser[0]));
}
private Parser createParser(ValueDescriptor v) throws IOException {
boolean constantPool = PrivateAccess.getInstance().isConstantPool(v);
if (v.isArray()) {
Type valueType = PrivateAccess.getInstance().getType(v);
ValueDescriptor element = PrivateAccess.getInstance().newValueDescriptor(v.getName(), valueType, v.getAnnotationElements(), 0, constantPool, null);
return new ArrayParser(createParser(element));
}
long id = v.getTypeId();
Type type = types.get(id);
if (type == null) {
throw new IOException("Type '" + v.getTypeName() + "' is not defined");
}
if (constantPool) {
ConstantMap pool = constantPools.get(id);
if (pool == null) {
pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
constantPools.put(id, pool);
}
return new ConstantMapValueParser(pool);
}
Parser parser = parsers.get(id);
if (parser == null) {
if (!v.getFields().isEmpty()) {
return createCompositeParser(type);
} else {
return registerParserType(type, createPrimitiveParser(type));
}
}
return parser;
}
private Parser createPrimitiveParser(Type type) throws IOException {
switch (type.getName()) {
case "int":
return new IntegerParser();
case "long":
return new LongParser();
case "float":
return new FloatParser();
case "double":
return new DoubleParser();
case "char":
return new CharacterParser();
case "boolean":
return new BooleanParser();
case "short":
return new ShortParser();
case "byte":
return new ByteParser();
case "java.lang.String":
ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
constantPools.put(type.getId(), pool);
return new StringParser(pool);
default:
throw new IOException("Unknown primitive type " + type.getName());
}
}
private Parser registerParserType(Type t, Parser parser) {
Parser p = parsers.get(t.getId());
// check if parser exists (known type)
if (p != null) {
return p;
}
parsers.put(t.getId(), parser);
return parser;
}
private CompositeParser createCompositeParser(Type type) throws IOException {
List<ValueDescriptor> vds = type.getFields();
Parser[] parsers = new Parser[vds.size()];
CompositeParser composite = new CompositeParser(parsers);
// need to pre-register so recursive types can be handled
registerParserType(type, composite);
int index = 0;
for (ValueDescriptor vd : vds) {
parsers[index++] = createParser(vd);
}
return composite;
}
private static final class BooleanParser extends Parser {
@Override
public Object parse(RecordingInput input) throws IOException {
return input.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
}
}
private static final class ByteParser extends Parser {
@Override
public Object parse(RecordingInput input) throws IOException {
return Byte.valueOf(input.readByte());
}
}
private static final class LongParser extends Parser {
@Override
public Object parse(RecordingInput input) throws IOException {
return Long.valueOf(input.readLong());
}
}
private static final class IntegerParser extends Parser {
@Override
public Object parse(RecordingInput input) throws IOException {
return Integer.valueOf(input.readInt());
}
}
private static final class ShortParser extends Parser {
@Override
public Object parse(RecordingInput input) throws IOException {
return Short.valueOf(input.readShort());
}
}
private static final class CharacterParser extends Parser {
@Override
public Object parse(RecordingInput input) throws IOException {
return Character.valueOf(input.readChar());
}
}
private static final class FloatParser extends Parser {
@Override
public Object parse(RecordingInput input) throws IOException {
return Float.valueOf(input.readFloat());
}
}
private static final class DoubleParser extends Parser {
@Override
public Object parse(RecordingInput input) throws IOException {
return Double.valueOf(input.readDouble());
}
}
private static final class StringParser extends Parser {
private final ConstantMap stringConstantMap;
private String last;
StringParser(ConstantMap stringConstantMap) {
this.stringConstantMap = stringConstantMap;
}
@Override
public Object parse(RecordingInput input) throws IOException {
String s = parseEncodedString(input);
if (!Objects.equals(s, last)) {
last = s;
}
return last;
}
private String parseEncodedString(RecordingInput input) throws IOException {
byte encoding = input.readByte();
if (encoding == RecordingInput.STRING_ENCODING_CONSTANT_POOL) {
long id = input.readLong();
return (String) stringConstantMap.get(id);
} else {
return input.readEncodedString(encoding);
}
}
}
private final static class ArrayParser extends Parser {
private final Parser elementParser;
public ArrayParser(Parser elementParser) {
this.elementParser = elementParser;
}
@Override
public Object parse(RecordingInput input) throws IOException {
final int size = input.readInt();
input.require(size, "Array size %d exceeds available data" );
final Object[] array = new Object[size];
for (int i = 0; i < size; i++) {
array[i] = elementParser.parse(input);
}
return array;
}
}
private final static class CompositeParser extends Parser {
private final Parser[] parsers;
public CompositeParser(Parser[] valueParsers) {
this.parsers = valueParsers;
}
@Override
public Object parse(RecordingInput input) throws IOException {
final Object[] values = new Object[parsers.length];
for (int i = 0; i < values.length; i++) {
values[i] = parsers[i].parse(input);
}
return values;
}
}
private static final class ConstantMapValueParser extends Parser {
private final ConstantMap pool;
ConstantMapValueParser(ConstantMap pool) {
this.pool = pool;
}
@Override
public Object parse(RecordingInput input) throws IOException {
return pool.get(input.readLong());
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.consumer;
import java.lang.reflect.Modifier;
import java.util.List;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/**
* A recorded Java type, such as a class or an interface.
*
* @since 8
*/
public final class RecordedClass extends RecordedObject {
static ObjectFactory<RecordedClass> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedClass>(type) {
@Override
RecordedClass createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedClass(desc, id, object, timeConverter);
}
};
}
private final long uniqueId;
// package private
private RecordedClass(List<ValueDescriptor> descriptors, long id, Object[] values, TimeConverter timeConverter) {
super(descriptors, values, timeConverter);
this.uniqueId = id;
}
/**
* Returns the modifiers of the class.
* <p>
* See {@link java.lang.reflect.Modifier}
*
* @return the modifiers
*
* @see Modifier
*/
public int getModifiers() {
return getTyped("modifiers", Integer.class, -1);
}
/**
* Returns the class loader that defined the class.
* <P>
* If the bootstrap class loader is represented as {@code null} in the Java
* Virtual Machine (JVM), then {@code null} is also the return value of this method.
*
* @return the class loader defining this class, can be {@code null}
*/
public RecordedClassLoader getClassLoader() {
return getTyped("classLoader", RecordedClassLoader.class, null);
}
/**
* Returns the fully qualified name of the class (for example,
* {@code "java.lang.String"}).
*
* @return the class name, not {@code null}
*/
public String getName() {
return getTyped("name", String.class, null).replace("/", ".");
}
/**
* Returns a unique ID for the class.
* <p>
* The ID might not be the same between Java Virtual Machine (JVM) instances.
*
* @return a unique ID
*/
public long getId() {
return uniqueId;
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.consumer;
import java.util.List;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/**
* A recorded Java class loader.
*
* @since 8
*/
public final class RecordedClassLoader extends RecordedObject {
static ObjectFactory<RecordedClassLoader> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedClassLoader>(type) {
@Override
RecordedClassLoader createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedClassLoader(desc, id, object, timeConverter);
}
};
}
private final long uniqueId;
// package private
private RecordedClassLoader(List<ValueDescriptor> descriptors, long id, Object[] values, TimeConverter timeConverter) {
super(descriptors, values, timeConverter);
this.uniqueId = id;
}
/**
* Returns the class of the class loader.
* <P>
* If the bootstrap class loader is represented as {@code null} in the Java
* Virtual Machine (JVM), then {@code null} is also the return value of this
* method.
*
* @return class of the class loader, can be {@code null}
*/
public RecordedClass getType() {
return getTyped("type", RecordedClass.class, null);
}
/**
* Returns the name of the class loader (for example, "boot", "platform", and
* "app").
*
* @return the class loader name, can be {@code null}
*/
public String getName() {
return getTyped("name", String.class, null);
}
/**
* Returns a unique ID for the class loader.
* <p>
* The ID might not be the same between Java Virtual Machine (JVM) instances.
*
* @return a unique ID
*/
public long getId() {
return uniqueId;
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.consumer;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import jdk.jfr.EventType;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.EventInstrumentation;
/**
* A recorded event.
*
* @since 8
*/
public final class RecordedEvent extends RecordedObject {
private final EventType eventType;
private final long startTime;
// package private needed for efficient sorting
final long endTime;
// package private
RecordedEvent(EventType type, List<ValueDescriptor> vds, Object[] values, long startTime, long endTime, TimeConverter timeConverter) {
super(vds, values, timeConverter);
this.eventType = type;
this.startTime = startTime;
this.endTime = endTime;
}
/**
* Returns the stack trace that was created when the event was committed, or
* {@code null} if the event lacks a stack trace.
*
* @return stack trace, or {@code null} if doesn't exist for the event
*/
public RecordedStackTrace getStackTrace() {
return getTyped(EventInstrumentation.FIELD_STACK_TRACE, RecordedStackTrace.class, null);
}
/**
* Returns the thread from which the event was committed, or {@code null} if
* the thread was not recorded.
*
* @return thread, or {@code null} if doesn't exist for the event
*/
public RecordedThread getThread() {
return getTyped(EventInstrumentation.FIELD_EVENT_THREAD, RecordedThread.class, null);
}
/**
* Returns the event type that describes the event.
*
* @return the event type, not {@code null}
*/
public EventType getEventType() {
return eventType;
}
/**
* Returns the start time of the event.
* <p>
* If the event is an instant event, then the start time and end time are the same.
*
* @return the start time, not {@code null}
*/
public Instant getStartTime() {
return Instant.ofEpochSecond(0, startTime);
}
/**
* Returns the end time of the event.
* <p>
* If the event is an instant event, then the start time and end time are the same.
*
* @return the end time, not {@code null}
*/
public Instant getEndTime() {
return Instant.ofEpochSecond(0, endTime);
}
/**
* Returns the duration of the event, measured in nanoseconds.
*
* @return the duration in nanoseconds, not {@code null}
*/
public Duration getDuration() {
return Duration.ofNanos(endTime - startTime);
}
/**
* Returns the list of descriptors that describes the fields of the event.
*
* @return descriptors, not {@code null}
*/
@Override
public List<ValueDescriptor> getFields() {
return getEventType().getFields();
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.consumer;
import java.lang.reflect.Modifier;
import java.util.List;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/**
* A recorded frame in a stack trace.
*
* @since 8
*/
public final class RecordedFrame extends RecordedObject {
static ObjectFactory<RecordedFrame> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedFrame>(type) {
@Override
RecordedFrame createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedFrame(desc, object, timeConverter);
}
};
}
// package private
RecordedFrame(List<ValueDescriptor> desc, Object[] objects, TimeConverter timeConverter) {
super(desc, objects, timeConverter);
}
/**
* Returns {@code true} if this is a Java frame, {@code false} otherwise.
* <p>
* A Java method that has a native modifier is considered a Java frame.
*
* @return {@code true} if this is a Java frame, {@code false} otherwise
*
* @see Modifier#isNative(int)
*/
public boolean isJavaFrame() {
// Only Java frames exist today, but this allows
// API to be extended for native frame in the future.
if (hasField("javaFrame")) {
return getTyped("javaFrame", Boolean.class, Boolean.TRUE);
}
return true;
}
/**
* Returns the bytecode index for the execution point that is represented by
* this recorded frame.
*
* @return byte code index, or {@code -1} if doesn't exist
*/
public int getBytecodeIndex() {
return getTyped("bytecodeIndex", Integer.class, Integer.valueOf(-1));
}
/**
* Returns the line number for the execution point that is represented by this
* recorded frame, or {@code -1} if doesn't exist
*
* @return the line number, or {@code -1} if doesn't exist
*/
public int getLineNumber() {
return getTyped("lineNumber", Integer.class, Integer.valueOf(-1));
}
/**
* Returns the frame type for the execution point that is represented by this
* recorded frame (for example, {@code "Interpreted"}, {@code "JIT compiled"} or
* {@code "Inlined"}).
*
* @return the frame type, or {@code null} if doesn't exist
*/
public String getType() {
return getTyped("type", String.class, null);
}
/**
* Returns the method for the execution point that is represented by this
* recorded frame.
*
* @return the method, not {@code null}
*/
public RecordedMethod getMethod() {
return getTyped("method", RecordedMethod.class, null);
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.consumer;
import java.lang.reflect.Modifier;
import java.util.List;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/**
* A recorded method.
*
* @since 8
*/
public final class RecordedMethod extends RecordedObject {
static ObjectFactory<RecordedMethod> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedMethod>(type) {
@Override
RecordedMethod createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedMethod(desc, object, timeConverter);
}
};
}
private RecordedMethod(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
super(descriptors, objects, timeConverter);
}
/**
* Returns the class this method belongs to, if it belong to a Java frame.
* <p>
* To ensure this is a Java frame, use the {@link RecordedFrame#isJavaFrame()}
* method.
*
* @return the class, may be {@code null} if not a Java frame
*
* @see RecordedFrame#isJavaFrame()
*/
public RecordedClass getType() {
return getTyped("type", RecordedClass.class, null);
}
/**
* Returns the name of this method, for example {@code "toString"}.
* <p>
* If this method doesn't belong to a Java frame the result is undefined.
*
* @return method name, or {@code null} if doesn't exist
*
* @see RecordedFrame#isJavaFrame()
*/
public String getName() {
return getTyped("name", String.class, null);
}
/**
* Returns the method descriptor for this method (for example,
* {@code "(Ljava/lang/String;)V"}).
* <p>
* See Java Virtual Machine Specification, 4.3
* <p>
* If this method doesn't belong to a Java frame then the the result is undefined.
*
* @return method descriptor.
*
* @see RecordedFrame#isJavaFrame()
*/
public String getDescriptor() {
return getTyped("descriptor", String.class, null);
}
/**
* Returns the modifiers for this method.
* <p>
* If this method doesn't belong to a Java frame, then the result is undefined.
*
* @return the modifiers
*
* @see Modifier
* @see RecordedFrame#isJavaFrame
*/
public int getModifiers() {
return getTyped("modifiers", Integer.class, Integer.valueOf(0));
}
/**
* Returns whether this method is hidden (for example, wrapper code in a lambda
* expressions).
*
* @return {@code true} if method is hidden, {@code false} otherwise
*/
public boolean isHidden() {
return getTyped("hidden", Boolean.class, Boolean.FALSE);
}
}

View File

@@ -0,0 +1,903 @@
/*
* 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.consumer;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Duration;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
import jdk.jfr.Timespan;
import jdk.jfr.Timestamp;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.tool.PrettyWriter;
/**
* A complex data type that consists of one or more fields.
* <p>
* This class provides methods to select and query nested objects by passing a
* dot {@code "."} delimited {@code String} object (for instance,
* {@code "aaa.bbb"}). A method evaluates a nested object from left to right,
* and if a part is {@code null}, it throws {@code NullPointerException}.
*
* @since 8
*/
public class RecordedObject {
private final static class UnsignedValue {
private final Object o;
UnsignedValue(Object o) {
this.o = o;
}
Object value() {
return o;
}
}
private final Object[] objects;
private final List<ValueDescriptor> descriptors;
private final TimeConverter timeConverter;
// package private, not to be subclassed outside this package
RecordedObject(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
this.descriptors = descriptors;
this.objects = objects;
this.timeConverter = timeConverter;
}
// package private
final <T> T getTyped(String name, Class<T> clazz, T defaultValue) {
// Unnecessary to check field presence twice, but this
// will do for now.
if (!hasField(name)) {
return defaultValue;
}
T object = getValue(name);
if (object == null || object.getClass().isAssignableFrom(clazz)) {
return object;
} else {
return defaultValue;
}
}
/**
* Returns {@code true} if a field with the given name exists, {@code false}
* otherwise.
*
* @param name name of the field to get, not {@code null}
*
* @return {@code true} if the field exists, {@code false} otherwise.
*
* @see #getFields()
*/
public boolean hasField(String name) {
Objects.requireNonNull(name);
for (ValueDescriptor v : descriptors) {
if (v.getName().equals(name)) {
return true;
}
}
int dotIndex = name.indexOf(".");
if (dotIndex > 0) {
String structName = name.substring(0, dotIndex);
for (ValueDescriptor v : descriptors) {
if (!v.getFields().isEmpty() && v.getName().equals(structName)) {
RecordedObject child = getValue(structName);
if (child != null) {
return child.hasField(name.substring(dotIndex + 1));
}
}
}
}
return false;
}
/**
* Returns the value of the field with the given name.
* <p>
* The return type may be a primitive type or a subclass of
* {@link RecordedObject}.
* <p>
* It's possible to index into a nested object by using {@code "."} (for
* instance {@code "thread.group.parent.name}").
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
* <p>
* Example
*
* <pre>
* <code>
* if (event.hasField("intValue")) {
* int intValue = event.getValue("intValue");
* System.out.println("Int value: " + intValue);
* }
*
* if (event.hasField("objectClass")) {
* RecordedClass clazz = event.getValue("objectClass");
* System.out.println("Class name: " + clazz.getName());
* }
*
* if (event.hasField("sampledThread")) {
* RecordedThread sampledThread = event.getValue("sampledThread");
* System.out.println("Sampled thread: " + sampledThread.getName());
* }
* </code>
* </pre>
*
* @param <T> the return type
* @param name of the field to get, not {@code null}
* @throws IllegalArgumentException if no field called {@code name} exists
*
* @return the value, can be {@code null}
*
* @see #hasField(String)
*
*/
final public <T> T getValue(String name) {
@SuppressWarnings("unchecked")
T t = (T) getValue(name, false);
return t;
}
private Object getValue(String name, boolean allowUnsigned) {
Objects.requireNonNull(name);
int index = 0;
for (ValueDescriptor v : descriptors) {
if (name.equals(v.getName())) {
Object object = objects[index];
if (object == null) {
// error or missing
return null;
}
if (v.getFields().isEmpty()) {
if (allowUnsigned && PrivateAccess.getInstance().isUnsigned(v)) {
// Types that are meaningless to widen
if (object instanceof Character || object instanceof Long) {
return object;
}
return new UnsignedValue(object);
}
return object; // primitives and primitive arrays
} else {
if (object instanceof RecordedObject) {
// known types from factory
return object;
}
// must be array type
Object[] array = (Object[]) object;
if (v.isArray()) {
// struct array
return structifyArray(v, array, 0);
}
// struct
return new RecordedObject(v.getFields(), (Object[]) object, timeConverter);
}
}
index++;
}
int dotIndex = name.indexOf(".");
if (dotIndex > 0) {
String structName = name.substring(0, dotIndex);
for (ValueDescriptor v : descriptors) {
if (!v.getFields().isEmpty() && v.getName().equals(structName)) {
RecordedObject child = getValue(structName);
String subName = name.substring(dotIndex + 1);
if (child != null) {
return child.getValue(subName, allowUnsigned);
} else {
// Call getValueDescriptor to trigger IllegalArgumentException if the name is
// incorrect. Type can't be validate due to type erasure
getValueDescriptor(v.getFields(), subName, null);
throw new NullPointerException("Field value for \"" + structName + "\" was null. Can't access nested field \"" + subName + "\"");
}
}
}
}
throw new IllegalArgumentException("Could not find field with name " + name);
}
// Returns the leaf value descriptor matches both name or value, or throws an
// IllegalArgumentException
private ValueDescriptor getValueDescriptor(List<ValueDescriptor> descriptors, String name, String leafType) {
int dotIndex = name.indexOf(".");
if (dotIndex > 0) {
String first = name.substring(0, dotIndex);
String second = name.substring(dotIndex + 1);
for (ValueDescriptor v : descriptors) {
if (v.getName().equals(first)) {
List<ValueDescriptor> fields = v.getFields();
if (!fields.isEmpty()) {
return getValueDescriptor(v.getFields(), second, leafType);
}
}
}
throw new IllegalArgumentException("Attempt to get unknown field \"" + first + "\"");
}
for (ValueDescriptor v : descriptors) {
if (v.getName().equals(name)) {
if (leafType != null && !v.getTypeName().equals(leafType)) {
throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal data type conversion " + leafType);
}
return v;
}
}
throw new IllegalArgumentException("\"Attempt to get unknown field \"" + name + "\"");
}
// Gets a value, but checks that type and name is correct first
// This is to prevent a call to getString on a thread field, that is
// null to succeed.
private <T> T getTypedValue(String name, String typeName) {
Objects.requireNonNull(name);
// Validate name and type first
getValueDescriptor(descriptors, name, typeName);
return getValue(name);
}
private Object[] structifyArray(ValueDescriptor v, Object[] array, int dimension) {
if (array == null) {
return null;
}
Object[] structArray = new Object[array.length];
for (int i = 0; i < structArray.length; i++) {
Object arrayElement = array[i];
if (dimension == 0) {
// No general way to handle structarrays
// without invoking ObjectFactory for every instance (which may require id)
if (isStackFrameType(v.getTypeName())) {
structArray[i] = new RecordedFrame(v.getFields(), (Object[]) arrayElement, timeConverter);
} else {
structArray[i] = new RecordedObject(v.getFields(), (Object[]) arrayElement, timeConverter);
}
} else {
structArray[i] = structifyArray(v, (Object[]) arrayElement, dimension - 1);
}
}
return structArray;
}
private boolean isStackFrameType(String typeName) {
if (ObjectFactory.STACK_FRAME_VERSION_1.equals(typeName)) {
return true;
}
if (ObjectFactory.STACK_FRAME_VERSION_2.equals(typeName)) {
return true;
}
return false;
}
/**
* Returns an immutable list of the fields for this object.
*
* @return the fields, not {@code null}
*/
public List<ValueDescriptor> getFields() {
return descriptors;
}
/**
* Returns the value of a field of type {@code boolean}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name name of the field to get, not {@code null}
*
* @return the value of the field, {@code true} or {@code false}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field is
* not of type {@code boolean}
*
* @see #hasField(String)
* @see #getValue(String)
*/
public final boolean getBoolean(String name) {
Object o = getValue(name);
if (o instanceof Boolean) {
return ((Boolean) o).booleanValue();
}
throw newIllegalArgumentException(name, "boolean");
}
/**
* Returns the value of a field of type {@code byte}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "foo.bar"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field
*
* @throws IllegalArgumentException if the field doesn't exist, or the field is
* not of type {@code byte}
*
* @see #hasField(String)
* @see #getValue(String)
*/
public final byte getByte(String name) {
Object o = getValue(name);
if (o instanceof Byte) {
return ((Byte) o).byteValue();
}
throw newIllegalArgumentException(name, "byte");
}
/**
* Returns the value of a field of type {@code char}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field as a {@code char}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field is
* not of type {@code char}
*
* @see #hasField(String)
* @see #getValue(String)
*/
public final char getChar(String name) {
Object o = getValue(name);
if (o instanceof Character) {
return ((Character) o).charValue();
}
throw newIllegalArgumentException(name, "char");
}
/**
* Returns the value of a field of type {@code short} or of another primitive
* type convertible to type {@code short} by a widening conversion.
* <p>
* This method can be used on the following types: {@code short} and {@code byte}.
* <p>
* If the field has the {@code @Unsigned} annotation and is of a narrower type
* than {@code short}, then the value is returned as an unsigned.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field converted to type {@code short}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* value can't be converted to the type {@code short} by a widening
* conversion
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final short getShort(String name) {
Object o = getValue(name, true);
if (o instanceof Short) {
return ((Short) o).shortValue();
}
if (o instanceof Byte) {
return ((Byte) o).byteValue();
}
if (o instanceof UnsignedValue) {
Object u = ((UnsignedValue) o).value();
if (u instanceof Short) {
return ((Short) u).shortValue();
}
if (u instanceof Byte) {
return (short) Byte.toUnsignedInt(((Byte) u));
}
}
throw newIllegalArgumentException(name, "short");
}
/**
* Returns the value of a field of type {@code int} or of another primitive type
* that is convertible to type {@code int} by a widening conversion.
* <p>
* This method can be used on fields of the following types: {@code int},
* {@code short}, {@code char}, and {@code byte}.
* <p>
* If the field has the {@code @Unsigned} annotation and is of a narrower type
* than {@code int}, then the value will be returned as an unsigned.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field converted to type {@code int}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* value can't be converted to the type {@code int} by a widening
* conversion
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final int getInt(String name) {
Object o = getValue(name, true);
if (o instanceof Integer) {
return ((Integer) o).intValue();
}
if (o instanceof Short) {
return ((Short) o).intValue();
}
if (o instanceof Character) {
return ((Character) o).charValue();
}
if (o instanceof Byte) {
return ((Byte) o).intValue();
}
if (o instanceof UnsignedValue) {
Object u = ((UnsignedValue) o).value();
if (u instanceof Integer) {
return ((Integer) u).intValue();
}
if (u instanceof Short) {
return Short.toUnsignedInt(((Short) u));
}
if (u instanceof Byte) {
return Byte.toUnsignedInt(((Byte) u));
}
}
throw newIllegalArgumentException(name, "int");
}
/**
* Returns the value of a field of type {@code float} or of another primitive
* type convertible to type {@code float} by a widening conversion.
* <p>
* This method can be used on fields of the following types: {@code float},
* {@code long}, {@code int}, {@code short}, {@code char}, and {@code byte}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field converted to type {@code float}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* value can't be converted to the type {@code float} by a widening
* conversion
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final float getFloat(String name) {
Object o = getValue(name);
if (o instanceof Float) {
return ((Float) o).floatValue();
}
if (o instanceof Long) {
return ((Long) o).floatValue();
}
if (o instanceof Integer) {
return ((Integer) o).floatValue();
}
if (o instanceof Short) {
return ((Short) o).floatValue();
}
if (o instanceof Byte) {
return ((Byte) o).byteValue();
}
if (o instanceof Character) {
return ((Character) o).charValue();
}
throw newIllegalArgumentException(name, "float");
}
/**
* Returns the value of a field of type {@code long} or of another primitive
* type that is convertible to type {@code long} by a widening conversion.
* <p>
* This method can be used on fields of the following types: {@code long},
* {@code int}, {@code short}, {@code char}, and {@code byte}.
* <p>
* If the field has the {@code @Unsigned} annotation and is of a narrower type
* than {@code long}, then the value will be returned as an unsigned.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field converted to type {@code long}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* value can't be converted to the type {@code long} via a widening
* conversion
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final long getLong(String name) {
Object o = getValue(name, true);
if (o instanceof Long) {
return ((Long) o).longValue();
}
if (o instanceof Integer) {
return ((Integer) o).longValue();
}
if (o instanceof Short) {
return ((Short) o).longValue();
}
if (o instanceof Character) {
return ((Character) o).charValue();
}
if (o instanceof Byte) {
return ((Byte) o).longValue();
}
if (o instanceof UnsignedValue) {
Object u = ((UnsignedValue) o).value();
if (u instanceof Integer) {
return Integer.toUnsignedLong(((Integer) u));
}
if (u instanceof Short) {
return Short.toUnsignedLong(((Short) u));
}
if (u instanceof Byte) {
return Byte.toUnsignedLong(((Byte) u));
}
}
throw newIllegalArgumentException(name, "long");
}
/**
* Returns the value of a field of type {@code double} or of another primitive
* type that is convertible to type {@code double} by a widening conversion.
* <p>
* This method can be used on fields of the following types: {@code double}, {@code float},
* {@code long}, {@code int}, {@code short}, {@code char}, and {@code byte}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field converted to type {@code double}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* value can't be converted to the type {@code double} by a widening
* conversion
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final double getDouble(String name) {
Object o = getValue(name);
if (o instanceof Double) {
return ((Double) o).doubleValue();
}
if (o instanceof Float) {
return ((Float) o).doubleValue();
}
if (o instanceof Long) {
return ((Long) o).doubleValue();
}
if (o instanceof Integer) {
return ((Integer) o).doubleValue();
}
if (o instanceof Short) {
return ((Short) o).doubleValue();
}
if (o instanceof Byte) {
return ((Byte) o).byteValue();
}
if (o instanceof Character) {
return ((Character) o).charValue();
}
throw newIllegalArgumentException(name, "double");
}
/**
* Returns the value of a field of type {@code String}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "foo.bar"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field as a {@code String}, can be {@code null}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* isn't of type {@code String}
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final String getString(String name) {
return getTypedValue(name, "java.lang.String");
}
/**
* Returns the value of a timespan field.
* <p>
* This method can be used on fields annotated with {@code @Timespan}, and of
* the following types: {@code long}, {@code int}, {@code short}, {@code char},
* and {@code byte}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return a time span represented as a {@code Duration}, not {@code null}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* value can't be converted to a {@code Duration} object
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final Duration getDuration(String name) {
Object o = getValue(name);
if (o instanceof Long) {
return getDuration(((Long) o).longValue(), name);
}
if (o instanceof Integer) {
return getDuration(((Integer) o).longValue(), name);
}
if (o instanceof Short) {
return getDuration(((Short) o).longValue(), name);
}
if (o instanceof Character) {
return getDuration(((Character) o).charValue(), name);
}
if (o instanceof Byte) {
return getDuration(((Byte) o).longValue(), name);
}
if (o instanceof UnsignedValue) {
Object u = ((UnsignedValue) o).value();
if (u instanceof Integer) {
return getDuration(Integer.toUnsignedLong((Integer) u), name);
}
if (u instanceof Short) {
return getDuration(Short.toUnsignedLong((Short) u), name);
}
if (u instanceof Byte) {
return getDuration(Short.toUnsignedLong((Byte) u), name);
}
}
throw newIllegalArgumentException(name, "java,time.Duration");
}
private Duration getDuration(long timespan, String name) throws InternalError {
ValueDescriptor v = getValueDescriptor(descriptors, name, null);
if (timespan == Long.MIN_VALUE) {
return Duration.ofSeconds(Long.MIN_VALUE, 0);
}
Timespan ts = v.getAnnotation(Timespan.class);
if (ts != null) {
switch (ts.value()) {
case Timespan.MICROSECONDS:
return Duration.ofNanos(1000 * timespan);
case Timespan.SECONDS:
return Duration.ofSeconds(timespan);
case Timespan.MILLISECONDS:
return Duration.ofMillis(timespan);
case Timespan.NANOSECONDS:
return Duration.ofNanos(timespan);
case Timespan.TICKS:
return Duration.ofNanos(timeConverter.convertTimespan(timespan));
}
throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timespan unit " + ts.value());
}
throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with missing @Timespan");
}
/**
* Returns the value of a timestamp field.
* <p>
* This method can be used on fields annotated with {@code @Timestamp}, and of
* the following types: {@code long}, {@code int}, {@code short}, {@code char}
* and {@code byte}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return a timstamp represented as an {@code Instant}, not {@code null}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* value can't be converted to an {@code Instant} object
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final Instant getInstant(String name) {
Object o = getValue(name, true);
if (o instanceof Long) {
return getInstant(((Long) o).longValue(), name);
}
if (o instanceof Integer) {
return getInstant(((Integer) o).longValue(), name);
}
if (o instanceof Short) {
return getInstant(((Short) o).longValue(), name);
}
if (o instanceof Character) {
return getInstant(((Character) o).charValue(), name);
}
if (o instanceof Byte) {
return getInstant(((Byte) o).longValue(), name);
}
if (o instanceof UnsignedValue) {
Object u = ((UnsignedValue) o).value();
if (u instanceof Integer) {
return getInstant(Integer.toUnsignedLong((Integer) u), name);
}
if (u instanceof Short) {
return getInstant(Short.toUnsignedLong((Short) u), name);
}
if (u instanceof Byte) {
return getInstant(Short.toUnsignedLong((Byte) u), name);
}
}
throw newIllegalArgumentException(name, "java.time.Instant");
}
private Instant getInstant(long timestamp, String name) {
ValueDescriptor v = getValueDescriptor(descriptors, name, null);
Timestamp ts = v.getAnnotation(Timestamp.class);
if (ts != null) {
if (timestamp == Long.MIN_VALUE) {
return Instant.MIN;
}
switch (ts.value()) {
case Timestamp.MILLISECONDS_SINCE_EPOCH:
return Instant.ofEpochMilli(timestamp);
case Timestamp.TICKS:
return Instant.ofEpochSecond(0, timeConverter.convertTimestamp(timestamp));
}
throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timestamp unit " + ts.value());
}
throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with missing @Timestamp");
}
/**
* Returns the value of a field of type {@code Class}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "aaa.bbb"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field as a {@code RecordedClass}, can be
* {@code null}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* isn't of type {@code Class}
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final RecordedClass getClass(String name) {
return getTypedValue(name, "java.lang.Class");
}
/**
* Returns the value of a field of type {@code Thread}.
* <p>
* It's possible to index into a nested object using {@code "."} (for example,
* {@code "foo.bar"}).
* <p>
* A field might change or be removed in a future JDK release. A best practice
* for callers of this method is to validate the field before attempting access.
*
* @param name of the field to get, not {@code null}
*
* @return the value of the field as a {@code RecordedThread} object, can be
* {@code null}
*
* @throws IllegalArgumentException if the field doesn't exist, or the field
* isn't of type {@code Thread}
*
* @see #hasField(String)
* @set #getValue(String)
*/
public final RecordedThread getThread(String name) {
return getTypedValue(name, "java.lang.Thread");
}
/**
* Returns a textual representation of this object.
*
* @return textual description of this object
*/
@Override
final public String toString() {
StringWriter s = new StringWriter();
PrettyWriter p = new PrettyWriter(new PrintWriter(s));
p.setStackDepth(5);
if (this instanceof RecordedEvent) {
p.print((RecordedEvent) this);
} else {
p.print(this, "");
}
p.flush(true);
return s.toString();
}
// package private for now. Used by EventWriter
OffsetDateTime getOffsetDateTime(String name) {
Instant instant = getInstant(name);
if (instant.equals(Instant.MIN)) {
return OffsetDateTime.MIN;
}
return OffsetDateTime.ofInstant(getInstant(name), timeConverter.getZoneOffset());
}
private static IllegalArgumentException newIllegalArgumentException(String name, String typeName) {
return new IllegalArgumentException("Attempt to get field \"" + name + "\" with illegal data type conversion " + typeName);
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.consumer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/**
* A recorded stack trace.
*
* @since 8
*/
public final class RecordedStackTrace extends RecordedObject {
static ObjectFactory<RecordedStackTrace> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedStackTrace>(type) {
@Override
RecordedStackTrace createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedStackTrace(desc, object, timeConverter);
}
};
}
private RecordedStackTrace(List<ValueDescriptor> desc, Object[] values, TimeConverter timeConverter) {
super(desc, values, timeConverter);
}
/**
* Returns the frames in the stack trace.
*
* @return a list of Java stack frames, not {@code null}
*/
@SuppressWarnings("unchecked")
public List<RecordedFrame> getFrames() {
Object[] array = getTyped("frames", Object[].class, null);
if (array == null) {
return Collections.EMPTY_LIST;
}
List<?> list = Arrays.asList(array);
return (List<RecordedFrame>) list;
}
/**
* Returns {@code true} if the stack trace is truncated due to its size,
* {@code false} otherwise.
*
* @return {@code true} if the stack trace is truncated, {@code false}
* otherwise
*/
public boolean isTruncated() {
return getTyped("truncated", Boolean.class, true);
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.consumer;
import java.util.List;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/**
* A recorded thread.
*
* @since 8
*/
public final class RecordedThread extends RecordedObject {
static ObjectFactory<RecordedThread> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedThread>(type) {
@Override
RecordedThread createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedThread(desc, id, object, timeConverter);
}
};
}
private final long uniqueId;
private RecordedThread(List<ValueDescriptor> descriptors, long id, Object[] values, TimeConverter timeConverter) {
super(descriptors, values, timeConverter);
this.uniqueId = id;
}
/**
* Returns the thread name used by the operating system.
*
* @return the OS thread name, or {@code null} if doesn't exist
*/
public String getOSName() {
return getTyped("osName", String.class, null);
}
/**
* Returns the thread ID used by the operating system.
*
* @return The Java thread ID, or {@code -1} if doesn't exist
*/
public long getOSThreadId() {
Long l = getTyped("osThreadId", Long.class, -1L);
return l.longValue();
}
/**
* Returns the Java thread group, if available.
*
* @return the thread group, or {@code null} if doesn't exist
*/
public RecordedThreadGroup getThreadGroup() {
return getTyped("group", RecordedThreadGroup.class, null);
}
/**
* Returns the Java thread name, or {@code null} if if doesn't exist.
* <p>
* Returns {@code java.lang.Thread.getName()} if the thread has a Java
* representation. {@code null} otherwise.
*
* @return the Java thread name, or {@code null} if doesn't exist
*/
public String getJavaName() {
return getTyped("javaName", String.class, null);
}
/**
* Returns the Java thread ID, or {@code -1} if it's not a Java thread.
*
* @return the Java thread ID, or {@code -1} if it's not a Java thread
*/
public long getJavaThreadId() {
Long l = getTyped("javaThreadId", Long.class, -1L);
return l.longValue();
}
/**
* Returns a unique ID for both native threads and Java threads that can't be
* reused within the lifespan of the JVM.
* <p>
* See {@link #getJavaThreadId()} for the ID that is returned by
* {@code java.lang.Thread.getId()}
*
* @return a unique ID for the thread
*/
public long getId() {
return uniqueId;
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.consumer;
import java.util.List;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/**
* A recorded Java thread group.
*
* @since 8
*/
public final class RecordedThreadGroup extends RecordedObject {
static ObjectFactory<RecordedThreadGroup> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedThreadGroup>(type) {
@Override
RecordedThreadGroup createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedThreadGroup(desc, object, timeConverter);
}
};
}
private RecordedThreadGroup(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
super(descriptors, objects, timeConverter);
}
/**
* Returns the name of the thread group, or {@code null} if doesn't exist.
*
* @return the thread group name, or {@code null} if doesn't exist
*/
public String getName() {
return getTyped("name", String.class, null);
}
/**
* Returns the parent thread group, or {@code null} if it doesn't exist.
*
* @return parent thread group, or {@code null} if it doesn't exist.
*/
public RecordedThreadGroup getParent() {
return getTyped("parent", RecordedThreadGroup.class, null);
}
}

View File

@@ -0,0 +1,269 @@
/*
* 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.consumer;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
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;
import jdk.jfr.internal.consumer.RecordingInternals;
/**
* A recording file.
* <p>
* The following example shows how read and print all events in a recording file.
*
* <pre>
* <code>
* try (RecordingFile recordingFile = new RecordingFile(Paths.get("recording.jfr"))) {
* while (recordingFile.hasMoreEvents()) {
* RecordedEvent event = recordingFile.readEvent();
* System.out.println(event);
* }
* }
* </code>
* </pre>
*
* @since 8
*/
public final class RecordingFile implements Closeable {
static{
RecordingInternals.INSTANCE = new RecordingInternals() {
public List<Type> readTypes(RecordingFile file) throws IOException {
return file.readTypes();
}
public boolean isLastEventInChunk(RecordingFile file) {
return file.isLastEventInChunk;
}
@Override
public Object getOffsetDataTime(RecordedObject event, String name) {
return event.getOffsetDateTime(name);
}
@Override
public void sort(List<RecordedEvent> events) {
Collections.sort(events, (e1, e2) -> Long.compare(e1.endTime, e2.endTime));
}
};
}
private boolean isLastEventInChunk;
private final File file;
private RecordingInput input;
private ChunkParser chunkParser;
private RecordedEvent nextEvent;
private boolean eof;
/**
* Creates a recording file.
*
* @param file the path of the file to open, not {@code null}
* @throws IOException if it's not a valid recording file, or an I/O error
* occurred
* @throws NoSuchFileException if the {@code file} can't be located
*
* @throws SecurityException if a security manager exists and its
* {@code checkRead} method denies read access to the file.
*/
public RecordingFile(Path file) throws IOException {
this.file = file.toFile();
this.input = new RecordingInput(this.file);
findNext();
}
/**
* Reads the next event in the recording.
*
* @return the next event, not {@code null}
*
* @throws EOFException if no more events exist in the recording file
* @throws IOException if an I/O error occurs.
*
* @see #hasMoreEvents()
*/
public RecordedEvent readEvent() throws IOException {
if (eof) {
ensureOpen();
throw new EOFException();
}
isLastEventInChunk = false;
RecordedEvent event = nextEvent;
nextEvent = chunkParser.readEvent();
if (nextEvent == null) {
isLastEventInChunk = true;
findNext();
}
return event;
}
/**
* Returns {@code true} if unread events exist in the recording file,
* {@code false} otherwise.
*
* @return {@code true} if unread events exist in the recording, {@code false}
* otherwise.
*/
public boolean hasMoreEvents() {
return !eof;
}
/**
* Returns a list of all event types in this recording.
*
* @return a list of event types, not {@code null}
* @throws IOException if an I/O error occurred while reading from the file
*
* @see #hasMoreEvents()
*/
public List<EventType> readEventTypes() throws IOException {
ensureOpen();
List<EventType> types = new ArrayList<>();
HashSet<Long> foundIds = new HashSet<>();
try (RecordingInput ri = new RecordingInput(file)) {
ChunkHeader ch = new ChunkHeader(ri);
aggregateEventTypeForChunk(ch, types, foundIds);
while (!ch.isLastChunk()) {
ch = ch.nextHeader();
aggregateEventTypeForChunk(ch, types, foundIds);
}
}
return types;
}
List<Type> readTypes() throws IOException {
ensureOpen();
List<Type> types = new ArrayList<>();
HashSet<Long> foundIds = new HashSet<>();
try (RecordingInput ri = new RecordingInput(file)) {
ChunkHeader ch = new ChunkHeader(ri);
aggregateTypeForChunk(ch, types, foundIds);
while (!ch.isLastChunk()) {
ch = ch.nextHeader();
aggregateTypeForChunk(ch, types, foundIds);
}
}
return types;
}
private void aggregateTypeForChunk(ChunkHeader ch, List<Type> types, HashSet<Long> foundIds) throws IOException {
MetadataDescriptor m = ch.readMetadata();
for (Type t : m.getTypes()) {
if (!foundIds.contains(t.getId())) {
types.add(t);
foundIds.add(t.getId());
}
}
}
private static void aggregateEventTypeForChunk(ChunkHeader ch, List<EventType> types, HashSet<Long> foundIds) throws IOException {
MetadataDescriptor m = ch.readMetadata();
for (EventType t : m.getEventTypes()) {
if (!foundIds.contains(t.getId())) {
types.add(t);
foundIds.add(t.getId());
}
}
}
/**
* Closes this recording file and releases any system resources that are
* associated with it.
*
* @throws IOException if an I/O error occurred
*/
public void close() throws IOException {
if (input != null) {
eof = true;
input.close();
chunkParser = null;
input = null;
nextEvent = null;
}
}
/**
* Returns a list of all events in a file.
* <p>
* This method is intended for simple cases where it's convenient to read all
* events in a single operation. It isn't intended for reading large files.
*
* @param path the path to the file, not {@code null}
*
* @return the events from the file as a {@code List} object; whether the
* {@code List} is modifiable or not is implementation dependent and
* therefore not specified, not {@code null}
*
* @throws IOException if an I/O error occurred, it's not a Flight Recorder
* file or a version of a JFR file that can't be parsed
*
* @throws SecurityException if a security manager exists and its
* {@code checkRead} method denies read access to the file.
*/
public static List<RecordedEvent> readAllEvents(Path path) throws IOException {
try (RecordingFile r = new RecordingFile(path)) {
List<RecordedEvent> list = new ArrayList<>();
while (r.hasMoreEvents()) {
list.add(r.readEvent());
}
return list;
}
}
// either sets next to an event or sets eof to true
private void findNext() throws IOException {
while (nextEvent == null) {
if (chunkParser == null) {
chunkParser = new ChunkParser(input);
} else if (!chunkParser.isLastChunk()) {
chunkParser = chunkParser.nextChunkParser();
} else {
eof = true;
return;
}
nextEvent = chunkParser.readEvent();
}
}
private void ensureOpen() throws IOException {
if (input == null) {
throw new IOException("Stream Closed");
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.consumer;
import java.time.DateTimeException;
import java.time.ZoneOffset;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.consumer.ChunkHeader;
/**
* Converts ticks to nanoseconds
*/
final class TimeConverter {
private final long startTicks;
private final long startNanos;
private final double divisor;
private final ZoneOffset zoneOffet;
TimeConverter(ChunkHeader chunkHeader, int rawOffset) {
this.startTicks = chunkHeader.getStartTicks();
this.startNanos = chunkHeader.getStartNanos();
this.divisor = chunkHeader.getTicksPerSecond() / 1000_000_000L;
this.zoneOffet = zoneOfSet(rawOffset);
}
private ZoneOffset zoneOfSet(int rawOffset) {
try {
return ZoneOffset.ofTotalSeconds(rawOffset / 1000);
} catch (DateTimeException dte) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Could not create ZoneOffset from raw offset " + rawOffset);
}
return ZoneOffset.UTC;
}
public long convertTimestamp(long ticks) {
return startNanos + (long) ((ticks - startTicks) / divisor);
}
public long convertTimespan(long ticks) {
return (long) (ticks / divisor);
}
public ZoneOffset getZoneOffset() {
return zoneOffet;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2017, 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 consuming Flight Recorder data.
* <p>
* In the following example, the program prints a histogram of all method samples in a recording.
* <pre>
* <code>
* public static void main(String[] args) {
* if (args.length != 0) {
* System.out.println("Must specify recording file.");
* return;
* }
* try (RecordingFile f = new RecordingFile(Paths.get(args[0]))) {
* Map{@literal <}String, SimpleEntry{@literal <}String, Integer{@literal >}{@literal >} histogram = new HashMap{@literal <}{@literal >}();
* int total = 0;
* while (f.hasMoreEvents()) {
* RecordedEvent event = f.readEvent();
* if (event.getEventType().getName().equals("jdk.ExecutionSample")) {
* RecordedStackTrace s = event.getStackTrace();
* if (s != null) {
* RecordedFrame topFrame= s.getFrames().get(0);
* if (topFrame.isJavaFrame()) {
* RecordedMethod method = topFrame.getMethod();
* String methodName = method.getType().getName() + "#" + method.getName() + " " + method.getDescriptor();
* Entry entry = histogram.computeIfAbsent(methodName, u -{@literal >} new SimpleEntry{@literal <}String, Integer{@literal >}(methodName, 0));
* entry.setValue(entry.getValue() + 1);
* total++;
* }
* }
* }
* }
* List{@literal <}SimpleEntry{@literal <}String, Integer{@literal >}{@literal >} entries = new ArrayList{@literal <}{@literal >}(histogram.values());
* entries.sort((u, v) -{@literal >} v.getValue().compareTo(u.getValue()));
* for (SimpleEntry{@literal <}String, Integer{@literal >} c : entries) {
* System.out.printf("%2.0f%% %s\n", 100 * (float) c.getValue() / total, c.getKey());
* }
* System.out.println("\nSample count: " + total);
* } catch (IOException ioe) {
* System.out.println("Error reading file " + args[0] + ". " + ioe.getMessage());
* }
* }
* </code>
* </pre>
* <p>
* <b>Null-handling</b>
* <p>
* All methods define whether they accept or return {@code null} in the Javadoc.
* Typically this is expressed as {@code "not null"}. If a {@code null}
* parameter is used where it is not allowed, a
* {@code java.lang.NullPointerException} is thrown. If a {@code null}
* parameters is passed to a method that throws other exceptions, such as
* {@code java.io.IOException}, the {@code java.lang.NullPointerException} takes
* precedence, unless the Javadoc for the method explicitly states how
* {@code null} is handled, i.e. by throwing {@code java.lang.IllegalArgumentException}.
*
* @since 8
*/
package jdk.jfr.consumer;

View File

@@ -0,0 +1,37 @@
/*
* 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.events;
import jdk.jfr.Enabled;
import jdk.jfr.Event;
import jdk.jfr.Registered;
import jdk.jfr.StackTrace;
@Registered(false)
@Enabled(false)
@StackTrace(false)
abstract class AbstractJDKEvent extends Event {
}

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.events;
import jdk.jfr.Category;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
import jdk.jfr.Name;
import jdk.jfr.StackTrace;
import jdk.jfr.Timespan;
import jdk.jfr.Timestamp;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "ActiveRecording")
@Label("Flight Recording")
@Category("Flight Recorder")
@StackTrace(false)
public final class ActiveRecordingEvent extends AbstractJDKEvent {
@Label("Id")
public long id;
@Label("Name")
public String name;
@Label("Destination")
public String destination;
@Label("Max Age")
@Timespan(Timespan.MILLISECONDS)
public long maxAge;
@Label("Max Size")
@DataAmount
public long maxSize;
@Label("Start Time")
@Timestamp(Timestamp.MILLISECONDS_SINCE_EPOCH)
public long recordingStart;
@Label("Recording Duration")
@Timespan(Timespan.MILLISECONDS)
public long recordingDuration;
}

View File

@@ -0,0 +1,48 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.StackTrace;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "ActiveSetting")
@Label("Recording Setting")
@Category("Flight Recorder")
@StackTrace(false)
public final class ActiveSettingEvent extends AbstractJDKEvent {
@Label("Event Id")
public long id;
@Label("Setting Name")
public String name;
@Label("Setting Value")
public String value;
}

View File

@@ -0,0 +1,44 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "JavaErrorThrow")
@Label("Java Error")
@Category("Java Application")
@Description("An object derived from java.lang.Error has been created. OutOfMemoryErrors are ignored")
public final class ErrorThrownEvent extends AbstractJDKEvent {
@Label("Message")
public String message;
@Label("Class")
public Class<?> thrownClass;
}

View File

@@ -0,0 +1,44 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.StackTrace;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "ExceptionStatistics")
@Label("Exception Statistics")
@Category({ "Java Application", "Statistics" })
@Description("Number of objects derived from java.lang.Throwable that have been created")
@StackTrace(false)
public final class ExceptionStatisticsEvent extends AbstractJDKEvent {
@Label("Exceptions Created")
public long throwables;
}

View File

@@ -0,0 +1,45 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "JavaExceptionThrow")
@Label("Java Exception")
@Category("Java Application")
@Description("An object derived from java.lang.Exception has been created")
public final class ExceptionThrownEvent extends AbstractJDKEvent {
@Label("Message")
public String message;
@Label("Class")
public Class<?> thrownClass;
}

View File

@@ -0,0 +1,59 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "FileForce")
@Label("File Force")
@Category("Java Application")
@Description("Force updates to be written to file")
public final class FileForceEvent extends AbstractJDKEvent {
public static final ThreadLocal<FileForceEvent> EVENT =
new ThreadLocal<FileForceEvent>() {
@Override protected FileForceEvent initialValue() {
return new FileForceEvent();
}
};
@Label("Path")
@Description("Full path of the file")
public String path;
@Label("Update Metadata")
@Description("Whether the file metadata is updated")
public boolean metaData;
public void reset() {
path = null;
metaData = false;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
import jdk.jfr.Name;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "FileRead")
@Label("File Read")
@Category("Java Application")
@Description("Reading data from a file")
public final class FileReadEvent extends AbstractJDKEvent {
public static final ThreadLocal<FileReadEvent> EVENT =
new ThreadLocal<FileReadEvent>() {
@Override protected FileReadEvent initialValue() {
return new FileReadEvent();
}
};
@Label("Path")
@Description("Full path of the file")
public String path;
@Label("Bytes Read")
@Description("Number of bytes read from the file (possibly 0)")
@DataAmount
public long bytesRead;
@Label("End of File")
@Description("If end of file was reached")
public boolean endOfFile;
public void reset() {
path = null;
endOfFile = false;
bytesRead = 0;
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
import jdk.jfr.Name;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "FileWrite")
@Label("File Write")
@Category("Java Application")
@Description("Writing data to a file")
public final class FileWriteEvent extends AbstractJDKEvent {
public static final ThreadLocal<FileWriteEvent> EVENT =
new ThreadLocal<FileWriteEvent>() {
@Override protected FileWriteEvent initialValue() {
return new FileWriteEvent();
}
};
@Label("Path")
@Description("Full path of the file")
public String path;
@Label("Bytes Written")
@Description("Number of bytes written to the file")
@DataAmount
public long bytesWritten;
public void reset() {
path = null;
bytesWritten = 0;
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
import jdk.jfr.Name;
import jdk.jfr.Timespan;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "SocketRead")
@Label("Socket Read")
@Category("Java Application")
@Description("Reading data from a socket")
public final class SocketReadEvent extends AbstractJDKEvent {
public static final ThreadLocal<SocketReadEvent> EVENT =
new ThreadLocal<SocketReadEvent>() {
@Override protected SocketReadEvent initialValue() {
return new SocketReadEvent();
}
};
@Label("Remote Host")
public String host;
@Label("Remote Address")
public String address;
@Label("Remote Port")
public int port;
@Label("Timeout Value")
@Timespan(Timespan.MILLISECONDS)
public long timeout;
@Label("Bytes Read")
@Description("Number of bytes read from the socket")
@DataAmount
public long bytesRead;
@Label("End of Stream")
@Description("If end of stream was reached")
public boolean endOfStream;
public void reset() {
host = null;
address = null;
port = 0;
timeout = 0;
bytesRead = 0L;
endOfStream = false;
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.events;
import jdk.jfr.Category;
import jdk.jfr.Description;
import jdk.jfr.Label;
import jdk.jfr.DataAmount;
import jdk.jfr.Name;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "SocketWrite")
@Label("Socket Write")
@Category("Java Application")
@Description("Writing data to a socket")
public final class SocketWriteEvent extends AbstractJDKEvent {
public static final ThreadLocal<SocketWriteEvent> EVENT =
new ThreadLocal<SocketWriteEvent>() {
@Override protected SocketWriteEvent initialValue() {
return new SocketWriteEvent();
}
};
@Label("Remote Host")
public String host;
@Label("Remote Address")
public String address;
@Label("Remote Port")
public int port;
@Label("Bytes Written")
@Description("Number of bytes written to the socket")
@DataAmount
public long bytesWritten;
public void reset() {
host = null;
address = null;
port = 0;
bytesWritten = 0;
}
}

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;
}
}

Some files were not shown because too many files have changed in this diff Show More