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; 27 28 import static jdk.jfr.internal.LogLevel.DEBUG; 29 import static jdk.jfr.internal.LogLevel.INFO; 30 import static jdk.jfr.internal.LogTag.JFR; 31 32 import java.security.AccessControlContext; 33 import java.security.AccessController; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Objects; 39 40 import jdk.jfr.internal.JVM; 41 import jdk.jfr.internal.JVMSupport; 42 import jdk.jfr.internal.LogLevel; 43 import jdk.jfr.internal.Logger; 44 import jdk.jfr.internal.MetadataRepository; 45 import jdk.jfr.internal.Options; 46 import jdk.jfr.internal.PlatformRecorder; 47 import jdk.jfr.internal.PlatformRecording; 48 import jdk.jfr.internal.Repository; 49 import jdk.jfr.internal.RequestEngine; 50 import jdk.jfr.internal.Utils; 51 52 /** 53 * Class for accessing, controlling, and managing Flight Recorder. 54 * <p> 55 * This class provides the methods necessary for creating, starting, stopping, 56 * and destroying recordings. 57 * 58 * @since 9 59 */ 60 public final class FlightRecorder { 61 private static volatile FlightRecorder platformRecorder; 62 private static volatile boolean initialized; 63 private final PlatformRecorder internal; 64 65 private FlightRecorder(PlatformRecorder internal) { 66 this.internal = internal; 67 } 68 69 /** 70 * Returns an immutable list of the available recordings. 71 * <p> 72 * A recording becomes available when it is created. It becomes unavailable when it 73 * is in the {@code CLOSED} state, typically after a call to 74 * {@link Recording#close()}. 75 * 76 * @return a list of recordings, not {@code null} 77 */ 78 public List<Recording> getRecordings() { 79 List<Recording> recs = new ArrayList<>(); 80 for (PlatformRecording internal : internal.getRecordings()) { 81 recs.add(internal.getRecording()); 82 } 83 return Collections.unmodifiableList(recs); 84 } 85 86 /** 87 * Creates a snapshot of all available recorded data. 88 * <p> 89 * A snapshot is a synthesized recording in a stopped state. If no data is 90 * available, a recording with size {@code 0} is returned. 91 * <p> 92 * A snapshot provides stable access to data for later operations (for example, 93 * operations to change the interval or to reduce the data size). 94 * <p> 95 * Example, 96 * 97 * <pre> 98 * <code> 99 * try (Recording snapshot = FlightRecorder.getFlightRecorder().takeSnapshot()) { 100 * if (snapshot.getSize() > 0) { 101 * snapshot.setMaxSize(100_000_000); 102 * snapshot.setMaxAge(Duration.ofMinutes(5)); 103 * snapshot.dump(Paths.get("snapshot.jfr")); 104 * } 105 * } 106 * </code> 107 * </pre> 108 * 109 * The caller must close the recording when access to the data is no longer 110 * needed. 111 * 112 * @return a snapshot of all available recording data, not {@code null} 113 */ 114 public Recording takeSnapshot() { 115 return internal.newSnapshot(); 116 } 117 118 /** 119 * Registers an event class. 120 * <p> 121 * If the event class is already registered, then the invocation of this method is 122 * ignored. 123 * 124 * @param eventClass the event class to register, not {@code null} 125 * 126 * @throws IllegalArgumentException if class is abstract or not a subclass 127 * of {@link Event} 128 * @throws SecurityException if a security manager exists and the caller 129 * does not have {@code FlightRecorderPermission("registerEvent")} 130 */ 131 public static void register(Class<? extends Event> eventClass) { 132 Objects.requireNonNull(eventClass); 133 if (JVMSupport.isNotAvailable()) { 134 return; 135 } 136 Utils.ensureValidEventSubclass(eventClass); 137 MetadataRepository.getInstance().register(eventClass); 138 } 139 140 /** 141 * Unregisters an event class. 142 * <p> 143 * If the event class is not registered, then the invocation of this method is 144 * ignored. 145 * 146 * @param eventClass the event class to unregistered, not {@code null} 147 * @throws IllegalArgumentException if a class is abstract or not a subclass 148 * of {@link Event} 149 * 150 * @throws SecurityException if a security manager exists and the caller 151 * does not have {@code FlightRecorderPermission("registerEvent")} 152 */ 153 public static void unregister(Class<? extends Event> eventClass) { 154 Objects.requireNonNull(eventClass); 155 if (JVMSupport.isNotAvailable()) { 156 return; 157 } 158 Utils.ensureValidEventSubclass(eventClass); 159 MetadataRepository.getInstance().unregister(eventClass); 160 } 161 162 /** 163 * Returns the Flight Recorder for the platform. 164 * 165 * @return a Flight Recorder instance, not {@code null} 166 * 167 * @throws IllegalStateException if the platform Flight Recorder couldn't be 168 * created (for example, if the file repository can't be created or 169 * accessed) 170 * 171 * @throws SecurityException if a security manager exists and the caller does 172 * not have {@code FlightRecorderPermission("accessFlightRecorder")} 173 */ 174 public static FlightRecorder getFlightRecorder() throws IllegalStateException, SecurityException { 175 synchronized (PlatformRecorder.class) { 176 Utils.checkAccessFlightRecorder(); 177 JVMSupport.ensureWithIllegalStateException(); 178 if (platformRecorder == null) { 179 try { 180 platformRecorder = new FlightRecorder(new PlatformRecorder()); 181 } catch (IllegalStateException ise) { 182 throw ise; 183 } catch (Exception e) { 184 throw new IllegalStateException("Can't create Flight Recorder. " + e.getMessage(), e); 185 } 186 // Must be in synchronized block to prevent instance leaking out 187 // before initialization is done 188 initialized = true; 189 Logger.log(JFR, INFO, "Flight Recorder initialized"); 190 Logger.log(JFR, DEBUG, "maxchunksize: " + Options.getMaxChunkSize()+ " bytes"); 191 Logger.log(JFR, DEBUG, "memorysize: " + Options.getMemorySize()+ " bytes"); 192 Logger.log(JFR, DEBUG, "globalbuffersize: " + Options.getGlobalBufferSize()+ " bytes"); 193 Logger.log(JFR, DEBUG, "globalbuffercount: " + Options.getGlobalBufferCount()); 194 Logger.log(JFR, DEBUG, "dumppath: " + Options.getDumpPath()); 195 Logger.log(JFR, DEBUG, "samplethreads: " + Options.getSampleThreads()); 196 Logger.log(JFR, DEBUG, "stackdepth: " + Options.getStackDepth()); 197 Logger.log(JFR, DEBUG, "threadbuffersize: " + Options.getThreadBufferSize()); 198 Logger.log(JFR, LogLevel.INFO, "Created repository " + Repository.getRepository().getRepositoryPath().toString()); 199 PlatformRecorder.notifyRecorderInitialized(platformRecorder); 200 } 201 } 202 return platformRecorder; 203 } 204 205 /** 206 * Adds a callback for a periodic event. 207 * <p> 208 * The implementation of the hook should return as soon as possible, to 209 * avoid blocking other Flight Recorder operations. The hook should emit 210 * one or more events of the specified type. When a hook is added, the 211 * interval at which the call is invoked is configurable using the 212 * {@code "period"} setting. 213 * 214 * @param eventClass the class that the hook should run for, not {@code null} 215 * @param hook the hook, not {@code null} 216 * @throws IllegalArgumentException if a class is not a subclass of 217 * {@link Event}, is abstract, or the hook is already added 218 * @throws IllegalStateException if the event class has the 219 * {@code Registered(false)} annotation and is not registered manually 220 * @throws SecurityException if a security manager exists and the caller 221 * does not have {@code FlightRecorderPermission("registerEvent")} 222 */ 223 public static void addPeriodicEvent(Class<? extends Event> eventClass, Runnable hook) throws SecurityException { 224 Objects.requireNonNull(eventClass); 225 Objects.requireNonNull(hook); 226 if (JVMSupport.isNotAvailable()) { 227 return; 228 } 229 230 Utils.ensureValidEventSubclass(eventClass); 231 Utils.checkRegisterPermission(); 232 AccessControlContext acc = AccessController.getContext(); 233 RequestEngine.addHook(acc, EventType.getEventType(eventClass).getPlatformEventType(), hook); 234 } 235 236 /** 237 * Removes a callback hook for a periodic event. 238 * 239 * @param hook the hook to remove, not {@code null} 240 * @return {@code true} if hook is removed, {@code false} otherwise 241 * @throws SecurityException if a security manager exists and the caller 242 * does not have {@code FlightRecorderPermission("registerEvent")} 243 */ 244 public static boolean removePeriodicEvent(Runnable hook) throws SecurityException { 245 Objects.requireNonNull(hook); 246 Utils.checkRegisterPermission(); 247 if (JVMSupport.isNotAvailable()) { 248 return false; 249 } 250 return RequestEngine.removeHook(hook); 251 } 252 253 /** 254 * Returns an immutable list that contains all currently registered events. 255 * 256 * By default, events are registered when they are first used, typically 257 * when an event object is allocated. To ensure an event is visible early, 258 * registration can be triggered by invoking the 259 * {@link FlightRecorder#register(Class)} method. 260 * 261 * @return list of events, not {@code null} 262 */ 263 public List<EventType> getEventTypes() { 264 return Collections.unmodifiableList(MetadataRepository.getInstance().getRegisteredEventTypes()); 265 } 266 267 /** 268 * Adds a recorder listener and captures the {@code AccessControlContext} to 269 * use when invoking the listener. 270 * <p> 271 * If Flight Recorder is already initialized when the listener is added, the the method 272 * {@link FlightRecorderListener#recorderInitialized(FlightRecorder)} method is 273 * invoked before returning from this method. 274 * 275 * @param changeListener the listener to add, not {@code null} 276 * 277 * @throws SecurityException if a security manager exists and the caller 278 * does not have 279 * {@code FlightRecorderPermission("accessFlightRecorder")} 280 */ 281 public static void addListener(FlightRecorderListener changeListener) { 282 Objects.requireNonNull(changeListener); 283 Utils.checkAccessFlightRecorder(); 284 if (JVMSupport.isNotAvailable()) { 285 return; 286 } 287 PlatformRecorder.addListener(changeListener); 288 } 289 290 /** 291 * Removes a recorder listener. 292 * <p> 293 * If the same listener is added multiple times, only one instance is 294 * removed. 295 * 296 * @param changeListener listener to remove, not {@code null} 297 * 298 * @throws SecurityException if a security manager exists and the caller 299 * does not have 300 * {@code FlightRecorderPermission("accessFlightRecorder")} 301 * 302 * @return {@code true}, if the listener could be removed, {@code false} 303 * otherwise 304 */ 305 public static boolean removeListener(FlightRecorderListener changeListener) { 306 Objects.requireNonNull(changeListener); 307 Utils.checkAccessFlightRecorder(); 308 if (JVMSupport.isNotAvailable()) { 309 return false; 310 } 311 312 return PlatformRecorder.removeListener(changeListener); 313 } 314 315 /** 316 * Returns {@code true} if the Java Virtual Machine (JVM) has Flight Recorder capabilities. 317 * <p> 318 * This method can quickly check whether Flight Recorder can be 319 * initialized, without actually doing the initialization work. The value may 320 * change during runtime and it is not safe to cache it. 321 * 322 * @return {@code true}, if Flight Recorder is available, {@code false} 323 * otherwise 324 * 325 * @see FlightRecorderListener for callback when Flight Recorder is 326 * initialized 327 */ 328 public static boolean isAvailable() { 329 if (JVMSupport.isNotAvailable()) { 330 return false; 331 } 332 return JVM.getJVM().isAvailable(); 333 } 334 335 /** 336 * Returns {@code true} if Flight Recorder is initialized. 337 * 338 * @return {@code true}, if Flight Recorder is initialized, 339 * {@code false} otherwise 340 * 341 * @see FlightRecorderListener for callback when Flight Recorder is 342 * initialized 343 */ 344 public static boolean isInitialized() { 345 return initialized; 346 } 347 348 // package private 349 PlatformRecording newInternalRecording(Map<String, String> settings) { 350 return internal.newRecording(settings); 351 } 352 353 }