--- /dev/null 2017-11-09 09:38:01.297999907 +0100 +++ new/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java 2018-04-09 16:06:16.795245384 +0200 @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jfr.internal; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import jdk.jfr.AnnotationElement; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.MetadataDefinition; +import jdk.jfr.Name; +import jdk.jfr.SettingDescriptor; +import jdk.jfr.Timespan; +import jdk.jfr.Timestamp; +import jdk.jfr.ValueDescriptor; + +public final class TypeLibrary { + + private static TypeLibrary instance; + private static final Map types = new LinkedHashMap<>(100); + static final ValueDescriptor DURATION_FIELD = createDurationField(); + static final ValueDescriptor THREAD_FIELD = createThreadField(); + static final ValueDescriptor STACK_TRACE_FIELD = createStackTraceField(); + static final ValueDescriptor START_TIME_FIELD = createStartTimeField(); + + private TypeLibrary(List jvmTypes) { + visitReachable(jvmTypes, t -> !types.containsKey(t.getId()), t -> types.put(t.getId(), t)); + if (LogTag.JFR_SYSTEM_METADATA.shouldLog(LogLevel.INFO.level)) { + Stream s = types.values().stream().sorted((x, y) -> Long.compare(x.getId(), y.getId())); + s.forEach(t -> t.log("Added", LogTag.JFR_SYSTEM_METADATA, LogLevel.INFO)); + } + } + + private static ValueDescriptor createStartTimeField() { + List annos = createStandardAnnotations("Start Time", null); + annos.add(new jdk.jfr.AnnotationElement(Timestamp.class, Timestamp.TICKS)); + return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_START_TIME, Type.LONG, annos, 0, false, + EventInstrumentation.FIELD_START_TIME); + + } + + private static ValueDescriptor createStackTraceField() { + List annos = new ArrayList<>(); + annos = createStandardAnnotations("Stack Trace", "Stack Trace starting from the method the event was committed in"); + return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_STACK_TRACE, Type.STACK_TRACE, annos, 0, true, + EventInstrumentation.FIELD_STACK_TRACE); + } + + private static ValueDescriptor createThreadField() { + List annos = new ArrayList<>(); + annos = createStandardAnnotations("Event Thread", "Thread in which event was committed in"); + return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_EVENT_THREAD, Type.THREAD, annos, 0, true, + EventInstrumentation.FIELD_EVENT_THREAD); + } + + private static ValueDescriptor createDurationField() { + List annos = new ArrayList<>(); + annos = createStandardAnnotations("Duration", null); + annos.add(new jdk.jfr.AnnotationElement(Timespan.class, Timespan.TICKS)); + return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_DURATION, Type.LONG, annos, 0, false, EventInstrumentation.FIELD_DURATION); + } + + public static TypeLibrary getInstance() { + synchronized (TypeLibrary.class) { + if (instance == null) { + List jvmTypes; + try { + jvmTypes = TraceHandler.createTypes(); + } catch (IOException e) { + throw new Error("JFR: Could not read metadata"); + } + instance = new TypeLibrary(jvmTypes); + } + return instance; + } + } + + public List getTypes() { + return new ArrayList<>(types.values()); + } + + public static Type createAnnotationType(Class a) { + if (shouldPersist(a)) { + Type type = defineType(a, Type.SUPER_TYPE_ANNOTATION, false); + if (type != null) { + SecuritySupport.makeVisibleToJFR(a); + for (Method method : a.getDeclaredMethods()) { + type.add(PrivateAccess.getInstance().newValueDescriptor(method.getReturnType(), method.getName())); + } + ArrayList aes = new ArrayList<>(); + for (Annotation annotation : resolveRepeatedAnnotations(a.getAnnotations())) { + AnnotationElement ae = createAnnotation(annotation); + if (ae != null) { + aes.add(ae); + } + } + aes.trimToSize(); + type.setAnnotations(aes); + } + return getType(a); + } + return null; + } + + static AnnotationElement createAnnotation(Annotation annotation) { + Class annotationType = annotation.annotationType(); + Type type = createAnnotationType(annotationType); + if (type != null) { + List values = new ArrayList<>(); + for (ValueDescriptor v : type.getFields()) { + values.add(invokeAnnotation(annotation, v.getName())); + } + + return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null); + } + return null; + } + + private static Object invokeAnnotation(Annotation annotation, String methodName) { + final Method m; + try { + m = annotation.getClass().getMethod(methodName, new Class[0]); + } catch (NoSuchMethodException e1) { + throw (Error) new InternalError("Could not loacate method " + methodName + " in annotation " + annotation.getClass().getName()); + } + SecuritySupport.setAccessible(m); + try { + return m.invoke(annotation, new Object[0]); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw (Error) new InternalError("Could not get value for method " + methodName + " in annotation " + annotation.getClass().getName()); + } + } + + private static boolean shouldPersist(Class a) { + if (a == MetadataDefinition.class || a.getAnnotation(MetadataDefinition.class) == null) { + return false; + } + return true; + } + + private static boolean isDefined(Class clazz) { + return types.containsKey(Type.getTypeId(clazz)); + } + + private static Type getType(Class clazz) { + return types.get(Type.getTypeId(clazz)); + } + + private static Type defineType(Class clazz, String superType, boolean eventType) { + if (!isDefined(clazz)) { + Name name = clazz.getAnnotation(Name.class); + String typeName = name != null ? name.value() : clazz.getName(); + long id = Type.getTypeId(clazz); + Type t; + if (eventType) { + t = new PlatformEventType(typeName, id, clazz.getClassLoader() == null, true); + } else { + t = new Type(typeName, superType, id); + } + types.put(t.getId(), t); + return t; + } + return null; + } + public static Type createType(Class clazz) { + return createType(clazz, Collections.emptyList(), Collections.emptyList()); + } + + public static Type createType(Class clazz, List dynamicAnnotations, List dynamicFields) { + + if (Thread.class == clazz) { + return Type.THREAD; + } + + if (Class.class.isAssignableFrom(clazz)) { + return Type.CLASS; + } + + if (String.class.equals(clazz)) { + return Type.STRING; + } + + if (isDefined(clazz)) { + return getType(clazz); + } + + if (clazz.isPrimitive()) { + return defineType(clazz, null,false); + } + + if (clazz.isArray()) { + throw new InternalError("Arrays not supported"); + } + + // STRUCT + String superType = null; + boolean eventType = false; + if (Event.class.isAssignableFrom(clazz)) { + superType = Type.SUPER_TYPE_EVENT; + eventType= true; + } + if (Control.class.isAssignableFrom(clazz)) { + superType = Type.SUPER_TYPE_SETTING; + } + + // forward declare to avoid infinite recursion + defineType(clazz, superType, eventType); + Type type = getType(clazz); + + if (eventType) { + addImplicitFields(type, true, true, true, true ,false); + addUserFields(clazz, type, dynamicFields); + type.trimFields(); + } + addAnnotations(clazz, type, dynamicAnnotations); + + if (clazz.getClassLoader() == null) { + type.log("Added", LogTag.JFR_SYSTEM_METADATA, LogLevel.INFO); + } else { + type.log("Added", LogTag.JFR_METADATA, LogLevel.INFO); + } + return type; + } + + private static void addAnnotations(Class clazz, Type type, List dynamicAnnotations) { + ArrayList aes = new ArrayList<>(); + if (dynamicAnnotations.isEmpty()) { + for (Annotation a : Utils.getAnnotations(clazz)) { + AnnotationElement ae = createAnnotation(a); + if (ae != null) { + aes.add(ae); + } + } + } else { + List newTypes = new ArrayList<>(); + aes.addAll(dynamicAnnotations); + for (AnnotationElement ae : dynamicAnnotations) { + newTypes.add(PrivateAccess.getInstance().getType(ae)); + } + addTypes(newTypes); + } + type.setAnnotations(aes); + aes.trimToSize(); + } + + private static void addUserFields(Class clazz, Type type, List dynamicFields) { + Map dynamicFieldSet = new HashMap<>(); + for (ValueDescriptor dynamicField : dynamicFields) { + dynamicFieldSet.put(dynamicField.getName(), dynamicField); + } + List newTypes = new ArrayList<>(); + for (Field field : Utils.getVisibleEventFields(clazz)) { + ValueDescriptor vd = dynamicFieldSet.get(field.getName()); + if (vd != null) { + if (!vd.getTypeName().equals(field.getType().getName())) { + throw new InternalError("Type expected to match for field " + vd.getName() + " expected " + field.getName() + " but got " + vd.getName()); + } + for (AnnotationElement ae : vd.getAnnotationElements()) { + newTypes.add(PrivateAccess.getInstance().getType(ae)); + } + newTypes.add(PrivateAccess.getInstance().getType(vd)); + } else { + vd = createField(field); + } + if (vd != null) { + type.add(vd); + } + } + addTypes(newTypes); + } + + // By convention all events have these fields. + static void addImplicitFields(Type type, boolean requestable, boolean hasDuration, boolean hasThread, boolean hasStackTrace, boolean hasCutoff) { + createAnnotationType(Timespan.class); + createAnnotationType(Timestamp.class); + createAnnotationType(Label.class); + defineType(long.class, null,false); + addFields(type, requestable, hasDuration, hasThread, hasStackTrace, hasCutoff); + } + + private static void addFields(Type type, boolean requestable, boolean hasDuration, boolean hasThread, boolean hasStackTrace, boolean hasCutoff) { + type.add(START_TIME_FIELD); + if (hasDuration || hasCutoff) { + type.add(DURATION_FIELD); + } + if (hasThread) { + type.add(THREAD_FIELD); + } + if (hasStackTrace) { + type.add(STACK_TRACE_FIELD); + } + } + + private static List createStandardAnnotations(String name, String description) { + List annotationElements = new ArrayList<>(2); + annotationElements.add(new jdk.jfr.AnnotationElement(Label.class, name)); + if (description != null) { + annotationElements.add(new jdk.jfr.AnnotationElement(Description.class, description)); + } + return annotationElements; + } + + private static ValueDescriptor createField(Field field) { + int mod = field.getModifiers(); + if (Modifier.isTransient(mod)) { + return null; + } + if (Modifier.isStatic(mod)) { + return null; + } + Class fieldType = field.getType(); + if (!Type.isKnownType(fieldType)) { + return null; + } + boolean constantPool = Thread.class == fieldType || fieldType == Class.class; + Type type = createType(fieldType); + String fieldName = field.getName(); + Name name = field.getAnnotation(Name.class); + String useName = fieldName; + if (name != null) { + useName = name.value(); + } + List ans = new ArrayList<>(); + for (Annotation a : resolveRepeatedAnnotations(field.getAnnotations())) { + AnnotationElement ae = createAnnotation(a); + if (ae != null) { + ans.add(ae); + } + } + return PrivateAccess.getInstance().newValueDescriptor(useName, type, ans, 0, constantPool, fieldName); + } + + private static List resolveRepeatedAnnotations(Annotation[] annotations) { + List annos = new ArrayList<>(annotations.length); + for (Annotation a : annotations) { + boolean repeated = false; + Method m; + try { + m = a.annotationType().getMethod("value"); + Class returnType = m.getReturnType(); + if (returnType.isArray()) { + Class ct = returnType.getComponentType(); + if (Annotation.class.isAssignableFrom(ct) && ct.getAnnotation(Repeatable.class) != null) { + Object res = m.invoke(a, new Object[0]); + if (res != null && Annotation[].class.isAssignableFrom(res.getClass())) { + for (Annotation rep : (Annotation[]) m.invoke(a, new Object[0])) { + annos.add(rep); + } + repeated = true; + } + } + } + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // Ignore, can't access repeatable information + } + if (!repeated) { + annos.add(a); + } + } + return annos; + } + + // Purpose of this method is to mark types that are reachable + // from registered event types. Those types that are not reachable can + // safely be removed + public boolean clearUnregistered() { + Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Cleaning out obsolete metadata"); + List registered = new ArrayList<>(); + for (Type type : types.values()) { + if (type instanceof PlatformEventType) { + if (((PlatformEventType) type).isRegistered()) { + registered.add(type); + } + } + } + visitReachable(registered, t -> t.getRemove(), t -> t.setRemove(false)); + List removeIds = new ArrayList<>(); + for (Type type : types.values()) { + if (type.getRemove() && !Type.isDefinedByJVM(type.getId())) { + removeIds.add(type.getId()); + if (LogTag.JFR_METADATA.shouldLog(LogLevel.TRACE.level)) { + Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Removed obsolete metadata " + type.getName()); + } + } + // Optimization, set to true now to avoid iterating + // types first thing at next call to clearUnregistered + type.setRemove(true); + } + for (Long id : removeIds) { + types.remove(id); + } + return !removeIds.isEmpty(); + } + + public void addType(Type type) { + addTypes(Collections.singletonList(type)); + } + + public static void addTypes(List ts) { + if (!ts.isEmpty()) { + visitReachable(ts, t -> !types.containsKey(t.getId()), t -> types.put(t.getId(), t)); + } + } + + /** + * Iterates all reachable types from a start collection + * + * @param rootSet the types to start from + * @param p if a type should be accepted + * @param c action to take on an accepted type + */ + private static void visitReachable(Collection rootSet, Predicate p, Consumer c) { + Queue typeQ = new ArrayDeque<>(rootSet); + while (!typeQ.isEmpty()) { + Type type = typeQ.poll(); + if (p.test(type)) { + c.accept(type); + visitAnnotations(typeQ, type.getAnnotationElements()); + for (ValueDescriptor v : type.getFields()) { + typeQ.add(PrivateAccess.getInstance().getType(v)); + visitAnnotations(typeQ, v.getAnnotationElements()); + } + if (type instanceof PlatformEventType) { + PlatformEventType pe = (PlatformEventType) type; + for (SettingDescriptor s : pe.getAllSettings()) { + typeQ.add(PrivateAccess.getInstance().getType(s)); + visitAnnotations(typeQ, s.getAnnotationElements()); + } + } + } + } + } + + private static void visitAnnotations(Queue typeQ, List aes) { + Queue aQ = new ArrayDeque<>(aes); + Set visited = new HashSet<>(); + while (!aQ.isEmpty()) { + AnnotationElement ae = aQ.poll(); + if (!visited.contains(ae)) { + Type ty = PrivateAccess.getInstance().getType(ae); + typeQ.add(ty); + visited.add(ae); + } + aQ.addAll(ae.getAnnotationElements()); + } + } +}