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.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.Collections; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Objects; 53 54 import jdk.internal.org.objectweb.asm.ClassReader; 55 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter; 56 import jdk.jfr.Event; 57 import jdk.jfr.FlightRecorderPermission; 58 import jdk.jfr.RecordingState; 59 import jdk.jfr.internal.handlers.EventHandler; 60 import jdk.jfr.internal.settings.PeriodSetting; 61 import jdk.jfr.internal.settings.StackTraceSetting; 62 import jdk.jfr.internal.settings.ThresholdSetting; 63 64 public final class Utils { 65 66 private static Boolean SAVE_GENERATED; 67 68 public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events"; 69 public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument"; 70 public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers"; 71 public static final String REGISTER_EVENT = "registerEvent"; 72 public static final String ACCESS_FLIGHT_RECORDER = "accessFlightRecorder"; 73 74 public static void checkAccessFlightRecorder() throws SecurityException { 75 SecurityManager sm = System.getSecurityManager(); 76 if (sm != null) { 77 sm.checkPermission(new FlightRecorderPermission(ACCESS_FLIGHT_RECORDER)); 78 } 79 } 80 81 public static void checkRegisterPermission() throws SecurityException { 82 SecurityManager sm = System.getSecurityManager(); 83 if (sm != null) { 84 sm.checkPermission(new FlightRecorderPermission(REGISTER_EVENT)); 85 } 86 } 87 88 private static enum TimespanUnit { 89 NANOSECONDS("ns", 1000), MICROSECONDS("us", 1000), MILLISECONDS("ms", 1000), SECONDS("s", 60), MINUTES("m", 60), HOURS("h", 24), DAYS("d", 7); 90 91 final String text; 92 final long amount; 93 94 TimespanUnit(String unit, long amount) { 95 this.text = unit; 96 this.amount = amount; 97 } 98 } 99 100 public static String formatBytes(long bytes, String separation) { 101 if (bytes < 1024) { 102 return bytes + " bytes"; 103 } 104 int exp = (int) (Math.log(bytes) / Math.log(1024)); 105 char bytePrefix = "kMGTPE".charAt(exp - 1); 106 return String.format("%.1f%s%cB", bytes / Math.pow(1024, exp), separation, bytePrefix); 107 } 108 109 public static String formatTimespan(Duration dValue, String separation) { 110 if (dValue == null) { 111 return "0"; 112 } 113 114 long value = dValue.toNanos(); 115 TimespanUnit result = TimespanUnit.NANOSECONDS; 116 for (TimespanUnit unit : TimespanUnit.values()) { 117 result = unit; 118 long amount = unit.amount; 119 if (result == TimespanUnit.DAYS || value < amount || value % amount != 0) { 120 break; 121 } 122 value /= amount; 123 } 124 return String.format("%d%s%s", value, separation, result.text); 125 } 126 127 public static long parseTimespan(String s) { 128 if (s.endsWith("ns")) { 129 return Long.parseLong(s.substring(0, s.length() - 2).trim()); 130 } 131 if (s.endsWith("us")) { 132 return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 2).trim()), MICROSECONDS); 133 } 134 if (s.endsWith("ms")) { 135 return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 2).trim()), MILLISECONDS); 136 } 137 if (s.endsWith("s")) { 138 return NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS); 139 } 140 if (s.endsWith("m")) { 141 return 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS); 142 } 143 if (s.endsWith("h")) { 144 return 60 * 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS); 145 } 146 if (s.endsWith("d")) { 147 return 24 * 60 * 60 * NANOSECONDS.convert(Long.parseLong(s.substring(0, s.length() - 1).trim()), SECONDS); 148 } 149 150 try { 151 Long.parseLong(s); 152 } catch (NumberFormatException nfe) { 153 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."); 154 } 155 // Only accept values with units 156 throw new NumberFormatException("Timespan + '" + s + "' is missing unit. Valid units are ns, us, s, m, h and d."); 157 } 158 159 /** 160 * Return all annotations as they are visible in the source code 161 * 162 * @param clazz class to return annotations from 163 * 164 * @return list of annotation 165 * 166 */ 167 static List<Annotation> getAnnotations(Class<?> clazz) { 168 List<Annotation> annos = new ArrayList<>(); 169 for (Annotation a : clazz.getAnnotations()) { 170 annos.addAll(getAnnotation(a)); 171 } 172 return annos; 173 } 174 175 private static List<? extends Annotation> getAnnotation(Annotation a) { 176 Class<?> annotated = a.annotationType(); 177 Method valueMethod = getValueMethod(annotated); 178 if (valueMethod != null) { 179 Class<?> returnType = valueMethod.getReturnType(); 180 if (returnType.isArray()) { 181 Class<?> candidate = returnType.getComponentType(); 182 Repeatable r = candidate.getAnnotation(Repeatable.class); 183 if (r != null) { 184 Class<?> repeatClass = r.value(); 185 if (annotated == repeatClass) { 186 return getAnnotationValues(a, valueMethod); 187 } 188 } 189 } 190 } 191 List<Annotation> annos = new ArrayList<>(); 192 annos.add(a); 193 return annos; 194 } 195 196 static boolean isAfter(RecordingState stateToTest, RecordingState b) { 197 return stateToTest.ordinal() > b.ordinal(); 198 } 199 200 static boolean isBefore(RecordingState stateToTest, RecordingState b) { 201 return stateToTest.ordinal() < b.ordinal(); 202 } 203 204 static boolean isState(RecordingState stateToTest, RecordingState... states) { 205 for (RecordingState s : states) { 206 if (s == stateToTest) { 207 return true; 208 } 209 } 210 return false; 211 } 212 213 private static List<Annotation> getAnnotationValues(Annotation a, Method valueMethod) { 214 try { 215 return Arrays.asList((Annotation[]) valueMethod.invoke(a, new Object[0])); 216 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 217 return new ArrayList<>(); 218 } 219 } 220 221 private static Method getValueMethod(Class<?> annotated) { 222 try { 223 return annotated.getMethod("value", new Class<?>[0]); 224 } catch (NoSuchMethodException e) { 225 return null; 226 } 227 } 228 229 public static void touch(Path dumpFile) throws IOException { 230 RandomAccessFile raf = new RandomAccessFile(dumpFile.toFile(), "rw"); 231 raf.close(); 232 } 233 234 public static Class<?> unboxType(Class<?> t) { 235 if (t == Integer.class) { 236 return int.class; 237 } 238 if (t == Long.class) { 239 return long.class; 240 } 241 if (t == Float.class) { 242 return float.class; 243 } 244 if (t == Double.class) { 245 return double.class; 246 } 247 if (t == Byte.class) { 248 return byte.class; 249 } 250 if (t == Short.class) { 251 return short.class; 252 } 253 if (t == Boolean.class) { 254 return boolean.class; 255 } 256 if (t == Character.class) { 257 return char.class; 258 } 259 return t; 260 } 261 262 static long nanosToTicks(long nanos) { 263 return (long) (nanos * JVM.getJVM().getTimeConversionFactor()); 264 } 265 266 static synchronized EventHandler getHandler(Class<? extends Event> eventClass) { 267 Utils.ensureValidEventSubclass(eventClass); 268 try { 269 Field f = eventClass.getDeclaredField(EventInstrumentation.FIELD_EVENT_HANDLER); 270 SecuritySupport.setAccessible(f); 271 return (EventHandler) f.get(null); 272 } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { 273 throw new InternalError("Could not access event handler"); 274 } 275 } 276 277 static synchronized void setHandler(Class<? extends Event> eventClass, EventHandler handler) { 278 Utils.ensureValidEventSubclass(eventClass); 279 try { 280 Field field = eventClass.getDeclaredField(EventInstrumentation.FIELD_EVENT_HANDLER); 281 SecuritySupport.setAccessible(field); 282 field.set(null, handler); 283 } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { 284 throw new InternalError("Could not access event handler"); 285 } 286 } 287 288 public static Map<String, String> sanitizeNullFreeStringMap(Map<String, String> settings) { 289 HashMap<String, String> map = new HashMap<>(settings.size()); 290 for (Map.Entry<String, String> e : settings.entrySet()) { 291 String key = e.getKey(); 292 if (key == null) { 293 throw new NullPointerException("Null key is not allowed in map"); 294 } 295 String value = e.getValue(); 296 if (value == null) { 297 throw new NullPointerException("Null value is not allowed in map"); 298 } 299 map.put(key, value); 300 } 301 return map; 302 } 303 304 public static <T> List<T> sanitizeNullFreeList(List<T> elements, Class<T> clazz) { 305 List<T> sanitized = new ArrayList<>(elements.size()); 306 for (T element : elements) { 307 if (element == null) { 308 throw new NullPointerException("Null is not an allowed element in list"); 309 } 310 if (element.getClass() != clazz) { 311 throw new ClassCastException(); 312 } 313 sanitized.add(element); 314 } 315 return sanitized; 316 } 317 318 static List<Field> getVisibleEventFields(Class<?> clazz) { 319 Utils.ensureValidEventSubclass(clazz); 320 List<Field> fields = new ArrayList<>(); 321 for (Class<?> c = clazz; c != Event.class; c = c.getSuperclass()) { 322 for (Field field : c.getDeclaredFields()) { 323 // skip private field in base classes 324 if (c == clazz || !Modifier.isPrivate(field.getModifiers())) { 325 fields.add(field); 326 } 327 } 328 } 329 return fields; 330 } 331 332 public static void ensureValidEventSubclass(Class<?> eventClass) { 333 if (Event.class.isAssignableFrom(eventClass) && Modifier.isAbstract(eventClass.getModifiers())) { 334 throw new IllegalArgumentException("Abstract event classes are not allowed"); 335 } 336 if (eventClass == Event.class || !Event.class.isAssignableFrom(eventClass)) { 337 throw new IllegalArgumentException("Must be a subclass to " + Event.class.getName()); 338 } 339 } 340 341 public static void writeGeneratedASM(String className, byte[] bytes) { 342 if (SAVE_GENERATED == null) { 343 // We can't calculate value statically because it will force 344 // initialization of SecuritySupport, which cause 345 // UnsatisfiedLinkedError on JDK 8 or non-Oracle JDKs 346 SAVE_GENERATED = SecuritySupport.getBooleanProperty("jfr.save.generated.asm"); 347 } 348 if (SAVE_GENERATED) { 349 try { 350 try (FileOutputStream fos = new FileOutputStream(className + ".class")) { 351 fos.write(bytes); 352 } 353 354 try (FileWriter fw = new FileWriter(className + ".asm"); PrintWriter pw = new PrintWriter(fw)) { 355 ClassReader cr = new ClassReader(bytes); 356 CheckClassAdapter.verify(cr, true, pw); 357 } 358 Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Instrumented code saved to " + className + ".class and .asm"); 359 } catch (IOException e) { 360 Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Could not save instrumented code, for " + className + ".class and .asm"); 361 } 362 } 363 } 364 365 public static void ensureInitialized(Class<? extends Event> eventClass) { 366 SecuritySupport.ensureClassIsInitialized(eventClass); 367 } 368 369 public static Object makePrimitiveArray(String typeName, List<Object> values) { 370 int length = values.size(); 371 switch (typeName) { 372 case "int": 373 int[] ints = new int[length]; 374 for (int i = 0; i < length; i++) { 375 ints[i] = (int) values.get(i); 376 } 377 return ints; 378 case "long": 379 long[] longs = new long[length]; 380 for (int i = 0; i < length; i++) { 381 longs[i] = (long) values.get(i); 382 } 383 return longs; 384 385 case "float": 386 float[] floats = new float[length]; 387 for (int i = 0; i < length; i++) { 388 floats[i] = (float) values.get(i); 389 } 390 return floats; 391 392 case "double": 393 double[] doubles = new double[length]; 394 for (int i = 0; i < length; i++) { 395 doubles[i] = (double) values.get(i); 396 } 397 return doubles; 398 399 case "short": 400 short[] shorts = new short[length]; 401 for (int i = 0; i < length; i++) { 402 shorts[i] = (short) values.get(i); 403 } 404 return shorts; 405 case "char": 406 char[] chars = new char[length]; 407 for (int i = 0; i < length; i++) { 408 chars[i] = (char) values.get(i); 409 } 410 return chars; 411 case "byte": 412 byte[] bytes = new byte[length]; 413 for (int i = 0; i < length; i++) { 414 bytes[i] = (byte) values.get(i); 415 } 416 return bytes; 417 case "boolean": 418 boolean[] booleans = new boolean[length]; 419 for (int i = 0; i < length; i++) { 420 booleans[i] = (boolean) values.get(i); 421 } 422 return booleans; 423 case "java.lang.String": 424 String[] strings = new String[length]; 425 for (int i = 0; i < length; i++) { 426 strings[i] = (String) values.get(i); 427 } 428 return strings; 429 } 430 return null; 431 } 432 433 public static boolean isSettingVisible(Control c, boolean hasEventHook) { 434 if (c instanceof ThresholdSetting) { 435 return !hasEventHook; 436 } 437 if (c instanceof PeriodSetting) { 438 return hasEventHook; 439 } 440 if (c instanceof StackTraceSetting) { 441 return !hasEventHook; 442 } 443 return true; 444 } 445 446 public static boolean isSettingVisible(long typeId, boolean hasEventHook) { 447 if (ThresholdSetting.isType(typeId)) { 448 return !hasEventHook; 449 } 450 if (PeriodSetting.isType(typeId)) { 451 return hasEventHook; 452 } 453 if (StackTraceSetting.isType(typeId)) { 454 return !hasEventHook; 455 } 456 return true; 457 } 458 459 public static Type getValidType(Class<?> type, String name) { 460 Objects.requireNonNull(type, "Null is not a valid type for value descriptor " + name); 461 if (type.isArray()) { 462 type = type.getComponentType(); 463 if (type != String.class && !type.isPrimitive()) { 464 throw new IllegalArgumentException("Only arrays of primitives and Strings are allowed"); 465 } 466 } 467 468 Type knownType = Type.getKnownType(type); 469 if (knownType == null || knownType == Type.STACK_TRACE) { 470 throw new IllegalArgumentException("Only primitive types, java.lang.Thread, java.lang.String and java.lang.Class are allowed for value descriptors. " + type.getName()); 471 } 472 return knownType; 473 } 474 475 public static <T> List<T> smallUnmodifiable(List<T> list) { 476 if (list.isEmpty()) { 477 return Collections.emptyList(); 478 } 479 if (list.size() == 1) { 480 return Collections.singletonList(list.get(0)); 481 } 482 return Collections.unmodifiableList(list); 483 } 484 485 public static void updateSettingPathToGcRoots(Map<String, String> settings, Boolean pathToGcRoots) { 486 if (pathToGcRoots != null) { 487 settings.put("com.oracle.jdk.OldObjectSample#cutoff", pathToGcRoots ? "infinity" : "0 ns" ); 488 } 489 } 490 }