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