1 /*
   2  * Copyright (c) 2016, 2018, 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.io.InputStream;
  30 import java.util.ArrayList;
  31 import java.util.Collections;
  32 import java.util.HashMap;
  33 import java.util.LinkedHashMap;
  34 import java.util.List;
  35 import java.util.Map;
  36 
  37 import jdk.internal.org.xml.sax.Attributes;
  38 import jdk.internal.org.xml.sax.EntityResolver;
  39 import jdk.internal.org.xml.sax.SAXException;
  40 import jdk.internal.org.xml.sax.helpers.DefaultHandler;
  41 import jdk.internal.util.xml.SAXParser;
  42 import jdk.internal.util.xml.impl.SAXParserImpl;
  43 import jdk.jfr.AnnotationElement;
  44 import jdk.jfr.Category;
  45 import jdk.jfr.Description;
  46 import jdk.jfr.Enabled;
  47 import jdk.jfr.Experimental;
  48 import jdk.jfr.Label;
  49 import jdk.jfr.MemoryAddress;
  50 import jdk.jfr.DataAmount;
  51 import jdk.jfr.Percentage;
  52 import jdk.jfr.Period;
  53 import jdk.jfr.Relational;
  54 import jdk.jfr.StackTrace;
  55 import jdk.jfr.Threshold;
  56 import jdk.jfr.Timespan;
  57 import jdk.jfr.Timestamp;
  58 import jdk.jfr.TransitionFrom;
  59 import jdk.jfr.TransitionTo;
  60 import jdk.jfr.Unsigned;
  61 import jdk.jfr.ValueDescriptor;
  62 
  63 final class TraceHandler extends DefaultHandler implements EntityResolver {
  64 
  65     private static class ValueDeclaration {
  66         private final String typeName;
  67         private final String field;
  68         private final String label;
  69         private final boolean experimental;
  70         private final String description;
  71         private final String relation;
  72         private final int dimension;
  73         private final Type type;
  74         private final boolean transitionFrom;
  75         private final boolean transitionTo;
  76 
  77         public ValueDeclaration(Type type, int dimension, Attributes attributes) {
  78             typeName = attributes.getValue(ATTRIBUTE_TYPE);
  79             field = attributes.getValue(ATTRIBUTE_FIELD);
  80             label = attributes.getValue(ATTRIBUTE_LABEL);
  81             experimental = "true".equals(attributes.getValue(ATTRIBUTE_EXPERIMENTAL));
  82             description = attributes.getValue(ATTRIBUTE_DESCRIPTION);
  83             String t = attributes.getValue(ATTRIBUTE_RELATION);
  84             relation = "NOT_AVAILABLE".equals(t) ? null : t;
  85             String transition = attributes.getValue(ATTRIBUTE_TRANSITION);
  86             transitionFrom = "FROM".equals(transition);
  87             transitionTo = "TO".equals(transition);
  88             this.dimension = dimension;
  89             this.type = type;
  90         }
  91     }
  92     private static final Map<String, String> knownCategorySegments = new HashMap<>();
  93     static {
  94         knownCategorySegments.put("os", "Operating System");
  95         knownCategorySegments.put("java", "Java Application");
  96         knownCategorySegments.put("vm", "Java Virtual Machine");
  97         knownCategorySegments.put("class", "Class Loading");
  98         knownCategorySegments.put("gc", "GC");
  99         knownCategorySegments.put("prof", "Profiling");
 100         knownCategorySegments.put("flight_recorder", "Flight Recorder");
 101     }
 102     private static final String ELEMENT_CONTENT_TYPE = "content_type";
 103     private static final String ELEMENT_PRIMARY_TYPE = "primary_type";
 104     private static final String ELEMENT_EVENT = "event";
 105     private static final String ELEMENT_VALUE = "value";
 106     private static final String ELEMENT_STRUCT_VALUE ="structvalue";
 107     private static final String ELEMENT_STRUCT_TYPE = "struct_type";
 108     private static final String ELEMENT_STRUCT_ARRAY = "structarray";
 109     private static final String ELEMENT_STRUCT = "struct";
 110     private static final String ATTRIBUTE_LABEL = "label";
 111     private static final String ATTRIBUTE_EXPERIMENTAL = "experimental";
 112     private static final String ATTRIBUTE_TRANSITION = "transition";
 113     private static final String ATTRIBUTE_ID = "id";
 114     private static final String ATTRIBUTE_IS_CONSTANT = "is_constant";
 115     private static final String IS_REQUESTABLE = "is_requestable";
 116     private static final String ATTRIBUTE_IS_REQUESTABLE = IS_REQUESTABLE;
 117     private static final String ATTRIBUTE_IS_INSTANT = "is_instant";
 118     private static final String ATTRIBUTE_CUTOFF = "cutoff";
 119     private static final String ATTRIBUTE_URI = "uri";
 120     private static final String ATTRIBUTE_CONTENTTYPE = "contenttype";
 121     private static final String ATTRIBUTE_FIELD = "field";
 122     private static final String ATTRIBUTE_TYPE = "type";
 123     private static final String ATTRIBUTE_DESCRIPTION = "description";
 124     private static final String ATTRIBUTE_HR_NAME = "hr_name";
 125     private static final String ATTRIBUTE_RELATION = "relation";
 126     private static final String ATTRIBUTE_DATATYPE = "datatype";
 127     private static final String ATTRIBUTE_SYMBOL = "symbol";
 128     private static final String ATTRIBUTE_BUILTIN_TYPE = "builtin_type";
 129     private static final String ATTRIBUTE_JVM_TYPE = "jvm_type";
 130     private static final String ATTRIBUTE_PATH = "path";
 131     private static final String ATTRIBUTE_VALUE_NONE = "NONE";
 132     private static final String ELEMENT_RELATION_DECL = "relation_decl";
 133     private static final String ATTRIBUTE_HAS_STACKTRACE = "has_stacktrace";
 134     private static final String ATTRIBUTE_HAS_THREAD = "has_thread";
 135 
 136     private final Map<String, Type> types = new LinkedHashMap<>(200);
 137     private final Map<String, String> typedef = new HashMap<>(100);
 138     private final Map<String, AnnotationElement> annotationTypes = new HashMap<>();
 139     private final Map<String, Type> unsignedTypes = new HashMap<>();
 140     private final List<ValueDeclaration> valueDeclarations = new ArrayList<>(1000);
 141 
 142     private Type type;
 143     private long structTypeId = 255;
 144     private long jvmTypeId = 33; // content_type with jvm_type attribute, others hard wired
 145 
 146     TraceHandler() {
 147 
 148         // Content types handled using annotation in Java
 149         annotationTypes.put("BYTES64", new AnnotationElement(DataAmount.class, DataAmount.BYTES));
 150         annotationTypes.put("BYTES", new AnnotationElement(DataAmount.class, DataAmount.BYTES));
 151         annotationTypes.put("MILLIS", new AnnotationElement(Timespan.class, Timespan.MILLISECONDS));
 152         annotationTypes.put("EPOCHMILLIS", new AnnotationElement(Timestamp.class, Timestamp.MILLISECONDS_SINCE_EPOCH));
 153         annotationTypes.put("NANOS", new AnnotationElement(Timespan.class, Timespan.NANOSECONDS));
 154         annotationTypes.put("TICKSPAN",  new AnnotationElement(Timespan.class, Timespan.TICKS));
 155         annotationTypes.put("TICKS",  new AnnotationElement(Timestamp.class, Timespan.TICKS));
 156         annotationTypes.put("ADDRESS", new AnnotationElement(MemoryAddress.class));
 157         annotationTypes.put("PERCENTAGE",  new AnnotationElement(Percentage.class));
 158 
 159         // Add known unsigned types, and their counter part in Java
 160         unsignedTypes.put("U8", Type.LONG);
 161         unsignedTypes.put("U4", Type.INT);
 162         unsignedTypes.put("U2", Type.SHORT);
 163         unsignedTypes.put("U1", Type.BYTE);
 164 
 165         // Map trace.xml primitive to Java type
 166         typedef.put("U8", Type.LONG.getName());
 167         typedef.put("U4", Type.INT.getName());
 168         typedef.put("U2", Type.SHORT.getName());
 169         typedef.put("U1", Type.BYTE.getName());
 170         typedef.put("LONG", Type.LONG.getName());
 171         typedef.put("INT", Type.INT.getName());
 172         typedef.put("SHORT", Type.SHORT.getName());
 173         typedef.put("BYTE", Type.BYTE.getName());
 174         typedef.put("DOUBLE", Type.DOUBLE.getName());
 175         typedef.put("BOOLEAN", Type.BOOLEAN.getName());
 176         typedef.put("FLOAT", Type.FLOAT.getName());
 177         typedef.put("CHAR", Type.CHAR.getName());
 178         typedef.put("STRING", Type.STRING.getName());
 179         typedef.put("THREAD", Type.THREAD.getName());
 180         typedef.put("CLASS", Type.CLASS.getName());
 181         // Add known types
 182         for (Type type : Type.getKnownTypes()) {
 183             types.put(type.getName(), type);
 184         }
 185     }
 186 
 187     @Override
 188     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
 189         switch (qName) {
 190         case ELEMENT_RELATION_DECL:
 191             String relationalURI = attributes.getValue(ATTRIBUTE_URI);
 192             String id = attributes.getValue(ATTRIBUTE_ID);
 193             typedef.put(id, relationalURI);
 194             break;
 195         case ELEMENT_PRIMARY_TYPE:
 196             addTypedef(attributes);
 197             break;
 198         case ELEMENT_STRUCT_ARRAY:
 199             valueDeclarations.add(new ValueDeclaration(type, 1, attributes));
 200             break;
 201         case ELEMENT_VALUE:
 202         case ELEMENT_STRUCT_VALUE:
 203             valueDeclarations.add(new ValueDeclaration(type, 0, attributes));
 204             break;
 205         case ELEMENT_EVENT:
 206             type = createType(attributes, true, structTypeId++, false);
 207             boolean stackTrace = getBoolean(attributes, ATTRIBUTE_HAS_STACKTRACE);
 208             boolean thread = getBoolean(attributes, (ATTRIBUTE_HAS_THREAD));
 209             boolean instant = getBoolean(attributes, (ATTRIBUTE_IS_INSTANT));
 210             boolean requestable = getBoolean(attributes, (ATTRIBUTE_IS_REQUESTABLE));
 211             boolean constant = getBoolean(attributes, (ATTRIBUTE_IS_CONSTANT));
 212             boolean duration = !requestable && !instant;
 213             boolean experimental = getBoolean(attributes, ATTRIBUTE_EXPERIMENTAL);
 214             boolean cutoff =  getBoolean(attributes, ATTRIBUTE_CUTOFF);
 215             TypeLibrary.addImplicitFields(type, requestable, duration, thread, stackTrace, cutoff);
 216             ArrayList<AnnotationElement> aes = new ArrayList<>();
 217             aes.addAll(type.getAnnotationElements());
 218             if (requestable) {
 219                 String period = constant ? "endChunk" : "everyChunk";
 220                 aes.add(new AnnotationElement(Period.class, period));
 221             } else {
 222                 if (!instant) {
 223                     aes.add(new AnnotationElement(Threshold.class, "0 ns"));
 224                 }
 225                 if (stackTrace) {
 226                     aes.add(new AnnotationElement(StackTrace.class, true));
 227                 }
 228             }
 229             if (cutoff) {
 230                 aes.add(new AnnotationElement(Cutoff.class, Cutoff.INIFITY));
 231             }
 232             if (experimental) {
 233                 aes.add(new AnnotationElement(Experimental.class));
 234             }
 235             aes.add(new AnnotationElement(Enabled.class, false));
 236             aes.trimToSize();
 237             type.setAnnotations(aes);
 238             break;
 239         case ELEMENT_CONTENT_TYPE:
 240             if (attributes.getValue(ATTRIBUTE_BUILTIN_TYPE) != null) {
 241                 type = createType(attributes, false, builtInId(attributes), true);
 242             } else {
 243                 type = createType(attributes, false, jvmTypeId++, true);
 244             }
 245             break;
 246         case ELEMENT_STRUCT_TYPE:
 247         case ELEMENT_STRUCT:
 248             type = createType(attributes, false, structTypeId++, false);
 249             break;
 250         }
 251     }
 252 
 253     private long builtInId(Attributes attributes) {
 254         String t = attributes.getValue(ATTRIBUTE_ID);
 255         if ("Thread".equals(t)) {
 256             return Type.THREAD.getId();
 257         }
 258         if ("STRING".equals(t)) {
 259             return Type.STRING.getId();
 260         }
 261         if ("Class".equals(t)) {
 262             return Type.CLASS.getId();
 263         }
 264         for (Type type : Type.getKnownTypes()) {
 265             if (type.getName().equals(Type.ORACLE_TYPE_PREFIX + t)) {
 266                return type.getId();
 267             }
 268         }
 269         throw new IllegalStateException("Built-in type " + t + " not defined in Java");
 270     }
 271 
 272     private boolean getBoolean(Attributes attributes, String attribute) {
 273         return "true".equals(attributes.getValue(attribute));
 274     }
 275 
 276     @Override
 277     public void endElement(String uri, String localName, String qName) {
 278         switch (qName) {
 279         case ELEMENT_CONTENT_TYPE:
 280         case ELEMENT_EVENT:
 281         case ELEMENT_STRUCT_TYPE:
 282         case ELEMENT_STRUCT:
 283             types.put(type.getName(), type);
 284             type = null;
 285             break;
 286         }
 287     }
 288 
 289     private void addTypedef(Attributes attributes) {
 290         String symbol = attributes.getValue(ATTRIBUTE_SYMBOL);
 291         String contentType = attributes.getValue(ATTRIBUTE_CONTENTTYPE);
 292         String dataType = attributes.getValue(ATTRIBUTE_DATATYPE);
 293         if (unsignedTypes.containsKey(dataType)) {
 294             unsignedTypes.put(symbol, unsignedTypes.get(dataType));
 295         }
 296         if (annotationTypes.containsKey(contentType)) {
 297             typedef.put(symbol, dataType);
 298             return;
 299         }
 300         if (typedef.containsKey(symbol)) {
 301             return;
 302         }
 303         typedef.put(symbol, ATTRIBUTE_VALUE_NONE.equals(contentType) ? dataType : contentType);
 304     }
 305 
 306     private void defineValues() {
 307         for (ValueDeclaration decl : valueDeclarations) {
 308             decl.type.add(createValueDescriptor(decl));
 309         }
 310     }
 311 
 312     private ValueDescriptor createValueDescriptor(ValueDeclaration declaration) {
 313         Type referencedType = null;
 314         String typeAlias = declaration.typeName;
 315         while (referencedType == null && typeAlias != null) {
 316             referencedType = types.get(typeAlias);
 317             typeAlias = typedef.get(typeAlias);
 318         }
 319         if (referencedType == null) {
 320             throw new InternalError("Trace files contains incompatible type definition");
 321         }
 322         List<AnnotationElement> annos = new ArrayList<>();
 323         if (unsignedTypes.containsKey(declaration.typeName) && !referencedType.isConstantPool()) {
 324             annos.add(new AnnotationElement(Unsigned.class));
 325         }
 326         AnnotationElement contentType = annotationTypes.get(declaration.typeName);
 327         if (contentType != null) {
 328             annos.add(contentType);
 329         }
 330         if (declaration.relation != null) {
 331             Type relationType = types.get(typedef.get(declaration.relation));
 332             if (relationType == null) {
 333                 String typeName = Type.ORACLE_TYPE_PREFIX + declaration.relation;
 334                 typedef.put(declaration.relation, typeName);
 335                 relationType = new Type(typeName, Type.SUPER_TYPE_ANNOTATION, structTypeId++);
 336                 relationType.setAnnotations(Collections.singletonList(new AnnotationElement(Relational.class)));
 337                 types.put(typeName, relationType);
 338             }
 339             annos.add(PrivateAccess.getInstance().newAnnotation(relationType, new ArrayList<>(), true));
 340         }
 341 
 342         if (declaration.label != null) {
 343             annos.add(new AnnotationElement(Label.class, declaration.label));
 344         }
 345 
 346         if (declaration.experimental) {
 347             annos.add(new AnnotationElement(Experimental.class));
 348         }
 349 
 350         if (declaration.description != null) {
 351             annos.add(new AnnotationElement(Description.class, declaration.description));
 352         }
 353         if (declaration.transitionFrom) {
 354             annos.add(new AnnotationElement(TransitionFrom.class));
 355         }
 356         if (declaration.transitionTo) {
 357             annos.add(new AnnotationElement(TransitionTo.class));
 358         }
 359 
 360         return PrivateAccess.getInstance().newValueDescriptor(declaration.field, referencedType, annos, declaration.dimension, referencedType.isConstantPool(), null);
 361     }
 362 
 363     private Type createType(Attributes attributes, boolean eventType, long typeId, boolean contantPool) {
 364         String labelAttribute = ATTRIBUTE_LABEL;
 365         String id = attributes.getValue(ATTRIBUTE_ID);
 366         String path = attributes.getValue(ATTRIBUTE_PATH);
 367         String builtInType = attributes.getValue(ATTRIBUTE_BUILTIN_TYPE);
 368         String jvmType = attributes.getValue(ATTRIBUTE_JVM_TYPE);
 369 
 370         String typeName = makeTypeName(id, path);
 371         Type t;
 372         if (eventType) {
 373             t = new PlatformEventType(typeName, typeId, false, true);
 374         } else {
 375             t = new Type(typeName, null, typeId, contantPool);
 376         }
 377         typedef.put(id, typeName);
 378         if (contantPool) {
 379             labelAttribute = ATTRIBUTE_HR_NAME; // not "label" for some reason?
 380             if (builtInType != null) {
 381                 typedef.put(builtInType, typeName);
 382             }
 383             if (jvmType != null) {
 384                 typedef.put(jvmType, typeName);
 385             }
 386         }
 387         ArrayList<AnnotationElement> aes = new ArrayList<>();
 388         if (path != null) {
 389             aes.add(new AnnotationElement(Category.class, makeCategory(path)));
 390         }
 391         String label = attributes.getValue(labelAttribute);
 392         if (label != null) {
 393             aes.add(new AnnotationElement(Label.class, label));
 394         }
 395         String description = attributes.getValue(ATTRIBUTE_DESCRIPTION);
 396         if (description != null) {
 397             aes.add(new AnnotationElement(Description.class, description));
 398         }
 399         aes.trimToSize();
 400         t.setAnnotations(aes);
 401         return t;
 402     }
 403 
 404     private String makeTypeName(String id, String path) {
 405         if ("Thread".equals(id)) {
 406             return Type.THREAD.getName();
 407         }
 408         if ("Class".equals(id)) {
 409             return Type.CLASS.getName();
 410         }
 411         return path == null ? Type.ORACLE_TYPE_PREFIX + id : Type.ORACLE_EVENT_PREFIX + id;
 412     }
 413 
 414     private static String[] makeCategory(String path) {
 415         List<String> categoryNames = new ArrayList<>();
 416         int index = 0;
 417         int segmentEnd;
 418         while ((segmentEnd = path.indexOf("/", index)) != -1) {
 419             String pathSegment = path.substring(index, segmentEnd);
 420             String known = knownCategorySegments.get(pathSegment);
 421             if (known != null) {
 422                 categoryNames.add(known);
 423             } else {
 424                 String categorySegment = humanifySegmentPath(pathSegment);
 425                 categoryNames.add(categorySegment);
 426                 knownCategorySegments.put(pathSegment, categorySegment);
 427             }
 428             index = segmentEnd + 1;
 429         }
 430         String[] result = new String[categoryNames.size()];
 431         for (int i = 0; i< result.length;i++) {
 432             result[i] = categoryNames.get(i);
 433         }
 434         return result;
 435     }
 436 
 437     private static String humanifySegmentPath(String pathSegment) {
 438         char[] chars = pathSegment.toCharArray();
 439         char lastCharacter = ' '; // signals new word
 440         for (int i = 0; i < chars.length; i++) {
 441             char c = pathSegment.charAt(i);
 442             if (c == '_') {
 443                 chars[i] = ' ';
 444             }
 445             if (lastCharacter == ' ') {
 446                 chars[i] = Character.toUpperCase(c);
 447             }
 448             lastCharacter = c;
 449         }
 450         return new String(chars);
 451     }
 452 
 453     public static List<Type> createTypes() throws IOException {
 454         String[] xmls = { "trace.xml", "tracerelationdecls.xml", "traceevents.xml",
 455                           "tracetypes.xml"};
 456         TraceHandler t = new TraceHandler();
 457         try {
 458              SAXParser parser = new SAXParserImpl();
 459             for (String xml : xmls) {
 460                 Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Parsing " + xml);
 461                 parser.parse(createInputStream(xml), t);
 462             }
 463             t.defineValues();
 464             return new ArrayList<>(t.types.values());
 465         }  catch (SAXException  e) {
 466             throw new IOException(e);
 467         }
 468     }
 469     private static InputStream createInputStream(String name) throws IOException {
 470         return SecuritySupport.getResourceAsStream("/jdk/jfr/internal/types/" + name);
 471     }
 472 }