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() &gt; 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 }