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