1 /*
2  * Copyright (c) 2016, 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.internal;
27 
28 import static java.util.concurrent.TimeUnit.MICROSECONDS;
29 import static java.util.concurrent.TimeUnit.MILLISECONDS;
30 import static java.util.concurrent.TimeUnit.NANOSECONDS;
31 import static java.util.concurrent.TimeUnit.SECONDS;
32 
33 import java.io.FileOutputStream;
34 import java.io.FileWriter;
35 import java.io.IOException;
36 import java.io.PrintWriter;
37 import java.io.RandomAccessFile;
38 import java.lang.annotation.Annotation;
39 import java.lang.annotation.Repeatable;
40 import java.lang.reflect.Field;
41 import java.lang.reflect.InvocationTargetException;
42 import java.lang.reflect.Method;
43 import java.lang.reflect.Modifier;
44 import java.nio.file.Path;
45 import java.time.Duration;
46 import java.time.LocalDateTime;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Objects;
54 
55 import jdk.internal.org.objectweb.asm.ClassReader;
56 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
57 import jdk.jfr.Event;
58 import jdk.jfr.FlightRecorderPermission;
59 import jdk.jfr.Recording;
60 import jdk.jfr.RecordingState;
61 import jdk.jfr.internal.handlers.EventHandler;
62 import jdk.jfr.internal.settings.PeriodSetting;
63 import jdk.jfr.internal.settings.StackTraceSetting;
64 import jdk.jfr.internal.settings.ThresholdSetting;
65 
66 public final class Utils {
67 
68     private static final String INFINITY = "infinity";
69 
70     private static Boolean SAVE_GENERATED;
71 
72     public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events";
73     public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument";
74     public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers";
75     public static final String REGISTER_EVENT = "registerEvent";
76     public static final String ACCESS_FLIGHT_RECORDER = "accessFlightRecorder";
77 
78     private final static String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk.";
79 
checkAccessFlightRecorder()80     public static void checkAccessFlightRecorder() throws SecurityException {
81         SecurityManager sm = System.getSecurityManager();
82         if (sm != null) {
83             sm.checkPermission(new FlightRecorderPermission(ACCESS_FLIGHT_RECORDER));
84         }
85     }
86 
checkRegisterPermission()87     public static void checkRegisterPermission() throws SecurityException {
88         SecurityManager sm = System.getSecurityManager();
89         if (sm != null) {
90             sm.checkPermission(new FlightRecorderPermission(REGISTER_EVENT));
91         }
92     }
93 
94     private static enum TimespanUnit {
95         NANOSECONDS("ns", 1000), MICROSECONDS("us", 1000), MILLISECONDS("ms", 1000), SECONDS("s", 60), MINUTES("m", 60), HOURS("h", 24), DAYS("d", 7);
96 
97         final String text;
98         final long amount;
99 
TimespanUnit(String unit, long amount)100         TimespanUnit(String unit, long amount) {
101             this.text = unit;
102             this.amount = amount;
103         }
104     }
105 
106     // Tjis method can't handle Long.MIN_VALUE because absolute value is negative
formatDataAmount(String formatter, long amount)107     private static String formatDataAmount(String formatter, long amount) {
108         int exp = (int) (Math.log(Math.abs(amount)) / Math.log(1024));
109         char unitPrefix = "kMGTPE".charAt(exp - 1);
110         return String.format(formatter, amount / Math.pow(1024, exp), unitPrefix);
111     }
112 
formatBytesCompact(long bytes)113     public static String formatBytesCompact(long bytes) {
114         if (bytes < 1024) {
115             return String.valueOf(bytes);
116         }
117         return formatDataAmount("%.1f%cB", bytes);
118     }
119 
formatBits(long bits)120     public static String formatBits(long bits) {
121         if (bits == 1 || bits == -1) {
122             return bits + " bit";
123         }
124         if (bits < 1024 && bits > -1024) {
125             return bits + " bits";
126         }
127         return formatDataAmount("%.1f %cbit", bits);
128     }
129 
formatBytes(long bytes)130     public static String formatBytes(long bytes) {
131         if (bytes == 1 || bytes == -1) {
132             return bytes + " byte";
133         }
134         if (bytes < 1024 && bytes > -1024) {
135             return bytes + " bytes";
136         }
137         return formatDataAmount("%.1f %cB", bytes);
138     }
139 
formatBytesPerSecond(long bytes)140     public static String formatBytesPerSecond(long bytes) {
141         if (bytes < 1024 && bytes > -1024) {
142             return bytes + " byte/s";
143         }
144         return formatDataAmount("%.1f %cB/s", bytes);
145     }
146 
formatBitsPerSecond(long bits)147     public static String formatBitsPerSecond(long bits) {
148         if (bits < 1024 && bits > -1024) {
149             return bits + " bps";
150         }
151         return formatDataAmount("%.1f %cbps", bits);
152     }
formatTimespan(Duration dValue, String separation)153     public static String formatTimespan(Duration dValue, String separation) {
154         if (dValue == null) {
155             return "0";
156         }
157         long value = dValue.toNanos();
158         TimespanUnit result = TimespanUnit.NANOSECONDS;
159         for (TimespanUnit unit : TimespanUnit.values()) {
160             result = unit;
161             long amount = unit.amount;
162             if (result == TimespanUnit.DAYS || value < amount || value % amount != 0) {
163                 break;
164             }
165             value /= amount;
166         }
167         return String.format("%d%s%s", value, separation, result.text);
168     }
169 
parseTimespanWithInfinity(String s)170     public static long parseTimespanWithInfinity(String s) {
171         if (INFINITY.equals(s)) {
172             return Long.MAX_VALUE;
173         }
174         return parseTimespan(s);
175     }
176 
parseTimespan(String s)177     public static long parseTimespan(String s) {
178         if (s.endsWith("ns")) {
179             return Long.parseLong(s.substring(0, s.length() - 2).trim());
180         }
181         if (s.endsWith("us")) {
182             return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 2).trim()), MICROSECONDS);
183         }
184         if (s.endsWith("ms")) {
185             return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 2).trim()), MILLISECONDS);
186         }
187         if (s.endsWith("s")) {
188             return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
189         }
190         if (s.endsWith("m")) {
191             return 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
192         }
193         if (s.endsWith("h")) {
194             return 60 * 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
195         }
196         if (s.endsWith("d")) {
197             return 24 * 60 * 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS);
198         }
199 
200         try {
201             Long.parseLong(s);
202         } catch (NumberFormatException nfe) {
203             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.");
204         }
205         // Only accept values with units
206         throw new NumberFormatException("Timespan + '" + s + "' is missing unit. Valid units are ns, us, s, m, h and d.");
207     }
208 
209     /**
210      * Return all annotations as they are visible in the source code
211      *
212      * @param clazz class to return annotations from
213      *
214      * @return list of annotation
215      *
216      */
getAnnotations(Class<?> clazz)217     static List<Annotation> getAnnotations(Class<?> clazz) {
218         List<Annotation> annos = new ArrayList<>();
219         for (Annotation a : clazz.getAnnotations()) {
220             annos.addAll(getAnnotation(a));
221         }
222         return annos;
223     }
224 
getAnnotation(Annotation a)225     private static List<? extends Annotation> getAnnotation(Annotation a) {
226         Class<?> annotated = a.annotationType();
227         Method valueMethod = getValueMethod(annotated);
228         if (valueMethod != null) {
229             Class<?> returnType = valueMethod.getReturnType();
230             if (returnType.isArray()) {
231                 Class<?> candidate = returnType.getComponentType();
232                 Repeatable r = candidate.getAnnotation(Repeatable.class);
233                 if (r != null) {
234                     Class<?> repeatClass = r.value();
235                     if (annotated == repeatClass) {
236                         return getAnnotationValues(a, valueMethod);
237                     }
238                 }
239             }
240         }
241         List<Annotation> annos = new ArrayList<>();
242         annos.add(a);
243         return annos;
244     }
245 
isAfter(RecordingState stateToTest, RecordingState b)246     static boolean isAfter(RecordingState stateToTest, RecordingState b) {
247         return stateToTest.ordinal() > b.ordinal();
248     }
249 
isBefore(RecordingState stateToTest, RecordingState b)250     static boolean isBefore(RecordingState stateToTest, RecordingState b) {
251         return stateToTest.ordinal() < b.ordinal();
252     }
253 
isState(RecordingState stateToTest, RecordingState... states)254     static boolean isState(RecordingState stateToTest, RecordingState... states) {
255         for (RecordingState s : states) {
256             if (s == stateToTest) {
257                 return true;
258             }
259         }
260         return false;
261     }
262 
getAnnotationValues(Annotation a, Method valueMethod)263     private static List<Annotation> getAnnotationValues(Annotation a, Method valueMethod) {
264         try {
265             return Arrays.asList((Annotation[]) valueMethod.invoke(a, new Object[0]));
266         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
267             return new ArrayList<>();
268         }
269     }
270 
getValueMethod(Class<?> annotated)271     private static Method getValueMethod(Class<?> annotated) {
272         try {
273             return annotated.getMethod("value", new Class<?>[0]);
274         } catch (NoSuchMethodException e) {
275             return null;
276         }
277     }
278 
touch(Path dumpFile)279     public static void touch(Path dumpFile) throws IOException {
280         RandomAccessFile raf = new RandomAccessFile(dumpFile.toFile(), "rw");
281         raf.close();
282     }
283 
unboxType(Class<?> t)284     public static Class<?> unboxType(Class<?> t) {
285         if (t == Integer.class) {
286             return int.class;
287         }
288         if (t == Long.class) {
289             return long.class;
290         }
291         if (t == Float.class) {
292             return float.class;
293         }
294         if (t == Double.class) {
295             return double.class;
296         }
297         if (t == Byte.class) {
298             return byte.class;
299         }
300         if (t == Short.class) {
301             return short.class;
302         }
303         if (t == Boolean.class) {
304             return boolean.class;
305         }
306         if (t == Character.class) {
307             return char.class;
308         }
309         return t;
310     }
311 
nanosToTicks(long nanos)312     static long nanosToTicks(long nanos) {
313         return (long) (nanos * JVM.getJVM().getTimeConversionFactor());
314     }
315 
getHandler(Class<? extends jdk.internal.event.Event> eventClass)316     static synchronized EventHandler getHandler(Class<? extends jdk.internal.event.Event> eventClass) {
317         Utils.ensureValidEventSubclass(eventClass);
318         try {
319             Field f = eventClass.getDeclaredField(EventInstrumentation.FIELD_EVENT_HANDLER);
320             SecuritySupport.setAccessible(f);
321             return (EventHandler) f.get(null);
322         } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
323             throw new InternalError("Could not access event handler");
324         }
325     }
326 
setHandler(Class<? extends jdk.internal.event.Event> eventClass, EventHandler handler)327     static synchronized void setHandler(Class<? extends jdk.internal.event.Event> eventClass, EventHandler handler) {
328         Utils.ensureValidEventSubclass(eventClass);
329         try {
330             Field field = eventClass.getDeclaredField(EventInstrumentation.FIELD_EVENT_HANDLER);
331             SecuritySupport.setAccessible(field);
332             field.set(null, handler);
333         } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
334             throw new InternalError("Could not access event handler");
335         }
336     }
337 
sanitizeNullFreeStringMap(Map<String, String> settings)338     public static Map<String, String> sanitizeNullFreeStringMap(Map<String, String> settings) {
339         HashMap<String, String> map = new HashMap<>(settings.size());
340         for (Map.Entry<String, String> e : settings.entrySet()) {
341             String key = e.getKey();
342             if (key == null) {
343                 throw new NullPointerException("Null key is not allowed in map");
344             }
345             String value = e.getValue();
346             if (value == null) {
347                 throw new NullPointerException("Null value is not allowed in map");
348             }
349             map.put(key, value);
350         }
351         return map;
352     }
353 
sanitizeNullFreeList(List<T> elements, Class<T> clazz)354     public static <T> List<T> sanitizeNullFreeList(List<T> elements, Class<T> clazz) {
355         List<T> sanitized = new ArrayList<>(elements.size());
356         for (T element : elements) {
357             if (element == null) {
358                 throw new NullPointerException("Null is not an allowed element in list");
359             }
360             if (element.getClass() != clazz) {
361                 throw new ClassCastException();
362             }
363             sanitized.add(element);
364         }
365         return sanitized;
366     }
367 
getVisibleEventFields(Class<?> clazz)368     static List<Field> getVisibleEventFields(Class<?> clazz) {
369         Utils.ensureValidEventSubclass(clazz);
370         List<Field> fields = new ArrayList<>();
371         for (Class<?> c = clazz; c != jdk.internal.event.Event.class; c = c.getSuperclass()) {
372             for (Field field : c.getDeclaredFields()) {
373                 // skip private field in base classes
374                 if (c == clazz || !Modifier.isPrivate(field.getModifiers())) {
375                     fields.add(field);
376                 }
377             }
378         }
379         return fields;
380     }
381 
ensureValidEventSubclass(Class<?> eventClass)382     public static void ensureValidEventSubclass(Class<?> eventClass) {
383         if (jdk.internal.event.Event.class.isAssignableFrom(eventClass) && Modifier.isAbstract(eventClass.getModifiers())) {
384             throw new IllegalArgumentException("Abstract event classes are not allowed");
385         }
386         if (eventClass == Event.class || eventClass == jdk.internal.event.Event.class || !jdk.internal.event.Event.class.isAssignableFrom(eventClass)) {
387             throw new IllegalArgumentException("Must be a subclass to " + Event.class.getName());
388         }
389     }
390 
writeGeneratedASM(String className, byte[] bytes)391     public static void writeGeneratedASM(String className, byte[] bytes) {
392         if (SAVE_GENERATED == null) {
393             // We can't calculate value statically because it will force
394             // initialization of SecuritySupport, which cause
395             // UnsatisfiedLinkedError on JDK 8 or non-Oracle JDKs
396             SAVE_GENERATED = SecuritySupport.getBooleanProperty("jfr.save.generated.asm");
397         }
398         if (SAVE_GENERATED) {
399             try {
400                 try (FileOutputStream fos = new FileOutputStream(className + ".class")) {
401                     fos.write(bytes);
402                 }
403 
404                 try (FileWriter fw = new FileWriter(className + ".asm"); PrintWriter pw = new PrintWriter(fw)) {
405                     ClassReader cr = new ClassReader(bytes);
406                     CheckClassAdapter.verify(cr, true, pw);
407                 }
408                 Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Instrumented code saved to " + className + ".class and .asm");
409             } catch (IOException e) {
410                 Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Could not save instrumented code, for " + className + ".class and .asm");
411             }
412         }
413     }
414 
ensureInitialized(Class<? extends jdk.internal.event.Event> eventClass)415     public static void ensureInitialized(Class<? extends jdk.internal.event.Event> eventClass) {
416         SecuritySupport.ensureClassIsInitialized(eventClass);
417     }
418 
makePrimitiveArray(String typeName, List<Object> values)419     public static Object makePrimitiveArray(String typeName, List<Object> values) {
420         int length = values.size();
421         switch (typeName) {
422         case "int":
423             int[] ints = new int[length];
424             for (int i = 0; i < length; i++) {
425                 ints[i] = (int) values.get(i);
426             }
427             return ints;
428         case "long":
429             long[] longs = new long[length];
430             for (int i = 0; i < length; i++) {
431                 longs[i] = (long) values.get(i);
432             }
433             return longs;
434 
435         case "float":
436             float[] floats = new float[length];
437             for (int i = 0; i < length; i++) {
438                 floats[i] = (float) values.get(i);
439             }
440             return floats;
441 
442         case "double":
443             double[] doubles = new double[length];
444             for (int i = 0; i < length; i++) {
445                 doubles[i] = (double) values.get(i);
446             }
447             return doubles;
448 
449         case "short":
450             short[] shorts = new short[length];
451             for (int i = 0; i < length; i++) {
452                 shorts[i] = (short) values.get(i);
453             }
454             return shorts;
455         case "char":
456             char[] chars = new char[length];
457             for (int i = 0; i < length; i++) {
458                 chars[i] = (char) values.get(i);
459             }
460             return chars;
461         case "byte":
462             byte[] bytes = new byte[length];
463             for (int i = 0; i < length; i++) {
464                 bytes[i] = (byte) values.get(i);
465             }
466             return bytes;
467         case "boolean":
468             boolean[] booleans = new boolean[length];
469             for (int i = 0; i < length; i++) {
470                 booleans[i] = (boolean) values.get(i);
471             }
472             return booleans;
473         case "java.lang.String":
474             String[] strings = new String[length];
475             for (int i = 0; i < length; i++) {
476                 strings[i] = (String) values.get(i);
477             }
478             return strings;
479         }
480         return null;
481     }
482 
isSettingVisible(Control c, boolean hasEventHook)483     public static boolean isSettingVisible(Control c, boolean hasEventHook) {
484         if (c instanceof ThresholdSetting) {
485             return !hasEventHook;
486         }
487         if (c instanceof PeriodSetting) {
488             return hasEventHook;
489         }
490         if (c instanceof StackTraceSetting) {
491             return !hasEventHook;
492         }
493         return true;
494     }
495 
isSettingVisible(long typeId, boolean hasEventHook)496     public static boolean isSettingVisible(long typeId, boolean hasEventHook) {
497         if (ThresholdSetting.isType(typeId)) {
498             return !hasEventHook;
499         }
500         if (PeriodSetting.isType(typeId)) {
501             return hasEventHook;
502         }
503         if (StackTraceSetting.isType(typeId)) {
504             return !hasEventHook;
505         }
506         return true;
507     }
508 
getValidType(Class<?> type, String name)509     public static Type getValidType(Class<?> type, String name) {
510         Objects.requireNonNull(type, "Null is not a valid type for value descriptor " + name);
511         if (type.isArray()) {
512             type = type.getComponentType();
513             if (type != String.class && !type.isPrimitive()) {
514                 throw new IllegalArgumentException("Only arrays of primitives and Strings are allowed");
515             }
516         }
517 
518         Type knownType = Type.getKnownType(type);
519         if (knownType == null || knownType == Type.STACK_TRACE) {
520             throw new IllegalArgumentException("Only primitive types, java.lang.Thread, java.lang.String and java.lang.Class are allowed for value descriptors. " + type.getName());
521         }
522         return knownType;
523     }
524 
smallUnmodifiable(List<T> list)525     public static <T> List<T> smallUnmodifiable(List<T> list) {
526         if (list.isEmpty()) {
527             return Collections.emptyList();
528         }
529         if (list.size() == 1) {
530             return Collections.singletonList(list.get(0));
531         }
532         return Collections.unmodifiableList(list);
533     }
534 
upgradeLegacyJDKEvent(String eventName)535     public static String upgradeLegacyJDKEvent(String eventName) {
536         if (eventName.length() <= LEGACY_EVENT_NAME_PREFIX.length()) {
537             return eventName;
538         }
539         if (eventName.startsWith(LEGACY_EVENT_NAME_PREFIX)) {
540             int index = eventName.lastIndexOf(".");
541             if (index == LEGACY_EVENT_NAME_PREFIX.length() - 1) {
542                 return Type.EVENT_NAME_PREFIX + eventName.substring(index + 1);
543             }
544         }
545         return eventName;
546     }
547 
verifyMirror(Class<?> mirror, Class<?> real)548     public static void verifyMirror(Class<?> mirror, Class<?> real) {
549         Class<?> cMirror = Objects.requireNonNull(mirror);
550         Class<?> cReal = Objects.requireNonNull(real);
551 
552         while (cReal != null) {
553             Map<String, Field> mirrorFields = new HashMap<>();
554             if (cMirror != null) {
555                 for (Field f : cMirror.getDeclaredFields()) {
556                     if (isSupportedType(f.getType())) {
557                         mirrorFields.put(f.getName(), f);
558                     }
559                 }
560             }
561             for (Field realField : cReal.getDeclaredFields()) {
562                 if (isSupportedType(realField.getType())) {
563                     String fieldName = realField.getName();
564                     Field mirrorField = mirrorFields.get(fieldName);
565                     if (mirrorField == null) {
566                         throw new InternalError("Missing mirror field for " + cReal.getName() + "#" + fieldName);
567                     }
568                     if (realField.getModifiers() != mirrorField.getModifiers()) {
569                         throw new InternalError("Incorrect modifier for mirror field "+ cMirror.getName() + "#" + fieldName);
570                     }
571                     mirrorFields.remove(fieldName);
572                 }
573             }
574             if (!mirrorFields.isEmpty()) {
575                 throw new InternalError(
576                         "Found additional fields in mirror class " + cMirror.getName() + " " + mirrorFields.keySet());
577             }
578             if (cMirror != null) {
579                 cMirror = cMirror.getSuperclass();
580             }
581             cReal = cReal.getSuperclass();
582         }
583     }
584 
isSupportedType(Class<?> type)585     private static boolean isSupportedType(Class<?> type) {
586         if (Modifier.isTransient(type.getModifiers()) || Modifier.isStatic(type.getModifiers())) {
587             return false;
588         }
589         return Type.isValidJavaFieldType(type.getName());
590     }
591 
makeFilename(Recording recording)592     public static String makeFilename(Recording recording) {
593         String pid = JVM.getJVM().getPid();
594         String date = Repository.REPO_DATE_FORMAT.format(LocalDateTime.now());
595         String idText = recording == null ? "" :  "-id-" + Long.toString(recording.getId());
596         return "hotspot-" + "pid-" + pid + idText + "-" + date + ".jfr";
597     }
598 }
599