test/jdk/jdk/jfr/event/metadata/TestEventMetadata.java
changeset 50113 caf115bb98ad
child 51214 67736b4846a0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/event/metadata/TestEventMetadata.java	Tue May 15 20:24:34 2018 +0200
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr.event.metadata;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import jdk.jfr.EventType;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.ValueDescriptor;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.jfr.EventNames;
+
+/*
+ * @test
+ * @key jfr
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.event.metadata.TestEventMetadata
+ */
+
+public class TestEventMetadata {
+
+    /*
+     * Short guide to writing event metadata
+     * =====================================
+
+     * Name
+     * ----
+     *
+     * Symbolic name that is used to identify an event, or a field. Referred to
+     * as "id" and "field" in trace.xml-files and @Name in the Java API. If it is
+     * the name of an event, the name should be prefixed "jdk.", which
+     * happens automatically for native events.
+     *
+     * The name should be short, but not so brief that collision is likely with
+     * future events or fields. It should only consist of letters and numbers.
+     * Use Java naming convention , i.e. "FileRead" for an event and
+     * "allocationRate" for a field. Do not use "_" and don't add the word
+     * "Event" to the event name.
+     *
+     * Abbreviations should be avoided, but may be acceptable if the name
+     * becomes long, or if it is a well established acronym. Write whole words,
+     * i.e. "allocation" instead of "alloc". The name should not be a reserved
+     * Java keyword, i.e "void" or "class".
+     *
+     * Label
+     * -----
+     *
+     * Describes a human-readable name, typically 1-3 words. Use headline-style
+     * capitalization, capitalize the first and last words, and all nouns,
+     * pronouns, adjectives, verbs and adverbs. Do not include ending
+     * punctuation.
+     *
+     * Description
+     * -----------
+     *
+     * Describes an event with a sentence or two. It's better to omit the
+     * description then copying the label. Use sentence-style
+     * capitalization, capitalize the first letter of the first word, and any
+     * proper names such as the word Java. If the description is one sentence,
+     * period should not be included.
+     *
+     *
+     * Do not forget to set proper units for fields, i.e "NANOS", "MILLS",
+     * "TICKSPAN" ,"BYETS", "PECENTAGE" etc. in native and @Timespan, @Timespan
+     * etc. in Java.
+     */
+    public static void main(String[] args) throws Exception {
+        Set<String> types = new HashSet<>();
+        List<EventType> eventTypes = FlightRecorder.getFlightRecorder().getEventTypes();
+        Set<String> eventNames= new HashSet<>();
+        for (EventType eventType : eventTypes) {
+            verifyEventType(eventType);
+            verifyValueDesscriptors(eventType.getFields(), types);
+            System.out.println();
+            String eventName = eventType.getName();
+            if (eventNames.contains(eventName)) {
+                throw new Exception("Event with name " +eventName+ " already exists");
+            }
+            eventNames.add(eventName);
+            Set<String> fieldNames = new HashSet<>();
+            for (ValueDescriptor v : eventType.getFields()) {
+                String fieldName = v.getName();
+                if (fieldNames.contains(fieldName)) {
+                    throw new Exception("Field with name " + fieldName +" is already in use in event name " +eventName);
+                }
+                fieldNames.add(fieldName);
+            }
+        }
+    }
+
+    private static void verifyValueDesscriptors(List<ValueDescriptor> fields, Set<String> visitedTypes) {
+        for (ValueDescriptor v : fields) {
+            if (!visitedTypes.contains(v.getTypeName())) {
+                visitedTypes.add(v.getTypeName());
+                verifyValueDesscriptors(v.getFields(), visitedTypes);
+            }
+            verifyValueDescriptor(v);
+        }
+    }
+
+    private static void verifyValueDescriptor(ValueDescriptor v) {
+        verifyName(v.getName());
+        verifyLabel(v.getLabel());
+        verifyDescription(v.getDescription());
+    }
+
+    private static void verifyDescription(String description) {
+        if (description == null) {
+            return;
+        }
+        Asserts.assertTrue(description.length() > 10, "Description must be at least ten characters");
+        Asserts.assertTrue(description.length() < 300, "Description should not exceed 300 characters. Found " + description);
+        Asserts.assertTrue(description.length() == description.trim().length(), "Description should not have trim character at start or end");
+        Asserts.assertFalse(description.endsWith(".") && description.indexOf(".") == description.length() - 1, "Single sentence descriptions should not use end punctuation");
+    }
+
+    private static void verifyName(String name) {
+        System.out.println("Verifying name: " + name);
+        Asserts.assertNotEquals(name, null, "Name not allowed to be null");
+        Asserts.assertTrue(name.length() > 1, "Name must be at least two characters");
+        Asserts.assertTrue(name.length() < 32, "Name should not exceed 32 characters");
+        Asserts.assertFalse(isReservedKeyword(name),"Name must not be reserved keyword in the Java language (" + name + ")");
+        char firstChar = name.charAt(0);
+        Asserts.assertTrue(Character.isAlphabetic(firstChar), "Name must start with a character");
+        Asserts.assertTrue(Character.isLowerCase(firstChar), "Name must start with lower case letter");
+        Asserts.assertTrue(Character.isJavaIdentifierStart(firstChar), "Not valid first character for Java identifier");
+        for (int i = 1; i < name.length(); i++) {
+            Asserts.assertTrue(Character.isJavaIdentifierPart(name.charAt(i)), "Not valid character for a Java identifier");
+            Asserts.assertTrue(Character.isAlphabetic(name.charAt(i)), "Name must consists of characters, found '" + name.charAt(i) + "'");
+        }
+        Asserts.assertFalse(name.contains("ID"), "'ID' should not be used in name, consider using 'Id'");
+        checkCommonAbbreviations(name);
+    }
+
+    private static void verifyLabel(String label) {
+        Asserts.assertNotEquals(label, null, "Label not allowed to be null");
+        Asserts.assertTrue(label.length() > 1, "Name must be at least two characters");
+        Asserts.assertTrue(label.length() < 45, "Label should not exceed 45 characters, use description to explain " + label);
+        Asserts.assertTrue(label.length() == label.trim().length(), "Label should not have trim character at start and end");
+        Asserts.assertTrue(Character.isUpperCase(label.charAt(0)), "Label should start with upper case letter");
+        for (int i = 0; i < label.length(); i++) {
+            char c = label.charAt(i);
+            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)
+                    + "'");
+        }
+    }
+
+    private static void verifyEventType(EventType eventType) {
+        System.out.println("Verifying event: " + eventType.getName());
+        verifyDescription(eventType.getDescription());
+        verifyLabel(eventType.getLabel());
+        Asserts.assertNotEquals(eventType.getName(), null, "Name not allowed to be null");
+        Asserts.assertTrue(eventType.getName().startsWith(EventNames.PREFIX), "OpenJDK events must start with " + EventNames.PREFIX);
+        String name = eventType.getName().substring(EventNames.PREFIX.length());
+        Asserts.assertFalse(isReservedKeyword(name),"Name must not be reserved keyword in the Java language (" + name + ")");
+        checkCommonAbbreviations(name);
+          char firstChar = name.charAt(0);
+        Asserts.assertFalse(name.contains("ID"), "'ID' should not be used in name, consider using 'Id'");
+        Asserts.assertTrue(Character.isAlphabetic(firstChar), "Name " + name + " must start with a character");
+        Asserts.assertTrue(Character.isUpperCase(firstChar), "Name " + name + " must start with upper case letter");
+        for (int i = 0; i < name.length(); i++) {
+            char c = name.charAt(i);
+            Asserts.assertTrue(Character.isAlphabetic(c) || Character.isDigit(c), "Name " + name + " must consists of characters or numbers, found '" + name.charAt(i) + "'");
+        }
+    }
+
+    static boolean isReservedKeyword(String s) {
+        String[] keywords = new String[] {
+                // "module", "requires", "exports", "to", "uses", "provides", "with", module-info.java
+                "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum",
+                "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private",
+                "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "void", "volatile", "while" };
+        for (int i = 0; i < keywords.length; i++) {
+            if (s.equals(keywords[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static void checkCommonAbbreviations(String name) {
+        String lowerCased = name.toLowerCase();
+        Asserts.assertFalse(lowerCased.contains("info") && !lowerCased.contains("information"), "Use 'information' instead 'info' in name");
+        Asserts.assertFalse(lowerCased.contains("alloc") && !lowerCased.contains("alloca"), "Use 'allocation' instead 'alloc' in name");
+        Asserts.assertFalse(lowerCased.contains("config") && !lowerCased.contains("configuration"), "Use 'configuration' instead of 'config' in name");
+        Asserts.assertFalse(lowerCased.contains("evac") && !lowerCased.contains("evacu"), "Use 'evacuation' instead of 'evac' in name");
+        Asserts.assertFalse(lowerCased.contains("stat") && !(lowerCased.contains("state") ||lowerCased.contains("statistic")) , "Use 'statistics' instead of 'stat' in name");
+        Asserts.assertFalse(name.contains("ID") , "Use 'id' or 'Id' instead of 'ID' in name");
+    }
+}