1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.internal.parser.v1; 34 35 import java.io.IOException; 36 import java.text.MessageFormat; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Map; 42 43 import org.openjdk.jmc.common.collection.FastAccessNumberMap; 44 import org.openjdk.jmc.common.unit.ContentType; 45 import org.openjdk.jmc.common.unit.IUnit; 46 import org.openjdk.jmc.common.unit.StructContentType; 47 import org.openjdk.jmc.common.unit.UnitLookup; 48 import org.openjdk.jmc.common.util.LabeledIdentifier; 49 import org.openjdk.jmc.flightrecorder.internal.InvalidJfrFileException; 50 import org.openjdk.jmc.flightrecorder.internal.parser.LoaderContext; 51 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ChunkMetadata.AnnotatedElement; 52 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ChunkMetadata.AnnotationElement; 53 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ChunkMetadata.ClassElement; 54 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ChunkMetadata.FieldElement; 55 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrFrame; 56 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrJavaClass; 57 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrJavaClassLoader; 58 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrJavaModule; 59 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrJavaPackage; 60 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrMethod; 61 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrOldObject; 62 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrOldObjectArray; 63 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrOldObjectField; 64 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrOldObjectGcRoot; 65 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrStackTrace; 66 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrThread; 67 import org.openjdk.jmc.flightrecorder.internal.parser.v1.StructTypes.JfrThreadGroup; 68 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.AbstractStructReader; 69 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.ArrayReader; 70 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.IValueReader; 71 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.PoolReader; 72 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.PrimitiveReader; 73 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.QuantityReader; 74 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.ReflectiveReader; 75 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.StringReader; 76 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.StructReader; 77 import org.openjdk.jmc.flightrecorder.internal.parser.v1.ValueReaders.TicksTimestampReader; 78 import org.openjdk.jmc.flightrecorder.internal.util.JfrInternalConstants; 79 import org.openjdk.jmc.flightrecorder.messages.internal.Messages; 80 import org.openjdk.jmc.flightrecorder.parser.IEventSink; 81 import org.openjdk.jmc.flightrecorder.parser.ValueField; 82 83 class TypeManager { 84 85 private static class NopEventSink implements IEventSink { 86 @Override 87 public void addEvent(Object[] values) { 88 } 89 } 90 91 private static class SkipFieldsEventSink implements IEventSink { 92 private final IEventSink subSink; 93 private final List<Integer> skipFields; 94 private final Object[] reusableStruct; 95 96 SkipFieldsEventSink(IEventSink subSink, List<Integer> skipFields, int fieldCount) { 97 this.subSink = subSink; 98 this.skipFields = skipFields; 99 reusableStruct = new Object[fieldCount - skipFields.size()]; 100 } 101 102 @Override 103 public void addEvent(Object[] fieldValues) { 104 Iterator<Integer> skipIter = skipFields.iterator(); 105 int skipNext = skipIter.next(); 106 int j = 0; 107 for (int i = 0; i < fieldValues.length; i++) { 108 if (i != skipNext) { 109 reusableStruct[j++] = fieldValues[i]; 110 } else if (skipIter.hasNext()) { 111 skipNext = skipIter.next(); 112 } 113 } 114 subSink.addEvent(reusableStruct); 115 } 116 } 117 118 // NOTE: Using constant pool id as identifier. 119 private static final Map<Long, StructContentType<Object[]>> STRUCT_TYPES = new HashMap<>(); 120 121 private class TypeEntry { 122 private static final String STRUCT_TYPE_STACK_TRACE = "com.oracle.jfr.types.StackTrace"; //$NON-NLS-1$ 123 private static final String STRUCT_TYPE_STACK_FRAME = "com.oracle.jfr.types.StackFrame"; //$NON-NLS-1$ 124 private static final String STRUCT_TYPE_METHOD = "com.oracle.jfr.types.Method"; //$NON-NLS-1$ 125 private static final String STRUCT_TYPE_CLASS = "java.lang.Class"; //$NON-NLS-1$ 126 private static final String STRUCT_TYPE_CLASS_LOADER = "com.oracle.jfr.types.ClassLoader"; //$NON-NLS-1$ 127 private static final String STRUCT_TYPE_MODULE = "com.oracle.jfr.types.Module"; //$NON-NLS-1$ 128 private static final String STRUCT_TYPE_PACKAGE = "com.oracle.jfr.types.Package"; //$NON-NLS-1$ 129 private static final String STRUCT_TYPE_OLD_OBJECT = "com.oracle.jfr.types.OldObject"; //$NON-NLS-1$ 130 private static final String STRUCT_TYPE_OLD_OBJECT_ARRAY = "com.oracle.jfr.types.OldObjectArray"; //$NON-NLS-1$ 131 private static final String STRUCT_TYPE_OLD_OBJECT_FIELD = "com.oracle.jfr.types.OldObjectField"; //$NON-NLS-1$ 132 private static final String STRUCT_TYPE_OLD_OBJECT_GC_ROOT = "com.oracle.jfr.types.OldObjectGcRoot"; //$NON-NLS-1$ 133 private static final String STRUCT_TYPE_THREAD_GROUP = "com.oracle.jfr.types.ThreadGroup"; //$NON-NLS-1$ 134 private static final String STRUCT_TYPE_THREAD = "java.lang.Thread"; //$NON-NLS-1$ 135 136 final ClassElement element; 137 final FastAccessNumberMap<Object> constants; 138 private IValueReader reader; 139 140 TypeEntry(ClassElement element) { 141 this(element, new FastAccessNumberMap<>()); 142 } 143 144 /** 145 * Temporary constructor for sharing constants. Only used for Strings. 146 */ 147 TypeEntry(ClassElement element, FastAccessNumberMap<Object> constants) { 148 this.element = element; 149 this.constants = constants; 150 } 151 152 public IValueReader getReader() throws InvalidJfrFileException { 153 if (reader == null) { 154 int fieldCount = element.getFieldCount(); 155 if (element.isSimpleType() && fieldCount == 1) { 156 FieldElement singleField = element.fields.get(0); 157 if (singleField.classId == element.classId) { 158 throw new InvalidJfrFileException( 159 element.typeIdentifier + " is a simple type referring to itself"); //$NON-NLS-1$ 160 } else { 161 reader = createFieldReader(element.fields.get(0), null); 162 } 163 } else if (fieldCount == 0 && element.superType == null) { 164 if (StringReader.STRING.equals(element.typeIdentifier)) { 165 reader = new StringReader(constants); 166 } else { 167 reader = new PrimitiveReader(element.typeIdentifier); 168 } 169 } else { 170 AbstractStructReader typeReader = createStructReader(element.typeIdentifier, element.label, 171 element.description, fieldCount); 172 // assign before resolving field since it may be recursive 173 reader = typeReader; 174 for (int i = 0; i < fieldCount; i++) { 175 FieldElement fe = element.fields.get(i); 176 IValueReader reader = createFieldReader(fe, null); 177 String labelOrId = (fe.label == null) ? fe.fieldIdentifier : fe.label; 178 typeReader.addField(fe.fieldIdentifier, labelOrId, fe.description, reader); 179 } 180 } 181 } 182 return reader; 183 } 184 185 private AbstractStructReader createStructReader( 186 String identifier, String name, String description, int fieldCount) { 187 switch (identifier) { 188 case STRUCT_TYPE_THREAD: 189 return new ReflectiveReader(JfrThread.class, fieldCount, UnitLookup.THREAD); 190 case STRUCT_TYPE_THREAD_GROUP: 191 return new ReflectiveReader(JfrThreadGroup.class, fieldCount, UnitLookup.THREAD_GROUP); 192 case STRUCT_TYPE_CLASS: 193 return new ReflectiveReader(JfrJavaClass.class, fieldCount, UnitLookup.CLASS); 194 case STRUCT_TYPE_CLASS_LOADER: 195 return new ReflectiveReader(JfrJavaClassLoader.class, fieldCount, UnitLookup.CLASS_LOADER); 196 case STRUCT_TYPE_OLD_OBJECT_GC_ROOT: 197 return new ReflectiveReader(JfrOldObjectGcRoot.class, fieldCount, UnitLookup.OLD_OBJECT_GC_ROOT); 198 case STRUCT_TYPE_OLD_OBJECT: 199 return new ReflectiveReader(JfrOldObject.class, fieldCount, UnitLookup.OLD_OBJECT); 200 case STRUCT_TYPE_OLD_OBJECT_ARRAY: 201 return new ReflectiveReader(JfrOldObjectArray.class, fieldCount, UnitLookup.OLD_OBJECT_ARRAY); 202 case STRUCT_TYPE_OLD_OBJECT_FIELD: 203 return new ReflectiveReader(JfrOldObjectField.class, fieldCount, UnitLookup.OLD_OBJECT_FIELD); 204 case STRUCT_TYPE_METHOD: 205 return new ReflectiveReader(JfrMethod.class, fieldCount, UnitLookup.METHOD); 206 case STRUCT_TYPE_STACK_FRAME: 207 return new ReflectiveReader(JfrFrame.class, fieldCount, UnitLookup.STACKTRACE_FRAME); 208 case STRUCT_TYPE_STACK_TRACE: 209 return new ReflectiveReader(JfrStackTrace.class, fieldCount, UnitLookup.STACKTRACE); 210 case STRUCT_TYPE_MODULE: 211 return new ReflectiveReader(JfrJavaModule.class, fieldCount, UnitLookup.MODULE); 212 case STRUCT_TYPE_PACKAGE: 213 return new ReflectiveReader(JfrJavaPackage.class, fieldCount, UnitLookup.PACKAGE); 214 default: 215 synchronized (STRUCT_TYPES) { 216 StructContentType<Object[]> structType = STRUCT_TYPES.get(element.classId); 217 if (structType == null) { 218 structType = new StructContentType<>(element.typeIdentifier, element.label, 219 element.description); 220 STRUCT_TYPES.put(element.classId, structType); 221 } 222 return new StructReader(structType, fieldCount); 223 } 224 } 225 } 226 227 void resolveConstants() throws InvalidJfrFileException { 228 IValueReader r = reader; 229 if (r != null) { 230 for (Object c : constants) { 231 r.resolve(c); 232 // FIXME: During resolve, some constants may become equal. Should we ensure canonical constants? 233 } 234 } 235 } 236 237 void readConstant(IDataInput input) throws InvalidJfrFileException, IOException { 238 // FIXME: Constant lookup can perhaps be optimized (across chunks) 239 long constantIndex = input.readLong(); 240 Object value = constants.get(constantIndex); 241 if (value == null) { 242 value = getReader().read(input, true); 243 constants.put(constantIndex, value); 244 } else { 245 getReader().skip(input); 246 } 247 } 248 } 249 250 private class EventTypeEntry { 251 private final ClassElement element; 252 private final List<IValueReader> valueReaders; 253 private Object[] reusableStruct; 254 private IEventSink eventSink; 255 private LabeledIdentifier eventType; 256 257 EventTypeEntry(ClassElement element) { 258 this.element = element; 259 valueReaders = new ArrayList<>(element.getFieldCount()); 260 } 261 262 void readEvent(IDataInput input) throws InvalidJfrFileException, IOException { 263 for (int i = 0; i < valueReaders.size(); i++) { 264 reusableStruct[i] = valueReaders.get(i).read(input, false); 265 } 266 eventSink.addEvent(reusableStruct); 267 } 268 269 LabeledIdentifier getValueType() { 270 if (eventType == null) { 271 eventType = new LabeledIdentifier(element.typeIdentifier, element.classId, element.label, 272 element.description); 273 } 274 return eventType; 275 } 276 277 void init(LoaderContext context) throws InvalidJfrFileException, IOException { 278 if (context.hideExperimentals() && element.experimental) { 279 eventSink = new NopEventSink(); 280 } else { 281 List<ValueField> fieldsList = new ArrayList<>(); 282 List<Integer> skipFields = new ArrayList<>(); 283 for (int i = 0; i < element.getFieldCount(); i++) { 284 FieldElement fe = element.fields.get(i); 285 String valueType = context.getValueInterpretation(element.typeIdentifier, fe.fieldIdentifier); 286 IValueReader reader = createFieldReader(fe, valueType); 287 String fieldLabel = buildLabel(fe.fieldIdentifier, fe); 288 if (context.hideExperimentals() && fe.experimental) { 289 valueReaders.add(reader); 290 skipFields.add(i); 291 } else if (reader instanceof StructReader) { 292 // Flattening of nested structs 293 ClassElement fieldType = getTypeEntry(fe.classId).element; 294 for (int j = 0; j < fieldType.getFieldCount(); j++) { 295 FieldElement nestedField = fieldType.fields.get(j); 296 String nestedId = fe.fieldIdentifier + ":" + nestedField.fieldIdentifier; //$NON-NLS-1$ 297 String nestedValueType = context.getValueInterpretation(element.typeIdentifier, nestedId); 298 IValueReader nestedReader = createFieldReader(nestedField, nestedValueType); 299 valueReaders.add(nestedReader); 300 String nestedLabel = fieldLabel + " : " //$NON-NLS-1$ 301 + (nestedField.label == null ? nestedField.fieldIdentifier : nestedField.label); 302 fieldsList.add(new ValueField(nestedId, nestedLabel, nestedField.description, 303 nestedReader.getContentType())); 304 } 305 } else { 306 valueReaders.add(reader); 307 fieldsList.add(new ValueField(fe.fieldIdentifier, fieldLabel, fe.description, 308 reader.getContentType())); 309 } 310 } 311 String typeLabel = buildLabel(element.typeIdentifier, element); 312 // FIXME: Consider making the category array into something else, like an event type metadata array? 313 eventSink = context.getSinkFactory().create(element.typeIdentifier, typeLabel, element.category, 314 element.description, fieldsList); 315 reusableStruct = new Object[valueReaders.size()]; 316 if (skipFields.size() > 0) { 317 eventSink = new SkipFieldsEventSink(eventSink, skipFields, reusableStruct.length); 318 } 319 } 320 } 321 } 322 323 private final FastAccessNumberMap<TypeEntry> otherTypes = new FastAccessNumberMap<>(); 324 private final FastAccessNumberMap<EventTypeEntry> eventTypes = new FastAccessNumberMap<>(); 325 private final ChunkStructure header; 326 327 TypeManager(List<ClassElement> classList, LoaderContext context, ChunkStructure header) 328 throws InvalidJfrFileException, IOException { 329 this.header = header; 330 for (ClassElement ce : classList) { 331 if (ce.isEventType()) { 332 eventTypes.put(ce.classId, new EventTypeEntry(ce)); 333 } else { 334 otherTypes.put(ce.classId, new TypeEntry(ce)); 335 } 336 } 337 for (ClassElement ce : classList) { 338 resolveAnnotations(ce); 339 for (int i = 0; i < ce.getFieldCount(); i++) { 340 resolveAnnotations(ce.fields.get(i)); 341 } 342 } 343 344 for (EventTypeEntry ce : eventTypes) { 345 ce.init(context); 346 } 347 } 348 349 void readEvent(long typeId, IDataInput input) throws InvalidJfrFileException, IOException { 350 EventTypeEntry entry = eventTypes.get(typeId); 351 if (entry == null) { 352 throw new InvalidJfrFileException("Event type with id " + typeId + " was not declared"); //$NON-NLS-1$ //$NON-NLS-2$ 353 } 354 entry.readEvent(input); 355 } 356 357 void readConstants(long typeId, IDataInput input, int constantCount) throws InvalidJfrFileException, IOException { 358 TypeEntry entry = getTypeEntry(typeId); 359 for (int j = 0; j < constantCount; j++) { 360 entry.readConstant(input); 361 } 362 } 363 364 void resolveConstants() throws InvalidJfrFileException { 365 for (TypeEntry classEntry : otherTypes) { 366 classEntry.resolveConstants(); 367 } 368 } 369 370 private TypeEntry getTypeEntry(long typeId) throws InvalidJfrFileException { 371 TypeEntry entry = otherTypes.get(typeId); 372 if (entry == null) { 373 throw new InvalidJfrFileException("Class with id " + typeId + " was not declared"); //$NON-NLS-1$ //$NON-NLS-2$ 374 } 375 return entry; 376 } 377 378 private void resolveAnnotations(AnnotatedElement ae) throws InvalidJfrFileException { 379 if (ae.annotations != null) { 380 for (AnnotationElement a : ae.annotations) { 381 ClassElement annotationType = getTypeEntry(a.classId).element; 382 ae.resolveAnnotation(annotationType.typeIdentifier, a.values); 383 } 384 } 385 } 386 387 private IValueReader createFieldReader(FieldElement f, String valueType) throws InvalidJfrFileException { 388 TypeEntry fieldType = getTypeEntry(f.classId); 389 String typeIdentifier = fieldType.element.typeIdentifier; 390 boolean isNumeric = PrimitiveReader.isNumeric(typeIdentifier); 391 IValueReader reader = fieldType.getReader(); 392 if (f.ticksUnitKind == UnitLookup.TIMESPAN) { 393 reader = new QuantityReader(typeIdentifier, header.getTicksTimespanUnit(), f.unsigned); 394 } else if (f.ticksUnitKind == UnitLookup.TIMESTAMP) { 395 reader = new TicksTimestampReader(typeIdentifier, header, f.unsigned); 396 } else if (f.unit != null) { 397 reader = new QuantityReader(typeIdentifier, f.unit, f.unsigned); 398 } else if (isNumeric) { 399 if (JfrInternalConstants.TYPE_IDENTIFIER_VALUE_INTERPRETATION.equals(valueType)) { 400 reader = new TypeIdentifierReader(typeIdentifier, f.unsigned); 401 } else { 402 IUnit unit = UnitLookup.getUnitOrNull(valueType); 403 /* 404 * FIXME: Currently we convert all numbers to quantities. This might not be ideal, 405 * for example for thread IDs. See multiple notes referring to this method in 406 * StructTypes. 407 */ 408 reader = new QuantityReader(typeIdentifier, unit == null ? UnitLookup.NUMBER_UNITY : unit, f.unsigned); 409 } 410 } 411 if (f.isStoredInPool()) { 412 if (isNumeric) { 413 throw new InvalidJfrFileException("Numerics should not be put in constant pools"); //$NON-NLS-1$ 414 } 415 reader = new PoolReader(fieldType.constants, reader.getContentType()); 416 } 417 return f.isArray() ? new ArrayReader(reader) : reader; 418 } 419 420 private static String buildLabel(String id, AnnotatedElement element) { 421 String labelOrId = element.label == null ? id : element.label; 422 return element.experimental 423 ? MessageFormat.format(Messages.getString(Messages.TypeManager_EXPERIMENTAL_TYPE), labelOrId) 424 : labelOrId; 425 } 426 427 private class TypeIdentifierReader implements IValueReader { 428 private final String typeIdentifier; 429 private final boolean unsigned; 430 431 TypeIdentifierReader(String typeIdentifier, boolean unsigned) throws InvalidJfrFileException { 432 this.typeIdentifier = typeIdentifier; 433 this.unsigned = unsigned; 434 } 435 436 @Override 437 public Object read(IDataInput in, boolean allowUnresolvedReference) 438 throws IOException, InvalidJfrFileException { 439 long typeId = PrimitiveReader.readLong(in, typeIdentifier, unsigned); 440 return eventTypes.get(typeId).getValueType(); 441 } 442 443 @Override 444 public Object resolve(Object value) throws InvalidJfrFileException { 445 return value; 446 } 447 448 @Override 449 public void skip(IDataInput in) throws IOException, InvalidJfrFileException { 450 PrimitiveReader.readLong(in, typeIdentifier, unsigned); 451 } 452 453 @Override 454 public ContentType<?> getContentType() { 455 return UnitLookup.LABELED_IDENTIFIER; 456 } 457 } 458 }