src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java
changeset 50113 caf115bb98ad
child 50745 a390cbb82d47
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java	Tue May 15 20:24:34 2018 +0200
@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2016, 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.internal;
+
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Repeatable;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import jdk.internal.org.objectweb.asm.ClassReader;
+import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorderPermission;
+import jdk.jfr.RecordingState;
+import jdk.jfr.internal.handlers.EventHandler;
+import jdk.jfr.internal.settings.PeriodSetting;
+import jdk.jfr.internal.settings.StackTraceSetting;
+import jdk.jfr.internal.settings.ThresholdSetting;
+
+public final class Utils {
+
+    private static Boolean SAVE_GENERATED;
+
+    public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events";
+    public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument";
+    public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers";
+    public static final String REGISTER_EVENT = "registerEvent";
+    public static final String ACCESS_FLIGHT_RECORDER = "accessFlightRecorder";
+
+    private final static String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk.";
+
+    public static void checkAccessFlightRecorder() throws SecurityException {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(new FlightRecorderPermission(ACCESS_FLIGHT_RECORDER));
+        }
+    }
+
+    public static void checkRegisterPermission() throws SecurityException {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(new FlightRecorderPermission(REGISTER_EVENT));
+        }
+    }
+
+    private static enum TimespanUnit {
+        NANOSECONDS("ns", 1000), MICROSECONDS("us", 1000), MILLISECONDS("ms", 1000), SECONDS("s", 60), MINUTES("m", 60), HOURS("h", 24), DAYS("d", 7);
+
+        final String text;
+        final long amount;
+
+        TimespanUnit(String unit, long amount) {
+            this.text = unit;
+            this.amount = amount;
+        }
+    }
+
+    public static String formatBytes(long bytes, String separation) {
+        if (bytes < 1024) {
+            return bytes + " bytes";
+        }
+        int exp = (int) (Math.log(bytes) / Math.log(1024));
+        char bytePrefix = "kMGTPE".charAt(exp - 1);
+        return String.format("%.1f%s%cB", bytes / Math.pow(1024, exp), separation, bytePrefix);
+    }
+
+    public static String formatTimespan(Duration dValue, String separation) {
+        if (dValue == null) {
+            return "0";
+        }
+
+        long value = dValue.toNanos();
+        TimespanUnit result = TimespanUnit.NANOSECONDS;
+        for (TimespanUnit unit : TimespanUnit.values()) {
+            result = unit;
+            long amount = unit.amount;
+            if (result == TimespanUnit.DAYS || value < amount || value % amount != 0) {
+                break;
+            }
+            value /= amount;
+        }
+        return String.format("%d%s%s", value, separation, result.text);
+    }
+
+    public static long parseTimespan(String s) {
+        if (s.endsWith("ns")) {
+            return Long.parseLong(s.substring(0, s.length() - 2).trim());
+        }
+        if (s.endsWith("us")) {
+            return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 2).trim()), MICROSECONDS);
+        }
+        if (s.endsWith("ms")) {
+            return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 2).trim()), MILLISECONDS);
+        }
+        if (s.endsWith("s")) {
+            return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
+        }
+        if (s.endsWith("m")) {
+            return 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
+        }
+        if (s.endsWith("h")) {
+            return 60 * 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
+        }
+        if (s.endsWith("d")) {
+            return 24 * 60 * 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
+        }
+
+        try {
+            Long.parseLong(s);
+        } catch (NumberFormatException nfe) {
+            throw new NumberFormatException("'" + s + "' is not a valid timespan. Shoule be numeric value followed by a unit, i.e. 20 ms. Valid units are ns, us, s, m, h and d.");
+        }
+        // Only accept values with units
+        throw new NumberFormatException("Timespan + '" + s + "' is missing unit. Valid units are ns, us, s, m, h and d.");
+    }
+
+    /**
+     * Return all annotations as they are visible in the source code
+     *
+     * @param clazz class to return annotations from
+     *
+     * @return list of annotation
+     *
+     */
+    static List<Annotation> getAnnotations(Class<?> clazz) {
+        List<Annotation> annos = new ArrayList<>();
+        for (Annotation a : clazz.getAnnotations()) {
+            annos.addAll(getAnnotation(a));
+        }
+        return annos;
+    }
+
+    private static List<? extends Annotation> getAnnotation(Annotation a) {
+        Class<?> annotated = a.annotationType();
+        Method valueMethod = getValueMethod(annotated);
+        if (valueMethod != null) {
+            Class<?> returnType = valueMethod.getReturnType();
+            if (returnType.isArray()) {
+                Class<?> candidate = returnType.getComponentType();
+                Repeatable r = candidate.getAnnotation(Repeatable.class);
+                if (r != null) {
+                    Class<?> repeatClass = r.value();
+                    if (annotated == repeatClass) {
+                        return getAnnotationValues(a, valueMethod);
+                    }
+                }
+            }
+        }
+        List<Annotation> annos = new ArrayList<>();
+        annos.add(a);
+        return annos;
+    }
+
+    static boolean isAfter(RecordingState stateToTest, RecordingState b) {
+        return stateToTest.ordinal() > b.ordinal();
+    }
+
+    static boolean isBefore(RecordingState stateToTest, RecordingState b) {
+        return stateToTest.ordinal() < b.ordinal();
+    }
+
+    static boolean isState(RecordingState stateToTest, RecordingState... states) {
+        for (RecordingState s : states) {
+            if (s == stateToTest) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static List<Annotation> getAnnotationValues(Annotation a, Method valueMethod) {
+        try {
+            return Arrays.asList((Annotation[]) valueMethod.invoke(a, new Object[0]));
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+            return new ArrayList<>();
+        }
+    }
+
+    private static Method getValueMethod(Class<?> annotated) {
+        try {
+            return annotated.getMethod("value", new Class<?>[0]);
+        } catch (NoSuchMethodException e) {
+            return null;
+        }
+    }
+
+    public static void touch(Path dumpFile) throws IOException {
+        RandomAccessFile raf = new RandomAccessFile(dumpFile.toFile(), "rw");
+        raf.close();
+    }
+
+    public static Class<?> unboxType(Class<?> t) {
+        if (t == Integer.class) {
+            return int.class;
+        }
+        if (t == Long.class) {
+            return long.class;
+        }
+        if (t == Float.class) {
+            return float.class;
+        }
+        if (t == Double.class) {
+            return double.class;
+        }
+        if (t == Byte.class) {
+            return byte.class;
+        }
+        if (t == Short.class) {
+            return short.class;
+        }
+        if (t == Boolean.class) {
+            return boolean.class;
+        }
+        if (t == Character.class) {
+            return char.class;
+        }
+        return t;
+    }
+
+    static long nanosToTicks(long nanos) {
+        return (long) (nanos * JVM.getJVM().getTimeConversionFactor());
+    }
+
+    static synchronized EventHandler getHandler(Class<? extends Event> eventClass) {
+        Utils.ensureValidEventSubclass(eventClass);
+        try {
+            Field f = eventClass.getDeclaredField(EventInstrumentation.FIELD_EVENT_HANDLER);
+            SecuritySupport.setAccessible(f);
+            return (EventHandler) f.get(null);
+        } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
+            throw new InternalError("Could not access event handler");
+        }
+    }
+
+    static synchronized void setHandler(Class<? extends Event> eventClass, EventHandler handler) {
+        Utils.ensureValidEventSubclass(eventClass);
+        try {
+            Field field = eventClass.getDeclaredField(EventInstrumentation.FIELD_EVENT_HANDLER);
+            SecuritySupport.setAccessible(field);
+            field.set(null, handler);
+        } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
+            throw new InternalError("Could not access event handler");
+        }
+    }
+
+    public static Map<String, String> sanitizeNullFreeStringMap(Map<String, String> settings) {
+        HashMap<String, String> map = new HashMap<>(settings.size());
+        for (Map.Entry<String, String> e : settings.entrySet()) {
+            String key = e.getKey();
+            if (key == null) {
+                throw new NullPointerException("Null key is not allowed in map");
+            }
+            String value = e.getValue();
+            if (value == null) {
+                throw new NullPointerException("Null value is not allowed in map");
+            }
+            map.put(key, value);
+        }
+        return map;
+    }
+
+    public static <T> List<T> sanitizeNullFreeList(List<T> elements, Class<T> clazz) {
+        List<T> sanitized = new ArrayList<>(elements.size());
+        for (T element : elements) {
+            if (element == null) {
+                throw new NullPointerException("Null is not an allowed element in list");
+            }
+            if (element.getClass() != clazz) {
+                throw new ClassCastException();
+            }
+            sanitized.add(element);
+        }
+        return sanitized;
+    }
+
+    static List<Field> getVisibleEventFields(Class<?> clazz) {
+        Utils.ensureValidEventSubclass(clazz);
+        List<Field> fields = new ArrayList<>();
+        for (Class<?> c = clazz; c != Event.class; c = c.getSuperclass()) {
+            for (Field field : c.getDeclaredFields()) {
+                // skip private field in base classes
+                if (c == clazz || !Modifier.isPrivate(field.getModifiers())) {
+                    fields.add(field);
+                }
+            }
+        }
+        return fields;
+    }
+
+    public static void ensureValidEventSubclass(Class<?> eventClass) {
+        if (Event.class.isAssignableFrom(eventClass) && Modifier.isAbstract(eventClass.getModifiers())) {
+            throw new IllegalArgumentException("Abstract event classes are not allowed");
+        }
+        if (eventClass == Event.class || !Event.class.isAssignableFrom(eventClass)) {
+            throw new IllegalArgumentException("Must be a subclass to " + Event.class.getName());
+        }
+    }
+
+    public static void writeGeneratedASM(String className, byte[] bytes) {
+        if (SAVE_GENERATED == null) {
+            // We can't calculate value statically because it will force
+            // initialization of SecuritySupport, which cause
+            // UnsatisfiedLinkedError on JDK 8 or non-Oracle JDKs
+            SAVE_GENERATED = SecuritySupport.getBooleanProperty("jfr.save.generated.asm");
+        }
+        if (SAVE_GENERATED) {
+            try {
+                try (FileOutputStream fos = new FileOutputStream(className + ".class")) {
+                    fos.write(bytes);
+                }
+
+                try (FileWriter fw = new FileWriter(className + ".asm"); PrintWriter pw = new PrintWriter(fw)) {
+                    ClassReader cr = new ClassReader(bytes);
+                    CheckClassAdapter.verify(cr, true, pw);
+                }
+                Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Instrumented code saved to " + className + ".class and .asm");
+            } catch (IOException e) {
+                Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Could not save instrumented code, for " + className + ".class and .asm");
+            }
+        }
+    }
+
+    public static void ensureInitialized(Class<? extends Event> eventClass) {
+        SecuritySupport.ensureClassIsInitialized(eventClass);
+    }
+
+    public static Object makePrimitiveArray(String typeName, List<Object> values) {
+        int length = values.size();
+        switch (typeName) {
+        case "int":
+            int[] ints = new int[length];
+            for (int i = 0; i < length; i++) {
+                ints[i] = (int) values.get(i);
+            }
+            return ints;
+        case "long":
+            long[] longs = new long[length];
+            for (int i = 0; i < length; i++) {
+                longs[i] = (long) values.get(i);
+            }
+            return longs;
+
+        case "float":
+            float[] floats = new float[length];
+            for (int i = 0; i < length; i++) {
+                floats[i] = (float) values.get(i);
+            }
+            return floats;
+
+        case "double":
+            double[] doubles = new double[length];
+            for (int i = 0; i < length; i++) {
+                doubles[i] = (double) values.get(i);
+            }
+            return doubles;
+
+        case "short":
+            short[] shorts = new short[length];
+            for (int i = 0; i < length; i++) {
+                shorts[i] = (short) values.get(i);
+            }
+            return shorts;
+        case "char":
+            char[] chars = new char[length];
+            for (int i = 0; i < length; i++) {
+                chars[i] = (char) values.get(i);
+            }
+            return chars;
+        case "byte":
+            byte[] bytes = new byte[length];
+            for (int i = 0; i < length; i++) {
+                bytes[i] = (byte) values.get(i);
+            }
+            return bytes;
+        case "boolean":
+            boolean[] booleans = new boolean[length];
+            for (int i = 0; i < length; i++) {
+                booleans[i] = (boolean) values.get(i);
+            }
+            return booleans;
+        case "java.lang.String":
+            String[] strings = new String[length];
+            for (int i = 0; i < length; i++) {
+                strings[i] = (String) values.get(i);
+            }
+            return strings;
+        }
+        return null;
+    }
+
+    public static boolean isSettingVisible(Control c, boolean hasEventHook) {
+        if (c instanceof ThresholdSetting) {
+            return !hasEventHook;
+        }
+        if (c instanceof PeriodSetting) {
+            return hasEventHook;
+        }
+        if (c instanceof StackTraceSetting) {
+            return !hasEventHook;
+        }
+        return true;
+    }
+
+    public static boolean isSettingVisible(long typeId, boolean hasEventHook) {
+        if (ThresholdSetting.isType(typeId)) {
+            return !hasEventHook;
+        }
+        if (PeriodSetting.isType(typeId)) {
+            return hasEventHook;
+        }
+        if (StackTraceSetting.isType(typeId)) {
+            return !hasEventHook;
+        }
+        return true;
+    }
+
+    public static Type getValidType(Class<?> type, String name) {
+        Objects.requireNonNull(type, "Null is not a valid type for value descriptor " + name);
+        if (type.isArray()) {
+            type = type.getComponentType();
+            if (type != String.class && !type.isPrimitive()) {
+                throw new IllegalArgumentException("Only arrays of primitives and Strings are allowed");
+            }
+        }
+
+        Type knownType = Type.getKnownType(type);
+        if (knownType == null || knownType == Type.STACK_TRACE) {
+            throw new IllegalArgumentException("Only primitive types, java.lang.Thread, java.lang.String and java.lang.Class are allowed for value descriptors. " + type.getName());
+        }
+        return knownType;
+    }
+
+    public static <T> List<T> smallUnmodifiable(List<T> list) {
+        if (list.isEmpty()) {
+            return Collections.emptyList();
+        }
+        if (list.size() == 1) {
+            return Collections.singletonList(list.get(0));
+        }
+        return Collections.unmodifiableList(list);
+    }
+
+    public static void updateSettingPathToGcRoots(Map<String, String> settings, Boolean pathToGcRoots) {
+        if (pathToGcRoots != null) {
+            settings.put(Type.EVENT_NAME_PREFIX + "OldObjectSample#cutoff", pathToGcRoots ? "infinity" : "0 ns" );
+        }
+    }
+
+
+    public static String upgradeLegacyJDKEvent(String eventName) {
+        if (eventName.length() <= LEGACY_EVENT_NAME_PREFIX.length()) {
+            return eventName;
+        }
+        if (eventName.startsWith(LEGACY_EVENT_NAME_PREFIX)) {
+            int index = eventName.lastIndexOf(".");
+            if (index == LEGACY_EVENT_NAME_PREFIX.length() - 1) {
+                return Type.EVENT_NAME_PREFIX + eventName.substring(index + 1);
+            }
+        }
+        return eventName;
+    }
+}