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