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 }