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