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