1 /*
   2  * Copyright (c) 2013, 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.event.metadata;
  27 
  28 import java.util.HashSet;
  29 import java.util.List;
  30 import java.util.Set;
  31 
  32 import jdk.jfr.EventType;
  33 import jdk.jfr.FlightRecorder;
  34 import jdk.jfr.ValueDescriptor;
  35 import jdk.testlibrary.Asserts;
  36 
  37 /*
  38  * @test
  39  * @key jfr
  40  * @library /lib/testlibrary
  41  * @run main/othervm jdk.jfr.event.metadata.TestEventMetadata
  42  */
  43 
  44 public class TestEventMetadata {
  45 
  46     /*
  47      * Short guide to writing event metadata
  48      * =====================================
  49 
  50      * Name
  51      * ----
  52      *
  53      * Symbolic name that is used to identify an event, or a field. Referred to
  54      * as "id" and "field" in trace.xml-files and @Name in the Java API. If it is
  55      * the name of an event, the name should be prefixed "com.oracle.jdk.", which
  56      * happens automatically for native events.
  57      *
  58      * The name should be short, but not so brief that collision is likely with
  59      * future events or fields. It should only consist of letters and numbers.
  60      * Use Java naming convention , i.e. "FileRead" for an event and
  61      * "allocationRate" for a field. Do not use "_" and don't add the word
  62      * "Event" to the event name.
  63      *
  64      * Abbreviations should be avoided, but may be acceptable if the name
  65      * becomes long, or if it is a well established acronym. Write whole words,
  66      * i.e. "allocation" instead of "alloc". The name should not be a reserved
  67      * Java keyword, i.e "void" or "class".
  68      *
  69      * Label
  70      * -----
  71      *
  72      * Describes a human readable name, typically 1-3 words. Use headline-style
  73      * capitalization, capitalize the first and last words, and all nouns,
  74      * pronouns, adjectives, verbs and adverbs. Do not include ending
  75      * punctuation.
  76      *
  77      * Description
  78      * -----------
  79      *
  80      * Describes an event with a sentence or two. It's better to omit the
  81      * description then copying the label. Use sentence-style
  82      * capitalization, capitalize the first letter of the first word, and any
  83      * proper names such as the word Java. If the description is one sentence,
  84      * period should not be included.
  85      *
  86      *
  87      * Do not forget to set proper units for fields, i.e "NANOS", "MILLS",
  88      * "TICKSPAN" ,"BYETS", "PECENTAGE" etc. in native and @Timespan, @Timespan
  89      * etc. in Java.
  90      */
  91     public static void main(String[] args) throws Exception {
  92         Set<String> types = new HashSet<>();
  93         List<EventType> eventTypes = FlightRecorder.getFlightRecorder().getEventTypes();
  94         Set<String> eventNames= new HashSet<>();
  95         for (EventType eventType : eventTypes) {
  96             verifyEventType(eventType);
  97             verifyValueDesscriptors(eventType.getFields(), types);
  98             System.out.println();
  99             String eventName = eventType.getName();
 100             if (eventNames.contains(eventName)) {
 101                 throw new Exception("Event with name " +eventName+ " already exists");
 102             }
 103             eventNames.add(eventName);
 104             Set<String> fieldNames = new HashSet<>();
 105             for (ValueDescriptor v : eventType.getFields()) {
 106                 String fieldName = v.getName();
 107                 if (fieldNames.contains(fieldName)) {
 108                     throw new Exception("Field with name " + fieldName +" is already in use in event name " +eventName);
 109                 }
 110                 fieldNames.add(fieldName);
 111             }
 112         }
 113     }
 114 
 115     private static void verifyValueDesscriptors(List<ValueDescriptor> fields, Set<String> visitedTypes) {
 116         for (ValueDescriptor v : fields) {
 117             if (!visitedTypes.contains(v.getTypeName())) {
 118                 visitedTypes.add(v.getTypeName());
 119                 verifyValueDesscriptors(v.getFields(), visitedTypes);
 120             }
 121             verifyValueDescriptor(v);
 122         }
 123     }
 124 
 125     private static void verifyValueDescriptor(ValueDescriptor v) {
 126         verifyName(v.getName());
 127         verifyLabel(v.getLabel());
 128         verifyDescription(v.getDescription());
 129     }
 130 
 131     private static void verifyDescription(String description) {
 132         if (description == null) {
 133             return;
 134         }
 135         Asserts.assertTrue(description.length() > 10, "Description must be at least ten characters");
 136         Asserts.assertTrue(description.length() < 300, "Description should not exceed 300 characters. Found " + description);
 137         Asserts.assertTrue(description.length() == description.trim().length(), "Description should not have trim character at start or end");
 138         Asserts.assertFalse(description.endsWith(".") && description.indexOf(".") == description.length() - 1, "Single sentence descriptions should not use end punctuation");
 139     }
 140 
 141     private static void verifyName(String name) {
 142         System.out.println("Verifying name: " + name);
 143         Asserts.assertNotEquals(name, null, "Name not allowed to be null");
 144         Asserts.assertTrue(name.length() > 1, "Name must be at least two characters");
 145         Asserts.assertTrue(name.length() < 32, "Name should not exceed 32 characters");
 146         Asserts.assertFalse(isReservedKeyword(name),"Name must not be reserved keyword in the Java language (" + name + ")");
 147         char firstChar = name.charAt(0);
 148         Asserts.assertTrue(Character.isAlphabetic(firstChar), "Name must start with a character");
 149         Asserts.assertTrue(Character.isLowerCase(firstChar), "Name must start with lower case letter");
 150         Asserts.assertTrue(Character.isJavaIdentifierStart(firstChar), "Not valid first character for Java identifier");
 151         for (int i = 1; i < name.length(); i++) {
 152             Asserts.assertTrue(Character.isJavaIdentifierPart(name.charAt(i)), "Not valid character for a Java identifier");
 153             Asserts.assertTrue(Character.isAlphabetic(name.charAt(i)), "Name must consists of characters, found '" + name.charAt(i) + "'");
 154         }
 155         Asserts.assertFalse(name.contains("ID"), "'ID' should not be used in name, consider using 'Id'");
 156         checkCommonAbbreviations(name);
 157     }
 158 
 159     private static void verifyLabel(String label) {
 160         Asserts.assertNotEquals(label, null, "Label not allowed to be null");
 161         Asserts.assertTrue(label.length() > 1, "Name must be at least two characters");
 162         Asserts.assertTrue(label.length() < 45, "Label should not exceed 45 characters, use description to explain " + label);
 163         Asserts.assertTrue(label.length() == label.trim().length(), "Label should not have trim character at start and end");
 164         Asserts.assertTrue(Character.isUpperCase(label.charAt(0)), "Label should start with upper case letter");
 165         for (int i = 0; i < label.length(); i++) {
 166             char c = label.charAt(i);
 167             Asserts.assertTrue(Character.isDigit(c) || Character.isAlphabetic(label.charAt(i)) || c == ' ' || c == '(' || c == ')' || c == '-', "Label should only consist of letters or space, found '" + label.charAt(i)
 168                     + "'");
 169         }
 170     }
 171 
 172     private static void verifyEventType(EventType eventType) {
 173         System.out.println("Verifying event: " + eventType.getName());
 174         verifyDescription(eventType.getDescription());
 175         verifyLabel(eventType.getLabel());
 176         Asserts.assertNotEquals(eventType.getName(), null, "Name not allowed to be null");
 177         Asserts.assertTrue(eventType.getName().startsWith("com.oracle.jdk."), "Oracle events must start with com.oracle.jdk");
 178         String name = eventType.getName().substring("com.oracle.jdk.".length());
 179         Asserts.assertFalse(isReservedKeyword(name),"Name must not be reserved keyword in the Java language (" + name + ")");
 180         checkCommonAbbreviations(name);
 181           char firstChar = name.charAt(0);
 182         Asserts.assertFalse(name.contains("ID"), "'ID' should not be used in name, consider using 'Id'");
 183         Asserts.assertTrue(Character.isAlphabetic(firstChar), "Name " + name + " must start with a character");
 184         Asserts.assertTrue(Character.isUpperCase(firstChar), "Name " + name + " must start with upper case letter");
 185         for (int i = 0; i < name.length(); i++) {
 186             char c = name.charAt(i);
 187             Asserts.assertTrue(Character.isAlphabetic(c) || Character.isDigit(c), "Name " + name + " must consists of characters or numbers, found '" + name.charAt(i) + "'");
 188         }
 189     }
 190 
 191     static boolean isReservedKeyword(String s) {
 192         String[] keywords = new String[] {
 193                 // "module", "requires", "exports", "to", "uses", "provides", "with", module-info.java
 194                 "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum",
 195                 "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private",
 196                 "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "void", "volatile", "while" };
 197         for (int i = 0; i < keywords.length; i++) {
 198             if (s.equals(keywords[i])) {
 199                 return true;
 200             }
 201         }
 202         return false;
 203     }
 204 
 205     private static void checkCommonAbbreviations(String name) {
 206         String lowerCased = name.toLowerCase();
 207         Asserts.assertFalse(lowerCased.contains("info") && !lowerCased.contains("information"), "Use 'information' instead 'info' in name");
 208         Asserts.assertFalse(lowerCased.contains("alloc") && !lowerCased.contains("alloca"), "Use 'allocation' instead 'alloc' in name");
 209         Asserts.assertFalse(lowerCased.contains("config") && !lowerCased.contains("configuration"), "Use 'configuration' instead of 'config' in name");
 210         Asserts.assertFalse(lowerCased.contains("evac") && !lowerCased.contains("evacu"), "Use 'evacuation' instead of 'evac' in name");
 211         Asserts.assertFalse(lowerCased.contains("stat") && !(lowerCased.contains("state") ||lowerCased.contains("statistic")) , "Use 'statistics' instead of 'stat' in name");
 212         Asserts.assertFalse(name.contains("ID") , "Use 'id' or 'Id' instead of 'ID' in name");
 213     }
 214 }