1 /* 2 * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr.internal; 27 28 import java.io.IOException; 29 import java.lang.annotation.Annotation; 30 import java.lang.annotation.Repeatable; 31 import java.lang.reflect.Field; 32 import java.lang.reflect.InvocationTargetException; 33 import java.lang.reflect.Method; 34 import java.lang.reflect.Modifier; 35 import java.util.ArrayDeque; 36 import java.util.ArrayList; 37 import java.util.Collection; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.LinkedHashMap; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Queue; 45 import java.util.Set; 46 import java.util.function.Consumer; 47 import java.util.function.Predicate; 48 import java.util.stream.Stream; 49 50 import jdk.jfr.AnnotationElement; 51 import jdk.jfr.Description; 52 import jdk.jfr.Event; 53 import jdk.jfr.Label; 54 import jdk.jfr.MetadataDefinition; 55 import jdk.jfr.Name; 56 import jdk.jfr.SettingDescriptor; 57 import jdk.jfr.Timespan; 58 import jdk.jfr.Timestamp; 59 import jdk.jfr.ValueDescriptor; 60 61 public final class TypeLibrary { 62 63 private static TypeLibrary instance; 64 private static final Map<Long, Type> types = new LinkedHashMap<>(100); 65 static final ValueDescriptor DURATION_FIELD = createDurationField(); 66 static final ValueDescriptor THREAD_FIELD = createThreadField(); 67 static final ValueDescriptor STACK_TRACE_FIELD = createStackTraceField(); 68 static final ValueDescriptor START_TIME_FIELD = createStartTimeField(); 69 70 private TypeLibrary(List<Type> jvmTypes) { 71 visitReachable(jvmTypes, t -> !types.containsKey(t.getId()), t -> types.put(t.getId(), t)); 72 if (LogTag.JFR_SYSTEM_METADATA.shouldLog(LogLevel.INFO.level)) { 73 Stream<Type> s = types.values().stream().sorted((x, y) -> Long.compare(x.getId(), y.getId())); 74 s.forEach(t -> t.log("Added", LogTag.JFR_SYSTEM_METADATA, LogLevel.INFO)); 75 } 76 } 77 78 private static ValueDescriptor createStartTimeField() { 79 List<AnnotationElement> annos = createStandardAnnotations("Start Time", null); 80 annos.add(new jdk.jfr.AnnotationElement(Timestamp.class, Timestamp.TICKS)); 81 return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_START_TIME, Type.LONG, annos, 0, false, 82 EventInstrumentation.FIELD_START_TIME); 83 84 } 85 86 private static ValueDescriptor createStackTraceField() { 87 List<AnnotationElement> annos = new ArrayList<>(); 88 annos = createStandardAnnotations("Stack Trace", "Stack Trace starting from the method the event was committed in"); 89 return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_STACK_TRACE, Type.STACK_TRACE, annos, 0, true, 90 EventInstrumentation.FIELD_STACK_TRACE); 91 } 92 93 private static ValueDescriptor createThreadField() { 94 List<AnnotationElement> annos = new ArrayList<>(); 95 annos = createStandardAnnotations("Event Thread", "Thread in which event was committed in"); 96 return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_EVENT_THREAD, Type.THREAD, annos, 0, true, 97 EventInstrumentation.FIELD_EVENT_THREAD); 98 } 99 100 private static ValueDescriptor createDurationField() { 101 List<AnnotationElement> annos = new ArrayList<>(); 102 annos = createStandardAnnotations("Duration", null); 103 annos.add(new jdk.jfr.AnnotationElement(Timespan.class, Timespan.TICKS)); 104 return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_DURATION, Type.LONG, annos, 0, false, EventInstrumentation.FIELD_DURATION); 105 } 106 107 public static TypeLibrary getInstance() { 108 synchronized (TypeLibrary.class) { 109 if (instance == null) { 110 List<Type> jvmTypes; 111 try { 112 jvmTypes = TraceHandler.createTypes(); 113 } catch (IOException e) { 114 throw new Error("JFR: Could not read metadata"); 115 } 116 instance = new TypeLibrary(jvmTypes); 117 } 118 return instance; 119 } 120 } 121 122 public List<Type> getTypes() { 123 return new ArrayList<>(types.values()); 124 } 125 126 public static Type createAnnotationType(Class<? extends Annotation> a) { 127 if (shouldPersist(a)) { 128 Type type = defineType(a, Type.SUPER_TYPE_ANNOTATION, false); 129 if (type != null) { 130 for (Method method : a.getDeclaredMethods()) { 131 type.add(PrivateAccess.getInstance().newValueDescriptor(method.getReturnType(), method.getName())); 132 } 133 ArrayList<AnnotationElement> aes = new ArrayList<>(); 134 for (Annotation annotation : resolveRepeatedAnnotations(a.getAnnotations())) { 135 AnnotationElement ae = createAnnotation(annotation); 136 if (ae != null) { 137 aes.add(ae); 138 } 139 } 140 aes.trimToSize(); 141 type.setAnnotations(aes); 142 } 143 return getType(a); 144 } 145 return null; 146 } 147 148 static AnnotationElement createAnnotation(Annotation annotation) { 149 Class<? extends Annotation> annotationType = annotation.annotationType(); 150 Type type = createAnnotationType(annotationType); 151 if (type != null) { 152 List<Object> values = new ArrayList<>(); 153 for (ValueDescriptor v : type.getFields()) { 154 values.add(invokeAnnotation(annotation, v.getName())); 155 } 156 157 return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null); 158 } 159 return null; 160 } 161 162 private static Object invokeAnnotation(Annotation annotation, String methodName) { 163 final Method m; 164 try { 165 m = annotation.getClass().getMethod(methodName, new Class<?>[0]); 166 } catch (NoSuchMethodException e1) { 167 throw (Error) new InternalError("Could not loacate method " + methodName + " in annotation " + annotation.getClass().getName()); 168 } 169 SecuritySupport.setAccessible(m); 170 try { 171 return m.invoke(annotation, new Object[0]); 172 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 173 throw (Error) new InternalError("Could not get value for method " + methodName + " in annotation " + annotation.getClass().getName()); 174 } 175 } 176 177 private static boolean shouldPersist(Class<? extends Annotation> a) { 178 if (a == MetadataDefinition.class || a.getAnnotation(MetadataDefinition.class) == null) { 179 return false; 180 } 181 return true; 182 } 183 184 private static boolean isDefined(Class<?> clazz) { 185 return types.containsKey(Type.getTypeId(clazz)); 186 } 187 188 private static Type getType(Class<?> clazz) { 189 return types.get(Type.getTypeId(clazz)); 190 } 191 192 private static Type defineType(Class<?> clazz, String superType, boolean eventType) { 193 if (!isDefined(clazz)) { 194 Name name = clazz.getAnnotation(Name.class); 195 String typeName = name != null ? name.value() : clazz.getName(); 196 long id = Type.getTypeId(clazz); 197 Type t; 198 if (eventType) { 199 t = new PlatformEventType(typeName, id, clazz.getClassLoader() == null, true); 200 } else { 201 t = new Type(typeName, superType, id); 202 } 203 types.put(t.getId(), t); 204 return t; 205 } 206 return null; 207 } 208 public static Type createType(Class<?> clazz) { 209 return createType(clazz, Collections.emptyList(), Collections.emptyList()); 210 } 211 212 public static Type createType(Class<?> clazz, List<AnnotationElement> dynamicAnnotations, List<ValueDescriptor> dynamicFields) { 213 214 if (Thread.class == clazz) { 215 return Type.THREAD; 216 } 217 218 if (Class.class.isAssignableFrom(clazz)) { 219 return Type.CLASS; 220 } 221 222 if (String.class.equals(clazz)) { 223 return Type.STRING; 224 } 225 226 if (isDefined(clazz)) { 227 return getType(clazz); 228 } 229 230 if (clazz.isPrimitive()) { 231 return defineType(clazz, null,false); 232 } 233 234 if (clazz.isArray()) { 235 throw new InternalError("Arrays not supported"); 236 } 237 238 // STRUCT 239 String superType = null; 240 boolean eventType = false; 241 if (Event.class.isAssignableFrom(clazz)) { 242 superType = Type.SUPER_TYPE_EVENT; 243 eventType= true; 244 } 245 if (Control.class.isAssignableFrom(clazz)) { 246 superType = Type.SUPER_TYPE_SETTING; 247 } 248 249 // forward declare to avoid infinite recursion 250 defineType(clazz, superType, eventType); 251 Type type = getType(clazz); 252 253 if (eventType) { 254 addImplicitFields(type, true, true, true, true ,false); 255 addUserFields(clazz, type, dynamicFields); 256 type.trimFields(); 257 } 258 addAnnotations(clazz, type, dynamicAnnotations); 259 260 if (clazz.getClassLoader() == null) { 261 type.log("Added", LogTag.JFR_SYSTEM_METADATA, LogLevel.INFO); 262 } else { 263 type.log("Added", LogTag.JFR_METADATA, LogLevel.INFO); 264 } 265 return type; 266 } 267 268 private static void addAnnotations(Class<?> clazz, Type type, List<AnnotationElement> dynamicAnnotations) { 269 ArrayList<AnnotationElement> aes = new ArrayList<>(); 270 if (dynamicAnnotations.isEmpty()) { 271 for (Annotation a : Utils.getAnnotations(clazz)) { 272 AnnotationElement ae = createAnnotation(a); 273 if (ae != null) { 274 aes.add(ae); 275 } 276 } 277 } else { 278 List<Type> newTypes = new ArrayList<>(); 279 aes.addAll(dynamicAnnotations); 280 for (AnnotationElement ae : dynamicAnnotations) { 281 newTypes.add(PrivateAccess.getInstance().getType(ae)); 282 } 283 addTypes(newTypes); 284 } 285 type.setAnnotations(aes); 286 aes.trimToSize(); 287 } 288 289 private static void addUserFields(Class<?> clazz, Type type, List<ValueDescriptor> dynamicFields) { 290 Map<String, ValueDescriptor> dynamicFieldSet = new HashMap<>(); 291 for (ValueDescriptor dynamicField : dynamicFields) { 292 dynamicFieldSet.put(dynamicField.getName(), dynamicField); 293 } 294 List<Type> newTypes = new ArrayList<>(); 295 for (Field field : Utils.getVisibleEventFields(clazz)) { 296 ValueDescriptor vd = dynamicFieldSet.get(field.getName()); 297 if (vd != null) { 298 if (!vd.getTypeName().equals(field.getType().getName())) { 299 throw new InternalError("Type expected to match for field " + vd.getName() + " expected " + field.getName() + " but got " + vd.getName()); 300 } 301 for (AnnotationElement ae : vd.getAnnotationElements()) { 302 newTypes.add(PrivateAccess.getInstance().getType(ae)); 303 } 304 newTypes.add(PrivateAccess.getInstance().getType(vd)); 305 } else { 306 vd = createField(field); 307 } 308 if (vd != null) { 309 type.add(vd); 310 } 311 } 312 addTypes(newTypes); 313 } 314 315 // By convention all events have these fields. 316 static void addImplicitFields(Type type, boolean requestable, boolean hasDuration, boolean hasThread, boolean hasStackTrace, boolean hasCutoff) { 317 createAnnotationType(Timespan.class); 318 createAnnotationType(Timestamp.class); 319 createAnnotationType(Label.class); 320 defineType(long.class, null,false); 321 addFields(type, requestable, hasDuration, hasThread, hasStackTrace, hasCutoff); 322 } 323 324 private static void addFields(Type type, boolean requestable, boolean hasDuration, boolean hasThread, boolean hasStackTrace, boolean hasCutoff) { 325 type.add(START_TIME_FIELD); 326 if (hasDuration || hasCutoff) { 327 type.add(DURATION_FIELD); 328 } 329 if (hasThread) { 330 type.add(THREAD_FIELD); 331 } 332 if (hasStackTrace) { 333 type.add(STACK_TRACE_FIELD); 334 } 335 } 336 337 private static List<AnnotationElement> createStandardAnnotations(String name, String description) { 338 List<AnnotationElement> annotationElements = new ArrayList<>(2); 339 annotationElements.add(new jdk.jfr.AnnotationElement(Label.class, name)); 340 if (description != null) { 341 annotationElements.add(new jdk.jfr.AnnotationElement(Description.class, description)); 342 } 343 return annotationElements; 344 } 345 346 private static ValueDescriptor createField(Field field) { 347 int mod = field.getModifiers(); 348 if (Modifier.isTransient(mod)) { 349 return null; 350 } 351 if (Modifier.isStatic(mod)) { 352 return null; 353 } 354 Class<?> fieldType = field.getType(); 355 if (!Type.isKnownType(fieldType)) { 356 return null; 357 } 358 boolean constantPool = Thread.class == fieldType || fieldType == Class.class; 359 Type type = createType(fieldType); 360 String fieldName = field.getName(); 361 Name name = field.getAnnotation(Name.class); 362 String useName = fieldName; 363 if (name != null) { 364 useName = name.value(); 365 } 366 List<jdk.jfr.AnnotationElement> ans = new ArrayList<>(); 367 for (Annotation a : resolveRepeatedAnnotations(field.getAnnotations())) { 368 AnnotationElement ae = createAnnotation(a); 369 if (ae != null) { 370 ans.add(ae); 371 } 372 } 373 return PrivateAccess.getInstance().newValueDescriptor(useName, type, ans, 0, constantPool, fieldName); 374 } 375 376 private static List<Annotation> resolveRepeatedAnnotations(Annotation[] annotations) { 377 List<Annotation> annos = new ArrayList<>(annotations.length); 378 for (Annotation a : annotations) { 379 boolean repeated = false; 380 Method m; 381 try { 382 m = a.annotationType().getMethod("value"); 383 Class<?> returnType = m.getReturnType(); 384 if (returnType.isArray()) { 385 Class<?> ct = returnType.getComponentType(); 386 if (Annotation.class.isAssignableFrom(ct) && ct.getAnnotation(Repeatable.class) != null) { 387 Object res = m.invoke(a, new Object[0]); 388 if (res != null && Annotation[].class.isAssignableFrom(res.getClass())) { 389 for (Annotation rep : (Annotation[]) m.invoke(a, new Object[0])) { 390 annos.add(rep); 391 } 392 repeated = true; 393 } 394 } 395 } 396 } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 397 // Ignore, can't access repeatable information 398 } 399 if (!repeated) { 400 annos.add(a); 401 } 402 } 403 return annos; 404 } 405 406 // Purpose of this method is to mark types that are reachable 407 // from registered event types. Those types that are not reachable can 408 // safely be removed 409 public boolean clearUnregistered() { 410 Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Cleaning out obsolete metadata"); 411 List<Type> registered = new ArrayList<>(); 412 for (Type type : types.values()) { 413 if (type instanceof PlatformEventType) { 414 if (((PlatformEventType) type).isRegistered()) { 415 registered.add(type); 416 } 417 } 418 } 419 visitReachable(registered, t -> t.getRemove(), t -> t.setRemove(false)); 420 List<Long> removeIds = new ArrayList<>(); 421 for (Type type : types.values()) { 422 if (type.getRemove() && !Type.isDefinedByJVM(type.getId())) { 423 removeIds.add(type.getId()); 424 if (LogTag.JFR_METADATA.shouldLog(LogLevel.TRACE.level)) { 425 Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Removed obsolete metadata " + type.getName()); 426 } 427 } 428 // Optimization, set to true now to avoid iterating 429 // types first thing at next call to clearUnregistered 430 type.setRemove(true); 431 } 432 for (Long id : removeIds) { 433 types.remove(id); 434 } 435 return !removeIds.isEmpty(); 436 } 437 438 public void addType(Type type) { 439 addTypes(Collections.singletonList(type)); 440 } 441 442 public static void addTypes(List<Type> ts) { 443 if (!ts.isEmpty()) { 444 visitReachable(ts, t -> !types.containsKey(t.getId()), t -> types.put(t.getId(), t)); 445 } 446 } 447 448 /** 449 * Iterates all reachable types from a start collection 450 * 451 * @param rootSet the types to start from 452 * @param p if a type should be accepted 453 * @param c action to take on an accepted type 454 */ 455 private static void visitReachable(Collection<Type> rootSet, Predicate<Type> p, Consumer<Type> c) { 456 Queue<Type> typeQ = new ArrayDeque<>(rootSet); 457 while (!typeQ.isEmpty()) { 458 Type type = typeQ.poll(); 459 if (p.test(type)) { 460 c.accept(type); 461 visitAnnotations(typeQ, type.getAnnotationElements()); 462 for (ValueDescriptor v : type.getFields()) { 463 typeQ.add(PrivateAccess.getInstance().getType(v)); 464 visitAnnotations(typeQ, v.getAnnotationElements()); 465 } 466 if (type instanceof PlatformEventType) { 467 PlatformEventType pe = (PlatformEventType) type; 468 for (SettingDescriptor s : pe.getAllSettings()) { 469 typeQ.add(PrivateAccess.getInstance().getType(s)); 470 visitAnnotations(typeQ, s.getAnnotationElements()); 471 } 472 } 473 } 474 } 475 } 476 477 private static void visitAnnotations(Queue<Type> typeQ, List<AnnotationElement> aes) { 478 Queue<AnnotationElement> aQ = new ArrayDeque<>(aes); 479 Set<AnnotationElement> visited = new HashSet<>(); 480 while (!aQ.isEmpty()) { 481 AnnotationElement ae = aQ.poll(); 482 if (!visited.contains(ae)) { 483 Type ty = PrivateAccess.getInstance().getType(ae); 484 typeQ.add(ty); 485 visited.add(ae); 486 } 487 aQ.addAll(ae.getAnnotationElements()); 488 } 489 } 490 }